Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chore: Migrate BaseKYCWall.js to function component #31538

Merged
merged 7 commits into from
Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
237 changes: 132 additions & 105 deletions src/components/KYCWall/BaseKYCWall.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
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';
Expand All @@ -15,90 +15,113 @@ 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;
teneeto marked this conversation as resolved.
Show resolved Hide resolved

// 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.
Expand All @@ -108,82 +131,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
8 changes: 4 additions & 4 deletions src/libs/actions/PaymentMethods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import PaymentMethod from '@src/types/onyx/PaymentMethod';
import {FilterMethodPaymentType} from '@src/types/onyx/WalletTransfer';

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 Expand Up @@ -214,7 +214,7 @@ function clearDebitCardFormErrorAndSubmit() {
Onyx.set(ONYXKEYS.FORMS.ADD_DEBIT_CARD_FORM, {
isLoading: false,
errors: undefined,
setupComplete: false,
teneeto marked this conversation as resolved.
Show resolved Hide resolved
setupComplete: true,
});
}

Expand Down
Loading