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 (
{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 ? (
) : (
)
}
/>
}
/>
}
/>
) : (
)
}
/>
) : (
)
}
/>
) : (
)
}
/>
) : (
)
}
/>
) : (
)
}
/>
: }
/>
) : (
)
}
/>
} />
>
)
}