style: 로그인 페이지 재설계 + 'GEN 3 ALGO' 태그라인 제거

- Login: 카드형 레이아웃, 키 아이콘·표시/숨김 토글, 인라인 에러(aria), 로딩 스피너,
  입력 시 에러 자동 해제, 그라디언트 텍스트 제거
- Layout 헤더: 태그라인 삭제, 브랜드명 단일 표기

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-19 23:58:28 +09:00
parent 19d25e5528
commit f8c4612650
2 changed files with 73 additions and 45 deletions

View File

@@ -8,7 +8,7 @@
* *
* 구조: * 구조:
* [Header] — sticky 상단 바 (z-50) * [Header] — sticky 상단 바 (z-50)
* - 브랜드 로고 (AutoStock GEN 3 ALGO) * - 브랜드 로고 (AutoStock)
* - 네비게이션 링크 (대시보드, 성과분석, 전략설정, 백테스트, 매매로그, MCP상태) * - 네비게이션 링크 (대시보드, 성과분석, 전략설정, 백테스트, 매매로그, MCP상태)
* - 연결 상태 표시 (Backend LIVE/OFFLINE, MCP connected/unknown) * - 연결 상태 표시 (Backend LIVE/OFFLINE, MCP connected/unknown)
* [Main] — 페이지 컨텐츠 영역 * [Main] — 페이지 컨텐츠 영역
@@ -78,12 +78,7 @@ const Layout = ({ children }: LayoutProps) => {
<div className="bg-blue-600 p-2 rounded-lg shadow-lg shadow-blue-500/20"> <div className="bg-blue-600 p-2 rounded-lg shadow-lg shadow-blue-500/20">
<Activity className="text-white" size={20} /> <Activity className="text-white" size={20} />
</div> </div>
<div> <h1 className="text-lg font-semibold tracking-tight leading-none">AutoStock</h1>
<h1 className="text-xl font-bold bg-gradient-to-r from-blue-400 to-purple-400 bg-clip-text text-transparent leading-none">
AutoStock
</h1>
<p className="text-[10px] text-gray-500 font-mono tracking-wider">GEN 3 ALGO</p>
</div>
</div> </div>
{/* Navigation */} {/* Navigation */}

View File

@@ -10,7 +10,7 @@
* - onLogin: 인증 성공 시 API 키를 전달받는 콜백 (App에서 localStorage 저장) * - onLogin: 인증 성공 시 API 키를 전달받는 콜백 (App에서 localStorage 저장)
*/ */
import { useState, FormEvent } from 'react' import { useState, FormEvent } from 'react'
import { Activity } from 'lucide-react' import { Activity, Eye, EyeOff, KeyRound, Loader2 } from 'lucide-react'
/** Login 컴포넌트 Props 정의 */ /** Login 컴포넌트 Props 정의 */
interface LoginProps { interface LoginProps {
@@ -30,6 +30,7 @@ const Login = ({ onLogin }: LoginProps) => {
const [apiKey, setApiKey] = useState('') const [apiKey, setApiKey] = useState('')
const [error, setError] = useState('') const [error, setError] = useState('')
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [showKey, setShowKey] = useState(false)
/** /**
* 폼 제출 핸들러 — API 키 유효성 검증 * 폼 제출 핸들러 — API 키 유효성 검증
@@ -77,48 +78,80 @@ const Login = ({ onLogin }: LoginProps) => {
} }
return ( return (
<div className="min-h-screen bg-[#0f1115] flex items-center justify-center"> <div className="min-h-screen bg-[#0f1115] text-gray-100 flex items-center justify-center px-4 py-12">
<div className="w-full max-w-sm p-8"> <div className="w-full max-w-[380px]">
<div className="flex flex-col items-center gap-4 mb-8"> {/* 브랜드 */}
<div className="bg-blue-600 p-3 rounded-xl shadow-lg shadow-blue-500/20"> <div className="flex flex-col items-center mb-8">
<Activity className="text-white" size={28} /> <div className="bg-blue-600 p-3 rounded-2xl shadow-lg shadow-blue-500/25 mb-4">
</div> <Activity className="text-white" size={26} />
<div className="text-center">
<h1 className="text-2xl font-bold bg-gradient-to-r from-blue-400 to-purple-400 bg-clip-text text-transparent">
AutoStock
</h1>
<p className="text-xs text-gray-500 font-mono mt-1">GEN 3 ALGO</p>
</div> </div>
<h1 className="text-2xl font-semibold tracking-tight">AutoStock</h1>
<p className="text-sm text-gray-500 mt-1">자동매매 대시보드</p>
</div> </div>
<form onSubmit={handleSubmit} className="space-y-4"> {/* 로그인 카드 */}
<div> <div className="bg-[#161b22] border border-gray-800 rounded-2xl p-6 shadow-xl shadow-black/30">
<label className="block text-sm text-gray-400 mb-2">API Key</label> <form onSubmit={handleSubmit} className="space-y-5" noValidate>
<input <div>
type="password" <label htmlFor="api-key" className="block text-sm font-medium text-gray-300 mb-2">
value={apiKey} API 키
onChange={(e) => setApiKey(e.target.value)} </label>
placeholder="SECRET_KEY 입력" <div className="relative">
className="w-full px-4 py-3 bg-gray-800/50 border border-gray-700 rounded-lg text-gray-100 placeholder-gray-600 focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 transition-colors" <KeyRound
autoFocus size={16}
/> className="absolute left-3.5 top-1/2 -translate-y-1/2 text-gray-500 pointer-events-none"
</div> />
<input
id="api-key"
type={showKey ? 'text' : 'password'}
value={apiKey}
onChange={(e) => {
setApiKey(e.target.value)
if (error) setError('')
}}
placeholder="SECRET_KEY"
autoComplete="current-password"
spellCheck={false}
aria-invalid={!!error}
aria-describedby={error ? 'api-key-error' : undefined}
className={`w-full pl-10 pr-11 py-3 bg-[#0f1115] border rounded-xl text-gray-100 placeholder-gray-600 font-mono text-sm transition-colors focus:outline-none focus:ring-2 ${
error
? 'border-red-500/60 focus:border-red-500 focus:ring-red-500/30'
: 'border-gray-700 focus:border-blue-500 focus:ring-blue-500/30'
}`}
autoFocus
/>
<button
type="button"
onClick={() => setShowKey((v) => !v)}
aria-label={showKey ? 'API 키 숨기기' : 'API 키 표시'}
tabIndex={-1}
className="absolute right-3 top-1/2 -translate-y-1/2 p-1 text-gray-500 hover:text-gray-300 transition-colors"
>
{showKey ? <EyeOff size={16} /> : <Eye size={16} />}
</button>
</div>
{error && (
<p id="api-key-error" role="alert" className="text-red-400 text-sm mt-2">
{error}
</p>
)}
</div>
{error && ( <button
<p className="text-red-400 text-sm">{error}</p> type="submit"
)} disabled={loading}
className="w-full py-3 bg-blue-600 hover:bg-blue-500 active:bg-blue-700 disabled:bg-gray-700 disabled:text-gray-500 disabled:cursor-not-allowed text-white font-medium rounded-xl transition-colors flex items-center justify-center gap-2"
>
{loading && <Loader2 size={16} className="animate-spin" />}
{loading ? '확인 중' : '로그인'}
</button>
</form>
</div>
<button <p className="text-xs text-gray-600 text-center mt-6 leading-relaxed">
type="submit" 백엔드 <code className="font-mono text-gray-500">.env</code>의{' '}
disabled={loading} <code className="font-mono text-gray-500">SECRET_KEY</code> 값으로 로그인합니다.
className="w-full py-3 bg-blue-600 hover:bg-blue-500 disabled:bg-gray-700 disabled:text-gray-500 text-white font-medium rounded-lg transition-colors"
>
{loading ? '확인 중...' : '로그인'}
</button>
</form>
<p className="text-xs text-gray-600 text-center mt-6">
.env 파일의 SECRET_KEY 값을 입력하세요
</p> </p>
</div> </div>
</div> </div>