diff --git a/packages/lib/modules/pool/PoolDetail/PoolActivity/poolActivity.types.ts b/packages/lib/modules/pool/PoolDetail/PoolActivity/poolActivity.types.ts index 4d20998cd..03b8cb144 100644 --- a/packages/lib/modules/pool/PoolDetail/PoolActivity/poolActivity.types.ts +++ b/packages/lib/modules/pool/PoolDetail/PoolActivity/poolActivity.types.ts @@ -31,11 +31,6 @@ export interface PoolActivityTypeTab { label: string } -export enum Sorting { - asc = 'asc', - desc = 'desc', -} - export function getPoolActivityTabsList({ variant, poolType, diff --git a/packages/lib/modules/pool/PoolDetail/PoolActivity/usePoolActivity.tsx b/packages/lib/modules/pool/PoolDetail/PoolActivity/usePoolActivity.tsx index fec52552b..5ba0662ad 100644 --- a/packages/lib/modules/pool/PoolDetail/PoolActivity/usePoolActivity.tsx +++ b/packages/lib/modules/pool/PoolDetail/PoolActivity/usePoolActivity.tsx @@ -19,11 +19,11 @@ import { PoolActivityTokens, SortingBy, getPoolActivityTabsList, - Sorting, } from './poolActivity.types' import { PaginationState } from '@repo/lib/shared/components/pagination/pagination.types' import { usePoolActivityViewType } from '../PoolActivityViewType/usePoolActivityViewType' import { sortAlphabetically } from '@repo/lib/shared/utils/sorting' +import { Sorting } from '@repo/lib/shared/components/tables/SortableHeader' export type PoolActivityResponse = ReturnType export const PoolActivityContext = createContext(null) diff --git a/packages/lib/modules/pool/PoolDetail/PoolActivityTable/PoolActivityTableHeader.tsx b/packages/lib/modules/pool/PoolDetail/PoolActivityTable/PoolActivityTableHeader.tsx index c8e621b73..001ddd929 100644 --- a/packages/lib/modules/pool/PoolDetail/PoolActivityTable/PoolActivityTableHeader.tsx +++ b/packages/lib/modules/pool/PoolDetail/PoolActivityTable/PoolActivityTableHeader.tsx @@ -2,8 +2,8 @@ import { Divider, Grid, GridItem, Text } from '@chakra-ui/react' import { usePoolActivity } from '../PoolActivity/usePoolActivity' -import { Sorting, SortingBy } from '../PoolActivity/poolActivity.types' -import { SortableHeader } from '@repo/lib/shared/components/tables/SortableHeader' +import { SortingBy } from '../PoolActivity/poolActivity.types' +import { SortableHeader, Sorting } from '@repo/lib/shared/components/tables/SortableHeader' export function PoolActivityTableHeader({ ...rest }) { const { diff --git a/packages/lib/modules/pool/PoolList/PoolListFilters.tsx b/packages/lib/modules/pool/PoolList/PoolListFilters.tsx index b1aa4c7b0..c56ce6489 100644 --- a/packages/lib/modules/pool/PoolList/PoolListFilters.tsx +++ b/packages/lib/modules/pool/PoolList/PoolListFilters.tsx @@ -128,12 +128,21 @@ function PoolCategoryFilters() { ) } -function PoolTypeFilters() { - const { - queryState: { togglePoolType, poolTypes, poolTypeLabel, setPoolTypes }, - hidePoolTypes, - } = usePoolList() +export interface PoolTypeFiltersArgs { + poolTypes: PoolFilterType[] + poolTypeLabel: (poolType: PoolFilterType) => string + setPoolTypes: (value: PoolFilterType[] | null) => void + togglePoolType: (checked: boolean, value: PoolFilterType) => void + hidePoolTypes?: GqlPoolType[] +} +export function PoolTypeFilters({ + togglePoolType, + poolTypes, + poolTypeLabel, + setPoolTypes, + hidePoolTypes, +}: PoolTypeFiltersArgs) { // remove query param when empty useEffect(() => { if (!poolTypes.length) { @@ -141,7 +150,9 @@ function PoolTypeFilters() { } }, [poolTypes]) - const _poolTypeFilters = poolTypeFilters.filter(poolType => !hidePoolTypes.includes(poolType)) + const _poolTypeFilters = poolTypeFilters.filter( + poolType => !(hidePoolTypes ?? []).includes(poolType) + ) return ( @@ -161,11 +172,18 @@ function PoolTypeFilters() { ) } -function PoolNetworkFilters() { +export interface PoolNetworkFiltersArgs { + toggledNetworks: GqlChain[] + toggleNetwork: (checked: boolean, value: GqlChain) => void + setNetworks: (value: GqlChain[] | null) => void +} + +export function PoolNetworkFilters({ + toggledNetworks, + toggleNetwork, + setNetworks, +}: PoolNetworkFiltersArgs) { const { supportedNetworks } = getProjectConfig() - const { - queryState: { networks: toggledNetworks, toggleNetwork, setNetworks }, - } = usePoolList() // Sort networks alphabetically after mainnet const sortedNetworks = [supportedNetworks[0], ...supportedNetworks.slice(1).sort()] @@ -243,25 +261,45 @@ function PoolMinTvlFilter() { ) } -export function FilterTags() { - const { - queryState: { - networks, - toggleNetwork, - poolTypes, - togglePoolType, - poolTypeLabel, - minTvl, - setMinTvl, - poolTags, - togglePoolTag, - poolTagLabel, - }, - } = usePoolList() +export interface FilterTagsPops { + networks: GqlChain[] + toggleNetwork: (checked: boolean, value: GqlChain) => void + poolTypes: PoolFilterType[] + togglePoolType: (checked: boolean, value: PoolFilterType) => void + poolTypeLabel: (poolType: PoolFilterType) => string + minTvl?: number + setMinTvl?: (value: number | null) => void + poolTags?: PoolTagType[] + togglePoolTag?: (checked: boolean, value: PoolTagType) => void + poolTagLabel?: (poolTag: PoolTagType) => string + includeExpiredPools?: boolean + toggleIncludeExpiredPools?: (checked: boolean) => void +} + +export function FilterTags({ + networks, + toggleNetwork, + poolTypes, + togglePoolType, + poolTypeLabel, + minTvl, + setMinTvl, + poolTags, + togglePoolTag, + poolTagLabel, + includeExpiredPools, + toggleIncludeExpiredPools, +}: FilterTagsPops) { const { toCurrency } = useCurrency() // prevents layout shift in mobile view - if (networks.length === 0 && poolTypes.length === 0 && minTvl === 0 && poolTags.length === 0) { + if ( + networks.length === 0 && + poolTypes.length === 0 && + minTvl === 0 && + (poolTags ? poolTags.length === 0 : true) && + !includeExpiredPools + ) { return } @@ -313,7 +351,7 @@ export function FilterTags() { ))} - {minTvl > 0 && ( + {minTvl && minTvl > 0 && ( ${toCurrency(minTvl)}`} - setMinTvl(0)} /> + setMinTvl && setMinTvl(0)} /> )} - - {poolTags.map(tag => ( + {poolTags && poolTagLabel && ( + + {poolTags.map(tag => ( + + + + + {poolTagLabel(tag)} + + + togglePoolTag && togglePoolTag(false, tag)} /> + + + ))} + + )} + {includeExpiredPools && ( + - {poolTagLabel(tag)} + Expired - togglePoolTag(false, tag)} /> + toggleIncludeExpiredPools && toggleIncludeExpiredPools(false)} + /> - ))} - + + )} ) } -const FilterButton = forwardRef((props, ref) => { - const { - queryState: { totalFilterCount }, - } = usePoolList() - const { isMobile } = useBreakpoints() - const textColor = useColorModeValue('#fff', 'font.dark') - - return ( - - ) -}) - -function ProtocolVersionFilter() { - const { - queryState: { - setProtocolVersion, - protocolVersion, - poolTypes, - activeProtocolVersionTab, - setActiveProtocolVersionTab, - }, - hideProtocolVersion, - } = usePoolList() +export const FilterButton = forwardRef( + ({ totalFilterCount, ...props }, ref) => { + const { isMobile } = useBreakpoints() + const textColor = useColorModeValue('#fff', 'font.dark') + + return ( + + ) + } +) + +export interface ProtocolVersionFilterProps { + setProtocolVersion: React.Dispatch> + protocolVersion: number | null + poolTypes: PoolFilterType[] + activeProtocolVersionTab: ButtonGroupOption + setActiveProtocolVersionTab: React.Dispatch> + hideProtocolVersion?: string[] +} +export function ProtocolVersionFilter({ + setProtocolVersion, + protocolVersion, + poolTypes, + activeProtocolVersionTab, + setActiveProtocolVersionTab, + hideProtocolVersion, +}: ProtocolVersionFilterProps) { const tabs = PROTOCOL_VERSION_TABS function toggleTab(option: ButtonGroupOption) { @@ -441,7 +510,7 @@ function ProtocolVersionFilter() { currentOption={activeProtocolVersionTab} groupId="protocol-version" onChange={toggleTab} - options={tabs.filter(tab => !hideProtocolVersion.includes(tab.value))} + options={tabs.filter(tab => !(hideProtocolVersion ?? []).includes(tab.value))} size="xxs" /> ) @@ -452,7 +521,23 @@ export function PoolListFilters() { const [isPopoverOpen, setIsPopoverOpen] = useState(false) const { isFixedPoolType, - queryState: { resetFilters, totalFilterCount, setActiveProtocolVersionTab }, + queryState: { + resetFilters, + totalFilterCount, + setActiveProtocolVersionTab, + networks: toggledNetworks, + toggleNetwork, + setNetworks, + togglePoolType, + poolTypes, + poolTypeLabel, + setPoolTypes, + setProtocolVersion, + protocolVersion, + activeProtocolVersionTab, + }, + hidePoolTypes, + hideProtocolVersion, } = usePoolList() const { isCowPath } = useCow() const { isMobile } = useBreakpoints() @@ -477,7 +562,7 @@ export function PoolListFilters() { placement="bottom-end" > - + @@ -531,14 +616,25 @@ export function PoolListFilters() { Networks - + {!isCowPath && ( Protocol version - + )} {!isFixedPoolType && ( @@ -546,7 +642,13 @@ export function PoolListFilters() { Pool types - + )} diff --git a/packages/lib/modules/pool/PoolList/PoolListLayout.tsx b/packages/lib/modules/pool/PoolList/PoolListLayout.tsx index f2c50a828..848ab40f0 100644 --- a/packages/lib/modules/pool/PoolList/PoolListLayout.tsx +++ b/packages/lib/modules/pool/PoolList/PoolListLayout.tsx @@ -10,7 +10,23 @@ import { ErrorBoundary } from 'react-error-boundary' import { BoundaryError } from '@repo/lib/shared/components/errors/ErrorBoundary' export function PoolListLayout() { - const { pools, loading, count } = usePoolList() + const { + pools, + loading, + count, + queryState: { + networks, + toggleNetwork, + poolTypes, + togglePoolType, + poolTypeLabel, + minTvl, + setMinTvl, + poolTags, + togglePoolTag, + poolTagLabel, + }, + } = usePoolList() const isFilterVisible = useFilterTagsVisible() const isMd = useBreakpointValue({ base: false, md: true }) @@ -56,7 +72,18 @@ export function PoolListLayout() { - + @@ -48,7 +48,7 @@ export function PoolListTableHeader({ ...rest }) { isSorted={sortingObj.id === orderByItem} label={orderByHash[orderByItem]} onSort={() => handleSort(orderByItem)} - sorting={sortingObj.desc ? 'desc' : 'asc'} + sorting={sortingObj.desc ? Sorting.desc : Sorting.asc} /> ))} diff --git a/packages/lib/modules/pool/PoolList/usePoolListQueryState.tsx b/packages/lib/modules/pool/PoolList/usePoolListQueryState.tsx index 62c9d20c0..29ea56379 100644 --- a/packages/lib/modules/pool/PoolList/usePoolListQueryState.tsx +++ b/packages/lib/modules/pool/PoolList/usePoolListQueryState.tsx @@ -40,6 +40,25 @@ export const PROTOCOL_VERSION_TABS: ButtonGroupOption[] = [ }, ] as const +export function poolTypeLabel(poolType: GqlPoolType) { + switch (poolType) { + case GqlPoolType.Weighted: + return 'Weighted' + case GqlPoolType.Stable: + return 'Stable' + case GqlPoolType.LiquidityBootstrapping: + return 'Liquidity Bootstrapping (LBP)' + case GqlPoolType.Gyro: + return 'Gyro CLP' + case GqlPoolType.CowAmm: + return 'CoW AMM' + case GqlPoolType.Fx: + return 'FX' + default: + return poolType.toLowerCase() + } +} + export function usePoolListQueryState() { const [first, setFirst] = useQueryState('first', poolListQueryStateParsers.first) const [skip, setSkip] = useQueryState('skip', poolListQueryStateParsers.skip) @@ -140,25 +159,6 @@ export function usePoolListQueryState() { setTextSearch(text) } - function poolTypeLabel(poolType: GqlPoolType) { - switch (poolType) { - case GqlPoolType.Weighted: - return 'Weighted' - case GqlPoolType.Stable: - return 'Stable' - case GqlPoolType.LiquidityBootstrapping: - return 'Liquidity Bootstrapping (LBP)' - case GqlPoolType.Gyro: - return 'Gyro CLP' - case GqlPoolType.CowAmm: - return 'CoW AMM' - case GqlPoolType.Fx: - return 'FX' - default: - return poolType.toLowerCase() - } - } - function poolTagLabel(poolTag: PoolTagType) { switch (poolTag) { case 'INCENTIVIZED': diff --git a/packages/lib/modules/portfolio/PortfolioTable/PortfolioTable.tsx b/packages/lib/modules/portfolio/PortfolioTable/PortfolioTable.tsx index 621deecfc..ee9a895fc 100644 --- a/packages/lib/modules/portfolio/PortfolioTable/PortfolioTable.tsx +++ b/packages/lib/modules/portfolio/PortfolioTable/PortfolioTable.tsx @@ -167,6 +167,8 @@ export function PortfolioTable() { left={{ base: '-4px', sm: '0' }} p={{ base: '0', sm: '0' }} position="relative" + // fixing right padding for horizontal scroll on mobile + pr={{ base: 'lg', sm: 'lg', md: 'lg', lg: '0' }} w={{ base: '100vw', lg: 'full' }} > currentSortingObj.id === id ? !currentSortingObj.desc : true @@ -49,7 +49,7 @@ export function PortfolioTableHeader({ currentSortingObj, setCurrentSortingObj, setCurrentSortingObj({ id: orderByItem.id, desc: false }) } }} - sorting={currentSortingObj.desc ? 'desc' : 'asc'} + sorting={currentSortingObj.desc ? Sorting.desc : Sorting.asc} /> ))} diff --git a/packages/lib/modules/vebal/vote/VoteExpiredTooltip.tsx b/packages/lib/modules/vebal/vote/VoteExpiredTooltip.tsx new file mode 100644 index 000000000..08c21e4ad --- /dev/null +++ b/packages/lib/modules/vebal/vote/VoteExpiredTooltip.tsx @@ -0,0 +1,44 @@ +import { + Badge, + Popover, + PopoverContent, + PopoverTrigger, + Portal, + Text, + VStack, +} from '@chakra-ui/react' + +interface Props { + usePortal?: boolean +} + +export function VoteExpiredTooltip({ usePortal }: Props) { + const popoverContent = ( + + + + Expired pool gauge + + + This pool gauge that collects votes from veBAL holders to distribute BAL liquidity + incentives is no longer active. If you have active votes to this pool, reallocate them to + active pool gauges to avoid wasting your vote. + + + + ) + + return ( + + <> + + + Expired + + + + {usePortal ? {popoverContent} : popoverContent} + + + ) +} diff --git a/packages/lib/modules/vebal/vote/VoteList/VoteList.tsx b/packages/lib/modules/vebal/vote/VoteList/VoteList.tsx index 4874ae066..1ef68f07b 100644 --- a/packages/lib/modules/vebal/vote/VoteList/VoteList.tsx +++ b/packages/lib/modules/vebal/vote/VoteList/VoteList.tsx @@ -1,4 +1,3 @@ -import { PoolListProvider } from '@repo/lib/modules/pool/PoolList/PoolListProvider' import { VoteListLayout } from './VoteListLayout' import { VoteListProvider } from '@repo/lib/modules/vebal/vote/VoteList/VoteListProvider' import { getHiddenHandVotingIncentivesEither } from '@repo/lib/shared/services/hidden-hand/getHiddenHandVotingIncentives' @@ -6,7 +5,6 @@ import { GetVeBalVotingListDocument } from '@repo/lib/shared/services/api/genera import { mins } from '@repo/lib/shared/utils/time' import { getApolloServerClient } from '@repo/lib/shared/services/api/apollo-server.client' import { parseError } from '@repo/lib/shared/utils/errors' -import { PoolListDisplayType } from '@repo/lib/modules/pool/pool.types' export async function VoteList() { const client = getApolloServerClient() @@ -32,15 +30,7 @@ export async function VoteList() { votingIncentivesErrorMessage={parseError(votingIncentivesError)} votingIncentivesLoading={false} /* RSC (SSR) mode, no loading needed */ > - {/* fix: remove PoolListProvider when voteFilters implemented */} - - - + ) } diff --git a/packages/lib/modules/vebal/vote/VoteList/VoteListFilters.tsx b/packages/lib/modules/vebal/vote/VoteList/VoteListFilters.tsx new file mode 100644 index 000000000..a5b3acf63 --- /dev/null +++ b/packages/lib/modules/vebal/vote/VoteList/VoteListFilters.tsx @@ -0,0 +1,170 @@ +import { useEffect, useState } from 'react' +import { poolTypeLabel } from '@repo/lib/modules/pool/PoolList/usePoolListQueryState' +import { + Box, + Button, + Checkbox, + Flex, + Heading, + HStack, + Popover, + PopoverArrow, + PopoverBody, + PopoverCloseButton, + PopoverContent, + PopoverTrigger, + Text, + VStack, +} from '@chakra-ui/react' +import { AnimatePresence, motion } from 'framer-motion' +import { staggeredFadeInUp } from '@repo/lib/shared/utils/animations' +import { + FilterButton, + PoolNetworkFilters, + PoolTypeFilters, +} from '@repo/lib/modules/pool/PoolList/PoolListFilters' +import { useVoteList } from '@repo/lib/modules/vebal/vote/VoteList/VoteListProvider' +import { VoteListSearch } from '@repo/lib/modules/vebal/vote/VoteList/VoteListSearch' + +export function useFilterTagsVisible() { + const { + filtersState: { networks, poolTypes, includeExpiredPools }, + } = useVoteList() + const [isVisible, setIsVisible] = useState(false) + + useEffect(() => { + setIsVisible(networks.length > 0 || poolTypes.length > 0 || includeExpiredPools) + }, [networks, poolTypes, includeExpiredPools]) + + return isVisible +} + +export function VoteListFilters() { + const [isPopoverOpen, setIsPopoverOpen] = useState(false) + const { + filtersState: { + resetFilters, + totalFilterCount, + networks: toggledNetworks, + toggleNetwork, + setNetworks, + togglePoolType, + poolTypes, + setPoolTypes, + includeExpiredPools, + toggleIncludeExpiredPools, + }, + } = useVoteList() + + function _resetFilters() { + resetFilters() + } + + return ( + + + + setIsPopoverOpen(false)} + onOpen={() => setIsPopoverOpen(true)} + placement="bottom-end" + > + + + + + + + + + + {isPopoverOpen ? ( + + + + + Filters + + {totalFilterCount > 0 && ( + + )} + + + + + + Networks + + + + + + Pool types + + + + + + Pool gauge display + + + + toggleIncludeExpiredPools(e.target.checked)} + > + + Show expired pools gauges + + + + + + + ) : null} + + + + + + + + ) +} diff --git a/packages/lib/modules/vebal/vote/VoteList/VoteListLayout.tsx b/packages/lib/modules/vebal/vote/VoteList/VoteListLayout.tsx index 76a20c610..e113aa3e7 100644 --- a/packages/lib/modules/vebal/vote/VoteList/VoteListLayout.tsx +++ b/packages/lib/modules/vebal/vote/VoteList/VoteListLayout.tsx @@ -5,16 +5,29 @@ import { motion } from 'framer-motion' import { fNum } from '@repo/lib/shared/utils/numbers' import { ErrorBoundary } from 'react-error-boundary' import { BoundaryError } from '@repo/lib/shared/components/errors/ErrorBoundary' -import { - FilterTags, - PoolListFilters, - useFilterTagsVisible, -} from '@repo/lib/modules/pool/PoolList/PoolListFilters' +import { FilterTags } from '@repo/lib/modules/pool/PoolList/PoolListFilters' import { VoteListTable } from '@repo/lib/modules/vebal/vote/VoteList/VoteListTable/VoteListTable' import { useVoteList } from '@repo/lib/modules/vebal/vote/VoteList/VoteListProvider' +import { + useFilterTagsVisible, + VoteListFilters, +} from '@repo/lib/modules/vebal/vote/VoteList/VoteListFilters' +import { poolTypeLabel } from '@repo/lib/modules/pool/PoolList/usePoolListQueryState' export function VoteListLayout() { - const { sortedVoteList, loading, count } = useVoteList() + const { + sortedVoteList, + loading, + count, + filtersState: { + networks, + toggleNetwork, + poolTypes, + togglePoolType, + includeExpiredPools, + toggleIncludeExpiredPools, + }, + } = useVoteList() const isFilterVisible = useFilterTagsVisible() const isMd = useBreakpointValue({ base: false, md: true }) @@ -60,7 +73,15 @@ export function VoteListLayout() { - + - + diff --git a/packages/lib/modules/vebal/vote/VoteList/VoteListProvider.tsx b/packages/lib/modules/vebal/vote/VoteList/VoteListProvider.tsx index afb5a0cc0..145ff43af 100644 --- a/packages/lib/modules/vebal/vote/VoteList/VoteListProvider.tsx +++ b/packages/lib/modules/vebal/vote/VoteList/VoteListProvider.tsx @@ -1,13 +1,76 @@ 'use client' import { createContext, PropsWithChildren, useMemo } from 'react' -import { GetVeBalVotingListQuery } from '@repo/lib/shared/services/api/generated/graphql' +import { GetVeBalVotingListQuery, GqlChain } from '@repo/lib/shared/services/api/generated/graphql' import { useMandatoryContext } from '@repo/lib/shared/utils/contexts' -import { usePoolListQueryState } from '@repo/lib/modules/pool/PoolList/usePoolListQueryState' import { useGaugeVotes } from '@repo/lib/modules/vebal/vote/useGaugeVotes' -import { VotingPoolWithData } from '@repo/lib/modules/vebal/vote/vote.types' +import { SortVotesBy, VotingPoolWithData } from '@repo/lib/modules/vebal/vote/vote.types' import { orderBy } from 'lodash' import { HiddenHandData } from '@repo/lib/shared/services/hidden-hand/hidden-hand.types' +import { useVoteListFiltersState } from '@repo/lib/modules/vebal/vote/VoteList/useVoteListFiltersState' +import { Sorting } from '@repo/lib/shared/components/tables/SortableHeader' +import { PoolFilterType } from '@repo/lib/modules/pool/pool.types' + +function sortVoteList(voteList: VotingPoolWithData[], sortBy: SortVotesBy, order: Sorting) { + return orderBy( + voteList, + value => { + switch (sortBy) { + case SortVotesBy.votes: + return value.gaugeVotes ? Number(value.gaugeVotes.votesNextPeriod) : -1 + case SortVotesBy.bribes: + return value.votingIncentive ? Number(value.votingIncentive.totalValue) : -1 + case SortVotesBy.bribesPerVebal: + return value.votingIncentive ? Number(value.votingIncentive.valuePerVote) : -1 + case SortVotesBy.type: + return value.type + default: + throw new Error(`Unsupported SortVotesBy value (${sortBy})`) + } + }, + order === Sorting.asc ? 'asc' : 'desc' + ) +} + +function filterVoteList( + voteList: VotingPoolWithData[], + textSearch: string, + networks: GqlChain[], + poolTypes: PoolFilterType[], + includeExpiredPools: boolean +) { + let result = voteList + + const _textSearch = textSearch.toLowerCase().trim() + if (_textSearch) { + result = result.filter(value => { + return ( + value.id.toLowerCase().includes(_textSearch) || + value.tokens.some(token => { + return ( + token.symbol.toLowerCase().includes(_textSearch) || + token.address.toLowerCase().includes(_textSearch) + ) + }) + ) + }) + } + + if (networks.length > 0) { + result = result.filter(value => networks.includes(value.chain)) + } + + if (poolTypes.length > 0) { + result = result.filter(value => poolTypes.includes(value.type as PoolFilterType)) + } + + if (!includeExpiredPools) { + // fix: fixed in feat/my-votes + result = result.filter(value => !value.gaugeVotes?.isKilled) + } + + return result +} export interface UseVoteListArgs { data: GetVeBalVotingListQuery | undefined @@ -24,13 +87,10 @@ export function _useVoteList({ votingIncentivesErrorMessage, votingIncentivesLoading = false, }: UseVoteListArgs) { - // todo: implement vote's sorting/filtering - const queryState = usePoolListQueryState() + const filtersState = useVoteListFiltersState() const voteListData = useMemo(() => data?.veBalGetVotingList || [], [data?.veBalGetVotingList]) - const pagination = queryState.pagination - const gaugeAddresses = useMemo(() => voteListData.map(vote => vote.gauge.address), [voteListData]) const { gaugeVotes, isLoading: gaugeVotesIsLoading } = useGaugeVotes({ gaugeAddresses }) @@ -45,25 +105,41 @@ export function _useVoteList({ })) }, [voteListData, gaugeVotes, votingIncentives]) - const sortedVoteList = useMemo(() => { - const sortedList = orderBy( + const filteredVoteList = useMemo(() => { + return filterVoteList( votingPoolsList, - v => (v.gaugeVotes ? Number(v.gaugeVotes.votesNextPeriod) : 0), - 'desc' + filtersState.searchText, + filtersState.networks, + filtersState.poolTypes, + filtersState.includeExpiredPools + ) + }, [ + votingPoolsList, + filtersState.searchText, + filtersState.networks, + filtersState.poolTypes, + filtersState.includeExpiredPools, + ]) + + const sortedVoteList = useMemo(() => { + const sortedList = sortVoteList( + filteredVoteList, + filtersState.sortVotesBy, + filtersState.sorting ) return sortedList.slice( - pagination.pageIndex * pagination.pageSize, - pagination.pageSize * (pagination.pageIndex + 1) + filtersState.pagination.pageIndex * filtersState.pagination.pageSize, + filtersState.pagination.pageSize * (filtersState.pagination.pageIndex + 1) ) - }, [votingPoolsList, pagination]) + }, [filteredVoteList, filtersState.pagination, filtersState.sorting, filtersState.sortVotesBy]) return { - queryState, + filtersState, sortedVoteList, voteListLoading, loading: voteListLoading || votingIncentivesLoading || gaugeVotesIsLoading, - count: data?.veBalGetVotingList.length, + count: filteredVoteList.length, votingIncentivesLoading, votingIncentivesErrorMessage, // todo: should be used in VoteListTable gaugeVotesIsLoading, diff --git a/packages/lib/modules/vebal/vote/VoteList/VoteListSearch.tsx b/packages/lib/modules/vebal/vote/VoteList/VoteListSearch.tsx new file mode 100644 index 000000000..c974d316f --- /dev/null +++ b/packages/lib/modules/vebal/vote/VoteList/VoteListSearch.tsx @@ -0,0 +1,26 @@ +import { FormControl, Box } from '@chakra-ui/react' +import { SearchInput } from '@repo/lib/shared/components/inputs/SearchInput' +import { useVoteList } from '@repo/lib/modules/vebal/vote/VoteList/VoteListProvider' + +export function VoteListSearch() { + const { + loading, + filtersState: { searchText, setSearch }, + } = useVoteList() + + return ( + +
+ + + +
+
+ ) +} diff --git a/packages/lib/modules/vebal/vote/VoteList/VoteListTable/VoteListTable.tsx b/packages/lib/modules/vebal/vote/VoteList/VoteListTable/VoteListTable.tsx index 2205368b3..1da5bf221 100644 --- a/packages/lib/modules/vebal/vote/VoteList/VoteListTable/VoteListTable.tsx +++ b/packages/lib/modules/vebal/vote/VoteList/VoteListTable/VoteListTable.tsx @@ -18,7 +18,7 @@ interface Props { export function VoteListTable({ voteList, count, loading }: Props) { const isMounted = useIsMounted() const { - queryState: { pagination, setPagination }, + filtersState: { pagination, setPagination }, } = useVoteList() const paginationProps = getPaginationProps(count || 0, pagination, setPagination) const showPagination = !!voteList.length && !!count && count > pagination.pageSize @@ -38,6 +38,8 @@ export function VoteListTable({ voteList, count, loading }: Props) { left={{ base: '-4px', sm: '0' }} p={{ base: '0', sm: '0' }} position="relative" + // fixing right padding for horizontal scroll on mobile + pr={{ base: 'lg', sm: 'lg', md: 'lg', lg: '0' }} w={{ base: '100vw', lg: 'full' }} > { + if (sortVotesBy === newSortVotesBy) { + toggleSorting() + } else { + setSortVotesBy(newSortVotesBy) + setSorting(Sorting.desc) + } + } + return ( @@ -14,26 +33,25 @@ export function VoteListTableHeader({ ...rest }) { Pool name - - - Type - - - - - Bribes - - - - - Bribes/veBAL - - - - - veBAL votes - - + {orderBy.map(orderByItem => ( + + handleSort(orderByItem)} + popoverContent={ + orderByHash[orderByItem].title ? ( + + + {orderByHash[orderByItem].title} + + + ) : undefined + } + sorting={sorting} + /> + + ))} Action diff --git a/packages/lib/modules/vebal/vote/VoteList/VoteListTable/VoteListTableRow.tsx b/packages/lib/modules/vebal/vote/VoteList/VoteListTable/VoteListTableRow.tsx index d07704514..03a43dfca 100644 --- a/packages/lib/modules/vebal/vote/VoteList/VoteListTable/VoteListTableRow.tsx +++ b/packages/lib/modules/vebal/vote/VoteList/VoteListTable/VoteListTableRow.tsx @@ -10,6 +10,7 @@ import { ArrowUpIcon } from '@repo/lib/shared/components/icons/ArrowUpIcon' import React, { useState } from 'react' import { useVoteList } from '@repo/lib/modules/vebal/vote/VoteList/VoteListProvider' import { VoteListVotesCell } from '@repo/lib/modules/vebal/vote/VoteList/VoteListTable/VoteListVotesCell' +import { VoteExpiredTooltip } from '@repo/lib/modules/vebal/vote/VoteExpiredTooltip' interface Props extends GridProps { vote: VotingPoolWithData @@ -29,6 +30,9 @@ export function VoteListTableRow({ vote, keyValue, ...rest }: Props) { const { votingIncentivesLoading, gaugeVotesIsLoading } = useVoteList() + // fix: fixed in feat/my-votes + const isKilled = vote.gaugeVotes?.isKilled + return ( + {isKilled && } @@ -100,16 +105,28 @@ export function VoteListTableRow({ vote, keyValue, ...rest }: Props) { )} - + {isKilled ? ( + + ) : ( + + )} diff --git a/packages/lib/modules/vebal/vote/VoteList/useVoteListFiltersState.tsx b/packages/lib/modules/vebal/vote/VoteList/useVoteListFiltersState.tsx new file mode 100644 index 000000000..214c04b5e --- /dev/null +++ b/packages/lib/modules/vebal/vote/VoteList/useVoteListFiltersState.tsx @@ -0,0 +1,105 @@ +import { useMemo, useState } from 'react' +import { PaginationState } from '@repo/lib/shared/components/pagination/pagination.types' +import { SortVotesBy } from '@repo/lib/modules/vebal/vote/vote.types' +import { Sorting } from '@repo/lib/shared/components/tables/SortableHeader' +import { GqlChain } from '@repo/lib/shared/services/api/generated/graphql' +import { uniq } from 'lodash' +import { PoolFilterType } from '@repo/lib/modules/pool/pool.types' + +const EMPTY_ARRAY: never[] = [] + +export function useVoteListFiltersState() { + const [sorting, setSorting] = useState(Sorting.desc) + const [sortVotesBy, setSortVotesBy] = useState(SortVotesBy.votes) + + const [first, setFirst] = useState(20) + const [skip, setSkip] = useState(0) + + const pagination: PaginationState = useMemo( + () => ({ + pageIndex: skip / first, + pageSize: first, + }), + [skip, first] + ) + + function setPagination(pagination: PaginationState) { + setFirst(pagination.pageSize) + setSkip(pagination.pageIndex * pagination.pageSize) + } + + function toggleSorting() { + setSorting(sorting === Sorting.asc ? Sorting.desc : Sorting.asc) + } + + const [networks, setNetworks] = useState(null) + const [poolTypes, setPoolTypes] = useState(null) + const [includeExpiredPools, setIncludeExpiredPools] = useState(false) + + const [textSearch, setTextSearch] = useState('') + + function resetFilters() { + setNetworks(null) + setPoolTypes(null) + setIncludeExpiredPools(false) + + setPagination({ pageSize: 20, pageIndex: 0 }) + setSorting(Sorting.desc) + setSortVotesBy(SortVotesBy.votes) + } + + // Set internal checked state + function toggleNetwork(checked: boolean, network: GqlChain) { + if (checked) { + setNetworks(current => uniq([...(current ?? []), network])) + } else { + setNetworks(current => (current ?? []).filter(chain => chain !== network)) + } + } + + // Set internal checked state + function togglePoolType(checked: boolean, poolType: PoolFilterType) { + if (checked) { + setPoolTypes(current => uniq([...(current ?? []), poolType])) + } else { + setPoolTypes(current => (current ?? []).filter(type => type !== poolType)) + } + } + + // Set internal checked state + function toggleIncludeExpiredPools(checked: boolean) { + setIncludeExpiredPools(checked) + } + + const totalFilterCount = + (networks?.length ?? 0) + (poolTypes?.length ?? 0) + (includeExpiredPools ? 1 : 0) + + function setSearch(text: string) { + if (text.length > 0) { + setSkip(0) + } + setTextSearch(text) + } + + return { + sorting, + setSorting, + sortVotesBy, + setSortVotesBy, + pagination, + setPagination, + toggleSorting, + totalFilterCount, + resetFilters, + searchText: textSearch, + setSearch, + networks: networks ?? EMPTY_ARRAY, + poolTypes: poolTypes ?? EMPTY_ARRAY, + includeExpiredPools, + toggleNetwork, + togglePoolType, + toggleIncludeExpiredPools, + setNetworks, + setPoolTypes, + } +} diff --git a/packages/lib/modules/vebal/vote/useGaugeVotes.tsx b/packages/lib/modules/vebal/vote/useGaugeVotes.tsx index 94759b8eb..1c1951ed1 100644 --- a/packages/lib/modules/vebal/vote/useGaugeVotes.tsx +++ b/packages/lib/modules/vebal/vote/useGaugeVotes.tsx @@ -22,6 +22,7 @@ export interface RawVotesData { gaugeWeightNextPeriod?: { result?: bigint; status: string } userVotes?: { result?: UserVotesData; status: string } lastUserVoteTime?: { result?: bigint; status: string } + isKilled?: { result?: boolean; status: string } } export interface VotesData { @@ -29,6 +30,7 @@ export interface VotesData { votesNextPeriod: string userVotes: string lastUserVoteTime: number + isKilled: boolean } function formatVotes(votesData: RawVotesData): VotesData { @@ -40,6 +42,7 @@ function formatVotes(votesData: RawVotesData): VotesData { votesNextPeriod, userVotes: votesData?.userVotes?.result?.power.toString() || '0', lastUserVoteTime: Number(votesData?.lastUserVoteTime?.result) || 0, + isKilled: votesData?.isKilled?.result ?? false, } } diff --git a/packages/lib/modules/vebal/vote/vote.helpers.ts b/packages/lib/modules/vebal/vote/vote.helpers.ts index ef35ad656..9dcbc9544 100644 --- a/packages/lib/modules/vebal/vote/vote.helpers.ts +++ b/packages/lib/modules/vebal/vote/vote.helpers.ts @@ -1,4 +1,4 @@ -import { VotesState } from '@repo/lib/modules/vebal/vote/vote.types' +import { VotesState, SortVotesBy } from '@repo/lib/modules/vebal/vote/vote.types' export function getVotesState(relativeWeightCap: number, votesNextPeriod: number) { if (relativeWeightCap === 0 || votesNextPeriod === 0) return VotesState.Normal @@ -10,3 +10,18 @@ export function getVotesState(relativeWeightCap: number, votesNextPeriod: number } return VotesState.Normal } + +export const orderByHash: Record = { + type: { label: 'Type' }, + bribes: { + label: 'Bribes', + title: + 'Voting incentives (referred to as ‘Bribes’ in DeFi) are provided by unaffiliated 3rd parties through the Hidden Hand platform to incentivize liquidity to certain pools.', + }, + bribesPerVebal: { + label: 'Bribes/veBAL', + title: + 'This shows the ratio of 3rd party voting incentives (known as Bribes) to veBAL. The higher this ratio, the more profitable it is to currently vote on this pool. Note this ratio could change up till the voting deadline.', + }, + votes: { label: 'veBAL votes' }, +} diff --git a/packages/lib/modules/vebal/vote/vote.types.ts b/packages/lib/modules/vebal/vote/vote.types.ts index b84c89af6..ece90999d 100644 --- a/packages/lib/modules/vebal/vote/vote.types.ts +++ b/packages/lib/modules/vebal/vote/vote.types.ts @@ -17,13 +17,9 @@ export enum VotesState { Exceeded = 'exceeded', } -export function getVotesState(relativeWeightCap: number, votesNextPeriod: number) { - if (relativeWeightCap === 0 || votesNextPeriod === 0) return VotesState.Normal - if (votesNextPeriod > relativeWeightCap) { - return VotesState.Exceeded - } - if (relativeWeightCap - votesNextPeriod <= 0.01) { - return VotesState.Close - } - return VotesState.Normal +export enum SortVotesBy { + type = 'type', + bribes = 'bribes', + bribesPerVebal = 'bribesPerVebal', + votes = 'votes', } diff --git a/packages/lib/modules/web3/contracts/AbiMap.ts b/packages/lib/modules/web3/contracts/AbiMap.ts index 3ca322010..1f493a805 100644 --- a/packages/lib/modules/web3/contracts/AbiMap.ts +++ b/packages/lib/modules/web3/contracts/AbiMap.ts @@ -14,6 +14,7 @@ import { } from './abi/generated' import { VeDelegationProxyL2Abi } from './abi/veDelegationProxyL2' import { sfcAbi, sonicStakingAbi } from './abi/beets/generated' +import { LiquidityGaugeV5Abi } from './abi/LiquidityGaugeV5Abi' export const AbiMap = { 'balancer.vaultV2': balancerV2VaultAbi, @@ -29,6 +30,7 @@ export const AbiMap = { 'balancer.LiquidityGauge': LiquidityGaugeAbi, 'balancer.omniVotingEscrowAbi': OmniVotingEscrowAbi, 'balancer.gaugeControllerAbi': GaugeControllerAbi, + 'balancer.liquidityGaugeV5Abi': LiquidityGaugeV5Abi, 'beets.lstStaking': sonicStakingAbi, 'beets.sfc': sfcAbi, } diff --git a/packages/lib/modules/web3/contracts/abi/LiquidityGaugeV5Abi.ts b/packages/lib/modules/web3/contracts/abi/LiquidityGaugeV5Abi.ts new file mode 100644 index 000000000..706a3264b --- /dev/null +++ b/packages/lib/modules/web3/contracts/abi/LiquidityGaugeV5Abi.ts @@ -0,0 +1,1016 @@ +export const LiquidityGaugeV5Abi = [ + { + name: 'Deposit', + inputs: [ + { + name: 'provider', + type: 'address', + indexed: true, + }, + { + name: 'value', + type: 'uint256', + indexed: false, + }, + ], + anonymous: false, + type: 'event', + }, + { + name: 'Withdraw', + inputs: [ + { + name: 'provider', + type: 'address', + indexed: true, + }, + { + name: 'value', + type: 'uint256', + indexed: false, + }, + ], + anonymous: false, + type: 'event', + }, + { + name: 'UpdateLiquidityLimit', + inputs: [ + { + name: 'user', + type: 'address', + indexed: true, + }, + { + name: 'original_balance', + type: 'uint256', + indexed: false, + }, + { + name: 'original_supply', + type: 'uint256', + indexed: false, + }, + { + name: 'working_balance', + type: 'uint256', + indexed: false, + }, + { + name: 'working_supply', + type: 'uint256', + indexed: false, + }, + ], + anonymous: false, + type: 'event', + }, + { + name: 'Transfer', + inputs: [ + { + name: '_from', + type: 'address', + indexed: true, + }, + { + name: '_to', + type: 'address', + indexed: true, + }, + { + name: '_value', + type: 'uint256', + indexed: false, + }, + ], + anonymous: false, + type: 'event', + }, + { + name: 'Approval', + inputs: [ + { + name: '_owner', + type: 'address', + indexed: true, + }, + { + name: '_spender', + type: 'address', + indexed: true, + }, + { + name: '_value', + type: 'uint256', + indexed: false, + }, + ], + anonymous: false, + type: 'event', + }, + { + stateMutability: 'nonpayable', + type: 'constructor', + inputs: [ + { + name: 'minter', + type: 'address', + }, + { + name: 'veBoostProxy', + type: 'address', + }, + ], + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + name: 'deposit', + inputs: [ + { + name: '_value', + type: 'uint256', + }, + ], + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + name: 'deposit', + inputs: [ + { + name: '_value', + type: 'uint256', + }, + { + name: '_addr', + type: 'address', + }, + ], + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + name: 'deposit', + inputs: [ + { + name: '_value', + type: 'uint256', + }, + { + name: '_addr', + type: 'address', + }, + { + name: '_claim_rewards', + type: 'bool', + }, + ], + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + name: 'withdraw', + inputs: [ + { + name: '_value', + type: 'uint256', + }, + ], + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + name: 'withdraw', + inputs: [ + { + name: '_value', + type: 'uint256', + }, + { + name: '_claim_rewards', + type: 'bool', + }, + ], + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + name: 'claim_rewards', + inputs: [], + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + name: 'claim_rewards', + inputs: [ + { + name: '_addr', + type: 'address', + }, + ], + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + name: 'claim_rewards', + inputs: [ + { + name: '_addr', + type: 'address', + }, + { + name: '_receiver', + type: 'address', + }, + ], + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + name: 'transferFrom', + inputs: [ + { + name: '_from', + type: 'address', + }, + { + name: '_to', + type: 'address', + }, + { + name: '_value', + type: 'uint256', + }, + ], + outputs: [ + { + name: '', + type: 'bool', + }, + ], + }, + { + stateMutability: 'nonpayable', + type: 'function', + name: 'transfer', + inputs: [ + { + name: '_to', + type: 'address', + }, + { + name: '_value', + type: 'uint256', + }, + ], + outputs: [ + { + name: '', + type: 'bool', + }, + ], + }, + { + stateMutability: 'nonpayable', + type: 'function', + name: 'approve', + inputs: [ + { + name: '_spender', + type: 'address', + }, + { + name: '_value', + type: 'uint256', + }, + ], + outputs: [ + { + name: '', + type: 'bool', + }, + ], + }, + { + stateMutability: 'nonpayable', + type: 'function', + name: 'permit', + inputs: [ + { + name: '_owner', + type: 'address', + }, + { + name: '_spender', + type: 'address', + }, + { + name: '_value', + type: 'uint256', + }, + { + name: '_deadline', + type: 'uint256', + }, + { + name: '_v', + type: 'uint8', + }, + { + name: '_r', + type: 'bytes32', + }, + { + name: '_s', + type: 'bytes32', + }, + ], + outputs: [ + { + name: '', + type: 'bool', + }, + ], + }, + { + stateMutability: 'nonpayable', + type: 'function', + name: 'increaseAllowance', + inputs: [ + { + name: '_spender', + type: 'address', + }, + { + name: '_added_value', + type: 'uint256', + }, + ], + outputs: [ + { + name: '', + type: 'bool', + }, + ], + }, + { + stateMutability: 'nonpayable', + type: 'function', + name: 'decreaseAllowance', + inputs: [ + { + name: '_spender', + type: 'address', + }, + { + name: '_subtracted_value', + type: 'uint256', + }, + ], + outputs: [ + { + name: '', + type: 'bool', + }, + ], + }, + { + stateMutability: 'nonpayable', + type: 'function', + name: 'user_checkpoint', + inputs: [ + { + name: 'addr', + type: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'bool', + }, + ], + }, + { + stateMutability: 'nonpayable', + type: 'function', + name: 'set_rewards_receiver', + inputs: [ + { + name: '_receiver', + type: 'address', + }, + ], + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + name: 'kick', + inputs: [ + { + name: 'addr', + type: 'address', + }, + ], + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + name: 'deposit_reward_token', + inputs: [ + { + name: '_reward_token', + type: 'address', + }, + { + name: '_amount', + type: 'uint256', + }, + ], + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + name: 'add_reward', + inputs: [ + { + name: '_reward_token', + type: 'address', + }, + { + name: '_distributor', + type: 'address', + }, + ], + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + name: 'set_reward_distributor', + inputs: [ + { + name: '_reward_token', + type: 'address', + }, + { + name: '_distributor', + type: 'address', + }, + ], + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + name: 'set_killed', + inputs: [ + { + name: '_is_killed', + type: 'bool', + }, + ], + outputs: [], + }, + { + stateMutability: 'view', + type: 'function', + name: 'claimed_reward', + inputs: [ + { + name: '_addr', + type: 'address', + }, + { + name: '_token', + type: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'claimable_reward', + inputs: [ + { + name: '_user', + type: 'address', + }, + { + name: '_reward_token', + type: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'claimable_reward_write', + inputs: [ + { + name: '_addr', + type: 'address', + }, + { + name: '_token', + type: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'claimable_tokens', + inputs: [ + { + name: 'addr', + type: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'integrate_checkpoint', + inputs: [], + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'future_epoch_time', + inputs: [], + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'inflation_rate', + inputs: [], + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'decimals', + inputs: [], + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'version', + inputs: [], + outputs: [ + { + name: '', + type: 'string', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'allowance', + inputs: [ + { + name: 'owner', + type: 'address', + }, + { + name: 'spender', + type: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + }, + { + stateMutability: 'nonpayable', + type: 'function', + name: 'initialize', + inputs: [ + { + name: '_lp_token', + type: 'address', + }, + ], + outputs: [], + }, + { + stateMutability: 'view', + type: 'function', + name: 'balanceOf', + inputs: [ + { + name: 'arg0', + type: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'totalSupply', + inputs: [], + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'name', + inputs: [], + outputs: [ + { + name: '', + type: 'string', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'symbol', + inputs: [], + outputs: [ + { + name: '', + type: 'string', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'DOMAIN_SEPARATOR', + inputs: [], + outputs: [ + { + name: '', + type: 'bytes32', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'nonces', + inputs: [ + { + name: 'arg0', + type: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'factory', + inputs: [], + outputs: [ + { + name: '', + type: 'address', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'lp_token', + inputs: [], + outputs: [ + { + name: '', + type: 'address', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'is_killed', + inputs: [], + outputs: [ + { + name: '', + type: 'bool', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'reward_count', + inputs: [], + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'reward_data', + inputs: [ + { + name: 'arg0', + type: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'tuple', + components: [ + { + name: 'token', + type: 'address', + }, + { + name: 'distributor', + type: 'address', + }, + { + name: 'period_finish', + type: 'uint256', + }, + { + name: 'rate', + type: 'uint256', + }, + { + name: 'last_update', + type: 'uint256', + }, + { + name: 'integral', + type: 'uint256', + }, + ], + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'rewards_receiver', + inputs: [ + { + name: 'arg0', + type: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'address', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'reward_integral_for', + inputs: [ + { + name: 'arg0', + type: 'address', + }, + { + name: 'arg1', + type: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'working_balances', + inputs: [ + { + name: 'arg0', + type: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'working_supply', + inputs: [], + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'integrate_inv_supply_of', + inputs: [ + { + name: 'arg0', + type: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'integrate_checkpoint_of', + inputs: [ + { + name: 'arg0', + type: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'integrate_fraction', + inputs: [ + { + name: 'arg0', + type: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'period', + inputs: [], + outputs: [ + { + name: '', + type: 'int128', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'reward_tokens', + inputs: [ + { + name: 'arg0', + type: 'uint256', + }, + ], + outputs: [ + { + name: '', + type: 'address', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'period_timestamp', + inputs: [ + { + name: 'arg0', + type: 'uint256', + }, + ], + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'integrate_inv_supply', + inputs: [ + { + name: 'arg0', + type: 'uint256', + }, + ], + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + }, +] diff --git a/packages/lib/shared/components/tables/SortableHeader.tsx b/packages/lib/shared/components/tables/SortableHeader.tsx index b7519f4e4..e3d36ab99 100644 --- a/packages/lib/shared/components/tables/SortableHeader.tsx +++ b/packages/lib/shared/components/tables/SortableHeader.tsx @@ -1,14 +1,31 @@ -import { Box, Button, GridItem, HStack, Text } from '@chakra-ui/react' +import { + Box, + Button, + GridItem, + HStack, + Popover, + PopoverTrigger, + Portal, + Text, +} from '@chakra-ui/react' import { SortableIcon } from '../icons/SortableIcon' import { ArrowDownIcon } from '../icons/ArrowDownIcon' import { ArrowUpIcon } from '../icons/ArrowUpIcon' +import { ReactNode } from 'react' + +export enum Sorting { + asc = 'asc', + desc = 'desc', +} type SortableHeaderProps = { label: string isSorted: boolean - sorting: 'asc' | 'desc' + sorting: Sorting onSort: (newSortingBy: any) => void align?: 'left' | 'right' + popoverContent?: ReactNode + usePortal?: string } export function SortableHeader({ @@ -17,25 +34,44 @@ export function SortableHeader({ onSort, sorting, align = 'left', + popoverContent, + usePortal, }: SortableHeaderProps) { const renderSortIcon = () => { return !isSorted ? : sorting === 'asc' ? : } const color = isSorted ? 'font.highlight' : 'font.primary' + const justifySelf = align === 'left' ? 'start' : 'end' - return ( - - - + const HeaderContent = ( + ) + + if (popoverContent) { + return ( + + + {() => ( + <> + {HeaderContent} + + {usePortal ? {popoverContent} : popoverContent} + + )} + + + ) + } + + return {HeaderContent} }