create-fullstack-scaffold 0.1.1 → 0.2.0
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/dist/cli/index.js +1673 -696
- package/dist/cli/index.js.map +1 -1
- package/package.json +16 -10
- package/template/.husky/pre-commit +1 -1
- package/template/modules.config.ts +29 -2
- package/template/package.json +12 -12
- package/template/patches/{typescript+5.8.3.patch → typescript+5.9.3.patch} +2 -4
- package/template/playwright.config.ts +7 -1
- package/template/src/admin/App.tsx +2 -2
- package/template/src/admin/components/CaptchaModal.tsx +7 -9
- package/template/src/admin/layouts/Header.tsx +56 -22
- package/template/src/admin/layouts/Layout.tsx +14 -9
- package/template/src/admin/layouts/Sidebar.tsx +122 -105
- package/template/src/admin/pages/CategoryManagementPage.tsx +241 -0
- package/template/src/admin/pages/ContentPage.tsx +2 -0
- package/template/src/admin/pages/DashboardPage.tsx +2 -0
- package/template/src/admin/pages/DisputesPage.tsx +2 -0
- package/template/src/admin/pages/MediaTestPage.tsx +1 -1
- package/template/src/admin/pages/OrdersPage.tsx +2 -0
- package/template/src/admin/pages/PluginDashboardPage.tsx +297 -0
- package/template/src/admin/pages/PluginManagementPage.tsx +340 -0
- package/template/src/admin/pages/PluginReviewPage.tsx +255 -0
- package/template/src/admin/pages/SystemLogsPage.tsx +11 -2
- package/template/src/admin/pages/TicketsPage.tsx +2 -0
- package/template/src/admin/pages/UsersPage.tsx +3 -2
- package/template/src/admin/stores/adminStore.ts +5 -0
- package/template/src/cli/index.ts +17 -19
- package/template/src/cli/modules/auth/index.ts +65 -0
- package/template/src/cli/modules/config/index.ts +74 -77
- package/template/src/cli/modules/index.ts +28 -6
- package/template/src/cli/modules/notification/index.ts +95 -79
- package/template/src/cli/modules/plugin/index.ts +111 -0
- package/template/src/cli/modules/todo/index.ts +99 -51
- package/template/src/cli/utils/auto-command.ts +7 -25
- package/template/src/cli/utils/index.ts +3 -1
- package/template/src/client/App.tsx +36 -16
- package/template/src/client/Layout.tsx +67 -10
- package/template/src/client/components/AuthButton.tsx +25 -18
- package/template/src/client/components/BottomTabBar.tsx +107 -0
- package/template/src/client/components/Navigation.tsx +162 -52
- package/template/src/client/components/__tests__/App.test.tsx +48 -32
- package/template/src/client/components/__tests__/AuthButton.test.tsx +78 -77
- package/template/src/client/components/__tests__/Navigation.test.tsx +31 -20
- package/template/src/client/components/index.ts +1 -0
- package/template/src/client/contexts/PresetContext.tsx +10 -0
- package/template/src/client/main.tsx +63 -8
- package/template/src/client/pages/CartPage.tsx +244 -0
- package/template/src/client/pages/CategoriesPage.tsx +100 -0
- package/template/src/client/pages/ContentDetailPage.tsx +9 -14
- package/template/src/client/pages/ContentListPage.tsx +4 -11
- package/template/src/client/pages/DashboardPage.tsx +261 -0
- package/template/src/client/pages/DeveloperDashboardPage.tsx +211 -0
- package/template/src/client/pages/LoginPage.tsx +127 -0
- package/template/src/client/pages/OrdersPage.tsx +196 -0
- package/template/src/client/pages/PluginDetailPage.tsx +345 -0
- package/template/src/client/pages/PluginsPage.tsx +223 -0
- package/template/src/client/pages/ProfilePage.tsx +206 -0
- package/template/src/client/pages/PublishPage.tsx +336 -0
- package/template/src/client/pages/RegisterPage.tsx +136 -0
- package/template/src/client/pages/SearchPage.tsx +204 -0
- package/template/src/client/pages/SettingsPage.tsx +220 -0
- package/template/src/client/pages/TopicsPage.tsx +180 -0
- package/template/src/client/pages/__tests__/LoginPage.test.tsx +170 -0
- package/template/src/client/pages/__tests__/RegisterPage.test.tsx +168 -0
- package/template/src/client/preset-ui-config.ts +492 -0
- package/template/src/client/services/apiClient.ts +14 -4
- package/template/src/client/stores/__tests__/authStore.test.ts +293 -38
- package/template/src/client/stores/__tests__/todoStore.test.ts +7 -17
- package/template/src/client/stores/authStore.ts +58 -5
- package/template/src/client/stores/chatWSStore.ts +9 -0
- package/template/src/client/stores/notificationStore.ts +4 -0
- package/template/src/client/stores/pluginStore.ts +279 -0
- package/template/src/client/stores/todoStore.ts +2 -6
- package/template/src/server/core/__tests__/isr-cache.test.ts +117 -0
- package/template/src/server/core/__tests__/isr-invalidation.test.ts +72 -0
- package/template/src/server/core/__tests__/ssr-renderer.test.ts +89 -0
- package/template/src/server/core/isr-cache.ts +239 -0
- package/template/src/server/core/isr-invalidation.ts +45 -0
- package/template/src/server/core/module-loader.ts +14 -7
- package/template/src/server/core/ssr-renderer.ts +240 -0
- package/template/src/server/db/init.ts +257 -10
- package/template/src/server/db/schema/developers.ts +20 -0
- package/template/src/server/db/schema/index.ts +2 -0
- package/template/src/server/db/schema/plugins.ts +114 -0
- package/template/src/server/db/test-setup.ts +91 -0
- package/template/src/server/entries/cloudflare.ts +79 -7
- package/template/src/server/entries/node.ts +48 -5
- package/template/src/server/middleware/__tests__/captcha.test.ts +23 -13
- package/template/src/server/middleware/auth.ts +8 -1
- package/template/src/server/middleware/captcha.ts +14 -4
- package/template/src/server/middleware/rate-limit.ts +7 -2
- package/template/src/server/module-admin/module.ts +9 -5
- package/template/src/server/module-admin/routes/admin-notification-routes.ts +43 -14
- package/template/src/server/module-admin/routes/admin-routes.ts +0 -2
- package/template/src/server/module-admin/routes/client-auth-routes.ts +90 -0
- package/template/src/server/module-admin/routes/dashboard-routes.ts +79 -0
- package/template/src/server/module-admin/services/admin-service.ts +68 -11
- package/template/src/server/module-auth/__tests__/auth-service.test.ts +239 -0
- package/template/src/server/module-auth/index.ts +7 -0
- package/template/src/server/module-auth/module.ts +40 -0
- package/template/src/server/module-auth/routes/auth-routes.ts +94 -0
- package/template/src/server/module-auth/routes/profile-routes.ts +31 -0
- package/template/src/server/module-auth/services/auth-service.ts +100 -0
- package/template/src/server/module-content/module.ts +10 -4
- package/template/src/server/module-content/routes/public-content-routes.ts +2 -2
- package/template/src/server/module-content/routes/topics-routes.ts +205 -0
- package/template/src/server/module-content/services/content-service.ts +18 -2
- package/template/src/server/module-dispute/routes/dispute-routes.ts +2 -1
- package/template/src/server/module-dispute/services/dispute-service.ts +1 -1
- package/template/src/server/module-notifications/__tests__/sse-rpc.test.ts +14 -14
- package/template/src/server/module-notifications/routes/notification-routes.ts +34 -8
- package/template/src/server/module-order/__tests__/order-route.test.ts +22 -2
- package/template/src/server/module-order/module.ts +15 -0
- package/template/src/server/module-order/routes/cart-routes.ts +103 -0
- package/template/src/server/module-order/routes/orders-mock-routes.ts +67 -0
- package/template/src/server/module-order/services/order-service.ts +1 -1
- package/template/src/server/module-plugin/__tests__/plugin-query-service.test.ts +203 -0
- package/template/src/server/module-plugin/__tests__/plugin-service.test.ts +234 -0
- package/template/src/server/module-plugin/index.ts +2 -0
- package/template/src/server/module-plugin/module.ts +52 -0
- package/template/src/server/module-plugin/routes/plugin-admin-routes.ts +261 -0
- package/template/src/server/module-plugin/routes/plugin-routes.ts +354 -0
- package/template/src/server/module-plugin/services/admin-category-service.ts +99 -0
- package/template/src/server/module-plugin/services/admin-plugin-service.ts +170 -0
- package/template/src/server/module-plugin/services/admin-stats-service.ts +41 -0
- package/template/src/server/module-plugin/services/plugin-query-service.ts +360 -0
- package/template/src/server/module-plugin/services/plugin-review-service.ts +95 -0
- package/template/src/server/module-plugin/services/plugin-service.ts +163 -0
- package/template/src/server/module-ticket/services/ticket-service.ts +1 -1
- package/template/src/server/module-todos/routes/todos-routes.ts +0 -4
- package/template/src/server/route-registry.ts +16 -1
- package/template/src/server/test-utils/test-client.ts +1 -2
- package/template/src/server/utils/auth.ts +6 -0
- package/template/src/server/utils/json.ts +13 -0
- package/template/src/shared/core/module-manifest.ts +3 -6
- package/template/src/shared/modules/auth/index.ts +12 -0
- package/template/src/shared/modules/auth/schemas.ts +50 -0
- package/template/src/shared/modules/cart/index.ts +1 -0
- package/template/src/shared/modules/cart/schemas.ts +41 -0
- package/template/src/shared/modules/community/index.ts +1 -0
- package/template/src/shared/modules/community/schemas.ts +58 -0
- package/template/src/shared/modules/dashboard/index.ts +1 -0
- package/template/src/shared/modules/dashboard/schemas.ts +35 -0
- package/template/src/shared/modules/index.ts +41 -0
- package/template/src/shared/modules/order/schemas.ts +29 -0
- package/template/src/shared/modules/plugins/index.ts +48 -0
- package/template/src/shared/modules/plugins/schemas.ts +227 -0
- package/template/src/shared/schemas/index.ts +130 -0
- package/template/tests/e2e/todo.spec.ts +23 -18
- package/template/tests/e2e/visual-screenshots.spec.ts +1824 -0
- package/template/uploads/.gitkeep +0 -0
- package/template/vite.config.ts +2 -1
- package/template/vitest.setup.ts +11 -0
- package/template/wrangler.toml +4 -3
- package/template/package-lock.json +0 -14554
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-fullstack-scaffold",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"create-fullstack-scaffold": "dist/cli/index.js"
|
|
@@ -9,6 +9,12 @@
|
|
|
9
9
|
"dist/cli",
|
|
10
10
|
"template"
|
|
11
11
|
],
|
|
12
|
+
"workspaces": [
|
|
13
|
+
"template",
|
|
14
|
+
"packages/core",
|
|
15
|
+
"packages/infrastructure",
|
|
16
|
+
"packages/domain-plugin"
|
|
17
|
+
],
|
|
12
18
|
"publishConfig": {
|
|
13
19
|
"access": "public"
|
|
14
20
|
},
|
|
@@ -73,7 +79,12 @@
|
|
|
73
79
|
"dev:guide": "node --import tsx/esm lint-scripts/dev-guide-generator.ts",
|
|
74
80
|
"ai:context": "node --import tsx/esm lint-scripts/ai-context-generator.ts",
|
|
75
81
|
"validate:template": "npm run typecheck:template && npm run lint:template",
|
|
76
|
-
"test:template": "cd template && npx vitest run"
|
|
82
|
+
"test:template": "cd template && npx vitest run",
|
|
83
|
+
"turbo:build": "turbo build",
|
|
84
|
+
"turbo:test": "turbo test",
|
|
85
|
+
"turbo:lint": "turbo lint",
|
|
86
|
+
"turbo:typecheck": "turbo typecheck",
|
|
87
|
+
"turbo:all": "turbo lint typecheck test build"
|
|
77
88
|
},
|
|
78
89
|
"dependencies": {
|
|
79
90
|
"@hono/node-server": "^1.14.4",
|
|
@@ -86,7 +97,7 @@
|
|
|
86
97
|
"dotenv": "^16.5.0",
|
|
87
98
|
"drizzle-orm": "^0.45.1",
|
|
88
99
|
"fs-extra": "^11.3.5",
|
|
89
|
-
"hono": "
|
|
100
|
+
"hono": "4.12.16",
|
|
90
101
|
"jsonwebtoken": "^9.0.3",
|
|
91
102
|
"lucide-react": "^0.576.0",
|
|
92
103
|
"ora": "^9.4.0",
|
|
@@ -154,12 +165,6 @@
|
|
|
154
165
|
],
|
|
155
166
|
"*.{json,md,css}": [
|
|
156
167
|
"prettier --write"
|
|
157
|
-
],
|
|
158
|
-
"src/server/**/*routes*.ts": [
|
|
159
|
-
"npm run validate:all"
|
|
160
|
-
],
|
|
161
|
-
"src/client/**/*.{ts,tsx}": [
|
|
162
|
-
"npm run validate:all"
|
|
163
168
|
]
|
|
164
169
|
},
|
|
165
170
|
"engines": {
|
|
@@ -167,5 +172,6 @@
|
|
|
167
172
|
},
|
|
168
173
|
"overrides": {
|
|
169
174
|
"esbuild": "^0.25.0"
|
|
170
|
-
}
|
|
175
|
+
},
|
|
176
|
+
"packageManager": "npm@11.6.2"
|
|
171
177
|
}
|
|
@@ -11,7 +11,7 @@ export const TEMPLATE_PRESETS: TemplatePreset[] = [
|
|
|
11
11
|
id: 'fullstack-admin',
|
|
12
12
|
name: 'Full Admin (Recommended)',
|
|
13
13
|
description:
|
|
14
|
-
'Complete fullstack app with all modules: todos, chat, notifications, admin panel, RBAC, orders, tickets, disputes, content management, file upload, and CAPTCHA',
|
|
14
|
+
'Complete fullstack app with all modules: todos, chat, notifications, admin panel, RBAC, auth, plugin marketplace, orders, tickets, disputes, content management, file upload, and CAPTCHA',
|
|
15
15
|
modules: [
|
|
16
16
|
// Core (no dependencies)
|
|
17
17
|
'todos',
|
|
@@ -22,6 +22,8 @@ export const TEMPLATE_PRESETS: TemplatePreset[] = [
|
|
|
22
22
|
// System (permission first, then dependents)
|
|
23
23
|
'permission',
|
|
24
24
|
'admin',
|
|
25
|
+
'auth',
|
|
26
|
+
'plugin',
|
|
25
27
|
// Business (all depend on permission)
|
|
26
28
|
'order',
|
|
27
29
|
'ticket',
|
|
@@ -33,7 +35,7 @@ export const TEMPLATE_PRESETS: TemplatePreset[] = [
|
|
|
33
35
|
id: 'todo-app',
|
|
34
36
|
name: 'Todo App',
|
|
35
37
|
description: 'Simple todo app with chat and notifications — great starting point for learning',
|
|
36
|
-
modules: ['todos', 'chat', 'notifications'],
|
|
38
|
+
modules: ['todos', 'chat', 'notifications', 'auth'],
|
|
37
39
|
},
|
|
38
40
|
{
|
|
39
41
|
id: 'ecommerce',
|
|
@@ -52,6 +54,31 @@ export const TEMPLATE_PRESETS: TemplatePreset[] = [
|
|
|
52
54
|
'content',
|
|
53
55
|
],
|
|
54
56
|
},
|
|
57
|
+
{
|
|
58
|
+
id: 'xbrowser-marketplace',
|
|
59
|
+
name: 'Plugin Marketplace',
|
|
60
|
+
description:
|
|
61
|
+
'xbrowser plugin marketplace with developer auth, plugin CRUD, reviews, categories, admin management, and CLI support',
|
|
62
|
+
modules: [
|
|
63
|
+
'notifications',
|
|
64
|
+
'file',
|
|
65
|
+
'captcha',
|
|
66
|
+
'auth',
|
|
67
|
+
'permission',
|
|
68
|
+
'admin',
|
|
69
|
+
'plugin',
|
|
70
|
+
'order',
|
|
71
|
+
'ticket',
|
|
72
|
+
'dispute',
|
|
73
|
+
'content',
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: 'cli-only',
|
|
78
|
+
name: 'CLI Agent',
|
|
79
|
+
description: 'CLI + Server only, no web client. For AI agent automation — no browser UI needed',
|
|
80
|
+
modules: ['todos', 'chat', 'notifications', 'auth'],
|
|
81
|
+
},
|
|
55
82
|
{
|
|
56
83
|
id: 'minimal',
|
|
57
84
|
name: 'Minimal',
|
package/template/package.json
CHANGED
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"dev": "vite",
|
|
11
|
+
"dev:todo": "VITE_PRESET=todo vite",
|
|
12
|
+
"dev:plugin": "VITE_PRESET=plugin vite",
|
|
13
|
+
"dev:ecommerce": "VITE_PRESET=ecommerce vite",
|
|
14
|
+
"dev:community": "VITE_PRESET=community vite",
|
|
15
|
+
"dev:saas": "VITE_PRESET=saas vite",
|
|
11
16
|
"build": "npm run build:client && npm run build:server",
|
|
12
17
|
"build:client": "vite build",
|
|
13
18
|
"build:server": "tsup",
|
|
@@ -20,7 +25,7 @@
|
|
|
20
25
|
"start:dev": "NODE_ENV=development node --import tsx src/server/entries/node.ts",
|
|
21
26
|
"dev:cf": "wrangler dev",
|
|
22
27
|
"deploy:cf": "npm run build:cloudflare && wrangler deploy",
|
|
23
|
-
"typecheck": "tsc --noEmit
|
|
28
|
+
"typecheck": "tsc --noEmit",
|
|
24
29
|
"test": "vitest",
|
|
25
30
|
"test:smart": "node --import tsx/esm lint-scripts/smart-test.ts",
|
|
26
31
|
"test:smart:staged": "node --import tsx/esm lint-scripts/smart-test.ts --staged",
|
|
@@ -48,8 +53,6 @@
|
|
|
48
53
|
"validate:modules": "npx tsx src/server/core/module-loader.ts",
|
|
49
54
|
"check:refs": "node --import tsx/esm lint-scripts/check-refs.ts",
|
|
50
55
|
"create:module": "node --import tsx/esm scripts/create-template.ts",
|
|
51
|
-
"postinstall": "patch-package",
|
|
52
|
-
"prepare": "husky",
|
|
53
56
|
"db:generate": "drizzle-kit generate",
|
|
54
57
|
"db:migrate": "drizzle-kit migrate",
|
|
55
58
|
"db:push": "drizzle-kit push",
|
|
@@ -59,19 +62,22 @@
|
|
|
59
62
|
"framework:check": "node --import tsx/esm lint-scripts/framework/check-modify.ts",
|
|
60
63
|
"dev:context": "node --import tsx/esm lint-scripts/dev-context-analyzer.ts",
|
|
61
64
|
"dev:guide": "node --import tsx/esm lint-scripts/dev-guide-generator.ts",
|
|
62
|
-
"ai:context": "node --import tsx/esm lint-scripts/ai-context-generator.ts"
|
|
65
|
+
"ai:context": "node --import tsx/esm lint-scripts/ai-context-generator.ts",
|
|
66
|
+
"postinstall": "node -e \"process.exit(require('fs').existsSync('node_modules/hono')?0:1)\" && patch-package || true",
|
|
67
|
+
"prepare": "husky",
|
|
68
|
+
"typecheck:full": "tsc --noEmit && eslint . --max-warnings=0"
|
|
63
69
|
},
|
|
64
70
|
"dependencies": {
|
|
71
|
+
"@dyyz1993/xcli-core": "^0.7.4",
|
|
65
72
|
"@hono/node-server": "^1.14.4",
|
|
66
73
|
"@hono/zod-openapi": "1.2.2",
|
|
67
74
|
"@hono/zod-validator": "0.7.6",
|
|
68
75
|
"@libsql/client": "^0.17.0",
|
|
69
76
|
"antd": "^5.24.0",
|
|
70
77
|
"bcryptjs": "^3.0.3",
|
|
71
|
-
"commander": "^12.0.0",
|
|
72
78
|
"dotenv": "^16.5.0",
|
|
73
79
|
"drizzle-orm": "^0.45.1",
|
|
74
|
-
"hono": "
|
|
80
|
+
"hono": "4.12.16",
|
|
75
81
|
"jsonwebtoken": "^9.0.3",
|
|
76
82
|
"lucide-react": "^0.576.0",
|
|
77
83
|
"pino": "^9.7.0",
|
|
@@ -141,12 +147,6 @@
|
|
|
141
147
|
],
|
|
142
148
|
"*.{json,md,css}": [
|
|
143
149
|
"prettier --write"
|
|
144
|
-
],
|
|
145
|
-
"src/server/**/*routes*.ts": [
|
|
146
|
-
"npm run validate:all"
|
|
147
|
-
],
|
|
148
|
-
"src/client/**/*.{ts,tsx}": [
|
|
149
|
-
"npm run validate:all"
|
|
150
150
|
]
|
|
151
151
|
},
|
|
152
152
|
"engines": {
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
diff --git a/node_modules/typescript/lib/_tsc.js b/node_modules/typescript/lib/_tsc.js
|
|
2
|
-
index ae2f844..7355946 100644
|
|
3
2
|
--- a/node_modules/typescript/lib/_tsc.js
|
|
4
3
|
+++ b/node_modules/typescript/lib/_tsc.js
|
|
5
|
-
@@ -
|
|
4
|
+
@@ -63591,7 +63591,7 @@ function createTypeChecker(host) {
|
|
6
5
|
if (!couldContainTypeVariables(type)) {
|
|
7
6
|
return type;
|
|
8
7
|
}
|
|
@@ -12,10 +11,9 @@ index ae2f844..7355946 100644
|
|
|
12
11
|
error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite);
|
|
13
12
|
return errorType;
|
|
14
13
|
diff --git a/node_modules/typescript/lib/typescript.js b/node_modules/typescript/lib/typescript.js
|
|
15
|
-
index 927ce9..01fcfa 100644
|
|
16
14
|
--- a/node_modules/typescript/lib/typescript.js
|
|
17
15
|
+++ b/node_modules/typescript/lib/typescript.js
|
|
18
|
-
@@ -
|
|
16
|
+
@@ -68202,7 +68202,7 @@ function createTypeChecker(host) {
|
|
19
17
|
if (!couldContainTypeVariables(type)) {
|
|
20
18
|
return type;
|
|
21
19
|
}
|
|
@@ -14,7 +14,13 @@ export default defineConfig({
|
|
|
14
14
|
testDir: './tests/e2e',
|
|
15
15
|
testMatch: '**/*.spec.ts',
|
|
16
16
|
timeout: 30 * 1000,
|
|
17
|
-
expect: {
|
|
17
|
+
expect: {
|
|
18
|
+
timeout: 5 * 1000,
|
|
19
|
+
toHaveScreenshot: {
|
|
20
|
+
maxDiffPixelRatio: 0.1,
|
|
21
|
+
animations: 'disabled',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
18
24
|
forbidOnly: !!process.env.CI,
|
|
19
25
|
retries: process.env.CI ? 2 : 0,
|
|
20
26
|
workers: 3,
|
|
@@ -15,7 +15,7 @@ import { DisputesPage } from './pages/DisputesPage'
|
|
|
15
15
|
import { ContentPage } from './pages/ContentPage'
|
|
16
16
|
import { ProtectedRoute, CaptchaModal } from './components'
|
|
17
17
|
|
|
18
|
-
export const App: React.FC = () => {
|
|
18
|
+
export const App: React.FC<{ basePath?: string }> = ({ basePath = '/admin' }) => {
|
|
19
19
|
return (
|
|
20
20
|
<ConfigProvider
|
|
21
21
|
theme={{
|
|
@@ -24,7 +24,7 @@ export const App: React.FC = () => {
|
|
|
24
24
|
},
|
|
25
25
|
}}
|
|
26
26
|
>
|
|
27
|
-
<BrowserRouter basename=
|
|
27
|
+
<BrowserRouter basename={basePath}>
|
|
28
28
|
<Routes>
|
|
29
29
|
<Route path="/login" element={<LoginPage />} />
|
|
30
30
|
<Route path="/register" element={<RegisterPage />} />
|
|
@@ -2,6 +2,7 @@ import { Modal, Input, Button, message } from 'antd'
|
|
|
2
2
|
import { ReloadOutlined } from '@ant-design/icons'
|
|
3
3
|
import { useState, useEffect } from 'react'
|
|
4
4
|
import { useCaptchaStore } from '../stores/captchaStore'
|
|
5
|
+
import { apiClient } from '../services/apiClient'
|
|
5
6
|
import type { CaptchaResponse } from '@shared/modules/captcha'
|
|
6
7
|
|
|
7
8
|
export const CaptchaModal: React.FC = () => {
|
|
@@ -14,8 +15,8 @@ export const CaptchaModal: React.FC = () => {
|
|
|
14
15
|
const fetchCaptcha = async () => {
|
|
15
16
|
setRefreshing(true)
|
|
16
17
|
try {
|
|
17
|
-
const response = await
|
|
18
|
-
const result =
|
|
18
|
+
const response = await apiClient.api.captcha.$get()
|
|
19
|
+
const result = await response.json()
|
|
19
20
|
|
|
20
21
|
if (result.success && result.data) {
|
|
21
22
|
setCaptchaData(result.data)
|
|
@@ -53,16 +54,13 @@ export const CaptchaModal: React.FC = () => {
|
|
|
53
54
|
|
|
54
55
|
setLoading(true)
|
|
55
56
|
try {
|
|
56
|
-
const response = await
|
|
57
|
-
|
|
58
|
-
headers: { 'Content-Type': 'application/json' },
|
|
59
|
-
body: JSON.stringify({
|
|
57
|
+
const response = await apiClient.api['verify-captcha'].$post({
|
|
58
|
+
json: {
|
|
60
59
|
id: captchaData.id,
|
|
61
60
|
code,
|
|
62
|
-
}
|
|
61
|
+
},
|
|
63
62
|
})
|
|
64
|
-
|
|
65
|
-
const result = (await response.json()) as { success: boolean; error?: string }
|
|
63
|
+
const result = await response.json()
|
|
66
64
|
|
|
67
65
|
if (result.success) {
|
|
68
66
|
message.success('验证成功')
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { Dropdown, Avatar } from 'antd'
|
|
1
|
+
import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons'
|
|
2
|
+
import { Breadcrumb, Select, Avatar, Dropdown, Layout as AntLayout, theme } from 'antd'
|
|
4
3
|
import type { MenuProps } from 'antd'
|
|
4
|
+
import { User, LogOut, Settings } from 'lucide-react'
|
|
5
|
+
import { useNavigate } from 'react-router-dom'
|
|
5
6
|
import { useAdminStore } from '../stores/adminStore'
|
|
6
7
|
import { useAdminNotifications } from '../hooks/useAdminNotifications'
|
|
7
8
|
import { NotificationDrawer, NotificationBell } from '../components/NotificationDrawer'
|
|
@@ -9,10 +10,17 @@ import { AccountSwitcher } from '../components/AccountSwitcher'
|
|
|
9
10
|
import { useEffect, useState } from 'react'
|
|
10
11
|
|
|
11
12
|
interface HeaderProps {
|
|
12
|
-
|
|
13
|
+
collapsed: boolean
|
|
14
|
+
onToggle: () => void
|
|
13
15
|
}
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
const ROLE_OPTIONS = [
|
|
18
|
+
{ label: 'Super Admin', value: 'super_admin' },
|
|
19
|
+
{ label: 'Moderator', value: 'moderator' },
|
|
20
|
+
{ label: 'Operator', value: 'operator' },
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
export const Header: React.FC<HeaderProps> = ({ collapsed, onToggle }) => {
|
|
16
24
|
const navigate = useNavigate()
|
|
17
25
|
const { user, logout } = useAdminStore()
|
|
18
26
|
const [drawerOpen, setDrawerOpen] = useState(false)
|
|
@@ -30,7 +38,7 @@ export const Header: React.FC<HeaderProps> = ({ onToggleSidebar }) => {
|
|
|
30
38
|
navigate('/login')
|
|
31
39
|
}
|
|
32
40
|
|
|
33
|
-
const
|
|
41
|
+
const userMenuItems: MenuProps['items'] = [
|
|
34
42
|
{
|
|
35
43
|
key: 'profile',
|
|
36
44
|
label: 'Profile',
|
|
@@ -39,11 +47,10 @@ export const Header: React.FC<HeaderProps> = ({ onToggleSidebar }) => {
|
|
|
39
47
|
{
|
|
40
48
|
key: 'settings',
|
|
41
49
|
label: 'Settings',
|
|
50
|
+
icon: <Settings className="w-4 h-4" />,
|
|
42
51
|
onClick: () => navigate('/settings'),
|
|
43
52
|
},
|
|
44
|
-
{
|
|
45
|
-
type: 'divider',
|
|
46
|
-
},
|
|
53
|
+
{ type: 'divider' },
|
|
47
54
|
{
|
|
48
55
|
key: 'logout',
|
|
49
56
|
label: 'Logout',
|
|
@@ -53,31 +60,58 @@ export const Header: React.FC<HeaderProps> = ({ onToggleSidebar }) => {
|
|
|
53
60
|
},
|
|
54
61
|
]
|
|
55
62
|
|
|
63
|
+
const { token } = theme.useToken()
|
|
64
|
+
|
|
56
65
|
return (
|
|
57
66
|
<>
|
|
58
|
-
<
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
+
<AntLayout.Header
|
|
68
|
+
className="flex items-center justify-between px-6 bg-white border-b border-gray-200"
|
|
69
|
+
style={{
|
|
70
|
+
paddingInline: 24,
|
|
71
|
+
height: 64,
|
|
72
|
+
lineHeight: '64px',
|
|
73
|
+
backgroundColor: token.colorBgContainer,
|
|
74
|
+
borderBottom: `1px solid ${token.colorBorderSecondary}`,
|
|
75
|
+
}}
|
|
76
|
+
data-testid="admin-header"
|
|
77
|
+
>
|
|
67
78
|
<div className="flex items-center gap-4">
|
|
79
|
+
<button
|
|
80
|
+
onClick={onToggle}
|
|
81
|
+
className="p-2 rounded-lg hover:bg-gray-100 transition-colors cursor-pointer border-none bg-transparent"
|
|
82
|
+
data-testid="toggle-sidebar-button"
|
|
83
|
+
>
|
|
84
|
+
{collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
|
|
85
|
+
</button>
|
|
86
|
+
|
|
87
|
+
<Breadcrumb
|
|
88
|
+
items={[{ title: 'Home' }, { title: 'Dashboard' }]}
|
|
89
|
+
style={{ fontSize: 14 }}
|
|
90
|
+
/>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<div className="flex items-center gap-3">
|
|
94
|
+
<Select
|
|
95
|
+
size="small"
|
|
96
|
+
defaultValue="super_admin"
|
|
97
|
+
options={ROLE_OPTIONS}
|
|
98
|
+
style={{ width: 130 }}
|
|
99
|
+
data-testid="role-switcher"
|
|
100
|
+
/>
|
|
101
|
+
|
|
68
102
|
<NotificationBell unreadCount={unreadCount} onClick={() => setDrawerOpen(true)} />
|
|
69
103
|
<AccountSwitcher />
|
|
70
|
-
<Dropdown menu={{ items:
|
|
71
|
-
<button className="flex items-center gap-2 p-
|
|
104
|
+
<Dropdown menu={{ items: userMenuItems }} placement="bottomRight" trigger={['click']}>
|
|
105
|
+
<button className="flex items-center gap-2 p-1.5 rounded-lg hover:bg-gray-100 transition-colors cursor-pointer border-none bg-transparent">
|
|
72
106
|
<Avatar
|
|
73
|
-
size=
|
|
107
|
+
size={32}
|
|
74
108
|
src={user?.avatar}
|
|
75
109
|
icon={!user?.avatar && <User className="w-4 h-4" />}
|
|
76
110
|
/>
|
|
77
111
|
</button>
|
|
78
112
|
</Dropdown>
|
|
79
113
|
</div>
|
|
80
|
-
</
|
|
114
|
+
</AntLayout.Header>
|
|
81
115
|
|
|
82
116
|
<NotificationDrawer
|
|
83
117
|
open={drawerOpen}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ReactNode, useState } from 'react'
|
|
2
|
+
import { Layout as AntLayout } from 'antd'
|
|
2
3
|
import { Sidebar } from './Sidebar'
|
|
3
4
|
import { Header } from './Header'
|
|
4
5
|
|
|
@@ -7,17 +8,21 @@ interface LayoutProps {
|
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
export const Layout: React.FC<LayoutProps> = ({ children }) => {
|
|
10
|
-
const [
|
|
11
|
+
const [collapsed, setCollapsed] = useState(false)
|
|
11
12
|
|
|
12
13
|
return (
|
|
13
|
-
<
|
|
14
|
-
<Sidebar
|
|
15
|
-
<
|
|
16
|
-
<Header
|
|
17
|
-
<
|
|
14
|
+
<AntLayout className="min-h-screen" data-testid="admin-layout">
|
|
15
|
+
<Sidebar collapsed={collapsed} onCollapse={setCollapsed} />
|
|
16
|
+
<AntLayout>
|
|
17
|
+
<Header collapsed={collapsed} onToggle={() => setCollapsed(!collapsed)} />
|
|
18
|
+
<AntLayout.Content
|
|
19
|
+
className="m-6 p-6 bg-white rounded-lg shadow-sm"
|
|
20
|
+
style={{ minHeight: 'calc(100vh - 64px - 48px)' }}
|
|
21
|
+
data-testid="admin-main"
|
|
22
|
+
>
|
|
18
23
|
{children}
|
|
19
|
-
</
|
|
20
|
-
</
|
|
21
|
-
</
|
|
24
|
+
</AntLayout.Content>
|
|
25
|
+
</AntLayout>
|
|
26
|
+
</AntLayout>
|
|
22
27
|
)
|
|
23
28
|
}
|