Initial commit: ANOUMA website with Payload CMS, WebRTC meetings, and booking system
- Next.js 16 App Router site with the ANOUMA design system - Payload CMS (PostgreSQL) for offers, events, posts and page content - WebRTC video-call system with custom signaling server - SMTP email reminders and booking-request notifications - Customer accounts, calendar-based availability, and booking workflow
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
import { escapeHtml, paragraph, renderButton, renderEmailShell, renderFactBox } from "./layout";
|
||||
|
||||
type BaseArgs = {
|
||||
name: string;
|
||||
offerTitle: string;
|
||||
dateLabel: string;
|
||||
timeLabel: string;
|
||||
};
|
||||
|
||||
export function bookingRequestReceivedEmail(args: BaseArgs & { appointmentTypeLabel: string }): { subject: string; html: string } {
|
||||
const html = renderEmailShell({
|
||||
bodyHtml: [
|
||||
paragraph(`Hallo ${args.name},`),
|
||||
paragraph("deine Terminanfrage bei ANOUMA wurde übermittelt.", { italic: true }),
|
||||
renderFactBox([
|
||||
{ label: "Angebot", value: args.offerTitle },
|
||||
{ label: "Datum", value: args.dateLabel },
|
||||
{ label: "Uhrzeit", value: args.timeLabel },
|
||||
{ label: "Terminart", value: args.appointmentTypeLabel },
|
||||
]),
|
||||
paragraph("Der Termin ist noch nicht verbindlich bestätigt. Du erhältst eine weitere E-Mail, sobald Anna deine Anfrage bestätigt hat.", { small: true }),
|
||||
].join("\n"),
|
||||
});
|
||||
return { subject: "Deine Terminanfrage bei ANOUMA", html };
|
||||
}
|
||||
|
||||
export function newBookingRequestAdminEmail(args: {
|
||||
customerName: string;
|
||||
customerEmail: string;
|
||||
offerTitle: string;
|
||||
dateLabel: string;
|
||||
timeLabel: string;
|
||||
appointmentTypeLabel: string;
|
||||
adminUrl: string;
|
||||
}): { subject: string; html: string } {
|
||||
const html = renderEmailShell({
|
||||
bodyHtml: [
|
||||
paragraph("Neue Terminanfrage", { italic: true }),
|
||||
renderFactBox([
|
||||
{ label: "Name", value: args.customerName },
|
||||
{ label: "E-Mail", value: args.customerEmail },
|
||||
{ label: "Angebot", value: args.offerTitle },
|
||||
{ label: "Datum", value: args.dateLabel },
|
||||
{ label: "Uhrzeit", value: args.timeLabel },
|
||||
{ label: "Terminart", value: args.appointmentTypeLabel },
|
||||
]),
|
||||
renderButton("Im Admin-Bereich ansehen", args.adminUrl),
|
||||
].join("\n"),
|
||||
});
|
||||
return { subject: "Neue Terminanfrage", html };
|
||||
}
|
||||
|
||||
export function bookingConfirmedEmail(
|
||||
args: BaseArgs & {
|
||||
appointmentTypeLabel: string;
|
||||
online?: { joinUrl: string; password: string };
|
||||
onsite?: { address: string; mapsUrl: string };
|
||||
},
|
||||
): { subject: string; html: string } {
|
||||
const rows = [
|
||||
{ label: "Angebot", value: args.offerTitle },
|
||||
{ label: "Datum", value: args.dateLabel },
|
||||
{ label: "Uhrzeit", value: args.timeLabel },
|
||||
{ label: "Ort", value: args.appointmentTypeLabel },
|
||||
];
|
||||
if (args.onsite) rows.push({ label: "Adresse", value: args.onsite.address });
|
||||
|
||||
const extras: string[] = [];
|
||||
if (args.online) {
|
||||
extras.push(renderButton("Meeting betreten", args.online.joinUrl));
|
||||
extras.push(
|
||||
`<p style="margin:0 0 4px;color:#66505f;font-size:14px;"><strong>Meeting-Passwort:</strong></p>` +
|
||||
`<p style="margin:0 0 20px;color:#66505f;font-size:20px;letter-spacing:0.08em;font-family:'Courier New',monospace;">${escapeHtml(args.online.password)}</p>`,
|
||||
);
|
||||
}
|
||||
if (args.onsite) {
|
||||
extras.push(renderButton("Route öffnen", args.onsite.mapsUrl));
|
||||
}
|
||||
|
||||
const html = renderEmailShell({
|
||||
bodyHtml: [
|
||||
paragraph(`Hallo ${args.name},`),
|
||||
paragraph("dein Termin bei ANOUMA wurde bestätigt.", { italic: true }),
|
||||
renderFactBox(rows),
|
||||
...extras,
|
||||
].join("\n"),
|
||||
});
|
||||
return { subject: "Dein Termin bei ANOUMA wurde bestätigt", html };
|
||||
}
|
||||
|
||||
export function bookingRejectedEmail(args: BaseArgs): { subject: string; html: string } {
|
||||
const html = renderEmailShell({
|
||||
bodyHtml: [
|
||||
paragraph(`Hallo ${args.name},`),
|
||||
paragraph("leider kann der folgende Termin nicht stattfinden.", { italic: true }),
|
||||
renderFactBox([
|
||||
{ label: "Angebot", value: args.offerTitle },
|
||||
{ label: "Datum", value: args.dateLabel },
|
||||
{ label: "Uhrzeit", value: args.timeLabel },
|
||||
]),
|
||||
paragraph("Melde dich gerne für einen neuen Termin — schau einfach wieder in deinem ANOUMA-Konto vorbei.", { small: true }),
|
||||
].join("\n"),
|
||||
});
|
||||
return { subject: "Deine Terminanfrage bei ANOUMA", html };
|
||||
}
|
||||
|
||||
export function alternativeProposedEmail(args: {
|
||||
name: string;
|
||||
offerTitle: string;
|
||||
originalDateLabel: string;
|
||||
originalTimeLabel: string;
|
||||
altDateLabel: string;
|
||||
altTimeLabel: string;
|
||||
accountUrl: string;
|
||||
}): { subject: string; html: string } {
|
||||
const html = renderEmailShell({
|
||||
bodyHtml: [
|
||||
paragraph(`Hallo ${args.name},`),
|
||||
paragraph("Anna hat einen alternativen Termin für dich vorgeschlagen.", { italic: true }),
|
||||
renderFactBox([
|
||||
{ label: "Angebot", value: args.offerTitle },
|
||||
{ label: "Ursprünglich angefragt", value: `${args.originalDateLabel}, ${args.originalTimeLabel}` },
|
||||
{ label: "Neuer Vorschlag", value: `${args.altDateLabel}, ${args.altTimeLabel}` },
|
||||
]),
|
||||
renderButton("Zum Termin in meinem Konto", args.accountUrl),
|
||||
paragraph("Dort kannst du den neuen Termin annehmen oder einen anderen anfragen.", { small: true }),
|
||||
].join("\n"),
|
||||
});
|
||||
return { subject: "Alternativer Terminvorschlag von ANOUMA", html };
|
||||
}
|
||||
|
||||
export function bookingCancelledAdminEmail(args: {
|
||||
customerName: string;
|
||||
offerTitle: string;
|
||||
dateLabel: string;
|
||||
timeLabel: string;
|
||||
adminUrl: string;
|
||||
}): { subject: string; html: string } {
|
||||
const html = renderEmailShell({
|
||||
bodyHtml: [
|
||||
paragraph("Ein Termin wurde storniert.", { italic: true }),
|
||||
renderFactBox([
|
||||
{ label: "Name", value: args.customerName },
|
||||
{ label: "Angebot", value: args.offerTitle },
|
||||
{ label: "Datum", value: args.dateLabel },
|
||||
{ label: "Uhrzeit", value: args.timeLabel },
|
||||
]),
|
||||
renderButton("Im Admin-Bereich ansehen", args.adminUrl),
|
||||
].join("\n"),
|
||||
});
|
||||
return { subject: "Termin storniert", html };
|
||||
}
|
||||
Reference in New Issue
Block a user