// ContactCalendar.jsx - booking calendar that replaces the contact form on the Deezer homepage. // Flow: pick date → pick time → form (name/email/company/topic/notes) → thanks. // Dostępność i rezerwacja idą przez window.BookingAPI (booking-api.js, ładowany przed tym plikiem). function ContactCalendar() { const MONTHS_PL = ['styczeń','luty','marzec','kwiecień','maj','czerwiec','lipiec','sierpień','wrzesień','październik','listopad','grudzień']; const MONTHS_PL_DM = ['stycznia','lutego','marca','kwietnia','maja','czerwca','lipca','sierpnia','września','października','listopada','grudnia']; const DOW_PL = ['Pn','Wt','Śr','Cz','Pt','Sb','Nd']; const DOW_PL_FULL = ['Niedziela','Poniedziałek','Wtorek','Środa','Czwartek','Piątek','Sobota']; const TOPICS = ['Bezpłatny audyt','Automatyzacja','Aplikacja webowa','System rezerwacji','Agent AI','Inne']; const today = React.useMemo(() => { const d = new Date(); d.setHours(0,0,0,0); return d; }, []); const [step, setStep] = React.useState(0); // 0 kalendarz, 1 formularz, 2 podziękowanie const [date, setDate] = React.useState(null); const [time, setTime] = React.useState(null); const [month, setMonth] = React.useState(() => new Date(today.getFullYear(), today.getMonth(), 1)); const [form, setForm] = React.useState({ name:'', email:'', company:'', topic:'', notes:'', _hp:'' }); // dostępność z backendu const [availability, setAvailability] = React.useState({}); // { 'YYYY-MM-DD': ['10:30', ...] } const [loadState, setLoadState] = React.useState('idle'); // 'idle'|'loading'|'ok'|'error' const [meetLink, setMeetLink] = React.useState(null); const [submitting, setSubmitting] = React.useState(false); const [bookingError, setBookingError] = React.useState(null); // 'slot_taken'|'error'|null React.useEffect(() => { if (window.lucide) lucide.createIcons(); }); // helpers const pad = (n) => String(n).padStart(2,'0'); const fmtKey = (d) => `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}`; const monthKey = (mDate) => `${mDate.getFullYear()}-${pad(mDate.getMonth()+1)}`; const fmtPrettyDate = (d) => `${DOW_PL_FULL[d.getDay()]}, ${d.getDate()} ${MONTHS_PL_DM[d.getMonth()]} ${d.getFullYear()}`; // pobierz dostępność dla widocznego miesiąca const loadMonth = React.useCallback((mDate) => { if (!window.BookingAPI) { setLoadState('error'); return; } setLoadState('loading'); window.BookingAPI.fetchAvailability(monthKey(mDate)) .then((res) => { setAvailability((res && res.days) || {}); setLoadState('ok'); }) .catch(() => { setLoadState('error'); }); }, []); React.useEffect(() => { loadMonth(month); }, [month, loadMonth]); const slotsForKey = (key) => availability[key] || []; const daysGrid = React.useMemo(() => { const year = month.getFullYear(), m = month.getMonth(); const offset = (new Date(year, m, 1).getDay() + 6) % 7; const daysInMonth = new Date(year, m + 1, 0).getDate(); const cells = []; for (let i = 0; i < offset; i++) cells.push(null); for (let d = 1; d <= daysInMonth; d++) cells.push(new Date(year, m, d)); return cells; }, [month]); const monthIsCurrent = month.getFullYear() === today.getFullYear() && month.getMonth() === today.getMonth(); const prevMonth = () => { const prev = new Date(month.getFullYear(), month.getMonth() - 1, 1); if (prev < new Date(today.getFullYear(), today.getMonth(), 1)) return; setMonth(prev); }; const nextMonth = () => setMonth(new Date(month.getFullYear(), month.getMonth() + 1, 1)); const selectedDateObj = date ? new Date(date) : null; const slots = date ? slotsForKey(date) : []; const pickDate = (d) => { setDate(fmtKey(d)); setTime(null); }; const pickTime = (t) => { setTime(t); setBookingError(null); setStep(1); }; const goBackToCal = () => setStep(0); const submit = (e) => { if (e) e.preventDefault(); if (submitting) return; setBookingError(null); setSubmitting(true); const payload = { date, time, name: form.name, email: form.email, company: form.company, topic: form.topic, notes: form.notes, _hp: form._hp }; window.BookingAPI.createBooking(payload) .then((res) => { setMeetLink((res && res.meetLink) || null); setStep(2); }) .catch((err) => { if (err && err.reason === 'slot_taken') { setBookingError('slot_taken'); setStep(0); setTime(null); loadMonth(month); // odśwież — slot mógł zniknąć } else { setBookingError('error'); } }) .finally(() => setSubmitting(false)); }; const restart = () => { setStep(0); setDate(null); setTime(null); setMeetLink(null); setBookingError(null); setForm({ name:'', email:'', company:'', topic:'', notes:'', _hp:'' }); loadMonth(month); }; const canSubmit = form.name.trim() && form.email.trim() && form.company.trim() && form.topic && !submitting; // ── renderery ── const renderProgress = () => (
Wysłałem Ci zaproszenie na maila{meetLink ? ' z linkiem do spotkania' : ''}. Do zobaczenia!
30 minut, w których poznaję Twój biznes i mówię wprost, jak mogę Ci pomóc.