@farmzone/fz-template-react 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/template/.env.example +5 -5
- package/template/package.json +55 -55
- package/template/pnpm-lock.yaml +4214 -0
- package/template/public/mockServiceWorker.js +349 -349
- package/template/src/app/api/api.ts +178 -178
- package/template/src/app/api/queries.ts +321 -321
- package/template/src/app/api/queryKey.ts +7 -7
- package/template/src/app/api/token.ts +7 -7
- package/template/src/app/layout/Layout.tsx +33 -33
- package/template/src/app/layout/ListContents.tsx +9 -9
- package/template/src/app/layout/ListHeader.tsx +41 -41
- package/template/src/app/layout/MultiTabNav.tsx +101 -101
- package/template/src/app/layout/Sidebar.tsx +33 -33
- package/template/src/app/layout/UserInfo.tsx +94 -94
- package/template/src/app/layout/menu.ts +4 -1
- package/template/src/app/layout/tabSwitchStore.ts +11 -11
- package/template/src/app/router/Router.tsx +56 -54
- package/template/src/app/store/index.ts +26 -26
- package/template/src/index.tsx +21 -21
- package/template/src/mocks/browser.ts +17 -17
- package/template/src/mocks/handlers.ts +43 -43
- package/template/src/mocks/scenarios.ts +57 -57
- package/template/src/pages/dashboard/index.tsx +541 -541
- package/template/src/pages/error/Error.tsx +29 -29
- package/template/src/pages/error/NotFound.tsx +27 -27
- package/template/src/pages/login/index.tsx +317 -317
- package/template/src/pages/post/PostFormModal.tsx +128 -128
- package/template/src/pages/post/detail/index.tsx +548 -548
- package/template/src/pages/post/index.tsx +267 -267
- package/template/src/pages/sample/SampleFormModal.tsx +77 -77
- package/template/src/pages/sample/detail/index.tsx +424 -424
- package/template/src/pages/sample/index.tsx +269 -269
- package/template/src/pages/sample/modal/index.tsx +253 -0
- package/template/src/pages/system/log/index.tsx +173 -173
- package/template/src/pages/user/config/columns.tsx +109 -109
- package/template/src/pages/user/config/schema.ts +54 -54
- package/template/src/pages/user/index.tsx +641 -641
- package/template/src/shared/components/CommentInput.tsx +243 -243
- package/template/src/shared/config/text.ts +27 -27
- package/template/src/shared/utils/format.ts +11 -11
- package/template/src/types/auth.ts +10 -10
- package/template/src/types/comment.ts +33 -33
- package/template/src/types/common.ts +19 -19
- package/template/src/types/dashboard.ts +53 -53
- package/template/src/types/index.ts +16 -16
- package/template/src/types/log.ts +21 -21
- package/template/src/types/post.ts +32 -32
- package/template/src/types/sample.ts +28 -28
- package/template/src/types/user.ts +51 -51
- package/template/src/vite-env.d.ts +10 -10
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { create } from "zustand";
|
|
2
|
-
|
|
3
|
-
interface TabSwitchState {
|
|
4
|
-
tabSwitchKey: number;
|
|
5
|
-
commitTabSwitch: () => void;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export const useTabSwitchStore = create<TabSwitchState>((set) => ({
|
|
9
|
-
tabSwitchKey: 0,
|
|
10
|
-
commitTabSwitch: () => set((s) => ({ tabSwitchKey: s.tabSwitchKey + 1 })),
|
|
11
|
-
}));
|
|
1
|
+
import { create } from "zustand";
|
|
2
|
+
|
|
3
|
+
interface TabSwitchState {
|
|
4
|
+
tabSwitchKey: number;
|
|
5
|
+
commitTabSwitch: () => void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const useTabSwitchStore = create<TabSwitchState>((set) => ({
|
|
9
|
+
tabSwitchKey: 0,
|
|
10
|
+
commitTabSwitch: () => set((s) => ({ tabSwitchKey: s.tabSwitchKey + 1 })),
|
|
11
|
+
}));
|
|
@@ -1,54 +1,56 @@
|
|
|
1
|
-
import Cookies from "js-cookie";
|
|
2
|
-
import { useMemo } from "react";
|
|
3
|
-
import { createBrowserRouter, redirect, RouterProvider } from "react-router";
|
|
4
|
-
|
|
5
|
-
import Layout from "@/app/layout/Layout";
|
|
6
|
-
import DashboardPage from "@/pages/dashboard";
|
|
7
|
-
import ErrorPage from "@/pages/error/Error";
|
|
8
|
-
import NotFoundPage from "@/pages/error/NotFound";
|
|
9
|
-
import LoginPage from "@/pages/login";
|
|
10
|
-
import SamplePage from "@/pages/sample";
|
|
11
|
-
import SampleDetailPage from "@/pages/sample/detail";
|
|
12
|
-
import
|
|
13
|
-
import
|
|
14
|
-
import
|
|
15
|
-
import
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
{
|
|
41
|
-
{ path: "sample
|
|
42
|
-
{ path: "
|
|
43
|
-
{ path: "
|
|
44
|
-
{ path: "
|
|
45
|
-
{ path: "
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
1
|
+
import Cookies from "js-cookie";
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
import { createBrowserRouter, redirect, RouterProvider } from "react-router";
|
|
4
|
+
|
|
5
|
+
import Layout from "@/app/layout/Layout";
|
|
6
|
+
import DashboardPage from "@/pages/dashboard";
|
|
7
|
+
import ErrorPage from "@/pages/error/Error";
|
|
8
|
+
import NotFoundPage from "@/pages/error/NotFound";
|
|
9
|
+
import LoginPage from "@/pages/login";
|
|
10
|
+
import SamplePage from "@/pages/sample";
|
|
11
|
+
import SampleDetailPage from "@/pages/sample/detail";
|
|
12
|
+
import SampleModalPage from "@/pages/sample/modal";
|
|
13
|
+
import PostPage from "@/pages/post";
|
|
14
|
+
import PostDetailPage from "@/pages/post/detail";
|
|
15
|
+
import UserPage from "@/pages/user";
|
|
16
|
+
import LogPage from "@/pages/system/log";
|
|
17
|
+
|
|
18
|
+
const authLoader = () => {
|
|
19
|
+
const accessToken = Cookies.get("AccessToken");
|
|
20
|
+
if (!accessToken) {
|
|
21
|
+
return redirect("/login");
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default function Router() {
|
|
27
|
+
const router = useMemo(
|
|
28
|
+
() =>
|
|
29
|
+
createBrowserRouter([
|
|
30
|
+
{
|
|
31
|
+
path: "/login",
|
|
32
|
+
element: <LoginPage />,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
path: "/",
|
|
36
|
+
element: <Layout />,
|
|
37
|
+
errorElement: <ErrorPage />,
|
|
38
|
+
loader: authLoader,
|
|
39
|
+
children: [
|
|
40
|
+
{ index: true, element: <DashboardPage /> },
|
|
41
|
+
{ path: "sample", element: <SamplePage /> },
|
|
42
|
+
{ path: "sample/modal", element: <SampleModalPage /> },
|
|
43
|
+
{ path: "sample/:id", element: <SampleDetailPage /> },
|
|
44
|
+
{ path: "post", element: <PostPage /> },
|
|
45
|
+
{ path: "post/:id", element: <PostDetailPage /> },
|
|
46
|
+
{ path: "user", element: <UserPage /> },
|
|
47
|
+
{ path: "system/log", element: <LogPage /> },
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
{ path: "*", element: <NotFoundPage /> },
|
|
51
|
+
]),
|
|
52
|
+
[],
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
return <RouterProvider router={router} />;
|
|
56
|
+
}
|
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
import { create } from "zustand";
|
|
2
|
-
import { persist } from "zustand/middleware";
|
|
3
|
-
|
|
4
|
-
export interface User {
|
|
5
|
-
id: string;
|
|
6
|
-
userId: string;
|
|
7
|
-
name: string;
|
|
8
|
-
role: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
interface UserStore {
|
|
12
|
-
user: User | null;
|
|
13
|
-
setUser: (user: User) => void;
|
|
14
|
-
clearUser: () => void;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export const useUserStore = create<UserStore>()(
|
|
18
|
-
persist(
|
|
19
|
-
(set) => ({
|
|
20
|
-
user: null,
|
|
21
|
-
setUser: (user) => set({ user }),
|
|
22
|
-
clearUser: () => set({ user: null }),
|
|
23
|
-
}),
|
|
24
|
-
{ name: "user-store" },
|
|
25
|
-
),
|
|
26
|
-
);
|
|
1
|
+
import { create } from "zustand";
|
|
2
|
+
import { persist } from "zustand/middleware";
|
|
3
|
+
|
|
4
|
+
export interface User {
|
|
5
|
+
id: string;
|
|
6
|
+
userId: string;
|
|
7
|
+
name: string;
|
|
8
|
+
role: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface UserStore {
|
|
12
|
+
user: User | null;
|
|
13
|
+
setUser: (user: User) => void;
|
|
14
|
+
clearUser: () => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const useUserStore = create<UserStore>()(
|
|
18
|
+
persist(
|
|
19
|
+
(set) => ({
|
|
20
|
+
user: null,
|
|
21
|
+
setUser: (user) => set({ user }),
|
|
22
|
+
clearUser: () => set({ user: null }),
|
|
23
|
+
}),
|
|
24
|
+
{ name: "user-store" },
|
|
25
|
+
),
|
|
26
|
+
);
|
package/template/src/index.tsx
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import { StrictMode } from "react";
|
|
2
|
-
import { createRoot } from "react-dom/client";
|
|
3
|
-
|
|
4
|
-
import App from "@/app/App";
|
|
5
|
-
|
|
6
|
-
import "../index.css";
|
|
7
|
-
|
|
8
|
-
const prepare = async () => {
|
|
9
|
-
if (import.meta.env.DEV) {
|
|
10
|
-
const { worker } = await import("./mocks/browser");
|
|
11
|
-
await worker.start({ onUnhandledRequest: "bypass" });
|
|
12
|
-
}
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
prepare().then(() => {
|
|
16
|
-
createRoot(document.getElementById("root")!).render(
|
|
17
|
-
<StrictMode>
|
|
18
|
-
<App />
|
|
19
|
-
</StrictMode>,
|
|
20
|
-
);
|
|
21
|
-
});
|
|
1
|
+
import { StrictMode } from "react";
|
|
2
|
+
import { createRoot } from "react-dom/client";
|
|
3
|
+
|
|
4
|
+
import App from "@/app/App";
|
|
5
|
+
|
|
6
|
+
import "../index.css";
|
|
7
|
+
|
|
8
|
+
const prepare = async () => {
|
|
9
|
+
if (import.meta.env.DEV) {
|
|
10
|
+
const { worker } = await import("./mocks/browser");
|
|
11
|
+
await worker.start({ onUnhandledRequest: "bypass" });
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
prepare().then(() => {
|
|
16
|
+
createRoot(document.getElementById("root")!).render(
|
|
17
|
+
<StrictMode>
|
|
18
|
+
<App />
|
|
19
|
+
</StrictMode>,
|
|
20
|
+
);
|
|
21
|
+
});
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { setupWorker } from "msw/browser";
|
|
2
|
-
|
|
3
|
-
import { handlers } from "./handlers";
|
|
4
|
-
import { setErrorScenario, type MswScenario, getScenarioGuide } from "./scenarios";
|
|
5
|
-
|
|
6
|
-
export const worker = setupWorker(...handlers);
|
|
7
|
-
|
|
8
|
-
// 콘솔에서 시나리오 가이드 출력: window.__getMswGuide()
|
|
9
|
-
declare global {
|
|
10
|
-
interface Window {
|
|
11
|
-
__setMswErrorScenario: (scenario: MswScenario, targetPath?: string) => void;
|
|
12
|
-
__getMswGuide: (scenario: MswScenario) => void;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
window.__setMswErrorScenario = setErrorScenario;
|
|
17
|
-
window.__getMswGuide = getScenarioGuide;
|
|
1
|
+
import { setupWorker } from "msw/browser";
|
|
2
|
+
|
|
3
|
+
import { handlers } from "./handlers";
|
|
4
|
+
import { setErrorScenario, type MswScenario, getScenarioGuide } from "./scenarios";
|
|
5
|
+
|
|
6
|
+
export const worker = setupWorker(...handlers);
|
|
7
|
+
|
|
8
|
+
// 콘솔에서 시나리오 가이드 출력: window.__getMswGuide()
|
|
9
|
+
declare global {
|
|
10
|
+
interface Window {
|
|
11
|
+
__setMswErrorScenario: (scenario: MswScenario, targetPath?: string) => void;
|
|
12
|
+
__getMswGuide: (scenario: MswScenario) => void;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
window.__setMswErrorScenario = setErrorScenario;
|
|
17
|
+
window.__getMswGuide = getScenarioGuide;
|
|
@@ -1,43 +1,43 @@
|
|
|
1
|
-
import { HttpResponse, delay, http, passthrough } from "msw";
|
|
2
|
-
|
|
3
|
-
import { getScenario, getTargetPath } from "./scenarios";
|
|
4
|
-
|
|
5
|
-
const BASE_URL = `${import.meta.env.VITE_APP_API_HOST}/api/${import.meta.env.VITE_APP_API_VERSION}`;
|
|
6
|
-
|
|
7
|
-
const ERROR_RESPONSES: Record<string, () => Response> = {
|
|
8
|
-
"500": () => HttpResponse.json({ message: "Internal Server Error" }, { status: 500 }),
|
|
9
|
-
"503": () => HttpResponse.json({ message: "Service Unavailable" }, { status: 503 }),
|
|
10
|
-
"408": () => HttpResponse.json({ message: "Request Timeout" }, { status: 408 }),
|
|
11
|
-
"401": () => HttpResponse.json({ message: "Unauthorized" }, { status: 401 }),
|
|
12
|
-
"403": () => HttpResponse.json({ message: "Forbidden" }, { status: 403 }),
|
|
13
|
-
"404": () => HttpResponse.json({ message: "Not Found" }, { status: 404 }),
|
|
14
|
-
"400": () => HttpResponse.json({ message: "Bad Request" }, { status: 400 }),
|
|
15
|
-
network: () => HttpResponse.error(),
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const timeoutInterceptor = async () => {
|
|
19
|
-
await delay(3_000); // 3초 지연 후 408 반환 (타임아웃 시뮬레이션)
|
|
20
|
-
return HttpResponse.json({ message: "Request Timeout" }, { status: 408 });
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
const errorInterceptor = async ({ request }: { request: Request }) => {
|
|
24
|
-
const scenario = getScenario();
|
|
25
|
-
|
|
26
|
-
if (scenario === "none") return passthrough();
|
|
27
|
-
|
|
28
|
-
const targetPath = getTargetPath();
|
|
29
|
-
if (targetPath && !request.url.includes(targetPath)) return passthrough();
|
|
30
|
-
|
|
31
|
-
if (scenario === "timeout") return timeoutInterceptor();
|
|
32
|
-
|
|
33
|
-
const handler = ERROR_RESPONSES[scenario];
|
|
34
|
-
return handler ? handler() : passthrough();
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
export const handlers = [
|
|
38
|
-
http.get(`${BASE_URL}/*`, errorInterceptor),
|
|
39
|
-
http.post(`${BASE_URL}/*`, errorInterceptor),
|
|
40
|
-
http.put(`${BASE_URL}/*`, errorInterceptor),
|
|
41
|
-
http.patch(`${BASE_URL}/*`, errorInterceptor),
|
|
42
|
-
http.delete(`${BASE_URL}/*`, errorInterceptor),
|
|
43
|
-
];
|
|
1
|
+
import { HttpResponse, delay, http, passthrough } from "msw";
|
|
2
|
+
|
|
3
|
+
import { getScenario, getTargetPath } from "./scenarios";
|
|
4
|
+
|
|
5
|
+
const BASE_URL = `${import.meta.env.VITE_APP_API_HOST}/api/${import.meta.env.VITE_APP_API_VERSION}`;
|
|
6
|
+
|
|
7
|
+
const ERROR_RESPONSES: Record<string, () => Response> = {
|
|
8
|
+
"500": () => HttpResponse.json({ message: "Internal Server Error" }, { status: 500 }),
|
|
9
|
+
"503": () => HttpResponse.json({ message: "Service Unavailable" }, { status: 503 }),
|
|
10
|
+
"408": () => HttpResponse.json({ message: "Request Timeout" }, { status: 408 }),
|
|
11
|
+
"401": () => HttpResponse.json({ message: "Unauthorized" }, { status: 401 }),
|
|
12
|
+
"403": () => HttpResponse.json({ message: "Forbidden" }, { status: 403 }),
|
|
13
|
+
"404": () => HttpResponse.json({ message: "Not Found" }, { status: 404 }),
|
|
14
|
+
"400": () => HttpResponse.json({ message: "Bad Request" }, { status: 400 }),
|
|
15
|
+
network: () => HttpResponse.error(),
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const timeoutInterceptor = async () => {
|
|
19
|
+
await delay(3_000); // 3초 지연 후 408 반환 (타임아웃 시뮬레이션)
|
|
20
|
+
return HttpResponse.json({ message: "Request Timeout" }, { status: 408 });
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const errorInterceptor = async ({ request }: { request: Request }) => {
|
|
24
|
+
const scenario = getScenario();
|
|
25
|
+
|
|
26
|
+
if (scenario === "none") return passthrough();
|
|
27
|
+
|
|
28
|
+
const targetPath = getTargetPath();
|
|
29
|
+
if (targetPath && !request.url.includes(targetPath)) return passthrough();
|
|
30
|
+
|
|
31
|
+
if (scenario === "timeout") return timeoutInterceptor();
|
|
32
|
+
|
|
33
|
+
const handler = ERROR_RESPONSES[scenario];
|
|
34
|
+
return handler ? handler() : passthrough();
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const handlers = [
|
|
38
|
+
http.get(`${BASE_URL}/*`, errorInterceptor),
|
|
39
|
+
http.post(`${BASE_URL}/*`, errorInterceptor),
|
|
40
|
+
http.put(`${BASE_URL}/*`, errorInterceptor),
|
|
41
|
+
http.patch(`${BASE_URL}/*`, errorInterceptor),
|
|
42
|
+
http.delete(`${BASE_URL}/*`, errorInterceptor),
|
|
43
|
+
];
|
|
@@ -1,57 +1,57 @@
|
|
|
1
|
-
// 브라우저 콘솔에서 에러 시나리오 변경:
|
|
2
|
-
// window.__setMswErrorScenario('500') → 모든 API 서버 내부 오류
|
|
3
|
-
// window.__setMswErrorScenario('500', 'users') → /users 포함 URL만 500, 나머지 정상
|
|
4
|
-
// window.__setMswErrorScenario('503') → 서비스 일시 불능 (1회 재시도)
|
|
5
|
-
// window.__setMswErrorScenario('408') → 타임아웃 (1회 재시도)
|
|
6
|
-
// window.__setMswErrorScenario('401') → 인증 오류
|
|
7
|
-
// window.__setMswErrorScenario('403') → 권한 오류
|
|
8
|
-
// window.__setMswErrorScenario('404') → 존재하지 않는 리소스
|
|
9
|
-
// window.__setMswErrorScenario('400') → 잘못된 요청
|
|
10
|
-
// window.__setMswErrorScenario('timeout') → 타임아웃 (3초 지연 후 408 반환 → 1회 재시도)
|
|
11
|
-
// window.__setMswErrorScenario('network') → 네트워크 장애 (1회 재시도)
|
|
12
|
-
// window.__setMswErrorScenario('none') → 모킹 해제
|
|
13
|
-
|
|
14
|
-
export type MswScenario =
|
|
15
|
-
| "500"
|
|
16
|
-
| "503"
|
|
17
|
-
| "408"
|
|
18
|
-
| "401"
|
|
19
|
-
| "403"
|
|
20
|
-
| "404"
|
|
21
|
-
| "400"
|
|
22
|
-
| "network"
|
|
23
|
-
| "timeout"
|
|
24
|
-
| "none";
|
|
25
|
-
|
|
26
|
-
const SCENARIO_KEY = "msw-scenario";
|
|
27
|
-
const TARGET_KEY = "msw-target-path";
|
|
28
|
-
|
|
29
|
-
export const getScenario = (): MswScenario => (localStorage.getItem(SCENARIO_KEY) as MswScenario) ?? "none";
|
|
30
|
-
|
|
31
|
-
export const getTargetPath = (): string | null => localStorage.getItem(TARGET_KEY);
|
|
32
|
-
|
|
33
|
-
export const setErrorScenario = (scenario: MswScenario, targetPath?: string) => {
|
|
34
|
-
localStorage.setItem(SCENARIO_KEY, scenario);
|
|
35
|
-
|
|
36
|
-
if (targetPath) {
|
|
37
|
-
localStorage.setItem(TARGET_KEY, targetPath);
|
|
38
|
-
} else {
|
|
39
|
-
localStorage.removeItem(TARGET_KEY);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const flags = [targetPath ? `대상: *${targetPath}*` : null].filter(Boolean).join(", ");
|
|
43
|
-
|
|
44
|
-
console.log(`[MSW] 시나리오 변경: ${scenario}${flags ? ` (${flags})` : ""} (새로고침 후 적용)`);
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
export const getScenarioGuide = () => {
|
|
48
|
-
console.log(`
|
|
49
|
-
[브라우저 콘솔에서 에러 시나리오 변경 Guide]
|
|
50
|
-
|
|
51
|
-
(1) API 500 에러
|
|
52
|
-
window.__setMswErrorScenario('500')
|
|
53
|
-
|
|
54
|
-
(2) users API만 500 에러
|
|
55
|
-
window.__setMswErrorScenario('500', 'users')
|
|
56
|
-
`);
|
|
57
|
-
};
|
|
1
|
+
// 브라우저 콘솔에서 에러 시나리오 변경:
|
|
2
|
+
// window.__setMswErrorScenario('500') → 모든 API 서버 내부 오류
|
|
3
|
+
// window.__setMswErrorScenario('500', 'users') → /users 포함 URL만 500, 나머지 정상
|
|
4
|
+
// window.__setMswErrorScenario('503') → 서비스 일시 불능 (1회 재시도)
|
|
5
|
+
// window.__setMswErrorScenario('408') → 타임아웃 (1회 재시도)
|
|
6
|
+
// window.__setMswErrorScenario('401') → 인증 오류
|
|
7
|
+
// window.__setMswErrorScenario('403') → 권한 오류
|
|
8
|
+
// window.__setMswErrorScenario('404') → 존재하지 않는 리소스
|
|
9
|
+
// window.__setMswErrorScenario('400') → 잘못된 요청
|
|
10
|
+
// window.__setMswErrorScenario('timeout') → 타임아웃 (3초 지연 후 408 반환 → 1회 재시도)
|
|
11
|
+
// window.__setMswErrorScenario('network') → 네트워크 장애 (1회 재시도)
|
|
12
|
+
// window.__setMswErrorScenario('none') → 모킹 해제
|
|
13
|
+
|
|
14
|
+
export type MswScenario =
|
|
15
|
+
| "500"
|
|
16
|
+
| "503"
|
|
17
|
+
| "408"
|
|
18
|
+
| "401"
|
|
19
|
+
| "403"
|
|
20
|
+
| "404"
|
|
21
|
+
| "400"
|
|
22
|
+
| "network"
|
|
23
|
+
| "timeout"
|
|
24
|
+
| "none";
|
|
25
|
+
|
|
26
|
+
const SCENARIO_KEY = "msw-scenario";
|
|
27
|
+
const TARGET_KEY = "msw-target-path";
|
|
28
|
+
|
|
29
|
+
export const getScenario = (): MswScenario => (localStorage.getItem(SCENARIO_KEY) as MswScenario) ?? "none";
|
|
30
|
+
|
|
31
|
+
export const getTargetPath = (): string | null => localStorage.getItem(TARGET_KEY);
|
|
32
|
+
|
|
33
|
+
export const setErrorScenario = (scenario: MswScenario, targetPath?: string) => {
|
|
34
|
+
localStorage.setItem(SCENARIO_KEY, scenario);
|
|
35
|
+
|
|
36
|
+
if (targetPath) {
|
|
37
|
+
localStorage.setItem(TARGET_KEY, targetPath);
|
|
38
|
+
} else {
|
|
39
|
+
localStorage.removeItem(TARGET_KEY);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const flags = [targetPath ? `대상: *${targetPath}*` : null].filter(Boolean).join(", ");
|
|
43
|
+
|
|
44
|
+
console.log(`[MSW] 시나리오 변경: ${scenario}${flags ? ` (${flags})` : ""} (새로고침 후 적용)`);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const getScenarioGuide = () => {
|
|
48
|
+
console.log(`
|
|
49
|
+
[브라우저 콘솔에서 에러 시나리오 변경 Guide]
|
|
50
|
+
|
|
51
|
+
(1) API 500 에러
|
|
52
|
+
window.__setMswErrorScenario('500')
|
|
53
|
+
|
|
54
|
+
(2) users API만 500 에러
|
|
55
|
+
window.__setMswErrorScenario('500', 'users')
|
|
56
|
+
`);
|
|
57
|
+
};
|