import { type ReactNode, useEffect, useState } from 'react' import { AnimatePresence, motion } from 'framer-motion' import useSWR from 'swr' import { Navigate, Route, Routes, useLocation, useNavigate, useSearchParams, } from 'react-router-dom' import HeaderBar from '../components/common/HeaderBar' import { SiteFooter } from '../components/common/SiteFooter' import { LoginRequiredOverlay } from '../components/auth/LoginRequiredOverlay' import { LoginPage } from '../components/auth/LoginPage' import { RegisterPage } from '../components/auth/RegisterPage' import { ResetPasswordPage } from '../components/auth/ResetPasswordPage' import { SetupPage } from '../components/modals/SetupPage' import { CompetitionPage } from '../components/trader/CompetitionPage' import { AITradersPage } from '../components/trader/AITradersPage' import { TraderLaunchGuestPage } from '../components/trader/TraderLaunchGuestPage' import { FAQPage } from '../pages/FAQPage' import { LandingPage } from '../pages/LandingPage' import { BeginnerOnboardingPage } from '../pages/BeginnerOnboardingPage' import { DataPage } from '../pages/DataPage' import { SettingsPage } from '../pages/SettingsPage' import { StrategyStudioPage } from '../pages/StrategyStudioPage' import { TerminalDashboard } from '../components/terminal/TerminalDashboard' import { useAuth } from '../contexts/AuthContext' import { useLanguage } from '../contexts/LanguageContext' import { useSystemConfig } from '../hooks/useSystemConfig' import { t } from '../i18n/translations' import { api } from '../lib/api' import type { AccountInfo, DecisionRecord, Position, SystemStatus, TraderInfo, } from '../types' import { buildDashboardPath, LEGACY_HASH_ROUTES, ROUTES, type Page, } from './paths' function getTraderSlug(trader: TraderInfo) { const idPrefix = trader.trader_id.slice(0, 4) return `${trader.trader_name}-${idPrefix}` } function findTraderBySlug(slug: string, traderList: TraderInfo[]) { const lastDashIndex = slug.lastIndexOf('-') if (lastDashIndex === -1) { return traderList.find((trader) => trader.trader_name === slug) } const name = slug.slice(0, lastDashIndex) const idPrefix = slug.slice(lastDashIndex + 1) return traderList.find( (trader) => trader.trader_name === name && trader.trader_id.startsWith(idPrefix) ) } function LoadingScreen() { const { language } = useLanguage() return (
NoFx Logo

{t('loading', language)}

) } function LegacyHashRedirect() { const location = useLocation() const navigate = useNavigate() useEffect(() => { const hashRoute = LEGACY_HASH_ROUTES[location.hash.slice(1)] if (!hashRoute) { return } if (hashRoute === location.pathname && location.hash === '') { return } navigate( { pathname: hashRoute, search: location.search, }, { replace: true } ) }, [location.hash, location.pathname, location.search, navigate]) return null } interface AppChromeProps { children: ReactNode currentPage?: Page showFooter?: boolean wrapInMain?: boolean animateContent?: boolean extraContent?: ReactNode } function AppChrome({ children, currentPage, showFooter = true, wrapInMain = true, animateContent = false, extraContent, }: AppChromeProps) { const location = useLocation() const { language, setLanguage } = useLanguage() const { user, logout } = useAuth() const [loginOverlayOpen, setLoginOverlayOpen] = useState(false) const [loginOverlayFeature, setLoginOverlayFeature] = useState('') const handleLoginRequired = (featureName: string) => { setLoginOverlayFeature(featureName) setLoginOverlayOpen(true) } const content = animateContent ? ( {children} ) : ( children ) return (
{wrapInMain ? (
{content}
) : ( content )} {showFooter ? : null} setLoginOverlayOpen(false)} featureName={loginOverlayFeature} /> {extraContent}
) } function TradersRoute({ showBeginnerOnboarding = false, }: { showBeginnerOnboarding?: boolean }) { const navigate = useNavigate() const { user, token } = useAuth() const { data: traders } = useSWR( user && token ? 'traders-route' : null, api.getTraders, { refreshInterval: 5000, shouldRetryOnError: false, } ) return ( : null} > { const trader = traders?.find((item) => item.trader_id === traderId) navigate( buildDashboardPath(trader ? getTraderSlug(trader) : undefined) ) }} /> ) } function DashboardRoute() { const { user, token } = useAuth() const navigate = useNavigate() const [searchParams] = useSearchParams() const selectedTraderSlug = searchParams.get('trader') || undefined const [selectedTraderId, setSelectedTraderId] = useState() const [, setLastUpdate] = useState('--:--:--') const [decisionsLimit] = useState(5) const [accountPollOff, setAccountPollOff] = useState(false) const [positionsPollOff, setPositionsPollOff] = useState(false) const [decisionsPollOff, setDecisionsPollOff] = useState(false) useEffect(() => { setAccountPollOff(false) setPositionsPollOff(false) setDecisionsPollOff(false) }, [selectedTraderId]) const { data: traders } = useSWR( user && token ? 'traders-dashboard' : null, () => api.getTraders(true), { refreshInterval: 10000, shouldRetryOnError: false, } ) useEffect(() => { if (!traders || traders.length === 0) { return } if (selectedTraderSlug) { const trader = findTraderBySlug(selectedTraderSlug, traders) const nextTraderId = trader?.trader_id || traders[0].trader_id if (nextTraderId !== selectedTraderId) { setSelectedTraderId(nextTraderId) } return } if (!selectedTraderId) { setSelectedTraderId(traders[0].trader_id) } }, [selectedTraderId, selectedTraderSlug, traders]) const { data: status } = useSWR( selectedTraderId ? `status-${selectedTraderId}` : null, () => api.getStatus(selectedTraderId, true), { refreshInterval: 15000, revalidateOnFocus: false, dedupingInterval: 10000, } ) const { data: account } = useSWR( selectedTraderId ? `account-${selectedTraderId}` : null, () => api.getAccount(selectedTraderId, true), { refreshInterval: accountPollOff ? 0 : 15000, revalidateOnFocus: false, dedupingInterval: 10000, onErrorRetry: (_err, _key, _config, revalidate, { retryCount }) => { if (retryCount >= 2) { setAccountPollOff(true) return } setTimeout(() => revalidate({ retryCount }), 500) }, onSuccess: () => { if (accountPollOff) { setAccountPollOff(false) } }, } ) const { data: positions } = useSWR( selectedTraderId ? `positions-${selectedTraderId}` : null, () => api.getPositions(selectedTraderId, true), { refreshInterval: positionsPollOff ? 0 : 15000, revalidateOnFocus: false, dedupingInterval: 10000, onErrorRetry: (_err, _key, _config, revalidate, { retryCount }) => { if (retryCount >= 2) { setPositionsPollOff(true) return } setTimeout(() => revalidate({ retryCount }), 500) }, onSuccess: () => { if (positionsPollOff) { setPositionsPollOff(false) } }, } ) const { data: decisions } = useSWR( selectedTraderId ? `decisions/latest-${selectedTraderId}-${decisionsLimit}` : null, () => api.getLatestDecisions(selectedTraderId, decisionsLimit, true), { refreshInterval: decisionsPollOff ? 0 : 30000, revalidateOnFocus: false, dedupingInterval: 20000, onErrorRetry: (_err, _key, _config, revalidate, { retryCount }) => { if (retryCount >= 2) { setDecisionsPollOff(true) return } setTimeout(() => revalidate({ retryCount }), 500) }, onSuccess: () => { if (decisionsPollOff) { setDecisionsPollOff(false) } }, } ) useEffect(() => { if (account) { setLastUpdate(new Date().toLocaleTimeString()) } }, [account]) const selectedTrader = traders?.find( (trader) => trader.trader_id === selectedTraderId ) return ( { setSelectedTraderId(traderId) const trader = traders?.find((item) => item.trader_id === traderId) navigate( buildDashboardPath(trader ? getTraderSlug(trader) : undefined), { replace: true, } ) }} /> ) } export function AppRoutes() { const { user, token, isLoading } = useAuth() const { config: systemConfig, loading: configLoading } = useSystemConfig() const isAuthenticated = !!user && !!token if (isLoading || configLoading) { return } if (systemConfig && !systemConfig.initialized && !user) { return } return ( <> } /> } /> } /> } /> ) : systemConfig?.initialized ? ( ) : ( ) } /> } /> } /> ) : ( ) } /> ) : ( ) } /> ) : ( ) } /> ) : ( ) } /> ) : ( ) } /> : } /> ) : ( ) } /> } /> ) }