diff --git a/apps/admin/app/layout.tsx b/apps/admin/app/layout.tsx index cf1d8b81..7b198a7b 100644 --- a/apps/admin/app/layout.tsx +++ b/apps/admin/app/layout.tsx @@ -1,10 +1,12 @@ import "./global.css"; import "wowds-ui/styles.css"; import "@wow-class/ui/styles.css"; +import "react-toastify/dist/ReactToastify.css"; import { JotaiProvider } from "components/JotaiProvider"; import type { Metadata } from "next"; import type { ReactNode } from "react"; +import { ToastContainer } from "react-toastify"; export const metadata: Metadata = { title: { @@ -47,6 +49,12 @@ const RootLayout = ({ return ( + {children} {modal} diff --git a/apps/admin/package.json b/apps/admin/package.json index 74112600..5ec0e41a 100644 --- a/apps/admin/package.json +++ b/apps/admin/package.json @@ -17,7 +17,8 @@ "react-clock": "^5.0.0", "react-day-picker": "^9.0.8", "react-dom": "^18.3.1", - "react-hook-form": "^7.53.0" + "react-hook-form": "^7.53.0", + "react-toastify": "^10.0.5" }, "devDependencies": { "@hookform/resolvers": "^3.9.0", diff --git a/apps/client/app/layout.tsx b/apps/client/app/layout.tsx index 3ffa3ccb..a2963883 100644 --- a/apps/client/app/layout.tsx +++ b/apps/client/app/layout.tsx @@ -1,8 +1,10 @@ import "./global.css"; import "wowds-ui/styles.css"; import "@wow-class/ui/styles.css"; +import "react-toastify/dist/ReactToastify.css"; import type { Metadata } from "next"; +import { ToastContainer } from "react-toastify"; import { JotaiProvider } from "../components/JotaiProvider"; @@ -53,6 +55,12 @@ const RootLayout = ({ return ( + {children} diff --git a/apps/client/package.json b/apps/client/package.json index d7c4fb9f..713d0d6d 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -14,6 +14,7 @@ "next": "^14.2.5", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-toastify": "^10.0.5", "wowds-icons": "^0.1.3", "wowds-tokens": "^0.1.1" }, diff --git a/packages/utils/package.json b/packages/utils/package.json index 5410d84a..2845c1b5 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -14,10 +14,11 @@ "@wow-class/typescript-config": "workspace:*", "jest": "^29.7.0", "jest-fetch-mock": "^3.0.3", - "typescript": "^5.3.3", - "ts-jest": "^29.2.4" + "ts-jest": "^29.2.4", + "typescript": "^5.3.3" }, "dependencies": { - "next": "^14.2.5" + "next": "^14.2.5", + "react-toastify": "^10.0.5" } } diff --git a/packages/utils/src/fetcher/index.ts b/packages/utils/src/fetcher/index.ts index c91cc884..451775a3 100644 --- a/packages/utils/src/fetcher/index.ts +++ b/packages/utils/src/fetcher/index.ts @@ -1,3 +1,5 @@ +import { toast } from "react-toastify"; + type ApiResponse = Response & { data?: T; success?: boolean }; type RequestInterceptor = ( @@ -7,6 +9,8 @@ type ResponseInterceptor = ( response: ApiResponse ) => ApiResponse | Promise>; +const isClient = typeof window !== "undefined"; + class Fetcher { private baseUrl: string; private defaultHeaders: HeadersInit; @@ -68,16 +72,21 @@ class Fetcher { return response.text(); } - private async handleError(response: Response) { + private async handleError( + response: Response, + data: { + errorCodeName: string; + errorMessage: string; + } + ) { if (!response.ok) { - const text = await response.text(); - const error = new Error( - `HTTP Error: ${response.status} ${response.statusText}` - ); - (error as any).response = response; - (error as any).responseText = text; - - throw error; + const error = new Error(); + error.message = data.errorMessage; + error.name = data.errorCodeName; + + if (isClient) { + toast.error(error.message); + } else throw error; } } @@ -96,10 +105,11 @@ class Fetcher { let response: ApiResponse = await fetch(fullUrl, fetchOptions); - await this.handleError(response); + const data = await this.parseJsonResponse(response); + await this.handleError(response, data); response = await this.interceptResponse(response); - response.data = await this.parseJsonResponse(response); + response.data = data; return response; } @@ -162,8 +172,6 @@ class Fetcher { } } -const isClient = typeof window !== "undefined"; - const fetcher = new Fetcher({ baseUrl: process.env.NEXT_PUBLIC_VERCEL_ENV === "production" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 92d76d02..29eca237 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -72,6 +72,9 @@ importers: react-hook-form: specifier: ^7.53.0 version: 7.53.0(react@18.3.1) + react-toastify: + specifier: ^10.0.5 + version: 10.0.5(react-dom@18.3.1)(react@18.3.1) devDependencies: '@hookform/resolvers': specifier: ^3.9.0 @@ -127,6 +130,9 @@ importers: react-dom: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) + react-toastify: + specifier: ^10.0.5 + version: 10.0.5(react-dom@18.3.1)(react@18.3.1) wowds-icons: specifier: ^0.1.3 version: 0.1.3 @@ -286,6 +292,9 @@ importers: next: specifier: ^14.2.5 version: 14.2.5(@babel/core@7.25.2)(react-dom@18.3.1)(react@18.3.1) + react-toastify: + specifier: ^10.0.5 + version: 10.0.5(react-dom@18.3.1)(react@18.3.1) devDependencies: '@types/jest': specifier: ^29.5.12 @@ -11655,6 +11664,17 @@ packages: - '@types/react-dom' dev: false + /react-toastify@10.0.5(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-mNKt2jBXJg4O7pSdbNUfDdTsK9FIdikfsIE/yUCxbAEXl4HMyJaivrVFcn3Elvt5xvCQYhUZm+hqTIu1UXM3Pw==} + peerDependencies: + react: '>=18' + react-dom: '>=18' + dependencies: + clsx: 2.1.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + dev: false + /react@18.3.1: resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'}