create-fullstack-scaffold 0.4.9 → 0.4.10
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 +499 -100
- package/dist/cli/index.js.map +1 -1
- package/package.json +2 -2
- package/template/CLAUDE.md +18 -6
- package/template/drizzle/0004_add_merchants_products.sql +30 -0
- package/template/drizzle/meta/_journal.json +7 -0
- package/template/eslint-rules/module-boundary.js +6 -2
- package/template/eslint-rules/no-cross-module-service-import.js +105 -0
- package/template/eslint-rules/no-disable-type-safe-client.js +5 -0
- package/template/eslint.config.js +9 -0
- package/template/lint-scripts/config/project.config.ts +21 -0
- package/template/lint-scripts/validate-all.ts +48 -12
- package/template/lint-scripts/validators/index.ts +29 -0
- package/template/lint-scripts/validators/module-public-api.validator.ts +103 -0
- package/template/lint-scripts/validators/schema-uniqueness.validator.ts +147 -0
- package/template/modules.config.ts +2 -0
- package/template/package.json +2 -0
- package/template/playwright.config.ts +14 -0
- package/template/src/admin/App.tsx +40 -3
- package/template/src/admin/components/LanguageSwitcher.tsx +22 -0
- package/template/src/admin/components/NotificationDrawer.tsx +108 -38
- package/template/src/admin/components/StatsCard.tsx +3 -1
- package/template/src/admin/components/ThemeToggle.tsx +28 -0
- package/template/src/admin/hooks/useRoles.ts +2 -2
- package/template/src/admin/i18n/index.ts +18 -0
- package/template/src/admin/i18n/locales/en-US.json +381 -0
- package/template/src/admin/i18n/locales/zh-CN.json +381 -0
- package/template/src/admin/i18n/useLanguage.ts +21 -0
- package/template/src/admin/layouts/Header.tsx +37 -16
- package/template/src/admin/layouts/Layout.tsx +7 -3
- package/template/src/admin/layouts/Sidebar.tsx +70 -68
- package/template/src/admin/main.tsx +1 -0
- package/template/src/admin/pages/ContentPage.tsx +68 -56
- package/template/src/admin/pages/DashboardPage.tsx +82 -76
- package/template/src/admin/pages/DisputesPage.tsx +61 -53
- package/template/src/admin/pages/LoginPage.tsx +41 -33
- package/template/src/admin/pages/OrdersPage.tsx +69 -64
- package/template/src/admin/pages/PermissionsPage.tsx +10 -8
- package/template/src/admin/pages/PluginDashboardPage.tsx +3 -1
- package/template/src/admin/pages/PluginManagementPage.tsx +3 -1
- package/template/src/admin/pages/RegisterPage.tsx +18 -16
- package/template/src/admin/pages/RolesPage.tsx +51 -49
- package/template/src/admin/pages/SettingsPage.tsx +22 -22
- package/template/src/admin/pages/SystemLogsPage.tsx +32 -30
- package/template/src/admin/pages/TicketsPage.tsx +67 -59
- package/template/src/admin/pages/UsersPage.tsx +63 -53
- package/template/src/admin/pages/__tests__/RolesPage.test.tsx +2 -2
- package/template/src/admin/stores/themeStore.ts +32 -0
- package/template/src/client/index.css +73 -0
- package/template/src/merchant/App.tsx +2 -0
- package/template/src/merchant/components/MerchantGuard.tsx +2 -6
- package/template/src/merchant/components/__tests__/MerchantGuard.test.tsx +117 -0
- package/template/src/merchant/layouts/Header.tsx +2 -2
- package/template/src/merchant/pages/DashboardPage.tsx +2 -2
- package/template/src/merchant/pages/DisputesPage.tsx +14 -10
- package/template/src/merchant/pages/LoginPage.tsx +49 -0
- package/template/src/merchant/pages/OrdersPage.tsx +17 -10
- package/template/src/merchant/pages/ProductsPage.tsx +31 -8
- package/template/src/merchant/pages/SettingsPage.tsx +3 -7
- package/template/src/merchant/stores/__tests__/merchantStore.test.ts +317 -0
- package/template/src/merchant/stores/merchantStore.ts +24 -27
- package/template/src/server/db/schema/index.ts +2 -0
- package/template/src/server/db/schema/merchants.ts +26 -0
- package/template/src/server/db/schema/products.ts +25 -0
- package/template/src/server/db/test-setup.ts +237 -0
- package/template/src/server/middleware/__tests__/audit-log.test.ts +144 -0
- package/template/src/server/middleware/__tests__/cors.test.ts +84 -0
- package/template/src/server/middleware/__tests__/logger.test.ts +117 -0
- package/template/src/server/middleware/__tests__/rate-limit.test.ts +73 -0
- package/template/src/server/middleware/__tests__/realtime-env.test.ts +56 -0
- package/template/src/server/middleware/__tests__/tenant-isolation.test.ts +150 -0
- package/template/src/server/module-admin/__tests__/admin-routes.test.ts +1 -1
- package/template/src/server/module-admin/module.ts +4 -0
- package/template/src/server/module-admin/routes/admin-notification-routes.ts +3 -3
- package/template/src/server/module-admin/routes/user-management-routes.ts +2 -2
- package/template/src/server/module-auth/__tests__/auth-routes.test.ts +315 -0
- package/template/src/server/module-auth/__tests__/profile-routes.test.ts +83 -0
- package/template/src/server/module-auth/module.ts +2 -0
- package/template/src/server/module-content/module.ts +1 -0
- package/template/src/server/module-content/routes/content-routes.ts +2 -2
- package/template/src/server/module-dispute/module.ts +1 -0
- package/template/src/server/module-dispute/routes/dispute-routes.ts +2 -2
- package/template/src/server/module-merchant/__tests__/merchant-routes.test.ts +218 -0
- package/template/src/server/module-merchant/__tests__/merchant-service.test.ts +183 -0
- package/template/src/server/module-merchant/index.ts +10 -0
- package/template/src/server/module-merchant/module.ts +33 -0
- package/template/src/server/module-merchant/routes/merchant-routes.ts +138 -0
- package/template/src/server/module-merchant/services/merchant-service.ts +292 -0
- package/template/src/server/module-notifications/index.ts +18 -0
- package/template/src/server/module-notifications/module.ts +2 -0
- package/template/src/server/module-order/module.ts +1 -0
- package/template/src/server/module-order/routes/order-routes.ts +2 -2
- package/template/src/server/module-permission/routes/role-routes.ts +3 -3
- package/template/src/server/module-plugin/__tests__/admin-category-service.test.ts +181 -0
- package/template/src/server/module-plugin/__tests__/admin-plugin-service.test.ts +277 -0
- package/template/src/server/module-plugin/__tests__/admin-stats-service.test.ts +170 -0
- package/template/src/server/module-plugin/__tests__/plugin-review-service.test.ts +260 -0
- package/template/src/server/module-plugin/__tests__/plugin-seed-service.test.ts +170 -0
- package/template/src/server/module-plugin/module.ts +3 -0
- package/template/src/server/module-tenant/__tests__/tenant-routes.test.ts +142 -0
- package/template/src/server/module-tenant/__tests__/tenant-service.test.ts +152 -0
- package/template/src/server/module-tenant/module.ts +1 -0
- package/template/src/server/module-tenant/services/tenant-service.ts +1 -37
- package/template/src/server/module-ticket/module.ts +1 -0
- package/template/src/server/module-ticket/routes/ticket-routes.ts +2 -2
- package/template/src/server/module-todos/module.ts +3 -0
- package/template/src/server/route-registry.ts +4 -0
- package/template/src/server/utils/__tests__/date.test.ts +130 -0
- package/template/src/server/utils/__tests__/env.test.ts +25 -0
- package/template/src/server/utils/__tests__/generate.test.ts +82 -0
- package/template/src/server/utils/__tests__/id-helpers.test.ts +29 -0
- package/template/src/server/utils/__tests__/json.test.ts +49 -0
- package/template/src/server/utils/__tests__/permission-utils.test.ts +26 -0
- package/template/src/server/utils/__tests__/uuid.test.ts +25 -0
- package/template/src/shared/core/module-manifest.ts +15 -0
- package/template/src/shared/modules/admin/schemas.ts +1 -1
- package/template/src/shared/modules/content/schemas.ts +2 -2
- package/template/src/shared/modules/dispute/schemas.ts +2 -2
- package/template/src/shared/modules/index.ts +181 -1
- package/template/src/shared/modules/merchant/index.ts +12 -0
- package/template/src/shared/modules/merchant/schemas.ts +63 -0
- package/template/src/shared/modules/order/schemas.ts +2 -2
- package/template/src/shared/modules/permission/index.ts +1 -1
- package/template/src/shared/modules/permission/schemas.ts +1 -1
- package/template/src/shared/modules/role/schemas.ts +2 -2
- package/template/src/shared/modules/ticket/schemas.ts +2 -2
- package/template/src/shared/schemas/index.ts +74 -2
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
2
|
+
import type { Merchant, MerchantStats } from '@shared/schemas'
|
|
3
|
+
|
|
4
|
+
interface MerchantState {
|
|
5
|
+
merchant: Merchant | null
|
|
6
|
+
token: string | null
|
|
7
|
+
isAuthenticated: boolean
|
|
8
|
+
stats: MerchantStats | null
|
|
9
|
+
loading: boolean
|
|
10
|
+
error: string | null
|
|
11
|
+
startLoading: (message?: string) => void
|
|
12
|
+
stopLoading: () => void
|
|
13
|
+
checkAuth: () => Promise<void>
|
|
14
|
+
login: (username: string, password: string) => Promise<void>
|
|
15
|
+
logout: () => void
|
|
16
|
+
fetchStats: () => Promise<void>
|
|
17
|
+
setLoading: (loading: boolean) => void
|
|
18
|
+
setError: (error: string | null) => void
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const mockMerchant: Merchant = {
|
|
22
|
+
id: 1,
|
|
23
|
+
userId: 'user-1',
|
|
24
|
+
tenantId: 100,
|
|
25
|
+
businessName: 'Test Shop',
|
|
26
|
+
businessType: 'retail',
|
|
27
|
+
status: 'active',
|
|
28
|
+
description: 'A test shop',
|
|
29
|
+
phone: '1234567890',
|
|
30
|
+
email: 'shop@test.com',
|
|
31
|
+
address: '123 Test St',
|
|
32
|
+
createdAt: '2024-01-01T00:00:00Z',
|
|
33
|
+
updatedAt: '2024-01-01T00:00:00Z',
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const mockStats: MerchantStats = {
|
|
37
|
+
totalOrders: 150,
|
|
38
|
+
totalRevenue: 50000,
|
|
39
|
+
totalProducts: 80,
|
|
40
|
+
activeProducts: 65,
|
|
41
|
+
pendingOrders: 12,
|
|
42
|
+
thisMonthRevenue: 8500,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const mockCheckAuthResponse = {
|
|
46
|
+
success: true,
|
|
47
|
+
data: mockMerchant,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const mockLoginResponse = {
|
|
51
|
+
success: true,
|
|
52
|
+
data: {
|
|
53
|
+
merchant: mockMerchant,
|
|
54
|
+
token: 'merchant-token-123',
|
|
55
|
+
},
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const mockStatsResponse = {
|
|
59
|
+
success: true,
|
|
60
|
+
data: mockStats,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Extract mock functions to top level (same pattern as todoStore.test.ts)
|
|
64
|
+
const mockMeGet = vi.fn(async () => ({
|
|
65
|
+
json: async () => mockCheckAuthResponse,
|
|
66
|
+
}))
|
|
67
|
+
const mockLoginPost = vi.fn(async () => ({
|
|
68
|
+
json: async () => mockLoginResponse,
|
|
69
|
+
}))
|
|
70
|
+
const mockStatsGet = vi.fn(async () => ({
|
|
71
|
+
json: async () => mockStatsResponse,
|
|
72
|
+
}))
|
|
73
|
+
|
|
74
|
+
vi.mock('@client/services/apiClient', () => ({
|
|
75
|
+
apiClient: {
|
|
76
|
+
api: {
|
|
77
|
+
merchant: {
|
|
78
|
+
me: { $get: mockMeGet },
|
|
79
|
+
login: { $post: mockLoginPost },
|
|
80
|
+
stats: { $get: mockStatsGet },
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
}))
|
|
85
|
+
|
|
86
|
+
describe('useMerchantStore', () => {
|
|
87
|
+
let useMerchantStore: {
|
|
88
|
+
getState: () => MerchantState
|
|
89
|
+
setState: (s: Partial<MerchantState>) => void
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
beforeEach(async () => {
|
|
93
|
+
const mod = await import('../merchantStore')
|
|
94
|
+
useMerchantStore = mod.useMerchantStore
|
|
95
|
+
useMerchantStore.setState({
|
|
96
|
+
merchant: null,
|
|
97
|
+
token: null,
|
|
98
|
+
isAuthenticated: false,
|
|
99
|
+
stats: null,
|
|
100
|
+
loading: false,
|
|
101
|
+
error: null,
|
|
102
|
+
})
|
|
103
|
+
vi.clearAllMocks()
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('should have correct initial state', () => {
|
|
107
|
+
const state = useMerchantStore.getState()
|
|
108
|
+
expect(state.merchant).toBeNull()
|
|
109
|
+
expect(state.token).toBeNull()
|
|
110
|
+
expect(state.isAuthenticated).toBe(false)
|
|
111
|
+
expect(state.stats).toBeNull()
|
|
112
|
+
expect(state.loading).toBe(false)
|
|
113
|
+
expect(state.error).toBeNull()
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
describe('checkAuth', () => {
|
|
117
|
+
it('should set merchant and isAuthenticated on successful check', async () => {
|
|
118
|
+
await useMerchantStore.getState().checkAuth()
|
|
119
|
+
|
|
120
|
+
const state = useMerchantStore.getState()
|
|
121
|
+
expect(state.merchant).toEqual(mockMerchant)
|
|
122
|
+
expect(state.isAuthenticated).toBe(true)
|
|
123
|
+
expect(state.loading).toBe(false)
|
|
124
|
+
expect(state.error).toBeNull()
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('should clear merchant on failed check (success: false)', async () => {
|
|
128
|
+
mockMeGet.mockImplementationOnce(
|
|
129
|
+
async () =>
|
|
130
|
+
({
|
|
131
|
+
json: async () => ({
|
|
132
|
+
success: false,
|
|
133
|
+
error: 'Not authenticated',
|
|
134
|
+
}),
|
|
135
|
+
} as never)
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
await useMerchantStore.getState().checkAuth()
|
|
139
|
+
|
|
140
|
+
const state = useMerchantStore.getState()
|
|
141
|
+
expect(state.merchant).toBeNull()
|
|
142
|
+
expect(state.isAuthenticated).toBe(false)
|
|
143
|
+
expect(state.loading).toBe(false)
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('should handle network error gracefully', async () => {
|
|
147
|
+
mockMeGet.mockImplementationOnce(async () => {
|
|
148
|
+
throw new Error('Network error')
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
await useMerchantStore.getState().checkAuth()
|
|
152
|
+
|
|
153
|
+
const state = useMerchantStore.getState()
|
|
154
|
+
expect(state.merchant).toBeNull()
|
|
155
|
+
expect(state.isAuthenticated).toBe(false)
|
|
156
|
+
expect(state.error).toBe('Authentication failed')
|
|
157
|
+
expect(state.loading).toBe(false)
|
|
158
|
+
})
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
describe('login', () => {
|
|
162
|
+
it('should set merchant, token, isAuthenticated on successful login', async () => {
|
|
163
|
+
await useMerchantStore.getState().login('testmerchant', 'password123')
|
|
164
|
+
|
|
165
|
+
const state = useMerchantStore.getState()
|
|
166
|
+
expect(state.merchant).toEqual(mockMerchant)
|
|
167
|
+
expect(state.token).toBe('merchant-token-123')
|
|
168
|
+
expect(state.isAuthenticated).toBe(true)
|
|
169
|
+
expect(state.loading).toBe(false)
|
|
170
|
+
expect(state.error).toBeNull()
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
it('should set error on failed login (success: false)', async () => {
|
|
174
|
+
mockLoginPost.mockImplementationOnce(
|
|
175
|
+
async () =>
|
|
176
|
+
({
|
|
177
|
+
json: async () => ({
|
|
178
|
+
success: false,
|
|
179
|
+
error: 'Invalid username or password',
|
|
180
|
+
}),
|
|
181
|
+
} as never)
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
await useMerchantStore.getState().login('wrong', 'creds')
|
|
185
|
+
|
|
186
|
+
const state = useMerchantStore.getState()
|
|
187
|
+
expect(state.merchant).toBeNull()
|
|
188
|
+
expect(state.token).toBeNull()
|
|
189
|
+
expect(state.isAuthenticated).toBe(false)
|
|
190
|
+
expect(state.error).toBe('Invalid username or password')
|
|
191
|
+
expect(state.loading).toBe(false)
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it('should handle network error', async () => {
|
|
195
|
+
mockLoginPost.mockImplementationOnce(async () => {
|
|
196
|
+
throw new Error('Network error')
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
await useMerchantStore.getState().login('testmerchant', 'password123')
|
|
200
|
+
|
|
201
|
+
const state = useMerchantStore.getState()
|
|
202
|
+
expect(state.merchant).toBeNull()
|
|
203
|
+
expect(state.isAuthenticated).toBe(false)
|
|
204
|
+
expect(state.error).toBe('Network error')
|
|
205
|
+
expect(state.loading).toBe(false)
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
it('should set loading state during login', async () => {
|
|
209
|
+
let resolveResponse!: () => void
|
|
210
|
+
const responsePromise = new Promise<void>(resolve => {
|
|
211
|
+
resolveResponse = resolve
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
mockLoginPost.mockImplementationOnce(async () => {
|
|
215
|
+
await responsePromise
|
|
216
|
+
return {
|
|
217
|
+
json: async () => mockLoginResponse,
|
|
218
|
+
}
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
const loginPromise = useMerchantStore.getState().login('testmerchant', 'password123')
|
|
222
|
+
|
|
223
|
+
expect(useMerchantStore.getState().loading).toBe(true)
|
|
224
|
+
|
|
225
|
+
resolveResponse()
|
|
226
|
+
await loginPromise
|
|
227
|
+
|
|
228
|
+
expect(useMerchantStore.getState().loading).toBe(false)
|
|
229
|
+
})
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
describe('logout', () => {
|
|
233
|
+
it('should clear all auth state on logout', async () => {
|
|
234
|
+
await useMerchantStore.getState().login('testmerchant', 'password123')
|
|
235
|
+
expect(useMerchantStore.getState().isAuthenticated).toBe(true)
|
|
236
|
+
|
|
237
|
+
await useMerchantStore.getState().fetchStats()
|
|
238
|
+
expect(useMerchantStore.getState().stats).toEqual(mockStats)
|
|
239
|
+
|
|
240
|
+
useMerchantStore.getState().logout()
|
|
241
|
+
|
|
242
|
+
const state = useMerchantStore.getState()
|
|
243
|
+
expect(state.merchant).toBeNull()
|
|
244
|
+
expect(state.token).toBeNull()
|
|
245
|
+
expect(state.isAuthenticated).toBe(false)
|
|
246
|
+
expect(state.stats).toBeNull()
|
|
247
|
+
})
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
describe('fetchStats', () => {
|
|
251
|
+
it('should populate stats on successful fetch', async () => {
|
|
252
|
+
await useMerchantStore.getState().fetchStats()
|
|
253
|
+
|
|
254
|
+
const state = useMerchantStore.getState()
|
|
255
|
+
expect(state.stats).toEqual(mockStats)
|
|
256
|
+
expect(state.loading).toBe(false)
|
|
257
|
+
expect(state.error).toBeNull()
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
it('should handle failed response', async () => {
|
|
261
|
+
mockStatsGet.mockImplementationOnce(
|
|
262
|
+
async () =>
|
|
263
|
+
({
|
|
264
|
+
json: async () => ({
|
|
265
|
+
success: false,
|
|
266
|
+
error: 'Unauthorized',
|
|
267
|
+
}),
|
|
268
|
+
} as never)
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
await useMerchantStore.getState().fetchStats()
|
|
272
|
+
|
|
273
|
+
const state = useMerchantStore.getState()
|
|
274
|
+
expect(state.stats).toBeNull()
|
|
275
|
+
expect(state.loading).toBe(false)
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
it('should handle network error', async () => {
|
|
279
|
+
mockStatsGet.mockImplementationOnce(async () => {
|
|
280
|
+
throw new Error('Network error')
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
await useMerchantStore.getState().fetchStats()
|
|
284
|
+
|
|
285
|
+
const state = useMerchantStore.getState()
|
|
286
|
+
expect(state.stats).toBeNull()
|
|
287
|
+
expect(state.error).toBe('Failed to fetch statistics')
|
|
288
|
+
expect(state.loading).toBe(false)
|
|
289
|
+
})
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
describe('setLoading / setError', () => {
|
|
293
|
+
it('should update loading state', () => {
|
|
294
|
+
useMerchantStore.getState().setLoading(true)
|
|
295
|
+
expect(useMerchantStore.getState().loading).toBe(true)
|
|
296
|
+
|
|
297
|
+
useMerchantStore.getState().setLoading(false)
|
|
298
|
+
expect(useMerchantStore.getState().loading).toBe(false)
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
it('should update error state', () => {
|
|
302
|
+
useMerchantStore.getState().setError('Something went wrong')
|
|
303
|
+
expect(useMerchantStore.getState().error).toBe('Something went wrong')
|
|
304
|
+
|
|
305
|
+
useMerchantStore.getState().setError(null)
|
|
306
|
+
expect(useMerchantStore.getState().error).toBeNull()
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
it('should update loading state via startLoading/stopLoading', () => {
|
|
310
|
+
useMerchantStore.getState().startLoading()
|
|
311
|
+
expect(useMerchantStore.getState().loading).toBe(true)
|
|
312
|
+
|
|
313
|
+
useMerchantStore.getState().stopLoading()
|
|
314
|
+
expect(useMerchantStore.getState().loading).toBe(false)
|
|
315
|
+
})
|
|
316
|
+
})
|
|
317
|
+
})
|
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
import { create } from 'zustand'
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
interface MerchantStats {
|
|
5
|
-
totalSales: number
|
|
6
|
-
totalOrders: number
|
|
7
|
-
pendingOrders: number
|
|
8
|
-
}
|
|
2
|
+
import { apiClient } from '@client/services/apiClient'
|
|
3
|
+
import type { Merchant, MerchantStats } from '@shared/schemas'
|
|
9
4
|
|
|
10
5
|
interface MerchantState {
|
|
11
|
-
|
|
6
|
+
merchant: Merchant | null
|
|
7
|
+
token: string | null
|
|
12
8
|
isAuthenticated: boolean
|
|
13
9
|
stats: MerchantStats | null
|
|
14
10
|
loading: boolean
|
|
@@ -24,7 +20,8 @@ interface MerchantState {
|
|
|
24
20
|
}
|
|
25
21
|
|
|
26
22
|
export const useMerchantStore = create<MerchantState>(set => ({
|
|
27
|
-
|
|
23
|
+
merchant: null,
|
|
24
|
+
token: null,
|
|
28
25
|
isAuthenticated: false,
|
|
29
26
|
stats: null,
|
|
30
27
|
loading: false,
|
|
@@ -36,16 +33,16 @@ export const useMerchantStore = create<MerchantState>(set => ({
|
|
|
36
33
|
checkAuth: async () => {
|
|
37
34
|
set({ loading: true, error: null })
|
|
38
35
|
try {
|
|
39
|
-
const response = await
|
|
40
|
-
const result =
|
|
36
|
+
const response = await apiClient.api.merchant.me.$get()
|
|
37
|
+
const result = await response.json()
|
|
41
38
|
if (result.success === true && result.data) {
|
|
42
|
-
set({
|
|
39
|
+
set({ merchant: result.data, isAuthenticated: true })
|
|
43
40
|
} else {
|
|
44
|
-
set({
|
|
41
|
+
set({ merchant: null, isAuthenticated: false })
|
|
45
42
|
}
|
|
46
43
|
} catch (error) {
|
|
47
44
|
console.error('Auth check failed:', error)
|
|
48
|
-
set({
|
|
45
|
+
set({ merchant: null, isAuthenticated: false, error: 'Authentication failed' })
|
|
49
46
|
} finally {
|
|
50
47
|
set({ loading: false })
|
|
51
48
|
}
|
|
@@ -54,18 +51,18 @@ export const useMerchantStore = create<MerchantState>(set => ({
|
|
|
54
51
|
login: async (username: string, password: string) => {
|
|
55
52
|
set({ loading: true, error: null })
|
|
56
53
|
try {
|
|
57
|
-
const response = await
|
|
58
|
-
|
|
59
|
-
headers: {
|
|
60
|
-
'Content-Type': 'application/json',
|
|
61
|
-
},
|
|
62
|
-
body: JSON.stringify({ username, password }),
|
|
54
|
+
const response = await apiClient.api.merchant.login.$post({
|
|
55
|
+
json: { username, password },
|
|
63
56
|
})
|
|
64
|
-
const result =
|
|
57
|
+
const result = await response.json()
|
|
65
58
|
if (result.success === true && result.data) {
|
|
66
|
-
set({
|
|
67
|
-
|
|
68
|
-
|
|
59
|
+
set({
|
|
60
|
+
merchant: result.data.merchant,
|
|
61
|
+
token: result.data.token,
|
|
62
|
+
isAuthenticated: true,
|
|
63
|
+
})
|
|
64
|
+
} else if (result.success === false) {
|
|
65
|
+
set({ error: result.error })
|
|
69
66
|
}
|
|
70
67
|
} catch (error) {
|
|
71
68
|
console.error('Login failed:', error)
|
|
@@ -76,14 +73,14 @@ export const useMerchantStore = create<MerchantState>(set => ({
|
|
|
76
73
|
},
|
|
77
74
|
|
|
78
75
|
logout: () => {
|
|
79
|
-
set({
|
|
76
|
+
set({ merchant: null, token: null, isAuthenticated: false, stats: null })
|
|
80
77
|
},
|
|
81
78
|
|
|
82
79
|
fetchStats: async () => {
|
|
83
80
|
set({ loading: true, error: null })
|
|
84
81
|
try {
|
|
85
|
-
const response = await
|
|
86
|
-
const result =
|
|
82
|
+
const response = await apiClient.api.merchant.stats.$get()
|
|
83
|
+
const result = await response.json()
|
|
87
84
|
if (result.success === true && result.data) {
|
|
88
85
|
set({ stats: result.data })
|
|
89
86
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'
|
|
2
|
+
|
|
3
|
+
export const merchantStatuses = ['active', 'inactive', 'suspended'] as const
|
|
4
|
+
export type MerchantStatus = (typeof merchantStatuses)[number]
|
|
5
|
+
|
|
6
|
+
export const businessTypes = ['retail', 'wholesale', 'service', 'restaurant'] as const
|
|
7
|
+
export type BusinessType = (typeof businessTypes)[number]
|
|
8
|
+
|
|
9
|
+
export const merchants = sqliteTable('merchants', {
|
|
10
|
+
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
11
|
+
userId: text('user_id').notNull(),
|
|
12
|
+
tenantId: integer('tenant_id').notNull(),
|
|
13
|
+
businessName: text('business_name').notNull(),
|
|
14
|
+
businessType: text('business_type', { enum: businessTypes }).notNull().default('retail'),
|
|
15
|
+
status: text('status', { enum: merchantStatuses }).notNull().default('active'),
|
|
16
|
+
description: text('description'),
|
|
17
|
+
phone: text('phone'),
|
|
18
|
+
email: text('email'),
|
|
19
|
+
address: text('address'),
|
|
20
|
+
password: text('password').notNull(), // Simplified - use bcrypt hash in production
|
|
21
|
+
createdAt: text('created_at').notNull(),
|
|
22
|
+
updatedAt: text('updated_at').notNull(),
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
export type MerchantTable = typeof merchants.$inferSelect
|
|
26
|
+
export type NewMerchant = typeof merchants.$inferInsert
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core'
|
|
2
|
+
import { sql } from 'drizzle-orm'
|
|
3
|
+
|
|
4
|
+
export const productStatuses = ['active', 'inactive', 'out_of_stock'] as const
|
|
5
|
+
export type ProductStatus = (typeof productStatuses)[number]
|
|
6
|
+
|
|
7
|
+
export const products = sqliteTable('products', {
|
|
8
|
+
id: text('id').primaryKey(),
|
|
9
|
+
name: text('name').notNull(),
|
|
10
|
+
description: text('description'),
|
|
11
|
+
price: integer('price').notNull(), // 单位:分
|
|
12
|
+
status: text('status', { enum: productStatuses }).notNull().default('active'),
|
|
13
|
+
stock: integer('stock').notNull().default(0),
|
|
14
|
+
imageUrl: text('image_url'),
|
|
15
|
+
merchantId: integer('merchant_id').notNull(), // Changed to integer to match merchants.id
|
|
16
|
+
createdAt: integer('created_at', { mode: 'timestamp' })
|
|
17
|
+
.notNull()
|
|
18
|
+
.default(sql`(unixepoch() * 1000)`),
|
|
19
|
+
updatedAt: integer('updated_at', { mode: 'timestamp' })
|
|
20
|
+
.notNull()
|
|
21
|
+
.default(sql`(unixepoch() * 1000)`),
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
export type Product = typeof products.$inferSelect
|
|
25
|
+
export type NewProduct = typeof products.$inferInsert
|