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'}