"use client"; import { useMemo, useState } from "react"; import { useRouter } from "next/navigation"; import { translations, Language } from "./translations"; import { toast } from "sonner"; import { Navbar } from "@/components/navbar"; import { Footer } from "@/components/footer"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { Label } from "@/components/ui/label"; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; import { Building2, Briefcase, Mail, Lock, Phone, MapPin, Eye, EyeOff, Globe } from "lucide-react"; import { LocationPicker } from "@/components/location-picker"; import { RichTextEditor } from "@/components/rich-text-editor"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { useTranslation } from "@/lib/i18n"; import { JobFormBuilder, Question } from "@/components/job-form-builder"; // Common Country Codes const COUNTRY_CODES = [ { code: "+55", country: "Brasil (BR)" }, { code: "+1", country: "Estados Unidos (US)" }, { code: "+351", country: "Portugal (PT)" }, { code: "+44", country: "Reino Unido (UK)" }, { code: "+33", country: "França (FR)" }, { code: "+49", country: "Alemanha (DE)" }, { code: "+34", country: "Espanha (ES)" }, { code: "+39", country: "Itália (IT)" }, { code: "+81", country: "Japão (JP)" }, { code: "+86", country: "China (CN)" }, { code: "+91", country: "Índia (IN)" }, { code: "+52", country: "México (MX)" }, { code: "+54", country: "Argentina (AR)" }, { code: "+57", country: "Colômbia (CO)" }, { code: "+56", country: "Chile (CL)" }, { code: "+51", country: "Peru (PE)" }, ].sort((a, b) => a.country.localeCompare(b.country)); // Currency symbol helper const getCurrencySymbol = (code: string): string => { const symbols: Record = { 'BRL': 'R$', 'USD': '$', 'EUR': '€', 'JPY': '¥', 'GBP': '£', 'CNY': '¥', 'AED': 'د.إ', 'CAD': 'C$', 'AUD': 'A$', 'CHF': 'Fr' }; return symbols[code] || code; }; export default function PostJobPage() { const router = useRouter(); const [step, setStep] = useState<1 | 2 | 3 | 4>(1); const { locale, setLocale } = useTranslation(); const lang = useMemo(() => (locale === "pt-BR" ? "pt" : locale), [locale]); const t = translations[lang]; // Helper inside to use t const getSalaryPeriodLabel = (type: string): string => { const labels: Record = { 'hourly': t.options.period.hourly, 'daily': t.options.period.daily, 'weekly': t.options.period.weekly, 'monthly': t.options.period.monthly, 'yearly': t.options.period.yearly }; return labels[type] || ''; }; const [loading, setLoading] = useState(false); // Company/User data const [company, setCompany] = useState({ name: "", email: "", document: "", password: "", confirmPassword: "", ddi: "+55", phone: "", website: "", employeeCount: "", foundedYear: "", description: "", hidePublicProfile: false, }); const [billing, setBilling] = useState({ legalType: "company", document: "", billingCountry: "", address: "", }); const [showPassword, setShowPassword] = useState(false); const [showConfirmPassword, setShowConfirmPassword] = useState(false); // Job data const [job, setJob] = useState({ title: "", description: "", location: "", country: "", salaryMin: "", salaryMax: "", salaryFixed: "", // For fixed salary mode currency: "BRL", // Default currency salaryType: "monthly", // Default salary period employmentType: "", workMode: "remote", workingHours: "", salaryNegotiable: false, // Candidate proposes salary descriptionLanguage: "", applicationChannel: "email", applicationEmail: "", applicationUrl: "", applicationPhone: "", resumeRequirement: "optional", jobCategory: "", benefits: [] as string[], }); const [questions, setQuestions] = useState([]); // Salary mode toggle: 'fixed' | 'range' const [salaryMode, setSalaryMode] = useState<'fixed' | 'range'>('fixed'); const BENEFIT_OPTIONS = ["Plano de saúde", "Vale refeição", "Vale transporte", "Bônus", "Home office", "Gym pass"]; const JOB_CATEGORIES = ["Tecnologia", "Produto", "Dados", "Marketing", "Vendas", "Operações", "Financeiro", "RH"]; const JOB_COUNTRIES = ["BR", "PT", "US", "ES", "UK", "DE", "FR", "JP"]; const cleanCNPJ = (value: string) => value.replace(/\D/g, ""); const isValidCNPJ = (value: string) => { const cnpj = cleanCNPJ(value); if (cnpj.length !== 14) return false; if (/^(\d)\1+$/.test(cnpj)) return false; const calcCheckDigit = (base: string, weights: number[]) => { const sum = base .split("") .reduce((acc, current, index) => acc + Number(current) * weights[index], 0); const mod = sum % 11; return mod < 2 ? 0 : 11 - mod; }; const base12 = cnpj.slice(0, 12); const digit1 = calcCheckDigit(base12, [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]); const digit2 = calcCheckDigit(`${base12}${digit1}`, [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]); return cnpj === `${base12}${digit1}${digit2}`; }; const isHttpsUrl = (value: string) => /^https:\/\/.+/i.test(value); const isPhoneWithDDI = (value: string) => /^\+\d{1,3}\s?\d{8,14}$/.test(value.trim()); const formatPhoneForDisplay = (value: string) => { // Simple formatting to just allow numbers and basic separators if needed // For now, just pass through but maybe restrict chars? return value.replace(/[^\d\s-]/g, ""); }; const validateForm = () => { if (!company.name || !company.email || !company.password) { toast.error(t.errors.company_required); setStep(1); // Ensure we are on step 1 for company data errors return false; } if (company.document && !isValidCNPJ(company.document)) { toast.error("CNPJ inválido."); setStep(1); return false; } if (company.password !== company.confirmPassword) { toast.error(t.errors.password_mismatch); setStep(1); // Ensure we are on step 1 for password mismatch return false; } if (company.password.length < 8) { toast.error(t.errors.password_length); setStep(1); // Ensure we are on step 1 for password length return false; } if (!job.title || !job.description || !job.location || !job.country || !job.descriptionLanguage) { toast.error(t.errors.job_required); setStep(1); // Stay on step 1 for job data errors return false; } if (job.title.length > 65) { toast.error("O título da vaga deve ter no máximo 65 caracteres."); setStep(1); return false; } if (job.applicationChannel === "email" && !job.applicationEmail) { toast.error("Informe um e-mail para candidatura."); setStep(1); return false; } if (job.applicationChannel === "url" && !isHttpsUrl(job.applicationUrl)) { toast.error("Informe uma URL HTTPS válida para candidatura."); setStep(1); return false; } if (job.applicationChannel === "phone" && !isPhoneWithDDI(job.applicationPhone)) { toast.error("Informe um telefone com DDI válido (ex: +55 11999998888)."); setStep(1); return false; } if (!billing.document || !billing.billingCountry || !billing.address) { toast.error("Preencha os dados obrigatórios de faturamento."); setStep(4); return false; } return true; }; const handleNext = () => { // Only validate step 1 fields to move to step 2 if (step === 1) { if (!company.name || !company.email || !company.password) { toast.error(t.errors.company_required); return; } if (company.password !== company.confirmPassword) { toast.error(t.errors.password_mismatch); return; } if (company.password.length < 8) { toast.error(t.errors.password_length); return; } if (!job.title || !job.description || !job.location || !job.country || !job.descriptionLanguage) { toast.error("Preencha título, localidade, país, idioma e descrição da vaga."); return; } if (job.title.length > 65) { toast.error("O título da vaga deve ter no máximo 65 caracteres."); return; } setStep(2); } else if (step === 2) { setStep(3); } else if (step === 3) { setStep(4); } }; const handleSubmit = async () => { if (!validateForm()) return; setLoading(true); try { const apiBase = process.env.NEXT_PUBLIC_API_URL || ""; // Format phone: DDI + Phone (digits only) const cleanPhone = company.phone.replace(/\D/g, ""); const finalPhone = cleanPhone ? `${company.ddi}${cleanPhone}` : ""; // 1. Register Company (creates user + company) const registerRes = await fetch(`${apiBase}/api/v1/auth/register/company`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ companyName: company.name, email: company.email, document: cleanCNPJ(company.document) || null, password: company.password, phone: finalPhone, website: company.website || null, employeeCount: company.employeeCount || null, foundedYear: company.foundedYear ? parseInt(company.foundedYear) : null, description: company.description || null, }), }); if (!registerRes.ok) { const err = await registerRes.json(); throw new Error(err.message || "Erro ao registrar empresa"); } const { token } = await registerRes.json(); // 2. Create Job with token const jobRes = await fetch(`${apiBase}/api/v1/jobs`, { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}`, }, body: JSON.stringify({ title: job.title, description: job.description, location: `${job.location}, ${job.country}`, // Salary logic: if negotiable, send null values salaryMin: job.salaryNegotiable ? null : (salaryMode === 'fixed' ? (job.salaryFixed ? parseInt(job.salaryFixed) : null) : (job.salaryMin ? parseInt(job.salaryMin) : null)), salaryMax: job.salaryNegotiable ? null : (salaryMode === 'fixed' ? (job.salaryFixed ? parseInt(job.salaryFixed) : null) : (job.salaryMax ? parseInt(job.salaryMax) : null)), salaryType: job.salaryNegotiable ? null : job.salaryType, currency: job.salaryNegotiable ? null : job.currency, salaryNegotiable: job.salaryNegotiable, employmentType: job.employmentType || null, workingHours: job.workingHours || null, workMode: job.workMode, status: "pending", questions: questions.length > 0 ? questions : null, languageLevel: job.descriptionLanguage || null, requirements: { category: job.jobCategory || null, resumeRequirement: job.resumeRequirement, applicationChannel: job.applicationChannel, applicationEmail: job.applicationEmail || null, applicationUrl: job.applicationUrl || null, applicationPhone: job.applicationPhone || null, hideCompanyData: company.hidePublicProfile, }, benefits: { selected: job.benefits, }, }), }); if (!jobRes.ok) { const err = await jobRes.json(); throw new Error(err.message || "Erro ao criar vaga"); } // Save token for future use localStorage.setItem("token", token); localStorage.setItem("auth_token", token); toast.success(t.success.job_created); router.push("/dashboard/jobs"); } catch (err: any) { toast.error(err.message || t.errors.submit_failed); } finally { setLoading(false); } }; return (

{t.title}

{t.subtitle}

{/* Progress Steps */}
{[1, 2, 3, 4].map((s) => (
= s ? "text-primary" : "text-muted-foreground"}`} >
= s ? "bg-primary text-white" : "bg-muted"}`}> {s}
{s === 1 && "Dados"} {s === 2 && "Formulário"} {s === 3 && "Pré-visualização"} {s === 4 && "Faturamento"}
))}
{step === 1 && t.cardTitle.step1} {step === 2 && "Configure o Formulário"} {step === 3 && "Pré-visualização"} {step === 4 && t.cardTitle.step2} {step === 1 && t.cardDesc.step1} {step === 2 && "Defina as perguntas que os candidatos deverão responder."} {step === 3 && "Confira como o anúncio será exibido antes de prosseguir."} {step === 4 && "Informe os dados fiscais para finalizar a publicação."} {/* Step 1: Company */} {step === 1 && (
setCompany({ ...company, name: e.target.value })} placeholder={t.company.namePlaceholder} className="pl-10" />
setCompany({ ...company, email: e.target.value })} placeholder="contato@empresa.com" className="pl-10" />
setCompany({ ...company, document: e.target.value })} placeholder="00.000.000/0000-00" />
{/* Password Field */}
setCompany({ ...company, password: e.target.value })} placeholder="••••••••" className="pl-10 pr-10" // Extra padding for eye icon />
{/* Confirm Password Field */}
setCompany({ ...company, confirmPassword: e.target.value })} placeholder="••••••••" className="pl-10 pr-10" />
{/* Phone Field with DDI */}
setCompany({ ...company, phone: e.target.value })} placeholder="11 99999-9999" className="pl-10 border-0 shadow-none focus-visible:ring-0 rounded-l-none h-10" />

{t.company.phoneHelp}

{/* Website */}
setCompany({ ...company, website: e.target.value })} placeholder="https://www.suaempresa.com.br" className="pl-10" />
{/* Employee Count & Founded Year */}
setCompany({ ...company, foundedYear: e.target.value })} placeholder="ex: 2010" min="1800" max={new Date().getFullYear()} />
{/* About Company */}
setCompany({ ...company, description: val })} placeholder="Descreva sua empresa, cultura, missão e valores..." minHeight="120px" />
setCompany({ ...company, hidePublicProfile: e.target.checked })} className="mt-1" />

Quando ativo, nome, site e descrição da empresa não aparecem na vaga pública.

{/* Separator */}

{t.job.title}

setJob({ ...job, title: e.target.value })} placeholder={t.job.jobTitlePlaceholder} className="pl-10" maxLength={65} />

{job.title.length}/65 caracteres

setJob({ ...job, description: val })} placeholder="Descreva as responsabilidades, requisitos e benefícios..." minHeight="150px" />
{/* Includes Label and Layout internally. */} setJob({ ...job, location: val })} />
{/* Salary Section */}
{!job.salaryNegotiable && ( <> {/* Currency and Period Row */}
{/* Salary Value(s) */} {salaryMode === 'fixed' ? ( setJob({ ...job, salaryFixed: e.target.value })} placeholder="Valor" /> ) : (
setJob({ ...job, salaryMin: e.target.value })} placeholder="Mín" /> setJob({ ...job, salaryMax: e.target.value })} placeholder="Máx" />
)} )}
{job.applicationChannel === "email" && (
setJob({ ...job, applicationEmail: e.target.value })} placeholder="jobs@empresa.com" />
)} {job.applicationChannel === "url" && (
setJob({ ...job, applicationUrl: e.target.value })} placeholder="https://empresa.com/carreiras" />
)} {job.applicationChannel === "phone" && (
setJob({ ...job, applicationPhone: e.target.value })} placeholder="+55 11999998888" />
)}
{BENEFIT_OPTIONS.map((benefit) => { const checked = job.benefits.includes(benefit); return ( ); })}
)} {/* Step 2: Questions */} {step === 2 && (

Crie formulários inteligentes: Faça perguntas específicas para filtrar os melhores candidatos.

)} {/* Step 3: Preview */} {step === 3 && (

{t.common.company}

{t.common.name}: {company.name}

{t.common.email}: {company.email}

{company.phone &&

{t.common.phone}: {company.ddi} {company.phone}

}

{t.common.job}

{t.common.title}: {job.title}

{t.common.location}: {job.location || "Não informado"}

País: {job.country || "Não informado"}

Idioma: {job.descriptionLanguage || "Não informado"}

{t.common.salary}: { job.salaryNegotiable ? t.job.salaryNegotiable : salaryMode === 'fixed' ? (job.salaryFixed ? `${getCurrencySymbol(job.currency)} ${job.salaryFixed} ${getSalaryPeriodLabel(job.salaryType)}` : t.job.salaryNegotiable) : (job.salaryMin && job.salaryMax ? `${getCurrencySymbol(job.currency)} ${job.salaryMin} - ${job.salaryMax} ${getSalaryPeriodLabel(job.salaryType)}` : t.job.salaryNegotiable) }

Perguntas Personalizadas: {questions.length}

Canal de candidatura: {job.applicationChannel}

Currículo: {job.resumeRequirement}

Área: {job.jobCategory || "Não informado"}

Benefícios: {job.benefits.length > 0 ? job.benefits.join(", ") : "Não informado"}

Visibilidade da empresa: {company.hidePublicProfile ? "Oculta na vaga pública" : "Visível"}

{company.hidePublicProfile && (

Campos ocultos: nome da empresa, site e descrição.

)}

{t.common.type}: { (job.employmentType ? (t.options.contract[job.employmentType as keyof typeof t.options.contract] || job.employmentType) : t.options.any) } / { job.workingHours === 'full-time' ? t.options.hours.fullTime : job.workingHours === 'part-time' ? t.options.hours.partTime : t.options.any } / { job.workMode === 'remote' ? t.options.mode.remote : job.workMode === 'hybrid' ? t.options.mode.hybrid : t.options.mode.onsite }

)} {/* Step 4: Billing + Publish */} {step === 4 && (
setBilling({ ...billing, document: e.target.value })} placeholder={billing.legalType === "company" ? "CNPJ" : "CPF/NIF"} />