@@ -167,6 +167,33 @@ export default function HackathonPageClient() {
167167 } ) ;
168168 }
169169
170+ // Filter tabs against enabledTabs so only explicitly enabled tabs are shown.
171+ // 'overview' is always kept as it is the default fallback tab.
172+ // If enabledTabs is undefined/null (not configured), all tabs are shown as before.
173+ // Map UI tab ids to backend enabledTabs keys where they differ.
174+ const tabIdToEnabledKey : Record < string , string > = {
175+ 'team-formation' : 'joinATeamTab' ,
176+ winners : 'winnersTab' ,
177+ resources : 'resourcesTab' ,
178+ participants : 'participantsTab' ,
179+ announcements : 'announcementsTab' ,
180+ submission : 'submissionTab' ,
181+ discussions : 'discussionTab' ,
182+ } ;
183+
184+ type EnabledTab = NonNullable <
185+ typeof currentHackathon
186+ > [ 'enabledTabs' ] [ number ] ;
187+ const enabledTabs = currentHackathon ?. enabledTabs ;
188+
189+ if ( Array . isArray ( enabledTabs ) ) {
190+ const enabledSet = new Set ( enabledTabs ) ;
191+ return tabs . filter ( tab => {
192+ if ( tab . id === 'overview' ) return true ;
193+ const key = ( tabIdToEnabledKey [ tab . id ] ?? tab . id ) as EnabledTab ;
194+ return enabledSet . has ( key ) ;
195+ } ) ;
196+ }
170197 return tabs ;
171198 } , [
172199 currentHackathon ?. participants ,
@@ -273,12 +300,29 @@ export default function HackathonPageClient() {
273300 } , [ hackathonId , setCurrentHackathon ] ) ;
274301
275302 // Handle tab changes from URL
303+ // Now also defaults to 'overview' if the URL tab is not in the filtered hackathonTabs list.
304+ // This handles direct URL access to a disabled tab — user is silently redirected to overview.
276305 useEffect ( ( ) => {
277306 const tabFromUrl = searchParams . get ( 'tab' ) ;
278- if ( tabFromUrl && hackathonTabs . some ( tab => tab . id === tabFromUrl ) ) {
307+
308+ // No tab in URL — default to overview
309+ if ( ! tabFromUrl ) {
310+ setActiveTab ( 'overview' ) ;
311+ return ;
312+ }
313+
314+ if ( hackathonTabs . some ( tab => tab . id === tabFromUrl ) ) {
315+ // Tab exists in filtered list — activate it normally
279316 setActiveTab ( tabFromUrl ) ;
317+ return ;
280318 }
281- } , [ searchParams , hackathonTabs ] ) ;
319+
320+ // Tab is disabled or unrecognised — fall back to overview
321+ setActiveTab ( 'overview' ) ;
322+ const queryParams = new URLSearchParams ( searchParams . toString ( ) ) ;
323+ queryParams . set ( 'tab' , 'overview' ) ;
324+ router . replace ( `?${ queryParams . toString ( ) } ` , { scroll : false } ) ;
325+ } , [ searchParams , hackathonTabs , router ] ) ;
282326
283327 const handleTabChange = ( tabId : string ) => {
284328 setActiveTab ( tabId ) ;
@@ -308,6 +352,11 @@ export default function HackathonPageClient() {
308352 ) ;
309353 }
310354
355+ // Helper: checks if a tab id is present in the filtered hackathonTabs array.
356+ // Used below to guard each tab's content from rendering if the tab is disabled.
357+ const isTabVisible = ( tabId : string ) =>
358+ hackathonTabs . some ( tab => tab . id === tabId ) ;
359+
311360 // Shared props for banner and sticky card
312361 const sharedActionProps = {
313362 deadline : currentHackathon . submissionDeadline ,
@@ -380,48 +429,60 @@ export default function HackathonPageClient() {
380429 />
381430 ) }
382431
432+ { /* isTabVisible('resources') guard — HackathonResources will not
433+ render at all if 'resources' is not in enabledTabs, even via direct URL */ }
383434 { activeTab === 'resources' &&
435+ isTabVisible ( 'resources' ) &&
384436 currentHackathon . resources ?. length > 0 && < HackathonResources /> }
437+
438+ { /* isTabVisible('participants') guard */ }
385439 { activeTab === 'participants' &&
440+ isTabVisible ( 'participants' ) &&
386441 currentHackathon . participants ?. length > 0 && (
387442 < HackathonParticipants />
388443 ) }
389444
390- { activeTab === 'announcements' && announcements . length > 0 && (
391- < AnnouncementsTab
392- announcements = { announcements }
393- hackathonSlug = { hackathonId }
394- />
395- ) }
445+ { /* isTabVisible('announcements') guard */ }
446+ { activeTab === 'announcements' &&
447+ isTabVisible ( 'announcements' ) &&
448+ announcements . length > 0 && (
449+ < AnnouncementsTab
450+ announcements = { announcements }
451+ hackathonSlug = { hackathonId }
452+ />
453+ ) }
396454
397- { activeTab === 'submission' && (
455+ { /* isTabVisible('submission') guard */ }
456+ { activeTab === 'submission' && isTabVisible ( 'submission' ) && (
398457 < SubmissionTab
399458 organizationId = { currentHackathon . organizationId }
400459 isRegistered = { isRegistered }
401460 />
402461 ) }
403462
404- { activeTab === 'discussions' && (
463+ { /* isTabVisible('discussions') guard */ }
464+ { activeTab === 'discussions' && isTabVisible ( 'discussions' ) && (
405465 < HackathonDiscussions
406466 hackathonId = { hackathonId }
407467 isRegistered = { isRegistered }
408468 />
409469 ) }
410470
411- { activeTab === 'team-formation' && (
412- < TeamFormationTab
413- hackathonSlugOrId = { hackathonId }
414- isRegistered = { isRegistered }
415- />
416- ) }
471+ { /* isTabVisible('team-formation') guard */ }
472+ { activeTab === 'team-formation' &&
473+ isTabVisible ( 'team-formation' ) && (
474+ < TeamFormationTab
475+ hackathonSlugOrId = { hackathonId }
476+ isRegistered = { isRegistered }
477+ />
478+ ) }
417479
418- { activeTab === 'winners' && (
480+ { /* isTabVisible('winners') guard */ }
481+ { activeTab === 'winners' && isTabVisible ( 'winners' ) && (
419482 < WinnersTab winners = { winners } hackathonSlug = { hackathonId } />
420483 ) }
421484
422- { activeTab === 'resources' && currentHackathon ?. resources ?. [ 0 ] && (
423- < HackathonResources />
424- ) }
485+ { /* Note: duplicate resources render removed — was already covered above */ }
425486 </ div >
426487 </ div >
427488
0 commit comments