diff --git a/src/lib/i18n.ts b/src/lib/i18n.ts index 5f894bb4b..173a30c54 100644 --- a/src/lib/i18n.ts +++ b/src/lib/i18n.ts @@ -219,6 +219,9 @@ const resources = { transaction_summary_plus_fees_capitalcase: '+ Fees', transaction_summary_insufficient_funds: 'Insufficient funds', transaction_summary_address_text: 'Address', + transaction_summary_add_non_existent_contact_title: 'Do you wish to add', + transaction_summary_add_non_existent_contact_title_2: 'as your contact?', + transaction_summary_add_non_existent_contact_action: 'Yes, sure', profile_screen_title: 'Profile', profile_contact_details_subtitle: 'Contact Details', profile_phone_label: 'Phone Number', diff --git a/src/navigation/contactsNavigator/index.tsx b/src/navigation/contactsNavigator/index.tsx index 2a9c5b743..e53e84a70 100644 --- a/src/navigation/contactsNavigator/index.tsx +++ b/src/navigation/contactsNavigator/index.tsx @@ -29,7 +29,7 @@ export const ContactsNavigation = ({ }} + options={{ header: AppHeader }} /> () +const TabBar = (isShown: boolean) => (props: BottomTabBarProps) => + !isShown ? null : + export const RootNavigationComponent = () => { const { t } = useTranslation() const { top } = useSafeAreaInsets() @@ -48,7 +54,7 @@ export const RootNavigationComponent = () => { const fullscreen = useAppSelector(selectFullscreen) const settingsLoading = useAppSelector(selectSettingsIsLoading) - const isShown = unlocked && !fullscreen + const isShown = useMemo(() => unlocked && !fullscreen, [unlocked, fullscreen]) useEffect(() => { BootSplash.hide() @@ -56,8 +62,7 @@ export const RootNavigationComponent = () => { return ( - (!isShown ? null : )}> + {!unlocked ? ( void) => () => + ( + + + + ) + export const ContactDetails = ({ navigation, route: { @@ -123,18 +137,7 @@ export const ContactDetails = ({ useEffect(() => { navigation.setOptions({ - headerRight: _ => ( - - - - ), + headerRight: HeaderRight(onDeleteContact), headerStyle: { backgroundColor: sharedColors.inputActive, }, diff --git a/src/screens/contacts/ContactFormScreen.tsx b/src/screens/contacts/ContactFormScreen.tsx index c7fceaa89..8b5118ee3 100644 --- a/src/screens/contacts/ContactFormScreen.tsx +++ b/src/screens/contacts/ContactFormScreen.tsx @@ -50,6 +50,10 @@ export const checkIfContactExists = ( name: string, searchArray: Contact[], ) => { + if (!address || !name) { + return false + } + const index = searchArray.findIndex(c => { return ( c.displayAddress === address.toLowerCase() || @@ -166,6 +170,7 @@ export const ContactFormScreen = ({ address: lAddress, displayAddress, } + const contactExists = checkIfContactExists( displayAddress && lAddress, trimmedName, diff --git a/src/screens/send/SendScreen.tsx b/src/screens/send/SendScreen.tsx index 891518bac..cbe9598bd 100644 --- a/src/screens/send/SendScreen.tsx +++ b/src/screens/send/SendScreen.tsx @@ -21,14 +21,15 @@ import { selectBalances, selectTotalUsdValue, } from 'store/slices/balancesSlice/selectors' -import { sharedColors, sharedStyles } from 'shared/constants' import { TokenBalanceObject } from 'store/slices/balancesSlice/types' import { selectChainId } from 'store/slices/settingsSlice' +import { TransactionStatus } from 'store/shared/types' +import { selectRecentRskTransactions } from 'store/slices/transactionsSlice' +import { getContactsAsArrayAndSelected } from 'store/slices/contactsSlice' import { FullScreenSpinner } from 'components/fullScreenSpinner' import { SuccessIcon } from 'components/icons/SuccessIcon' import { FeedbackModal } from 'components/feedbackModal' -import { getContactsAsArrayAndSelected } from 'store/slices/contactsSlice' -import { selectRecentRskTransactions } from 'store/slices/transactionsSlice' +import { sharedColors, sharedStyles } from 'shared/constants' import { useWalletState } from 'shared/wallet' import { TransactionForm } from './TransactionForm' @@ -177,15 +178,15 @@ export const SendScreen = ({ status = error.toString() } else if ( currentTransaction?.status && - currentTransaction.status === 'USER_CONFIRM' + currentTransaction.status === TransactionStatus.USER_CONFIRM ) { status = t('send_screen_sending_transaction') } useEffect(() => { if ( - currentTransaction?.status === 'SUCCESS' || - currentTransaction?.status === 'FAILED' + currentTransaction?.status === TransactionStatus.SUCCESS || + currentTransaction?.status === TransactionStatus.FAILED ) { navigation.navigate(rootTabsRouteNames.Home, { screen: homeStackRouteNames.Main, @@ -239,7 +240,7 @@ export const SendScreen = ({ status={status} bitcoinBalance={bitcoinBalance} /> - {currentTransaction?.status === 'USER_CONFIRM' && ( + {currentTransaction?.status === TransactionStatus.USER_CONFIRM && ( )} diff --git a/src/screens/transactionSummary/TransactionSummaryComponent.tsx b/src/screens/transactionSummary/TransactionSummaryComponent.tsx index f71e0c585..16b01f58d 100644 --- a/src/screens/transactionSummary/TransactionSummaryComponent.tsx +++ b/src/screens/transactionSummary/TransactionSummaryComponent.tsx @@ -15,10 +15,8 @@ import { import { castStyle, formatTokenValues } from 'shared/utils' import { AppButton, AppTouchable, Typography } from 'components/index' import { useAppSelector } from 'store/storeUtils' -import { isMyAddress } from 'components/address/lib' import { DollarIcon } from 'components/icons/DollarIcon' import { FullScreenSpinner } from 'components/fullScreenSpinner' -import { getContactByAddress } from 'store/slices/contactsSlice' import { getWalletSetting } from 'core/config' import { SETTINGS } from 'core/types' import { selectChainId } from 'store/slices/settingsSlice' @@ -44,13 +42,13 @@ type TransactionSummaryComponentProps = Omit< Props export const TransactionSummaryComponent = ({ - address, transaction, buttons, functionName, goBack, isLoaded, FeeComponent, + contact, }: TransactionSummaryComponentProps) => { const [confirmed, setConfirmed] = useState(false) const chainId = useAppSelector(selectChainId) @@ -64,21 +62,14 @@ export const TransactionSummaryComponent = ({ time, hashId, to, - from, totalToken, totalUsd, + amIReceiver, } = transaction const iconObject = transactionStatusToIconPropsMap.get(status) const transactionStatusText = transactionStatusDisplayText.get(status) - const amIReceiver = transaction.amIReceiver ?? isMyAddress(address, to) - const contactAddress = amIReceiver ? from || '' : to - const contact = useAppSelector( - getContactByAddress(contactAddress.toLowerCase()), - ) - const contactToUse = contact || { address: contactAddress } - const title = useMemo(() => { if (amIReceiver) { if (status === TransactionStatus.SUCCESS) { @@ -121,7 +112,7 @@ export const TransactionSummaryComponent = ({ {functionName && ( @@ -247,7 +238,7 @@ export const TransactionSummaryComponent = ({ {/* separator */} {/* address value */} - {contactToUse?.name && ( + {contact?.name && ( - {contactToUse.address} + {contact.address} )} diff --git a/src/screens/transactionSummary/index.tsx b/src/screens/transactionSummary/index.tsx index f3745f6d3..0fcde0003 100644 --- a/src/screens/transactionSummary/index.tsx +++ b/src/screens/transactionSummary/index.tsx @@ -1,20 +1,29 @@ import { useFocusEffect, useIsFocused } from '@react-navigation/native' import { ReactNode, useCallback, useEffect, useMemo } from 'react' import { BackHandler } from 'react-native' +import { showMessage } from 'react-native-flash-message' +import { useTranslation } from 'react-i18next' + +import { shortAddress } from 'lib/utils' import { AppButtonProps } from 'components/button' import { CurrencyValue } from 'components/token' import { sharedHeaderLeftOptions } from 'navigation/index' +import { contactsStackRouteNames } from 'navigation/contactsNavigator' import { RootTabsScreenProps, rootTabsRouteNames, } from 'navigation/rootNavigator' +import { isMyAddress } from 'components/index' import { TransactionSummaryComponent } from 'screens/transactionSummary/TransactionSummaryComponent' import { setFullscreen } from 'store/slices/settingsSlice' import { TokenFeeValueObject } from 'store/slices/transactionsSlice' -import { useAppDispatch } from 'store/storeUtils' +import { useAppDispatch, useAppSelector } from 'store/storeUtils' import { useWallet } from 'shared/wallet' +import { getPopupMessage } from 'shared/popupMessage' +import { ContactWithAddressRequired } from 'shared/types' import { TransactionStatus } from 'store/shared/types' +import { getContactByAddress } from 'store/slices/contactsSlice' export interface TransactionSummaryScreenProps { transaction: { @@ -30,6 +39,7 @@ export interface TransactionSummaryScreenProps { amIReceiver?: boolean from?: string } + contact: ContactWithAddressRequired buttons?: [AppButtonProps, AppButtonProps] functionName?: string backScreen?: rootTabsRouteNames @@ -41,10 +51,19 @@ export const TransactionSummaryScreen = ({ route, navigation, }: RootTabsScreenProps) => { + const { t } = useTranslation() + const { transaction, backScreen } = route.params + const { to, from } = transaction + const { address } = useWallet() const dispatch = useAppDispatch() const isFocused = useIsFocused() - const { backScreen } = route.params + const amIReceiver = transaction.amIReceiver ?? isMyAddress(address, to) + const contactAddress = amIReceiver ? from || '' : to + const contact = useAppSelector( + getContactByAddress(contactAddress.toLowerCase()), + ) + const contactToUse = contact || { address: contactAddress } const goBack = useMemo(() => { if (backScreen) { @@ -77,9 +96,41 @@ export const TransactionSummaryScreen = ({ }) }, [goBack, navigation]) + const moveToCreateContact = useCallback(() => { + navigation.navigate(rootTabsRouteNames.Contacts, { + screen: contactsStackRouteNames.ContactForm, + params: { + initialValue: { + address: contactAddress, + name: '', + displayAddress: '', + }, + proposed: true, + }, + }) + }, [navigation, contactAddress]) + + useEffect(() => { + if (!contact) { + showMessage( + getPopupMessage( + `${t( + 'transaction_summary_add_non_existent_contact_title', + )} ${shortAddress(contactAddress)} ${t( + 'transaction_summary_add_non_existent_contact_title_2', + )}`, + t('transaction_summary_add_non_existent_contact_action'), + moveToCreateContact, + ), + ) + } + }, [contact, contactAddress, moveToCreateContact, t]) + return ( diff --git a/src/ux/requestsModal/ReviewBitcoinTransactionContainer.tsx b/src/ux/requestsModal/ReviewBitcoinTransactionContainer.tsx index 9d7f9627c..7d14b4bf5 100644 --- a/src/ux/requestsModal/ReviewBitcoinTransactionContainer.tsx +++ b/src/ux/requestsModal/ReviewBitcoinTransactionContainer.tsx @@ -14,10 +14,11 @@ import { TransactionSummaryComponent } from 'screens/transactionSummary/Transact import { TokenSymbol } from 'screens/home/TokenImage' import { selectUsdPrices } from 'store/slices/usdPricesSlice' import { useAppSelector } from 'store/storeUtils' -import { sharedColors } from 'shared/constants' +import { getContactByAddress } from 'store/slices/contactsSlice' import { AppButtonBackgroundVarietyEnum, Input } from 'components/index' import { TransactionSummaryScreenProps } from 'screens/transactionSummary' import { formatTokenValues } from 'shared/utils' +import { sharedColors } from 'shared/constants' import { BitcoinMiningFeeContainer, @@ -44,6 +45,8 @@ export const ReviewBitcoinTransactionContainer = ({ payload: { addressToPay, payment, ...payload }, } = request + const contact = useAppSelector(getContactByAddress(addressToPay)) + const [miningFeeState, setMiningFeeState] = useState(payload.miningFee) const onMiningFeeChange = useCallback( @@ -85,6 +88,7 @@ export const ReviewBitcoinTransactionContainer = ({ const feeUsd = convertToUSD(miningFee) const isAmountSmall = !Number(amountToPayUsd) && !!Number(amountToPay) const totalSent = Number(amountToPay) + Number(miningFee) + const contactToUse = contact || { address: addressToPay } return { transaction: { @@ -114,6 +118,7 @@ export const ReviewBitcoinTransactionContainer = ({ ), to: addressToPay, }, + contact: contactToUse, buttons: [ { title: t('transaction_summary_title_confirm_button_title'), @@ -146,6 +151,7 @@ export const ReviewBitcoinTransactionContainer = ({ tokenPrices, payload.miningFee, onMiningFeeChange, + contact, ]) return ( diff --git a/src/ux/requestsModal/ReviewRelayTransaction/ReviewTransactionContainer.tsx b/src/ux/requestsModal/ReviewRelayTransaction/ReviewTransactionContainer.tsx index 736add9e6..8ceca2f04 100644 --- a/src/ux/requestsModal/ReviewRelayTransaction/ReviewTransactionContainer.tsx +++ b/src/ux/requestsModal/ReviewRelayTransaction/ReviewTransactionContainer.tsx @@ -25,7 +25,10 @@ import { } from 'shared/utils' import { selectUsdPrices } from 'store/slices/usdPricesSlice' import { useAppDispatch, useAppSelector } from 'store/storeUtils' -import { addRecentContact } from 'store/slices/contactsSlice' +import { + addRecentContact, + getContactByAddress, +} from 'store/slices/contactsSlice' import { selectBalances } from 'store/slices/balancesSlice' import { selectRecentRskTransactions } from 'store/slices/transactionsSlice' import { Wallet } from 'shared/wallet' @@ -82,6 +85,8 @@ export const ReviewTransactionContainer = ({ gasLimit, } = enhancedTransactionRequest + const contact = useAppSelector(getContactByAddress(to)) + const getTokenBySymbol = useCallback( (symb: string) => { const result = Object.values(balances).find( @@ -239,6 +244,7 @@ export const ReviewTransactionContainer = ({ time: 'approx 1 min', to, }, + contact: contact || { address: to }, buttons: [ { title: t('transaction_summary_title_confirm_button_title'), @@ -272,6 +278,7 @@ export const ReviewTransactionContainer = ({ cancelTransaction, functionName, wallet, + contact, ]) return (