mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 21:10:59 +00:00
Settings now reads as nine groups: Account, Tracking, Arrangement, Notifications, Health profile, AI, Connections, Data and System. On a phone one button names the current section and opens the grouped list, replacing the strip of every section. What moved, every old address still working: - Sessions, trusted devices and security activity sit under Security; the privacy page anchors forward there. - Notifications holds every reminder kind: the Coach nudges and the checkup reminder switches joined, and a card names the reminders set where their subject lives (dose times, cycle reminders). - Arrangement holds order and view only. Mood tags, the labs biomarker catalog and on-device text recognition, and the injection sites live on module pages under /settings/modules/<id>, linked from each Modules row and from the module's own wrench. /settings/layout/mood redirects. - The timezone saves on change in the Display card, with a one-tap offer of the device zone. - Screening hints sit beside the life circumstances they come from. - System gains Preview features with the Topics switch. Duplicates: the Coach and MCP pages show their state and link to the one switch in Modules (turning MCP off keeps tokens), the Coach overlay shows the provider and model with a link to Settings > AI instead of a second set of selects, and the privacy page drops its copy of the export links. A guard walks every historical /settings address through the redirects and every forwarded anchor to a page that renders.
117 lines
4.0 KiB
TypeScript
117 lines
4.0 KiB
TypeScript
"use client";
|
|
|
|
/**
|
|
* Settings > System > Preview features.
|
|
*
|
|
* One switch per preview feature, each off until the person turns it on, each
|
|
* saved the moment it changes (`PATCH /api/settings/preview-features`). A
|
|
* preview adds something beside what is there and never replaces a page.
|
|
*
|
|
* The `topics` flag is stored and shown here; the topic overview pages that
|
|
* read it arrive in a later release, and the card says so rather than
|
|
* promising a change the switch does not make yet.
|
|
*/
|
|
|
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
import Link from "next/link";
|
|
import { LayoutDashboard } from "lucide-react";
|
|
|
|
import { toastWrittenOutcome } from "@/components/outcome/outcome-toast";
|
|
import { SettingsCard } from "@/components/settings/settings-card";
|
|
import { SettingsCardHeader } from "@/components/settings/_card-header";
|
|
import { InlineAction } from "@/components/ui/inline-action";
|
|
import { QueryErrorRow } from "@/components/ui/query-error-row";
|
|
import { Switch } from "@/components/ui/switch";
|
|
import { apiGet, apiPatch } from "@/lib/api/api-fetch";
|
|
import { showErrorToast } from "@/lib/errors/error-toast";
|
|
import { useTranslations } from "@/lib/i18n/context";
|
|
import { queryKeys } from "@/lib/query-keys";
|
|
import {
|
|
DEFAULT_PREVIEW_FEATURES,
|
|
type PreviewFeatures,
|
|
} from "@/lib/settings/preview-features";
|
|
|
|
const DISCUSSIONS_URL = "https://github.com/MBombeck/HealthLog/discussions";
|
|
|
|
export function PreviewSection() {
|
|
const { t } = useTranslations();
|
|
const queryClient = useQueryClient();
|
|
const key = queryKeys.previewFeatures();
|
|
|
|
const query = useQuery({
|
|
queryKey: key,
|
|
queryFn: () => apiGet<PreviewFeatures>("/api/settings/preview-features"),
|
|
});
|
|
const features = query.data ?? DEFAULT_PREVIEW_FEATURES;
|
|
|
|
const toggle = useMutation({
|
|
mutationKey: key,
|
|
mutationFn: (topics: boolean) =>
|
|
apiPatch<PreviewFeatures>("/api/settings/preview-features", { topics }),
|
|
onMutate: async (topics) => {
|
|
await queryClient.cancelQueries({ queryKey: key });
|
|
const previous = queryClient.getQueryData<PreviewFeatures>(key);
|
|
queryClient.setQueryData<PreviewFeatures>(key, {
|
|
...(previous ?? DEFAULT_PREVIEW_FEATURES),
|
|
topics,
|
|
});
|
|
return { previous };
|
|
},
|
|
onSuccess: (saved, topics) => {
|
|
if (saved) queryClient.setQueryData(key, saved);
|
|
toastWrittenOutcome(
|
|
"success",
|
|
topics
|
|
? t("settings.preview.topics.savedOn")
|
|
: t("settings.preview.topics.savedOff"),
|
|
);
|
|
},
|
|
onError: (failure, _topics, context) => {
|
|
queryClient.setQueryData(key, context?.previous);
|
|
showErrorToast(t("settings.savingError"), { cause: failure });
|
|
},
|
|
});
|
|
|
|
return (
|
|
<SettingsCard data-slot="settings-preview-topics">
|
|
<SettingsCardHeader
|
|
anchor="preview-topics"
|
|
icon={LayoutDashboard}
|
|
title={t("settings.preview.topics.title")}
|
|
titleId="settings-preview-topics-title"
|
|
description={t("settings.preview.topics.description")}
|
|
status={
|
|
<Switch
|
|
data-slot="settings-preview-topics-switch"
|
|
checked={features.topics}
|
|
disabled={query.isLoading || toggle.isPending}
|
|
onCheckedChange={(next) => toggle.mutate(next)}
|
|
aria-labelledby="settings-preview-topics-title"
|
|
/>
|
|
}
|
|
/>
|
|
{query.isError ? (
|
|
<QueryErrorRow
|
|
inset
|
|
error={query.error}
|
|
slot="settings-preview-error"
|
|
message={t("settings.preview.loadError")}
|
|
onRetry={() => void query.refetch()}
|
|
/>
|
|
) : null}
|
|
<p className="text-sm">{t("settings.preview.topics.detail")}</p>
|
|
<p className="text-muted-foreground text-xs">
|
|
<InlineAction asChild tone="muted" size="xs">
|
|
<Link
|
|
href={DISCUSSIONS_URL}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
{t("settings.preview.feedbackLink")}
|
|
</Link>
|
|
</InlineAction>
|
|
</p>
|
|
</SettingsCard>
|
|
);
|
|
}
|