// page-book.jsx — Meeting booking calendar /* ── Mini calendar ────────────────────────────────────────────── */ function BookingCalendar({ date, onSelect }) { const today = new Date(); today.setHours(0, 0, 0, 0); const [viewYear, setViewYear] = React.useState(date ? date.getFullYear() : today.getFullYear()); const [viewMonth, setViewMonth] = React.useState(date ? date.getMonth() : today.getMonth()); const MONTHS = ['January','February','March','April','May','June','July','August','September','October','November','December']; const DAYS = ['Su','Mo','Tu','We','Th','Fr','Sa']; const maxDate = new Date(today); maxDate.setMonth(maxDate.getMonth() + 3); const goMonth = (dir) => { let m = viewMonth + dir, y = viewYear; if (m < 0) { m = 11; y--; } if (m > 11) { m = 0; y++; } setViewYear(y); setViewMonth(m); }; const canPrev = (viewYear > today.getFullYear()) || (viewYear === today.getFullYear() && viewMonth > today.getMonth()); const firstDow = new Date(viewYear, viewMonth, 1).getDay(); const daysInMonth = new Date(viewYear, viewMonth + 1, 0).getDate(); return (
{MONTHS[viewMonth]} {viewYear}
{DAYS.map(d =>
{d}
)} {Array.from({ length: firstDow }, (_, i) =>
)} {Array.from({ length: daysInMonth }, (_, i) => { const d = new Date(viewYear, viewMonth, i + 1); const isPast = d < today; const isSun = d.getDay() === 0; const isFar = d > maxDate; const disabled = isPast || isSun || isFar; const isSel = date && d.toDateString() === date.toDateString(); const isToday = d.toDateString() === today.toDateString(); return ( ); })}

Mon – Sat · 10 AM – 7 PM IST

); } /* ── Main booking page ────────────────────────────────────────── */ function BookingPage() { const [step, setStep] = React.useState(1); const [duration, setDuration] = React.useState(null); const [selDate, setSelDate] = React.useState(null); const [selSlot, setSelSlot] = React.useState(null); const [slots, setSlots] = React.useState([]); const [loadingSlots, setLoadingSlots] = React.useState(false); const [slotsErr, setSlotsErr] = React.useState(null); const [form, setForm] = React.useState({ name: '', email: '', note: '' }); const [formErr, setFormErr] = React.useState({}); const [submitting, setSubmitting] = React.useState(false); const [submitErr, setSubmitErr] = React.useState(null); const [confirmed, setConfirmed] = React.useState(null); const API = (window.BOOKING_API_URL || '').replace(/\/$/, ''); /* ── Helpers ── */ const fmtISO = (d) => { if (!d) return ''; const y = d.getFullYear(); const m = String(d.getMonth() + 1).padStart(2, '0'); const day = String(d.getDate()).padStart(2, '0'); return `${y}-${m}-${day}`; }; const fmtTime = (iso) => { if (!iso) return ''; return new Date(iso).toLocaleTimeString('en-IN', { hour: 'numeric', minute: '2-digit', hour12: true, timeZone: 'Asia/Kolkata' }); }; const fmtDateLong = (d) => { if (!d) return ''; return d.toLocaleDateString('en-IN', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' }); }; /* ── Fetch slots when date or duration changes ── */ React.useEffect(() => { if (!selDate || !duration) return; setLoadingSlots(true); setSlotsErr(null); setSelSlot(null); setSlots([]); fetch(`${API}/api/booking.php?action=slots&date=${fmtISO(selDate)}&duration=${duration}`) .then(r => { if (!r.ok) throw new Error('server'); return r.json(); }) .then(data => { setSlots(data.slots || []); setLoadingSlots(false); }) .catch(() => { setSlotsErr('Could not load available times. Please try again.'); setLoadingSlots(false); }); }, [selDate, duration]); /* ── Handlers ── */ const pickDuration = (d) => { setDuration(d); setSelSlot(null); setStep(2); }; const pickDate = (d) => { setSelDate(d); setSelSlot(null); }; const update = (k) => (e) => setForm({ ...form, [k]: e.target.value }); const validate = () => { const e = {}; if (!form.name.trim()) e.name = 'Required'; if (!form.email.includes('@')) e.email = 'Valid email required'; return e; }; const submitBooking = async (e) => { e.preventDefault(); const errs = validate(); setFormErr(errs); if (Object.keys(errs).length > 0) return; setSubmitting(true); setSubmitErr(null); try { const res = await fetch(`${API}/api/booking.php`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: form.name.trim(), email: form.email.trim(), note: form.note.trim(), start: selSlot.start, end: selSlot.end, duration }) }); const data = await res.json(); if (!res.ok) throw new Error(data.error || 'Booking failed'); setConfirmed(data); setStep(4); } catch (err) { setSubmitErr(err.message || 'Something went wrong. Please try again.'); } finally { setSubmitting(false); } }; /* ── Duration options ── */ const DURATIONS = [ { mins: 15, label: 'Quick Chat', icon: '⚡' }, { mins: 30, label: 'Consultation', icon: '☕' }, { mins: 60, label: 'Full Meeting', icon: '📋' }, ]; /* ── Step labels ── */ const STEPS = ['Duration', 'Date & Time', 'Your Details']; /* ── Confirmation screen ── */ if (step === 4 && confirmed) { return (

You’re booked.

A calendar invite with the Google Meet link has been sent to your inbox. We look forward to speaking with you.

Meeting confirmed

Check your inbox for the calendar invite.

{[ ['With', 'Satnam Singh · Harjog Solutions'], ['Date', fmtDateLong(selDate)], ['Time', `${fmtTime(selSlot && selSlot.start)} IST · ${duration} min`], ['For', `${form.name} · ${form.email}`], ].map(([label, val]) => (
{label} {val}
))}
{confirmed.meetLink && ( )}
); } return (

Book a meeting.

Schedule time directly on Satnam’s calendar. Every booking includes a Google Meet link sent to your inbox.

{/* ── Step indicator ── */}
{STEPS.map((s, i) => (
i + 1 ? ' done' : ''}`}> {step > i + 1 ? '✓' : i + 1} {s}
))}
{/* ── Step 1: Duration ── */} {step === 1 && (

Choose meeting type

{DURATIONS.map((d, i) => ( pickDuration(d.mins)}> {d.icon} {d.mins} minutes {d.label} ))}
)} {/* ── Step 2: Date & Time ── */} {step === 2 && (

Choose a date

{!selDate ? (
Select a date to see available times
) : ( <>

{fmtDateLong(selDate)}

{loadingSlots &&
Loading available times…
} {slotsErr &&
{slotsErr}
} {!loadingSlots && !slotsErr && slots.length === 0 && (
No slots available on this day. Try another date.
)} {!loadingSlots && !slotsErr && slots.length > 0 && (
{slots.map(s => ( ))}
)} )}
)} {/* ── Step 3: Details ── */} {step === 3 && (

Your details