"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. * * Every module — including the two delegated ones (coach, cycle) — now * renders a real Switch, so the hub reads uniformly. Four shapes ride on top * of that Switch: * - toggleable: the Switch is live; flipping it calls `onToggle(next)`. * - delegated (coach, cycle): the Switch is live and drives the canonical * state (coach → `User.disableCoach`; cycle → `cycleTrackingEnabled`), * plus a small `manageLink` deep-link beneath the description points at * the fuller settings surface (Coach cadence, cycle goal/predictions). * - locked (core domains): the Switch is checked + disabled with a short * "always on" note, so the always-on measurement engine reads as * deliberately fixed rather than broken. * - disabled-with-reason: the operator turned the module off server-wide * (or the module is otherwise not applicable). The Switch is disabled and * a `disabledReason` hint renders beneath the description — an honest * "you can't flip this and here's why", never a toggle that no-ops and * snaps back behind a misleading "saved" toast. * * Palette stays neutral throughout — no alarming green-when-on / * red-when-off tint. */ import Link from "next/link"; import { ChevronRight } from "lucide-react"; 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; /** * Delegated modules (coach, cycle) keep a small deep-link to the fuller * settings surface beside their live Switch — Coach cadence/memory, the * cycle goal/prediction/length fields. `label` is the link text; `href` * the in-app destination. */ manageLink?: { href: string; label: string }; /** * When set the Switch is disabled and this line renders beneath the * description as an honest "why you can't flip this" hint. Used when the * operator turned the module off server-wide (a per-user toggle cannot * re-enable it) or the module is otherwise not applicable to the account. */ disabledReason?: 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, manageLink, disabledReason, pending = false, onToggle, }: ModuleToggleRowProps) { const inputId = `module-toggle-${moduleKey}`; // The Switch is inert when the domain is core (locked), when the operator // turned it off / it is inapplicable (disabledReason), or while a mutation // is in flight. const interactive = !locked && disabledReason == null; return (
onToggle?.(v) : undefined} className="mt-0.5 shrink-0" aria-label={label} />
); }