diff --git a/app/(app)/layout.tsx b/app/(app)/layout.tsx index 1a5b3b9..fa41d4a 100644 --- a/app/(app)/layout.tsx +++ b/app/(app)/layout.tsx @@ -9,6 +9,7 @@ import { NavBar } from '@/components/shared/nav-bar' import { MinWidthBanner } from '@/components/shared/min-width-banner' import { StatusBar } from '@/components/shared/status-bar' import { SoloRealtimeBridge } from '@/components/solo/realtime-bridge' +import { NotificationsBridge } from '@/components/notifications/notifications-bridge' import { AlertToast } from '@/components/shared/alert-toast' import { Suspense } from 'react' @@ -92,6 +93,7 @@ export default async function AppLayout({ children }: { children: React.ReactNod + diff --git a/components/notifications/answer-modal.tsx b/components/notifications/answer-modal.tsx new file mode 100644 index 0000000..cbe574d --- /dev/null +++ b/components/notifications/answer-modal.tsx @@ -0,0 +1,157 @@ +'use client' + +// ST-1105: Modal waar de gebruiker een Claude-vraag beantwoordt (M11). +// +// Free-text Textarea (max 4000) of multiple-choice via knoppen wanneer de +// vraag `options` heeft. Submit roept answerQuestion-Server-Action aan via +// useTransition; bij succes wordt de vraag uit de store verwijderd +// (optimistisch) en sluit de modal. Demo-modus: textarea readOnly + submit +// disabled met tooltip. + +import { useState, useTransition } from 'react' +import Link from 'next/link' +import { ExternalLink } from 'lucide-react' +import { toast } from 'sonner' +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogFooter, +} from '@/components/ui/dialog' +import { Button } from '@/components/ui/button' +import { Textarea } from '@/components/ui/textarea' +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip' +import { answerQuestion } from '@/actions/questions' +import { useNotificationsStore, type NotificationQuestion } from '@/stores/notifications-store' + +const MAX_ANSWER_CHARS = 4000 + +interface AnswerModalProps { + question: NotificationQuestion | null + isDemo: boolean + onClose: () => void +} + +export function AnswerModal({ question, isDemo, onClose }: AnswerModalProps) { + const [answer, setAnswer] = useState('') + const [pending, startTransition] = useTransition() + + if (!question) return null + + const charsLeft = MAX_ANSWER_CHARS - answer.length + const tooLong = charsLeft < 0 + const submitDisabled = isDemo || pending || answer.trim().length === 0 || tooLong + + function submit(text: string) { + if (!question) return + if (isDemo) { + toast.error('Niet beschikbaar in demo-modus') + return + } + startTransition(async () => { + const res = await answerQuestion(question.id, text) + if (!res.ok) { + toast.error(res.error) + return + } + // Optimistisch verwijderen — SSE-event komt anders later met dezelfde + // remove en kost een extra render + useNotificationsStore.getState().remove(question.id) + toast.success('Antwoord verstuurd') + setAnswer('') + onClose() + }) + } + + return ( + !open && onClose()}> + + + Beantwoord Claude + + {question.story_code ?? 'story'} + {' — '} + {question.story_title} + + + + + Open in Sprint + + +
+ {question.question} +
+ + {question.options && question.options.length > 0 ? ( +
+

Kies een van de opties:

+
+ {question.options.map((opt) => ( + + ))} +
+
+ ) : ( +
+