From 0c7ffe6b448f2bf67c3c19d31c7c2ab1b67749da Mon Sep 17 00:00:00 2001 From: Sidney Date: Sun, 7 Jun 2026 22:00:09 -0300 Subject: [PATCH] feat: Registro de conta (Self-service SaaS) 100% funcional integrado ao Admin Panel --- src/app/api/auth/register/route.ts | 73 ++++++++++++++++++++++++++++++ src/app/register/page.tsx | 40 ++++++++++------ 2 files changed, 98 insertions(+), 15 deletions(-) create mode 100644 src/app/api/auth/register/route.ts diff --git a/src/app/api/auth/register/route.ts b/src/app/api/auth/register/route.ts new file mode 100644 index 0000000..2fbb0ae --- /dev/null +++ b/src/app/api/auth/register/route.ts @@ -0,0 +1,73 @@ +import { NextResponse } from 'next/server'; +import { query } from '@/lib/db'; +import bcrypt from 'bcryptjs'; +import jwt from 'jsonwebtoken'; + +export async function POST(req: Request) { + try { + const { name, slug, email, password } = await req.json(); + + if (!name || !slug || !email || !password) { + return NextResponse.json({ error: 'Preencha todos os campos obrigatórios' }, { status: 400 }); + } + + // 1. Verificar se o slug ou email já existem + const checkSlug = await query('SELECT id FROM tenants WHERE slug = $1', [slug]); + if (checkSlug.rows.length > 0) { + return NextResponse.json({ error: 'Este link público já está em uso. Escolha outro.' }, { status: 400 }); + } + + const checkEmail = await query('SELECT id FROM users WHERE email = $1', [email]); + if (checkEmail.rows.length > 0) { + return NextResponse.json({ error: 'Este e-mail já está cadastrado.' }, { status: 400 }); + } + + // 2. Criar o Tenant (Estabelecimento) - Começa no plano Starter, com status trial de 7 dias + const planRes = await query("SELECT id FROM plans WHERE name = 'Starter' LIMIT 1"); + const planId = planRes.rows.length > 0 ? planRes.rows[0].id : null; + + const tenantRes = await query( + `INSERT INTO tenants (name, slug, email, plan_id, subscription_status, subscription_ends_at) + VALUES ($1, $2, $3, $4, 'trial', NOW() + INTERVAL '7 days') RETURNING *`, + [name, slug, email, planId] + ); + const newTenant = tenantRes.rows[0]; + + // 3. Criar o Usuário Dono (Owner) + const salt = await bcrypt.genSalt(10); + const hash = await bcrypt.hash(password, salt); + + const userRes = await query(` + INSERT INTO users (tenant_id, name, email, password_hash, role) + VALUES ($1, $2, $3, $4, 'owner') RETURNING id, name, email, role + `, [newTenant.id, name, email, hash]); + + const newUser = userRes.rows[0]; + + // 4. Gerar Token JWT + const secret = process.env.JWT_SECRET || 'fallback_secret_for_development'; + const token = jwt.sign( + { userId: newUser.id, tenantId: newTenant.id, role: newUser.role }, + secret, + { expiresIn: '7d' } + ); + + return NextResponse.json({ + success: true, + token, + user: { + id: newUser.id, + name: newUser.name, + email: newUser.email, + role: newUser.role, + tenant_id: newTenant.id, + tenant_slug: newTenant.slug, + tenant_name: newTenant.name + } + }); + + } catch (error: any) { + console.error('Registration error:', error); + return NextResponse.json({ error: error.message }, { status: 500 }); + } +} diff --git a/src/app/register/page.tsx b/src/app/register/page.tsx index fba5222..fee3a3e 100644 --- a/src/app/register/page.tsx +++ b/src/app/register/page.tsx @@ -11,25 +11,35 @@ export default function RegisterPage() { const [loading, setLoading] = useState(false); const router = useRouter(); - const handleRegister = (e: React.FormEvent) => { + const handleRegister = async (e: React.FormEvent) => { e.preventDefault(); setLoading(true); - // Simulação do provisionamento do novo tenant do SaaS - setTimeout(() => { + try { + const res = await fetch('/api/auth/register', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ name, slug, email, password }) + }); + + const data = await res.json(); + + if (res.ok) { + // Salva os dados de login reais! + localStorage.setItem('agendapro_token', data.token); + localStorage.setItem('agendapro_tenant_name', data.user.tenant_name); + localStorage.setItem('agendapro_tenant_slug', data.user.tenant_slug); + + // Redireciona para o painel principal do app + router.push('/'); + } else { + alert(data.error || 'Erro ao criar conta'); + } + } catch (error) { + alert('Erro de conexão ao criar conta'); + } finally { setLoading(false); - // Salva o novo tenant simulado nos cookies ou localstorage para o login funcionar - localStorage.setItem('agendapro_token', 'demo_token'); - localStorage.setItem('agendapro_tenant_name', name); - localStorage.setItem('agendapro_plan', 'Starter'); // Plano inicial trial - - // Cria cookie para o admin enxergar o novo tenant se necessário - const date = new Date(); - date.setTime(date.getTime() + (7*24*60*60*1000)); - document.cookie = "agendapro_new_tenant=" + encodeURIComponent(name) + "; expires=" + date.toUTCString() + "; path=/"; - - router.push('/'); - }, 1500); + } }; const handleNameChange = (val: string) => {