Skip to content

feature: Top Categories#2

Merged
markvalenzuela72 merged 3 commits into
mainfrom
feature/top-categories
Jul 4, 2026
Merged

feature: Top Categories#2
markvalenzuela72 merged 3 commits into
mainfrom
feature/top-categories

Conversation

@markvalenzuela72

@markvalenzuela72 markvalenzuela72 commented Jun 11, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Added a Top Categories section with a data table and enriched market-change/value display.
    • Added a live coin details experience, including real-time candlestick updates and recent trades.
    • Introduced a currency converter and coin pagination UI.
  • Improvements

    • Enhanced the Categories loading experience with a dedicated fallback.
    • Improved table styling for per-column formatting and more consistent percentage/currency rendering.
    • Updated live/pending controls and removed the “Max” period option.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@components/home/Categories.tsx`:
- Line 31: The cellClassName value contains an extraneous trailing space
('change-header-cell '); update the assignment for cellClassName in
Categories.tsx to remove the trailing space so it becomes 'change-header-cell',
and verify any class concatenation logic uses explicit separators instead of
relying on that trailing space.
- Around line 18-26: The map rendering in category.top_3_coins (inside
Categories.tsx) uses the image URL for both key and alt, which is fragile and
inaccessible; update the Image usage in the category.top_3_coins.map callback to
use a stable unique key (e.g., combine category id/name with the map index or a
coin id if available — e.g., `${category.id}-${index}`) and replace the alt prop
with a meaningful description (e.g., the coin name or a human-friendly string
like `${category.name} coin ${index + 1}` or extract the filename from the URL)
so screen readers get useful text; ensure you update the JSX that returns the
Image component (the Image element inside category.top_3_coins.map) accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ab67eba4-4bb3-409b-ba0a-215f390344e2

📥 Commits

Reviewing files that changed from the base of the PR and between 8598894 and 30ac5f3.

📒 Files selected for processing (4)
  • app/page.tsx
  • components/DataTable.tsx
  • components/home/Categories.tsx
  • components/home/TrendingCoins.tsx

Comment thread components/home/Categories.tsx Outdated
Comment on lines +18 to +26
return category.top_3_coins.map((coin, index) => (
<Image
key={category.top_3_coins[index]}
src={coin}
alt={category.top_3_coins[index]}
width={24}
height={24}
/>
));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix Image key and improve alt text for accessibility.

Two issues with the Image rendering:

  1. Key: Using the URL (category.top_3_coins[index]) as the React key is fragile. If the same coin URL appears multiple times or positions change, React's reconciliation may behave unexpectedly.

  2. Alt text: The alt prop is set to the URL string, which provides no meaningful description for screen readers and violates accessibility best practices.

♿ Proposed fix for key and alt attributes
-      return category.top_3_coins.map((coin, index) => (
+      return category.top_3_coins.map((coin, index) => {
+        const coinName = category.name || 'coin';
+        return (
         <Image
-          key={category.top_3_coins[index]}
+          key={`${category.name}-coin-${index}`}
           src={coin}
-          alt={category.top_3_coins[index]}
+          alt={`Top coin ${index + 1} in ${coinName} category`}
           width={24}
           height={24}
         />
-      ));
+        );
+      });
📝 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.

Suggested change
return category.top_3_coins.map((coin, index) => (
<Image
key={category.top_3_coins[index]}
src={coin}
alt={category.top_3_coins[index]}
width={24}
height={24}
/>
));
return category.top_3_coins.map((coin, index) => {
const coinName = category.name || 'coin';
return (
<Image
key={`${category.name}-coin-${index}`}
src={coin}
alt={`Top coin ${index + 1} in ${coinName} category`}
width={24}
height={24}
/>
);
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/home/Categories.tsx` around lines 18 - 26, The map rendering in
category.top_3_coins (inside Categories.tsx) uses the image URL for both key and
alt, which is fragile and inaccessible; update the Image usage in the
category.top_3_coins.map callback to use a stable unique key (e.g., combine
category id/name with the map index or a coin id if available — e.g.,
`${category.id}-${index}`) and replace the alt prop with a meaningful
description (e.g., the coin name or a human-friendly string like
`${category.name} coin ${index + 1}` or extract the filename from the URL) so
screen readers get useful text; ensure you update the JSX that returns the Image
component (the Image element inside category.top_3_coins.map) accordingly.

Comment thread components/home/Categories.tsx Outdated
},
{
header: '24h Change',
cellClassName: 'change-header-cell ',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove trailing space from className.

The string 'change-header-cell ' contains an unnecessary trailing space.

🧹 Proposed fix
-    cellClassName: 'change-header-cell ',
+    cellClassName: 'change-header-cell',
📝 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.

Suggested change
cellClassName: 'change-header-cell ',
cellClassName: 'change-header-cell',
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/home/Categories.tsx` at line 31, The cellClassName value contains
an extraneous trailing space ('change-header-cell '); update the assignment for
cellClassName in Categories.tsx to remove the trailing space so it becomes
'change-header-cell', and verify any class concatenation logic uses explicit
separators instead of relying on that trailing space.

feat: create CoinHeader component for displaying live coin data

feat: implement CoinsPagination component for navigating through coin pages

feat: add Converter component for currency conversion

feat: create LiveDataWrapper component to handle live data updates

feat: add TopGainersLosers component to display top gainers and losers

feat: implement reusable UI components: Badge, Button, Input, Pagination, Select, Separator, Tabs

feat: add useCoinGeckoWebSocket hook for real-time data fetching

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🧹 Nitpick comments (5)
components/home/CoinOverview.tsx (1)

8-22: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Failures are now silently swallowed with no logging.

Previously wrapped in try/catch with console.error; now each fetch fails silently into null/[]. If /coins/bitcoin or its OHLC endpoint start failing, there's no signal in logs to diagnose it — the UI simply falls back to the skeleton with no trace. CandlestickChart's own fetchOHLCData (components/CandlestickChart.tsx) still logs its failures via console.error, so this is now inconsistent within the same feature.

♻️ Suggested fix
 const getCoinData = async () => {
   const [coin, coinOHLCData] = await Promise.all([
     fetcher<CoinDetailsData>('/coins/bitcoin', {
       dex_pair_format: 'symbol',
-    }).catch(() => null),
+    }).catch((e) => {
+      console.error('Failed to fetch coin details', e);
+      return null;
+    }),

     fetcher<OHLCData[]>('/coins/bitcoin/ohlc', {
       vs_currency: 'usd',
       days: 1,
       precision: 'full',
-    }).catch(() => []),
+    }).catch((e) => {
+      console.error('Failed to fetch OHLC data', e);
+      return [];
+    }),
   ]);

   return { coin, coinOHLCData };
 };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/home/CoinOverview.tsx` around lines 8 - 22, The fetch failures in
getCoinData are being swallowed by Promise.all(...).catch(() => null/[]) with no
logging, so restore error reporting while keeping the fallback values. Update
getCoinData in CoinOverview to log failures for the /coins/bitcoin and
/coins/bitcoin/ohlc fetcher calls (similar to CandlestickChart.fetchOHLCData)
before returning null or [] so the feature remains debuggable.
components/CoinsPagination.tsx (1)

15-15: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Pagination type name collides with the imported Pagination component.

Pagination is imported as a component from @/components/ui/pagination (Line 4) and then reused as a prop type annotation here. TypeScript resolves this via separate value/type namespaces (assuming a global Pagination interface exists in type.d.ts), so it compiles, but the shared name is confusing to readers and easy to break if either symbol is refactored.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/CoinsPagination.tsx` at line 15, The `Pagination` name is being
used both for the imported UI component and the prop type in `CoinsPagination`,
which is confusing and fragile. Rename the props type annotation in
`CoinsPagination` to a more specific identifier (or inline the shape) and update
the component signature so it no longer relies on the shared `Pagination`
symbol. Keep the imported `Pagination` component name as-is and use distinct,
descriptive type names to avoid future collisions.
components/CandlestickChart.tsx (1)

78-89: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate millisecond-to-second conversion logic.

The same convertedToSeconds mapping is repeated in both the init effect and the update effect. Extracting a shared helper (e.g. toSecondsOHLC(data: OHLCData[])) would reduce duplication and the risk of the two copies drifting apart.

Also applies to: 112-121

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/CandlestickChart.tsx` around lines 78 - 89, The
millisecond-to-second mapping is duplicated in both the init and update paths of
CandlestickChart. Extract the repeated ohlcData.map conversion into a shared
helper (for example, a toSecondsOHLC function near convertOHLCData) and reuse it
in both effects so the series.setData flow stays consistent and there is only
one place to maintain the conversion logic.
hooks/useCoinGeckoWebSocket.ts (1)

77-88: 🩺 Stability & Availability | 🔵 Trivial | 🏗️ Heavy lift

No reconnection logic on socket close/error.

ws.onclose/ws.onerror only flip isWsReady to false; there's no retry/backoff to re-establish the connection. For a live-data feature this means a dropped connection silently stops updates until the user reloads.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@hooks/useCoinGeckoWebSocket.ts` around lines 77 - 88, The WebSocket lifecycle
in useCoinGeckoWebSocket only toggles isWsReady off in ws.onclose and
ws.onerror, so add reconnection handling there instead of stopping after a
disconnect. Update the hook to schedule a retry with backoff and reconnect the
socket from the same setup path used by ws.onopen/ws.onmessage, and make sure
the cleanup returned by the effect cancels any pending retry when unmounting.
app/coins/[id]/page.tsx (1)

14-24: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

No 404 handling for invalid/unknown coin IDs.

If id doesn't exist, fetcher throws (per lib/coingecko.actions.ts), and there's no notFound() call or try/catch here—users hit the generic error boundary instead of a proper 404 page.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/coins/`[id]/page.tsx around lines 14 - 24, The coin detail page currently
lets an unknown or invalid id bubble up as a generic fetch error instead of
rendering a 404. Update the logic in the page component that calls Promise.all
and fetcher for CoinDetailsData and OHLCData to catch not-found cases and call
notFound() from the Next.js page flow when the coin cannot be resolved. Keep the
fix localized to the coin page’s data-loading path so valid coins still render
normally and only missing coins route to the 404 page.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/coins/page.tsx`:
- Around line 10-16: The page parsing in the coins page is accepting negative
values because `Number(page) || 1` only falls back on falsy values, so update
the `currentPage` logic in `page.tsx` to validate that the parsed page is a
positive integer before using it, otherwise default to 1. Also remove the
hardcoded `totalCoins = 1000`/derived `totalPages` assumption and compute
pagination from the API response or a shared source of truth so
`CoinsPagination` and this page stay in sync, especially the `isLastPage` logic.

In `@components/CandlestickChart.tsx`:
- Around line 26-30: Make liveInterval and setLiveInterval optional in
CandlestickChartProps so CoinOverview can render CandlestickChart without
passing them. Update the CandlestickChartProps definition in type.d.ts to align
with the runtime guards already present in CandlestickChart, and ensure the
component signature still destructures liveInterval and setLiveInterval safely.
- Around line 40-57: The period fetch in `fetchOHLCData` is happening outside
`startTransition`, so `isPending` only covers `setOhlcData` and the buttons
remain enabled while `/coins/${coinId}/ohlc` is loading. Move the async fetch
and state update into the same transition flow used by
`fetchOHLCData`/`startTransition`, so the pending state covers the request and
prevents overlapping clicks from older responses overwriting newer
`selectedPeriod` data.

In `@components/CoinHeader.tsx`:
- Around line 60-80: The CoinHeader stats icon rendering always shows an icon
because the ternary in the stats list only switches between TrendingUp and
TrendingDown instead of skipping rendering when showIcon is false. Update the
JSX in CoinHeader’s stats map so the icon block is conditionally rendered only
when stat.showIcon is true, and then choose TrendingUp or TrendingDown based on
stat.isUp for the remaining cases.

In `@components/CoinsPagination.tsx`:
- Around line 15-23: The CoinsPagination component’s isLastPage logic is using a
hardcoded page limit instead of the totalPages prop. Update CoinsPagination so
the last-page check is derived from currentPage and totalPages, and keep the
next-control disabling logic aligned with buildPageNumbers and handlePageChange.
This will ensure pagination behavior stays correct if the coin count or page
size changes.
- Around line 29-34: The pagination controls rendered via PaginationPrevious,
PaginationNext, and PaginationLink are not keyboard-accessible because they are
currently behaving like bare anchors without real link semantics. Update the
pagination components in components/ui/pagination.tsx so these controls render
as proper buttons or include valid href-based navigation, and make sure
CoinsPagination keeps using those updated primitives for page changes and
previous/next actions.

In `@hooks/useCoinGeckoWebSocket.ts`:
- Around line 28-39: The handleMessage function in useCoinGeckoWebSocket can
throw on malformed WebSocket payloads because both JSON.parse(event.data) and
JSON.parse(msg?.identifier ?? '') are unguarded. Wrap the parsing and the
confirm_subscription identifier extraction in try/catch, and only process ping
or update subscribed.current when parsing succeeds; if parsing fails, safely
ignore or log the bad message so onmessage keeps working. Refer to
handleMessage, WebSocketMessage, and subscribed.current when updating the logic.
- Around line 123-147: Move the pool-related subscriptions in
useCoinGeckoWebSocket into the same queueMicrotask block as the CGSimplePrice
reset/subscribe flow. The current OnchainTrade and OnchainOHLCV subscriptions
run after unsubscribeAll(), which can cause a newly selected pool to be
immediately unsubscribed if the channel already exists; update the effect so
poolAddress handling happens inside the microtask after unsubscribeAll() and
before the new set_pools subscriptions are issued.
- Line 5: The websocket URL in useCoinGeckoWebSocket currently inlines
NEXT_PUBLIC_COINGECKO_API_KEY into the browser bundle, exposing the secret to
clients. Update the CoinGecko handshake so useCoinGeckoWebSocket no longer
builds a client-side URL with the API key; instead route the connection through
a server-side proxy or a backend endpoint that attaches the credential, or
switch to a tightly scoped demo token. Make sure WS_BASE is removed or replaced
with a server-derived endpoint and that no browser-accessible code references
the secret key directly.

In `@lib/coingecko.actions.ts`:
- Around line 55-61: The fast-path branch in getPools that calls fetcher for a
specific network and contractAddress can throw on common non-2xx responses,
unlike the fallback lookup. Wrap that fetcher call in the same try/catch pattern
used later in getPools, and return the provided fallback when the request fails;
keep the existing behavior of returning poolData.data?.[0] when successful.

---

Nitpick comments:
In `@app/coins/`[id]/page.tsx:
- Around line 14-24: The coin detail page currently lets an unknown or invalid
id bubble up as a generic fetch error instead of rendering a 404. Update the
logic in the page component that calls Promise.all and fetcher for
CoinDetailsData and OHLCData to catch not-found cases and call notFound() from
the Next.js page flow when the coin cannot be resolved. Keep the fix localized
to the coin page’s data-loading path so valid coins still render normally and
only missing coins route to the 404 page.

In `@components/CandlestickChart.tsx`:
- Around line 78-89: The millisecond-to-second mapping is duplicated in both the
init and update paths of CandlestickChart. Extract the repeated ohlcData.map
conversion into a shared helper (for example, a toSecondsOHLC function near
convertOHLCData) and reuse it in both effects so the series.setData flow stays
consistent and there is only one place to maintain the conversion logic.

In `@components/CoinsPagination.tsx`:
- Line 15: The `Pagination` name is being used both for the imported UI
component and the prop type in `CoinsPagination`, which is confusing and
fragile. Rename the props type annotation in `CoinsPagination` to a more
specific identifier (or inline the shape) and update the component signature so
it no longer relies on the shared `Pagination` symbol. Keep the imported
`Pagination` component name as-is and use distinct, descriptive type names to
avoid future collisions.

In `@components/home/CoinOverview.tsx`:
- Around line 8-22: The fetch failures in getCoinData are being swallowed by
Promise.all(...).catch(() => null/[]) with no logging, so restore error
reporting while keeping the fallback values. Update getCoinData in CoinOverview
to log failures for the /coins/bitcoin and /coins/bitcoin/ohlc fetcher calls
(similar to CandlestickChart.fetchOHLCData) before returning null or [] so the
feature remains debuggable.

In `@hooks/useCoinGeckoWebSocket.ts`:
- Around line 77-88: The WebSocket lifecycle in useCoinGeckoWebSocket only
toggles isWsReady off in ws.onclose and ws.onerror, so add reconnection handling
there instead of stopping after a disconnect. Update the hook to schedule a
retry with backoff and reconnect the socket from the same setup path used by
ws.onopen/ws.onmessage, and make sure the cleanup returned by the effect cancels
any pending retry when unmounting.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 034472de-6cde-4f6d-a54b-21ad371712e9

📥 Commits

Reviewing files that changed from the base of the PR and between 30ac5f3 and 43938c7.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (24)
  • app/coins/[id]/page.tsx
  • app/coins/page.tsx
  • app/globals.css
  • components/CandlestickChart.tsx
  • components/CoinHeader.tsx
  • components/CoinsPagination.tsx
  • components/Converter.tsx
  • components/DataTable.tsx
  • components/LiveDataWrapper.tsx
  • components/TopGainersLosers.tsx
  • components/home/Categories.tsx
  • components/home/CoinOverview.tsx
  • components/ui/badge.tsx
  • components/ui/button.tsx
  • components/ui/input.tsx
  • components/ui/pagination.tsx
  • components/ui/select.tsx
  • components/ui/separator.tsx
  • components/ui/tabs.tsx
  • constants.ts
  • hooks/useCoinGeckoWebSocket.ts
  • lib/coingecko.actions.ts
  • package.json
  • type.d.ts
💤 Files with no reviewable changes (1)
  • components/DataTable.tsx
✅ Files skipped from review due to trivial changes (2)
  • package.json
  • constants.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • components/home/Categories.tsx

Comment thread app/coins/page.tsx
Comment thread components/CandlestickChart.tsx
Comment thread components/CandlestickChart.tsx
Comment thread components/CoinHeader.tsx
Comment on lines +15 to +23
const CoinsPagination = ({ currentPage, totalPages }: Pagination) => {
const router = useRouter();

const handlePageChange = (page: number) => {
router.push(`/coins?page=${page}`);
};

const pageNumbers = buildPageNumbers(currentPage, totalPages);
const isLastPage = currentPage >= 100;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

isLastPage ignores the totalPages prop.

totalPages is passed in explicitly but isLastPage uses a hardcoded currentPage >= 100. It only coincidentally matches today because app/coins/page.tsx computes totalPages = 1000 / 10 = 100. If the total coin count or page size ever changes, this component will disable/enable the "next" control based on stale, unrelated logic instead of the actual page count it was given.

🐛 Suggested fix
-  const isLastPage = currentPage >= 100;
+  const isLastPage = currentPage >= totalPages;
📝 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.

Suggested change
const CoinsPagination = ({ currentPage, totalPages }: Pagination) => {
const router = useRouter();
const handlePageChange = (page: number) => {
router.push(`/coins?page=${page}`);
};
const pageNumbers = buildPageNumbers(currentPage, totalPages);
const isLastPage = currentPage >= 100;
const CoinsPagination = ({ currentPage, totalPages }: Pagination) => {
const router = useRouter();
const handlePageChange = (page: number) => {
router.push(`/coins?page=${page}`);
};
const pageNumbers = buildPageNumbers(currentPage, totalPages);
const isLastPage = currentPage >= totalPages;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/CoinsPagination.tsx` around lines 15 - 23, The CoinsPagination
component’s isLastPage logic is using a hardcoded page limit instead of the
totalPages prop. Update CoinsPagination so the last-page check is derived from
currentPage and totalPages, and keep the next-control disabling logic aligned
with buildPageNumbers and handlePageChange. This will ensure pagination behavior
stays correct if the coin count or page size changes.

Comment thread components/CoinsPagination.tsx

import { useEffect, useRef, useState } from 'react';

const WS_BASE = `${process.env.NEXT_PUBLIC_COINGECKO_WEBSOCKET_URL}?x_cg_pro_api_key=${process.env.NEXT_PUBLIC_COINGECKO_API_KEY}`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the file structure and inspect the hook with line numbers
git ls-files hooks/useCoinGeckoWebSocket.ts
wc -l hooks/useCoinGeckoWebSocket.ts
cat -n hooks/useCoinGeckoWebSocket.ts | sed -n '1,260p'

# Find related CoinGecko websocket/env usages
rg -n "NEXT_PUBLIC_COINGECKO|useCoinGeckoWebSocket|queueMicrotask|JSON.parse\\(msg\\?\\.identifier|ws\\.onclose|ws\\.onerror|unsubscribeAll|OnchainTrade|OnchainOHLCV|CGSimplePrice" -S .

Repository: markvalenzuela72/coin-pulse

Length of output: 6283


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the hook's message types and surrounding consumer
rg -n "type WebSocketMessage|interface WebSocketMessage|UseCoinGeckoWebSocketProps|UseCoinGeckoWebSocketReturn|ExtendedPriceData|Trade|OHLCData|LiveDataWrapper" hooks components types . -S

# Read the relevant type definitions and consumer usage if present
for f in \
  hooks/useCoinGeckoWebSocket.ts \
  components/LiveDataWrapper.tsx \
  types.ts \
  src/types.ts \
  lib/types.ts
do
  if [ -f "$f" ]; then
    echo "===== $f ====="
    cat -n "$f" | sed -n '1,260p'
  fi
done

Repository: markvalenzuela72/coin-pulse

Length of output: 6298


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "===== type.d.ts (relevant ranges) ====="
cat -n type.d.ts | sed -n '140,270p'

echo
echo "===== env/docs references ====="
rg -n "NEXT_PUBLIC_COINGECKO_API_KEY|COINGECKO_WEBSOCKET|coingecko" -S . \
  -g '!node_modules' -g '!dist' -g '!build'

Repository: markvalenzuela72/coin-pulse

Length of output: 4385


🌐 Web query:

CoinGecko websocket x_cg_pro_api_key public client-side exposure documentation restricted demo key

💡 Result:

CoinGecko documentation and security best practices explicitly advise against exposing any API keys in client-side code [1][2][3]. Even if you are using a demo key or a restricted API key, you should avoid embedding it directly into frontend applications (such as JavaScript in browsers or mobile apps) because it can be easily extracted by third parties [2][3]. Official documentation and guidance consistently recommend the following security practices: 1. Secure Storage: Store your API key in a secure backend server environment, such as an environment variable (.env file), rather than hardcoding it into your codebase [4][1][2]. 2. Backend Proxy: All requests that require authentication should be sent from your frontend to your own backend server. The backend then attaches the API key and forwards the request to the CoinGecko API [1][2][5]. This prevents your key from ever being exposed to the client-side browser or application [2][6]. 3. Header Authentication: When connecting to CoinGecko APIs (including WebSocket and REST endpoints), using the HTTP header x-cg-pro-api-key (for Pro/paid plans) or x-cg-demo-api-key (for demo plans) is preferred over passing the key as a query string parameter [7][8][9][2]. Passing keys in query strings is risky because they may be captured in browser history, server logs, or proxy logs [9][2][3]. Even for development or demo purposes, exposing a key in the browser creates a security risk where an attacker can monitor and potentially misuse your key, potentially leading to rate limiting or unauthorized consumption of your API credits [10][2][3]. Always prioritize server-side proxying for any application involving API authentication [1][5][3].

Citations:


Do not expose the CoinGecko API key in the browser. NEXT_PUBLIC_COINGECKO_API_KEY is inlined into the client bundle and sent on the websocket URL, so anyone can extract and reuse it. Move this handshake behind a server-side proxy or replace it with a tightly scoped demo token.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@hooks/useCoinGeckoWebSocket.ts` at line 5, The websocket URL in
useCoinGeckoWebSocket currently inlines NEXT_PUBLIC_COINGECKO_API_KEY into the
browser bundle, exposing the secret to clients. Update the CoinGecko handshake
so useCoinGeckoWebSocket no longer builds a client-side URL with the API key;
instead route the connection through a server-side proxy or a backend endpoint
that attaches the credential, or switch to a tightly scoped demo token. Make
sure WS_BASE is removed or replaced with a server-derived endpoint and that no
browser-accessible code references the secret key directly.

Comment thread hooks/useCoinGeckoWebSocket.ts
Comment thread hooks/useCoinGeckoWebSocket.ts
Comment thread lib/coingecko.actions.ts
Repository owner deleted a comment from coderabbitai Bot Jul 4, 2026
@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@markvalenzuela72, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 41 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 044b1807-14ec-47e2-983f-f61f158d9a85

📥 Commits

Reviewing files that changed from the base of the PR and between 43938c7 and 60f5f0f.

📒 Files selected for processing (1)
  • components/CoinHeader.tsx
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/top-categories

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@markvalenzuela72 markvalenzuela72 merged commit 5ba1884 into main Jul 4, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant