packages/synapse-interface/pages/pools/PoolCard.tsx
import _ from 'lodash'
import Link from 'next/link'
import { useEffect, useMemo, useState, memo } from 'react'
import { useAccount } from 'wagmi'
import { type Address } from 'viem'
import { LoaderIcon, toast } from 'react-hot-toast'
import { useTranslations } from 'next-intl'
import { useAppSelector } from '@/store/hooks'
import { usePortfolioState } from '@/slices/portfolio/hooks'
import { getSinglePoolData } from '@utils/actions/getPoolData'
import { getPoolApyData } from '@utils/actions/getPoolApyData'
import { getStakedBalance } from '@/utils/actions/getStakedBalance'
import { formatBigIntToString } from '@/utils/bigint/format'
import { PoolActionOptions } from '../../components/Pools/PoolActionOptions'
import { PoolHeader } from '../../components/Pools/PoolHeader'
import { PoolCardBody } from '../../components/Pools/PoolCardBody'
import { STAKE_PATH, getPoolUrl } from '@urls'
import { Token } from '@types'
const PoolCard = memo(({ pool, address }: { pool: Token; address: string }) => {
const [isClient, setIsClient] = useState(false)
const [poolData, setPoolData] = useState(undefined)
const [poolApyData, setPoolApyData] = useState(undefined)
const [stakedBalance, setStakedBalance] = useState({
amount: 0n,
reward: 0n,
})
const { isDisconnected } = useAccount()
const { synPrices, ethPrice, avaxPrice, metisPrice } = useAppSelector(
(state) => state.priceData
)
const prices = { synPrices, ethPrice, avaxPrice, metisPrice }
let popup: string
useEffect(() => {
setIsClient(true)
}, [])
useEffect(() => {
if (pool && isClient) {
// TODO - separate the apy and tvl so they load async.
getSinglePoolData(pool.chainId, pool, prices)
.then((res) => {
setPoolData(res)
})
.catch((err) => {
console.log('Could not get Pool Data: ', err)
})
getPoolApyData(pool.chainId, pool, prices)
.then((res) => {
setPoolApyData(res)
})
.catch((err) => {
console.log('Could not get Pool APY Data: ', err)
})
}
}, [pool, isClient])
useEffect(() => {
if (address && isClient) {
getStakedBalance(
address as Address,
pool.chainId,
pool.poolId[pool.chainId],
pool
)
.then((res) => {
setStakedBalance(res)
})
.catch((err) => {
console.log('Could not get staked balances: ', err)
})
} else {
setStakedBalance({ amount: 0n, reward: 0n })
}
}, [address, isClient])
/*
useEffect triggers: address, isDisconnected, popup
- will dismiss toast asking user to connect wallet once wallet has been connected
*/
useEffect(() => {
if (address && !isDisconnected && popup) {
toast.dismiss(popup)
}
}, [address, isDisconnected, popup])
return (
<div
className={`
border
${
pool && pool.incentivized
? 'bg-bgBase border-transparent'
: 'bg-bgDark border-gray-700'
}
rounded-md items-center
space-y-2
whitespace-wrap
`}
>
<div>
<Link href={getPoolUrl(pool)}>
{pool && <PoolHeader pool={pool} address={address as Address} />}
{pool &&
poolData &&
poolApyData &&
poolData.tokens &&
poolData.totalLockedUSD ? (
<PoolCardBody
pool={pool}
poolApyData={poolApyData}
poolData={poolData}
/>
) : (
<div className="flex items-center justify-between px-3 pt-1 pb-2 h-[65px]">
<LoaderIcon />
</div>
)}
</Link>
{pool && (
<>
<ManageLp
pool={pool}
stakedBalance={stakedBalance}
address={address}
/>
</>
)}
</div>
</div>
)
})
const ManageLp = ({ pool, stakedBalance, address }) => {
const { poolTokenBalances } = usePortfolioState()
const { amount, reward } = stakedBalance
const t = useTranslations('Pools')
const lpTokenBalance = useMemo(() => {
if (!address) {
return null
}
const token = _(poolTokenBalances[pool.chainId])
.pickBy(
(value, _key) =>
value.tokenAddress === pool.addresses[pool.chainId] &&
value.balance > 0n
)
.value()
if (Object.keys(token).length === 0) {
return null
} else {
return token[0]
}
}, [pool, poolTokenBalances, address])
if (!lpTokenBalance && amount === 0n && reward === 0n) {
return null
}
return (
<div className="border-t-2 border-[#302C33] pt-2 pb-2 ">
<div className="flex items-center justify-between pt-2 pl-3 pr-3">
<DisplayBalances
pool={pool}
address={address}
stakedBalance={stakedBalance}
showIcon={true}
/>
<div className="flex items-center text-xs">
<PoolActionOptions
pool={pool}
options={[
t('Deposit'),
t('Withdraw'),
t('Stake'),
t('Unstake'),
t('Claim'),
]}
/>
</div>
</div>
</div>
)
}
export const DisplayBalances = ({ pool, stakedBalance, showIcon, address }) => {
const { poolTokenBalances } = usePortfolioState()
const { amount, reward } = stakedBalance
const t = useTranslations('Pools')
const lpTokenBalance = useMemo(() => {
if (!address) {
return null
}
const token = _(poolTokenBalances[pool.chainId])
.pickBy(
(value, _key) =>
value.tokenAddress === pool.addresses[pool.chainId] &&
value.balance > 0n
)
.value()
if (Object.keys(token).length === 0) {
return null
} else {
return token[0]
}
}, [pool, poolTokenBalances, address])
const sum = useMemo(() => {
const b =
lpTokenBalance && lpTokenBalance.balance ? lpTokenBalance.balance : 0n
return amount + b
}, [lpTokenBalance, amount])
if (!lpTokenBalance && amount === 0n && reward === 0n) {
return null
}
return (
<div id="display-balance" className="flex items-center space-x-2">
{showIcon && (
<img src={pool.icon.src} className="w-[20px] h-[20px] rounded-full" />
)}
<div className="">
<div className="flex items-center space-x-1">
<div className="text-white text-md">
<Link href={`${STAKE_PATH}/${pool.routerIndex}`}>
<span className="hover:underline">
{formatBigIntToString(amount, pool.decimals[pool.chainId], 5)}
</span>
</Link>
<span className="text-[#BFBCC2] text-sm">
{' '}
/{' '}
<Link href={getPoolUrl(pool)}>
<span className="hover:underline">
{formatBigIntToString(sum, pool.decimals[pool.chainId], 5)}
</span>
</Link>
</span>
</div>
<div className="text-sm text-[#BFBCC2]">{pool.symbol}</div>
</div>
{reward > 0n && (
<div className="text-sm">
<span className="text-white">{t('Earned')}: </span>
<span className="text-green-400 hover:underline">
<Link href={`${STAKE_PATH}/${pool.routerIndex}`}>
{formatBigIntToString(reward, 18, 5)}{' '}
{pool?.customRewardToken ?? 'SYN'}
</Link>
</span>
</div>
)}
</div>
</div>
)
}
export default PoolCard