create-fullstack-scaffold 0.4.11 → 0.4.12
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.map +1 -1
- package/package.json +1 -1
- package/template/src/admin/App.tsx +4 -1
- package/template/src/admin/components/NotificationDrawer.tsx +13 -42
- package/template/src/admin/components/ThemeToggle.tsx +2 -2
- package/template/src/admin/layouts/Header.tsx +12 -9
- package/template/src/admin/pages/ContentPage.tsx +8 -10
- package/template/src/admin/pages/DashboardPage.tsx +30 -17
- package/template/src/admin/pages/DisputesPage.tsx +20 -14
- package/template/src/admin/pages/LoginPage.tsx +14 -3
- package/template/src/admin/pages/OrdersPage.tsx +10 -6
- package/template/src/admin/pages/PermissionsPage.tsx +1 -3
- package/template/src/admin/pages/RegisterPage.tsx +6 -4
- package/template/src/admin/pages/RolesPage.tsx +10 -3
- package/template/src/admin/pages/SystemLogsPage.tsx +12 -4
- package/template/src/admin/pages/TicketsPage.tsx +15 -15
- package/template/src/admin/pages/UsersPage.tsx +10 -6
- package/template/src/admin/stores/themeStore.ts +1 -1
- package/template/src/cli/modules/admin/index.ts +8 -16
- package/template/src/cli/modules/content/index.ts +10 -2
- package/template/src/cli/modules/dispute/index.ts +8 -2
- package/template/src/cli/modules/file/index.ts +6 -1
- package/template/src/cli/modules/order/index.ts +16 -3
- package/template/src/cli/modules/permission/index.ts +7 -1
- package/template/src/cli/modules/ticket/index.ts +14 -3
- package/template/src/client/index.css +22 -24
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useEffect } from 'react'
|
|
1
|
+
import { useState, useEffect, useCallback } from 'react'
|
|
2
2
|
import {
|
|
3
3
|
Table,
|
|
4
4
|
Card,
|
|
@@ -37,18 +37,20 @@ export const UsersPage: React.FC = () => {
|
|
|
37
37
|
const [form] = Form.useForm<UserFormData>()
|
|
38
38
|
const { roleLabels } = useRoleLabels()
|
|
39
39
|
|
|
40
|
-
const fetchUsers = async () => {
|
|
40
|
+
const fetchUsers = useCallback(async () => {
|
|
41
41
|
try {
|
|
42
|
-
const data = await api(apiClient.api.admin.users.$get())
|
|
42
|
+
const data = await api(apiClient.api.admin.users.$get())
|
|
43
|
+
.withLoading(t('users.loading'))
|
|
44
|
+
.json()
|
|
43
45
|
setUsers(data)
|
|
44
46
|
} catch {
|
|
45
47
|
// handled by api-request
|
|
46
48
|
}
|
|
47
|
-
}
|
|
49
|
+
}, [t])
|
|
48
50
|
|
|
49
51
|
useEffect(() => {
|
|
50
52
|
fetchUsers()
|
|
51
|
-
}, [])
|
|
53
|
+
}, [fetchUsers])
|
|
52
54
|
|
|
53
55
|
const handleCreate = () => {
|
|
54
56
|
setEditingUser(null)
|
|
@@ -271,7 +273,9 @@ export const UsersPage: React.FC = () => {
|
|
|
271
273
|
>
|
|
272
274
|
<Select placeholder={t('users.rolePlaceholder')}>
|
|
273
275
|
<Select.Option value={Role.SUPER_ADMIN}>{t('users.superAdmin')}</Select.Option>
|
|
274
|
-
<Select.Option value={Role.CUSTOMER_SERVICE}>
|
|
276
|
+
<Select.Option value={Role.CUSTOMER_SERVICE}>
|
|
277
|
+
{t('users.customerService')}
|
|
278
|
+
</Select.Option>
|
|
275
279
|
<Select.Option value={Role.USER}>{t('users.normalUser')}</Select.Option>
|
|
276
280
|
</Select>
|
|
277
281
|
</Form.Item>
|
|
@@ -2,6 +2,7 @@ import type { SiteInstance } from '@dyyz1993/xcli-core'
|
|
|
2
2
|
import { ok, fail } from '@dyyz1993/xcli-core'
|
|
3
3
|
import { z } from 'zod'
|
|
4
4
|
import { getClient } from '@cli/utils/api'
|
|
5
|
+
import type { Role } from '@shared/modules/permission'
|
|
5
6
|
|
|
6
7
|
export function registerAdminCommands(site: SiteInstance) {
|
|
7
8
|
site.command('dashboard', {
|
|
@@ -91,22 +92,17 @@ export function registerAdminCommands(site: SiteInstance) {
|
|
|
91
92
|
role: z.enum(['super_admin', 'customer_service', 'user']).default('user').describe('Role'),
|
|
92
93
|
}),
|
|
93
94
|
handler: async (params: unknown) => {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
email: string
|
|
97
|
-
password: string
|
|
98
|
-
role: 'super_admin' | 'customer_service' | 'user'
|
|
99
|
-
}
|
|
95
|
+
type CreateParams = { username: string; email: string; password: string; role: 'super_admin' | 'customer_service' | 'user' }
|
|
96
|
+
const p = params as CreateParams
|
|
100
97
|
try {
|
|
101
98
|
const client = getClient()
|
|
102
99
|
const res = await client.api.admin.users.$post({
|
|
103
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
104
100
|
json: {
|
|
105
101
|
username: p.username,
|
|
106
102
|
email: p.email,
|
|
107
103
|
password: p.password,
|
|
108
|
-
role: p.role,
|
|
109
|
-
}
|
|
104
|
+
role: p.role as Role,
|
|
105
|
+
},
|
|
110
106
|
})
|
|
111
107
|
const data = await res.json()
|
|
112
108
|
return ok(data, ['User created'])
|
|
@@ -125,12 +121,8 @@ export function registerAdminCommands(site: SiteInstance) {
|
|
|
125
121
|
role: z.enum(['super_admin', 'customer_service', 'user']).optional().describe('New role'),
|
|
126
122
|
}),
|
|
127
123
|
handler: async (params: unknown) => {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
username?: string
|
|
131
|
-
email?: string
|
|
132
|
-
role?: 'super_admin' | 'customer_service' | 'user'
|
|
133
|
-
}
|
|
124
|
+
type UpdateParams = { id: string; username?: string; email?: string; role?: 'super_admin' | 'customer_service' | 'user' }
|
|
125
|
+
const p = params as UpdateParams
|
|
134
126
|
const { id, ...rest } = p
|
|
135
127
|
const body: Record<string, string | undefined> = {}
|
|
136
128
|
if (rest.username) body.username = rest.username
|
|
@@ -138,7 +130,7 @@ export function registerAdminCommands(site: SiteInstance) {
|
|
|
138
130
|
if (rest.role) body.role = rest.role
|
|
139
131
|
try {
|
|
140
132
|
const client = getClient()
|
|
141
|
-
const res = await client.api.admin.users[':id'].$put({ param: { id }, json: body
|
|
133
|
+
const res = await client.api.admin.users[':id'].$put({ param: { id }, json: body })
|
|
142
134
|
const data = await res.json()
|
|
143
135
|
return ok(data, ['User updated'])
|
|
144
136
|
} catch (err) {
|
|
@@ -48,7 +48,10 @@ export function registerContentCommands(site: SiteInstance) {
|
|
|
48
48
|
parameters: z.object({
|
|
49
49
|
title: z.string().min(1).describe('Content title'),
|
|
50
50
|
content: z.string().min(1).describe('Content body'),
|
|
51
|
-
category: z
|
|
51
|
+
category: z
|
|
52
|
+
.enum(['article', 'announcement', 'tutorial', 'news', 'policy'])
|
|
53
|
+
.default('article')
|
|
54
|
+
.describe('Category'),
|
|
52
55
|
tags: z.string().optional().describe('Comma-separated tags'),
|
|
53
56
|
}),
|
|
54
57
|
handler: async (params: unknown) => {
|
|
@@ -57,7 +60,12 @@ export function registerContentCommands(site: SiteInstance) {
|
|
|
57
60
|
const client = getClient()
|
|
58
61
|
const tags = p.tags ? p.tags.split(',') : undefined
|
|
59
62
|
const res = await client.api.contents.$post({
|
|
60
|
-
json: {
|
|
63
|
+
json: {
|
|
64
|
+
title: p.title,
|
|
65
|
+
content: p.content,
|
|
66
|
+
category: p.category as 'article' | 'announcement' | 'tutorial' | 'news' | 'policy',
|
|
67
|
+
tags,
|
|
68
|
+
},
|
|
61
69
|
})
|
|
62
70
|
const data = await res.json()
|
|
63
71
|
return ok(data, ['Content created'])
|
|
@@ -50,7 +50,10 @@ export function registerDisputeCommands(site: SiteInstance) {
|
|
|
50
50
|
'order-no': z.string().describe('Order number'),
|
|
51
51
|
'customer-name': z.string().describe('Customer name'),
|
|
52
52
|
'customer-email': z.string().email().describe('Customer email'),
|
|
53
|
-
type: z
|
|
53
|
+
type: z
|
|
54
|
+
.enum(['refund', 'product_quality', 'service_quality', 'delivery', 'other'])
|
|
55
|
+
.default('other')
|
|
56
|
+
.describe('Type'),
|
|
54
57
|
description: z.string().min(1).describe('Description'),
|
|
55
58
|
amount: z.coerce.number().positive().describe('Amount'),
|
|
56
59
|
}),
|
|
@@ -89,7 +92,10 @@ export function registerDisputeCommands(site: SiteInstance) {
|
|
|
89
92
|
description: 'Update a dispute status',
|
|
90
93
|
parameters: z.object({
|
|
91
94
|
id: z.string().describe('Dispute ID'),
|
|
92
|
-
status: z
|
|
95
|
+
status: z
|
|
96
|
+
.enum(['pending', 'resolved', 'investigating', 'rejected'])
|
|
97
|
+
.optional()
|
|
98
|
+
.describe('New status'),
|
|
93
99
|
}),
|
|
94
100
|
handler: async (params: unknown) => {
|
|
95
101
|
const p = params as { id: string; status?: string }
|
|
@@ -13,7 +13,12 @@ export function registerFileCommands(site: SiteInstance) {
|
|
|
13
13
|
'expiry-seconds': z.coerce.number().default(3600).describe('URL expiry in seconds'),
|
|
14
14
|
}),
|
|
15
15
|
handler: async (params: unknown) => {
|
|
16
|
-
const p = params as {
|
|
16
|
+
const p = params as {
|
|
17
|
+
namespace: string
|
|
18
|
+
filename: string
|
|
19
|
+
private: boolean
|
|
20
|
+
'expiry-seconds': number
|
|
21
|
+
}
|
|
17
22
|
try {
|
|
18
23
|
const client = getClient()
|
|
19
24
|
const res = await client.api['generate-url'].$post({
|
|
@@ -13,7 +13,12 @@ export function registerOrderCommands(site: SiteInstance) {
|
|
|
13
13
|
'customer-name': z.string().optional().describe('Filter by customer name'),
|
|
14
14
|
}),
|
|
15
15
|
handler: async (params: unknown) => {
|
|
16
|
-
const p = params as {
|
|
16
|
+
const p = params as {
|
|
17
|
+
limit: number
|
|
18
|
+
offset: number
|
|
19
|
+
status?: string
|
|
20
|
+
'customer-name'?: string
|
|
21
|
+
}
|
|
17
22
|
try {
|
|
18
23
|
const client = getClient()
|
|
19
24
|
const query: Record<string, string> = {
|
|
@@ -58,7 +63,12 @@ export function registerOrderCommands(site: SiteInstance) {
|
|
|
58
63
|
amount: z.coerce.number().positive().describe('Order amount'),
|
|
59
64
|
}),
|
|
60
65
|
handler: async (params: unknown) => {
|
|
61
|
-
const p = params as {
|
|
66
|
+
const p = params as {
|
|
67
|
+
'customer-name': string
|
|
68
|
+
'customer-email': string
|
|
69
|
+
'product-name': string
|
|
70
|
+
amount: number
|
|
71
|
+
}
|
|
62
72
|
try {
|
|
63
73
|
const client = getClient()
|
|
64
74
|
const res = await client.api.orders.$post({
|
|
@@ -81,7 +91,10 @@ export function registerOrderCommands(site: SiteInstance) {
|
|
|
81
91
|
description: 'Update order status',
|
|
82
92
|
parameters: z.object({
|
|
83
93
|
id: z.string().describe('Order ID'),
|
|
84
|
-
status: z
|
|
94
|
+
status: z
|
|
95
|
+
.enum(['pending', 'processing', 'completed', 'cancelled', 'disputed'])
|
|
96
|
+
.optional()
|
|
97
|
+
.describe('New status'),
|
|
85
98
|
}),
|
|
86
99
|
handler: async (params: unknown) => {
|
|
87
100
|
const p = params as { id: string; status?: string }
|
|
@@ -152,7 +152,13 @@ export function registerPermissionCommands(site: SiteInstance) {
|
|
|
152
152
|
'resource-type': z.string().optional().describe('Filter by resource type'),
|
|
153
153
|
}),
|
|
154
154
|
handler: async (params: unknown) => {
|
|
155
|
-
const p = params as {
|
|
155
|
+
const p = params as {
|
|
156
|
+
limit: number
|
|
157
|
+
offset: number
|
|
158
|
+
'user-id'?: string
|
|
159
|
+
action?: string
|
|
160
|
+
'resource-type'?: string
|
|
161
|
+
}
|
|
156
162
|
try {
|
|
157
163
|
const client = getClient()
|
|
158
164
|
const query: Record<string, string> = {
|
|
@@ -50,7 +50,10 @@ export function registerTicketCommands(site: SiteInstance) {
|
|
|
50
50
|
'customer-email': z.string().email().describe('Customer email'),
|
|
51
51
|
subject: z.string().min(1).describe('Ticket subject'),
|
|
52
52
|
description: z.string().min(1).describe('Ticket description'),
|
|
53
|
-
category: z
|
|
53
|
+
category: z
|
|
54
|
+
.enum(['technical', 'billing', 'feature_request', 'bug_report', 'general'])
|
|
55
|
+
.default('general')
|
|
56
|
+
.describe('Category'),
|
|
54
57
|
priority: z.enum(['low', 'medium', 'high', 'urgent']).default('medium').describe('Priority'),
|
|
55
58
|
}),
|
|
56
59
|
handler: async (params: unknown) => {
|
|
@@ -70,7 +73,12 @@ export function registerTicketCommands(site: SiteInstance) {
|
|
|
70
73
|
customerEmail: p['customer-email'],
|
|
71
74
|
subject: p.subject,
|
|
72
75
|
description: p.description,
|
|
73
|
-
category: p.category as
|
|
76
|
+
category: p.category as
|
|
77
|
+
| 'technical'
|
|
78
|
+
| 'billing'
|
|
79
|
+
| 'feature_request'
|
|
80
|
+
| 'bug_report'
|
|
81
|
+
| 'general',
|
|
74
82
|
priority: p.priority as 'low' | 'medium' | 'high' | 'urgent',
|
|
75
83
|
},
|
|
76
84
|
})
|
|
@@ -86,7 +94,10 @@ export function registerTicketCommands(site: SiteInstance) {
|
|
|
86
94
|
description: 'Update ticket status',
|
|
87
95
|
parameters: z.object({
|
|
88
96
|
id: z.string().describe('Ticket ID'),
|
|
89
|
-
status: z
|
|
97
|
+
status: z
|
|
98
|
+
.enum(['open', 'closed', 'in_progress', 'waiting_customer', 'resolved'])
|
|
99
|
+
.optional()
|
|
100
|
+
.describe('New status'),
|
|
90
101
|
'assigned-to': z.string().optional().describe('Assign to user'),
|
|
91
102
|
}),
|
|
92
103
|
handler: async (params: unknown) => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
@import
|
|
1
|
+
@import 'tailwindcss';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Global styles
|
|
@@ -10,91 +10,89 @@
|
|
|
10
10
|
|
|
11
11
|
body {
|
|
12
12
|
margin: 0;
|
|
13
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
|
14
|
-
'
|
|
15
|
-
sans-serif;
|
|
13
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',
|
|
14
|
+
'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
|
|
16
15
|
-webkit-font-smoothing: antialiased;
|
|
17
16
|
-moz-osx-font-smoothing: grayscale;
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
code {
|
|
21
|
-
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
|
22
|
-
monospace;
|
|
20
|
+
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
|
|
23
21
|
}
|
|
24
22
|
|
|
25
23
|
#root {
|
|
26
24
|
min-height: 100vh;
|
|
27
25
|
}
|
|
28
26
|
|
|
29
|
-
[data-theme=
|
|
27
|
+
[data-theme='dark'] {
|
|
30
28
|
color-scheme: dark;
|
|
31
29
|
}
|
|
32
30
|
|
|
33
|
-
[data-theme=
|
|
31
|
+
[data-theme='dark'] body {
|
|
34
32
|
background-color: #141414;
|
|
35
33
|
color: rgba(255, 255, 255, 0.85);
|
|
36
34
|
}
|
|
37
35
|
|
|
38
|
-
[data-theme=
|
|
36
|
+
[data-theme='dark'] .bg-white {
|
|
39
37
|
background-color: #1f1f1f !important;
|
|
40
38
|
}
|
|
41
39
|
|
|
42
|
-
[data-theme=
|
|
40
|
+
[data-theme='dark'] .bg-gray-50 {
|
|
43
41
|
background-color: #1a1a1a !important;
|
|
44
42
|
}
|
|
45
43
|
|
|
46
|
-
[data-theme=
|
|
44
|
+
[data-theme='dark'] .bg-gray-100 {
|
|
47
45
|
background-color: #1f1f1f !important;
|
|
48
46
|
}
|
|
49
47
|
|
|
50
|
-
[data-theme=
|
|
48
|
+
[data-theme='dark'] .text-gray-900 {
|
|
51
49
|
color: rgba(255, 255, 255, 0.95) !important;
|
|
52
50
|
}
|
|
53
51
|
|
|
54
|
-
[data-theme=
|
|
52
|
+
[data-theme='dark'] .text-gray-800 {
|
|
55
53
|
color: rgba(255, 255, 255, 0.85) !important;
|
|
56
54
|
}
|
|
57
55
|
|
|
58
|
-
[data-theme=
|
|
56
|
+
[data-theme='dark'] .text-gray-700 {
|
|
59
57
|
color: rgba(255, 255, 255, 0.75) !important;
|
|
60
58
|
}
|
|
61
59
|
|
|
62
|
-
[data-theme=
|
|
60
|
+
[data-theme='dark'] .text-gray-600 {
|
|
63
61
|
color: rgba(255, 255, 255, 0.65) !important;
|
|
64
62
|
}
|
|
65
63
|
|
|
66
|
-
[data-theme=
|
|
64
|
+
[data-theme='dark'] .text-gray-500 {
|
|
67
65
|
color: rgba(255, 255, 255, 0.55) !important;
|
|
68
66
|
}
|
|
69
67
|
|
|
70
|
-
[data-theme=
|
|
68
|
+
[data-theme='dark'] .border-gray-200 {
|
|
71
69
|
border-color: #303030 !important;
|
|
72
70
|
}
|
|
73
71
|
|
|
74
|
-
[data-theme=
|
|
72
|
+
[data-theme='dark'] .border-gray-100 {
|
|
75
73
|
border-color: #262626 !important;
|
|
76
74
|
}
|
|
77
75
|
|
|
78
|
-
[data-theme=
|
|
76
|
+
[data-theme='dark'] .hover\:bg-gray-100:hover {
|
|
79
77
|
background-color: rgba(255, 255, 255, 0.08) !important;
|
|
80
78
|
}
|
|
81
79
|
|
|
82
|
-
[data-theme=
|
|
80
|
+
[data-theme='dark'] .hover\:bg-gray-50:hover {
|
|
83
81
|
background-color: rgba(255, 255, 255, 0.06) !important;
|
|
84
82
|
}
|
|
85
83
|
|
|
86
|
-
[data-theme=
|
|
84
|
+
[data-theme='dark'] .text-gray-400 {
|
|
87
85
|
color: #8c8c8c;
|
|
88
86
|
}
|
|
89
87
|
|
|
90
|
-
[data-theme=
|
|
88
|
+
[data-theme='dark'] .text-gray-300 {
|
|
91
89
|
color: #a0a0a0;
|
|
92
90
|
}
|
|
93
91
|
|
|
94
|
-
[data-theme=
|
|
92
|
+
[data-theme='dark'] .bg-blue-50 {
|
|
95
93
|
background-color: rgba(59, 130, 246, 0.1);
|
|
96
94
|
}
|
|
97
95
|
|
|
98
|
-
[data-theme=
|
|
96
|
+
[data-theme='dark'] .border-gray-300 {
|
|
99
97
|
border-color: #434343;
|
|
100
98
|
}
|