diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index d4dda8e..ccf683a 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -1,471 +1,160 @@ -"use client" +use client + +import { Button } from @/components/ui/button +import { mockJobs } from @/lib/mock-data +import Link from next/link +import { ArrowRight, CheckCircle2 } 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 + +export default function Home() { + const { t } = useTranslation() + + return ( +
+ + +
+ {/* Hero Section */} +
+ {/* Background Image with Overlay */} +
+ Background +
+
+ +
+
+ + Encontre a Vaga de TI
+ dos Seus Sonhos. +
+ + + Conectamos você com as melhores empresas e techs. + + + + + + + +
+
+
+ + {/* Search Section */} +
+
+ +
+
+ + {/* Latest Jobs Section */} +
+
+

+ Últimas Vagas Cadastradas +

+ +
+ {mockJobs.slice(0, 4).map((job, index) => ( + + ))} +
+
+
+ + {/* More Jobs Section */} +
+
+
+

+ Mais Vagas +

+ + + +
+ +
+ {mockJobs.slice(0, 8).map((job, index) => ( + + ))} +
+
+
+ + + {/* Bottom CTA Section */} +
+
+
+ + {/* Content */} +
+

+ Milhares de oportunidades
esperam você. +

+

+ Conecte cargos, talentos, tomada de ações de vagas. +

+ + + +
+ + {/* Image Background for CTA */} +
+
+ Professional + {/* Gradient Overlay to blend with dark background */} +
+
+
+
+
+
+
+ +
+ ) +} -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, ChevronLeft, ChevronRight, Eye } from "lucide-react" -import Link from "next/link" -import { motion } from "framer-motion" -import Image from "next/image" -import { useTranslation } from "@/lib/i18n" -import { useConfig } from "@/contexts/ConfigContext" - -import { useState, useEffect } from "react" -import type { Job } from "@/lib/types" - -export default function HomePage() { - const { t } = useTranslation() - const config = useConfig() - const [featuredJobs, setFeaturedJobs] = useState(mockJobs.slice(0, 31)) - const [loading, setLoading] = useState(true) - const [featuredIndex, setFeaturedIndex] = useState(0) - const [moreJobsIndex, setMoreJobsIndex] = useState(0) - const [openFilters, setOpenFilters] = useState({ - contractType: false, - workMode: false, - location: false, - salary: false - }) - - const toggleFilter = (filterName: keyof typeof openFilters) => { - setOpenFilters(prev => ({ - contractType: false, - workMode: false, - location: false, - salary: false, - [filterName]: !prev[filterName] - })) - } - - useEffect(() => { - async function fetchFeaturedJobs() { - try { - const apiBase = config.apiUrl - console.log("[DEBUG] API Base URL:", apiBase) - - 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 - })) - - console.log("[DEBUG] Fetching featured jobs from:", `${apiBase}/api/v1/jobs?featured=true&limit=31`) - const featuredRes = await fetch(`${apiBase}/api/v1/jobs?featured=true&limit=31`) - console.log("[DEBUG] Featured response status:", featuredRes.status) - - if (!featuredRes.ok) throw new Error("Failed to fetch featured jobs") - const featuredData = await featuredRes.json() - console.log("[DEBUG] Featured data from API:", featuredData) - - const featuredList = featuredData.data ? mapJobs(featuredData.data) : [] - console.log("[DEBUG] Mapped featured jobs:", featuredList.length, "jobs") - - if (featuredList.length >= 24) { - console.log("[DEBUG] Using featured/API jobs") - setFeaturedJobs(featuredList.slice(0, 31)) - return - } - - console.log("[DEBUG] Fetching fallback jobs from:", `${apiBase}/api/v1/jobs?limit=31`) - const fallbackRes = await fetch(`${apiBase}/api/v1/jobs?limit=31`) - console.log("[DEBUG] Fallback response status:", fallbackRes.status) - - if (!fallbackRes.ok) throw new Error("Failed to fetch fallback jobs") - const fallbackData = await fallbackRes.json() - console.log("[DEBUG] Fallback data from API:", fallbackData) - - const fallbackList = fallbackData.data ? mapJobs(fallbackData.data) : [] - console.log("[DEBUG] Mapped fallback jobs:", fallbackList.length, "jobs") - - const combined = [...featuredList, ...fallbackList].slice(0, 31) - console.log("[DEBUG] Combined jobs:", combined.length, "jobs") - - if (combined.length >= 24) { - console.log("[DEBUG] Using combined jobs") - setFeaturedJobs(combined) - } - } catch (error) { - console.error("[DEBUG] ❌ Error fetching featured jobs:", error) - } finally { - setLoading(false) - } - } - fetchFeaturedJobs() - }, []) - - return ( -
- - -
- {/* Hero Section */} -
-
- Background -
-
-
-
- - {t('home.hero.title')}
{t('home.hero.titleLine2')} -
- - {t('home.hero.subtitle')} - - - - - - -
-
-
-
- - {/* Search Bar Section */} -
-
-
-
- - - - -
- -
- {/* Contract Type - Static Expanded */} -
-
- {t('home.search.contractType')} -
-
- - - -
-
- - {/* Work Mode - Static Expanded */} -
-
- {t('home.search.workMode')} -
-
- - - -
-
- - {/* Location - Static Expanded */} -
-
- {t('home.search.location')} -
-
- -
-
- - {/* Salary - Static Expanded */} -
-
- {t('home.search.salary')} -
-
- -
-
- - {/* Filter Button - Unified */} - -
-
-
-
- - {/* Featured Jobs */} -
-
-
-

{t('home.featuredJobs.title')}

-
- -
- {(featuredJobs.length >= 4 ? featuredJobs.slice(0, 4) : mockJobs.slice(0, 4)) - .map((job, index) => { - const dates = ['02/06', '05/06', '08/06', '11/06']; - const randomDate = dates[index % dates.length]; - const levels = [t('home.levels.mid'), t('home.levels.junior'), t('home.levels.senior'), t('home.levels.mid')]; - const level = levels[index % levels.length]; - const statusLabels = [t('workMode.remote'), t('workMode.hybrid'), t('workMode.onsite'), t('workMode.remote')]; - const statusLabel = statusLabels[index % statusLabels.length]; - return ( - - -
- {/* Header */} -
-
-
- -
- {job.company} -
- - {statusLabel} - -
- - {/* Content */} -
-

{job.title}

-
- Job Illustration -
-
-
- - {/* Footer Section with Separator */} -
-
-

- {level} - - {job.location} -

-
- -
- - - - -
-
-
-
- ) - })} -
-
-
- - {/* More Jobs Section */} -
-
-
-

{t('home.moreJobs.title')}

- - - -
- -
- {mockJobs.slice(0, 8) - .map((job, index) => { - const colors = [ - 'bg-cyan-500', 'bg-blue-500', 'bg-indigo-500', 'bg-gray-500', - 'bg-teal-500', 'bg-sky-500', 'bg-orange-500', 'bg-purple-500' - ]; - const bgColor = colors[index % colors.length]; - const icons = ['💻', '🎨', '📊', '🚀', '⚙️', '🔧', '📱', '🎯']; - const icon = icons[index % icons.length]; - - return ( - - - - {/* Cabeçalho com logo e seta */} -
-
-
- {icon} -
-
-

{job.title}

-

{job.company}

-
-
- -
- - {/* Rodapé com botões */} -
-
- - - - -
-
-
-
-
- ) - })} -
-
-
- - {/* CTA Section */} -
-
-
- {/* Image Layer: Single Image with Seamless Gradient Overlay */} -
- Woman with Notebook - {/* - Seamless Blend Gradient: - Starts solid gray-900 (matching, container) on left. - Fades gradually to transparent on right. - This "dyes" the dark background of the photo to match the container. - */} -
-
- -
- {/* Text Content */} -
-

- {t('home.cta.title')} -

-

- {t('home.cta.subtitle')} -

- -
-
-
-
-
-
- -
-
- ) -} - -function FilterIcon() { - return ( - - - - - - ); -} diff --git a/frontend/src/components/job-card.tsx b/frontend/src/components/job-card.tsx index 2e32843..e5b6de0 100644 --- a/frontend/src/components/job-card.tsx +++ b/frontend/src/components/job-card.tsx @@ -1,223 +1,226 @@ -"use client" +use client; + +import { motion } from framer-motion; +import { + Building2, + MapPin, + Clock, + Heart, +} from lucide-react; +import Link from next/link; +import { useState } from react; +import { + Card, + CardHeader, + CardContent, + CardFooter, +} from @/components/ui/card; +import { Button } from @/components/ui/button; +import { Badge } from @/components/ui/badge; +import { Avatar, AvatarImage, AvatarFallback } from @/components/ui/avatar; +import { formatTimeAgo } from @/lib/utils; +import { useTranslation } from @/lib/i18n; +import { useAuth } from @/contexts/AuthContext; +import { useNotification } from @/contexts/notification-context; + +interface JobCardProps { + job: { + id: string; + title: string; + company: string; + location: string; + type: string; + postedAt: string | Date; + description: string; + salary?: string; + requirements?: string[]; + }; + isApplied?: boolean; + applicationStatus?: string; +} + +export function JobCard({ job, isApplied, applicationStatus }: JobCardProps) { + const { t } = useTranslation(); + const { user } = useAuth(); + const notify = useNotification(); + const [isFavorited, setIsFavorited] = useState(false); + + const getTypeLabel = (type: string) => { + switch (type.toLowerCase()) { + case full-time: + return CLT; + case contract: + return PJ; + case freelance: + return Freelancer; + case remote: + return Remoto; + default: + return type; + } + }; + + const getTypeBadgeVariant = (type: string): default | secondary | outline | destructive | null => { + switch (type.toLowerCase()) { + case full-time: + return secondary; + case contract: + return outline; + case remote: + return default; + default: + return outline; + } + }; + + const getCompanyInitials = (company: string) => { + return company + .split( ) + .map((word) => word[0]) + .join(") + .toUpperCase() + .slice(0, 2); + }; + + const handleFavorite = () => { + setIsFavorited(!isFavorited); + if (!isFavorited) { + notify.info( + t('jobs.favorites.added.title'), + t('jobs.favorites.added.desc', { title: job.title }), + { + actionUrl: /dashboard/favorites, + actionLabel: t('jobs.favorites.action'), + } + ); + } + }; + + return ( + + + +
+
+ + + + {getCompanyInitials(job.company)} + + +
+

+ {job.title} +

+
+ + + {job.company} + +
+
+
+ + +
+
+ + + {/* Job Meta Information */} +
+
+ + {job.location} +
+ +
+
+ + {getTypeLabel(job.type)} + +
+ + {job.salary && ( + + {job.salary} + + )} +
+
+ + {/* Job Description Preview */} +
+

{job.description}

+
+ + {/* Skills/Requirements Preview */} + {job.requirements && job.requirements.length > 0 && ( +
+ {job.requirements.slice(0, 3).map((requirement, index) => ( + + {requirement} + + ))} + {job.requirements.length > 3 && ( + + {t('jobs.requirements.more', { count: job.requirements.length - 3 })} + + )} +
+ )} + + {/* Time Posted */} +
+ + {formatTimeAgo(job.postedAt)} +
+
+ + +
+ + + + {isApplied ? ( + + ) : ( + + + + )} +
+
+
+
+ ); +} -import type { Job } from "@/lib/types"; -import { - Card, - CardContent, - CardFooter, - CardHeader, -} from "@/components/ui/card"; -import { Button } from "@/components/ui/button"; -import { Badge } from "@/components/ui/badge"; -import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; -import { - MapPin, - Briefcase, - DollarSign, - Clock, - Building2, - Heart, -} from "lucide-react"; -import Link from "next/link"; -import { motion } from "framer-motion"; -import { useState } from "react"; -import { useNotify } from "@/contexts/notification-context"; -import { useTranslation } from "@/lib/i18n"; - -interface JobCardProps { - job: Job; - isApplied?: boolean; - applicationStatus?: string; -} - -export function JobCard({ job, isApplied, applicationStatus }: JobCardProps) { - const { t } = useTranslation(); - const [isFavorited, setIsFavorited] = useState(false); - const notify = useNotify(); - - const formatTimeAgo = (dateString: string) => { - const date = new Date(dateString); - const now = new Date(); - const diffInMs = now.getTime() - date.getTime(); - const diffInDays = Math.floor(diffInMs / (1000 * 60 * 60 * 24)); - - if (diffInDays === 0) return t('jobs.posted.today'); - if (diffInDays === 1) return t('jobs.posted.yesterday'); - if (diffInDays < 7) return t('jobs.posted.daysAgo', { count: diffInDays }); - if (diffInDays < 30) return t('jobs.posted.weeksAgo', { count: Math.floor(diffInDays / 7) }); - return t('jobs.posted.monthsAgo', { count: Math.floor(diffInDays / 30) }); - }; - - const getTypeLabel = (type: string) => { - return t(`jobs.types.${type}`) !== `jobs.types.${type}` ? t(`jobs.types.${type}`) : type; - }; - - const getTypeBadgeVariant = (type: string) => { - switch (type) { - case "full-time": - return "default"; - case "part-time": - return "secondary"; - case "contract": - return "outline"; - case "remote": - return "default"; - default: - return "outline"; - } - }; - - const getCompanyInitials = (company: string) => { - return company - .split(" ") - .map((word) => word[0]) - .join("") - .toUpperCase() - .slice(0, 2); - }; - - const handleFavorite = () => { - setIsFavorited(!isFavorited); - if (!isFavorited) { - notify.info( - t('jobs.favorites.added.title'), - t('jobs.favorites.added.desc', { title: job.title }), - { - actionUrl: "/dashboard/favorites", - actionLabel: t('jobs.favorites.action'), - } - ); - } - }; - - return ( - - - -
-
- - - - {getCompanyInitials(job.company)} - - -
-

- {job.title} -

-
- - - {job.company} - -
-
-
- - -
-
- - - {/* Job Meta Information */} - {/* Job Meta Information */} -
-
- - {job.location} -
- -
-
- - - {getTypeLabel(job.type)} - -
- - {job.salary && ( - - {job.salary} - - )} -
-
- - {/* Job Description Preview */} -
-

{job.description}

-
- - {/* Skills/Requirements Preview */} - {job.requirements && job.requirements.length > 0 && ( -
- {job.requirements.slice(0, 3).map((requirement, index) => ( - - {requirement} - - ))} - {job.requirements.length > 3 && ( - - {t('jobs.requirements.more', { count: job.requirements.length - 3 })} - - )} -
- )} - - {/* Time Posted */} -
- - {formatTimeAgo(job.postedAt)} -
-
- - -
- - - - {isApplied ? ( - - ) : ( - - - - )} -
-
-
-
- ); -}