// login.jsx — 로그인 페이지 // 중앙 정렬 카드, ID + Password + 로그인 버튼 const { useState: useLoginState, useEffect: useLoginEffect } = React; function LoginPage({ onSuccess }) { const [id, setId] = useLoginState(''); const [pw, setPw] = useLoginState(''); const [err, setErr] = useLoginState(''); const [loading, setLoading] = useLoginState(false); // Read /api/health (public) once on mount so the login screen's mode chip // matches reality. Don't poll — login screen is short-lived. const [paperTrading, setPaperTrading] = useLoginState(null); useLoginEffect(() => { let alive = true; fetch('/api/health', { credentials: 'same-origin' }) .then(r => r.json()) .then(d => { if (alive && d) setPaperTrading(d.paper_trading); }) .catch(() => {}); return () => { alive = false; }; }, []); const submit = async (e) => { e?.preventDefault(); if (!id || !pw) { setErr('아이디와 비밀번호를 입력하세요.'); return; } setErr(''); setLoading(true); try { const res = await fetch('/api/auth/login', { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id, password: pw }), }); const data = await res.json().catch(() => ({})); if (res.ok && data && data.authenticated) { onSuccess?.(data.user || { id }); return; } const code = data?.error?.code || ''; if (res.status === 503) { setErr('서버 설정 누락 — 관리자에게 문의 (' + code + ').'); } else { setErr('인증 실패 — 자격 증명이 올바르지 않습니다.'); } } catch (err) { setErr('네트워크 오류 — 잠시 후 다시 시도해 주세요.'); } finally { setLoading(false); } }; return (
L
LSTL stock-trading-lite

로그인

{paperTrading === false ? ( LIVE ) : paperTrading === true ? ( PAPER MODE ) : null}
{err && (
{err}
)}
build 0.4.2 · main · {new Date().toISOString().slice(0,10)}
env {paperTrading === false ? 'live · 실거래' : paperTrading === true ? 'paper / read-only' : '—'} · 단일 사용자
운영자 단일 계정. 자격 증명은 서버 환경변수에서 로드됨.
이 시스템은 LLM 기반 자동매매를 모니터링합니다.{' '} {paperTrading === false ? '현재 LIVE(실거래) 모드로 동작 중입니다.' : paperTrading === true ? '모든 결정은 PAPER 모드에서 시뮬레이션됩니다.' : ''}
); } Object.assign(window, { LoginPage });