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

[InternalQA] Bring in Settle Up feature for workspace feeds on monthly settlement frequency #55607

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
5cc68ab
feat: add settlement date info
koko57 Jan 20, 2025
f39f073
feat: add translations and settle balance button
koko57 Jan 20, 2025
da5d7b0
fix: move the button to the card list label component
koko57 Jan 21, 2025
0302a2c
fix: button styles for mobile
koko57 Jan 21, 2025
a78be2d
feat: add new onyx key, move the text inside the label
koko57 Jan 21, 2025
10da849
fix:fix the conditions to display the button and the text
koko57 Jan 21, 2025
cd72c63
fix: apply the proper design for smaller screens
koko57 Jan 22, 2025
4553035
feat: add a new write command
koko57 Jan 22, 2025
7d41f47
feat: create queueExpensifyCardForBilling action
koko57 Jan 22, 2025
0efa773
fix: use the old design when button is not displayed
koko57 Jan 22, 2025
98a9770
feat: assign the function to the button, fix a typo in the request name
koko57 Jan 22, 2025
d00f249
feat: show the settlement date
koko57 Jan 22, 2025
2ec830e
fix: resolve conflicts
koko57 Jan 22, 2025
d6c5e98
fix: lint
koko57 Jan 23, 2025
82ecc46
fix: lint once again
koko57 Jan 23, 2025
f3d9924
Merge branch 'main' into feat/55158-settle-up-feature
koko57 Jan 24, 2025
93f5130
fix: add error message, minor fix
koko57 Jan 24, 2025
bc4c3e0
Merge branch 'main' into feat/55158-settle-up-feature
koko57 Jan 29, 2025
1bd6110
fix: change the button location
koko57 Jan 29, 2025
17169ff
fix: minor fix
koko57 Jan 29, 2025
2c168c1
fix: remove isSmallScreen usage, minor style fix
koko57 Jan 29, 2025
e57ea72
Merge branch 'main' into feat/55158-settle-up-feature
koko57 Jan 30, 2025
dfc0a06
fix: make all the columns the same width
koko57 Jan 30, 2025
e6b0937
Merge branch 'main' into feat/55158-settle-up-feature
koko57 Jan 31, 2025
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
4 changes: 4 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,9 @@ const ONYXKEYS = {
/** Expensify cards settings */
PRIVATE_EXPENSIFY_CARD_SETTINGS: 'private_expensifyCardSettings_',

/** Expensify cards manual billing setting */
PRIVATE_EXPENSIFY_CARD_MANUAL_BILLING: 'private_expensifyCardManualBilling_',

/** Stores which connection is set up to use Continuous Reconciliation */
EXPENSIFY_CARD_CONTINUOUS_RECONCILIATION_CONNECTION: 'expensifyCard_continuousReconciliationConnection_',

Expand Down Expand Up @@ -898,6 +901,7 @@ type OnyxCollectionValuesMapping = {
[ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END]: OnyxTypes.BillingGraceEndPeriod;
[ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER]: OnyxTypes.CardFeeds;
[ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS]: OnyxTypes.ExpensifyCardSettings;
[ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_MANUAL_BILLING]: boolean;
[ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST]: OnyxTypes.WorkspaceCardsList;
[ONYXKEYS.COLLECTION.EXPENSIFY_CARD_CONTINUOUS_RECONCILIATION_CONNECTION]: OnyxTypes.PolicyConnectionName;
[ONYXKEYS.COLLECTION.EXPENSIFY_CARD_USE_CONTINUOUS_RECONCILIATION]: boolean;
Expand Down
3 changes: 3 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ import type {
SetTheRequestParams,
SettledAfterAddedBankAccountParams,
SettleExpensifyCardParams,
SettlementDateParams,
ShareParams,
SignUpNewFaceCodeParams,
SizeExceededParams,
Expand Down Expand Up @@ -3506,6 +3507,8 @@ const translations = {
limit: 'Limit',
currentBalance: 'Current balance',
currentBalanceDescription: 'Current balance is the sum of all posted Expensify Card transactions that have occurred since the last settlement date.',
balanceWillBeSettledOn: ({settlementDate}: SettlementDateParams) => `Balance will be settled on ${settlementDate}`,
settleBalance: 'Settle balance',
cardLimit: 'Card limit',
remainingLimit: 'Remaining limit',
requestLimitIncrease: 'Request limit increase',
Expand Down
3 changes: 3 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ import type {
SetTheRequestParams,
SettledAfterAddedBankAccountParams,
SettleExpensifyCardParams,
SettlementDateParams,
ShareParams,
SignUpNewFaceCodeParams,
SizeExceededParams,
Expand Down Expand Up @@ -3547,6 +3548,8 @@ const translations = {
currentBalance: 'Saldo actual',
currentBalanceDescription:
'El saldo actual es la suma de todas las transacciones contabilizadas con la Tarjeta Expensify que se han producido desde la última fecha de liquidación.',
balanceWillBeSettledOn: ({settlementDate}: SettlementDateParams) => `El saldo se liquidará el ${settlementDate}.`,
settleBalance: 'Liquidar saldo',
cardLimit: 'Límite de la tarjeta',
remainingLimit: 'Límite restante',
requestLimitIncrease: 'Solicitar aumento de límite',
Expand Down
5 changes: 5 additions & 0 deletions src/languages/params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,10 @@ type FlightLayoverParams = {
layover: string;
};

type SettlementDateParams = {
settlementDate: string;
};

export type {
AuthenticationErrorParams,
ImportMembersSuccessfullDescriptionParams,
Expand Down Expand Up @@ -816,4 +820,5 @@ export type {
ChatWithAccountManagerParams,
EditDestinationSubtitleParams,
FlightLayoverParams,
SettlementDateParams,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
type QueueExpensifyCardForBillingParams = {
feedCountry: string;
domainAccountID: number;
};

export default QueueExpensifyCardForBillingParams;
1 change: 1 addition & 0 deletions src/libs/API/parameters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,7 @@ export type {default as DismissProductTrainingParams} from './DismissProductTrai
export type {default as OpenWorkspacePlanPageParams} from './OpenWorkspacePlanPage';
export type {default as ResetSMSDeliveryFailureStatusParams} from './ResetSMSDeliveryFailureStatusParams';
export type {default as CreatePerDiemRequestParams} from './CreatePerDiemRequestParams';
export type {default as QueueExpensifyCardForBillingParams} from './QueueExpensifyCardForBillingParams';
export type {default as GetCorpayOnboardingFieldsParams} from './GetCorpayOnboardingFieldsParams';
export type {SaveCorpayOnboardingCompanyDetailsParams} from './SaveCorpayOnboardingCompanyDetailsParams';
export type {default as AcceptSpotnanaTermsParams} from './AcceptSpotnanaTermsParams';
2 changes: 2 additions & 0 deletions src/libs/API/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,7 @@ const WRITE_COMMANDS = {
CONFIGURE_EXPENSIFY_CARDS_FOR_POLICY: 'ConfigureExpensifyCardsForPolicy',
CREATE_EXPENSIFY_CARD: 'CreateExpensifyCard',
CREATE_ADMIN_ISSUED_VIRTUAL_CARD: 'CreateAdminIssuedVirtualCard',
QUEUE_EXPENSIFY_CARD_FOR_BILLING: 'Domain_QueueExpensifyCardForBilling',
ADD_DELEGATE: 'AddDelegate',
REMOVE_DELEGATE: 'RemoveDelegate',
UPDATE_DELEGATE_ROLE: 'UpdateDelegateRole',
Expand Down Expand Up @@ -873,6 +874,7 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.CONFIGURE_EXPENSIFY_CARDS_FOR_POLICY]: Parameters.ConfigureExpensifyCardsForPolicyParams;
[WRITE_COMMANDS.CREATE_EXPENSIFY_CARD]: Omit<Parameters.CreateExpensifyCardParams, 'domainAccountID'>;
[WRITE_COMMANDS.CREATE_ADMIN_ISSUED_VIRTUAL_CARD]: Omit<Parameters.CreateExpensifyCardParams, 'feedCountry'>;
[WRITE_COMMANDS.QUEUE_EXPENSIFY_CARD_FOR_BILLING]: Parameters.QueueExpensifyCardForBillingParams;
[WRITE_COMMANDS.ADD_DELEGATE]: Parameters.AddDelegateParams;
[WRITE_COMMANDS.UPDATE_DELEGATE_ROLE]: Parameters.UpdateDelegateRoleParams;
[WRITE_COMMANDS.REMOVE_DELEGATE]: Parameters.RemoveDelegateParams;
Expand Down
10 changes: 10 additions & 0 deletions src/libs/actions/Card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -897,6 +897,15 @@ function updateSelectedFeed(feed: CompanyCardFeed, policyID: string | undefined)
]);
}

function queueExpensifyCardForBilling(feedCountry: string, domainAccountID: number) {
const parameters = {
feedCountry,
domainAccountID,
};

API.write(WRITE_COMMANDS.QUEUE_EXPENSIFY_CARD_FOR_BILLING, parameters);
}

export {
requestReplacementExpensifyCard,
activatePhysicalExpensifyCard,
Expand All @@ -920,5 +929,6 @@ export {
updateSelectedFeed,
deactivateCard,
getCardDefaultName,
queueExpensifyCardForBilling,
};
export type {ReplacementReason};
71 changes: 63 additions & 8 deletions src/pages/workspace/expensifyCard/WorkspaceCardListHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import React from 'react';
import {View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import FormHelpMessage from '@components/FormHelpMessage';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useThemeStyles from '@hooks/useThemeStyles';
import * as PolicyUtils from '@libs/PolicyUtils';
import {getLatestErrorMessage} from '@libs/ErrorUtils';
import {getWorkspaceAccountID} from '@libs/PolicyUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import WorkspaceCardsListLabel from './WorkspaceCardsListLabel';
Expand All @@ -21,15 +23,55 @@ function WorkspaceCardListHeader({policyID}: WorkspaceCardListHeaderProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();

const workspaceAccountID = PolicyUtils.getWorkspaceAccountID(policyID);
const workspaceAccountID = getWorkspaceAccountID(policyID);
const isLessThanMediumScreen = isMediumScreenWidth || isSmallScreenWidth;

const [cardSettings] = useOnyx(`${ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS}${workspaceAccountID}`);
const [cardManualBilling] = useOnyx(`${ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_MANUAL_BILLING}${workspaceAccountID}`);

return (
<View style={styles.appBG}>
<View style={[isLessThanMediumScreen ? styles.flexColumn : styles.flexRow, isLessThanMediumScreen ? [styles.mt5, styles.mb3] : styles.mv5, styles.mh5, styles.ph4]}>
<View style={[styles.flexRow, styles.flex1, isLessThanMediumScreen && styles.mb5]}>
const errorMessage = getLatestErrorMessage(cardSettings) ?? '';

const shouldShowSettlementButtonOrDate = !!cardSettings?.isMonthlySettlementAllowed || cardManualBilling;

const getLabelsLayout = () => {
if (!isLessThanMediumScreen) {
return (
<>
<WorkspaceCardsListLabel
type={CONST.WORKSPACE_CARDS_LIST_LABEL_TYPE.CURRENT_BALANCE}
value={cardSettings?.[CONST.WORKSPACE_CARDS_LIST_LABEL_TYPE.CURRENT_BALANCE] ?? 0}
/>
<WorkspaceCardsListLabel
type={CONST.WORKSPACE_CARDS_LIST_LABEL_TYPE.REMAINING_LIMIT}
value={cardSettings?.[CONST.WORKSPACE_CARDS_LIST_LABEL_TYPE.REMAINING_LIMIT] ?? 0}
/>
<WorkspaceCardsListLabel
type={CONST.WORKSPACE_CARDS_LIST_LABEL_TYPE.CASH_BACK}
value={cardSettings?.[CONST.WORKSPACE_CARDS_LIST_LABEL_TYPE.CASH_BACK] ?? 0}
/>
</>
);
}
return shouldShowSettlementButtonOrDate ? (
<>
<WorkspaceCardsListLabel
type={CONST.WORKSPACE_CARDS_LIST_LABEL_TYPE.CURRENT_BALANCE}
value={cardSettings?.[CONST.WORKSPACE_CARDS_LIST_LABEL_TYPE.CURRENT_BALANCE] ?? 0}
/>
<View style={[styles.flexRow, !isLessThanMediumScreen && styles.flex2, isLessThanMediumScreen && styles.mt5]}>
<WorkspaceCardsListLabel
type={CONST.WORKSPACE_CARDS_LIST_LABEL_TYPE.REMAINING_LIMIT}
value={cardSettings?.[CONST.WORKSPACE_CARDS_LIST_LABEL_TYPE.REMAINING_LIMIT] ?? 0}
/>
<WorkspaceCardsListLabel
type={CONST.WORKSPACE_CARDS_LIST_LABEL_TYPE.CASH_BACK}
value={cardSettings?.[CONST.WORKSPACE_CARDS_LIST_LABEL_TYPE.CASH_BACK] ?? 0}
/>
</View>
</>
) : (
<>
<View style={[styles.flexRow, isLessThanMediumScreen && styles.mb5]}>
<WorkspaceCardsListLabel
type={CONST.WORKSPACE_CARDS_LIST_LABEL_TYPE.CURRENT_BALANCE}
value={cardSettings?.[CONST.WORKSPACE_CARDS_LIST_LABEL_TYPE.CURRENT_BALANCE] ?? 0}
Expand All @@ -43,9 +85,22 @@ function WorkspaceCardListHeader({policyID}: WorkspaceCardListHeaderProps) {
type={CONST.WORKSPACE_CARDS_LIST_LABEL_TYPE.CASH_BACK}
value={cardSettings?.[CONST.WORKSPACE_CARDS_LIST_LABEL_TYPE.CASH_BACK] ?? 0}
/>
</View>
</>
);
};

<View style={[styles.flexRow, styles.mh5, styles.gap2, styles.p4]}>
return (
<View style={styles.appBG}>
<View style={[isLessThanMediumScreen ? styles.flexColumn : styles.flexRow, styles.mt5, styles.mh5, styles.ph4]}>{getLabelsLayout()}</View>
{!!errorMessage && (
<View style={[styles.mh5, styles.ph4, styles.mt2]}>
<FormHelpMessage
isError
message={errorMessage}
/>
</View>
)}
<View style={[styles.flexRow, styles.mh5, styles.gap2, styles.p4, isLessThanMediumScreen ? styles.mt3 : styles.mt5]}>
<View style={[styles.flexRow, styles.flex4, styles.gap2, styles.alignItemsCenter]}>
<Text
numberOfLines={1}
Expand Down
85 changes: 57 additions & 28 deletions src/pages/workspace/expensifyCard/WorkspaceCardsListLabel.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {useRoute} from '@react-navigation/native';
import {addDays, format} from 'date-fns';
import React, {useEffect, useMemo, useRef, useState} from 'react';
import {View} from 'react-native';
import type {StyleProp, ViewStyle} from 'react-native';
Expand All @@ -16,14 +17,15 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import * as CurrencyUtils from '@libs/CurrencyUtils';
import {convertToDisplayString} from '@libs/CurrencyUtils';
import getClickedTargetLocation from '@libs/getClickedTargetLocation';
import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types';
import * as PolicyUtils from '@libs/PolicyUtils';
import {getWorkspaceAccountID} from '@libs/PolicyUtils';
import type {FullScreenNavigatorParamList} from '@navigation/types';
import variables from '@styles/variables';
import * as Policy from '@userActions/Policy/Policy';
import * as Report from '@userActions/Report';
import {queueExpensifyCardForBilling} from '@userActions/Card';
import {requestExpensifyCardLimitIncrease} from '@userActions/Policy/Policy';
import {navigateToConciergeChat} from '@userActions/Report';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type SCREENS from '@src/SCREENS';
Expand All @@ -44,21 +46,25 @@ function WorkspaceCardsListLabel({type, value, style}: WorkspaceCardsListLabelPr
const policy = usePolicy(route.params.policyID);
const styles = useThemeStyles();
const {windowWidth} = useWindowDimensions();
const {shouldUseNarrowLayout} = useResponsiveLayout();
const {shouldUseNarrowLayout, isMediumScreenWidth} = useResponsiveLayout();
const theme = useTheme();
const {translate} = useLocalize();
const [bankAccountList] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST);
const [isVisible, setVisible] = useState(false);
const [anchorPosition, setAnchorPosition] = useState({top: 0, left: 0});
const anchorRef = useRef(null);

const workspaceAccountID = PolicyUtils.getWorkspaceAccountID(route.params.policyID);
const workspaceAccountID = getWorkspaceAccountID(route.params.policyID);

const policyCurrency = useMemo(() => policy?.outputCurrency ?? CONST.CURRENCY.USD, [policy]);
const [cardSettings] = useOnyx(`${ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS}${workspaceAccountID}`);
const [cardManualBilling] = useOnyx(`${ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_MANUAL_BILLING}${workspaceAccountID}`);
const paymentBankAccountID = cardSettings?.paymentBankAccountID;

const isLessThanMediumScreen = isMediumScreenWidth || shouldUseNarrowLayout;

const isConnectedWithPlaid = useMemo(() => {
const bankAccountData = bankAccountList?.[paymentBankAccountID ?? 0]?.accountData;
const bankAccountData = bankAccountList?.[paymentBankAccountID ?? CONST.DEFAULT_NUMBER_ID]?.accountData;

// TODO: remove the extra check when plaidAccountID storing is aligned in https://github.com/Expensify/App/issues/47944
// Right after adding a bank account plaidAccountID is stored inside the accountData and not in the additionalData
Expand All @@ -80,34 +86,57 @@ function WorkspaceCardsListLabel({type, value, style}: WorkspaceCardsListLabelPr
}, [isVisible, windowWidth]);

const requestLimitIncrease = () => {
Policy.requestExpensifyCardLimitIncrease(cardSettings?.paymentBankAccountID);
requestExpensifyCardLimitIncrease(cardSettings?.paymentBankAccountID);
setVisible(false);
Report.navigateToConciergeChat();
navigateToConciergeChat();
};

const isCurrentBalanceType = type === CONST.WORKSPACE_CARDS_LIST_LABEL_TYPE.CURRENT_BALANCE;
const isSettleBalanceButtonDisplayed = !!cardSettings?.isMonthlySettlementAllowed && !cardManualBilling && isCurrentBalanceType;
const isSettleDateTextDisplayed = !!cardManualBilling && isCurrentBalanceType;

const settlementDate = isSettleDateTextDisplayed ? format(addDays(new Date(), 1), CONST.DATE.FNS_FORMAT_STRING) : '';

const handleSettleBalanceButtonClick = () => {
queueExpensifyCardForBilling(CONST.COUNTRY.US, workspaceAccountID);
};

return (
<View style={styles.flex1}>
<View
ref={anchorRef}
style={[styles.flexRow, styles.alignItemsCenter, styles.mb1, style]}
>
<Text style={[styles.mutedNormalTextLabel, styles.mr1]}>{translate(`workspace.expensifyCard.${type}`)}</Text>
<PressableWithFeedback
accessibilityLabel={translate(`workspace.expensifyCard.${type}`)}
accessibilityRole={CONST.ROLE.BUTTON}
onPress={() => setVisible(true)}
<View style={styles.flex1}>
<View
ref={anchorRef}
style={[styles.flexRow, styles.alignItemsCenter, styles.mb1, style]}
>
<Icon
src={Expensicons.Info}
width={variables.iconSizeExtraSmall}
height={variables.iconSizeExtraSmall}
fill={theme.icon}
/>
</PressableWithFeedback>
<Text style={[styles.mutedNormalTextLabel, styles.mr1]}>{translate(`workspace.expensifyCard.${type}`)}</Text>
<PressableWithFeedback
accessibilityLabel={translate(`workspace.expensifyCard.${type}`)}
accessibilityRole={CONST.ROLE.BUTTON}
onPress={() => setVisible(true)}
>
<Icon
src={Expensicons.Info}
width={variables.iconSizeExtraSmall}
height={variables.iconSizeExtraSmall}
fill={theme.icon}
/>
</PressableWithFeedback>
</View>
<View style={[styles.flexRow, styles.flexWrap]}>
<Text style={[styles.shortTermsHeadline, isSettleBalanceButtonDisplayed && [styles.mb2, styles.mr3]]}>{convertToDisplayString(value, policyCurrency)}</Text>
{isSettleBalanceButtonDisplayed && (
<View style={[styles.mr2, isLessThanMediumScreen && styles.mb3]}>
<Button
onPress={handleSettleBalanceButtonClick}
text={translate('workspace.expensifyCard.settleBalance')}
innerStyles={[styles.buttonSmall]}
textStyles={[styles.buttonSmallText]}
/>
</View>
)}
</View>
</View>

<Text style={styles.shortTermsHeadline}>{CurrencyUtils.convertToDisplayString(value, policyCurrency)}</Text>

{isSettleDateTextDisplayed && <Text style={[styles.mutedNormalTextLabel, styles.mt1]}>{translate('workspace.expensifyCard.balanceWillBeSettledOn', {settlementDate})}</Text>}
<Popover
onClose={() => setVisible(false)}
isVisible={isVisible}
Expand Down