Skip to content

Commit 846fefd

Browse files
Pr 486 (#488) (#489)
* feat: Implement comprehensive hackathon detail page with new header, sidebar, and tabbed content sections including teams, announcements, winners, participants, and resources. * fix: update coderabit * feat: Enhance hackathon team invitation flow, recruitment post management, and dynamic tab visibility. * feat: Integrate AuthModalProvider for authentication handling and enhance messaging features across hackathon components * fix: fix pagination issues in particiapants and submissions page for organizers * fix: remove any type --------- Co-authored-by: Collins Ikechukwu <collinschristroa@gmail.com>
1 parent 7af9582 commit 846fefd

12 files changed

Lines changed: 230 additions & 122 deletions

File tree

app/(landing)/organizations/[id]/hackathons/[hackathonId]/participants/page.tsx

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ interface FilterState {
4343

4444
const mapFiltersToParams = (filters: FilterState, searchOverride?: string) => ({
4545
search: searchOverride !== undefined ? searchOverride : filters.search,
46-
status: filters.status === 'all' ? undefined : filters.status,
47-
type: filters.type === 'all' ? undefined : filters.type,
46+
status: filters.status === 'all' ? undefined : filters.status.toUpperCase(),
47+
type: filters.type === 'all' ? undefined : filters.type.toUpperCase(),
4848
});
4949

5050
const ParticipantsPage: React.FC = () => {
@@ -228,9 +228,45 @@ const ParticipantsPage: React.FC = () => {
228228
}
229229
};
230230

231+
// Frontend-side filtering as per requirement
232+
const filteredParticipants = useMemo(() => {
233+
return participants.filter(participant => {
234+
// Search: filter by name or username
235+
const search = filters.search.toLowerCase();
236+
const matchesSearch = search
237+
? (participant.user?.profile?.name || '')
238+
.toLowerCase()
239+
.includes(search) ||
240+
(participant.user?.profile?.username || '')
241+
.toLowerCase()
242+
.includes(search)
243+
: true;
244+
245+
// Status: filter by participant.submission.status
246+
// Filter values are 'submitted', 'not_submitted', etc.
247+
// ParticipantSubmission.status values are 'submitted', 'shortlisted', etc.
248+
const matchesStatus =
249+
filters.status === 'all'
250+
? true
251+
: filters.status === 'not_submitted'
252+
? !participant.submission
253+
: participant.submission?.status?.toLowerCase() ===
254+
filters.status.toLowerCase();
255+
256+
// Type: filter by participant.participationType
257+
const matchesType =
258+
filters.type === 'all'
259+
? true
260+
: participant.participationType?.toLowerCase() ===
261+
filters.type.toLowerCase();
262+
263+
return matchesSearch && matchesStatus && matchesType;
264+
});
265+
}, [participants, filters.search, filters.status, filters.type]);
266+
231267
// Mock table instance for DataTablePagination
232268
const table = useReactTable({
233-
data: participants,
269+
data: filteredParticipants,
234270
columns: [], // Not used for rendering here
235271
getCoreRowModel: getCoreRowModel(),
236272
manualPagination: true,
@@ -316,15 +352,15 @@ const ParticipantsPage: React.FC = () => {
316352
<div className='space-y-6'>
317353
{view === 'table' ? (
318354
<ParticipantsTable
319-
data={participants}
355+
data={filteredParticipants}
320356
loading={participantsLoading}
321357
onReview={handleReview}
322358
onViewTeam={handleViewTeam}
323359
onGrade={handleGrade}
324360
/>
325361
) : (
326362
<ParticipantsGrid
327-
data={participants}
363+
data={filteredParticipants}
328364
loading={participantsLoading}
329365
onReview={handleReview}
330366
onViewTeam={handleViewTeam}

app/(landing)/organizations/[id]/hackathons/[hackathonId]/submissions/page.tsx

Lines changed: 77 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,30 @@
11
'use client';
22

33
import { useParams } from 'next/navigation';
4-
import { useEffect, useState } from 'react';
4+
import { useEffect, useState, useMemo } from 'react';
55
import { Loader2, AlertCircle } from 'lucide-react';
66
import { useOrganizerSubmissions } from '@/hooks/hackathon/use-organizer-submissions';
77
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
88
import { AuthGuard } from '@/components/auth';
99
import Loading from '@/components/Loading';
1010
import { SubmissionsManagement } from '@/components/organization/hackathons/submissions/SubmissionsManagement';
1111
import { authClient } from '@/lib/auth-client';
12-
import { getHackathon, type Hackathon } from '@/lib/api/hackathons';
12+
import {
13+
getHackathon,
14+
type Hackathon,
15+
type ParticipantSubmission,
16+
} from '@/lib/api/hackathons';
1317
import { reportError } from '@/lib/error-reporting';
18+
import { useReactTable, getCoreRowModel } from '@tanstack/react-table';
19+
import { DataTablePagination } from '@/components/ui/data-table-pagination';
1420

1521
export default function SubmissionsPage() {
1622
const params = useParams();
1723
const hackathonId = params.hackathonId as string;
1824
const organizationId = params.id as string;
1925

2026
const {
21-
submissions,
27+
submissions: allSubmissions,
2228
pagination,
2329
filters,
2430
loading,
@@ -27,14 +33,14 @@ export default function SubmissionsPage() {
2733
updateFilters,
2834
goToPage,
2935
refresh,
36+
updateLimit,
3037
} = useOrganizerSubmissions(hackathonId);
3138

3239
const [currentUserId, setCurrentUserId] = useState<string | null>(null);
3340
const [hackathon, setHackathon] = useState<Hackathon | null>(null);
3441

3542
useEffect(() => {
3643
if (hackathonId) {
37-
fetchSubmissions();
3844
const fetchHackathonDetails = async () => {
3945
try {
4046
const res = await getHackathon(hackathonId);
@@ -50,7 +56,7 @@ export default function SubmissionsPage() {
5056
};
5157
fetchHackathonDetails();
5258
}
53-
}, [hackathonId, fetchSubmissions]);
59+
}, [hackathonId]);
5460

5561
useEffect(() => {
5662
const fetchSession = async () => {
@@ -66,6 +72,52 @@ export default function SubmissionsPage() {
6672
fetchSession();
6773
}, []);
6874

75+
// Frontend-side filtering
76+
const filteredSubmissions = useMemo(() => {
77+
return allSubmissions.filter(sub => {
78+
const search = filters.search?.toLowerCase() || '';
79+
const matchesSearch = search
80+
? (sub.projectName || '').toLowerCase().includes(search) ||
81+
(sub.participant?.name || '').toLowerCase().includes(search) ||
82+
(sub.participant?.username || '').toLowerCase().includes(search)
83+
: true;
84+
85+
const matchesStatus = !filters.status || sub.status === filters.status;
86+
87+
const matchesType =
88+
!filters.type || sub.participationType === filters.type;
89+
90+
return matchesSearch && matchesStatus && matchesType;
91+
});
92+
}, [allSubmissions, filters.search, filters.status, filters.type]);
93+
94+
const table = useReactTable({
95+
data: filteredSubmissions,
96+
columns: [],
97+
getCoreRowModel: getCoreRowModel(),
98+
manualPagination: true,
99+
pageCount: pagination.totalPages,
100+
state: {
101+
pagination: {
102+
pageIndex: pagination.page - 1,
103+
pageSize: pagination.limit,
104+
},
105+
},
106+
onPaginationChange: updater => {
107+
if (typeof updater === 'function') {
108+
const newState = updater({
109+
pageIndex: pagination.page - 1,
110+
pageSize: pagination.limit,
111+
});
112+
if (newState.pageSize !== pagination.limit) {
113+
updateLimit(newState.pageSize);
114+
} else {
115+
goToPage(newState.pageIndex + 1);
116+
}
117+
}
118+
},
119+
});
120+
69121
if (error) {
70122
return (
71123
<div className='flex min-h-screen items-center justify-center bg-black p-6'>
@@ -100,27 +152,33 @@ export default function SubmissionsPage() {
100152

101153
{/* Main Content */}
102154
<div className='mx-auto max-w-7xl px-6 py-12 sm:px-8 lg:px-12'>
103-
{loading && submissions.length === 0 ? (
155+
{loading && allSubmissions.length === 0 ? (
104156
<div className='flex items-center justify-center py-20'>
105157
<div className='flex flex-col items-center gap-3'>
106158
<Loader2 className='h-6 w-6 animate-spin text-gray-400' />
107159
<p className='text-sm text-gray-500'>Loading submissions...</p>
108160
</div>
109161
</div>
110162
) : (
111-
<SubmissionsManagement
112-
submissions={submissions}
113-
pagination={pagination}
114-
filters={filters}
115-
loading={loading}
116-
onFilterChange={updateFilters}
117-
onPageChange={goToPage}
118-
onRefresh={refresh}
119-
organizationId={organizationId}
120-
hackathonId={hackathonId}
121-
currentUserId={currentUserId || undefined}
122-
hackathon={hackathon || undefined}
123-
/>
163+
<div className='space-y-6'>
164+
<SubmissionsManagement
165+
submissions={filteredSubmissions}
166+
pagination={pagination}
167+
filters={filters}
168+
loading={loading}
169+
onFilterChange={updateFilters}
170+
onPageChange={goToPage}
171+
onRefresh={refresh}
172+
organizationId={organizationId}
173+
hackathonId={hackathonId}
174+
currentUserId={currentUserId || undefined}
175+
hackathon={hackathon || undefined}
176+
/>
177+
178+
<div className='flex justify-end'>
179+
<DataTablePagination table={table} loading={loading} />
180+
</div>
181+
</div>
124182
)}
125183
</div>
126184
</div>

components/hackathons/submissions/SubmissionDetailModal.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -166,16 +166,16 @@ export function SubmissionDetailModal({
166166
<div className='flex items-center gap-4'>
167167
<Badge
168168
className={`${
169-
submission.status === 'shortlisted'
169+
submission.status === 'SHORTLISTED'
170170
? 'border-primary bg-[#E5FFE5] text-[#4E9E00]'
171-
: submission.status === 'disqualified'
171+
: submission.status === 'DISQUALIFIED'
172172
? 'border-[#FF5757] bg-[#FFEAEA] text-[#D33]'
173173
: 'border-[#645D5D] bg-[#E4DBDB] text-[#645D5D]'
174174
}`}
175175
>
176-
{submission.status === 'shortlisted'
176+
{submission.status === 'SHORTLISTED'
177177
? 'Shortlisted'
178-
: submission.status === 'disqualified'
178+
: submission.status === 'DISQUALIFIED'
179179
? 'Disqualified'
180180
: 'Submitted'}
181181
</Badge>
@@ -295,7 +295,7 @@ export function SubmissionDetailModal({
295295
</div>
296296

297297
{/* Disqualification Reason */}
298-
{submission.status === 'disqualified' &&
298+
{submission.status === 'DISQUALIFIED' &&
299299
submission.disqualificationReason && (
300300
<div className='rounded-lg border border-red-500/50 bg-red-500/10 p-4'>
301301
<h4 className='mb-2 font-semibold text-red-400'>

components/organization/cards/Participant.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ const Participant = ({
4545

4646
// Check if submission is shortlisted
4747
const isShortlisted = useMemo(() => {
48-
return participant.submission?.status === 'shortlisted';
48+
return participant.submission?.status === 'SHORTLISTED';
4949
}, [participant.submission?.status]);
5050

5151
// Fetch criteria when opening judge modal

components/organization/hackathons/ParticipantsGrid.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ export const ParticipantsGrid: React.FC<ParticipantsGridProps> = ({
6767
const user = participant.user;
6868
const submission = participant.submission;
6969
const hasSubmission = !!submission;
70-
const isShortlisted = submission?.status === 'shortlisted';
71-
const isDisqualified = submission?.status === 'disqualified';
70+
const isShortlisted = submission?.status === 'SHORTLISTED';
71+
const isDisqualified = submission?.status === 'DISQUALIFIED';
7272
const isTeam = participant.participationType === 'team';
7373

7474
const votesCount = Array.isArray(submission?.votes)

components/organization/hackathons/ParticipantsTable.tsx

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -110,22 +110,25 @@ export function ParticipantsTable({
110110
);
111111
}
112112

113-
const status = submission.status;
113+
const status = submission.status as
114+
| 'SHORTLISTED'
115+
| 'DISQUALIFIED'
116+
| 'SUBMITTED';
114117
return (
115118
<Badge
116119
variant='outline'
117120
className={cn(
118-
'capitalize',
119-
status === 'shortlisted'
120-
? 'border-green-500/30 bg-green-500/10 text-green-400'
121-
: status === 'disqualified'
122-
? 'border-red-500/30 bg-red-500/10 text-red-400'
123-
: 'border-yellow-500/30 bg-yellow-500/10 text-yellow-400'
121+
'rounded-full border px-2 py-0.5 text-[10px] font-medium transition-colors',
122+
status === 'SHORTLISTED'
123+
? 'border-primary/30 bg-primary/5 text-primary'
124+
: status === 'DISQUALIFIED'
125+
? 'border-red-500/30 bg-red-500/5 text-red-400'
126+
: 'border-yellow-500/30 bg-yellow-500/5 text-yellow-400'
124127
)}
125128
>
126-
{status === 'shortlisted'
129+
{status === 'SHORTLISTED'
127130
? 'Shortlisted'
128-
: status === 'disqualified'
131+
: status === 'DISQUALIFIED'
129132
? 'Disqualified'
130133
: 'Submitted'}
131134
</Badge>
@@ -150,7 +153,7 @@ export function ParticipantsTable({
150153
const participant = row.original;
151154
const hasSubmission = !!participant.submission;
152155
const isShortlisted =
153-
participant.submission?.status === 'shortlisted';
156+
participant.submission?.status === 'SHORTLISTED';
154157

155158
return (
156159
<DropdownMenu>

0 commit comments

Comments
 (0)