← back to Bertha
kalshi-dash/src/Login.jsx
344 lines
import React, { useState, useEffect, useRef } from 'react';
import { useNavigate } from 'react-router-dom';
import { login, checkSession, googleLogin, getGoogleConfig } from './api';
/* CSS-only animated dot background is defined inline via keyframes injected once */
const DOT_STYLE = `
@keyframes floatDot {
0%, 100% { transform: translateY(0px) scale(1); opacity: 0.18; }
50% { transform: translateY(-18px) scale(1.1); opacity: 0.35; }
}
.login-dot {
position: absolute;
border-radius: 50%;
background: var(--cyan);
animation: floatDot linear infinite;
pointer-events: none;
}
`;
const DOTS = Array.from({ length: 22 }, (_, i) => ({
size: 2 + Math.random() * 4,
top: Math.random() * 100,
left: Math.random() * 100,
duration: 4 + Math.random() * 6,
delay: -(Math.random() * 8),
}));
export default function Login() {
const [user, setUser] = useState('');
const [pass, setPass] = useState('');
const [showPass, setShowPass] = useState(false);
const [err, setErr] = useState('');
const [loading, setLoading] = useState(false);
const [checking, setChecking] = useState(true);
const [googleReady, setGoogleReady] = useState(false);
const [googleClientId, setGoogleClientId] = useState(null);
const googleBtnRef = useRef(null);
const nav = useNavigate();
useEffect(() => {
checkSession().then(ok => { if (ok) nav('/'); else setChecking(false); });
getGoogleConfig().then(cfg => {
if (cfg.configured && cfg.client_id) {
setGoogleClientId(cfg.client_id);
loadGoogleScript(cfg.client_id);
}
});
}, []);
function loadGoogleScript(clientId) {
if (document.getElementById('google-gsi')) { initGoogle(clientId); return; }
const s = document.createElement('script');
s.id = 'google-gsi'; s.src = 'https://accounts.google.com/gsi/client';
s.async = true; s.defer = true;
s.onload = () => initGoogle(clientId);
document.head.appendChild(s);
}
function initGoogle(clientId) {
if (!window.google?.accounts?.id) return;
window.google.accounts.id.initialize({ client_id: clientId, callback: handleGoogleResponse, auto_select: false });
setGoogleReady(true);
}
useEffect(() => {
if (googleReady && googleBtnRef.current && window.google?.accounts?.id) {
window.google.accounts.id.renderButton(googleBtnRef.current, {
type: 'standard', theme: 'filled_black', size: 'large', text: 'signin_with', shape: 'pill', width: 360,
});
}
}, [googleReady]);
async function handleGoogleResponse(response) {
setErr(''); setLoading(true);
try {
const r = await googleLogin(response.credential);
if (r.success) nav('/'); else setErr(r.error || 'Google login failed');
} catch { setErr('Connection error'); }
setLoading(false);
}
async function submit(e) {
e.preventDefault(); setErr(''); setLoading(true);
try {
const ok = await login(user, pass);
if (ok) nav('/'); else setErr('Invalid credentials');
} catch { setErr('Connection error'); }
setLoading(false);
}
if (checking) {
return (
<div
className="flex items-center justify-center min-h-screen"
style={{ background: 'var(--bg)' }}
>
<div
className="w-8 h-8 rounded-full border-2 border-t-transparent animate-spin"
style={{ borderColor: 'var(--cyan)', borderTopColor: 'transparent' }}
/>
</div>
);
}
return (
<div
className="min-h-screen flex items-center justify-center p-4"
style={{ background: 'var(--bg)', position: 'relative', overflow: 'hidden' }}
>
{/* Inject dot animation styles once */}
<style>{DOT_STYLE}</style>
{/* CSS-only animated background dots */}
{DOTS.map((dot, i) => (
<span
key={i}
className="login-dot"
style={{
width: dot.size,
height: dot.size,
top: `${dot.top}%`,
left: `${dot.left}%`,
animationDuration: `${dot.duration}s`,
animationDelay: `${dot.delay}s`,
}}
/>
))}
{/* Subtle radial glow behind card */}
<div
style={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 600,
height: 600,
borderRadius: '50%',
background: 'radial-gradient(ellipse, rgba(0,240,255,0.04) 0%, transparent 70%)',
pointerEvents: 'none',
}}
/>
{/* Login card */}
<div
className="card-glow fade-in w-full"
style={{ maxWidth: 400, position: 'relative', zIndex: 1 }}
>
{/* Logo + heading */}
<div className="flex flex-col items-center mb-7">
{/* Large K badge */}
<div
style={{
width: 72,
height: 72,
borderRadius: 18,
background: 'linear-gradient(135deg, var(--orange), #C45A08)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
marginBottom: 16,
boxShadow: '0 4px 24px rgba(247,147,26,0.35)',
}}
>
<span
style={{
color: '#0E0E10',
fontWeight: 900,
fontSize: 32,
fontFamily: 'var(--font-heading)',
lineHeight: 1,
}}
>
K
</span>
</div>
<h1
className="kalshi-gradient heading"
style={{ fontSize: 26, fontWeight: 800, letterSpacing: '-0.02em', marginBottom: 6 }}
>
KEN TRADER
</h1>
<p style={{ fontSize: 13, color: 'var(--text-muted)', marginBottom: 4 }}>
Event Contracts Trading Platform
</p>
<span className="badge bg-green" style={{ fontSize: 10 }}>
CFTC-Regulated DCM
</span>
</div>
{/* Error alert */}
{err && (
<div
className="fade-in"
style={{
display: 'flex',
alignItems: 'center',
gap: 8,
background: 'var(--red-dim)',
border: '1px solid rgba(255,92,92,0.25)',
borderRadius: 8,
padding: '10px 12px',
marginBottom: 16,
color: 'var(--red)',
fontSize: 13,
}}
>
<svg style={{ width: 15, height: 15, flexShrink: 0 }} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
{err}
</div>
)}
{/* Google Sign-In */}
{googleClientId && (
<div style={{ marginBottom: 4 }}>
<div ref={googleBtnRef} style={{ display: 'flex', justifyContent: 'center', marginBottom: 16 }} />
<div style={{ position: 'relative', display: 'flex', alignItems: 'center', marginBottom: 20 }}>
<div style={{ flex: 1, height: 1, background: 'var(--border)' }} />
<span
style={{
padding: '0 12px',
fontSize: 11,
color: 'var(--text-muted)',
background: 'var(--surface)',
whiteSpace: 'nowrap',
}}
>
or sign in with password
</span>
<div style={{ flex: 1, height: 1, background: 'var(--border)' }} />
</div>
</div>
)}
{/* Password form */}
<form onSubmit={submit}>
<div style={{ marginBottom: 14 }}>
<label
style={{
display: 'block',
fontSize: 11,
fontWeight: 600,
color: 'var(--text-dim)',
marginBottom: 6,
textTransform: 'uppercase',
letterSpacing: '0.06em',
}}
>
Username
</label>
<input
type="text"
value={user}
onChange={e => setUser(e.target.value)}
required
autoComplete="username"
placeholder="admin"
className="input"
/>
</div>
<div style={{ marginBottom: 20 }}>
<label
style={{
display: 'block',
fontSize: 11,
fontWeight: 600,
color: 'var(--text-dim)',
marginBottom: 6,
textTransform: 'uppercase',
letterSpacing: '0.06em',
}}
>
Password
</label>
<div style={{ position: 'relative' }}>
<input
type={showPass ? 'text' : 'password'}
value={pass}
onChange={e => setPass(e.target.value)}
required
autoComplete="current-password"
placeholder="Enter password"
className="input"
style={{ paddingRight: '2.5rem' }}
/>
<button
type="button"
onClick={() => setShowPass(!showPass)}
style={{
position: 'absolute',
right: 12,
top: '50%',
transform: 'translateY(-50%)',
background: 'none',
border: 'none',
cursor: 'pointer',
color: 'var(--text-muted)',
padding: 0,
display: 'flex',
}}
>
<svg style={{ width: 16, height: 16 }} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d={showPass ? 'M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.878 9.878L3 3m6.878 6.879L21 21' : 'M15 12a3 3 0 11-6 0 3 3 0 016 0z M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z'} />
</svg>
</button>
</div>
</div>
<button
type="submit"
disabled={loading}
className="btn btn-p btn-lg"
style={{ width: '100%', opacity: loading ? 0.6 : 1 }}
>
{loading && (
<span
className="w-4 h-4 rounded-full border-2 border-t-transparent animate-spin"
style={{ borderColor: '#0E0E10', borderTopColor: 'transparent' }}
/>
)}
{loading ? 'Signing in...' : 'Sign In'}
</button>
</form>
<p
style={{
textAlign: 'center',
marginTop: 20,
fontSize: 10,
color: 'var(--text-muted)',
letterSpacing: '0.02em',
}}
>
Ken operates as a CFTC-regulated Designated Contract Market
</p>
</div>
</div>
);
}