Merge branch 'feature/smtp-templates' into configuration-management
This commit is contained in:
commit
0e1a6b14db
|
@ -4,11 +4,32 @@ const {
|
||||||
StaticDatabases,
|
StaticDatabases,
|
||||||
} = require("@budibase/auth").db
|
} = require("@budibase/auth").db
|
||||||
const { CouchDB } = require("../../../db")
|
const { CouchDB } = require("../../../db")
|
||||||
const { TemplatePurposePretty } = require("../../../constants")
|
const { TemplatePurposePretty, TemplateTypes, EmailTemplatePurpose, TemplatePurpose } = require("../../../constants")
|
||||||
|
const { getTemplateByPurpose } = require("../../../constants/templates")
|
||||||
|
|
||||||
const GLOBAL_DB = StaticDatabases.GLOBAL.name
|
const GLOBAL_DB = StaticDatabases.GLOBAL.name
|
||||||
const GLOBAL_OWNER = "global"
|
const GLOBAL_OWNER = "global"
|
||||||
|
|
||||||
|
function addBaseTemplates(templates, type = null) {
|
||||||
|
let purposeList
|
||||||
|
switch (type) {
|
||||||
|
case TemplateTypes.EMAIL:
|
||||||
|
purposeList = Object.values(EmailTemplatePurpose)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
purposeList = Object.values(TemplatePurpose)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
for (let purpose of purposeList) {
|
||||||
|
// check if a template exists already for purpose
|
||||||
|
if (templates.find(template => template.purpose === purpose)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
templates.push(getTemplateByPurpose(purpose))
|
||||||
|
}
|
||||||
|
return templates
|
||||||
|
}
|
||||||
|
|
||||||
async function getTemplates({ ownerId, type, id } = {}) {
|
async function getTemplates({ ownerId, type, id } = {}) {
|
||||||
const db = new CouchDB(GLOBAL_DB)
|
const db = new CouchDB(GLOBAL_DB)
|
||||||
const response = await db.allDocs(
|
const response = await db.allDocs(
|
||||||
|
@ -17,10 +38,15 @@ async function getTemplates({ ownerId, type, id } = {}) {
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
let templates = response.rows.map(row => row.doc)
|
let templates = response.rows.map(row => row.doc)
|
||||||
|
// should only be one template with ID
|
||||||
|
if (id) {
|
||||||
|
return templates[0]
|
||||||
|
}
|
||||||
if (type) {
|
if (type) {
|
||||||
templates = templates.filter(template => template.type === type)
|
templates = templates.filter(template => template.type === type)
|
||||||
}
|
}
|
||||||
return templates
|
|
||||||
|
return addBaseTemplates(templates, type)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.save = async ctx => {
|
exports.save = async ctx => {
|
||||||
|
@ -73,7 +99,8 @@ exports.find = async ctx => {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.destroy = async ctx => {
|
exports.destroy = async ctx => {
|
||||||
// TODO
|
const db = new CouchDB(GLOBAL_DB)
|
||||||
// const db = new CouchDB(GLOBAL_DB)
|
await db.remove(ctx.params.id, ctx.params.rev)
|
||||||
ctx.body = {}
|
ctx.message = `Template ${ctx.params.id} deleted.`
|
||||||
|
ctx.status = 200
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,5 +25,5 @@ router
|
||||||
.get("/api/admin/template", controller.fetch)
|
.get("/api/admin/template", controller.fetch)
|
||||||
.get("/api/admin/template/:type", controller.fetchByType)
|
.get("/api/admin/template/:type", controller.fetchByType)
|
||||||
.get("/api/admin/template/:ownerId", controller.fetchByOwner)
|
.get("/api/admin/template/:ownerId", controller.fetchByOwner)
|
||||||
.delete("/api/admin/template/:id", controller.destroy)
|
|
||||||
.get("/api/admin/template/:id", controller.find)
|
.get("/api/admin/template/:id", controller.find)
|
||||||
|
.delete("/api/admin/template/:id/:rev", controller.destroy)
|
||||||
|
|
|
@ -14,27 +14,52 @@ exports.Configs = {
|
||||||
GOOGLE: "google",
|
GOOGLE: "google",
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.TemplateTypes = {
|
const TemplateTypes = {
|
||||||
EMAIL: "email",
|
EMAIL: "email",
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.TemplatePurpose = {
|
const EmailTemplatePurpose = {
|
||||||
|
HEADER: "header",
|
||||||
|
FOOTER: "footer",
|
||||||
|
STYLES: "styles",
|
||||||
PASSWORD_RECOVERY: "password_recovery",
|
PASSWORD_RECOVERY: "password_recovery",
|
||||||
INVITATION: "invitation",
|
INVITATION: "invitation",
|
||||||
CUSTOM: "custom",
|
CUSTOM: "custom",
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.TemplatePurposePretty = [
|
const TemplatePurposePretty = {
|
||||||
{
|
[TemplateTypes.EMAIL]: [
|
||||||
name: "Password Recovery",
|
{
|
||||||
value: exports.TemplatePurpose.PASSWORD_RECOVERY,
|
name: "Styling",
|
||||||
},
|
value: EmailTemplatePurpose.STYLES,
|
||||||
{
|
},
|
||||||
name: "New User Invitation",
|
{
|
||||||
value: exports.TemplatePurpose.INVITATION,
|
name: "Header",
|
||||||
},
|
value: EmailTemplatePurpose.HEADER,
|
||||||
{
|
},
|
||||||
name: "Custom",
|
{
|
||||||
value: exports.TemplatePurpose.CUSTOM,
|
name: "Footer",
|
||||||
},
|
value: EmailTemplatePurpose.FOOTER,
|
||||||
]
|
},
|
||||||
|
{
|
||||||
|
name: "Password Recovery",
|
||||||
|
value: EmailTemplatePurpose.PASSWORD_RECOVERY
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "New User Invitation",
|
||||||
|
value: EmailTemplatePurpose.INVITATION,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Custom",
|
||||||
|
value: EmailTemplatePurpose.CUSTOM,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
// all purpose combined
|
||||||
|
exports.TemplatePurpose = {
|
||||||
|
...EmailTemplatePurpose,
|
||||||
|
}
|
||||||
|
exports.TemplateTypes = TemplateTypes
|
||||||
|
exports.EmailTemplatePurpose = EmailTemplatePurpose
|
||||||
|
exports.TemplatePurposePretty = TemplatePurposePretty
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
<table align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="margin: auto;">
|
||||||
|
<tbody><tr>
|
||||||
|
<td valign="middle" class="bg_black footer email-section">
|
||||||
|
<table>
|
||||||
|
<tbody><tr>
|
||||||
|
<td valign="top" width="50%" style="padding-top: 20px;">
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||||
|
<tbody><tr>
|
||||||
|
<td style="text-align: left; padding-right: 10px;">
|
||||||
|
<h3 class="heading">{{ company }}</h3>
|
||||||
|
<p>Company information.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody></table>
|
||||||
|
</td>
|
||||||
|
<td valign="top" width="50%" style="padding-top: 20px;">
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||||
|
<tbody><tr>
|
||||||
|
<td style="text-align: left; padding-left: 10px;">
|
||||||
|
<h3 class="heading">Budibase Portal</h3>
|
||||||
|
{{ portalUrl }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody></table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody></table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td valign="middle" class="bg_black footer email-section">
|
||||||
|
<table>
|
||||||
|
<tbody><tr>
|
||||||
|
<td valign="top" width="50%">
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||||
|
<tbody><tr>
|
||||||
|
<td style="text-align: left; padding-right: 10px;">
|
||||||
|
<p>© 2021 Restobar. All Rights Reserved</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody></table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody></table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
|
@ -0,0 +1,36 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="x-apple-disable-message-reformatting">
|
||||||
|
<style>
|
||||||
|
{{ styles }}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<table align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="margin: auto;">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="bg_white logo" style="padding: 1em 2.5em; text-align: center">
|
||||||
|
<h1><a href="{{ companyUrl }}">{{ company }}</a></h1>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="bg_white logo" style="padding: 1em 2.5em; text-align: center">
|
||||||
|
{{ body }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="bg_white logo" style="padding: 1em 2.5em; text-align: center">
|
||||||
|
{{ footer }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</body>
|
|
@ -0,0 +1,19 @@
|
||||||
|
const { readStaticFile } = require("../../utilities/fileSystem")
|
||||||
|
const { EmailTemplatePurpose } = require("../index")
|
||||||
|
const { join } = require("path")
|
||||||
|
|
||||||
|
const TEMPLATE_PATH = join(__dirname, "..", "constants", "templates")
|
||||||
|
|
||||||
|
exports.EmailTemplates = {
|
||||||
|
[EmailTemplatePurpose.PASSWORD_RECOVERY]: readStaticFile(join(TEMPLATE_PATH, "passwordRecovery.html")),
|
||||||
|
[EmailTemplatePurpose.INVITATION]: readStaticFile(join(TEMPLATE_PATH, "invitation.html")),
|
||||||
|
[EmailTemplatePurpose.HEADER]: readStaticFile(join(TEMPLATE_PATH, "header.html")),
|
||||||
|
[EmailTemplatePurpose.FOOTER]: readStaticFile(join(TEMPLATE_PATH, "footer.html")),
|
||||||
|
[EmailTemplatePurpose.STYLES]: readStaticFile(join(TEMPLATE_PATH, "style.css")),
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.getTemplateByPurpose = purpose => {
|
||||||
|
if (exports.EmailTemplates[purpose]) {
|
||||||
|
return exports.EmailTemplates[purpose]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="bg_dark email-section" style="text-align:center;">
|
||||||
|
<div class="heading-section heading-section-white">
|
||||||
|
<span class="subheading">Budibase Invitation</span>
|
||||||
|
<h2>You've been invited to join {{ company }}'s Budibase platform!</h2>
|
||||||
|
<p>Please follow the below link to finish your registration.</p>
|
||||||
|
<p><a href="{{ registrationUrl }}" class="btn btn-primary">Finish registration</a></p>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
|
@ -0,0 +1,15 @@
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="bg_dark email-section" style="text-align:center;">
|
||||||
|
<div class="heading-section heading-section-white">
|
||||||
|
<span class="subheading">Budibase Password reset</span>
|
||||||
|
<h2>Please follow the below link to reset your password.</h2>
|
||||||
|
<p><a href="{{ resetUrl }}" class="btn btn-primary">Reset password</a></p>
|
||||||
|
<p>This password reset was required for {{ user }} if you did not
|
||||||
|
request this then please contact your administrator.</p>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
|
@ -0,0 +1,269 @@
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Playfair Display';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url(/fonts.gstatic.com/s/playfairdisplay/v22/nuFkD-vYSZviVYUb_rj3ij__anPXDTnohkk72xU.woff2) format('woff2');
|
||||||
|
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||||
|
}
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
margin: 0 auto !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
height: 100% !important;
|
||||||
|
width: 100% !important;
|
||||||
|
background: #f1f1f1;
|
||||||
|
}
|
||||||
|
* {
|
||||||
|
-ms-text-size-adjust: 100%;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
}
|
||||||
|
div[style*="margin: 16px 0"] {
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
table,
|
||||||
|
td {
|
||||||
|
mso-table-lspace: 0pt !important;
|
||||||
|
mso-table-rspace: 0pt !important;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
border-spacing: 0 !important;
|
||||||
|
border-collapse: collapse !important;
|
||||||
|
table-layout: fixed !important;
|
||||||
|
margin: 0 auto !important;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
-ms-interpolation-mode:bicubic;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
*[x-apple-data-detectors], /* iOS */
|
||||||
|
.unstyle-auto-detected-links *,
|
||||||
|
.aBn {
|
||||||
|
border-bottom: 0 !important;
|
||||||
|
cursor: default !important;
|
||||||
|
color: inherit !important;
|
||||||
|
text-decoration: none !important;
|
||||||
|
font-size: inherit !important;
|
||||||
|
font-family: inherit !important;
|
||||||
|
font-weight: inherit !important;
|
||||||
|
line-height: inherit !important;
|
||||||
|
}
|
||||||
|
.a6S {
|
||||||
|
display: none !important;
|
||||||
|
opacity: 0.01 !important;
|
||||||
|
}
|
||||||
|
.im {
|
||||||
|
color: inherit !important;
|
||||||
|
}
|
||||||
|
img.g-img + div {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
@media only screen and (min-device-width: 320px) and (max-device-width: 374px) {
|
||||||
|
u ~ div .email-container {
|
||||||
|
min-width: 320px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media only screen and (min-device-width: 375px) and (max-device-width: 413px) {
|
||||||
|
u ~ div .email-container {
|
||||||
|
min-width: 375px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media only screen and (min-device-width: 414px) {
|
||||||
|
u ~ div .email-container {
|
||||||
|
min-width: 414px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.primary{
|
||||||
|
background: #f3a333;
|
||||||
|
}
|
||||||
|
.bg_white{
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
.bg_light{
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
.bg_black{
|
||||||
|
background: #000000;
|
||||||
|
}
|
||||||
|
.bg_dark{
|
||||||
|
background: rgba(0,0,0,.8);
|
||||||
|
}
|
||||||
|
.email-section{
|
||||||
|
padding:2.5em;
|
||||||
|
}
|
||||||
|
.btn{
|
||||||
|
padding: 10px 15px;
|
||||||
|
}
|
||||||
|
.btn.btn-primary{
|
||||||
|
border-radius: 30px;
|
||||||
|
background: #f3a333;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
h1,h2,h3,h4,h5,h6{
|
||||||
|
font-family: 'Playfair Display', serif;
|
||||||
|
color: #000000;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
body{
|
||||||
|
font-family: 'Montserrat', sans-serif;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1.8;
|
||||||
|
color: rgba(0,0,0,.4);
|
||||||
|
}
|
||||||
|
a{
|
||||||
|
color: #f3a333;
|
||||||
|
}
|
||||||
|
table{
|
||||||
|
}
|
||||||
|
.logo h1{
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.logo h1 a{
|
||||||
|
color: #000;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-family: 'Montserrat', sans-serif;
|
||||||
|
}
|
||||||
|
.hero{
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.hero img{
|
||||||
|
|
||||||
|
}
|
||||||
|
.hero .text{
|
||||||
|
color: rgba(255,255,255,.8);
|
||||||
|
}
|
||||||
|
.hero .text h2{
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 30px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.heading-section{
|
||||||
|
}
|
||||||
|
.heading-section h2{
|
||||||
|
color: #000000;
|
||||||
|
font-size: 28px;
|
||||||
|
margin-top: 0;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
.heading-section .subheading{
|
||||||
|
margin-bottom: 20px !important;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 13px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
color: rgba(0,0,0,.4);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.heading-section .subheading::after{
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: -10px;
|
||||||
|
content: '';
|
||||||
|
width: 100%;
|
||||||
|
height: 2px;
|
||||||
|
background: #f3a333;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.heading-section-white{
|
||||||
|
color: rgba(255,255,255,.8);
|
||||||
|
}
|
||||||
|
.heading-section-white h2{
|
||||||
|
font-size: 28px;
|
||||||
|
line-height: 1;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
.heading-section-white h2{
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
.heading-section-white .subheading{
|
||||||
|
margin-bottom: 0;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 13px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
color: rgba(255,255,255,.4);
|
||||||
|
}
|
||||||
|
.icon{
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.icon img{
|
||||||
|
}
|
||||||
|
.text-services{
|
||||||
|
padding: 10px 10px 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.text-services h3{
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
.text-services .meta{
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.img{
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.img .icon{
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
margin-top: -25px;
|
||||||
|
}
|
||||||
|
.img .icon a{
|
||||||
|
display: block;
|
||||||
|
width: 60px;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -25px;
|
||||||
|
}
|
||||||
|
.counter-text{
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.counter-text .num{
|
||||||
|
display: block;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 34px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.counter-text .name{
|
||||||
|
display: block;
|
||||||
|
color: rgba(255,255,255,.9);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
.footer{
|
||||||
|
color: rgba(255,255,255,.5);
|
||||||
|
}
|
||||||
|
.footer .heading{
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
.footer ul{
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.footer ul li{
|
||||||
|
list-style: none;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.footer ul li a{
|
||||||
|
color: rgba(255,255,255,1);
|
||||||
|
}
|
||||||
|
@media screen and (max-width: 500px) {
|
||||||
|
.icon{
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.text-services{
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 20px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
const { readFileSync } = require("fs")
|
||||||
|
|
||||||
|
exports.readStaticFile = path => {
|
||||||
|
return readFileSync(path, "utf-8")
|
||||||
|
}
|
Loading…
Reference in New Issue