Merge version_2 into main #2

Merged
bender merged 1 commits from version_2 into main 2026-03-03 17:05:28 +00:00

View File

@@ -9,9 +9,359 @@ import FeatureCardOne from "@/components/sections/feature/FeatureCardOne";
import TestimonialCardSix from "@/components/sections/testimonial/TestimonialCardSix";
import ContactSplit from "@/components/sections/contact/ContactSplit";
import FooterBase from "@/components/sections/footer/FooterBase";
import { Star, Sparkles, Award, Heart, Mail } from "lucide-react";
import { Star, Sparkles, Award, Heart, Mail, Calendar, X, Clock, Users, CheckCircle } from "lucide-react";
import { useState } from "react";
import React from "react";
interface TimeSlot {
time: string;
available: boolean;
}
interface BookingState {
isOpen: boolean;
step: 'service' | 'date' | 'time' | 'confirmation';
selectedService: string | null;
selectedDate: string | null;
selectedTime: string | null;
name: string;
email: string;
phone: string;
}
const SERVICES = [
{ id: 'cosmetic', name: 'Cosmetic Dentistry', duration: '1 hour' },
{ id: 'implants', name: 'Dental Implants', duration: '2 hours' },
{ id: 'cleaning', name: 'Professional Cleaning', duration: '45 minutes' },
{ id: 'root-canal', name: 'Root Canal', duration: '1.5 hours' },
{ id: 'orthodontics', name: 'Orthodontics Consultation', duration: '30 minutes' },
];
const TIME_SLOTS: TimeSlot[] = [
{ time: '09:00 AM', available: true },
{ time: '09:30 AM', available: true },
{ time: '10:00 AM', available: false },
{ time: '10:30 AM', available: true },
{ time: '11:00 AM', available: true },
{ time: '02:00 PM', available: true },
{ time: '02:30 PM', available: false },
{ time: '03:00 PM', available: true },
{ time: '03:30 PM', available: true },
{ time: '04:00 PM', available: true },
];
function BookingModal({ isOpen, onClose, onSubmit }: { isOpen: boolean; onClose: () => void; onSubmit: (data: any) => void }) {
const [state, setState] = React.useState<BookingState>({
isOpen,
step: 'service',
selectedService: null,
selectedDate: null,
selectedTime: null,
name: '',
email: '',
phone: '',
});
React.useEffect(() => {
setState(prev => ({ ...prev, isOpen }));
}, [isOpen]);
const handleServiceSelect = (serviceId: string) => {
setState(prev => ({
...prev,
selectedService: serviceId,
step: 'date',
}));
};
const handleDateSelect = (date: string) => {
setState(prev => ({
...prev,
selectedDate: date,
step: 'time',
}));
};
const handleTimeSelect = (time: string) => {
setState(prev => ({
...prev,
selectedTime: time,
step: 'confirmation',
}));
};
const handleInputChange = (field: string, value: string) => {
setState(prev => ({
...prev,
[field]: value,
}));
};
const handleSubmit = () => {
const service = SERVICES.find(s => s.id === state.selectedService);
onSubmit({
service: service?.name,
date: state.selectedDate,
time: state.selectedTime,
name: state.name,
email: state.email,
phone: state.phone,
});
onClose();
};
const getNextAvailableDate = () => {
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
return tomorrow.toISOString().split('T')[0];
};
const getDateRangeOptions = () => {
const dates = [];
for (let i = 1; i <= 30; i++) {
const date = new Date();
date.setDate(date.getDate() + i);
dates.push(date.toISOString().split('T')[0]);
}
return dates;
};
const formatDate = (dateStr: string) => {
return new Date(dateStr + 'T00:00:00').toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric' });
};
if (!isOpen) return null;
return (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
<div className="bg-white rounded-2xl max-w-2xl w-full max-h-[90vh] overflow-y-auto shadow-2xl">
{/* Header */}
<div className="sticky top-0 bg-gradient-to-r from-blue-50 to-blue-100 px-6 py-6 flex justify-between items-center border-b">
<h2 className="text-2xl font-bold text-gray-900">Schedule Your Appointment</h2>
<button
onClick={onClose}
className="p-2 hover:bg-blue-200 rounded-lg transition"
>
<X size={24} className="text-gray-600" />
</button>
</div>
{/* Progress Indicator */}
<div className="px-6 py-4 bg-gray-50 border-b">
<div className="flex justify-between items-center">
{['Service', 'Date', 'Time', 'Confirm'].map((label, idx) => (
<React.Fragment key={label}>
<div className={`flex flex-col items-center ${
state.step === ['service', 'date', 'time', 'confirmation'][idx]
? 'text-blue-600'
: idx < ['service', 'date', 'time', 'confirmation'].indexOf(state.step)
? 'text-green-600'
: 'text-gray-400'
}`}>
<div className={`w-8 h-8 rounded-full flex items-center justify-center font-bold ${
state.step === ['service', 'date', 'time', 'confirmation'][idx]
? 'bg-blue-600 text-white'
: idx < ['service', 'date', 'time', 'confirmation'].indexOf(state.step)
? 'bg-green-600 text-white'
: 'bg-gray-200 text-gray-600'
}`}>
{idx < ['service', 'date', 'time', 'confirmation'].indexOf(state.step) ? '✓' : idx + 1}
</div>
<span className="text-xs mt-1">{label}</span>
</div>
{idx < 3 && (
<div className={`flex-1 h-1 mx-2 ${
idx < ['service', 'date', 'time', 'confirmation'].indexOf(state.step)
? 'bg-green-600'
: 'bg-gray-200'
}`} />
)}
</React.Fragment>
))}
</div>
</div>
{/* Content */}
<div className="p-6">
{/* Service Selection */}
{state.step === 'service' && (
<div className="space-y-4">
<h3 className="text-lg font-semibold text-gray-900 mb-6">Select a Service</h3>
<div className="grid grid-cols-1 gap-3">
{SERVICES.map(service => (
<button
key={service.id}
onClick={() => handleServiceSelect(service.id)}
className={`p-4 rounded-lg border-2 text-left transition ${
state.selectedService === service.id
? 'border-blue-600 bg-blue-50'
: 'border-gray-200 hover:border-blue-400'
}`}
>
<div className="font-semibold text-gray-900">{service.name}</div>
<div className="text-sm text-gray-500 flex items-center gap-1 mt-1">
<Clock size={14} /> {service.duration}
</div>
</button>
))}
</div>
<button
onClick={() => setState(prev => ({ ...prev, step: 'date' }))}
disabled={!state.selectedService}
className="w-full mt-6 px-6 py-3 bg-blue-600 text-white rounded-lg font-semibold hover:bg-blue-700 disabled:bg-gray-300 transition"
>
Continue
</button>
</div>
)}
{/* Date Selection */}
{state.step === 'date' && (
<div className="space-y-4">
<h3 className="text-lg font-semibold text-gray-900 mb-6">Select a Date</h3>
<div className="grid grid-cols-4 gap-2 max-h-64 overflow-y-auto">
{getDateRangeOptions().map(date => (
<button
key={date}
onClick={() => handleDateSelect(date)}
className={`p-3 rounded-lg border-2 text-center text-sm font-medium transition ${
state.selectedDate === date
? 'border-blue-600 bg-blue-50 text-blue-600'
: 'border-gray-200 hover:border-blue-400 text-gray-700'
}`}
>
{formatDate(date)}
</button>
))}
</div>
<div className="flex gap-3 mt-6">
<button
onClick={() => setState(prev => ({ ...prev, step: 'service' }))}
className="flex-1 px-6 py-3 border-2 border-gray-300 text-gray-700 rounded-lg font-semibold hover:bg-gray-50 transition"
>
Back
</button>
<button
onClick={() => setState(prev => ({ ...prev, step: 'time' }))}
disabled={!state.selectedDate}
className="flex-1 px-6 py-3 bg-blue-600 text-white rounded-lg font-semibold hover:bg-blue-700 disabled:bg-gray-300 transition"
>
Continue
</button>
</div>
</div>
)}
{/* Time Selection */}
{state.step === 'time' && (
<div className="space-y-4">
<h3 className="text-lg font-semibold text-gray-900 mb-6">Select a Time</h3>
<div className="grid grid-cols-3 gap-2">
{TIME_SLOTS.map(slot => (
<button
key={slot.time}
onClick={() => slot.available && handleTimeSelect(slot.time)}
disabled={!slot.available}
className={`p-3 rounded-lg border-2 text-center font-medium transition ${
state.selectedTime === slot.time
? 'border-blue-600 bg-blue-50 text-blue-600'
: slot.available
? 'border-gray-200 hover:border-blue-400 text-gray-700'
: 'border-gray-100 bg-gray-100 text-gray-400 cursor-not-allowed'
}`}
>
{slot.time}
</button>
))}
</div>
<div className="flex gap-3 mt-6">
<button
onClick={() => setState(prev => ({ ...prev, step: 'date' }))}
className="flex-1 px-6 py-3 border-2 border-gray-300 text-gray-700 rounded-lg font-semibold hover:bg-gray-50 transition"
>
Back
</button>
<button
onClick={() => setState(prev => ({ ...prev, step: 'confirmation' }))}
disabled={!state.selectedTime}
className="flex-1 px-6 py-3 bg-blue-600 text-white rounded-lg font-semibold hover:bg-blue-700 disabled:bg-gray-300 transition"
>
Continue
</button>
</div>
</div>
)}
{/* Confirmation */}
{state.step === 'confirmation' && (
<div className="space-y-6">
<div className="bg-green-50 border border-green-200 rounded-lg p-4 flex gap-3">
<CheckCircle size={24} className="text-green-600 flex-shrink-0" />
<div>
<div className="font-semibold text-green-900">Appointment Details</div>
<div className="text-sm text-green-700 mt-1">
{SERVICES.find(s => s.id === state.selectedService)?.name} on {formatDate(state.selectedDate!)} at {state.selectedTime}
</div>
</div>
</div>
<div className="space-y-4">
<h3 className="text-lg font-semibold text-gray-900">Your Information</h3>
<input
type="text"
placeholder="Full Name"
value={state.name}
onChange={e => handleInputChange('name', e.target.value)}
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:border-blue-600"
/>
<input
type="email"
placeholder="Email Address"
value={state.email}
onChange={e => handleInputChange('email', e.target.value)}
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:border-blue-600"
/>
<input
type="tel"
placeholder="Phone Number"
value={state.phone}
onChange={e => handleInputChange('phone', e.target.value)}
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:border-blue-600"
/>
</div>
<div className="flex gap-3">
<button
onClick={() => setState(prev => ({ ...prev, step: 'time' }))}
className="flex-1 px-6 py-3 border-2 border-gray-300 text-gray-700 rounded-lg font-semibold hover:bg-gray-50 transition"
>
Back
</button>
<button
onClick={handleSubmit}
disabled={!state.name || !state.email || !state.phone}
className="flex-1 px-6 py-3 bg-green-600 text-white rounded-lg font-semibold hover:bg-green-700 disabled:bg-gray-300 transition"
>
Confirm Booking
</button>
</div>
</div>
)}
</div>
</div>
</div>
);
}
export default function LandingPage() {
const [bookingOpen, setBookingOpen] = useState(false);
const handleBookingSubmit = (data: any) => {
console.log('Booking submitted:', data);
// Here you would typically send this to your backend
alert(`Booking confirmed for ${data.name} on ${data.date} at ${data.time}`);
};
return (
<ThemeProvider
defaultButtonVariant="text-stagger"
@@ -25,6 +375,12 @@ export default function LandingPage() {
secondaryButtonStyle="solid"
headingFontWeight="light"
>
<BookingModal
isOpen={bookingOpen}
onClose={() => setBookingOpen(false)}
onSubmit={handleBookingSubmit}
/>
<div id="nav" data-section="nav">
<NavbarLayoutFloatingInline
brandName="Dental Excellence"
@@ -35,7 +391,8 @@ export default function LandingPage() {
{ name: "Contact", id: "contact" },
]}
button={{
text: "Schedule Appointment", href: "#contact"}}
text: "Schedule Appointment", onClick: () => setBookingOpen(true),
}}
animateOnLoad={true}
/>
</div>
@@ -55,7 +412,7 @@ export default function LandingPage() {
]}
enableKpiAnimation={true}
buttons={[
{ text: "Book Consultation", href: "#contact" },
{ text: "Book Consultation", onClick: () => setBookingOpen(true) },
{ text: "Learn More", href: "#services" },
]}
buttonAnimation="slide-up"
@@ -88,7 +445,7 @@ export default function LandingPage() {
]}
gridVariant="three-columns-all-equal-width"
animationType="slide-up"
buttons={[{ text: "View All Services", href: "#contact" }]}
buttons={[{ text: "View All Services", onClick: () => setBookingOpen(true) }]}
buttonAnimation="slide-up"
/>
</div>
@@ -129,7 +486,7 @@ export default function LandingPage() {
]}
gridVariant="three-columns-all-equal-width"
animationType="slide-up"
buttons={[{ text: "Schedule Appointment", href: "#contact" }]}
buttons={[{ text: "Schedule Appointment", onClick: () => setBookingOpen(true) }]}
buttonAnimation="slide-up"
/>
</div>