The api client now catches network failures and non-JSON bodies robustly, and routes unexpected errors (offline / 5xx) to a global toast — 4xx stay with the caller so forms keep their inline messages. Toast actions are now optional (undo toasts keep their button; error toasts are message-only), and ToastHost moved from AppShell to the app root so toasts show everywhere, including the login screen. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
52 lines
1.3 KiB
TypeScript
52 lines
1.3 KiB
TypeScript
import { defineStore } from "pinia";
|
|
import { ref } from "vue";
|
|
|
|
interface ToastAction {
|
|
label: string;
|
|
run: () => void;
|
|
}
|
|
|
|
interface Toast {
|
|
id: number;
|
|
message: string;
|
|
action?: ToastAction;
|
|
}
|
|
|
|
// Cross-component UI signals that don't belong to any single view (e.g. a
|
|
// global shortcut in the app shell asking the board to act).
|
|
export const useUiStore = defineStore("ui", () => {
|
|
// Bumped to ask the board to open + focus its quick-add composer.
|
|
const composeTick = ref(0);
|
|
function requestCompose() {
|
|
composeTick.value++;
|
|
}
|
|
|
|
// Transient undo toast (e.g. after trash/archive).
|
|
const toast = ref<Toast | null>(null);
|
|
let toastTimer: ReturnType<typeof setTimeout> | undefined;
|
|
let toastSeq = 0;
|
|
|
|
function showToast(message: string, action?: ToastAction) {
|
|
toastSeq += 1;
|
|
const id = toastSeq;
|
|
toast.value = { id, message, action };
|
|
clearTimeout(toastTimer);
|
|
toastTimer = setTimeout(() => {
|
|
if (toast.value?.id === id) toast.value = null;
|
|
}, 5000);
|
|
}
|
|
|
|
function dismissToast() {
|
|
clearTimeout(toastTimer);
|
|
toast.value = null;
|
|
}
|
|
|
|
function runToastAction() {
|
|
const run = toast.value?.action?.run;
|
|
dismissToast();
|
|
run?.();
|
|
}
|
|
|
|
return { composeTick, requestCompose, toast, showToast, dismissToast, runToastAction };
|
|
});
|