Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/assets/translations/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -2802,6 +2802,7 @@
"depositsDisabledDescription": "Deposits are temporarily unavailable for this yield opportunity.",
"withdrawalsDisabled": "Withdrawals Disabled",
"withdrawalsDisabledDescription": "Withdrawals are temporarily unavailable for this yield opportunity.",
"noActiveBalanceToExit": "No active balance available to unstake.",
"noAvailableYields": "No yield opportunities available for your assets",
"connectWalletAvailable": "Connect a wallet to see yields available for your assets",
"aboutProvider": "About %{provider}",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,11 @@ export const EarnConfirm = memo(() => {

{selectedYield && (
<Box mt={4}>
<YieldExplainers selectedYield={selectedYield} sellAssetSymbol={sellAsset?.symbol} />
<YieldExplainers
selectedYield={selectedYield}
sellAssetSymbol={sellAsset?.symbol}
action='enter'
/>
</Box>
)}

Expand Down
1 change: 1 addition & 0 deletions src/pages/Yields/components/YieldEnterModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,7 @@ export const YieldEnterModal = memo(
<YieldExplainers
selectedYield={yieldItem}
sellAssetSymbol={inputTokenAsset?.symbol}
action='enter'
/>
{stepsToShow.length > 0 && <TransactionStepsList steps={stepsToShow} />}
</Flex>
Expand Down
112 changes: 66 additions & 46 deletions src/pages/Yields/components/YieldExplainers.tsx
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whitespaces off!

Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const infoIcon = <InfoIcon color='text.subtle' />
type ExplainerItem = {
icon: ReactNode
textKey: string
relevance: 'enter' | 'exit' | 'both'
}

const getYieldExplainers = (selectedYield: AugmentedYieldDto): ExplainerItem[] => {
Expand All @@ -29,31 +30,40 @@ const getYieldExplainers = (selectedYield: AugmentedYieldDto): ExplainerItem[] =
textKey: outputTokenSymbol
? 'earn.explainers.liquidStakingReceive'
: 'earn.explainers.liquidStakingTrade',
relevance: 'enter' as const,
},
{ icon: giftIcon, textKey: 'earn.explainers.rewardsSchedule', relevance: 'enter' as const },
{
icon: infoIcon,
textKey: 'earn.explainers.liquidStakingWithdraw',
relevance: 'both' as const,
},
{ icon: giftIcon, textKey: 'earn.explainers.rewardsSchedule' },
{ icon: infoIcon, textKey: 'earn.explainers.liquidStakingWithdraw' },
]
case 'native-staking':
case 'pooled-staking':
case 'staking':
return [
{ icon: giftIcon, textKey: 'earn.explainers.rewardsSchedule' },
{ icon: infoIcon, textKey: 'earn.explainers.stakingUnbonding' },
{ icon: giftIcon, textKey: 'earn.explainers.rewardsSchedule', relevance: 'enter' as const },
{ icon: infoIcon, textKey: 'earn.explainers.stakingUnbonding', relevance: 'both' as const },
]
case 'restaking':
return [
{ icon: giftIcon, textKey: 'earn.explainers.restakingYield' },
{ icon: infoIcon, textKey: 'earn.explainers.restakingWithdraw' },
{ icon: giftIcon, textKey: 'earn.explainers.restakingYield', relevance: 'enter' as const },
{
icon: infoIcon,
textKey: 'earn.explainers.restakingWithdraw',
relevance: 'both' as const,
},
]
case 'vault':
return [
{ icon: giftIcon, textKey: 'earn.explainers.vaultYield' },
{ icon: infoIcon, textKey: 'earn.explainers.vaultWithdraw' },
{ icon: giftIcon, textKey: 'earn.explainers.vaultYield', relevance: 'enter' as const },
{ icon: infoIcon, textKey: 'earn.explainers.vaultWithdraw', relevance: 'both' as const },
]
case 'lending':
return [
{ icon: giftIcon, textKey: 'earn.explainers.lendingYield' },
{ icon: infoIcon, textKey: 'earn.explainers.lendingWithdraw' },
{ icon: giftIcon, textKey: 'earn.explainers.lendingYield', relevance: 'enter' as const },
{ icon: infoIcon, textKey: 'earn.explainers.lendingWithdraw', relevance: 'both' as const },
]
default:
return []
Expand All @@ -63,48 +73,58 @@ const getYieldExplainers = (selectedYield: AugmentedYieldDto): ExplainerItem[] =
type YieldExplainersProps = {
selectedYield: AugmentedYieldDto
sellAssetSymbol?: string
action: 'enter' | 'exit' | 'claim'
}

export const YieldExplainers = memo(({ selectedYield, sellAssetSymbol }: YieldExplainersProps) => {
const translate = useTranslate()
export const YieldExplainers = memo(
({ selectedYield, sellAssetSymbol, action }: YieldExplainersProps) => {
const translate = useTranslate()

const explainers = useMemo(() => getYieldExplainers(selectedYield), [selectedYield])
const actionRelevance = action === 'enter' ? 'enter' : 'exit'
const explainers = useMemo(
() =>
getYieldExplainers(selectedYield).filter(
e => e.relevance === actionRelevance || e.relevance === 'both',
),
[selectedYield, actionRelevance],
)

const rewardSchedule = selectedYield.mechanics.rewardSchedule
const outputSymbol = selectedYield.outputToken?.symbol
const rewardSchedule = selectedYield.mechanics.rewardSchedule
const outputSymbol = selectedYield.outputToken?.symbol

const cooldownDays = useMemo(() => {
const seconds = selectedYield.mechanics.cooldownPeriod?.seconds
if (!seconds) return undefined
return Math.ceil(seconds / 86400)
}, [selectedYield.mechanics.cooldownPeriod?.seconds])
const cooldownDays = useMemo(() => {
const seconds = selectedYield.mechanics.cooldownPeriod?.seconds
if (!seconds) return undefined
return Math.ceil(seconds / 86400)
}, [selectedYield.mechanics.cooldownPeriod?.seconds])

const symbol = outputSymbol ?? sellAssetSymbol ?? ''
const symbol = outputSymbol ?? sellAssetSymbol ?? ''

const translatedExplainers = useMemo(() => {
if (explainers.length === 0) return []
return explainers.map(explainer => ({
icon: explainer.icon,
text: translate(explainer.textKey, {
symbol,
schedule: rewardSchedule ?? '',
days: cooldownDays ?? '',
}),
}))
}, [explainers, translate, symbol, rewardSchedule, cooldownDays])
const translatedExplainers = useMemo(() => {
if (explainers.length === 0) return []
return explainers.map(explainer => ({
icon: explainer.icon,
text: translate(explainer.textKey, {
symbol,
schedule: rewardSchedule ?? '',
days: cooldownDays ?? '',
}),
}))
}, [explainers, translate, symbol, rewardSchedule, cooldownDays])

if (translatedExplainers.length === 0) return null
if (translatedExplainers.length === 0) return null

return (
<VStack spacing={3} align='stretch'>
{translatedExplainers.map((explainer, index) => (
<HStack key={index} spacing={3} align='flex-start'>
<Box mt={0.5}>{explainer.icon}</Box>
<Text fontSize='sm' color='text.subtle'>
{explainer.text}
</Text>
</HStack>
))}
</VStack>
)
})
return (
<VStack spacing={3} align='stretch'>
{translatedExplainers.map((explainer, index) => (
<HStack key={index} spacing={3} align='flex-start'>
<Box mt={0.5}>{explainer.icon}</Box>
<Text fontSize='sm' color='text.subtle'>
{explainer.text}
</Text>
</HStack>
))}
</VStack>
)
},
)
41 changes: 25 additions & 16 deletions src/pages/Yields/components/YieldForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
Icon,
Skeleton,
Text,
VStack,
} from '@chakra-ui/react'
import type { AccountId } from '@shapeshiftoss/caip'
import { useQueryClient } from '@tanstack/react-query'
Expand Down Expand Up @@ -515,23 +516,27 @@ export const YieldForm = memo(

const statsContent = useMemo(
() => (
<Box
<VStack
spacing={3}
align='stretch'
bg='background.surface.raised.base'
borderRadius='xl'
p={4}
borderWidth='1px'
borderColor='border.base'
>
<Flex justify='space-between' align='center'>
<Text fontSize='sm' color='text.subtle'>
{translate('yieldXYZ.currentApy')}
</Text>
<GradientApy fontSize='sm' fontWeight='bold'>
{apyDisplay}
</GradientApy>
</Flex>
{hasAmount && (
<Flex justify='space-between' align='center' mt={3}>
{action === 'enter' && (
<Flex justify='space-between' align='center'>
<Text fontSize='sm' color='text.subtle'>
{translate('yieldXYZ.currentApy')}
</Text>
<GradientApy fontSize='sm' fontWeight='bold'>
{apyDisplay}
</GradientApy>
</Flex>
)}
{action === 'enter' && hasAmount && (
<Flex justify='space-between' align='center'>
<Text fontSize='sm' color='text.subtle'>
{translate('yieldXYZ.estYearlyEarnings')}
</Text>
Expand All @@ -546,7 +551,7 @@ export const YieldForm = memo(
</Flex>
)}
{isStaking && maybeSelectedValidatorMetadata && (
<Flex justify='space-between' align='center' mt={3}>
<Flex justify='space-between' align='center'>
<Text fontSize='sm' color='text.subtle'>
{translate('yieldXYZ.validator')}
</Text>
Expand All @@ -563,7 +568,7 @@ export const YieldForm = memo(
</Flex>
)}
{(!isStaking || !maybeSelectedValidatorMetadata) && maybeProviderMetadata && (
<Flex justify='space-between' align='center' mt={3}>
<Flex justify='space-between' align='center'>
<Text fontSize='sm' color='text.subtle'>
{translate('yieldXYZ.provider')}
</Text>
Expand All @@ -580,7 +585,7 @@ export const YieldForm = memo(
</Flex>
)}
{minDeposit && bnOrZero(minDeposit).gt(0) && action === 'enter' && (
<Flex justify='space-between' align='center' mt={3}>
<Flex justify='space-between' align='center'>
<Text fontSize='sm' color='text.subtle'>
{translate(getYieldMinAmountKey(yieldItem.mechanics.type))}
</Text>
Expand All @@ -593,7 +598,7 @@ export const YieldForm = memo(
</Text>
</Flex>
)}
</Box>
</VStack>
),
[
translate,
Expand Down Expand Up @@ -757,7 +762,11 @@ export const YieldForm = memo(
)}
{!isClaimAction && statsContent}
{!isClaimAction && (
<YieldExplainers selectedYield={yieldItem} sellAssetSymbol={inputTokenAsset?.symbol} />
<YieldExplainers
selectedYield={yieldItem}
sellAssetSymbol={inputTokenAsset?.symbol}
action={action}
/>
)}
{stepsToShow.length > 0 && <TransactionStepsList steps={stepsToShow} />}
</Flex>
Expand Down
45 changes: 27 additions & 18 deletions src/pages/Yields/components/YieldPositionCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
HStack,
Skeleton,
Text,
Tooltip,
VStack,
} from '@chakra-ui/react'
import { fromAccountId } from '@shapeshiftoss/caip'
Expand Down Expand Up @@ -105,6 +106,17 @@ export const YieldPositionCard = memo(
const withdrawableBalance = balancesByType?.[YieldBalanceType.Withdrawable]
const claimableBalance = balancesByType?.[YieldBalanceType.Claimable]

const hasActiveBalance = Boolean(
activeBalance && bnOrZero(activeBalance.aggregatedAmount).gt(0),
)

const isExitDisabled = !yieldItem.status.exit || !hasActiveBalance

const exitDisabledTitle = useMemo(() => {
if (!yieldItem.status.exit) return translate('yieldXYZ.withdrawalsDisabledDescription')
if (!hasActiveBalance) return translate('yieldXYZ.noActiveBalanceToExit')
}, [hasActiveBalance, translate, yieldItem.status.exit])

const claimAction = useMemo(
() =>
claimableBalance?.pendingActions?.find(action =>
Expand Down Expand Up @@ -505,24 +517,21 @@ export const YieldPositionCard = memo(
{enterLabel}
</Button>
{hasAnyPosition && (
<Button
leftIcon={exitIcon}
variant='outline'
size='lg'
height={12}
borderRadius='xl'
onClick={handleExit}
flex={1}
fontWeight='bold'
isDisabled={!yieldItem.status.exit}
title={
!yieldItem.status.exit
? translate('yieldXYZ.withdrawalsDisabledDescription')
: undefined
}
>
{exitLabel}
</Button>
<Tooltip label={exitDisabledTitle} isDisabled={!isExitDisabled} hasArrow>
<Button
leftIcon={exitIcon}
variant='outline'
size='lg'
height={12}
borderRadius='xl'
onClick={handleExit}
flex={1}
fontWeight='bold'
isDisabled={isExitDisabled}
>
{exitLabel}
</Button>
</Tooltip>
)}
</HStack>
</Display.Desktop>
Expand Down