style: 로그인 페이지 재설계 + 'GEN 3 ALGO' 태그라인 제거
- Login: 카드형 레이아웃, 키 아이콘·표시/숨김 토글, 인라인 에러(aria), 로딩 스피너, 입력 시 에러 자동 해제, 그라디언트 텍스트 제거 - Layout 헤더: 태그라인 삭제, 브랜드명 단일 표기 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
*
|
||||
* 구조:
|
||||
* [Header] — sticky 상단 바 (z-50)
|
||||
* - 브랜드 로고 (AutoStock GEN 3 ALGO)
|
||||
* - 브랜드 로고 (AutoStock)
|
||||
* - 네비게이션 링크 (대시보드, 성과분석, 전략설정, 백테스트, 매매로그, MCP상태)
|
||||
* - 연결 상태 표시 (Backend LIVE/OFFLINE, MCP connected/unknown)
|
||||
* [Main] — 페이지 컨텐츠 영역
|
||||
@@ -78,12 +78,7 @@ const Layout = ({ children }: LayoutProps) => {
|
||||
<div className="bg-blue-600 p-2 rounded-lg shadow-lg shadow-blue-500/20">
|
||||
<Activity className="text-white" size={20} />
|
||||
</div>
|
||||
<div>
|
||||
<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>
|
||||
<h1 className="text-lg font-semibold tracking-tight leading-none">AutoStock</h1>
|
||||
</div>
|
||||
|
||||
{/* Navigation */}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* - onLogin: 인증 성공 시 API 키를 전달받는 콜백 (App에서 localStorage 저장)
|
||||
*/
|
||||
import { useState, FormEvent } from 'react'
|
||||
import { Activity } from 'lucide-react'
|
||||
import { Activity, Eye, EyeOff, KeyRound, Loader2 } from 'lucide-react'
|
||||
|
||||
/** Login 컴포넌트 Props 정의 */
|
||||
interface LoginProps {
|
||||
@@ -30,6 +30,7 @@ const Login = ({ onLogin }: LoginProps) => {
|
||||
const [apiKey, setApiKey] = useState('')
|
||||
const [error, setError] = useState('')
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [showKey, setShowKey] = useState(false)
|
||||
|
||||
/**
|
||||
* 폼 제출 핸들러 — API 키 유효성 검증
|
||||
@@ -77,48 +78,80 @@ const Login = ({ onLogin }: LoginProps) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-[#0f1115] flex items-center justify-center">
|
||||
<div className="w-full max-w-sm p-8">
|
||||
<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">
|
||||
<Activity className="text-white" size={28} />
|
||||
</div>
|
||||
<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 className="min-h-screen bg-[#0f1115] text-gray-100 flex items-center justify-center px-4 py-12">
|
||||
<div className="w-full max-w-[380px]">
|
||||
{/* 브랜드 */}
|
||||
<div className="flex flex-col items-center mb-8">
|
||||
<div className="bg-blue-600 p-3 rounded-2xl shadow-lg shadow-blue-500/25 mb-4">
|
||||
<Activity className="text-white" size={26} />
|
||||
</div>
|
||||
<h1 className="text-2xl font-semibold tracking-tight">AutoStock</h1>
|
||||
<p className="text-sm text-gray-500 mt-1">자동매매 대시보드</p>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
{/* 로그인 카드 */}
|
||||
<div className="bg-[#161b22] border border-gray-800 rounded-2xl p-6 shadow-xl shadow-black/30">
|
||||
<form onSubmit={handleSubmit} className="space-y-5" noValidate>
|
||||
<div>
|
||||
<label className="block text-sm text-gray-400 mb-2">API Key</label>
|
||||
<label htmlFor="api-key" className="block text-sm font-medium text-gray-300 mb-2">
|
||||
API 키
|
||||
</label>
|
||||
<div className="relative">
|
||||
<KeyRound
|
||||
size={16}
|
||||
className="absolute left-3.5 top-1/2 -translate-y-1/2 text-gray-500 pointer-events-none"
|
||||
/>
|
||||
<input
|
||||
type="password"
|
||||
id="api-key"
|
||||
type={showKey ? 'text' : 'password'}
|
||||
value={apiKey}
|
||||
onChange={(e) => setApiKey(e.target.value)}
|
||||
placeholder="SECRET_KEY 입력"
|
||||
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"
|
||||
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 className="text-red-400 text-sm">{error}</p>
|
||||
<p id="api-key-error" role="alert" className="text-red-400 text-sm mt-2">
|
||||
{error}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
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"
|
||||
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 ? '확인 중...' : '로그인'}
|
||||
{loading && <Loader2 size={16} className="animate-spin" />}
|
||||
{loading ? '확인 중' : '로그인'}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-gray-600 text-center mt-6">
|
||||
.env 파일의 SECRET_KEY 값을 입력하세요
|
||||
<p className="text-xs text-gray-600 text-center mt-6 leading-relaxed">
|
||||
백엔드 <code className="font-mono text-gray-500">.env</code>의{' '}
|
||||
<code className="font-mono text-gray-500">SECRET_KEY</code> 값으로 로그인합니다.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user