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 = (