flowlink-auth 2.7.3 → 2.7.5

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/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "flowlink-auth",
3
- "version": "2.7.3",
3
+ "version": "2.7.5",
4
4
  "description": "Custom auth library",
5
5
  "main": "dist/index.js",
6
- "types": "dist/index.d.ts",
6
+ "types": "src/index.d.ts",
7
7
  "files": [
8
- "dist"
8
+ "dist",
9
+ "src"
9
10
  ],
10
11
  "scripts": {
11
12
  "build": "esbuild src/*.js src/*.jsx --outdir=dist --bundle --format=esm --loader:.js=jsx --loader:.jsx=jsx",
@@ -0,0 +1,22 @@
1
+ // src/ErrorBox.jsx
2
+ import React from 'react'
3
+
4
+ export default function ErrorBox({ message }) {
5
+ const style = {
6
+ border: '1px solid #e53935',
7
+ background: '#fff6f6',
8
+ color: '#b71c1c',
9
+ padding: 12,
10
+ borderRadius: 8,
11
+ fontFamily: 'system-ui, Roboto, Arial',
12
+ maxWidth: 640,
13
+ margin: '8px auto'
14
+ }
15
+ return (
16
+ <div role="alert" style={style}>
17
+ <strong>flowlink Auth Error —</strong>
18
+ <div style={{ marginTop: 6 }}>{message}</div>
19
+ </div>
20
+ )
21
+ }
22
+
package/src/SignIn.jsx ADDED
@@ -0,0 +1,212 @@
1
+ // src/signin.jsx
2
+ 'use client'
3
+ import React, { useState } from 'react'
4
+ import { useAuth } from './provider.js'
5
+
6
+ export default function SignIn({ onSuccess } = {}) {
7
+ const {
8
+ publishableKey,
9
+ baseUrl,
10
+ redirect,
11
+ redirectTo,
12
+ user,
13
+ loadingUser,
14
+ completeLogin,
15
+ fetchMe,
16
+ setUser
17
+ } = useAuth()
18
+
19
+ const [email, setEmail] = useState('')
20
+ const [password, setPassword] = useState('')
21
+ const [loading, setLoading] = useState(false)
22
+ const [error, setError] = useState(null)
23
+ const [message, setMessage] = useState(null)
24
+
25
+ if (loadingUser) return null
26
+
27
+ if (user && redirect) {
28
+ if (typeof redirectTo === 'function') redirectTo(redirect)
29
+ else if (typeof window !== 'undefined') window.location.assign(redirect)
30
+ return null
31
+ }
32
+
33
+ async function submit(e) {
34
+ e.preventDefault()
35
+ setError(null)
36
+ setMessage(null)
37
+
38
+ if (!email || !password) {
39
+ setError('Email and password are required')
40
+ return
41
+ }
42
+
43
+ setLoading(true)
44
+
45
+ try {
46
+ const endpoint = `${(baseUrl || '').replace(/\/+$/, '')}/api/sdk/login`
47
+
48
+ const res = await fetch(endpoint, {
49
+ method: 'POST',
50
+ credentials: 'include',
51
+ headers: {
52
+ 'Content-Type': 'application/json',
53
+ 'x-publishable-key': publishableKey || ''
54
+ },
55
+ body: JSON.stringify({ email, password })
56
+ })
57
+
58
+ const ct = res.headers.get('content-type') || ''
59
+ let data = {}
60
+ if (ct.includes('application/json')) data = await res.json()
61
+ else {
62
+ const text = await res.text()
63
+ throw new Error(`Unexpected response (status ${res.status}): ${text.slice(0, 200)}`)
64
+ }
65
+
66
+ if (!res.ok) throw new Error(data.error || data.message || `Login failed (status ${res.status})`)
67
+
68
+ const serverUser = data.user ?? null
69
+ if (serverUser && typeof setUser === 'function') {
70
+ try { setUser(serverUser) } catch (_) {}
71
+ }
72
+
73
+ if (typeof completeLogin === 'function') {
74
+ try {
75
+ await completeLogin()
76
+ } catch (e) {
77
+ if (typeof fetchMe === 'function') await fetchMe()
78
+ }
79
+ } else if (typeof fetchMe === 'function') {
80
+ await fetchMe()
81
+ }
82
+
83
+ if (onSuccess) {
84
+ try { onSuccess(data) } catch (_) {}
85
+ }
86
+
87
+ setMessage('Signed in. Redirecting...')
88
+ if (redirect) {
89
+ setTimeout(() => {
90
+ if (typeof redirectTo === 'function') redirectTo(redirect)
91
+ else if (typeof window !== 'undefined') window.location.assign(redirect)
92
+ }, 250)
93
+ }
94
+ } catch (err) {
95
+ setError(err.message || 'Network error')
96
+ } finally {
97
+ setLoading(false)
98
+ }
99
+ }
100
+
101
+ // --- OAuth start flow (Google / GitHub) ---
102
+ async function startOAuthFlow(provider) {
103
+ setError(null)
104
+ setLoading(true)
105
+
106
+ try {
107
+ const rid =
108
+ (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function')
109
+ ? crypto.randomUUID()
110
+ : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`
111
+
112
+ // Use main app signin page as callback so user lands on /signin after flow
113
+ const callbackUrl = encodeURIComponent(`${window.location.origin}/signin`)
114
+
115
+ const sdkBase = (typeof process !== 'undefined' && process.env && process.env.NEXT_PUBLIC_FLOWLINK_BASE_URL)
116
+ || baseUrl || 'http://localhost:3001'
117
+ const startUrl = `${sdkBase.replace(/\/+$/, '')}/sdk/auth/start?rid=${rid}&source=${encodeURIComponent(provider)}&callbackUrl=${callbackUrl}`
118
+
119
+ if (!publishableKey) {
120
+ throw new Error('Missing publishable key (client side). Set NEXT_PUBLIC_FLOWLINK_PUBLISHABLE_KEY or provide publishableKey in provider.')
121
+ }
122
+
123
+ const res = await fetch(startUrl, {
124
+ method: 'GET',
125
+ headers: { 'x-publishable-key': publishableKey },
126
+ mode: 'cors'
127
+ })
128
+
129
+ const data = await res.json().catch(() => null)
130
+ if (!res.ok) throw new Error(data?.error || `OAuth start failed (${res.status})`)
131
+ if (!data?.oauthUrl) throw new Error('SDK start did not return oauthUrl')
132
+
133
+ window.location.href = data.oauthUrl
134
+ } catch (err) {
135
+ console.error('OAuth start error:', err)
136
+ setError(err?.message || 'OAuth start failed')
137
+ setLoading(false)
138
+ }
139
+ }
140
+
141
+ const handleGoogle = (e) => {
142
+ if (e && typeof e.preventDefault === 'function') e.preventDefault()
143
+ startOAuthFlow('google')
144
+ }
145
+
146
+ const handleGithub = (e) => {
147
+ if (e && typeof e.preventDefault === 'function') e.preventDefault()
148
+ startOAuthFlow('github')
149
+ }
150
+
151
+ return (
152
+ <div style={overlay}>
153
+ <div style={modal}>
154
+ <h2 style={title}>Sign in</h2>
155
+ <p style={subtitle}>Welcome back — enter your credentials.</p>
156
+
157
+ <form onSubmit={submit} style={{ width: '100%' }}>
158
+ <label style={label}>Email</label>
159
+ <input
160
+ style={input}
161
+ value={email}
162
+ onChange={e => setEmail(e.target.value)}
163
+ type="email"
164
+ required
165
+ />
166
+
167
+ <label style={label}>Password</label>
168
+ <input
169
+ style={input}
170
+ type="password"
171
+ value={password}
172
+ onChange={e => setPassword(e.target.value)}
173
+ required
174
+ />
175
+
176
+ <div style={{ marginTop: 12 }}>
177
+ <button style={button} type="submit" disabled={loading}>
178
+ {loading ? 'Signing in…' : 'Sign in'}
179
+ </button>
180
+ </div>
181
+
182
+ <div style={{ display: 'flex', gap: 8, marginTop: 16 }}>
183
+ <button type="button" onClick={handleGoogle} style={oauthButtonGoogle} disabled={loading}>
184
+ Continue with Google
185
+ </button>
186
+
187
+ <button type="button" onClick={handleGithub} style={oauthButtonGithub} disabled={loading}>
188
+ Continue with GitHub
189
+ </button>
190
+ </div>
191
+
192
+ {error && <div style={errorBox}>{error}</div>}
193
+ {message && <div style={successBox}>{message}</div>}
194
+ </form>
195
+ </div>
196
+ </div>
197
+ )
198
+ }
199
+
200
+ /* styles */
201
+ const overlay = { position: 'fixed', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'rgba(0,0,0,0.45)', zIndex: 9999, padding: 20 }
202
+ const modal = { width: '100%', maxWidth: 420, background: '#0f1724', color: '#fff', borderRadius: 12, padding: 22, boxShadow: '0 10px 30px rgba(2,6,23,0.6)', border: '1px solid rgba(255,255,255,0.04)' }
203
+ const title = { margin: 0, fontSize: 20, fontWeight: 600 }
204
+ const subtitle = { marginTop: 6, marginBottom: 14, color: '#cbd5e1', fontSize: 13 }
205
+ const label = { display: 'block', color: '#cbd5e1', fontSize: 13, marginTop: 8 }
206
+ const input = { width: '100%', padding: '10px 12px', marginTop: 6, borderRadius: 8, border: '1px solid rgba(255,255,255,0.06)', background: '#0b1220', color: '#fff', boxSizing: 'border-box' }
207
+ const button = { width: '100%', padding: '10px 12px', borderRadius: 8, background: 'linear-gradient(90deg,#06b6d4,#2563eb)', color: '#0b1220', border: 'none', fontWeight: 700, cursor: 'pointer' }
208
+ const oauthButtonGoogle = { flex: 1, padding: '10px 12px', borderRadius: 8, background: '#db4437', color: '#fff', border: 'none', cursor: 'pointer' }
209
+ const oauthButtonGithub = { flex: 1, padding: '10px 12px', borderRadius: 8, background: '#24292f', color: '#fff', border: 'none', cursor: 'pointer' }
210
+ const errorBox = { marginTop: 10, color: '#ffb4b4', fontSize: 13 }
211
+ const successBox = { marginTop: 10, color: '#bef264', fontSize: 13 }
212
+