From 6cf3ab3a9c6a0bfb66e78cabf221bb3776b330d6 Mon Sep 17 00:00:00 2001 From: "Builder.io" Date: Sat, 8 Nov 2025 01:35:29 +0000 Subject: [PATCH] MyApplications page - track submitted job applications cgen-2bb6148abd844992a5bbd7235c1fa9a2 --- client/pages/profile/MyApplications.tsx | 319 ++++++++++++++++++++++++ 1 file changed, 319 insertions(+) create mode 100644 client/pages/profile/MyApplications.tsx diff --git a/client/pages/profile/MyApplications.tsx b/client/pages/profile/MyApplications.tsx new file mode 100644 index 00000000..01358030 --- /dev/null +++ b/client/pages/profile/MyApplications.tsx @@ -0,0 +1,319 @@ +import { useState, useEffect } from "react"; +import Layout from "@/components/Layout"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar"; +import { + Loader2, + FileText, + CheckCircle, + Clock, + XCircle, + Eye, + Trash2, +} from "lucide-react"; +import { getMyApplications, withdrawApplication } from "@/api/applications"; +import { useNavigate } from "react-router-dom"; +import { useAethexToast } from "@/hooks/use-aethex-toast"; +import type { Application } from "@/api/applications"; + +export default function MyApplications() { + const navigate = useNavigate(); + const { toast } = useAethexToast(); + const [applications, setApplications] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [selectedStatus, setSelectedStatus] = useState(); + + useEffect(() => { + const fetchApplications = async () => { + setIsLoading(true); + try { + const result = await getMyApplications({ + status: selectedStatus, + page: 1, + limit: 50, + }); + setApplications(result.data); + } catch (error) { + console.error("Failed to fetch applications:", error); + setApplications([]); + } finally { + setIsLoading(false); + } + }; + + fetchApplications(); + }, [selectedStatus]); + + const handleWithdraw = async (applicationId: string) => { + try { + await withdrawApplication(applicationId); + toast("Application withdrawn", "success"); + setApplications( + applications.filter((app) => app.id !== applicationId) + ); + } catch (error) { + toast( + error instanceof Error + ? error.message + : "Failed to withdraw application", + "error" + ); + } + }; + + const getStatusColor = (status: string) => { + switch (status) { + case "submitted": + return "bg-blue-500/10 text-blue-300"; + case "reviewing": + return "bg-yellow-500/10 text-yellow-300"; + case "accepted": + return "bg-green-500/10 text-green-300"; + case "rejected": + return "bg-red-500/10 text-red-300"; + default: + return "bg-gray-500/10 text-gray-300"; + } + }; + + const getStatusIcon = (status: string) => { + switch (status) { + case "submitted": + return ; + case "reviewing": + return ; + case "accepted": + return ; + case "rejected": + return ; + default: + return null; + } + }; + + return ( + +
+ {/* Background */} +
+
+ +
+
+
+

+ My Applications +

+

+ Track the status of all your job applications +

+
+ + {isLoading ? ( +
+ +
+ ) : applications.length === 0 ? ( + + + +

+ No applications yet +

+

+ Start applying to opportunities to see them here +

+ +
+
+ ) : ( + <> + + setSelectedStatus(value === "all" ? undefined : value) + } + className="mb-8" + > + + + All ({applications.length}) + + + Submitted ( + { + applications.filter((app) => app.status === "submitted") + .length + } + ) + + + Reviewing ( + { + applications.filter((app) => app.status === "reviewing") + .length + } + ) + + + Accepted ( + { + applications.filter((app) => app.status === "accepted") + .length + } + ) + + + Rejected ( + { + applications.filter((app) => app.status === "rejected") + .length + } + ) + + + + + {applications.map((app) => ( + + navigate(`/opportunities/${app.opportunity_id}`) + } + /> + ))} + + + {["submitted", "reviewing", "accepted", "rejected"].map( + (status) => ( + + {applications + .filter((app) => app.status === status) + .map((app) => ( + + navigate(`/opportunities/${app.opportunity_id}`) + } + /> + ))} + + ) + )} + + + )} +
+
+
+ + ); +} + +interface ApplicationCardProps { + application: Application; + statusColor: string; + statusIcon: React.ReactNode; + onWithdraw: (id: string) => void; + onViewOpportunity: () => void; +} + +function ApplicationCard({ + application, + statusColor, + statusIcon, + onWithdraw, + onViewOpportunity, +}: ApplicationCardProps) { + const opportunity = application.aethex_opportunities; + if (!opportunity) return null; + + const poster = opportunity.aethex_creators; + + return ( + + +
+
+
+

+ {opportunity.title} +

+ + {statusIcon} + {application.status.charAt(0).toUpperCase() + + application.status.slice(1)} + +
+ +
+ + + + {poster.username.charAt(0).toUpperCase()} + + +
+

Posted by

+

@{poster.username}

+
+
+ +

+ Applied on {new Date(application.applied_at).toLocaleDateString()} +

+ + {application.response_message && ( +

+ {application.response_message} +

+ )} +
+ +
+ + {application.status === "submitted" && ( + + )} +
+
+
+
+ ); +}