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() {