@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,60 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { useApi } from '@mettlecast/sdk';
3
+ import { useProject } from '@mettlecast/dashboard-shell';
4
+ import { TibFormError } from '@mettlecast/design-system';
5
+
6
+ interface ActivityEvent {
7
+ id: string;
8
+ type: string;
9
+ actor: string;
10
+ summary: string;
11
+ createdAt: string;
12
+ }
13
+
14
+ export function AuditLogPage() {
15
+ const tib = useApi();
16
+ const { workspaceId } = useProject();
17
+ const [events, setEvents] = useState<ActivityEvent[]>([]);
18
+ const [error, setError] = useState<string | null>(null);
19
+ const [loading, setLoading] = useState(true);
20
+
21
+ useEffect(() => {
22
+ if (!workspaceId) return;
23
+ tib?.listActivity(workspaceId, { limit: 50 })
24
+ .then(res => setEvents((res as any)?.items ?? []))
25
+ .catch(err => setError(err instanceof Error ? err.message : 'Failed to load audit log'))
26
+ .finally(() => setLoading(false));
27
+ }, [tib, workspaceId]);
28
+
29
+ return (
30
+ <div className="p-6 max-w-3xl space-y-6">
31
+ <div>
32
+ <h1 className="text-2xl font-semibold tracking-tight">Audit log</h1>
33
+ <p className="text-sm text-muted-foreground mt-1">Recent activity in this workspace</p>
34
+ </div>
35
+ <TibFormError error={error} />
36
+ {loading ? (
37
+ <p className="text-sm text-muted-foreground">Loading…</p>
38
+ ) : events.length === 0 ? (
39
+ <p className="text-sm text-muted-foreground py-8 text-center">No activity yet</p>
40
+ ) : (
41
+ <ol className="space-y-3" aria-label="Audit log">
42
+ {events.map(ev => (
43
+ <li key={ev.id} className="flex gap-4 text-sm">
44
+ <time dateTime={ev.createdAt} className="w-36 shrink-0 text-xs text-muted-foreground pt-0.5">
45
+ {new Date(ev.createdAt).toLocaleString()}
46
+ </time>
47
+ <div>
48
+ <span className="font-medium">{ev.actor}</span>
49
+ {' '}
50
+ <span className="text-muted-foreground">{ev.summary}</span>
51
+ </div>
52
+ </li>
53
+ ))}
54
+ </ol>
55
+ )}
56
+ </div>
57
+ );
58
+ }
59
+
60
+ export default AuditLogPage;
@@ -0,0 +1,5 @@
1
+ export { ProfilePage } from './profile';
2
+ export { WorkspaceSettingsPage } from './workspace-settings';
3
+ export { MembersPage } from './members';
4
+ export { ApiKeysPage } from './api-keys';
5
+ export { AuditLogPage } from './audit-log';
@@ -0,0 +1,107 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { useApi } from '@mettlecast/sdk';
3
+ import { useProject } from '@mettlecast/dashboard-shell';
4
+ import { TibFormError, TibSubmitButton } from '@mettlecast/design-system';
5
+
6
+ interface Member {
7
+ userId: string;
8
+ email: string;
9
+ name?: string;
10
+ role: string;
11
+ }
12
+
13
+ export function MembersPage() {
14
+ const tib = useApi();
15
+ const { workspaceId } = useProject();
16
+ const [members, setMembers] = useState<Member[]>([]);
17
+ const [inviteEmail, setInviteEmail] = useState('');
18
+ const [inviteRole, setInviteRole] = useState('member');
19
+ const [error, setError] = useState<string | null>(null);
20
+ const [loading, setLoading] = useState(true);
21
+ const [inviting, setInviting] = useState(false);
22
+
23
+ useEffect(() => {
24
+ if (!workspaceId) return;
25
+ tib?.listMembers(workspaceId)
26
+ .then(res => setMembers((res as any)?.items ?? []))
27
+ .catch(err => setError(err instanceof Error ? err.message : 'Failed to load members'))
28
+ .finally(() => setLoading(false));
29
+ }, [tib, workspaceId]);
30
+
31
+ async function handleInvite(e: React.FormEvent) {
32
+ e.preventDefault();
33
+ if (!workspaceId) return;
34
+ setInviting(true);
35
+ setError(null);
36
+ try {
37
+ await tib?.inviteMember(workspaceId, { email: inviteEmail, role: inviteRole });
38
+ setInviteEmail('');
39
+ } catch (err) {
40
+ setError(err instanceof Error ? err.message : 'Invite failed');
41
+ } finally {
42
+ setInviting(false);
43
+ }
44
+ }
45
+
46
+ return (
47
+ <div className="p-6 max-w-3xl space-y-8">
48
+ <div>
49
+ <h1 className="text-2xl font-semibold tracking-tight">Members</h1>
50
+ <p className="text-sm text-muted-foreground mt-1">Manage workspace members and roles</p>
51
+ </div>
52
+
53
+ <form onSubmit={handleInvite} className="flex gap-2 items-end">
54
+ <div className="flex-1">
55
+ <label htmlFor="inviteEmail" className="text-sm font-medium">Email</label>
56
+ <input id="inviteEmail" type="email" value={inviteEmail} onChange={e => setInviteEmail(e.target.value)}
57
+ className="mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
58
+ placeholder="colleague@company.com" required />
59
+ </div>
60
+ <div>
61
+ <label htmlFor="inviteRole" className="text-sm font-medium">Role</label>
62
+ <select id="inviteRole" value={inviteRole} onChange={e => setInviteRole(e.target.value)}
63
+ className="mt-1 rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">
64
+ <option value="member">Member</option>
65
+ <option value="admin">Admin</option>
66
+ <option value="viewer">Viewer</option>
67
+ </select>
68
+ </div>
69
+ <TibSubmitButton loading={inviting}>Invite</TibSubmitButton>
70
+ </form>
71
+ <TibFormError error={error} />
72
+
73
+ {loading ? (
74
+ <p className="text-sm text-muted-foreground">Loading…</p>
75
+ ) : (
76
+ <table className="w-full text-sm">
77
+ <thead>
78
+ <tr className="border-b border-border">
79
+ <th className="text-left py-2 font-medium text-muted-foreground">Member</th>
80
+ <th className="text-left py-2 font-medium text-muted-foreground">Role</th>
81
+ <th className="py-2" />
82
+ </tr>
83
+ </thead>
84
+ <tbody>
85
+ {members.map(m => (
86
+ <tr key={m.userId} className="border-b border-border/50">
87
+ <td className="py-3">
88
+ <div className="font-medium">{m.name ?? m.email}</div>
89
+ {m.name && <div className="text-xs text-muted-foreground">{m.email}</div>}
90
+ </td>
91
+ <td className="py-3 capitalize">{m.role}</td>
92
+ <td className="py-3 text-right">
93
+ <button type="button" onClick={() => workspaceId && tib?.removeMember(workspaceId, m.userId)}
94
+ className="text-xs text-destructive hover:underline underline-offset-4">
95
+ Remove
96
+ </button>
97
+ </td>
98
+ </tr>
99
+ ))}
100
+ </tbody>
101
+ </table>
102
+ )}
103
+ </div>
104
+ );
105
+ }
106
+
107
+ export default MembersPage;
@@ -0,0 +1,53 @@
1
+ import { useState } from 'react';
2
+ import { useApi } from '@mettlecast/sdk';
3
+ import { TibFormField, TibFormError, TibSubmitButton } from '@mettlecast/design-system';
4
+
5
+ export function ProfilePage() {
6
+ const tib = useApi();
7
+ const [name, setName] = useState('');
8
+ const [email] = useState('');
9
+ const [error, setError] = useState<string | null>(null);
10
+ const [loading, setLoading] = useState(false);
11
+ const [saved, setSaved] = useState(false);
12
+
13
+ async function handleSubmit(e: React.FormEvent) {
14
+ e.preventDefault();
15
+ setLoading(true);
16
+ setError(null);
17
+ setSaved(false);
18
+ try {
19
+ await (tib as any)?.workspace?.update({ name });
20
+ setSaved(true);
21
+ } catch (err) {
22
+ setError(err instanceof Error ? err.message : 'Save failed');
23
+ } finally {
24
+ setLoading(false);
25
+ }
26
+ }
27
+
28
+ return (
29
+ <div className="p-6 max-w-2xl space-y-8">
30
+ <div>
31
+ <h1 className="text-2xl font-semibold tracking-tight">Profile</h1>
32
+ <p className="text-sm text-muted-foreground mt-1">Manage your personal information</p>
33
+ </div>
34
+ <form onSubmit={handleSubmit} className="space-y-4">
35
+ <h2 className="text-lg font-semibold">Personal details</h2>
36
+ <TibFormField name="name" label="Full name">
37
+ <input id="name" type="text" value={name} onChange={e => setName(e.target.value)}
38
+ className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
39
+ autoComplete="name" />
40
+ </TibFormField>
41
+ <TibFormField name="email" label="Email">
42
+ <input id="email" type="email" value={email} readOnly
43
+ className="w-full rounded-md border border-input bg-muted px-3 py-2 text-sm text-muted-foreground cursor-not-allowed" />
44
+ </TibFormField>
45
+ {saved && <p className="text-sm text-green-600" role="status">Changes saved</p>}
46
+ <TibFormError error={error} />
47
+ <TibSubmitButton loading={loading}>Save changes</TibSubmitButton>
48
+ </form>
49
+ </div>
50
+ );
51
+ }
52
+
53
+ export default ProfilePage;
@@ -0,0 +1,64 @@
1
+ import { useState } from 'react';
2
+ import { useApi } from '@mettlecast/sdk';
3
+ import { useProject } from '@mettlecast/dashboard-shell';
4
+ import { TibFormField, TibFormError, TibSubmitButton } from '@mettlecast/design-system';
5
+
6
+ export function WorkspaceSettingsPage() {
7
+ const tib = useApi();
8
+ const { workspaceId } = useProject();
9
+ const [name, setName] = useState('');
10
+ const [error, setError] = useState<string | null>(null);
11
+ const [loading, setLoading] = useState(false);
12
+ const [saved, setSaved] = useState(false);
13
+
14
+ async function handleSubmit(e: React.FormEvent) {
15
+ e.preventDefault();
16
+ setLoading(true);
17
+ setError(null);
18
+ setSaved(false);
19
+ try {
20
+ await (tib as any)?.workspace?.update({ workspaceId, name });
21
+ setSaved(true);
22
+ } catch (err) {
23
+ setError(err instanceof Error ? err.message : 'Save failed');
24
+ } finally {
25
+ setLoading(false);
26
+ }
27
+ }
28
+
29
+ return (
30
+ <div className="p-6 max-w-2xl space-y-8">
31
+ <div>
32
+ <h1 className="text-2xl font-semibold tracking-tight">Workspace settings</h1>
33
+ <p className="text-sm text-muted-foreground mt-1">Manage workspace configuration</p>
34
+ </div>
35
+ <form onSubmit={handleSubmit} className="space-y-4">
36
+ <h2 className="text-lg font-semibold">General</h2>
37
+ <TibFormField name="name" label="Workspace name">
38
+ <input id="name" type="text" value={name} onChange={e => setName(e.target.value)}
39
+ className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
40
+ autoComplete="organization" />
41
+ </TibFormField>
42
+ <TibFormField name="workspaceId" label="Workspace ID">
43
+ <input id="workspaceId" type="text" value={workspaceId ?? ''} readOnly
44
+ className="w-full rounded-md border border-input bg-muted px-3 py-2 text-sm text-muted-foreground font-mono cursor-not-allowed" />
45
+ </TibFormField>
46
+ {saved && <p className="text-sm text-green-600" role="status">Changes saved</p>}
47
+ <TibFormError error={error} />
48
+ <TibSubmitButton loading={loading}>Save changes</TibSubmitButton>
49
+ </form>
50
+
51
+ <div className="space-y-4 border-t border-border pt-8">
52
+ <h2 className="text-lg font-semibold text-destructive">Danger zone</h2>
53
+ <p className="text-sm text-muted-foreground">These actions are irreversible.</p>
54
+ <button type="button"
55
+ className="rounded-md border border-destructive px-4 py-2 text-sm font-medium text-destructive hover:bg-destructive hover:text-destructive-foreground transition-colors"
56
+ onClick={() => window.confirm('Are you sure? This cannot be undone.') && (tib as any)?.workspace?.delete({ workspaceId })}>
57
+ Delete workspace
58
+ </button>
59
+ </div>
60
+ </div>
61
+ );
62
+ }
63
+
64
+ export default WorkspaceSettingsPage;
@@ -0,0 +1,62 @@
1
+ import { useState } from 'react';
2
+ import { useNavigate, useSearchParams } from 'react-router-dom';
3
+ import { useApi } from '@mettlecast/sdk';
4
+ import { TibFormField, TibFormError, TibSubmitButton } from '@mettlecast/design-system';
5
+
6
+ export function AcceptInvitationPage() {
7
+ const tib = useApi();
8
+ const navigate = useNavigate();
9
+ const [searchParams] = useSearchParams();
10
+ const token = searchParams.get('token') ?? '';
11
+ const email = searchParams.get('email') ?? '';
12
+ const [name, setName] = useState('');
13
+ const [password, setPassword] = useState('');
14
+ const [error, setError] = useState<string | null>(null);
15
+ const [loading, setLoading] = useState(false);
16
+
17
+ async function handleSubmit(e: React.FormEvent) {
18
+ e.preventDefault();
19
+ setLoading(true);
20
+ setError(null);
21
+ try {
22
+ await (tib as any)?.auth?.acceptInvite({ token, name, password });
23
+ navigate('/login');
24
+ } catch (err) {
25
+ setError(err instanceof Error ? err.message : 'Failed to accept invitation');
26
+ } finally {
27
+ setLoading(false);
28
+ }
29
+ }
30
+
31
+ return (
32
+ <div className="min-h-screen flex items-center justify-center bg-background p-4">
33
+ <div className="w-full max-w-md space-y-6">
34
+ <div>
35
+ <h1 className="text-2xl font-semibold tracking-tight">Accept invitation</h1>
36
+ <p className="text-sm text-muted-foreground mt-1">You've been invited to join the workspace</p>
37
+ </div>
38
+ <form onSubmit={handleSubmit} className="space-y-4" noValidate>
39
+ {email && (
40
+ <div className="rounded-md bg-muted px-3 py-2 text-sm">
41
+ Signing up as <strong>{email}</strong>
42
+ </div>
43
+ )}
44
+ <TibFormField name="name" label="Full name">
45
+ <input id="name" type="text" value={name} onChange={e => setName(e.target.value)}
46
+ className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
47
+ required autoComplete="name" autoFocus />
48
+ </TibFormField>
49
+ <TibFormField name="password" label="Create password">
50
+ <input id="password" type="password" value={password} onChange={e => setPassword(e.target.value)}
51
+ className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
52
+ required autoComplete="new-password" minLength={8} />
53
+ </TibFormField>
54
+ <TibFormError error={error} />
55
+ <TibSubmitButton loading={loading} className="w-full">Join workspace</TibSubmitButton>
56
+ </form>
57
+ </div>
58
+ </div>
59
+ );
60
+ }
61
+
62
+ export default AcceptInvitationPage;
@@ -0,0 +1,66 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { useNavigate } from 'react-router-dom';
3
+ import { useApi } from '@mettlecast/sdk';
4
+ import { TibFormError } from '@mettlecast/design-system';
5
+
6
+ interface OrgItem {
7
+ orgId: string;
8
+ name: string;
9
+ role: string;
10
+ }
11
+
12
+ export function ChooseOrgPage() {
13
+ const tib = useApi();
14
+ const navigate = useNavigate();
15
+ const [orgs, setOrgs] = useState<OrgItem[]>([]);
16
+ const [error, setError] = useState<string | null>(null);
17
+ const [loading, setLoading] = useState(true);
18
+
19
+ useEffect(() => {
20
+ (async () => {
21
+ try {
22
+ const res = await (tib as any)?.org?.listWorkspaces({});
23
+ setOrgs((res?.items ?? []) as OrgItem[]);
24
+ } catch (err) {
25
+ setError(err instanceof Error ? err.message : 'Failed to load organisations');
26
+ } finally {
27
+ setLoading(false);
28
+ }
29
+ })();
30
+ }, [tib]);
31
+
32
+ return (
33
+ <div className="min-h-screen flex items-center justify-center bg-background p-4">
34
+ <div className="w-full max-w-md space-y-6">
35
+ <div>
36
+ <h1 className="text-2xl font-semibold tracking-tight">Choose organisation</h1>
37
+ <p className="text-sm text-muted-foreground mt-1">Select the workspace to continue</p>
38
+ </div>
39
+ <TibFormError error={error} />
40
+ {loading ? (
41
+ <p className="text-sm text-muted-foreground">Loading…</p>
42
+ ) : (
43
+ <ul className="space-y-2" role="list">
44
+ {orgs.map(org => (
45
+ <li key={org.orgId}>
46
+ <button
47
+ type="button"
48
+ onClick={() => navigate(`/t/${org.orgId}`)}
49
+ className="w-full text-left rounded-md border border-border px-4 py-3 text-sm hover:bg-accent hover:text-accent-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
50
+ >
51
+ <div className="font-medium">{org.name}</div>
52
+ <div className="text-xs text-muted-foreground capitalize">{org.role}</div>
53
+ </button>
54
+ </li>
55
+ ))}
56
+ {orgs.length === 0 && (
57
+ <li className="text-sm text-muted-foreground text-center py-4">No organisations found</li>
58
+ )}
59
+ </ul>
60
+ )}
61
+ </div>
62
+ </div>
63
+ );
64
+ }
65
+
66
+ export default ChooseOrgPage;
@@ -0,0 +1,69 @@
1
+ import { useState } from 'react';
2
+ import { Link } from 'react-router-dom';
3
+ import { useApi } from '@mettlecast/sdk';
4
+ import { TibFormField, TibFormError, TibSubmitButton } from '@mettlecast/design-system';
5
+
6
+ export function ForgotPasswordPage() {
7
+ const tib = useApi();
8
+ const [email, setEmail] = useState('');
9
+ const [sent, setSent] = useState(false);
10
+ const [error, setError] = useState<string | null>(null);
11
+ const [loading, setLoading] = useState(false);
12
+
13
+ async function handleSubmit(e: React.FormEvent) {
14
+ e.preventDefault();
15
+ setLoading(true);
16
+ setError(null);
17
+ try {
18
+ await (tib as any)?.auth?.forgotPassword({ email });
19
+ setSent(true);
20
+ } catch (err) {
21
+ setError(err instanceof Error ? err.message : 'Failed to send reset email');
22
+ } finally {
23
+ setLoading(false);
24
+ }
25
+ }
26
+
27
+ if (sent) {
28
+ return (
29
+ <div className="min-h-screen flex items-center justify-center bg-background p-4">
30
+ <div className="w-full max-w-md space-y-4 text-center">
31
+ <h1 className="text-2xl font-semibold tracking-tight">Check your email</h1>
32
+ <p className="text-sm text-muted-foreground">
33
+ We sent a password reset link to <strong>{email}</strong>
34
+ </p>
35
+ <Link to="/login" className="text-sm text-primary hover:underline underline-offset-4">
36
+ Back to sign in
37
+ </Link>
38
+ </div>
39
+ </div>
40
+ );
41
+ }
42
+
43
+ return (
44
+ <div className="min-h-screen flex items-center justify-center bg-background p-4">
45
+ <div className="w-full max-w-md space-y-6">
46
+ <div>
47
+ <h1 className="text-2xl font-semibold tracking-tight">Reset password</h1>
48
+ <p className="text-sm text-muted-foreground mt-1">Enter your email to receive a reset link</p>
49
+ </div>
50
+ <form onSubmit={handleSubmit} className="space-y-4" noValidate>
51
+ <TibFormField name="email" label="Email">
52
+ <input id="email" type="email" value={email} onChange={e => setEmail(e.target.value)}
53
+ className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
54
+ required autoComplete="email" autoFocus />
55
+ </TibFormField>
56
+ <TibFormError error={error} />
57
+ <TibSubmitButton loading={loading} className="w-full">Send reset link</TibSubmitButton>
58
+ </form>
59
+ <p className="text-center">
60
+ <Link to="/login" className="text-sm text-muted-foreground hover:text-foreground">
61
+ ← Back to sign in
62
+ </Link>
63
+ </p>
64
+ </div>
65
+ </div>
66
+ );
67
+ }
68
+
69
+ export default ForgotPasswordPage;
@@ -0,0 +1,10 @@
1
+ export { LoginPage } from './login';
2
+ export { SignupPage } from './signup';
3
+ export { VerifyEmailPage } from './verify-email';
4
+ export { ForgotPasswordPage } from './forgot-password';
5
+ export { ResetPasswordPage } from './reset-password';
6
+ export { MfaSetupPage } from './mfa-setup';
7
+ export { MfaVerifyPage } from './mfa-verify';
8
+ export { AcceptInvitationPage } from './accept-invitation';
9
+ export { ChooseOrgPage } from './choose-org';
10
+ export { SignOutPage } from './sign-out';
@@ -0,0 +1,75 @@
1
+ import { useState } from 'react';
2
+ import { useNavigate, Link } from 'react-router-dom';
3
+ import { signIn } from '@mettlecast/dashboard-shell';
4
+ import { TibFormField, TibFormError, TibSubmitButton } from '@mettlecast/design-system';
5
+
6
+ export function LoginPage() {
7
+ const navigate = useNavigate();
8
+ const [email, setEmail] = useState('');
9
+ const [password, setPassword] = useState('');
10
+ const [error, setError] = useState<string | null>(null);
11
+ const [loading, setLoading] = useState(false);
12
+
13
+ async function handleSubmit(e: React.FormEvent) {
14
+ e.preventDefault();
15
+ setLoading(true);
16
+ setError(null);
17
+ try {
18
+ await signIn(email, password);
19
+ navigate('/');
20
+ } catch (err) {
21
+ setError(err instanceof Error ? err.message : 'Sign in failed');
22
+ } finally {
23
+ setLoading(false);
24
+ }
25
+ }
26
+
27
+ return (
28
+ <div className="min-h-screen flex items-center justify-center bg-background p-4">
29
+ <div className="w-full max-w-md space-y-6">
30
+ <div>
31
+ <h1 className="text-2xl font-semibold tracking-tight">Sign in</h1>
32
+ <p className="text-sm text-muted-foreground mt-1">Enter your credentials to continue</p>
33
+ </div>
34
+ <form onSubmit={handleSubmit} className="space-y-4" noValidate>
35
+ <TibFormField name="email" label="Email">
36
+ <input
37
+ id="email"
38
+ type="email"
39
+ value={email}
40
+ onChange={e => setEmail(e.target.value)}
41
+ className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
42
+ required
43
+ autoComplete="email"
44
+ autoFocus
45
+ />
46
+ </TibFormField>
47
+ <TibFormField name="password" label="Password">
48
+ <input
49
+ id="password"
50
+ type="password"
51
+ value={password}
52
+ onChange={e => setPassword(e.target.value)}
53
+ className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
54
+ required
55
+ autoComplete="current-password"
56
+ />
57
+ </TibFormField>
58
+ <div className="text-right">
59
+ <Link to="/forgot-password" className="text-sm text-primary hover:underline underline-offset-4">
60
+ Forgot password?
61
+ </Link>
62
+ </div>
63
+ <TibFormError error={error} />
64
+ <TibSubmitButton loading={loading} className="w-full">Sign in</TibSubmitButton>
65
+ </form>
66
+ <p className="text-center text-sm text-muted-foreground">
67
+ No account?{' '}
68
+ <Link to="/signup" className="text-primary hover:underline underline-offset-4">Create one</Link>
69
+ </p>
70
+ </div>
71
+ </div>
72
+ );
73
+ }
74
+
75
+ export default LoginPage;
@@ -0,0 +1,56 @@
1
+ import { useState } from 'react';
2
+ import { useNavigate } from 'react-router-dom';
3
+ import { useApi } from '@mettlecast/sdk';
4
+ import { TibFormField, TibFormError, TibSubmitButton } from '@mettlecast/design-system';
5
+
6
+ export function MfaSetupPage() {
7
+ const tib = useApi();
8
+ const navigate = useNavigate();
9
+ const [code, setCode] = useState('');
10
+ const [error, setError] = useState<string | null>(null);
11
+ const [loading, setLoading] = useState(false);
12
+ const totpUri = (tib as any)?.auth?.getTotpUri?.() ?? '';
13
+
14
+ async function handleSubmit(e: React.FormEvent) {
15
+ e.preventDefault();
16
+ setLoading(true);
17
+ setError(null);
18
+ try {
19
+ await (tib as any)?.auth?.verifyTotpSetup({ code });
20
+ navigate('/');
21
+ } catch (err) {
22
+ setError(err instanceof Error ? err.message : 'MFA setup failed');
23
+ } finally {
24
+ setLoading(false);
25
+ }
26
+ }
27
+
28
+ return (
29
+ <div className="min-h-screen flex items-center justify-center bg-background p-4">
30
+ <div className="w-full max-w-md space-y-6">
31
+ <div>
32
+ <h1 className="text-2xl font-semibold tracking-tight">Set up two-factor authentication</h1>
33
+ <p className="text-sm text-muted-foreground mt-1">Scan the QR code with your authenticator app</p>
34
+ </div>
35
+ {totpUri && (
36
+ <div className="flex justify-center">
37
+ <img src={`https://api.qrserver.com/v1/create-qr-code/?size=180x180&data=${encodeURIComponent(totpUri)}`}
38
+ alt="TOTP QR code" className="rounded-md border border-border" width={180} height={180} />
39
+ </div>
40
+ )}
41
+ <form onSubmit={handleSubmit} className="space-y-4" noValidate>
42
+ <TibFormField name="code" label="6-digit code">
43
+ <input id="code" type="text" inputMode="numeric" pattern="[0-9]{6}" value={code}
44
+ onChange={e => setCode(e.target.value.replace(/\D/g, '').slice(0, 6))}
45
+ className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm tracking-widest text-center focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
46
+ required autoComplete="one-time-code" autoFocus maxLength={6} />
47
+ </TibFormField>
48
+ <TibFormError error={error} />
49
+ <TibSubmitButton loading={loading} className="w-full">Enable MFA</TibSubmitButton>
50
+ </form>
51
+ </div>
52
+ </div>
53
+ );
54
+ }
55
+
56
+ export default MfaSetupPage;