SPB Git

spb/findata

Public
2.2 KB · 63 lines tsx
1"use client";2import { createContext, useCallback, useContext, useMemo, useRef, useState, type ReactNode } from "react";3import { isApiError } from "./api";45export type ToastKind = "info" | "success" | "warning" | "error";6export interface ToastItem {7  id: number;8  kind: ToastKind;9  title: string;10  detail?: string;11  requestId?: string | null;12}1314interface ToastCtx {15  toasts: ToastItem[];16  push: (t: Omit<ToastItem, "id">) => void;17  dismiss: (id: number) => void;18  error: (e: unknown, fallback?: string) => void;19  success: (title: string, detail?: string) => void;20  info: (title: string, detail?: string) => void;21}2223const Ctx = createContext<ToastCtx | null>(null);2425export function ToastProvider({ children }: { children: ReactNode }) {26  const [toasts, setToasts] = useState<ToastItem[]>([]);27  const seq = useRef(0);2829  const dismiss = useCallback((id: number) => setToasts((t) => t.filter((x) => x.id !== id)), []);30  const push = useCallback(31    (t: Omit<ToastItem, "id">) => {32      const id = ++seq.current;33      setToasts((prev) => [...prev.slice(-4), { ...t, id }]);34      const ttl = t.kind === "error" ? 9000 : 4500;35      window.setTimeout(() => dismiss(id), ttl);36    },37    [dismiss],38  );39  const error = useCallback(40    (e: unknown, fallback = "Request failed") => {41      if (isApiError(e)) {42        push({ kind: "error", title: `${e.message}`, detail: `${e.code} · HTTP ${e.status}`, requestId: e.request_id });43      } else if (e instanceof Error) {44        push({ kind: "error", title: fallback, detail: e.message });45      } else {46        push({ kind: "error", title: fallback });47      }48    },49    [push],50  );51  const success = useCallback((title: string, detail?: string) => push({ kind: "success", title, detail }), [push]);52  const info = useCallback((title: string, detail?: string) => push({ kind: "info", title, detail }), [push]);5354  const value = useMemo(() => ({ toasts, push, dismiss, error, success, info }), [toasts, push, dismiss, error, success, info]);55  return <Ctx.Provider value={value}>{children}</Ctx.Provider>;56}5758export function useToast(): ToastCtx {59  const c = useContext(Ctx);60  if (!c) throw new Error("useToast outside ToastProvider");61  return c;62}63
Files · 17d97adc36cc25b48b7afafe2f7c8bc86b240e41
Menu

Theme