Skip to content

Commit de31da1

Browse files
authored
Feat/hackthon features (#340)
* feat: implemented hackathon features * fix: update
1 parent 883868b commit de31da1

119 files changed

Lines changed: 12569 additions & 3032 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.

app/(landing)/hackathons/page.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
import React from 'react';
21
import { Metadata } from 'next';
32
import { generatePageMetadata } from '@/lib/metadata';
3+
import HackathonsPageHero from '@/components/hackathons/HackathonsPageHero';
4+
import HackathonsPage from '@/components/hackathons/HackathonsPage';
45

56
export const metadata: Metadata = generatePageMetadata('hackathons');
67

7-
const HackathonsPage = () => {
8+
export default function HackathonsPageRoute() {
89
return (
9-
<div className='mx-auto mt-10 max-w-[1440px] px-5 py-5 text-center text-4xl font-bold text-white md:px-[50px] lg:px-[100px]'>
10-
Hackathons Page
10+
<div className='relative mx-auto min-h-screen max-w-[1440px] px-5 py-5 md:px-[50px] lg:px-[100px]'>
11+
<HackathonsPageHero />
12+
<HackathonsPage />
1113
</div>
1214
);
13-
};
14-
15-
export default HackathonsPage;
15+
}
Lines changed: 135 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,151 @@
11
'use client';
22

3+
import { useEffect, useState, useCallback } from 'react';
34
import MetricsCard from '@/components/organization/cards/MetricsCard';
45
import JudgingParticipant from '@/components/organization/cards/JudgingParticipant';
56
import { useParams } from 'next/navigation';
7+
import {
8+
getJudgingSubmissions,
9+
type JudgingSubmission,
10+
} from '@/lib/api/hackathons';
11+
import { Loader2 } from 'lucide-react';
12+
import { toast } from 'sonner';
13+
import { Button } from '@/components/ui/button';
614

715
export default function JudgingPage() {
816
const params = useParams();
9-
void params.id;
10-
void params.hackathonId;
17+
const organizationId = params.id as string;
18+
const hackathonId = params.hackathonId as string;
19+
20+
const [submissions, setSubmissions] = useState<JudgingSubmission[]>([]);
21+
const [isLoading, setIsLoading] = useState(true);
22+
const [page, setPage] = useState(1);
23+
const [pagination, setPagination] = useState({
24+
currentPage: 1,
25+
totalPages: 1,
26+
totalItems: 0,
27+
itemsPerPage: 10,
28+
hasNext: false,
29+
hasPrev: false,
30+
});
31+
32+
const fetchSubmissions = useCallback(
33+
async (pageNum = 1) => {
34+
if (!organizationId || !hackathonId) return;
35+
36+
setIsLoading(true);
37+
try {
38+
const response = await getJudgingSubmissions(
39+
organizationId,
40+
hackathonId,
41+
pageNum,
42+
10
43+
);
44+
45+
if (response.success) {
46+
setSubmissions(response.data);
47+
setPagination(response.pagination);
48+
setPage(pageNum);
49+
}
50+
} catch {
51+
toast.error('Failed to load submissions');
52+
} finally {
53+
setIsLoading(false);
54+
}
55+
},
56+
[organizationId, hackathonId]
57+
);
58+
59+
useEffect(() => {
60+
fetchSubmissions();
61+
}, [fetchSubmissions]);
62+
63+
const handleSuccess = () => {
64+
fetchSubmissions(page);
65+
};
66+
67+
// Calculate statistics
68+
const totalSubmissions = pagination.totalItems;
69+
const averageScore =
70+
submissions.length > 0
71+
? submissions.reduce((sum, sub) => {
72+
return sum + (sub.averageScore || 0);
73+
}, 0) / submissions.length
74+
: 0;
75+
const totalJudges = new Set(
76+
submissions.flatMap(sub => sub.scores.map(score => score.judge._id))
77+
).size;
1178

1279
return (
1380
<div className='bg-background min-h-screen space-y-4 p-8 text-white'>
1481
<div className='flex gap-4'>
15-
<MetricsCard />
16-
<MetricsCard />
17-
</div>
18-
<div className='flex flex-col gap-4'>
19-
<JudgingParticipant isSubmitted={true} />
20-
<JudgingParticipant isSubmitted={true} />
21-
<JudgingParticipant isSubmitted={true} />
22-
<JudgingParticipant isSubmitted={true} />
82+
<MetricsCard
83+
title='Shortlisted Submissions'
84+
value={totalSubmissions}
85+
subtitle={`${submissions.length} displayed`}
86+
/>
87+
<MetricsCard
88+
title='Average Score'
89+
value={averageScore > 0 ? averageScore.toFixed(2) : 'N/A'}
90+
subtitle={
91+
submissions.length > 0 ? 'Across all submissions' : undefined
92+
}
93+
/>
94+
<MetricsCard
95+
title='Active Judges'
96+
value={totalJudges}
97+
subtitle='Judges who have graded'
98+
/>
2399
</div>
100+
101+
{isLoading ? (
102+
<div className='flex items-center justify-center py-12'>
103+
<Loader2 className='h-8 w-8 animate-spin text-gray-400' />
104+
</div>
105+
) : submissions.length === 0 ? (
106+
<div className='bg-background/8 rounded-lg border border-gray-900 p-8 text-center text-gray-400'>
107+
No shortlisted submissions found
108+
</div>
109+
) : (
110+
<>
111+
<div className='flex flex-col gap-4'>
112+
{submissions.map(submission => (
113+
<JudgingParticipant
114+
key={submission.participant._id}
115+
submission={submission}
116+
organizationId={organizationId}
117+
hackathonId={hackathonId}
118+
onSuccess={handleSuccess}
119+
/>
120+
))}
121+
</div>
122+
123+
{/* Pagination */}
124+
{pagination.totalPages > 1 && (
125+
<div className='flex items-center justify-center gap-2 pt-4'>
126+
<Button
127+
variant='outline'
128+
onClick={() => fetchSubmissions(page - 1)}
129+
disabled={!pagination.hasPrev || isLoading}
130+
className='border-gray-700 text-gray-400 hover:bg-gray-800'
131+
>
132+
Previous
133+
</Button>
134+
<span className='text-sm text-gray-400'>
135+
Page {pagination.currentPage} of {pagination.totalPages}
136+
</span>
137+
<Button
138+
variant='outline'
139+
onClick={() => fetchSubmissions(page + 1)}
140+
disabled={!pagination.hasNext || isLoading}
141+
className='border-gray-700 text-gray-400 hover:bg-gray-800'
142+
>
143+
Next
144+
</Button>
145+
</div>
146+
)}
147+
</>
148+
)}
24149
</div>
25150
);
26151
}

0 commit comments

Comments
 (0)