Soma / frontend /src /components /AuthScreen.jsx
Komalpreet Kaur
feat: add dark mode toggle to welcome/auth page
fcae58a unverified
import { useState, useEffect, useRef } from 'react';
import './AuthScreen.css';
function AuthScreen({ onAuth, darkMode, setDarkMode }) {
const [stage, setStage] = useState('splash'); // 'splash' | 'enter'
const [username, setUsername] = useState('');
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);
const [transitioning, setTransitioning] = useState(false);
const inputRef = useRef(null);
// Focus input when entering name stage
useEffect(() => {
if (stage === 'enter' && inputRef.current) {
setTimeout(() => inputRef.current.focus(), 600);
}
}, [stage]);
const handleEnterClick = () => {
setTransitioning(true);
setTimeout(() => {
setStage('enter');
setTransitioning(false);
}, 500);
};
const handleSubmit = async (e) => {
e.preventDefault();
if (!username.trim()) return;
setError('');
setLoading(true);
try {
const res = await fetch('/api/v1/auth/enter', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: username.trim() }),
});
const data = await res.json();
if (!res.ok) {
const msg = Array.isArray(data.detail)
? data.detail[0]?.msg ?? 'Invalid input'
: data.detail ?? 'Something went wrong';
setError(msg);
return;
}
localStorage.setItem('soma_token', data.access_token);
localStorage.setItem('soma_username', data.username);
onAuth(data.username);
} catch {
setError('Could not reach the server. Is it running?');
} finally {
setLoading(false);
}
};
return (
<div className={`landing ${transitioning ? 'transitioning' : ''} ${darkMode ? 'dark' : ''}`}>
{/* Dark Mode Toggle */}
<button
className="landing-theme-toggle"
onClick={() => setDarkMode(!darkMode)}
title={darkMode ? 'Switch to Light Mode' : 'Switch to Dark Mode'}
>
<span className="material-icons">{darkMode ? 'light_mode' : 'dark_mode'}</span>
</button>
{/* Animated background */}
<div className="landing-bg">
<div className="bg-orb orb-1" />
<div className="bg-orb orb-2" />
<div className="bg-orb orb-3" />
<div className="bg-grid" />
{/* Floating particles */}
<div className="particles">
{Array.from({ length: 20 }).map((_, i) => (
<div key={i} className="particle" style={{
'--x': `${Math.random() * 100}%`,
'--y': `${Math.random() * 100}%`,
'--duration': `${8 + Math.random() * 12}s`,
'--delay': `${Math.random() * 5}s`,
'--size': `${2 + Math.random() * 3}px`,
}} />
))}
</div>
</div>
{/* ══════ STAGE 1: Splash ══════ */}
{stage === 'splash' && (
<div className={`splash-content ${transitioning ? 'exit' : ''}`}>
{/* Neural orb */}
<div className="hero-orb">
<div className="hero-ring r1" />
<div className="hero-ring r2" />
<div className="hero-ring r3" />
<div className="hero-pulse-ring" />
<div className="hero-core" />
</div>
<h1 className="hero-title">SOMA</h1>
<p className="hero-tagline">Cognitive Architecture for AI</p>
<p className="hero-desc">
A brain-inspired system that builds memory as you talk.
Every conversation shapes a living neural mesh — unique to you.
</p>
{/* Features row */}
<div className="splash-features">
<div className="splash-feature">
<div className="sf-dot" />
<span>Sensory Memory</span>
</div>
<div className="splash-feature">
<div className="sf-dot" />
<span>Knowledge Graph</span>
</div>
<div className="splash-feature">
<div className="sf-dot" />
<span>Neural Dreaming</span>
</div>
</div>
<button className="enter-btn" onClick={handleEnterClick}>
<span className="enter-btn-text">Enter</span>
<span className="enter-btn-arrow">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M5 12h14M12 5l7 7-7 7"/>
</svg>
</span>
</button>
<div className="splash-hint">Press Enter or click to begin</div>
</div>
)}
{/* ══════ STAGE 2: Name Input ══════ */}
{stage === 'enter' && (
<div className="name-content">
{/* Small orb */}
<div className="name-orb">
<div className="hero-ring r1" />
<div className="hero-ring r2" />
<div className="hero-core" />
</div>
<h2 className="name-heading">Who are you?</h2>
<p className="name-sub">
Enter your name to initialize your neural space.
New here? We'll create your brain automatically.
</p>
<form className="name-form" onSubmit={handleSubmit}>
<div className="name-input-wrap">
<input
ref={inputRef}
className="name-input"
type="text"
value={username}
onChange={e => setUsername(e.target.value)}
placeholder="Type your name..."
autoComplete="username"
required
/>
<div className="name-input-glow" />
</div>
{error && <p className="auth-error">{error}</p>}
<button className="name-submit" type="submit" disabled={loading || !username.trim()}>
{loading ? (
<span className="loading-dots">
<span />
<span />
<span />
</span>
) : 'Initialize'}
</button>
</form>
<button className="back-link" onClick={() => setStage('splash')}>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M19 12H5M12 19l-7-7 7-7"/>
</svg>
Back
</button>
</div>
)}
{/* Footer */}
<footer className="landing-footer">
<span>Soma</span>
<span className="footer-dot" />
<span>Brain-Inspired AI</span>
</footer>
</div>
);
}
export default AuthScreen;