Skip to content

Commit

Permalink
Merge pull request #28798 from Swor71/migrate/16170-KYCWall-to-functi…
Browse files Browse the repository at this point in the history
…onal

feat: migrate KYCWall class component to functional
  • Loading branch information
bondydaa authored Oct 20, 2023
2 parents b1f7832 + f27220a commit fba87cd
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 108 deletions.
238 changes: 133 additions & 105 deletions src/components/KYCWall/BaseKYCWall.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, {useEffect, useState, useRef, useCallback} from 'react';
import _ from 'underscore';
import React from 'react';
import {withOnyx} from 'react-native-onyx';
import {Dimensions} from 'react-native';
import lodashGet from 'lodash/get';
Expand All @@ -15,90 +15,114 @@ import {propTypes, defaultProps} from './kycWallPropTypes';
import * as Wallet from '../../libs/actions/Wallet';
import * as ReportUtils from '../../libs/ReportUtils';

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);
}

Wallet.setKYCWallSourceChatReportID(chatReportID);

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.
Expand All @@ -108,82 +132,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 (
<>
<AddPaymentMethodMenu
isVisible={this.state.shouldShowAddPaymentMenu}
onClose={() => 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 (
<>
<AddPaymentMethodMenu
isVisible={shouldShowAddPaymentMenu}
onClose={() => 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: {
Expand Down
6 changes: 3 additions & 3 deletions src/libs/actions/PaymentMethods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {FilterMethodPaymentType} from '../../types/onyx/WalletTransfer';
import PaymentMethod from '../../types/onyx/PaymentMethod';

type KYCWallRef = {
continue?: () => void;
continueAction?: () => void;
};

/**
Expand All @@ -23,14 +23,14 @@ const kycWallRef = createRef<KYCWallRef>();
* 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() {
Expand Down

0 comments on commit fba87cd

Please sign in to comment.