Merge pull request #56 from rede5/codex/implementar-mais-25-por-cento

feat(post-job): add preview and billing steps with company-visibility toggle
This commit is contained in:
Tiago Yamamoto 2026-02-14 17:22:27 -03:00 committed by GitHub
commit cd99fdb66a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 121 additions and 16 deletions

View file

@ -171,12 +171,12 @@ Mapear o que já existe no GoHorseJobs e o que ainda falta para alcançar um flu
---
### 2) Pré-visualização
- [ ] Exibir versão final da vaga com todos os metadados selecionados.
- [ ] Indicar claramente campos ocultos (ex.: dados de empresa) antes de prosseguir.
- [ ] Permitir voltar para edição sem perda de dados.
- [x] Exibir versão final da vaga com todos os metadados selecionados.
- [x] Indicar claramente campos ocultos (ex.: dados de empresa) antes de prosseguir.
- [x] Permitir voltar para edição sem perda de dados.
### 3) Informações de faturamento
- [ ] Capturar dados fiscais (pessoa/empresa, documento, endereço de cobrança).
- [x] Capturar dados fiscais (pessoa/empresa, documento, endereço de cobrança).
- [ ] Exibir plano/preço por país e duração (ex.: **US$130/30 dias** para EUA, quando aplicável).
- [ ] Validar consistência entre país da vaga e país de faturamento conforme regra de negócio.

View file

@ -54,7 +54,7 @@ const getCurrencySymbol = (code: string): string => {
export default function PostJobPage() {
const router = useRouter();
const [step, setStep] = useState<1 | 2 | 3>(1);
const [step, setStep] = useState<1 | 2 | 3 | 4>(1);
const { locale, setLocale } = useTranslation();
const lang = useMemo<Language>(() => (locale === "pt-BR" ? "pt" : locale), [locale]);
@ -87,6 +87,14 @@ export default function PostJobPage() {
employeeCount: "",
foundedYear: "",
description: "",
hidePublicProfile: false,
});
const [billing, setBilling] = useState({
legalType: "company",
document: "",
billingCountry: "",
address: "",
});
const [showPassword, setShowPassword] = useState(false);
@ -213,6 +221,12 @@ export default function PostJobPage() {
return false;
}
if (!billing.document || !billing.billingCountry || !billing.address) {
toast.error("Preencha os dados obrigatórios de faturamento.");
setStep(4);
return false;
}
return true;
};
@ -242,6 +256,8 @@ export default function PostJobPage() {
setStep(2);
} else if (step === 2) {
setStep(3);
} else if (step === 3) {
setStep(4);
}
};
@ -307,6 +323,10 @@ export default function PostJobPage() {
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,
@ -368,7 +388,7 @@ export default function PostJobPage() {
{/* Progress Steps */}
<div className="flex justify-center gap-4 mb-8">
{[1, 2, 3].map((s) => (
{[1, 2, 3, 4].map((s) => (
<div
key={s}
className={`flex items-center gap-2 ${step >= s ? "text-primary" : "text-muted-foreground"}`}
@ -377,7 +397,10 @@ export default function PostJobPage() {
{s}
</div>
<span className="hidden sm:inline text-sm">
{s === 1 ? t.steps.data : s === 2 ? "Formulário" : t.steps.confirm}
{s === 1 && "Dados"}
{s === 2 && "Formulário"}
{s === 3 && "Pré-visualização"}
{s === 4 && "Faturamento"}
</span>
</div>
))}
@ -388,12 +411,14 @@ export default function PostJobPage() {
<CardTitle>
{step === 1 && t.cardTitle.step1}
{step === 2 && "Configure o Formulário"}
{step === 3 && t.cardTitle.step2}
{step === 3 && "Pré-visualização"}
{step === 4 && t.cardTitle.step2}
</CardTitle>
<CardDescription>
{step === 1 && t.cardDesc.step1}
{step === 2 && "Defina as perguntas que os candidatos deverão responder."}
{step === 3 && t.cardDesc.step2}
{step === 3 && "Confira como o anúncio será exibido antes de prosseguir."}
{step === 4 && "Informe os dados fiscais para finalizar a publicação."}
</CardDescription>
</CardHeader>
<CardContent>
@ -574,6 +599,20 @@ export default function PostJobPage() {
/>
</div>
<div className="flex items-start gap-3 rounded-md border p-3">
<input
id="hide-company-data"
type="checkbox"
checked={company.hidePublicProfile}
onChange={(e) => setCompany({ ...company, hidePublicProfile: e.target.checked })}
className="mt-1"
/>
<div>
<Label htmlFor="hide-company-data" className="cursor-pointer">Ocultar dados da empresa</Label>
<p className="text-xs text-muted-foreground">Quando ativo, nome, site e descrição da empresa não aparecem na vaga pública.</p>
</div>
</div>
{/* Separator */}
<div className="border-t pt-6 mt-6">
<h3 className="font-semibold text-lg mb-4 flex items-center gap-2">
@ -886,24 +925,24 @@ export default function PostJobPage() {
<div className="bg-blue-50 border border-blue-200 text-blue-800 px-4 py-3 rounded text-sm mb-4">
<p><strong>Crie formulários inteligentes:</strong> Faça perguntas específicas para filtrar os melhores candidatos.</p>
</div>
<JobFormBuilder
questions={questions}
onChange={setQuestions}
<JobFormBuilder
questions={questions}
onChange={setQuestions}
/>
<div className="flex gap-3 pt-6">
<Button variant="outline" onClick={() => setStep(1)} className="flex-1">
{t.buttons.back}
</Button>
<Button onClick={handleNext} className="flex-1">
Revisar e Publicar
Ir para pré-visualização
</Button>
</div>
</div>
)}
{/* Step 3: Confirm */}
{/* Step 3: Preview */}
{step === 3 && (
<div className="space-y-6">
<div className="bg-muted/50 rounded-lg p-4">
@ -934,6 +973,10 @@ export default function PostJobPage() {
<p><strong>Currículo:</strong> {job.resumeRequirement}</p>
<p><strong>Área:</strong> {job.jobCategory || "Não informado"}</p>
<p><strong>Benefícios:</strong> {job.benefits.length > 0 ? job.benefits.join(", ") : "Não informado"}</p>
<p><strong>Visibilidade da empresa:</strong> {company.hidePublicProfile ? "Oculta na vaga pública" : "Visível"}</p>
{company.hidePublicProfile && (
<p className="text-xs text-muted-foreground mt-1">Campos ocultos: nome da empresa, site e descrição.</p>
)}
<p><strong>{t.common.type}:</strong> {
(job.employmentType ? (t.options.contract[job.employmentType as keyof typeof t.options.contract] || job.employmentType) : t.options.any)
} / {
@ -946,6 +989,68 @@ export default function PostJobPage() {
<Button variant="outline" onClick={() => setStep(2)} className="flex-1">
{t.buttons.back}
</Button>
<Button onClick={handleNext} className="flex-1">
Prosseguir para faturamento
</Button>
</div>
</div>
)}
{/* Step 4: Billing + Publish */}
{step === 4 && (
<div className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<Label>Tipo fiscal</Label>
<select
value={billing.legalType}
onChange={(e) => setBilling({ ...billing, legalType: e.target.value })}
className="w-full px-3 py-2 border rounded-lg bg-background"
>
<option value="company">Pessoa jurídica</option>
<option value="individual">Pessoa física</option>
</select>
</div>
<div>
<Label>Documento fiscal *</Label>
<Input
value={billing.document}
onChange={(e) => setBilling({ ...billing, document: e.target.value })}
placeholder={billing.legalType === "company" ? "CNPJ" : "CPF/NIF"}
/>
</div>
</div>
<div>
<Label>País de faturamento *</Label>
<select
value={billing.billingCountry}
onChange={(e) => setBilling({ ...billing, billingCountry: e.target.value })}
className="w-full px-3 py-2 border rounded-lg bg-background"
>
<option value="">Selecione</option>
{JOB_COUNTRIES.map((country) => (
<option key={country} value={country}>{country}</option>
))}
</select>
</div>
<div>
<Label>Endereço de cobrança *</Label>
<Textarea
value={billing.address}
onChange={(e) => setBilling({ ...billing, address: e.target.value })}
placeholder="Rua, número, cidade, estado e CEP"
/>
</div>
<div className="bg-muted/50 rounded-lg p-4 text-sm">
<p><strong>Resumo:</strong> {job.title} · {job.country || "País não informado"}</p>
<p><strong>Status após envio:</strong> pending_review</p>
</div>
<div className="flex gap-3">
<Button variant="outline" onClick={() => setStep(3)} className="flex-1">
{t.buttons.back}
</Button>
<Button onClick={handleSubmit} disabled={loading} className="flex-1">
{loading ? t.buttons.publishing : t.buttons.publish}
</Button>