feat(ST-1112): add Suspense skeleton for edit-mode task loading

Extracts task fetch into EditTaskLoader (async server component) so
the sprint board renders immediately while the task loads.
TaskDialogSkeleton shows 3 grey bars during the fetch. Invalid or
out-of-scope task IDs redirect to closePath.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Janpeter Visser 2026-04-29 23:54:20 +02:00
parent e27d25a662
commit 03c90c1fdd
3 changed files with 104 additions and 28 deletions

View file

@ -0,0 +1,47 @@
import { redirect } from 'next/navigation'
import { prisma } from '@/lib/prisma'
import { productAccessFilter } from '@/lib/product-access'
import { TaskDialog } from './task-dialog'
interface EditTaskLoaderProps {
taskId: string
userId: string
productId: string
closePath: string
isDemo: boolean
}
export async function EditTaskLoader({
taskId,
userId,
productId,
closePath,
isDemo,
}: EditTaskLoaderProps) {
const task = await prisma.task.findFirst({
where: {
id: taskId,
story: { product: productAccessFilter(userId) },
},
select: {
id: true,
title: true,
description: true,
implementation_plan: true,
priority: true,
status: true,
created_at: true,
},
})
if (!task) redirect(closePath)
return (
<TaskDialog
task={task}
productId={productId}
closePath={closePath}
isDemo={isDemo}
/>
)
}

View file

@ -0,0 +1,41 @@
import { Skeleton } from '@/components/ui/skeleton'
import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog'
import { cn } from '@/lib/utils'
export function TaskDialogSkeleton() {
return (
<Dialog open>
<DialogContent
showCloseButton={false}
className={cn(
'flex flex-col p-0 gap-0',
'max-h-[90vh] w-full max-w-[calc(100%-2rem)]',
'sm:max-w-[90vw] sm:max-h-[85vh]',
'lg:max-w-[50vw] lg:min-w-[480px]',
)}
>
<DialogTitle className="sr-only">Taak laden</DialogTitle>
{/* Header */}
<div className="px-6 pt-5 pb-4 border-b border-outline-variant shrink-0">
<Skeleton className="h-7 w-40" />
</div>
{/* Body — 3 bars mimicking title + description + plan */}
<div className="flex-1 px-6 py-6 space-y-6">
<Skeleton className="h-14 w-full" />
<Skeleton className="h-24 w-full" />
<Skeleton className="h-32 w-full" />
</div>
{/* Footer */}
<div className="border-t border-outline-variant px-6 py-4 shrink-0">
<div className="flex justify-end gap-2">
<Skeleton className="h-8 w-24" />
<Skeleton className="h-8 w-24" />
</div>
</div>
</DialogContent>
</Dialog>
)
}