spine-framework 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.framework/README.md +129 -0
- package/.framework/cli/bin.cjs +14 -0
- package/.framework/cli/commands/agents.ts +153 -0
- package/.framework/cli/commands/auth.ts +94 -0
- package/.framework/cli/commands/create-app.ts +185 -0
- package/.framework/cli/commands/dev.ts +295 -0
- package/.framework/cli/commands/doctor.ts +442 -0
- package/.framework/cli/commands/generate.ts +332 -0
- package/.framework/cli/commands/init.ts +272 -0
- package/.framework/cli/commands/install-app.ts +391 -0
- package/.framework/cli/commands/items.ts +253 -0
- package/.framework/cli/commands/migrations.ts +141 -0
- package/.framework/cli/commands/pipelines.ts +166 -0
- package/.framework/cli/commands/status.ts +197 -0
- package/.framework/cli/commands/system.ts +184 -0
- package/.framework/cli/commands/test.ts +227 -0
- package/.framework/cli/commands/uninstall-app.ts +166 -0
- package/.framework/cli/context.ts +268 -0
- package/.framework/cli/env-loader.ts +36 -0
- package/.framework/cli/index.ts +106 -0
- package/.framework/cli/welcome.cjs +45 -0
- package/.framework/docs/API.md +384 -0
- package/.framework/docs/STABILITY.md +52 -0
- package/.framework/docs/admin-routes.md +76 -0
- package/.framework/docs/api-docs-progress.md +38 -0
- package/.framework/docs/api-governance.md +146 -0
- package/.framework/docs/api-testing-results.md +212 -0
- package/.framework/docs/apis/admin-configs.md +567 -0
- package/.framework/docs/apis/admin-data.md +272 -0
- package/.framework/docs/apis/index.md +231 -0
- package/.framework/docs/apis/internal.md +295 -0
- package/.framework/docs/apis/runtime.md +537 -0
- package/.framework/docs/assembly-launch-guide.md +138 -0
- package/.framework/docs/audit-results.md +590 -0
- package/.framework/docs/authorization-model.md +170 -0
- package/.framework/docs/db-api-inventory.md +95 -0
- package/.framework/docs/examples/custom-app/README.md +77 -0
- package/.framework/docs/examples/custom-function/README.md +27 -0
- package/.framework/docs/examples/custom-function/handler.ts +48 -0
- package/.framework/docs/examples/custom-webhook/README.md +68 -0
- package/.framework/docs/gap-remediation-backlog.md +103 -0
- package/.framework/docs/guides/cli-guide.md +224 -0
- package/.framework/docs/guides/getting-started.md +103 -0
- package/.framework/docs/guides/import-guide.md +193 -0
- package/.framework/docs/guides/testing-guide.md +229 -0
- package/.framework/docs/permission-examples.md +326 -0
- package/.framework/docs/ui-adoption-verification.md +111 -0
- package/.framework/docs/ui-api-coverage.md +84 -0
- package/.framework/docs/v2-compatibility-audit.md +228 -0
- package/.framework/functions/.gitkeep +1 -0
- package/.framework/functions/_shared/agent-runner.ts +1097 -0
- package/.framework/functions/_shared/app-manifest.ts +184 -0
- package/.framework/functions/_shared/audit.ts +150 -0
- package/.framework/functions/_shared/db.ts +174 -0
- package/.framework/functions/_shared/index.ts +382 -0
- package/.framework/functions/_shared/middleware.ts +490 -0
- package/.framework/functions/_shared/permissions.ts +1325 -0
- package/.framework/functions/_shared/pipeline-runner.ts +731 -0
- package/.framework/functions/_shared/principal.ts +760 -0
- package/.framework/functions/_shared/schema-utils.ts +967 -0
- package/.framework/functions/_shared/testing.ts +258 -0
- package/.framework/functions/_shared/trigger-engine.ts +425 -0
- package/.framework/functions/_shared/webhook-registration.ts +168 -0
- package/.framework/functions/_shared/webhook-registry.ts +129 -0
- package/.framework/functions/account-nodes.ts +111 -0
- package/.framework/functions/admin-data.ts +606 -0
- package/.framework/functions/ai-agents.ts +323 -0
- package/.framework/functions/api-keys.ts +376 -0
- package/.framework/functions/apps.ts +483 -0
- package/.framework/functions/auth.ts +196 -0
- package/.framework/functions/debug-auth.ts +107 -0
- package/.framework/functions/embeddings.ts +556 -0
- package/.framework/functions/integration-routes.ts +523 -0
- package/.framework/functions/integrations.ts +319 -0
- package/.framework/functions/item-progress.ts +272 -0
- package/.framework/functions/logs.ts +438 -0
- package/.framework/functions/observability.ts +275 -0
- package/.framework/functions/pipeline-executions.ts +494 -0
- package/.framework/functions/pipelines.ts +485 -0
- package/.framework/functions/prompt-configs.ts +339 -0
- package/.framework/functions/roles.ts +387 -0
- package/.framework/functions/system-cron.ts +742 -0
- package/.framework/functions/system.ts +323 -0
- package/.framework/functions/tests.ts +119 -0
- package/.framework/functions/timers.ts +357 -0
- package/.framework/functions/triggers.ts +563 -0
- package/.framework/functions/types.ts +604 -0
- package/.framework/migrations/000_foundation.sql +1256 -0
- package/.framework/migrations/001_seed.sql +92 -0
- package/.framework/migrations/002_seed_constraints.sql +13 -0
- package/.framework/migrations/003_auth_user_trigger.sql +59 -0
- package/.framework/src/App.tsx +126 -0
- package/.framework/src/apps/admin/index.tsx +173 -0
- package/.framework/src/components/AppWrapper.tsx +56 -0
- package/.framework/src/components/CustomAppLoader.tsx +116 -0
- package/.framework/src/components/admin/AdminListPage.tsx +151 -0
- package/.framework/src/components/admin/AdminSidebar.tsx +166 -0
- package/.framework/src/components/admin/AdminStatsCard.tsx +62 -0
- package/.framework/src/components/admin/SortableTableHeader.tsx +42 -0
- package/.framework/src/components/app-shell/GenericAppShell.tsx +181 -0
- package/.framework/src/components/app-shell/GenericDetailPage.tsx +200 -0
- package/.framework/src/components/app-shell/GenericListPage.tsx +116 -0
- package/.framework/src/components/app-sidebar.tsx +228 -0
- package/.framework/src/components/auth/ProtectedRoute.tsx +88 -0
- package/.framework/src/components/layout/AppShell.tsx +91 -0
- package/.framework/src/components/layout/Header.tsx +88 -0
- package/.framework/src/components/layout/Layout.tsx +95 -0
- package/.framework/src/components/layout/Sidebar.tsx +329 -0
- package/.framework/src/components/runtime/DataDetailHeader.tsx +77 -0
- package/.framework/src/components/runtime/DataDetailPage.tsx +171 -0
- package/.framework/src/components/runtime/DataFilters.tsx +91 -0
- package/.framework/src/components/runtime/DataHeader.tsx +68 -0
- package/.framework/src/components/runtime/DataListPage.tsx +124 -0
- package/.framework/src/components/runtime/DataStats.tsx +70 -0
- package/.framework/src/components/runtime/DataTable.tsx +174 -0
- package/.framework/src/components/runtime/SchemaDetailForm.tsx +134 -0
- package/.framework/src/components/runtime/index.ts +18 -0
- package/.framework/src/components/search-form.tsx +29 -0
- package/.framework/src/components/shared/AgentView.tsx +213 -0
- package/.framework/src/components/shared/FieldRenderer.tsx +478 -0
- package/.framework/src/components/shared/SchemaFields.tsx +226 -0
- package/.framework/src/components/ui/DataTable.tsx +343 -0
- package/.framework/src/components/ui/Form.tsx +281 -0
- package/.framework/src/components/ui/ItemCard.tsx +296 -0
- package/.framework/src/components/ui/ItemListView.tsx +308 -0
- package/.framework/src/components/ui/LoadingSpinner.tsx +52 -0
- package/.framework/src/components/ui/Modal.tsx +61 -0
- package/.framework/src/components/ui/RichTextEditor.tsx +210 -0
- package/.framework/src/components/ui/accordion.tsx +82 -0
- package/.framework/src/components/ui/alert-dialog.tsx +197 -0
- package/.framework/src/components/ui/alert.tsx +76 -0
- package/.framework/src/components/ui/aspect-ratio.tsx +11 -0
- package/.framework/src/components/ui/avatar.tsx +110 -0
- package/.framework/src/components/ui/badge.tsx +49 -0
- package/.framework/src/components/ui/breadcrumb.tsx +122 -0
- package/.framework/src/components/ui/button-group.tsx +83 -0
- package/.framework/src/components/ui/button.tsx +65 -0
- package/.framework/src/components/ui/calendar.tsx +222 -0
- package/.framework/src/components/ui/card.tsx +100 -0
- package/.framework/src/components/ui/carousel.tsx +240 -0
- package/.framework/src/components/ui/chart.tsx +373 -0
- package/.framework/src/components/ui/checkbox.tsx +31 -0
- package/.framework/src/components/ui/collapsible.tsx +33 -0
- package/.framework/src/components/ui/combobox.tsx +299 -0
- package/.framework/src/components/ui/command.tsx +193 -0
- package/.framework/src/components/ui/context-menu.tsx +261 -0
- package/.framework/src/components/ui/dialog.tsx +165 -0
- package/.framework/src/components/ui/direction.tsx +22 -0
- package/.framework/src/components/ui/drawer.tsx +132 -0
- package/.framework/src/components/ui/dropdown-menu.tsx +269 -0
- package/.framework/src/components/ui/empty.tsx +104 -0
- package/.framework/src/components/ui/field.tsx +238 -0
- package/.framework/src/components/ui/hover-card.tsx +42 -0
- package/.framework/src/components/ui/input-group.tsx +153 -0
- package/.framework/src/components/ui/input-otp.tsx +87 -0
- package/.framework/src/components/ui/input.tsx +19 -0
- package/.framework/src/components/ui/item.tsx +196 -0
- package/.framework/src/components/ui/kbd.tsx +26 -0
- package/.framework/src/components/ui/label.tsx +22 -0
- package/.framework/src/components/ui/menubar.tsx +277 -0
- package/.framework/src/components/ui/native-select.tsx +61 -0
- package/.framework/src/components/ui/navigation-menu.tsx +164 -0
- package/.framework/src/components/ui/pagination.tsx +129 -0
- package/.framework/src/components/ui/popover.tsx +87 -0
- package/.framework/src/components/ui/progress.tsx +31 -0
- package/.framework/src/components/ui/radio-group.tsx +42 -0
- package/.framework/src/components/ui/resizable.tsx +50 -0
- package/.framework/src/components/ui/scroll-area.tsx +53 -0
- package/.framework/src/components/ui/select.tsx +195 -0
- package/.framework/src/components/ui/separator.tsx +26 -0
- package/.framework/src/components/ui/sheet.tsx +145 -0
- package/.framework/src/components/ui/sidebar.tsx +706 -0
- package/.framework/src/components/ui/skeleton.tsx +13 -0
- package/.framework/src/components/ui/slider.tsx +59 -0
- package/.framework/src/components/ui/sonner.tsx +47 -0
- package/.framework/src/components/ui/spinner.tsx +10 -0
- package/.framework/src/components/ui/switch.tsx +33 -0
- package/.framework/src/components/ui/table-primitives.tsx +141 -0
- package/.framework/src/components/ui/table.tsx +114 -0
- package/.framework/src/components/ui/tabs.tsx +90 -0
- package/.framework/src/components/ui/textarea.tsx +18 -0
- package/.framework/src/components/ui/toggle-group.tsx +89 -0
- package/.framework/src/components/ui/toggle.tsx +45 -0
- package/.framework/src/components/ui/tooltip.tsx +57 -0
- package/.framework/src/contexts/AppContext.tsx +133 -0
- package/.framework/src/contexts/AuthContext.tsx +371 -0
- package/.framework/src/hooks/use-mobile.ts +19 -0
- package/.framework/src/hooks/useApi.ts +526 -0
- package/.framework/src/hooks/useApps.ts +114 -0
- package/.framework/src/hooks/useEntityList.ts +190 -0
- package/.framework/src/hooks/useEntityRecord.ts +308 -0
- package/.framework/src/hooks/useForm.ts +307 -0
- package/.framework/src/hooks/useListSchema.ts +264 -0
- package/.framework/src/hooks/useSchemaRecord.ts +223 -0
- package/.framework/src/index.css +128 -0
- package/.framework/src/lib/api.ts +156 -0
- package/.framework/src/lib/supabase.ts +94 -0
- package/.framework/src/lib/utils.ts +317 -0
- package/.framework/src/main.tsx +27 -0
- package/.framework/src/pages/DashboardPage.tsx +181 -0
- package/.framework/src/pages/NotFoundPage.tsx +39 -0
- package/.framework/src/pages/admin/AIAgentDetailPage.tsx +161 -0
- package/.framework/src/pages/admin/AIAgentsPage.tsx +318 -0
- package/.framework/src/pages/admin/APIKeyDetailPage.tsx +199 -0
- package/.framework/src/pages/admin/APIKeysPage.tsx +303 -0
- package/.framework/src/pages/admin/AlertsConfigPage.tsx +523 -0
- package/.framework/src/pages/admin/AppDetailPage.tsx +493 -0
- package/.framework/src/pages/admin/AppsPage.tsx +355 -0
- package/.framework/src/pages/admin/DesignedPage.tsx +491 -0
- package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +534 -0
- package/.framework/src/pages/admin/EmbeddingsPage.tsx +424 -0
- package/.framework/src/pages/admin/ExtendedShadcnTestPage.tsx +176 -0
- package/.framework/src/pages/admin/IncrementalShadcnTestPage.tsx +109 -0
- package/.framework/src/pages/admin/IntegratedDashboard.tsx +402 -0
- package/.framework/src/pages/admin/IntegrationDetailPage.tsx +187 -0
- package/.framework/src/pages/admin/IntegrationsPage.tsx +301 -0
- package/.framework/src/pages/admin/LogsPage.tsx +283 -0
- package/.framework/src/pages/admin/MinimalShadcnTestPage.tsx +85 -0
- package/.framework/src/pages/admin/ObservabilityDashboard.tsx +470 -0
- package/.framework/src/pages/admin/PipelineDetailPage.tsx +183 -0
- package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +279 -0
- package/.framework/src/pages/admin/PipelinesPage.tsx +390 -0
- package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +299 -0
- package/.framework/src/pages/admin/PromptConfigsPage.tsx +292 -0
- package/.framework/src/pages/admin/ProperlyDesignedPage.tsx +434 -0
- package/.framework/src/pages/admin/RoleDetailPage.tsx +273 -0
- package/.framework/src/pages/admin/RolesPage.tsx +292 -0
- package/.framework/src/pages/admin/SelectTestPage.tsx +61 -0
- package/.framework/src/pages/admin/ShadcnTestPage.tsx +588 -0
- package/.framework/src/pages/admin/SimpleDashboard.tsx +387 -0
- package/.framework/src/pages/admin/TestRunDetailPage.tsx +172 -0
- package/.framework/src/pages/admin/TestingDashboard.tsx +257 -0
- package/.framework/src/pages/admin/TimerDetailPage.tsx +151 -0
- package/.framework/src/pages/admin/TimersPage.tsx +376 -0
- package/.framework/src/pages/admin/TriggerDetailPage.tsx +149 -0
- package/.framework/src/pages/admin/TriggersPage.tsx +381 -0
- package/.framework/src/pages/admin/TypeDetailPage.tsx +694 -0
- package/.framework/src/pages/admin/TypesPage.tsx +295 -0
- package/.framework/src/pages/auth/LoginPage.tsx +188 -0
- package/.framework/src/pages/auth/RegisterPage.tsx +163 -0
- package/.framework/src/pages/spine-framework/APIPage.tsx +17 -0
- package/.framework/src/pages/spine-framework/CLIPage.tsx +25 -0
- package/.framework/src/types/auth.ts +125 -0
- package/.framework/src/types/types.ts +407 -0
- package/STRUCTURE.md +150 -0
- package/config/components.json +25 -0
- package/config/deno.lock +108 -0
- package/config/package-lock.json +17183 -0
- package/config/postcss.config.cjs +10 -0
- package/config/tailwind.config.cjs +78 -0
- package/config/tsconfig.build.json +32 -0
- package/config/tsconfig.cli.json +18 -0
- package/config/tsconfig.json +41 -0
- package/config/tsconfig.node.json +17 -0
- package/config/tsconfig.node.tsbuildinfo +1 -0
- package/config/tsconfig.tsbuildinfo +1 -0
- package/config/typedoc.json +16 -0
- package/config/vite.config.d.ts +2 -0
- package/config/vite.config.ts +72 -0
- package/dist/cli/commands/agents.d.ts +39 -0
- package/dist/cli/commands/agents.d.ts.map +1 -0
- package/dist/cli/commands/auth.d.ts +36 -0
- package/dist/cli/commands/auth.d.ts.map +1 -0
- package/dist/cli/commands/create-app.d.ts +23 -0
- package/dist/cli/commands/create-app.d.ts.map +1 -0
- package/dist/cli/commands/dev.d.ts +39 -0
- package/dist/cli/commands/dev.d.ts.map +1 -0
- package/dist/cli/commands/doctor.d.ts +42 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -0
- package/dist/cli/commands/generate.d.ts +36 -0
- package/dist/cli/commands/generate.d.ts.map +1 -0
- package/dist/cli/commands/init.d.ts +30 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/install-app.d.ts +30 -0
- package/dist/cli/commands/install-app.d.ts.map +1 -0
- package/dist/cli/commands/items.d.ts +45 -0
- package/dist/cli/commands/items.d.ts.map +1 -0
- package/dist/cli/commands/migrations.d.ts +41 -0
- package/dist/cli/commands/migrations.d.ts.map +1 -0
- package/dist/cli/commands/pipelines.d.ts +40 -0
- package/dist/cli/commands/pipelines.d.ts.map +1 -0
- package/dist/cli/commands/status.d.ts +23 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/system.d.ts +29 -0
- package/dist/cli/commands/system.d.ts.map +1 -0
- package/dist/cli/commands/test.d.ts +46 -0
- package/dist/cli/commands/test.d.ts.map +1 -0
- package/dist/cli/commands/uninstall-app.d.ts +23 -0
- package/dist/cli/commands/uninstall-app.d.ts.map +1 -0
- package/dist/cli/context.d.ts +88 -0
- package/dist/cli/context.d.ts.map +1 -0
- package/dist/cli/env-loader.d.ts +14 -0
- package/dist/cli/env-loader.d.ts.map +1 -0
- package/dist/cli/index.d.ts +41 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/functions/_shared/agent-runner.d.ts +156 -0
- package/dist/functions/_shared/agent-runner.d.ts.map +1 -0
- package/dist/functions/_shared/app-manifest.d.ts +68 -0
- package/dist/functions/_shared/app-manifest.d.ts.map +1 -0
- package/dist/functions/_shared/audit.d.ts +91 -0
- package/dist/functions/_shared/audit.d.ts.map +1 -0
- package/dist/functions/_shared/db.d.ts +125 -0
- package/dist/functions/_shared/db.d.ts.map +1 -0
- package/dist/functions/_shared/index.d.ts +298 -0
- package/dist/functions/_shared/index.d.ts.map +1 -0
- package/dist/functions/_shared/middleware.d.ts +315 -0
- package/dist/functions/_shared/middleware.d.ts.map +1 -0
- package/dist/functions/_shared/permissions.d.ts +626 -0
- package/dist/functions/_shared/permissions.d.ts.map +1 -0
- package/dist/functions/_shared/pipeline-runner.d.ts +124 -0
- package/dist/functions/_shared/pipeline-runner.d.ts.map +1 -0
- package/dist/functions/_shared/principal.d.ts +284 -0
- package/dist/functions/_shared/principal.d.ts.map +1 -0
- package/dist/functions/_shared/schema-utils.d.ts +181 -0
- package/dist/functions/_shared/schema-utils.d.ts.map +1 -0
- package/dist/functions/_shared/testing.d.ts +172 -0
- package/dist/functions/_shared/testing.d.ts.map +1 -0
- package/dist/functions/_shared/trigger-engine.d.ts +140 -0
- package/dist/functions/_shared/trigger-engine.d.ts.map +1 -0
- package/dist/functions/_shared/webhook-registration.d.ts +81 -0
- package/dist/functions/_shared/webhook-registration.d.ts.map +1 -0
- package/dist/functions/_shared/webhook-registry.d.ts +57 -0
- package/dist/functions/_shared/webhook-registry.d.ts.map +1 -0
- package/dist/functions/account-nodes.d.ts +48 -0
- package/dist/functions/account-nodes.d.ts.map +1 -0
- package/dist/functions/admin-data.d.ts +178 -0
- package/dist/functions/admin-data.d.ts.map +1 -0
- package/dist/functions/ai-agents.d.ts +125 -0
- package/dist/functions/ai-agents.d.ts.map +1 -0
- package/dist/functions/api-keys.d.ts +140 -0
- package/dist/functions/api-keys.d.ts.map +1 -0
- package/dist/functions/apps.d.ts +163 -0
- package/dist/functions/apps.d.ts.map +1 -0
- package/dist/functions/auth.d.ts +74 -0
- package/dist/functions/auth.d.ts.map +1 -0
- package/dist/functions/debug-auth.d.ts +33 -0
- package/dist/functions/debug-auth.d.ts.map +1 -0
- package/dist/functions/embeddings.d.ts +205 -0
- package/dist/functions/embeddings.d.ts.map +1 -0
- package/dist/functions/integration-routes.d.ts +45 -0
- package/dist/functions/integration-routes.d.ts.map +1 -0
- package/dist/functions/integrations.d.ts +124 -0
- package/dist/functions/integrations.d.ts.map +1 -0
- package/dist/functions/item-progress.d.ts +41 -0
- package/dist/functions/item-progress.d.ts.map +1 -0
- package/dist/functions/logs.d.ts +162 -0
- package/dist/functions/logs.d.ts.map +1 -0
- package/dist/functions/observability.d.ts +123 -0
- package/dist/functions/observability.d.ts.map +1 -0
- package/dist/functions/pipeline-executions.d.ts +190 -0
- package/dist/functions/pipeline-executions.d.ts.map +1 -0
- package/dist/functions/pipelines.d.ts +171 -0
- package/dist/functions/pipelines.d.ts.map +1 -0
- package/dist/functions/prompt-configs.d.ts +125 -0
- package/dist/functions/prompt-configs.d.ts.map +1 -0
- package/dist/functions/roles.d.ts +118 -0
- package/dist/functions/roles.d.ts.map +1 -0
- package/dist/functions/system-cron.d.ts +65 -0
- package/dist/functions/system-cron.d.ts.map +1 -0
- package/dist/functions/system.d.ts +29 -0
- package/dist/functions/system.d.ts.map +1 -0
- package/dist/functions/tests.d.ts +28 -0
- package/dist/functions/tests.d.ts.map +1 -0
- package/dist/functions/timers.d.ts +139 -0
- package/dist/functions/timers.d.ts.map +1 -0
- package/dist/functions/triggers.d.ts +203 -0
- package/dist/functions/triggers.d.ts.map +1 -0
- package/dist/functions/types.d.ts +151 -0
- package/dist/functions/types.d.ts.map +1 -0
- package/dist/src/types/types.d.ts +364 -0
- package/dist/src/types/types.d.ts.map +1 -0
- package/package.json +192 -0
- package/scripts/app-install-cli.ts +286 -0
- package/scripts/assemble-frontend.sh +79 -0
- package/scripts/assemble-functions.sh +62 -0
- package/scripts/assemble.sh +35 -0
- package/scripts/boundary-check.sh +106 -0
- package/scripts/build-manifest.sh +80 -0
- package/scripts/check-core-integrity.sh +82 -0
- package/scripts/ingest-chunks.cjs +202 -0
- package/scripts/kb-chunk-parser.cjs +312 -0
- package/scripts/kb-chunk-parser.ts +330 -0
- package/scripts/load-test-app-install.ts +484 -0
- package/scripts/netlify-dev-wrapper.sh +22 -0
- package/scripts/verify-integrity.sh +69 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module src/pages/admin/TypesPage
|
|
3
|
+
* @audience installer
|
|
4
|
+
* @layer frontend-page
|
|
5
|
+
* @stability stable
|
|
6
|
+
*
|
|
7
|
+
* Admin list page for item/account/person types. Fetches all types via
|
|
8
|
+
* `/api/types?action=list`, then applies client-side search, category
|
|
9
|
+
* filter (`all` | `system` | `custom` | `active`), and sort. Renders
|
|
10
|
+
* inside `AdminListPage` with four stat cards and a sortable table.
|
|
11
|
+
* Row clicks navigate to `/spine-framework/admin/configs/types/:id`.
|
|
12
|
+
*
|
|
13
|
+
* @seeAlso src/components/admin/AdminListPage.tsx
|
|
14
|
+
* @seeAlso src/pages/admin/TypeDetailPage.tsx
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import React, { useState } from 'react'
|
|
18
|
+
import { useApi } from '../../hooks/useApi'
|
|
19
|
+
import { apiFetch } from '../../lib/api'
|
|
20
|
+
import {
|
|
21
|
+
Plus,
|
|
22
|
+
Box,
|
|
23
|
+
FileText,
|
|
24
|
+
Calendar,
|
|
25
|
+
Settings,
|
|
26
|
+
CheckCircle
|
|
27
|
+
} from 'lucide-react'
|
|
28
|
+
import { LoadingSpinner } from '../../components/ui/LoadingSpinner'
|
|
29
|
+
import { Button } from '../../components/ui/button'
|
|
30
|
+
import { AdminListPage } from '../../components/admin/AdminListPage'
|
|
31
|
+
import { SortableTableHeader } from '../../components/admin/SortableTableHeader'
|
|
32
|
+
import { formatDateTime } from '../../lib/utils'
|
|
33
|
+
|
|
34
|
+
interface Type {
|
|
35
|
+
id: string
|
|
36
|
+
name: string
|
|
37
|
+
slug: string
|
|
38
|
+
kind: string
|
|
39
|
+
description?: string
|
|
40
|
+
icon?: string
|
|
41
|
+
color?: string
|
|
42
|
+
schema: {
|
|
43
|
+
fields: Record<string, {
|
|
44
|
+
type: string
|
|
45
|
+
label?: string
|
|
46
|
+
required?: boolean
|
|
47
|
+
options?: string[]
|
|
48
|
+
}>
|
|
49
|
+
}
|
|
50
|
+
ownership: string
|
|
51
|
+
is_active: boolean
|
|
52
|
+
created_at: string
|
|
53
|
+
updated_at: string
|
|
54
|
+
app_id?: string | null
|
|
55
|
+
app?: any
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function TypesPage() {
|
|
59
|
+
const [searchTerm, setSearchTerm] = useState('')
|
|
60
|
+
const [selectedCategory, setSelectedCategory] = useState('all')
|
|
61
|
+
const [sortKey, setSortKey] = useState('name')
|
|
62
|
+
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc')
|
|
63
|
+
|
|
64
|
+
// Fetch types from API
|
|
65
|
+
const { data: types, loading, error, refetch } = useApi<Type[]>(
|
|
66
|
+
async ({ signal }: { signal?: AbortSignal }) => {
|
|
67
|
+
const response = await apiFetch('/api/types?action=list', { signal })
|
|
68
|
+
if (!response.ok) throw new Error('Failed to fetch types')
|
|
69
|
+
const result = await response.json()
|
|
70
|
+
return result.data || []
|
|
71
|
+
},
|
|
72
|
+
{ immediate: true }
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
const categories = [
|
|
76
|
+
{ value: 'all', label: 'All Types' },
|
|
77
|
+
{ value: 'system', label: 'System Types' },
|
|
78
|
+
{ value: 'custom', label: 'Custom Types' },
|
|
79
|
+
{ value: 'active', label: 'Active Only' },
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
const filteredTypes = (types || []).filter(type => {
|
|
83
|
+
const matchesSearch = type.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
84
|
+
(type.description && type.description.toLowerCase().includes(searchTerm.toLowerCase()))
|
|
85
|
+
|
|
86
|
+
let matchesCategory = true
|
|
87
|
+
if (selectedCategory === 'system') {
|
|
88
|
+
matchesCategory = type.ownership === 'system'
|
|
89
|
+
} else if (selectedCategory === 'custom') {
|
|
90
|
+
matchesCategory = type.ownership !== 'system'
|
|
91
|
+
} else if (selectedCategory === 'active') {
|
|
92
|
+
matchesCategory = type.is_active
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return matchesSearch && matchesCategory
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
// Helper functions
|
|
99
|
+
const getCategoryBadgeColor = (type: Type) => {
|
|
100
|
+
if (type.ownership === 'system') {
|
|
101
|
+
return 'bg-purple-50 text-purple-700'
|
|
102
|
+
}
|
|
103
|
+
return 'bg-primary/10 text-primary'
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const getStatusBadgeColor = (isActive: boolean) => {
|
|
107
|
+
return isActive
|
|
108
|
+
? 'bg-green-50 text-green-700'
|
|
109
|
+
: 'bg-muted text-muted-foreground'
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const handleSort = (key: string) => {
|
|
113
|
+
if (sortKey === key) {
|
|
114
|
+
setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc')
|
|
115
|
+
} else {
|
|
116
|
+
setSortKey(key)
|
|
117
|
+
setSortDirection('asc')
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const handleRowClick = (type: Type) => {
|
|
122
|
+
window.location.href = `/spine-framework/admin/configs/types/${type.id}`
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Sort types
|
|
126
|
+
const sortedTypes = [...(filteredTypes || [])].sort((a, b) => {
|
|
127
|
+
let aValue: any = a[sortKey as keyof Type]
|
|
128
|
+
let bValue: any = b[sortKey as keyof Type]
|
|
129
|
+
|
|
130
|
+
if (typeof aValue === 'string') {
|
|
131
|
+
return sortDirection === 'asc'
|
|
132
|
+
? aValue.localeCompare(bValue)
|
|
133
|
+
: bValue.localeCompare(aValue)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (typeof aValue === 'number') {
|
|
137
|
+
return sortDirection === 'asc' ? aValue - bValue : bValue - aValue
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return 0
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
const statsCards = [
|
|
144
|
+
{
|
|
145
|
+
title: 'Total Types',
|
|
146
|
+
value: (types || []).length,
|
|
147
|
+
icon: Settings,
|
|
148
|
+
iconColor: 'text-primary',
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
title: 'Custom Types',
|
|
152
|
+
value: (types || []).filter(t => t.ownership !== 'system').length,
|
|
153
|
+
icon: Box,
|
|
154
|
+
iconColor: 'text-green-600',
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
title: 'Active Types',
|
|
158
|
+
value: (types || []).filter(t => t.is_active).length,
|
|
159
|
+
icon: CheckCircle,
|
|
160
|
+
iconColor: 'text-orange-600',
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
title: 'Total Fields',
|
|
164
|
+
value: (types || []).reduce((sum, t) => sum + Object.keys(t.schema?.fields || {}).length, 0),
|
|
165
|
+
icon: FileText,
|
|
166
|
+
iconColor: 'text-purple-600',
|
|
167
|
+
}
|
|
168
|
+
]
|
|
169
|
+
|
|
170
|
+
const filters = [
|
|
171
|
+
{
|
|
172
|
+
label: 'Category',
|
|
173
|
+
value: selectedCategory,
|
|
174
|
+
options: categories,
|
|
175
|
+
onChange: setSelectedCategory
|
|
176
|
+
}
|
|
177
|
+
]
|
|
178
|
+
|
|
179
|
+
return (
|
|
180
|
+
<AdminListPage
|
|
181
|
+
title="Types"
|
|
182
|
+
description="Manage item types and their schemas"
|
|
183
|
+
newButtonText="New Type"
|
|
184
|
+
newButtonHref="/spine-framework/admin/configs/types/new"
|
|
185
|
+
statsCards={statsCards}
|
|
186
|
+
searchPlaceholder="Search types..."
|
|
187
|
+
searchValue={searchTerm}
|
|
188
|
+
onSearchChange={setSearchTerm}
|
|
189
|
+
filters={filters}
|
|
190
|
+
loading={loading}
|
|
191
|
+
error={error}
|
|
192
|
+
onRetry={refetch}
|
|
193
|
+
emptyMessage="No types found"
|
|
194
|
+
emptyIcon={Settings}
|
|
195
|
+
>
|
|
196
|
+
{sortedTypes.length === 0 ? (
|
|
197
|
+
<div className="p-8 text-center">
|
|
198
|
+
<Settings className="mx-auto h-12 w-12 text-muted-foreground" />
|
|
199
|
+
<h3 className="mt-2 text-sm font-medium text-foreground">No types found</h3>
|
|
200
|
+
<p className="mt-1 text-sm text-muted-foreground">
|
|
201
|
+
Try adjusting your search or filters
|
|
202
|
+
</p>
|
|
203
|
+
</div>
|
|
204
|
+
) : (
|
|
205
|
+
<table className="min-w-full divide-y divide-border">
|
|
206
|
+
<thead className="bg-muted">
|
|
207
|
+
<tr>
|
|
208
|
+
<SortableTableHeader
|
|
209
|
+
title="Type"
|
|
210
|
+
sortKey="name"
|
|
211
|
+
currentSortKey={sortKey}
|
|
212
|
+
currentSortDirection={sortDirection}
|
|
213
|
+
onSort={handleSort}
|
|
214
|
+
/>
|
|
215
|
+
<SortableTableHeader
|
|
216
|
+
title="Category"
|
|
217
|
+
sortKey="ownership"
|
|
218
|
+
currentSortKey={sortKey}
|
|
219
|
+
currentSortDirection={sortDirection}
|
|
220
|
+
onSort={handleSort}
|
|
221
|
+
/>
|
|
222
|
+
<SortableTableHeader
|
|
223
|
+
title="Status"
|
|
224
|
+
sortKey="is_active"
|
|
225
|
+
currentSortKey={sortKey}
|
|
226
|
+
currentSortDirection={sortDirection}
|
|
227
|
+
onSort={handleSort}
|
|
228
|
+
/>
|
|
229
|
+
<SortableTableHeader
|
|
230
|
+
title="Fields"
|
|
231
|
+
sortKey="schema"
|
|
232
|
+
currentSortKey={sortKey}
|
|
233
|
+
currentSortDirection={sortDirection}
|
|
234
|
+
onSort={handleSort}
|
|
235
|
+
/>
|
|
236
|
+
<SortableTableHeader
|
|
237
|
+
title="Created"
|
|
238
|
+
sortKey="created_at"
|
|
239
|
+
currentSortKey={sortKey}
|
|
240
|
+
currentSortDirection={sortDirection}
|
|
241
|
+
onSort={handleSort}
|
|
242
|
+
/>
|
|
243
|
+
<th className="relative px-6 py-3">
|
|
244
|
+
<span className="sr-only">Actions</span>
|
|
245
|
+
</th>
|
|
246
|
+
</tr>
|
|
247
|
+
</thead>
|
|
248
|
+
<tbody className="bg-background divide-y divide-border">
|
|
249
|
+
{sortedTypes.map((type) => (
|
|
250
|
+
<tr
|
|
251
|
+
key={type.id}
|
|
252
|
+
className="hover:bg-muted/50 cursor-pointer transition-colors"
|
|
253
|
+
onClick={() => handleRowClick(type)}
|
|
254
|
+
>
|
|
255
|
+
<td className="px-6 py-4 whitespace-nowrap">
|
|
256
|
+
<div>
|
|
257
|
+
<div className="font-medium text-foreground">
|
|
258
|
+
<span className="text-primary hover:text-primary/80">
|
|
259
|
+
{type.name}
|
|
260
|
+
</span>
|
|
261
|
+
</div>
|
|
262
|
+
<div className="text-sm text-muted-foreground">
|
|
263
|
+
{type.description || type.slug}
|
|
264
|
+
<span className="mx-1.5 text-muted-foreground">·</span>
|
|
265
|
+
{Object.keys(type.schema?.fields || {}).length} fields
|
|
266
|
+
</div>
|
|
267
|
+
</div>
|
|
268
|
+
</td>
|
|
269
|
+
<td className="px-6 py-4 whitespace-nowrap">
|
|
270
|
+
<span className={`inline-flex px-2 py-0.5 text-xs font-medium rounded-md ${getCategoryBadgeColor(type)}`}>
|
|
271
|
+
{type.ownership === 'system' ? 'System' : 'Custom'}
|
|
272
|
+
</span>
|
|
273
|
+
</td>
|
|
274
|
+
<td className="px-6 py-4 whitespace-nowrap">
|
|
275
|
+
<span className={`inline-flex px-2 py-0.5 text-xs font-medium rounded-md ${getStatusBadgeColor(type.is_active)}`}>
|
|
276
|
+
{type.is_active ? 'Active' : 'Inactive'}
|
|
277
|
+
</span>
|
|
278
|
+
</td>
|
|
279
|
+
<td className="px-6 py-4 whitespace-nowrap text-sm text-muted-foreground">
|
|
280
|
+
{Object.keys(type.schema?.fields || {}).length}
|
|
281
|
+
</td>
|
|
282
|
+
<td className="px-6 py-4 whitespace-nowrap text-sm text-muted-foreground">
|
|
283
|
+
{formatDateTime(type.created_at)}
|
|
284
|
+
</td>
|
|
285
|
+
<td className="px-6 py-4 whitespace-nowrap text-right">
|
|
286
|
+
<span className="text-muted-foreground">→</span>
|
|
287
|
+
</td>
|
|
288
|
+
</tr>
|
|
289
|
+
))}
|
|
290
|
+
</tbody>
|
|
291
|
+
</table>
|
|
292
|
+
)}
|
|
293
|
+
</AdminListPage>
|
|
294
|
+
)
|
|
295
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module src/pages/auth/LoginPage
|
|
3
|
+
* @audience installer
|
|
4
|
+
* @layer frontend-page
|
|
5
|
+
* @stability stable
|
|
6
|
+
*
|
|
7
|
+
* Unauthenticated login page. Renders a centred card with email/password
|
|
8
|
+
* inputs. On submit, calls `AuthContext.login(email, password)` and
|
|
9
|
+
* navigates to `/dashboard` on success. Displays inline error text on
|
|
10
|
+
* failure. Shows a `LoadingSpinner` inside the submit button while the
|
|
11
|
+
* auth request is in-flight.
|
|
12
|
+
*
|
|
13
|
+
* @seeAlso src/contexts/AuthContext.tsx (login implementation)
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import React, { useState, useEffect } from 'react'
|
|
17
|
+
import { Link, useNavigate } from 'react-router-dom'
|
|
18
|
+
import { useAuth } from '../../contexts/AuthContext'
|
|
19
|
+
import { useAppsRegistry } from '../../contexts/AppContext'
|
|
20
|
+
import { LoadingSpinner } from '../../components/ui/LoadingSpinner'
|
|
21
|
+
|
|
22
|
+
export function LoginPage() {
|
|
23
|
+
const [email, setEmail] = useState('')
|
|
24
|
+
const [password, setPassword] = useState('')
|
|
25
|
+
const [error, setError] = useState('')
|
|
26
|
+
const [isLoading, setIsLoading] = useState(false)
|
|
27
|
+
|
|
28
|
+
const { user, login } = useAuth()
|
|
29
|
+
const navigate = useNavigate()
|
|
30
|
+
const { routableApps: apps, loading } = useAppsRegistry()
|
|
31
|
+
|
|
32
|
+
// Redirect logged-in users to their qualified app
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (user && !loading) {
|
|
35
|
+
// System admin gets special handling
|
|
36
|
+
if (user.roles?.includes('system_admin')) {
|
|
37
|
+
navigate('/spine-framework/admin', { replace: true })
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Find app matching user's role
|
|
42
|
+
const userRole = user.roles?.[0] // Single role per person
|
|
43
|
+
if (userRole) {
|
|
44
|
+
const matchingApp = apps.find(app => app.min_role === userRole)
|
|
45
|
+
if (matchingApp?.route_prefix) {
|
|
46
|
+
navigate(matchingApp.route_prefix, { replace: true })
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// No matching app - show 404
|
|
52
|
+
// The 404 will be handled by the catch-all route in App.tsx
|
|
53
|
+
}
|
|
54
|
+
}, [user, loading, apps, navigate])
|
|
55
|
+
|
|
56
|
+
// Show loading while checking auth state (but allow redirect logic to run)
|
|
57
|
+
if (loading) {
|
|
58
|
+
return (
|
|
59
|
+
<div className="min-h-screen flex items-center justify-center bg-slate-50">
|
|
60
|
+
<div className="text-center">
|
|
61
|
+
<LoadingSpinner className="w-8 h-8 mx-auto mb-4" />
|
|
62
|
+
<p className="text-slate-600">Checking authentication...</p>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
69
|
+
e.preventDefault()
|
|
70
|
+
setError('')
|
|
71
|
+
setIsLoading(true)
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
console.log('Attempting login...')
|
|
75
|
+
await login(email, password)
|
|
76
|
+
// Redirect will be handled by useEffect above
|
|
77
|
+
console.log('Login successful, redirect will be handled by useEffect...')
|
|
78
|
+
} catch (err: any) {
|
|
79
|
+
console.error('Login error:', err)
|
|
80
|
+
setError(err.message || 'Login failed')
|
|
81
|
+
} finally {
|
|
82
|
+
console.log('Login process completed, setting loading to false')
|
|
83
|
+
setIsLoading(false)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<div className="min-h-screen flex items-center justify-center bg-slate-50 py-12 px-4 sm:px-6 lg:px-8">
|
|
89
|
+
<div className="max-w-md w-full space-y-8">
|
|
90
|
+
<div>
|
|
91
|
+
<div className="mx-auto h-12 w-12 flex items-center justify-center rounded-full bg-slate-900">
|
|
92
|
+
<span className="text-white font-bold text-xl">S</span>
|
|
93
|
+
</div>
|
|
94
|
+
<h2 className="mt-6 text-center text-3xl font-extrabold text-slate-900">
|
|
95
|
+
Sign in to Spine
|
|
96
|
+
</h2>
|
|
97
|
+
<p className="mt-2 text-center text-sm text-slate-600">
|
|
98
|
+
Or{' '}
|
|
99
|
+
<Link to="/register" className="font-medium text-accent-blue hover:text-accent-blue-light">
|
|
100
|
+
create a new account
|
|
101
|
+
</Link>
|
|
102
|
+
</p>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<form className="mt-8 space-y-6" onSubmit={handleSubmit}>
|
|
106
|
+
{error && (
|
|
107
|
+
<div className="rounded-md bg-red-50 p-4">
|
|
108
|
+
<div className="text-sm text-red-800">{error}</div>
|
|
109
|
+
</div>
|
|
110
|
+
)}
|
|
111
|
+
|
|
112
|
+
<div className="space-y-4">
|
|
113
|
+
<div>
|
|
114
|
+
<label htmlFor="email" className="block text-sm font-medium text-slate-700">
|
|
115
|
+
Email address
|
|
116
|
+
</label>
|
|
117
|
+
<input
|
|
118
|
+
id="email"
|
|
119
|
+
name="email"
|
|
120
|
+
type="email"
|
|
121
|
+
autoComplete="email"
|
|
122
|
+
required
|
|
123
|
+
value={email}
|
|
124
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
125
|
+
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"
|
|
126
|
+
placeholder="Enter your email"
|
|
127
|
+
/>
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
<div>
|
|
131
|
+
<label htmlFor="password" className="block text-sm font-medium text-slate-700">
|
|
132
|
+
Password
|
|
133
|
+
</label>
|
|
134
|
+
<input
|
|
135
|
+
id="password"
|
|
136
|
+
name="password"
|
|
137
|
+
type="password"
|
|
138
|
+
autoComplete="current-password"
|
|
139
|
+
required
|
|
140
|
+
value={password}
|
|
141
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
142
|
+
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"
|
|
143
|
+
placeholder="Enter your password"
|
|
144
|
+
/>
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
|
|
148
|
+
<div className="flex items-center justify-between">
|
|
149
|
+
<div className="flex items-center">
|
|
150
|
+
<input
|
|
151
|
+
id="remember-me"
|
|
152
|
+
name="remember-me"
|
|
153
|
+
type="checkbox"
|
|
154
|
+
className="h-4 w-4 text-accent-blue focus:ring-accent-blue border-slate-300 rounded"
|
|
155
|
+
/>
|
|
156
|
+
<label htmlFor="remember-me" className="ml-2 block text-sm text-slate-900">
|
|
157
|
+
Remember me
|
|
158
|
+
</label>
|
|
159
|
+
</div>
|
|
160
|
+
|
|
161
|
+
<div className="text-sm">
|
|
162
|
+
<Link to="/forgot-password" className="font-medium text-accent-blue hover:text-accent-blue-light">
|
|
163
|
+
Forgot your password?
|
|
164
|
+
</Link>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
<div>
|
|
169
|
+
<button
|
|
170
|
+
type="submit"
|
|
171
|
+
disabled={isLoading}
|
|
172
|
+
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"
|
|
173
|
+
>
|
|
174
|
+
{isLoading ? (
|
|
175
|
+
<>
|
|
176
|
+
<LoadingSpinner size="sm" />
|
|
177
|
+
<span className="ml-2">Signing in...</span>
|
|
178
|
+
</>
|
|
179
|
+
) : (
|
|
180
|
+
'Sign in'
|
|
181
|
+
)}
|
|
182
|
+
</button>
|
|
183
|
+
</div>
|
|
184
|
+
</form>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
)
|
|
188
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module src/pages/auth/RegisterPage
|
|
3
|
+
* @audience installer
|
|
4
|
+
* @layer frontend-page
|
|
5
|
+
* @stability stable
|
|
6
|
+
*
|
|
7
|
+
* Self-registration page. Renders a centred card with email/password/name
|
|
8
|
+
* inputs. On submit, calls Supabase signUp and navigates to /login with
|
|
9
|
+
* a confirmation message.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import React, { useState } from 'react'
|
|
13
|
+
import { Link, useNavigate } from 'react-router-dom'
|
|
14
|
+
import { useAuth } from '../../contexts/AuthContext'
|
|
15
|
+
import { LoadingSpinner } from '../../components/ui/LoadingSpinner'
|
|
16
|
+
import { supabase } from '../../lib/supabase'
|
|
17
|
+
|
|
18
|
+
export function RegisterPage() {
|
|
19
|
+
const [email, setEmail] = useState('')
|
|
20
|
+
const [password, setPassword] = useState('')
|
|
21
|
+
const [fullName, setFullName] = useState('')
|
|
22
|
+
const [error, setError] = useState('')
|
|
23
|
+
const [success, setSuccess] = useState('')
|
|
24
|
+
const [isLoading, setIsLoading] = useState(false)
|
|
25
|
+
|
|
26
|
+
const navigate = useNavigate()
|
|
27
|
+
|
|
28
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
29
|
+
e.preventDefault()
|
|
30
|
+
setError('')
|
|
31
|
+
setSuccess('')
|
|
32
|
+
setIsLoading(true)
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const { error: signUpError } = await supabase.auth.signUp({
|
|
36
|
+
email,
|
|
37
|
+
password,
|
|
38
|
+
options: {
|
|
39
|
+
data: {
|
|
40
|
+
full_name: fullName,
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
if (signUpError) {
|
|
46
|
+
setError(signUpError.message)
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
setSuccess('Account created! Check your email for a confirmation link.')
|
|
51
|
+
setTimeout(() => navigate('/login'), 3000)
|
|
52
|
+
} catch (err: any) {
|
|
53
|
+
setError(err.message || 'Registration failed')
|
|
54
|
+
} finally {
|
|
55
|
+
setIsLoading(false)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<div className="min-h-screen flex items-center justify-center bg-slate-50 py-12 px-4 sm:px-6 lg:px-8">
|
|
61
|
+
<div className="max-w-md w-full space-y-8">
|
|
62
|
+
<div>
|
|
63
|
+
<div className="mx-auto h-12 w-12 flex items-center justify-center rounded-full bg-slate-900">
|
|
64
|
+
<span className="text-white font-bold text-xl">S</span>
|
|
65
|
+
</div>
|
|
66
|
+
<h2 className="mt-6 text-center text-3xl font-extrabold text-slate-900">
|
|
67
|
+
Create your account
|
|
68
|
+
</h2>
|
|
69
|
+
<p className="mt-2 text-center text-sm text-slate-600">
|
|
70
|
+
Already have an account?{' '}
|
|
71
|
+
<Link to="/login" className="font-medium text-accent-blue hover:text-accent-blue-light">
|
|
72
|
+
Sign in
|
|
73
|
+
</Link>
|
|
74
|
+
</p>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<form className="mt-8 space-y-6" onSubmit={handleSubmit}>
|
|
78
|
+
{error && (
|
|
79
|
+
<div className="rounded-md bg-red-50 p-4">
|
|
80
|
+
<div className="text-sm text-red-800">{error}</div>
|
|
81
|
+
</div>
|
|
82
|
+
)}
|
|
83
|
+
|
|
84
|
+
{success && (
|
|
85
|
+
<div className="rounded-md bg-green-50 p-4">
|
|
86
|
+
<div className="text-sm text-green-800">{success}</div>
|
|
87
|
+
</div>
|
|
88
|
+
)}
|
|
89
|
+
|
|
90
|
+
<div className="space-y-4">
|
|
91
|
+
<div>
|
|
92
|
+
<label htmlFor="fullName" className="block text-sm font-medium text-slate-700">
|
|
93
|
+
Full name
|
|
94
|
+
</label>
|
|
95
|
+
<input
|
|
96
|
+
id="fullName"
|
|
97
|
+
name="fullName"
|
|
98
|
+
type="text"
|
|
99
|
+
required
|
|
100
|
+
value={fullName}
|
|
101
|
+
onChange={(e) => setFullName(e.target.value)}
|
|
102
|
+
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"
|
|
103
|
+
placeholder="Enter your full name"
|
|
104
|
+
/>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<div>
|
|
108
|
+
<label htmlFor="email" className="block text-sm font-medium text-slate-700">
|
|
109
|
+
Email address
|
|
110
|
+
</label>
|
|
111
|
+
<input
|
|
112
|
+
id="email"
|
|
113
|
+
name="email"
|
|
114
|
+
type="email"
|
|
115
|
+
autoComplete="email"
|
|
116
|
+
required
|
|
117
|
+
value={email}
|
|
118
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
119
|
+
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"
|
|
120
|
+
placeholder="Enter your email"
|
|
121
|
+
/>
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
<div>
|
|
125
|
+
<label htmlFor="password" className="block text-sm font-medium text-slate-700">
|
|
126
|
+
Password
|
|
127
|
+
</label>
|
|
128
|
+
<input
|
|
129
|
+
id="password"
|
|
130
|
+
name="password"
|
|
131
|
+
type="password"
|
|
132
|
+
autoComplete="new-password"
|
|
133
|
+
required
|
|
134
|
+
minLength={6}
|
|
135
|
+
value={password}
|
|
136
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
137
|
+
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"
|
|
138
|
+
placeholder="Choose a password (min 6 characters)"
|
|
139
|
+
/>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<div>
|
|
144
|
+
<button
|
|
145
|
+
type="submit"
|
|
146
|
+
disabled={isLoading}
|
|
147
|
+
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"
|
|
148
|
+
>
|
|
149
|
+
{isLoading ? (
|
|
150
|
+
<>
|
|
151
|
+
<LoadingSpinner size="sm" />
|
|
152
|
+
<span className="ml-2">Creating account...</span>
|
|
153
|
+
</>
|
|
154
|
+
) : (
|
|
155
|
+
'Create account'
|
|
156
|
+
)}
|
|
157
|
+
</button>
|
|
158
|
+
</div>
|
|
159
|
+
</form>
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
)
|
|
163
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Layout } from '../../components/layout/Layout'
|
|
2
|
+
|
|
3
|
+
export function APIPage() {
|
|
4
|
+
return (
|
|
5
|
+
<Layout>
|
|
6
|
+
<div className="p-8 max-w-3xl">
|
|
7
|
+
<h1 className="text-2xl font-bold text-slate-900 mb-2">Framework API</h1>
|
|
8
|
+
<p className="text-slate-500 mb-6">
|
|
9
|
+
Core API endpoints available to all authenticated users and custom applications.
|
|
10
|
+
</p>
|
|
11
|
+
<div className="bg-amber-50 border border-amber-200 rounded-lg p-4 text-sm text-amber-800">
|
|
12
|
+
API reference documentation coming soon.
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
</Layout>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Layout } from '../../components/layout/Layout'
|
|
2
|
+
|
|
3
|
+
export function CLIPage() {
|
|
4
|
+
return (
|
|
5
|
+
<Layout>
|
|
6
|
+
<div className="p-8 max-w-3xl">
|
|
7
|
+
<h1 className="text-2xl font-bold text-slate-900 mb-2">Framework CLI</h1>
|
|
8
|
+
<p className="text-slate-500 mb-6">
|
|
9
|
+
The <code className="bg-slate-100 px-1.5 py-0.5 rounded text-slate-800">spine-framework</code> CLI runs locally in your terminal or agentic IDE.
|
|
10
|
+
</p>
|
|
11
|
+
<div className="bg-slate-50 border border-slate-200 rounded-lg p-4 font-mono text-sm text-slate-700 space-y-1 mb-4">
|
|
12
|
+
<div><span className="text-slate-400">$</span> npx spine-framework --help</div>
|
|
13
|
+
<div><span className="text-slate-400">$</span> npx spine-framework auth whoami</div>
|
|
14
|
+
<div><span className="text-slate-400">$</span> npx spine-framework migrations list</div>
|
|
15
|
+
<div><span className="text-slate-400">$</span> npx spine-framework pipelines run <id></div>
|
|
16
|
+
<div><span className="text-slate-400">$</span> npx spine-framework items list --type support_ticket</div>
|
|
17
|
+
<div><span className="text-slate-400">$</span> npx spine-framework doctor</div>
|
|
18
|
+
</div>
|
|
19
|
+
<div className="bg-amber-50 border border-amber-200 rounded-lg p-4 text-sm text-amber-800">
|
|
20
|
+
Full CLI reference documentation coming soon.
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
</Layout>
|
|
24
|
+
)
|
|
25
|
+
}
|