feat(jobs/new): add user account registration and fix companyId in job payload

- Add password + confirm-password fields to billing step (Step 3) so the
  recruiter creates their account credentials during the job posting flow
- Validate password length (≥8) and confirmation match before proceeding
- Extract company `id` from POST /auth/register/company response and send
  it as `companyId` in the job creation payload (was missing — caused 400)
- Pass `contact` (full name) to company registration endpoint
- Remove hardcoded "Temp@123456" password; use the user-provided one
- Remove hardcoded "+55" phone prefix; send raw digits with "+" prefix
- Add translations (pt-BR, en, es) for password fields and error messages

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tiago Yamamoto 2026-02-22 12:32:55 -06:00
parent 61d64d846a
commit 2686b69506

View file

@ -179,6 +179,12 @@ const contentByLocale = {
phonePlaceholder: "(00) 0000-0000", phonePlaceholder: "(00) 0000-0000",
mobile: "Celular", mobile: "Celular",
mobilePlaceholder: "(00) 00000-0000", mobilePlaceholder: "(00) 00000-0000",
accountTitle: "Dados de acesso à conta",
accountHint: "Você usará este e-mail e senha para acessar o painel da empresa.",
password: "Senha *",
passwordPlaceholder: "Mínimo 8 caracteres",
confirmPassword: "Confirmar senha *",
confirmPasswordPlaceholder: "Repita a senha",
termsAccept: "Li e aceito as", termsAccept: "Li e aceito as",
termsLink: "Condições Legais", termsLink: "Condições Legais",
termsAnd: "e a", termsAnd: "e a",
@ -218,6 +224,8 @@ const contentByLocale = {
paymentRequired: "Selecione um método de pagamento.", paymentRequired: "Selecione um método de pagamento.",
registerError: "Erro ao registrar empresa", registerError: "Erro ao registrar empresa",
unexpectedError: "Erro inesperado ao publicar vaga", unexpectedError: "Erro inesperado ao publicar vaga",
passwordRequired: "Informe uma senha com ao menos 8 caracteres.",
passwordMismatch: "As senhas não coincidem.",
}, },
success: "Vaga cadastrada com sucesso!", success: "Vaga cadastrada com sucesso!",
}, },
@ -335,6 +343,12 @@ const contentByLocale = {
phonePlaceholder: "(00) 0000-0000", phonePlaceholder: "(00) 0000-0000",
mobile: "Mobile", mobile: "Mobile",
mobilePlaceholder: "(00) 00000-0000", mobilePlaceholder: "(00) 00000-0000",
accountTitle: "Account access details",
accountHint: "You will use this email and password to access the company dashboard.",
password: "Password *",
passwordPlaceholder: "Minimum 8 characters",
confirmPassword: "Confirm password *",
confirmPasswordPlaceholder: "Repeat password",
termsAccept: "I have read and accept the", termsAccept: "I have read and accept the",
termsLink: "Legal Terms", termsLink: "Legal Terms",
termsAnd: "and the", termsAnd: "and the",
@ -374,6 +388,8 @@ const contentByLocale = {
paymentRequired: "Please select a payment method.", paymentRequired: "Please select a payment method.",
registerError: "Error registering company", registerError: "Error registering company",
unexpectedError: "Unexpected error publishing job", unexpectedError: "Unexpected error publishing job",
passwordRequired: "Please enter a password with at least 8 characters.",
passwordMismatch: "Passwords do not match.",
}, },
success: "Job posted successfully!", success: "Job posted successfully!",
}, },
@ -491,6 +507,12 @@ const contentByLocale = {
phonePlaceholder: "(00) 0000-0000", phonePlaceholder: "(00) 0000-0000",
mobile: "Celular", mobile: "Celular",
mobilePlaceholder: "(00) 00000-0000", mobilePlaceholder: "(00) 00000-0000",
accountTitle: "Datos de acceso a la cuenta",
accountHint: "Usará este correo y contraseña para acceder al panel de la empresa.",
password: "Contraseña *",
passwordPlaceholder: "Mínimo 8 caracteres",
confirmPassword: "Confirmar contraseña *",
confirmPasswordPlaceholder: "Repita la contraseña",
termsAccept: "He leído y acepto las", termsAccept: "He leído y acepto las",
termsLink: "Condiciones Legales", termsLink: "Condiciones Legales",
termsAnd: "y la", termsAnd: "y la",
@ -530,6 +552,8 @@ const contentByLocale = {
paymentRequired: "Seleccione un método de pago.", paymentRequired: "Seleccione un método de pago.",
registerError: "Error al registrar empresa", registerError: "Error al registrar empresa",
unexpectedError: "Error inesperado al publicar vacante", unexpectedError: "Error inesperado al publicar vacante",
passwordRequired: "Ingrese una contraseña con al menos 8 caracteres.",
passwordMismatch: "Las contraseñas no coinciden.",
}, },
success: "¡Vacante publicada con éxito!", success: "¡Vacante publicada con éxito!",
}, },
@ -593,6 +617,8 @@ export default function PostJobPage() {
contactEmail: "", contactEmail: "",
contactPhone: "", contactPhone: "",
contactMobile: "", contactMobile: "",
password: "",
confirmPassword: "",
acceptTerms: false, acceptTerms: false,
acceptMarketing: false, acceptMarketing: false,
}); });
@ -655,6 +681,14 @@ export default function PostJobPage() {
toast.error(c.errors.billingEmailInvalid); toast.error(c.errors.billingEmailInvalid);
return false; return false;
} }
if (!billing.password || billing.password.length < 8) {
toast.error(c.errors.passwordRequired);
return false;
}
if (billing.password !== billing.confirmPassword) {
toast.error(c.errors.passwordMismatch);
return false;
}
if (!billing.acceptTerms) { if (!billing.acceptTerms) {
toast.error(c.errors.termsRequired); toast.error(c.errors.termsRequired);
return false; return false;
@ -688,8 +722,8 @@ export default function PostJobPage() {
setLoading(true); setLoading(true);
try { try {
const apiBase = process.env.NEXT_PUBLIC_API_URL || ""; const apiBase = process.env.NEXT_PUBLIC_API_URL || "";
const password = "Temp@123456";
const billingPhone = cleanDigits(billing.contactMobile || billing.contactPhone || job.applicationPhone); const billingPhone = cleanDigits(billing.contactMobile || billing.contactPhone || job.applicationPhone);
const contactFullName = [billing.contactName, billing.contactLastName].filter(Boolean).join(" ");
const registerRes = await fetch(`${apiBase}/api/v1/auth/register/company`, { const registerRes = await fetch(`${apiBase}/api/v1/auth/register/company`, {
method: "POST", method: "POST",
@ -697,9 +731,10 @@ export default function PostJobPage() {
body: JSON.stringify({ body: JSON.stringify({
companyName: company.name, companyName: company.name,
email: billing.contactEmail, email: billing.contactEmail,
contact: contactFullName || null,
document: cleanDigits(company.document) || null, document: cleanDigits(company.document) || null,
password, password: billing.password,
phone: billingPhone ? `+55${billingPhone}` : null, phone: billingPhone ? `+${billingPhone}` : null,
website: company.website || null, website: company.website || null,
employeeCount: company.employeeCount || null, employeeCount: company.employeeCount || null,
foundedYear: company.foundedYear ? Number(company.foundedYear) : null, foundedYear: company.foundedYear ? Number(company.foundedYear) : null,
@ -712,7 +747,7 @@ export default function PostJobPage() {
throw new Error(err.message || c.errors.registerError); throw new Error(err.message || c.errors.registerError);
} }
const { token } = await registerRes.json(); const { id: companyId, token } = await registerRes.json();
const salaryMin = job.salaryMode === "fixed" ? Number(job.salaryFixed || 0) : Number(job.salaryMin || 0); const salaryMin = job.salaryMode === "fixed" ? Number(job.salaryFixed || 0) : Number(job.salaryMin || 0);
const salaryMax = job.salaryMode === "fixed" ? Number(job.salaryFixed || 0) : Number(job.salaryMax || 0); const salaryMax = job.salaryMode === "fixed" ? Number(job.salaryFixed || 0) : Number(job.salaryMax || 0);
@ -724,6 +759,7 @@ export default function PostJobPage() {
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
}, },
body: JSON.stringify({ body: JSON.stringify({
companyId,
title: job.title, title: job.title,
description: job.description, description: job.description,
location: `${job.location}, ${job.country}`, location: `${job.location}, ${job.country}`,
@ -1227,6 +1263,33 @@ export default function PostJobPage() {
</div> </div>
</div> </div>
<div className="border rounded-lg p-4 space-y-4 bg-gray-50">
<div>
<h4 className="font-semibold text-[#1d2c44]">{c.billing.accountTitle}</h4>
<p className="text-xs text-muted-foreground mt-1">{c.billing.accountHint}</p>
</div>
<div className="grid md:grid-cols-2 gap-4">
<div>
<Label>{c.billing.password}</Label>
<Input
type="password"
placeholder={c.billing.passwordPlaceholder}
value={billing.password}
onChange={(e) => setBilling({ ...billing, password: e.target.value })}
/>
</div>
<div>
<Label>{c.billing.confirmPassword}</Label>
<Input
type="password"
placeholder={c.billing.confirmPasswordPlaceholder}
value={billing.confirmPassword}
onChange={(e) => setBilling({ ...billing, confirmPassword: e.target.value })}
/>
</div>
</div>
</div>
<div> <div>
<Label>{c.billing.billingAddress}</Label> <Label>{c.billing.billingAddress}</Label>
<textarea <textarea