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
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:
@@ -3,6 +3,7 @@
|
||||
import { useState, useMemo } from 'react'
|
||||
import Link from 'next/link'
|
||||
import type { Route } from 'next'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { trpc } from '@/lib/trpc/client'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Input } from '@/components/ui/input'
|
||||
@@ -55,6 +56,7 @@ type SemiFinalistsContentProps = {
|
||||
}
|
||||
|
||||
export function SemiFinalistsContent({ editionId }: SemiFinalistsContentProps) {
|
||||
const router = useRouter()
|
||||
const { data, isLoading } = trpc.dashboard.getSemiFinalistDetail.useQuery(
|
||||
{ editionId },
|
||||
{ enabled: !!editionId }
|
||||
@@ -116,11 +118,9 @@ export function SemiFinalistsContent({ editionId }: SemiFinalistsContentProps) {
|
||||
{/* Header */}
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<Link href={'/admin' as Route}>
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8">
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
</Link>
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8" onClick={() => router.back()}>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
<div>
|
||||
<h1 className="text-xl font-bold tracking-tight md:text-2xl">
|
||||
Semi-Finalists
|
||||
|
||||
@@ -142,8 +142,6 @@ export function RoleNav({ navigation, roleName, user, basePath, statusBadge, edi
|
||||
<a
|
||||
key={item.name}
|
||||
href={item.href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={className}
|
||||
onClick={() => logNavClick.mutate({ url: item.href })}
|
||||
>
|
||||
@@ -291,8 +289,6 @@ export function RoleNav({ navigation, roleName, user, basePath, statusBadge, edi
|
||||
<a
|
||||
key={item.name}
|
||||
href={item.href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={() => { logNavClick.mutate({ url: item.href }); setIsMobileMenuOpen(false) }}
|
||||
className={className}
|
||||
>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import Link from 'next/link'
|
||||
import type { Route } from 'next'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { trpc } from '@/lib/trpc/client'
|
||||
import {
|
||||
Card,
|
||||
@@ -43,6 +44,7 @@ import {
|
||||
import { cn, formatDate, formatDateOnly } from '@/lib/utils'
|
||||
|
||||
export function ObserverProjectDetail({ projectId }: { projectId: string }) {
|
||||
const router = useRouter()
|
||||
const { data, isLoading } = trpc.analytics.getProjectDetail.useQuery(
|
||||
{ id: projectId },
|
||||
{ refetchInterval: 30_000 },
|
||||
@@ -78,8 +80,8 @@ export function ObserverProjectDetail({ projectId }: { projectId: string }) {
|
||||
<CardContent className="flex flex-col items-center justify-center py-12 text-center">
|
||||
<AlertCircle className="h-12 w-12 text-destructive/50" />
|
||||
<p className="mt-2 font-medium">Project Not Found</p>
|
||||
<Button asChild className="mt-4">
|
||||
<Link href={'/observer' as Route}>Back to Dashboard</Link>
|
||||
<Button className="mt-4" onClick={() => router.back()}>
|
||||
Back
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -152,11 +154,9 @@ export function ObserverProjectDetail({ projectId }: { projectId: string }) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Back button */}
|
||||
<Button variant="ghost" size="sm" className="gap-1.5 -ml-2 text-muted-foreground" asChild>
|
||||
<Link href={'/observer/projects' as Route}>
|
||||
<ArrowLeft className="h-3.5 w-3.5" />
|
||||
Back to Projects
|
||||
</Link>
|
||||
<Button variant="ghost" size="sm" className="gap-1.5 -ml-2 text-muted-foreground" onClick={() => router.back()}>
|
||||
<ArrowLeft className="h-3.5 w-3.5" />
|
||||
Back
|
||||
</Button>
|
||||
|
||||
{/* Project Header */}
|
||||
|
||||
@@ -927,13 +927,13 @@ function PlatformFeaturesSection({ settings }: { settings: Record<string, string
|
||||
<Label className="text-sm font-medium">Learning Hub</Label>
|
||||
<SettingToggle
|
||||
label="Use External Learning Hub"
|
||||
description="When enabled, jury and mentor navigation links will open the external URL instead of the built-in Learning Hub"
|
||||
description="When enabled, all user navigation links (jury, mentor, applicant) will redirect to the external URL instead of the built-in Learning Hub"
|
||||
settingKey="learning_hub_external"
|
||||
value={settings.learning_hub_external || 'false'}
|
||||
/>
|
||||
<SettingInput
|
||||
label="External URL"
|
||||
description="The URL to redirect jury and mentor users to (e.g. Google Drive, Notion, etc.)"
|
||||
description="The URL to redirect users to (e.g. Google Drive, Notion, etc.)"
|
||||
settingKey="learning_hub_external_url"
|
||||
value={settings.learning_hub_external_url || ''}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user