Skip to content

Commit

Permalink
Merge pull request #30582 from Expensify/vit-bottomUp
Browse files Browse the repository at this point in the history
Create v1 Collect workspace Bottom Up flow
  • Loading branch information
mountiny authored Nov 22, 2023
2 parents 33243f3 + 7bf4260 commit 6aa94c9
Show file tree
Hide file tree
Showing 22 changed files with 595 additions and 89 deletions.
8 changes: 7 additions & 1 deletion src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,7 @@ const CONST = {
CREATED: 'CREATED',
IOU: 'IOU',
MODIFIEDEXPENSE: 'MODIFIEDEXPENSE',
MOVED: 'MOVED',
REIMBURSEMENTQUEUED: 'REIMBURSEMENTQUEUED',
RENAMED: 'RENAMED',
REPORTPREVIEW: 'REPORTPREVIEW',
Expand Down Expand Up @@ -1188,7 +1189,8 @@ const CONST = {

PAYMENT_METHODS: {
DEBIT_CARD: 'debitCard',
BANK_ACCOUNT: 'bankAccount',
PERSONAL_BANK_ACCOUNT: 'bankAccount',
BUSINESS_BANK_ACCOUNT: 'businessBankAccount',
},

PAYMENT_METHOD_ID_KEYS: {
Expand Down Expand Up @@ -1278,7 +1280,11 @@ const CONST = {
TYPE: {
FREE: 'free',
PERSONAL: 'personal',

// Often referred to as "control" workspaces
CORPORATE: 'corporate',

// Often referred to as "collect" workspaces
TEAM: 'team',
},
ROLE: {
Expand Down
73 changes: 53 additions & 20 deletions src/components/AddPaymentMethodMenu.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import lodashGet from 'lodash/get';
import PropTypes from 'prop-types';
import React from 'react';
import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
import useLocalize from '@hooks/useLocalize';
import compose from '@libs/compose';
import Permissions from '@libs/Permissions';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import * as ReportUtils from '@libs/ReportUtils';
import iouReportPropTypes from '@pages/iouReportPropTypes';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import * as Expensicons from './Icon/Expensicons';
import PopoverMenu from './PopoverMenu';
import refPropTypes from './refPropTypes';
import withLocalize, {withLocalizePropTypes} from './withLocalize';
import withWindowDimensions from './withWindowDimensions';

const propTypes = {
Expand All @@ -19,6 +23,12 @@ const propTypes = {
/** Callback to execute when the component closes. */
onClose: PropTypes.func.isRequired,

/** Callback to execute when the payment method is selected. */
onItemSelected: PropTypes.func.isRequired,

/** The IOU/Expense report we are paying */
iouReport: iouReportPropTypes,

/** Anchor position for the AddPaymentMenu. */
anchorPosition: PropTypes.shape({
horizontal: PropTypes.number,
Expand All @@ -37,42 +47,63 @@ const propTypes = {
/** Popover anchor ref */
anchorRef: refPropTypes,

...withLocalizePropTypes,
/** Session info for the currently logged in user. */
session: PropTypes.shape({
/** Currently logged in user accountID */
accountID: PropTypes.number,
}),
};

const defaultProps = {
iouReport: {},
anchorPosition: {},
anchorAlignment: {
horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT,
vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM,
},
betas: [],
anchorRef: () => {},
session: {},
};

function AddPaymentMethodMenu(props) {
function AddPaymentMethodMenu({isVisible, onClose, anchorPosition, anchorAlignment, anchorRef, iouReport, onItemSelected, session, betas}) {
const {translate} = useLocalize();

return (
<PopoverMenu
isVisible={props.isVisible}
onClose={props.onClose}
anchorPosition={props.anchorPosition}
anchorAlignment={props.anchorAlignment}
anchorRef={props.anchorRef}
onItemSelected={props.onClose}
isVisible={isVisible}
onClose={onClose}
anchorPosition={anchorPosition}
anchorAlignment={anchorAlignment}
anchorRef={anchorRef}
onItemSelected={onClose}
menuItems={[
{
text: props.translate('common.bankAccount'),
icon: Expensicons.Bank,
onSelected: () => {
props.onItemSelected(CONST.PAYMENT_METHODS.BANK_ACCOUNT);
},
},
...(Permissions.canUseWallet(props.betas)
...(ReportUtils.isIOUReport(iouReport)
? [
{
text: props.translate('common.debitCard'),
text: translate('common.personalBankAccount'),
icon: Expensicons.Bank,
onSelected: () => {
onItemSelected(CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT);
},
},
]
: []),
...(!ReportActionsUtils.hasRequestFromCurrentAccount(lodashGet(iouReport, 'reportID', 0), lodashGet(session, 'accountID', 0))
? [
{
text: translate('common.businessBankAccount'),
icon: Expensicons.Building,
onSelected: () => onItemSelected(CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT),
},
]
: []),
...(Permissions.canUseWallet(betas)
? [
{
text: translate('common.debitCard'),
icon: Expensicons.CreditCard,
onSelected: () => props.onItemSelected(CONST.PAYMENT_METHODS.DEBIT_CARD),
onSelected: () => onItemSelected(CONST.PAYMENT_METHODS.DEBIT_CARD),
},
]
: []),
Expand All @@ -88,10 +119,12 @@ AddPaymentMethodMenu.displayName = 'AddPaymentMethodMenu';

export default compose(
withWindowDimensions,
withLocalize,
withOnyx({
betas: {
key: ONYXKEYS.BETAS,
},
session: {
key: ONYXKEYS.SESSION,
},
}),
)(AddPaymentMethodMenu);
16 changes: 14 additions & 2 deletions src/components/KYCWall/BaseKYCWall.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ 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 Policy from '@userActions/Policy';
import * as Wallet from '@userActions/Wallet';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import {defaultProps, propTypes} from './kycWallPropTypes';

// 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
Expand Down Expand Up @@ -93,10 +95,19 @@ class KYCWall extends React.Component {
*/
selectPaymentMethod(paymentMethod) {
this.props.onSelectPaymentMethod(paymentMethod);
if (paymentMethod === CONST.PAYMENT_METHODS.BANK_ACCOUNT) {
if (paymentMethod === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT) {
Navigation.navigate(this.props.addBankAccountRoute);
} else if (paymentMethod === CONST.PAYMENT_METHODS.DEBIT_CARD) {
Navigation.navigate(this.props.addDebitCardRoute);
} else if (paymentMethod === CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT) {
if (ReportUtils.isIOUReport(this.props.iouReport)) {
const policyID = Policy.createWorkspaceFromIOUPayment(this.props.iouReport);

// Navigate to the bank account set up flow for this specific policy
Navigation.navigate(ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.getRoute('', policyID));
return;
}
Navigation.navigate(this.props.addBankAccountRoute);
}
}

Expand Down Expand Up @@ -135,7 +146,7 @@ class KYCWall extends React.Component {
) {
Log.info('[KYC Wallet] User does not have valid payment method');
if (!this.props.shouldIncludeDebitCard) {
this.selectPaymentMethod(CONST.PAYMENT_METHODS.BANK_ACCOUNT);
this.selectPaymentMethod(CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT);
return;
}
const clickedElementLocation = getClickedTargetLocation(targetElement);
Expand Down Expand Up @@ -164,6 +175,7 @@ class KYCWall extends React.Component {
<>
<AddPaymentMethodMenu
isVisible={this.state.shouldShowAddPaymentMenu}
iouReport={this.props.iouReport}
onClose={() => this.setState({shouldShowAddPaymentMenu: false})}
anchorRef={this.anchorRef}
anchorPosition={{
Expand Down
2 changes: 0 additions & 2 deletions src/components/MoneyReportHeader.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,6 @@ function MoneyReportHeader({session, personalDetails, policy, chatReport, nextSt
onPress={(paymentType) => IOU.payMoneyRequest(paymentType, chatReport, moneyRequestReport)}
enablePaymentsRoute={ROUTES.ENABLE_PAYMENTS}
addBankAccountRoute={bankAccountRoute}
shouldShowPaymentOptions
style={[styles.pv2]}
formattedAmount={formattedAmount}
/>
Expand Down Expand Up @@ -164,7 +163,6 @@ function MoneyReportHeader({session, personalDetails, policy, chatReport, nextSt
onPress={(paymentType) => IOU.payMoneyRequest(paymentType, chatReport, moneyRequestReport)}
enablePaymentsRoute={ROUTES.ENABLE_PAYMENTS}
addBankAccountRoute={bankAccountRoute}
shouldShowPaymentOptions
formattedAmount={formattedAmount}
/>
</View>
Expand Down
1 change: 0 additions & 1 deletion src/components/MoneyRequestConfirmationList.js
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,6 @@ function MoneyRequestConfirmationList(props) {
addDebitCardRoute={ROUTES.IOU_SEND_ADD_DEBIT_CARD}
currency={props.iouCurrencyCode}
policyID={props.policyID}
shouldShowPaymentOptions
buttonSize={CONST.DROPDOWN_BUTTON_SIZE.LARGE}
kycWallAnchorAlignment={{
horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT,
Expand Down
1 change: 0 additions & 1 deletion src/components/ReportActionItem/ReportPreview.js
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,6 @@ function ReportPreview(props) {
onPress={(paymentType) => IOU.payMoneyRequest(paymentType, props.chatReport, props.iouReport)}
enablePaymentsRoute={ROUTES.ENABLE_PAYMENTS}
addBankAccountRoute={bankAccountRoute}
shouldShowPaymentOptions
style={[styles.mt3]}
kycWallAnchorAlignment={{
horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT,
Expand Down
34 changes: 3 additions & 31 deletions src/components/SettlementButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,6 @@ const propTypes = {
/** The route to redirect if user does not have a payment method setup */
enablePaymentsRoute: PropTypes.string.isRequired,

/** Should we show the payment options? */
shouldShowPaymentOptions: PropTypes.bool,

/** The last payment method used per policy */
nvp_lastPaymentMethod: PropTypes.objectOf(PropTypes.string),

Expand Down Expand Up @@ -97,7 +94,6 @@ const defaultProps = {
betas: CONST.EMPTY_ARRAY,
iouReport: CONST.EMPTY_OBJECT,
nvp_lastPaymentMethod: CONST.EMPTY_OBJECT,
shouldShowPaymentOptions: false,
style: [],
policyID: '',
formattedAmount: '',
Expand Down Expand Up @@ -130,7 +126,6 @@ function SettlementButton({
onPress,
pressOnEnter,
policyID,
shouldShowPaymentOptions,
style,
}) {
const {translate} = useLocalize();
Expand Down Expand Up @@ -164,42 +159,19 @@ function SettlementButton({

// To achieve the one tap pay experience we need to choose the correct payment type as default,
// if user already paid for some request or expense, let's use the last payment method or use default.
let paymentMethod = nvp_lastPaymentMethod[policyID] || '';
if (!shouldShowPaymentOptions) {
if (!paymentMethod) {
// In case the user hasn't paid a request yet, let's default to VBBA payment type in case of expense reports
if (isExpenseReport) {
paymentMethod = CONST.IOU.PAYMENT_TYPE.VBBA;
} else if (canUseWallet) {
// If they have Wallet set up, use that payment method as default
paymentMethod = CONST.IOU.PAYMENT_TYPE.EXPENSIFY;
} else {
paymentMethod = CONST.IOU.PAYMENT_TYPE.ELSEWHERE;
}
}

// In case of the settlement button in the report preview component, we do not show payment options and the label for Wallet and ACH type is simply "Pay".
return [
{
...paymentMethods[paymentMethod],
text: paymentMethod === CONST.IOU.PAYMENT_TYPE.ELSEWHERE ? translate('iou.payElsewhere') : translate('iou.pay'),
},
];
}
const paymentMethod = nvp_lastPaymentMethod[policyID] || '';
if (canUseWallet) {
buttonOptions.push(paymentMethods[CONST.IOU.PAYMENT_TYPE.EXPENSIFY]);
}
if (isExpenseReport) {
buttonOptions.push(paymentMethods[CONST.IOU.PAYMENT_TYPE.VBBA]);
}
buttonOptions.push(paymentMethods[CONST.IOU.PAYMENT_TYPE.VBBA]);
buttonOptions.push(paymentMethods[CONST.IOU.PAYMENT_TYPE.ELSEWHERE]);

// Put the preferred payment method to the front of the array so its shown as default
if (paymentMethod) {
return _.sortBy(buttonOptions, (method) => (method.value === paymentMethod ? 0 : 1));
}
return buttonOptions;
}, [betas, currency, formattedAmount, iouReport, nvp_lastPaymentMethod, policyID, shouldShowPaymentOptions, translate]);
}, [betas, currency, formattedAmount, iouReport, nvp_lastPaymentMethod, policyID, translate]);

const selectPaymentType = (event, iouPaymentType, triggerKYCFlow) => {
if (iouPaymentType === CONST.IOU.PAYMENT_TYPE.EXPENSIFY || iouPaymentType === CONST.IOU.PAYMENT_TYPE.VBBA) {
Expand Down
2 changes: 2 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,8 @@ export default {
more: 'More',
debitCard: 'Debit card',
bankAccount: 'Bank account',
personalBankAccount: 'Personal bank account',
businessBankAccount: 'Business bank account',
join: 'Join',
leave: 'Leave',
decline: 'Decline',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,8 @@ export default {
more: 'Más',
debitCard: 'Tarjeta de débito',
bankAccount: 'Cuenta bancaria',
personalBankAccount: 'Cuenta bancaria personal',
businessBankAccount: 'Cuenta bancaria comercial',
join: 'Unirse',
leave: 'Salir',
decline: 'Rechazar',
Expand Down
2 changes: 1 addition & 1 deletion src/libs/PaymentUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ function hasExpensifyPaymentMethod(fundList: Record<string, Fund>, bankAccountLi

function getPaymentMethodDescription(accountType: AccountType, account: BankAccount['accountData'] | Fund['accountData']): string {
if (account) {
if (accountType === CONST.PAYMENT_METHODS.BANK_ACCOUNT && 'accountNumber' in account) {
if (accountType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT && 'accountNumber' in account) {
return `${Localize.translateLocal('paymentMethodList.accountLastFour')} ${account.accountNumber?.slice(-4)}`;
}
if (accountType === CONST.PAYMENT_METHODS.DEBIT_CARD && 'cardNumber' in account) {
Expand Down
23 changes: 22 additions & 1 deletion src/libs/ReportActionsUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ function isDeletedParentAction(reportAction: OnyxEntry<ReportAction>): boolean {
}

function isReversedTransaction(reportAction: OnyxEntry<ReportAction>) {
return (reportAction?.message?.[0].isReversedTransaction ?? false) && (reportAction?.childVisibleActionCount ?? 0) > 0;
return (reportAction?.message?.[0]?.isReversedTransaction ?? false) && (reportAction?.childVisibleActionCount ?? 0) > 0;
}

function isPendingRemove(reportAction: OnyxEntry<ReportAction>): boolean {
Expand Down Expand Up @@ -631,6 +631,26 @@ function isNotifiableReportAction(reportAction: OnyxEntry<ReportAction>): boolea
return actions.includes(reportAction.actionName);
}

/**
* Helper method to determine if the provided accountID has made a request on the specified report.
*
* @param reportID
* @param currentAccountID
* @returns
*/
function hasRequestFromCurrentAccount(reportID: string, currentAccountID: number): boolean {
if (!reportID) {
return false;
}

const reportActions = Object.values(getAllReportActions(reportID));
if (reportActions.length === 0) {
return false;
}

return reportActions.some((action) => action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && action.actorAccountID === currentAccountID);
}

export {
extractLinksFromMessageHtml,
getAllReportActions,
Expand Down Expand Up @@ -671,6 +691,7 @@ export {
isReimbursementQueuedAction,
shouldReportActionBeVisible,
shouldReportActionBeVisibleAsLastAction,
hasRequestFromCurrentAccount,
getFirstVisibleReportActionID,
isChannelLogMemberAction,
};
Loading

0 comments on commit 6aa94c9

Please sign in to comment.