import { NativeStackScreenProps } from '@react-navigation/native-stack' import * as React from 'react' import { WithTranslation } from 'react-i18next' import { Keyboard, StyleSheet, Text, View } from 'react-native' import { connect } from 'react-redux' import AppAnalytics from 'src/analytics/AppAnalytics' import { SendEvents } from 'src/analytics/Events' import { ErrorMessages } from 'src/app/ErrorMessages' import AccountNumberCard from 'src/components/AccountNumberCard' import BackButton from 'src/components/BackButton' import Button, { BtnTypes } from 'src/components/Button' import CodeRow, { CodeRowStatus } from 'src/components/CodeRow' import ErrorMessageInline from 'src/components/ErrorMessageInline' import KeyboardAwareScrollView from 'src/components/KeyboardAwareScrollView' import KeyboardSpacer from 'src/components/KeyboardSpacer' import Modal from 'src/components/Modal' import { SingleDigitInput } from 'src/components/SingleDigitInput' import TextButton from 'src/components/TextButton' import { withTranslation } from 'src/i18n' import HamburgerCard from 'src/icons/HamburgerCard' import InfoIcon from 'src/icons/InfoIcon' import { validateRecipientAddress, validateRecipientAddressReset } from 'src/identity/actions' import { AddressValidationType } from 'src/identity/reducer' import { getAddressValidationType, getSecureSendAddress } from 'src/identity/secureSend' import { emptyHeader } from 'src/navigator/Headers' import { navigate } from 'src/navigator/NavigationService' import { Screens } from 'src/navigator/Screens' import { StackParamList } from 'src/navigator/types' import { Recipient, getDisplayName } from 'src/recipients/recipient' import { RootState } from 'src/redux/reducers' import { TransactionDataInput } from 'src/send/types' import colors from 'src/styles/colors' import { typeScale } from 'src/styles/fonts' const FULL_ADDRESS_PLACEHOLDER = '0xf1b1d5a6e7728g309c4a025k122d71ad75a61976' const PARTIAL_ADDRESS_PLACEHOLDER = ['a', '0', 'F', '4'] interface StateProps { recipient: Recipient transactionData?: TransactionDataInput addressValidationType: AddressValidationType validationSuccessful: boolean error?: ErrorMessages | null validatedAddress?: string } interface State { inputValue: string singleDigitInputValueArr: string[] isModalVisible: boolean digitsRefs: React.MutableRefObject[] } interface DispatchProps { validateRecipientAddressReset: typeof validateRecipientAddressReset validateRecipientAddress: typeof validateRecipientAddress } type OwnProps = NativeStackScreenProps type Props = StateProps & DispatchProps & WithTranslation & OwnProps const mapDispatchToProps = { validateRecipientAddressReset, validateRecipientAddress, } const mapStateToProps = (state: RootState, ownProps: OwnProps): StateProps => { const { route } = ownProps const { recipient } = route.params const e164PhoneNumber = recipient.e164PhoneNumber const error = state.alert ? state.alert.underlyingError : null const secureSendPhoneNumberMapping = state.identity.secureSendPhoneNumberMapping const validationSuccessful = !!e164PhoneNumber && !!secureSendPhoneNumberMapping[e164PhoneNumber]?.validationSuccessful const addressValidationType: AddressValidationType = getAddressValidationType( recipient, secureSendPhoneNumberMapping ) const validatedAddress = getSecureSendAddress(recipient, secureSendPhoneNumberMapping) return { recipient, validationSuccessful, addressValidationType, error, validatedAddress, } } export const validateRecipientAccountScreenNavOptions = () => ({ ...emptyHeader, headerLeft: () => , }) export class ValidateRecipientAccount extends React.Component { state: State = { inputValue: '', singleDigitInputValueArr: [], isModalVisible: false, digitsRefs: [React.createRef(), React.createRef(), React.createRef(), React.createRef()], } componentDidMount = () => { const e164PhoneNumber = this.props.recipient.e164PhoneNumber if (e164PhoneNumber) { this.props.validateRecipientAddressReset(e164PhoneNumber) } } componentDidUpdate = (prevProps: Props) => { const { validationSuccessful, route, validatedAddress } = this.props const { singleDigitInputValueArr } = this.state if (validationSuccessful && !prevProps.validationSuccessful && validatedAddress) { navigate(Screens.SendEnterAmount, { origin: route.params.origin, recipient: { ...route.params.recipient, address: validatedAddress, }, isFromScan: false, forceTokenId: route.params.forceTokenId, defaultTokenIdOverride: route.params.defaultTokenIdOverride, }) } // If the user has entered 4 valid digits, dismiss the keyboard if (singleDigitInputValueArr.filter((entry) => /[a-f0-9]/gi.test(entry)).length === 4) { Keyboard.dismiss() } } onPressConfirm = () => { const { inputValue, singleDigitInputValueArr } = this.state const { recipient, addressValidationType } = this.props const { requesterAddress } = this.props.route.params const inputToValidate = addressValidationType === AddressValidationType.FULL ? inputValue : singleDigitInputValueArr.join('') AppAnalytics.track(SendEvents.send_secure_submit, { partialAddressValidation: addressValidationType === AddressValidationType.PARTIAL, address: inputToValidate, }) this.props.validateRecipientAddress( inputToValidate, addressValidationType, recipient, requesterAddress ) } onInputChange = (value: string) => { this.setState({ inputValue: value }) } onSingleDigitInputChange = (value: string, index: number) => { const { singleDigitInputValueArr, digitsRefs } = this.state if (value === ' ') return if (value !== '') { digitsRefs[index + 1]?.current?.focus() } else { digitsRefs[index - 1]?.current?.focus() } singleDigitInputValueArr[index] = value this.setState({ singleDigitInputValueArr }) } toggleModal = () => { const { addressValidationType } = this.props if (this.state.isModalVisible) { AppAnalytics.track(SendEvents.send_secure_info, { partialAddressValidation: addressValidationType === AddressValidationType.PARTIAL, }) } else { AppAnalytics.track(SendEvents.send_secure_info_dismissed, { partialAddressValidation: addressValidationType === AddressValidationType.PARTIAL, }) } this.setState({ isModalVisible: !this.state.isModalVisible }) } shouldShowClipboard = () => false renderInstructionsAndInputField = () => { const { t, recipient, addressValidationType } = this.props const { inputValue, singleDigitInputValueArr, digitsRefs } = this.state if (addressValidationType === AddressValidationType.FULL) { return ( {recipient.name ? t('confirmAccountNumber.body1Full', { displayName: recipient.name }) : t('confirmAccountNumber.body1FullNoDisplayName')} {t('confirmAccountNumber.body2Full')} {t('accountInputHeaderFull')} ) } const singleDigitInputComponentArr = PARTIAL_ADDRESS_PLACEHOLDER.map( (placeholderValue, index) => ( this.onSingleDigitInputChange(value, index)} testID={`SingleDigitInput/digit${index}`} /> ) ) return ( {recipient.name ? t('confirmAccountNumber.bodyPartial', { displayName: recipient.name }) : t('confirmAccountNumber.bodyPartialNoDisplayName')} {t('accountInputHeaderPartial')} {singleDigitInputComponentArr} ) } render = () => { const { t, recipient, error, addressValidationType } = this.props const { singleDigitInputValueArr, inputValue } = this.state const displayName = getDisplayName(recipient, t) const isFilled = addressValidationType === AddressValidationType.FULL ? inputValue.length > 0 : singleDigitInputValueArr.filter((entry) => /[a-f0-9]/gi.test(entry)).length === 4 return ( <> {t('confirmAccountNumber.title')} {this.renderInstructionsAndInputField()}