diff --git a/app/(landing)/hackathons/[slug]/HackathonPageClient.tsx b/app/(landing)/hackathons/[slug]/HackathonPageClient.tsx index 1519f5373..26864b924 100644 --- a/app/(landing)/hackathons/[slug]/HackathonPageClient.tsx +++ b/app/(landing)/hackathons/[slug]/HackathonPageClient.tsx @@ -167,6 +167,33 @@ export default function HackathonPageClient() { }); } + // Filter tabs against enabledTabs so only explicitly enabled tabs are shown. + // 'overview' is always kept as it is the default fallback tab. + // If enabledTabs is undefined/null (not configured), all tabs are shown as before. + // Map UI tab ids to backend enabledTabs keys where they differ. + const tabIdToEnabledKey: Record = { + 'team-formation': 'joinATeamTab', + winners: 'winnersTab', + resources: 'resourcesTab', + participants: 'participantsTab', + announcements: 'announcementsTab', + submission: 'submissionTab', + discussions: 'discussionTab', + }; + + type EnabledTab = NonNullable< + typeof currentHackathon + >['enabledTabs'][number]; + const enabledTabs = currentHackathon?.enabledTabs; + + if (Array.isArray(enabledTabs)) { + const enabledSet = new Set(enabledTabs); + return tabs.filter(tab => { + if (tab.id === 'overview') return true; + const key = (tabIdToEnabledKey[tab.id] ?? tab.id) as EnabledTab; + return enabledSet.has(key); + }); + } return tabs; }, [ currentHackathon?.participants, @@ -273,12 +300,29 @@ export default function HackathonPageClient() { }, [hackathonId, setCurrentHackathon]); // Handle tab changes from URL + // Now also defaults to 'overview' if the URL tab is not in the filtered hackathonTabs list. + // This handles direct URL access to a disabled tab — user is silently redirected to overview. useEffect(() => { const tabFromUrl = searchParams.get('tab'); - if (tabFromUrl && hackathonTabs.some(tab => tab.id === tabFromUrl)) { + + // No tab in URL — default to overview + if (!tabFromUrl) { + setActiveTab('overview'); + return; + } + + if (hackathonTabs.some(tab => tab.id === tabFromUrl)) { + // Tab exists in filtered list — activate it normally setActiveTab(tabFromUrl); + return; } - }, [searchParams, hackathonTabs]); + + // Tab is disabled or unrecognised — fall back to overview + setActiveTab('overview'); + const queryParams = new URLSearchParams(searchParams.toString()); + queryParams.set('tab', 'overview'); + router.replace(`?${queryParams.toString()}`, { scroll: false }); + }, [searchParams, hackathonTabs, router]); const handleTabChange = (tabId: string) => { setActiveTab(tabId); @@ -308,6 +352,11 @@ export default function HackathonPageClient() { ); } + // Helper: checks if a tab id is present in the filtered hackathonTabs array. + // Used below to guard each tab's content from rendering if the tab is disabled. + const isTabVisible = (tabId: string) => + hackathonTabs.some(tab => tab.id === tabId); + // Shared props for banner and sticky card const sharedActionProps = { deadline: currentHackathon.submissionDeadline, @@ -378,48 +427,60 @@ export default function HackathonPageClient() { /> )} + {/* isTabVisible('resources') guard — HackathonResources will not + render at all if 'resources' is not in enabledTabs, even via direct URL */} {activeTab === 'resources' && + isTabVisible('resources') && currentHackathon.resources?.length > 0 && } + + {/* isTabVisible('participants') guard */} {activeTab === 'participants' && + isTabVisible('participants') && currentHackathon.participants?.length > 0 && ( )} - {activeTab === 'announcements' && announcements.length > 0 && ( - - )} + {/* isTabVisible('announcements') guard */} + {activeTab === 'announcements' && + isTabVisible('announcements') && + announcements.length > 0 && ( + + )} - {activeTab === 'submission' && ( + {/* isTabVisible('submission') guard */} + {activeTab === 'submission' && isTabVisible('submission') && ( )} - {activeTab === 'discussions' && ( + {/* isTabVisible('discussions') guard */} + {activeTab === 'discussions' && isTabVisible('discussions') && ( )} - {activeTab === 'team-formation' && ( - - )} + {/* isTabVisible('team-formation') guard */} + {activeTab === 'team-formation' && + isTabVisible('team-formation') && ( + + )} - {activeTab === 'winners' && ( + {/* isTabVisible('winners') guard */} + {activeTab === 'winners' && isTabVisible('winners') && ( )} - {activeTab === 'resources' && currentHackathon?.resources?.[0] && ( - - )} + {/* Note: duplicate resources render removed — was already covered above */}