create-fullstack-scaffold 0.1.1 → 0.2.0

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 (155) hide show
  1. package/dist/cli/index.js +1673 -696
  2. package/dist/cli/index.js.map +1 -1
  3. package/package.json +16 -10
  4. package/template/.husky/pre-commit +1 -1
  5. package/template/modules.config.ts +29 -2
  6. package/template/package.json +12 -12
  7. package/template/patches/{typescript+5.8.3.patch → typescript+5.9.3.patch} +2 -4
  8. package/template/playwright.config.ts +7 -1
  9. package/template/src/admin/App.tsx +2 -2
  10. package/template/src/admin/components/CaptchaModal.tsx +7 -9
  11. package/template/src/admin/layouts/Header.tsx +56 -22
  12. package/template/src/admin/layouts/Layout.tsx +14 -9
  13. package/template/src/admin/layouts/Sidebar.tsx +122 -105
  14. package/template/src/admin/pages/CategoryManagementPage.tsx +241 -0
  15. package/template/src/admin/pages/ContentPage.tsx +2 -0
  16. package/template/src/admin/pages/DashboardPage.tsx +2 -0
  17. package/template/src/admin/pages/DisputesPage.tsx +2 -0
  18. package/template/src/admin/pages/MediaTestPage.tsx +1 -1
  19. package/template/src/admin/pages/OrdersPage.tsx +2 -0
  20. package/template/src/admin/pages/PluginDashboardPage.tsx +297 -0
  21. package/template/src/admin/pages/PluginManagementPage.tsx +340 -0
  22. package/template/src/admin/pages/PluginReviewPage.tsx +255 -0
  23. package/template/src/admin/pages/SystemLogsPage.tsx +11 -2
  24. package/template/src/admin/pages/TicketsPage.tsx +2 -0
  25. package/template/src/admin/pages/UsersPage.tsx +3 -2
  26. package/template/src/admin/stores/adminStore.ts +5 -0
  27. package/template/src/cli/index.ts +17 -19
  28. package/template/src/cli/modules/auth/index.ts +65 -0
  29. package/template/src/cli/modules/config/index.ts +74 -77
  30. package/template/src/cli/modules/index.ts +28 -6
  31. package/template/src/cli/modules/notification/index.ts +95 -79
  32. package/template/src/cli/modules/plugin/index.ts +111 -0
  33. package/template/src/cli/modules/todo/index.ts +99 -51
  34. package/template/src/cli/utils/auto-command.ts +7 -25
  35. package/template/src/cli/utils/index.ts +3 -1
  36. package/template/src/client/App.tsx +36 -16
  37. package/template/src/client/Layout.tsx +67 -10
  38. package/template/src/client/components/AuthButton.tsx +25 -18
  39. package/template/src/client/components/BottomTabBar.tsx +107 -0
  40. package/template/src/client/components/Navigation.tsx +162 -52
  41. package/template/src/client/components/__tests__/App.test.tsx +48 -32
  42. package/template/src/client/components/__tests__/AuthButton.test.tsx +78 -77
  43. package/template/src/client/components/__tests__/Navigation.test.tsx +31 -20
  44. package/template/src/client/components/index.ts +1 -0
  45. package/template/src/client/contexts/PresetContext.tsx +10 -0
  46. package/template/src/client/main.tsx +63 -8
  47. package/template/src/client/pages/CartPage.tsx +244 -0
  48. package/template/src/client/pages/CategoriesPage.tsx +100 -0
  49. package/template/src/client/pages/ContentDetailPage.tsx +9 -14
  50. package/template/src/client/pages/ContentListPage.tsx +4 -11
  51. package/template/src/client/pages/DashboardPage.tsx +261 -0
  52. package/template/src/client/pages/DeveloperDashboardPage.tsx +211 -0
  53. package/template/src/client/pages/LoginPage.tsx +127 -0
  54. package/template/src/client/pages/OrdersPage.tsx +196 -0
  55. package/template/src/client/pages/PluginDetailPage.tsx +345 -0
  56. package/template/src/client/pages/PluginsPage.tsx +223 -0
  57. package/template/src/client/pages/ProfilePage.tsx +206 -0
  58. package/template/src/client/pages/PublishPage.tsx +336 -0
  59. package/template/src/client/pages/RegisterPage.tsx +136 -0
  60. package/template/src/client/pages/SearchPage.tsx +204 -0
  61. package/template/src/client/pages/SettingsPage.tsx +220 -0
  62. package/template/src/client/pages/TopicsPage.tsx +180 -0
  63. package/template/src/client/pages/__tests__/LoginPage.test.tsx +170 -0
  64. package/template/src/client/pages/__tests__/RegisterPage.test.tsx +168 -0
  65. package/template/src/client/preset-ui-config.ts +492 -0
  66. package/template/src/client/services/apiClient.ts +14 -4
  67. package/template/src/client/stores/__tests__/authStore.test.ts +293 -38
  68. package/template/src/client/stores/__tests__/todoStore.test.ts +7 -17
  69. package/template/src/client/stores/authStore.ts +58 -5
  70. package/template/src/client/stores/chatWSStore.ts +9 -0
  71. package/template/src/client/stores/notificationStore.ts +4 -0
  72. package/template/src/client/stores/pluginStore.ts +279 -0
  73. package/template/src/client/stores/todoStore.ts +2 -6
  74. package/template/src/server/core/__tests__/isr-cache.test.ts +117 -0
  75. package/template/src/server/core/__tests__/isr-invalidation.test.ts +72 -0
  76. package/template/src/server/core/__tests__/ssr-renderer.test.ts +89 -0
  77. package/template/src/server/core/isr-cache.ts +239 -0
  78. package/template/src/server/core/isr-invalidation.ts +45 -0
  79. package/template/src/server/core/module-loader.ts +14 -7
  80. package/template/src/server/core/ssr-renderer.ts +240 -0
  81. package/template/src/server/db/init.ts +257 -10
  82. package/template/src/server/db/schema/developers.ts +20 -0
  83. package/template/src/server/db/schema/index.ts +2 -0
  84. package/template/src/server/db/schema/plugins.ts +114 -0
  85. package/template/src/server/db/test-setup.ts +91 -0
  86. package/template/src/server/entries/cloudflare.ts +79 -7
  87. package/template/src/server/entries/node.ts +48 -5
  88. package/template/src/server/middleware/__tests__/captcha.test.ts +23 -13
  89. package/template/src/server/middleware/auth.ts +8 -1
  90. package/template/src/server/middleware/captcha.ts +14 -4
  91. package/template/src/server/middleware/rate-limit.ts +7 -2
  92. package/template/src/server/module-admin/module.ts +9 -5
  93. package/template/src/server/module-admin/routes/admin-notification-routes.ts +43 -14
  94. package/template/src/server/module-admin/routes/admin-routes.ts +0 -2
  95. package/template/src/server/module-admin/routes/client-auth-routes.ts +90 -0
  96. package/template/src/server/module-admin/routes/dashboard-routes.ts +79 -0
  97. package/template/src/server/module-admin/services/admin-service.ts +68 -11
  98. package/template/src/server/module-auth/__tests__/auth-service.test.ts +239 -0
  99. package/template/src/server/module-auth/index.ts +7 -0
  100. package/template/src/server/module-auth/module.ts +40 -0
  101. package/template/src/server/module-auth/routes/auth-routes.ts +94 -0
  102. package/template/src/server/module-auth/routes/profile-routes.ts +31 -0
  103. package/template/src/server/module-auth/services/auth-service.ts +100 -0
  104. package/template/src/server/module-content/module.ts +10 -4
  105. package/template/src/server/module-content/routes/public-content-routes.ts +2 -2
  106. package/template/src/server/module-content/routes/topics-routes.ts +205 -0
  107. package/template/src/server/module-content/services/content-service.ts +18 -2
  108. package/template/src/server/module-dispute/routes/dispute-routes.ts +2 -1
  109. package/template/src/server/module-dispute/services/dispute-service.ts +1 -1
  110. package/template/src/server/module-notifications/__tests__/sse-rpc.test.ts +14 -14
  111. package/template/src/server/module-notifications/routes/notification-routes.ts +34 -8
  112. package/template/src/server/module-order/__tests__/order-route.test.ts +22 -2
  113. package/template/src/server/module-order/module.ts +15 -0
  114. package/template/src/server/module-order/routes/cart-routes.ts +103 -0
  115. package/template/src/server/module-order/routes/orders-mock-routes.ts +67 -0
  116. package/template/src/server/module-order/services/order-service.ts +1 -1
  117. package/template/src/server/module-plugin/__tests__/plugin-query-service.test.ts +203 -0
  118. package/template/src/server/module-plugin/__tests__/plugin-service.test.ts +234 -0
  119. package/template/src/server/module-plugin/index.ts +2 -0
  120. package/template/src/server/module-plugin/module.ts +52 -0
  121. package/template/src/server/module-plugin/routes/plugin-admin-routes.ts +261 -0
  122. package/template/src/server/module-plugin/routes/plugin-routes.ts +354 -0
  123. package/template/src/server/module-plugin/services/admin-category-service.ts +99 -0
  124. package/template/src/server/module-plugin/services/admin-plugin-service.ts +170 -0
  125. package/template/src/server/module-plugin/services/admin-stats-service.ts +41 -0
  126. package/template/src/server/module-plugin/services/plugin-query-service.ts +360 -0
  127. package/template/src/server/module-plugin/services/plugin-review-service.ts +95 -0
  128. package/template/src/server/module-plugin/services/plugin-service.ts +163 -0
  129. package/template/src/server/module-ticket/services/ticket-service.ts +1 -1
  130. package/template/src/server/module-todos/routes/todos-routes.ts +0 -4
  131. package/template/src/server/route-registry.ts +16 -1
  132. package/template/src/server/test-utils/test-client.ts +1 -2
  133. package/template/src/server/utils/auth.ts +6 -0
  134. package/template/src/server/utils/json.ts +13 -0
  135. package/template/src/shared/core/module-manifest.ts +3 -6
  136. package/template/src/shared/modules/auth/index.ts +12 -0
  137. package/template/src/shared/modules/auth/schemas.ts +50 -0
  138. package/template/src/shared/modules/cart/index.ts +1 -0
  139. package/template/src/shared/modules/cart/schemas.ts +41 -0
  140. package/template/src/shared/modules/community/index.ts +1 -0
  141. package/template/src/shared/modules/community/schemas.ts +58 -0
  142. package/template/src/shared/modules/dashboard/index.ts +1 -0
  143. package/template/src/shared/modules/dashboard/schemas.ts +35 -0
  144. package/template/src/shared/modules/index.ts +41 -0
  145. package/template/src/shared/modules/order/schemas.ts +29 -0
  146. package/template/src/shared/modules/plugins/index.ts +48 -0
  147. package/template/src/shared/modules/plugins/schemas.ts +227 -0
  148. package/template/src/shared/schemas/index.ts +130 -0
  149. package/template/tests/e2e/todo.spec.ts +23 -18
  150. package/template/tests/e2e/visual-screenshots.spec.ts +1824 -0
  151. package/template/uploads/.gitkeep +0 -0
  152. package/template/vite.config.ts +2 -1
  153. package/template/vitest.setup.ts +11 -0
  154. package/template/wrangler.toml +4 -3
  155. package/template/package-lock.json +0 -14554
@@ -0,0 +1,168 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest'
2
+ import { render, screen, fireEvent, waitFor } from '@testing-library/react'
3
+ import '@testing-library/jest-dom'
4
+ import { RegisterPage } from '../RegisterPage'
5
+ import { MemoryRouter } from 'react-router-dom'
6
+
7
+ interface MockAuthStore {
8
+ register: ReturnType<typeof vi.fn>
9
+ loading: boolean
10
+ error: string | null
11
+ clearError: ReturnType<typeof vi.fn>
12
+ }
13
+
14
+ const mockStore: MockAuthStore = {
15
+ register: vi.fn().mockResolvedValue(undefined),
16
+ loading: false,
17
+ error: null,
18
+ clearError: vi.fn(),
19
+ }
20
+
21
+ vi.mock('@client/stores/authStore', () => ({
22
+ useAuthStore: Object.assign(
23
+ vi.fn((selector?: (state: MockAuthStore) => unknown) => {
24
+ if (selector) {
25
+ return selector(mockStore)
26
+ }
27
+ return mockStore
28
+ }),
29
+ {
30
+ getState: () => mockStore,
31
+ }
32
+ ),
33
+ }))
34
+
35
+ const renderRegisterPage = () =>
36
+ render(
37
+ <MemoryRouter>
38
+ <RegisterPage />
39
+ </MemoryRouter>
40
+ )
41
+
42
+ describe('RegisterPage', () => {
43
+ beforeEach(() => {
44
+ vi.clearAllMocks()
45
+ mockStore.loading = false
46
+ mockStore.error = null
47
+ mockStore.register.mockResolvedValue(undefined)
48
+ })
49
+
50
+ describe('Initial Render', () => {
51
+ it('should render register page container', () => {
52
+ renderRegisterPage()
53
+ expect(screen.getByTestId('register-page')).toBeInTheDocument()
54
+ })
55
+
56
+ it('should render page title', () => {
57
+ renderRegisterPage()
58
+ const headings = screen.getAllByText('Create Account')
59
+ expect(headings[0]).toBeInTheDocument()
60
+ })
61
+
62
+ it('should render subtitle', () => {
63
+ renderRegisterPage()
64
+ expect(screen.getByText('Join Biomimic App today')).toBeInTheDocument()
65
+ })
66
+ })
67
+
68
+ describe('Form Inputs', () => {
69
+ it('should render username input', () => {
70
+ renderRegisterPage()
71
+ expect(screen.getByTestId('register-username')).toBeInTheDocument()
72
+ })
73
+
74
+ it('should render email input', () => {
75
+ renderRegisterPage()
76
+ expect(screen.getByTestId('register-email')).toBeInTheDocument()
77
+ })
78
+
79
+ it('should render password input', () => {
80
+ renderRegisterPage()
81
+ expect(screen.getByTestId('register-password')).toBeInTheDocument()
82
+ })
83
+
84
+ it('should render submit button', () => {
85
+ renderRegisterPage()
86
+ expect(screen.getByTestId('register-submit')).toBeInTheDocument()
87
+ })
88
+
89
+ it('should show Create Account text when not loading', () => {
90
+ renderRegisterPage()
91
+ expect(screen.getByTestId('register-submit')).toHaveTextContent('Create Account')
92
+ })
93
+
94
+ it('should show Creating account... text when loading', () => {
95
+ mockStore.loading = true
96
+ renderRegisterPage()
97
+ expect(screen.getByText('Creating account...')).toBeInTheDocument()
98
+ })
99
+
100
+ it('should disable submit button when loading', () => {
101
+ mockStore.loading = true
102
+ renderRegisterPage()
103
+ expect(screen.getByTestId('register-submit')).toBeDisabled()
104
+ })
105
+ })
106
+
107
+ describe('Form Submission', () => {
108
+ it('should call register with username, email, and password', async () => {
109
+ renderRegisterPage()
110
+ fireEvent.change(screen.getByTestId('register-username'), { target: { value: 'newuser' } })
111
+ fireEvent.change(screen.getByTestId('register-email'), { target: { value: 'new@test.com' } })
112
+ fireEvent.change(screen.getByTestId('register-password'), { target: { value: 'pass123' } })
113
+ fireEvent.click(screen.getByTestId('register-submit'))
114
+
115
+ await waitFor(() => {
116
+ expect(mockStore.register).toHaveBeenCalledWith('newuser', 'new@test.com', 'pass123')
117
+ })
118
+ })
119
+ })
120
+
121
+ describe('Error Display', () => {
122
+ it('should display error message when error exists', () => {
123
+ mockStore.error = 'Username already taken'
124
+ renderRegisterPage()
125
+ expect(screen.getByTestId('register-error')).toBeInTheDocument()
126
+ expect(screen.getByText('Username already taken')).toBeInTheDocument()
127
+ })
128
+
129
+ it('should not display error message when error is null', () => {
130
+ renderRegisterPage()
131
+ expect(screen.queryByTestId('register-error')).not.toBeInTheDocument()
132
+ })
133
+
134
+ it('should call clearError when typing in username field with existing error', () => {
135
+ mockStore.error = 'Some error'
136
+ renderRegisterPage()
137
+ fireEvent.change(screen.getByTestId('register-username'), { target: { value: 'a' } })
138
+ expect(mockStore.clearError).toHaveBeenCalled()
139
+ })
140
+
141
+ it('should call clearError when typing in email field with existing error', () => {
142
+ mockStore.error = 'Some error'
143
+ renderRegisterPage()
144
+ fireEvent.change(screen.getByTestId('register-email'), { target: { value: 'a@b.com' } })
145
+ expect(mockStore.clearError).toHaveBeenCalled()
146
+ })
147
+
148
+ it('should call clearError when typing in password field with existing error', () => {
149
+ mockStore.error = 'Some error'
150
+ renderRegisterPage()
151
+ fireEvent.change(screen.getByTestId('register-password'), { target: { value: 'a' } })
152
+ expect(mockStore.clearError).toHaveBeenCalled()
153
+ })
154
+ })
155
+
156
+ describe('Navigation Links', () => {
157
+ it('should render login link', () => {
158
+ renderRegisterPage()
159
+ expect(screen.getByTestId('register-login-link')).toBeInTheDocument()
160
+ })
161
+
162
+ it('should link to login page', () => {
163
+ renderRegisterPage()
164
+ const link = screen.getByTestId('register-login-link')
165
+ expect(link).toHaveAttribute('href', '/login')
166
+ })
167
+ })
168
+ })
@@ -0,0 +1,492 @@
1
+ import { lazy, type ComponentType } from 'react'
2
+
3
+ export type PresetType = 'todo' | 'plugin' | 'ecommerce' | 'community' | 'saas'
4
+
5
+ export type AppType = 'client' | 'admin'
6
+ export type LayoutType = 'top-nav' | 'minimal'
7
+ export type AuthStyle = 'buttons' | 'text-link' | 'icon' | 'avatar' | 'none'
8
+
9
+ // ClientNavItem is intentionally simpler than admin MenuItem (no permissions/children needed for client nav)
10
+ // eslint-disable-next-line local-rules/prefer-shared-types
11
+ export interface ClientNavItem {
12
+ label: string
13
+ icon: string
14
+ path: string
15
+ }
16
+
17
+ export type TabItem = ClientNavItem
18
+
19
+ export interface NavigationConfig {
20
+ visible: boolean
21
+ showLogo: boolean
22
+ showSearch: boolean
23
+ showCart: boolean
24
+ authStyle: AuthStyle
25
+ navItems: 'desktop' | 'none'
26
+ }
27
+
28
+ export interface PresetTheme {
29
+ primaryColor: string
30
+ primaryHover: string
31
+ bgColor: string
32
+ textColor: string
33
+ secondaryBg: string
34
+ borderColor: string
35
+ borderRadius: string
36
+ logoText: string
37
+ fontFamily: string
38
+ }
39
+
40
+ export interface RouteDef {
41
+ path: string
42
+ component: ComponentType<Record<string, unknown>> | null
43
+ label: string
44
+ }
45
+
46
+ export interface PresetUIConfig {
47
+ id: PresetType
48
+ name: string
49
+ appType: AppType
50
+ layout: LayoutType
51
+ theme: PresetTheme
52
+ navigation: NavigationConfig
53
+ desktopNav: ClientNavItem[]
54
+ mobileTabs: ClientNavItem[]
55
+ routes: RouteDef[]
56
+ defaultRoute: string
57
+ }
58
+
59
+ const TODO_THEME: PresetTheme = {
60
+ primaryColor: '#6366f1',
61
+ primaryHover: '#4f46e5',
62
+ bgColor: '#ffffff',
63
+ textColor: '#111827',
64
+ secondaryBg: '#f9fafb',
65
+ borderColor: '#e5e7eb',
66
+ borderRadius: '12px',
67
+ logoText: 'Biomimic',
68
+ fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
69
+ }
70
+
71
+ const PLUGIN_MARKET_THEME: PresetTheme = {
72
+ primaryColor: '#3b82f6',
73
+ primaryHover: '#2563eb',
74
+ bgColor: '#ffffff',
75
+ textColor: '#111827',
76
+ secondaryBg: '#f0f9ff',
77
+ borderColor: '#bae6fd',
78
+ borderRadius: '12px',
79
+ logoText: 'PluginHub',
80
+ fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
81
+ }
82
+
83
+ const ECOMMERCE_THEME: PresetTheme = {
84
+ primaryColor: '#f59e0b',
85
+ primaryHover: '#d97706',
86
+ bgColor: '#ffffff',
87
+ textColor: '#111827',
88
+ secondaryBg: '#fffbeb',
89
+ borderColor: '#fde68a',
90
+ borderRadius: '12px',
91
+ logoText: 'ShopMart',
92
+ fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
93
+ }
94
+
95
+ const COMMUNITY_THEME: PresetTheme = {
96
+ primaryColor: '#10b981',
97
+ primaryHover: '#059669',
98
+ bgColor: '#ffffff',
99
+ textColor: '#111827',
100
+ secondaryBg: '#ecfdf5',
101
+ borderColor: '#a7f3d0',
102
+ borderRadius: '12px',
103
+ logoText: 'Community',
104
+ fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
105
+ }
106
+
107
+ const SAAS_ADMIN_THEME: PresetTheme = {
108
+ primaryColor: '#1f2937',
109
+ primaryHover: '#374151',
110
+ bgColor: '#f9fafb',
111
+ textColor: '#111827',
112
+ secondaryBg: '#f3f4f6',
113
+ borderColor: '#e5e7eb',
114
+ borderRadius: '8px',
115
+ logoText: 'AdminPanel',
116
+ fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
117
+ }
118
+
119
+ export const PRESET_UI_CONFIGS: Record<PresetType, PresetUIConfig> = {
120
+ todo: {
121
+ id: 'todo',
122
+ name: 'Todo App',
123
+ appType: 'client',
124
+ layout: 'top-nav',
125
+ theme: TODO_THEME,
126
+ navigation: {
127
+ visible: true,
128
+ showLogo: true,
129
+ showSearch: false,
130
+ showCart: false,
131
+ authStyle: 'none',
132
+ navItems: 'desktop',
133
+ },
134
+ desktopNav: [
135
+ { label: 'Todos', icon: 'CheckSquare', path: '/todos' },
136
+ { label: 'SSE Demo', icon: 'Bell', path: '/notifications' },
137
+ { label: 'WebSocket', icon: 'Zap', path: '/websocket' },
138
+ ],
139
+ mobileTabs: [
140
+ { label: 'Todos', icon: 'CheckSquare', path: '/todos' },
141
+ { label: 'SSE', icon: 'Bell', path: '/notifications' },
142
+ { label: 'WS', icon: 'Zap', path: '/websocket' },
143
+ ],
144
+ defaultRoute: '/todos',
145
+ routes: [
146
+ {
147
+ path: '/login',
148
+ component: lazy(() => import('./pages/LoginPage').then(m => ({ default: m.LoginPage }))),
149
+ label: 'Login',
150
+ },
151
+ {
152
+ path: '/register',
153
+ component: lazy(() =>
154
+ import('./pages/RegisterPage').then(m => ({ default: m.RegisterPage }))
155
+ ),
156
+ label: 'Register',
157
+ },
158
+ {
159
+ path: '/todos',
160
+ component: lazy(() => import('./pages/TodoPage').then(m => ({ default: m.TodoPage }))),
161
+ label: 'Todos',
162
+ },
163
+ {
164
+ path: '/notifications',
165
+ component: lazy(() =>
166
+ import('./pages/NotificationPage').then(m => ({ default: m.NotificationPage }))
167
+ ),
168
+ label: 'Notifications',
169
+ },
170
+ {
171
+ path: '/websocket',
172
+ component: lazy(() =>
173
+ import('./pages/WebSocketPage').then(m => ({ default: m.WebSocketPage }))
174
+ ),
175
+ label: 'WebSocket',
176
+ },
177
+ ],
178
+ },
179
+
180
+ plugin: {
181
+ id: 'plugin',
182
+ name: 'Plugin Market',
183
+ appType: 'client',
184
+ layout: 'top-nav',
185
+ theme: PLUGIN_MARKET_THEME,
186
+ navigation: {
187
+ visible: true,
188
+ showLogo: true,
189
+ showSearch: true,
190
+ showCart: false,
191
+ authStyle: 'text-link',
192
+ navItems: 'desktop',
193
+ },
194
+ desktopNav: [
195
+ { label: 'Discover', icon: 'Compass', path: '/plugins' },
196
+ { label: 'Plugins', icon: 'Puzzle', path: '/plugins/list' },
197
+ { label: 'Categories', icon: 'Tags', path: '/categories' },
198
+ { label: 'Search', icon: 'Search', path: '/search' },
199
+ { label: 'Publish', icon: 'PlusCircle', path: '/publish' },
200
+ { label: 'Developer', icon: 'Code', path: '/developer' },
201
+ ],
202
+ mobileTabs: [
203
+ { label: 'Discover', icon: 'Compass', path: '/plugins' },
204
+ { label: 'Plugins', icon: 'Puzzle', path: '/plugins/list' },
205
+ { label: 'Categories', icon: 'Tags', path: '/categories' },
206
+ { label: 'Search', icon: 'Search', path: '/search' },
207
+ { label: 'My', icon: 'User', path: '/developer' },
208
+ ],
209
+ defaultRoute: '/plugins',
210
+ routes: [
211
+ {
212
+ path: '/',
213
+ component: lazy(() =>
214
+ import('./pages/PluginsPage').then(m => ({ default: m.PluginsPage }))
215
+ ),
216
+ label: 'Home',
217
+ },
218
+ {
219
+ path: '/login',
220
+ component: lazy(() => import('./pages/LoginPage').then(m => ({ default: m.LoginPage }))),
221
+ label: 'Login',
222
+ },
223
+ {
224
+ path: '/register',
225
+ component: lazy(() =>
226
+ import('./pages/RegisterPage').then(m => ({ default: m.RegisterPage }))
227
+ ),
228
+ label: 'Register',
229
+ },
230
+ {
231
+ path: '/plugins',
232
+ component: lazy(() =>
233
+ import('./pages/PluginsPage').then(m => ({ default: m.PluginsPage }))
234
+ ),
235
+ label: 'Plugins',
236
+ },
237
+ {
238
+ path: '/plugins/:slug',
239
+ component: lazy(() =>
240
+ import('./pages/PluginDetailPage').then(m => ({ default: m.PluginDetailPage }))
241
+ ),
242
+ label: 'Plugin Detail',
243
+ },
244
+ {
245
+ path: '/categories',
246
+ component: lazy(() =>
247
+ import('./pages/CategoriesPage').then(m => ({ default: m.CategoriesPage }))
248
+ ),
249
+ label: 'Categories',
250
+ },
251
+ {
252
+ path: '/search',
253
+ component: lazy(() => import('./pages/SearchPage').then(m => ({ default: m.SearchPage }))),
254
+ label: 'Search',
255
+ },
256
+ {
257
+ path: '/publish',
258
+ component: lazy(() =>
259
+ import('./pages/PublishPage').then(m => ({ default: m.PublishPage }))
260
+ ),
261
+ label: 'Publish',
262
+ },
263
+ {
264
+ path: '/developer',
265
+ component: lazy(() =>
266
+ import('./pages/DeveloperDashboardPage').then(m => ({
267
+ default: m.DeveloperDashboardPage,
268
+ }))
269
+ ),
270
+ label: 'Developer',
271
+ },
272
+ {
273
+ path: '/notifications',
274
+ component: lazy(() =>
275
+ import('./pages/NotificationPage').then(m => ({ default: m.NotificationPage }))
276
+ ),
277
+ label: 'Notifications',
278
+ },
279
+ ],
280
+ },
281
+
282
+ ecommerce: {
283
+ id: 'ecommerce',
284
+ name: 'E-Commerce',
285
+ appType: 'client',
286
+ layout: 'top-nav',
287
+ theme: ECOMMERCE_THEME,
288
+ navigation: {
289
+ visible: true,
290
+ showLogo: true,
291
+ showSearch: true,
292
+ showCart: true,
293
+ authStyle: 'icon',
294
+ navItems: 'desktop',
295
+ },
296
+ desktopNav: [
297
+ { label: 'Home', icon: 'Home', path: '/' },
298
+ { label: 'Products', icon: 'ShoppingBag', path: '/products' },
299
+ { label: 'Cart', icon: 'ShoppingCart', path: '/cart' },
300
+ { label: 'Orders', icon: 'Package', path: '/orders' },
301
+ { label: 'Account', icon: 'User', path: '/content' },
302
+ ],
303
+ mobileTabs: [
304
+ { label: 'Home', icon: 'Home', path: '/' },
305
+ { label: 'Products', icon: 'ShoppingBag', path: '/products' },
306
+ { label: 'Cart', icon: 'ShoppingCart', path: '/cart' },
307
+ { label: 'Orders', icon: 'Package', path: '/orders' },
308
+ { label: 'Me', icon: 'User', path: '/content' },
309
+ ],
310
+ defaultRoute: '/',
311
+ routes: [
312
+ {
313
+ path: '/',
314
+ component: lazy(() =>
315
+ import('./pages/ContentListPage').then(m => ({ default: m.ContentListPage }))
316
+ ),
317
+ label: 'Home',
318
+ },
319
+ {
320
+ path: '/products',
321
+ component: lazy(() =>
322
+ import('./pages/ContentListPage').then(m => ({ default: m.ContentListPage }))
323
+ ),
324
+ label: 'Products',
325
+ },
326
+ {
327
+ path: '/products/:id',
328
+ component: lazy(() =>
329
+ import('./pages/ContentDetailPage').then(m => ({ default: m.ContentDetailPage }))
330
+ ),
331
+ label: 'Product Detail',
332
+ },
333
+ {
334
+ path: '/cart',
335
+ component: lazy(() => import('./pages/CartPage').then(m => ({ default: m.CartPage }))),
336
+ label: 'Cart',
337
+ },
338
+ {
339
+ path: '/orders',
340
+ component: lazy(() => import('./pages/OrdersPage').then(m => ({ default: m.OrdersPage }))),
341
+ label: 'Orders',
342
+ },
343
+ {
344
+ path: '/content',
345
+ component: lazy(() =>
346
+ import('./pages/ContentListPage').then(m => ({ default: m.ContentListPage }))
347
+ ),
348
+ label: 'Content',
349
+ },
350
+ {
351
+ path: '/content/:id',
352
+ component: lazy(() =>
353
+ import('./pages/ContentDetailPage').then(m => ({ default: m.ContentDetailPage }))
354
+ ),
355
+ label: 'Content Detail',
356
+ },
357
+ {
358
+ path: '/login',
359
+ component: lazy(() => import('./pages/LoginPage').then(m => ({ default: m.LoginPage }))),
360
+ label: 'Login',
361
+ },
362
+ {
363
+ path: '/register',
364
+ component: lazy(() =>
365
+ import('./pages/RegisterPage').then(m => ({ default: m.RegisterPage }))
366
+ ),
367
+ label: 'Register',
368
+ },
369
+ ],
370
+ },
371
+
372
+ community: {
373
+ id: 'community',
374
+ name: 'Community',
375
+ appType: 'client',
376
+ layout: 'top-nav',
377
+ theme: COMMUNITY_THEME,
378
+ navigation: {
379
+ visible: true,
380
+ showLogo: true,
381
+ showSearch: false,
382
+ showCart: false,
383
+ authStyle: 'avatar',
384
+ navItems: 'desktop',
385
+ },
386
+ desktopNav: [
387
+ { label: 'Topics', icon: 'Hash', path: '/topics' },
388
+ { label: 'Popular', icon: 'TrendingUp', path: '/popular' },
389
+ { label: 'Notifications', icon: 'Bell', path: '/notifications' },
390
+ { label: 'Profile', icon: 'User', path: '/profile' },
391
+ ],
392
+ mobileTabs: [
393
+ { label: 'Topics', icon: 'Hash', path: '/topics' },
394
+ { label: 'Popular', icon: 'TrendingUp', path: '/popular' },
395
+ { label: 'Notifications', icon: 'Bell', path: '/notifications' },
396
+ { label: 'Me', icon: 'User', path: '/profile' },
397
+ ],
398
+ defaultRoute: '/topics',
399
+ routes: [
400
+ {
401
+ path: '/topics',
402
+ component: lazy(() => import('./pages/TopicsPage').then(m => ({ default: m.TopicsPage }))),
403
+ label: 'Topics',
404
+ },
405
+ {
406
+ path: '/popular',
407
+ component: lazy(() => import('./pages/TopicsPage').then(m => ({ default: m.TopicsPage }))),
408
+ label: 'Popular',
409
+ },
410
+ {
411
+ path: '/notifications',
412
+ component: lazy(() =>
413
+ import('./pages/NotificationPage').then(m => ({ default: m.NotificationPage }))
414
+ ),
415
+ label: 'Notifications',
416
+ },
417
+ {
418
+ path: '/profile',
419
+ component: lazy(() =>
420
+ import('./pages/ProfilePage').then(m => ({ default: m.ProfilePage }))
421
+ ),
422
+ label: 'Profile',
423
+ },
424
+ {
425
+ path: '/login',
426
+ component: lazy(() => import('./pages/LoginPage').then(m => ({ default: m.LoginPage }))),
427
+ label: 'Login',
428
+ },
429
+ {
430
+ path: '/register',
431
+ component: lazy(() =>
432
+ import('./pages/RegisterPage').then(m => ({ default: m.RegisterPage }))
433
+ ),
434
+ label: 'Register',
435
+ },
436
+ ],
437
+ },
438
+
439
+ saas: {
440
+ id: 'saas',
441
+ name: 'SaaS Admin',
442
+ appType: 'admin',
443
+ layout: 'minimal',
444
+ theme: SAAS_ADMIN_THEME,
445
+ navigation: {
446
+ visible: false,
447
+ showLogo: false,
448
+ showSearch: false,
449
+ showCart: false,
450
+ authStyle: 'none',
451
+ navItems: 'none',
452
+ },
453
+ desktopNav: [
454
+ { label: 'Dashboard', icon: 'LayoutDashboard', path: '/dashboard' },
455
+ { label: 'Settings', icon: 'Settings', path: '/settings' },
456
+ ],
457
+ mobileTabs: [
458
+ { label: 'Dashboard', icon: 'LayoutDashboard', path: '/dashboard' },
459
+ { label: 'Settings', icon: 'Settings', path: '/settings' },
460
+ ],
461
+ defaultRoute: '/dashboard',
462
+ routes: [
463
+ {
464
+ path: '/dashboard',
465
+ component: lazy(() =>
466
+ import('./pages/DashboardPage').then(m => ({ default: m.DashboardPage }))
467
+ ),
468
+ label: 'Dashboard',
469
+ },
470
+ {
471
+ path: '/settings',
472
+ component: lazy(() =>
473
+ import('./pages/SettingsPage').then(m => ({ default: m.SettingsPage }))
474
+ ),
475
+ label: 'Settings',
476
+ },
477
+ {
478
+ path: '/login',
479
+ component: lazy(() => import('./pages/LoginPage').then(m => ({ default: m.LoginPage }))),
480
+ label: 'Login',
481
+ },
482
+ ],
483
+ },
484
+ }
485
+
486
+ export function getPresetUIConfig(id: string): PresetUIConfig {
487
+ return PRESET_UI_CONFIGS[id as PresetType] ?? PRESET_UI_CONFIGS.todo
488
+ }
489
+
490
+ export function getPresetUIConfigs(): Record<PresetType, PresetUIConfig> {
491
+ return PRESET_UI_CONFIGS
492
+ }
@@ -38,10 +38,20 @@ const authenticatedFetch = (url: string | URL | Request, init?: RequestInit): Pr
38
38
  headers.set('Authorization', `Bearer ${token}`)
39
39
  }
40
40
 
41
- return window.fetch(url, {
42
- ...init,
43
- headers,
44
- })
41
+ return window
42
+ .fetch(url, {
43
+ ...init,
44
+ headers,
45
+ })
46
+ .then(response => {
47
+ if (response.status === 401) {
48
+ localStorage.removeItem('auth-token')
49
+ if (!window.location.pathname.includes('/login')) {
50
+ window.location.href = '/login'
51
+ }
52
+ }
53
+ return response
54
+ })
45
55
  }
46
56
 
47
57
  export const apiClient = hc<ClientApiType>(baseUrl, {