@farmzone/fz-template-react 1.0.5 → 1.0.7

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.
Files changed (64) hide show
  1. package/README.md +102 -102
  2. package/bin/create.js +108 -108
  3. package/package.json +24 -24
  4. package/template/.env.example +5 -5
  5. package/template/.prettierrc +9 -9
  6. package/template/eslint.config.js +26 -26
  7. package/template/index.css +32 -32
  8. package/template/index.html +19 -19
  9. package/template/package.json +54 -55
  10. package/template/pnpm-lock.yaml +4214 -4214
  11. package/template/public/mockServiceWorker.js +349 -349
  12. package/template/src/app/App.tsx +26 -26
  13. package/template/src/app/api/api.ts +178 -178
  14. package/template/src/app/api/queries.ts +335 -326
  15. package/template/src/app/api/queryKey.ts +7 -7
  16. package/template/src/app/api/token.ts +8 -7
  17. package/template/src/app/layout/Layout.tsx +33 -33
  18. package/template/src/app/layout/ListContents.tsx +9 -9
  19. package/template/src/app/layout/ListHeader.tsx +41 -41
  20. package/template/src/app/layout/MultiTabNav.tsx +106 -101
  21. package/template/src/app/layout/Sidebar.tsx +33 -33
  22. package/template/src/app/layout/UserInfo.tsx +95 -94
  23. package/template/src/app/layout/menu.ts +79 -55
  24. package/template/src/app/layout/tabSwitchStore.ts +11 -11
  25. package/template/src/app/router/Router.tsx +56 -56
  26. package/template/src/app/store/index.ts +26 -26
  27. package/template/src/index.tsx +21 -21
  28. package/template/src/mocks/browser.ts +17 -17
  29. package/template/src/mocks/handlers.ts +43 -43
  30. package/template/src/mocks/scenarios.ts +57 -57
  31. package/template/src/pages/dashboard/index.tsx +541 -541
  32. package/template/src/pages/error/Error.tsx +29 -29
  33. package/template/src/pages/error/NotFound.tsx +27 -27
  34. package/template/src/pages/login/index.tsx +317 -317
  35. package/template/src/pages/post/PostFormModal.tsx +128 -128
  36. package/template/src/pages/post/detail/index.tsx +545 -548
  37. package/template/src/pages/post/index.tsx +266 -266
  38. package/template/src/pages/sample/SampleFormModal.tsx +188 -115
  39. package/template/src/pages/sample/detail/index.tsx +551 -400
  40. package/template/src/pages/sample/index.tsx +298 -278
  41. package/template/src/pages/sample/modal/index.tsx +308 -300
  42. package/template/src/pages/system/log/index.tsx +173 -173
  43. package/template/src/pages/user/config/columns.tsx +102 -102
  44. package/template/src/pages/user/config/schema.ts +54 -54
  45. package/template/src/pages/user/index.tsx +704 -641
  46. package/template/src/shared/components/CommentInput.tsx +243 -243
  47. package/template/src/shared/components/FilePreviewCard.tsx +71 -70
  48. package/template/src/shared/config/text.ts +27 -27
  49. package/template/src/shared/config/type.ts +40 -40
  50. package/template/src/shared/utils/format.ts +11 -11
  51. package/template/src/types/auth.ts +10 -10
  52. package/template/src/types/comment.ts +33 -33
  53. package/template/src/types/common.ts +19 -19
  54. package/template/src/types/dashboard.ts +53 -53
  55. package/template/src/types/index.ts +16 -16
  56. package/template/src/types/log.ts +21 -21
  57. package/template/src/types/post.ts +32 -32
  58. package/template/src/types/sample.ts +33 -29
  59. package/template/src/types/user.ts +51 -51
  60. package/template/src/vite-env.d.ts +10 -10
  61. package/template/tsconfig.app.json +32 -32
  62. package/template/tsconfig.json +7 -7
  63. package/template/tsconfig.node.json +26 -26
  64. package/template/vite.config.ts +13 -13
@@ -1,94 +1,95 @@
1
- import { Edit, EllipsisVertical, LogOut } from "lucide-react";
2
- import { useState } from "react";
3
- import { useNavigate } from "react-router";
4
- import { confirmModal, Popover, PopoverContent, PopoverTrigger } from "@farmzone/fz-react-ui";
5
-
6
- import { clearUserToken } from "@/app/api/token";
7
- import { useUserStore } from "@/app/store";
8
-
9
- export default function UserInfo() {
10
- const navigate = useNavigate();
11
- const { user, clearUser } = useUserStore();
12
- const [open, setOpen] = useState(false);
13
-
14
- const roleLabel = user?.role === "ADMIN" ? "관리자" : user?.role ? "사용자" : "";
15
-
16
- const handleLogout = () => {
17
- setOpen(false);
18
- confirmModal({
19
- content: "로그아웃 하시겠습니까?",
20
- onOk: async () => {
21
- clearUserToken();
22
- clearUser();
23
- navigate("/login");
24
- },
25
- onCancel: async () => {},
26
- okText: "예",
27
- cancelText: "아니오",
28
- className: "max-w-100",
29
- });
30
- };
31
-
32
- return (
33
- <div className="group flex items-center gap-1 py-3 pl-3 pr-2 transition-all duration-200 border border-[var(--color-basic-gray)]/50 rounded-sm">
34
- <div className="flex flex-row items-center min-w-0 flex-1">
35
- <span className="w-[60px] text-xs text-gray-400 truncate mt-0.5">
36
- {roleLabel && (
37
- <span className="shrink-0 text-[10px] font-medium px-1.5 py-0.5 mr-1 rounded-full bg-[var(--color-main)]/10 text-[var(--color-main)] leading-none">
38
- {roleLabel}
39
- </span>
40
- )}
41
- </span>
42
- <p className="w-full text-sm font-normal text-gray-900 leading-4 tracking-tight truncate">
43
- {user?.name || ""}
44
- </p>
45
- </div>
46
-
47
- <Popover open={open} onOpenChange={setOpen}>
48
- <PopoverTrigger>
49
- <EllipsisVertical className="shrink-0 w-4 h-4 text-gray-300 group-hover:text-gray-500 transition-colors duration-200 cursor-pointer" />
50
- </PopoverTrigger>
51
- <PopoverContent position="right" sideOffset={15} align="start" className="w-56 p-0">
52
- <div className="flex flex-col">
53
- {/* 사용자 ID */}
54
- <div className="flex items-center px-4 py-3 border-b border-gray-100">
55
- <span className="text-sm text-gray-500 font-normal">{user?.userId || ""}</span>
56
- </div>
57
-
58
- {/* 버튼 영역 */}
59
- <div className="flex flex-col gap-2 p-3">
60
- <div className="flex gap-2">
61
- <button
62
- type="button"
63
- onClick={handleLogout}
64
- className="w-full flex items-center justify-center gap-1 py-2 px-4 text-xs font-medium text-gray-700 hover:text-red-900 hover:bg-red-50 transition-all duration-150 bg-gray-50 border border-gray-200 rounded-sm cursor-pointer"
65
- >
66
- <LogOut size={12} />
67
- <span>로그아웃</span>
68
- </button>
69
- <button
70
- type="button"
71
- onClick={() => {
72
- setOpen(false);
73
- navigate("/user");
74
- }}
75
- className="w-full flex items-center justify-center gap-1 py-2 px-4 text-xs font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-100 transition-all duration-150 bg-gray-50 border border-gray-200 rounded-sm cursor-pointer"
76
- >
77
- <Edit size={12} />
78
- <span>정보수정</span>
79
- </button>
80
- </div>
81
- <button
82
- type="button"
83
- onClick={() => setOpen(false)}
84
- className="w-full flex items-center justify-center gap-1 py-2 px-4 text-xs font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-100 transition-all duration-150 bg-gray-50 border border-gray-200 rounded-sm cursor-pointer"
85
- >
86
- <span>닫기</span>
87
- </button>
88
- </div>
89
- </div>
90
- </PopoverContent>
91
- </Popover>
92
- </div>
93
- );
94
- }
1
+ import { Edit, EllipsisVertical, LogOut } from "lucide-react";
2
+ import { useState } from "react";
3
+ import { useNavigate } from "react-router";
4
+ import { confirmModal, Popover, PopoverContent, PopoverTrigger, useMultiTabStore } from "@farmzone/fz-react-ui";
5
+
6
+ import { clearUserToken } from "@/app/api/token";
7
+ import { useUserStore } from "@/app/store";
8
+
9
+ export default function UserInfo() {
10
+ const navigate = useNavigate();
11
+ const { user, clearUser } = useUserStore();
12
+ const [open, setOpen] = useState(false);
13
+
14
+ const roleLabel = user?.role === "ADMIN" ? "관리자" : user?.role ? "사용자" : "";
15
+
16
+ const handleLogout = () => {
17
+ setOpen(false);
18
+ confirmModal({
19
+ content: "로그아웃 하시겠습니까?",
20
+ onOk: async () => {
21
+ clearUserToken();
22
+ clearUser();
23
+ useMultiTabStore.setState({ tabs: [], activeTabId: null });
24
+ navigate("/login");
25
+ },
26
+ onCancel: async () => {},
27
+ okText: "",
28
+ cancelText: "아니오",
29
+ className: "max-w-100",
30
+ });
31
+ };
32
+
33
+ return (
34
+ <div className="group flex items-center gap-1 py-3 pl-3 pr-2 transition-all duration-200 border border-[var(--color-basic-gray)]/50 rounded-sm">
35
+ <div className="flex flex-row items-center min-w-0 flex-1">
36
+ <span className="w-[60px] text-xs text-gray-400 truncate mt-0.5">
37
+ {roleLabel && (
38
+ <span className="shrink-0 text-[10px] font-medium px-1.5 py-0.5 mr-1 rounded-full bg-[var(--color-main)]/10 text-[var(--color-main)] leading-none">
39
+ {roleLabel}
40
+ </span>
41
+ )}
42
+ </span>
43
+ <p className="w-full text-sm font-normal text-gray-900 leading-4 tracking-tight truncate">
44
+ {user?.name || ""}
45
+ </p>
46
+ </div>
47
+
48
+ <Popover open={open} onOpenChange={setOpen}>
49
+ <PopoverTrigger>
50
+ <EllipsisVertical className="shrink-0 w-4 h-4 text-gray-300 group-hover:text-gray-500 transition-colors duration-200 cursor-pointer" />
51
+ </PopoverTrigger>
52
+ <PopoverContent position="right" sideOffset={15} align="start" className="w-56 p-0">
53
+ <div className="flex flex-col">
54
+ {/* 사용자 ID */}
55
+ <div className="flex items-center px-4 py-3 border-b border-gray-100">
56
+ <span className="text-sm text-gray-500 font-normal">{user?.userId || ""}</span>
57
+ </div>
58
+
59
+ {/* 버튼 영역 */}
60
+ <div className="flex flex-col gap-2 p-3">
61
+ <div className="flex gap-2">
62
+ <button
63
+ type="button"
64
+ onClick={handleLogout}
65
+ className="w-full flex items-center justify-center gap-1 py-2 px-4 text-xs font-medium text-gray-700 hover:text-red-900 hover:bg-red-50 transition-all duration-150 bg-gray-50 border border-gray-200 rounded-sm cursor-pointer"
66
+ >
67
+ <LogOut size={12} />
68
+ <span>로그아웃</span>
69
+ </button>
70
+ <button
71
+ type="button"
72
+ onClick={() => {
73
+ setOpen(false);
74
+ navigate("/user");
75
+ }}
76
+ className="w-full flex items-center justify-center gap-1 py-2 px-4 text-xs font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-100 transition-all duration-150 bg-gray-50 border border-gray-200 rounded-sm cursor-pointer"
77
+ >
78
+ <Edit size={12} />
79
+ <span>정보수정</span>
80
+ </button>
81
+ </div>
82
+ <button
83
+ type="button"
84
+ onClick={() => setOpen(false)}
85
+ className="w-full flex items-center justify-center gap-1 py-2 px-4 text-xs font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-100 transition-all duration-150 bg-gray-50 border border-gray-200 rounded-sm cursor-pointer"
86
+ >
87
+ <span>닫기</span>
88
+ </button>
89
+ </div>
90
+ </div>
91
+ </PopoverContent>
92
+ </Popover>
93
+ </div>
94
+ );
95
+ }
@@ -1,55 +1,79 @@
1
- import type { MenuSection } from "@farmzone/fz-react-ui";
2
- import { FileText, FlaskConical, LayoutDashboard, ScrollText, Users } from "lucide-react";
3
-
4
- export function findTabInfoForPath(pathname: string): { basePath: string; label: string } | null {
5
- for (const section of MENU_SECTIONS) {
6
- for (const item of section.items) {
7
- if (item.children?.length) {
8
- for (const child of item.children) {
9
- if (child.children?.length) {
10
- for (const grandchild of child.children) {
11
- if (pathname === grandchild.path || pathname.startsWith(`${grandchild.path}/`)) {
12
- return { basePath: grandchild.path, label: grandchild.label };
13
- }
14
- }
15
- } else if (child.path && (pathname === child.path || pathname.startsWith(`${child.path}/`))) {
16
- return { basePath: child.path, label: child.label };
17
- }
18
- }
19
- } else if (item.path && (pathname === item.path || pathname.startsWith(`${item.path}/`))) {
20
- return { basePath: item.path, label: item.label };
21
- }
22
- }
23
- }
24
- return null;
25
- }
26
-
27
- export const MENU_SECTIONS: Array<MenuSection> = [
28
- {
29
- items: [
30
- { icon: LayoutDashboard, label: "대시보드", path: "/" },
31
- {
32
- icon: FlaskConical,
33
- label: "샘플 관리",
34
- children: [
35
- { label: "샘플 관리", path: "/sample" },
36
- { label: "샘플 모달", path: "/sample/modal" },
37
- ],
38
- },
39
- {
40
- icon: FileText,
41
- label: "게시글 관리",
42
- children: [{ label: "게시글 관리", path: "/post" }],
43
- },
44
- {
45
- icon: Users,
46
- label: "사용자 관리",
47
- children: [{ label: "사용자 관리", path: "/user" }],
48
- },
49
- ],
50
- },
51
- {
52
- title: "시스템",
53
- items: [{ icon: ScrollText, label: "로그 관리", path: "/system/log" }],
54
- },
55
- ];
1
+ import type { MenuSection } from "@farmzone/fz-react-ui";
2
+ import { FileText, FlaskConical, LayoutDashboard, ScrollText, Users } from "lucide-react";
3
+
4
+ const DETAIL_ROUTES: Array<{ pattern: RegExp; label: string }> = [
5
+ { pattern: /^\/sample\/\d+$/, label: "샘플 상세" },
6
+ { pattern: /^\/post\/\d+$/, label: "게시글 상세" },
7
+ ];
8
+
9
+ export function getDetailTabInfo(pathname: string): { basePath: string; label: string } | null {
10
+ for (const { pattern, label } of DETAIL_ROUTES) {
11
+ if (pattern.test(pathname)) {
12
+ return { basePath: pathname, label };
13
+ }
14
+ }
15
+ return null;
16
+ }
17
+
18
+ export function findTabInfoForPath(pathname: string): { basePath: string; label: string } | null {
19
+ const detailInfo = getDetailTabInfo(pathname);
20
+ if (detailInfo) return detailInfo;
21
+
22
+ for (const section of MENU_SECTIONS) {
23
+ for (const item of section.items) {
24
+ if (item.children?.length) {
25
+ // 구체적인 경로(긴 경로)를 먼저 검사해서 /sample이 /sample/modal을 흡수하지 않도록
26
+ const sortedChildren = [...item.children].sort(
27
+ (a, b) => (b.path?.length ?? 0) - (a.path?.length ?? 0),
28
+ );
29
+ for (const child of sortedChildren) {
30
+ if (child.children?.length) {
31
+ const sortedGrandchildren = [...child.children].sort(
32
+ (a, b) => (b.path?.length ?? 0) - (a.path?.length ?? 0),
33
+ );
34
+ for (const grandchild of sortedGrandchildren) {
35
+ if (pathname === grandchild.path || pathname.startsWith(`${grandchild.path}/`)) {
36
+ return { basePath: grandchild.path, label: grandchild.label };
37
+ }
38
+ }
39
+ } else if (child.path && (pathname === child.path || pathname.startsWith(`${child.path}/`))) {
40
+ return { basePath: child.path, label: child.label };
41
+ }
42
+ }
43
+ } else if (item.path && (pathname === item.path || pathname.startsWith(`${item.path}/`))) {
44
+ return { basePath: item.path, label: item.label };
45
+ }
46
+ }
47
+ }
48
+ return null;
49
+ }
50
+
51
+ export const MENU_SECTIONS: Array<MenuSection> = [
52
+ {
53
+ items: [
54
+ { icon: LayoutDashboard, label: "대시보드", path: "/" },
55
+ {
56
+ icon: FlaskConical,
57
+ label: "샘플 관리",
58
+ children: [
59
+ { label: "샘플 관리", path: "/sample" },
60
+ { label: "샘플 모달", path: "/sample/modal" },
61
+ ],
62
+ },
63
+ {
64
+ icon: FileText,
65
+ label: "게시글 관리",
66
+ children: [{ label: "게시글 관리", path: "/post" }],
67
+ },
68
+ {
69
+ icon: Users,
70
+ label: "사용자 관리",
71
+ children: [{ label: "사용자 관리", path: "/user" }],
72
+ },
73
+ ],
74
+ },
75
+ {
76
+ title: "시스템",
77
+ items: [{ icon: ScrollText, label: "로그 관리", path: "/system/log" }],
78
+ },
79
+ ];
@@ -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,56 +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 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
+ 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
+ );
@@ -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;