diff --git a/messages/de.json b/messages/de.json index 0509f4a9d..9a9480e87 100644 --- a/messages/de.json +++ b/messages/de.json @@ -4122,6 +4122,21 @@ "title": "Benachrichtigungskanäle", "description": "APNs, Telegram, ntfy, Web Push, Webhook und E-Mail." } + }, + "modules": { + "title": "Module", + "saved": "Gespeichert.", + "error": "Konnte nicht gespeichert werden. Bitte erneut versuchen.", + "toggleable": { + "title": "Was du trackst", + "description": "Optionale Bereiche. Deaktivierte Module verschwinden aus Navigation, Kacheln und Auswertungen — deine Daten bleiben erhalten." + }, + "core": { + "title": "Immer aktiv", + "description": "Die Grundlagen von HealthLog. Diese Bereiche lassen sich nicht abschalten.", + "note": "Kernbereich — immer aktiv" + }, + "description": "Lege fest, was HealthLog für dich erfasst. Schalte einzelne Bereiche ein oder aus — die Navigation, Kacheln und Auswertungen passen sich sofort an." } }, "profile": "Profil", @@ -6511,5 +6526,67 @@ "savedToast": "Wert gespeichert", "saveError": "Wert konnte nicht gespeichert werden" } + }, + "modules": { + "cycle": { + "label": "Zyklus", + "description": "Periode, Phasen und Vorhersagen verfolgen." + }, + "mood": { + "label": "Stimmung", + "description": "Tägliche Stimmung und Tags erfassen." + }, + "sleep": { + "label": "Schlaf", + "description": "Schlafdauer, Phasen und Schlaf-Timeline." + }, + "glucose": { + "label": "Glukose", + "description": "Blutzuckerwerte und Verläufe." + }, + "workouts": { + "label": "Workouts", + "description": "Trainingseinheiten und Aktivität." + }, + "recovery": { + "label": "Erholung", + "description": "Erholungs- und Belastungswerte deiner Geräte." + }, + "labs": { + "label": "Laborwerte", + "description": "Laborergebnisse erfassen und im Verlauf ablegen." + }, + "achievements": { + "label": "Erfolge", + "description": "Abzeichen und Serien für deine Routine." + }, + "coach": { + "label": "Coach", + "description": "KI-Chat mit Bezug zu deinen Daten." + }, + "insights": { + "label": "Auswertungen", + "description": "KI-Analysen, Briefing und Korrelationen." + }, + "doctorReport": { + "label": "Arztbericht", + "description": "PDF- und FHIR-Export für deine Praxis." + }, + "weight": { + "label": "Gewicht", + "description": "Gewicht und Körperzusammensetzung." + }, + "bloodPressure": { + "label": "Blutdruck", + "description": "Systolisch und diastolisch." + }, + "pulse": { + "label": "Puls", + "description": "Ruhepuls und Herzfrequenz." + }, + "medications": { + "label": "Medikamente", + "description": "Pläne, Einnahmen und Compliance." + } } } diff --git a/messages/en.json b/messages/en.json index bbeb6b05a..9c94551de 100644 --- a/messages/en.json +++ b/messages/en.json @@ -4122,6 +4122,21 @@ "title": "Notification channels", "description": "APNs, Telegram, ntfy, Web Push, Webhook, and Email." } + }, + "modules": { + "title": "Modules", + "saved": "Saved.", + "error": "Couldn't save. Please try again.", + "toggleable": { + "title": "What you track", + "description": "Optional areas. Disabled modules drop out of the navigation, tiles, and insights — your data stays untouched." + }, + "core": { + "title": "Always on", + "description": "HealthLog's foundations. These areas can't be turned off.", + "note": "Core area — always on" + }, + "description": "Choose what HealthLog tracks for you. Turn individual areas on or off — the navigation, tiles, and insights adapt right away." } }, "profile": "Profile", @@ -6511,5 +6526,67 @@ "savedToast": "Result saved", "saveError": "Could not save the result" } + }, + "modules": { + "cycle": { + "label": "Cycle", + "description": "Track period, phases, and predictions." + }, + "mood": { + "label": "Mood", + "description": "Log daily mood and tags." + }, + "sleep": { + "label": "Sleep", + "description": "Sleep duration, stages, and timeline." + }, + "glucose": { + "label": "Glucose", + "description": "Blood-glucose readings and trends." + }, + "workouts": { + "label": "Workouts", + "description": "Training sessions and activity." + }, + "recovery": { + "label": "Recovery", + "description": "Recovery and strain metrics from your devices." + }, + "labs": { + "label": "Lab results", + "description": "Capture lab results and keep their history." + }, + "achievements": { + "label": "Achievements", + "description": "Badges and streaks for your routine." + }, + "coach": { + "label": "Coach", + "description": "AI chat grounded in your own data." + }, + "insights": { + "label": "Insights", + "description": "AI analysis, briefing, and correlations." + }, + "doctorReport": { + "label": "Doctor report", + "description": "PDF and FHIR export for your practice." + }, + "weight": { + "label": "Weight", + "description": "Weight and body composition." + }, + "bloodPressure": { + "label": "Blood pressure", + "description": "Systolic and diastolic." + }, + "pulse": { + "label": "Pulse", + "description": "Resting pulse and heart rate." + }, + "medications": { + "label": "Medications", + "description": "Schedules, intakes, and compliance." + } } } diff --git a/messages/es.json b/messages/es.json index 1eed54b4c..8af901931 100644 --- a/messages/es.json +++ b/messages/es.json @@ -4122,6 +4122,21 @@ "title": "Canales de notificación", "description": "APNs, Telegram, ntfy, Web Push, Webhook y correo." } + }, + "modules": { + "title": "Módulos", + "saved": "Guardado.", + "error": "No se pudo guardar. Inténtalo de nuevo.", + "toggleable": { + "title": "Qué registras", + "description": "Áreas opcionales. Los módulos desactivados desaparecen de la navegación, los paneles y los análisis; tus datos se conservan." + }, + "core": { + "title": "Siempre activos", + "description": "Los pilares de HealthLog. Estas áreas no se pueden desactivar.", + "note": "Área esencial — siempre activa" + }, + "description": "Elige qué registra HealthLog por ti. Activa o desactiva áreas concretas: la navegación, los paneles y los análisis se adaptan al instante." } }, "profile": "Perfil", @@ -6511,5 +6526,67 @@ "savedToast": "Resultado guardado", "saveError": "No se pudo guardar el resultado" } + }, + "modules": { + "cycle": { + "label": "Ciclo", + "description": "Registra periodo, fases y predicciones." + }, + "mood": { + "label": "Estado de ánimo", + "description": "Registra el ánimo diario y etiquetas." + }, + "sleep": { + "label": "Sueño", + "description": "Duración, fases y cronología del sueño." + }, + "glucose": { + "label": "Glucosa", + "description": "Lecturas de glucosa y tendencias." + }, + "workouts": { + "label": "Entrenamientos", + "description": "Sesiones de entrenamiento y actividad." + }, + "recovery": { + "label": "Recuperación", + "description": "Métricas de recuperación y carga de tus dispositivos." + }, + "labs": { + "label": "Resultados de laboratorio", + "description": "Registra resultados de laboratorio y su historial." + }, + "achievements": { + "label": "Logros", + "description": "Insignias y rachas para tu rutina." + }, + "coach": { + "label": "Coach", + "description": "Chat de IA basado en tus propios datos." + }, + "insights": { + "label": "Análisis", + "description": "Análisis de IA, resumen y correlaciones." + }, + "doctorReport": { + "label": "Informe médico", + "description": "Exportación PDF y FHIR para tu consulta." + }, + "weight": { + "label": "Peso", + "description": "Peso y composición corporal." + }, + "bloodPressure": { + "label": "Presión arterial", + "description": "Sistólica y diastólica." + }, + "pulse": { + "label": "Pulso", + "description": "Pulso en reposo y frecuencia cardiaca." + }, + "medications": { + "label": "Medicamentos", + "description": "Pautas, tomas y cumplimiento." + } } } diff --git a/messages/fr.json b/messages/fr.json index c3a2a9dcc..84d6d1c1e 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -4122,6 +4122,21 @@ "title": "Canaux de notification", "description": "APNs, Telegram, ntfy, Web Push, Webhook et e-mail." } + }, + "modules": { + "title": "Modules", + "saved": "Enregistré.", + "error": "Échec de l'enregistrement. Veuillez réessayer.", + "toggleable": { + "title": "Ce que vous suivez", + "description": "Domaines optionnels. Les modules désactivés disparaissent de la navigation, des tuiles et des analyses ; vos données sont conservées." + }, + "core": { + "title": "Toujours actifs", + "description": "Les fondations de HealthLog. Ces domaines ne peuvent pas être désactivés.", + "note": "Domaine essentiel — toujours actif" + }, + "description": "Choisissez ce que HealthLog suit pour vous. Activez ou désactivez chaque domaine : la navigation, les tuiles et les analyses s'adaptent aussitôt." } }, "profile": "Profil", @@ -6511,5 +6526,67 @@ "savedToast": "Résultat enregistré", "saveError": "Impossible d'enregistrer le résultat" } + }, + "modules": { + "cycle": { + "label": "Cycle", + "description": "Suivez règles, phases et prévisions." + }, + "mood": { + "label": "Humeur", + "description": "Notez l'humeur quotidienne et les tags." + }, + "sleep": { + "label": "Sommeil", + "description": "Durée, phases et chronologie du sommeil." + }, + "glucose": { + "label": "Glycémie", + "description": "Mesures de glycémie et tendances." + }, + "workouts": { + "label": "Entraînements", + "description": "Séances d'entraînement et activité." + }, + "recovery": { + "label": "Récupération", + "description": "Indicateurs de récupération et de charge de vos appareils." + }, + "labs": { + "label": "Résultats de labo", + "description": "Enregistrez les résultats de laboratoire et leur historique." + }, + "achievements": { + "label": "Réussites", + "description": "Badges et séries pour votre routine." + }, + "coach": { + "label": "Coach", + "description": "Chat IA fondé sur vos propres données." + }, + "insights": { + "label": "Analyses", + "description": "Analyses IA, briefing et corrélations." + }, + "doctorReport": { + "label": "Rapport médical", + "description": "Export PDF et FHIR pour votre cabinet." + }, + "weight": { + "label": "Poids", + "description": "Poids et composition corporelle." + }, + "bloodPressure": { + "label": "Tension artérielle", + "description": "Systolique et diastolique." + }, + "pulse": { + "label": "Pouls", + "description": "Pouls au repos et fréquence cardiaque." + }, + "medications": { + "label": "Médicaments", + "description": "Schémas, prises et observance." + } } } diff --git a/messages/it.json b/messages/it.json index 2afb522b8..91514bbbb 100644 --- a/messages/it.json +++ b/messages/it.json @@ -4122,6 +4122,21 @@ "title": "Canali di notifica", "description": "APNs, Telegram, ntfy, Web Push, Webhook ed e-mail." } + }, + "modules": { + "title": "Moduli", + "saved": "Salvato.", + "error": "Salvataggio non riuscito. Riprova.", + "toggleable": { + "title": "Cosa monitori", + "description": "Ambiti opzionali. I moduli disattivati spariscono da navigazione, riquadri e analisi; i tuoi dati restano intatti." + }, + "core": { + "title": "Sempre attivi", + "description": "Le fondamenta di HealthLog. Questi ambiti non possono essere disattivati.", + "note": "Ambito di base — sempre attivo" + }, + "description": "Scegli cosa registra HealthLog per te. Attiva o disattiva i singoli ambiti: navigazione, riquadri e analisi si adattano subito." } }, "profile": "Profilo", @@ -6511,5 +6526,67 @@ "savedToast": "Risultato salvato", "saveError": "Impossibile salvare il risultato" } + }, + "modules": { + "cycle": { + "label": "Ciclo", + "description": "Monitora mestruazioni, fasi e previsioni." + }, + "mood": { + "label": "Umore", + "description": "Registra umore quotidiano ed etichette." + }, + "sleep": { + "label": "Sonno", + "description": "Durata, fasi e cronologia del sonno." + }, + "glucose": { + "label": "Glicemia", + "description": "Valori della glicemia e andamenti." + }, + "workouts": { + "label": "Allenamenti", + "description": "Sessioni di allenamento e attività." + }, + "recovery": { + "label": "Recupero", + "description": "Metriche di recupero e carico dai tuoi dispositivi." + }, + "labs": { + "label": "Esami di laboratorio", + "description": "Registra i referti di laboratorio e il loro storico." + }, + "achievements": { + "label": "Traguardi", + "description": "Badge e serie per la tua routine." + }, + "coach": { + "label": "Coach", + "description": "Chat IA basata sui tuoi dati." + }, + "insights": { + "label": "Analisi", + "description": "Analisi IA, briefing e correlazioni." + }, + "doctorReport": { + "label": "Referto medico", + "description": "Esportazione PDF e FHIR per il tuo studio." + }, + "weight": { + "label": "Peso", + "description": "Peso e composizione corporea." + }, + "bloodPressure": { + "label": "Pressione", + "description": "Sistolica e diastolica." + }, + "pulse": { + "label": "Battito", + "description": "Battito a riposo e frequenza cardiaca." + }, + "medications": { + "label": "Farmaci", + "description": "Schemi, assunzioni e aderenza." + } } } diff --git a/messages/pl.json b/messages/pl.json index a14120194..68cfd262f 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -4122,6 +4122,21 @@ "title": "Kanały powiadomień", "description": "APNs, Telegram, ntfy, Web Push, Webhook i e-mail." } + }, + "modules": { + "title": "Moduły", + "saved": "Zapisano.", + "error": "Nie udało się zapisać. Spróbuj ponownie.", + "toggleable": { + "title": "Co śledzisz", + "description": "Obszary opcjonalne. Wyłączone moduły znikają z nawigacji, kafelków i analiz — Twoje dane pozostają nienaruszone." + }, + "core": { + "title": "Zawsze aktywne", + "description": "Podstawy HealthLog. Tych obszarów nie można wyłączyć.", + "note": "Obszar podstawowy — zawsze aktywny" + }, + "description": "Wybierz, co HealthLog ma dla Ciebie śledzić. Włączaj i wyłączaj poszczególne obszary — nawigacja, kafelki i analizy dostosują się od razu." } }, "profile": "Profil", @@ -6511,5 +6526,67 @@ "savedToast": "Wynik zapisany", "saveError": "Nie udało się zapisać wyniku" } + }, + "modules": { + "cycle": { + "label": "Cykl", + "description": "Śledź miesiączkę, fazy i prognozy." + }, + "mood": { + "label": "Nastrój", + "description": "Zapisuj codzienny nastrój i tagi." + }, + "sleep": { + "label": "Sen", + "description": "Długość, fazy i oś czasu snu." + }, + "glucose": { + "label": "Glukoza", + "description": "Odczyty glukozy i trendy." + }, + "workouts": { + "label": "Treningi", + "description": "Sesje treningowe i aktywność." + }, + "recovery": { + "label": "Regeneracja", + "description": "Wskaźniki regeneracji i obciążenia z Twoich urządzeń." + }, + "labs": { + "label": "Wyniki badań", + "description": "Zapisuj wyniki badań laboratoryjnych i ich historię." + }, + "achievements": { + "label": "Osiągnięcia", + "description": "Odznaki i serie dla Twojej rutyny." + }, + "coach": { + "label": "Coach", + "description": "Czat AI oparty na Twoich danych." + }, + "insights": { + "label": "Analizy", + "description": "Analizy AI, briefing i korelacje." + }, + "doctorReport": { + "label": "Raport dla lekarza", + "description": "Eksport PDF i FHIR dla Twojej przychodni." + }, + "weight": { + "label": "Waga", + "description": "Waga i skład ciała." + }, + "bloodPressure": { + "label": "Ciśnienie krwi", + "description": "Skurczowe i rozkurczowe." + }, + "pulse": { + "label": "Tętno", + "description": "Tętno spoczynkowe i rytm serca." + }, + "medications": { + "label": "Leki", + "description": "Schematy, przyjęcia i zgodność." + } } } diff --git a/src/app/settings/[section]/page.tsx b/src/app/settings/[section]/page.tsx index ceee2abed..645e28a1a 100644 --- a/src/app/settings/[section]/page.tsx +++ b/src/app/settings/[section]/page.tsx @@ -12,6 +12,7 @@ import { InsightsSection } from "@/components/settings/insights-section"; import { IntegrationsSection } from "@/components/settings/integrations-section"; import { LayoutSection } from "@/components/settings/layout-section"; import { MedicationsSection } from "@/components/settings/medications-section"; +import { ModulesSection } from "@/components/settings/modules-section"; import { MoodSection } from "@/components/settings/mood-section"; import { NotificationsSection } from "@/components/settings/notifications-section"; import { RemindersSection } from "@/components/settings/reminders-section"; @@ -51,6 +52,7 @@ const SECTION_COMPONENTS: Record< () => JSX.Element | null > = { account: AccountSection, + modules: ModulesSection, about: AboutSection, ai: AiSection, integrations: IntegrationsSection, diff --git a/src/components/settings/__tests__/settings-shell.test.tsx b/src/components/settings/__tests__/settings-shell.test.tsx index b10b03c07..ddca9005d 100644 --- a/src/components/settings/__tests__/settings-shell.test.tsx +++ b/src/components/settings/__tests__/settings-shell.test.tsx @@ -63,8 +63,11 @@ describe("SETTINGS_SECTION_SLUGS", () => { // Layout hub instead of four standalone nav entries. // v1.17.1 — `reminders` is the one "Reminders & Notifications" home and // sits right after `notifications`. + // v1.18.0 — `modules` ("Was du trackst") sits right after `account` as + // the single front door for enabling/disabling secondary domains. expect([...SETTINGS_SECTION_SLUGS]).toEqual([ "account", + "modules", "integrations", "notifications", "reminders", diff --git a/src/components/settings/module-toggle-row.tsx b/src/components/settings/module-toggle-row.tsx new file mode 100644 index 000000000..47a8cbb23 --- /dev/null +++ b/src/components/settings/module-toggle-row.tsx @@ -0,0 +1,85 @@ +"use client"; + +/** + * `` — one row in the Settings → Module ("Was du trackst") + * hub. v1.18.0. + * + * Renders a single module: a neutral icon, the i18n label + one-line + * description, and a `` reflecting the resolved enabled-state. The + * row is presentation-only — the parent `` owns the mutation + * and cache invalidation, so this component stays a pure, easily-tested leaf. + * + * Two modes: + * - toggleable: the Switch is live; flipping it calls `onToggle(next)`. + * - locked (core domains): the Switch is checked + disabled with a short + * "always on" note, so the always-on measurement engine + meds read as + * deliberately fixed rather than broken. Palette stays neutral — no + * alarming green-when-on / red-when-off tint. + */ + +import type { LucideIcon } from "lucide-react"; + +import { Label } from "@/components/ui/label"; +import { Switch } from "@/components/ui/switch"; + +export interface ModuleToggleRowProps { + /** Stable key — used for the input id + the test query handle. */ + moduleKey: string; + icon: LucideIcon; + label: string; + description: string; + /** Whether the Switch reads on. Core rows pass `true`. */ + enabled: boolean; + /** + * When set the row is read-only: the Switch is locked checked and the + * `lockedNote` line renders beneath the description. Core domains use this. + */ + locked?: boolean; + /** One-line "core / always on" note, only shown when `locked`. */ + lockedNote?: string; + /** Disable the Switch while a mutation is in flight (toggleable rows). */ + pending?: boolean; + /** Fired with the next desired state on a user toggle (toggleable rows). */ + onToggle?: (next: boolean) => void; +} + +export function ModuleToggleRow({ + moduleKey, + icon: Icon, + label, + description, + enabled, + locked = false, + lockedNote, + pending = false, + onToggle, +}: ModuleToggleRowProps) { + const inputId = `module-toggle-${moduleKey}`; + return ( +
+
+
+ onToggle?.(v)} + className="mt-0.5 shrink-0" + aria-label={label} + /> +
+ ); +} diff --git a/src/components/settings/modules-section.tsx b/src/components/settings/modules-section.tsx new file mode 100644 index 000000000..7c81829d3 --- /dev/null +++ b/src/components/settings/modules-section.tsx @@ -0,0 +1,175 @@ +"use client"; + +/** + * `` — Settings → Module ("Was du trackst") hub. v1.18.0. + * + * The one place a user decides which secondary domains HealthLog surfaces. + * Each toggleable module (mood, sleep, glucose, workouts, recovery, labs, + * achievements, coach, insights, doctor report, cycle) gets a row with an + * icon, label, one-line description, and a ``. Flipping a switch + * PATCHes `/api/auth/me/modules` (a DISABLED allowlist; the endpoint refuses + * core keys) and then invalidates the `authMe()` query so the nav, Insights + * pills, and dashboard tiles re-gate live off `useAuth().user.modules`. + * + * The four CORE domains (weight, blood pressure, pulse, medications) render + * as a separate read-only "always on" group — locked switches with a short + * note — so the always-on measurement engine + meds read as deliberately + * fixed, never disableable. Palette stays neutral throughout; this is a + * calm configuration surface, not an alarm panel. + * + * State source is `useAuth().user.modules` (the resolved `/auth/me` map). + * A module is enabled unless its key is explicitly `false` — default-on. + */ + +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { toast } from "sonner"; +import { + Activity, + Award, + Blocks, + CalendarHeart, + Droplet, + Dumbbell, + FileText, + HeartPulse, + Lock, + MessageCircleHeart, + Moon, + Pill, + Scale, + Smile, + Sparkles, + TestTube, + type LucideIcon, +} from "lucide-react"; + +import { SettingsCardHeader } from "@/components/settings/_card-header"; +import { ModuleToggleRow } from "@/components/settings/module-toggle-row"; +import { useAuth } from "@/hooks/use-auth"; +import { useTranslations } from "@/lib/i18n/context"; +import { apiPatch } from "@/lib/api/api-fetch"; +import { queryKeys } from "@/lib/query-keys"; +import { + CORE_DOMAIN_KEYS, + MODULE_REGISTRY, + type CoreDomainKey, + type ModuleKey, +} from "@/lib/modules/registry"; + +/** Neutral Lucide glyph per toggleable module. */ +const MODULE_ICONS: Record = { + cycle: CalendarHeart, + mood: Smile, + sleep: Moon, + glucose: Droplet, + workouts: Dumbbell, + recovery: Activity, + labs: TestTube, + achievements: Award, + coach: MessageCircleHeart, + insights: Sparkles, + doctorReport: FileText, +}; + +/** Neutral Lucide glyph per core (always-on) domain. */ +const CORE_ICONS: Record = { + weight: Scale, + bloodPressure: HeartPulse, + pulse: Activity, + medications: Pill, +}; + +export function ModulesSection() { + const { t } = useTranslations(); + const { user } = useAuth(); + const queryClient = useQueryClient(); + + const modules = user?.modules ?? {}; + + const toggle = useMutation({ + // Factory-routed; the in-repo eslint rule forbids a bare literal here. + mutationKey: queryKeys.modulesPrefs(), + mutationFn: async (vars: { key: ModuleKey; enabled: boolean }) => { + // DISABLED allowlist: send only the single key the user flipped. + return apiPatch("/api/auth/me/modules", { [vars.key]: vars.enabled }); + }, + onSuccess: () => { + // Re-gate every surface that reads the module map off /auth/me: nav, + // Insights pills, dashboard tiles, quick-add, search. + void queryClient.invalidateQueries({ queryKey: queryKeys.authMe() }); + toast.success(t("settings.sections.modules.saved")); + }, + onError: () => { + toast.error(t("settings.sections.modules.error")); + }, + }); + + return ( +
+
+

+ {t("settings.sections.modules.title")} +

+

+ {t("settings.sections.modules.description")} +

+
+ + {/* Toggleable modules */} +
+ +
+ {(Object.keys(MODULE_REGISTRY) as ModuleKey[]).map((key) => { + const def = MODULE_REGISTRY[key]; + // Default-on: a module is enabled unless explicitly `false`. + const enabled = modules[key] !== false; + return ( + toggle.mutate({ key, enabled: next })} + /> + ); + })} +
+
+ + {/* Core domains — always on, read-only */} +
+ +
+ {CORE_DOMAIN_KEYS.map((key) => ( + + ))} +
+
+
+ ); +} diff --git a/src/components/settings/section-placeholder.tsx b/src/components/settings/section-placeholder.tsx index e437f3b95..846d3c3af 100644 --- a/src/components/settings/section-placeholder.tsx +++ b/src/components/settings/section-placeholder.tsx @@ -14,6 +14,7 @@ import { AlarmClock, Bell, + Blocks, Download, Info, KeyRound, @@ -36,6 +37,7 @@ import type { SettingsSectionSlug } from "./section-slugs"; const SLUG_ICON: Record = { account: User, + modules: Blocks, integrations: Link2, notifications: Bell, reminders: AlarmClock, diff --git a/src/components/settings/section-slugs.ts b/src/components/settings/section-slugs.ts index 87e9723b3..b2e65c233 100644 --- a/src/components/settings/section-slugs.ts +++ b/src/components/settings/section-slugs.ts @@ -34,6 +34,9 @@ // the hub is consolidation by linking. export const SETTINGS_SECTION_SLUGS = [ "account", + // v1.18.0 — `modules` ("Was du trackst") sits right after account: the + // single front door for enabling/disabling secondary tracking domains. + "modules", "integrations", "notifications", "reminders", diff --git a/src/components/settings/settings-shell.tsx b/src/components/settings/settings-shell.tsx index 28d2c168d..1c5c14309 100644 --- a/src/components/settings/settings-shell.tsx +++ b/src/components/settings/settings-shell.tsx @@ -20,6 +20,7 @@ import { usePathname } from "next/navigation"; import { AlarmClock, Bell, + Blocks, Download, Info, KeyRound, @@ -80,6 +81,13 @@ interface SettingsSection { */ export const SETTINGS_SECTIONS: readonly SettingsSection[] = [ { slug: "account", titleKey: "settings.sections.account.title", icon: User }, + // v1.18.0 — the "Was du trackst" hub. Sits right below Account: the one + // place to enable/disable the secondary tracking domains. + { + slug: "modules", + titleKey: "settings.sections.modules.title", + icon: Blocks, + }, { slug: "integrations", titleKey: "settings.sections.integrations.title", diff --git a/src/lib/query-keys/settings.ts b/src/lib/query-keys/settings.ts index 83fdd03c0..a085dd9ce 100644 --- a/src/lib/query-keys/settings.ts +++ b/src/lib/query-keys/settings.ts @@ -21,6 +21,15 @@ export const settingsKeys = { /** v1.8.5 — user-level injection-site preferences (global exclusion). */ injectionSitePrefs: () => ["settings", "injection-site-prefs"] as const, + /** + * v1.18.0 — the per-user module enable/disable map behind + * `GET/PATCH /api/auth/me/modules`. Used as the Module-hub mutation key; + * the resolved map itself rides on `authMe()` (the `/auth/me` payload), + * so the toggle mutation invalidates `authMe()` to re-gate the nav, + * Insights pills, and dashboard tiles in lockstep. + */ + modulesPrefs: () => ["settings", "modules"] as const, + apiVersion: () => ["api", "version"] as const, publicVersion: () => ["public", "version"] as const, /** v1.15.12 H1 — admin overview "update available" check against the