spine-framework 0.3.124 → 0.3.126

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 (319) hide show
  1. package/.framework/cli/commands/agents.ts +35 -37
  2. package/.framework/cli/commands/auth.ts +35 -34
  3. package/.framework/cli/commands/create-app.ts +104 -40
  4. package/.framework/cli/commands/dev.ts +104 -69
  5. package/.framework/cli/commands/doctor.ts +179 -69
  6. package/.framework/cli/commands/generate.ts +83 -143
  7. package/.framework/cli/commands/init.ts +72 -34
  8. package/.framework/cli/commands/install-app.ts +260 -125
  9. package/.framework/cli/commands/items.ts +33 -44
  10. package/.framework/cli/commands/migrate.ts +122 -40
  11. package/.framework/cli/commands/migrations.ts +33 -40
  12. package/.framework/cli/commands/pipelines.ts +31 -39
  13. package/.framework/cli/commands/status.ts +47 -23
  14. package/.framework/cli/commands/system.ts +47 -37
  15. package/.framework/cli/commands/test.ts +112 -68
  16. package/.framework/cli/commands/validate-app.ts +215 -121
  17. package/.framework/cli/context.ts +112 -130
  18. package/.framework/cli/env-loader.ts +47 -18
  19. package/.framework/cli/index.ts +81 -42
  20. package/.framework/docs/runtime-types.md +1024 -0
  21. package/.framework/functions/_shared/agent-runner.ts +1296 -961
  22. package/.framework/functions/_shared/app-manifest.ts +82 -68
  23. package/.framework/functions/_shared/audit.ts +135 -83
  24. package/.framework/functions/_shared/{cil-state.ts → cil.ts} +158 -56
  25. package/.framework/functions/_shared/db.ts +80 -126
  26. package/.framework/functions/_shared/hash-api-key.ts +63 -0
  27. package/.framework/functions/_shared/index.ts +224 -335
  28. package/.framework/functions/_shared/kb.ts +319 -0
  29. package/.framework/functions/_shared/llm.ts +92 -0
  30. package/.framework/functions/_shared/logging.ts +288 -0
  31. package/.framework/functions/_shared/middleware.ts +329 -365
  32. package/.framework/functions/_shared/outcomes.ts +36 -0
  33. package/.framework/functions/_shared/permissions.ts +359 -643
  34. package/.framework/functions/_shared/pipeline-runner.ts +243 -316
  35. package/.framework/functions/_shared/principal.ts +376 -468
  36. package/.framework/functions/_shared/resolve-credentials.ts +102 -0
  37. package/.framework/functions/_shared/resolve-ids.ts +188 -16
  38. package/.framework/functions/_shared/retry-deliveries.ts +104 -0
  39. package/.framework/functions/_shared/schema-utils.ts +611 -373
  40. package/.framework/functions/_shared/testing.ts +207 -137
  41. package/.framework/functions/_shared/tracing.ts +276 -0
  42. package/.framework/functions/_shared/trigger-engine.ts +159 -211
  43. package/.framework/functions/_shared/webhook-registration.ts +95 -81
  44. package/.framework/functions/_shared/webhook-registry.ts +153 -75
  45. package/.framework/functions/account-nodes.ts +31 -53
  46. package/.framework/functions/adapters/generic-webhook-inbound.ts +56 -0
  47. package/.framework/functions/adapters/generic-webhook-outbound.ts +43 -0
  48. package/.framework/functions/adapters/openai-outbound.ts +40 -0
  49. package/.framework/functions/admin-data.ts +685 -537
  50. package/.framework/functions/admin-funnel.ts +31 -20
  51. package/.framework/functions/admin-install-funnel.ts +47 -28
  52. package/.framework/functions/admin-signals.ts +47 -29
  53. package/.framework/functions/ai-agents.ts +122 -147
  54. package/.framework/functions/ai-interactions.ts +180 -90
  55. package/.framework/functions/api-keys.ts +250 -192
  56. package/.framework/functions/apps.ts +203 -197
  57. package/.framework/functions/auth.ts +98 -91
  58. package/.framework/functions/cil-agent-ingest.ts +773 -0
  59. package/.framework/functions/cil-agent.ts +592 -0
  60. package/.framework/functions/cil-callmap.ts +1227 -0
  61. package/.framework/functions/cil-config.ts +583 -0
  62. package/.framework/functions/cil-context.ts +445 -0
  63. package/.framework/functions/cil-doc.ts +1752 -0
  64. package/.framework/functions/cil-embeddings.ts +1202 -0
  65. package/.framework/functions/cil-graph.ts +585 -0
  66. package/.framework/functions/cil-ingest.ts +2416 -0
  67. package/.framework/functions/cil-pipeline.ts +318 -0
  68. package/.framework/functions/cil-project.ts +499 -0
  69. package/.framework/functions/cil-validate.ts +469 -0
  70. package/.framework/functions/debug-auth.ts +73 -53
  71. package/.framework/functions/deliveries.ts +117 -0
  72. package/.framework/functions/embeddings.ts +528 -516
  73. package/.framework/functions/eval-harness.ts +158 -106
  74. package/.framework/functions/integration-outbound.ts +311 -0
  75. package/.framework/functions/integration-routes.ts +240 -466
  76. package/.framework/functions/integrations.ts +132 -140
  77. package/.framework/functions/invites.ts +401 -201
  78. package/.framework/functions/item-progress.ts +113 -53
  79. package/.framework/functions/kb.ts +28 -0
  80. package/.framework/functions/logs.ts +239 -222
  81. package/.framework/functions/observability.ts +308 -259
  82. package/.framework/functions/pipeline-executions.ts +179 -235
  83. package/.framework/functions/pipelines.ts +163 -205
  84. package/.framework/functions/prompt-configs.ts +109 -156
  85. package/.framework/functions/roles.ts +97 -147
  86. package/.framework/functions/system-cron.ts +132 -218
  87. package/.framework/functions/system.ts +112 -52
  88. package/.framework/functions/tests.ts +32 -28
  89. package/.framework/functions/timers.ts +162 -210
  90. package/.framework/functions/triggers.ts +282 -346
  91. package/.framework/functions/types.ts +283 -400
  92. package/.framework/migrations/001_core_schema.sql +630 -0
  93. package/.framework/migrations/002_core_rls_and_functions.sql +532 -0
  94. package/.framework/migrations/{003_system_data.sql → 003_core_system_data.sql} +26 -153
  95. package/.framework/migrations/004_core_rpc_and_broker.sql +421 -0
  96. package/.framework/migrations/005_core_observability.sql +598 -0
  97. package/.framework/migrations/006_security_hardening_rls.sql +97 -0
  98. package/.framework/migrations/007_kb_article_list.sql +237 -0
  99. package/.framework/migrations/008_items_target.sql +8 -0
  100. package/.framework/migrations/009_roles_slug_unique.sql +11 -0
  101. package/.framework/public/spine.config.json +91 -0
  102. package/.framework/scripts/app-install-cli.ts +112 -98
  103. package/.framework/src/App.tsx +67 -20
  104. package/.framework/src/apps/admin/index.tsx +97 -27
  105. package/.framework/src/components/AppWrapper.tsx +50 -11
  106. package/.framework/src/components/CustomAppLoader.tsx +131 -22
  107. package/.framework/src/components/admin/AdminListPage.tsx +75 -8
  108. package/.framework/src/components/admin/AdminSidebar.tsx +148 -36
  109. package/.framework/src/components/admin/AdminStatsCard.tsx +48 -29
  110. package/.framework/src/components/admin/PageInstructions.tsx +49 -0
  111. package/.framework/src/components/admin/SortableTableHeader.tsx +49 -2
  112. package/.framework/src/components/app-shell/GenericAppShell.tsx +69 -11
  113. package/.framework/src/components/app-shell/GenericDetailPage.tsx +55 -10
  114. package/.framework/src/components/app-shell/GenericListPage.tsx +76 -9
  115. package/.framework/src/components/app-sidebar.tsx +50 -3
  116. package/.framework/src/components/auth/ProtectedRoute.tsx +53 -45
  117. package/.framework/src/components/kb/KBArticleEditor.tsx +409 -0
  118. package/.framework/src/components/kb/KBArticleList.tsx +557 -0
  119. package/.framework/src/components/layout/AppShell.tsx +54 -16
  120. package/.framework/src/components/layout/Header.tsx +50 -38
  121. package/.framework/src/components/layout/Layout.tsx +48 -32
  122. package/.framework/src/components/layout/Sidebar.tsx +167 -16
  123. package/.framework/src/components/runtime/DataDetailHeader.tsx +33 -1
  124. package/.framework/src/components/runtime/DataDetailPage.tsx +289 -4
  125. package/.framework/src/components/runtime/DataFilters.tsx +78 -1
  126. package/.framework/src/components/runtime/DataHeader.tsx +32 -29
  127. package/.framework/src/components/runtime/DataListPage.tsx +161 -2
  128. package/.framework/src/components/runtime/DataStats.tsx +48 -1
  129. package/.framework/src/components/runtime/DataTable.tsx +153 -1
  130. package/.framework/src/components/runtime/SchemaDetailForm.tsx +77 -46
  131. package/.framework/src/components/runtime/index.ts +16 -9
  132. package/.framework/src/components/search-form.tsx +33 -1
  133. package/.framework/src/components/shared/AgentView.tsx +48 -51
  134. package/.framework/src/components/shared/FieldRenderer.tsx +84 -67
  135. package/.framework/src/components/shared/InviteManager.tsx +113 -52
  136. package/.framework/src/components/shared/MemberList.tsx +182 -0
  137. package/.framework/src/components/shared/SchemaFields.tsx +96 -78
  138. package/.framework/src/components/ui/DataTable.tsx +86 -56
  139. package/.framework/src/components/ui/Form.tsx +188 -109
  140. package/.framework/src/components/ui/ItemCard.tsx +86 -58
  141. package/.framework/src/components/ui/ItemListView.tsx +88 -61
  142. package/.framework/src/components/ui/LoadingSpinner.tsx +52 -27
  143. package/.framework/src/components/ui/Modal.tsx +81 -4
  144. package/.framework/src/components/ui/RichTextEditor.tsx +101 -9
  145. package/.framework/src/components/ui/accordion.tsx +97 -5
  146. package/.framework/src/components/ui/alert-dialog.tsx +225 -13
  147. package/.framework/src/components/ui/alert.tsx +113 -6
  148. package/.framework/src/components/ui/aspect-ratio.tsx +49 -2
  149. package/.framework/src/components/ui/avatar.tsx +129 -7
  150. package/.framework/src/components/ui/badge.tsx +65 -3
  151. package/.framework/src/components/ui/breadcrumb.tsx +145 -8
  152. package/.framework/src/components/ui/button-group.tsx +81 -4
  153. package/.framework/src/components/ui/button.tsx +49 -2
  154. package/.framework/src/components/ui/calendar.tsx +65 -3
  155. package/.framework/src/components/ui/card.tsx +145 -8
  156. package/.framework/src/components/ui/carousel.tsx +129 -7
  157. package/.framework/src/components/ui/chart.tsx +161 -10
  158. package/.framework/src/components/ui/checkbox.tsx +49 -2
  159. package/.framework/src/components/ui/collapsible.tsx +81 -5
  160. package/.framework/src/components/ui/combobox.tsx +305 -20
  161. package/.framework/src/components/ui/command.tsx +177 -11
  162. package/.framework/src/components/ui/context-menu.tsx +273 -17
  163. package/.framework/src/components/ui/dialog.tsx +209 -27
  164. package/.framework/src/components/ui/direction.tsx +64 -4
  165. package/.framework/src/components/ui/drawer.tsx +193 -12
  166. package/.framework/src/components/ui/dropdown-menu.tsx +273 -18
  167. package/.framework/src/components/ui/empty.tsx +145 -9
  168. package/.framework/src/components/ui/field.tsx +209 -14
  169. package/.framework/src/components/ui/hover-card.tsx +81 -4
  170. package/.framework/src/components/ui/input-group.tsx +162 -10
  171. package/.framework/src/components/ui/input-otp.tsx +98 -5
  172. package/.framework/src/components/ui/input.tsx +49 -2
  173. package/.framework/src/components/ui/item.tsx +225 -13
  174. package/.framework/src/components/ui/kbd.tsx +65 -3
  175. package/.framework/src/components/ui/label.tsx +49 -2
  176. package/.framework/src/components/ui/menubar.tsx +289 -17
  177. package/.framework/src/components/ui/native-select.tsx +97 -5
  178. package/.framework/src/components/ui/navigation-menu.tsx +177 -10
  179. package/.framework/src/components/ui/pagination.tsx +161 -9
  180. package/.framework/src/components/ui/popover.tsx +145 -8
  181. package/.framework/src/components/ui/progress.tsx +49 -2
  182. package/.framework/src/components/ui/radio-group.tsx +65 -3
  183. package/.framework/src/components/ui/resizable.tsx +82 -4
  184. package/.framework/src/components/ui/scroll-area.tsx +65 -3
  185. package/.framework/src/components/ui/select.tsx +193 -11
  186. package/.framework/src/components/ui/separator.tsx +49 -2
  187. package/.framework/src/components/ui/sheet.tsx +193 -11
  188. package/.framework/src/components/ui/sidebar.tsx +481 -38
  189. package/.framework/src/components/ui/skeleton.tsx +49 -2
  190. package/.framework/src/components/ui/slider.tsx +49 -2
  191. package/.framework/src/components/ui/sonner.tsx +49 -2
  192. package/.framework/src/components/ui/spinner.tsx +49 -2
  193. package/.framework/src/components/ui/switch.tsx +49 -2
  194. package/.framework/src/components/ui/table-primitives.tsx +160 -33
  195. package/.framework/src/components/ui/table.tsx +161 -9
  196. package/.framework/src/components/ui/tabs.tsx +113 -6
  197. package/.framework/src/components/ui/textarea.tsx +49 -2
  198. package/.framework/src/components/ui/toggle-group.tsx +81 -4
  199. package/.framework/src/components/ui/toggle.tsx +65 -3
  200. package/.framework/src/components/ui/tooltip.tsx +97 -5
  201. package/.framework/src/contexts/AppContext.tsx +129 -45
  202. package/.framework/src/contexts/AuthContext.tsx +187 -144
  203. package/.framework/src/hooks/use-mobile.ts +48 -2
  204. package/.framework/src/hooks/useApi.ts +208 -159
  205. package/.framework/src/hooks/useAppPath.ts +32 -23
  206. package/.framework/src/hooks/useApps.ts +51 -20
  207. package/.framework/src/hooks/useEntityList.ts +32 -78
  208. package/.framework/src/hooks/useEntityRecord.ts +35 -96
  209. package/.framework/src/hooks/useForm.ts +32 -92
  210. package/.framework/src/hooks/useListSchema.ts +49 -78
  211. package/.framework/src/hooks/useSchemaRecord.ts +114 -105
  212. package/.framework/src/lib/api.ts +195 -116
  213. package/.framework/src/lib/supabase.ts +98 -64
  214. package/.framework/src/lib/utils.ts +287 -151
  215. package/.framework/src/main.tsx +49 -3
  216. package/.framework/src/pages/DashboardPage.tsx +52 -29
  217. package/.framework/src/pages/NotFoundPage.tsx +32 -10
  218. package/.framework/src/pages/SetupPage.tsx +32 -12
  219. package/.framework/src/pages/admin/AIAgentDetailPage.tsx +119 -10
  220. package/.framework/src/pages/admin/AIAgentsPage.tsx +48 -19
  221. package/.framework/src/pages/admin/APIKeyDetailPage.tsx +187 -17
  222. package/.framework/src/pages/admin/APIKeysPage.tsx +98 -22
  223. package/.framework/src/pages/admin/AlertsConfigPage.tsx +113 -26
  224. package/.framework/src/pages/admin/AppDetailPage.tsx +125 -8
  225. package/.framework/src/pages/admin/AppsPage.tsx +49 -2
  226. package/.framework/src/pages/admin/DeliveriesPage.tsx +196 -0
  227. package/.framework/src/pages/admin/DesignedPage.tsx +32 -18
  228. package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +215 -484
  229. package/.framework/src/pages/admin/EmbeddingsPage.tsx +116 -319
  230. package/.framework/src/pages/admin/ExtendedShadcnTestPage.tsx +34 -9
  231. package/.framework/src/pages/admin/IncrementalShadcnTestPage.tsx +34 -9
  232. package/.framework/src/pages/admin/IntegratedDashboard.tsx +32 -16
  233. package/.framework/src/pages/admin/IntegrationDetailPage.tsx +183 -26
  234. package/.framework/src/pages/admin/IntegrationsPage.tsx +48 -19
  235. package/.framework/src/pages/admin/InvitesPage.tsx +48 -14
  236. package/.framework/src/pages/admin/LogsPage.tsx +422 -75
  237. package/.framework/src/pages/admin/MinimalShadcnTestPage.tsx +34 -9
  238. package/.framework/src/pages/admin/ObservabilityDashboard.tsx +113 -24
  239. package/.framework/src/pages/admin/PipelineDetailPage.tsx +206 -34
  240. package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +50 -17
  241. package/.framework/src/pages/admin/PipelinesPage.tsx +160 -21
  242. package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +378 -88
  243. package/.framework/src/pages/admin/PromptConfigsPage.tsx +125 -16
  244. package/.framework/src/pages/admin/ProperlyDesignedPage.tsx +49 -10
  245. package/.framework/src/pages/admin/RoleDetailPage.tsx +130 -10
  246. package/.framework/src/pages/admin/RolesPage.tsx +171 -18
  247. package/.framework/src/pages/admin/SelectTestPage.tsx +34 -9
  248. package/.framework/src/pages/admin/ShadcnTestPage.tsx +49 -11
  249. package/.framework/src/pages/admin/SimpleDashboard.tsx +49 -10
  250. package/.framework/src/pages/admin/TestRunDetailPage.tsx +49 -4
  251. package/.framework/src/pages/admin/TestingDashboard.tsx +98 -28
  252. package/.framework/src/pages/admin/TimerDetailPage.tsx +143 -33
  253. package/.framework/src/pages/admin/TimersPage.tsx +48 -20
  254. package/.framework/src/pages/admin/TriggerDetailPage.tsx +118 -30
  255. package/.framework/src/pages/admin/TriggersPage.tsx +64 -19
  256. package/.framework/src/pages/admin/TypeDetailPage.tsx +138 -15
  257. package/.framework/src/pages/admin/TypesPage.tsx +48 -20
  258. package/.framework/src/pages/admin/cil/CILAgentPage.tsx +465 -0
  259. package/.framework/src/pages/admin/cil/CILChunkDetailPage.tsx +787 -0
  260. package/.framework/src/pages/admin/cil/CILDashboardPage.tsx +699 -0
  261. package/.framework/src/pages/admin/cil/CILFileListPage.tsx +647 -0
  262. package/.framework/src/pages/admin/cil/CILGraphPage.tsx +616 -0
  263. package/.framework/src/pages/admin/cil/CILIngestionPage.tsx +1402 -0
  264. package/.framework/src/pages/admin/cil/CILProjectsPage.tsx +614 -0
  265. package/.framework/src/pages/admin/cil/CILSearchPage.tsx +571 -0
  266. package/.framework/src/pages/admin/cil/FlexiblePipelinePage.tsx +731 -0
  267. package/.framework/src/pages/admin/kb/KBAdminPage.tsx +64 -0
  268. package/.framework/src/pages/auth/AuthConfirmPage.tsx +52 -51
  269. package/.framework/src/pages/auth/ForgotPasswordPage.tsx +31 -22
  270. package/.framework/src/pages/auth/LoginPage.tsx +50 -23
  271. package/.framework/src/pages/auth/PendingApprovalPage.tsx +31 -15
  272. package/.framework/src/pages/auth/RegisterPage.tsx +281 -317
  273. package/.framework/src/pages/spine-framework/APIPage.tsx +33 -0
  274. package/.framework/src/pages/spine-framework/CLIPage.tsx +33 -0
  275. package/.framework/src/types/auth.ts +128 -71
  276. package/.framework/src/types/types.ts +373 -157
  277. package/custom/.devin/skills/spine-workbench-discovery/SKILL.md +39 -0
  278. package/custom/.devin/skills/spine-workbench-idea/SKILL.md +36 -0
  279. package/custom/.devin/skills/spine-workbench-implement/SKILL.md +46 -0
  280. package/custom/.devin/skills/spine-workbench-initiative/SKILL.md +28 -0
  281. package/custom/.devin/skills/spine-workbench-plan/SKILL.md +38 -0
  282. package/docs/README.md +9 -130
  283. package/netlify.toml +5 -2
  284. package/package.json +13 -25
  285. package/scripts/kb-ingest-docs.ts +203 -0
  286. package/spine.config.json +65 -4
  287. package/.framework/migrations/001_schema.sql +0 -425
  288. package/.framework/migrations/002_rls_and_functions.sql +0 -763
  289. package/.framework/migrations/004_enhancements.sql +0 -43
  290. package/.framework/migrations/005_auth_user_trigger.sql +0 -105
  291. package/.framework/migrations/006_fix_current_actor_id.sql +0 -21
  292. package/.framework/migrations/016_invites.sql +0 -27
  293. package/.framework/migrations/017_fix_get_account_apps.sql +0 -78
  294. package/.framework/migrations/018_apps_required_roles.sql +0 -17
  295. package/.framework/migrations/019_triggers_unique_constraint.sql +0 -13
  296. package/.framework/migrations/020_get_account_apps_role_filter.sql +0 -117
  297. package/.framework/migrations/021_app_domains.sql +0 -112
  298. package/.framework/migrations/022_route_prefix_domain_unique.sql +0 -21
  299. package/.framework/migrations/023_match_embeddings_rpc.sql +0 -78
  300. package/.framework/migrations/024_messages_account_id.sql +0 -9
  301. package/.framework/migrations/025_retype_threads_messages.sql +0 -240
  302. package/.framework/migrations/026_fix_rls_policies.sql +0 -47
  303. package/.framework/migrations/027_embedding_lifecycle.sql +0 -176
  304. package/.framework/migrations/028_fix_message_data_permissions.sql +0 -49
  305. package/.framework/migrations/029_tighten_support_triage_prompt.sql +0 -41
  306. package/.framework/migrations/030_messages_updated_by.sql +0 -6
  307. package/.framework/migrations/031_seed_upsert_constraints.sql +0 -35
  308. package/docs/APPS.md +0 -232
  309. package/docs/ARCHITECTURE.md +0 -246
  310. package/docs/INSTALL.md +0 -155
  311. package/docs/design-schema-spec.md +0 -1837
  312. package/docs/dev-guides/01-create-an-app.md +0 -183
  313. package/docs/dev-guides/02-create-a-function.md +0 -178
  314. package/docs/dev-guides/03-create-a-component.md +0 -178
  315. package/docs/dev-guides/04-webhook-handlers.md +0 -129
  316. package/docs/dev-guides/05-testing.md +0 -168
  317. package/docs/dev-guides/README.md +0 -40
  318. package/docs/npm-packages-plan.md +0 -288
  319. package/docs/spine-core--api-reference.md +0 -822
@@ -1,44 +1,38 @@
1
- /**
2
- * @module src/pages/auth/RegisterPage
3
- * @audience installer
4
- * @layer frontend-page
5
- * @stability stable
6
- *
7
- * Registration page. Handles multiple paths:
8
- *
9
- * 1. First user (no people in system) — simplified form, becomes system_admin.
10
- * No personal/company toggle shown.
11
- *
12
- * 2. Invite link (?t=TOKEN) — email + account pre-filled and locked.
13
- * On submit: Supabase signUp → auth trigger resolves stub → active.
14
- *
15
- * 3. Walk-up personal — new account named after the person.
16
- * On submit: Supabase signUp → complete-registration(new_account).
17
- *
18
- * 4. Walk-up company, no domain match — new account with company name.
19
- * On submit: Supabase signUp → complete-registration(new_account).
20
- *
21
- * 5. Walk-up company, domain match — user chooses join or create new.
22
- * Join: Supabase signUp → complete-registration(join_request) → pending.
23
- * Create new: same as path 3.
24
- *
25
- * URL params:
26
- * ?app=portal|cortex — override default registration app
27
- * ?role=member|support — override default role
28
- *
29
- * @seeAlso functions/invites.ts (check-domain, validate-token, complete-registration)
30
- * @seeAlso migrations/005_auth_user_trigger.sql (invite stub resolution)
31
- * @seeAlso src/pages/auth/PendingApprovalPage.tsx
32
- */
33
-
1
+ // CIL[BEGIN chunk_id="RegisterPage_module_header_1783662155" type="module_header" identity="module_header" parent="none"]
2
+ // CIL[DOC-MACRO purpose="Import React hooks, routing, auth, and Supabase for registration functionality"]
3
+ // CIL[DOC-RESPONSIBILITY responsibility="Provide necessary imports for complex registration component with multiple flows"]
4
+ // CIL[DOC-CONTEXT module="src/pages/auth/RegisterPage" layer="frontend-page" related_files="[\"custom/apps/portal/pages/ContentPage.tsx\",\"custom/apps/cortex/pages/operations/OperationsDashboard.tsx\",\".framework/src/pages/admin/TypeDetailPage.tsx\",\".framework/functions/_shared/principal.ts\",\".framework/functions/_shared/schema-utils.ts\",\"\\\"]"]
5
+ // CIL[DOC-CONTRACT inputs="none" outputs="React hooks, routing, auth, Supabase client" errors="none" side_effects="none"]
6
+ // CIL[DOC-MICRO logic_summary="Imports for registration page with invite handling and domain checking" notable_decisions="[]"]
7
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
8
+ // CIL[DOC-STABILITY stability="stable"]
9
+ // CIL[DOC-AUDIENCE audience="developer"]
10
+ // CIL[DOC-VECTOR type="context" content="Layer: frontend-page. Direct callers: 0. Direct calls: 0. Related files: custom/apps/portal/pages/ContentPage.tsx, custom/apps/cortex/pages/operations/OperationsDashboard.tsx, .framework/src/pages/admin/TypeDetailPage.tsx, .framework/functions/_shared/principal.ts, .framework/functions/_shared/schema-utils.ts, \."]
11
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
12
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
13
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
14
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
15
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
34
16
  import React, { useState, useEffect, useCallback } from 'react'
35
17
  import { Link, useNavigate, useSearchParams } from 'react-router-dom'
36
18
  import { LoadingSpinner } from '../../components/ui/LoadingSpinner'
37
19
  import { useAuth } from '../../contexts/AuthContext'
38
20
  import { supabase } from '../../lib/supabase'
39
-
40
- // ─── TYPES ────────────────────────────────────────────────────────────────────
41
-
21
+ // CIL[END chunk_id="RegisterPage_module_header_1783662155"]
22
+ // CIL[BEGIN chunk_id="RegisterPage_types_1783662155" type="type" identity="types" parent="none"]
23
+ // CIL[DOC-MACRO purpose="Define TypeScript types for registration flows and configuration"]
24
+ // CIL[DOC-RESPONSIBILITY responsibility="Provide type definitions for account types, invite data, and registration config"]
25
+ // CIL[DOC-CONTEXT module="src/pages/auth/RegisterPage" layer="frontend-page" related_files="[\"custom/apps/portal/pages/ContentPage.tsx\",\".framework/functions/_shared/principal.ts\",\"custom/apps/cortex/pages/operations/OperationsDashboard.tsx\",\".framework/functions/_shared/schema-utils.ts\",\".framework/src/pages/admin/TypeDetailPage.tsx\",\"\\\"]"]
26
+ // CIL[DOC-CONTRACT inputs="none" outputs="UseType, DomainMatchChoice, InvitePreview, RegistrationConfig" errors="none" side_effects="none"]
27
+ // CIL[DOC-MICRO logic_summary="Type definitions for personal/company account types and invite handling" notable_decisions="[]"]
28
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
29
+ // CIL[DOC-STABILITY stability="stable"]
30
+ // CIL[DOC-VECTOR type="context" content="Layer: frontend-page. Direct callers: 0. Direct calls: 0. Related files: custom/apps/portal/pages/ContentPage.tsx, .framework/functions/_shared/principal.ts, custom/apps/cortex/pages/operations/OperationsDashboard.tsx, .framework/functions/_shared/schema-utils.ts, .framework/src/pages/admin/TypeDetailPage.tsx, \."]
31
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
32
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
33
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
34
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
35
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
42
36
  type UseType = 'personal' | 'company'
43
37
  type DomainMatchChoice = 'join' | 'create_new' | null
44
38
 
@@ -54,6 +48,7 @@ interface RegistrationConfig {
54
48
  default_app: string
55
49
  default_role: string
56
50
  allow_url_override: boolean
51
+ default_app_by_role?: Record<string, string>
57
52
  apps: Record<string, {
58
53
  roles: string[]
59
54
  default_role: string
@@ -63,9 +58,21 @@ interface RegistrationConfig {
63
58
  target_account?: string
64
59
  }>
65
60
  }
66
-
67
- // ─── HELPERS ──────────────────────────────────────────────────────────────────
68
-
61
+ // CIL[END chunk_id="RegisterPage_types_1783662155"]
62
+ // CIL[BEGIN chunk_id="RegisterPage_callinvitesapi_1783662155" type="function" identity="callInvitesApi" parent="none"]
63
+ // CIL[DOC-MACRO purpose="Call invites API endpoint for registration and domain validation"]
64
+ // CIL[DOC-RESPONSIBILITY responsibility="Handle HTTP requests to invites API with GET/POST methods"]
65
+ // CIL[DOC-CONTEXT module="src/pages/auth/RegisterPage" layer="frontend-page" related_files="[\".framework/functions/_shared/schema-utils.ts\",\"custom/apps/portal/pages/ContentPage.tsx\",\".framework/src/pages/admin/TypeDetailPage.tsx\",\"custom/apps/cortex/pages/operations/OperationsDashboard.tsx\",\".framework/functions/_shared/principal.ts\",\"\\\"]"]
66
+ // CIL[DOC-CONTRACT inputs="none" outputs="API response JSON" errors="network errors, API errors" side_effects="HTTP requests to backend"]
67
+ // CIL[DOC-MICRO logic_summary="Generic API wrapper for invites functionality with method handling" notable_decisions="[]"]
68
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
69
+ // CIL[DOC-STABILITY stability="stable"]
70
+ // CIL[DOC-VECTOR type="context" content="Layer: frontend-page. Direct callers: 2. Direct calls: 0. Related files: .framework/functions/_shared/schema-utils.ts, custom/apps/portal/pages/ContentPage.tsx, .framework/src/pages/admin/TypeDetailPage.tsx, custom/apps/cortex/pages/operations/OperationsDashboard.tsx, .framework/functions/_shared/principal.ts, \."]
71
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 2. Total affected: 2."]
72
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
73
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
74
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
75
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
69
76
  async function callInvitesApi(action: string, params: Record<string, any> = {}, method: 'GET' | 'POST' = 'GET') {
70
77
  const base = '/.netlify/functions/invites'
71
78
  if (method === 'GET') {
@@ -80,7 +87,21 @@ async function callInvitesApi(action: string, params: Record<string, any> = {},
80
87
  })
81
88
  return res.json()
82
89
  }
83
-
90
+ // CIL[END chunk_id="RegisterPage_callinvitesapi_1783662155"]
91
+ // CIL[BEGIN chunk_id="RegisterPage_loadregistrationconfig_1783662155" type="function" identity="loadRegistrationConfig" parent="none"]
92
+ // CIL[DOC-MACRO purpose="Load registration configuration from spine.config.json file"]
93
+ // CIL[DOC-RESPONSIBILITY responsibility="Fetch and parse registration config with error handling"]
94
+ // CIL[DOC-CONTEXT module="src/pages/auth/RegisterPage" layer="frontend-page" related_files="[\"custom/apps/cortex/functions/custom_cil-embeddings.ts\",\"\\\"]"]
95
+ // CIL[DOC-CONTRACT inputs="none" outputs="RegistrationConfig or null" errors="fetch errors, parse errors" side_effects="HTTP request to config file"]
96
+ // CIL[DOC-MICRO logic_summary="Fetch config file and extract registration section with fallback" notable_decisions="[]"]
97
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
98
+ // CIL[DOC-STABILITY stability="stable"]
99
+ // CIL[DOC-VECTOR type="context" content="Layer: frontend-page. Direct callers: 1. Direct calls: 0. Related files: custom/apps/cortex/functions/custom_cil-embeddings.ts, \."]
100
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 1. Total affected: 1."]
101
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
102
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
103
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
104
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
84
105
  async function loadRegistrationConfig(): Promise<RegistrationConfig | null> {
85
106
  try {
86
107
  const res = await fetch('/spine.config.json')
@@ -91,7 +112,21 @@ async function loadRegistrationConfig(): Promise<RegistrationConfig | null> {
91
112
  return null
92
113
  }
93
114
  }
94
-
115
+ // CIL[END chunk_id="RegisterPage_loadregistrationconfig_1783662155"]
116
+ // CIL[BEGIN chunk_id="RegisterPage_checkfirstuser_1783662155" type="function" identity="checkFirstUser" parent="none"]
117
+ // CIL[DOC-MACRO purpose="Check if current user is the first user in the system"]
118
+ // CIL[DOC-RESPONSIBILITY responsibility="Determine if registration should use simplified first-user flow"]
119
+ // CIL[DOC-CONTEXT module="src/pages/auth/RegisterPage" layer="frontend-page" related_files="[\".framework/functions/_shared/principal.ts\",\".framework/migrations/025_retype_threads_messages.sql\",\"custom/apps/cortex/pages/operations/OperationsDashboard.tsx\",\".framework/src/pages/admin/RolesPage.tsx\"]"]
120
+ // CIL[DOC-CONTRACT inputs="none" outputs="boolean indicating if first user" errors="API errors" side_effects="API call to check-first-user"]
121
+ // CIL[DOC-MICRO logic_summary="Call invites API to check first user status with fallback" notable_decisions="[]"]
122
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
123
+ // CIL[DOC-STABILITY stability="stable"]
124
+ // CIL[DOC-VECTOR type="context" content="Layer: frontend-page. Direct callers: 1. Direct calls: 0. Related files: .framework/functions/_shared/principal.ts, .framework/migrations/025_retype_threads_messages.sql, custom/apps/cortex/pages/operations/OperationsDashboard.tsx, .framework/src/pages/admin/RolesPage.tsx."]
125
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 1. Total affected: 1."]
126
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
127
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
128
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
129
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
95
130
  async function checkFirstUser(): Promise<boolean> {
96
131
  try {
97
132
  const result = await callInvitesApi('check-first-user')
@@ -100,19 +135,32 @@ async function checkFirstUser(): Promise<boolean> {
100
135
  return false
101
136
  }
102
137
  }
103
-
138
+ // CIL[END chunk_id="RegisterPage_checkfirstuser_1783662155"]
139
+ // CIL[BEGIN chunk_id="RegisterPage_getpostregistrationredirect_1783662155" type="function" identity="getPostRegistrationRedirect" parent="none"]
140
+ // CIL[DOC-MACRO purpose="Determine redirect path after successful registration"]
141
+ // CIL[DOC-RESPONSIBILITY responsibility="Calculate post-registration redirect based on config, URL params, and invite status"]
142
+ // CIL[DOC-CONTEXT module="src/pages/auth/RegisterPage" layer="frontend-page" related_files="[]"]
143
+ // CIL[DOC-CONTRACT inputs="none" outputs="redirect path string" errors="none" side_effects="none"]
144
+ // CIL[DOC-MICRO logic_summary="Redirect logic with URL override support and config fallbacks" notable_decisions="[]"]
145
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
146
+ // CIL[DOC-STABILITY stability="stable"]
147
+ // CIL[DOC-VECTOR type="context" content="Layer: frontend-page. Direct callers: 0. Direct calls: 0. Related files: none."]
148
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
149
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
150
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
151
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
152
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
104
153
  function getPostRegistrationRedirect(
105
154
  config: RegistrationConfig | null,
106
155
  searchParams: URLSearchParams,
107
156
  isInvite: boolean,
157
+ effectiveRole?: string,
108
158
  inviteAccountId?: string
109
159
  ): string {
110
- // Invites: redirect to the account's primary app or default
111
160
  if (isInvite) {
112
161
  return '/'
113
162
  }
114
163
 
115
- // Check for URL override if allowed
116
164
  if (config?.allow_url_override) {
117
165
  const appOverride = searchParams.get('app')
118
166
  if (appOverride && config.apps[appOverride]?.enabled) {
@@ -120,51 +168,63 @@ function getPostRegistrationRedirect(
120
168
  }
121
169
  }
122
170
 
123
- // Default app from config
171
+ const roleForRedirect = effectiveRole || searchParams.get('role') || config?.default_role
172
+ if (roleForRedirect && config?.default_app_by_role?.[roleForRedirect]) {
173
+ const roleBasedApp = config.default_app_by_role[roleForRedirect]
174
+ if (config.apps[roleBasedApp]?.enabled) {
175
+ return config.apps[roleBasedApp].path
176
+ }
177
+ }
178
+
124
179
  if (config?.default_app && config.apps[config.default_app]?.enabled) {
125
180
  return config.apps[config.default_app].path
126
181
  }
127
182
 
128
- // Fallback
129
183
  return '/'
130
184
  }
131
-
132
- // ─── COMPONENT ────────────────────────────────────────────────────────────────
133
-
185
+ // CIL[END chunk_id="RegisterPage_getpostregistrationredirect_1783662155"]
186
+ // CIL[BEGIN chunk_id="RegisterPage_registerpage_1783662155" type="function" identity="RegisterPage" parent="none"]
187
+ // CIL[DOC-MACRO purpose="Handle complex registration flow with invite support, domain checking, and multiple account strategies"]
188
+ // CIL[DOC-RESPONSIBILITY responsibility="Manage registration state, form validation, invite processing, and user creation flows"]
189
+ // CIL[DOC-CONTEXT module="src/pages/auth/RegisterPage" layer="frontend-page" related_files="[]"]
190
+ // CIL[DOC-CONTRACT inputs="none" outputs="registered user session or error states" errors="validation errors, API errors, network errors" side_effects="creates user account, updates auth state"]
191
+ // CIL[DOC-MICRO logic_summary="Complex registration component with multiple flows and validation" notable_decisions="[]"]
192
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
193
+ // CIL[DOC-STABILITY stability="stable"]
194
+ // CIL[DOC-VECTOR type="context" content="Layer: frontend-page. Direct callers: 0. Direct calls: 0. Related files: none."]
195
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
196
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
197
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
198
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
199
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
134
200
  export function RegisterPage() {
135
201
  const [searchParams] = useSearchParams()
136
202
  const navigate = useNavigate()
137
203
  const { refreshUser } = useAuth()
138
204
  const inviteToken = searchParams.get('t')
139
205
 
140
- // Registration config
141
206
  const [regConfig, setRegConfig] = useState<RegistrationConfig | null>(null)
142
207
  const [isFirstUser, setIsFirstUser] = useState<boolean | null>(null)
143
208
 
144
- // Invite preview (when token present)
145
209
  const [invitePreview, setInvitePreview] = useState<InvitePreview | null>(null)
146
210
  const [tokenError, setTokenError] = useState('')
147
211
  const [tokenLoading, setTokenLoading] = useState(!!inviteToken)
148
212
 
149
- // Form fields
150
213
  const [fullName, setFullName] = useState('')
151
214
  const [email, setEmail] = useState('')
152
215
  const [companyName, setCompanyName] = useState('')
153
216
  const [password, setPassword] = useState('')
154
217
 
155
- // UX state
156
218
  const [useType, setUseType] = useState<UseType>('personal')
157
219
  const [domainMatchAccountId, setDomainMatchAccountId] = useState<string | null>(null)
158
220
  const [domainMatchChoice, setDomainMatchChoice] = useState<DomainMatchChoice>(null)
159
221
  const [domainCheckLoading, setDomainCheckLoading] = useState(false)
160
222
 
161
- // Account strategy state
162
223
  const [accountStrategy, setAccountStrategy] = useState<'existing' | 'new'>('new')
163
224
 
164
225
  const [error, setError] = useState('')
165
226
  const [isLoading, setIsLoading] = useState(false)
166
227
 
167
- // ── Load config and check first user on mount ──────────────────────────────
168
228
  useEffect(() => {
169
229
  ;(async () => {
170
230
  const [config, firstUserCheck] = await Promise.all([
@@ -176,7 +236,6 @@ export function RegisterPage() {
176
236
  })()
177
237
  }, [])
178
238
 
179
- // ── Resolve invite token ───────────────────────────────────────────────────
180
239
  useEffect(() => {
181
240
  if (!inviteToken) return
182
241
  ;(async () => {
@@ -188,13 +247,11 @@ export function RegisterPage() {
188
247
  } else {
189
248
  setInvitePreview(result.data)
190
249
  setEmail(result.data.email)
191
- // Pre-fill name from invite stub (editable — user can correct it)
192
250
  if (result.data.full_name) setFullName(result.data.full_name)
193
251
  }
194
252
  })()
195
253
  }, [inviteToken])
196
254
 
197
- // ── Domain check on email blur ─────────────────────────────────────────────
198
255
  const handleEmailBlur = useCallback(async () => {
199
256
  if (!email || !email.includes('@') || inviteToken || useType === 'personal' || isFirstUser) return
200
257
 
@@ -210,13 +267,10 @@ export function RegisterPage() {
210
267
  }
211
268
  }, [email, inviteToken, useType, isFirstUser])
212
269
 
213
- // ── Submit ─────────────────────────────────────────────────────────────────
214
270
  const handleSubmit = async (e: React.FormEvent) => {
215
271
  e.preventDefault()
216
272
  setError('')
217
273
 
218
- // Guard: if registration config hasn't loaded yet, block submission
219
- // This prevents silently falling through to new_account when config is needed
220
274
  if (!isFirstUser && !inviteToken && !regConfig) {
221
275
  setError('Registration configuration not loaded. Please refresh and try again.')
222
276
  return
@@ -225,7 +279,6 @@ export function RegisterPage() {
225
279
  setIsLoading(true)
226
280
 
227
281
  try {
228
- // 1. Supabase auth signup
229
282
  const { data: signUpData, error: signUpError } = await supabase.auth.signUp({
230
283
  email: invitePreview?.email ?? email,
231
284
  password,
@@ -240,123 +293,70 @@ export function RegisterPage() {
240
293
  return
241
294
  }
242
295
 
243
- // 2. First user path — trigger handles everything, navigate to root
244
296
  if (isFirstUser) {
245
- // First user becomes system_admin immediately, go to admin
246
297
  navigate('/spine-framework/admin', { replace: true })
247
298
  return
248
299
  }
249
300
 
250
- // 3. Invite path — trigger handles everything, navigate to account
251
301
  if (inviteToken) {
252
302
  navigate('/', { replace: true })
253
303
  return
254
304
  }
255
305
 
256
- // 4. Walk-up paths — call complete-registration
257
306
  const resolvedEmail = email
258
307
 
259
- // Determine role and account strategy from URL params or config
260
308
  const urlRole = searchParams.get('role')
261
309
  const urlApp = searchParams.get('app')
262
310
 
263
- // Resolve which app config to use: URL param → default_app → none
264
- const effectiveApp = urlApp || regConfig?.default_app
265
-
266
- let effectiveRole = regConfig?.default_role || 'member'
311
+ let effectiveRole = urlRole || regConfig?.default_role || 'member'
312
+ const effectiveApp = urlApp || regConfig?.default_app_by_role?.[effectiveRole] || regConfig?.default_app
267
313
  let effectiveAccountStrategy: 'existing' | 'new' | 'choice' = 'new'
268
314
  let targetAccount: string | undefined
269
315
 
270
- // If an app config is found (via URL param or default_app), apply it
271
316
  if (effectiveApp && regConfig?.apps[effectiveApp]) {
272
317
  const appConfig = regConfig.apps[effectiveApp]
273
318
 
274
- console.log('App config found:', { app: effectiveApp, appConfig })
275
-
276
- // Determine role: URL param (if valid for this app) → app default → config default
277
- if (urlRole && appConfig.roles.includes(urlRole)) {
278
- effectiveRole = urlRole
279
- } else {
280
- effectiveRole = appConfig.default_role
281
- }
282
-
283
- // Determine account strategy
319
+ effectiveRole = urlRole || appConfig.default_role
284
320
  effectiveAccountStrategy = appConfig.account_strategy
285
321
  targetAccount = appConfig.target_account
286
-
287
- console.log('Account strategy resolved:', { effectiveAccountStrategy, targetAccount })
288
- }
289
- // If only URL role is specified (no app), use it directly with default strategy
290
- else if (urlRole) {
291
- effectiveRole = urlRole
292
- effectiveAccountStrategy = 'new'
293
322
  }
294
-
295
- // For 'existing' strategy, always use the target account
296
- if (effectiveAccountStrategy === 'existing') {
297
- setAccountStrategy('existing')
298
- } else {
299
- setAccountStrategy('new')
323
+
324
+ const completeRegistrationParams: any = {
325
+ email: resolvedEmail,
326
+ full_name: fullName,
327
+ app: effectiveApp,
328
+ role: effectiveRole,
329
+ redirect: getPostRegistrationRedirect(regConfig, searchParams, false, effectiveRole),
300
330
  }
301
331
 
302
- if (domainMatchChoice === 'join') {
303
- // Join existing account as access_request
304
- const joinResult = await callInvitesApi('complete-registration', {
305
- path: 'join_request',
306
- email: resolvedEmail,
307
- role_slug: effectiveRole,
308
- }, 'POST')
309
-
310
- if (joinResult.error) {
311
- setError(joinResult.error)
312
- return
313
- }
314
- navigate('/pending-approval', { replace: true })
315
- return
332
+ if (useType === 'company') {
333
+ completeRegistrationParams.account_name = companyName
316
334
  }
317
335
 
318
- // Handle account creation vs existing account assignment
319
- // IMPORTANT: use effectiveAccountStrategy (local var), NOT accountStrategy (React state)
320
- // React state updates are async — accountStrategy may still hold the previous value here
321
- let registrationResult
322
- console.log('Registration decision:', { effectiveAccountStrategy, targetAccount, effectiveRole })
323
-
324
336
  if (effectiveAccountStrategy === 'existing' && targetAccount) {
325
- // Assign to existing account
326
- console.log('Using existing_account path')
327
- registrationResult = await callInvitesApi('complete-registration', {
328
- path: 'existing_account',
329
- email: resolvedEmail,
330
- target_account: targetAccount,
331
- role_slug: effectiveRole,
332
- }, 'POST')
333
- } else if (effectiveAccountStrategy === 'existing' && !targetAccount) {
334
- // Misconfigured: existing strategy but no target account in spine.config.json
335
- throw new Error('Registration misconfigured: account_strategy is "existing" but no target_account is set.')
337
+ completeRegistrationParams.account_id = targetAccount
338
+ completeRegistrationParams.strategy = 'join_request'
339
+ } else if (domainMatchAccountId === '__matched__' && domainMatchChoice === 'join') {
340
+ completeRegistrationParams.strategy = 'join_request'
336
341
  } else {
337
- // Create new account (personal or company)
338
- console.log('Using new_account path')
339
- const accountName = useType === 'personal' ? fullName : companyName
340
- registrationResult = await callInvitesApi('complete-registration', {
341
- path: 'new_account',
342
- email: resolvedEmail,
343
- account_name: accountName,
344
- role_slug: effectiveRole,
345
- }, 'POST')
342
+ completeRegistrationParams.strategy = 'new_account'
346
343
  }
347
344
 
348
- if (registrationResult.error) {
349
- setError(registrationResult.error)
345
+ const result = await callInvitesApi('complete-registration', completeRegistrationParams, 'POST')
346
+
347
+ if (result.error) {
348
+ setError(result.error)
350
349
  return
351
350
  }
352
351
 
353
- // Re-fetch user context so roles are populated (complete-registration
354
- // sets role_id, but the initial auth fetch may have raced ahead of it)
355
- await refreshUser()
352
+ if (result.data?.next === 'pending_approval') {
353
+ navigate('/auth/pending-approval', { replace: true })
354
+ return
355
+ }
356
356
 
357
- // Navigate to configured post-registration destination
358
- const redirectPath = getPostRegistrationRedirect(regConfig, searchParams, false)
357
+ const redirectPath = getPostRegistrationRedirect(regConfig, searchParams, false, effectiveRole)
359
358
  navigate(redirectPath, { replace: true })
359
+
360
360
  } catch (err: any) {
361
361
  setError(err.message || 'Registration failed')
362
362
  } finally {
@@ -364,264 +364,228 @@ export function RegisterPage() {
364
364
  }
365
365
  }
366
366
 
367
- // ── Loading states ───────────────────────────────────────────────────────────
368
- if (tokenLoading || isFirstUser === null) {
369
- return (
370
- <div className="min-h-screen flex items-center justify-center bg-slate-50">
371
- <LoadingSpinner className="w-8 h-8" />
372
- </div>
373
- )
374
- }
367
+ const isInvite = !!inviteToken && !tokenError
368
+ const showPersonalCompanyToggle = !isInvite && !isFirstUser
369
+ const showCompanyName = showPersonalCompanyToggle && useType === 'company'
370
+ const showDomainMatchChoice = domainMatchAccountId === '__matched__' && !isInvite && !isFirstUser
375
371
 
376
- if (tokenError) {
372
+ if (tokenLoading || isFirstUser === null || (!isFirstUser && !isInvite && !regConfig)) {
377
373
  return (
378
- <div className="min-h-screen flex items-center justify-center bg-slate-50 px-4">
379
- <div className="max-w-md w-full text-center space-y-4">
380
- <div className="rounded-md bg-red-50 p-6">
381
- <p className="text-red-800 font-medium">Invite link invalid</p>
382
- <p className="text-red-700 text-sm mt-1">{tokenError}</p>
383
- </div>
384
- <Link to="/login" className="text-sm text-slate-600 hover:text-slate-900">
385
- Back to login
386
- </Link>
374
+ <div className="min-h-screen flex items-center justify-center bg-slate-50">
375
+ <div className="text-center">
376
+ <LoadingSpinner size="lg" />
377
+ <p className="mt-4 text-slate-600">Loading registration...</p>
387
378
  </div>
388
379
  </div>
389
380
  )
390
381
  }
391
382
 
392
- const isInvite = !!inviteToken && !!invitePreview
393
- const showDomainMatchPrompt = domainMatchAccountId && domainMatchChoice === null && useType === 'company' && !isFirstUser
394
- const effectiveEmail = isInvite ? invitePreview.email : email
395
- const showUseTypeToggle = !isInvite && !isFirstUser && accountStrategy === 'new'
396
-
397
- // Header text based on scenario
398
- let headerTitle = 'Create your account'
399
- let headerSubtitle: React.ReactNode = null
400
-
401
- if (isFirstUser) {
402
- headerTitle = 'Create system administrator account'
403
- headerSubtitle = (
404
- <p className="mt-2 text-center text-sm text-slate-500">
405
- You're the first user. This account will have full system access.
406
- </p>
407
- )
408
- } else if (isInvite) {
409
- headerTitle = `Join ${invitePreview.account_name ?? 'the team'}`
410
- if (invitePreview.role_name) {
411
- headerSubtitle = (
412
- <p className="mt-2 text-center text-sm text-slate-500">
413
- You've been invited as <span className="font-medium">{invitePreview.role_name}</span>
414
- </p>
415
- )
416
- }
417
- }
418
-
419
383
  return (
420
384
  <div className="min-h-screen flex items-center justify-center bg-slate-50 py-12 px-4 sm:px-6 lg:px-8">
421
385
  <div className="max-w-md w-full space-y-8">
422
-
423
- {/* Header */}
424
386
  <div>
425
387
  <div className="mx-auto h-12 w-12 flex items-center justify-center rounded-full bg-slate-900">
426
388
  <span className="text-white font-bold text-xl">S</span>
427
389
  </div>
428
390
  <h2 className="mt-6 text-center text-3xl font-extrabold text-slate-900">
429
- {headerTitle}
391
+ {isInvite ? 'Complete your registration' : 'Create your account'}
430
392
  </h2>
431
- {headerSubtitle}
432
- {!isFirstUser && (
393
+ {isInvite && (
433
394
  <p className="mt-2 text-center text-sm text-slate-600">
434
- Already have an account?{' '}
435
- <Link to="/login" className="font-medium text-blue-600 hover:text-blue-500">
436
- Sign in
437
- </Link>
395
+ You've been invited to join{' '}
396
+ {invitePreview?.account_name && (
397
+ <span className="font-medium">{invitePreview.account_name}</span>
398
+ )}
438
399
  </p>
439
400
  )}
440
401
  </div>
441
402
 
403
+ {tokenError && (
404
+ <div className="rounded-md bg-red-50 p-4">
405
+ <div className="text-sm text-red-800">
406
+ Invalid or expired invitation link.{' '}
407
+ <Link to="/register" className="font-medium text-accent-blue hover:text-accent-blue-light">
408
+ Start a new registration
409
+ </Link>
410
+ </div>
411
+ </div>
412
+ )}
413
+
442
414
  <form className="mt-8 space-y-6" onSubmit={handleSubmit}>
443
415
  {error && (
444
416
  <div className="rounded-md bg-red-50 p-4">
445
- <p className="text-sm text-red-800">{error}</p>
417
+ <div className="text-sm text-red-800">{error}</div>
446
418
  </div>
447
419
  )}
448
420
 
449
421
  <div className="space-y-4">
450
- {/* Personal / Company toggle — only for walk-up, not first user */}
451
- {showUseTypeToggle && (
452
- <div>
453
- <label className="block text-sm font-medium text-slate-700 mb-2">
454
- Account type
455
- </label>
456
- <div className="flex gap-3">
457
- {(['personal', 'company'] as UseType[]).map(type => (
458
- <button
459
- key={type}
460
- type="button"
461
- onClick={() => {
462
- setUseType(type)
463
- setDomainMatchAccountId(null)
464
- setDomainMatchChoice(null)
465
- }}
466
- className={`flex-1 py-2 px-4 rounded-[5px] border text-sm font-medium transition-colors ${
467
- useType === type
468
- ? 'bg-slate-900 text-white border-slate-900'
469
- : 'bg-white text-slate-700 border-slate-300 hover:border-slate-400'
470
- }`}
471
- >
472
- {type === 'personal' ? 'Personal' : 'Company'}
473
- </button>
474
- ))}
475
- </div>
476
- </div>
477
- )}
478
-
479
- {/* Full name */}
480
422
  <div>
481
- <label htmlFor="fullName" className="block text-sm font-medium text-slate-700">
423
+ <label htmlFor="full-name" className="block text-sm font-medium text-slate-700">
482
424
  Full name
483
425
  </label>
484
426
  <input
485
- id="fullName"
427
+ id="full-name"
428
+ name="full-name"
486
429
  type="text"
487
430
  required
488
431
  value={fullName}
489
- onChange={e => setFullName(e.target.value)}
490
- className="mt-1 block w-full px-3 py-2 border border-slate-300 rounded-[5px] shadow-sm placeholder-slate-400 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
432
+ onChange={(e) => setFullName(e.target.value)}
433
+ className="mt-1 block w-full px-3 py-2 border border-slate-300 rounded-[5px] shadow-sm placeholder-slate-400 focus:outline-none focus:ring-accent-blue focus:border-accent-blue sm:text-sm"
491
434
  placeholder="Enter your full name"
435
+ disabled={isInvite}
492
436
  />
493
437
  </div>
494
438
 
495
- {/* Email — locked when invite */}
496
439
  <div>
497
440
  <label htmlFor="email" className="block text-sm font-medium text-slate-700">
498
441
  Email address
499
442
  </label>
500
443
  <input
501
444
  id="email"
445
+ name="email"
502
446
  type="email"
447
+ autoComplete="email"
503
448
  required
504
- value={effectiveEmail}
505
- onChange={e => !isInvite && setEmail(e.target.value)}
449
+ value={email}
450
+ onChange={(e) => setEmail(e.target.value)}
506
451
  onBlur={handleEmailBlur}
507
- readOnly={isInvite}
508
- className={`mt-1 block w-full px-3 py-2 border rounded-[5px] shadow-sm placeholder-slate-400 focus:outline-none sm:text-sm ${
509
- isInvite
510
- ? 'bg-slate-50 border-slate-200 text-slate-500 cursor-not-allowed'
511
- : 'border-slate-300 focus:ring-blue-500 focus:border-blue-500'
512
- }`}
452
+ className="mt-1 block w-full px-3 py-2 border border-slate-300 rounded-[5px] shadow-sm placeholder-slate-400 focus:outline-none focus:ring-accent-blue focus:border-accent-blue sm:text-sm"
513
453
  placeholder="Enter your email"
454
+ disabled={isInvite}
514
455
  />
515
456
  {domainCheckLoading && (
516
- <p className="mt-1 text-xs text-slate-500">Checking domain...</p>
457
+ <p className="mt-1 text-sm text-slate-500">Checking domain...</p>
517
458
  )}
518
459
  </div>
519
460
 
520
- {/* Domain match prompt */}
521
- {showDomainMatchPrompt && (
522
- <div className="rounded-md bg-blue-50 border border-blue-200 p-4 space-y-3">
523
- <p className="text-sm text-blue-800 font-medium">
524
- An account for this domain already exists.
525
- </p>
526
- <p className="text-sm text-blue-700">
527
- Would you like to request to join the existing account, or create a new one?
528
- </p>
529
- <div className="flex gap-2">
530
- <button
531
- type="button"
532
- onClick={() => setDomainMatchChoice('join')}
533
- className="flex-1 py-2 px-3 rounded-[5px] bg-blue-600 text-white text-sm font-medium hover:bg-blue-700"
534
- >
535
- Request to join
536
- </button>
537
- <button
538
- type="button"
539
- onClick={() => setDomainMatchChoice('create_new')}
540
- className="flex-1 py-2 px-3 rounded-[5px] border border-blue-300 text-blue-700 text-sm font-medium hover:bg-blue-100"
541
- >
542
- Create new account
543
- </button>
544
- </div>
545
- </div>
546
- )}
547
-
548
- {/* Company name — shown for company type when not joining existing and using new account strategy */}
549
- {!isInvite && useType === 'company' && domainMatchChoice !== 'join' && !isFirstUser && accountStrategy === 'new' && (
461
+ {showCompanyName && (
550
462
  <div>
551
- <label htmlFor="companyName" className="block text-sm font-medium text-slate-700">
463
+ <label htmlFor="company-name" className="block text-sm font-medium text-slate-700">
552
464
  Company name
553
465
  </label>
554
466
  <input
555
- id="companyName"
467
+ id="company-name"
468
+ name="company-name"
556
469
  type="text"
557
- required={useType === 'company'}
470
+ required
558
471
  value={companyName}
559
- onChange={e => setCompanyName(e.target.value)}
560
- className="mt-1 block w-full px-3 py-2 border border-slate-300 rounded-[5px] shadow-sm placeholder-slate-400 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
472
+ onChange={(e) => setCompanyName(e.target.value)}
473
+ className="mt-1 block w-full px-3 py-2 border border-slate-300 rounded-[5px] shadow-sm placeholder-slate-400 focus:outline-none focus:ring-accent-blue focus:border-accent-blue sm:text-sm"
561
474
  placeholder="Enter your company name"
562
475
  />
563
476
  </div>
564
477
  )}
565
478
 
566
- {/* Invite account name — locked */}
567
- {isInvite && invitePreview.account_name && (
568
- <div>
569
- <label className="block text-sm font-medium text-slate-700">Account</label>
570
- <input
571
- type="text"
572
- readOnly
573
- value={invitePreview.account_name}
574
- className="mt-1 block w-full px-3 py-2 border border-slate-200 rounded-[5px] bg-slate-50 text-slate-500 cursor-not-allowed sm:text-sm"
575
- />
576
- </div>
577
- )}
578
-
579
- {/* Password */}
580
479
  <div>
581
480
  <label htmlFor="password" className="block text-sm font-medium text-slate-700">
582
481
  Password
583
482
  </label>
584
483
  <input
585
484
  id="password"
485
+ name="password"
586
486
  type="password"
487
+ autoComplete="new-password"
587
488
  required
588
- minLength={6}
589
489
  value={password}
590
- onChange={e => setPassword(e.target.value)}
591
- className="mt-1 block w-full px-3 py-2 border border-slate-300 rounded-[5px] shadow-sm placeholder-slate-400 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
592
- placeholder="Choose a password (min 6 characters)"
490
+ onChange={(e) => setPassword(e.target.value)}
491
+ className="mt-1 block w-full px-3 py-2 border border-slate-300 rounded-[5px] shadow-sm placeholder-slate-400 focus:outline-none focus:ring-accent-blue focus:border-accent-blue sm:text-sm"
492
+ placeholder="Create a password"
593
493
  />
594
494
  </div>
595
495
  </div>
596
496
 
597
- {/* Join request info banner */}
598
- {domainMatchChoice === 'join' && (
599
- <div className="rounded-md bg-amber-50 border border-amber-200 p-3">
600
- <p className="text-sm text-amber-800">
601
- Your registration will be submitted for approval. You'll gain access once an administrator approves your request.
602
- </p>
497
+ {showPersonalCompanyToggle && (
498
+ <div>
499
+ <label className="block text-sm font-medium text-slate-700 mb-2">
500
+ Account type
501
+ </label>
502
+ <div className="flex space-x-4">
503
+ <label className="flex items-center">
504
+ <input
505
+ type="radio"
506
+ name="use-type"
507
+ value="personal"
508
+ checked={useType === 'personal'}
509
+ onChange={(e) => setUseType(e.target.value as UseType)}
510
+ className="h-4 w-4 text-accent-blue focus:ring-accent-blue border-slate-300"
511
+ />
512
+ <span className="ml-2 text-sm text-slate-700">Personal</span>
513
+ </label>
514
+ <label className="flex items-center">
515
+ <input
516
+ type="radio"
517
+ name="use-type"
518
+ value="company"
519
+ checked={useType === 'company'}
520
+ onChange={(e) => setUseType(e.target.value as UseType)}
521
+ className="h-4 w-4 text-accent-blue focus:ring-accent-blue border-slate-300"
522
+ />
523
+ <span className="ml-2 text-sm text-slate-700">Company</span>
524
+ </label>
525
+ </div>
603
526
  </div>
604
527
  )}
605
528
 
606
- <button
607
- type="submit"
608
- disabled={isLoading}
609
- className="group relative w-full flex justify-center py-2 px-4 border border-transparent rounded-[5px] shadow-sm text-sm font-medium text-white bg-slate-900 hover:bg-slate-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
610
- >
611
- {isLoading ? (
612
- <LoadingSpinner className="w-5 h-5" />
613
- ) : isFirstUser ? (
614
- 'Create System Admin Account'
615
- ) : isInvite ? (
616
- 'Accept Invite'
617
- ) : domainMatchChoice === 'join' ? (
618
- 'Request to Join'
619
- ) : (
620
- 'Create Account'
621
- )}
622
- </button>
529
+ {showDomainMatchChoice && (
530
+ <div className="rounded-md bg-amber-50 p-4">
531
+ <div className="text-sm text-amber-800">
532
+ <p className="font-medium mb-2">We found an existing account for your domain.</p>
533
+ <div className="space-y-2">
534
+ <label className="flex items-center">
535
+ <input
536
+ type="radio"
537
+ name="domain-choice"
538
+ value="join"
539
+ checked={domainMatchChoice === 'join'}
540
+ onChange={(e) => setDomainMatchChoice(e.target.value as DomainMatchChoice)}
541
+ className="h-4 w-4 text-accent-blue focus:ring-accent-blue border-slate-300"
542
+ />
543
+ <span className="ml-2 text-sm text-amber-800">Join existing account</span>
544
+ </label>
545
+ <label className="flex items-center">
546
+ <input
547
+ type="radio"
548
+ name="domain-choice"
549
+ value="create_new"
550
+ checked={domainMatchChoice === 'create_new'}
551
+ onChange={(e) => setDomainMatchChoice(e.target.value as DomainMatchChoice)}
552
+ className="h-4 w-4 text-accent-blue focus:ring-accent-blue border-slate-300"
553
+ />
554
+ <span className="ml-2 text-sm text-amber-800">Create new account</span>
555
+ </label>
556
+ </div>
557
+ </div>
558
+ </div>
559
+ )}
560
+
561
+ <div>
562
+ <button
563
+ type="submit"
564
+ disabled={isLoading || (showDomainMatchChoice && !domainMatchChoice)}
565
+ className="group relative w-full flex justify-center py-2 px-4 border border-transparent rounded-[5px] shadow-sm text-sm font-medium text-white bg-slate-900 hover:bg-slate-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-accent-blue disabled:opacity-50 disabled:cursor-not-allowed"
566
+ >
567
+ {isLoading ? (
568
+ <>
569
+ <LoadingSpinner size="sm" />
570
+ <span className="ml-2">Creating account...</span>
571
+ </>
572
+ ) : (
573
+ 'Create account'
574
+ )}
575
+ </button>
576
+ </div>
577
+
578
+ <div className="text-center">
579
+ <p className="text-sm text-slate-600">
580
+ Already have an account?{' '}
581
+ <Link to="/login" className="font-medium text-accent-blue hover:text-accent-blue-light">
582
+ Sign in
583
+ </Link>
584
+ </p>
585
+ </div>
623
586
  </form>
624
587
  </div>
625
588
  </div>
626
589
  )
627
590
  }
591
+ // CIL[END chunk_id="RegisterPage_registerpage_1783662155"]