feat: Token-basierte Kunden-Statusseite
- Neue /booking/{token} Route für einheitliche Buchungsübersicht - Vollständige Termin-Details mit Status-Badges (pending/confirmed/cancelled/completed) - Integrierte Stornierungsfunktion mit Bestätigungsdialog - Anzeige von Behandlungsdetails, Kundendaten und verbleibender Zeit - Automatische Berechnung ob Stornierung noch möglich - Responsive UI mit modernem Design Server-Erweiterungen: - BookingAccessToken statt CancellationToken (semantisch präziser) - Erweiterte Rückgabe von getBookingByToken (Preis, Dauer, canCancel, hoursUntilAppointment) - Token-Generierung bei Buchungserstellung (pending) und Bestätigung E-Mail-Integration: - Status-Links in pending-Mails - 'Termin verwalten' statt 'Termin stornieren' in confirmed-Mails - Einheitliches Branding (Pink/Orange statt Rot) Aufgeräumt: - Legacy cancellation-page.tsx entfernt - /cancel/ Route entfernt (keine Rückwärtskompatibilität nötig) - Backlog aktualisiert
This commit is contained in:
@@ -158,13 +158,22 @@ const create = os
|
||||
|
||||
// Notify customer: request received (pending)
|
||||
void (async () => {
|
||||
// Create booking access token for status viewing
|
||||
const bookingAccessToken = await queryClient.cancellation.createToken({ bookingId: id });
|
||||
const bookingUrl = generateUrl(`/booking/${bookingAccessToken.token}`);
|
||||
|
||||
const formattedDate = formatDateGerman(input.appointmentDate);
|
||||
const homepageUrl = generateUrl();
|
||||
const html = await renderBookingPendingHTML({ name: input.customerName, date: input.appointmentDate, time: input.appointmentTime });
|
||||
const html = await renderBookingPendingHTML({
|
||||
name: input.customerName,
|
||||
date: input.appointmentDate,
|
||||
time: input.appointmentTime,
|
||||
statusUrl: bookingUrl
|
||||
});
|
||||
await sendEmail({
|
||||
to: input.customerEmail,
|
||||
subject: "Deine Terminanfrage ist eingegangen",
|
||||
text: `Hallo ${input.customerName},\n\nwir haben deine Anfrage für ${formattedDate} um ${input.appointmentTime} erhalten. Wir bestätigen deinen Termin in Kürze.\n\nRechtliche Informationen: ${generateUrl('/legal')}\nZur Website: ${homepageUrl}\n\nLiebe Grüße\nStargirlnails Kiel`,
|
||||
text: `Hallo ${input.customerName},\n\nwir haben deine Anfrage für ${formattedDate} um ${input.appointmentTime} erhalten. Wir bestätigen deinen Termin in Kürze. Du erhältst eine weitere E-Mail, sobald der Termin bestätigt ist.\n\nTermin-Status ansehen: ${bookingUrl}\n\nRechtliche Informationen: ${generateUrl('/legal')}\nZur Website: ${homepageUrl}\n\nLiebe Grüße\nStargirlnails Kiel`,
|
||||
html,
|
||||
}).catch(() => {});
|
||||
})();
|
||||
@@ -292,18 +301,18 @@ const updateStatus = os
|
||||
// Email notifications on status changes
|
||||
try {
|
||||
if (input.status === "confirmed") {
|
||||
// Create cancellation token for this booking
|
||||
const cancellationToken = await queryClient.cancellation.createToken({ bookingId: booking.id });
|
||||
// Create booking access token for this booking (status + cancellation)
|
||||
const bookingAccessToken = await queryClient.cancellation.createToken({ bookingId: booking.id });
|
||||
|
||||
const formattedDate = formatDateGerman(booking.appointmentDate);
|
||||
const cancellationUrl = generateUrl(`/cancel/${cancellationToken.token}`);
|
||||
const bookingUrl = generateUrl(`/booking/${bookingAccessToken.token}`);
|
||||
const homepageUrl = generateUrl();
|
||||
|
||||
const html = await renderBookingConfirmedHTML({
|
||||
name: booking.customerName,
|
||||
date: booking.appointmentDate,
|
||||
time: booking.appointmentTime,
|
||||
cancellationUrl
|
||||
cancellationUrl: bookingUrl // Now points to booking status page
|
||||
});
|
||||
|
||||
// Get treatment information for ICS file
|
||||
@@ -315,7 +324,7 @@ const updateStatus = os
|
||||
await sendEmailWithAGBAndCalendar({
|
||||
to: booking.customerEmail,
|
||||
subject: "Dein Termin wurde bestätigt - AGB im Anhang",
|
||||
text: `Hallo ${booking.customerName},\n\nwir haben deinen Termin am ${formattedDate} um ${booking.appointmentTime} bestätigt.\n\nWichtiger Hinweis: Die Allgemeinen Geschäftsbedingungen (AGB) findest du im Anhang dieser E-Mail. Bitte lies sie vor deinem Termin durch.\n\nFalls du den Termin stornieren möchtest, kannst du das hier tun: ${cancellationUrl}\n\nRechtliche Informationen: ${generateUrl('/legal')}\nZur Website: ${homepageUrl}\n\nBis bald!\nStargirlnails Kiel`,
|
||||
text: `Hallo ${booking.customerName},\n\nwir haben deinen Termin am ${formattedDate} um ${booking.appointmentTime} bestätigt.\n\nWichtiger Hinweis: Die Allgemeinen Geschäftsbedingungen (AGB) findest du im Anhang dieser E-Mail. Bitte lies sie vor deinem Termin durch.\n\nTermin-Status ansehen und verwalten: ${bookingUrl}\nFalls du den Termin stornieren möchtest, kannst du das über den obigen Link tun.\n\nRechtliche Informationen: ${generateUrl('/legal')}\nZur Website: ${homepageUrl}\n\nBis bald!\nStargirlnails Kiel`,
|
||||
html,
|
||||
cc: process.env.ADMIN_EMAIL ? [process.env.ADMIN_EMAIL] : undefined,
|
||||
}, {
|
||||
|
Reference in New Issue
Block a user