Skip to content

Commit

Permalink
feat(earn): Add UI skeleton of earn collect (#5411)
Browse files Browse the repository at this point in the history
### Description


[Figma](https://www.figma.com/design/E1rC3MG74qEg5V4tvbeUnU/Stablecoin-Enablement?node-id=2472-14441&m=dev)

TODO in followup PR: 
* unit tests
* hook into Earn flow
* fetch real data for rewards, apy
* prepare transactions, estimate gas, execute transactions
* loading, disabled, error states.


![Screenshot_20240513_235415](https://github.com/valora-inc/wallet/assets/8432644/45d90907-3b46-471a-8e7b-bbdff640fa05)


### Test plan

Unit tests in followup PR

### Related issues


https://linear.app/valora/issue/ACT-1180/wallet-earn-collect-review-screen
  • Loading branch information
jh2oman committed May 14, 2024
1 parent 69e1e64 commit af4fd52
Show file tree
Hide file tree
Showing 6 changed files with 271 additions and 0 deletions.
9 changes: 9 additions & 0 deletions locales/base/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -2369,6 +2369,15 @@
"footer": "By depositing crypto into an Aave pool, you accept the risks associated with using Aave. <0>Terms & Conditions</0>",
"primaryCta": "Complete",
"secondaryCta": "Cancel"
},
"collect": {
"title": "Congratulations! Review & collect your earnings",
"total": "TOTAL",
"plus": "PLUS",
"rate": "Rate (est.)",
"apy": "~{{apy}}% APY",
"fee": "Gas Fee",
"cta": "Collect Earnings"
}
}
}
247 changes: 247 additions & 0 deletions src/earn/EarnCollectScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
import React from 'react'

import BigNumber from 'bignumber.js'
import { useTranslation } from 'react-i18next'
import { SafeAreaView, ScrollView, StyleSheet, Text, View } from 'react-native'
import Button, { BtnSizes } from 'src/components/Button'
import TokenDisplay from 'src/components/TokenDisplay'
import TokenIcon, { IconSize } from 'src/components/TokenIcon'
import Colors from 'src/styles/colors'
import { typeScale } from 'src/styles/fonts'
import { Spacing } from 'src/styles/styles'
import { useTokenInfo } from 'src/tokens/hooks'
import { TokenBalance } from 'src/tokens/slice'
import { NetworkId } from 'src/transactions/types'
import networkConfig from 'src/web3/networkConfig'

export default function EarnCollectScreen() {
// TODO: replace with actual token info, pool info, rewards info, pool apy, max fee amount, fee currency

const tokenInfo = useTokenInfo(networkConfig.arbUsdcTokenId)
const poolTokenInfo = useTokenInfo(networkConfig.aaveArbUsdcTokenId)
const rewardTokenInfo = useTokenInfo(`${NetworkId['arbitrum-sepolia']}:native`)
const feeCurrency = useTokenInfo(`${NetworkId['arbitrum-sepolia']}:native`)
if (!tokenInfo || !rewardTokenInfo || !feeCurrency || !poolTokenInfo) {
return null
}

const dummyRewardsInfo = [{ amount: new BigNumber(0.00003), tokenInfo: rewardTokenInfo }]
const poolApy = 3.47
const maxFeeAmount = new BigNumber(0.0001)
const onPress = () => {
// Todo handle prepared transactions
}

// TODO: Add loading state
return (
<EarnCollectComponent
tokenInfo={tokenInfo}
poolTokenInfo={poolTokenInfo}
rewardsInfo={dummyRewardsInfo}
poolApy={poolApy}
maxFeeAmount={maxFeeAmount}
feeCurrency={feeCurrency}
onPress={onPress}
/>
)
}

function EarnCollectComponent({
tokenInfo,
poolTokenInfo,
poolApy,
rewardsInfo,
maxFeeAmount,
feeCurrency,
onPress,
}: {
tokenInfo: TokenBalance
poolTokenInfo: TokenBalance
poolApy: number
maxFeeAmount: BigNumber
feeCurrency: TokenBalance
onPress: () => void
rewardsInfo: { amount: BigNumber; tokenInfo: TokenBalance }[]
}) {
const { t } = useTranslation()
return (
<SafeAreaView style={styles.container}>
<ScrollView contentContainerStyle={styles.contentContainer}>
<Text style={styles.title}>{t('earnFlow.collect.title')}</Text>
<View style={styles.collectInfoContainer}>
<CollectItem
title={t('earnFlow.collect.total')}
tokenInfo={tokenInfo}
rewardAmount={poolTokenInfo.balance}
/>
{rewardsInfo.map((info, index) => (
<CollectItem
title={t('earnFlow.collect.plus')}
key={index}
tokenInfo={info.tokenInfo}
rewardAmount={info.amount}
/>
))}
<View style={styles.separator} />
<Rate tokenInfo={poolTokenInfo} poolApy={poolApy} />
<GasFee maxFeeAmount={maxFeeAmount} feeCurrency={feeCurrency} />
</View>
</ScrollView>
<Button
style={styles.button}
size={BtnSizes.FULL}
text={t('earnFlow.collect.cta')}
onPress={onPress}
testID="EarnCollectScreen/CTA"
/>
</SafeAreaView>
)
}

function CollectItem({
tokenInfo,
rewardAmount,
title,
}: {
tokenInfo: TokenBalance
rewardAmount: BigNumber
title: string
}) {
return (
<>
<Text style={styles.collectItemTitle}>{title}</Text>
<View style={styles.row}>
<View style={styles.iconContainer}>
<TokenIcon token={tokenInfo} size={IconSize.MEDIUM} />
</View>
<View>
<TokenDisplay
style={styles.cryptoText}
tokenId={tokenInfo.tokenId}
amount={rewardAmount}
showLocalAmount={false}
/>
<TokenDisplay
style={styles.fiatText}
tokenId={tokenInfo.tokenId}
amount={rewardAmount}
showLocalAmount={true}
/>
</View>
</View>
</>
)
}

function Rate({ tokenInfo, poolApy }: { tokenInfo: TokenBalance; poolApy: number }) {
const { t } = useTranslation()
return (
<View>
<Text style={styles.rateText}>{t('earnFlow.collect.rate')}</Text>
<View style={styles.row}>
<TokenIcon token={tokenInfo} size={IconSize.SMALL} />
<View style={styles.apyContainer}>
<Text style={styles.apyText}>
{t('earnFlow.activePools.apy', {
apy: poolApy,
})}
</Text>
</View>
</View>
</View>
)
}

function GasFee({
maxFeeAmount,
feeCurrency,
}: {
maxFeeAmount: BigNumber
feeCurrency: TokenBalance
}) {
const { t } = useTranslation()

return (
<View>
<Text style={styles.rateText}>{t('earnFlow.collect.fee')}</Text>
<TokenDisplay
style={styles.apyText}
tokenId={feeCurrency.tokenId}
amount={maxFeeAmount}
showLocalAmount={false}
/>
<TokenDisplay
style={styles.gasFeeFiat}
tokenId={feeCurrency.tokenId}
amount={maxFeeAmount}
showLocalAmount={true}
/>
</View>
)
}

const styles = StyleSheet.create({
contentContainer: {
padding: Spacing.Thick24,
},
container: {
flex: 1,
justifyContent: 'space-between',
},
title: {
...typeScale.titleMedium,
marginBottom: Spacing.Thick24,
},
collectInfoContainer: {
padding: Spacing.Regular16,
borderColor: Colors.gray2,
borderWidth: 1,
borderRadius: 16,
backgroundColor: Colors.gray1,
},
row: {
flexDirection: 'row',
gap: Spacing.Smallest8,
marginBottom: Spacing.Regular16,
},
cryptoText: {
...typeScale.labelSemiBoldLarge,
color: Colors.black,
},
fiatText: {
...typeScale.bodySmall,
color: Colors.gray4,
},
collectItemTitle: {
...typeScale.labelSemiBoldXSmall,
color: Colors.black,
marginBottom: Spacing.Smallest8,
},
separator: {
marginBottom: Spacing.Regular16,
borderBottomWidth: 1,
borderBottomColor: Colors.gray2,
},
apyContainer: {
flexDirection: 'row',
alignItems: 'center',
},
rateText: {
...typeScale.bodySmall,
color: Colors.gray4,
},
iconContainer: {
flexDirection: 'row',
alignItems: 'center',
},
apyText: {
...typeScale.labelSemiBoldSmall,
},
gasFeeFiat: {
...typeScale.bodyXSmall,
color: Colors.gray4,
},
button: {
padding: Spacing.Thick24,
},
})
12 changes: 12 additions & 0 deletions src/navigator/Navigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import DappKitAccountScreen from 'src/dappkit/DappKitAccountScreen'
import DappKitSignTxScreen from 'src/dappkit/DappKitSignTxScreen'
import DappShortcutTransactionRequest from 'src/dapps/DappShortcutTransactionRequest'
import DappShortcutsRewards from 'src/dapps/DappShortcutsRewards'
import EarnCollectScreen from 'src/earn/EarnCollectScreen'
import EscrowedPaymentListScreen from 'src/escrow/EscrowedPaymentListScreen'
import ReclaimPaymentConfirmationScreen from 'src/escrow/ReclaimPaymentConfirmationScreen'
import BidaliScreen from 'src/fiatExchanges/BidaliScreen'
Expand Down Expand Up @@ -533,6 +534,16 @@ const generalScreens = (Navigator: typeof Stack) => (
</>
)

const earnScreens = (Navigator: typeof Stack) => (
<>
<Navigator.Screen
name={Screens.EarnCollectScreen}
component={EarnCollectScreen}
options={headerWithBackButton}
/>
</>
)

const swapScreens = (Navigator: typeof Stack) => (
<>
<Navigator.Screen name={Screens.SwapScreenWithBack} component={SwapScreen} options={noHeader} />
Expand Down Expand Up @@ -634,6 +645,7 @@ export function MainStackScreen() {
{settingsScreens(Stack)}
{generalScreens(Stack)}
{swapScreens(Stack)}
{earnScreens(Stack)}
{nftScreens(Stack)}
{assetScreens(Stack)}
{pointsScreens(Stack)}
Expand Down
1 change: 1 addition & 0 deletions src/navigator/Screens.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export enum Screens {
DappShortcutsRewards = 'DappShortcutsRewards',
DappShortcutTransactionRequest = 'DappShortcutTransactionRequest',
Debug = 'Debug',
EarnCollectScreen = 'EarnCollectScreen',
EnableBiometry = 'EnableBiometry',
ErrorScreen = 'ErrorScreen',
EscrowedPaymentListScreen = 'EscrowedPaymentListScreen',
Expand Down
1 change: 1 addition & 0 deletions src/navigator/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export type StackParamList = {
rewardId: string
}
[Screens.Debug]: undefined
[Screens.EarnCollectScreen]: undefined
[Screens.ErrorScreen]: {
errorMessage?: string
}
Expand Down
1 change: 1 addition & 0 deletions test/RootStateSchema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2803,6 +2803,7 @@
"DappShortcutTransactionRequest",
"DappShortcutsRewards",
"Debug",
"EarnCollectScreen",
"EnableBiometry",
"ErrorScreen",
"EscrowedPaymentListScreen",
Expand Down

0 comments on commit af4fd52

Please sign in to comment.