flowlink-auth 2.7.6 → 2.7.8
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.js +3957 -527
- package/package.json +1 -1
- package/src/SignUp.jsx +205 -113
package/package.json
CHANGED
package/src/SignUp.jsx
CHANGED
|
@@ -261,55 +261,135 @@
|
|
|
261
261
|
// src/signup.jsx
|
|
262
262
|
'use client'
|
|
263
263
|
import React, { useState, useRef, useEffect } from 'react'
|
|
264
|
-
import { ToastContainer, toast } from 'react-toastify'
|
|
265
|
-
import 'react-toastify/dist/ReactToastify.css'
|
|
266
264
|
import Link from 'next/link'
|
|
265
|
+
import { useAuth } from './provider.js' // keep if your app provides it; safe to remove if not used
|
|
267
266
|
|
|
268
267
|
export default function SignUp({ agency = { name: 'MyApp', logo: '' } }) {
|
|
268
|
+
const {
|
|
269
|
+
publishableKey,
|
|
270
|
+
baseUrl,
|
|
271
|
+
redirect,
|
|
272
|
+
redirectTo,
|
|
273
|
+
user,
|
|
274
|
+
loadingUser,
|
|
275
|
+
fetchMe,
|
|
276
|
+
setUser
|
|
277
|
+
} = (typeof useAuth === 'function' ? useAuth() : {}) || {}
|
|
278
|
+
|
|
269
279
|
const [name, setName] = useState('')
|
|
270
280
|
const [email, setEmail] = useState('')
|
|
271
281
|
const [password, setPassword] = useState('')
|
|
272
282
|
const [loading, setLoading] = useState(false)
|
|
273
283
|
const [loadingOauth, setLoadingOauth] = useState({ google: false, github: false })
|
|
274
|
-
const [message, setMessage] = useState(null)
|
|
275
|
-
const [showKeysPanel, setShowKeysPanel] = useState(false)
|
|
276
|
-
const [newKeys, setNewKeys] = useState(null)
|
|
277
284
|
const redirectTimer = useRef(null)
|
|
285
|
+
const toastId = useRef(0)
|
|
286
|
+
const [toasts, setToasts] = useState([]) // local toast system to avoid external libs (black background)
|
|
278
287
|
|
|
279
288
|
useEffect(() => {
|
|
280
289
|
return () => {
|
|
281
290
|
if (redirectTimer.current) clearTimeout(redirectTimer.current)
|
|
291
|
+
// clear pending toast timers
|
|
292
|
+
toasts.forEach(t => clearTimeout(t._timer))
|
|
282
293
|
}
|
|
294
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
283
295
|
}, [])
|
|
284
296
|
|
|
285
|
-
//
|
|
297
|
+
// simple toast helper (auto-dismiss). type: 'success' | 'error' | 'info'
|
|
298
|
+
function showToast(type, message, ms = 5000) {
|
|
299
|
+
const id = ++toastId.current
|
|
300
|
+
const created = Date.now()
|
|
301
|
+
const t = { id, type, message, created, _timer: null }
|
|
302
|
+
// add toast
|
|
303
|
+
setToasts(prev => {
|
|
304
|
+
const next = [t, ...prev].slice(0, 6)
|
|
305
|
+
return next
|
|
306
|
+
})
|
|
307
|
+
// schedule removal
|
|
308
|
+
const timer = setTimeout(() => {
|
|
309
|
+
setToasts(prev => prev.filter(x => x.id !== id))
|
|
310
|
+
}, ms)
|
|
311
|
+
// store timer so cleanup can clear it
|
|
312
|
+
t._timer = timer
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// remove toast immediately
|
|
316
|
+
function removeToast(id) {
|
|
317
|
+
setToasts(prev => {
|
|
318
|
+
prev.forEach(t => {
|
|
319
|
+
if (t.id === id) clearTimeout(t._timer)
|
|
320
|
+
})
|
|
321
|
+
return prev.filter(x => x.id !== id)
|
|
322
|
+
})
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (loadingUser) return null
|
|
326
|
+
|
|
327
|
+
if (user && redirect) {
|
|
328
|
+
if (typeof redirectTo === 'function') redirectTo(redirect)
|
|
329
|
+
else if (typeof window !== 'undefined') window.location.assign(redirect)
|
|
330
|
+
return null
|
|
331
|
+
}
|
|
332
|
+
|
|
286
333
|
async function submit(e) {
|
|
287
334
|
e.preventDefault()
|
|
288
335
|
if (loading) return
|
|
289
|
-
setMessage(null)
|
|
290
336
|
setLoading(true)
|
|
291
337
|
|
|
338
|
+
// basic client validation
|
|
339
|
+
if (!name.trim()) {
|
|
340
|
+
showToast('error', 'Please enter your name.')
|
|
341
|
+
setLoading(false)
|
|
342
|
+
return
|
|
343
|
+
}
|
|
344
|
+
if (!email.includes('@')) {
|
|
345
|
+
showToast('error', 'Please enter a valid email address.')
|
|
346
|
+
setLoading(false)
|
|
347
|
+
return
|
|
348
|
+
}
|
|
349
|
+
if (password.length < 8) {
|
|
350
|
+
showToast('error', 'Password must be at least 8 characters.')
|
|
351
|
+
setLoading(false)
|
|
352
|
+
return
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const url = `${(baseUrl || '').replace(/\/+$/, '')}/api/sdk/signup`
|
|
356
|
+
|
|
292
357
|
try {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
358
|
+
const res = await fetch(url, {
|
|
359
|
+
method: 'POST',
|
|
360
|
+
credentials: 'include',
|
|
361
|
+
headers: {
|
|
362
|
+
'Content-Type': 'application/json',
|
|
363
|
+
'x-publishable-key': publishableKey || ''
|
|
364
|
+
},
|
|
365
|
+
body: JSON.stringify({ name: name.trim(), email: email.trim(), password })
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
const data = await res.json().catch(async () => {
|
|
369
|
+
const text = await res.text().catch(() => '')
|
|
370
|
+
return { _raw: text }
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
if (!res.ok) {
|
|
374
|
+
const errMsg = data?.error || data?._raw || `Signup failed (${res.status})`
|
|
375
|
+
throw new Error(errMsg)
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (data.user && typeof setUser === 'function') setUser(data.user)
|
|
379
|
+
if (typeof fetchMe === 'function') await fetchMe()
|
|
380
|
+
|
|
381
|
+
showToast('success', 'Account created. Redirecting…')
|
|
382
|
+
// redirect after tiny delay so user sees toast
|
|
383
|
+
if (redirect) {
|
|
384
|
+
redirectTimer.current = setTimeout(() => {
|
|
385
|
+
if (typeof redirectTo === 'function') redirectTo(redirect)
|
|
386
|
+
else if (typeof window !== 'undefined') window.location.assign(redirect)
|
|
387
|
+
}, 450)
|
|
388
|
+
}
|
|
309
389
|
} catch (err) {
|
|
390
|
+
// present server errors as toast (black background)
|
|
310
391
|
const text = err?.message ?? 'Network error'
|
|
311
|
-
|
|
312
|
-
toast.error(text)
|
|
392
|
+
showToast('error', text)
|
|
313
393
|
console.error('Signup error:', err)
|
|
314
394
|
} finally {
|
|
315
395
|
setLoading(false)
|
|
@@ -321,14 +401,32 @@ export default function SignUp({ agency = { name: 'MyApp', logo: '' } }) {
|
|
|
321
401
|
setLoadingOauth(prev => ({ ...prev, [provider]: true }))
|
|
322
402
|
|
|
323
403
|
try {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
404
|
+
const rid =
|
|
405
|
+
(typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function')
|
|
406
|
+
? crypto.randomUUID()
|
|
407
|
+
: `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`
|
|
408
|
+
|
|
409
|
+
const callbackUrl = encodeURIComponent(`${typeof window !== 'undefined' ? window.location.origin : ''}/signup`)
|
|
410
|
+
const sdkBase = baseUrl || (typeof window !== 'undefined' ? window.location.origin.replace(/\/+$/, '') : '')
|
|
411
|
+
const startUrl = `${sdkBase}/sdk/auth/start?rid=${rid}&source=${encodeURIComponent(provider)}&callbackUrl=${callbackUrl}`
|
|
412
|
+
|
|
413
|
+
if (!publishableKey) {
|
|
414
|
+
throw new Error('Missing publishable key (client side).')
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const res = await fetch(startUrl, {
|
|
418
|
+
method: 'GET',
|
|
419
|
+
headers: { 'x-publishable-key': publishableKey }
|
|
420
|
+
})
|
|
421
|
+
|
|
422
|
+
const data = await res.json().catch(() => null)
|
|
423
|
+
if (!res.ok) throw new Error(data?.error || `OAuth start failed (${res.status})`)
|
|
424
|
+
if (!data?.oauthUrl) throw new Error('SDK start did not return oauthUrl')
|
|
425
|
+
|
|
426
|
+
// navigate away to provider
|
|
427
|
+
if (typeof window !== 'undefined') window.location.href = data.oauthUrl
|
|
330
428
|
} catch (err) {
|
|
331
|
-
|
|
429
|
+
showToast('error', err?.message || 'OAuth start failed')
|
|
332
430
|
console.error('OAuth start error:', err)
|
|
333
431
|
setLoadingOauth(prev => ({ ...prev, [provider]: false }))
|
|
334
432
|
}
|
|
@@ -346,33 +444,43 @@ export default function SignUp({ agency = { name: 'MyApp', logo: '' } }) {
|
|
|
346
444
|
|
|
347
445
|
return (
|
|
348
446
|
<div style={overlay}>
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
447
|
+
{/* toast container (custom) */}
|
|
448
|
+
<div style={toastContainer}>
|
|
449
|
+
{toasts.map(t => (
|
|
450
|
+
<div
|
|
451
|
+
key={t.id}
|
|
452
|
+
role="status"
|
|
453
|
+
aria-live="polite"
|
|
454
|
+
style={{
|
|
455
|
+
...toastBase,
|
|
456
|
+
...(t.type === 'error' ? toastError : t.type === 'success' ? toastSuccess : toastInfo)
|
|
457
|
+
}}
|
|
458
|
+
onMouseEnter={() => {
|
|
459
|
+
// pause dismiss
|
|
460
|
+
clearTimeout(t._timer)
|
|
461
|
+
}}
|
|
462
|
+
onMouseLeave={() => {
|
|
463
|
+
// restart dismiss for this toast
|
|
464
|
+
const timer = setTimeout(() => removeToast(t.id), 3000)
|
|
465
|
+
// patch timer into state (imperative but fine for local toast)
|
|
466
|
+
setToasts(prev => prev.map(x => x.id === t.id ? { ...x, _timer: timer } : x))
|
|
467
|
+
}}
|
|
468
|
+
>
|
|
469
|
+
<div style={{ flex: 1 }}>{t.message}</div>
|
|
470
|
+
<button aria-label="Dismiss" onClick={() => removeToast(t.id)} style={toastCloseBtn}>✕</button>
|
|
471
|
+
</div>
|
|
472
|
+
))}
|
|
473
|
+
</div>
|
|
365
474
|
|
|
366
475
|
<div style={modal}>
|
|
367
476
|
<div style={modalInner}>
|
|
368
477
|
<header style={header}>
|
|
369
478
|
<div style={brandRow}>
|
|
370
479
|
<div style={logo}>
|
|
371
|
-
{/* if you have a real logo, replace with <Image src={agency.logo} ... /> */}
|
|
372
480
|
<div style={logoCircle} aria-hidden />
|
|
373
481
|
</div>
|
|
374
482
|
<div>
|
|
375
|
-
<h1 style={title}>Sign up to {agency
|
|
483
|
+
<h1 style={title}>Sign up to {agency?.name || 'App'}</h1>
|
|
376
484
|
<div style={subtitle}>Welcome! Create your account.</div>
|
|
377
485
|
</div>
|
|
378
486
|
</div>
|
|
@@ -479,34 +587,11 @@ export default function SignUp({ agency = { name: 'MyApp', logo: '' } }) {
|
|
|
479
587
|
</div>
|
|
480
588
|
</div>
|
|
481
589
|
</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
|
-
)}
|
|
503
590
|
</div>
|
|
504
591
|
)
|
|
505
592
|
}
|
|
506
593
|
|
|
507
|
-
/*
|
|
508
|
-
|
|
509
|
-
/* overlay: translucent so underlying content is visible; backdrop blur and subtle vignette */
|
|
594
|
+
/* styles (JS objects) - neutral palette, translucent overlay so underlying content shows */
|
|
510
595
|
const overlay = {
|
|
511
596
|
position: 'fixed',
|
|
512
597
|
inset: 0,
|
|
@@ -514,20 +599,18 @@ const overlay = {
|
|
|
514
599
|
alignItems: 'center',
|
|
515
600
|
justifyContent: 'center',
|
|
516
601
|
padding: 20,
|
|
517
|
-
background: 'linear-gradient(180deg, rgba(2,6,23,0.
|
|
602
|
+
background: 'linear-gradient(180deg, rgba(2,6,23,0.22), rgba(2,6,23,0.32))',
|
|
518
603
|
backdropFilter: 'blur(6px)',
|
|
519
604
|
minHeight: '100vh',
|
|
520
605
|
zIndex: 9999
|
|
521
606
|
}
|
|
522
607
|
|
|
523
|
-
/* modal itself uses semi-transparent background + strong soft shadow,
|
|
524
|
-
so things beneath are slightly visible but content still readable */
|
|
525
608
|
const modal = {
|
|
526
609
|
width: '100%',
|
|
527
610
|
maxWidth: 560,
|
|
528
611
|
borderRadius: 14,
|
|
529
|
-
background: 'rgba(10,
|
|
530
|
-
border: '1px solid rgba(
|
|
612
|
+
background: 'linear-gradient(180deg, rgba(15,19,24,0.6), rgba(10,12,16,0.6))', // translucent neutral so background peeks through
|
|
613
|
+
border: '1px solid rgba(99,102,106,0.12)', // slightly stronger neutral border
|
|
531
614
|
boxShadow: '0 20px 50px rgba(2,6,23,0.55), inset 0 1px 0 rgba(255,255,255,0.02)',
|
|
532
615
|
overflow: 'hidden',
|
|
533
616
|
display: 'flex',
|
|
@@ -542,7 +625,7 @@ const modalInner = {
|
|
|
542
625
|
gap: 12
|
|
543
626
|
}
|
|
544
627
|
|
|
545
|
-
/*
|
|
628
|
+
/* header / brand */
|
|
546
629
|
const header = {
|
|
547
630
|
paddingBottom: 6,
|
|
548
631
|
borderBottom: '1px solid rgba(148,163,184,0.04)'
|
|
@@ -583,7 +666,7 @@ const subtitle = {
|
|
|
583
666
|
marginTop: 4
|
|
584
667
|
}
|
|
585
668
|
|
|
586
|
-
/*
|
|
669
|
+
/* oauth row */
|
|
587
670
|
const oauthSection = {
|
|
588
671
|
marginTop: 8,
|
|
589
672
|
display: 'flex',
|
|
@@ -615,7 +698,7 @@ const oauthGithub = {
|
|
|
615
698
|
color: '#fff'
|
|
616
699
|
}
|
|
617
700
|
|
|
618
|
-
/*
|
|
701
|
+
/* divider row */
|
|
619
702
|
const dividerRow = {
|
|
620
703
|
display: 'flex',
|
|
621
704
|
alignItems: 'center',
|
|
@@ -635,7 +718,7 @@ const orText = {
|
|
|
635
718
|
padding: '0 8px'
|
|
636
719
|
}
|
|
637
720
|
|
|
638
|
-
/*
|
|
721
|
+
/* form */
|
|
639
722
|
const form = {
|
|
640
723
|
display: 'flex',
|
|
641
724
|
flexDirection: 'column',
|
|
@@ -660,7 +743,7 @@ const input = {
|
|
|
660
743
|
borderRadius: 10,
|
|
661
744
|
background: 'rgba(255,255,255,0.02)',
|
|
662
745
|
color: '#e6e6e6',
|
|
663
|
-
border: '1px solid rgba(148,163,184,0.10)',
|
|
746
|
+
border: '1px solid rgba(148,163,184,0.10)',
|
|
664
747
|
fontSize: 14,
|
|
665
748
|
outline: 'none',
|
|
666
749
|
boxSizing: 'border-box',
|
|
@@ -672,7 +755,7 @@ const submitButton = {
|
|
|
672
755
|
width: '100%',
|
|
673
756
|
padding: '10px 12px',
|
|
674
757
|
borderRadius: 10,
|
|
675
|
-
background: 'linear-gradient(180deg,#475569,#94a3b8)',
|
|
758
|
+
background: 'linear-gradient(180deg,#475569,#94a3b8)',
|
|
676
759
|
border: 'none',
|
|
677
760
|
color: '#fff',
|
|
678
761
|
fontWeight: 600,
|
|
@@ -684,7 +767,7 @@ const submitButton = {
|
|
|
684
767
|
minHeight: 44
|
|
685
768
|
}
|
|
686
769
|
|
|
687
|
-
/*
|
|
770
|
+
/* modal footer */
|
|
688
771
|
const modalFooter = {
|
|
689
772
|
padding: '12px 18px 18px 18px',
|
|
690
773
|
borderTop: '1px solid rgba(148,163,184,0.03)',
|
|
@@ -729,41 +812,50 @@ const securedText = {
|
|
|
729
812
|
fontWeight: 600
|
|
730
813
|
}
|
|
731
814
|
|
|
732
|
-
/*
|
|
733
|
-
const
|
|
815
|
+
/* toasts - black background as requested */
|
|
816
|
+
const toastContainer = {
|
|
734
817
|
position: 'fixed',
|
|
735
|
-
|
|
736
|
-
|
|
818
|
+
top: 18,
|
|
819
|
+
right: 18,
|
|
820
|
+
width: 360,
|
|
821
|
+
maxWidth: 'calc(100% - 36px)',
|
|
737
822
|
display: 'flex',
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
823
|
+
flexDirection: 'column',
|
|
824
|
+
gap: 10,
|
|
825
|
+
zIndex: 60000
|
|
741
826
|
}
|
|
742
827
|
|
|
743
|
-
const
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
828
|
+
const toastBase = {
|
|
829
|
+
display: 'flex',
|
|
830
|
+
gap: 10,
|
|
831
|
+
alignItems: 'center',
|
|
832
|
+
padding: '10px 12px',
|
|
747
833
|
borderRadius: 10,
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
834
|
+
boxShadow: '0 8px 20px rgba(2,6,23,0.6)',
|
|
835
|
+
color: '#fff',
|
|
836
|
+
fontSize: 13,
|
|
837
|
+
minWidth: 120
|
|
751
838
|
}
|
|
752
839
|
|
|
753
|
-
const
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
840
|
+
const toastError = {
|
|
841
|
+
background: '#000000',
|
|
842
|
+
border: '1px solid rgba(255,255,255,0.06)'
|
|
843
|
+
}
|
|
844
|
+
const toastSuccess = {
|
|
845
|
+
background: '#000000',
|
|
846
|
+
border: '1px solid rgba(255,255,255,0.06)'
|
|
847
|
+
}
|
|
848
|
+
const toastInfo = {
|
|
849
|
+
background: '#000000',
|
|
850
|
+
border: '1px solid rgba(255,255,255,0.06)'
|
|
759
851
|
}
|
|
760
852
|
|
|
761
|
-
const
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
borderRadius: 8,
|
|
765
|
-
background: '#111827',
|
|
766
|
-
color: '#fff',
|
|
853
|
+
const toastCloseBtn = {
|
|
854
|
+
marginLeft: 8,
|
|
855
|
+
background: 'transparent',
|
|
767
856
|
border: 'none',
|
|
768
|
-
|
|
857
|
+
color: 'rgba(255,255,255,0.7)',
|
|
858
|
+
cursor: 'pointer',
|
|
859
|
+
fontSize: 14,
|
|
860
|
+
lineHeight: 1
|
|
769
861
|
}
|