diff --git a/frontend/src/features/settings/tabs/ProductUsageTab.tsx b/frontend/src/features/settings/tabs/ProductUsageTab.tsx
index 1aade5ab..28fec5fe 100644
--- a/frontend/src/features/settings/tabs/ProductUsageTab.tsx
+++ b/frontend/src/features/settings/tabs/ProductUsageTab.tsx
@@ -1,13 +1,41 @@
-import { useEffect, useRef, useState } from 'react';
+import { useEffect, useRef, useState, type ComponentType } from 'react';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next';
import {
productUsageService as service,
type ProductFeedback
} from '../../../services/productUsage.service';
+import {
+ Globe,
+ ListChecks,
+ MessageSquare,
+ Send,
+ ShieldOff,
+ Sparkles,
+ Trash2
+} from 'lucide-react';
import { useConfirm } from '../../../components/common/ConfirmDialog';
import { Button, Card } from '../../../components/common';
+/**
+ * Sections of the disclosure, in reading order. Each is a translated
+ * paragraph; the heading and icon give it a shape you can scan instead of
+ * seven identical blocks of prose.
+ */
+const DISCLOSURE: {
+ key: string;
+ heading: string;
+ Icon: ComponentType<{ className?: string }>;
+ tone?: 'positive';
+}[] = [
+ { key: 'fields', heading: 'sectionFields', Icon: ListChecks },
+ { key: 'excluded', heading: 'sectionExcluded', Icon: ShieldOff, tone: 'positive' },
+ { key: 'transport', heading: 'sectionTransport', Icon: Send },
+ { key: 'visibility', heading: 'sectionVisibility', Icon: Globe },
+ { key: 'deletion', heading: 'sectionDeletion', Icon: Trash2 },
+ { key: 'feedbackDisclosure', heading: 'sectionFeedback', Icon: MessageSquare }
+];
+
function ConsentDialog({
close,
enable,
@@ -30,58 +58,100 @@ function ConsentDialog({
ref={ref}
onCancel={close}
aria-labelledby="usage-consent-title"
- className="w-full max-w-2xl rounded-xl p-6 text-theme bg-theme-surface backdrop:bg-black/50"
+ // Column layout with its own scroll region, so the title stays put and
+ // the actions never scroll out of reach on a short screen.
+ //
+ // Surface is class-driven rather than `bg-theme-surface`: that variable
+ // does not follow dark mode, so it stayed white while the dark: text
+ // variants below turned near-white. neutral-800 is what `.card`
+ // resolves to in dark, which is what the rest of the admin UI uses.
+ className="w-full max-w-2xl max-h-[85vh] flex flex-col overflow-hidden rounded-xl p-0 bg-white dark:bg-neutral-800 shadow-xl backdrop:bg-black/50"
>
-
+
+
+ {/* A scrollable region is focusable, which is correct for keyboard use —
+ but unstyled it drew a default ring that made the disclosure look
+ like a textarea. Given a real label and ring so it reads as what it
+ is: a document you can scroll. */}
+
+
+
);
}
diff --git a/frontend/src/i18n/locales/de.json b/frontend/src/i18n/locales/de.json
index 2ffcb6f7..c578cbb3 100644
--- a/frontend/src/i18n/locales/de.json
+++ b/frontend/src/i18n/locales/de.json
@@ -11,6 +11,12 @@
"failed": "Der Vorgang konnte nicht abgeschlossen werden. Prüfe den Status und versuche es erneut.",
"purpose": "Hilf bei der Priorisierung von PicPeak-Funktionen, Fehlerbehebungen und Wartung mit groben Informationen über teilnehmende Installationen.",
"consentTitle": "Produktnutzung freiwillig teilen",
+ "sectionFields": "Was ein Bericht enthält",
+ "sectionExcluded": "Was niemals enthalten ist",
+ "sectionTransport": "Wie er gesendet wird",
+ "sectionVisibility": "Wo er sichtbar ist",
+ "sectionDeletion": "Beenden und löschen",
+ "sectionFeedback": "Feedback ist getrennt",
"fields": "Berichte enthalten einen Installationsfingerabdruck, PicPeak-Version, Berichtstag, Schema- und Signaturmetadaten, Galerie-Layouts sowie Konfiguriert/Genutzt-Werte für CRM und Unterfunktionen, Buchhaltung, Workflows, Newsletter, Gesichtserkennung, eigenes CSS, OAuth, SMTP, WhatsApp, Backups, S3 und eingebundene Freigaben. „Genutzt“ bedeutet seit der Teilnahme beobachtet, nicht wie häufig.",
"excluded": "Automatische Berichte enthalten keine Galeriebesucher, Klickverläufe, Foto- oder Galerieanzahlen, Namen, E-Mail-Adressen, Domains, Dateinamen oder Zugangsdaten.",
"transport": "Dein PicPeak-Backend verwahrt den Signaturschlüssel und sendet signierte Berichte an {{collector}}, einmal pro UTC-Tag bei Admin-Nutzung. Du kannst Berichte vorab ansehen und alle angenommenen Rohpakete herunterladen.",
diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json
index 2853dd0b..70f35126 100644
--- a/frontend/src/i18n/locales/en.json
+++ b/frontend/src/i18n/locales/en.json
@@ -11,6 +11,12 @@
"failed": "The operation could not be completed. Check the status and try again.",
"purpose": "Help prioritize PicPeak features, fixes, and maintenance using coarse information about participating installations.",
"consentTitle": "Choose whether to share product usage",
+ "sectionFields": "What a report contains",
+ "sectionExcluded": "What is never included",
+ "sectionTransport": "How it is sent",
+ "sectionVisibility": "Where it is visible",
+ "sectionDeletion": "Leaving and deleting",
+ "sectionFeedback": "Feedback is separate",
"fields": "Reports contain an installation fingerprint, PicPeak version, report day, schema and signing metadata, gallery layout choices, and configured/used booleans for CRM and its subfeatures, accounting, workflows, newsletters, face recognition, custom CSS, OAuth, SMTP, WhatsApp, backups, S3, and share mounts. “Used” means observed since joining, not how often.",
"excluded": "No gallery visitors, clickstreams, photo or gallery counts, names, emails, domains, filenames, or configuration secrets are included in automatic usage reports.",
"transport": "Your PicPeak backend keeps the signing key and sends signed reports to {{collector}} once per UTC day when an admin uses the app. You can preview reports and download every accepted raw packet.",