/* global React, Sparkle, Icon, WhatsAppIcon, SERVICES_LIST */ const { useState, useEffect, useMemo } = React; const SALON = { name: 'Refinada Beauty', phone: '5582999973643', address: 'Av. Siqueira Campos, 1538 — Prado, Maceió/AL', }; const TIMES = ['08:00','09:00','10:00','11:00','12:00','13:00','14:00','15:00','16:00','17:00','18:00']; // Mock unavailability per day index (idx 0..13) const BOOKED = { 0: ['10:00'], 2: ['14:00','15:00'], 4: ['11:00'], 5: ['09:00','10:00'] }; const pad = (n) => n.toString().padStart(2, '0'); const fmtGCalDate = (d) => `${d.getFullYear()}${pad(d.getMonth()+1)}${pad(d.getDate())}T${pad(d.getHours())}${pad(d.getMinutes())}00`; const parseDuration = (t) => { if (!t) return 60; const m = t.match(/(\d+)\s*min/); if (m) return parseInt(m[1]); const h = t.match(/(\d+)\s*h/); if (h) return parseInt(h[1]) * 60; return 60; }; function buildGoogleCalendarUrl(appt, customer) { const start = appt.dateTime; const end = new Date(start.getTime() + parseDuration(appt.service.time) * 60000); const text = encodeURIComponent(`${appt.service.name} — ${customer.name}`); const dates = `${fmtGCalDate(start)}/${fmtGCalDate(end)}`; const details = encodeURIComponent( `Agendamento Refinada Beauty\n\nServiço: ${appt.service.name}\nCliente: ${customer.name}\nTelefone: ${customer.phone}\nDuração: ${appt.service.time}\nValor: R$ ${appt.service.price}\n\nReservado pelo site refinadabeauty.com.br` ); const location = encodeURIComponent(`${SALON.name} — ${SALON.address}`); return `https://calendar.google.com/calendar/render?action=TEMPLATE&text=${text}&dates=${dates}&details=${details}&location=${location}&ctz=America/Maceio`; } function buildWhatsAppUrl(appts, customer) { const lines = appts.map((a, i) => { const d = a.dateTime; const dateStr = d.toLocaleDateString('pt-BR', { weekday: 'long', day: '2-digit', month: 'long' }); const timeStr = d.toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' }); return `*${i+1}.* ${a.service.name}\n ${dateStr} · ${timeStr}`; }).join('\n\n'); const total = appts.reduce((sum, a) => sum + (typeof a.service.price === 'number' ? a.service.price : 0), 0); const msg = `Olá, Refinada Beauty! Sou *${customer.name}* e gostaria de confirmar ${appts.length === 1 ? 'meu agendamento' : 'os seguintes agendamentos'}:\n\n${lines}\n\n*Total estimado:* R$ ${total}\n\nReservei pelo site, aguardando confirmação. ✨`; return `https://wa.me/${SALON.phone}?text=${encodeURIComponent(msg)}`; } // 14-day rolling window starting from "today" (anchored to May 23, 2026 for demo) function buildDays() { const out = []; const base = new Date(2026, 4, 23); for (let i = 0; i < 14; i++) { const d = new Date(base); d.setDate(base.getDate() + i); out.push(d); } return out; } const DAY_LABELS = ['DOM','SEG','TER','QUA','QUI','SEX','SÁB']; const MONTH_LABELS = ['jan','fev','mar','abr','mai','jun','jul','ago','set','out','nov','dez']; function buildDateTime(day, time) { const [h, m] = time.split(':').map(Number); const d = new Date(day); d.setHours(h, m, 0, 0); return d; } // Each appointment row: { service, dayIdx, time } function makeAppt(service, dayIdx = 2, time = '14:00') { return { service, dayIdx, time }; } function Booking({ preselectedService, onConsumePreselection }) { const services = SERVICES_LIST.filter(s => !s.isProduct); const days = useMemo(buildDays, []); // appointments[] — start with one row const [appts, setAppts] = useState([makeAppt(services[6])]); // default: Design de sobrancelhas const [name, setName] = useState(''); const [phone, setPhone] = useState(''); const [confirmed, setConfirmed] = useState(false); const [errors, setErrors] = useState({}); useEffect(() => { if (preselectedService) { // If first slot is the default and untouched, replace it; else add. setAppts(prev => { const exists = prev.some(a => a.service.name === preselectedService.name); if (exists) return prev; if (prev.length === 1 && prev[0].service.name === services[6].name) { return [makeAppt(preselectedService)]; } return [...prev, makeAppt(preselectedService)]; }); setConfirmed(false); if (onConsumePreselection) onConsumePreselection(); } }, [preselectedService]); const updateAppt = (idx, patch) => { setAppts(prev => prev.map((a, i) => i === idx ? { ...a, ...patch } : a)); }; const removeAppt = (idx) => { setAppts(prev => prev.length === 1 ? prev : prev.filter((_, i) => i !== idx)); }; const addAppt = () => { setAppts(prev => [...prev, makeAppt(services[0], 2, '15:00')]); }; const total = appts.reduce((sum, a) => sum + (typeof a.service.price === 'number' ? a.service.price : 0), 0); // Validation const validate = () => { const e = {}; if (!name.trim() || name.trim().length < 2) e.name = 'Informe seu nome'; if (!phone.replace(/\D/g, '') || phone.replace(/\D/g, '').length < 10) e.phone = 'Telefone inválido'; // Check no duplicate date+time const seen = new Set(); for (const a of appts) { const key = `${a.dayIdx}-${a.time}`; if (seen.has(key)) { e.appts = 'Dois serviços no mesmo horário'; break; } seen.add(key); } setErrors(e); return Object.keys(e).length === 0; }; const handleConfirm = () => { if (!validate()) return; setConfirmed(true); const appsWithDate = appts.map(a => ({ ...a, dateTime: buildDateTime(days[a.dayIdx], a.time) })); const waUrl = buildWhatsAppUrl(appsWithDate, { name, phone }); window.open(waUrl, '_blank'); }; const reset = () => { setConfirmed(false); setName(''); setPhone(''); setAppts([makeAppt(services[6])]); }; const formatPhone = (v) => { const d = v.replace(/\D/g, '').slice(0, 11); if (d.length <= 2) return d; if (d.length <= 7) return `(${d.slice(0,2)}) ${d.slice(2)}`; return `(${d.slice(0,2)}) ${d.slice(2, 7)}-${d.slice(7)}`; }; // Build summary appointments with dateTime const apptsForSummary = appts.map(a => ({ ...a, dateTime: buildDateTime(days[a.dayIdx], a.time), })); return (
Agendamento online

Reserve seu momento

Adicione um ou mais serviços, cada um com data e horário próprios. Confirmação no WhatsApp e convite pro seu Google Agenda.

{/* LEFT — form */}
{confirmed ? ( ) : ( <> {appts.map((a, idx) => ( updateAppt(idx, patch)} onRemove={() => removeAppt(idx)} /> ))} {errors.appts && {errors.appts}}
Seus dados
setName(e.target.value)} placeholder="Como podemos te chamar?" style={bkStyles.darkInput}/> {errors.name && {errors.name}}
setPhone(formatPhone(e.target.value))} placeholder="(82) 99999-0000" style={bkStyles.darkInput}/> {errors.phone && {errors.phone}}

Ao confirmar, abrimos o WhatsApp da Refinada com sua reserva detalhada. Em seguida você recebe o link pra adicionar cada agendamento no seu Google Agenda.

)}
{/* RIGHT — sua reserva */}
); } function AppointmentBlock({ idx, total, appt, services, days, onUpdate, onRemove }) { const [openPicker, setOpenPicker] = useState(null); // 'date' | 'time' | null const bookedToday = BOOKED[appt.dayIdx] || []; const isTimeBooked = bookedToday.includes(appt.time); const dayObj = days[appt.dayIdx]; const dayLabel = `${DAY_LABELS[dayObj.getDay()].toLowerCase()}, ${dayObj.getDate()} ${MONTH_LABELS[dayObj.getMonth()]}`; // Close pickers when clicking outside const wrapRef = React.useRef(null); useEffect(() => { if (!openPicker) return; const onDown = (e) => { if (!wrapRef.current?.contains(e.target)) setOpenPicker(null); }; document.addEventListener('mousedown', onDown); return () => document.removeEventListener('mousedown', onDown); }, [openPicker]); return (
{idx + 1} Serviço {idx + 1}
{total > 1 && ( )}
{/* Service select */}
{/* Date + Time triggers (side by side) */}
{openPicker === 'date' && ( { onUpdate({ dayIdx: i }); setOpenPicker('time'); }} /> )}
{openPicker === 'time' && ( { onUpdate({ time: t }); setOpenPicker(null); }} /> )}
); } function DatePopover({ days, selectedIdx, onPick }) { // Group days into 7-column grid; pad first week with empty cells const firstDay = days[0]; const firstOffset = firstDay.getDay(); // 0..6 const cells = []; for (let i = 0; i < firstOffset; i++) cells.push(null); days.forEach((d, i) => cells.push({ d, idx: i })); return (
Escolha uma data Próximos {days.length} dias
{DAY_LABELS.map(d => {d[0]})}
{cells.map((c, i) => { if (!c) return ; const active = c.idx === selectedIdx; const isToday = c.idx === 0; return ( ); })}
); } function TimePopover({ times, booked, selected, onPick }) { return (
Escolha um horário Atendemos 8h – 18h
{times.map(t => { const isBooked = booked.includes(t); const active = t === selected && !isBooked; return ( ); })}
Selecionado Indisponível
); } function PreviewCard({ appts, customer, total, confirmed }) { return (
{confirmed ? 'Reservado' : 'Sua reserva'} {confirmed ? Enviado : Aguardando confirmação}
{appts.map((a, i) => { const dateStr = a.dateTime.toLocaleDateString('pt-BR', { weekday: 'short', day: '2-digit', month: 'short' }); const timeStr = a.dateTime.toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' }); return (
{i + 1}
{a.service.name} {dateStr}   ·   {timeStr}   ·   {a.service.time}
R$ {a.service.price}
); })}
{customer.name}
{SALON.address}
Total estimado R$ {total}
); } function Confirmed({ appts, customer, onAgain }) { return (

Pré-reserva enviada.

Abrimos o WhatsApp da Refinada com {appts.length === 1 ? 'sua reserva' : 'suas reservas'}. Assim que confirmarmos por lá, {appts.length === 1 ? 'o atendimento entra' : 'os atendimentos entram'} na agenda do salão. Adicione ao seu Google Agenda para não esquecer.

{appts.map((a, i) => { const gcalUrl = buildGoogleCalendarUrl(a, customer); const dateStr = a.dateTime.toLocaleDateString('pt-BR', { weekday: 'long', day: '2-digit', month: 'long' }); const timeStr = a.dateTime.toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' }); return (
{a.service.name} {dateStr} · {timeStr}
 Google Agenda
); })}
); } const bkStyles = { section: { background: 'var(--rb-nude)', padding: 'var(--section-pad) var(--page-pad)' }, grid: { alignItems: 'start' }, formCol: { display: 'flex', flexDirection: 'column', gap: 14, minWidth: 0 }, previewCol: { display: 'flex', flexDirection: 'column', gap: 16, position: 'sticky', top: 100, minWidth: 0 }, apptCard: { background: 'var(--rb-creme)', border: '1px solid var(--rb-champagne)', borderRadius: 14, padding: 22, }, apptHead: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }, removeBtn: { width: 30, height: 30, borderRadius: 999, background: 'transparent', border: '1px solid var(--rb-champagne)', color: 'var(--fg-2)', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', transition: 'all 200ms var(--ease-refined)', }, stepDot: { fontFamily: 'var(--font-display)', fontSize: 13, color: 'var(--rb-rose)', background: 'rgba(201,138,125,0.18)', borderRadius: 999, width: 28, height: 28, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', }, stepLabel: { fontFamily: 'var(--font-sans)', fontSize: 11, fontWeight: 500, letterSpacing: '0.22em', textTransform: 'uppercase', color: 'var(--fg-1)', }, addBtn: { display: 'flex', alignItems: 'center', gap: 12, background: 'transparent', border: '1px dashed var(--rb-champagne)', borderRadius: 14, padding: '18px 22px', color: 'var(--fg-1)', cursor: 'pointer', fontFamily: 'var(--font-sans)', fontSize: 13, fontWeight: 500, letterSpacing: '0.14em', textTransform: 'uppercase', transition: 'all 220ms var(--ease-refined)', }, addPlus: { width: 26, height: 26, borderRadius: 999, background: 'var(--rb-rose)', color: 'var(--rb-creme)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontSize: 16, lineHeight: 1, fontWeight: 400, }, customer: { background: 'var(--rb-creme)', border: '1px solid var(--rb-champagne)', borderRadius: 14, padding: 22, marginTop: 6, }, daysRow: { display: 'flex', gap: 8, overflowX: 'auto', paddingBottom: 4, marginTop: 6 }, /* Compact date/time triggers + popovers */ pickerRow: { display: 'flex', gap: 10, flexWrap: 'wrap' }, pickerTrigger: { width: '100%', display: 'flex', alignItems: 'center', gap: 12, background: 'var(--rb-pearl)', border: '1px solid var(--rb-champagne)', borderRadius: 12, padding: '12px 14px', cursor: 'pointer', transition: 'all 220ms var(--ease-refined)', textAlign: 'left', }, pickerTriggerActive: { background: 'rgba(201,138,125,0.12)', borderColor: 'var(--rb-rose)', }, pickerLabel: { fontFamily: 'var(--font-sans)', fontSize: 9, fontWeight: 500, letterSpacing: '0.22em', textTransform: 'uppercase', color: 'var(--fg-3)', }, pickerValue: { fontFamily: 'var(--font-sans)', fontSize: 17, fontWeight: 350, color: 'var(--fg-1)', lineHeight: 1.2, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', maxWidth: '100%', textTransform: 'capitalize', }, dayBtn: { flexShrink: 0, width: 70, background: 'var(--rb-pearl)', border: '1px solid var(--rb-champagne)', borderRadius: 10, padding: '10px 0', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2, cursor: 'pointer', color: 'var(--fg-1)', transition: 'all 240ms var(--ease-refined)', }, dayBtnActive: { background: 'var(--rb-rose)', borderColor: 'transparent', color: 'var(--rb-creme)' }, dayName: { fontFamily: 'var(--font-sans)', fontSize: 10, letterSpacing: '0.18em', opacity: 0.75 }, dayNum: { fontFamily: 'var(--font-display)', fontSize: 22, lineHeight: 1 }, dayMonth: { fontFamily: 'var(--font-sans)', fontSize: 9, letterSpacing: '0.1em', opacity: 0.65, textTransform: 'lowercase' }, timesGrid: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(72px, 1fr))', gap: 8, marginTop: 6 }, timeBtn: { height: 42, borderRadius: 8, background: 'var(--rb-pearl)', border: '1px solid var(--rb-champagne)', fontFamily: 'var(--font-sans)', fontSize: 13, color: 'var(--fg-1)', letterSpacing: '0.06em', cursor: 'pointer', transition: 'all 200ms var(--ease-refined)', }, timeBtnActive: { background: 'var(--rb-rose)', color: 'var(--rb-creme)', borderColor: 'transparent', boxShadow: '0 8px 22px rgba(201,138,125,0.30)' }, timeBtnBooked: { background: 'transparent', color: 'rgba(43,27,18,0.30)', border: '1px dashed rgba(43,27,18,0.18)', textDecoration: 'line-through', cursor: 'not-allowed', }, fieldsRow: { display: 'flex', flexWrap: 'wrap', gap: 14 }, darkInput: { background: 'var(--rb-pearl)', border: '1px solid var(--rb-champagne)', color: 'var(--fg-1)', }, err: { fontFamily: 'var(--font-sans)', fontSize: 11, color: 'var(--status-err)', marginTop: 6, display: 'block' }, submitRow: { display: 'flex', gap: 14, marginTop: 8, flexWrap: 'wrap' }, fineprint: { fontFamily: 'var(--font-sans)', fontSize: 12, lineHeight: 1.65, color: 'var(--fg-3)', marginTop: 16, textWrap: 'pretty' }, /* Preview */ previewCard: { background: 'var(--rb-creme)', borderRadius: 16, padding: 24, display: 'flex', flexDirection: 'column', }, previewItem: { display: 'flex', alignItems: 'flex-start', gap: 12 }, previewItemBullet: { width: 22, height: 22, borderRadius: 999, background: 'rgba(201,138,125,0.18)', color: 'var(--rb-rose-deep)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontFamily: 'var(--font-display)', fontSize: 12, fontWeight: 500, flexShrink: 0, marginTop: 2, }, previewItemName: { fontFamily: 'var(--font-display)', fontSize: 20, color: 'var(--fg-1)', lineHeight: 1.2 }, previewItemMeta: { fontFamily: 'var(--font-sans)', fontSize: 12, color: 'var(--fg-2)', display: 'inline-flex', alignItems: 'center', flexWrap: 'wrap', gap: 4, }, previewItemPrice: { fontFamily: 'var(--font-sans)', fontSize: 16, color: 'var(--fg-1)', fontWeight: 350, whiteSpace: 'nowrap' }, previewClient: { display: 'flex', alignItems: 'center', gap: 8, fontFamily: 'var(--font-sans)', fontSize: 13, color: 'var(--fg-2)', paddingTop: 14, marginTop: 14, borderTop: '1px solid var(--rb-champagne)', }, previewTotal: { display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', borderTop: '1px solid var(--rb-champagne)', paddingTop: 16, marginTop: 18, }, statusOk: { display: 'inline-flex', alignItems: 'center', gap: 6, background: 'rgba(122,140,94,0.18)', color: 'var(--status-ok)', padding: '4px 10px', borderRadius: 999, fontFamily: 'var(--font-sans)', fontSize: 10, fontWeight: 600, letterSpacing: '0.18em', textTransform: 'uppercase', }, statusPending: { background: 'rgba(201,138,125,0.18)', color: 'var(--rb-rose-deep)', padding: '4px 10px', borderRadius: 999, fontFamily: 'var(--font-sans)', fontSize: 10, fontWeight: 600, letterSpacing: '0.18em', textTransform: 'uppercase', }, }; const confirmStyles = { wrap: { display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: 16, padding: '20px 0' }, icon: { width: 80, height: 80, borderRadius: 999, background: 'rgba(201,138,125,0.18)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--rb-rose)' }, h3: { fontFamily: 'var(--font-display)', fontSize: 'clamp(32px, 4vw, 44px)', fontWeight: 400, color: 'var(--fg-1)', margin: 0 }, p: { fontFamily: 'var(--font-sans)', fontSize: 15, lineHeight: 1.65, color: 'var(--fg-2)', margin: 0, maxWidth: 520, textWrap: 'pretty' }, eventList: { display: 'flex', flexDirection: 'column', gap: 10, width: '100%', marginTop: 8 }, eventCard: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 16, background: 'var(--rb-creme)', border: '1px solid var(--rb-champagne)', borderRadius: 12, padding: '14px 18px', flexWrap: 'wrap', }, eventTitle: { fontFamily: 'var(--font-display)', fontSize: 18, color: 'var(--fg-1)', lineHeight: 1.2 }, eventMeta: { fontFamily: 'var(--font-sans)', fontSize: 12, color: 'var(--fg-2)' }, }; const popStyles = { pop: { position: 'absolute', top: 'calc(100% + 8px)', left: 0, right: 0, background: 'var(--rb-creme)', border: '1px solid var(--rb-champagne)', borderRadius: 14, padding: 18, boxShadow: '0 20px 50px rgba(0,0,0,0.35), 0 4px 12px rgba(0,0,0,0.18)', zIndex: 30, minWidth: 280, animation: 'popIn 180ms var(--ease-refined)', }, popHeader: { display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 14, }, popTitle: { fontFamily: 'var(--font-display)', fontSize: 16, color: 'var(--fg-1)', }, popSub: { fontFamily: 'var(--font-sans)', fontSize: 10, letterSpacing: '0.18em', textTransform: 'uppercase', color: 'var(--fg-3)', }, weekHeader: { display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: 4, marginBottom: 6, }, weekDay: { fontFamily: 'var(--font-sans)', fontSize: 10, fontWeight: 600, color: 'var(--fg-3)', textAlign: 'center', letterSpacing: '0.1em', }, daysGrid: { display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: 4, }, dayCell: { position: 'relative', height: 36, borderRadius: 8, background: 'transparent', border: '1px solid transparent', color: 'var(--fg-1)', cursor: 'pointer', fontFamily: 'var(--font-sans)', fontSize: 13, display: 'flex', alignItems: 'center', justifyContent: 'center', transition: 'all 160ms var(--ease-refined)', }, dayCellActive: { background: 'var(--rb-rose)', color: 'var(--rb-creme)', }, dayCellNum: { lineHeight: 1 }, todayDot: { position: 'absolute', bottom: 4, width: 4, height: 4, borderRadius: 999, background: 'var(--rb-rose-deep)', }, timesGrid: { display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 6, }, timeCell: { height: 38, borderRadius: 8, background: 'var(--rb-pearl)', border: '1px solid var(--rb-champagne)', color: 'var(--fg-1)', cursor: 'pointer', fontFamily: 'var(--font-sans)', fontSize: 13, transition: 'all 160ms var(--ease-refined)', }, timeCellActive: { background: 'var(--rb-rose)', borderColor: 'transparent', color: 'var(--rb-creme)', boxShadow: '0 6px 18px rgba(201,138,125,0.30)', }, timeCellBooked: { background: 'transparent', color: 'rgba(43,27,18,0.30)', borderStyle: 'dashed', borderColor: 'rgba(43,27,18,0.18)', cursor: 'not-allowed', textDecoration: 'line-through', }, popLegend: { display: 'flex', justifyContent: 'space-between', marginTop: 14, paddingTop: 12, borderTop: '1px solid var(--rb-champagne)', fontFamily: 'var(--font-sans)', fontSize: 11, color: 'var(--fg-3)', }, legendDot: { width: 9, height: 9, borderRadius: 3, display: 'inline-block', marginRight: 6, verticalAlign: 'middle', }, }; // Pop animation if (typeof document !== 'undefined' && !document.getElementById('picker-anim')) { const s = document.createElement('style'); s.id = 'picker-anim'; s.textContent = `@keyframes popIn { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: translateY(0); } }`; document.head.appendChild(s); } Object.assign(window, { Booking });