diff --git a/apps/main/src/llamalend/PageLlamaMarkets/LlamaMarketsTable.tsx b/apps/main/src/llamalend/PageLlamaMarkets/LlamaMarketsTable.tsx index b1765333f..69249f184 100644 --- a/apps/main/src/llamalend/PageLlamaMarkets/LlamaMarketsTable.tsx +++ b/apps/main/src/llamalend/PageLlamaMarkets/LlamaMarketsTable.tsx @@ -19,6 +19,8 @@ import { SortingState, useReactTable, } from '@tanstack/react-table' +import { useUserProfileStore } from '@ui-kit/features/user-profile' +import { SMALL_POOL_TVL } from '@ui-kit/features/user-profile/store' import { useIsMobile, useIsTablet } from '@ui-kit/hooks/useBreakpoints' import { useSortFromQueryString } from '@ui-kit/hooks/useSortFromQueryString' import { t } from '@ui-kit/lib/i18n' @@ -26,9 +28,6 @@ import { DataTable } from '@ui-kit/shared/ui/DataTable' import { type Option, SelectFilter } from '@ui-kit/shared/ui/DataTable/SelectFilter' import { TableFilters, useColumnFilters } from '@ui-kit/shared/ui/DataTable/TableFilters' import { useVisibilitySettings } from '@ui-kit/shared/ui/DataTable/TableVisibilitySettingsPopover' -import { SizesAndSpaces } from '@ui-kit/themes/design/1_sizes_spaces' - -const { Spacing, MaxWidth } = SizesAndSpaces /** * Hook to manage the visibility of columns in the Llama Markets table. @@ -43,24 +42,31 @@ const useVisibility = (sorting: SortingState, hasPositions: boolean | undefined) return { sortField, ...visibilitySettings, ...(useIsMobile() && { columnVisibility }) } } +const TITLE = 'Llamalend Markets' // not using the t`` here as the value is used as a key in the local storage + +const useDefaultLlamaFilter = (minLiquidity: number) => + useMemo(() => [{ id: LlamaMarketColumnId.LiquidityUsd, value: [minLiquidity, undefined] }], [minLiquidity]) + export const LlamaMarketsTable = ({ onReload, result, isError, - minLiquidity, }: { onReload: () => void result: LlamaMarketsResult | undefined isError: boolean - minLiquidity: number }) => { const { markets: data = [], hasPositions, hasFavorites } = result ?? {} - const [columnFilters, columnFiltersById, setColumnFilter, resetFilters] = useColumnFilters([ - { id: LlamaMarketColumnId.LiquidityUsd, value: [minLiquidity, undefined] }, - ]) + + const minLiquidity = useUserProfileStore((s) => s.hideSmallPools) ? SMALL_POOL_TVL : 0 + const [columnFilters, columnFiltersById, setColumnFilter, resetFilters] = useColumnFilters( + TITLE, + useDefaultLlamaFilter(minLiquidity), + ) const [sorting, onSortingChange] = useSortFromQueryString(DEFAULT_SORT) - const { columnSettings, columnVisibility, toggleVisibility, sortField } = useVisibility(sorting, result?.hasPositions) + const { columnSettings, columnVisibility, toggleVisibility, sortField } = useVisibility(sorting, hasPositions) const [expanded, setExpanded] = useState({}) + const table = useReactTable({ columns: LLAMA_MARKET_COLUMNS, data, @@ -87,7 +93,7 @@ export const LlamaMarketsTable = ({ shouldStickFirstColumn={useIsTablet() && !!hasPositions} > - title={t`Llamalend Markets`} + title={TITLE} subtitle={t`Borrow with the power of Curve soft liquidations`} onReload={onReload} visibilityGroups={columnSettings} diff --git a/apps/main/src/llamalend/PageLlamaMarkets/Page.tsx b/apps/main/src/llamalend/PageLlamaMarkets/Page.tsx index 908362d11..66f5ef202 100644 --- a/apps/main/src/llamalend/PageLlamaMarkets/Page.tsx +++ b/apps/main/src/llamalend/PageLlamaMarkets/Page.tsx @@ -14,8 +14,6 @@ import { LendTableFooter } from '@/llamalend/PageLlamaMarkets/LendTableFooter' import { LlamaMarketsTable } from '@/llamalend/PageLlamaMarkets/LlamaMarketsTable' import { Stack } from '@mui/material' import Box from '@mui/material/Box' -import { useUserProfileStore } from '@ui-kit/features/user-profile' -import { SMALL_POOL_TVL } from '@ui-kit/features/user-profile/store' import { useIsTiny } from '@ui-kit/hooks/useBreakpoints' import { logSuccess } from '@ui-kit/lib' import { WithSkeleton } from '@ui-kit/shared/ui/WithSkeleton' @@ -56,7 +54,6 @@ export const LlamaMarketsPage = (props: LlamalendServerData) => { useInjectServerData(props) const { address } = useAccount() const { data, isError, isLoading } = useLlamaMarkets(address) - const minLiquidity = useUserProfileStore((s) => s.hideSmallPools) ? SMALL_POOL_TVL : 0 const showSkeleton = !data && (!isError || isLoading) // on initial render isLoading is still false return ( @@ -70,12 +67,7 @@ export const LlamaMarketsPage = (props: LlamalendServerData) => { minHeight: MinHeight.pageContent, }} > - onReload(address)} - result={data} - isError={isError} - minLiquidity={minLiquidity} - /> + onReload(address)} result={data} isError={isError} /> diff --git a/apps/main/src/llamalend/PageLlamaMarkets/filters/RangeSliderFilter.tsx b/apps/main/src/llamalend/PageLlamaMarkets/filters/RangeSliderFilter.tsx index a8c1c8882..54bc4a606 100644 --- a/apps/main/src/llamalend/PageLlamaMarkets/filters/RangeSliderFilter.tsx +++ b/apps/main/src/llamalend/PageLlamaMarkets/filters/RangeSliderFilter.tsx @@ -55,9 +55,11 @@ export const RangeSliderFilter = ({ (newRange: NumberRange) => setColumnFilter( id, - newRange.every((value, i) => value === defaultValue[i]) ? undefined : (newRange as NumberRange), + newRange.every((value, i) => value === defaultValue[i]) + ? undefined // remove the filter if the range is the same as the default + : [newRange[0] === defaultMinimum ? null : newRange[0], newRange[1] === maxValue ? null : newRange[1]], ), - [defaultValue, id, setColumnFilter], + [defaultMinimum, defaultValue, id, maxValue, setColumnFilter], ), ) diff --git a/packages/curve-ui-kit/src/hooks/useLocalStorage.ts b/packages/curve-ui-kit/src/hooks/useLocalStorage.ts index 0f3163160..af33c9899 100644 --- a/packages/curve-ui-kit/src/hooks/useLocalStorage.ts +++ b/packages/curve-ui-kit/src/hooks/useLocalStorage.ts @@ -1,6 +1,7 @@ import { kebabCase } from 'lodash' import { useMemo } from 'react' import type { Address } from '@curvefi/prices-api' +import type { ColumnFiltersState } from '@tanstack/table-core' import { isBetaDefault } from '@ui-kit/utils' import { useStoredState } from './useStoredState' @@ -38,6 +39,9 @@ export const useBetaFlag = () => useLocalStorage('beta', isBetaDefault) export const useFilterExpanded = (tableTitle: string) => useLocalStorage(`filter-expanded-${kebabCase(tableTitle)}`, false) +export const useTableFilters = (tableTitle: string, defaultFilters: ColumnFiltersState) => + useLocalStorage(`table-filters-${kebabCase(tableTitle)}`, defaultFilters) + export const getFavoriteMarkets = () => getFromLocalStorage('favoriteMarkets') ?? [] export const useFavoriteMarkets = () => { const initialValue = useMemo(() => [], []) diff --git a/packages/curve-ui-kit/src/shared/ui/DataTable/TableFilters.tsx b/packages/curve-ui-kit/src/shared/ui/DataTable/TableFilters.tsx index 49be434ef..23cb8fc10 100644 --- a/packages/curve-ui-kit/src/shared/ui/DataTable/TableFilters.tsx +++ b/packages/curve-ui-kit/src/shared/ui/DataTable/TableFilters.tsx @@ -1,4 +1,4 @@ -import { forwardRef, ReactNode, useCallback, useMemo, useRef, useState } from 'react' +import { forwardRef, ReactNode, useCallback, useMemo, useRef } from 'react' import Box from '@mui/material/Box' import Collapse from '@mui/material/Collapse' import Grid from '@mui/material/Grid' @@ -8,7 +8,7 @@ import SvgIcon from '@mui/material/SvgIcon' import Typography from '@mui/material/Typography' import { ColumnFiltersState } from '@tanstack/react-table' import { useIsMobile, useIsTiny } from '@ui-kit/hooks/useBreakpoints' -import { useFilterExpanded } from '@ui-kit/hooks/useLocalStorage' +import { useFilterExpanded, useTableFilters } from '@ui-kit/hooks/useLocalStorage' import { useSwitch } from '@ui-kit/hooks/useSwitch' import { SizesAndSpaces } from '@ui-kit/themes/design/1_sizes_spaces' import { FilterIcon } from '../../icons/FilterIcon' @@ -142,8 +142,8 @@ export const TableFilters = ({ /** * A hook to manage filters for a table. Currently saved in the state, but the URL could be a better place. */ -export function useColumnFilters(defaultFilters: ColumnFiltersState = []) { - const [columnFilters, setColumnFilters] = useState(defaultFilters) +export function useColumnFilters(tableTitle: string, defaultFilters: ColumnFiltersState = []) { + const [columnFilters, setColumnFilters] = useTableFilters(tableTitle, defaultFilters) const setColumnFilter = useCallback( (id: string, value: unknown) => setColumnFilters((filters) => [ @@ -157,7 +157,7 @@ export function useColumnFilters(defaultFilters: ColumnFiltersState = []) { }, ]), ]), - [], + [setColumnFilters], ) const columnFiltersById: Record = useMemo( () => @@ -171,7 +171,7 @@ export function useColumnFilters(defaultFilters: ColumnFiltersState = []) { [columnFilters], ) - const resetFilters = useCallback(() => setColumnFilters(defaultFilters), [defaultFilters]) + const resetFilters = useCallback(() => setColumnFilters(defaultFilters), [defaultFilters, setColumnFilters]) return [columnFilters, columnFiltersById, setColumnFilter, resetFilters] as const } diff --git a/packages/tsconfig/cypress.json b/packages/tsconfig/cypress.json index 678e0a4e1..5ddb85289 100644 --- a/packages/tsconfig/cypress.json +++ b/packages/tsconfig/cypress.json @@ -3,7 +3,7 @@ "compilerOptions": { "target": "ES2020", "module": "ESNext", - "moduleResolution": "Node", + "moduleResolution": "bundler", "lib": ["es5", "dom"], "types": ["cypress", "node"], "resolveJsonModule": true diff --git a/tests/cypress/e2e/all/disclaimers.cy.ts b/tests/cypress/e2e/all/disclaimers.cy.ts index bafdf268b..963ee92be 100644 --- a/tests/cypress/e2e/all/disclaimers.cy.ts +++ b/tests/cypress/e2e/all/disclaimers.cy.ts @@ -1,5 +1,12 @@ import { oneOf } from '@/support/generators' -import { LOAD_TIMEOUT, oneAppPath, oneDesktopViewport, oneTabletViewport, oneViewport } from '@/support/ui' +import { + API_LOAD_TIMEOUT, + LOAD_TIMEOUT, + oneAppPath, + oneDesktopViewport, + oneTabletViewport, + oneViewport, +} from '@/support/ui' describe('Disclaimers', () => { describe('Footer link', () => { @@ -11,7 +18,7 @@ describe('Disclaimers', () => { // Navigate to risk disclaimer from footer. cy.get(`[data-testid='footer']`, LOAD_TIMEOUT).should('be.visible') cy.get(`[data-testid='footer'] a`).contains('disclaimer', { matchCase: false }).click() - cy.url(LOAD_TIMEOUT).should('match', /\/disclaimer\/?(\?tab=(lend|crvusd))?$/) + cy.url(API_LOAD_TIMEOUT).should('match', /\/disclaimer\/?(\?tab=(lend|crvusd))?$/) cy.get(`[data-testid='disclaimer']`, LOAD_TIMEOUT).should('be.visible') }) }) diff --git a/tests/cypress/e2e/all/header.cy.ts b/tests/cypress/e2e/all/header.cy.ts index 95ae59b25..38a671d36 100644 --- a/tests/cypress/e2e/all/header.cy.ts +++ b/tests/cypress/e2e/all/header.cy.ts @@ -179,7 +179,7 @@ describe('Header', () => { cy.get(`[data-testid='chain-icon-${eth}']`, LOAD_TIMEOUT).should('be.visible') cy.get(`[data-testid='btn-change-chain']`).click() cy.get(`[data-testid='menu-item-chain-${arbitrum}']`).click() - cy.get(`[data-testid^='menu-item-chain-']`, LOAD_TIMEOUT).should('not.exist') + cy.get(`[data-testid^='menu-item-chain-']`, API_LOAD_TIMEOUT).should('not.exist') cy.get(`[data-testid='chain-icon-${arbitrum}']`).should('be.visible') } }) diff --git a/tests/cypress/e2e/llamalend/llamalend-markets.cy.ts b/tests/cypress/e2e/llamalend/llamalend-markets.cy.ts index 1e2650c6e..a9905374c 100644 --- a/tests/cypress/e2e/llamalend/llamalend-markets.cy.ts +++ b/tests/cypress/e2e/llamalend/llamalend-markets.cy.ts @@ -18,7 +18,7 @@ import { oneViewport, RETRY_IN_CI, } from '@/support/ui' -import type { GetMarketsResponse } from '@curvefi/prices-api/dist/llamalend' +import type { GetMarketsResponse } from '@curvefi/prices-api/llamalend' import { SMALL_POOL_TVL } from '@ui-kit/features/user-profile/store' describe(`LlamaLend Markets`, () => { @@ -44,7 +44,7 @@ describe(`LlamaLend Markets`, () => { cy.get('[data-testid="data-table"]', LOAD_TIMEOUT).should('be.visible') }) - const firstRow = () => cy.get(`[data-testid^="data-table-row-"]`).eq(0) + const firstRow = () => cy.get(`[data-testid^="data-table-row-"]`).first() it('should have sticky headers', () => { cy.get('[data-testid^="data-table-row"]').last().then(assertNotInViewport) cy.get('[data-testid^="data-table-row"]').eq(10).scrollIntoView() @@ -76,7 +76,7 @@ describe(`LlamaLend Markets`, () => { .should('exist') expandFirstRowOnMobile() // note: not possible currently to sort ascending - cy.get('[data-testid="metric-utilizationPercent"]').first().contains('99.99%', LOAD_TIMEOUT) + cy.get('[data-testid="metric-utilizationPercent"]').contains('99.99%', LOAD_TIMEOUT) } else { cy.get(`[data-testid="data-table-cell-rates_borrow"]`).first().contains('%') cy.get('[data-testid="data-table-header-utilizationPercent"]').click() diff --git a/tests/cypress/support/helpers/lending-mocks.ts b/tests/cypress/support/helpers/lending-mocks.ts index fcf738db1..c038566fb 100644 --- a/tests/cypress/support/helpers/lending-mocks.ts +++ b/tests/cypress/support/helpers/lending-mocks.ts @@ -1,7 +1,7 @@ import { MAX_USD_VALUE, oneAddress, oneFloat, oneInt, oneOf, onePrice, range } from '@/support/generators' import { oneToken } from '@/support/helpers/tokens' -import type { GetMarketsResponse } from '@curvefi/prices-api/src/llamalend' -import { fromEntries } from '../../../../packages/prices-api/src/objects.util' +import type { GetMarketsResponse } from '@curvefi/prices-api/llamalend' +import { fromEntries } from '@curvefi/prices-api/objects.util' const LendingChains = ['ethereum', 'fraxtal', 'arbitrum'] as const export type Chain = (typeof LendingChains)[number] @@ -64,12 +64,12 @@ export const HighUtilizationAddress = '0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA function oneLendingVaultResponse(chain: Chain): GetMarketsResponse { const count = oneInt(2, 20) const data = [ - ...range(count).map((index) => oneLendingPool(chain, index / (count - 1))), + ...range(count).map((index) => oneLendingPool(chain, index / count)), ...(chain == 'ethereum' ? ([ { // fixed vault address to test campaign rewards - ...oneLendingPool(chain, oneFloat()), + ...oneLendingPool(chain, oneFloat(0.98)), vault: '0xc28c2fd809fc1795f90de1c9da2131434a77721d', }, { diff --git a/tests/cypress/support/ui.ts b/tests/cypress/support/ui.ts index c54a2aa2b..74fde3353 100644 --- a/tests/cypress/support/ui.ts +++ b/tests/cypress/support/ui.ts @@ -37,7 +37,7 @@ export const oneAppPath = () => oneOf(...([oneDexPath(), 'lend', 'dao', 'crvusd' export type AppPath = ReturnType export const LOAD_TIMEOUT = { timeout: 30000 } -export const API_LOAD_TIMEOUT = { timeout: 60000 } +export const API_LOAD_TIMEOUT = { timeout: 120000 } // unfortunately the prices API can be REAL SLOW 😭 // scrollbar in px for the test browser. Firefox behaves when headless. export const SCROLL_WIDTH = Cypress.browser.name === 'firefox' ? (Cypress.browser.isHeadless ? 12 : 0) : 15 diff --git a/tests/cypress/tsconfig.json b/tests/cypress/tsconfig.json index 0e826f6e3..7f1a021fc 100644 --- a/tests/cypress/tsconfig.json +++ b/tests/cypress/tsconfig.json @@ -6,6 +6,7 @@ "paths": { "@/*": ["*"], "@curvefi/prices-api": ["../../packages/prices-api/src"], + "@curvefi/prices-api/*": ["../../packages/prices-api/src/*"], "@ui-kit/*": ["../../packages/curve-ui-kit/src/*"] } }, diff --git a/tests/tsconfig.json b/tests/tsconfig.json index 6a93c1f7e..03ce4db6c 100644 --- a/tests/tsconfig.json +++ b/tests/tsconfig.json @@ -3,7 +3,14 @@ "compilerOptions": { "types": ["hardhat"], "baseUrl": "./cypress", - "resolveJsonModule": true + "resolveJsonModule": true, + "paths": { + "@ui": ["../../packages/ui/src/index.ts"], + "@ui/*": ["../../packages/ui/src/*"], + "@curvefi/prices-api": ["../../packages/prices-api/src/index.ts"], + "@curvefi/prices-api/*": ["../../packages/prices-api/src/*"], + "@ui-kit/*": ["../../packages/curve-ui-kit/src/*"], + } }, "include": [ "./**/*.ts", diff --git a/tests/vite.config.ts b/tests/vite.config.ts index 5face1ce2..a33728546 100644 --- a/tests/vite.config.ts +++ b/tests/vite.config.ts @@ -1,7 +1,17 @@ +import { resolve } from 'path' import { defineConfig } from 'vite' import tsconfigPaths from 'vite-tsconfig-paths' import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react(), tsconfigPaths()], + resolve: { + alias: [ + { find: '@ui', replacement: resolve(__dirname, '../packages/ui/src/') }, + { find: '@ui-kit', replacement: resolve(__dirname, '../packages/curve-ui-kit/src') }, + { find: '@external-rewards', replacement: resolve(__dirname, '../packages/external-rewards/src/index.ts') }, + { find: '@curvefi/prices-api', replacement: resolve(__dirname, '../packages/prices-api/src') }, + { find: '@curvefi/prices-api/', replacement: resolve(__dirname, '../packages/prices-api/src/') }, + ], + }, })