+
{(['cliente', 'barbearia'] as const).map((r) => (
@@ -143,18 +141,18 @@ export default function AuthRegister() {
setRole(r)
setError('')
}}
- className={`p-4 rounded-xl border-2 transition-all ${role === r
- ? 'border-amber-500 bg-amber-50 shadow-md'
- : 'border-slate-200 hover:border-amber-300'
+ className={`p-4 rounded-2xl border-2 transition-all group ${role === r
+ ? 'border-slate-900 bg-slate-900 text-amber-500 shadow-lg'
+ : 'border-slate-100 bg-slate-50/50 hover:border-slate-200 text-slate-500'
}`}
>
{r === 'cliente' ? (
-
+
) : (
-
+
)}
-
+
{r === 'cliente' ? 'Cliente' : 'Barbearia'}
@@ -163,55 +161,63 @@ export default function AuthRegister() {
-
-
- Já tem conta?{' '}
-
- Entrar
+
+
+ Já tem uma conta?{' '}
+
+ Fazer Login
diff --git a/web/src/pages/Booking.tsx b/web/src/pages/Booking.tsx
index f3e017c..a704bc8 100644
--- a/web/src/pages/Booking.tsx
+++ b/web/src/pages/Booking.tsx
@@ -1,16 +1,10 @@
-/**
- * @file Booking.tsx
- * @description Página de Agendamento da versão Web.
- * Gere um formulário multi-passo unificado para selecionar o Serviço,
- * Barbeiro, Data e Horário. Cruza disponibilidades em tempo real.
- */
-import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
+import { useNavigate, useParams, useSearchParams, Link } from 'react-router-dom';
import { useMemo, useState, useEffect } from 'react';
import { Card } from '../components/ui/card';
import { Button } from '../components/ui/button';
import { Input } from '../components/ui/input';
import { useApp } from '../context/AppContext';
-import { Calendar, Clock, Scissors, User, CheckCircle2 } from 'lucide-react';
+import { Calendar, Clock, Scissors, User, CheckCircle2, MapPin, ArrowLeft, ArrowRight } from 'lucide-react';
import { currency } from '../lib/format';
export default function Booking() {
@@ -47,35 +41,21 @@ export default function Booking() {
const nextStep = () => setStep((s) => Math.min(s + 1, 4));
const prevStep = () => setStep((s) => Math.max(s - 1, 1));
- /**
- * Função para gerar horários padrão se não houver horários específicos predefinidos
- * pelo barbeiro na Base de Dados.
- * @returns {string[]} Lista de horários de 1 em 1 hora.
- */
const generateDefaultSlots = (): string[] => {
const slots: string[] = [];
- // Horário de trabalho padrão estipulado: 09:00 às 18:00
for (let hour = 9; hour <= 18; hour++) {
slots.push(`${hour.toString().padStart(2, '0')}:00`);
}
return slots;
};
- /**
- * Deriva reativamente a lista exata de horários disponíveis.
- * Elimina os slots que já estejam formalmente ocupados ('appointments' não cancelados) na BD.
- */
- // Buscar horários disponíveis para a data selecionada interagindo com dados transacionais
const availableSlots = useMemo(() => {
if (!selectedBarber || !date) return [];
-
- // Primeiro, tenta encontrar horários específicos configurados para a data
const specificSchedule = selectedBarber.schedule?.find((s) => s.day === date);
let slots = specificSchedule && specificSchedule.slots.length > 0
? [...specificSchedule.slots]
: generateDefaultSlots();
- // Filtra agendamentos atuais já alocados para impedir duplo-agendamento ('Double Booking')
const bookedSlots = appointments
.filter((apt) =>
apt.barberId === barberId &&
@@ -83,34 +63,25 @@ export default function Booking() {
apt.date.startsWith(date)
)
.map((apt) => {
- // Separação para identificar a secção das "horas" na data ISO/String ("YYYY-MM-DD HH:MM")
const parts = apt.date.split(' ');
return parts.length > 1 ? parts[1] : '';
})
.filter(Boolean);
- // Devolve diferença de conjuntos
return slots.filter((slot) => !bookedSlots.includes(slot));
}, [selectedBarber, date, barberId, appointments]);
- if (!shop) return
Barbearia não encontrada
;
+ if (!shop) return
Barbearia não encontrada
;
const canSubmit = serviceId && barberId && date && slot;
- /**
- * Dispara a ação de guardar a nova marcação na base de dados Supabase via Context API.
- */
const submit = async () => {
if (!user) {
- // Bloqueia ações de clientes anónimos exigindo Sessão iniciada
navigate('/login');
return;
}
if (!canSubmit) return;
-
- // O método 'createAppointment' fará internamente um pedido `supabase.from('appointments').insert(...)`
const appt = await createAppointment({ shopId: shop.id, serviceId, barberId, customerId: user.id, date: `${date} ${slot}` });
-
if (appt) {
navigate('/perfil');
} else {
@@ -126,89 +97,75 @@ export default function Booking() {
];
return (
-
-
-
-
Agendar em {shop.name}
-
{shop.address}
+
+
+
+
+ Reserva Exclusiva
- {step > 1 && (
-
- )}
-
+
+
+ Agendar em {shop.name}
+
+
+
+
- {/* Progress Steps */}
-
- {steps.map((s, idx) => (
-
-
+ {/* Progress Steps - Premium Stepper */}
+
+
+
+ {steps.map((s) => (
+
-
+
{s.label}
-
+
- {idx < steps.length - 1 && (
-
- )}
-
- ))}
+ ))}
+
-
- {/* Step Info Summary (Show what was already selected) */}
- {step > 1 && selectedService && (
-
-
-
-
-
-
-
Serviço Selecionado
-
{selectedService.name}
-
-
-
-
{currency(selectedService.price)}
-
-
- )}
-
- {step > 2 && selectedBarber && (
-
-
-
-
-
-
Barbeiro
-
{selectedBarber.name}
-
-
- )}
-
+
{/* Dynamic Step Content */}
-
+
+
+ {/* Step Back Button */}
+ {step > 1 && (
+
+ )}
+
{step === 1 && (
-
-
-
1
-
Escolha o serviço
+
+
+
1. Selecione o Serviço
+
O primeiro passo para a sua transformação de elite.
-
+
{shop.services.map((s) => (