Performance optimization, applicant portal, and missing DB migration
Performance: - Convert admin dashboard from SSR to client-side tRPC (fixes 503/ChunkLoadError) - New dashboard.getStats tRPC endpoint batches 16 queries into single response - Parallelize jury dashboard queries (assignments + gracePeriods via Promise.all) - Add project.getFullDetail combined endpoint (project + assignments + stats) - Configure Prisma connection pool (connection_limit=20, pool_timeout=10) - Add optimizePackageImports for lucide-react tree-shaking - Increase React Query staleTime from 1min to 5min Applicant portal: - Add applicant layout, nav, dashboard, documents, team, and mentor pages - Add applicant router with document and team management endpoints - Add chunk error recovery utility - Update role nav and auth redirect for applicant role Database: - Add migration for missing schema elements (SpecialAward job tracking columns, WizardTemplate table, missing indexes) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@ import Link from 'next/link'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { AlertTriangle, RefreshCw, ClipboardList } from 'lucide-react'
|
||||
import { isChunkLoadError, attemptChunkErrorRecovery } from '@/lib/chunk-error-recovery'
|
||||
|
||||
export default function JuryError({
|
||||
error,
|
||||
@@ -15,8 +16,14 @@ export default function JuryError({
|
||||
}) {
|
||||
useEffect(() => {
|
||||
console.error('Jury section error:', error)
|
||||
|
||||
if (isChunkLoadError(error)) {
|
||||
attemptChunkErrorRecovery('jury')
|
||||
}
|
||||
}, [error])
|
||||
|
||||
const isChunk = isChunkLoadError(error)
|
||||
|
||||
return (
|
||||
<div className="flex min-h-[50vh] items-center justify-center p-4">
|
||||
<Card className="max-w-md">
|
||||
@@ -28,22 +35,32 @@ export default function JuryError({
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4 text-center">
|
||||
<p className="text-muted-foreground">
|
||||
An error occurred while loading this page. Please try again or
|
||||
return to your assignments.
|
||||
{isChunk
|
||||
? 'A new version of the platform may have been deployed. Please reload the page.'
|
||||
: 'An error occurred while loading this page. Please try again or return to your assignments.'}
|
||||
</p>
|
||||
<div className="flex justify-center gap-2">
|
||||
<Button onClick={reset} variant="outline">
|
||||
<RefreshCw className="mr-2 h-4 w-4" />
|
||||
Try Again
|
||||
</Button>
|
||||
<Button asChild>
|
||||
<Link href="/jury">
|
||||
<ClipboardList className="mr-2 h-4 w-4" />
|
||||
My Assignments
|
||||
</Link>
|
||||
</Button>
|
||||
{isChunk ? (
|
||||
<Button onClick={() => window.location.reload()}>
|
||||
<RefreshCw className="mr-2 h-4 w-4" />
|
||||
Reload Page
|
||||
</Button>
|
||||
) : (
|
||||
<>
|
||||
<Button onClick={reset} variant="outline">
|
||||
<RefreshCw className="mr-2 h-4 w-4" />
|
||||
Try Again
|
||||
</Button>
|
||||
<Button asChild>
|
||||
<Link href="/jury">
|
||||
<ClipboardList className="mr-2 h-4 w-4" />
|
||||
My Assignments
|
||||
</Link>
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{error.digest && (
|
||||
{!isChunk && error.digest && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Error ID: {error.digest}
|
||||
</p>
|
||||
|
||||
@@ -47,50 +47,62 @@ async function JuryDashboardContent() {
|
||||
return null
|
||||
}
|
||||
|
||||
// Get all assignments for this jury member
|
||||
const assignments = await prisma.assignment.findMany({
|
||||
where: { userId },
|
||||
include: {
|
||||
project: {
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
teamName: true,
|
||||
country: true,
|
||||
// Get assignments and grace periods in parallel
|
||||
const [assignments, gracePeriods] = await Promise.all([
|
||||
prisma.assignment.findMany({
|
||||
where: { userId },
|
||||
include: {
|
||||
project: {
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
teamName: true,
|
||||
country: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
round: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
status: true,
|
||||
votingStartAt: true,
|
||||
votingEndAt: true,
|
||||
program: {
|
||||
select: {
|
||||
name: true,
|
||||
year: true,
|
||||
round: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
status: true,
|
||||
votingStartAt: true,
|
||||
votingEndAt: true,
|
||||
program: {
|
||||
select: {
|
||||
name: true,
|
||||
year: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
evaluation: {
|
||||
select: {
|
||||
id: true,
|
||||
status: true,
|
||||
submittedAt: true,
|
||||
criterionScoresJson: true,
|
||||
form: {
|
||||
select: { criteriaJson: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
evaluation: {
|
||||
select: {
|
||||
id: true,
|
||||
status: true,
|
||||
submittedAt: true,
|
||||
criterionScoresJson: true,
|
||||
form: {
|
||||
select: { criteriaJson: true },
|
||||
},
|
||||
},
|
||||
orderBy: [
|
||||
{ round: { votingEndAt: 'asc' } },
|
||||
{ createdAt: 'asc' },
|
||||
],
|
||||
}),
|
||||
prisma.gracePeriod.findMany({
|
||||
where: {
|
||||
userId,
|
||||
extendedUntil: { gte: new Date() },
|
||||
},
|
||||
},
|
||||
orderBy: [
|
||||
{ round: { votingEndAt: 'asc' } },
|
||||
{ createdAt: 'asc' },
|
||||
],
|
||||
})
|
||||
select: {
|
||||
roundId: true,
|
||||
extendedUntil: true,
|
||||
},
|
||||
}),
|
||||
])
|
||||
|
||||
// Calculate stats
|
||||
const totalAssignments = assignments.length
|
||||
@@ -122,18 +134,6 @@ async function JuryDashboardContent() {
|
||||
{} as Record<string, { round: (typeof assignments)[0]['round']; assignments: typeof assignments }>
|
||||
)
|
||||
|
||||
// Get grace periods for this user
|
||||
const gracePeriods = await prisma.gracePeriod.findMany({
|
||||
where: {
|
||||
userId,
|
||||
extendedUntil: { gte: new Date() },
|
||||
},
|
||||
select: {
|
||||
roundId: true,
|
||||
extendedUntil: true,
|
||||
},
|
||||
})
|
||||
|
||||
const graceByRound = new Map<string, Date>()
|
||||
for (const gp of gracePeriods) {
|
||||
const existing = graceByRound.get(gp.roundId)
|
||||
|
||||
Reference in New Issue
Block a user