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

@@ -28,7 +28,7 @@ import { UserAvatar } from '@/components/shared/user-avatar'
import { UserActions, UserMobileActions } from '@/components/admin/user-actions'
import { Pagination } from '@/components/shared/pagination'
import { Plus, Users, Search } from 'lucide-react'
import { formatDate } from '@/lib/utils'
import { formatRelativeTime } from '@/lib/utils'
type RoleValue = 'SUPER_ADMIN' | 'PROGRAM_ADMIN' | 'JURY_MEMBER' | 'MENTOR' | 'OBSERVER'
type TabKey = 'all' | 'jury' | 'mentors' | 'observers' | 'admins'
@@ -221,7 +221,9 @@ export function MembersContent() {
</TableCell>
<TableCell>
{user.lastLoginAt ? (
formatDate(user.lastLoginAt)
<span title={new Date(user.lastLoginAt).toLocaleString()}>
{formatRelativeTime(user.lastLoginAt)}
</span>
) : (
<span className="text-muted-foreground">Never</span>
)}
@@ -280,6 +282,16 @@ export function MembersContent() {
: `${(user as unknown as { _count: { mentorAssignments: number; assignments: number } })._count.assignments} assigned`}
</span>
</div>
<div className="flex items-center justify-between text-sm">
<span className="text-muted-foreground">Last Login</span>
<span>
{user.lastLoginAt ? (
formatRelativeTime(user.lastLoginAt)
) : (
<span className="text-muted-foreground">Never</span>
)}
</span>
</div>
{user.expertiseTags && user.expertiseTags.length > 0 && (
<div className="flex flex-wrap gap-1">
{user.expertiseTags.map((tag) => (