diff --git a/locales/base/translation.json b/locales/base/translation.json
index 5bacd247aa5..9d6a5534aac 100644
--- a/locales/base/translation.json
+++ b/locales/base/translation.json
@@ -1341,6 +1341,12 @@
"rewards": {
"title": "Your rewards",
"description": "Claim available rewards from open dapp positions"
+ },
+ "claimRewardsScreen": {
+ "title": "Your rewards",
+ "description": "Now you can claim rewards from open dapp positions directly in {{appName}}!",
+ "claimButton": "Claim",
+ "rewardLabel": "Available reward"
}
},
"dappsScreenHelpDialog": {
diff --git a/src/dapps/DappShortcutsRewards.test.tsx b/src/dapps/DappShortcutsRewards.test.tsx
new file mode 100644
index 00000000000..750677ef796
--- /dev/null
+++ b/src/dapps/DappShortcutsRewards.test.tsx
@@ -0,0 +1,74 @@
+import { render, within } from '@testing-library/react-native'
+import React from 'react'
+import { Provider } from 'react-redux'
+import DappShortcutsRewards from 'src/dapps/DappShortcutsRewards'
+import { createMockStore } from 'test/utils'
+import { mockCusdAddress, mockPositions, mockShortcuts } from 'test/values'
+
+jest.mock('src/statsig', () => ({
+ getFeatureGate: jest.fn(() => true),
+}))
+
+const mockCeloAddress = '0x471ece3750da237f93b8e339c536989b8978a438'
+const mockUbeAddress = '0x00be915b9dcf56a3cbe739d9b9c202ca692409ec'
+
+describe('DappShortcutsRewards', () => {
+ beforeEach(() => {
+ jest.clearAllMocks()
+ })
+
+ it('should render claimable rewards correctly', () => {
+ const { getByText, getAllByTestId } = render(
+
+
+
+ )
+
+ expect(getByText('dappShortcuts.claimRewardsScreen.title')).toBeTruthy()
+ expect(getByText('dappShortcuts.claimRewardsScreen.description')).toBeTruthy()
+ expect(getAllByTestId('DappShortcutsRewards/Card').length).toBe(1)
+
+ const rewardCard = getAllByTestId('DappShortcutsRewards/Card')[0]
+ expect(within(rewardCard).getByTestId('DappShortcutsRewards/RewardAmount')).toHaveTextContent(
+ '0.098 UBE, 0.95 CELO'
+ )
+ expect(
+ within(rewardCard).getByTestId('DappShortcutsRewards/RewardAmountFiat')
+ ).toHaveTextContent('₱0.88') // USD value $0.66, mocked exchange rate 1.33
+ expect(within(rewardCard).getByTestId('DappShortcutsRewards/ClaimButton')).toBeTruthy()
+ })
+})
diff --git a/src/dapps/DappShortcutsRewards.tsx b/src/dapps/DappShortcutsRewards.tsx
new file mode 100644
index 00000000000..41d19611864
--- /dev/null
+++ b/src/dapps/DappShortcutsRewards.tsx
@@ -0,0 +1,171 @@
+import { BigNumber } from 'bignumber.js'
+import React from 'react'
+import { useTranslation } from 'react-i18next'
+import { Image, StyleSheet, Text, View } from 'react-native'
+import Animated from 'react-native-reanimated'
+import { useSafeAreaInsets } from 'react-native-safe-area-context'
+import { useSelector } from 'react-redux'
+import Button, { BtnSizes } from 'src/components/Button'
+import TokenDisplay from 'src/components/TokenDisplay'
+import { positionsWithClaimableRewardsSelector } from 'src/positions/selectors'
+import { ClaimablePosition } from 'src/positions/types'
+import Colors from 'src/styles/colors'
+import fontStyles from 'src/styles/fonts'
+import { Spacing } from 'src/styles/styles'
+import { Currency } from 'src/utils/currencies'
+
+function DappShortcutsRewards() {
+ const { t } = useTranslation()
+ const insets = useSafeAreaInsets()
+
+ const positionsWithClaimableRewards = useSelector(positionsWithClaimableRewardsSelector)
+
+ const handleClaimReward = (position: ClaimablePosition) => () => {
+ // do something
+ }
+
+ const renderItem = ({ item }: { item: ClaimablePosition }) => {
+ let claimableValueUsd = new BigNumber(0)
+ item.claimableShortcut.claimableTokens.forEach((token) => {
+ claimableValueUsd = claimableValueUsd.plus(
+ BigNumber(token.priceUsd).times(BigNumber(token.balance))
+ )
+ })
+
+ return (
+
+
+
+
+ {t('dappShortcuts.claimRewardsScreen.rewardLabel')}
+
+
+
+ {item.claimableShortcut.claimableTokens.map((token, index) => (
+
+ {index > 0 && ', '}
+
+
+ ))}
+
+ {claimableValueUsd && (
+
+ )}
+
+
+
+
+
+ {item.appName}
+
+
+ )
+ }
+
+ const renderHeader = () => {
+ return (
+
+ {t('dappShortcuts.claimRewardsScreen.title')}
+ {t('dappShortcuts.claimRewardsScreen.description')}
+
+ )
+ }
+
+ return (
+ <>
+
+ >
+ )
+}
+
+const styles = StyleSheet.create({
+ card: {
+ borderWidth: 1,
+ borderColor: Colors.gray2,
+ borderRadius: 12,
+ },
+ rewardInfoContainer: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ paddingHorizontal: Spacing.Regular16,
+ paddingVertical: Spacing.Small12,
+ },
+ rewardAmountContainer: {
+ flex: 1,
+ marginRight: Spacing.Small12,
+ },
+ rewardLabel: {
+ ...fontStyles.xsmall,
+ color: Colors.gray3,
+ },
+ rewardAmount: {
+ ...fontStyles.large600,
+ lineHeight: 28,
+ flexWrap: 'wrap',
+ },
+ rewardFiatAmount: {
+ ...fontStyles.small,
+ },
+ dappInfoContainer: {
+ flexDirection: 'row',
+ paddingHorizontal: Spacing.Regular16,
+ paddingVertical: Spacing.Small12,
+ backgroundColor: Colors.gray1,
+ borderBottomLeftRadius: 12,
+ borderBottomRightRadius: 12,
+ },
+ dappLogo: {
+ width: 18,
+ height: 18,
+ marginRight: Spacing.Smallest8,
+ backgroundColor: Colors.light,
+ borderRadius: 100,
+ },
+ dappName: {
+ ...fontStyles.small600,
+ },
+ headerContainer: {
+ paddingTop: Spacing.Smallest8,
+ paddingBottom: Spacing.Thick24,
+ },
+ heading: {
+ ...fontStyles.large600,
+ fontSize: 24,
+ lineHeight: 32,
+ marginBottom: Spacing.Tiny4,
+ },
+ subHeading: {
+ ...fontStyles.small,
+ color: Colors.gray3,
+ },
+ claimButton: {
+ minWidth: 72,
+ },
+})
+
+export default DappShortcutsRewards
diff --git a/src/dappsExplorer/DappFeaturedActions.tsx b/src/dappsExplorer/DappFeaturedActions.tsx
index f05b8858f68..e390e8018c1 100644
--- a/src/dappsExplorer/DappFeaturedActions.tsx
+++ b/src/dappsExplorer/DappFeaturedActions.tsx
@@ -7,6 +7,8 @@ import Touchable from 'src/components/Touchable'
import { mostPopularDappsSelector } from 'src/dapps/selectors'
import Trophy from 'src/icons/Trophy'
import Wallet from 'src/icons/Wallet'
+import { navigate } from 'src/navigator/NavigationService'
+import { Screens } from 'src/navigator/Screens'
import { positionsWithClaimableRewardsSelector } from 'src/positions/selectors'
import { getExperimentParams, getFeatureGate } from 'src/statsig'
import { ExperimentConfigs } from 'src/statsig/constants'
@@ -58,7 +60,7 @@ export function DappFeaturedActions({
// TODO impression analytics on scroll
const handleShowRewardsShortcuts = () => {
- // TODO
+ navigate(Screens.DappShortcutsRewards)
}
const scrollEnabled = showDappRankings && showClaimRewards // more than one item in the view
diff --git a/src/navigator/Navigator.tsx b/src/navigator/Navigator.tsx
index ebf385d7f54..24fc6126965 100644
--- a/src/navigator/Navigator.tsx
+++ b/src/navigator/Navigator.tsx
@@ -4,8 +4,8 @@ import {
useBottomSheetDynamicSnapPoints,
} from '@gorhom/bottom-sheet'
import {
- NativeStackNavigationOptions,
createNativeStackNavigator,
+ NativeStackNavigationOptions,
} from '@react-navigation/native-stack'
import { createBottomSheetNavigator } from '@th3rdwave/react-navigation-bottom-sheet'
import * as React from 'react'
@@ -31,11 +31,21 @@ import BackupQuiz, { navOptionsForQuiz } from 'src/backup/BackupQuiz'
import ConsumerIncentivesHomeScreen from 'src/consumerIncentives/ConsumerIncentivesHomeScreen'
import DappKitAccountScreen from 'src/dappkit/DappKitAccountScreen'
import DappKitSignTxScreen from 'src/dappkit/DappKitSignTxScreen'
+import DappShortcutsRewards from 'src/dapps/DappShortcutsRewards'
import EscrowedPaymentListScreen from 'src/escrow/EscrowedPaymentListScreen'
import ReclaimPaymentConfirmationScreen from 'src/escrow/ReclaimPaymentConfirmationScreen'
import WithdrawCeloQrScannerScreen from 'src/exchange/WithdrawCeloQrScannerScreen'
import WithdrawCeloReviewScreen from 'src/exchange/WithdrawCeloReviewScreen'
import WithdrawCeloScreen from 'src/exchange/WithdrawCeloScreen'
+import FiatDetailsScreen from 'src/fiatconnect/FiatDetailsScreen'
+import KycDenied from 'src/fiatconnect/kyc/KycDenied'
+import KycExpired from 'src/fiatconnect/kyc/KycExpired'
+import KycPending from 'src/fiatconnect/kyc/KycPending'
+import KycLanding from 'src/fiatconnect/KycLanding'
+import FiatConnectLinkAccountScreen from 'src/fiatconnect/LinkAccountScreen'
+import FiatConnectRefetchQuoteScreen from 'src/fiatconnect/RefetchQuoteScreen'
+import FiatConnectReviewScreen from 'src/fiatconnect/ReviewScreen'
+import FiatConnectTransferStatusScreen from 'src/fiatconnect/TransferStatusScreen'
import BidaliScreen from 'src/fiatExchanges/BidaliScreen'
import CashInSuccess from 'src/fiatExchanges/CashInSuccess'
import CoinbasePayScreen from 'src/fiatExchanges/CoinbasePayScreen'
@@ -51,15 +61,6 @@ import SelectProviderScreen from 'src/fiatExchanges/SelectProvider'
import SimplexScreen from 'src/fiatExchanges/SimplexScreen'
import Spend, { spendScreenOptions } from 'src/fiatExchanges/Spend'
import WithdrawSpend from 'src/fiatExchanges/WithdrawSpend'
-import FiatDetailsScreen from 'src/fiatconnect/FiatDetailsScreen'
-import KycLanding from 'src/fiatconnect/KycLanding'
-import FiatConnectLinkAccountScreen from 'src/fiatconnect/LinkAccountScreen'
-import FiatConnectRefetchQuoteScreen from 'src/fiatconnect/RefetchQuoteScreen'
-import FiatConnectReviewScreen from 'src/fiatconnect/ReviewScreen'
-import FiatConnectTransferStatusScreen from 'src/fiatconnect/TransferStatusScreen'
-import KycDenied from 'src/fiatconnect/kyc/KycDenied'
-import KycExpired from 'src/fiatconnect/kyc/KycExpired'
-import KycPending from 'src/fiatconnect/kyc/KycPending'
import { currentLanguageSelector } from 'src/i18n/selectors'
import PhoneNumberLookupQuotaScreen from 'src/identity/PhoneNumberLookupQuotaScreen'
import ImportWallet from 'src/import/ImportWallet'
@@ -78,9 +79,9 @@ import {
noHeaderGestureDisabled,
nuxNavigationOptions,
} from 'src/navigator/Headers'
+import { getInitialRoute } from 'src/navigator/initialRoute'
import QRNavigator from 'src/navigator/QRNavigator'
import { Screens } from 'src/navigator/Screens'
-import { getInitialRoute } from 'src/navigator/initialRoute'
import { StackParamList } from 'src/navigator/types'
import NftsInfoCarousel from 'src/nfts/NftsInfoCarousel'
import ChooseYourAdventure from 'src/onboarding/ChooseYourAdventure'
@@ -317,6 +318,11 @@ const consumerIncentivesScreens = (Navigator: typeof Stack) => (
component={ConsumerIncentivesHomeScreen}
options={ConsumerIncentivesHomeScreen.navOptions}
/>
+
>
)
diff --git a/src/navigator/Screens.tsx b/src/navigator/Screens.tsx
index 38a0527605c..3d0831073b9 100644
--- a/src/navigator/Screens.tsx
+++ b/src/navigator/Screens.tsx
@@ -16,6 +16,7 @@ export enum Screens {
DappKitAccountScreen = 'DappKitAccountScreen',
DappKitSignTxScreen = 'DappKitSignTxScreen',
DAppsExplorerScreen = 'DAppsExplorerScreen',
+ DappShortcutsRewards = 'DappShortcutsRewards',
Debug = 'Debug',
DrawerNavigator = 'DrawerNavigator',
EnableBiometry = 'EnableBiometry',
diff --git a/src/navigator/types.tsx b/src/navigator/types.tsx
index 3d39e1518a4..9d8fb6d0106 100644
--- a/src/navigator/types.tsx
+++ b/src/navigator/types.tsx
@@ -89,6 +89,7 @@ export type StackParamList = {
dappKitRequest: SignTxRequest
}
[Screens.DAppsExplorerScreen]: undefined
+ [Screens.DappShortcutsRewards]: undefined
[Screens.Debug]: undefined
[Screens.DrawerNavigator]: {
initialScreen?: Screens
diff --git a/src/styles/styles.ts b/src/styles/styles.ts
index 2feb5095e3b..33f518393d1 100644
--- a/src/styles/styles.ts
+++ b/src/styles/styles.ts
@@ -3,6 +3,7 @@ import { StyleSheet } from 'react-native'
const BASE_UNIT = 8
export enum Spacing {
+ Tiny4 = BASE_UNIT / 2,
Smallest8 = BASE_UNIT,
Small12 = BASE_UNIT * 1.5,
Regular16 = BASE_UNIT * 2,
diff --git a/test/RootStateSchema.json b/test/RootStateSchema.json
index 4cbece95193..b6fa313a05f 100644
--- a/test/RootStateSchema.json
+++ b/test/RootStateSchema.json
@@ -2337,6 +2337,7 @@
"DAppsExplorerScreen",
"DappKitAccountScreen",
"DappKitSignTxScreen",
+ "DappShortcutsRewards",
"Debug",
"DrawerNavigator",
"EnableBiometry",
diff --git a/test/values.ts b/test/values.ts
index 99b85e20039..6152df608d7 100644
--- a/test/values.ts
+++ b/test/values.ts
@@ -1183,7 +1183,7 @@ export const mockPositions: Position[] = [
symbol: 'CELO',
decimals: 18,
priceUsd: '0.6959536890241361',
- balance: '0.950545800159603456',
+ balance: '0.950545800159603456', // total USD value = priceUsd * balance = $0.66
category: 'claimable',
},
{
@@ -1208,7 +1208,7 @@ export const mockPositions: Position[] = [
category: 'claimable',
decimals: 18,
network: 'celo',
- balance: '0.098322815093446616',
+ balance: '0.098322815093446616', // total USD value = priceUsd * balance = $0.00009
symbol: 'UBE',
address: '0x00be915b9dcf56a3cbe739d9b9c202ca692409ec',
},