)
+
+ return (
+ <>
+ {logoElement}
+ {canEnlarge && (
+
+ )}
+ >
+ )
}
diff --git a/src/lib/auth.ts b/src/lib/auth.ts
index d962b86..ac26502 100644
--- a/src/lib/auth.ts
+++ b/src/lib/auth.ts
@@ -18,7 +18,8 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
logger: {
error(error) {
// CredentialsSignin is expected (wrong password, bots) — already logged to AuditLog with detail
- if (error?.name === 'CredentialsSignin') return
+ // Check both name and type: name gets minified in production builds
+ if (error?.name === 'CredentialsSignin' || (error as unknown as { type?: string })?.type === 'CredentialsSignin') return
console.error('[auth][error]', error)
},
warn(code) {
diff --git a/src/lib/session-update.ts b/src/lib/session-update.ts
new file mode 100644
index 0000000..e9560bb
--- /dev/null
+++ b/src/lib/session-update.ts
@@ -0,0 +1,23 @@
+/**
+ * Direct session update that bypasses useSession().update()'s `if (loading) return;` guard.
+ * This ensures the JWT cookie is always updated, even when another session refresh is in-flight.
+ */
+export async function directSessionUpdate(data: Record
): Promise {
+ try {
+ // Get CSRF token
+ const csrfResp = await fetch('/api/auth/csrf')
+ if (!csrfResp.ok) return false
+ const { csrfToken } = await csrfResp.json()
+
+ // POST session update
+ const resp = await fetch('/api/auth/session', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ csrfToken, data }),
+ })
+
+ return resp.ok
+ } catch {
+ return false
+ }
+}
diff --git a/src/server/routers/dashboard.ts b/src/server/routers/dashboard.ts
index c6754ad..f795283 100644
--- a/src/server/routers/dashboard.ts
+++ b/src/server/routers/dashboard.ts
@@ -720,17 +720,34 @@ export const dashboardRouter = router({
orderBy: { sortOrder: 'asc' },
})
- // Determine which round to show
+ // Determine which round to show — pick the latest round that has PASSED projects
let selectedRoundId = roundId
if (!selectedRoundId) {
- // Pick latest active round, or if none, the most recently closed
- const activeRound = allRounds.find(r => r.status === 'ROUND_ACTIVE')
- if (activeRound) {
- selectedRoundId = activeRound.id
+ // Find rounds that actually have PASSED projects (most useful default)
+ const roundsWithPassed = await ctx.prisma.projectRoundState.groupBy({
+ by: ['roundId'],
+ where: {
+ state: 'PASSED',
+ roundId: { in: allRounds.map(r => r.id) },
+ },
+ })
+ const passedRoundIds = new Set(roundsWithPassed.map(r => r.roundId))
+ // Pick the latest round (highest sortOrder) that has PASSED projects
+ const roundsWithPassedSorted = allRounds
+ .filter(r => passedRoundIds.has(r.id))
+ .sort((a, b) => b.sortOrder - a.sortOrder)
+ if (roundsWithPassedSorted.length > 0) {
+ selectedRoundId = roundsWithPassedSorted[0].id
} else {
- const closedRounds = allRounds.filter(r => r.status === 'ROUND_CLOSED' || r.status === 'ROUND_ARCHIVED')
- if (closedRounds.length > 0) {
- selectedRoundId = closedRounds[closedRounds.length - 1].id
+ // Fallback: active round, then most recently closed
+ const activeRound = allRounds.find(r => r.status === 'ROUND_ACTIVE')
+ if (activeRound) {
+ selectedRoundId = activeRound.id
+ } else {
+ const closedRounds = allRounds.filter(r => r.status === 'ROUND_CLOSED' || r.status === 'ROUND_ARCHIVED')
+ if (closedRounds.length > 0) {
+ selectedRoundId = closedRounds[closedRounds.length - 1].id
+ }
}
}
}
diff --git a/src/types/competition-configs.ts b/src/types/competition-configs.ts
index 4737dad..f08468a 100644
--- a/src/types/competition-configs.ts
+++ b/src/types/competition-configs.ts
@@ -160,19 +160,6 @@ export type EvaluationConfig = z.infer
export const SubmissionConfigSchema = z.object({
...generalSettingsFields,
- eligibleStatuses: z
- .array(
- z.enum([
- 'PENDING',
- 'IN_PROGRESS',
- 'PASSED',
- 'REJECTED',
- 'COMPLETED',
- 'WITHDRAWN',
- ]),
- )
- .default(['PASSED']),
-
notifyEligibleTeams: z.boolean().default(true),
lockPreviousWindows: z.boolean().default(true),
})