diff --git a/app/(landing)/hackathons/[slug]/page.tsx b/app/(landing)/hackathons/[slug]/page.tsx index 8f5946e55..c69c106b7 100644 --- a/app/(landing)/hackathons/[slug]/page.tsx +++ b/app/(landing)/hackathons/[slug]/page.tsx @@ -15,6 +15,7 @@ import SubmissionTab from '@/components/hackathons/submissions/submissionTab'; import { HackathonDiscussions } from '@/components/hackathons/discussion/comment'; import { TeamFormationTab } from '@/components/hackathons/team-formation/TeamFormationTab'; import LoadingScreen from '@/components/landing-page/project/CreateProjectModal/LoadingScreen'; +import { useTimelineEvents } from '@/hooks/hackathon/use-timeline-events'; export default function HackathonPage() { const router = useRouter(); @@ -23,14 +24,16 @@ export default function HackathonPage() { const { currentHackathon, - // content, - timelineEvents, participants, submissions, - prizes, loading, setCurrentHackathon, } = useHackathonData(); + console.log(currentHackathon); + const timeline_Events = useTimelineEvents(currentHackathon, { + includeEndDate: false, + dateFormat: { month: 'short', day: 'numeric', year: 'numeric' }, + }); const hackathonTabs = useMemo(() => { const tabs = [ @@ -193,8 +196,9 @@ export default function HackathonPage() { {activeTab === 'overview' && ( )} diff --git a/app/(landing)/hackathons/preview/[orgId]/[draftId]/page.tsx b/app/(landing)/hackathons/preview/[orgId]/[draftId]/page.tsx index 4432cf4ba..9e14c6a50 100644 --- a/app/(landing)/hackathons/preview/[orgId]/[draftId]/page.tsx +++ b/app/(landing)/hackathons/preview/[orgId]/[draftId]/page.tsx @@ -5,6 +5,7 @@ import { useRouter, useSearchParams } from 'next/navigation'; import { ArrowLeft } from 'lucide-react'; import { previewDraft, + PrizeTier, transformPublicHackathonToHackathon, } from '@/lib/api/hackathons'; import { HackathonBanner } from '@/components/hackathons/hackathonBanner'; @@ -28,30 +29,29 @@ const mockTimelineEvents = [ { event: 'Winners Announced', date: new Date().toISOString() }, ]; -const mockPrizes = [ +const mockPrizes: PrizeTier[] = [ { - title: 'First Place', - rank: '1st', - prize: '$5,000', - icon: '🥇', - details: ['Cash prize', 'Certificate', 'Featured on homepage'], + position: '1st', + amount: 5000, + currency: 'USD', + description: 'Cash prize, Certificate, Featured on homepage', }, { - title: 'Second Place', - rank: '2nd', - prize: '$3,000', - icon: '🥈', - details: ['Cash prize', 'Certificate'], + position: '2nd', + amount: 3000, + currency: 'USD', + description: 'Cash prize, Certificate', }, { - title: 'Third Place', - rank: '3rd', - prize: '$2,000', - icon: '🥉', - details: ['Cash prize', 'Certificate'], + position: '3rd', + amount: 2000, + currency: 'USD', + description: 'Cash prize, Certificate', }, ]; +const totalPrizePool = mockPrizes.reduce((sum, prize) => sum + prize.amount, 0); + interface PreviewPageProps { params: Promise<{ orgId: string; @@ -400,6 +400,7 @@ export default function DraftPreviewPage({ params }: PreviewPageProps) {
{activeTab === 'overview' && ( {timelineEvents && } - {prizes && } + {prizes && ( + + )}
); } diff --git a/components/hackathons/overview/hackathonPrizes.tsx b/components/hackathons/overview/hackathonPrizes.tsx index cf6d50439..825a10ba1 100644 --- a/components/hackathons/overview/hackathonPrizes.tsx +++ b/components/hackathons/overview/hackathonPrizes.tsx @@ -1,66 +1,110 @@ 'use client'; +import { PrizeTier } from '@/lib/api/hackathons'; + interface HackathonPrizesProps { title?: string; - totalPrizes?: string; + totalPrizePool?: string; otherPrizes?: string; - prizes: Array<{ - title: string; - rank: string; - prize: string; - details: string[]; - icon?: string; - }>; + prizes: PrizeTier[]; } export function HackathonPrizes({ title = 'PRIZES', - totalPrizes = '$1,000+ in prizes', + totalPrizePool, otherPrizes, prizes, }: HackathonPrizesProps) { + const firstThreePrizes = prizes.slice(0, 3); + const remainingPrizes = prizes.slice(3); + return (

{title}

- {totalPrizes} + {totalPrizePool} USDC {otherPrizes && ( + {otherPrizes} )}
-
- {prizes.map((prize, index) => ( -
-
- {prize.icon || '⭐'} -
-

{prize.title}

-

{prize.rank}

+ {/* First 3 prizes in cards */} + {firstThreePrizes.length > 0 && ( +
+ {firstThreePrizes.map((prize, index) => ( +
+
+ + {index === 0 + ? '🥇' + : index === 1 + ? '🥈' + : index === 2 + ? '🥉' + : '⭐'} + +
+

+ {prize.position} +

+

{prize.position}

+
-
-
-
- {prize.prize} +
+
+ {prize.amount} {prize.currency || 'USDC'} +
-
    - {prize.details.map((detail, i) => ( -
  • - - {detail} -
  • - ))} -
+ ))} +
+ )} + + {remainingPrizes.length > 0 && ( +
+
+ + + + + + + + + + {remainingPrizes.map((prize, index) => ( + + + + + + ))} + +
+ POSITION + + PRIZE AMOUNT + + CURRENCY +
+ {prize.position} + + {prize.amount} + + {prize.currency || 'USDC'} +
- ))} -
+
+ )}
); } diff --git a/components/hackathons/overview/hackathonTimeline.tsx b/components/hackathons/overview/hackathonTimeline.tsx index 2a9ed046f..d785b2ec4 100644 --- a/components/hackathons/overview/hackathonTimeline.tsx +++ b/components/hackathons/overview/hackathonTimeline.tsx @@ -1,63 +1,87 @@ 'use client'; -interface TimelineEvent { +import { Calendar, CheckCircle2, Clock, Flag, Trophy } from 'lucide-react'; + +interface Timeline { event: string; date: string; } - interface HackathonTimelineProps { - events?: TimelineEvent[]; + events: Timeline[]; } -export function HackathonTimeline({ - events = [ - { event: 'Hackathon Launch', date: 'September 9, 2025' }, - { event: 'First Workshop: Getting Started', date: 'September 10' }, - { - event: 'Weekly Web Design & Deployment Sessions', - date: 'Sept 11 – Sep 28', - }, - { event: 'Submission Deadline', date: 'October 28, 2025 @ 11:59 PM UTC' }, - { event: 'Judging Period', date: 'November 9' }, - { event: 'Winners Announced', date: 'December 15, 2025' }, - ], -}: HackathonTimelineProps) { +export function HackathonTimeline({ events }: HackathonTimelineProps) { + if (!events.length) return null; + + function getEventIcon(event: Timeline['event']) { + const normalized = event.toLowerCase(); + + if (normalized.includes('start')) return Flag; + if (normalized.includes('deadline')) return Clock; + if (normalized.includes('judging')) return CheckCircle2; + if (normalized.includes('winner')) return Trophy; + + return Calendar; + } + return (
-
-

+
+

TIMELINE & KEY DATES

-
-
- - - - - - - - - {events.map((event, index) => ( - - - - - ))} - -
- EVENT - - DATE -
- {event.event} - - {event.date} -
+
+ {/* Vertical line */} +
+ + {/* Timeline events */} +
+ {events.map((event, index) => { + const Icon = getEventIcon(event.event); + return ( +
+ {/* Dot marker */} +
+
+ +
+
+ + {/* Content card */} +
+
+
+

+ {event.event} +

+
+ + {event.date} +
+
+ + {/* Event number badge */} +
+
+ {index + 1} +
+
+
+
+
+ ); + })} +
+
+ + {/* Bottom decoration */} + {/*
+
+ + All times in UTC unless specified +
+
*/}
); diff --git a/hooks/hackathon/use-timeline-events.ts b/hooks/hackathon/use-timeline-events.ts new file mode 100644 index 000000000..c107bd667 --- /dev/null +++ b/hooks/hackathon/use-timeline-events.ts @@ -0,0 +1,102 @@ +'use client'; +import { Hackathon } from '@/types/hackathon'; +import { useMemo } from 'react'; + +export interface TimelineEvent { + event: string; + date: string; + rawDate: Date; + type: 'start' | 'deadline' | 'judging' | 'winner' | 'end'; +} + +interface UseTimelineEventsOptions { + includeEndDate?: boolean; + dateFormat?: Intl.DateTimeFormatOptions; + deadlineFormat?: Intl.DateTimeFormatOptions; +} + +export const useTimelineEvents = ( + currentHackathon: Hackathon | null, + options: UseTimelineEventsOptions = {} +) => { + const { + includeEndDate = true, + dateFormat = { year: 'numeric', month: 'long', day: 'numeric' }, + deadlineFormat = { + year: 'numeric', + month: 'long', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + }, + } = options; + + return useMemo(() => { + if (!currentHackathon) return []; + + const events: TimelineEvent[] = []; + + // Add start date + if (currentHackathon.startDate) { + const startDate = new Date(currentHackathon.startDate); + events.push({ + event: 'Hackathon Starts', + date: startDate.toLocaleDateString('en-US', dateFormat), + rawDate: startDate, + type: 'start', + }); + } + + // Add submission deadline + if (currentHackathon.deadline) { + const deadline = new Date(currentHackathon.deadline); + events.push({ + event: 'Submission Deadline', + date: deadline.toLocaleDateString('en-US', deadlineFormat), + rawDate: deadline, + type: 'deadline', + }); + } + + // Add judging date + if (currentHackathon.judgingDate) { + const judgingDate = new Date(currentHackathon.judgingDate); + events.push({ + event: 'Judging Period', + date: judgingDate.toLocaleDateString('en-US', dateFormat), + rawDate: judgingDate, + type: 'judging', + }); + } + + // Add winner announcement date + if (currentHackathon.winnerAnnouncementDate) { + const winnerDate = new Date(currentHackathon.winnerAnnouncementDate); + events.push({ + event: 'Winners Announced', + date: winnerDate.toLocaleDateString('en-US', dateFormat), + rawDate: winnerDate, + type: 'winner', + }); + } + + // Add end date if enabled and different from winner announcement + if ( + includeEndDate && + currentHackathon.endDate && + currentHackathon.endDate !== currentHackathon.winnerAnnouncementDate + ) { + const endDate = new Date(currentHackathon.endDate); + events.push({ + event: 'Hackathon Ends', + date: endDate.toLocaleDateString('en-US', dateFormat), + rawDate: endDate, + type: 'end', + }); + } + + return events + .sort((a, b) => a.rawDate.getTime() - b.rawDate.getTime()) + .map(({ ...event }) => event); + }, [currentHackathon, includeEndDate, dateFormat, deadlineFormat]); +}; diff --git a/types/hackathon.ts b/types/hackathon.ts index 2b6ab0432..b3b2b3a9b 100644 --- a/types/hackathon.ts +++ b/types/hackathon.ts @@ -1,3 +1,23 @@ +import { + HackathonPhase, + PrizeTier, + SponsorPartner, +} from '@/lib/api/hackathons'; + +export interface JudgingCriteria { + title: string; + weight?: number; + description?: string; +} + +export interface Venue { + type: 'virtual' | 'physical'; + country?: string; + state?: string; + city?: string; + venueName?: string; + venueAddress?: string; +} export interface Participant { id: string | number; name: string; @@ -112,6 +132,28 @@ export interface HackathonParticipationSettings { tabVisibility?: HackathonTabVisibility; } +// export interface Hackathon { +// id: string; +// title: string; +// tagline: string; +// description: string; +// slug?: string; +// imageUrl: string; +// status: 'upcoming' | 'ongoing' | 'ended'; +// participants: number; +// totalPrizePool: string; +// deadline: string; +// categories: string[]; +// startDate: string; +// endDate: string; +// organizer: string; +// featured?: boolean; +// resources?: string[]; +// participantType?: ParticipantType; +// tabVisibility?: Pick; +// participation?: HackathonParticipationSettings; +// } + export interface Hackathon { id: string; title: string; @@ -132,4 +174,19 @@ export interface Hackathon { participantType?: ParticipantType; tabVisibility?: Pick; participation?: HackathonParticipationSettings; + orgId?: string; + prizeTiers?: PrizeTier[]; + teamMin?: number; + teamMax?: number; + venue?: Venue; + sponsors?: SponsorPartner[]; + socialLinks?: string[]; + contactEmail?: string; + telegram?: string; + discord?: string; + phases?: HackathonPhase[]; + timezone?: string; + judgingDate?: string; + winnerAnnouncementDate?: string; + criteria?: JudgingCriteria[]; }