Files
tradeing/frontend/components/ui.tsx
T
chpark c4e6aab7b2 React + FastAPI 풀 마이그레이션 — Streamlit 제거
- 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>
2026-05-06 17:27:11 +09:00

129 lines
4.5 KiB
TypeScript

'use client';
import { cn } from '@/lib/cn';
export function PageHeader({ title, subtitle, right }: { title: string; subtitle?: string; right?: React.ReactNode }) {
return (
<div className="flex items-end justify-between border-b border-slate-200 pb-3 mb-5">
<div>
<h1 className="text-xl font-bold text-slate-900">{title}</h1>
{subtitle && <p className="text-xs text-slate-500 mt-1">{subtitle}</p>}
</div>
{right && <div>{right}</div>}
</div>
);
}
export function Card({ className, children }: { className?: string; children: React.ReactNode }) {
return (
<div className={cn('bg-white rounded-xl border border-slate-200 shadow-sm p-5', className)}>
{children}
</div>
);
}
export function CardHeader({ icon: Icon, title, hint }: any) {
return (
<div className="flex items-center gap-2 mb-3">
{Icon && <Icon size={16} className="text-blue-600" />}
<span className="text-sm font-bold text-slate-800">{title}</span>
{hint && <span className="text-xs text-slate-400">· {hint}</span>}
</div>
);
}
export function Input({ label, ...props }: any) {
return (
<label className="block">
{label && <span className="block text-xs font-medium text-slate-600 mb-1">{label}</span>}
<input
{...props}
className={cn(
'w-full px-3 py-2 text-sm rounded-md border border-slate-300 bg-slate-50',
'focus:outline-none focus:ring-2 focus:ring-blue-500 focus:bg-white',
'placeholder:text-slate-400 disabled:bg-slate-100 disabled:text-slate-500',
props.className,
)}
/>
</label>
);
}
export function Select({ label, children, ...props }: any) {
return (
<label className="block">
{label && <span className="block text-xs font-medium text-slate-600 mb-1">{label}</span>}
<select {...props} className={cn(
'w-full px-3 py-2 text-sm rounded-md border border-slate-300 bg-slate-50',
'focus:outline-none focus:ring-2 focus:ring-blue-500 focus:bg-white',
props.className,
)}>
{children}
</select>
</label>
);
}
export function Button({ variant = 'primary', size = 'md', className, children, ...props }: any) {
const variants: Record<string, string> = {
primary: 'bg-blue-600 hover:bg-blue-700 text-white shadow-sm',
secondary: 'bg-slate-100 hover:bg-slate-200 text-slate-800 border border-slate-300',
ghost: 'bg-transparent hover:bg-slate-100 text-slate-700',
danger: 'bg-red-600 hover:bg-red-700 text-white',
};
const sizes: Record<string, string> = {
sm: 'px-2.5 py-1 text-xs',
md: 'px-4 py-2 text-sm',
lg: 'px-5 py-2.5 text-base',
};
return (
<button
{...props}
className={cn(
'rounded-md font-medium transition-colors disabled:opacity-50 disabled:cursor-not-allowed',
variants[variant],
sizes[size],
className,
)}
>
{children}
</button>
);
}
export function Toggle({ checked, onChange, label }: any) {
return (
<label className="inline-flex items-center gap-2 cursor-pointer">
<span className="relative">
<input type="checkbox" checked={checked} onChange={(e) => onChange(e.target.checked)} className="sr-only peer" />
<div className="w-9 h-5 bg-slate-300 rounded-full peer-checked:bg-blue-600 transition" />
<div className="absolute left-0.5 top-0.5 w-4 h-4 bg-white rounded-full shadow transition peer-checked:translate-x-4" />
</span>
<span className="text-sm text-slate-700">{label}</span>
</label>
);
}
export function Banner({ level = 'info', children }: { level: 'info' | 'success' | 'warning' | 'danger'; children: React.ReactNode }) {
const styles: Record<string, string> = {
info: 'bg-blue-50 border-blue-200 text-blue-800',
success: 'bg-green-50 border-green-200 text-green-800',
warning: 'bg-yellow-50 border-yellow-200 text-yellow-800',
danger: 'bg-red-50 border-red-200 text-red-800',
};
return (
<div className={cn('border rounded-lg px-4 py-3 text-sm', styles[level])}>
{children}
</div>
);
}
export function Stat({ label, value, hint }: { label: string; value: any; hint?: string }) {
return (
<div className="bg-white rounded-xl border border-slate-200 p-4 shadow-sm">
<div className="text-xs text-slate-500 mb-1">{label}</div>
<div className="text-xl font-bold text-slate-900">{value}</div>
{hint && <div className="text-xs text-slate-400 mt-1">{hint}</div>}
</div>
);
}