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,57 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { Tooltip as TooltipPrimitive } from "radix-ui"
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils"
|
|
7
|
+
|
|
8
|
+
function TooltipProvider({
|
|
9
|
+
delayDuration = 0,
|
|
10
|
+
...props
|
|
11
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
|
|
12
|
+
return (
|
|
13
|
+
<TooltipPrimitive.Provider
|
|
14
|
+
data-slot="tooltip-provider"
|
|
15
|
+
delayDuration={delayDuration}
|
|
16
|
+
{...props}
|
|
17
|
+
/>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function Tooltip({
|
|
22
|
+
...props
|
|
23
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
|
|
24
|
+
return <TooltipPrimitive.Root data-slot="tooltip" {...props} />
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function TooltipTrigger({
|
|
28
|
+
...props
|
|
29
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
|
|
30
|
+
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function TooltipContent({
|
|
34
|
+
className,
|
|
35
|
+
sideOffset = 0,
|
|
36
|
+
children,
|
|
37
|
+
...props
|
|
38
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
|
|
39
|
+
return (
|
|
40
|
+
<TooltipPrimitive.Portal>
|
|
41
|
+
<TooltipPrimitive.Content
|
|
42
|
+
data-slot="tooltip-content"
|
|
43
|
+
sideOffset={sideOffset}
|
|
44
|
+
className={cn(
|
|
45
|
+
"z-50 inline-flex w-fit max-w-xs origin-(--radix-tooltip-content-transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pr-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
|
46
|
+
className
|
|
47
|
+
)}
|
|
48
|
+
{...props}
|
|
49
|
+
>
|
|
50
|
+
{children}
|
|
51
|
+
<TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground" />
|
|
52
|
+
</TooltipPrimitive.Content>
|
|
53
|
+
</TooltipPrimitive.Portal>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger }
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import React, { createContext, useContext, useState, useEffect } from 'react'
|
|
2
|
+
import { AppRecord } from '../hooks/useApps'
|
|
3
|
+
import { apiFetch } from '../lib/api'
|
|
4
|
+
import { useAuth } from './AuthContext'
|
|
5
|
+
|
|
6
|
+
// ─── Current App Context (per-route) ───────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
interface AppContextType {
|
|
9
|
+
app: AppRecord
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const AppContext = createContext<AppContextType | undefined>(undefined)
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Returns the current app record from the nearest AppProvider.
|
|
16
|
+
* Must be called inside an AppProvider (i.e., inside an app route).
|
|
17
|
+
*/
|
|
18
|
+
export function useCurrentApp(): AppRecord {
|
|
19
|
+
const context = useContext(AppContext)
|
|
20
|
+
if (context === undefined) {
|
|
21
|
+
throw new Error('useCurrentApp must be used within an AppProvider')
|
|
22
|
+
}
|
|
23
|
+
return context.app
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface AppProviderProps {
|
|
27
|
+
app: AppRecord
|
|
28
|
+
children: React.ReactNode
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Provides the current app record to all descendants via useCurrentApp().
|
|
33
|
+
*/
|
|
34
|
+
export function AppProvider({ app, children }: AppProviderProps) {
|
|
35
|
+
return (
|
|
36
|
+
<AppContext.Provider value={{ app }}>
|
|
37
|
+
{children}
|
|
38
|
+
</AppContext.Provider>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ─── Apps Registry Context (global, fetch-once) ─────────────────────────────
|
|
43
|
+
|
|
44
|
+
interface AppsRegistryContextType {
|
|
45
|
+
apps: AppRecord[]
|
|
46
|
+
routableApps: AppRecord[]
|
|
47
|
+
loading: boolean
|
|
48
|
+
error: string | null
|
|
49
|
+
refetch: () => void
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const AppsRegistryContext = createContext<AppsRegistryContextType | undefined>(undefined)
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Returns the global apps registry (all accessible apps for this user).
|
|
56
|
+
* Fetches once on mount after auth, cached for the session.
|
|
57
|
+
* Use this instead of useApps() to avoid per-page refetches.
|
|
58
|
+
*/
|
|
59
|
+
export function useAppsRegistry(): AppsRegistryContextType {
|
|
60
|
+
const context = useContext(AppsRegistryContext)
|
|
61
|
+
if (context === undefined) {
|
|
62
|
+
throw new Error('useAppsRegistry must be used within an AppsRegistryProvider')
|
|
63
|
+
}
|
|
64
|
+
return context
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
interface AppsRegistryProviderProps {
|
|
68
|
+
children: React.ReactNode
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Fetches all accessible apps once after authentication and provides them
|
|
73
|
+
* globally. Wrap this around AuthenticatedRouter so routes don't re-fetch
|
|
74
|
+
* on every navigation.
|
|
75
|
+
*/
|
|
76
|
+
export function AppsRegistryProvider({ children }: AppsRegistryProviderProps) {
|
|
77
|
+
const { user } = useAuth()
|
|
78
|
+
const [apps, setApps] = useState<AppRecord[]>([])
|
|
79
|
+
const [loading, setLoading] = useState(true)
|
|
80
|
+
const [error, setError] = useState<string | null>(null)
|
|
81
|
+
|
|
82
|
+
const fetchApps = async () => {
|
|
83
|
+
if (!user) {
|
|
84
|
+
setApps([])
|
|
85
|
+
setLoading(false)
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
setLoading(true)
|
|
91
|
+
setError(null)
|
|
92
|
+
|
|
93
|
+
const response = await apiFetch('/api/apps?action=list')
|
|
94
|
+
if (!response.ok) throw new Error(`Failed to fetch apps: ${response.statusText}`)
|
|
95
|
+
|
|
96
|
+
const data = await response.json()
|
|
97
|
+
if (data.error) throw new Error(data.error)
|
|
98
|
+
|
|
99
|
+
const allApps: AppRecord[] = data.data || data || []
|
|
100
|
+
|
|
101
|
+
const accessible = allApps.filter(app => {
|
|
102
|
+
if (!app.is_active) return false
|
|
103
|
+
const requiredRoles = app.required_roles || (app.min_role ? [app.min_role] : [])
|
|
104
|
+
if (requiredRoles.length === 0) return true
|
|
105
|
+
if (!user.roles || user.roles.length === 0) return false
|
|
106
|
+
if (user.roles.includes('system_admin')) return true
|
|
107
|
+
return requiredRoles.some(role => user.roles!.includes(role))
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
setApps(accessible)
|
|
111
|
+
} catch (err) {
|
|
112
|
+
setError(err instanceof Error ? err.message : 'Failed to load apps')
|
|
113
|
+
setApps([])
|
|
114
|
+
} finally {
|
|
115
|
+
setLoading(false)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Fetch once when user changes (login/logout), not on every render
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
fetchApps()
|
|
122
|
+
}, [user?.id, user?.account_id])
|
|
123
|
+
|
|
124
|
+
const routableApps = apps.filter(
|
|
125
|
+
app => app.route_prefix != null && app.renderer !== 'none'
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<AppsRegistryContext.Provider value={{ apps, routableApps, loading, error, refetch: fetchApps }}>
|
|
130
|
+
{children}
|
|
131
|
+
</AppsRegistryContext.Provider>
|
|
132
|
+
)
|
|
133
|
+
}
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module src/contexts/AuthContext
|
|
3
|
+
* @audience installer
|
|
4
|
+
* @layer frontend-hook
|
|
5
|
+
* @stability stable
|
|
6
|
+
*
|
|
7
|
+
* React authentication context and provider for the Spine frontend.
|
|
8
|
+
* Manages the full Supabase auth lifecycle: initial session hydration,
|
|
9
|
+
* login, logout, auth state change events, and a server-side user context
|
|
10
|
+
* fetch (principal, account, roles, permissions).
|
|
11
|
+
*
|
|
12
|
+
* **Session hydration strategy:**
|
|
13
|
+
* 1. On mount, restore user from `sessionStorage` (survives page reloads
|
|
14
|
+
* without a loading flash)
|
|
15
|
+
* 2. If no stored user, call `checkAuth` → `fetchUserContext` → `setAccountId`
|
|
16
|
+
* 3. Subscribe to `supabase.auth.onAuthStateChange` for `SIGNED_IN`,
|
|
17
|
+
* `TOKEN_REFRESHED`, and `SIGNED_OUT` events
|
|
18
|
+
*
|
|
19
|
+
* **Race-condition guards:**
|
|
20
|
+
* - `isLoggingIn` flag prevents `onAuthStateChange` from re-fetching context
|
|
21
|
+
* while `login()` is already in progress
|
|
22
|
+
* - `SIGNED_IN` / `TOKEN_REFRESHED` handler skips re-fetch if user is already
|
|
23
|
+
* loaded (prevents blocking page data fetches on browser focus)
|
|
24
|
+
* - `checkAuth` exits early if user is already loaded from `sessionStorage`
|
|
25
|
+
*
|
|
26
|
+
* **Account scope:** Calls `setAccountId(userContext.account_id)` after
|
|
27
|
+
* every successful context fetch, keeping `src/lib/api.ts` in sync.
|
|
28
|
+
*
|
|
29
|
+
* @seeAlso src/lib/supabase.ts (supabase client singleton)
|
|
30
|
+
* @seeAlso src/lib/api.ts (setAccountId, apiFetch)
|
|
31
|
+
* @seeAlso src/types/auth.ts (User shape)
|
|
32
|
+
* @seeAlso functions/auth.ts (backend `/api/auth?action=context` endpoint)
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import React, { createContext, useContext, useEffect, useState } from 'react'
|
|
36
|
+
import { supabase } from '../lib/supabase'
|
|
37
|
+
import { setAccountId, apiFetch } from '../lib/api'
|
|
38
|
+
import { User } from '../types/auth'
|
|
39
|
+
|
|
40
|
+
// ─── TYPES ───────────────────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Shape of the value provided by `AuthContext`.
|
|
44
|
+
*
|
|
45
|
+
* @prop user - Resolved server-side user context, or null if unauthenticated
|
|
46
|
+
* @prop isLoading - True during initial `checkAuth` (prevents premature redirects)
|
|
47
|
+
* @prop login - Sign in with email/password and hydrate user context
|
|
48
|
+
* @prop logout - Sign out and clear user + account scope
|
|
49
|
+
* @prop refreshUser - Re-fetch user context from the server (e.g. after role change)
|
|
50
|
+
*/
|
|
51
|
+
interface AuthContextType {
|
|
52
|
+
user: User | null
|
|
53
|
+
isLoading: boolean
|
|
54
|
+
login: (email: string, password: string) => Promise<void>
|
|
55
|
+
logout: () => Promise<void>
|
|
56
|
+
refreshUser: () => Promise<void>
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const AuthContext = createContext<AuthContextType | undefined>(undefined)
|
|
60
|
+
|
|
61
|
+
// ─── useAuth ─────────────────────────────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Hook to access the authentication context. Must be called inside `AuthProvider`.
|
|
65
|
+
*
|
|
66
|
+
* @returns `AuthContextType` — user, isLoading, login, logout, refreshUser
|
|
67
|
+
* @throws Error('useAuth must be used within an AuthProvider') if called outside provider
|
|
68
|
+
* @sideEffects none (read-only context access)
|
|
69
|
+
* @calledBy every component that needs the current user or auth actions
|
|
70
|
+
*/
|
|
71
|
+
export function useAuth() {
|
|
72
|
+
const context = useContext(AuthContext)
|
|
73
|
+
if (context === undefined) {
|
|
74
|
+
throw new Error('useAuth must be used within an AuthProvider')
|
|
75
|
+
}
|
|
76
|
+
return context
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
interface AuthProviderProps {
|
|
80
|
+
children: React.ReactNode
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ─── INTERNAL HELPERS ───────────────────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Fetches the server-side user context from `GET /api/auth`. Returns null on
|
|
87
|
+
* any error (network, auth, or API) rather than throwing, so callers can
|
|
88
|
+
* apply fallback logic without try/catch.
|
|
89
|
+
*
|
|
90
|
+
* @returns Resolved `User` object or null on failure
|
|
91
|
+
* @throws never (all errors are caught internally)
|
|
92
|
+
* @sideEffects Network request via apiFetch; console logging
|
|
93
|
+
* @calledBy AuthProvider.login, AuthProvider.refreshUser, AuthProvider.checkAuth
|
|
94
|
+
*/
|
|
95
|
+
async function fetchUserContext(): Promise<User | null> {
|
|
96
|
+
try {
|
|
97
|
+
console.log('Fetching user context from backend API')
|
|
98
|
+
|
|
99
|
+
// Use backend API to get user context - backend handles all security
|
|
100
|
+
const response = await apiFetch('/api/auth', {
|
|
101
|
+
method: 'GET'
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
if (!response.ok) {
|
|
105
|
+
console.error('Backend API error:', response.status, response.statusText)
|
|
106
|
+
return null
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const data = await response.json()
|
|
110
|
+
|
|
111
|
+
if (data.error) {
|
|
112
|
+
console.error('Backend returned error:', data.error)
|
|
113
|
+
return null
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (!data.data) {
|
|
117
|
+
console.error('No user data returned from backend')
|
|
118
|
+
return null
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
console.log('User context loaded from backend:', {
|
|
122
|
+
id: data.data.id,
|
|
123
|
+
email: data.data.email,
|
|
124
|
+
account: data.data.account?.slug,
|
|
125
|
+
role: data.data.roles?.[0]
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
return data.data
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.error('Error fetching user context from backend:', error)
|
|
131
|
+
return null
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ─── AuthProvider ──────────────────────────────────────────────────────────────
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Root authentication provider. Wrap the entire application with this component
|
|
139
|
+
* to enable `useAuth()` in any descendant.
|
|
140
|
+
*
|
|
141
|
+
* @param children - React subtree to wrap
|
|
142
|
+
* @sideEffects
|
|
143
|
+
* - Reads/writes `sessionStorage` key `spine_user` for persistence
|
|
144
|
+
* - Calls `setAccountId` (mutates `src/lib/api.ts` module state)
|
|
145
|
+
* - Subscribes to `supabase.auth.onAuthStateChange`; unsubscribes on unmount
|
|
146
|
+
* @calledBy src/main.tsx (app root)
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* ```tsx
|
|
150
|
+
* <AuthProvider>
|
|
151
|
+
* <App />
|
|
152
|
+
* </AuthProvider>
|
|
153
|
+
* ```
|
|
154
|
+
*/
|
|
155
|
+
export function AuthProvider({ children }: AuthProviderProps) {
|
|
156
|
+
// Initialize user from sessionStorage to survive full page reloads
|
|
157
|
+
const getStoredUser = (): User | null => {
|
|
158
|
+
try {
|
|
159
|
+
const stored = sessionStorage.getItem('spine_user')
|
|
160
|
+
return stored ? JSON.parse(stored) : null
|
|
161
|
+
} catch {
|
|
162
|
+
return null
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const [user, setUser] = useState<User | null>(getStoredUser)
|
|
167
|
+
const [isLoading, setIsLoading] = useState(false) // Start with false, will set to true only if we need to check
|
|
168
|
+
const [isLoggingIn, setIsLoggingIn] = useState(false)
|
|
169
|
+
|
|
170
|
+
// Save user to sessionStorage whenever it changes
|
|
171
|
+
const setUserWithStorage = (user: User | null) => {
|
|
172
|
+
setUser(user)
|
|
173
|
+
try {
|
|
174
|
+
if (user) {
|
|
175
|
+
sessionStorage.setItem('spine_user', JSON.stringify(user))
|
|
176
|
+
} else {
|
|
177
|
+
sessionStorage.removeItem('spine_user')
|
|
178
|
+
}
|
|
179
|
+
} catch (error) {
|
|
180
|
+
console.warn('Failed to save user to sessionStorage:', error)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const login = async (email: string, password: string) => {
|
|
185
|
+
console.log('Login function called with:', email)
|
|
186
|
+
setIsLoggingIn(true)
|
|
187
|
+
|
|
188
|
+
const { data, error } = await supabase.auth.signInWithPassword({
|
|
189
|
+
email,
|
|
190
|
+
password,
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
console.log('Supabase auth response:', { data, error })
|
|
194
|
+
|
|
195
|
+
if (error) {
|
|
196
|
+
console.error('Supabase auth error:', error)
|
|
197
|
+
setIsLoggingIn(false)
|
|
198
|
+
throw error
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (data.user) {
|
|
202
|
+
console.log('User logged in, fetching server context...')
|
|
203
|
+
|
|
204
|
+
// Wait a moment for the session to be properly established
|
|
205
|
+
await new Promise(resolve => setTimeout(resolve, 100))
|
|
206
|
+
|
|
207
|
+
// Fetch user context from server instead of hardcoding
|
|
208
|
+
const userContext = await fetchUserContext()
|
|
209
|
+
if (userContext) {
|
|
210
|
+
console.log('Setting server-derived user context:', userContext)
|
|
211
|
+
setUserWithStorage(userContext)
|
|
212
|
+
setAccountId(userContext.account_id)
|
|
213
|
+
} else {
|
|
214
|
+
console.error('Failed to get user context from server')
|
|
215
|
+
// Fallback to minimal user object
|
|
216
|
+
const fallbackUser = {
|
|
217
|
+
id: data.user.id,
|
|
218
|
+
email: data.user.email || '',
|
|
219
|
+
full_name: data.user.user_metadata?.full_name || data.user.email || 'User',
|
|
220
|
+
account_id: '',
|
|
221
|
+
roles: [],
|
|
222
|
+
permissions: [],
|
|
223
|
+
}
|
|
224
|
+
setUserWithStorage(fallbackUser)
|
|
225
|
+
setAccountId(fallbackUser.account_id)
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
setIsLoggingIn(false)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const logout = async () => {
|
|
233
|
+
await supabase.auth.signOut()
|
|
234
|
+
setUserWithStorage(null)
|
|
235
|
+
setAccountId(null)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const refreshUser = async () => {
|
|
239
|
+
try {
|
|
240
|
+
console.log('Refreshing user...')
|
|
241
|
+
const { data: { session } } = await supabase.auth.getSession()
|
|
242
|
+
console.log('Got session:', session)
|
|
243
|
+
|
|
244
|
+
if (!session?.user) {
|
|
245
|
+
console.log('No session user, setting user to null')
|
|
246
|
+
setUser(null)
|
|
247
|
+
return
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Fetch user context from server instead of hardcoding
|
|
251
|
+
const userContext = await fetchUserContext()
|
|
252
|
+
if (userContext) {
|
|
253
|
+
console.log('Setting server-derived user context:', userContext)
|
|
254
|
+
setUser(userContext)
|
|
255
|
+
setAccountId(userContext.account_id)
|
|
256
|
+
} else {
|
|
257
|
+
console.error('Failed to get user context from server')
|
|
258
|
+
setUser(null)
|
|
259
|
+
setAccountId(null)
|
|
260
|
+
}
|
|
261
|
+
} catch (error) {
|
|
262
|
+
console.error('Error refreshing user:', error)
|
|
263
|
+
setUser(null)
|
|
264
|
+
setAccountId(null)
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
useEffect(() => {
|
|
269
|
+
// Check initial auth state
|
|
270
|
+
const checkAuth = async () => {
|
|
271
|
+
try {
|
|
272
|
+
// Early exit if user is already loaded
|
|
273
|
+
if (user) {
|
|
274
|
+
console.log('User already loaded, skipping auth check:', user.id)
|
|
275
|
+
return
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Only show loading if we actually need to check auth
|
|
279
|
+
setIsLoading(true)
|
|
280
|
+
console.log('Checking initial auth state...')
|
|
281
|
+
const { data: { session } } = await supabase.auth.getSession()
|
|
282
|
+
console.log('Initial session check:', session?.user?.id ? 'User found' : 'No user')
|
|
283
|
+
|
|
284
|
+
if (session?.user) {
|
|
285
|
+
// Fetch user context from server instead of hardcoding
|
|
286
|
+
const userContext = await fetchUserContext()
|
|
287
|
+
if (userContext) {
|
|
288
|
+
console.log('Setting server-derived user context from session:', userContext)
|
|
289
|
+
setUserWithStorage(userContext)
|
|
290
|
+
setAccountId(userContext.account_id)
|
|
291
|
+
} else {
|
|
292
|
+
console.error('Failed to get user context from server')
|
|
293
|
+
setUserWithStorage(null)
|
|
294
|
+
setAccountId(null)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
} catch (error) {
|
|
298
|
+
console.error('Auth check failed:', error)
|
|
299
|
+
setUserWithStorage(null)
|
|
300
|
+
setAccountId(null)
|
|
301
|
+
} finally {
|
|
302
|
+
// Always set loading to false after checking
|
|
303
|
+
setIsLoading(false)
|
|
304
|
+
console.log('Auth check completed, loading set to false')
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
checkAuth()
|
|
309
|
+
|
|
310
|
+
// Listen for auth changes
|
|
311
|
+
const { data: { subscription } } = supabase.auth.onAuthStateChange(
|
|
312
|
+
async (event, session) => {
|
|
313
|
+
console.log('Auth state changed:', event, session?.user?.id)
|
|
314
|
+
|
|
315
|
+
// Don't refresh during login to avoid conflicts
|
|
316
|
+
if (isLoggingIn) {
|
|
317
|
+
console.log('Skipping refresh during login')
|
|
318
|
+
return
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (event === 'SIGNED_IN' || event === 'TOKEN_REFRESHED') {
|
|
322
|
+
if (session?.user) {
|
|
323
|
+
// Skip re-fetching if user is already loaded — prevents blocking
|
|
324
|
+
// page fetches when Supabase re-fires SIGNED_IN on browser focus
|
|
325
|
+
setUser(currentUser => {
|
|
326
|
+
if (currentUser) {
|
|
327
|
+
console.log('Auth state changed: user already loaded, skipping re-fetch')
|
|
328
|
+
return currentUser
|
|
329
|
+
}
|
|
330
|
+
// No user yet — fetch context asynchronously
|
|
331
|
+
fetchUserContext().then(userContext => {
|
|
332
|
+
if (userContext) {
|
|
333
|
+
console.log('Setting server-derived user context from auth state change:', userContext)
|
|
334
|
+
setUserWithStorage(userContext)
|
|
335
|
+
setAccountId(userContext.account_id)
|
|
336
|
+
} else {
|
|
337
|
+
console.error('Failed to get user context from server')
|
|
338
|
+
setUserWithStorage(null)
|
|
339
|
+
setAccountId(null)
|
|
340
|
+
}
|
|
341
|
+
})
|
|
342
|
+
return currentUser
|
|
343
|
+
})
|
|
344
|
+
}
|
|
345
|
+
} else if (event === 'SIGNED_OUT') {
|
|
346
|
+
setUserWithStorage(null)
|
|
347
|
+
setAccountId(null)
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
return () => subscription.unsubscribe()
|
|
353
|
+
}, [isLoggingIn])
|
|
354
|
+
|
|
355
|
+
const value = {
|
|
356
|
+
user,
|
|
357
|
+
isLoading,
|
|
358
|
+
login,
|
|
359
|
+
logout,
|
|
360
|
+
refreshUser
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Debug: Log loading state changes
|
|
364
|
+
console.log('AuthContext state:', { user: user?.id, isLoading, userEmail: user?.email })
|
|
365
|
+
|
|
366
|
+
return (
|
|
367
|
+
<AuthContext.Provider value={value}>
|
|
368
|
+
{children}
|
|
369
|
+
</AuthContext.Provider>
|
|
370
|
+
)
|
|
371
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
const MOBILE_BREAKPOINT = 768
|
|
4
|
+
|
|
5
|
+
export function useIsMobile() {
|
|
6
|
+
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
|
|
7
|
+
|
|
8
|
+
React.useEffect(() => {
|
|
9
|
+
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
|
|
10
|
+
const onChange = () => {
|
|
11
|
+
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
|
12
|
+
}
|
|
13
|
+
mql.addEventListener("change", onChange)
|
|
14
|
+
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
|
15
|
+
return () => mql.removeEventListener("change", onChange)
|
|
16
|
+
}, [])
|
|
17
|
+
|
|
18
|
+
return !!isMobile
|
|
19
|
+
}
|