feat(post-job): add preview and billing step to publish flow
This commit is contained in:
parent
a9f8e40d7e
commit
01e7a3b920
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
|
### 2) Pré-visualização
|
||||||
- [ ] Exibir versão final da vaga com todos os metadados selecionados.
|
- [x] Exibir versão final da vaga com todos os metadados selecionados.
|
||||||
- [ ] Indicar claramente campos ocultos (ex.: dados de empresa) antes de prosseguir.
|
- [x] Indicar claramente campos ocultos (ex.: dados de empresa) antes de prosseguir.
|
||||||
- [ ] Permitir voltar para edição sem perda de dados.
|
- [x] Permitir voltar para edição sem perda de dados.
|
||||||
|
|
||||||
### 3) Informações de faturamento
|
### 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).
|
- [ ] 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.
|
- [ ] 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() {
|
export default function PostJobPage() {
|
||||||
const router = useRouter();
|
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 { locale, setLocale } = useTranslation();
|
||||||
|
|
||||||
const lang = useMemo<Language>(() => (locale === "pt-BR" ? "pt" : locale), [locale]);
|
const lang = useMemo<Language>(() => (locale === "pt-BR" ? "pt" : locale), [locale]);
|
||||||
|
|
@ -87,6 +87,14 @@ export default function PostJobPage() {
|
||||||
employeeCount: "",
|
employeeCount: "",
|
||||||
foundedYear: "",
|
foundedYear: "",
|
||||||
description: "",
|
description: "",
|
||||||
|
hidePublicProfile: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [billing, setBilling] = useState({
|
||||||
|
legalType: "company",
|
||||||
|
document: "",
|
||||||
|
billingCountry: "",
|
||||||
|
address: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
|
@ -213,6 +221,12 @@ export default function PostJobPage() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!billing.document || !billing.billingCountry || !billing.address) {
|
||||||
|
toast.error("Preencha os dados obrigatórios de faturamento.");
|
||||||
|
setStep(4);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -242,6 +256,8 @@ export default function PostJobPage() {
|
||||||
setStep(2);
|
setStep(2);
|
||||||
} else if (step === 2) {
|
} else if (step === 2) {
|
||||||
setStep(3);
|
setStep(3);
|
||||||
|
} else if (step === 3) {
|
||||||
|
setStep(4);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -307,6 +323,10 @@ export default function PostJobPage() {
|
||||||
category: job.jobCategory || null,
|
category: job.jobCategory || null,
|
||||||
resumeRequirement: job.resumeRequirement,
|
resumeRequirement: job.resumeRequirement,
|
||||||
applicationChannel: job.applicationChannel,
|
applicationChannel: job.applicationChannel,
|
||||||
|
applicationEmail: job.applicationEmail || null,
|
||||||
|
applicationUrl: job.applicationUrl || null,
|
||||||
|
applicationPhone: job.applicationPhone || null,
|
||||||
|
hideCompanyData: company.hidePublicProfile,
|
||||||
},
|
},
|
||||||
benefits: {
|
benefits: {
|
||||||
selected: job.benefits,
|
selected: job.benefits,
|
||||||
|
|
@ -368,7 +388,7 @@ export default function PostJobPage() {
|
||||||
|
|
||||||
{/* Progress Steps */}
|
{/* Progress Steps */}
|
||||||
<div className="flex justify-center gap-4 mb-8">
|
<div className="flex justify-center gap-4 mb-8">
|
||||||
{[1, 2, 3].map((s) => (
|
{[1, 2, 3, 4].map((s) => (
|
||||||
<div
|
<div
|
||||||
key={s}
|
key={s}
|
||||||
className={`flex items-center gap-2 ${step >= s ? "text-primary" : "text-muted-foreground"}`}
|
className={`flex items-center gap-2 ${step >= s ? "text-primary" : "text-muted-foreground"}`}
|
||||||
|
|
@ -377,7 +397,10 @@ export default function PostJobPage() {
|
||||||
{s}
|
{s}
|
||||||
</div>
|
</div>
|
||||||
<span className="hidden sm:inline text-sm">
|
<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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
@ -388,12 +411,14 @@ export default function PostJobPage() {
|
||||||
<CardTitle>
|
<CardTitle>
|
||||||
{step === 1 && t.cardTitle.step1}
|
{step === 1 && t.cardTitle.step1}
|
||||||
{step === 2 && "Configure o Formulário"}
|
{step === 2 && "Configure o Formulário"}
|
||||||
{step === 3 && t.cardTitle.step2}
|
{step === 3 && "Pré-visualização"}
|
||||||
|
{step === 4 && t.cardTitle.step2}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
{step === 1 && t.cardDesc.step1}
|
{step === 1 && t.cardDesc.step1}
|
||||||
{step === 2 && "Defina as perguntas que os candidatos deverão responder."}
|
{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>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
|
|
@ -574,6 +599,20 @@ export default function PostJobPage() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</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 */}
|
{/* Separator */}
|
||||||
<div className="border-t pt-6 mt-6">
|
<div className="border-t pt-6 mt-6">
|
||||||
<h3 className="font-semibold text-lg mb-4 flex items-center gap-2">
|
<h3 className="font-semibold text-lg mb-4 flex items-center gap-2">
|
||||||
|
|
@ -897,13 +936,13 @@ export default function PostJobPage() {
|
||||||
{t.buttons.back}
|
{t.buttons.back}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={handleNext} className="flex-1">
|
<Button onClick={handleNext} className="flex-1">
|
||||||
Revisar e Publicar
|
Ir para pré-visualização
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Step 3: Confirm */}
|
{/* Step 3: Preview */}
|
||||||
{step === 3 && (
|
{step === 3 && (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="bg-muted/50 rounded-lg p-4">
|
<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>Currículo:</strong> {job.resumeRequirement}</p>
|
||||||
<p><strong>Área:</strong> {job.jobCategory || "Não informado"}</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>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> {
|
<p><strong>{t.common.type}:</strong> {
|
||||||
(job.employmentType ? (t.options.contract[job.employmentType as keyof typeof t.options.contract] || job.employmentType) : t.options.any)
|
(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">
|
<Button variant="outline" onClick={() => setStep(2)} className="flex-1">
|
||||||
{t.buttons.back}
|
{t.buttons.back}
|
||||||
</Button>
|
</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">
|
<Button onClick={handleSubmit} disabled={loading} className="flex-1">
|
||||||
{loading ? t.buttons.publishing : t.buttons.publish}
|
{loading ? t.buttons.publishing : t.buttons.publish}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue