Remove unnecessary blank lines in ShopCard and Explore components

This commit is contained in:
2026-02-03 15:23:04 +00:00
parent 22356086bb
commit a895f83db7
7 changed files with 161 additions and 96 deletions

View File

@@ -57,3 +57,4 @@ export const ShopCard = ({ shop }: { shop: BarberShop }) => (

View File

@@ -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

View File

@@ -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() {
<div className="inline-flex p-3 bg-gradient-to-br from-amber-500 to-amber-600 rounded-xl text-white shadow-lg mb-2">
<UserPlus size={24} />
</div>
<h1 className="text-2xl font-bold text-slate-900">Criar conta</h1>
<p className="text-sm text-slate-600">Escolha o tipo de acesso</p>
<h1 className="text-2xl font-bold text-slate-900">
Criar conta
</h1>
<p className="text-sm text-slate-600">
Escolha o tipo de acesso
</p>
</div>
{error && (
@@ -95,9 +104,11 @@ export default function AuthRegister() {
)}
<form className="space-y-5" onSubmit={onSubmit}>
{/* Role Selection */}
{/* Tipo de conta */}
<div className="space-y-2">
<label className="text-sm font-medium text-slate-700">Tipo de conta</label>
<label className="text-sm font-medium text-slate-700">
Tipo de conta
</label>
<div className="grid grid-cols-2 gap-3">
{(['cliente', 'barbearia'] as const).map((r) => (
<button
@@ -109,8 +120,8 @@ export default function AuthRegister() {
}}
className={`p-4 rounded-xl border-2 transition-all ${
role === r
? 'border-amber-500 bg-gradient-to-br from-amber-50 to-amber-100/50 shadow-md'
: 'border-slate-200 hover:border-amber-300 hover:bg-amber-50/50'
? 'border-amber-500 bg-amber-50 shadow-md'
: 'border-slate-200 hover:border-amber-300'
}`}
>
<div className="flex flex-col items-center gap-2">
@@ -119,7 +130,7 @@ export default function AuthRegister() {
) : (
<Scissors size={20} className={role === r ? 'text-amber-600' : 'text-slate-400'} />
)}
<span className={`text-sm font-semibold ${role === r ? 'text-amber-700' : 'text-slate-600'}`}>
<span className="text-sm font-semibold">
{r === 'cliente' ? 'Cliente' : 'Barbearia'}
</span>
</div>
@@ -131,56 +142,51 @@ export default function AuthRegister() {
<Input
label="Nome completo"
value={name}
onChange={(e) => {
setName(e.target.value)
setError('')
}}
onChange={(e) => setName(e.target.value)}
placeholder="João Silva"
required
/>
<Input
label="Email"
type="email"
value={email}
onChange={(e) => {
setEmail(e.target.value)
setError('')
}}
onChange={(e) => setEmail(e.target.value)}
placeholder="seu@email.com"
required
/>
<Input
label="Senha"
type="password"
value={password}
onChange={(e) => {
setPassword(e.target.value)
setError('')
}}
onChange={(e) => setPassword(e.target.value)}
placeholder="••••••••"
required
/>
{role === 'barbearia' && (
<Input
label="Nome da barbearia"
value={shopName}
onChange={(e) => {
setShopName(e.target.value)
setError('')
}}
onChange={(e) => setShopName(e.target.value)}
placeholder="Barbearia XPTO"
required
/>
)}
<Button type="submit" className="w-full" size="lg" disabled={loading}>
{loading ? 'A criar...' : 'Criar conta'}
{loading ? 'A criar conta…' : 'Criar conta'}
</Button>
</form>
<div className="text-center pt-4 border-t border-slate-200">
<p className="text-sm text-slate-600">
tem conta?{' '}
<Link to="/login" className="text-amber-700 font-semibold hover:text-amber-800 transition-colors">
<Link
to="/login"
className="text-amber-700 font-semibold hover:text-amber-800"
>
Entrar
</Link>
</p>

View File

@@ -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 (
<div className="max-w-md mx-auto py-8">
<Card className="p-6 space-y-4">
<h1 className="text-xl font-bold text-slate-900">Criar evento</h1>
<div className="max-w-xl mx-auto py-8">
<Card className="p-8 space-y-6">
<div className="flex items-center gap-3">
<div className="p-3 bg-gradient-to-br from-amber-500 to-amber-600 rounded-xl text-white shadow-lg">
<CalendarPlus size={22} />
</div>
<div>
<h1 className="text-2xl font-bold text-slate-900">Criar evento</h1>
<p className="text-sm text-slate-600">Este evento vai aparecer no teu perfil</p>
</div>
</div>
{error && (
<div className="rounded-lg border border-rose-200 bg-rose-50 px-4 py-3 text-sm text-rose-700">
@@ -46,27 +73,44 @@ export default function EventsCreate() {
<Input
label="Título"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Ex: Corte às 15h"
onChange={(e) => {
setTitle(e.target.value)
setError('')
}}
placeholder="Ex: Corte às 18h"
required
/>
<Input
label="Descrição"
label="Descrição (opcional)"
value={description}
onChange={(e) => 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 <input> normal */}
<Input
label="Data"
label="Data e hora"
type="datetime-local"
value={date}
onChange={(e) => setDate(e.target.value)}
onChange={(e) => {
setDate(e.target.value)
setError('')
}}
required
/>
<Button className="w-full" disabled={loading}>
{loading ? 'A guardar...' : 'Guardar'}
</Button>
<div className="flex gap-3 pt-2">
<Button type="button" variant="outline" className="w-full" onClick={() => navigate('/perfil')}>
Cancelar
</Button>
<Button type="submit" className="w-full" disabled={saving}>
{saving ? 'A guardar…' : 'Criar evento'}
</Button>
</div>
</form>
</Card>
</div>

View File

@@ -115,4 +115,3 @@ export default function Explore() {
}

View File

@@ -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<string, string> = {
export default function Profile() {
const { appointments, orders, shops } = useApp()
const location = useLocation()
const navigate = useNavigate()
// ✅ Utilizador Supabase
const [authEmail, setAuthEmail] = useState<string>('')
@@ -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 (
<div className="text-center py-12">
<p className="text-slate-600">Sessão não encontrada. Faz login novamente.</p>
<button
type="button"
onClick={() => navigate('/login', { replace: true })}
className="mt-4 text-sm font-semibold text-amber-700 hover:text-amber-800"
>
Ir para login
</button>
</div>
)
}

View File

@@ -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: <Explore /> },
{ path: '/barbearia/:id', element: <ShopDetails /> },
// ✅ PROTEGIDAS (precisam de login)
// ✅ PROTEGIDAS
{
path: '/agendar/:id',
element: (