feat: ST-601-ST-612 M6 polish, beveiliging en launch-ready
- ST-601/602: loading skeletons en error boundary - ST-603: Sonner toasts op alle CRUD-operaties - ST-604: DemoTooltip op uitgeschakelde knoppen - ST-605: KeyboardSensor dnd-kit, Escape sluit modals - ST-606: min-width banner < 1024px - ST-607: WCAG AA aria-labels en skip link - ST-608: rate limiting login (10/min) en registratie (5/uur) - ST-609: security integratietests cross-user toegang (7 tests) - ST-610: GitHub Actions CI/CD workflow - ST-611: README met quickstart, deployment en API-docs - ST-612: Lars-flow acceptatiechecklist - fix: settings toont gebruikersnaam i.p.v. interne id - fix: seed idempotent, testdata altijd gekoppeld aan demo-gebruiker Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
8bb8754d01
commit
d11b114fc1
27 changed files with 1858 additions and 67 deletions
|
|
@ -4,10 +4,11 @@ import { useState, useTransition, useEffect, useActionState } from 'react'
|
|||
import { useFormStatus } from 'react-dom'
|
||||
import {
|
||||
DndContext, DragEndEvent, DragOverlay, DragStartEvent,
|
||||
PointerSensor, useSensor, useSensors, closestCenter,
|
||||
KeyboardSensor, PointerSensor, useSensor, useSensors, closestCenter,
|
||||
} from '@dnd-kit/core'
|
||||
import {
|
||||
SortableContext, useSortable, verticalListSortingStrategy, arrayMove,
|
||||
sortableKeyboardCoordinates,
|
||||
} from '@dnd-kit/sortable'
|
||||
import { CSS } from '@dnd-kit/utilities'
|
||||
import { toast } from 'sonner'
|
||||
|
|
@ -97,7 +98,7 @@ function SortableTaskRow({
|
|||
</p>
|
||||
<span className="text-xs text-muted-foreground">{PRIORITY_LABELS[task.priority]}</span>
|
||||
</div>
|
||||
<button onClick={onStatusToggle} disabled={isDemo}>
|
||||
<button onClick={onStatusToggle} disabled={isDemo} aria-label={`Status: ${STATUS_LABELS[task.status]}`}>
|
||||
<Badge className={cn('text-xs border cursor-pointer hover:opacity-80 transition-opacity', STATUS_COLORS[task.status])}>
|
||||
{STATUS_LABELS[task.status]}
|
||||
</Badge>
|
||||
|
|
@ -105,7 +106,7 @@ function SortableTaskRow({
|
|||
{!isDemo && (
|
||||
<div className="opacity-0 group-hover:opacity-100 flex gap-1">
|
||||
<button onClick={() => setEditing(true)} className="text-xs text-muted-foreground hover:text-foreground">Bewerk</button>
|
||||
<button onClick={onDelete} className="text-xs text-muted-foreground hover:text-error">×</button>
|
||||
<button onClick={onDelete} aria-label="Verwijder taak" className="text-xs text-muted-foreground hover:text-error">×</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -161,7 +162,10 @@ export function TaskList({ storyId, sprintId, productId, tasks, isDemo }: TaskLi
|
|||
|
||||
const doneCount = orderedTasks.filter(t => t.status === 'DONE').length
|
||||
|
||||
const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 5 } }))
|
||||
const sensors = useSensors(
|
||||
useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),
|
||||
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
|
||||
)
|
||||
|
||||
function handleDragEnd(event: DragEndEvent) {
|
||||
const { active, over } = event
|
||||
|
|
@ -184,7 +188,8 @@ export function TaskList({ storyId, sprintId, productId, tasks, isDemo }: TaskLi
|
|||
|
||||
function handleDelete(id: string) {
|
||||
startTransition(async () => {
|
||||
await deleteTaskAction(id)
|
||||
const result = await deleteTaskAction(id)
|
||||
if (result && 'error' in result) toast.error(result.error ?? 'Verwijderen mislukt')
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue