diff --git a/__tests__/api/pair-claim.test.ts b/__tests__/api/pair-claim.test.ts index 3c594e5..60406f2 100644 --- a/__tests__/api/pair-claim.test.ts +++ b/__tests__/api/pair-claim.test.ts @@ -103,7 +103,7 @@ describe('POST /api/auth/pair/claim', () => { expect(mockClearPairCookie).toHaveBeenCalledTimes(1) }) - it('demo-user: isDemo doorgezet als vangnet', async () => { + it('demo-user: claim geblokkeerd met 403 (ST-1110.4)', async () => { mockReadPairCookie.mockResolvedValue(COOKIE_TOKEN) mockPrisma.loginPairing.updateMany.mockResolvedValue({ count: 1 }) mockPrisma.loginPairing.findUnique.mockResolvedValue({ @@ -112,8 +112,10 @@ describe('POST /api/auth/pair/claim', () => { }) const res = await POST(makePost({ pairingId: PAIRING_ID })) - expect(res.status).toBe(200) - expect(mockSession.isDemo).toBe(true) + expect(res.status).toBe(403) + const body = await res.json() + expect(body.error).toMatch(/demo-modus/i) + expect(mockClearPairCookie).toHaveBeenCalledTimes(1) }) it('401 zonder s4m_pair-cookie', async () => { diff --git a/__tests__/api/pair-start.test.ts b/__tests__/api/pair-start.test.ts index 8c7207f..7543458 100644 --- a/__tests__/api/pair-start.test.ts +++ b/__tests__/api/pair-start.test.ts @@ -1,7 +1,12 @@ import { describe, it, expect, vi, beforeEach } from 'vitest' -const { cookieJar } = vi.hoisted(() => ({ +const { cookieJar, mockGetIronSession } = vi.hoisted(() => ({ cookieJar: { set: vi.fn(), get: vi.fn(), delete: vi.fn() }, + mockGetIronSession: vi.fn().mockResolvedValue({ isDemo: false }), +})) + +vi.mock('iron-session', () => ({ + getIronSession: mockGetIronSession, })) vi.mock('@/lib/prisma', () => ({ diff --git a/app/(app)/settings/page.tsx b/app/(app)/settings/page.tsx index 07b05a8..b299e45 100644 --- a/app/(app)/settings/page.tsx +++ b/app/(app)/settings/page.tsx @@ -95,10 +95,7 @@ export default async function SettingsPage() {

{!session.isDemo && ( - + + Nieuw product )} @@ -149,8 +146,8 @@ export default async function SettingsPage() { label="Maak actief" /> )} - {pb.kind === 'member' && !session.isDemo && ( - + {pb.kind === 'member' && ( + )} diff --git a/components/backlog/pbi-list.tsx b/components/backlog/pbi-list.tsx index 9586fd8..c4b0171 100644 --- a/components/backlog/pbi-list.tsx +++ b/components/backlog/pbi-list.tsx @@ -32,6 +32,7 @@ import { reorderPbisAction, updatePbiPriorityAction } from '@/actions/stories' import { cn } from '@/lib/utils' import { PbiDialog, type PbiDialogState } from './pbi-dialog' import { BacklogCard } from './backlog-card' +import { DemoTooltip } from '@/components/shared/demo-tooltip' import { PRIORITY_COLORS } from '@/components/shared/priority-select' import { PBI_STATUS_LABELS, PBI_STATUS_COLORS } from '@/components/shared/pbi-status-select' import type { PbiStatusApi } from '@/lib/task-status' @@ -164,24 +165,30 @@ function SortablePbiRow({ {PBI_STATUS_LABELS[pbi.status]} } - actions={!isDemo ? ( + actions={
- - + + + + + +
- ) : undefined} + } /> ) } @@ -383,15 +390,16 @@ export function PbiList({ productId, pbis, isDemo }: PbiListProps) { - {!isDemo && ( + - )} + } /> @@ -400,11 +408,11 @@ export function PbiList({ productId, pbis, isDemo }: PbiListProps) { {pbis.length === 0 ? (

Nog geen PBI's aangemaakt.

- {!isDemo && ( - - )} +
) : ( = { DONE: 'Klaar', } -function SubmitButton({ label }: { label: string }) { +function SubmitButton({ label, disabled }: { label: string; disabled?: boolean }) { const { pending } = useFormStatus() return ( - ) @@ -262,9 +263,9 @@ export function StoryDialog({ state, onClose, isDemo = false }: StoryDialogProps )} - {isEdit && !isDemo && ( + {isEdit && (
- {confirmDelete ? ( + {!isDemo && confirmDelete ? (
Weet je het zeker? Taken worden ook verwijderd. @@ -277,24 +278,29 @@ export function StoryDialog({ state, onClose, isDemo = false }: StoryDialogProps
) : ( - + + + )}
)}
}> - {isDemo ? 'Sluiten' : 'Annuleren'} + Annuleren - {!isDemo && } + + +
diff --git a/components/backlog/story-panel.tsx b/components/backlog/story-panel.tsx index 5f4eb6d..bb65854 100644 --- a/components/backlog/story-panel.tsx +++ b/components/backlog/story-panel.tsx @@ -30,6 +30,7 @@ import { usePlannerStore } from '@/stores/planner-store' import { reorderStoriesAction } from '@/actions/stories' import { StoryDialog, type StoryDialogState } from './story-dialog' import { BacklogCard } from './backlog-card' +import { DemoTooltip } from '@/components/shared/demo-tooltip' import { cn } from '@/lib/utils' type SortMode = 'priority' | 'code' | 'date' @@ -223,14 +224,17 @@ export function StoryPanel({ productId, storiesByPbi, isDemo }: StoryPanelProps) Klaar - {selectedPbiId && !isDemo && ( - + {selectedPbiId && ( + + + )} } @@ -244,10 +248,12 @@ export function StoryPanel({ productId, storiesByPbi, isDemo }: StoryPanelProps) ) : rawStories.length === 0 ? (

Nog geen stories voor dit PBI.

- {!isDemo && selectedPbiId && ( - + {selectedPbiId && ( + + + )}
) : ( diff --git a/components/dashboard/product-list.tsx b/components/dashboard/product-list.tsx index fa1280b..d01f8ce 100644 --- a/components/dashboard/product-list.tsx +++ b/components/dashboard/product-list.tsx @@ -7,6 +7,7 @@ import { toast } from 'sonner' import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' import { CodeBadge } from '@/components/shared/code-badge' +import { DemoTooltip } from '@/components/shared/demo-tooltip' import { restoreProductAction } from '@/actions/products' import { setActiveProductAction } from '@/actions/active-product' @@ -38,7 +39,6 @@ export function ProductList({ products, isDemo, showArchived = false, activeProd } function handleActivate(id: string) { - if (isDemo) { toast.error('Niet beschikbaar in demo-modus'); return } startTransition(async () => { const result = await setActiveProductAction(id) if (result?.error) toast.error(typeof result.error === 'string' ? result.error : 'Activeren mislukt') @@ -54,11 +54,11 @@ export function ProductList({ products, isDemo, showArchived = false, activeProd ? 'Geen gearchiveerde producten.' : 'Je hebt nog geen producten aangemaakt.'}

- {!isDemo && !showArchived && ( - - )} + ) } @@ -103,21 +103,27 @@ export function ProductList({ products, isDemo, showArchived = false, activeProd product.id === activeProductId ? Actief : ( - + + + ) )} - {showArchived && !isDemo && ( - + {showArchived && ( + + + )} diff --git a/components/settings/leave-product-button.tsx b/components/settings/leave-product-button.tsx index 4dba5ad..976e480 100644 --- a/components/settings/leave-product-button.tsx +++ b/components/settings/leave-product-button.tsx @@ -2,13 +2,15 @@ import { useState, useTransition } from 'react' import { Button } from '@/components/ui/button' +import { DemoTooltip } from '@/components/shared/demo-tooltip' import { leaveProductAction } from '@/actions/products' interface LeaveProductButtonProps { productId: string + isDemo?: boolean } -export function LeaveProductButton({ productId }: LeaveProductButtonProps) { +export function LeaveProductButton({ productId, isDemo = false }: LeaveProductButtonProps) { const [confirming, setConfirming] = useState(false) const [isPending, startTransition] = useTransition() @@ -32,13 +34,16 @@ export function LeaveProductButton({ productId }: LeaveProductButtonProps) { } return ( - + + + ) } diff --git a/components/settings/token-manager.tsx b/components/settings/token-manager.tsx index 6f48b51..ba1f705 100644 --- a/components/settings/token-manager.tsx +++ b/components/settings/token-manager.tsx @@ -4,6 +4,7 @@ import { useState, useActionState, useTransition } from 'react' import { useFormStatus } from 'react-dom' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' +import { DemoTooltip } from '@/components/shared/demo-tooltip' import { createApiTokenAction, revokeApiTokenAction } from '@/actions/api-tokens' interface Token { @@ -18,12 +19,14 @@ interface TokenManagerProps { isDemo: boolean } -function CreateSubmitButton() { +function CreateSubmitButton({ isDemo }: { isDemo: boolean }) { const { pending } = useFormStatus() return ( - + + + ) } @@ -80,21 +83,19 @@ export function TokenManager({ tokens, isDemo }: TokenManagerProps) { )} {/* Create form */} - {!isDemo && ( -
-

Nieuw token aanmaken

-
- - - - {typeof state?.error === 'string' && ( -

{state.error}

- )} -

- Maximaal 10 actieve tokens. Je hebt er nu {activeTokens.length}. -

-
- )} +
+

Nieuw token aanmaken

+
+ + + + {typeof state?.error === 'string' && ( +

{state.error}

+ )} +

+ Maximaal 10 actieve tokens. Je hebt er nu {activeTokens.length}. +

+
{/* Active tokens */}
@@ -111,16 +112,17 @@ export function TokenManager({ tokens, isDemo }: TokenManagerProps) { Aangemaakt {new Date(token.created_at).toLocaleDateString('nl-NL')}

- {!isDemo && ( + - )} + ))} diff --git a/components/shared/activate-product-button.tsx b/components/shared/activate-product-button.tsx index 90cf54b..90c19c4 100644 --- a/components/shared/activate-product-button.tsx +++ b/components/shared/activate-product-button.tsx @@ -3,6 +3,7 @@ import { useRouter } from 'next/navigation' import { useTransition } from 'react' import { toast } from 'sonner' +import { DemoTooltip } from '@/components/shared/demo-tooltip' import { setActiveProductAction } from '@/actions/active-product' interface Props { @@ -18,7 +19,6 @@ export function ActivateProductButton({ productId, isDemo, redirectTo, label = ' const [isPending, startTransition] = useTransition() function handleActivate() { - if (isDemo) { toast.error('Niet beschikbaar in demo-modus'); return } startTransition(async () => { const result = await setActiveProductAction(productId) if (result?.error) toast.error(typeof result.error === 'string' ? result.error : 'Activeren mislukt') @@ -28,12 +28,14 @@ export function ActivateProductButton({ productId, isDemo, redirectTo, label = ' } return ( - + + + ) } diff --git a/components/sprint/sprint-backlog.tsx b/components/sprint/sprint-backlog.tsx index 5d4ceb7..3555912 100644 --- a/components/sprint/sprint-backlog.tsx +++ b/components/sprint/sprint-backlog.tsx @@ -189,15 +189,16 @@ function SortableSprintRow({ - {!isDemo && ( + - )} + @@ -352,14 +353,15 @@ function DraggablePbiStoryRow({ - {!isDemo && ( + - )} + ) diff --git a/components/sprint/task-list.tsx b/components/sprint/task-list.tsx index 3261322..99650c3 100644 --- a/components/sprint/task-list.tsx +++ b/components/sprint/task-list.tsx @@ -24,6 +24,7 @@ import { createTaskAction, updateTaskStatusAction, updateTaskAction, deleteTaskAction, reorderTasksAction, } from '@/actions/tasks' +import { DemoTooltip } from '@/components/shared/demo-tooltip' import { cn } from '@/lib/utils' const STATUS_CYCLE: Record = { @@ -99,7 +100,7 @@ function SortableTaskRow({ PRIORITY_BORDER[task.priority] )}> {!isDemo && ( - + )}
@@ -114,12 +115,14 @@ function SortableTaskRow({ {STATUS_LABELS[task.status]} - {!isDemo && ( -
- - -
- )} +
+ + + + + + +
@@ -220,9 +223,9 @@ export function TaskList({ storyId, storyCode, sprintId, productId: _productId, actions={ <> {doneCount}/{orderedTasks.length} klaar - {!isDemo && ( - - )} + + + } /> @@ -235,7 +238,9 @@ export function TaskList({ storyId, storyCode, sprintId, productId: _productId, {orderedTasks.length === 0 && !creating ? (

Geen taken voor deze story.

- {!isDemo && } + + +
) : (