Files
goldenChart/app/src/App.tsx
T
2026-05-28 20:48:28 +09:00

132 lines
4.1 KiB
TypeScript

import React, { lazy, Suspense, useEffect, useState } from 'react';
import { Capacitor } from '@capacitor/core';
import { StatusBar, Style } from '@capacitor/status-bar';
import { SplashScreen as CapSplashScreen } from '@capacitor/splash-screen';
import { AuthProvider, useAuth } from './contexts/AuthContext';
import { NavigationProvider, useNavigation } from './contexts/NavigationContext';
import { TradeNotificationProvider, useTradeNotification } from './contexts/TradeNotificationContext';
import { useAppSettings, resolveAppDefaults } from './hooks/useAppSettings';
import TabBar, { type TabId } from './components/TabBar';
import LiveSignalBridge from './components/LiveSignalBridge';
import LoginScreen from './screens/LoginScreen';
import '@frontend/styles/splashScreen.css';
import './theme/global.css';
const VirtualTradingScreen = lazy(() => import('./screens/virtual/VirtualTradingScreen'));
const StrategyEditorScreen = lazy(() => import('./screens/strategy/StrategyEditorScreen'));
const NotificationsScreen = lazy(() => import('./screens/notifications/NotificationsScreen'));
const SettingsScreen = lazy(() => import('./screens/settings/SettingsScreen'));
function ToastStack() {
const { toastNotifications, dismissToast } = useTradeNotification();
if (toastNotifications.length === 0) return null;
return (
<div className="toast-stack">
{toastNotifications.slice(0, 3).map(t => (
<div key={t.id} className="toast-item" role="button" tabIndex={0} onClick={() => dismissToast(t.id)}>
<strong className={t.signalType === 'BUY' ? 'text-green' : 'text-red'}>
{t.signalType === 'BUY' ? '매수' : '매도'}
</strong>
{' '}{t.market} · {t.price?.toLocaleString()}
</div>
))}
</div>
);
}
function MainApp() {
const { tab, setTab, openVirtualFocus, goVirtualList, goNotifyList } = useNavigation();
const { unreadCount } = useTradeNotification();
const { sessionKey } = useAuth();
const { settings, isLoaded } = useAppSettings(sessionKey);
const defaults = resolveAppDefaults(settings);
/** FCM/알림 권한: 로그인·메인 진입 시 호출하지 않음 — 설정 버튼에서만 */
useEffect(() => {
document.documentElement.setAttribute('data-theme', defaults.theme ?? 'dark');
}, [defaults.theme]);
useEffect(() => {
if (tab === 'virtual') goNotifyList();
else if (tab === 'notifications') goVirtualList();
}, [tab, goVirtualList, goNotifyList]);
const screens: Record<TabId, React.ReactNode> = {
virtual: <VirtualTradingScreen />,
strategy: <StrategyEditorScreen />,
notifications: <NotificationsScreen />,
settings: <SettingsScreen />,
};
return (
<div className="app-shell">
<LiveSignalBridge />
<ToastStack />
<main className="app-content">
<Suspense fallback={<div className="loading-center">로딩…</div>}>
{screens[tab]}
</Suspense>
</main>
<TabBar active={tab} onChange={setTab} unreadCount={unreadCount} />
</div>
);
}
function AppRoot() {
const {
authReady,
isAppEntered,
handleLoginSuccess,
handleGuestEnter,
sessionKey,
} = useAuth();
const [nativeReady, setNativeReady] = useState(false);
useEffect(() => {
void (async () => {
if (Capacitor.isNativePlatform()) {
try {
await StatusBar.setStyle({ style: Style.Dark });
await StatusBar.setBackgroundColor({ color: '#0f0f23' });
await CapSplashScreen.hide();
} catch { /* web dev */ }
}
setNativeReady(true);
})();
}, []);
if (!authReady || !nativeReady) {
return <div className="loading-center">GoldenChart</div>;
}
if (!isAppEntered) {
return (
<LoginScreen
onLoginSuccess={handleLoginSuccess}
onGuest={handleGuestEnter}
/>
);
}
return (
<NavigationProvider>
<TradeNotificationProvider
soundEnabled
popupEnabled={false}
settingsSessionKey={sessionKey}
>
<MainApp />
</TradeNotificationProvider>
</NavigationProvider>
);
}
export default function App() {
return (
<AuthProvider>
<AppRoot />
</AuthProvider>
);
}