flowlink-auth 2.7.7 → 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.
Files changed (3) hide show
  1. package/dist/index.js +242 -582
  2. package/package.json +1 -1
  3. package/src/SignUp.jsx +205 -113
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flowlink-auth",
3
- "version": "2.7.7",
3
+ "version": "2.7.8",
4
4
  "description": "Custom auth library",
5
5
  "main": "dist/index.js",
6
6
  "types": "src/index.d.ts",
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
- // Example submit replace with real auth call
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
- // 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')
300
-
301
- // success
302
- setMessage('Account created. Redirecting…')
303
- toast.success('Account created. Redirecting…')
304
-
305
- // simulate redirect timer
306
- redirectTimer.current = setTimeout(() => {
307
- // real redirect logic here
308
- }, 300)
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
- // show toast with black background (ToastContainer configured below)
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
- // simulate network latency
325
- await new Promise((r) => setTimeout(r, 600))
326
-
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
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
- toast.error(err?.message || 'OAuth start failed')
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
- <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
- />
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.name}</h1>
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
- /* Styles (JS objects) */
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.25), rgba(2,6,23,0.35))',
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,14,20,0.65)', // translucent neutral
530
- border: '1px solid rgba(148,163,184,0.12)', // more visible neutral border
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
- /* HEADER / BRAND */
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
- /* OAUTH ROW */
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
- /* DIVIDER */
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
- /* FORM */
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)', // slightly stronger border for visibility
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)', // neutral blue-gray gradient
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
- /* FOOTER / BELOW */
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
- /* KEYS PANEL */
733
- const keysOverlay = {
815
+ /* toasts - black background as requested */
816
+ const toastContainer = {
734
817
  position: 'fixed',
735
- inset: 0,
736
- zIndex: 20000,
818
+ top: 18,
819
+ right: 18,
820
+ width: 360,
821
+ maxWidth: 'calc(100% - 36px)',
737
822
  display: 'flex',
738
- alignItems: 'center',
739
- justifyContent: 'center',
740
- background: 'rgba(2,6,23,0.45)'
823
+ flexDirection: 'column',
824
+ gap: 10,
825
+ zIndex: 60000
741
826
  }
742
827
 
743
- const keysPanel = {
744
- background: '#fff',
745
- color: '#111827',
746
- padding: 18,
828
+ const toastBase = {
829
+ display: 'flex',
830
+ gap: 10,
831
+ alignItems: 'center',
832
+ padding: '10px 12px',
747
833
  borderRadius: 10,
748
- maxWidth: 720,
749
- width: '95%',
750
- boxShadow: '0 12px 40px rgba(2,6,23,0.5)'
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 keyBox = {
754
- padding: 10,
755
- background: '#f3f4f6',
756
- borderRadius: 8,
757
- overflowX: 'auto',
758
- fontSize: 13
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 copyBtn = {
762
- marginTop: 8,
763
- padding: '8px 10px',
764
- borderRadius: 8,
765
- background: '#111827',
766
- color: '#fff',
853
+ const toastCloseBtn = {
854
+ marginLeft: 8,
855
+ background: 'transparent',
767
856
  border: 'none',
768
- cursor: 'pointer'
857
+ color: 'rgba(255,255,255,0.7)',
858
+ cursor: 'pointer',
859
+ fontSize: 14,
860
+ lineHeight: 1
769
861
  }