Comprehensive platform audit: security, UX, performance, and visual polish

Phase 1: Security - status transition validation, Zod tightening, DB indexes, transactions

Phase 2: Admin UX - search/filter for awards, learning, partners pages

Phase 3: Dashboard - Recent Activity feed, Pending Actions card, quick actions

Phase 4: Jury - assignments progress/urgency, autosave indicator, divergence highlighting

Phase 5: Portals - observer charts, mentor search, login/onboarding polish

Phase 6: Messages preview dialog, CsvExportDialog with column selection

Phase 7: Performance - query optimizations, loading skeletons, useDebounce hook

Phase 8: Visual - AnimatedCard, hover effects, StatusBadge, empty state CTAs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-08 22:05:01 +01:00
parent e0e4cb2a32
commit e73a676412
33 changed files with 3193 additions and 977 deletions

View File

@@ -32,7 +32,17 @@ import {
type Criterion,
} from '@/components/forms/evaluation-form-builder'
import { RoundTypeSettings } from '@/components/forms/round-type-settings'
import { ArrowLeft, Loader2, AlertCircle, AlertTriangle, Bell, GitCompare, MessageSquare, FileText, Calendar } from 'lucide-react'
import { ArrowLeft, Loader2, AlertCircle, AlertTriangle, Bell, GitCompare, MessageSquare, FileText, Calendar, LayoutTemplate } from 'lucide-react'
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/components/ui/dialog'
import { toast } from 'sonner'
import { Switch } from '@/components/ui/switch'
import { Slider } from '@/components/ui/slider'
import { Label } from '@/components/ui/label'
@@ -113,9 +123,23 @@ function EditRoundContent({ roundId }: { roundId: string }) {
roundId,
})
const [saveTemplateOpen, setSaveTemplateOpen] = useState(false)
const [templateName, setTemplateName] = useState('')
const utils = trpc.useUtils()
// Mutations
const saveAsTemplate = trpc.roundTemplate.create.useMutation({
onSuccess: () => {
toast.success('Round saved as template')
setSaveTemplateOpen(false)
setTemplateName('')
},
onError: (error) => {
toast.error(error.message)
},
})
const updateRound = trpc.round.update.useMutation({
onSuccess: () => {
// Invalidate cache to ensure fresh data
@@ -825,6 +849,58 @@ function EditRoundContent({ roundId }: { roundId: string }) {
{/* Actions */}
<div className="flex justify-end gap-3">
<Dialog open={saveTemplateOpen} onOpenChange={setSaveTemplateOpen}>
<DialogTrigger asChild>
<Button type="button" variant="outline">
<LayoutTemplate className="mr-2 h-4 w-4" />
Save as Template
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>Save as Template</DialogTitle>
<DialogDescription>
Save the current round configuration as a reusable template.
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
<div className="space-y-2">
<Label htmlFor="templateName">Template Name</Label>
<Input
id="templateName"
value={templateName}
onChange={(e) => setTemplateName(e.target.value)}
placeholder="e.g., Standard Evaluation Round"
/>
</div>
</div>
<DialogFooter>
<Button
variant="outline"
onClick={() => setSaveTemplateOpen(false)}
>
Cancel
</Button>
<Button
disabled={!templateName.trim() || saveAsTemplate.isPending}
onClick={() => {
saveAsTemplate.mutate({
name: templateName.trim(),
roundType: roundType,
criteriaJson: criteria,
settingsJson: roundSettings,
programId: round?.programId,
})
}}
>
{saveAsTemplate.isPending && (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
)}
Save Template
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<Button type="button" variant="outline" asChild>
<Link href={`/admin/rounds/${roundId}`}>Cancel</Link>
</Button>