c4e6aab7b2
- backend/ — FastAPI + JWT + 모든 REST 엔드포인트 - frontend/ — Next.js 14 + Tailwind + 7페이지 (대시보드/트레이드/거래소/자동매매/설정/내정보/로그인) - core_logic.py — 신호계산/알림 로직 분리 (기존 app_streamlit.py 에서 추출) - users_db.py + bcrypt 인증, exchange_keys.py + Fernet 암호화 - trades_db.py — 진입/청산 lifecycle 추적, signal_events raw 로그 - settings_db.py — 모든 운영 파라미터 DB 영속 저장 (RSI/거래량/펀딩비 임계값 포함) - docker-compose: frontend / backend / postgres + Traefik 라우팅 - assets/logo.svg — JUNGGOMOA 그라디언트 로고 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
64 lines
2.9 KiB
TypeScript
64 lines
2.9 KiB
TypeScript
'use client';
|
|
import { useEffect, useState } from 'react';
|
|
import { api } from '@/lib/api';
|
|
import { useAuth } from '@/lib/auth';
|
|
import { Card, PageHeader, Input, Button, Banner } from '@/components/ui';
|
|
import { User, KeyRound } from 'lucide-react';
|
|
|
|
export default function ProfilePage() {
|
|
const { user, fetchMe } = useAuth();
|
|
const [oldPw, setOldPw] = useState('');
|
|
const [newPw, setNewPw] = useState('');
|
|
const [newPw2, setNewPw2] = useState('');
|
|
const [msg, setMsg] = useState<{ level: any; text: string } | null>(null);
|
|
|
|
useEffect(() => { fetchMe(); }, []);
|
|
|
|
async function change(e: React.FormEvent) {
|
|
e.preventDefault();
|
|
setMsg(null);
|
|
if (newPw !== newPw2) { setMsg({ level: 'danger', text: '새 비밀번호가 일치하지 않습니다' }); return; }
|
|
if (newPw.length < 6) { setMsg({ level: 'danger', text: '새 비밀번호는 6자 이상' }); return; }
|
|
try {
|
|
await api.put('/api/auth/password', { old_password: oldPw, new_password: newPw });
|
|
setMsg({ level: 'success', text: '✅ 비밀번호 변경 완료' });
|
|
setOldPw(''); setNewPw(''); setNewPw2('');
|
|
} catch (e: any) { setMsg({ level: 'danger', text: e.message }); }
|
|
}
|
|
|
|
return (
|
|
<div>
|
|
<PageHeader title="👤 개인정보 수정" subtitle="비밀번호 변경" />
|
|
|
|
<div className="grid lg:grid-cols-2 gap-5">
|
|
<Card>
|
|
<div className="flex items-center gap-2 mb-3 text-blue-600">
|
|
<User size={16} /> <span className="font-bold text-slate-800 text-sm">계정 정보</span>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<Input label="아이디" value={user?.username || ''} disabled />
|
|
<Input label="권한" value={user?.role || ''} disabled />
|
|
</div>
|
|
<div className="text-xs text-slate-500 mt-3 pt-3 border-t border-slate-100">
|
|
가입: {user?.created_at || '-'}<br />
|
|
마지막 로그인: {user?.last_login_at || '-'}
|
|
</div>
|
|
</Card>
|
|
|
|
<Card>
|
|
<div className="flex items-center gap-2 mb-3 text-blue-600">
|
|
<KeyRound size={16} /> <span className="font-bold text-slate-800 text-sm">비밀번호 변경</span>
|
|
</div>
|
|
<form onSubmit={change} className="space-y-3">
|
|
<Input label="현재 비밀번호" type="password" value={oldPw} onChange={(e: any) => setOldPw(e.target.value)} required />
|
|
<Input label="새 비밀번호 (6자 이상)" type="password" value={newPw} onChange={(e: any) => setNewPw(e.target.value)} required />
|
|
<Input label="새 비밀번호 확인" type="password" value={newPw2} onChange={(e: any) => setNewPw2(e.target.value)} required />
|
|
{msg && <Banner level={msg.level}>{msg.text}</Banner>}
|
|
<Button type="submit" className="w-full">비밀번호 변경</Button>
|
|
</form>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|