Fix TypeScript errors for Docker build

- Fix optional chaining for booking properties
- Fix useMutation isLoading to isPending
- Fix email parameter types
- Fix expiredDetails array typing
This commit is contained in:
2025-10-05 16:28:28 +02:00
parent a8cec16d7a
commit 6cf657168b
4 changed files with 47 additions and 31 deletions

View File

@@ -73,7 +73,7 @@ export function AdminCalendar() {
); );
// Propose reschedule mutation // Propose reschedule mutation
const { mutate: proposeReschedule, isLoading: isProposingReschedule } = useMutation( const { mutate: proposeReschedule, isPending: isProposingReschedule } = useMutation(
queryClient.bookings.proposeReschedule.mutationOptions() queryClient.bookings.proposeReschedule.mutationOptions()
); );

View File

@@ -71,13 +71,20 @@ export default function BookingStatusPage({ token }: BookingStatusPageProps) {
); );
// Try fetching reschedule proposal if booking not found or error // Try fetching reschedule proposal if booking not found or error
useQuery({ const rescheduleQuery = useQuery({
...queryClient.cancellation.getRescheduleProposal.queryOptions({ input: { token } }), ...queryClient.cancellation.getRescheduleProposal.queryOptions({ input: { token } }),
enabled: !!token && (!!bookingError || !booking), enabled: !!token && (!!bookingError || !booking),
onSuccess: (data: any) => setRescheduleProposal(data),
onError: () => setRescheduleProposal(null),
}); });
// Handle reschedule proposal data
useEffect(() => {
if (rescheduleQuery.data) {
setRescheduleProposal(rescheduleQuery.data);
} else if (rescheduleQuery.error) {
setRescheduleProposal(null);
}
}, [rescheduleQuery.data, rescheduleQuery.error]);
// Cancellation mutation // Cancellation mutation
const cancelMutation = useMutation({ const cancelMutation = useMutation({
...queryClient.cancellation.cancelByToken.mutationOptions(), ...queryClient.cancellation.cancelByToken.mutationOptions(),
@@ -387,7 +394,7 @@ export default function BookingStatusPage({ token }: BookingStatusPageProps) {
); );
} }
const statusInfo = getStatusInfo(booking.status); const statusInfo = getStatusInfo(booking?.status || "pending");
return ( return (
<div className="min-h-screen bg-gradient-to-br from-pink-50 to-purple-50 py-8 px-4"> <div className="min-h-screen bg-gradient-to-br from-pink-50 to-purple-50 py-8 px-4">
@@ -430,22 +437,22 @@ export default function BookingStatusPage({ token }: BookingStatusPageProps) {
<h2 className={`text-xl font-bold ${statusInfo.textColor} mb-2`}> <h2 className={`text-xl font-bold ${statusInfo.textColor} mb-2`}>
Status: {statusInfo.label} Status: {statusInfo.label}
</h2> </h2>
{booking.status === "pending" && ( {booking?.status === "pending" && (
<p className={statusInfo.textColor}> <p className={statusInfo.textColor}>
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. 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.
</p> </p>
)} )}
{booking.status === "confirmed" && ( {booking?.status === "confirmed" && (
<p className={statusInfo.textColor}> <p className={statusInfo.textColor}>
Dein Termin wurde bestätigt! Wir freuen uns auf dich. Du hast eine Bestätigungs-E-Mail mit Kalendereintrag erhalten. Dein Termin wurde bestätigt! Wir freuen uns auf dich. Du hast eine Bestätigungs-E-Mail mit Kalendereintrag erhalten.
</p> </p>
)} )}
{booking.status === "cancelled" && ( {booking?.status === "cancelled" && (
<p className={statusInfo.textColor}> <p className={statusInfo.textColor}>
Dieser Termin wurde storniert. Du kannst jederzeit einen neuen Termin buchen. Dieser Termin wurde storniert. Du kannst jederzeit einen neuen Termin buchen.
</p> </p>
)} )}
{booking.status === "completed" && ( {booking?.status === "completed" && (
<p className={statusInfo.textColor}> <p className={statusInfo.textColor}>
Dieser Termin wurde erfolgreich abgeschlossen. Vielen Dank für deinen Besuch! Dieser Termin wurde erfolgreich abgeschlossen. Vielen Dank für deinen Besuch!
</p> </p>
@@ -465,27 +472,27 @@ export default function BookingStatusPage({ token }: BookingStatusPageProps) {
<div className="space-y-3"> <div className="space-y-3">
<div className="flex justify-between py-2 border-b border-gray-100"> <div className="flex justify-between py-2 border-b border-gray-100">
<span className="text-gray-600">Datum:</span> <span className="text-gray-600">Datum:</span>
<span className="font-medium text-gray-900">{booking.formattedDate}</span> <span className="font-medium text-gray-900">{booking?.formattedDate}</span>
</div> </div>
<div className="flex justify-between py-2 border-b border-gray-100"> <div className="flex justify-between py-2 border-b border-gray-100">
<span className="text-gray-600">Uhrzeit:</span> <span className="text-gray-600">Uhrzeit:</span>
<span className="font-medium text-gray-900">{booking.appointmentTime} Uhr</span> <span className="font-medium text-gray-900">{booking?.appointmentTime} Uhr</span>
</div> </div>
<div className="flex justify-between py-2 border-b border-gray-100"> <div className="flex justify-between py-2 border-b border-gray-100">
<span className="text-gray-600">Behandlung:</span> <span className="text-gray-600">Behandlung:</span>
<span className="font-medium text-gray-900">{booking.treatmentName}</span> <span className="font-medium text-gray-900">{booking?.treatmentName}</span>
</div> </div>
<div className="flex justify-between py-2 border-b border-gray-100"> <div className="flex justify-between py-2 border-b border-gray-100">
<span className="text-gray-600">Dauer:</span> <span className="text-gray-600">Dauer:</span>
<span className="font-medium text-gray-900">{booking.treatmentDuration} Minuten</span> <span className="font-medium text-gray-900">{booking?.treatmentDuration} Minuten</span>
</div> </div>
{booking.treatmentPrice > 0 && ( {booking?.treatmentPrice && booking.treatmentPrice > 0 && (
<div className="flex justify-between py-2 border-b border-gray-100"> <div className="flex justify-between py-2 border-b border-gray-100">
<span className="text-gray-600">Preis:</span> <span className="text-gray-600">Preis:</span>
<span className="font-medium text-gray-900">{booking.treatmentPrice.toFixed(2)} </span> <span className="font-medium text-gray-900">{booking.treatmentPrice.toFixed(2)} </span>
</div> </div>
)} )}
{booking.hoursUntilAppointment > 0 && booking.status !== "cancelled" && booking.status !== "completed" && ( {booking?.hoursUntilAppointment && booking.hoursUntilAppointment > 0 && booking.status !== "cancelled" && booking.status !== "completed" && (
<div className="flex justify-between py-2"> <div className="flex justify-between py-2">
<span className="text-gray-600">Verbleibende Zeit:</span> <span className="text-gray-600">Verbleibende Zeit:</span>
<span className="font-medium text-pink-600"> <span className="font-medium text-pink-600">
@@ -507,18 +514,18 @@ export default function BookingStatusPage({ token }: BookingStatusPageProps) {
<div className="space-y-3"> <div className="space-y-3">
<div className="flex justify-between py-2 border-b border-gray-100"> <div className="flex justify-between py-2 border-b border-gray-100">
<span className="text-gray-600">Name:</span> <span className="text-gray-600">Name:</span>
<span className="font-medium text-gray-900">{booking.customerName}</span> <span className="font-medium text-gray-900">{booking?.customerName}</span>
</div> </div>
<div className="flex justify-between py-2 border-b border-gray-100"> <div className="flex justify-between py-2 border-b border-gray-100">
<span className="text-gray-600">E-Mail:</span> <span className="text-gray-600">E-Mail:</span>
<span className="font-medium text-gray-900">{booking.customerEmail || '—'}</span> <span className="font-medium text-gray-900">{booking?.customerEmail || '—'}</span>
</div> </div>
<div className="flex justify-between py-2"> <div className="flex justify-between py-2">
<span className="text-gray-600">Telefon:</span> <span className="text-gray-600">Telefon:</span>
<span className="font-medium text-gray-900">{booking.customerPhone || '—'}</span> <span className="font-medium text-gray-900">{booking?.customerPhone || '—'}</span>
</div> </div>
</div> </div>
{booking.notes && ( {booking?.notes && (
<div className="mt-4 pt-4 border-t border-gray-200"> <div className="mt-4 pt-4 border-t border-gray-200">
<h3 className="text-sm font-semibold text-gray-700 mb-2">Notizen:</h3> <h3 className="text-sm font-semibold text-gray-700 mb-2">Notizen:</h3>
<p className="text-gray-600 text-sm">{booking.notes}</p> <p className="text-gray-600 text-sm">{booking.notes}</p>
@@ -527,7 +534,7 @@ export default function BookingStatusPage({ token }: BookingStatusPageProps) {
</div> </div>
{/* Cancellation Section */} {/* Cancellation Section */}
{booking.canCancel && !cancellationResult?.success && ( {booking?.canCancel && !cancellationResult?.success && (
<div className="bg-white rounded-lg shadow-lg p-6 mb-6"> <div className="bg-white rounded-lg shadow-lg p-6 mb-6">
<h2 className="text-lg font-semibold text-gray-900 mb-4 flex items-center"> <h2 className="text-lg font-semibold text-gray-900 mb-4 flex items-center">
<svg className="w-5 h-5 mr-2 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-5 h-5 mr-2 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -587,7 +594,7 @@ export default function BookingStatusPage({ token }: BookingStatusPageProps) {
</div> </div>
)} )}
{!booking.canCancel && booking.status !== "cancelled" && booking.status !== "completed" && ( {!booking?.canCancel && booking?.status !== "cancelled" && booking?.status !== "completed" && (
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4 mb-6"> <div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4 mb-6">
<p className="text-yellow-800 text-sm"> <p className="text-yellow-800 text-sm">
<strong> Stornierungsfrist abgelaufen:</strong> 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. <strong> Stornierungsfrist abgelaufen:</strong> 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.

View File

@@ -612,13 +612,12 @@ const createManual = os
cancellationUrl: bookingUrl cancellationUrl: bookingUrl
}); });
await sendEmailWithAGBAndCalendar({ await sendEmailWithAGBAndCalendar({
to: input.customerEmail, to: input.customerEmail!,
subject: "Dein Termin wurde bestätigt - AGB im Anhang", subject: "Dein Termin wurde bestätigt - AGB im Anhang",
text: `Hallo ${input.customerName},\n\nwir haben deinen Termin am ${formattedDate} um ${input.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`, text: `Hallo ${input.customerName},\n\nwir haben deinen Termin am ${formattedDate} um ${input.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, html,
bcc: process.env.ADMIN_EMAIL ? [process.env.ADMIN_EMAIL] : undefined, }, {
}, {
date: input.appointmentDate, date: input.appointmentDate,
time: input.appointmentTime, time: input.appointmentTime,
durationMinutes: treatment.duration, durationMinutes: treatment.duration,

View File

@@ -343,7 +343,17 @@ export const router = {
} }
// Get booking details for each expired proposal // Get booking details for each expired proposal
const expiredDetails = []; const expiredDetails: Array<{
customerName: string;
originalDate: string;
originalTime: string;
proposedDate: string;
proposedTime: string;
treatmentName: string;
customerEmail?: string;
customerPhone?: string;
expiredAt: string;
}> = [];
for (const proposal of expiredProposals) { for (const proposal of expiredProposals) {
const booking = await bookingsKV.getItem(proposal.bookingId); const booking = await bookingsKV.getItem(proposal.bookingId);
if (booking) { if (booking) {
@@ -353,8 +363,8 @@ export const router = {
customerName: booking.customerName, customerName: booking.customerName,
originalDate: proposal.originalDate || booking.appointmentDate, originalDate: proposal.originalDate || booking.appointmentDate,
originalTime: proposal.originalTime || booking.appointmentTime, originalTime: proposal.originalTime || booking.appointmentTime,
proposedDate: proposal.proposedDate, proposedDate: proposal.proposedDate!,
proposedTime: proposal.proposedTime, proposedTime: proposal.proposedTime!,
treatmentName: treatment?.name || "Unbekannte Behandlung", treatmentName: treatment?.name || "Unbekannte Behandlung",
customerEmail: booking.customerEmail, customerEmail: booking.customerEmail,
customerPhone: booking.customerPhone, customerPhone: booking.customerPhone,