gohorsejobs/frontend/src/app/page.tsx
2025-12-22 15:30:06 -03:00

308 lines
13 KiB
TypeScript

"use client"
import { Navbar } from "@/components/navbar"
import { Footer } from "@/components/footer"
import { JobCard } from "@/components/job-card"
import { Button } from "@/components/ui/button"
import { Card, CardContent } from "@/components/ui/card"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { mockJobs, mockTestimonials } from "@/lib/mock-data"
import { FileText, CheckCircle, ArrowRight, Building2, Users } from "lucide-react"
import Link from "next/link"
import { motion } from "framer-motion"
import Image from "next/image"
import { useTranslation } from "@/lib/i18n"
import { useState, useEffect } from "react"
import type { Job } from "@/lib/types"
export default function HomePage() {
const { t } = useTranslation()
const [featuredJobs, setFeaturedJobs] = useState<Job[]>([])
const [loading, setLoading] = useState(true)
useEffect(() => {
async function fetchFeaturedJobs() {
try {
const apiBase = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8521"
const mapJobs = (jobs: any[]): Job[] =>
jobs.map((j: any) => ({
id: String(j.id),
title: j.title,
company: j.companyName || t("jobs.confidential"),
location: j.location || t("workMode.remote"),
type: j.employmentType || "full-time",
salary: j.salaryMin ? `R$ ${j.salaryMin}` : t("jobs.salary.negotiable"),
description: j.description,
requirements: j.requirements || [],
postedAt: j.createdAt,
isFeatured: j.isFeatured
}))
const featuredRes = await fetch(`${apiBase}/jobs?featured=true&limit=6`)
if (!featuredRes.ok) throw new Error("Failed to fetch featured jobs")
const featuredData = await featuredRes.json()
const featuredList = featuredData.data ? mapJobs(featuredData.data) : []
if (featuredList.length === 6) {
setFeaturedJobs(featuredList)
return
}
const fallbackRes = await fetch(`${apiBase}/jobs?limit=6`)
if (!fallbackRes.ok) throw new Error("Failed to fetch fallback jobs")
const fallbackData = await fallbackRes.json()
const fallbackList = fallbackData.data ? mapJobs(fallbackData.data) : []
const combined = [...featuredList, ...fallbackList].slice(0, 6)
if (combined.length === 6) {
setFeaturedJobs(combined)
} else if (combined.length > 0) {
setFeaturedJobs(combined)
} else {
setFeaturedJobs(mockJobs.slice(0, 6))
}
} catch (error) {
console.error("Error fetching featured jobs:", error)
// Fallback to mock data if API fails? Or just empty.
// For MVP let's leave empty or maybe keep mock as fallback if needed.
setFeaturedJobs(mockJobs.slice(0, 6))
} finally {
setLoading(false)
}
}
fetchFeaturedJobs()
}, [])
return (
<div className="min-h-screen flex flex-col py-2.5">
<Navbar />
<main className="flex-1">
{/* Hero Section */}
<section className="container mx-auto px-4 sm:px-6 lg:px-8 py-20 lg:py-32">
<div className="grid lg:grid-cols-2 gap-12 items-center max-w-7xl mx-auto">
<div className="text-center lg:text-left">
<motion.h1
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
className="text-4xl sm:text-5xl lg:text-6xl font-bold text-balance mb-6"
>
{t('home.hero.title')}
</motion.h1>
<motion.p
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.1 }}
className="text-lg sm:text-xl text-muted-foreground text-balance mb-8 leading-relaxed"
>
{t('home.hero.subtitle')}
</motion.p>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.2 }}
className="flex flex-col sm:flex-row gap-4 justify-center lg:justify-start"
>
<Link href="/login">
<Button size="lg" className="w-full sm:w-auto">
{t('home.hero.searchJobs')}
<ArrowRight className="ml-2 h-4 w-4" />
</Button>
</Link>
<Link href="/register/company">
<Button size="lg" variant="outline" className="w-full sm:w-auto bg-transparent">
<Building2 className="mr-2 h-4 w-4" />
{t('home.hero.imCompany')}
</Button>
</Link>
</motion.div>
</div>
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.6, delay: 0.3 }}
className="relative hidden lg:block"
>
<div className="relative aspect-square rounded-2xl overflow-hidden bg-muted">
<Image
src="/minimalist-professional-workspace-with-laptop-and-.jpg"
alt="Professional Workspace"
fill
className="object-cover opacity-20"
priority
sizes="100vw"
/>
</div>
</motion.div>
</div>
</section>
{/* Featured Jobs */}
<section className="bg-muted/30 py-20">
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center mb-12">
<h2 className="text-3xl font-bold mb-4">{t('home.featured.title')}</h2>
<p className="text-muted-foreground text-base">{t('home.featured.subtitle')}</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 max-w-7xl mx-auto">
{featuredJobs.map((job, index) => (
<motion.div
key={job.id}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: index * 0.1 }}
>
<JobCard job={job} />
</motion.div>
))}
</div>
<div className="text-center mt-12">
<Link href="/login">
<Button variant="outline" size="lg">
{t('home.featured.viewAll')}
<ArrowRight className="ml-2 h-4 w-4" />
</Button>
</Link>
</div>
</div>
</section>
{/* How it Works */}
<section className="py-20 relative overflow-hidden">
<div className="absolute inset-0 opacity-5">
<Image src="/abstract-minimal-geometric-pattern-light-gray.jpg" alt="" fill className="object-cover" />
</div>
<div className="container mx-auto px-4 sm:px-6 lg:px-8 relative">
<div className="text-center mb-16">
<h2 className="text-3xl font-bold mb-4">{t('home.howItWorks.title')}</h2>
<p className="text-muted-foreground text-base">{t('home.howItWorks.subtitle')}</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 max-w-5xl mx-auto">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
className="text-center"
>
<div className="w-16 h-16 rounded-full flex items-center justify-center mx-auto mb-6 bg-background">
<Users className="text-primary w-9 h-9" />
</div>
<h3 className="text-xl font-semibold mb-3">{t('home.howItWorks.step1.title')}</h3>
<p className="text-muted-foreground leading-relaxed">{t('home.howItWorks.step1.description')}</p>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.1 }}
className="text-center"
>
<div className="w-16 h-16 rounded-full flex items-center justify-center mx-auto mb-6 bg-background">
<FileText className="text-primary w-9 h-9" />
</div>
<h3 className="text-xl font-semibold mb-3">{t('home.howItWorks.step2.title')}</h3>
<p className="text-muted-foreground leading-relaxed">{t('home.howItWorks.step2.description')}</p>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.2 }}
className="text-center"
>
<div className="w-16 h-16 rounded-full flex items-center justify-center mx-auto mb-6 bg-background">
<CheckCircle className="text-primary h-9 w-9" />
</div>
<h3 className="text-xl font-semibold mb-3">{t('home.howItWorks.step3.title')}</h3>
<p className="text-muted-foreground leading-relaxed">{t('home.howItWorks.step3.description')}</p>
</motion.div>
</div>
</div>
</section>
{/* Testimonials */}
<section className="bg-muted/30 py-20 border-0">
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center mb-12">
<h2 className="text-3xl font-bold mb-4">{t('home.testimonials.title')}</h2>
<p className="text-muted-foreground text-base">{t('home.testimonials.subtitle')}</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 max-w-6xl mx-auto">
{mockTestimonials.map((testimonial, index) => (
<motion.div
key={testimonial.id}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: index * 0.1 }}
>
<Card>
<CardContent className="pt-6">
<div className="flex items-center gap-4 mb-4">
<div>
<p className="font-semibold">{testimonial.name}</p>
<p className="text-sm text-muted-foreground">{testimonial.role}</p>
</div>
</div>
<p className="text-muted-foreground leading-relaxed text-pretty">{testimonial.content}</p>
</CardContent>
</Card>
</motion.div>
))}
</div>
</div>
</section>
{/* CTA Section */}
<section className="py-20">
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
<Card className="overflow-hidden bg-primary">
<div className="grid lg:grid-cols-2 gap-0">
{/* Text Content */}
<CardContent className="py-16 px-8 lg:px-12 flex flex-col justify-center bg-primary text-primary-foreground">
<h2 className="text-3xl lg:text-4xl font-bold mb-4 text-balance">{t('home.cta.title')}</h2>
<p className="mb-8 text-primary-foreground/90 text-balance leading-relaxed text-base">
{t('home.cta.subtitle')}
</p>
<div>
<Link href="/register/candidate">
<Button size="lg" variant="secondary">
{t('home.cta.button')}
<ArrowRight className="ml-2 h-4 w-4" />
</Button>
</Link>
</div>
</CardContent>
{/* Image */}
<div className="relative h-64 lg:h-auto min-h-[500px]">
<Image
src="/professional-person-smiling-at-laptop-optimistic.jpg"
alt="Professional smiling at laptop"
fill
className="object-cover opacity-10"
sizes="100vw"
/>
</div>
</div>
</Card>
</div>
</section>
</main>
<Footer />
</div>
)
}