Skip to content

Commit eee5400

Browse files
authored
Merge pull request Expensify#56838 from getusha/feat-standardize-pay-button
Feat standardize pay button
2 parents ebcc0b5 + c914862 commit eee5400

57 files changed

Lines changed: 1434 additions & 315 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/CONST/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7191,9 +7191,9 @@ const CONST = {
71917191
},
71927192
LAST_PAYMENT_METHOD: {
71937193
LAST_USED: 'lastUsed',
7194-
IOU: 'Iou',
7195-
EXPENSE: 'Expense',
7196-
INVOICE: 'Invoice',
7194+
IOU: 'iou',
7195+
EXPENSE: 'expense',
7196+
INVOICE: 'invoice',
71977197
},
71987198
SKIPPABLE_COLLECTION_MEMBER_IDS: [String(DEFAULT_NUMBER_ID), '-1', 'undefined', 'null', 'NaN'] as string[],
71997199
SETUP_SPECIALIST_LOGIN: 'Setup Specialist',

src/components/Button/index.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,19 @@ function Button(
310310
const textComponent = secondLineText ? (
311311
<View style={[styles.alignItemsCenter, styles.flexColumn, styles.flexShrink1]}>
312312
{primaryText}
313-
<Text style={[isLoading && styles.opacity0, styles.pointerEventsNone, styles.fontWeightNormal, styles.textDoubleDecker]}>{secondLineText}</Text>
313+
<Text
314+
style={[
315+
isLoading && styles.opacity0,
316+
styles.pointerEventsNone,
317+
styles.fontWeightNormal,
318+
styles.textDoubleDecker,
319+
!!secondLineText && styles.textExtraSmallSupporting,
320+
styles.textWhite,
321+
styles.textBold,
322+
]}
323+
>
324+
{secondLineText}
325+
</Text>
314326
</View>
315327
) : (
316328
primaryText

src/components/ButtonWithDropdownMenu/index.tsx

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ function ButtonWithDropdownMenu<IValueType>({
5050
testID,
5151
secondLineText = '',
5252
icon,
53+
shouldPopoverUseScrollView = false,
54+
containerStyles,
5355
shouldUseModalPaddingStyle = true,
5456
}: ButtonWithDropdownMenuProps<IValueType>) {
5557
const theme = useTheme();
@@ -73,6 +75,10 @@ function ButtonWithDropdownMenu<IValueType>({
7375
const isButtonSizeLarge = buttonSize === CONST.DROPDOWN_BUTTON_SIZE.LARGE;
7476
const nullCheckRef = (ref: RefObject<View | null>) => ref ?? null;
7577

78+
useEffect(() => {
79+
setSelectedItemIndex(defaultSelectedIndex);
80+
}, [defaultSelectedIndex]);
81+
7682
useEffect(() => {
7783
if (!dropdownAnchor.current) {
7884
return;
@@ -233,17 +239,27 @@ function ButtonWithDropdownMenu<IValueType>({
233239
// eslint-disable-next-line react-compiler/react-compiler
234240
anchorRef={nullCheckRef(dropdownAnchor)}
235241
withoutOverlay
236-
shouldUseScrollView
237242
scrollContainerStyle={!shouldUseModalPaddingStyle && isSmallScreenWidth && styles.pv4}
238-
shouldUseModalPaddingStyle={shouldUseModalPaddingStyle}
239243
anchorAlignment={anchorAlignment}
244+
shouldUseModalPaddingStyle={shouldUseModalPaddingStyle}
240245
headerText={menuHeaderText}
246+
shouldUseScrollView={shouldPopoverUseScrollView}
247+
containerStyles={containerStyles}
241248
menuItems={options.map((item, index) => ({
242249
...item,
243250
onSelected: item.onSelected
244-
? () => item.onSelected?.()
251+
? () => {
252+
item.onSelected?.();
253+
if (item.shouldUpdateSelectedIndex) {
254+
setSelectedItemIndex(index);
255+
}
256+
}
245257
: () => {
246258
onOptionSelected?.(item);
259+
if (!item.shouldUpdateSelectedIndex && typeof item.shouldUpdateSelectedIndex === 'boolean') {
260+
return;
261+
}
262+
247263
setSelectedItemIndex(index);
248264
},
249265
shouldCallAfterModalHide: true,

src/components/ButtonWithDropdownMenu/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ type DropdownOption<TValueType> = {
4040
descriptionTextStyle?: StyleProp<TextStyle>;
4141
wrapperStyle?: StyleProp<ViewStyle>;
4242
displayInDefaultIconColor?: boolean;
43+
/** Whether the selected index should be updated when the option is selected even if we have onSelected callback */
44+
shouldUpdateSelectedIndex?: boolean;
4345
subMenuItems?: PopoverMenuItem[];
4446
backButtonText?: string;
4547
avatarSize?: ValueOf<typeof CONST.AVATAR_SIZE>;
@@ -140,6 +142,12 @@ type ButtonWithDropdownMenuProps<TValueType> = {
140142
/** Icon for main button */
141143
icon?: IconAsset;
142144

145+
/** Whether the popover content should be scrollable */
146+
shouldPopoverUseScrollView?: boolean;
147+
148+
/** Container style to be applied to the popover of the dropdown menu */
149+
containerStyles?: StyleProp<ViewStyle>;
150+
143151
/** Whether to use modal padding style for the popover menu */
144152
shouldUseModalPaddingStyle?: boolean;
145153
};

src/components/Icon/BankIcons/index.native.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import GenericBank from '@assets/images/bank-icons/generic-bank-account.svg';
21
import GenericBankCard from '@assets/images/cardicons/generic-bank-card.svg';
32
import type {BankIconParams} from '@components/Icon/BankIconsUtils';
43
import {getBankIconAsset, getBankNameKey} from '@components/Icon/BankIconsUtils';
4+
import {Bank} from '@components/Icon/Expensicons';
55
import variables from '@styles/variables';
66
import CONST from '@src/CONST';
77
import type {BankIcon} from '@src/types/onyx/Bank';
@@ -11,7 +11,7 @@ import type {BankIcon} from '@src/types/onyx/Bank';
1111
*/
1212
export default function getBankIcon({styles, bankName, isCard = false}: BankIconParams): BankIcon {
1313
const bankIcon: BankIcon = {
14-
icon: isCard ? GenericBankCard : GenericBank,
14+
icon: isCard ? GenericBankCard : Bank,
1515
};
1616
if (bankName) {
1717
const bankNameKey = getBankNameKey(bankName.toLowerCase());

src/components/KYCWall/BaseKYCWall.tsx

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,20 @@ import type {EmitterSubscription, GestureResponderEvent, View} from 'react-nativ
55
import AddPaymentMethodMenu from '@components/AddPaymentMethodMenu';
66
import useOnyx from '@hooks/useOnyx';
77
import {openPersonalBankAccountSetupView} from '@libs/actions/BankAccounts';
8-
import {completePaymentOnboarding} from '@libs/actions/IOU';
8+
import {completePaymentOnboarding, savePreferredPaymentMethod} from '@libs/actions/IOU';
9+
import {moveIOUReportToPolicy, moveIOUReportToPolicyAndInviteSubmitter} from '@libs/actions/Report';
910
import getClickedTargetLocation from '@libs/getClickedTargetLocation';
1011
import Log from '@libs/Log';
1112
import Navigation from '@libs/Navigation/Navigation';
1213
import {hasExpensifyPaymentMethod} from '@libs/PaymentUtils';
13-
import {isExpenseReport as isExpenseReportReportUtils, isIOUReport} from '@libs/ReportUtils';
14+
import {getPolicyExpenseChat, isExpenseReport as isExpenseReportReportUtils, isIOUReport} from '@libs/ReportUtils';
1415
import {kycWallRef} from '@userActions/PaymentMethods';
1516
import {createWorkspaceFromIOUPayment} from '@userActions/Policy/Policy';
1617
import {setKYCWallSource} from '@userActions/Wallet';
1718
import CONST from '@src/CONST';
1819
import ONYXKEYS from '@src/ONYXKEYS';
1920
import ROUTES from '@src/ROUTES';
21+
import type {Policy} from '@src/types/onyx';
2022
import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage';
2123
import viewRef from '@src/types/utils/viewRef';
2224
import type {AnchorPosition, DomRect, KYCWallProps, PaymentMethod} from './types';
@@ -45,11 +47,11 @@ function KYCWall({
4547
source,
4648
shouldShowPersonalBankAccountOption = false,
4749
}: KYCWallProps) {
48-
const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET);
49-
const [walletTerms] = useOnyx(ONYXKEYS.WALLET_TERMS);
50-
const [fundList] = useOnyx(ONYXKEYS.FUND_LIST);
51-
const [bankAccountList = {}] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST);
52-
const [reimbursementAccount] = useOnyx(ONYXKEYS.REIMBURSEMENT_ACCOUNT);
50+
const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET, {canBeMissing: true});
51+
const [walletTerms] = useOnyx(ONYXKEYS.WALLET_TERMS, {canBeMissing: true});
52+
const [fundList] = useOnyx(ONYXKEYS.FUND_LIST, {canBeMissing: true});
53+
const [bankAccountList = {}] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST, {canBeMissing: true});
54+
const [reimbursementAccount] = useOnyx(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {canBeMissing: true});
5355

5456
const anchorRef = useRef<HTMLDivElement | View>(null);
5557
const transferBalanceButtonRef = useRef<HTMLDivElement | View | null>(null);
@@ -100,24 +102,48 @@ function KYCWall({
100102
}, [getAnchorPosition]);
101103

102104
const selectPaymentMethod = useCallback(
103-
(paymentMethod: PaymentMethod) => {
104-
onSelectPaymentMethod(paymentMethod);
105+
(paymentMethod?: PaymentMethod, policy?: Policy) => {
106+
if (paymentMethod) {
107+
onSelectPaymentMethod(paymentMethod);
108+
}
105109

106110
if (paymentMethod === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT) {
107111
openPersonalBankAccountSetupView();
108112
} else if (paymentMethod === CONST.PAYMENT_METHODS.DEBIT_CARD) {
109113
Navigation.navigate(addDebitCardRoute ?? ROUTES.HOME);
110-
} else if (paymentMethod === CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT) {
114+
} else if (paymentMethod === CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT || policy) {
111115
if (iouReport && isIOUReport(iouReport)) {
116+
if (policy) {
117+
const policyExpenseChatReportID = getPolicyExpenseChat(iouReport.ownerAccountID, policy.id)?.reportID;
118+
if (!policyExpenseChatReportID) {
119+
const {policyExpenseChatReportID: newPolicyExpenseChatReportID} = moveIOUReportToPolicyAndInviteSubmitter(iouReport.reportID, policy.id) ?? {};
120+
savePreferredPaymentMethod(iouReport.policyID, policy.id, CONST.LAST_PAYMENT_METHOD.IOU);
121+
Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(newPolicyExpenseChatReportID));
122+
} else {
123+
moveIOUReportToPolicy(iouReport.reportID, policy.id, true);
124+
savePreferredPaymentMethod(iouReport.policyID, policy.id, CONST.LAST_PAYMENT_METHOD.IOU);
125+
Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(policyExpenseChatReportID));
126+
}
127+
128+
if (policy?.achAccount) {
129+
return;
130+
}
131+
// Navigate to the bank account set up flow for this specific policy
132+
Navigation.navigate(ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.getRoute(policy.id));
133+
return;
134+
}
135+
112136
const {policyID, workspaceChatReportID, reportPreviewReportActionID, adminsChatReportID} = createWorkspaceFromIOUPayment(iouReport) ?? {};
137+
if (policyID) {
138+
savePreferredPaymentMethod(iouReport.policyID, policyID, CONST.LAST_PAYMENT_METHOD.IOU);
139+
}
113140
completePaymentOnboarding(CONST.PAYMENT_SELECTED.BBA, adminsChatReportID, policyID);
114141
if (workspaceChatReportID) {
115142
Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(workspaceChatReportID, reportPreviewReportActionID));
116143
}
117144

118145
// Navigate to the bank account set up flow for this specific policy
119146
Navigation.navigate(ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.getRoute(policyID));
120-
121147
return;
122148
}
123149
Navigation.navigate(addBankAccountRoute);
@@ -133,7 +159,7 @@ function KYCWall({
133159
*
134160
*/
135161
const continueAction = useCallback(
136-
(event?: GestureResponderEvent | KeyboardEvent, iouPaymentType?: PaymentMethodType) => {
162+
(event?: GestureResponderEvent | KeyboardEvent, iouPaymentType?: PaymentMethodType, paymentMethod?: PaymentMethod, policy?: Policy) => {
137163
const currentSource = walletTerms?.source ?? source;
138164

139165
/**
@@ -170,6 +196,13 @@ function KYCWall({
170196
const clickedElementLocation = getClickedTargetLocation(targetElement as HTMLDivElement);
171197
const position = getAnchorPosition(clickedElementLocation);
172198

199+
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
200+
if (paymentMethod || policy) {
201+
setShouldShowAddPaymentMenu(false);
202+
selectPaymentMethod(paymentMethod, policy);
203+
return;
204+
}
205+
173206
setPositionAddPaymentMenu(position);
174207
setShouldShowAddPaymentMenu(true);
175208

src/components/KYCWall/types.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type {OnyxEntry} from 'react-native-onyx';
44
import type {ValueOf} from 'type-fest';
55
import type CONST from '@src/CONST';
66
import type {Route} from '@src/ROUTES';
7-
import type {Report} from '@src/types/onyx';
7+
import type {Policy, Report} from '@src/types/onyx';
88
import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage';
99
import type AnchorAlignment from '@src/types/utils/AnchorAlignment';
1010

@@ -63,6 +63,9 @@ type KYCWallProps = {
6363

6464
/** Children to build the KYC */
6565
children: (continueAction: (event: GestureResponderEvent | KeyboardEvent | undefined, method?: PaymentMethodType) => void, anchorRef: RefObject<View | null>) => void;
66+
67+
/** The policy used for payment */
68+
policy?: Policy;
6669
};
6770

6871
export type {AnchorPosition, KYCWallProps, PaymentMethod, DomRect, PaymentMethodType, Source};

src/components/MoneyReportHeader.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ function MoneyReportHeader({
315315
payInvoice(type, chatReport, moneyRequestReport, payAsBusiness, methodID, paymentMethod);
316316
} else {
317317
startAnimation();
318-
payMoneyRequest(type, chatReport, moneyRequestReport, true);
318+
payMoneyRequest(type, chatReport, moneyRequestReport, undefined, true);
319319
}
320320
},
321321
[chatReport, isAnyTransactionOnHold, isDelegateAccessRestricted, showDelegateNoAccessModal, isInvoiceReport, moneyRequestReport, startAnimation],
@@ -525,6 +525,7 @@ function MoneyReportHeader({
525525
isPaidAnimationRunning={isPaidAnimationRunning}
526526
isApprovedAnimationRunning={isApprovedAnimationRunning}
527527
onAnimationFinish={stopAnimation}
528+
formattedAmount={totalAmount}
528529
canIOUBePaid
529530
onlyShowPayElsewhere={onlyShowPayElsewhere}
530531
currency={moneyRequestReport?.currency}

src/components/PopoverMenu.tsx

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -312,13 +312,10 @@ function PopoverMenu({
312312
}
313313
setFocusedIndex(menuIndex);
314314
}}
315-
wrapperStyle={StyleUtils.getItemBackgroundColorStyle(
316-
!!item.isSelected,
317-
focusedIndex === menuIndex,
318-
item.disabled ?? false,
319-
theme.activeComponentBG,
320-
theme.hoverComponentBG,
321-
)}
315+
wrapperStyle={[
316+
StyleUtils.getItemBackgroundColorStyle(!!item.isSelected, focusedIndex === menuIndex, item.disabled ?? false, theme.activeComponentBG, theme.hoverComponentBG),
317+
shouldUseScrollView && StyleUtils.getOptionMargin(menuIndex, currentMenuItems.length - 1),
318+
]}
322319
shouldRemoveHoverBackground={item.isSelected}
323320
titleStyle={StyleSheet.flatten([styles.flex1, item.titleStyle])}
324321
// Spread other props dynamically

src/components/ProcessMoneyReportHoldMenu.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import useLocalize from '@hooks/useLocalize';
44
import useResponsiveLayout from '@hooks/useResponsiveLayout';
55
import Navigation from '@libs/Navigation/Navigation';
66
import {isLinkedTransactionHeld} from '@libs/ReportActionsUtils';
7-
import * as IOU from '@userActions/IOU';
7+
import {approveMoneyRequest, payMoneyRequest} from '@userActions/IOU';
88
import CONST from '@src/CONST';
99
import ROUTES from '@src/ROUTES';
1010
import type * as OnyxTypes from '@src/types/onyx';
@@ -69,15 +69,15 @@ function ProcessMoneyReportHoldMenu({
6969
if (startAnimation) {
7070
startAnimation();
7171
}
72-
IOU.approveMoneyRequest(moneyRequestReport, full);
73-
if (!full && isLinkedTransactionHeld(Navigation.getTopmostReportActionId() ?? '-1', moneyRequestReport?.reportID ?? '')) {
74-
Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(moneyRequestReport?.reportID ?? ''));
72+
approveMoneyRequest(moneyRequestReport, full);
73+
if (!full && isLinkedTransactionHeld(Navigation.getTopmostReportActionId(), moneyRequestReport?.reportID)) {
74+
Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(moneyRequestReport?.reportID));
7575
}
7676
} else if (chatReport && paymentType) {
7777
if (startAnimation) {
7878
startAnimation();
7979
}
80-
IOU.payMoneyRequest(paymentType, chatReport, moneyRequestReport, full);
80+
payMoneyRequest(paymentType, chatReport, moneyRequestReport, undefined, full);
8181
}
8282
onClose();
8383
};

0 commit comments

Comments
 (0)