feat: router.back() navigation, read-only evaluation view, auth audit logging
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m53s

- Convert all Back buttons platform-wide (38 files) to use router.back()
  for natural browser-back behavior regardless of entry point
- Add read-only view for submitted evaluations in closed rounds with
  blue banner, disabled inputs, and contextual back navigation
- Add auth audit logs: MAGIC_LINK_SENT, PASSWORD_RESET_LINK_CLICKED,
  PASSWORD_RESET_LINK_EXPIRED, PASSWORD_RESET_LINK_INVALID
- Learning Hub links navigate in same window for all roles
- Update settings descriptions to reflect all-user scope

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 14:25:56 +01:00
parent a556732b46
commit a1e758bc39
44 changed files with 398 additions and 384 deletions

View File

@@ -1,7 +1,7 @@
'use client'
import { useState, useMemo, useCallback, useRef, useEffect } from 'react'
import { useParams, useSearchParams } from 'next/navigation'
import { useParams, useRouter } from 'next/navigation'
import Link from 'next/link'
import type { Route } from 'next'
import { trpc } from '@/lib/trpc/client'
@@ -152,8 +152,7 @@ const stateColors: Record<string, string> = Object.fromEntries(
export default function RoundDetailPage() {
const params = useParams()
const roundId = params.roundId as string
const searchParams = useSearchParams()
const backUrl = searchParams.get('from')
const router = useRouter()
const [config, setConfig] = useState<Record<string, unknown>>({})
const [autosaveStatus, setAutosaveStatus] = useState<'idle' | 'saving' | 'saved' | 'error'>('idle')
@@ -546,11 +545,9 @@ export default function RoundDetailPage() {
return (
<div className="space-y-6">
<div className="flex items-center gap-3">
<Link href={'/admin/rounds' as Route}>
<Button variant="ghost" size="icon" className="h-8 w-8" aria-label="Back">
<ArrowLeft className="h-4 w-4" />
</Button>
</Link>
<Button variant="ghost" size="icon" className="h-8 w-8" aria-label="Back" onClick={() => router.back()}>
<ArrowLeft className="h-4 w-4" />
</Button>
<div>
<h1 className="text-xl font-bold">Round Not Found</h1>
<p className="text-sm text-muted-foreground">This round does not exist.</p>
@@ -622,12 +619,10 @@ export default function RoundDetailPage() {
>
<div className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
<div className="flex items-start gap-3 min-w-0">
<Link href={(backUrl ?? (round.specialAwardId ? `/admin/awards/${round.specialAwardId}` : '/admin/rounds')) as Route} className="mt-0.5 shrink-0">
<Button variant="ghost" size="sm" className="h-8 text-white/80 hover:text-white hover:bg-white/10 gap-1.5" aria-label={round.specialAwardId ? 'Back to Award' : 'Back to rounds'}>
<ArrowLeft className="h-4 w-4" />
<span className="text-xs hidden sm:inline">{round.specialAwardId ? 'Back to Award' : 'Back to Rounds'}</span>
</Button>
</Link>
<Button variant="ghost" size="sm" className="mt-0.5 shrink-0 h-8 text-white/80 hover:text-white hover:bg-white/10 gap-1.5" aria-label="Back" onClick={() => router.back()}>
<ArrowLeft className="h-4 w-4" />
<span className="text-xs hidden sm:inline">Back</span>
</Button>
<div className="min-w-0">
<div className="flex flex-wrap items-center gap-2.5">
{/* 4.6 Inline-editable round name */}