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 [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', {
|
||||||
setLoading(false);
|
method: 'POST',
|
||||||
// Salva o novo tenant simulado nos cookies ou localstorage para o login funcionar
|
headers: { 'Content-Type': 'application/json' },
|
||||||
localStorage.setItem('agendapro_token', 'demo_token');
|
body: JSON.stringify({ name, slug, email, password })
|
||||||
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 data = await res.json();
|
||||||
const date = new Date();
|
|
||||||
date.setTime(date.getTime() + (7*24*60*60*1000));
|
|
||||||
document.cookie = "agendapro_new_tenant=" + encodeURIComponent(name) + "; expires=" + date.toUTCString() + "; path=/";
|
|
||||||
|
|
||||||
|
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('/');
|
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) => {
|
const handleNameChange = (val: string) => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue