mirror of
https://github.com/cerc-io/mars-interface.git
synced 2025-08-08 02:20:06 +00:00
267 lines
8.6 KiB
TypeScript
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>
|
|
)
|
|
}
|