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:
commit
cd99fdb66a
2 changed files with 121 additions and 16 deletions
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in a new issue