From 06dd14477767e5e58d431682caf863c42d675b93 Mon Sep 17 00:00:00 2001 From: Peniel Samuel Date: Sat, 27 Jun 2026 11:25:27 +0000 Subject: [PATCH] fix: address 4 issues - wallet checksum validation, route error boundaries, DataRefreshWrapper timer cleanup, wallet emoji icons - #429: Add assertValidAddress with EIP-55 checksum validation in walletHelpers.ts - #428: Wrap 7 route pages with withRouteErrorBoundary for graceful error handling - #435: Fix DataRefreshWrapper timer stacking with useRef cleanup + unmount cleanup - #434: Replace emoji wallet icons with lucide-react SVGs and add aria-labels --- src/app/compare/page.tsx | 5 +- src/app/developers/tokenize/page.tsx | 5 +- src/app/governance/page.tsx | 5 +- src/app/mobile-properties/page.tsx | 5 +- src/app/secondary-market/page.tsx | 5 +- src/app/tax-report/page.tsx | 5 +- src/app/widget/investment-calculator/page.tsx | 5 +- src/components/WalletModal.tsx | 14 +- .../dashboard/DataRefreshWrapper.tsx | 24 ++- .../__tests__/DataRefreshWrapper.test.tsx | 194 ++++++++++++++++++ src/utils/__tests__/walletHelpers.test.ts | 101 +++++++++ src/utils/walletHelpers.ts | 54 +++++ 12 files changed, 406 insertions(+), 16 deletions(-) create mode 100644 src/components/dashboard/__tests__/DataRefreshWrapper.test.tsx create mode 100644 src/utils/__tests__/walletHelpers.test.ts create mode 100644 src/utils/walletHelpers.ts diff --git a/src/app/compare/page.tsx b/src/app/compare/page.tsx index 0ceaacf2..13f8533c 100644 --- a/src/app/compare/page.tsx +++ b/src/app/compare/page.tsx @@ -7,6 +7,7 @@ import { ArrowLeft, Share2, Download, Clock, Trash2, FileText } from 'lucide-rea import { propertyService } from '@/lib/propertyService'; import { useComparisonHistoryStore } from '@/store/comparisonHistoryStore'; import { useComparisonStore } from '@/store/comparisonStore'; +import { withRouteErrorBoundary } from '@/components/error/withRouteErrorBoundary'; import type { Property } from '@/types/property'; import { formatPrice, formatROI } from '@/utils/searchUtils'; @@ -93,7 +94,7 @@ function getBestValue(properties: Property[], metric: ComparisonMetric): number return metric.higherIsBetter ? Math.max(...numericValues) : Math.min(...numericValues); } -export default function ComparePage() { +function ComparePage() { const router = useRouter(); const searchParams = useSearchParams(); const { selectedProperties, clearProperties } = useComparisonStore(); @@ -403,3 +404,5 @@ export default function ComparePage() { ); } + +export default withRouteErrorBoundary(ComparePage, { routeName: 'compare' }); diff --git a/src/app/developers/tokenize/page.tsx b/src/app/developers/tokenize/page.tsx index e8db083a..62e6c507 100644 --- a/src/app/developers/tokenize/page.tsx +++ b/src/app/developers/tokenize/page.tsx @@ -2,6 +2,7 @@ import React, { useState } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; +import { withRouteErrorBoundary } from '@/components/error/withRouteErrorBoundary'; import { Building2, Coins, @@ -32,7 +33,7 @@ const STEPS = [ { id: 5, title: 'Review & Submit', icon: CheckCircle2 }, ]; -export default function TokenizationWizardPage() { +function TokenizationWizardPage() { const [currentStep, setCurrentStep] = useState(1); const [formData, setFormData] = useState({ name: '', @@ -382,3 +383,5 @@ export default function TokenizationWizardPage() { ); } + +export default withRouteErrorBoundary(TokenizationWizardPage, { routeName: 'developers/tokenize' }); diff --git a/src/app/governance/page.tsx b/src/app/governance/page.tsx index 7cd44989..c80fab09 100644 --- a/src/app/governance/page.tsx +++ b/src/app/governance/page.tsx @@ -3,6 +3,7 @@ import React, { useState, useEffect, useCallback } from 'react'; import Link from 'next/link'; import { WalletConnector } from '@/components/WalletConnector'; +import { withRouteErrorBoundary } from '@/components/error/withRouteErrorBoundary'; // ─── Types ──────────────────────────────────────────────────────────────────── @@ -235,7 +236,7 @@ function ProposalCard({ // ─── Page ───────────────────────────────────────────────────────────────────── -export default function GovernancePage() { +function GovernancePage() { const [proposals, setProposals] = useState(MOCK_PROPOSALS); const [userVotes, setUserVotes] = useState>({}); const [filter, setFilter] = useState<'all' | Proposal['status']>('all'); @@ -331,3 +332,5 @@ export default function GovernancePage() { ); } + +export default withRouteErrorBoundary(GovernancePage, { routeName: 'governance' }); diff --git a/src/app/mobile-properties/page.tsx b/src/app/mobile-properties/page.tsx index 40215fce..cad13b47 100644 --- a/src/app/mobile-properties/page.tsx +++ b/src/app/mobile-properties/page.tsx @@ -3,6 +3,7 @@ import React, { useState, useEffect } from "react"; import dynamic from "next/dynamic"; import Image from "next/image"; +import { withRouteErrorBoundary } from '@/components/error/withRouteErrorBoundary'; import { Search, MapPin, @@ -211,7 +212,7 @@ const properties: MobileProperty[] = [ }, ]; -export default function MobilePropertiesPage() { +function MobilePropertiesPage() { const [activeTab, setActiveTab] = useState("browse"); const [searchQuery, setSearchQuery] = useState(""); const [viewMode, setViewMode] = useState<"grid" | "list">("grid"); @@ -463,6 +464,8 @@ export default function MobilePropertiesPage() { ); } +export default withRouteErrorBoundary(MobilePropertiesPage, { routeName: 'mobile-properties' }); + function SectionSkeleton() { return (
diff --git a/src/app/secondary-market/page.tsx b/src/app/secondary-market/page.tsx index bf824358..66706cfd 100644 --- a/src/app/secondary-market/page.tsx +++ b/src/app/secondary-market/page.tsx @@ -6,11 +6,12 @@ import { SecondaryMarketListing } from '@/types/property'; import { WalletConnector } from '@/components/WalletConnector'; import { Button } from '@/components/ui/button'; import { LoadingSpinner } from '@/components/LoadingSpinner'; +import { withRouteErrorBoundary } from '@/components/error/withRouteErrorBoundary'; import Link from 'next/link'; import Image from 'next/image'; import { toast } from 'sonner'; -export default function SecondaryMarketPage() { +function SecondaryMarketPage() { const [listings, setListings] = useState([]); const [isLoading, setIsLoading] = useState(true); @@ -137,3 +138,5 @@ export default function SecondaryMarketPage() {
); } + +export default withRouteErrorBoundary(SecondaryMarketPage, { routeName: 'secondary-market' }); diff --git a/src/app/tax-report/page.tsx b/src/app/tax-report/page.tsx index df920689..297f88b3 100644 --- a/src/app/tax-report/page.tsx +++ b/src/app/tax-report/page.tsx @@ -3,6 +3,7 @@ import React, { useState } from 'react'; import Link from 'next/link'; import { WalletConnector } from '@/components/WalletConnector'; +import { withRouteErrorBoundary } from '@/components/error/withRouteErrorBoundary'; // ─── Types ──────────────────────────────────────────────────────────────────── @@ -180,7 +181,7 @@ async function generatePDF( // ─── Component ──────────────────────────────────────────────────────────────── -export default function TaxReportPage() { +function TaxReportPage() { const [taxYear, setTaxYear] = useState('2024'); const [method, setMethod] = useState('FIFO'); const [generating, setGenerating] = useState(false); @@ -358,3 +359,5 @@ export default function TaxReportPage() { ); } + +export default withRouteErrorBoundary(TaxReportPage, { routeName: 'tax-report' }); diff --git a/src/app/widget/investment-calculator/page.tsx b/src/app/widget/investment-calculator/page.tsx index a8e94c5b..d88647cf 100644 --- a/src/app/widget/investment-calculator/page.tsx +++ b/src/app/widget/investment-calculator/page.tsx @@ -3,8 +3,9 @@ import React, { useEffect, useState } from 'react'; import { useSearchParams } from 'next/navigation'; import { InvestmentCalculatorWidget } from '@/components/widget/InvestmentCalculatorWidget'; +import { withRouteErrorBoundary } from '@/components/error/withRouteErrorBoundary'; -export default function InvestmentCalculatorEmbedPage() { +function InvestmentCalculatorEmbedPage() { const searchParams = useSearchParams(); const [mounted, setMounted] = useState(false); @@ -46,3 +47,5 @@ export default function InvestmentCalculatorEmbedPage() { ); } + +export default withRouteErrorBoundary(InvestmentCalculatorEmbedPage, { routeName: 'widget/investment-calculator' }); diff --git a/src/components/WalletModal.tsx b/src/components/WalletModal.tsx index bbe9f1df..e08645d4 100644 --- a/src/components/WalletModal.tsx +++ b/src/components/WalletModal.tsx @@ -6,7 +6,7 @@ import { getWalletErrorMessage } from '@/utils/errorHandling'; import { toChainId } from '@/config/chains'; import { useSecurity } from '@/hooks/useSecurity'; import { useWalletConnector } from '@/hooks/useWalletConnector'; -import { AlertTriangle, Shield, X, CheckCircle, AlertCircle, Loader2 } from 'lucide-react'; +import { AlertTriangle, Shield, X, CheckCircle, AlertCircle, Loader2, Wallet, Link2, QrCode } from 'lucide-react'; import { motion, AnimatePresence } from 'framer-motion'; import { ModalTransition } from './PageTransition'; @@ -199,7 +199,7 @@ export const WalletModal: React.FC = ({ isOpen, onClose }) => id: SupportedWalletId; name: string; description: string; - icon: string; + icon: React.ReactNode; color: string; installUrl?: string; }> = [ @@ -207,7 +207,7 @@ export const WalletModal: React.FC = ({ isOpen, onClose }) => id: 'metamask', name: 'MetaMask', description: 'Connect to your MetaMask wallet', - icon: '🦊', + icon: