From 055bc04d22f96164daac818599bcb4a246a5021c Mon Sep 17 00:00:00 2001 From: QSchlegel Date: Fri, 12 Dec 2025 15:39:51 +0100 Subject: [PATCH 1/3] Refactor button component and enhance error handling in DRep data fetching - Updated the Button component to use forwardRef and improved type definitions for better integration with ShadcnButton. - Enhanced error handling in WalletDataLoaderWrapper and DrepOverviewPage to gracefully manage 404 errors when fetching DRep metadata and details. - Removed unnecessary console logs in various components to clean up the codebase and improve performance. - Improved wallet balance fetching logic to handle errors more effectively, ensuring a smoother user experience. --- src/components/common/button.tsx | 36 ++- .../wallet-data-loader-wrapper.tsx | 9 +- .../homepage/governance/drep/id/index.tsx | 15 +- .../pages/homepage/governance/drep/index.tsx | 25 +- .../pages/homepage/wallets/index.tsx | 4 - .../governance/proposal/voteButtton.tsx | 8 +- .../pages/wallet/governance/proposals.tsx | 241 +++++------------- src/hooks/common.ts | 39 ++- src/hooks/useUserWallets.ts | 8 +- src/hooks/useWalletBalances.ts | 43 +--- src/utils/common.ts | 43 +++- src/utils/multisigSDK.ts | 3 - 12 files changed, 204 insertions(+), 270 deletions(-) diff --git a/src/components/common/button.tsx b/src/components/common/button.tsx index fd5f5ab1..32c26efe 100644 --- a/src/components/common/button.tsx +++ b/src/components/common/button.tsx @@ -1,18 +1,9 @@ import { Button as ShadcnButton } from "@/components/ui/button"; import { Loader } from "lucide-react"; -import { useState } from "react"; +import { useState, forwardRef } from "react"; +import type { ButtonProps as ShadcnButtonProps } from "@/components/ui/button"; -export default function Button({ - children, - onClick, - disabled = false, - loading, - className, - variant, - size, - asChild, - hold, -}: { +export interface ButtonProps { children: React.ReactNode; onClick?: () => void; disabled?: boolean; @@ -30,12 +21,26 @@ export default function Button({ size?: "default" | "sm" | "lg" | "icon" | null | undefined; asChild?: boolean | undefined; hold?: number; -}) { +} + +const Button = forwardRef(function Button({ + children, + onClick, + disabled = false, + loading, + className, + variant, + size, + asChild, + hold, + ...props +}, ref) { const [holding, setHolding] = useState(false); const [curTime, setCurTime] = useState(0); return ( {loading && } {children} @@ -69,4 +75,6 @@ export default function Button({ ` (Hold for ${Math.round((hold - (Date.now() - curTime)) / 1000)} secs)`} ); -} +}); + +export default Button; diff --git a/src/components/common/overall-layout/mobile-wrappers/wallet-data-loader-wrapper.tsx b/src/components/common/overall-layout/mobile-wrappers/wallet-data-loader-wrapper.tsx index 63983655..6421511b 100644 --- a/src/components/common/overall-layout/mobile-wrappers/wallet-data-loader-wrapper.tsx +++ b/src/components/common/overall-layout/mobile-wrappers/wallet-data-loader-wrapper.tsx @@ -173,11 +173,14 @@ export default function WalletDataLoaderWrapper({ ); if (!drepInfo) throw new Error(`No dRep for ID ${drepids.cip105} found.`); setDrepInfo(drepInfo); - } catch (err) { + } catch (err: any) { // DRep not found (404) is expected if DRep hasn't been registered yet // This is normal behavior - the DRep ID exists but isn't registered on-chain + const is404 = err?.response?.status === 404 || err?.data?.status_code === 404; + if (!is404) { + console.error(`Error fetching DRep info:`, err); + } setDrepInfo(undefined); - console.log(`DRep not yet registered on-chain (this is normal before registration)`); } } @@ -194,7 +197,6 @@ export default function WalletDataLoaderWrapper({ // Fetch all proxy data in parallel using the new batch function if (proxies.length > 0) { - console.log(`WalletDataLoaderWrapper: Fetching data for ${proxies.length} proxies in parallel`); await fetchAllProxyData( appWallet.id, proxies, @@ -202,7 +204,6 @@ export default function WalletDataLoaderWrapper({ network.toString(), false // Use cache to avoid duplicate requests ); - console.log("WalletDataLoaderWrapper: Successfully fetched all proxy data"); } } catch (error) { console.error("WalletDataLoaderWrapper: Error fetching proxy data:", error); diff --git a/src/components/pages/homepage/governance/drep/id/index.tsx b/src/components/pages/homepage/governance/drep/id/index.tsx index bb54a28b..525c944d 100644 --- a/src/components/pages/homepage/governance/drep/id/index.tsx +++ b/src/components/pages/homepage/governance/drep/id/index.tsx @@ -58,14 +58,21 @@ export default function DrepDetailPage() { metadata = await blockchainProvider.get( `/governance/dreps/${drepId}/metadata/`, ); - } catch { - console.warn(`No metadata found for DRep ${drepId}`); + } catch (err: any) { + // 404 is expected if metadata doesn't exist - silently ignore + const is404 = err?.response?.status === 404 || err?.data?.status_code === 404; + if (!is404) { + console.warn(`No metadata found for DRep ${drepId}:`, err); + } } setDrepInfo(details || null); setDrepMetadata(metadata || null); - } catch (error) { - console.error(`Failed to fetch DRep ${drepId} details:`, error); + } catch (error: any) { + const is404 = error?.response?.status === 404 || error?.data?.status_code === 404; + if (!is404) { + console.error(`Failed to fetch DRep ${drepId} details:`, error); + } } finally { setLoading(false); } diff --git a/src/components/pages/homepage/governance/drep/index.tsx b/src/components/pages/homepage/governance/drep/index.tsx index 3988aa03..766e8167 100644 --- a/src/components/pages/homepage/governance/drep/index.tsx +++ b/src/components/pages/homepage/governance/drep/index.tsx @@ -85,17 +85,30 @@ export default function DrepOverviewPage() { const details: BlockfrostDrepInfo = await blockchainProvider.get( `/governance/dreps/${drepId}`, ); - const metadata: BlockfrostDrepMetadata = await blockchainProvider.get( - `/governance/dreps/${drepId}/metadata/`, - ); + + let metadata: BlockfrostDrepMetadata | null = null; + try { + metadata = await blockchainProvider.get( + `/governance/dreps/${drepId}/metadata/`, + ); + } catch (err: any) { + // 404 is expected if metadata doesn't exist - silently ignore + const is404 = err?.response?.status === 404 || err?.data?.status_code === 404; + if (!is404) { + console.warn(`Failed to fetch metadata for DRep ${drepId}:`, err); + } + } setDrepList((prevList) => prevList.map((drep) => - drep.details.drep_id === drepId ? { details, metadata } : drep, + drep.details.drep_id === drepId ? { details, metadata: metadata || {} } : drep, ), ); - } catch (error) { - console.error(`Failed to fetch details for DREP ${drepId}:`, error); + } catch (error: any) { + const is404 = error?.response?.status === 404 || error?.data?.status_code === 404; + if (!is404) { + console.error(`Failed to fetch details for DREP ${drepId}:`, error); + } } }; diff --git a/src/components/pages/homepage/wallets/index.tsx b/src/components/pages/homepage/wallets/index.tsx index 614abb61..c3c961b1 100644 --- a/src/components/pages/homepage/wallets/index.tsx +++ b/src/components/pages/homepage/wallets/index.tsx @@ -82,10 +82,6 @@ export default function PageWallets() { .map((wallet) => { const walletBalance = balances[wallet.id] ?? null; const walletLoadingState = loadingStates[wallet.id] ?? "idle"; - // Debug log - if (process.env.NODE_ENV === "development") { - console.log(`Wallet ${wallet.id}: balance=${walletBalance}, loadingState=${walletLoadingState}`); - } return ( +
{!isProposalActive ? ( // Inactive proposal state
@@ -429,7 +429,7 @@ export default function VoteButton({ > {isOnAnyBallot ? ( <> - + {ballotCount > 0 && ( {ballotCount} @@ -437,7 +437,7 @@ export default function VoteButton({ )} ) : ( - + )} diff --git a/src/components/pages/wallet/governance/proposals.tsx b/src/components/pages/wallet/governance/proposals.tsx index 0c26c538..c2a60acd 100644 --- a/src/components/pages/wallet/governance/proposals.tsx +++ b/src/components/pages/wallet/governance/proposals.tsx @@ -298,11 +298,43 @@ export default function AllProposals({ appWallet, utxos, selectedBallotId, onSel const proposalId = p.tx_hash + "#" + p.cert_index; try { // Fetch both metadata and details in parallel + // 404 for metadata is expected if proposal doesn't have metadata - handle silently const [metadata, details] = await Promise.all([ - blockchainProvider.get(`/governance/proposals/${p.tx_hash}/${p.cert_index}/metadata`), + blockchainProvider.get(`/governance/proposals/${p.tx_hash}/${p.cert_index}/metadata`).catch((err: any) => { + const is404 = err?.response?.status === 404 || err?.data?.status_code === 404; + if (is404) { + return null; // 404 is expected - proposal has no metadata + } + throw err; // Re-throw non-404 errors + }), blockchainProvider.get(`/governance/proposals/${p.tx_hash}/${p.cert_index}`).catch(() => null) ]); + // If metadata is null (404), use default structure + if (!metadata) { + return { + key: proposalId, + metadata: { + tx_hash: p.tx_hash, + cert_index: Number(p.cert_index), + governance_type: p.governance_type, + hash: "", + url: "", + bytes: "", + json_metadata: { + body: { + title: "Metadata not available", + abstract: "", + motivation: "", + rationale: "", + references: [] + } + } + }, + details: details as ProposalDetails | null + }; + } + return { key: proposalId, metadata: { @@ -312,7 +344,7 @@ export default function AllProposals({ appWallet, utxos, selectedBallotId, onSel details: details as ProposalDetails | null }; } catch (e: any) { - // If metadata fetch fails, try to still get details + // If metadata fetch fails with non-404 error, try to still get details const details = await blockchainProvider.get(`/governance/proposals/${p.tx_hash}/${p.cert_index}`).catch(() => null); return { @@ -634,108 +666,7 @@ export default function AllProposals({ appWallet, utxos, selectedBallotId, onSel {/* Expandable Content */} {isExpanded && (
- {/* Proposal Details */} - {isLoadingDetails ? ( -
-
- Loading details... -
- ) : details && ( -
- {/* Status */} - {getProposalStatus(details) && (() => { - const status = getProposalStatus(details)!; - const StatusIcon = status.icon; - return ( -
-
- - Status -
- - - {status.label} - -
- ); - })()} - - {/* Governance Type */} -
-
- - Type -
-
- {details.governance_type.replace(/_/g, " ")} -
-
- - {/* Deposit */} - {details.deposit && ( -
-
- - Deposit -
-
- {(parseInt(details.deposit) / 1_000_000).toFixed(2)} ADA -
-
- )} - - {/* Ratified Epoch */} - {details.ratified_epoch && ( -
-
- - Ratified Epoch -
-
- {details.ratified_epoch} -
-
- )} - - {/* Enacted Epoch */} - {details.enacted_epoch && ( -
-
- - Enacted Epoch -
-
- {details.enacted_epoch} -
-
- )} - - {/* Expiration */} - {details.expiration && ( -
-
- - Expires In -
-
- {details.expiration} epochs -
-
- )} - - {/* Transaction Hash */} -
-
- - Transaction -
-
- {details.tx_hash} -
-
-
- )} - {/* Abstract - only show if not already visible */} + {/* Abstract - Mobile only */}

Abstract

@@ -745,28 +676,6 @@ export default function AllProposals({ appWallet, utxos, selectedBallotId, onSel
- {/* Only show additional details not already visible */} - {proposal.json_metadata.body.motivation && ( -
-

Motivation

-
- - {proposal.json_metadata.body.motivation} - -
-
- )} - {proposal.json_metadata.body.rationale && ( -
-

Rationale

-
- - {proposal.json_metadata.body.rationale} - -
-
- )} - {/* Voting History */} {vote && (
@@ -785,18 +694,18 @@ export default function AllProposals({ appWallet, utxos, selectedBallotId, onSel )} {/* Actions */} -
+
- -
- -
+
)} @@ -960,15 +867,24 @@ function ProposalRow({
e.stopPropagation()}> -
- +
+ {/* Voting Status Indicator */} + {voteDisplay && ( +
+ +
+ )} + {/* Action Buttons - Right-aligned for table */} +
+ +
@@ -984,24 +900,6 @@ function ProposalRow({
) : details && (
- {/* Status */} - {getProposalStatus(details) && (() => { - const status = getProposalStatus(details)!; - const StatusIcon = status.icon; - return ( -
-
- - Status -
- - - {status.label} - -
- ); - })()} - {/* Governance Type */}
@@ -1089,16 +987,7 @@ function ProposalRow({
)} - {/* Show full abstract in expanded view */} -
-

Abstract

-
- - {proposal.json_metadata.body.abstract} - -
-
-
+