flowlink-auth 2.7.4 → 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 +664 -74
- package/package.json +1 -1
- package/src/SignUp.jsx +696 -209
package/src/SignUp.jsx
CHANGED
|
@@ -1,260 +1,747 @@
|
|
|
1
|
-
// src/signup.jsx
|
|
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
|
+
|
|
2
261
|
'use client'
|
|
3
|
-
import React, { useState } from 'react'
|
|
262
|
+
import React, { useState, useRef, useEffect } from 'react'
|
|
263
|
+
import { ToastContainer, toast } from 'react-toastify'
|
|
264
|
+
import 'react-toastify/dist/ReactToastify.css'
|
|
4
265
|
import { useAuth } from './provider.js'
|
|
5
266
|
|
|
6
267
|
export default function SignUp() {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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)
|
|
31
326
|
}
|
|
32
327
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
}
|
|
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)
|
|
72
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
|
+
}
|
|
73
347
|
|
|
74
|
-
async function startOAuthFlow(provider) {
|
|
75
|
-
// prevent double clicks
|
|
76
|
-
setError(null)
|
|
77
|
-
setLoading(true)
|
|
78
348
|
|
|
79
|
-
|
|
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)}`
|
|
349
|
+
}
|
|
84
350
|
|
|
85
|
-
|
|
86
|
-
|
|
351
|
+
async function startOAuthFlow(provider) {
|
|
352
|
+
if (loading) return
|
|
353
|
+
// prevent double clicks
|
|
354
|
+
setLoading(true)
|
|
87
355
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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)}`
|
|
92
361
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
throw new Error('Missing publishable key (client side). Set NEXT_PUBLIC_FLOWLINK_PUBLISHABLE_KEY or provide publishableKey in provider.')
|
|
96
|
-
}
|
|
362
|
+
// callback must match what your SDK start expects and what Google console allows
|
|
363
|
+
const callbackUrl = encodeURIComponent(`${window.location.origin}/signup`)
|
|
97
364
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
'x-publishable-key': publishableKey
|
|
102
|
-
}
|
|
103
|
-
})
|
|
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}`
|
|
104
368
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
|
111
378
|
}
|
|
379
|
+
})
|
|
112
380
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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')
|
|
119
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
|
+
|
|
120
398
|
}
|
|
121
399
|
|
|
122
400
|
const handleGoogle = (e) => {
|
|
123
|
-
|
|
124
|
-
|
|
401
|
+
if (e && typeof e.preventDefault === 'function') e.preventDefault()
|
|
402
|
+
startOAuthFlow('google')
|
|
125
403
|
}
|
|
126
404
|
|
|
127
405
|
const handleGithub = (e) => {
|
|
128
|
-
|
|
129
|
-
|
|
406
|
+
if (e && typeof e.preventDefault === 'function') e.preventDefault()
|
|
407
|
+
startOAuthFlow('github')
|
|
130
408
|
}
|
|
131
|
-
return (
|
|
132
|
-
<div style={overlay}>
|
|
133
|
-
<div style={modal}>
|
|
134
|
-
<h2 style={title}>Create account</h2>
|
|
135
409
|
|
|
136
|
-
|
|
137
|
-
|
|
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>
|
|
138
466
|
<input
|
|
139
|
-
|
|
140
|
-
|
|
467
|
+
id="name"
|
|
468
|
+
type="text"
|
|
141
469
|
value={name}
|
|
142
470
|
onChange={e => setName(e.target.value)}
|
|
471
|
+
placeholder="Your name"
|
|
472
|
+
style={input}
|
|
143
473
|
autoComplete="name"
|
|
144
474
|
/>
|
|
475
|
+
</label>
|
|
145
476
|
|
|
146
|
-
|
|
477
|
+
<label style={label} htmlFor="email">
|
|
478
|
+
<span style={labelText}>Email address</span>
|
|
147
479
|
<input
|
|
148
|
-
|
|
480
|
+
id="email"
|
|
149
481
|
type="email"
|
|
150
|
-
style={input}
|
|
151
482
|
value={email}
|
|
152
483
|
onChange={e => setEmail(e.target.value)}
|
|
153
484
|
required
|
|
485
|
+
placeholder="you@example.com"
|
|
486
|
+
style={input}
|
|
154
487
|
autoComplete="email"
|
|
155
488
|
/>
|
|
489
|
+
</label>
|
|
156
490
|
|
|
157
|
-
|
|
491
|
+
<label style={label} htmlFor="password">
|
|
492
|
+
<span style={labelText}>Password</span>
|
|
158
493
|
<input
|
|
159
|
-
|
|
494
|
+
id="password"
|
|
160
495
|
type="password"
|
|
161
|
-
style={input}
|
|
162
496
|
value={password}
|
|
163
497
|
onChange={e => setPassword(e.target.value)}
|
|
164
498
|
required
|
|
499
|
+
placeholder="••••••••"
|
|
500
|
+
style={input}
|
|
165
501
|
autoComplete="new-password"
|
|
166
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>
|
|
167
515
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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>
|
|
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>
|
|
195
524
|
</div>
|
|
196
525
|
</div>
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const
|
|
219
|
-
|
|
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
|
+
|
|
220
677
|
const input = {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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
|
+
}
|
|
260
742
|
|
|
743
|
+
const securedText = {
|
|
744
|
+
color: 'rgba(255,255,255,0.9)',
|
|
745
|
+
fontSize: 13,
|
|
746
|
+
fontWeight: 600
|
|
747
|
+
}
|