diff --git a/app/(landing)/hackathons/[slug]/page.tsx b/app/(landing)/hackathons/[slug]/page.tsx index 43e983bb..b74bf434 100644 --- a/app/(landing)/hackathons/[slug]/page.tsx +++ b/app/(landing)/hackathons/[slug]/page.tsx @@ -13,6 +13,7 @@ import { HackathonResources } from '@/components/hackathons/resources/resources' import SubmissionTab from '@/components/hackathons/submissions/submissionTab'; import { HackathonDiscussions } from '@/components/hackathons/discussion/comment'; import { TeamFormationTab } from '@/components/hackathons/team-formation/TeamFormationTab'; +import { WinnersTab } from '@/components/hackathons/winners/WinnersTab'; import LoadingScreen from '@/features/projects/components/CreateProjectModal/LoadingScreen'; import { useTimelineEvents } from '@/hooks/hackathon/use-timeline-events'; import { toast } from 'sonner'; @@ -22,6 +23,7 @@ import { HackathonParticipants } from '@/components/hackathons/participants/hack import { useCommentSystem } from '@/hooks/use-comment-system'; import { CommentEntityType } from '@/types/comment'; import { useTeamPosts } from '@/hooks/hackathon/use-team-posts'; +import { HackathonWinner } from '@/lib/api/hackathons'; export default function HackathonPage() { const router = useRouter(); @@ -31,6 +33,7 @@ export default function HackathonPage() { const { currentHackathon, submissions, + winners, loading, setCurrentHackathon, refreshCurrentHackathon, @@ -75,6 +78,19 @@ export default function HackathonPage() { const isTabEnabled = currentHackathon?.enabledTabs?.includes('joinATeamTab') !== false; + // For testing: Use mock winners if real winners are empty + // const displayWinners = + // winners && winners.length > 0 ? winners : MOCK_WINNERS; + // const hasWinners = displayWinners.length > 0; + const hasWinners = winners && winners.length > 0; + + // For testing: Force enable winners tab + // const isWinnersTabEnabled = + // currentHackathon?.enabledTabs?.includes('winnersTab') !== false; + // const isWinnersTabEnabled = true; + const isWinnersTabEnabled = + currentHackathon?.enabledTabs?.includes('winnersTab') !== false; + const tabs = [ { id: 'overview', label: 'Overview' }, ...(hasParticipants @@ -115,6 +131,13 @@ export default function HackathonPage() { }); } + if (hasWinners && isWinnersTabEnabled) { + tabs.push({ + id: 'winners', + label: 'Winners', + }); + } + return tabs; }, [ currentHackathon?.participants, @@ -126,6 +149,7 @@ export default function HackathonPage() { discussionComments.comments.length, teamPosts.length, hackathonId, + winners, ]); // Refresh hackathon data @@ -342,11 +366,9 @@ export default function HackathonPage() { )} {activeTab === 'resources' && - currentHackathon.resources?.length > 0 && ( // Direct array check - - )} + currentHackathon.resources?.length > 0 && } {activeTab === 'participants' && - currentHackathon.participants?.length > 0 && ( // Direct array check + currentHackathon.participants?.length > 0 && ( )} @@ -365,7 +387,14 @@ export default function HackathonPage() { )} {activeTab === 'team-formation' && ( - + + )} + + {activeTab === 'winners' && ( + )} {activeTab === 'resources' && currentHackathon?.resources?.[0] && ( diff --git a/app/(landing)/hackathons/layout.tsx b/app/(landing)/hackathons/layout.tsx index 9bc3e940..68565993 100644 --- a/app/(landing)/hackathons/layout.tsx +++ b/app/(landing)/hackathons/layout.tsx @@ -1,4 +1,5 @@ import { HackathonDataProvider } from '@/lib/providers/hackathonProvider'; +import { OrganizationProvider } from '@/lib/providers/OrganizationProvider'; import { use } from 'react'; interface HackathonLayoutProps { @@ -15,8 +16,10 @@ export default function HackathonLayout({ const resolvedParams = use(params); return ( - - {children} - + + + {children} + + ); } diff --git a/app/(landing)/organizations/[id]/hackathons/[hackathonId]/page.tsx b/app/(landing)/organizations/[id]/hackathons/[hackathonId]/page.tsx index a9ed2fcd..346e2a8a 100644 --- a/app/(landing)/organizations/[id]/hackathons/[hackathonId]/page.tsx +++ b/app/(landing)/organizations/[id]/hackathons/[hackathonId]/page.tsx @@ -1,7 +1,13 @@ 'use client'; import { useParams } from 'next/navigation'; -import { Loader2, AlertCircle, Calendar, TrendingUp } from 'lucide-react'; +import { + Loader2, + AlertCircle, + Calendar, + TrendingUp, + Check, +} from 'lucide-react'; import { useHackathons } from '@/hooks/use-hackathons'; import { useEffect } from 'react'; import { useHackathonAnalytics } from '@/hooks/use-hackathon-analytics'; @@ -23,8 +29,10 @@ export default function HackathonPage() { autoFetch: false, }); - const { statistics, statisticsLoading, timeSeriesData, timeSeriesLoading } = - useHackathonAnalytics(organizationId, hackathonId); + const { analytics, loading: analyticsLoading } = useHackathonAnalytics( + organizationId, + hackathonId + ); useEffect(() => { if (organizationId && hackathonId) { @@ -60,6 +68,29 @@ export default function HackathonPage() { ); } + // Adapt key metrics + const statistics = analytics?.summary || null; + + // Adapt charts data + const timeSeriesData = analytics?.trends + ? { + submissions: { + daily: analytics.trends.submissionsOverTime.map(p => ({ + date: p.date, + count: p.count, + })), + weekly: [], + }, + participants: { + daily: analytics.trends.participantSignupsOverTime.map(p => ({ + date: p.date, + count: p.count, + })), + weekly: [], + }, + } + : null; + return ( }>
@@ -69,11 +100,6 @@ export default function HackathonPage() {

{currentHackathon?.name || 'Hackathon Dashboard'}

- {/* {currentHackathon?.information?.description && ( -

- {currentHackathon.information.description} -

- )} */}
@@ -89,7 +115,7 @@ export default function HackathonPage() { @@ -97,7 +123,7 @@ export default function HackathonPage() {
@@ -109,21 +135,97 @@ export default function HackathonPage() { Timeline - ({ - name: phase.name || '', - startDate: phase.startDate || '', - endDate: phase.endDate || '', - })), - }} - /> + + {/* Render new timeline from analytics */} +
+
+ {(() => { + const timelineEvents = analytics?.timeline || []; + const hasWinnerAnnouncement = timelineEvents.some( + e => e.phase === 'Winner Announcement' + ); + + // Manually append Winner Announcement if missing and date exists + const fullTimeline = [...timelineEvents]; + if (!hasWinnerAnnouncement && currentHackathon?.endDate) { + const winnerDate = new Date(currentHackathon.endDate); + const now = new Date(); + // Simple status logic for single date event + // If date is passed, completed. If today (roughly), ongoing? + // Or just use 'upcoming' if future, 'completed' if past. + // Ideally we'd match the phase logic, but for a single date event: + let status: 'completed' | 'ongoing' | 'upcoming' = + 'upcoming'; + if (now > winnerDate) { + status = 'completed'; + } + // For "Winner Announcement", it might be "ongoing" on the day of? + // keeping simple for now. + + fullTimeline.push({ + phase: 'Winner Announcement', + description: + 'Final results published and prizes distributed to winners.', + date: currentHackathon.endDate, + status: status, + }); + } + + return fullTimeline.map((phase, index) => { + const isLast = index === fullTimeline.length - 1; + const isActive = phase.status === 'ongoing'; + const isCompleted = phase.status === 'completed'; + + return ( +
+
+ {isActive ? ( +
+
+
+ ) : isCompleted ? ( +
+ +
+ ) : ( +
+ )} + {!isLast && ( +
+
+
+ )} +
+
+
+

+ {phase.phase} +

+

+ {phase.description} +

+
+
+ {new Date(phase.date).toLocaleDateString()} +
+
+
+ ); + }); + })()} +
+
diff --git a/app/(landing)/organizations/[id]/hackathons/[hackathonId]/settings/page.tsx b/app/(landing)/organizations/[id]/hackathons/[hackathonId]/settings/page.tsx index 56ff60fc..2ba98388 100644 --- a/app/(landing)/organizations/[id]/hackathons/[hackathonId]/settings/page.tsx +++ b/app/(landing)/organizations/[id]/hackathons/[hackathonId]/settings/page.tsx @@ -12,6 +12,7 @@ import { Trophy, Handshake, Sliders, + Eye, } from 'lucide-react'; import { toast } from 'sonner'; import { api } from '@/lib/api/api'; @@ -21,6 +22,7 @@ import ParticipantSettingsTab from '@/components/organization/hackathons/setting import RewardsSettingsTab from '@/components/organization/hackathons/settings/RewardsSettingsTab'; import CollaborationSettingsTab from '@/components/organization/hackathons/settings/CollaborationSettingsTab'; import AdvancedSettingsTab from '@/components/organization/hackathons/settings/AdvancedSettingsTab'; +import SubmissionVisibilitySettingsTab from '@/components/organization/hackathons/settings/SubmissionVisibilitySettingsTab'; import { AuthGuard } from '@/components/auth'; import Loading from '@/components/Loading'; @@ -189,6 +191,13 @@ export default function SettingsPage() { Advanced + + + Submissions +
@@ -253,6 +262,13 @@ export default function SettingsPage() { isLoading={isSaving} /> + + + + diff --git a/app/(landing)/organizations/[id]/hackathons/page.tsx b/app/(landing)/organizations/[id]/hackathons/page.tsx index 8a324e41..1f0fb3e0 100644 --- a/app/(landing)/organizations/[id]/hackathons/page.tsx +++ b/app/(landing)/organizations/[id]/hackathons/page.tsx @@ -157,7 +157,7 @@ export default function HackathonsPage() { ? (item.data as HackathonDraft).data.information?.categories ?.join(',') ?.toLowerCase() || '' - : ''; // Categories filtering only applies to drafts for now + : ''; return category.includes(categoryFilter.toLowerCase()); }); } @@ -494,9 +494,11 @@ export default function HackathonsPage() { ); } + const publishedHackathon = hackathon as Hackathon; + return (
@@ -506,15 +508,15 @@ export default function HackathonsPage() { - {hackathon.status === 'PUBLISHED' + {publishedHackathon.status === 'PUBLISHED' ? 'Live' - : hackathon.status} + : publishedHackathon.status} {endDate && (
@@ -531,11 +533,19 @@ export default function HackathonsPage() {
- 0 participants + + {publishedHackathon.participants?.length || + publishedHackathon._count?.participants || + 0}{' '} + participants +
- 0 submissions + + {publishedHackathon._count?.submissions || 0}{' '} + submissions +
{totalPrize > 0 && ( <> @@ -560,7 +570,7 @@ export default function HackathonsPage() {