gohorsejobs/frontend/src/app/vagas/[id]/page.tsx

528 lines
22 KiB
TypeScript

"use client";
import { use } from "react";
import { useRouter } from "next/navigation";
import { Navbar } from "@/components/navbar";
import { Footer } from "@/components/footer";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Separator } from "@/components/ui/separator";
import { mockJobs } from "@/lib/mock-data";
import {
MapPin,
Briefcase,
DollarSign,
Calendar,
ArrowLeft,
CheckCircle2,
Building2,
Users,
Clock,
Heart,
Share2,
Bookmark,
Star,
Globe,
} from "lucide-react";
import Link from "next/link";
import { useState } from "react";
import { motion } from "framer-motion";
export const runtime = 'edge';
export default function JobDetailPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = use(params);
const router = useRouter();
const [isFavorited, setIsFavorited] = useState(false);
const [isBookmarked, setIsBookmarked] = useState(false);
const job = mockJobs.find((j) => j.id === id);
if (!job) {
return (
<div className="min-h-screen flex flex-col">
<Navbar />
<main className="flex-1 flex items-center justify-center">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="text-center max-w-md mx-auto p-6"
>
<div className="w-16 h-16 bg-muted rounded-full flex items-center justify-center mx-auto mb-4">
<Briefcase className="w-8 h-8 text-muted-foreground" />
</div>
<h1 className="text-2xl font-bold mb-2">Vaga não encontrada</h1>
<p className="text-muted-foreground mb-6">
A vaga que você está procurando não existe ou foi removida.
</p>
<Link href="/vagas">
<Button>Ver todas as vagas</Button>
</Link>
</motion.div>
</main>
<Footer />
</div>
);
}
const getCompanyInitials = (company: string) => {
return company
.split(" ")
.map((word) => word[0])
.join("")
.toUpperCase()
.slice(0, 2);
};
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 "Hoje";
if (diffInDays === 1) return "Ontem";
if (diffInDays < 7) return `${diffInDays} dias atrás`;
if (diffInDays < 30) return `${Math.floor(diffInDays / 7)} semanas atrás`;
return `${Math.floor(diffInDays / 30)} meses atrás`;
};
const getTypeLabel = (type: string) => {
const typeLabels: { [key: string]: string } = {
"full-time": "Tempo integral",
"part-time": "Meio período",
contract: "Contrato",
Remoto: "Remoto",
};
return typeLabels[type] || type;
};
const mockCompanyInfo = {
size: "100-500 funcionários",
industry: "Tecnologia",
founded: "2015",
website: "www.empresa.com",
rating: 4.5,
};
return (
<div className="min-h-screen flex flex-col">
<Navbar />
<main className="flex-1 py-8">
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
<div className="max-w-5xl mx-auto">
{/* Breadcrumb */}
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
className="mb-6"
>
<Link href="/vagas">
<Button variant="ghost" className="gap-2 hover:bg-muted">
<ArrowLeft className="h-4 w-4" />
Voltar para vagas
</Button>
</Link>
</motion.div>
<div className="grid lg:grid-cols-3 gap-8">
{/* Main Content */}
<div className="lg:col-span-2 space-y-6">
{/* Job Header */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
>
<Card>
<CardHeader>
<div className="flex flex-col sm:flex-row items-start gap-4">
<Avatar className="h-16 w-16 shrink-0">
<AvatarImage
src={`https://avatar.vercel.sh/${job.company}`}
alt={job.company}
/>
<AvatarFallback className="bg-primary/10 text-primary font-bold text-lg">
{getCompanyInitials(job.company)}
</AvatarFallback>
</Avatar>
<div className="flex-1 w-full min-w-0">
<div className="flex flex-col gap-3">
<div className="flex items-start justify-between gap-2">
<div className="flex-1 min-w-0">
<CardTitle className="text-xl sm:text-2xl md:text-3xl mb-2 leading-tight">
{job.title}
</CardTitle>
<div className="flex flex-wrap items-center gap-2 mb-3">
<Building2 className="h-4 w-4 sm:h-5 sm:w-5 text-muted-foreground shrink-0" />
<CardDescription className="text-base sm:text-lg font-medium">
{job.company}
</CardDescription>
<div className="flex items-center gap-1">
<Star className="h-4 w-4 fill-yellow-400 text-yellow-400" />
<span className="text-sm text-muted-foreground">
{mockCompanyInfo.rating}
</span>
</div>
</div>
</div>
<div className="hidden sm:flex gap-2 shrink-0">
<Button
variant="outline"
size="icon"
onClick={() => setIsFavorited(!isFavorited)}
>
<Heart
className={`h-4 w-4 ${isFavorited
? "fill-red-500 text-red-500"
: ""
}`}
/>
</Button>
<Button
variant="outline"
size="icon"
onClick={() => setIsBookmarked(!isBookmarked)}
>
<Bookmark
className={`h-4 w-4 ${isBookmarked ? "fill-current" : ""
}`}
/>
</Button>
<Button variant="outline" size="icon">
<Share2 className="h-4 w-4" />
</Button>
</div>
</div>
{/* Action buttons mobile */}
<div className="flex sm:hidden gap-2">
<Button
variant="outline"
size="sm"
onClick={() => setIsFavorited(!isFavorited)}
className="flex-1"
>
<Heart
className={`h-4 w-4 mr-1 ${isFavorited
? "fill-red-500 text-red-500"
: ""
}`}
/>
{isFavorited ? "Favoritado" : "Favoritar"}
</Button>
<Button
variant="outline"
size="sm"
onClick={() => setIsBookmarked(!isBookmarked)}
className="flex-1"
>
<Bookmark
className={`h-4 w-4 mr-1 ${isBookmarked ? "fill-current" : ""
}`}
/>
{isBookmarked ? "Salvo" : "Salvar"}
</Button>
<Button
variant="outline"
size="icon"
className="shrink-0"
>
<Share2 className="h-4 w-4" />
</Button>
</div>
{/* Job Meta */}
<div className="flex flex-wrap gap-3 sm:gap-4 text-sm text-muted-foreground">
<div className="flex items-center gap-2">
<MapPin className="h-4 w-4 shrink-0" />
<span className="truncate">{job.location}</span>
</div>
<div className="flex items-center gap-2">
<Briefcase className="h-4 w-4 shrink-0" />
<Badge
variant="secondary"
className="whitespace-nowrap"
>
{getTypeLabel(job.type)}
</Badge>
</div>
{job.salary && (
<div className="flex items-center gap-2">
<DollarSign className="h-4 w-4 shrink-0" />
<span className="font-medium text-foreground whitespace-nowrap">
{job.salary}
</span>
</div>
)}
<div className="flex items-center gap-2">
<Clock className="h-4 w-4 shrink-0" />
<span className="whitespace-nowrap">
{formatTimeAgo(job.postedAt)}
</span>
</div>
</div>
</div>
</div>
</div>
{/* Apply Button - Mobile */}
<div className="lg:hidden pt-4">
<Link
href={`/vagas/${job.id}/candidatura`}
className="w-full"
>
<Button size="lg" className="w-full cursor-pointer">
Candidatar-se
</Button>
</Link>
</div>
</CardHeader>
</Card>
</motion.div>
{/* Job Description */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.1 }}
>
<Card>
<CardHeader>
<CardTitle className="text-xl">Sobre a vaga</CardTitle>
</CardHeader>
<CardContent className="prose prose-sm max-w-none">
<p className="text-muted-foreground leading-relaxed whitespace-pre-line">
{job.description}
</p>
</CardContent>
</Card>
</motion.div>
{/* Requirements */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2 }}
>
<Card>
<CardHeader>
<CardTitle className="text-xl">Requisitos</CardTitle>
</CardHeader>
<CardContent>
<div className="grid gap-3">
{job.requirements.map((req, index) => (
<div key={index} className="flex items-start gap-3">
<CheckCircle2 className="h-5 w-5 text-primary mt-0.5 shrink-0" />
<span className="text-muted-foreground">{req}</span>
</div>
))}
</div>
</CardContent>
</Card>
</motion.div>
{/* Company Info */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3 }}
>
<Card>
<CardHeader>
<CardTitle className="text-xl">Sobre a empresa</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<p className="text-muted-foreground leading-relaxed">
{job.company} é uma empresa líder no mercado,
comprometida em criar um ambiente de trabalho inclusivo
e inovador. Oferecemos benefícios competitivos e
oportunidades de crescimento profissional.
</p>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 pt-4 border-t">
<div>
<div className="flex items-center gap-2 text-sm text-muted-foreground mb-1">
<Users className="h-4 w-4 shrink-0" />
<span>Tamanho</span>
</div>
<p className="font-medium text-sm">
{mockCompanyInfo.size}
</p>
</div>
<div>
<div className="flex items-center gap-2 text-sm text-muted-foreground mb-1">
<Building2 className="h-4 w-4 shrink-0" />
<span>Setor</span>
</div>
<p className="font-medium text-sm">
{mockCompanyInfo.industry}
</p>
</div>
<div>
<div className="flex items-center gap-2 text-sm text-muted-foreground mb-1">
<Calendar className="h-4 w-4 shrink-0" />
<span>Fundada</span>
</div>
<p className="font-medium text-sm">
{mockCompanyInfo.founded}
</p>
</div>
<div>
<div className="flex items-center gap-2 text-sm text-muted-foreground mb-1">
<Globe className="h-4 w-4 shrink-0" />
<span>Website</span>
</div>
<a
href={`https://${mockCompanyInfo.website}`}
target="_blank"
rel="noopener noreferrer"
className="font-medium text-sm text-primary hover:underline break-all"
>
{mockCompanyInfo.website}
</a>
</div>
</div>
</CardContent>
</Card>
</motion.div>
</div>
{/* Sidebar */}
<div className="space-y-6 lg:sticky lg:top-20 lg:self-start">
{/* Apply Card - Desktop */}
<motion.div
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
className="hidden lg:block"
>
<Card>
<CardHeader>
<CardTitle className="text-lg">
Interessado na vaga?
</CardTitle>
<CardDescription>
Candidate-se agora e faça parte da nossa equipe!
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<Link
href={`/vagas/${job.id}/candidatura`}
className="w-full"
>
<Button size="lg" className="w-full cursor-pointer">
Candidatar-se
</Button>
</Link>
<Separator />
<div className="space-y-3 text-sm">
<div className="flex items-start justify-between gap-2">
<span className="text-muted-foreground">
Tipo de vaga:
</span>
<Badge
variant="outline"
className="whitespace-nowrap"
>
{getTypeLabel(job.type)}
</Badge>
</div>
<div className="flex items-start justify-between gap-2">
<span className="text-muted-foreground shrink-0">
Localização:
</span>
<span className="font-medium text-right">
{job.location}
</span>
</div>
{job.salary && (
<div className="flex items-start justify-between gap-2">
<span className="text-muted-foreground">
Salário:
</span>
<span className="font-medium text-right whitespace-nowrap">
{job.salary}
</span>
</div>
)}
<div className="flex items-start justify-between gap-2">
<span className="text-muted-foreground">
Publicado:
</span>
<span className="font-medium text-right whitespace-nowrap">
{formatTimeAgo(job.postedAt)}
</span>
</div>
</div>
</CardContent>
</Card>
</motion.div>
{/* Similar Jobs */}
<motion.div
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.2 }}
>
<Card>
<CardHeader>
<CardTitle className="text-lg">Vagas similares</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
{mockJobs
.filter((j) => j.id !== job.id)
.slice(0, 3)
.map((similarJob) => (
<Link
key={similarJob.id}
href={`/vagas/${similarJob.id}`}
>
<div className="p-3 rounded-lg border hover:bg-muted/50 transition-colors cursor-pointer">
<h4 className="font-medium text-sm mb-1 line-clamp-1">
{similarJob.title}
</h4>
<p className="text-xs text-muted-foreground mb-2">
{similarJob.company}
</p>
<div className="flex items-center gap-2 text-xs text-muted-foreground">
<MapPin className="h-3 w-3" />
<span>{similarJob.location}</span>
</div>
</div>
</Link>
))}
<Link href="/vagas">
<Button variant="outline" size="sm" className="w-full">
Ver todas as vagas
</Button>
</Link>
</CardContent>
</Card>
</motion.div>
</div>
</div>
</div>
</div>
</main>
<Footer />
</div>
);
}