feat: add initial implementation of Kitchen CRM with authentication and dashboard features
- Create global styles and theme management - Implement app shell layout with sidebar navigation - Add authentication layout and pages for login and registration - Develop dashboard page with placeholder content - Introduce routing guards for guest-only and authenticated routes - Set up Zustand for state management of authentication and theme - Create API types and structures for CRM entities - Configure Vite with PWA support and Tailwind CSS
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
import * as React from 'react'
|
||||
|
||||
import type { ToastActionElement, ToastProps } from '@/components/ui/toast'
|
||||
|
||||
const TOAST_LIMIT = 10
|
||||
const TOAST_REMOVE_DELAY = 1000
|
||||
|
||||
type ToasterToast = ToastProps & {
|
||||
id: string
|
||||
title?: React.ReactNode
|
||||
description?: React.ReactNode
|
||||
action?: ToastActionElement
|
||||
}
|
||||
|
||||
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
|
||||
|
||||
const addToRemoveQueue = (toastId: string) => {
|
||||
if (toastTimeouts.has(toastId)) return
|
||||
const timeout = setTimeout(() => {
|
||||
toastTimeouts.delete(toastId)
|
||||
toastState.remove(toastId)
|
||||
}, TOAST_REMOVE_DELAY)
|
||||
toastTimeouts.set(toastId, timeout)
|
||||
}
|
||||
|
||||
const createToastState = () => {
|
||||
const listeners = new Set<(toasts: ToasterToast[]) => void>()
|
||||
let toasts: ToasterToast[] = []
|
||||
|
||||
const notify = () => {
|
||||
listeners.forEach((listener) => listener(toasts))
|
||||
}
|
||||
|
||||
return {
|
||||
subscribe: (listener: (toasts: ToasterToast[]) => void) => {
|
||||
listeners.add(listener)
|
||||
return () => {
|
||||
listeners.delete(listener)
|
||||
}
|
||||
},
|
||||
add: (toast: ToasterToast) => {
|
||||
toasts = [toast, ...toasts].slice(0, TOAST_LIMIT)
|
||||
notify()
|
||||
},
|
||||
update: (toastId: string, toast: Partial<ToasterToast>) => {
|
||||
toasts = toasts.map((item) => (item.id === toastId ? { ...item, ...toast } : item))
|
||||
notify()
|
||||
},
|
||||
dismiss: (toastId: string) => {
|
||||
addToRemoveQueue(toastId)
|
||||
},
|
||||
remove: (toastId: string) => {
|
||||
toasts = toasts.filter((item) => item.id !== toastId)
|
||||
notify()
|
||||
},
|
||||
getSnapshot: () => toasts,
|
||||
}
|
||||
}
|
||||
|
||||
const toastState = createToastState()
|
||||
|
||||
export const useToast = () => {
|
||||
const [toasts, setToasts] = React.useState<ToasterToast[]>(toastState.getSnapshot())
|
||||
|
||||
React.useEffect(() => toastState.subscribe(setToasts), [])
|
||||
|
||||
const toast = React.useCallback((props: Omit<ToasterToast, 'id'>) => {
|
||||
const id = crypto.randomUUID()
|
||||
toastState.add({ id, ...props })
|
||||
return id
|
||||
}, [])
|
||||
|
||||
const dismiss = React.useCallback((toastId: string) => {
|
||||
toastState.dismiss(toastId)
|
||||
}, [])
|
||||
|
||||
return {
|
||||
toast,
|
||||
dismiss,
|
||||
toasts,
|
||||
}
|
||||
}
|
||||
|
||||
export type { ToastProps, ToasterToast }
|
||||
Reference in New Issue
Block a user