Fix: Slot reservation only after successful email validation

- Move email validation before slot reservation in backend
- Remove duplicate frontend email validation
- Slots are no longer blocked by failed booking attempts
- Clean up unused email error UI components
- Ensure slots remain available if email validation fails
This commit is contained in:
2025-10-02 13:39:13 +02:00
parent 73cf733c5f
commit 5baa231d3c
5 changed files with 64 additions and 48 deletions

View File

@@ -96,12 +96,15 @@ const create = os
);
}
// Deep email validation using Rapid Email Validator API
// Email validation before slot reservation
console.log(`Validating email: ${input.customerEmail}`);
const emailValidation = await validateEmail(input.customerEmail);
console.log(`Email validation result:`, emailValidation);
if (!emailValidation.valid) {
console.log(`Email validation failed: ${emailValidation.reason}`);
throw new Error(emailValidation.reason || "Ungültige E-Mail-Adresse");
}
// Validate that the booking is not in the past
const today = new Date().toISOString().split("T")[0]; // YYYY-MM-DD
if (input.appointmentDate < today) {
@@ -118,8 +121,8 @@ const create = os
}
// Prevent double booking: same customer email with pending/confirmed on same date
// Skip duplicate check in development mode
if (process.env.NODE_ENV !== 'development') {
// Skip duplicate check when DISABLE_DUPLICATE_CHECK is set
if (!process.env.DISABLE_DUPLICATE_CHECK) {
const existing = await kv.getAllItems();
const hasConflict = existing.some(b =>
b.customerEmail.toLowerCase() === input.customerEmail.toLowerCase() &&
@@ -137,7 +140,11 @@ const create = os
status: "pending" as const,
createdAt: new Date().toISOString()
};
// If a slotId is provided, tentatively reserve the slot (mark reserved but pending)
// First save the booking
await kv.setItem(id, booking);
// Then reserve the slot only after successful booking creation
if (booking.slotId) {
const slot = await availabilityKV.getItem(booking.slotId);
if (!slot) throw new Error("Availability slot not found");
@@ -149,12 +156,11 @@ const create = os
};
await availabilityKV.setItem(slot.id, updatedSlot);
}
await kv.setItem(id, booking);
// Notify customer: request received (pending)
void (async () => {
// Create booking access token for status viewing
const bookingAccessToken = await queryClient.cancellation.createToken({ input: { bookingId: id } });
const bookingAccessToken = await queryClient.cancellation.createToken({ bookingId: id });
const bookingUrl = generateUrl(`/booking/${bookingAccessToken.token}`);
const formattedDate = formatDateGerman(input.appointmentDate);
@@ -222,8 +228,10 @@ const create = os
}
})();
return booking;
} catch (error) {
} catch (error) {
console.error("Booking creation error:", error);
// Re-throw the error for oRPC to handle
throw error;
}
});
@@ -297,7 +305,7 @@ const updateStatus = os
try {
if (input.status === "confirmed") {
// Create booking access token for this booking (status + cancellation)
const bookingAccessToken = await queryClient.cancellation.createToken({ input: { bookingId: booking.id } });
const bookingAccessToken = await queryClient.cancellation.createToken({ bookingId: booking.id });
const formattedDate = formatDateGerman(booking.appointmentDate);
const bookingUrl = generateUrl(`/booking/${bookingAccessToken.token}`);