528 lines
22 KiB
TypeScript
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>
|
|
);
|
|
}
|