refactor(settings): compose single-card sections from SettingsCard

Swap the hand-rolled bg-card border-border rounded-xl border p-4 sm:p-6
container for SettingsCard across the single-card sections (integration
cards, notification channel cards, coach prefs, cycle, modules, mood
reminder, thresholds, dashboard-layout, about-me, low-stock, and the
health-record export panel). Drops the sm: padding split and the
primitive bypass; landmark sections keep their element via as="section".
This commit is contained in:
Marc-André Bombeck committed 2026-06-19 21:14:57 +02:00
1 parent 9182b60dc8
commit e31f9690e2
28 files changed
+129 -83

No files matched your search

+5 -3
View File
@@ -33,6 +33,7 @@ import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { SettingsCard } from "@/components/settings/settings-card";
import { SettingsCardHeader } from "@/components/settings/_card-header";
import { useTranslations } from "@/lib/i18n/context";
import { queryKeys } from "@/lib/query-keys";
@@ -181,10 +182,11 @@ export function AboutMeSection({
}
return (
<section
<SettingsCard
as="section"
aria-labelledby="settings-ai-about-me-title"
data-testid="settings-about-me-card"
className="bg-card border-border space-y-5 rounded-xl border p-4 sm:p-6"
className="space-y-5"
>
<div className="flex flex-wrap items-start justify-between gap-3">
<SettingsCardHeader
@@ -346,6 +348,6 @@ export function AboutMeSection({
<p className="text-muted-foreground border-border border-t pt-3 pl-7 text-xs">
{t("settings.ai.aboutMe.hint")} {t("settings.ai.aboutMe.profileHint")}
</p>
</section>
</SettingsCard>
);
}
@@ -21,6 +21,7 @@ import { Sparkles } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import { Label } from "@/components/ui/label";
import { NativeSelect } from "@/components/ui/native-select";
import { SettingsCard } from "@/components/settings/settings-card";
import { SettingsCardHeader } from "@/components/settings/_card-header";
import { apiFetchRaw } from "@/lib/api/api-fetch";
import { formatDateTime } from "@/lib/format";
@@ -133,7 +134,7 @@ export function AiInsightsCard({
}
return (
<div className="bg-card border-border space-y-4 rounded-xl border p-4 sm:p-6">
<SettingsCard className="space-y-4">
{/* The card used to render an icon-only header row (Sparkles +
status badges, no title), which left the tile unanchored next
to its titled siblings. It now follows the shared
@@ -192,7 +193,7 @@ export function AiInsightsCard({
}
/>
</div>
</div>
</SettingsCard>
);
}
@@ -33,6 +33,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
import { MessageCircleHeart } from "lucide-react";
import { Switch } from "@/components/ui/switch";
import { SettingsCard } from "@/components/settings/settings-card";
import { SettingsCardHeader } from "@/components/settings/_card-header";
import { apiPatch } from "@/lib/api/api-fetch";
import { useAuth } from "@/hooks/use-auth";
@@ -140,10 +141,10 @@ export function DisableCoachCard({
}
return (
<section
<SettingsCard
as="section"
aria-labelledby="settings-coach-activate-title"
data-testid="settings-disable-coach-card"
className="bg-card border-border rounded-xl border p-4 sm:p-6"
>
<SettingsCardHeader
icon={MessageCircleHeart}
@@ -173,6 +174,6 @@ export function DisableCoachCard({
{msg}
</p>
)}
</section>
</SettingsCard>
);
}
@@ -35,6 +35,7 @@ import {
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
import { formatDateOrRelative } from "@/lib/format";
import { SettingsCard } from "@/components/settings/settings-card";
import { SettingsCardHeader } from "@/components/settings/_card-header";
import { useTranslations } from "@/lib/i18n/context";
import { queryKeys } from "@/lib/query-keys";
@@ -134,10 +135,11 @@ export function CoachMemorySection({
}, [facts]);
return (
<section
<SettingsCard
as="section"
aria-labelledby="settings-ai-coach-memory-title"
data-testid="settings-coach-memory-card"
className="bg-card border-border space-y-4 rounded-xl border p-4 sm:p-6"
className="space-y-4"
>
<SettingsCardHeader
icon={Brain}
@@ -278,6 +280,6 @@ export function CoachMemorySection({
<p className="text-muted-foreground border-border border-t pt-3 text-xs">
{t("settings.ai.coachMemory.summaryNote")}
</p>
</section>
</SettingsCard>
);
}
+4 -3
View File
@@ -21,6 +21,7 @@ import { MessageCircleHeart } from "lucide-react";
import { Switch } from "@/components/ui/switch";
import { NativeSelect } from "@/components/ui/native-select";
import { SettingsCard } from "@/components/settings/settings-card";
import { SettingsCardHeader } from "@/components/settings/_card-header";
import { useTranslations } from "@/lib/i18n/context";
import { queryKeys } from "@/lib/query-keys";
@@ -170,9 +171,9 @@ export function CoachNudgeCard({
}
return (
<section
<SettingsCard
as="section"
aria-labelledby="settings-coach-nudge-title"
className="bg-card border-border rounded-xl border p-4 sm:p-6"
data-testid="settings-coach-nudge-card"
>
<SettingsCardHeader
@@ -266,6 +267,6 @@ export function CoachNudgeCard({
{msg}
</p>
)}
</section>
</SettingsCard>
);
}
@@ -34,6 +34,7 @@ import {
SelectValue,
} from "@/components/ui/select";
import { Skeleton } from "@/components/ui/skeleton";
import { SettingsCard } from "@/components/settings/settings-card";
import { SettingsCardHeader } from "@/components/settings/_card-header";
import { useTranslations } from "@/lib/i18n/context";
import { useCoachPrefs, useSaveCoachPrefs } from "@/hooks/use-coach-prefs";
@@ -106,8 +107,7 @@ export function CoachPrefsSection({ isAuthenticated }: CoachPrefsSectionProps) {
const isLoading = isAuthenticated && !persisted;
return (
<div
className="bg-card border-border rounded-xl border p-4 sm:p-6"
<SettingsCard
data-slot="coach-prefs-section"
>
<SettingsCardHeader
@@ -351,6 +351,6 @@ export function CoachPrefsSection({ isAuthenticated }: CoachPrefsSectionProps) {
{t("insights.coach.settingsSave")}
</Button>
</div>
</div>
</SettingsCard>
);
}
@@ -7,6 +7,7 @@ import { CalendarHeart } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { SettingsCard } from "@/components/settings/settings-card";
import { SettingsCardHeader } from "@/components/settings/_card-header";
import { useTranslations } from "@/lib/i18n/context";
import { queryKeys } from "@/lib/query-keys";
@@ -59,7 +60,7 @@ export function CycleTrackingCard({
const enabled = data?.cycleTrackingEnabled ?? false;
return (
<div className="bg-card border-border rounded-xl border p-4 sm:p-6">
<SettingsCard>
<SettingsCardHeader
icon={CalendarHeart}
title={t("settings.cycleTracking.title")}
@@ -89,6 +90,6 @@ export function CycleTrackingCard({
</p>
) : null}
</div>
</div>
</SettingsCard>
);
}
@@ -51,6 +51,7 @@ import {
import { apiDelete, apiGet, apiPut } from "@/lib/api/api-fetch";
import { useAuth } from "@/hooks/use-auth";
import { WIDGET_MODULE_BY_ID } from "@/lib/dashboard/widget-modules";
import { SettingsCard } from "@/components/settings/settings-card";
/**
* v1.4.47 W4 — pure reorder helper shared by the arrow buttons and the
@@ -316,9 +317,9 @@ export function DashboardLayoutSection({ id }: { id: string }) {
const dirty = draft !== null && layout !== null;
return (
<div
<SettingsCard
id={id}
className="bg-card border-border scroll-mt-28 space-y-4 rounded-xl border p-4 sm:p-6"
className="scroll-mt-28 space-y-4"
>
{/* v1.4.19 A6 — title / action row uses the same stack-on-mobile,
right-align-on-desktop contract as Account → Password +
@@ -577,7 +578,7 @@ export function DashboardLayoutSection({ id }: { id: string }) {
: t("dashboard.layoutCustomized")}
</p>
)}
</div>
</SettingsCard>
);
}
+3 -2
View File
@@ -8,6 +8,7 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { SettingsCard } from "@/components/settings/settings-card";
import { SettingsCardHeader } from "@/components/settings/_card-header";
import { TestConnectionButton } from "@/components/settings/test-connection-button";
import { useTranslations } from "@/lib/i18n/context";
@@ -74,7 +75,7 @@ export function EmailCard({ isAuthenticated }: { isAuthenticated: boolean }) {
if (settings && !settings.smtpConfigured) return null;
return (
<div className="bg-card border-border rounded-xl border p-4 sm:p-6">
<SettingsCard>
<SettingsCardHeader
icon={Mail}
title={t("settings.email")}
@@ -137,6 +138,6 @@ export function EmailCard({ isAuthenticated }: { isAuthenticated: boolean }) {
</div>
</form>
</div>
</div>
</SettingsCard>
);
}
@@ -23,6 +23,7 @@
import { useId, useState } from "react";
import { ChevronDown, Download, FileText, Loader2 } from "lucide-react";
import { SettingsCard } from "@/components/settings/settings-card";
import { SettingsCardHeader } from "@/components/settings/_card-header";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
@@ -191,15 +192,10 @@ export function HealthRecordExportPanel() {
}
return (
<section
<SettingsCard
as="section"
aria-labelledby="health-record-export-title"
data-testid="health-record-export-panel"
className={cn(
// v1.18.1 (D9) — the gradient/glow hero treatment dropped in favour
// of a normal card so the Gesundheitsakte reads like every other
// settings surface rather than shouting.
"bg-card border-border rounded-xl border p-4 sm:p-6",
)}
>
<SettingsCardHeader
className="mb-4"
@@ -462,7 +458,7 @@ export function HealthRecordExportPanel() {
</p>
)}
</div>
</section>
</SettingsCard>
);
}
@@ -9,6 +9,7 @@ import { useAuth } from "@/hooks/use-auth";
import { useFeatureFlags } from "@/hooks/use-feature-flags";
import { useInsightsLayoutQuery } from "@/hooks/use-insights-layout";
import { InsightsEditMode } from "@/components/insights/insights-edit-mode";
import { SettingsCard } from "@/components/settings/settings-card";
import { type InsightsSectionId } from "@/lib/insights-layout";
/**
@@ -67,12 +68,12 @@ export function InsightsOverviewArrangeSection({ id }: { id?: string }) {
defaults over the user's real saved layout (the same QA-L1 gate the
mother page applies to its "Anpassen" toggle). */}
{isLoading ? (
<div
className="bg-card border-border text-muted-foreground flex items-center gap-2 rounded-xl border p-4 text-sm sm:p-6"
<SettingsCard
className="text-muted-foreground flex items-center gap-2 text-sm"
data-slot="insights-overview-arrange-loading"
>
<Loader2 className="h-4 w-4 animate-spin motion-reduce:animate-none" />
</div>
</SettingsCard>
) : (
<InsightsEditMode
layout={layout}
@@ -43,6 +43,7 @@ import {
} from "@/lib/insights/sub-page-metric";
import { SUB_PAGE_TABS } from "@/components/insights/insights-tab-strip";
import { apiPut } from "@/lib/api/api-fetch";
import { SettingsCard } from "@/components/settings/settings-card";
/**
* v1.15.18 — dedicated pill-sort control for the Insights settings section.
@@ -202,11 +203,12 @@ export function InsightsPillOrderSection({ id }: { id?: string }) {
);
return (
<section
<SettingsCard
as="section"
id={id}
data-slot="insights-pill-order-section"
aria-labelledby="insights-pill-order-title"
className="bg-card border-border space-y-4 rounded-xl border p-4 sm:p-6"
className="space-y-4"
>
<div className="flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between sm:gap-3">
<div className="space-y-1">
@@ -286,7 +288,7 @@ export function InsightsPillOrderSection({ id }: { id?: string }) {
))}
</div>
)}
</section>
</SettingsCard>
);
}
@@ -34,6 +34,7 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { PasswordInput } from "@/components/ui/password-input";
import { SettingsCard } from "@/components/settings/settings-card";
import { SettingsCardHeader } from "@/components/settings/_card-header";
import { IntegrationStatusPill } from "@/components/settings/integration-status-pill";
import type { IntegrationPillState } from "@/components/settings/integration-status-pill";
@@ -188,9 +189,8 @@ export function FitbitCard({
: null;
return (
<div
<SettingsCard
data-testid="fitbit-card"
className="bg-card border-border rounded-xl border p-4 sm:p-6"
>
<SettingsCardHeader
icon={HeartPulse}
@@ -478,6 +478,6 @@ export function FitbitCard({
</div>
)}
</div>
</div>
</SettingsCard>
);
}
@@ -28,6 +28,7 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { PasswordInput } from "@/components/ui/password-input";
import { Switch } from "@/components/ui/switch";
import { SettingsCard } from "@/components/settings/settings-card";
import { SettingsCardHeader } from "@/components/settings/_card-header";
import {
IntegrationStatusPill,
@@ -158,9 +159,8 @@ export function NightscoutCard({ enabled = true }: { enabled?: boolean }) {
: null;
return (
<div
<SettingsCard
data-testid="nightscout-card"
className="bg-card border-border rounded-xl border p-4 sm:p-6"
>
<SettingsCardHeader
icon={Droplet}
@@ -369,6 +369,6 @@ export function NightscoutCard({ enabled = true }: { enabled?: boolean }) {
</>
)}
</div>
</div>
</SettingsCard>
);
}
@@ -28,6 +28,7 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { PasswordInput } from "@/components/ui/password-input";
import { SettingsCard } from "@/components/settings/settings-card";
import { SettingsCardHeader } from "@/components/settings/_card-header";
import {
IntegrationStatusPill,
@@ -201,9 +202,8 @@ export function OAuthProviderCard({
}
return (
<div
<SettingsCard
data-testid={`${provider}-card`}
className="bg-card border-border rounded-xl border p-4 sm:p-6"
>
<SettingsCardHeader
icon={icon}
@@ -429,6 +429,6 @@ export function OAuthProviderCard({
</p>
)}
</div>
</div>
</SettingsCard>
);
}
@@ -26,6 +26,7 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { PasswordInput } from "@/components/ui/password-input";
import { SettingsCard } from "@/components/settings/settings-card";
import { SettingsCardHeader } from "@/components/settings/_card-header";
import { IntegrationStatusPill } from "@/components/settings/integration-status-pill";
import type { IntegrationPillState } from "@/components/settings/integration-status-pill";
@@ -182,7 +183,7 @@ export function WhoopCard({
: null;
return (
<div className="bg-card border-border rounded-xl border p-4 sm:p-6">
<SettingsCard>
<SettingsCardHeader
icon={Activity}
title={t("settings.whoop")}
@@ -456,6 +457,6 @@ export function WhoopCard({
</div>
)}
</div>
</div>
</SettingsCard>
);
}
@@ -19,6 +19,7 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { PasswordInput } from "@/components/ui/password-input";
import { SettingsCard } from "@/components/settings/settings-card";
import { SettingsCardHeader } from "@/components/settings/_card-header";
import { IntegrationStatusPill } from "@/components/settings/integration-status-pill";
import type { IntegrationPillState } from "@/components/settings/integration-status-pill";
@@ -190,7 +191,7 @@ export function WithingsCard({
: null;
return (
<div className="bg-card border-border rounded-xl border p-4 sm:p-6">
<SettingsCard>
<SettingsCardHeader
icon={Link2}
title={t("settings.withings")}
@@ -501,6 +502,6 @@ export function WithingsCard({
</div>
)}
</div>
</div>
</SettingsCard>
);
}
+5 -3
View File
@@ -7,6 +7,7 @@ import { PackageOpen } from "lucide-react";
import { Input } from "@/components/ui/input";
import { Switch } from "@/components/ui/switch";
import { SettingsCard } from "@/components/settings/settings-card";
import { SettingsCardHeader } from "@/components/settings/_card-header";
import { useTranslations } from "@/lib/i18n/context";
import { queryKeys } from "@/lib/query-keys";
@@ -190,12 +191,13 @@ export function LowStockCard({
}
return (
<section
<SettingsCard
as="section"
// `id` is the anchor target of the supply tab's cross-link
// (`/settings/notifications#low-stock`) — keep it stable.
id="low-stock"
aria-labelledby="settings-low-stock-title"
className="bg-card border-border scroll-mt-20 rounded-xl border p-4 sm:p-6"
className="scroll-mt-20"
>
<SettingsCardHeader
icon={PackageOpen}
@@ -303,6 +305,6 @@ export function LowStockCard({
{t("notifications.lowStock.medicationsLink")}
</Link>
</p>
</section>
</SettingsCard>
);
}
+3 -2
View File
@@ -42,6 +42,7 @@ import {
type LucideIcon,
} from "lucide-react";
import { SettingsCard } from "@/components/settings/settings-card";
import { SettingsCardHeader } from "@/components/settings/_card-header";
import { ModuleToggleRow } from "@/components/settings/module-toggle-row";
import { useAuth } from "@/hooks/use-auth";
@@ -106,7 +107,7 @@ export function ModulesSection() {
// frame's standard subtitle). The "Module immer aktiv" core-domains card
// was removed: a domain that can't be turned off doesn't need to be listed.
return (
<div className="bg-card border-border rounded-xl border p-4 sm:p-6">
<SettingsCard>
<SettingsCardHeader
icon={Blocks}
title={t("settings.sections.modules.toggleable.title")}
@@ -160,6 +161,6 @@ export function ModulesSection() {
);
})}
</div>
</div>
</SettingsCard>
);
}
@@ -6,6 +6,7 @@ import { SmilePlus } from "lucide-react";
import { NativeSelect } from "@/components/ui/native-select";
import { Switch } from "@/components/ui/switch";
import { SettingsCard } from "@/components/settings/settings-card";
import { SettingsCardHeader } from "@/components/settings/_card-header";
import { useTranslations } from "@/lib/i18n/context";
import { queryKeys } from "@/lib/query-keys";
@@ -155,9 +156,9 @@ export function MoodReminderCard({
}
return (
<section
<SettingsCard
as="section"
aria-labelledby="settings-mood-reminder-title"
className="bg-card border-border rounded-xl border p-4 sm:p-6"
>
<SettingsCardHeader
icon={SmilePlus}
@@ -214,6 +215,6 @@ export function MoodReminderCard({
{msg}
</p>
)}
</section>
</SettingsCard>
);
}
+3 -2
View File
@@ -9,6 +9,7 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { PasswordInput } from "@/components/ui/password-input";
import { SettingsCard } from "@/components/settings/settings-card";
import { SettingsCardHeader } from "@/components/settings/_card-header";
import { TestConnectionButton } from "@/components/settings/test-connection-button";
import { useTranslations } from "@/lib/i18n/context";
@@ -82,7 +83,7 @@ export function NtfyCard({ isAuthenticated }: { isAuthenticated: boolean }) {
});
return (
<div className="bg-card border-border rounded-xl border p-4 sm:p-6">
<SettingsCard>
<SettingsCardHeader
icon={Bell}
title={t("settings.ntfy")}
@@ -171,6 +172,6 @@ export function NtfyCard({ isAuthenticated }: { isAuthenticated: boolean }) {
</div>
</form>
</div>
</div>
</SettingsCard>
);
}
+35 -11
View File
@@ -14,26 +14,50 @@ import { cn } from "@/lib/utils";
* `sm:` (640 px) instead of `md:` (768 px), so on a ~700 px tablet Settings
* cards read denser than every other surface.
*
* This wraps the ui `<Card>` so the whole surface composes from one shape.
* The default renders the ui `<Card>` so the surface composes from one shape.
* Unlike the bare `<Card>` it does NOT impose the `flex flex-col gap` layout:
* Settings cards manage their own internal rhythm (`SettingsCardHeader` +
* Settings card bodies own their internal rhythm (`SettingsCardHeader` +
* `mt-4`/`space-y-*` bodies), so the container only owns the border, radius,
* background, and padding. Pass `className` for per-card extras
* (`scroll-mt-28`, `space-y-4`, `flex h-full flex-col`, …).
*
* `as="section"` keeps a card's semantic landmark element (a `<section>` with
* an `aria-labelledby`) while painting the same shape and padding contract.
*/
export function SettingsCard({
const SETTINGS_CARD_SHELL = "block gap-0 p-4 md:p-6";
type SettingsCardOwnProps = { className?: string };
type SettingsCardProps<E extends React.ElementType> = SettingsCardOwnProps & {
as?: E;
} & Omit<React.ComponentPropsWithoutRef<E>, keyof SettingsCardOwnProps | "as">;
export function SettingsCard<E extends React.ElementType = typeof Card>({
as,
className,
...props
}: React.ComponentProps<typeof Card>) {
}: SettingsCardProps<E>) {
if (as) {
const Component = as as React.ElementType;
return (
<Component
data-slot="card"
// Mirror the ui Card shell so a semantic landmark card paints
// identically to the default <Card>-backed one.
className={cn(
"bg-card text-card-foreground rounded-xl border p-4 shadow-sm md:p-6",
className,
)}
{...props}
/>
);
}
return (
<Card
className={cn(
// Reset the primitive's flex/gap layout — Settings card bodies own
// their internal spacing — and apply the shared `p-4 md:p-6` padding.
"block gap-0 p-4 md:p-6",
className,
)}
{...props}
// Reset the primitive's flex/gap layout and apply the shared
// `p-4 md:p-6` padding; Settings bodies own their internal spacing.
className={cn(SETTINGS_CARD_SHELL, className)}
{...(props as React.ComponentProps<typeof Card>)}
/>
);
}
+3 -2
View File
@@ -42,6 +42,7 @@ import {
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import { SettingsCard } from "@/components/settings/settings-card";
import { SettingsCardHeader } from "@/components/settings/_card-header";
import { useTranslations } from "@/lib/i18n/context";
import { queryKeys } from "@/lib/query-keys";
@@ -285,7 +286,7 @@ export function SourcesSection() {
// card, below the ladder.
return (
<div className="space-y-6">
<div className="bg-card border-border space-y-4 rounded-xl border p-4 sm:p-6">
<SettingsCard className="space-y-4">
<SettingsCardHeader
icon={Layers}
title={t("settings.sections.sources.cardTitle")}
@@ -574,7 +575,7 @@ export function SourcesSection() {
{t("settings.sections.sources.integrationsHintLink")}
</Link>
</p>
</div>
</SettingsCard>
</div>
);
}
+3 -2
View File
@@ -9,6 +9,7 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { PasswordInput } from "@/components/ui/password-input";
import { SettingsCard } from "@/components/settings/settings-card";
import { SettingsCardHeader } from "@/components/settings/_card-header";
import { TestConnectionButton } from "@/components/settings/test-connection-button";
import { useTranslations } from "@/lib/i18n/context";
@@ -87,7 +88,7 @@ export function TelegramCard({
}
return (
<div className="bg-card border-border rounded-xl border p-4 sm:p-6">
<SettingsCard>
<SettingsCardHeader
icon={MessageCircle}
title={t("settings.telegram")}
@@ -177,6 +178,6 @@ export function TelegramCard({
</div>
</form>
</div>
</div>
</SettingsCard>
);
}
@@ -19,6 +19,7 @@ import {
type EffectiveRange,
} from "@/lib/analytics/effective-range";
import { apiFetchRaw, apiGet } from "@/lib/api/api-fetch";
import { SettingsCard } from "@/components/settings/settings-card";
interface ThresholdsApiResponse {
effective: Record<ThresholdMetric, EffectiveRange>;
@@ -115,9 +116,9 @@ export function ThresholdsEditorSection({ id }: { id: string }) {
});
return (
<div
<SettingsCard
id={id}
className="bg-card border-border scroll-mt-28 space-y-4 rounded-xl border p-4 sm:p-6"
className="scroll-mt-28 space-y-4"
>
{/* v1.4.19 A8 / F-07: page header `settings.sections.thresholds.*`
already provides the title + description for this route, so the
@@ -159,7 +160,7 @@ export function ThresholdsEditorSection({ id }: { id: string }) {
))}
</div>
)}
</div>
</SettingsCard>
);
}
+3 -2
View File
@@ -5,6 +5,7 @@ import { BellRing, Loader2 } from "lucide-react";
import { Button } from "@/components/ui/button";
import { TestConnectionButton } from "@/components/settings/test-connection-button";
import { SettingsCard } from "@/components/settings/settings-card";
import { SettingsCardHeader } from "@/components/settings/_card-header";
import { useTranslations } from "@/lib/i18n/context";
import { apiFetchRaw } from "@/lib/api/api-fetch";
@@ -204,7 +205,7 @@ export function WebPushCard() {
);
return (
<div className="bg-card border-border rounded-xl border p-4 sm:p-6">
<SettingsCard>
<SettingsCardHeader
icon={BellRing}
title={t("settings.webPush")}
@@ -234,6 +235,6 @@ export function WebPushCard() {
)}
</div>
) : null}
</div>
</SettingsCard>
);
}
+3 -2
View File
@@ -9,6 +9,7 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { PasswordInput } from "@/components/ui/password-input";
import { SettingsCard } from "@/components/settings/settings-card";
import { SettingsCardHeader } from "@/components/settings/_card-header";
import { TestConnectionButton } from "@/components/settings/test-connection-button";
import { useTranslations } from "@/lib/i18n/context";
@@ -80,7 +81,7 @@ export function WebhookCard({ isAuthenticated }: { isAuthenticated: boolean }) {
});
return (
<div className="bg-card border-border rounded-xl border p-4 sm:p-6">
<SettingsCard>
<SettingsCardHeader
icon={Webhook}
title={t("settings.webhook")}
@@ -169,6 +170,6 @@ export function WebhookCard({ isAuthenticated }: { isAuthenticated: boolean }) {
</div>
</form>
</div>
</div>
</SettingsCard>
);
}
+1
View File
@@ -0,0 +1 @@
/Users/marc/Projects/HealthLog/src/generated