Skip to content

Commit 71b8692

Browse files
committed
Merge branch 'main' of github.com:0xdevcollins/boundless
2 parents 516a3b4 + 3e87c4a commit 71b8692

7 files changed

Lines changed: 184 additions & 21 deletions

File tree

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

Lines changed: 62 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
'use client';
22

3-
import { useState, useEffect, useMemo } from 'react';
3+
import { useState, useEffect, useMemo, useCallback } from 'react';
44
import { useRouter, useSearchParams, useParams } from 'next/navigation';
55
import { useHackathonData } from '@/lib/providers/hackathonProvider';
66
import { useRegisterHackathon } from '@/hooks/hackathon/use-register-hackathon';
7+
import { useLeaveHackathon } from '@/hooks/hackathon/use-leave-hackathon';
78
import { RegisterHackathonModal } from '@/components/hackathons/overview/RegisterHackathonModal';
89

910
import { HackathonBanner } from '@/components/hackathons/hackathonBanner';
@@ -16,6 +17,9 @@ import { HackathonDiscussions } from '@/components/hackathons/discussion/comment
1617
import { TeamFormationTab } from '@/components/hackathons/team-formation/TeamFormationTab';
1718
import LoadingScreen from '@/components/landing-page/project/CreateProjectModal/LoadingScreen';
1819
import { useTimelineEvents } from '@/hooks/hackathon/use-timeline-events';
20+
import { toast } from 'sonner';
21+
import { Participant } from '@/lib/api/hackathons';
22+
// import { HackathonResources } from '@/components/hackathons/resources/resources';
1923

2024
export default function HackathonPage() {
2125
const router = useRouter();
@@ -28,14 +32,16 @@ export default function HackathonPage() {
2832
submissions,
2933
loading,
3034
setCurrentHackathon,
35+
refreshCurrentHackathon,
3136
} = useHackathonData();
37+
3238
const timeline_Events = useTimelineEvents(currentHackathon, {
3339
includeEndDate: false,
3440
dateFormat: { month: 'short', day: 'numeric', year: 'numeric' },
3541
});
42+
3643
const hackathonTabs = useMemo(() => {
3744
const hasParticipants = participants.length > 0;
38-
// const hasSubmissions = submissions.filter(p => p.status === 'Approved').length > 0;
3945

4046
const tabs = [
4147
{ id: 'overview', label: 'Overview' },
@@ -81,19 +87,37 @@ export default function HackathonPage() {
8187
const [activeTab, setActiveTab] = useState('overview');
8288
const [showRegisterModal, setShowRegisterModal] = useState(false);
8389

84-
// Registration logic
85-
const { isRegistered, hasSubmitted, checkStatus } = useRegisterHackathon({
90+
// Function to refresh all hackathon data
91+
const refreshHackathonData = useCallback(async () => {
92+
if (hackathonId && refreshCurrentHackathon) {
93+
await refreshCurrentHackathon();
94+
}
95+
}, [hackathonId, refreshCurrentHackathon]);
96+
97+
// Registration logic - use the updated hook with setters
98+
const {
99+
isRegistered,
100+
hasSubmitted,
101+
checkStatus,
102+
// isChecking,
103+
setIsRegistered,
104+
setParticipant,
105+
} = useRegisterHackathon({
86106
hackathonSlugOrId: hackathonId,
87-
organizationId: undefined, // organizationId not available in currentHackathon type
107+
organizationId: undefined,
88108
autoCheck: !!hackathonId,
89109
});
90110

111+
// Leave hackathon logic
112+
const { isLeaving, leave: leaveHackathon } = useLeaveHackathon({
113+
hackathonSlugOrId: hackathonId,
114+
organizationId: undefined,
115+
});
116+
91117
const isEnded = useMemo(() => {
92118
if (!currentHackathon?.deadline) return false;
93-
94119
const deadline = new Date(currentHackathon.deadline);
95120
const now = new Date();
96-
97121
return now > deadline;
98122
}, [currentHackathon?.deadline]);
99123

@@ -111,8 +135,33 @@ export default function HackathonPage() {
111135
setShowRegisterModal(true);
112136
};
113137

114-
const handleRegisterSuccess = () => {
115-
checkStatus();
138+
const handleLeaveClick = async () => {
139+
try {
140+
setIsRegistered(false);
141+
setParticipant(null);
142+
143+
await leaveHackathon();
144+
145+
refreshHackathonData();
146+
147+
router.push('?tab=overview');
148+
} catch (error) {
149+
const errorMessage =
150+
error instanceof Error ? error.message : 'Failed to leave hackathon';
151+
toast.error(errorMessage);
152+
setIsRegistered(true);
153+
checkStatus();
154+
}
155+
};
156+
157+
const handleRegisterSuccess = async (participantData: Participant) => {
158+
// IMMEDIATELY update state with the returned participant data
159+
setIsRegistered(true);
160+
setParticipant(participantData);
161+
162+
// Refresh hackathon data in background (participants count)
163+
await refreshHackathonData();
164+
116165
router.push('?tab=submission');
117166
};
118167

@@ -151,6 +200,7 @@ export default function HackathonPage() {
151200
if (loading) {
152201
return <LoadingScreen />;
153202
}
203+
154204
if (!currentHackathon) {
155205
return (
156206
<div className='flex min-h-screen items-center justify-center'>
@@ -187,6 +237,8 @@ export default function HackathonPage() {
187237
registrationDeadline={currentHackathon.registrationDeadline}
188238
isTeamFormationEnabled={isTeamFormationEnabled}
189239
onJoinClick={handleJoinClick}
240+
onLeaveClick={handleLeaveClick}
241+
isLeaving={isLeaving}
190242
onSubmitClick={handleSubmitClick}
191243
onViewSubmissionClick={handleViewSubmissionClick}
192244
onFindTeamClick={handleFindTeamClick}
@@ -199,7 +251,7 @@ export default function HackathonPage() {
199251
onOpenChange={setShowRegisterModal}
200252
hackathonSlugOrId={hackathonId}
201253
organizationId={undefined}
202-
onSuccess={handleRegisterSuccess}
254+
onSuccess={handleRegisterSuccess} // Now passes participant data
203255
participantType={
204256
(currentHackathon?.participantType as
205257
| 'team'
@@ -232,9 +284,6 @@ export default function HackathonPage() {
232284
{activeTab === 'participants' && participants.length > 0 && (
233285
<HackathonParticipants />
234286
)}
235-
{/* {activeTab === 'resources' && (
236-
<HackathonResources hackathonSlugOrId={hackathonId} />
237-
)} */}
238287

239288
{activeTab === 'submission' && (
240289
<SubmissionTab

components/hackathons/hackathonBanner.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ interface HackathonBannerProps {
3333
onSubmitClick?: () => void;
3434
onViewSubmissionClick?: () => void;
3535
onFindTeamClick?: () => void;
36+
onLeaveClick?: () => void;
37+
isLeaving?: boolean;
3638
}
3739

3840
interface TimeRemaining {
@@ -125,10 +127,12 @@ export function HackathonBanner({
125127
isTeamFormationEnabled = false,
126128
registrationDeadlinePolicy,
127129
registrationDeadline,
130+
isLeaving,
128131
onJoinClick,
129132
onSubmitClick,
130133
onViewSubmissionClick,
131134
onFindTeamClick,
135+
onLeaveClick,
132136
}: HackathonBannerProps) {
133137
const [timeRemaining, setTimeRemaining] = useState<TimeRemaining>({
134138
days: 0,
@@ -381,11 +385,14 @@ export function HackathonBanner({
381385
if (isRegistered) {
382386
return (
383387
<Button
384-
onClick={onJoinClick}
385-
variant='outline'
386-
className='border-red-500/50 text-red-400 hover:bg-red-500/20'
388+
onClick={onLeaveClick}
389+
disabled={isLeaving}
390+
className='border border-red-500/40 bg-red-500 font-semibold text-white hover:bg-red-500/50 hover:text-white'
391+
variant='ghost'
387392
>
388-
Leave Hackathon
393+
<Calendar className='mr-2 h-4 w-4 text-white' />
394+
{isLeaving ? 'Leaving...' : 'Leave Hackathon'}
395+
<ArrowRight className='ml-2 h-4 w-4 text-red-400' />
389396
</Button>
390397
);
391398
}

components/hackathons/overview/RegisterHackathonModal.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ interface RegisterHackathonModalProps {
5555
hackathonSlugOrId: string;
5656
organizationId?: string;
5757
participantType: 'individual' | 'team' | 'team_or_individual';
58-
onSuccess?: () => void;
58+
onSuccess?: (participantData?: any) => void;
5959
}
6060

6161
export function RegisterHackathonModal({
@@ -107,7 +107,7 @@ export function RegisterHackathonModal({
107107

108108
const onSubmit = async (data: RegisterFormData) => {
109109
try {
110-
await registerForHackathon({
110+
const participantData = await registerForHackathon({
111111
participationType: data.participationType,
112112
teamName: data.participationType === 'team' ? data.teamName : undefined,
113113
teamMembers:
@@ -119,7 +119,9 @@ export function RegisterHackathonModal({
119119
form.reset();
120120
setTeamMembers([]);
121121
setTeamMemberEmail('');
122-
onSuccess?.();
122+
123+
// Pass the participant data to onSuccess
124+
onSuccess?.(participantData);
123125
} catch {
124126
// Error handled in hook
125127
}

components/organization/OrganizationContent.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ export default function OrganizationContent() {
384384
</>
385385
) : (
386386
<div className='flex min-h-[60vh] items-center justify-center'>
387-
<div className='w-full max-w-2xl text-center'>
387+
<div className='mt-10 w-full max-w-2xl text-center'>
388388
{/* Decorative background */}
389389
<div className='relative mx-auto mb-8 h-48 w-48'>
390390
<div className='from-primary/20 absolute inset-0 animate-pulse rounded-full bg-gradient-to-br to-purple-500/20 blur-3xl'></div>
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
'use client';
2+
3+
import { useState, useCallback } from 'react';
4+
import { leaveHackathon } from '@/lib/api/hackathons';
5+
import { useAuthStatus } from '@/hooks/use-auth';
6+
import { toast } from 'sonner';
7+
8+
interface UseLeaveHackathonOptions {
9+
hackathonSlugOrId: string;
10+
organizationId?: string;
11+
}
12+
13+
export function useLeaveHackathon({
14+
hackathonSlugOrId,
15+
organizationId,
16+
}: UseLeaveHackathonOptions) {
17+
const { isAuthenticated } = useAuthStatus();
18+
const [isLeaving, setIsLeaving] = useState(false);
19+
const [error, setError] = useState<string | null>(null);
20+
21+
const leave = useCallback(async () => {
22+
if (!isAuthenticated) {
23+
toast.error('Please sign in to leave hackathons');
24+
throw new Error('Authentication required');
25+
}
26+
27+
if (!hackathonSlugOrId) {
28+
toast.error('Hackathon ID is required');
29+
throw new Error('Hackathon ID is required');
30+
}
31+
32+
setIsLeaving(true);
33+
setError(null);
34+
35+
try {
36+
const response = await leaveHackathon(hackathonSlugOrId, organizationId);
37+
38+
if (response.success) {
39+
toast.success('Successfully left the hackathon');
40+
return response.data;
41+
} else {
42+
throw new Error(response.message || 'Failed to leave hackathon');
43+
}
44+
} catch (err) {
45+
const errorMessage =
46+
err instanceof Error ? err.message : 'Failed to leave hackathon';
47+
setError(errorMessage);
48+
toast.error(errorMessage);
49+
throw err;
50+
} finally {
51+
setIsLeaving(false);
52+
}
53+
}, [hackathonSlugOrId, organizationId, isAuthenticated]);
54+
55+
return {
56+
isLeaving,
57+
error,
58+
leave,
59+
};
60+
}

hooks/hackathon/use-register-hackathon.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,13 @@ export function useRegisterHackathon({
2727
const [isRegistering, setIsRegistering] = useState(false);
2828
const [isChecking, setIsChecking] = useState(false);
2929
const [error, setError] = useState<string | null>(null);
30+
const [hasCheckedInitially, setHasCheckedInitially] = useState(false);
3031

3132
const checkStatus = useCallback(async () => {
3233
if (!isAuthenticated || !hackathonSlugOrId) {
3334
setIsRegistered(false);
3435
setParticipant(null);
36+
setHasCheckedInitially(true);
3537
return;
3638
}
3739

@@ -51,6 +53,7 @@ export function useRegisterHackathon({
5153
setIsRegistered(false);
5254
setParticipant(null);
5355
}
56+
setHasCheckedInitially(true);
5457
} catch (err) {
5558
const errorMessage =
5659
err instanceof Error
@@ -59,6 +62,7 @@ export function useRegisterHackathon({
5962
setError(errorMessage);
6063
setIsRegistered(false);
6164
setParticipant(null);
65+
setHasCheckedInitially(true);
6266
} finally {
6367
setIsChecking(false);
6468
}
@@ -87,6 +91,7 @@ export function useRegisterHackathon({
8791
);
8892

8993
if (response.success && response.data) {
94+
// IMMEDIATELY update state - don't wait for checkStatus
9095
setIsRegistered(true);
9196
setParticipant(response.data);
9297
toast.success('Successfully registered for hackathon!');
@@ -116,6 +121,7 @@ export function useRegisterHackathon({
116121
} else if (!isAuthenticated) {
117122
setIsRegistered(false);
118123
setParticipant(null);
124+
setHasCheckedInitially(true);
119125
}
120126
}, [autoCheck, isAuthenticated, hackathonSlugOrId, checkStatus]);
121127

@@ -127,6 +133,10 @@ export function useRegisterHackathon({
127133
error,
128134
register,
129135
checkStatus,
136+
hasCheckedInitially,
137+
// Expose setters for immediate updates
138+
setIsRegistered,
139+
setParticipant,
130140
hasSubmitted: participant?.submission?.status === 'submitted',
131141
};
132142
}

0 commit comments

Comments
 (0)