From 37531f2b2b3be722a4c582d0659a01758ee0de95 Mon Sep 17 00:00:00 2001 From: iamkiddy Date: Sun, 23 Nov 2025 21:43:13 +0000 Subject: [PATCH] Refactor booking process in BookNowPage to integrate appointment creation via useAppointments hook. Enhance form submission logic to check user authentication before proceeding. Update API endpoint configurations for appointment management, including available dates and user appointments. Improve error handling and user feedback with toast notifications. --- app/(pages)/book-now/page.tsx | 176 ++++++++-------- hooks/useAppointments.ts | 207 +++++++++++++++++++ lib/actions/appointments.ts | 364 ++++++++++++++++++++++++++++++++++ lib/api_urls.ts | 4 + lib/models/appointments.ts | 78 ++++++++ lib/schema/appointments.ts | 43 ++++ 6 files changed, 781 insertions(+), 91 deletions(-) create mode 100644 hooks/useAppointments.ts create mode 100644 lib/actions/appointments.ts create mode 100644 lib/models/appointments.ts create mode 100644 lib/schema/appointments.ts diff --git a/app/(pages)/book-now/page.tsx b/app/(pages)/book-now/page.tsx index b8528af..63f28a3 100644 --- a/app/(pages)/book-now/page.tsx +++ b/app/(pages)/book-now/page.tsx @@ -30,7 +30,9 @@ import Image from "next/image"; import { useRouter } from "next/navigation"; import { LoginDialog } from "@/components/LoginDialog"; import { useAuth } from "@/hooks/useAuth"; +import { useAppointments } from "@/hooks/useAppointments"; import { toast } from "sonner"; +import type { Appointment } from "@/lib/models/appointments"; interface User { ID: number; @@ -77,6 +79,7 @@ export default function BookNowPage() { const { theme } = useAppTheme(); const isDark = theme === "dark"; const { isAuthenticated, logout } = useAuth(); + const { create, isCreating } = useAppointments(); const [formData, setFormData] = useState({ firstName: "", lastName: "", @@ -86,7 +89,6 @@ export default function BookNowPage() { preferredTimes: [] as string[], message: "", }); - const [loading, setLoading] = useState(false); const [booking, setBooking] = useState(null); const [error, setError] = useState(null); const [showLoginDialog, setShowLoginDialog] = useState(false); @@ -100,131 +102,123 @@ export default function BookNowPage() { // Handle submit button click const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - // Open login dialog instead of submitting directly - setShowLoginDialog(true); + + // Check if user is authenticated + if (!isAuthenticated) { + // Open login dialog if not authenticated + setShowLoginDialog(true); + return; + } + + // If authenticated, proceed with booking + await submitBooking(); }; const handleLoginSuccess = async () => { + // Close login dialog + setShowLoginDialog(false); // After successful login, proceed with booking submission await submitBooking(); }; const submitBooking = async () => { - setLoading(true); setError(null); try { if (formData.preferredDays.length === 0) { setError("Please select at least one available day."); - setLoading(false); return; } if (formData.preferredTimes.length === 0) { setError("Please select at least one preferred time."); - setLoading(false); return; } - // For now, we'll use the first selected day and first selected time - // This can be adjusted based on your backend requirements - const firstDay = formData.preferredDays[0]; - const firstTime = formData.preferredTimes[0]; - const timeMap: { [key: string]: string } = { - morning: "09:00", - lunchtime: "12:00", - afternoon: "14:00", - }; - const time24 = timeMap[firstTime] || "09:00"; - - // Get next occurrence of the first selected day + // Convert day names to dates (YYYY-MM-DD format) + // Get next occurrence of each selected day const today = new Date(); const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; - const targetDayIndex = days.indexOf(firstDay); - let daysUntilTarget = (targetDayIndex - today.getDay() + 7) % 7; - if (daysUntilTarget === 0) daysUntilTarget = 7; // Next week if today - const targetDate = new Date(today); - targetDate.setDate(today.getDate() + daysUntilTarget); - const dateString = targetDate.toISOString().split("T")[0]; + const preferredDates: string[] = []; - // Combine date and time into scheduled_at (ISO format) - const dateTimeString = `${dateString}T${time24}:00Z`; + formData.preferredDays.forEach((dayName) => { + const targetDayIndex = days.indexOf(dayName); + if (targetDayIndex === -1) return; + + let daysUntilTarget = (targetDayIndex - today.getDay() + 7) % 7; + if (daysUntilTarget === 0) daysUntilTarget = 7; // Next week if today + + const targetDate = new Date(today); + targetDate.setDate(today.getDate() + daysUntilTarget); + const dateString = targetDate.toISOString().split("T")[0]; + preferredDates.push(dateString); + }); + + // Map time slots - API expects "morning", "afternoon", "evening" + // Form has "morning", "lunchtime", "afternoon" + const timeSlotMap: { [key: string]: "morning" | "afternoon" | "evening" } = { + morning: "morning", + lunchtime: "afternoon", // Map lunchtime to afternoon + afternoon: "afternoon", + }; - // Prepare request payload + const preferredTimeSlots = formData.preferredTimes + .map((time) => timeSlotMap[time] || "morning") + .filter((time, index, self) => self.indexOf(time) === index) as ("morning" | "afternoon" | "evening")[]; // Remove duplicates + + // Prepare request payload according to API spec const payload = { first_name: formData.firstName, last_name: formData.lastName, email: formData.email, - phone: formData.phone, - scheduled_at: dateTimeString, - duration: 60, // Default to 60 minutes - preferred_days: formData.preferredDays, - preferred_times: formData.preferredTimes, - notes: formData.message || "", + preferred_dates: preferredDates, + preferred_time_slots: preferredTimeSlots, + ...(formData.phone && { phone: formData.phone }), + ...(formData.message && { reason: formData.message }), }; - // Simulate API call - Replace with actual API endpoint - const response = await fetch("/api/bookings", { - method: "POST", - headers: { - "Content-Type": "application/json", + // Call the actual API using the hook + const appointmentData = await create(payload); + + // Convert API response to Booking format for display + const bookingData: Booking = { + ID: parseInt(appointmentData.id) || Math.floor(Math.random() * 1000), + CreatedAt: appointmentData.created_at || new Date().toISOString(), + UpdatedAt: appointmentData.updated_at || new Date().toISOString(), + DeletedAt: null, + user_id: 0, // API doesn't return user_id in this response + user: { + ID: 0, + first_name: appointmentData.first_name, + last_name: appointmentData.last_name, + email: appointmentData.email, + phone: appointmentData.phone || "", + location: "", + is_admin: false, + bookings: null, }, - body: JSON.stringify(payload), - }).catch(() => { - // Fallback to mock data if API is not available - return null; - }); - - let bookingData: Booking; - - if (response && response.ok) { - const data: BookingsResponse = await response.json(); - bookingData = data.bookings[0]; - } else { - // Mock response for development - matches the API structure provided - await new Promise((resolve) => setTimeout(resolve, 1000)); - bookingData = { - ID: Math.floor(Math.random() * 1000), - CreatedAt: new Date().toISOString(), - UpdatedAt: new Date().toISOString(), - DeletedAt: null, - user_id: 1, - user: { - ID: 1, - CreatedAt: new Date().toISOString(), - UpdatedAt: new Date().toISOString(), - DeletedAt: null, - first_name: formData.firstName, - last_name: formData.lastName, - email: formData.email, - phone: formData.phone, - location: "", - date_of_birth: "0001-01-01T00:00:00Z", - is_admin: false, - bookings: null, - }, - scheduled_at: dateTimeString, - duration: 60, - status: "scheduled", - jitsi_room_id: `booking-${Math.floor(Math.random() * 1000)}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, - jitsi_room_url: `https://meet.jit.si/booking-${Math.floor(Math.random() * 1000)}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, - payment_id: "", - payment_status: "pending", - amount: 52, - notes: formData.message || "Initial consultation session", - }; - } + scheduled_at: appointmentData.scheduled_datetime || "", + duration: appointmentData.scheduled_duration || 60, + status: appointmentData.status || "pending_review", + jitsi_room_id: appointmentData.jitsi_room_id || "", + jitsi_room_url: appointmentData.jitsi_meet_url || "", + payment_id: "", + payment_status: "pending", + amount: 0, + notes: appointmentData.reason || "", + }; setBooking(bookingData); - setLoading(false); + toast.success("Appointment request submitted successfully! We'll review and get back to you soon."); - // Redirect to home after 2 seconds + // Redirect to user dashboard after 3 seconds setTimeout(() => { - router.push("/"); - }, 2000); + router.push("/user/dashboard"); + }, 3000); } catch (err) { - setError("Failed to submit booking. Please try again."); - setLoading(false); + const errorMessage = err instanceof Error ? err.message : "Failed to submit booking. Please try again."; + setError(errorMessage); + toast.error(errorMessage); console.error("Booking error:", err); } }; @@ -638,10 +632,10 @@ export default function BookNowPage() {