Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ const ActionButtons = () => {
const isParticipant = user
? !!hackathon?.isParticipant ||
(hackathon?.participants || []).some(
(p: Participant) => p.userId === user.id
(p: Participant) =>
(p as any).userId === user.id || p.user?.id === user.id
)
: false;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
useHackathon,
useHackathonParticipants,
} from '@/hooks/hackathon/use-hackathon-queries';
import { useDebounce } from '@/hooks/use-debounce';
import { TabsContent } from '@/components/ui/tabs';
import { ChevronDown, Search, Filter, Loader2 } from 'lucide-react';
import { cn } from '@/lib/utils';
Expand Down Expand Up @@ -53,6 +54,12 @@ const Participants = () => {
'all' | 'submitted' | 'in_progress'
>('all');
const [skillFilter, setSkillFilter] = useState<string>('all');
const [participationType, setParticipationType] = useState<
'all' | 'individual' | 'team'
>('all');
const [searchQuery, setSearchQuery] = useState('');
const debouncedSearch = useDebounce(searchQuery, 300);

const [page, setPage] = useState(1);
const [accumulatedParticipants, setAccumulatedParticipants] = useState<
Participant[]
Expand All @@ -68,13 +75,15 @@ const Participants = () => {
limit,
status: statusFilter === 'all' ? undefined : statusFilter,
skill: skillFilter === 'all' ? undefined : skillFilter,
type: participationType === 'all' ? undefined : participationType,
search: debouncedSearch || undefined,
});

// Reset page and list when filters change
React.useEffect(() => {
setPage(1);
setAccumulatedParticipants([]);
}, [statusFilter, skillFilter]);
}, [statusFilter, skillFilter, participationType, debouncedSearch]);

// Accumulate participants as they are fetched
React.useEffect(() => {
Expand Down Expand Up @@ -133,14 +142,28 @@ const Participants = () => {
>
{/* Header with Count and Filters */}
<div className='flex flex-col gap-6 md:flex-row md:items-end md:justify-between'>
<div>
<h2 className='text-2xl font-bold text-white'>Participants</h2>
<p className='mt-1 text-gray-500'>
<span className='font-bold text-gray-300'>
{totalBuilders.toLocaleString()}
</span>{' '}
builders competing in {hackathon.name}
</p>
<div className='flex flex-1 flex-col gap-4'>
<div>
<h2 className='text-2xl font-bold text-white'>Participants</h2>
<p className='mt-1 text-gray-500'>
<span className='font-bold text-gray-300'>
{totalBuilders.toLocaleString()}
</span>{' '}
builders competing in {hackathon.name}
</p>
</div>

{/* Search Bar */}
<div className='relative w-full max-w-md'>
<input
type='text'
placeholder='Search by name, username, or project...'
value={searchQuery}
onChange={e => setSearchQuery(e.target.value)}
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'
/>
<Search className='absolute top-1/2 left-3.5 h-4 w-4 -translate-y-1/2 text-gray-500' />
</div>
</div>

<div className='flex items-center gap-3'>
Expand Down Expand Up @@ -175,6 +198,38 @@ const Participants = () => {
</DropdownMenuContent>
</DropdownMenu>

{/* Type Filter */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<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'>
<span>
{participationType === 'all'
? 'Type: All'
: participationType === 'individual'
? 'Type: Individual'
: 'Type: Team'}
</span>
<ChevronDown className='h-4 w-4 opacity-50' />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent
align='end'
className='w-48 border-white/10 bg-[#141517] text-gray-300'
>
<DropdownMenuItem onClick={() => setParticipationType('all')}>
All Types
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => setParticipationType('individual')}
>
Individual
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setParticipationType('team')}>
Team
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>

{/* Status Filter */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
Expand Down Expand Up @@ -222,7 +277,7 @@ const Participants = () => {
key={p.id}
name={p.user.profile.name}
username={p.user.profile.username}
image={p.user.profile.image}
image={p.user.profile.image || undefined}
submitted={!!p.submittedAt}
skills={p.user.profile.skills}
userId={p.userId ?? p.user?.id}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ interface FilterState {

const mapFiltersToParams = (filters: FilterState, searchOverride?: string) => ({
search: searchOverride !== undefined ? searchOverride : filters.search,
status: filters.status === 'all' ? undefined : filters.status,
type: filters.type === 'all' ? undefined : filters.type,
status: filters.status === 'all' ? undefined : filters.status.toUpperCase(),
type: filters.type === 'all' ? undefined : filters.type.toUpperCase(),
});

const ParticipantsPage: React.FC = () => {
Expand Down Expand Up @@ -228,6 +228,8 @@ const ParticipantsPage: React.FC = () => {
}
};

// Removed redundant frontend-side filtering as backend now handles it.

// Mock table instance for DataTablePagination
const table = useReactTable({
data: participants,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,30 @@
'use client';

import { useParams } from 'next/navigation';
import { useEffect, useState } from 'react';
import { useEffect, useState, useMemo } from 'react';
import { Loader2, AlertCircle } from 'lucide-react';
import { useOrganizerSubmissions } from '@/hooks/hackathon/use-organizer-submissions';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { AuthGuard } from '@/components/auth';
import Loading from '@/components/Loading';
import { SubmissionsManagement } from '@/components/organization/hackathons/submissions/SubmissionsManagement';
import { authClient } from '@/lib/auth-client';
import { getHackathon, type Hackathon } from '@/lib/api/hackathons';
import {
getHackathon,
type Hackathon,
type ParticipantSubmission,
} from '@/lib/api/hackathons';
import { reportError } from '@/lib/error-reporting';
import { useReactTable, getCoreRowModel } from '@tanstack/react-table';
import { DataTablePagination } from '@/components/ui/data-table-pagination';

export default function SubmissionsPage() {
const params = useParams();
const hackathonId = params.hackathonId as string;
const organizationId = params.id as string;

const {
submissions,
submissions: allSubmissions,
pagination,
filters,
loading,
Expand All @@ -27,14 +33,14 @@ export default function SubmissionsPage() {
updateFilters,
goToPage,
refresh,
updateLimit,
} = useOrganizerSubmissions(hackathonId);

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

useEffect(() => {
if (hackathonId) {
fetchSubmissions();
const fetchHackathonDetails = async () => {
try {
const res = await getHackathon(hackathonId);
Expand All @@ -50,7 +56,7 @@ export default function SubmissionsPage() {
};
fetchHackathonDetails();
}
}, [hackathonId, fetchSubmissions]);
}, [hackathonId]);

useEffect(() => {
const fetchSession = async () => {
Expand All @@ -66,6 +72,33 @@ export default function SubmissionsPage() {
fetchSession();
}, []);

const table = useReactTable({
data: allSubmissions,
columns: [],
getCoreRowModel: getCoreRowModel(),
manualPagination: true,
pageCount: pagination.totalPages,
state: {
pagination: {
pageIndex: pagination.page - 1,
pageSize: pagination.limit,
},
},
onPaginationChange: updater => {
if (typeof updater === 'function') {
const newState = updater({
pageIndex: pagination.page - 1,
pageSize: pagination.limit,
});
if (newState.pageSize !== pagination.limit) {
updateLimit(newState.pageSize);
} else {
goToPage(newState.pageIndex + 1);
}
}
},
});

if (error) {
return (
<div className='flex min-h-screen items-center justify-center bg-black p-6'>
Expand Down Expand Up @@ -100,27 +133,33 @@ export default function SubmissionsPage() {

{/* Main Content */}
<div className='mx-auto max-w-7xl px-6 py-12 sm:px-8 lg:px-12'>
{loading && submissions.length === 0 ? (
{loading && allSubmissions.length === 0 ? (
<div className='flex items-center justify-center py-20'>
<div className='flex flex-col items-center gap-3'>
<Loader2 className='h-6 w-6 animate-spin text-gray-400' />
<p className='text-sm text-gray-500'>Loading submissions...</p>
</div>
</div>
) : (
<SubmissionsManagement
submissions={submissions}
pagination={pagination}
filters={filters}
loading={loading}
onFilterChange={updateFilters}
onPageChange={goToPage}
onRefresh={refresh}
organizationId={organizationId}
hackathonId={hackathonId}
currentUserId={currentUserId || undefined}
hackathon={hackathon || undefined}
/>
<div className='space-y-6'>
<SubmissionsManagement
submissions={allSubmissions}
pagination={pagination}
filters={filters}
loading={loading}
onFilterChange={updateFilters}
onPageChange={goToPage}
onRefresh={refresh}
organizationId={organizationId}
hackathonId={hackathonId}
currentUserId={currentUserId || undefined}
hackathon={hackathon || undefined}
/>

<div className='flex justify-end'>
<DataTablePagination table={table} loading={loading} />
</div>
</div>
)}
</div>
</div>
Expand Down
10 changes: 5 additions & 5 deletions components/hackathons/submissions/SubmissionDetailModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -166,16 +166,16 @@ export function SubmissionDetailModal({
<div className='flex items-center gap-4'>
<Badge
className={`${
submission.status === 'shortlisted'
submission.status === 'SHORTLISTED'
? 'border-primary bg-[#E5FFE5] text-[#4E9E00]'
: submission.status === 'disqualified'
: submission.status === 'DISQUALIFIED'
? 'border-[#FF5757] bg-[#FFEAEA] text-[#D33]'
: 'border-[#645D5D] bg-[#E4DBDB] text-[#645D5D]'
}`}
>
{submission.status === 'shortlisted'
{submission.status === 'SHORTLISTED'
? 'Shortlisted'
: submission.status === 'disqualified'
: submission.status === 'DISQUALIFIED'
? 'Disqualified'
: 'Submitted'}
</Badge>
Expand Down Expand Up @@ -295,7 +295,7 @@ export function SubmissionDetailModal({
</div>

{/* Disqualification Reason */}
{submission.status === 'disqualified' &&
{submission.status === 'DISQUALIFIED' &&
submission.disqualificationReason && (
<div className='rounded-lg border border-red-500/50 bg-red-500/10 p-4'>
<h4 className='mb-2 font-semibold text-red-400'>
Expand Down
2 changes: 1 addition & 1 deletion components/hackathons/submissions/submissionCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ interface SubmissionCardProps {
title: string;
description: string;
submitterName: string;
submitterAvatar?: string;
submitterAvatar?: string | null;
category?: string;
categories?: string[];
status?: 'Pending' | 'Approved' | 'Rejected';
Expand Down
2 changes: 1 addition & 1 deletion components/hackathons/submissions/submissionCard2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { cn } from '@/lib/utils';
interface SubmissionCard2Props {
title: string;
submitterName: string;
submitterAvatar?: string;
submitterAvatar?: string | null;
image?: string;
upvotes?: number;
comments?: number;
Expand Down
2 changes: 1 addition & 1 deletion components/organization/cards/Participant.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ const Participant = ({

// Check if submission is shortlisted
const isShortlisted = useMemo(() => {
return participant.submission?.status === 'shortlisted';
return participant.submission?.status === 'SHORTLISTED';
}, [participant.submission?.status]);

// Fetch criteria when opening judge modal
Expand Down
2 changes: 1 addition & 1 deletion components/organization/cards/ParticipantInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const ParticipantInfo: React.FC<ParticipantInfoProps> = ({
<div className='flex flex-row items-center justify-start p-5'>
<div className='flex-1'>
<Avatar className='h-10.5 w-10.5'>
<AvatarImage src={userAvatar} />
<AvatarImage src={userAvatar || undefined} />
<AvatarFallback>{userName.charAt(0).toUpperCase()}</AvatarFallback>
</Avatar>
<h4 className='text-sm text-white'>{userName}</h4>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ interface Voter {
id: string;
name: string;
username: string;
avatar?: string;
avatar?: string | null;
votedAt?: string;
voteType?: 'positive' | 'negative';
}
Expand Down Expand Up @@ -42,7 +42,10 @@ export const SubmissionVotesTab: React.FC<SubmissionVotesTabProps> = ({
<div className='flex min-w-0 items-center gap-4'>
<div className='relative'>
<Avatar className='h-12 w-12 shrink-0 border border-gray-800'>
<AvatarImage src={voter.avatar} alt={voter.name} />
<AvatarImage
src={voter.avatar || undefined}
alt={voter.name}
/>
<AvatarFallback className='bg-background-card font-bold text-gray-400'>
{voter.name.charAt(0).toUpperCase()}
</AvatarFallback>
Expand Down
Loading
Loading