)}
@@ -281,7 +331,7 @@ export function TeamRecruitmentPostCard({
@@ -304,17 +354,21 @@ export function TeamRecruitmentPostCard({
)}
{/* Footer - Contact Button and Date */}
-
-
-
Posted: {formatDate(post.createdAt)}
+
+
+ Posted
+
+ {getTimeAgo(post.createdAt)}
+
diff --git a/components/organization/hackathons/details/HackathonSidebar.tsx b/components/organization/hackathons/details/HackathonSidebar.tsx
index b62bd515..a0bd5f83 100644
--- a/components/organization/hackathons/details/HackathonSidebar.tsx
+++ b/components/organization/hackathons/details/HackathonSidebar.tsx
@@ -7,6 +7,7 @@ import {
Users,
BarChartBig,
Megaphone,
+ FileText,
} from 'lucide-react';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
@@ -206,6 +207,16 @@ export default function HackathonSidebar({
description: 'Manage registrations',
disabled: hackathonId?.startsWith('draft-'),
},
+ {
+ icon: FileText,
+ label: 'Submissions',
+ href:
+ basePath !== '#' && !hackathonId?.startsWith('draft-')
+ ? `${basePath}/submissions`
+ : '#',
+ description: 'View all submissions',
+ disabled: hackathonId?.startsWith('draft-'),
+ },
{
icon: BarChartBig,
label: 'Judging',
diff --git a/components/organization/hackathons/submissions/SubmissionsList.tsx b/components/organization/hackathons/submissions/SubmissionsList.tsx
new file mode 100644
index 00000000..18ec523e
--- /dev/null
+++ b/components/organization/hackathons/submissions/SubmissionsList.tsx
@@ -0,0 +1,227 @@
+'use client';
+
+import { useRouter } from 'next/navigation';
+import { Users, User, Calendar, ExternalLink } from 'lucide-react';
+import { Card, CardContent } from '@/components/ui/card';
+import { Badge } from '@/components/ui/badge';
+import type { ParticipantSubmission } from '@/lib/api/hackathons';
+import Image from 'next/image';
+
+interface SubmissionsListProps {
+ submissions: ParticipantSubmission[];
+ viewMode: 'grid' | 'table';
+ loading: boolean;
+ onRefresh: () => void;
+}
+
+export function SubmissionsList({
+ submissions,
+ viewMode,
+ loading,
+}: SubmissionsListProps) {
+ const router = useRouter();
+
+ const getStatusColor = (status: string) => {
+ switch (status) {
+ case 'SUBMITTED':
+ return 'bg-blue-500/10 text-blue-400 border-blue-500/20';
+ case 'SHORTLISTED':
+ return 'bg-green-500/10 text-green-400 border-green-500/20';
+ case 'DISQUALIFIED':
+ return 'bg-red-500/10 text-red-400 border-red-500/20';
+ case 'WITHDRAWN':
+ return 'bg-gray-500/10 text-gray-400 border-gray-500/20';
+ default:
+ return 'bg-gray-500/10 text-gray-400 border-gray-500/20';
+ }
+ };
+
+ const handleSubmissionClick = (submissionId: string) => {
+ router.push(`/projects/${submissionId}?type=submission`);
+ };
+
+ if (submissions.length === 0 && !loading) {
+ return (
+
+
+ No submissions found
+
+
+ Try adjusting your filters or check back later
+
+
+ );
+ }
+
+ if (viewMode === 'grid') {
+ return (
+
+ {submissions.map(submission => {
+ const subData = submission as any;
+ return (
+
handleSubmissionClick(subData.id)}
+ >
+
+ {/* Logo and Status */}
+
+
+ {subData.logo ? (
+
+ ) : (
+
+ {subData.projectName?.charAt(0) || 'P'}
+
+ )}
+
+
+ {subData.status}
+
+
+
+ {/* Project Name */}
+
+ {subData.projectName || 'Untitled Project'}
+
+
+ {/* Category */}
+ {subData.category && (
+
+ {subData.category}
+
+ )}
+
+ {/* Type & Date */}
+
+
+ {subData.participationType === 'TEAM' ? (
+ <>
+
+ Team
+ >
+ ) : (
+ <>
+
+ Individual
+ >
+ )}
+
+
+
+
+ {new Date(
+ subData.submittedAt || subData.createdAt
+ ).toLocaleDateString()}
+
+
+
+
+ {/* View Link */}
+
+ View Details
+
+
+
+
+ );
+ })}
+
+ );
+ }
+
+ // Table view
+ return (
+
+
+
+
+ |
+ Project
+ |
+
+ Category
+ |
+
+ Type
+ |
+
+ Status
+ |
+
+ Submitted
+ |
+
+
+
+ {submissions.map(submission => {
+ const subData = submission as any;
+ return (
+ handleSubmissionClick(subData.id)}
+ >
+
+
+
+ {subData.logo ? (
+
+ ) : (
+
+ {subData.projectName?.charAt(0) || 'P'}
+
+ )}
+
+
+ {subData.projectName || 'Untitled Project'}
+
+
+ |
+
+ {subData.category || '-'}
+ |
+
+
+ {subData.participationType === 'TEAM' ? (
+ <>
+
+ Team
+ >
+ ) : (
+ <>
+
+ Individual
+ >
+ )}
+
+ |
+
+
+ {subData.status}
+
+ |
+
+ {new Date(
+ subData.submittedAt || subData.createdAt
+ ).toLocaleDateString()}
+ |
+
+ );
+ })}
+
+
+
+ );
+}
diff --git a/components/organization/hackathons/submissions/SubmissionsManagement.tsx b/components/organization/hackathons/submissions/SubmissionsManagement.tsx
new file mode 100644
index 00000000..6f7e52ff
--- /dev/null
+++ b/components/organization/hackathons/submissions/SubmissionsManagement.tsx
@@ -0,0 +1,224 @@
+'use client';
+
+import { useState } from 'react';
+import type { FormEvent } from 'react';
+import { Search, Grid3x3, List, RefreshCw } from 'lucide-react';
+
+import { Input } from '@/components/ui/input';
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from '@/components/ui/select';
+import { Button } from '@/components/ui/button';
+import { SubmissionsList } from './SubmissionsList';
+import type { ParticipantSubmission } from '@/lib/api/hackathons';
+
+/* -------------------------------------------------------------------------- */
+/* Types */
+/* -------------------------------------------------------------------------- */
+
+type SubmissionStatus =
+ | 'SUBMITTED'
+ | 'SHORTLISTED'
+ | 'DISQUALIFIED'
+ | 'WITHDRAWN';
+
+type SubmissionType = 'INDIVIDUAL' | 'TEAM';
+
+interface SubmissionFilters {
+ status?: SubmissionStatus;
+ type?: SubmissionType;
+ search?: string;
+}
+
+interface SubmissionsManagementProps {
+ submissions: ParticipantSubmission[];
+ pagination: {
+ page: number;
+ limit: number;
+ total: number;
+ totalPages: number;
+ };
+ filters: SubmissionFilters;
+ loading: boolean;
+ onFilterChange: (filters: SubmissionFilters) => void;
+ onPageChange: (page: number) => void;
+ onRefresh: () => void;
+}
+
+/* -------------------------------------------------------------------------- */
+/* Main Component */
+/* -------------------------------------------------------------------------- */
+
+export function SubmissionsManagement({
+ submissions,
+ pagination,
+ filters,
+ loading,
+ onFilterChange,
+ onPageChange,
+ onRefresh,
+}: SubmissionsManagementProps) {
+ const [viewMode, setViewMode] = useState<'grid' | 'table'>('grid');
+ const [searchTerm, setSearchTerm] = useState(filters.search ?? '');
+
+ const handleSearchSubmit = (e: FormEvent) => {
+ e.preventDefault();
+ onFilterChange({ ...filters, search: searchTerm });
+ };
+
+ const handleStatusChange = (value: string) => {
+ onFilterChange({
+ ...filters,
+ status: value === 'all' ? undefined : (value as SubmissionStatus),
+ });
+ };
+
+ const handleTypeChange = (value: string) => {
+ onFilterChange({
+ ...filters,
+ type: value === 'all' ? undefined : (value as SubmissionType),
+ });
+ };
+
+ return (
+
+ {/* Filters and Controls */}
+
+ {/* Search */}
+
+
+ {/* Filters */}
+
+ {/* Status Filter */}
+
+
+ {/* Type Filter */}
+
+
+ {/* View Mode Toggle */}
+
+
+
+
+
+ {/* Refresh */}
+
+
+
+
+ {/* Results Count */}
+
+ Showing{' '}
+ {submissions.length} of{' '}
+ {pagination.total}{' '}
+ submissions
+
+
+ {/* Submissions List */}
+
+
+ {/* Pagination */}
+ {pagination.totalPages > 1 && (
+
+
+
+ Page {pagination.page} of {pagination.totalPages}
+
+
+
+ )}
+
+ );
+}
diff --git a/components/project-details/project-details.tsx b/components/project-details/project-details.tsx
index 864e66b7..23e5bc2c 100644
--- a/components/project-details/project-details.tsx
+++ b/components/project-details/project-details.tsx
@@ -63,15 +63,30 @@ export function ProjectDetails({ project }: ProjectDetailsProps) {
-
-
+ {project.demoVideo.includes('youtube.com') ||
+ project.demoVideo.includes('youtu.be') ? (
+
+ ) : (
+ <>
+
+
+ >
+ )}
diff --git a/components/project-details/project-layout.tsx b/components/project-details/project-layout.tsx
index f68bd4ca..d0060274 100644
--- a/components/project-details/project-layout.tsx
+++ b/components/project-details/project-layout.tsx
@@ -1,6 +1,7 @@
'use client';
import { useEffect, useRef, useState } from 'react';
+import { useSearchParams } from 'next/navigation';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { ProjectDetails } from './project-details';
import { ProjectAbout } from './project-about';
@@ -18,16 +19,21 @@ import { Crowdfunding, CrowdfundingProject } from '@/types/project';
export function ProjectLayout({
project,
crowdfund,
+ hiddenTabs = [],
+ hideProgress = false,
}: {
project: CrowdfundingProject;
crowdfund: Crowdfunding;
+ hiddenTabs?: string[];
+ hideProgress?: boolean;
}) {
const isMobile = useIsMobile();
- const [activeTab, setActiveTab] = useState('details'); // Start with about tab on mobile
+ const searchParams = useSearchParams();
+ const initialTab = searchParams.get('tab') || 'details';
+ const [activeTab, setActiveTab] = useState(initialTab);
const [isLeftScrollable, setIsLeftScrollable] = useState(true);
const [isRightScrollable, setIsRightScrollable] = useState(true);
const tabsListRef = useRef(null);
- console.log('Rendering ProjectLayout with project:', project);
const handleScroll = () => {
if (tabsListRef.current) {
const { scrollLeft, scrollWidth, clientWidth } = tabsListRef.current;
@@ -98,6 +104,7 @@ export function ProjectLayout({
project={project}
crowdfund={crowdfund}
isMobile={true}
+ hideProgress={hideProgress}
/>
@@ -136,20 +143,22 @@ export function ProjectLayout({
{ value: 'milestones', label: 'Milestones' },
{ value: 'voters', label: 'Voters' },
{ value: 'comments', label: 'Comments' },
- ].map(tab => (
-
- {tab.label}
-
- ))}
+ ]
+ .filter(tab => !hiddenTabs.includes(tab.value))
+ .map(tab => (
+
+ {tab.label}
+
+ ))}
@@ -198,6 +207,7 @@ export function ProjectLayout({
project={project}
crowdfund={crowdfund}
isMobile={false}
+ hideProgress={hideProgress}
/>