chore(docker): .dockerignore angepasst; lokale Build-Schritte in Rebuild-Skripten; Doku/README zu production vs production-prebuilt aktualisiert
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { sanitizeText, sanitizeHtml, sanitizePhone } from "./sanitize.js";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { dirname, resolve } from "node:path";
|
||||
// Helper function to convert date from yyyy-mm-dd to dd.mm.yyyy
|
||||
@@ -56,12 +57,13 @@ async function renderBrandedEmail(title, bodyHtml) {
|
||||
}
|
||||
export async function renderBookingPendingHTML(params) {
|
||||
const { name, date, time, statusUrl } = params;
|
||||
const safeName = sanitizeText(name);
|
||||
const formattedDate = formatDateGerman(date);
|
||||
const domain = process.env.DOMAIN || 'localhost:5173';
|
||||
const protocol = domain.includes('localhost') ? 'http' : 'https';
|
||||
const legalUrl = `${protocol}://${domain}/legal`;
|
||||
const inner = `
|
||||
<p>Hallo ${name},</p>
|
||||
<p>Hallo ${safeName},</p>
|
||||
<p>wir haben deine Anfrage für <strong>${formattedDate}</strong> um <strong>${time}</strong> erhalten.</p>
|
||||
<p>Wir bestätigen deinen Termin in Kürze. Du erhältst eine weitere E-Mail, sobald der Termin bestätigt ist.</p>
|
||||
${statusUrl ? `
|
||||
@@ -81,12 +83,13 @@ export async function renderBookingPendingHTML(params) {
|
||||
}
|
||||
export async function renderBookingConfirmedHTML(params) {
|
||||
const { name, date, time, cancellationUrl, reviewUrl } = params;
|
||||
const safeName = sanitizeText(name);
|
||||
const formattedDate = formatDateGerman(date);
|
||||
const domain = process.env.DOMAIN || 'localhost:5173';
|
||||
const protocol = domain.includes('localhost') ? 'http' : 'https';
|
||||
const legalUrl = `${protocol}://${domain}/legal`;
|
||||
const inner = `
|
||||
<p>Hallo ${name},</p>
|
||||
<p>Hallo ${safeName},</p>
|
||||
<p>wir haben deinen Termin am <strong>${formattedDate}</strong> um <strong>${time}</strong> bestätigt.</p>
|
||||
<p>Wir freuen uns auf dich!</p>
|
||||
<div style="background-color: #f8fafc; border-left: 4px solid #db2777; padding: 16px; margin: 20px 0; border-radius: 4px;">
|
||||
@@ -118,12 +121,13 @@ export async function renderBookingConfirmedHTML(params) {
|
||||
}
|
||||
export async function renderBookingCancelledHTML(params) {
|
||||
const { name, date, time } = params;
|
||||
const safeName = sanitizeText(name);
|
||||
const formattedDate = formatDateGerman(date);
|
||||
const domain = process.env.DOMAIN || 'localhost:5173';
|
||||
const protocol = domain.includes('localhost') ? 'http' : 'https';
|
||||
const legalUrl = `${protocol}://${domain}/legal`;
|
||||
const inner = `
|
||||
<p>Hallo ${name},</p>
|
||||
<p>Hallo ${safeName},</p>
|
||||
<p>dein Termin am <strong>${formattedDate}</strong> um <strong>${time}</strong> wurde abgesagt.</p>
|
||||
<p>Bitte buche einen neuen Termin. Bei Fragen helfen wir dir gerne weiter.</p>
|
||||
<div style="background-color: #f8fafc; border-left: 4px solid #3b82f6; padding: 16px; margin: 20px 0; border-radius: 4px;">
|
||||
@@ -136,6 +140,10 @@ export async function renderBookingCancelledHTML(params) {
|
||||
}
|
||||
export async function renderAdminBookingNotificationHTML(params) {
|
||||
const { name, date, time, treatment, phone, notes, hasInspirationPhoto } = params;
|
||||
const safeName = sanitizeText(name);
|
||||
const safeTreatment = sanitizeText(treatment);
|
||||
const safePhone = sanitizePhone(phone);
|
||||
const safeNotes = sanitizeHtml(notes);
|
||||
const formattedDate = formatDateGerman(date);
|
||||
const inner = `
|
||||
<p>Hallo Admin,</p>
|
||||
@@ -143,12 +151,12 @@ export async function renderAdminBookingNotificationHTML(params) {
|
||||
<div style="background-color: #f8fafc; border-left: 4px solid #db2777; padding: 16px; margin: 20px 0; border-radius: 4px;">
|
||||
<p style="margin: 0; font-weight: 600; color: #db2777;">📅 Buchungsdetails:</p>
|
||||
<ul style="margin: 8px 0 0 0; color: #475569; list-style: none; padding: 0;">
|
||||
<li><strong>Name:</strong> ${name}</li>
|
||||
<li><strong>Telefon:</strong> ${phone}</li>
|
||||
<li><strong>Behandlung:</strong> ${treatment}</li>
|
||||
<li><strong>Name:</strong> ${safeName}</li>
|
||||
<li><strong>Telefon:</strong> ${safePhone}</li>
|
||||
<li><strong>Behandlung:</strong> ${safeTreatment}</li>
|
||||
<li><strong>Datum:</strong> ${formattedDate}</li>
|
||||
<li><strong>Uhrzeit:</strong> ${time}</li>
|
||||
${notes ? `<li><strong>Notizen:</strong> ${notes}</li>` : ''}
|
||||
${safeNotes ? `<li><strong>Notizen:</strong> ${safeNotes}</li>` : ''}
|
||||
<li><strong>Inspiration-Foto:</strong> ${hasInspirationPhoto ? '✅ Im Anhang verfügbar' : '❌ Kein Foto hochgeladen'}</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -158,12 +166,14 @@ export async function renderAdminBookingNotificationHTML(params) {
|
||||
return renderBrandedEmail("Neue Buchungsanfrage - Admin-Benachrichtigung", inner);
|
||||
}
|
||||
export async function renderBookingRescheduleProposalHTML(params) {
|
||||
const safeName = sanitizeText(params.name);
|
||||
const safeTreatment = sanitizeText(params.treatmentName);
|
||||
const formattedOriginalDate = formatDateGerman(params.originalDate);
|
||||
const formattedProposedDate = formatDateGerman(params.proposedDate);
|
||||
const expiryDate = new Date(params.expiresAt);
|
||||
const formattedExpiry = `${expiryDate.toLocaleDateString('de-DE')} ${expiryDate.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })}`;
|
||||
const inner = `
|
||||
<p>Hallo ${params.name},</p>
|
||||
<p>Hallo ${safeName},</p>
|
||||
<p>wir müssen deinen Termin leider verschieben. Hier ist unser Vorschlag:</p>
|
||||
<div style="background-color: #f8fafc; border-left: 4px solid #f59e0b; padding: 16px; margin: 20px 0; border-radius: 4px;">
|
||||
<p style="margin: 0; font-weight: 600; color: #92400e;">📅 Übersicht</p>
|
||||
@@ -178,7 +188,7 @@ export async function renderBookingRescheduleProposalHTML(params) {
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:6px 0; width:45%"><strong>Behandlung</strong></td>
|
||||
<td style="padding:6px 0;">${params.treatmentName}</td>
|
||||
<td style="padding:6px 0;">${safeTreatment}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
@@ -198,15 +208,19 @@ export async function renderBookingRescheduleProposalHTML(params) {
|
||||
return renderBrandedEmail("Terminänderung vorgeschlagen", inner);
|
||||
}
|
||||
export async function renderAdminRescheduleDeclinedHTML(params) {
|
||||
const safeCustomerName = sanitizeText(params.customerName);
|
||||
const safeTreatment = sanitizeText(params.treatmentName);
|
||||
const safeEmail = params.customerEmail ? sanitizeText(params.customerEmail) : undefined;
|
||||
const safePhone = params.customerPhone ? sanitizeText(params.customerPhone) : undefined;
|
||||
const inner = `
|
||||
<p>Hallo Admin,</p>
|
||||
<p>der Kunde <strong>${params.customerName}</strong> hat den Terminänderungsvorschlag abgelehnt.</p>
|
||||
<p>der Kunde <strong>${safeCustomerName}</strong> hat den Terminänderungsvorschlag abgelehnt.</p>
|
||||
<div style="background-color:#f8fafc; border-left:4px solid #ef4444; padding:16px; margin:16px 0; border-radius:4px;">
|
||||
<ul style="margin:0; padding:0; list-style:none; color:#475569; font-size:14px;">
|
||||
<li><strong>Kunde:</strong> ${params.customerName}</li>
|
||||
${params.customerEmail ? `<li><strong>E-Mail:</strong> ${params.customerEmail}</li>` : ''}
|
||||
${params.customerPhone ? `<li><strong>Telefon:</strong> ${params.customerPhone}</li>` : ''}
|
||||
<li><strong>Behandlung:</strong> ${params.treatmentName}</li>
|
||||
<li><strong>Kunde:</strong> ${safeCustomerName}</li>
|
||||
${safeEmail ? `<li><strong>E-Mail:</strong> ${safeEmail}</li>` : ''}
|
||||
${safePhone ? `<li><strong>Telefon:</strong> ${safePhone}</li>` : ''}
|
||||
<li><strong>Behandlung:</strong> ${safeTreatment}</li>
|
||||
<li><strong>Ursprünglicher Termin:</strong> ${formatDateGerman(params.originalDate)} um ${params.originalTime} Uhr (bleibt bestehen)</li>
|
||||
<li><strong>Abgelehnter Vorschlag:</strong> ${formatDateGerman(params.proposedDate)} um ${params.proposedTime} Uhr</li>
|
||||
</ul>
|
||||
@@ -216,13 +230,15 @@ export async function renderAdminRescheduleDeclinedHTML(params) {
|
||||
return renderBrandedEmail("Kunde hat Terminänderung abgelehnt", inner);
|
||||
}
|
||||
export async function renderAdminRescheduleAcceptedHTML(params) {
|
||||
const safeCustomerName = sanitizeText(params.customerName);
|
||||
const safeTreatment = sanitizeText(params.treatmentName);
|
||||
const inner = `
|
||||
<p>Hallo Admin,</p>
|
||||
<p>der Kunde <strong>${params.customerName}</strong> hat den Terminänderungsvorschlag akzeptiert.</p>
|
||||
<p>der Kunde <strong>${safeCustomerName}</strong> hat den Terminänderungsvorschlag akzeptiert.</p>
|
||||
<div style="background-color:#ecfeff; border-left:4px solid #10b981; padding:16px; margin:16px 0; border-radius:4px;">
|
||||
<ul style="margin:0; padding:0; list-style:none; color:#475569; font-size:14px;">
|
||||
<li><strong>Kunde:</strong> ${params.customerName}</li>
|
||||
<li><strong>Behandlung:</strong> ${params.treatmentName}</li>
|
||||
<li><strong>Kunde:</strong> ${safeCustomerName}</li>
|
||||
<li><strong>Behandlung:</strong> ${safeTreatment}</li>
|
||||
<li><strong>Alter Termin:</strong> ${formatDateGerman(params.originalDate)} um ${params.originalTime} Uhr</li>
|
||||
<li><strong>Neuer Termin:</strong> ${formatDateGerman(params.newDate)} um ${params.newTime} Uhr ✅</li>
|
||||
</ul>
|
||||
@@ -237,19 +253,25 @@ export async function renderAdminRescheduleExpiredHTML(params) {
|
||||
<p><strong>${params.expiredProposals.length} Terminänderungsvorschlag${params.expiredProposals.length > 1 ? 'e' : ''} ${params.expiredProposals.length > 1 ? 'sind' : 'ist'} abgelaufen</strong> und wurde${params.expiredProposals.length > 1 ? 'n' : ''} automatisch entfernt.</p>
|
||||
<div style="background-color:#fef2f2; border-left:4px solid #ef4444; padding:16px; margin:16px 0; border-radius:4px;">
|
||||
<p style="margin:0 0 12px 0; font-weight:600; color:#dc2626;">⚠️ Abgelaufene Vorschläge:</p>
|
||||
${params.expiredProposals.map(proposal => `
|
||||
${params.expiredProposals.map(proposal => {
|
||||
const safeName = sanitizeText(proposal.customerName);
|
||||
const safeTreatment = sanitizeText(proposal.treatmentName);
|
||||
const safeEmail = proposal.customerEmail ? sanitizeText(proposal.customerEmail) : undefined;
|
||||
const safePhone = proposal.customerPhone ? sanitizeText(proposal.customerPhone) : undefined;
|
||||
return `
|
||||
<div style="background-color:#ffffff; border:1px solid #fecaca; border-radius:4px; padding:12px; margin:8px 0;">
|
||||
<ul style="margin:0; padding:0; list-style:none; color:#475569; font-size:13px;">
|
||||
<li><strong>Kunde:</strong> ${proposal.customerName}</li>
|
||||
${proposal.customerEmail ? `<li><strong>E-Mail:</strong> ${proposal.customerEmail}</li>` : ''}
|
||||
${proposal.customerPhone ? `<li><strong>Telefon:</strong> ${proposal.customerPhone}</li>` : ''}
|
||||
<li><strong>Behandlung:</strong> ${proposal.treatmentName}</li>
|
||||
<li><strong>Kunde:</strong> ${safeName}</li>
|
||||
${safeEmail ? `<li><strong>E-Mail:</strong> ${safeEmail}</li>` : ''}
|
||||
${safePhone ? `<li><strong>Telefon:</strong> ${safePhone}</li>` : ''}
|
||||
<li><strong>Behandlung:</strong> ${safeTreatment}</li>
|
||||
<li><strong>Ursprünglicher Termin:</strong> ${formatDateGerman(proposal.originalDate)} um ${proposal.originalTime} Uhr</li>
|
||||
<li><strong>Vorgeschlagener Termin:</strong> ${formatDateGerman(proposal.proposedDate)} um ${proposal.proposedTime} Uhr</li>
|
||||
<li><strong>Abgelaufen am:</strong> ${new Date(proposal.expiredAt).toLocaleString('de-DE')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
`).join('')}
|
||||
`;
|
||||
}).join('')}
|
||||
</div>
|
||||
<p style="color:#dc2626; font-weight:600;">Bitte kontaktiere die Kunden, um eine alternative Lösung zu finden.</p>
|
||||
<p>Die ursprünglichen Termine bleiben bestehen.</p>
|
||||
|
Reference in New Issue
Block a user