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/dist/index.css +694 -0
- package/dist/index.js +701 -1134
- package/package.json +4 -3
- package/src/ErrorBox.jsx +22 -0
- package/src/SignIn.jsx +212 -0
- package/src/SignUp.jsx +747 -0
- package/src/api.js +60 -0
- package/src/createAuthMiddleware.js +69 -0
- package/src/index.d.ts +15 -0
- package/src/index.js +5 -0
- package/src/init.js +100 -0
- package/src/provider.js +261 -0
- package/src/securityUtils.js +151 -0
- package/src/useAuth.js +13 -0
package/src/SignUp.jsx
ADDED
|
@@ -0,0 +1,747 @@
|
|
|
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
|
+
|
|
261
|
+
'use client'
|
|
262
|
+
import React, { useState, useRef, useEffect } from 'react'
|
|
263
|
+
import { ToastContainer, toast } from 'react-toastify'
|
|
264
|
+
import 'react-toastify/dist/ReactToastify.css'
|
|
265
|
+
import { useAuth } from './provider.js'
|
|
266
|
+
|
|
267
|
+
export default function SignUp() {
|
|
268
|
+
const {
|
|
269
|
+
publishableKey,
|
|
270
|
+
baseUrl,
|
|
271
|
+
redirect,
|
|
272
|
+
redirectTo,
|
|
273
|
+
user,
|
|
274
|
+
loadingUser,
|
|
275
|
+
fetchMe,
|
|
276
|
+
setUser
|
|
277
|
+
} = useAuth()
|
|
278
|
+
|
|
279
|
+
const [name, setName] = useState('')
|
|
280
|
+
const [email, setEmail] = useState('')
|
|
281
|
+
const [password, setPassword] = useState('')
|
|
282
|
+
const [loading, setLoading] = useState(false)
|
|
283
|
+
const [message, setMessage] = useState(null)
|
|
284
|
+
const redirectTimer = useRef(null)
|
|
285
|
+
|
|
286
|
+
useEffect(() => {
|
|
287
|
+
return () => {
|
|
288
|
+
if (redirectTimer.current) clearTimeout(redirectTimer.current)
|
|
289
|
+
}
|
|
290
|
+
}, [])
|
|
291
|
+
|
|
292
|
+
if (loadingUser) return null
|
|
293
|
+
|
|
294
|
+
if (user && redirect) {
|
|
295
|
+
if (typeof redirectTo === 'function') redirectTo(redirect)
|
|
296
|
+
else if (typeof window !== 'undefined') window.location.assign(redirect)
|
|
297
|
+
return null
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async function submit(e) {
|
|
301
|
+
e.preventDefault()
|
|
302
|
+
if (loading) return
|
|
303
|
+
setMessage(null)
|
|
304
|
+
setLoading(true)
|
|
305
|
+
|
|
306
|
+
const url = `${(baseUrl || '').replace(/\/+$/, '')}/api/sdk/signup`
|
|
307
|
+
|
|
308
|
+
try {
|
|
309
|
+
const res = await fetch(url, {
|
|
310
|
+
method: 'POST',
|
|
311
|
+
credentials: 'include',
|
|
312
|
+
headers: {
|
|
313
|
+
'Content-Type': 'application/json',
|
|
314
|
+
'x-publishable-key': publishableKey || ''
|
|
315
|
+
},
|
|
316
|
+
body: JSON.stringify({ name, email, password })
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
const data = await res.json().catch(async () => {
|
|
320
|
+
const text = await res.text().catch(() => '')
|
|
321
|
+
return { _raw: text }
|
|
322
|
+
})
|
|
323
|
+
if (!res.ok) {
|
|
324
|
+
const errMsg = data?.error || data?._raw || `Signup failed (${res.status})`
|
|
325
|
+
throw new Error(errMsg)
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (data.user && typeof setUser === 'function') setUser(data.user)
|
|
329
|
+
if (typeof fetchMe === 'function') await fetchMe()
|
|
330
|
+
|
|
331
|
+
setMessage('Account created. Redirecting…')
|
|
332
|
+
toast.success('Account created. Redirecting…')
|
|
333
|
+
|
|
334
|
+
if (redirect) {
|
|
335
|
+
redirectTimer.current = setTimeout(() => {
|
|
336
|
+
if (typeof redirectTo === 'function') redirectTo(redirect)
|
|
337
|
+
else if (typeof window !== 'undefined') window.location.assign(redirect)
|
|
338
|
+
}, 300)
|
|
339
|
+
}
|
|
340
|
+
} catch (err) {
|
|
341
|
+
const text = err?.message ?? 'Network error'
|
|
342
|
+
toast.error(text)
|
|
343
|
+
console.error('Signup error:', err)
|
|
344
|
+
} finally {
|
|
345
|
+
setLoading(false)
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
async function startOAuthFlow(provider) {
|
|
352
|
+
if (loading) return
|
|
353
|
+
// prevent double clicks
|
|
354
|
+
setLoading(true)
|
|
355
|
+
|
|
356
|
+
try {
|
|
357
|
+
const rid =
|
|
358
|
+
(typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function')
|
|
359
|
+
? crypto.randomUUID()
|
|
360
|
+
: `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`
|
|
361
|
+
|
|
362
|
+
// callback must match what your SDK start expects and what Google console allows
|
|
363
|
+
const callbackUrl = encodeURIComponent(`${window.location.origin}/signup`)
|
|
364
|
+
|
|
365
|
+
// build start URL (server returns { oauthUrl })
|
|
366
|
+
const sdkBase = baseUrl || window.location.origin.replace(/\/+$/, '')
|
|
367
|
+
const startUrl = `${sdkBase}/sdk/auth/start?rid=${rid}&source=${encodeURIComponent(provider)}&callbackUrl=${callbackUrl}`
|
|
368
|
+
|
|
369
|
+
// ensure publishableKey exists
|
|
370
|
+
if (!publishableKey) {
|
|
371
|
+
throw new Error('Missing publishable key (client side). Set NEXT_PUBLIC_FLOWLINK_PUBLISHABLE_KEY or provide publishableKey in provider.')
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const res = await fetch(startUrl, {
|
|
375
|
+
method: 'GET',
|
|
376
|
+
headers: {
|
|
377
|
+
'x-publishable-key': publishableKey
|
|
378
|
+
}
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
const data = await res.json().catch(() => null)
|
|
382
|
+
if (!res.ok) {
|
|
383
|
+
throw new Error(data?.error || `OAuth start failed (${res.status})`)
|
|
384
|
+
}
|
|
385
|
+
if (!data?.oauthUrl) {
|
|
386
|
+
throw new Error('SDK start did not return oauthUrl')
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// navigate to provider (Google/GitHub)
|
|
390
|
+
window.location.href = data.oauthUrl
|
|
391
|
+
} catch (err) {
|
|
392
|
+
console.error('OAuth start error:', err)
|
|
393
|
+
toast.error(err?.message || 'OAuth start failed')
|
|
394
|
+
setLoading(false)
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const handleGoogle = (e) => {
|
|
401
|
+
if (e && typeof e.preventDefault === 'function') e.preventDefault()
|
|
402
|
+
startOAuthFlow('google')
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const handleGithub = (e) => {
|
|
406
|
+
if (e && typeof e.preventDefault === 'function') e.preventDefault()
|
|
407
|
+
startOAuthFlow('github')
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return (
|
|
411
|
+
<div style={page}>
|
|
412
|
+
<ToastContainer position="top-right" autoClose={5000} />
|
|
413
|
+
<div style={card}>
|
|
414
|
+
<div style={cardInner}>
|
|
415
|
+
<div style={brand}>
|
|
416
|
+
<div style={brandRow}>
|
|
417
|
+
<div style={logoPlaceholder} aria-hidden />
|
|
418
|
+
<h1 style={brandTitle}>Create account</h1>
|
|
419
|
+
</div>
|
|
420
|
+
<div style={brandSub}>
|
|
421
|
+
<div style={brandLead}>Sign up to continue</div>
|
|
422
|
+
<div style={brandMuted}>Welcome! Create your account.</div>
|
|
423
|
+
</div>
|
|
424
|
+
</div>
|
|
425
|
+
|
|
426
|
+
<div style={oauthRow}>
|
|
427
|
+
<button
|
|
428
|
+
onClick={handleGoogle}
|
|
429
|
+
type="button"
|
|
430
|
+
style={{ ...oauthButton, ...oauthGoogle }}
|
|
431
|
+
disabled={loading}
|
|
432
|
+
aria-disabled={loading}
|
|
433
|
+
>
|
|
434
|
+
<svg width={18} style={{ marginRight: 10 }} viewBox="-3 0 262 262" xmlns="http://www.w3.org/2000/svg" fill="#000000" aria-hidden>
|
|
435
|
+
<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>
|
|
436
|
+
<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>
|
|
437
|
+
<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>
|
|
438
|
+
<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>
|
|
439
|
+
</svg>
|
|
440
|
+
<span>{loading ? 'Loading...' : 'Continue with Google'}</span>
|
|
441
|
+
</button>
|
|
442
|
+
|
|
443
|
+
<button
|
|
444
|
+
onClick={handleGithub}
|
|
445
|
+
type="button"
|
|
446
|
+
style={{ ...oauthButton, ...oauthGithub }}
|
|
447
|
+
disabled={loading}
|
|
448
|
+
aria-disabled={loading}
|
|
449
|
+
>
|
|
450
|
+
<svg width={18} style={{ marginRight: 10 }} xmlns="http://www.w3.org/2000/svg" fill="white" viewBox="0 0 20 20" aria-hidden>
|
|
451
|
+
<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" />
|
|
452
|
+
</svg>
|
|
453
|
+
<span>{loading ? 'Loading...' : 'Continue with GitHub'}</span>
|
|
454
|
+
</button>
|
|
455
|
+
</div>
|
|
456
|
+
|
|
457
|
+
<div style={dividerRow}>
|
|
458
|
+
<div style={line} />
|
|
459
|
+
<div style={orText}>or</div>
|
|
460
|
+
<div style={line} />
|
|
461
|
+
</div>
|
|
462
|
+
|
|
463
|
+
<form onSubmit={submit} style={form}>
|
|
464
|
+
<label style={label} htmlFor="name">
|
|
465
|
+
<span style={labelText}>Name</span>
|
|
466
|
+
<input
|
|
467
|
+
id="name"
|
|
468
|
+
type="text"
|
|
469
|
+
value={name}
|
|
470
|
+
onChange={e => setName(e.target.value)}
|
|
471
|
+
placeholder="Your name"
|
|
472
|
+
style={input}
|
|
473
|
+
autoComplete="name"
|
|
474
|
+
/>
|
|
475
|
+
</label>
|
|
476
|
+
|
|
477
|
+
<label style={label} htmlFor="email">
|
|
478
|
+
<span style={labelText}>Email address</span>
|
|
479
|
+
<input
|
|
480
|
+
id="email"
|
|
481
|
+
type="email"
|
|
482
|
+
value={email}
|
|
483
|
+
onChange={e => setEmail(e.target.value)}
|
|
484
|
+
required
|
|
485
|
+
placeholder="you@example.com"
|
|
486
|
+
style={input}
|
|
487
|
+
autoComplete="email"
|
|
488
|
+
/>
|
|
489
|
+
</label>
|
|
490
|
+
|
|
491
|
+
<label style={label} htmlFor="password">
|
|
492
|
+
<span style={labelText}>Password</span>
|
|
493
|
+
<input
|
|
494
|
+
id="password"
|
|
495
|
+
type="password"
|
|
496
|
+
value={password}
|
|
497
|
+
onChange={e => setPassword(e.target.value)}
|
|
498
|
+
required
|
|
499
|
+
placeholder="••••••••"
|
|
500
|
+
style={input}
|
|
501
|
+
autoComplete="new-password"
|
|
502
|
+
/>
|
|
503
|
+
</label>
|
|
504
|
+
|
|
505
|
+
<button
|
|
506
|
+
type="submit"
|
|
507
|
+
style={submitButton}
|
|
508
|
+
disabled={loading}
|
|
509
|
+
aria-disabled={loading}
|
|
510
|
+
>
|
|
511
|
+
{loading ? 'Signing up...' : 'Sign Up'}
|
|
512
|
+
</button>
|
|
513
|
+
</form>
|
|
514
|
+
</div>
|
|
515
|
+
|
|
516
|
+
<div style={below}>
|
|
517
|
+
<div style={belowRow}>
|
|
518
|
+
<span style={muted}>Already have an account? </span>
|
|
519
|
+
<a href="/sign-in" style={link}>Sign in</a>
|
|
520
|
+
</div>
|
|
521
|
+
<div style={dividerThin} />
|
|
522
|
+
<div style={secured}>
|
|
523
|
+
<div style={securedText}>Secured by auth</div>
|
|
524
|
+
</div>
|
|
525
|
+
</div>
|
|
526
|
+
</div>
|
|
527
|
+
</div>
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
)
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/* styles (JS objects) */
|
|
534
|
+
const page = {
|
|
535
|
+
position: 'fixed',
|
|
536
|
+
inset: 0,
|
|
537
|
+
display: 'flex',
|
|
538
|
+
alignItems: 'center',
|
|
539
|
+
justifyContent: 'center',
|
|
540
|
+
padding: 24,
|
|
541
|
+
background: 'linear-gradient(to bottom, #0b1220 0%, #071023 40%, #02040a 100%)',
|
|
542
|
+
color: '#f8e9d3',
|
|
543
|
+
minHeight: '100vh',
|
|
544
|
+
zIndex: 9999
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
const card = {
|
|
548
|
+
width: '100%',
|
|
549
|
+
maxWidth: 520,
|
|
550
|
+
borderRadius: 18,
|
|
551
|
+
background: 'linear-gradient(180deg, rgba(15,19,36,0.9) 0%, rgba(6,10,18,0.95) 100%)',
|
|
552
|
+
border: '1px solid rgba(148,163,184,0.06)',
|
|
553
|
+
boxShadow: '0 10px 30px rgba(2,6,23,0.6)',
|
|
554
|
+
overflow: 'hidden',
|
|
555
|
+
display: 'flex',
|
|
556
|
+
flexDirection: 'column',
|
|
557
|
+
gap: 12
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
const cardInner = {
|
|
561
|
+
padding: 26,
|
|
562
|
+
display: 'flex',
|
|
563
|
+
flexDirection: 'column',
|
|
564
|
+
gap: 18
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
const brand = {
|
|
568
|
+
display: 'flex',
|
|
569
|
+
flexDirection: 'column',
|
|
570
|
+
gap: 8
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const brandRow = {
|
|
574
|
+
display: 'flex',
|
|
575
|
+
alignItems: 'center',
|
|
576
|
+
gap: 12
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
const logoPlaceholder = {
|
|
580
|
+
width: 36,
|
|
581
|
+
height: 36,
|
|
582
|
+
borderRadius: 999,
|
|
583
|
+
background: 'linear-gradient(135deg,#2b313a,#0f1724)'
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
const brandTitle = {
|
|
587
|
+
margin: 0,
|
|
588
|
+
fontSize: 20,
|
|
589
|
+
fontWeight: 600,
|
|
590
|
+
color: '#fff'
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
const brandSub = {
|
|
594
|
+
display: 'flex',
|
|
595
|
+
flexDirection: 'column',
|
|
596
|
+
gap: 4
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
const brandLead = {
|
|
600
|
+
fontSize: 15,
|
|
601
|
+
color: 'rgba(255,255,255,0.95)'
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
const brandMuted = {
|
|
605
|
+
fontSize: 13,
|
|
606
|
+
color: 'rgba(255,255,255,0.65)',
|
|
607
|
+
marginTop: 2
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
const oauthRow = {
|
|
611
|
+
display: 'flex',
|
|
612
|
+
gap: 10,
|
|
613
|
+
marginTop: 8
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
const oauthButton = {
|
|
617
|
+
flex: 1,
|
|
618
|
+
display: 'inline-flex',
|
|
619
|
+
alignItems: 'center',
|
|
620
|
+
justifyContent: 'center',
|
|
621
|
+
padding: '10px 12px',
|
|
622
|
+
borderRadius: 10,
|
|
623
|
+
border: '1px solid rgba(148,163,184,0.08)',
|
|
624
|
+
fontSize: 14,
|
|
625
|
+
cursor: 'pointer',
|
|
626
|
+
userSelect: 'none',
|
|
627
|
+
gap: 8
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
const oauthGoogle = {
|
|
631
|
+
background: 'rgba(255,255,255,0.06)',
|
|
632
|
+
color: '#fff'
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
const oauthGithub = {
|
|
636
|
+
background: 'rgba(255,255,255,0.03)',
|
|
637
|
+
color: '#fff'
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
const dividerRow = {
|
|
641
|
+
display: 'flex',
|
|
642
|
+
alignItems: 'center',
|
|
643
|
+
gap: 12,
|
|
644
|
+
marginTop: 14
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
const line = {
|
|
648
|
+
flex: 1,
|
|
649
|
+
height: 1,
|
|
650
|
+
background: 'rgba(148,163,184,0.06)'
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
const orText = {
|
|
654
|
+
fontSize: 13,
|
|
655
|
+
color: 'rgba(255,255,255,0.6)',
|
|
656
|
+
padding: '0 8px'
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
const form = {
|
|
660
|
+
display: 'flex',
|
|
661
|
+
flexDirection: 'column',
|
|
662
|
+
gap: 12,
|
|
663
|
+
marginTop: 6
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
const label = {
|
|
667
|
+
display: 'flex',
|
|
668
|
+
flexDirection: 'column',
|
|
669
|
+
gap: 6
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
const labelText = {
|
|
673
|
+
fontSize: 13,
|
|
674
|
+
color: 'rgba(255,255,255,0.66)'
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
const input = {
|
|
678
|
+
width: '100%',
|
|
679
|
+
padding: '10px 12px',
|
|
680
|
+
borderRadius: 10,
|
|
681
|
+
background: 'rgba(11,18,32,0.5)',
|
|
682
|
+
color: '#f8e9d3',
|
|
683
|
+
border: '1px solid rgba(148,163,184,0.06)',
|
|
684
|
+
fontSize: 14,
|
|
685
|
+
outline: 'none',
|
|
686
|
+
boxSizing: 'border-box'
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
const submitButton = {
|
|
690
|
+
marginTop: 6,
|
|
691
|
+
width: '100%',
|
|
692
|
+
padding: '10px 12px',
|
|
693
|
+
borderRadius: 10,
|
|
694
|
+
background: 'linear-gradient(180deg,#2563eb,#60a5fa)',
|
|
695
|
+
border: 'none',
|
|
696
|
+
color: '#fff',
|
|
697
|
+
fontWeight: 600,
|
|
698
|
+
cursor: 'pointer',
|
|
699
|
+
fontSize: 15,
|
|
700
|
+
display: 'inline-flex',
|
|
701
|
+
alignItems: 'center',
|
|
702
|
+
justifyContent: 'center'
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
const below = {
|
|
706
|
+
padding: '12px 20px 20px 20px',
|
|
707
|
+
borderTop: '1px solid rgba(148,163,184,0.03)',
|
|
708
|
+
display: 'flex',
|
|
709
|
+
flexDirection: 'column',
|
|
710
|
+
gap: 10
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
const belowRow = {
|
|
714
|
+
textAlign: 'center',
|
|
715
|
+
display: 'flex',
|
|
716
|
+
justifyContent: 'center',
|
|
717
|
+
gap: 8,
|
|
718
|
+
alignItems: 'center'
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
const muted = {
|
|
722
|
+
color: 'rgba(255,255,255,0.65)',
|
|
723
|
+
fontSize: 13
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
const link = {
|
|
727
|
+
color: '#3b82f6',
|
|
728
|
+
textDecoration: 'none',
|
|
729
|
+
fontWeight: 600
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
const dividerThin = {
|
|
733
|
+
height: 1,
|
|
734
|
+
background: 'rgba(148,163,184,0.05)',
|
|
735
|
+
marginTop: 6,
|
|
736
|
+
marginBottom: 6
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
const secured = {
|
|
740
|
+
textAlign: 'center'
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
const securedText = {
|
|
744
|
+
color: 'rgba(255,255,255,0.9)',
|
|
745
|
+
fontSize: 13,
|
|
746
|
+
fontWeight: 600
|
|
747
|
+
}
|