From 7529447422fe2e8a71fb8aa4d60f9da5e72fa9d5 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Mon, 20 Nov 2023 11:11:38 +0100 Subject: [PATCH 1/5] Migrate BaseKYCWall.js to function component --- src/components/KYCWall/BaseKYCWall.js | 244 ++++++++++++++------------ src/libs/actions/PaymentMethods.ts | 14 +- 2 files changed, 142 insertions(+), 116 deletions(-) diff --git a/src/components/KYCWall/BaseKYCWall.js b/src/components/KYCWall/BaseKYCWall.js index d55417a6190a..cbadf35e5bf6 100644 --- a/src/components/KYCWall/BaseKYCWall.js +++ b/src/components/KYCWall/BaseKYCWall.js @@ -1,104 +1,126 @@ import lodashGet from 'lodash/get'; -import React from 'react'; +import React, {useCallback, useEffect, useRef, useState} from 'react'; import {Dimensions} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; -import AddPaymentMethodMenu from '@components/AddPaymentMethodMenu'; +import CONST from '@src/CONST'; +import * as PaymentMethods from '@userActions/PaymentMethods'; +import * as Wallet from '@userActions/Wallet'; import getClickedTargetLocation from '@libs/getClickedTargetLocation'; import Log from '@libs/Log'; import Navigation from '@libs/Navigation/Navigation'; import * as PaymentUtils from '@libs/PaymentUtils'; import * as ReportUtils from '@libs/ReportUtils'; -import * as PaymentMethods from '@userActions/PaymentMethods'; -import * as Wallet from '@userActions/Wallet'; -import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import AddPaymentMethodMenu from '@components/AddPaymentMethodMenu'; import {defaultProps, propTypes} from './kycWallPropTypes'; +const POPOVER_MENU_ANCHOR_POSITION_HORIZONTAL_OFFSET = 20; + // This component allows us to block various actions by forcing the user to first add a default payment method and successfully make it through our Know Your Customer flow // before continuing to take whatever action they originally intended to take. It requires a button as a child and a native event so we can get the coordinates and use it // to render the AddPaymentMethodMenu in the correct location. -class KYCWall extends React.Component { - constructor(props) { - super(props); - - this.continue = this.continue.bind(this); - this.setMenuPosition = this.setMenuPosition.bind(this); - this.selectPaymentMethod = this.selectPaymentMethod.bind(this); - this.anchorRef = React.createRef(null); - - this.state = { - shouldShowAddPaymentMenu: false, - anchorPositionVertical: 0, - anchorPositionHorizontal: 0, - transferBalanceButton: null, - }; - } - - componentDidMount() { - PaymentMethods.kycWallRef.current = this; - if (this.props.shouldListenForResize) { - this.dimensionsSubscription = Dimensions.addEventListener('change', this.setMenuPosition); - } - } - - componentWillUnmount() { - if (this.props.shouldListenForResize && this.dimensionsSubscription) { - this.dimensionsSubscription.remove(); - } - PaymentMethods.kycWallRef.current = null; - } - - setMenuPosition() { - if (!this.state.transferBalanceButton) { - return; - } - const buttonPosition = getClickedTargetLocation(this.state.transferBalanceButton); - const position = this.getAnchorPosition(buttonPosition); - this.setPositionAddPaymentMenu(position); - } +function KYCWall({ + addBankAccountRoute, + addDebitCardRoute, + anchorAlignment, + bankAccountList, + chatReportID, + children, + enablePaymentsRoute, + fundList, + iouReport, + onSelectPaymentMethod, + onSuccessfulKYC, + reimbursementAccount, + shouldIncludeDebitCard, + shouldListenForResize, + source, + userWallet, + walletTerms, +}) { + const anchorRef = useRef(null); + const transferBalanceButtonRef = useRef(null); + + const [shouldShowAddPaymentMenu, setShouldShowAddPaymentMenu] = useState(false); + const [anchorPosition, setAnchorPosition] = useState({ + anchorPositionVertical: 0, + anchorPositionHorizontal: 0, + }); /** * @param {DOMRect} domRect * @returns {Object} */ - getAnchorPosition(domRect) { - if (this.props.anchorAlignment.vertical === CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP) { + const getAnchorPosition = useCallback( + (domRect) => { + if (anchorAlignment.vertical === CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP) { + return { + anchorPositionVertical: domRect.top + domRect.height + CONST.MODAL.POPOVER_MENU_PADDING, + anchorPositionHorizontal: domRect.left + POPOVER_MENU_ANCHOR_POSITION_HORIZONTAL_OFFSET, + }; + } + return { - anchorPositionVertical: domRect.top + domRect.height + CONST.MODAL.POPOVER_MENU_PADDING, - anchorPositionHorizontal: domRect.left + 20, + anchorPositionVertical: domRect.top - CONST.MODAL.POPOVER_MENU_PADDING, + anchorPositionHorizontal: domRect.left, }; - } - - return { - anchorPositionVertical: domRect.top - CONST.MODAL.POPOVER_MENU_PADDING, - anchorPositionHorizontal: domRect.left, - }; - } + }, + [anchorAlignment.vertical], + ); /** * Set position of the transfer payment menu * * @param {Object} position */ - setPositionAddPaymentMenu(position) { - this.setState({ - anchorPositionVertical: position.anchorPositionVertical, - anchorPositionHorizontal: position.anchorPositionHorizontal, + const setPositionAddPaymentMenu = ({anchorPositionVertical, anchorPositionHorizontal}) => { + setAnchorPosition({ + anchorPositionVertical, + anchorPositionHorizontal, }); - } + }; + + const setMenuPosition = useCallback(() => { + if (!transferBalanceButtonRef.current) { + return; + } + const buttonPosition = getClickedTargetLocation(transferBalanceButtonRef.current); + const position = getAnchorPosition(buttonPosition); + + setPositionAddPaymentMenu(position); + }, [getAnchorPosition]); + + useEffect(() => { + let dimensionsSubscription = null; + + PaymentMethods.kycWallRef.current = this; + + if (shouldListenForResize) { + dimensionsSubscription = Dimensions.addEventListener('change', setMenuPosition); + } + + return () => { + if (shouldListenForResize && dimensionsSubscription) { + dimensionsSubscription.remove(); + } + + PaymentMethods.kycWallRef.current = null; + }; + }, [chatReportID, setMenuPosition, shouldListenForResize]); /** * @param {String} paymentMethod */ - selectPaymentMethod(paymentMethod) { - this.props.onSelectPaymentMethod(paymentMethod); + const selectPaymentMethod = (paymentMethod) => { + onSelectPaymentMethod(paymentMethod); + if (paymentMethod === CONST.PAYMENT_METHODS.BANK_ACCOUNT) { - Navigation.navigate(this.props.addBankAccountRoute); + Navigation.navigate(addBankAccountRoute); } else if (paymentMethod === CONST.PAYMENT_METHODS.DEBIT_CARD) { - Navigation.navigate(this.props.addDebitCardRoute); + Navigation.navigate(addDebitCardRoute); } - } + }; /** * Take the position of the button that calls this method and show the Add Payment method menu when the user has no valid payment method. @@ -108,82 +130,86 @@ class KYCWall extends React.Component { * @param {Event} event * @param {String} iouPaymentType */ - continue(event, iouPaymentType) { - const currentSource = lodashGet(this.props.walletTerms, 'source', this.props.source); + const continueAction = (event, iouPaymentType) => { + const currentSource = lodashGet(walletTerms, 'source', source); /** * Set the source, so we can tailor the process according to how we got here. * We do not want to set this on mount, as the source can change upon completing the flow, e.g. when upgrading the wallet to Gold. */ - Wallet.setKYCWallSource(this.props.source, this.props.chatReportID); + Wallet.setKYCWallSource(source, chatReportID); + + if (shouldShowAddPaymentMenu) { + setShouldShowAddPaymentMenu(false); - if (this.state.shouldShowAddPaymentMenu) { - this.setState({shouldShowAddPaymentMenu: false}); return; } // Use event target as fallback if anchorRef is null for safety - const targetElement = this.anchorRef.current || event.nativeEvent.target; - this.setState({transferBalanceButton: targetElement}); - const isExpenseReport = ReportUtils.isExpenseReport(this.props.iouReport); - const paymentCardList = this.props.fundList || {}; + const targetElement = anchorRef.current || event.nativeEvent.target; + + transferBalanceButtonRef.current = targetElement; + const isExpenseReport = ReportUtils.isExpenseReport(iouReport); + const paymentCardList = fundList || {}; // Check to see if user has a valid payment method on file and display the add payment popover if they don't if ( - (isExpenseReport && lodashGet(this.props.reimbursementAccount, 'achData.state', '') !== CONST.BANK_ACCOUNT.STATE.OPEN) || - (!isExpenseReport && !PaymentUtils.hasExpensifyPaymentMethod(paymentCardList, this.props.bankAccountList, this.props.shouldIncludeDebitCard)) + (isExpenseReport && lodashGet(reimbursementAccount, 'achData.state', '') !== CONST.BANK_ACCOUNT.STATE.OPEN) || + (!isExpenseReport && !PaymentUtils.hasExpensifyPaymentMethod(paymentCardList, bankAccountList, shouldIncludeDebitCard)) ) { Log.info('[KYC Wallet] User does not have valid payment method'); - if (!this.props.shouldIncludeDebitCard) { - this.selectPaymentMethod(CONST.PAYMENT_METHODS.BANK_ACCOUNT); + if (!shouldIncludeDebitCard) { + selectPaymentMethod(CONST.PAYMENT_METHODS.BANK_ACCOUNT); return; } + const clickedElementLocation = getClickedTargetLocation(targetElement); - const position = this.getAnchorPosition(clickedElementLocation); - this.setPositionAddPaymentMenu(position); - this.setState({ - shouldShowAddPaymentMenu: true, - }); + const position = getAnchorPosition(clickedElementLocation); + + setPositionAddPaymentMenu(position); + setShouldShowAddPaymentMenu(true); + return; } + if (!isExpenseReport) { // Ask the user to upgrade to a gold wallet as this means they have not yet gone through our Know Your Customer (KYC) checks - const hasActivatedWallet = this.props.userWallet.tierName && _.contains([CONST.WALLET.TIER_NAME.GOLD, CONST.WALLET.TIER_NAME.PLATINUM], this.props.userWallet.tierName); + const hasActivatedWallet = userWallet.tierName && _.contains([CONST.WALLET.TIER_NAME.GOLD, CONST.WALLET.TIER_NAME.PLATINUM], userWallet.tierName); if (!hasActivatedWallet) { Log.info('[KYC Wallet] User does not have active wallet'); - Navigation.navigate(this.props.enablePaymentsRoute); + Navigation.navigate(enablePaymentsRoute); return; } } + Log.info('[KYC Wallet] User has valid payment method and passed KYC checks or did not need them'); - this.props.onSuccessfulKYC(iouPaymentType, currentSource); - } - - render() { - return ( - <> - this.setState({shouldShowAddPaymentMenu: false})} - anchorRef={this.anchorRef} - anchorPosition={{ - vertical: this.state.anchorPositionVertical, - horizontal: this.state.anchorPositionHorizontal, - }} - anchorAlignment={this.props.anchorAlignment} - onItemSelected={(item) => { - this.setState({shouldShowAddPaymentMenu: false}); - this.selectPaymentMethod(item); - }} - /> - {this.props.children(this.continue, this.anchorRef)} - - ); - } + onSuccessfulKYC(iouPaymentType, currentSource); + }; + + return ( + <> + setShouldShowAddPaymentMenu(false)} + anchorRef={anchorRef} + anchorAlignment={anchorAlignment} + anchorPosition={{ + vertical: anchorPosition.anchorPositionVertical, + horizontal: anchorPosition.anchorPositionHorizontal, + }} + onItemSelected={(item) => { + setShouldShowAddPaymentMenu(false); + selectPaymentMethod(item); + }} + /> + {children(continueAction, anchorRef)} + + ); } KYCWall.propTypes = propTypes; KYCWall.defaultProps = defaultProps; +KYCWall.displayName = 'BaseKYCWall'; export default withOnyx({ userWallet: { diff --git a/src/libs/actions/PaymentMethods.ts b/src/libs/actions/PaymentMethods.ts index bcc5d8142470..bb55297bab75 100644 --- a/src/libs/actions/PaymentMethods.ts +++ b/src/libs/actions/PaymentMethods.ts @@ -1,17 +1,17 @@ import {createRef} from 'react'; import Onyx, {OnyxUpdate} from 'react-native-onyx'; import {ValueOf} from 'type-fest'; -import * as API from '@libs/API'; -import * as CardUtils from '@libs/CardUtils'; -import Navigation from '@libs/Navigation/Navigation'; import CONST from '@src/CONST'; import ONYXKEYS, {OnyxValues} from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import PaymentMethod from '@src/types/onyx/PaymentMethod'; import {FilterMethodPaymentType} from '@src/types/onyx/WalletTransfer'; +import * as API from '@libs/API'; +import * as CardUtils from '@libs/CardUtils'; +import Navigation from '@libs/Navigation/Navigation'; type KYCWallRef = { - continue?: () => void; + continueAction?: () => void; }; /** @@ -23,14 +23,14 @@ const kycWallRef = createRef(); * When we successfully add a payment method or pass the KYC checks we will continue with our setup action if we have one set. */ function continueSetup(fallbackRoute = ROUTES.HOME) { - if (!kycWallRef.current?.continue) { + if (!kycWallRef.current?.continueAction) { Navigation.goBack(fallbackRoute); return; } // Close the screen (Add Debit Card, Add Bank Account, or Enable Payments) on success and continue with setup Navigation.goBack(fallbackRoute); - kycWallRef.current.continue(); + kycWallRef.current.continueAction(); } function openWalletPage() { @@ -214,7 +214,7 @@ function clearDebitCardFormErrorAndSubmit() { Onyx.set(ONYXKEYS.FORMS.ADD_DEBIT_CARD_FORM, { isLoading: false, errors: undefined, - setupComplete: false, + setupComplete: true, }); } From 980749a59347ea5097c4c28a14d380eb7142c60b Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Mon, 20 Nov 2023 11:26:29 +0100 Subject: [PATCH 2/5] lint: prettier fix --- src/components/KYCWall/BaseKYCWall.js | 8 ++++---- src/libs/actions/PaymentMethods.ts | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/KYCWall/BaseKYCWall.js b/src/components/KYCWall/BaseKYCWall.js index cbadf35e5bf6..81e2a4e03542 100644 --- a/src/components/KYCWall/BaseKYCWall.js +++ b/src/components/KYCWall/BaseKYCWall.js @@ -3,16 +3,16 @@ import React, {useCallback, useEffect, useRef, useState} from 'react'; import {Dimensions} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; -import CONST from '@src/CONST'; -import * as PaymentMethods from '@userActions/PaymentMethods'; -import * as Wallet from '@userActions/Wallet'; +import AddPaymentMethodMenu from '@components/AddPaymentMethodMenu'; import getClickedTargetLocation from '@libs/getClickedTargetLocation'; import Log from '@libs/Log'; import Navigation from '@libs/Navigation/Navigation'; import * as PaymentUtils from '@libs/PaymentUtils'; import * as ReportUtils from '@libs/ReportUtils'; +import * as PaymentMethods from '@userActions/PaymentMethods'; +import * as Wallet from '@userActions/Wallet'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import AddPaymentMethodMenu from '@components/AddPaymentMethodMenu'; import {defaultProps, propTypes} from './kycWallPropTypes'; const POPOVER_MENU_ANCHOR_POSITION_HORIZONTAL_OFFSET = 20; diff --git a/src/libs/actions/PaymentMethods.ts b/src/libs/actions/PaymentMethods.ts index bb55297bab75..924e22b08e8e 100644 --- a/src/libs/actions/PaymentMethods.ts +++ b/src/libs/actions/PaymentMethods.ts @@ -1,14 +1,14 @@ import {createRef} from 'react'; import Onyx, {OnyxUpdate} from 'react-native-onyx'; import {ValueOf} from 'type-fest'; +import * as API from '@libs/API'; +import * as CardUtils from '@libs/CardUtils'; +import Navigation from '@libs/Navigation/Navigation'; import CONST from '@src/CONST'; import ONYXKEYS, {OnyxValues} from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import PaymentMethod from '@src/types/onyx/PaymentMethod'; import {FilterMethodPaymentType} from '@src/types/onyx/WalletTransfer'; -import * as API from '@libs/API'; -import * as CardUtils from '@libs/CardUtils'; -import Navigation from '@libs/Navigation/Navigation'; type KYCWallRef = { continueAction?: () => void; From 44ee43a279f18c9d11f8bda014fdc26891e6671f Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Tue, 21 Nov 2023 15:25:38 +0100 Subject: [PATCH 3/5] add comment to const --- src/components/KYCWall/BaseKYCWall.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/KYCWall/BaseKYCWall.js b/src/components/KYCWall/BaseKYCWall.js index 81e2a4e03542..6c10ae129803 100644 --- a/src/components/KYCWall/BaseKYCWall.js +++ b/src/components/KYCWall/BaseKYCWall.js @@ -15,6 +15,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import {defaultProps, propTypes} from './kycWallPropTypes'; +// This sets the Horizontal anchor position offset for POPOVER MENU. const POPOVER_MENU_ANCHOR_POSITION_HORIZONTAL_OFFSET = 20; // This component allows us to block various actions by forcing the user to first add a default payment method and successfully make it through our Know Your Customer flow From ef906d8ddbb8649d10faf9a988d3768ac81fefea Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Mon, 27 Nov 2023 10:24:01 +0100 Subject: [PATCH 4/5] revert wrong merge conflict update --- src/libs/actions/PaymentMethods.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/PaymentMethods.ts b/src/libs/actions/PaymentMethods.ts index 3ddd33522d8e..b3d1e3e23a24 100644 --- a/src/libs/actions/PaymentMethods.ts +++ b/src/libs/actions/PaymentMethods.ts @@ -214,7 +214,7 @@ function clearDebitCardFormErrorAndSubmit() { Onyx.set(ONYXKEYS.FORMS.ADD_DEBIT_CARD_FORM, { isLoading: false, errors: undefined, - setupComplete: true, + setupComplete: false, }); } From 3be77c68371e53967a084c672aa1c7594ca5f1e0 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Mon, 27 Nov 2023 14:32:52 +0100 Subject: [PATCH 5/5] fix merge conflict issue --- src/components/KYCWall/BaseKYCWall.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/KYCWall/BaseKYCWall.js b/src/components/KYCWall/BaseKYCWall.js index cbc8a204ad0d..e229a860dd9f 100644 --- a/src/components/KYCWall/BaseKYCWall.js +++ b/src/components/KYCWall/BaseKYCWall.js @@ -201,7 +201,7 @@ function KYCWall({ shouldShowAddPaymentMenu(false)} + onClose={() => setShouldShowAddPaymentMenu(false)} anchorRef={anchorRef} anchorPosition={{ vertical: anchorPosition.anchorPositionVertical, @@ -209,7 +209,7 @@ function KYCWall({ }} anchorAlignment={anchorAlignment} onItemSelected={(item) => { - shouldShowAddPaymentMenu(false); + setShouldShowAddPaymentMenu(false); selectPaymentMethod(item); }} />