308 lines
13 KiB
TypeScript
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>
|
|
)
|
|
}
|