From 61cb4ec0bb9223df48ceef04ce1e95164afd336c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Wo=C5=82odko?= Date: Sat, 4 Jan 2025 20:59:58 +0100 Subject: [PATCH 01/42] Implement your position rewarded idication --- .../PromotedPoolPopover.tsx | 43 +++++++---- .../PositionItem/PositionItem.tsx | 73 ++++++++++++++++++- .../PositionsList/PositionItem/style.tsx | 20 +++++ .../PositionsList/PositionsList.tsx | 8 +- src/components/PositionsList/style.ts | 1 + .../WrappedPositionsList.tsx | 6 ++ 6 files changed, 132 insertions(+), 19 deletions(-) diff --git a/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx b/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx index 5b953e86..ebf80bc8 100644 --- a/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx +++ b/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx @@ -2,13 +2,15 @@ import { BN } from '@coral-xyz/anchor' import useStyles from './style' import { Popover, Typography } from '@mui/material' import { formatNumberWithCommas, printBN } from '@utils/utils' +import { LEADERBOARD_DECIMAL } from '@pages/LeaderboardPage/config' export interface IPromotedPoolPopover { open: boolean anchorEl: HTMLElement | null onClose: () => void - apr: number - apy: number + apr?: BN + apy?: number + estPoints?: BN points: BN } @@ -18,6 +20,7 @@ export const PromotedPoolPopover = ({ anchorEl, apr, apy, + estPoints, points }: IPromotedPoolPopover) => { const { classes } = useStyles() @@ -57,18 +60,30 @@ export const PromotedPoolPopover = ({ {formatNumberWithCommas(printBN(points, 0))} -
- - APY - APR - {' '} - - {`${apy > 1000 ? '>1000%' : apy === 0 ? '' : apy.toFixed(2) + '%'}`} - - {`${apr > 1000 ? '>1000%' : apr === 0 ? '-' : apr.toFixed(2) + '%'}`} - - -
+ {estPoints ? ( +
+ Your Points + {printBN(estPoints, 0)} +
+ ) : null} + + {apr && apy ? ( + <> +
+ + APY + APR + {' '} + + {`${apy > 1000 ? '>1000%' : apy === 0 ? '' : apy.toFixed(2) + '%'}`} + + {`${apr > 1000 ? '>1000%' : apr === 0 ? '-' : apr.toFixed(2) + '%'}`} + + {printBN(apr, LEADERBOARD_DECIMAL)} + +
+ + ) : null} diff --git a/src/components/PositionsList/PositionItem/PositionItem.tsx b/src/components/PositionsList/PositionItem/PositionItem.tsx index 87d6375c..3d1f7799 100644 --- a/src/components/PositionsList/PositionItem/PositionItem.tsx +++ b/src/components/PositionsList/PositionItem/PositionItem.tsx @@ -1,15 +1,24 @@ -import { Grid, Hidden, Tooltip, Typography, useMediaQuery } from '@mui/material' +import { Box, Grid, Hidden, Tooltip, Typography, useMediaQuery } from '@mui/material' import SwapList from '@static/svg/swap-list.svg' import { theme } from '@static/theme' import { formatNumber } from '@utils/utils' import classNames from 'classnames' -import { useMemo, useState } from 'react' +import { useMemo, useRef, useState } from 'react' import { useStyles } from './style' import { TooltipHover } from '@components/TooltipHover/TooltipHover' import { initialXtoY, tickerToAddress } from '@utils/utils' import { NetworkType } from '@store/consts/static' import lockIcon from '@static/svg/lock.svg' import unlockIcon from '@static/svg/unlock.svg' +import icons from '@static/icons' +import { PublicKey } from '@solana/web3.js' +import { useSelector } from 'react-redux' +import { leaderboardSelectors } from '@store/selectors/leaderboard' +import PromotedPoolPopover from '@components/Modals/PromotedPoolPopover/PromotedPoolPopover' +import { BN } from '@coral-xyz/anchor' +import { estimatePointsForLiquidity } from '@invariant-labs/points-sdk' +import { PoolStructure } from '@invariant-labs/sdk-eclipse/lib/market' +import { PoolWithAddressAndIndex } from '@store/selectors/positions' export interface IPositionItem { tokenXName: string @@ -17,6 +26,7 @@ export interface IPositionItem { tokenXIcon: string tokenYIcon: string tokenXLiq: number + poolAddress: PublicKey tokenYLiq: number fee: number min: number @@ -30,18 +40,23 @@ export interface IPositionItem { network: NetworkType isFullRange: boolean isLocked: boolean + poolData: PoolWithAddressAndIndex + liquidity: BN } export const PositionItem: React.FC = ({ tokenXName, tokenYName, tokenXIcon, + poolAddress, tokenYIcon, fee, min, max, valueX, valueY, + liquidity, + poolData, isActive = false, currentPrice, tokenXLiq, @@ -51,6 +66,18 @@ export const PositionItem: React.FC = ({ isLocked }) => { const { classes } = useStyles() + const airdropIconRef = useRef(null) + + const { promotedPools } = useSelector(leaderboardSelectors.config) + const [isPromotedPoolPopoverOpen, setIsPromotedPoolPopoverOpen] = useState(false) + + const { isPromoted, pointsPerSecond } = useMemo(() => { + if (!poolAddress) return { isPromoted: false, pointsPerSecond: '00' } + const promotedPool = promotedPools.find(pool => pool.address === poolAddress.toString()) + + if (!promotedPool) return { isPromoted: false, pointsPerSecond: '00' } + return { isPromoted: true, pointsPerSecond: promotedPool.pointsPerSecond } + }, [promotedPools, poolAddress]) const isXs = useMediaQuery(theme.breakpoints.down('xs')) const isDesktop = useMediaQuery(theme.breakpoints.up('lg')) @@ -134,6 +161,15 @@ export const PositionItem: React.FC = ({ ), [valueX, valueY, tokenXName, classes, isXs, isDesktop, tokenYName, xToY] ) + const estimation = useMemo(() => { + const estOfPoints = estimatePointsForLiquidity( + new BN(liquidity, 'hex'), + poolData as PoolStructure, + new BN(pointsPerSecond, 'hex'), + false + ) as BN + return estOfPoints + }, [liquidity, poolData, pointsPerSecond]) return ( = ({ {xToY ? tokenXName : tokenYName} - {xToY ? tokenYName : tokenXName} - - {feeFragment} + + {isPromoted && ( + <> +
{ + setIsPromotedPoolPopoverOpen(false) + }} + onPointerEnter={() => { + setIsPromotedPoolPopoverOpen(true) + }}> + {'Airdrop'} +
+ { + setIsPromotedPoolPopoverOpen(false) + }} + estPoints={estimation} + points={new BN(pointsPerSecond, 'hex').muln(24).muln(60).muln(60)} + /> + + )} + {feeFragment} +
{feeFragment} ({ width: 28 } }, + actionButton: { + background: 'none', + padding: 0, + margin: 0, + border: 'none', + display: 'inline-flex', + position: 'relative', + color: colors.invariant.black, + textTransform: 'none', + + transition: 'filter 0.2s linear', + + '&:hover': { + filter: 'brightness(1.2)', + cursor: 'pointer', + '@media (hover: none)': { + filter: 'none' + } + } + }, arrows: { width: 36, marginLeft: 4, diff --git a/src/components/PositionsList/PositionsList.tsx b/src/components/PositionsList/PositionsList.tsx index 7d8597ce..408ab90e 100644 --- a/src/components/PositionsList/PositionsList.tsx +++ b/src/components/PositionsList/PositionsList.tsx @@ -19,6 +19,8 @@ import { IPositionItem, PositionItem } from './PositionItem/PositionItem' import { useStyles } from './style' import { TooltipHover } from '@components/TooltipHover/TooltipHover' import { PaginationList } from '@components/Pagination/Pagination' +import { useDispatch } from 'react-redux' +import { actions } from '@store/reducers/leaderboard' export enum LiquidityPools { Standard = 'Standard', @@ -69,6 +71,7 @@ export const PositionsList: React.FC = ({ const { classes } = useStyles() const navigate = useNavigate() const [defaultPage] = useState(initialPage) + const dispatch = useDispatch() const [page, setPage] = useState(initialPage) const [alignment, setAlignment] = useState(LiquidityPools.Standard) @@ -135,10 +138,13 @@ export const PositionsList: React.FC = ({ handleChangePagination(initialPage) }, [initialPage]) + useEffect(() => { + dispatch(actions.getLeaderboardConfig()) + }, [dispatch]) + // useEffect(() => { // pageChanged(page) // }, [page]) - return ( ({ height: 48 } }, + button: { color: colors.invariant.dark, ...typography.body1, diff --git a/src/containers/WrappedPositionsList/WrappedPositionsList.tsx b/src/containers/WrappedPositionsList/WrappedPositionsList.tsx index c2ae3648..a7556ead 100644 --- a/src/containers/WrappedPositionsList/WrappedPositionsList.tsx +++ b/src/containers/WrappedPositionsList/WrappedPositionsList.tsx @@ -115,6 +115,9 @@ export const WrappedPositionsList: React.FC = () => { tokenYName: position.tokenY.symbol, tokenXIcon: position.tokenX.logoURI, tokenYIcon: position.tokenY.logoURI, + poolAddress: position.poolData.address, + liquidity: position.liquidity, + poolData: position.poolData, fee: +printBN(position.poolData.fee, DECIMAL - 2), min, max, @@ -206,6 +209,9 @@ export const WrappedPositionsList: React.FC = () => { max, valueX, valueY, + poolAddress: position.poolData.address, + liquidity: position.liquidity, + poolData: position.poolData, address: walletAddress.toString(), id: position.id.toString() + '_' + position.pool.toString(), isActive: currentPrice >= min && currentPrice <= max, From 501e531aa988b642b9f440492fd12a9d4f556e71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Wo=C5=82odko?= Date: Sat, 4 Jan 2025 21:20:04 +0100 Subject: [PATCH 02/42] Fix --- .../PositionItem/PositionItem.stories.tsx | 34 +++++ .../PositionsList/PositionsList.stories.tsx | 125 +++++++++++++++++- 2 files changed, 156 insertions(+), 3 deletions(-) diff --git a/src/components/PositionsList/PositionItem/PositionItem.stories.tsx b/src/components/PositionsList/PositionItem/PositionItem.stories.tsx index 6dac29e7..f88ed7e6 100644 --- a/src/components/PositionsList/PositionItem/PositionItem.stories.tsx +++ b/src/components/PositionsList/PositionItem/PositionItem.stories.tsx @@ -2,6 +2,8 @@ import { NetworkType } from '@store/consts/static' import { PositionItem } from './PositionItem' import type { Meta, StoryObj } from '@storybook/react' +import { Keypair } from '@solana/web3.js' +import { BN } from '@coral-xyz/anchor' const meta = { title: 'Components/PositionItem', @@ -23,7 +25,39 @@ export const Primary: Story = { max: 149.6, fee: 0.05, valueX: 10000.45, + valueY: 2137.4, + liquidity: new BN(0), + poolAddress: Keypair.generate().publicKey, + + poolData: { + address: Keypair.generate().publicKey, + bump: 0, + currentTickIndex: 0, + fee: new BN(0), + feeGrowthGlobalX: new BN(0), + feeProtocolTokenX: new BN(0), + feeProtocolTokenY: new BN(0), + feeReceiver: Keypair.generate().publicKey, + lastTimestamp: new BN(0), + oracleAddress: Keypair.generate().publicKey, + oracleInitialized: true, + liquidity: new BN(0), + poolIndex: 0, + positionIterator: new BN(0), + protocolFee: new BN(0), + secondsPerLiquidityGlobal: new BN(0), + sqrtPrice: new BN(0), + startTimestamp: new BN(0), + tickmap: Keypair.generate().publicKey, + tickSpacing: 0, + tokenX: Keypair.generate().publicKey, + tokenY: Keypair.generate().publicKey, + tokenXReserve: Keypair.generate().publicKey, + tokenYReserve: Keypair.generate().publicKey, + feeGrowthGlobalY: new BN(0) + }, + id: '0', address: '', tokenXLiq: 5000, diff --git a/src/components/PositionsList/PositionsList.stories.tsx b/src/components/PositionsList/PositionsList.stories.tsx index 8b019c28..71702893 100644 --- a/src/components/PositionsList/PositionsList.stories.tsx +++ b/src/components/PositionsList/PositionsList.stories.tsx @@ -3,6 +3,9 @@ import { BrowserRouter } from 'react-router-dom' import { PositionsList } from './PositionsList' import { NetworkType } from '@store/consts/static' import { IPositionItem } from './PositionItem/PositionItem' +import { Pair } from '@invariant-labs/sdk-eclipse' +import { Keypair } from '@solana/web3.js' +import { BN } from '@coral-xyz/anchor' const meta = { title: 'Components/PositionsList', @@ -33,6 +36,35 @@ const data: IPositionItem[] = [ 'https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v/logo.png', min: 2149.6, max: 149.6, + liquidity: 453.5, + poolAddress: Keypair.generate().publicKey, + poolData: { + address: Keypair.generate().publicKey, + bump: 0, + currentTickIndex: 0, + fee: new BN(0), + feeGrowthGlobalX: new BN(0), + feeProtocolTokenX: new BN(0), + feeProtocolTokenY: new BN(0), + feeReceiver: Keypair.generate().publicKey, + lastTimestamp: new BN(0), + oracleAddress: Keypair.generate().publicKey, + oracleInitialized: true, + liquidity: new BN(0), + poolIndex: 0, + positionIterator: new BN(0), + protocolFee: new BN(0), + secondsPerLiquidityGlobal: new BN(0), + sqrtPrice: new BN(0), + startTimestamp: new BN(0), + tickmap: Keypair.generate().publicKey, + tickSpacing: 0, + tokenX: Keypair.generate().publicKey, + tokenY: Keypair.generate().publicKey, + tokenXReserve: Keypair.generate().publicKey, + tokenYReserve: Keypair.generate().publicKey, + feeGrowthGlobalY: new BN(0) + }, fee: 0.05, tokenXLiq: 5000, tokenYLiq: 300.2, @@ -60,7 +92,36 @@ const data: IPositionItem[] = [ valueX: 10000.45, valueY: 21370.4, id: '2', - isLocked: false + isLocked: false, + liquidity: new BN(0), + poolAddress: Keypair.generate().publicKey, + poolData: { + address: Keypair.generate().publicKey, + bump: 0, + currentTickIndex: 0, + fee: new BN(0), + feeGrowthGlobalX: new BN(0), + feeProtocolTokenX: new BN(0), + feeProtocolTokenY: new BN(0), + feeReceiver: Keypair.generate().publicKey, + lastTimestamp: new BN(0), + oracleAddress: Keypair.generate().publicKey, + oracleInitialized: true, + liquidity: new BN(0), + poolIndex: 0, + positionIterator: new BN(0), + protocolFee: new BN(0), + secondsPerLiquidityGlobal: new BN(0), + sqrtPrice: new BN(0), + startTimestamp: new BN(0), + tickmap: Keypair.generate().publicKey, + tickSpacing: 0, + tokenX: Keypair.generate().publicKey, + tokenY: Keypair.generate().publicKey, + tokenXReserve: Keypair.generate().publicKey, + tokenYReserve: Keypair.generate().publicKey, + feeGrowthGlobalY: new BN(0) + } }, { address: 'So11111111111111111111111111111111111111112', @@ -81,7 +142,36 @@ const data: IPositionItem[] = [ valueX: 10000.45, valueY: 21370.4, id: '3', - isLocked: false + isLocked: false, + liquidity: new BN(0), + poolAddress: Keypair.generate().publicKey, + poolData: { + address: Keypair.generate().publicKey, + bump: 0, + currentTickIndex: 0, + fee: new BN(0), + feeGrowthGlobalX: new BN(0), + feeProtocolTokenX: new BN(0), + feeProtocolTokenY: new BN(0), + feeReceiver: Keypair.generate().publicKey, + lastTimestamp: new BN(0), + oracleAddress: Keypair.generate().publicKey, + oracleInitialized: true, + liquidity: new BN(0), + poolIndex: 0, + positionIterator: new BN(0), + protocolFee: new BN(0), + secondsPerLiquidityGlobal: new BN(0), + sqrtPrice: new BN(0), + startTimestamp: new BN(0), + tickmap: Keypair.generate().publicKey, + tickSpacing: 0, + tokenX: Keypair.generate().publicKey, + tokenY: Keypair.generate().publicKey, + tokenXReserve: Keypair.generate().publicKey, + tokenYReserve: Keypair.generate().publicKey, + feeGrowthGlobalY: new BN(0) + } }, { address: 'So11111111111111111111111111111111111111112', @@ -102,7 +192,36 @@ const data: IPositionItem[] = [ valueX: 10000.45, valueY: 21370.4, id: '4', - isLocked: false + isLocked: false, + liquidity: new BN(0), + poolAddress: Keypair.generate().publicKey, + poolData: { + address: Keypair.generate().publicKey, + bump: 0, + currentTickIndex: 0, + fee: new BN(0), + feeGrowthGlobalX: new BN(0), + feeProtocolTokenX: new BN(0), + feeProtocolTokenY: new BN(0), + feeReceiver: Keypair.generate().publicKey, + lastTimestamp: new BN(0), + oracleAddress: Keypair.generate().publicKey, + oracleInitialized: true, + liquidity: new BN(0), + poolIndex: 0, + positionIterator: new BN(0), + protocolFee: new BN(0), + secondsPerLiquidityGlobal: new BN(0), + sqrtPrice: new BN(0), + startTimestamp: new BN(0), + tickmap: Keypair.generate().publicKey, + tickSpacing: 0, + tokenX: Keypair.generate().publicKey, + tokenY: Keypair.generate().publicKey, + tokenXReserve: Keypair.generate().publicKey, + tokenYReserve: Keypair.generate().publicKey, + feeGrowthGlobalY: new BN(0) + } } ] From 890a31d4c06f58d79e352e763f8b5d486910a5ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Wo=C5=82odko?= Date: Sat, 4 Jan 2025 21:22:32 +0100 Subject: [PATCH 03/42] Fix --- src/components/PositionsList/PositionsList.stories.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/PositionsList/PositionsList.stories.tsx b/src/components/PositionsList/PositionsList.stories.tsx index 71702893..70d9ef5c 100644 --- a/src/components/PositionsList/PositionsList.stories.tsx +++ b/src/components/PositionsList/PositionsList.stories.tsx @@ -3,7 +3,6 @@ import { BrowserRouter } from 'react-router-dom' import { PositionsList } from './PositionsList' import { NetworkType } from '@store/consts/static' import { IPositionItem } from './PositionItem/PositionItem' -import { Pair } from '@invariant-labs/sdk-eclipse' import { Keypair } from '@solana/web3.js' import { BN } from '@coral-xyz/anchor' From d2f851b5ba39aebc0d8f94ae06e5f935879d29f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Wo=C5=82odko?= Date: Sun, 5 Jan 2025 08:53:06 +0100 Subject: [PATCH 04/42] Recalculate estimation on hover --- src/components/PositionsList/PositionItem/PositionItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/PositionsList/PositionItem/PositionItem.tsx b/src/components/PositionsList/PositionItem/PositionItem.tsx index 3d1f7799..dfd8aa84 100644 --- a/src/components/PositionsList/PositionItem/PositionItem.tsx +++ b/src/components/PositionsList/PositionItem/PositionItem.tsx @@ -169,7 +169,7 @@ export const PositionItem: React.FC = ({ false ) as BN return estOfPoints - }, [liquidity, poolData, pointsPerSecond]) + }, [liquidity, poolData, pointsPerSecond, isPromotedPoolPopoverOpen]) return ( Date: Sun, 5 Jan 2025 10:16:32 +0100 Subject: [PATCH 05/42] Fix est point calculation --- .../PositionItem/PositionItem.stories.tsx | 16 ++++- .../PositionItem/PositionItem.tsx | 20 ++++--- .../PositionsList/PositionsList.stories.tsx | 60 +++++++++++++++++++ .../WrappedPositionsList.tsx | 2 + 4 files changed, 90 insertions(+), 8 deletions(-) diff --git a/src/components/PositionsList/PositionItem/PositionItem.stories.tsx b/src/components/PositionsList/PositionItem/PositionItem.stories.tsx index f88ed7e6..2f756492 100644 --- a/src/components/PositionsList/PositionItem/PositionItem.stories.tsx +++ b/src/components/PositionsList/PositionItem/PositionItem.stories.tsx @@ -25,7 +25,21 @@ export const Primary: Story = { max: 149.6, fee: 0.05, valueX: 10000.45, - + position: { + bump: 0, + feeGrowthInsideX: new BN(0), + feeGrowthInsideY: new BN(0), + id: 0, + lastSlot: new BN(0), + lowerTickIndex: new BN(0), + owner: Keypair.generate().publicKey, + pool: Keypair.generate().publicKey, + secondsPerLiquidityInside: new BN(0), + tokensOwedX: new BN(0), + tokensOwedY: new BN(0), + upperTickIndex: new BN(0), + liquidity: new BN(0) + }, valueY: 2137.4, liquidity: new BN(0), poolAddress: Keypair.generate().publicKey, diff --git a/src/components/PositionsList/PositionItem/PositionItem.tsx b/src/components/PositionsList/PositionItem/PositionItem.tsx index dfd8aa84..7ea7ac34 100644 --- a/src/components/PositionsList/PositionItem/PositionItem.tsx +++ b/src/components/PositionsList/PositionItem/PositionItem.tsx @@ -16,9 +16,10 @@ import { useSelector } from 'react-redux' import { leaderboardSelectors } from '@store/selectors/leaderboard' import PromotedPoolPopover from '@components/Modals/PromotedPoolPopover/PromotedPoolPopover' import { BN } from '@coral-xyz/anchor' -import { estimatePointsForLiquidity } from '@invariant-labs/points-sdk' -import { PoolStructure } from '@invariant-labs/sdk-eclipse/lib/market' +import { estimatePointsForUserPositions } from '@invariant-labs/points-sdk' +import { PoolStructure, Position } from '@invariant-labs/sdk-eclipse/lib/market' import { PoolWithAddressAndIndex } from '@store/selectors/positions' +import { LEADERBOARD_DECIMAL } from '@pages/LeaderboardPage/config' export interface IPositionItem { tokenXName: string @@ -27,6 +28,7 @@ export interface IPositionItem { tokenYIcon: string tokenXLiq: number poolAddress: PublicKey + position: Position tokenYLiq: number fee: number min: number @@ -55,6 +57,7 @@ export const PositionItem: React.FC = ({ max, valueX, valueY, + position, liquidity, poolData, isActive = false, @@ -162,12 +165,15 @@ export const PositionItem: React.FC = ({ [valueX, valueY, tokenXName, classes, isXs, isDesktop, tokenYName, xToY] ) const estimation = useMemo(() => { - const estOfPoints = estimatePointsForLiquidity( - new BN(liquidity, 'hex'), + const estOfPoints = estimatePointsForUserPositions( + [position] as Position[], poolData as PoolStructure, - new BN(pointsPerSecond, 'hex'), - false - ) as BN + new BN( + promotedPools.find(pool => pool.address === [position][0].pool.toString())!.pointsPerSecond, + 'hex' + ).mul(new BN(10).pow(new BN(LEADERBOARD_DECIMAL))) + ) + return estOfPoints }, [liquidity, poolData, pointsPerSecond, isPromotedPoolPopoverOpen]) diff --git a/src/components/PositionsList/PositionsList.stories.tsx b/src/components/PositionsList/PositionsList.stories.tsx index 70d9ef5c..ff1cbcfa 100644 --- a/src/components/PositionsList/PositionsList.stories.tsx +++ b/src/components/PositionsList/PositionsList.stories.tsx @@ -29,6 +29,21 @@ const data: IPositionItem[] = [ network: NetworkType.Testnet, tokenXName: 'BTC', tokenYName: 'SNY', + position: { + bump: 0, + feeGrowthInsideX: new BN(0), + feeGrowthInsideY: new BN(0), + id: 0, + lastSlot: new BN(0), + lowerTickIndex: new BN(0), + owner: Keypair.generate().publicKey, + pool: Keypair.generate().publicKey, + secondsPerLiquidityInside: new BN(0), + tokensOwedX: new BN(0), + tokensOwedY: new BN(0), + upperTickIndex: new BN(0), + liquidity: new BN(0) + }, tokenXIcon: 'https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v/logo.png', tokenYIcon: @@ -79,6 +94,21 @@ const data: IPositionItem[] = [ network: NetworkType.Testnet, tokenXName: 'BTC', tokenYName: 'SNY', + position: { + bump: 0, + feeGrowthInsideX: new BN(0), + feeGrowthInsideY: new BN(0), + id: 0, + lastSlot: new BN(0), + lowerTickIndex: new BN(0), + owner: Keypair.generate().publicKey, + pool: Keypair.generate().publicKey, + secondsPerLiquidityInside: new BN(0), + tokensOwedX: new BN(0), + tokensOwedY: new BN(0), + upperTickIndex: new BN(0), + liquidity: new BN(0) + }, tokenXIcon: 'https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v/logo.png', tokenYIcon: @@ -129,6 +159,21 @@ const data: IPositionItem[] = [ network: NetworkType.Testnet, tokenXName: 'BTC', tokenYName: 'SNY', + position: { + bump: 0, + feeGrowthInsideX: new BN(0), + feeGrowthInsideY: new BN(0), + id: 0, + lastSlot: new BN(0), + lowerTickIndex: new BN(0), + owner: Keypair.generate().publicKey, + pool: Keypair.generate().publicKey, + secondsPerLiquidityInside: new BN(0), + tokensOwedX: new BN(0), + tokensOwedY: new BN(0), + upperTickIndex: new BN(0), + liquidity: new BN(0) + }, tokenXIcon: 'https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v/logo.png', tokenYIcon: @@ -179,6 +224,21 @@ const data: IPositionItem[] = [ network: NetworkType.Testnet, tokenXName: 'BTC', tokenYName: 'SNY', + position: { + bump: 0, + feeGrowthInsideX: new BN(0), + feeGrowthInsideY: new BN(0), + id: 0, + lastSlot: new BN(0), + lowerTickIndex: new BN(0), + owner: Keypair.generate().publicKey, + pool: Keypair.generate().publicKey, + secondsPerLiquidityInside: new BN(0), + tokensOwedX: new BN(0), + tokensOwedY: new BN(0), + upperTickIndex: new BN(0), + liquidity: new BN(0) + }, tokenXIcon: 'https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v/logo.png', tokenYIcon: diff --git a/src/containers/WrappedPositionsList/WrappedPositionsList.tsx b/src/containers/WrappedPositionsList/WrappedPositionsList.tsx index a7556ead..cf93f903 100644 --- a/src/containers/WrappedPositionsList/WrappedPositionsList.tsx +++ b/src/containers/WrappedPositionsList/WrappedPositionsList.tsx @@ -121,6 +121,7 @@ export const WrappedPositionsList: React.FC = () => { fee: +printBN(position.poolData.fee, DECIMAL - 2), min, max, + position, valueX, valueY, address: walletAddress.toString(), @@ -208,6 +209,7 @@ export const WrappedPositionsList: React.FC = () => { min, max, valueX, + position, valueY, poolAddress: position.poolData.address, liquidity: position.liquidity, From 290630837dde0f6be7839f7f7e0b6993a432f242 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Wo=C5=82odko?= Date: Sun, 5 Jan 2025 10:24:44 +0100 Subject: [PATCH 06/42] Remove useless print APR --- .../Modals/PromotedPoolPopover/PromotedPoolPopover.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx b/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx index ebf80bc8..70b96201 100644 --- a/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx +++ b/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx @@ -79,7 +79,6 @@ export const PromotedPoolPopover = ({ {`${apr > 1000 ? '>1000%' : apr === 0 ? '-' : apr.toFixed(2) + '%'}`} - {printBN(apr, LEADERBOARD_DECIMAL)} From 1bf893341277effe4f68410c1978efe332df7ccb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Wo=C5=82odko?= Date: Sun, 5 Jan 2025 10:26:37 +0100 Subject: [PATCH 07/42] Fix unused --- .../Modals/PromotedPoolPopover/PromotedPoolPopover.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx b/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx index 70b96201..2a23c90d 100644 --- a/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx +++ b/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx @@ -2,7 +2,6 @@ import { BN } from '@coral-xyz/anchor' import useStyles from './style' import { Popover, Typography } from '@mui/material' import { formatNumberWithCommas, printBN } from '@utils/utils' -import { LEADERBOARD_DECIMAL } from '@pages/LeaderboardPage/config' export interface IPromotedPoolPopover { open: boolean From 0ad11331a4284798d3762ad7591871226eaa3a03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Wo=C5=82odko?= Date: Sun, 5 Jan 2025 10:28:53 +0100 Subject: [PATCH 08/42] Added comas formating --- .../Modals/PromotedPoolPopover/PromotedPoolPopover.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx b/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx index 2a23c90d..3a0b8eff 100644 --- a/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx +++ b/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx @@ -62,7 +62,9 @@ export const PromotedPoolPopover = ({ {estPoints ? (
Your Points - {printBN(estPoints, 0)} + + {formatNumberWithCommas(printBN(estPoints, 0))} +
) : null} From c96b9c6046e600a69272fae8bd64f6827a40bc67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Wo=C5=82odko?= Date: Sun, 5 Jan 2025 12:47:56 +0100 Subject: [PATCH 09/42] Bug fixes and add inactive indicator --- .../PositionItem/PositionItem.tsx | 54 +++++++++++++++---- 1 file changed, 43 insertions(+), 11 deletions(-) diff --git a/src/components/PositionsList/PositionItem/PositionItem.tsx b/src/components/PositionsList/PositionItem/PositionItem.tsx index 7ea7ac34..467313be 100644 --- a/src/components/PositionsList/PositionItem/PositionItem.tsx +++ b/src/components/PositionsList/PositionItem/PositionItem.tsx @@ -165,18 +165,35 @@ export const PositionItem: React.FC = ({ [valueX, valueY, tokenXName, classes, isXs, isDesktop, tokenYName, xToY] ) const estimation = useMemo(() => { - const estOfPoints = estimatePointsForUserPositions( - [position] as Position[], - poolData as PoolStructure, - new BN( - promotedPools.find(pool => pool.address === [position][0].pool.toString())!.pointsPerSecond, - 'hex' - ).mul(new BN(10).pow(new BN(LEADERBOARD_DECIMAL))) - ) + if (!position || !promotedPools || !poolData) { + return new BN(0) + } - return estOfPoints - }, [liquidity, poolData, pointsPerSecond, isPromotedPoolPopoverOpen]) + try { + const poolAddress = position.pool?.toString() + if (!poolAddress) { + return new BN(0) + } + const promotedPool = promotedPools.find(pool => pool.address === poolAddress) + if (!promotedPool?.pointsPerSecond) { + return new BN(0) + } + + const pointsPerSecond = new BN(promotedPool.pointsPerSecond, 'hex').mul( + new BN(10).pow(new BN(LEADERBOARD_DECIMAL)) + ) + + return estimatePointsForUserPositions( + [position] as Position[], + poolData as PoolStructure, + pointsPerSecond + ) + } catch (error) { + console.error('Error calculating estimation:', error) + return new BN(0) + } + }, [position, promotedPools, poolData, liquidity, isPromotedPoolPopoverOpen]) return ( = ({ - {isPromoted && ( + {isPromoted ? ( <>
= ({ points={new BN(pointsPerSecond, 'hex').muln(24).muln(60).muln(60)} /> + ) : ( + <> + + {'Airdrop'} + + )} {feeFragment} From 2f39662e2157da8729cb793c894319f68a52fb31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Wo=C5=82odko?= Date: Sun, 5 Jan 2025 13:26:43 +0100 Subject: [PATCH 10/42] Fix --- .../Modals/PromotedPoolPopover/PromotedPoolPopover.tsx | 10 ++++++++-- .../PositionsList/PositionItem/PositionItem.tsx | 6 +++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx b/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx index 3a0b8eff..7af26ea6 100644 --- a/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx +++ b/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx @@ -1,7 +1,8 @@ import { BN } from '@coral-xyz/anchor' import useStyles from './style' import { Popover, Typography } from '@mui/material' -import { formatNumberWithCommas, printBN } from '@utils/utils' +import { formatNumberWithCommas, printBN, removeAdditionalDecimals } from '@utils/utils' +import { LEADERBOARD_DECIMAL } from '@pages/LeaderboardPage/config' export interface IPromotedPoolPopover { open: boolean @@ -63,7 +64,12 @@ export const PromotedPoolPopover = ({
Your Points - {formatNumberWithCommas(printBN(estPoints, 0))} + {estPoints.isZero() + ? removeAdditionalDecimals( + formatNumberWithCommas(printBN(estPoints, LEADERBOARD_DECIMAL)), + 2 + ) + : 0}
) : null} diff --git a/src/components/PositionsList/PositionItem/PositionItem.tsx b/src/components/PositionsList/PositionItem/PositionItem.tsx index 467313be..b7667dd2 100644 --- a/src/components/PositionsList/PositionItem/PositionItem.tsx +++ b/src/components/PositionsList/PositionItem/PositionItem.tsx @@ -235,7 +235,7 @@ export const PositionItem: React.FC = ({ - {isPromoted ? ( + {isPromoted && isActive ? ( <>
= ({ ) : ( <> - + {'Airdrop'} = ({ filter: 'grayscale(1)' }} /> - + )} {feeFragment} From d8faf56d8d3a2e0bab03d5803e883280f037d6c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Wo=C5=82odko?= Date: Sun, 5 Jan 2025 15:23:10 +0100 Subject: [PATCH 11/42] Test --- .../PromotedPoolPopover.tsx | 10 +- .../PositionItem/PositionItem.tsx | 101 +++++++++++++----- 2 files changed, 77 insertions(+), 34 deletions(-) diff --git a/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx b/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx index 7af26ea6..8bc31103 100644 --- a/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx +++ b/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx @@ -64,12 +64,10 @@ export const PromotedPoolPopover = ({
Your Points - {estPoints.isZero() - ? removeAdditionalDecimals( - formatNumberWithCommas(printBN(estPoints, LEADERBOARD_DECIMAL)), - 2 - ) - : 0} + {removeAdditionalDecimals( + formatNumberWithCommas(printBN(estPoints, LEADERBOARD_DECIMAL)), + 2 + )}
) : null} diff --git a/src/components/PositionsList/PositionItem/PositionItem.tsx b/src/components/PositionsList/PositionItem/PositionItem.tsx index b7667dd2..e3fe2e7d 100644 --- a/src/components/PositionsList/PositionItem/PositionItem.tsx +++ b/src/components/PositionsList/PositionItem/PositionItem.tsx @@ -18,7 +18,7 @@ import PromotedPoolPopover from '@components/Modals/PromotedPoolPopover/Promoted import { BN } from '@coral-xyz/anchor' import { estimatePointsForUserPositions } from '@invariant-labs/points-sdk' import { PoolStructure, Position } from '@invariant-labs/sdk-eclipse/lib/market' -import { PoolWithAddressAndIndex } from '@store/selectors/positions' +import { PoolWithAddressAndIndex, positionsWithPoolsData } from '@store/selectors/positions' import { LEADERBOARD_DECIMAL } from '@pages/LeaderboardPage/config' export interface IPositionItem { @@ -164,36 +164,68 @@ export const PositionItem: React.FC = ({ ), [valueX, valueY, tokenXName, classes, isXs, isDesktop, tokenYName, xToY] ) - const estimation = useMemo(() => { - if (!position || !promotedPools || !poolData) { - return new BN(0) - } + // const estimation = useMemo(() => { + // if (!position || !promotedPools || !poolData) { + // return new BN(0) + // } - try { - const poolAddress = position.pool?.toString() - if (!poolAddress) { - return new BN(0) - } + // try { + // const poolAddress = position.pool?.toString() + // if (!poolAddress) { + // return new BN(0) + // } + + // const promotedPool = promotedPools.find(pool => pool.address === poolAddress) + // if (!promotedPool?.pointsPerSecond) { + // return new BN(0) + // } + + // const pointsPerSecond = new BN(promotedPool.pointsPerSecond, 'hex').mul( + // new BN(10).pow(new BN(LEADERBOARD_DECIMAL)) + // ) - const promotedPool = promotedPools.find(pool => pool.address === poolAddress) - if (!promotedPool?.pointsPerSecond) { - return new BN(0) + // return estimatePointsForUserPositions( + // [position] as Position[], + // poolData as PoolStructure, + // pointsPerSecond + // ) + // } catch (error) { + // console.error('Error calculating estimation:', error) + // return new BN(0) + // } + // }, [position, promotedPools, poolData, liquidity, isPromotedPoolPopoverOpen]) + const list = useSelector(positionsWithPoolsData) + + const estimated24hPoints = useMemo(() => { + const eligiblePositions = {} + list.forEach(position => { + if (promotedPools.some(pool => pool.address === position.pool.toString())) { + const v = eligiblePositions[position.pool.toString()] + if (!v) { + eligiblePositions[position.pool.toString()] = [position] + } else { + eligiblePositions[position.pool.toString()].push(position) + } } + }) - const pointsPerSecond = new BN(promotedPool.pointsPerSecond, 'hex').mul( - new BN(10).pow(new BN(LEADERBOARD_DECIMAL)) + const estimation: BN = Object.values(eligiblePositions).reduce((acc: BN, positions: any) => { + const estimation = estimatePointsForUserPositions( + positions as Position[], + positions[0].poolData as PoolStructure, + new BN( + promotedPools.find( + pool => pool.address === positions[0].pool.toString() + )!.pointsPerSecond, + 'hex' + ).mul(new BN(10).pow(new BN(LEADERBOARD_DECIMAL))) ) - return estimatePointsForUserPositions( - [position] as Position[], - poolData as PoolStructure, - pointsPerSecond - ) - } catch (error) { - console.error('Error calculating estimation:', error) - return new BN(0) - } - }, [position, promotedPools, poolData, liquidity, isPromotedPoolPopoverOpen]) + return acc.add(estimation) + }, new BN(0)) + + return estimation + }, [list]) return ( = ({ onClose={() => { setIsPromotedPoolPopoverOpen(false) }} - estPoints={estimation} + estPoints={estimated24hPoints} points={new BN(pointsPerSecond, 'hex').muln(24).muln(60).muln(60)} /> ) : ( <> - + e.stopPropagation()} + title={ + <> + This pool generates points, but your position has inactive liquidity.{' '} + Create a position within range to start earning points + + } + placement='top' + classes={{ + tooltip: classes.tooltip + }}> {'Airdrop'} = ({ filter: 'grayscale(1)' }} /> - + )} {feeFragment} From 2edb100aaf4911aec2306f79dcae0412fda5e500 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Wo=C5=82odko?= Date: Sun, 5 Jan 2025 15:24:42 +0100 Subject: [PATCH 12/42] Test --- src/components/PositionsList/PositionItem/PositionItem.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/PositionsList/PositionItem/PositionItem.tsx b/src/components/PositionsList/PositionItem/PositionItem.tsx index e3fe2e7d..5c5b3e51 100644 --- a/src/components/PositionsList/PositionItem/PositionItem.tsx +++ b/src/components/PositionsList/PositionItem/PositionItem.tsx @@ -57,9 +57,9 @@ export const PositionItem: React.FC = ({ max, valueX, valueY, - position, - liquidity, - poolData, + // position, + // liquidity, + // poolData, isActive = false, currentPrice, tokenXLiq, From fd9572c087ff84dd96c658eeb11c846d64a94980 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Wo=C5=82odko?= Date: Sun, 5 Jan 2025 15:45:46 +0100 Subject: [PATCH 13/42] Fix --- .../PositionItem/PositionItem.tsx | 96 +++++++++---------- 1 file changed, 43 insertions(+), 53 deletions(-) diff --git a/src/components/PositionsList/PositionItem/PositionItem.tsx b/src/components/PositionsList/PositionItem/PositionItem.tsx index 5c5b3e51..8ff5365d 100644 --- a/src/components/PositionsList/PositionItem/PositionItem.tsx +++ b/src/components/PositionsList/PositionItem/PositionItem.tsx @@ -18,7 +18,7 @@ import PromotedPoolPopover from '@components/Modals/PromotedPoolPopover/Promoted import { BN } from '@coral-xyz/anchor' import { estimatePointsForUserPositions } from '@invariant-labs/points-sdk' import { PoolStructure, Position } from '@invariant-labs/sdk-eclipse/lib/market' -import { PoolWithAddressAndIndex, positionsWithPoolsData } from '@store/selectors/positions' +import { PoolWithAddressAndIndex } from '@store/selectors/positions' import { LEADERBOARD_DECIMAL } from '@pages/LeaderboardPage/config' export interface IPositionItem { @@ -57,9 +57,9 @@ export const PositionItem: React.FC = ({ max, valueX, valueY, - // position, + position, // liquidity, - // poolData, + poolData, isActive = false, currentPrice, tokenXLiq, @@ -164,68 +164,58 @@ export const PositionItem: React.FC = ({ ), [valueX, valueY, tokenXName, classes, isXs, isDesktop, tokenYName, xToY] ) - // const estimation = useMemo(() => { - // if (!position || !promotedPools || !poolData) { - // return new BN(0) - // } - // try { - // const poolAddress = position.pool?.toString() - // if (!poolAddress) { - // return new BN(0) - // } + // const list = useSelector(positionsWithPoolsData) - // const promotedPool = promotedPools.find(pool => pool.address === poolAddress) - // if (!promotedPool?.pointsPerSecond) { - // return new BN(0) + // const estimated24hPoints = useMemo(() => { + // const eligiblePositions = {} + // list.forEach(position => { + // if (promotedPools.some(pool => pool.address === position.pool.toString())) { + // const v = eligiblePositions[position.pool.toString()] + // if (!v) { + // eligiblePositions[position.pool.toString()] = [position] + // } else { + // eligiblePositions[position.pool.toString()].push(position) + // } // } + // }) - // const pointsPerSecond = new BN(promotedPool.pointsPerSecond, 'hex').mul( - // new BN(10).pow(new BN(LEADERBOARD_DECIMAL)) + // const estimation: BN = Object.values(eligiblePositions).reduce((acc: BN, positions: any) => { + // const estimation = estimatePointsForUserPositions( + // positions as Position[], + // positions[0].poolData as PoolStructure, + // new BN( + // promotedPools.find( + // pool => pool.address === positions[0].pool.toString() + // )!.pointsPerSecond, + // 'hex' + // ).mul(new BN(10).pow(new BN(LEADERBOARD_DECIMAL))) // ) - // return estimatePointsForUserPositions( - // [position] as Position[], - // poolData as PoolStructure, - // pointsPerSecond - // ) - // } catch (error) { - // console.error('Error calculating estimation:', error) - // return new BN(0) - // } - // }, [position, promotedPools, poolData, liquidity, isPromotedPoolPopoverOpen]) - const list = useSelector(positionsWithPoolsData) + // return acc.add(estimation) + // }, new BN(0)) + + // return estimation + // }, [list]) const estimated24hPoints = useMemo(() => { - const eligiblePositions = {} - list.forEach(position => { - if (promotedPools.some(pool => pool.address === position.pool.toString())) { - const v = eligiblePositions[position.pool.toString()] - if (!v) { - eligiblePositions[position.pool.toString()] = [position] - } else { - eligiblePositions[position.pool.toString()].push(position) - } - } - }) + if (!promotedPools.some(pool => pool.address === poolAddress.toString())) { + return new BN(0) + } - const estimation: BN = Object.values(eligiblePositions).reduce((acc: BN, positions: any) => { - const estimation = estimatePointsForUserPositions( - positions as Position[], - positions[0].poolData as PoolStructure, - new BN( - promotedPools.find( - pool => pool.address === positions[0].pool.toString() - )!.pointsPerSecond, - 'hex' - ).mul(new BN(10).pow(new BN(LEADERBOARD_DECIMAL))) - ) + const poolPointsPerSecond = promotedPools.find( + pool => pool.address === poolAddress.toString() + )!.pointsPerSecond - return acc.add(estimation) - }, new BN(0)) + const estimation = estimatePointsForUserPositions( + [position], + poolData as PoolStructure, + new BN(poolPointsPerSecond, 'hex').mul(new BN(10).pow(new BN(LEADERBOARD_DECIMAL))) + ) return estimation - }, [list]) + }, [poolAddress, position, poolData, promotedPools]) + return ( Date: Sun, 5 Jan 2025 16:48:12 +0100 Subject: [PATCH 14/42] Fix RWD --- .../PositionItem/PositionItem.tsx | 128 ++++++++++-------- 1 file changed, 72 insertions(+), 56 deletions(-) diff --git a/src/components/PositionsList/PositionItem/PositionItem.tsx b/src/components/PositionsList/PositionItem/PositionItem.tsx index 8ff5365d..38cabcf5 100644 --- a/src/components/PositionsList/PositionItem/PositionItem.tsx +++ b/src/components/PositionsList/PositionItem/PositionItem.tsx @@ -216,6 +216,64 @@ export const PositionItem: React.FC = ({ return estimation }, [poolAddress, position, poolData, promotedPools]) + const PromotedIcon = () => + isPromoted && isActive ? ( + <> +
{ + setIsPromotedPoolPopoverOpen(false) + }} + onPointerEnter={() => { + setIsPromotedPoolPopoverOpen(true) + }}> + {'Airdrop'} +
+ { + setIsPromotedPoolPopoverOpen(false) + }} + estPoints={estimated24hPoints} + points={new BN(pointsPerSecond, 'hex').muln(24).muln(60).muln(60)} + /> + + ) : ( + <> + e.stopPropagation()} + title={ + <> + This pool generates points, but your position has inactive liquidity.{' '} + Create a position within range to start earning points + + } + placement='top' + classes={{ + tooltip: classes.tooltip + }}> + {'Airdrop'} + + + ) + return ( = ({ {xToY ? tokenXName : tokenYName} - {xToY ? tokenYName : tokenXName} + + + + +
- {isPromoted && isActive ? ( - <> -
{ - setIsPromotedPoolPopoverOpen(false) - }} - onPointerEnter={() => { - setIsPromotedPoolPopoverOpen(true) - }}> - {'Airdrop'} -
- { - setIsPromotedPoolPopoverOpen(false) - }} - estPoints={estimated24hPoints} - points={new BN(pointsPerSecond, 'hex').muln(24).muln(60).muln(60)} - /> - - ) : ( - <> - e.stopPropagation()} - title={ - <> - This pool generates points, but your position has inactive liquidity.{' '} - Create a position within range to start earning points - - } - placement='top' - classes={{ - tooltip: classes.tooltip - }}> - {'Airdrop'} - - - )} + + + {feeFragment}
{feeFragment} From 3330b18d319a7226bf287dd63f151616c766f343 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Wo=C5=82odko?= Date: Sun, 5 Jan 2025 17:16:15 +0100 Subject: [PATCH 15/42] Fix popover position --- src/components/PositionsList/PositionItem/PositionItem.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/PositionsList/PositionItem/PositionItem.tsx b/src/components/PositionsList/PositionItem/PositionItem.tsx index 38cabcf5..2f9b9008 100644 --- a/src/components/PositionsList/PositionItem/PositionItem.tsx +++ b/src/components/PositionsList/PositionItem/PositionItem.tsx @@ -220,7 +220,6 @@ export const PositionItem: React.FC = ({ isPromoted && isActive ? ( <>
{ setIsPromotedPoolPopoverOpen(false) @@ -312,6 +311,7 @@ export const PositionItem: React.FC = ({ = ({ - + From 964d1e29a9ce9f03dd04df77d6dc8c5f6b84ad66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Wo=C5=82odko?= Date: Sun, 5 Jan 2025 18:37:57 +0100 Subject: [PATCH 16/42] Change tooltip --- .../PromotedPoolPopover.tsx | 73 ++++++++++++++----- .../PositionItem/PositionItem.tsx | 53 +++++--------- 2 files changed, 71 insertions(+), 55 deletions(-) diff --git a/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx b/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx index 8bc31103..5fd770ac 100644 --- a/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx +++ b/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx @@ -12,6 +12,10 @@ export interface IPromotedPoolPopover { apy?: number estPoints?: BN points: BN + headerText?: string | React.ReactNode + pointsLabel?: string | React.ReactNode + // New prop for section order + showEstPointsFirst?: boolean } export const PromotedPoolPopover = ({ @@ -21,12 +25,42 @@ export const PromotedPoolPopover = ({ apr, apy, estPoints, - points + points, + headerText = 'The pool distributes points:', + pointsLabel = 'Points per 24H', + showEstPointsFirst = false }: IPromotedPoolPopover) => { const { classes } = useStyles() if (!anchorEl) return null + const TotalPointsSection = ( +
+ + {typeof pointsLabel !== 'string' ? pointsLabel : null} + + + {formatNumberWithCommas(printBN(points, 0))} + +
+ ) + + const EstPointsSection = estPoints ? ( +
+ Points earned by this position per 24H: + + {removeAdditionalDecimals( + formatNumberWithCommas(printBN(estPoints, LEADERBOARD_DECIMAL)), + 2 + )} + +
+ ) : null + return (
- This pool distributes points: -
- Points per 24H - - {formatNumberWithCommas(printBN(points, 0))} - -
- {estPoints ? ( -
- Your Points - - {removeAdditionalDecimals( - formatNumberWithCommas(printBN(estPoints, LEADERBOARD_DECIMAL)), - 2 - )} - -
- ) : null} + + {typeof headerText !== 'string' ? headerText : null} + + + {showEstPointsFirst ? ( + <> + {EstPointsSection} + {TotalPointsSection} + + ) : ( + <> + {TotalPointsSection} + {EstPointsSection} + + )} {apr && apy ? ( <> diff --git a/src/components/PositionsList/PositionItem/PositionItem.tsx b/src/components/PositionsList/PositionItem/PositionItem.tsx index 2f9b9008..ed9b0edd 100644 --- a/src/components/PositionsList/PositionItem/PositionItem.tsx +++ b/src/components/PositionsList/PositionItem/PositionItem.tsx @@ -70,7 +70,6 @@ export const PositionItem: React.FC = ({ }) => { const { classes } = useStyles() const airdropIconRef = useRef(null) - const { promotedPools } = useSelector(leaderboardSelectors.config) const [isPromotedPoolPopoverOpen, setIsPromotedPoolPopoverOpen] = useState(false) @@ -165,39 +164,6 @@ export const PositionItem: React.FC = ({ [valueX, valueY, tokenXName, classes, isXs, isDesktop, tokenYName, xToY] ) - // const list = useSelector(positionsWithPoolsData) - - // const estimated24hPoints = useMemo(() => { - // const eligiblePositions = {} - // list.forEach(position => { - // if (promotedPools.some(pool => pool.address === position.pool.toString())) { - // const v = eligiblePositions[position.pool.toString()] - // if (!v) { - // eligiblePositions[position.pool.toString()] = [position] - // } else { - // eligiblePositions[position.pool.toString()].push(position) - // } - // } - // }) - - // const estimation: BN = Object.values(eligiblePositions).reduce((acc: BN, positions: any) => { - // const estimation = estimatePointsForUserPositions( - // positions as Position[], - // positions[0].poolData as PoolStructure, - // new BN( - // promotedPools.find( - // pool => pool.address === positions[0].pool.toString() - // )!.pointsPerSecond, - // 'hex' - // ).mul(new BN(10).pow(new BN(LEADERBOARD_DECIMAL))) - // ) - - // return acc.add(estimation) - // }, new BN(0)) - - // return estimation - // }, [list]) - const estimated24hPoints = useMemo(() => { if (!promotedPools.some(pool => pool.address === poolAddress.toString())) { return new BN(0) @@ -234,11 +200,18 @@ export const PositionItem: React.FC = ({ />
{ setIsPromotedPoolPopoverOpen(false) }} + headerText={ + <> + This position is currently earning points + + } + pointsLabel={'Total points distributed across the pool per 24H:'} estPoints={estimated24hPoints} points={new BN(pointsPerSecond, 'hex').muln(24).muln(60).muln(60)} /> @@ -251,8 +224,16 @@ export const PositionItem: React.FC = ({ onClick={e => e.stopPropagation()} title={ <> - This pool generates points, but your position has inactive liquidity.{' '} - Create a position within range to start earning points + {!isActive ? ( +

+ The position is in a pool that distributes points, but your position is{' '} + inactive. +

+ ) : !isPromoted ? ( +

+ The position is in a pool that does not distribute points. +

+ ) : null} } placement='top' From 0f33179d6ff57eb0afafd09d748bf41e5017b444 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Wo=C5=82odko?= Date: Sun, 5 Jan 2025 18:59:25 +0100 Subject: [PATCH 17/42] Update tooltip text --- .../PositionsList/PositionItem/PositionItem.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/components/PositionsList/PositionItem/PositionItem.tsx b/src/components/PositionsList/PositionItem/PositionItem.tsx index ed9b0edd..b5b7bfff 100644 --- a/src/components/PositionsList/PositionItem/PositionItem.tsx +++ b/src/components/PositionsList/PositionItem/PositionItem.tsx @@ -226,12 +226,18 @@ export const PositionItem: React.FC = ({ <> {!isActive ? (

- The position is in a pool that distributes points, but your position is{' '} - inactive. + This position isn't earning points, even though the pool is generating + them. Your position's liquidity remains inactive and won't earn + points as long as the current price is outside its specified price range. + To start earning points again, close the current position and open a new one with + a price range that includes the pool's current price

) : !isPromoted ? (

- The position is in a pool that does not distribute points. + This position isn't earning points because it was opened on a pool that + doesn't generate them. If you were expecting to earn points, make sure you + selected a pool with a fee tier that generates points, as not all pools do. Only + pools with the specified fee tier can generate points

) : null} From 23a995afa9549cf9908ea320ac565b459f948c91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Wo=C5=82odko?= Date: Sun, 5 Jan 2025 19:12:55 +0100 Subject: [PATCH 18/42] Update texts --- .../Modals/PromotedPoolPopover/style.ts | 2 +- .../PositionsList/PositionItem/PositionItem.tsx | 15 +++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/components/Modals/PromotedPoolPopover/style.ts b/src/components/Modals/PromotedPoolPopover/style.ts index e9860f34..bf8a2cbd 100644 --- a/src/components/Modals/PromotedPoolPopover/style.ts +++ b/src/components/Modals/PromotedPoolPopover/style.ts @@ -13,7 +13,7 @@ const useStyles = makeStyles()(() => { }, root: { background: colors.invariant.component, - width: 217, + width: 'fit-content', height: 'fit-content', borderRadius: 14, paddingTop: 16, diff --git a/src/components/PositionsList/PositionItem/PositionItem.tsx b/src/components/PositionsList/PositionItem/PositionItem.tsx index b5b7bfff..8bd74cf5 100644 --- a/src/components/PositionsList/PositionItem/PositionItem.tsx +++ b/src/components/PositionsList/PositionItem/PositionItem.tsx @@ -228,16 +228,19 @@ export const PositionItem: React.FC = ({

This position isn't earning points, even though the pool is generating them. Your position's liquidity remains inactive and won't earn - points as long as the current price is outside its specified price range. - To start earning points again, close the current position and open a new one with - a price range that includes the pool's current price + points as long as the current price is outside its specified price range.
+
To start earning points again, close the current position and open a new + one with a price range that includes the pool's current price

) : !isPromoted ? (

This position isn't earning points because it was opened on a pool that - doesn't generate them. If you were expecting to earn points, make sure you - selected a pool with a fee tier that generates points, as not all pools do. Only - pools with the specified fee tier can generate points + doesn't generate them. +
+
If you were expecting to earn points, make sure you selected a pool with a + fee tier that generates points, as not all pools do. Only pools with the{' '} + specified + fee tier can generate points

) : null} From 45a22b9092a02a27ecedb9232482344da493bbbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Wo=C5=82odko?= Date: Sun, 5 Jan 2025 19:16:46 +0100 Subject: [PATCH 19/42] Update text --- .../PositionsList/PositionItem/PositionItem.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/PositionsList/PositionItem/PositionItem.tsx b/src/components/PositionsList/PositionItem/PositionItem.tsx index 8bd74cf5..7da5c641 100644 --- a/src/components/PositionsList/PositionItem/PositionItem.tsx +++ b/src/components/PositionsList/PositionItem/PositionItem.tsx @@ -230,17 +230,17 @@ export const PositionItem: React.FC = ({ them. Your position's liquidity remains inactive and won't earn points as long as the current price is outside its specified price range.

To start earning points again, close the current position and open a new - one with a price range that includes the pool's current price + one with a price range that includes the pool's current price.

) : !isPromoted ? (

This position isn't earning points because it was opened on a pool that - doesn't generate them. + doesn't generate them.

If you were expecting to earn points, make sure you selected a pool with a fee tier that generates points, as not all pools do. Only pools with the{' '} - specified - fee tier can generate points + specified + fee tier can generate points.

) : null} From 76041594d5133045d1f2fec6635f109f786d3156 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Wo=C5=82odko?= Date: Mon, 6 Jan 2025 08:54:37 +0100 Subject: [PATCH 20/42] Fix bug with entering position --- .../Modals/PromotedPoolPopover/PromotedPoolPopover.tsx | 1 + src/components/PositionsList/PositionItem/PositionItem.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx b/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx index 5fd770ac..01b51417 100644 --- a/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx +++ b/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx @@ -63,6 +63,7 @@ export const PromotedPoolPopover = ({ return ( e.stopPropagation()} open={open} anchorEl={anchorEl} classes={{ diff --git a/src/components/PositionsList/PositionItem/PositionItem.tsx b/src/components/PositionsList/PositionItem/PositionItem.tsx index 7da5c641..50852c49 100644 --- a/src/components/PositionsList/PositionItem/PositionItem.tsx +++ b/src/components/PositionsList/PositionItem/PositionItem.tsx @@ -186,6 +186,7 @@ export const PositionItem: React.FC = ({ isPromoted && isActive ? ( <>
e.stopPropagation()} className={classes.actionButton} onPointerLeave={() => { setIsPromotedPoolPopoverOpen(false) From 525ad4dc539f2353be02e4f8fc3a7f4a033f015e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Wo=C5=82odko?= Date: Tue, 7 Jan 2025 22:01:47 +0100 Subject: [PATCH 21/42] Redesign mobile card --- .../PositionItem/components/PromotedIcons.tsx | 93 +++++ .../PositionItem/hooks/usePromotedPool.ts | 42 ++ .../PositionItem/utils/calculations.ts | 19 + .../PositionItemDesktop.tsx} | 201 ++++------ .../variants/PositionItemMobile.tsx | 372 ++++++++++++++++++ .../PositionItem/variants/style/desktop.ts | 293 ++++++++++++++ .../PositionItem/variants/style/mobile.tsx | 46 +++ .../{style.tsx => variants/style/shared.ts} | 93 +---- .../PositionsList/PositionsList.tsx | 14 +- src/components/PositionsList/types.d.ts | 30 ++ 10 files changed, 994 insertions(+), 209 deletions(-) create mode 100644 src/components/PositionsList/PositionItem/components/PromotedIcons.tsx create mode 100644 src/components/PositionsList/PositionItem/hooks/usePromotedPool.ts create mode 100644 src/components/PositionsList/PositionItem/utils/calculations.ts rename src/components/PositionsList/PositionItem/{PositionItem.tsx => variants/PositionItemDesktop.tsx} (65%) create mode 100644 src/components/PositionsList/PositionItem/variants/PositionItemMobile.tsx create mode 100644 src/components/PositionsList/PositionItem/variants/style/desktop.ts create mode 100644 src/components/PositionsList/PositionItem/variants/style/mobile.tsx rename src/components/PositionsList/PositionItem/{style.tsx => variants/style/shared.ts} (66%) create mode 100644 src/components/PositionsList/types.d.ts diff --git a/src/components/PositionsList/PositionItem/components/PromotedIcons.tsx b/src/components/PositionsList/PositionItem/components/PromotedIcons.tsx new file mode 100644 index 00000000..fbe3a673 --- /dev/null +++ b/src/components/PositionsList/PositionItem/components/PromotedIcons.tsx @@ -0,0 +1,93 @@ +import PromotedPoolPopover from '@components/Modals/PromotedPoolPopover/PromotedPoolPopover' +import { BN } from '@coral-xyz/anchor' +import { Tooltip } from '@mui/material' +import icons from '@static/icons' + +interface IPromotedIconProps { + isPromoted: boolean + isActive: boolean + pointsPerSecond: string + estimated24hPoints: BN + onOpenChange: (isOpen: boolean) => void + isOpen: boolean + iconRef: React.RefObject + isDesktop?: boolean +} + +export const PromotedIcon: React.FC = ({ + isPromoted, + isActive, + pointsPerSecond, + estimated24hPoints, + onOpenChange, + isOpen, + iconRef, + isDesktop +}) => { + if (!isPromoted || !isActive) { + return ( + e.stopPropagation()} + title={ + !isActive ? ( +

+ This position isn't earning points, even though the pool is generating them. + Your position's liquidity remains inactive and won't earn points as long + as the current price is outside its specified price range. +

+ ) : !isPromoted ? ( +

+ This position isn't earning points because it was opened on a pool that + doesn't generate them. +

+ ) : null + } + placement='top'> + {'Airdrop'} +
+ ) + } + + return ( + <> +
e.stopPropagation()} + onPointerLeave={() => onOpenChange(false)} + onPointerEnter={() => onOpenChange(true)}> + {'Airdrop'} +
+ onOpenChange(false)} + headerText={ + <> + This position is currently earning points + + } + pointsLabel={'Total points distributed across the pool per 24H:'} + estPoints={estimated24hPoints} + points={new BN(pointsPerSecond, 'hex').muln(24).muln(60).muln(60)} + /> + + ) +} diff --git a/src/components/PositionsList/PositionItem/hooks/usePromotedPool.ts b/src/components/PositionsList/PositionItem/hooks/usePromotedPool.ts new file mode 100644 index 00000000..91ce124a --- /dev/null +++ b/src/components/PositionsList/PositionItem/hooks/usePromotedPool.ts @@ -0,0 +1,42 @@ +import { BN } from '@coral-xyz/anchor' +import { estimatePointsForUserPositions } from '@invariant-labs/points-sdk' +import { Position, PoolStructure } from '@invariant-labs/sdk-eclipse/lib/market' +import { LEADERBOARD_DECIMAL } from '@pages/LeaderboardPage/config' +import { PublicKey } from '@solana/web3.js' +import { leaderboardSelectors } from '@store/selectors/leaderboard' +import { PoolWithAddressAndIndex } from '@store/selectors/positions' +import { useMemo } from 'react' +import { useSelector } from 'react-redux' + +export const usePromotedPool = ( + poolAddress: PublicKey, + position: Position, + poolData: PoolWithAddressAndIndex +) => { + const { promotedPools } = useSelector(leaderboardSelectors.config) + + const { isPromoted, pointsPerSecond } = useMemo(() => { + if (!poolAddress) return { isPromoted: false, pointsPerSecond: '00' } + const promotedPool = promotedPools.find(pool => pool.address === poolAddress.toString()) + if (!promotedPool) return { isPromoted: false, pointsPerSecond: '00' } + return { isPromoted: true, pointsPerSecond: promotedPool.pointsPerSecond } + }, [promotedPools, poolAddress]) + + const estimated24hPoints = useMemo(() => { + if (!promotedPools.some(pool => pool.address === poolAddress.toString())) { + return new BN(0) + } + + const poolPointsPerSecond = promotedPools.find( + pool => pool.address === poolAddress.toString() + )!.pointsPerSecond + + return estimatePointsForUserPositions( + [position], + poolData as PoolStructure, + new BN(poolPointsPerSecond, 'hex').mul(new BN(10).pow(new BN(LEADERBOARD_DECIMAL))) + ) + }, [poolAddress, position, poolData, promotedPools]) + + return { isPromoted, pointsPerSecond, estimated24hPoints } +} diff --git a/src/components/PositionsList/PositionItem/utils/calculations.ts b/src/components/PositionsList/PositionItem/utils/calculations.ts new file mode 100644 index 00000000..1561c8d8 --- /dev/null +++ b/src/components/PositionsList/PositionItem/utils/calculations.ts @@ -0,0 +1,19 @@ +export const calculatePercentageRatio = ( + tokenXLiq: number, + tokenYLiq: number, + currentPrice: number, + xToY: boolean +) => { + const firstTokenPercentage = + ((tokenXLiq * currentPrice) / (tokenYLiq + tokenXLiq * currentPrice)) * 100 + const tokenXPercentageFloat = xToY ? firstTokenPercentage : 100 - firstTokenPercentage + const tokenXPercentage = + tokenXPercentageFloat > 50 + ? Math.floor(tokenXPercentageFloat) + : Math.ceil(tokenXPercentageFloat) + + return { + tokenXPercentage, + tokenYPercentage: 100 - tokenXPercentage + } +} diff --git a/src/components/PositionsList/PositionItem/PositionItem.tsx b/src/components/PositionsList/PositionItem/variants/PositionItemDesktop.tsx similarity index 65% rename from src/components/PositionsList/PositionItem/PositionItem.tsx rename to src/components/PositionsList/PositionItem/variants/PositionItemDesktop.tsx index 50852c49..1da7cd96 100644 --- a/src/components/PositionsList/PositionItem/PositionItem.tsx +++ b/src/components/PositionsList/PositionItem/variants/PositionItemDesktop.tsx @@ -4,49 +4,20 @@ import { theme } from '@static/theme' import { formatNumber } from '@utils/utils' import classNames from 'classnames' import { useMemo, useRef, useState } from 'react' -import { useStyles } from './style' +import { useDesktopStyles } from './style/desktop' import { TooltipHover } from '@components/TooltipHover/TooltipHover' import { initialXtoY, tickerToAddress } from '@utils/utils' -import { NetworkType } from '@store/consts/static' import lockIcon from '@static/svg/lock.svg' import unlockIcon from '@static/svg/unlock.svg' import icons from '@static/icons' -import { PublicKey } from '@solana/web3.js' -import { useSelector } from 'react-redux' -import { leaderboardSelectors } from '@store/selectors/leaderboard' import PromotedPoolPopover from '@components/Modals/PromotedPoolPopover/PromotedPoolPopover' import { BN } from '@coral-xyz/anchor' -import { estimatePointsForUserPositions } from '@invariant-labs/points-sdk' -import { PoolStructure, Position } from '@invariant-labs/sdk-eclipse/lib/market' -import { PoolWithAddressAndIndex } from '@store/selectors/positions' -import { LEADERBOARD_DECIMAL } from '@pages/LeaderboardPage/config' +import { usePromotedPool } from '../hooks/usePromotedPool' +import { calculatePercentageRatio } from '../utils/calculations' +import { IPositionItem } from '@components/PositionsList/types' +import { useSharedStyles } from './style/shared' -export interface IPositionItem { - tokenXName: string - tokenYName: string - tokenXIcon: string - tokenYIcon: string - tokenXLiq: number - poolAddress: PublicKey - position: Position - tokenYLiq: number - fee: number - min: number - max: number - valueX: number - valueY: number - id: string - address: string - isActive?: boolean - currentPrice: number - network: NetworkType - isFullRange: boolean - isLocked: boolean - poolData: PoolWithAddressAndIndex - liquidity: BN -} - -export const PositionItem: React.FC = ({ +export const PositionItemDesktop: React.FC = ({ tokenXName, tokenYName, tokenXIcon, @@ -68,19 +39,11 @@ export const PositionItem: React.FC = ({ isFullRange, isLocked }) => { - const { classes } = useStyles() + const { classes } = useDesktopStyles() + const { classes: sharedClasses } = useSharedStyles() const airdropIconRef = useRef(null) - const { promotedPools } = useSelector(leaderboardSelectors.config) const [isPromotedPoolPopoverOpen, setIsPromotedPoolPopoverOpen] = useState(false) - const { isPromoted, pointsPerSecond } = useMemo(() => { - if (!poolAddress) return { isPromoted: false, pointsPerSecond: '00' } - const promotedPool = promotedPools.find(pool => pool.address === poolAddress.toString()) - - if (!promotedPool) return { isPromoted: false, pointsPerSecond: '00' } - return { isPromoted: true, pointsPerSecond: promotedPool.pointsPerSecond } - }, [promotedPools, poolAddress]) - const isXs = useMediaQuery(theme.breakpoints.down('xs')) const isDesktop = useMediaQuery(theme.breakpoints.up('lg')) @@ -88,22 +51,18 @@ export const PositionItem: React.FC = ({ initialXtoY(tickerToAddress(network, tokenXName), tickerToAddress(network, tokenYName)) ) - const getPercentageRatio = () => { - const firstTokenPercentage = - ((tokenXLiq * currentPrice) / (tokenYLiq + tokenXLiq * currentPrice)) * 100 - - const tokenXPercentageFloat = xToY ? firstTokenPercentage : 100 - firstTokenPercentage - const tokenXPercentage = - tokenXPercentageFloat > 50 - ? Math.floor(tokenXPercentageFloat) - : Math.ceil(tokenXPercentageFloat) - - const tokenYPercentage = 100 - tokenXPercentage - - return { tokenXPercentage, tokenYPercentage } - } + const { tokenXPercentage, tokenYPercentage } = calculatePercentageRatio( + tokenXLiq, + tokenYLiq, + currentPrice, + xToY + ) - const { tokenXPercentage, tokenYPercentage } = getPercentageRatio() + const { isPromoted, pointsPerSecond, estimated24hPoints } = usePromotedPool( + poolAddress, + position, + poolData + ) const feeFragment = useMemo( () => ( @@ -126,16 +85,20 @@ export const PositionItem: React.FC = ({ } placement='top' classes={{ - tooltip: classes.tooltip + tooltip: sharedClasses.tooltip }}> + className={classNames( + sharedClasses.infoText, + isActive ? sharedClasses.activeInfoText : undefined + )}> {fee}% fee @@ -149,13 +112,25 @@ export const PositionItem: React.FC = ({ - Value - - + + Value + + + {formatNumber(xToY ? valueX : valueY)} {xToY ? tokenXName : tokenYName} @@ -164,24 +139,6 @@ export const PositionItem: React.FC = ({ [valueX, valueY, tokenXName, classes, isXs, isDesktop, tokenYName, xToY] ) - const estimated24hPoints = useMemo(() => { - if (!promotedPools.some(pool => pool.address === poolAddress.toString())) { - return new BN(0) - } - - const poolPointsPerSecond = promotedPools.find( - pool => pool.address === poolAddress.toString() - )!.pointsPerSecond - - const estimation = estimatePointsForUserPositions( - [position], - poolData as PoolStructure, - new BN(poolPointsPerSecond, 'hex').mul(new BN(10).pow(new BN(LEADERBOARD_DECIMAL))) - ) - - return estimation - }, [poolAddress, position, poolData, promotedPools]) - const PromotedIcon = () => isPromoted && isActive ? ( <> @@ -248,7 +205,7 @@ export const PositionItem: React.FC = ({ } placement='top' classes={{ - tooltip: classes.tooltip + tooltip: sharedClasses.tooltip }}> = ({ justifyContent='space-between'> - + {xToY Arrow { @@ -291,45 +248,47 @@ export const PositionItem: React.FC = ({ /> {xToY - + {xToY ? tokenXName : tokenYName} - {xToY ? tokenYName : tokenXName} - - - - - - + - - - - {feeFragment} + {feeFragment} - + {tokenXPercentage === 100 && ( {tokenXPercentage} @@ -352,7 +311,6 @@ export const PositionItem: React.FC = ({ )} - {valueFragment} = ({ alignItems='center' wrap='nowrap'> <> - + MIN - MAX - + {isFullRange ? ( - FULL RANGE + FULL RANGE ) : ( - + {formatNumber(xToY ? min : 1 / max)} - {formatNumber(xToY ? max : 1 / min)}{' '} {xToY ? tokenYName : tokenXName} per {xToY ? tokenXName : tokenYName} @@ -384,7 +342,18 @@ export const PositionItem: React.FC = ({ diff --git a/src/components/PositionsList/PositionItem/variants/PositionItemMobile.tsx b/src/components/PositionsList/PositionItem/variants/PositionItemMobile.tsx new file mode 100644 index 00000000..fd7ffadb --- /dev/null +++ b/src/components/PositionsList/PositionItem/variants/PositionItemMobile.tsx @@ -0,0 +1,372 @@ +import { Box, Grid, Tooltip, Typography, useMediaQuery } from '@mui/material' +import SwapList from '@static/svg/swap-list.svg' +import { theme } from '@static/theme' +import { formatNumber } from '@utils/utils' +import classNames from 'classnames' +import { useMemo, useRef, useState } from 'react' +import { useMobileStyles } from './style/mobile' +import { TooltipHover } from '@components/TooltipHover/TooltipHover' +import { initialXtoY, tickerToAddress } from '@utils/utils' +import lockIcon from '@static/svg/lock.svg' +import unlockIcon from '@static/svg/unlock.svg' +import icons from '@static/icons' +import PromotedPoolPopover from '@components/Modals/PromotedPoolPopover/PromotedPoolPopover' +import { BN } from '@coral-xyz/anchor' +import { usePromotedPool } from '../hooks/usePromotedPool' +import { calculatePercentageRatio } from '../utils/calculations' +import { IPositionItem } from '@components/PositionsList/types' +import { useSharedStyles } from './style/shared' + +export const PositionItemMobile: React.FC = ({ + tokenXName, + tokenYName, + tokenXIcon, + poolAddress, + tokenYIcon, + fee, + min, + max, + valueX, + valueY, + position, + // liquidity, + poolData, + isActive = false, + currentPrice, + tokenXLiq, + tokenYLiq, + network, + isFullRange, + isLocked +}) => { + const { classes } = useMobileStyles() + const { classes: sharedClasses } = useSharedStyles() + const airdropIconRef = useRef(null) + const [isPromotedPoolPopoverOpen, setIsPromotedPoolPopoverOpen] = useState(false) + + const isXs = useMediaQuery(theme.breakpoints.down('xs')) + const isDesktop = useMediaQuery(theme.breakpoints.up('lg')) + + const [xToY, setXToY] = useState( + initialXtoY(tickerToAddress(network, tokenXName), tickerToAddress(network, tokenYName)) + ) + + const { tokenXPercentage, tokenYPercentage } = calculatePercentageRatio( + tokenXLiq, + tokenYLiq, + currentPrice, + xToY + ) + + const { isPromoted, pointsPerSecond, estimated24hPoints } = usePromotedPool( + poolAddress, + position, + poolData + ) + + const feeFragment = useMemo( + () => ( + e.stopPropagation()} + title={ + isActive ? ( + <> + The position is active and currently earning a fee as long as the + current price is within the position's price range. + + ) : ( + <> + The position is inactive and not earning a fee as long as the current + price is outside the position's price range. + + ) + } + placement='top' + classes={{ + tooltip: sharedClasses.tooltip + }}> + + + {fee}% fee + + + + ), + [fee, classes, isActive] + ) + + const valueFragment = useMemo( + () => ( + + + Value + + + + {formatNumber(xToY ? valueX : valueY)} {xToY ? tokenXName : tokenYName} + + + + ), + [valueX, valueY, tokenXName, classes, isXs, isDesktop, tokenYName, xToY] + ) + + const PromotedIcon = () => + isPromoted && isActive ? ( + <> +
e.stopPropagation()} + className={classes.actionButton} + onPointerLeave={() => { + setIsPromotedPoolPopoverOpen(false) + }} + onPointerEnter={() => { + setIsPromotedPoolPopoverOpen(true) + }}> + {'Airdrop'} +
+ { + setIsPromotedPoolPopoverOpen(false) + }} + headerText={ + <> + This position is currently earning points + + } + pointsLabel={'Total points distributed across the pool per 24H:'} + estPoints={estimated24hPoints} + points={new BN(pointsPerSecond, 'hex').muln(24).muln(60).muln(60)} + /> + + ) : ( + <> + e.stopPropagation()} + title={ + <> + {!isActive ? ( +

+ This position isn't earning points, even though the pool is generating + them. Your position's liquidity remains inactive and won't earn + points as long as the current price is outside its specified price range.
+
To start earning points again, close the current position and open a new + one with a price range that includes the pool's current price. +

+ ) : !isPromoted ? ( +

+ This position isn't earning points because it was opened on a pool that + doesn't generate them. +
+
If you were expecting to earn points, make sure you selected a pool with a + fee tier that generates points, as not all pools do. Only pools with the{' '} + specified + fee tier can generate points. +

+ ) : null} + + } + placement='top' + classes={{ + tooltip: sharedClasses.tooltip + }}> + {'Airdrop'} +
+ + ) + + return ( + + + + + + {xToY + + Arrow { + e.stopPropagation() + setXToY(!xToY) + }} + /> + + {xToY + + + + {xToY ? tokenXName : tokenYName} - {xToY ? tokenYName : tokenXName} + + + + + + + + + + + + + + {tokenXPercentage === 100 && ( + + {tokenXPercentage} + {'%'} {xToY ? tokenXName : tokenYName} + + )} + {tokenYPercentage === 100 && ( + + {tokenYPercentage} + {'%'} {xToY ? tokenYName : tokenXName} + + )} + + {tokenYPercentage !== 100 && tokenXPercentage !== 100 && ( + + {tokenXPercentage} + {'%'} {xToY ? tokenXName : tokenYName} {' - '} {tokenYPercentage} + {'%'} {xToY ? tokenYName : tokenXName} + + )} + + + + + + + {feeFragment} + + {/* {feeFragment} */} + + {valueFragment} + + + + <> + + MIN - MAX + + + {isFullRange ? ( + FULL RANGE + ) : ( + + {formatNumber(xToY ? min : 1 / max)} - {formatNumber(xToY ? max : 1 / min)}{' '} + {xToY ? tokenYName : tokenXName} per {xToY ? tokenXName : tokenYName} + + )} + + + + + {isLocked && ( + + {isLocked ? ( + + Lock + + ) : ( + + Lock + + )} + + )} + + + ) +} diff --git a/src/components/PositionsList/PositionItem/variants/style/desktop.ts b/src/components/PositionsList/PositionItem/variants/style/desktop.ts new file mode 100644 index 00000000..53df499b --- /dev/null +++ b/src/components/PositionsList/PositionItem/variants/style/desktop.ts @@ -0,0 +1,293 @@ +// import { Theme } from '@mui/material' +// import { colors, typography } from '@static/theme' +// import { makeStyles } from 'tss-react/mui' +// //desktop style +// export const useStyles = makeStyles()((theme: Theme) => ({ +// root: { +// background: colors.invariant.component, +// borderRadius: 24, +// padding: 20, +// flexWrap: 'nowrap', +// '&:not(:last-child)': { +// marginBottom: 20 +// }, + +// '&:hover': { +// background: `${colors.invariant.component}B0` +// }, + +// [theme.breakpoints.down('lg')]: { +// padding: 16, +// flexWrap: 'wrap' +// } +// }, +// icons: { +// marginRight: 12, +// width: 'fit-content', + +// [theme.breakpoints.down('lg')]: { +// marginRight: 12 +// } +// }, +// tokenIcon: { +// width: 40, +// borderRadius: '100%', + +// [theme.breakpoints.down('sm')]: { +// width: 28 +// } +// }, +// actionButton: { +// background: 'none', +// padding: 0, +// margin: 0, +// border: 'none', +// display: 'inline-flex', +// position: 'relative', +// color: colors.invariant.black, +// textTransform: 'none', + +// transition: 'filter 0.2s linear', + +// '&:hover': { +// filter: 'brightness(1.2)', +// cursor: 'pointer', +// '@media (hover: none)': { +// filter: 'none' +// } +// } +// }, +// arrows: { +// width: 36, +// marginLeft: 4, +// marginRight: 4, + +// [theme.breakpoints.down('lg')]: { +// width: 30 +// }, + +// [theme.breakpoints.down('sm')]: { +// width: 24 +// }, + +// '&:hover': { +// filter: 'brightness(2)' +// } +// }, +// names: { +// textOverflow: 'ellipsis', +// overflow: 'hidden', +// ...typography.heading2, +// color: colors.invariant.text, +// lineHeight: '40px', +// whiteSpace: 'nowrap', +// width: 180, +// [theme.breakpoints.down('xl')]: { +// ...typography.heading2 +// }, +// [theme.breakpoints.down('lg')]: { +// lineHeight: '32px', +// width: 'unset' +// }, +// [theme.breakpoints.down('sm')]: { +// ...typography.heading3, +// lineHeight: '25px' +// } +// }, +// infoText: { +// ...typography.body1, +// color: colors.invariant.lightGrey, +// whiteSpace: 'nowrap', +// textOverflow: 'ellipsis', +// overflow: 'hidden', +// [theme.breakpoints.down('sm')]: { +// ...typography.caption1, +// padding: '0 4px' +// } +// }, +// activeInfoText: { +// color: colors.invariant.black +// }, +// greenText: { +// ...typography.body1, +// color: colors.invariant.green, +// whiteSpace: 'nowrap', +// textOverflow: 'ellipsis', +// overflow: 'hidden', +// [theme.breakpoints.down('sm')]: { +// ...typography.caption1 +// } +// }, +// liquidity: { +// background: colors.invariant.light, +// borderRadius: 11, +// height: 36, +// width: 170, +// marginRight: 8, +// lineHeight: 20, +// paddingInline: 10, +// [theme.breakpoints.down('lg')]: { +// flex: '1 1 0%' +// } +// }, +// fee: { +// background: colors.invariant.light, +// borderRadius: 11, +// height: 36, +// width: 90, +// marginRight: 8, + +// [theme.breakpoints.down('md')]: { +// marginRight: 0 +// } +// }, +// activeFee: { +// background: colors.invariant.greenLinearGradient +// }, +// infoCenter: { +// flex: '1 1 0%' +// }, +// minMax: { +// background: colors.invariant.light, +// borderRadius: 11, +// height: 36, +// width: 320, +// paddingInline: 10, +// marginRight: 8, + +// [theme.breakpoints.down('md')]: { +// width: '100%', +// marginRight: 0, +// marginTop: 8 +// } +// }, +// dropdown: { +// background: colors.invariant.greenLinearGradient, +// borderRadius: 11, +// height: 36, +// width: 57, +// paddingInline: 10, +// marginRight: 8, + +// [theme.breakpoints.down(1029)]: { +// width: '100%', +// marginRight: 0, +// marginTop: 8 +// } +// }, +// dropdownLocked: { +// background: colors.invariant.lightHover +// }, +// dropdownText: { +// color: colors.invariant.black, +// width: '100%' +// }, +// value: { +// background: colors.invariant.light, +// borderRadius: 11, +// height: 36, +// width: 160, +// paddingInline: 12, +// marginRight: 8, + +// [theme.breakpoints.down(1029)]: { +// marginRight: 0 +// }, +// [theme.breakpoints.down('sm')]: { +// width: 144, +// paddingInline: 6 +// } +// }, +// mdInfo: { +// width: 'fit-content', +// flexWrap: 'nowrap', + +// [theme.breakpoints.down('lg')]: { +// flexWrap: 'nowrap', +// marginTop: 16, +// width: '100%' +// }, + +// [theme.breakpoints.down(1029)]: { +// flexWrap: 'wrap' +// } +// }, +// mdTop: { +// width: 'fit-content', + +// [theme.breakpoints.down('lg')]: { +// width: '100%', +// justifyContent: 'space-between' +// } +// }, +// iconsAndNames: { +// width: 'fit-content' +// }, +// label: { +// marginRight: 2 +// }, +// tooltip: { +// color: colors.invariant.textGrey, +// ...typography.caption4, +// lineHeight: '24px', +// background: colors.black.full, +// borderRadius: 12, +// fontSize: 14 +// } +// })) +import { Theme } from '@mui/material' +import { colors } from '@static/theme' +import { makeStyles } from 'tss-react/mui' + +export const useDesktopStyles = makeStyles()((theme: Theme) => ({ + root: { + padding: 20, + flexWrap: 'nowrap', + background: colors.invariant.component, + borderRadius: 24, + '&:not(:last-child)': { + marginBottom: 20 + }, + '&:hover': { + background: `${colors.invariant.component}B0` + } + }, + actionButton: { + display: 'inline-flex' + }, + minMax: { + background: colors.invariant.light, + borderRadius: 11, + height: 36, + width: 320, + paddingInline: 10, + marginRight: 8, + [theme.breakpoints.down('md')]: { + width: '100%', + marginRight: 0, + marginTop: 8 + } + }, + mdInfo: { + width: 'fit-content', + flexWrap: 'nowrap', + [theme.breakpoints.down('lg')]: { + flexWrap: 'nowrap', + marginTop: 16, + width: '100%' + }, + [theme.breakpoints.down(1029)]: { + flexWrap: 'wrap' + } + }, + mdTop: { + width: 'fit-content', + [theme.breakpoints.down('lg')]: { + width: '100%', + justifyContent: 'space-between' + } + }, + iconsAndNames: { + width: 'fit-content' + } +})) diff --git a/src/components/PositionsList/PositionItem/variants/style/mobile.tsx b/src/components/PositionsList/PositionItem/variants/style/mobile.tsx new file mode 100644 index 00000000..57ff8922 --- /dev/null +++ b/src/components/PositionsList/PositionItem/variants/style/mobile.tsx @@ -0,0 +1,46 @@ +import { Theme } from '@mui/material' +import { colors } from '@static/theme' +import { makeStyles } from 'tss-react/mui' + +export const useMobileStyles = makeStyles()((theme: Theme) => ({ + root: { + padding: 16, + flexWrap: 'wrap', + [theme.breakpoints.down('sm')]: { + padding: 8 + }, + background: colors.invariant.component, + borderRadius: 24, + '&:not(:last-child)': { + marginBottom: 20 + }, + '&:hover': { + background: `${colors.invariant.component}B0` + } + }, + actionButton: { + display: 'flex', + justifyContent: 'center', + alignItems: 'center' + }, + minMax: { + background: colors.invariant.light, + borderRadius: 11, + height: 36, + paddingInline: 10, + width: '100%', + marginRight: 0, + marginTop: '8px' + }, + mdInfo: { + flexWrap: 'wrap', + width: '100%' + }, + mdTop: { + justifyContent: 'space-between', + width: '100%' + }, + iconsAndNames: { + display: 'flex' + } +})) diff --git a/src/components/PositionsList/PositionItem/style.tsx b/src/components/PositionsList/PositionItem/variants/style/shared.ts similarity index 66% rename from src/components/PositionsList/PositionItem/style.tsx rename to src/components/PositionsList/PositionItem/variants/style/shared.ts index aa8119b5..33849f5e 100644 --- a/src/components/PositionsList/PositionItem/style.tsx +++ b/src/components/PositionsList/PositionItem/variants/style/shared.ts @@ -2,32 +2,10 @@ import { Theme } from '@mui/material' import { colors, typography } from '@static/theme' import { makeStyles } from 'tss-react/mui' -export const useStyles = makeStyles()((theme: Theme) => ({ - root: { - background: colors.invariant.component, - borderRadius: 24, - padding: 20, - flexWrap: 'nowrap', - '&:not(:last-child)': { - marginBottom: 20 - }, - - '&:hover': { - background: `${colors.invariant.component}B0` - }, - - [theme.breakpoints.down('lg')]: { - padding: 16, - flexWrap: 'wrap' - }, - [theme.breakpoints.down('sm')]: { - padding: 8 - } - }, +export const useSharedStyles = makeStyles()((theme: Theme) => ({ icons: { marginRight: 12, width: 'fit-content', - [theme.breakpoints.down('lg')]: { marginRight: 12 } @@ -35,7 +13,6 @@ export const useStyles = makeStyles()((theme: Theme) => ({ tokenIcon: { width: 40, borderRadius: '100%', - [theme.breakpoints.down('sm')]: { width: 28 } @@ -45,13 +22,10 @@ export const useStyles = makeStyles()((theme: Theme) => ({ padding: 0, margin: 0, border: 'none', - display: 'inline-flex', position: 'relative', color: colors.invariant.black, textTransform: 'none', - transition: 'filter 0.2s linear', - '&:hover': { filter: 'brightness(1.2)', cursor: 'pointer', @@ -64,15 +38,12 @@ export const useStyles = makeStyles()((theme: Theme) => ({ width: 36, marginLeft: 4, marginRight: 4, - [theme.breakpoints.down('lg')]: { width: 30 }, - [theme.breakpoints.down('sm')]: { width: 24 }, - '&:hover': { filter: 'brightness(2)' } @@ -125,7 +96,6 @@ export const useStyles = makeStyles()((theme: Theme) => ({ background: colors.invariant.light, borderRadius: 11, height: 36, - width: 170, marginRight: 8, lineHeight: 20, paddingInline: 10, @@ -137,9 +107,7 @@ export const useStyles = makeStyles()((theme: Theme) => ({ background: colors.invariant.light, borderRadius: 11, height: 36, - width: 90, marginRight: 8, - [theme.breakpoints.down('md')]: { marginRight: 0 } @@ -150,33 +118,12 @@ export const useStyles = makeStyles()((theme: Theme) => ({ infoCenter: { flex: '1 1 0%' }, - minMax: { - background: colors.invariant.light, - borderRadius: 11, - height: 36, - width: 320, - paddingInline: 10, - marginRight: 8, - - [theme.breakpoints.down('md')]: { - width: '100%', - marginRight: 0, - marginTop: 8 - } - }, dropdown: { background: colors.invariant.greenLinearGradient, borderRadius: 11, height: 36, - width: 57, paddingInline: 10, - marginRight: 8, - - [theme.breakpoints.down(1029)]: { - width: '100%', - marginRight: 0, - marginTop: 8 - } + marginRight: 8 }, dropdownLocked: { background: colors.invariant.lightHover @@ -189,42 +136,8 @@ export const useStyles = makeStyles()((theme: Theme) => ({ background: colors.invariant.light, borderRadius: 11, height: 36, - width: 160, paddingInline: 12, - marginRight: 8, - - [theme.breakpoints.down(1029)]: { - marginRight: 0 - }, - [theme.breakpoints.down('sm')]: { - width: 144, - paddingInline: 6 - } - }, - mdInfo: { - width: 'fit-content', - flexWrap: 'nowrap', - - [theme.breakpoints.down('lg')]: { - flexWrap: 'nowrap', - marginTop: 16, - width: '100%' - }, - - [theme.breakpoints.down(1029)]: { - flexWrap: 'wrap' - } - }, - mdTop: { - width: 'fit-content', - - [theme.breakpoints.down('lg')]: { - width: '100%', - justifyContent: 'space-between' - } - }, - iconsAndNames: { - width: 'fit-content' + marginRight: 8 }, label: { marginRight: 2 diff --git a/src/components/PositionsList/PositionsList.tsx b/src/components/PositionsList/PositionsList.tsx index 408ab90e..97406054 100644 --- a/src/components/PositionsList/PositionsList.tsx +++ b/src/components/PositionsList/PositionsList.tsx @@ -8,19 +8,22 @@ import { InputBase, ToggleButton, ToggleButtonGroup, - Typography + Typography, + useMediaQuery } from '@mui/material' import loader from '@static/gif/loader.gif' import SearchIcon from '@static/svg/lupaDark.svg' import refreshIcon from '@static/svg/refresh.svg' import { useEffect, useMemo, useState } from 'react' import { useNavigate } from 'react-router-dom' -import { IPositionItem, PositionItem } from './PositionItem/PositionItem' import { useStyles } from './style' import { TooltipHover } from '@components/TooltipHover/TooltipHover' import { PaginationList } from '@components/Pagination/Pagination' import { useDispatch } from 'react-redux' import { actions } from '@store/reducers/leaderboard' +import { PositionItemDesktop } from './PositionItem/variants/PositionItemDesktop' +import { PositionItemMobile } from './PositionItem/variants/PositionItemMobile' +import { IPositionItem } from './types' export enum LiquidityPools { Standard = 'Standard', @@ -74,6 +77,7 @@ export const PositionsList: React.FC = ({ const dispatch = useDispatch() const [page, setPage] = useState(initialPage) const [alignment, setAlignment] = useState(LiquidityPools.Standard) + const isLg = useMediaQuery('@media (max-width: 1360px)') const currentData = useMemo(() => { if (alignment === LiquidityPools.Standard) { @@ -237,7 +241,11 @@ export const PositionsList: React.FC = ({ }} key={element.id} className={classes.itemLink}> - + {isLg ? ( + + ) : ( + + )}
)) ) : showNoConnected ? ( diff --git a/src/components/PositionsList/types.d.ts b/src/components/PositionsList/types.d.ts new file mode 100644 index 00000000..6830c40c --- /dev/null +++ b/src/components/PositionsList/types.d.ts @@ -0,0 +1,30 @@ +import { BN } from '@coral-xyz/anchor' +import { Position } from '@invariant-labs/sdk-eclipse/lib/market' +import { PublicKey } from '@solana/web3.js' +import { NetworkType } from '@store/consts/static' +import { PoolWithAddressAndIndex } from '@store/selectors/positions' + +export interface IPositionItem { + tokenXName: string + tokenYName: string + tokenXIcon: string + tokenYIcon: string + tokenXLiq: number + poolAddress: PublicKey + position: Position + tokenYLiq: number + fee: number + min: number + max: number + valueX: number + valueY: number + id: string + address: string + isActive?: boolean + currentPrice: number + network: NetworkType + isFullRange: boolean + isLocked: boolean + poolData: PoolWithAddressAndIndex + liquidity: BN +} From 28b23ac36f9e43c31514f27c09760614d9229ed5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Wo=C5=82odko?= Date: Tue, 7 Jan 2025 22:05:05 +0100 Subject: [PATCH 22/42] Fix --- src/components/PositionsList/PositionsList.stories.tsx | 2 +- src/containers/WrappedPositionsList/WrappedPositionsList.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/PositionsList/PositionsList.stories.tsx b/src/components/PositionsList/PositionsList.stories.tsx index ff1cbcfa..8c45043a 100644 --- a/src/components/PositionsList/PositionsList.stories.tsx +++ b/src/components/PositionsList/PositionsList.stories.tsx @@ -2,9 +2,9 @@ import type { Meta, StoryObj } from '@storybook/react' import { BrowserRouter } from 'react-router-dom' import { PositionsList } from './PositionsList' import { NetworkType } from '@store/consts/static' -import { IPositionItem } from './PositionItem/PositionItem' import { Keypair } from '@solana/web3.js' import { BN } from '@coral-xyz/anchor' +import { IPositionItem } from './types' const meta = { title: 'Components/PositionsList', diff --git a/src/containers/WrappedPositionsList/WrappedPositionsList.tsx b/src/containers/WrappedPositionsList/WrappedPositionsList.tsx index cf93f903..8a2d8938 100644 --- a/src/containers/WrappedPositionsList/WrappedPositionsList.tsx +++ b/src/containers/WrappedPositionsList/WrappedPositionsList.tsx @@ -16,7 +16,7 @@ import { useEffect, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' import { useNavigate } from 'react-router-dom' import { calcYPerXPriceBySqrtPrice, printBN } from '@utils/utils' -import { IPositionItem } from '@components/PositionsList/PositionItem/PositionItem' +import { IPositionItem } from '@components/PositionsList/types' export const WrappedPositionsList: React.FC = () => { const walletAddress = useSelector(address) From 82315564ac5b3646169d10f3904630255e215aa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Wo=C5=82odko?= Date: Tue, 7 Jan 2025 22:05:47 +0100 Subject: [PATCH 23/42] Fix --- .../PositionsList/PositionItem/PositionItem.stories.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/PositionsList/PositionItem/PositionItem.stories.tsx b/src/components/PositionsList/PositionItem/PositionItem.stories.tsx index 2f756492..2d16749b 100644 --- a/src/components/PositionsList/PositionItem/PositionItem.stories.tsx +++ b/src/components/PositionsList/PositionItem/PositionItem.stories.tsx @@ -1,14 +1,14 @@ import { NetworkType } from '@store/consts/static' -import { PositionItem } from './PositionItem' import type { Meta, StoryObj } from '@storybook/react' import { Keypair } from '@solana/web3.js' import { BN } from '@coral-xyz/anchor' +import { PositionItemDesktop } from './variants/PositionItemDesktop' const meta = { title: 'Components/PositionItem', - component: PositionItem -} satisfies Meta + component: PositionItemDesktop +} satisfies Meta export default meta type Story = StoryObj From 9ae6c6a46f4f3f2d0acdd084a33dcefde8b663c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Wo=C5=82odko?= Date: Wed, 8 Jan 2025 10:09:31 +0100 Subject: [PATCH 24/42] Fixed tooltip --- .../components/PositionStatusTooltip.tsx | 33 +++++++++++++++++++ .../variants/PositionItemDesktop.tsx | 21 ++---------- .../variants/PositionItemMobile.tsx | 21 ++---------- 3 files changed, 37 insertions(+), 38 deletions(-) create mode 100644 src/components/PositionsList/PositionItem/components/PositionStatusTooltip.tsx diff --git a/src/components/PositionsList/PositionItem/components/PositionStatusTooltip.tsx b/src/components/PositionsList/PositionItem/components/PositionStatusTooltip.tsx new file mode 100644 index 00000000..4e34b344 --- /dev/null +++ b/src/components/PositionsList/PositionItem/components/PositionStatusTooltip.tsx @@ -0,0 +1,33 @@ +const PositionStatusTooltip: React.FC<{ isActive: boolean; isPromoted: boolean }> = ({ + isActive, + isPromoted +}) => { + if (!isActive) { + return ( +

+ This position isn't earning points, even though the pool is generating them. Your + position's liquidity remains inactive and won't earn points as long as the + current price is outside its specified price range.
+
To start earning points again, close the current position and open a new one with a + price range that includes the pool's current price. +

+ ) + } + + if (isActive && !isPromoted) { + return ( +

+ This position isn't earning points because it was opened on a pool that + doesn't generate them. +
+
If you were expecting to earn points, make sure you selected a pool with a fee tier + that generates points, as not all pools do. Only pools with the specified fee tier + can generate points. +

+ ) + } + + return null +} + +export default PositionStatusTooltip diff --git a/src/components/PositionsList/PositionItem/variants/PositionItemDesktop.tsx b/src/components/PositionsList/PositionItem/variants/PositionItemDesktop.tsx index 1da7cd96..b7464530 100644 --- a/src/components/PositionsList/PositionItem/variants/PositionItemDesktop.tsx +++ b/src/components/PositionsList/PositionItem/variants/PositionItemDesktop.tsx @@ -16,6 +16,7 @@ import { usePromotedPool } from '../hooks/usePromotedPool' import { calculatePercentageRatio } from '../utils/calculations' import { IPositionItem } from '@components/PositionsList/types' import { useSharedStyles } from './style/shared' +import PositionStatusTooltip from '../components/PositionStatusTooltip' export const PositionItemDesktop: React.FC = ({ tokenXName, @@ -182,25 +183,7 @@ export const PositionItemDesktop: React.FC = ({ onClick={e => e.stopPropagation()} title={ <> - {!isActive ? ( -

- This position isn't earning points, even though the pool is generating - them. Your position's liquidity remains inactive and won't earn - points as long as the current price is outside its specified price range.
-
To start earning points again, close the current position and open a new - one with a price range that includes the pool's current price. -

- ) : !isPromoted ? ( -

- This position isn't earning points because it was opened on a pool that - doesn't generate them. -
-
If you were expecting to earn points, make sure you selected a pool with a - fee tier that generates points, as not all pools do. Only pools with the{' '} - specified - fee tier can generate points. -

- ) : null} + } placement='top' diff --git a/src/components/PositionsList/PositionItem/variants/PositionItemMobile.tsx b/src/components/PositionsList/PositionItem/variants/PositionItemMobile.tsx index fd7ffadb..777aece3 100644 --- a/src/components/PositionsList/PositionItem/variants/PositionItemMobile.tsx +++ b/src/components/PositionsList/PositionItem/variants/PositionItemMobile.tsx @@ -16,6 +16,7 @@ import { usePromotedPool } from '../hooks/usePromotedPool' import { calculatePercentageRatio } from '../utils/calculations' import { IPositionItem } from '@components/PositionsList/types' import { useSharedStyles } from './style/shared' +import PositionStatusTooltip from '../components/PositionStatusTooltip' export const PositionItemMobile: React.FC = ({ tokenXName, @@ -167,25 +168,7 @@ export const PositionItemMobile: React.FC = ({ onClick={e => e.stopPropagation()} title={ <> - {!isActive ? ( -

- This position isn't earning points, even though the pool is generating - them. Your position's liquidity remains inactive and won't earn - points as long as the current price is outside its specified price range.
-
To start earning points again, close the current position and open a new - one with a price range that includes the pool's current price. -

- ) : !isPromoted ? ( -

- This position isn't earning points because it was opened on a pool that - doesn't generate them. -
-
If you were expecting to earn points, make sure you selected a pool with a - fee tier that generates points, as not all pools do. Only pools with the{' '} - specified - fee tier can generate points. -

- ) : null} + } placement='top' From eed7569d63e0c0bb0c113404eb5a7d2808cf1fa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Wo=C5=82odko?= Date: Wed, 8 Jan 2025 10:35:35 +0100 Subject: [PATCH 25/42] Fix --- .../PromotedPoolPopover.tsx | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx b/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx index 01b51417..c6c775fa 100644 --- a/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx +++ b/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx @@ -14,7 +14,6 @@ export interface IPromotedPoolPopover { points: BN headerText?: string | React.ReactNode pointsLabel?: string | React.ReactNode - // New prop for section order showEstPointsFirst?: boolean } @@ -61,9 +60,15 @@ export const PromotedPoolPopover = ({
) : null + const handlePopoverClick = (e: React.MouseEvent | React.TouchEvent) => { + e.preventDefault() + e.stopPropagation() + } + return ( e.stopPropagation()} + onClick={handlePopoverClick} + onTouchStart={handlePopoverClick} open={open} anchorEl={anchorEl} classes={{ @@ -78,15 +83,23 @@ export const PromotedPoolPopover = ({ disableRestoreFocus slotProps={{ paper: { - onMouseLeave: onClose + onMouseLeave: onClose, + onTouchEnd: e => { + if (!e.currentTarget.contains(e.target as Node)) { + onClose() + } + }, + style: { + touchAction: 'none' + } } }} transformOrigin={{ vertical: 'top', horizontal: 'center' }} - marginThreshold={16}> -
+ marginThreshold={32}> +
Date: Wed, 8 Jan 2025 10:47:09 +0100 Subject: [PATCH 26/42] Fix --- .../PromotedPoolPopover.tsx | 21 ++++--------------- .../variants/PositionItemMobile.tsx | 18 +++++++++------- 2 files changed, 15 insertions(+), 24 deletions(-) diff --git a/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx b/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx index c6c775fa..9ba5b609 100644 --- a/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx +++ b/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx @@ -14,6 +14,7 @@ export interface IPromotedPoolPopover { points: BN headerText?: string | React.ReactNode pointsLabel?: string | React.ReactNode + // New prop for section order showEstPointsFirst?: boolean } @@ -60,15 +61,9 @@ export const PromotedPoolPopover = ({
) : null - const handlePopoverClick = (e: React.MouseEvent | React.TouchEvent) => { - e.preventDefault() - e.stopPropagation() - } - return ( e.stopPropagation()} open={open} anchorEl={anchorEl} classes={{ @@ -83,15 +78,7 @@ export const PromotedPoolPopover = ({ disableRestoreFocus slotProps={{ paper: { - onMouseLeave: onClose, - onTouchEnd: e => { - if (!e.currentTarget.contains(e.target as Node)) { - onClose() - } - }, - style: { - touchAction: 'none' - } + onMouseLeave: onClose } }} transformOrigin={{ @@ -99,7 +86,7 @@ export const PromotedPoolPopover = ({ horizontal: 'center' }} marginThreshold={32}> -
+
= ({ [valueX, valueY, tokenXName, classes, isXs, isDesktop, tokenYName, xToY] ) + const handleInteraction = (event: React.MouseEvent | React.TouchEvent) => { + event.stopPropagation() + + if (event.type === 'touchstart') { + event.preventDefault() + setIsPromotedPoolPopoverOpen(!isPromotedPoolPopoverOpen) + } + } + const PromotedIcon = () => isPromoted && isActive ? ( <>
e.stopPropagation()} className={classes.actionButton} - onPointerLeave={() => { - setIsPromotedPoolPopoverOpen(false) - }} - onPointerEnter={() => { - setIsPromotedPoolPopoverOpen(true) - }}> + onClick={handleInteraction} + onTouchStart={handleInteraction}> {'Airdrop'}
Date: Wed, 8 Jan 2025 10:48:04 +0100 Subject: [PATCH 27/42] Add hover --- .../PositionItem/variants/PositionItemMobile.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/components/PositionsList/PositionItem/variants/PositionItemMobile.tsx b/src/components/PositionsList/PositionItem/variants/PositionItemMobile.tsx index 2301b605..7b257e61 100644 --- a/src/components/PositionsList/PositionItem/variants/PositionItemMobile.tsx +++ b/src/components/PositionsList/PositionItem/variants/PositionItemMobile.tsx @@ -154,6 +154,16 @@ export const PositionItemMobile: React.FC = ({ onClose={() => { setIsPromotedPoolPopoverOpen(false) }} + onPointerEnter={() => { + if (window.matchMedia('(hover: hover)').matches) { + setIsPromotedPoolPopoverOpen(true) + } + }} + onPointerLeave={() => { + if (window.matchMedia('(hover: hover)').matches) { + setIsPromotedPoolPopoverOpen(false) + } + }} headerText={ <> This position is currently earning points From fc29fcac2c47c6f26d2ce5eeea40db292e2fc4dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Wo=C5=82odko?= Date: Wed, 8 Jan 2025 10:51:19 +0100 Subject: [PATCH 28/42] Fix --- .../variants/PositionItemMobile.tsx | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/components/PositionsList/PositionItem/variants/PositionItemMobile.tsx b/src/components/PositionsList/PositionItem/variants/PositionItemMobile.tsx index 7b257e61..0130e0cf 100644 --- a/src/components/PositionsList/PositionItem/variants/PositionItemMobile.tsx +++ b/src/components/PositionsList/PositionItem/variants/PositionItemMobile.tsx @@ -144,16 +144,6 @@ export const PositionItemMobile: React.FC = ({
- {'Airdrop'} -
- { - setIsPromotedPoolPopoverOpen(false) - }} onPointerEnter={() => { if (window.matchMedia('(hover: hover)').matches) { setIsPromotedPoolPopoverOpen(true) @@ -164,6 +154,16 @@ export const PositionItemMobile: React.FC = ({ setIsPromotedPoolPopoverOpen(false) } }} + onTouchStart={handleInteraction}> + {'Airdrop'} +
+ { + setIsPromotedPoolPopoverOpen(false) + }} headerText={ <> This position is currently earning points From d071e9f0b8c78bc15018294bd020b5256abdf6fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Wo=C5=82odko?= Date: Wed, 8 Jan 2025 11:03:39 +0100 Subject: [PATCH 29/42] Click outside --- .../variants/PositionItemMobile.tsx | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/src/components/PositionsList/PositionItem/variants/PositionItemMobile.tsx b/src/components/PositionsList/PositionItem/variants/PositionItemMobile.tsx index 0130e0cf..6ef0861d 100644 --- a/src/components/PositionsList/PositionItem/variants/PositionItemMobile.tsx +++ b/src/components/PositionsList/PositionItem/variants/PositionItemMobile.tsx @@ -3,7 +3,7 @@ import SwapList from '@static/svg/swap-list.svg' import { theme } from '@static/theme' import { formatNumber } from '@utils/utils' import classNames from 'classnames' -import { useMemo, useRef, useState } from 'react' +import { useEffect, useMemo, useRef, useState } from 'react' import { useMobileStyles } from './style/mobile' import { TooltipHover } from '@components/TooltipHover/TooltipHover' import { initialXtoY, tickerToAddress } from '@utils/utils' @@ -138,8 +138,30 @@ export const PositionItemMobile: React.FC = ({ } } - const PromotedIcon = () => - isPromoted && isActive ? ( + const PromotedIcon = () => { + useEffect(() => { + const handleClickOutside = (event: TouchEvent | MouseEvent) => { + if ( + airdropIconRef.current && + !(airdropIconRef.current as HTMLElement).contains(event.target as Node) && + !document.querySelector('.promoted-pool-popover')?.contains(event.target as Node) + ) { + setIsPromotedPoolPopoverOpen(false) + } + } + + if (isPromotedPoolPopoverOpen) { + document.addEventListener('click', handleClickOutside) + document.addEventListener('touchstart', handleClickOutside) + } + + return () => { + document.removeEventListener('click', handleClickOutside) + document.removeEventListener('touchstart', handleClickOutside) + } + }, [isPromotedPoolPopoverOpen]) + + return isPromoted && isActive ? ( <>
= ({ ) + } return ( Date: Wed, 8 Jan 2025 11:04:27 +0100 Subject: [PATCH 30/42] add class --- .../Modals/PromotedPoolPopover/PromotedPoolPopover.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx b/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx index 9ba5b609..7ced9183 100644 --- a/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx +++ b/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx @@ -66,6 +66,7 @@ export const PromotedPoolPopover = ({ onClick={e => e.stopPropagation()} open={open} anchorEl={anchorEl} + className='promoted-pool-popover' classes={{ paper: classes.paper, root: classes.popover From 2b4ca7ae8e789ca2c5abf6f7e39fb5a17f5e13c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Wo=C5=82odko?= Date: Wed, 8 Jan 2025 15:18:21 +0100 Subject: [PATCH 31/42] Add tooltip inactive 2 reasons --- .../components/PositionStatusTooltip.tsx | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/components/PositionsList/PositionItem/components/PositionStatusTooltip.tsx b/src/components/PositionsList/PositionItem/components/PositionStatusTooltip.tsx index 4e34b344..b7368696 100644 --- a/src/components/PositionsList/PositionItem/components/PositionStatusTooltip.tsx +++ b/src/components/PositionsList/PositionItem/components/PositionStatusTooltip.tsx @@ -2,6 +2,23 @@ const PositionStatusTooltip: React.FC<{ isActive: boolean; isPromoted: boolean } isActive, isPromoted }) => { + if (!isActive && !isPromoted) { + return ( +

+ This position isn't earning points for two reasons: +
+
+ 1. Your position's liquidity remains inactive and won't earn points as long as + the current price is outside its specified price range. +
+
+ 2. This position was opened on a pool that doesn't generate points. If you were + expecting to earn points, make sure you selected a pool with a fee tier that generates + points. +

+ ) + } + if (!isActive) { return (

@@ -14,7 +31,7 @@ const PositionStatusTooltip: React.FC<{ isActive: boolean; isPromoted: boolean } ) } - if (isActive && !isPromoted) { + if (!isPromoted) { return (

This position isn't earning points because it was opened on a pool that From 7a90769c9b8c232b25e1ac8e5403fca50c45d1c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Wo=C5=82odko?= Date: Wed, 8 Jan 2025 15:21:08 +0100 Subject: [PATCH 32/42] Add dev info --- .../PositionsList/PositionItem/variants/PositionItemMobile.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/PositionsList/PositionItem/variants/PositionItemMobile.tsx b/src/components/PositionsList/PositionItem/variants/PositionItemMobile.tsx index 6ef0861d..2f8482b6 100644 --- a/src/components/PositionsList/PositionItem/variants/PositionItemMobile.tsx +++ b/src/components/PositionsList/PositionItem/variants/PositionItemMobile.tsx @@ -204,6 +204,8 @@ export const PositionItemMobile: React.FC = ({ onClick={e => e.stopPropagation()} title={ <> + isActive = {JSON.stringify(isActive)} + isPromoted = {JSON.stringify(isPromoted)} } From 67c9313df4ae5deee09b6d57795b66656f24d970 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Wo=C5=82odko?= Date: Wed, 8 Jan 2025 18:02:41 +0100 Subject: [PATCH 33/42] remove comment --- .../Modals/PromotedPoolPopover/PromotedPoolPopover.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx b/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx index 7ced9183..4d300a80 100644 --- a/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx +++ b/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx @@ -14,7 +14,6 @@ export interface IPromotedPoolPopover { points: BN headerText?: string | React.ReactNode pointsLabel?: string | React.ReactNode - // New prop for section order showEstPointsFirst?: boolean } From 21c7db91eb36b8a9e0b5541fe12e26016188b2fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Wo=C5=82odko?= Date: Wed, 8 Jan 2025 19:32:32 +0100 Subject: [PATCH 34/42] Fix bug with entering position on closing popover --- .../PromotedPoolPopover.tsx | 2 +- .../InactivePoolsPopover.tsx | 58 ++++++++++++ .../components/InactivePoolsPopover/style.ts | 63 +++++++++++++ .../variants/PositionItemMobile.tsx | 88 ++++++++++++++----- .../PositionsList/PositionsList.tsx | 15 ++-- 5 files changed, 200 insertions(+), 26 deletions(-) create mode 100644 src/components/PositionsList/PositionItem/components/InactivePoolsPopover/InactivePoolsPopover.tsx create mode 100644 src/components/PositionsList/PositionItem/components/InactivePoolsPopover/style.ts diff --git a/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx b/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx index 4d300a80..fe35136f 100644 --- a/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx +++ b/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx @@ -85,7 +85,7 @@ export const PromotedPoolPopover = ({ vertical: 'top', horizontal: 'center' }} - marginThreshold={32}> + marginThreshold={16}>

void + + isActive: boolean + isPromoted: boolean +} + +export const InactivePoolsPopover = ({ + open, + onClose, + anchorEl, + isActive, + isPromoted +}: IPromotedPoolPopover) => { + const { classes } = useStyles() + + if (!anchorEl) return null + + return ( + e.stopPropagation()} + open={open} + anchorEl={anchorEl} + className='promoted-pool-inactive-popover' + classes={{ + paper: classes.paper, + root: classes.popover + }} + onClose={onClose} + anchorOrigin={{ + vertical: 'bottom', + horizontal: 'center' + }} + disableRestoreFocus + slotProps={{ + paper: { + onMouseLeave: onClose + } + }} + transformOrigin={{ + vertical: 'top', + horizontal: 'center' + }} + marginThreshold={16}> +
+
+ +
+
+
+ ) +} diff --git a/src/components/PositionsList/PositionItem/components/InactivePoolsPopover/style.ts b/src/components/PositionsList/PositionItem/components/InactivePoolsPopover/style.ts new file mode 100644 index 00000000..f9272dd5 --- /dev/null +++ b/src/components/PositionsList/PositionItem/components/InactivePoolsPopover/style.ts @@ -0,0 +1,63 @@ +import { colors, theme, typography } from '@static/theme' +import { makeStyles } from 'tss-react/mui' + +const useStyles = makeStyles()(() => { + return { + popover: { + marginTop: '8px', + pointerEvents: 'none', + [theme.breakpoints.down('sm')]: { + width: '100%', + padding: '16px' + } + }, + root: { + width: '300px', + height: 'fit-content', + + color: colors.invariant.textGrey, + ...typography.caption4, + lineHeight: '24px', + background: colors.black.full, + borderRadius: 12, + padding: 10, + fontSize: 14 + }, + paper: { + background: 'transparent', + boxShadow: 'none', + borderRadius: '14px', + border: '1px solid transparent', + // backgroundImage: 'linear-gradient(#2A365C, #2A365C), linear-gradient(0deg, #2EE09A, #EF84F5)', + backgroundOrigin: 'border-box', + backgroundClip: 'padding-box, border-box' + }, + container: { + display: 'flex', + flexDirection: 'column', + width: '100%', + justifyContent: 'flex-start', + alignItems: 'flex-start', + gap: 16 + }, + insideBox: { + display: 'flex', + flexDirection: 'column', + width: '100%', + justifyContent: 'flex-start', + alignItems: 'flex-start' + }, + greyText: { + ...typography.body2, + color: colors.invariant.textGrey + }, + whiteText: { ...typography.heading4, color: colors.invariant.text }, + apr: { + ...typography.tiny2, + color: colors.invariant.textGrey, + marginLeft: 8 + } + } +}) + +export default useStyles diff --git a/src/components/PositionsList/PositionItem/variants/PositionItemMobile.tsx b/src/components/PositionsList/PositionItem/variants/PositionItemMobile.tsx index 2f8482b6..330ef13c 100644 --- a/src/components/PositionsList/PositionItem/variants/PositionItemMobile.tsx +++ b/src/components/PositionsList/PositionItem/variants/PositionItemMobile.tsx @@ -16,9 +16,13 @@ import { usePromotedPool } from '../hooks/usePromotedPool' import { calculatePercentageRatio } from '../utils/calculations' import { IPositionItem } from '@components/PositionsList/types' import { useSharedStyles } from './style/shared' -import PositionStatusTooltip from '../components/PositionStatusTooltip' +import { InactivePoolsPopover } from '../components/InactivePoolsPopover/InactivePoolsPopover' -export const PositionItemMobile: React.FC = ({ +interface IPositionItemMobile extends IPositionItem { + setAllowPropagation: React.Dispatch> +} + +export const PositionItemMobile: React.FC = ({ tokenXName, tokenYName, tokenXIcon, @@ -30,6 +34,7 @@ export const PositionItemMobile: React.FC = ({ valueX, valueY, position, + setAllowPropagation, // liquidity, poolData, isActive = false, @@ -44,6 +49,7 @@ export const PositionItemMobile: React.FC = ({ const { classes: sharedClasses } = useSharedStyles() const airdropIconRef = useRef(null) const [isPromotedPoolPopoverOpen, setIsPromotedPoolPopoverOpen] = useState(false) + const [isPromotedPoolInactive, setIsPromotedPoolInactive] = useState(false) const isXs = useMediaQuery(theme.breakpoints.down('xs')) const isDesktop = useMediaQuery(theme.breakpoints.up('lg')) @@ -65,6 +71,17 @@ export const PositionItemMobile: React.FC = ({ poolData ) + // const isAnyPopoverActive = isPromotedPoolPopoverOpen || isPromotedPoolInactive + + // const handleGridClick = (e: React.MouseEvent) => { + // // if (isAnyPopoverActive) { + // // e.preventDefault() + + // return + // // } + // // e.stopPropagation() + // } + const feeFragment = useMemo( () => ( = ({ if (event.type === 'touchstart') { event.preventDefault() setIsPromotedPoolPopoverOpen(!isPromotedPoolPopoverOpen) + setAllowPropagation(false) } } const PromotedIcon = () => { + const PROPAGATION_ALLOW_TIME = 500 useEffect(() => { const handleClickOutside = (event: TouchEvent | MouseEvent) => { if ( airdropIconRef.current && !(airdropIconRef.current as HTMLElement).contains(event.target as Node) && - !document.querySelector('.promoted-pool-popover')?.contains(event.target as Node) + !document.querySelector('.promoted-pool-popover')?.contains(event.target as Node) && + !document.querySelector('.promoted-pool-inactive-popover')?.contains(event.target as Node) ) { setIsPromotedPoolPopoverOpen(false) + setIsPromotedPoolInactive(false) + setTimeout(() => { + setAllowPropagation(true) + }, PROPAGATION_ALLOW_TIME) } } - if (isPromotedPoolPopoverOpen) { + if (isPromotedPoolPopoverOpen || isPromotedPoolInactive) { document.addEventListener('click', handleClickOutside) document.addEventListener('touchstart', handleClickOutside) } @@ -159,7 +183,7 @@ export const PositionItemMobile: React.FC = ({ document.removeEventListener('click', handleClickOutside) document.removeEventListener('touchstart', handleClickOutside) } - }, [isPromotedPoolPopoverOpen]) + }, [isPromotedPoolPopoverOpen, isPromotedPoolInactive]) return isPromoted && isActive ? ( <> @@ -198,20 +222,44 @@ export const PositionItemMobile: React.FC = ({ ) : ( <> - e.stopPropagation()} - title={ - <> - isActive = {JSON.stringify(isActive)} - isPromoted = {JSON.stringify(isPromoted)} - - - } - placement='top' - classes={{ - tooltip: sharedClasses.tooltip + { + setIsPromotedPoolInactive(false) + }} + isActive={isActive} + isPromoted={isPromoted} + /> + +
{ + event.stopPropagation() + + if (event.type === 'touchstart') { + event.preventDefault() + setIsPromotedPoolInactive(!isPromotedPoolInactive) + } + }} + onPointerEnter={() => { + if (window.matchMedia('(hover: hover)').matches) { + setIsPromotedPoolInactive(true) + } + }} + onPointerLeave={() => { + if (window.matchMedia('(hover: hover)').matches) { + setIsPromotedPoolInactive(false) + } + }} + onTouchStart={event => { + event.stopPropagation() + + if (event.type === 'touchstart') { + event.preventDefault() + setIsPromotedPoolInactive(!isPromotedPoolInactive) + setAllowPropagation(false) + } }}> = ({ filter: 'grayscale(1)' }} /> - +
) } diff --git a/src/components/PositionsList/PositionsList.tsx b/src/components/PositionsList/PositionsList.tsx index 97406054..fc3b8c9a 100644 --- a/src/components/PositionsList/PositionsList.tsx +++ b/src/components/PositionsList/PositionsList.tsx @@ -146,9 +146,8 @@ export const PositionsList: React.FC = ({ dispatch(actions.getLeaderboardConfig()) }, [dispatch]) - // useEffect(() => { - // pageChanged(page) - // }, [page]) + const [allowPropagation, setAllowPropagation] = useState(true) + return ( = ({ paginator(page).data.map((element, index) => ( { - navigate(`/position/${element.id}`) + if (allowPropagation) { + navigate(`/position/${element.id}`) + } }} key={element.id} className={classes.itemLink}> {isLg ? ( - + ) : ( )} From 84baac1c7fd0001b5092c1159dadbd06d27adef3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Wo=C5=82odko?= Date: Wed, 8 Jan 2025 19:37:00 +0100 Subject: [PATCH 35/42] Remove useless prevent default --- .../PositionsList/PositionItem/variants/PositionItemMobile.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/components/PositionsList/PositionItem/variants/PositionItemMobile.tsx b/src/components/PositionsList/PositionItem/variants/PositionItemMobile.tsx index 330ef13c..d66120d5 100644 --- a/src/components/PositionsList/PositionItem/variants/PositionItemMobile.tsx +++ b/src/components/PositionsList/PositionItem/variants/PositionItemMobile.tsx @@ -150,7 +150,6 @@ export const PositionItemMobile: React.FC = ({ event.stopPropagation() if (event.type === 'touchstart') { - event.preventDefault() setIsPromotedPoolPopoverOpen(!isPromotedPoolPopoverOpen) setAllowPropagation(false) } @@ -238,7 +237,6 @@ export const PositionItemMobile: React.FC = ({ event.stopPropagation() if (event.type === 'touchstart') { - event.preventDefault() setIsPromotedPoolInactive(!isPromotedPoolInactive) } }} @@ -256,7 +254,6 @@ export const PositionItemMobile: React.FC = ({ event.stopPropagation() if (event.type === 'touchstart') { - event.preventDefault() setIsPromotedPoolInactive(!isPromotedPoolInactive) setAllowPropagation(false) } From dba53c5a7eb4ae4cba630953aecf00c58e9b5231 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Wo=C5=82odko?= Date: Thu, 9 Jan 2025 07:21:17 +0100 Subject: [PATCH 36/42] Fix label when points per day is zero --- .../PromotedPoolPopover/PromotedPoolPopover.tsx | 2 +- .../components/YourProgress/YourProgress.tsx | 17 +++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx b/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx index fe35136f..e62477db 100644 --- a/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx +++ b/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx @@ -43,7 +43,7 @@ export const PromotedPoolPopover = ({ {typeof pointsLabel !== 'string' ? pointsLabel : null}
- {formatNumberWithCommas(printBN(points, 0))} + {points.isZero ? '<0.01' : formatNumberWithCommas(printBN(points, 0))}
) diff --git a/src/pages/LeaderboardPage/components/YourProgress/YourProgress.tsx b/src/pages/LeaderboardPage/components/YourProgress/YourProgress.tsx index ac35b4e1..ab8c2cc5 100644 --- a/src/pages/LeaderboardPage/components/YourProgress/YourProgress.tsx +++ b/src/pages/LeaderboardPage/components/YourProgress/YourProgress.tsx @@ -55,6 +55,14 @@ export const YourProgress: React.FC = ({ // return '0' // } // } + const pointsPerDayFormat: string | number = userStats + ? estimated24hPoints.isZero + ? '<0.01' + : removeAdditionalDecimals( + formatNumberWithCommas(printBN(estimated24hPoints, LEADERBOARD_DECIMAL)), + 2 + ) + : 0 return ( = ({ desktopLabelAligment='right' label='Points Per Day' isLoading={isLoadingList} - value={ - userStats - ? removeAdditionalDecimals( - formatNumberWithCommas(printBN(estimated24hPoints, LEADERBOARD_DECIMAL)), - 2 - ) - : 0 - } + value={pointsPerDayFormat} /> Date: Thu, 9 Jan 2025 07:28:38 +0100 Subject: [PATCH 37/42] Fix --- .../PromotedPoolPopover/PromotedPoolPopover.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx b/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx index e62477db..9da8a0f3 100644 --- a/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx +++ b/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx @@ -43,7 +43,7 @@ export const PromotedPoolPopover = ({ {typeof pointsLabel !== 'string' ? pointsLabel : null} - {points.isZero ? '<0.01' : formatNumberWithCommas(printBN(points, 0))} + {formatNumberWithCommas(printBN(points, 0))}
) @@ -52,10 +52,12 @@ export const PromotedPoolPopover = ({
Points earned by this position per 24H: - {removeAdditionalDecimals( - formatNumberWithCommas(printBN(estPoints, LEADERBOARD_DECIMAL)), - 2 - )} + {points.isZero + ? '<0.01' + : removeAdditionalDecimals( + formatNumberWithCommas(printBN(estPoints, LEADERBOARD_DECIMAL)), + 2 + )}
) : null From c9f0aa7e504325d935b295be054f920605653a1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Wo=C5=82odko?= Date: Thu, 9 Jan 2025 07:36:47 +0100 Subject: [PATCH 38/42] Fix --- .../Modals/PromotedPoolPopover/PromotedPoolPopover.tsx | 2 +- .../LeaderboardPage/components/YourProgress/YourProgress.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx b/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx index 9da8a0f3..064e9e53 100644 --- a/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx +++ b/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx @@ -52,7 +52,7 @@ export const PromotedPoolPopover = ({
Points earned by this position per 24H: - {points.isZero + {points.isZero() ? '<0.01' : removeAdditionalDecimals( formatNumberWithCommas(printBN(estPoints, LEADERBOARD_DECIMAL)), diff --git a/src/pages/LeaderboardPage/components/YourProgress/YourProgress.tsx b/src/pages/LeaderboardPage/components/YourProgress/YourProgress.tsx index ab8c2cc5..9f079ca9 100644 --- a/src/pages/LeaderboardPage/components/YourProgress/YourProgress.tsx +++ b/src/pages/LeaderboardPage/components/YourProgress/YourProgress.tsx @@ -56,7 +56,7 @@ export const YourProgress: React.FC = ({ // } // } const pointsPerDayFormat: string | number = userStats - ? estimated24hPoints.isZero + ? estimated24hPoints.isZero() ? '<0.01' : removeAdditionalDecimals( formatNumberWithCommas(printBN(estimated24hPoints, LEADERBOARD_DECIMAL)), From a711fb7e72b5a0e9c5318a6b8b4fb3eb1c1ef85d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Wo=C5=82odko?= Date: Thu, 9 Jan 2025 08:40:01 +0100 Subject: [PATCH 39/42] Fix desktop filcker --- .../PromotedPoolPopover.tsx | 26 +++++- .../variants/PositionItemDesktop.tsx | 85 ++++++++++++++----- 2 files changed, 89 insertions(+), 22 deletions(-) diff --git a/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx b/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx index 064e9e53..749bb389 100644 --- a/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx +++ b/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx @@ -3,6 +3,7 @@ import useStyles from './style' import { Popover, Typography } from '@mui/material' import { formatNumberWithCommas, printBN, removeAdditionalDecimals } from '@utils/utils' import { LEADERBOARD_DECIMAL } from '@pages/LeaderboardPage/config' +import { useRef, useCallback, useEffect } from 'react' export interface IPromotedPoolPopover { open: boolean @@ -30,6 +31,27 @@ export const PromotedPoolPopover = ({ showEstPointsFirst = false }: IPromotedPoolPopover) => { const { classes } = useStyles() + const timeoutRef = useRef() + + const handleMouseEnter = useCallback(() => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current) + } + }, []) + + const handleMouseLeave = useCallback(() => { + timeoutRef.current = setTimeout(() => { + onClose() + }, 100) + }, [onClose]) + + useEffect(() => { + return () => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current) + } + } + }, []) if (!anchorEl) return null @@ -80,7 +102,8 @@ export const PromotedPoolPopover = ({ disableRestoreFocus slotProps={{ paper: { - onMouseLeave: onClose + onMouseEnter: handleMouseEnter, + onMouseLeave: handleMouseLeave } }} transformOrigin={{ @@ -90,6 +113,7 @@ export const PromotedPoolPopover = ({ marginThreshold={16}>
+ {/* Content remains the same */} = ({ const { classes: sharedClasses } = useSharedStyles() const airdropIconRef = useRef(null) const [isPromotedPoolPopoverOpen, setIsPromotedPoolPopoverOpen] = useState(false) + const timeoutRef = useRef() const isXs = useMediaQuery(theme.breakpoints.down('xs')) const isDesktop = useMediaQuery(theme.breakpoints.up('lg')) @@ -65,6 +66,43 @@ export const PositionItemDesktop: React.FC = ({ poolData ) + const handleMouseEnter = useCallback(() => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current) + } + setIsPromotedPoolPopoverOpen(true) + }, []) + + const handleMouseLeave = useCallback(() => { + timeoutRef.current = setTimeout(() => { + setIsPromotedPoolPopoverOpen(false) + }, 100) + }, []) + + useEffect(() => { + return () => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current) + } + } + }, []) + + const [isTooltipOpen, setIsTooltipOpen] = useState(false) + const tooltipTimeoutRef = useRef() + + const handleTooltipEnter = useCallback(() => { + if (tooltipTimeoutRef.current) { + clearTimeout(tooltipTimeoutRef.current) + } + setIsTooltipOpen(true) + }, []) + + const handleTooltipLeave = useCallback(() => { + tooltipTimeoutRef.current = setTimeout(() => { + setIsTooltipOpen(false) + }, 100) + }, []) + const feeFragment = useMemo( () => ( = ({
e.stopPropagation()} className={classes.actionButton} - onPointerLeave={() => { - setIsPromotedPoolPopoverOpen(false) - }} - onPointerEnter={() => { - setIsPromotedPoolPopoverOpen(true) - }}> + onMouseEnter={handleMouseEnter} + onMouseLeave={handleMouseLeave}> {'Airdrop'}
= ({ ) : ( <> setIsTooltipOpen(true)} + onClose={() => setIsTooltipOpen(false)} enterTouchDelay={0} - leaveTouchDelay={Number.MAX_SAFE_INTEGER} + leaveTouchDelay={0} onClick={e => e.stopPropagation()} title={ - <> +
- +
} placement='top' classes={{ tooltip: sharedClasses.tooltip }}> - {'Airdrop'} +
+ {'Airdrop'} +
) From 4b0a6993789b873675e521f697bc906ce8eb05b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Wo=C5=82odko?= Date: Thu, 9 Jan 2025 17:35:15 +0100 Subject: [PATCH 40/42] Fixes --- .../PromotedPoolPopover.tsx | 10 +- .../variants/PositionItemDesktop.tsx | 160 +++++++++--------- .../variants/PositionItemMobile.tsx | 152 +++++++++-------- .../components/YourProgress/YourProgress.tsx | 8 +- 4 files changed, 169 insertions(+), 161 deletions(-) diff --git a/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx b/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx index 749bb389..141085aa 100644 --- a/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx +++ b/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx @@ -9,6 +9,7 @@ export interface IPromotedPoolPopover { open: boolean anchorEl: HTMLElement | null onClose: () => void + isActive: boolean apr?: BN apy?: number estPoints?: BN @@ -22,6 +23,7 @@ export const PromotedPoolPopover = ({ open, onClose, anchorEl, + isActive, apr, apy, estPoints, @@ -39,6 +41,11 @@ export const PromotedPoolPopover = ({ } }, []) + const isLessThanMinimal = (value: BN) => { + const minimalValue = new BN(1).mul(new BN(10).pow(new BN(LEADERBOARD_DECIMAL - 2))) + return value.lt(minimalValue) + } + const handleMouseLeave = useCallback(() => { timeoutRef.current = setTimeout(() => { onClose() @@ -69,12 +76,11 @@ export const PromotedPoolPopover = ({
) - const EstPointsSection = estPoints ? (
Points earned by this position per 24H: - {points.isZero() + {isLessThanMinimal(estPoints) && isActive ? '<0.01' : removeAdditionalDecimals( formatNumberWithCommas(printBN(estPoints, LEADERBOARD_DECIMAL)), diff --git a/src/components/PositionsList/PositionItem/variants/PositionItemDesktop.tsx b/src/components/PositionsList/PositionItem/variants/PositionItemDesktop.tsx index 6c4fe2f5..31ccb60c 100644 --- a/src/components/PositionsList/PositionItem/variants/PositionItemDesktop.tsx +++ b/src/components/PositionsList/PositionItem/variants/PositionItemDesktop.tsx @@ -44,7 +44,6 @@ export const PositionItemDesktop: React.FC = ({ const { classes: sharedClasses } = useSharedStyles() const airdropIconRef = useRef(null) const [isPromotedPoolPopoverOpen, setIsPromotedPoolPopoverOpen] = useState(false) - const timeoutRef = useRef() const isXs = useMediaQuery(theme.breakpoints.down('xs')) const isDesktop = useMediaQuery(theme.breakpoints.up('lg')) @@ -67,40 +66,21 @@ export const PositionItemDesktop: React.FC = ({ ) const handleMouseEnter = useCallback(() => { - if (timeoutRef.current) { - clearTimeout(timeoutRef.current) - } setIsPromotedPoolPopoverOpen(true) }, []) const handleMouseLeave = useCallback(() => { - timeoutRef.current = setTimeout(() => { - setIsPromotedPoolPopoverOpen(false) - }, 100) - }, []) - - useEffect(() => { - return () => { - if (timeoutRef.current) { - clearTimeout(timeoutRef.current) - } - } + setIsPromotedPoolPopoverOpen(false) }, []) const [isTooltipOpen, setIsTooltipOpen] = useState(false) - const tooltipTimeoutRef = useRef() const handleTooltipEnter = useCallback(() => { - if (tooltipTimeoutRef.current) { - clearTimeout(tooltipTimeoutRef.current) - } setIsTooltipOpen(true) }, []) const handleTooltipLeave = useCallback(() => { - tooltipTimeoutRef.current = setTimeout(() => { - setIsTooltipOpen(false) - }, 100) + setIsTooltipOpen(false) }, []) const feeFragment = useMemo( @@ -177,75 +157,87 @@ export const PositionItemDesktop: React.FC = ({ ), [valueX, valueY, tokenXName, classes, isXs, isDesktop, tokenYName, xToY] ) + const promotedIconContent = useMemo(() => { + if (isPromoted && isActive) { + return ( + <> +
e.stopPropagation()} + className={classes.actionButton} + onMouseEnter={handleMouseEnter} + onMouseLeave={handleMouseLeave}> + {'Airdrop'} +
+ setIsPromotedPoolPopoverOpen(false)} + headerText={ + <> + This position is currently earning points + + } + pointsLabel={'Total points distributed across the pool per 24H:'} + estPoints={estimated24hPoints} + points={new BN(pointsPerSecond, 'hex').muln(24).muln(60).muln(60)} + /> + + ) + } - const PromotedIcon = () => - isPromoted && isActive ? ( - <> + return ( + setIsTooltipOpen(true)} + onClose={() => setIsTooltipOpen(false)} + enterTouchDelay={0} + leaveTouchDelay={0} + onClick={e => e.stopPropagation()} + title={ +
+ +
+ } + placement='top' + classes={{ + tooltip: sharedClasses.tooltip + }}>
e.stopPropagation()} - className={classes.actionButton} - onMouseEnter={handleMouseEnter} - onMouseLeave={handleMouseLeave}> + onMouseEnter={handleTooltipEnter} + onMouseLeave={handleTooltipLeave} + style={{ display: 'flex', justifyContent: 'center' }}> {'Airdrop'}
- { - setIsPromotedPoolPopoverOpen(false) - }} - headerText={ - <> - This position is currently earning points - - } - pointsLabel={'Total points distributed across the pool per 24H:'} - estPoints={estimated24hPoints} - points={new BN(pointsPerSecond, 'hex').muln(24).muln(60).muln(60)} - /> - - ) : ( - <> - setIsTooltipOpen(true)} - onClose={() => setIsTooltipOpen(false)} - enterTouchDelay={0} - leaveTouchDelay={0} - onClick={e => e.stopPropagation()} - title={ -
- -
- } - placement='top' - classes={{ - tooltip: sharedClasses.tooltip - }}> -
- {'Airdrop'} -
-
- +
) + }, [ + isPromoted, + isActive, + isPromotedPoolPopoverOpen, + isTooltipOpen, + handleMouseEnter, + handleMouseLeave, + handleTooltipEnter, + handleTooltipLeave, + estimated24hPoints, + pointsPerSecond + ]) return ( = ({ }} direction='row'> - + {promotedIconContent} {feeFragment} = ({ poolData ) - // const isAnyPopoverActive = isPromotedPoolPopoverOpen || isPromotedPoolInactive - - // const handleGridClick = (e: React.MouseEvent) => { - // // if (isAnyPopoverActive) { - // // e.preventDefault() - - // return - // // } - // // e.stopPropagation() - // } - const feeFragment = useMemo( () => ( = ({ setAllowPropagation(false) } } - - const PromotedIcon = () => { + useEffect(() => { const PROPAGATION_ALLOW_TIME = 500 - useEffect(() => { - const handleClickOutside = (event: TouchEvent | MouseEvent) => { - if ( - airdropIconRef.current && - !(airdropIconRef.current as HTMLElement).contains(event.target as Node) && - !document.querySelector('.promoted-pool-popover')?.contains(event.target as Node) && - !document.querySelector('.promoted-pool-inactive-popover')?.contains(event.target as Node) - ) { - setIsPromotedPoolPopoverOpen(false) - setIsPromotedPoolInactive(false) - setTimeout(() => { - setAllowPropagation(true) - }, PROPAGATION_ALLOW_TIME) - } - } - if (isPromotedPoolPopoverOpen || isPromotedPoolInactive) { - document.addEventListener('click', handleClickOutside) - document.addEventListener('touchstart', handleClickOutside) + const handleClickOutside = (event: TouchEvent | MouseEvent) => { + if ( + airdropIconRef.current && + !(airdropIconRef.current as HTMLElement).contains(event.target as Node) && + !document.querySelector('.promoted-pool-popover')?.contains(event.target as Node) && + !document.querySelector('.promoted-pool-inactive-popover')?.contains(event.target as Node) + ) { + setIsPromotedPoolPopoverOpen(false) + setIsPromotedPoolInactive(false) + setTimeout(() => { + setAllowPropagation(true) + }, PROPAGATION_ALLOW_TIME) } + } - return () => { - document.removeEventListener('click', handleClickOutside) - document.removeEventListener('touchstart', handleClickOutside) - } - }, [isPromotedPoolPopoverOpen, isPromotedPoolInactive]) + if (isPromotedPoolPopoverOpen || isPromotedPoolInactive) { + document.addEventListener('click', handleClickOutside) + document.addEventListener('touchstart', handleClickOutside) + } - return isPromoted && isActive ? ( - <> -
{ - if (window.matchMedia('(hover: hover)').matches) { - setIsPromotedPoolPopoverOpen(true) - } - }} - onPointerLeave={() => { - if (window.matchMedia('(hover: hover)').matches) { + return () => { + document.removeEventListener('click', handleClickOutside) + document.removeEventListener('touchstart', handleClickOutside) + } + }, [isPromotedPoolPopoverOpen, isPromotedPoolInactive]) + + const promotedIconFragment = useMemo(() => { + if (isPromoted && isActive) { + return ( + <> +
{ + if (window.matchMedia('(hover: hover)').matches) { + setIsPromotedPoolPopoverOpen(true) + } + }} + onPointerLeave={() => { + if (window.matchMedia('(hover: hover)').matches) { + setIsPromotedPoolPopoverOpen(false) + } + }} + onTouchStart={handleInteraction}> + {'Airdrop'} +
+ { setIsPromotedPoolPopoverOpen(false) + }} + headerText={ + <> + This position is currently earning points + } - }} - onTouchStart={handleInteraction}> - {'Airdrop'} -
- { - setIsPromotedPoolPopoverOpen(false) - }} - headerText={ - <> - This position is currently earning points - - } - pointsLabel={'Total points distributed across the pool per 24H:'} - estPoints={estimated24hPoints} - points={new BN(pointsPerSecond, 'hex').muln(24).muln(60).muln(60)} - /> - - ) : ( + pointsLabel={'Total points distributed across the pool per 24H:'} + estPoints={estimated24hPoints} + points={new BN(pointsPerSecond, 'hex').muln(24).muln(60).muln(60)} + /> + + ) + } + + return ( <> = ({
) - } - + }, [ + isPromoted, + isActive, + isPromotedPoolPopoverOpen, + isPromotedPoolInactive, + classes.actionButton, + handleInteraction, + airdropIconRef, + estimated24hPoints, + pointsPerSecond, + setIsPromotedPoolPopoverOpen, + setIsPromotedPoolInactive, + setAllowPropagation + ]) return ( = ({ justifyContent: 'center', alignItems: 'center' }}> - + {promotedIconFragment} diff --git a/src/pages/LeaderboardPage/components/YourProgress/YourProgress.tsx b/src/pages/LeaderboardPage/components/YourProgress/YourProgress.tsx index 9f079ca9..f11a5a22 100644 --- a/src/pages/LeaderboardPage/components/YourProgress/YourProgress.tsx +++ b/src/pages/LeaderboardPage/components/YourProgress/YourProgress.tsx @@ -55,8 +55,13 @@ export const YourProgress: React.FC = ({ // return '0' // } // } + const isLessThanMinimal = (value: BN) => { + const minimalValue = new BN(1).mul(new BN(10).pow(new BN(LEADERBOARD_DECIMAL - 2))) + return value.lt(minimalValue) + } + const pointsPerDayFormat: string | number = userStats - ? estimated24hPoints.isZero() + ? isLessThanMinimal(estimated24hPoints) ? '<0.01' : removeAdditionalDecimals( formatNumberWithCommas(printBN(estimated24hPoints, LEADERBOARD_DECIMAL)), @@ -75,7 +80,6 @@ export const YourProgress: React.FC = ({ width: '100%' }}> Your Progress - From 0c2c01fd66efbd58946b7a96ea90677a79d81540 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Wo=C5=82odko?= Date: Thu, 9 Jan 2025 17:42:28 +0100 Subject: [PATCH 41/42] Fixes --- .../PromotedPoolPopover.tsx | 2 +- .../InactivePoolsPopover.tsx | 2 +- .../components/PositionStatusTooltip.tsx | 2 +- .../PositionItem/components/PromotedIcons.tsx | 93 ------------------- .../variants/PositionItemDesktop.tsx | 2 +- 5 files changed, 4 insertions(+), 97 deletions(-) delete mode 100644 src/components/PositionsList/PositionItem/components/PromotedIcons.tsx diff --git a/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx b/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx index 141085aa..46ece80f 100644 --- a/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx +++ b/src/components/Modals/PromotedPoolPopover/PromotedPoolPopover.tsx @@ -9,7 +9,7 @@ export interface IPromotedPoolPopover { open: boolean anchorEl: HTMLElement | null onClose: () => void - isActive: boolean + isActive?: boolean apr?: BN apy?: number estPoints?: BN diff --git a/src/components/PositionsList/PositionItem/components/InactivePoolsPopover/InactivePoolsPopover.tsx b/src/components/PositionsList/PositionItem/components/InactivePoolsPopover/InactivePoolsPopover.tsx index c8abfdf3..8969e56e 100644 --- a/src/components/PositionsList/PositionItem/components/InactivePoolsPopover/InactivePoolsPopover.tsx +++ b/src/components/PositionsList/PositionItem/components/InactivePoolsPopover/InactivePoolsPopover.tsx @@ -7,7 +7,7 @@ export interface IPromotedPoolPopover { anchorEl: HTMLElement | null onClose: () => void - isActive: boolean + isActive?: boolean isPromoted: boolean } diff --git a/src/components/PositionsList/PositionItem/components/PositionStatusTooltip.tsx b/src/components/PositionsList/PositionItem/components/PositionStatusTooltip.tsx index b7368696..27bda92e 100644 --- a/src/components/PositionsList/PositionItem/components/PositionStatusTooltip.tsx +++ b/src/components/PositionsList/PositionItem/components/PositionStatusTooltip.tsx @@ -1,4 +1,4 @@ -const PositionStatusTooltip: React.FC<{ isActive: boolean; isPromoted: boolean }> = ({ +const PositionStatusTooltip: React.FC<{ isActive?: boolean; isPromoted?: boolean }> = ({ isActive, isPromoted }) => { diff --git a/src/components/PositionsList/PositionItem/components/PromotedIcons.tsx b/src/components/PositionsList/PositionItem/components/PromotedIcons.tsx deleted file mode 100644 index fbe3a673..00000000 --- a/src/components/PositionsList/PositionItem/components/PromotedIcons.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import PromotedPoolPopover from '@components/Modals/PromotedPoolPopover/PromotedPoolPopover' -import { BN } from '@coral-xyz/anchor' -import { Tooltip } from '@mui/material' -import icons from '@static/icons' - -interface IPromotedIconProps { - isPromoted: boolean - isActive: boolean - pointsPerSecond: string - estimated24hPoints: BN - onOpenChange: (isOpen: boolean) => void - isOpen: boolean - iconRef: React.RefObject - isDesktop?: boolean -} - -export const PromotedIcon: React.FC = ({ - isPromoted, - isActive, - pointsPerSecond, - estimated24hPoints, - onOpenChange, - isOpen, - iconRef, - isDesktop -}) => { - if (!isPromoted || !isActive) { - return ( - e.stopPropagation()} - title={ - !isActive ? ( -

- This position isn't earning points, even though the pool is generating them. - Your position's liquidity remains inactive and won't earn points as long - as the current price is outside its specified price range. -

- ) : !isPromoted ? ( -

- This position isn't earning points because it was opened on a pool that - doesn't generate them. -

- ) : null - } - placement='top'> - {'Airdrop'} -
- ) - } - - return ( - <> -
e.stopPropagation()} - onPointerLeave={() => onOpenChange(false)} - onPointerEnter={() => onOpenChange(true)}> - {'Airdrop'} -
- onOpenChange(false)} - headerText={ - <> - This position is currently earning points - - } - pointsLabel={'Total points distributed across the pool per 24H:'} - estPoints={estimated24hPoints} - points={new BN(pointsPerSecond, 'hex').muln(24).muln(60).muln(60)} - /> - - ) -} diff --git a/src/components/PositionsList/PositionItem/variants/PositionItemDesktop.tsx b/src/components/PositionsList/PositionItem/variants/PositionItemDesktop.tsx index 31ccb60c..ca73316d 100644 --- a/src/components/PositionsList/PositionItem/variants/PositionItemDesktop.tsx +++ b/src/components/PositionsList/PositionItem/variants/PositionItemDesktop.tsx @@ -3,7 +3,7 @@ import SwapList from '@static/svg/swap-list.svg' import { theme } from '@static/theme' import { formatNumber } from '@utils/utils' import classNames from 'classnames' -import { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { useCallback, useMemo, useRef, useState } from 'react' import { useDesktopStyles } from './style/desktop' import { TooltipHover } from '@components/TooltipHover/TooltipHover' import { initialXtoY, tickerToAddress } from '@utils/utils' From 00559f17d30186084b8b2bf85006c383cc6a36f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Wo=C5=82odko?= Date: Thu, 9 Jan 2025 17:56:21 +0100 Subject: [PATCH 42/42] Fix display in yourprogress --- .../components/YourProgress/YourProgress.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/pages/LeaderboardPage/components/YourProgress/YourProgress.tsx b/src/pages/LeaderboardPage/components/YourProgress/YourProgress.tsx index f11a5a22..3c4a71dd 100644 --- a/src/pages/LeaderboardPage/components/YourProgress/YourProgress.tsx +++ b/src/pages/LeaderboardPage/components/YourProgress/YourProgress.tsx @@ -60,14 +60,14 @@ export const YourProgress: React.FC = ({ return value.lt(minimalValue) } - const pointsPerDayFormat: string | number = userStats - ? isLessThanMinimal(estimated24hPoints) + const pointsPerDayFormat: string | number = isLessThanMinimal(estimated24hPoints) + ? isConnected && !estimated24hPoints.isZero() ? '<0.01' - : removeAdditionalDecimals( - formatNumberWithCommas(printBN(estimated24hPoints, LEADERBOARD_DECIMAL)), - 2 - ) - : 0 + : 0 + : removeAdditionalDecimals( + formatNumberWithCommas(printBN(estimated24hPoints, LEADERBOARD_DECIMAL)), + 2 + ) return (