'use client' import { useState } from 'react' import { useSession } from 'next-auth/react' import Link from 'next/link' import type { Route } from 'next' import { trpc } from '@/lib/trpc/client' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Card, CardContent, CardHeader, CardTitle, } from '@/components/ui/card' import { Skeleton } from '@/components/ui/skeleton' import { Textarea } from '@/components/ui/textarea' import { CompetitionTimelineSidebar } from '@/components/applicant/competition-timeline' import { MentoringRequestCard } from '@/components/applicant/mentoring-request-card' import { AnimatedCard } from '@/components/shared/animated-container' import { ProjectLogoUpload } from '@/components/shared/project-logo-upload' import { Progress } from '@/components/ui/progress' import { FileText, Calendar, CheckCircle, Users, Crown, MessageSquare, Upload, ArrowRight, Star, AlertCircle, Pencil, Loader2, Check, X, UserCircle, Trophy, Vote, } from 'lucide-react' import { toast } from 'sonner' const statusColors: Record = { DRAFT: 'secondary', SUBMITTED: 'default', UNDER_REVIEW: 'default', ELIGIBLE: 'default', SEMIFINALIST: 'success', FINALIST: 'success', WINNER: 'success', REJECTED: 'destructive', } // Keys to hide from the metadata display (shown elsewhere or internal) const HIDDEN_METADATA_KEYS = new Set(['TeamMembers', 'teammembers', 'team_members']) export default function ApplicantDashboardPage() { const { data: session, status: sessionStatus } = useSession() const isAuthenticated = sessionStatus === 'authenticated' const utils = trpc.useUtils() const { data, isLoading } = trpc.applicant.getMyDashboard.useQuery(undefined, { enabled: isAuthenticated, }) const { data: deadlines } = trpc.applicant.getUpcomingDeadlines.useQuery(undefined, { enabled: isAuthenticated, }) const { data: docCompleteness } = trpc.applicant.getDocumentCompleteness.useQuery(undefined, { enabled: isAuthenticated, }) const { data: evaluations } = trpc.applicant.getMyEvaluations.useQuery(undefined, { enabled: isAuthenticated, }) const { data: flags } = trpc.settings.getFeatureFlags.useQuery(undefined, { enabled: isAuthenticated, }) if (sessionStatus === 'loading' || (isAuthenticated && isLoading)) { return (
) } // No project yet if (!data?.project) { return (

My Project

Your applicant dashboard

No Project Yet

You haven't submitted a project yet. Check for open application rounds on the MOPC website.

) } const { project, timeline, currentStatus, openRounds, hasPassedIntake, isRejected } = data const programYear = project.program?.year const programName = project.program?.name const totalEvaluations = evaluations?.reduce((sum, r) => sum + r.evaluationCount, 0) ?? 0 const canEditDescription = flags?.applicantAllowDescriptionEdit && !isRejected return (
{/* Header — no withdraw button here */}
{/* Project logo — clickable for any team member to change */} utils.applicant.getMyDashboard.invalidate()} >

{project.title}

{currentStatus && ( {currentStatus.replace('_', ' ')} )}

{programYear ? `${programYear} Edition` : ''}{programName ? ` - ${programName}` : ''}

{/* Main content */}
{/* Project details */} Project Details {project.teamName && (

Team/Organization

{project.teamName}

)} {/* Description — editable if admin allows */} {project.description && !canEditDescription && (

Description

{project.description}

)} {canEditDescription && ( )} {project.tags && project.tags.length > 0 && (

Tags

{project.tags.map((tag) => ( {tag} ))}
)} {/* Metadata — filter out team members (shown in sidebar) */} {project.metadataJson && (() => { const entries = Object.entries(project.metadataJson as Record) .filter(([key]) => !HIDDEN_METADATA_KEYS.has(key)) if (entries.length === 0) return null return (

Additional Information

{entries.map(([key, value]) => (
{key.replace(/_/g, ' ')}
{String(value)}
))}
) })()} {/* Meta info row */}
Created {new Date(project.createdAt).toLocaleDateString()}
{project.submittedAt && (
Submitted {new Date(project.submittedAt).toLocaleDateString()}
)}
{project.files.length} file(s)
{/* Rejected banner */} {isRejected && (

Your project was not selected to advance. Your project space is now read-only.

)} {/* Quick actions */} {!isRejected && (

Documents

{openRounds.length > 0 ? `${openRounds.length} round(s) open` : 'View uploads'}

Team

{project.teamMembers.length} member(s)

{project.mentorAssignment && (

Mentor

{project.mentorAssignment.mentor?.name || 'Assigned'}

)}
)} {/* Document Completeness */} {docCompleteness && docCompleteness.length > 0 && ( Document Progress {docCompleteness.map((dc) => (
{dc.roundName} {dc.uploaded}/{dc.required} files
0 ? Math.round((dc.uploaded / dc.required) * 100) : 0}%` }} />
))} )}
{/* Sidebar */}
{/* Competition timeline */} Status Timeline {/* Mentoring Request Card */} {project.isTeamLead && openRounds.filter((r) => r.roundType === 'MENTORING').map((mentoringRound) => ( ))} {/* Jury Feedback Card */} {totalEvaluations > 0 && (
Jury Feedback
{evaluations?.map((round) => { const scores = round.evaluations .map((ev) => ev.globalScore) .filter((s): s is number => s !== null) const avgScore = scores.length > 0 ? scores.reduce((a, b) => a + b, 0) / scores.length : null const maxScore = round.roundType === 'LIVE_FINAL' ? 10 : 100 const pct = avgScore !== null ? (avgScore / maxScore) * 100 : 0 const roundIcon = round.roundType === 'LIVE_FINAL' ? : round.roundType === 'DELIBERATION' ? : return (
{roundIcon} {round.roundName} {round.evaluationCount} review{round.evaluationCount !== 1 ? 's' : ''}
{avgScore !== null && (
Avg Score {avgScore.toFixed(1)} / {maxScore}
)}
) })}
)} {/* Team overview — proper cards */}
Team
{project.teamMembers.length > 0 ? ( project.teamMembers.slice(0, 5).map((member) => (
{member.role === 'LEAD' ? ( ) : ( )}

{member.user.name || member.user.email}

{member.role === 'LEAD' ? 'Lead' : member.role === 'ADVISOR' ? 'Advisor' : 'Member'}
)) ) : (

No team members yet

)} {project.teamMembers.length > 5 && (

+{project.teamMembers.length - 5} more

)}
{/* Upcoming Deadlines */} {deadlines && deadlines.length > 0 && ( Upcoming Deadlines {deadlines.map((dl, i) => (
{dl.roundName} {new Date(dl.windowCloseAt).toLocaleDateString()}
))}
)} {/* Key dates */} Key Dates
Created {new Date(project.createdAt).toLocaleDateString()}
{project.submittedAt && (
Submitted {new Date(project.submittedAt).toLocaleDateString()}
)}
Last Updated {new Date(project.updatedAt).toLocaleDateString()}
) } function EditableDescription({ projectId, initialDescription }: { projectId: string; initialDescription: string }) { const [isEditing, setIsEditing] = useState(false) const [description, setDescription] = useState(initialDescription) const utils = trpc.useUtils() const mutation = trpc.applicant.updateDescription.useMutation({ onSuccess: () => { utils.applicant.getMyDashboard.invalidate() setIsEditing(false) toast.success('Description updated') }, onError: (e) => toast.error(e.message), }) const handleSave = () => { mutation.mutate({ projectId, description }) } const handleCancel = () => { setDescription(initialDescription) setIsEditing(false) } if (!isEditing) { return (

Description

{initialDescription || No description yet. Click Edit to add one.}

) } return (

Description