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

@@ -11,7 +11,7 @@ import {
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Skeleton } from '@/components/ui/skeleton'
import {
Bot,
Cog,
Palette,
Mail,
HardDrive,
@@ -29,6 +29,7 @@ import {
} from 'lucide-react'
import Link from 'next/link'
import { Button } from '@/components/ui/button'
import { AnimatedCard } from '@/components/shared/animated-container'
import { AISettingsForm } from './ai-settings-form'
import { AIUsageCard } from './ai-usage-card'
import { BrandingSettingsForm } from './branding-settings-form'
@@ -195,7 +196,7 @@ export function SettingsContent({ initialSettings, isSuperAdmin = true }: Settin
</TabsTrigger>
{isSuperAdmin && (
<TabsTrigger value="ai" className="gap-2 shrink-0">
<Bot className="h-4 w-4" />
<Cog className="h-4 w-4" />
AI
</TabsTrigger>
)}
@@ -275,7 +276,7 @@ export function SettingsContent({ initialSettings, isSuperAdmin = true }: Settin
<TabsList className="flex flex-col items-stretch h-auto w-full bg-transparent p-0 gap-0.5">
{isSuperAdmin && (
<TabsTrigger value="ai" className="justify-start gap-2 w-full px-3 py-2 h-auto data-[state=active]:bg-muted">
<Bot className="h-4 w-4" />
<Cog className="h-4 w-4" />
AI
</TabsTrigger>
)}
@@ -308,6 +309,7 @@ export function SettingsContent({ initialSettings, isSuperAdmin = true }: Settin
{isSuperAdmin && (
<TabsContent value="ai" className="space-y-6">
<AnimatedCard>
<Card>
<CardHeader>
<CardTitle>AI Configuration</CardTitle>
@@ -319,11 +321,13 @@ export function SettingsContent({ initialSettings, isSuperAdmin = true }: Settin
<AISettingsForm settings={aiSettings} />
</CardContent>
</Card>
</AnimatedCard>
<AIUsageCard />
</TabsContent>
)}
<TabsContent value="tags">
<AnimatedCard>
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
@@ -353,9 +357,11 @@ export function SettingsContent({ initialSettings, isSuperAdmin = true }: Settin
</Button>
</CardContent>
</Card>
</AnimatedCard>
</TabsContent>
<TabsContent value="branding">
<AnimatedCard>
<Card>
<CardHeader>
<CardTitle>Platform Branding</CardTitle>
@@ -367,10 +373,12 @@ export function SettingsContent({ initialSettings, isSuperAdmin = true }: Settin
<BrandingSettingsForm settings={brandingSettings} />
</CardContent>
</Card>
</AnimatedCard>
</TabsContent>
{isSuperAdmin && (
<TabsContent value="email">
<AnimatedCard>
<Card>
<CardHeader>
<CardTitle>Email Configuration</CardTitle>
@@ -382,10 +390,12 @@ export function SettingsContent({ initialSettings, isSuperAdmin = true }: Settin
<EmailSettingsForm settings={emailSettings} />
</CardContent>
</Card>
</AnimatedCard>
</TabsContent>
)}
<TabsContent value="notifications">
<AnimatedCard>
<Card>
<CardHeader>
<CardTitle>Notification Email Settings</CardTitle>
@@ -397,10 +407,12 @@ export function SettingsContent({ initialSettings, isSuperAdmin = true }: Settin
<NotificationSettingsForm />
</CardContent>
</Card>
</AnimatedCard>
</TabsContent>
{isSuperAdmin && (
<TabsContent value="storage">
<AnimatedCard>
<Card>
<CardHeader>
<CardTitle>File Storage</CardTitle>
@@ -412,11 +424,13 @@ export function SettingsContent({ initialSettings, isSuperAdmin = true }: Settin
<StorageSettingsForm settings={storageSettings} />
</CardContent>
</Card>
</AnimatedCard>
</TabsContent>
)}
{isSuperAdmin && (
<TabsContent value="security">
<AnimatedCard>
<Card>
<CardHeader>
<CardTitle>Security Settings</CardTitle>
@@ -428,10 +442,12 @@ export function SettingsContent({ initialSettings, isSuperAdmin = true }: Settin
<SecuritySettingsForm settings={securitySettings} />
</CardContent>
</Card>
</AnimatedCard>
</TabsContent>
)}
<TabsContent value="defaults">
<AnimatedCard>
<Card>
<CardHeader>
<CardTitle>Default Settings</CardTitle>
@@ -443,9 +459,11 @@ export function SettingsContent({ initialSettings, isSuperAdmin = true }: Settin
<DefaultsSettingsForm settings={defaultsSettings} />
</CardContent>
</Card>
</AnimatedCard>
</TabsContent>
<TabsContent value="digest" className="space-y-6">
<AnimatedCard>
<Card>
<CardHeader>
<CardTitle>Digest Configuration</CardTitle>
@@ -457,9 +475,11 @@ export function SettingsContent({ initialSettings, isSuperAdmin = true }: Settin
<DigestSettingsSection settings={digestSettings} />
</CardContent>
</Card>
</AnimatedCard>
</TabsContent>
<TabsContent value="analytics" className="space-y-6">
<AnimatedCard>
<Card>
<CardHeader>
<CardTitle>Analytics & Reports</CardTitle>
@@ -471,9 +491,11 @@ export function SettingsContent({ initialSettings, isSuperAdmin = true }: Settin
<AnalyticsSettingsSection settings={analyticsSettings} />
</CardContent>
</Card>
</AnimatedCard>
</TabsContent>
<TabsContent value="audit" className="space-y-6">
<AnimatedCard>
<Card>
<CardHeader>
<CardTitle>Audit & Security</CardTitle>
@@ -485,9 +507,11 @@ export function SettingsContent({ initialSettings, isSuperAdmin = true }: Settin
<AuditSettingsSection settings={auditSecuritySettings} />
</CardContent>
</Card>
</AnimatedCard>
</TabsContent>
<TabsContent value="localization" className="space-y-6">
<AnimatedCard>
<Card>
<CardHeader>
<CardTitle>Localization</CardTitle>
@@ -499,6 +523,7 @@ export function SettingsContent({ initialSettings, isSuperAdmin = true }: Settin
<LocalizationSettingsSection settings={localizationSettings} />
</CardContent>
</Card>
</AnimatedCard>
</TabsContent>
</div>{/* end content area */}
</div>{/* end lg:flex */}
@@ -506,7 +531,7 @@ export function SettingsContent({ initialSettings, isSuperAdmin = true }: Settin
{/* Quick Links to sub-pages */}
<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>
<CardTitle className="text-base flex items-center gap-2">
<LayoutTemplate className="h-4 w-4" />
@@ -528,7 +553,7 @@ export function SettingsContent({ initialSettings, isSuperAdmin = true }: Settin
</Card>
{isSuperAdmin && (
<Card>
<Card className="transition-all duration-200 hover:-translate-y-0.5 hover:shadow-md">
<CardHeader>
<CardTitle className="text-base flex items-center gap-2">
<Webhook className="h-4 w-4" />