Skip to content

Commit a09c00a

Browse files
Replace email templates (#4)
1 parent a975322 commit a09c00a

30 files changed

+1376
-8958
lines changed

Diff for: server/package.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"@nestjs/jwt": "^10.2.0",
3636
"@nestjs/platform-fastify": "^10.3.10",
3737
"@prisma/client": "5.18.0",
38+
"@usewaypoint/email-builder": "^0.0.8",
3839
"axios": "^1.7.7",
3940
"class-transformer": "^0.5.1",
4041
"class-validator": "^0.14.1",
@@ -43,10 +44,13 @@
4344
"nodemailer": "^6.9.14",
4445
"pino": "^9.3.1",
4546
"pino-pretty": "^11.2.1",
47+
"react": "^18.3.1",
48+
"react-dom": "^18.3.1",
4649
"reflect-metadata": "^0.2.0",
4750
"rxjs": "^7.8.1",
4851
"sharp": "^0.33.5",
49-
"uuid": "^10.0.0"
52+
"uuid": "^10.0.0",
53+
"zod": "^3.23.8"
5054
},
5155
"devDependencies": {
5256
"@nestjs/cli": "^10.0.0",

Diff for: server/src/app.controller.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export class AppController {
1717
@PublicRoute()
1818
async getFrontendEnvs() {
1919
return {
20-
BACKEND_URL: new URL('/api', this.appConfigService.publicUrl).href,
20+
BACKEND_URL: new URL('/api', this.appConfigService.backendPublicUrl).href,
2121
PUBLIC_URL: this.appConfigService.frontendPublicUrl,
2222
};
2323
}

Diff for: server/src/auth/google.auth.service.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export class GoogleAuthService {
4141
) {}
4242

4343
public buildGoogleOAuthRedirectUrl(): string {
44-
const url = new URL(this.appConfigService.googleOAuthRedirectUri, this.appConfigService.publicUrl);
44+
const url = new URL(this.appConfigService.googleOAuthRedirectUri, this.appConfigService.backendPublicUrl);
4545

4646
return url.href;
4747
}

Diff for: server/src/config/app-config.service.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export class AppConfigService {
2828
return this.get('HOST');
2929
}
3030

31-
get publicUrl(): string {
31+
get backendPublicUrl(): string {
3232
return this.get('VITE_BACKEND_URL');
3333
}
3434

Diff for: server/src/mail/mail.service.ts

+21-20
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,19 @@ import { AppConfigService } from '@/config/app-config.service';
88
import { Language } from '@prisma/client';
99
import { UserWithSettings } from '@/types/prisma';
1010
import { templates } from '@/mail/templates/templates';
11-
import { generateSocials } from '@/mail/templates/generate-socials';
1211
import * as path from 'path';
12+
import { renderToStaticMarkup, TReaderDocument } from '@usewaypoint/email-builder';
13+
import { defaultEmailTemplateFactory } from '@/mail/templates/default-template';
1314
export interface MailTemplate {
14-
template: string;
15+
templateFactory: (translations: any) => TReaderDocument; // TODO type this
1516
translations: Record<Language, EmailTranslation>;
16-
images?: Attachment[];
17+
images: Attachment[];
18+
headlineIconUrl: string;
1719
}
1820
export interface EmailTranslation {
1921
subject: string;
22+
headline: string;
23+
contactHeadline: string;
2024
[key: string]: string;
2125
}
2226

@@ -94,15 +98,18 @@ export class MailService {
9498
otherKeys?: Record<string, string>,
9599
): void {
96100
const template = templates[email];
97-
101+
const socials = this.appConfigService.socials.map((social) => ({
102+
name: social.name.toLowerCase(),
103+
url: social.url.toLowerCase(),
104+
}));
98105
const images = template.images ?? [];
99106
images.push(
100107
{
101108
cid: 'logo',
102109
filename: 'logo.png',
103110
path: path.join(__dirname, './templates/images/logo.png'),
104111
},
105-
...this.appConfigService.socials.map((social) => ({
112+
...socials.map((social) => ({
106113
cid: social.name,
107114
filename: `${social.name}.png`,
108115
path: path.join(__dirname, `./templates/images/${social.name}.png`),
@@ -111,24 +118,27 @@ export class MailService {
111118

112119
const translation = template.translations[options.language];
113120

114-
const socials = generateSocials(this.appConfigService.socials);
115-
116-
const htmlContent = this.applyTranslation(template.template, {
121+
const templateRoot = defaultEmailTemplateFactory({
117122
...translation,
118-
...otherKeys,
123+
iconUrl: template.headlineIconUrl,
119124
contact1: this.appConfigService.imprintContact1,
120125
contact2: this.appConfigService.imprintContact2,
121126
contact3: this.appConfigService.imprintContact3,
122127
contact4: this.appConfigService.imprintContact4,
128+
copyRight: this.appConfigService.imprintCopyright,
123129
socials,
124-
imprintCopyright: this.appConfigService.imprintCopyright,
125130
});
131+
const contentTemplate = template.templateFactory({ ...translation, ...otherKeys });
126132

133+
const templateOptions = {
134+
...templateRoot,
135+
...contentTemplate,
136+
};
127137
const mailOptions: MailOptions = {
128138
from: this.appConfigService.smtpUser,
129139
to: options.email,
130140
subject: translation.subject,
131-
html: htmlContent,
141+
html: renderToStaticMarkup(templateOptions, { rootBlockId: 'root' }),
132142
attachments: template.images,
133143
};
134144

@@ -140,13 +150,4 @@ export class MailService {
140150
}
141151
});
142152
}
143-
144-
private applyTranslation(template: string, translation: EmailTranslation): string {
145-
let result = template;
146-
Object.keys(translation).forEach((key) => {
147-
const regex = new RegExp(`{{\\s*${key}\\s*}}`, 'g');
148-
result = result.replace(regex, translation[key]);
149-
});
150-
return result;
151-
}
152153
}

0 commit comments

Comments
 (0)