@farmzone/fz-template-react 1.0.6 → 1.0.8
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/README.md +102 -102
- package/bin/create.js +108 -108
- package/package.json +24 -24
- package/template/.env.example +5 -5
- package/template/.prettierrc +9 -9
- package/template/eslint.config.js +26 -26
- package/template/index.css +32 -32
- package/template/index.html +19 -19
- package/template/package.json +54 -54
- package/template/pnpm-lock.yaml +4214 -4214
- package/template/public/mockServiceWorker.js +349 -349
- package/template/src/app/App.tsx +26 -26
- package/template/src/app/api/api.ts +178 -178
- package/template/src/app/api/queries.ts +335 -335
- package/template/src/app/api/queryKey.ts +7 -7
- package/template/src/app/api/token.ts +8 -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 +106 -101
- package/template/src/app/layout/Sidebar.tsx +33 -33
- package/template/src/app/layout/UserInfo.tsx +95 -94
- package/template/src/app/layout/menu.ts +79 -55
- package/template/src/app/layout/tabSwitchStore.ts +11 -11
- package/template/src/app/router/Router.tsx +56 -56
- 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 +545 -545
- package/template/src/pages/post/index.tsx +266 -266
- package/template/src/pages/sample/SampleFormModal.tsx +188 -188
- package/template/src/pages/sample/detail/index.tsx +551 -517
- package/template/src/pages/sample/index.tsx +298 -298
- package/template/src/pages/sample/modal/index.tsx +308 -308
- package/template/src/pages/system/log/index.tsx +173 -173
- package/template/src/pages/user/config/columns.tsx +102 -102
- package/template/src/pages/user/config/schema.ts +54 -54
- package/template/src/pages/user/index.tsx +704 -650
- package/template/src/shared/components/CommentInput.tsx +243 -243
- package/template/src/shared/components/FilePreviewCard.tsx +71 -71
- package/template/src/shared/config/text.ts +27 -27
- package/template/src/shared/config/type.ts +40 -40
- 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 +33 -33
- package/template/src/types/user.ts +51 -51
- package/template/src/vite-env.d.ts +10 -10
- package/template/tsconfig.app.json +32 -32
- package/template/tsconfig.json +7 -7
- package/template/tsconfig.node.json +26 -26
- 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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
<
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
<
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
items: [
|
|
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
|
+
);
|
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;
|