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

@@ -49,8 +49,12 @@ export default function LoginPage() {
// Use window.location for external redirects or callback URLs
window.location.href = callbackUrl
}
} catch {
setError('An unexpected error occurred. Please try again.')
} catch (err: unknown) {
if (err instanceof Error && err.message.includes('429')) {
setError('Too many attempts. Please wait a few minutes before trying again.')
} else {
setError('An unexpected error occurred. Please try again.')
}
} finally {
setIsLoading(false)
}
@@ -84,8 +88,12 @@ export default function LoginPage() {
} else {
setError('Failed to send magic link. Please try again.')
}
} catch {
setError('An unexpected error occurred. Please try again.')
} catch (err: unknown) {
if (err instanceof Error && err.message.includes('429')) {
setError('Too many attempts. Please wait a few minutes before trying again.')
} else {
setError('An unexpected error occurred. Please try again.')
}
} finally {
setIsLoading(false)
}
@@ -96,8 +104,8 @@ export default function LoginPage() {
return (
<Card className="w-full max-w-md">
<CardHeader className="text-center">
<div className="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-green-100">
<CheckCircle2 className="h-6 w-6 text-green-600" />
<div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-green-100 animate-in zoom-in-50 duration-300">
<Mail className="h-8 w-8 text-green-600" />
</div>
<CardTitle className="text-xl">Check your email</CardTitle>
<CardDescription className="text-base">
@@ -105,22 +113,27 @@ export default function LoginPage() {
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<p className="text-sm text-muted-foreground text-center">
Click the link in the email to sign in. The link will expire in 15
minutes.
</p>
<div className="border-t pt-4">
<div className="rounded-lg border bg-muted/50 p-4 text-sm text-muted-foreground space-y-2">
<p>Click the link in the email to sign in. The link will expire in 15 minutes.</p>
<p>If you don&apos;t see it, check your spam folder.</p>
</div>
<div className="border-t pt-4 space-y-2">
<Button
variant="ghost"
variant="outline"
className="w-full"
onClick={() => {
setIsSent(false)
setEmail('')
setPassword('')
setError(null)
}}
>
Use a different email
Send to a different email
</Button>
<p className="text-xs text-center text-muted-foreground">
Having trouble?{' '}
<a href="mailto:support@monaco-opc.com" className="text-primary hover:underline">
Contact support
</a>
</p>
</div>
</CardContent>
</Card>

View File

@@ -22,6 +22,7 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import { cn } from '@/lib/utils'
import { toast } from 'sonner'
import { ExpertiseSelect } from '@/components/shared/expertise-select'
import { AvatarUpload } from '@/components/shared/avatar-upload'
@@ -195,6 +196,30 @@ export default function OnboardingPage() {
</div>
))}
</div>
{/* Step labels */}
<div className="flex items-center gap-2 mt-1">
{steps.slice(0, -1).map((s, i) => {
const labels: Record<string, string> = {
name: 'Name',
photo: 'Photo',
country: 'Country',
bio: 'About',
phone: 'Phone',
tags: 'Expertise',
preferences: 'Settings',
}
return (
<div key={s} className="flex-1 text-center">
<span className={cn(
'text-[10px]',
i <= currentIndex ? 'text-primary font-medium' : 'text-muted-foreground'
)}>
{labels[s] || s}
</span>
</div>
)
})}
</div>
<p className="text-sm text-muted-foreground mt-2">
Step {currentIndex + 1} of {totalVisibleSteps}
</p>
@@ -530,10 +555,12 @@ export default function OnboardingPage() {
{/* Step 7: Complete */}
{step === 'complete' && (
<CardContent className="flex flex-col items-center justify-center py-12">
<div className="rounded-full bg-green-100 p-4 mb-4">
<div className="rounded-full bg-green-100 p-4 mb-4 animate-in zoom-in-50 duration-500">
<CheckCircle className="h-12 w-12 text-green-600" />
</div>
<h2 className="text-xl font-semibold mb-2">Welcome, {name}!</h2>
<h2 className="text-xl font-semibold mb-2 animate-in fade-in slide-in-from-bottom-2 duration-500 delay-200">
Welcome, {name}!
</h2>
<p className="text-muted-foreground text-center mb-4">
Your profile is all set up. You&apos;ll be redirected to your dashboard
shortly.