diff --git a/bun.lock b/bun.lock index 67ba286..8cead78 100644 --- a/bun.lock +++ b/bun.lock @@ -16,6 +16,7 @@ "react-big-calendar": "^1.19.4", "react-dom": "19.1.1", "react-hook-form": "^7.60.0", + "react-toastify": "^11.0.5", "tailwind-merge": "^3.3.1", "zod": "^4.0.9", "zustand": "^5.0.6", @@ -1098,6 +1099,8 @@ "react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], + "react-toastify": ["react-toastify@11.0.5", "", { "dependencies": { "clsx": "^2.1.1" }, "peerDependencies": { "react": "^18 || ^19", "react-dom": "^18 || ^19" } }, "sha512-EpqHBGvnSTtHYhCPLxML05NLY2ZX0JURbAdNYa6BUkk+amz4wbKBQvoKQAB0ardvSarUBuY4Q4s1sluAzZwkmA=="], + "react-lifecycles-compat": ["react-lifecycles-compat@3.0.4", "", {}, "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="], "react-overlays": ["react-overlays@5.2.1", "", { "dependencies": { "@babel/runtime": "^7.13.8", "@popperjs/core": "^2.11.6", "@restart/hooks": "^0.4.7", "@types/warning": "^3.0.0", "dom-helpers": "^5.2.0", "prop-types": "^15.7.2", "uncontrollable": "^7.2.1", "warning": "^4.0.3" }, "peerDependencies": { "react": ">=16.3.0", "react-dom": ">=16.3.0" } }, "sha512-GLLSOLWr21CqtJn8geSwQfoJufdt3mfdsnIiQswouuQ2MMPns+ihZklxvsTDKD3cR2tF8ELbi5xUsvqVhR6WvA=="], diff --git a/package.json b/package.json index 45584d1..fcf0bb1 100644 --- a/package.json +++ b/package.json @@ -14,17 +14,18 @@ }, "dependencies": { "@headlessui/react": "^2.2.4", - "motion": "^12.23.12", "@hookform/resolvers": "^5.1.1", "axios": "^1.10.0", "clsx": "^2.1.1", "jwt-decode": "^4.0.0", + "motion": "^12.23.12", "mime-types": "^3.0.1", "next": "15.4.5", "react": "19.1.1", "react-big-calendar": "^1.19.4", "react-dom": "19.1.1", "react-hook-form": "^7.60.0", + "react-toastify": "^11.0.5", "tailwind-merge": "^3.3.1", "zod": "^4.0.9", "zustand": "^5.0.6" diff --git a/src/app/layout.tsx b/src/app/layout.tsx index c674bfc..52a9ef6 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,6 +1,9 @@ import type { Metadata } from "next"; import { Bai_Jamjuree } from "next/font/google"; import "./globals.css"; +import "react-toastify/dist/ReactToastify.css"; +import { ToastContainer } from "react-toastify"; +import { ToastProvider } from "@/contexts/ToastContext"; import { UserProvider } from "@/contexts/user-provider"; const jamjuree = Bai_Jamjuree({ @@ -24,7 +27,25 @@ export default function RootLayout({ return ( - {children} + + + {children} + + + ); diff --git a/src/contexts/ToastContext.tsx b/src/contexts/ToastContext.tsx new file mode 100644 index 0000000..53be004 --- /dev/null +++ b/src/contexts/ToastContext.tsx @@ -0,0 +1,54 @@ +"use client"; + +import React, { createContext, useContext, ReactNode } from "react"; +import { toast, ToastOptions } from "react-toastify"; + +interface ToastContextType { + showSuccess: (message: string, options?: ToastOptions) => void; + showError: (message: string, options?: ToastOptions) => void; + showWarning: (message: string, options?: ToastOptions) => void; + showInfo: (message: string, options?: ToastOptions) => void; +} + +const ToastContext = createContext(undefined); + +interface ToastProviderProps { + children: ReactNode; +} + +export const ToastProvider: React.FC = ({ children }) => { + const showSuccess = (message: string, options?: ToastOptions) => { + toast.success(message, options); + }; + + const showError = (message: string, options?: ToastOptions) => { + toast.error(message, options); + }; + + const showWarning = (message: string, options?: ToastOptions) => { + toast.warning(message, options); + }; + + const showInfo = (message: string, options?: ToastOptions) => { + toast.info(message, options); + }; + + const value = { + showSuccess, + showError, + showWarning, + showInfo, + }; + + return ( + {children} + ); +}; + +export const useToast = (): ToastContextType => { + const context = useContext(ToastContext); + if (context === undefined) { + throw new Error("useToast must be used within a ToastProvider"); + } + return context; +};