diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 044c66c6..f4dd9d2b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -252,6 +252,9 @@ importers: '@tailwindcss/postcss': specifier: ^4 version: 4.2.4 + '@testing-library/dom': + specifier: ^10.4.1 + version: 10.4.1 '@testing-library/jest-dom': specifier: ^6.5.0 version: 6.9.1 @@ -314,7 +317,7 @@ importers: version: 10.3.5(eslint@9.39.4(jiti@2.6.1))(storybook@10.3.5(@testing-library/dom@10.4.1)(bufferutil@4.1.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(utf-8-validate@5.0.10))(typescript@5.9.3) jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.19.39) + version: 29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)) jest-axe: specifier: ^10.0.0 version: 10.0.0 @@ -336,11 +339,14 @@ importers: tailwindcss: specifier: ^4.1.4 version: 4.2.4 + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@20.19.39)(typescript@5.9.3) tw-animate-css: specifier: ^1.4.0 version: 1.4.0 typescript: - specifier: ^5 + specifier: ^5.9.3 version: 5.9.3 vite: specifier: ^8.0.3 @@ -565,6 +571,10 @@ packages: '@coinbase/wallet-sdk@4.3.7': resolution: {integrity: sha512-z6e5XDw6EF06RqkeyEa+qD0dZ2ZbLci99vx3zwDY//XO8X7166tqKJrR2XlQnzVmtcUuJtCd5fCvr9Cu6zzX7w==} + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + '@cypress/react@9.0.2': resolution: {integrity: sha512-b20a0g6Ot3u92wdmDD+4/r5NkAKPJz+yN2miuydDwy63Hzpk9fLQ0tee5xzx/0VPxYrh3dzedoO8dwUR3qpBlg==} peerDependencies: @@ -1142,6 +1152,9 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@mdx-js/react@3.1.1': resolution: {integrity: sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==} peerDependencies: @@ -2458,6 +2471,18 @@ packages: resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} engines: {node: '>= 10'} + '@tsconfig/node10@1.0.12': + resolution: {integrity: sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==} + + '@tsconfig/node12@1.0.11': + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + + '@tsconfig/node14@1.0.3': + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + + '@tsconfig/node16@1.0.4': + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} @@ -3160,6 +3185,9 @@ packages: arch@2.2.0: resolution: {integrity: sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==} + arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} @@ -3682,6 +3710,9 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true + create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + cross-fetch@2.2.6: resolution: {integrity: sha512-9JZz+vXCmfKUZ68zAptS7k4Nu8e2qcibe7WVZYps7sAgk5R8GYTc+T1WR0v1rlP9HxgARmOX1UTIJZFytajpNA==} @@ -3912,6 +3943,10 @@ packages: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + diff@4.0.4: + resolution: {integrity: sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==} + engines: {node: '>=0.3.1'} + dijkstrajs@1.0.3: resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==} @@ -5487,6 +5522,9 @@ packages: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + makeerror@1.0.12: resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} @@ -6748,6 +6786,20 @@ packages: resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} engines: {node: '>=6.10'} + ts-node@10.9.2: + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + tsconfck@3.1.6: resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} engines: {node: ^18 || >=20} @@ -6934,6 +6986,9 @@ packages: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} hasBin: true + v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + v8-to-istanbul@9.3.0: resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} engines: {node: '>=10.12.0'} @@ -7299,6 +7354,10 @@ packages: yauzl@2.10.0: resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -7606,6 +7665,10 @@ snapshots: - utf-8-validate - zod + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + '@cypress/react@9.0.2(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(cypress@15.14.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: '@types/react-dom': 19.2.3(@types/react@19.2.14) @@ -7992,7 +8055,7 @@ snapshots: jest-util: 29.7.0 slash: 3.0.0 - '@jest/core@29.7.0': + '@jest/core@29.7.0(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3))': dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0 @@ -8006,7 +8069,7 @@ snapshots: exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.19.39) + jest-config: 29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -8172,6 +8235,11 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + '@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.5)': dependencies: '@types/mdx': 2.0.13 @@ -9507,6 +9575,14 @@ snapshots: '@tootallnate/once@2.0.0': {} + '@tsconfig/node10@1.0.12': {} + + '@tsconfig/node12@1.0.11': {} + + '@tsconfig/node14@1.0.3': {} + + '@tsconfig/node16@1.0.4': {} + '@tybys/wasm-util@0.10.1': dependencies: tslib: 2.8.1 @@ -10287,6 +10363,8 @@ snapshots: arch@2.2.0: {} + arg@4.1.3: {} + argparse@1.0.10: dependencies: sprintf-js: 1.0.3 @@ -10844,13 +10922,13 @@ snapshots: safe-buffer: 5.2.1 sha.js: 2.4.12 - create-jest@29.7.0(@types/node@20.19.39): + create-jest@29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.19.39) + jest-config: 29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -10859,6 +10937,8 @@ snapshots: - supports-color - ts-node + create-require@1.1.1: {} + cross-fetch@2.2.6: dependencies: node-fetch: 2.7.0 @@ -11094,6 +11174,8 @@ snapshots: diff-sequences@29.6.3: {} + diff@4.0.4: {} + dijkstrajs@1.0.3: {} doctrine@2.1.0: @@ -12548,16 +12630,16 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@20.19.39): + jest-cli@29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)): dependencies: - '@jest/core': 29.7.0 + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.19.39) + create-jest: 29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@20.19.39) + jest-config: 29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -12567,7 +12649,7 @@ snapshots: - supports-color - ts-node - jest-config@29.7.0(@types/node@20.19.39): + jest-config@29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)): dependencies: '@babel/core': 7.29.0 '@jest/test-sequencer': 29.7.0 @@ -12593,6 +12675,7 @@ snapshots: strip-json-comments: 3.1.1 optionalDependencies: '@types/node': 20.19.39 + ts-node: 10.9.2(@types/node@20.19.39)(typescript@5.9.3) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -12834,12 +12917,12 @@ snapshots: merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.7.0(@types/node@20.19.39): + jest@29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)): dependencies: - '@jest/core': 29.7.0 + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@20.19.39) + jest-cli: 29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -13178,6 +13261,8 @@ snapshots: dependencies: semver: 7.7.4 + make-error@1.3.6: {} + makeerror@1.0.12: dependencies: tmpl: 1.0.5 @@ -14542,6 +14627,24 @@ snapshots: ts-dedent@2.2.0: {} + ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.12 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 20.19.39 + acorn: 8.16.0 + acorn-walk: 8.3.5 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.4 + make-error: 1.3.6 + typescript: 5.9.3 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + tsconfck@3.1.6(typescript@5.9.3): optionalDependencies: typescript: 5.9.3 @@ -14750,6 +14853,8 @@ snapshots: uuid@9.0.1: {} + v8-compile-cache-lib@3.0.1: {} + v8-to-istanbul@9.3.0: dependencies: '@jridgewell/trace-mapping': 0.3.31 @@ -15154,6 +15259,8 @@ snapshots: buffer-crc32: 0.2.13 fd-slicer: 1.1.0 + yn@3.1.1: {} + yocto-queue@0.1.0: {} zod-validation-error@4.0.2(zod@4.3.6): diff --git a/src/app/properties/page.tsx b/src/app/properties/page.tsx index 0d8b9d57..82a2b89b 100644 --- a/src/app/properties/page.tsx +++ b/src/app/properties/page.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { Suspense, useEffect } from "react"; +import React, { Suspense, useCallback, useEffect } from "react"; import { SearchFilterForm } from "@/components/forms/SearchFilterForm"; import { SearchResults } from "@/components/SearchResults"; import { WalletConnector } from "@/components/WalletConnector"; @@ -13,6 +13,7 @@ import { useWalletStore } from "@/store/walletStore"; import { useNotificationChecker } from "@/hooks/useNotificationChecker"; import { useFavoritesStore } from "@/store/favoritesStore"; import { usePaginationParams, isValidPageSize, type PageSize } from "@/hooks/usePaginationParams"; +import type { SortOption } from "@/types/property"; import Link from "next/link"; import { Heart } from "lucide-react"; import PropertyPageSkeleton from "@/components/PropertyPageSkeleton"; @@ -29,7 +30,6 @@ function PropertiesContent() { // Ensure viewMode is only 'grid' or 'list' for now (map view not implemented yet) const viewMode: "grid" | "list" = storeViewMode === "map" ? "grid" : storeViewMode; - const setViewMode = (mode: "grid" | "list") => setStoreViewMode(mode); const { favorites } = useFavoritesStore(); @@ -68,14 +68,33 @@ function PropertiesContent() { }, [urlSize]); // eslint-disable-line react-hooks/exhaustive-deps // Page change: update URL (which triggers the effect above to sync the store) - const handlePageChange = (newPage: number) => { + const handlePageChange = useCallback((newPage: number) => { setUrlPage(newPage); - }; + }, [setUrlPage]); // Page size change: update URL (resets to page 1 inside setUrlSize) - const handlePageSizeChange = (newSize: PageSize) => { + const handlePageSizeChange = useCallback((newSize: PageSize) => { setUrlSize(newSize); - }; + }, [setUrlSize]); + + const handleSortChange = useCallback((newSort: SortOption) => { + setSortBy(newSort); + setUrlPage(1); + }, [setSortBy, setUrlPage]); + + const handleViewModeChange = useCallback((mode: "grid" | "list") => { + setStoreViewMode(mode); + }, [setStoreViewMode]); + + const handleApplyFilters = useCallback((newFilters: typeof filters) => { + setFilters(newFilters); + setUrlPage(1); + }, [setFilters, setUrlPage]); + + const handleClearFilters = useCallback(() => { + clearFilters(); + setUrlPage(1); + }, [clearFilters, setUrlPage]); return (
@@ -137,15 +156,8 @@ function PropertiesContent() { { - // Apply full filter object and reset to page 1 - setFilters(newFilters); - setUrlPage(1); - }} - onClearFilters={() => { - clearFilters(); - setUrlPage(1); - }} + onApplyFilters={handleApplyFilters} + onClearFilters={handleClearFilters} />
@@ -162,11 +174,8 @@ function PropertiesContent() { totalPages={totalPages} pageSize={urlSize} filters={filters} - onViewModeChange={setViewMode} - onSortChange={(newSort) => { - setSortBy(newSort); - setUrlPage(1); - }} + onViewModeChange={handleViewModeChange} + onSortChange={handleSortChange} onPageChange={handlePageChange} onPageSizeChange={handlePageSizeChange} buildPageHref={buildHref} diff --git a/src/components/FilterSidebar.tsx b/src/components/FilterSidebar.tsx index 8940d99e..7b76cf8d 100644 --- a/src/components/FilterSidebar.tsx +++ b/src/components/FilterSidebar.tsx @@ -10,7 +10,7 @@ interface FilterSidebarProps { onClearFilters: () => void; } -export const FilterSidebar: React.FC = ({ +const FilterSidebarInner: React.FC = ({ filters, onFilterChange, onClearFilters, @@ -304,3 +304,5 @@ export const FilterSidebar: React.FC = ({ ); }; + +export const FilterSidebar = React.memo(FilterSidebarInner); diff --git a/src/components/MultiChainPortfolio.tsx b/src/components/MultiChainPortfolio.tsx index faf66c7e..54604839 100644 --- a/src/components/MultiChainPortfolio.tsx +++ b/src/components/MultiChainPortfolio.tsx @@ -1,6 +1,6 @@ 'use client'; -import React, { useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { RefreshCw, TrendingUp, AlertTriangle, ExternalLink, Filter, Wallet, Briefcase } from 'lucide-react'; import { usePortfolioStore } from '@/store/portfolioStore'; @@ -10,7 +10,7 @@ import { formatPrice } from '@/utils/searchUtils'; import type { ChainPortfolio, BridgeSuggestion } from '@/types/portfolio'; import { EmptyState } from '@/components/ui/EmptyState'; -export const MultiChainPortfolio: React.FC = () => { +const MultiChainPortfolioInner: React.FC = () => { const { t, i18n } = useTranslation(); const { portfolio, @@ -42,9 +42,9 @@ export const MultiChainPortfolio: React.FC = () => { return portfolio.chains.filter(chain => chain.chainId === selectedChain); }, [portfolio, selectedChain]); - const handleRefresh = () => { + const handleRefresh = useCallback(() => { refreshPortfolio(); - }; + }, [refreshPortfolio]); if (!isConnected) { return ( @@ -217,8 +217,8 @@ export const MultiChainPortfolio: React.FC = () => { {showBridgeSuggestions && (
- {bridgeSuggestions.map((suggestion, index) => ( - + {bridgeSuggestions.map((suggestion) => ( + ))}
)} @@ -228,6 +228,8 @@ export const MultiChainPortfolio: React.FC = () => { ); }; +export const MultiChainPortfolio = React.memo(MultiChainPortfolioInner); + interface ChainPortfolioCardProps { chain: ChainPortfolio; } diff --git a/src/components/PropertyCard.tsx b/src/components/PropertyCard.tsx index 5295ca42..0614baf0 100644 --- a/src/components/PropertyCard.tsx +++ b/src/components/PropertyCard.tsx @@ -1,6 +1,6 @@ 'use client'; -import React from 'react'; +import React, { useCallback } from 'react'; import Image from 'next/image'; import Link from 'next/link'; import { ShoppingCart, Plus, CheckSquare, Square, Heart } from 'lucide-react'; @@ -19,7 +19,7 @@ interface PropertyCardProps { viewMode?: 'grid' | 'list'; } -export const PropertyCard: React.FC = ({ +const PropertyCardInner: React.FC = ({ property, viewMode = 'grid' }) => { @@ -35,31 +35,35 @@ export const PropertyCard: React.FC = ({ const compareLimitReached = selectedIds.length >= 3 && !isCompared; const { addFavorite, removeFavorite, isFavorite } = useFavoritesStore(); - const handleAddToCart = (e: React.MouseEvent) => { + const handleAddToCart = useCallback((e: React.MouseEvent) => { + e.preventDefault(); e.stopPropagation(); addItem(property, 1); - }; + }, [addItem, property]); - const handleComparisonToggle = (e: React.MouseEvent) => { + const handleComparisonToggle = useCallback((e: React.MouseEvent) => { + e.preventDefault(); e.stopPropagation(); toggleProperty(property); - }; + }, [toggleProperty, property]); - const handleCompareToggle = (e: React.MouseEvent) => { + const handleCompareToggle = useCallback((e: React.MouseEvent) => { + e.preventDefault(); e.stopPropagation(); if (!compareLimitReached) { togglePropertyId(property.id); } - }; + }, [compareLimitReached, togglePropertyId, property.id]); - const handleToggleFavorite = (e: React.MouseEvent) => { + const handleToggleFavorite = useCallback((e: React.MouseEvent) => { + e.preventDefault(); e.stopPropagation(); if (isFavorite(property.id)) { removeFavorite(property.id); } else { addFavorite(property); } - }; + }, [isFavorite, removeFavorite, addFavorite, property]); return (
= ({
); }; + +export const PropertyCard = React.memo(PropertyCardInner); diff --git a/src/components/SearchResults.tsx b/src/components/SearchResults.tsx index aded03c7..53f55eb3 100644 --- a/src/components/SearchResults.tsx +++ b/src/components/SearchResults.tsx @@ -32,7 +32,7 @@ interface SearchResultsProps { buildPageHref?: (page: number) => string; } -export const SearchResults: React.FC = ({ +const SearchResultsInner: React.FC = ({ properties, totalResults, isLoading, @@ -219,6 +219,8 @@ export const SearchResults: React.FC = ({ ); }; +export const SearchResults = React.memo(SearchResultsInner); + function cn(...classes: any[]) { return classes.filter(Boolean).join(' '); } \ No newline at end of file diff --git a/src/components/SecureTransactionConfirmation.tsx b/src/components/SecureTransactionConfirmation.tsx index 90f6f5e5..75df20c2 100644 --- a/src/components/SecureTransactionConfirmation.tsx +++ b/src/components/SecureTransactionConfirmation.tsx @@ -236,7 +236,7 @@ export const SecureTransactionConfirmation: React.FC {validation.warnings.map((warning: string, index: number) => ( -

+

• {warning}

))} @@ -254,7 +254,7 @@ export const SecureTransactionConfirmation: React.FC {validation.risks.map((risk: string, index: number) => ( -

+

• {risk}

))} diff --git a/src/components/TransactionConfirmation.tsx b/src/components/TransactionConfirmation.tsx index 383fa14a..13f5cfdf 100644 --- a/src/components/TransactionConfirmation.tsx +++ b/src/components/TransactionConfirmation.tsx @@ -370,7 +370,7 @@ export const TransactionConfirmation: React.FC = (
{validation.warnings.map((warning: string, index: number) => ( -

+

• {warning}

))} @@ -390,7 +390,7 @@ export const TransactionConfirmation: React.FC = (
{validation.blocks.map((block: string, index: number) => ( -

+

• {block}

))} diff --git a/src/components/WalletConnectedView.tsx b/src/components/WalletConnectedView.tsx index 6c17ddad..c2fead89 100644 --- a/src/components/WalletConnectedView.tsx +++ b/src/components/WalletConnectedView.tsx @@ -62,7 +62,7 @@ interface WalletConnectedViewProps { * - Disconnect button * - Error messages */ -export function WalletConnectedView({ address }: WalletConnectedViewProps) { +const WalletConnectedViewInner: React.FC = ({ address }) => { const { setDisconnected, clearError, balance, error } = useWalletStore(); const { chainConfig } = useChain(); const { profile } = useKycStore(); @@ -113,4 +113,6 @@ export function WalletConnectedView({ address }: WalletConnectedViewProps) { )}
); -} +}; + +export const WalletConnectedView = React.memo(WalletConnectedViewInner); diff --git a/src/components/WalletModal.tsx b/src/components/WalletModal.tsx index e08645d4..2067154d 100644 --- a/src/components/WalletModal.tsx +++ b/src/components/WalletModal.tsx @@ -83,7 +83,7 @@ export const WalletModal: React.FC = ({ isOpen, onClose }) =>
{blocks.map((block, index) => ( -

+

• {block}

))} @@ -105,7 +105,7 @@ export const WalletModal: React.FC = ({ isOpen, onClose }) =>
{warnings.map((warning, index) => ( -

+

• {warning}

))}