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
Build and Deploy (Gitea Actions) / build-and-deploy (push) Successful in 2m38s
Details
This commit is contained in:
parent
959c6f6039
commit
0c7ffe6b44
|
|
@ -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 });
|
||||
}
|
||||
}
|
||||
|
|
@ -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(() => {
|
||||
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
|
||||
try {
|
||||
const res = await fetch('/api/auth/register', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name, slug, email, password })
|
||||
});
|
||||
|
||||
// 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=/";
|
||||
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('/');
|
||||
}, 1500);
|
||||
} else {
|
||||
alert(data.error || 'Erro ao criar conta');
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Erro de conexão ao criar conta');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleNameChange = (val: string) => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue