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",
mobile: "Celular",
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",
termsLink: "Condições Legais",
termsAnd: "e a",
@ -218,6 +224,8 @@ const contentByLocale = {
paymentRequired: "Selecione um método de pagamento.",
registerError: "Erro ao registrar empresa",
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!",
},
@ -335,6 +343,12 @@ const contentByLocale = {
phonePlaceholder: "(00) 0000-0000",
mobile: "Mobile",
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",
termsLink: "Legal Terms",
termsAnd: "and the",
@ -374,6 +388,8 @@ const contentByLocale = {
paymentRequired: "Please select a payment method.",
registerError: "Error registering company",
unexpectedError: "Unexpected error publishing job",
passwordRequired: "Please enter a password with at least 8 characters.",
passwordMismatch: "Passwords do not match.",
},
success: "Job posted successfully!",
},
@ -491,6 +507,12 @@ const contentByLocale = {
phonePlaceholder: "(00) 0000-0000",
mobile: "Celular",
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",
termsLink: "Condiciones Legales",
termsAnd: "y la",
@ -530,6 +552,8 @@ const contentByLocale = {
paymentRequired: "Seleccione un método de pago.",
registerError: "Error al registrar empresa",
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!",
},
@ -593,6 +617,8 @@ export default function PostJobPage() {
contactEmail: "",
contactPhone: "",
contactMobile: "",
password: "",
confirmPassword: "",
acceptTerms: false,
acceptMarketing: false,
});
@ -655,6 +681,14 @@ export default function PostJobPage() {
toast.error(c.errors.billingEmailInvalid);
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) {
toast.error(c.errors.termsRequired);
return false;
@ -688,8 +722,8 @@ export default function PostJobPage() {
setLoading(true);
try {
const apiBase = process.env.NEXT_PUBLIC_API_URL || "";
const password = "Temp@123456";
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`, {
method: "POST",
@ -697,9 +731,10 @@ export default function PostJobPage() {
body: JSON.stringify({
companyName: company.name,
email: billing.contactEmail,
contact: contactFullName || null,
document: cleanDigits(company.document) || null,
password,
phone: billingPhone ? `+55${billingPhone}` : null,
password: billing.password,
phone: billingPhone ? `+${billingPhone}` : null,
website: company.website || null,
employeeCount: company.employeeCount || null,
foundedYear: company.foundedYear ? Number(company.foundedYear) : null,
@ -712,7 +747,7 @@ export default function PostJobPage() {
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 salaryMax = job.salaryMode === "fixed" ? Number(job.salaryFixed || 0) : Number(job.salaryMax || 0);
@ -724,6 +759,7 @@ export default function PostJobPage() {
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
companyId,
title: job.title,
description: job.description,
location: `${job.location}, ${job.country}`,
@ -1227,6 +1263,33 @@ export default function PostJobPage() {
</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>
<Label>{c.billing.billingAddress}</Label>
<textarea