From 0df5aacc55a1998ff1c9b5c1a004a5b43b7b6845 Mon Sep 17 00:00:00 2001 From: MuckT Date: Thu, 12 Oct 2023 17:40:33 -0700 Subject: [PATCH] feat: more actions token bottom sheet --- locales/base/translation.json | 7 ++- src/tokens/MoreActionsBottomSheet.tsx | 75 +++++++++++++++++++++++++++ src/tokens/TokenDetails.tsx | 62 +++++++++++++++------- 3 files changed, 123 insertions(+), 21 deletions(-) create mode 100644 src/tokens/MoreActionsBottomSheet.tsx diff --git a/locales/base/translation.json b/locales/base/translation.json index d4a252eca64..74d4ea863d9 100644 --- a/locales/base/translation.json +++ b/locales/base/translation.json @@ -1845,10 +1845,15 @@ "priceDeltaSuffix": "Today", "actions": { "send": "Send", + "sendDetails": "Use any Celo or Ethereum address, domain, phone number, etc.", "swap": "Swap", + "swapDetails": "Swap your tokens without leaving your wallet", "add": "Add", + "addDetails": "Buy tokens using one of our trusted providers", "withdraw": "Withdraw", + "withdrawDetails": "Transfer tokens to a bank account, mobile money, gift card etc.", "more": "More" - } + }, + "moreActions": "More Actions" } } diff --git a/src/tokens/MoreActionsBottomSheet.tsx b/src/tokens/MoreActionsBottomSheet.tsx new file mode 100644 index 00000000000..e7093b30dd4 --- /dev/null +++ b/src/tokens/MoreActionsBottomSheet.tsx @@ -0,0 +1,75 @@ +import React, { RefObject } from 'react' +import { useTranslation } from 'react-i18next' +import { StyleSheet, Text, View } from 'react-native' +import BottomSheet, { BottomSheetRefType } from 'src/components/BottomSheet' +import Touchable from 'src/components/Touchable' +import Colors from 'src/styles/colors' +import { typeScale } from 'src/styles/fonts' +import { Spacing } from 'src/styles/styles' +import { useActions } from 'src/tokens/TokenDetails' +import { TokenBalance } from 'src/tokens/slice' + +export default function MoreActionsBottomSheet({ + forwardedRef, + token, +}: { + forwardedRef: RefObject + token: TokenBalance +}) { + const { t } = useTranslation() + const actions = useActions(token) + return ( + + + {actions.map((action) => { + return ( + + + + + + {action.text} + {action.details} + + + + + ) + })} + + + ) +} + +const styles = StyleSheet.create({ + actionsContainer: { + gap: Spacing.Regular16, + }, + actionContainer: { + backgroundColor: Colors.gray1, + borderRadius: 20, + }, + touchable: { + margin: Spacing.Regular16, + }, + touchableView: { + flexDirection: 'row', + gap: Spacing.Regular16, + alignItems: 'center', + }, + actionTitle: { + ...typeScale.labelMedium, + }, + actionDetails: { + ...typeScale.bodySmall, + }, +}) diff --git a/src/tokens/TokenDetails.tsx b/src/tokens/TokenDetails.tsx index 6b687d2c163..67d510b0d33 100644 --- a/src/tokens/TokenDetails.tsx +++ b/src/tokens/TokenDetails.tsx @@ -1,5 +1,5 @@ import { NativeStackScreenProps } from '@react-navigation/native-stack' -import React from 'react' +import React, { useRef } from 'react' import { useTranslation } from 'react-i18next' import { ScrollView, StyleSheet, Text, View } from 'react-native' import { SafeAreaView } from 'react-native-safe-area-context' @@ -7,6 +7,7 @@ import { useSelector } from 'react-redux' import { AssetsEvents } from 'src/analytics/Events' import { TokenProperties } from 'src/analytics/Properties' import ValoraAnalytics from 'src/analytics/ValoraAnalytics' +import { BottomSheetRefType } from 'src/components/BottomSheet' import Button, { BtnSizes } from 'src/components/Button' import PercentageIndicator from 'src/components/PercentageIndicator' import TokenDisplay from 'src/components/TokenDisplay' @@ -31,6 +32,7 @@ import { StackParamList } from 'src/navigator/types' import Colors from 'src/styles/colors' import { typeScale } from 'src/styles/fonts' import { Spacing } from 'src/styles/styles' +import MoreActionsBottomSheet from 'src/tokens/MoreActionsBottomSheet' import { TokenBalanceItem } from 'src/tokens/TokenBalanceItem' import { useCashInTokens, @@ -52,6 +54,7 @@ export default function TokenDetailsScreen({ route }: Props) { const { tokenId } = route.params const { t } = useTranslation() const token = useTokenInfo(tokenId) + const moreActionsBottomSheetRef = useRef(null) if (!token) { throw new Error(`token with id ${tokenId} not found`) @@ -86,7 +89,7 @@ export default function TokenDetailsScreen({ route }: Props) { testID="TokenDetails/Chart" /> )} - + {t('tokenDetails.yourBalance')} {token.infoUrl && ( @@ -97,6 +100,7 @@ export default function TokenDetailsScreen({ route }: Props) { /> )} + ) } @@ -136,7 +140,21 @@ function PriceInfo({ token }: { token: TokenBalance }) { ) } -function Actions({ token }: { token: TokenBalance }) { +export const onPressCicoAction = (token: TokenBalance, flow: CICOFlow) => { + const tokenSymbol = token.symbol + // this should always be true given that we only show Add / Withdraw if a + // token is CiCoCurrency, but adding it here to ensure type safety + if (isCicoToken(tokenSymbol)) { + navigate(Screens.FiatExchangeAmount, { + currency: tokenSymbol, + tokenId: token.tokenId, + flow, + network: Network.Celo, + }) + } +} + +export const useActions = (token: TokenBalance) => { const { t } = useTranslation() const sendableTokens = useSendableTokens() const swappableTokens = useSwappableTokens() @@ -145,24 +163,11 @@ function Actions({ token }: { token: TokenBalance }) { const isSwapEnabled = useSelector(isAppSwapsEnabledSelector) const showWithdraw = !!cashOutTokens.find((tokenInfo) => tokenInfo.tokenId === token.tokenId) - const onPressCicoAction = (flow: CICOFlow) => { - const tokenSymbol = token.symbol - // this should always be true given that we only show Add / Withdraw if a - // token is CiCoCurrency, but adding it here to ensure type safety - if (isCicoToken(tokenSymbol)) { - navigate(Screens.FiatExchangeAmount, { - currency: tokenSymbol, - tokenId: token.tokenId, - flow, - network: Network.Celo, - }) - } - } - - const actions = [ + return [ { name: TokenDetailsActionName.Send, text: t('tokenDetails.actions.send'), + details: t('tokenDetails.actions.sendDetails'), iconComponent: QuickActionsSend, onPress: () => { navigate(Screens.Send, { defaultTokenIdOverride: token.tokenId }) @@ -172,6 +177,7 @@ function Actions({ token }: { token: TokenBalance }) { { name: TokenDetailsActionName.Swap, text: t('tokenDetails.actions.swap'), + details: t('tokenDetails.actions.swapDetails'), iconComponent: QuickActionsSwap, onPress: () => { navigate(Screens.SwapScreenWithBack, { fromTokenId: token.tokenId }) @@ -184,22 +190,37 @@ function Actions({ token }: { token: TokenBalance }) { { name: TokenDetailsActionName.Add, text: t('tokenDetails.actions.add'), + details: t('tokenDetails.actions.addDetails'), iconComponent: QuickActionsAdd, onPress: () => { - onPressCicoAction(CICOFlow.CashIn) + onPressCicoAction(token, CICOFlow.CashIn) }, visible: !!cashInTokens.find((tokenInfo) => tokenInfo.tokenId === token.tokenId), }, { name: TokenDetailsActionName.Withdraw, text: t('tokenDetails.actions.withdraw'), + details: t('tokenDetails.actions.withdrawDetails'), iconComponent: QuickActionsWithdraw, onPress: () => { - onPressCicoAction(CICOFlow.CashOut) + onPressCicoAction(token, CICOFlow.CashOut) }, visible: showWithdraw, }, ].filter((action) => action.visible) +} + +function Actions({ + token, + moreActionsBottomSheetRef, +}: { + token: TokenBalance + moreActionsBottomSheetRef: React.RefObject +}) { + const { t } = useTranslation() + const actions = useActions(token) + const cashOutTokens = useCashOutTokens() + const showWithdraw = !!cashOutTokens.find((tokenInfo) => tokenInfo.tokenId === token.tokenId) const moreAction = { name: TokenDetailsActionName.More, @@ -207,6 +228,7 @@ function Actions({ token }: { token: TokenBalance }) { iconComponent: QuickActionsMore, onPress: () => { // TODO(ACT-917): open bottom sheet + moreActionsBottomSheetRef.current?.snapToIndex(0) }, }