flowlink-auth 2.7.4 → 2.7.6
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/dist/index.css +694 -0
- package/dist/index.js +664 -74
- package/package.json +1 -1
- package/src/SignUp.jsx +686 -177
package/src/SignUp.jsx
CHANGED
|
@@ -1,70 +1,315 @@
|
|
|
1
|
+
// // src/signup.jsx
|
|
2
|
+
// 'use client'
|
|
3
|
+
// import React, { useState } from 'react'
|
|
4
|
+
// import { useAuth } from './provider.js'
|
|
5
|
+
|
|
6
|
+
// export default function SignUp() {
|
|
7
|
+
// const {
|
|
8
|
+
// publishableKey,
|
|
9
|
+
// baseUrl,
|
|
10
|
+
// redirect,
|
|
11
|
+
// redirectTo,
|
|
12
|
+
// user,
|
|
13
|
+
// loadingUser,
|
|
14
|
+
// fetchMe,
|
|
15
|
+
// setUser
|
|
16
|
+
// } = useAuth()
|
|
17
|
+
|
|
18
|
+
// const [name, setName] = useState('')
|
|
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
|
+
// setLoading(true)
|
|
38
|
+
|
|
39
|
+
// const url = `${(baseUrl || '').replace(/\/+$/, '')}/api/sdk/signup`
|
|
40
|
+
|
|
41
|
+
// try {
|
|
42
|
+
// const res = await fetch(url, {
|
|
43
|
+
// method: 'POST',
|
|
44
|
+
// credentials: 'include',
|
|
45
|
+
// headers: {
|
|
46
|
+
// 'Content-Type': 'application/json',
|
|
47
|
+
// 'x-publishable-key': publishableKey || ''
|
|
48
|
+
// },
|
|
49
|
+
// body: JSON.stringify({ name, email, password })
|
|
50
|
+
// })
|
|
51
|
+
|
|
52
|
+
// const data = await res.json().catch(() => ({}))
|
|
53
|
+
// if (!res.ok) throw new Error(data.error || 'Signup failed')
|
|
54
|
+
|
|
55
|
+
// if (data.user && typeof setUser === 'function') setUser(data.user)
|
|
56
|
+
// if (typeof fetchMe === 'function') await fetchMe()
|
|
57
|
+
|
|
58
|
+
// setMessage('Account created. Redirecting…')
|
|
59
|
+
|
|
60
|
+
// if (redirect) {
|
|
61
|
+
// setTimeout(() => {
|
|
62
|
+
// if (typeof redirectTo === 'function') redirectTo(redirect)
|
|
63
|
+
// else if (typeof window !== 'undefined') window.location.assign(redirect)
|
|
64
|
+
// }, 300)
|
|
65
|
+
// }
|
|
66
|
+
// } catch (err) {
|
|
67
|
+
// setError(err?.message ?? 'Network error')
|
|
68
|
+
// console.error('Signup error:', err)
|
|
69
|
+
// } finally {
|
|
70
|
+
// setLoading(false)
|
|
71
|
+
// }
|
|
72
|
+
// }
|
|
73
|
+
|
|
74
|
+
// async function startOAuthFlow(provider) {
|
|
75
|
+
// // prevent double clicks
|
|
76
|
+
// setError(null)
|
|
77
|
+
// setLoading(true)
|
|
78
|
+
|
|
79
|
+
// try {
|
|
80
|
+
// const rid =
|
|
81
|
+
// (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function')
|
|
82
|
+
// ? crypto.randomUUID()
|
|
83
|
+
// : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`
|
|
84
|
+
|
|
85
|
+
// // callback must match what your SDK start expects and what Google console allows
|
|
86
|
+
// const callbackUrl = encodeURIComponent(`${window.location.origin}/signup`)
|
|
87
|
+
|
|
88
|
+
// // build start URL (server returns { oauthUrl })
|
|
89
|
+
// const sdkBase = (typeof process !== 'undefined' && process.env && process.env.NEXT_PUBLIC_FLOWLINK_BASE_URL)
|
|
90
|
+
// || baseUrl || window.location.origin.replace(/\/+$/, '')
|
|
91
|
+
// const startUrl = `${sdkBase}/sdk/auth/start?rid=${rid}&source=${encodeURIComponent(provider)}&callbackUrl=${callbackUrl}`
|
|
92
|
+
|
|
93
|
+
// // ensure publishableKey exists
|
|
94
|
+
// if (!publishableKey) {
|
|
95
|
+
// throw new Error('Missing publishable key (client side). Set NEXT_PUBLIC_FLOWLINK_PUBLISHABLE_KEY or provide publishableKey in provider.')
|
|
96
|
+
// }
|
|
97
|
+
|
|
98
|
+
// const res = await fetch(startUrl, {
|
|
99
|
+
// method: 'GET',
|
|
100
|
+
// headers: {
|
|
101
|
+
// 'x-publishable-key': publishableKey
|
|
102
|
+
// }
|
|
103
|
+
// })
|
|
104
|
+
|
|
105
|
+
// const data = await res.json().catch(() => null)
|
|
106
|
+
// if (!res.ok) {
|
|
107
|
+
// throw new Error(data?.error || `OAuth start failed (${res.status})`)
|
|
108
|
+
// }
|
|
109
|
+
// if (!data?.oauthUrl) {
|
|
110
|
+
// throw new Error('SDK start did not return oauthUrl')
|
|
111
|
+
// }
|
|
112
|
+
|
|
113
|
+
// // navigate to provider (Google/GitHub)
|
|
114
|
+
// window.location.href = data.oauthUrl
|
|
115
|
+
// } catch (err) {
|
|
116
|
+
// console.error('OAuth start error:', err)
|
|
117
|
+
// setError(err?.message || 'OAuth start failed')
|
|
118
|
+
// setLoading(false)
|
|
119
|
+
// }
|
|
120
|
+
// }
|
|
121
|
+
|
|
122
|
+
// const handleGoogle = (e) => {
|
|
123
|
+
// if (e && typeof e.preventDefault === 'function') e.preventDefault()
|
|
124
|
+
// startOAuthFlow('google')
|
|
125
|
+
// }
|
|
126
|
+
|
|
127
|
+
// const handleGithub = (e) => {
|
|
128
|
+
// if (e && typeof e.preventDefault === 'function') e.preventDefault()
|
|
129
|
+
// startOAuthFlow('github')
|
|
130
|
+
// }
|
|
131
|
+
// return (
|
|
132
|
+
// <div style={overlay}>
|
|
133
|
+
// <div style={modal}>
|
|
134
|
+
// <h2 style={title}>Create account</h2>
|
|
135
|
+
|
|
136
|
+
// <form onSubmit={submit}>
|
|
137
|
+
// <label style={label}>Full name</label>
|
|
138
|
+
// <input
|
|
139
|
+
// name="name"
|
|
140
|
+
// style={input}
|
|
141
|
+
// value={name}
|
|
142
|
+
// onChange={e => setName(e.target.value)}
|
|
143
|
+
// autoComplete="name"
|
|
144
|
+
// />
|
|
145
|
+
|
|
146
|
+
// <label style={label}>Email</label>
|
|
147
|
+
// <input
|
|
148
|
+
// name="email"
|
|
149
|
+
// type="email"
|
|
150
|
+
// style={input}
|
|
151
|
+
// value={email}
|
|
152
|
+
// onChange={e => setEmail(e.target.value)}
|
|
153
|
+
// required
|
|
154
|
+
// autoComplete="email"
|
|
155
|
+
// />
|
|
156
|
+
|
|
157
|
+
// <label style={label}>Password</label>
|
|
158
|
+
// <input
|
|
159
|
+
// name="password"
|
|
160
|
+
// type="password"
|
|
161
|
+
// style={input}
|
|
162
|
+
// value={password}
|
|
163
|
+
// onChange={e => setPassword(e.target.value)}
|
|
164
|
+
// required
|
|
165
|
+
// autoComplete="new-password"
|
|
166
|
+
// />
|
|
167
|
+
|
|
168
|
+
// <div style={{ marginTop: 12 }}>
|
|
169
|
+
// <button style={button} type="submit" disabled={loading}>
|
|
170
|
+
// {loading ? 'Creating...' : 'Create account'}
|
|
171
|
+
// </button>
|
|
172
|
+
// </div>
|
|
173
|
+
|
|
174
|
+
// <div style={{ display: 'flex', gap: 8, marginTop: 16 }}>
|
|
175
|
+
// <button
|
|
176
|
+
// type="button"
|
|
177
|
+
// onClick={handleGoogle}
|
|
178
|
+
// style={oauthButtonGoogle}
|
|
179
|
+
// >
|
|
180
|
+
// Continue with Google
|
|
181
|
+
// </button>
|
|
182
|
+
|
|
183
|
+
// <button
|
|
184
|
+
// type="button"
|
|
185
|
+
// onClick={handleGithub}
|
|
186
|
+
// style={oauthButtonGithub}
|
|
187
|
+
// >
|
|
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
|
+
// const overlay = {
|
|
201
|
+
// position: 'fixed',
|
|
202
|
+
// inset: 0,
|
|
203
|
+
// background: 'rgba(0,0,0,0.5)',
|
|
204
|
+
// display: 'flex',
|
|
205
|
+
// justifyContent: 'center',
|
|
206
|
+
// alignItems: 'center',
|
|
207
|
+
// padding: 20,
|
|
208
|
+
// zIndex: 9999
|
|
209
|
+
// }
|
|
210
|
+
// const modal = {
|
|
211
|
+
// width: '100%',
|
|
212
|
+
// maxWidth: 420,
|
|
213
|
+
// background: '#0f1724',
|
|
214
|
+
// borderRadius: 12,
|
|
215
|
+
// padding: 24,
|
|
216
|
+
// color: '#fff'
|
|
217
|
+
// }
|
|
218
|
+
// const title = { margin: 0, fontSize: 20, fontWeight: 600 }
|
|
219
|
+
// const label = { marginTop: 10, display: 'block', fontSize: 13 }
|
|
220
|
+
// const input = {
|
|
221
|
+
// width: '100%',
|
|
222
|
+
// padding: '10px 12px',
|
|
223
|
+
// borderRadius: 8,
|
|
224
|
+
// background: '#0b1220',
|
|
225
|
+
// color: '#fff',
|
|
226
|
+
// border: '1px solid #1e293b',
|
|
227
|
+
// marginTop: 6
|
|
228
|
+
// }
|
|
229
|
+
// const button = {
|
|
230
|
+
// marginTop: 15,
|
|
231
|
+
// width: '100%',
|
|
232
|
+
// padding: '10px 12px',
|
|
233
|
+
// borderRadius: 8,
|
|
234
|
+
// background: '#2563eb',
|
|
235
|
+
// border: 'none',
|
|
236
|
+
// color: '#fff',
|
|
237
|
+
// fontWeight: 600,
|
|
238
|
+
// cursor: 'pointer'
|
|
239
|
+
// }
|
|
240
|
+
// const oauthButtonGoogle = {
|
|
241
|
+
// flex: 1,
|
|
242
|
+
// padding: '10px 12px',
|
|
243
|
+
// borderRadius: 8,
|
|
244
|
+
// background: '#db4437',
|
|
245
|
+
// color: '#fff',
|
|
246
|
+
// border: 'none',
|
|
247
|
+
// cursor: 'pointer'
|
|
248
|
+
// }
|
|
249
|
+
// const oauthButtonGithub = {
|
|
250
|
+
// flex: 1,
|
|
251
|
+
// padding: '10px 12px',
|
|
252
|
+
// borderRadius: 8,
|
|
253
|
+
// background: '#24292f',
|
|
254
|
+
// color: '#fff',
|
|
255
|
+
// border: 'none',
|
|
256
|
+
// cursor: 'pointer'
|
|
257
|
+
// }
|
|
258
|
+
// const errorBox = { marginTop: 10, color: '#ffb4b4', fontSize: 13 }
|
|
259
|
+
// const successBox = { marginTop: 10, color: '#bef264', fontSize: 13 }
|
|
260
|
+
|
|
1
261
|
// src/signup.jsx
|
|
2
262
|
'use client'
|
|
3
|
-
import React, { useState } from 'react'
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const {
|
|
8
|
-
publishableKey,
|
|
9
|
-
baseUrl,
|
|
10
|
-
redirect,
|
|
11
|
-
redirectTo,
|
|
12
|
-
user,
|
|
13
|
-
loadingUser,
|
|
14
|
-
fetchMe,
|
|
15
|
-
setUser
|
|
16
|
-
} = useAuth()
|
|
263
|
+
import React, { useState, useRef, useEffect } from 'react'
|
|
264
|
+
import { ToastContainer, toast } from 'react-toastify'
|
|
265
|
+
import 'react-toastify/dist/ReactToastify.css'
|
|
266
|
+
import Link from 'next/link'
|
|
17
267
|
|
|
268
|
+
export default function SignUp({ agency = { name: 'MyApp', logo: '' } }) {
|
|
18
269
|
const [name, setName] = useState('')
|
|
19
270
|
const [email, setEmail] = useState('')
|
|
20
271
|
const [password, setPassword] = useState('')
|
|
21
272
|
const [loading, setLoading] = useState(false)
|
|
22
|
-
const [
|
|
273
|
+
const [loadingOauth, setLoadingOauth] = useState({ google: false, github: false })
|
|
23
274
|
const [message, setMessage] = useState(null)
|
|
275
|
+
const [showKeysPanel, setShowKeysPanel] = useState(false)
|
|
276
|
+
const [newKeys, setNewKeys] = useState(null)
|
|
277
|
+
const redirectTimer = useRef(null)
|
|
24
278
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return null
|
|
31
|
-
}
|
|
279
|
+
useEffect(() => {
|
|
280
|
+
return () => {
|
|
281
|
+
if (redirectTimer.current) clearTimeout(redirectTimer.current)
|
|
282
|
+
}
|
|
283
|
+
}, [])
|
|
32
284
|
|
|
285
|
+
// Example submit — replace with real auth call
|
|
33
286
|
async function submit(e) {
|
|
34
287
|
e.preventDefault()
|
|
35
|
-
|
|
288
|
+
if (loading) return
|
|
36
289
|
setMessage(null)
|
|
37
290
|
setLoading(true)
|
|
38
291
|
|
|
39
|
-
const url = `${(baseUrl || '').replace(/\/+$/, '')}/api/sdk/signup`
|
|
40
|
-
|
|
41
292
|
try {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
body: JSON.stringify({ name, email, password })
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
const data = await res.json().catch(() => ({}))
|
|
53
|
-
if (!res.ok) throw new Error(data.error || 'Signup failed')
|
|
54
|
-
|
|
55
|
-
if (data.user && typeof setUser === 'function') setUser(data.user)
|
|
56
|
-
if (typeof fetchMe === 'function') await fetchMe()
|
|
293
|
+
// simulate API
|
|
294
|
+
await new Promise((r) => setTimeout(r, 700))
|
|
295
|
+
|
|
296
|
+
// example error cases (replace with real server response handling)
|
|
297
|
+
if (!email.includes('@')) throw new Error('Invalid email address')
|
|
298
|
+
if (password.length < 8) throw new Error('Password must be at least 8 characters')
|
|
299
|
+
if (email === 'taken@example.com') throw new Error('User already exists')
|
|
57
300
|
|
|
301
|
+
// success
|
|
58
302
|
setMessage('Account created. Redirecting…')
|
|
303
|
+
toast.success('Account created. Redirecting…')
|
|
59
304
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}, 300)
|
|
65
|
-
}
|
|
305
|
+
// simulate redirect timer
|
|
306
|
+
redirectTimer.current = setTimeout(() => {
|
|
307
|
+
// real redirect logic here
|
|
308
|
+
}, 300)
|
|
66
309
|
} catch (err) {
|
|
67
|
-
|
|
310
|
+
const text = err?.message ?? 'Network error'
|
|
311
|
+
// show toast with black background (ToastContainer configured below)
|
|
312
|
+
toast.error(text)
|
|
68
313
|
console.error('Signup error:', err)
|
|
69
314
|
} finally {
|
|
70
315
|
setLoading(false)
|
|
@@ -72,189 +317,453 @@ export default function SignUp() {
|
|
|
72
317
|
}
|
|
73
318
|
|
|
74
319
|
async function startOAuthFlow(provider) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
setLoading(true)
|
|
78
|
-
|
|
79
|
-
try {
|
|
80
|
-
const rid =
|
|
81
|
-
(typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function')
|
|
82
|
-
? crypto.randomUUID()
|
|
83
|
-
: `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`
|
|
84
|
-
|
|
85
|
-
// callback must match what your SDK start expects and what Google console allows
|
|
86
|
-
const callbackUrl = encodeURIComponent(`${window.location.origin}/signup`)
|
|
87
|
-
|
|
88
|
-
// build start URL (server returns { oauthUrl })
|
|
89
|
-
const sdkBase = (typeof process !== 'undefined' && process.env && process.env.NEXT_PUBLIC_FLOWLINK_BASE_URL)
|
|
90
|
-
|| baseUrl || window.location.origin.replace(/\/+$/, '')
|
|
91
|
-
const startUrl = `${sdkBase}/sdk/auth/start?rid=${rid}&source=${encodeURIComponent(provider)}&callbackUrl=${callbackUrl}`
|
|
92
|
-
|
|
93
|
-
// ensure publishableKey exists
|
|
94
|
-
if (!publishableKey) {
|
|
95
|
-
throw new Error('Missing publishable key (client side). Set NEXT_PUBLIC_FLOWLINK_PUBLISHABLE_KEY or provide publishableKey in provider.')
|
|
96
|
-
}
|
|
320
|
+
if (loading || loadingOauth[provider]) return
|
|
321
|
+
setLoadingOauth(prev => ({ ...prev, [provider]: true }))
|
|
97
322
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
'x-publishable-key': publishableKey
|
|
102
|
-
}
|
|
103
|
-
})
|
|
323
|
+
try {
|
|
324
|
+
// simulate network latency
|
|
325
|
+
await new Promise((r) => setTimeout(r, 600))
|
|
104
326
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
|
|
327
|
+
// replace with actual start flow & redirect
|
|
328
|
+
toast.info(`Starting ${provider} sign-in...`)
|
|
329
|
+
// on success real flow will redirect, so no need to unset loading in that case
|
|
330
|
+
} catch (err) {
|
|
331
|
+
toast.error(err?.message || 'OAuth start failed')
|
|
332
|
+
console.error('OAuth start error:', err)
|
|
333
|
+
setLoadingOauth(prev => ({ ...prev, [provider]: false }))
|
|
111
334
|
}
|
|
335
|
+
}
|
|
112
336
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
console.error('OAuth start error:', err)
|
|
117
|
-
setError(err?.message || 'OAuth start failed')
|
|
118
|
-
setLoading(false)
|
|
337
|
+
const handleGoogle = (e) => {
|
|
338
|
+
if (e && typeof e.preventDefault === 'function') e.preventDefault()
|
|
339
|
+
startOAuthFlow('google')
|
|
119
340
|
}
|
|
120
|
-
}
|
|
121
341
|
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
342
|
+
const handleGithub = (e) => {
|
|
343
|
+
if (e && typeof e.preventDefault === 'function') e.preventDefault()
|
|
344
|
+
startOAuthFlow('github')
|
|
345
|
+
}
|
|
126
346
|
|
|
127
|
-
const handleGithub = (e) => {
|
|
128
|
-
if (e && typeof e.preventDefault === 'function') e.preventDefault()
|
|
129
|
-
startOAuthFlow('github')
|
|
130
|
-
}
|
|
131
347
|
return (
|
|
132
348
|
<div style={overlay}>
|
|
349
|
+
<ToastContainer
|
|
350
|
+
position="top-right"
|
|
351
|
+
autoClose={5000}
|
|
352
|
+
newestOnTop
|
|
353
|
+
closeOnClick
|
|
354
|
+
pauseOnHover
|
|
355
|
+
draggable
|
|
356
|
+
toastStyle={{
|
|
357
|
+
background: '#000000',
|
|
358
|
+
color: '#ffffff',
|
|
359
|
+
borderRadius: 10,
|
|
360
|
+
boxShadow: '0 6px 18px rgba(0,0,0,0.6)',
|
|
361
|
+
padding: '10px 14px',
|
|
362
|
+
fontWeight: 500
|
|
363
|
+
}}
|
|
364
|
+
/>
|
|
365
|
+
|
|
133
366
|
<div style={modal}>
|
|
134
|
-
<
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
<input
|
|
148
|
-
name="email"
|
|
149
|
-
type="email"
|
|
150
|
-
style={input}
|
|
151
|
-
value={email}
|
|
152
|
-
onChange={e => setEmail(e.target.value)}
|
|
153
|
-
required
|
|
154
|
-
autoComplete="email"
|
|
155
|
-
/>
|
|
156
|
-
|
|
157
|
-
<label style={label}>Password</label>
|
|
158
|
-
<input
|
|
159
|
-
name="password"
|
|
160
|
-
type="password"
|
|
161
|
-
style={input}
|
|
162
|
-
value={password}
|
|
163
|
-
onChange={e => setPassword(e.target.value)}
|
|
164
|
-
required
|
|
165
|
-
autoComplete="new-password"
|
|
166
|
-
/>
|
|
167
|
-
|
|
168
|
-
<div style={{ marginTop: 12 }}>
|
|
169
|
-
<button style={button} type="submit" disabled={loading}>
|
|
170
|
-
{loading ? 'Creating...' : 'Create account'}
|
|
171
|
-
</button>
|
|
172
|
-
</div>
|
|
367
|
+
<div style={modalInner}>
|
|
368
|
+
<header style={header}>
|
|
369
|
+
<div style={brandRow}>
|
|
370
|
+
<div style={logo}>
|
|
371
|
+
{/* if you have a real logo, replace with <Image src={agency.logo} ... /> */}
|
|
372
|
+
<div style={logoCircle} aria-hidden />
|
|
373
|
+
</div>
|
|
374
|
+
<div>
|
|
375
|
+
<h1 style={title}>Sign up to {agency.name}</h1>
|
|
376
|
+
<div style={subtitle}>Welcome! Create your account.</div>
|
|
377
|
+
</div>
|
|
378
|
+
</div>
|
|
379
|
+
</header>
|
|
173
380
|
|
|
174
|
-
<
|
|
381
|
+
<section style={oauthSection}>
|
|
175
382
|
<button
|
|
176
|
-
type="button"
|
|
177
383
|
onClick={handleGoogle}
|
|
178
|
-
|
|
384
|
+
type="button"
|
|
385
|
+
style={{ ...oauthButton, ...oauthGoogle }}
|
|
386
|
+
disabled={loading || loadingOauth.google}
|
|
387
|
+
aria-disabled={loading || loadingOauth.google}
|
|
179
388
|
>
|
|
180
|
-
|
|
389
|
+
<svg width={18} style={{ marginRight: 10 }} viewBox="-3 0 262 262" xmlns="http://www.w3.org/2000/svg" fill="#000000" aria-hidden>
|
|
390
|
+
<path d="M255.878 133.451c0-10.734-.871-18.567-2.756-26.69H130.55v48.448h71.947c-1.45 12.04-9.283 30.172-26.69 42.356l-.244 1.622 38.755 30.023 2.685.268c24.659-22.774 38.875-56.282 38.875-96.027" fill="#4285F4"></path>
|
|
391
|
+
<path d="M130.55 261.1c35.248 0 64.839-11.605 86.453-31.622l-41.196-31.913c-11.024 7.688-25.82 13.055-45.257 13.055-34.523 0-63.824-22.773-74.269-54.25l-1.531.13-40.298 31.187-.527 1.465C35.393 231.798 79.49 261.1 130.55 261.1" fill="#34A853"></path>
|
|
392
|
+
<path d="M56.281 156.37c-2.756-8.123-4.351-16.827-4.351-25.82 0-8.994 1.595-17.697 4.206-25.82l-.073-1.73L15.26 71.312l-1.335.635C5.077 89.644 0 109.517 0 130.55s5.077 40.905 13.925 58.602l42.356-32.782" fill="#FBBC05"></path>
|
|
393
|
+
<path d="M130.55 50.479c24.514 0 41.05 10.589 50.479 19.438l36.844-35.974C195.245 12.91 165.798 0 130.55 0 79.49 0 35.393 29.301 13.925 71.947l42.211 32.783c10.59-31.477 39.891-54.251 74.414-54.251" fill="#EB4335"></path>
|
|
394
|
+
</svg>
|
|
395
|
+
<span>{loadingOauth.google ? 'Loading...' : 'Continue with Google'}</span>
|
|
181
396
|
</button>
|
|
182
397
|
|
|
183
398
|
<button
|
|
184
|
-
type="button"
|
|
185
399
|
onClick={handleGithub}
|
|
186
|
-
|
|
400
|
+
type="button"
|
|
401
|
+
style={{ ...oauthButton, ...oauthGithub }}
|
|
402
|
+
disabled={loading || loadingOauth.github}
|
|
403
|
+
aria-disabled={loading || loadingOauth.github}
|
|
187
404
|
>
|
|
188
|
-
|
|
405
|
+
<svg width={18} style={{ marginRight: 10 }} xmlns="http://www.w3.org/2000/svg" fill="white" viewBox="0 0 20 20" aria-hidden>
|
|
406
|
+
<path fillRule="evenodd" d="M10 .333A9.911 9.911 0 0 0 6.866 19.65c.5.092.678-.215.678-.477 0-.237-.01-1.017-.014-1.845-2.757.6-3.338-1.169-3.338-1.169a2.627 2.627 0 0 0-1.1-1.451c-.9-.615.07-.6.07-.6a2.084 2.084 0 0 1 1.518 1.021 2.11 2.11 0 0 0 2.884.823c.044-.503.268-.973.63-1.325-2.2-.25-4.516-1.1-4.516-4.9A3.832 3.832 0 0 1 4.7 7.068a3.56 3.56 0 0 1 .095-2.623s.832-.266 2.726 1.016a9.409 9.409 0 0 1 4.962 0c1.89-1.282 2.717-1.016 2.717-1.016.366.83.402 1.768.1 2.623a3.827 3.827 0 0 1 1.02 2.659c0 3.807-2.319 4.644-4.525 4.889a2.366 2.366 0 0 1 .673 1.834c0 1.326-.012 2.394-.012 2.72 0 .263.18.572.681.475A9.911 9.911 0 0 0 10 .333Z" clipRule="evenodd" />
|
|
407
|
+
</svg>
|
|
408
|
+
<span>{loadingOauth.github ? 'Loading...' : 'Continue with GitHub'}</span>
|
|
189
409
|
</button>
|
|
410
|
+
</section>
|
|
411
|
+
|
|
412
|
+
<div style={dividerRow}>
|
|
413
|
+
<div style={line} />
|
|
414
|
+
<div style={orText}>or</div>
|
|
415
|
+
<div style={line} />
|
|
190
416
|
</div>
|
|
191
417
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
418
|
+
<form onSubmit={submit} style={form}>
|
|
419
|
+
<label style={label} htmlFor="name">
|
|
420
|
+
<span style={labelText}>Name</span>
|
|
421
|
+
<input
|
|
422
|
+
id="name"
|
|
423
|
+
type="text"
|
|
424
|
+
value={name}
|
|
425
|
+
onChange={e => setName(e.target.value)}
|
|
426
|
+
placeholder="Your name"
|
|
427
|
+
style={input}
|
|
428
|
+
autoComplete="name"
|
|
429
|
+
/>
|
|
430
|
+
</label>
|
|
431
|
+
|
|
432
|
+
<label style={label} htmlFor="email">
|
|
433
|
+
<span style={labelText}>Email address</span>
|
|
434
|
+
<input
|
|
435
|
+
id="email"
|
|
436
|
+
type="email"
|
|
437
|
+
value={email}
|
|
438
|
+
onChange={e => setEmail(e.target.value)}
|
|
439
|
+
required
|
|
440
|
+
placeholder="you@example.com"
|
|
441
|
+
style={input}
|
|
442
|
+
autoComplete="email"
|
|
443
|
+
/>
|
|
444
|
+
</label>
|
|
445
|
+
|
|
446
|
+
<label style={label} htmlFor="password">
|
|
447
|
+
<span style={labelText}>Password</span>
|
|
448
|
+
<input
|
|
449
|
+
id="password"
|
|
450
|
+
type="password"
|
|
451
|
+
value={password}
|
|
452
|
+
onChange={e => setPassword(e.target.value)}
|
|
453
|
+
required
|
|
454
|
+
placeholder="••••••••"
|
|
455
|
+
style={input}
|
|
456
|
+
autoComplete="new-password"
|
|
457
|
+
/>
|
|
458
|
+
</label>
|
|
459
|
+
|
|
460
|
+
<button
|
|
461
|
+
type="submit"
|
|
462
|
+
style={submitButton}
|
|
463
|
+
disabled={loading}
|
|
464
|
+
aria-disabled={loading}
|
|
465
|
+
>
|
|
466
|
+
{loading ? 'Signing up...' : 'Sign Up'}
|
|
467
|
+
</button>
|
|
468
|
+
</form>
|
|
469
|
+
</div>
|
|
470
|
+
|
|
471
|
+
<div style={modalFooter}>
|
|
472
|
+
<div style={belowRow}>
|
|
473
|
+
<span style={muted}>Already have an account? </span>
|
|
474
|
+
<Link href="/sign-in" style={link}>Sign in</Link>
|
|
475
|
+
</div>
|
|
476
|
+
<div style={dividerThin} />
|
|
477
|
+
<div style={secured}>
|
|
478
|
+
<div style={securedText}>Secured by auth</div>
|
|
479
|
+
</div>
|
|
480
|
+
</div>
|
|
195
481
|
</div>
|
|
482
|
+
|
|
483
|
+
{showKeysPanel && newKeys && (
|
|
484
|
+
<div style={keysOverlay}>
|
|
485
|
+
<div style={keysPanel}>
|
|
486
|
+
<h3 style={{ margin: 0, marginBottom: 8 }}>Your API keys — copy now</h3>
|
|
487
|
+
<p style={{ margin: 0, marginBottom: 12, color: '#4b5563', fontSize: 13 }}>Secret key is shown only once. Save it securely.</p>
|
|
488
|
+
|
|
489
|
+
<div style={{ marginBottom: 10 }}>
|
|
490
|
+
<div style={{ fontSize: 12, color: '#6b7280' }}>Publishable key (use in client)</div>
|
|
491
|
+
<pre style={keyBox}>{newKeys.publishableKey}</pre>
|
|
492
|
+
<button onClick={() => navigator.clipboard.writeText(newKeys.publishableKey)} style={copyBtn}>Copy publishable key</button>
|
|
493
|
+
</div>
|
|
494
|
+
|
|
495
|
+
<div style={{ marginBottom: 6 }}>
|
|
496
|
+
<div style={{ fontSize: 12, color: '#6b7280' }}>Secret key (server only)</div>
|
|
497
|
+
<pre style={keyBox}>{newKeys.secretKey}</pre>
|
|
498
|
+
<button onClick={() => navigator.clipboard.writeText(newKeys.secretKey)} style={copyBtn}>Copy secret key</button>
|
|
499
|
+
</div>
|
|
500
|
+
</div>
|
|
501
|
+
</div>
|
|
502
|
+
)}
|
|
196
503
|
</div>
|
|
197
504
|
)
|
|
198
505
|
}
|
|
199
506
|
|
|
507
|
+
/* Styles (JS objects) */
|
|
508
|
+
|
|
509
|
+
/* overlay: translucent so underlying content is visible; backdrop blur and subtle vignette */
|
|
200
510
|
const overlay = {
|
|
201
511
|
position: 'fixed',
|
|
202
512
|
inset: 0,
|
|
203
|
-
background: 'rgba(0,0,0,0.5)',
|
|
204
513
|
display: 'flex',
|
|
205
|
-
justifyContent: 'center',
|
|
206
514
|
alignItems: 'center',
|
|
515
|
+
justifyContent: 'center',
|
|
207
516
|
padding: 20,
|
|
517
|
+
background: 'linear-gradient(180deg, rgba(2,6,23,0.25), rgba(2,6,23,0.35))',
|
|
518
|
+
backdropFilter: 'blur(6px)',
|
|
519
|
+
minHeight: '100vh',
|
|
208
520
|
zIndex: 9999
|
|
209
521
|
}
|
|
522
|
+
|
|
523
|
+
/* modal itself uses semi-transparent background + strong soft shadow,
|
|
524
|
+
so things beneath are slightly visible but content still readable */
|
|
210
525
|
const modal = {
|
|
211
526
|
width: '100%',
|
|
212
|
-
maxWidth:
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
527
|
+
maxWidth: 560,
|
|
528
|
+
borderRadius: 14,
|
|
529
|
+
background: 'rgba(10,14,20,0.65)', // translucent neutral
|
|
530
|
+
border: '1px solid rgba(148,163,184,0.12)', // more visible neutral border
|
|
531
|
+
boxShadow: '0 20px 50px rgba(2,6,23,0.55), inset 0 1px 0 rgba(255,255,255,0.02)',
|
|
532
|
+
overflow: 'hidden',
|
|
533
|
+
display: 'flex',
|
|
534
|
+
flexDirection: 'column',
|
|
535
|
+
transform: 'translateY(-6px)'
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
const modalInner = {
|
|
539
|
+
padding: 18,
|
|
540
|
+
display: 'flex',
|
|
541
|
+
flexDirection: 'column',
|
|
542
|
+
gap: 12
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/* HEADER / BRAND */
|
|
546
|
+
const header = {
|
|
547
|
+
paddingBottom: 6,
|
|
548
|
+
borderBottom: '1px solid rgba(148,163,184,0.04)'
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
const brandRow = {
|
|
552
|
+
display: 'flex',
|
|
553
|
+
alignItems: 'center',
|
|
554
|
+
gap: 12
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const logo = {
|
|
558
|
+
width: 44,
|
|
559
|
+
height: 44,
|
|
560
|
+
display: 'flex',
|
|
561
|
+
alignItems: 'center',
|
|
562
|
+
justifyContent: 'center'
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
const logoCircle = {
|
|
566
|
+
width: 36,
|
|
567
|
+
height: 36,
|
|
568
|
+
borderRadius: 999,
|
|
569
|
+
background: 'linear-gradient(135deg,#2f3438,#11151a)',
|
|
570
|
+
boxShadow: '0 4px 12px rgba(2,6,23,0.6)'
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const title = {
|
|
574
|
+
margin: 0,
|
|
575
|
+
fontSize: 18,
|
|
576
|
+
fontWeight: 600,
|
|
577
|
+
color: '#e6e6e6'
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
const subtitle = {
|
|
581
|
+
fontSize: 13,
|
|
582
|
+
color: 'rgba(230,230,230,0.72)',
|
|
583
|
+
marginTop: 4
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/* OAUTH ROW */
|
|
587
|
+
const oauthSection = {
|
|
588
|
+
marginTop: 8,
|
|
589
|
+
display: 'flex',
|
|
590
|
+
gap: 8
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
const oauthButton = {
|
|
594
|
+
flex: 1,
|
|
595
|
+
display: 'inline-flex',
|
|
596
|
+
alignItems: 'center',
|
|
597
|
+
justifyContent: 'center',
|
|
598
|
+
padding: '10px 12px',
|
|
599
|
+
borderRadius: 10,
|
|
600
|
+
border: '1px solid rgba(148,163,184,0.08)',
|
|
601
|
+
fontSize: 14,
|
|
602
|
+
cursor: 'pointer',
|
|
603
|
+
userSelect: 'none',
|
|
604
|
+
gap: 8,
|
|
605
|
+
minHeight: 40
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
const oauthGoogle = {
|
|
609
|
+
background: 'linear-gradient(180deg, rgba(255,255,255,0.03), rgba(255,255,255,0.01))',
|
|
216
610
|
color: '#fff'
|
|
217
611
|
}
|
|
218
|
-
|
|
219
|
-
const
|
|
612
|
+
|
|
613
|
+
const oauthGithub = {
|
|
614
|
+
background: 'linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0.00))',
|
|
615
|
+
color: '#fff'
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/* DIVIDER */
|
|
619
|
+
const dividerRow = {
|
|
620
|
+
display: 'flex',
|
|
621
|
+
alignItems: 'center',
|
|
622
|
+
gap: 10,
|
|
623
|
+
marginTop: 12
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
const line = {
|
|
627
|
+
flex: 1,
|
|
628
|
+
height: 1,
|
|
629
|
+
background: 'rgba(148,163,184,0.06)'
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
const orText = {
|
|
633
|
+
fontSize: 13,
|
|
634
|
+
color: 'rgba(230,230,230,0.65)',
|
|
635
|
+
padding: '0 8px'
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/* FORM */
|
|
639
|
+
const form = {
|
|
640
|
+
display: 'flex',
|
|
641
|
+
flexDirection: 'column',
|
|
642
|
+
gap: 10,
|
|
643
|
+
marginTop: 6
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
const label = {
|
|
647
|
+
display: 'flex',
|
|
648
|
+
flexDirection: 'column',
|
|
649
|
+
gap: 6
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
const labelText = {
|
|
653
|
+
fontSize: 13,
|
|
654
|
+
color: 'rgba(230,230,230,0.68)'
|
|
655
|
+
}
|
|
656
|
+
|
|
220
657
|
const input = {
|
|
221
658
|
width: '100%',
|
|
222
659
|
padding: '10px 12px',
|
|
223
|
-
borderRadius:
|
|
224
|
-
background: '
|
|
225
|
-
color: '#
|
|
226
|
-
border: '1px solid
|
|
227
|
-
|
|
660
|
+
borderRadius: 10,
|
|
661
|
+
background: 'rgba(255,255,255,0.02)',
|
|
662
|
+
color: '#e6e6e6',
|
|
663
|
+
border: '1px solid rgba(148,163,184,0.10)', // slightly stronger border for visibility
|
|
664
|
+
fontSize: 14,
|
|
665
|
+
outline: 'none',
|
|
666
|
+
boxSizing: 'border-box',
|
|
667
|
+
transition: 'box-shadow 120ms, border-color 120ms'
|
|
228
668
|
}
|
|
229
|
-
|
|
230
|
-
|
|
669
|
+
|
|
670
|
+
const submitButton = {
|
|
671
|
+
marginTop: 6,
|
|
231
672
|
width: '100%',
|
|
232
673
|
padding: '10px 12px',
|
|
233
|
-
borderRadius:
|
|
234
|
-
background: '
|
|
674
|
+
borderRadius: 10,
|
|
675
|
+
background: 'linear-gradient(180deg,#475569,#94a3b8)', // neutral blue-gray gradient
|
|
235
676
|
border: 'none',
|
|
236
677
|
color: '#fff',
|
|
237
678
|
fontWeight: 600,
|
|
238
|
-
cursor: 'pointer'
|
|
679
|
+
cursor: 'pointer',
|
|
680
|
+
fontSize: 15,
|
|
681
|
+
display: 'inline-flex',
|
|
682
|
+
alignItems: 'center',
|
|
683
|
+
justifyContent: 'center',
|
|
684
|
+
minHeight: 44
|
|
239
685
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
686
|
+
|
|
687
|
+
/* FOOTER / BELOW */
|
|
688
|
+
const modalFooter = {
|
|
689
|
+
padding: '12px 18px 18px 18px',
|
|
690
|
+
borderTop: '1px solid rgba(148,163,184,0.03)',
|
|
691
|
+
display: 'flex',
|
|
692
|
+
flexDirection: 'column',
|
|
693
|
+
gap: 8
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
const belowRow = {
|
|
697
|
+
textAlign: 'center',
|
|
698
|
+
display: 'flex',
|
|
699
|
+
justifyContent: 'center',
|
|
700
|
+
gap: 8,
|
|
701
|
+
alignItems: 'center'
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
const muted = {
|
|
705
|
+
color: 'rgba(230,230,230,0.66)',
|
|
706
|
+
fontSize: 13
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
const link = {
|
|
710
|
+
color: '#9fb0d9',
|
|
711
|
+
textDecoration: 'none',
|
|
712
|
+
fontWeight: 600
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
const dividerThin = {
|
|
716
|
+
height: 1,
|
|
717
|
+
background: 'rgba(148,163,184,0.04)',
|
|
718
|
+
marginTop: 6,
|
|
719
|
+
marginBottom: 6
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
const secured = {
|
|
723
|
+
textAlign: 'center'
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
const securedText = {
|
|
727
|
+
color: 'rgba(230,230,230,0.9)',
|
|
728
|
+
fontSize: 13,
|
|
729
|
+
fontWeight: 600
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
/* KEYS PANEL */
|
|
733
|
+
const keysOverlay = {
|
|
734
|
+
position: 'fixed',
|
|
735
|
+
inset: 0,
|
|
736
|
+
zIndex: 20000,
|
|
737
|
+
display: 'flex',
|
|
738
|
+
alignItems: 'center',
|
|
739
|
+
justifyContent: 'center',
|
|
740
|
+
background: 'rgba(2,6,23,0.45)'
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
const keysPanel = {
|
|
744
|
+
background: '#fff',
|
|
745
|
+
color: '#111827',
|
|
746
|
+
padding: 18,
|
|
747
|
+
borderRadius: 10,
|
|
748
|
+
maxWidth: 720,
|
|
749
|
+
width: '95%',
|
|
750
|
+
boxShadow: '0 12px 40px rgba(2,6,23,0.5)'
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
const keyBox = {
|
|
754
|
+
padding: 10,
|
|
755
|
+
background: '#f3f4f6',
|
|
243
756
|
borderRadius: 8,
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
border: 'none',
|
|
247
|
-
cursor: 'pointer'
|
|
757
|
+
overflowX: 'auto',
|
|
758
|
+
fontSize: 13
|
|
248
759
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
760
|
+
|
|
761
|
+
const copyBtn = {
|
|
762
|
+
marginTop: 8,
|
|
763
|
+
padding: '8px 10px',
|
|
252
764
|
borderRadius: 8,
|
|
253
|
-
background: '#
|
|
765
|
+
background: '#111827',
|
|
254
766
|
color: '#fff',
|
|
255
767
|
border: 'none',
|
|
256
768
|
cursor: 'pointer'
|
|
257
769
|
}
|
|
258
|
-
const errorBox = { marginTop: 10, color: '#ffb4b4', fontSize: 13 }
|
|
259
|
-
const successBox = { marginTop: 10, color: '#bef264', fontSize: 13 }
|
|
260
|
-
|