Skip to content

Commit d536c43

Browse files
Pr 486 (#490)
* 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 * fix: enhanced filtering for organizations * fix: fix coderabbit corrections --------- Co-authored-by: Collins Ikechukwu <collinschristroa@gmail.com>
1 parent ed65b36 commit d536c43

20 files changed

Lines changed: 139 additions & 103 deletions

File tree

app/(landing)/hackathons/[slug]/components/header/ActionButtons.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ const ActionButtons = () => {
3838
const isParticipant = user
3939
? !!hackathon?.isParticipant ||
4040
(hackathon?.participants || []).some(
41-
(p: Participant) => p.userId === user.id
41+
(p: Participant) =>
42+
(p as any).userId === user.id || p.user?.id === user.id
4243
)
4344
: false;
4445

app/(landing)/hackathons/[slug]/components/tabs/contents/Participants.tsx

Lines changed: 65 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
useHackathon,
77
useHackathonParticipants,
88
} from '@/hooks/hackathon/use-hackathon-queries';
9+
import { useDebounce } from '@/hooks/use-debounce';
910
import { TabsContent } from '@/components/ui/tabs';
1011
import { ChevronDown, Search, Filter, Loader2 } from 'lucide-react';
1112
import { cn } from '@/lib/utils';
@@ -53,6 +54,12 @@ const Participants = () => {
5354
'all' | 'submitted' | 'in_progress'
5455
>('all');
5556
const [skillFilter, setSkillFilter] = useState<string>('all');
57+
const [participationType, setParticipationType] = useState<
58+
'all' | 'individual' | 'team'
59+
>('all');
60+
const [searchQuery, setSearchQuery] = useState('');
61+
const debouncedSearch = useDebounce(searchQuery, 300);
62+
5663
const [page, setPage] = useState(1);
5764
const [accumulatedParticipants, setAccumulatedParticipants] = useState<
5865
Participant[]
@@ -68,13 +75,15 @@ const Participants = () => {
6875
limit,
6976
status: statusFilter === 'all' ? undefined : statusFilter,
7077
skill: skillFilter === 'all' ? undefined : skillFilter,
78+
type: participationType === 'all' ? undefined : participationType,
79+
search: debouncedSearch || undefined,
7180
});
7281

7382
// Reset page and list when filters change
7483
React.useEffect(() => {
7584
setPage(1);
7685
setAccumulatedParticipants([]);
77-
}, [statusFilter, skillFilter]);
86+
}, [statusFilter, skillFilter, participationType, debouncedSearch]);
7887

7988
// Accumulate participants as they are fetched
8089
React.useEffect(() => {
@@ -133,14 +142,28 @@ const Participants = () => {
133142
>
134143
{/* Header with Count and Filters */}
135144
<div className='flex flex-col gap-6 md:flex-row md:items-end md:justify-between'>
136-
<div>
137-
<h2 className='text-2xl font-bold text-white'>Participants</h2>
138-
<p className='mt-1 text-gray-500'>
139-
<span className='font-bold text-gray-300'>
140-
{totalBuilders.toLocaleString()}
141-
</span>{' '}
142-
builders competing in {hackathon.name}
143-
</p>
145+
<div className='flex flex-1 flex-col gap-4'>
146+
<div>
147+
<h2 className='text-2xl font-bold text-white'>Participants</h2>
148+
<p className='mt-1 text-gray-500'>
149+
<span className='font-bold text-gray-300'>
150+
{totalBuilders.toLocaleString()}
151+
</span>{' '}
152+
builders competing in {hackathon.name}
153+
</p>
154+
</div>
155+
156+
{/* Search Bar */}
157+
<div className='relative w-full max-w-md'>
158+
<input
159+
type='text'
160+
placeholder='Search by name, username, or project...'
161+
value={searchQuery}
162+
onChange={e => setSearchQuery(e.target.value)}
163+
className='hover:border-primary/20 focus:border-primary/30 h-10 w-full rounded-xl border border-white/5 bg-[#141517] pr-4 pl-10 text-sm text-white transition-all outline-none placeholder:text-gray-500'
164+
/>
165+
<Search className='absolute top-1/2 left-3.5 h-4 w-4 -translate-y-1/2 text-gray-500' />
166+
</div>
144167
</div>
145168

146169
<div className='flex items-center gap-3'>
@@ -175,6 +198,38 @@ const Participants = () => {
175198
</DropdownMenuContent>
176199
</DropdownMenu>
177200

201+
{/* Type Filter */}
202+
<DropdownMenu>
203+
<DropdownMenuTrigger asChild>
204+
<button className='hover:border-primary/20 flex min-w-[140px] items-center justify-between gap-2 rounded-xl border border-white/5 bg-[#141517] px-4 py-2.5 text-sm font-medium text-gray-400 transition-all hover:text-white'>
205+
<span>
206+
{participationType === 'all'
207+
? 'Type: All'
208+
: participationType === 'individual'
209+
? 'Type: Individual'
210+
: 'Type: Team'}
211+
</span>
212+
<ChevronDown className='h-4 w-4 opacity-50' />
213+
</button>
214+
</DropdownMenuTrigger>
215+
<DropdownMenuContent
216+
align='end'
217+
className='w-48 border-white/10 bg-[#141517] text-gray-300'
218+
>
219+
<DropdownMenuItem onClick={() => setParticipationType('all')}>
220+
All Types
221+
</DropdownMenuItem>
222+
<DropdownMenuItem
223+
onClick={() => setParticipationType('individual')}
224+
>
225+
Individual
226+
</DropdownMenuItem>
227+
<DropdownMenuItem onClick={() => setParticipationType('team')}>
228+
Team
229+
</DropdownMenuItem>
230+
</DropdownMenuContent>
231+
</DropdownMenu>
232+
178233
{/* Status Filter */}
179234
<DropdownMenu>
180235
<DropdownMenuTrigger asChild>
@@ -222,7 +277,7 @@ const Participants = () => {
222277
key={p.id}
223278
name={p.user.profile.name}
224279
username={p.user.profile.username}
225-
image={p.user.profile.image}
280+
image={p.user.profile.image || undefined}
226281
submitted={!!p.submittedAt}
227282
skills={p.user.profile.skills}
228283
userId={p.userId ?? p.user?.id}

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

Lines changed: 4 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -228,45 +228,11 @@ 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]);
231+
// Removed redundant frontend-side filtering as backend now handles it.
266232

267233
// Mock table instance for DataTablePagination
268234
const table = useReactTable({
269-
data: filteredParticipants,
235+
data: participants,
270236
columns: [], // Not used for rendering here
271237
getCoreRowModel: getCoreRowModel(),
272238
manualPagination: true,
@@ -352,15 +318,15 @@ const ParticipantsPage: React.FC = () => {
352318
<div className='space-y-6'>
353319
{view === 'table' ? (
354320
<ParticipantsTable
355-
data={filteredParticipants}
321+
data={participants}
356322
loading={participantsLoading}
357323
onReview={handleReview}
358324
onViewTeam={handleViewTeam}
359325
onGrade={handleGrade}
360326
/>
361327
) : (
362328
<ParticipantsGrid
363-
data={filteredParticipants}
329+
data={participants}
364330
loading={participantsLoading}
365331
onReview={handleReview}
366332
onViewTeam={handleViewTeam}

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

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -72,27 +72,8 @@ export default function SubmissionsPage() {
7272
fetchSession();
7373
}, []);
7474

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-
9475
const table = useReactTable({
95-
data: filteredSubmissions,
76+
data: allSubmissions,
9677
columns: [],
9778
getCoreRowModel: getCoreRowModel(),
9879
manualPagination: true,
@@ -162,7 +143,7 @@ export default function SubmissionsPage() {
162143
) : (
163144
<div className='space-y-6'>
164145
<SubmissionsManagement
165-
submissions={filteredSubmissions}
146+
submissions={allSubmissions}
166147
pagination={pagination}
167148
filters={filters}
168149
loading={loading}

components/hackathons/submissions/submissionCard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ interface SubmissionCardProps {
2828
title: string;
2929
description: string;
3030
submitterName: string;
31-
submitterAvatar?: string;
31+
submitterAvatar?: string | null;
3232
category?: string;
3333
categories?: string[];
3434
status?: 'Pending' | 'Approved' | 'Rejected';

components/hackathons/submissions/submissionCard2.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { cn } from '@/lib/utils';
99
interface SubmissionCard2Props {
1010
title: string;
1111
submitterName: string;
12-
submitterAvatar?: string;
12+
submitterAvatar?: string | null;
1313
image?: string;
1414
upvotes?: number;
1515
comments?: number;

components/organization/cards/ParticipantInfo.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export const ParticipantInfo: React.FC<ParticipantInfoProps> = ({
2525
<div className='flex flex-row items-center justify-start p-5'>
2626
<div className='flex-1'>
2727
<Avatar className='h-10.5 w-10.5'>
28-
<AvatarImage src={userAvatar} />
28+
<AvatarImage src={userAvatar || undefined} />
2929
<AvatarFallback>{userName.charAt(0).toUpperCase()}</AvatarFallback>
3030
</Avatar>
3131
<h4 className='text-sm text-white'>{userName}</h4>

components/organization/cards/ReviewSubmissionModal/SubmissionVotesTab.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ interface Voter {
1111
id: string;
1212
name: string;
1313
username: string;
14-
avatar?: string;
14+
avatar?: string | null;
1515
votedAt?: string;
1616
voteType?: 'positive' | 'negative';
1717
}
@@ -42,7 +42,10 @@ export const SubmissionVotesTab: React.FC<SubmissionVotesTabProps> = ({
4242
<div className='flex min-w-0 items-center gap-4'>
4343
<div className='relative'>
4444
<Avatar className='h-12 w-12 shrink-0 border border-gray-800'>
45-
<AvatarImage src={voter.avatar} alt={voter.name} />
45+
<AvatarImage
46+
src={voter.avatar || undefined}
47+
alt={voter.name}
48+
/>
4649
<AvatarFallback className='bg-background-card font-bold text-gray-400'>
4750
{voter.name.charAt(0).toUpperCase()}
4851
</AvatarFallback>

components/organization/cards/ReviewSubmissionModal/TeamSection.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ interface TeamMember {
1111
id: string;
1212
name: string;
1313
role: string;
14-
avatar?: string;
14+
avatar?: string | null;
1515
username?: string;
1616
}
1717

@@ -37,7 +37,10 @@ export const TeamSection: React.FC<TeamSectionProps> = ({ teamMembers }) => {
3737
className='group bg-background-card/20 hover:bg-background-card/40 flex items-center gap-4 rounded-xl border border-gray-900/60 p-4 transition-all hover:border-gray-800'
3838
>
3939
<Avatar className='h-12 w-12 border border-gray-800 transition-transform group-hover:scale-105'>
40-
<AvatarImage src={member.avatar} alt={member.name} />
40+
<AvatarImage
41+
src={member.avatar || undefined}
42+
alt={member.name}
43+
/>
4144
<AvatarFallback className='bg-background-card font-bold text-gray-400'>
4245
{member.name.charAt(0).toUpperCase()}
4346
</AvatarFallback>

components/organization/cards/ReviewSubmissionModal/types.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@ export interface TeamMember {
22
id: string;
33
name: string;
44
role: string;
5-
avatar?: string;
5+
avatar?: string | null;
66
username?: string;
77
}
88

99
export interface Voter {
1010
id: string;
1111
name: string;
1212
username: string;
13-
avatar?: string;
13+
avatar?: string | null;
1414
votedAt?: string;
1515
voteType?: 'positive' | 'negative';
1616
}
@@ -21,7 +21,7 @@ export interface Comment {
2121
author: {
2222
name: string;
2323
username: string;
24-
avatar?: string;
24+
avatar?: string | null;
2525
};
2626
createdAt: string;
2727
reactions?: {

0 commit comments

Comments
 (0)