@mettlecast/domain-cli 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 (301) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +133 -0
  3. package/dist/builder/build-registry.d.ts +20 -0
  4. package/dist/builder/build-registry.js +178 -0
  5. package/dist/builder/build-types.d.ts +10 -0
  6. package/dist/builder/build-types.js +32 -0
  7. package/dist/builder/load-domain-module-config.d.ts +12 -0
  8. package/dist/builder/load-domain-module-config.js +34 -0
  9. package/dist/builder/load-module.d.ts +26 -0
  10. package/dist/builder/load-module.js +114 -0
  11. package/dist/builder/zod-to-json.d.ts +10 -0
  12. package/dist/builder/zod-to-json.js +15 -0
  13. package/dist/cli.d.ts +2 -0
  14. package/dist/cli.js +338 -0
  15. package/dist/commands/add-api.d.ts +32 -0
  16. package/dist/commands/add-api.js +106 -0
  17. package/dist/commands/add-component.d.ts +15 -0
  18. package/dist/commands/add-component.js +78 -0
  19. package/dist/commands/add-domain.d.ts +21 -0
  20. package/dist/commands/add-domain.js +71 -0
  21. package/dist/commands/add-flow.d.ts +14 -0
  22. package/dist/commands/add-flow.js +108 -0
  23. package/dist/commands/add-hook.d.ts +15 -0
  24. package/dist/commands/add-hook.js +65 -0
  25. package/dist/commands/add-migration.d.ts +26 -0
  26. package/dist/commands/add-migration.js +93 -0
  27. package/dist/commands/add-module.d.ts +7 -0
  28. package/dist/commands/add-module.js +261 -0
  29. package/dist/commands/add-page.d.ts +13 -0
  30. package/dist/commands/add-page.js +170 -0
  31. package/dist/commands/add-seed-page.d.ts +5 -0
  32. package/dist/commands/add-seed-page.js +251 -0
  33. package/dist/commands/add-subscriber.d.ts +30 -0
  34. package/dist/commands/add-subscriber.js +87 -0
  35. package/dist/commands/add-test.d.ts +22 -0
  36. package/dist/commands/add-test.js +285 -0
  37. package/dist/commands/audit.d.ts +39 -0
  38. package/dist/commands/audit.js +171 -0
  39. package/dist/commands/build-catalog.d.ts +68 -0
  40. package/dist/commands/build-catalog.js +105 -0
  41. package/dist/commands/build-flows.d.ts +26 -0
  42. package/dist/commands/build-flows.js +174 -0
  43. package/dist/commands/build.d.ts +17 -0
  44. package/dist/commands/build.js +39 -0
  45. package/dist/commands/check-hashes.d.ts +38 -0
  46. package/dist/commands/check-hashes.js +97 -0
  47. package/dist/commands/create-project.d.ts +5 -0
  48. package/dist/commands/create-project.js +272 -0
  49. package/dist/commands/dev.d.ts +15 -0
  50. package/dist/commands/dev.js +27 -0
  51. package/dist/commands/doctor.d.ts +48 -0
  52. package/dist/commands/doctor.js +1301 -0
  53. package/dist/commands/explain.d.ts +34 -0
  54. package/dist/commands/explain.js +167 -0
  55. package/dist/commands/power-tune.d.ts +1 -0
  56. package/dist/commands/power-tune.js +87 -0
  57. package/dist/commands/reseed-page.d.ts +6 -0
  58. package/dist/commands/reseed-page.js +297 -0
  59. package/dist/commands/show-dns.d.ts +15 -0
  60. package/dist/commands/show-dns.js +101 -0
  61. package/dist/commands/show.d.ts +46 -0
  62. package/dist/commands/show.js +135 -0
  63. package/dist/commands/test.d.ts +18 -0
  64. package/dist/commands/test.js +56 -0
  65. package/dist/commands/upgrade-backend.d.ts +9 -0
  66. package/dist/commands/upgrade-backend.js +46 -0
  67. package/dist/commands/upgrade-event.d.ts +22 -0
  68. package/dist/commands/upgrade-event.js +111 -0
  69. package/dist/commands/upgrade-frontend.d.ts +5 -0
  70. package/dist/commands/upgrade-frontend.js +99 -0
  71. package/dist/commands/upgrade.d.ts +7 -0
  72. package/dist/commands/upgrade.js +495 -0
  73. package/dist/commands/validate.d.ts +30 -0
  74. package/dist/commands/validate.js +148 -0
  75. package/dist/runtime-stubs/hydrate-local-ctx.d.ts +49 -0
  76. package/dist/runtime-stubs/hydrate-local-ctx.js +45 -0
  77. package/dist/runtime-stubs/load-secrets.d.ts +11 -0
  78. package/dist/runtime-stubs/load-secrets.js +37 -0
  79. package/dist/server/api-server.d.ts +21 -0
  80. package/dist/server/api-server.js +14 -0
  81. package/dist/server/mount-routes.d.ts +19 -0
  82. package/dist/server/mount-routes.js +42 -0
  83. package/dist/templates/api-skeleton.d.ts +18 -0
  84. package/dist/templates/api-skeleton.js +69 -0
  85. package/dist/templates/claude-md.d.ts +4 -0
  86. package/dist/templates/claude-md.js +7 -0
  87. package/dist/templates/dashboard-pages/index.d.ts +101 -0
  88. package/dist/templates/dashboard-pages/index.js +22 -0
  89. package/dist/templates/domain-config.d.ts +10 -0
  90. package/dist/templates/domain-config.js +19 -0
  91. package/dist/templates/events-skeleton.d.ts +9 -0
  92. package/dist/templates/events-skeleton.js +23 -0
  93. package/dist/templates/patterns/api/create-with-event.d.ts +5 -0
  94. package/dist/templates/patterns/api/create-with-event.js +69 -0
  95. package/dist/templates/patterns/api/idempotent-mutation.d.ts +5 -0
  96. package/dist/templates/patterns/api/idempotent-mutation.js +99 -0
  97. package/dist/templates/patterns/api/paginated-list.d.ts +5 -0
  98. package/dist/templates/patterns/api/paginated-list.js +71 -0
  99. package/dist/templates/patterns/api/simple-crud.d.ts +6 -0
  100. package/dist/templates/patterns/api/simple-crud.js +95 -0
  101. package/dist/templates/patterns/api/system-admin.d.ts +5 -0
  102. package/dist/templates/patterns/api/system-admin.js +98 -0
  103. package/dist/templates/patterns/api/webhook-receiver-style.d.ts +5 -0
  104. package/dist/templates/patterns/api/webhook-receiver-style.js +125 -0
  105. package/dist/templates/patterns/component/index.d.ts +25 -0
  106. package/dist/templates/patterns/component/index.js +814 -0
  107. package/dist/templates/patterns/flow/autonomous-remediation.d.ts +5 -0
  108. package/dist/templates/patterns/flow/autonomous-remediation.js +98 -0
  109. package/dist/templates/patterns/flow/fan-out-reaction.d.ts +5 -0
  110. package/dist/templates/patterns/flow/fan-out-reaction.js +53 -0
  111. package/dist/templates/patterns/flow/human-in-loop.d.ts +5 -0
  112. package/dist/templates/patterns/flow/human-in-loop.js +90 -0
  113. package/dist/templates/patterns/flow/multi-step-orchestration.d.ts +5 -0
  114. package/dist/templates/patterns/flow/multi-step-orchestration.js +67 -0
  115. package/dist/templates/patterns/hook/index.d.ts +7 -0
  116. package/dist/templates/patterns/hook/index.js +378 -0
  117. package/dist/templates/patterns/migration/column-add.d.ts +5 -0
  118. package/dist/templates/patterns/migration/column-add.js +52 -0
  119. package/dist/templates/patterns/migration/event-store.d.ts +6 -0
  120. package/dist/templates/patterns/migration/event-store.js +97 -0
  121. package/dist/templates/patterns/migration/index.d.ts +5 -0
  122. package/dist/templates/patterns/migration/index.js +52 -0
  123. package/dist/templates/patterns/migration/rls-policy.d.ts +5 -0
  124. package/dist/templates/patterns/migration/rls-policy.js +78 -0
  125. package/dist/templates/patterns/migration/tenant-scoped-table.d.ts +6 -0
  126. package/dist/templates/patterns/migration/tenant-scoped-table.js +81 -0
  127. package/dist/templates/patterns/page/dashboard.d.ts +4 -0
  128. package/dist/templates/patterns/page/dashboard.js +164 -0
  129. package/dist/templates/patterns/page/detail.d.ts +4 -0
  130. package/dist/templates/patterns/page/detail.js +162 -0
  131. package/dist/templates/patterns/page/form.d.ts +4 -0
  132. package/dist/templates/patterns/page/form.js +192 -0
  133. package/dist/templates/patterns/page/index.d.ts +8 -0
  134. package/dist/templates/patterns/page/index.js +14 -0
  135. package/dist/templates/patterns/page/list.d.ts +4 -0
  136. package/dist/templates/patterns/page/list.js +138 -0
  137. package/dist/templates/patterns/page/settings.d.ts +4 -0
  138. package/dist/templates/patterns/page/settings.js +318 -0
  139. package/dist/templates/patterns/page/wizard.d.ts +4 -0
  140. package/dist/templates/patterns/page/wizard.js +293 -0
  141. package/dist/templates/patterns/subscriber/audit-relay.d.ts +5 -0
  142. package/dist/templates/patterns/subscriber/audit-relay.js +51 -0
  143. package/dist/templates/patterns/subscriber/cascade-deletion.d.ts +5 -0
  144. package/dist/templates/patterns/subscriber/cascade-deletion.js +60 -0
  145. package/dist/templates/patterns/subscriber/single-step-projection.d.ts +5 -0
  146. package/dist/templates/patterns/subscriber/single-step-projection.js +36 -0
  147. package/dist/templates/readme.d.ts +4 -0
  148. package/dist/templates/readme.js +7 -0
  149. package/dist/templates/subscriber-skeleton.d.ts +11 -0
  150. package/dist/templates/subscriber-skeleton.js +23 -0
  151. package/dist/utils/checksum.d.ts +18 -0
  152. package/dist/utils/checksum.js +39 -0
  153. package/dist/utils/file-helpers.d.ts +35 -0
  154. package/dist/utils/file-helpers.js +59 -0
  155. package/dist/utils/header-inject.d.ts +15 -0
  156. package/dist/utils/header-inject.js +93 -0
  157. package/dist/utils/install-file.d.ts +17 -0
  158. package/dist/utils/install-file.js +78 -0
  159. package/dist/utils/logger.d.ts +12 -0
  160. package/dist/utils/logger.js +24 -0
  161. package/dist/utils/manifest.d.ts +28 -0
  162. package/dist/utils/manifest.js +93 -0
  163. package/dist/utils/s3-fetch.d.ts +25 -0
  164. package/dist/utils/s3-fetch.js +52 -0
  165. package/dist/utils/scaffold-config.d.ts +95 -0
  166. package/dist/utils/scaffold-config.js +53 -0
  167. package/dist/utils/token-substitution.d.ts +35 -0
  168. package/dist/utils/token-substitution.js +52 -0
  169. package/dist/utils/zod-extractor.d.ts +10 -0
  170. package/dist/utils/zod-extractor.js +28 -0
  171. package/package.json +43 -0
  172. package/src/__tests__/build-registry.test.ts +115 -0
  173. package/src/__tests__/build-types.test.ts +100 -0
  174. package/src/__tests__/builder/walkDomainDir.test.ts +140 -0
  175. package/src/__tests__/commands/add-domain.test.ts +174 -0
  176. package/src/__tests__/commands/add-flow.test.ts +69 -0
  177. package/src/__tests__/commands/add-module.test.ts +207 -0
  178. package/src/__tests__/commands/build-flows.test.ts +154 -0
  179. package/src/__tests__/commands/check-hashes.test.ts +142 -0
  180. package/src/__tests__/commands/create-project.test.ts +712 -0
  181. package/src/__tests__/commands/upgrade.test.ts +762 -0
  182. package/src/__tests__/doctor.test.ts +291 -0
  183. package/src/__tests__/hydrate-local-ctx.test.ts +71 -0
  184. package/src/__tests__/load-domain-module-config.test.ts +10 -0
  185. package/src/__tests__/mount-routes.test.ts +100 -0
  186. package/src/__tests__/scaffold-src/part-a-layout.test.ts +46 -0
  187. package/src/__tests__/scripts/package-scaffold.test.ts +45 -0
  188. package/src/__tests__/utils/checksum.test.ts +90 -0
  189. package/src/__tests__/utils/header-inject.test.ts +89 -0
  190. package/src/__tests__/utils/install-file.test.ts +207 -0
  191. package/src/__tests__/utils/manifest.test.ts +229 -0
  192. package/src/__tests__/utils/token-substitution.test.ts +134 -0
  193. package/src/__tests__/validate.test.ts +18 -0
  194. package/src/builder/build-registry.ts +237 -0
  195. package/src/builder/build-types.ts +36 -0
  196. package/src/builder/load-domain-module-config.ts +43 -0
  197. package/src/builder/load-module.ts +133 -0
  198. package/src/builder/zod-to-json.ts +16 -0
  199. package/src/cli.ts +387 -0
  200. package/src/commands/add-api.ts +145 -0
  201. package/src/commands/add-component.ts +116 -0
  202. package/src/commands/add-domain.ts +100 -0
  203. package/src/commands/add-flow.ts +136 -0
  204. package/src/commands/add-hook.ts +100 -0
  205. package/src/commands/add-migration.ts +129 -0
  206. package/src/commands/add-module.ts +347 -0
  207. package/src/commands/add-page.ts +242 -0
  208. package/src/commands/add-seed-page.ts +338 -0
  209. package/src/commands/add-subscriber.ts +123 -0
  210. package/src/commands/add-test.ts +328 -0
  211. package/src/commands/audit.ts +217 -0
  212. package/src/commands/build-catalog.ts +181 -0
  213. package/src/commands/build-flows.ts +198 -0
  214. package/src/commands/build.ts +59 -0
  215. package/src/commands/check-hashes.ts +150 -0
  216. package/src/commands/create-project.ts +367 -0
  217. package/src/commands/dev.ts +47 -0
  218. package/src/commands/doctor.ts +1362 -0
  219. package/src/commands/explain.ts +209 -0
  220. package/src/commands/power-tune.ts +112 -0
  221. package/src/commands/reseed-page.ts +410 -0
  222. package/src/commands/show-dns.ts +139 -0
  223. package/src/commands/show.ts +179 -0
  224. package/src/commands/test.ts +91 -0
  225. package/src/commands/upgrade-backend.ts +72 -0
  226. package/src/commands/upgrade-event.ts +175 -0
  227. package/src/commands/upgrade-frontend.ts +135 -0
  228. package/src/commands/upgrade.ts +668 -0
  229. package/src/commands/validate.ts +208 -0
  230. package/src/runtime-stubs/hydrate-local-ctx.ts +102 -0
  231. package/src/runtime-stubs/load-secrets.ts +40 -0
  232. package/src/server/api-server.ts +34 -0
  233. package/src/server/mount-routes.ts +68 -0
  234. package/src/templates/api-skeleton.ts +72 -0
  235. package/src/templates/claude-md.ts +7 -0
  236. package/src/templates/dashboard-pages/account/api-keys.tsx +107 -0
  237. package/src/templates/dashboard-pages/account/audit-log.tsx +60 -0
  238. package/src/templates/dashboard-pages/account/index.ts +5 -0
  239. package/src/templates/dashboard-pages/account/members.tsx +107 -0
  240. package/src/templates/dashboard-pages/account/profile.tsx +53 -0
  241. package/src/templates/dashboard-pages/account/workspace-settings.tsx +64 -0
  242. package/src/templates/dashboard-pages/auth/accept-invitation.tsx +62 -0
  243. package/src/templates/dashboard-pages/auth/choose-org.tsx +66 -0
  244. package/src/templates/dashboard-pages/auth/forgot-password.tsx +69 -0
  245. package/src/templates/dashboard-pages/auth/index.ts +10 -0
  246. package/src/templates/dashboard-pages/auth/login.tsx +75 -0
  247. package/src/templates/dashboard-pages/auth/mfa-setup.tsx +56 -0
  248. package/src/templates/dashboard-pages/auth/mfa-verify.tsx +49 -0
  249. package/src/templates/dashboard-pages/auth/reset-password.tsx +64 -0
  250. package/src/templates/dashboard-pages/auth/sign-out.tsx +20 -0
  251. package/src/templates/dashboard-pages/auth/signup.tsx +71 -0
  252. package/src/templates/dashboard-pages/auth/verify-email.tsx +53 -0
  253. package/src/templates/dashboard-pages/index.ts +22 -0
  254. package/src/templates/dashboard-pages/shell/dashboard-home.tsx +41 -0
  255. package/src/templates/dashboard-pages/shell/forbidden.tsx +46 -0
  256. package/src/templates/dashboard-pages/shell/index.ts +5 -0
  257. package/src/templates/dashboard-pages/shell/maintenance.tsx +27 -0
  258. package/src/templates/dashboard-pages/shell/not-found.tsx +34 -0
  259. package/src/templates/dashboard-pages/shell/server-error.tsx +44 -0
  260. package/src/templates/domain-config.ts +20 -0
  261. package/src/templates/events-skeleton.ts +24 -0
  262. package/src/templates/patterns/api/create-with-event.ts +72 -0
  263. package/src/templates/patterns/api/idempotent-mutation.ts +102 -0
  264. package/src/templates/patterns/api/paginated-list.ts +74 -0
  265. package/src/templates/patterns/api/simple-crud.ts +100 -0
  266. package/src/templates/patterns/api/system-admin.ts +101 -0
  267. package/src/templates/patterns/api/webhook-receiver-style.ts +128 -0
  268. package/src/templates/patterns/component/index.ts +833 -0
  269. package/src/templates/patterns/flow/autonomous-remediation.ts +101 -0
  270. package/src/templates/patterns/flow/fan-out-reaction.ts +56 -0
  271. package/src/templates/patterns/flow/human-in-loop.ts +93 -0
  272. package/src/templates/patterns/flow/multi-step-orchestration.ts +70 -0
  273. package/src/templates/patterns/hook/index.ts +382 -0
  274. package/src/templates/patterns/migration/column-add.ts +54 -0
  275. package/src/templates/patterns/migration/event-store.ts +99 -0
  276. package/src/templates/patterns/migration/index.ts +54 -0
  277. package/src/templates/patterns/migration/rls-policy.ts +80 -0
  278. package/src/templates/patterns/migration/tenant-scoped-table.ts +83 -0
  279. package/src/templates/patterns/page/dashboard.ts +164 -0
  280. package/src/templates/patterns/page/detail.ts +162 -0
  281. package/src/templates/patterns/page/form.ts +192 -0
  282. package/src/templates/patterns/page/index.ts +17 -0
  283. package/src/templates/patterns/page/list.ts +138 -0
  284. package/src/templates/patterns/page/settings.ts +318 -0
  285. package/src/templates/patterns/page/wizard.ts +293 -0
  286. package/src/templates/patterns/subscriber/audit-relay.ts +54 -0
  287. package/src/templates/patterns/subscriber/cascade-deletion.ts +63 -0
  288. package/src/templates/patterns/subscriber/single-step-projection.ts +39 -0
  289. package/src/templates/readme.ts +7 -0
  290. package/src/templates/subscriber-skeleton.ts +28 -0
  291. package/src/utils/checksum.ts +43 -0
  292. package/src/utils/file-helpers.ts +91 -0
  293. package/src/utils/header-inject.ts +126 -0
  294. package/src/utils/install-file.ts +99 -0
  295. package/src/utils/logger.ts +26 -0
  296. package/src/utils/manifest.ts +137 -0
  297. package/src/utils/s3-fetch.ts +91 -0
  298. package/src/utils/scaffold-config.ts +172 -0
  299. package/src/utils/token-substitution.ts +80 -0
  300. package/src/utils/zod-extractor.ts +31 -0
  301. package/tsconfig.json +13 -0
@@ -0,0 +1,138 @@
1
+ /**
2
+ * List page pattern template — data table page with pagination, sorting, filtering.
3
+ */
4
+ export function listPageTemplate(pageName) {
5
+ const componentName = `${pageName}Page`;
6
+ return `import { useState, useCallback } from 'react';
7
+ import { useSearchParams } from 'react-router-dom';
8
+ import { Plus, Search, Filter } from 'lucide-react';
9
+ import { useQuery } from '@tanstack/react-query';
10
+ import { useTib } from '@/hooks/useTib';
11
+ import { Page } from '@/components/layout/Page';
12
+ import { PageHeader } from '@/components/layout/PageHeader';
13
+ import { DataTable } from '@/components/data/DataTable';
14
+ import { FilterBar } from '@/components/data/FilterBar';
15
+ import { Button } from '@/components/ui/button';
16
+ import { Input } from '@/components/ui/input';
17
+ import { EmptyState } from '@/components/state/EmptyState';
18
+ import { ErrorState } from '@/components/state/ErrorState';
19
+ import { LoadingState } from '@/components/state/LoadingState';
20
+ import type { ColumnDef } from '@tanstack/react-table';
21
+
22
+ interface ${componentName}Props {
23
+ workspaceId: string;
24
+ }
25
+
26
+ interface DataItem {
27
+ id: string;
28
+ name: string;
29
+ status: string;
30
+ createdAt: string;
31
+ updatedAt: string;
32
+ }
33
+
34
+ const FILTER_DEFS = [
35
+ { key: 'status', label: 'Status', type: 'select' as const, options: [
36
+ { value: 'active', label: 'Active' },
37
+ { value: 'inactive', label: 'Inactive' },
38
+ ]},
39
+ { key: 'search', label: 'Search', type: 'text' as const },
40
+ ];
41
+
42
+ const COLUMNS: ColumnDef<DataItem>[] = [
43
+ {
44
+ accessorKey: 'name',
45
+ header: 'Name',
46
+ enableSorting: true,
47
+ },
48
+ {
49
+ accessorKey: 'status',
50
+ header: 'Status',
51
+ enableSorting: true,
52
+ },
53
+ {
54
+ accessorKey: 'createdAt',
55
+ header: 'Created',
56
+ enableSorting: true,
57
+ },
58
+ {
59
+ accessorKey: 'updatedAt',
60
+ header: 'Updated',
61
+ enableSorting: true,
62
+ },
63
+ ];
64
+
65
+ export function ${componentName}({ workspaceId }: ${componentName}Props) {
66
+ const tib = useTib();
67
+ const [searchParams, setSearchParams] = useSearchParams();
68
+ const [filters, setFilters] = useState<Record<string, string | string[]>>({});
69
+
70
+ const { data, isLoading, isError, error, refetch } = useQuery({
71
+ queryKey: ['data-list', workspaceId, filters],
72
+ queryFn: () => tib.data.list({ workspaceId, ...filters }),
73
+ staleTime: 10_000,
74
+ });
75
+
76
+ const handleFilterChange = useCallback((next: Record<string, string | string[]>) => {
77
+ setFilters(next);
78
+ const params = new URLSearchParams();
79
+ for (const [key, value] of Object.entries(next)) {
80
+ if (Array.isArray(value)) {
81
+ params.set(key, value.join(','));
82
+ } else if (value) {
83
+ params.set(key, value);
84
+ }
85
+ }
86
+ setSearchParams(params, { replace: true });
87
+ }, [setSearchParams]);
88
+
89
+ return (
90
+ <Page title="${pageName}" description="Browse and manage your data">
91
+ <Page.Header
92
+ breadcrumb={[{ label: '${pageName}' }]}
93
+ actions={
94
+ <Button variant="teal" size="sm">
95
+ <Plus className="h-4 w-4 mr-1" />
96
+ New item
97
+ </Button>
98
+ }
99
+ />
100
+ <Page.Body>
101
+ <FilterBar
102
+ filters={FILTER_DEFS}
103
+ value={filters}
104
+ onChange={handleFilterChange}
105
+ className="mb-4"
106
+ />
107
+ {isLoading ? (
108
+ <LoadingState variant="page" />
109
+ ) : isError ? (
110
+ <ErrorState
111
+ message={error instanceof Error ? error.message : 'Failed to load data'}
112
+ onRetry={() => refetch()}
113
+ />
114
+ ) : !data?.items?.length ? (
115
+ <EmptyState
116
+ icon={Search}
117
+ title="No items found"
118
+ description="Create your first item to get started."
119
+ cta={<Button variant="teal">Create item</Button>}
120
+ />
121
+ ) : (
122
+ <DataTable
123
+ columns={COLUMNS}
124
+ data={data.items}
125
+ onRowClick={(row) => {/* navigate to detail */}}
126
+ />
127
+ )}
128
+ </Page.Body>
129
+ <Page.Footer>
130
+ {/* Pagination component goes here */}
131
+ </Page.Footer>
132
+ </Page>
133
+ );
134
+ }
135
+
136
+ export default ${componentName};
137
+ `;
138
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Settings page pattern template — settings page with tabs (profile, security, notifications).
3
+ */
4
+ export declare function settingsPageTemplate(pageName: string): string;
@@ -0,0 +1,318 @@
1
+ /**
2
+ * Settings page pattern template — settings page with tabs (profile, security, notifications).
3
+ */
4
+ export function settingsPageTemplate(pageName) {
5
+ const componentName = `${pageName}Page`;
6
+ return `import { useState, useCallback } from 'react';
7
+ import { useSearchParams } from 'react-router-dom';
8
+ import { useForm } from 'react-hook-form';
9
+ import { zodResolver } from '@hookform/resolvers/zod';
10
+ import { z } from 'zod';
11
+ import { Save, User, Shield, Bell } from 'lucide-react';
12
+ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
13
+ import { useTib } from '@/hooks/useTib';
14
+ import { Page } from '@/components/layout/Page';
15
+ import { PageHeader } from '@/components/layout/PageHeader';
16
+ import { Button } from '@/components/ui/button';
17
+ import { Input } from '@/components/ui/input';
18
+ import { Label } from '@/components/ui/label';
19
+ import { Switch } from '@/components/ui/switch';
20
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
21
+ import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs';
22
+ import { ErrorState } from '@/components/state/ErrorState';
23
+ import { LoadingState } from '@/components/state/LoadingState';
24
+ import { toast } from 'sonner';
25
+
26
+ const profileSchema = z.object({
27
+ displayName: z.string().min(1, 'Display name is required'),
28
+ email: z.string().email('Invalid email'),
29
+ });
30
+
31
+ const securitySchema = z.object({
32
+ mfaEnabled: z.boolean(),
33
+ sessionTimeout: z.string(),
34
+ });
35
+
36
+ const notificationsSchema = z.object({
37
+ emailAlerts: z.boolean(),
38
+ pushNotifications: z.boolean(),
39
+ weeklyDigest: z.boolean(),
40
+ });
41
+
42
+ type ProfileValues = z.infer<typeof profileSchema>;
43
+ type SecurityValues = z.infer<typeof securitySchema>;
44
+ type NotificationsValues = z.infer<typeof notificationsSchema>;
45
+
46
+ interface ${componentName}Props {
47
+ workspaceId: string;
48
+ }
49
+
50
+ export function ${componentName}({ workspaceId }: ${componentName}Props) {
51
+ const tib = useTib();
52
+ const queryClient = useQueryClient();
53
+ const [searchParams, setSearchParams] = useSearchParams();
54
+ const activeTab = searchParams.get('tab') ?? 'profile';
55
+
56
+ const { data: settings, isLoading, isError, refetch } = useQuery({
57
+ queryKey: ['settings', workspaceId],
58
+ queryFn: () => tib.settings.getProject({ projectShortcode: workspaceId }) as Promise<{
59
+ displayName: string;
60
+ email: string;
61
+ mfaEnabled: boolean;
62
+ sessionTimeout: string;
63
+ emailAlerts: boolean;
64
+ pushNotifications: boolean;
65
+ weeklyDigest: boolean;
66
+ }>,
67
+ staleTime: 60_000,
68
+ });
69
+
70
+ const updateMutation = useMutation({
71
+ mutationFn: (values: Record<string, unknown>) =>
72
+ tib.settings.updateProject({ projectShortcode: workspaceId, ...values }),
73
+ onSuccess: () => {
74
+ toast.success('Settings saved');
75
+ queryClient.invalidateQueries({ queryKey: ['settings', workspaceId] });
76
+ },
77
+ onError: (err) => {
78
+ toast.error('Failed to save settings', {
79
+ description: err instanceof Error ? err.message : 'Unknown error',
80
+ });
81
+ },
82
+ });
83
+
84
+ const setTab = (tab: string) => setSearchParams({ tab });
85
+
86
+ return (
87
+ <Page title="Settings" description="Manage your account and preferences">
88
+ <Page.Header
89
+ breadcrumb={[{ label: '${pageName}' }]}
90
+ />
91
+ <Page.Tabs items={[
92
+ { value: 'profile', label: 'Profile', to: 'profile' },
93
+ { value: 'security', label: 'Security', to: 'security' },
94
+ { value: 'notifications', label: 'Notifications', to: 'notifications' },
95
+ ]} />
96
+ <Page.Body>
97
+ {isLoading ? (
98
+ <LoadingState variant="page" />
99
+ ) : isError ? (
100
+ <ErrorState
101
+ message="Failed to load settings"
102
+ onRetry={() => refetch()}
103
+ />
104
+ ) : (
105
+ <Tabs value={activeTab} onValueChange={setTab}>
106
+ <TabsContent value="profile">
107
+ <SettingsProfileForm
108
+ defaultValues={settings}
109
+ onSave={(values) => updateMutation.mutate(values)}
110
+ isSaving={updateMutation.isPending}
111
+ />
112
+ </TabsContent>
113
+ <TabsContent value="security">
114
+ <SettingsSecurityForm
115
+ defaultValues={settings}
116
+ onSave={(values) => updateMutation.mutate(values)}
117
+ isSaving={updateMutation.isPending}
118
+ />
119
+ </TabsContent>
120
+ <TabsContent value="notifications">
121
+ <SettingsNotificationsForm
122
+ defaultValues={settings}
123
+ onSave={(values) => updateMutation.mutate(values)}
124
+ isSaving={updateMutation.isPending}
125
+ />
126
+ </TabsContent>
127
+ </Tabs>
128
+ )}
129
+ </Page.Body>
130
+ </Page>
131
+ );
132
+ }
133
+
134
+ function SettingsProfileForm({
135
+ defaultValues,
136
+ onSave,
137
+ isSaving,
138
+ }: {
139
+ defaultValues?: ProfileValues;
140
+ onSave: (values: ProfileValues) => void;
141
+ isSaving: boolean;
142
+ }) {
143
+ const { register, handleSubmit, formState: { errors } } = useForm<ProfileValues>({
144
+ resolver: zodResolver(profileSchema),
145
+ defaultValues: defaultValues ?? { displayName: '', email: '' },
146
+ });
147
+
148
+ return (
149
+ <form onSubmit={handleSubmit(onSave)} className="max-w-xl space-y-6">
150
+ <Card>
151
+ <CardHeader>
152
+ <CardTitle className="flex items-center gap-2">
153
+ <User className="h-5 w-5" />
154
+ Profile
155
+ </CardTitle>
156
+ <CardDescription>Update your profile information.</CardDescription>
157
+ </CardHeader>
158
+ <CardContent className="space-y-4">
159
+ <div className="space-y-2">
160
+ <Label htmlFor="displayName">Display Name</Label>
161
+ <Input
162
+ id="displayName"
163
+ {...register('displayName')}
164
+ aria-invalid={!!errors.displayName}
165
+ />
166
+ {errors.displayName && (
167
+ <p className="text-xs text-destructive">{errors.displayName.message}</p>
168
+ )}
169
+ </div>
170
+ <div className="space-y-2">
171
+ <Label htmlFor="email">Email</Label>
172
+ <Input
173
+ id="email"
174
+ type="email"
175
+ {...register('email')}
176
+ aria-invalid={!!errors.email}
177
+ />
178
+ {errors.email && (
179
+ <p className="text-xs text-destructive">{errors.email.message}</p>
180
+ )}
181
+ </div>
182
+ <Button type="submit" variant="teal" disabled={isSaving}>
183
+ <Save className="h-4 w-4 mr-1" />
184
+ {isSaving ? 'Saving...' : 'Save'}
185
+ </Button>
186
+ </CardContent>
187
+ </Card>
188
+ </form>
189
+ );
190
+ }
191
+
192
+ function SettingsSecurityForm({
193
+ defaultValues,
194
+ onSave,
195
+ isSaving,
196
+ }: {
197
+ defaultValues?: SecurityValues;
198
+ onSave: (values: SecurityValues) => void;
199
+ isSaving: boolean;
200
+ }) {
201
+ const { register, handleSubmit, setValue, watch } = useForm<SecurityValues>({
202
+ defaultValues: defaultValues ?? { mfaEnabled: false, sessionTimeout: '3600' },
203
+ });
204
+
205
+ return (
206
+ <form onSubmit={handleSubmit(onSave)} className="max-w-xl space-y-6">
207
+ <Card>
208
+ <CardHeader>
209
+ <CardTitle className="flex items-center gap-2">
210
+ <Shield className="h-5 w-5" />
211
+ Security
212
+ </CardTitle>
213
+ <CardDescription>Manage your security settings.</CardDescription>
214
+ </CardHeader>
215
+ <CardContent className="space-y-4">
216
+ <div className="flex items-center justify-between">
217
+ <div>
218
+ <Label htmlFor="mfaEnabled">Two-Factor Authentication</Label>
219
+ <p className="text-xs text-muted-foreground">Require MFA for login.</p>
220
+ </div>
221
+ <Switch
222
+ id="mfaEnabled"
223
+ checked={watch('mfaEnabled')}
224
+ onCheckedChange={(checked) => setValue('mfaEnabled', checked)}
225
+ />
226
+ </div>
227
+ <div className="space-y-2">
228
+ <Label htmlFor="sessionTimeout">Session Timeout (seconds)</Label>
229
+ <Input
230
+ id="sessionTimeout"
231
+ {...register('sessionTimeout')}
232
+ type="number"
233
+ />
234
+ </div>
235
+ <Button type="submit" variant="teal" disabled={isSaving}>
236
+ <Save className="h-4 w-4 mr-1" />
237
+ {isSaving ? 'Saving...' : 'Save'}
238
+ </Button>
239
+ </CardContent>
240
+ </Card>
241
+ </form>
242
+ );
243
+ }
244
+
245
+ function SettingsNotificationsForm({
246
+ defaultValues,
247
+ onSave,
248
+ isSaving,
249
+ }: {
250
+ defaultValues?: NotificationsValues;
251
+ onSave: (values: NotificationsValues) => void;
252
+ isSaving: boolean;
253
+ }) {
254
+ const { handleSubmit, setValue, watch } = useForm<NotificationsValues>({
255
+ defaultValues: defaultValues ?? {
256
+ emailAlerts: true,
257
+ pushNotifications: false,
258
+ weeklyDigest: true,
259
+ },
260
+ });
261
+
262
+ return (
263
+ <form onSubmit={handleSubmit(onSave)} className="max-w-xl space-y-6">
264
+ <Card>
265
+ <CardHeader>
266
+ <CardTitle className="flex items-center gap-2">
267
+ <Bell className="h-5 w-5" />
268
+ Notifications
269
+ </CardTitle>
270
+ <CardDescription>Configure how you receive notifications.</CardDescription>
271
+ </CardHeader>
272
+ <CardContent className="space-y-4">
273
+ <div className="flex items-center justify-between">
274
+ <div>
275
+ <Label htmlFor="emailAlerts">Email Alerts</Label>
276
+ <p className="text-xs text-muted-foreground">Receive email notifications.</p>
277
+ </div>
278
+ <Switch
279
+ id="emailAlerts"
280
+ checked={watch('emailAlerts')}
281
+ onCheckedChange={(checked) => setValue('emailAlerts', checked)}
282
+ />
283
+ </div>
284
+ <div className="flex items-center justify-between">
285
+ <div>
286
+ <Label htmlFor="pushNotifications">Push Notifications</Label>
287
+ <p className="text-xs text-muted-foreground">Receive push notifications.</p>
288
+ </div>
289
+ <Switch
290
+ id="pushNotifications"
291
+ checked={watch('pushNotifications')}
292
+ onCheckedChange={(checked) => setValue('pushNotifications', checked)}
293
+ />
294
+ </div>
295
+ <div className="flex items-center justify-between">
296
+ <div>
297
+ <Label htmlFor="weeklyDigest">Weekly Digest</Label>
298
+ <p className="text-xs text-muted-foreground">Receive a weekly summary email.</p>
299
+ </div>
300
+ <Switch
301
+ id="weeklyDigest"
302
+ checked={watch('weeklyDigest')}
303
+ onCheckedChange={(checked) => setValue('weeklyDigest', checked)}
304
+ />
305
+ </div>
306
+ <Button type="submit" variant="teal" disabled={isSaving}>
307
+ <Save className="h-4 w-4 mr-1" />
308
+ {isSaving ? 'Saving...' : 'Save'}
309
+ </Button>
310
+ </CardContent>
311
+ </Card>
312
+ </form>
313
+ );
314
+ }
315
+
316
+ export default ${componentName};
317
+ `;
318
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Wizard page pattern template — multi-step form with progress bar.
3
+ */
4
+ export declare function wizardPageTemplate(pageName: string): string;