Fix rounds management bugs and invitation flow

- Fix rounds list showing 0 projects by adding _count to program.list query
- Fix round reordering by using correct cache invalidation params
- Fix finalizeResults to auto-advance passed projects to next round
- Fix member list not updating after add/remove by invalidating user.list
- Fix invitation link error page by correcting path from /auth-error to /error
- Add /apply, /verify, /error to public paths in auth config

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-03 22:15:22 +01:00
parent 0277768ed7
commit 03c031a8b6
7 changed files with 159 additions and 73 deletions

View File

@@ -163,12 +163,20 @@ function RoundDetailContent({ roundId }: { roundId: string }) {
const handleFinalizeFiltering = async () => {
try {
const result = await finalizeResults.mutateAsync({ roundId })
toast.success(
`Finalized: ${result.passed} passed, ${result.filteredOut} filtered out`
)
if (result.advancedToRoundName) {
toast.success(
`Finalized: ${result.passed} projects advanced to "${result.advancedToRoundName}", ${result.filteredOut} filtered out`
)
} else {
toast.success(
`Finalized: ${result.passed} passed, ${result.filteredOut} filtered out. No next round to advance to.`
)
}
refetchFilteringStats()
refetchRound()
utils.project.list.invalidate()
utils.program.list.invalidate({ includeRounds: true })
utils.round.get.invalidate({ id: roundId })
} catch (error) {
toast.error(
error instanceof Error ? error.message : 'Failed to finalize'
@@ -292,43 +300,60 @@ function RoundDetailContent({ roundId }: { roundId: string }) {
Close Round
</Button>
)}
{round.status === 'DRAFT' && (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="destructive">
<Trash2 className="mr-2 h-4 w-4" />
Delete
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Delete Round</AlertDialogTitle>
<AlertDialogDescription>
This will permanently delete &ldquo;{round.name}&rdquo; and all
associated projects, assignments, and evaluations. This action
cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={() => deleteRound.mutate({ id: round.id })}
disabled={deleteRound.isPending}
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
{deleteRound.isPending ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Deleting...
</>
) : (
'Delete Round'
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="destructive">
<Trash2 className="mr-2 h-4 w-4" />
Delete
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle className="flex items-center gap-2">
{round.status === 'ACTIVE' && (
<AlertTriangle className="h-5 w-5 text-destructive" />
)}
Delete Round
</AlertDialogTitle>
<AlertDialogDescription asChild>
<div className="space-y-3">
{round.status === 'ACTIVE' && (
<div className="rounded-md bg-destructive/10 p-3 text-destructive text-sm font-medium">
Warning: This round is currently ACTIVE. Deleting it will immediately end all ongoing evaluations.
</div>
)}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)}
<p>
This will permanently delete &ldquo;{round.name}&rdquo; and all
associated data:
</p>
<ul className="list-disc list-inside text-sm space-y-1">
<li>{progress?.totalProjects || 0} projects in this round</li>
<li>{progress?.totalAssignments || 0} jury assignments</li>
<li>{progress?.completedAssignments || 0} submitted evaluations</li>
</ul>
<p className="font-medium">This action cannot be undone.</p>
</div>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={() => deleteRound.mutate({ id: round.id })}
disabled={deleteRound.isPending}
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
{deleteRound.isPending ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Deleting...
</>
) : (
'Delete Round'
)}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
</div>

View File

@@ -161,17 +161,19 @@ function RoundRow({
const reorder = trpc.round.reorder.useMutation({
onSuccess: () => {
utils.program.list.invalidate()
utils.program.list.invalidate({ includeRounds: true })
},
})
const moveUp = () => {
if (index <= 0) return
const ids = [...allRoundIds]
;[ids[index - 1], ids[index]] = [ids[index], ids[index - 1]]
reorder.mutate({ programId, roundIds: ids })
}
const moveDown = () => {
if (index >= totalRounds - 1) return
const ids = [...allRoundIds]
;[ids[index], ids[index + 1]] = [ids[index + 1], ids[index]]
reorder.mutate({ programId, roundIds: ids })
@@ -179,14 +181,14 @@ function RoundRow({
const updateStatus = trpc.round.updateStatus.useMutation({
onSuccess: () => {
utils.program.list.invalidate()
utils.program.list.invalidate({ includeRounds: true })
},
})
const deleteRound = trpc.round.delete.useMutation({
onSuccess: () => {
toast.success('Round deleted successfully')
utils.program.list.invalidate()
utils.program.list.invalidate({ includeRounds: true })
},
onError: (error) => {
toast.error(error.message || 'Failed to delete round')