From 0df21b5dae122b668329a17fc7d56af9cb548c7b Mon Sep 17 00:00:00 2001 From: GoHorse Deploy Date: Sun, 8 Feb 2026 03:21:15 +0000 Subject: [PATCH] feat(ui): implement jobs carousel and clean registration links --- frontend/src/app/page.tsx | 729 ++++++++++++++------------------------ 1 file changed, 258 insertions(+), 471 deletions(-) diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index d4dda8e..0606d76 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -1,471 +1,258 @@ -"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, 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 ( - - - - - - ); -} +'use client' + +import { useState, useCallback, useEffect } from 'react' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Search, MapPin, Briefcase, TrendingUp, ChevronLeft, ChevronRight, Star, Globe, Zap, Users, Heart } from 'lucide-react' +import Link from 'next/link' +import Image from 'next/image' +import { Footer } from '@/components/footer' +import { Navbar } from '@/components/navbar' +import useEmblaCarousel from 'embla-carousel-react' + +const mockJobs = [ + { + id: 1, + title: 'Senior Full Stack Developer', + company: 'TechCorp', + location: 'São Paulo, SP', + type: 'Tiempo completo', + salary: 'R$ 12,000 - R$ 18,000', + description: 'We are looking for an experienced full stack developer to lead...', + tags: ['React', 'Node.js', 'TypeScript'], + logo: 'https://api.dicebear.com/7.x/initials/svg?seed=TC', + posted: 'hace 3 meses' + }, + { + id: 2, + title: 'UX/UI Designer', + company: 'DesignHub', + location: 'São Paulo, SP', + type: 'Remoto', + salary: 'R$ 8,000 - R$ 12,000', + description: 'We are looking for a creative designer to craft incredible...', + tags: ['Figma', 'Adobe XD', 'Strong portfolio'], + logo: 'https://api.dicebear.com/7.x/initials/svg?seed=DH', + posted: 'hace 3 meses' + }, + { + id: 3, + title: 'Data Engineer', + company: 'DataFlow', + location: 'Rio de Janeiro, RJ', + type: 'Tiempo completo', + salary: 'R$ 15,000 - R$ 22,000', + description: 'Opportunity to work with big data and machine learning.', + tags: ['Python', 'SQL', 'Spark'], + logo: 'https://api.dicebear.com/7.x/initials/svg?seed=DF', + posted: 'hace 3 meses' + }, + { + id: 4, + title: 'Product Manager', + company: 'InnovateLab', + location: 'Belo Horizonte, MG', + type: 'Tiempo completo', + salary: 'R$ 10,000 - R$ 16,000', + description: 'Lead the development of innovative digital products.', + tags: ['Product management', 'Agile', 'Data analysis'], + logo: 'https://api.dicebear.com/7.x/initials/svg?seed=IL', + posted: 'hace 3 meses' + }, + { + id: 5, + title: 'Backend Developer', + company: 'CloudScale', + location: 'Florianópolis, SC', + type: 'Remoto', + salary: 'R$ 11,000 - R$ 17,000', + description: 'Scale our cloud infrastructure to millions of users.', + tags: ['Go', 'Kubernetes', 'AWS'], + logo: 'https://api.dicebear.com/7.x/initials/svg?seed=CS', + posted: 'hace 2 meses' + } +] + +export default function Home() { + const [emblaRef, emblaApi] = useEmblaCarousel({ + align: 'start', + loop: false, + skipSnaps: false, + dragFree: true + }) + + const [prevBtnDisabled, setPrevBtnDisabled] = useState(true) + const [nextBtnDisabled, setNextBtnDisabled] = useState(true) + + const scrollPrev = useCallback(() => { + if (emblaApi) emblaApi.scrollPrev() + }, [emblaApi]) + + const scrollNext = useCallback(() => { + if (emblaApi) emblaApi.scrollNext() + }, [emblaApi]) + + const onSelect = useCallback((emblaApi: any) => { + setPrevBtnDisabled(!emblaApi.canScrollPrev()) + setNextBtnDisabled(!emblaApi.canScrollNext()) + }, []) + + useEffect(() => { + if (!emblaApi) return + + onSelect(emblaApi) + emblaApi.on('reInit', onSelect) + emblaApi.on('select', onSelect) + }, [emblaApi, onSelect]) + + return ( +
+ +
+ {/* Hero Section */} +
+
+
+ Hero Background +
+ +
+
+

+ Encontre seu próximo desafio na tecnologia +

+

+ Conectamos os melhores talentos às empresas mais inovadoras do Brasil e do exterior. +

+ +
+ + + +
+
+ {[1, 2, 3, 4].map((i) => ( +
+ Avatar +
+ ))} +
+ + +500 talentos já contratados + +
+
+
+
+
+ + {/* Latest Jobs Section */} +
+
+
+
+

Últimas Vagas Cadastradas

+
+
+
+
+ + +
+ + Ver todas + +
+
+ +
+
+ {mockJobs.map((job, index) => ( +
+
+
+
+ {job.company} +
+ +
+ +
+

+ {job.title} +

+
+ + {job.company} +
+ +
+
+ + {job.location} +
+
+
+ {job.type} +
+ {job.salary} +
+
+ +

+ {job.description} +

+ +
+ {job.tags.map((tag) => ( + + {tag} + + ))} +
+
+ +
+
+ + {job.posted} +
+ + + +
+
+
+ ))} +
+
+
+
+
+ +
+ ) +}