feat: multi-role jury fix, country flags, applicant deadline banner, timeline
Some checks failed
Build and Push Docker Image / build (push) Has been cancelled

- Fix project list returning empty for users with both SUPER_ADMIN and
  JURY_MEMBER roles (jury filter now skips admins) in project, assignment,
  and evaluation routers
- Add CountryDisplay component showing flag emoji + name everywhere
  country is displayed (admin, observer, jury, mentor views — 17 files)
- Add countdown deadline banner on applicant dashboard for INTAKE,
  SUBMISSION, and MENTORING rounds with live timer
- Remove quick action buttons from applicant dashboard
- Fix competition timeline sidebar: green dots/connectors only up to
  current round, yellow dot for current round, red connector into
  rejected round, grey after

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 15:00:29 +01:00
parent a1e758bc39
commit 37351044ed
21 changed files with 172 additions and 79 deletions

View File

@@ -9,6 +9,7 @@ import { Badge } from '@/components/ui/badge'
import { Skeleton } from '@/components/ui/skeleton'
import { Button } from '@/components/ui/button'
import { AnimatedCard } from '@/components/shared/animated-container'
import { CountryDisplay } from '@/components/shared/country-display'
import {
Select,
SelectContent,
@@ -200,7 +201,7 @@ export function FilteringPanel({ roundId }: { roundId: string }) {
{r.project?.title ?? 'Unknown'}
</Link>
<p className="text-xs text-muted-foreground truncate">
{formatCategory(r.project?.competitionCategory)} · {r.project?.country ?? ''}
{formatCategory(r.project?.competitionCategory)} · {r.project?.country ? <CountryDisplay country={r.project.country} /> : ''}
</p>
</div>
<div className="flex items-center gap-2 shrink-0">

View File

@@ -7,6 +7,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Skeleton } from '@/components/ui/skeleton'
import { Badge } from '@/components/ui/badge'
import { AnimatedCard } from '@/components/shared/animated-container'
import { CountryDisplay } from '@/components/shared/country-display'
import { Inbox, Globe, FolderOpen } from 'lucide-react'
function relativeTime(date: Date | string): string {
@@ -87,11 +88,11 @@ export function IntakePanel({ roundId, programId }: { roundId: string; programId
<div className="min-w-0 flex-1">
<p className="text-sm font-medium truncate">{p.title}</p>
<p className="text-xs text-muted-foreground truncate">
{p.teamName ?? 'No team'} · {p.country ?? ''}
{p.teamName ?? 'No team'} · {p.country ? <CountryDisplay country={p.country} /> : ''}
</p>
</div>
<span className="text-[11px] tabular-nums text-muted-foreground shrink-0">
{p.country ?? ''}
{p.country ? <CountryDisplay country={p.country} /> : ''}
</span>
</Link>
))}

View File

@@ -6,6 +6,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { Skeleton } from '@/components/ui/skeleton'
import { AnimatedCard } from '@/components/shared/animated-container'
import { CountryDisplay } from '@/components/shared/country-display'
import { ArrowDown, ChevronDown, ChevronUp, TrendingDown } from 'lucide-react'
import { cn, formatCategory } from '@/lib/utils'
@@ -107,7 +108,7 @@ export function PreviousRoundSection({ currentRoundId }: { currentRoundId: strin
<div className="grid grid-cols-2 gap-x-4 gap-y-1">
{countryAttrition.map((c: any) => (
<div key={c.country} className="flex items-center justify-between text-sm py-0.5">
<span className="truncate">{c.country}</span>
<span className="truncate"><CountryDisplay country={c.country} /></span>
<Badge variant="destructive" className="tabular-nums text-xs">
-{c.lost}
</Badge>

View File

@@ -7,6 +7,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Skeleton } from '@/components/ui/skeleton'
import { Badge } from '@/components/ui/badge'
import { AnimatedCard } from '@/components/shared/animated-container'
import { CountryDisplay } from '@/components/shared/country-display'
import { FileText, Upload, Users } from 'lucide-react'
function relativeTime(date: Date | string): string {
@@ -146,11 +147,11 @@ export function SubmissionPanel({ roundId, programId }: { roundId: string; progr
<div className="min-w-0 flex-1">
<p className="text-sm font-medium truncate">{p.title}</p>
<p className="text-xs text-muted-foreground truncate">
{p.teamName ?? 'No team'} · {p.country ?? ''}
{p.teamName ?? 'No team'} · {p.country ? <CountryDisplay country={p.country} /> : ''}
</p>
</div>
<Badge variant="outline" className="text-xs shrink-0">
{p.country ?? '—'}
{p.country ? <CountryDisplay country={p.country} /> : '—'}
</Badge>
</Link>
))}

View File

@@ -22,6 +22,7 @@ import { ProjectLogoWithUrl } from '@/components/shared/project-logo-with-url'
import { UserAvatar } from '@/components/shared/user-avatar'
import { StatusBadge } from '@/components/shared/status-badge'
import { AnimatedCard } from '@/components/shared/animated-container'
import { CountryDisplay } from '@/components/shared/country-display'
import {
AlertCircle,
Users,
@@ -174,7 +175,7 @@ export function ObserverProjectDetail({ projectId }: { projectId: string }) {
{(project.country || project.geographicZone) && (
<Badge variant="outline" className="gap-1">
<MapPin className="h-3 w-3" />
{project.country || project.geographicZone}
{project.country ? <CountryDisplay country={project.country} /> : project.geographicZone}
</Badge>
)}
{project.competitionCategory && (
@@ -392,7 +393,7 @@ export function ObserverProjectDetail({ projectId }: { projectId: string }) {
<MapPin className="h-4 w-4 text-muted-foreground mt-0.5" />
<div>
<p className="text-sm font-medium text-muted-foreground">Location</p>
<p className="text-sm">{project.geographicZone || project.country}</p>
<p className="text-sm">{project.geographicZone}{project.geographicZone && project.country ? ', ' : ''}{project.country ? <CountryDisplay country={project.country} /> : null}</p>
</div>
</div>
)}

View File

@@ -13,6 +13,7 @@ import {
CardDescription,
} from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { CountryDisplay } from '@/components/shared/country-display'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Skeleton } from '@/components/ui/skeleton'
@@ -394,7 +395,7 @@ export function ObserverProjectsContent() {
</div>
</TableCell>
<TableCell className="text-sm">
{project.country ?? '-'}
{project.country ? <CountryDisplay country={project.country} /> : '-'}
</TableCell>
<TableCell>
<Badge variant="outline" className="text-xs whitespace-nowrap">

View File

@@ -26,6 +26,7 @@ import { ChevronLeft, ChevronRight, ChevronDown, ChevronUp } from 'lucide-react'
import { RoundTypeStatsCards } from '@/components/observer/round-type-stats'
import { FilteringScreeningBar } from './filtering-screening-bar'
import { ProjectPreviewDialog } from './project-preview-dialog'
import { CountryDisplay } from '@/components/shared/country-display'
interface FilteringReportTabsProps {
roundId: string
@@ -176,7 +177,7 @@ export function FilteringReportTabs({ roundId }: FilteringReportTabsProps) {
{formatCategory(r.project.competitionCategory) || '—'}
</TableCell>
<TableCell className="text-muted-foreground">
{r.project.country ?? '—'}
{r.project.country ? <CountryDisplay country={r.project.country} /> : '—'}
</TableCell>
<TableCell>{outcomeBadge(effectiveOutcome)}</TableCell>
</TableRow>
@@ -258,7 +259,7 @@ export function FilteringReportTabs({ roundId }: FilteringReportTabsProps) {
</div>
<div className="flex gap-3 text-xs text-muted-foreground mt-1">
{r.project.competitionCategory && <span>{formatCategory(r.project.competitionCategory)}</span>}
{r.project.country && <span>{r.project.country}</span>}
{r.project.country && <span><CountryDisplay country={r.project.country} /></span>}
</div>
</div>

View File

@@ -13,6 +13,7 @@ import { Button } from '@/components/ui/button'
import { Skeleton } from '@/components/ui/skeleton'
import { Separator } from '@/components/ui/separator'
import { StatusBadge } from '@/components/shared/status-badge'
import { CountryDisplay } from '@/components/shared/country-display'
import { ExternalLink, MapPin, Waves, Users } from 'lucide-react'
import Link from 'next/link'
import type { Route } from 'next'
@@ -78,7 +79,7 @@ export function ProjectPreviewDialog({ projectId, open, onOpenChange }: ProjectP
{data.project.country && (
<Badge variant="outline" className="gap-1">
<MapPin className="h-3 w-3" />
{data.project.country}
<CountryDisplay country={data.project.country} />
</Badge>
)}
{data.project.competitionCategory && (