Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,20 @@ src/
└── utils/ # Pure utility functions
```

### Dashboard routes

`/dashboard` is the single primary prediction terminal. It now includes the
high-value panels that previously lived only on the legacy `/play` view: the
price chart, the round lifecycle timeline, round-update connection status, the
end-round modal, and opt-in community chat (toggled from the dashboard header so
the default terminal stays uncluttered).

`/play` is **deprecated** and permanently redirects to `/dashboard`. The
`LegacyDashboard` component is retained only for reference and is no longer
routed. Chat and notifications are available via the in-dashboard toggle; further
social/notification consolidation is tracked in issue
[#130](https://github.com/TevaLabs/Xelma-Frontend/issues/130).

---

## Testing
Expand Down
47 changes: 23 additions & 24 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { lazy, Suspense } from 'react';
import { Routes, Route, useLocation } from 'react-router-dom';
import { Routes, Route, Navigate, useLocation } from 'react-router-dom';
import { Toaster } from 'sonner';
import Navbar from './components/Navbar';
import PageSkeleton from './components/PageSkeleton';
Expand All @@ -11,10 +11,9 @@
import Footer from './components/Footer';
import ComingSoonPage from './pages/ComingSoonPage';
import { Trophy } from 'lucide-react';
import { ENTER } from './utils/motion';

Check failure on line 14 in src/App.tsx

View workflow job for this annotation

GitHub Actions / Lint and Test

'ENTER' is defined but never used

const Dashboard = lazy(() => import(/* webpackChunkName: "dashboard" */ './pages/Dashboard'));
const LegacyDashboard = lazy(() => import(/* webpackChunkName: "legacy-dashboard" */ './pages/LegacyDashboard'));
const Leaderboard = lazy(() => import(/* webpackChunkName: "leaderboard" */ './components/Leaderboard'));
const LearnPage = lazy(() => import(/* webpackChunkName: "learn" */ './pages/Learn'));
const Connect = lazy(() => import('./pages/Connect'));
Expand All @@ -40,28 +39,28 @@
<ErrorBoundary>
<LazyBoundary>
<Suspense fallback={<RouteFallback />}>
<main id="main-content" key={pathname} className={ENTER}>
<Routes>
<Route path="/" element={<Landing />} />
<Route path="/dashboard" element={<Suspense fallback={<PageSkeleton type="dashboard" />}><Dashboard /></Suspense>} />
<Route path="/play" element={<Suspense fallback={<PageSkeleton type="legacy" />}><LegacyDashboard /></Suspense>} />
<Route path="/leaderboard" element={<Suspense fallback={<PageSkeleton type="leaderboard" />}><Leaderboard /></Suspense>} />
<Route path="/learn" element={<Suspense fallback={<PageSkeleton type="learn" />}><LearnPage /></Suspense>} />
<Route path="/connect" element={<Connect />} />
<Route path="/pools" element={<Pools />} />
<Route
path="/tournament"
element={
<ComingSoonPage
icon={Trophy}
title="Tournament"
description="Competitive tournament mode is being built. Check back soon to compete for top rankings and exclusive rewards."
/>
}
/>
<Route path="/profile" element={<Profile />} />
</Routes>
</main>
<Routes>
<Route path="/" element={<Landing />} />
<Route path="/dashboard" element={<Suspense fallback={<PageSkeleton type="dashboard" />}><Dashboard /></Suspense>} />
{/* /play is deprecated: its high-value panels (price chart, round
timeline, chat, end-round modal) now live in /dashboard. */}
<Route path="/play" element={<Navigate to="/dashboard" replace />} />
<Route path="/leaderboard" element={<Suspense fallback={<PageSkeleton type="leaderboard" />}><Leaderboard /></Suspense>} />
<Route path="/learn" element={<Suspense fallback={<PageSkeleton type="learn" />}><LearnPage /></Suspense>} />
<Route path="/connect" element={<Connect />} />
<Route path="/pools" element={<Pools />} />
<Route
path="/tournament"
element={
<ComingSoonPage
icon={Trophy}
title="Tournament"
description="Competitive tournament mode is being built. Check back soon to compete for top rankings and exclusive rewards."
/>
}
/>
<Route path="/profile" element={<Profile />} />
</Routes>
</Suspense>
</LazyBoundary>
</ErrorBoundary>
Expand Down
52 changes: 51 additions & 1 deletion src/pages/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
import type { PredictionData } from "../components/PredictionControls";
import BetModal from "../components/BetModal";
import EndRoundModal from "../components/EndRoundModal";
import RoundTimeline from "../components/RoundTimeline";
import { ChatSidebar } from "../components/ChatSidebar";
import { ConnectionStatus } from "../components/ConnectionStatus";
import { useConnectionStatus } from "../hooks/useConnectionStatus";
import { useRoundStore } from "../store/useRoundStore";
import type { Round } from "../lib/api-client";
import { educationApi } from "../lib/api-client";
Expand Down Expand Up @@ -90,16 +94,20 @@
const Dashboard = () => {
const isRoundActive = useRoundStore((state) => state.isRoundActive);
const isLoading = useRoundStore((state) => state.isLoading);
const sseConnection = useRoundStore((state) => state.sseConnection);
const isWalletConnected = useWalletStore(selectIsWalletConnected);
const isWalletConnecting = useWalletStore(
(s) => s.status === "connecting" || s.status === "checking"
);
const resolvedRound = useRoundStore((state) => state.resolvedRound);
const dismissResolvedRound = useRoundStore((state) => state.dismissResolvedRound);
const publicKey = useWalletStore((s) => s.publicKey);
const balance = useWalletStore((s) => s.balance);
const { isConnected: isSocketConnected } = useConnectionStatus();
const [isBetModalOpen, setIsBetModalOpen] = useState(false);
const [pendingPrediction, setPendingPrediction] = useState<PredictionData | null>(null);
// Community chat is opt-in so the default terminal stays uncluttered.
const [isChatOpen, setIsChatOpen] = useState(false);
const timeoutRef = useRef<number | null>(null);

Check failure on line 110 in src/pages/Dashboard.tsx

View workflow job for this annotation

GitHub Actions / Lint and Test

'timeoutRef' is assigned a value but never used

useEffect(() => {
const { fetchActiveRound, subscribeToRoundEvents } = useRoundStore.getState();
Expand Down Expand Up @@ -151,9 +159,51 @@

return (
<div className="xelma-grid-bg min-h-screen px-4 py-8 sm:px-6 lg:px-8">
{/* Opt-in community chat (ported from the legacy /play view). Self-positions
as a fixed slide-over, so mounting it does not shift the terminal layout. */}
{isChatOpen && <ChatSidebar />}

<div className="mx-auto max-w-7xl">
{isLoading && <DashboardSkeleton />}

{!isLoading && (
<div className="mb-4 flex items-center justify-end">
<button
type="button"
onClick={() => setIsChatOpen((open) => !open)}
aria-pressed={isChatOpen}
className="btn-ghost inline-flex min-h-[40px] items-center gap-2 rounded-lg px-4 py-2 text-sm font-semibold"
>
{isChatOpen ? "Hide community chat" : "Community chat"}
</button>
</div>
)}

{/* Round-update connectivity, ported from /play so users see SSE/socket health. */}
{!isLoading &&
(!isSocketConnected ||
(sseConnection && sseConnection.status !== "connected")) && (
<div className="mb-4">
<ConnectionStatus />
{sseConnection &&
sseConnection.status !== "connected" &&
sseConnection.error && (
<div className="mt-2 rounded-lg border border-yellow-200 bg-yellow-50 p-3 dark:border-yellow-800 dark:bg-yellow-900/20">
<p className="text-sm text-yellow-800 dark:text-yellow-200">
Round updates: {sseConnection.error}
</p>
</div>
)}
</div>
)}

{/* Round lifecycle timeline, ported from /play. */}
{!isLoading && (
<div className="mb-6">
<RoundTimeline />
</div>
)}

{!isLoading && !isWalletConnected && (
<div className="mb-6 flex flex-col gap-3 rounded-xl border border-[#2C4BFD]/30 bg-[#2C4BFD]/10 p-4 text-sm text-[#BEC7FE] sm:flex-row sm:items-center sm:justify-between sm:gap-4 sm:px-5 sm:py-4">
<p className="leading-relaxed" data-testid="dashboard-wallet-prompt">
Expand Down
Loading