create-fullstack-scaffold 0.4.9 → 0.4.11
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/cli/modules/admin/index.ts +200 -0
- package/template/src/cli/modules/captcha/index.ts +40 -0
- package/template/src/cli/modules/chat/index.ts +21 -0
- package/template/src/cli/modules/content/index.ts +144 -0
- package/template/src/cli/modules/dispute/index.ts +150 -0
- package/template/src/cli/modules/file/index.ts +55 -0
- package/template/src/cli/modules/index.ts +30 -5
- package/template/src/cli/modules/order/index.ts +170 -0
- package/template/src/cli/modules/permission/index.ts +195 -0
- package/template/src/cli/modules/tenant/index.ts +142 -0
- package/template/src/cli/modules/ticket/index.ts +167 -0
- package/template/src/cli/rpc/client.ts +2 -2
- 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
|
@@ -25,3 +25,76 @@ code {
|
|
|
25
25
|
#root {
|
|
26
26
|
min-height: 100vh;
|
|
27
27
|
}
|
|
28
|
+
|
|
29
|
+
[data-theme="dark"] {
|
|
30
|
+
color-scheme: dark;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
[data-theme="dark"] body {
|
|
34
|
+
background-color: #141414;
|
|
35
|
+
color: rgba(255, 255, 255, 0.85);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
[data-theme="dark"] .bg-white {
|
|
39
|
+
background-color: #1f1f1f !important;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
[data-theme="dark"] .bg-gray-50 {
|
|
43
|
+
background-color: #1a1a1a !important;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
[data-theme="dark"] .bg-gray-100 {
|
|
47
|
+
background-color: #1f1f1f !important;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
[data-theme="dark"] .text-gray-900 {
|
|
51
|
+
color: rgba(255, 255, 255, 0.95) !important;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
[data-theme="dark"] .text-gray-800 {
|
|
55
|
+
color: rgba(255, 255, 255, 0.85) !important;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
[data-theme="dark"] .text-gray-700 {
|
|
59
|
+
color: rgba(255, 255, 255, 0.75) !important;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
[data-theme="dark"] .text-gray-600 {
|
|
63
|
+
color: rgba(255, 255, 255, 0.65) !important;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
[data-theme="dark"] .text-gray-500 {
|
|
67
|
+
color: rgba(255, 255, 255, 0.55) !important;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
[data-theme="dark"] .border-gray-200 {
|
|
71
|
+
border-color: #303030 !important;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
[data-theme="dark"] .border-gray-100 {
|
|
75
|
+
border-color: #262626 !important;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
[data-theme="dark"] .hover\:bg-gray-100:hover {
|
|
79
|
+
background-color: rgba(255, 255, 255, 0.08) !important;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
[data-theme="dark"] .hover\:bg-gray-50:hover {
|
|
83
|
+
background-color: rgba(255, 255, 255, 0.06) !important;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
[data-theme="dark"] .text-gray-400 {
|
|
87
|
+
color: #8c8c8c;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
[data-theme="dark"] .text-gray-300 {
|
|
91
|
+
color: #a0a0a0;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
[data-theme="dark"] .bg-blue-50 {
|
|
95
|
+
background-color: rgba(59, 130, 246, 0.1);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
[data-theme="dark"] .border-gray-300 {
|
|
99
|
+
border-color: #434343;
|
|
100
|
+
}
|
|
@@ -7,6 +7,7 @@ import { ProductsPage } from './pages/ProductsPage'
|
|
|
7
7
|
import { OrdersPage } from './pages/OrdersPage'
|
|
8
8
|
import { DisputesPage } from './pages/DisputesPage'
|
|
9
9
|
import { SettingsPage } from './pages/SettingsPage'
|
|
10
|
+
import { LoginPage } from './pages/LoginPage'
|
|
10
11
|
import { MerchantGuard } from './components/MerchantGuard'
|
|
11
12
|
import { useMerchantStore } from './stores/merchantStore'
|
|
12
13
|
|
|
@@ -32,6 +33,7 @@ export const App: React.FC = () => {
|
|
|
32
33
|
>
|
|
33
34
|
<BrowserRouter basename="/merchant">
|
|
34
35
|
<Routes>
|
|
36
|
+
<Route path="/login" element={<LoginPage />} />
|
|
35
37
|
<Route
|
|
36
38
|
path="/*"
|
|
37
39
|
element={
|
|
@@ -6,13 +6,9 @@ interface MerchantGuardProps {
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
export const MerchantGuard: React.FC<MerchantGuardProps> = ({ children }) => {
|
|
9
|
-
const { isAuthenticated,
|
|
9
|
+
const { isAuthenticated, merchant } = useMerchantStore()
|
|
10
10
|
|
|
11
|
-
if (!isAuthenticated) {
|
|
12
|
-
return <Navigate to="/login" replace />
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
if (!user || user.role !== 'merchant') {
|
|
11
|
+
if (!isAuthenticated || !merchant) {
|
|
16
12
|
return <Navigate to="/login" replace />
|
|
17
13
|
}
|
|
18
14
|
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
|
2
|
+
import { render, screen, cleanup } from '@testing-library/react'
|
|
3
|
+
import { MemoryRouter, Route, Routes } from 'react-router-dom'
|
|
4
|
+
import { MerchantGuard } from '../MerchantGuard'
|
|
5
|
+
|
|
6
|
+
const mockMerchantState = {
|
|
7
|
+
isAuthenticated: false as boolean,
|
|
8
|
+
merchant: null as { id: string; name: string } | null,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
vi.mock('../../stores/merchantStore', () => ({
|
|
12
|
+
useMerchantStore: <T,>(selector?: (state: typeof mockMerchantState) => T) => {
|
|
13
|
+
if (selector) return selector(mockMerchantState)
|
|
14
|
+
return mockMerchantState
|
|
15
|
+
},
|
|
16
|
+
}))
|
|
17
|
+
|
|
18
|
+
function renderWithRouter(ui: React.ReactElement, initialPath = '/protected') {
|
|
19
|
+
return render(
|
|
20
|
+
<MemoryRouter initialEntries={[initialPath]}>
|
|
21
|
+
<Routes>
|
|
22
|
+
<Route path="/login" element={<div data-testid="login-page">Login</div>} />
|
|
23
|
+
<Route path="/protected" element={ui} />
|
|
24
|
+
</Routes>
|
|
25
|
+
</MemoryRouter>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe('MerchantGuard', () => {
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
mockMerchantState.isAuthenticated = false
|
|
32
|
+
mockMerchantState.merchant = null
|
|
33
|
+
vi.clearAllMocks()
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
afterEach(() => {
|
|
37
|
+
cleanup()
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('should redirect to /login when not authenticated', () => {
|
|
41
|
+
renderWithRouter(
|
|
42
|
+
<MerchantGuard>
|
|
43
|
+
<div>Protected Content</div>
|
|
44
|
+
</MerchantGuard>
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
expect(screen.getByTestId('login-page')).toBeInTheDocument()
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('should redirect to /login when merchant is null', () => {
|
|
51
|
+
mockMerchantState.isAuthenticated = true
|
|
52
|
+
mockMerchantState.merchant = null
|
|
53
|
+
|
|
54
|
+
renderWithRouter(
|
|
55
|
+
<MerchantGuard>
|
|
56
|
+
<div>Protected Content</div>
|
|
57
|
+
</MerchantGuard>
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
expect(screen.getByTestId('login-page')).toBeInTheDocument()
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('should render children when authenticated with merchant', () => {
|
|
64
|
+
mockMerchantState.isAuthenticated = true
|
|
65
|
+
mockMerchantState.merchant = { id: 'merchant-1', name: 'Test Merchant' }
|
|
66
|
+
|
|
67
|
+
renderWithRouter(
|
|
68
|
+
<MerchantGuard>
|
|
69
|
+
<div data-testid="protected">Protected Content</div>
|
|
70
|
+
</MerchantGuard>
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
expect(screen.getByTestId('protected')).toBeInTheDocument()
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('should render children when both isAuthenticated and merchant are set', () => {
|
|
77
|
+
mockMerchantState.isAuthenticated = true
|
|
78
|
+
mockMerchantState.merchant = { id: 'merchant-2', name: 'Another Merchant' }
|
|
79
|
+
|
|
80
|
+
renderWithRouter(
|
|
81
|
+
<MerchantGuard>
|
|
82
|
+
<div data-testid="protected">Protected Content</div>
|
|
83
|
+
</MerchantGuard>
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
expect(screen.getByTestId('protected')).toBeInTheDocument()
|
|
87
|
+
expect(screen.queryByTestId('login-page')).not.toBeInTheDocument()
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('should redirect to /login when authenticated but merchant is null', () => {
|
|
91
|
+
mockMerchantState.isAuthenticated = true
|
|
92
|
+
mockMerchantState.merchant = null
|
|
93
|
+
|
|
94
|
+
renderWithRouter(
|
|
95
|
+
<MerchantGuard>
|
|
96
|
+
<div data-testid="protected">Protected Content</div>
|
|
97
|
+
</MerchantGuard>
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
expect(screen.getByTestId('login-page')).toBeInTheDocument()
|
|
101
|
+
expect(screen.queryByTestId('protected')).not.toBeInTheDocument()
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it('should redirect to /login when merchant is set but not authenticated', () => {
|
|
105
|
+
mockMerchantState.isAuthenticated = false
|
|
106
|
+
mockMerchantState.merchant = { id: 'merchant-1', name: 'Test Merchant' }
|
|
107
|
+
|
|
108
|
+
renderWithRouter(
|
|
109
|
+
<MerchantGuard>
|
|
110
|
+
<div data-testid="protected">Protected Content</div>
|
|
111
|
+
</MerchantGuard>
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
expect(screen.getByTestId('login-page')).toBeInTheDocument()
|
|
115
|
+
expect(screen.queryByTestId('protected')).not.toBeInTheDocument()
|
|
116
|
+
})
|
|
117
|
+
})
|
|
@@ -11,7 +11,7 @@ interface HeaderProps {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export const Header: FC<HeaderProps> = ({ onToggle }) => {
|
|
14
|
-
const {
|
|
14
|
+
const { merchant, logout } = useMerchantStore()
|
|
15
15
|
|
|
16
16
|
const handleLogout = () => {
|
|
17
17
|
logout()
|
|
@@ -45,7 +45,7 @@ export const Header: FC<HeaderProps> = ({ onToggle }) => {
|
|
|
45
45
|
<Button type="text" icon={<MenuOutlined />} onClick={onToggle} />
|
|
46
46
|
|
|
47
47
|
<Space>
|
|
48
|
-
<Text>Welcome, {
|
|
48
|
+
<Text>Welcome, {merchant?.businessName || 'Merchant'}</Text>
|
|
49
49
|
<Dropdown menu={{ items }} placement="bottomRight">
|
|
50
50
|
<Avatar icon={<UserOutlined />} style={{ cursor: 'pointer' }} />
|
|
51
51
|
</Dropdown>
|
|
@@ -17,9 +17,9 @@ export const DashboardPage: FC = () => {
|
|
|
17
17
|
<Col span={8}>
|
|
18
18
|
<Card>
|
|
19
19
|
<Statistic
|
|
20
|
-
title="Total
|
|
20
|
+
title="Total Revenue"
|
|
21
21
|
prefix={<DollarOutlined />}
|
|
22
|
-
value={stats?.
|
|
22
|
+
value={stats?.totalRevenue || 0}
|
|
23
23
|
precision={2}
|
|
24
24
|
suffix="$"
|
|
25
25
|
/>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { FC, useState, useEffect } from 'react'
|
|
1
|
+
import { FC, useState, useEffect, useCallback } from 'react'
|
|
2
2
|
import { Table, Tag, Button, Space, Typography, Descriptions } from 'antd'
|
|
3
3
|
import { EyeOutlined, CheckOutlined, CloseOutlined } from '@ant-design/icons'
|
|
4
|
+
import { apiClient } from '@client/services/apiClient'
|
|
4
5
|
import type { Dispute, DisputeStatus } from '@shared/schemas'
|
|
5
6
|
|
|
6
7
|
const { Text } = Typography
|
|
@@ -11,11 +12,12 @@ export const DisputesPage: FC = () => {
|
|
|
11
12
|
const [selectedDispute, setSelectedDispute] = useState<Dispute | null>(null)
|
|
12
13
|
const setShowModal = useState(false)[1]
|
|
13
14
|
|
|
14
|
-
const fetchDisputes = async () => {
|
|
15
|
+
const fetchDisputes = useCallback(async () => {
|
|
15
16
|
setLoading(true)
|
|
16
17
|
try {
|
|
17
|
-
|
|
18
|
-
const
|
|
18
|
+
// @ts-expect-error - Hono type depth limit in full template with 15+ modules; resolves in generated projects
|
|
19
|
+
const response = await apiClient.api.merchant.disputes.$get()
|
|
20
|
+
const result = await response.json()
|
|
19
21
|
if (result.success === true && result.data) {
|
|
20
22
|
setDisputes(result.data)
|
|
21
23
|
}
|
|
@@ -24,7 +26,7 @@ export const DisputesPage: FC = () => {
|
|
|
24
26
|
} finally {
|
|
25
27
|
setLoading(false)
|
|
26
28
|
}
|
|
27
|
-
}
|
|
29
|
+
}, [])
|
|
28
30
|
|
|
29
31
|
const handleView = (dispute: Dispute) => {
|
|
30
32
|
setSelectedDispute(dispute)
|
|
@@ -33,8 +35,9 @@ export const DisputesPage: FC = () => {
|
|
|
33
35
|
|
|
34
36
|
const handleResolve = async (disputeId: string) => {
|
|
35
37
|
try {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
// @ts-expect-error - Hono type depth limit in full template with 15+ modules; resolves in generated projects
|
|
39
|
+
await apiClient.api.merchant.disputes[':id'].resolve.$post({
|
|
40
|
+
param: { id: disputeId },
|
|
38
41
|
})
|
|
39
42
|
fetchDisputes()
|
|
40
43
|
} catch (error) {
|
|
@@ -44,8 +47,9 @@ export const DisputesPage: FC = () => {
|
|
|
44
47
|
|
|
45
48
|
const handleClose = async (disputeId: string) => {
|
|
46
49
|
try {
|
|
47
|
-
|
|
48
|
-
|
|
50
|
+
// @ts-expect-error - Hono type depth limit in full template with 15+ modules; resolves in generated projects
|
|
51
|
+
await apiClient.api.merchant.disputes[':id'].close.$post({
|
|
52
|
+
param: { id: disputeId },
|
|
49
53
|
})
|
|
50
54
|
fetchDisputes()
|
|
51
55
|
} catch (error) {
|
|
@@ -55,7 +59,7 @@ export const DisputesPage: FC = () => {
|
|
|
55
59
|
|
|
56
60
|
useEffect(() => {
|
|
57
61
|
fetchDisputes()
|
|
58
|
-
}, [])
|
|
62
|
+
}, [fetchDisputes])
|
|
59
63
|
|
|
60
64
|
const getStatusColor = (status: DisputeStatus): string => {
|
|
61
65
|
const colors: Record<DisputeStatus, string> = {
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { FC } from 'react'
|
|
2
|
+
import { Form, Input, Button, Card, message } from 'antd'
|
|
3
|
+
import { UserOutlined, LockOutlined } from '@ant-design/icons'
|
|
4
|
+
import { useNavigate } from 'react-router-dom'
|
|
5
|
+
import { useMerchantStore } from '../stores/merchantStore'
|
|
6
|
+
|
|
7
|
+
export const LoginPage: FC = () => {
|
|
8
|
+
const navigate = useNavigate()
|
|
9
|
+
const login = useMerchantStore(state => state.login)
|
|
10
|
+
const loading = useMerchantStore(state => state.loading)
|
|
11
|
+
const error = useMerchantStore(state => state.error)
|
|
12
|
+
|
|
13
|
+
const handleSubmit = async (values: { username: string; password: string }) => {
|
|
14
|
+
await login(values.username, values.password)
|
|
15
|
+
|
|
16
|
+
const { isAuthenticated } = useMerchantStore.getState()
|
|
17
|
+
if (isAuthenticated) {
|
|
18
|
+
message.success('Login successful!')
|
|
19
|
+
navigate('/dashboard', { replace: true })
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div className="min-h-screen flex items-center justify-center bg-gray-100">
|
|
25
|
+
<Card className="w-full max-w-md" title="Merchant Login">
|
|
26
|
+
<Form onFinish={handleSubmit} layout="vertical" autoComplete="off">
|
|
27
|
+
<Form.Item
|
|
28
|
+
name="username"
|
|
29
|
+
rules={[{ required: true, message: 'Please input your username!' }]}
|
|
30
|
+
>
|
|
31
|
+
<Input prefix={<UserOutlined />} placeholder="Username" size="large" />
|
|
32
|
+
</Form.Item>
|
|
33
|
+
<Form.Item
|
|
34
|
+
name="password"
|
|
35
|
+
rules={[{ required: true, message: 'Please input your password!' }]}
|
|
36
|
+
>
|
|
37
|
+
<Input.Password prefix={<LockOutlined />} placeholder="Password" size="large" />
|
|
38
|
+
</Form.Item>
|
|
39
|
+
{error && <div className="mb-4 text-red-500 text-sm">{error}</div>}
|
|
40
|
+
<Form.Item>
|
|
41
|
+
<Button type="primary" htmlType="submit" size="large" block loading={loading}>
|
|
42
|
+
Login
|
|
43
|
+
</Button>
|
|
44
|
+
</Form.Item>
|
|
45
|
+
</Form>
|
|
46
|
+
</Card>
|
|
47
|
+
</div>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { FC, useState, useEffect } from 'react'
|
|
1
|
+
import { FC, useState, useEffect, useCallback } from 'react'
|
|
2
2
|
import { Select, Space, DatePicker, Button } from 'antd'
|
|
3
3
|
import { SearchOutlined } from '@ant-design/icons'
|
|
4
|
+
import { apiClient } from '@client/services/apiClient'
|
|
4
5
|
import type { Order } from '@shared/schemas'
|
|
5
6
|
import { OrderTable } from '../components/OrderTable'
|
|
6
7
|
|
|
@@ -10,13 +11,14 @@ export const OrdersPage: FC = () => {
|
|
|
10
11
|
const [orders, setOrders] = useState<Order[]>([])
|
|
11
12
|
const [loading, setLoading] = useState(false)
|
|
12
13
|
const setStatusFilter = useState<string>('all')[1]
|
|
13
|
-
const setDateRange = useState<[
|
|
14
|
+
const setDateRange = useState<[import('dayjs').Dayjs, import('dayjs').Dayjs] | null>(null)[1]
|
|
14
15
|
|
|
15
|
-
const fetchOrders = async () => {
|
|
16
|
+
const fetchOrders = useCallback(async () => {
|
|
16
17
|
setLoading(true)
|
|
17
18
|
try {
|
|
18
|
-
|
|
19
|
-
const
|
|
19
|
+
// @ts-expect-error - Hono type depth limit in full template with 15+ modules; resolves in generated projects
|
|
20
|
+
const response = await apiClient.api.merchant.orders.$get()
|
|
21
|
+
const result = await response.json()
|
|
20
22
|
if (result.success === true && result.data) {
|
|
21
23
|
setOrders(result.data)
|
|
22
24
|
}
|
|
@@ -25,15 +27,20 @@ export const OrdersPage: FC = () => {
|
|
|
25
27
|
} finally {
|
|
26
28
|
setLoading(false)
|
|
27
29
|
}
|
|
28
|
-
}
|
|
30
|
+
}, [])
|
|
29
31
|
|
|
30
32
|
const handleStatusChange = (value: string) => {
|
|
31
33
|
setStatusFilter(value)
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
const handleDateRangeChange = (
|
|
37
|
+
dates: [import('dayjs').Dayjs | null, import('dayjs').Dayjs | null] | null
|
|
38
|
+
) => {
|
|
39
|
+
if (dates && dates[0] && dates[1]) {
|
|
40
|
+
setDateRange([dates[0], dates[1]])
|
|
41
|
+
} else {
|
|
42
|
+
setDateRange(null)
|
|
43
|
+
}
|
|
37
44
|
}
|
|
38
45
|
|
|
39
46
|
const handleSearch = () => {
|
|
@@ -42,7 +49,7 @@ export const OrdersPage: FC = () => {
|
|
|
42
49
|
|
|
43
50
|
useEffect(() => {
|
|
44
51
|
fetchOrders()
|
|
45
|
-
}, [])
|
|
52
|
+
}, [fetchOrders])
|
|
46
53
|
|
|
47
54
|
return (
|
|
48
55
|
<div>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { FC, useState, useEffect } from 'react'
|
|
1
|
+
import { FC, useState, useEffect, useCallback } from 'react'
|
|
2
2
|
import { Table, Button, Space, Tag, Typography } from 'antd'
|
|
3
3
|
import { PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons'
|
|
4
|
+
import { apiClient } from '@client/services/apiClient'
|
|
4
5
|
import { ProductCard } from '../components/ProductCard'
|
|
5
6
|
import type { Product } from '@shared/schemas'
|
|
6
7
|
|
|
@@ -9,25 +10,32 @@ const { Title } = Typography
|
|
|
9
10
|
export const ProductsPage: FC = () => {
|
|
10
11
|
const [products, setProducts] = useState<Product[]>([])
|
|
11
12
|
const [loading, setLoading] = useState(false)
|
|
13
|
+
const [total, setTotal] = useState(0)
|
|
14
|
+
const [page, setPage] = useState(1)
|
|
15
|
+
const [pageSize, setPageSize] = useState(10)
|
|
12
16
|
|
|
13
|
-
const fetchProducts = async () => {
|
|
17
|
+
const fetchProducts = useCallback(async () => {
|
|
14
18
|
setLoading(true)
|
|
15
19
|
try {
|
|
16
|
-
|
|
17
|
-
const
|
|
20
|
+
// @ts-expect-error - Hono type depth limit in full template with 15+ modules; resolves in generated projects
|
|
21
|
+
const response = await apiClient.api.merchant.products.$get()
|
|
22
|
+
const result = await response.json()
|
|
18
23
|
if (result.success === true && result.data) {
|
|
19
|
-
setProducts(result.data)
|
|
24
|
+
setProducts(result.data.items)
|
|
25
|
+
setTotal(result.data.total)
|
|
26
|
+
setPage(result.data.page)
|
|
27
|
+
setPageSize(result.data.pageSize)
|
|
20
28
|
}
|
|
21
29
|
} catch (error) {
|
|
22
30
|
console.error('Failed to fetch products:', error)
|
|
23
31
|
} finally {
|
|
24
32
|
setLoading(false)
|
|
25
33
|
}
|
|
26
|
-
}
|
|
34
|
+
}, [])
|
|
27
35
|
|
|
28
36
|
useEffect(() => {
|
|
29
37
|
fetchProducts()
|
|
30
|
-
}, [])
|
|
38
|
+
}, [fetchProducts])
|
|
31
39
|
|
|
32
40
|
const columns = [
|
|
33
41
|
{
|
|
@@ -77,7 +85,22 @@ export const ProductsPage: FC = () => {
|
|
|
77
85
|
</Button>
|
|
78
86
|
</div>
|
|
79
87
|
<ProductCard products={products} loading={loading} />
|
|
80
|
-
<Table
|
|
88
|
+
<Table
|
|
89
|
+
dataSource={products}
|
|
90
|
+
columns={columns}
|
|
91
|
+
loading={loading}
|
|
92
|
+
rowKey="id"
|
|
93
|
+
pagination={{
|
|
94
|
+
current: page,
|
|
95
|
+
pageSize,
|
|
96
|
+
total,
|
|
97
|
+
showSizeChanger: true,
|
|
98
|
+
onChange: (newPage, newPageSize) => {
|
|
99
|
+
setPage(newPage)
|
|
100
|
+
setPageSize(newPageSize)
|
|
101
|
+
},
|
|
102
|
+
}}
|
|
103
|
+
/>
|
|
81
104
|
</div>
|
|
82
105
|
)
|
|
83
106
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { FC } from 'react'
|
|
2
2
|
import { Form, Input, Button, Card, Typography, Upload, message } from 'antd'
|
|
3
3
|
import { UploadOutlined } from '@ant-design/icons'
|
|
4
|
+
import { apiClient } from '@client/services/apiClient'
|
|
4
5
|
import type { UploadProps } from 'antd'
|
|
5
6
|
|
|
6
7
|
const { Title, Text } = Typography
|
|
@@ -10,13 +11,8 @@ export const SettingsPage: FC = () => {
|
|
|
10
11
|
|
|
11
12
|
const handleSave = async (values: unknown) => {
|
|
12
13
|
try {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
headers: {
|
|
16
|
-
'Content-Type': 'application/json',
|
|
17
|
-
},
|
|
18
|
-
body: JSON.stringify(values),
|
|
19
|
-
})
|
|
14
|
+
// @ts-expect-error - Hono type depth limit in full template with 15+ modules; resolves in generated projects
|
|
15
|
+
await apiClient.api.merchant.settings.$put({ json: values })
|
|
20
16
|
message.success('Settings saved successfully')
|
|
21
17
|
} catch {
|
|
22
18
|
message.error('Failed to save settings')
|