Skip to content

feat: add week/month/year range selector to token price chart#2121

Merged
Nick-1979 merged 4 commits intomainfrom
addPriceRange
Mar 4, 2026
Merged

feat: add week/month/year range selector to token price chart#2121
Nick-1979 merged 4 commits intomainfrom
addPriceRange

Conversation

@Nick-1979
Copy link
Member

@Nick-1979 Nick-1979 commented Mar 4, 2026

Summary by CodeRabbit

  • New Features

    • Time-range selector added to the token price chart with Week, Month, and Year toggles.
    • Chart title, tooltips, axis formatting, and data labels update dynamically by range; range toggle styled consistently and placed under the chart.
  • Bug Fixes

    • Improved price fetching with range-aware polling, timeout handling, and rate-limit/error notifications.
    • Supports multiple API price data shapes for more robust parsing.
  • Documentation

    • Added localization keys for Week, Month, and Year.

@Nick-1979 Nick-1979 self-assigned this Mar 4, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 4, 2026

📝 Walkthrough

Walkthrough

Adds a 7/30/365-day range toggle to LineChart, replaces prior fetch with CoinGecko market_chart endpoint driven by selectedRange, introduces fetchWithTimeout, range-aware polling, unified price parsing, updated tooltips/axes/title, and new translation keys.

Changes

Cohort / File(s) Summary
LineChart + Range UI & Fetch Logic
packages/extension-polkagate/src/fullscreen/components/LineChart.tsx
Adds selectedRange (default 7) and ToggleButtonGroup for 7/30/365; switches to CoinGecko market_chart (days-based) data shape; maps incoming [time, value] pairs to internal priceData; updates polling interval, tooltip/axis formatting, dynamic title, rate-limit and error handling, and applies button styling.
Fetch Utility
packages/extension-polkagate/src/fullscreen/components/utils.ts
Adds fetchWithTimeout(url: string, timeout = 10000) using AbortController to support request timeouts and clearer error handling used by LineChart.
Localization
packages/extension/public/locales/en/translation.json
Adds English keys for range labels: Week, Month, Year (empty values).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as ToggleButtonGroup
    participant ChartComp as LineChart Component
    participant Utils as fetchWithTimeout
    participant API as CoinGecko API
    participant Renderer as Chart Renderer

    User->>UI: select range (7 / 30 / 365)
    UI->>ChartComp: set selectedRange
    ChartComp->>ChartComp: compute endpoint & interval based on range
    ChartComp->>Utils: fetchWithTimeout(GET /coins/{id}/market_chart?days={selectedRange})
    Utils->>API: HTTP request
    API-->>Utils: MarketChartResponse (prices: [[time, value], ...])
    Utils-->>ChartComp: Response data
    ChartComp->>ChartComp: parse prices -> priceData[{time, value}, ...]
    ChartComp->>Renderer: update dataset, tooltips, axes, title
    Renderer-->>User: render updated chart
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

ready to be merged

Suggested reviewers

  • AMIRKHANEF

Poem

🐰 I toggled days with a curious hop,
Seven, thirty, year—each datapoint a drop.
Fetches timed and parsed with care,
I nibble the chart and hum a hare's fanfare.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main change: adding week/month/year range selector to the token price chart, which is reflected in the LineChart component refactor, new toggle buttons, selectedRange state, and translation keys.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch addPriceRange

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 and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/extension-polkagate/src/fullscreen/components/LineChart.tsx (1)

129-218: ⚠️ Potential issue | 🟠 Major

Prevent stale responses from overwriting the latest selected range.

Line 207 updates chart state for any completed request. If the user switches ranges quickly, an older request can finish last and replace newer data, causing a range/title mismatch.

Suggested fix
@@
   const chartRef = useRef<ChartJS<'line'>>(null);
+  const latestRequestIdRef = useRef(0);
@@
   const fetchPriceData = useCallback(async () => {
+    const requestId = ++latestRequestIdRef.current;
     try {
@@
-      setPriceData(prices);
+      if (requestId !== latestRequestIdRef.current) {
+        return; // stale response
+      }
+      setPriceData(prices);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/extension-polkagate/src/fullscreen/components/LineChart.tsx` around
lines 129 - 218, fetchPriceData can allow an older fetch to overwrite newer
state when the user switches ranges quickly; capture the current selectedRange
at the start of fetchPriceData (e.g., const requestRange = selectedRange) and
before calling setPriceData compare selectedRange to requestRange and abort
updating if they differ, or alternatively wire an AbortController to the fetch
(use fetchWithTimeout's signal) and abort prior requests when selectedRange
changes; update the function identifiers fetchPriceData and setPriceData and the
dependency array accordingly so only the intended response updates the chart.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/extension-polkagate/src/fullscreen/components/LineChart.tsx`:
- Around line 193-205: The component currently conflates "still loading" with
"parsed-but-empty" by using priceData.length === 0 to decide to show a loader;
change the loading flag so it reflects an undefined/null input or an explicit
isLoading prop instead of an empty parsed array. In LineChart.tsx adjust the
parsing block around maybePrices/needsSparkline (the code that sets prices from
maybePrices) to always set prices = [] when parsing yields no points, but do not
set isLoading there; ensure the component uses a separate boolean (e.g.,
isLoading or props.isLoading) to show the spinner and use priceData.length === 0
to render a no-data state. Update any places that currently derive loading from
priceData (the render logic around priceData.length checks) to read the new
loading flag instead.
- Around line 338-340: The three ToggleButton labels ("Week", "Month", "Year")
are hardcoded; replace them with localized strings using the component's
translation function (e.g., change ToggleButton value={7}>Week</ToggleButton to
ToggleButton value={7}>{t('...')}</ToggleButton), using appropriate translation
keys (e.g., 'range.week', 'range.month', 'range.year') to match existing i18n
patterns used in this component (refer to the ToggleButton elements near
value={7}, value={30}, value={365} and the t(...) usage elsewhere).

---

Outside diff comments:
In `@packages/extension-polkagate/src/fullscreen/components/LineChart.tsx`:
- Around line 129-218: fetchPriceData can allow an older fetch to overwrite
newer state when the user switches ranges quickly; capture the current
selectedRange at the start of fetchPriceData (e.g., const requestRange =
selectedRange) and before calling setPriceData compare selectedRange to
requestRange and abort updating if they differ, or alternatively wire an
AbortController to the fetch (use fetchWithTimeout's signal) and abort prior
requests when selectedRange changes; update the function identifiers
fetchPriceData and setPriceData and the dependency array accordingly so only the
intended response updates the chart.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 13555b84-8394-4325-80a2-519e3555cddb

📥 Commits

Reviewing files that changed from the base of the PR and between a2f73e9 and e1fa83f.

📒 Files selected for processing (1)
  • packages/extension-polkagate/src/fullscreen/components/LineChart.tsx

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/extension-polkagate/src/fullscreen/components/LineChart.tsx (2)

142-143: ⚠️ Potential issue | 🟠 Major

Throttle 429 warnings to avoid alert spam during polling.

With interval polling enabled (Lines 219-221), every 429 triggers notify(...) again at Line 143. useAlerts currently appends every alert (no dedupe), so this can spam users repeatedly.

Suggested fix (cooldown for rate-limit alerts)
@@
   const { notify } = useAlerts();
+  const lastRateLimitNotifyAtRef = useRef(0);
@@
       if (!res.ok) {
         if (res.status === 429) {
-          notify(t('Rate limited by CoinGecko. Please try again shortly.'), 'warning');
+          const now = Date.now();
+          const cooldownMs = 5 * 60 * 1000;
+          if (now - lastRateLimitNotifyAtRef.current > cooldownMs) {
+            lastRateLimitNotifyAtRef.current = now;
+            notify(t('Rate limited by CoinGecko. Please try again shortly.'), 'warning');
+          }
         } else {
           notify(t(`Failed to fetch token data (status ${res.status}).`), 'error');
         }

Also applies to: 219-221

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/extension-polkagate/src/fullscreen/components/LineChart.tsx` around
lines 142 - 143, The rate-limit notify call (notify(...)) in LineChart.tsx is
spamming users during interval polling; add a cooldown guard (e.g., a
module-level or component ref/state like lastRateLimitAt or isRateLimitNotified)
and only call notify for a 429 if the last notification timestamp is older than
a chosen cooldown (e.g., 60s); update both the 429 branch where res.status ===
429 and the polling callback (the code around the interval polling at lines
~219-221) to consult and update that guard so alerts are throttled rather than
appended on every poll.

126-205: ⚠️ Potential issue | 🟠 Major

Prevent stale responses from overwriting the currently selected range.

If users switch ranges quickly, an older request can resolve later and overwrite newer data via setPriceData(prices), causing range/title mismatch.

Suggested fix (request sequencing guard)
@@
   const [priceData, setPriceData] = useState<PricePoint[]>([]);
   const [selectedRange, setSelectedRange] = useState<number>(7);
+  const latestRequestIdRef = useRef(0);

   const fetchPriceData = useCallback(async () => {
+    const requestId = ++latestRequestIdRef.current;
     try {
@@
-      setPriceData(prices);
+      if (requestId !== latestRequestIdRef.current) {
+        return;
+      }
+      setPriceData(prices);
     } catch (err: unknown) {
@@
-      notify(message, isTimeOut ? 'warning' : 'error');
+      if (requestId === latestRequestIdRef.current) {
+        notify(message, isTimeOut ? 'warning' : 'error');
+      }
     }
   }, [coinId, notify, selectedRange, t, vsCurrency]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/extension-polkagate/src/fullscreen/components/LineChart.tsx` around
lines 126 - 205, The fetchPriceData routine can be overwritten by stale async
responses when selectedRange changes; add a request-sequencing guard: create a
mutable ref (e.g., latestFetchIdRef) and increment it each time fetchPriceData
runs, capture the current id inside fetchPriceData, and before calling
setPriceData(prices) verify the captured id matches latestFetchIdRef.current (if
not, return early). Update the same guard on cleanup/unmount or when
selectedRange changes (or alternatively wire an AbortController into
fetchWithTimeout and abort previous requests) so only the most recent fetch
updates state.
♻️ Duplicate comments (1)
packages/extension-polkagate/src/fullscreen/components/LineChart.tsx (1)

319-325: ⚠️ Potential issue | 🟠 Major

Separate loading state from empty-data state.

Line 319 still treats priceData.length === 0 as loading. If parsing yields an empty array, the loader can persist indefinitely instead of showing a no-data state.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/extension-polkagate/src/fullscreen/components/LineChart.tsx` around
lines 319 - 325, The current conditional uses priceData.length === 0 to show
SineWaveLoader which confuses "loading" with "empty data"; change the render
logic to check a distinct loading boolean (e.g., isLoading or loadingPriceData)
and only render SineWaveLoader when that flag is true, otherwise if loading is
false and priceData.length === 0 render a no-data placeholder component/message,
and only render <Line data={chartData} options={options}
plugins={[gradientFillPlugin]} ref={chartRef} /> when loading is false and
priceData.length > 0; update the component state/props to provide the new
loading flag and adjust references to priceData, SineWaveLoader, chartData,
gradientFillPlugin, options, and chartRef accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@packages/extension-polkagate/src/fullscreen/components/LineChart.tsx`:
- Around line 142-143: The rate-limit notify call (notify(...)) in LineChart.tsx
is spamming users during interval polling; add a cooldown guard (e.g., a
module-level or component ref/state like lastRateLimitAt or isRateLimitNotified)
and only call notify for a 429 if the last notification timestamp is older than
a chosen cooldown (e.g., 60s); update both the 429 branch where res.status ===
429 and the polling callback (the code around the interval polling at lines
~219-221) to consult and update that guard so alerts are throttled rather than
appended on every poll.
- Around line 126-205: The fetchPriceData routine can be overwritten by stale
async responses when selectedRange changes; add a request-sequencing guard:
create a mutable ref (e.g., latestFetchIdRef) and increment it each time
fetchPriceData runs, capture the current id inside fetchPriceData, and before
calling setPriceData(prices) verify the captured id matches
latestFetchIdRef.current (if not, return early). Update the same guard on
cleanup/unmount or when selectedRange changes (or alternatively wire an
AbortController into fetchWithTimeout and abort previous requests) so only the
most recent fetch updates state.

---

Duplicate comments:
In `@packages/extension-polkagate/src/fullscreen/components/LineChart.tsx`:
- Around line 319-325: The current conditional uses priceData.length === 0 to
show SineWaveLoader which confuses "loading" with "empty data"; change the
render logic to check a distinct loading boolean (e.g., isLoading or
loadingPriceData) and only render SineWaveLoader when that flag is true,
otherwise if loading is false and priceData.length === 0 render a no-data
placeholder component/message, and only render <Line data={chartData}
options={options} plugins={[gradientFillPlugin]} ref={chartRef} /> when loading
is false and priceData.length > 0; update the component state/props to provide
the new loading flag and adjust references to priceData, SineWaveLoader,
chartData, gradientFillPlugin, options, and chartRef accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 897bb79c-2504-4922-9632-fcbd1fc98214

📥 Commits

Reviewing files that changed from the base of the PR and between e1fa83f and fc759dc.

📒 Files selected for processing (1)
  • packages/extension-polkagate/src/fullscreen/components/LineChart.tsx

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
packages/extension-polkagate/src/fullscreen/components/LineChart.tsx (1)

342-345: ⚠️ Potential issue | 🟠 Major

Loading state is still coupled to empty data.

At Line 342, priceData.length === 0 is treated as “loading”, so valid empty responses can leave the loader spinning indefinitely instead of showing a no-data state.

Proposed fix
 const [priceData, setPriceData] = useState<PricePoint[]>([]);
+const [isLoading, setIsLoading] = useState<boolean>(true);

 const fetchPriceData = useCallback(async () => {
+  setIsLoading(true);
   try {
@@
       setPriceData(prices);
     } catch (err: unknown) {
@@
       notify(message, isTimeOut ? 'warning' : 'error');
+    } finally {
+      setIsLoading(false);
     }
   }, [coinId, notify, selectedRange, t, vsCurrency]);
@@
-        {priceData.length === 0
+        {isLoading
           ? (
             <SineWaveLoader height={300} width={637} />
           )
+          : priceData.length === 0
+            ? (
+              <Typography variant='S-2'>{t('Nothing Found')}</Typography>
+            )
           : (
             <Line data={chartData} options={options} plugins={[gradientFillPlugin]} ref={chartRef} />
           )}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/extension-polkagate/src/fullscreen/components/LineChart.tsx` around
lines 342 - 345, The component treats priceData.length === 0 as the loading
condition causing spinners for legitimate empty responses; change the logic in
LineChart to use an explicit loading flag (e.g., isLoading prop/state) instead
of priceData.length, render <SineWaveLoader> only when isLoading is true, and
render a distinct no-data UI when isLoading is false and priceData is empty;
locate the conditional that currently checks priceData.length === 0 and replace
it to check the new isLoading flag (or hook-provided loading state), and add a
NoData/empty-state element to display when priceData is empty but not loading.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@packages/extension-polkagate/src/fullscreen/components/LineChart.tsx`:
- Around line 342-345: The component treats priceData.length === 0 as the
loading condition causing spinners for legitimate empty responses; change the
logic in LineChart to use an explicit loading flag (e.g., isLoading prop/state)
instead of priceData.length, render <SineWaveLoader> only when isLoading is
true, and render a distinct no-data UI when isLoading is false and priceData is
empty; locate the conditional that currently checks priceData.length === 0 and
replace it to check the new isLoading flag (or hook-provided loading state), and
add a NoData/empty-state element to display when priceData is empty but not
loading.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d1f62157-af39-400a-a6d5-4fa898720b8f

📥 Commits

Reviewing files that changed from the base of the PR and between fc759dc and 2dde085.

📒 Files selected for processing (2)
  • packages/extension-polkagate/src/fullscreen/components/LineChart.tsx
  • packages/extension/public/locales/en/translation.json

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/extension-polkagate/src/fullscreen/components/LineChart.tsx (1)

284-290: ⚠️ Potential issue | 🟠 Major

Loading state is still coupled to empty-data state.

At Lines 284-287, using priceData.length === 0 as loading means an empty API result can keep the spinner visible indefinitely instead of showing a no-data state.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/extension-polkagate/src/fullscreen/components/LineChart.tsx` around
lines 284 - 290, The current render uses priceData.length === 0 to decide
loading, which conflates an empty API result with loading; introduce and use an
explicit loading flag (e.g., isLoading from the fetch state/props) instead of
priceData length when rendering SineWaveLoader, and add a separate no-data UI
when not loading and priceData is empty; update the JSX around priceData /
SineWaveLoader / Line (and the surrounding logic that supplies priceData, e.g.,
the fetch hook or parent prop) so the conditional becomes: if isLoading show
SineWaveLoader, else if priceData.length === 0 show a NoData placeholder, else
render <Line data={chartData} options={options} plugins={[gradientFillPlugin]}
ref={chartRef} />.
🧹 Nitpick comments (1)
packages/extension-polkagate/src/fullscreen/components/LineChart.tsx (1)

127-133: Remove unreachable check and stale “Sparkline” message.

After Line 121-125, maybePrices is already guaranteed to be an array, so Line 129-133 is dead code and the message is outdated.

Proposed cleanup
-      const maybePrices = data.prices;
-
-      if (!maybePrices) {
-        notify(t('Sparkline data not available for this token.'), 'info');
-
-        return;
-      }
+      const maybePrices = data.prices;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/extension-polkagate/src/fullscreen/components/LineChart.tsx` around
lines 127 - 133, The check for maybePrices being falsy and the notify call are
unreachable because data.prices is already guaranteed to be an array earlier;
remove the entire conditional block that references maybePrices and the stale
t('Sparkline data not available for this token.') message (i.e., delete the if
(!maybePrices) { notify(...); return; } block) so the component no longer
contains dead code or outdated user-facing text; ensure no other logic depends
on that early return and keep using maybePrices directly wherever used in this
component (e.g., in LineChart rendering logic).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/extension-polkagate/src/fullscreen/components/LineChart.tsx`:
- Line 281: The chart title is hardcoded in LineChart.tsx
(title={`${coinId.toUpperCase()} Price — Last ${selectedRange} Days`}); update
it to use the i18n translator (t) instead of a literal string. Replace the
current title expression with a call to t(...) that includes coinId and
selectedRange as interpolation variables (e.g., pass coinId.toUpperCase() and
selectedRange), and add or use an appropriate translation key (e.g.,
"chart.priceLastDays") so the title becomes localized while preserving the coin
and day values.

---

Duplicate comments:
In `@packages/extension-polkagate/src/fullscreen/components/LineChart.tsx`:
- Around line 284-290: The current render uses priceData.length === 0 to decide
loading, which conflates an empty API result with loading; introduce and use an
explicit loading flag (e.g., isLoading from the fetch state/props) instead of
priceData length when rendering SineWaveLoader, and add a separate no-data UI
when not loading and priceData is empty; update the JSX around priceData /
SineWaveLoader / Line (and the surrounding logic that supplies priceData, e.g.,
the fetch hook or parent prop) so the conditional becomes: if isLoading show
SineWaveLoader, else if priceData.length === 0 show a NoData placeholder, else
render <Line data={chartData} options={options} plugins={[gradientFillPlugin]}
ref={chartRef} />.

---

Nitpick comments:
In `@packages/extension-polkagate/src/fullscreen/components/LineChart.tsx`:
- Around line 127-133: The check for maybePrices being falsy and the notify call
are unreachable because data.prices is already guaranteed to be an array
earlier; remove the entire conditional block that references maybePrices and the
stale t('Sparkline data not available for this token.') message (i.e., delete
the if (!maybePrices) { notify(...); return; } block) so the component no longer
contains dead code or outdated user-facing text; ensure no other logic depends
on that early return and keep using maybePrices directly wherever used in this
component (e.g., in LineChart rendering logic).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e18f0fef-3f44-4b94-94f0-52bfc37e5b78

📥 Commits

Reviewing files that changed from the base of the PR and between 2dde085 and ce9c8d6.

📒 Files selected for processing (2)
  • packages/extension-polkagate/src/fullscreen/components/LineChart.tsx
  • packages/extension-polkagate/src/fullscreen/components/utils.ts

@Nick-1979 Nick-1979 merged commit 5820a3e into main Mar 4, 2026
8 checks passed
@Nick-1979 Nick-1979 deleted the addPriceRange branch March 4, 2026 23:11
github-actions bot pushed a commit that referenced this pull request Mar 4, 2026
# [1.27.0](v1.26.2...v1.27.0) (2026-03-04)

### Features

* add week/month/year range selector to token price chart ([#2121](#2121)) ([5820a3e](5820a3e))
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