Fix reschedule token handling and improve admin notifications
- Fix getBookingByToken to only accept booking_access tokens - Add sweepExpiredRescheduleProposals with admin notifications - Return isExpired flag instead of throwing errors for expired proposals - Fix email template to use actual token expiry time - Remove duplicate admin emails in acceptReschedule - Add one-click accept/decline support via URL parameters
This commit is contained in:
@@ -169,3 +169,145 @@ export async function renderAdminBookingNotificationHTML(params: {
|
||||
}
|
||||
|
||||
|
||||
export async function renderBookingRescheduleProposalHTML(params: {
|
||||
name: string;
|
||||
originalDate: string;
|
||||
originalTime: string;
|
||||
proposedDate: string;
|
||||
proposedTime: string;
|
||||
treatmentName: string;
|
||||
acceptUrl: string;
|
||||
declineUrl: string;
|
||||
expiresAt: string;
|
||||
}) {
|
||||
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>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>
|
||||
<table role="presentation" cellspacing="0" cellpadding="0" style="width:100%; margin-top:8px; font-size:14px; color:#475569;">
|
||||
<tr>
|
||||
<td style="padding:6px 0; width:45%"><strong>Alter Termin</strong></td>
|
||||
<td style="padding:6px 0;">${formattedOriginalDate} um ${params.originalTime} Uhr</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:6px 0; width:45%"><strong>Neuer Vorschlag</strong></td>
|
||||
<td style="padding:6px 0; color:#b45309;"><strong>${formattedProposedDate} um ${params.proposedTime} Uhr</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:6px 0; width:45%"><strong>Behandlung</strong></td>
|
||||
<td style="padding:6px 0;">${params.treatmentName}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div style="background-color: #fffbeb; border-left: 4px solid #f59e0b; padding: 12px; margin: 16px 0; border-radius: 4px; color:#92400e;">
|
||||
⏰ Bitte antworte bis ${formattedExpiry}.
|
||||
</div>
|
||||
<div style="text-align:center; margin: 20px 0;">
|
||||
<a href="${params.acceptUrl}" style="display:inline-block; background-color:#16a34a; color:#ffffff; padding:12px 18px; border-radius:8px; text-decoration:none; font-weight:700; margin-right:8px;">Neuen Termin akzeptieren</a>
|
||||
<a href="${params.declineUrl}" style="display:inline-block; background-color:#dc2626; color:#ffffff; padding:12px 18px; border-radius:8px; text-decoration:none; font-weight:700;">Termin ablehnen</a>
|
||||
</div>
|
||||
<div style="background-color: #f8fafc; border-left: 4px solid #10b981; padding: 12px; margin: 16px 0; border-radius: 4px; color:#065f46;">
|
||||
Wenn du den Vorschlag ablehnst, bleibt dein ursprünglicher Termin bestehen und wir kontaktieren dich für eine alternative Lösung.
|
||||
</div>
|
||||
<p>Falls du einen komplett neuen Termin buchen möchtest, kannst du deinen aktuellen Termin stornieren und einen neuen Termin auf unserer Website buchen.</p>
|
||||
<p>Liebe Grüße,<br/>Stargirlnails Kiel</p>
|
||||
`;
|
||||
return renderBrandedEmail("Terminänderung vorgeschlagen", inner);
|
||||
}
|
||||
|
||||
export async function renderAdminRescheduleDeclinedHTML(params: {
|
||||
customerName: string;
|
||||
originalDate: string;
|
||||
originalTime: string;
|
||||
proposedDate: string;
|
||||
proposedTime: string;
|
||||
treatmentName: string;
|
||||
customerEmail?: string;
|
||||
customerPhone?: string;
|
||||
}) {
|
||||
const inner = `
|
||||
<p>Hallo Admin,</p>
|
||||
<p>der Kunde <strong>${params.customerName}</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>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>
|
||||
</div>
|
||||
<p>Bitte kontaktiere den Kunden, um eine alternative Lösung zu finden.</p>
|
||||
`;
|
||||
return renderBrandedEmail("Kunde hat Terminänderung abgelehnt", inner);
|
||||
}
|
||||
|
||||
export async function renderAdminRescheduleAcceptedHTML(params: {
|
||||
customerName: string;
|
||||
originalDate: string;
|
||||
originalTime: string;
|
||||
newDate: string;
|
||||
newTime: string;
|
||||
treatmentName: string;
|
||||
}) {
|
||||
const inner = `
|
||||
<p>Hallo Admin,</p>
|
||||
<p>der Kunde <strong>${params.customerName}</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>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>
|
||||
</div>
|
||||
<p>Der Termin wurde automatisch aktualisiert.</p>
|
||||
`;
|
||||
return renderBrandedEmail("Kunde hat Terminänderung akzeptiert", inner);
|
||||
}
|
||||
|
||||
export async function renderAdminRescheduleExpiredHTML(params: {
|
||||
expiredProposals: Array<{
|
||||
customerName: string;
|
||||
originalDate: string;
|
||||
originalTime: string;
|
||||
proposedDate: string;
|
||||
proposedTime: string;
|
||||
treatmentName: string;
|
||||
customerEmail?: string;
|
||||
customerPhone?: string;
|
||||
expiredAt: string;
|
||||
}>;
|
||||
}) {
|
||||
const inner = `
|
||||
<p>Hallo Admin,</p>
|
||||
<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 => `
|
||||
<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>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('')}
|
||||
</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>
|
||||
`;
|
||||
return renderBrandedEmail("Abgelaufene Terminänderungsvorschläge", inner);
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user