feat(web): Base UI toast region + global mutation feedback wiring (#47)

Add a module-scope Base UI toast manager bridged to the QueryClient so
every mutation can give consistent feedback. A MutationCache (extracted
into a makeQueryClient() factory for test reuse) emits a catch-all,
type-aware error toast (unless meta.suppressErrorToast) and an opt-in
success toast (meta.successMessage), reading mutation.meta + i18n.t
outside React. meta is type-checked via a react-query Register
augmentation. ToastRegion is mounted app-wide in main.tsx. Adds a toast
i18n namespace (en/sv parity) and a validated story.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-07 12:46:26 +02:00
parent 63bfff417b
commit 604d4f6005
8 changed files with 183 additions and 5 deletions
+49
View File
@@ -0,0 +1,49 @@
import {
MutationCache,
QueryClient,
type MutationMeta,
} from "@tanstack/react-query";
import i18n from "../i18n";
import { toastManager } from "../toast/toast-manager";
import { HttpError, InUseError } from "./queries";
function mutationErrorMessage(
error: unknown,
meta: MutationMeta | undefined,
): string {
if (meta?.errorMessage) return i18n.t(meta.errorMessage);
if (error instanceof InUseError) {
return i18n.t("actions.inUse", { count: error.count });
}
if (error instanceof HttpError && error.status === 503) {
return i18n.t("search.unavailable");
}
return i18n.t("toast.error");
}
/** Builds the app's QueryClient, including the MutationCache that bridges every
* mutation to the toast region (catch-all error toast + opt-in success toast).
* Shared by main.tsx and tests so the toast wiring stays consistent. */
export function makeQueryClient(): QueryClient {
return new QueryClient({
defaultOptions: { queries: { retry: false, refetchOnWindowFocus: false } },
mutationCache: new MutationCache({
onError: (error, _vars, _ctx, mutation) => {
if (mutation.meta?.suppressErrorToast) return;
toastManager.add({
type: "error",
description: mutationErrorMessage(error, mutation.meta),
});
},
onSuccess: (_data, _vars, _ctx, mutation) => {
if (mutation.meta?.successMessage) {
toastManager.add({
type: "success",
description: i18n.t(mutation.meta.successMessage),
});
}
},
}),
});
}