diff --git a/app/(admin)/_components/header.tsx b/app/(admin)/_components/header.tsx index 7bc7ac6..c1a65a0 100644 --- a/app/(admin)/_components/header.tsx +++ b/app/(admin)/_components/header.tsx @@ -97,7 +97,7 @@ export function Header() { )} - + {/* Thumbtack Design at Top Right */}
@@ -170,7 +170,7 @@ export function Header() { - + {/* Thumbtack Design at Top Right */}
diff --git a/app/book-now/page.tsx b/app/book-now/page.tsx new file mode 100644 index 0000000..e003c1c --- /dev/null +++ b/app/book-now/page.tsx @@ -0,0 +1,647 @@ +"use client"; + +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { + Calendar, + Clock, + User, + Mail, + Phone, + MessageSquare, + ArrowLeft, + Heart, + CheckCircle2, + CheckCircle, + Loader2, +} from "lucide-react"; +import Link from "next/link"; +import Image from "next/image"; +import { useRouter } from "next/navigation"; +import { LoginDialog } from "@/components/LoginDialog"; + +interface User { + ID: number; + CreatedAt?: string; + UpdatedAt?: string; + DeletedAt?: string | null; + first_name: string; + last_name: string; + email: string; + phone: string; + location: string; + date_of_birth?: string; + is_admin?: boolean; + bookings?: null; +} + +interface Booking { + ID: number; + CreatedAt: string; + UpdatedAt: string; + DeletedAt: string | null; + user_id: number; + user: User; + scheduled_at: string; + duration: number; + status: string; + jitsi_room_id: string; + jitsi_room_url: string; + payment_id: string; + payment_status: string; + amount: number; + notes: string; +} + +interface BookingsResponse { + bookings: Booking[]; + limit: number; + offset: number; + total: number; +} + +export default function BookNowPage() { + const router = useRouter(); + const [formData, setFormData] = useState({ + firstName: "", + lastName: "", + email: "", + phone: "", + appointmentType: "", + preferredDate: "", + preferredTime: "", + message: "", + }); + const [loading, setLoading] = useState(false); + const [booking, setBooking] = useState(null); + const [error, setError] = useState(null); + const [showLoginDialog, setShowLoginDialog] = useState(false); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + // Open login dialog instead of submitting directly + setShowLoginDialog(true); + }; + + const handleLoginSuccess = async () => { + // After successful login, proceed with booking submission + await submitBooking(); + }; + + const submitBooking = async () => { + setLoading(true); + setError(null); + + try { + // Convert time to 24-hour format for ISO string + const time24 = formData.preferredTime.includes("PM") + ? formData.preferredTime.replace("PM", "").trim().split(":").map((v, i) => + i === 0 ? (parseInt(v) === 12 ? 12 : parseInt(v) + 12) : v + ).join(":") + : formData.preferredTime.replace("AM", "").trim().split(":").map((v, i) => + i === 0 ? (parseInt(v) === 12 ? "00" : v.padStart(2, "0")) : v + ).join(":"); + + // Combine date and time into scheduled_at (ISO format) + const dateTimeString = `${formData.preferredDate}T${time24}:00Z`; + + // Prepare request payload + const payload = { + first_name: formData.firstName, + last_name: formData.lastName, + email: formData.email, + phone: formData.phone, + appointment_type: formData.appointmentType, + scheduled_at: dateTimeString, + duration: 60, // Default to 60 minutes + notes: formData.message || "", + }; + + // Simulate API call - Replace with actual API endpoint + const response = await fetch("/api/bookings", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + 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", + }; + } + + setBooking(bookingData); + setLoading(false); + } catch (err) { + setError("Failed to submit booking. Please try again."); + setLoading(false); + console.error("Booking error:", err); + } + }; + + const handleChange = (field: string, value: string) => { + setFormData((prev) => ({ ...prev, [field]: value })); + }; + + const formatDateTime = (dateString: string) => { + const date = new Date(dateString); + return date.toLocaleString("en-US", { + month: "short", + day: "numeric", + year: "numeric", + hour: "numeric", + minute: "2-digit", + hour12: true, + }); + }; + + return ( +
+ {/* Main Content */} +
+ {/* Left Side - Image (Fixed) */} +
+
+ Therapy session +
+
+ + {/* Logo at Top */} +
+ +
+ +
+ + Attune Heart Therapy + + +
+ + {/* Overlay Content - Lower Position */} +
+
+

+ Begin Your Journey to Wellness +

+

+ Take the first step towards healing and growth. Our compassionate team is here to support you every step of the way. +

+ + {/* Features List */} +
+
+
+ +
+ Safe and confidential environment +
+
+
+ +
+ Experienced licensed therapists +
+
+
+ +
+ Personalized treatment plans +
+
+
+ +
+ Flexible scheduling options +
+
+
+
+
+ + {/* Right Side - Form (Scrollable) */} +
+
+
+ {/* Page Header */} +
+ +
+

+ Book Your Appointment +

+

+ Fill out the form below and we'll get back to you to confirm your appointment +

+
+
+ + {/* Booking Form or Success Message */} +
+ {booking ? ( +
+
+
+ +
+
+

+ Booking Confirmed! +

+

+ Your appointment has been successfully booked. +

+
+
+
+

Booking ID

+

#{booking.ID}

+
+
+

Patient

+

+ {booking.user.first_name} {booking.user.last_name} +

+
+
+

Scheduled Time

+

{formatDateTime(booking.scheduled_at)}

+
+
+

Duration

+

{booking.duration} minutes

+
+
+

Status

+ + {booking.status} + +
+
+

Amount

+

${booking.amount}

+
+ {booking.notes && ( +
+

Notes

+

{booking.notes}

+
+ )} +
+
+ + +
+
+
+ ) : ( + <> +
+ {error && ( +
+

{error}

+
+ )} +
+ {/* Personal Information Section */} +
+

+ + Personal Information +

+ +
+
+ + + handleChange("firstName", e.target.value) + } + required + className="h-11" + /> +
+ +
+ + + handleChange("lastName", e.target.value) + } + required + className="h-11" + /> +
+
+ +
+ + handleChange("email", e.target.value)} + required + className="h-11" + /> +
+ +
+ + handleChange("phone", e.target.value)} + required + className="h-11" + /> +
+
+ + {/* Appointment Details Section */} +
+

+ + Appointment Details +

+ +
+ + +
+ +
+
+ + + handleChange("preferredDate", e.target.value) + } + required + min={new Date().toISOString().split("T")[0]} + className="h-11" + /> +
+ +
+ + +
+
+
+ + {/* Additional Message Section */} +
+ +