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

@@ -8,6 +8,14 @@ import {
} from '../services/in-app-notification'
import { logAudit } from '@/server/utils/audit'
// Valid round status transitions (state machine)
const VALID_ROUND_TRANSITIONS: Record<string, string[]> = {
DRAFT: ['ACTIVE', 'ARCHIVED'], // Draft can be activated or archived
ACTIVE: ['CLOSED'], // Active rounds can only be closed
CLOSED: ['ARCHIVED'], // Closed rounds can be archived
ARCHIVED: [], // Archived is terminal — no transitions out
}
export const roundRouter = router({
/**
* List rounds for a program
@@ -296,6 +304,15 @@ export const roundRouter = router({
select: { status: true, votingStartAt: true, votingEndAt: true },
})
// Validate status transition
const allowedTransitions = VALID_ROUND_TRANSITIONS[previousRound.status] || []
if (!allowedTransitions.includes(input.status)) {
throw new TRPCError({
code: 'BAD_REQUEST',
message: `Invalid status transition: cannot change from ${previousRound.status} to ${input.status}. Allowed transitions: ${allowedTransitions.join(', ') || 'none (terminal state)'}`,
})
}
const now = new Date()
// When activating a round, if votingStartAt is in the future, update it to now