-
Notifications
You must be signed in to change notification settings - Fork 77
feat(provider): changed AKT dropdown popup design change #2202
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
base: main
Are you sure you want to change the base?
Conversation
WalkthroughThis PR refactors wallet UI presentation from a detailed WalletInfo component to a compact dropdown-driven WalletStatus interface, introduces a new WalletPopup component for wallet details display, and updates market data fetching to retrieve live CoinGecko pricing with enhanced error handling and a dedicated AKT price hook. Changes
Sequence DiagramsequenceDiagram
participant User
participant WalletStatus
participant DropdownMenu
participant WalletPopup
participant WalletProvider
participant PricingProvider
User->>WalletStatus: Click wallet header
WalletStatus->>DropdownMenu: Toggle open state
DropdownMenu->>WalletPopup: Render in dropdown
WalletPopup->>WalletProvider: Get wallet address
WalletPopup->>PricingProvider: Fetch AKT price
PricingProvider-->>WalletPopup: Return pricing data
WalletPopup->>WalletPopup: Calculate balances & conversions
WalletPopup-->>User: Display wallet details
User->>WalletPopup: Click disconnect
WalletPopup->>WalletProvider: Trigger logout
WalletProvider-->>WalletStatus: Clear connection
WalletStatus-->>User: Show ConnectWalletButton
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #2202 +/- ##
==========================================
- Coverage 47.19% 46.85% -0.35%
==========================================
Files 1023 1013 -10
Lines 29078 28729 -349
Branches 7584 7543 -41
==========================================
- Hits 13724 13461 -263
+ Misses 14961 14884 -77
+ Partials 393 384 -9
*This pull request uses carry forward flags. Click here to find out more. 🚀 New features to boost your workflow:
|
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
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
apps/provider-console/src/components/layout/WalletStatus.tsx(1 hunks)apps/provider-console/src/components/wallet/WalletPopup.tsx(1 hunks)apps/provider-console/src/queries/useMarketData.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/general.mdc)
Never use type any or cast to type any. Always define the proper TypeScript types.
Files:
apps/provider-console/src/components/layout/WalletStatus.tsxapps/provider-console/src/components/wallet/WalletPopup.tsxapps/provider-console/src/queries/useMarketData.ts
**/*.{js,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/general.mdc)
**/*.{js,ts,tsx}: Never use deprecated methods from libraries.
Don't add unnecessary comments to the code
Files:
apps/provider-console/src/components/layout/WalletStatus.tsxapps/provider-console/src/components/wallet/WalletPopup.tsxapps/provider-console/src/queries/useMarketData.ts
🧠 Learnings (1)
📚 Learning: 2025-10-15T16:39:55.348Z
Learnt from: jzsfkzm
Repo: akash-network/console PR: 2039
File: apps/deploy-web/tests/ui/change-wallets.spec.ts:4-10
Timestamp: 2025-10-15T16:39:55.348Z
Learning: In the Akash Console E2E tests using the context-with-extension fixture, the first wallet is automatically created during fixture setup via `importWalletToLeap` in `apps/deploy-web/tests/ui/fixture/wallet-setup.ts`, so tests that call `frontPage.createWallet()` are creating a second wallet to test wallet switching functionality.
Applied to files:
apps/provider-console/src/components/wallet/WalletPopup.tsx
🧬 Code graph analysis (3)
apps/provider-console/src/components/layout/WalletStatus.tsx (6)
apps/provider-console/src/context/WalletProvider/WalletProvider.tsx (1)
useWallet(340-342)apps/provider-console/src/hooks/useWalletBalance.ts (1)
useTotalWalletBalance(11-26)apps/provider-console/src/utils/styleUtils.ts (1)
cn(4-6)apps/provider-console/src/components/wallet/WalletPopup.tsx (1)
WalletPopup(16-67)apps/provider-console/src/components/wallet/ConnectWalletButton.tsx (1)
ConnectWalletButton(16-24)packages/ui/components/spinner.tsx (1)
Spinner(10-39)
apps/provider-console/src/components/wallet/WalletPopup.tsx (2)
apps/provider-console/src/utils/constants.ts (1)
uAktDenom(23-23)packages/ui/components/custom/address.tsx (1)
Address(25-57)
apps/provider-console/src/queries/useMarketData.ts (1)
apps/provider-console/src/types/dashboard.ts (1)
MarketData(100-107)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: test-build
🔇 Additional comments (1)
apps/provider-console/src/components/wallet/WalletPopup.tsx (1)
33-54: Nice resilience message path.Thanks for keeping the blockchain-unavailable fallback inside the balance panel; it preserves context for the user.
| <DropdownMenu modal={false} open={open}> | ||
| <DropdownMenuTrigger asChild> | ||
| <div | ||
| className={cn("bg-background text-foreground flex items-center justify-center rounded-md border px-4 py-2 text-sm")} | ||
| onMouseOver={() => setOpen(true)} | ||
| > | ||
| <div className="flex items-center space-x-2" aria-label="Connected wallet name and balance"> | ||
| <Wallet className="text-xs" /> | ||
| {walletName?.length > 20 ? ( | ||
| <span className="text-xs">{getSplitText(walletName, 4, 4)}</span> | ||
| ) : ( | ||
| <span className="text-xs">{walletName}</span> | ||
| )} | ||
| </div> | ||
|
|
||
| {walletBalance > 0 && <div className="px-2">|</div>} | ||
|
|
||
| <div className="text-xs"> | ||
| {walletBalance > 0 && ( | ||
| <FormattedNumber | ||
| value={walletBalance} | ||
| // eslint-disable-next-line react/style-prop-object | ||
| style="currency" | ||
| currency="USD" | ||
| /> | ||
| )} | ||
| </div> | ||
|
|
||
| <div> | ||
| <NavArrowDown className="ml-2 text-xs" /> | ||
| </div> | ||
| </div> | ||
| </DropdownMenuTrigger> | ||
| <DropdownMenuContent | ||
| align="end" | ||
| onMouseLeave={() => { | ||
| setOpen(false); | ||
| }} | ||
| > | ||
| <ClickAwayListener | ||
| onClickAway={() => { | ||
| setOpen(false); | ||
| }} | ||
| > | ||
| <div> | ||
| <WalletPopup walletBalances={walletBalances} /> | ||
| </div> | ||
| </ClickAwayListener> | ||
| </DropdownMenuContent> |
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.
Restore an accessible trigger for the dropdown.
With the trigger now being a plain <div> that only sets open on onMouseOver, touch users can’t open the wallet menu at all and keyboard users can’t focus/activate it, so they lose the ability to see balances or disconnect. Please use a focusable button (or let the Radix trigger manage state) and wire onOpenChange so pointer, keyboard, and touch interactions all work.
- <DropdownMenu modal={false} open={open}>
+ <DropdownMenu modal={false} open={open} onOpenChange={setOpen}>
<DropdownMenuTrigger asChild>
- <div
- className={cn("bg-background text-foreground flex items-center justify-center rounded-md border px-4 py-2 text-sm")}
- onMouseOver={() => setOpen(true)}
- >
+ <button
+ type="button"
+ className={cn("bg-background text-foreground flex items-center justify-center rounded-md border px-4 py-2 text-sm")}
+ onMouseEnter={() => setOpen(true)}
+ onFocus={() => setOpen(true)}
+ onClick={() => setOpen((prev) => !prev)}
+ >
...
- </div>
+ </button>
</DropdownMenuTrigger>📝 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.
| <DropdownMenu modal={false} open={open}> | |
| <DropdownMenuTrigger asChild> | |
| <div | |
| className={cn("bg-background text-foreground flex items-center justify-center rounded-md border px-4 py-2 text-sm")} | |
| onMouseOver={() => setOpen(true)} | |
| > | |
| <div className="flex items-center space-x-2" aria-label="Connected wallet name and balance"> | |
| <Wallet className="text-xs" /> | |
| {walletName?.length > 20 ? ( | |
| <span className="text-xs">{getSplitText(walletName, 4, 4)}</span> | |
| ) : ( | |
| <span className="text-xs">{walletName}</span> | |
| )} | |
| </div> | |
| {walletBalance > 0 && <div className="px-2">|</div>} | |
| <div className="text-xs"> | |
| {walletBalance > 0 && ( | |
| <FormattedNumber | |
| value={walletBalance} | |
| // eslint-disable-next-line react/style-prop-object | |
| style="currency" | |
| currency="USD" | |
| /> | |
| )} | |
| </div> | |
| <div> | |
| <NavArrowDown className="ml-2 text-xs" /> | |
| </div> | |
| </div> | |
| </DropdownMenuTrigger> | |
| <DropdownMenuContent | |
| align="end" | |
| onMouseLeave={() => { | |
| setOpen(false); | |
| }} | |
| > | |
| <ClickAwayListener | |
| onClickAway={() => { | |
| setOpen(false); | |
| }} | |
| > | |
| <div> | |
| <WalletPopup walletBalances={walletBalances} /> | |
| </div> | |
| </ClickAwayListener> | |
| </DropdownMenuContent> | |
| <DropdownMenu modal={false} open={open} onOpenChange={setOpen}> | |
| <DropdownMenuTrigger asChild> | |
| <button | |
| type="button" | |
| className={cn("bg-background text-foreground flex items-center justify-center rounded-md border px-4 py-2 text-sm")} | |
| onMouseEnter={() => setOpen(true)} | |
| onFocus={() => setOpen(true)} | |
| onClick={() => setOpen((prev) => !prev)} | |
| > | |
| <div className="flex items-center space-x-2" aria-label="Connected wallet name and balance"> | |
| <Wallet className="text-xs" /> | |
| {walletName?.length > 20 ? ( | |
| <span className="text-xs">{getSplitText(walletName, 4, 4)}</span> | |
| ) : ( | |
| <span className="text-xs">{walletName}</span> | |
| )} | |
| </div> | |
| {walletBalance > 0 && <div className="px-2">|</div>} | |
| <div className="text-xs"> | |
| {walletBalance > 0 && ( | |
| <FormattedNumber | |
| value={walletBalance} | |
| // eslint-disable-next-line react/style-prop-object | |
| style="currency" | |
| currency="USD" | |
| /> | |
| )} | |
| </div> | |
| <div> | |
| <NavArrowDown className="ml-2 text-xs" /> | |
| </div> | |
| </button> | |
| </DropdownMenuTrigger> | |
| <DropdownMenuContent | |
| align="end" | |
| onMouseLeave={() => { | |
| setOpen(false); | |
| }} | |
| > | |
| <ClickAwayListener | |
| onClickAway={() => { | |
| setOpen(false); | |
| }} | |
| > | |
| <div> | |
| <WalletPopup walletBalances={walletBalances} /> | |
| </div> | |
| </ClickAwayListener> | |
| </DropdownMenuContent> |
🤖 Prompt for AI Agents
In apps/provider-console/src/components/layout/WalletStatus.tsx around lines 30
to 78, the dropdown trigger is a non-focusable div using onMouseOver which
breaks keyboard and touch accessibility; replace the div trigger with a
focusable element (e.g., a <button> via DropdownMenuTrigger asChild) or allow
Radix to manage state, and wire the DropdownMenu onOpenChange prop to update
local open state so pointer, keyboard (Enter/Space), and touch interactions
work; ensure the trigger remains styled the same, is focusable, and includes
appropriate aria-label/role for screen readers.
| const coinbasePrice = data.tickers.find((ticker: any) => ticker.market.name === "Coinbase Exchange"); | ||
| const price = coinbasePrice ? parseFloat(coinbasePrice.converted_last.usd) : 0; | ||
|
|
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
Replace any with typed CoinGecko response.
any is disallowed here and it hides the risk of dereferencing missing fields when Coinbase data is absent. Let’s type the response and normalise the USD value before using it so we stay type-safe and avoid toFixed on undefined.
import type { QueryKey, UseQueryOptions } from "@tanstack/react-query";
import { useQuery } from "@tanstack/react-query";
import type { MarketData } from "@src/types";
import { QueryKeys } from "./queryKeys";
+
+type CoinGeckoTicker = {
+ market: { name: string };
+ converted_last: { usd: number | string };
+};
+
+type CoinGeckoResponse = {
+ tickers: CoinGeckoTicker[];
+};
+
+function extractCoinbaseUsd(tickers: CoinGeckoTicker[]): number | null {
+ const usd = tickers.find((ticker) => ticker.market.name === "Coinbase Exchange")?.converted_last.usd;
+ const numericUsd = typeof usd === "string" ? Number.parseFloat(usd) : usd;
+ return typeof numericUsd === "number" && Number.isFinite(numericUsd) ? numericUsd : null;
+}
async function getMarketData(): Promise<MarketData> {
try {
- const response = await fetch("https://api.coingecko.com/api/v3/coins/akash-network/tickers");
- const data = await response.json();
- const coinbasePrice = data.tickers.find((ticker: any) => ticker.market.name === "Coinbase Exchange");
- const price = coinbasePrice ? parseFloat(coinbasePrice.converted_last.usd) : 0;
+ const response = await fetch("https://api.coingecko.com/api/v3/coins/akash-network/tickers");
+ const data: CoinGeckoResponse = await response.json();
+ const price = extractCoinbaseUsd(data.tickers) ?? 0;
return {
price,
@@
async function getAKTPrice(): Promise<{ aktPrice: string }> {
const response = await fetch("https://api.coingecko.com/api/v3/coins/akash-network/tickers");
- const data = await response.json();
- const coinbasePrice = data.tickers.find((ticker: any) => ticker.market.name === "Coinbase Exchange");
+ const data: CoinGeckoResponse = await response.json();
+ const usdPrice = extractCoinbaseUsd(data.tickers);
return {
- aktPrice: coinbasePrice ? coinbasePrice.converted_last.usd.toFixed(2) : "N/A"
+ aktPrice: usdPrice !== null ? usdPrice.toFixed(2) : "N/A"
};
}As per coding guidelines
Also applies to: 46-48
🤖 Prompt for AI Agents
In apps/provider-console/src/queries/useMarketData.ts around lines 11-13 (and
similarly at lines 46-48), the code uses `any` for the CoinGecko response and
directly dereferences fields which can be absent; define a proper TypeScript
interface for the CoinGecko response (ticker, market.name, converted_last.usd)
and use it instead of `any`, then safely read the USD value using optional
chaining and a default (e.g., const usd = ticker?.converted_last?.usd ?? "0"),
parse/normalize that value to a number (parseFloat or Number) before any math or
toFixed, and finally use the normalized number (or 0) so toFixed is never called
on undefined; update the other occurrences at lines 46-48 the same way.
Summary by CodeRabbit
New Features
Improvements