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.
package/.framework/src/App.tsx
CHANGED
|
@@ -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
|
|
89
|
-
<Route path="/login" element={<
|
|
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={
|
|
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={
|
|
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
|
|
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
|
-
//
|
|
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,
|
|
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.
|
|
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": [
|
|
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": [
|
|
18
|
+
"roles": [
|
|
19
|
+
"support",
|
|
20
|
+
"support_admin"
|
|
21
|
+
],
|
|
17
22
|
"default_role": "support",
|
|
18
23
|
"path": "/cortex",
|
|
19
24
|
"enabled": true,
|