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.
- package/package.json +1 -1
- package/src/SignUp.jsx +422 -400
package/package.json
CHANGED
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
|
|
266
|
-
|
|
267
|
-
export default function SignUp() {
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
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
|
-
|
|
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
|
|
375
|
-
|
|
376
|
-
|
|
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
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
<div style={
|
|
412
|
-
<
|
|
413
|
-
<
|
|
414
|
-
<div style={
|
|
415
|
-
|
|
416
|
-
<div
|
|
417
|
-
<
|
|
418
|
-
<
|
|
419
|
-
</div>
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
</
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
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
|
-
|
|
464
|
-
<
|
|
465
|
-
<
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
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
|
-
|
|
527
|
-
</div>
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
)
|
|
504
|
+
)
|
|
531
505
|
}
|
|
532
506
|
|
|
533
|
-
/*
|
|
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
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
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
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
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
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
538
|
+
const modalInner = {
|
|
539
|
+
padding: 18,
|
|
540
|
+
display: 'flex',
|
|
541
|
+
flexDirection: 'column',
|
|
542
|
+
gap: 12
|
|
571
543
|
}
|
|
572
544
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
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
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
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
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
557
|
+
const logo = {
|
|
558
|
+
width: 44,
|
|
559
|
+
height: 44,
|
|
560
|
+
display: 'flex',
|
|
561
|
+
alignItems: 'center',
|
|
562
|
+
justifyContent: 'center'
|
|
591
563
|
}
|
|
592
564
|
|
|
593
|
-
const
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
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
|
|
600
|
-
|
|
601
|
-
|
|
573
|
+
const title = {
|
|
574
|
+
margin: 0,
|
|
575
|
+
fontSize: 18,
|
|
576
|
+
fontWeight: 600,
|
|
577
|
+
color: '#e6e6e6'
|
|
602
578
|
}
|
|
603
579
|
|
|
604
|
-
const
|
|
605
|
-
fontSize: 13,
|
|
606
|
-
color: 'rgba(
|
|
607
|
-
marginTop:
|
|
580
|
+
const subtitle = {
|
|
581
|
+
fontSize: 13,
|
|
582
|
+
color: 'rgba(230,230,230,0.72)',
|
|
583
|
+
marginTop: 4
|
|
608
584
|
}
|
|
609
585
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
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.
|
|
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.
|
|
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:
|
|
644
|
-
marginTop:
|
|
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(
|
|
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:
|
|
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(
|
|
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(
|
|
682
|
-
color: '#
|
|
683
|
-
border: '1px solid rgba(148,163,184,0.
|
|
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,#
|
|
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
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
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(
|
|
723
|
-
fontSize: 13
|
|
705
|
+
color: 'rgba(230,230,230,0.66)',
|
|
706
|
+
fontSize: 13
|
|
724
707
|
}
|
|
725
708
|
|
|
726
709
|
const link = {
|
|
727
|
-
color: '#
|
|
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.
|
|
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(
|
|
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
|
+
}
|