@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,318 @@
1
+ /**
2
+ * Settings page pattern template — settings page with tabs (profile, security, notifications).
3
+ */
4
+ export function settingsPageTemplate(pageName: string): string {
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,293 @@
1
+ /**
2
+ * Wizard page pattern template — multi-step form with progress bar.
3
+ */
4
+ export function wizardPageTemplate(pageName: string): string {
5
+ const componentName = `${pageName}Page`;
6
+ return `import { useState, useCallback } from 'react';
7
+ import { useNavigate } 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 { ArrowLeft, ArrowRight, Check, Save } from 'lucide-react';
12
+ import { useMutation, 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 { Textarea } from '@/components/ui/textarea';
20
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
21
+ import { Progress } from '@/components/ui/progress';
22
+ import { toast } from 'sonner';
23
+
24
+ interface ${componentName}Props {
25
+ workspaceId: string;
26
+ }
27
+
28
+ /** Steps definition */
29
+ const STEPS = [
30
+ { id: 'basics', label: 'Basics', description: 'Basic information' },
31
+ { id: 'details', label: 'Details', description: 'Additional details' },
32
+ { id: 'review', label: 'Review', description: 'Review and confirm' },
33
+ ] as const;
34
+
35
+ type StepId = (typeof STEPS)[number]['id'];
36
+
37
+ /** Full form schema across all steps */
38
+ const wizardSchema = z.object({
39
+ name: z.string().min(1, 'Name is required'),
40
+ description: z.string().optional(),
41
+ type: z.string().min(1, 'Type is required'),
42
+ status: z.enum(['active', 'inactive']),
43
+ notes: z.string().optional(),
44
+ });
45
+
46
+ type WizardValues = z.infer<typeof wizardSchema>;
47
+
48
+ export function ${componentName}({ workspaceId }: ${componentName}Props) {
49
+ const tib = useTib();
50
+ const navigate = useNavigate();
51
+ const queryClient = useQueryClient();
52
+ const [currentStep, setCurrentStep] = useState<number>(0);
53
+ const [completedSteps, setCompletedSteps] = useState<Set<number>>(new Set([0]));
54
+
55
+ const form = useForm<WizardValues>({
56
+ resolver: zodResolver(wizardSchema),
57
+ defaultValues: {
58
+ name: '',
59
+ description: '',
60
+ type: '',
61
+ status: 'active',
62
+ notes: '',
63
+ },
64
+ mode: 'onChange',
65
+ });
66
+
67
+ const { register, handleSubmit, formState: { errors }, setValue, watch, trigger } = form;
68
+
69
+ const mutation = useMutation({
70
+ mutationFn: (values: WizardValues) =>
71
+ tib.data.create({ workspaceId, ...values }),
72
+ onSuccess: () => {
73
+ toast.success('\${pageName} created successfully');
74
+ queryClient.invalidateQueries({ queryKey: ['data-list', workspaceId] });
75
+ navigate('..');
76
+ },
77
+ onError: (err) => {
78
+ toast.error('Failed to create', {
79
+ description: err instanceof Error ? err.message : 'Unknown error',
80
+ });
81
+ },
82
+ });
83
+
84
+ const progress = ((completedSteps.size) / STEPS.length) * 100;
85
+
86
+ const isFirstStep = currentStep === 0;
87
+ const isLastStep = currentStep === STEPS.length - 1;
88
+
89
+ const goNext = useCallback(async () => {
90
+ const fieldsToValidate = getFieldsForStep(currentStep);
91
+ const valid = await trigger(fieldsToValidate);
92
+ if (valid) {
93
+ const next = currentStep + 1;
94
+ setCompletedSteps(prev => new Set([...prev, next]));
95
+ setCurrentStep(next);
96
+ }
97
+ }, [currentStep, trigger]);
98
+
99
+ const goBack = useCallback(() => {
100
+ setCurrentStep(prev => Math.max(0, prev - 1));
101
+ }, []);
102
+
103
+ const onSubmit = (values: WizardValues) => mutation.mutate(values);
104
+
105
+ return (
106
+ <Page title="Create ${pageName}" description="Follow the steps to create a new entity">
107
+ <Page.Header
108
+ breadcrumb={[
109
+ { label: '${pageName}', to: '..' },
110
+ { label: 'Create' },
111
+ ]}
112
+ />
113
+ <Page.Body>
114
+ <div className="max-w-2xl mx-auto">
115
+ {/* Progress bar */}
116
+ <div className="mb-8">
117
+ <Progress value={progress} className="h-2" />
118
+ <div className="flex justify-between mt-2">
119
+ {STEPS.map((step, idx) => (
120
+ <div
121
+ key={step.id}
122
+ className={[
123
+ 'flex items-center gap-2 text-sm',
124
+ idx <= currentStep ? 'text-primary' : 'text-muted-foreground',
125
+ ].join(' ')}
126
+ >
127
+ <span className={[
128
+ 'flex h-6 w-6 items-center justify-center rounded-full text-xs font-medium',
129
+ completedSteps.has(idx)
130
+ ? 'bg-primary text-primary-foreground'
131
+ : 'border border-muted-foreground text-muted-foreground',
132
+ ].join(' ')}>
133
+ {completedSteps.has(idx) ? <Check className="h-3 w-3" /> : idx + 1}
134
+ </span>
135
+ <span className="hidden sm:inline">{step.label}</span>
136
+ </div>
137
+ ))}
138
+ </div>
139
+ </div>
140
+
141
+ <form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
142
+ {/* Step 1: Basics */}
143
+ {currentStep === 0 && (
144
+ <div className="space-y-4">
145
+ <h2 className="text-lg font-semibold">Basic Information</h2>
146
+ <div className="space-y-2">
147
+ <Label htmlFor="name">Name</Label>
148
+ <Input
149
+ id="name"
150
+ {...register('name')}
151
+ placeholder="Enter name"
152
+ aria-invalid={!!errors.name}
153
+ />
154
+ {errors.name && (
155
+ <p className="text-xs text-destructive">{errors.name.message}</p>
156
+ )}
157
+ </div>
158
+ <div className="space-y-2">
159
+ <Label htmlFor="description">Description</Label>
160
+ <Textarea
161
+ id="description"
162
+ {...register('description')}
163
+ placeholder="Optional description"
164
+ rows={3}
165
+ />
166
+ </div>
167
+ <div className="space-y-2">
168
+ <Label htmlFor="type">Type</Label>
169
+ <Input
170
+ id="type"
171
+ {...register('type')}
172
+ placeholder="Enter type"
173
+ aria-invalid={!!errors.type}
174
+ />
175
+ {errors.type && (
176
+ <p className="text-xs text-destructive">{errors.type.message}</p>
177
+ )}
178
+ </div>
179
+ </div>
180
+ )}
181
+
182
+ {/* Step 2: Details */}
183
+ {currentStep === 1 && (
184
+ <div className="space-y-4">
185
+ <h2 className="text-lg font-semibold">Additional Details</h2>
186
+ <div className="space-y-2">
187
+ <Label htmlFor="wiz-status">Status</Label>
188
+ <Select
189
+ value={watch('status')}
190
+ onValueChange={(value) => setValue('status', value as 'active' | 'inactive')}
191
+ >
192
+ <SelectTrigger id="wiz-status">
193
+ <SelectValue placeholder="Select status" />
194
+ </SelectTrigger>
195
+ <SelectContent>
196
+ <SelectItem value="active">Active</SelectItem>
197
+ <SelectItem value="inactive">Inactive</SelectItem>
198
+ </SelectContent>
199
+ </Select>
200
+ </div>
201
+ <div className="space-y-2">
202
+ <Label htmlFor="notes">Notes</Label>
203
+ <Textarea
204
+ id="notes"
205
+ {...register('notes')}
206
+ placeholder="Add any notes"
207
+ rows={3}
208
+ />
209
+ </div>
210
+ </div>
211
+ )}
212
+
213
+ {/* Step 3: Review */}
214
+ {currentStep === 2 && (
215
+ <div className="space-y-4">
216
+ <h2 className="text-lg font-semibold">Review & Confirm</h2>
217
+ <div className="rounded-lg border border-border p-4 space-y-3">
218
+ <div className="flex justify-between">
219
+ <span className="text-sm text-muted-foreground">Name</span>
220
+ <span className="text-sm font-medium">{watch('name') || '—'}</span>
221
+ </div>
222
+ <div className="flex justify-between">
223
+ <span className="text-sm text-muted-foreground">Description</span>
224
+ <span className="text-sm font-medium">{watch('description') || '—'}</span>
225
+ </div>
226
+ <div className="flex justify-between">
227
+ <span className="text-sm text-muted-foreground">Type</span>
228
+ <span className="text-sm font-medium">{watch('type') || '—'}</span>
229
+ </div>
230
+ <div className="flex justify-between">
231
+ <span className="text-sm text-muted-foreground">Status</span>
232
+ <span className="text-sm font-medium">{watch('status')}</span>
233
+ </div>
234
+ <div className="flex justify-between">
235
+ <span className="text-sm text-muted-foreground">Notes</span>
236
+ <span className="text-sm font-medium">{watch('notes') || '—'}</span>
237
+ </div>
238
+ </div>
239
+ <p className="text-xs text-muted-foreground">
240
+ Review the details above and click &ldquo;Create&rdquo; to finalize.
241
+ </p>
242
+ </div>
243
+ )}
244
+
245
+ {/* Navigation buttons */}
246
+ <div className="flex items-center justify-between pt-4 border-t border-border">
247
+ <Button
248
+ type="button"
249
+ variant="outline"
250
+ size="sm"
251
+ onClick={goBack}
252
+ disabled={isFirstStep}
253
+ >
254
+ <ArrowLeft className="h-4 w-4 mr-1" />
255
+ Back
256
+ </Button>
257
+ {!isLastStep ? (
258
+ <Button type="button" variant="teal" size="sm" onClick={goNext}>
259
+ Next
260
+ <ArrowRight className="h-4 w-4 ml-1" />
261
+ </Button>
262
+ ) : (
263
+ <Button
264
+ type="submit"
265
+ variant="teal"
266
+ size="sm"
267
+ disabled={mutation.isPending}
268
+ >
269
+ <Save className="h-4 w-4 mr-1" />
270
+ {mutation.isPending ? 'Creating...' : 'Create'}
271
+ </Button>
272
+ )}
273
+ </div>
274
+ </form>
275
+ </div>
276
+ </Page.Body>
277
+ </Page>
278
+ );
279
+ }
280
+
281
+ /** Returns the field names that belong to each step for validation. */
282
+ function getFieldsForStep(step: number): Array<keyof WizardValues> {
283
+ switch (step) {
284
+ case 0: return ['name', 'description', 'type'];
285
+ case 1: return ['status', 'notes'];
286
+ case 2: return []; // review step — already valid
287
+ default: return [];
288
+ }
289
+ }
290
+
291
+ export default ${componentName};
292
+ `;
293
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Audit-relay subscriber pattern template.
3
+ * Produces: subscriber that relays domain events to an audit event-store or external sink.
4
+ */
5
+
6
+ function camelCase(s: string): string {
7
+ return s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
8
+ }
9
+
10
+ export function auditRelayTemplate(domain: string, id: string, event: string): string {
11
+ const varName = camelCase(id);
12
+
13
+ return `import { defineSubscriber } from '@mettlecast/domain-runtime';
14
+
15
+ /**
16
+ * ${id} — Relays ${event} to the audit event-store for compliance.
17
+ *
18
+ * Captures every instance of ${event} and stores an immutable
19
+ * audit record including caller identity, timestamp, and event payload.
20
+ */
21
+ export const ${varName} = defineSubscriber({
22
+ id: '${domain}.${id}',
23
+ event: '${event}',
24
+ semverRange: '^1',
25
+ handler: async (event, ctx) => {
26
+ const auditRecord = {
27
+ pk: 'AUDIT#' + event.data.tenantId,
28
+ sk: 'EVENT#' + event.id,
29
+ eventType: event.type,
30
+ eventVersion: event.version,
31
+ payload: event.data,
32
+ timestamp: event.timestamp,
33
+ source: event.source,
34
+ correlationId: event.correlationId,
35
+ ttl: Math.floor(Date.now() / 1000) + 90 * 86400, // 90-day retention
36
+ };
37
+
38
+ // ── Write to audit store ─────────────────────────────────
39
+ await ctx.db.put(auditRecord);
40
+
41
+ // ── Optionally forward to external sink ──────────────────
42
+ if (ctx.integrations?.auditSink) {
43
+ await ctx.fetch(ctx.integrations.auditSink, {
44
+ method: 'POST',
45
+ body: JSON.stringify(auditRecord),
46
+ headers: { 'content-type': 'application/json' },
47
+ });
48
+ }
49
+
50
+ ctx.metrics?.putMetric('${id}.audit.relayed', 1, 'Count');
51
+ },
52
+ });
53
+ `;
54
+ }