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.
- package/.framework/cli/commands/agents.ts +35 -37
- package/.framework/cli/commands/auth.ts +35 -34
- package/.framework/cli/commands/create-app.ts +104 -40
- package/.framework/cli/commands/dev.ts +104 -69
- package/.framework/cli/commands/doctor.ts +179 -69
- package/.framework/cli/commands/generate.ts +83 -143
- package/.framework/cli/commands/init.ts +72 -34
- package/.framework/cli/commands/install-app.ts +260 -125
- package/.framework/cli/commands/items.ts +33 -44
- package/.framework/cli/commands/migrate.ts +122 -40
- package/.framework/cli/commands/migrations.ts +33 -40
- package/.framework/cli/commands/pipelines.ts +31 -39
- package/.framework/cli/commands/status.ts +47 -23
- package/.framework/cli/commands/system.ts +47 -37
- package/.framework/cli/commands/test.ts +112 -68
- package/.framework/cli/commands/validate-app.ts +215 -121
- package/.framework/cli/context.ts +112 -130
- package/.framework/cli/env-loader.ts +47 -18
- package/.framework/cli/index.ts +81 -42
- package/.framework/docs/runtime-types.md +1024 -0
- package/.framework/functions/_shared/agent-runner.ts +1296 -961
- package/.framework/functions/_shared/app-manifest.ts +82 -68
- package/.framework/functions/_shared/audit.ts +135 -83
- package/.framework/functions/_shared/{cil-state.ts → cil.ts} +158 -56
- package/.framework/functions/_shared/db.ts +80 -126
- package/.framework/functions/_shared/hash-api-key.ts +63 -0
- package/.framework/functions/_shared/index.ts +224 -335
- package/.framework/functions/_shared/kb.ts +319 -0
- package/.framework/functions/_shared/llm.ts +92 -0
- package/.framework/functions/_shared/logging.ts +288 -0
- package/.framework/functions/_shared/middleware.ts +329 -365
- package/.framework/functions/_shared/outcomes.ts +36 -0
- package/.framework/functions/_shared/permissions.ts +359 -643
- package/.framework/functions/_shared/pipeline-runner.ts +243 -316
- package/.framework/functions/_shared/principal.ts +376 -468
- package/.framework/functions/_shared/resolve-credentials.ts +102 -0
- package/.framework/functions/_shared/resolve-ids.ts +188 -16
- package/.framework/functions/_shared/retry-deliveries.ts +104 -0
- package/.framework/functions/_shared/schema-utils.ts +611 -373
- package/.framework/functions/_shared/testing.ts +207 -137
- package/.framework/functions/_shared/tracing.ts +276 -0
- package/.framework/functions/_shared/trigger-engine.ts +159 -211
- package/.framework/functions/_shared/webhook-registration.ts +95 -81
- package/.framework/functions/_shared/webhook-registry.ts +153 -75
- package/.framework/functions/account-nodes.ts +31 -53
- package/.framework/functions/adapters/generic-webhook-inbound.ts +56 -0
- package/.framework/functions/adapters/generic-webhook-outbound.ts +43 -0
- package/.framework/functions/adapters/openai-outbound.ts +40 -0
- package/.framework/functions/admin-data.ts +685 -537
- package/.framework/functions/admin-funnel.ts +31 -20
- package/.framework/functions/admin-install-funnel.ts +47 -28
- package/.framework/functions/admin-signals.ts +47 -29
- package/.framework/functions/ai-agents.ts +122 -147
- package/.framework/functions/ai-interactions.ts +180 -90
- package/.framework/functions/api-keys.ts +250 -192
- package/.framework/functions/apps.ts +203 -197
- package/.framework/functions/auth.ts +98 -91
- package/.framework/functions/cil-agent-ingest.ts +773 -0
- package/.framework/functions/cil-agent.ts +592 -0
- package/.framework/functions/cil-callmap.ts +1227 -0
- package/.framework/functions/cil-config.ts +583 -0
- package/.framework/functions/cil-context.ts +445 -0
- package/.framework/functions/cil-doc.ts +1752 -0
- package/.framework/functions/cil-embeddings.ts +1202 -0
- package/.framework/functions/cil-graph.ts +585 -0
- package/.framework/functions/cil-ingest.ts +2416 -0
- package/.framework/functions/cil-pipeline.ts +318 -0
- package/.framework/functions/cil-project.ts +499 -0
- package/.framework/functions/cil-validate.ts +469 -0
- package/.framework/functions/debug-auth.ts +73 -53
- package/.framework/functions/deliveries.ts +117 -0
- package/.framework/functions/embeddings.ts +528 -516
- package/.framework/functions/eval-harness.ts +158 -106
- package/.framework/functions/integration-outbound.ts +311 -0
- package/.framework/functions/integration-routes.ts +240 -466
- package/.framework/functions/integrations.ts +132 -140
- package/.framework/functions/invites.ts +401 -201
- package/.framework/functions/item-progress.ts +113 -53
- package/.framework/functions/kb.ts +28 -0
- package/.framework/functions/logs.ts +239 -222
- package/.framework/functions/observability.ts +308 -259
- package/.framework/functions/pipeline-executions.ts +179 -235
- package/.framework/functions/pipelines.ts +163 -205
- package/.framework/functions/prompt-configs.ts +109 -156
- package/.framework/functions/roles.ts +97 -147
- package/.framework/functions/system-cron.ts +132 -218
- package/.framework/functions/system.ts +112 -52
- package/.framework/functions/tests.ts +32 -28
- package/.framework/functions/timers.ts +162 -210
- package/.framework/functions/triggers.ts +282 -346
- package/.framework/functions/types.ts +283 -400
- package/.framework/migrations/001_core_schema.sql +630 -0
- package/.framework/migrations/002_core_rls_and_functions.sql +532 -0
- package/.framework/migrations/{003_system_data.sql → 003_core_system_data.sql} +26 -153
- package/.framework/migrations/004_core_rpc_and_broker.sql +421 -0
- package/.framework/migrations/005_core_observability.sql +598 -0
- package/.framework/migrations/006_security_hardening_rls.sql +97 -0
- package/.framework/migrations/007_kb_article_list.sql +237 -0
- package/.framework/migrations/008_items_target.sql +8 -0
- package/.framework/migrations/009_roles_slug_unique.sql +11 -0
- package/.framework/public/spine.config.json +91 -0
- package/.framework/scripts/app-install-cli.ts +112 -98
- package/.framework/src/App.tsx +67 -20
- package/.framework/src/apps/admin/index.tsx +97 -27
- package/.framework/src/components/AppWrapper.tsx +50 -11
- package/.framework/src/components/CustomAppLoader.tsx +131 -22
- package/.framework/src/components/admin/AdminListPage.tsx +75 -8
- package/.framework/src/components/admin/AdminSidebar.tsx +148 -36
- package/.framework/src/components/admin/AdminStatsCard.tsx +48 -29
- package/.framework/src/components/admin/PageInstructions.tsx +49 -0
- package/.framework/src/components/admin/SortableTableHeader.tsx +49 -2
- package/.framework/src/components/app-shell/GenericAppShell.tsx +69 -11
- package/.framework/src/components/app-shell/GenericDetailPage.tsx +55 -10
- package/.framework/src/components/app-shell/GenericListPage.tsx +76 -9
- package/.framework/src/components/app-sidebar.tsx +50 -3
- package/.framework/src/components/auth/ProtectedRoute.tsx +53 -45
- package/.framework/src/components/kb/KBArticleEditor.tsx +409 -0
- package/.framework/src/components/kb/KBArticleList.tsx +557 -0
- package/.framework/src/components/layout/AppShell.tsx +54 -16
- package/.framework/src/components/layout/Header.tsx +50 -38
- package/.framework/src/components/layout/Layout.tsx +48 -32
- package/.framework/src/components/layout/Sidebar.tsx +167 -16
- package/.framework/src/components/runtime/DataDetailHeader.tsx +33 -1
- package/.framework/src/components/runtime/DataDetailPage.tsx +289 -4
- package/.framework/src/components/runtime/DataFilters.tsx +78 -1
- package/.framework/src/components/runtime/DataHeader.tsx +32 -29
- package/.framework/src/components/runtime/DataListPage.tsx +161 -2
- package/.framework/src/components/runtime/DataStats.tsx +48 -1
- package/.framework/src/components/runtime/DataTable.tsx +153 -1
- package/.framework/src/components/runtime/SchemaDetailForm.tsx +77 -46
- package/.framework/src/components/runtime/index.ts +16 -9
- package/.framework/src/components/search-form.tsx +33 -1
- package/.framework/src/components/shared/AgentView.tsx +48 -51
- package/.framework/src/components/shared/FieldRenderer.tsx +84 -67
- package/.framework/src/components/shared/InviteManager.tsx +113 -52
- package/.framework/src/components/shared/MemberList.tsx +182 -0
- package/.framework/src/components/shared/SchemaFields.tsx +96 -78
- package/.framework/src/components/ui/DataTable.tsx +86 -56
- package/.framework/src/components/ui/Form.tsx +188 -109
- package/.framework/src/components/ui/ItemCard.tsx +86 -58
- package/.framework/src/components/ui/ItemListView.tsx +88 -61
- package/.framework/src/components/ui/LoadingSpinner.tsx +52 -27
- package/.framework/src/components/ui/Modal.tsx +81 -4
- package/.framework/src/components/ui/RichTextEditor.tsx +101 -9
- package/.framework/src/components/ui/accordion.tsx +97 -5
- package/.framework/src/components/ui/alert-dialog.tsx +225 -13
- package/.framework/src/components/ui/alert.tsx +113 -6
- package/.framework/src/components/ui/aspect-ratio.tsx +49 -2
- package/.framework/src/components/ui/avatar.tsx +129 -7
- package/.framework/src/components/ui/badge.tsx +65 -3
- package/.framework/src/components/ui/breadcrumb.tsx +145 -8
- package/.framework/src/components/ui/button-group.tsx +81 -4
- package/.framework/src/components/ui/button.tsx +49 -2
- package/.framework/src/components/ui/calendar.tsx +65 -3
- package/.framework/src/components/ui/card.tsx +145 -8
- package/.framework/src/components/ui/carousel.tsx +129 -7
- package/.framework/src/components/ui/chart.tsx +161 -10
- package/.framework/src/components/ui/checkbox.tsx +49 -2
- package/.framework/src/components/ui/collapsible.tsx +81 -5
- package/.framework/src/components/ui/combobox.tsx +305 -20
- package/.framework/src/components/ui/command.tsx +177 -11
- package/.framework/src/components/ui/context-menu.tsx +273 -17
- package/.framework/src/components/ui/dialog.tsx +209 -27
- package/.framework/src/components/ui/direction.tsx +64 -4
- package/.framework/src/components/ui/drawer.tsx +193 -12
- package/.framework/src/components/ui/dropdown-menu.tsx +273 -18
- package/.framework/src/components/ui/empty.tsx +145 -9
- package/.framework/src/components/ui/field.tsx +209 -14
- package/.framework/src/components/ui/hover-card.tsx +81 -4
- package/.framework/src/components/ui/input-group.tsx +162 -10
- package/.framework/src/components/ui/input-otp.tsx +98 -5
- package/.framework/src/components/ui/input.tsx +49 -2
- package/.framework/src/components/ui/item.tsx +225 -13
- package/.framework/src/components/ui/kbd.tsx +65 -3
- package/.framework/src/components/ui/label.tsx +49 -2
- package/.framework/src/components/ui/menubar.tsx +289 -17
- package/.framework/src/components/ui/native-select.tsx +97 -5
- package/.framework/src/components/ui/navigation-menu.tsx +177 -10
- package/.framework/src/components/ui/pagination.tsx +161 -9
- package/.framework/src/components/ui/popover.tsx +145 -8
- package/.framework/src/components/ui/progress.tsx +49 -2
- package/.framework/src/components/ui/radio-group.tsx +65 -3
- package/.framework/src/components/ui/resizable.tsx +82 -4
- package/.framework/src/components/ui/scroll-area.tsx +65 -3
- package/.framework/src/components/ui/select.tsx +193 -11
- package/.framework/src/components/ui/separator.tsx +49 -2
- package/.framework/src/components/ui/sheet.tsx +193 -11
- package/.framework/src/components/ui/sidebar.tsx +481 -38
- package/.framework/src/components/ui/skeleton.tsx +49 -2
- package/.framework/src/components/ui/slider.tsx +49 -2
- package/.framework/src/components/ui/sonner.tsx +49 -2
- package/.framework/src/components/ui/spinner.tsx +49 -2
- package/.framework/src/components/ui/switch.tsx +49 -2
- package/.framework/src/components/ui/table-primitives.tsx +160 -33
- package/.framework/src/components/ui/table.tsx +161 -9
- package/.framework/src/components/ui/tabs.tsx +113 -6
- package/.framework/src/components/ui/textarea.tsx +49 -2
- package/.framework/src/components/ui/toggle-group.tsx +81 -4
- package/.framework/src/components/ui/toggle.tsx +65 -3
- package/.framework/src/components/ui/tooltip.tsx +97 -5
- package/.framework/src/contexts/AppContext.tsx +129 -45
- package/.framework/src/contexts/AuthContext.tsx +187 -144
- package/.framework/src/hooks/use-mobile.ts +48 -2
- package/.framework/src/hooks/useApi.ts +208 -159
- package/.framework/src/hooks/useAppPath.ts +32 -23
- package/.framework/src/hooks/useApps.ts +51 -20
- package/.framework/src/hooks/useEntityList.ts +32 -78
- package/.framework/src/hooks/useEntityRecord.ts +35 -96
- package/.framework/src/hooks/useForm.ts +32 -92
- package/.framework/src/hooks/useListSchema.ts +49 -78
- package/.framework/src/hooks/useSchemaRecord.ts +114 -105
- package/.framework/src/lib/api.ts +195 -116
- package/.framework/src/lib/supabase.ts +98 -64
- package/.framework/src/lib/utils.ts +287 -151
- package/.framework/src/main.tsx +49 -3
- package/.framework/src/pages/DashboardPage.tsx +52 -29
- package/.framework/src/pages/NotFoundPage.tsx +32 -10
- package/.framework/src/pages/SetupPage.tsx +32 -12
- package/.framework/src/pages/admin/AIAgentDetailPage.tsx +119 -10
- package/.framework/src/pages/admin/AIAgentsPage.tsx +48 -19
- package/.framework/src/pages/admin/APIKeyDetailPage.tsx +187 -17
- package/.framework/src/pages/admin/APIKeysPage.tsx +98 -22
- package/.framework/src/pages/admin/AlertsConfigPage.tsx +113 -26
- package/.framework/src/pages/admin/AppDetailPage.tsx +125 -8
- package/.framework/src/pages/admin/AppsPage.tsx +49 -2
- package/.framework/src/pages/admin/DeliveriesPage.tsx +196 -0
- package/.framework/src/pages/admin/DesignedPage.tsx +32 -18
- package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +215 -484
- package/.framework/src/pages/admin/EmbeddingsPage.tsx +116 -319
- package/.framework/src/pages/admin/ExtendedShadcnTestPage.tsx +34 -9
- package/.framework/src/pages/admin/IncrementalShadcnTestPage.tsx +34 -9
- package/.framework/src/pages/admin/IntegratedDashboard.tsx +32 -16
- package/.framework/src/pages/admin/IntegrationDetailPage.tsx +183 -26
- package/.framework/src/pages/admin/IntegrationsPage.tsx +48 -19
- package/.framework/src/pages/admin/InvitesPage.tsx +48 -14
- package/.framework/src/pages/admin/LogsPage.tsx +422 -75
- package/.framework/src/pages/admin/MinimalShadcnTestPage.tsx +34 -9
- package/.framework/src/pages/admin/ObservabilityDashboard.tsx +113 -24
- package/.framework/src/pages/admin/PipelineDetailPage.tsx +206 -34
- package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +50 -17
- package/.framework/src/pages/admin/PipelinesPage.tsx +160 -21
- package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +378 -88
- package/.framework/src/pages/admin/PromptConfigsPage.tsx +125 -16
- package/.framework/src/pages/admin/ProperlyDesignedPage.tsx +49 -10
- package/.framework/src/pages/admin/RoleDetailPage.tsx +130 -10
- package/.framework/src/pages/admin/RolesPage.tsx +171 -18
- package/.framework/src/pages/admin/SelectTestPage.tsx +34 -9
- package/.framework/src/pages/admin/ShadcnTestPage.tsx +49 -11
- package/.framework/src/pages/admin/SimpleDashboard.tsx +49 -10
- package/.framework/src/pages/admin/TestRunDetailPage.tsx +49 -4
- package/.framework/src/pages/admin/TestingDashboard.tsx +98 -28
- package/.framework/src/pages/admin/TimerDetailPage.tsx +143 -33
- package/.framework/src/pages/admin/TimersPage.tsx +48 -20
- package/.framework/src/pages/admin/TriggerDetailPage.tsx +118 -30
- package/.framework/src/pages/admin/TriggersPage.tsx +64 -19
- package/.framework/src/pages/admin/TypeDetailPage.tsx +138 -15
- package/.framework/src/pages/admin/TypesPage.tsx +48 -20
- package/.framework/src/pages/admin/cil/CILAgentPage.tsx +465 -0
- package/.framework/src/pages/admin/cil/CILChunkDetailPage.tsx +787 -0
- package/.framework/src/pages/admin/cil/CILDashboardPage.tsx +699 -0
- package/.framework/src/pages/admin/cil/CILFileListPage.tsx +647 -0
- package/.framework/src/pages/admin/cil/CILGraphPage.tsx +616 -0
- package/.framework/src/pages/admin/cil/CILIngestionPage.tsx +1402 -0
- package/.framework/src/pages/admin/cil/CILProjectsPage.tsx +614 -0
- package/.framework/src/pages/admin/cil/CILSearchPage.tsx +571 -0
- package/.framework/src/pages/admin/cil/FlexiblePipelinePage.tsx +731 -0
- package/.framework/src/pages/admin/kb/KBAdminPage.tsx +64 -0
- package/.framework/src/pages/auth/AuthConfirmPage.tsx +52 -51
- package/.framework/src/pages/auth/ForgotPasswordPage.tsx +31 -22
- package/.framework/src/pages/auth/LoginPage.tsx +50 -23
- package/.framework/src/pages/auth/PendingApprovalPage.tsx +31 -15
- package/.framework/src/pages/auth/RegisterPage.tsx +281 -317
- package/.framework/src/pages/spine-framework/APIPage.tsx +33 -0
- package/.framework/src/pages/spine-framework/CLIPage.tsx +33 -0
- package/.framework/src/types/auth.ts +128 -71
- package/.framework/src/types/types.ts +373 -157
- package/custom/.devin/skills/spine-workbench-discovery/SKILL.md +39 -0
- package/custom/.devin/skills/spine-workbench-idea/SKILL.md +36 -0
- package/custom/.devin/skills/spine-workbench-implement/SKILL.md +46 -0
- package/custom/.devin/skills/spine-workbench-initiative/SKILL.md +28 -0
- package/custom/.devin/skills/spine-workbench-plan/SKILL.md +38 -0
- package/docs/README.md +9 -130
- package/netlify.toml +5 -2
- package/package.json +13 -25
- package/scripts/kb-ingest-docs.ts +203 -0
- package/spine.config.json +65 -4
- package/.framework/migrations/001_schema.sql +0 -425
- package/.framework/migrations/002_rls_and_functions.sql +0 -763
- package/.framework/migrations/004_enhancements.sql +0 -43
- package/.framework/migrations/005_auth_user_trigger.sql +0 -105
- package/.framework/migrations/006_fix_current_actor_id.sql +0 -21
- package/.framework/migrations/016_invites.sql +0 -27
- package/.framework/migrations/017_fix_get_account_apps.sql +0 -78
- package/.framework/migrations/018_apps_required_roles.sql +0 -17
- package/.framework/migrations/019_triggers_unique_constraint.sql +0 -13
- package/.framework/migrations/020_get_account_apps_role_filter.sql +0 -117
- package/.framework/migrations/021_app_domains.sql +0 -112
- package/.framework/migrations/022_route_prefix_domain_unique.sql +0 -21
- package/.framework/migrations/023_match_embeddings_rpc.sql +0 -78
- package/.framework/migrations/024_messages_account_id.sql +0 -9
- package/.framework/migrations/025_retype_threads_messages.sql +0 -240
- package/.framework/migrations/026_fix_rls_policies.sql +0 -47
- package/.framework/migrations/027_embedding_lifecycle.sql +0 -176
- package/.framework/migrations/028_fix_message_data_permissions.sql +0 -49
- package/.framework/migrations/029_tighten_support_triage_prompt.sql +0 -41
- package/.framework/migrations/030_messages_updated_by.sql +0 -6
- package/.framework/migrations/031_seed_upsert_constraints.sql +0 -35
- package/docs/APPS.md +0 -232
- package/docs/ARCHITECTURE.md +0 -246
- package/docs/INSTALL.md +0 -155
- package/docs/design-schema-spec.md +0 -1837
- package/docs/dev-guides/01-create-an-app.md +0 -183
- package/docs/dev-guides/02-create-a-function.md +0 -178
- package/docs/dev-guides/03-create-a-component.md +0 -178
- package/docs/dev-guides/04-webhook-handlers.md +0 -129
- package/docs/dev-guides/05-testing.md +0 -168
- package/docs/dev-guides/README.md +0 -40
- package/docs/npm-packages-plan.md +0 -288
- package/docs/spine-core--api-reference.md +0 -822
|
@@ -1,44 +1,38 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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 (
|
|
303
|
-
|
|
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
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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
|
-
|
|
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
|
-
|
|
349
|
-
|
|
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
|
-
|
|
354
|
-
|
|
355
|
-
|
|
352
|
+
if (result.data?.next === 'pending_approval') {
|
|
353
|
+
navigate('/auth/pending-approval', { replace: true })
|
|
354
|
+
return
|
|
355
|
+
}
|
|
356
356
|
|
|
357
|
-
|
|
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
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
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 (
|
|
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
|
|
379
|
-
<div className="
|
|
380
|
-
<
|
|
381
|
-
|
|
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
|
-
{
|
|
391
|
+
{isInvite ? 'Complete your registration' : 'Create your account'}
|
|
430
392
|
</h2>
|
|
431
|
-
{
|
|
432
|
-
{!isFirstUser && (
|
|
393
|
+
{isInvite && (
|
|
433
394
|
<p className="mt-2 text-center text-sm text-slate-600">
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
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
|
-
<
|
|
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="
|
|
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="
|
|
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
|
|
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={
|
|
505
|
-
onChange={e =>
|
|
449
|
+
value={email}
|
|
450
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
506
451
|
onBlur={handleEmailBlur}
|
|
507
|
-
|
|
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-
|
|
457
|
+
<p className="mt-1 text-sm text-slate-500">Checking domain...</p>
|
|
517
458
|
)}
|
|
518
459
|
</div>
|
|
519
460
|
|
|
520
|
-
{
|
|
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="
|
|
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="
|
|
467
|
+
id="company-name"
|
|
468
|
+
name="company-name"
|
|
556
469
|
type="text"
|
|
557
|
-
required
|
|
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
|
|
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
|
|
592
|
-
placeholder="
|
|
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
|
-
{
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
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
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
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"]
|