← 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>
  );
}