agendapro/src/app/[slug]/agendar/page.tsx

626 lines
31 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client';
import { useState, useEffect, use } from 'react';
import { Calendar, Clock, User, Phone, Mail, ChevronLeft, ChevronRight, Check, Scissors, MapPin, Star, Loader } from 'lucide-react';
import { showToast } from '@/lib/toast';
import ToastContainer from '@/components/Toast';
export default function PublicBookingPage({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = use(params);
const [step, setStep] = useState(1);
const [selectedService, setSelectedService] = useState<string | null>(null);
const [selectedProfessional, setSelectedProfessional] = useState<string | null>(null);
const [selectedDate, setSelectedDate] = useState<string>('');
const [selectedTime, setSelectedTime] = useState<string | null>(null);
const [clientName, setClientName] = useState('');
const [clientPhone, setClientPhone] = useState('');
const [clientEmail, setClientEmail] = useState('');
const [booked, setBooked] = useState(false);
const [slots, setSlots] = useState<string[]>([]);
const [loadingDb, setLoadingDb] = useState(true);
const [dbServices, setDbServices] = useState<any[]>([]);
const [dbProfessionals, setDbProfessionals] = useState<any[]>([]);
const [activeBookingInfo, setActiveBookingInfo] = useState<any>(null);
// Dados do tenant vindos do banco
const [establishment, setEstablishment] = useState({
name: '', type: 'barbearia', description: '', phone: '',
address: '', rating: 5.0, reviews: 0,
allowOnlineBooking: true, bookingAdvanceMin: 60, bookingAdvanceMaxDays: 30
});
const [profileSchedules, setProfileSchedules] = useState<any>({
dailySchedules: null,
profSchedules: {},
interval: 30,
startTime: '09:00',
endTime: '19:00'
});
// Algoritmo dinâmico de fatiamento de grade multi-turno
const generateSlotsForShifts = (shifts: { start: string; end: string }[], intervalMin: number) => {
const generated: string[] = [];
for (const shift of shifts) {
if (!shift.start || !shift.end) continue;
const [sh, sm] = shift.start.split(':').map(Number);
const [eh, em] = shift.end.split(':').map(Number);
let currentMinutes = sh * 60 + sm;
const endMinutes = eh * 60 + em;
while (currentMinutes < endMinutes) {
const h = Math.floor(currentMinutes / 60);
const m = currentMinutes % 60;
const timeStr = `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`;
if (!generated.includes(timeStr)) {
generated.push(timeStr);
}
currentMinutes += intervalMin;
}
}
return generated.sort();
};
const generateSlots = (startTimeStr: string, endTimeStr: string, intervalMin: number) => {
return generateSlotsForShifts([{ start: startTimeStr, end: endTimeStr }], intervalMin);
};
useEffect(() => {
const fetchData = async () => {
try {
const [profileRes, agendaRes, servicesRes] = await Promise.all([
fetch(`/api/tenant/profile?slug=${slug}`),
fetch(`/api/tenant/agenda?slug=${slug}`),
fetch(`/api/tenant/services?slug=${slug}`)
]);
const savedBooking = localStorage.getItem(`agendapro_active_booking_${slug}`);
if (savedBooking) {
try {
const parsed = JSON.parse(savedBooking);
const statusRes = await fetch(`/api/tenant/agenda/appointment?id=${parsed.id}`);
if (statusRes.ok) {
const apt = await statusRes.json();
if (apt.status === 'pending' || apt.status === 'confirmed') {
setActiveBookingInfo(parsed);
setBooked(true);
} else {
localStorage.removeItem(`agendapro_active_booking_${slug}`);
}
} else {
localStorage.removeItem(`agendapro_active_booking_${slug}`);
}
} catch (e) {
localStorage.removeItem(`agendapro_active_booking_${slug}`);
}
}
// Carregar perfil do tenant
if (profileRes.ok) {
const profile = await profileRes.json();
const fullAddress = [profile.address, profile.city, profile.state].filter(Boolean).join(', ');
setEstablishment({
name: profile.name || slug,
type: profile.businessType || 'barbearia',
description: profile.description || '',
phone: profile.phone || '',
address: fullAddress || '',
rating: 5.0,
reviews: 0,
allowOnlineBooking: profile.allowOnlineBooking !== false,
bookingAdvanceMin: profile.bookingAdvanceMin || 60,
bookingAdvanceMaxDays: profile.bookingAdvanceMaxDays || 30,
});
setProfileSchedules({
dailySchedules: profile.dailySchedules || null,
profSchedules: profile.profSchedules || {},
interval: profile.interval || 30,
startTime: profile.startTime || '09:00',
endTime: profile.endTime || '19:00'
});
// Slots iniciais
setSlots(generateSlots(profile.startTime || '09:00', profile.endTime || '19:00', profile.interval || 30));
// Aplicar cor do tema
if (profile.themeColor) {
document.documentElement.style.setProperty('--accent', profile.themeColor);
document.documentElement.style.setProperty('--accent-glow', `${profile.themeColor}26`);
}
} else {
setSlots(generateSlots('09:00', '19:00', 30));
}
if (agendaRes.ok) {
const data = await agendaRes.json();
setDbProfessionals(data.professionals || []);
}
if (servicesRes.ok) {
const data = await servicesRes.json();
setDbServices(data || []);
}
} catch (err) {
console.error('Erro ao buscar dados do banco:', err);
setSlots(generateSlots('09:00', '19:00', 30));
} finally {
setLoadingDb(false);
}
};
fetchData();
}, [slug]);
// Recalcular horários quando mudar data ou profissional
useEffect(() => {
if (!selectedDate) return;
const dateObj = new Date(selectedDate + 'T12:00:00');
const dayOfWeek = dateObj.getDay();
const interval = profileSchedules.interval || 30;
let dayConfig = null;
if (selectedProfessional && profileSchedules.profSchedules?.[selectedProfessional]) {
dayConfig = profileSchedules.profSchedules[selectedProfessional][dayOfWeek];
}
if (!dayConfig && profileSchedules.dailySchedules) {
dayConfig = profileSchedules.dailySchedules[dayOfWeek];
}
if (dayConfig) {
if (!dayConfig.active) {
setSlots([]);
return;
}
if (Array.isArray(dayConfig.shifts) && dayConfig.shifts.length > 0) {
setSlots(generateSlotsForShifts(dayConfig.shifts, interval));
return;
}
}
setSlots(generateSlots(profileSchedules.startTime || '09:00', profileSchedules.endTime || '19:00', interval));
}, [selectedDate, selectedProfessional, profileSchedules]);
const today = new Date();
const maxDays = establishment.bookingAdvanceMaxDays || 30;
const dates = Array.from({ length: maxDays }, (_, i) => {
const d = new Date(today);
d.setDate(d.getDate() + i);
return d;
});
const svc = dbServices.find(s => s.id === selectedService);
const prof = dbProfessionals.find(p => p.id === selectedProfessional);
const canNext = () => {
if (step === 1) return !!selectedService;
if (step === 2) return !!selectedProfessional;
if (step === 3) return !!selectedDate && !!selectedTime;
if (step === 4) return clientName.trim() && clientPhone.trim();
return false;
};
const [showPhoneModal, setShowPhoneModal] = useState(false);
const handleBook = async () => {
if (!clientName.trim()) {
showToast('Por favor, informe o seu nome completo.', 'warning');
return;
}
if (!clientPhone.trim()) {
showToast('É necessário informar o seu telefone para confirmar o agendamento!', 'error');
setShowPhoneModal(true);
return;
}
const [startHour, startMin] = selectedTime ? selectedTime.split(':').map(Number) : [9, 0];
try {
const res = await fetch('/api/tenant/agenda', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
slug,
professionalId: selectedProfessional || '1',
client: clientName,
phone: clientPhone,
email: clientEmail,
service: svc?.name || 'Serviço',
price: svc?.price || 0,
duration: svc?.duration || 30,
startHour,
startMin,
status: 'pending',
date: selectedDate,
})
});
if (res.ok) {
const aptData = await res.json();
localStorage.setItem(`agendapro_active_booking_${slug}`, JSON.stringify({
id: aptData.id,
service: svc?.name,
prof: prof?.name,
date: selectedDate,
time: selectedTime,
price: svc?.price
}));
setBooked(true);
} else {
const errData = await res.json();
showToast(errData.error || 'Erro ao realizar o agendamento.', 'warning');
}
} catch (e) {
console.error(e);
}
};
const cancelBooking = async () => {
if (!confirm('Tem certeza que deseja cancelar este agendamento?')) return;
try {
const idToCancel = activeBookingInfo?.id;
if (!idToCancel) return;
const res = await fetch('/api/tenant/agenda', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: idToCancel, status: 'cancelled' })
});
if (res.ok) {
showToast('Agendamento cancelado com sucesso.', 'success');
localStorage.removeItem(`agendapro_active_booking_${slug}`);
setActiveBookingInfo(null);
setBooked(false);
setStep(1);
setSelectedTime(null);
}
} catch (e) {
console.error(e);
showToast('Erro ao cancelar agendamento.', 'error');
}
};
if (booked) {
const renderSvcName = activeBookingInfo?.service || svc?.name;
const renderProfName = activeBookingInfo?.prof || prof?.name;
const renderDate = activeBookingInfo?.date || selectedDate;
const renderTime = activeBookingInfo?.time || selectedTime;
const renderPrice = activeBookingInfo?.price !== undefined ? activeBookingInfo.price : svc?.price;
return (
<div style={{ minHeight: '100vh', background: 'var(--bg-primary)', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '20px' }}>
<div style={{ textAlign: 'center', maxWidth: '480px', animation: 'slideUp 0.5s ease', width: '100%' }}>
<div style={{
width: 80, height: 80, borderRadius: '50%', background: 'var(--success-bg)', display: 'flex',
alignItems: 'center', justifyContent: 'center', margin: '0 auto 24px', border: '3px solid var(--success)'
}}>
<Check size={40} color="var(--success)" />
</div>
<h1 style={{ fontSize: '28px', fontWeight: 800, marginBottom: '12px' }}>Agendamento Confirmado! 🎉</h1>
<p style={{ color: 'var(--text-secondary)', fontSize: '16px', marginBottom: '32px' }}>
Seu horário está reservado e ativo.
</p>
<div style={{
background: 'var(--bg-card)', border: '1px solid var(--border-color)',
borderRadius: 'var(--radius-lg)', padding: '24px', textAlign: 'left', marginBottom: '20px'
}}>
<div style={{ display: 'grid', gap: '16px' }}>
<div><span style={{ fontSize: '12px', color: 'var(--text-muted)', textTransform: 'uppercase' }}>Serviço</span><div style={{ fontWeight: 600 }}>{renderSvcName}</div></div>
<div><span style={{ fontSize: '12px', color: 'var(--text-muted)', textTransform: 'uppercase' }}>Profissional</span><div style={{ fontWeight: 600 }}>{renderProfName}</div></div>
<div className="grid-2">
<div><span style={{ fontSize: '12px', color: 'var(--text-muted)', textTransform: 'uppercase' }}>Data</span><div style={{ fontWeight: 600 }}>{renderDate ? new Date(renderDate + 'T12:00:00').toLocaleDateString('pt-BR', { weekday: 'long', day: 'numeric', month: 'long' }) : ''}</div></div>
<div><span style={{ fontSize: '12px', color: 'var(--text-muted)', textTransform: 'uppercase' }}>Horário</span><div style={{ fontWeight: 600 }}>{renderTime}</div></div>
</div>
<div style={{ borderTop: '1px solid var(--border-color)', paddingTop: '16px', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<span style={{ color: 'var(--text-secondary)' }}>Total</span>
<span style={{ fontSize: '24px', fontWeight: 800, color: 'var(--accent)' }}>R$ {renderPrice?.toFixed(2)}</span>
</div>
</div>
</div>
<button className="btn btn-outline" style={{ width: '100%', borderColor: 'var(--danger)', color: 'var(--danger)' }} onClick={cancelBooking}>
Cancelar este agendamento
</button>
<p style={{ fontSize: '13px', color: 'var(--text-muted)', marginTop: '20px' }}>
Você não pode realizar outro agendamento enquanto este estiver ativo.
</p>
</div>
</div>
);
}
if (!establishment.allowOnlineBooking && !booked) {
return (
<div style={{ minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'var(--bg-primary)', color: 'white' }}>
<div style={{ maxWidth: '440px', textAlign: 'center', padding: '40px', background: 'var(--bg-card)', borderRadius: 'var(--radius-lg)', border: '1px solid var(--border-color)', boxShadow: 'var(--shadow-lg)' }}>
<div style={{ width: 80, height: 80, borderRadius: '50%', background: 'var(--danger-bg)', color: 'var(--danger)', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 24px' }}>
<Calendar size={40} />
</div>
<h2 style={{ fontSize: '24px', fontWeight: 800, marginBottom: '16px', color: 'var(--text-primary)' }}>Agendamento Indisponível</h2>
<p style={{ color: 'var(--text-secondary)' }}>O agendamento online está temporariamente desativado para este estabelecimento. Por favor, entre em contato diretamente pelo WhatsApp ou telefone.</p>
</div>
</div>
);
}
return (
<div style={{ minHeight: '100vh', background: 'var(--bg-primary)' }}>
{/* HEADER */}
<header style={{
background: 'var(--gradient-primary)', padding: '32px 24px', textAlign: 'center',
position: 'relative', overflow: 'hidden'
}}>
<div style={{ position: 'absolute', inset: 0, background: 'radial-gradient(circle at 30% 50%, rgba(255,255,255,0.1), transparent 60%)' }} />
<div style={{ position: 'relative', maxWidth: '600px', margin: '0 auto' }}>
<div style={{ fontSize: '32px', marginBottom: '8px' }}></div>
<h1 style={{ fontSize: '28px', fontWeight: 900, letterSpacing: '-1px', marginBottom: '6px' }}>{establishment.name}</h1>
<p style={{ fontSize: '14px', opacity: 0.9, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '6px' }}>
<MapPin size={14} /> {establishment.address}
</p>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '6px', marginTop: '8px' }}>
<Star size={14} fill="white" />
<span style={{ fontWeight: 700 }}>{establishment.rating}</span>
<span style={{ opacity: 0.8 }}>({establishment.reviews} avaliações)</span>
</div>
</div>
</header>
{/* STEPPER */}
<div style={{ maxWidth: '700px', margin: '0 auto', padding: '32px 20px' }}>
<div style={{ display: 'flex', justifyContent: 'center', gap: '8px', marginBottom: '40px' }}>
{['Serviço', 'Profissional', 'Data e Hora', 'Seus Dados'].map((label, i) => (
<div key={label} style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<div style={{
width: 32, height: 32, borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center',
fontSize: '13px', fontWeight: 700,
background: step > i + 1 ? 'var(--success)' : step === i + 1 ? 'var(--accent)' : 'var(--bg-elevated)',
color: step >= i + 1 ? 'white' : 'var(--text-muted)',
transition: 'all 0.3s'
}}>
{step > i + 1 ? <Check size={16} /> : i + 1}
</div>
<span style={{
fontSize: '13px', fontWeight: step === i + 1 ? 600 : 400,
color: step === i + 1 ? 'var(--text-primary)' : 'var(--text-muted)',
display: i < 3 ? 'inline' : 'inline'
}}>{label}</span>
{i < 3 && <div style={{ width: '24px', height: '2px', background: step > i + 1 ? 'var(--success)' : 'var(--bg-elevated)', borderRadius: '1px' }} />}
</div>
))}
</div>
{/* STEP 1: SELECT SERVICE */}
{step === 1 && (
<div className="slide-up">
<h2 style={{ fontSize: '20px', fontWeight: 700, marginBottom: '20px' }}>Escolha o serviço</h2>
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
{dbServices.map(s => (
<div key={s.id} onClick={() => setSelectedService(s.id)} style={{
padding: '18px 20px', borderRadius: 'var(--radius-md)', cursor: 'pointer',
background: selectedService === s.id ? 'var(--accent-glow)' : 'var(--bg-card)',
border: `2px solid ${selectedService === s.id ? 'var(--accent)' : 'var(--border-color)'}`,
transition: 'all 0.2s', display: 'flex', justifyContent: 'space-between', alignItems: 'center'
}}>
<div>
<div style={{ fontWeight: 700, fontSize: '15px', marginBottom: '4px' }}>{s.name}</div>
<div style={{ fontSize: '13px', color: 'var(--text-secondary)' }}>{s.desc || s.description || 'Serviço profissional'}</div>
<div style={{ fontSize: '12px', color: 'var(--text-muted)', marginTop: '4px', display: 'flex', alignItems: 'center', gap: '4px' }}>
<Clock size={12} /> {s.duration} min
</div>
</div>
<div style={{ textAlign: 'right' }}>
<div style={{ fontSize: '20px', fontWeight: 800, color: 'var(--accent)' }}>R$ {Number(s.price).toFixed(2)}</div>
</div>
</div>
))}
</div>
</div>
)}
{/* STEP 2: SELECT PROFESSIONAL */}
{step === 2 && (
<div className="slide-up">
<h2 style={{ fontSize: '20px', fontWeight: 700, marginBottom: '20px' }}>Escolha o profissional</h2>
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
{dbProfessionals.map(p => (
<div key={p.id} onClick={() => setSelectedProfessional(p.id)} style={{
padding: '20px', borderRadius: 'var(--radius-md)', cursor: 'pointer',
background: selectedProfessional === p.id ? 'var(--accent-glow)' : 'var(--bg-card)',
border: `2px solid ${selectedProfessional === p.id ? 'var(--accent)' : 'var(--border-color)'}`,
transition: 'all 0.2s', display: 'flex', alignItems: 'center', gap: '16px'
}}>
<div className="avatar avatar-lg">{p.name.split(' ').map((n: string) => n[0]).join('').substring(0, 2).toUpperCase()}</div>
<div style={{ flex: 1 }}>
<div style={{ fontWeight: 700, fontSize: '15px' }}>{p.name}</div>
<div style={{ fontSize: '13px', color: 'var(--text-secondary)' }}>{p.role || 'Barbeiro/Profissional'}</div>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '4px', color: 'var(--warning)' }}>
<Star size={14} fill="var(--warning)" /> <span style={{ fontWeight: 700 }}>{p.rating || '5.0'}</span>
</div>
</div>
))}
</div>
</div>
)}
{/* STEP 3: SELECT DATE & TIME */}
{step === 3 && (
<div className="slide-up">
<h2 style={{ fontSize: '20px', fontWeight: 700, marginBottom: '20px' }}>Escolha data e horário</h2>
<div style={{ marginBottom: '24px' }}>
<h3 style={{ fontSize: '14px', fontWeight: 600, color: 'var(--text-secondary)', marginBottom: '12px' }}>
<Calendar size={14} style={{ display: 'inline', marginRight: '6px' }} />Selecione a data
</h3>
<div style={{ display: 'flex', gap: '8px', overflowX: 'auto', paddingBottom: '8px' }}>
{dates.map(d => {
const iso = d.toISOString().split('T')[0];
const isSelected = selectedDate === iso;
const isToday = d.toDateString() === today.toDateString();
const dayName = d.toLocaleDateString('pt-BR', { weekday: 'short' }).replace('.', '');
return (
<div key={iso} onClick={() => { setSelectedDate(iso); setSelectedTime(null); }} style={{
minWidth: '64px', padding: '12px 8px', borderRadius: 'var(--radius-md)', cursor: 'pointer',
textAlign: 'center', flexShrink: 0, transition: 'all 0.2s',
background: isSelected ? 'var(--accent)' : 'var(--bg-card)',
border: `2px solid ${isSelected ? 'var(--accent)' : 'var(--border-color)'}`,
color: isSelected ? 'white' : 'var(--text-primary)'
}}>
<div style={{ fontSize: '11px', textTransform: 'uppercase', opacity: 0.7, marginBottom: '4px' }}>{dayName}</div>
<div style={{ fontSize: '20px', fontWeight: 800 }}>{d.getDate()}</div>
<div style={{ fontSize: '11px', opacity: 0.7 }}>{d.toLocaleDateString('pt-BR', { month: 'short' }).replace('.', '')}</div>
{isToday && <div style={{ fontSize: '9px', fontWeight: 700, color: isSelected ? 'white' : 'var(--accent)', marginTop: '2px' }}>HOJE</div>}
</div>
);
})}
</div>
</div>
{selectedDate && (
<div>
<h3 style={{ fontSize: '14px', fontWeight: 600, color: 'var(--text-secondary)', marginBottom: '12px' }}>
<Clock size={14} style={{ display: 'inline', marginRight: '6px' }} />Horários disponíveis
</h3>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(90px, 1fr))', gap: '8px' }}>
{(slots.length > 0 ? slots : ['09:00', '09:30', '10:00', '10:30', '11:00', '11:30', '14:00', '14:30', '15:00', '15:30', '16:00', '16:30', '17:00', '17:30', '18:00']).map(slot => {
const isToday = selectedDate === today.toISOString().split('T')[0];
const [sh, sm] = slot.split(':').map(Number);
const slotMinutes = sh * 60 + sm;
const currentMinutes = today.getHours() * 60 + today.getMinutes();
const isPastOrTooSoon = isToday && (slotMinutes < currentMinutes + (establishment.bookingAdvanceMin || 60));
if (isPastOrTooSoon) return null;
return (
<div key={slot} onClick={() => setSelectedTime(slot)} style={{
padding: '12px', borderRadius: 'var(--radius-sm)', cursor: 'pointer', textAlign: 'center',
fontWeight: 600, fontSize: '14px', transition: 'all 0.2s',
background: selectedTime === slot ? 'var(--accent)' : 'var(--bg-card)',
border: `1px solid ${selectedTime === slot ? 'var(--accent)' : 'var(--border-color)'}`,
color: selectedTime === slot ? 'white' : 'var(--text-primary)'
}}>
{slot}
</div>
)})}
</div>
</div>
)}
</div>
)}
{/* STEP 4: CLIENT DATA */}
{step === 4 && (
<div className="slide-up">
<h2 style={{ fontSize: '20px', fontWeight: 700, marginBottom: '20px' }}>Seus dados</h2>
<div style={{
background: 'var(--bg-card)', border: '1px solid var(--border-color)',
borderRadius: 'var(--radius-lg)', padding: '20px', marginBottom: '24px'
}}>
<h3 style={{ fontSize: '13px', fontWeight: 600, color: 'var(--text-muted)', textTransform: 'uppercase', marginBottom: '12px' }}>Resumo do agendamento</h3>
<div style={{ display: 'grid', gap: '8px', fontSize: '14px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between' }}><span style={{ color: 'var(--text-secondary)' }}>Serviço</span><span style={{ fontWeight: 600 }}>{svc?.name}</span></div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}><span style={{ color: 'var(--text-secondary)' }}>Profissional</span><span style={{ fontWeight: 600 }}>{prof?.name}</span></div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}><span style={{ color: 'var(--text-secondary)' }}>Data</span><span style={{ fontWeight: 600 }}>{selectedDate ? new Date(selectedDate + 'T12:00:00').toLocaleDateString('pt-BR') : ''}</span></div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}><span style={{ color: 'var(--text-secondary)' }}>Horário</span><span style={{ fontWeight: 600 }}>{selectedTime}</span></div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}><span style={{ color: 'var(--text-secondary)' }}>Duração</span><span style={{ fontWeight: 600 }}>{svc?.duration} min</span></div>
<div style={{ borderTop: '1px solid var(--border-color)', paddingTop: '8px', display: 'flex', justifyContent: 'space-between' }}>
<span style={{ fontWeight: 600 }}>Total</span>
<span style={{ fontSize: '20px', fontWeight: 800, color: 'var(--accent)' }}>R$ {svc?.price.toFixed(2)}</span>
</div>
</div>
</div>
<div className="form-group">
<label className="form-label"><User size={14} style={{ display: 'inline', marginRight: 4 }} />Nome completo *</label>
<input className="form-input" placeholder="Seu nome" value={clientName} onChange={e => setClientName(e.target.value)} />
</div>
<div className="grid-2">
<div className="form-group">
<label className="form-label"><Phone size={14} style={{ display: 'inline', marginRight: 4 }} />Telefone / WhatsApp *</label>
<input className="form-input" placeholder="(00) 00000-0000" value={clientPhone} onChange={e => setClientPhone(e.target.value)} />
</div>
<div className="form-group">
<label className="form-label"><Mail size={14} style={{ display: 'inline', marginRight: 4 }} />E-mail (opcional)</label>
<input className="form-input" placeholder="seu@email.com" value={clientEmail} onChange={e => setClientEmail(e.target.value)} />
</div>
</div>
</div>
)}
{/* NAVIGATION BUTTONS */}
<div style={{ display: 'flex', justifyContent: 'space-between', marginTop: '32px', gap: '12px' }}>
{step > 1 ? (
<button className="btn btn-secondary" onClick={() => setStep(s => s - 1)}>
<ChevronLeft size={16} /> Voltar
</button>
) : <div />}
{step < 4 ? (
<button className="btn btn-primary" disabled={!canNext()} onClick={() => setStep(s => s + 1)}
style={{ opacity: canNext() ? 1 : 0.5 }}>
Próximo <ChevronRight size={16} />
</button>
) : (
<button className="btn btn-primary" onClick={handleBook}
style={{ padding: '12px 32px', fontSize: '16px' }}>
<Check size={18} /> Confirmar Agendamento
</button>
)}
</div>
</div>
{/* MODAL POP-UP TELEFONE OBRIGATÓRIO */}
{showPhoneModal && (
<div className="modal-overlay" onClick={() => setShowPhoneModal(false)}>
<div className="modal" onClick={e => e.stopPropagation()} style={{ maxWidth: '420px', textAlign: 'center' }}>
<div className="modal-header" style={{ justifyContent: 'center', borderBottom: 'none', paddingBottom: 0 }}>
<div style={{ width: 60, height: 60, borderRadius: '50%', background: 'rgba(245, 158, 11, 0.15)', color: 'var(--warning)', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto' }}>
<Phone size={30} />
</div>
</div>
<div className="modal-body">
<h3 style={{ fontSize: '20px', fontWeight: 800, marginBottom: '12px' }}>Telefone / WhatsApp Obrigatório 📱</h3>
<p style={{ color: 'var(--text-secondary)', fontSize: '14px', marginBottom: '20px', lineHeight: 1.5 }}>
Por favor, informe o seu número de telefone ou WhatsApp para confirmarmos e enviarmos a nota do seu agendamento.
</p>
<div className="form-group" style={{ textAlign: 'left', marginBottom: '20px' }}>
<label className="form-label">Seu Telefone / WhatsApp *</label>
<input
className="form-input"
placeholder="(00) 00000-0000"
value={clientPhone}
autoFocus
onChange={e => setClientPhone(e.target.value)}
/>
</div>
<button
className="btn btn-primary"
style={{ width: '100%', padding: '12px', fontSize: '15px' }}
onClick={() => {
if (!clientPhone.trim()) {
showToast('Por favor, digite seu número de telefone.', 'warning');
return;
}
setShowPhoneModal(false);
handleBook();
}}
>
<Check size={16} /> Confirmar e Agendar
</button>
</div>
</div>
</div>
)}
{/* FOOTER */}
<footer style={{ textAlign: 'center', padding: '32px 20px', borderTop: '1px solid var(--border-color)', marginTop: '40px' }}>
<p style={{ fontSize: '13px', color: 'var(--text-muted)' }}>
Powered by <span style={{ fontWeight: 700, color: 'var(--accent)' }}>AgendaPRO</span>
</p>
</footer>
<ToastContainer />
</div>
);
}