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) => {