feat: Registro de conta (Self-service SaaS) 100% funcional integrado ao Admin Panel
Build and Deploy (Gitea Actions) / build-and-deploy (push) Successful in 2m38s Details

This commit is contained in:
Sidney 2026-06-07 22:00:09 -03:00
parent 959c6f6039
commit 0c7ffe6b44
2 changed files with 98 additions and 15 deletions

View File

@ -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 });
}
}

View File

@ -11,25 +11,35 @@ export default function RegisterPage() {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const router = useRouter(); const router = useRouter();
const handleRegister = (e: React.FormEvent) => { const handleRegister = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
setLoading(true); setLoading(true);
// Simulação do provisionamento do novo tenant do SaaS try {
setTimeout(() => { 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); 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) => { const handleNameChange = (val: string) => {