mars-interface/src/components/common/Header/IncentivesButton.tsx
Linkie Link 6b6d717fac
v1.6.0
2023-08-02 11:25:13 +02:00

267 lines
8.6 KiB
TypeScript

import { ChainInfoID, SimpleChainInfoList, TxBroadcastResult } from '@marsprotocol/wallet-connector'
import { useQueryClient } from '@tanstack/react-query'
import classNames from 'classnames'
import {
AnimatedNumber,
Button,
DisplayCurrency,
ErrorMessage,
SVG,
Tooltip,
TxLink,
} from 'components/common'
import { MARS_SYMBOL } from 'constants/appConstants'
import { findByDenom } from 'functions'
import { getClaimUserRewardsMsgOptions } from 'functions/messages'
import { useEstimateFee } from 'hooks/queries'
import { lookup, lookupSymbol } from 'libs/parse'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import useStore from 'store'
import { State } from 'types/enums'
import { QUERY_KEYS } from 'types/enums/queryKeys'
import styles from './IncentivesButton.module.scss'
export const IncentivesButton = () => {
// ---------------
// EXTERNAL HOOKS
// ---------------
const { t } = useTranslation()
const queryClient = useQueryClient()
// ---------------
// STORE STATE
// ---------------
const client = useStore((s) => s.client)
const whitelistedAssets = useStore((s) => s.whitelistedAssets)
const otherAssets = useStore((s) => s.otherAssets)
const userWalletAddress = useStore((s) => s.userWalletAddress)
const networkConfig = useStore((s) => s.networkConfig)
const unclaimedRewards = useStore((s) => s.userUnclaimedRewards)
const convertToDisplayCurrency = useStore((s) => s.convertToDisplayCurrency)
const marsPriceState = useStore((s) => s.marsPriceState)
const incentivesContractAddress = useStore((s) => s.networkConfig.contracts.incentives)
const chainInfo = useStore((s) => s.chainInfo)
const executeMsg = useStore((s) => s.executeMsg)
// ---------------
// LOCAL STATE
// ---------------
const [showDetails, setShowDetails] = useState(false)
const [disabled, setDisabled] = useState(true)
const [fetching, setFetching] = useState(false)
const [submitted, setSubmitted] = useState(false)
const [response, setResponse] = useState<TxBroadcastResult>()
const [error, setError] = useState<string>()
const [hasUnclaimedRewards, setHasUnclaimedRewards] = useState(false)
const [unclaimedRewardsValue, setUnclaimedRewardsValue] = useState(0)
// ---------------
// LOCAL VARIABLES
// ---------------
const explorerUrl = chainInfo && SimpleChainInfoList[chainInfo.chainId as ChainInfoID].explorer
const assets = [...whitelistedAssets, ...otherAssets]
// ---------------
// FUNCTIONS
// ---------------
const onClickAway = useCallback(() => {
setShowDetails(false)
setResponse(undefined)
setError(undefined)
}, [])
useEffect(() => {
let rewardsValue = 0
let rewardsAmount = 0
if (marsPriceState !== State.READY) return
unclaimedRewards.forEach((reward) => {
rewardsValue += convertToDisplayCurrency(reward)
rewardsAmount += Number(reward.amount)
})
setUnclaimedRewardsValue(rewardsValue)
setHasUnclaimedRewards(rewardsAmount > 0)
}, [unclaimedRewards, convertToDisplayCurrency, marsPriceState])
const txMsgOptions = useMemo(() => {
if (!hasUnclaimedRewards) return
return getClaimUserRewardsMsgOptions()
}, [hasUnclaimedRewards])
const { data: fee, error: feeError } = useEstimateFee({
msg: txMsgOptions?.msg,
funds: [],
contract: incentivesContractAddress,
})
if (feeError && error !== feeError && !fee) {
setError(feeError as string)
}
useEffect(() => {
const isFetching = submitted || (!fee && !response && hasUnclaimedRewards)
if (fetching === isFetching) return
setFetching(isFetching)
}, [submitted, fetching, fee, response, hasUnclaimedRewards])
useEffect(() => {
if (error) {
setDisabled(!hasUnclaimedRewards)
setSubmitted(false)
return
}
if (response?.hash) {
setDisabled(true)
setSubmitted(false)
return
}
setDisabled(!hasUnclaimedRewards)
}, [error, response, hasUnclaimedRewards])
const claimRewards = async () => {
if (!incentivesContractAddress || !client) {
setError(t('error.errorClaim'))
setDisabled(true)
setSubmitted(false)
return
}
setDisabled(true)
setSubmitted(true)
setError(undefined)
if (!fee || !txMsgOptions) {
return
}
try {
const res = await executeMsg({
msg: txMsgOptions.msg,
funds: [],
contract: incentivesContractAddress,
fee,
})
setResponse(res)
queryClient.invalidateQueries([QUERY_KEYS.REDBANK])
} catch (error) {
const e = error as { message: string }
setError(e.message as string)
}
}
const transactionHash = response?.hash || ''
if (!userWalletAddress) return null
return (
<div className={styles.wrapper}>
<button
className={classNames(
unclaimedRewardsValue > 1 ? `${styles.button} ${styles.buttonHighlight}` : styles.button,
)}
onClick={() => {
setShowDetails(!showDetails)
}}
>
{networkConfig.hasMultiAssetIncentives ? (
<AnimatedNumber
amount={unclaimedRewardsValue}
minDecimals={2}
maxDecimals={2}
prefix='$'
/>
) : (
<>
<SVG.Logo />
<span>
<AnimatedNumber
className={styles.marsAmount}
amount={Number(unclaimedRewards[0]?.amount ?? 0) / 1e6}
minDecimals={2}
maxDecimals={2}
/>
</span>
{MARS_SYMBOL}
</>
)}
</button>
{showDetails && (
<>
<div className={styles.details}>
<div className={styles.detailsHeader}>
<p className={styles.detailsHead}>{t('incentives.marsRewardsCenter')}</p>
<div className={styles.tooltip}>
<Tooltip content={t('incentives.marsRewardsCenterTooltip')} />
</div>
</div>
<div className={styles.detailsBody}>
{response ? (
<div className={`${styles.container} ${styles.info}`}>
<p className='m'>{t('incentives.successfullyClaimed')}</p>
<TxLink hash={transactionHash} link={`${explorerUrl}/txs/${transactionHash}`} />
</div>
) : (
<>
{unclaimedRewards.map((rewards, index) => {
const asset = findByDenom(assets, rewards.denom)
if (!asset) return null
return (
<div className={styles.container} key={index}>
<div className={styles.position}>
<div className={styles.label}>
<p className={styles.token}>{lookupSymbol(asset.denom, assets)}</p>
<p className={styles.subhead}>{t('redbank.redBankRewards')}</p>
</div>
<div className={styles.value}>
<AnimatedNumber
className={styles.tokenAmount}
amount={lookup(
Number(rewards.amount) || 0,
asset.symbol,
asset.decimals,
)}
maxDecimals={asset.decimals}
minDecimals={2}
/>
<DisplayCurrency
className={styles.tokenValue}
coin={{
amount: rewards.amount,
denom: asset.denom,
}}
/>
</div>
</div>
</div>
)
})}
</>
)}
<div className={styles.claimButton}>
<Button
disabled={disabled}
showProgressIndicator={fetching}
text={
hasUnclaimedRewards && !disabled
? t('incentives.claimRewards')
: t('incentives.nothingToClaim')
}
onClick={() => (submitted ? null : claimRewards())}
color='primary'
/>
<ErrorMessage message={error} alignment='center' />
</div>
</div>
</div>
<div className={styles.clickAway} onClick={onClickAway} role='button' />
</>
)}
</div>
)
}