feat(quick-260914-m97): Fehler-melden-Knopf in der Kopfzeile — Bildschirmfoto vor dem Dialog (html-to-image 1.11.13), Fehlerpuffer, Dialog mit Vorschau, i18n bugReport
- Knopf (Kaefer-Symbol) unmittelbar vor dem Erscheinungsbild-Schalter; captureScreenshot() laeuft VOR dem Oeffnen, Test pinnt es im toPng-Mock - computeCaptureSize (laengste Kante 1600 px, reine Funktion, getestet), dataUrlToBlob, sendBugReport als Multipart mit credentials und ohne Content-Type-Header - error-buffer: Ringpuffer 20, window error/unhandledrejection, console.error (Original bleibt), fetch-Wrapper nur bei !ok ohne Anfrage-Rumpf/Suchteil/Kopfzeilen (T-M97-02), idempotent, SSR-sicher; installiert in app-shell - Dialog mit Vorschau, Haekchen, Beschreibung, Meldungen je Status 409/413/429/502/allgemein, Admin-Link auf /admin/smtp - i18n bugReport de/en, Allowlist um "passiert"; html-to-image exakt 1.11.13, Lockfile aktualisiert Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
'use client';
|
||||
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useState } from 'react';
|
||||
import { captureScreenshot } from '@/lib/bug-report-api';
|
||||
import { useAuthStore } from '@/lib/stores/auth-store';
|
||||
import { BugReportDialog } from './bug-report-dialog';
|
||||
|
||||
/**
|
||||
* Fehler-melden-Knopf in der Kopfzeile (quick-260914-m97), Stil wie
|
||||
* `ThemeToggle`. Kernanforderung: das Bild der Seite wird aufgenommen,
|
||||
* BEVOR der Dialog erscheint — sonst waere der Dialog im Bild.
|
||||
*/
|
||||
export function BugReportButton() {
|
||||
const t = useTranslations('bugReport');
|
||||
const user = useAuthStore((s) => s.user);
|
||||
const isAdmin = user?.role === 'ADMIN' || user?.role === 'SUPER_ADMIN';
|
||||
const [capturing, setCapturing] = useState(false);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [screenshot, setScreenshot] = useState<string | null>(null);
|
||||
|
||||
const handleClick = async () => {
|
||||
if (capturing) return;
|
||||
setCapturing(true);
|
||||
// Reihenfolge ist die Kernanforderung: erst aufnehmen, dann oeffnen.
|
||||
const shot = await captureScreenshot();
|
||||
setScreenshot(shot);
|
||||
setOpen(true);
|
||||
setCapturing(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleClick}
|
||||
className="inline-flex items-center justify-center rounded-md p-2 text-muted-foreground hover:bg-muted hover:text-foreground transition-colors disabled:opacity-50"
|
||||
aria-label={t('button')}
|
||||
title={t('button')}
|
||||
disabled={capturing}
|
||||
data-bug-report-ignore="true"
|
||||
>
|
||||
{/* Kaefer-Symbol nach dem lucide-Symbol "bug" */}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="m8 2 1.88 1.88" />
|
||||
<path d="M14.12 3.88 16 2" />
|
||||
<path d="M9 7.13v-1a3.003 3.003 0 1 1 6 0v1" />
|
||||
<path d="M12 20c-3.3 0-6-2.7-6-6v-3a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v3c0 3.3-2.7 6-6 6" />
|
||||
<path d="M12 20v-9" />
|
||||
<path d="M6.53 9C4.6 8.8 3 7.1 3 5" />
|
||||
<path d="M6 13H2" />
|
||||
<path d="M3 21c0-2.1 1.7-3.9 3.8-4" />
|
||||
<path d="M20.97 5c0 2.1-1.6 3.8-3.5 4" />
|
||||
<path d="M22 13h-4" />
|
||||
<path d="M17.2 17c2.1.1 3.8 1.9 3.8 4" />
|
||||
</svg>
|
||||
</button>
|
||||
<BugReportDialog open={open} screenshot={screenshot} isAdmin={isAdmin} onClose={() => setOpen(false)} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user