-
Notifications
You must be signed in to change notification settings - Fork 0
Feature/front end #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…wind CSS - Added package.json with dependencies for Next.js, React, and Recharts. - Created postcss.config.mjs for Tailwind CSS integration. - Added SVG assets for UI components (file.svg, globe.svg, next.svg, vercel.svg, window.svg). - Configured TypeScript with strict settings and Next.js support in tsconfig.json. - Basic Layout done
WalkthroughAdds a Docker-based multi-service setup and a new Next.js TypeScript trading dashboard client with UI components, hooks, utilities, types, and tooling; plus a Node.js/TypeScript market-trading-service with Docker configuration and documentation. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant UI as Dashboard UI
participant Hook as useTradingData
participant API as Market API
participant Cache as React Query
participant Chart as PriceChart
User->>UI: Open dashboard
UI->>Hook: init()
Hook->>API: GET /tickers (poll every 2s)
API-->>Hook: tickers[]
Hook->>Cache: update tickers cache
Hook->>UI: provide tickers, priceStatus
UI->>UI: auto-select first ticker (if none)
User->>UI: select ticker
UI->>Hook: setSelectedTicker
Hook->>API: GET /chart?symbol=&days=
API-->>Hook: historical data
Hook->>Cache: update chart cache
Hook->>UI: chartData
UI->>Chart: render chart with chartData
Note over Hook,API: on fetch error -> fallback to MOCK_TICKERS / generated mock history
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 19
🧹 Nitpick comments (14)
client/trading-dashboard/tsconfig.json (1)
2-24: Configuration follows Next.js best practices and is well-structured.The compiler options are solid:
strict: trueenables comprehensive type checking,jsx: "react-jsx"aligns with React 19,moduleResolution: "bundler"is appropriate for Next.js build systems, andincremental: trueoptimizes rebuild times. The Next.js plugin andisolatedModules: trueensure compatibility with Next.js's compilation model.Optional consideration: If targeting only modern browsers or deploying with a specific Node.js LTS version, you may want to increase
targetfrom ES2017 to ES2020 or ES2022 for better JavaScript feature support (e.g., optional chaining, nullish coalescing). However, ES2017 is a reasonable choice for broader compatibility—verify this aligns with your deployment environment and browser support requirements.client/trading-dashboard/README.md (1)
1-36: Consider adding project-specific documentation.This is the standard Next.js boilerplate README. Consider enhancing it with:
- Trading dashboard overview and features
- Environment variable configuration (API_BASE_URL, etc.)
- Reference to README.DOCKER.md for containerized setup
- Links to the market-trading-service backend
client/trading-dashboard/lib/utils.ts (1)
19-24: Consider using consistent formatting approach.
formatLargeNumberuses manual string concatenation whileformatCurrencyusesIntl.NumberFormat. For consistency and better i18n support, consider usingIntl.NumberFormatwithnotation: "compact"for large numbers as well.Apply this diff to use a consistent formatting approach:
export const formatLargeNumber = (value: number): string => { - if (value >= 1e12) return `$${(value / 1e12).toFixed(2)}T`; - if (value >= 1e9) return `$${(value / 1e9).toFixed(2)}B`; - if (value >= 1e6) return `$${(value / 1e6).toFixed(2)}M`; - return `$${value.toFixed(2)}`; + return new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + notation: "compact", + minimumFractionDigits: 0, + maximumFractionDigits: 2, + }).format(value); };README.DOCKER.md (1)
17-18: Format URLs as Markdown links to satisfy markdownlintStatic analysis flags the bare URLs here (MD034). Consider wrapping them as proper Markdown links, e.g.
<http://localhost:3000>or[client](http://localhost:3000), to keep linters green without changing behavior.client/trading-dashboard/Dockerfile (1)
1-25: Reorder COPY / install steps to improve Docker layer cachingRight now
COPY . .happens beforenpm ci, so any source change invalidates the dependency layer and forces a full reinstall. You can speed rebuilds by installing dependencies before copying the full app:FROM node:20-alpine AS builder WORKDIR /app -# Install deps and build -COPY package*.json next.config.ts tsconfig.json ./ -COPY . . -RUN npm ci --silent && npm run build +# Install deps and then build +COPY package*.json next.config.ts tsconfig.json ./ +RUN npm ci --silent +COPY . . +RUN npm run buildThis keeps dependency installation cached as long as package files don’t change.
server/market-trading-service/Dockerfile (1)
1-23: Optimize Docker build cache by separating dependency install from source copyIn the builder stage,
npm ciruns after copyingsrc, so any code change forces a full reinstall. Consider splitting steps so dependency installation is only invalidated when package files change:# Install deps and build -COPY package*.json tsconfig*.json ./ -COPY src ./src -RUN npm ci --silent && npm run build +COPY package*.json ./ +RUN npm ci --silent +COPY tsconfig*.json ./ +COPY src ./src +RUN npm run buildThis keeps builds faster while preserving the final image behavior.
client/trading-dashboard/components/Header.tsx (1)
6-45: Header implementation is good; consider more flexible active-state logicOverall structure and styling look good, and using
usePathnamefor active nav works. One small improvement would be to treat nested dashboard routes as active too:className={`nav-link ${ pathname.startsWith("/dashboard") ? "bg-primary/10 text-primary" : "" }`}Also, just confirm that your Tailwind v4 setup recognizes
border/40as intended for the header border color; if not, you may want a more explicit token (e.g., a named border color with/40opacity).client/trading-dashboard/app/page.tsx (1)
5-13: MakeIndexsynchronous and consider using<main>for semantics
Indexis markedasyncbut never awaits anything, so it can be a plain synchronous component. Also, wrapping the content in a<main>element improves document semantics while keeping the structure the same.You could do:
-const Index = async () => { - return ( - <div className="min-h-screen bg-background"> +const Index = () => { + return ( + <main className="min-h-screen bg-background"> <Hero /> <TickerGrid /> <NewsFeed /> - </div> + </main> ); };client/trading-dashboard/components/NewsFeed.tsx (2)
59-68: Simplify the timer pattern and clarify intent.The nested
setTimeoutinsidesetIntervaladds unnecessary complexity and can cause timing issues. The 500ms delay appears to serve no functional purpose beyond a "visual delay," but this creates a gap where the interval might fire again before the previous timeout completes, potentially queuing multiple shuffles.Additionally, the comment "Auto-refresh" is misleading—this code shuffles existing data rather than fetching fresh data.
Apply this diff to simplify:
useEffect(() => { const interval = setInterval(() => { - // Simulate slight delay (optional) - setTimeout(() => { - setNews((prevNews) => [...prevNews].sort(() => Math.random() - 0.5)); - }, 500); // optional internal visual delay - }, 2000); // Auto-refresh every 2 seconds + setNews((prevNews) => [...prevNews].sort(() => Math.random() - 0.5)); + }, 2000); // Re-shuffle news items every 2 seconds return () => clearInterval(interval); // Clean up on unmount }, []);If you genuinely need fresh news data, consider fetching from an API instead of shuffling static content.
63-63: Consider using a proper shuffle algorithm.Using
.sort(() => Math.random() - 0.5)is a common but flawed shuffle approach—it doesn't produce a uniform distribution and has O(n log n) complexity.For a proper shuffle, use the Fisher-Yates algorithm:
const shuffleArray = <T,>(array: T[]): T[] => { const shuffled = [...array]; for (let i = shuffled.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; } return shuffled; }; // Then in useEffect: setNews((prevNews) => shuffleArray(prevNews));client/trading-dashboard/components/TickerCard.tsx (1)
31-38: Consider simplifying the sign display logic.The conditional logic for adding the "+" prefix is duplicated across the change value and percentage. You could simplify this by extracting a helper or using a more concise approach.
Apply this diff:
- <div - className={`flex items-center gap-2 text-sm font-medium ${ - isPositive ? "text-success" : "text-destructive" - }`} - > - <span> - {isPositive ? "+" : ""} - {change.toFixed(2)} - </span> - <span> - ({isPositive ? "+" : ""} - {changePercent.toFixed(2)}%) - </span> + <div + className={`flex items-center gap-2 text-sm font-medium ${ + isPositive ? "text-success" : "text-destructive" + }`} + > + <span>{isPositive ? "+" : ""}{change.toFixed(2)}</span> + <span>({isPositive ? "+" : ""}{changePercent.toFixed(2)}%)</span> </div>Or extract a formatting helper:
const formatChange = (value: number) => `${value >= 0 ? "+" : ""}${value.toFixed(2)}`; // Then use: <span>{formatChange(change)}</span> <span>({formatChange(changePercent)}%)</span>client/trading-dashboard/components/PriceChart.tsx (1)
86-100: Consider extracting button styles to improve readability.The button's
classNamestring spans multiple lines with complex conditional logic, making it difficult to read and maintain.Extract the button styling logic:
const getButtonClassName = (isSelected: boolean) => { const baseStyles = "px-4 py-2 text-sm font-semibold rounded-md transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-blue-600 focus:ring-offset-1 mr-2 last:mr-0"; if (isSelected) { return `${baseStyles} bg-blue-600 text-white`; } return isDarkMode ? `${baseStyles} text-gray-300 hover:bg-gray-600` : `${baseStyles} text-gray-600 hover:bg-gray-200`; }; // Then use: <button key={days} onClick={() => onChartDaysChange(days)} className={getButtonClassName(chartDays === days)} aria-pressed={chartDays === days} > {label} </button>client/trading-dashboard/components/ui/LoadingSpinner.tsx (1)
4-20: LoadingSpinner is solid; consider a small accessibility enhancementThe visual behavior looks good. To make loading states clearer for screen readers, consider adding
role="status"andaria-live="polite"(and/or anaria-labelwhentextis missing) on the wrapper so assistive tech is notified of the loading state.client/trading-dashboard/app/(dashboard)/dashboard/page.tsx (1)
78-78: Empty div with unused classes can be removed.This div appears to serve no purpose and contains no content.
- <div className="flex items-center space-x-2 overflow-hidden"></div>
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (7)
client/trading-dashboard/app/favicon.icois excluded by!**/*.icoclient/trading-dashboard/package-lock.jsonis excluded by!**/package-lock.jsonclient/trading-dashboard/public/file.svgis excluded by!**/*.svgclient/trading-dashboard/public/globe.svgis excluded by!**/*.svgclient/trading-dashboard/public/next.svgis excluded by!**/*.svgclient/trading-dashboard/public/vercel.svgis excluded by!**/*.svgclient/trading-dashboard/public/window.svgis excluded by!**/*.svg
📒 Files selected for processing (41)
README.DOCKER.md(1 hunks)client/trading-dashboard/.dockerignore(1 hunks)client/trading-dashboard/.gitignore(1 hunks)client/trading-dashboard/Dockerfile(1 hunks)client/trading-dashboard/README.md(1 hunks)client/trading-dashboard/app/(dashboard)/dashboard/page.tsx(1 hunks)client/trading-dashboard/app/Providers.tsx(1 hunks)client/trading-dashboard/app/globals.css(1 hunks)client/trading-dashboard/app/layout.tsx(1 hunks)client/trading-dashboard/app/page.tsx(1 hunks)client/trading-dashboard/components.json(1 hunks)client/trading-dashboard/components/Footer.tsx(1 hunks)client/trading-dashboard/components/Header.tsx(1 hunks)client/trading-dashboard/components/Hero.tsx(1 hunks)client/trading-dashboard/components/NewsCard.tsx(1 hunks)client/trading-dashboard/components/NewsFeed.tsx(1 hunks)client/trading-dashboard/components/PriceChart.tsx(1 hunks)client/trading-dashboard/components/Sidebar.tsx(1 hunks)client/trading-dashboard/components/StatsGrid.tsx(1 hunks)client/trading-dashboard/components/TickerCard.tsx(1 hunks)client/trading-dashboard/components/TickerDetails.tsx(1 hunks)client/trading-dashboard/components/TickerGrid.tsx(1 hunks)client/trading-dashboard/components/TickerIcon.tsx(1 hunks)client/trading-dashboard/components/TickerItem.tsx(1 hunks)client/trading-dashboard/components/ui/LoadingSpinner.tsx(1 hunks)client/trading-dashboard/components/ui/badge.tsx(1 hunks)client/trading-dashboard/components/ui/button.tsx(1 hunks)client/trading-dashboard/components/ui/card.tsx(1 hunks)client/trading-dashboard/constants/index.ts(1 hunks)client/trading-dashboard/eslint.config.mjs(1 hunks)client/trading-dashboard/hooks/useTradingData.ts(1 hunks)client/trading-dashboard/lib/utils.ts(1 hunks)client/trading-dashboard/next.config.ts(1 hunks)client/trading-dashboard/package.json(1 hunks)client/trading-dashboard/postcss.config.mjs(1 hunks)client/trading-dashboard/tsconfig.json(1 hunks)client/trading-dashboard/types/index.ts(1 hunks)docker-compose.yml(1 hunks)docs/architecture.md(2 hunks)server/market-trading-service/.dockerignore(1 hunks)server/market-trading-service/Dockerfile(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (15)
client/trading-dashboard/hooks/useTradingData.ts (3)
client/trading-dashboard/types/index.ts (2)
Ticker(1-12)HistoricalData(14-22)client/trading-dashboard/constants/index.ts (2)
API_BASE_URL(4-6)MOCK_TICKERS(8-117)client/trading-dashboard/lib/utils.ts (1)
generateMockHistory(33-65)
client/trading-dashboard/components/TickerDetails.tsx (2)
client/trading-dashboard/components/TickerIcon.tsx (1)
TickerIcon(11-37)client/trading-dashboard/lib/utils.ts (1)
formatCurrency(10-17)
client/trading-dashboard/constants/index.ts (1)
client/trading-dashboard/types/index.ts (1)
Ticker(1-12)
client/trading-dashboard/components/TickerItem.tsx (3)
client/trading-dashboard/types/index.ts (1)
Ticker(1-12)client/trading-dashboard/components/TickerIcon.tsx (1)
TickerIcon(11-37)client/trading-dashboard/lib/utils.ts (1)
formatCurrency(10-17)
client/trading-dashboard/components/StatsGrid.tsx (1)
client/trading-dashboard/lib/utils.ts (1)
formatLargeNumber(19-24)
client/trading-dashboard/components/Sidebar.tsx (3)
client/trading-dashboard/types/index.ts (1)
Ticker(1-12)client/trading-dashboard/components/ui/LoadingSpinner.tsx (1)
LoadingSpinner(9-21)client/trading-dashboard/components/TickerItem.tsx (1)
TickerItem(15-84)
client/trading-dashboard/lib/utils.ts (1)
client/trading-dashboard/types/index.ts (2)
Ticker(1-12)HistoricalData(14-22)
client/trading-dashboard/components/ui/badge.tsx (1)
client/trading-dashboard/lib/utils.ts (1)
cn(5-7)
client/trading-dashboard/components/PriceChart.tsx (2)
client/trading-dashboard/lib/utils.ts (1)
formatCurrency(10-17)client/trading-dashboard/constants/index.ts (1)
TIME_FRAMES(119-124)
client/trading-dashboard/app/(dashboard)/dashboard/page.tsx (5)
client/trading-dashboard/hooks/useTradingData.ts (1)
useTradingData(7-94)client/trading-dashboard/components/Sidebar.tsx (1)
Sidebar(16-74)client/trading-dashboard/components/TickerDetails.tsx (1)
TickerDetails(12-60)client/trading-dashboard/components/StatsGrid.tsx (1)
StatsGrid(50-115)client/trading-dashboard/components/PriceChart.tsx (1)
PriceChart(56-165)
client/trading-dashboard/components/TickerGrid.tsx (3)
client/trading-dashboard/types/index.ts (1)
Ticker(1-12)client/trading-dashboard/constants/index.ts (1)
API_BASE_URL(4-6)client/trading-dashboard/components/ui/LoadingSpinner.tsx (1)
LoadingSpinner(9-21)
client/trading-dashboard/components/ui/button.tsx (1)
client/trading-dashboard/lib/utils.ts (1)
cn(5-7)
client/trading-dashboard/components/TickerCard.tsx (1)
client/trading-dashboard/types/index.ts (1)
Ticker(1-12)
client/trading-dashboard/components/ui/card.tsx (1)
client/trading-dashboard/lib/utils.ts (1)
cn(5-7)
client/trading-dashboard/app/layout.tsx (1)
client/trading-dashboard/app/Providers.tsx (1)
Providers(7-26)
🪛 Biome (2.1.2)
client/trading-dashboard/app/globals.css
[error] 4-4: Each @import should be unique unless differing by media queries.
Consider removing one of the duplicated imports.
(lint/suspicious/noDuplicateAtImportRules)
🪛 markdownlint-cli2 (0.18.1)
docs/architecture.md
70-70: Code block style
Expected: fenced; Actual: indented
(MD046, code-block-style)
75-75: Code block style
Expected: fenced; Actual: indented
(MD046, code-block-style)
78-78: Code block style
Expected: fenced; Actual: indented
(MD046, code-block-style)
README.DOCKER.md
17-17: Bare URL used
(MD034, no-bare-urls)
18-18: Bare URL used
(MD034, no-bare-urls)
🔇 Additional comments (27)
client/trading-dashboard/.gitignore (1)
1-41: Solid, standard gitignore for Next.js project.The file comprehensively covers dependencies, build outputs, logs, environment files, and TypeScript artifacts. All patterns are appropriate and follow Next.js conventions. Docker files are correctly not excluded (should be committed to the repository).
client/trading-dashboard/tsconfig.json (1)
21-33: Path aliases and include/exclude patterns are well-configured.The
@/*path alias provides a clean import pattern for the project root, improving maintainability. Theincludearray properly covers Next.js-generated types (.next/types,.next/dev/types), and the standardexcludeofnode_modulesis appropriate. This setup will support the trading dashboard components and utilities referenced in the PR objectives.client/trading-dashboard/components.json (4)
8-8: CSS globals path verified successfully.The
cssfield correctly referencesapp/globals.css, which exists atclient/trading-dashboard/app/globals.css. The Next.js app router structure is properly configured withlayout.tsxin place. No issues found.
14-20: Path aliases are consistent with TypeScript configuration.The wildcard mapping
@/*→./*in tsconfig.json encompasses all five aliases defined in components.json. Each alias (@/components, @/lib/utils, @/components/ui, @/lib, @/hooks) resolves correctly through the wildcard pattern to its corresponding relative path. No mismatches or module resolution failures will occur.
1-22: All components are properly configured for Tailwind CSS v4—no adjustments needed.The project is already using Tailwind CSS v4 correctly:
- PostCSS configured with
@tailwindcss/postcssv4 plugin- CSS syntax uses
@import "tailwindcss"and@themeblock with oklch color variables (v4 patterns)- UI components (e.g., button.tsx) use v4-compatible class patterns with no deprecated syntax
- components.json configuration is valid for v4
No breaking changes or compatibility issues detected.
7-7: The configuration is correct and follows shadcn/ui + Tailwind CSS v4 best practices.For Tailwind v4, the "config" field should be left empty in components.json. Verification confirms:
- Tailwind CSS v4 is properly installed (
@tailwindcss/postcss: ^4)postcss.config.mjsis correctly configured for v4- No
tailwind.configfile is needed for v4- Path aliases in
tsconfig.jsonalign withcomponents.jsonThe empty
configfield is intentional, correct, and documented—not a scaffolding risk.Likely an incorrect or invalid review comment.
client/trading-dashboard/eslint.config.mjs (1)
1-3: ****The original review's concerns are unfounded. Per the official Next.js documentation for ESLint 9 flat config, the correct pattern with Next.js 15 and eslint-config-next 16 is to import
defineConfigandglobalIgnoresfrom'eslint/config'andnextVitalsfrom'eslint-config-next/core-web-vitals'—exactly as shown in the code snippet (lines 1–3). These imports are valid and the recommended approach.Likely an incorrect or invalid review comment.
client/trading-dashboard/.dockerignore (1)
1-8: LGTM! Standard Docker exclusions for Next.js.The ignore patterns appropriately exclude development artifacts, build outputs, environment files, and IDE configurations from the Docker build context.
server/market-trading-service/.dockerignore (1)
1-8: LGTM! Standard Docker exclusions for Node.js service.The ignore patterns appropriately exclude dependencies, build outputs, logs, environment files, and development artifacts from the Docker build context.
client/trading-dashboard/components/Hero.tsx (1)
1-56: LGTM! Clean and well-structured Hero component.The component is well-organized with appropriate use of Tailwind CSS utilities, semantic HTML, and clear visual hierarchy. The static content and styling are production-ready.
client/trading-dashboard/components/ui/badge.tsx (1)
1-46: LGTM! Clean Badge component with React 19 patterns.The Badge component correctly implements the same architectural patterns as Button. The same Radix UI compatibility verification noted for Button applies here.
client/trading-dashboard/lib/utils.ts (2)
5-7: LGTM! Standard class name utility.The
cnfunction correctly combinesclsxandtailwind-mergefor optimal class name handling with Tailwind CSS.
10-17: LGTM! Appropriate currency formatting with decimal precision.The conditional
maximumFractionDigitscorrectly shows more precision for values less than $1 (e.g., cryptocurrencies).client/trading-dashboard/components/ui/button.tsx (1)
1-60: Button component correctly implements React 19 patterns; monitor known Radix UI warning.@radix-ui/react-slot@1.2.4 declares peer support for React 19 but triggers a React 19 warning about accessing element.ref. The Button implementation is correct—it properly uses React 19's ref-as-prop pattern without
forwardRef. The console warning originates in Radix UI's Slot component whenasChildis used; no changes are needed in button.tsx, but monitor for Radix UI updates that address this warning.client/trading-dashboard/package.json (1)
1-35: All dependencies verified as compatible and secure.Next.js 16.0.3 is compatible with React 19.2.0, and Turbopack is production-ready as the default bundler in Next.js 16. Security checks confirm no known vulnerabilities in the specified versions of Next.js, @radix-ui/react-slot, @tanstack/react-query, or recharts. The
--turboflag on line 6 is now redundant (Turbopack is the default) but remains valid. All dependencies are production-ready.client/trading-dashboard/components/Footer.tsx (1)
3-27: Footer component looks solidThe layout, dynamic year handling, and semantics for the footer all look good as-is. No changes needed from my side.
client/trading-dashboard/types/index.ts (1)
1-22: Verify date/time field types match the backend payloads
Ticker.lastUpdateis typed asstringwhileHistoricalData.timestampis aDate. That’s fine if mock data constructsDateobjects, but if this is coming from a JSON API, everything will arrive as strings and need parsing.Please double‑check that these types match what the server actually returns (or that you parse responses into these shapes) so you don’t end up with silent
any/casting or runtime date issues.client/trading-dashboard/next.config.ts (1)
1-7: Next.js config scaffold is fine as a starting pointThis typed
NextConfigexport is valid and keeps the door open for future options. Once you start adding config (e.g., experimental flags, redirects, or typed routes), you can drop the placeholder comment and rely on the type to catch mistakes.client/trading-dashboard/postcss.config.mjs (1)
1-7: PostCSS configuration matches Tailwind v4 setupUsing
@tailwindcss/postcssas the sole plugin is aligned with Tailwind 4’s recommended PostCSS integration, given Lightning CSS handles prefixing and nesting. No changes needed here.client/trading-dashboard/components/NewsCard.tsx (1)
5-40: Solid NewsCard composition and external-link handlingProps are well-typed, layout is clear, and external links correctly use
rel="noopener noreferrer"withtarget="_blank". Nothing blocking here.client/trading-dashboard/components/TickerIcon.tsx (1)
11-37: LGTM! Clean and deterministic icon implementation.The component effectively generates a stable, visually distinct icon for each ticker symbol using a deterministic color mapping. The use of
aria-hidden="true"is appropriate since the icon is decorative and the symbol text provides the semantic content.client/trading-dashboard/app/Providers.tsx (1)
7-26: LGTM! Proper React Query setup.The provider correctly uses lazy initialization via
useStateto create theQueryClientonce. The default options (60-second stale time, disabled refetch on window focus) are appropriate for a trading dashboard where data freshness is controlled by polling intervals rather than user focus events.client/trading-dashboard/app/(dashboard)/dashboard/page.tsx (1)
153-182: LGTM: Clean conditional rendering pattern.The conditional rendering correctly handles the no-selection state with a friendly empty state UI, and properly renders the ticker details when selected. The component composition with TickerDetails, StatsGrid, and PriceChart follows good separation of concerns.
client/trading-dashboard/components/ui/card.tsx (2)
5-16: LGTM: Well-structured Card component.The Card component follows React best practices with proper TypeScript typing using
React.ComponentProps<"div">, and thedata-slotattribute enables slot-based styling patterns. Thecnutility correctly merges default and custom classes.
51-62: LGTM: Grid-based action positioning.The CardAction component uses a clean grid-based approach with
col-start-2 row-span-2 row-start-1to position action elements in the header. This creates a flexible layout pattern for header actions.client/trading-dashboard/components/Sidebar.tsx (2)
1-14: LGTM: Clean interface definition.The SidebarProps interface is well-defined with appropriate types. The priceStatus typing as a record with union values (
"up" | "down" | "neutral") provides good type safety.
26-33: LGTM: Responsive sidebar with smooth transitions.The sidebar implementation correctly handles mobile/desktop responsiveness with conditional transforms, proper z-indexing, and smooth transitions. The
mt-16offset accounts for the fixed header height, and accessibility is properly addressed witharia-label.
| }; | ||
|
|
||
| return ( | ||
| <div className="min-h-screen bg-gray-900"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Background color should respect theme state.
The root div uses a hardcoded bg-gray-900 class, which doesn't respond to the isDarkMode state. This causes the background to remain dark even in light mode.
- <div className="min-h-screen bg-gray-900">
+ <div className={`min-h-screen ${isDarkMode ? "bg-gray-900" : "bg-gray-50"}`}>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <div className="min-h-screen bg-gray-900"> | |
| <div className={`min-h-screen ${isDarkMode ? "bg-gray-900" : "bg-gray-50"}`}> |
🤖 Prompt for AI Agents
In client/trading-dashboard/app/(dashboard)/dashboard/page.tsx around line 45,
the root div currently uses a hardcoded "bg-gray-900" which ignores the
isDarkMode theme state; change it to apply background classes conditionally
based on isDarkMode (e.g., choose dark class when true and light class when
false) or use a theme-aware utility (like classNames or template literal) to
toggle between the dark and light background classes so the UI respects the
current theme state.
| @import "tailwindcss"; | ||
| @import "tw-animate-css"; | ||
|
|
||
| @import "tailwindcss"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove duplicate Tailwind import
@import "tailwindcss"; is declared twice; keeping a single import is enough and satisfies the linter.
You can simplify to:
-@import "tailwindcss";
-@import "tw-animate-css";
-
-@import "tailwindcss";
+@import "tailwindcss";
+@import "tw-animate-css";
+📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @import "tailwindcss"; | |
| @import "tw-animate-css"; | |
| @import "tailwindcss"; | |
| @import "tailwindcss"; | |
| @import "tw-animate-css"; | |
🧰 Tools
🪛 Biome (2.1.2)
[error] 4-4: Each @import should be unique unless differing by media queries.
Consider removing one of the duplicated imports.
(lint/suspicious/noDuplicateAtImportRules)
🤖 Prompt for AI Agents
In client/trading-dashboard/app/globals.css around lines 1 to 4, there is a
duplicate `@import "tailwindcss";` declaration; remove the redundant second
occurrence and keep a single `@import "tailwindcss";` (preserve the `@import
"tw-animate-css";` line) so the file contains only one Tailwind import to
satisfy the linter.
|
|
||
| <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> | ||
| {news.map((item, index) => ( | ||
| <NewsCard key={index} {...item} /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid using index as key for shuffled items.
Using array index as the key prop is problematic when the array is shuffled. React relies on keys to track component identity, and using indices will cause React to incorrectly match old items with new positions, leading to incorrect renders, lost component state, and potential bugs.
Apply this diff to use a stable identifier:
- {news.map((item, index) => (
- <NewsCard key={index} {...item} />
+ {news.map((item) => (
+ <NewsCard key={item.url || item.title} {...item} />
))}Note: If url is always "#" as shown in the mock data, consider adding a unique id field to the NewsItem interface.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In client/trading-dashboard/components/NewsFeed.tsx around line 86, the code
uses the array index as the React key which breaks when items are shuffled;
change the key to a stable unique identifier (e.g., item.id or item.url if
guaranteed unique). If NewsItem lacks an id, add an id field to the NewsItem
interface, update the mock data to include unique ids (or generate them when
mapping, e.g., via a UUID or a deterministic hash) and pass the id through to
NewsCard so you can use key={item.id}; ensure the chosen field is stable across
renders and not the array index.
| const eslintConfig = defineConfig([ | ||
| ...nextVitals, | ||
| ...nextTs, | ||
| // Override default ignores of eslint-config-next. | ||
| globalIgnores([ | ||
| // Default ignores of eslint-config-next: | ||
| ".next/**", | ||
| "out/**", | ||
| "build/**", | ||
| "next-env.d.ts", | ||
| ]), | ||
| ]); | ||
|
|
||
| export default eslintConfig; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Configuration structure needs adjustment for ESLint 9 flat config.
While the ignore patterns are appropriate for a Next.js project, the configuration structure depends on using the correct imports. Once the imports are fixed, the flat config format should use a plain object with an ignores property rather than a globalIgnores() function call.
The corrected structure should look like:
export default [
// ...spread your Next.js configs here
{
ignores: [
".next/**",
"out/**",
"build/**",
"next-env.d.ts",
],
},
];🤖 Prompt for AI Agents
In client/trading-dashboard/eslint.config.mjs around lines 5 to 18, the config
is using globalIgnores() which is not compatible with ESLint v9 flat config;
replace the current array-of-configs approach so the file exports a flat-config
array where ignores are provided via an object with an ignores property (i.e.,
remove globalIgnores(...) and instead include an object like { ignores:
[".next/**","out/**","build/**","next-env.d.ts"] } alongside the spread next
configs), and ensure imports are the flat-config compatible helpers before
exporting the array.
| const priceStatus = useRef<{ [key: string]: "up" | "down" | "neutral" }>({}); | ||
| const previousTickers = useRef<Ticker[]>([]); | ||
|
|
||
| // Fetch tickers with automatic polling every 2 seconds for tickers | ||
| const { data: tickers = [], isLoading: tickersLoading } = useQuery({ | ||
| queryKey: ["tickers"], | ||
| queryFn: async () => { | ||
| try { | ||
| const response = await fetch(`${API_BASE_URL}/tickers`); | ||
| if (!response.ok) throw new Error("Network response was not ok"); | ||
| const { | ||
| data: { tickers }, | ||
| } = await response.json(); | ||
| setError(null); | ||
| return tickers as Ticker[]; | ||
| } catch (err) { | ||
| console.error("Failed to fetch tickers:", err); | ||
| setError("Failed to load market data. Displaying mock data."); | ||
| return MOCK_TICKERS; | ||
| } | ||
| }, | ||
| refetchInterval: 2000, | ||
| staleTime: 0, | ||
| }); | ||
|
|
||
| // Track price changes for animations | ||
| useEffect(() => { | ||
| if (previousTickers.current.length > 0 && tickers.length > 0) { | ||
| tickers.forEach((newTicker) => { | ||
| const oldTicker = previousTickers.current.find( | ||
| (t) => t.symbol === newTicker.symbol | ||
| ); | ||
| if (oldTicker) { | ||
| if (newTicker.price > oldTicker.price) { | ||
| priceStatus.current[newTicker.symbol] = "up"; | ||
| } else if (newTicker.price < oldTicker.price) { | ||
| priceStatus.current[newTicker.symbol] = "down"; | ||
| } | ||
| } | ||
| }); | ||
| } | ||
| previousTickers.current = tickers; | ||
| }, [tickers]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Price status tracking lags one poll behind and never sets neutral
The priceStatus ref is only updated inside the useEffect that runs after a render triggered by new tickers data. Because you mutate a ref (no setState), components using useTradingData will see status values from the previous interval, not from the latest tick. Also, when price is unchanged (or a ticker appears for the first time), you never explicitly assign "neutral", so old "up"/"down" values can stick around.
To get accurate, per-interval statuses:
- Derive a fresh status map whenever
tickerschanges, and store it in state instead of a ref. - Explicitly set
"neutral"when there is no prior price or when the price is unchanged.
Example sketch:
- const priceStatus = useRef<{ [key: string]: "up" | "down" | "neutral" }>({});
+ const [priceStatus, setPriceStatus] = useState<
+ Record<string, "up" | "down" | "neutral">
+ >({});
const previousTickers = useRef<Ticker[]>([]);
useEffect(() => {
- if (previousTickers.current.length > 0 && tickers.length > 0) {
- tickers.forEach((newTicker) => {
- const oldTicker = previousTickers.current.find(
- (t) => t.symbol === newTicker.symbol
- );
- if (oldTicker) {
- if (newTicker.price > oldTicker.price) {
- priceStatus.current[newTicker.symbol] = "up";
- } else if (newTicker.price < oldTicker.price) {
- priceStatus.current[newTicker.symbol] = "down";
- }
- }
- });
- }
- previousTickers.current = tickers;
+ if (tickers.length === 0) {
+ previousTickers.current = [];
+ setPriceStatus({});
+ return;
+ }
+
+ const next: Record<string, "up" | "down" | "neutral"> = {};
+ tickers.forEach((nextTicker) => {
+ const prev = previousTickers.current.find(
+ (t) => t.symbol === nextTicker.symbol
+ );
+ if (!prev) {
+ next[nextTicker.symbol] = "neutral";
+ } else if (nextTicker.price > prev.price) {
+ next[nextTicker.symbol] = "up";
+ } else if (nextTicker.price < prev.price) {
+ next[nextTicker.symbol] = "down";
+ } else {
+ next[nextTicker.symbol] = "neutral";
+ }
+ });
+
+ previousTickers.current = tickers;
+ setPriceStatus(next);
}, [tickers]);
return {
...
- priceStatus: priceStatus.current,
+ priceStatus,
};This keeps the API shape the same while ensuring callers always see current, explicit "up" | "down" | "neutral" values.
I'll verify the review comment by examining the actual code and its usages.
---
🏁 Script executed:
```shell
#!/bin/bash
# Find the useTradingData.ts file and examine its full content
fd -name "useTradingData.ts" -x cat {}
Length of output: 230
🏁 Script executed:
#!/bin/bash
# Search for usages of useTradingData hook to understand consumer impact
rg "useTradingData" -n -C2 --type=ts --type=tsxLength of output: 27
🏁 Script executed:
#!/bin/bash
# Look for any consumers that access priceStatus
rg "priceStatus" -n -C2 --type=ts --type=tsxLength of output: 27
🏁 Script executed:
#!/bin/bash
# Check if there are any tests for the hook
fd -name "*useTradingData*test*" -o -name "*useTradingData*.spec.*"Length of output: 230
---
🏁 Script executed:
```shell
#!/bin/bash
# Find useTradingData.ts file
fd "useTradingData.ts"
Length of output: 48
🏁 Script executed:
#!/bin/bash
# Examine the full file
fd "useTradingData.ts" -x catLength of output: 3153
🏁 Script executed:
#!/bin/bash
# Search usages of priceStatus in TypeScript files
rg "priceStatus" -n -C2 -t tsLength of output: 3652
🏁 Script executed:
#!/bin/bash
# Search usages of useTradingData hook
rg "useTradingData" -n -C2 -t tsLength of output: 1479
🏁 Script executed:
#!/bin/bash
# Look for test files related to useTradingData
fd "useTradingData" --type fLength of output: 48
Price status tracking lags one poll behind and never sets neutral
The priceStatus ref is mutated in the useEffect that runs after a render triggered by new tickers data. Because you update a ref (no state), components receive status values from the previous interval, not the latest tick. Additionally, when a ticker price is unchanged or appears for the first time, you never explicitly assign "neutral", allowing old "up"/"down" values to persist.
Consumers in client/trading-dashboard/app/(dashboard)/dashboard/page.tsx (line 27) and client/trading-dashboard/components/Sidebar.tsx (line 65) will display stale status indicators.
To fix, move priceStatus to state and compute a fresh status map on each tick with explicit "neutral" assignments:
- const priceStatus = useRef<{ [key: string]: "up" | "down" | "neutral" }>({});
+ const [priceStatus, setPriceStatus] = useState<
+ Record<string, "up" | "down" | "neutral">
+ >({});
const previousTickers = useRef<Ticker[]>([]);
useEffect(() => {
- if (previousTickers.current.length > 0 && tickers.length > 0) {
- tickers.forEach((newTicker) => {
- const oldTicker = previousTickers.current.find(
- (t) => t.symbol === newTicker.symbol
- );
- if (oldTicker) {
- if (newTicker.price > oldTicker.price) {
- priceStatus.current[newTicker.symbol] = "up";
- } else if (newTicker.price < oldTicker.price) {
- priceStatus.current[newTicker.symbol] = "down";
- }
- }
- });
- }
- previousTickers.current = tickers;
+ if (tickers.length === 0) {
+ previousTickers.current = [];
+ setPriceStatus({});
+ return;
+ }
+
+ const next: Record<string, "up" | "down" | "neutral"> = {};
+ tickers.forEach((nextTicker) => {
+ const prev = previousTickers.current.find(
+ (t) => t.symbol === nextTicker.symbol
+ );
+ if (!prev) {
+ next[nextTicker.symbol] = "neutral";
+ } else if (nextTicker.price > prev.price) {
+ next[nextTicker.symbol] = "up";
+ } else if (nextTicker.price < prev.price) {
+ next[nextTicker.symbol] = "down";
+ } else {
+ next[nextTicker.symbol] = "neutral";
+ }
+ });
+
+ previousTickers.current = tickers;
+ setPriceStatus(next);
}, [tickers]);
return {
...
- priceStatus: priceStatus.current,
+ priceStatus,
};This preserves the API while ensuring callers always see current, explicit "up" | "down" | "neutral" values.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In client/trading-dashboard/hooks/useTradingData.ts around lines 11 to 53, the
priceStatus ref causes consumers to see stale values and never sets "neutral";
replace the ref with React state (e.g., const [priceStatus, setPriceStatus] =
useState<Record<string,"up"|"down"|"neutral">>({})), and on every new tickers
array compute a fresh status map (iterate current tickers, compare to
previousTickers to decide "up"/"down"/"neutral", and explicitly set "neutral"
when unchanged or when a ticker is new) then call setPriceStatus with that map;
update previousTickers.current after computing the map so UI always receives the
latest explicit statuses.
docs/architecture.md
Outdated
| ### Data Fetching | ||
|
|
||
| - Using React Query (TanStack Query ) as we need to implement polling | ||
| - No state mangement library, using Context if needed | ||
|
|
||
| ### Chart | ||
|
|
||
| - Using Recharts which is leveraging D3.js components for easy integration into React | ||
|
|
||
| ### Unit Test | ||
| - RTL (React Tesing Libary, Jest) for unit test | ||
| - Cypress (E2E tests) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix markdown code block formatting.
The static analysis tool correctly flags that these sections use indented code blocks (4 spaces) instead of fenced code blocks. Modern markdown parsers prefer fenced blocks with explicit language identifiers for better syntax highlighting and consistency.
Apply this diff:
### Data Fetching
- - Using React Query (TanStack Query ) as we need to implement polling
- - No state mangement library, using Context if needed
+```
+- Using React Query (TanStack Query) as we need to implement polling
+- No state management library, using Context if needed
+```
### Chart
- - Using Recharts which is leveraging D3.js components for easy integration into React
+```
+- Using Recharts which is leveraging D3.js components for easy integration into React
+```
### Unit Test
- - RTL (React Tesing Libary, Jest) for unit test
- - Cypress (E2E tests)
+```
+- RTL (React Testing Library, Jest) for unit test
+- Cypress (E2E tests)
+```Note: Also fixed typos "mangement" → "management" and "Tesing Libary" → "Testing Library".
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ### Data Fetching | |
| - Using React Query (TanStack Query ) as we need to implement polling | |
| - No state mangement library, using Context if needed | |
| ### Chart | |
| - Using Recharts which is leveraging D3.js components for easy integration into React | |
| ### Unit Test | |
| - RTL (React Tesing Libary, Jest) for unit test | |
| - Cypress (E2E tests) | |
| ### Data Fetching | |
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
70-70: Code block style
Expected: fenced; Actual: indented
(MD046, code-block-style)
75-75: Code block style
Expected: fenced; Actual: indented
(MD046, code-block-style)
78-78: Code block style
Expected: fenced; Actual: indented
(MD046, code-block-style)
🤖 Prompt for AI Agents
In docs/architecture.md around lines 68 to 79, replace the indented (4-space)
code block sections with fenced code blocks using triple backticks, correct the
typos ("mangement" → "management", "Tesing Libary" → "Testing Library"), and
ensure list items use consistent hyphen + single space formatting; specifically
wrap the Data Fetching list, Chart line, and Unit Test list each in their own
``` fenced block and update the three lines as shown in the diff (React Query
(TanStack Query), No state management library, Using Recharts..., RTL (React
Testing Library, Jest), Cypress).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (2)
docs/architecture.md (1)
68-86: Use triple backticks (```) for fenced code blocks, not single backticks.This appears to be the same markdown code block formatting issue flagged in the previous review. The markdownlint tool (MD046) requires fenced code blocks with triple backticks, not single backticks around content.
Apply this diff to fix the code block formatting:
### Data Fetching -+` -+- Using React Query (TanStack Query) as we need to implement polling -+- No state management library, using Context if needed -+` +``` +- Using React Query (TanStack Query) as we need to implement polling +- No state management library, using Context if needed +``` ### Chart -+` -+- Using Recharts which is leveraging D3.js components for easy integration into React -+` +``` +- Using Recharts which is leveraging D3.js components for easy integration into React +``` ### Unit Test -+` -+- RTL (React Testing Library, Jest) for unit test -+- Cypress (E2E tests) -+` +``` +- RTL (React Testing Library, Jest) for unit test +- Cypress (E2E tests) +```Note: The typos mentioned in the previous review ("management" and "Testing Library") have been correctly fixed in this version.
client/trading-dashboard/app/(dashboard)/dashboard/page.tsx (1)
45-47: Root background still ignoresisDarkModeThe outer
<div>uses a hardcodedbg-gray-900, so the page background is always dark even whenisDarkModeis false. Consider wiring it to the theme state:- <div className="min-h-screen bg-gray-900"> + <div className={`min-h-screen ${isDarkMode ? "bg-gray-900" : "bg-gray-50"}`}>
🧹 Nitpick comments (2)
client/trading-dashboard/lib/utils.ts (1)
19-24: Consider using Intl.NumberFormat for consistency.While the current implementation works, you could use
Intl.NumberFormatwithnotation: "compact"for more consistent formatting withformatCurrency. This would also handle edge cases better.Optional refactor:
export const formatLargeNumber = (value: number): string => { - if (value >= 1e12) return `$${(value / 1e12).toFixed(2)}T`; - if (value >= 1e9) return `$${(value / 1e9).toFixed(2)}B`; - if (value >= 1e6) return `$${(value / 1e6).toFixed(2)}M`; - return `$${value.toFixed(2)}`; + return new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + notation: "compact", + maximumFractionDigits: 2, + }).format(value); };client/trading-dashboard/hooks/userDarkMode.ts (1)
4-31: Hook initialization is solid; keep DOM/persistence logic centralized if possibleThe hook correctly restores theme from
localStorage/OS and applies thedarkclass on mount. Just be aware that callers gettingsetIsDarkModemust also keepdocument.documentElement.classListandlocalStoragein sync when toggling; otherwise Tailwinddark:styles won’t follow state. You might consider wrapping the setter here (e.g., customsetDarkMode/toggleDarkMode) so all DOM/localStorage updates live in this hook.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
client/trading-dashboard/README.md(1 hunks)client/trading-dashboard/app/(dashboard)/dashboard/page.tsx(1 hunks)client/trading-dashboard/app/layout.tsx(1 hunks)client/trading-dashboard/example.env(1 hunks)client/trading-dashboard/hooks/userDarkMode.ts(1 hunks)client/trading-dashboard/lib/utils.ts(1 hunks)docker-compose.yml(1 hunks)docs/architecture.md(2 hunks)server/market-trading-service/README.md(1 hunks)
✅ Files skipped from review due to trivial changes (2)
- server/market-trading-service/README.md
- client/trading-dashboard/example.env
🚧 Files skipped from review as they are similar to previous changes (3)
- client/trading-dashboard/README.md
- client/trading-dashboard/app/layout.tsx
- docker-compose.yml
🧰 Additional context used
🧬 Code graph analysis (2)
client/trading-dashboard/lib/utils.ts (1)
client/trading-dashboard/types/index.ts (2)
Ticker(1-12)HistoricalData(14-22)
client/trading-dashboard/app/(dashboard)/dashboard/page.tsx (6)
client/trading-dashboard/hooks/useTradingData.ts (1)
useTradingData(7-94)client/trading-dashboard/hooks/userDarkMode.ts (1)
useDarkMode(4-32)client/trading-dashboard/components/Sidebar.tsx (1)
Sidebar(16-74)client/trading-dashboard/components/TickerDetails.tsx (1)
TickerDetails(12-60)client/trading-dashboard/components/StatsGrid.tsx (1)
StatsGrid(50-115)client/trading-dashboard/components/PriceChart.tsx (1)
PriceChart(56-165)
🔇 Additional comments (5)
docs/architecture.md (1)
34-62: LGTM—documentation accurately reflects the server structure changes.The renaming from
real-time-trading/toserver/market-trading-service/is properly documented.client/trading-dashboard/lib/utils.ts (4)
5-7: LGTM!Standard utility for composing Tailwind class names. Implementation is correct.
10-17: LGTM!The conditional decimal places (up to 6 for values < 1) are appropriate for displaying cryptocurrency prices.
26-31: LGTM!The heuristic volatility values are reasonable for mock data generation.
33-72: Good fix for the timeline issue, but minor OHLC inconsistency remains.The refactored implementation correctly addresses the past review's concern by walking forward and ensuring the last point matches
ticker.price. However, as noted in the previous comment, theopenvalue should also be adjusted to maintain OHLC consistency.
| // Toggle theme and save to localStorage | ||
| const toggleTheme = () => { | ||
| setIsDarkMode((prev) => { | ||
| const newTheme = !prev; | ||
| localStorage.setItem("theme", newTheme ? "dark" : "light"); | ||
| return newTheme; | ||
| }); | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix theme toggle to update the global dark class
toggleTheme flips isDarkMode and updates localStorage, but it never updates document.documentElement.classList. Since components like the “Live” pill use Tailwind’s dark: variants, the global dark class stays stuck at whatever the hook set on mount and won’t follow user toggles.
Update the toggle to keep the html class in sync:
const toggleTheme = () => {
setIsDarkMode((prev) => {
- const newTheme = !prev;
- localStorage.setItem("theme", newTheme ? "dark" : "light");
- return newTheme;
+ const newTheme = !prev;
+ if (typeof document !== "undefined") {
+ document.documentElement.classList.toggle("dark", newTheme);
+ }
+ localStorage.setItem("theme", newTheme ? "dark" : "light");
+ return newTheme;
});
};That way, isDarkMode, localStorage, and the Tailwind dark: styles all stay consistent.
Also applies to: 82-90
| // Ensure the last point matches the current ticker price | ||
| if (data.length > 0) { | ||
| const lastPoint = data[data.length - 1]; | ||
| lastPoint.price = ticker.price; | ||
| lastPoint.close = ticker.price; | ||
| lastPoint.high = Math.max(lastPoint.high, ticker.price); | ||
| lastPoint.low = Math.min(lastPoint.low, ticker.price); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ensure open is consistent with adjusted OHLC bounds.
The adjustment correctly fixes price, close, high, and low, but leaves open unchanged. If the random walk has drifted significantly from ticker.price, the unadjusted open might fall outside the new [low, high] range, violating OHLC constraints that charting libraries expect.
Apply this diff to clamp open within the adjusted range:
// Ensure the last point matches the current ticker price
if (data.length > 0) {
const lastPoint = data[data.length - 1];
lastPoint.price = ticker.price;
lastPoint.close = ticker.price;
lastPoint.high = Math.max(lastPoint.high, ticker.price);
lastPoint.low = Math.min(lastPoint.low, ticker.price);
+ lastPoint.open = Math.max(
+ lastPoint.low,
+ Math.min(lastPoint.high, lastPoint.open)
+ );
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Ensure the last point matches the current ticker price | |
| if (data.length > 0) { | |
| const lastPoint = data[data.length - 1]; | |
| lastPoint.price = ticker.price; | |
| lastPoint.close = ticker.price; | |
| lastPoint.high = Math.max(lastPoint.high, ticker.price); | |
| lastPoint.low = Math.min(lastPoint.low, ticker.price); | |
| } | |
| // Ensure the last point matches the current ticker price | |
| if (data.length > 0) { | |
| const lastPoint = data[data.length - 1]; | |
| lastPoint.price = ticker.price; | |
| lastPoint.close = ticker.price; | |
| lastPoint.high = Math.max(lastPoint.high, ticker.price); | |
| lastPoint.low = Math.min(lastPoint.low, ticker.price); | |
| lastPoint.open = Math.max( | |
| lastPoint.low, | |
| Math.min(lastPoint.high, lastPoint.open) | |
| ); | |
| } |
🤖 Prompt for AI Agents
In client/trading-dashboard/lib/utils.ts around lines 62 to 69, the
lastPoint.open is left unchanged after adjusting price/close/high/low which can
leave open outside the new [low, high] bounds; clamp lastPoint.open to the
adjusted range by setting it to Math.min(Math.max(lastPoint.open,
lastPoint.low), lastPoint.high) (i.e., after you update high and low, ensure
open is not less than low and not greater than high).
Summary by CodeRabbit
New Features
Documentation