feat(final-docs): finalist upload banner on applicant dashboard
This commit is contained in:
@@ -20,6 +20,7 @@ import { MentoringRequestCard } from '@/components/applicant/mentoring-request-c
|
|||||||
import { MentorConversationCard } from '@/components/applicant/mentor-conversation-card'
|
import { MentorConversationCard } from '@/components/applicant/mentor-conversation-card'
|
||||||
import { AttendingMembersCard } from '@/components/applicant/attending-members-card'
|
import { AttendingMembersCard } from '@/components/applicant/attending-members-card'
|
||||||
import { MyLogisticsCard } from '@/components/applicant/my-logistics-card'
|
import { MyLogisticsCard } from '@/components/applicant/my-logistics-card'
|
||||||
|
import { FinalDocumentsBanner } from '@/components/applicant/final-documents-banner'
|
||||||
import { LunchBanner } from '@/components/applicant/lunch-banner'
|
import { LunchBanner } from '@/components/applicant/lunch-banner'
|
||||||
import { ExternalAttendeesStrip } from '@/components/applicant/external-attendees-strip'
|
import { ExternalAttendeesStrip } from '@/components/applicant/external-attendees-strip'
|
||||||
import { AnimatedCard } from '@/components/shared/animated-container'
|
import { AnimatedCard } from '@/components/shared/animated-container'
|
||||||
@@ -206,6 +207,9 @@ export default function ApplicantDashboardPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Grand Final document upload banner (auto-hides for non-finalists) */}
|
||||||
|
<FinalDocumentsBanner />
|
||||||
|
|
||||||
{/* Active round deadline banner */}
|
{/* Active round deadline banner */}
|
||||||
{!isRejected && openRounds.length > 0 && (() => {
|
{!isRejected && openRounds.length > 0 && (() => {
|
||||||
const submissionTypes = new Set(['INTAKE', 'SUBMISSION', 'MENTORING'])
|
const submissionTypes = new Set(['INTAKE', 'SUBMISSION', 'MENTORING'])
|
||||||
|
|||||||
61
src/components/applicant/final-documents-banner.tsx
Normal file
61
src/components/applicant/final-documents-banner.tsx
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import Link from 'next/link'
|
||||||
|
import { trpc } from '@/lib/trpc/client'
|
||||||
|
import { Card, CardContent } from '@/components/ui/card'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { FileText, Video, CheckCircle2, Circle, Clock, Upload } from 'lucide-react'
|
||||||
|
|
||||||
|
export function FinalDocumentsBanner() {
|
||||||
|
const { data: status } = trpc.applicant.getFinalDocumentStatus.useQuery()
|
||||||
|
if (!status) return null
|
||||||
|
|
||||||
|
const fmt = new Intl.DateTimeFormat(undefined, { dateStyle: 'long', timeStyle: 'short' })
|
||||||
|
const zone = new Intl.DateTimeFormat(undefined, { timeZoneName: 'short' })
|
||||||
|
.formatToParts(new Date()).find((p) => p.type === 'timeZoneName')?.value
|
||||||
|
const uploadedCount = status.requirements.filter((r) => r.uploaded).length
|
||||||
|
const total = status.requirements.length
|
||||||
|
const done = status.allRequiredUploaded
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className={done ? 'border-emerald-200 bg-emerald-50/50' : 'border-brand-blue/30 bg-brand-blue/5'}>
|
||||||
|
<CardContent className="py-4 space-y-3">
|
||||||
|
<div className="flex items-center justify-between gap-3 flex-wrap">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{done ? <CheckCircle2 className="h-5 w-5 text-emerald-600" /> : <Upload className="h-5 w-5 text-brand-blue" />}
|
||||||
|
<span className="font-semibold">
|
||||||
|
{done ? 'Grand Final documents submitted' : 'Upload your Grand Final documents'}
|
||||||
|
</span>
|
||||||
|
<span className="text-sm text-muted-foreground">({uploadedCount} of {total})</span>
|
||||||
|
</div>
|
||||||
|
{status.deadline && (
|
||||||
|
<span className={`flex items-center gap-1.5 text-sm ${status.deadlinePassed ? 'text-destructive' : 'text-muted-foreground'}`}>
|
||||||
|
<Clock className="h-4 w-4" />
|
||||||
|
{status.deadlinePassed ? 'Deadline passed' : 'Due'}: {fmt.format(new Date(status.deadline))}
|
||||||
|
{zone ? ` (${zone})` : ''}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-3">
|
||||||
|
{status.requirements.map((r) => {
|
||||||
|
const Icon = r.acceptedMimeTypes.some((m) => m.startsWith('video/')) ? Video : FileText
|
||||||
|
return (
|
||||||
|
<span key={r.id} className="flex items-center gap-1.5 text-sm">
|
||||||
|
{r.uploaded ? <CheckCircle2 className="h-4 w-4 text-emerald-600" /> : <Circle className="h-4 w-4 text-muted-foreground/50" />}
|
||||||
|
<Icon className="h-4 w-4 text-muted-foreground" />
|
||||||
|
{r.name}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
{!done && (
|
||||||
|
<Button asChild size="sm" className="bg-brand-blue hover:bg-brand-blue-light">
|
||||||
|
<Link href="/applicant/documents">
|
||||||
|
<Upload className="mr-2 h-4 w-4" /> Upload documents
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user