feat/booking-panel #57

Merged
Hammond merged 2 commits from feat/booking-panel into master 2025-12-04 19:53:22 +00:00
2 changed files with 43 additions and 6 deletions

View File

@ -104,6 +104,14 @@ export default function BookNowPage() {
const [showSignupDialog, setShowSignupDialog] = useState(false); const [showSignupDialog, setShowSignupDialog] = useState(false);
const [loginPrefillEmail, setLoginPrefillEmail] = useState<string | undefined>(undefined); const [loginPrefillEmail, setLoginPrefillEmail] = useState<string | undefined>(undefined);
// Require authentication before accessing booking page
useEffect(() => {
if (!isAuthenticated) {
// Show login dialog immediately if not authenticated
setShowLoginDialog(true);
}
}, [isAuthenticated]);
// Helper function to convert day name to day number (0-6) // Helper function to convert day name to day number (0-6)
const getDayNumber = (dayName: string): number => { const getDayNumber = (dayName: string): number => {
const dayMap: Record<string, number> = { const dayMap: Record<string, number> = {
@ -241,15 +249,21 @@ export default function BookNowPage() {
const handleLoginSuccess = async () => { const handleLoginSuccess = async () => {
// Close login dialog // Close login dialog
setShowLoginDialog(false); setShowLoginDialog(false);
// After successful login, proceed with booking submission // If there's a pending booking submission, proceed with it
await submitBooking(); // Otherwise, just close the dialog and allow user to fill the form
if (formData.selectedSlots.length > 0 && formData.firstName && formData.lastName && formData.email) {
await submitBooking();
}
}; };
const handleSignupSuccess = async () => { const handleSignupSuccess = async () => {
// Close signup dialog // Close signup dialog
setShowSignupDialog(false); setShowSignupDialog(false);
// After successful signup, proceed with booking submission // If there's a pending booking submission, proceed with it
await submitBooking(); // Otherwise, just close the dialog and allow user to fill the form
if (formData.selectedSlots.length > 0 && formData.firstName && formData.lastName && formData.email) {
await submitBooking();
}
}; };
const handleSwitchToSignup = () => { const handleSwitchToSignup = () => {
@ -652,6 +666,13 @@ export default function BookNowPage() {
<p className={`text-sm ${isDark ? 'text-red-200' : 'text-red-800'}`}>{error}</p> <p className={`text-sm ${isDark ? 'text-red-200' : 'text-red-800'}`}>{error}</p>
</div> </div>
)} )}
{!isAuthenticated && (
<div className={`mb-6 p-4 rounded-lg border ${isDark ? 'bg-yellow-900/20 border-yellow-800' : 'bg-yellow-50 border-yellow-200'}`}>
<p className={`text-sm ${isDark ? 'text-yellow-200' : 'text-yellow-800'}`}>
Please log in to book an appointment. The login dialog should appear automatically.
</p>
</div>
)}
<form onSubmit={handleSubmit} className="space-y-6"> <form onSubmit={handleSubmit} className="space-y-6">
{/* Personal Information Section */} {/* Personal Information Section */}
<div className="space-y-4"> <div className="space-y-4">
@ -678,6 +699,7 @@ export default function BookNowPage() {
} }
maxLength={100} maxLength={100}
required required
disabled={!isAuthenticated}
className={`h-11 ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900 placeholder:text-gray-500'}`} className={`h-11 ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900 placeholder:text-gray-500'}`}
/> />
</div> </div>
@ -699,6 +721,7 @@ export default function BookNowPage() {
} }
maxLength={100} maxLength={100}
required required
disabled={!isAuthenticated}
className={`h-11 ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900 placeholder:text-gray-500'}`} className={`h-11 ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900 placeholder:text-gray-500'}`}
/> />
</div> </div>
@ -720,6 +743,7 @@ export default function BookNowPage() {
onChange={(e) => handleChange("email", e.target.value)} onChange={(e) => handleChange("email", e.target.value)}
maxLength={100} maxLength={100}
required required
disabled={!isAuthenticated}
className={`h-11 ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900 placeholder:text-gray-500'}`} className={`h-11 ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900 placeholder:text-gray-500'}`}
/> />
</div> </div>
@ -740,6 +764,7 @@ export default function BookNowPage() {
onChange={(e) => handleChange("phone", e.target.value)} onChange={(e) => handleChange("phone", e.target.value)}
maxLength={100} maxLength={100}
required required
disabled={!isAuthenticated}
className={`h-11 ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900 placeholder:text-gray-500'}`} className={`h-11 ${isDark ? 'bg-gray-700 border-gray-600 text-white placeholder:text-gray-400' : 'bg-white border-gray-300 text-gray-900 placeholder:text-gray-500'}`}
/> />
</div> </div>
@ -814,9 +839,14 @@ export default function BookNowPage() {
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
if (!isAuthenticated) {
setShowLoginDialog(true);
return;
}
// Pass the specific day and time slot for this button // Pass the specific day and time slot for this button
handleSlotToggle(currentDay, normalizedTimeSlot); handleSlotToggle(currentDay, normalizedTimeSlot);
}} }}
disabled={!isAuthenticated}
aria-pressed={isSelected} aria-pressed={isSelected}
className={`flex items-center gap-2 cursor-pointer px-4 py-2 rounded-lg border-2 transition-all focus:outline-none focus:ring-2 focus:ring-rose-500 ${ className={`flex items-center gap-2 cursor-pointer px-4 py-2 rounded-lg border-2 transition-all focus:outline-none focus:ring-2 focus:ring-rose-500 ${
isSelected isSelected
@ -861,6 +891,7 @@ export default function BookNowPage() {
value={formData.message} value={formData.message}
onChange={(e) => handleChange("message", e.target.value)} onChange={(e) => handleChange("message", e.target.value)}
maxLength={100} maxLength={100}
disabled={!isAuthenticated}
className={`w-full rounded-md border px-3 py-2 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-rose-500 focus-visible:border-rose-500 disabled:cursor-not-allowed disabled:opacity-50 ${isDark ? 'border-gray-600 bg-gray-700 text-white placeholder:text-gray-400 focus-visible:ring-rose-400 focus-visible:border-rose-400' : 'border-gray-300 bg-white text-gray-900 placeholder:text-gray-500'}`} className={`w-full rounded-md border px-3 py-2 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-rose-500 focus-visible:border-rose-500 disabled:cursor-not-allowed disabled:opacity-50 ${isDark ? 'border-gray-600 bg-gray-700 text-white placeholder:text-gray-400 focus-visible:ring-rose-400 focus-visible:border-rose-400' : 'border-gray-300 bg-white text-gray-900 placeholder:text-gray-500'}`}
/> />
</div> </div>
@ -870,7 +901,7 @@ export default function BookNowPage() {
<Button <Button
type="submit" type="submit"
size="lg" size="lg"
disabled={isCreating || availableDaysOfWeek.length === 0 || formData.selectedSlots.length === 0} disabled={!isAuthenticated || isCreating || availableDaysOfWeek.length === 0 || formData.selectedSlots.length === 0}
className="w-full bg-gradient-to-r from-rose-500 to-pink-600 hover:from-rose-600 hover:to-pink-700 text-white shadow-lg hover:shadow-xl transition-all h-12 text-base font-semibold disabled:opacity-50 disabled:cursor-not-allowed" className="w-full bg-gradient-to-r from-rose-500 to-pink-600 hover:from-rose-600 hover:to-pink-700 text-white shadow-lg hover:shadow-xl transition-all h-12 text-base font-semibold disabled:opacity-50 disabled:cursor-not-allowed"
> >
{isCreating ? ( {isCreating ? (
@ -915,6 +946,12 @@ export default function BookNowPage() {
<LoginDialog <LoginDialog
open={showLoginDialog} open={showLoginDialog}
onOpenChange={(open) => { onOpenChange={(open) => {
// Prevent closing if user is not authenticated (force login)
if (!open && !isAuthenticated) {
// Redirect to home if they try to close without logging in
router.push("/");
return;
}
setShowLoginDialog(open); setShowLoginDialog(open);
if (!open) { if (!open) {
setLoginPrefillEmail(undefined); setLoginPrefillEmail(undefined);

View File

@ -84,7 +84,7 @@ export function Navbar() {
whileTap={{ scale: 0.95 }} whileTap={{ scale: 0.95 }}
> >
<Link <Link
href={isAuthenticated && !isAdmin ? "/user/dashboard" : "/"} href="/"
className="flex items-center gap-1.5 sm:gap-2" className="flex items-center gap-1.5 sm:gap-2"
> >
<div className="bg-gradient-to-r from-rose-500 to-pink-600 p-1.5 sm:p-2 rounded-lg sm:rounded-xl"> <div className="bg-gradient-to-r from-rose-500 to-pink-600 p-1.5 sm:p-2 rounded-lg sm:rounded-xl">