spine-framework 0.3.40 → 0.3.42

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.
@@ -15,6 +15,7 @@
15
15
  */
16
16
 
17
17
  import { createClient } from '@supabase/supabase-js'
18
+ import WebSocket from 'ws'
18
19
 
19
20
  // ─── ENVIRONMENT RESOLUTION ──────────────────────────────────────────────────
20
21
 
@@ -61,7 +62,8 @@ const dbSchema: string = _env.DB_SCHEMA || 'public'
61
62
  * ```
62
63
  */
63
64
  export const adminDb = createClient(supabaseUrl, supabaseServiceKey, {
64
- db: { schema: dbSchema }
65
+ db: { schema: dbSchema },
66
+ realtime: { transport: WebSocket as any }
65
67
  })
66
68
 
67
69
  // ─── CHUNK_START: SHARED_DB_GET_USER_DB ──────────────────────────────────────────────
@@ -81,7 +83,8 @@ export const adminDb = createClient(supabaseUrl, supabaseServiceKey, {
81
83
  export function getUserDb(jwt: string) {
82
84
  return createClient(supabaseUrl, supabaseAnonKey, {
83
85
  db: { schema: dbSchema },
84
- global: { headers: { Authorization: `Bearer ${jwt}` } }
86
+ global: { headers: { Authorization: `Bearer ${jwt}` } },
87
+ realtime: { transport: WebSocket as any }
85
88
  })
86
89
  }
87
90
  // ─── CHUNK_END: SHARED_DB_GET_USER_DB ────────────────────────────────────────────────
@@ -32,6 +32,7 @@
32
32
 
33
33
  import { getUserDb, adminDb } from './db'
34
34
  import { createClient } from '@supabase/supabase-js'
35
+ import WebSocket from 'ws'
35
36
 
36
37
  // ─── AUTH CLIENT ─────────────────────────────────────────────────────────────
37
38
 
@@ -43,10 +44,8 @@ const supabase = createClient(
43
44
  env.SUPABASE_URL || 'https://placeholder.supabase.co',
44
45
  env.SUPABASE_ANON_KEY || 'placeholder',
45
46
  {
46
- auth: {
47
- autoRefreshToken: false,
48
- persistSession: false
49
- }
47
+ auth: { autoRefreshToken: false, persistSession: false },
48
+ realtime: { transport: WebSocket as any }
50
49
  }
51
50
  )
52
51
 
@@ -3,6 +3,7 @@ import { Routes, Route, Navigate } 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'
6
+ import { RegisterPage } from './pages/auth/RegisterPage'
6
7
  import { NotFoundPage } from './pages/NotFoundPage'
7
8
  import { DashboardPage } from './pages/DashboardPage'
8
9
  import { AppWrapper } from './components/AppWrapper'
@@ -33,6 +34,7 @@ function App() {
33
34
  {!user ? (
34
35
  <Routes>
35
36
  <Route path="/login" element={<LoginPage />} />
37
+ <Route path="/register" element={<RegisterPage />} />
36
38
  <Route path="*" element={<Navigate to="/login" replace />} />
37
39
  </Routes>
38
40
  ) : (
@@ -76,8 +78,9 @@ function AuthenticatedRouter() {
76
78
  <Route path="/" element={<Navigate to={defaultRedirect} replace />} />
77
79
  <Route path="/dashboard" element={<DashboardPage />} />
78
80
 
79
- {/* Login route for authenticated users (redirects to qualified app) */}
81
+ {/* Login/register routes for authenticated users (redirects to qualified app) */}
80
82
  <Route path="/login" element={<LoginPage />} />
83
+ <Route path="/register" element={<RegisterPage />} />
81
84
 
82
85
  {/* ── Spine Framework namespace (reserved — no custom app may use this path) ── */}
83
86
  <Route path="/spine-framework/admin/*" element={<AdminApp />} />
@@ -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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spine-framework",
3
- "version": "0.3.40",
3
+ "version": "0.3.42",
4
4
  "description": "Multi-tenant, modular application platform for modern SaaS systems",
5
5
  "type": "module",
6
6
  "bin": {