'use client'; import Link from 'next/link'; import { useTranslations } from 'next-intl'; import { useEffect, useRef, useState } from 'react'; import { appVersion } from '@/lib/app-version'; import { dataUrlToBlob, sendBugReport } from '@/lib/bug-report-api'; import { getDesktopClientInfo, isDesktopClient } from '@/lib/desktop-client'; import { formatErrorsForReport } from '@/lib/error-buffer'; /** * Dialog des Fehler-melden-Knopfs (quick-260914-m97). Muster: * `marketplace/components/ActivationDialog.tsx` (Overlay, `role="dialog"`, * Escape, Fokus). Bekommt das bereits aufgenommene Bild als Data-URL — * die Aufnahme passiert im Knopf, BEVOR dieser Dialog erscheint. * * Der Dialog wird nur eingehaengt, solange er offen ist (quick-260921-ldf): * jedes Oeffnen ist ein frischer Mount, damit gilt der Anfangszustand schon * im ERSTEN Commit. Vorher zog ein Effekt den Zustand erst einen Commit * spaeter nach — dazwischen stand sichtbar "Bild da, Haekchen aus" im DOM. */ interface BugReportDialogProps { screenshot: string | null; isAdmin: boolean; onClose: () => void; } type Status = 'ready' | 'sending' | 'sent' | 'failed'; export function BugReportDialog({ screenshot, isAdmin, onClose }: BugReportDialogProps) { const t = useTranslations('bugReport'); const textareaRef = useRef(null); const [status, setStatus] = useState('ready'); const [failedStatus, setFailedStatus] = useState(0); const [description, setDescription] = useState(''); // Das Haekchen wird beim Rendern aus `screenshot` abgeleitet, nicht per // Effekt nachgezogen: gibt es ein Bild, ist es an. `attachChoice` haelt // allein die bewusste Abwahl des Nutzers. const [attachChoice, setAttachChoice] = useState(null); const attach = screenshot !== null && (attachChoice ?? true); useEffect(() => { textareaRef.current?.focus(); }, []); useEffect(() => { const handler = (e: KeyboardEvent) => { if (e.key === 'Escape' && status !== 'sending') onClose(); }; document.addEventListener('keydown', handler); return () => document.removeEventListener('keydown', handler); }, [status, onClose]); const handleSend = async () => { setStatus('sending'); // Herkunft (quick-260918-gza): tessera_desktop entscheidet Desktop/ // Browser, tessera_desktop_client liefert die Details dazu; fehlt es // (alter Client), bleibt es bei Desktop ohne Details. const desktop = isDesktopClient(); const info = desktop ? getDesktopClientInfo() : null; const result = await sendBugReport({ description: description.trim(), page: window.location.pathname + window.location.search, webVersion: appVersion.version, webChannel: appVersion.channel, webCommit: appVersion.commit, userAgent: navigator.userAgent, viewport: `${window.innerWidth}x${window.innerHeight}`, clientTime: new Date().toISOString(), errors: formatErrorsForReport(), screenshot: attach && screenshot ? dataUrlToBlob(screenshot) : null, clientKind: desktop ? 'desktop' : 'browser', clientOs: info?.os ?? '', clientVersion: info?.version ?? '', clientCommit: info?.commit ?? '', }); if (result.ok) { setStatus('sent'); } else { setFailedStatus(result.status); setStatus('failed'); } }; const errorKey = failedStatus === 409 ? 'errorNotConfigured' : failedStatus === 429 ? 'errorTooMany' : failedStatus === 413 ? 'errorTooLarge' : failedStatus === 502 ? 'errorSendFailed' : 'errorGeneric'; const busy = status === 'sending'; return (

{t('title')}

{status === 'sent' ? ( <>

{t('sent')}

) : ( <>

{t('intro')}

{screenshot ? ( {t('screenshotAlt')} ) : (

{t('screenshotUnavailable')}

)}
setAttachChoice(e.target.checked)} />