Platform-wide visual overhaul, team invites, analytics improvements, and deployment hardening

UI overhaul applying jury dashboard design patterns across all pages:
- Stat cards with border-l-4 accent + icon pills on admin, observer, mentor, applicant dashboards and reports
- Card section headers with color-coded icon pills throughout
- Hover lift effects (translate-y + shadow) on cards and list items
- Gradient progress bars (brand-teal to brand-blue) platform-wide
- AnimatedCard stagger animations on all dashboard sections
- Auth pages with gradient accent strip and polished icon containers
- EmptyState component upgraded with rounded icon pill containers
- Replaced AI-looking icons (Brain/Sparkles/Bot/Wand2/Cpu) with descriptive alternatives across 12 files
- Removed gradient overlay from jury dashboard header
- Quick actions restyled as card links with group hover effects

Backend improvements:
- Team member invite emails with account setup flow and notification logging
- Analytics routers accept edition-wide queries (programId) in addition to roundId
- Round detail endpoint returns inline progress data (eliminates extra getProgress call)
- Award voting endpoints parallelized with Promise.all
- Bulk invite supports optional sendInvitation flag
- AwardVote composite index migration for query performance

Infrastructure:
- Docker entrypoint with migration retry loop (configurable retries/delay)
- docker-compose pull_policy: always for automatic image refresh
- Simplified deploy/update scripts using docker compose up -d --pull always
- Updated deployment documentation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-11 13:20:52 +01:00
parent 98f4a957cc
commit ce4069bf92
59 changed files with 1949 additions and 913 deletions

View File

@@ -28,6 +28,7 @@ import { FileUpload } from '@/components/shared/file-upload'
import { ProjectLogoWithUrl } from '@/components/shared/project-logo-with-url'
import { UserAvatar } from '@/components/shared/user-avatar'
import { EvaluationSummaryCard } from '@/components/admin/evaluation-summary-card'
import { AnimatedCard } from '@/components/shared/animated-container'
import {
ArrowLeft,
Edit,
@@ -184,13 +185,16 @@ function ProjectDetailContent({ projectId }: { projectId: string }) {
{/* Stats Grid */}
{stats && (
<AnimatedCard index={0}>
<div className="grid gap-4 sm:grid-cols-2">
<Card>
<Card className="transition-all duration-200 hover:-translate-y-0.5 hover:shadow-md">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">
Average Score
</CardTitle>
<BarChart3 className="h-4 w-4 text-muted-foreground" />
<div className="rounded-lg bg-brand-teal/10 p-1.5">
<BarChart3 className="h-4 w-4 text-brand-teal" />
</div>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
@@ -202,12 +206,14 @@ function ProjectDetailContent({ projectId }: { projectId: string }) {
</CardContent>
</Card>
<Card>
<Card className="transition-all duration-200 hover:-translate-y-0.5 hover:shadow-md">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">
Recommendations
</CardTitle>
<ThumbsUp className="h-4 w-4 text-muted-foreground" />
<div className="rounded-lg bg-emerald-500/10 p-1.5">
<ThumbsUp className="h-4 w-4 text-emerald-500" />
</div>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
@@ -219,12 +225,19 @@ function ProjectDetailContent({ projectId }: { projectId: string }) {
</CardContent>
</Card>
</div>
</AnimatedCard>
)}
{/* Project Info */}
<AnimatedCard index={1}>
<Card>
<CardHeader>
<CardTitle className="text-lg">Project Information</CardTitle>
<CardTitle className="flex items-center gap-2.5 text-lg">
<div className="rounded-lg bg-emerald-500/10 p-1.5">
<FileText className="h-4 w-4 text-emerald-500" />
</div>
Project Information
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
{/* Category & Ocean Issue badges */}
@@ -393,14 +406,18 @@ function ProjectDetailContent({ projectId }: { projectId: string }) {
</div>
</CardContent>
</Card>
</AnimatedCard>
{/* Team Members Section */}
{project.teamMembers && project.teamMembers.length > 0 && (
<AnimatedCard index={2}>
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle className="text-lg flex items-center gap-2">
<Users className="h-5 w-5" />
<CardTitle className="flex items-center gap-2.5 text-lg">
<div className="rounded-lg bg-violet-500/10 p-1.5">
<Users className="h-4 w-4 text-violet-500" />
</div>
Team Members ({project.teamMembers.length})
</CardTitle>
</div>
@@ -437,15 +454,19 @@ function ProjectDetailContent({ projectId }: { projectId: string }) {
</div>
</CardContent>
</Card>
</AnimatedCard>
)}
{/* Mentor Assignment Section */}
{project.wantsMentorship && (
<AnimatedCard index={3}>
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle className="text-lg flex items-center gap-2">
<Heart className="h-5 w-5" />
<CardTitle className="flex items-center gap-2.5 text-lg">
<div className="rounded-lg bg-rose-500/10 p-1.5">
<Heart className="h-4 w-4 text-rose-500" />
</div>
Mentor Assignment
</CardTitle>
{!project.mentorAssignment && (
@@ -487,12 +508,19 @@ function ProjectDetailContent({ projectId }: { projectId: string }) {
)}
</CardContent>
</Card>
</AnimatedCard>
)}
{/* Files Section */}
<AnimatedCard index={4}>
<Card>
<CardHeader>
<CardTitle className="text-lg">Files</CardTitle>
<CardTitle className="flex items-center gap-2.5 text-lg">
<div className="rounded-lg bg-rose-500/10 p-1.5">
<FileText className="h-4 w-4 text-rose-500" />
</div>
Files
</CardTitle>
<CardDescription>
Project documents and materials
</CardDescription>
@@ -535,14 +563,21 @@ function ProjectDetailContent({ projectId }: { projectId: string }) {
</div>
</CardContent>
</Card>
</AnimatedCard>
{/* Assignments Section */}
{assignments && assignments.length > 0 && (
<AnimatedCard index={5}>
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<div>
<CardTitle className="text-lg">Jury Assignments</CardTitle>
<CardTitle className="flex items-center gap-2.5 text-lg">
<div className="rounded-lg bg-violet-500/10 p-1.5">
<Users className="h-4 w-4 text-violet-500" />
</div>
Jury Assignments
</CardTitle>
<CardDescription>
{assignments.filter((a) => a.evaluation?.status === 'SUBMITTED')
.length}{' '}
@@ -649,6 +684,7 @@ function ProjectDetailContent({ projectId }: { projectId: string }) {
</Table>
</CardContent>
</Card>
</AnimatedCard>
)}
{/* AI Evaluation Summary */}