diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index cd9b4a0..5d0428d 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,6 +1,8 @@ import { Routes, Route } from 'react-router-dom' import { ModalProvider } from './context/modals/useModal' import { ModalRoot } from './context/modals/ModalRoot' +import { ToastProvider } from './context/toasts/useToast' +import { ToastRoot } from './context/toasts/ToastRoot' import Cart from './pages/Cart' import { Navbar } from './components/Navbar' import { Home } from './pages/Home' @@ -11,22 +13,25 @@ import { Orders } from './pages/Orders' const App = () => { return ( - -
- -
- - } /> - } /> - } /> - } /> - } /> - } /> - -
- -
-
+ + +
+ +
+ + } /> + } /> + } /> + } /> + } /> + } /> + +
+ + +
+
+
) } diff --git a/frontend/src/context/toasts/ToastExample.tsx b/frontend/src/context/toasts/ToastExample.tsx new file mode 100644 index 0000000..dbc52f0 --- /dev/null +++ b/frontend/src/context/toasts/ToastExample.tsx @@ -0,0 +1,92 @@ +import { useToast } from './useToast'; + +export const ToastExample = () => { + const { addNotification } = useToast(); + + const showSuccess = () => { + addNotification({ + type: 'success', + title: 'Success!', + message: 'Operation completed successfully.', + duration: 3000, + }); + }; + + const showError = () => { + addNotification({ + type: 'error', + title: 'Error!', + message: 'Something went wrong. Please try again.', + duration: 5000, + }); + }; + + const showWarning = () => { + addNotification({ + type: 'warning', + title: 'Warning', + message: 'Please review your input before proceeding.', + duration: 4000, + }); + }; + + const showInfo = () => { + addNotification({ + type: 'info', + title: 'Information', + message: 'This is an informational message.', + duration: 3000, + }); + }; + + const showSticky = () => { + addNotification({ + type: 'success', + title: 'Sticky Notification', + message: 'This will stay until you close it!', + duration: 0, // 0 means no auto-dismiss + }); + }; + + return ( +
+

Toast System Examples

+

+ Click the buttons below to see different toast notifications in action. The toast uses React Context for state management. +

+ +
+ + + + + +
+
+ ); +}; \ No newline at end of file diff --git a/frontend/src/context/toasts/ToastRoot.tsx b/frontend/src/context/toasts/ToastRoot.tsx new file mode 100644 index 0000000..3d92fc2 --- /dev/null +++ b/frontend/src/context/toasts/ToastRoot.tsx @@ -0,0 +1,71 @@ +import { createPortal } from 'react-dom'; +import { useToast, NotificationType } from './useToast'; + +// Simple Icons (No external lib) +const Icons = { + success: ( + + + + ), + error: ( + + + + ), + warning: ( + + + + ), + info: ( + + + + ), +}; + +const getColors = (type: NotificationType) => { + switch (type) { + case 'success': return 'bg-white border-l-4 border-green-500'; + case 'error': return 'bg-white border-l-4 border-red-500'; + case 'warning': return 'bg-white border-l-4 border-yellow-500'; + case 'info': return 'bg-white border-l-4 border-blue-500'; + } +}; + +export const ToastRoot = () => { + const { toasts, removeNotification } = useToast(); + + if (toasts.length === 0) return null; + + return createPortal( +
+ {toasts.map((toast) => ( +
+
{Icons[toast.type]}
+
+

{toast.title}

+ {toast.message && ( +

{toast.message}

+ )} +
+ +
+ ))} +
, + document.body + ); +}; \ No newline at end of file diff --git a/frontend/src/context/toasts/index.ts b/frontend/src/context/toasts/index.ts new file mode 100644 index 0000000..486e6ab --- /dev/null +++ b/frontend/src/context/toasts/index.ts @@ -0,0 +1,3 @@ +export { ToastProvider, useToast } from './useToast'; +export type { NotificationType, ToastNotification } from './useToast'; +export { ToastRoot } from './ToastRoot'; diff --git a/frontend/src/context/toasts/useToast.tsx b/frontend/src/context/toasts/useToast.tsx new file mode 100644 index 0000000..75b9e2d --- /dev/null +++ b/frontend/src/context/toasts/useToast.tsx @@ -0,0 +1,62 @@ +import { createContext, useContext, useState, useCallback, ReactNode, FC } from 'react'; + +export type NotificationType = 'success' | 'error' | 'warning' | 'info'; + +export interface ToastNotification { + id: string; + type: NotificationType; + title: string; + message?: string; + duration?: number; // ms, default 5000 +} + +interface ToastContextType { + toasts: ToastNotification[]; + addNotification: (toast: Omit) => void; + removeNotification: (id: string) => void; +} + +const ToastContext = createContext(undefined); + +interface ToastProviderProps { + children: ReactNode; +} + +export const ToastProvider: FC = ({ children }) => { + const [toasts, setToasts] = useState([]); + + const addNotification = useCallback((toast: Omit) => { + const id = Math.random().toString(36).substr(2, 9); + const newToast: ToastNotification = { + ...toast, + id, + duration: toast.duration ?? 5000, // Default 5s + }; + + setToasts((prev) => [...prev, newToast]); + + // Auto-remove after duration + const duration = newToast.duration ?? 5000; + if (duration > 0) { + setTimeout(() => { + setToasts((prev) => prev.filter((t) => t.id !== id)); + }, duration); + } + }, []); + + const removeNotification = useCallback((id: string) => { + setToasts((prev) => prev.filter((t) => t.id !== id)); + }, []); + + return ( + + {children} + + ); +}; + +export const useToast = () => { + const context = useContext(ToastContext); + if (!context) throw new Error('useToast must be used within a ToastProvider'); + return context; +}; \ No newline at end of file diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home.tsx index 73e56dd..507c24c 100644 --- a/frontend/src/pages/Home.tsx +++ b/frontend/src/pages/Home.tsx @@ -1,5 +1,6 @@ import { Link } from 'react-router-dom' import { ModalExample } from '../context/modals/ModalExample' +import { ToastExample } from '../context/toasts/ToastExample' export function Home() { return ( @@ -41,6 +42,14 @@ export function Home() {

+ +
+

Toast System Demo

+

+ Test our toast notification system with this interactive example. The toast uses React Context for state management. +

+ +
) }