flowlink-auth 2.8.0 → 2.8.2

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/src/SignUp.jsx CHANGED
@@ -258,12 +258,11 @@
258
258
  // const errorBox = { marginTop: 10, color: '#ffb4b4', fontSize: 13 }
259
259
  // const successBox = { marginTop: 10, color: '#bef264', fontSize: 13 }
260
260
 
261
- // src/signup.jsx
262
261
  // src/signup.jsx
263
262
  'use client'
264
263
  import React, { useState, useRef, useEffect } from 'react'
265
264
  import Link from 'next/link'
266
- import { useAuth } from './provider.js' // keep if your SDK provides this; safe to remove if not used
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: '' } }) {
269
268
  const {
@@ -283,17 +282,14 @@ export default function SignUp({ agency = { name: 'MyApp', logo: '' } }) {
283
282
  const [loading, setLoading] = useState(false)
284
283
  const [loadingOauth, setLoadingOauth] = useState({ google: false, github: false })
285
284
  const redirectTimer = useRef(null)
286
-
287
- // Local toast system (black background)
288
285
  const toastId = useRef(0)
289
- const [toasts, setToasts] = useState([])
286
+ const [toasts, setToasts] = useState([]) // local toast system (black background)
290
287
 
288
+ // Insert a viewport meta tag while this component is mounted to avoid pinch-zoom on mobile
291
289
  useEffect(() => {
292
- // Soft-disable pinch-zoom on mobile while this component is mounted.
293
- // This inserts a viewport meta tag and removes it on unmount.
294
290
  const meta = document.createElement('meta')
295
291
  meta.name = 'viewport'
296
- meta.content = 'width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no'
292
+ meta.content = 'width=device-width, initial-scale=1, maximum-scale=1'
297
293
  document.head.appendChild(meta)
298
294
 
299
295
  return () => {
@@ -303,7 +299,7 @@ export default function SignUp({ agency = { name: 'MyApp', logo: '' } }) {
303
299
  if (existing && existing.content === meta.content) {
304
300
  document.head.removeChild(existing)
305
301
  }
306
- // clear any toast timers if present (we store timers on the toast objects)
302
+ // clear any toast timers stored on the toasts (best-effort)
307
303
  toasts.forEach(t => {
308
304
  if (t._timer) clearTimeout(t._timer)
309
305
  })
@@ -311,17 +307,21 @@ export default function SignUp({ agency = { name: 'MyApp', logo: '' } }) {
311
307
  // eslint-disable-next-line react-hooks/exhaustive-deps
312
308
  }, [])
313
309
 
314
- // Toast helpers
310
+ // simple toast helper (auto-dismiss). type: 'success' | 'error' | 'info'
315
311
  function showToast(type, message, ms = 5000) {
316
312
  const id = ++toastId.current
317
313
  const t = { id, type, message, _timer: null }
318
- setToasts(prev => [t, ...prev].slice(0, 6))
314
+ setToasts(prev => {
315
+ const next = [t, ...prev].slice(0, 6)
316
+ return next
317
+ })
319
318
  const timer = setTimeout(() => {
320
319
  setToasts(prev => prev.filter(x => x.id !== id))
321
320
  }, ms)
322
321
  t._timer = timer
323
322
  }
324
323
 
324
+ // remove toast immediately
325
325
  function removeToast(id) {
326
326
  setToasts(prev => {
327
327
  prev.forEach(t => {
@@ -339,7 +339,6 @@ export default function SignUp({ agency = { name: 'MyApp', logo: '' } }) {
339
339
  return null
340
340
  }
341
341
 
342
- // Prevent double submit
343
342
  async function submit(e) {
344
343
  e.preventDefault()
345
344
  if (loading) return
@@ -376,8 +375,8 @@ export default function SignUp({ agency = { name: 'MyApp', logo: '' } }) {
376
375
  })
377
376
 
378
377
  const data = await res.json().catch(async () => {
379
- const txt = await res.text().catch(() => '')
380
- return { _raw: txt }
378
+ const text = await res.text().catch(() => '')
379
+ return { _raw: text }
381
380
  })
382
381
 
383
382
  if (!res.ok) {
@@ -399,7 +398,6 @@ export default function SignUp({ agency = { name: 'MyApp', logo: '' } }) {
399
398
  } catch (err) {
400
399
  const text = err?.message ?? 'Network error'
401
400
  showToast('error', text)
402
- // keep console error for integrators
403
401
  console.error('Signup error:', err)
404
402
  } finally {
405
403
  setLoading(false)
@@ -420,7 +418,9 @@ export default function SignUp({ agency = { name: 'MyApp', logo: '' } }) {
420
418
  const sdkBase = baseUrl || (typeof window !== 'undefined' ? window.location.origin.replace(/\/+$/, '') : '')
421
419
  const startUrl = `${sdkBase}/sdk/auth/start?rid=${rid}&source=${encodeURIComponent(provider)}&callbackUrl=${callbackUrl}`
422
420
 
423
- if (!publishableKey) throw new Error('Missing publishable key (client side).')
421
+ if (!publishableKey) {
422
+ throw new Error('Missing publishable key (client side).')
423
+ }
424
424
 
425
425
  const res = await fetch(startUrl, {
426
426
  method: 'GET',
@@ -431,7 +431,7 @@ export default function SignUp({ agency = { name: 'MyApp', logo: '' } }) {
431
431
  if (!res.ok) throw new Error(data?.error || `OAuth start failed (${res.status})`)
432
432
  if (!data?.oauthUrl) throw new Error('SDK start did not return oauthUrl')
433
433
 
434
- // Navigate to provider (this will unload the page)
434
+ // navigate away to provider
435
435
  if (typeof window !== 'undefined') window.location.href = data.oauthUrl
436
436
  } catch (err) {
437
437
  showToast('error', err?.message || 'OAuth start failed')
@@ -451,51 +451,51 @@ export default function SignUp({ agency = { name: 'MyApp', logo: '' } }) {
451
451
  }
452
452
 
453
453
  return (
454
- <div style={styles.overlay}>
455
- {/* custom toast container (black background) */}
456
- <div style={styles.toastContainer} aria-live="polite" aria-atomic="true">
454
+ <div style={overlay}>
455
+ {/* toast container (custom black toasts) */}
456
+ <div style={toastContainer} aria-live="polite" aria-atomic="true">
457
457
  {toasts.map(t => (
458
458
  <div
459
459
  key={t.id}
460
460
  role="status"
461
+ aria-live="polite"
461
462
  style={{
462
- ...styles.toastBase,
463
- ...(t.type === 'error' ? styles.toastError : t.type === 'success' ? styles.toastSuccess : styles.toastInfo)
463
+ ...toastBase,
464
+ ...(t.type === 'error' ? toastError : t.type === 'success' ? toastSuccess : toastInfo)
464
465
  }}
465
466
  onMouseEnter={() => {
466
467
  if (t._timer) clearTimeout(t._timer)
467
468
  }}
468
469
  onMouseLeave={() => {
469
- // restart auto-dismiss shorter when mouse leaves
470
470
  const timer = setTimeout(() => removeToast(t.id), 3000)
471
471
  setToasts(prev => prev.map(x => x.id === t.id ? { ...x, _timer: timer } : x))
472
472
  }}
473
473
  >
474
474
  <div style={{ flex: 1 }}>{t.message}</div>
475
- <button aria-label="Dismiss" onClick={() => removeToast(t.id)} style={styles.toastCloseBtn}>✕</button>
475
+ <button aria-label="Dismiss" onClick={() => removeToast(t.id)} style={toastCloseBtn}>✕</button>
476
476
  </div>
477
477
  ))}
478
478
  </div>
479
479
 
480
- <div style={styles.modal} role="dialog" aria-modal="true" aria-labelledby="signup-title">
481
- <div style={styles.modalInner}>
482
- <header style={styles.header}>
483
- <div style={styles.brandRow}>
484
- <div style={styles.logo} aria-hidden>
485
- <div style={styles.logoCircle} />
480
+ <div style={modal}>
481
+ <div style={modalInner}>
482
+ <header style={header}>
483
+ <div style={brandRow}>
484
+ <div style={logo}>
485
+ <div style={logoCircle} aria-hidden />
486
486
  </div>
487
487
  <div>
488
- <h1 id="signup-title" style={styles.title}>Sign up to {agency?.name || 'App'}</h1>
489
- <div style={styles.subtitle}>Welcome create your account.</div>
488
+ <h1 style={title}>Sign up to {agency?.name || 'App'}</h1>
489
+ <div style={subtitle}>Welcome! Create your account.</div>
490
490
  </div>
491
491
  </div>
492
492
  </header>
493
493
 
494
- <section style={styles.oauthSection}>
494
+ <section style={oauthSection}>
495
495
  <button
496
496
  onClick={handleGoogle}
497
497
  type="button"
498
- style={{ ...styles.oauthButton, ...styles.oauthGoogle }}
498
+ style={{ ...oauthButton, ...oauthGoogle }}
499
499
  disabled={loading || loadingOauth.google}
500
500
  aria-disabled={loading || loadingOauth.google}
501
501
  >
@@ -511,7 +511,7 @@ export default function SignUp({ agency = { name: 'MyApp', logo: '' } }) {
511
511
  <button
512
512
  onClick={handleGithub}
513
513
  type="button"
514
- style={{ ...styles.oauthButton, ...styles.oauthGithub }}
514
+ style={{ ...oauthButton, ...oauthGithub }}
515
515
  disabled={loading || loadingOauth.github}
516
516
  aria-disabled={loading || loadingOauth.github}
517
517
  >
@@ -522,28 +522,28 @@ export default function SignUp({ agency = { name: 'MyApp', logo: '' } }) {
522
522
  </button>
523
523
  </section>
524
524
 
525
- <div style={styles.dividerRow}>
526
- <div style={styles.line} />
527
- <div style={styles.orText}>or</div>
528
- <div style={styles.line} />
525
+ <div style={dividerRow}>
526
+ <div style={line} />
527
+ <div style={orText}>or</div>
528
+ <div style={line} />
529
529
  </div>
530
530
 
531
- <form onSubmit={submit} style={styles.form}>
532
- <label style={styles.label} htmlFor="name">
533
- <span style={styles.labelText}>Name</span>
531
+ <form onSubmit={submit} style={form}>
532
+ <label style={label} htmlFor="name">
533
+ <span style={labelText}>Name</span>
534
534
  <input
535
535
  id="name"
536
536
  type="text"
537
537
  value={name}
538
538
  onChange={e => setName(e.target.value)}
539
539
  placeholder="Your name"
540
- style={styles.input}
540
+ style={input}
541
541
  autoComplete="name"
542
542
  />
543
543
  </label>
544
544
 
545
- <label style={styles.label} htmlFor="email">
546
- <span style={styles.labelText}>Email address</span>
545
+ <label style={label} htmlFor="email">
546
+ <span style={labelText}>Email address</span>
547
547
  <input
548
548
  id="email"
549
549
  type="email"
@@ -551,13 +551,13 @@ export default function SignUp({ agency = { name: 'MyApp', logo: '' } }) {
551
551
  onChange={e => setEmail(e.target.value)}
552
552
  required
553
553
  placeholder="you@example.com"
554
- style={styles.input}
554
+ style={input}
555
555
  autoComplete="email"
556
556
  />
557
557
  </label>
558
558
 
559
- <label style={styles.label} htmlFor="password">
560
- <span style={styles.labelText}>Password</span>
559
+ <label style={label} htmlFor="password">
560
+ <span style={labelText}>Password</span>
561
561
  <input
562
562
  id="password"
563
563
  type="password"
@@ -565,14 +565,14 @@ export default function SignUp({ agency = { name: 'MyApp', logo: '' } }) {
565
565
  onChange={e => setPassword(e.target.value)}
566
566
  required
567
567
  placeholder="••••••••"
568
- style={styles.input}
568
+ style={input}
569
569
  autoComplete="new-password"
570
570
  />
571
571
  </label>
572
572
 
573
573
  <button
574
574
  type="submit"
575
- style={styles.submitButton}
575
+ style={submitButton}
576
576
  disabled={loading}
577
577
  aria-disabled={loading}
578
578
  >
@@ -581,14 +581,14 @@ export default function SignUp({ agency = { name: 'MyApp', logo: '' } }) {
581
581
  </form>
582
582
  </div>
583
583
 
584
- <div style={styles.modalFooter}>
585
- <div style={styles.belowRow}>
586
- <span style={styles.muted}>Already have an account? </span>
587
- <Link href="/sign-in" style={styles.link}>Sign in</Link>
584
+ <div style={modalFooter}>
585
+ <div style={belowRow}>
586
+ <span style={muted}>Already have an account? </span>
587
+ <Link href="/sign-in" style={link}>Sign in</Link>
588
588
  </div>
589
- <div style={styles.dividerThin} />
590
- <div style={styles.secured}>
591
- <div style={styles.securedText}>Secured by auth</div>
589
+ <div style={dividerThin} />
590
+ <div style={secured}>
591
+ <div style={securedText}>Secured by auth</div>
592
592
  </div>
593
593
  </div>
594
594
  </div>
@@ -596,273 +596,273 @@ export default function SignUp({ agency = { name: 'MyApp', logo: '' } }) {
596
596
  )
597
597
  }
598
598
 
599
- /* Styles (JS objects) - reduced width and zoom-resilient layout; neutral palette */
600
- const styles = {
601
- overlay: {
602
- position: 'fixed',
603
- inset: 0,
604
- display: 'block', // use block + scrolling container for zoom resilience
605
- padding: 20,
606
- background: 'linear-gradient(180deg, rgba(2,6,23,0.22), rgba(2,6,23,0.32))',
607
- backdropFilter: 'blur(6px)',
608
- overflowY: 'auto', // allow page scroll when zoomed
609
- WebkitOverflowScrolling: 'touch',
610
- minHeight: '100vh',
611
- zIndex: 9999
612
- },
613
-
614
- // modal: reduced width (not too wide), centered with margin auto, allows internal scroll
615
- modal: {
616
- width: '100%',
617
- maxWidth: 460, // reduced width per request
618
- margin: '40px auto', // vertical margin so modal isn't glued to top when zoomed
619
- borderRadius: 14,
620
- background: 'linear-gradient(180deg, rgba(15,19,24,0.85), rgba(10,12,16,0.85))', // translucent neutral
621
- border: '1px solid rgba(99,102,106,0.12)', // visible neutral border
622
- boxShadow: '0 18px 48px rgba(2,6,23,0.55), inset 0 1px 0 rgba(255,255,255,0.02)',
623
- overflow: 'hidden',
624
- display: 'flex',
625
- flexDirection: 'column',
626
- maxHeight: 'calc(100vh - 80px)', // keep inside viewport
627
- },
628
-
629
- modalInner: {
630
- padding: 18,
631
- display: 'flex',
632
- flexDirection: 'column',
633
- gap: 12,
634
- overflowY: 'auto'
635
- },
636
-
637
- header: {
638
- paddingBottom: 6,
639
- borderBottom: '1px solid rgba(148,163,184,0.04)'
640
- },
641
-
642
- brandRow: {
643
- display: 'flex',
644
- alignItems: 'center',
645
- gap: 12
646
- },
647
-
648
- logo: {
649
- width: 44,
650
- height: 44,
651
- display: 'flex',
652
- alignItems: 'center',
653
- justifyContent: 'center'
654
- },
655
-
656
- logoCircle: {
657
- width: 36,
658
- height: 36,
659
- borderRadius: 999,
660
- background: 'linear-gradient(135deg,#2f3438,#11151a)',
661
- boxShadow: '0 4px 12px rgba(2,6,23,0.6)'
662
- },
663
-
664
- title: {
665
- margin: 0,
666
- fontSize: 18,
667
- fontWeight: 600,
668
- color: '#e6e6e6'
669
- },
670
-
671
- subtitle: {
672
- fontSize: 13,
673
- color: 'rgba(230,230,230,0.72)',
674
- marginTop: 4
675
- },
676
-
677
- oauthSection: {
678
- marginTop: 8,
679
- display: 'flex',
680
- gap: 8
681
- },
682
-
683
- oauthButton: {
684
- flex: 1,
685
- display: 'inline-flex',
686
- alignItems: 'center',
687
- justifyContent: 'center',
688
- padding: '10px 12px',
689
- borderRadius: 10,
690
- border: '1px solid rgba(148,163,184,0.08)',
691
- fontSize: 14,
692
- cursor: 'pointer',
693
- userSelect: 'none',
694
- gap: 8,
695
- minHeight: 40,
696
- background: 'transparent',
697
- color: '#fff'
698
- },
699
-
700
- oauthGoogle: {
701
- background: 'linear-gradient(180deg, rgba(255,255,255,0.03), rgba(255,255,255,0.01))'
702
- },
703
-
704
- oauthGithub: {
705
- background: 'linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0.00))'
706
- },
707
-
708
- dividerRow: {
709
- display: 'flex',
710
- alignItems: 'center',
711
- gap: 10,
712
- marginTop: 12
713
- },
714
-
715
- line: {
716
- flex: 1,
717
- height: 1,
718
- background: 'rgba(148,163,184,0.06)'
719
- },
720
-
721
- orText: {
722
- fontSize: 13,
723
- color: 'rgba(230,230,230,0.65)',
724
- padding: '0 8px'
725
- },
726
-
727
- form: {
728
- display: 'flex',
729
- flexDirection: 'column',
730
- gap: 10,
731
- marginTop: 6
732
- },
733
-
734
- label: {
735
- display: 'flex',
736
- flexDirection: 'column',
737
- gap: 6
738
- },
739
-
740
- labelText: {
741
- fontSize: 13,
742
- color: 'rgba(230,230,230,0.68)'
743
- },
744
-
745
- input: {
746
- width: '100%',
747
- padding: '10px 12px',
748
- borderRadius: 10,
749
- background: 'rgba(255,255,255,0.02)',
750
- color: '#e6e6e6',
751
- border: '1px solid rgba(148,163,184,0.10)',
752
- fontSize: 14,
753
- outline: 'none',
754
- boxSizing: 'border-box',
755
- transition: 'box-shadow 120ms, border-color 120ms'
756
- },
757
-
758
- submitButton: {
759
- marginTop: 6,
760
- width: '100%',
761
- padding: '10px 12px',
762
- borderRadius: 10,
763
- background: 'linear-gradient(180deg,#475569,#94a3b8)',
764
- border: 'none',
765
- color: '#fff',
766
- fontWeight: 600,
767
- cursor: 'pointer',
768
- fontSize: 15,
769
- display: 'inline-flex',
770
- alignItems: 'center',
771
- justifyContent: 'center',
772
- minHeight: 44
773
- },
774
-
775
- modalFooter: {
776
- padding: '12px 18px 18px 18px',
777
- borderTop: '1px solid rgba(148,163,184,0.03)',
778
- display: 'flex',
779
- flexDirection: 'column',
780
- gap: 8
781
- },
782
-
783
- belowRow: {
784
- textAlign: 'center',
785
- display: 'flex',
786
- justifyContent: 'center',
787
- gap: 8,
788
- alignItems: 'center'
789
- },
790
-
791
- muted: {
792
- color: 'rgba(230,230,230,0.66)',
793
- fontSize: 13
794
- },
795
-
796
- link: {
797
- color: '#9fb0d9',
798
- textDecoration: 'none',
799
- fontWeight: 600
800
- },
801
-
802
- dividerThin: {
803
- height: 1,
804
- background: 'rgba(148,163,184,0.04)',
805
- marginTop: 6,
806
- marginBottom: 6
807
- },
808
-
809
- secured: {
810
- textAlign: 'center'
811
- },
812
-
813
- securedText: {
814
- color: 'rgba(230,230,230,0.9)',
815
- fontSize: 13,
816
- fontWeight: 600
817
- },
818
-
819
- /* Toasts: black background */
820
- toastContainer: {
821
- position: 'fixed',
822
- top: 18,
823
- right: 18,
824
- width: 360,
825
- maxWidth: 'calc(100% - 36px)',
826
- display: 'flex',
827
- flexDirection: 'column',
828
- gap: 10,
829
- zIndex: 60000
830
- },
831
-
832
- toastBase: {
833
- display: 'flex',
834
- gap: 10,
835
- alignItems: 'center',
836
- padding: '10px 12px',
837
- borderRadius: 10,
838
- boxShadow: '0 8px 20px rgba(2,6,23,0.6)',
839
- color: '#fff',
840
- fontSize: 13,
841
- minWidth: 120
842
- },
843
-
844
- toastError: {
845
- background: '#000000',
846
- border: '1px solid rgba(255,255,255,0.06)'
847
- },
848
-
849
- toastSuccess: {
850
- background: '#000000',
851
- border: '1px solid rgba(255,255,255,0.06)'
852
- },
853
-
854
- toastInfo: {
855
- background: '#000000',
856
- border: '1px solid rgba(255,255,255,0.06)'
857
- },
858
-
859
- toastCloseBtn: {
860
- marginLeft: 8,
861
- background: 'transparent',
862
- border: 'none',
863
- color: 'rgba(255,255,255,0.7)',
864
- cursor: 'pointer',
865
- fontSize: 14,
866
- lineHeight: 1
867
- }
599
+ /* styles (JS objects) - neutral palette, translucent overlay so underlying content shows */
600
+ const overlay = {
601
+ position: 'fixed',
602
+ inset: 0,
603
+ display: 'block', // page scroll model
604
+ padding: 20,
605
+ background: 'linear-gradient(180deg, rgba(2,6,23,0.22), rgba(2,6,23,0.32))',
606
+ backdropFilter: 'blur(6px)',
607
+ overflowY: 'auto',
608
+ WebkitOverflowScrolling: 'touch',
609
+ minHeight: '100vh',
610
+ zIndex: 9999
611
+ }
612
+
613
+ const modal = {
614
+ width: '100%',
615
+ maxWidth: 460, // reduced width per request
616
+ margin: '40px auto',
617
+ borderRadius: 14,
618
+ background: 'linear-gradient(180deg, rgba(15,19,24,0.6), rgba(10,12,16,0.6))',
619
+ border: '1px solid rgba(99,102,106,0.12)',
620
+ boxShadow: '0 20px 50px rgba(2,6,23,0.55), inset 0 1px 0 rgba(255,255,255,0.02)',
621
+ overflow: 'hidden',
622
+ display: 'flex',
623
+ flexDirection: 'column',
624
+ maxHeight: 'calc(100vh - 80px)'
625
+ }
626
+
627
+ const modalInner = {
628
+ padding: 18,
629
+ display: 'flex',
630
+ flexDirection: 'column',
631
+ gap: 12,
632
+ overflowY: 'auto'
633
+ }
634
+
635
+ /* header / brand */
636
+ const header = {
637
+ paddingBottom: 6,
638
+ borderBottom: '1px solid rgba(148,163,184,0.04)'
639
+ }
640
+
641
+ const brandRow = {
642
+ display: 'flex',
643
+ alignItems: 'center',
644
+ gap: 12
645
+ }
646
+
647
+ const logo = {
648
+ width: 44,
649
+ height: 44,
650
+ display: 'flex',
651
+ alignItems: 'center',
652
+ justifyContent: 'center'
653
+ }
654
+
655
+ const logoCircle = {
656
+ width: 36,
657
+ height: 36,
658
+ borderRadius: 999,
659
+ background: 'linear-gradient(135deg,#2f3438,#11151a)',
660
+ boxShadow: '0 4px 12px rgba(2,6,23,0.6)'
661
+ }
662
+
663
+ const title = {
664
+ margin: 0,
665
+ fontSize: 18,
666
+ fontWeight: 600,
667
+ color: '#e6e6e6'
668
+ }
669
+
670
+ const subtitle = {
671
+ fontSize: 13,
672
+ color: 'rgba(230,230,230,0.72)',
673
+ marginTop: 4
674
+ }
675
+
676
+ /* oauth row */
677
+ const oauthSection = {
678
+ marginTop: 8,
679
+ display: 'flex',
680
+ gap: 8
681
+ }
682
+
683
+ const oauthButton = {
684
+ flex: 1,
685
+ display: 'inline-flex',
686
+ alignItems: 'center',
687
+ justifyContent: 'center',
688
+ padding: '10px 12px',
689
+ borderRadius: 10,
690
+ border: '1px solid rgba(148,163,184,0.08)',
691
+ fontSize: 14,
692
+ cursor: 'pointer',
693
+ userSelect: 'none',
694
+ gap: 8,
695
+ minHeight: 40
696
+ }
697
+
698
+ const oauthGoogle = {
699
+ background: 'linear-gradient(180deg, rgba(255,255,255,0.03), rgba(255,255,255,0.01))',
700
+ color: '#fff'
701
+ }
702
+
703
+ const oauthGithub = {
704
+ background: 'linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0.00))',
705
+ color: '#fff'
706
+ }
707
+
708
+ /* divider row */
709
+ const dividerRow = {
710
+ display: 'flex',
711
+ alignItems: 'center',
712
+ gap: 10,
713
+ marginTop: 12
714
+ }
715
+
716
+ const line = {
717
+ flex: 1,
718
+ height: 1,
719
+ background: 'rgba(148,163,184,0.06)'
720
+ }
721
+
722
+ const orText = {
723
+ fontSize: 13,
724
+ color: 'rgba(230,230,230,0.65)',
725
+ padding: '0 8px'
726
+ }
727
+
728
+ /* form */
729
+ const form = {
730
+ display: 'flex',
731
+ flexDirection: 'column',
732
+ gap: 10,
733
+ marginTop: 6
734
+ }
735
+
736
+ const label = {
737
+ display: 'flex',
738
+ flexDirection: 'column',
739
+ gap: 6
740
+ }
741
+
742
+ const labelText = {
743
+ fontSize: 13,
744
+ color: 'rgba(230,230,230,0.68)'
745
+ }
746
+
747
+ const input = {
748
+ width: '100%',
749
+ padding: '10px 12px',
750
+ borderRadius: 10,
751
+ background: 'rgba(255,255,255,0.02)',
752
+ color: '#e6e6e6',
753
+ border: '1px solid rgba(148,163,184,0.10)',
754
+ fontSize: 14,
755
+ outline: 'none',
756
+ boxSizing: 'border-box',
757
+ transition: 'box-shadow 120ms, border-color 120ms'
758
+ }
759
+
760
+ const submitButton = {
761
+ marginTop: 6,
762
+ width: '100%',
763
+ padding: '10px 12px',
764
+ borderRadius: 10,
765
+ background: 'linear-gradient(180deg,#475569,#94a3b8)',
766
+ border: 'none',
767
+ color: '#fff',
768
+ fontWeight: 600,
769
+ cursor: 'pointer',
770
+ fontSize: 15,
771
+ display: 'inline-flex',
772
+ alignItems: 'center',
773
+ justifyContent: 'center',
774
+ minHeight: 44
775
+ }
776
+
777
+ /* modal footer */
778
+ const modalFooter = {
779
+ padding: '12px 18px 18px 18px',
780
+ borderTop: '1px solid rgba(148,163,184,0.03)',
781
+ display: 'flex',
782
+ flexDirection: 'column',
783
+ gap: 8
784
+ }
785
+
786
+ const belowRow = {
787
+ textAlign: 'center',
788
+ display: 'flex',
789
+ justifyContent: 'center',
790
+ gap: 8,
791
+ alignItems: 'center'
792
+ }
793
+
794
+ const muted = {
795
+ color: 'rgba(230,230,230,0.66)',
796
+ fontSize: 13
797
+ }
798
+
799
+ const link = {
800
+ color: '#9fb0d9',
801
+ textDecoration: 'none',
802
+ fontWeight: 600
803
+ }
804
+
805
+ const dividerThin = {
806
+ height: 1,
807
+ background: 'rgba(148,163,184,0.04)',
808
+ marginTop: 6,
809
+ marginBottom: 6
810
+ }
811
+
812
+ const secured = {
813
+ textAlign: 'center'
814
+ }
815
+
816
+ const securedText = {
817
+ color: 'rgba(230,230,230,0.9)',
818
+ fontSize: 13,
819
+ fontWeight: 600
820
+ }
821
+
822
+ /* toasts - black background as requested */
823
+ const toastContainer = {
824
+ position: 'fixed',
825
+ top: 18,
826
+ right: 18,
827
+ width: 360,
828
+ maxWidth: 'calc(100% - 36px)',
829
+ display: 'flex',
830
+ flexDirection: 'column',
831
+ gap: 10,
832
+ zIndex: 60000
833
+ }
834
+
835
+ const toastBase = {
836
+ display: 'flex',
837
+ gap: 10,
838
+ alignItems: 'center',
839
+ padding: '10px 12px',
840
+ borderRadius: 10,
841
+ boxShadow: '0 8px 20px rgba(2,6,23,0.6)',
842
+ color: '#fff',
843
+ fontSize: 13,
844
+ minWidth: 120
845
+ }
846
+
847
+ const toastError = {
848
+ background: '#000000',
849
+ border: '1px solid rgba(255,255,255,0.06)'
850
+ }
851
+ const toastSuccess = {
852
+ background: '#000000',
853
+ border: '1px solid rgba(255,255,255,0.06)'
854
+ }
855
+ const toastInfo = {
856
+ background: '#000000',
857
+ border: '1px solid rgba(255,255,255,0.06)'
858
+ }
859
+
860
+ const toastCloseBtn = {
861
+ marginLeft: 8,
862
+ background: 'transparent',
863
+ border: 'none',
864
+ color: 'rgba(255,255,255,0.7)',
865
+ cursor: 'pointer',
866
+ fontSize: 14,
867
+ lineHeight: 1
868
868
  }