diff --git a/client/src/module/student/applications/MyApplicationsPage.tsx b/client/src/module/student/applications/MyApplicationsPage.tsx index 1ae4a49e1..2ba1fab28 100644 --- a/client/src/module/student/applications/MyApplicationsPage.tsx +++ b/client/src/module/student/applications/MyApplicationsPage.tsx @@ -234,6 +234,13 @@ const STATUS_ORDER: Record = { REJECTED: 4, WITHDRAWN: 5, }; +const EXTERNAL_STATUS = "APPLIED"; +const EXTERNAL_VISIBLE_FILTERS = ["ALL", "APPLIED", "IN_PROGRESS"] as const; +type ExternalVisibleFilter = (typeof EXTERNAL_VISIBLE_FILTERS)[number]; + +function isExternalVisibleFilter(filter: string): filter is ExternalVisibleFilter { + return (EXTERNAL_VISIBLE_FILTERS as readonly string[]).includes(filter); +} function sortApplications( apps: Application[], @@ -259,6 +266,7 @@ export default function MyApplicationsPage() { const [page, setPage] = useState(1); const [pendingDelete, setPendingDelete] = useState(null); const [sortOption, setSortOption] = useState<"newest" | "oldest" | "company" | "status">("newest"); + const [activeFilter, setActiveFilter] = useState("ALL"); useEffect(() => { const t = setTimeout(() => setDebouncedSearch(search), 200); @@ -268,7 +276,7 @@ export default function MyApplicationsPage() { useEffect(() => { // eslint-disable-next-line react-hooks/set-state-in-effect setPage(1); - }, [debouncedSearch]); + }, [debouncedSearch, activeFilter]); const { data, isLoading } = useQuery({ queryKey: queryKeys.applications.mine(), @@ -288,9 +296,10 @@ export default function MyApplicationsPage() { (a) => a.job?.title?.toLowerCase().includes(debouncedSearch.toLowerCase()) || a.job?.company?.toLowerCase().includes(debouncedSearch.toLowerCase()) ); return sortApplications(base, sortOption); - }, [applications, debouncedSearch, sortOption]); + }, [applications, debouncedSearch, sortOption, activeFilter]); const filteredExternal = useMemo(() => { + if (!isExternalVisibleFilter(activeFilter)) return []; const base = !debouncedSearch.trim() ? externalApplications : externalApplications.filter( @@ -304,7 +313,7 @@ export default function MyApplicationsPage() { if (sortOption === "company") return (a.adminJob.company ?? "").localeCompare(b.adminJob.company ?? ""); return 0; }); -}, [externalApplications, debouncedSearch, sortOption]); +}, [externalApplications, debouncedSearch, sortOption, activeFilter]); const totalAll = applications.length + externalApplications.length; const totalFiltered = filtered.length + filteredExternal.length; diff --git a/client/src/module/student/opensource/FirstPRRoadmapPage.tsx b/client/src/module/student/opensource/FirstPRRoadmapPage.tsx index 08dab5dbc..6f74cbf60 100644 --- a/client/src/module/student/opensource/FirstPRRoadmapPage.tsx +++ b/client/src/module/student/opensource/FirstPRRoadmapPage.tsx @@ -123,8 +123,27 @@ export default function FirstPRRoadmapPage() { >
-

You're an open source contributor!

-

You've completed all {totalSteps} steps. Time to find your next issue!

+

You shipped your first PR!

+

10 / 10 steps complete. Your open source journey has begun.

+
+ + Discover repos to contribute to + + +
)} diff --git a/client/src/module/student/opensource/OpenSourceAnalyticsPage.tsx b/client/src/module/student/opensource/OpenSourceAnalyticsPage.tsx index 1cd350402..a50dfb309 100644 --- a/client/src/module/student/opensource/OpenSourceAnalyticsPage.tsx +++ b/client/src/module/student/opensource/OpenSourceAnalyticsPage.tsx @@ -387,6 +387,29 @@ export default function OpenSourceAnalyticsPage() { ); } + // Empty state: student has zero contributions + if (!trendIsLoading && !trendIsError && contributionTotal === 0) { + return ( +
+
+ +
+

+ No contributions tracked yet +

+

+ Submit a repo suggestion and get it approved to start tracking + your open source journey. +

+ + Discover Repositories → + +
+ ); + } return (
{/* Header */}