import { useState, useEffect } from "react"; import { Link } from "react-router-dom"; import Layout from "@/components/Layout"; import SEO from "@/components/SEO"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Textarea } from "@/components/ui/textarea"; import { Gift, ArrowLeft, DollarSign, Calendar, Building, Loader2, CheckCircle2, XCircle, Clock, AlertTriangle, ExternalLink, } from "lucide-react"; import { useAuth } from "@/contexts/AuthContext"; import { aethexToast } from "@/lib/aethex-toast"; interface Offer { id: string; position_title: string; company_name: string; salary_amount: number | null; salary_type: string | null; start_date: string | null; offer_expiry: string | null; benefits: any[]; offer_letter_url: string | null; status: string; notes: string | null; created_at: string; employer: { full_name: string; avatar_url: string | null; email: string; } | null; } interface GroupedOffers { pending: Offer[]; accepted: Offer[]; declined: Offer[]; expired: Offer[]; withdrawn: Offer[]; } export default function CandidateOffers() { const { session } = useAuth(); const [loading, setLoading] = useState(true); const [offers, setOffers] = useState([]); const [grouped, setGrouped] = useState({ pending: [], accepted: [], declined: [], expired: [], withdrawn: [], }); const [filter, setFilter] = useState("all"); const [respondingTo, setRespondingTo] = useState(null); const [responseAction, setResponseAction] = useState<"accept" | "decline" | null>(null); const [responseNotes, setResponseNotes] = useState(""); const [submitting, setSubmitting] = useState(false); useEffect(() => { if (session?.access_token) { fetchOffers(); } }, [session?.access_token]); const fetchOffers = async () => { try { const response = await fetch("/api/candidate/offers", { headers: { Authorization: `Bearer ${session?.access_token}` }, }); if (response.ok) { const data = await response.json(); setOffers(data.offers || []); setGrouped( data.grouped || { pending: [], accepted: [], declined: [], expired: [], withdrawn: [], }, ); } } catch (error) { console.error("Error fetching offers:", error); aethexToast.error("Failed to load offers"); } finally { setLoading(false); } }; const respondToOffer = async () => { if (!respondingTo || !responseAction || !session?.access_token) return; setSubmitting(true); try { const response = await fetch("/api/candidate/offers", { method: "PATCH", headers: { Authorization: `Bearer ${session.access_token}`, "Content-Type": "application/json", }, body: JSON.stringify({ id: respondingTo.id, status: responseAction === "accept" ? "accepted" : "declined", notes: responseNotes, }), }); if (!response.ok) throw new Error("Failed to respond to offer"); aethexToast.success( responseAction === "accept" ? "Congratulations! You've accepted the offer." : "Offer declined", ); // Refresh offers await fetchOffers(); // Close dialog setRespondingTo(null); setResponseAction(null); setResponseNotes(""); } catch (error) { console.error("Error responding to offer:", error); aethexToast.error("Failed to respond to offer"); } finally { setSubmitting(false); } }; const formatDate = (date: string) => { return new Date(date).toLocaleDateString("en-US", { month: "long", day: "numeric", year: "numeric", }); }; const formatSalary = (amount: number | null, type: string | null) => { if (!amount) return "Not specified"; const formatted = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", maximumFractionDigits: 0, }).format(amount); const suffix = type === "hourly" ? "/hr" : type === "monthly" ? "/mo" : "/yr"; return formatted + suffix; }; const getStatusBadge = (status: string) => { switch (status) { case "pending": return ( Pending ); case "accepted": return ( Accepted ); case "declined": return ( Declined ); case "expired": return ( Expired ); case "withdrawn": return ( Withdrawn ); default: return {status}; } }; const getDaysUntilExpiry = (expiry: string | null) => { if (!expiry) return null; const diff = new Date(expiry).getTime() - new Date().getTime(); const days = Math.ceil(diff / (1000 * 60 * 60 * 24)); return days; }; const getFilteredOffers = () => { switch (filter) { case "pending": return grouped.pending; case "accepted": return grouped.accepted; case "declined": return grouped.declined; case "expired": return grouped.expired; default: return offers; } }; if (loading) { return (
); } const filteredOffers = getFilteredOffers(); return (
{/* Background effects */}
{/* Header */}

Job Offers

Review and respond to offers

{/* Stats */}

{grouped.pending.length}

Pending

{grouped.accepted.length}

Accepted

{grouped.declined.length}

Declined

{offers.length}

Total

{/* Filter */}
{/* Offers List */}
{filteredOffers.length === 0 ? (

No offers found

{filter === "all" ? "You don't have any job offers yet" : `No ${filter} offers`}

) : ( filteredOffers.map((offer) => { const daysUntilExpiry = getDaysUntilExpiry(offer.offer_expiry); const isExpiringSoon = daysUntilExpiry !== null && daysUntilExpiry > 0 && daysUntilExpiry <= 3; return (

{offer.position_title}

{offer.company_name}
{getStatusBadge(offer.status)}
{/* Expiry Warning */} {offer.status === "pending" && isExpiringSoon && (
Expires in {daysUntilExpiry} day {daysUntilExpiry !== 1 ? "s" : ""}
)} {/* Details */}
{formatSalary( offer.salary_amount, offer.salary_type, )}
{offer.start_date && (
Start: {formatDate(offer.start_date)}
)} {offer.offer_expiry && (
Expires: {formatDate(offer.offer_expiry)}
)}
{/* Benefits */} {offer.benefits && offer.benefits.length > 0 && (
{offer.benefits.map((benefit: string, i: number) => ( {benefit} ))}
)} {/* Actions */}
{offer.status === "pending" && ( <> )} {offer.offer_letter_url && ( )}
); }) )}
{/* Response Dialog */} { setRespondingTo(null); setResponseAction(null); setResponseNotes(""); }} > {responseAction === "accept" ? "Accept Offer" : "Decline Offer"} {responseAction === "accept" ? "Congratulations! Please confirm you want to accept this offer." : "Are you sure you want to decline this offer? This action cannot be undone."} {respondingTo && (

{respondingTo.position_title}

{respondingTo.company_name}

{formatSalary( respondingTo.salary_amount, respondingTo.salary_type, )}