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:
@@ -4,7 +4,7 @@ import { useForm } from 'react-hook-form'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { z } from 'zod'
|
||||
import { toast } from 'sonner'
|
||||
import { Bot, Loader2, Zap, AlertCircle, RefreshCw, Brain } from 'lucide-react'
|
||||
import { Cog, Loader2, Zap, AlertCircle, RefreshCw, SlidersHorizontal } from 'lucide-react'
|
||||
import { trpc } from '@/lib/trpc/client'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
@@ -264,7 +264,7 @@ export function AISettingsForm({ settings }: AISettingsFormProps) {
|
||||
<SelectItem key={model.id} value={model.id}>
|
||||
<div className="flex items-center gap-2">
|
||||
{model.isReasoning && (
|
||||
<Brain className="h-3 w-3 text-purple-500" />
|
||||
<SlidersHorizontal className="h-3 w-3 text-purple-500" />
|
||||
)}
|
||||
<span>{model.name}</span>
|
||||
</div>
|
||||
@@ -278,7 +278,7 @@ export function AISettingsForm({ settings }: AISettingsFormProps) {
|
||||
<FormDescription>
|
||||
{form.watch('ai_model')?.startsWith('o') ? (
|
||||
<span className="flex items-center gap-1 text-purple-600">
|
||||
<Brain className="h-3 w-3" />
|
||||
<SlidersHorizontal className="h-3 w-3" />
|
||||
Reasoning model - optimized for complex analysis tasks
|
||||
</span>
|
||||
) : (
|
||||
@@ -323,7 +323,7 @@ export function AISettingsForm({ settings }: AISettingsFormProps) {
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Bot className="mr-2 h-4 w-4" />
|
||||
<Cog className="mr-2 h-4 w-4" />
|
||||
Save AI Settings
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
Zap,
|
||||
TrendingUp,
|
||||
Activity,
|
||||
Brain,
|
||||
SlidersHorizontal,
|
||||
Filter,
|
||||
Users,
|
||||
Award,
|
||||
@@ -26,7 +26,7 @@ const ACTION_ICONS: Record<string, typeof Zap> = {
|
||||
ASSIGNMENT: Users,
|
||||
FILTERING: Filter,
|
||||
AWARD_ELIGIBILITY: Award,
|
||||
MENTOR_MATCHING: Brain,
|
||||
MENTOR_MATCHING: SlidersHorizontal,
|
||||
}
|
||||
|
||||
const ACTION_LABELS: Record<string, string> = {
|
||||
@@ -235,7 +235,7 @@ export function AIUsageCard() {
|
||||
variant="outline"
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Brain className="h-3 w-3" />
|
||||
<SlidersHorizontal className="h-3 w-3" />
|
||||
<span>{model}</span>
|
||||
<span className="text-muted-foreground">
|
||||
{(data as { costFormatted?: string }).costFormatted}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
Reference in New Issue
Block a user