From 1a71d782b93aa881152cf225467f8835bb396791 Mon Sep 17 00:00:00 2001
From: ren-jones <153645623+ren-jones@users.noreply.github.com>
Date: Fri, 16 Feb 2024 08:28:00 -0600
Subject: [PATCH 01/48] Create Change-or-add-email-address.md
New settings help article
---
.../Change-or-add-email-address.md | 24 +++++++++++++++++++
1 file changed, 24 insertions(+)
create mode 100644 docs/articles/expensify-classic/settings/account-settings/Change-or-add-email-address.md
diff --git a/docs/articles/expensify-classic/settings/account-settings/Change-or-add-email-address.md b/docs/articles/expensify-classic/settings/account-settings/Change-or-add-email-address.md
new file mode 100644
index 000000000000..754b9a7f9ac0
--- /dev/null
+++ b/docs/articles/expensify-classic/settings/account-settings/Change-or-add-email-address.md
@@ -0,0 +1,24 @@
+---
+title: Change or add email address
+description: Update your Expensify email address or add a secondary email
+---
+
+
+The primary email address on your Expensify account is the email that receives email updates and notifications for your account. You can add a secondary email address in order to
+- Change your primary email to a new one.
+- Connect your personal email address as a secondary login if your primary email address is one from your employer. This allows you to always have access to your Expensify account, even if your employer changes.
+
+{% include info.html %}
+Before you can remove a primary email address, you must add a new one to your Expensify account and make it the primary using the steps below. Email addresses must be added as a secondary login before they can be made the primary.
+{% include end-info.html %}
+
+*Note: This process is currently not available from the mobile app and must be completed from the Expensify website.*
+
+1. Hover over Settings, then click **Account**.
+2. Under the Account Details tab, scroll down to the Secondary Logins section and click **Add Secondary Login**.
+3. Enter the email address or phone number you wish to use as a secondary login. For phone numbers, be sure to include the international code, if applicable.
+4. Find the email or text message from Expensify containing the Magic Code and enter it into the field.
+5. To make the new email address the primary address for your account, click **Make Primary**.
+
+You can keep both logins, or you can click **Remove** next to the old email address to delete it from your account.
+
From 8ac8f5a36e4886e6e55d4a96d671ee389376c8c1 Mon Sep 17 00:00:00 2001
From: dukenv0307
Date: Fri, 23 Feb 2024 15:51:09 +0700
Subject: [PATCH 02/48] Add current to default miliage rate
---
src/CONST.ts | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/src/CONST.ts b/src/CONST.ts
index 008002a71078..83dbd0c9fce8 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -6,6 +6,12 @@ import * as KeyCommand from 'react-native-key-command';
import * as Url from './libs/Url';
import SCREENS from './SCREENS';
+type RateAndUnit = {
+ unit: string,
+ rate: number
+}
+type CurrencyDefaultMileageRate = Record
+
// Creating a default array and object this way because objects ({}) and arrays ([]) are not stable types.
// Freezing the array ensures that it cannot be unintentionally modified.
const EMPTY_ARRAY = Object.freeze([]);
@@ -3305,6 +3311,16 @@ const CONST = {
ADDRESS: 3,
},
},
+ CURRENCY_TO_DEFAULT_MILEAGE_RATE: JSON.parse(`{
+ "EUR": {
+ "unit": "kilometer",
+ "rate": 1
+ },
+ "USD": {
+ "unit": "mile",
+ "rate": 2
+ }
+ }`) as CurrencyDefaultMileageRate,
} as const;
type Country = keyof typeof CONST.ALL_COUNTRIES;
From 18197585cc81ed1757423452ac4ceeabc193873c Mon Sep 17 00:00:00 2001
From: dukenv0307
Date: Fri, 23 Feb 2024 16:38:35 +0700
Subject: [PATCH 03/48] fix lint
---
src/CONST.ts | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/CONST.ts b/src/CONST.ts
index 83dbd0c9fce8..aebf84b68027 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -7,10 +7,10 @@ import * as Url from './libs/Url';
import SCREENS from './SCREENS';
type RateAndUnit = {
- unit: string,
- rate: number
-}
-type CurrencyDefaultMileageRate = Record
+ unit: string;
+ rate: number;
+};
+type CurrencyDefaultMileageRate = Record;
// Creating a default array and object this way because objects ({}) and arrays ([]) are not stable types.
// Freezing the array ensures that it cannot be unintentionally modified.
@@ -3320,7 +3320,7 @@ const CONST = {
"unit": "mile",
"rate": 2
}
- }`) as CurrencyDefaultMileageRate,
+ }`) as CurrencyDefaultMileageRate,
} as const;
type Country = keyof typeof CONST.ALL_COUNTRIES;
From b832c2c6b28bb6670a5f9a70c98440df478f6183 Mon Sep 17 00:00:00 2001
From: tienifr
Date: Mon, 26 Feb 2024 00:08:49 +0700
Subject: [PATCH 04/48] feature: enable p2p/splits in App
---
src/CONST.ts | 2 ++
src/ONYXKEYS.ts | 4 +++
.../MoneyRequestConfirmationList.js | 7 ++---
...oraryForRefactorRequestConfirmationList.js | 7 ++---
src/libs/DistanceRequestUtils.ts | 25 ++++++++++++++++-
src/libs/Permissions.ts | 5 ++++
src/libs/actions/IOU.ts | 27 ++++++++++++++++---
src/pages/iou/request/IOURequestStartPage.js | 4 +--
...yForRefactorRequestParticipantsSelector.js | 12 ++++-----
.../MoneyRequestParticipantsSelector.js | 9 ++++---
src/types/onyx/LastSelectedDistanceRates.ts | 5 ++++
src/types/onyx/index.ts | 2 ++
12 files changed, 86 insertions(+), 23 deletions(-)
create mode 100644 src/types/onyx/LastSelectedDistanceRates.ts
diff --git a/src/CONST.ts b/src/CONST.ts
index 008002a71078..a3df727251f0 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -307,6 +307,7 @@ const CONST = {
BETA_COMMENT_LINKING: 'commentLinking',
VIOLATIONS: 'violations',
REPORT_FIELDS: 'reportFields',
+ P2P_DISTANCE_REQUESTS: 'p2pDistanceRequests',
},
BUTTON_STATES: {
DEFAULT: 'default',
@@ -1392,6 +1393,7 @@ const CONST = {
MILEAGE_IRS_RATE: 0.655,
DEFAULT_RATE: 'Default Rate',
RATE_DECIMALS: 3,
+ FAKE_P2P_ID: '__fake_p2p_id__',
},
TERMS: {
diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts
index ee8bceeab44a..aad05bee7c2c 100755
--- a/src/ONYXKEYS.ts
+++ b/src/ONYXKEYS.ts
@@ -126,6 +126,9 @@ const ONYXKEYS = {
/** This NVP contains the choice that the user made on the engagement modal */
NVP_INTRO_SELECTED: 'introSelected',
+ /** The NVP with the last distance rate used per policy */
+ NVP_LAST_SELECTED_DISTANCE_RATES: 'lastSelectedDistanceRates',
+
/** Does this user have push notifications enabled for this device? */
PUSH_NOTIFICATIONS_ENABLED: 'pushNotificationsEnabled',
@@ -511,6 +514,7 @@ type OnyxValuesMapping = {
[ONYXKEYS.NVP_RECENT_WAYPOINTS]: OnyxTypes.RecentWaypoint[];
[ONYXKEYS.NVP_HAS_DISMISSED_IDLE_PANEL]: boolean;
[ONYXKEYS.NVP_INTRO_SELECTED]: OnyxTypes.IntroSelected;
+ [ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES]: OnyxTypes.LastSelectedDistanceRates;
[ONYXKEYS.PUSH_NOTIFICATIONS_ENABLED]: boolean;
[ONYXKEYS.PLAID_DATA]: OnyxTypes.PlaidData;
[ONYXKEYS.IS_PLAID_DISABLED]: boolean;
diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js
index 0de601bc9f61..843ae41905e1 100755
--- a/src/components/MoneyRequestConfirmationList.js
+++ b/src/components/MoneyRequestConfirmationList.js
@@ -217,7 +217,7 @@ function MoneyRequestConfirmationList(props) {
const {onSendMoney, onConfirm, onSelectParticipant} = props;
const {translate, toLocaleDigit} = useLocalize();
const transaction = props.transaction;
- const {canUseViolations} = usePermissions();
+ const {canUseP2PDistanceRequests, canUseViolations} = usePermissions();
const isTypeRequest = props.iouType === CONST.IOU.TYPE.REQUEST;
const isSplitBill = props.iouType === CONST.IOU.TYPE.SPLIT;
@@ -227,6 +227,7 @@ function MoneyRequestConfirmationList(props) {
const {unit, rate, currency} = props.mileageRate;
const distance = lodashGet(transaction, 'routes.route0.distance', 0);
+ const displayDistance = DistanceRequestUtils.getDistanceForDisplay(hasRoute, distance, unit, rate, translate);
const shouldCalculateDistanceAmount = props.isDistanceRequest && props.iouAmount === 0;
// A flag for showing the categories field
@@ -720,12 +721,12 @@ function MoneyRequestConfirmationList(props) {
{props.isDistanceRequest && (
Navigation.navigate(ROUTES.MONEY_REQUEST_DISTANCE.getRoute(props.iouType, props.reportID))}
- disabled={didConfirm || !isTypeRequest}
+ disabled={didConfirm || canUseP2PDistanceRequests || !isTypeRequest}
interactive={!props.isReadOnly}
/>
)}
diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js
index 3939e847707d..b7ee85d2513d 100755
--- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js
+++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js
@@ -243,7 +243,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
const theme = useTheme();
const styles = useThemeStyles();
const {translate, toLocaleDigit} = useLocalize();
- const {canUseViolations} = usePermissions();
+ const {canUseP2PDistanceRequests, canUseViolations} = usePermissions();
const isTypeRequest = iouType === CONST.IOU.TYPE.REQUEST;
const isTypeSplit = iouType === CONST.IOU.TYPE.SPLIT;
@@ -251,6 +251,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
const {unit, rate, currency} = mileageRate;
const distance = lodashGet(transaction, 'routes.route0.distance', 0);
+ const displayDistance = DistanceRequestUtils.getDistanceForDisplay(hasRoute, distance, unit, rate, translate);
const shouldCalculateDistanceAmount = isDistanceRequest && iouAmount === 0;
// A flag for showing the categories field
@@ -680,12 +681,12 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams()))}
- disabled={didConfirm || !isTypeRequest}
+ disabled={didConfirm || canUseP2PDistanceRequests || !isTypeRequest}
interactive={!isReadOnly}
/>
),
diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts
index a42cb6a8f756..0a02cd6796ca 100644
--- a/src/libs/DistanceRequestUtils.ts
+++ b/src/libs/DistanceRequestUtils.ts
@@ -7,6 +7,7 @@ import * as CurrencyUtils from './CurrencyUtils';
import * as PolicyUtils from './PolicyUtils';
type DefaultMileageRate = {
+ rateID?: string;
rate?: number;
currency?: string;
unit: Unit;
@@ -38,6 +39,7 @@ function getDefaultMileageRate(policy: OnyxEntry): DefaultMileageRate |
}
return {
+ rateID: distanceRate.customUnitRateID,
rate: distanceRate.rate,
currency: distanceRate.currency,
unit: distanceUnit.attributes.unit,
@@ -76,6 +78,27 @@ function getRoundedDistanceInUnits(distanceInMeters: number, unit: Unit): string
return convertedDistance.toFixed(2);
}
+/**
+ * @param hasRoute Whether the route exists for the distance request
+ * @param distanceInMeters Distance traveled
+ * @param unit Unit that should be used to display the distance
+ * @param rate Expensable amount allowed per unit
+ * @param translate Translate function
+ * @returns A string that describes the distance traveled
+ */
+function getDistanceForDisplay(hasRoute: boolean, distanceInMeters: number, unit: Unit, rate: number, translate: LocaleContextProps['translate']): string {
+ if (!hasRoute || !rate) {
+ return translate('iou.routePending');
+ }
+
+ const distanceInUnits = getRoundedDistanceInUnits(distanceInMeters, unit);
+ const distanceUnit = unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.miles') : translate('common.kilometers');
+ const singularDistanceUnit = unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.mile') : translate('common.kilometer');
+ const unitString = distanceInUnits === '1' ? singularDistanceUnit : distanceUnit;
+
+ return `${distanceInUnits} ${unitString}`;
+}
+
/**
* @param hasRoute Whether the route exists for the distance request
* @param distanceInMeters Distance traveled
@@ -124,4 +147,4 @@ function getDistanceRequestAmount(distance: number, unit: Unit, rate: number): n
return Math.round(roundedDistance * rate);
}
-export default {getDefaultMileageRate, getDistanceMerchant, getDistanceRequestAmount};
+export default {getDefaultMileageRate, getDistanceForDisplay, getDistanceMerchant, getDistanceRequestAmount};
diff --git a/src/libs/Permissions.ts b/src/libs/Permissions.ts
index ce5e0e674c59..8eaf33605e8f 100644
--- a/src/libs/Permissions.ts
+++ b/src/libs/Permissions.ts
@@ -26,6 +26,10 @@ function canUseViolations(betas: OnyxEntry): boolean {
return !!betas?.includes(CONST.BETAS.VIOLATIONS) || canUseAllBetas(betas);
}
+function canUseP2PDistanceRequests(betas: OnyxEntry): boolean {
+ return !!betas?.includes(CONST.BETAS.P2P_DISTANCE_REQUESTS) || canUseAllBetas(betas);
+}
+
/**
* Link previews are temporarily disabled.
*/
@@ -40,4 +44,5 @@ export default {
canUseLinkPreviews,
canUseViolations,
canUseReportFields,
+ canUseP2PDistanceRequests,
};
diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts
index f39728e7d31c..8d708e7f4d42 100644
--- a/src/libs/actions/IOU.ts
+++ b/src/libs/actions/IOU.ts
@@ -26,6 +26,7 @@ import type {
import {WRITE_COMMANDS} from '@libs/API/types';
import * as CurrencyUtils from '@libs/CurrencyUtils';
import DateUtils from '@libs/DateUtils';
+import DistanceRequestUtils from '@libs/DistanceRequestUtils';
import * as ErrorUtils from '@libs/ErrorUtils';
import * as FileUtils from '@libs/fileDownload/FileUtils';
import * as IOUUtils from '@libs/IOUUtils';
@@ -218,12 +219,27 @@ Onyx.connect({
},
});
+let lastSelectedDistanceRates: OnyxEntry = {};
+Onyx.connect({
+ key: ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES,
+ callback: (value) => {
+ lastSelectedDistanceRates = value;
+ },
+});
+
/**
* Initialize money request info
- * @param reportID to attach the transaction to
+ * @param report to attach the transaction to
+ * @param policy
+ * @param isFromGlobalCreate
* @param iouRequestType one of manual/scan/distance
*/
-function initMoneyRequest(reportID: string, isFromGlobalCreate: boolean, iouRequestType: IOURequestType = CONST.IOU.REQUEST_TYPE.MANUAL) {
+function initMoneyRequest(
+ report: OnyxEntry,
+ policy: OnyxEntry,
+ isFromGlobalCreate: boolean,
+ iouRequestType: IOURequestType = CONST.IOU.REQUEST_TYPE.MANUAL,
+) {
// Generate a brand new transactionID
const newTransactionID = CONST.IOU.OPTIMISTIC_TRANSACTION_ID;
// Disabling this line since currentDate can be an empty string
@@ -237,6 +253,11 @@ function initMoneyRequest(reportID: string, isFromGlobalCreate: boolean, iouRequ
waypoint0: {},
waypoint1: {},
};
+ let customUnitRateID: string = CONST.CUSTOM_UNITS.FAKE_P2P_ID;
+ if (report?.isPolicyExpenseChat) {
+ customUnitRateID = lastSelectedDistanceRates?.[policy?.id ?? '']?.customUnitRateID ?? DistanceRequestUtils.getDefaultMileageRate(policy)?.rateID ?? '';
+ }
+ comment.customUnit = {customUnitRateID};
}
// Store the transaction in Onyx and mark it as not saved so it can be cleaned up later
@@ -247,7 +268,7 @@ function initMoneyRequest(reportID: string, isFromGlobalCreate: boolean, iouRequ
created,
currency: currentUserPersonalDetails.localCurrencyCode ?? CONST.CURRENCY.USD,
iouRequestType,
- reportID,
+ reportID: report?.reportID ?? '',
transactionID: newTransactionID,
isFromGlobalCreate,
merchant: CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT,
diff --git a/src/pages/iou/request/IOURequestStartPage.js b/src/pages/iou/request/IOURequestStartPage.js
index 05e3d7c96311..d1b416f79fdc 100644
--- a/src/pages/iou/request/IOURequestStartPage.js
+++ b/src/pages/iou/request/IOURequestStartPage.js
@@ -98,8 +98,8 @@ function IOURequestStartPage({
if (transaction.reportID === reportID) {
return;
}
- IOU.initMoneyRequest(reportID, isFromGlobalCreate, transactionRequestType.current);
- }, [transaction, reportID, iouType, isFromGlobalCreate]);
+ IOU.initMoneyRequest(report, policy, isFromGlobalCreate, transactionRequestType.current);
+ }, [transaction, reportID, report, policy, iouType, isFromGlobalCreate]);
const isExpenseChat = ReportUtils.isPolicyExpenseChat(report);
const isExpenseReport = ReportUtils.isExpenseReport(report);
diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js
index 238b66c0e727..b4b95107dc52 100644
--- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js
+++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js
@@ -14,6 +14,7 @@ import SelectionList from '@components/SelectionList';
import UserListItem from '@components/SelectionList/UserListItem';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
+import usePermissions from '@hooks/usePermissions';
import useSearchTermAndSearch from '@hooks/useSearchTermAndSearch';
import useThemeStyles from '@hooks/useThemeStyles';
import * as DeviceCapabilities from '@libs/DeviceCapabilities';
@@ -90,6 +91,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({
const referralContentType = iouType === CONST.IOU.TYPE.SEND ? CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SEND_MONEY : CONST.REFERRAL_PROGRAM.CONTENT_TYPES.MONEY_REQUEST;
const {isOffline} = useNetwork();
const personalDetails = usePersonalDetails();
+ const {canUseP2PDistanceRequests} = usePermissions();
const offlineMessage = isOffline ? [`${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}`, {isTranslated: true}] : '';
@@ -120,18 +122,14 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({
// sees the option to request money from their admin on their own Workspace Chat.
iouType === CONST.IOU.TYPE.REQUEST,
- // We don't want to include any P2P options like personal details or reports that are not workspace chats for certain features.
- iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE,
+ canUseP2PDistanceRequests || iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE,
false,
{},
[],
false,
{},
[],
-
- // We don't want the user to be able to invite individuals when they are in the "Distance request" flow for now.
- // This functionality is being built here: https://github.com/Expensify/App/issues/23291
- iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE,
+ canUseP2PDistanceRequests || iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE,
false,
);
@@ -256,7 +254,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({
// the app from crashing on native when you try to do this, we'll going to hide the button if you have a workspace and other participants
const hasPolicyExpenseChatParticipant = _.some(participants, (participant) => participant.isPolicyExpenseChat);
const shouldShowSplitBillErrorMessage = participants.length > 1 && hasPolicyExpenseChatParticipant;
- const isAllowedToSplit = iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE;
+ const isAllowedToSplit = canUseP2PDistanceRequests || iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE;
const handleConfirmSelection = useCallback(() => {
if (shouldShowSplitBillErrorMessage) {
diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js
index 3fde970327d7..ec45fafd5954 100755
--- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js
+++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js
@@ -14,6 +14,7 @@ import SelectionList from '@components/SelectionList';
import UserListItem from '@components/SelectionList/UserListItem';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
+import usePermissions from '@hooks/usePermissions';
import useSearchTermAndSearch from '@hooks/useSearchTermAndSearch';
import useThemeStyles from '@hooks/useThemeStyles';
import * as DeviceCapabilities from '@libs/DeviceCapabilities';
@@ -94,6 +95,7 @@ function MoneyRequestParticipantsSelector({
const referralContentType = iouType === CONST.IOU.TYPE.SEND ? CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SEND_MONEY : CONST.REFERRAL_PROGRAM.CONTENT_TYPES.MONEY_REQUEST;
const {isOffline} = useNetwork();
const personalDetails = usePersonalDetails();
+ const {canUseP2PDistanceRequests} = usePermissions();
const maxParticipantsReached = participants.length === CONST.REPORT.MAXIMUM_PARTICIPANTS;
const setSearchTermAndSearchInServer = useSearchTermAndSearch(setSearchTerm, maxParticipantsReached);
@@ -113,8 +115,7 @@ function MoneyRequestParticipantsSelector({
// sees the option to request money from their admin on their own Workspace Chat.
iouType === CONST.IOU.TYPE.REQUEST,
- // We don't want to include any P2P options like personal details or reports that are not workspace chats for certain features.
- !isDistanceRequest,
+ canUseP2PDistanceRequests || !isDistanceRequest,
false,
{},
[],
@@ -123,7 +124,7 @@ function MoneyRequestParticipantsSelector({
[],
// We don't want the user to be able to invite individuals when they are in the "Distance request" flow for now.
// This functionality is being built here: https://github.com/Expensify/App/issues/23291
- !isDistanceRequest,
+ canUseP2PDistanceRequests || !isDistanceRequest,
true,
);
return {
@@ -272,7 +273,7 @@ function MoneyRequestParticipantsSelector({
// the app from crashing on native when you try to do this, we'll going to show error message if you have a workspace and other participants
const hasPolicyExpenseChatParticipant = _.some(participants, (participant) => participant.isPolicyExpenseChat);
const shouldShowSplitBillErrorMessage = participants.length > 1 && hasPolicyExpenseChatParticipant;
- const isAllowedToSplit = !isDistanceRequest && iouType !== CONST.IOU.TYPE.SEND;
+ const isAllowedToSplit = (canUseP2PDistanceRequests || !isDistanceRequest) && iouType !== CONST.IOU.TYPE.SEND;
const handleConfirmSelection = useCallback(() => {
if (shouldShowSplitBillErrorMessage) {
diff --git a/src/types/onyx/LastSelectedDistanceRates.ts b/src/types/onyx/LastSelectedDistanceRates.ts
new file mode 100644
index 000000000000..4ebf39e43bcb
--- /dev/null
+++ b/src/types/onyx/LastSelectedDistanceRates.ts
@@ -0,0 +1,5 @@
+import type {Rate} from './Policy';
+
+type LastSelectedDistanceRates = Record;
+
+export default LastSelectedDistanceRates;
diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts
index c24e0871e0ed..e654ba30f53d 100644
--- a/src/types/onyx/index.ts
+++ b/src/types/onyx/index.ts
@@ -19,6 +19,7 @@ import type IntroSelected from './IntroSelected';
import type InvitedEmailsToAccountIDs from './InvitedEmailsToAccountIDs';
import type IOU from './IOU';
import type LastPaymentMethod from './LastPaymentMethod';
+import type LastSelectedDistanceRates from './LastSelectedDistanceRates';
import type Locale from './Locale';
import type {LoginList} from './Login';
import type Login from './Login';
@@ -148,6 +149,7 @@ export type {
PolicyReportFields,
RecentlyUsedReportFields,
LastPaymentMethod,
+ LastSelectedDistanceRates,
InvitedEmailsToAccountIDs,
Log,
};
From 8da91dcb079bfafd3fe0b28e4367e3d30b9efe63 Mon Sep 17 00:00:00 2001
From: tienifr
Date: Mon, 26 Feb 2024 00:22:06 +0700
Subject: [PATCH 05/48] fix lint
---
src/components/MoneyRequestConfirmationList.js | 4 ++--
.../MoneyTemporaryForRefactorRequestConfirmationList.js | 4 ++--
.../MoneyTemporaryForRefactorRequestParticipantsSelector.js | 2 +-
.../MoneyRequestParticipantsSelector.js | 2 +-
4 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js
index 7a4eedcd281b..e9291c8c8457 100755
--- a/src/components/MoneyRequestConfirmationList.js
+++ b/src/components/MoneyRequestConfirmationList.js
@@ -227,7 +227,6 @@ function MoneyRequestConfirmationList(props) {
const {unit, rate, currency} = props.mileageRate;
const distance = lodashGet(transaction, 'routes.route0.distance', 0);
- const displayDistance = DistanceRequestUtils.getDistanceForDisplay(hasRoute, distance, unit, rate, translate);
const shouldCalculateDistanceAmount = props.isDistanceRequest && props.iouAmount === 0;
// A flag for showing the categories field
@@ -262,6 +261,7 @@ function MoneyRequestConfirmationList(props) {
props.isDistanceRequest ? currency : props.iouCurrencyCode,
);
const formattedTaxAmount = CurrencyUtils.convertToDisplayString(props.transaction.taxAmount, props.iouCurrencyCode);
+ const formattedDistance = DistanceRequestUtils.getDistanceForDisplay(hasRoute, distance, unit, rate, translate);
const defaultTaxKey = props.policyTaxRates.defaultExternalID;
const defaultTaxName = (defaultTaxKey && `${props.policyTaxRates.taxes[defaultTaxKey].name} (${props.policyTaxRates.taxes[defaultTaxKey].value}) • ${translate('common.default')}`) || '';
@@ -721,7 +721,7 @@ function MoneyRequestConfirmationList(props) {
{props.isDistanceRequest && (
Date: Mon, 26 Feb 2024 00:37:30 +0700
Subject: [PATCH 06/48] fix: waypoints are not saved
---
src/libs/actions/IOU.ts | 15 +++++----------
src/pages/iou/request/IOURequestStartPage.js | 4 ++--
2 files changed, 7 insertions(+), 12 deletions(-)
diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts
index 4dc745b0e3b3..0e8d3f392feb 100644
--- a/src/libs/actions/IOU.ts
+++ b/src/libs/actions/IOU.ts
@@ -231,17 +231,11 @@ Onyx.connect({
/**
* Initialize money request info
- * @param report to attach the transaction to
- * @param policy
+ * @param reportID to attach the transaction to
* @param isFromGlobalCreate
* @param iouRequestType one of manual/scan/distance
*/
-function initMoneyRequest(
- report: OnyxEntry,
- policy: OnyxEntry,
- isFromGlobalCreate: boolean,
- iouRequestType: IOURequestType = CONST.IOU.REQUEST_TYPE.MANUAL,
-) {
+function initMoneyRequest(reportID: string, isFromGlobalCreate: boolean, iouRequestType: IOURequestType = CONST.IOU.REQUEST_TYPE.MANUAL) {
// Generate a brand new transactionID
const newTransactionID = CONST.IOU.OPTIMISTIC_TRANSACTION_ID;
// Disabling this line since currentDate can be an empty string
@@ -255,9 +249,10 @@ function initMoneyRequest(
waypoint0: {},
waypoint1: {},
};
+ const report = allReports?.[reportID];
let customUnitRateID: string = CONST.CUSTOM_UNITS.FAKE_P2P_ID;
if (report?.isPolicyExpenseChat) {
- customUnitRateID = lastSelectedDistanceRates?.[policy?.id ?? '']?.customUnitRateID ?? DistanceRequestUtils.getDefaultMileageRate(policy)?.rateID ?? '';
+ customUnitRateID = lastSelectedDistanceRates?.[report?.policyID ?? '']?.customUnitRateID ?? DistanceRequestUtils.getDefaultMileageRate(policy)?.rateID ?? '';
}
comment.customUnit = {customUnitRateID};
}
@@ -270,7 +265,7 @@ function initMoneyRequest(
created,
currency: currentUserPersonalDetails.localCurrencyCode ?? CONST.CURRENCY.USD,
iouRequestType,
- reportID: report?.reportID ?? '',
+ reportID,
transactionID: newTransactionID,
isFromGlobalCreate,
merchant: CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT,
diff --git a/src/pages/iou/request/IOURequestStartPage.js b/src/pages/iou/request/IOURequestStartPage.js
index d1b416f79fdc..05e3d7c96311 100644
--- a/src/pages/iou/request/IOURequestStartPage.js
+++ b/src/pages/iou/request/IOURequestStartPage.js
@@ -98,8 +98,8 @@ function IOURequestStartPage({
if (transaction.reportID === reportID) {
return;
}
- IOU.initMoneyRequest(report, policy, isFromGlobalCreate, transactionRequestType.current);
- }, [transaction, reportID, report, policy, iouType, isFromGlobalCreate]);
+ IOU.initMoneyRequest(reportID, isFromGlobalCreate, transactionRequestType.current);
+ }, [transaction, reportID, iouType, isFromGlobalCreate]);
const isExpenseChat = ReportUtils.isPolicyExpenseChat(report);
const isExpenseReport = ReportUtils.isExpenseReport(report);
From 11d67d190e78ed3bc9820eb25947d5fff174069e Mon Sep 17 00:00:00 2001
From: tienifr
Date: Mon, 26 Feb 2024 00:45:18 +0700
Subject: [PATCH 07/48] fix typecheck
---
src/libs/actions/IOU.ts | 5 +++--
src/pages/iou/request/IOURequestStartPage.js | 8 ++++----
2 files changed, 7 insertions(+), 6 deletions(-)
diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts
index 0e8d3f392feb..92454226519f 100644
--- a/src/libs/actions/IOU.ts
+++ b/src/libs/actions/IOU.ts
@@ -232,10 +232,11 @@ Onyx.connect({
/**
* Initialize money request info
* @param reportID to attach the transaction to
+ * @param policy
* @param isFromGlobalCreate
* @param iouRequestType one of manual/scan/distance
*/
-function initMoneyRequest(reportID: string, isFromGlobalCreate: boolean, iouRequestType: IOURequestType = CONST.IOU.REQUEST_TYPE.MANUAL) {
+function initMoneyRequest(reportID: string, policy: OnyxEntry, isFromGlobalCreate: boolean, iouRequestType: IOURequestType = CONST.IOU.REQUEST_TYPE.MANUAL) {
// Generate a brand new transactionID
const newTransactionID = CONST.IOU.OPTIMISTIC_TRANSACTION_ID;
// Disabling this line since currentDate can be an empty string
@@ -252,7 +253,7 @@ function initMoneyRequest(reportID: string, isFromGlobalCreate: boolean, iouRequ
const report = allReports?.[reportID];
let customUnitRateID: string = CONST.CUSTOM_UNITS.FAKE_P2P_ID;
if (report?.isPolicyExpenseChat) {
- customUnitRateID = lastSelectedDistanceRates?.[report?.policyID ?? '']?.customUnitRateID ?? DistanceRequestUtils.getDefaultMileageRate(policy)?.rateID ?? '';
+ customUnitRateID = lastSelectedDistanceRates?.[policy?.id ?? '']?.customUnitRateID ?? DistanceRequestUtils.getDefaultMileageRate(policy)?.rateID ?? '';
}
comment.customUnit = {customUnitRateID};
}
diff --git a/src/pages/iou/request/IOURequestStartPage.js b/src/pages/iou/request/IOURequestStartPage.js
index 05e3d7c96311..8f682b7ffc5e 100644
--- a/src/pages/iou/request/IOURequestStartPage.js
+++ b/src/pages/iou/request/IOURequestStartPage.js
@@ -98,8 +98,8 @@ function IOURequestStartPage({
if (transaction.reportID === reportID) {
return;
}
- IOU.initMoneyRequest(reportID, isFromGlobalCreate, transactionRequestType.current);
- }, [transaction, reportID, iouType, isFromGlobalCreate]);
+ IOU.initMoneyRequest(reportID, policy, isFromGlobalCreate, transactionRequestType.current);
+ }, [transaction, policy, reportID, iouType, isFromGlobalCreate]);
const isExpenseChat = ReportUtils.isPolicyExpenseChat(report);
const isExpenseReport = ReportUtils.isExpenseReport(report);
@@ -117,10 +117,10 @@ function IOURequestStartPage({
if (newIouType === previousIOURequestType) {
return;
}
- IOU.initMoneyRequest(reportID, isFromGlobalCreate, newIouType);
+ IOU.initMoneyRequest(reportID, policy, isFromGlobalCreate, newIouType);
transactionRequestType.current = newIouType;
},
- [previousIOURequestType, reportID, isFromGlobalCreate],
+ [policy, previousIOURequestType, reportID, isFromGlobalCreate],
);
if (!transaction.transactionID) {
From 867979957e65f66125e5e11db230d8ba987a510b Mon Sep 17 00:00:00 2001
From: tienifr
Date: Mon, 26 Feb 2024 00:57:05 +0700
Subject: [PATCH 08/48] get report from onyx
---
src/libs/actions/IOU.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts
index 92454226519f..f3112ed00415 100644
--- a/src/libs/actions/IOU.ts
+++ b/src/libs/actions/IOU.ts
@@ -250,9 +250,9 @@ function initMoneyRequest(reportID: string, policy: OnyxEntry,
waypoint0: {},
waypoint1: {},
};
- const report = allReports?.[reportID];
+ const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null;
let customUnitRateID: string = CONST.CUSTOM_UNITS.FAKE_P2P_ID;
- if (report?.isPolicyExpenseChat) {
+ if (ReportUtils.isPolicyExpenseChat(report)) {
customUnitRateID = lastSelectedDistanceRates?.[policy?.id ?? '']?.customUnitRateID ?? DistanceRequestUtils.getDefaultMileageRate(policy)?.rateID ?? '';
}
comment.customUnit = {customUnitRateID};
From b4401b3243a6e0d35482a479125d47d95e61871c Mon Sep 17 00:00:00 2001
From: tienifr
Date: Mon, 26 Feb 2024 15:07:04 +0700
Subject: [PATCH 09/48] revert: show p2p distance
---
src/components/MoneyRequestConfirmationList.js | 7 +++----
.../MoneyTemporaryForRefactorRequestConfirmationList.js | 7 +++----
2 files changed, 6 insertions(+), 8 deletions(-)
diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js
index e9291c8c8457..7d896a9aef39 100755
--- a/src/components/MoneyRequestConfirmationList.js
+++ b/src/components/MoneyRequestConfirmationList.js
@@ -261,7 +261,6 @@ function MoneyRequestConfirmationList(props) {
props.isDistanceRequest ? currency : props.iouCurrencyCode,
);
const formattedTaxAmount = CurrencyUtils.convertToDisplayString(props.transaction.taxAmount, props.iouCurrencyCode);
- const formattedDistance = DistanceRequestUtils.getDistanceForDisplay(hasRoute, distance, unit, rate, translate);
const defaultTaxKey = props.policyTaxRates.defaultExternalID;
const defaultTaxName = (defaultTaxKey && `${props.policyTaxRates.taxes[defaultTaxKey].name} (${props.policyTaxRates.taxes[defaultTaxKey].value}) • ${translate('common.default')}`) || '';
@@ -720,13 +719,13 @@ function MoneyRequestConfirmationList(props) {
)}
{props.isDistanceRequest && (
Navigation.navigate(ROUTES.MONEY_REQUEST_DISTANCE.getRoute(props.iouType, props.reportID))}
- disabled={didConfirm || canUseP2PDistanceRequests || !isTypeRequest}
+ disabled={didConfirm || !(canUseP2PDistanceRequests || isTypeRequest)}
interactive={!props.isReadOnly}
/>
)}
diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js
index 88b339508367..2431acea8077 100755
--- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js
+++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js
@@ -285,7 +285,6 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
isDistanceRequest ? currency : iouCurrencyCode,
);
const formattedTaxAmount = CurrencyUtils.convertToDisplayString(transaction.taxAmount, iouCurrencyCode);
- const formattedDistance = DistanceRequestUtils.getDistanceForDisplay(hasRoute, distance, unit, rate, translate);
const defaultTaxKey = policyTaxRates.defaultExternalID;
const defaultTaxName = (defaultTaxKey && `${policyTaxRates.taxes[defaultTaxKey].name} (${policyTaxRates.taxes[defaultTaxKey].value}) • ${translate('common.default')}`) || '';
@@ -680,13 +679,13 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
item: (
Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams()))}
- disabled={didConfirm || canUseP2PDistanceRequests || !isTypeRequest}
+ disabled={didConfirm || !(canUseP2PDistanceRequests || isTypeRequest)}
interactive={!isReadOnly}
/>
),
From f9c03d8a7008355ee18ed7143b7446e8cc8c63f7 Mon Sep 17 00:00:00 2001
From: tienifr
Date: Mon, 26 Feb 2024 15:12:54 +0700
Subject: [PATCH 10/48] fix lint
---
src/components/MoneyRequestConfirmationList.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js
index 7d896a9aef39..ac7d2cfae725 100755
--- a/src/components/MoneyRequestConfirmationList.js
+++ b/src/components/MoneyRequestConfirmationList.js
@@ -720,7 +720,7 @@ function MoneyRequestConfirmationList(props) {
{props.isDistanceRequest && (
Date: Mon, 26 Feb 2024 15:58:20 +0700
Subject: [PATCH 11/48] add beta check
---
src/components/MoneyRequestConfirmationList.js | 2 +-
.../MoneyTemporaryForRefactorRequestConfirmationList.js | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js
index ac7d2cfae725..b7cb530612be 100755
--- a/src/components/MoneyRequestConfirmationList.js
+++ b/src/components/MoneyRequestConfirmationList.js
@@ -719,7 +719,7 @@ function MoneyRequestConfirmationList(props) {
)}
{props.isDistanceRequest && (
Date: Thu, 29 Feb 2024 02:53:34 +0700
Subject: [PATCH 12/48] resolve feedbacks
---
src/CONST.ts | 2 +-
src/components/MoneyRequestConfirmationList.tsx | 9 ++++++---
.../MoneyTemporaryForRefactorRequestConfirmationList.js | 9 ++++++---
src/libs/DistanceRequestUtils.ts | 2 +-
src/libs/actions/IOU.ts | 2 +-
src/types/onyx/LastSelectedDistanceRates.ts | 4 +---
src/types/onyx/Policy.ts | 2 +-
7 files changed, 17 insertions(+), 13 deletions(-)
diff --git a/src/CONST.ts b/src/CONST.ts
index 75487fa6c162..e314856c9031 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -1396,7 +1396,7 @@ const CONST = {
MILEAGE_IRS_RATE: 0.655,
DEFAULT_RATE: 'Default Rate',
RATE_DECIMALS: 3,
- FAKE_P2P_ID: '__fake_p2p_id__',
+ FAKE_P2P_ID: '_FAKE_P2P_ID_',
},
TERMS: {
diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx
index 6da05e52d3fd..8387ceddbe98 100755
--- a/src/components/MoneyRequestConfirmationList.tsx
+++ b/src/components/MoneyRequestConfirmationList.tsx
@@ -272,6 +272,7 @@ function MoneyRequestConfirmationList({
isDistanceRequest ? mileageRate?.currency : iouCurrencyCode,
);
const formattedTaxAmount = CurrencyUtils.convertToDisplayString(transaction?.taxAmount, iouCurrencyCode);
+ const formattedDistance = DistanceRequestUtils.getDistanceForDisplay(hasRoute, distance, mileageRate?.unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, mileageRate?.rate ?? 0, translate);
const defaultTaxKey = taxRates?.defaultExternalID;
const defaultTaxName = (defaultTaxKey && `${taxRates?.taxes[defaultTaxKey].name} (${taxRates?.taxes[defaultTaxKey].value}) • ${translate('common.default')}`) ?? '';
@@ -597,6 +598,7 @@ function MoneyRequestConfirmationList({
styles.mb2,
]);
+ const canEditDistance = isTypeRequest || (canUseP2PDistanceRequests && isSplitBill);
const receiptData = receiptPath && receiptFilename ? ReceiptUtils.getThumbnailAndImageURIs(transaction ?? null, receiptPath, receiptFilename) : null;
return (
// @ts-expect-error TODO: Remove this once OptionsSelector (https://github.com/Expensify/App/issues/25125) is migrated to TypeScript.
@@ -738,13 +740,14 @@ function MoneyRequestConfirmationList({
)}
{isDistanceRequest && (
Navigation.navigate(ROUTES.MONEY_REQUEST_DISTANCE.getRoute(iouType, reportID))}
- disabled={didConfirm || !(canUseP2PDistanceRequests || isTypeRequest)}
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+ disabled={didConfirm || !canEditDistance}
interactive={!isReadOnly}
/>
)}
diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js
index 5374e7fcb0c8..81bdf110373d 100755
--- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js
+++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js
@@ -247,6 +247,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
const isTypeRequest = iouType === CONST.IOU.TYPE.REQUEST;
const isTypeSplit = iouType === CONST.IOU.TYPE.SPLIT;
const isTypeSend = iouType === CONST.IOU.TYPE.SEND;
+ const canEditDistance = isTypeRequest || (canUseP2PDistanceRequests && isTypeSplit);
const {unit, rate, currency} = mileageRate;
const distance = lodashGet(transaction, 'routes.route0.distance', 0);
@@ -285,6 +286,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
isDistanceRequest ? currency : iouCurrencyCode,
);
const formattedTaxAmount = CurrencyUtils.convertToDisplayString(transaction.taxAmount, iouCurrencyCode);
+ const formattedDistance = DistanceRequestUtils.getDistanceForDisplay(hasRoute, distance, unit, rate, translate);
const defaultTaxKey = taxRates.defaultExternalID;
const defaultTaxName = (defaultTaxKey && `${taxRates.taxes[defaultTaxKey].name} (${taxRates.taxes[defaultTaxKey].value}) • ${translate('common.default')}`) || '';
@@ -679,13 +681,14 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
item: (
Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams()))}
- disabled={didConfirm || !(canUseP2PDistanceRequests || isTypeRequest)}
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+ disabled={didConfirm || !canEditDistance}
interactive={!isReadOnly}
/>
),
diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts
index 8e95394605a3..3585d8e12a92 100644
--- a/src/libs/DistanceRequestUtils.ts
+++ b/src/libs/DistanceRequestUtils.ts
@@ -32,7 +32,7 @@ function getDefaultMileageRate(policy: OnyxEntry): MileageRate | null {
}
return {
- rateID: distanceRate.customUnitRateID,
+ customUnitRateID: distanceRate.customUnitRateID,
rate: distanceRate.rate,
currency: distanceRate.currency ?? 'USD',
unit: distanceUnit.attributes.unit,
diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts
index c4388563af9f..b9eba1368ce9 100644
--- a/src/libs/actions/IOU.ts
+++ b/src/libs/actions/IOU.ts
@@ -255,7 +255,7 @@ function initMoneyRequest(reportID: string, policy: OnyxEntry,
const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null;
let customUnitRateID: string = CONST.CUSTOM_UNITS.FAKE_P2P_ID;
if (ReportUtils.isPolicyExpenseChat(report)) {
- customUnitRateID = lastSelectedDistanceRates?.[policy?.id ?? '']?.customUnitRateID ?? DistanceRequestUtils.getDefaultMileageRate(policy)?.rateID ?? '';
+ customUnitRateID = lastSelectedDistanceRates?.[policy?.id ?? ''] ?? DistanceRequestUtils.getDefaultMileageRate(policy)?.customUnitRateID ?? '';
}
comment.customUnit = {customUnitRateID};
}
diff --git a/src/types/onyx/LastSelectedDistanceRates.ts b/src/types/onyx/LastSelectedDistanceRates.ts
index 4ebf39e43bcb..1db1cf32b160 100644
--- a/src/types/onyx/LastSelectedDistanceRates.ts
+++ b/src/types/onyx/LastSelectedDistanceRates.ts
@@ -1,5 +1,3 @@
-import type {Rate} from './Policy';
-
-type LastSelectedDistanceRates = Record;
+type LastSelectedDistanceRates = Record;
export default LastSelectedDistanceRates;
diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts
index 2e4447e92c8b..c7a2a7f8a4e1 100644
--- a/src/types/onyx/Policy.ts
+++ b/src/types/onyx/Policy.ts
@@ -18,7 +18,7 @@ type Attributes = {
};
type MileageRate = {
- rateID?: string;
+ customUnitRateID?: string;
unit: Unit;
rate?: number;
currency: string;
From e137f6234675a54877022d30d8202bbf281a42af Mon Sep 17 00:00:00 2001
From: tienifr
Date: Fri, 1 Mar 2024 03:24:09 +0700
Subject: [PATCH 13/48] reapply changes
---
src/components/MoneyRequestConfirmationList.js | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js
index df2781d3ea89..4f892e9ace75 100755
--- a/src/components/MoneyRequestConfirmationList.js
+++ b/src/components/MoneyRequestConfirmationList.js
@@ -217,11 +217,12 @@ function MoneyRequestConfirmationList(props) {
const {onSendMoney, onConfirm, onSelectParticipant} = props;
const {translate, toLocaleDigit} = useLocalize();
const transaction = props.transaction;
- const {canUseViolations} = usePermissions();
+ const {canUseP2PDistanceRequests, canUseViolations} = usePermissions();
const isTypeRequest = props.iouType === CONST.IOU.TYPE.REQUEST;
const isSplitBill = props.iouType === CONST.IOU.TYPE.SPLIT;
const isTypeSend = props.iouType === CONST.IOU.TYPE.SEND;
+ const canEditDistance = isTypeRequest || (canUseP2PDistanceRequests && isSplitBill);
const isSplitWithScan = isSplitBill && props.isScanRequest;
@@ -262,6 +263,7 @@ function MoneyRequestConfirmationList(props) {
props.isDistanceRequest ? currency : props.iouCurrencyCode,
);
const formattedTaxAmount = CurrencyUtils.convertToDisplayString(props.transaction.taxAmount, props.iouCurrencyCode);
+ const formattedDistance = DistanceRequestUtils.getDistanceForDisplay(hasRoute, distance, unit, rate, translate);
const defaultTaxKey = taxRates.defaultExternalID;
const defaultTaxName = (defaultTaxKey && `${taxRates.taxes[defaultTaxKey].name} (${taxRates.taxes[defaultTaxKey].value}) • ${translate('common.default')}`) || '';
@@ -720,13 +722,14 @@ function MoneyRequestConfirmationList(props) {
)}
{props.isDistanceRequest && (
Navigation.navigate(ROUTES.MONEY_REQUEST_DISTANCE.getRoute(props.iouType, props.reportID))}
- disabled={didConfirm || !isTypeRequest}
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+ disabled={didConfirm || !canEditDistance}
interactive={!props.isReadOnly}
/>
)}
From 6851e9de62e04bec0706ec0e40ed7be584a597c3 Mon Sep 17 00:00:00 2001
From: tienifr
Date: Mon, 4 Mar 2024 15:32:41 +0700
Subject: [PATCH 14/48] refactor distancefordisplay func
---
src/components/MoneyRequestConfirmationList.js | 3 +--
.../MoneyTemporaryForRefactorRequestConfirmationList.js | 3 +--
src/libs/DistanceRequestUtils.ts | 8 +++-----
3 files changed, 5 insertions(+), 9 deletions(-)
diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js
index 4f892e9ace75..9c322cee56ac 100755
--- a/src/components/MoneyRequestConfirmationList.js
+++ b/src/components/MoneyRequestConfirmationList.js
@@ -263,7 +263,6 @@ function MoneyRequestConfirmationList(props) {
props.isDistanceRequest ? currency : props.iouCurrencyCode,
);
const formattedTaxAmount = CurrencyUtils.convertToDisplayString(props.transaction.taxAmount, props.iouCurrencyCode);
- const formattedDistance = DistanceRequestUtils.getDistanceForDisplay(hasRoute, distance, unit, rate, translate);
const defaultTaxKey = taxRates.defaultExternalID;
const defaultTaxName = (defaultTaxKey && `${taxRates.taxes[defaultTaxKey].name} (${taxRates.taxes[defaultTaxKey].value}) • ${translate('common.default')}`) || '';
@@ -723,7 +722,7 @@ function MoneyRequestConfirmationList(props) {
{props.isDistanceRequest && (
Date: Mon, 4 Mar 2024 16:02:33 +0700
Subject: [PATCH 15/48] allow distance request in group chat and dm
---
src/pages/iou/MoneyRequestSelectorPage.js | 4 +++-
src/pages/iou/request/IOURequestStartPage.js | 4 +++-
2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/src/pages/iou/MoneyRequestSelectorPage.js b/src/pages/iou/MoneyRequestSelectorPage.js
index 8d7272df63e9..62b1adf1fb8c 100644
--- a/src/pages/iou/MoneyRequestSelectorPage.js
+++ b/src/pages/iou/MoneyRequestSelectorPage.js
@@ -10,6 +10,7 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton';
import ScreenWrapper from '@components/ScreenWrapper';
import TabSelector from '@components/TabSelector/TabSelector';
import useLocalize from '@hooks/useLocalize';
+import usePermissions from '@hooks/usePermissions';
import usePrevious from '@hooks/usePrevious';
import useThemeStyles from '@hooks/useThemeStyles';
import compose from '@libs/compose';
@@ -62,6 +63,7 @@ const defaultProps = {
function MoneyRequestSelectorPage(props) {
const styles = useThemeStyles();
const [isDraggingOver, setIsDraggingOver] = useState(false);
+ const {canUseP2PDistanceRequests} = usePermissions();
const iouType = lodashGet(props.route, 'params.iouType', '');
const reportID = lodashGet(props.route, 'params.reportID', '');
@@ -75,7 +77,7 @@ function MoneyRequestSelectorPage(props) {
const isFromGlobalCreate = !reportID;
const isExpenseChat = ReportUtils.isPolicyExpenseChat(props.report);
const isExpenseReport = ReportUtils.isExpenseReport(props.report);
- const shouldDisplayDistanceRequest = isExpenseChat || isExpenseReport || isFromGlobalCreate;
+ const shouldDisplayDistanceRequest = canUseP2PDistanceRequests || isExpenseChat || isExpenseReport || isFromGlobalCreate;
const resetMoneyRequestInfo = () => {
const moneyRequestID = `${iouType}${reportID}`;
diff --git a/src/pages/iou/request/IOURequestStartPage.js b/src/pages/iou/request/IOURequestStartPage.js
index fa6fee2202ac..b1ae257b792f 100644
--- a/src/pages/iou/request/IOURequestStartPage.js
+++ b/src/pages/iou/request/IOURequestStartPage.js
@@ -13,6 +13,7 @@ import ScreenWrapper from '@components/ScreenWrapper';
import TabSelector from '@components/TabSelector/TabSelector';
import transactionPropTypes from '@components/transactionPropTypes';
import useLocalize from '@hooks/useLocalize';
+import usePermissions from '@hooks/usePermissions';
import usePrevious from '@hooks/usePrevious';
import useThemeStyles from '@hooks/useThemeStyles';
import * as DeviceCapabilities from '@libs/DeviceCapabilities';
@@ -80,6 +81,7 @@ function IOURequestStartPage({
};
const transactionRequestType = useRef(TransactionUtils.getRequestType(transaction));
const previousIOURequestType = usePrevious(transactionRequestType.current);
+ const {canUseP2PDistanceRequests} = usePermissions();
const isFromGlobalCreate = _.isEmpty(report.reportID);
useFocusEffect(
@@ -107,7 +109,7 @@ function IOURequestStartPage({
const isExpenseChat = ReportUtils.isPolicyExpenseChat(report);
const isExpenseReport = ReportUtils.isExpenseReport(report);
- const shouldDisplayDistanceRequest = isExpenseChat || isExpenseReport || isFromGlobalCreate;
+ const shouldDisplayDistanceRequest = canUseP2PDistanceRequests || isExpenseChat || isExpenseReport || isFromGlobalCreate;
// Allow the user to create the request if we are creating the request in global menu or the report can create the request
const isAllowedToCreateRequest = _.isEmpty(report.reportID) || ReportUtils.canCreateRequest(report, policy, iouType);
From c0d651e47e36644c745ecffbadd819db8f7b15ca Mon Sep 17 00:00:00 2001
From: Carlos Martins
Date: Mon, 4 Mar 2024 16:53:46 -0700
Subject: [PATCH 16/48] register route
---
src/ROUTES.ts | 4 ++++
src/SCREENS.ts | 1 +
src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx | 1 +
src/libs/Navigation/linkingConfig/config.ts | 3 +++
src/libs/Navigation/types.ts | 3 +++
5 files changed, 12 insertions(+)
diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index cfc287ba2cdc..83ac055c448e 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -546,6 +546,10 @@ const ROUTES = {
route: 'workspace/:policyID/categories/settings',
getRoute: (policyID: string) => `workspace/${policyID}/categories/settings` as const,
},
+ WORKSPACE_CREATE_CATEGORY: {
+ route: 'workspace/:policyID/categories/new',
+ getRoute: (policyID: string) => `workspace/${policyID}/categories/new` as const,
+ },
WORKSPACE_TAGS: {
route: 'workspace/:policyID/tags',
getRoute: (policyID: string) => `workspace/${policyID}/tags` as const,
diff --git a/src/SCREENS.ts b/src/SCREENS.ts
index 2369fe435feb..17073d289f8f 100644
--- a/src/SCREENS.ts
+++ b/src/SCREENS.ts
@@ -223,6 +223,7 @@ const SCREENS = {
DESCRIPTION: 'Workspace_Profile_Description',
SHARE: 'Workspace_Profile_Share',
NAME: 'Workspace_Profile_Name',
+ CATEGORY_CREATE: 'Category_Create',
CATEGORY_SETTINGS: 'Category_Settings',
CATEGORIES_SETTINGS: 'Categories_Settings',
},
diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx
index 545641957c9a..cee9e31cedea 100644
--- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx
+++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx
@@ -251,6 +251,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../pages/workspace/WorkspaceProfileCurrencyPage').default as React.ComponentType,
[SCREENS.WORKSPACE.CATEGORY_SETTINGS]: () => require('../../../pages/workspace/categories/CategorySettingsPage').default as React.ComponentType,
[SCREENS.WORKSPACE.CATEGORIES_SETTINGS]: () => require('../../../pages/workspace/categories/WorkspaceCategoriesSettingsPage').default as React.ComponentType,
+ [SCREENS.WORKSPACE.CATEGORY_CREATE]: () => require('../../../pages/workspace/categories/CreateCategoryPage').default as React.ComponentType,
[SCREENS.REIMBURSEMENT_ACCOUNT]: () => require('../../../pages/ReimbursementAccount/ReimbursementAccountPage').default as React.ComponentType,
[SCREENS.GET_ASSISTANCE]: () => require('../../../pages/GetAssistancePage').default as React.ComponentType,
[SCREENS.SETTINGS.TWO_FACTOR_AUTH]: () => require('../../../pages/settings/Security/TwoFactorAuth/TwoFactorAuthPage').default as React.ComponentType,
diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts
index 3ceb3c1ac7df..4f2810b2e501 100644
--- a/src/libs/Navigation/linkingConfig/config.ts
+++ b/src/libs/Navigation/linkingConfig/config.ts
@@ -280,6 +280,9 @@ const config: LinkingOptions['config'] = {
[SCREENS.WORKSPACE.CATEGORIES_SETTINGS]: {
path: ROUTES.WORKSPACE_CATEGORIES_SETTINGS.route,
},
+ [SCREENS.WORKSPACE.CATEGORY_CREATE]: {
+ path: ROUTES.WORKSPACE_CREATE_CATEGORY.route,
+ },
[SCREENS.REIMBURSEMENT_ACCOUNT]: {
path: ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.route,
exact: true,
diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts
index 9426ca6e900c..feb7ec0f7cbe 100644
--- a/src/libs/Navigation/types.ts
+++ b/src/libs/Navigation/types.ts
@@ -91,6 +91,9 @@ type CentralPaneNavigatorParamList = {
[SCREENS.WORKSPACE.CATEGORIES]: {
policyID: string;
};
+ [SCREENS.WORKSPACE.CATEGORY_CREATE]: {
+ policyID: string;
+ };
[SCREENS.WORKSPACE.TAGS]: {
policyID: string;
};
From 39a2af4d0aa8c1a271dc27e15bdddc0190931458 Mon Sep 17 00:00:00 2001
From: Carlos Martins
Date: Mon, 4 Mar 2024 17:10:07 -0700
Subject: [PATCH 17/48] add button on web
---
src/languages/en.ts | 1 +
src/languages/es.ts | 1 +
src/libs/Navigation/types.ts | 1 +
.../categories/CreateCategoryPage.tsx | 68 +++++++++++++++++++
.../categories/WorkspaceCategoriesPage.tsx | 18 ++++-
5 files changed, 88 insertions(+), 1 deletion(-)
create mode 100644 src/pages/workspace/categories/CreateCategoryPage.tsx
diff --git a/src/languages/en.ts b/src/languages/en.ts
index cfbc3ecd0565..59d08b13aa4e 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -1768,6 +1768,7 @@ export default {
subtitle: 'Add a category to organize your spend.',
},
genericFailureMessage: 'An error occurred while updating the category, please try again.',
+ addCategory: "Add category",
},
tags: {
requiresTag: 'Members must tag all spend',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 9aea06797cc5..716a64a2650b 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -1792,6 +1792,7 @@ export default {
subtitle: 'Añade una categoría para organizar tu gasto.',
},
genericFailureMessage: 'Se ha producido un error al intentar eliminar la categoría. Por favor, inténtalo más tarde.',
+ addCategory: 'Añadir categoría',
},
tags: {
requiresTag: 'Los miembros deben etiquetar todos los gastos',
diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts
index feb7ec0f7cbe..f40c2772f3f4 100644
--- a/src/libs/Navigation/types.ts
+++ b/src/libs/Navigation/types.ts
@@ -96,6 +96,7 @@ type CentralPaneNavigatorParamList = {
};
[SCREENS.WORKSPACE.TAGS]: {
policyID: string;
+ categoryName: string;
};
};
diff --git a/src/pages/workspace/categories/CreateCategoryPage.tsx b/src/pages/workspace/categories/CreateCategoryPage.tsx
new file mode 100644
index 000000000000..8f0cfc4950b3
--- /dev/null
+++ b/src/pages/workspace/categories/CreateCategoryPage.tsx
@@ -0,0 +1,68 @@
+import type {StackScreenProps} from '@react-navigation/stack';
+import React, {useMemo, useState} from 'react';
+import {View} from 'react-native';
+import {withOnyx} from 'react-native-onyx';
+import type {OnyxEntry} from 'react-native-onyx';
+import Button from '@components/Button';
+import HeaderWithBackButton from '@components/HeaderWithBackButton';
+import Icon from '@components/Icon';
+import * as Expensicons from '@components/Icon/Expensicons';
+import * as Illustrations from '@components/Icon/Illustrations';
+import ScreenWrapper from '@components/ScreenWrapper';
+import SelectionList from '@components/SelectionList';
+import TableListItem from '@components/SelectionList/TableListItem';
+import Text from '@components/Text';
+import WorkspaceEmptyStateSection from '@components/WorkspaceEmptyStateSection';
+import useLocalize from '@hooks/useLocalize';
+import useTheme from '@hooks/useTheme';
+import useThemeStyles from '@hooks/useThemeStyles';
+import useWindowDimensions from '@hooks/useWindowDimensions';
+import Navigation from '@libs/Navigation/Navigation';
+import type {CentralPaneNavigatorParamList} from '@navigation/types';
+import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper';
+import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper';
+import ONYXKEYS from '@src/ONYXKEYS';
+import ROUTES from '@src/ROUTES';
+import type SCREENS from '@src/SCREENS';
+import type * as OnyxTypes from '@src/types/onyx';
+
+type PolicyForList = {
+ value: string;
+ text: string;
+ keyForList: string;
+ isSelected: boolean;
+ rightElement: React.ReactNode;
+};
+
+type WorkspaceCategoriesOnyxProps = {
+ /** Collection of categories attached to a policy */
+ policyCategories: OnyxEntry;
+};
+
+type WorkspaceCategoriesPageProps = WorkspaceCategoriesOnyxProps & StackScreenProps;
+
+function CreateCategoryPage({route}: WorkspaceCategoriesPageProps) {
+
+ return (
+
+
+
+ Test
+
+
+
+ );
+}
+
+CreateCategoryPage.displayName = 'CreateCategoryPage';
+
+export default withOnyx({
+ policyCategories: {
+ key: ({route}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${route.params.policyID}`,
+ },
+})(CreateCategoryPage);
diff --git a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx
index d15011489bac..ae38a9f4e247 100644
--- a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx
+++ b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx
@@ -97,6 +97,10 @@ function WorkspaceCategoriesPage({policyCategories, route}: WorkspaceCategoriesP
Navigation.navigate(ROUTES.WORKSPACE_CATEGORY_SETTINGS.getRoute(route.params.policyID, category.text));
};
+ const navigateToCreateCategoryPage = () => {
+ Navigation.navigate(ROUTES.WORKSPACE_CREATE_CATEGORY.getRoute(route.params.policyID))
+ }
+
const settingsButton = (