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.
Files changed (127) hide show
  1. package/dist/cli/index.js +499 -100
  2. package/dist/cli/index.js.map +1 -1
  3. package/package.json +2 -2
  4. package/template/CLAUDE.md +18 -6
  5. package/template/drizzle/0004_add_merchants_products.sql +30 -0
  6. package/template/drizzle/meta/_journal.json +7 -0
  7. package/template/eslint-rules/module-boundary.js +6 -2
  8. package/template/eslint-rules/no-cross-module-service-import.js +105 -0
  9. package/template/eslint-rules/no-disable-type-safe-client.js +5 -0
  10. package/template/eslint.config.js +9 -0
  11. package/template/lint-scripts/config/project.config.ts +21 -0
  12. package/template/lint-scripts/validate-all.ts +48 -12
  13. package/template/lint-scripts/validators/index.ts +29 -0
  14. package/template/lint-scripts/validators/module-public-api.validator.ts +103 -0
  15. package/template/lint-scripts/validators/schema-uniqueness.validator.ts +147 -0
  16. package/template/modules.config.ts +2 -0
  17. package/template/package.json +2 -0
  18. package/template/playwright.config.ts +14 -0
  19. package/template/src/admin/App.tsx +40 -3
  20. package/template/src/admin/components/LanguageSwitcher.tsx +22 -0
  21. package/template/src/admin/components/NotificationDrawer.tsx +108 -38
  22. package/template/src/admin/components/StatsCard.tsx +3 -1
  23. package/template/src/admin/components/ThemeToggle.tsx +28 -0
  24. package/template/src/admin/hooks/useRoles.ts +2 -2
  25. package/template/src/admin/i18n/index.ts +18 -0
  26. package/template/src/admin/i18n/locales/en-US.json +381 -0
  27. package/template/src/admin/i18n/locales/zh-CN.json +381 -0
  28. package/template/src/admin/i18n/useLanguage.ts +21 -0
  29. package/template/src/admin/layouts/Header.tsx +37 -16
  30. package/template/src/admin/layouts/Layout.tsx +7 -3
  31. package/template/src/admin/layouts/Sidebar.tsx +70 -68
  32. package/template/src/admin/main.tsx +1 -0
  33. package/template/src/admin/pages/ContentPage.tsx +68 -56
  34. package/template/src/admin/pages/DashboardPage.tsx +82 -76
  35. package/template/src/admin/pages/DisputesPage.tsx +61 -53
  36. package/template/src/admin/pages/LoginPage.tsx +41 -33
  37. package/template/src/admin/pages/OrdersPage.tsx +69 -64
  38. package/template/src/admin/pages/PermissionsPage.tsx +10 -8
  39. package/template/src/admin/pages/PluginDashboardPage.tsx +3 -1
  40. package/template/src/admin/pages/PluginManagementPage.tsx +3 -1
  41. package/template/src/admin/pages/RegisterPage.tsx +18 -16
  42. package/template/src/admin/pages/RolesPage.tsx +51 -49
  43. package/template/src/admin/pages/SettingsPage.tsx +22 -22
  44. package/template/src/admin/pages/SystemLogsPage.tsx +32 -30
  45. package/template/src/admin/pages/TicketsPage.tsx +67 -59
  46. package/template/src/admin/pages/UsersPage.tsx +63 -53
  47. package/template/src/admin/pages/__tests__/RolesPage.test.tsx +2 -2
  48. package/template/src/admin/stores/themeStore.ts +32 -0
  49. package/template/src/client/index.css +73 -0
  50. package/template/src/merchant/App.tsx +2 -0
  51. package/template/src/merchant/components/MerchantGuard.tsx +2 -6
  52. package/template/src/merchant/components/__tests__/MerchantGuard.test.tsx +117 -0
  53. package/template/src/merchant/layouts/Header.tsx +2 -2
  54. package/template/src/merchant/pages/DashboardPage.tsx +2 -2
  55. package/template/src/merchant/pages/DisputesPage.tsx +14 -10
  56. package/template/src/merchant/pages/LoginPage.tsx +49 -0
  57. package/template/src/merchant/pages/OrdersPage.tsx +17 -10
  58. package/template/src/merchant/pages/ProductsPage.tsx +31 -8
  59. package/template/src/merchant/pages/SettingsPage.tsx +3 -7
  60. package/template/src/merchant/stores/__tests__/merchantStore.test.ts +317 -0
  61. package/template/src/merchant/stores/merchantStore.ts +24 -27
  62. package/template/src/server/db/schema/index.ts +2 -0
  63. package/template/src/server/db/schema/merchants.ts +26 -0
  64. package/template/src/server/db/schema/products.ts +25 -0
  65. package/template/src/server/db/test-setup.ts +237 -0
  66. package/template/src/server/middleware/__tests__/audit-log.test.ts +144 -0
  67. package/template/src/server/middleware/__tests__/cors.test.ts +84 -0
  68. package/template/src/server/middleware/__tests__/logger.test.ts +117 -0
  69. package/template/src/server/middleware/__tests__/rate-limit.test.ts +73 -0
  70. package/template/src/server/middleware/__tests__/realtime-env.test.ts +56 -0
  71. package/template/src/server/middleware/__tests__/tenant-isolation.test.ts +150 -0
  72. package/template/src/server/module-admin/__tests__/admin-routes.test.ts +1 -1
  73. package/template/src/server/module-admin/module.ts +4 -0
  74. package/template/src/server/module-admin/routes/admin-notification-routes.ts +3 -3
  75. package/template/src/server/module-admin/routes/user-management-routes.ts +2 -2
  76. package/template/src/server/module-auth/__tests__/auth-routes.test.ts +315 -0
  77. package/template/src/server/module-auth/__tests__/profile-routes.test.ts +83 -0
  78. package/template/src/server/module-auth/module.ts +2 -0
  79. package/template/src/server/module-content/module.ts +1 -0
  80. package/template/src/server/module-content/routes/content-routes.ts +2 -2
  81. package/template/src/server/module-dispute/module.ts +1 -0
  82. package/template/src/server/module-dispute/routes/dispute-routes.ts +2 -2
  83. package/template/src/server/module-merchant/__tests__/merchant-routes.test.ts +218 -0
  84. package/template/src/server/module-merchant/__tests__/merchant-service.test.ts +183 -0
  85. package/template/src/server/module-merchant/index.ts +10 -0
  86. package/template/src/server/module-merchant/module.ts +33 -0
  87. package/template/src/server/module-merchant/routes/merchant-routes.ts +138 -0
  88. package/template/src/server/module-merchant/services/merchant-service.ts +292 -0
  89. package/template/src/server/module-notifications/index.ts +18 -0
  90. package/template/src/server/module-notifications/module.ts +2 -0
  91. package/template/src/server/module-order/module.ts +1 -0
  92. package/template/src/server/module-order/routes/order-routes.ts +2 -2
  93. package/template/src/server/module-permission/routes/role-routes.ts +3 -3
  94. package/template/src/server/module-plugin/__tests__/admin-category-service.test.ts +181 -0
  95. package/template/src/server/module-plugin/__tests__/admin-plugin-service.test.ts +277 -0
  96. package/template/src/server/module-plugin/__tests__/admin-stats-service.test.ts +170 -0
  97. package/template/src/server/module-plugin/__tests__/plugin-review-service.test.ts +260 -0
  98. package/template/src/server/module-plugin/__tests__/plugin-seed-service.test.ts +170 -0
  99. package/template/src/server/module-plugin/module.ts +3 -0
  100. package/template/src/server/module-tenant/__tests__/tenant-routes.test.ts +142 -0
  101. package/template/src/server/module-tenant/__tests__/tenant-service.test.ts +152 -0
  102. package/template/src/server/module-tenant/module.ts +1 -0
  103. package/template/src/server/module-tenant/services/tenant-service.ts +1 -37
  104. package/template/src/server/module-ticket/module.ts +1 -0
  105. package/template/src/server/module-ticket/routes/ticket-routes.ts +2 -2
  106. package/template/src/server/module-todos/module.ts +3 -0
  107. package/template/src/server/route-registry.ts +4 -0
  108. package/template/src/server/utils/__tests__/date.test.ts +130 -0
  109. package/template/src/server/utils/__tests__/env.test.ts +25 -0
  110. package/template/src/server/utils/__tests__/generate.test.ts +82 -0
  111. package/template/src/server/utils/__tests__/id-helpers.test.ts +29 -0
  112. package/template/src/server/utils/__tests__/json.test.ts +49 -0
  113. package/template/src/server/utils/__tests__/permission-utils.test.ts +26 -0
  114. package/template/src/server/utils/__tests__/uuid.test.ts +25 -0
  115. package/template/src/shared/core/module-manifest.ts +15 -0
  116. package/template/src/shared/modules/admin/schemas.ts +1 -1
  117. package/template/src/shared/modules/content/schemas.ts +2 -2
  118. package/template/src/shared/modules/dispute/schemas.ts +2 -2
  119. package/template/src/shared/modules/index.ts +181 -1
  120. package/template/src/shared/modules/merchant/index.ts +12 -0
  121. package/template/src/shared/modules/merchant/schemas.ts +63 -0
  122. package/template/src/shared/modules/order/schemas.ts +2 -2
  123. package/template/src/shared/modules/permission/index.ts +1 -1
  124. package/template/src/shared/modules/permission/schemas.ts +1 -1
  125. package/template/src/shared/modules/role/schemas.ts +2 -2
  126. package/template/src/shared/modules/ticket/schemas.ts +2 -2
  127. 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, user } = useMerchantStore()
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 { user, logout } = useMerchantStore()
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, {user?.username || 'Merchant'}</Text>
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 Sales"
20
+ title="Total Revenue"
21
21
  prefix={<DollarOutlined />}
22
- value={stats?.totalSales || 0}
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
- const response = await fetch('/api/merchant/disputes')
18
- const result = (await response.json()) as { success?: boolean; data?: Dispute[] }
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
- await fetch(`/api/merchant/disputes/${disputeId}/resolve`, {
37
- method: 'POST',
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
- await fetch(`/api/merchant/disputes/${disputeId}/close`, {
48
- method: 'POST',
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<[Date, Date] | null>(null)[1]
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
- const response = await fetch('/api/merchant/orders')
19
- const result = (await response.json()) as { success?: boolean; data?: Order[] }
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
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Ant Design RangePicker types are complex
35
- const handleDateRangeChange = (dates: any) => {
36
- setDateRange(dates)
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
- const response = await fetch('/api/merchant/products')
17
- const result = (await response.json()) as { success?: boolean; data?: Product[] }
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 dataSource={products} columns={columns} loading={loading} rowKey="id" />
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
- await fetch('/api/merchant/settings', {
14
- method: 'PUT',
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')