SPB Git

spb/vquant Public MIT

VibeQuant — AI-powered institutional-grade financial intelligence platform.

TypeScript 84.3% Python 11.7% JavaScript 1.6% CSS 1.5% HTML 0.7%
4.4 KB · 208 lines typescript
Raw Blame History
1/*2 * =============================================================================3 *  VibeQuant (vquant) — AI-Powered Financial Intelligence Platform4 * -----------------------------------------------------------------------------5 *  File:      client/src/hooks/use-toast.ts6 *7 *  Author:    Simon-Pierre Boucher8 *  Contact:   contact@spboucher.ai9 *  Website:   https://www.spboucher.ai10 *  Demo:      https://www.vquant.ai11 *  License:   MIT (see LICENSE)12 *13 *  Copyright © 2026 Simon-Pierre Boucher. All rights reserved.14 * =============================================================================15 */1617import * as React from "react"1819import type {20  ToastActionElement,21  ToastProps,22} from "@/components/ui/toast"2324const TOAST_LIMIT = 125const TOAST_REMOVE_DELAY = 10000002627type ToasterToast = ToastProps & {28  id: string29  title?: React.ReactNode30  description?: React.ReactNode31  action?: ToastActionElement32}3334const actionTypes = {35  ADD_TOAST: "ADD_TOAST",36  UPDATE_TOAST: "UPDATE_TOAST",37  DISMISS_TOAST: "DISMISS_TOAST",38  REMOVE_TOAST: "REMOVE_TOAST",39} as const4041let count = 04243function genId() {44  count = (count + 1) % Number.MAX_SAFE_INTEGER45  return count.toString()46}4748type ActionType = typeof actionTypes4950type Action =51  | {52      type: ActionType["ADD_TOAST"]53      toast: ToasterToast54    }55  | {56      type: ActionType["UPDATE_TOAST"]57      toast: Partial<ToasterToast>58    }59  | {60      type: ActionType["DISMISS_TOAST"]61      toastId?: ToasterToast["id"]62    }63  | {64      type: ActionType["REMOVE_TOAST"]65      toastId?: ToasterToast["id"]66    }6768interface State {69  toasts: ToasterToast[]70}7172const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()7374const addToRemoveQueue = (toastId: string) => {75  if (toastTimeouts.has(toastId)) {76    return77  }7879  const timeout = setTimeout(() => {80    toastTimeouts.delete(toastId)81    dispatch({82      type: "REMOVE_TOAST",83      toastId: toastId,84    })85  }, TOAST_REMOVE_DELAY)8687  toastTimeouts.set(toastId, timeout)88}8990export const reducer = (state: State, action: Action): State => {91  switch (action.type) {92    case "ADD_TOAST":93      return {94        ...state,95        toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),96      }9798    case "UPDATE_TOAST":99      return {100        ...state,101        toasts: state.toasts.map((t) =>102          t.id === action.toast.id ? { ...t, ...action.toast } : t103        ),104      }105106    case "DISMISS_TOAST": {107      const { toastId } = action108109      // ! Side effects ! - This could be extracted into a dismissToast() action,110      // but I'll keep it here for simplicity111      if (toastId) {112        addToRemoveQueue(toastId)113      } else {114        state.toasts.forEach((toast) => {115          addToRemoveQueue(toast.id)116        })117      }118119      return {120        ...state,121        toasts: state.toasts.map((t) =>122          t.id === toastId || toastId === undefined123            ? {124                ...t,125                open: false,126              }127            : t128        ),129      }130    }131    case "REMOVE_TOAST":132      if (action.toastId === undefined) {133        return {134          ...state,135          toasts: [],136        }137      }138      return {139        ...state,140        toasts: state.toasts.filter((t) => t.id !== action.toastId),141      }142  }143}144145const listeners: Array<(state: State) => void> = []146147let memoryState: State = { toasts: [] }148149function dispatch(action: Action) {150  memoryState = reducer(memoryState, action)151  listeners.forEach((listener) => {152    listener(memoryState)153  })154}155156type Toast = Omit<ToasterToast, "id">157158function toast({ ...props }: Toast) {159  const id = genId()160161  const update = (props: ToasterToast) =>162    dispatch({163      type: "UPDATE_TOAST",164      toast: { ...props, id },165    })166  const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })167168  dispatch({169    type: "ADD_TOAST",170    toast: {171      ...props,172      id,173      open: true,174      onOpenChange: (open) => {175        if (!open) dismiss()176      },177    },178  })179180  return {181    id: id,182    dismiss,183    update,184  }185}186187function useToast() {188  const [state, setState] = React.useState<State>(memoryState)189190  React.useEffect(() => {191    listeners.push(setState)192    return () => {193      const index = listeners.indexOf(setState)194      if (index > -1) {195        listeners.splice(index, 1)196      }197    }198  }, [state])199200  return {201    ...state,202    toast,203    dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),204  }205}206207export { useToast, toast }208