287 lines
14 KiB
TypeScript
287 lines
14 KiB
TypeScript
"use client"
|
|
|
|
import { useState, useCallback, useEffect } from "react"
|
|
import { Button } from "@/components/ui/button"
|
|
import { jobsApi, transformApiJobToFrontend } from "@/lib/api"
|
|
import { Job } from "@/lib/types"
|
|
import Link from "next/link"
|
|
import { ArrowRight, CheckCircle2, ChevronLeft, ChevronRight } from "lucide-react"
|
|
import Image from "next/image"
|
|
import { motion } from "framer-motion"
|
|
import { useTranslation } from "@/lib/i18n"
|
|
import { Navbar } from "@/components/navbar"
|
|
import { Footer } from "@/components/footer"
|
|
import { HomeSearch } from "@/components/home-search"
|
|
import { JobCard } from "@/components/job-card"
|
|
import useEmblaCarousel from "embla-carousel-react"
|
|
|
|
export default function Home() {
|
|
const { t } = useTranslation()
|
|
const [jobs, setJobs] = useState<Job[]>([])
|
|
const [loading, setLoading] = useState(true)
|
|
|
|
const [emblaRef, emblaApi] = useEmblaCarousel({
|
|
align: "start",
|
|
loop: false,
|
|
skipSnaps: false,
|
|
dragFree: true
|
|
})
|
|
|
|
const [moreJobsEmblaRef, moreJobsEmblaApi] = useEmblaCarousel({
|
|
align: "start",
|
|
loop: false,
|
|
skipSnaps: false,
|
|
dragFree: true
|
|
})
|
|
|
|
const [prevBtnDisabled, setPrevBtnDisabled] = useState(true)
|
|
const [nextBtnDisabled, setNextBtnDisabled] = useState(true)
|
|
const [moreJobsPrevBtnDisabled, setMoreJobsPrevBtnDisabled] = useState(true)
|
|
const [moreJobsNextBtnDisabled, setMoreJobsNextBtnDisabled] = useState(true)
|
|
|
|
useEffect(() => {
|
|
async function fetchJobs() {
|
|
try {
|
|
const res = await jobsApi.list({ limit: 8 })
|
|
if (res.data) {
|
|
setJobs(res.data.map(transformApiJobToFrontend))
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to fetch jobs:", error)
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
fetchJobs()
|
|
}, [])
|
|
|
|
const scrollPrev = useCallback(() => {
|
|
if (emblaApi) emblaApi.scrollPrev()
|
|
}, [emblaApi])
|
|
|
|
const scrollNext = useCallback(() => {
|
|
if (emblaApi) emblaApi.scrollNext()
|
|
}, [emblaApi])
|
|
|
|
const scrollMoreJobsPrev = useCallback(() => {
|
|
if (moreJobsEmblaApi) moreJobsEmblaApi.scrollPrev()
|
|
}, [moreJobsEmblaApi])
|
|
|
|
const scrollMoreJobsNext = useCallback(() => {
|
|
if (moreJobsEmblaApi) moreJobsEmblaApi.scrollNext()
|
|
}, [moreJobsEmblaApi])
|
|
|
|
const onSelect = useCallback((emblaApi: any) => {
|
|
setPrevBtnDisabled(!emblaApi.canScrollPrev())
|
|
setNextBtnDisabled(!emblaApi.canScrollNext())
|
|
}, [])
|
|
|
|
const onMoreJobsSelect = useCallback((emblaApi: any) => {
|
|
setMoreJobsPrevBtnDisabled(!emblaApi.canScrollPrev())
|
|
setMoreJobsNextBtnDisabled(!emblaApi.canScrollNext())
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
if (!emblaApi) return
|
|
onSelect(emblaApi)
|
|
emblaApi.on("reInit", onSelect)
|
|
emblaApi.on("select", onSelect)
|
|
}, [emblaApi, onSelect])
|
|
|
|
useEffect(() => {
|
|
if (!moreJobsEmblaApi) return
|
|
onMoreJobsSelect(moreJobsEmblaApi)
|
|
moreJobsEmblaApi.on("reInit", onMoreJobsSelect)
|
|
moreJobsEmblaApi.on("select", onMoreJobsSelect)
|
|
}, [moreJobsEmblaApi, onMoreJobsSelect])
|
|
|
|
return (
|
|
<div className="min-h-screen bg-gray-50 flex flex-col font-sans">
|
|
<Navbar />
|
|
|
|
<main className="flex-grow">
|
|
{/* Hero Section */}
|
|
<section className="relative h-[520px] flex items-center justify-center bg-[#1F2F40]">
|
|
<div className="absolute inset-0 z-0">
|
|
<Image
|
|
src="/10.png"
|
|
alt="Background"
|
|
fill
|
|
className="object-cover opacity-55 contrast-125"
|
|
priority
|
|
/>
|
|
<div className="absolute inset-0 bg-gradient-to-r from-[#132131]/95 via-[#1F2F40]/90 to-[#1F2F40]/45" />
|
|
</div>
|
|
|
|
<div className="container mx-auto px-4 sm:px-6 relative z-10 text-center sm:text-left">
|
|
<div className="max-w-3xl">
|
|
<motion.h1
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.5 }}
|
|
className="text-4xl md:text-5xl lg:text-6xl font-bold text-white mb-6 leading-tight tracking-tight"
|
|
>
|
|
{t("home.hero.title")} <br className="hidden sm:block" />
|
|
{t("home.hero.titleLine2")}
|
|
</motion.h1>
|
|
|
|
<motion.p
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.5, delay: 0.1 }}
|
|
className="text-lg md:text-xl text-gray-300 mb-8 max-w-xl 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 }}
|
|
>
|
|
<Link href="/jobs">
|
|
<Button className="h-12 px-8 bg-orange-500 hover:bg-orange-600 text-white font-bold text-lg rounded-lg shadow-lg transition-transform hover:scale-105">
|
|
{t("home.hero.cta")}
|
|
</Button>
|
|
</Link>
|
|
</motion.div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Search Section */}
|
|
<section className="px-4 sm:px-6 mb-16">
|
|
<div className="container mx-auto">
|
|
<HomeSearch />
|
|
</div>
|
|
</section>
|
|
|
|
{/* Latest Jobs Section */}
|
|
<section className="py-12 bg-gray-50">
|
|
<div className="container mx-auto px-4 sm:px-6">
|
|
<div className="flex items-center justify-between mb-8">
|
|
<h2 className="text-2xl md:text-3xl font-bold text-gray-900">
|
|
{t("home.featuredJobs.title")}
|
|
</h2>
|
|
<div className="flex items-center gap-2">
|
|
<Button
|
|
variant="outline"
|
|
size="icon"
|
|
className="rounded-full border-gray-300 hover:border-orange-500 hover:text-orange-500 transition-all w-10 h-10"
|
|
onClick={scrollPrev}
|
|
disabled={prevBtnDisabled}
|
|
>
|
|
<ChevronLeft className="w-5 h-5" />
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
size="icon"
|
|
className="rounded-full border-gray-300 hover:border-orange-500 hover:text-orange-500 transition-all w-10 h-10"
|
|
onClick={scrollNext}
|
|
disabled={nextBtnDisabled}
|
|
>
|
|
<ChevronRight className="w-5 h-5" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="overflow-hidden" ref={emblaRef}>
|
|
<div className="flex gap-6">
|
|
{loading ? (
|
|
<div className="flex-[0_0_100%] text-center py-8">Carregando vagas...</div>
|
|
) : jobs.slice(0, 8).map((job, index) => (
|
|
<div key={`latest-${job.id}-${index}`} className="flex-[0_0_100%] sm:flex-[0_0_50%] lg:flex-[0_0_50%] xl:flex-[0_0_33.333%] 2xl:flex-[0_0_25%] min-w-0 pb-1">
|
|
<JobCard job={job} />
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* More Jobs Section */}
|
|
<section className="py-12 bg-white">
|
|
<div className="container mx-auto px-4 sm:px-6">
|
|
<div className="flex items-center justify-between mb-8">
|
|
<h2 className="text-2xl md:text-3xl font-bold text-gray-900">
|
|
{t("home.moreJobs.title")}
|
|
</h2>
|
|
<div className="flex items-center gap-3">
|
|
<Button
|
|
variant="outline"
|
|
size="icon"
|
|
className="rounded-full border-gray-300 hover:border-orange-500 hover:text-orange-500 transition-all w-10 h-10"
|
|
onClick={scrollMoreJobsPrev}
|
|
disabled={moreJobsPrevBtnDisabled}
|
|
>
|
|
<ChevronLeft className="w-5 h-5" />
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
size="icon"
|
|
className="rounded-full border-gray-300 hover:border-orange-500 hover:text-orange-500 transition-all w-10 h-10"
|
|
onClick={scrollMoreJobsNext}
|
|
disabled={moreJobsNextBtnDisabled}
|
|
>
|
|
<ChevronRight className="w-5 h-5" />
|
|
</Button>
|
|
<Link href="/jobs">
|
|
<Button className="bg-orange-500 hover:bg-orange-600 text-white font-bold rounded-lg">
|
|
{t("home.moreJobs.viewAll")}
|
|
</Button>
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="overflow-hidden" ref={moreJobsEmblaRef}>
|
|
<div className="flex gap-6">
|
|
{loading ? (
|
|
<div className="flex-[0_0_100%] text-center py-8">Carregando vagas...</div>
|
|
) : jobs.slice(0, 8).map((job, index) => (
|
|
<div key={`more-${job.id}-${index}`} className="flex-[0_0_100%] sm:flex-[0_0_50%] lg:flex-[0_0_50%] xl:flex-[0_0_33.333%] 2xl:flex-[0_0_25%] min-w-0 pb-1">
|
|
<JobCard job={job} />
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Bottom CTA Section */}
|
|
<section className="py-16 bg-white">
|
|
<div className="container mx-auto px-4 sm:px-6">
|
|
<div className="bg-[#1F2F40] rounded-[2rem] p-8 md:p-16 relative overflow-hidden text-center md:text-left flex flex-col md:flex-row items-center justify-between min-h-[400px]">
|
|
<div className="relative z-10 max-w-xl">
|
|
<h2 className="text-3xl md:text-4xl font-bold text-white mb-4 leading-tight">
|
|
{t("home.cta.title")}
|
|
</h2>
|
|
<p className="text-base text-gray-300 mb-8">
|
|
{t("home.cta.subtitle")}
|
|
</p>
|
|
<Link href="/register/user">
|
|
<Button className="h-12 px-8 bg-white text-gray-900 hover:bg-gray-100 font-bold text-lg rounded-lg">
|
|
{t("home.cta.button")}
|
|
</Button>
|
|
</Link>
|
|
</div>
|
|
|
|
<div className="absolute inset-0 z-0">
|
|
<div className="absolute right-0 top-0 h-full w-full md:w-2/3 lg:w-1/2">
|
|
<Image
|
|
src="/muie.jpeg"
|
|
alt="Professional"
|
|
fill
|
|
className="object-cover object-center md:object-right opacity-40 md:opacity-100"
|
|
/>
|
|
<div className="absolute inset-0 bg-gradient-to-t md:bg-gradient-to-r from-[#1F2F40] via-[#1F2F40]/30 to-transparent" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
|
|
<Footer />
|
|
</div>
|
|
)
|
|
}
|