모의투자 로직 변경

This commit is contained in:
Macbook
2026-05-31 14:05:46 +09:00
parent ead97dad5d
commit d6eedf19bb
33 changed files with 1456 additions and 330 deletions
@@ -0,0 +1,23 @@
import React from 'react';
import { FORMAL_LOGIN_REQUIRED_MSG } from '../utils/tradingAccess';
interface Props {
onLogin: () => void;
title?: string;
}
/** 게스트·비로그인 — 매매 관련 화면 대체 */
export const FormalLoginGate: React.FC<Props> = ({
onLogin,
title = '정식 로그인이 필요합니다',
}) => (
<div className="formal-login-gate">
<h2 className="formal-login-gate__title">{title}</h2>
<p className="formal-login-gate__desc">{FORMAL_LOGIN_REQUIRED_MSG}</p>
<button type="button" className="formal-login-gate__btn" onClick={onLogin}>
</button>
</div>
);
export default FormalLoginGate;
+18 -2
View File
@@ -33,6 +33,7 @@ import {
import TradeAlertPopupPreview from './TradeAlertPopupPreview';
import TrendSearchSettingsPanel from './trendSearch/TrendSearchSettingsPanel';
import type { TrendSearchAppSettings } from '../utils/trendSearchAppSettings';
import { isTradingSettingsCategory } from '../utils/tradingAccess';
import {
CHART_LEGEND_SETTING_ITEMS,
type ChartLegendVisibility,
@@ -168,6 +169,9 @@ interface SettingsPageProps {
onTrendSearchSettingsChange?: (s: TrendSearchAppSettings) => void;
/** 설정 화면 진입 시 선택할 카테고리 (예: 모의투자 화면에서 링크) */
initialCategory?: SettingsCategoryId;
/** 정식 로그인 여부 (게스트는 매매·전략 설정 탭 차단) */
isFormalLogin?: boolean;
onRequireFormalLogin?: () => void;
}
// ── 카테고리 정의 ────────────────────────────────────────────────────────────
@@ -1743,6 +1747,8 @@ const SettingsPage: React.FC<SettingsPageProps> = ({
trendSearchSettings,
onTrendSearchSettingsChange,
initialCategory,
isFormalLogin = true,
onRequireFormalLogin,
}) => {
const categories = filterCategories(menuPermissions);
const [active, setActive] = useState<CategoryId>(initialCategory ?? 'general');
@@ -1757,10 +1763,14 @@ const SettingsPage: React.FC<SettingsPageProps> = ({
useEffect(() => {
if (!initialCategory) return;
if (!isFormalLogin && isTradingSettingsCategory(initialCategory)) {
onRequireFormalLogin?.();
return;
}
if (categories.some(c => c.id === initialCategory)) {
setActive(initialCategory);
}
}, [initialCategory, categories]);
}, [initialCategory, categories, isFormalLogin, onRequireFormalLogin]);
useEffect(() => {
if (active !== 'indicators') setIndicatorSaveUi(null);
@@ -1948,7 +1958,13 @@ const SettingsPage: React.FC<SettingsPageProps> = ({
<button
key={cat.id}
className={`stg-sidebar-item ${active === cat.id ? 'stg-sidebar-item--active' : ''}`}
onClick={() => setActive(cat.id)}
onClick={() => {
if (!isFormalLogin && isTradingSettingsCategory(cat.id)) {
onRequireFormalLogin?.();
return;
}
setActive(cat.id);
}}
>
<span className="stg-sidebar-icon">{cat.icon}</span>
<span className="stg-sidebar-label" style={{ fontSize: 13.5, fontWeight: 'inherit' }}>{cat.label}</span>
+2 -2
View File
@@ -3,6 +3,7 @@
*/
import React, { useState } from 'react';
import { loginUser, type LoginResponse } from '../utils/backendApi';
import SplashGradientBackdrop from './splash/SplashGradientBackdrop';
import '../styles/splashScreen.css';
const DEFAULT_USERNAME = 'admin';
@@ -36,8 +37,7 @@ const SplashScreen: React.FC<Props> = ({ onLoginSuccess, onGuest }) => {
return (
<div className="splash">
<div className="splash-photo-bg" aria-hidden />
<div className="splash-photo-overlay" aria-hidden />
<SplashGradientBackdrop />
<div className="splash-center">
<div className="splash-glass">
@@ -0,0 +1,39 @@
/**
* 로그인 스플래시 — 중앙→우상단 대각선으로 흐릿한 캔들·호가 실루엣 + 메시 그라데이션
*/
import React from 'react';
import SplashChartBackground from './SplashChartBackground';
import SplashOrderbookBackground from './SplashOrderbookBackground';
const SplashGradientBackdrop: React.FC = () => (
<div className="splash-backdrop" aria-hidden>
<div className="splash-backdrop__base" />
<div className="splash-backdrop__mesh" />
<div className="splash-backdrop__orbs">
<span className="splash-backdrop__orb splash-backdrop__orb--gold" />
<span className="splash-backdrop__orb splash-backdrop__orb--teal" />
<span className="splash-backdrop__orb splash-backdrop__orb--indigo" />
</div>
{/* 캔들 + 호가 — 화면 중앙에서 우측 상단으로 퍼지는 그라데이션 마스크 */}
<div className="splash-backdrop__market-flow">
<div className="splash-backdrop__flow-glow" />
<div className="splash-backdrop__flow-inner">
<div className="splash-backdrop__chart-layer">
<SplashChartBackground />
</div>
<div className="splash-backdrop__orderbook-layer">
<SplashOrderbookBackground />
</div>
</div>
</div>
<div className="splash-backdrop__grid" />
<div className="splash-backdrop__grain" />
<div className="splash-backdrop__vignette" />
<div className="splash-backdrop__spotlight" />
</div>
);
export default SplashGradientBackdrop;
@@ -0,0 +1,69 @@
import React, { useMemo } from 'react';
const W = 420;
const H = 560;
const ROWS = 14;
const MID = H / 2;
/** 호가창 실루엣 — 로그인 배경용 */
export default function SplashOrderbookBackground() {
const rows = useMemo(() => {
const out: { y: number; askW: number; bidW: number; askPx: number; bidPx: number }[] = [];
for (let i = 0; i < ROWS; i++) {
const t = i / (ROWS - 1);
const y = 36 + t * (H - 72);
const dist = Math.abs(y - MID) / MID;
const spread = 0.35 + dist * 0.65;
const askW = 40 + (1 - dist) * 90 + Math.sin(i * 1.1) * 12;
const bidW = 38 + (1 - dist) * 85 + Math.cos(i * 0.9) * 10;
const base = 118000 + (0.5 - t) * 4200;
out.push({
y,
askW,
bidW,
askPx: base + spread * 120,
bidPx: base - spread * 115,
});
}
return out;
}, []);
return (
<svg
className="splash-orderbook-bg"
viewBox={`0 0 ${W} ${H}`}
preserveAspectRatio="xMidYMid meet"
aria-hidden
>
<rect width={W} height={H} fill="transparent" />
<line x1={W / 2} y1={28} x2={W / 2} y2={H - 28} className="splash-ob-mid" />
{rows.map((r, i) => (
<g key={i}>
<rect
x={W / 2 - r.askW}
y={r.y - 9}
width={r.askW}
height={18}
className="splash-ob-ask-bar"
rx="2"
/>
<rect
x={W / 2}
y={r.y - 9}
width={r.bidW}
height={18}
className="splash-ob-bid-bar"
rx="2"
/>
<text x={W / 2 - 10} y={r.y + 4} className="splash-ob-ask-txt" textAnchor="end">
{Math.round(r.askPx).toLocaleString()}
</text>
<text x={W / 2 + 10} y={r.y + 4} className="splash-ob-bid-txt" textAnchor="start">
{Math.round(r.bidPx).toLocaleString()}
</text>
</g>
))}
<rect x={W / 2 - 72} y={MID - 14} width={144} height={28} className="splash-ob-spread" rx="4" />
</svg>
);
}