Skip to content

Commit 8690841

Browse files
committed
chore(analytics): merge main into feature branch
2 parents 07b06b3 + 0060643 commit 8690841

65 files changed

Lines changed: 3244 additions & 2475 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

COMMENT_SYSTEM_README.md

Lines changed: 0 additions & 571 deletions
This file was deleted.

Redesign Project Detail Page

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Completed: Redesign Project Detail Page — Simpler, clearer UI/UX
2+
3+
https://www.figma.com/design/EMNGAQl1SGObXcsoa24krt/Boundless_Project-Details?node-id=0-1&t=CY7XUp5OWWoJ8qpK-1
4+

app/(landing)/hackathons/[slug]/HackathonPageClient.tsx

Lines changed: 87 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -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

app/(landing)/hackathons/preview/[orgId]/[draftId]/page.tsx

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -145,36 +145,14 @@ export default function DraftPreviewPage({ params }: PreviewPageProps) {
145145
timezone: draft.data.timeline?.timezone || 'UTC',
146146

147147
startDate: draft.data.timeline?.startDate || '',
148-
endDate:
149-
draft.data.timeline?.winnersAnnouncedAt ||
150-
draft.data.timeline?.winnerAnnouncementDate ||
151-
draft.data.timeline?.judgingEnd ||
152-
draft.data.timeline?.judgingDate ||
153-
draft.data.timeline?.judgingStart ||
154-
draft.data.timeline?.submissionDeadline ||
155-
draft.data.timeline?.startDate ||
156-
'',
157148
submissionDeadline: draft.data.timeline?.submissionDeadline || '',
158149
registrationDeadline:
159-
draft.data.participation?.registrationDeadline || '',
160-
judgingStart: draft.data.timeline?.judgingStart || '',
161-
judgingEnd: draft.data.timeline?.judgingEnd || '',
162-
winnersAnnouncedAt:
163-
draft.data.timeline?.winnersAnnouncedAt ||
164-
draft.data.timeline?.winnerAnnouncementDate ||
165-
'',
166-
customRegistrationDeadline:
167-
draft.data.participation?.registrationDeadline || null,
150+
draft.data.timeline?.registrationDeadline || '',
151+
judgingDeadline: draft.data.timeline?.judgingDeadline || '',
168152

169153
registrationOpen: false,
170-
registrationDeadlinePolicy:
171-
(draft.data.participation?.registrationDeadlinePolicy as
172-
| 'BEFORE_START'
173-
| 'BEFORE_SUBMISSION_DEADLINE'
174-
| 'CUSTOM') || 'BEFORE_SUBMISSION_DEADLINE',
175154

176155
daysUntilStart: 0,
177-
daysUntilEnd: 0,
178156

179157
participantType: (draft.data.participation?.participantType ===
180158
'individual'
@@ -527,7 +505,6 @@ export default function DraftPreviewPage({ params }: PreviewPageProps) {
527505
participants={0}
528506
imageUrl={previewHackathon.banner}
529507
startDate={previewHackathon.startDate}
530-
endDate={previewHackathon.endDate}
531508
/>
532509

533510
{/* Tabs */}

app/(landing)/org/[id]/org-profile-client.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ export default function OrgProfileClient({ slug }: OrgProfileClientProps) {
8585
return (
8686
<section className='mx-auto max-w-[1440px] px-5 py-10 md:px-[50px] lg:px-[100px]'>
8787
{/* Banner / Header */}
88-
<div className='relative mb-8 overflow-hidden rounded-2xl border border-[#a7f950]/20 bg-gradient-to-br from-[#a7f950]/10 via-zinc-900/80 to-zinc-900/40'>
88+
<div className='relative mb-8 overflow-hidden rounded-2xl border border-[#a7f950]/20 bg-linear-to-br from-[#a7f950]/10 via-zinc-900/80 to-zinc-900/40'>
8989
<div className='absolute -top-24 -right-24 h-64 w-64 rounded-full bg-[#a7f950]/5 blur-3xl' />
9090
<div className='absolute -bottom-16 -left-16 h-48 w-48 rounded-full bg-[#a7f950]/5 blur-3xl' />
9191

app/(landing)/organizations/[id]/hackathons/[hackathonId]/page.tsx

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -158,31 +158,7 @@ export default function HackathonPage() {
158158
e => e.phase === 'Winner Announcement'
159159
);
160160

161-
// Manually append Winner Announcement if missing and date exists
162161
const fullTimeline = [...timelineEvents];
163-
if (!hasWinnerAnnouncement && currentHackathon?.endDate) {
164-
const winnerDate = new Date(currentHackathon.endDate);
165-
const now = new Date();
166-
// Simple status logic for single date event
167-
// If date is passed, completed. If today (roughly), ongoing?
168-
// Or just use 'upcoming' if future, 'completed' if past.
169-
// Ideally we'd match the phase logic, but for a single date event:
170-
let status: 'completed' | 'ongoing' | 'upcoming' =
171-
'upcoming';
172-
if (now > winnerDate) {
173-
status = 'completed';
174-
}
175-
// For "Winner Announcement", it might be "ongoing" on the day of?
176-
// keeping simple for now.
177-
178-
fullTimeline.push({
179-
phase: 'Winner Announcement',
180-
description:
181-
'Final results published and prizes distributed to winners.',
182-
date: currentHackathon.endDate,
183-
status: status,
184-
});
185-
}
186162

187163
return fullTimeline.map((phase, index) => {
188164
const isLast = index === fullTimeline.length - 1;

app/(landing)/organizations/[id]/hackathons/[hackathonId]/settings/page.tsx

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -80,16 +80,16 @@ export default function SettingsPage() {
8080
if (!h) return undefined;
8181
return {
8282
startDate: h.startDate ? new Date(h.startDate) : undefined,
83-
endDate: h.endDate ? new Date(h.endDate) : undefined,
8483
submissionDeadline: h.submissionDeadline
8584
? new Date(h.submissionDeadline)
8685
: undefined,
87-
judgingStart: h.judgingStart ? new Date(h.judgingStart) : undefined,
88-
judgingEnd: h.judgingEnd ? new Date(h.judgingEnd) : undefined,
89-
winnersAnnouncedAt: h.winnersAnnouncedAt
90-
? new Date(h.winnersAnnouncedAt)
91-
: undefined,
9286
timezone: h.timezone || 'UTC',
87+
registrationDeadline: h.registrationDeadline
88+
? new Date(h.registrationDeadline)
89+
: undefined,
90+
judgingDeadline: h.judgingDeadline
91+
? new Date(h.judgingDeadline)
92+
: undefined,
9393
phases: h.phases?.map(p => ({
9494
...p,
9595
startDate: p.startDate ? new Date(p.startDate) : undefined,
@@ -107,9 +107,6 @@ export default function SettingsPage() {
107107
require_github: h.requireGithub,
108108
require_demo_video: h.requireDemoVideo,
109109
require_other_links: h.requireOtherLinks,
110-
registrationDeadlinePolicy:
111-
h.registrationDeadlinePolicy?.toLowerCase() as any,
112-
registrationDeadline: h.customRegistrationDeadline || undefined,
113110
detailsTab: h.enabledTabs.includes('detailsTab'),
114111
participantsTab: h.enabledTabs.includes('participantsTab'),
115112
resourcesTab: h.enabledTabs.includes('resourcesTab'),
@@ -269,7 +266,12 @@ export default function SettingsPage() {
269266
await handleSave('General', data);
270267
}}
271268
isLoading={isSaving}
272-
isPublished={hackathon?.status === 'PUBLISHED'}
269+
isPublished={[
270+
'UPCOMING',
271+
'ACTIVE',
272+
'JUDGING',
273+
'COMPLETED',
274+
].includes(hackathon?.status ?? '')}
273275
/>
274276
</TabsContent>
275277

app/(landing)/organizations/[id]/hackathons/[hackathonId]/submissions/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { useParams } from 'next/navigation';
44
import { useEffect, useState } from 'react';
55
import { Loader2, AlertCircle } from 'lucide-react';
6-
import { useHackathonSubmissions } from '@/hooks/hackathon/use-hackathon-submissions';
6+
import { useOrganizerSubmissions } from '@/hooks/hackathon/use-organizer-submissions';
77
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
88
import { AuthGuard } from '@/components/auth';
99
import Loading from '@/components/Loading';
@@ -26,7 +26,7 @@ export default function SubmissionsPage() {
2626
updateFilters,
2727
goToPage,
2828
refresh,
29-
} = useHackathonSubmissions(hackathonId);
29+
} = useOrganizerSubmissions(hackathonId);
3030

3131
const [currentUserId, setCurrentUserId] = useState<string | null>(null);
3232
const [hackathon, setHackathon] = useState<Hackathon | null>(null);

0 commit comments

Comments
 (0)