diff --git a/web/src/components/ShopCard.tsx b/web/src/components/ShopCard.tsx index fa0ed30..b1fcc1c 100644 --- a/web/src/components/ShopCard.tsx +++ b/web/src/components/ShopCard.tsx @@ -57,3 +57,4 @@ export const ShopCard = ({ shop }: { shop: BarberShop }) => ( + diff --git a/web/src/lib/events.ts b/web/src/lib/events.ts index 342966b..9d38a45 100644 --- a/web/src/lib/events.ts +++ b/web/src/lib/events.ts @@ -9,21 +9,27 @@ export type EventRow = { user_id: string } -// LISTAR eventos (RLS deve garantir que só vês os teus) +/** + * LISTAR eventos do utilizador autenticado + * (RLS garante isolamento por user_id) + */ export async function listEvents() { const user = await getUser() if (!user) throw new Error('Utilizador não autenticado') const { data, error } = await supabase .from('events') - .select('id,title,description,date,user_id') + .select('id, title, description, date, user_id') .order('date', { ascending: true }) if (error) throw error return (data ?? []) as EventRow[] } -// CRIAR evento +/** + * CRIAR evento + * user_id vem automaticamente de auth.uid() + */ export async function createEvent(input: { title: string description?: string @@ -38,11 +44,9 @@ export async function createEvent(input: { title: input.title, description: input.description ?? null, date: input.date, - // user_id: - // - Se a tua coluna user_id tem DEFAULT auth.uid(), NÃO metas aqui. - // - Se não tem default, então mete: user_id: user.id + // ❌ NÃO enviar user_id se tiver DEFAULT auth.uid() }) - .select('id,title,description,date,user_id') + .select('id, title, description, date, user_id') .single() if (error) throw error diff --git a/web/src/pages/AuthRegister.tsx b/web/src/pages/AuthRegister.tsx index e5031e1..8467829 100644 --- a/web/src/pages/AuthRegister.tsx +++ b/web/src/pages/AuthRegister.tsx @@ -1,5 +1,5 @@ -import { FormEvent, useState } from 'react' -import { useNavigate, Link } from 'react-router-dom' +import { FormEvent, useEffect, useState } from 'react' +import { Link, useNavigate } from 'react-router-dom' import { Input } from '../components/ui/input' import { Button } from '../components/ui/button' import { Card } from '../components/ui/card' @@ -17,61 +17,66 @@ export default function AuthRegister() { const navigate = useNavigate() - const onSubmit = async (e: FormEvent) => { + // 🔐 Se já estiver logado, não pode ver o registo + useEffect(() => { + let mounted = true + ;(async () => { + const { data } = await supabase.auth.getSession() + if (!mounted) return + if (data.session) { + navigate('/explorar', { replace: true }) + } + })() + return () => { + mounted = false + } + }, [navigate]) + + async function onSubmit(e: FormEvent) { e.preventDefault() setError('') if (!name.trim()) return setError('Preencha o nome completo') if (!email.trim()) return setError('Preencha o email') if (!password.trim()) return setError('Preencha a senha') - if (role === 'barbearia' && !shopName.trim()) return setError('Informe o nome da barbearia') - - setLoading(true) + if (role === 'barbearia' && !shopName.trim()) { + return setError('Informe o nome da barbearia') + } try { - // 1) Criar conta no Supabase Auth - const { data, error: signUpError } = await supabase.auth.signUp({ + setLoading(true) + + const { data, error } = await supabase.auth.signUp({ email, password, options: { data: { - name, + name: name.trim(), role, - shop_name: role === 'barbearia' ? shopName : null, + shopName: role === 'barbearia' ? shopName.trim() : null, }, }, }) - if (signUpError) throw signUpError - const userId = data.user?.id - if (!userId) { - // Em alguns casos o user pode exigir confirmação por email - // Mesmo assim, mostramos uma mensagem útil. - throw new Error('Conta criada, mas sessão não iniciada. Verifica o email para confirmar a conta.') + if (error) throw error + + // 🔔 Se confirmação de email estiver ON + if (!data.session) { + navigate('/login', { + replace: true, + state: { + msg: 'Conta criada! Confirma o email antes de fazer login.', + }, + }) + return } - // 2) Criar perfil na tabela public.profiles - const { error: profileError } = await supabase - .from('profiles') - .upsert( - { - id: userId, - name, - role, - shop_name: role === 'barbearia' ? shopName : null, - }, - { onConflict: 'id' } - ) - if (profileError) throw profileError - - // 3) Redirecionar - navigate('/explorar', { replace: true }) - } catch (e: any) { - // Mensagens comuns - const msg = - e?.message || - (typeof e === 'string' ? e : 'Erro ao criar conta') - setError(msg) + // ✅ Login automático + navigate(role === 'barbearia' ? '/painel' : '/explorar', { + replace: true, + }) + } catch (err: any) { + setError(err?.message || 'Erro ao criar conta') } finally { setLoading(false) } @@ -84,8 +89,12 @@ export default function AuthRegister() {
-

Criar conta

-

Escolha o tipo de acesso

+

+ Criar conta +

+

+ Escolha o tipo de acesso +

{error && ( @@ -95,9 +104,11 @@ export default function AuthRegister() { )}
- {/* Role Selection */} + {/* Tipo de conta */}
- +
{(['cliente', 'barbearia'] as const).map((r) => (

Já tem conta?{' '} - + Entrar

diff --git a/web/src/pages/EventsCreate.tsx b/web/src/pages/EventsCreate.tsx index 45eb38f..52d3e12 100644 --- a/web/src/pages/EventsCreate.tsx +++ b/web/src/pages/EventsCreate.tsx @@ -1,40 +1,67 @@ +// src/pages/EventsCreate.tsx import { FormEvent, useState } from 'react' import { useNavigate } from 'react-router-dom' import { Card } from '../components/ui/card' import { Input } from '../components/ui/input' import { Button } from '../components/ui/button' import { createEvent } from '../lib/events' +import { CalendarPlus } from 'lucide-react' export default function EventsCreate() { + const navigate = useNavigate() + const [title, setTitle] = useState('') const [description, setDescription] = useState('') - const [date, setDate] = useState('') + const [date, setDate] = useState('') // datetime-local + const [saving, setSaving] = useState(false) const [error, setError] = useState('') - const [loading, setLoading] = useState(false) - const navigate = useNavigate() async function onSubmit(e: FormEvent) { e.preventDefault() setError('') - if (!title.trim()) return setError('Título obrigatório') - if (!date) return setError('Data obrigatória') + if (!title.trim()) { + setError('Preenche o título') + return + } + if (!date) { + setError('Escolhe a data e hora') + return + } - setLoading(true) try { - await createEvent({ title, description, date }) + setSaving(true) + + // datetime-local -> ISO string (mantendo o valor como está) + // Ex: "2026-01-28T10:30" + const iso = new Date(date).toISOString() + + await createEvent({ + title: title.trim(), + description: description.trim() ? description.trim() : undefined, + date: iso, + }) + navigate('/perfil', { replace: true }) } catch (e: any) { - setError(e?.message ?? 'Erro ao guardar evento') + setError(e?.message || 'Erro ao criar evento') } finally { - setLoading(false) + setSaving(false) } } return ( -
- -

Criar evento

+
+ +
+
+ +
+
+

Criar evento

+

Este evento vai aparecer no teu perfil

+
+
{error && (
@@ -46,27 +73,44 @@ export default function EventsCreate() { setTitle(e.target.value)} - placeholder="Ex: Corte às 15h" + onChange={(e) => { + setTitle(e.target.value) + setError('') + }} + placeholder="Ex: Corte às 18h" + required /> setDescription(e.target.value)} - placeholder="Opcional" + onChange={(e) => { + setDescription(e.target.value) + setError('') + }} + placeholder="Notas do evento..." /> + {/* Se o teu componente Input não suportar type datetime-local, troca por normal */} setDate(e.target.value)} + onChange={(e) => { + setDate(e.target.value) + setError('') + }} + required /> - +
+ + +
diff --git a/web/src/pages/Explore.tsx b/web/src/pages/Explore.tsx index 38e5052..15f1588 100644 --- a/web/src/pages/Explore.tsx +++ b/web/src/pages/Explore.tsx @@ -115,4 +115,3 @@ export default function Explore() { } - diff --git a/web/src/pages/Profile.tsx b/web/src/pages/Profile.tsx index 603ef99..b430829 100644 --- a/web/src/pages/Profile.tsx +++ b/web/src/pages/Profile.tsx @@ -1,5 +1,6 @@ +// src/pages/Profile.tsx import { useEffect, useMemo, useState } from 'react' -import { Link, useLocation } from 'react-router-dom' +import { Link, useLocation, useNavigate } from 'react-router-dom' import { Card } from '../components/ui/card' import { Badge } from '../components/ui/badge' import { currency } from '../lib/format' @@ -25,6 +26,7 @@ const statusLabel: Record = { export default function Profile() { const { appointments, orders, shops } = useApp() const location = useLocation() + const navigate = useNavigate() // ✅ Utilizador Supabase const [authEmail, setAuthEmail] = useState('') @@ -50,6 +52,7 @@ export default function Profile() { setAuthEmail(data.user.email ?? '') setAuthId(data.user.id) } + setLoadingAuth(false) })() @@ -77,7 +80,7 @@ export default function Profile() { // eslint-disable-next-line react-hooks/exhaustive-deps }, []) - // ✅ Recarrega eventos sempre que muda a rota (ex.: voltas de /eventos/novo para /perfil) + // ✅ Recarrega eventos sempre que voltares para /perfil useEffect(() => { if (location.pathname === '/perfil') { loadEvents() @@ -107,6 +110,13 @@ export default function Profile() { return (

Sessão não encontrada. Faz login novamente.

+
) } diff --git a/web/src/routes.tsx b/web/src/routes.tsx index a581873..870397c 100644 --- a/web/src/routes.tsx +++ b/web/src/routes.tsx @@ -1,3 +1,4 @@ +// src/routes.tsx import { createBrowserRouter } from 'react-router-dom' import { Shell } from './components/layout/Shell' import { RequireAuth } from './components/auth/RequireAuth' @@ -23,7 +24,7 @@ export const router = createBrowserRouter([ { path: '/explorar', element: }, { path: '/barbearia/:id', element: }, - // ✅ PROTEGIDAS (precisam de login) + // ✅ PROTEGIDAS { path: '/agendar/:id', element: (