132 lines
3.9 KiB
TypeScript
132 lines
3.9 KiB
TypeScript
"use server";
|
|
|
|
import argon2 from "argon2";
|
|
import { redirect } from "next/navigation";
|
|
import { db } from "@/lib/db";
|
|
import { encryptCpf, hashCpf, onlyDigits } from "@/lib/crypto";
|
|
import { cadastroSchema, normalizeWhatsapp } from "@/lib/validators";
|
|
import { sendMail } from "@/lib/mail";
|
|
import { checkRateLimit } from "@/lib/rate-limit";
|
|
|
|
export type CadastroState = {
|
|
ok: boolean;
|
|
message?: string;
|
|
};
|
|
|
|
export async function cadastrarCliente(_state: CadastroState, formData: FormData): Promise<CadastroState> {
|
|
const emailForLimit = formData.get("email")?.toString().trim().toLowerCase() ?? "sem-email";
|
|
const limit = checkRateLimit(`cadastro:${emailForLimit}`, 5, 60 * 60 * 1000);
|
|
|
|
if (!limit.allowed) {
|
|
return { ok: false, message: "Muitas tentativas. Tente novamente mais tarde." };
|
|
}
|
|
|
|
const parsed = cadastroSchema.safeParse({
|
|
nomeCompleto: formData.get("nomeCompleto"),
|
|
cpf: formData.get("cpf"),
|
|
whatsapp: formData.get("whatsapp"),
|
|
email: formData.get("email"),
|
|
senha: formData.get("senha"),
|
|
confirmarSenha: formData.get("confirmarSenha"),
|
|
tipo: formData.get("tipo"),
|
|
razaoSocial: formData.get("razaoSocial") || undefined,
|
|
cnpj: formData.get("cnpj") || undefined,
|
|
cep: formData.get("cep") || undefined,
|
|
logradouro: formData.get("logradouro") || undefined,
|
|
numero: formData.get("numero") || undefined,
|
|
complemento: formData.get("complemento") || undefined,
|
|
bairro: formData.get("bairro") || undefined,
|
|
cidade: formData.get("cidade") || undefined,
|
|
uf: formData.get("uf") || undefined,
|
|
lgpdAceite: formData.get("lgpdAceite")
|
|
});
|
|
|
|
if (!parsed.success) {
|
|
return {
|
|
ok: false,
|
|
message: parsed.error.issues[0]?.message ?? "Revise os dados informados."
|
|
};
|
|
}
|
|
|
|
const data = parsed.data;
|
|
const cpfHash = hashCpf(data.cpf);
|
|
const whatsapp = normalizeWhatsapp(data.whatsapp);
|
|
|
|
if (!whatsapp) {
|
|
return { ok: false, message: "Informe um WhatsApp com DDD." };
|
|
}
|
|
|
|
try {
|
|
await db.$transaction(async (tx) => {
|
|
const exists = await tx.user.findFirst({
|
|
where: {
|
|
OR: [{ email: data.email }, { cliente: { cpfHash } }]
|
|
},
|
|
select: { id: true }
|
|
});
|
|
|
|
if (exists) {
|
|
throw new Error("DUPLICADO");
|
|
}
|
|
|
|
await tx.user.create({
|
|
data: {
|
|
email: data.email,
|
|
senhaHash: await argon2.hash(data.senha),
|
|
papel: "CLIENTE",
|
|
cliente: {
|
|
create: {
|
|
nomeCompleto: data.nomeCompleto,
|
|
cpfCifrado: encryptCpf(data.cpf),
|
|
cpfHash,
|
|
whatsapp,
|
|
tipo: data.tipo,
|
|
razaoSocial: data.tipo === "PJ" ? data.razaoSocial : null,
|
|
cnpj: data.tipo === "PJ" && data.cnpj ? onlyDigits(data.cnpj) : null,
|
|
cep: data.cep ? onlyDigits(data.cep) : null,
|
|
logradouro: data.logradouro || null,
|
|
numero: data.numero || null,
|
|
complemento: data.complemento || null,
|
|
bairro: data.bairro || null,
|
|
cidade: data.cidade || null,
|
|
uf: data.uf || null,
|
|
lgpdAceiteEm: new Date()
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
} catch (error) {
|
|
if (error instanceof Error && error.message === "DUPLICADO") {
|
|
return {
|
|
ok: false,
|
|
message: "Nao foi possivel concluir o cadastro com os dados informados."
|
|
};
|
|
}
|
|
|
|
return {
|
|
ok: false,
|
|
message: "Nao foi possivel concluir o cadastro agora. Tente novamente."
|
|
};
|
|
}
|
|
|
|
try {
|
|
await sendMail({
|
|
to: data.email,
|
|
subject: "Cadastro criado - Briefing SeConectai",
|
|
text: [
|
|
"Seu cadastro foi criado com sucesso.",
|
|
"",
|
|
"Acesse o sistema para iniciar ou acompanhar seu briefing.",
|
|
"",
|
|
"Este e-mail nao inclui CPF completo nem senhas."
|
|
].join("\n")
|
|
});
|
|
} catch {
|
|
// Falha de e-mail nao desfaz cadastro.
|
|
}
|
|
|
|
redirect("/login");
|
|
}
|