-
Notifications
You must be signed in to change notification settings - Fork 3k
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
feat: migrate KYCWall class component to functional #28798
Changes from all commits
2df7e2c
1390178
b981474
851b3ac
29c05bd
b6b6be1
52f43e4
d1db95f
280e4e5
89b5618
f27220a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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'; | ||
|
@@ -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. | ||
|
@@ -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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Functional components are expected to have a displayName value There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @abdulrahuman5196 I am aware of this, however this component is exported from the
EDIT: I've actually added the |
||
KYCWall.defaultProps = defaultProps; | ||
KYCWall.displayName = 'BaseKYCWall'; | ||
|
||
export default withOnyx({ | ||
userWallet: { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
btw had to change the name as
continue
is a forbidden keyword