Jury dashboard compact layout, assignment redesign, auth fixes

- Jury dashboard: collapse zero-assignment state into single welcome card
  with inline quick actions; merge completion bar into stats row; tighten spacing
- Manual assignment: replace tiny Dialog modal with inline collapsible section
  featuring searchable juror combobox and multi-select project list with bulk assign
- Fix applicant invite URL path (/auth/accept-invite -> /accept-invite)
- Add APPLICANT role redirect to /my-submission from root page
- Add Applicant label to accept-invite role display
- Fix a/an grammar in invitation emails and accept-invite page
- Set-password page: use MOPC logo instead of lock icon
- Notification bell: remove filter tabs, always show all notifications

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-11 01:26:19 +01:00
parent 74515768f5
commit 09091d7c08
8 changed files with 411 additions and 236 deletions

View File

@@ -215,6 +215,54 @@ async function JuryDashboardContent() {
},
]
// Zero-assignment state: compact welcome card
if (totalAssignments === 0) {
return (
<AnimatedCard index={0}>
<Card className="overflow-hidden">
<div className="h-1 w-full bg-gradient-to-r from-brand-teal/40 via-brand-blue/40 to-brand-teal/40" />
<CardContent className="py-8 px-6">
<div className="flex flex-col items-center text-center mb-6">
<div className="rounded-2xl bg-gradient-to-br from-brand-teal/10 to-brand-blue/10 p-4 mb-3 dark:from-brand-teal/20 dark:to-brand-blue/20">
<ClipboardList className="h-8 w-8 text-brand-teal/60" />
</div>
<p className="text-lg font-semibold">No assignments yet</p>
<p className="text-sm text-muted-foreground mt-1 max-w-sm">
Your project assignments will appear here once an administrator assigns them to you.
</p>
</div>
<div className="grid gap-3 sm:grid-cols-2 max-w-md mx-auto">
<Link
href="/jury/assignments"
className="group flex items-center gap-3 rounded-xl border border-border/60 p-3 transition-all duration-200 hover:border-brand-blue/30 hover:bg-brand-blue/5 hover:-translate-y-0.5 hover:shadow-md dark:hover:border-brand-teal/30 dark:hover:bg-brand-teal/5"
>
<div className="rounded-lg bg-blue-50 p-2 transition-colors group-hover:bg-blue-100 dark:bg-blue-950/40">
<ClipboardList className="h-4 w-4 text-blue-600 dark:text-blue-400" />
</div>
<div className="text-left">
<p className="font-semibold text-sm group-hover:text-brand-blue dark:group-hover:text-brand-teal transition-colors">All Assignments</p>
<p className="text-xs text-muted-foreground">View evaluations</p>
</div>
</Link>
<Link
href="/jury/compare"
className="group flex items-center gap-3 rounded-xl border border-border/60 p-3 transition-all duration-200 hover:border-brand-teal/30 hover:bg-brand-teal/5 hover:-translate-y-0.5 hover:shadow-md"
>
<div className="rounded-lg bg-teal-50 p-2 transition-colors group-hover:bg-teal-100 dark:bg-teal-950/40">
<GitCompare className="h-4 w-4 text-brand-teal" />
</div>
<div className="text-left">
<p className="font-semibold text-sm group-hover:text-brand-teal transition-colors">Compare Projects</p>
<p className="text-xs text-muted-foreground">Side-by-side view</p>
</div>
</Link>
</div>
</CardContent>
</Card>
</AnimatedCard>
)
}
return (
<>
{/* Hero CTA - Jump to next evaluation */}
@@ -248,8 +296,8 @@ async function JuryDashboardContent() {
</AnimatedCard>
)}
{/* Stats */}
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
{/* Stats + Overall Completion in one row */}
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-5">
{stats.map((stat, i) => (
<AnimatedCard key={stat.label} index={i + 1}>
<Card className={cn(
@@ -268,43 +316,33 @@ async function JuryDashboardContent() {
</Card>
</AnimatedCard>
))}
{/* Overall completion as 5th stat card */}
<AnimatedCard index={5}>
<Card className="border-l-4 border-l-brand-teal transition-all duration-200 hover:-translate-y-0.5 hover:shadow-md">
<CardContent className="flex items-center gap-4 py-5 px-5">
<div className="rounded-xl p-3 bg-brand-blue/10 dark:bg-brand-blue/20">
<BarChart3 className="h-5 w-5 text-brand-blue dark:text-brand-teal" />
</div>
<div className="flex-1 min-w-0">
<p className="text-2xl font-bold tabular-nums tracking-tight text-brand-blue dark:text-brand-teal">
{completionRate.toFixed(0)}%
</p>
<div className="relative h-1.5 w-full overflow-hidden rounded-full bg-muted/60 mt-1">
<div
className="h-full rounded-full bg-gradient-to-r from-brand-teal to-brand-blue transition-all duration-500 ease-out"
style={{ width: `${completionRate}%` }}
/>
</div>
</div>
</CardContent>
</Card>
</AnimatedCard>
</div>
{/* Overall Progress */}
<AnimatedCard index={5}>
<Card className="overflow-hidden">
<div className="h-1 w-full bg-gradient-to-r from-brand-teal via-brand-blue to-brand-teal" />
<CardContent className="py-5 px-6">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2.5">
<div className="rounded-lg bg-brand-blue/10 p-2 dark:bg-brand-blue/20">
<BarChart3 className="h-4 w-4 text-brand-blue dark:text-brand-teal" />
</div>
<span className="text-sm font-semibold">Overall Completion</span>
</div>
<div className="flex items-baseline gap-1">
<span className="text-2xl font-bold tabular-nums text-brand-blue dark:text-brand-teal">
{completionRate.toFixed(0)}%
</span>
<span className="text-xs text-muted-foreground ml-1">
({completedAssignments}/{totalAssignments})
</span>
</div>
</div>
<div className="relative h-3 w-full overflow-hidden rounded-full bg-muted/60">
<div
className="h-full rounded-full bg-gradient-to-r from-brand-teal to-brand-blue transition-all duration-500 ease-out"
style={{ width: `${completionRate}%` }}
/>
</div>
</CardContent>
</Card>
</AnimatedCard>
{/* Main content -- two column layout */}
<div className="grid gap-6 lg:grid-cols-12">
<div className="grid gap-4 lg:grid-cols-12">
{/* Left column */}
<div className="lg:col-span-7 space-y-6">
<div className="lg:col-span-7 space-y-4">
{/* Recent Assignments */}
<AnimatedCard index={6}>
<Card>
@@ -402,11 +440,11 @@ async function JuryDashboardContent() {
})}
</div>
) : (
<div className="flex flex-col items-center justify-center py-10 text-center">
<div className="rounded-2xl bg-brand-teal/10 p-4 mb-3">
<ClipboardList className="h-8 w-8 text-brand-teal/60" />
<div className="flex flex-col items-center justify-center py-6 text-center">
<div className="rounded-2xl bg-brand-teal/10 p-3 mb-2">
<ClipboardList className="h-6 w-6 text-brand-teal/60" />
</div>
<p className="font-medium text-muted-foreground">
<p className="font-medium text-sm text-muted-foreground">
No assignments yet
</p>
<p className="text-xs text-muted-foreground/70 mt-1 max-w-[240px]">
@@ -462,7 +500,7 @@ async function JuryDashboardContent() {
</div>
{/* Right column */}
<div className="lg:col-span-5 space-y-6">
<div className="lg:col-span-5 space-y-4">
{/* Active Rounds */}
{activeRounds.length > 0 && (
<AnimatedCard index={8}>
@@ -561,12 +599,12 @@ async function JuryDashboardContent() {
)}
{/* No active rounds */}
{activeRounds.length === 0 && totalAssignments > 0 && (
{activeRounds.length === 0 && (
<AnimatedCard index={8}>
<Card>
<CardContent className="flex flex-col items-center justify-center py-10 text-center">
<div className="rounded-2xl bg-brand-teal/10 p-4 mb-3 dark:bg-brand-teal/20">
<Clock className="h-7 w-7 text-brand-teal/70" />
<CardContent className="flex flex-col items-center justify-center py-6 text-center">
<div className="rounded-2xl bg-brand-teal/10 p-3 mb-2 dark:bg-brand-teal/20">
<Clock className="h-6 w-6 text-brand-teal/70" />
</div>
<p className="font-semibold text-sm">No active voting rounds</p>
<p className="text-xs text-muted-foreground mt-1 max-w-[220px]">
@@ -618,24 +656,6 @@ async function JuryDashboardContent() {
)}
</div>
</div>
{/* No assignments at all */}
{totalAssignments === 0 && (
<AnimatedCard index={1}>
<Card className="overflow-hidden">
<div className="h-1 w-full bg-gradient-to-r from-brand-teal/40 via-brand-blue/40 to-brand-teal/40" />
<CardContent className="flex flex-col items-center justify-center py-14 text-center">
<div className="rounded-2xl bg-gradient-to-br from-brand-teal/10 to-brand-blue/10 p-5 mb-4 dark:from-brand-teal/20 dark:to-brand-blue/20">
<ClipboardList className="h-10 w-10 text-brand-teal/60" />
</div>
<p className="text-lg font-semibold">No assignments yet</p>
<p className="text-sm text-muted-foreground mt-1.5 max-w-sm">
You&apos;ll see your project assignments here once they&apos;re assigned to you by an administrator.
</p>
</CardContent>
</Card>
</AnimatedCard>
)}
</>
)
}
@@ -721,7 +741,7 @@ export default async function JuryDashboardPage() {
const session = await auth()
return (
<div className="space-y-6">
<div className="space-y-4">
{/* Header */}
<div className="relative">
<div className="absolute -top-6 -left-6 -right-6 h-32 bg-gradient-to-b from-brand-blue/[0.03] to-transparent dark:from-brand-blue/[0.06] pointer-events-none rounded-xl" />