Skip to content
Merged
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
4 changes: 1 addition & 3 deletions app/(landing)/hackathons/[slug]/components/tabs/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ const HackathonTabs = ({ sidebar }: HackathonTabsProps) => {
const {
currentHackathon,
winners,
submissions,
loading: generalLoading,
} = useHackathonData();
const { data: announcements = [], isLoading: announcementsLoading } =
Expand Down Expand Up @@ -91,7 +90,7 @@ const HackathonTabs = ({ sidebar }: HackathonTabsProps) => {
{
id: 'submissions',
label: 'Submissions',
badge: submissions.filter(p => p.status === 'Approved').length,
badge: currentHackathon._count?.submissions ?? 0,
},
{
id: 'discussions',
Expand Down Expand Up @@ -140,7 +139,6 @@ const HackathonTabs = ({ sidebar }: HackathonTabsProps) => {
}, [
currentHackathon,
winners,
submissions,
discussionComments.pagination.totalItems,
announcements,
announcementsLoading,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,14 @@ export default function DraftPreviewPage({ params }: PreviewPageProps) {
resolvedParams.orgId,
resolvedParams.draftId
);
let organizationData = { name: '', logo: '' };
let organizationData = { name: '', logo: '', slug: '' };
try {
const orgRes = await getOrganization(resolvedParams.orgId);
if (orgRes) {
organizationData = {
name: orgRes.name,
logo: orgRes.logo || '',
slug: orgRes.slug || '',
};
}
} catch (orgErr) {
Expand Down Expand Up @@ -130,6 +131,7 @@ export default function DraftPreviewPage({ params }: PreviewPageProps) {
id: resolvedParams.orgId,
name: organizationData.name,
logo: organizationData.logo,
slug: organizationData.slug,
},

status: 'DRAFT',
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
7 changes: 2 additions & 5 deletions components/avatars/GroupAvatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,15 @@ const GroupAvatar = ({ members }: GroupAvatarProps) => {
return (
<AvatarGroup>
{visibleMembers.map((member, index) => (
<Avatar
key={index}
className='size-8 border-2 border-[#141517] sm:size-10'
>
<Avatar key={index} className='size-8 border-none! sm:size-6'>
<AvatarImage src={member} alt={`Member ${index + 1}`} />
<AvatarFallback className='bg-[#1E2329] text-[10px] text-white'>
{member.slice(0, 2).toUpperCase()}
</AvatarFallback>
</Avatar>
))}
{remainingCount > 0 && (
<AvatarGroupCount className='size-8 bg-[#1E2329] text-xs font-bold text-gray-400 ring-2 ring-[#141517] sm:size-10 sm:text-sm'>
<AvatarGroupCount className='bg-background text-foreground ring-border size-8 text-xs font-bold ring-2 sm:size-6 sm:text-xs'>
+{remainingCount}
</AvatarGroupCount>
)}
Expand Down
2 changes: 1 addition & 1 deletion components/hackathons/HackathonsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client';

import React, { useState } from 'react';
import HackathonCard from '@/components/landing-page/hackathon/HackathonCard';
import { HackathonCard } from '@/components/landing-page/hackathon/HackathonCard';
import HackathonsFiltersHeader from '@/components/hackathons/HackathonsFiltersHeader';
import LoadingSpinner from '@/components/LoadingSpinner';
import { useHackathonFilters } from '@/hooks/hackathon/use-hackathon-filters';
Expand Down
Loading
Loading