diff --git a/src/app/aluno/dashboard/page.tsx b/src/app/aluno/dashboard/page.tsx index d9a1b6e4..bdfe785d 100644 --- a/src/app/aluno/dashboard/page.tsx +++ b/src/app/aluno/dashboard/page.tsx @@ -34,7 +34,74 @@ export default async function AlunoDashboardPage() { }, }); - const aluno = await alunoPromise; + let aluno = await alunoPromise; + + // Auto-provision: OAuth users (Google/GitHub/Apple) who have no aluno row get + // one created lazily on first dashboard visit. Email+name come from Supabase + // auth.user_metadata; CPF is required @unique but unknown from Google, so we + // mint a deterministic placeholder from the auth user id (uniqueness from the + // already-unique auth uid). Admin can fix the real CPF later. Throws on the + // rare race where two concurrent requests both miss findUnique — caught below + // surfaces as the original empty-state so the next request finds the row. + if (!aluno) { + const meta = user.user_metadata ?? {}; + const nomeCompleto = + (meta.full_name as string | undefined) ?? + (meta.name as string | undefined) ?? + user.email ?? + 'Aluno Google'; + const fotoUrl = (meta.avatar_url as string | undefined) ?? null; + const cpfPlaceholder = `OAUTH-${user.id.slice(0, 18)}`; + try { + aluno = await prisma.aluno.create({ + data: { + email: user.email!, + nomeCompleto, + fotoUrl, + cpf: cpfPlaceholder, + }, + select: { + id: true, + nomeCompleto: true, + fotoUrl: true, + nivel: true, + exp: true, + statusMatricula: true, + streakDiasSeguidos: true, + treinosNoMes: true, + ultimoTreinoData: true, + Matriculas: { + where: { status: 'ATIVA' }, + orderBy: { dataVencimento: 'desc' }, + take: 1, + select: { id: true, status: true, dataVencimento: true, planoId: true }, + }, + }, + }); + } catch { + // Race: another request created the row. Refetch instead of crashing. + aluno = await prisma.aluno.findUnique({ + where: { email: user.email }, + select: { + id: true, + nomeCompleto: true, + fotoUrl: true, + nivel: true, + exp: true, + statusMatricula: true, + streakDiasSeguidos: true, + treinosNoMes: true, + ultimoTreinoData: true, + Matriculas: { + where: { status: 'ATIVA' }, + orderBy: { dataVencimento: 'desc' }, + take: 1, + select: { id: true, status: true, dataVencimento: true, planoId: true }, + }, + }, + }); + } + } if (!aluno) { return ( diff --git a/supabase/config.toml b/supabase/config.toml index d5be74f5..0df62d5f 100644 --- a/supabase/config.toml +++ b/supabase/config.toml @@ -150,8 +150,11 @@ max_indexes = 5 [auth] enabled = true # The base URL of your website. Used as an allow-list for redirects and for constructing URLs used -# in emails. -site_url = "http://localhost:3000" +# in emails. Must be the production URL — Supabase falls back to this when a `redirectTo` +# fails allowlist validation, so leaving it as localhost in a linked cloud project sends +# users to localhost on every OAuth redirect mismatch. Local dev resolves via +# additional_redirect_urls (localhost entry) instead. +site_url = "https://smartmanagementsystem.vercel.app" # A list of *exact* URLs that auth providers are permitted to redirect to post authentication. additional_redirect_urls = [ "http://localhost:3000/auth/callback",