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.
@@ -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()).withLoading(t('users.loading')).json()
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}>{t('users.customerService')}</Select.Option>
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>
@@ -21,7 +21,7 @@ export const useThemeStore = create<ThemeState>()(
21
21
  {
22
22
  name: 'admin-theme',
23
23
  onRehydrateStorage: () => {
24
- return (state) => {
24
+ return state => {
25
25
  if (state) {
26
26
  document.documentElement.setAttribute('data-theme', state.mode)
27
27
  }
@@ -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
- const p = params as {
95
- username: string
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
- } as any,
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
- const p = params as {
129
- id: string
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 as Record<string, string | null | undefined> })
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.enum(['article', 'announcement', 'tutorial', 'news', 'policy']).default('article').describe('Category'),
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: { title: p.title, content: p.content, category: p.category as 'article' | 'announcement' | 'tutorial' | 'news' | 'policy', tags },
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.enum(['refund', 'product_quality', 'service_quality', 'delivery', 'other']).default('other').describe('Type'),
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.enum(['pending', 'resolved', 'investigating', 'rejected']).optional().describe('New status'),
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 { namespace: string; filename: string; private: boolean; 'expiry-seconds': number }
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 { limit: number; offset: number; status?: string; 'customer-name'?: string }
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 { 'customer-name': string; 'customer-email': string; 'product-name': string; amount: number }
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.enum(['pending', 'processing', 'completed', 'cancelled', 'disputed']).optional().describe('New status'),
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 { limit: number; offset: number; 'user-id'?: string; action?: string; 'resource-type'?: string }
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.enum(['technical', 'billing', 'feature_request', 'bug_report', 'general']).default('general').describe('Category'),
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 'technical' | 'billing' | 'feature_request' | 'bug_report' | 'general',
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.enum(['open', 'closed', 'in_progress', 'waiting_customer', 'resolved']).optional().describe('New status'),
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 "tailwindcss";
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
- 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
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="dark"] {
27
+ [data-theme='dark'] {
30
28
  color-scheme: dark;
31
29
  }
32
30
 
33
- [data-theme="dark"] body {
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="dark"] .bg-white {
36
+ [data-theme='dark'] .bg-white {
39
37
  background-color: #1f1f1f !important;
40
38
  }
41
39
 
42
- [data-theme="dark"] .bg-gray-50 {
40
+ [data-theme='dark'] .bg-gray-50 {
43
41
  background-color: #1a1a1a !important;
44
42
  }
45
43
 
46
- [data-theme="dark"] .bg-gray-100 {
44
+ [data-theme='dark'] .bg-gray-100 {
47
45
  background-color: #1f1f1f !important;
48
46
  }
49
47
 
50
- [data-theme="dark"] .text-gray-900 {
48
+ [data-theme='dark'] .text-gray-900 {
51
49
  color: rgba(255, 255, 255, 0.95) !important;
52
50
  }
53
51
 
54
- [data-theme="dark"] .text-gray-800 {
52
+ [data-theme='dark'] .text-gray-800 {
55
53
  color: rgba(255, 255, 255, 0.85) !important;
56
54
  }
57
55
 
58
- [data-theme="dark"] .text-gray-700 {
56
+ [data-theme='dark'] .text-gray-700 {
59
57
  color: rgba(255, 255, 255, 0.75) !important;
60
58
  }
61
59
 
62
- [data-theme="dark"] .text-gray-600 {
60
+ [data-theme='dark'] .text-gray-600 {
63
61
  color: rgba(255, 255, 255, 0.65) !important;
64
62
  }
65
63
 
66
- [data-theme="dark"] .text-gray-500 {
64
+ [data-theme='dark'] .text-gray-500 {
67
65
  color: rgba(255, 255, 255, 0.55) !important;
68
66
  }
69
67
 
70
- [data-theme="dark"] .border-gray-200 {
68
+ [data-theme='dark'] .border-gray-200 {
71
69
  border-color: #303030 !important;
72
70
  }
73
71
 
74
- [data-theme="dark"] .border-gray-100 {
72
+ [data-theme='dark'] .border-gray-100 {
75
73
  border-color: #262626 !important;
76
74
  }
77
75
 
78
- [data-theme="dark"] .hover\:bg-gray-100:hover {
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="dark"] .hover\:bg-gray-50:hover {
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="dark"] .text-gray-400 {
84
+ [data-theme='dark'] .text-gray-400 {
87
85
  color: #8c8c8c;
88
86
  }
89
87
 
90
- [data-theme="dark"] .text-gray-300 {
88
+ [data-theme='dark'] .text-gray-300 {
91
89
  color: #a0a0a0;
92
90
  }
93
91
 
94
- [data-theme="dark"] .bg-blue-50 {
92
+ [data-theme='dark'] .bg-blue-50 {
95
93
  background-color: rgba(59, 130, 246, 0.1);
96
94
  }
97
95
 
98
- [data-theme="dark"] .border-gray-300 {
96
+ [data-theme='dark'] .border-gray-300 {
99
97
  border-color: #434343;
100
98
  }