feat: impersonation system, semi-finalist detail page, tRPC resilience
- Add super-admin impersonation: "Login As" from user list, red banner with "Return to Admin", audit logged start/end, nested impersonation blocked, onboarding gate skipped during impersonation - Fix semi-finalist stats: check latest terminal state (not any PASSED), use passwordHash OR status=ACTIVE for activation check - Add /admin/semi-finalists detail page with search, category/status filters - Add account_reminder_days setting to notifications tab - Add tRPC resilience: retry on 503/HTML responses, custom fetch detects nginx error pages, exponential backoff (2s/4s/8s) - Reduce dashboard polling intervals (60s stats, 30s activity, 120s semi) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,8 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Progress } from '@/components/ui/progress'
|
||||
import Link from 'next/link'
|
||||
import type { Route } from 'next'
|
||||
import {
|
||||
Users,
|
||||
Send,
|
||||
@@ -12,6 +14,7 @@ import {
|
||||
AlertCircle,
|
||||
Trophy,
|
||||
Loader2,
|
||||
ExternalLink,
|
||||
} from 'lucide-react'
|
||||
import { trpc } from '@/lib/trpc/client'
|
||||
import { toast } from 'sonner'
|
||||
@@ -44,6 +47,7 @@ type SemiFinalistTrackerProps = {
|
||||
byAward: AwardStat[]
|
||||
unactivatedProjects: UnactivatedProject[]
|
||||
editionId: string
|
||||
reminderThresholdDays?: number
|
||||
}
|
||||
|
||||
const categoryLabels: Record<string, string> = {
|
||||
@@ -57,6 +61,7 @@ export function SemiFinalistTracker({
|
||||
byAward,
|
||||
unactivatedProjects,
|
||||
editionId,
|
||||
reminderThresholdDays = 3,
|
||||
}: SemiFinalistTrackerProps) {
|
||||
const utils = trpc.useUtils()
|
||||
const sendReminders = trpc.dashboard.sendAccountReminders.useMutation({
|
||||
@@ -97,9 +102,16 @@ export function SemiFinalistTracker({
|
||||
<Users className="h-4 w-4 text-brand-blue" />
|
||||
Semi-Finalist Tracker
|
||||
</CardTitle>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{totalActivated}/{totalProjects} activated
|
||||
</Badge>
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{totalActivated}/{totalProjects} activated
|
||||
</Badge>
|
||||
<Link href={`/admin/semi-finalists?editionId=${editionId}` as Route}>
|
||||
<Button variant="ghost" size="sm" className="h-6 px-2 text-xs">
|
||||
See All <ExternalLink className="ml-1 h-3 w-3" />
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
|
||||
Reference in New Issue
Block a user