Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 81 additions & 20 deletions app/(landing)/hackathons/[slug]/HackathonPageClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string> = {
'team-formation': 'joinATeamTab',
winners: 'winnersTab',
resources: 'resourcesTab',
participants: 'participantsTab',
announcements: 'announcementsTab',
submission: 'submissionTab',
discussions: 'discussionTab',
};
Comment thread
coderabbitai[bot] marked this conversation as resolved.

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,
Expand Down Expand Up @@ -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]);
Comment thread
coderabbitai[bot] marked this conversation as resolved.

const handleTabChange = (tabId: string) => {
setActiveTab(tabId);
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 && <HackathonResources />}

{/* isTabVisible('participants') guard */}
{activeTab === 'participants' &&
isTabVisible('participants') &&
currentHackathon.participants?.length > 0 && (
<HackathonParticipants />
)}

{activeTab === 'announcements' && announcements.length > 0 && (
<AnnouncementsTab
announcements={announcements}
hackathonSlug={hackathonId}
/>
)}
{/* isTabVisible('announcements') guard */}
{activeTab === 'announcements' &&
isTabVisible('announcements') &&
announcements.length > 0 && (
<AnnouncementsTab
announcements={announcements}
hackathonSlug={hackathonId}
/>
)}

{activeTab === 'submission' && (
{/* isTabVisible('submission') guard */}
{activeTab === 'submission' && isTabVisible('submission') && (
<SubmissionTab
organizationId={currentHackathon.organizationId}
isRegistered={isRegistered}
/>
)}

{activeTab === 'discussions' && (
{/* isTabVisible('discussions') guard */}
{activeTab === 'discussions' && isTabVisible('discussions') && (
<HackathonDiscussions
hackathonId={hackathonId}
isRegistered={isRegistered}
/>
)}

{activeTab === 'team-formation' && (
<TeamFormationTab
hackathonSlugOrId={hackathonId}
isRegistered={isRegistered}
/>
)}
{/* isTabVisible('team-formation') guard */}
{activeTab === 'team-formation' &&
isTabVisible('team-formation') && (
<TeamFormationTab
hackathonSlugOrId={hackathonId}
isRegistered={isRegistered}
/>
)}

{activeTab === 'winners' && (
{/* isTabVisible('winners') guard */}
{activeTab === 'winners' && isTabVisible('winners') && (
<WinnersTab winners={winners} hackathonSlug={hackathonId} />
)}

{activeTab === 'resources' && currentHackathon?.resources?.[0] && (
<HackathonResources />
)}
{/* Note: duplicate resources render removed — was already covered above */}
</div>
</div>

Expand Down
Loading