flowlink-auth 2.7.5 → 2.7.6

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/SignUp.jsx +422 -400
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flowlink-auth",
3
- "version": "2.7.5",
3
+ "version": "2.7.6",
4
4
  "description": "Custom auth library",
5
5
  "main": "dist/index.js",
6
6
  "types": "src/index.d.ts",
package/src/SignUp.jsx CHANGED
@@ -258,490 +258,512 @@
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
261
262
  'use client'
262
263
  import React, { useState, useRef, useEffect } from 'react'
263
264
  import { ToastContainer, toast } from 'react-toastify'
264
265
  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)
266
+ import Link from 'next/link'
267
+
268
+ export default function SignUp({ agency = { name: 'MyApp', logo: '' } }) {
269
+ const [name, setName] = useState('')
270
+ const [email, setEmail] = useState('')
271
+ const [password, setPassword] = useState('')
272
+ const [loading, setLoading] = useState(false)
273
+ 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
+ const redirectTimer = useRef(null)
278
+
279
+ useEffect(() => {
280
+ return () => {
281
+ if (redirectTimer.current) clearTimeout(redirectTimer.current)
282
+ }
283
+ }, [])
284
+
285
+ // Example submit — replace with real auth call
286
+ async function submit(e) {
287
+ e.preventDefault()
288
+ if (loading) return
289
+ setMessage(null)
290
+ setLoading(true)
291
+
292
+ 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)
309
+ } catch (err) {
310
+ const text = err?.message ?? 'Network error'
311
+ // show toast with black background (ToastContainer configured below)
312
+ toast.error(text)
313
+ console.error('Signup error:', err)
314
+ } finally {
315
+ setLoading(false)
316
+ }
326
317
  }
327
318
 
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)
319
+ async function startOAuthFlow(provider) {
320
+ if (loading || loadingOauth[provider]) return
321
+ setLoadingOauth(prev => ({ ...prev, [provider]: true }))
322
+
323
+ 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
330
+ } catch (err) {
331
+ toast.error(err?.message || 'OAuth start failed')
332
+ console.error('OAuth start error:', err)
333
+ setLoadingOauth(prev => ({ ...prev, [provider]: false }))
334
+ }
339
335
  }
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
336
 
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.')
337
+ const handleGoogle = (e) => {
338
+ if (e && typeof e.preventDefault === 'function') e.preventDefault()
339
+ startOAuthFlow('google')
372
340
  }
373
341
 
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')
342
+ const handleGithub = (e) => {
343
+ if (e && typeof e.preventDefault === 'function') e.preventDefault()
344
+ startOAuthFlow('github')
387
345
  }
388
346
 
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} />
347
+ return (
348
+ <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
+ />
365
+
366
+ <div style={modal}>
367
+ <div style={modalInner}>
368
+ <header style={header}>
369
+ <div style={brandRow}>
370
+ <div style={logo}>
371
+ {/* if you have a real logo, replace with <Image src={agency.logo} ... /> */}
372
+ <div style={logoCircle} aria-hidden />
373
+ </div>
374
+ <div>
375
+ <h1 style={title}>Sign up to {agency.name}</h1>
376
+ <div style={subtitle}>Welcome! Create your account.</div>
377
+ </div>
378
+ </div>
379
+ </header>
380
+
381
+ <section style={oauthSection}>
382
+ <button
383
+ onClick={handleGoogle}
384
+ type="button"
385
+ style={{ ...oauthButton, ...oauthGoogle }}
386
+ disabled={loading || loadingOauth.google}
387
+ aria-disabled={loading || loadingOauth.google}
388
+ >
389
+ <svg width={18} style={{ marginRight: 10 }} viewBox="-3 0 262 262" xmlns="http://www.w3.org/2000/svg" fill="#000000" aria-hidden>
390
+ <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>
391
+ <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>
392
+ <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>
393
+ <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>
394
+ </svg>
395
+ <span>{loadingOauth.google ? 'Loading...' : 'Continue with Google'}</span>
396
+ </button>
397
+
398
+ <button
399
+ onClick={handleGithub}
400
+ type="button"
401
+ style={{ ...oauthButton, ...oauthGithub }}
402
+ disabled={loading || loadingOauth.github}
403
+ aria-disabled={loading || loadingOauth.github}
404
+ >
405
+ <svg width={18} style={{ marginRight: 10 }} xmlns="http://www.w3.org/2000/svg" fill="white" viewBox="0 0 20 20" aria-hidden>
406
+ <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" />
407
+ </svg>
408
+ <span>{loadingOauth.github ? 'Loading...' : 'Continue with GitHub'}</span>
409
+ </button>
410
+ </section>
411
+
412
+ <div style={dividerRow}>
413
+ <div style={line} />
414
+ <div style={orText}>or</div>
415
+ <div style={line} />
416
+ </div>
417
+
418
+ <form onSubmit={submit} style={form}>
419
+ <label style={label} htmlFor="name">
420
+ <span style={labelText}>Name</span>
421
+ <input
422
+ id="name"
423
+ type="text"
424
+ value={name}
425
+ onChange={e => setName(e.target.value)}
426
+ placeholder="Your name"
427
+ style={input}
428
+ autoComplete="name"
429
+ />
430
+ </label>
431
+
432
+ <label style={label} htmlFor="email">
433
+ <span style={labelText}>Email address</span>
434
+ <input
435
+ id="email"
436
+ type="email"
437
+ value={email}
438
+ onChange={e => setEmail(e.target.value)}
439
+ required
440
+ placeholder="you@example.com"
441
+ style={input}
442
+ autoComplete="email"
443
+ />
444
+ </label>
445
+
446
+ <label style={label} htmlFor="password">
447
+ <span style={labelText}>Password</span>
448
+ <input
449
+ id="password"
450
+ type="password"
451
+ value={password}
452
+ onChange={e => setPassword(e.target.value)}
453
+ required
454
+ placeholder="••••••••"
455
+ style={input}
456
+ autoComplete="new-password"
457
+ />
458
+ </label>
459
+
460
+ <button
461
+ type="submit"
462
+ style={submitButton}
463
+ disabled={loading}
464
+ aria-disabled={loading}
465
+ >
466
+ {loading ? 'Signing up...' : 'Sign Up'}
467
+ </button>
468
+ </form>
469
+ </div>
470
+
471
+ <div style={modalFooter}>
472
+ <div style={belowRow}>
473
+ <span style={muted}>Already have an account? </span>
474
+ <Link href="/sign-in" style={link}>Sign in</Link>
475
+ </div>
476
+ <div style={dividerThin} />
477
+ <div style={secured}>
478
+ <div style={securedText}>Secured by auth</div>
479
+ </div>
480
+ </div>
461
481
  </div>
462
482
 
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>
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
+ )}
525
503
  </div>
526
- </div>
527
- </div>
528
-
529
-
530
- )
504
+ )
531
505
  }
532
506
 
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
- }
507
+ /* Styles (JS objects) */
546
508
 
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
509
+ /* overlay: translucent so underlying content is visible; backdrop blur and subtle vignette */
510
+ const overlay = {
511
+ position: 'fixed',
512
+ inset: 0,
513
+ display: 'flex',
514
+ alignItems: 'center',
515
+ justifyContent: 'center',
516
+ padding: 20,
517
+ background: 'linear-gradient(180deg, rgba(2,6,23,0.25), rgba(2,6,23,0.35))',
518
+ backdropFilter: 'blur(6px)',
519
+ minHeight: '100vh',
520
+ zIndex: 9999
558
521
  }
559
522
 
560
- const cardInner = {
561
- padding: 26,
562
- display: 'flex',
563
- flexDirection: 'column',
564
- gap: 18
523
+ /* modal itself uses semi-transparent background + strong soft shadow,
524
+ so things beneath are slightly visible but content still readable */
525
+ const modal = {
526
+ width: '100%',
527
+ maxWidth: 560,
528
+ 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
531
+ boxShadow: '0 20px 50px rgba(2,6,23,0.55), inset 0 1px 0 rgba(255,255,255,0.02)',
532
+ overflow: 'hidden',
533
+ display: 'flex',
534
+ flexDirection: 'column',
535
+ transform: 'translateY(-6px)'
565
536
  }
566
537
 
567
- const brand = {
568
- display: 'flex',
569
- flexDirection: 'column',
570
- gap: 8
538
+ const modalInner = {
539
+ padding: 18,
540
+ display: 'flex',
541
+ flexDirection: 'column',
542
+ gap: 12
571
543
  }
572
544
 
573
- const brandRow = {
574
- display: 'flex',
575
- alignItems: 'center',
576
- gap: 12
545
+ /* HEADER / BRAND */
546
+ const header = {
547
+ paddingBottom: 6,
548
+ borderBottom: '1px solid rgba(148,163,184,0.04)'
577
549
  }
578
550
 
579
- const logoPlaceholder = {
580
- width: 36,
581
- height: 36,
582
- borderRadius: 999,
583
- background: 'linear-gradient(135deg,#2b313a,#0f1724)'
551
+ const brandRow = {
552
+ display: 'flex',
553
+ alignItems: 'center',
554
+ gap: 12
584
555
  }
585
556
 
586
- const brandTitle = {
587
- margin: 0,
588
- fontSize: 20,
589
- fontWeight: 600,
590
- color: '#fff'
557
+ const logo = {
558
+ width: 44,
559
+ height: 44,
560
+ display: 'flex',
561
+ alignItems: 'center',
562
+ justifyContent: 'center'
591
563
  }
592
564
 
593
- const brandSub = {
594
- display: 'flex',
595
- flexDirection: 'column',
596
- gap: 4
565
+ const logoCircle = {
566
+ width: 36,
567
+ height: 36,
568
+ borderRadius: 999,
569
+ background: 'linear-gradient(135deg,#2f3438,#11151a)',
570
+ boxShadow: '0 4px 12px rgba(2,6,23,0.6)'
597
571
  }
598
572
 
599
- const brandLead = {
600
- fontSize: 15,
601
- color: 'rgba(255,255,255,0.95)'
573
+ const title = {
574
+ margin: 0,
575
+ fontSize: 18,
576
+ fontWeight: 600,
577
+ color: '#e6e6e6'
602
578
  }
603
579
 
604
- const brandMuted = {
605
- fontSize: 13,
606
- color: 'rgba(255,255,255,0.65)',
607
- marginTop: 2
580
+ const subtitle = {
581
+ fontSize: 13,
582
+ color: 'rgba(230,230,230,0.72)',
583
+ marginTop: 4
608
584
  }
609
585
 
610
- const oauthRow = {
611
- display: 'flex',
612
- gap: 10,
613
- marginTop: 8
586
+ /* OAUTH ROW */
587
+ const oauthSection = {
588
+ marginTop: 8,
589
+ display: 'flex',
590
+ gap: 8
614
591
  }
615
592
 
616
593
  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
594
+ flex: 1,
595
+ display: 'inline-flex',
596
+ alignItems: 'center',
597
+ justifyContent: 'center',
598
+ padding: '10px 12px',
599
+ borderRadius: 10,
600
+ border: '1px solid rgba(148,163,184,0.08)',
601
+ fontSize: 14,
602
+ cursor: 'pointer',
603
+ userSelect: 'none',
604
+ gap: 8,
605
+ minHeight: 40
628
606
  }
629
607
 
630
608
  const oauthGoogle = {
631
- background: 'rgba(255,255,255,0.06)',
632
- color: '#fff'
609
+ background: 'linear-gradient(180deg, rgba(255,255,255,0.03), rgba(255,255,255,0.01))',
610
+ color: '#fff'
633
611
  }
634
612
 
635
613
  const oauthGithub = {
636
- background: 'rgba(255,255,255,0.03)',
637
- color: '#fff'
614
+ background: 'linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0.00))',
615
+ color: '#fff'
638
616
  }
639
617
 
618
+ /* DIVIDER */
640
619
  const dividerRow = {
641
- display: 'flex',
642
- alignItems: 'center',
643
- gap: 12,
644
- marginTop: 14
620
+ display: 'flex',
621
+ alignItems: 'center',
622
+ gap: 10,
623
+ marginTop: 12
645
624
  }
646
625
 
647
626
  const line = {
648
- flex: 1,
649
- height: 1,
650
- background: 'rgba(148,163,184,0.06)'
627
+ flex: 1,
628
+ height: 1,
629
+ background: 'rgba(148,163,184,0.06)'
651
630
  }
652
631
 
653
632
  const orText = {
654
- fontSize: 13,
655
- color: 'rgba(255,255,255,0.6)',
656
- padding: '0 8px'
633
+ fontSize: 13,
634
+ color: 'rgba(230,230,230,0.65)',
635
+ padding: '0 8px'
657
636
  }
658
637
 
638
+ /* FORM */
659
639
  const form = {
660
- display: 'flex',
661
- flexDirection: 'column',
662
- gap: 12,
663
- marginTop: 6
640
+ display: 'flex',
641
+ flexDirection: 'column',
642
+ gap: 10,
643
+ marginTop: 6
664
644
  }
665
645
 
666
646
  const label = {
667
- display: 'flex',
668
- flexDirection: 'column',
669
- gap: 6
647
+ display: 'flex',
648
+ flexDirection: 'column',
649
+ gap: 6
670
650
  }
671
651
 
672
652
  const labelText = {
673
- fontSize: 13,
674
- color: 'rgba(255,255,255,0.66)'
653
+ fontSize: 13,
654
+ color: 'rgba(230,230,230,0.68)'
675
655
  }
676
656
 
677
657
  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'
658
+ width: '100%',
659
+ padding: '10px 12px',
660
+ borderRadius: 10,
661
+ background: 'rgba(255,255,255,0.02)',
662
+ color: '#e6e6e6',
663
+ border: '1px solid rgba(148,163,184,0.10)', // slightly stronger border for visibility
664
+ fontSize: 14,
665
+ outline: 'none',
666
+ boxSizing: 'border-box',
667
+ transition: 'box-shadow 120ms, border-color 120ms'
687
668
  }
688
669
 
689
670
  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
671
+ marginTop: 6,
672
+ width: '100%',
673
+ padding: '10px 12px',
674
+ borderRadius: 10,
675
+ background: 'linear-gradient(180deg,#475569,#94a3b8)', // neutral blue-gray gradient
676
+ border: 'none',
677
+ color: '#fff',
678
+ fontWeight: 600,
679
+ cursor: 'pointer',
680
+ fontSize: 15,
681
+ display: 'inline-flex',
682
+ alignItems: 'center',
683
+ justifyContent: 'center',
684
+ minHeight: 44
685
+ }
686
+
687
+ /* FOOTER / BELOW */
688
+ const modalFooter = {
689
+ padding: '12px 18px 18px 18px',
690
+ borderTop: '1px solid rgba(148,163,184,0.03)',
691
+ display: 'flex',
692
+ flexDirection: 'column',
693
+ gap: 8
711
694
  }
712
695
 
713
696
  const belowRow = {
714
- textAlign: 'center',
715
- display: 'flex',
716
- justifyContent: 'center',
717
- gap: 8,
718
- alignItems: 'center'
697
+ textAlign: 'center',
698
+ display: 'flex',
699
+ justifyContent: 'center',
700
+ gap: 8,
701
+ alignItems: 'center'
719
702
  }
720
703
 
721
704
  const muted = {
722
- color: 'rgba(255,255,255,0.65)',
723
- fontSize: 13
705
+ color: 'rgba(230,230,230,0.66)',
706
+ fontSize: 13
724
707
  }
725
708
 
726
709
  const link = {
727
- color: '#3b82f6',
728
- textDecoration: 'none',
729
- fontWeight: 600
710
+ color: '#9fb0d9',
711
+ textDecoration: 'none',
712
+ fontWeight: 600
730
713
  }
731
714
 
732
715
  const dividerThin = {
733
- height: 1,
734
- background: 'rgba(148,163,184,0.05)',
735
- marginTop: 6,
736
- marginBottom: 6
716
+ height: 1,
717
+ background: 'rgba(148,163,184,0.04)',
718
+ marginTop: 6,
719
+ marginBottom: 6
737
720
  }
738
721
 
739
722
  const secured = {
740
- textAlign: 'center'
723
+ textAlign: 'center'
741
724
  }
742
725
 
743
726
  const securedText = {
744
- color: 'rgba(255,255,255,0.9)',
745
- fontSize: 13,
746
- fontWeight: 600
747
- }
727
+ color: 'rgba(230,230,230,0.9)',
728
+ fontSize: 13,
729
+ fontWeight: 600
730
+ }
731
+
732
+ /* KEYS PANEL */
733
+ const keysOverlay = {
734
+ position: 'fixed',
735
+ inset: 0,
736
+ zIndex: 20000,
737
+ display: 'flex',
738
+ alignItems: 'center',
739
+ justifyContent: 'center',
740
+ background: 'rgba(2,6,23,0.45)'
741
+ }
742
+
743
+ const keysPanel = {
744
+ background: '#fff',
745
+ color: '#111827',
746
+ padding: 18,
747
+ borderRadius: 10,
748
+ maxWidth: 720,
749
+ width: '95%',
750
+ boxShadow: '0 12px 40px rgba(2,6,23,0.5)'
751
+ }
752
+
753
+ const keyBox = {
754
+ padding: 10,
755
+ background: '#f3f4f6',
756
+ borderRadius: 8,
757
+ overflowX: 'auto',
758
+ fontSize: 13
759
+ }
760
+
761
+ const copyBtn = {
762
+ marginTop: 8,
763
+ padding: '8px 10px',
764
+ borderRadius: 8,
765
+ background: '#111827',
766
+ color: '#fff',
767
+ border: 'none',
768
+ cursor: 'pointer'
769
+ }