From 1a2a9af33556c8e005ba13df74f8ae401acf748f Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 26 Jan 2026 22:01:13 +0000 Subject: [PATCH] Complete Candidate Portal with all pages and routes - Add CandidateInterviews.tsx with interview list, filtering by status, meeting type badges, and join meeting buttons - Add CandidateOffers.tsx with offer management, accept/decline dialogs, expiry warnings, and salary formatting - Add routes to App.tsx for /candidate, /candidate/profile, /candidate/interviews, /candidate/offers --- client/App.tsx | 38 ++ .../pages/candidate/CandidateInterviews.tsx | 402 ++++++++++++ client/pages/candidate/CandidateOffers.tsx | 591 ++++++++++++++++++ 3 files changed, 1031 insertions(+) create mode 100644 client/pages/candidate/CandidateInterviews.tsx create mode 100644 client/pages/candidate/CandidateOffers.tsx diff --git a/client/App.tsx b/client/App.tsx index 4ec9b1f9..7f84f2f2 100644 --- a/client/App.tsx +++ b/client/App.tsx @@ -161,6 +161,10 @@ import StaffProjectTracking from "./pages/staff/StaffProjectTracking"; import StaffTeamHandbook from "./pages/staff/StaffTeamHandbook"; import StaffOnboarding from "./pages/staff/StaffOnboarding"; import StaffOnboardingChecklist from "./pages/staff/StaffOnboardingChecklist"; +import CandidatePortal from "./pages/candidate/CandidatePortal"; +import CandidateProfile from "./pages/candidate/CandidateProfile"; +import CandidateInterviews from "./pages/candidate/CandidateInterviews"; +import CandidateOffers from "./pages/candidate/CandidateOffers"; const queryClient = new QueryClient(); @@ -542,6 +546,40 @@ const App = () => ( } /> + {/* Candidate Portal Routes */} + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + {/* Dev-Link routes - now redirect to Nexus Opportunities with ecosystem filter */} } /> ([]); + const [grouped, setGrouped] = useState({ + upcoming: [], + past: [], + cancelled: [], + }); + const [filter, setFilter] = useState("all"); + + useEffect(() => { + if (session?.access_token) { + fetchInterviews(); + } + }, [session?.access_token]); + + const fetchInterviews = async () => { + try { + const response = await fetch("/api/candidate/interviews", { + headers: { Authorization: `Bearer ${session?.access_token}` }, + }); + if (response.ok) { + const data = await response.json(); + setInterviews(data.interviews || []); + setGrouped(data.grouped || { upcoming: [], past: [], cancelled: [] }); + } + } catch (error) { + console.error("Error fetching interviews:", error); + aethexToast.error("Failed to load interviews"); + } finally { + setLoading(false); + } + }; + + const formatDate = (date: string) => { + return new Date(date).toLocaleDateString("en-US", { + weekday: "long", + month: "long", + day: "numeric", + year: "numeric", + }); + }; + + const formatTime = (date: string) => { + return new Date(date).toLocaleTimeString("en-US", { + hour: "2-digit", + minute: "2-digit", + }); + }; + + const getInitials = (name: string) => { + return name + .split(" ") + .map((n) => n[0]) + .join("") + .toUpperCase(); + }; + + const getMeetingIcon = (type: string) => { + switch (type) { + case "video": + return