From 26390589d76cb5cc9927217388779e6190b90640 Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sat, 2 May 2026 02:09:54 +0530 Subject: [PATCH] fix(dashboard): keep settings storage opaque to users Page header no longer mentions persistence or shared scope; toast only surfaces validation messages, hiding 5xx/network errors behind a generic title. --- dashboard/src/features/settings/hooks.ts | 23 +++++++++++++++++------ dashboard/src/routes/settings.tsx | 2 +- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/dashboard/src/features/settings/hooks.ts b/dashboard/src/features/settings/hooks.ts index b56d5e1..40e1b09 100644 --- a/dashboard/src/features/settings/hooks.ts +++ b/dashboard/src/features/settings/hooks.ts @@ -1,10 +1,25 @@ import { queryOptions, useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { toast } from "sonner"; +import { ApiError } from "@/lib/api-client"; import { deleteSetting, fetchSettings, setSetting } from "./api"; import type { SettingsSnapshot } from "./types"; const KEY = ["settings"] as const; +/** + * Return a user-friendly error description. + * + * Validation errors (4xx) carry messages we wrote ourselves and are useful + * to surface verbatim. Anything else (5xx, network, unknown) is hidden + * behind a generic message so internal details never leak to the UI. + */ +function describeError(error: unknown): string | undefined { + if (error instanceof ApiError && error.status >= 400 && error.status < 500) { + return error.message; + } + return undefined; +} + export function settingsQuery() { return queryOptions({ queryKey: KEY, @@ -38,9 +53,7 @@ export function useUpdateSetting() { }, onError: (error, _input, ctx) => { if (ctx?.prev) qc.setQueryData(KEY, ctx.prev); - toast.error("Couldn't save setting", { - description: error instanceof Error ? error.message : String(error), - }); + toast.error("Couldn't save setting", { description: describeError(error) }); }, onSuccess: () => toast.success("Setting saved"), onSettled: () => qc.invalidateQueries({ queryKey: KEY }), @@ -63,9 +76,7 @@ export function useDeleteSetting() { }, onError: (error, _key, ctx) => { if (ctx?.prev) qc.setQueryData(KEY, ctx.prev); - toast.error("Couldn't delete setting", { - description: error instanceof Error ? error.message : String(error), - }); + toast.error("Couldn't delete setting", { description: describeError(error) }); }, onSettled: () => qc.invalidateQueries({ queryKey: KEY }), }); diff --git a/dashboard/src/routes/settings.tsx b/dashboard/src/routes/settings.tsx index 4e9c611..69274c7 100644 --- a/dashboard/src/routes/settings.tsx +++ b/dashboard/src/routes/settings.tsx @@ -21,7 +21,7 @@ function SettingsPage() { <> {isLoading || !data ? (