spine-framework 0.3.85 → 0.3.87

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.
@@ -6,7 +6,7 @@ import { LoginPage } from './pages/auth/LoginPage'
6
6
  import { RegisterPage } from './pages/auth/RegisterPage'
7
7
  import { PendingApprovalPage } from './pages/auth/PendingApprovalPage'
8
8
  import { NotFoundPage } from './pages/NotFoundPage'
9
- import { DashboardPage } from './pages/DashboardPage'
9
+ import { SetupPage } from './pages/SetupPage'
10
10
  import { AppWrapper } from './components/AppWrapper'
11
11
  import { AppsRegistryProvider, useAppsRegistry } from './contexts/AppContext'
12
12
  import { CustomAppLoader } from './components/CustomAppLoader'
@@ -36,6 +36,8 @@ function App() {
36
36
  <Routes>
37
37
  <Route path="/login" element={<LoginPage />} />
38
38
  <Route path="/register" element={<RegisterPage />} />
39
+ {/* / shows setup instructions when no user is logged in */}
40
+ <Route path="/" element={<SetupPage />} />
39
41
  <Route path="*" element={<Navigate to="/login" replace />} />
40
42
  </Routes>
41
43
  ) : user.status === 'pending' ? (
@@ -64,8 +66,9 @@ function AuthenticatedRouter() {
64
66
  )
65
67
  }
66
68
 
67
- // Sort: longer explicit prefixes first (most specific), root (/) last so it
68
- // doesn't swallow other routes. spine-framework reserved namespace excluded.
69
+ // Sort: longer explicit prefixes first (most specific), root (/) last.
70
+ // The /spine-framework namespace is registered statically before dynamic routes
71
+ // so it always wins — but also filter it from dynamic registration to be safe.
69
72
  const sorted = [...apps]
70
73
  .filter(app => app.slug !== 'spine-framework' && !app.route_prefix?.startsWith('/spine-framework'))
71
74
  .sort((a, b) => {
@@ -74,33 +77,19 @@ function AuthenticatedRouter() {
74
77
  return (b.route_prefix?.length || 0) - (a.route_prefix?.length || 0)
75
78
  })
76
79
 
77
- // Determine default redirect:
78
- // - If an app owns root ('/'), redirect there directly
79
- // - Otherwise use the first non-root app's prefix
80
- // - Fall back to /dashboard if no apps installed
81
- const rootApp = sorted.find(app => app.route_prefix === '/')
82
- const defaultApp = sorted.find(app => app.route_prefix && app.route_prefix !== '/')
83
- const defaultRedirect = rootApp ? '/' : (defaultApp?.route_prefix || '/dashboard')
84
-
85
80
  return (
86
81
  <Suspense fallback={<div className="min-h-screen flex items-center justify-center"><LoadingSpinner /></div>}>
87
82
  <Routes>
88
- {/* Default redirect to first available app only when root is not itself an app */}
89
- {defaultRedirect !== '/' && (
90
- <Route path="/" element={<Navigate to={defaultRedirect} replace />} />
91
- )}
92
- <Route path="/dashboard" element={<DashboardPage />} />
93
-
94
- {/* Login/register routes for authenticated users (redirects to qualified app) */}
95
- <Route path="/login" element={<LoginPage />} />
96
- <Route path="/register" element={<RegisterPage />} />
97
-
98
- {/* ── Spine Framework namespace (reserved — no custom app may use this path) ── */}
83
+ {/* ── Spine Framework namespaceregistered first, always wins over /* ── */}
99
84
  <Route path="/spine-framework/admin/*" element={<AdminApp />} />
100
85
  <Route path="/spine-framework/api" element={<APIPage />} />
101
86
  <Route path="/spine-framework/cli" element={<CLIPage />} />
102
87
 
103
- {/* Dynamic app routes */}
88
+ {/* Auth routes for already-authenticated users (LoginPage handles redirect) */}
89
+ <Route path="/login" element={<LoginPage />} />
90
+ <Route path="/register" element={<RegisterPage />} />
91
+
92
+ {/* Dynamic app routes — no framework opinions about / or /dashboard */}
104
93
  {sorted.map(app => {
105
94
  const prefix = app.route_prefix!
106
95
 
@@ -135,6 +124,7 @@ function AuthenticatedRouter() {
135
124
  return null
136
125
  })}
137
126
 
127
+ {/* No apps claim this path */}
138
128
  <Route path="*" element={<NotFoundPage />} />
139
129
  </Routes>
140
130
  </Suspense>
@@ -5,7 +5,7 @@
5
5
  * @stability stable
6
6
  *
7
7
  * Generic 404 page rendered by the catch-all route. Displays a large
8
- * "404" heading and a "Go back home" link to `/dashboard`.
8
+ * "404" heading and a link back to the sign-in page.
9
9
  */
10
10
 
11
11
  import React from 'react'
@@ -25,11 +25,11 @@ export function NotFoundPage() {
25
25
 
26
26
  <div className="mt-6">
27
27
  <Link
28
- to="/dashboard"
28
+ to="/login"
29
29
  className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
30
30
  >
31
31
  <Home className="w-4 h-4 mr-2" />
32
- Go back home
32
+ Go to sign in
33
33
  </Link>
34
34
  </div>
35
35
  </div>
@@ -0,0 +1,66 @@
1
+ /**
2
+ * @module src/pages/SetupPage
3
+ * @audience installer
4
+ * @layer frontend-page
5
+ * @stability stable
6
+ *
7
+ * Shown at the root path when no apps are installed or no users have been
8
+ * created yet. Guides the developer through the initial setup steps.
9
+ * Replaces the old /dashboard fallback — the framework has no opinion
10
+ * about what lives at / once apps are installed.
11
+ */
12
+
13
+ import React from 'react'
14
+ import { Link } from 'react-router-dom'
15
+
16
+ export function SetupPage() {
17
+ return (
18
+ <div className="min-h-screen flex items-center justify-center bg-slate-50 py-12 px-4 sm:px-6 lg:px-8">
19
+ <div className="max-w-lg w-full space-y-8">
20
+ <div className="text-center">
21
+ <div className="mx-auto h-12 w-12 flex items-center justify-center rounded-full bg-slate-900">
22
+ <span className="text-white font-bold text-xl">S</span>
23
+ </div>
24
+ <h2 className="mt-6 text-3xl font-extrabold text-slate-900">
25
+ Welcome to Spine
26
+ </h2>
27
+ <p className="mt-2 text-sm text-slate-600">
28
+ Your project is running but no apps are installed yet.
29
+ </p>
30
+ </div>
31
+
32
+ <div className="bg-white shadow rounded-lg p-6 space-y-4">
33
+ <h3 className="text-sm font-semibold text-slate-700 uppercase tracking-wide">
34
+ Getting started
35
+ </h3>
36
+ <ol className="space-y-3 text-sm text-slate-600 list-decimal list-inside">
37
+ <li>
38
+ Edit <code className="bg-slate-100 px-1 rounded">.env</code> with your Supabase credentials
39
+ </li>
40
+ <li>
41
+ Run migrations:{' '}
42
+ <code className="bg-slate-100 px-1 rounded">npx spine-framework migrate</code>
43
+ </li>
44
+ <li>
45
+ Install an app:{' '}
46
+ <code className="bg-slate-100 px-1 rounded">npx spine-framework install-app spine-framework-cortex@next</code>
47
+ </li>
48
+ <li>
49
+ Reassemble:{' '}
50
+ <code className="bg-slate-100 px-1 rounded">npm run assemble && netlify dev</code>
51
+ </li>
52
+ </ol>
53
+ </div>
54
+
55
+ <div className="text-center">
56
+ <Link
57
+ to="/login"
58
+ className="text-sm font-medium text-slate-600 hover:text-slate-900"
59
+ >
60
+ Already set up? Sign in →
61
+ </Link>
62
+ </div>
63
+ </div>
64
+ </div>
65
+ )
66
+ }
@@ -13,10 +13,9 @@
13
13
  * @seeAlso src/contexts/AuthContext.tsx (login implementation)
14
14
  */
15
15
 
16
- import React, { useState, useEffect } from 'react'
17
- import { Link, useNavigate } from 'react-router-dom'
16
+ import React, { useState } from 'react'
17
+ import { Link } from 'react-router-dom'
18
18
  import { useAuth } from '../../contexts/AuthContext'
19
- import { useAppsRegistry } from '../../contexts/AppContext'
20
19
  import { LoadingSpinner } from '../../components/ui/LoadingSpinner'
21
20
 
22
21
  export function LoginPage() {
@@ -25,45 +24,11 @@ export function LoginPage() {
25
24
  const [error, setError] = useState('')
26
25
  const [isLoading, setIsLoading] = useState(false)
27
26
 
28
- const { user, login } = useAuth()
29
- const navigate = useNavigate()
30
- const { routableApps: apps, loading } = useAppsRegistry()
27
+ const { login } = useAuth()
31
28
 
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
- }
29
+ // After login, App.tsx switches to AuthenticatedRouter which handles
30
+ // all routing decisions based on the user's role and installed apps.
31
+ // LoginPage has no redirect logic of its own.
67
32
 
68
33
  const handleSubmit = async (e: React.FormEvent) => {
69
34
  e.preventDefault()
@@ -71,15 +36,11 @@ export function LoginPage() {
71
36
  setIsLoading(true)
72
37
 
73
38
  try {
74
- console.log('Attempting login...')
75
39
  await login(email, password)
76
- // Redirect will be handled by useEffect above
77
- console.log('Login successful, redirect will be handled by useEffect...')
40
+ // App.tsx switches to AuthenticatedRouter on success — no redirect needed here
78
41
  } catch (err: any) {
79
- console.error('Login error:', err)
80
42
  setError(err.message || 'Login failed')
81
43
  } finally {
82
- console.log('Login process completed, setting loading to false')
83
44
  setIsLoading(false)
84
45
  }
85
46
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spine-framework",
3
- "version": "0.3.85",
3
+ "version": "0.3.87",
4
4
  "description": "Multi-tenant, modular application platform for modern SaaS systems",
5
5
  "type": "module",
6
6
  "bin": {