spine-framework 0.3.87 → 0.3.90

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.
@@ -1,5 +1,5 @@
1
- import { Suspense, lazy } from 'react'
2
- import { Routes, Route, Navigate } from 'react-router-dom'
1
+ import { Suspense, lazy, useEffect } from 'react'
2
+ import { Routes, Route, Navigate, useNavigate, useLocation } from 'react-router-dom'
3
3
  import { useAuth } from './contexts/AuthContext'
4
4
  import { LoadingSpinner } from './components/ui/LoadingSpinner'
5
5
  import { LoginPage } from './pages/auth/LoginPage'
@@ -53,7 +53,25 @@ function App() {
53
53
  }
54
54
 
55
55
  function AuthenticatedRouter() {
56
+ const { user } = useAuth()
56
57
  const { routableApps: apps, loading } = useAppsRegistry()
58
+ const navigate = useNavigate()
59
+ const location = useLocation()
60
+
61
+ // Where to send an already-authenticated user who lands on /login or /register.
62
+ // System admins go to the framework admin namespace; everyone else goes to the
63
+ // first app they are allowed to access (routableApps is already role-filtered).
64
+ const defaultRedirect = user?.roles?.includes('system_admin')
65
+ ? '/spine-framework/admin'
66
+ : (apps[0]?.route_prefix || '/')
67
+
68
+ // Redirect away from auth pages once apps have loaded.
69
+ // This fires reliably after loading completes, regardless of render timing.
70
+ useEffect(() => {
71
+ if (!loading && ['/login', '/register'].includes(location.pathname)) {
72
+ navigate(defaultRedirect, { replace: true })
73
+ }
74
+ }, [loading, location.pathname, defaultRedirect, navigate])
57
75
 
58
76
  if (loading) {
59
77
  return (
@@ -77,6 +95,9 @@ function AuthenticatedRouter() {
77
95
  return (b.route_prefix?.length || 0) - (a.route_prefix?.length || 0)
78
96
  })
79
97
 
98
+ // Build a valid route path: prefix "/" must become "/*", not "//*"
99
+ const toRoutePath = (prefix: string) => prefix === '/' ? '/*' : `${prefix}/*`
100
+
80
101
  return (
81
102
  <Suspense fallback={<div className="min-h-screen flex items-center justify-center"><LoadingSpinner /></div>}>
82
103
  <Routes>
@@ -85,8 +106,8 @@ function AuthenticatedRouter() {
85
106
  <Route path="/spine-framework/api" element={<APIPage />} />
86
107
  <Route path="/spine-framework/cli" element={<CLIPage />} />
87
108
 
88
- {/* Auth routes for already-authenticated users (LoginPage handles redirect) */}
89
- <Route path="/login" element={<LoginPage />} />
109
+ {/* Auth routes: redirect handled by useEffect above, but keep as fallback */}
110
+ <Route path="/login" element={<Navigate to={defaultRedirect} replace />} />
90
111
  <Route path="/register" element={<RegisterPage />} />
91
112
 
92
113
  {/* Dynamic app routes — no framework opinions about / or /dashboard */}
@@ -97,7 +118,7 @@ function AuthenticatedRouter() {
97
118
  return (
98
119
  <Route
99
120
  key={app.slug}
100
- path={`${prefix}/*`}
121
+ path={toRoutePath(prefix)}
101
122
  element={
102
123
  <AppWrapper app={app}>
103
124
  <CustomAppLoader slug={app.slug} />
@@ -111,7 +132,7 @@ function AuthenticatedRouter() {
111
132
  return (
112
133
  <Route
113
134
  key={app.slug}
114
- path={`${prefix}/*`}
135
+ path={toRoutePath(prefix)}
115
136
  element={
116
137
  <AppWrapper app={app}>
117
138
  <GenericAppShell app={app} />
@@ -116,10 +116,11 @@ export function AppsRegistryProvider({ children }: AppsRegistryProviderProps) {
116
116
  }
117
117
  }
118
118
 
119
- // Fetch once when user changes (login/logout), not on every render
119
+ // Fetch when user identity or roles change (roles may update after registration)
120
+ const rolesKey = user?.roles?.join(',') || ''
120
121
  useEffect(() => {
121
122
  fetchApps()
122
- }, [user?.id, user?.account_id])
123
+ }, [user?.id, user?.account_id, rolesKey])
123
124
 
124
125
  const routableApps = apps.filter(
125
126
  app => app.route_prefix != null && app.renderer !== 'none'
@@ -269,9 +269,20 @@ export function AuthProvider({ children }: AuthProviderProps) {
269
269
  // Check initial auth state
270
270
  const checkAuth = async () => {
271
271
  try {
272
- // Early exit if user is already loaded
272
+ // If user is loaded from sessionStorage, skip the loading state but
273
+ // still refresh context in the background to pick up role changes
273
274
  if (user) {
274
- console.log('User already loaded, skipping auth check:', user.id)
275
+ console.log('User already loaded, refreshing context in background:', user.id)
276
+ const { data: { session } } = await supabase.auth.getSession()
277
+ if (session?.user) {
278
+ const userContext = await fetchUserContext()
279
+ if (userContext) {
280
+ setUserWithStorage(userContext)
281
+ setAccountId(userContext.account_id)
282
+ }
283
+ }
284
+ setIsLoading(false)
285
+ console.log('Auth check completed, loading set to false')
275
286
  return
276
287
  }
277
288
 
@@ -34,6 +34,7 @@
34
34
  import React, { useState, useEffect, useCallback } from 'react'
35
35
  import { Link, useNavigate, useSearchParams } from 'react-router-dom'
36
36
  import { LoadingSpinner } from '../../components/ui/LoadingSpinner'
37
+ import { useAuth } from '../../contexts/AuthContext'
37
38
  import { supabase } from '../../lib/supabase'
38
39
 
39
40
  // ─── TYPES ────────────────────────────────────────────────────────────────────
@@ -132,6 +133,7 @@ function getPostRegistrationRedirect(
132
133
  export function RegisterPage() {
133
134
  const [searchParams] = useSearchParams()
134
135
  const navigate = useNavigate()
136
+ const { refreshUser } = useAuth()
135
137
  const inviteToken = searchParams.get('t')
136
138
 
137
139
  // Registration config
@@ -342,6 +344,10 @@ export function RegisterPage() {
342
344
  return
343
345
  }
344
346
 
347
+ // Re-fetch user context so roles are populated (complete-registration
348
+ // sets role_id, but the initial auth fetch may have raced ahead of it)
349
+ await refreshUser()
350
+
345
351
  // Navigate to configured post-registration destination
346
352
  const redirectPath = getPostRegistrationRedirect(regConfig, searchParams, false)
347
353
  navigate(redirectPath, { replace: true })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spine-framework",
3
- "version": "0.3.87",
3
+ "version": "0.3.90",
4
4
  "description": "Multi-tenant, modular application platform for modern SaaS systems",
5
5
  "type": "module",
6
6
  "bin": {
@@ -84,9 +84,6 @@
84
84
  "docs": "typedoc --entryPoints .framework/functions/_shared/index.ts --out docs/generated"
85
85
  },
86
86
  "dependencies": {
87
- "tsx": "^4.21.0",
88
- "ws": "^8.18.0",
89
- "pg": "^8.13.0",
90
87
  "@base-ui/react": "^1.4.1",
91
88
  "@fontsource-variable/inter": "^5.2.8",
92
89
  "@heroicons/react": "^2.2.0",
@@ -138,6 +135,7 @@
138
135
  "marked": "^17.0.3",
139
136
  "mermaid": "^11.12.3",
140
137
  "next-themes": "^0.4.6",
138
+ "pg": "^8.13.0",
141
139
  "radix-ui": "^1.4.3",
142
140
  "react": "^18.3.1",
143
141
  "react-day-picker": "^10.0.0",
@@ -155,11 +153,14 @@
155
153
  "remark-gfm": "^4.0.1",
156
154
  "shadcn": "^4.7.0",
157
155
  "sonner": "^2.0.7",
156
+ "spine-framework-cortex": "^0.2.12",
158
157
  "tailwind-merge": "^3.5.0",
159
158
  "tailwindcss-animate": "^1.0.7",
160
159
  "tiptap-markdown": "^0.9.0",
160
+ "tsx": "^4.21.0",
161
161
  "tw-animate-css": "^1.4.0",
162
- "vaul": "^1.1.2"
162
+ "vaul": "^1.1.2",
163
+ "ws": "^8.18.0"
163
164
  },
164
165
  "devDependencies": {
165
166
  "@netlify/functions": "^2.8.2",
package/spine.config.json CHANGED
@@ -6,14 +6,19 @@
6
6
  "allow_url_override": true,
7
7
  "apps": {
8
8
  "portal": {
9
- "roles": ["member"],
9
+ "roles": [
10
+ "member"
11
+ ],
10
12
  "default_role": "member",
11
13
  "path": "/portal",
12
14
  "enabled": true,
13
15
  "account_strategy": "new"
14
16
  },
15
17
  "cortex": {
16
- "roles": ["support", "support_admin"],
18
+ "roles": [
19
+ "support",
20
+ "support_admin"
21
+ ],
17
22
  "default_role": "support",
18
23
  "path": "/cortex",
19
24
  "enabled": true,