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

Invoicing bank accounts section #47218

Merged
Merged
Changes from 1 commit
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
67e37b9
create raw WorkspaceInvoiceVBASection
rezkiy37 Aug 12, 2024
c2b9af4
add invoice types
rezkiy37 Aug 12, 2024
077a401
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Aug 12, 2024
be90481
integrate bank accounts logic
rezkiy37 Aug 12, 2024
8e9d73e
add translations
rezkiy37 Aug 12, 2024
3661b67
integrate add bank account button
rezkiy37 Aug 12, 2024
e7a88a0
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Aug 13, 2024
97d6073
omit card logic
rezkiy37 Aug 13, 2024
54591c2
reuse types
rezkiy37 Aug 13, 2024
2a8f11f
integrate usePaymentMethodState hook
rezkiy37 Aug 13, 2024
cb90633
rename shouldUseSuccessAddBankAccountButton
rezkiy37 Aug 13, 2024
05af88f
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Aug 14, 2024
edd8507
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Sep 2, 2024
2bec476
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Sep 3, 2024
162d4da
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Sep 9, 2024
c3258e2
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Sep 12, 2024
68cb5a2
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Sep 16, 2024
676a0da
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Sep 24, 2024
8094a1a
implement setInvoicingTransferBankAccount
rezkiy37 Sep 25, 2024
6b2fff4
PaymentMethodList accepts invoiceTransferBankAccountID
rezkiy37 Sep 25, 2024
d273a5a
implement default back account in WorkspaceInvoiceVBASection
rezkiy37 Sep 25, 2024
46f5e1b
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Sep 25, 2024
03b76f2
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Sep 26, 2024
386dc9c
Refactor PaymentMethodList component to use useOnyx
rezkiy37 Sep 26, 2024
7b0f675
Merge branches 'feature/45177-invoice-back-accounts-section', 'featur…
rezkiy37 Sep 27, 2024
c8861f3
update disabled prop
rezkiy37 Sep 27, 2024
5ae1ca6
rename shouldShowAddBankAccountButton
rezkiy37 Sep 27, 2024
5635b84
tweak comment
rezkiy37 Sep 27, 2024
2ffc83e
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Sep 30, 2024
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
Prev Previous commit
Next Next commit
integrate bank accounts logic
  • Loading branch information
rezkiy37 committed Aug 12, 2024
commit be904818353197e5f303ac1d9ebdef7da3bf9c9a
294 changes: 289 additions & 5 deletions src/pages/workspace/invoices/WorkspaceInvoiceVBASection.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,46 @@
import React from 'react';
import type {RefObject} from 'react';
import React, {useCallback, useRef, useState} from 'react';
import {View} from 'react-native';
import type {GestureResponderEvent} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import AddPaymentMethodMenu from '@components/AddPaymentMethodMenu';
import ConfirmModal from '@components/ConfirmModal';
import * as Expensicons from '@components/Icon/Expensicons';
import MenuItem from '@components/MenuItem';
import Popover from '@components/Popover';
import Section from '@components/Section';
import useLocalize from '@hooks/useLocalize';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import getClickedTargetLocation from '@libs/getClickedTargetLocation';
import Navigation from '@libs/Navigation/Navigation';
import * as PaymentUtils from '@libs/PaymentUtils';
import PaymentMethodList from '@pages/settings/Wallet/PaymentMethodList';
import type {FormattedSelectedPaymentMethodIcon} from '@pages/settings/Wallet/WalletPage/types';
import variables from '@styles/variables';
import * as BankAccounts from '@userActions/BankAccounts';
import * as PaymentMethods from '@userActions/PaymentMethods';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {AccountData} from '@src/types/onyx';
import {isEmptyObject} from '@src/types/utils/EmptyObject';

type FormattedSelectedPaymentMethod = {
title: string;
icon?: FormattedSelectedPaymentMethodIcon;
description?: string;
type?: string;
};

type PaymentMethodState = {
isSelectedPaymentMethodDefault: boolean;
selectedPaymentMethod: AccountData;
formattedSelectedPaymentMethod: FormattedSelectedPaymentMethod;
methodID: string | number;
selectedPaymentMethodType: string;
};

type WorkspaceInvoiceVBASectionProps = {
/** The policy ID currently being configured */
Expand All @@ -15,8 +50,186 @@ type WorkspaceInvoiceVBASectionProps = {
function WorkspaceInvoiceVBASection({policyID}: WorkspaceInvoiceVBASectionProps) {
const styles = useThemeStyles();
const {shouldUseNarrowLayout} = useResponsiveLayout();
const {windowWidth} = useWindowDimensions();
const {translate} = useLocalize();
const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`);
const [bankAccountList] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST);
const [cardList] = useOnyx(ONYXKEYS.CARD_LIST);
const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET);
const [fundList] = useOnyx(ONYXKEYS.FUND_LIST);
const addPaymentMethodAnchorRef = useRef(null);
const paymentMethodButtonRef = useRef<HTMLDivElement | null>(null);
const [shouldShowAddPaymentMenu, setShouldShowAddPaymentMenu] = useState(false);
const [showConfirmDeleteModal, setShowConfirmDeleteModal] = useState(false);
const [shouldShowDefaultDeleteMenu, setShouldShowDefaultDeleteMenu] = useState(false);
const [paymentMethod, setPaymentMethod] = useState<PaymentMethodState>({
isSelectedPaymentMethodDefault: false,
selectedPaymentMethod: {},
formattedSelectedPaymentMethod: {
title: '',
},
methodID: '',
selectedPaymentMethodType: '',
});
const [anchorPosition, setAnchorPosition] = useState({
anchorPositionHorizontal: 0,
anchorPositionVertical: 0,
anchorPositionTop: 0,
anchorPositionRight: 0,
});
const hasBankAccount = !isEmptyObject(bankAccountList) || !isEmptyObject(fundList);
const hasWallet = !isEmptyObject(userWallet);
const hasAssignedCard = !isEmptyObject(cardList);
const shouldShowEmptyState = !hasBankAccount && !hasWallet && !hasAssignedCard;
// Determines whether or not the modal popup is mounted from the bottom of the screen instead of the side mount on Web or Desktop screens
const isPopoverBottomMount = anchorPosition.anchorPositionTop === 0 || shouldUseNarrowLayout;
const shouldShowMakeDefaultButton =
!paymentMethod.isSelectedPaymentMethodDefault &&
!(paymentMethod.formattedSelectedPaymentMethod.type === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT && paymentMethod.selectedPaymentMethod.type === CONST.BANK_ACCOUNT.TYPE.BUSINESS);

/**
* Set position of the payment menu
*/
const setMenuPosition = useCallback(() => {
if (!paymentMethodButtonRef.current) {
return;
}

const position = getClickedTargetLocation(paymentMethodButtonRef.current);

setAnchorPosition({
anchorPositionTop: position.top + position.height - variables.bankAccountActionPopoverTopSpacing,
// We want the position to be 23px to the right of the left border
anchorPositionRight: windowWidth - position.right + variables.bankAccountActionPopoverRightSpacing,
anchorPositionHorizontal: position.x + (shouldShowEmptyState ? -variables.addPaymentMethodLeftSpacing : variables.addBankAccountLeftSpacing),
anchorPositionVertical: position.y,
});
}, [shouldShowEmptyState, windowWidth]);

/**
* Display the delete/default menu, or the add payment method menu
*/
const paymentMethodPressed = (
nativeEvent?: GestureResponderEvent | KeyboardEvent,
accountType?: string,
account?: AccountData,
icon?: FormattedSelectedPaymentMethodIcon,
isDefault?: boolean,
methodID?: string | number,
) => {
if (shouldShowAddPaymentMenu) {
setShouldShowAddPaymentMenu(false);
return;
}

if (shouldShowDefaultDeleteMenu) {
setShouldShowDefaultDeleteMenu(false);
return;
}
paymentMethodButtonRef.current = nativeEvent?.currentTarget as HTMLDivElement;

// The delete/default menu
if (accountType) {
let formattedSelectedPaymentMethod: FormattedSelectedPaymentMethod = {
title: '',
};
if (accountType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT) {
formattedSelectedPaymentMethod = {
title: account?.addressName ?? '',
icon,
description: PaymentUtils.getPaymentMethodDescription(accountType, account),
type: CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT,
};
}
setPaymentMethod({
isSelectedPaymentMethodDefault: !!isDefault,
selectedPaymentMethod: account ?? {},
selectedPaymentMethodType: accountType,
formattedSelectedPaymentMethod,
methodID: methodID ?? '-1',
});
setShouldShowDefaultDeleteMenu(true);
setMenuPosition();
return;
}
setShouldShowAddPaymentMenu(true);
setMenuPosition();
};

/**
* Hide the add payment modal
*/
const hideAddPaymentMenu = () => {
setShouldShowAddPaymentMenu(false);
};

/**
* Hide the default / delete modal
*/
const hideDefaultDeleteMenu = useCallback(() => {
setShouldShowDefaultDeleteMenu(false);
setShowConfirmDeleteModal(false);
}, [setShouldShowDefaultDeleteMenu, setShowConfirmDeleteModal]);

const deletePaymentMethod = useCallback(() => {
const bankAccountID = paymentMethod.selectedPaymentMethod.bankAccountID;
if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT && bankAccountID) {
BankAccounts.deletePaymentBankAccount(bankAccountID);
}
}, [paymentMethod.selectedPaymentMethod.bankAccountID, paymentMethod.selectedPaymentMethodType]);

const makeDefaultPaymentMethod = useCallback(() => {
const paymentCardList = fundList ?? {};
// Find the previous default payment method so we can revert if the MakeDefaultPaymentMethod command errors
const paymentMethods = PaymentUtils.formatPaymentMethods(bankAccountList ?? {}, paymentCardList, styles);

const previousPaymentMethod = paymentMethods.find((method) => !!method.isDefault);
const currentPaymentMethod = paymentMethods.find((method) => method.methodID === paymentMethod.methodID);
if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT) {
PaymentMethods.makeDefaultPaymentMethod(paymentMethod.selectedPaymentMethod.bankAccountID ?? -1, 0, previousPaymentMethod, currentPaymentMethod);
} else if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.DEBIT_CARD) {
PaymentMethods.makeDefaultPaymentMethod(0, paymentMethod.selectedPaymentMethod.fundID ?? -1, previousPaymentMethod, currentPaymentMethod);
}
}, [
paymentMethod.methodID,
paymentMethod.selectedPaymentMethod.bankAccountID,
paymentMethod.selectedPaymentMethod.fundID,
paymentMethod.selectedPaymentMethodType,
bankAccountList,
fundList,
styles,
]);

const resetSelectedPaymentMethodData = useCallback(() => {
// Reset to same values as in the constructor
setPaymentMethod({
isSelectedPaymentMethodDefault: false,
selectedPaymentMethod: {},
formattedSelectedPaymentMethod: {
title: '',
},
methodID: '',
selectedPaymentMethodType: '',
});
}, [setPaymentMethod]);

/**
* Navigate to the appropriate payment type addition screen
*/
const addPaymentMethodTypePressed = (paymentType: string) => {
hideAddPaymentMenu();

if (paymentType === CONST.PAYMENT_METHODS.DEBIT_CARD) {
Navigation.navigate(ROUTES.SETTINGS_ADD_DEBIT_CARD);
return;
}
if (paymentType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT || paymentType === CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT) {
BankAccounts.openPersonalBankAccountSetupView();
return;
}

throw new Error('Invalid payment method type selected');
};

return (
<Section
Expand All @@ -28,15 +241,86 @@ function WorkspaceInvoiceVBASection({policyID}: WorkspaceInvoiceVBASectionProps)
<PaymentMethodList
shouldShowAddPaymentMethodButton={false}
shouldShowEmptyListMessage={false}
onPress={() => console.debug('onPress')}
// actionPaymentMethodType={shouldShowDefaultDeleteMenu ? paymentMethod.selectedPaymentMethodType : ''}
onPress={paymentMethodPressed}
activePaymentMethodID={policy?.invoice?.bankAccount?.transferBankAccountID ?? ''}
// buttonRef={addPaymentMethodAnchorRef}
// onListContentSizeChange={shouldShowAddPaymentMenu || shouldShowDefaultDeleteMenu ? setMenuPosition : () => {}}
actionPaymentMethodType={shouldShowDefaultDeleteMenu ? paymentMethod.selectedPaymentMethodType : ''}
buttonRef={addPaymentMethodAnchorRef}
shouldEnableScroll={false}
style={[styles.mt5, [shouldUseNarrowLayout ? styles.mhn5 : styles.mhn8]]}
listItemStyle={shouldUseNarrowLayout ? styles.ph5 : styles.ph8}
/>

<Popover
isVisible={shouldShowDefaultDeleteMenu}
onClose={hideDefaultDeleteMenu}
anchorPosition={{
top: anchorPosition.anchorPositionTop,
right: anchorPosition.anchorPositionRight,
}}
anchorRef={paymentMethodButtonRef as RefObject<View>}
>
{!showConfirmDeleteModal && (
<View style={[styles.mv5, !shouldUseNarrowLayout ? styles.sidebarPopover : {}]}>
{isPopoverBottomMount && (
<MenuItem
title={paymentMethod.formattedSelectedPaymentMethod.title}
icon={paymentMethod.formattedSelectedPaymentMethod.icon?.icon}
iconHeight={paymentMethod.formattedSelectedPaymentMethod.icon?.iconHeight ?? paymentMethod.formattedSelectedPaymentMethod.icon?.iconSize}
iconWidth={paymentMethod.formattedSelectedPaymentMethod.icon?.iconWidth ?? paymentMethod.formattedSelectedPaymentMethod.icon?.iconSize}
iconStyles={paymentMethod.formattedSelectedPaymentMethod.icon?.iconStyles}
description={paymentMethod.formattedSelectedPaymentMethod.description}
wrapperStyle={[styles.mb4, styles.ph5, styles.pv0]}
interactive={false}
displayInDefaultIconColor
/>
)}
{shouldShowMakeDefaultButton && (
<MenuItem
title={translate('walletPage.setDefaultConfirmation')}
icon={Expensicons.Mail}
onPress={() => {
makeDefaultPaymentMethod();
setShouldShowDefaultDeleteMenu(false);
}}
wrapperStyle={[styles.pv3, styles.ph5, !shouldUseNarrowLayout ? styles.sidebarPopover : {}]}
/>
)}
<MenuItem
title={translate('common.delete')}
icon={Expensicons.Trashcan}
onPress={() => setShowConfirmDeleteModal(true)}
wrapperStyle={[styles.pv3, styles.ph5, !shouldUseNarrowLayout ? styles.sidebarPopover : {}]}
/>
</View>
)}
<ConfirmModal
isVisible={showConfirmDeleteModal}
onConfirm={() => {
deletePaymentMethod();
hideDefaultDeleteMenu();
}}
onCancel={hideDefaultDeleteMenu}
title={translate('walletPage.deleteAccount')}
prompt={translate('walletPage.deleteConfirmation')}
confirmText={translate('common.delete')}
cancelText={translate('common.cancel')}
shouldShowCancelButton
danger
onModalHide={resetSelectedPaymentMethodData}
/>
</Popover>

<AddPaymentMethodMenu
isVisible={shouldShowAddPaymentMenu}
onClose={hideAddPaymentMenu}
anchorPosition={{
horizontal: anchorPosition.anchorPositionHorizontal,
vertical: anchorPosition.anchorPositionVertical - CONST.MODAL.POPOVER_MENU_PADDING,
}}
onItemSelected={(method: string) => addPaymentMethodTypePressed(method)}
anchorRef={addPaymentMethodAnchorRef}
shouldShowPersonalBankAccountOption
/>
</Section>
);
}
Expand Down