diff --git a/app/markets/components/MarketSettingsModal.tsx b/app/markets/components/MarketSettingsModal.tsx new file mode 100644 index 00000000..89be6292 --- /dev/null +++ b/app/markets/components/MarketSettingsModal.tsx @@ -0,0 +1,242 @@ +import React from 'react'; +import { + Modal, + ModalContent, + ModalHeader, + ModalBody, + ModalFooter, + Button, + Switch, + Input, + Divider, +} from '@nextui-org/react'; + +type MarketSettingsModalProps = { + isOpen: boolean; + onOpenChange: () => void; + // Unknown Filters + includeUnknownTokens: boolean; + setIncludeUnknownTokens: (value: boolean) => void; + showUnknownOracle: boolean; + setShowUnknownOracle: (value: boolean) => void; + // USD Filters (Simplified) + usdFilters: { + minSupply: string; + minBorrow: string; + }; + setUsdFilters: (filters: MarketSettingsModalProps['usdFilters']) => void; + // Pagination + entriesPerPage: number; + onEntriesPerPageChange: (value: number) => void; +}; + +// Reusable component for consistent setting layout +function SettingItem({ + title, + description, + children, +}: { + title: string; + description: string; + children: React.ReactNode; +}) { + return ( +
+
+

{title}

+

{description}

+
+
+ {' '} + {/* Align control slightly lower */} + {children} +
+
+ ); +} + +export default function MarketSettingsModal({ + isOpen, + onOpenChange, + includeUnknownTokens, + setIncludeUnknownTokens, + showUnknownOracle, + setShowUnknownOracle, + usdFilters, + setUsdFilters, + entriesPerPage, + onEntriesPerPageChange, +}: MarketSettingsModalProps) { + const [customEntries, setCustomEntries] = React.useState(entriesPerPage.toString()); + + const handleEntriesChange = (value: number) => { + onEntriesPerPageChange(value); + setCustomEntries(value.toString()); // Update local state if preset is clicked + }; + + const handleCustomEntriesSubmit = () => { + const value = parseInt(customEntries, 10); + if (!isNaN(value) && value > 0) { + onEntriesPerPageChange(value); + } else { + setCustomEntries(entriesPerPage.toString()); + } + }; + + const handleUsdFilterChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + if (/^\d*$/.test(value)) { + setUsdFilters({ + ...usdFilters, + [name]: value, + }); + } + }; + + return ( + + + {(onClose) => ( + <> + Market View Settings + + {/* --- Filter Settings Section --- */} +
+ {/* Section Header: Adjusted style & position */} +

Filter Settings

+ + + + + + + +
+ + {/* --- USD Value Filters Section --- */} +
+ {/* Section Header: Adjusted style & position */} +

+ Filter by Min USD Value +

+

+ {' '} + {/* Fine-tune note position */} + Note: USD values are estimates and may not be available or accurate for all + markets. +

+ + + $ +
+ } + /> + + + + + $ + + } + /> + + + + {/* --- Pagination Settings Section --- */} +
+ {/* Section Header: Adjusted style & position */} +

View Options

+

Entries per page:

+
+ {[8, 10, 15].map((value) => ( + + ))} +
+ setCustomEntries(e.target.value)} + min="1" + size="sm" + className="w-20" + onKeyDown={(e) => e.key === 'Enter' && handleCustomEntriesSubmit()} + /> + +
+
+
+
+ + + + + )} +
+
+ ); +} diff --git a/app/markets/components/Pagination.tsx b/app/markets/components/Pagination.tsx index d270c84c..38c75591 100644 --- a/app/markets/components/Pagination.tsx +++ b/app/markets/components/Pagination.tsx @@ -1,23 +1,12 @@ -import React, { useEffect, useState } from 'react'; -import { - Pagination as NextUIPagination, - Modal, - ModalContent, - ModalHeader, - ModalBody, - Button, - useDisclosure, - Input, -} from '@nextui-org/react'; -import { FiSettings } from 'react-icons/fi'; +import React from 'react'; +import { Pagination as NextUIPagination } from '@nextui-org/react'; type PaginationProps = { totalPages: number; currentPage: number; onPageChange: (page: number) => void; entriesPerPage: number; - onEntriesPerPageChange: (entries: number) => void; - isDataLoaded: boolean; // New prop to check if data is loaded + isDataLoaded: boolean; }; export function Pagination({ @@ -25,40 +14,17 @@ export function Pagination({ currentPage, onPageChange, entriesPerPage, - onEntriesPerPageChange, - isDataLoaded, // New prop + isDataLoaded, }: PaginationProps) { - const { isOpen, onOpen, onOpenChange } = useDisclosure(); - const [customEntries, setCustomEntries] = useState(entriesPerPage.toString()); - - const handleEntriesChange = (value: number) => { - onEntriesPerPageChange(value); - onOpenChange(); // Close the modal - }; - - const handleCustomEntriesChange = () => { - const value = parseInt(customEntries, 10); - if (!isNaN(value) && value > 0) { - handleEntriesChange(value); - } - }; - - // Force re-render when data is loaded - useEffect(() => { - if (isDataLoaded) { - onPageChange(1); // Reset to first page when data loads - } - }, [isDataLoaded, onPageChange]); - if (!isDataLoaded || totalPages === 0) { - return null; // Don't render pagination if data isn't loaded or there are no pages + return null; } return (
-
- - - {() => ( - <> - - Settings - - -
-

Entries per page:

-
-
- {[6, 15, 24].map((value) => ( - - ))} -
-
- setCustomEntries(e.target.value)} - min="1" - size="sm" - className="w-20" - /> - -
-
-
-
- - )} -
-
); } diff --git a/app/markets/components/markets.tsx b/app/markets/components/markets.tsx index da474a13..887eb5f6 100644 --- a/app/markets/components/markets.tsx +++ b/app/markets/components/markets.tsx @@ -1,18 +1,17 @@ 'use client'; import { useCallback, useEffect, useState, useRef } from 'react'; -import { Tooltip } from '@nextui-org/react'; -import { QuestionMarkCircledIcon } from '@radix-ui/react-icons'; +import { useDisclosure } from '@nextui-org/react'; import { Chain } from '@rainbow-me/rainbowkit'; import storage from 'local-storage-fallback'; import { useRouter, useSearchParams } from 'next/navigation'; import { FaSync } from 'react-icons/fa'; +import { FiSettings } from 'react-icons/fi'; import { Button } from '@/components/common'; import Header from '@/components/layout/header/Header'; import { useTokens } from '@/components/providers/TokenProvider'; import EmptyScreen from '@/components/Status/EmptyScreen'; import LoadingScreen from '@/components/Status/LoadingScreen'; import { SupplyModal } from '@/components/supplyModal'; -import { TooltipContent } from '@/components/TooltipContent'; import { useLocalStorage } from '@/hooks/useLocalStorage'; import { useMarkets } from '@/hooks/useMarkets'; import { usePagination } from '@/hooks/usePagination'; @@ -26,6 +25,7 @@ import { Market } from '@/utils/types'; import AdvancedSearchBar, { ShortcutType } from './AdvancedSearchBar'; import AssetFilter from './AssetFilter'; import { SortColumn } from './constants'; +import MarketSettingsModal from './MarketSettingsModal'; import MarketsTable from './marketsTable'; import NetworkFilter from './NetworkFilter'; import OracleFilter from './OracleFilter'; @@ -53,6 +53,12 @@ export default function Markets() { const { loading, markets: rawMarkets, refetch, isRefetching } = useMarkets(); + const { + isOpen: isSettingsModalOpen, + onOpen: onSettingsModalOpen, + onOpenChange: onSettingsModalOpenChange, + } = useDisclosure(); + const defaultNetwork = (() => { const networkParam = searchParams.get('network'); return networkParam && @@ -89,11 +95,19 @@ export default function Markets() { const { currentPage, setCurrentPage, entriesPerPage, handleEntriesPerPageChange, resetPage } = usePagination(); - const [includeUnknownTokens] = useLocalStorage('includeUnknownTokens', false); - const [showUnknownOracle] = useLocalStorage('showUnknownOracle', false); + const [includeUnknownTokens, setIncludeUnknownTokens] = useLocalStorage( + 'includeUnknownTokens', + false, + ); + const [showUnknownOracle, setShowUnknownOracle] = useLocalStorage('showUnknownOracle', false); const { allTokens, findToken } = useTokens(); + const [usdFilters, setUsdFilters] = useState({ + minSupply: '', + minBorrow: '', + }); + useEffect(() => { const currentParams = searchParams.toString(); if (currentParams !== prevParamsRef.current) { @@ -236,6 +250,7 @@ export default function Markets() { selectedOracles, staredIds, findToken, + usdFilters, ).filter((market) => { if (!searchQuery) return true; // If no search query, show all markets const lowercaseQuery = searchQuery.toLowerCase(); @@ -255,13 +270,16 @@ export default function Markets() { sortColumn, sortDirection, selectedNetwork, + includeUnknownTokens, showUnknownOracle, selectedCollaterals, selectedLoanAssets, selectedOracles, + staredIds, + findToken, + usdFilters, searchQuery, resetPage, - staredIds, ]); useEffect(() => { @@ -350,7 +368,19 @@ export default function Markets() { /> )} - {/* Pass uniqueCollaterals and uniqueLoanAssets to AdvancedSearchBar */} + +
- {/* basic filter row */}
- {/* left section: asset filters */}
- + Refresh - } - title="Can't find a particular market?" - detail="Some markets are hidden by default. Check the settings page for advanced filter options." - /> - } - className="max-w-[400px] rounded-sm" +
@@ -449,15 +475,14 @@ export default function Markets() { sortColumn={sortColumn} sortDirection={sortDirection} onMarketClick={handleMarketClick} - setShowSupplyModal={setShowSupplyModal} - setSelectedMarket={setSelectedMarket} staredIds={staredIds} starMarket={starMarket} unstarMarket={unstarMarket} currentPage={currentPage} entriesPerPage={entriesPerPage} - handleEntriesPerPageChange={handleEntriesPerPageChange} setCurrentPage={setCurrentPage} + setShowSupplyModal={setShowSupplyModal} + setSelectedMarket={setSelectedMarket} /> ) : ( void; currentPage: number; entriesPerPage: number; - handleEntriesPerPageChange: (value: number) => void; setCurrentPage: (value: number) => void; onMarketClick: (market: Market) => void; }; @@ -35,7 +34,6 @@ function MarketsTable({ unstarMarket, currentPage, entriesPerPage, - handleEntriesPerPageChange, setCurrentPage, onMarketClick, }: MarketsTableProps) { @@ -128,7 +126,6 @@ function MarketsTable({ currentPage={currentPage} onPageChange={setCurrentPage} entriesPerPage={entriesPerPage} - onEntriesPerPageChange={handleEntriesPerPageChange} isDataLoaded={markets.length > 0} />
diff --git a/app/markets/components/utils.ts b/app/markets/components/utils.ts index 5b25aa5b..aab898b0 100644 --- a/app/markets/components/utils.ts +++ b/app/markets/components/utils.ts @@ -46,6 +46,12 @@ const isSelectedAsset = ( ); }; +// Define the type for USD Filters +type UsdFilters = { + minSupply: string; + minBorrow: string; +}; + export function applyFilterAndSort( markets: Market[], sortColumn: SortColumn, @@ -58,7 +64,17 @@ export function applyFilterAndSort( selectedOracles: OracleVendors[], staredIds: string[], findToken: (address: string, chainId: number) => ERC20Token | undefined, + usdFilters: UsdFilters, ): Market[] { + const parseUsdValue = (value: string | null | undefined): number | null => { + if (value === null || value === undefined || value === '') return null; + const num = parseFloat(value); + return isNaN(num) ? null : num; + }; + + const minSupplyUsd = parseUsdValue(usdFilters.minSupply); + const minBorrowUsd = parseUsdValue(usdFilters.minBorrow); + return markets .filter((market) => { if (selectedNetwork !== null && market.morphoBlue.chain.id !== selectedNetwork) { @@ -92,6 +108,18 @@ export function applyFilterAndSort( } } + // Add USD Filters + const supplyUsd = parseUsdValue(market.state?.supplyAssetsUsd); // Use optional chaining + const borrowUsd = parseUsdValue(market.state?.borrowAssetsUsd); // Use optional chaining + + if (minSupplyUsd !== null && (supplyUsd === null || supplyUsd < minSupplyUsd)) { + return false; + } + if (minBorrowUsd !== null && (borrowUsd === null || borrowUsd < minBorrowUsd)) { + return false; + } + // End USD Filters + return true; }) .sort((a, b) => {