Skip to content

Commit 1bcdd8b

Browse files
committed
feat: enhance hackathon registration and overview features
- Removed unused image domain from next.config.ts. - Integrated registration modal into HackathonPage for improved user experience. - Updated HackathonBanner to include action buttons for registration and submission management. - Refactored HackathonOverview to utilize markdown for content rendering and improved loading/error handling. - Added validation utilities for hackathon step data to ensure meaningful input during creation.
1 parent 3b9dee3 commit 1bcdd8b

8 files changed

Lines changed: 760 additions & 349 deletions

File tree

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

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import { useState, useEffect, useMemo } from 'react';
44
import { useRouter, useSearchParams, useParams } from 'next/navigation';
55
import { useHackathonData } from '@/lib/providers/hackathonProvider';
6+
import { useRegisterHackathon } from '@/hooks/hackathon/use-register-hackathon';
7+
import { RegisterHackathonModal } from '@/components/hackathons/overview/RegisterHackathonModal';
68

79
import { HackathonBanner } from '@/components/hackathons/hackathonBanner';
810
import { HackathonNavTabs } from '@/components/hackathons/hackathonNavTabs';
@@ -64,6 +66,47 @@ export default function HackathonPage() {
6466

6567
const hackathonId = params.slug as string;
6668
const [activeTab, setActiveTab] = useState('overview');
69+
const [showRegisterModal, setShowRegisterModal] = useState(false);
70+
71+
// Registration logic
72+
const { isRegistered, hasSubmitted, checkStatus } = useRegisterHackathon({
73+
hackathonSlugOrId: hackathonId,
74+
organizationId: undefined, // organizationId not available in currentHackathon type
75+
autoCheck: !!hackathonId,
76+
});
77+
78+
const isEnded = currentHackathon?.status === 'ended';
79+
80+
// Check if team formation is available
81+
const isTeamHackathon =
82+
currentHackathon?.participation?.participantType === 'team' ||
83+
currentHackathon?.participation?.participantType === 'team_or_individual';
84+
85+
const isTeamFormationEnabled =
86+
isTeamHackathon &&
87+
currentHackathon?.participation?.tabVisibility?.joinATeamTab !== false;
88+
89+
// Button handlers
90+
const handleJoinClick = () => {
91+
setShowRegisterModal(true);
92+
};
93+
94+
const handleRegisterSuccess = () => {
95+
checkStatus();
96+
router.push('?tab=submission');
97+
};
98+
99+
const handleSubmitClick = () => {
100+
router.push('?tab=submission');
101+
};
102+
103+
const handleViewSubmissionClick = () => {
104+
router.push('?tab=submission');
105+
};
106+
107+
const handleFindTeamClick = () => {
108+
router.push('?tab=team-formation');
109+
};
67110

68111
useEffect(() => {
69112
if (hackathonId) {
@@ -115,9 +158,29 @@ export default function HackathonPage() {
115158
participants={currentHackathon.participants}
116159
totalPrizePool={currentHackathon.totalPrizePool}
117160
imageUrl={currentHackathon.imageUrl}
118-
startDate={currentHackathon.startDate} // if upcoming
161+
startDate={currentHackathon.startDate}
162+
endDate={currentHackathon.endDate}
163+
isRegistered={isRegistered}
164+
hasSubmitted={hasSubmitted}
165+
isEnded={isEnded}
166+
isTeamFormationEnabled={isTeamFormationEnabled}
167+
onJoinClick={handleJoinClick}
168+
onSubmitClick={handleSubmitClick}
169+
onViewSubmissionClick={handleViewSubmissionClick}
170+
onFindTeamClick={handleFindTeamClick}
119171
/>
120172

173+
{/* Registration Modal */}
174+
{hackathonId && (
175+
<RegisterHackathonModal
176+
open={showRegisterModal}
177+
onOpenChange={setShowRegisterModal}
178+
hackathonSlugOrId={hackathonId}
179+
organizationId={undefined}
180+
onSuccess={handleRegisterSuccess}
181+
/>
182+
)}
183+
121184
{/* Tabs */}
122185
<HackathonNavTabs
123186
tabs={hackathonTabs}

components/hackathons/hackathonBanner.tsx

Lines changed: 110 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
import { Card } from '@/components/ui/card';
44
import { Badge } from '@/components/ui/badge';
5+
import { Button } from '@/components/ui/button';
56
import { formatDate } from '@/lib/utils';
6-
import { useEffect, useState } from 'react';
7+
import { useEffect, useState, useRef } from 'react';
8+
import { FileText, Users, ArrowRight } from 'lucide-react';
79
// import { sanitizeHtml } from '@/lib/utils/renderHtml';
810

911
interface HackathonBannerProps {
@@ -17,6 +19,15 @@ interface HackathonBannerProps {
1719
status?: string;
1820
participants?: number;
1921
totalPrizePool?: string;
22+
// Action buttons
23+
isRegistered?: boolean;
24+
hasSubmitted?: boolean;
25+
isEnded?: boolean;
26+
isTeamFormationEnabled?: boolean;
27+
onJoinClick?: () => void;
28+
onSubmitClick?: () => void;
29+
onViewSubmissionClick?: () => void;
30+
onFindTeamClick?: () => void;
2031
}
2132

2233
interface TimeRemaining {
@@ -59,6 +70,47 @@ function formatCountdown(time: TimeRemaining): string {
5970
}
6071
}
6172

73+
const CategoriesDisplay = ({
74+
categoriesList,
75+
}: {
76+
categoriesList: string[];
77+
}) => {
78+
const ref = useRef<HTMLDivElement>(null);
79+
const [showEllipsis, setShowEllipsis] = useState(false);
80+
81+
useEffect(() => {
82+
const el = ref.current;
83+
if (!el) return;
84+
85+
const check = () => setShowEllipsis(el.scrollWidth > el.clientWidth);
86+
87+
check();
88+
89+
window.addEventListener('resize', check);
90+
return () => window.removeEventListener('resize', check);
91+
}, [categoriesList]);
92+
93+
return (
94+
<div className={`relative flex items-center overflow-hidden`}>
95+
<div ref={ref} className='scrollbar-hide flex gap-1.5 overflow-x-auto'>
96+
{categoriesList.map((cat, i) => (
97+
<span
98+
key={i}
99+
className='rounded-md bg-neutral-800/70 px-2 py-0.5 text-[11px] font-medium whitespace-nowrap text-gray-300'
100+
>
101+
{cat}
102+
</span>
103+
))}
104+
</div>
105+
{showEllipsis && (
106+
<div className='pointer-events-none absolute top-0 right-0 bottom-0 flex w-6 items-center justify-end bg-gradient-to-l from-[#030303] via-[#030303]/80 to-transparent pr-1'>
107+
<span className='text-xs font-medium text-gray-500'>...</span>
108+
</div>
109+
)}
110+
</div>
111+
);
112+
};
113+
62114
export function HackathonBanner({
63115
title,
64116
tagline,
@@ -70,6 +122,14 @@ export function HackathonBanner({
70122
status,
71123
participants,
72124
totalPrizePool,
125+
isRegistered = false,
126+
hasSubmitted = false,
127+
isEnded = false,
128+
isTeamFormationEnabled = false,
129+
onJoinClick,
130+
onSubmitClick,
131+
onViewSubmissionClick,
132+
onFindTeamClick,
73133
}: HackathonBannerProps) {
74134
const [timeRemaining, setTimeRemaining] = useState<TimeRemaining>({
75135
days: 0,
@@ -203,9 +263,9 @@ export function HackathonBanner({
203263
};
204264

205265
return (
206-
<Card className='relative w-full overflow-hidden rounded-none border-0 p-0 shadow-none'>
266+
<Card className='relative w-full overflow-hidden rounded-none border-0 bg-transparent p-0 shadow-none'>
207267
<div
208-
className='relative h-64 md:h-80 lg:h-96'
268+
className='relative h-64 rounded-4xl md:h-80 lg:h-96'
209269
style={{
210270
backgroundImage: imageUrl ? `url(${imageUrl})` : 'none',
211271
backgroundSize: 'cover',
@@ -260,23 +320,62 @@ export function HackathonBanner({
260320
)}
261321
</div>
262322

263-
{/* Bottom section: Deadline/Start/Ended and categories */}
323+
{/* Bottom section: Deadline/Start/Ended, categories, and action buttons */}
264324
<div className='flex flex-col gap-2 md:gap-3'>
265325
{renderDateSection()}
266326
{categories && categories.length > 0 && (
267327
<div className='flex flex-wrap items-center gap-2'>
268328
<span className='text-sm font-semibold text-[#a7f950]'>
269329
Categories:
270330
</span>
271-
{categories.map(category => (
272-
<Badge
273-
key={category}
331+
<CategoriesDisplay categoriesList={categories} />
332+
</div>
333+
)}
334+
335+
{/* Action Buttons */}
336+
{!isEnded && (
337+
<div className='mt-2 flex flex-wrap items-center gap-3'>
338+
{!isRegistered && onJoinClick && (
339+
<Button
340+
onClick={onJoinClick}
341+
className='bg-[#a7f950] font-semibold text-black hover:bg-[#8fd93f]'
342+
>
343+
Join Hackathon
344+
<ArrowRight className='ml-2 h-4 w-4' />
345+
</Button>
346+
)}
347+
348+
{isRegistered && !hasSubmitted && onSubmitClick && (
349+
<Button
350+
onClick={onSubmitClick}
351+
className='bg-[#a7f950] font-semibold text-black hover:bg-[#8fd93f]'
352+
>
353+
<FileText className='mr-2 h-4 w-4' />
354+
Submit Project
355+
<ArrowRight className='ml-2 h-4 w-4' />
356+
</Button>
357+
)}
358+
359+
{isRegistered && hasSubmitted && onViewSubmissionClick && (
360+
<Button
361+
onClick={onViewSubmissionClick}
362+
className='bg-[#a7f950] font-semibold text-black hover:bg-[#8fd93f]'
363+
>
364+
<FileText className='mr-2 h-4 w-4' />
365+
View Submission
366+
</Button>
367+
)}
368+
369+
{isRegistered && isTeamFormationEnabled && onFindTeamClick && (
370+
<Button
371+
onClick={onFindTeamClick}
274372
variant='outline'
275-
className='border-gray-400 text-xs text-gray-100'
373+
className='border-blue-500/50 font-semibold text-blue-400 hover:bg-blue-500/20'
276374
>
277-
{category}
278-
</Badge>
279-
))}
375+
<Users className='mr-2 h-4 w-4' />
376+
Find Team
377+
</Button>
378+
)}
280379
</div>
281380
)}
282381
</div>

0 commit comments

Comments
 (0)