diff --git a/web/src/pages/Booking.tsx b/web/src/pages/Booking.tsx
index 4859c72..89e755b 100644
--- a/web/src/pages/Booking.tsx
+++ b/web/src/pages/Booking.tsx
@@ -13,11 +13,37 @@ export default function Booking() {
const navigate = useNavigate();
// Extração das ferramentas vitais do Context global da aplicação
- const { shops, createAppointment, user, appointments, waitlists, joinWaitlist } = useApp();
+ const { shops, shopsReady, createAppointment, user, appointments, waitlists, joinWaitlist } = useApp();
// Procura a barbearia acedida (com base no URL parameter ':id')
const shop = useMemo(() => shops.find((s) => s.id === id), [shops, id]);
+
+ if (!shopsReady || (shops.length === 0 && !shop)) {
+ return (
+
+
+
A preparar reserva...
+
+ );
+ }
+
+ if (!shop) {
+ return (
+
+
+
+
+
+
Espaço não encontrado
+
Não foi possível iniciar o agendamento para este estabelecimento.
+
+
+
+ );
+ }
// Estados para as escolhas parciais do utilizador
const [serviceId, setService] = useState(searchParams.get('service') || '');
const [barberId, setBarber] = useState('');
@@ -75,7 +101,6 @@ export default function Booking() {
});
}, [selectedBarber, date, barberId, appointments, user, waitlists]);
- if (!shop) return Barbearia não encontrada
;
const canSubmit = serviceId && barberId && date && slot;
diff --git a/web/src/pages/Profile.tsx b/web/src/pages/Profile.tsx
index b1c6d93..37eaa20 100644
--- a/web/src/pages/Profile.tsx
+++ b/web/src/pages/Profile.tsx
@@ -91,6 +91,8 @@ export default function Profile() {
setReviewTarget(null)
}
+ const [activeTab, setActiveTab] = useState<'favoritos' | 'agenda' | 'pedidos'>('favoritos')
+
if (loadingAuth) {
return (
@@ -135,27 +137,27 @@ export default function Profile() {
/>
)}
-
+
{/* Cabeçalho do Perfil */}
-
+
-
-
-
-
+
+
+
Utilizador Registado
-
+
{displayName}
-
{authEmail}
+
{authEmail}
@@ -186,167 +188,191 @@ export default function Profile() {
)}
- {/* ❤️ Barbearias Favoritas - Horizontal Scroll or Grid */}
- {favoriteShops.length > 0 && (
-
-
-
-
-
Cofre de Favoritos
-
-
{favoriteShops.length} Espaços
-
-
- {favoriteShops.map((shop) => (
-
-
-
-
- {shop.imageUrl ? (
-

- ) : (
-
-
+ {/* Navegação por Tabs no Perfil */}
+
+
+ {[
+ { id: 'favoritos', label: 'Favoritos', icon: Heart, count: favoriteShops.length },
+ { id: 'agenda', label: 'Minha Agenda', icon: Calendar, count: myAppointments.length },
+ { id: 'pedidos', label: 'Pedidos', icon: ShoppingBag, count: myOrders.length },
+ ].map((t) => (
+
+ ))}
+
+
+
+
+ {activeTab === 'favoritos' && (
+
+ {!favoriteShops.length ? (
+
+
+ Sem Favoritos
+ Ainda não escolheste os teus espaços preferidos.
+
+
+ ) : (
+
+ {favoriteShops.map((shop) => (
+
+
+
+
+ {shop.imageUrl ? (
+

+ ) : (
+
+
+
+ )}
- )}
-
-
-
{shop.name}
-
-
-
- {shop.rating.toFixed(1)}
-
-
-
- {shop.address.split(',')[0]}
-
-
-
-
-
-
- ))}
-
-
- )}
-
-
- {/* Main Column: Appointments */}
-
-
-
- {!myAppointments.length ? (
-
-
- Sem Reservas
- Sua jornada de estilo ainda não começou.
-
-
- ) : (
-
- {myAppointments.map((a) => {
- const shop = shops.find((s) => s.id === a.shopId)
- const service = shop?.services.find((s) => s.id === a.serviceId)
- const canReview = a.status === 'concluido' && !reviewedAppointments.has(a.id)
-
- return (
-
-
-
-
-
-
{shop?.name}
-
- {statusLabel[a.status]}
-
+
+
{shop.name}
+
+
+
+ {shop.rating.toFixed(1)}
+
+
+
+ {shop.address.split(',')[0]}
-
{a.date}
-
- {currency(a.total)}
-
-
-
-
- {service && (
-
-
- {service.name} · {service.duration} MIN
-
- )}
-
- {canReview && (
-
- )}
-
- {a.status === 'concluido' && reviewedAppointments.has(a.id) && (
-
-
- Avaliado
-
- )}
-
-
-
- )
- })}
-
- )}
-
-
- {/* Side Column: Orders */}
-
-
-
- {!myOrders.length ? (
-
- Sem encomendas efetuadas.
-
- ) : (
-
- {myOrders.map((o) => {
- const shop = shops.find((s) => s.id === o.shopId)
- return (
-
-
-
-
{shop?.name}
-
{currency(o.total)}
+
-
-
- {o.items.length} {o.items.length === 1 ? 'Item' : 'Itens'}
+
+
+ ))}
+
+ )}
+
+ )}
+
+ {activeTab === 'agenda' && (
+
+ {!myAppointments.length ? (
+
+
+ Sem Reservas
+ Sua jornada de estilo ainda não começou.
+
+
+ ) : (
+
+ {myAppointments.map((a) => {
+ const shop = shops.find((s) => s.id === a.shopId)
+ const service = shop?.services.find((s) => s.id === a.serviceId)
+ const canReview = a.status === 'concluido' && !reviewedAppointments.has(a.id)
+
+ return (
+
+
+
+
+
+
{shop?.name}
+
+ {statusLabel[a.status]}
+
+
+
{a.date}
+
+
+ {currency(a.total)}
+
-
- {statusLabel[o.status]}
+
+
+ {service && (
+
+
+ {service.name} · {service.duration} MIN
+
+ )}
+
+ {canReview && (
+
+ )}
+
+ {a.status === 'concluido' && reviewedAppointments.has(a.id) && (
+
+
+ Avaliado
+
+ )}
-
-
- )
- })}
-
- )}
-
+
+ )
+ })}
+
+ )}
+
+ )}
+
+ {activeTab === 'pedidos' && (
+
+ {!myOrders.length ? (
+
+
+ Sem encomendas efetuadas.
+
+ ) : (
+
+ {myOrders.map((o) => {
+ const shop = shops.find((s) => s.id === o.shopId)
+ return (
+
+
+
+
{shop?.name}
+
{currency(o.total)}
+
+
{new Date(o.createdAt).toLocaleString('pt-PT')}
+
+
+ {o.items.length} {o.items.length === 1 ? 'Item' : 'Itens'}
+
+
+ {statusLabel[o.status]}
+
+
+
+
+ )
+ })}
+
+ )}
+
+ )}
>
diff --git a/web/src/pages/ShopDetails.tsx b/web/src/pages/ShopDetails.tsx
index f00a5bf..a648c14 100644
--- a/web/src/pages/ShopDetails.tsx
+++ b/web/src/pages/ShopDetails.tsx
@@ -23,16 +23,31 @@ export default function ShopDetails() {
const [tab, setTab] = useState<'servicos' | 'produtos' | 'barbeiros' | 'detalhes'>('servicos');
const [imageOpen, setImageOpen] = useState(false);
- if (!shopsReady) {
+ if (!shopsReady || (shops.length === 0 && !shop)) {
return (
-
-
-
A carregar detalhes...
+
+
+
A carregar detalhes...
);
}
- if (!shop) return
Barbearia não encontrada.
;
+ if (!shop) {
+ return (
+
+
+
+
+
+
Barbearia não encontrada
+
O espaço que procura pode ter sido removido ou o link está incorreto.
+
+
+
+ );
+ }
const mapUrl = `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(
`${shop.name} ${shop.address}`
)}`;