From 2851f8d03c28d5ba4b9849b3004cf41219d7826c Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sun, 16 Mar 2025 09:33:24 +0000 Subject: [PATCH] Fix: Resolve toast notification issue The toast notifications were not displaying. This commit addresses the problem. --- src/components/ui/toaster.tsx | 1 + src/components/ui/use-toast.ts | 6 +- src/hooks/use-toast.ts | 244 +++++++++++++++++++++++---------- 3 files changed, 178 insertions(+), 73 deletions(-) diff --git a/src/components/ui/toaster.tsx b/src/components/ui/toaster.tsx index 6c67edf..2367807 100644 --- a/src/components/ui/toaster.tsx +++ b/src/components/ui/toaster.tsx @@ -1,3 +1,4 @@ + import { useToast } from "@/hooks/use-toast" import { Toast, diff --git a/src/components/ui/use-toast.ts b/src/components/ui/use-toast.ts index f888a93..cb982bd 100644 --- a/src/components/ui/use-toast.ts +++ b/src/components/ui/use-toast.ts @@ -1,5 +1,7 @@ -// useToast.wrapper.ts에서 수정된 toast 함수를 사용하도록 리디렉션 -import { useToast, toast } from "@/hooks/useToast.wrapper"; +// Shadcn UI의 최신 버전에서는 use-toast가 hooks 폴더로 이동했습니다. +// 이 파일은 기존 import 경로가 작동하도록 리디렉션합니다. +import { useToast, toast } from "@/hooks/use-toast"; export { useToast, toast }; +export type { ToasterToast } from "@/hooks/use-toast"; diff --git a/src/hooks/use-toast.ts b/src/hooks/use-toast.ts index 83f6389..a684d4f 100644 --- a/src/hooks/use-toast.ts +++ b/src/hooks/use-toast.ts @@ -1,88 +1,190 @@ import * as React from "react" -import { ToastProps } from "@/components/ui/toast" -// useToastStore 대신 toast를 임포트합니다. -import { toast as originalToast } from "@/components/ui/use-toast" -import { ToastActionElement } from "@/components/ui/toast" +const TOAST_LIMIT = 20 +export const TOAST_REMOVE_DELAY = 1000000 -// 기본 지속 시간 늘리기 (기존 3000ms → 3500ms) -export const DEFAULT_TOAST_DURATION = 3500; +export type ToasterToast = { + id: string + title?: React.ReactNode + description?: React.ReactNode + action?: React.ReactNode + variant?: "default" | "destructive" + duration?: number +} -export const TOAST_REMOVE_DELAY = 1000000; - -export type ToasterToast = ToastProps & { - id: string; - title?: React.ReactNode; - description?: React.ReactNode; - action?: React.ReactNode; - duration?: number; // 지속 시간 추가 - variant?: "default" | "destructive"; // variant 속성 추가 -}; - -const actionTypes = ['default', 'cancel'] as const - -export type ToastActionType = typeof actionTypes[number] +const actionTypes = { + ADD_TOAST: "ADD_TOAST", + UPDATE_TOAST: "UPDATE_TOAST", + DISMISS_TOAST: "DISMISS_TOAST", + REMOVE_TOAST: "REMOVE_TOAST", +} as const let count = 0 function genId() { - return String(count++) + count = (count + 1) % Number.MAX_SAFE_INTEGER + return count.toString() } -export const useToast = () => { - const [toasts, setToasts] = React.useState([]); +type ActionType = typeof actionTypes - // 토스트 추가 함수 - const addToast = React.useCallback( - (props: Omit) => { - // 토스트에 기본 지속 시간 추가 - const toastWithDefaults = { - ...props, - duration: props.duration || DEFAULT_TOAST_DURATION, - }; - - const id = genId() +type Action = + | { + type: ActionType["ADD_TOAST"] + toast: ToasterToast + } + | { + type: ActionType["UPDATE_TOAST"] + toast: Partial & { id: string } + } + | { + type: ActionType["DISMISS_TOAST"] + toastId?: string + } + | { + type: ActionType["REMOVE_TOAST"] + toastId?: string + } - setToasts((prev) => [...prev, { id, ...toastWithDefaults }]) - return id +interface State { + toasts: ToasterToast[] +} + +const toastTimeouts = new Map>() + +const addToRemoveQueue = (toastId: string) => { + if (toastTimeouts.has(toastId)) { + return + } + + const timeout = setTimeout(() => { + toastTimeouts.delete(toastId) + dispatch({ + type: actionTypes.REMOVE_TOAST, + toastId: toastId, + }) + }, TOAST_REMOVE_DELAY) + + toastTimeouts.set(toastId, timeout) +} + +export const reducer = (state: State, action: Action): State => { + switch (action.type) { + case actionTypes.ADD_TOAST: + return { + ...state, + toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), + } + + case actionTypes.UPDATE_TOAST: + return { + ...state, + toasts: state.toasts.map((t) => + t.id === action.toast.id ? { ...t, ...action.toast } : t + ), + } + + case actionTypes.DISMISS_TOAST: { + const { toastId } = action + + if (toastId) { + addToRemoveQueue(toastId) + } else { + state.toasts.forEach((toast) => { + addToRemoveQueue(toast.id) + }) + } + + return { + ...state, + toasts: state.toasts.map((t) => + t.id === toastId || toastId === undefined + ? { + ...t, + open: false, + } + : t + ), + } + } + case actionTypes.REMOVE_TOAST: + if (action.toastId === undefined) { + return { + ...state, + toasts: [], + } + } + return { + ...state, + toasts: state.toasts.filter((t) => t.id !== action.toastId), + } + default: + return state + } +} + +const listeners: Array<(state: State) => void> = [] + +let memoryState: State = { toasts: [] } + +function dispatch(action: Action) { + memoryState = reducer(memoryState, action) + listeners.forEach((listener) => { + listener(memoryState) + }) +} + +type Toast = Omit + +function toast({ ...props }: Toast) { + const id = genId() + + const update = (props: ToasterToast) => + dispatch({ + type: actionTypes.UPDATE_TOAST, + toast: { ...props, id }, + }) + const dismiss = () => dispatch({ type: actionTypes.DISMISS_TOAST, toastId: id }) + + dispatch({ + type: actionTypes.ADD_TOAST, + toast: { + ...props, + id, + open: true, + onOpenChange: (open) => { + if (!open) dismiss() + }, + duration: props.duration || 5000, // 기본 지속 시간 설정 }, - [toasts] - ); - - const updateToast = React.useCallback( - (id: string, props: Partial) => { - setToasts((prev) => - prev.map((toast) => (toast.id === id ? { ...toast, ...props } : toast)) - ) - }, - [toasts] - ) - - const dismissToast = React.useCallback( - (id: string) => { - setToasts((prev) => prev.filter((toast) => toast.id !== id)) - }, - [toasts] - ) + }) return { - toasts, - addToast, - updateToast, - dismissToast, - toast: (props: Omit) => addToast(props), - }; -}; + id, + dismiss, + update, + } +} -// 전역 토스트 함수에도 기본 지속 시간 적용 -export const toast = (props: Omit) => { - // 기본 지속 시간을 적용 - const toastWithDefaults = { - ...props, - duration: props.duration || DEFAULT_TOAST_DURATION, - }; - - // 원래 toast 함수 호출 - return originalToast(toastWithDefaults); -}; +function useToast() { + const [state, setState] = React.useState(memoryState) + + React.useEffect(() => { + listeners.push(setState) + return () => { + const index = listeners.indexOf(setState) + if (index > -1) { + listeners.splice(index, 1) + } + } + }, [state]) + + return { + ...state, + toast, + dismiss: (toastId?: string) => dispatch({ type: actionTypes.DISMISS_TOAST, toastId }), + } +} + +export { useToast, toast }