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%
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