date-fns#intervalToDuration TypeScript Examples
The following examples show how to use
date-fns#intervalToDuration.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: NftForm.utils.ts From atlas with GNU General Public License v3.0 | 7 votes |
getTotalDaysAndHours = (startDate: AuctionDatePickerValue, endDate: AuctionDatePickerValue) => {
const start = (startDate?.type === 'date' && startDate.date) || new Date()
if (endDate?.type === 'date') {
const { days, hours } = intervalToDuration({ start: start, end: endDate.date })
const duration = formatDuration({ hours: hours, days: days }, { format: ['days', 'hours'] })
return duration ? duration : 'Less than 1 hour'
}
if (endDate?.type === 'duration') {
return pluralizeNoun(endDate.durationDays, 'day')
}
}
Example #2
Source File: duration.tsx From livepeer-com with MIT License | 6 votes |
DurationCell = <D extends TableData>({
cell,
}: CellComponentProps<D, DurationCellProps>) => {
if (cell.value.status === "waiting") {
return "In progress";
}
if (cell.value.duration === 0) {
return "n/a";
}
try {
const dur = intervalToDuration({
start: new Date(0),
end: new Date(Math.ceil(cell.value.duration / 60) * 60 * 1000 || 0),
});
return formatDuration(dur);
} catch (error) {
return "n/a";
}
}
Example #3
Source File: duration.tsx From livepeer-com with MIT License | 6 votes |
DurationCell = <D extends TableData>({
cell,
}: CellComponentProps<D, DurationCellProps>) => {
if (cell.value.status === "waiting") {
return "In progress";
}
if (cell.value.sourceSegmentsDuration === 0) {
return "n/a";
}
try {
const dur = intervalToDuration({
start: new Date(0),
end: new Date(
Math.ceil(cell.value.sourceSegmentsDuration / 60) * 60 * 1000 || 0
),
});
return formatDuration(dur);
} catch (error) {
return "n/a";
}
}
Example #4
Source File: date.ts From frontend with MIT License | 5 votes |
getDurationUntilNow = (date: Date) => {
return intervalToDuration({ start: date, end: new Date() })
}
Example #5
Source File: NftPurchaseBottomDrawer.tsx From atlas with GNU General Public License v3.0 | 4 votes |
NftPurchaseBottomDrawer: React.FC = () => {
const { displaySnackbar } = useSnackbar()
const [type, setType] = useState<'english_auction' | 'open_auction' | 'buy_now'>('english_auction')
const [showBuyNowInfo, setBuyNowInfo] = useState(false)
const { currentAction, closeNftAction, currentNftId, isBuyNowClicked } = useNftActions()
const { nft, nftStatus, loading, refetch } = useNft(currentNftId || '')
const { userBid, canChangeBid, userBidUnlockDate } = useNftState(nft)
const { isLoadingAsset: thumbnailLoading, url: thumbnailUrl } = useAsset(nft?.video.thumbnailPhoto)
const { url: creatorAvatarUrl } = useAsset(nft?.video.channel.avatarPhoto)
const { url: ownerMemberAvatarUrl } = useMemberAvatar(nft?.ownerMember)
const mdMatch = useMediaMatch('md')
const { convertToUSD } = useTokenPrice()
const accountBalance = useSubscribeAccountBalance()
const timestamp = useMsTimestamp({ shouldStop: !currentAction })
const { convertBlockToMsTimestamp, convertBlocksToDuration } = useBlockTimeEstimation()
const {
joystream,
proxyCallback,
currentBlock,
chainState: { nftPlatformFeePercentage },
} = useJoystream()
const handleTransaction = useTransaction()
const { activeMemberId } = useUser()
const {
watch,
setValue,
handleSubmit: createSubmitHandler,
register,
reset,
formState: { errors },
} = useForm<{ bid: number }>()
const isAuction = nftStatus?.status === 'auction'
const isBuyNow = nftStatus?.status === 'buy-now'
const isEnglishAuction = nftStatus?.status === 'auction' && nftStatus.type === 'english'
const isOpenAuction = nftStatus?.status === 'auction' && nftStatus?.type === 'open'
useEffect(() => {
if (isBuyNow) {
setType('buy_now')
}
if (isEnglishAuction) {
setType('english_auction')
}
if (isOpenAuction) {
setType('open_auction')
}
}, [isBuyNow, isEnglishAuction, isOpenAuction])
const auctionBuyNowPrice = (isAuction && nftStatus.buyNowPrice) || 0
const bidLockingTime = isAuction && nftStatus.bidLockingTime && convertBlocksToDuration(nftStatus.bidLockingTime)
const buyNowPrice = (isBuyNow && nftStatus.buyNowPrice) || 0
const startingPrice = isAuction && nftStatus.startingPrice
const topBidder = isAuction && nftStatus.topBidder ? nftStatus.topBidder : undefined
const topBidAmount = (isAuction && !nftStatus.topBid?.isCanceled && nftStatus.topBidAmount) || 0
const minimalBidStep = (isAuction && nftStatus.minimalBidStep) || 0
const endAtBlock = isAuction && nftStatus.auctionPlannedEndBlock
const minimumBidEnglishAuction = startingPrice > topBidAmount ? startingPrice : topBidAmount + minimalBidStep
const minimumBid = type === 'open_auction' ? startingPrice : minimumBidEnglishAuction
const endTime = endAtBlock && convertBlockToMsTimestamp(endAtBlock)
const timeLeftSeconds = endTime ? Math.trunc((endTime - timestamp) / 1000) : 0
const creatorRoyalty = nft?.creatorRoyalty || 0
const ownerRoyalty = 100 - creatorRoyalty - nftPlatformFeePercentage
// check if input value isn't bigger than fixed price
useEffect(() => {
if (type === 'buy_now' || !auctionBuyNowPrice) {
return
}
const subscription = watch(({ bid }) => {
if (bid >= auctionBuyNowPrice) {
setBuyNowInfo(true)
} else {
setBuyNowInfo(false)
}
if (bid > auctionBuyNowPrice) {
setValue('bid', auctionBuyNowPrice)
}
})
return () => subscription.unsubscribe()
}, [auctionBuyNowPrice, setValue, type, watch])
const handleBuyNow = useCallback(async () => {
if (!joystream || !currentNftId || !activeMemberId) return
const completed = await handleTransaction({
onError: () => refetch(),
txFactory: async (updateStatus) => {
if (!isAuction) {
return (await joystream.extrinsics).buyNftNow(
currentNftId,
activeMemberId,
buyNowPrice,
proxyCallback(updateStatus)
)
} else {
return (await joystream.extrinsics).makeNftBid(
currentNftId,
activeMemberId,
Number(auctionBuyNowPrice),
isEnglishAuction ? 'english' : 'open',
proxyCallback(updateStatus)
)
}
},
onTxSync: async (_) => refetch(),
})
if (completed) {
closeNftAction()
displaySnackbar({
title: 'You have successfully bought NFT.',
iconType: 'success',
})
}
}, [
activeMemberId,
auctionBuyNowPrice,
buyNowPrice,
closeNftAction,
currentNftId,
displaySnackbar,
handleTransaction,
isAuction,
joystream,
isEnglishAuction,
proxyCallback,
refetch,
])
const handleBidOnAuction = useCallback(() => {
const submit = createSubmitHandler(async (data) => {
if (!joystream || !currentNftId || !activeMemberId) return
const completed = await handleTransaction({
txFactory: async (updateStatus) =>
(
await joystream.extrinsics
).makeNftBid(
currentNftId,
activeMemberId,
Number(data.bid),
isEnglishAuction ? 'english' : 'open',
proxyCallback(updateStatus)
),
onTxSync: (_) => refetch(),
})
if (completed) {
if (Number(data.bid) === auctionBuyNowPrice) {
displaySnackbar({
title: 'You have bought this NFT successfully',
iconType: 'success',
})
} else {
displaySnackbar({
title: 'Your bid has been placed',
description: 'We will notify you about any changes.',
iconType: 'success',
})
}
closeNftAction()
}
})
submit()
}, [
activeMemberId,
auctionBuyNowPrice,
closeNftAction,
createSubmitHandler,
currentNftId,
displaySnackbar,
handleTransaction,
isEnglishAuction,
joystream,
proxyCallback,
refetch,
])
const isBuyNowAffordable = (buyNowPrice || auctionBuyNowPrice) + TRANSACTION_FEE < (accountBalance || 0)
const bid = watch('bid')
const timeLeftUnderMinute = !!timeLeftSeconds && timeLeftSeconds < 60
const auctionEnded = type === 'english_auction' && timeLeftSeconds <= 0
const insufficientFoundsError = errors.bid && errors.bid.type === 'bidTooHigh'
const primaryButtonText =
type === 'buy_now' || (auctionBuyNowPrice && auctionBuyNowPrice <= bid) || isBuyNowClicked
? 'Buy NFT'
: canChangeBid
? 'Change bid'
: 'Place bid'
const blocksLeft = endAtBlock && endAtBlock - currentBlock
const isOpen = currentAction === 'purchase'
useEffect(() => {
if (!isOpen) {
setValue('bid', NaN)
}
}, [isOpen, setValue])
const hasErrors = !!Object.keys(errors).length
const { isLoadingAsset: userBidAvatarLoading, url: userBidAvatarUrl } = useMemberAvatar(userBid?.bidder)
const { isLoadingAsset: topBidderAvatarLoading, url: topBidderAvatarUrl } = useMemberAvatar(topBidder)
const timeToUnlockSeconds = userBidUnlockDate ? differenceInSeconds(userBidUnlockDate, new Date()) : 0
return (
<BottomDrawer
isOpen={isOpen}
onClose={() => {
reset()
closeNftAction()
}}
actionBar={{
primaryButton: {
text: primaryButtonText,
disabled: isBuyNowClicked || type === 'buy_now' ? !isBuyNowAffordable : hasErrors,
onClick: () => (isBuyNowClicked || type === 'buy_now' ? handleBuyNow() : handleBidOnAuction()),
},
}}
>
<Content>
<NftPreview>
<NftCard
title={nft?.video.title}
thumbnail={{
loading: thumbnailLoading || loading || !nft,
thumbnailUrl: thumbnailUrl,
}}
creator={{ name: nft?.video.channel.title, assetUrl: creatorAvatarUrl }}
owner={{ name: nft?.ownerMember?.handle, assetUrl: ownerMemberAvatarUrl }}
loading={loading}
fullWidth={!mdMatch}
/>
</NftPreview>
<PlaceBidWrapper>
<InnerContainer>
<Header>
<Text variant="h600">
{type !== 'buy_now' && !isBuyNowClicked ? (canChangeBid ? 'Change bid' : 'Place bid') : 'Buy NFT'}
</Text>
{type === 'english_auction' && (
<FlexWrapper>
<Text variant="h200" secondary>
Ending in:
</Text>
<Text
variant="h200"
margin={{ left: 2, right: 2 }}
color={
auctionEnded
? cVar('colorTextMuted', true)
: timeLeftUnderMinute
? cVar('colorTextError')
: undefined
}
>
{!auctionEnded
? !timeLeftUnderMinute
? formatDurationShort(timeLeftSeconds, true)
: 'Under 1 min'
: 'Auction ended'}
<Text variant="h200" as="span" secondary>
{' '}
/ {pluralizeNoun(blocksLeft && blocksLeft > 0 ? blocksLeft : 0, 'block')}
</Text>
</Text>
<FlexWrapper>
{endAtBlock && (
<Information
text={`On blockchain, duration is expressed in number of blocks. This auction ends at block ${endAtBlock}.`}
placement="top"
/>
)}
</FlexWrapper>
</FlexWrapper>
)}
</Header>
{type !== 'buy_now' && !isBuyNowClicked ? (
<>
<CurrentBidWrapper>
{topBidder && !!topBidAmount ? (
<ActiveBidWrapper>
<ActionBarCell>
<Text variant="h100" secondary margin={{ bottom: 2 }}>
Top bid
</Text>
<FlexWrapper>
<Avatar size="bid" assetUrl={topBidderAvatarUrl} loading={topBidderAvatarLoading} />
<TokenWrapper>
<StyledJoyTokenIcon variant="gray" size={24} />
</TokenWrapper>
<BidAmount variant="h400">{formatNumberShort(topBidAmount)}</BidAmount>
</FlexWrapper>
<Text variant="t100" secondary margin={{ top: 1 }}>
{topBidder.handle === userBid?.bidder.handle ? 'You' : topBidder.handle}
</Text>
</ActionBarCell>
{userBid && (
<ActionBarCell>
<Text variant="h100" secondary margin={{ bottom: 2 }}>
Your Bid
</Text>
<FlexWrapper>
<Avatar size="bid" assetUrl={userBidAvatarUrl} loading={userBidAvatarLoading} />
<TokenWrapper>
<StyledJoyTokenIcon variant="gray" size={24} />
</TokenWrapper>
<BidAmount variant="h400">{formatNumberShort(Number(userBid.amount))}</BidAmount>
</FlexWrapper>
<Text variant="t100" secondary margin={{ top: 1 }}>
You
</Text>
</ActionBarCell>
)}
</ActiveBidWrapper>
) : (
<ActiveBidWrapper>
<ActionBarCell>
<Text variant="h100" secondary margin={{ bottom: 2 }}>
Top bid
</Text>
<Text variant="h400">No bids yet</Text>
</ActionBarCell>
</ActiveBidWrapper>
)}
</CurrentBidWrapper>
{!auctionEnded && (
<MinimumBidWrapper>
<MinimumBid>
<Text variant="h300" secondary>
Minimum bid
</Text>
<JoyTokenIcon variant="gray" size={24} /> <Text variant="h400">{minimumBid}</Text>
</MinimumBid>
{auctionBuyNowPrice > 0 && (
<Text variant="t100" secondary>
Buy now: {formatTokens(auctionBuyNowPrice, true)}
</Text>
)}
</MinimumBidWrapper>
)}
<TextField
{...register('bid', {
valueAsNumber: true,
validate: {
bidLocked: (value) => {
if (isOpenAuction && value < Number(userBid?.amount) && timeToUnlockSeconds > 0) {
return `You will be able to change your bid to a lower one after ${
userBidUnlockDate && formatDateTime(userBidUnlockDate)
}`
}
return true
},
bidTooLow: (value) =>
Number(value) >= minimumBid ? true : 'Your bid must be higher than the minimum bid',
bidTooHigh: (value) => {
return Number(value) + TRANSACTION_FEE > (accountBalance || 0)
? 'You do not have enough funds to place this bid'
: true
},
},
})}
disabled={auctionEnded}
placeholder={auctionEnded ? 'Auction ended' : 'Enter your bid'}
nodeStart={<JoyTokenIcon variant="gray" size={24} />}
nodeEnd={!!bid && <Pill variant="default" label={`${convertToUSD(bid)}`} />}
type="number"
error={!!errors.bid}
helperText={errors.bid && errors.bid.message}
onBlur={(event) => {
const { target } = event
if (Number(target.value) % 1 !== 0) {
setValue('bid', Math.floor(Number(event.target.value)))
}
}}
/>
{showBuyNowInfo && (
<BuyNowInfo variant="t100" margin={{ top: 2 }}>
Max bid cannot be more than buy now price. Bidding for amount higher than Buy now will automatically
end the auction and make you an owner of that NFT.
</BuyNowInfo>
)}
</>
) : (
<MinimumBidWrapper>
<MinimumBid>
<Text variant="h300" secondary>
Price:
</Text>
<JoyTokenIcon variant="silver" size={24} />{' '}
<Text variant="h400">{buyNowPrice || auctionBuyNowPrice}</Text>
</MinimumBid>
</MinimumBidWrapper>
)}
<Divider />
<FlexWrapper>
<Text variant="h400">Revenue split</Text>
<Information
placement="top"
text="Revenue split shows the proceedings from this sale based on royalties set up by the creator"
/>
</FlexWrapper>
<PaymentSplitWrapper>
<div>
<Text variant="h300" secondary>
Owner
</Text>
<PaymentSplitValues>
<Avatar size="bid" assetUrl={ownerMemberAvatarUrl} />
<Text variant="h400" secondary margin={{ left: 2 }}>
{ownerRoyalty}%
</Text>
</PaymentSplitValues>
</div>
<div>
<Text variant="h300" secondary>
Creator
</Text>
<PaymentSplitValues>
<Avatar size="bid" assetUrl={creatorAvatarUrl} />
<Text variant="h400" secondary margin={{ left: 2 }}>
{creatorRoyalty}%
</Text>
</PaymentSplitValues>
</div>
<div>
<Text variant="h300" secondary>
Platform
</Text>
<PaymentSplitValues>
<SvgJoystreamLogoShort height={24} viewBox="0 0 26 32" />
<Text variant="h400" secondary margin={{ left: 2 }}>
{nftPlatformFeePercentage}%
</Text>
</PaymentSplitValues>
</div>
</PaymentSplitWrapper>
<Divider />
<Text variant="h400" margin={{ bottom: 4 }}>
Price breakdown
</Text>
<Row>
<Text variant="t100" secondary color={insufficientFoundsError ? cVar('colorTextError') : undefined}>
Your balance
</Text>
{accountBalance != null ? (
<Text variant="t100" secondary color={insufficientFoundsError ? cVar('colorTextError') : undefined}>
{formatTokens(accountBalance, true)}
</Text>
) : (
<SkeletonLoader width={82} height={16} />
)}
</Row>
<Row>
<Text variant="t100" secondary>
{type === 'buy_now' || isBuyNowClicked ? 'Price' : bid ? 'Your bid' : ''}
</Text>
{(bid > 0 || isBuyNowClicked || type === 'buy_now') && (
<Text variant="t100" secondary>
{formatTokens(type !== 'buy_now' ? (isBuyNowClicked ? auctionBuyNowPrice : bid) : buyNowPrice, true)}
</Text>
)}
</Row>
{(bid || type === 'buy_now') && (
<>
<Row>
<Text variant="t100" secondary>
Transaction fee
</Text>
<Text variant="t100" secondary>
{formatTokens(TRANSACTION_FEE, true)}
</Text>
</Row>
<Row>
<Text variant="h500" secondary>
You will pay
</Text>
<Text variant="h500">
{formatTokens((type === 'buy_now' ? buyNowPrice : Number(bid) || 0) + TRANSACTION_FEE, true)}
</Text>
</Row>
</>
)}
{type === 'open_auction' && bidLockingTime && (
<Messages>
<SvgAlertsWarning24 />
<Text variant="t200" secondary margin={{ left: 2 }}>
Your bid can be withdrawn if it’s not accepted by the owner within{' '}
{formatDuration(intervalToDuration({ start: 0, end: bidLockingTime }))} from placing it.
</Text>
</Messages>
)}
{type === 'english_auction' && !isBuyNowClicked && (
<Messages>
<SvgAlertsWarning24 />
<Text variant="t200" secondary margin={{ left: 2 }}>
After placing your bid, you will not be able to withdraw it. If someone places higher bid, your bid
will be returned automatically.
</Text>
</Messages>
)}
</InnerContainer>
</PlaceBidWrapper>
</Content>
</BottomDrawer>
)
}
Example #6
Source File: FraxStake.tsx From mStable-apps with GNU Lesser General Public License v3.0 | 4 votes |
FraxStake: FC = () => {
const network = useNetwork() as MaticMainnet
const { subscribedData: userData, staticData } = useFraxStakingState()
const feederPool = useSelectedFeederPoolState()
const contract = useFraxStakingContract()
const propose = usePropose()
const yieldAPY = feederPool?.dailyApy
const sliderStart = staticData.value?.lockTimeMin
const sliderEnd = staticData.value?.lockTimeMax
const maxMultiplier = staticData.value?.lockMaxMultiplier ?? 1
const lockTimeMax = staticData.value?.lockTimeMax
const accountData = userData.value?.accountData
const poolBalance: BigDecimal | undefined = accountData?.poolBalance
const lockedStakes = accountData?.lockedStakes
const earned = accountData?.earned
const [seconds, setValue] = useState(sliderStart ?? DAY)
const [inputValue, inputFormValue, handleSetAmount] = useBigDecimalInput(poolBalance ?? '0')
const showDeposit = !!accountData?.poolBalance?.simple
const showWithdraw = lockedStakes?.length
const allowance = useTokenAllowance(network.addresses.FRAX.stakingToken, contract?.address)
const needsApprove = !inputValue || !allowance || (inputValue && allowance?.exact.lt(inputValue.exact))
const timeDifference = useMemo(() => {
const start = Date.now()
const duration = intervalToDuration({ start, end: start + seconds * 1000 })
const nonzero = Object.entries(duration)
.filter(([, v]) => v)
.map(([unit]) => unit)
return formatDuration(duration, {
format: ['years', 'months', 'weeks', 'days'].filter(i => new Set(nonzero).has(i)).slice(0, 3),
delimiter: ', ',
})
}, [seconds])
const boostMultiplier = useMemo(() => {
if (!lockTimeMax) return 1
const secs = Math.ceil(seconds)
return 1 + (secs * (maxMultiplier - 1)) / lockTimeMax
}, [maxMultiplier, lockTimeMax, seconds])
const handleWithdraw = (kekId: string): void => {
if (!contract) return
propose<Interfaces.FraxCrossChainFarm, 'withdrawLocked'>(
new TransactionManifest(contract, 'withdrawLocked', [kekId], {
present: 'Withdrawing LP token',
past: 'Withdrew LP token',
}),
)
}
const handleDeposit = (): void => {
if (!contract || !inputValue?.exact || !seconds) return
if (seconds >= DAY) {
propose<Interfaces.FraxCrossChainFarm, 'stakeLocked'>(
new TransactionManifest(contract, 'stakeLocked', [inputValue.exact, seconds], {
present: 'Staking LP token',
past: 'Staked LP token',
}),
)
}
}
const handleClaim = (): void => {
if (!contract) return
propose<Interfaces.FraxCrossChainFarm, 'getReward'>(
new TransactionManifest(contract, 'getReward', [], {
present: 'Claiming rewards',
past: 'Claimed rewards',
}),
)
}
const handleSetMax = (): void => handleSetAmount(poolBalance?.string ?? '0')
const rewards = useMemo(() => {
const stakingRewards: StakingRewardsExtended = {
stakingRewardsContract: undefined,
rewards: [
{
id: 'yield',
name: 'Yield',
apy: yieldAPY,
apyTip:
'This APY is derived from the native interest rate + current available staking rewards, and is not reflective of future rates.',
tokens: ['yield'],
priority: true,
},
{
id: 'MTA',
name: 'FRAX',
apy: 0,
apyTip: 'This APY is derived from currently available staking rewards, and is not reflective of future rates.',
tokens: ['FXS'],
priority: false,
},
{
id: 'FRAX',
name: 'FRAX',
apy: 0,
apyTip: 'This APY is derived from currently available staking rewards, and is not reflective of future rates.',
tokens: ['MTA'],
priority: false,
},
].map(v => ({ ...v, stakeLabel: undefined, balance: undefined, amounts: undefined })),
earned: undefined,
stakedBalance: undefined,
unstakedBalance: undefined,
hasStakedBalance: false,
hasUnstakedBalance: false,
}
const rewardsEarned = {
canClaim: earned?.reduce((a, b) => a.add(b.amount), BigDecimal.ZERO).exact.gt(0) ?? false,
rewards: earned?.map(({ symbol, amount }) => ({ token: symbol, earned: amount })) ?? [],
}
return { rewardsEarned, stakingRewards }
}, [earned, yieldAPY])
return (
<Container>
<StakingRewards stakingRewards={rewards.stakingRewards} />
{!!showDeposit && (
<StyledTable headerTitles={depositHeaderTitles}>
<StyledRow buttonTitle="Stake">
<TableCell width={60}>
<Input
handleSetAmount={handleSetAmount}
handleSetMax={handleSetMax}
formValue={inputFormValue}
address={network.addresses.FRAX.stakingToken}
spender={network.addresses.FRAX.stakingContract}
hideToken
/>
</TableCell>
<TableCell width={40}>
<Button disabled={needsApprove} highlighted={!needsApprove} onClick={handleDeposit}>
Stake
</Button>
</TableCell>
</StyledRow>
{!!sliderStart && !!sliderEnd && (
<LockupRow>
<TableCell>
<div>
<h4>Amount:</h4>
<span>{inputFormValue} mUSD/FRAX</span>
</div>
<div>
<h4>Lock time: </h4>
<span>{timeDifference}</span>
</div>
<div>
<h4>Boost:</h4>
<span>{boostMultiplier.toFixed(3)}x</span>
</div>
<Slider min={sliderStart} max={sliderEnd} step={DAY} value={seconds} onChange={setValue} />
</TableCell>
</LockupRow>
)}
</StyledTable>
)}
{!!showWithdraw && (
<StyledTable headerTitles={withdrawHeaderTitles} widths={TABLE_CELL_WIDTHS} width={31}>
{lockedStakes?.map(({ liquidity, lockMultiplier, endTime, startTime, kekId }) => {
const dateRange = endTime - startTime
const unlocked = endTime < Date.now()
const percentage = 100 * ((endTime - Date.now()) / dateRange)
return (
!!liquidity?.simple && (
<StyledRow key={kekId} onClick={unlocked ? () => handleWithdraw(kekId) : undefined} buttonTitle="Withdraw">
<TableCell width={TABLE_CELL_WIDTHS[0]}>
<h3>
<CountUp end={liquidity?.simple} decimals={2} />
{` mUSD/FRAX`}
</h3>
</TableCell>
<MultiplierCell width={TABLE_CELL_WIDTHS[1]}>
<span>{lockMultiplier?.simple.toFixed(3)}x</span>
</MultiplierCell>
<TableCell width={TABLE_CELL_WIDTHS[2]}>
{unlocked ? <span>Unlocked</span> : <CountdownBar percentage={percentage} end={endTime} />}
</TableCell>
</StyledRow>
)
)
})}
</StyledTable>
)}
<MultiRewards rewardsEarned={rewards.rewardsEarned} onClaimRewards={handleClaim} />
</Container>
)
}