@@ -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,17 +352,21 @@ 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 ,
314363 startDate : currentHackathon . startDate ,
315364 totalPrizePool : currentHackathon . prizeTiers
316- . reduce ( ( acc , prize ) => acc + Number ( prize . amount || 0 ) , 0 )
365+ . reduce ( ( acc , prize ) => acc + Number ( prize . prizeAmount || 0 ) , 0 )
317366 . toString ( ) ,
318367 isRegistered,
319368 hasSubmitted,
320369 isTeamFormationEnabled,
321- registrationDeadlinePolicy : currentHackathon . registrationDeadlinePolicy ,
322370 registrationDeadline : currentHackathon . registrationDeadline ,
323371 participantType : currentHackathon . participantType ,
324372 onJoinClick : handleJoinClick ,
@@ -359,10 +407,13 @@ export default function HackathonPageClient() {
359407 currency : tier . currency ,
360408 passMark : tier . passMark ,
361409 description : tier . description ,
362- prizeAmount : tier . amount ,
410+ prizeAmount : tier . prizeAmount ,
363411 } ) ) }
364412 totalPrizePool = { currentHackathon . prizeTiers
365- . reduce ( ( acc , prize ) => acc + Number ( prize . amount || 0 ) , 0 )
413+ . reduce (
414+ ( acc , prize ) => acc + Number ( prize . prizeAmount || 0 ) ,
415+ 0
416+ )
366417 . toString ( ) }
367418 hackathonSlugOrId = { hackathonId }
368419 venue = { {
@@ -378,48 +429,60 @@ export default function HackathonPageClient() {
378429 />
379430 ) }
380431
432+ { /* isTabVisible('resources') guard — HackathonResources will not
433+ render at all if 'resources' is not in enabledTabs, even via direct URL */ }
381434 { activeTab === 'resources' &&
435+ isTabVisible ( 'resources' ) &&
382436 currentHackathon . resources ?. length > 0 && < HackathonResources /> }
437+
438+ { /* isTabVisible('participants') guard */ }
383439 { activeTab === 'participants' &&
440+ isTabVisible ( 'participants' ) &&
384441 currentHackathon . participants ?. length > 0 && (
385442 < HackathonParticipants />
386443 ) }
387444
388- { activeTab === 'announcements' && announcements . length > 0 && (
389- < AnnouncementsTab
390- announcements = { announcements }
391- hackathonSlug = { hackathonId }
392- />
393- ) }
445+ { /* isTabVisible('announcements') guard */ }
446+ { activeTab === 'announcements' &&
447+ isTabVisible ( 'announcements' ) &&
448+ announcements . length > 0 && (
449+ < AnnouncementsTab
450+ announcements = { announcements }
451+ hackathonSlug = { hackathonId }
452+ />
453+ ) }
394454
395- { activeTab === 'submission' && (
455+ { /* isTabVisible('submission') guard */ }
456+ { activeTab === 'submission' && isTabVisible ( 'submission' ) && (
396457 < SubmissionTab
397458 organizationId = { currentHackathon . organizationId }
398459 isRegistered = { isRegistered }
399460 />
400461 ) }
401462
402- { activeTab === 'discussions' && (
463+ { /* isTabVisible('discussions') guard */ }
464+ { activeTab === 'discussions' && isTabVisible ( 'discussions' ) && (
403465 < HackathonDiscussions
404466 hackathonId = { hackathonId }
405467 isRegistered = { isRegistered }
406468 />
407469 ) }
408470
409- { activeTab === 'team-formation' && (
410- < TeamFormationTab
411- hackathonSlugOrId = { hackathonId }
412- isRegistered = { isRegistered }
413- />
414- ) }
471+ { /* isTabVisible('team-formation') guard */ }
472+ { activeTab === 'team-formation' &&
473+ isTabVisible ( 'team-formation' ) && (
474+ < TeamFormationTab
475+ hackathonSlugOrId = { hackathonId }
476+ isRegistered = { isRegistered }
477+ />
478+ ) }
415479
416- { activeTab === 'winners' && (
480+ { /* isTabVisible('winners') guard */ }
481+ { activeTab === 'winners' && isTabVisible ( 'winners' ) && (
417482 < WinnersTab winners = { winners } hackathonSlug = { hackathonId } />
418483 ) }
419484
420- { activeTab === 'resources' && currentHackathon ?. resources ?. [ 0 ] && (
421- < HackathonResources />
422- ) }
485+ { /* Note: duplicate resources render removed — was already covered above */ }
423486 </ div >
424487 </ div >
425488
0 commit comments