@clink/emails 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/index.js +60 -0
- package/package.json +19 -0
- package/src/build-emails.ts +114 -0
- package/src/index.ts +92 -0
- package/src/layouts/base.mjml +132 -0
- package/src/templates/supplier/order-received/da.json +10 -0
- package/src/templates/supplier/order-received/en.json +10 -0
- package/src/templates/supplier/order-received/template.mjml +81 -0
- package/tsconfig.json +13 -0
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# emails
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.listTemplates = listTemplates;
|
|
7
|
+
exports.getEmailTemplate = getEmailTemplate;
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const templatesRoot = path_1.default.join(__dirname, 'templates');
|
|
11
|
+
const htmlRoot = path_1.default.join(__dirname, 'html');
|
|
12
|
+
function getDirectories(root) {
|
|
13
|
+
if (!fs_1.default.existsSync(root))
|
|
14
|
+
return [];
|
|
15
|
+
return fs_1.default
|
|
16
|
+
.readdirSync(root, { withFileTypes: true })
|
|
17
|
+
.filter(d => d.isDirectory())
|
|
18
|
+
.map(d => d.name);
|
|
19
|
+
}
|
|
20
|
+
function getLocalesForTemplate(audience, template) {
|
|
21
|
+
const dir = path_1.default.join(templatesRoot, audience, template);
|
|
22
|
+
if (!fs_1.default.existsSync(dir))
|
|
23
|
+
return [];
|
|
24
|
+
return fs_1.default
|
|
25
|
+
.readdirSync(dir, { withFileTypes: true })
|
|
26
|
+
.filter(f => f.isFile() && f.name.endsWith('.json'))
|
|
27
|
+
.map(f => f.name.replace(/\.json$/, ''));
|
|
28
|
+
}
|
|
29
|
+
function listTemplates() {
|
|
30
|
+
const result = [];
|
|
31
|
+
const audiences = getDirectories(templatesRoot);
|
|
32
|
+
for (const audience of audiences) {
|
|
33
|
+
const audienceDir = path_1.default.join(templatesRoot, audience);
|
|
34
|
+
const templates = getDirectories(audienceDir);
|
|
35
|
+
for (const template of templates) {
|
|
36
|
+
const locales = getLocalesForTemplate(audience, template);
|
|
37
|
+
result.push({ audience, template, locales });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
function getEmailTemplate(audience, template, locale) {
|
|
43
|
+
const translationsPath = path_1.default.join(templatesRoot, audience, template, `${locale}.json`);
|
|
44
|
+
const htmlPath = path_1.default.join(htmlRoot, audience, template, `${locale}.html`);
|
|
45
|
+
if (!fs_1.default.existsSync(translationsPath)) {
|
|
46
|
+
throw new Error(`Missing translations for audience="${audience}" template="${template}" locale="${locale}"`);
|
|
47
|
+
}
|
|
48
|
+
if (!fs_1.default.existsSync(htmlPath)) {
|
|
49
|
+
throw new Error(`Missing HTML for audience="${audience}" template="${template}" locale="${locale}". Did you run the build step?`);
|
|
50
|
+
}
|
|
51
|
+
const translations = JSON.parse(fs_1.default.readFileSync(translationsPath, 'utf8'));
|
|
52
|
+
const html = fs_1.default.readFileSync(htmlPath, 'utf8');
|
|
53
|
+
if (typeof translations.subject !== 'string') {
|
|
54
|
+
throw new Error(`Translations for audience="${audience}" template="${template}" locale="${locale}" do not contain a "subject" key`);
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
subject: translations.subject,
|
|
58
|
+
html,
|
|
59
|
+
};
|
|
60
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@clink/emails",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"types": "dist/index.d.ts",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "tsc && node dist/build-emails.js",
|
|
8
|
+
"prepublishOnly": "yarn build"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@types/mjml": "^4.7.4",
|
|
12
|
+
"mjml": "^4.2.1"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@types/node": "^24.10.1",
|
|
16
|
+
"ts-node": "^10.9.2",
|
|
17
|
+
"typescript": "^5.9.3"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import mjml2html from 'mjml'
|
|
4
|
+
|
|
5
|
+
const repoRoot = path.join(__dirname, '..')
|
|
6
|
+
const templatesSrcRoot = path.join(repoRoot, 'src', 'templates')
|
|
7
|
+
const templatesRoot = path.join(__dirname, 'templates')
|
|
8
|
+
const htmlRoot = path.join(__dirname, 'html')
|
|
9
|
+
|
|
10
|
+
function ensureDir(dir: string) {
|
|
11
|
+
if (!fs.existsSync(dir)) {
|
|
12
|
+
fs.mkdirSync(dir, {recursive: true})
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function copyDir(src: string, dest: string) {
|
|
17
|
+
if (!fs.existsSync(src)) return
|
|
18
|
+
ensureDir(dest)
|
|
19
|
+
const entries = fs.readdirSync(src, {withFileTypes: true})
|
|
20
|
+
for (const entry of entries) {
|
|
21
|
+
const srcPath = path.join(src, entry.name)
|
|
22
|
+
const destPath = path.join(dest, entry.name)
|
|
23
|
+
if (entry.isDirectory()) {
|
|
24
|
+
copyDir(srcPath, destPath)
|
|
25
|
+
} else {
|
|
26
|
+
fs.copyFileSync(srcPath, destPath)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function read(file: string) {
|
|
32
|
+
return fs.readFileSync(file, 'utf8')
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function write(file: string, content: string) {
|
|
36
|
+
fs.mkdirSync(path.dirname(file), {recursive: true})
|
|
37
|
+
fs.writeFileSync(file, content)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function replaceLocaleTokens(mjml: string, dict: Record<string, string>) {
|
|
41
|
+
let output = mjml
|
|
42
|
+
for (const [key, value] of Object.entries(dict)) {
|
|
43
|
+
output = output.split(`[[${key}]]`).join(value)
|
|
44
|
+
}
|
|
45
|
+
return output
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function getDirectories(root: string) {
|
|
49
|
+
if (!fs.existsSync(root)) return []
|
|
50
|
+
return fs
|
|
51
|
+
.readdirSync(root, {withFileTypes: true})
|
|
52
|
+
.filter(d => d.isDirectory())
|
|
53
|
+
.map(d => d.name)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function getLocaleFiles(templateDir: string) {
|
|
57
|
+
if (!fs.existsSync(templateDir)) return []
|
|
58
|
+
return fs
|
|
59
|
+
.readdirSync(templateDir, {withFileTypes: true})
|
|
60
|
+
.filter(f => f.isFile() && f.name.endsWith('.json'))
|
|
61
|
+
.map(f => f.name)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function buildTemplate(
|
|
65
|
+
audience: string,
|
|
66
|
+
templateName: string,
|
|
67
|
+
localeFile: string,
|
|
68
|
+
) {
|
|
69
|
+
const locale = localeFile.replace(/\.json$/, '')
|
|
70
|
+
const templateDir = path.join(templatesRoot, audience, templateName)
|
|
71
|
+
const contentPath = path.join(templateDir, 'template.mjml')
|
|
72
|
+
const translationsPath = path.join(templateDir, localeFile)
|
|
73
|
+
if (!fs.existsSync(contentPath)) return
|
|
74
|
+
|
|
75
|
+
const baseTemplatePath = path.join(__dirname, 'layouts', 'base.mjml')
|
|
76
|
+
const baseTemplate = read(baseTemplatePath)
|
|
77
|
+
const content = read(contentPath)
|
|
78
|
+
const translations = JSON.parse(read(translationsPath)) as Record<
|
|
79
|
+
string,
|
|
80
|
+
string
|
|
81
|
+
>
|
|
82
|
+
|
|
83
|
+
const localizedContent = replaceLocaleTokens(content, translations)
|
|
84
|
+
|
|
85
|
+
let mjmlSource = baseTemplate
|
|
86
|
+
mjmlSource = mjmlSource.replace('%CONTENT%', localizedContent)
|
|
87
|
+
mjmlSource = replaceLocaleTokens(mjmlSource, translations)
|
|
88
|
+
|
|
89
|
+
const {html} = mjml2html(mjmlSource, {minify: true})
|
|
90
|
+
const outPath = path.join(htmlRoot, audience, templateName, `${locale}.html`)
|
|
91
|
+
write(outPath, html)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function buildAll() {
|
|
95
|
+
copyDir(templatesSrcRoot, templatesRoot)
|
|
96
|
+
const layoutsSrcRoot = path.join(repoRoot, 'src', 'layouts')
|
|
97
|
+
const layoutsRoot = path.join(__dirname, 'layouts')
|
|
98
|
+
copyDir(layoutsSrcRoot, layoutsRoot)
|
|
99
|
+
|
|
100
|
+
const audiences = getDirectories(templatesRoot)
|
|
101
|
+
for (const audience of audiences) {
|
|
102
|
+
const audienceDir = path.join(templatesRoot, audience)
|
|
103
|
+
const templateNames = getDirectories(audienceDir)
|
|
104
|
+
for (const templateName of templateNames) {
|
|
105
|
+
const templateDir = path.join(audienceDir, templateName)
|
|
106
|
+
const localeFiles = getLocaleFiles(templateDir)
|
|
107
|
+
for (const localeFile of localeFiles) {
|
|
108
|
+
buildTemplate(audience, templateName, localeFile)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
buildAll()
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
|
|
4
|
+
export type Locale = 'da' | 'en'
|
|
5
|
+
export type Audience = 'buyer' | 'supplier'
|
|
6
|
+
|
|
7
|
+
export type EmailTemplate = {
|
|
8
|
+
subject: string
|
|
9
|
+
html: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type TemplateDefinition = {
|
|
13
|
+
audience: string
|
|
14
|
+
template: string
|
|
15
|
+
locales: string[]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const templatesRoot = path.join(__dirname, 'templates')
|
|
19
|
+
const htmlRoot = path.join(__dirname, 'html')
|
|
20
|
+
|
|
21
|
+
function getDirectories(root: string) {
|
|
22
|
+
if (!fs.existsSync(root)) return []
|
|
23
|
+
return fs
|
|
24
|
+
.readdirSync(root, {withFileTypes: true})
|
|
25
|
+
.filter(d => d.isDirectory())
|
|
26
|
+
.map(d => d.name)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function getLocalesForTemplate(audience: string, template: string) {
|
|
30
|
+
const dir = path.join(templatesRoot, audience, template)
|
|
31
|
+
if (!fs.existsSync(dir)) return []
|
|
32
|
+
return fs
|
|
33
|
+
.readdirSync(dir, {withFileTypes: true})
|
|
34
|
+
.filter(f => f.isFile() && f.name.endsWith('.json'))
|
|
35
|
+
.map(f => f.name.replace(/\.json$/, ''))
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function listTemplates(): TemplateDefinition[] {
|
|
39
|
+
const result: TemplateDefinition[] = []
|
|
40
|
+
const audiences = getDirectories(templatesRoot)
|
|
41
|
+
for (const audience of audiences) {
|
|
42
|
+
const audienceDir = path.join(templatesRoot, audience)
|
|
43
|
+
const templates = getDirectories(audienceDir)
|
|
44
|
+
for (const template of templates) {
|
|
45
|
+
const locales = getLocalesForTemplate(audience, template)
|
|
46
|
+
result.push({audience, template, locales})
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return result
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function getEmailTemplate(
|
|
53
|
+
audience: Audience,
|
|
54
|
+
template: string,
|
|
55
|
+
locale: Locale,
|
|
56
|
+
): EmailTemplate {
|
|
57
|
+
const translationsPath = path.join(
|
|
58
|
+
templatesRoot,
|
|
59
|
+
audience,
|
|
60
|
+
template,
|
|
61
|
+
`${locale}.json`,
|
|
62
|
+
)
|
|
63
|
+
const htmlPath = path.join(htmlRoot, audience, template, `${locale}.html`)
|
|
64
|
+
|
|
65
|
+
if (!fs.existsSync(translationsPath)) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
`Missing translations for audience="${audience}" template="${template}" locale="${locale}"`,
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!fs.existsSync(htmlPath)) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
`Missing HTML for audience="${audience}" template="${template}" locale="${locale}". Did you run the build step?`,
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const translations = JSON.parse(
|
|
78
|
+
fs.readFileSync(translationsPath, 'utf8'),
|
|
79
|
+
) as Record<string, string>
|
|
80
|
+
const html = fs.readFileSync(htmlPath, 'utf8')
|
|
81
|
+
|
|
82
|
+
if (typeof translations.subject !== 'string') {
|
|
83
|
+
throw new Error(
|
|
84
|
+
`Translations for audience="${audience}" template="${template}" locale="${locale}" do not contain a "subject" key`,
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
subject: translations.subject,
|
|
90
|
+
html,
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
<mjml>
|
|
2
|
+
<mj-head>
|
|
3
|
+
<mj-font name="Arial" />
|
|
4
|
+
<mj-attributes>
|
|
5
|
+
<mj-all
|
|
6
|
+
font-family="Arial, sans-serif"
|
|
7
|
+
line-height="24px"
|
|
8
|
+
font-size="16px"
|
|
9
|
+
color="#222730"
|
|
10
|
+
/>
|
|
11
|
+
<mj-wrapper background-color="#ffffff" />
|
|
12
|
+
<mj-divider
|
|
13
|
+
border-width="1px"
|
|
14
|
+
border-color="#dadada"
|
|
15
|
+
padding="16px 0px 8px 0px"
|
|
16
|
+
/>
|
|
17
|
+
<mj-section padding="0px" />
|
|
18
|
+
<!-- <mj-text padding="0px 16px" /> -->
|
|
19
|
+
<mj-text padding="0px 24px" />
|
|
20
|
+
<!-- <mj-image padding="0px 16px" /> -->
|
|
21
|
+
<mj-image padding="0px 0px" />
|
|
22
|
+
<mj-table padding="0px 24px" />
|
|
23
|
+
<mj-button
|
|
24
|
+
border-radius="999px"
|
|
25
|
+
background-color="#FA3232"
|
|
26
|
+
color="#ffffff"
|
|
27
|
+
align="left"
|
|
28
|
+
font-size="16px"
|
|
29
|
+
/>
|
|
30
|
+
<mj-accordion border="none" padding="1px" />
|
|
31
|
+
<mj-accordion-element
|
|
32
|
+
icon-wrapped-url="https://i.imgur.com/Xvw0vjq.png"
|
|
33
|
+
icon-unwrapped-url="https://i.imgur.com/KKHenWa.png"
|
|
34
|
+
icon-height="24px"
|
|
35
|
+
icon-width="24px"
|
|
36
|
+
/>
|
|
37
|
+
<mj-accordion-title
|
|
38
|
+
font-family="Roboto, Open Sans, Helvetica, Arial, sans-serif"
|
|
39
|
+
background-color="#fff"
|
|
40
|
+
color="#031017"
|
|
41
|
+
padding="15px"
|
|
42
|
+
font-size="18px"
|
|
43
|
+
/>
|
|
44
|
+
<mj-accordion-text
|
|
45
|
+
font-family="Open Sans, Helvetica, Arial, sans-serif"
|
|
46
|
+
background-color="#fafafa"
|
|
47
|
+
padding="15px"
|
|
48
|
+
color="#505050"
|
|
49
|
+
font-size="14px"
|
|
50
|
+
/>
|
|
51
|
+
</mj-attributes>
|
|
52
|
+
<mj-style inline="inline">
|
|
53
|
+
.btn-link a { display: block; padding: 16px 24px; border-radius: 8px;
|
|
54
|
+
background-color: #FA3232; font-size: 16px; align: left; color: #ffffff; }
|
|
55
|
+
</mj-style>
|
|
56
|
+
<mj-style inline="inline">
|
|
57
|
+
.no-href-style a { text-decoration: none; color: inherit; }
|
|
58
|
+
</mj-style>
|
|
59
|
+
<mj-style inline="inline">
|
|
60
|
+
a, p, u, i, b, em, small, strong { line-height: 24px; } p { margin: 16px
|
|
61
|
+
0px; } p.small { line-height: 1; } p.big { line-height: 2; } a {
|
|
62
|
+
text-decoration: underline; } h1 { font-size: 39px; line-height: 110%;
|
|
63
|
+
font-weight: 700; margin: 0px 0px 24px; } h2 { font-size: 31px;
|
|
64
|
+
line-height: 110%; font-weight: 700; margin: 24px 0px 0px; } h3 {
|
|
65
|
+
font-size: 25px; line-height: 110%; font-weight: 700; margin: 24px 0px
|
|
66
|
+
0px; } h4 { font-size: 20px; line-height: 110%; font-weight: 700; margin:
|
|
67
|
+
24px 0px 0px; } h5 { font-size: 16px; line-height: 110%; font-weight: 700;
|
|
68
|
+
margin: 24px 0px 0px; } .table-header { margin-top: "8px" } .menuComment
|
|
69
|
+
p{ margin-top: 4px; margin-bottom: 4px; } .menuComment p:first-of-type. {
|
|
70
|
+
margin-top: 0px; } .menuComment p:last-of-type { margin-bottom: 0px; }
|
|
71
|
+
.bordered-col { box-shadow: 0 0 0 1px #E4EBF2; border-radius: 4px; }
|
|
72
|
+
</mj-style>
|
|
73
|
+
<mj-style>
|
|
74
|
+
.body-bordered { border-radius: 24px; max-width: 600px; margin: 0 auto;
|
|
75
|
+
outline: none; /* Ensure the border is visible */ } @media (min-width:
|
|
76
|
+
600px) { .body-bordered { border-radius: 24px; max-width: 600px; margin: 0
|
|
77
|
+
auto; margin-top: 40px; outline: 1px solid #D6D6D6 !important; } }
|
|
78
|
+
.discountBox div { border-width: 1px; border-style: dashed; border-color:
|
|
79
|
+
#FCBC42; }
|
|
80
|
+
</mj-style>
|
|
81
|
+
</mj-head>
|
|
82
|
+
|
|
83
|
+
<mj-body css-class="body-bordered" background-color: #FFFFFF>
|
|
84
|
+
<mj-wrapper padding="12px 0px">
|
|
85
|
+
<mj-section padding="24px 0px 0px">
|
|
86
|
+
<mj-column>
|
|
87
|
+
<mj-image
|
|
88
|
+
width="40px"
|
|
89
|
+
height="40px"
|
|
90
|
+
align="left"
|
|
91
|
+
src="https://ik.imagekit.io/nuento/logo/Logomark_Uj47FzOvV.png"
|
|
92
|
+
padding="0px 24px 8px"
|
|
93
|
+
alt="Nuento"
|
|
94
|
+
/>
|
|
95
|
+
</mj-column>
|
|
96
|
+
</mj-section>
|
|
97
|
+
|
|
98
|
+
%CONTENT%
|
|
99
|
+
|
|
100
|
+
<mj-section>
|
|
101
|
+
<mj-column>
|
|
102
|
+
<mj-divider />
|
|
103
|
+
</mj-column>
|
|
104
|
+
</mj-section>
|
|
105
|
+
|
|
106
|
+
<mj-section>
|
|
107
|
+
<mj-column>
|
|
108
|
+
<mj-text padding="24px 24px 0px">
|
|
109
|
+
<b>Sendt fra</b>
|
|
110
|
+
<br />
|
|
111
|
+
Nuento Denmark ApS, CVR 39000725
|
|
112
|
+
<br />
|
|
113
|
+
Lundeborgvej 4, 9220 Aalborg
|
|
114
|
+
</mj-text>
|
|
115
|
+
</mj-column>
|
|
116
|
+
</mj-section>
|
|
117
|
+
|
|
118
|
+
<mj-section padding="32px 0px 0px">
|
|
119
|
+
<mj-column>
|
|
120
|
+
<mj-image
|
|
121
|
+
width="118px"
|
|
122
|
+
height="40px"
|
|
123
|
+
align="left"
|
|
124
|
+
src="https://ik.imagekit.io/nuento/logo/logo-40_5iYF6s3Jp.png"
|
|
125
|
+
padding="0px 24px 24px"
|
|
126
|
+
alt="Nuento"
|
|
127
|
+
/>
|
|
128
|
+
</mj-column>
|
|
129
|
+
</mj-section>
|
|
130
|
+
</mj-wrapper>
|
|
131
|
+
</mj-body>
|
|
132
|
+
</mjml>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"intro_greeting": "Hej {{ catering.contact.name }},",
|
|
3
|
+
"intro_body": "Du har modtaget en ny bestilling via Nuento med ordrenr. {{ catering_order.id8digit }}.",
|
|
4
|
+
"button_reply": "Svar på ordre",
|
|
5
|
+
"payment_link_intro": "Ordren er blevet gennemført på baggrund af et bestillingslink med følgende betegnelse:",
|
|
6
|
+
"delivery_line": "Levering {{ catering_order.selectedDate }} til spisetid kl. {{ catering_order.selectedTime }}",
|
|
7
|
+
"total_label": "Samlet ordrestørrelse",
|
|
8
|
+
"support_heading": "Har du brug for hjælp?",
|
|
9
|
+
"support_body": "Så tøv ikke med at kontakte os."
|
|
10
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"intro_greeting": "Hi {{ catering.contact.name }},",
|
|
3
|
+
"intro_body": "You have received a new order via Nuento with order no. {{ catering_order.id8digit }}.",
|
|
4
|
+
"button_reply": "Respond to order",
|
|
5
|
+
"payment_link_intro": "The order was placed using an order link with the following label:",
|
|
6
|
+
"delivery_line": "Delivery on {{ catering_order.selectedDate }} at {{ catering_order.selectedTime }}",
|
|
7
|
+
"total_label": "Total order amount",
|
|
8
|
+
"support_heading": "Need help?",
|
|
9
|
+
"support_body": "Don’t hesitate to contact us."
|
|
10
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<mj-section>
|
|
2
|
+
<mj-column>
|
|
3
|
+
<mj-text>
|
|
4
|
+
<p>[[intro_greeting]]</p>
|
|
5
|
+
<p>[[intro_body]]</p>
|
|
6
|
+
</mj-text>
|
|
7
|
+
</mj-column>
|
|
8
|
+
</mj-section>
|
|
9
|
+
|
|
10
|
+
<mj-section>
|
|
11
|
+
<mj-column>
|
|
12
|
+
<mj-button
|
|
13
|
+
css-class="btn-link"
|
|
14
|
+
align="left"
|
|
15
|
+
font-size="16px"
|
|
16
|
+
color="#ffffff"
|
|
17
|
+
background-color="#11141A"
|
|
18
|
+
href="{{{ catering_order.approvalUrl }}}"
|
|
19
|
+
>
|
|
20
|
+
[[button_reply]]
|
|
21
|
+
</mj-button>
|
|
22
|
+
</mj-column>
|
|
23
|
+
</mj-section>
|
|
24
|
+
|
|
25
|
+
<mj-raw>{{#if catering_order.paymentLinkName}}</mj-raw>
|
|
26
|
+
<mj-section>
|
|
27
|
+
<mj-column>
|
|
28
|
+
<mj-text>
|
|
29
|
+
<p>[[payment_link_intro]]</p>
|
|
30
|
+
</mj-text>
|
|
31
|
+
</mj-column>
|
|
32
|
+
</mj-section>
|
|
33
|
+
|
|
34
|
+
<mj-section
|
|
35
|
+
padding-left="16px"
|
|
36
|
+
padding-right="16px"
|
|
37
|
+
padding-bottom="10px"
|
|
38
|
+
padding-top="10px"
|
|
39
|
+
>
|
|
40
|
+
<mj-column border-radius="8px" background-color="#E6F7FF">
|
|
41
|
+
<mj-text align="center">
|
|
42
|
+
<p><i>{{{catering_order.paymentLinkName}}}</i></p>
|
|
43
|
+
</mj-text>
|
|
44
|
+
</mj-column>
|
|
45
|
+
</mj-section>
|
|
46
|
+
<mj-raw>{{/if}}</mj-raw>
|
|
47
|
+
|
|
48
|
+
<mj-section padding-top="10px" padding-bottom="10px">
|
|
49
|
+
<mj-column>
|
|
50
|
+
<mj-text>
|
|
51
|
+
<p>
|
|
52
|
+
<font size="4">[[delivery_line]]</font>
|
|
53
|
+
</p>
|
|
54
|
+
<p>
|
|
55
|
+
<font size="4">[[total_label]]</font>
|
|
56
|
+
<br /><font size="4"><b>{{ invoice.price.total }}</b></font>
|
|
57
|
+
</p>
|
|
58
|
+
</mj-text>
|
|
59
|
+
</mj-column>
|
|
60
|
+
</mj-section>
|
|
61
|
+
|
|
62
|
+
<mj-section>
|
|
63
|
+
<mj-column>
|
|
64
|
+
<mj-divider />
|
|
65
|
+
</mj-column>
|
|
66
|
+
</mj-section>
|
|
67
|
+
|
|
68
|
+
<mj-section padding-top="10px" padding-bottom="10px">
|
|
69
|
+
<mj-column>
|
|
70
|
+
<mj-text>
|
|
71
|
+
<p>
|
|
72
|
+
<font size="4"><b>[[support_heading]]</b></font>
|
|
73
|
+
</p>
|
|
74
|
+
<p>[[support_body]]</p>
|
|
75
|
+
<p>
|
|
76
|
+
{{ support.email }}
|
|
77
|
+
<br /><a href="tel:{{ support.phone }}">{{ support.phone }}</a>
|
|
78
|
+
</p>
|
|
79
|
+
</mj-text>
|
|
80
|
+
</mj-column>
|
|
81
|
+
</mj-section>
|
package/tsconfig.json
ADDED