From 74aa41675d46514c2f3f3925efb668e5583d23dc Mon Sep 17 00:00:00 2001 From: Tiago Yamamoto Date: Mon, 22 Dec 2025 14:58:11 -0300 Subject: [PATCH] Add English jobs slug with redirects --- frontend/README.md | 2 +- frontend/public/sitemap.xml | 14 +- .../src/app/jobs/[id]/candidatura/page.tsx | 619 ++++++++++++++++++ frontend/src/app/jobs/[id]/page.tsx | 528 +++++++++++++++ frontend/src/app/jobs/page.tsx | 463 +++++++++++++ .../src/app/vagas/[id]/candidatura/page.tsx | 619 +----------------- frontend/src/app/vagas/[id]/page.tsx | 526 +-------------- frontend/src/app/vagas/page.tsx | 464 +------------ frontend/src/components/footer.tsx | 10 +- frontend/src/components/job-card.tsx | 4 +- frontend/src/components/navbar.tsx | 2 +- 11 files changed, 1638 insertions(+), 1613 deletions(-) create mode 100644 frontend/src/app/jobs/[id]/candidatura/page.tsx create mode 100644 frontend/src/app/jobs/[id]/page.tsx create mode 100644 frontend/src/app/jobs/page.tsx diff --git a/frontend/README.md b/frontend/README.md index 543f52e..ada0796 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -66,7 +66,7 @@ O tema está definido em `src/app/globals.css` usando CSS variables com cores `o | Rota | Descrição | Acesso | |------|-----------|--------| | `/` | Landing page | Público | -| `/vagas` | Listagem de vagas | Público | +| `/jobs` | Listagem de vagas | Público | | `/login` | Autenticação | Público | | `/dashboard/admin` | Painel admin | SuperAdmin | | `/dashboard/empresa` | Painel empresa | CompanyAdmin, Recruiter | diff --git a/frontend/public/sitemap.xml b/frontend/public/sitemap.xml index 709c392..a4dde7b 100644 --- a/frontend/public/sitemap.xml +++ b/frontend/public/sitemap.xml @@ -8,7 +8,7 @@ 1.0 - https://gohorsejobs.com/vagas + https://gohorsejobs.com/jobs 2025-12-14 daily 0.9 @@ -28,25 +28,25 @@ - https://gohorsejobs.com/vagas?tech=python + https://gohorsejobs.com/jobs?tech=python 2025-12-14 daily 0.8 - https://gohorsejobs.com/vagas?tech=react + https://gohorsejobs.com/jobs?tech=react 2025-12-14 daily 0.8 - https://gohorsejobs.com/vagas?tech=nodejs + https://gohorsejobs.com/jobs?tech=nodejs 2025-12-14 daily 0.8 - https://gohorsejobs.com/vagas?tech=dados + https://gohorsejobs.com/jobs?tech=dados 2025-12-14 daily 0.8 @@ -54,13 +54,13 @@ - https://gohorsejobs.com/vagas?type=remoto + https://gohorsejobs.com/jobs?type=remoto 2025-12-14 daily 0.8 - https://gohorsejobs.com/vagas?type=full-time + https://gohorsejobs.com/jobs?type=full-time 2025-12-14 daily 0.7 diff --git a/frontend/src/app/jobs/[id]/candidatura/page.tsx b/frontend/src/app/jobs/[id]/candidatura/page.tsx new file mode 100644 index 0000000..f76d224 --- /dev/null +++ b/frontend/src/app/jobs/[id]/candidatura/page.tsx @@ -0,0 +1,619 @@ +"use client"; + +import { useState, use } from "react"; +import { useRouter } from "next/navigation"; +import Link from "next/link"; +import { motion, AnimatePresence } from "framer-motion"; +import { + ChevronRight, + ChevronLeft, + Upload, + CheckCircle2, + Briefcase, + FileText, + User, + MessageSquare, + Save, + ArrowLeft, +} from "lucide-react"; + +import { Button } from "@/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Textarea } from "@/components/ui/textarea"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Checkbox } from "@/components/ui/checkbox"; +import { Progress } from "@/components/ui/progress"; +import { Separator } from "@/components/ui/separator"; +import { Navbar } from "@/components/navbar"; +import { Footer } from "@/components/footer"; +import { useNotify } from "@/contexts/notification-context"; +import { mockJobs } from "@/lib/mock-data"; + +// Definição Dos Passos +const steps = [ + { id: 1, title: "Dados Pessoais", icon: User }, + { id: 2, title: "Currículo e Documentos", icon: FileText }, + { id: 3, title: "Experiência", icon: Briefcase }, + { id: 4, title: "Perguntas Adicionais", icon: MessageSquare }, +]; + + +export const runtime = 'edge'; + +export default function JobApplicationPage({ + params, +}: { + params: Promise<{ id: string }>; +}) { + const { id } = use(params); + const router = useRouter(); + const notify = useNotify(); + const [currentStep, setCurrentStep] = useState(1); + const [isSubmitting, setIsSubmitting] = useState(false); + + // Achar os detalhes da vaga + const job = mockJobs.find((j) => j.id === id) || mockJobs[0]; + + // Estado do formulário + const [formData, setFormData] = useState({ + // Etapa 1 + fullName: "", + email: "", + phone: "", + linkedin: "", + privacyAccepted: false, + // Etapa 2 + resume: null as File | null, + coverLetter: "", + portfolioUrl: "", + // Etapa 3 + salaryExpectation: "", + hasExperience: "", + // Etapa 4 + whyUs: "", + availability: [] as string[], + }); + + const handleInputChange = (field: string, value: any) => { + setFormData((prev) => ({ ...prev, [field]: value })); + }; + + const validateStep = (step: number) => { + switch (step) { + case 1: + if (!formData.fullName || !formData.email || !formData.phone) { + notify.error( + "Campos obrigatórios", + "Por favor, preencha todos os campos obrigatórios." + ); + return false; + } + if (!formData.email.includes("@")) { + notify.error( + "E-mail inválido", + "Por favor, insira um e-mail válido." + ); + return false; + } + if (!formData.privacyAccepted) { + notify.error( + "Termos de Privacidade", + "Você precisa aceitar a política de privacidade para continuar." + ); + return false; + } + return true; + case 2: + return true; + case 3: + if (!formData.salaryExpectation || !formData.hasExperience) { + notify.error( + "Campos obrigatórios", + "Por favor, responda todas as perguntas." + ); + return false; + } + return true; + case 4: + if (!formData.whyUs || formData.availability.length === 0) { + notify.error( + "Campos obrigatórios", + "Por favor, preencha o motivo e selecione pelo menos uma disponibilidade." + ); + return false; + } + return true; + default: + return true; + } + }; + + const handleNext = () => { + if (validateStep(currentStep)) { + if (currentStep < steps.length) { + setCurrentStep((prev) => prev + 1); + window.scrollTo(0, 0); + } else { + handleSubmit(); + } + } + }; + + const handleBack = () => { + if (currentStep > 1) { + setCurrentStep((prev) => prev - 1); + window.scrollTo(0, 0); + } + }; + + const handleSubmit = async () => { + setIsSubmitting(true); + // Simular um chamado de API + await new Promise((resolve) => setTimeout(resolve, 2000)); + + notify.success( + "Candidatura enviada com sucesso!", + `Boa sorte! Sua candidatura para ${job.title} foi recebida.` + ); + + router.push("/dashboard/candidato/candidaturas"); + }; + + const handleSaveDraft = () => { + notify.info( + "Rascunho salvo", + "Você pode continuar sua candidatura mais tarde." + ); + }; + + const progress = (currentStep / steps.length) * 100; + + if (!job) return null; + + return ( +
+ + +
+
+ {/* Header */} +
+ + + Voltar para detalhes da vaga + +
+
+

+ Candidatura: {job.title} +

+

+ {job.company} • {job.location} +

+
+
+ Tempo estimado: 5 min +
+
+
+ + {/* Progresso das etapas */} +
+
+ + Etapa {currentStep} de {steps.length}:{" "} + + {steps[currentStep - 1].title} + + + + {Math.round(progress)}% + +
+ + + {/* Indicador de etapas (DESKTOP) */} +
+ {steps.map((step) => { + const Icon = step.icon; + const isActive = step.id === currentStep; + const isCompleted = step.id < currentStep; + + return ( +
+
+ {isCompleted ? ( + + ) : ( + + )} +
+ {step.title} +
+ ); + })} +
+
+ + {/* Conteúdo do formulário */} +
+ + + + + {steps[currentStep - 1].title} + + Preencha as informações abaixo para continuar. + + + + + {/* Etapa 1: Dados Pessoais */} + {currentStep === 1 && ( +
+
+
+ + + handleInputChange("fullName", e.target.value) + } + /> +
+
+ + + handleInputChange("email", e.target.value) + } + /> +
+
+ +
+
+ + + handleInputChange("phone", e.target.value) + } + /> +
+
+ + + handleInputChange("linkedin", e.target.value) + } + /> +
+
+ +
+ + handleInputChange("privacyAccepted", checked) + } + /> + +
+
+ )} + + {/* Etapa 2: Dccumentos */} + {currentStep === 2 && ( +
+
+ +
+
+
+ +
+
+

+ Clique para fazer upload ou arraste o arquivo +

+

+ PDF, DOCX ou TXT (Máx. 5MB) +

+
+
+
+
+ +
+ + + handleInputChange("portfolioUrl", e.target.value) + } + /> +
+ +
+ +