import React, { useState, useEffect } from "react"; import { useQuery, useMutation } from "@tanstack/react-query"; import { queryClient } from "@/client/rpc-client"; interface BookingStatusPageProps { token: string; } type BookingStatus = "pending" | "confirmed" | "cancelled" | "completed"; interface Treatment { id: string; name: string; duration: number; price: number; } interface BookingDetails { id: string; customerName: string; customerEmail?: string; customerPhone?: string; appointmentDate: string; appointmentTime: string; treatments: Treatment[]; totalDuration: number; totalPrice: number; status: BookingStatus; notes?: string; formattedDate: string; createdAt: string; canCancel: boolean; hoursUntilAppointment: number; } interface RescheduleProposalDetails { booking: { id: string; customerName: string; customerEmail?: string; customerPhone?: string; status: BookingStatus; treatments: Treatment[]; totalDuration: number; totalPrice: number; }; original: { date: string; time: string }; proposed: { date?: string; time?: string }; expiresAt: string; hoursUntilExpiry: number; isExpired: boolean; canRespond: boolean; } function getStatusInfo(status: BookingStatus) { switch (status) { case "pending": return { label: "Wartet auf Bestätigung", color: "yellow", icon: "⏳", bgColor: "bg-yellow-50", borderColor: "border-yellow-200", textColor: "text-yellow-800", badgeColor: "bg-yellow-100 text-yellow-800", }; case "confirmed": return { label: "Bestätigt", color: "green", icon: "✓", bgColor: "bg-green-50", borderColor: "border-green-200", textColor: "text-green-800", badgeColor: "bg-green-100 text-green-800", }; case "cancelled": return { label: "Storniert", color: "red", icon: "✕", bgColor: "bg-red-50", borderColor: "border-red-200", textColor: "text-red-800", badgeColor: "bg-red-100 text-red-800", }; case "completed": return { label: "Abgeschlossen", color: "gray", icon: "✓", bgColor: "bg-gray-50", borderColor: "border-gray-200", textColor: "text-gray-800", badgeColor: "bg-gray-100 text-gray-800", }; } } export default function BookingStatusPage({ token }: BookingStatusPageProps) { const [showCancelConfirm, setShowCancelConfirm] = useState(false); const [isCancelling, setIsCancelling] = useState(false); const [cancellationResult, setCancellationResult] = useState<{ success: boolean; message: string; formattedDate?: string } | null>(null); const [rescheduleProposal, setRescheduleProposal] = useState(null); const [rescheduleResult, setRescheduleResult] = useState<{ success: boolean; message: string } | null>(null); const [isAccepting, setIsAccepting] = useState(false); const [isDeclining, setIsDeclining] = useState(false); const [showDeclineConfirm, setShowDeclineConfirm] = useState(false); const [oneClickAction, setOneClickAction] = useState(null); const [oneClickLoading, setOneClickLoading] = useState(false); // Fetch booking details const { data: booking, isLoading, error, refetch, error: bookingError } = useQuery( queryClient.cancellation.getBookingByToken.queryOptions({ input: { token } }) ); // Try fetching reschedule proposal if booking not found or error const rescheduleQuery = useQuery({ ...queryClient.cancellation.getRescheduleProposal.queryOptions({ input: { token } }), enabled: !!token && (!!bookingError || !booking), }); // Handle reschedule proposal data useEffect(() => { if (rescheduleQuery.data) { setRescheduleProposal(rescheduleQuery.data); } else if (rescheduleQuery.error) { setRescheduleProposal(null); } }, [rescheduleQuery.data, rescheduleQuery.error]); // Cancellation mutation const cancelMutation = useMutation({ ...queryClient.cancellation.cancelByToken.mutationOptions(), onSuccess: (result: any) => { setCancellationResult({ success: true, message: result.message, formattedDate: result.formattedDate, }); setIsCancelling(false); setShowCancelConfirm(false); refetch(); // Refresh booking data }, onError: (error: any) => { setCancellationResult({ success: false, message: error?.message || "Ein Fehler ist aufgetreten.", }); setIsCancelling(false); }, }); const acceptMutation = useMutation({ ...queryClient.bookings.acceptReschedule.mutationOptions(), onSuccess: (result: any) => { setRescheduleResult({ success: true, message: result.message }); setIsAccepting(false); setShowDeclineConfirm(false); refetch(); }, onError: (error: any) => { setRescheduleResult({ success: false, message: error?.message || 'Ein Fehler ist aufgetreten.' }); setIsAccepting(false); } }); const declineMutation = useMutation({ ...queryClient.bookings.declineReschedule.mutationOptions(), onSuccess: (result: any) => { setRescheduleResult({ success: true, message: result.message }); setIsDeclining(false); setShowDeclineConfirm(false); }, onError: (error: any) => { setRescheduleResult({ success: false, message: error?.message || 'Ein Fehler ist aufgetreten.' }); setIsDeclining(false); } }); const handleCancel = () => { setIsCancelling(true); setCancellationResult(null); cancelMutation.mutate({ token }); }; // Handle one-click actions from URL parameters useEffect(() => { if (rescheduleProposal && !oneClickAction) { const urlParams = new URLSearchParams(window.location.search); const action = urlParams.get('action'); if (action === 'accept' || action === 'decline') { setOneClickAction(action); } } }, [rescheduleProposal, oneClickAction]); // Auto-execute one-click action useEffect(() => { if (oneClickAction && rescheduleProposal && !oneClickLoading && !rescheduleResult) { setOneClickLoading(true); if (oneClickAction === 'accept') { const confirmAccept = window.confirm( `Möchtest du den neuen Termin am ${rescheduleProposal.proposed.date || 'TBD'} um ${rescheduleProposal.proposed.time || 'TBD'} Uhr akzeptieren?` ); if (confirmAccept) { acceptMutation.mutate({ token }); } else { setOneClickLoading(false); setOneClickAction(null); } } else if (oneClickAction === 'decline') { const confirmDecline = window.confirm( `Möchtest du den Vorschlag ablehnen? Dein ursprünglicher Termin am ${rescheduleProposal.original.date} um ${rescheduleProposal.original.time} Uhr bleibt dann bestehen.` ); if (confirmDecline) { declineMutation.mutate({ token }); } else { setOneClickLoading(false); setOneClickAction(null); } } } }, [oneClickAction, rescheduleProposal, oneClickLoading, rescheduleResult, acceptMutation, declineMutation, token]); // Reset one-click loading when mutations complete useEffect(() => { if (rescheduleResult) { setOneClickLoading(false); setOneClickAction(null); } }, [rescheduleResult]); if (isLoading) { return (
Buchung wird geladen...
); } if (error && !rescheduleProposal) { return (

Link nicht verfügbar

Dieser Buchungslink ist nicht mehr verfügbar. Mögliche Gründe:

  • • Der Link ist abgelaufen
  • • Die Buchung wurde bereits storniert
  • • Der Link wurde bereits verwendet
Neue Buchung erstellen

Bei Fragen wende dich direkt an uns.

); } if (!booking && !rescheduleProposal) { return (

Buchung nicht gefunden

Die angeforderte Buchung konnte nicht gefunden werden.

Zur Startseite
); } if (rescheduleProposal) { const isExpired = rescheduleProposal.isExpired; const handleAccept = () => { setIsAccepting(true); setRescheduleResult(null); acceptMutation.mutate({ token }); }; const handleDecline = () => { setIsDeclining(true); setRescheduleResult(null); declineMutation.mutate({ token }); }; return (
Stargil Nails Logo ⚠️ Terminänderung vorgeschlagen

Vorschlag zur Terminänderung

Bitte bestätige, ob der neue Termin für dich passt.

{oneClickLoading && (

{oneClickAction === 'accept' ? 'Akzeptiere Termin...' : 'Lehne Termin ab...'}

)} {rescheduleResult && (

{rescheduleResult.message}

)}

Vergleich

Aktueller Termin
{rescheduleProposal.original.date} um {rescheduleProposal.original.time} Uhr
{rescheduleProposal.booking.treatments && rescheduleProposal.booking.treatments.length > 0 ? ( <> {rescheduleProposal.booking.treatments.length <= 2 ? ( rescheduleProposal.booking.treatments.map((t, i) => (
{t.name}
)) ) : ( <> {rescheduleProposal.booking.treatments.slice(0, 2).map((t, i) => (
{t.name}
))}
+{rescheduleProposal.booking.treatments.length - 2} weitere
)}
{rescheduleProposal.booking.totalDuration} Min
) : ( Keine Behandlungen )}
Neuer Vorschlag
{rescheduleProposal.proposed.date || 'TBD'} um {rescheduleProposal.proposed.time || 'TBD'} Uhr
{rescheduleProposal.booking.treatments && rescheduleProposal.booking.treatments.length > 0 ? ( <> {rescheduleProposal.booking.treatments.length <= 2 ? ( rescheduleProposal.booking.treatments.map((t, i) => (
{t.name}
)) ) : ( <> {rescheduleProposal.booking.treatments.slice(0, 2).map((t, i) => (
{t.name}
))}
+{rescheduleProposal.booking.treatments.length - 2} weitere
)}
{rescheduleProposal.booking.totalDuration} Min
) : ( Keine Behandlungen )}
Bitte antworte bis {new Date(rescheduleProposal.expiresAt).toLocaleDateString('de-DE')} {new Date(rescheduleProposal.expiresAt).toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })} ({rescheduleProposal.hoursUntilExpiry} Stunden).
{!isExpired && !rescheduleResult && !oneClickLoading && (
Wenn du ablehnst, bleibt dein ursprünglicher Termin bestehen.
)} {isExpired && (

Diese Terminänderung ist abgelaufen. Dein ursprünglicher Termin bleibt bestehen. Bei Fragen kontaktiere uns bitte.

)} {showDeclineConfirm && (

Vorschlag ablehnen?

Bist du sicher, dass du den neuen Terminvorschlag ablehnen möchtest?
Dein ursprünglicher Termin am {rescheduleProposal.original.date} um {rescheduleProposal.original.time} bleibt dann bestehen.

)}
); } const statusInfo = getStatusInfo(booking?.status || "pending"); return (
{/* Header */}
Stargil Nails Logo {statusInfo.icon} {statusInfo.label}

Buchungsübersicht

Hier findest du alle Details zu deinem Termin

{/* Cancellation Result */} {cancellationResult && (

{cancellationResult.message} {cancellationResult.formattedDate && ( <>
Stornierter Termin: {cancellationResult.formattedDate} )}

)} {/* Status Banner */}
{statusInfo.icon}

Status: {statusInfo.label}

{booking?.status === "pending" && (

Wir haben deine Terminanfrage erhalten und werden sie in Kürze prüfen. Du erhältst eine E-Mail, sobald dein Termin bestätigt wurde.

)} {booking?.status === "confirmed" && (

Dein Termin wurde bestätigt! Wir freuen uns auf dich. Du hast eine Bestätigungs-E-Mail mit Kalendereintrag erhalten.

)} {booking?.status === "cancelled" && (

Dieser Termin wurde storniert. Du kannst jederzeit einen neuen Termin buchen.

)} {booking?.status === "completed" && (

Dieser Termin wurde erfolgreich abgeschlossen. Vielen Dank für deinen Besuch!

)}
{/* Appointment Details */}

Termin-Details

Datum: {booking?.formattedDate}
Uhrzeit: {booking?.appointmentTime} Uhr
{/* Treatments List */}
Behandlungen:
{booking?.treatments && booking.treatments.length > 0 ? (
{booking.treatments.map((treatment, index) => (
• {treatment.name} {treatment.duration} Min - {treatment.price.toFixed(2)} €
))}
Gesamt: {booking.totalDuration} Min - {booking.totalPrice.toFixed(2)} €
) : (
Keine Behandlungen angegeben {((booking?.totalDuration ?? 0) > 0 || (booking?.totalPrice ?? 0) > 0) && (
Gesamt: {booking?.totalDuration ?? 0} Min - {(booking?.totalPrice ?? 0).toFixed(2)} €
)}
)}
{booking?.hoursUntilAppointment && booking.hoursUntilAppointment > 0 && booking.status !== "cancelled" && booking.status !== "completed" && (
Verbleibende Zeit: {booking.hoursUntilAppointment} Stunde{booking.hoursUntilAppointment !== 1 ? 'n' : ''}
)}
{/* Customer Details */}

Deine Daten

Name: {booking?.customerName}
E-Mail: {booking?.customerEmail || '—'}
Telefon: {booking?.customerPhone || '—'}
{booking?.notes && (

Notizen:

{booking.notes}

)}
{/* Cancellation Section */} {booking?.canCancel && !cancellationResult?.success && (

Termin stornieren

{!showCancelConfirm ? (

Du kannst diesen Termin noch bis {parseInt(process.env.MIN_STORNO_TIMESPAN || "24")} Stunden vor dem Termin kostenlos stornieren.

) : (

Bist du sicher?

Diese Aktion kann nicht rückgängig gemacht werden. Der Termin wird storniert und der Slot wird wieder für andere Kunden verfügbar.

)}
)} {!booking?.canCancel && booking?.status !== "cancelled" && booking?.status !== "completed" && (

ℹ️ Stornierungsfrist abgelaufen: Dieser Termin liegt weniger als {parseInt(process.env.MIN_STORNO_TIMESPAN || "24")} Stunden in der Zukunft und kann nicht mehr online storniert werden. Bitte kontaktiere uns direkt.

)} {/* Footer */}
); }