From 05f1ad6d0d50a45b188c4d8fccd8f72788205fc6 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 11 Apr 2024 19:19:37 +0800 Subject: [PATCH 001/230] navigate back when delete modal hides --- src/components/MoneyReportHeader.tsx | 18 +++++++++++++----- src/components/MoneyRequestHeader.tsx | 17 +++++++++++++---- src/libs/actions/IOU.ts | 7 +++---- 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 14227d6a2051..5d68440266f3 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -1,4 +1,4 @@ -import React, {useCallback, useEffect, useMemo, useState} from 'react'; +import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; @@ -13,7 +13,7 @@ import * as ReportUtils from '@libs/ReportUtils'; import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; +import ROUTES, { Route } from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -83,6 +83,8 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money const isDraft = ReportUtils.isOpenExpenseReport(moneyRequestReport); const [isConfirmModalVisible, setIsConfirmModalVisible] = useState(false); + const navigateBackToAfterDelete = useRef(); + const cancelPayment = useCallback(() => { if (!chatReport) { return; @@ -136,10 +138,10 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money if (requestParentReportAction) { const iouTransactionID = requestParentReportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? requestParentReportAction.originalMessage?.IOUTransactionID ?? '' : ''; if (ReportActionsUtils.isTrackExpenseAction(requestParentReportAction)) { - IOU.deleteTrackExpense(moneyRequestReport?.reportID ?? '', iouTransactionID, requestParentReportAction, true); - return; + navigateBackToAfterDelete.current = IOU.deleteTrackExpense(moneyRequestReport?.reportID ?? '', iouTransactionID, requestParentReportAction, true); + } else { + navigateBackToAfterDelete.current = IOU.deleteMoneyRequest(iouTransactionID, requestParentReportAction, true); } - IOU.deleteMoneyRequest(iouTransactionID, requestParentReportAction, true); } setIsDeleteRequestModalVisible(false); @@ -292,6 +294,12 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money isVisible={isDeleteRequestModalVisible} onConfirm={deleteTransaction} onCancel={() => setIsDeleteRequestModalVisible(false)} + onModalHide={() => { + if (!navigateBackToAfterDelete.current) { + return; + } + Navigation.goBack(navigateBackToAfterDelete.current) + }} prompt={translate('iou.deleteConfirmation')} confirmText={translate('common.delete')} cancelText={translate('common.cancel')} diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index f451f5f15581..2aea42d46503 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -1,4 +1,4 @@ -import React, {useCallback, useEffect, useState} from 'react'; +import React, {useCallback, useEffect, useRef, useState} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; @@ -14,6 +14,7 @@ import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type {Route} from '@src/ROUTES'; import type {Policy, Report, ReportAction, ReportActions, Session, Transaction} from '@src/types/onyx'; import type {OriginalMessageIOU} from '@src/types/onyx/OriginalMessage'; import ConfirmModal from './ConfirmModal'; @@ -63,6 +64,8 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, const isOnHold = TransactionUtils.isOnHold(transaction); const {isSmallScreenWidth, windowWidth} = useWindowDimensions(); + const navigateBackToAfterDelete = useRef(); + // Only the requestor can take delete the request, admins can only edit it. const isActionOwner = typeof parentReportAction?.actorAccountID === 'number' && typeof session?.accountID === 'number' && parentReportAction.actorAccountID === session?.accountID; const isPolicyAdmin = policy?.role === CONST.POLICY.ROLE.ADMIN; @@ -72,10 +75,10 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, if (parentReportAction) { const iouTransactionID = parentReportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? parentReportAction.originalMessage?.IOUTransactionID ?? '' : ''; if (ReportActionsUtils.isTrackExpenseAction(parentReportAction)) { - IOU.deleteTrackExpense(parentReport?.reportID ?? '', iouTransactionID, parentReportAction, true); - return; + navigateBackToAfterDelete.current = IOU.deleteTrackExpense(parentReport?.reportID ?? '', iouTransactionID, parentReportAction, true); + } else { + navigateBackToAfterDelete.current = IOU.deleteMoneyRequest(iouTransactionID, parentReportAction, true); } - IOU.deleteMoneyRequest(iouTransactionID, parentReportAction, true); } setIsDeleteModalVisible(false); @@ -200,6 +203,12 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, isVisible={isDeleteModalVisible} onConfirm={deleteTransaction} onCancel={() => setIsDeleteModalVisible(false)} + onModalHide={() => { + if (!navigateBackToAfterDelete.current) { + return; + } + Navigation.goBack(navigateBackToAfterDelete.current) + }} prompt={translate('iou.deleteConfirmation')} confirmText={translate('common.delete')} cancelText={translate('common.cancel')} diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index c2d462bbc4a8..ab50e709a972 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -4133,13 +4133,12 @@ function deleteMoneyRequest(transactionID: string, reportAction: OnyxTypes.Repor // STEP 7: Navigate the user depending on which page they are on and which resources were deleted if (iouReport && isSingleTransactionView && shouldDeleteTransactionThread && !shouldDeleteIOUReport) { // Pop the deleted report screen before navigating. This prevents navigating to the Concierge chat due to the missing report. - Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(iouReport.reportID)); - return; + return ROUTES.REPORT_WITH_ID.getRoute(iouReport.reportID); } if (iouReport?.chatReportID && shouldDeleteIOUReport) { // Pop the deleted report screen before navigating. This prevents navigating to the Concierge chat due to the missing report. - Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(iouReport.chatReportID)); + return ROUTES.REPORT_WITH_ID.getRoute(iouReport.chatReportID); } } @@ -4304,7 +4303,7 @@ function deleteTrackExpense(chatReportID: string, transactionID: string, reportA // STEP 7: Navigate the user depending on which page they are on and which resources were deleted if (isSingleTransactionView && shouldDeleteTransactionThread) { // Pop the deleted report screen before navigating. This prevents navigating to the Concierge chat due to the missing report. - Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(chatReport?.reportID ?? '')); + return ROUTES.REPORT_WITH_ID.getRoute(chatReport?.reportID ?? ''); } } From 43e030b5d42bf5f19bc7dcb93a6cc3d44890ba3c Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Tue, 23 Apr 2024 03:32:30 +0100 Subject: [PATCH 002/230] fix(money request): missing tags in violations translations --- .../ReportActionItem/MoneyRequestView.tsx | 2 +- src/hooks/useViolations.ts | 41 ++++++++++++++++--- src/libs/Violations/ViolationsUtils.ts | 2 + 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 73fc7e9bae6e..9032c8026012 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -162,7 +162,7 @@ function MoneyRequestView({ // A flag for showing tax rate const shouldShowTax = isTaxTrackingEnabled(isPolicyExpenseChat, policy); - const {getViolationsForField} = useViolations(transactionViolations ?? []); + const {getViolationsForField} = useViolations(transactionViolations ?? [], transaction, policy, policyTagList); const hasViolations = useCallback( (field: ViolationField, data?: OnyxTypes.TransactionViolation['data']): boolean => !!canUseViolations && getViolationsForField(field, data).length > 0, [canUseViolations, getViolationsForField], diff --git a/src/hooks/useViolations.ts b/src/hooks/useViolations.ts index 7aadb4ce4ca3..1d25a5e6f3d2 100644 --- a/src/hooks/useViolations.ts +++ b/src/hooks/useViolations.ts @@ -1,6 +1,8 @@ import {useCallback, useMemo} from 'react'; +import type {OnyxEntry} from 'react-native-onyx'; +import ViolationsUtils from '@libs/Violations/ViolationsUtils'; import CONST from '@src/CONST'; -import type {TransactionViolation, ViolationName} from '@src/types/onyx'; +import type {Policy, PolicyTagList, Transaction, TransactionViolation, ViolationName} from '@src/types/onyx'; /** * Names of Fields where violations can occur. @@ -48,7 +50,12 @@ const violationFields: Record = { type ViolationsMap = Map; -function useViolations(violations: TransactionViolation[]) { +function useViolations( + violations: TransactionViolation[], + transaction: OnyxEntry = {} as Transaction, + policy: OnyxEntry = {} as Policy, + policyTagList: OnyxEntry = {}, +) { const violationsByField = useMemo((): ViolationsMap => { const filteredViolations = violations.filter((violation) => violation.type === CONST.VIOLATION_TYPES.VIOLATION); const violationGroups = new Map(); @@ -63,10 +70,11 @@ function useViolations(violations: TransactionViolation[]) { const getViolationsForField = useCallback( (field: ViolationField, data?: TransactionViolation['data']) => { const currentViolations = violationsByField.get(field) ?? []; + const firstViolation = currentViolations[0]; // someTagLevelsRequired has special logic becase data.errorIndexes is a bit unique in how it denotes the tag list that has the violation // tagListIndex can be 0 so we compare with undefined - if (currentViolations[0]?.name === 'someTagLevelsRequired' && data?.tagListIndex !== undefined && Array.isArray(currentViolations[0]?.data?.errorIndexes)) { + if (firstViolation?.name === 'someTagLevelsRequired' && data?.tagListIndex !== undefined && Array.isArray(firstViolation?.data?.errorIndexes)) { return currentViolations .filter((violation) => violation.data?.errorIndexes?.includes(data?.tagListIndex ?? -1)) .map((violation) => ({ @@ -78,14 +86,37 @@ function useViolations(violations: TransactionViolation[]) { })); } + // missingTag has special logic because if its data is null, we need to convert it to someTagLevelsRequired + if (firstViolation?.name === 'missingTag' && firstViolation?.data === null) { + const newViolations = + Object.keys(policyTagList ?? {}).length === 1 + ? ViolationsUtils.getTagViolationsForSingleLevelTags(transaction ?? ({} as Transaction), violations, !!policy?.requiresTag, policyTagList ?? {}) + : ViolationsUtils.getTagViolationsForMultiLevelTags(transaction ?? ({} as Transaction), violations, !!policy?.requiresTag, policyTagList ?? {}); + const newViolation = newViolations.find( + (currentViolation) => currentViolation?.name === 'someTagLevelsRequired' && data?.tagListIndex !== undefined && Array.isArray(currentViolation?.data?.errorIndexes), + ); + if (newViolation) { + return newViolations + .filter((currentViolation) => currentViolation.data?.errorIndexes?.includes(data?.tagListIndex ?? -1)) + .map((currentViolation) => ({ + ...currentViolation, + data: { + ...currentViolation.data, + tagName: data?.tagListName, + }, + })); + } + return test; + } + // tagOutOfPolicy has special logic because we have to account for multi-level tags and use tagName to find the right tag to put the violation on - if (currentViolations[0]?.name === 'tagOutOfPolicy' && data?.tagListName !== undefined && currentViolations[0]?.data?.tagName) { + if (firstViolation?.name === 'tagOutOfPolicy' && data?.tagListName !== undefined && firstViolation?.data?.tagName) { return currentViolations.filter((violation) => violation.data?.tagName === data?.tagListName); } return currentViolations; }, - [violationsByField], + [policy?.requiresTag, policyTagList, transaction, violations, violationsByField], ); return { diff --git a/src/libs/Violations/ViolationsUtils.ts b/src/libs/Violations/ViolationsUtils.ts index 42f58be1d699..3565a3d28b9a 100644 --- a/src/libs/Violations/ViolationsUtils.ts +++ b/src/libs/Violations/ViolationsUtils.ts @@ -110,6 +110,8 @@ function getTagViolationsForMultiLevelTags( } const ViolationsUtils = { + getTagViolationsForSingleLevelTags, + getTagViolationsForMultiLevelTags, /** * Checks a transaction for policy violations and returns an object with Onyx method, key and updated transaction * violations. From b9d8b24bfa1dd3346fe92f82d3e4542b50d98b17 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Tue, 23 Apr 2024 03:39:22 +0100 Subject: [PATCH 003/230] refactor: remove unexpected return --- src/hooks/useViolations.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/hooks/useViolations.ts b/src/hooks/useViolations.ts index 1d25a5e6f3d2..cf02545e20b0 100644 --- a/src/hooks/useViolations.ts +++ b/src/hooks/useViolations.ts @@ -106,7 +106,6 @@ function useViolations( }, })); } - return test; } // tagOutOfPolicy has special logic because we have to account for multi-level tags and use tagName to find the right tag to put the violation on From 0a0cac1e1baa810315b5686c0cfb6332e0c9573e Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Wed, 24 Apr 2024 22:04:08 +0100 Subject: [PATCH 004/230] chore: apply PR suggestions --- .../ReportActionItem/MoneyRequestView.tsx | 9 +++-- src/hooks/useViolations.ts | 36 ++++--------------- src/libs/PolicyUtils.ts | 8 +++++ src/libs/Violations/ViolationsUtils.ts | 17 +++++++-- src/libs/actions/IOU.ts | 14 ++++++-- src/types/onyx/PolicyTag.ts | 6 ++++ src/types/onyx/TransactionViolation.ts | 1 + 7 files changed, 54 insertions(+), 37 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index b9bf70f22a9d..2cb1d1d6ae84 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -174,7 +174,7 @@ function MoneyRequestView({ // A flag for showing tax rate const shouldShowTax = isTaxTrackingEnabled(isPolicyExpenseChat, policy); - const {getViolationsForField} = useViolations(transactionViolations ?? [], transaction, policy, policyTagList); + const {getViolationsForField} = useViolations(transactionViolations ?? []); const hasViolations = useCallback( (field: ViolationField, data?: OnyxTypes.TransactionViolation['data']): boolean => !!canUseViolations && getViolationsForField(field, data).length > 0, [canUseViolations, getViolationsForField], @@ -467,11 +467,16 @@ function MoneyRequestView({ getErrorForField('tag', { tagListIndex: index, tagListName: name, + policyHasDependentTagLists: PolicyUtils.hasDependentTags(policy, policyTagList), }) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined } - error={getErrorForField('tag', {tagListIndex: index, tagListName: name})} + error={getErrorForField('tag', { + tagListIndex: index, + tagListName: name, + policyHasDependentTagLists: PolicyUtils.hasDependentTags(policy, policyTagList), + })} /> ))} diff --git a/src/hooks/useViolations.ts b/src/hooks/useViolations.ts index cf02545e20b0..eb6138799973 100644 --- a/src/hooks/useViolations.ts +++ b/src/hooks/useViolations.ts @@ -1,8 +1,6 @@ import {useCallback, useMemo} from 'react'; -import type {OnyxEntry} from 'react-native-onyx'; -import ViolationsUtils from '@libs/Violations/ViolationsUtils'; import CONST from '@src/CONST'; -import type {Policy, PolicyTagList, Transaction, TransactionViolation, ViolationName} from '@src/types/onyx'; +import type {TransactionViolation, ViolationName} from '@src/types/onyx'; /** * Names of Fields where violations can occur. @@ -50,12 +48,7 @@ const violationFields: Record = { type ViolationsMap = Map; -function useViolations( - violations: TransactionViolation[], - transaction: OnyxEntry = {} as Transaction, - policy: OnyxEntry = {} as Policy, - policyTagList: OnyxEntry = {}, -) { +function useViolations(violations: TransactionViolation[]) { const violationsByField = useMemo((): ViolationsMap => { const filteredViolations = violations.filter((violation) => violation.type === CONST.VIOLATION_TYPES.VIOLATION); const violationGroups = new Map(); @@ -86,26 +79,9 @@ function useViolations( })); } - // missingTag has special logic because if its data is null, we need to convert it to someTagLevelsRequired - if (firstViolation?.name === 'missingTag' && firstViolation?.data === null) { - const newViolations = - Object.keys(policyTagList ?? {}).length === 1 - ? ViolationsUtils.getTagViolationsForSingleLevelTags(transaction ?? ({} as Transaction), violations, !!policy?.requiresTag, policyTagList ?? {}) - : ViolationsUtils.getTagViolationsForMultiLevelTags(transaction ?? ({} as Transaction), violations, !!policy?.requiresTag, policyTagList ?? {}); - const newViolation = newViolations.find( - (currentViolation) => currentViolation?.name === 'someTagLevelsRequired' && data?.tagListIndex !== undefined && Array.isArray(currentViolation?.data?.errorIndexes), - ); - if (newViolation) { - return newViolations - .filter((currentViolation) => currentViolation.data?.errorIndexes?.includes(data?.tagListIndex ?? -1)) - .map((currentViolation) => ({ - ...currentViolation, - data: { - ...currentViolation.data, - tagName: data?.tagListName, - }, - })); - } + // missingTag has special logic because we have to take into account dependent tags + if (firstViolation?.data?.policyHasDependentTagLists && firstViolation?.name === 'missingTag' && firstViolation?.data?.tagName === data?.tagListName) { + return currentViolations.filter((violation) => violation.data?.tagName === data?.tagListName); } // tagOutOfPolicy has special logic because we have to account for multi-level tags and use tagName to find the right tag to put the violation on @@ -115,7 +91,7 @@ function useViolations( return currentViolations; }, - [policy?.requiresTag, policyTagList, transaction, violations, violationsByField], + [violationsByField], ); return { diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 731dc5700c8e..3b60e0cba262 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -378,6 +378,13 @@ function getPolicy(policyID: string | undefined): Policy | EmptyObject { return allPolicies[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`] ?? {}; } +function hasDependentTags(policy: OnyxEntry, policyTagList: OnyxEntry) { + return ( + !!policy?.hasMultipleTagLists && + Object.values(policyTagList ?? {}).some((tagList) => Object.values(tagList.tags).some((tag) => !!tag.rules?.parentTagsFilter || !!tag.parentTagsFilter)) + ); +} + export { getActivePolicies, hasAccountingConnections, @@ -421,6 +428,7 @@ export { getSubmitToAccountID, getAdminEmployees, getPolicy, + hasDependentTags, }; export type {MemberEmailsToAccountIDs}; diff --git a/src/libs/Violations/ViolationsUtils.ts b/src/libs/Violations/ViolationsUtils.ts index 3565a3d28b9a..ffa346754d8a 100644 --- a/src/libs/Violations/ViolationsUtils.ts +++ b/src/libs/Violations/ViolationsUtils.ts @@ -56,6 +56,7 @@ function getTagViolationsForMultiLevelTags( transactionViolations: TransactionViolation[], policyRequiresTags: boolean, policyTagList: PolicyTagList, + hasDependentTags: boolean, ): TransactionViolation[] { const policyTagKeys = getSortedTagKeys(policyTagList); const selectedTags = updatedTransaction.tag?.split(CONST.COLON) ?? []; @@ -64,6 +65,17 @@ function getTagViolationsForMultiLevelTags( (violation) => violation.name !== CONST.VIOLATIONS.SOME_TAG_LEVELS_REQUIRED && violation.name !== CONST.VIOLATIONS.TAG_OUT_OF_POLICY, ); + if (hasDependentTags && !updatedTransaction.tag) { + Object.values(policyTagList).forEach((tagList) => { + newTransactionViolations.push({ + name: CONST.VIOLATIONS.MISSING_TAG, + type: CONST.VIOLATION_TYPES.VIOLATION, + data: {tagName: tagList.name}, + }); + }); + return newTransactionViolations; + } + // We first get the errorIndexes for someTagLevelsRequired. If it's not empty, we puth SOME_TAG_LEVELS_REQUIRED in Onyx. // Otherwise, we put TAG_OUT_OF_POLICY in Onyx (when applicable) const errorIndexes = []; @@ -110,8 +122,6 @@ function getTagViolationsForMultiLevelTags( } const ViolationsUtils = { - getTagViolationsForSingleLevelTags, - getTagViolationsForMultiLevelTags, /** * Checks a transaction for policy violations and returns an object with Onyx method, key and updated transaction * violations. @@ -123,6 +133,7 @@ const ViolationsUtils = { policyTagList: PolicyTagList, policyRequiresCategories: boolean, policyCategories: PolicyCategories, + hasDependentTags: boolean, ): OnyxUpdate { const isPartialTransaction = TransactionUtils.isPartialMerchant(TransactionUtils.getMerchant(updatedTransaction)) && TransactionUtils.isAmountMissing(updatedTransaction); if (isPartialTransaction) { @@ -168,7 +179,7 @@ const ViolationsUtils = { newTransactionViolations = Object.keys(policyTagList).length === 1 ? getTagViolationsForSingleLevelTags(updatedTransaction, newTransactionViolations, policyRequiresTags, policyTagList) - : getTagViolationsForMultiLevelTags(updatedTransaction, newTransactionViolations, policyRequiresTags, policyTagList); + : getTagViolationsForMultiLevelTags(updatedTransaction, newTransactionViolations, policyRequiresTags, policyTagList, hasDependentTags); } return { diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 30ab2688b593..64eb7ba3a645 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -765,7 +765,7 @@ function buildOnyxDataForMoneyRequest( return [optimisticData, successData, failureData]; } - const violationsOnyxData = ViolationsUtils.getViolationsOnyxData(transaction, [], !!policy.requiresTag, policyTagList ?? {}, !!policy.requiresCategory, policyCategories ?? {}); + const violationsOnyxData = ViolationsUtils.getViolationsOnyxData(transaction, [], !!policy.requiresTag, policyTagList ?? {}, !!policy.requiresCategory, policyCategories ?? {}, true); if (violationsOnyxData) { optimisticData.push(violationsOnyxData); @@ -1102,7 +1102,15 @@ function buildOnyxDataForTrackExpense( return [optimisticData, successData, failureData]; } - const violationsOnyxData = ViolationsUtils.getViolationsOnyxData(transaction, [], !!policy.requiresTag, policyTagList ?? {}, !!policy.requiresCategory, policyCategories ?? {}); + const violationsOnyxData = ViolationsUtils.getViolationsOnyxData( + transaction, + [], + !!policy.requiresTag, + policyTagList ?? {}, + !!policy.requiresCategory, + policyCategories ?? {}, + PolicyUtils.hasDependentTags(policy, policyTagList ?? {}), + ); if (violationsOnyxData) { optimisticData.push(violationsOnyxData); @@ -2054,6 +2062,7 @@ function getUpdateMoneyRequestParams( policyTagList ?? {}, !!policy.requiresCategory, policyCategories ?? {}, + PolicyUtils.hasDependentTags(policy, policyTagList ?? {}), ), ); failureData.push({ @@ -4375,6 +4384,7 @@ function editRegularMoneyRequest( policyTags, !!policy.requiresCategory, policyCategories, + PolicyUtils.hasDependentTags(policy, policyTags), ); optimisticData.push(updatedViolationsOnyxData); failureData.push({ diff --git a/src/types/onyx/PolicyTag.ts b/src/types/onyx/PolicyTag.ts index 37e979fb58f6..88b5297dbd38 100644 --- a/src/types/onyx/PolicyTag.ts +++ b/src/types/onyx/PolicyTag.ts @@ -13,6 +13,12 @@ type PolicyTag = OnyxCommon.OnyxValueWithOfflineFeedback<{ /** A list of errors keyed by microtime */ errors?: OnyxCommon.Errors | null; + + rules?: { + parentTagsFilter?: string; + }; + + parentTagsFilter?: string; }>; type PolicyTags = Record; diff --git a/src/types/onyx/TransactionViolation.ts b/src/types/onyx/TransactionViolation.ts index 28de4582bd5e..6859c6749567 100644 --- a/src/types/onyx/TransactionViolation.ts +++ b/src/types/onyx/TransactionViolation.ts @@ -10,6 +10,7 @@ type TransactionViolation = { type: string; name: ViolationName; data?: { + policyHasDependentTagLists?: boolean; rejectedBy?: string; rejectReason?: string; formattedLimit?: string; From a174c60a79bd5e8cd92eb6afd8913e76f60dd4ee Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Thu, 25 Apr 2024 22:18:10 +0100 Subject: [PATCH 005/230] fix: tag name missing in error UI --- src/hooks/useViolations.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/hooks/useViolations.ts b/src/hooks/useViolations.ts index eb6138799973..65e17785500d 100644 --- a/src/hooks/useViolations.ts +++ b/src/hooks/useViolations.ts @@ -80,8 +80,16 @@ function useViolations(violations: TransactionViolation[]) { } // missingTag has special logic because we have to take into account dependent tags - if (firstViolation?.data?.policyHasDependentTagLists && firstViolation?.name === 'missingTag' && firstViolation?.data?.tagName === data?.tagListName) { - return currentViolations.filter((violation) => violation.data?.tagName === data?.tagListName); + if (data?.policyHasDependentTagLists && firstViolation?.name === 'missingTag' && data?.tagListName) { + return [ + { + ...firstViolation, + data: { + ...firstViolation.data, + tagName: data?.tagListName, + }, + }, + ]; } // tagOutOfPolicy has special logic because we have to account for multi-level tags and use tagName to find the right tag to put the violation on From e343aa68acc529461e7612572b753169ee736c0e Mon Sep 17 00:00:00 2001 From: burczu Date: Mon, 22 Apr 2024 11:24:12 +0200 Subject: [PATCH 006/230] new persona added --- src/CONST.ts | 1 + src/libs/actions/Report.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/CONST.ts b/src/CONST.ts index 2e14aa7cf21f..0725fb47a85b 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1202,6 +1202,7 @@ const CONST = { CHRONOS: 'chronos@expensify.com', CONCIERGE: 'concierge@expensify.com', CONTRIBUTORS: 'contributors@expensify.com', + EXPENSIFY_PERSONA: 'expensify@expensify.com', FIRST_RESPONDER: 'firstresponders@expensify.com', GUIDES_DOMAIN: 'team.expensify.com', HELP: 'help@expensify.com', diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 7542ca12c592..8260b6194b21 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -3029,7 +3029,7 @@ function completeOnboarding( }, adminsChatReportID?: string, ) { - const targetEmail = CONST.EMAIL.CONCIERGE; + const targetEmail = CONST.EMAIL.EXPENSIFY_PERSONA; const actorAccountID = PersonalDetailsUtils.getAccountIDsByLogins([targetEmail])[0]; const targetChatReport = ReportUtils.getChatByParticipants([actorAccountID]); const {reportID: targetChatReportID = '', policyID: targetChatPolicyID = ''} = targetChatReport ?? {}; From 9a58f4fc89739c5a05df7bf8e56d0b1b51904b5d Mon Sep 17 00:00:00 2001 From: burczu Date: Mon, 22 Apr 2024 12:49:50 +0200 Subject: [PATCH 007/230] using expensify persona only for odd account ids --- src/libs/actions/Report.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 8260b6194b21..4afdeb5201a4 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -3029,7 +3029,12 @@ function completeOnboarding( }, adminsChatReportID?: string, ) { - const targetEmail = CONST.EMAIL.EXPENSIFY_PERSONA; + let targetEmail: string = CONST.EMAIL.CONCIERGE; + if (currentUserAccountID % 2 === 1) { + // for odd accountID, we will use the expensify persona instead of concierge + targetEmail = CONST.EMAIL.EXPENSIFY_PERSONA; + } + const actorAccountID = PersonalDetailsUtils.getAccountIDsByLogins([targetEmail])[0]; const targetChatReport = ReportUtils.getChatByParticipants([actorAccountID]); const {reportID: targetChatReportID = '', policyID: targetChatPolicyID = ''} = targetChatReport ?? {}; From 6da74e4ca18a8963ada6cf7083d7cbb84871ca32 Mon Sep 17 00:00:00 2001 From: burczu Date: Fri, 26 Apr 2024 10:56:25 +0200 Subject: [PATCH 008/230] handling new system report type --- src/CONST.ts | 2 +- src/libs/ReportUtils.ts | 21 +++++++++++++++++++-- src/libs/SidebarUtils.ts | 6 ++++++ src/libs/actions/Report.ts | 10 ++++------ 4 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 0725fb47a85b..1aaea219d3bd 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -53,6 +53,7 @@ const chatTypes = { POLICY_ROOM: 'policyRoom', POLICY_EXPENSE_CHAT: 'policyExpenseChat', SELF_DM: 'selfDM', + SYSTEM: 'system', } as const; // Explicit type annotation is required @@ -1202,7 +1203,6 @@ const CONST = { CHRONOS: 'chronos@expensify.com', CONCIERGE: 'concierge@expensify.com', CONTRIBUTORS: 'contributors@expensify.com', - EXPENSIFY_PERSONA: 'expensify@expensify.com', FIRST_RESPONDER: 'firstresponders@expensify.com', GUIDES_DOMAIN: 'team.expensify.com', HELP: 'help@expensify.com', diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 121518130cb4..1aac5cd716c5 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -978,6 +978,10 @@ function isGroupChat(report: OnyxEntry | Partial): boolean { return getChatType(report) === CONST.REPORT.CHAT_TYPE.GROUP; } +function isSystemChat(report: OnyxEntry | Partial): boolean { + return getChatType(report) === CONST.REPORT.CHAT_TYPE.SYSTEM; +} + /** * Only returns true if this is our main 1:1 DM report with Concierge */ @@ -4791,7 +4795,6 @@ function shouldReportBeInOptionList({ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing report?.isHidden || // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - report?.participantAccountIDs?.includes(CONST.ACCOUNT_ID.NOTIFICATIONS) || (report?.participantAccountIDs?.length === 0 && !isChatThread(report) && !isPublicRoom(report) && @@ -4800,7 +4803,8 @@ function shouldReportBeInOptionList({ !isMoneyRequestReport(report) && !isTaskReport(report) && !isSelfDM(report) && - !isGroupChat(report)) + !isGroupChat(report) && + !isSystemChat(report)) ) { return false; } @@ -4877,6 +4881,18 @@ function shouldReportBeInOptionList({ return true; } +/** + * Returns the system report from the list of reports. + * TODO: this method may not be necessary if the participants list of the system report is filled correctly + */ +function getSystemChat(): OnyxEntry { + if (!allReports) { + return null; + } + + return Object.values(allReports ?? {}).find((report) => report?.chatType === CONST.REPORT.CHAT_TYPE.SYSTEM) ?? null; +} + /** * Attempts to find a report in onyx with the provided list of participants. Does not include threads, task, expense, room, and policy expense chat. */ @@ -6306,6 +6322,7 @@ export { getRoomWelcomeMessage, getRootParentReport, getRouteFromLink, + getSystemChat, getTaskAssigneeChatOnyxData, getTransactionDetails, getTransactionReportName, diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index d230f58e46f9..2c52af317be5 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -20,6 +20,7 @@ import * as OptionsListUtils from './OptionsListUtils'; import * as ReportActionsUtils from './ReportActionsUtils'; import * as ReportUtils from './ReportUtils'; import * as TaskUtils from './TaskUtils'; +import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; const visibleReportActionItems: ReportActions = {}; Onyx.connect({ @@ -245,6 +246,11 @@ function getOptionData({ participantAccountIDs = [report.ownerAccountID ?? 0]; } + // TODO: this is added for the testing purposes only - should be removed once participants list of the system report is filled + if (report.chatType === CONST.REPORT.CHAT_TYPE.SYSTEM) { + participantAccountIDs = [report.ownerAccountID ?? 0, ...PersonalDetailsUtils.getAccountIDsByLogins([CONST.EMAIL.NOTIFICATIONS])]; + } + const participantPersonalDetailList = Object.values(OptionsListUtils.getPersonalDetailsForAccountIDs(participantAccountIDs, personalDetails)) as PersonalDetails[]; const personalDetail = participantPersonalDetailList[0] ?? {}; const hasErrors = Object.keys(result.allReportErrors ?? {}).length !== 0; diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 4afdeb5201a4..4d5e99966323 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -3029,14 +3029,12 @@ function completeOnboarding( }, adminsChatReportID?: string, ) { - let targetEmail: string = CONST.EMAIL.CONCIERGE; - if (currentUserAccountID % 2 === 1) { - // for odd accountID, we will use the expensify persona instead of concierge - targetEmail = CONST.EMAIL.EXPENSIFY_PERSONA; - } + const isAccountIDOdd = currentUserAccountID % 2 === 1; + const targetEmail = isAccountIDOdd ? CONST.EMAIL.NOTIFICATIONS : CONST.EMAIL.CONCIERGE; const actorAccountID = PersonalDetailsUtils.getAccountIDsByLogins([targetEmail])[0]; - const targetChatReport = ReportUtils.getChatByParticipants([actorAccountID]); + // TODO: using getSystemChat is rather not necessary if we could have participants list filled correctly + const targetChatReport= isAccountIDOdd ? ReportUtils.getSystemChat() : ReportUtils.getChatByParticipants([actorAccountID]); const {reportID: targetChatReportID = '', policyID: targetChatPolicyID = ''} = targetChatReport ?? {}; // Mention message From aaa3cd4c9bf2b136b430a61f9655308df57e88cc Mon Sep 17 00:00:00 2001 From: burczu Date: Fri, 26 Apr 2024 11:35:07 +0200 Subject: [PATCH 009/230] navigating to system chat at the end of the onboarding process added --- src/libs/AccountUtils.ts | 4 +++- src/libs/SidebarUtils.ts | 2 +- src/libs/actions/Report.ts | 18 ++++++++++++++++-- .../BaseOnboardingPersonalDetails.tsx | 7 ++++++- .../OnboardingWork/BaseOnboardingWork.tsx | 7 ++++++- 5 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/libs/AccountUtils.ts b/src/libs/AccountUtils.ts index d903584e15b4..8bc7037e9682 100644 --- a/src/libs/AccountUtils.ts +++ b/src/libs/AccountUtils.ts @@ -5,4 +5,6 @@ import type {Account} from '@src/types/onyx'; const isValidateCodeFormSubmitting = (account: OnyxEntry) => !!account?.isLoading && account.loadingForm === (account.requiresTwoFactorAuth ? CONST.FORMS.VALIDATE_TFA_CODE_FORM : CONST.FORMS.VALIDATE_CODE_FORM); -export default {isValidateCodeFormSubmitting}; +const isAccountIDOddNumber = (accountID: number) => accountID % 2 === 1; + +export default {isValidateCodeFormSubmitting, isAccountIDOddNumber}; diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 2c52af317be5..50d9cea26efe 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -17,10 +17,10 @@ import localeCompare from './LocaleCompare'; import * as LocalePhoneNumber from './LocalePhoneNumber'; import * as Localize from './Localize'; import * as OptionsListUtils from './OptionsListUtils'; +import * as PersonalDetailsUtils from './PersonalDetailsUtils'; import * as ReportActionsUtils from './ReportActionsUtils'; import * as ReportUtils from './ReportUtils'; import * as TaskUtils from './TaskUtils'; -import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; const visibleReportActionItems: ReportActions = {}; Onyx.connect({ diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 4d5e99966323..714cf2182db9 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -8,6 +8,7 @@ import Onyx from 'react-native-onyx'; import type {PartialDeep, ValueOf} from 'type-fest'; import type {Emoji} from '@assets/emojis/types'; import type {FileObject} from '@components/AttachmentModal'; +import AccountUtils from '@libs/AccountUtils'; import * as ActiveClientManager from '@libs/ActiveClientManager'; import * as API from '@libs/API'; import type { @@ -1930,6 +1931,18 @@ function navigateToConciergeChat(shouldDismissModal = false, checkIfCurrentPageA } } +/** + * Navigates to the 1:1 system chat + */ +function navigateToSystemChat() { + // TODO: when system report participants list is filled, we could just use `ReportUtils.getChatByParticipants()` method insted `getSystemChat()` + const systemChatReport = ReportUtils.getSystemChat(); + + if (systemChatReport && systemChatReport.reportID) { + Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(systemChatReport.reportID)); + } +} + /** Add a policy report (workspace room) optimistically and navigate to it. */ function addPolicyReport(policyReport: ReportUtils.OptimisticChatReport) { const createdReportAction = ReportUtils.buildOptimisticCreatedReportAction(CONST.POLICY.OWNER_EMAIL_FAKE); @@ -3029,12 +3042,12 @@ function completeOnboarding( }, adminsChatReportID?: string, ) { - const isAccountIDOdd = currentUserAccountID % 2 === 1; + const isAccountIDOdd = AccountUtils.isAccountIDOddNumber(currentUserAccountID ?? 0); const targetEmail = isAccountIDOdd ? CONST.EMAIL.NOTIFICATIONS : CONST.EMAIL.CONCIERGE; const actorAccountID = PersonalDetailsUtils.getAccountIDsByLogins([targetEmail])[0]; // TODO: using getSystemChat is rather not necessary if we could have participants list filled correctly - const targetChatReport= isAccountIDOdd ? ReportUtils.getSystemChat() : ReportUtils.getChatByParticipants([actorAccountID]); + const targetChatReport = isAccountIDOdd ? ReportUtils.getSystemChat() : ReportUtils.getChatByParticipants([actorAccountID]); const {reportID: targetChatReportID = '', policyID: targetChatPolicyID = ''} = targetChatReport ?? {}; // Mention message @@ -3736,6 +3749,7 @@ export { saveReportActionDraftNumberOfLines, deleteReportComment, navigateToConciergeChat, + navigateToSystemChat, addPolicyReport, deleteReport, navigateToConciergeChatAndDeleteReport, diff --git a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx index 7049b04cc293..0082ede92c4f 100644 --- a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx +++ b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx @@ -7,6 +7,7 @@ import type {FormOnyxValues} from '@components/Form/types'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import KeyboardAvoidingView from '@components/KeyboardAvoidingView'; import OfflineIndicator from '@components/OfflineIndicator'; +import {useSession} from '@components/OnyxProvider'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; @@ -16,6 +17,7 @@ import useLocalize from '@hooks/useLocalize'; import useOnboardingLayout from '@hooks/useOnboardingLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; +import AccountUtils from '@libs/AccountUtils'; import * as ErrorUtils from '@libs/ErrorUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as ValidationUtils from '@libs/ValidationUtils'; @@ -37,6 +39,7 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat const {shouldUseNarrowLayout} = useOnboardingLayout(); const {inputCallbackRef} = useAutoFocusInput(); const [shouldValidateOnChange, setShouldValidateOnChange] = useState(false); + const {accountID} = useSession(); useDisableModalDismissOnEscape(); @@ -69,6 +72,8 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat // Otherwise stay on the chats screen. if (isSmallScreenWidth) { Navigation.navigate(ROUTES.HOME); + } else if (AccountUtils.isAccountIDOddNumber(accountID ?? 0)) { + Report.navigateToSystemChat(); } else { Report.navigateToConciergeChat(); } @@ -79,7 +84,7 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat Navigation.navigate(ROUTES.WELCOME_VIDEO_ROOT); }, variables.welcomeVideoDelay); }, - [currentUserPersonalDetails.login, isSmallScreenWidth, onboardingPurposeSelected], + [currentUserPersonalDetails.login, isSmallScreenWidth, onboardingPurposeSelected, accountID], ); const validate = (values: FormOnyxValues<'onboardingPersonalDetailsForm'>) => { diff --git a/src/pages/OnboardingWork/BaseOnboardingWork.tsx b/src/pages/OnboardingWork/BaseOnboardingWork.tsx index 151c1bb35ea2..d2e583d778c2 100644 --- a/src/pages/OnboardingWork/BaseOnboardingWork.tsx +++ b/src/pages/OnboardingWork/BaseOnboardingWork.tsx @@ -7,6 +7,7 @@ import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import KeyboardAvoidingView from '@components/KeyboardAvoidingView'; import OfflineIndicator from '@components/OfflineIndicator'; +import {useSession} from '@components/OnyxProvider'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; @@ -15,6 +16,7 @@ import useLocalize from '@hooks/useLocalize'; import useOnboardingLayout from '@hooks/useOnboardingLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; +import AccountUtils from '@libs/AccountUtils'; import * as ErrorUtils from '@libs/ErrorUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as ValidationUtils from '@libs/ValidationUtils'; @@ -32,6 +34,7 @@ function BaseOnboardingWork({currentUserPersonalDetails, shouldUseNativeStyles, const {translate} = useLocalize(); const {isSmallScreenWidth} = useWindowDimensions(); const {shouldUseNarrowLayout} = useOnboardingLayout(); + const {accountID} = useSession(); useDisableModalDismissOnEscape(); @@ -62,6 +65,8 @@ function BaseOnboardingWork({currentUserPersonalDetails, shouldUseNativeStyles, // Otherwise stay on the chats screen. if (isSmallScreenWidth) { Navigation.navigate(ROUTES.HOME); + } else if (AccountUtils.isAccountIDOddNumber(accountID ?? 0)) { + Report.navigateToSystemChat(); } else { Report.navigateToConciergeChat(); } @@ -72,7 +77,7 @@ function BaseOnboardingWork({currentUserPersonalDetails, shouldUseNativeStyles, Navigation.navigate(ROUTES.WELCOME_VIDEO_ROOT); }, variables.welcomeVideoDelay); }, - [currentUserPersonalDetails.firstName, currentUserPersonalDetails.lastName, currentUserPersonalDetails.login, isSmallScreenWidth, onboardingPurposeSelected], + [currentUserPersonalDetails.firstName, currentUserPersonalDetails.lastName, currentUserPersonalDetails.login, isSmallScreenWidth, onboardingPurposeSelected, accountID], ); const validate = (values: FormOnyxValues<'onboardingWorkForm'>) => { From 282490a17937d7928230d573dc2af8834e5fcd80 Mon Sep 17 00:00:00 2001 From: burczu Date: Fri, 26 Apr 2024 11:41:47 +0200 Subject: [PATCH 010/230] addional todo comment added --- src/libs/ReportUtils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 1aac5cd716c5..e58dd3e54b12 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -4804,6 +4804,7 @@ function shouldReportBeInOptionList({ !isTaskReport(report) && !isSelfDM(report) && !isGroupChat(report) && + // TODO: this shouldn't be necessary if the system report has participants list filled !isSystemChat(report)) ) { return false; From 72f85755fab249ec89a8e8b674094baec90d261e Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Mon, 29 Apr 2024 21:41:03 +0100 Subject: [PATCH 011/230] refactor: apply pull request suggestions --- .../ReportActionItem/MoneyRequestView.tsx | 4 +- src/hooks/useViolations.ts | 25 +++---- src/libs/PolicyUtils.ts | 8 +-- src/libs/Violations/ViolationsUtils.ts | 71 ++++++++++++------- src/libs/actions/IOU.ts | 10 ++- src/types/onyx/PolicyTag.ts | 4 ++ src/types/onyx/TransactionViolation.ts | 2 +- 7 files changed, 80 insertions(+), 44 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 2cb1d1d6ae84..fa3707605044 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -467,7 +467,7 @@ function MoneyRequestView({ getErrorForField('tag', { tagListIndex: index, tagListName: name, - policyHasDependentTagLists: PolicyUtils.hasDependentTags(policy, policyTagList), + policyHasDependentTags: PolicyUtils.hasDependentTags(policy, policyTagList), }) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined @@ -475,7 +475,7 @@ function MoneyRequestView({ error={getErrorForField('tag', { tagListIndex: index, tagListName: name, - policyHasDependentTagLists: PolicyUtils.hasDependentTags(policy, policyTagList), + policyHasDependentTags: PolicyUtils.hasDependentTags(policy, policyTagList), })} /> diff --git a/src/hooks/useViolations.ts b/src/hooks/useViolations.ts index 65e17785500d..578db947ed57 100644 --- a/src/hooks/useViolations.ts +++ b/src/hooks/useViolations.ts @@ -63,29 +63,30 @@ function useViolations(violations: TransactionViolation[]) { const getViolationsForField = useCallback( (field: ViolationField, data?: TransactionViolation['data']) => { const currentViolations = violationsByField.get(field) ?? []; - const firstViolation = currentViolations[0]; + const violation = currentViolations[0]; // someTagLevelsRequired has special logic becase data.errorIndexes is a bit unique in how it denotes the tag list that has the violation // tagListIndex can be 0 so we compare with undefined - if (firstViolation?.name === 'someTagLevelsRequired' && data?.tagListIndex !== undefined && Array.isArray(firstViolation?.data?.errorIndexes)) { + if (violation?.name === 'someTagLevelsRequired' && data?.tagListIndex !== undefined && Array.isArray(violation?.data?.errorIndexes)) { return currentViolations - .filter((violation) => violation.data?.errorIndexes?.includes(data?.tagListIndex ?? -1)) - .map((violation) => ({ - ...violation, + .filter((currentViolation) => currentViolation.data?.errorIndexes?.includes(data?.tagListIndex ?? -1)) + .map((currentViolation) => ({ + ...currentViolation, data: { - ...violation.data, + ...currentViolation.data, tagName: data?.tagListName, }, })); } - // missingTag has special logic because we have to take into account dependent tags - if (data?.policyHasDependentTagLists && firstViolation?.name === 'missingTag' && data?.tagListName) { + // missingTag has special logic for policies with dependent tags, because only violation is returned for all tags + // when no tags are present, so the tag name isn't set in the violation data. That's why we add it here + if (data?.policyHasDependentTags && violation?.name === 'missingTag' && data?.tagListName) { return [ { - ...firstViolation, + ...violation, data: { - ...firstViolation.data, + ...violation.data, tagName: data?.tagListName, }, }, @@ -93,8 +94,8 @@ function useViolations(violations: TransactionViolation[]) { } // tagOutOfPolicy has special logic because we have to account for multi-level tags and use tagName to find the right tag to put the violation on - if (firstViolation?.name === 'tagOutOfPolicy' && data?.tagListName !== undefined && firstViolation?.data?.tagName) { - return currentViolations.filter((violation) => violation.data?.tagName === data?.tagListName); + if (violation?.name === 'tagOutOfPolicy' && data?.tagListName !== undefined && violation?.data?.tagName) { + return currentViolations.filter((currentViolation) => currentViolation.data?.tagName === data?.tagListName); } return currentViolations; diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 657a3387ce7f..374ffa2436a0 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -391,10 +391,10 @@ function canSendInvoice(policies: OnyxCollection): boolean { } function hasDependentTags(policy: OnyxEntry, policyTagList: OnyxEntry) { - return ( - !!policy?.hasMultipleTagLists && - Object.values(policyTagList ?? {}).some((tagList) => Object.values(tagList.tags).some((tag) => !!tag.rules?.parentTagsFilter || !!tag.parentTagsFilter)) - ); + if (!policy?.hasMultipleTagLists) { + return false; + } + return Object.values(policyTagList ?? {}).some((tagList) => Object.values(tagList.tags).some((tag) => !!tag.rules?.parentTagsFilter || !!tag.parentTagsFilter)); } export { diff --git a/src/libs/Violations/ViolationsUtils.ts b/src/libs/Violations/ViolationsUtils.ts index ffa346754d8a..0ef102318d78 100644 --- a/src/libs/Violations/ViolationsUtils.ts +++ b/src/libs/Violations/ViolationsUtils.ts @@ -49,36 +49,31 @@ function getTagViolationsForSingleLevelTags( } /** - * Calculates some tag levels required and missing tag violations for the given transaction + * Calculates missing tag violations for policies with dependent tags, + * by returning one per tag with its corresponding tagName in the data */ -function getTagViolationsForMultiLevelTags( - updatedTransaction: Transaction, - transactionViolations: TransactionViolation[], - policyRequiresTags: boolean, - policyTagList: PolicyTagList, - hasDependentTags: boolean, -): TransactionViolation[] { - const policyTagKeys = getSortedTagKeys(policyTagList); - const selectedTags = updatedTransaction.tag?.split(CONST.COLON) ?? []; - let newTransactionViolations = [...transactionViolations]; - newTransactionViolations = newTransactionViolations.filter( - (violation) => violation.name !== CONST.VIOLATIONS.SOME_TAG_LEVELS_REQUIRED && violation.name !== CONST.VIOLATIONS.TAG_OUT_OF_POLICY, - ); +function getTagViolationsForDependentTags(policyTagList: PolicyTagList, transactionViolations: TransactionViolation[]) { + return [ + ...transactionViolations, + ...Object.values(policyTagList).map((tagList) => ({ + name: CONST.VIOLATIONS.MISSING_TAG, + type: CONST.VIOLATION_TYPES.VIOLATION, + data: {tagName: tagList.name}, + })), + ]; +} - if (hasDependentTags && !updatedTransaction.tag) { - Object.values(policyTagList).forEach((tagList) => { - newTransactionViolations.push({ - name: CONST.VIOLATIONS.MISSING_TAG, - type: CONST.VIOLATION_TYPES.VIOLATION, - data: {tagName: tagList.name}, - }); - }); - return newTransactionViolations; - } +/** + * Calculates missing tag violations for policies with independent tags, + * by returning one per tag with its corresponding tagName in the data + */ +function getTagViolationForIndependentTags(policyTagList: PolicyTagList, transactionViolations: TransactionViolation[], policyTagKeys: string[], selectedTags: string[]) { + let newTransactionViolations = [...transactionViolations]; // We first get the errorIndexes for someTagLevelsRequired. If it's not empty, we puth SOME_TAG_LEVELS_REQUIRED in Onyx. // Otherwise, we put TAG_OUT_OF_POLICY in Onyx (when applicable) const errorIndexes = []; + for (let i = 0; i < policyTagKeys.length; i++) { const isTagRequired = policyTagList[policyTagKeys[i]].required ?? true; const isTagSelected = Boolean(selectedTags[i]); @@ -86,6 +81,7 @@ function getTagViolationsForMultiLevelTags( errorIndexes.push(i); } } + if (errorIndexes.length !== 0) { newTransactionViolations.push({ name: CONST.VIOLATIONS.SOME_TAG_LEVELS_REQUIRED, @@ -96,10 +92,12 @@ function getTagViolationsForMultiLevelTags( }); } else { let hasInvalidTag = false; + for (let i = 0; i < policyTagKeys.length; i++) { const selectedTag = selectedTags[i]; const tags = policyTagList[policyTagKeys[i]].tags; const isTagInPolicy = Object.values(tags).some((tag) => tag.name === selectedTag && Boolean(tag.enabled)); + if (!isTagInPolicy) { newTransactionViolations.push({ name: CONST.VIOLATIONS.TAG_OUT_OF_POLICY, @@ -112,15 +110,40 @@ function getTagViolationsForMultiLevelTags( break; } } + if (!hasInvalidTag) { newTransactionViolations = reject(newTransactionViolations, { name: CONST.VIOLATIONS.TAG_OUT_OF_POLICY, }); } } + return newTransactionViolations; } +/** + * Calculates some tag levels required and missing tag violations for the given transaction + */ +function getTagViolationsForMultiLevelTags( + updatedTransaction: Transaction, + transactionViolations: TransactionViolation[], + policyRequiresTags: boolean, + policyTagList: PolicyTagList, + hasDependentTags: boolean, +): TransactionViolation[] { + const policyTagKeys = getSortedTagKeys(policyTagList); + const selectedTags = updatedTransaction.tag?.split(CONST.COLON) ?? []; + const filteredTransactionViolations = transactionViolations.filter( + (violation) => violation.name !== CONST.VIOLATIONS.SOME_TAG_LEVELS_REQUIRED && violation.name !== CONST.VIOLATIONS.TAG_OUT_OF_POLICY, + ); + + if (hasDependentTags && !updatedTransaction.tag) { + return getTagViolationsForDependentTags(policyTagList, filteredTransactionViolations); + } + + return getTagViolationForIndependentTags(policyTagList, filteredTransactionViolations, policyTagKeys, selectedTags); +} + const ViolationsUtils = { /** * Checks a transaction for policy violations and returns an object with Onyx method, key and updated transaction diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 1df688ce3bed..f605663793d2 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -779,7 +779,15 @@ function buildOnyxDataForMoneyRequest( return [optimisticData, successData, failureData]; } - const violationsOnyxData = ViolationsUtils.getViolationsOnyxData(transaction, [], !!policy.requiresTag, policyTagList ?? {}, !!policy.requiresCategory, policyCategories ?? {}, true); + const violationsOnyxData = ViolationsUtils.getViolationsOnyxData( + transaction, + [], + !!policy.requiresTag, + policyTagList ?? {}, + !!policy.requiresCategory, + policyCategories ?? {}, + PolicyUtils.hasDependentTags(policy, policyTagList ?? {}), + ); if (violationsOnyxData) { optimisticData.push(violationsOnyxData); diff --git a/src/types/onyx/PolicyTag.ts b/src/types/onyx/PolicyTag.ts index 88b5297dbd38..9d48b14ff444 100644 --- a/src/types/onyx/PolicyTag.ts +++ b/src/types/onyx/PolicyTag.ts @@ -15,6 +15,10 @@ type PolicyTag = OnyxCommon.OnyxValueWithOfflineFeedback<{ errors?: OnyxCommon.Errors | null; rules?: { + /** + * String representation of regex to match against parent tag. Eg, if San Francisco is a child tag of California + * its parentTagsFilter will be ^California$ + */ parentTagsFilter?: string; }; diff --git a/src/types/onyx/TransactionViolation.ts b/src/types/onyx/TransactionViolation.ts index 6859c6749567..39d0dc394753 100644 --- a/src/types/onyx/TransactionViolation.ts +++ b/src/types/onyx/TransactionViolation.ts @@ -10,7 +10,7 @@ type TransactionViolation = { type: string; name: ViolationName; data?: { - policyHasDependentTagLists?: boolean; + policyHasDependentTags?: boolean; rejectedBy?: string; rejectReason?: string; formattedLimit?: string; From 5c7e16ce6ad6aea0268431926dc965e2408c12a9 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Mon, 29 Apr 2024 22:24:24 +0100 Subject: [PATCH 012/230] chore(typescript): add missing argument --- tests/unit/ViolationUtilsTest.ts | 58 ++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/tests/unit/ViolationUtilsTest.ts b/tests/unit/ViolationUtilsTest.ts index b967617918c1..02668da462e0 100644 --- a/tests/unit/ViolationUtilsTest.ts +++ b/tests/unit/ViolationUtilsTest.ts @@ -43,7 +43,7 @@ describe('getViolationsOnyxData', () => { }); it('should return an object with correct shape and with empty transactionViolations array', () => { - const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); + const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories, false); expect(result).toEqual({ onyxMethod: Onyx.METHOD.SET, @@ -57,7 +57,7 @@ describe('getViolationsOnyxData', () => { {name: 'duplicatedTransaction', type: CONST.VIOLATION_TYPES.VIOLATION}, {name: 'receiptRequired', type: CONST.VIOLATION_TYPES.VIOLATION}, ]; - const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); + const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories, false); expect(result.value).toEqual(expect.arrayContaining(transactionViolations)); }); @@ -70,24 +70,32 @@ describe('getViolationsOnyxData', () => { it('should add missingCategory violation if no category is included', () => { transaction.category = undefined; - const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); + const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories, false); expect(result.value).toEqual(expect.arrayContaining([missingCategoryViolation, ...transactionViolations])); }); it('should add categoryOutOfPolicy violation when category is not in policy', () => { transaction.category = 'Bananas'; - const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); + const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories, false); expect(result.value).toEqual(expect.arrayContaining([categoryOutOfPolicyViolation, ...transactionViolations])); }); it('should not include a categoryOutOfPolicy violation when category is in policy', () => { - const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); + const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories, false); expect(result.value).not.toContainEqual(categoryOutOfPolicyViolation); }); it('should not add a category violation when the transaction is partial', () => { const partialTransaction = {...transaction, amount: 0, merchant: CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT, category: undefined}; - const result = ViolationsUtils.getViolationsOnyxData(partialTransaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); + const result = ViolationsUtils.getViolationsOnyxData( + partialTransaction, + transactionViolations, + policyRequiresTags, + policyTags, + policyRequiresCategories, + policyCategories, + false, + ); expect(result.value).not.toContainEqual(missingCategoryViolation); }); @@ -98,7 +106,7 @@ describe('getViolationsOnyxData', () => { {name: 'receiptRequired', type: CONST.VIOLATION_TYPES.VIOLATION}, ]; - const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); + const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories, false); expect(result.value).toEqual(expect.arrayContaining([categoryOutOfPolicyViolation, ...transactionViolations])); }); @@ -110,7 +118,7 @@ describe('getViolationsOnyxData', () => { {name: 'receiptRequired', type: CONST.VIOLATION_TYPES.VIOLATION}, ]; - const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); + const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories, false); expect(result.value).toEqual(expect.arrayContaining([missingCategoryViolation, ...transactionViolations])); }); @@ -122,7 +130,7 @@ describe('getViolationsOnyxData', () => { }); it('should not add any violations when categories are not required', () => { - const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); + const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories, false); expect(result.value).not.toContainEqual([categoryOutOfPolicyViolation]); expect(result.value).not.toContainEqual([missingCategoryViolation]); @@ -147,7 +155,7 @@ describe('getViolationsOnyxData', () => { }); it("shouldn't update the transactionViolations if the policy requires tags and the transaction has a tag from the policy", () => { - const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); + const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories, false); expect(result.value).toEqual(transactionViolations); }); @@ -155,7 +163,7 @@ describe('getViolationsOnyxData', () => { it('should add a missingTag violation if none is provided and policy requires tags', () => { transaction.tag = undefined; - const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); + const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories, false); expect(result.value).toEqual(expect.arrayContaining([{...missingTagViolation}])); }); @@ -163,14 +171,22 @@ describe('getViolationsOnyxData', () => { it('should add a tagOutOfPolicy violation when policy requires tags and tag is not in the policy', () => { policyTags = {}; - const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); + const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories, false); expect(result.value).toEqual([]); }); it('should not add a tag violation when the transaction is partial', () => { const partialTransaction = {...transaction, amount: 0, merchant: CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT, tag: undefined}; - const result = ViolationsUtils.getViolationsOnyxData(partialTransaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); + const result = ViolationsUtils.getViolationsOnyxData( + partialTransaction, + transactionViolations, + policyRequiresTags, + policyTags, + policyRequiresCategories, + policyCategories, + false, + ); expect(result.value).not.toContainEqual(missingTagViolation); }); @@ -181,7 +197,7 @@ describe('getViolationsOnyxData', () => { {name: 'receiptRequired', type: CONST.VIOLATION_TYPES.VIOLATION}, ]; - const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); + const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories, false); expect(result.value).toEqual(expect.arrayContaining([{...tagOutOfPolicyViolation}, ...transactionViolations])); }); @@ -193,7 +209,7 @@ describe('getViolationsOnyxData', () => { {name: 'receiptRequired', type: CONST.VIOLATION_TYPES.VIOLATION}, ]; - const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); + const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories, false); expect(result.value).toEqual(expect.arrayContaining([{...missingTagViolation}, ...transactionViolations])); }); @@ -205,7 +221,7 @@ describe('getViolationsOnyxData', () => { }); it('should not add any violations when tags are not required', () => { - const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); + const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories, false); expect(result.value).not.toContainEqual([tagOutOfPolicyViolation]); expect(result.value).not.toContainEqual([missingTagViolation]); @@ -260,30 +276,30 @@ describe('getViolationsOnyxData', () => { }; // Test case where transaction has no tags - let result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); + let result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories, false); expect(result.value).toEqual([someTagLevelsRequiredViolation]); // Test case where transaction has 1 tag transaction.tag = 'Africa'; someTagLevelsRequiredViolation.data = {errorIndexes: [1, 2]}; - result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); + result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories, false); expect(result.value).toEqual([someTagLevelsRequiredViolation]); // Test case where transaction has 2 tags transaction.tag = 'Africa::Project1'; someTagLevelsRequiredViolation.data = {errorIndexes: [1]}; - result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); + result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories, false); expect(result.value).toEqual([someTagLevelsRequiredViolation]); // Test case where transaction has all tags transaction.tag = 'Africa:Accounting:Project1'; - result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); + result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories, false); expect(result.value).toEqual([]); }); it('should return tagOutOfPolicy when a tag is not enabled in the policy but is set in the transaction', () => { policyTags.Department.tags.Accounting.enabled = false; transaction.tag = 'Africa:Accounting:Project1'; - const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); + const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories, false); const violation = {...tagOutOfPolicyViolation, data: {tagName: 'Department'}}; expect(result.value).toEqual([violation]); }); From 3d6478f10c327d7b57e628b98295a3185124b232 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Mon, 29 Apr 2024 22:25:26 +0100 Subject: [PATCH 013/230] chore(tests): add violation case for missing dependent tags --- tests/unit/ViolationUtilsTest.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/unit/ViolationUtilsTest.ts b/tests/unit/ViolationUtilsTest.ts index 02668da462e0..5e8f93a56dbc 100644 --- a/tests/unit/ViolationUtilsTest.ts +++ b/tests/unit/ViolationUtilsTest.ts @@ -303,5 +303,13 @@ describe('getViolationsOnyxData', () => { const violation = {...tagOutOfPolicyViolation, data: {tagName: 'Department'}}; expect(result.value).toEqual([violation]); }); + it('should return missingTag when all dependent tags are enabled in the policy but are not set in the transaction', () => { + const missingDepartmentTag = {...missingTagViolation, data: {tagName: 'Department'}}; + const missingRegionTag = {...missingTagViolation, data: {tagName: 'Region'}}; + const missingProjectTag = {...missingTagViolation, data: {tagName: 'Project'}}; + transaction.tag = undefined; + const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories, true); + expect(result.value).toEqual(expect.arrayContaining([missingDepartmentTag, missingRegionTag, missingProjectTag])); + }); }); }); From 0410d8ff2bb5ac120aeb3bf9fe5d98a5b579132f Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 30 Apr 2024 16:01:49 +0200 Subject: [PATCH 014/230] fix: minor fix --- src/libs/ReportUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index c9213d5460ea..cbce4a48928d 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -4983,7 +4983,7 @@ function shouldReportBeInOptionList({ !isTaskReport(report) && !isSelfDM(report) && !isGroupChat(report) && - !isInvoiceRoom(report)) && + !isInvoiceRoom(report) && // TODO: this shouldn't be necessary if the system report has participants list filled !isSystemChat(report)) ) { From 179575fb7ff9e91b9f7a87f90162d03191875c09 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 30 Apr 2024 17:03:58 +0200 Subject: [PATCH 015/230] fix: remove comment --- src/libs/ReportUtils.ts | 1 - src/libs/actions/Report.ts | 24 +++++++++++++++---- .../BaseOnboardingPersonalDetails.tsx | 2 ++ 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index cbce4a48928d..967004c7184d 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -5064,7 +5064,6 @@ function shouldReportBeInOptionList({ /** * Returns the system report from the list of reports. - * TODO: this method may not be necessary if the participants list of the system report is filled correctly */ function getSystemChat(): OnyxEntry { if (!allReports) { diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 9fe8f8d9fffa..4ef0b9e22d02 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -871,7 +871,11 @@ function openReport( if (isFromDeepLink) { // eslint-disable-next-line rulesdir/no-api-side-effects-method - API.makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.OPEN_REPORT, parameters, {optimisticData, successData, failureData}).finally(() => { + API.makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.OPEN_REPORT, parameters, { + optimisticData, + successData, + failureData, + }).finally(() => { Onyx.set(ONYXKEYS.IS_CHECKING_PUBLIC_ROOM, false); }); } else { @@ -1851,7 +1855,10 @@ function updateDescription(reportID: string, previousValue: string, newValue: st { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, - value: {description: parsedDescription, pendingFields: {description: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}}, + value: { + description: parsedDescription, + pendingFields: {description: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}, + }, }, ]; const failureData: OnyxUpdate[] = [ @@ -1930,7 +1937,6 @@ function navigateToConciergeChat(shouldDismissModal = false, checkIfCurrentPageA * Navigates to the 1:1 system chat */ function navigateToSystemChat() { - // TODO: when system report participants list is filled, we could just use `ReportUtils.getChatByParticipants()` method insted `getSystemChat()` const systemChatReport = ReportUtils.getSystemChat(); if (systemChatReport && systemChatReport.reportID) { @@ -2215,7 +2221,10 @@ function shouldShowReportActionNotification(reportID: string, action: ReportActi // If this notification was delayed and the user saw the message already, don't show it if (action && report?.lastReadTime && report.lastReadTime >= action.created) { - Log.info(`${tag} No notification because the comment was already read`, false, {created: action.created, lastReadTime: report.lastReadTime}); + Log.info(`${tag} No notification because the comment was already read`, false, { + created: action.created, + lastReadTime: report.lastReadTime, + }); return false; } @@ -2243,7 +2252,10 @@ function showReportActionNotification(reportID: string, reportAction: ReportActi const report = allReports?.[reportID] ?? null; if (!report) { - Log.hmmm("[LocalNotification] couldn't show report action notification because the report wasn't found", {reportID, reportActionID: reportAction.reportActionID}); + Log.hmmm("[LocalNotification] couldn't show report action notification because the report wasn't found", { + reportID, + reportActionID: reportAction.reportActionID, + }); return; } @@ -3027,6 +3039,8 @@ function completeOnboarding( const isAccountIDOdd = AccountUtils.isAccountIDOddNumber(currentUserAccountID ?? 0); const targetEmail = isAccountIDOdd ? CONST.EMAIL.NOTIFICATIONS : CONST.EMAIL.CONCIERGE; + console.log({currentUserAccountID}); + const actorAccountID = PersonalDetailsUtils.getAccountIDsByLogins([targetEmail])[0]; // TODO: using getSystemChat is rather not necessary if we could have participants list filled correctly const targetChatReport = isAccountIDOdd ? ReportUtils.getSystemChat() : ReportUtils.getChatByParticipants([actorAccountID]); diff --git a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx index 036408226e71..eb4d4e50ffbd 100644 --- a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx +++ b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx @@ -42,6 +42,7 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat const {accountID} = useSession(); useDisableModalDismissOnEscape(); + console.log({accountID}); const completeEngagement = useCallback( (values: FormOnyxValues<'onboardingPersonalDetailsForm'>) => { @@ -67,6 +68,7 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat }); Navigation.dismissModal(); + console.log({accountID}); // Only navigate to concierge chat when central pane is visible // Otherwise stay on the chats screen. From 9cba84a26366e0eb611ead798c877a0419b5eb4a Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 30 Apr 2024 17:12:31 +0200 Subject: [PATCH 016/230] fix: remove unnecessary condition --- src/libs/ReportUtils.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 967004c7184d..ed9639ab765a 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -4983,9 +4983,7 @@ function shouldReportBeInOptionList({ !isTaskReport(report) && !isSelfDM(report) && !isGroupChat(report) && - !isInvoiceRoom(report) && - // TODO: this shouldn't be necessary if the system report has participants list filled - !isSystemChat(report)) + !isInvoiceRoom(report)) ) { return false; } From ba3aa46bd40fbb1be8be752448c2ff254860e463 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 30 Apr 2024 17:25:53 +0200 Subject: [PATCH 017/230] fix: remove console log --- src/libs/actions/Report.ts | 12 ++++++++---- .../BaseOnboardingPersonalDetails.tsx | 2 -- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 4ef0b9e22d02..7d7e93463af2 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -351,7 +351,10 @@ function subscribeToReportTypingEvents(reportID: string) { delete typingWatchTimers[reportUserIdentifier]; }, 1500); }).catch((error) => { - Log.hmmm('[Report] Failed to initially subscribe to Pusher channel', {errorType: error.type, pusherChannelName}); + Log.hmmm('[Report] Failed to initially subscribe to Pusher channel', { + errorType: error.type, + pusherChannelName, + }); }); } @@ -382,7 +385,10 @@ function subscribeToReportLeavingEvents(reportID: string) { Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_USER_IS_LEAVING_ROOM}${reportID}`, true); }).catch((error) => { - Log.hmmm('[Report] Failed to initially subscribe to Pusher channel', {errorType: error.type, pusherChannelName}); + Log.hmmm('[Report] Failed to initially subscribe to Pusher channel', { + errorType: error.type, + pusherChannelName, + }); }); } @@ -3039,8 +3045,6 @@ function completeOnboarding( const isAccountIDOdd = AccountUtils.isAccountIDOddNumber(currentUserAccountID ?? 0); const targetEmail = isAccountIDOdd ? CONST.EMAIL.NOTIFICATIONS : CONST.EMAIL.CONCIERGE; - console.log({currentUserAccountID}); - const actorAccountID = PersonalDetailsUtils.getAccountIDsByLogins([targetEmail])[0]; // TODO: using getSystemChat is rather not necessary if we could have participants list filled correctly const targetChatReport = isAccountIDOdd ? ReportUtils.getSystemChat() : ReportUtils.getChatByParticipants([actorAccountID]); diff --git a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx index eb4d4e50ffbd..036408226e71 100644 --- a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx +++ b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx @@ -42,7 +42,6 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat const {accountID} = useSession(); useDisableModalDismissOnEscape(); - console.log({accountID}); const completeEngagement = useCallback( (values: FormOnyxValues<'onboardingPersonalDetailsForm'>) => { @@ -68,7 +67,6 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat }); Navigation.dismissModal(); - console.log({accountID}); // Only navigate to concierge chat when central pane is visible // Otherwise stay on the chats screen. From 433b7b8051f5e3532dd0b519adbb4c473d78e484 Mon Sep 17 00:00:00 2001 From: Ren Jones <153645623+ren-jones@users.noreply.github.com> Date: Wed, 1 May 2024 13:56:54 -0500 Subject: [PATCH 018/230] DOCS: Update and rename Profile.md to Add-profile-photo.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Article rewrite—we're splitting the previous version of this doc into multiple different articles --- .../settings/Add-profile-photo.md | 21 +++++ .../new-expensify/settings/Profile.md | 92 ------------------- 2 files changed, 21 insertions(+), 92 deletions(-) create mode 100644 docs/articles/new-expensify/settings/Add-profile-photo.md delete mode 100644 docs/articles/new-expensify/settings/Profile.md diff --git a/docs/articles/new-expensify/settings/Add-profile-photo.md b/docs/articles/new-expensify/settings/Add-profile-photo.md new file mode 100644 index 000000000000..60e56deaafbc --- /dev/null +++ b/docs/articles/new-expensify/settings/Add-profile-photo.md @@ -0,0 +1,21 @@ +--- +title: Add profile photo +description: Add an image to your profile +--- +
+ +{% include selector.html values="desktop, mobile" %} + +{% include option.html value="desktop" %} +1. Click your profile image or icon in the bottom left menu. +2. Click the Edit pencil icon next to your profile image or icon and select **Upload Image** to choose a new image from your saved files. +{% include end-option.html %} + +{% include option.html value="mobile" %} +1. Tap your profile image or icon at the bottom of the screen. +2. Tap the Edit pencil icon next to your profile image or icon and select **Upload Image** to choose a new image from your saved files. +{% include end-option.html %} + +{% include end-selector.html %} + +
diff --git a/docs/articles/new-expensify/settings/Profile.md b/docs/articles/new-expensify/settings/Profile.md deleted file mode 100644 index 908cf39c7ac6..000000000000 --- a/docs/articles/new-expensify/settings/Profile.md +++ /dev/null @@ -1,92 +0,0 @@ ---- -title: Profile -description: How to manage your Expensify Profile ---- -# Overview -Your Profile in Expensify allows you to: -- Set your public profile photo -- Set a display name -- Manage your contact methods -- Communicate your current status -- Set your pronouns -- Configure your timezone -- Store your personal details (for travel and payment purposes) - -# How to set your public profile photo - -To set or update your profile photo: -1. Go to **Settings > Profile** -2. Tap on the default or your existing profile photo, -3. You can either either upload (to set a new profile photo), remove or view your profile photo - -Your profile photo is visible to all Expensify users. - -# How to set a display name - -To set or update your display name: -1. Go to **Settings > Profile** -2. Tap on **Display name** -3. Set a first name and a last name, then **Save** - -Your display name is public to all Expensify users. - -# How to add or remove contact methods (email address and phone number) - -Your contact methods allow people to contact you (using your email address or phone number), and allow you to forward receipts to receipts@expensify.com from multiple email addresses. - -To manage your contact methods: -1. Go to **Settings > Profile** -2. Tap on **Contact method** -3. Tap **New contact method** to add a new email or phone number - -Your default contact method (email address or phone number) will be visible to "known" users, with whom you have interacted or are part of your team. - -To change the email address or phone number that's displayed on your Expensify account, add a new contact method, then tap on that email address and tap **Set as default**. - -# How to communicate your current status - -You can use your status emoji to communicate your mood, focus or current activity. You can optionally add a status message too! - -To set your status emoji and status message: -1. Go to **Settings > Profile** -2. Tap on **Status** then **Status** -3. Choose a status emoji, and optionally set a status message -4. Tap on **Save** - -Your status emoji will be visible next to your name in Expensify, and your status emoji and status message will appear in your profile (which is public to all Expensify users). On a computer, your status message will also be visible by hovering your mouse over your name. - -You can also remove your current status: -1. Go to **Settings > Profile** -2. Tap on **Status** -3. Tap on **Clear status** - -# How to set your pronouns - -To set your pronouns: -1. Go to **Settings > Profile** -2. Tap on **Pronouns** -3. Search for your preferred pronouns, then tap on your choice - -Your pronouns will be visible to "known" users, with whom you have interacted or are part of your team. - -# How to configure your timezone - -Your timezone is automatically set using an estimation based on your IP address. - -To set your timezone manually: -1. Go to **Settings > Profile** -2. Tap on **Timezone** -3. Disable **Automatically determine your location** -4. Tap on **Timezone** -5. Search for your preferred timezone, then tap on your choice - -Your timezone will be visible to "known" users, with whom you have interacted or are part of your team. - -# How to store your personal details (for travel and payment purposes) - -Your personal details can be used in Expensify for travel and payment purposes. These will not be shared with any other Expensify user. - -To set your timezone manually: -1. Go to **Settings > Profile** -2. Tap on **Personal details** -3. Tap on **Legal name**, **Date of birth**, and **Address** to set your personal details From 5c2e6d2b3f1319d52e79f0726dcc7e671a1f4e8f Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 2 May 2024 11:36:37 +0200 Subject: [PATCH 019/230] fix: remove unnecessary change --- src/libs/actions/Report.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 7d7e93463af2..270c126fbcad 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -351,10 +351,7 @@ function subscribeToReportTypingEvents(reportID: string) { delete typingWatchTimers[reportUserIdentifier]; }, 1500); }).catch((error) => { - Log.hmmm('[Report] Failed to initially subscribe to Pusher channel', { - errorType: error.type, - pusherChannelName, - }); + Log.hmmm('[Report] Failed to initially subscribe to Pusher channel', {errorType: error.type, pusherChannelName}); }); } @@ -385,10 +382,7 @@ function subscribeToReportLeavingEvents(reportID: string) { Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_USER_IS_LEAVING_ROOM}${reportID}`, true); }).catch((error) => { - Log.hmmm('[Report] Failed to initially subscribe to Pusher channel', { - errorType: error.type, - pusherChannelName, - }); + Log.hmmm('[Report] Failed to initially subscribe to Pusher channel', {errorType: error.type, pusherChannelName}); }); } From 36842b8d038974c945751ce7ad51fc026fed7c81 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 2 May 2024 11:42:50 +0200 Subject: [PATCH 020/230] fix: apply requested changes --- src/libs/ReportUtils.ts | 1 - src/libs/SidebarUtils.ts | 5 ----- src/libs/actions/Report.ts | 24 +++++------------------- 3 files changed, 5 insertions(+), 25 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index ed9639ab765a..1f3ececf17bd 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -4973,7 +4973,6 @@ function shouldReportBeInOptionList({ report?.reportName === undefined || // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing report?.isHidden || - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing (report?.participantAccountIDs?.length === 0 && !isChatThread(report) && !isPublicRoom(report) && diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 3d0b214d8377..2360366dde73 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -245,11 +245,6 @@ function getOptionData({ participantAccountIDs = [report.ownerAccountID ?? 0]; } - // TODO: this is added for the testing purposes only - should be removed once participants list of the system report is filled - if (report.chatType === CONST.REPORT.CHAT_TYPE.SYSTEM) { - participantAccountIDs = [report.ownerAccountID ?? 0, ...PersonalDetailsUtils.getAccountIDsByLogins([CONST.EMAIL.NOTIFICATIONS])]; - } - const participantPersonalDetailList = Object.values(OptionsListUtils.getPersonalDetailsForAccountIDs(participantAccountIDs, personalDetails)) as PersonalDetails[]; const personalDetail = participantPersonalDetailList[0] ?? {}; const hasErrors = Object.keys(result.allReportErrors ?? {}).length !== 0; diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 270c126fbcad..bb3855dd893a 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -871,11 +871,7 @@ function openReport( if (isFromDeepLink) { // eslint-disable-next-line rulesdir/no-api-side-effects-method - API.makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.OPEN_REPORT, parameters, { - optimisticData, - successData, - failureData, - }).finally(() => { + API.makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.OPEN_REPORT, parameters, {optimisticData, successData, failureData}).finally(() => { Onyx.set(ONYXKEYS.IS_CHECKING_PUBLIC_ROOM, false); }); } else { @@ -1855,10 +1851,7 @@ function updateDescription(reportID: string, previousValue: string, newValue: st { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, - value: { - description: parsedDescription, - pendingFields: {description: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}, - }, + value: {description: parsedDescription, pendingFields: {description: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}}, }, ]; const failureData: OnyxUpdate[] = [ @@ -2221,10 +2214,7 @@ function shouldShowReportActionNotification(reportID: string, action: ReportActi // If this notification was delayed and the user saw the message already, don't show it if (action && report?.lastReadTime && report.lastReadTime >= action.created) { - Log.info(`${tag} No notification because the comment was already read`, false, { - created: action.created, - lastReadTime: report.lastReadTime, - }); + Log.info(`${tag} No notification because the comment was already read`, false, {created: action.created, lastReadTime: report.lastReadTime}); return false; } @@ -2252,10 +2242,7 @@ function showReportActionNotification(reportID: string, reportAction: ReportActi const report = allReports?.[reportID] ?? null; if (!report) { - Log.hmmm("[LocalNotification] couldn't show report action notification because the report wasn't found", { - reportID, - reportActionID: reportAction.reportActionID, - }); + Log.hmmm("[LocalNotification] couldn't show report action notification because the report wasn't found", {reportID, reportActionID: reportAction.reportActionID}); return; } @@ -3040,8 +3027,7 @@ function completeOnboarding( const targetEmail = isAccountIDOdd ? CONST.EMAIL.NOTIFICATIONS : CONST.EMAIL.CONCIERGE; const actorAccountID = PersonalDetailsUtils.getAccountIDsByLogins([targetEmail])[0]; - // TODO: using getSystemChat is rather not necessary if we could have participants list filled correctly - const targetChatReport = isAccountIDOdd ? ReportUtils.getSystemChat() : ReportUtils.getChatByParticipants([actorAccountID]); + const targetChatReport = ReportUtils.getChatByParticipants([actorAccountID]); const {reportID: targetChatReportID = '', policyID: targetChatPolicyID = ''} = targetChatReport ?? {}; // Mention message From a6b6593adaf35c4ea1ec5c8282845fc5e3845c17 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 2 May 2024 13:56:59 +0200 Subject: [PATCH 021/230] fix: lint --- src/libs/SidebarUtils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 2360366dde73..c0d0c9020a64 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -18,7 +18,6 @@ import localeCompare from './LocaleCompare'; import * as LocalePhoneNumber from './LocalePhoneNumber'; import * as Localize from './Localize'; import * as OptionsListUtils from './OptionsListUtils'; -import * as PersonalDetailsUtils from './PersonalDetailsUtils'; import * as ReportActionsUtils from './ReportActionsUtils'; import * as ReportUtils from './ReportUtils'; import * as TaskUtils from './TaskUtils'; From e29768615c1e43b9708e4048aa0feb64041e6c7f Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Fri, 3 May 2024 09:29:44 +0100 Subject: [PATCH 022/230] refactor: apply pull request suggestions --- src/hooks/useViolations.ts | 23 +++++++++++------------ src/libs/Violations/ViolationsUtils.ts | 11 ++++++----- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/hooks/useViolations.ts b/src/hooks/useViolations.ts index 578db947ed57..6257a9251d08 100644 --- a/src/hooks/useViolations.ts +++ b/src/hooks/useViolations.ts @@ -63,30 +63,29 @@ function useViolations(violations: TransactionViolation[]) { const getViolationsForField = useCallback( (field: ViolationField, data?: TransactionViolation['data']) => { const currentViolations = violationsByField.get(field) ?? []; - const violation = currentViolations[0]; // someTagLevelsRequired has special logic becase data.errorIndexes is a bit unique in how it denotes the tag list that has the violation // tagListIndex can be 0 so we compare with undefined - if (violation?.name === 'someTagLevelsRequired' && data?.tagListIndex !== undefined && Array.isArray(violation?.data?.errorIndexes)) { + if (currentViolations[0]?.name === 'someTagLevelsRequired' && data?.tagListIndex !== undefined && Array.isArray(currentViolations[0]?.data?.errorIndexes)) { return currentViolations - .filter((currentViolation) => currentViolation.data?.errorIndexes?.includes(data?.tagListIndex ?? -1)) - .map((currentViolation) => ({ - ...currentViolation, + .filter((violation) => violation.data?.errorIndexes?.includes(data?.tagListIndex ?? -1)) + .map((violation) => ({ + ...violation, data: { - ...currentViolation.data, + ...violation.data, tagName: data?.tagListName, }, })); } - // missingTag has special logic for policies with dependent tags, because only violation is returned for all tags + // missingTag has special logic for policies with dependent tags, because only one violation is returned for all tags // when no tags are present, so the tag name isn't set in the violation data. That's why we add it here - if (data?.policyHasDependentTags && violation?.name === 'missingTag' && data?.tagListName) { + if (data?.policyHasDependentTags && currentViolations[0]?.name === 'missingTag' && data?.tagListName) { return [ { - ...violation, + ...currentViolations[0], data: { - ...violation.data, + ...currentViolations[0].data, tagName: data?.tagListName, }, }, @@ -94,8 +93,8 @@ function useViolations(violations: TransactionViolation[]) { } // tagOutOfPolicy has special logic because we have to account for multi-level tags and use tagName to find the right tag to put the violation on - if (violation?.name === 'tagOutOfPolicy' && data?.tagListName !== undefined && violation?.data?.tagName) { - return currentViolations.filter((currentViolation) => currentViolation.data?.tagName === data?.tagListName); + if (currentViolations[0]?.name === 'tagOutOfPolicy' && data?.tagListName !== undefined && currentViolations[0]?.data?.tagName) { + return currentViolations.filter((violation) => violation.data?.tagName === data?.tagListName); } return currentViolations; diff --git a/src/libs/Violations/ViolationsUtils.ts b/src/libs/Violations/ViolationsUtils.ts index 0ef102318d78..c7bcd66aef5e 100644 --- a/src/libs/Violations/ViolationsUtils.ts +++ b/src/libs/Violations/ViolationsUtils.ts @@ -67,7 +67,9 @@ function getTagViolationsForDependentTags(policyTagList: PolicyTagList, transact * Calculates missing tag violations for policies with independent tags, * by returning one per tag with its corresponding tagName in the data */ -function getTagViolationForIndependentTags(policyTagList: PolicyTagList, transactionViolations: TransactionViolation[], policyTagKeys: string[], selectedTags: string[]) { +function getTagViolationForIndependentTags(policyTagList: PolicyTagList, transactionViolations: TransactionViolation[], transaction: Transaction) { + const policyTagKeys = getSortedTagKeys(policyTagList); + const selectedTags = transaction.tag?.split(CONST.COLON) ?? []; let newTransactionViolations = [...transactionViolations]; // We first get the errorIndexes for someTagLevelsRequired. If it's not empty, we puth SOME_TAG_LEVELS_REQUIRED in Onyx. @@ -131,17 +133,16 @@ function getTagViolationsForMultiLevelTags( policyTagList: PolicyTagList, hasDependentTags: boolean, ): TransactionViolation[] { - const policyTagKeys = getSortedTagKeys(policyTagList); - const selectedTags = updatedTransaction.tag?.split(CONST.COLON) ?? []; const filteredTransactionViolations = transactionViolations.filter( - (violation) => violation.name !== CONST.VIOLATIONS.SOME_TAG_LEVELS_REQUIRED && violation.name !== CONST.VIOLATIONS.TAG_OUT_OF_POLICY, + (violation) => + violation.name !== CONST.VIOLATIONS.SOME_TAG_LEVELS_REQUIRED && violation.name !== CONST.VIOLATIONS.TAG_OUT_OF_POLICY && violation.name !== CONST.VIOLATIONS.MISSING_TAG, ); if (hasDependentTags && !updatedTransaction.tag) { return getTagViolationsForDependentTags(policyTagList, filteredTransactionViolations); } - return getTagViolationForIndependentTags(policyTagList, filteredTransactionViolations, policyTagKeys, selectedTags); + return getTagViolationForIndependentTags(policyTagList, filteredTransactionViolations, updatedTransaction); } const ViolationsUtils = { From a8ef1c390be0a38c687303101ec3169ddda7de15 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Fri, 3 May 2024 10:19:07 +0100 Subject: [PATCH 023/230] chore(typescript): add missing argument --- src/libs/actions/IOU.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 7b962e712cf2..e552c5eac7d0 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -1087,7 +1087,15 @@ function buildOnyxDataForInvoice( return [optimisticData, successData, failureData]; } - const violationsOnyxData = ViolationsUtils.getViolationsOnyxData(transaction, [], !!policy.requiresTag, policyTagList ?? {}, !!policy.requiresCategory, policyCategories ?? {}); + const violationsOnyxData = ViolationsUtils.getViolationsOnyxData( + transaction, + [], + !!policy.requiresTag, + policyTagList ?? {}, + !!policy.requiresCategory, + policyCategories ?? {}, + PolicyUtils.hasDependentTags(policy, policyTagList ?? {}), + ); if (violationsOnyxData) { optimisticData.push(violationsOnyxData); From 5590c17316a5ed71394d2dddad65b1ab13708a03 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 10 May 2024 12:20:17 +0200 Subject: [PATCH 024/230] Refactor LHNOptionsList to use useOnyx --- .../LHNOptionsList/LHNOptionsList.tsx | 58 +++++-------------- src/components/LHNOptionsList/types.ts | 30 +--------- 2 files changed, 15 insertions(+), 73 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 8c43ae542932..b0ef2a4edaef 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -4,7 +4,7 @@ import {FlashList} from '@shopify/flash-list'; import type {ReactElement} from 'react'; import React, {memo, useCallback, useContext, useEffect, useMemo, useRef} from 'react'; import {StyleSheet, View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; +import {useOnyx} from 'react-native-onyx'; import BlockingView from '@components/BlockingViews/BlockingView'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -23,31 +23,24 @@ import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import OptionRowLHNData from './OptionRowLHNData'; -import type {LHNOptionsListOnyxProps, LHNOptionsListProps, RenderItemProps} from './types'; +import type {LHNOptionsListProps, RenderItemProps} from './types'; const keyExtractor = (item: string) => `report_${item}`; -function LHNOptionsList({ - style, - contentContainerStyles, - data, - onSelectRow, - optionMode, - shouldDisableFocusOptions = false, - reports = {}, - reportActions = {}, - policy = {}, - preferredLocale = CONST.LOCALES.DEFAULT, - personalDetails = {}, - transactions = {}, - draftComments = {}, - transactionViolations = {}, - onFirstItemRendered = () => {}, -}: LHNOptionsListProps) { +function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optionMode, shouldDisableFocusOptions = false, onFirstItemRendered = () => {}}: LHNOptionsListProps) { const {saveScrollOffset, getScrollOffset} = useContext(ScrollOffsetContext); const flashListRef = useRef>(null); const route = useRoute(); + const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT); + const [reportActions] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS); + const [policy] = useOnyx(ONYXKEYS.COLLECTION.POLICY); + const [preferredLocale] = useOnyx(ONYXKEYS.NVP_PREFERRED_LOCALE, {initialValue: CONST.LOCALES.DEFAULT}); + const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); + const [transactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION); + const [draftComments] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT); + const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); + const theme = useTheme(); const styles = useThemeStyles(); const {canUseViolations} = usePermissions(); @@ -246,31 +239,6 @@ function LHNOptionsList({ LHNOptionsList.displayName = 'LHNOptionsList'; -export default withOnyx({ - reports: { - key: ONYXKEYS.COLLECTION.REPORT, - }, - reportActions: { - key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, - }, - policy: { - key: ONYXKEYS.COLLECTION.POLICY, - }, - preferredLocale: { - key: ONYXKEYS.NVP_PREFERRED_LOCALE, - }, - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - }, - transactions: { - key: ONYXKEYS.COLLECTION.TRANSACTION, - }, - draftComments: { - key: ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, - }, - transactionViolations: { - key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, - }, -})(memo(LHNOptionsList)); +export default memo(LHNOptionsList); export type {LHNOptionsListProps}; diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index 0f0c921747b4..7248742654d2 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -10,32 +10,6 @@ import type {EmptyObject} from '@src/types/utils/EmptyObject'; type OptionMode = ValueOf; -type LHNOptionsListOnyxProps = { - /** The policy which the user has access to and which the report could be tied to */ - policy: OnyxCollection; - - /** All reports shared with the user */ - reports: OnyxCollection; - - /** Array of report actions for this report */ - reportActions: OnyxCollection; - - /** Indicates which locale the user currently has selected */ - preferredLocale: OnyxEntry; - - /** List of users' personal details */ - personalDetails: OnyxEntry; - - /** The transaction from the parent report action */ - transactions: OnyxCollection; - - /** List of draft comments */ - draftComments: OnyxCollection; - - /** The list of transaction violations */ - transactionViolations: OnyxCollection; -}; - type CustomLHNOptionsListProps = { /** Wrapper style for the section list */ style?: StyleProp; @@ -59,7 +33,7 @@ type CustomLHNOptionsListProps = { onFirstItemRendered: () => void; }; -type LHNOptionsListProps = CustomLHNOptionsListProps & LHNOptionsListOnyxProps; +type LHNOptionsListProps = CustomLHNOptionsListProps; type OptionRowLHNDataProps = { /** Whether row should be focused */ @@ -141,4 +115,4 @@ type OptionRowLHNProps = { type RenderItemProps = {item: string}; -export type {LHNOptionsListProps, OptionRowLHNDataProps, OptionRowLHNProps, LHNOptionsListOnyxProps, RenderItemProps}; +export type {LHNOptionsListProps, OptionRowLHNDataProps, OptionRowLHNProps, RenderItemProps}; From 575b0be19bdd958a0de0d4d73443418bf90fb885 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 10 May 2024 16:51:38 +0200 Subject: [PATCH 025/230] fix SidebarTest --- tests/unit/SidebarTest.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/SidebarTest.ts b/tests/unit/SidebarTest.ts index c037c1ced3f0..10f9b4afd4dd 100644 --- a/tests/unit/SidebarTest.ts +++ b/tests/unit/SidebarTest.ts @@ -53,9 +53,9 @@ describe('Sidebar', () => { // Given the user is in all betas const betas = [CONST.BETAS.DEFAULT_ROOMS]; - LHNTestUtils.getDefaultRenderedSidebarLinks('0'); return ( waitForBatchedUpdates() + .then(() => LHNTestUtils.getDefaultRenderedSidebarLinks('0')) // When Onyx is updated with the data and the sidebar re-renders .then(() => { const reportCollection: ReportCollectionDataSet = { @@ -105,9 +105,9 @@ describe('Sidebar', () => { // Given the user is in all betas const betas = [CONST.BETAS.DEFAULT_ROOMS]; - LHNTestUtils.getDefaultRenderedSidebarLinks('0'); return ( waitForBatchedUpdates() + .then(() => LHNTestUtils.getDefaultRenderedSidebarLinks('0')) // When Onyx is updated with the data and the sidebar re-renders .then(() => { const reportCollection: ReportCollectionDataSet = { From e903c1df50dc13098e32a28e03c12d17ac569c56 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 10 May 2024 17:02:02 +0200 Subject: [PATCH 026/230] fix SidebarOrderTest --- tests/unit/SidebarOrderTest.ts | 109 ++++++++++++++++----------------- 1 file changed, 53 insertions(+), 56 deletions(-) diff --git a/tests/unit/SidebarOrderTest.ts b/tests/unit/SidebarOrderTest.ts index 644bba5a589b..a5285fe186a2 100644 --- a/tests/unit/SidebarOrderTest.ts +++ b/tests/unit/SidebarOrderTest.ts @@ -62,33 +62,29 @@ describe('Sidebar', () => { expect(screen.toJSON()).toBe(null); }); - it('is rendered with an empty list when personal details exist', () => { - // Given the sidebar is rendered with default props - LHNTestUtils.getDefaultRenderedSidebarLinks(); - - return ( - waitForBatchedUpdates() - // When Onyx is updated with some personal details - .then(() => - Onyx.multiSet({ - [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, - [ONYXKEYS.IS_LOADING_APP]: false, - }), - ) - - // Then the component should be rendered with an empty list since it will get past the early return - .then(() => { - expect(screen.toJSON()).not.toBe(null); - const navigatesToChatHintText = Localize.translateLocal('accessibilityHints.navigatesToChat'); - expect(screen.queryAllByAccessibilityHint(navigatesToChatHintText)).toHaveLength(0); - }) - ); - }); + it('is rendered with an empty list when personal details exist', () => + waitForBatchedUpdates() + // Given the sidebar is rendered with default props + .then(() => LHNTestUtils.getDefaultRenderedSidebarLinks('0')) + + // When Onyx is updated with some personal details + .then(() => + Onyx.multiSet({ + [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, + [ONYXKEYS.IS_LOADING_APP]: false, + }), + ) + + // Then the component should be rendered with an empty list since it will get past the early return + .then(() => { + expect(screen.toJSON()).not.toBe(null); + const navigatesToChatHintText = Localize.translateLocal('accessibilityHints.navigatesToChat'); + expect(screen.queryAllByAccessibilityHint(navigatesToChatHintText)).toHaveLength(0); + })); it('contains one report when a report is in Onyx', () => { // Given a single report const report = LHNTestUtils.getFakeReport([1, 2]); - LHNTestUtils.getDefaultRenderedSidebarLinks(report.reportID); const reportCollectionDataSet: ReportCollectionDataSet = { [`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`]: report, @@ -96,6 +92,8 @@ describe('Sidebar', () => { return ( waitForBatchedUpdates() + .then(() => LHNTestUtils.getDefaultRenderedSidebarLinks(report.reportID)) + // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -114,8 +112,6 @@ describe('Sidebar', () => { }); it('orders items with most recently updated on top', () => { - LHNTestUtils.getDefaultRenderedSidebarLinks(); - // Given three unread reports in the recently updated order of 3, 2, 1 const report1 = LHNTestUtils.getFakeReport([1, 2], 3); const report2 = LHNTestUtils.getFakeReport([3, 4], 2); @@ -134,6 +130,8 @@ describe('Sidebar', () => { return ( waitForBatchedUpdates() + .then(() => LHNTestUtils.getDefaultRenderedSidebarLinks()) + // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -173,8 +171,6 @@ describe('Sidebar', () => { Report.addComment(report3.reportID, 'Hi, this is a comment'); const currentReportId = report1.reportID; - LHNTestUtils.getDefaultRenderedSidebarLinks(currentReportId); - const reportCollectionDataSet: ReportCollectionDataSet = { [`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`]: report1, [`${ONYXKEYS.COLLECTION.REPORT}${report2.reportID}`]: report2, @@ -183,6 +179,8 @@ describe('Sidebar', () => { return ( waitForBatchedUpdates() + .then(() => LHNTestUtils.getDefaultRenderedSidebarLinks(currentReportId)) + // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -211,8 +209,6 @@ describe('Sidebar', () => { }); it('reorders the reports to always have the most recently updated one on top', () => { - LHNTestUtils.getDefaultRenderedSidebarLinks(); - // Given three reports in the recently updated order of 3, 2, 1 const report1 = LHNTestUtils.getFakeReport([1, 2], 3); const report2 = LHNTestUtils.getFakeReport([3, 4], 2); @@ -231,6 +227,8 @@ describe('Sidebar', () => { return ( waitForBatchedUpdates() + .then(() => LHNTestUtils.getDefaultRenderedSidebarLinks()) + // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -282,8 +280,6 @@ describe('Sidebar', () => { Report.addComment(report2.reportID, 'Hi, this is a comment'); Report.addComment(report3.reportID, 'Hi, this is a comment'); - LHNTestUtils.getDefaultRenderedSidebarLinks(taskReport.reportID); - const reportCollectionDataSet: ReportCollectionDataSet = { [`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`]: report1, [`${ONYXKEYS.COLLECTION.REPORT}${report2.reportID}`]: report2, @@ -293,6 +289,8 @@ describe('Sidebar', () => { return ( waitForBatchedUpdates() + .then(() => LHNTestUtils.getDefaultRenderedSidebarLinks(taskReport.reportID)) + // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -346,8 +344,6 @@ describe('Sidebar', () => { Report.addComment(report2.reportID, 'Hi, this is a comment'); Report.addComment(report3.reportID, 'Hi, this is a comment'); - LHNTestUtils.getDefaultRenderedSidebarLinks(report3.reportID); - const reportCollectionDataSet: ReportCollectionDataSet = { [`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`]: report1, [`${ONYXKEYS.COLLECTION.REPORT}${report2.reportID}`]: report2, @@ -357,6 +353,8 @@ describe('Sidebar', () => { return ( waitForBatchedUpdates() + .then(() => LHNTestUtils.getDefaultRenderedSidebarLinks(report3.reportID)) + // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -413,8 +411,6 @@ describe('Sidebar', () => { Report.addComment(report2.reportID, 'Hi, this is a comment'); Report.addComment(report3.reportID, 'Hi, this is a comment'); - LHNTestUtils.getDefaultRenderedSidebarLinks(report3.reportID); - const reportCollectionDataSet: ReportCollectionDataSet = { [`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`]: report1, [`${ONYXKEYS.COLLECTION.REPORT}${report2.reportID}`]: report2, @@ -424,6 +420,8 @@ describe('Sidebar', () => { return ( waitForBatchedUpdates() + .then(() => LHNTestUtils.getDefaultRenderedSidebarLinks(report3.reportID)) + // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -464,7 +462,6 @@ describe('Sidebar', () => { Report.addComment(report3.reportID, 'Hi, this is a comment'); const currentReportId = report2.reportID; - LHNTestUtils.getDefaultRenderedSidebarLinks(currentReportId); const reportCollectionDataSet: ReportCollectionDataSet = { [`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`]: report1, @@ -474,6 +471,8 @@ describe('Sidebar', () => { return ( waitForBatchedUpdates() + .then(() => LHNTestUtils.getDefaultRenderedSidebarLinks(currentReportId)) + // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -507,8 +506,6 @@ describe('Sidebar', () => { }); it('removes the pencil icon when draft is removed', () => { - LHNTestUtils.getDefaultRenderedSidebarLinks(); - // Given a single report // And the report has a draft const report: OnyxTypes.Report = { @@ -521,6 +518,8 @@ describe('Sidebar', () => { return ( waitForBatchedUpdates() + .then(() => LHNTestUtils.getDefaultRenderedSidebarLinks()) + // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -548,8 +547,6 @@ describe('Sidebar', () => { }); it('removes the pin icon when chat is unpinned', () => { - LHNTestUtils.getDefaultRenderedSidebarLinks(); - // Given a single report // And the report is pinned const report: OnyxTypes.Report = { @@ -563,6 +560,8 @@ describe('Sidebar', () => { return ( waitForBatchedUpdates() + .then(() => LHNTestUtils.getDefaultRenderedSidebarLinks()) + // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -624,8 +623,6 @@ describe('Sidebar', () => { const currentReportId = report2.reportID; const currentlyLoggedInUserAccountID = 9; - LHNTestUtils.getDefaultRenderedSidebarLinks(currentReportId); - const reportCollectionDataSet: ReportCollectionDataSet = { [`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`]: report1, [`${ONYXKEYS.COLLECTION.REPORT}${report2.reportID}`]: report2, @@ -635,6 +632,8 @@ describe('Sidebar', () => { return ( waitForBatchedUpdates() + .then(() => LHNTestUtils.getDefaultRenderedSidebarLinks(currentReportId)) + // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -683,8 +682,6 @@ describe('Sidebar', () => { isPinned: true, }; - LHNTestUtils.getDefaultRenderedSidebarLinks('0'); - const reportCollectionDataSet: ReportCollectionDataSet = { [`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`]: report1, [`${ONYXKEYS.COLLECTION.REPORT}${report2.reportID}`]: report2, @@ -693,6 +690,8 @@ describe('Sidebar', () => { return ( waitForBatchedUpdates() + .then(() => LHNTestUtils.getDefaultRenderedSidebarLinks('0')) + // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -745,8 +744,6 @@ describe('Sidebar', () => { ...LHNTestUtils.getFakeReport([7, 8], 0), }; - LHNTestUtils.getDefaultRenderedSidebarLinks('0'); - const reportCollectionDataSet: ReportCollectionDataSet = { [`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`]: report1, [`${ONYXKEYS.COLLECTION.REPORT}${report2.reportID}`]: report2, @@ -761,6 +758,8 @@ describe('Sidebar', () => { return ( waitForBatchedUpdates() + .then(() => LHNTestUtils.getDefaultRenderedSidebarLinks('0')) + // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -824,8 +823,6 @@ describe('Sidebar', () => { // Given the user is in all betas const betas = [CONST.BETAS.DEFAULT_ROOMS]; - LHNTestUtils.getDefaultRenderedSidebarLinks('0'); - const reportCollectionDataSet: ReportCollectionDataSet = { [`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`]: report1, [`${ONYXKEYS.COLLECTION.REPORT}${report2.reportID}`]: report2, @@ -834,6 +831,8 @@ describe('Sidebar', () => { return ( waitForBatchedUpdates() + .then(() => LHNTestUtils.getDefaultRenderedSidebarLinks('0')) + // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -860,8 +859,6 @@ describe('Sidebar', () => { describe('in #focus mode', () => { it('alphabetizes chats', () => { - LHNTestUtils.getDefaultRenderedSidebarLinks(); - const report1 = LHNTestUtils.getFakeReport([1, 2], 3, true); const report2 = LHNTestUtils.getFakeReport([3, 4], 2, true); const report3 = LHNTestUtils.getFakeReport([5, 6], 1, true); @@ -875,6 +872,7 @@ describe('Sidebar', () => { return ( waitForBatchedUpdates() + .then(() => LHNTestUtils.getDefaultRenderedSidebarLinks('0')) // Given the sidebar is rendered in #focus mode (hides read chats) // with all reports having unread comments .then(() => @@ -926,8 +924,6 @@ describe('Sidebar', () => { // Given the user is in all betas const betas = [CONST.BETAS.DEFAULT_ROOMS]; - LHNTestUtils.getDefaultRenderedSidebarLinks('0'); - const reportCollectionDataSet: ReportCollectionDataSet = { [`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`]: report1, [`${ONYXKEYS.COLLECTION.REPORT}${report2.reportID}`]: report2, @@ -936,6 +932,8 @@ describe('Sidebar', () => { return ( waitForBatchedUpdates() + .then(() => LHNTestUtils.getDefaultRenderedSidebarLinks('0')) + // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -1060,8 +1058,6 @@ describe('Sidebar', () => { const currentlyLoggedInUserAccountID = 13; - LHNTestUtils.getDefaultRenderedSidebarLinks('0'); - const reportCollectionDataSet: ReportCollectionDataSet = { [`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`]: report1, [`${ONYXKEYS.COLLECTION.REPORT}${report2.reportID}`]: report2, @@ -1077,6 +1073,7 @@ describe('Sidebar', () => { return ( waitForBatchedUpdates() + .then(() => LHNTestUtils.getDefaultRenderedSidebarLinks('0')) // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ @@ -1123,8 +1120,6 @@ describe('Sidebar', () => { Report.addComment(report2.reportID, 'Hi, this is a comment'); Report.addComment(report3.reportID, 'Hi, this is a comment'); - LHNTestUtils.getDefaultRenderedSidebarLinks('0'); - const reportCollectionDataSet: ReportCollectionDataSet = { [`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`]: report1, [`${ONYXKEYS.COLLECTION.REPORT}${report2.reportID}`]: report2, @@ -1133,6 +1128,8 @@ describe('Sidebar', () => { return ( waitForBatchedUpdates() + .then(() => LHNTestUtils.getDefaultRenderedSidebarLinks('0')) + // When Onyx is updated with the data and the sidebar re-renders .then(() => Onyx.multiSet({ From 9f0585e6f576bd3df8441b92c34671793b315df2 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Sat, 11 May 2024 19:08:30 +0530 Subject: [PATCH 027/230] fix: Status - Emoji in custom status holder is not centered. Signed-off-by: Krishna Gupta --- src/styles/index.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/styles/index.ts b/src/styles/index.ts index 060fb1c5ba90..b1b92869c4b7 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -4333,7 +4333,20 @@ const styles = (theme: ThemeColors) => }, emojiStatusLHN: { - fontSize: 9, + ...(Browser.getBrowser() && {transform: 'scale(.65)', lineHeight: 18, fontSize: 15}), + ...(Browser.getBrowser() && + Browser.isSafari() && + !Browser.isMobileSafari() && { + transform: 'scale(0.65)', + fontSize: 13, + lineHeight: 15, + }), + // transform: 'scale(.65)', + // lineHeight: 15, + // transform: 'scale(.65)', + // lineHeight: 18, + // fontSize: 15, + // overflow: 'visible', }, onboardingVideoPlayer: { From be5bcfa3b7cd047c48e1b542bb5c2abe25126f9c Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Mon, 13 May 2024 10:01:05 +0530 Subject: [PATCH 028/230] feat: [Held requests] option does not show in the preview overflow menu. Signed-off-by: Krishna Gupta --- src/libs/ReportUtils.ts | 86 +++++++++++++++++++ .../report/ContextMenu/ContextMenuActions.tsx | 36 ++++++++ 2 files changed, 122 insertions(+) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 51c797937b1d..cb729b3f7dfd 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -14,6 +14,7 @@ import * as Expensicons from '@components/Icon/Expensicons'; import * as defaultGroupAvatars from '@components/Icon/GroupDefaultAvatars'; import * as defaultWorkspaceAvatars from '@components/Icon/WorkspaceDefaultAvatars'; import type {MoneyRequestAmountInputProps} from '@components/MoneyRequestAmountInput'; +import * as ReportUtils from '@libs/ReportUtils'; import type {IOUAction, IOUType} from '@src/CONST'; import CONST from '@src/CONST'; import type {ParentNavigationSummaryParams, TranslationPaths} from '@src/languages/types'; @@ -2689,6 +2690,89 @@ function canEditReportAction(reportAction: OnyxEntry): boolean { ); } +function getParentReportAction(parentReportActions: ReportActions | null | undefined, parentReportActionID: string | undefined): ReportAction | null { + if (!parentReportActions || !parentReportActionID) { + return null; + } + return parentReportActions[parentReportActionID ?? '0']; +} + +function canHoldUnholdReportAction(reportAction: OnyxEntry): {canHoldRequest: boolean; canUnholdRequest: boolean} { + if (reportAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.IOU) { + return {canHoldRequest: false, canUnholdRequest: false}; + } + const moneyRequestReportID = reportAction?.originalMessage?.IOUReportID ?? 0; + + if (!moneyRequestReportID) { + return {canHoldRequest: false, canUnholdRequest: false}; + } + + const moneyRequestReport = getReport(String(moneyRequestReportID)); + + if (!moneyRequestReport) { + return {canHoldRequest: false, canUnholdRequest: false}; + } + + const isSettled = ReportUtils.isSettled(moneyRequestReport?.reportID); + const isApproved = ReportUtils.isReportApproved(moneyRequestReport); + const transactionID = moneyRequestReport ? reportAction?.originalMessage?.IOUTransactionID : 0; + const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? ({} as Transaction); + const parentReportActionsKey = `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${moneyRequestReport.parentReportID}`; + const parentReportActions = allReportActions?.[parentReportActionsKey]; + const parentReport = getReport(String(moneyRequestReport.parentReportID)); + const parentReportAction = getParentReportAction(parentReportActions, moneyRequestReport?.parentReportActionID); + + const isRequestIOU = parentReport?.type === 'iou'; + const isHoldCreator = ReportUtils.isHoldCreator(transaction, moneyRequestReport?.reportID) && isRequestIOU; + const isTrackExpenseReport = ReportUtils.isTrackExpenseReport(moneyRequestReport); + const isActionOwner = + typeof parentReportAction?.actorAccountID === 'number' && + typeof currentUserPersonalDetails?.accountID === 'number' && + parentReportAction.actorAccountID === currentUserPersonalDetails?.accountID; + const isApprover = + ReportUtils.isMoneyRequestReport(moneyRequestReport) && moneyRequestReport?.managerID !== null && currentUserPersonalDetails?.accountID === moneyRequestReport?.managerID; + const isOnHold = TransactionUtils.isOnHold(transaction); + const isScanning = TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction); + + const canModifyStatus = !isTrackExpenseReport && (isPolicyAdmin || isActionOwner || isApprover); + const isDeletedParentAction = ReportActionsUtils.isDeletedAction(parentReportAction); + + const canHoldOrUnholdRequest = !isSettled && !isApproved && !isDeletedParentAction; + const canHoldRequest = canHoldOrUnholdRequest && !isOnHold && (isHoldCreator || (!isRequestIOU && canModifyStatus)) && !isScanning; + const canUnholdRequest = Boolean(canHoldOrUnholdRequest && isOnHold && (isHoldCreator || (!isRequestIOU && canModifyStatus))); + + return {canHoldRequest, canUnholdRequest}; +} + +const changeMoneyRequestHoldStatus = (reportAction: OnyxEntry): void => { + if (reportAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.IOU) { + return; + } + const moneyRequestReportID = reportAction?.originalMessage?.IOUReportID ?? 0; + + const moneyRequestReport = getReport(String(moneyRequestReportID)); + if (!moneyRequestReportID || !moneyRequestReport) { + return; + } + + const parentReportActionsKey = `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${moneyRequestReport.parentReportID}`; + const parentReportActions = allReportActions?.[parentReportActionsKey]; + const parentReportAction = getParentReportAction(parentReportActions, moneyRequestReport?.parentReportActionID); + const transactionID = moneyRequestReport ? reportAction?.originalMessage?.IOUTransactionID : 0; + const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? ({} as Transaction); + const isOnHold = TransactionUtils.isOnHold(transaction); + const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${moneyRequestReport.policyID}`] ?? null; + + // const iouTransactionID = parentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? parentReportAction.originalMessage?.IOUTransactionID ?? '' : ''; + + if (isOnHold) { + IOU.unholdRequest(transactionID, reportAction.childReportID); + } else { + const activeRoute = encodeURIComponent(Navigation.getActiveRouteWithoutParams()); + Navigation.navigate(ROUTES.MONEY_REQUEST_HOLD_REASON.getRoute(policy?.type ?? CONST.POLICY.TYPE.PERSONAL, transactionID, reportAction.childReportID, activeRoute)); + } +}; + /** * Gets all transactions on an IOU report with a receipt */ @@ -6607,6 +6691,7 @@ export { canCreateTaskInReport, canCurrentUserOpenReport, canDeleteReportAction, + canHoldUnholdReportAction, canEditFieldOfMoneyRequest, canEditMoneyRequest, canEditPolicyDescription, @@ -6825,6 +6910,7 @@ export { shouldShowMerchantColumn, isCurrentUserInvoiceReceiver, isDraftReport, + changeMoneyRequestHoldStatus, }; export type { diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx index 105eadffd436..5d5b6c070eb0 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx @@ -255,6 +255,42 @@ const ContextMenuActions: ContextMenuAction[] = [ }, getDescription: () => {}, }, + { + isAnonymousAction: false, + textTranslateKey: 'iou.unholdExpense', + icon: Expensicons.Stopwatch, + shouldShow: (type, reportAction) => + type === CONST.CONTEXT_MENU_TYPES.REPORT_ACTION && ReportUtils.canEditReportAction(reportAction) && ReportUtils.canHoldUnholdReportAction(reportAction).canUnholdRequest, + onPress: (closePopover, {reportAction}) => { + // const hold=ReportUtils.changeMoneyRequestHoldStatus(). + if (closePopover) { + hideContextMenu(false, () => ReportUtils.changeMoneyRequestHoldStatus(reportAction)); + return; + } + + // No popover to hide, call changeMoneyRequestHoldStatus immediately + ReportUtils.changeMoneyRequestHoldStatus(reportAction); + }, + getDescription: () => {}, + }, + { + isAnonymousAction: false, + textTranslateKey: 'iou.hold', + icon: Expensicons.Stopwatch, + shouldShow: (type, reportAction) => + type === CONST.CONTEXT_MENU_TYPES.REPORT_ACTION && ReportUtils.canEditReportAction(reportAction) && ReportUtils.canHoldUnholdReportAction(reportAction).canHoldRequest, + onPress: (closePopover, {reportAction}) => { + // const hold=ReportUtils.changeMoneyRequestHoldStatus(). + if (closePopover) { + hideContextMenu(false, () => ReportUtils.changeMoneyRequestHoldStatus(reportAction)); + return; + } + + // No popover to hide, call changeMoneyRequestHoldStatus immediately + ReportUtils.changeMoneyRequestHoldStatus(reportAction); + }, + getDescription: () => {}, + }, { isAnonymousAction: false, textTranslateKey: 'reportActionContextMenu.joinThread', From f06518ef56d9af58342820ad4eb1a4c01792999f Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Mon, 13 May 2024 11:55:51 +0530 Subject: [PATCH 029/230] remove redundant code. Signed-off-by: Krishna Gupta --- src/styles/index.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/styles/index.ts b/src/styles/index.ts index b1b92869c4b7..2d90d89d2406 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -4333,20 +4333,16 @@ const styles = (theme: ThemeColors) => }, emojiStatusLHN: { - ...(Browser.getBrowser() && {transform: 'scale(.65)', lineHeight: 18, fontSize: 15}), + fontSize: 9, + ...(Browser.getBrowser() && !Browser.isMobile() && {transform: 'scale(.65)', lineHeight: 18, fontSize: 15, overflow: 'visible'}), ...(Browser.getBrowser() && Browser.isSafari() && - !Browser.isMobileSafari() && { + !Browser.isMobile() && { transform: 'scale(0.65)', fontSize: 13, lineHeight: 15, + overflow: 'visible', }), - // transform: 'scale(.65)', - // lineHeight: 15, - // transform: 'scale(.65)', - // lineHeight: 18, - // fontSize: 15, - // overflow: 'visible', }, onboardingVideoPlayer: { From 2263ee97af35ca6ddd2dfdab2e32ddd67190581f Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Tue, 14 May 2024 22:16:29 +0800 Subject: [PATCH 030/230] fix wrong selection after replacing all values --- src/components/MoneyRequestAmountInput.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/components/MoneyRequestAmountInput.tsx b/src/components/MoneyRequestAmountInput.tsx index a59b50e5bdb7..6201df3e8e6f 100644 --- a/src/components/MoneyRequestAmountInput.tsx +++ b/src/components/MoneyRequestAmountInput.tsx @@ -123,6 +123,8 @@ function MoneyRequestAmountInput( }); const forwardDeletePressedRef = useRef(false); + // The ref is used to ignore any onSelectionChange event that happens while we are updating the selection manually in setNewAmount + const willSelectionBeUpdatedManually = useRef(false); /** * Sets the selection and the amount accordingly to the value passed to the input @@ -145,6 +147,7 @@ function MoneyRequestAmountInput( // setCurrentAmount contains another setState(setSelection) making it error-prone since it is leading to setSelection being called twice for a single setCurrentAmount call. This solution introducing the hasSelectionBeenSet flag was chosen for its simplicity and lower risk of future errors https://github.com/Expensify/App/issues/23300#issuecomment-1766314724. + willSelectionBeUpdatedManually.current = true; let hasSelectionBeenSet = false; setCurrentAmount((prevAmount) => { const strippedAmount = MoneyRequestUtils.stripCommaFromAmount(finalAmount); @@ -152,6 +155,7 @@ function MoneyRequestAmountInput( if (!hasSelectionBeenSet) { hasSelectionBeenSet = true; setSelection((prevSelection) => getNewSelection(prevSelection, isForwardDelete ? strippedAmount.length : prevAmount.length, strippedAmount.length)); + willSelectionBeUpdatedManually.current = false; } onAmountChange?.(strippedAmount); return strippedAmount; @@ -266,6 +270,10 @@ function MoneyRequestAmountInput( selectedCurrencyCode={currency} selection={selection} onSelectionChange={(e: NativeSyntheticEvent) => { + if (willSelectionBeUpdatedManually.current) { + willSelectionBeUpdatedManually.current = false; + return; + } if (!shouldUpdateSelection) { return; } From 772d1b2ca03fe1f713a9c88ae5c7ecb13274bbaf Mon Sep 17 00:00:00 2001 From: Someshwar Tripathi Date: Wed, 15 May 2024 02:30:17 +0530 Subject: [PATCH 031/230] Disable write capability for invoice rooms --- src/libs/ReportUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index b8c306b549a7..966db769d070 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -5785,7 +5785,7 @@ function shouldDisableRename(report: OnyxEntry, policy: OnyxEntry, policy: OnyxEntry): boolean { - return PolicyUtils.isPolicyAdmin(policy) && !isAdminRoom(report) && !isArchivedRoom(report) && !isThread(report); + return PolicyUtils.isPolicyAdmin(policy) && !isAdminRoom(report) && !isArchivedRoom(report) && !isThread(report) && !isInvoiceRoom(report); } /** From d8fc20b3c8056f80f0ff498780b8e2b4cd975881 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Fri, 17 May 2024 14:30:07 +0530 Subject: [PATCH 032/230] minor fixes. Signed-off-by: Krishna Gupta --- src/libs/ReportUtils.ts | 47 ++++++++++++++++++----------------------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index e563bbc14500..8454ea5b918c 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -14,7 +14,6 @@ import * as Expensicons from '@components/Icon/Expensicons'; import * as defaultGroupAvatars from '@components/Icon/GroupDefaultAvatars'; import * as defaultWorkspaceAvatars from '@components/Icon/WorkspaceDefaultAvatars'; import type {MoneyRequestAmountInputProps} from '@components/MoneyRequestAmountInput'; -import * as ReportUtils from '@libs/ReportUtils'; import type {IOUAction, IOUType} from '@src/CONST'; import CONST from '@src/CONST'; import type {ParentNavigationSummaryParams, TranslationPaths} from '@src/languages/types'; @@ -2392,6 +2391,14 @@ function isReportFieldOfTypeTitle(reportField: OnyxEntry): bo return reportField?.type === 'formula' && reportField?.fieldID === CONST.REPORT_FIELD_TITLE_FIELD_ID; } +/** + * Check if Report has any held expenses + */ +function isHoldCreator(transaction: OnyxEntry, reportID: string): boolean { + const holdReportAction = ReportActionsUtils.getReportAction(reportID, `${transaction?.comment?.hold ?? ''}`); + return isActionCreator(holdReportAction); +} + /** * Check if report fields are available to use in a report */ @@ -2731,8 +2738,8 @@ function canHoldUnholdReportAction(reportAction: OnyxEntry): {canH return {canHoldRequest: false, canUnholdRequest: false}; } - const isSettled = ReportUtils.isSettled(moneyRequestReport?.reportID); - const isApproved = ReportUtils.isReportApproved(moneyRequestReport); + const isRequestSettled = isSettled(moneyRequestReport?.reportID); + const isApproved = isReportApproved(moneyRequestReport); const transactionID = moneyRequestReport ? reportAction?.originalMessage?.IOUTransactionID : 0; const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? ({} as Transaction); const parentReportActionsKey = `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${moneyRequestReport.parentReportID}`; @@ -2741,23 +2748,22 @@ function canHoldUnholdReportAction(reportAction: OnyxEntry): {canH const parentReportAction = getParentReportAction(parentReportActions, moneyRequestReport?.parentReportActionID); const isRequestIOU = parentReport?.type === 'iou'; - const isHoldCreator = ReportUtils.isHoldCreator(transaction, moneyRequestReport?.reportID) && isRequestIOU; - const isTrackExpenseReport = ReportUtils.isTrackExpenseReport(moneyRequestReport); + const isRequestHoldCreator = isHoldCreator(transaction, moneyRequestReport?.reportID) && isRequestIOU; + const isTrackExpenseMoneyReport = isTrackExpenseReport(moneyRequestReport); const isActionOwner = typeof parentReportAction?.actorAccountID === 'number' && typeof currentUserPersonalDetails?.accountID === 'number' && parentReportAction.actorAccountID === currentUserPersonalDetails?.accountID; - const isApprover = - ReportUtils.isMoneyRequestReport(moneyRequestReport) && moneyRequestReport?.managerID !== null && currentUserPersonalDetails?.accountID === moneyRequestReport?.managerID; + const isApprover = isMoneyRequestReport(moneyRequestReport) && moneyRequestReport?.managerID !== null && currentUserPersonalDetails?.accountID === moneyRequestReport?.managerID; const isOnHold = TransactionUtils.isOnHold(transaction); const isScanning = TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction); - const canModifyStatus = !isTrackExpenseReport && (isPolicyAdmin || isActionOwner || isApprover); + const canModifyStatus = !isTrackExpenseMoneyReport && (isPolicyAdmin || isActionOwner || isApprover); const isDeletedParentAction = ReportActionsUtils.isDeletedAction(parentReportAction); - const canHoldOrUnholdRequest = !isSettled && !isApproved && !isDeletedParentAction; - const canHoldRequest = canHoldOrUnholdRequest && !isOnHold && (isHoldCreator || (!isRequestIOU && canModifyStatus)) && !isScanning; - const canUnholdRequest = Boolean(canHoldOrUnholdRequest && isOnHold && (isHoldCreator || (!isRequestIOU && canModifyStatus))); + const canHoldOrUnholdRequest = !isRequestSettled && !isApproved && !isDeletedParentAction; + const canHoldRequest = canHoldOrUnholdRequest && !isOnHold && (isRequestHoldCreator || (!isRequestIOU && canModifyStatus)) && !isScanning; + const canUnholdRequest = Boolean(canHoldOrUnholdRequest && isOnHold && (isRequestHoldCreator || (!isRequestIOU && canModifyStatus))); return {canHoldRequest, canUnholdRequest}; } @@ -2773,21 +2779,16 @@ const changeMoneyRequestHoldStatus = (reportAction: OnyxEntry): vo return; } - const parentReportActionsKey = `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${moneyRequestReport.parentReportID}`; - const parentReportActions = allReportActions?.[parentReportActionsKey]; - const parentReportAction = getParentReportAction(parentReportActions, moneyRequestReport?.parentReportActionID); - const transactionID = moneyRequestReport ? reportAction?.originalMessage?.IOUTransactionID : 0; + const transactionID = reportAction?.originalMessage?.IOUTransactionID ?? ''; const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? ({} as Transaction); const isOnHold = TransactionUtils.isOnHold(transaction); const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${moneyRequestReport.policyID}`] ?? null; - // const iouTransactionID = parentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? parentReportAction.originalMessage?.IOUTransactionID ?? '' : ''; - if (isOnHold) { - IOU.unholdRequest(transactionID, reportAction.childReportID); + IOU.unholdRequest(transactionID, reportAction.childReportID ?? ''); } else { const activeRoute = encodeURIComponent(Navigation.getActiveRouteWithoutParams()); - Navigation.navigate(ROUTES.MONEY_REQUEST_HOLD_REASON.getRoute(policy?.type ?? CONST.POLICY.TYPE.PERSONAL, transactionID, reportAction.childReportID, activeRoute)); + Navigation.navigate(ROUTES.MONEY_REQUEST_HOLD_REASON.getRoute(policy?.type ?? CONST.POLICY.TYPE.PERSONAL, transactionID, reportAction.childReportID ?? '', activeRoute)); } }; @@ -6294,14 +6295,6 @@ function navigateToPrivateNotes(report: OnyxEntry, session: OnyxEntry, reportID: string): boolean { - const holdReportAction = ReportActionsUtils.getReportAction(reportID, `${transaction?.comment?.hold ?? ''}`); - return isActionCreator(holdReportAction); -} - /** * Get all held transactions of a iouReport */ From 6b33432a88f50a2c7501ebaaade4bafa74dc9a96 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 17 May 2024 19:23:06 +0800 Subject: [PATCH 033/230] don't set pointer events to none for deleted parent action --- src/pages/home/report/ReportActionItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 95bc4f5231d2..2bb5d59f674b 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -933,7 +933,7 @@ function ReportActionItem({ isSmallScreenWidth && DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()} onPressOut={() => ControlSelection.unblock()} onSecondaryInteraction={showPopover} From 4e0968d9a4385d1e9c30c008fce19e11067bcea3 Mon Sep 17 00:00:00 2001 From: tienifr Date: Sat, 18 May 2024 17:13:00 +0700 Subject: [PATCH 034/230] fix Inconsistency while pasting highlighted mention in room and expense description --- src/pages/RoomDescriptionPage.tsx | 1 + src/pages/workspace/WorkspaceNewRoomPage.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/src/pages/RoomDescriptionPage.tsx b/src/pages/RoomDescriptionPage.tsx index 3992dff188e2..2cab7a4ed40b 100644 --- a/src/pages/RoomDescriptionPage.tsx +++ b/src/pages/RoomDescriptionPage.tsx @@ -97,6 +97,7 @@ function RoomDescriptionPage({report, policies}: RoomDescriptionPageProps) { value={description} onChangeText={handleReportDescriptionChange} autoCapitalize="none" + isMarkdownEnabled /> diff --git a/src/pages/workspace/WorkspaceNewRoomPage.tsx b/src/pages/workspace/WorkspaceNewRoomPage.tsx index 5716812ced16..5004bac942cd 100644 --- a/src/pages/workspace/WorkspaceNewRoomPage.tsx +++ b/src/pages/workspace/WorkspaceNewRoomPage.tsx @@ -295,6 +295,7 @@ function WorkspaceNewRoomPage({policies, reports, formState, session, activePoli maxLength={CONST.REPORT_DESCRIPTION.MAX_LENGTH} autoCapitalize="none" shouldInterceptSwipe + isMarkdownEnabled /> From ce10020ffdc791278c1f1a83fcfca44f170d2c30 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Tue, 21 May 2024 12:32:37 +0200 Subject: [PATCH 035/230] Fixing the chat icon navigation --- src/ONYXKEYS.ts | 4 ++++ .../ActiveWorkspaceProvider.tsx | 15 +++++++++--- .../BottomTabBar.tsx | 23 +++++++++++-------- src/libs/actions/Policy.ts | 5 ++++ 4 files changed, 35 insertions(+), 12 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index ddf37fba2354..5d91ddcfa04a 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -75,6 +75,9 @@ const ONYXKEYS = { * rates and units for different workspaces at the same time. */ WORKSPACE_RATE_AND_UNIT: 'workspaceRateAndUnit', + /** Contains the last active workspace ID */ + ACTIVE_WORKSPACE_ID: 'activeWorkspaceID', + /** Contains a list of all currencies available to the user - user can * select a currency based on the list */ CURRENCY_LIST: 'currencyList', @@ -601,6 +604,7 @@ type OnyxValuesMapping = { [ONYXKEYS.PERSONAL_DETAILS_METADATA]: Record; [ONYXKEYS.TASK]: OnyxTypes.Task; [ONYXKEYS.WORKSPACE_RATE_AND_UNIT]: OnyxTypes.WorkspaceRateAndUnit; + [ONYXKEYS.ACTIVE_WORKSPACE_ID]: string | undefined; [ONYXKEYS.CURRENCY_LIST]: OnyxTypes.CurrencyList; [ONYXKEYS.UPDATE_AVAILABLE]: boolean; [ONYXKEYS.SCREEN_SHARE_REQUEST]: OnyxTypes.ScreenShareRequest; diff --git a/src/components/ActiveWorkspace/ActiveWorkspaceProvider.tsx b/src/components/ActiveWorkspace/ActiveWorkspaceProvider.tsx index 884b9a2a2d95..2d1b62ba9a65 100644 --- a/src/components/ActiveWorkspace/ActiveWorkspaceProvider.tsx +++ b/src/components/ActiveWorkspace/ActiveWorkspaceProvider.tsx @@ -1,16 +1,25 @@ -import React, {useMemo, useState} from 'react'; +import React, {useCallback, useMemo, useState} from 'react'; +import * as Policy from '@libs/actions/Policy'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; import ActiveWorkspaceContext from './ActiveWorkspaceContext'; function ActiveWorkspaceContextProvider({children}: ChildrenProps) { - const [activeWorkspaceID, setActiveWorkspaceID] = useState(undefined); + const [activeWorkspaceID, updateActiveWorkspaceID] = useState(undefined); + + const setActiveWorkspaceID = useCallback( + (workspaceID: string | undefined) => { + Policy.setActiveWorkspaceID(workspaceID); + updateActiveWorkspaceID(workspaceID); + }, + [updateActiveWorkspaceID], + ); const value = useMemo( () => ({ activeWorkspaceID, setActiveWorkspaceID, }), - [activeWorkspaceID], + [activeWorkspaceID, setActiveWorkspaceID], ); return {children}; diff --git a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx index 7f26177eeb0f..71da79a2f7cd 100644 --- a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx +++ b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx @@ -1,5 +1,5 @@ import {useNavigation, useNavigationState} from '@react-navigation/native'; -import React, {useEffect} from 'react'; +import React, {useCallback, useEffect} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; @@ -24,20 +24,19 @@ import * as Welcome from '@userActions/Welcome'; import CONST from '@src/CONST'; import NAVIGATORS from '@src/NAVIGATORS'; import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; +import ROUTES, {Route} from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; type PurposeForUsingExpensifyModalOnyxProps = { isLoadingApp: OnyxEntry; + activeWorkspaceID: OnyxEntry; }; type PurposeForUsingExpensifyModalProps = PurposeForUsingExpensifyModalOnyxProps; -function BottomTabBar({isLoadingApp = false}: PurposeForUsingExpensifyModalProps) { +function BottomTabBar({isLoadingApp = false, activeWorkspaceID}: PurposeForUsingExpensifyModalProps) { const theme = useTheme(); const styles = useThemeStyles(); const {translate} = useLocalize(); - const {activeWorkspaceID} = useActiveWorkspace(); - const navigation = useNavigation(); useEffect(() => { @@ -66,15 +65,18 @@ function BottomTabBar({isLoadingApp = false}: PurposeForUsingExpensifyModalProps return topmostBottomTabRoute?.name ?? SCREENS.HOME; }); - const chatTabBrickRoad = getChatTabBrickRoad(activeWorkspaceID); + const chatTabBrickRoad = getChatTabBrickRoad(activeWorkspaceID as string | undefined); + + const navigateToChats = useCallback(() => { + const route = activeWorkspaceID ? (`/w/${activeWorkspaceID}/r` as Route) : ROUTES.HOME; + Navigation.navigate(route); + }, [activeWorkspaceID]); return ( { - Navigation.navigate(ROUTES.HOME); - }} + onPress={navigateToChats} role={CONST.ROLE.BUTTON} accessibilityLabel={translate('common.chats')} wrapperStyle={styles.flex1} @@ -105,4 +107,7 @@ export default withOnyx Date: Tue, 21 May 2024 16:24:24 +0530 Subject: [PATCH 036/230] aligm status emoji to center. Signed-off-by: Krishna Gupta --- .../home/sidebar/AvatarWithOptionalStatus.tsx | 14 ++++++++------ src/styles/index.ts | 4 ++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/pages/home/sidebar/AvatarWithOptionalStatus.tsx b/src/pages/home/sidebar/AvatarWithOptionalStatus.tsx index 609e4044002e..5d4af7ea4961 100644 --- a/src/pages/home/sidebar/AvatarWithOptionalStatus.tsx +++ b/src/pages/home/sidebar/AvatarWithOptionalStatus.tsx @@ -19,12 +19,14 @@ function AvatarWithOptionalStatus({emojiStatus = '', isSelected = false}: Avatar - - {emojiStatus} - + + + {emojiStatus} + + ); diff --git a/src/styles/index.ts b/src/styles/index.ts index 2b3207eee4a8..5f160009d923 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -4335,11 +4335,11 @@ const styles = (theme: ThemeColors) => emojiStatusLHN: { fontSize: 9, - ...(Browser.getBrowser() && !Browser.isMobile() && {transform: 'scale(.65)', lineHeight: 18, fontSize: 15, overflow: 'visible'}), + ...(Browser.getBrowser() && !Browser.isMobile() && {transform: 'scale(.5)', fontSize: 22, overflow: 'visible'}), ...(Browser.getBrowser() && Browser.isSafari() && !Browser.isMobile() && { - transform: 'scale(0.65)', + transform: 'scale(0.7)', fontSize: 13, lineHeight: 15, overflow: 'visible', From 9f330e4d6f101b5cb6c6bffc3c28b847bed73af3 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Tue, 21 May 2024 18:38:16 +0530 Subject: [PATCH 037/230] canHoldUnholdReportAction util function refactoring. Signed-off-by: Krishna Gupta --- src/libs/ReportUtils.ts | 21 ++++--------------- .../report/ContextMenu/ContextMenuActions.tsx | 2 -- 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 8454ea5b918c..23874dc2da13 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2715,26 +2715,15 @@ function canEditReportAction(reportAction: OnyxEntry): boolean { ); } -function getParentReportAction(parentReportActions: ReportActions | null | undefined, parentReportActionID: string | undefined): ReportAction | null { - if (!parentReportActions || !parentReportActionID) { - return null; - } - return parentReportActions[parentReportActionID ?? '0']; -} - function canHoldUnholdReportAction(reportAction: OnyxEntry): {canHoldRequest: boolean; canUnholdRequest: boolean} { if (reportAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.IOU) { return {canHoldRequest: false, canUnholdRequest: false}; } - const moneyRequestReportID = reportAction?.originalMessage?.IOUReportID ?? 0; - - if (!moneyRequestReportID) { - return {canHoldRequest: false, canUnholdRequest: false}; - } + const moneyRequestReportID = reportAction?.originalMessage?.IOUReportID ?? 0; const moneyRequestReport = getReport(String(moneyRequestReportID)); - if (!moneyRequestReport) { + if (!moneyRequestReportID || !moneyRequestReport) { return {canHoldRequest: false, canUnholdRequest: false}; } @@ -2742,10 +2731,8 @@ function canHoldUnholdReportAction(reportAction: OnyxEntry): {canH const isApproved = isReportApproved(moneyRequestReport); const transactionID = moneyRequestReport ? reportAction?.originalMessage?.IOUTransactionID : 0; const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? ({} as Transaction); - const parentReportActionsKey = `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${moneyRequestReport.parentReportID}`; - const parentReportActions = allReportActions?.[parentReportActionsKey]; const parentReport = getReport(String(moneyRequestReport.parentReportID)); - const parentReportAction = getParentReportAction(parentReportActions, moneyRequestReport?.parentReportActionID); + const parentReportAction = ReportActionsUtils.getParentReportAction(moneyRequestReport); const isRequestIOU = parentReport?.type === 'iou'; const isRequestHoldCreator = isHoldCreator(transaction, moneyRequestReport?.reportID) && isRequestIOU; @@ -2759,7 +2746,7 @@ function canHoldUnholdReportAction(reportAction: OnyxEntry): {canH const isScanning = TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction); const canModifyStatus = !isTrackExpenseMoneyReport && (isPolicyAdmin || isActionOwner || isApprover); - const isDeletedParentAction = ReportActionsUtils.isDeletedAction(parentReportAction); + const isDeletedParentAction = !isEmpty(parentReportAction) ? ReportActionsUtils.isDeletedAction(parentReportAction as ReportAction) : true; const canHoldOrUnholdRequest = !isRequestSettled && !isApproved && !isDeletedParentAction; const canHoldRequest = canHoldOrUnholdRequest && !isOnHold && (isRequestHoldCreator || (!isRequestIOU && canModifyStatus)) && !isScanning; diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx index 5d5b6c070eb0..5ccdefce0646 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx @@ -262,7 +262,6 @@ const ContextMenuActions: ContextMenuAction[] = [ shouldShow: (type, reportAction) => type === CONST.CONTEXT_MENU_TYPES.REPORT_ACTION && ReportUtils.canEditReportAction(reportAction) && ReportUtils.canHoldUnholdReportAction(reportAction).canUnholdRequest, onPress: (closePopover, {reportAction}) => { - // const hold=ReportUtils.changeMoneyRequestHoldStatus(). if (closePopover) { hideContextMenu(false, () => ReportUtils.changeMoneyRequestHoldStatus(reportAction)); return; @@ -280,7 +279,6 @@ const ContextMenuActions: ContextMenuAction[] = [ shouldShow: (type, reportAction) => type === CONST.CONTEXT_MENU_TYPES.REPORT_ACTION && ReportUtils.canEditReportAction(reportAction) && ReportUtils.canHoldUnholdReportAction(reportAction).canHoldRequest, onPress: (closePopover, {reportAction}) => { - // const hold=ReportUtils.changeMoneyRequestHoldStatus(). if (closePopover) { hideContextMenu(false, () => ReportUtils.changeMoneyRequestHoldStatus(reportAction)); return; From 0fac245e625ef26324c3b32c52f259698906dfa9 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Wed, 22 May 2024 02:21:04 +0530 Subject: [PATCH 038/230] add getParentReportAction in ReportUtils. Signed-off-by: Krishna Gupta --- src/libs/ReportUtils.ts | 15 +++++++++++++-- src/pages/home/ReportScreen.tsx | 13 +++---------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 23874dc2da13..d5f0cc8a3158 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2715,6 +2715,13 @@ function canEditReportAction(reportAction: OnyxEntry): boolean { ); } +function getParentReportAction(parentReportActions: OnyxEntry, parentReportActionID: string | undefined): OnyxEntry { + if (!parentReportActions || !parentReportActionID) { + return null; + } + return parentReportActions[parentReportActionID ?? '0']; +} + function canHoldUnholdReportAction(reportAction: OnyxEntry): {canHoldRequest: boolean; canUnholdRequest: boolean} { if (reportAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.IOU) { return {canHoldRequest: false, canUnholdRequest: false}; @@ -2731,8 +2738,11 @@ function canHoldUnholdReportAction(reportAction: OnyxEntry): {canH const isApproved = isReportApproved(moneyRequestReport); const transactionID = moneyRequestReport ? reportAction?.originalMessage?.IOUTransactionID : 0; const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? ({} as Transaction); + + const parentReportActionsKey = `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${moneyRequestReport.parentReportID}`; + const parentReportActions = allReportActions?.[parentReportActionsKey]; const parentReport = getReport(String(moneyRequestReport.parentReportID)); - const parentReportAction = ReportActionsUtils.getParentReportAction(moneyRequestReport); + const parentReportAction = getParentReportAction(parentReportActions ?? {}, moneyRequestReport?.parentReportActionID); const isRequestIOU = parentReport?.type === 'iou'; const isRequestHoldCreator = isHoldCreator(transaction, moneyRequestReport?.reportID) && isRequestIOU; @@ -2746,7 +2756,7 @@ function canHoldUnholdReportAction(reportAction: OnyxEntry): {canH const isScanning = TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction); const canModifyStatus = !isTrackExpenseMoneyReport && (isPolicyAdmin || isActionOwner || isApprover); - const isDeletedParentAction = !isEmpty(parentReportAction) ? ReportActionsUtils.isDeletedAction(parentReportAction as ReportAction) : true; + const isDeletedParentAction = ReportActionsUtils.isDeletedAction(parentReportAction); const canHoldOrUnholdRequest = !isRequestSettled && !isApproved && !isDeletedParentAction; const canHoldRequest = canHoldOrUnholdRequest && !isOnHold && (isRequestHoldCreator || (!isRequestIOU && canModifyStatus)) && !isScanning; @@ -6847,6 +6857,7 @@ export { getOriginalReportID, getOutstandingChildRequest, getParentNavigationSubtitle, + getParentReportAction, getParsedComment, getParticipantAccountIDs, getParticipants, diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 8620d8d4866e..d55f5f42b34d 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -124,13 +124,6 @@ function isEmpty(report: OnyxTypes.Report): boolean { return !Object.values(report).some((value) => value !== undefined && value !== ''); } -function getParentReportAction(parentReportActions: OnyxEntry, parentReportActionID: string | undefined): OnyxEntry { - if (!parentReportActions || !parentReportActionID) { - return null; - } - return parentReportActions[parentReportActionID ?? '0']; -} - function ReportScreen({ betas = [], route, @@ -261,7 +254,7 @@ function ReportScreen({ ], ); - const parentReportAction = useMemo(() => getParentReportAction(parentReportActions, report?.parentReportActionID), [parentReportActions, report.parentReportActionID]); + const parentReportAction = useMemo(() => ReportUtils.getParentReportAction(parentReportActions, report?.parentReportActionID), [parentReportActions, report.parentReportActionID]); const prevReport = usePrevious(report); const prevUserLeavingStatus = usePrevious(userLeavingStatus); @@ -813,8 +806,8 @@ export default withCurrentReportID( }, })( memo(ReportScreen, (prevProps, nextProps) => { - const prevParentReportAction = getParentReportAction(prevProps.parentReportActions, prevProps.report?.parentReportActionID); - const nextParentReportAction = getParentReportAction(nextProps.parentReportActions, nextProps.report?.parentReportActionID); + const prevParentReportAction = ReportUtils.getParentReportAction(prevProps.parentReportActions, prevProps.report?.parentReportActionID); + const nextParentReportAction = ReportUtils.getParentReportAction(nextProps.parentReportActions, nextProps.report?.parentReportActionID); return ( prevProps.isSidebarLoaded === nextProps.isSidebarLoaded && lodashIsEqual(prevProps.sortedAllReportActions, nextProps.sortedAllReportActions) && From 549e647063a14d8435aaf5dd2df13e930e9b929d Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Fri, 24 May 2024 12:32:38 +0200 Subject: [PATCH 039/230] Show all policies in the workspace list --- src/libs/PolicyUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 6a26b1a6cfc2..37dafafb3e5e 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -116,7 +116,7 @@ function getPolicyBrickRoadIndicatorStatus(policy: OnyxEntry): ValueOf, isOffline: boolean): boolean { return ( !!policy && - (policy?.isPolicyExpenseChatEnabled || Boolean(policy?.isJoinRequestPending)) && + Boolean(policy?.isJoinRequestPending) && (isOffline || policy?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || Object.keys(policy.errors ?? {}).length > 0) ); } From ee6a0129eb5c6ff06d8e547a81787711b84e5214 Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Fri, 24 May 2024 12:32:52 +0200 Subject: [PATCH 040/230] Do not show personal policies in the list --- src/libs/PolicyUtils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 37dafafb3e5e..a1dc5534df8c 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -116,6 +116,7 @@ function getPolicyBrickRoadIndicatorStatus(policy: OnyxEntry): ValueOf, isOffline: boolean): boolean { return ( !!policy && + policy?.type !== CONST.POLICY.TYPE.PERSONAL && Boolean(policy?.isJoinRequestPending) && (isOffline || policy?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || Object.keys(policy.errors ?? {}).length > 0) ); From 8b2b02e411703c5e43633baea5994935787a4d39 Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Fri, 24 May 2024 12:50:10 +0200 Subject: [PATCH 041/230] Remove the isPolicyExpenseChatEnabled check from the AccessOrNotFoundPage --- src/libs/PolicyUtils.ts | 4 ++-- src/pages/workspace/AccessOrNotFoundWrapper.tsx | 2 +- src/pages/workspace/WorkspacesListPage.tsx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 021dbb5abcff..b5092f8244eb 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -116,8 +116,8 @@ function getPolicyBrickRoadIndicatorStatus(policy: OnyxEntry): ValueOf, isOffline: boolean): boolean { return ( !!policy && - policy?.type !== CONST.POLICY.TYPE.PERSONAL && - Boolean(policy?.isJoinRequestPending) && + (policy?.type !== CONST.POLICY.TYPE.PERSONAL || + Boolean(policy?.isJoinRequestPending)) && (isOffline || policy?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || Object.keys(policy.errors ?? {}).length > 0) ); } diff --git a/src/pages/workspace/AccessOrNotFoundWrapper.tsx b/src/pages/workspace/AccessOrNotFoundWrapper.tsx index b90a9cb38151..da33238793df 100644 --- a/src/pages/workspace/AccessOrNotFoundWrapper.tsx +++ b/src/pages/workspace/AccessOrNotFoundWrapper.tsx @@ -18,7 +18,7 @@ import callOrReturn from '@src/types/utils/callOrReturn'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; const POLICY_ACCESS_VARIANTS = { - [CONST.POLICY.ACCESS_VARIANTS.PAID]: (policy: OnyxEntry) => PolicyUtils.isPaidGroupPolicy(policy) && !!policy?.isPolicyExpenseChatEnabled, + [CONST.POLICY.ACCESS_VARIANTS.PAID]: (policy: OnyxEntry) => PolicyUtils.isPaidGroupPolicy(policy), [CONST.POLICY.ACCESS_VARIANTS.ADMIN]: (policy: OnyxEntry) => PolicyUtils.isPolicyAdmin(policy), } as const satisfies Record boolean>; diff --git a/src/pages/workspace/WorkspacesListPage.tsx b/src/pages/workspace/WorkspacesListPage.tsx index 60e471deb328..4028f2f18341 100755 --- a/src/pages/workspace/WorkspacesListPage.tsx +++ b/src/pages/workspace/WorkspacesListPage.tsx @@ -309,7 +309,7 @@ function WorkspacesListPage({policies, reimbursementAccount, reports, session}: if (isEmptyObject(policies)) { return []; } - + console.log("policies: ", policies); return Object.values(policies) .filter((policy): policy is PolicyType => PolicyUtils.shouldShowPolicy(policy, !!isOffline)) .map((policy): WorkspaceItem => { From ed7f2f719e44fa4a07eee9926902518df6d4103b Mon Sep 17 00:00:00 2001 From: Cong Pham Date: Wed, 15 May 2024 23:36:53 +0700 Subject: [PATCH 042/230] handle login scroll on virtual viewport --- src/components/ScreenWrapper.tsx | 2 +- src/pages/signin/LoginForm/BaseLoginForm.tsx | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/components/ScreenWrapper.tsx b/src/components/ScreenWrapper.tsx index e53823860ce0..45326ec822df 100644 --- a/src/components/ScreenWrapper.tsx +++ b/src/components/ScreenWrapper.tsx @@ -202,7 +202,7 @@ function ScreenWrapper( // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - const isAvoidingViewportScroll = useTackInputFocus(shouldEnableMaxHeight && shouldAvoidScrollOnVirtualViewport && Browser.isMobileSafari()); + const isAvoidingViewportScroll = useTackInputFocus(shouldEnableMaxHeight && shouldAvoidScrollOnVirtualViewport && Browser.isMobileWebKit()); const contextValue = useMemo(() => ({didScreenTransitionEnd}), [didScreenTransitionEnd]); return ( diff --git a/src/pages/signin/LoginForm/BaseLoginForm.tsx b/src/pages/signin/LoginForm/BaseLoginForm.tsx index 4286a2603341..310819cd7365 100644 --- a/src/pages/signin/LoginForm/BaseLoginForm.tsx +++ b/src/pages/signin/LoginForm/BaseLoginForm.tsx @@ -2,7 +2,7 @@ import {useIsFocused} from '@react-navigation/native'; import Str from 'expensify-common/lib/str'; import type {ForwardedRef} from 'react'; import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react'; -import {View} from 'react-native'; +import {InteractionManager, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import DotIndicatorMessage from '@components/DotIndicatorMessage'; @@ -35,6 +35,8 @@ import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import type {CloseAccountForm} from '@src/types/form'; import type {Account, Credentials} from '@src/types/onyx'; +import htmlDivElementRef from '@src/types/utils/htmlDivElementRef'; +import viewRef from '@src/types/utils/viewRef'; import type LoginFormProps from './types'; import type {InputHandle} from './types'; @@ -216,6 +218,13 @@ function BaseLoginForm({account, credentials, closeAccount, blurOnSubmit = false const serverErrorText = useMemo(() => (account ? ErrorUtils.getLatestErrorMessage(account) : ''), [account]); const shouldShowServerError = !!serverErrorText && !formError; + const submitContainerRef = useRef(null); + const handleFocus = useCallback(() => { + InteractionManager.runAfterInteractions(() => { + htmlDivElementRef(submitContainerRef).current?.scrollIntoView?.({behavior: 'smooth', block: 'end'}); + }); + }, []); + return ( <> + Date: Fri, 24 May 2024 21:51:08 +0530 Subject: [PATCH 043/230] hide overflowing emoji from emoji status container. Signed-off-by: Krishna Gupta --- src/styles/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/styles/index.ts b/src/styles/index.ts index 192b2779e05d..e567026a2e7b 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -4380,6 +4380,7 @@ const styles = (theme: ThemeColors) => bottom: -4, borderColor: theme.highlightBG, borderWidth: 2, + overflow: 'hidden', }, moneyRequestViewImage: { ...spacing.mh5, From 1a6199d7cf10a531ab60ce236a44e27e2cd836ae Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Tue, 28 May 2024 10:50:29 +0200 Subject: [PATCH 044/230] Use session storage to save the current active workspace --- src/CONST.ts | 1 + src/ONYXKEYS.ts | 4 ---- .../ActiveWorkspace/ActiveWorkspaceProvider.tsx | 17 +++++++++-------- .../BottomTabBar.tsx | 8 ++------ 4 files changed, 12 insertions(+), 18 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index d2be946cdb2b..8c27a61e9776 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -4740,6 +4740,7 @@ const CONST = { SESSION_STORAGE_KEYS: { INITIAL_URL: 'INITIAL_URL', + ACTIVE_WORKSPACE_ID: 'ACTIVE_WORKSPACE_ID', }, DOT_SEPARATOR: '•', diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 4e858e32495c..e669d4740f98 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -75,9 +75,6 @@ const ONYXKEYS = { * rates and units for different workspaces at the same time. */ WORKSPACE_RATE_AND_UNIT: 'workspaceRateAndUnit', - /** Contains the last active workspace ID */ - ACTIVE_WORKSPACE_ID: 'activeWorkspaceID', - /** Contains a list of all currencies available to the user - user can * select a currency based on the list */ CURRENCY_LIST: 'currencyList', @@ -607,7 +604,6 @@ type OnyxValuesMapping = { [ONYXKEYS.PERSONAL_DETAILS_METADATA]: Record; [ONYXKEYS.TASK]: OnyxTypes.Task; [ONYXKEYS.WORKSPACE_RATE_AND_UNIT]: OnyxTypes.WorkspaceRateAndUnit; - [ONYXKEYS.ACTIVE_WORKSPACE_ID]: string | undefined; [ONYXKEYS.CURRENCY_LIST]: OnyxTypes.CurrencyList; [ONYXKEYS.UPDATE_AVAILABLE]: boolean; [ONYXKEYS.SCREEN_SHARE_REQUEST]: OnyxTypes.ScreenShareRequest; diff --git a/src/components/ActiveWorkspace/ActiveWorkspaceProvider.tsx b/src/components/ActiveWorkspace/ActiveWorkspaceProvider.tsx index 2d1b62ba9a65..419dbef5d171 100644 --- a/src/components/ActiveWorkspace/ActiveWorkspaceProvider.tsx +++ b/src/components/ActiveWorkspace/ActiveWorkspaceProvider.tsx @@ -1,18 +1,19 @@ import React, {useCallback, useMemo, useState} from 'react'; -import * as Policy from '@libs/actions/Policy'; +import CONST from '@src/CONST'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; import ActiveWorkspaceContext from './ActiveWorkspaceContext'; function ActiveWorkspaceContextProvider({children}: ChildrenProps) { const [activeWorkspaceID, updateActiveWorkspaceID] = useState(undefined); - const setActiveWorkspaceID = useCallback( - (workspaceID: string | undefined) => { - Policy.setActiveWorkspaceID(workspaceID); - updateActiveWorkspaceID(workspaceID); - }, - [updateActiveWorkspaceID], - ); + const setActiveWorkspaceID = useCallback((workspaceID: string | undefined) => { + updateActiveWorkspaceID(workspaceID); + if (workspaceID) { + sessionStorage.setItem(CONST.SESSION_STORAGE_KEYS.ACTIVE_WORKSPACE_ID, workspaceID); + } else { + sessionStorage.removeItem(CONST.SESSION_STORAGE_KEYS.ACTIVE_WORKSPACE_ID); + } + }, []); const value = useMemo( () => ({ diff --git a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx index 71da79a2f7cd..3e48b89ad5c0 100644 --- a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx +++ b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx @@ -7,7 +7,6 @@ import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import {PressableWithFeedback} from '@components/Pressable'; import Tooltip from '@components/Tooltip'; -import useActiveWorkspace from '@hooks/useActiveWorkspace'; import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -29,15 +28,15 @@ import SCREENS from '@src/SCREENS'; type PurposeForUsingExpensifyModalOnyxProps = { isLoadingApp: OnyxEntry; - activeWorkspaceID: OnyxEntry; }; type PurposeForUsingExpensifyModalProps = PurposeForUsingExpensifyModalOnyxProps; -function BottomTabBar({isLoadingApp = false, activeWorkspaceID}: PurposeForUsingExpensifyModalProps) { +function BottomTabBar({isLoadingApp = false}: PurposeForUsingExpensifyModalProps) { const theme = useTheme(); const styles = useThemeStyles(); const {translate} = useLocalize(); const navigation = useNavigation(); + const activeWorkspaceID = sessionStorage.getItem(CONST.SESSION_STORAGE_KEYS.ACTIVE_WORKSPACE_ID); useEffect(() => { const navigationState = navigation.getState() as State | undefined; @@ -107,7 +106,4 @@ export default withOnyx Date: Wed, 29 May 2024 01:35:15 +0700 Subject: [PATCH 045/230] fix cannot add new line using enter --- src/CONST.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/CONST.ts b/src/CONST.ts index d2be946cdb2b..02de81e61aa4 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -3386,6 +3386,8 @@ const CONST = { * @deprecated Please stop using the accessibilityRole prop and use the role prop instead. */ IMAGE: 'image', + + TEXTBOX: 'textbox', }, /** * Acceptable values for the `role` attribute on react native components. From 5e206959fb7c2fe723c892ff7e30f840ed7f1d58 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Wed, 29 May 2024 09:38:05 +0100 Subject: [PATCH 046/230] fix: allTagLevels required violation showing on tags that are already filled --- src/components/ReportActionItem/MoneyRequestView.tsx | 2 ++ src/hooks/useViolations.ts | 11 ++++++++--- src/types/onyx/TransactionViolation.ts | 1 + 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index d2b92b9a0c41..73844630e2cc 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -479,6 +479,7 @@ function MoneyRequestView({ getErrorForField('tag', { tagListIndex: index, tagListName: name, + tagListValue: TransactionUtils.getTagForDisplay(transaction, index), policyHasDependentTags: PolicyUtils.hasDependentTags(policy, policyTagList), }) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR @@ -487,6 +488,7 @@ function MoneyRequestView({ errorText={getErrorForField('tag', { tagListIndex: index, tagListName: name, + tagListValue: TransactionUtils.getTagForDisplay(transaction, index), policyHasDependentTags: PolicyUtils.hasDependentTags(policy, policyTagList), })} /> diff --git a/src/hooks/useViolations.ts b/src/hooks/useViolations.ts index 582d997f436a..7dad3be026fe 100644 --- a/src/hooks/useViolations.ts +++ b/src/hooks/useViolations.ts @@ -67,7 +67,7 @@ function useViolations(violations: TransactionViolation[]) { // someTagLevelsRequired has special logic becase data.errorIndexes is a bit unique in how it denotes the tag list that has the violation // tagListIndex can be 0 so we compare with undefined - if (currentViolations[0]?.name === 'someTagLevelsRequired' && data?.tagListIndex !== undefined && Array.isArray(currentViolations[0]?.data?.errorIndexes)) { + if (currentViolations[0]?.name === CONST.VIOLATIONS.SOME_TAG_LEVELS_REQUIRED && data?.tagListIndex !== undefined && Array.isArray(currentViolations[0]?.data?.errorIndexes)) { return currentViolations .filter((violation) => violation.data?.errorIndexes?.includes(data?.tagListIndex ?? -1)) .map((violation) => ({ @@ -81,7 +81,7 @@ function useViolations(violations: TransactionViolation[]) { // missingTag has special logic for policies with dependent tags, because only one violation is returned for all tags // when no tags are present, so the tag name isn't set in the violation data. That's why we add it here - if (data?.policyHasDependentTags && currentViolations[0]?.name === 'missingTag' && data?.tagListName) { + if (data?.policyHasDependentTags && currentViolations[0]?.name === CONST.VIOLATIONS.MISSING_TAG && data?.tagListName) { return [ { ...currentViolations[0], @@ -94,7 +94,12 @@ function useViolations(violations: TransactionViolation[]) { } // tagOutOfPolicy has special logic because we have to account for multi-level tags and use tagName to find the right tag to put the violation on - if (currentViolations[0]?.name === 'tagOutOfPolicy' && data?.tagListName !== undefined && currentViolations[0]?.data?.tagName) { + if (currentViolations[0]?.name === CONST.VIOLATIONS.TAG_OUT_OF_POLICY && data?.tagListName !== undefined && currentViolations[0]?.data?.tagName) { + return currentViolations.filter((violation) => violation.data?.tagName === data?.tagListName); + } + + // allTagLevelsRequired has special logic because we have to account for tags that are already filled + if (currentViolations[0]?.name === CONST.VIOLATIONS.ALL_TAG_LEVELS_REQUIRED && data?.tagListValue) { return currentViolations.filter((violation) => violation.data?.tagName === data?.tagListName); } diff --git a/src/types/onyx/TransactionViolation.ts b/src/types/onyx/TransactionViolation.ts index 3dfa5fc8ba42..99ce3d86dfe1 100644 --- a/src/types/onyx/TransactionViolation.ts +++ b/src/types/onyx/TransactionViolation.ts @@ -29,6 +29,7 @@ type TransactionViolation = { taxName?: string; tagListIndex?: number; tagListName?: string; + tagListValue?: string; errorIndexes?: number[]; pendingPattern?: boolean; }; From 6ae01c121e425f40d221567bac76e7a242750621 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Thu, 30 May 2024 00:04:24 +0200 Subject: [PATCH 047/230] Removing the openWalletPage call from the SettlementButton component --- src/components/SettlementButton.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/components/SettlementButton.tsx b/src/components/SettlementButton.tsx index f56c4dd1a863..945f8be1f327 100644 --- a/src/components/SettlementButton.tsx +++ b/src/components/SettlementButton.tsx @@ -143,10 +143,6 @@ function SettlementButton({ const {translate} = useLocalize(); const {isOffline} = useNetwork(); - useEffect(() => { - PaymentMethods.openWalletPage(); - }, []); - const session = useSession(); const chatReport = ReportUtils.getReport(chatReportID); const isPaidGroupPolicy = ReportUtils.isPaidGroupPolicyExpenseChat(chatReport); From e7196bba4a224e6e5fc39eebc901e56ef7de1009 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Thu, 30 May 2024 00:34:10 +0200 Subject: [PATCH 048/230] cleaning --- src/components/SettlementButton.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/SettlementButton.tsx b/src/components/SettlementButton.tsx index 945f8be1f327..a994c4df8ab5 100644 --- a/src/components/SettlementButton.tsx +++ b/src/components/SettlementButton.tsx @@ -1,4 +1,4 @@ -import React, {useEffect, useMemo} from 'react'; +import React, {useMemo} from 'react'; import type {GestureResponderEvent, StyleProp, ViewStyle} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; @@ -8,7 +8,6 @@ import * as ReportUtils from '@libs/ReportUtils'; import playSound, {SOUNDS} from '@libs/Sound'; import * as BankAccounts from '@userActions/BankAccounts'; import * as IOU from '@userActions/IOU'; -import * as PaymentMethods from '@userActions/PaymentMethods'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Route} from '@src/ROUTES'; From b3f49a50777fd81e34c37d2e414ec01af7d241ba Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 30 May 2024 10:33:51 +0700 Subject: [PATCH 049/230] fix: IOU - Currency listing page displays empty when clicked on currency --- src/hooks/useArrowKeyFocusManager.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/hooks/useArrowKeyFocusManager.ts b/src/hooks/useArrowKeyFocusManager.ts index 28a137656cfa..0927c0915716 100644 --- a/src/hooks/useArrowKeyFocusManager.ts +++ b/src/hooks/useArrowKeyFocusManager.ts @@ -1,6 +1,7 @@ import {useCallback, useEffect, useMemo, useState} from 'react'; import CONST from '@src/CONST'; import useKeyboardShortcut from './useKeyboardShortcut'; +import usePrevious from './usePrevious'; type Config = { maxIndex: number; @@ -51,6 +52,7 @@ export default function useArrowKeyFocusManager({ isFocused = true, }: Config): UseArrowKeyFocusManager { const [focusedIndex, setFocusedIndex] = useState(initialFocusedIndex); + const prevIsFocusedIndex = usePrevious(focusedIndex); const arrowConfig = useMemo( () => ({ excludedNodes: shouldExcludeTextAreaNodes ? ['TEXTAREA'] : [], @@ -67,8 +69,13 @@ export default function useArrowKeyFocusManager({ [isActive, shouldExcludeTextAreaNodes, allowHorizontalArrowKeys], ); - // eslint-disable-next-line react-hooks/exhaustive-deps - useEffect(() => onFocusedIndexChange(focusedIndex), [focusedIndex]); + useEffect(() => { + if (prevIsFocusedIndex === focusedIndex) { + return; + } + onFocusedIndexChange(focusedIndex); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [focusedIndex, prevIsFocusedIndex]); const arrowUpCallback = useCallback(() => { if (maxIndex < 0 || !isFocused) { From 4ce8552316cedfa5c1a6ca6f58d58e92127f4a48 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 31 May 2024 10:56:01 +0700 Subject: [PATCH 050/230] fix: Custom name user searched with email id shows no results found --- .../WorkspaceWorkflowsApproverPage.tsx | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/pages/workspace/workflows/WorkspaceWorkflowsApproverPage.tsx b/src/pages/workspace/workflows/WorkspaceWorkflowsApproverPage.tsx index 6ddc53572d86..585623c4c90c 100644 --- a/src/pages/workspace/workflows/WorkspaceWorkflowsApproverPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceWorkflowsApproverPage.tsx @@ -68,6 +68,29 @@ function WorkspaceWorkflowsApproverPage({policy, personalDetails, isLoadingRepor Log.hmmm(`[WorkspaceMembersPage] no personal details found for policy member with accountID: ${accountID}`); return; } + const searchValue = OptionsListUtils.getSearchValueForPhoneOrEmail(searchTerm); + if (searchValue.trim()) { + let memberDetails = ''; + if (details.login) { + memberDetails += ` ${details.login.toLowerCase()}`; + } + if (details.firstName) { + memberDetails += ` ${details.firstName.toLowerCase()}`; + } + if (details.lastName) { + memberDetails += ` ${details.lastName.toLowerCase()}`; + } + if (details.displayName) { + memberDetails += ` ${PersonalDetailsUtils.getDisplayNameOrDefault(details).toLowerCase()}`; + } + if (details.phoneNumber) { + memberDetails += ` ${details.phoneNumber.toLowerCase()}`; + } + + if (!OptionsListUtils.isSearchStringMatch(searchValue.trim(), memberDetails)) { + return; + } + } const isOwner = policy?.owner === details.login; const isAdmin = policyEmployee.role === CONST.POLICY.ROLE.ADMIN; @@ -104,20 +127,16 @@ function WorkspaceWorkflowsApproverPage({policy, personalDetails, isLoadingRepor } }); return [policyMemberDetails, approverDetails]; - }, [personalDetails, translate, policy?.approver, isDeletedPolicyEmployee, policy?.owner, policy?.employeeList]); + }, [policy?.employeeList, policy?.owner, policy?.approver, isDeletedPolicyEmployee, personalDetails, searchTerm, translate]); const sections: MembersSection[] = useMemo(() => { const sectionsArray: MembersSection[] = []; if (searchTerm !== '') { - const filteredOptions = [...formattedApprover, ...formattedPolicyEmployeeList].filter((option) => { - const searchValue = OptionsListUtils.getSearchValueForPhoneOrEmail(searchTerm); - return !!option.text?.toLowerCase().includes(searchValue) || !!option.login?.toLowerCase().includes(searchValue); - }); return [ { title: undefined, - data: filteredOptions, + data: [...formattedApprover, ...formattedPolicyEmployeeList], shouldShow: true, }, ]; From e978ac4ef66927fd993f95a66adf2017574cd601 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 31 May 2024 12:06:07 +0700 Subject: [PATCH 051/230] fix: add new function utils --- src/libs/OptionsListUtils.ts | 21 +++++++++++++++++ src/pages/RoomMembersPage.tsx | 23 ++----------------- .../WorkspaceWorkflowsApproverPage.tsx | 23 ++----------------- 3 files changed, 25 insertions(+), 42 deletions(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index f321c10c686e..cdc70dcde9c6 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -582,6 +582,26 @@ function getAlternateText(option: ReportUtils.OptionData, {showChatPreviewLine = : LocalePhoneNumber.formatPhoneNumber(option.participantsList && option.participantsList.length > 0 ? option.participantsList[0].login ?? '' : ''); } +function isUserMatchWithSearch(personalDetail: PersonalDetails, searchValue: string) { + let memberDetails = ''; + if (personalDetail.login) { + memberDetails += ` ${personalDetail.login.toLowerCase()}`; + } + if (personalDetail.firstName) { + memberDetails += ` ${personalDetail.firstName.toLowerCase()}`; + } + if (personalDetail.lastName) { + memberDetails += ` ${personalDetail.lastName.toLowerCase()}`; + } + if (personalDetail.displayName) { + memberDetails += ` ${PersonalDetailsUtils.getDisplayNameOrDefault(personalDetail).toLowerCase()}`; + } + if (personalDetail.phoneNumber) { + memberDetails += ` ${personalDetail.phoneNumber.toLowerCase()}`; + } + return isSearchStringMatch(searchValue.trim(), memberDetails); +} + /** * Get the last message text from the report directly or from other sources for special cases. */ @@ -2436,6 +2456,7 @@ export { getPersonalDetailsForAccountIDs, getIOUConfirmationOptionsFromPayeePersonalDetail, getSearchText, + isUserMatchWithSearch, getAllReportErrors, getPolicyExpenseReportOption, getParticipantsOption, diff --git a/src/pages/RoomMembersPage.tsx b/src/pages/RoomMembersPage.tsx index aefd2603e859..fed8d63fbd51 100644 --- a/src/pages/RoomMembersPage.tsx +++ b/src/pages/RoomMembersPage.tsx @@ -183,27 +183,8 @@ function RoomMembersPage({report, session, policies}: RoomMembersPageProps) { } // If search value is provided, filter out members that don't match the search value - if (searchValue.trim()) { - let memberDetails = ''; - if (details.login) { - memberDetails += ` ${details.login.toLowerCase()}`; - } - if (details.firstName) { - memberDetails += ` ${details.firstName.toLowerCase()}`; - } - if (details.lastName) { - memberDetails += ` ${details.lastName.toLowerCase()}`; - } - if (details.displayName) { - memberDetails += ` ${PersonalDetailsUtils.getDisplayNameOrDefault(details).toLowerCase()}`; - } - if (details.phoneNumber) { - memberDetails += ` ${details.phoneNumber.toLowerCase()}`; - } - - if (!OptionsListUtils.isSearchStringMatch(searchValue.trim(), memberDetails)) { - return; - } + if (searchValue.trim() && !OptionsListUtils.isUserMatchWithSearch(details, searchValue)) { + return; } const pendingChatMember = report?.pendingChatMembers?.findLast((member) => member.accountID === accountID.toString()); diff --git a/src/pages/workspace/workflows/WorkspaceWorkflowsApproverPage.tsx b/src/pages/workspace/workflows/WorkspaceWorkflowsApproverPage.tsx index 585623c4c90c..04d39c489bf3 100644 --- a/src/pages/workspace/workflows/WorkspaceWorkflowsApproverPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceWorkflowsApproverPage.tsx @@ -69,27 +69,8 @@ function WorkspaceWorkflowsApproverPage({policy, personalDetails, isLoadingRepor return; } const searchValue = OptionsListUtils.getSearchValueForPhoneOrEmail(searchTerm); - if (searchValue.trim()) { - let memberDetails = ''; - if (details.login) { - memberDetails += ` ${details.login.toLowerCase()}`; - } - if (details.firstName) { - memberDetails += ` ${details.firstName.toLowerCase()}`; - } - if (details.lastName) { - memberDetails += ` ${details.lastName.toLowerCase()}`; - } - if (details.displayName) { - memberDetails += ` ${PersonalDetailsUtils.getDisplayNameOrDefault(details).toLowerCase()}`; - } - if (details.phoneNumber) { - memberDetails += ` ${details.phoneNumber.toLowerCase()}`; - } - - if (!OptionsListUtils.isSearchStringMatch(searchValue.trim(), memberDetails)) { - return; - } + if (searchValue.trim() && !OptionsListUtils.isUserMatchWithSearch(details, searchValue)) { + return; } const isOwner = policy?.owner === details.login; From 2974defa78ac22160fa41fc92252c981a1ae58c5 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 31 May 2024 12:26:51 +0700 Subject: [PATCH 052/230] Add confirmation prompt when approving held request via report preview --- .../ReportActionItem/ReportPreview.tsx | 48 ++++++++++++++++++- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index be3b104018db..933381b9e116 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -1,5 +1,5 @@ import truncate from 'lodash/truncate'; -import React, {useMemo} from 'react'; +import React, {useMemo, useState} from 'react'; import type {StyleProp, ViewStyle} from 'react-native'; import {View} from 'react-native'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; @@ -9,6 +9,7 @@ import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; +import ProcessMoneyReportHoldMenu from '@components/ProcessMoneyReportHoldMenu'; import SettlementButton from '@components/SettlementButton'; import {showContextMenuForReport} from '@components/ShowContextMenuContext'; import Text from '@components/Text'; @@ -16,6 +17,7 @@ import useLocalize from '@hooks/useLocalize'; import usePermissions from '@hooks/usePermissions'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; +import useWindowDimensions from '@hooks/useWindowDimensions'; import ControlSelection from '@libs/ControlSelection'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; @@ -119,6 +121,12 @@ function ReportPreview({ [transactions, iouReportID, action], ); + const [isHoldMenuVisible, setIsHoldMenuVisible] = useState(false); + const [requestType, setRequestType] = useState<'pay' | 'approve'>(); + const [nonHeldAmount, fullAmount] = ReportUtils.getNonHeldAndFullAmount(iouReport, policy); + const {isSmallScreenWidth} = useWindowDimensions(); + const [paymentType, setPaymentType] = useState(); + const managerID = iouReport?.managerID ?? 0; const {totalDisplaySpend, reimbursableSpend} = ReportUtils.getMoneyRequestSpendBreakdown(iouReport); @@ -162,6 +170,28 @@ function ReportPreview({ [chatReport?.isOwnPolicyExpenseChat, policy?.harvesting?.enabled], ); + const confirmPayment = (type?: PaymentMethodType | undefined) => { + if (!type) { + return; + } + setPaymentType(type); + setRequestType('pay'); + if (ReportUtils.hasHeldExpenses(iouReport?.reportID)) { + setIsHoldMenuVisible(true); + } else if (chatReport) { + IOU.payMoneyRequest(type, chatReport, iouReport as Report, false); + } + }; + + const confirmApproval = () => { + setRequestType('approve'); + if (ReportUtils.hasHeldExpenses(iouReport?.reportID)) { + setIsHoldMenuVisible(true); + } else { + IOU.approveMoneyRequest(iouReport as Report, true); + } + }; + const getDisplayAmount = (): string => { if (totalDisplaySpend) { return CurrencyUtils.convertToDisplayString(totalDisplaySpend, iouReport?.currency); @@ -368,7 +398,8 @@ function ReportPreview({ policyID={policyID} chatReportID={chatReportID} iouReport={iouReport} - onPress={(paymentType?: PaymentMethodType) => chatReport && iouReport && paymentType && IOU.payMoneyRequest(paymentType, chatReport, iouReport)} + onPress={confirmPayment} + confirmApproval={confirmApproval} enablePaymentsRoute={ROUTES.ENABLE_PAYMENTS} addBankAccountRoute={bankAccountRoute} shouldHidePaymentOptions={!shouldShowPayButton} @@ -399,6 +430,19 @@ function ReportPreview({ + {isHoldMenuVisible && requestType !== undefined && ( + setIsHoldMenuVisible(false)} + isVisible={isHoldMenuVisible} + paymentType={paymentType} + chatReport={chatReport} + moneyRequestReport={iouReport as Report} + /> + )} ); } From 68aa796fddcffcc8f4f1b128d9ad0af46c5ace9e Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 31 May 2024 13:19:00 +0700 Subject: [PATCH 053/230] fix lint --- src/components/ReportActionItem/ReportPreview.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 933381b9e116..755ca06ecb4d 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -179,6 +179,7 @@ function ReportPreview({ if (ReportUtils.hasHeldExpenses(iouReport?.reportID)) { setIsHoldMenuVisible(true); } else if (chatReport) { + // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style IOU.payMoneyRequest(type, chatReport, iouReport as Report, false); } }; @@ -188,6 +189,7 @@ function ReportPreview({ if (ReportUtils.hasHeldExpenses(iouReport?.reportID)) { setIsHoldMenuVisible(true); } else { + // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style IOU.approveMoneyRequest(iouReport as Report, true); } }; @@ -440,6 +442,7 @@ function ReportPreview({ isVisible={isHoldMenuVisible} paymentType={paymentType} chatReport={chatReport} + // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style moneyRequestReport={iouReport as Report} /> )} From 4c6771566bc96f37dac77dcbe386b2b1e64a7822 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 31 May 2024 13:34:12 +0700 Subject: [PATCH 054/230] safely check iouReport --- src/components/ReportActionItem/ReportPreview.tsx | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 755ca06ecb4d..ef34c6ba2982 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -178,9 +178,8 @@ function ReportPreview({ setRequestType('pay'); if (ReportUtils.hasHeldExpenses(iouReport?.reportID)) { setIsHoldMenuVisible(true); - } else if (chatReport) { - // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style - IOU.payMoneyRequest(type, chatReport, iouReport as Report, false); + } else if (chatReport && iouReport) { + IOU.payMoneyRequest(type, chatReport, iouReport, false); } }; @@ -189,8 +188,7 @@ function ReportPreview({ if (ReportUtils.hasHeldExpenses(iouReport?.reportID)) { setIsHoldMenuVisible(true); } else { - // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style - IOU.approveMoneyRequest(iouReport as Report, true); + IOU.approveMoneyRequest(iouReport ?? {}, true); } }; @@ -432,7 +430,7 @@ function ReportPreview({ - {isHoldMenuVisible && requestType !== undefined && ( + {isHoldMenuVisible && requestType !== undefined && !!iouReport && ( )} From 21b35f7eb2f6ce6abad4c431c1876d0bdb0745e0 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 31 May 2024 15:15:23 +0700 Subject: [PATCH 055/230] fix test --- src/components/ReportActionItem/ReportPreview.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index ef34c6ba2982..15e33b2d4d89 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -430,7 +430,7 @@ function ReportPreview({ - {isHoldMenuVisible && requestType !== undefined && !!iouReport && ( + {isHoldMenuVisible && !!iouReport && requestType !== undefined && ( Date: Fri, 31 May 2024 16:25:11 +0700 Subject: [PATCH 056/230] fix perf test --- src/components/ReportActionItem/ReportPreview.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 15e33b2d4d89..e1db781c5d16 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -178,8 +178,8 @@ function ReportPreview({ setRequestType('pay'); if (ReportUtils.hasHeldExpenses(iouReport?.reportID)) { setIsHoldMenuVisible(true); - } else if (chatReport && iouReport) { - IOU.payMoneyRequest(type, chatReport, iouReport, false); + } else if (chatReport) { + IOU.payMoneyRequest(type, chatReport, iouReport!, false); } }; From fca320f7dc7a7b44e9c63d5a15012e92416ce8e9 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 31 May 2024 16:34:56 +0700 Subject: [PATCH 057/230] fix lint --- src/components/ReportActionItem/ReportPreview.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index e1db781c5d16..5fcf1c53a337 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -179,7 +179,8 @@ function ReportPreview({ if (ReportUtils.hasHeldExpenses(iouReport?.reportID)) { setIsHoldMenuVisible(true); } else if (chatReport) { - IOU.payMoneyRequest(type, chatReport, iouReport!, false); + // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style + IOU.payMoneyRequest(type, chatReport, iouReport as Report, false); } }; From 52eb2bb52e9392874a396cfdaf03a18d6f85604a Mon Sep 17 00:00:00 2001 From: Ren Jones <153645623+ren-jones@users.noreply.github.com> Date: Fri, 31 May 2024 09:16:54 -0500 Subject: [PATCH 058/230] DOCS: Create Book-with-Expensify-Travel.md New article. First article with content included for both New and Classic Expensify. For now, will need to duplicate articles in the two separate repositories until both are merged. --- .../travel/Book-with-Expensify-Travel.md | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 docs/articles/new-expensify/travel/Book-with-Expensify-Travel.md diff --git a/docs/articles/new-expensify/travel/Book-with-Expensify-Travel.md b/docs/articles/new-expensify/travel/Book-with-Expensify-Travel.md new file mode 100644 index 000000000000..c48565f5149e --- /dev/null +++ b/docs/articles/new-expensify/travel/Book-with-Expensify-Travel.md @@ -0,0 +1,94 @@ +--- +title: Book with Expensify Travel +description: Book flights, hotels, cars, trains, and more with Expensify Travel +--- +
+ +Expensify Travel allows members to search and book flights, hotels, cars, and trains globally at the most competitive rates available. + +With Expensify Travel, you can: +- Search and book travel arrangements all in one place +- Book travel for yourself or for someone else +- Get real-time support by chat or phone +- Manage all your T&E expenses in Expensify +- Create specific rules for booking travel +- Enable approvals for out-of-policy trips +- Book with any credit card on the market +- Book with the Expensify Card to get cash back and automatically reconcile transactions + +There is a flat fee of $15 per trip booked. A single trip can include multiple bookings, such as a flight, a hotel, and a car rental. + +# Book travel + +To book travel from the Expensify web app, + +1. Click the **Travel** tab. +2. Click **Book or manage travel**. +3. Use the icons at the top to select the type of travel arrangement you want to book: flights, hotels, cars, or trains. +4. Enter the travel information relevant to the travel arrangement selected (for example, the destination, dates of travel, etc.). +5. Select all the details for the arrangement you want to book. +6. Review the booking details and click **Book Flight / Book Hotel / Book Car / Book Rail** to complete the booking. + +The traveler is emailed an itinerary of the booking. Additionally, +- Their travel details are added to a Trip chat room under their primary workspace. +- An expense report for the trip is created. +- If booked with an Expensify Card, the trip is automatically reconciled. + +{% include info.html %} +The travel itinerary is also emailed to the traveler’s [copilots](https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/Assign-or-remove-a-Copilot), if applicable. +{% include end-info.html %} + +
+ +
+Expensify Travel allows members to search and book flights, hotels, cars, and trains globally at the most competitive rates available. + +With Expensify Travel, you can: +- Search and book travel arrangements all in one place +- Book travel for yourself or for someone else +- Get real-time support by chat or phone +- Manage all your T&E expenses in Expensify +- Create specific rules for booking travel +- Enable approvals for out-of-policy trips +- Book with any credit card on the market +- Book with the Expensify Card to get cash back and automatically reconcile transactions + +There is a flat fee of $15 per trip booked. A single trip can include multiple bookings, such as a flight, a hotel, and a car rental. + +# Book travel + +{% include selector.html values="desktop, mobile" %} + +{% include option.html value="desktop" %} +1. Click the + icon in the bottom left menu and select **Book travel**. +2. Click **Book or manage travel**. +3. Agree to the terms and conditions and click **Continue**. +4. Use the icons at the top to select the type of travel arrangement you want to book: flights, hotels, cars, or trains. +5. Enter the travel information relevant to the travel arrangement selected (for example, the destination, dates of travel, etc.). +6. Select all the details for the arrangement you want to book. +7. Review the booking details and click **Book Flight / Book Hotel / Book Car / Book Rail** to complete the booking. +{% include end-option.html %} + +{% include option.html value="mobile" %} +1. Tap the + icon in the bottom menu and select **Book travel**. +2. Tap **Book or manage travel**. +3. Agree to the terms and conditions and tap **Continue**. +4. Use the icons at the top to select the type of travel arrangement you want to book: flights, hotels, cars, or trains. +5. Enter the travel information relevant to the travel arrangement selected (for example, the destination, dates of travel, etc.). +6. Select all the details for the arrangement you want to book. +7. Review the booking details and click **Book Flight / Book Hotel / Book Car / Book Rail** to complete the booking. +{% include end-option.html %} + +{% include end-selector.html %} + +The traveler is emailed an itinerary of the booking. Additionally, +- Their travel details are added to a Trip chat room under their primary workspace. +- An expense report for the trip is created. +- If booked with an Expensify Card, the trip is automatically reconciled. + +
+ + + + + From 0c7c71d46fecc8cde46f009d8ccb472316786ef8 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Mon, 3 Jun 2024 12:19:55 +0700 Subject: [PATCH 059/230] fix chat doesn't scroll to bottom --- src/libs/actions/IOU.ts | 7 ++++++- src/libs/actions/Report.ts | 2 +- src/pages/home/report/ReportActionsList.tsx | 7 +++++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 128ee5b85b7b..2702dd4b7b0c 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -1983,12 +1983,17 @@ function getMoneyRequestInformation( reportPreviewAction = ReportUtils.updateReportPreview(iouReport, reportPreviewAction as ReportPreviewAction, false, comment, optimisticTransaction); } else { reportPreviewAction = ReportUtils.buildOptimisticReportPreview(chatReport, iouReport, comment, optimisticTransaction); + chatReport.lastVisibleActionCreated = reportPreviewAction.created; // Generated ReportPreview action is a parent report action of the iou report. // We are setting the iou report's parentReportActionID to display subtitle correctly in IOU page when offline. iouReport.parentReportActionID = reportPreviewAction.reportActionID; } + if (iouAction) { + iouReport.lastVisibleActionCreated = iouAction.created; + } + const shouldCreateOptimisticPersonalDetails = isNewChatReport && !allPersonalDetails[payerAccountID]; // Add optimistic personal details for participant const optimisticPersonalDetailListAction = shouldCreateOptimisticPersonalDetails @@ -4625,7 +4630,7 @@ function startSplitBill({ API.write(WRITE_COMMANDS.START_SPLIT_BILL, parameters, {optimisticData, successData, failureData}); Navigation.dismissModalWithReport(splitChatReport); - Report.notifyNewAction(splitChatReport.chatReportID ?? '', currentUserAccountID); + Report.notifyNewAction(splitChatReport.reportID ?? '', currentUserAccountID); } /** Used for editing a split expense while it's still scanning or when SmartScan fails, it completes a split expense started by startSplitBill above. diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 7c73ef4a1eac..1dd67332cb99 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -478,7 +478,7 @@ function addActions(reportID: string, text = '', file?: FileObject) { const lastCommentText = ReportUtils.formatReportLastMessageText(lastComment?.text ?? ''); const optimisticReport: Partial = { - lastVisibleActionCreated: currentTime, + lastVisibleActionCreated: lastAction?.created, lastMessageTranslationKey: lastComment?.translationKey ?? '', lastMessageText: lastCommentText, lastMessageHtml: lastCommentText, diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx index c700fea4fb85..6ad01455b543 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -211,15 +211,17 @@ function ReportActionsList({ ); // whisper action doesn't affect lastVisibleActionCreated, so we should not take it into account while checking if there is the newest report action - const newestVisibleReportAction = useMemo(() => sortedVisibleReportActions.find((item) => !ReportActionsUtils.isWhisperAction(item)) ?? null, [sortedVisibleReportActions]); + // const newestVisibleReportAction = useMemo(() => sortedVisibleReportActions.find((item) => !ReportActionsUtils.isWhisperAction(item)) ?? null, [sortedVisibleReportActions]); const lastActionIndex = sortedVisibleReportActions[0]?.reportActionID; const reportActionSize = useRef(sortedVisibleReportActions.length); - const hasNewestReportAction = newestVisibleReportAction?.created === report.lastVisibleActionCreated; + const hasNewestReportAction = sortedVisibleReportActions[0]?.created === report.lastVisibleActionCreated; const hasNewestReportActionRef = useRef(hasNewestReportAction); hasNewestReportActionRef.current = hasNewestReportAction; const previousLastIndex = useRef(lastActionIndex); + console.log(sortedVisibleReportActions[0].created, report.lastVisibleActionCreated) + const isLastPendingActionIsDelete = sortedReportActions?.[0]?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; const linkedReportActionID = route.params?.reportActionID ?? ''; @@ -346,6 +348,7 @@ function ReportActionsList({ (isFromCurrentUser: boolean) => { // If a new comment is added and it's from the current user scroll to the bottom otherwise leave the user positioned where // they are now in the list. + console.log(hasNewestReportActionRef.current); if (!isFromCurrentUser || !hasNewestReportActionRef.current) { return; } From 98d6036912c90eff30bd32d8823035c6d4e80c4a Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 3 Jun 2024 15:32:19 +0700 Subject: [PATCH 060/230] fix: go back in AccessOrNotFoundWrapper --- src/pages/workspace/AccessOrNotFoundWrapper.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/AccessOrNotFoundWrapper.tsx b/src/pages/workspace/AccessOrNotFoundWrapper.tsx index 4afd3e1b31dd..f008ea8d9bd2 100644 --- a/src/pages/workspace/AccessOrNotFoundWrapper.tsx +++ b/src/pages/workspace/AccessOrNotFoundWrapper.tsx @@ -92,7 +92,7 @@ function PageNotFoundFallback({policyID, shouldShowFullScreenFallback, fullPageN /> ) : ( Navigation.goBack(policyID ? ROUTES.WORKSPACE_PROFILE.getRoute(policyID) : ROUTES.HOME)} + onBackButtonPress={() => Navigation.goBack(policyID ? ROUTES.WORKSPACE_PROFILE.getRoute(policyID) : undefined)} // eslint-disable-next-line react/jsx-props-no-spreading {...fullPageNotFoundViewProps} /> From cd48cd5f50f60c908b6df795ebd294b8fe61e20b Mon Sep 17 00:00:00 2001 From: truph01 Date: Mon, 3 Jun 2024 16:43:48 +0700 Subject: [PATCH 061/230] Fix: OpenWorkspaceView API call is made on pages that do not need it --- src/pages/workspace/WorkspacePageWithSections.tsx | 2 +- src/pages/workspace/WorkspaceProfilePage.tsx | 1 + src/pages/workspace/bills/WorkspaceBillsPage.tsx | 1 + src/pages/workspace/card/WorkspaceCardPage.tsx | 1 + src/pages/workspace/invoices/WorkspaceInvoicesPage.tsx | 1 + .../reimburse/WorkspaceRateAndUnitPage/InitialPage.tsx | 1 - .../workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx | 1 - .../workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx | 1 - src/pages/workspace/reimburse/WorkspaceReimbursePage.tsx | 1 - src/pages/workspace/travel/WorkspaceTravelPage.tsx | 1 + src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx | 1 - 11 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/pages/workspace/WorkspacePageWithSections.tsx b/src/pages/workspace/WorkspacePageWithSections.tsx index 58288f213818..738a0663b94b 100644 --- a/src/pages/workspace/WorkspacePageWithSections.tsx +++ b/src/pages/workspace/WorkspacePageWithSections.tsx @@ -106,7 +106,7 @@ function WorkspacePageWithSections({ reimbursementAccount = CONST.REIMBURSEMENT_ACCOUNT.DEFAULT_DATA, route, shouldUseScrollView = false, - shouldSkipVBBACall = false, + shouldSkipVBBACall = true, shouldShowBackButton = false, user, shouldShowLoading = true, diff --git a/src/pages/workspace/WorkspaceProfilePage.tsx b/src/pages/workspace/WorkspaceProfilePage.tsx index be6143ff6240..4d9ed3e138fd 100644 --- a/src/pages/workspace/WorkspaceProfilePage.tsx +++ b/src/pages/workspace/WorkspaceProfilePage.tsx @@ -125,6 +125,7 @@ function WorkspaceProfilePage({policy, currencyList = {}, route}: WorkSpaceProfi shouldShowOfflineIndicatorInWideScreen shouldShowNonAdmin icon={Illustrations.House} + shouldSkipVBBACall={false} > {(hasVBA?: boolean) => ( diff --git a/src/pages/workspace/bills/WorkspaceBillsPage.tsx b/src/pages/workspace/bills/WorkspaceBillsPage.tsx index 9e1810d74793..cdd733d59141 100644 --- a/src/pages/workspace/bills/WorkspaceBillsPage.tsx +++ b/src/pages/workspace/bills/WorkspaceBillsPage.tsx @@ -23,6 +23,7 @@ function WorkspaceBillsPage({route}: WorkspaceBillsPageProps) { shouldUseScrollView headerText={translate('workspace.common.bills')} route={route} + shouldSkipVBBACall={false} guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_BILLS} shouldShowOfflineIndicatorInWideScreen > diff --git a/src/pages/workspace/card/WorkspaceCardPage.tsx b/src/pages/workspace/card/WorkspaceCardPage.tsx index eeec0edf106b..c79a335376d6 100644 --- a/src/pages/workspace/card/WorkspaceCardPage.tsx +++ b/src/pages/workspace/card/WorkspaceCardPage.tsx @@ -24,6 +24,7 @@ function WorkspaceCardPage({route}: WorkspaceCardPageProps) { shouldUseScrollView headerText={translate('workspace.common.card')} route={route} + shouldSkipVBBACall={false} guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_CARD} shouldShowOfflineIndicatorInWideScreen > diff --git a/src/pages/workspace/invoices/WorkspaceInvoicesPage.tsx b/src/pages/workspace/invoices/WorkspaceInvoicesPage.tsx index ef0a73788dfa..f1a973d5c47d 100644 --- a/src/pages/workspace/invoices/WorkspaceInvoicesPage.tsx +++ b/src/pages/workspace/invoices/WorkspaceInvoicesPage.tsx @@ -23,6 +23,7 @@ function WorkspaceInvoicesPage({route}: WorkspaceInvoicesPageProps) { headerText={translate('workspace.common.invoices')} guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_INVOICES} shouldShowOfflineIndicatorInWideScreen + shouldSkipVBBACall={false} route={route} > {(hasVBA?: boolean, policyID?: string) => ( diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/InitialPage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/InitialPage.tsx index 6faf23af289e..b99157b0231a 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/InitialPage.tsx +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/InitialPage.tsx @@ -100,7 +100,6 @@ function WorkspaceRateAndUnitPage(props: WorkspaceRateAndUnitPageProps) { headerText={translate('workspace.reimburse.trackDistance')} route={props.route} guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_REIMBURSE} - shouldSkipVBBACall backButtonRoute="" shouldShowLoading={false} shouldShowBackButton diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx index 9c7eaa82167f..52fc35db59d6 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx @@ -63,7 +63,6 @@ function WorkspaceRatePage(props: WorkspaceRatePageProps) { headerText={translate('workspace.reimburse.trackDistanceRate')} route={props.route} guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_REIMBURSE} - shouldSkipVBBACall backButtonRoute={ROUTES.WORKSPACE_RATE_AND_UNIT.getRoute(props.policy?.id ?? '')} shouldShowLoading={false} shouldShowBackButton diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx index 92d76b4a33c6..8580b112c8db 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage.tsx @@ -51,7 +51,6 @@ function WorkspaceUnitPage(props: WorkspaceUnitPageProps) { headerText={translate('workspace.reimburse.trackDistanceUnit')} route={props.route} guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_REIMBURSE} - shouldSkipVBBACall backButtonRoute={ROUTES.WORKSPACE_RATE_AND_UNIT.getRoute(props.policy?.id ?? '')} shouldShowLoading={false} shouldShowBackButton diff --git a/src/pages/workspace/reimburse/WorkspaceReimbursePage.tsx b/src/pages/workspace/reimburse/WorkspaceReimbursePage.tsx index 6bb15a8a17ce..d929ec10748a 100644 --- a/src/pages/workspace/reimburse/WorkspaceReimbursePage.tsx +++ b/src/pages/workspace/reimburse/WorkspaceReimbursePage.tsx @@ -20,7 +20,6 @@ function WorkspaceReimbursePage({route, policy}: WorkspaceReimbursePageProps) { headerText={translate('workspace.common.reimburse')} route={route} guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_REIMBURSE} - shouldSkipVBBACall shouldShowLoading={false} shouldShowOfflineIndicatorInWideScreen > diff --git a/src/pages/workspace/travel/WorkspaceTravelPage.tsx b/src/pages/workspace/travel/WorkspaceTravelPage.tsx index 1acae9e6d359..eb61397d10b2 100644 --- a/src/pages/workspace/travel/WorkspaceTravelPage.tsx +++ b/src/pages/workspace/travel/WorkspaceTravelPage.tsx @@ -25,6 +25,7 @@ function WorkspaceTravelPage({route}: WorkspaceTravelPageProps) { route={route} guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_TRAVEL} shouldShowOfflineIndicatorInWideScreen + shouldSkipVBBACall={false} > {(hasVBA, policyID) => ( diff --git a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx index 5b17a4e26051..a008f89df50e 100644 --- a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx @@ -288,7 +288,6 @@ function WorkspaceWorkflowsPage({policy, betas, route}: WorkspaceWorkflowsPagePr guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_WORKFLOWS} shouldShowOfflineIndicatorInWideScreen shouldShowNotFoundPage={!isPaidGroupPolicy || !isPolicyAdmin} - shouldSkipVBBACall isLoading={isLoading} shouldShowLoading={isLoading} shouldUseScrollView From 0d68d3db47d84df13d7d79e93df102550291b834 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Mon, 3 Jun 2024 11:49:54 +0200 Subject: [PATCH 062/230] fix sessionStorage not found for mobile --- src/components/ActiveWorkspace/ActiveWorkspaceProvider.tsx | 6 +++--- .../createCustomBottomTabNavigator/BottomTabBar.tsx | 7 +++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/components/ActiveWorkspace/ActiveWorkspaceProvider.tsx b/src/components/ActiveWorkspace/ActiveWorkspaceProvider.tsx index 419dbef5d171..b4d57b5e4733 100644 --- a/src/components/ActiveWorkspace/ActiveWorkspaceProvider.tsx +++ b/src/components/ActiveWorkspace/ActiveWorkspaceProvider.tsx @@ -8,10 +8,10 @@ function ActiveWorkspaceContextProvider({children}: ChildrenProps) { const setActiveWorkspaceID = useCallback((workspaceID: string | undefined) => { updateActiveWorkspaceID(workspaceID); - if (workspaceID) { - sessionStorage.setItem(CONST.SESSION_STORAGE_KEYS.ACTIVE_WORKSPACE_ID, workspaceID); + if (workspaceID && sessionStorage) { + sessionStorage?.setItem(CONST.SESSION_STORAGE_KEYS.ACTIVE_WORKSPACE_ID, workspaceID); } else { - sessionStorage.removeItem(CONST.SESSION_STORAGE_KEYS.ACTIVE_WORKSPACE_ID); + sessionStorage?.removeItem(CONST.SESSION_STORAGE_KEYS.ACTIVE_WORKSPACE_ID); } }, []); diff --git a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx index 3e48b89ad5c0..30f0c2766251 100644 --- a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx +++ b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx @@ -7,6 +7,7 @@ import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import {PressableWithFeedback} from '@components/Pressable'; import Tooltip from '@components/Tooltip'; +import useActiveWorkspace from '@hooks/useActiveWorkspace'; import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -23,7 +24,8 @@ import * as Welcome from '@userActions/Welcome'; import CONST from '@src/CONST'; import NAVIGATORS from '@src/NAVIGATORS'; import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES, {Route} from '@src/ROUTES'; +import type {Route} from '@src/ROUTES'; +import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; type PurposeForUsingExpensifyModalOnyxProps = { @@ -36,7 +38,8 @@ function BottomTabBar({isLoadingApp = false}: PurposeForUsingExpensifyModalProps const styles = useThemeStyles(); const {translate} = useLocalize(); const navigation = useNavigation(); - const activeWorkspaceID = sessionStorage.getItem(CONST.SESSION_STORAGE_KEYS.ACTIVE_WORKSPACE_ID); + const {activeWorkspaceID: contextActiveWorkspaceID} = useActiveWorkspace(); + const activeWorkspaceID = sessionStorage ? sessionStorage.getItem(CONST.SESSION_STORAGE_KEYS.ACTIVE_WORKSPACE_ID) : contextActiveWorkspaceID; useEffect(() => { const navigationState = navigation.getState() as State | undefined; From 6b063e83776bf2a66a45705dd2406dc6c6a0611b Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Mon, 3 Jun 2024 12:21:16 +0200 Subject: [PATCH 063/230] Fix sessionStorage not found error for native devices --- src/App.tsx | 2 +- .../ActiveWorkspaceProvider/index.tsx | 19 +++ .../index.website.tsx} | 2 +- .../BottomTabBar/index.tsx | 111 ++++++++++++++++++ .../index.website.tsx} | 0 5 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 src/components/ActiveWorkspaceProvider/index.tsx rename src/components/{ActiveWorkspace/ActiveWorkspaceProvider.tsx => ActiveWorkspaceProvider/index.website.tsx} (92%) create mode 100644 src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.tsx rename src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/{BottomTabBar.tsx => BottomTabBar/index.website.tsx} (100%) diff --git a/src/App.tsx b/src/App.tsx index 6316fa80fba1..9eda57816e9d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,7 +6,7 @@ import {PickerStateProvider} from 'react-native-picker-select'; import {SafeAreaProvider} from 'react-native-safe-area-context'; import '../wdyr'; import ActiveElementRoleProvider from './components/ActiveElementRoleProvider'; -import ActiveWorkspaceContextProvider from './components/ActiveWorkspace/ActiveWorkspaceProvider'; +import ActiveWorkspaceContextProvider from './components/ActiveWorkspaceProvider'; import ColorSchemeWrapper from './components/ColorSchemeWrapper'; import ComposeProviders from './components/ComposeProviders'; import CustomStatusBarAndBackground from './components/CustomStatusBarAndBackground'; diff --git a/src/components/ActiveWorkspaceProvider/index.tsx b/src/components/ActiveWorkspaceProvider/index.tsx new file mode 100644 index 000000000000..bc7260cdf10b --- /dev/null +++ b/src/components/ActiveWorkspaceProvider/index.tsx @@ -0,0 +1,19 @@ +import React, {useMemo, useState} from 'react'; +import ActiveWorkspaceContext from '@components/ActiveWorkspace/ActiveWorkspaceContext'; +import type ChildrenProps from '@src/types/utils/ChildrenProps'; + +function ActiveWorkspaceContextProvider({children}: ChildrenProps) { + const [activeWorkspaceID, setActiveWorkspaceID] = useState(undefined); + + const value = useMemo( + () => ({ + activeWorkspaceID, + setActiveWorkspaceID, + }), + [activeWorkspaceID, setActiveWorkspaceID], + ); + + return {children}; +} + +export default ActiveWorkspaceContextProvider; diff --git a/src/components/ActiveWorkspace/ActiveWorkspaceProvider.tsx b/src/components/ActiveWorkspaceProvider/index.website.tsx similarity index 92% rename from src/components/ActiveWorkspace/ActiveWorkspaceProvider.tsx rename to src/components/ActiveWorkspaceProvider/index.website.tsx index b4d57b5e4733..82e46d70f896 100644 --- a/src/components/ActiveWorkspace/ActiveWorkspaceProvider.tsx +++ b/src/components/ActiveWorkspaceProvider/index.website.tsx @@ -1,7 +1,7 @@ import React, {useCallback, useMemo, useState} from 'react'; +import ActiveWorkspaceContext from '@components/ActiveWorkspace/ActiveWorkspaceContext'; import CONST from '@src/CONST'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; -import ActiveWorkspaceContext from './ActiveWorkspaceContext'; function ActiveWorkspaceContextProvider({children}: ChildrenProps) { const [activeWorkspaceID, updateActiveWorkspaceID] = useState(undefined); diff --git a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.tsx b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.tsx new file mode 100644 index 000000000000..ac4c0156f17c --- /dev/null +++ b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.tsx @@ -0,0 +1,111 @@ +import {useNavigation, useNavigationState} from '@react-navigation/native'; +import React, {useCallback, useEffect} from 'react'; +import {View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; +import Icon from '@components/Icon'; +import * as Expensicons from '@components/Icon/Expensicons'; +import {PressableWithFeedback} from '@components/Pressable'; +import Tooltip from '@components/Tooltip'; +import useActiveWorkspace from '@hooks/useActiveWorkspace'; +import useLocalize from '@hooks/useLocalize'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as Session from '@libs/actions/Session'; +import getTopmostBottomTabRoute from '@libs/Navigation/getTopmostBottomTabRoute'; +import getTopmostCentralPaneRoute from '@libs/Navigation/getTopmostCentralPaneRoute'; +import Navigation from '@libs/Navigation/Navigation'; +import type {RootStackParamList, State} from '@libs/Navigation/types'; +import {getChatTabBrickRoad} from '@libs/WorkspacesSettingsUtils'; +import BottomTabAvatar from '@pages/home/sidebar/BottomTabAvatar'; +import BottomTabBarFloatingActionButton from '@pages/home/sidebar/BottomTabBarFloatingActionButton'; +import variables from '@styles/variables'; +import * as Welcome from '@userActions/Welcome'; +import CONST from '@src/CONST'; +import NAVIGATORS from '@src/NAVIGATORS'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {Route} from '@src/ROUTES'; +import ROUTES from '@src/ROUTES'; +import SCREENS from '@src/SCREENS'; + +type PurposeForUsingExpensifyModalOnyxProps = { + isLoadingApp: OnyxEntry; +}; +type PurposeForUsingExpensifyModalProps = PurposeForUsingExpensifyModalOnyxProps; + +function BottomTabBar({isLoadingApp = false}: PurposeForUsingExpensifyModalProps) { + const theme = useTheme(); + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const navigation = useNavigation(); + const {activeWorkspaceID} = useActiveWorkspace(); + + useEffect(() => { + const navigationState = navigation.getState() as State | undefined; + const routes = navigationState?.routes; + const currentRoute = routes?.[navigationState?.index ?? 0]; + // When we are redirected to the Settings tab from the OldDot, we don't want to call the Welcome.show() method. + // To prevent this, the value of the bottomTabRoute?.name is checked here + if (Boolean(currentRoute && currentRoute.name !== NAVIGATORS.BOTTOM_TAB_NAVIGATOR && currentRoute.name !== NAVIGATORS.CENTRAL_PANE_NAVIGATOR) || Session.isAnonymousUser()) { + return; + } + + Welcome.isOnboardingFlowCompleted({onNotCompleted: () => Navigation.navigate(ROUTES.ONBOARDING_ROOT)}); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isLoadingApp]); + + // Parent navigator of the bottom tab bar is the root navigator. + const currentTabName = useNavigationState((state) => { + const topmostCentralPaneRoute = getTopmostCentralPaneRoute(state); + + if (topmostCentralPaneRoute && topmostCentralPaneRoute.name === SCREENS.SEARCH.CENTRAL_PANE) { + return SCREENS.SEARCH.CENTRAL_PANE; + } + + const topmostBottomTabRoute = getTopmostBottomTabRoute(state); + return topmostBottomTabRoute?.name ?? SCREENS.HOME; + }); + + const chatTabBrickRoad = getChatTabBrickRoad(activeWorkspaceID); + + const navigateToChats = useCallback(() => { + const route = activeWorkspaceID ? (`/w/${activeWorkspaceID}/r` as Route) : ROUTES.HOME; + Navigation.navigate(route); + }, [activeWorkspaceID]); + + return ( + + + + + + {chatTabBrickRoad && ( + + )} + + + + + + + ); +} + +BottomTabBar.displayName = 'BottomTabBar'; + +export default withOnyx({ + isLoadingApp: { + key: ONYXKEYS.IS_LOADING_APP, + }, +})(BottomTabBar); diff --git a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.website.tsx similarity index 100% rename from src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx rename to src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.website.tsx From 99eebd81d885e3e77b89aad1a19b0744c43630b7 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Mon, 3 Jun 2024 18:43:32 +0700 Subject: [PATCH 064/230] add new const --- src/CONST.ts | 4 ++++ src/components/MoneyReportHeader.tsx | 7 ++++--- src/components/ProcessMoneyReportHoldMenu.tsx | 6 ++++-- src/components/ReportActionItem/ReportPreview.tsx | 7 ++++--- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index de4e3305eddc..0c10b01c3d15 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1552,6 +1552,10 @@ const CONST = { CATEGORIZE: 'categorize', SHARE: 'share', }, + ACTION_TYPE: { + APPROVE: 'approve', + PAY: 'pay', + }, DEFAULT_AMOUNT: 0, TYPE: { SEND: 'send', diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index aad801a7c259..f60e2a3cb01b 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -19,6 +19,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage'; +import DeepValueOf from '@src/types/utils/DeepValueOf'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import Button from './Button'; import ConfirmModal from './ConfirmModal'; @@ -79,7 +80,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea isActionOwner && (ReportUtils.canAddOrDeleteTransactions(moneyRequestReport) || ReportUtils.isTrackExpenseReport(transactionThreadReport)) && !isDeletedParentAction; const [isHoldMenuVisible, setIsHoldMenuVisible] = useState(false); const [paymentType, setPaymentType] = useState(); - const [requestType, setRequestType] = useState<'pay' | 'approve'>(); + const [requestType, setRequestType] = useState>(); const canAllowSettlement = ReportUtils.hasUpdatedTotal(moneyRequestReport, policy); const policyType = policy?.type; const isPayer = ReportUtils.isPayer(session, moneyRequestReport); @@ -124,7 +125,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea return; } setPaymentType(type); - setRequestType('pay'); + setRequestType(CONST.IOU.ACTION_TYPE.PAY); if (ReportUtils.hasHeldExpenses(moneyRequestReport.reportID)) { setIsHoldMenuVisible(true); } else if (chatReport) { @@ -133,7 +134,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea }; const confirmApproval = () => { - setRequestType('approve'); + setRequestType(CONST.IOU.ACTION_TYPE.APPROVE); if (ReportUtils.hasHeldExpenses(moneyRequestReport.reportID)) { setIsHoldMenuVisible(true); } else { diff --git a/src/components/ProcessMoneyReportHoldMenu.tsx b/src/components/ProcessMoneyReportHoldMenu.tsx index 6e81c9d57bc8..08a78f699916 100644 --- a/src/components/ProcessMoneyReportHoldMenu.tsx +++ b/src/components/ProcessMoneyReportHoldMenu.tsx @@ -4,9 +4,11 @@ import useLocalize from '@hooks/useLocalize'; import Navigation from '@libs/Navigation/Navigation'; import {isLinkedTransactionHeld} from '@libs/ReportActionsUtils'; import * as IOU from '@userActions/IOU'; +import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage'; +import DeepValueOf from '@src/types/utils/DeepValueOf'; import DecisionModal from './DecisionModal'; type ProcessMoneyReportHoldMenuProps = { @@ -35,7 +37,7 @@ type ProcessMoneyReportHoldMenuProps = { paymentType?: PaymentMethodType; /** Type of action handled */ - requestType?: 'pay' | 'approve'; + requestType?: DeepValueOf; }; function ProcessMoneyReportHoldMenu({ @@ -50,7 +52,7 @@ function ProcessMoneyReportHoldMenu({ moneyRequestReport, }: ProcessMoneyReportHoldMenuProps) { const {translate} = useLocalize(); - const isApprove = requestType === 'approve'; + const isApprove = requestType === CONST.IOU.ACTION_TYPE.APPROVE; const onSubmit = (full: boolean) => { if (isApprove) { diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 5fcf1c53a337..afb8beebfd33 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -35,6 +35,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {Policy, Report, ReportAction, Transaction, TransactionViolations, UserWallet} from '@src/types/onyx'; import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage'; +import DeepValueOf from '@src/types/utils/DeepValueOf'; import type {PendingMessageProps} from './MoneyRequestPreview/types'; import ReportActionItemImages from './ReportActionItemImages'; @@ -122,7 +123,7 @@ function ReportPreview({ ); const [isHoldMenuVisible, setIsHoldMenuVisible] = useState(false); - const [requestType, setRequestType] = useState<'pay' | 'approve'>(); + const [requestType, setRequestType] = useState>(); const [nonHeldAmount, fullAmount] = ReportUtils.getNonHeldAndFullAmount(iouReport, policy); const {isSmallScreenWidth} = useWindowDimensions(); const [paymentType, setPaymentType] = useState(); @@ -175,7 +176,7 @@ function ReportPreview({ return; } setPaymentType(type); - setRequestType('pay'); + setRequestType(CONST.IOU.ACTION_TYPE.PAY); if (ReportUtils.hasHeldExpenses(iouReport?.reportID)) { setIsHoldMenuVisible(true); } else if (chatReport) { @@ -185,7 +186,7 @@ function ReportPreview({ }; const confirmApproval = () => { - setRequestType('approve'); + setRequestType(CONST.IOU.ACTION_TYPE.APPROVE); if (ReportUtils.hasHeldExpenses(iouReport?.reportID)) { setIsHoldMenuVisible(true); } else { From 0091e82822ee70a60a32efa380738c8ffabf2160 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Mon, 3 Jun 2024 20:37:21 +0700 Subject: [PATCH 065/230] fix lint --- src/components/MoneyReportHeader.tsx | 2 +- src/components/ProcessMoneyReportHoldMenu.tsx | 2 +- src/components/ReportActionItem/ReportPreview.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index f60e2a3cb01b..789bcaac7355 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -19,7 +19,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage'; -import DeepValueOf from '@src/types/utils/DeepValueOf'; +import type DeepValueOf from '@src/types/utils/DeepValueOf'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import Button from './Button'; import ConfirmModal from './ConfirmModal'; diff --git a/src/components/ProcessMoneyReportHoldMenu.tsx b/src/components/ProcessMoneyReportHoldMenu.tsx index 08a78f699916..f3e7790a6b5f 100644 --- a/src/components/ProcessMoneyReportHoldMenu.tsx +++ b/src/components/ProcessMoneyReportHoldMenu.tsx @@ -8,7 +8,7 @@ import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage'; -import DeepValueOf from '@src/types/utils/DeepValueOf'; +import type DeepValueOf from '@src/types/utils/DeepValueOf'; import DecisionModal from './DecisionModal'; type ProcessMoneyReportHoldMenuProps = { diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index afb8beebfd33..1fa16a73cff2 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -35,7 +35,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {Policy, Report, ReportAction, Transaction, TransactionViolations, UserWallet} from '@src/types/onyx'; import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage'; -import DeepValueOf from '@src/types/utils/DeepValueOf'; +import type DeepValueOf from '@src/types/utils/DeepValueOf'; import type {PendingMessageProps} from './MoneyRequestPreview/types'; import ReportActionItemImages from './ReportActionItemImages'; From 23e36e637db60f1421deba05268b55e6a03d365b Mon Sep 17 00:00:00 2001 From: Cong Pham Date: Mon, 3 Jun 2024 21:13:19 +0700 Subject: [PATCH 066/230] update specific platform --- src/pages/signin/LoginForm/BaseLoginForm.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/pages/signin/LoginForm/BaseLoginForm.tsx b/src/pages/signin/LoginForm/BaseLoginForm.tsx index 310819cd7365..e4c6479071c0 100644 --- a/src/pages/signin/LoginForm/BaseLoginForm.tsx +++ b/src/pages/signin/LoginForm/BaseLoginForm.tsx @@ -3,6 +3,7 @@ import Str from 'expensify-common/lib/str'; import type {ForwardedRef} from 'react'; import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react'; import {InteractionManager, View} from 'react-native'; +import * as Browser from '@libs/Browser'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import DotIndicatorMessage from '@components/DotIndicatorMessage'; @@ -220,6 +221,9 @@ function BaseLoginForm({account, credentials, closeAccount, blurOnSubmit = false const submitContainerRef = useRef(null); const handleFocus = useCallback(() => { + if (!Browser.isMobileWebKit()) { + return; + } InteractionManager.runAfterInteractions(() => { htmlDivElementRef(submitContainerRef).current?.scrollIntoView?.({behavior: 'smooth', block: 'end'}); }); From d3fb18d73eeeee115113520362157709f2b414a6 Mon Sep 17 00:00:00 2001 From: James Dean Date: Mon, 3 Jun 2024 11:08:46 -0700 Subject: [PATCH 067/230] Update en.ts minor copy update based on this convo: https://expensify.slack.com/archives/C05RECHFBEW/p1717100885297799 --- src/languages/en.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 87116ea039b5..852755bba2e4 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -354,7 +354,7 @@ export default { errorWhileSelectingCorruptedImage: 'An error occurred while selecting a corrupted attachment, please try another file.', takePhoto: 'Take photo', chooseFromGallery: 'Choose from gallery', - chooseDocument: 'Choose document', + chooseDocument: 'Choose from files', attachmentTooLarge: 'Attachment too large', sizeExceeded: 'Attachment size is larger than 24 MB limit.', attachmentTooSmall: 'Attachment too small', From 31c420d191feb42e7d9f7d49b2a1d550a602168d Mon Sep 17 00:00:00 2001 From: James Dean Date: Mon, 3 Jun 2024 11:31:56 -0700 Subject: [PATCH 068/230] Update en.ts --- src/languages/en.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 852755bba2e4..2740dc0d9792 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -354,7 +354,7 @@ export default { errorWhileSelectingCorruptedImage: 'An error occurred while selecting a corrupted attachment, please try another file.', takePhoto: 'Take photo', chooseFromGallery: 'Choose from gallery', - chooseDocument: 'Choose from files', + chooseDocument: 'Choose file', attachmentTooLarge: 'Attachment too large', sizeExceeded: 'Attachment size is larger than 24 MB limit.', attachmentTooSmall: 'Attachment too small', From b4d20a434c8ffc16d7fa7d3326969f1ad1dfac81 Mon Sep 17 00:00:00 2001 From: James Dean Date: Mon, 3 Jun 2024 11:32:59 -0700 Subject: [PATCH 069/230] Update es.ts --- src/languages/es.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index 01ce6a3b4679..2425d0a07577 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -348,7 +348,7 @@ export default { errorWhileSelectingCorruptedImage: 'Ha ocurrido un error al seleccionar un archivo adjunto corrupto. Por favor, inténtalo con otro archivo.', takePhoto: 'Hacer una foto', chooseFromGallery: 'Elegir de la galería', - chooseDocument: 'Elegir documento', + chooseDocument: 'Elegir un archivo', attachmentTooLarge: 'Archivo adjunto demasiado grande', sizeExceeded: 'El archivo adjunto supera el límite de 24 MB.', attachmentTooSmall: 'Archivo adjunto demasiado pequeño', From d67febbd775ae455c99dff200bf095a906da8ddd Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Mon, 3 Jun 2024 23:34:05 +0100 Subject: [PATCH 070/230] feat: calculate all tag required violations locally --- src/libs/Violations/ViolationsUtils.ts | 42 ++++++++++++++++++-------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/src/libs/Violations/ViolationsUtils.ts b/src/libs/Violations/ViolationsUtils.ts index 742b03c7554e..29516d3e560e 100644 --- a/src/libs/Violations/ViolationsUtils.ts +++ b/src/libs/Violations/ViolationsUtils.ts @@ -7,7 +7,7 @@ import * as TransactionUtils from '@libs/TransactionUtils'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {PolicyCategories, PolicyTagList, Transaction, TransactionViolation} from '@src/types/onyx'; +import type {PolicyCategories, PolicyTagList, Transaction, TransactionViolation, ViolationName} from '@src/types/onyx'; /** * Calculates tag out of policy and missing tag violations for the given transaction @@ -52,15 +52,29 @@ function getTagViolationsForSingleLevelTags( * Calculates missing tag violations for policies with dependent tags, * by returning one per tag with its corresponding tagName in the data */ -function getTagViolationsForDependentTags(policyTagList: PolicyTagList, transactionViolations: TransactionViolation[]) { - return [ - ...transactionViolations, - ...Object.values(policyTagList).map((tagList) => ({ - name: CONST.VIOLATIONS.MISSING_TAG, - type: CONST.VIOLATION_TYPES.VIOLATION, - data: {tagName: tagList.name}, - })), - ]; +function getTagViolationsForDependentTags(policyTagList: PolicyTagList, transactionViolations: TransactionViolation[], tagName: string | undefined) { + const tagViolations = [...transactionViolations]; + + if (!tagName) { + Object.values(policyTagList).forEach((tagList) => + tagViolations.push({ + name: CONST.VIOLATIONS.MISSING_TAG, + type: CONST.VIOLATION_TYPES.VIOLATION, + data: {tagName: tagList.name}, + }), + ); + } else { + const tags = TransactionUtils.getTagArrayFromName(tagName); + if (Object.keys(policyTagList).length !== tags.length || tags.includes('')) { + tagViolations.push({ + name: CONST.VIOLATIONS.ALL_TAG_LEVELS_REQUIRED, + type: CONST.VIOLATION_TYPES.VIOLATION, + data: undefined, + }); + } + } + + return tagViolations; } /** @@ -135,11 +149,13 @@ function getTagViolationsForMultiLevelTags( ): TransactionViolation[] { const filteredTransactionViolations = transactionViolations.filter( (violation) => - violation.name !== CONST.VIOLATIONS.SOME_TAG_LEVELS_REQUIRED && violation.name !== CONST.VIOLATIONS.TAG_OUT_OF_POLICY && violation.name !== CONST.VIOLATIONS.MISSING_TAG, + !( + [CONST.VIOLATIONS.SOME_TAG_LEVELS_REQUIRED, CONST.VIOLATIONS.TAG_OUT_OF_POLICY, CONST.VIOLATIONS.MISSING_TAG, CONST.VIOLATIONS.ALL_TAG_LEVELS_REQUIRED] as ViolationName[] + ).includes(violation.name), ); - if (hasDependentTags && !updatedTransaction.tag) { - return getTagViolationsForDependentTags(policyTagList, filteredTransactionViolations); + if (hasDependentTags) { + return getTagViolationsForDependentTags(policyTagList, filteredTransactionViolations, updatedTransaction.tag); } return getTagViolationForIndependentTags(policyTagList, filteredTransactionViolations, updatedTransaction); From e992245d7a2b00d9f8476b3f525fc3fe62c4a859 Mon Sep 17 00:00:00 2001 From: Cong Pham Date: Tue, 4 Jun 2024 07:48:31 +0700 Subject: [PATCH 071/230] fix eslint --- src/pages/signin/LoginForm/BaseLoginForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/signin/LoginForm/BaseLoginForm.tsx b/src/pages/signin/LoginForm/BaseLoginForm.tsx index e4c6479071c0..cb7fe92732c8 100644 --- a/src/pages/signin/LoginForm/BaseLoginForm.tsx +++ b/src/pages/signin/LoginForm/BaseLoginForm.tsx @@ -3,7 +3,6 @@ import Str from 'expensify-common/lib/str'; import type {ForwardedRef} from 'react'; import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react'; import {InteractionManager, View} from 'react-native'; -import * as Browser from '@libs/Browser'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import DotIndicatorMessage from '@components/DotIndicatorMessage'; @@ -20,6 +19,7 @@ import useNetwork from '@hooks/useNetwork'; import usePrevious from '@hooks/usePrevious'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; +import * as Browser from '@libs/Browser'; import canFocusInputOnScreenFocus from '@libs/canFocusInputOnScreenFocus'; import * as ErrorUtils from '@libs/ErrorUtils'; import isInputAutoFilled from '@libs/isInputAutoFilled'; From d22a1006cd4cdfabd9fbc631a79a56a829280659 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Tue, 4 Jun 2024 11:36:23 +0700 Subject: [PATCH 072/230] rename function --- src/libs/OptionsListUtils.ts | 14 +++++++------- src/pages/RoomMembersPage.tsx | 2 +- .../workflows/WorkspaceWorkflowsApproverPage.tsx | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index cdc70dcde9c6..db1b410e9f68 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -582,22 +582,22 @@ function getAlternateText(option: ReportUtils.OptionData, {showChatPreviewLine = : LocalePhoneNumber.formatPhoneNumber(option.participantsList && option.participantsList.length > 0 ? option.participantsList[0].login ?? '' : ''); } -function isUserMatchWithSearch(personalDetail: PersonalDetails, searchValue: string) { +function isSearchStringMatchUserDetails(personalDetail: PersonalDetails, searchValue: string) { let memberDetails = ''; if (personalDetail.login) { - memberDetails += ` ${personalDetail.login.toLowerCase()}`; + memberDetails += ` ${personalDetail.login}`.toLowerCase(); } if (personalDetail.firstName) { - memberDetails += ` ${personalDetail.firstName.toLowerCase()}`; + memberDetails += ` ${personalDetail.firstName}`.toLowerCase(); } if (personalDetail.lastName) { - memberDetails += ` ${personalDetail.lastName.toLowerCase()}`; + memberDetails += ` ${personalDetail.lastName}`.toLowerCase(); } if (personalDetail.displayName) { - memberDetails += ` ${PersonalDetailsUtils.getDisplayNameOrDefault(personalDetail).toLowerCase()}`; + memberDetails += ` ${PersonalDetailsUtils.getDisplayNameOrDefault(personalDetail)}`.toLowerCase(); } if (personalDetail.phoneNumber) { - memberDetails += ` ${personalDetail.phoneNumber.toLowerCase()}`; + memberDetails += ` ${personalDetail.phoneNumber}`.toLowerCase(); } return isSearchStringMatch(searchValue.trim(), memberDetails); } @@ -2456,7 +2456,7 @@ export { getPersonalDetailsForAccountIDs, getIOUConfirmationOptionsFromPayeePersonalDetail, getSearchText, - isUserMatchWithSearch, + isSearchStringMatchUserDetails, getAllReportErrors, getPolicyExpenseReportOption, getParticipantsOption, diff --git a/src/pages/RoomMembersPage.tsx b/src/pages/RoomMembersPage.tsx index fed8d63fbd51..f981e4d9a086 100644 --- a/src/pages/RoomMembersPage.tsx +++ b/src/pages/RoomMembersPage.tsx @@ -183,7 +183,7 @@ function RoomMembersPage({report, session, policies}: RoomMembersPageProps) { } // If search value is provided, filter out members that don't match the search value - if (searchValue.trim() && !OptionsListUtils.isUserMatchWithSearch(details, searchValue)) { + if (searchValue.trim() && !OptionsListUtils.isSearchStringMatchUserDetails(details, searchValue)) { return; } const pendingChatMember = report?.pendingChatMembers?.findLast((member) => member.accountID === accountID.toString()); diff --git a/src/pages/workspace/workflows/WorkspaceWorkflowsApproverPage.tsx b/src/pages/workspace/workflows/WorkspaceWorkflowsApproverPage.tsx index 04d39c489bf3..faebbaece7ba 100644 --- a/src/pages/workspace/workflows/WorkspaceWorkflowsApproverPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceWorkflowsApproverPage.tsx @@ -69,7 +69,7 @@ function WorkspaceWorkflowsApproverPage({policy, personalDetails, isLoadingRepor return; } const searchValue = OptionsListUtils.getSearchValueForPhoneOrEmail(searchTerm); - if (searchValue.trim() && !OptionsListUtils.isUserMatchWithSearch(details, searchValue)) { + if (searchValue.trim() && !OptionsListUtils.isSearchStringMatchUserDetails(details, searchValue)) { return; } From ab52e6a0553d3a48c4ffe5e03e3e30f009b3c72d Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Tue, 4 Jun 2024 12:47:46 +0800 Subject: [PATCH 073/230] lint --- src/components/MoneyReportHeader.tsx | 6 ++++-- src/components/MoneyRequestHeader.tsx | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 13acc98b191c..193b4091c98f 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -8,6 +8,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import * as HeaderUtils from '@libs/HeaderUtils'; +import Navigation from '@libs/Navigation/Navigation'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; @@ -16,7 +17,8 @@ import * as IOU from '@userActions/IOU'; import * as TransactionActions from '@userActions/Transaction'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES, { Route } from '@src/ROUTES'; +import ROUTES from '@src/ROUTES'; +import type {Route} from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -369,7 +371,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea if (!navigateBackToAfterDelete.current) { return; } - Navigation.goBack(navigateBackToAfterDelete.current) + Navigation.goBack(navigateBackToAfterDelete.current); }} prompt={translate('iou.deleteConfirmation')} confirmText={translate('common.delete')} diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index abc5f1de2773..a013b60eb0e7 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -261,7 +261,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, shouldUseNarrow if (!navigateBackToAfterDelete.current) { return; } - Navigation.goBack(navigateBackToAfterDelete.current) + Navigation.goBack(navigateBackToAfterDelete.current); }} prompt={translate('iou.deleteConfirmation')} confirmText={translate('common.delete')} From af663bcc107e0d9b0f07b2f8f2d79da6d7934de4 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Tue, 4 Jun 2024 13:36:50 +0800 Subject: [PATCH 074/230] update test --- tests/actions/IOUTest.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 940d533b9d2b..442a3eaf898f 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -2958,8 +2958,9 @@ describe('actions/IOU', () => { mockFetch?.pause?.(); + let navigateToAfterDelete; if (transaction && createIOUAction) { - IOU.deleteMoneyRequest(transaction.transactionID, createIOUAction, true); + navigateToAfterDelete = IOU.deleteMoneyRequest(transaction.transactionID, createIOUAction, true); } await waitForBatchedUpdates(); @@ -3001,16 +3002,17 @@ describe('actions/IOU', () => { // Then we expect to navigate to the iou report - expect(Navigation.goBack).toHaveBeenCalledWith(ROUTES.REPORT_WITH_ID.getRoute(IOU_REPORT_ID)); + expect(navigateToAfterDelete).toEqual(ROUTES.REPORT_WITH_ID.getRoute(IOU_REPORT_ID)); }); it('navigate the user correctly to the chat Report when appropriate', () => { + let navigateToAfterDelete; if (transaction && createIOUAction) { // When we delete the expense and we should delete the IOU report - IOU.deleteMoneyRequest(transaction.transactionID, createIOUAction, false); + navigateToAfterDelete = IOU.deleteMoneyRequest(transaction.transactionID, createIOUAction, false); } // Then we expect to navigate to the chat report - expect(Navigation.goBack).toHaveBeenCalledWith(ROUTES.REPORT_WITH_ID.getRoute(chatReport?.reportID ?? '')); + expect(navigateToAfterDelete).toEqual(ROUTES.REPORT_WITH_ID.getRoute(chatReport?.reportID ?? '')); }); }); From 31e8419b3054f746e3e6050259e29c5b8e617f35 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Tue, 4 Jun 2024 13:44:43 +0800 Subject: [PATCH 075/230] remove unused import --- tests/actions/IOUTest.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 442a3eaf898f..ad4be418a95a 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -10,7 +10,6 @@ import * as Report from '@src/libs/actions/Report'; import * as ReportActions from '@src/libs/actions/ReportActions'; import * as User from '@src/libs/actions/User'; import DateUtils from '@src/libs/DateUtils'; -import Navigation from '@src/libs/Navigation/Navigation'; import * as NumberUtils from '@src/libs/NumberUtils'; import * as PersonalDetailsUtils from '@src/libs/PersonalDetailsUtils'; import * as ReportActionsUtils from '@src/libs/ReportActionsUtils'; From d4e7d5d6f98614885aefade9cb335791934512c5 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Tue, 4 Jun 2024 13:10:09 +0700 Subject: [PATCH 076/230] fix lint --- src/pages/home/report/ReportActionsList.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx index 6ad01455b543..940feb99bc27 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -210,9 +210,6 @@ function ReportActionsList({ [sortedReportActions, isOffline], ); - // whisper action doesn't affect lastVisibleActionCreated, so we should not take it into account while checking if there is the newest report action - // const newestVisibleReportAction = useMemo(() => sortedVisibleReportActions.find((item) => !ReportActionsUtils.isWhisperAction(item)) ?? null, [sortedVisibleReportActions]); - const lastActionIndex = sortedVisibleReportActions[0]?.reportActionID; const reportActionSize = useRef(sortedVisibleReportActions.length); const hasNewestReportAction = sortedVisibleReportActions[0]?.created === report.lastVisibleActionCreated; @@ -220,8 +217,6 @@ function ReportActionsList({ hasNewestReportActionRef.current = hasNewestReportAction; const previousLastIndex = useRef(lastActionIndex); - console.log(sortedVisibleReportActions[0].created, report.lastVisibleActionCreated) - const isLastPendingActionIsDelete = sortedReportActions?.[0]?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; const linkedReportActionID = route.params?.reportActionID ?? ''; @@ -348,7 +343,6 @@ function ReportActionsList({ (isFromCurrentUser: boolean) => { // If a new comment is added and it's from the current user scroll to the bottom otherwise leave the user positioned where // they are now in the list. - console.log(hasNewestReportActionRef.current); if (!isFromCurrentUser || !hasNewestReportActionRef.current) { return; } From 224ea5ae9f8fb816d2bf270207b9338aa5fc6459 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Tue, 4 Jun 2024 13:44:22 +0700 Subject: [PATCH 077/230] fix: logic match user detail --- src/libs/OptionsListUtils.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index db1b410e9f68..6307a0c3d20f 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -585,21 +585,21 @@ function getAlternateText(option: ReportUtils.OptionData, {showChatPreviewLine = function isSearchStringMatchUserDetails(personalDetail: PersonalDetails, searchValue: string) { let memberDetails = ''; if (personalDetail.login) { - memberDetails += ` ${personalDetail.login}`.toLowerCase(); + memberDetails += ` ${personalDetail.login}`; } if (personalDetail.firstName) { - memberDetails += ` ${personalDetail.firstName}`.toLowerCase(); + memberDetails += ` ${personalDetail.firstName}`; } if (personalDetail.lastName) { - memberDetails += ` ${personalDetail.lastName}`.toLowerCase(); + memberDetails += ` ${personalDetail.lastName}`; } if (personalDetail.displayName) { - memberDetails += ` ${PersonalDetailsUtils.getDisplayNameOrDefault(personalDetail)}`.toLowerCase(); + memberDetails += ` ${PersonalDetailsUtils.getDisplayNameOrDefault(personalDetail)}`; } if (personalDetail.phoneNumber) { - memberDetails += ` ${personalDetail.phoneNumber}`.toLowerCase(); + memberDetails += ` ${personalDetail.phoneNumber}`; } - return isSearchStringMatch(searchValue.trim(), memberDetails); + return isSearchStringMatch(searchValue.trim(), memberDetails.toLowerCase()); } /** From 7392bd5bb7c192f0055f089cbcfeecc0941b971f Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Tue, 4 Jun 2024 15:53:27 +0700 Subject: [PATCH 078/230] fix perf test --- src/components/ReportActionItem/ReportPreview.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 1fa16a73cff2..e97389b23ecc 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -171,7 +171,7 @@ function ReportPreview({ [chatReport?.isOwnPolicyExpenseChat, policy?.harvesting?.enabled], ); - const confirmPayment = (type?: PaymentMethodType | undefined) => { + const confirmPayment = (type: PaymentMethodType | undefined) => { if (!type) { return; } @@ -400,7 +400,9 @@ function ReportPreview({ policyID={policyID} chatReportID={chatReportID} iouReport={iouReport} - onPress={confirmPayment} + onPress={(type) => { + confirmPayment(type); + }} confirmApproval={confirmApproval} enablePaymentsRoute={ROUTES.ENABLE_PAYMENTS} addBankAccountRoute={bankAccountRoute} From fd6d0f77d23ba1d839487e31b527e9bcda3f5e57 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Tue, 4 Jun 2024 15:58:44 +0700 Subject: [PATCH 079/230] update suggestion --- src/components/ReportActionItem/ReportPreview.tsx | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index e97389b23ecc..944b846f29f5 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -179,9 +179,8 @@ function ReportPreview({ setRequestType(CONST.IOU.ACTION_TYPE.PAY); if (ReportUtils.hasHeldExpenses(iouReport?.reportID)) { setIsHoldMenuVisible(true); - } else if (chatReport) { - // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style - IOU.payMoneyRequest(type, chatReport, iouReport as Report, false); + } else if (chatReport && iouReport) { + IOU.payMoneyRequest(type, chatReport, iouReport, false); } }; @@ -400,9 +399,7 @@ function ReportPreview({ policyID={policyID} chatReportID={chatReportID} iouReport={iouReport} - onPress={(type) => { - confirmPayment(type); - }} + onPress={confirmPayment} confirmApproval={confirmApproval} enablePaymentsRoute={ROUTES.ENABLE_PAYMENTS} addBankAccountRoute={bankAccountRoute} @@ -434,7 +431,7 @@ function ReportPreview({ - {isHoldMenuVisible && !!iouReport && requestType !== undefined && ( + {isHoldMenuVisible && iouReport && requestType !== undefined && ( Date: Tue, 4 Jun 2024 16:31:37 +0700 Subject: [PATCH 080/230] fix perf test --- src/libs/ReportUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 848376fb4b6d..d095defe8bcd 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -6399,8 +6399,8 @@ function getNonHeldAndFullAmount(iouReport: OnyxEntry, policy: OnyxEntry } return [ - CurrencyUtils.convertToDisplayString((iouReport?.unheldTotal ?? 0) * -1, iouReport?.currency ?? ''), - CurrencyUtils.convertToDisplayString((iouReport?.total ?? 0) * -1, iouReport?.currency ?? ''), + CurrencyUtils.convertToDisplayString((iouReport?.unheldTotal ?? 0) * -1, iouReport?.currency ?? CONST.CURRENCY.USD), + CurrencyUtils.convertToDisplayString((iouReport?.total ?? 0) * -1, iouReport?.currency ?? CONST.CURRENCY.USD), ]; } From e7969dee6158575ffa6becde8f3ad893edcda0d1 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Tue, 4 Jun 2024 16:56:31 +0700 Subject: [PATCH 081/230] fix type --- src/CONST.ts | 4 ---- src/components/MoneyReportHeader.tsx | 8 ++++---- src/components/ProcessMoneyReportHoldMenu.tsx | 7 +++++-- src/components/ReportActionItem/ReportPreview.tsx | 8 ++++---- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 2e6ef61daa88..aa3ade14b040 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1552,10 +1552,6 @@ const CONST = { CATEGORIZE: 'categorize', SHARE: 'share', }, - ACTION_TYPE: { - APPROVE: 'approve', - PAY: 'pay', - }, DEFAULT_AMOUNT: 0, TYPE: { SEND: 'send', diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 8c866731d150..47ea32d7fa41 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -19,8 +19,8 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage'; -import type DeepValueOf from '@src/types/utils/DeepValueOf'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import type {ActionHandledType} from './ProcessMoneyReportHoldMenu'; import Button from './Button'; import ConfirmModal from './ConfirmModal'; import HeaderWithBackButton from './HeaderWithBackButton'; @@ -80,7 +80,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea isActionOwner && (ReportUtils.canAddOrDeleteTransactions(moneyRequestReport) || ReportUtils.isTrackExpenseReport(transactionThreadReport)) && !isDeletedParentAction; const [isHoldMenuVisible, setIsHoldMenuVisible] = useState(false); const [paymentType, setPaymentType] = useState(); - const [requestType, setRequestType] = useState>(); + const [requestType, setRequestType] = useState(); const canAllowSettlement = ReportUtils.hasUpdatedTotal(moneyRequestReport, policy); const policyType = policy?.type; const isPayer = ReportUtils.isPayer(session, moneyRequestReport); @@ -125,7 +125,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea return; } setPaymentType(type); - setRequestType(CONST.IOU.ACTION_TYPE.PAY); + setRequestType(CONST.IOU.REPORT_ACTION_TYPE.PAY); if (ReportUtils.hasHeldExpenses(moneyRequestReport.reportID)) { setIsHoldMenuVisible(true); } else if (chatReport) { @@ -134,7 +134,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea }; const confirmApproval = () => { - setRequestType(CONST.IOU.ACTION_TYPE.APPROVE); + setRequestType(CONST.IOU.REPORT_ACTION_TYPE.APPROVE); if (ReportUtils.hasHeldExpenses(moneyRequestReport.reportID)) { setIsHoldMenuVisible(true); } else { diff --git a/src/components/ProcessMoneyReportHoldMenu.tsx b/src/components/ProcessMoneyReportHoldMenu.tsx index f3e7790a6b5f..01896fb0a3cb 100644 --- a/src/components/ProcessMoneyReportHoldMenu.tsx +++ b/src/components/ProcessMoneyReportHoldMenu.tsx @@ -11,6 +11,8 @@ import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage'; import type DeepValueOf from '@src/types/utils/DeepValueOf'; import DecisionModal from './DecisionModal'; +type ActionHandledType = DeepValueOf; + type ProcessMoneyReportHoldMenuProps = { /** The chat report this report is linked to */ chatReport: OnyxEntry; @@ -37,7 +39,7 @@ type ProcessMoneyReportHoldMenuProps = { paymentType?: PaymentMethodType; /** Type of action handled */ - requestType?: DeepValueOf; + requestType?: ActionHandledType; }; function ProcessMoneyReportHoldMenu({ @@ -52,7 +54,7 @@ function ProcessMoneyReportHoldMenu({ moneyRequestReport, }: ProcessMoneyReportHoldMenuProps) { const {translate} = useLocalize(); - const isApprove = requestType === CONST.IOU.ACTION_TYPE.APPROVE; + const isApprove = requestType === CONST.IOU.REPORT_ACTION_TYPE.APPROVE; const onSubmit = (full: boolean) => { if (isApprove) { @@ -84,3 +86,4 @@ function ProcessMoneyReportHoldMenu({ ProcessMoneyReportHoldMenu.displayName = 'ProcessMoneyReportHoldMenu'; export default ProcessMoneyReportHoldMenu; +export type {ActionHandledType}; diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 944b846f29f5..2ac69fd01b0c 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -10,6 +10,7 @@ import * as Expensicons from '@components/Icon/Expensicons'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import ProcessMoneyReportHoldMenu from '@components/ProcessMoneyReportHoldMenu'; +import type {ActionHandledType} from '@components/ProcessMoneyReportHoldMenu'; import SettlementButton from '@components/SettlementButton'; import {showContextMenuForReport} from '@components/ShowContextMenuContext'; import Text from '@components/Text'; @@ -35,7 +36,6 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {Policy, Report, ReportAction, Transaction, TransactionViolations, UserWallet} from '@src/types/onyx'; import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage'; -import type DeepValueOf from '@src/types/utils/DeepValueOf'; import type {PendingMessageProps} from './MoneyRequestPreview/types'; import ReportActionItemImages from './ReportActionItemImages'; @@ -123,7 +123,7 @@ function ReportPreview({ ); const [isHoldMenuVisible, setIsHoldMenuVisible] = useState(false); - const [requestType, setRequestType] = useState>(); + const [requestType, setRequestType] = useState(); const [nonHeldAmount, fullAmount] = ReportUtils.getNonHeldAndFullAmount(iouReport, policy); const {isSmallScreenWidth} = useWindowDimensions(); const [paymentType, setPaymentType] = useState(); @@ -176,7 +176,7 @@ function ReportPreview({ return; } setPaymentType(type); - setRequestType(CONST.IOU.ACTION_TYPE.PAY); + setRequestType(CONST.IOU.REPORT_ACTION_TYPE.PAY); if (ReportUtils.hasHeldExpenses(iouReport?.reportID)) { setIsHoldMenuVisible(true); } else if (chatReport && iouReport) { @@ -185,7 +185,7 @@ function ReportPreview({ }; const confirmApproval = () => { - setRequestType(CONST.IOU.ACTION_TYPE.APPROVE); + setRequestType(CONST.IOU.REPORT_ACTION_TYPE.APPROVE); if (ReportUtils.hasHeldExpenses(iouReport?.reportID)) { setIsHoldMenuVisible(true); } else { From 7ccb8ffe1ec699de8a34997b33f8500c92825ea9 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Tue, 4 Jun 2024 16:58:36 +0700 Subject: [PATCH 082/230] refactor suggestion --- src/libs/ReportUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index d095defe8bcd..99798c76d794 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -6399,8 +6399,8 @@ function getNonHeldAndFullAmount(iouReport: OnyxEntry, policy: OnyxEntry } return [ - CurrencyUtils.convertToDisplayString((iouReport?.unheldTotal ?? 0) * -1, iouReport?.currency ?? CONST.CURRENCY.USD), - CurrencyUtils.convertToDisplayString((iouReport?.total ?? 0) * -1, iouReport?.currency ?? CONST.CURRENCY.USD), + CurrencyUtils.convertToDisplayString((iouReport?.unheldTotal ?? 0) * -1, iouReport?.currency), + CurrencyUtils.convertToDisplayString((iouReport?.total ?? 0) * -1, iouReport?.currency), ]; } From 28220dd7e4e44522ffee29751db743d366f00982 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Tue, 4 Jun 2024 17:09:20 +0700 Subject: [PATCH 083/230] run prettier --- src/components/MoneyReportHeader.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 47ea32d7fa41..2e118f79f963 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -20,7 +20,6 @@ import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; -import type {ActionHandledType} from './ProcessMoneyReportHoldMenu'; import Button from './Button'; import ConfirmModal from './ConfirmModal'; import HeaderWithBackButton from './HeaderWithBackButton'; @@ -28,6 +27,7 @@ import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; import MoneyReportHeaderStatusBar from './MoneyReportHeaderStatusBar'; import MoneyRequestHeaderStatusBar from './MoneyRequestHeaderStatusBar'; +import type {ActionHandledType} from './ProcessMoneyReportHoldMenu'; import ProcessMoneyReportHoldMenu from './ProcessMoneyReportHoldMenu'; import SettlementButton from './SettlementButton'; From 9b431fa629fca5e74af4e848ba7a3fe452bdec42 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Tue, 4 Jun 2024 15:19:01 +0200 Subject: [PATCH 084/230] navigate to home in native devices --- .../BottomTabBar/index.tsx | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.tsx b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.tsx index ac4c0156f17c..c0679b120d0d 100644 --- a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.tsx +++ b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.tsx @@ -1,5 +1,5 @@ import {useNavigation, useNavigationState} from '@react-navigation/native'; -import React, {useCallback, useEffect} from 'react'; +import React, {useEffect} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; @@ -24,7 +24,6 @@ import * as Welcome from '@userActions/Welcome'; import CONST from '@src/CONST'; import NAVIGATORS from '@src/NAVIGATORS'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Route} from '@src/ROUTES'; import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; @@ -68,16 +67,13 @@ function BottomTabBar({isLoadingApp = false}: PurposeForUsingExpensifyModalProps const chatTabBrickRoad = getChatTabBrickRoad(activeWorkspaceID); - const navigateToChats = useCallback(() => { - const route = activeWorkspaceID ? (`/w/${activeWorkspaceID}/r` as Route) : ROUTES.HOME; - Navigation.navigate(route); - }, [activeWorkspaceID]); - return ( { + Navigation.navigate(ROUTES.HOME); + }} role={CONST.ROLE.BUTTON} accessibilityLabel={translate('common.chats')} wrapperStyle={styles.flex1} From 7f647fbfd7c8d4bea13d3b4db815fc67ccce429d Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Tue, 4 Jun 2024 15:30:16 +0200 Subject: [PATCH 085/230] refactoring --- .../BottomTabBar/index.tsx | 12 ++++++++---- .../BottomTabBar/index.website.tsx | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.tsx b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.tsx index c0679b120d0d..e6c49f1411ab 100644 --- a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.tsx +++ b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.tsx @@ -1,5 +1,5 @@ import {useNavigation, useNavigationState} from '@react-navigation/native'; -import React, {useEffect} from 'react'; +import React, {useCallback, useEffect} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; @@ -24,6 +24,7 @@ import * as Welcome from '@userActions/Welcome'; import CONST from '@src/CONST'; import NAVIGATORS from '@src/NAVIGATORS'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {Route} from '@src/ROUTES'; import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; @@ -67,13 +68,16 @@ function BottomTabBar({isLoadingApp = false}: PurposeForUsingExpensifyModalProps const chatTabBrickRoad = getChatTabBrickRoad(activeWorkspaceID); + const navigateToChats = useCallback(() => { + const route = activeWorkspaceID ? (`/w/${activeWorkspaceID}/home` as Route) : ROUTES.HOME; + Navigation.navigate(route); + }, [activeWorkspaceID]); + return ( { - Navigation.navigate(ROUTES.HOME); - }} + onPress={navigateToChats} role={CONST.ROLE.BUTTON} accessibilityLabel={translate('common.chats')} wrapperStyle={styles.flex1} diff --git a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.website.tsx b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.website.tsx index 30f0c2766251..d989f3747f6b 100644 --- a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.website.tsx +++ b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.website.tsx @@ -70,7 +70,7 @@ function BottomTabBar({isLoadingApp = false}: PurposeForUsingExpensifyModalProps const chatTabBrickRoad = getChatTabBrickRoad(activeWorkspaceID as string | undefined); const navigateToChats = useCallback(() => { - const route = activeWorkspaceID ? (`/w/${activeWorkspaceID}/r` as Route) : ROUTES.HOME; + const route = activeWorkspaceID ? (`/w/${activeWorkspaceID}/home` as Route) : ROUTES.HOME; Navigation.navigate(route); }, [activeWorkspaceID]); From 8365690feaa4fba5fda352ec3a69f80ac5f00645 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 4 Jun 2024 18:24:35 +0200 Subject: [PATCH 086/230] fix: apply requested changes --- src/libs/API/parameters/CompleteGuidedSetupParams.ts | 1 + src/libs/actions/Report.ts | 1 + src/pages/OnboardingWork/BaseOnboardingWork.tsx | 2 -- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/API/parameters/CompleteGuidedSetupParams.ts b/src/libs/API/parameters/CompleteGuidedSetupParams.ts index e3a0309d5113..8e1273ac6053 100644 --- a/src/libs/API/parameters/CompleteGuidedSetupParams.ts +++ b/src/libs/API/parameters/CompleteGuidedSetupParams.ts @@ -3,6 +3,7 @@ import type {OnboardingPurposeType} from '@src/CONST'; type CompleteGuidedSetupParams = { firstName: string; lastName: string; + actorAccountID: number; guidedSetupData: string; engagementChoice: OnboardingPurposeType; }; diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 8bbe027f73c0..01216e91d3fc 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -3428,6 +3428,7 @@ function completeOnboarding( engagementChoice, firstName, lastName, + actorAccountID, guidedSetupData: JSON.stringify(guidedSetupData), }; diff --git a/src/pages/OnboardingWork/BaseOnboardingWork.tsx b/src/pages/OnboardingWork/BaseOnboardingWork.tsx index 3f89d10942f7..95dde3107a15 100644 --- a/src/pages/OnboardingWork/BaseOnboardingWork.tsx +++ b/src/pages/OnboardingWork/BaseOnboardingWork.tsx @@ -15,7 +15,6 @@ import useLocalize from '@hooks/useLocalize'; import useOnboardingLayout from '@hooks/useOnboardingLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; -import AccountUtils from '@libs/AccountUtils'; import * as ErrorUtils from '@libs/ErrorUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as ValidationUtils from '@libs/ValidationUtils'; @@ -34,7 +33,6 @@ function BaseOnboardingWork({shouldUseNativeStyles, onboardingPurposeSelected, o const {translate} = useLocalize(); const {isSmallScreenWidth} = useWindowDimensions(); const {shouldUseNarrowLayout} = useOnboardingLayout(); - const {accountID} = useSession(); useDisableModalDismissOnEscape(); From 717fe74ca2a9260856ede78d4e509d06962deb52 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 4 Jun 2024 19:00:34 +0200 Subject: [PATCH 087/230] fix: lint --- src/pages/OnboardingWork/BaseOnboardingWork.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/OnboardingWork/BaseOnboardingWork.tsx b/src/pages/OnboardingWork/BaseOnboardingWork.tsx index 95dde3107a15..b0e01d0c8caa 100644 --- a/src/pages/OnboardingWork/BaseOnboardingWork.tsx +++ b/src/pages/OnboardingWork/BaseOnboardingWork.tsx @@ -7,7 +7,6 @@ import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import KeyboardAvoidingView from '@components/KeyboardAvoidingView'; import OfflineIndicator from '@components/OfflineIndicator'; -import {useSession} from '@components/OnyxProvider'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; import useDisableModalDismissOnEscape from '@hooks/useDisableModalDismissOnEscape'; From 0dc7238a8a40007130fdde38de0a8b6b2387bff5 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 5 Jun 2024 02:06:21 +0700 Subject: [PATCH 088/230] fix: stale personal details while creating task with new user --- src/libs/ReportUtils.ts | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 7e99c60cb618..1bf4a005876a 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -6043,18 +6043,27 @@ function getTaskAssigneeChatOnyxData( }, ); - successData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${assigneeChatReportID}`, - value: { - pendingFields: { - createChat: null, + // BE will send different report's participants and assigneeAccountID. We clear the optimistic ones to avoid duplicated entries + successData.push( + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${assigneeChatReportID}`, + value: { + pendingFields: { + createChat: null, + }, + isOptimisticReport: false, + participants: {[assigneeAccountID]: null}, }, - isOptimisticReport: false, - // BE will send a different participant. We clear the optimistic one to avoid duplicated entries - participants: {[assigneeAccountID]: null}, }, - }); + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.PERSONAL_DETAILS_LIST, + value: { + [assigneeAccountID]: null, + }, + }, + ); failureData.push( { From b29c8fd4c543b48b7a0d2e199917f01401791ce2 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Wed, 5 Jun 2024 01:49:00 +0530 Subject: [PATCH 089/230] fix: Distance rates - Default distance rate is Disabled instead of Enabled after creating new WS. Signed-off-by: Krishna Gupta --- src/CONST.ts | 2 +- src/libs/actions/Policy/Policy.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/CONST.ts b/src/CONST.ts index 32c500962d8a..23ac96f0e573 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1826,7 +1826,7 @@ const CONST = { NAME_DISTANCE: 'Distance', DISTANCE_UNIT_MILES: 'mi', DISTANCE_UNIT_KILOMETERS: 'km', - MILEAGE_IRS_RATE: 0.655, + MILEAGE_IRS_RATE: 0.67, DEFAULT_RATE: 'Default Rate', RATE_DECIMALS: 3, FAKE_P2P_ID: '_FAKE_P2P_ID_', diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index 4d918352ba91..32c347925d84 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -2008,6 +2008,7 @@ function buildOptimisticCustomUnits(): OptimisticCustomUnits { name: CONST.CUSTOM_UNITS.DEFAULT_RATE, rate: CONST.CUSTOM_UNITS.MILEAGE_IRS_RATE * CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET, currency, + enabled: true, }, }, }, From dfad444c2dc7d3b1e97b9db130c4308739206b32 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Wed, 5 Jun 2024 14:51:09 +0700 Subject: [PATCH 090/230] resolve conflict --- .../ReportActionItem/ReportPreview.tsx | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 7d0ff591795b..84e1691b3abe 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -180,7 +180,11 @@ function ReportPreview({ if (ReportUtils.hasHeldExpenses(iouReport?.reportID)) { setIsHoldMenuVisible(true); } else if (chatReport && iouReport) { - IOU.payMoneyRequest(type, chatReport, iouReport, false); + if (ReportUtils.isInvoiceReport(iouReport)) { + IOU.payInvoice(type, chatReport, iouReport); + } else { + IOU.payMoneyRequest(type, chatReport, iouReport); + } } }; @@ -311,17 +315,6 @@ function ReportPreview({ }; }, [formattedMerchant, formattedDescription, moneyRequestComment, translate, numberOfRequests, numberOfScanningReceipts, numberOfPendingRequests]); - const confirmPayment = (paymentMethodType?: PaymentMethodType) => { - if (!paymentMethodType || !chatReport || !iouReport) { - return; - } - if (ReportUtils.isInvoiceReport(iouReport)) { - IOU.payInvoice(paymentMethodType, chatReport, iouReport); - } else { - IOU.payMoneyRequest(paymentMethodType, chatReport, iouReport); - } - }; - return ( Date: Wed, 5 Jun 2024 15:09:40 +0700 Subject: [PATCH 091/230] move update to buildOnyxDataForMoneyRequest --- src/libs/actions/IOU.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 1050fdb48779..fe3a5a8425dd 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -507,6 +507,7 @@ function buildOnyxDataForMoneyRequest( ...iouReport, lastMessageText: iouAction.message?.[0]?.text, lastMessageHtml: iouAction.message?.[0]?.html, + lastVisibleActionCreated: iouAction.created, pendingFields: { ...(shouldCreateNewMoneyRequestReport ? {createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD} : {preview: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), }, @@ -1991,10 +1992,6 @@ function getMoneyRequestInformation( iouReport.parentReportActionID = reportPreviewAction.reportActionID; } - if (iouAction) { - iouReport.lastVisibleActionCreated = iouAction.created; - } - const shouldCreateOptimisticPersonalDetails = isNewChatReport && !allPersonalDetails[payerAccountID]; // Add optimistic personal details for participant const optimisticPersonalDetailListAction = shouldCreateOptimisticPersonalDetails From 30189caf9c1239a176a45aa944d4550b23a5ba83 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Wed, 5 Jun 2024 11:07:15 +0100 Subject: [PATCH 092/230] refactor: apply pull request suggestions --- .../ReportActionItem/MoneyRequestView.tsx | 41 +++++++++++-------- src/hooks/useViolations.ts | 7 ++-- src/libs/Violations/ViolationsUtils.ts | 20 +++++---- src/types/onyx/TransactionViolation.ts | 1 - 4 files changed, 39 insertions(+), 30 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 434395e4095c..fbba403e8e0b 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -174,7 +174,8 @@ function MoneyRequestView({ const {getViolationsForField} = useViolations(transactionViolations ?? []); const hasViolations = useCallback( - (field: ViolationField, data?: OnyxTypes.TransactionViolation['data']): boolean => !!canUseViolations && getViolationsForField(field, data).length > 0, + (field: ViolationField, data?: OnyxTypes.TransactionViolation['data'], policyHasDependentTags?: boolean): boolean => + !!canUseViolations && getViolationsForField(field, data, policyHasDependentTags).length > 0, [canUseViolations, getViolationsForField], ); @@ -241,7 +242,7 @@ function MoneyRequestView({ const getPendingFieldAction = (fieldPath: TransactionPendingFieldsKey) => transaction?.pendingFields?.[fieldPath] ?? pendingAction; const getErrorForField = useCallback( - (field: ViolationField, data?: OnyxTypes.TransactionViolation['data']) => { + (field: ViolationField, data?: OnyxTypes.TransactionViolation['data'], policyHasDependentTags?: boolean) => { // Checks applied when creating a new expense // NOTE: receipt field can return multiple violations, so we need to handle it separately const fieldChecks: Partial> = { @@ -267,14 +268,14 @@ function MoneyRequestView({ } // Return violations if there are any - if (canUseViolations && hasViolations(field, data)) { - const violations = getViolationsForField(field, data); + if (hasViolations(field, data, policyHasDependentTags)) { + const violations = getViolationsForField(field, data, policyHasDependentTags); return ViolationsUtils.getViolationTranslation(violations[0], translate); } return ''; }, - [transactionAmount, isSettled, isCancelled, isPolicyExpenseChat, isEmptyMerchant, transactionDate, hasErrors, canUseViolations, hasViolations, translate, getViolationsForField], + [transactionAmount, isSettled, isCancelled, isPolicyExpenseChat, isEmptyMerchant, transactionDate, hasErrors, hasViolations, translate, getViolationsForField], ); const distanceRequestFields = canUseP2PDistanceRequests ? ( @@ -497,21 +498,27 @@ function MoneyRequestView({ ) } brickRoadIndicator={ - getErrorForField('tag', { - tagListIndex: index, - tagListName: name, - tagListValue: TransactionUtils.getTagForDisplay(transaction, index), - policyHasDependentTags: PolicyUtils.hasDependentTags(policy, policyTagList), - }) + getErrorForField( + 'tag', + { + tagListIndex: index, + tagListName: name, + tagListValue: TransactionUtils.getTagForDisplay(transaction, index), + }, + PolicyUtils.hasDependentTags(policy, policyTagList), + ) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined } - errorText={getErrorForField('tag', { - tagListIndex: index, - tagListName: name, - tagListValue: TransactionUtils.getTagForDisplay(transaction, index), - policyHasDependentTags: PolicyUtils.hasDependentTags(policy, policyTagList), - })} + errorText={getErrorForField( + 'tag', + { + tagListIndex: index, + tagListName: name, + tagListValue: TransactionUtils.getTagForDisplay(transaction, index), + }, + PolicyUtils.hasDependentTags(policy, policyTagList), + )} /> ))} diff --git a/src/hooks/useViolations.ts b/src/hooks/useViolations.ts index 7dad3be026fe..9120c0709d9a 100644 --- a/src/hooks/useViolations.ts +++ b/src/hooks/useViolations.ts @@ -62,7 +62,7 @@ function useViolations(violations: TransactionViolation[]) { }, [violations]); const getViolationsForField = useCallback( - (field: ViolationField, data?: TransactionViolation['data']) => { + (field: ViolationField, data?: TransactionViolation['data'], policyHasDependentTags?: boolean) => { const currentViolations = violationsByField.get(field) ?? []; // someTagLevelsRequired has special logic becase data.errorIndexes is a bit unique in how it denotes the tag list that has the violation @@ -81,7 +81,7 @@ function useViolations(violations: TransactionViolation[]) { // missingTag has special logic for policies with dependent tags, because only one violation is returned for all tags // when no tags are present, so the tag name isn't set in the violation data. That's why we add it here - if (data?.policyHasDependentTags && currentViolations[0]?.name === CONST.VIOLATIONS.MISSING_TAG && data?.tagListName) { + if (!!policyHasDependentTags && currentViolations[0]?.name === CONST.VIOLATIONS.MISSING_TAG && data?.tagListName) { return [ { ...currentViolations[0], @@ -98,7 +98,8 @@ function useViolations(violations: TransactionViolation[]) { return currentViolations.filter((violation) => violation.data?.tagName === data?.tagListName); } - // allTagLevelsRequired has special logic because we have to account for tags that are already filled + // allTagLevelsRequired has special logic because it is returned when one but not all the tags are set, + // so we need to return the violation for the tag fields without a tag set if (currentViolations[0]?.name === CONST.VIOLATIONS.ALL_TAG_LEVELS_REQUIRED && data?.tagListValue) { return currentViolations.filter((violation) => violation.data?.tagName === data?.tagListName); } diff --git a/src/libs/Violations/ViolationsUtils.ts b/src/libs/Violations/ViolationsUtils.ts index 29516d3e560e..d433d123c903 100644 --- a/src/libs/Violations/ViolationsUtils.ts +++ b/src/libs/Violations/ViolationsUtils.ts @@ -9,6 +9,13 @@ import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PolicyCategories, PolicyTagList, Transaction, TransactionViolation, ViolationName} from '@src/types/onyx'; +const TRANSACTION_VIOLATIONS_FILTER = [ + CONST.VIOLATIONS.SOME_TAG_LEVELS_REQUIRED, + CONST.VIOLATIONS.TAG_OUT_OF_POLICY, + CONST.VIOLATIONS.MISSING_TAG, + CONST.VIOLATIONS.ALL_TAG_LEVELS_REQUIRED, +] as ViolationName[]; + /** * Calculates tag out of policy and missing tag violations for the given transaction */ @@ -52,7 +59,7 @@ function getTagViolationsForSingleLevelTags( * Calculates missing tag violations for policies with dependent tags, * by returning one per tag with its corresponding tagName in the data */ -function getTagViolationsForDependentTags(policyTagList: PolicyTagList, transactionViolations: TransactionViolation[], tagName: string | undefined) { +function getTagViolationsForDependentTags(policyTagList: PolicyTagList, transactionViolations: TransactionViolation[], tagName: string) { const tagViolations = [...transactionViolations]; if (!tagName) { @@ -138,7 +145,7 @@ function getTagViolationForIndependentTags(policyTagList: PolicyTagList, transac } /** - * Calculates some tag levels required and missing tag violations for the given transaction + * Calculates tag violations for a transaction on a policy with multi level tags */ function getTagViolationsForMultiLevelTags( updatedTransaction: Transaction, @@ -147,15 +154,10 @@ function getTagViolationsForMultiLevelTags( policyTagList: PolicyTagList, hasDependentTags: boolean, ): TransactionViolation[] { - const filteredTransactionViolations = transactionViolations.filter( - (violation) => - !( - [CONST.VIOLATIONS.SOME_TAG_LEVELS_REQUIRED, CONST.VIOLATIONS.TAG_OUT_OF_POLICY, CONST.VIOLATIONS.MISSING_TAG, CONST.VIOLATIONS.ALL_TAG_LEVELS_REQUIRED] as ViolationName[] - ).includes(violation.name), - ); + const filteredTransactionViolations = transactionViolations.filter((violation) => !TRANSACTION_VIOLATIONS_FILTER.includes(violation.name)); if (hasDependentTags) { - return getTagViolationsForDependentTags(policyTagList, filteredTransactionViolations, updatedTransaction.tag); + return getTagViolationsForDependentTags(policyTagList, filteredTransactionViolations, updatedTransaction.tag ?? ''); } return getTagViolationForIndependentTags(policyTagList, filteredTransactionViolations, updatedTransaction); diff --git a/src/types/onyx/TransactionViolation.ts b/src/types/onyx/TransactionViolation.ts index 99ce3d86dfe1..e81f7196209b 100644 --- a/src/types/onyx/TransactionViolation.ts +++ b/src/types/onyx/TransactionViolation.ts @@ -10,7 +10,6 @@ type TransactionViolation = { type: string; name: ViolationName; data?: { - policyHasDependentTags?: boolean; rejectedBy?: string; rejectReason?: string; formattedLimit?: string; From 633a2855a05b3524c2ec8068586c0519b264464e Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Wed, 5 Jun 2024 15:09:42 +0200 Subject: [PATCH 093/230] feat: subscription size action --- .../SubscriptionDetails/index.tsx | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/src/pages/settings/Subscription/SubscriptionDetails/index.tsx b/src/pages/settings/Subscription/SubscriptionDetails/index.tsx index 350d84d00a46..ccf6b7cafa18 100644 --- a/src/pages/settings/Subscription/SubscriptionDetails/index.tsx +++ b/src/pages/settings/Subscription/SubscriptionDetails/index.tsx @@ -11,9 +11,11 @@ import Section from '@components/Section'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@libs/Navigation/Navigation'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; type SubscriptionVariant = ValueOf; @@ -44,33 +46,30 @@ function SubscriptionDetails() { setSelectedOption(option); }; - // This section is only shown when the subscription is annual - // An onPress action is going to be assigned to these buttons in phase 2 - let subscriptionSizeSection: React.JSX.Element | null = null; + const onSubscriptionSizePress = () => { + Navigation.navigate(ROUTES.SETTINGS_SUBSCRIPTION_SIZE); + }; - if (privateSubscription?.type === CONST.SUBSCRIPTION.TYPE.ANNUAL) { - subscriptionSizeSection = privateSubscription?.userCount ? ( - - ) : ( + // This section is only shown when the subscription is annual + const subscriptionSizeSection: React.JSX.Element | null = + privateSubscription?.type === CONST.SUBSCRIPTION.TYPE.ANNUAL ? ( <> - - {translate('subscription.details.headsUpTitle')} - {translate('subscription.details.headsUpBody')} - + {!privateSubscription?.userCount && ( + + {translate('subscription.details.headsUpTitle')} + {translate('subscription.details.headsUpBody')} + + )} - ); - } + ) : null; return (
Date: Wed, 5 Jun 2024 16:13:43 +0200 Subject: [PATCH 094/230] create usePreferredCurrency and useSubscriptionPrice hooks, display dynamic price --- src/CONST.ts | 7 +++ src/hooks/usePreferredCurrency.ts | 49 +++++++++++++++ src/hooks/useSubscriptionPrice.ts | 62 +++++++++++++++++++ src/languages/en.ts | 8 +-- .../Subscription/SubscriptionPlan.tsx | 14 ++++- src/types/onyx/Fund.ts | 3 +- 6 files changed, 136 insertions(+), 7 deletions(-) create mode 100644 src/hooks/usePreferredCurrency.ts create mode 100644 src/hooks/useSubscriptionPrice.ts diff --git a/src/CONST.ts b/src/CONST.ts index bc8f627630a3..b652740667bc 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -4814,6 +4814,13 @@ const CONST = { }, SUBSCRIPTION_SIZE_LIMIT: 20000, + + PAYMENT_CARD_CURRENCY: { + USD: 'USD', + AUD: 'AUD', + GBP: 'GBP', + NZD: 'NZD', + }, } as const; type Country = keyof typeof CONST.ALL_COUNTRIES; diff --git a/src/hooks/usePreferredCurrency.ts b/src/hooks/usePreferredCurrency.ts new file mode 100644 index 000000000000..11675d4de9c1 --- /dev/null +++ b/src/hooks/usePreferredCurrency.ts @@ -0,0 +1,49 @@ +import {useMemo} from 'react'; +import {useOnyx} from 'react-native-onyx'; +import type {ValueOf} from 'type-fest'; +import {getCurrencySymbol} from '@libs/CurrencyUtils'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; + +type PreferredCurrency = { + name: ValueOf; + symbol?: string; +}; + +function usePreferredCurrency(): PreferredCurrency { + const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); + const [session] = useOnyx(ONYXKEYS.SESSION); + const [fundList] = useOnyx(ONYXKEYS.FUND_LIST); + + const defaultCard = useMemo(() => Object.values(fundList ?? {}).find((card) => card.isDefault), [fundList]); + + if (defaultCard?.accountData?.currency) { + return { + name: defaultCard?.accountData?.currency, + symbol: getCurrencySymbol(defaultCard?.accountData?.currency), + }; + } + + if (!session?.accountID) { + return { + name: CONST.PAYMENT_CARD_CURRENCY.USD, + symbol: getCurrencySymbol(CONST.PAYMENT_CARD_CURRENCY.USD), + }; + } + + const currentUserLocalCurrency = personalDetails?.[session.accountID]?.localCurrencyCode ?? CONST.PAYMENT_CARD_CURRENCY.USD; + + if (!(Object.values(CONST.PAYMENT_CARD_CURRENCY) as string[]).includes(currentUserLocalCurrency)) { + return { + name: CONST.PAYMENT_CARD_CURRENCY.USD, + symbol: getCurrencySymbol(CONST.PAYMENT_CARD_CURRENCY.USD), + }; + } + + return { + name: currentUserLocalCurrency, + symbol: getCurrencySymbol(currentUserLocalCurrency), + } as PreferredCurrency; +} + +export default usePreferredCurrency; diff --git a/src/hooks/useSubscriptionPrice.ts b/src/hooks/useSubscriptionPrice.ts new file mode 100644 index 000000000000..c7c36d100a9c --- /dev/null +++ b/src/hooks/useSubscriptionPrice.ts @@ -0,0 +1,62 @@ +import {useOnyx} from 'react-native-onyx'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import usePreferredCurrency from './usePreferredCurrency'; +import useSubscriptionPlan from './useSubscriptionPlan'; + +const SUBSCRIPTION_PRICES = { + [CONST.PAYMENT_CARD_CURRENCY.USD]: { + [CONST.POLICY.TYPE.CORPORATE]: { + [CONST.SUBSCRIPTION.TYPE.ANNUAL]: 900, + [CONST.SUBSCRIPTION.TYPE.PAYPERUSE]: 1800, + }, + [CONST.POLICY.TYPE.TEAM]: { + [CONST.SUBSCRIPTION.TYPE.ANNUAL]: 500, + [CONST.SUBSCRIPTION.TYPE.PAYPERUSE]: 1000, + }, + }, + [CONST.PAYMENT_CARD_CURRENCY.AUD]: { + [CONST.POLICY.TYPE.CORPORATE]: { + [CONST.SUBSCRIPTION.TYPE.ANNUAL]: 1500, + [CONST.SUBSCRIPTION.TYPE.PAYPERUSE]: 3000, + }, + [CONST.POLICY.TYPE.TEAM]: { + [CONST.SUBSCRIPTION.TYPE.ANNUAL]: 700, + [CONST.SUBSCRIPTION.TYPE.PAYPERUSE]: 1400, + }, + }, + [CONST.PAYMENT_CARD_CURRENCY.GBP]: { + [CONST.POLICY.TYPE.CORPORATE]: { + [CONST.SUBSCRIPTION.TYPE.ANNUAL]: 700, + [CONST.SUBSCRIPTION.TYPE.PAYPERUSE]: 1400, + }, + [CONST.POLICY.TYPE.TEAM]: { + [CONST.SUBSCRIPTION.TYPE.ANNUAL]: 400, + [CONST.SUBSCRIPTION.TYPE.PAYPERUSE]: 800, + }, + }, + [CONST.PAYMENT_CARD_CURRENCY.NZD]: { + [CONST.POLICY.TYPE.CORPORATE]: { + [CONST.SUBSCRIPTION.TYPE.ANNUAL]: 1600, + [CONST.SUBSCRIPTION.TYPE.PAYPERUSE]: 3200, + }, + [CONST.POLICY.TYPE.TEAM]: { + [CONST.SUBSCRIPTION.TYPE.ANNUAL]: 800, + [CONST.SUBSCRIPTION.TYPE.PAYPERUSE]: 1600, + }, + }, +} as const; + +function useSubscriptionPrice() { + const preferredCurrency = usePreferredCurrency(); + const subscriptionPlan = useSubscriptionPlan(); + const [privateSubscription] = useOnyx(ONYXKEYS.NVP_PRIVATE_SUBSCRIPTION); + + if (!subscriptionPlan || !privateSubscription?.type) { + return 0; + } + + return SUBSCRIPTION_PRICES[preferredCurrency.name][subscriptionPlan][privateSubscription.type]; +} + +export default useSubscriptionPrice; diff --git a/src/languages/en.ts b/src/languages/en.ts index 8ed0ef8207f1..ce8cd39ad3ef 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -3176,8 +3176,8 @@ export default { title: 'Your plan', collect: { title: 'Collect', - priceAnnual: 'From $5/active member with the Expensify Card, $10/active member without the Expensify Card.', - pricePayPerUse: 'From $10/active member with the Expensify Card, $20/active member without the Expensify Card.', + priceAnnual: ({price}) => `From $${price}/active member with the Expensify Card, $${price * 2}/active member without the Expensify Card.`, + pricePayPerUse: ({price}) => `From $${price}/active member with the Expensify Card, $${price * 2}/active member without the Expensify Card.`, benefit1: 'Unlimited SmartScans and distance tracking', benefit2: 'Expensify Cards with Smart Limits', benefit3: 'Bill pay and invoicing', @@ -3188,8 +3188,8 @@ export default { }, control: { title: 'Control', - priceAnnual: 'From $9/active member with the Expensify Card, $18/active member without the Expensify Card.', - pricePayPerUse: 'From $18/active member with the Expensify Card, $36/active member without the Expensify Card.', + priceAnnual: ({price}) => `From $${price}/active member with the Expensify Card, $${price * 2}/active member without the Expensify Card.`, + pricePayPerUse: ({price}) => `From $${price}/active member with the Expensify Card, $${price * 2}/active member without the Expensify Card.`, benefit1: 'Everything in Collect, plus:', benefit2: 'NetSuite and Sage Intacct integrations', benefit3: 'Certinia and Workday sync', diff --git a/src/pages/settings/Subscription/SubscriptionPlan.tsx b/src/pages/settings/Subscription/SubscriptionPlan.tsx index 0834d2b89e15..dd85eab06b1b 100644 --- a/src/pages/settings/Subscription/SubscriptionPlan.tsx +++ b/src/pages/settings/Subscription/SubscriptionPlan.tsx @@ -7,7 +7,9 @@ import * as Illustrations from '@components/Icon/Illustrations'; import Section from '@components/Section'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; +import usePreferredCurrency from '@hooks/usePreferredCurrency'; import useSubscriptionPlan from '@hooks/useSubscriptionPlan'; +import useSubscriptionPrice from '@hooks/useSubscriptionPrice'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import variables from '@styles/variables'; @@ -20,9 +22,15 @@ function SubscriptionPlan() { const styles = useThemeStyles(); const theme = useTheme(); - const subscriptionPlan = useSubscriptionPlan(); const [privateSubscription] = useOnyx(ONYXKEYS.NVP_PRIVATE_SUBSCRIPTION); + const subscriptionPlan = useSubscriptionPlan(); + const subscriptionPrice = useSubscriptionPrice(); + const preferredCurrency = usePreferredCurrency(); + + console.log('SUBSCRIPTION PRICE', subscriptionPrice); + console.log('PREFERRED CURRENCY', preferredCurrency); + const isCollect = subscriptionPlan === CONST.POLICY.TYPE.TEAM; const isAnnual = privateSubscription?.type === CONST.SUBSCRIPTION.TYPE.ANNUAL; @@ -59,7 +67,9 @@ function SubscriptionPlan() { /> {translate(`subscription.yourPlan.${isCollect ? 'collect' : 'control'}.title`)} - {translate(`subscription.yourPlan.${isCollect ? 'collect' : 'control'}.${isAnnual ? 'priceAnnual' : 'pricePayPerUse'}`)} + {translate(`subscription.yourPlan.${isCollect ? 'collect' : 'control'}.${isAnnual ? 'priceAnnual' : 'pricePayPerUse'}`, { + price: subscriptionPrice / 100, + })} {benefitsList.map((benefit) => ( ; fundID?: number; bank?: BankName; }; From 7fcaeca565775b670ff86fbee4ab902fe1ef7480 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Wed, 5 Jun 2024 21:59:29 +0700 Subject: [PATCH 095/230] merge main --- src/libs/DateUtils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/DateUtils.ts b/src/libs/DateUtils.ts index 50cb9a20dff6..032a8261bec9 100644 --- a/src/libs/DateUtils.ts +++ b/src/libs/DateUtils.ts @@ -380,7 +380,8 @@ function getDBTime(timestamp: string | number = ''): string { */ function getDBTimeWithSkew(timestamp: string | number = ''): string { if (networkTimeSkew > 0) { - return getDBTime(new Date(timestamp).valueOf() + networkTimeSkew); + const datetime = timestamp ? new Date(timestamp) : new Date(); + return getDBTime(datetime.valueOf() + networkTimeSkew); } return getDBTime(timestamp); } From 730a852ea0ddb0b426bd7d8205277d4db366e451 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 6 Jun 2024 10:27:58 +0700 Subject: [PATCH 096/230] fix: App is stuck offline after 'Force offline' toggled on and off --- src/libs/NetworkConnection.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/NetworkConnection.ts b/src/libs/NetworkConnection.ts index b3dd24fcd4ae..4b335a21db73 100644 --- a/src/libs/NetworkConnection.ts +++ b/src/libs/NetworkConnection.ts @@ -79,12 +79,12 @@ Onyx.connect({ } else { // If we are no longer forcing offline fetch the NetInfo to set isOffline appropriately NetInfo.fetch().then((state) => { - const isInternetReachable = Boolean(state.isInternetReachable); - setOfflineStatus(isInternetReachable); + const isInternetReachable = state.isInternetReachable; + setOfflineStatus((isInternetReachable ?? false) === false); Log.info( `[NetworkStatus] The force-offline mode was turned off. Getting the device network status from NetInfo. Network state: ${JSON.stringify( state, - )}. Setting the offline status to: ${isInternetReachable}.`, + )}. Setting the offline status to: ${!!isInternetReachable}.`, ); }); } From 5a8f69ae5af682c807016cd51f3ac4bed82d7d4e Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 6 Jun 2024 11:50:00 +0700 Subject: [PATCH 097/230] fix lastVisibleActionCreated of task and split case --- ios/Podfile.lock | 2 +- src/libs/actions/IOU.ts | 1 + src/libs/actions/Task.ts | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index de363c211cb3..eee5c82f82ae 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -2606,7 +2606,7 @@ SPEC CHECKSUMS: SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 Turf: 13d1a92d969ca0311bbc26e8356cca178ce95da2 VisionCamera: 1394a316c7add37e619c48d7aa40b38b954bf055 - Yoga: 64cd2a583ead952b0315d5135bf39e053ae9be70 + Yoga: 1b901a6d6eeba4e8a2e8f308f708691cdb5db312 PODFILE CHECKSUM: 66a5c97ae1059e4da1993a4ad95abe5d819f555b diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 49c8cfe666a6..066ee1cd0c61 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -3835,6 +3835,7 @@ function createSplitsAndOnyxData( splitChatReport.lastMessageText = splitIOUReportAction.message?.[0]?.text; splitChatReport.lastMessageHtml = splitIOUReportAction.message?.[0]?.html; splitChatReport.lastActorAccountID = currentUserAccountID; + splitChatReport.lastVisibleActionCreated = splitIOUReportAction.created; let splitChatReportNotificationPreference = splitChatReport.notificationPreference; if (splitChatReportNotificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN) { diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 55d898d3d4f3..57b551510d58 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -131,10 +131,10 @@ function createTaskAndNavigate( const optimisticAddCommentReport = ReportUtils.buildOptimisticTaskCommentReportAction(taskReportID, title, assigneeAccountID, `task for ${title}`, parentReportID); optimisticTaskReport.parentReportActionID = optimisticAddCommentReport.reportAction.reportActionID; - const currentTime = DateUtils.getDBTime(); + const currentTime = DateUtils.getDBTimeWithSkew(); const lastCommentText = ReportUtils.formatReportLastMessageText(optimisticAddCommentReport?.reportAction?.message?.[0]?.text ?? ''); const optimisticParentReport = { - lastVisibleActionCreated: currentTime, + lastVisibleActionCreated: optimisticAddCommentReport.reportAction.created, lastMessageText: lastCommentText, lastActorAccountID: currentUserAccountID, lastReadTime: currentTime, From b18b68111d7c6f9894cc8866f5129ed0b9716754 Mon Sep 17 00:00:00 2001 From: Cong Pham Date: Thu, 6 Jun 2024 13:49:42 +0700 Subject: [PATCH 098/230] explain change behavior --- src/pages/signin/LoginForm/BaseLoginForm.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pages/signin/LoginForm/BaseLoginForm.tsx b/src/pages/signin/LoginForm/BaseLoginForm.tsx index cb7fe92732c8..29d6ee7bcc75 100644 --- a/src/pages/signin/LoginForm/BaseLoginForm.tsx +++ b/src/pages/signin/LoginForm/BaseLoginForm.tsx @@ -224,6 +224,9 @@ function BaseLoginForm({account, credentials, closeAccount, blurOnSubmit = false if (!Browser.isMobileWebKit()) { return; } + // On mobile WebKit browsers, when an input field gains focus, the keyboard appears and the virtual viewport is resized and scrolled to make the input field visible. + // This occurs even when there is enough space to display both the input field and the submit button in the current view. + // so this change to correct the scroll position when the input field gains focus. InteractionManager.runAfterInteractions(() => { htmlDivElementRef(submitContainerRef).current?.scrollIntoView?.({behavior: 'smooth', block: 'end'}); }); From df7bf24ac4708e6b20852a8ac9e1103ce362465f Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Thu, 6 Jun 2024 09:21:22 +0200 Subject: [PATCH 099/230] fix: change condition for displaying size section --- src/pages/settings/Subscription/SubscriptionDetails/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Subscription/SubscriptionDetails/index.tsx b/src/pages/settings/Subscription/SubscriptionDetails/index.tsx index ccf6b7cafa18..9d858fa995ef 100644 --- a/src/pages/settings/Subscription/SubscriptionDetails/index.tsx +++ b/src/pages/settings/Subscription/SubscriptionDetails/index.tsx @@ -52,7 +52,7 @@ function SubscriptionDetails() { // This section is only shown when the subscription is annual const subscriptionSizeSection: React.JSX.Element | null = - privateSubscription?.type === CONST.SUBSCRIPTION.TYPE.ANNUAL ? ( + selectedOption === CONST.SUBSCRIPTION.TYPE.ANNUAL ? ( <> Date: Thu, 6 Jun 2024 10:32:29 +0200 Subject: [PATCH 100/230] fix: cr fixes --- src/components/OptionsPicker/OptionItem.tsx | 5 +---- src/languages/en.ts | 2 +- src/languages/es.ts | 2 +- .../settings/Subscription/SubscriptionSettingsPage.tsx | 9 +++++++-- src/styles/index.ts | 4 ---- 5 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/components/OptionsPicker/OptionItem.tsx b/src/components/OptionsPicker/OptionItem.tsx index a787c20f515c..629187044b92 100644 --- a/src/components/OptionsPicker/OptionItem.tsx +++ b/src/components/OptionsPicker/OptionItem.tsx @@ -54,10 +54,7 @@ function OptionItem({title, icon, onPress, isSelected = false, isDisabled, style /> {!isDisabled && ( - + )} diff --git a/src/languages/en.ts b/src/languages/en.ts index 8ed0ef8207f1..b8d487faab14 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -3217,7 +3217,7 @@ export default { eachMonth: 'Each month, your subscription covers up to the number of active members set above. Any time you increase your subscription size, you’ll start a new 12-month subscription at that new size.', note: 'Note: An active member is anyone who has created, edited, submitted, approved, reimbursed, or exported expense data tied to your company workspace.', - confirmDetails: 'Confirm your new annual subscription details', + confirmDetails: 'Confirm your new annual subscription details:', subscriptionSize: 'Subscription size', activeMembers: ({size}) => `${size} active members/month`, subscriptionRenews: 'Subscription renews', diff --git a/src/languages/es.ts b/src/languages/es.ts index 2316a5d09c9f..fde907378ba7 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3723,7 +3723,7 @@ export default { eachMonth: 'Cada mes, tu suscripción cubre hasta el número de miembros activos establecido anteriormente. Cada vez que aumentes el tamaño de tu suscripción, iniciarás una nueva suscripción de 12 meses con ese nuevo tamaño.', note: 'Nota: Un miembro activo es cualquiera que haya creado, editado, enviado, aprobado, reembolsado, o exportado datos de gastos vinculados al espacio de trabajo de tu empresa.', - confirmDetails: 'Confirma los datos de tu nueva suscripción anual', + confirmDetails: 'Confirma los datos de tu nueva suscripción anual:', subscriptionSize: 'Tamaño de suscripción', activeMembers: ({size}) => `${size} miembros activos/mes`, subscriptionRenews: 'Renovación de la suscripción', diff --git a/src/pages/settings/Subscription/SubscriptionSettingsPage.tsx b/src/pages/settings/Subscription/SubscriptionSettingsPage.tsx index 932c83c1b7d2..0480399a8323 100644 --- a/src/pages/settings/Subscription/SubscriptionSettingsPage.tsx +++ b/src/pages/settings/Subscription/SubscriptionSettingsPage.tsx @@ -1,4 +1,5 @@ import React, {useEffect} from 'react'; +import {View} from 'react-native'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Illustrations from '@components/Icon/Illustrations'; import ScreenWrapper from '@components/ScreenWrapper'; @@ -7,6 +8,7 @@ import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useSubscriptionPlan from '@hooks/useSubscriptionPlan'; import useThemeStyles from '@hooks/useThemeStyles'; +import useWindowDimensions from '@hooks/useWindowDimensions'; import Navigation from '@libs/Navigation/Navigation'; import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; import * as Subscription from '@userActions/Subscription'; @@ -15,6 +17,7 @@ import SubscriptionPlan from './SubscriptionPlan'; function SubscriptionSettingsPage() { const {shouldUseNarrowLayout} = useResponsiveLayout(); + const {isSmallScreenWidth} = useWindowDimensions(); const {translate} = useLocalize(); const styles = useThemeStyles(); const subscriptionPlan = useSubscriptionPlan(); @@ -36,8 +39,10 @@ function SubscriptionSettingsPage() { icon={Illustrations.CreditCardsNew} /> - - + + + + ); diff --git a/src/styles/index.ts b/src/styles/index.ts index 718942582801..1fdcc7bc8c0c 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -2818,10 +2818,6 @@ const styles = (theme: ThemeColors) => alignItems: 'center', }, - sectionSelectCircle: { - backgroundColor: colors.productDark200, - }, - qrShareSection: { width: 264, }, From 4ec5cc3af0bc2ff1751b49d19b402650d5ff478f Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Thu, 6 Jun 2024 13:49:20 +0200 Subject: [PATCH 101/230] display dynamic subscription price with correct currency --- src/hooks/usePreferredCurrency.ts | 38 ++++++++----------- src/hooks/useSubscriptionPrice.ts | 2 +- src/languages/en.ts | 8 ++-- src/languages/es.ts | 8 ++-- src/libs/CurrencyUtils.ts | 19 ++++++++++ .../Subscription/SubscriptionPlan.tsx | 7 ++-- 6 files changed, 46 insertions(+), 36 deletions(-) diff --git a/src/hooks/usePreferredCurrency.ts b/src/hooks/usePreferredCurrency.ts index 11675d4de9c1..e7f71823d993 100644 --- a/src/hooks/usePreferredCurrency.ts +++ b/src/hooks/usePreferredCurrency.ts @@ -1,15 +1,19 @@ import {useMemo} from 'react'; import {useOnyx} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; -import {getCurrencySymbol} from '@libs/CurrencyUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -type PreferredCurrency = { - name: ValueOf; - symbol?: string; -}; +type PreferredCurrency = ValueOf; +/** + * Get user's preferred currency in the following order: + * + * 1. Default card currency + * 2. User's local currency (if it's a valid payment card currency) + * 3. USD (default currency) + * + */ function usePreferredCurrency(): PreferredCurrency { const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); const [session] = useOnyx(ONYXKEYS.SESSION); @@ -18,32 +22,20 @@ function usePreferredCurrency(): PreferredCurrency { const defaultCard = useMemo(() => Object.values(fundList ?? {}).find((card) => card.isDefault), [fundList]); if (defaultCard?.accountData?.currency) { - return { - name: defaultCard?.accountData?.currency, - symbol: getCurrencySymbol(defaultCard?.accountData?.currency), - }; + return defaultCard?.accountData?.currency; } if (!session?.accountID) { - return { - name: CONST.PAYMENT_CARD_CURRENCY.USD, - symbol: getCurrencySymbol(CONST.PAYMENT_CARD_CURRENCY.USD), - }; + return CONST.PAYMENT_CARD_CURRENCY.USD; } - const currentUserLocalCurrency = personalDetails?.[session.accountID]?.localCurrencyCode ?? CONST.PAYMENT_CARD_CURRENCY.USD; + const currentUserLocalCurrency = (personalDetails?.[session.accountID]?.localCurrencyCode ?? CONST.PAYMENT_CARD_CURRENCY.USD) as PreferredCurrency; - if (!(Object.values(CONST.PAYMENT_CARD_CURRENCY) as string[]).includes(currentUserLocalCurrency)) { - return { - name: CONST.PAYMENT_CARD_CURRENCY.USD, - symbol: getCurrencySymbol(CONST.PAYMENT_CARD_CURRENCY.USD), - }; + if (!Object.values(CONST.PAYMENT_CARD_CURRENCY).includes(currentUserLocalCurrency)) { + return CONST.PAYMENT_CARD_CURRENCY.USD; } - return { - name: currentUserLocalCurrency, - symbol: getCurrencySymbol(currentUserLocalCurrency), - } as PreferredCurrency; + return currentUserLocalCurrency; } export default usePreferredCurrency; diff --git a/src/hooks/useSubscriptionPrice.ts b/src/hooks/useSubscriptionPrice.ts index c7c36d100a9c..697d774387ad 100644 --- a/src/hooks/useSubscriptionPrice.ts +++ b/src/hooks/useSubscriptionPrice.ts @@ -56,7 +56,7 @@ function useSubscriptionPrice() { return 0; } - return SUBSCRIPTION_PRICES[preferredCurrency.name][subscriptionPlan][privateSubscription.type]; + return SUBSCRIPTION_PRICES[preferredCurrency][subscriptionPlan][privateSubscription.type]; } export default useSubscriptionPrice; diff --git a/src/languages/en.ts b/src/languages/en.ts index ce8cd39ad3ef..60e901cf4b0e 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -3176,8 +3176,8 @@ export default { title: 'Your plan', collect: { title: 'Collect', - priceAnnual: ({price}) => `From $${price}/active member with the Expensify Card, $${price * 2}/active member without the Expensify Card.`, - pricePayPerUse: ({price}) => `From $${price}/active member with the Expensify Card, $${price * 2}/active member without the Expensify Card.`, + priceAnnual: ({lower, upper}) => `From ${lower}/active member with the Expensify Card, ${upper}/active member without the Expensify Card.`, + pricePayPerUse: ({lower, upper}) => `From ${lower}/active member with the Expensify Card, ${upper}/active member without the Expensify Card.`, benefit1: 'Unlimited SmartScans and distance tracking', benefit2: 'Expensify Cards with Smart Limits', benefit3: 'Bill pay and invoicing', @@ -3188,8 +3188,8 @@ export default { }, control: { title: 'Control', - priceAnnual: ({price}) => `From $${price}/active member with the Expensify Card, $${price * 2}/active member without the Expensify Card.`, - pricePayPerUse: ({price}) => `From $${price}/active member with the Expensify Card, $${price * 2}/active member without the Expensify Card.`, + priceAnnual: ({lower, upper}) => `From ${lower}/active member with the Expensify Card, ${upper}/active member without the Expensify Card.`, + pricePayPerUse: ({lower, upper}) => `From ${lower}/active member with the Expensify Card, ${upper}/active member without the Expensify Card.`, benefit1: 'Everything in Collect, plus:', benefit2: 'NetSuite and Sage Intacct integrations', benefit3: 'Certinia and Workday sync', diff --git a/src/languages/es.ts b/src/languages/es.ts index 2316a5d09c9f..069eb282b7d5 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3682,8 +3682,8 @@ export default { title: 'Tu plan', collect: { title: 'Recolectar', - priceAnnual: 'Desde $5/miembro activo con la Tarjeta Expensify, $10/miembro activo sin la Tarjeta Expensify.', - pricePayPerUse: 'Desde $10/miembro activo con la Tarjeta Expensify, $20/miembro activo sin la Tarjeta Expensify.', + priceAnnual: ({lower, upper}) => `Desde ${lower}/miembro activo con la Tarjeta Expensify, ${upper}/miembro activo sin la Tarjeta Expensify.`, + pricePayPerUse: ({lower, upper}) => `Desde ${lower}/miembro activo con la Tarjeta Expensify, ${upper}/miembro activo sin la Tarjeta Expensify.`, benefit1: 'SmartScans ilimitados y seguimiento de la distancia', benefit2: 'Tarjetas Expensify con Límites Inteligentes', benefit3: 'Pago de facturas y facturación', @@ -3694,8 +3694,8 @@ export default { }, control: { title: 'Control', - priceAnnual: 'Desde $9/miembro activo con la Tarjeta Expensify, $18/miembro activo sin la Tarjeta Expensify.', - pricePayPerUse: 'Desde $18/miembro activo con la Tarjeta Expensify, $36/miembro activo sin la Tarjeta Expensify.', + priceAnnual: ({lower, upper}) => `Desde ${lower}/miembro activo con la Tarjeta Expensify, ${upper}/miembro activo sin la Tarjeta Expensify.`, + pricePayPerUse: ({lower, upper}) => `Desde ${lower}/miembro activo con la Tarjeta Expensify, ${upper}/miembro activo sin la Tarjeta Expensify.`, benefit1: 'Todo en Recolectar, más:', benefit2: 'Integraciones con NetSuite y Sage Intacct', benefit3: 'Sincronización de Certinia y Workday', diff --git a/src/libs/CurrencyUtils.ts b/src/libs/CurrencyUtils.ts index d3660c5f16f4..e9c09c298565 100644 --- a/src/libs/CurrencyUtils.ts +++ b/src/libs/CurrencyUtils.ts @@ -122,6 +122,24 @@ function convertToDisplayString(amountInCents = 0, currency: string = CONST.CURR }); } +/** + * Given the amount in the "cents", convert it to a short string (no decimals) for display in the UI. + * The backend always handle things in "cents" (subunit equal to 1/100) + * + * @param amountInCents – should be an integer. Anything after a decimal place will be dropped. + * @param currency - IOU currency + */ +function convertToShortDisplayString(amountInCents = 0, currency: string = CONST.CURRENCY.USD): string { + const convertedAmount = convertToFrontendAmountAsInteger(amountInCents); + return NumberFormatUtils.format(BaseLocaleListener.getPreferredLocale(), convertedAmount, { + style: 'currency', + currency, + + // There will be no decimals displayed (e.g. $9) + maximumFractionDigits: 0, + }); +} + /** * Given an amount, convert it to a string for display in the UI. * @@ -177,4 +195,5 @@ export { convertAmountToDisplayString, convertToDisplayStringWithoutCurrency, isValidCurrencyCode, + convertToShortDisplayString, }; diff --git a/src/pages/settings/Subscription/SubscriptionPlan.tsx b/src/pages/settings/Subscription/SubscriptionPlan.tsx index dd85eab06b1b..19ceca1c23da 100644 --- a/src/pages/settings/Subscription/SubscriptionPlan.tsx +++ b/src/pages/settings/Subscription/SubscriptionPlan.tsx @@ -12,6 +12,7 @@ import useSubscriptionPlan from '@hooks/useSubscriptionPlan'; import useSubscriptionPrice from '@hooks/useSubscriptionPrice'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; +import {convertToShortDisplayString} from '@libs/CurrencyUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -28,9 +29,6 @@ function SubscriptionPlan() { const subscriptionPrice = useSubscriptionPrice(); const preferredCurrency = usePreferredCurrency(); - console.log('SUBSCRIPTION PRICE', subscriptionPrice); - console.log('PREFERRED CURRENCY', preferredCurrency); - const isCollect = subscriptionPlan === CONST.POLICY.TYPE.TEAM; const isAnnual = privateSubscription?.type === CONST.SUBSCRIPTION.TYPE.ANNUAL; @@ -68,7 +66,8 @@ function SubscriptionPlan() { {translate(`subscription.yourPlan.${isCollect ? 'collect' : 'control'}.title`)} {translate(`subscription.yourPlan.${isCollect ? 'collect' : 'control'}.${isAnnual ? 'priceAnnual' : 'pricePayPerUse'}`, { - price: subscriptionPrice / 100, + lower: convertToShortDisplayString(subscriptionPrice, preferredCurrency), + upper: convertToShortDisplayString(subscriptionPrice * 2, preferredCurrency), })} {benefitsList.map((benefit) => ( From 6e5e314f4c582951ecdaff5fe3d6a48f06aeadc6 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Thu, 6 Jun 2024 14:33:33 +0200 Subject: [PATCH 102/230] simplify usePreferredCurrency --- src/hooks/usePreferredCurrency.ts | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/hooks/usePreferredCurrency.ts b/src/hooks/usePreferredCurrency.ts index e7f71823d993..c9244c6ec5d0 100644 --- a/src/hooks/usePreferredCurrency.ts +++ b/src/hooks/usePreferredCurrency.ts @@ -19,23 +19,15 @@ function usePreferredCurrency(): PreferredCurrency { const [session] = useOnyx(ONYXKEYS.SESSION); const [fundList] = useOnyx(ONYXKEYS.FUND_LIST); - const defaultCard = useMemo(() => Object.values(fundList ?? {}).find((card) => card.isDefault), [fundList]); + const defaultCardCurrency = useMemo(() => Object.values(fundList ?? {}).find((card) => card.isDefault)?.accountData?.currency, [fundList]); - if (defaultCard?.accountData?.currency) { - return defaultCard?.accountData?.currency; + if (defaultCardCurrency) { + return defaultCardCurrency; } - if (!session?.accountID) { - return CONST.PAYMENT_CARD_CURRENCY.USD; - } - - const currentUserLocalCurrency = (personalDetails?.[session.accountID]?.localCurrencyCode ?? CONST.PAYMENT_CARD_CURRENCY.USD) as PreferredCurrency; - - if (!Object.values(CONST.PAYMENT_CARD_CURRENCY).includes(currentUserLocalCurrency)) { - return CONST.PAYMENT_CARD_CURRENCY.USD; - } + const currentUserLocalCurrency = (personalDetails?.[session?.accountID ?? '']?.localCurrencyCode ?? CONST.PAYMENT_CARD_CURRENCY.USD) as PreferredCurrency; - return currentUserLocalCurrency; + return Object.values(CONST.PAYMENT_CARD_CURRENCY).includes(currentUserLocalCurrency) ? currentUserLocalCurrency : CONST.PAYMENT_CARD_CURRENCY.USD; } export default usePreferredCurrency; From 2a8fd0981c39f23d448717425edd48779ab6a001 Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Thu, 6 Jun 2024 15:43:52 +0200 Subject: [PATCH 103/230] fix: changed select circle bg --- src/components/OptionsPicker/OptionItem.tsx | 5 ++++- src/styles/index.ts | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/OptionsPicker/OptionItem.tsx b/src/components/OptionsPicker/OptionItem.tsx index 629187044b92..a787c20f515c 100644 --- a/src/components/OptionsPicker/OptionItem.tsx +++ b/src/components/OptionsPicker/OptionItem.tsx @@ -54,7 +54,10 @@ function OptionItem({title, icon, onPress, isSelected = false, isDisabled, style /> {!isDisabled && ( - + )} diff --git a/src/styles/index.ts b/src/styles/index.ts index 88d3084b45a7..023e6eabd1f9 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -2818,6 +2818,10 @@ const styles = (theme: ThemeColors) => alignItems: 'center', }, + sectionSelectCircle: { + backgroundColor: theme.highlightBG, + }, + qrShareSection: { width: 264, }, From eb0f8d0e625bda0de3a9c5a94cb61da253137237 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Thu, 6 Jun 2024 16:12:57 +0200 Subject: [PATCH 104/230] add subscription price multiply factor to CONST --- src/CONST.ts | 2 ++ src/pages/settings/Subscription/SubscriptionPlan.tsx | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/CONST.ts b/src/CONST.ts index b652740667bc..b583122fcaac 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -4821,6 +4821,8 @@ const CONST = { GBP: 'GBP', NZD: 'NZD', }, + + SUBSCRIPTION_PRICE_FACTOR: 2, } as const; type Country = keyof typeof CONST.ALL_COUNTRIES; diff --git a/src/pages/settings/Subscription/SubscriptionPlan.tsx b/src/pages/settings/Subscription/SubscriptionPlan.tsx index 19ceca1c23da..33933027dd45 100644 --- a/src/pages/settings/Subscription/SubscriptionPlan.tsx +++ b/src/pages/settings/Subscription/SubscriptionPlan.tsx @@ -67,7 +67,7 @@ function SubscriptionPlan() { {translate(`subscription.yourPlan.${isCollect ? 'collect' : 'control'}.${isAnnual ? 'priceAnnual' : 'pricePayPerUse'}`, { lower: convertToShortDisplayString(subscriptionPrice, preferredCurrency), - upper: convertToShortDisplayString(subscriptionPrice * 2, preferredCurrency), + upper: convertToShortDisplayString(subscriptionPrice * CONST.SUBSCRIPTION_PRICE_FACTOR, preferredCurrency), })} {benefitsList.map((benefit) => ( From 56a64f1ddfe6894b9c1d9fec5cdb488db457a963 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Thu, 6 Jun 2024 16:22:52 +0200 Subject: [PATCH 105/230] add unit tests for convertToShortDisplayString util function --- tests/unit/CurrencyUtilsTest.ts | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/unit/CurrencyUtilsTest.ts b/tests/unit/CurrencyUtilsTest.ts index 089cdf8426a8..3022c593e87f 100644 --- a/tests/unit/CurrencyUtilsTest.ts +++ b/tests/unit/CurrencyUtilsTest.ts @@ -157,4 +157,31 @@ describe('CurrencyUtils', () => { Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.ES).then(() => expect(CurrencyUtils.convertToDisplayString(amount, currency)).toBe(expectedResult)), ); }); + + describe('convertToShortDisplayString', () => { + test.each([ + [CONST.CURRENCY.USD, 25, '$0'], + [CONST.CURRENCY.USD, 2500, '$25'], + [CONST.CURRENCY.USD, 150, '$2'], + [CONST.CURRENCY.USD, 250000, '$2,500'], + ['JPY', 2500, '¥25'], + ['JPY', 250000, '¥2,500'], + ['JPY', 2500.5, '¥25'], + ['RSD', 100, 'RSD\xa01'], + ['RSD', 145, 'RSD\xa01'], + ['BHD', 12345, 'BHD\xa0123'], + ['BHD', 1, 'BHD\xa00'], + ])('Correctly displays %s', (currency, amount, expectedResult) => { + expect(CurrencyUtils.convertToShortDisplayString(amount, currency)).toBe(expectedResult); + }); + + test.each([ + ['EUR', 25, '0\xa0€'], + ['EUR', 2500, '25\xa0€'], + ['EUR', 250000, '2500\xa0€'], + ['EUR', 250000000, '2.500.000\xa0€'], + ])('Correctly displays %s in ES locale', (currency, amount, expectedResult) => + Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.ES).then(() => expect(CurrencyUtils.convertToShortDisplayString(amount, currency)).toBe(expectedResult)), + ); + }) }); From a6cafa066a60702ab3c2c43006e5fc60fb8639e0 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Thu, 6 Jun 2024 11:34:26 -1000 Subject: [PATCH 106/230] Add logs for backend reachability and SequentialQueue --- src/libs/Network/SequentialQueue.ts | 24 ++++++++++++++++++++++-- src/libs/NetworkConnection.ts | 8 ++++---- src/libs/actions/Network.ts | 8 +++++++- 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/libs/Network/SequentialQueue.ts b/src/libs/Network/SequentialQueue.ts index b94166c0249d..91949b7933df 100644 --- a/src/libs/Network/SequentialQueue.ts +++ b/src/libs/Network/SequentialQueue.ts @@ -26,6 +26,7 @@ let isQueuePaused = false; */ function pause() { if (isQueuePaused) { + console.debug('[SequentialQueue] Queue already paused'); return; } @@ -40,6 +41,7 @@ function flushOnyxUpdatesQueue() { // The only situation where the queue is paused is if we found a gap between the app current data state and our server's. If that happens, // we'll trigger async calls to make the client updated again. While we do that, we don't want to insert anything in Onyx. if (isQueuePaused) { + console.debug('[SequentialQueue] Queue already paused'); return; } QueuedOnyxUpdates.flushQueue(); @@ -56,11 +58,18 @@ function flushOnyxUpdatesQueue() { function process(): Promise { // When the queue is paused, return early. This prevents any new requests from happening. The queue will be flushed again when the queue is unpaused. if (isQueuePaused) { + console.debug('[SequentialQueue] Unable to process. Queue is paused.'); + return Promise.resolve(); + } + + if (NetworkStore.isOffline()) { + console.debug('[SequentialQueue] Unable to process. We are offline.'); return Promise.resolve(); } const persistedRequests = PersistedRequests.getAll(); - if (persistedRequests.length === 0 || NetworkStore.isOffline()) { + if (persistedRequests.length === 0) { + console.debug('[SequentialQueue] Unable to process. No requests to process.'); return Promise.resolve(); } @@ -72,6 +81,7 @@ function process(): Promise { // A response might indicate that the queue should be paused. This happens when a gap in onyx updates is detected between the client and the server and // that gap needs resolved before the queue can continue. if (response?.shouldPauseQueue) { + console.debug('[SequentialQueue] Handled \'shouldPauseQueue\' in response. Pausing the queue.'); pause(); } PersistedRequests.remove(requestToProcess); @@ -102,16 +112,24 @@ function process(): Promise { function flush() { // When the queue is paused, return early. This will keep an requests in the queue and they will get flushed again when the queue is unpaused if (isQueuePaused) { + console.debug('[SequentialQueue] Unable to flush. Queue is paused.'); + return; + } + + if (isSequentialQueueRunning) { + console.debug('[SequentialQueue] Unable to flush. Queue is already running.'); return; } - if (isSequentialQueueRunning || PersistedRequests.getAll().length === 0) { + if (PersistedRequests.getAll().length === 0) { + console.debug('[SequentialQueue] Unable to flush. No requests to process.'); return; } // ONYXKEYS.PERSISTED_REQUESTS is shared across clients, thus every client/tab will have a copy // It is very important to only process the queue from leader client otherwise requests will be duplicated. if (!ActiveClientManager.isClientTheLeader()) { + console.debug('[SequentialQueue] Unable to flush. Client is not the leader.'); return; } @@ -128,6 +146,7 @@ function flush() { callback: () => { Onyx.disconnect(connectionID); process().finally(() => { + console.debug('[SequentialQueue] Finished processing queue.'); isSequentialQueueRunning = false; resolveIsReadyPromise?.(); currentRequest = null; @@ -142,6 +161,7 @@ function flush() { */ function unpause() { if (!isQueuePaused) { + console.debug('[SequentialQueue] Unable to unpause queue. We are already processing.'); return; } diff --git a/src/libs/NetworkConnection.ts b/src/libs/NetworkConnection.ts index b3dd24fcd4ae..b6a1481224de 100644 --- a/src/libs/NetworkConnection.ts +++ b/src/libs/NetworkConnection.ts @@ -43,7 +43,7 @@ function setOfflineStatus(isCurrentlyOffline: boolean): void { // When reconnecting, ie, going from offline to online, all the reconnection callbacks // are triggered (this is usually Actions that need to re-download data from the server) if (isOffline && !isCurrentlyOffline) { - NetworkActions.setIsBackendReachable(true); + NetworkActions.setIsBackendReachable(true, 'moved from offline to online'); triggerReconnectionCallbacks('offline status changed'); } @@ -118,20 +118,20 @@ function subscribeToBackendAndInternetReachability(): () => void { }) .then((isBackendReachable: boolean) => { if (isBackendReachable) { - NetworkActions.setIsBackendReachable(true); + NetworkActions.setIsBackendReachable(true, 'successfully completed API request'); return; } checkInternetReachability().then((isInternetReachable: boolean) => { setOfflineStatus(!isInternetReachable); setNetWorkStatus(isInternetReachable); - NetworkActions.setIsBackendReachable(false); + NetworkActions.setIsBackendReachable(false, 'request succeeded, but internet reachability test failed'); }); }) .catch(() => { checkInternetReachability().then((isInternetReachable: boolean) => { setOfflineStatus(!isInternetReachable); setNetWorkStatus(isInternetReachable); - NetworkActions.setIsBackendReachable(false); + NetworkActions.setIsBackendReachable(false, 'request failed and internet reachability test failed'); }); }); }, CONST.NETWORK.BACKEND_CHECK_INTERVAL_MS); diff --git a/src/libs/actions/Network.ts b/src/libs/actions/Network.ts index 9c88403b0e98..2e5ef5b605f7 100644 --- a/src/libs/actions/Network.ts +++ b/src/libs/actions/Network.ts @@ -1,8 +1,14 @@ import Onyx from 'react-native-onyx'; import type {NetworkStatus} from '@libs/NetworkConnection'; import ONYXKEYS from '@src/ONYXKEYS'; +import Log from '@libs/Log'; -function setIsBackendReachable(isBackendReachable: boolean) { +function setIsBackendReachable(isBackendReachable: boolean, reason: string) { + if (isBackendReachable) { + Log.info(`[Network] Backend is reachable because: ${reason}`); + } else { + Log.info(`[Network] Backend is not reachable because: ${reason}`); + } Onyx.merge(ONYXKEYS.NETWORK, {isBackendReachable}); } From 0011a1cb078c59d917763bea7d46eca7ca3f6769 Mon Sep 17 00:00:00 2001 From: truph01 Date: Fri, 7 Jun 2024 09:00:07 +0700 Subject: [PATCH 107/230] Fix: only call VBBA one time --- src/pages/workspace/WorkspacePageWithSections.tsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/pages/workspace/WorkspacePageWithSections.tsx b/src/pages/workspace/WorkspacePageWithSections.tsx index 738a0663b94b..3476f6748790 100644 --- a/src/pages/workspace/WorkspacePageWithSections.tsx +++ b/src/pages/workspace/WorkspacePageWithSections.tsx @@ -129,17 +129,20 @@ function WorkspacePageWithSections({ const firstRender = useRef(true); const isFocused = useIsFocused(); const prevPolicy = usePrevious(policy); + const isCalledVBBA = useRef(false); useEffect(() => { // Because isLoading is false before merging in Onyx, we need firstRender ref to display loading page as well before isLoading is change to true firstRender.current = false; }, []); - useFocusEffect( - useCallback(() => { - fetchData(policyID, shouldSkipVBBACall); - }, [policyID, shouldSkipVBBACall]), - ); + useEffect(() => { + if (isCalledVBBA.current) { + return; + } + isCalledVBBA.current = true; + fetchData(policyID, shouldSkipVBBACall); + }, [policyID, shouldSkipVBBACall]); const shouldShow = useMemo(() => { // If the policy object doesn't exist or contains only error data, we shouldn't display it. From 7951fcf44313156b4880274654f17c4e9e5404df Mon Sep 17 00:00:00 2001 From: truph01 Date: Fri, 7 Jun 2024 09:05:46 +0700 Subject: [PATCH 108/230] fix rename ref --- src/pages/workspace/WorkspacePageWithSections.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/workspace/WorkspacePageWithSections.tsx b/src/pages/workspace/WorkspacePageWithSections.tsx index 3476f6748790..dab029435923 100644 --- a/src/pages/workspace/WorkspacePageWithSections.tsx +++ b/src/pages/workspace/WorkspacePageWithSections.tsx @@ -129,7 +129,7 @@ function WorkspacePageWithSections({ const firstRender = useRef(true); const isFocused = useIsFocused(); const prevPolicy = usePrevious(policy); - const isCalledVBBA = useRef(false); + const isCalledVBBARef = useRef(false); useEffect(() => { // Because isLoading is false before merging in Onyx, we need firstRender ref to display loading page as well before isLoading is change to true @@ -137,10 +137,10 @@ function WorkspacePageWithSections({ }, []); useEffect(() => { - if (isCalledVBBA.current) { + if (isCalledVBBARef.current || !policyID) { return; } - isCalledVBBA.current = true; + isCalledVBBARef.current = true; fetchData(policyID, shouldSkipVBBACall); }, [policyID, shouldSkipVBBACall]); From ec9bc7c7b575b6277351ea6a8033eb5346d18d6e Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 7 Jun 2024 11:28:49 +0700 Subject: [PATCH 109/230] revert unnecessary change --- src/types/onyx/Policy.ts | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index 4e34edf5a764..070803e2e2fd 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -12,19 +12,16 @@ type TaxRateAttributes = { taxRateExternalID?: string; }; -type Rate = OnyxCommon.OnyxValueWithOfflineFeedback< - { - name?: string; - rate?: number; - currency?: string; - customUnitRateID?: string; - enabled?: boolean; - errors?: OnyxCommon.Errors; - errorFields?: OnyxCommon.ErrorFields; - attributes?: TaxRateAttributes; - }, - keyof TaxRateAttributes ->; +type Rate = OnyxCommon.OnyxValueWithOfflineFeedback<{ + name?: string; + rate?: number; + currency?: string; + customUnitRateID?: string; + enabled?: boolean; + errors?: OnyxCommon.Errors; + errorFields?: OnyxCommon.ErrorFields; + attributes?: TaxRateAttributes; +}>; type Attributes = { unit: Unit; From 9a8a96b2de2cd69c68abaade4f7cf0a16ca4f697 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 7 Jun 2024 13:07:28 +0700 Subject: [PATCH 110/230] revert unrelated change --- ios/Podfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index eee5c82f82ae..de363c211cb3 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -2606,7 +2606,7 @@ SPEC CHECKSUMS: SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 Turf: 13d1a92d969ca0311bbc26e8356cca178ce95da2 VisionCamera: 1394a316c7add37e619c48d7aa40b38b954bf055 - Yoga: 1b901a6d6eeba4e8a2e8f308f708691cdb5db312 + Yoga: 64cd2a583ead952b0315d5135bf39e053ae9be70 PODFILE CHECKSUM: 66a5c97ae1059e4da1993a4ad95abe5d819f555b From 6f67061b706b435d997ea96f4461a9c9b97af8db Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Fri, 7 Jun 2024 09:26:30 +0200 Subject: [PATCH 111/230] apply minor improvements based on feedback --- src/hooks/usePreferredCurrency.ts | 2 +- src/hooks/useSubscriptionPrice.ts | 2 +- tests/unit/CurrencyUtilsTest.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hooks/usePreferredCurrency.ts b/src/hooks/usePreferredCurrency.ts index c9244c6ec5d0..32809e7d70c8 100644 --- a/src/hooks/usePreferredCurrency.ts +++ b/src/hooks/usePreferredCurrency.ts @@ -25,7 +25,7 @@ function usePreferredCurrency(): PreferredCurrency { return defaultCardCurrency; } - const currentUserLocalCurrency = (personalDetails?.[session?.accountID ?? '']?.localCurrencyCode ?? CONST.PAYMENT_CARD_CURRENCY.USD) as PreferredCurrency; + const currentUserLocalCurrency = (personalDetails?.[session?.accountID ?? '-1']?.localCurrencyCode ?? CONST.PAYMENT_CARD_CURRENCY.USD) as PreferredCurrency; return Object.values(CONST.PAYMENT_CARD_CURRENCY).includes(currentUserLocalCurrency) ? currentUserLocalCurrency : CONST.PAYMENT_CARD_CURRENCY.USD; } diff --git a/src/hooks/useSubscriptionPrice.ts b/src/hooks/useSubscriptionPrice.ts index 697d774387ad..0b71fe62c7c8 100644 --- a/src/hooks/useSubscriptionPrice.ts +++ b/src/hooks/useSubscriptionPrice.ts @@ -47,7 +47,7 @@ const SUBSCRIPTION_PRICES = { }, } as const; -function useSubscriptionPrice() { +function useSubscriptionPrice(): number { const preferredCurrency = usePreferredCurrency(); const subscriptionPlan = useSubscriptionPlan(); const [privateSubscription] = useOnyx(ONYXKEYS.NVP_PRIVATE_SUBSCRIPTION); diff --git a/tests/unit/CurrencyUtilsTest.ts b/tests/unit/CurrencyUtilsTest.ts index 3022c593e87f..87b7c7ee4569 100644 --- a/tests/unit/CurrencyUtilsTest.ts +++ b/tests/unit/CurrencyUtilsTest.ts @@ -183,5 +183,5 @@ describe('CurrencyUtils', () => { ])('Correctly displays %s in ES locale', (currency, amount, expectedResult) => Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.ES).then(() => expect(CurrencyUtils.convertToShortDisplayString(amount, currency)).toBe(expectedResult)), ); - }) + }); }); From 457b4a2ee9bf03d0ab03dcf3bbb200e68a46d660 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Fri, 7 Jun 2024 09:36:17 +0200 Subject: [PATCH 112/230] add missing benefit to Control plan --- src/languages/en.ts | 3 ++- src/languages/es.ts | 3 ++- src/pages/settings/Subscription/SubscriptionPlan.tsx | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 0cf35025c50c..9897b2d3a604 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -3245,7 +3245,8 @@ export default { benefit3: 'Certinia and Workday sync', benefit4: 'Multiple expense approvers', benefit5: 'SAML/SSO', - benefit6: 'Budgeting', + benefit6: 'Custom insights and reporting', + benefit7: 'Budgeting', }, saveWithExpensifyTitle: 'Save with the Expensify Card', saveWithExpensifyDescription: 'Use our savings calculator to see how cash back from the Expensify Card can reduce your Expensify bill.', diff --git a/src/languages/es.ts b/src/languages/es.ts index 1996c05867d4..fee7a356543e 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3752,7 +3752,8 @@ export default { benefit3: 'Sincronización de Certinia y Workday', benefit4: 'Varios aprobadores de gastos', benefit5: 'SAML/SSO', - benefit6: 'Presupuestos', + benefit6: 'Reportes e informes personalizados', + benefit7: 'Presupuestos', }, saveWithExpensifyTitle: 'Ahorra con la Tarjeta Expensify', saveWithExpensifyDescription: 'Utiliza nuestra calculadora de ahorro para ver cómo el reembolso en efectivo de la Tarjeta Expensify puede reducir tu factura de Expensify', diff --git a/src/pages/settings/Subscription/SubscriptionPlan.tsx b/src/pages/settings/Subscription/SubscriptionPlan.tsx index 33933027dd45..4d42390ad581 100644 --- a/src/pages/settings/Subscription/SubscriptionPlan.tsx +++ b/src/pages/settings/Subscription/SubscriptionPlan.tsx @@ -49,6 +49,7 @@ function SubscriptionPlan() { translate('subscription.yourPlan.control.benefit4'), translate('subscription.yourPlan.control.benefit5'), translate('subscription.yourPlan.control.benefit6'), + translate('subscription.yourPlan.control.benefit7'), ]; return ( From 7481eb3a985328883717a6c09df066f7686a51bb Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 7 Jun 2024 17:07:21 +0800 Subject: [PATCH 113/230] only apply the fix for android --- src/components/MoneyRequestAmountInput.tsx | 3 ++- .../index.android.tsx | 5 +++++ src/libs/shouldIgnoreSelectionWhenUpdatedManually/index.tsx | 5 +++++ src/libs/shouldIgnoreSelectionWhenUpdatedManually/types.ts | 3 +++ 4 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 src/libs/shouldIgnoreSelectionWhenUpdatedManually/index.android.tsx create mode 100644 src/libs/shouldIgnoreSelectionWhenUpdatedManually/index.tsx create mode 100644 src/libs/shouldIgnoreSelectionWhenUpdatedManually/types.ts diff --git a/src/components/MoneyRequestAmountInput.tsx b/src/components/MoneyRequestAmountInput.tsx index d97d6ef39af4..fb17297dc642 100644 --- a/src/components/MoneyRequestAmountInput.tsx +++ b/src/components/MoneyRequestAmountInput.tsx @@ -7,6 +7,7 @@ import * as Browser from '@libs/Browser'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import getOperatingSystem from '@libs/getOperatingSystem'; import * as MoneyRequestUtils from '@libs/MoneyRequestUtils'; +import shouldIgnoreSelectionWhenUpdatedManually from '@libs/shouldIgnoreSelectionWhenUpdatedManually'; import CONST from '@src/CONST'; import type {BaseTextInputRef} from './TextInput/BaseTextInput/types'; import TextInputWithCurrencySymbol from './TextInputWithCurrencySymbol'; @@ -298,7 +299,7 @@ function MoneyRequestAmountInput( selectedCurrencyCode={currency} selection={selection} onSelectionChange={(e: NativeSyntheticEvent) => { - if (willSelectionBeUpdatedManually.current) { + if (shouldIgnoreSelectionWhenUpdatedManually && willSelectionBeUpdatedManually.current) { willSelectionBeUpdatedManually.current = false; return; } diff --git a/src/libs/shouldIgnoreSelectionWhenUpdatedManually/index.android.tsx b/src/libs/shouldIgnoreSelectionWhenUpdatedManually/index.android.tsx new file mode 100644 index 000000000000..d786760e8691 --- /dev/null +++ b/src/libs/shouldIgnoreSelectionWhenUpdatedManually/index.android.tsx @@ -0,0 +1,5 @@ +import type ShouldIgnoreSelectionWhenUpdatedManually from './types.ts'; + +const shouldIgnoreSelectionWhenUpdatedManually: ShouldIgnoreSelectionWhenUpdatedManually = true; + +export default shouldIgnoreSelectionWhenUpdatedManually; diff --git a/src/libs/shouldIgnoreSelectionWhenUpdatedManually/index.tsx b/src/libs/shouldIgnoreSelectionWhenUpdatedManually/index.tsx new file mode 100644 index 000000000000..0e7c69b39386 --- /dev/null +++ b/src/libs/shouldIgnoreSelectionWhenUpdatedManually/index.tsx @@ -0,0 +1,5 @@ +import type ShouldIgnoreSelectionWhenUpdatedManually from './types.ts'; + +const shouldIgnoreSelectionWhenUpdatedManually: ShouldIgnoreSelectionWhenUpdatedManually = false; + +export default shouldIgnoreSelectionWhenUpdatedManually; diff --git a/src/libs/shouldIgnoreSelectionWhenUpdatedManually/types.ts b/src/libs/shouldIgnoreSelectionWhenUpdatedManually/types.ts new file mode 100644 index 000000000000..56394183ef7d --- /dev/null +++ b/src/libs/shouldIgnoreSelectionWhenUpdatedManually/types.ts @@ -0,0 +1,3 @@ +type ShouldIgnoreSelectionWhenUpdatedManually = boolean; + +export default ShouldIgnoreSelectionWhenUpdatedManually; From 6d0630d35122717a484469b25e78fad35dbaa825 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 7 Jun 2024 17:17:35 +0800 Subject: [PATCH 114/230] lint --- src/libs/actions/Report.ts | 1 - .../shouldIgnoreSelectionWhenUpdatedManually/index.android.tsx | 2 +- src/libs/shouldIgnoreSelectionWhenUpdatedManually/index.tsx | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 1262e8af7d44..01025f21aa78 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -1027,7 +1027,6 @@ function navigateToAndOpenReportWithAccountIDs(participantAccountIDs: number[]) */ function navigateToAndOpenChildReport(childReportID = '0', parentReportAction: Partial = {}, parentReportID = '0') { if (childReportID !== '0') { - openReport(childReportID); Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(childReportID)); } else { const participantAccountIDs = [...new Set([currentUserAccountID, Number(parentReportAction.actorAccountID)])]; diff --git a/src/libs/shouldIgnoreSelectionWhenUpdatedManually/index.android.tsx b/src/libs/shouldIgnoreSelectionWhenUpdatedManually/index.android.tsx index d786760e8691..289c56ad69be 100644 --- a/src/libs/shouldIgnoreSelectionWhenUpdatedManually/index.android.tsx +++ b/src/libs/shouldIgnoreSelectionWhenUpdatedManually/index.android.tsx @@ -1,4 +1,4 @@ -import type ShouldIgnoreSelectionWhenUpdatedManually from './types.ts'; +import type ShouldIgnoreSelectionWhenUpdatedManually from './types'; const shouldIgnoreSelectionWhenUpdatedManually: ShouldIgnoreSelectionWhenUpdatedManually = true; diff --git a/src/libs/shouldIgnoreSelectionWhenUpdatedManually/index.tsx b/src/libs/shouldIgnoreSelectionWhenUpdatedManually/index.tsx index 0e7c69b39386..744a94aa1f32 100644 --- a/src/libs/shouldIgnoreSelectionWhenUpdatedManually/index.tsx +++ b/src/libs/shouldIgnoreSelectionWhenUpdatedManually/index.tsx @@ -1,4 +1,4 @@ -import type ShouldIgnoreSelectionWhenUpdatedManually from './types.ts'; +import type ShouldIgnoreSelectionWhenUpdatedManually from './types'; const shouldIgnoreSelectionWhenUpdatedManually: ShouldIgnoreSelectionWhenUpdatedManually = false; From c3ab92d2d8c506fdff0bf6a261b885a340be003c Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 7 Jun 2024 17:48:24 +0800 Subject: [PATCH 115/230] remove dupe open report --- src/libs/actions/Report.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 1262e8af7d44..01025f21aa78 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -1027,7 +1027,6 @@ function navigateToAndOpenReportWithAccountIDs(participantAccountIDs: number[]) */ function navigateToAndOpenChildReport(childReportID = '0', parentReportAction: Partial = {}, parentReportID = '0') { if (childReportID !== '0') { - openReport(childReportID); Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(childReportID)); } else { const participantAccountIDs = [...new Set([currentUserAccountID, Number(parentReportAction.actorAccountID)])]; From a11171a5dc51bb12a0be1a60e23612fa46373247 Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Fri, 7 Jun 2024 14:22:41 +0200 Subject: [PATCH 116/230] add possibility of excluding styles in useMarkdownSyles --- src/components/Composer/index.native.tsx | 3 +- src/components/Composer/index.tsx | 3 +- src/components/Composer/types.ts | 3 ++ src/hooks/useMarkdownStyle.ts | 35 ++++++++++++++++--- .../ComposerWithSuggestions.tsx | 1 + 5 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/components/Composer/index.native.tsx b/src/components/Composer/index.native.tsx index 2fbd635ed42e..5e5f126da773 100644 --- a/src/components/Composer/index.native.tsx +++ b/src/components/Composer/index.native.tsx @@ -28,6 +28,7 @@ function Composer( // On Android the selection prop is required on the TextInput but this prop has issues on IOS selection, value, + isGroupPolicyReport = false, ...props }: ComposerProps, ref: ForwardedRef, @@ -36,7 +37,7 @@ function Composer( const {isFocused, shouldResetFocus} = useResetComposerFocus(textInput); const textContainsOnlyEmojis = useMemo(() => EmojiUtils.containsOnlyEmojis(value ?? ''), [value]); const theme = useTheme(); - const markdownStyle = useMarkdownStyle(value); + const markdownStyle = useMarkdownStyle(value, !isGroupPolicyReport ? ['mentionReport'] : []); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); diff --git a/src/components/Composer/index.tsx b/src/components/Composer/index.tsx index 14762b2d4bc1..eae27440d175 100755 --- a/src/components/Composer/index.tsx +++ b/src/components/Composer/index.tsx @@ -71,6 +71,7 @@ function Composer( isReportActionCompose = false, isComposerFullSize = false, shouldContainScroll = false, + isGroupPolicyReport = false, ...props }: ComposerProps, ref: ForwardedRef, @@ -78,7 +79,7 @@ function Composer( const textContainsOnlyEmojis = useMemo(() => EmojiUtils.containsOnlyEmojis(value ?? ''), [value]); const theme = useTheme(); const styles = useThemeStyles(); - const markdownStyle = useMarkdownStyle(value); + const markdownStyle = useMarkdownStyle(value, !isGroupPolicyReport ? ['mentionReport'] : []); const StyleUtils = useStyleUtils(); const textRef = useRef(null); const textInput = useRef(null); diff --git a/src/components/Composer/types.ts b/src/components/Composer/types.ts index 531bcd03f8bf..0ff91111bd07 100644 --- a/src/components/Composer/types.ts +++ b/src/components/Composer/types.ts @@ -70,6 +70,9 @@ type ComposerProps = TextInputProps & { /** Should make the input only scroll inside the element avoid scroll out to parent */ shouldContainScroll?: boolean; + + /** Indicates whether the composer is in a group policy report. Used for disabling report mentioning style in markdown input */ + isGroupPolicyReport?: boolean; }; export type {TextSelection, ComposerProps}; diff --git a/src/hooks/useMarkdownStyle.ts b/src/hooks/useMarkdownStyle.ts index d1af33aa9da5..77ad5ce816f5 100644 --- a/src/hooks/useMarkdownStyle.ts +++ b/src/hooks/useMarkdownStyle.ts @@ -5,12 +5,25 @@ import FontUtils from '@styles/utils/FontUtils'; import variables from '@styles/variables'; import useTheme from './useTheme'; -function useMarkdownStyle(message: string | null = null): MarkdownStyle { +function useMarkdownStyle(message: string | null = null, excludeStyles: Array = []): MarkdownStyle { const theme = useTheme(); const emojiFontSize = containsOnlyEmojis(message ?? '') ? variables.fontSizeOnlyEmojis : variables.fontSizeNormal; - const markdownStyle = useMemo( + // this map is used to reset the styles that are not needed - passing undefined value can break the native side + const nonStylingDefaultValues: Record = useMemo( () => ({ + color: theme.text, + backgroundColor: 'transparent', + marginLeft: 0, + paddingLeft: 0, + borderColor: 'transparent', + borderWidth: 0, + }), + [theme], + ); + + const markdownStyle = useMemo(() => { + const styling = { syntax: { color: theme.syntax, }, @@ -59,9 +72,21 @@ function useMarkdownStyle(message: string | null = null): MarkdownStyle { color: theme.mentionText, backgroundColor: theme.mentionBG, }, - }), - [theme, emojiFontSize], - ); + }; + + if (excludeStyles.length) { + excludeStyles.forEach((key) => { + const style: Record = styling[key]; + if (style) { + Object.keys(style).forEach((styleKey) => { + style[styleKey] = nonStylingDefaultValues[styleKey] ?? style[styleKey]; + }); + } + }); + } + + return styling; + }, [theme, emojiFontSize, excludeStyles, nonStylingDefaultValues]); return markdownStyle; } diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx index 0515ca011517..d7f4ee7f6c5c 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx @@ -775,6 +775,7 @@ function ComposerWithSuggestions( onLayout={onLayout} onScroll={hideSuggestionMenu} shouldContainScroll={Browser.isMobileSafari()} + isGroupPolicyReport={isGroupPolicyReport} /> From 5cf2c77fa6c88956c2f36002a701dafc49cf578c Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Fri, 7 Jun 2024 14:22:58 +0200 Subject: [PATCH 117/230] bump react native live markdown --- package-lock.json | 13 +++++++++---- package.json | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index d972d33f7c27..9d54c06e46be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@babel/plugin-proposal-private-methods": "^7.18.6", "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@dotlottie/react-player": "^1.6.3", - "@expensify/react-native-live-markdown": "0.1.70", + "@expensify/react-native-live-markdown": "0.1.82", "@expo/metro-runtime": "~3.1.1", "@formatjs/intl-datetimeformat": "^6.10.0", "@formatjs/intl-listformat": "^7.2.2", @@ -3558,9 +3558,14 @@ } }, "node_modules/@expensify/react-native-live-markdown": { - "version": "0.1.70", - "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.70.tgz", - "integrity": "sha512-HyqBtZyvuJFB4gIUECKIMxWCnTPlPj+GPWmw80VzMBRFV9QiFRKUKRWefNEJ1cXV5hl8a6oOWDQla+dCnjCzOQ==", + "version": "0.1.82", + "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.82.tgz", + "integrity": "sha512-w/K2+0d1sAYvyLVpPv1ufDOTaj4y96Z362N3JDN+SDUmPQN2MvVGwsTL0ltzdw78yd62azFcQl6th7P6l62THQ==", + "workspaces": [ + "parser", + "example", + "WebExample" + ], "engines": { "node": ">= 18.0.0" }, diff --git a/package.json b/package.json index bfd035211038..34a825069341 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "@babel/plugin-proposal-private-methods": "^7.18.6", "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@dotlottie/react-player": "^1.6.3", - "@expensify/react-native-live-markdown": "0.1.70", + "@expensify/react-native-live-markdown": "0.1.82", "@expo/metro-runtime": "~3.1.1", "@formatjs/intl-datetimeformat": "^6.10.0", "@formatjs/intl-listformat": "^7.2.2", From 95f9a526efc6f57eef879ddb108759e6fbc6bce0 Mon Sep 17 00:00:00 2001 From: cretadn22 Date: Fri, 7 Jun 2024 19:37:49 +0700 Subject: [PATCH 118/230] hide amount in submit button if skip confirmtion page --- src/pages/iou/MoneyRequestAmountForm.tsx | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/pages/iou/MoneyRequestAmountForm.tsx b/src/pages/iou/MoneyRequestAmountForm.tsx index 5bbc9d22a97f..afea2efd266c 100644 --- a/src/pages/iou/MoneyRequestAmountForm.tsx +++ b/src/pages/iou/MoneyRequestAmountForm.tsx @@ -228,19 +228,7 @@ function MoneyRequestAmountForm( ); const buttonText: string = useMemo(() => { - const currentAmount = moneyRequestAmountInput.current?.getAmount() ?? ''; if (skipConfirmation) { - if (currentAmount !== '') { - const currencyAmount = CurrencyUtils.convertToDisplayString(CurrencyUtils.convertToBackendAmount(Number.parseFloat(currentAmount)), currency) ?? ''; - let text = translate('iou.submitAmount', {amount: currencyAmount}); - if (iouType === CONST.IOU.TYPE.SPLIT) { - text = translate('iou.splitAmount', {amount: currencyAmount}); - } else if (iouType === CONST.IOU.TYPE.TRACK) { - text = translate('iou.trackAmount', {amount: currencyAmount}); - } - return text[0].toUpperCase() + text.slice(1); - } - if (iouType === CONST.IOU.TYPE.SPLIT) { return translate('iou.splitExpense'); } From a1ad81a29e54d72aa194f34b4547a335270f9c09 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 7 Jun 2024 20:55:46 +0800 Subject: [PATCH 119/230] remove dupe openreport --- src/components/ReportActionItem/MoneyRequestAction.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestAction.tsx b/src/components/ReportActionItem/MoneyRequestAction.tsx index 4f91b2084b45..6dbe123b2054 100644 --- a/src/components/ReportActionItem/MoneyRequestAction.tsx +++ b/src/components/ReportActionItem/MoneyRequestAction.tsx @@ -92,7 +92,6 @@ function MoneyRequestAction({ } const childReportID = action?.childReportID ?? '0'; - Report.openReport(childReportID); Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(childReportID)); }; From f801dc83df490470c77be5dc54a35767900be680 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 7 Jun 2024 21:09:25 +0800 Subject: [PATCH 120/230] remove unused import --- src/components/ReportActionItem/MoneyRequestAction.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestAction.tsx b/src/components/ReportActionItem/MoneyRequestAction.tsx index 6dbe123b2054..fd82e723c6b9 100644 --- a/src/components/ReportActionItem/MoneyRequestAction.tsx +++ b/src/components/ReportActionItem/MoneyRequestAction.tsx @@ -10,7 +10,6 @@ import * as IOUUtils from '@libs/IOUUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import type {ContextMenuAnchor} from '@pages/home/report/ContextMenu/ReportActionContextMenu'; -import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; From 7f60a5d7db1d8710412a1a22a7eba0ef1d87bfec Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Fri, 7 Jun 2024 15:47:03 +0200 Subject: [PATCH 121/230] fix: set default size to empty string --- .../settings/Subscription/SubscriptionSize/substeps/Size.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Subscription/SubscriptionSize/substeps/Size.tsx b/src/pages/settings/Subscription/SubscriptionSize/substeps/Size.tsx index 75e0add6dd5e..15be8842b3bb 100644 --- a/src/pages/settings/Subscription/SubscriptionSize/substeps/Size.tsx +++ b/src/pages/settings/Subscription/SubscriptionSize/substeps/Size.tsx @@ -20,7 +20,7 @@ function Size({onNext}: SizeProps) { const defaultValues = { // TODO this is temporary and default value will be replaced in next phase once data in ONYX is ready - [INPUT_IDS.SUBSCRIPTION_SIZE]: '0', + [INPUT_IDS.SUBSCRIPTION_SIZE]: '', }; return ( From bdd0adc6e2b86741f8d8294b835003839df53c04 Mon Sep 17 00:00:00 2001 From: Yauheni Pasiukevich Date: Fri, 7 Jun 2024 14:03:37 +0200 Subject: [PATCH 122/230] implement billing banner --- .../simple-illustration__creditcardeyes.svg | 57 +++++++++++++++++++ src/components/Icon/Illustrations.ts | 2 + src/components/Section/index.tsx | 5 ++ .../CardSection/BillingBanner.tsx | 44 ++++++++++++++ 4 files changed, 108 insertions(+) create mode 100644 assets/images/simple-illustrations/simple-illustration__creditcardeyes.svg create mode 100644 src/pages/settings/Subscription/CardSection/BillingBanner.tsx diff --git a/assets/images/simple-illustrations/simple-illustration__creditcardeyes.svg b/assets/images/simple-illustrations/simple-illustration__creditcardeyes.svg new file mode 100644 index 000000000000..17ff47e6ca12 --- /dev/null +++ b/assets/images/simple-illustrations/simple-illustration__creditcardeyes.svg @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/Icon/Illustrations.ts b/src/components/Icon/Illustrations.ts index 53b8aa8acb72..3fe36239d631 100644 --- a/src/components/Icon/Illustrations.ts +++ b/src/components/Icon/Illustrations.ts @@ -49,6 +49,7 @@ import CompanyCard from '@assets/images/simple-illustrations/simple-illustration import ConciergeBubble from '@assets/images/simple-illustrations/simple-illustration__concierge-bubble.svg'; import ConciergeNew from '@assets/images/simple-illustrations/simple-illustration__concierge.svg'; import CreditCardsNew from '@assets/images/simple-illustrations/simple-illustration__credit-cards.svg'; +import CreditCardEyes from '@assets/images/simple-illustrations/simple-illustration__creditcardeyes.svg'; import EmailAddress from '@assets/images/simple-illustrations/simple-illustration__email-address.svg'; import FolderOpen from '@assets/images/simple-illustrations/simple-illustration__folder-open.svg'; import Gears from '@assets/images/simple-illustrations/simple-illustration__gears.svg'; @@ -190,4 +191,5 @@ export { ExpensifyApprovedLogoLight, SendMoney, CheckmarkCircle, + CreditCardEyes, }; diff --git a/src/components/Section/index.tsx b/src/components/Section/index.tsx index e7cadbe73b82..49ef1e89cff9 100644 --- a/src/components/Section/index.tsx +++ b/src/components/Section/index.tsx @@ -86,6 +86,9 @@ type SectionProps = Partial & { /** The height of the icon. */ iconHeight?: number; + + /** Banner to display at the top of the section */ + banner?: ReactNode; }; function isIllustrationLottieAnimation(illustration: DotLottieAnimation | IconAsset | undefined): illustration is DotLottieAnimation { @@ -119,6 +122,7 @@ function Section({ iconWidth, iconHeight, renderSubtitle, + banner = null, }: SectionProps) { const styles = useThemeStyles(); const theme = useTheme(); @@ -133,6 +137,7 @@ function Section({ return ( + {banner} {cardLayout === CARD_LAYOUT.ICON_ON_TOP && ( + + + {title} + {subtitle} + + {isError && shouldShowRedDotIndicator && ( + + )} + + ); +} + +BillingBanner.displayName = 'BillingBanner'; + +export default BillingBanner; From 1e95d49d6e8e7b536bc725b575a184f4e3011cb0 Mon Sep 17 00:00:00 2001 From: cretadn22 Date: Fri, 7 Jun 2024 21:39:06 +0700 Subject: [PATCH 123/230] remove dependency --- src/pages/iou/MoneyRequestAmountForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/MoneyRequestAmountForm.tsx b/src/pages/iou/MoneyRequestAmountForm.tsx index afea2efd266c..ce1cddc91958 100644 --- a/src/pages/iou/MoneyRequestAmountForm.tsx +++ b/src/pages/iou/MoneyRequestAmountForm.tsx @@ -238,7 +238,7 @@ function MoneyRequestAmountForm( return translate('iou.submitExpense'); } return isEditing ? translate('common.save') : translate('common.next'); - }, [skipConfirmation, iouType, currency, isEditing, translate]); + }, [skipConfirmation, iouType, isEditing, translate]); const canUseTouchScreen = DeviceCapabilities.canUseTouchScreen(); From 58fe38188973d11947d8912b7a30464e13230e6a Mon Sep 17 00:00:00 2001 From: Yauheni Pasiukevich Date: Fri, 7 Jun 2024 17:17:33 +0200 Subject: [PATCH 124/230] fix header --- src/pages/settings/Subscription/CardSection/BillingBanner.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Subscription/CardSection/BillingBanner.tsx b/src/pages/settings/Subscription/CardSection/BillingBanner.tsx index 6aed715563b1..0ccffdf511fc 100644 --- a/src/pages/settings/Subscription/CardSection/BillingBanner.tsx +++ b/src/pages/settings/Subscription/CardSection/BillingBanner.tsx @@ -26,7 +26,7 @@ function BillingBanner({title, subtitle, isError, shouldShowRedDotIndicator}: Bi height={48} /> - {title} + {title} {subtitle} {isError && shouldShowRedDotIndicator && ( From 492a6726ef093c645e6800a8afe5f8a7e490e930 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Fri, 7 Jun 2024 16:39:03 +0100 Subject: [PATCH 125/230] refactor: apply suggestions --- .../ReportActionItem/MoneyRequestView.tsx | 14 ++++----- src/hooks/useViolations.ts | 6 ++-- src/libs/Violations/ViolationsUtils.ts | 31 ++++++++----------- src/types/onyx/TransactionViolation.ts | 1 - 4 files changed, 23 insertions(+), 29 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index f3b0b3e863ed..d1fc875832ec 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -171,8 +171,8 @@ function MoneyRequestView({ const {getViolationsForField} = useViolations(transactionViolations ?? []); const hasViolations = useCallback( - (field: ViolationField, data?: OnyxTypes.TransactionViolation['data'], policyHasDependentTags?: boolean): boolean => - !!canUseViolations && getViolationsForField(field, data, policyHasDependentTags).length > 0, + (field: ViolationField, data?: OnyxTypes.TransactionViolation['data'], policyHasDependentTags = false, tagValue?: string): boolean => + !!canUseViolations && getViolationsForField(field, data, policyHasDependentTags, tagValue).length > 0, [canUseViolations, getViolationsForField], ); @@ -239,7 +239,7 @@ function MoneyRequestView({ const getPendingFieldAction = (fieldPath: TransactionPendingFieldsKey) => transaction?.pendingFields?.[fieldPath] ?? pendingAction; const getErrorForField = useCallback( - (field: ViolationField, data?: OnyxTypes.TransactionViolation['data'], policyHasDependentTags?: boolean) => { + (field: ViolationField, data?: OnyxTypes.TransactionViolation['data'], policyHasDependentTags = false, tagValue?: string) => { // Checks applied when creating a new expense // NOTE: receipt field can return multiple violations, so we need to handle it separately const fieldChecks: Partial> = { @@ -265,8 +265,8 @@ function MoneyRequestView({ } // Return violations if there are any - if (hasViolations(field, data, policyHasDependentTags)) { - const violations = getViolationsForField(field, data, policyHasDependentTags); + if (hasViolations(field, data, policyHasDependentTags, tagValue)) { + const violations = getViolationsForField(field, data, policyHasDependentTags, tagValue); return ViolationsUtils.getViolationTranslation(violations[0], translate); } @@ -492,9 +492,9 @@ function MoneyRequestView({ { tagListIndex: index, tagListName: name, - tagListValue: TransactionUtils.getTagForDisplay(transaction, index), }, PolicyUtils.hasDependentTags(policy, policyTagList), + TransactionUtils.getTagForDisplay(transaction, index), ) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined @@ -504,9 +504,9 @@ function MoneyRequestView({ { tagListIndex: index, tagListName: name, - tagListValue: TransactionUtils.getTagForDisplay(transaction, index), }, PolicyUtils.hasDependentTags(policy, policyTagList), + TransactionUtils.getTagForDisplay(transaction, index), )} /> diff --git a/src/hooks/useViolations.ts b/src/hooks/useViolations.ts index 9120c0709d9a..83c725a48db0 100644 --- a/src/hooks/useViolations.ts +++ b/src/hooks/useViolations.ts @@ -62,7 +62,7 @@ function useViolations(violations: TransactionViolation[]) { }, [violations]); const getViolationsForField = useCallback( - (field: ViolationField, data?: TransactionViolation['data'], policyHasDependentTags?: boolean) => { + (field: ViolationField, data?: TransactionViolation['data'], policyHasDependentTags = false, tagValue?: string) => { const currentViolations = violationsByField.get(field) ?? []; // someTagLevelsRequired has special logic becase data.errorIndexes is a bit unique in how it denotes the tag list that has the violation @@ -81,7 +81,7 @@ function useViolations(violations: TransactionViolation[]) { // missingTag has special logic for policies with dependent tags, because only one violation is returned for all tags // when no tags are present, so the tag name isn't set in the violation data. That's why we add it here - if (!!policyHasDependentTags && currentViolations[0]?.name === CONST.VIOLATIONS.MISSING_TAG && data?.tagListName) { + if (policyHasDependentTags && currentViolations[0]?.name === CONST.VIOLATIONS.MISSING_TAG && data?.tagListName) { return [ { ...currentViolations[0], @@ -100,7 +100,7 @@ function useViolations(violations: TransactionViolation[]) { // allTagLevelsRequired has special logic because it is returned when one but not all the tags are set, // so we need to return the violation for the tag fields without a tag set - if (currentViolations[0]?.name === CONST.VIOLATIONS.ALL_TAG_LEVELS_REQUIRED && data?.tagListValue) { + if (currentViolations[0]?.name === CONST.VIOLATIONS.ALL_TAG_LEVELS_REQUIRED && tagValue) { return currentViolations.filter((violation) => violation.data?.tagName === data?.tagListName); } diff --git a/src/libs/Violations/ViolationsUtils.ts b/src/libs/Violations/ViolationsUtils.ts index 2786fdd6ad05..00de9a2175ae 100644 --- a/src/libs/Violations/ViolationsUtils.ts +++ b/src/libs/Violations/ViolationsUtils.ts @@ -9,13 +9,6 @@ import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PolicyCategories, PolicyTagList, Transaction, TransactionViolation, ViolationName} from '@src/types/onyx'; -const TRANSACTION_VIOLATIONS_FILTER = [ - CONST.VIOLATIONS.SOME_TAG_LEVELS_REQUIRED, - CONST.VIOLATIONS.TAG_OUT_OF_POLICY, - CONST.VIOLATIONS.MISSING_TAG, - CONST.VIOLATIONS.ALL_TAG_LEVELS_REQUIRED, -] as ViolationName[]; - /** * Calculates tag out of policy and missing tag violations for the given transaction */ @@ -76,7 +69,7 @@ function getTagViolationsForDependentTags(policyTagList: PolicyTagList, transact tagViolations.push({ name: CONST.VIOLATIONS.ALL_TAG_LEVELS_REQUIRED, type: CONST.VIOLATION_TYPES.VIOLATION, - data: undefined, + data: {}, }); } } @@ -84,19 +77,19 @@ function getTagViolationsForDependentTags(policyTagList: PolicyTagList, transact return tagViolations; } -/** - * Calculates missing tag violations for policies with independent tags, - * by returning one per tag with its corresponding tagName in the data - */ +/** Calculates missing tag violations for policies with independent tags */ function getTagViolationForIndependentTags(policyTagList: PolicyTagList, transactionViolations: TransactionViolation[], transaction: Transaction) { const policyTagKeys = getSortedTagKeys(policyTagList); const selectedTags = transaction.tag?.split(CONST.COLON) ?? []; let newTransactionViolations = [...transactionViolations]; + newTransactionViolations = newTransactionViolations.filter( + (violation) => violation.name !== CONST.VIOLATIONS.SOME_TAG_LEVELS_REQUIRED && violation.name !== CONST.VIOLATIONS.TAG_OUT_OF_POLICY, + ); + // We first get the errorIndexes for someTagLevelsRequired. If it's not empty, we puth SOME_TAG_LEVELS_REQUIRED in Onyx. // Otherwise, we put TAG_OUT_OF_POLICY in Onyx (when applicable) const errorIndexes = []; - for (let i = 0; i < policyTagKeys.length; i++) { const isTagRequired = policyTagList[policyTagKeys[i]].required ?? true; const isTagSelected = !!selectedTags[i]; @@ -104,7 +97,6 @@ function getTagViolationForIndependentTags(policyTagList: PolicyTagList, transac errorIndexes.push(i); } } - if (errorIndexes.length !== 0) { newTransactionViolations.push({ name: CONST.VIOLATIONS.SOME_TAG_LEVELS_REQUIRED, @@ -115,7 +107,6 @@ function getTagViolationForIndependentTags(policyTagList: PolicyTagList, transac }); } else { let hasInvalidTag = false; - for (let i = 0; i < policyTagKeys.length; i++) { const selectedTag = selectedTags[i]; const tags = policyTagList[policyTagKeys[i]].tags; @@ -132,14 +123,12 @@ function getTagViolationForIndependentTags(policyTagList: PolicyTagList, transac break; } } - if (!hasInvalidTag) { newTransactionViolations = reject(newTransactionViolations, { name: CONST.VIOLATIONS.TAG_OUT_OF_POLICY, }); } } - return newTransactionViolations; } @@ -153,7 +142,13 @@ function getTagViolationsForMultiLevelTags( policyTagList: PolicyTagList, hasDependentTags: boolean, ): TransactionViolation[] { - const filteredTransactionViolations = transactionViolations.filter((violation) => !TRANSACTION_VIOLATIONS_FILTER.includes(violation.name)); + const tagViolations = [ + CONST.VIOLATIONS.SOME_TAG_LEVELS_REQUIRED, + CONST.VIOLATIONS.TAG_OUT_OF_POLICY, + CONST.VIOLATIONS.MISSING_TAG, + CONST.VIOLATIONS.ALL_TAG_LEVELS_REQUIRED, + ] as ViolationName[]; + const filteredTransactionViolations = transactionViolations.filter((violation) => !tagViolations.includes(violation.name)); if (hasDependentTags) { return getTagViolationsForDependentTags(policyTagList, filteredTransactionViolations, updatedTransaction.tag ?? ''); diff --git a/src/types/onyx/TransactionViolation.ts b/src/types/onyx/TransactionViolation.ts index e81f7196209b..ab2037ff336a 100644 --- a/src/types/onyx/TransactionViolation.ts +++ b/src/types/onyx/TransactionViolation.ts @@ -28,7 +28,6 @@ type TransactionViolation = { taxName?: string; tagListIndex?: number; tagListName?: string; - tagListValue?: string; errorIndexes?: number[]; pendingPattern?: boolean; }; From 24875929993f87d1263bcd2ff21151c99159ff67 Mon Sep 17 00:00:00 2001 From: Yauheni Pasiukevich Date: Fri, 7 Jun 2024 17:45:28 +0200 Subject: [PATCH 126/230] fix banner subtitle --- .../settings/Subscription/CardSection/BillingBanner.tsx | 2 +- src/styles/index.ts | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/pages/settings/Subscription/CardSection/BillingBanner.tsx b/src/pages/settings/Subscription/CardSection/BillingBanner.tsx index 0ccffdf511fc..3bf183a2a3d8 100644 --- a/src/pages/settings/Subscription/CardSection/BillingBanner.tsx +++ b/src/pages/settings/Subscription/CardSection/BillingBanner.tsx @@ -27,7 +27,7 @@ function BillingBanner({title, subtitle, isError, shouldShowRedDotIndicator}: Bi /> {title} - {subtitle} + {subtitle} {isError && shouldShowRedDotIndicator && ( width: variables.iconSizeExtraLarge, }, + billingBannerSubtitle: { + color: theme.textSupporting, + lineHeight: variables.lineHeightXLarge, + }, + selectCircle: { width: variables.componentSizeSmall, height: variables.componentSizeSmall, From df3326a8ee1faad996cb41cc7f10b0bd9b224478 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Fri, 7 Jun 2024 16:47:37 +0100 Subject: [PATCH 127/230] refactor: remove unused argument --- src/libs/Violations/ViolationsUtils.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libs/Violations/ViolationsUtils.ts b/src/libs/Violations/ViolationsUtils.ts index 00de9a2175ae..d03c2d0f4324 100644 --- a/src/libs/Violations/ViolationsUtils.ts +++ b/src/libs/Violations/ViolationsUtils.ts @@ -138,7 +138,6 @@ function getTagViolationForIndependentTags(policyTagList: PolicyTagList, transac function getTagViolationsForMultiLevelTags( updatedTransaction: Transaction, transactionViolations: TransactionViolation[], - policyRequiresTags: boolean, policyTagList: PolicyTagList, hasDependentTags: boolean, ): TransactionViolation[] { @@ -215,7 +214,7 @@ const ViolationsUtils = { newTransactionViolations = Object.keys(policyTagList).length === 1 ? getTagViolationsForSingleLevelTags(updatedTransaction, newTransactionViolations, policyRequiresTags, policyTagList) - : getTagViolationsForMultiLevelTags(updatedTransaction, newTransactionViolations, policyRequiresTags, policyTagList, hasDependentTags); + : getTagViolationsForMultiLevelTags(updatedTransaction, newTransactionViolations, policyTagList, hasDependentTags); } return { From e7f175fa239c1ccf8a893b640a7da6d789b07338 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Fri, 7 Jun 2024 17:39:48 +0100 Subject: [PATCH 128/230] refactor: apply suggestions --- .../ReportActionItem/MoneyRequestView.tsx | 74 ++++++++----------- src/types/onyx/PolicyTag.ts | 4 + 2 files changed, 36 insertions(+), 42 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index d1fc875832ec..4324b865cee0 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -334,6 +334,37 @@ function MoneyRequestView({ ...parentReportAction?.errors, }; + const TagList = policyTagLists.map(({name, orderWeight}, index) => { + const tagError = getErrorForField( + 'tag', + { + tagListIndex: index, + tagListName: name, + }, + PolicyUtils.hasDependentTags(policy, policyTagList), + TransactionUtils.getTagForDisplay(transaction, index), + ); + return ( + + + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAG.getRoute(CONST.IOU.ACTION.EDIT, iouType, orderWeight, transaction?.transactionID ?? '', report.reportID)) + } + brickRoadIndicator={tagError ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} + errorText={tagError} + /> + + ); + }); + return ( {shouldShowAnimatedBackground && } @@ -469,48 +500,7 @@ function MoneyRequestView({ /> )} - {shouldShowTag && - policyTagLists.map(({name, orderWeight}, index) => ( - - - Navigation.navigate( - ROUTES.MONEY_REQUEST_STEP_TAG.getRoute(CONST.IOU.ACTION.EDIT, iouType, orderWeight, transaction?.transactionID ?? '', report.reportID), - ) - } - brickRoadIndicator={ - getErrorForField( - 'tag', - { - tagListIndex: index, - tagListName: name, - }, - PolicyUtils.hasDependentTags(policy, policyTagList), - TransactionUtils.getTagForDisplay(transaction, index), - ) - ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR - : undefined - } - errorText={getErrorForField( - 'tag', - { - tagListIndex: index, - tagListName: name, - }, - PolicyUtils.hasDependentTags(policy, policyTagList), - TransactionUtils.getTagForDisplay(transaction, index), - )} - /> - - ))} + {shouldShowTag && TagList} {isCardTransaction && ( ; From df5e19f7bd5faab54cdbd605ed5de19fa6d88308 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Fri, 7 Jun 2024 18:12:55 +0100 Subject: [PATCH 129/230] refactor: apply suggestions --- src/components/ReportActionItem/MoneyRequestView.tsx | 4 ++-- src/libs/Violations/ViolationsUtils.ts | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 4324b865cee0..cc99a3f6e108 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -334,7 +334,7 @@ function MoneyRequestView({ ...parentReportAction?.errors, }; - const TagList = policyTagLists.map(({name, orderWeight}, index) => { + const tagList = policyTagLists.map(({name, orderWeight}, index) => { const tagError = getErrorForField( 'tag', { @@ -500,7 +500,7 @@ function MoneyRequestView({ /> )} - {shouldShowTag && TagList} + {shouldShowTag && tagList} {isCardTransaction && ( Date: Sat, 8 Jun 2024 00:19:26 +0700 Subject: [PATCH 130/230] fix eslint --- src/pages/signin/LoginForm/BaseLoginForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/signin/LoginForm/BaseLoginForm.tsx b/src/pages/signin/LoginForm/BaseLoginForm.tsx index 29d6ee7bcc75..f51fd2fe5880 100644 --- a/src/pages/signin/LoginForm/BaseLoginForm.tsx +++ b/src/pages/signin/LoginForm/BaseLoginForm.tsx @@ -224,7 +224,7 @@ function BaseLoginForm({account, credentials, closeAccount, blurOnSubmit = false if (!Browser.isMobileWebKit()) { return; } - // On mobile WebKit browsers, when an input field gains focus, the keyboard appears and the virtual viewport is resized and scrolled to make the input field visible. + // On mobile WebKit browsers, when an input field gains focus, the keyboard appears and the virtual viewport is resized and scrolled to make the input field visible. // This occurs even when there is enough space to display both the input field and the submit button in the current view. // so this change to correct the scroll position when the input field gains focus. InteractionManager.runAfterInteractions(() => { From 86a25a915e84fa6a24efe0ccd438609352bd1e87 Mon Sep 17 00:00:00 2001 From: Ren Jones <153645623+ren-jones@users.noreply.github.com> Date: Fri, 7 Jun 2024 13:38:12 -0500 Subject: [PATCH 131/230] Update Book-with-Expensify-Travel.md Extra spacing removed --- .../new-expensify/travel/Book-with-Expensify-Travel.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/docs/articles/new-expensify/travel/Book-with-Expensify-Travel.md b/docs/articles/new-expensify/travel/Book-with-Expensify-Travel.md index c48565f5149e..5d25670ac5ab 100644 --- a/docs/articles/new-expensify/travel/Book-with-Expensify-Travel.md +++ b/docs/articles/new-expensify/travel/Book-with-Expensify-Travel.md @@ -87,8 +87,3 @@ The traveler is emailed an itinerary of the booking. Additionally, - If booked with an Expensify Card, the trip is automatically reconciled. - - - - - From 69a4c79dfc9eefafd152e48cf9c0fafea69b1b70 Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Fri, 7 Jun 2024 12:44:39 -0600 Subject: [PATCH 132/230] Prettier --- src/libs/Network/SequentialQueue.ts | 2 +- src/libs/actions/Network.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/Network/SequentialQueue.ts b/src/libs/Network/SequentialQueue.ts index f27c89a47f72..43d9bc26e347 100644 --- a/src/libs/Network/SequentialQueue.ts +++ b/src/libs/Network/SequentialQueue.ts @@ -81,7 +81,7 @@ function process(): Promise { // A response might indicate that the queue should be paused. This happens when a gap in onyx updates is detected between the client and the server and // that gap needs resolved before the queue can continue. if (response?.shouldPauseQueue) { - console.debug('[SequentialQueue] Handled \'shouldPauseQueue\' in response. Pausing the queue.'); + console.debug("[SequentialQueue] Handled 'shouldPauseQueue' in response. Pausing the queue."); pause(); } PersistedRequests.remove(requestToProcess); diff --git a/src/libs/actions/Network.ts b/src/libs/actions/Network.ts index 2e5ef5b605f7..90d4649311d9 100644 --- a/src/libs/actions/Network.ts +++ b/src/libs/actions/Network.ts @@ -1,7 +1,7 @@ import Onyx from 'react-native-onyx'; +import Log from '@libs/Log'; import type {NetworkStatus} from '@libs/NetworkConnection'; import ONYXKEYS from '@src/ONYXKEYS'; -import Log from '@libs/Log'; function setIsBackendReachable(isBackendReachable: boolean, reason: string) { if (isBackendReachable) { From 4f8ec01a36a821009c2c2b473669393aa1c7a2d8 Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Fri, 7 Jun 2024 12:53:08 -0600 Subject: [PATCH 133/230] Change debug to logs --- src/libs/Network/SequentialQueue.ts | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/libs/Network/SequentialQueue.ts b/src/libs/Network/SequentialQueue.ts index 43d9bc26e347..10df3c703c92 100644 --- a/src/libs/Network/SequentialQueue.ts +++ b/src/libs/Network/SequentialQueue.ts @@ -1,5 +1,6 @@ import Onyx from 'react-native-onyx'; import * as ActiveClientManager from '@libs/ActiveClientManager'; +import Log from '@libs/Log'; import * as Request from '@libs/Request'; import * as RequestThrottle from '@libs/RequestThrottle'; import * as PersistedRequests from '@userActions/PersistedRequests'; @@ -26,11 +27,11 @@ let isQueuePaused = false; */ function pause() { if (isQueuePaused) { - console.debug('[SequentialQueue] Queue already paused'); + Log.info('[SequentialQueue] Queue already paused'); return; } - console.debug('[SequentialQueue] Pausing the queue'); + Log.info('[SequentialQueue] Pausing the queue'); isQueuePaused = true; } @@ -41,7 +42,7 @@ function flushOnyxUpdatesQueue() { // The only situation where the queue is paused is if we found a gap between the app current data state and our server's. If that happens, // we'll trigger async calls to make the client updated again. While we do that, we don't want to insert anything in Onyx. if (isQueuePaused) { - console.debug('[SequentialQueue] Queue already paused'); + Log.info('[SequentialQueue] Queue already paused'); return; } QueuedOnyxUpdates.flushQueue(); @@ -58,18 +59,18 @@ function flushOnyxUpdatesQueue() { function process(): Promise { // When the queue is paused, return early. This prevents any new requests from happening. The queue will be flushed again when the queue is unpaused. if (isQueuePaused) { - console.debug('[SequentialQueue] Unable to process. Queue is paused.'); + Log.info('[SequentialQueue] Unable to process. Queue is paused.'); return Promise.resolve(); } if (NetworkStore.isOffline()) { - console.debug('[SequentialQueue] Unable to process. We are offline.'); + Log.info('[SequentialQueue] Unable to process. We are offline.'); return Promise.resolve(); } const persistedRequests = PersistedRequests.getAll(); if (persistedRequests.length === 0) { - console.debug('[SequentialQueue] Unable to process. No requests to process.'); + Log.info('[SequentialQueue] Unable to process. No requests to process.'); return Promise.resolve(); } @@ -81,7 +82,7 @@ function process(): Promise { // A response might indicate that the queue should be paused. This happens when a gap in onyx updates is detected between the client and the server and // that gap needs resolved before the queue can continue. if (response?.shouldPauseQueue) { - console.debug("[SequentialQueue] Handled 'shouldPauseQueue' in response. Pausing the queue."); + Log.info("[SequentialQueue] Handled 'shouldPauseQueue' in response. Pausing the queue."); pause(); } PersistedRequests.remove(requestToProcess); @@ -112,24 +113,24 @@ function process(): Promise { function flush() { // When the queue is paused, return early. This will keep an requests in the queue and they will get flushed again when the queue is unpaused if (isQueuePaused) { - console.debug('[SequentialQueue] Unable to flush. Queue is paused.'); + Log.info('[SequentialQueue] Unable to flush. Queue is paused.'); return; } if (isSequentialQueueRunning) { - console.debug('[SequentialQueue] Unable to flush. Queue is already running.'); + Log.info('[SequentialQueue] Unable to flush. Queue is already running.'); return; } if (PersistedRequests.getAll().length === 0) { - console.debug('[SequentialQueue] Unable to flush. No requests to process.'); + Log.info('[SequentialQueue] Unable to flush. No requests to process.'); return; } // ONYXKEYS.PERSISTED_REQUESTS is shared across clients, thus every client/tab will have a copy // It is very important to only process the queue from leader client otherwise requests will be duplicated. if (!ActiveClientManager.isClientTheLeader()) { - console.debug('[SequentialQueue] Unable to flush. Client is not the leader.'); + Log.info('[SequentialQueue] Unable to flush. Client is not the leader.'); return; } @@ -146,7 +147,7 @@ function flush() { callback: () => { Onyx.disconnect(connectionID); process().finally(() => { - console.debug('[SequentialQueue] Finished processing queue.'); + Log.info('[SequentialQueue] Finished processing queue.'); isSequentialQueueRunning = false; if (NetworkStore.isOffline() || PersistedRequests.getAll().length === 0) { resolveIsReadyPromise?.(); @@ -163,7 +164,7 @@ function flush() { */ function unpause() { if (!isQueuePaused) { - console.debug('[SequentialQueue] Unable to unpause queue. We are already processing.'); + Log.info('[SequentialQueue] Unable to unpause queue. We are already processing.'); return; } From ed85fecf451ea05ba647bfae8134a6d7e273f111 Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Fri, 7 Jun 2024 12:53:19 -0600 Subject: [PATCH 134/230] Move check outside of promise --- src/libs/NetworkConnection.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/NetworkConnection.ts b/src/libs/NetworkConnection.ts index 0aa3f224858d..897b947e6815 100644 --- a/src/libs/NetworkConnection.ts +++ b/src/libs/NetworkConnection.ts @@ -125,17 +125,17 @@ function subscribeToBackendAndInternetReachability(): () => void { NetworkActions.setIsBackendReachable(true, 'successfully completed API request'); return; } + NetworkActions.setIsBackendReachable(false, 'request succeeded, but internet reachability test failed'); checkInternetReachability().then((isInternetReachable: boolean) => { setOfflineStatus(!isInternetReachable); setNetWorkStatus(isInternetReachable); - NetworkActions.setIsBackendReachable(false, 'request succeeded, but internet reachability test failed'); }); }) .catch(() => { + NetworkActions.setIsBackendReachable(false, 'request failed and internet reachability test failed'); checkInternetReachability().then((isInternetReachable: boolean) => { setOfflineStatus(!isInternetReachable); setNetWorkStatus(isInternetReachable); - NetworkActions.setIsBackendReachable(false, 'request failed and internet reachability test failed'); }); }); }, CONST.NETWORK.BACKEND_CHECK_INTERVAL_MS); From 98e2e5bf8f931ea82e2f2b2e2c3bf7397dc5cddc Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Fri, 7 Jun 2024 13:00:34 -0600 Subject: [PATCH 135/230] Add more logs for when going offline --- src/libs/NetworkConnection.ts | 14 +++++++------- src/libs/actions/Network.ts | 7 ++++++- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/libs/NetworkConnection.ts b/src/libs/NetworkConnection.ts index 897b947e6815..60f838759ef9 100644 --- a/src/libs/NetworkConnection.ts +++ b/src/libs/NetworkConnection.ts @@ -37,8 +37,8 @@ const triggerReconnectionCallbacks = throttle( * Called when the offline status of the app changes and if the network is "reconnecting" (going from offline to online) * then all of the reconnection callbacks are triggered */ -function setOfflineStatus(isCurrentlyOffline: boolean): void { - NetworkActions.setIsOffline(isCurrentlyOffline); +function setOfflineStatus(isCurrentlyOffline: boolean, reason: string): void { + NetworkActions.setIsOffline(isCurrentlyOffline, reason); // When reconnecting, ie, going from offline to online, all the reconnection callbacks // are triggered (this is usually Actions that need to re-download data from the server) @@ -74,13 +74,13 @@ Onyx.connect({ } shouldForceOffline = currentShouldForceOffline; if (shouldForceOffline) { - setOfflineStatus(true); + setOfflineStatus(true, 'shouldForceOffline was detected in the Onyx data'); Log.info(`[NetworkStatus] Setting "offlineStatus" to "true" because user is under force offline`); } else { // If we are no longer forcing offline fetch the NetInfo to set isOffline appropriately NetInfo.fetch().then((state) => { const isInternetReachable = !!state.isInternetReachable; - setOfflineStatus(isInternetReachable); + setOfflineStatus(isInternetReachable, 'NetInfo checked if the internet is reachable'); Log.info( `[NetworkStatus] The force-offline mode was turned off. Getting the device network status from NetInfo. Network state: ${JSON.stringify( state, @@ -127,14 +127,14 @@ function subscribeToBackendAndInternetReachability(): () => void { } NetworkActions.setIsBackendReachable(false, 'request succeeded, but internet reachability test failed'); checkInternetReachability().then((isInternetReachable: boolean) => { - setOfflineStatus(!isInternetReachable); + setOfflineStatus(!isInternetReachable, 'checkInternetReachability was called after api/ping returned a non-200 jsonCode'); setNetWorkStatus(isInternetReachable); }); }) .catch(() => { NetworkActions.setIsBackendReachable(false, 'request failed and internet reachability test failed'); checkInternetReachability().then((isInternetReachable: boolean) => { - setOfflineStatus(!isInternetReachable); + setOfflineStatus(!isInternetReachable, 'checkInternetReachability was called after api/ping request failed'); setNetWorkStatus(isInternetReachable); }); }); @@ -163,7 +163,7 @@ function subscribeToNetworkStatus(): () => void { Log.info('[NetworkConnection] Not setting offline status because shouldForceOffline = true'); return; } - setOfflineStatus(state.isInternetReachable === false); + setOfflineStatus(state.isInternetReachable === false, 'NetInfo received a state change event'); Log.info(`[NetworkStatus] NetInfo.addEventListener event coming, setting "offlineStatus" to ${!!state.isInternetReachable} with network state: ${JSON.stringify(state)}`); setNetWorkStatus(state.isInternetReachable); }); diff --git a/src/libs/actions/Network.ts b/src/libs/actions/Network.ts index 90d4649311d9..6bb0b9454012 100644 --- a/src/libs/actions/Network.ts +++ b/src/libs/actions/Network.ts @@ -12,7 +12,12 @@ function setIsBackendReachable(isBackendReachable: boolean, reason: string) { Onyx.merge(ONYXKEYS.NETWORK, {isBackendReachable}); } -function setIsOffline(isOffline: boolean) { +function setIsOffline(isOffline: boolean, reason: string) { + if (isOffline) { + Log.info(`[Network] Client is entering offline mode because: ${reason}`); + } else { + Log.info(`[Network] Client is back online because: ${reason}`); + } Onyx.merge(ONYXKEYS.NETWORK, {isOffline}); } From e404d1c9803a7d73fe748a981e81da5055a6b07f Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Fri, 7 Jun 2024 13:12:09 -0600 Subject: [PATCH 136/230] Default reason to a blank screen --- src/libs/actions/Network.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Network.ts b/src/libs/actions/Network.ts index 6bb0b9454012..5f9600818b2d 100644 --- a/src/libs/actions/Network.ts +++ b/src/libs/actions/Network.ts @@ -12,7 +12,7 @@ function setIsBackendReachable(isBackendReachable: boolean, reason: string) { Onyx.merge(ONYXKEYS.NETWORK, {isBackendReachable}); } -function setIsOffline(isOffline: boolean, reason: string) { +function setIsOffline(isOffline: boolean, reason = '') { if (isOffline) { Log.info(`[Network] Client is entering offline mode because: ${reason}`); } else { From cfb586c61b16b5c81a2d6aeeaf3e1ea5b0c94e8e Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Fri, 7 Jun 2024 13:13:48 -0600 Subject: [PATCH 137/230] Restructure logic for optional reason --- src/libs/actions/Network.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/Network.ts b/src/libs/actions/Network.ts index 5f9600818b2d..883e336d6c90 100644 --- a/src/libs/actions/Network.ts +++ b/src/libs/actions/Network.ts @@ -13,10 +13,11 @@ function setIsBackendReachable(isBackendReachable: boolean, reason: string) { } function setIsOffline(isOffline: boolean, reason = '') { - if (isOffline) { - Log.info(`[Network] Client is entering offline mode because: ${reason}`); - } else { - Log.info(`[Network] Client is back online because: ${reason}`); + if (reason) { + let textToLog = '[Network] Client is'; + textToLog += isOffline ? ' entering offline mode' : ' back online'; + textToLog += ` because: ${reason}`; + Log.info(textToLog); } Onyx.merge(ONYXKEYS.NETWORK, {isOffline}); } From 41ff6a2f873ef7b7648d01cdd4d2ae9221622aa1 Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Fri, 7 Jun 2024 13:30:58 -0600 Subject: [PATCH 138/230] Fix another default variable --- src/libs/NetworkConnection.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/NetworkConnection.ts b/src/libs/NetworkConnection.ts index 60f838759ef9..de064441047a 100644 --- a/src/libs/NetworkConnection.ts +++ b/src/libs/NetworkConnection.ts @@ -37,7 +37,7 @@ const triggerReconnectionCallbacks = throttle( * Called when the offline status of the app changes and if the network is "reconnecting" (going from offline to online) * then all of the reconnection callbacks are triggered */ -function setOfflineStatus(isCurrentlyOffline: boolean, reason: string): void { +function setOfflineStatus(isCurrentlyOffline: boolean, reason = ''): void { NetworkActions.setIsOffline(isCurrentlyOffline, reason); // When reconnecting, ie, going from offline to online, all the reconnection callbacks From 6b8c4b2e62ffc262671616467d42c4e44adc8fee Mon Sep 17 00:00:00 2001 From: truph01 Date: Sat, 8 Jun 2024 08:02:32 +0700 Subject: [PATCH 139/230] Fix: Remove isCalledVBBARef --- src/pages/workspace/WorkspacePageWithSections.tsx | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/pages/workspace/WorkspacePageWithSections.tsx b/src/pages/workspace/WorkspacePageWithSections.tsx index dab029435923..738a0663b94b 100644 --- a/src/pages/workspace/WorkspacePageWithSections.tsx +++ b/src/pages/workspace/WorkspacePageWithSections.tsx @@ -129,20 +129,17 @@ function WorkspacePageWithSections({ const firstRender = useRef(true); const isFocused = useIsFocused(); const prevPolicy = usePrevious(policy); - const isCalledVBBARef = useRef(false); useEffect(() => { // Because isLoading is false before merging in Onyx, we need firstRender ref to display loading page as well before isLoading is change to true firstRender.current = false; }, []); - useEffect(() => { - if (isCalledVBBARef.current || !policyID) { - return; - } - isCalledVBBARef.current = true; - fetchData(policyID, shouldSkipVBBACall); - }, [policyID, shouldSkipVBBACall]); + useFocusEffect( + useCallback(() => { + fetchData(policyID, shouldSkipVBBACall); + }, [policyID, shouldSkipVBBACall]), + ); const shouldShow = useMemo(() => { // If the policy object doesn't exist or contains only error data, we shouldn't display it. From 2df4f551b736e393cb4abc275cf9204420c2928f Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Sat, 8 Jun 2024 13:43:44 +0530 Subject: [PATCH 140/230] use deprecated ReportActionsUtils.getParentReportAction. Signed-off-by: Krishna Gupta --- src/libs/ReportUtils.ts | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 435460ec596e..fe732724ef3e 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2729,13 +2729,6 @@ function canEditReportAction(reportAction: OnyxEntry): boolean { ); } -function getParentReportAction(parentReportActions: OnyxEntry, parentReportActionID: string | undefined): OnyxEntry { - if (!parentReportActions || !parentReportActionID) { - return null; - } - return parentReportActions[parentReportActionID ?? '0']; -} - function canHoldUnholdReportAction(reportAction: OnyxEntry): {canHoldRequest: boolean; canUnholdRequest: boolean} { if (reportAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.IOU) { return {canHoldRequest: false, canUnholdRequest: false}; @@ -2753,10 +2746,8 @@ function canHoldUnholdReportAction(reportAction: OnyxEntry): {canH const transactionID = moneyRequestReport ? reportAction?.originalMessage?.IOUTransactionID : 0; const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? ({} as Transaction); - const parentReportActionsKey = `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${moneyRequestReport.parentReportID}`; - const parentReportActions = allReportActions?.[parentReportActionsKey]; const parentReport = getReport(String(moneyRequestReport.parentReportID)); - const parentReportAction = getParentReportAction(parentReportActions ?? {}, moneyRequestReport?.parentReportActionID); + const parentReportAction = ReportActionsUtils.getParentReportAction(moneyRequestReport); const isRequestIOU = parentReport?.type === 'iou'; const isRequestHoldCreator = isHoldCreator(transaction, moneyRequestReport?.reportID) && isRequestIOU; @@ -2770,7 +2761,7 @@ function canHoldUnholdReportAction(reportAction: OnyxEntry): {canH const isScanning = TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction); const canModifyStatus = !isTrackExpenseMoneyReport && (isPolicyAdmin || isActionOwner || isApprover); - const isDeletedParentAction = ReportActionsUtils.isDeletedAction(parentReportAction); + const isDeletedParentAction = isEmptyObject(parentReportAction) || ReportActionsUtils.isDeletedAction(parentReportAction); const canHoldOrUnholdRequest = !isRequestSettled && !isApproved && !isDeletedParentAction; const canHoldRequest = canHoldOrUnholdRequest && !isOnHold && (isRequestHoldCreator || (!isRequestIOU && canModifyStatus)) && !isScanning; @@ -7031,7 +7022,6 @@ export { getOriginalReportID, getOutstandingChildRequest, getParentNavigationSubtitle, - getParentReportAction, getParsedComment, getParticipantAccountIDs, getParticipants, From 721e40787bbfdaf9cbdc5f040edd04914f36ac4a Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Sat, 8 Jun 2024 14:19:36 +0530 Subject: [PATCH 141/230] fix lint issue. Signed-off-by: Krishna Gupta --- src/libs/ReportUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index fe732724ef3e..269f4c7bc01b 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2765,7 +2765,7 @@ function canHoldUnholdReportAction(reportAction: OnyxEntry): {canH const canHoldOrUnholdRequest = !isRequestSettled && !isApproved && !isDeletedParentAction; const canHoldRequest = canHoldOrUnholdRequest && !isOnHold && (isRequestHoldCreator || (!isRequestIOU && canModifyStatus)) && !isScanning; - const canUnholdRequest = Boolean(canHoldOrUnholdRequest && isOnHold && (isRequestHoldCreator || (!isRequestIOU && canModifyStatus))); + const canUnholdRequest = !!(canHoldOrUnholdRequest && isOnHold && (isRequestHoldCreator || (!isRequestIOU && canModifyStatus))); return {canHoldRequest, canUnholdRequest}; } From f302598a454996abbb4f1ff34ddb55b92aed52d0 Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Sun, 9 Jun 2024 16:38:57 +0200 Subject: [PATCH 142/230] Add missing js doc --- src/types/onyx/UserMetadata.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/types/onyx/UserMetadata.ts b/src/types/onyx/UserMetadata.ts index 14181b33c4b4..a8b34cb29401 100644 --- a/src/types/onyx/UserMetadata.ts +++ b/src/types/onyx/UserMetadata.ts @@ -11,6 +11,8 @@ type UserMetadata = { /** User's account ID */ accountID?: number; + + /** Type of environment the user is using (staging or production) */ environment?: string; }; From f1fc716d207902ce588b3d892a9ea8d22a2502af Mon Sep 17 00:00:00 2001 From: badeggg Date: Sun, 9 Jun 2024 23:22:32 +0800 Subject: [PATCH 143/230] try fix 42916 loop crash --- ios/Podfile.lock | 2 +- src/hooks/useAutoFocusInput.ts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index c138d1b27f61..f489ec290755 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -2606,7 +2606,7 @@ SPEC CHECKSUMS: SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 Turf: 13d1a92d969ca0311bbc26e8356cca178ce95da2 VisionCamera: 1394a316c7add37e619c48d7aa40b38b954bf055 - Yoga: 64cd2a583ead952b0315d5135bf39e053ae9be70 + Yoga: 1b901a6d6eeba4e8a2e8f308f708691cdb5db312 PODFILE CHECKSUM: 66a5c97ae1059e4da1993a4ad95abe5d819f555b diff --git a/src/hooks/useAutoFocusInput.ts b/src/hooks/useAutoFocusInput.ts index c1f58901cee0..2e2d9a086ab1 100644 --- a/src/hooks/useAutoFocusInput.ts +++ b/src/hooks/useAutoFocusInput.ts @@ -49,6 +49,9 @@ export default function useAutoFocusInput(): UseAutoFocusInput { const inputCallbackRef = (ref: TextInput | null) => { inputRef.current = ref; + if (isInputInitialized) { + return; + } setIsInputInitialized(true); }; From 21059739d373370f3b584cb2cbc4c8382b2dfd50 Mon Sep 17 00:00:00 2001 From: ShridharGoel <35566748+ShridharGoel@users.noreply.github.com> Date: Mon, 10 Jun 2024 01:26:10 +0530 Subject: [PATCH 144/230] Bump live-markdown to 0.1.83 --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index aaef29c6fad9..bd2bc7197320 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@babel/plugin-proposal-private-methods": "^7.18.6", "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@dotlottie/react-player": "^1.6.3", - "@expensify/react-native-live-markdown": "0.1.82", + "@expensify/react-native-live-markdown": "0.1.83", "@expo/metro-runtime": "~3.1.1", "@formatjs/intl-datetimeformat": "^6.10.0", "@formatjs/intl-listformat": "^7.2.2", @@ -3558,9 +3558,9 @@ } }, "node_modules/@expensify/react-native-live-markdown": { - "version": "0.1.82", - "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.82.tgz", - "integrity": "sha512-w/K2+0d1sAYvyLVpPv1ufDOTaj4y96Z362N3JDN+SDUmPQN2MvVGwsTL0ltzdw78yd62azFcQl6th7P6l62THQ==", + "version": "0.1.83", + "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.83.tgz", + "integrity": "sha512-xGn1P9FbFVueEF8BNKJJ4dQb0wPtsAvrrxND9pwVQT35ZL5cu1KZ4o6nzCqtesISPRB8Dw9Zx0ftIZy2uCQyzA==", "workspaces": [ "parser", "example", diff --git a/package.json b/package.json index ee1ad32a0067..e70c8c8e853e 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "@babel/plugin-proposal-private-methods": "^7.18.6", "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@dotlottie/react-player": "^1.6.3", - "@expensify/react-native-live-markdown": "0.1.82", + "@expensify/react-native-live-markdown": "0.1.83", "@expo/metro-runtime": "~3.1.1", "@formatjs/intl-datetimeformat": "^6.10.0", "@formatjs/intl-listformat": "^7.2.2", From 1e373b79a8af637110782a5d2b915c13221c379a Mon Sep 17 00:00:00 2001 From: Shridhar Goel <35566748+ShridharGoel@users.noreply.github.com> Date: Mon, 10 Jun 2024 01:31:49 +0530 Subject: [PATCH 145/230] Update podfile --- ios/Podfile.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index c138d1b27f61..aca46d6b18ed 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1852,7 +1852,7 @@ PODS: - RNGoogleSignin (10.0.1): - GoogleSignIn (~> 7.0) - React-Core - - RNLiveMarkdown (0.1.82): + - RNLiveMarkdown (0.1.83): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -1870,9 +1870,9 @@ PODS: - React-utils - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNLiveMarkdown/common (= 0.1.82) + - RNLiveMarkdown/common (= 0.1.83) - Yoga - - RNLiveMarkdown/common (0.1.82): + - RNLiveMarkdown/common (0.1.83): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -2589,7 +2589,7 @@ SPEC CHECKSUMS: RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 RNGestureHandler: 74b7b3d06d667ba0bbf41da7718f2607ae0dfe8f RNGoogleSignin: ccaa4a81582cf713eea562c5dd9dc1961a715fd0 - RNLiveMarkdown: d160a948e52282067439585c89a3962582c082ce + RNLiveMarkdown: 88030b7d9a31f5f6e67743df48ad952d64513b4a RNLocalize: d4b8af4e442d4bcca54e68fc687a2129b4d71a81 rnmapbox-maps: df8fe93dbd251f25022f4023d31bc04160d4d65c RNPermissions: 0b61d30d21acbeafe25baaa47d9bae40a0c65216 From f2b7460d0bde8fab0a191f02bd941da8fa6022ba Mon Sep 17 00:00:00 2001 From: OSBotify Date: Sun, 9 Jun 2024 21:03:44 +0000 Subject: [PATCH 146/230] Update version to 1.4.80-17 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- ios/NotificationServiceExtension/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 9343423c9f14..03328cb1e19f 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -107,8 +107,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001048016 - versionName "1.4.80-16" + versionCode 1001048017 + versionName "1.4.80-17" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index ad1d743d778d..17f238555fb9 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.80.16 + 1.4.80.17 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 23bdf74a3648..bab29a9f7bf2 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.4.80.16 + 1.4.80.17 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 9f8cc7b1612e..fa2b2c45f34b 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 1.4.80 CFBundleVersion - 1.4.80.16 + 1.4.80.17 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index bd2bc7197320..31f216228117 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.80-16", + "version": "1.4.80-17", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.80-16", + "version": "1.4.80-17", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index e70c8c8e853e..55202a6280ac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.80-16", + "version": "1.4.80-17", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From 063a481d7594b7bfaed9f5f9cda9082dc37b8ac0 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Mon, 10 Jun 2024 10:04:38 +0700 Subject: [PATCH 147/230] remove useless logic --- src/libs/NetworkConnection.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/NetworkConnection.ts b/src/libs/NetworkConnection.ts index c67edbf88488..b87505327e7d 100644 --- a/src/libs/NetworkConnection.ts +++ b/src/libs/NetworkConnection.ts @@ -84,7 +84,7 @@ Onyx.connect({ Log.info( `[NetworkStatus] The force-offline mode was turned off. Getting the device network status from NetInfo. Network state: ${JSON.stringify( state, - )}. Setting the offline status to: ${!!isInternetReachable}.`, + )}. Setting the offline status to: ${isInternetReachable}.`, ); }); } From 092c86a93c08fe4aef7e4a43350cc350cad36e61 Mon Sep 17 00:00:00 2001 From: badeggg Date: Mon, 10 Jun 2024 14:41:46 +0800 Subject: [PATCH 148/230] remote unnecessary change in ios/Podfile.lock --- ios/Podfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index f489ec290755..c138d1b27f61 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -2606,7 +2606,7 @@ SPEC CHECKSUMS: SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 Turf: 13d1a92d969ca0311bbc26e8356cca178ce95da2 VisionCamera: 1394a316c7add37e619c48d7aa40b38b954bf055 - Yoga: 1b901a6d6eeba4e8a2e8f308f708691cdb5db312 + Yoga: 64cd2a583ead952b0315d5135bf39e053ae9be70 PODFILE CHECKSUM: 66a5c97ae1059e4da1993a4ad95abe5d819f555b From 9d915406e6522f6b7d7dc2a832eb70dbcdd131af Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Mon, 10 Jun 2024 08:59:10 +0200 Subject: [PATCH 149/230] fix: added autoFocus to size input --- .../settings/Subscription/SubscriptionSize/substeps/Size.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pages/settings/Subscription/SubscriptionSize/substeps/Size.tsx b/src/pages/settings/Subscription/SubscriptionSize/substeps/Size.tsx index 15be8842b3bb..cdcc65197cbd 100644 --- a/src/pages/settings/Subscription/SubscriptionSize/substeps/Size.tsx +++ b/src/pages/settings/Subscription/SubscriptionSize/substeps/Size.tsx @@ -4,6 +4,7 @@ import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; +import useAutoFocusInput from '@hooks/useAutoFocusInput'; import useLocalize from '@hooks/useLocalize'; import type {SubStepProps} from '@hooks/useSubStep/types'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -17,6 +18,7 @@ type SizeProps = SubStepProps; function Size({onNext}: SizeProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); + const {inputCallbackRef} = useAutoFocusInput(); const defaultValues = { // TODO this is temporary and default value will be replaced in next phase once data in ONYX is ready @@ -35,6 +37,7 @@ function Size({onNext}: SizeProps) { {translate('subscription.subscriptionSize.yourSize')} Date: Mon, 10 Jun 2024 09:23:28 +0200 Subject: [PATCH 150/230] fix control benefits list --- src/languages/en.ts | 3 +-- src/languages/es.ts | 3 +-- src/pages/settings/Subscription/SubscriptionPlan.tsx | 1 - 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 9897b2d3a604..0cf35025c50c 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -3245,8 +3245,7 @@ export default { benefit3: 'Certinia and Workday sync', benefit4: 'Multiple expense approvers', benefit5: 'SAML/SSO', - benefit6: 'Custom insights and reporting', - benefit7: 'Budgeting', + benefit6: 'Budgeting', }, saveWithExpensifyTitle: 'Save with the Expensify Card', saveWithExpensifyDescription: 'Use our savings calculator to see how cash back from the Expensify Card can reduce your Expensify bill.', diff --git a/src/languages/es.ts b/src/languages/es.ts index fee7a356543e..1996c05867d4 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3752,8 +3752,7 @@ export default { benefit3: 'Sincronización de Certinia y Workday', benefit4: 'Varios aprobadores de gastos', benefit5: 'SAML/SSO', - benefit6: 'Reportes e informes personalizados', - benefit7: 'Presupuestos', + benefit6: 'Presupuestos', }, saveWithExpensifyTitle: 'Ahorra con la Tarjeta Expensify', saveWithExpensifyDescription: 'Utiliza nuestra calculadora de ahorro para ver cómo el reembolso en efectivo de la Tarjeta Expensify puede reducir tu factura de Expensify', diff --git a/src/pages/settings/Subscription/SubscriptionPlan.tsx b/src/pages/settings/Subscription/SubscriptionPlan.tsx index 4d42390ad581..33933027dd45 100644 --- a/src/pages/settings/Subscription/SubscriptionPlan.tsx +++ b/src/pages/settings/Subscription/SubscriptionPlan.tsx @@ -49,7 +49,6 @@ function SubscriptionPlan() { translate('subscription.yourPlan.control.benefit4'), translate('subscription.yourPlan.control.benefit5'), translate('subscription.yourPlan.control.benefit6'), - translate('subscription.yourPlan.control.benefit7'), ]; return ( From 1f77593f61f8c4976aab343efff01697e55f7f75 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Mon, 10 Jun 2024 09:29:30 +0200 Subject: [PATCH 151/230] lint code --- src/types/onyx/Fund.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/onyx/Fund.ts b/src/types/onyx/Fund.ts index 1b04a567562f..7e29334aeca0 100644 --- a/src/types/onyx/Fund.ts +++ b/src/types/onyx/Fund.ts @@ -32,7 +32,7 @@ type AccountData = { /** Debit card creation date */ created?: string; - + /** Debit card currency */ currency?: ValueOf; From 902b5605399eb1c0494bf4fd95b012018cb3c277 Mon Sep 17 00:00:00 2001 From: Tienifr <113963320+tienifr@users.noreply.github.com> Date: Mon, 10 Jun 2024 14:32:44 +0700 Subject: [PATCH 152/230] fix add comment to TEXTBOX Co-authored-by: Shubham Agrawal <58412969+shubham1206agra@users.noreply.github.com> --- src/CONST.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/CONST.ts b/src/CONST.ts index 02de81e61aa4..6a2b9a72279f 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -3387,6 +3387,9 @@ const CONST = { */ IMAGE: 'image', + /** + * @deprecated Please stop using the accessibilityRole prop and use the role prop instead. + */ TEXTBOX: 'textbox', }, /** From b5693a6a44c6ab33aedb62e2009c6f24e7eec53a Mon Sep 17 00:00:00 2001 From: OSBotify Date: Mon, 10 Jun 2024 07:45:31 +0000 Subject: [PATCH 153/230] Update version to 1.4.81-0 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 4 ++-- ios/NewExpensifyTests/Info.plist | 4 ++-- ios/NotificationServiceExtension/Info.plist | 4 ++-- package-lock.json | 4 ++-- package.json | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 03328cb1e19f..386c96587c7b 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -107,8 +107,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001048017 - versionName "1.4.80-17" + versionCode 1001048100 + versionName "1.4.81-0" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 17f238555fb9..03e11f677fbd 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.4.80 + 1.4.81 CFBundleSignature ???? CFBundleURLTypes @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.80.17 + 1.4.81.0 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index bab29a9f7bf2..661f10f6defc 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.4.80 + 1.4.81 CFBundleSignature ???? CFBundleVersion - 1.4.80.17 + 1.4.81.0 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index fa2b2c45f34b..f1d3734a4f94 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -11,9 +11,9 @@ CFBundleName $(PRODUCT_NAME) CFBundleShortVersionString - 1.4.80 + 1.4.81 CFBundleVersion - 1.4.80.17 + 1.4.81.0 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index 31f216228117..972ab4d261fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.80-17", + "version": "1.4.81-0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.80-17", + "version": "1.4.81-0", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 55202a6280ac..811342a5d265 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.80-17", + "version": "1.4.81-0", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From 3a7cfe61c6f86a0a9b532f53e8fc8637eca2489c Mon Sep 17 00:00:00 2001 From: OSBotify Date: Mon, 10 Jun 2024 08:03:49 +0000 Subject: [PATCH 154/230] Update version to 1.4.81-1 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- ios/NotificationServiceExtension/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 386c96587c7b..cc26412f0c0f 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -107,8 +107,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001048100 - versionName "1.4.81-0" + versionCode 1001048101 + versionName "1.4.81-1" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 03e11f677fbd..71fb74c7dc8a 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.81.0 + 1.4.81.1 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 661f10f6defc..6037678d5b9b 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.4.81.0 + 1.4.81.1 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index f1d3734a4f94..8bbe7f95850c 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 1.4.81 CFBundleVersion - 1.4.81.0 + 1.4.81.1 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index 972ab4d261fd..79f4e6053de1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.81-0", + "version": "1.4.81-1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.81-0", + "version": "1.4.81-1", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 811342a5d265..fe69f4594efd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.81-0", + "version": "1.4.81-1", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From 77548f3f2963b3a62b217953c7ebc6e82f227fde Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Mon, 10 Jun 2024 10:05:11 +0200 Subject: [PATCH 155/230] fix: display FullPageNotFoundView on mobile for subscription size page --- .../ModalStackNavigators/index.tsx | 2 +- .../Subscription/SubscriptionSettingsPage.tsx | 1 + .../SubscriptionSize/index.native.tsx | 19 +++++++++++++++++++ .../{SubscriptionSizePage.tsx => index.tsx} | 0 4 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 src/pages/settings/Subscription/SubscriptionSize/index.native.tsx rename src/pages/settings/Subscription/SubscriptionSize/{SubscriptionSizePage.tsx => index.tsx} (100%) diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 807c938e21dd..dea1fede3fe1 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -217,7 +217,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/settings/Profile/CustomStatus/StatusClearAfterPage').default as React.ComponentType, [SCREENS.SETTINGS.PROFILE.STATUS_CLEAR_AFTER_DATE]: () => require('../../../../pages/settings/Profile/CustomStatus/SetDatePage').default as React.ComponentType, [SCREENS.SETTINGS.PROFILE.STATUS_CLEAR_AFTER_TIME]: () => require('../../../../pages/settings/Profile/CustomStatus/SetTimePage').default as React.ComponentType, - [SCREENS.SETTINGS.SUBSCRIPTION.SIZE]: () => require('../../../../pages/settings/Subscription/SubscriptionSize/SubscriptionSizePage').default as React.ComponentType, + [SCREENS.SETTINGS.SUBSCRIPTION.SIZE]: () => require('../../../../pages/settings/Subscription/SubscriptionSize').default as React.ComponentType, [SCREENS.SETTINGS.SUBSCRIPTION.DISABLE_AUTO_RENEW_SURVEY]: () => require('../../../../pages/settings/Subscription/DisableAutoRenewSurveyPage').default as React.ComponentType, [SCREENS.WORKSPACE.RATE_AND_UNIT]: () => require('../../../../pages/workspace/reimburse/WorkspaceRateAndUnitPage/InitialPage').default as React.ComponentType, [SCREENS.WORKSPACE.RATE_AND_UNIT_RATE]: () => require('../../../../pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage').default as React.ComponentType, diff --git a/src/pages/settings/Subscription/SubscriptionSettingsPage.tsx b/src/pages/settings/Subscription/SubscriptionSettingsPage.tsx index c0a280f35e99..38825403b86b 100644 --- a/src/pages/settings/Subscription/SubscriptionSettingsPage.tsx +++ b/src/pages/settings/Subscription/SubscriptionSettingsPage.tsx @@ -12,6 +12,7 @@ import useWindowDimensions from '@hooks/useWindowDimensions'; import Navigation from '@libs/Navigation/Navigation'; import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; import * as Subscription from '@userActions/Subscription'; +import ROUTES from '@src/ROUTES'; import CardSection from './CardSection/CardSection'; import ReducedFunctionalityMessage from './ReducedFunctionalityMessage'; import SubscriptionDetails from './SubscriptionDetails'; diff --git a/src/pages/settings/Subscription/SubscriptionSize/index.native.tsx b/src/pages/settings/Subscription/SubscriptionSize/index.native.tsx new file mode 100644 index 000000000000..3afd51298551 --- /dev/null +++ b/src/pages/settings/Subscription/SubscriptionSize/index.native.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; +import ScreenWrapper from '@components/ScreenWrapper'; + +function SubscriptionSizePage() { + return ( + + + + ); +} + +SubscriptionSizePage.displayName = 'SubscriptionSizePage'; + +export default SubscriptionSizePage; diff --git a/src/pages/settings/Subscription/SubscriptionSize/SubscriptionSizePage.tsx b/src/pages/settings/Subscription/SubscriptionSize/index.tsx similarity index 100% rename from src/pages/settings/Subscription/SubscriptionSize/SubscriptionSizePage.tsx rename to src/pages/settings/Subscription/SubscriptionSize/index.tsx From e921c9216a935cdc5abe669df1f1aae893ed25de Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Mon, 10 Jun 2024 10:24:06 +0200 Subject: [PATCH 156/230] fix: show Persona chat in LHN --- src/libs/ReportUtils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index b63087763d32..ba9559a5b435 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -5275,7 +5275,6 @@ function shouldReportBeInOptionList({ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing report?.isHidden || // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - participantAccountIDs.includes(CONST.ACCOUNT_ID.NOTIFICATIONS) || (participantAccountIDs.length === 0 && !isChatThread(report) && !isPublicRoom(report) && From eab9f0b0870a41e077e012dd0087eb10fdb5a0f5 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Mon, 10 Jun 2024 10:24:40 +0200 Subject: [PATCH 157/230] fix: remove unnecessary comment --- src/libs/ReportUtils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index ba9559a5b435..eb4dfaf3d558 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -5274,7 +5274,6 @@ function shouldReportBeInOptionList({ report?.reportName === undefined || // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing report?.isHidden || - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing (participantAccountIDs.length === 0 && !isChatThread(report) && !isPublicRoom(report) && From 99b739832f7093e29e93fd5c90b48ab1eeb0a3cf Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Mon, 10 Jun 2024 10:29:36 +0200 Subject: [PATCH 158/230] fix: show only Expensify name for persona chat --- src/components/LHNOptionsList/OptionRowLHN.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index 851e2a9fdd16..1aa3fe47212d 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -221,7 +221,8 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti !!optionItem.isThread || !!optionItem.isMoneyRequestReport || !!optionItem.isInvoiceReport || - ReportUtils.isGroupChat(report) + ReportUtils.isGroupChat(report) || + ReportUtils.isSystemChat(report) } /> {isStatusVisible && ( From d11ce00bb3af98b097635d993ec94e33a6bfb649 Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Mon, 10 Jun 2024 10:39:05 +0200 Subject: [PATCH 159/230] fix: linter --- src/pages/settings/Subscription/SubscriptionSettingsPage.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/settings/Subscription/SubscriptionSettingsPage.tsx b/src/pages/settings/Subscription/SubscriptionSettingsPage.tsx index 38825403b86b..c0a280f35e99 100644 --- a/src/pages/settings/Subscription/SubscriptionSettingsPage.tsx +++ b/src/pages/settings/Subscription/SubscriptionSettingsPage.tsx @@ -12,7 +12,6 @@ import useWindowDimensions from '@hooks/useWindowDimensions'; import Navigation from '@libs/Navigation/Navigation'; import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; import * as Subscription from '@userActions/Subscription'; -import ROUTES from '@src/ROUTES'; import CardSection from './CardSection/CardSection'; import ReducedFunctionalityMessage from './ReducedFunctionalityMessage'; import SubscriptionDetails from './SubscriptionDetails'; From 94f0a013cba1acb5c72407e2ef01ba523d951745 Mon Sep 17 00:00:00 2001 From: Yauheni Pasiukevich Date: Mon, 10 Jun 2024 11:26:16 +0200 Subject: [PATCH 160/230] remove specific style --- .../settings/Subscription/CardSection/BillingBanner.tsx | 2 +- src/styles/index.ts | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/pages/settings/Subscription/CardSection/BillingBanner.tsx b/src/pages/settings/Subscription/CardSection/BillingBanner.tsx index 3bf183a2a3d8..61ee2bc9552c 100644 --- a/src/pages/settings/Subscription/CardSection/BillingBanner.tsx +++ b/src/pages/settings/Subscription/CardSection/BillingBanner.tsx @@ -27,7 +27,7 @@ function BillingBanner({title, subtitle, isError, shouldShowRedDotIndicator}: Bi /> {title} - {subtitle} + {subtitle} {isError && shouldShowRedDotIndicator && ( fontWeight: FontUtils.fontWeight.bold, }, + textLineHeightXLarge: { + lineHeight: variables.lineHeightXLarge, + }, + fontWeightNormal: { fontWeight: FontUtils.fontWeight.normal, }, @@ -2848,11 +2852,6 @@ const styles = (theme: ThemeColors) => width: variables.iconSizeExtraLarge, }, - billingBannerSubtitle: { - color: theme.textSupporting, - lineHeight: variables.lineHeightXLarge, - }, - selectCircle: { width: variables.componentSizeSmall, height: variables.componentSizeSmall, From d9485372fce8c1864ccb44155d4760315794346d Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Mon, 10 Jun 2024 11:40:54 +0200 Subject: [PATCH 161/230] merging into main --- .../BottomTabBar/index.website.tsx | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.website.tsx b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.website.tsx index d989f3747f6b..ed4ddc4c7d92 100644 --- a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.website.tsx +++ b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.website.tsx @@ -47,7 +47,7 @@ function BottomTabBar({isLoadingApp = false}: PurposeForUsingExpensifyModalProps const currentRoute = routes?.[navigationState?.index ?? 0]; // When we are redirected to the Settings tab from the OldDot, we don't want to call the Welcome.show() method. // To prevent this, the value of the bottomTabRoute?.name is checked here - if (Boolean(currentRoute && currentRoute.name !== NAVIGATORS.BOTTOM_TAB_NAVIGATOR && currentRoute.name !== NAVIGATORS.CENTRAL_PANE_NAVIGATOR) || Session.isAnonymousUser()) { + if (!!(currentRoute && currentRoute.name !== NAVIGATORS.BOTTOM_TAB_NAVIGATOR && currentRoute.name !== NAVIGATORS.CENTRAL_PANE_NAVIGATOR) || Session.isAnonymousUser()) { return; } @@ -76,17 +76,17 @@ function BottomTabBar({isLoadingApp = false}: PurposeForUsingExpensifyModalProps return ( - + - + + { + Navigation.navigate(ROUTES.SEARCH.getRoute(CONST.TAB_SEARCH.ALL)); + }} + role={CONST.ROLE.BUTTON} + accessibilityLabel={translate('common.search')} + wrapperStyle={styles.flex1} + style={styles.bottomTabBarItem} + > + + + + + + + + ); } From f0e23c9a8305d55afdd9ffb4ccace2c90946066a Mon Sep 17 00:00:00 2001 From: Yauheni Pasiukevich Date: Mon, 10 Jun 2024 11:44:25 +0200 Subject: [PATCH 162/230] remove specific lineHeight --- src/pages/settings/Subscription/CardSection/BillingBanner.tsx | 2 +- src/styles/index.ts | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/pages/settings/Subscription/CardSection/BillingBanner.tsx b/src/pages/settings/Subscription/CardSection/BillingBanner.tsx index 61ee2bc9552c..1ae20f177431 100644 --- a/src/pages/settings/Subscription/CardSection/BillingBanner.tsx +++ b/src/pages/settings/Subscription/CardSection/BillingBanner.tsx @@ -27,7 +27,7 @@ function BillingBanner({title, subtitle, isError, shouldShowRedDotIndicator}: Bi /> {title} - {subtitle} + {subtitle} {isError && shouldShowRedDotIndicator && ( fontWeight: FontUtils.fontWeight.bold, }, - textLineHeightXLarge: { - lineHeight: variables.lineHeightXLarge, - }, - fontWeightNormal: { fontWeight: FontUtils.fontWeight.normal, }, From 40d9c24d70fedc02e718923def345e57b0c624a1 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Mon, 10 Jun 2024 11:48:30 +0200 Subject: [PATCH 163/230] minor change --- .../BottomTabBar/index.website.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.website.tsx b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.website.tsx index ed4ddc4c7d92..ad42ef31e1e9 100644 --- a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.website.tsx +++ b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.website.tsx @@ -39,7 +39,7 @@ function BottomTabBar({isLoadingApp = false}: PurposeForUsingExpensifyModalProps const {translate} = useLocalize(); const navigation = useNavigation(); const {activeWorkspaceID: contextActiveWorkspaceID} = useActiveWorkspace(); - const activeWorkspaceID = sessionStorage ? sessionStorage.getItem(CONST.SESSION_STORAGE_KEYS.ACTIVE_WORKSPACE_ID) : contextActiveWorkspaceID; + const activeWorkspaceID = sessionStorage.getItem(CONST.SESSION_STORAGE_KEYS.ACTIVE_WORKSPACE_ID) ?? contextActiveWorkspaceID; useEffect(() => { const navigationState = navigation.getState() as State | undefined; From 57d77e3b7ed0bc9dcc546f5080da12c8fcdb8eea Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Mon, 10 Jun 2024 11:56:49 +0200 Subject: [PATCH 164/230] eslint fix --- .../BottomTabBar/index.website.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.website.tsx b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.website.tsx index ad42ef31e1e9..bc813f70f081 100644 --- a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.website.tsx +++ b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.website.tsx @@ -67,7 +67,7 @@ function BottomTabBar({isLoadingApp = false}: PurposeForUsingExpensifyModalProps return topmostBottomTabRoute?.name ?? SCREENS.HOME; }); - const chatTabBrickRoad = getChatTabBrickRoad(activeWorkspaceID as string | undefined); + const chatTabBrickRoad = getChatTabBrickRoad(activeWorkspaceID); const navigateToChats = useCallback(() => { const route = activeWorkspaceID ? (`/w/${activeWorkspaceID}/home` as Route) : ROUTES.HOME; From d584ab3d5f81a247304e03649e58efc1d7f2d47d Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Mon, 10 Jun 2024 13:17:14 +0200 Subject: [PATCH 165/230] use preferredLocale via useLocalize --- src/components/LHNOptionsList/LHNOptionsList.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index ea4bdb004667..b719f667736c 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -35,7 +35,6 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT); const [reportActions] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS); const [policy] = useOnyx(ONYXKEYS.COLLECTION.POLICY); - const [preferredLocale] = useOnyx(ONYXKEYS.NVP_PREFERRED_LOCALE, {initialValue: CONST.LOCALES.DEFAULT}); const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); const [transactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION); const [draftComments] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT); @@ -44,7 +43,7 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio const theme = useTheme(); const styles = useThemeStyles(); const {canUseViolations} = usePermissions(); - const {translate} = useLocalize(); + const {translate, preferredLocale} = useLocalize(); const {shouldUseNarrowLayout} = useResponsiveLayout(); const shouldShowEmptyLHN = shouldUseNarrowLayout && data.length === 0; From 300a14cb3ea671da16302fa8b2a0f63f4f7e37b4 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Mon, 10 Jun 2024 13:24:51 +0200 Subject: [PATCH 166/230] use legacy arguments --- tests/unit/SidebarOrderTest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/SidebarOrderTest.ts b/tests/unit/SidebarOrderTest.ts index 8a1a47cd75cf..98120a53b6d4 100644 --- a/tests/unit/SidebarOrderTest.ts +++ b/tests/unit/SidebarOrderTest.ts @@ -66,7 +66,7 @@ describe('Sidebar', () => { it('is rendered with an empty list when personal details exist', () => waitForBatchedUpdates() // Given the sidebar is rendered with default props - .then(() => LHNTestUtils.getDefaultRenderedSidebarLinks('0')) + .then(() => LHNTestUtils.getDefaultRenderedSidebarLinks()) // When Onyx is updated with some personal details .then(() => From 9be952ebc413e11b694cd07c052e0f3816e71182 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Mon, 10 Jun 2024 19:50:22 +0800 Subject: [PATCH 167/230] fix flashlist doesn't rerender correctly when chaning priority mode --- src/components/LHNOptionsList/LHNOptionsList.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 6405e3026b1a..7a2650ab5bd6 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -165,8 +165,8 @@ function LHNOptionsList({ ); const extraData = useMemo( - () => [reportActions, reports, policy, personalDetails, data.length, draftComments], - [reportActions, reports, policy, personalDetails, data.length, draftComments], + () => [reportActions, reports, policy, personalDetails, data.length, draftComments, optionMode], + [reportActions, reports, policy, personalDetails, data.length, draftComments, optionMode], ); const previousOptionMode = usePrevious(optionMode); From 50880d2121347f0638eba1c743b85c29708637ee Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Mon, 10 Jun 2024 14:38:52 +0200 Subject: [PATCH 168/230] add minimumFractionDigits to convertToShortDisplayString util function --- src/libs/CurrencyUtils.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libs/CurrencyUtils.ts b/src/libs/CurrencyUtils.ts index 69c95cefc82a..7b54fbf0bed7 100644 --- a/src/libs/CurrencyUtils.ts +++ b/src/libs/CurrencyUtils.ts @@ -138,11 +138,13 @@ function convertToDisplayString(amountInCents = 0, currency: string = CONST.CURR */ function convertToShortDisplayString(amountInCents = 0, currency: string = CONST.CURRENCY.USD): string { const convertedAmount = convertToFrontendAmountAsInteger(amountInCents); + return NumberFormatUtils.format(BaseLocaleListener.getPreferredLocale(), convertedAmount, { style: 'currency', currency, // There will be no decimals displayed (e.g. $9) + minimumFractionDigits: 0, maximumFractionDigits: 0, }); } From 02d3efd66d200533660d3a755bd8e1d9bf0c5bb8 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Thu, 6 Jun 2024 16:08:36 +0200 Subject: [PATCH 169/230] improve WorkspaceSwitcherButton --- src/components/WorkspaceSwitcherButton.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/WorkspaceSwitcherButton.tsx b/src/components/WorkspaceSwitcherButton.tsx index 7f7715f6f827..f12b60ec9228 100644 --- a/src/components/WorkspaceSwitcherButton.tsx +++ b/src/components/WorkspaceSwitcherButton.tsx @@ -1,4 +1,4 @@ -import React, {useMemo, useRef} from 'react'; +import React, {memo, useMemo, useRef} from 'react'; import type {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; @@ -26,7 +26,7 @@ function WorkspaceSwitcherButton({policy}: WorkspaceSwitcherButtonProps) { const pressableRef = useRef(null); - const {source, name, type, id} = useMemo(() => { + const mainAvatar = useMemo(() => { if (!policy) { return {source: Expensicons.ExpensifyAppIcon, name: CONST.WORKSPACE_SWITCHER.NAME, type: CONST.ICON_TYPE_AVATAR}; } @@ -56,7 +56,7 @@ function WorkspaceSwitcherButton({policy}: WorkspaceSwitcherButtonProps) { > {({hovered}) => ( Date: Mon, 10 Jun 2024 20:08:23 +0700 Subject: [PATCH 170/230] remove language translation --- src/languages/en.ts | 1 - src/languages/es.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 468f06f6b702..4382c05c48e9 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -688,7 +688,6 @@ export default { finished: 'Finished', sendInvoice: ({amount}: RequestAmountParams) => `Send ${amount} invoice`, submitAmount: ({amount}: RequestAmountParams) => `submit ${amount}`, - trackAmount: ({amount}: RequestAmountParams) => `track ${amount}`, submittedAmount: ({formattedAmount, comment}: RequestedAmountMessageParams) => `submitted ${formattedAmount}${comment ? ` for ${comment}` : ''}`, trackedAmount: ({formattedAmount, comment}: RequestedAmountMessageParams) => `tracking ${formattedAmount}${comment ? ` for ${comment}` : ''}`, splitAmount: ({amount}: SplitAmountParams) => `split ${amount}`, diff --git a/src/languages/es.ts b/src/languages/es.ts index 2d4500b823b7..53a431792ab8 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -682,7 +682,6 @@ export default { finished: 'Finalizado', sendInvoice: ({amount}: RequestAmountParams) => `Enviar factura de ${amount}`, submitAmount: ({amount}: RequestAmountParams) => `solicitar ${amount}`, - trackAmount: ({amount}: RequestAmountParams) => `seguimiento de ${amount}`, submittedAmount: ({formattedAmount, comment}: RequestedAmountMessageParams) => `solicitó ${formattedAmount}${comment ? ` para ${comment}` : ''}`, trackedAmount: ({formattedAmount, comment}: RequestedAmountMessageParams) => `realizó un seguimiento de ${formattedAmount}${comment ? ` para ${comment}` : ''}`, splitAmount: ({amount}: SplitAmountParams) => `dividir ${amount}`, From f30c194cdab3a360262c4344471949ba7c6733dd Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Mon, 10 Jun 2024 16:11:57 +0200 Subject: [PATCH 171/230] add themed illustrations to subscription details section --- .../Subscription/SubscriptionDetails/index.native.tsx | 5 ++++- .../settings/Subscription/SubscriptionDetails/index.tsx | 5 +++-- src/styles/theme/illustrations/themes/dark.ts | 2 ++ src/styles/theme/illustrations/themes/light.ts | 2 ++ src/styles/theme/illustrations/types.ts | 3 +++ 5 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/pages/settings/Subscription/SubscriptionDetails/index.native.tsx b/src/pages/settings/Subscription/SubscriptionDetails/index.native.tsx index 0eed6fd99ea5..8a529a9bd739 100644 --- a/src/pages/settings/Subscription/SubscriptionDetails/index.native.tsx +++ b/src/pages/settings/Subscription/SubscriptionDetails/index.native.tsx @@ -8,6 +8,7 @@ import OptionItem from '@components/OptionsPicker/OptionItem'; import Section from '@components/Section'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; +import useThemeIllustrations from '@hooks/useThemeIllustrations'; import useThemeStyles from '@hooks/useThemeStyles'; import variables from '@styles/variables'; import CONST from '@src/CONST'; @@ -16,6 +17,8 @@ import ONYXKEYS from '@src/ONYXKEYS'; function SubscriptionDetails() { const {translate} = useLocalize(); const styles = useThemeStyles(); + const illustrations = useThemeIllustrations(); + const [privateSubscription] = useOnyx(ONYXKEYS.NVP_PRIVATE_SUBSCRIPTION); const [account] = useOnyx(ONYXKEYS.ACCOUNT); @@ -48,7 +51,7 @@ function SubscriptionDetails() { {!!account?.isApprovedAccountant || !!account?.isApprovedAccountantClient ? ( diff --git a/src/pages/settings/Subscription/SubscriptionDetails/index.tsx b/src/pages/settings/Subscription/SubscriptionDetails/index.tsx index 350d84d00a46..80af95a64a8c 100644 --- a/src/pages/settings/Subscription/SubscriptionDetails/index.tsx +++ b/src/pages/settings/Subscription/SubscriptionDetails/index.tsx @@ -10,6 +10,7 @@ import OptionsPicker from '@components/OptionsPicker'; import Section from '@components/Section'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; +import useThemeIllustrations from '@hooks/useThemeIllustrations'; import useThemeStyles from '@hooks/useThemeStyles'; import variables from '@styles/variables'; import CONST from '@src/CONST'; @@ -33,10 +34,10 @@ const options: Array> = [ function SubscriptionDetails() { const {translate} = useLocalize(); const styles = useThemeStyles(); + const illustrations = useThemeIllustrations(); const [privateSubscription] = useOnyx(ONYXKEYS.NVP_PRIVATE_SUBSCRIPTION); const [account] = useOnyx(ONYXKEYS.ACCOUNT); - const [preferredTheme] = useOnyx(ONYXKEYS.PREFERRED_THEME); const [selectedOption, setSelectedOption] = useState(privateSubscription?.type ?? CONST.SUBSCRIPTION.TYPE.ANNUAL); @@ -81,7 +82,7 @@ function SubscriptionDetails() { {!!account?.isApprovedAccountant || !!account?.isApprovedAccountantClient ? ( diff --git a/src/styles/theme/illustrations/themes/dark.ts b/src/styles/theme/illustrations/themes/dark.ts index a46900510652..64d121448863 100644 --- a/src/styles/theme/illustrations/themes/dark.ts +++ b/src/styles/theme/illustrations/themes/dark.ts @@ -1,3 +1,4 @@ +import ExpensifyApprovedLogo from '@assets/images/subscription-details__approvedlogo.svg'; import EmptyStateBackgroundImage from '@assets/images/themeDependent/empty-state_background-fade-dark.png'; import ExampleCheckEN from '@assets/images/themeDependent/example-check-image-dark-en.png'; import ExampleCheckES from '@assets/images/themeDependent/example-check-image-dark-es.png'; @@ -9,6 +10,7 @@ const illustrations = { ExampleCheckEN, ExampleCheckES, WorkspaceProfile, + ExpensifyApprovedLogo, } satisfies IllustrationsType; export default illustrations; diff --git a/src/styles/theme/illustrations/themes/light.ts b/src/styles/theme/illustrations/themes/light.ts index 3406e219e656..a4e5b45ef99c 100644 --- a/src/styles/theme/illustrations/themes/light.ts +++ b/src/styles/theme/illustrations/themes/light.ts @@ -1,3 +1,4 @@ +import ExpensifyApprovedLogo from '@assets/images/subscription-details__approvedlogo--light.svg'; import EmptyStateBackgroundImage from '@assets/images/themeDependent/empty-state_background-fade-light.png'; import ExampleCheckEN from '@assets/images/themeDependent/example-check-image-light-en.png'; import ExampleCheckES from '@assets/images/themeDependent/example-check-image-light-es.png'; @@ -9,6 +10,7 @@ const illustrations = { ExampleCheckEN, ExampleCheckES, WorkspaceProfile, + ExpensifyApprovedLogo, } satisfies IllustrationsType; export default illustrations; diff --git a/src/styles/theme/illustrations/types.ts b/src/styles/theme/illustrations/types.ts index 86f2aa813118..04faeb43e9ed 100644 --- a/src/styles/theme/illustrations/types.ts +++ b/src/styles/theme/illustrations/types.ts @@ -1,10 +1,13 @@ +import type {FC} from 'react'; import type {ImageSourcePropType} from 'react-native'; +import type {SvgProps} from 'react-native-svg'; type IllustrationsType = { EmptyStateBackgroundImage: ImageSourcePropType; ExampleCheckES: ImageSourcePropType; ExampleCheckEN: ImageSourcePropType; WorkspaceProfile: ImageSourcePropType; + ExpensifyApprovedLogo: FC; }; export default IllustrationsType; From c8ae97bd6dc2d7d61db41c8e28d9dadf29609c1c Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Mon, 10 Jun 2024 16:52:51 +0200 Subject: [PATCH 172/230] add proper defualt props --- src/pages/Search/SearchPageBottomTab.tsx | 33 +++++++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/src/pages/Search/SearchPageBottomTab.tsx b/src/pages/Search/SearchPageBottomTab.tsx index c201047033e6..663a01ba02b8 100644 --- a/src/pages/Search/SearchPageBottomTab.tsx +++ b/src/pages/Search/SearchPageBottomTab.tsx @@ -1,4 +1,5 @@ -import React from 'react'; +import type {StackScreenProps} from '@react-navigation/stack'; +import React, {useMemo} from 'react'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import ScreenWrapper from '@components/ScreenWrapper'; import Search from '@components/Search'; @@ -7,20 +8,42 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import Navigation from '@libs/Navigation/Navigation'; +import type {CentralPaneNavigatorParamList} from '@libs/Navigation/types'; import TopBar from '@navigation/AppNavigator/createCustomBottomTabNavigator/TopBar'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; +import SCREENS from '@src/SCREENS'; import type {SearchQuery} from '@src/types/onyx/SearchResults'; import SearchFilters from './SearchFilters'; +type SearchPageProps = StackScreenProps; + +const defaultSearchProps = { + query: '' as SearchQuery, + policyIDs: undefined, + sortBy: CONST.SEARCH_TABLE_COLUMNS.DATE, + sortOrder: CONST.SORT_ORDER.DESC, +}; function SearchPageBottomTab() { const {translate} = useLocalize(); const {isSmallScreenWidth} = useWindowDimensions(); const activeRoute = useActiveRoute(); const styles = useThemeStyles(); - const currentQuery = activeRoute?.params && 'query' in activeRoute.params ? activeRoute?.params?.query : ''; - const policyIDs = activeRoute?.params && 'policyIDs' in activeRoute.params ? (activeRoute?.params?.policyIDs as string) : undefined; - const query = currentQuery as SearchQuery; + + const { + query: rawQuery, + policyIDs, + sortBy, + sortOrder, + } = useMemo(() => { + if (activeRoute?.name !== SCREENS.SEARCH.CENTRAL_PANE || !activeRoute.params) { + return defaultSearchProps; + } + return {...defaultSearchProps, ...activeRoute.params} as SearchPageProps['route']['params']; + }, [activeRoute]); + + const query = rawQuery as SearchQuery; + const isValidQuery = Object.values(CONST.TAB_SEARCH).includes(query); const handleOnBackButtonPress = () => Navigation.goBack(ROUTES.SEARCH.getRoute(CONST.TAB_SEARCH.ALL)); @@ -45,6 +68,8 @@ function SearchPageBottomTab() { )} From 34f0aa93233ec0db5e0e5ff9de1dd8991859ed93 Mon Sep 17 00:00:00 2001 From: Puneet Lath Date: Mon, 10 Jun 2024 11:00:22 -0400 Subject: [PATCH 173/230] Remove redundant enabled flag --- src/libs/actions/Policy/Policy.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index fdc2df185537..0e168f973078 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -1409,7 +1409,6 @@ function buildOptimisticCustomUnits(): OptimisticCustomUnits { rate: CONST.CUSTOM_UNITS.MILEAGE_IRS_RATE * CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET, enabled: true, currency, - enabled: true, }, }, }, From dfcde582fc798d057fb4767ee3de9b3df373f9ce Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Mon, 10 Jun 2024 23:27:47 +0800 Subject: [PATCH 174/230] get the cleaned tag name to display --- .../SelectionList/Search/TransactionListItemRow.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/SelectionList/Search/TransactionListItemRow.tsx b/src/components/SelectionList/Search/TransactionListItemRow.tsx index 50a34be86f61..c23ae815f472 100644 --- a/src/components/SelectionList/Search/TransactionListItemRow.tsx +++ b/src/components/SelectionList/Search/TransactionListItemRow.tsx @@ -181,14 +181,14 @@ function TagCell({isLargeScreenWidth, showTooltip, transactionItem}: Transaction return isLargeScreenWidth ? ( ) : ( ); } From 01d001f8683da9f505205304af1a550e4f57c8eb Mon Sep 17 00:00:00 2001 From: Mateusz Titz Date: Mon, 10 Jun 2024 17:28:30 +0200 Subject: [PATCH 175/230] Prevent navigating to Search when user is anonymous --- .../createCustomBottomTabNavigator/BottomTabBar.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx index 25bec235ed45..29b46df54e23 100644 --- a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx +++ b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx @@ -12,6 +12,7 @@ import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import * as Session from '@libs/actions/Session'; +import interceptAnonymousUser from '@libs/interceptAnonymousUser'; import getTopmostBottomTabRoute from '@libs/Navigation/getTopmostBottomTabRoute'; import getTopmostCentralPaneRoute from '@libs/Navigation/getTopmostCentralPaneRoute'; import Navigation from '@libs/Navigation/Navigation'; @@ -96,7 +97,7 @@ function BottomTabBar({isLoadingApp = false}: PurposeForUsingExpensifyModalProps { - Navigation.navigate(ROUTES.SEARCH.getRoute(CONST.TAB_SEARCH.ALL)); + interceptAnonymousUser(() => Navigation.navigate(ROUTES.SEARCH.getRoute(CONST.TAB_SEARCH.ALL))); }} role={CONST.ROLE.BUTTON} accessibilityLabel={translate('common.search')} From 169109ef9f69200d71b83cb4e05ee5b7cec2d91c Mon Sep 17 00:00:00 2001 From: OSBotify Date: Mon, 10 Jun 2024 15:51:36 +0000 Subject: [PATCH 176/230] Update version to 1.4.81-2 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- ios/NotificationServiceExtension/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index cc26412f0c0f..76ed848c95de 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -107,8 +107,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001048101 - versionName "1.4.81-1" + versionCode 1001048102 + versionName "1.4.81-2" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 71fb74c7dc8a..fcf3fd78d476 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.81.1 + 1.4.81.2 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 6037678d5b9b..1f6276364287 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.4.81.1 + 1.4.81.2 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 8bbe7f95850c..944a919d7ed7 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 1.4.81 CFBundleVersion - 1.4.81.1 + 1.4.81.2 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index 79f4e6053de1..2ff0a013f064 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.81-1", + "version": "1.4.81-2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.81-1", + "version": "1.4.81-2", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index fe69f4594efd..c689ccf39d87 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.81-1", + "version": "1.4.81-2", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From e1e0f7c5e29d78f3ba2437d62cf580ce4ed314c7 Mon Sep 17 00:00:00 2001 From: OSBotify Date: Mon, 10 Jun 2024 16:01:55 +0000 Subject: [PATCH 177/230] Update version to 1.4.81-3 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- ios/NotificationServiceExtension/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 76ed848c95de..a133140caaca 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -107,8 +107,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001048102 - versionName "1.4.81-2" + versionCode 1001048103 + versionName "1.4.81-3" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index fcf3fd78d476..170a624bd058 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.81.2 + 1.4.81.3 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 1f6276364287..7c638c077dde 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.4.81.2 + 1.4.81.3 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 944a919d7ed7..855e74ed1c0b 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 1.4.81 CFBundleVersion - 1.4.81.2 + 1.4.81.3 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index 2ff0a013f064..00fbcbdd362c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.81-2", + "version": "1.4.81-3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.81-2", + "version": "1.4.81-3", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index c689ccf39d87..3c0495da37cc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.81-2", + "version": "1.4.81-3", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From 763047d99281702f495afd56e67aa670d700c147 Mon Sep 17 00:00:00 2001 From: OSBotify Date: Mon, 10 Jun 2024 16:30:49 +0000 Subject: [PATCH 178/230] Update version to 1.4.81-4 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- ios/NotificationServiceExtension/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index a133140caaca..3c78c0b5e5a3 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -107,8 +107,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001048103 - versionName "1.4.81-3" + versionCode 1001048104 + versionName "1.4.81-4" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 170a624bd058..72750512d714 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.81.3 + 1.4.81.4 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 7c638c077dde..9e1488df6595 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.4.81.3 + 1.4.81.4 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 855e74ed1c0b..fd28fa7dc5bc 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 1.4.81 CFBundleVersion - 1.4.81.3 + 1.4.81.4 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index 00fbcbdd362c..7094b58b5962 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.81-3", + "version": "1.4.81-4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.81-3", + "version": "1.4.81-4", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 3c0495da37cc..fc72f146c384 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.81-3", + "version": "1.4.81-4", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From 1178c0a63a06dacd4913437dc20851a3cdefe394 Mon Sep 17 00:00:00 2001 From: Danny McClain Date: Mon, 10 Jun 2024 11:51:26 -0500 Subject: [PATCH 179/230] Added new connections illustration and updated both hubs to use it --- docs/_data/_routes.yml | 4 +-- ...imple-illustration__monitor-remotesync.svg | 30 +++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 docs/assets/images/simple-illustration__monitor-remotesync.svg diff --git a/docs/_data/_routes.yml b/docs/_data/_routes.yml index ad738e44ab44..5fd65532c021 100644 --- a/docs/_data/_routes.yml +++ b/docs/_data/_routes.yml @@ -71,7 +71,7 @@ platforms: - href: integrations title: Integrations - icon: /assets/images/workflow.svg + icon: /assets/images/simple-illustration__monitor-remotesync.svg description: Integrate with accounting or HR software to streamline expense approvals. - href: spending-insights @@ -131,7 +131,7 @@ platforms: - href: connections title: Connections - icon: /assets/images/workflow.svg + icon: /assets/images/simple-illustration__monitor-remotesync.svg description: Connect to accounting software to streamline expense approvals. - href: settings diff --git a/docs/assets/images/simple-illustration__monitor-remotesync.svg b/docs/assets/images/simple-illustration__monitor-remotesync.svg new file mode 100644 index 000000000000..e4ed84a35851 --- /dev/null +++ b/docs/assets/images/simple-illustration__monitor-remotesync.svg @@ -0,0 +1,30 @@ + + + + + + + + + \ No newline at end of file From 43e8373fe030dba5ec2bf528ec6bc3550c0df7e8 Mon Sep 17 00:00:00 2001 From: Rodrigo Lino da Costa Date: Mon, 10 Jun 2024 14:33:29 -0300 Subject: [PATCH 180/230] stop showing archived invoice room as an option on pickers --- src/libs/OptionsListUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 9840e29ff347..bfe6b492575d 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -1933,7 +1933,7 @@ function getOptions( const isCurrentUserOwnedPolicyExpenseChatThatCouldShow = reportOption.isPolicyExpenseChat && reportOption.ownerAccountID === currentUserAccountID && includeOwnedWorkspaceChats && !reportOption.isArchivedRoom; - const shouldShowInvoiceRoom = includeInvoiceRooms && ReportUtils.isInvoiceRoom(reportOption.item) && ReportUtils.isPolicyAdmin(reportOption.policyID ?? '', policies); + const shouldShowInvoiceRoom = includeInvoiceRooms && ReportUtils.isInvoiceRoom(reportOption.item) && ReportUtils.isPolicyAdmin(reportOption.policyID ?? '', policies) && !reportOption.isArchivedRoom; /** Exclude the report option if it doesn't meet any of the following conditions: From 175097372a26f10071bed26226cc4857e6ac0a1e Mon Sep 17 00:00:00 2001 From: Rodrigo Lino da Costa Date: Mon, 10 Jun 2024 14:44:06 -0300 Subject: [PATCH 181/230] prettier --- src/libs/OptionsListUtils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index bfe6b492575d..7cf60227d7ce 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -1933,7 +1933,8 @@ function getOptions( const isCurrentUserOwnedPolicyExpenseChatThatCouldShow = reportOption.isPolicyExpenseChat && reportOption.ownerAccountID === currentUserAccountID && includeOwnedWorkspaceChats && !reportOption.isArchivedRoom; - const shouldShowInvoiceRoom = includeInvoiceRooms && ReportUtils.isInvoiceRoom(reportOption.item) && ReportUtils.isPolicyAdmin(reportOption.policyID ?? '', policies) && !reportOption.isArchivedRoom; + const shouldShowInvoiceRoom = + includeInvoiceRooms && ReportUtils.isInvoiceRoom(reportOption.item) && ReportUtils.isPolicyAdmin(reportOption.policyID ?? '', policies) && !reportOption.isArchivedRoom; /** Exclude the report option if it doesn't meet any of the following conditions: From f8de14187ed1cddf5ccfa624764a9112c5420ff5 Mon Sep 17 00:00:00 2001 From: James Dean Date: Mon, 10 Jun 2024 11:06:47 -0700 Subject: [PATCH 182/230] Update en.ts minor copy updates --- src/languages/en.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 40c21a7fbfd0..06b63f2db36c 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2248,8 +2248,8 @@ export default { }, connectionsWarningModal: { featureEnabledTitle: 'Not so fast...', - featureEnabledText: 'To enable or disable this feature change your accounting import settings.', - disconnectText: 'Disconnect your accounting connection from the workspace if you want to disable Accounting.', + featureEnabledText: 'To enable or disable this feature, you'll need to change your accounting import settings.', + disconnectText: 'To disable accounting, you'll need to disconnect your accounting connection from your workspace.', manageSettings: 'Manage settings', }, }, From bca071f8d7434e60fd7aefaa5ef5898e395a2ce9 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Mon, 10 Jun 2024 12:26:15 -0600 Subject: [PATCH 183/230] fix lint --- src/types/onyx/PolicyTag.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/types/onyx/PolicyTag.ts b/src/types/onyx/PolicyTag.ts index ef3146c27c75..467ba3271981 100644 --- a/src/types/onyx/PolicyTag.ts +++ b/src/types/onyx/PolicyTag.ts @@ -18,6 +18,7 @@ type PolicyTag = OnyxCommon.OnyxValueWithOfflineFeedback<{ /** A list of errors keyed by microtime */ errors?: OnyxCommon.Errors | null; + /** The rules applied to the policy tag */ rules?: { /** * String representation of regex to match against parent tag. Eg, if San Francisco is a child tag of California From 86866c815990406db9976da31945897bebf22b5c Mon Sep 17 00:00:00 2001 From: James Dean Date: Mon, 10 Jun 2024 11:30:38 -0700 Subject: [PATCH 184/230] Update Troubleshooting.md --- .../connect-credit-cards/company-cards/Troubleshooting.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/articles/expensify-classic/connect-credit-cards/company-cards/Troubleshooting.md b/docs/articles/expensify-classic/connect-credit-cards/company-cards/Troubleshooting.md index 814bf8fc559b..05366a91d9fa 100644 --- a/docs/articles/expensify-classic/connect-credit-cards/company-cards/Troubleshooting.md +++ b/docs/articles/expensify-classic/connect-credit-cards/company-cards/Troubleshooting.md @@ -64,8 +64,9 @@ Make sure you're importing your card in the correct spot in Expensify and select Please note there are some things that cannot be bypassed within Expensify, including two-factor authentication being enabled within your bank account. This will prevent the connection from remaining stable and will need to be turned off on the bank side. ## What are the most reliable bank connections in Expensify?* -The most reliable corporate card to use with Expensify is the Expensify Visa® Commercial Card. We offer daily settlement, unapproved expense limits, and real-time compliance for secure and efficient spending, as well as 2% cash back (_Applies to USD purchases only._) Click here to learn more or apply. -Additionally, we've teamed up with major banks worldwide to ensure a smooth import of credit card transactions into your accounts. Corporate cards from the following banks also offer the most dependable connections in Expensify: +All bank connections listed below are extremely reliable, but we recommend transacting with the Expensify Visa® Commercial Card. It also offers daily and monthly settlement, unapproved expense limits, realtime compliance for secure and efficient spending, and cash back on all US purchases. [Click here to learn more about the Expensify Card](https://use.expensify.com/company-credit-card). + +We've also teamed up with major banks worldwide to ensure a smooth import of credit card transactions into your accounts: - American Express - Bank of America - Brex @@ -75,7 +76,7 @@ Additionally, we've teamed up with major banks worldwide to ensure a smooth impo - Stripe - Wells Fargo -Commercial feeds for company cards are the dependable connections in Expensify. If you have a corporate or commercial card account, you might have access to a daily transaction feed where expenses from Visa, MasterCard, and American Express are automatically sent to Expensify. Reach out to your banking relationship manager to check if your card program qualifies for this feature. +Commercial feeds for company cards are the dependable connections in Expensify. If you have a corporate or commercial card account, you might have access to a daily transaction feed where expenses from Visa, Mastercard, and American Express are automatically sent to Expensify. Reach out to your banking relationship manager to check if your card program qualifies for this feature. # Troubleshooting American Express Business From bebfb0a507b021fbc24ba0ad87b89a8f861c4de4 Mon Sep 17 00:00:00 2001 From: James Dean Date: Mon, 10 Jun 2024 11:38:37 -0700 Subject: [PATCH 185/230] Update Expensify-Playbook-For-US-Based-VC-Backed-Startups.md --- ...ensify-Playbook-For-US-Based-VC-Backed-Startups.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-US-Based-VC-Backed-Startups.md b/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-US-Based-VC-Backed-Startups.md index 30d3b3e7732c..0f6df238db5b 100644 --- a/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-US-Based-VC-Backed-Startups.md +++ b/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-US-Based-VC-Backed-Startups.md @@ -155,11 +155,12 @@ The Expensify Card has many benefits for your company. Two in particular are wor ### If you don't have a corporate card, use the Expensify Card Expensify provides a corporate card with the following features: -- Up to 2% cash back (Applies to USD purchases only) -- [SmartLimits](https://community.expensify.com/discussion/4851/deep-dive-what-are-unapproved-expense-limits#latest) -- Unlimited Virtual Cards - single-purpose cards with a fixed or monthly limit for specific company purchases -- A stable, unbreakable connection (third-party bank feeds can run into connectivity issues) - +- Finish your expenses in a swipe, we'll take care of everything else +- Get cash back on every US purchase and up to 50% off your monthly Expensify bill +- Stay in control with realtime alerts, spend limits, and auto-reconciliation +- Don't worry about credit checks, annual fees, or personal guarantees +- Create unlimited virtual cards with fixed or monthly limits for specific purchases + The Expensify Card is recommended as the most efficient way to manage your company's spending. Here’s how to enable it: From fb7fc9fe5d385b594cc35692935759b73e10ed00 Mon Sep 17 00:00:00 2001 From: James Dean Date: Mon, 10 Jun 2024 11:42:45 -0700 Subject: [PATCH 186/230] Update Expensify-Playbook-For-Small-To-Medium-Sized-Businesses.md --- ...Playbook-For-Small-To-Medium-Sized-Businesses.md | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-Small-To-Medium-Sized-Businesses.md b/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-Small-To-Medium-Sized-Businesses.md index 42d06d45fa87..411cc64eda7f 100644 --- a/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-Small-To-Medium-Sized-Businesses.md +++ b/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-Small-To-Medium-Sized-Businesses.md @@ -194,14 +194,11 @@ As mentioned above, we’ll be able to pull in transactions as they post (daily) ### If you don't have a corporate card, use the Expensify Card (US only) Expensify provides a corporate card with the following features: -- Up to 2% cash back (_Applies to USD purchases only._) -- [SmartLimits](https://help.expensify.com/articles/expensify-classic/expensify-card/Cardholder-Settings-and-Features) to control what each individual cardholder can spend -- A stable, unbreakable real-time connection (third-party bank feeds can run into connectivity issues) -- Receipt compliance - informing notifications (e.g. add a receipt!) for users *as soon as the card is swiped* -- Unlimited Virtual Cards - single-purpose cards with a fixed or monthly limit for specific company purchases -- A 50% discount on the price of all Expensify plans -- Multiple discounts and savings on a host of partner tech suppliers -- Good Karma - 10% of all card interchange we earn goes directly to the Expensify.org Social Justice Community funds +- Finish your expenses in a swipe, we'll take care of everything else +- Get cash back on every US purchase and up to 50% off your monthly Expensify bill +- Stay in control with realtime alerts, spend limits, and auto-reconciliation +- Don't worry about credit checks, annual fees, or personal guarantees +- Create unlimited virtual cards with fixed or monthly limits for specific purchases The Expensify Card is recommended as the most efficient way to manage your company's spending. From 0dd74d72ac48b16bd0e92ab19960e101b86e750e Mon Sep 17 00:00:00 2001 From: Yauheni Pasiukevich Date: Mon, 10 Jun 2024 20:45:23 +0200 Subject: [PATCH 187/230] update props, add variables --- .../Subscription/CardSection/BillingBanner.tsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/pages/settings/Subscription/CardSection/BillingBanner.tsx b/src/pages/settings/Subscription/CardSection/BillingBanner.tsx index 1ae20f177431..062ab5c8db58 100644 --- a/src/pages/settings/Subscription/CardSection/BillingBanner.tsx +++ b/src/pages/settings/Subscription/CardSection/BillingBanner.tsx @@ -6,10 +6,11 @@ import * as Illustrations from '@components/Icon/Illustrations'; import Text from '@components/Text'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; +import variables from '@styles/variables'; type BillingBannerProps = { - title: string; - subtitle: string; + title?: string; + subtitle?: string; isError?: boolean; shouldShowRedDotIndicator?: boolean; }; @@ -22,12 +23,12 @@ function BillingBanner({title, subtitle, isError, shouldShowRedDotIndicator}: Bi - {title} - {subtitle} + {title && {title}} + {subtitle && {subtitle}} {isError && shouldShowRedDotIndicator && ( Date: Mon, 10 Jun 2024 20:50:24 +0200 Subject: [PATCH 188/230] merge to main --- .../BottomTabBar/index.website.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.website.tsx b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.website.tsx index bc813f70f081..4fecfdcb0e3e 100644 --- a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.website.tsx +++ b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.website.tsx @@ -12,6 +12,7 @@ import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import * as Session from '@libs/actions/Session'; +import interceptAnonymousUser from '@libs/interceptAnonymousUser'; import getTopmostBottomTabRoute from '@libs/Navigation/getTopmostBottomTabRoute'; import getTopmostCentralPaneRoute from '@libs/Navigation/getTopmostCentralPaneRoute'; import Navigation from '@libs/Navigation/Navigation'; @@ -100,7 +101,7 @@ function BottomTabBar({isLoadingApp = false}: PurposeForUsingExpensifyModalProps { - Navigation.navigate(ROUTES.SEARCH.getRoute(CONST.TAB_SEARCH.ALL)); + interceptAnonymousUser(() => Navigation.navigate(ROUTES.SEARCH.getRoute(CONST.TAB_SEARCH.ALL))); }} role={CONST.ROLE.BUTTON} accessibilityLabel={translate('common.search')} From 0fb9222754d90ded9d965a2d656f6bca316e7408 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Mon, 10 Jun 2024 13:24:35 -0600 Subject: [PATCH 189/230] update receipt background color --- src/components/ReceiptImage.tsx | 5 - .../SelectionList/Search/ReportListItem.tsx | 101 +++++++++--------- .../Search/TransactionListItem.tsx | 17 ++- .../Search/TransactionListItemRow.tsx | 11 +- src/components/ThumbnailImage.tsx | 8 +- 5 files changed, 60 insertions(+), 82 deletions(-) diff --git a/src/components/ReceiptImage.tsx b/src/components/ReceiptImage.tsx index 65eb9b82ecdc..946856ecec37 100644 --- a/src/components/ReceiptImage.tsx +++ b/src/components/ReceiptImage.tsx @@ -76,9 +76,6 @@ type ReceiptImageProps = ( /** The colod of the fallback icon */ fallbackIconColor?: string; - - /** Whether the component is hovered */ - isHovered?: boolean; }; function ReceiptImage({ @@ -96,7 +93,6 @@ function ReceiptImage({ fallbackIconSize, shouldUseInitialObjectPosition = false, fallbackIconColor, - isHovered = false, }: ReceiptImageProps) { const styles = useThemeStyles(); @@ -134,7 +130,6 @@ function ReceiptImage({ fallbackIconSize={fallbackIconSize} fallbackIconColor={fallbackIconColor} objectPosition={shouldUseInitialObjectPosition ? CONST.IMAGE_OBJECT_POSITION.INITIAL : CONST.IMAGE_OBJECT_POSITION.TOP} - isHovered={isHovered} /> ); } diff --git a/src/components/SelectionList/Search/ReportListItem.tsx b/src/components/SelectionList/Search/ReportListItem.tsx index bc5a84c33b90..9adff46395e6 100644 --- a/src/components/SelectionList/Search/ReportListItem.tsx +++ b/src/components/SelectionList/Search/ReportListItem.tsx @@ -146,64 +146,61 @@ function ReportListItem({ shouldSyncFocus={shouldSyncFocus} hoverStyle={item.isSelected && styles.activeComponentBG} > - {(hovered?: boolean) => ( - - {!isLargeScreenWidth && ( - - )} - - - - - {reportItem?.reportName} - {`${reportItem.transactions.length} ${translate('search.groupedExpenses')}`} - + + {!isLargeScreenWidth && ( + + )} + + + + + {reportItem?.reportName} + {`${reportItem.transactions.length} ${translate('search.groupedExpenses')}`} - - + + + + + {isLargeScreenWidth && ( + <> + {/** We add an empty view with type style to align the total with the table header */} + + + - - {isLargeScreenWidth && ( - <> - {/** We add an empty view with type style to align the total with the table header */} - - - - - - )} - - - {reportItem.transactions.map((transaction) => ( - { - openReportInRHP(transaction); - }} - showItemHeaderOnNarrowLayout={false} - containerStyle={styles.mt3} - isHovered={hovered} - isChildListItem - /> - ))} + + )} - )} + + {reportItem.transactions.map((transaction) => ( + { + openReportInRHP(transaction); + }} + showItemHeaderOnNarrowLayout={false} + containerStyle={styles.mt3} + isChildListItem + /> + ))} + ); } diff --git a/src/components/SelectionList/Search/TransactionListItem.tsx b/src/components/SelectionList/Search/TransactionListItem.tsx index b222631b7273..ecf9264301c2 100644 --- a/src/components/SelectionList/Search/TransactionListItem.tsx +++ b/src/components/SelectionList/Search/TransactionListItem.tsx @@ -50,16 +50,13 @@ function TransactionListItem({ shouldSyncFocus={shouldSyncFocus} hoverStyle={item.isSelected && styles.activeComponentBG} > - {(hovered?: boolean) => ( - { - onSelectRow(item); - }} - isHovered={hovered} - /> - )} + { + onSelectRow(item); + }} + /> ); } diff --git a/src/components/SelectionList/Search/TransactionListItemRow.tsx b/src/components/SelectionList/Search/TransactionListItemRow.tsx index c23ae815f472..c2ad559511ff 100644 --- a/src/components/SelectionList/Search/TransactionListItemRow.tsx +++ b/src/components/SelectionList/Search/TransactionListItemRow.tsx @@ -33,10 +33,6 @@ type TransactionCellProps = { transactionItem: TransactionListItemType; } & CellProps; -type ReceiptCellProps = { - isHovered?: boolean; -} & TransactionCellProps; - type ActionCellProps = { onButtonPress: () => void; } & CellProps; @@ -51,7 +47,6 @@ type TransactionListItemRowProps = { onButtonPress: () => void; showItemHeaderOnNarrowLayout?: boolean; containerStyle?: StyleProp; - isHovered?: boolean; isChildListItem?: boolean; }; @@ -68,7 +63,7 @@ const getTypeIcon = (type?: SearchTransactionType) => { } }; -function ReceiptCell({transactionItem, isHovered = false}: ReceiptCellProps) { +function ReceiptCell({transactionItem}: TransactionCellProps) { const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -85,7 +80,6 @@ function ReceiptCell({transactionItem, isHovered = false}: ReceiptCellProps) { fallbackIconSize={20} fallbackIconColor={theme.icon} iconSize="x-small" - isHovered={isHovered} /> ); @@ -215,7 +209,6 @@ function TransactionListItemRow({ onButtonPress, showItemHeaderOnNarrowLayout = true, containerStyle, - isHovered = false, isChildListItem = false, }: TransactionListItemRowProps) { const styles = useThemeStyles(); @@ -242,7 +235,6 @@ function TransactionListItemRow({ transactionItem={item} isLargeScreenWidth={false} showTooltip={false} - isHovered={isHovered} /> diff --git a/src/components/ThumbnailImage.tsx b/src/components/ThumbnailImage.tsx index d4a45f3d93b3..269c94bc761d 100644 --- a/src/components/ThumbnailImage.tsx +++ b/src/components/ThumbnailImage.tsx @@ -5,6 +5,7 @@ import useNetwork from '@hooks/useNetwork'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useThumbnailDimensions from '@hooks/useThumbnailDimensions'; +import useStyleUtils from '@hooks/useStyleUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import type IconAsset from '@src/types/utils/IconAsset'; @@ -48,9 +49,6 @@ type ThumbnailImageProps = { /** The object position of image */ objectPosition?: ImageObjectPosition; - - /** Whether the component is hovered */ - isHovered?: boolean; }; type UpdateImageSizeParams = { @@ -69,7 +67,6 @@ function ThumbnailImage({ fallbackIconSize = variables.iconSizeSuperLarge, fallbackIconColor, objectPosition = CONST.IMAGE_OBJECT_POSITION.INITIAL, - isHovered = false, }: ThumbnailImageProps) { const styles = useThemeStyles(); const theme = useTheme(); @@ -78,6 +75,7 @@ function ThumbnailImage({ const cachedDimensions = shouldDynamicallyResize && typeof previewSourceURL === 'string' ? thumbnailDimensionsCache.get(previewSourceURL) : null; const [imageDimensions, setImageDimensions] = useState({width: cachedDimensions?.width ?? imageWidth, height: cachedDimensions?.height ?? imageHeight}); const {thumbnailDimensionsStyles} = useThumbnailDimensions(imageDimensions.width, imageDimensions.height); + const StyleUtils = useStyleUtils(); useEffect(() => { setFailedToLoad(false); @@ -110,7 +108,7 @@ function ThumbnailImage({ if (failedToLoad || previewSourceURL === '') { return ( - + Date: Mon, 10 Jun 2024 13:43:51 -0600 Subject: [PATCH 190/230] fix topbar text overflow --- src/components/Breadcrumbs.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Breadcrumbs.tsx b/src/components/Breadcrumbs.tsx index e5eb09691eba..e641a0c2218a 100644 --- a/src/components/Breadcrumbs.tsx +++ b/src/components/Breadcrumbs.tsx @@ -68,7 +68,7 @@ function Breadcrumbs({breadcrumbs, style}: BreadcrumbsProps) { / {secondaryBreadcrumb.text} From ea6652471da0b22c86a7d5cc07956f40b599fa05 Mon Sep 17 00:00:00 2001 From: OSBotify Date: Mon, 10 Jun 2024 20:09:16 +0000 Subject: [PATCH 191/230] Update version to 1.4.81-5 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- ios/NotificationServiceExtension/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 3c78c0b5e5a3..2e89bbc7e26a 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -107,8 +107,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001048104 - versionName "1.4.81-4" + versionCode 1001048105 + versionName "1.4.81-5" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 72750512d714..f0c5755901cc 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.81.4 + 1.4.81.5 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 9e1488df6595..94700c2c07ef 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.4.81.4 + 1.4.81.5 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index fd28fa7dc5bc..c26dd25e1c96 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 1.4.81 CFBundleVersion - 1.4.81.4 + 1.4.81.5 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index 7094b58b5962..598784cfc378 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.81-4", + "version": "1.4.81-5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.81-4", + "version": "1.4.81-5", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index fc72f146c384..a74743a1d88f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.81-4", + "version": "1.4.81-5", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From 628c5a79e6b18b3d7ecedb4a29962bc3014d7d6e Mon Sep 17 00:00:00 2001 From: Andrew Gable Date: Mon, 10 Jun 2024 14:48:45 -0600 Subject: [PATCH 192/230] Revert "chore: remove all selection ranges during navigation" --- src/libs/Navigation/NavigationRoot.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/libs/Navigation/NavigationRoot.tsx b/src/libs/Navigation/NavigationRoot.tsx index 9e1eb348451b..06a3dce8d59a 100644 --- a/src/libs/Navigation/NavigationRoot.tsx +++ b/src/libs/Navigation/NavigationRoot.tsx @@ -135,11 +135,6 @@ function NavigationRoot({authenticated, lastVisitedPath, initialUrl, onReady}: N // We want to clean saved scroll offsets for screens that aren't anymore in the state. cleanStaleScrollOffsets(state); - - // clear all window selection on navigation - // this is to prevent the selection from persisting when navigating to a new page in web - // using "?" to avoid crash in native - window?.getSelection?.()?.removeAllRanges?.(); }; return ( From 89c64835daab4c01f65defb664a8d10566539cb9 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Mon, 10 Jun 2024 15:09:27 -0600 Subject: [PATCH 193/230] fix lint --- .../SelectionList/Search/TransactionListItemRow.tsx | 9 +-------- src/components/ThumbnailImage.tsx | 2 +- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/components/SelectionList/Search/TransactionListItemRow.tsx b/src/components/SelectionList/Search/TransactionListItemRow.tsx index c2ad559511ff..d17d923a54e1 100644 --- a/src/components/SelectionList/Search/TransactionListItemRow.tsx +++ b/src/components/SelectionList/Search/TransactionListItemRow.tsx @@ -203,14 +203,7 @@ function TaxCell({transactionItem, showTooltip}: TransactionCellProps) { ); } -function TransactionListItemRow({ - item, - showTooltip, - onButtonPress, - showItemHeaderOnNarrowLayout = true, - containerStyle, - isChildListItem = false, -}: TransactionListItemRowProps) { +function TransactionListItemRow({item, showTooltip, onButtonPress, showItemHeaderOnNarrowLayout = true, containerStyle, isChildListItem = false}: TransactionListItemRowProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const {isLargeScreenWidth} = useWindowDimensions(); diff --git a/src/components/ThumbnailImage.tsx b/src/components/ThumbnailImage.tsx index 269c94bc761d..3b89b7c3a7ad 100644 --- a/src/components/ThumbnailImage.tsx +++ b/src/components/ThumbnailImage.tsx @@ -2,10 +2,10 @@ import React, {useCallback, useEffect, useState} from 'react'; import type {ImageSourcePropType, StyleProp, ViewStyle} from 'react-native'; import {View} from 'react-native'; import useNetwork from '@hooks/useNetwork'; +import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useThumbnailDimensions from '@hooks/useThumbnailDimensions'; -import useStyleUtils from '@hooks/useStyleUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import type IconAsset from '@src/types/utils/IconAsset'; From 061a7fdcc0900cc3e60da7bf0c2d00d7e7f05003 Mon Sep 17 00:00:00 2001 From: OSBotify Date: Mon, 10 Jun 2024 21:13:58 +0000 Subject: [PATCH 194/230] Update version to 1.4.81-6 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- ios/NotificationServiceExtension/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 2e89bbc7e26a..1aa1369a556a 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -107,8 +107,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001048105 - versionName "1.4.81-5" + versionCode 1001048106 + versionName "1.4.81-6" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index f0c5755901cc..08b0e985cf5f 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.81.5 + 1.4.81.6 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 94700c2c07ef..ac9882f31c38 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.4.81.5 + 1.4.81.6 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index c26dd25e1c96..9af1d6896871 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 1.4.81 CFBundleVersion - 1.4.81.5 + 1.4.81.6 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index 598784cfc378..b2a5b377b281 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.81-5", + "version": "1.4.81-6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.81-5", + "version": "1.4.81-6", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index a74743a1d88f..058406cf369d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.81-5", + "version": "1.4.81-6", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From 7b2ef058ed671ed5e4f6d31027d60db1710d9399 Mon Sep 17 00:00:00 2001 From: James Dean Date: Mon, 10 Jun 2024 15:44:35 -0700 Subject: [PATCH 195/230] Update en.ts --- src/languages/en.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 06b63f2db36c..7d96167559e8 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2248,8 +2248,8 @@ export default { }, connectionsWarningModal: { featureEnabledTitle: 'Not so fast...', - featureEnabledText: 'To enable or disable this feature, you'll need to change your accounting import settings.', - disconnectText: 'To disable accounting, you'll need to disconnect your accounting connection from your workspace.', + featureEnabledText: "To enable or disable this feature, you'll need to change your accounting import settings.", + disconnectText: "To disable accounting, you'll need to disconnect your accounting connection from your workspace.", manageSettings: 'Manage settings', }, }, From b630821529ffc1f3a72bac351275cc30fcc6e51a Mon Sep 17 00:00:00 2001 From: James Dean Date: Mon, 10 Jun 2024 15:56:19 -0700 Subject: [PATCH 196/230] Update es.ts --- src/languages/es.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index a6d317c623e0..9b86a1bf0f78 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2288,7 +2288,7 @@ export default { connectionsWarningModal: { featureEnabledTitle: 'No tan rápido...', featureEnabledText: 'Para activar o desactivar esta función, cambia la configuración de importación contable.', - disconnectText: 'Desconecta tu conexión contable del espacio de trabajo si deseas desactivar la Contabilidad.', + disconnectText: 'Para desactivar la contabilidad, desconecta tu conexión contable del espacio de trabajo.', manageSettings: 'Gestionar la configuración', }, }, From 4c026a454392f836d519550a673a08a4701c61a9 Mon Sep 17 00:00:00 2001 From: James Dean Date: Mon, 10 Jun 2024 16:02:30 -0700 Subject: [PATCH 197/230] Update en.ts --- src/languages/en.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 40c21a7fbfd0..59c9e61a54e2 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2360,7 +2360,7 @@ export default { noVBACopy: 'Connect a bank account to issue Expensify Cards to your workspace members, and access these incredible benefits and more:', VBANoECardCopy: 'Add a work email address to issue unlimited Expensify Cards for your workspace members, as well as all of these incredible benefits:', VBAWithECardCopy: 'Access these incredible benefits and more:', - benefit1: 'Up to 2% cash back', + benefit1: 'Cash back on every US purchase', benefit2: 'Digital and physical cards', benefit3: 'No personal liability', benefit4: 'Customizable limits', From d7772fbac4befcfa6fc3fc484a96cf2ad738ac0f Mon Sep 17 00:00:00 2001 From: rory Date: Mon, 10 Jun 2024 16:36:34 -0700 Subject: [PATCH 198/230] Upgrade mock-github --- package-lock.json | 51 ++++++++--------------------------------------- package.json | 2 +- 2 files changed, 9 insertions(+), 44 deletions(-) diff --git a/package-lock.json b/package-lock.json index b2a5b377b281..675c698986fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "@gorhom/portal": "^1.0.14", "@invertase/react-native-apple-authentication": "^2.2.2", "@kie/act-js": "^2.6.0", - "@kie/mock-github": "^1.0.0", + "@kie/mock-github": "2.0.1", "@onfido/react-native-sdk": "10.6.0", "@react-native-camera-roll/camera-roll": "7.4.0", "@react-native-clipboard/clipboard": "^1.13.2", @@ -7051,48 +7051,12 @@ "act-js": "bin/act" } }, - "node_modules/@kie/act-js/node_modules/@kie/mock-github": { - "version": "2.0.0", - "license": "SEE LICENSE IN LICENSE", - "dependencies": { - "@octokit/openapi-types-ghec": "^18.0.0", - "ajv": "^8.11.0", - "express": "^4.18.1", - "fast-glob": "^3.2.12", - "fs-extra": "^10.1.0", - "nock": "^13.2.7", - "simple-git": "^3.8.0", - "totalist": "^3.0.0" - } - }, - "node_modules/@kie/act-js/node_modules/@octokit/openapi-types-ghec": { - "version": "18.1.1", - "license": "MIT" - }, - "node_modules/@kie/act-js/node_modules/fs-extra": { - "version": "10.1.0", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@kie/act-js/node_modules/totalist": { - "version": "3.0.1", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/@kie/mock-github": { - "version": "1.1.0", - "license": "SEE LICENSE IN LICENSE", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@kie/mock-github/-/mock-github-2.0.1.tgz", + "integrity": "sha512-G1FD/jg1KyW7a6NvKI4uEVJCK3eJnzXkh4Ikxn2is5tiNC980lavi8ak6bn1QEFEgpYcfM4DpZM3yHDfOmyLuQ==", "dependencies": { - "@octokit/openapi-types-ghec": "^14.0.0", + "@octokit/openapi-types-ghec": "^18.0.0", "ajv": "^8.11.0", "express": "^4.18.1", "fast-glob": "^3.2.12", @@ -7542,8 +7506,9 @@ "license": "MIT" }, "node_modules/@octokit/openapi-types-ghec": { - "version": "14.0.0", - "license": "MIT" + "version": "18.1.1", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types-ghec/-/openapi-types-ghec-18.1.1.tgz", + "integrity": "sha512-5Ri7FLYX4gJSdG+G0Q8QDca/gOLfkPN4YR2hkbVg6hEL+0N62MIsJPTyNaT9pGEXCLd1KbYV6Lh3T2ggsmyBJw==" }, "node_modules/@octokit/plugin-paginate-rest": { "version": "3.1.0", diff --git a/package.json b/package.json index 058406cf369d..ebbf0725fd7a 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "@gorhom/portal": "^1.0.14", "@invertase/react-native-apple-authentication": "^2.2.2", "@kie/act-js": "^2.6.0", - "@kie/mock-github": "^1.0.0", + "@kie/mock-github": "2.0.1", "@onfido/react-native-sdk": "10.6.0", "@react-native-camera-roll/camera-roll": "7.4.0", "@react-native-clipboard/clipboard": "^1.13.2", From 943e6dc2d637a72904698a4457d960b74beb1fed Mon Sep 17 00:00:00 2001 From: rory Date: Mon, 10 Jun 2024 16:37:01 -0700 Subject: [PATCH 199/230] Log into github docker registry --- .github/workflows/testGithubActionsWorkflows.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/testGithubActionsWorkflows.yml b/.github/workflows/testGithubActionsWorkflows.yml index d052b343cf0c..51672a07432e 100644 --- a/.github/workflows/testGithubActionsWorkflows.yml +++ b/.github/workflows/testGithubActionsWorkflows.yml @@ -28,6 +28,9 @@ jobs: - name: Install Act run: brew install act + - name: Log into GitHub Container Registry + run: docker login ghcr.io -u OSBotify -p ${{ secrets.OS_BOTIFY_TOKEN }} + - name: Set ACT_BINARY run: echo "ACT_BINARY=$(which act)" >> "$GITHUB_ENV" From e70f69b5d4c9001840d01f8ad7bca80957b78782 Mon Sep 17 00:00:00 2001 From: rory Date: Mon, 10 Jun 2024 16:37:16 -0700 Subject: [PATCH 200/230] Specify archicture if running locally --- workflow_tests/utils/ExtendedAct.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/workflow_tests/utils/ExtendedAct.ts b/workflow_tests/utils/ExtendedAct.ts index e2bb12ec8e01..45450113f8b8 100644 --- a/workflow_tests/utils/ExtendedAct.ts +++ b/workflow_tests/utils/ExtendedAct.ts @@ -2,6 +2,7 @@ // This eslint-disable comment is here to allow accessing private properties in the Act class import type {RunOpts, Step, Workflow} from '@kie/act-js'; import {Act} from '@kie/act-js'; +import os from 'os'; import path from 'path'; import JobMocker from './JobMocker'; import type {MockJobs} from './JobMocker'; @@ -23,6 +24,10 @@ class ExtendedAct extends Act { actArguments.push('--actor', opts.actor); } + if (os.arch() === 'arm64') { + actArguments.push('--container-architecture', 'linux/amd64'); + } + return {cwd, actArguments, proxy}; } From ce6c1125ee2641ccbe3115dffcf98283957cf671 Mon Sep 17 00:00:00 2001 From: chiragsalian Date: Mon, 10 Jun 2024 16:46:19 -0700 Subject: [PATCH 201/230] removing check for shouldShowHoldMessage --- .../MoneyRequestPreview/MoneyRequestPreviewContent.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index b6d6915279cb..e03ed59ffb79 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -163,11 +163,7 @@ function MoneyRequestPreviewContent({ const isTooLong = violationsCount > 1 || violationMessage.length > 15; const hasViolationsAndFieldErrors = violationsCount > 0 && hasFieldErrors; - message += ` ${CONST.DOT_SEPARATOR} ${isTooLong || hasViolationsAndFieldErrors ? translate('violations.reviewRequired') : violationMessage}`; - if (shouldShowHoldMessage) { - message += ` ${CONST.DOT_SEPARATOR} ${translate('iou.hold')}`; - } - return message; + return `${message} ${CONST.DOT_SEPARATOR} ${isTooLong || hasViolationsAndFieldErrors ? translate('violations.reviewRequired') : violationMessage}`; } const isMerchantMissing = TransactionUtils.isMerchantMissing(transaction); From 336db4e6dd2a06a000366e3056a442a2073b4dd5 Mon Sep 17 00:00:00 2001 From: rory Date: Mon, 10 Jun 2024 16:51:26 -0700 Subject: [PATCH 202/230] Use docker/login-action for better security --- .github/workflows/testGithubActionsWorkflows.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/testGithubActionsWorkflows.yml b/.github/workflows/testGithubActionsWorkflows.yml index 51672a07432e..f65319f14be4 100644 --- a/.github/workflows/testGithubActionsWorkflows.yml +++ b/.github/workflows/testGithubActionsWorkflows.yml @@ -25,12 +25,16 @@ jobs: - name: Setup Homebrew uses: Homebrew/actions/setup-homebrew@master + - name: Login to GitHub Container Regstry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: OSBotify + password: ${{ secrets.GITHUB_TOKEN }} + - name: Install Act run: brew install act - - name: Log into GitHub Container Registry - run: docker login ghcr.io -u OSBotify -p ${{ secrets.OS_BOTIFY_TOKEN }} - - name: Set ACT_BINARY run: echo "ACT_BINARY=$(which act)" >> "$GITHUB_ENV" From 001c72dddcc5b6eaad4c37b3701233f5ba7b217d Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Tue, 11 Jun 2024 02:04:51 +0200 Subject: [PATCH 203/230] Clean up --- src/pages/workspace/WorkspacesListPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/WorkspacesListPage.tsx b/src/pages/workspace/WorkspacesListPage.tsx index 393e25076765..599e8818b52c 100755 --- a/src/pages/workspace/WorkspacesListPage.tsx +++ b/src/pages/workspace/WorkspacesListPage.tsx @@ -309,7 +309,7 @@ function WorkspacesListPage({policies, reimbursementAccount, reports, session}: if (isEmptyObject(policies)) { return []; } - console.log('policies: ', policies); + return Object.values(policies) .filter((policy): policy is PolicyType => PolicyUtils.shouldShowPolicy(policy, !!isOffline)) .map((policy): WorkspaceItem => { From 60442419ab6434f1f22729399637c8c67c0167a3 Mon Sep 17 00:00:00 2001 From: OSBotify Date: Tue, 11 Jun 2024 00:07:59 +0000 Subject: [PATCH 204/230] Update version to 1.4.81-7 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- ios/NotificationServiceExtension/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 1aa1369a556a..ba41291038f3 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -107,8 +107,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001048106 - versionName "1.4.81-6" + versionCode 1001048107 + versionName "1.4.81-7" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 08b0e985cf5f..e782155beee7 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.81.6 + 1.4.81.7 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index ac9882f31c38..c18015ff0858 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.4.81.6 + 1.4.81.7 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 9af1d6896871..2feda22f8a36 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 1.4.81 CFBundleVersion - 1.4.81.6 + 1.4.81.7 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index b2a5b377b281..6fb017197b28 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.81-6", + "version": "1.4.81-7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.81-6", + "version": "1.4.81-7", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 058406cf369d..dcd8fd60863c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.81-6", + "version": "1.4.81-7", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From d7c0d6e2b008606a064d2b4e285c92b935d8e929 Mon Sep 17 00:00:00 2001 From: Jack Nam <30609178+thienlnam@users.noreply.github.com> Date: Mon, 10 Jun 2024 17:33:53 -0700 Subject: [PATCH 205/230] Revert "Immediately show file size message for large attachments" --- src/components/AttachmentPicker/index.native.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/AttachmentPicker/index.native.tsx b/src/components/AttachmentPicker/index.native.tsx index 559d5b24403a..154fcf838c86 100644 --- a/src/components/AttachmentPicker/index.native.tsx +++ b/src/components/AttachmentPicker/index.native.tsx @@ -5,7 +5,7 @@ import RNFetchBlob from 'react-native-blob-util'; import RNDocumentPicker from 'react-native-document-picker'; import type {DocumentPickerOptions, DocumentPickerResponse} from 'react-native-document-picker'; import {launchImageLibrary} from 'react-native-image-picker'; -import type {Asset, Callback, CameraOptions, ImageLibraryOptions, ImagePickerResponse} from 'react-native-image-picker'; +import type {Asset, Callback, CameraOptions, ImagePickerResponse} from 'react-native-image-picker'; import ImageSize from 'react-native-image-size'; import type {FileObject, ImagePickerResponse as FileResponse} from '@components/AttachmentModal'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -41,12 +41,11 @@ type Item = { * See https://github.com/react-native-image-picker/react-native-image-picker/#options * for ImagePicker configuration options */ -const imagePickerOptions: Partial = { +const imagePickerOptions = { includeBase64: false, saveToPhotos: false, selectionLimit: 1, includeExtra: false, - assetRepresentationMode: 'current', }; /** From 5439a1a50cbc4050bd84f880f487fe4c24646582 Mon Sep 17 00:00:00 2001 From: Francois Laithier Date: Mon, 10 Jun 2024 19:01:14 -0700 Subject: [PATCH 206/230] Prevent crash if connection object doesn't have a `config` --- src/pages/workspace/WorkspaceMoreFeaturesPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx b/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx index 05f72ac3c803..55fbbedd3edd 100644 --- a/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx +++ b/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx @@ -58,7 +58,7 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro const {translate} = useLocalize(); const {canUseAccountingIntegrations} = usePermissions(); const hasAccountingConnection = !!policy?.areConnectionsEnabled && !isEmptyObject(policy?.connections); - const isSyncTaxEnabled = !!policy?.connections?.quickbooksOnline?.config.syncTax || !!policy?.connections?.xero?.config.importTaxRates; + const isSyncTaxEnabled = !!policy?.connections?.quickbooksOnline?.config?.syncTax || !!policy?.connections?.xero?.config?.importTaxRates; const policyID = policy?.id ?? ''; const [isOrganizeWarningModalOpen, setIsOrganizeWarningModalOpen] = useState(false); From 27e235b9339f1eb5d029716eb2de353d746b2384 Mon Sep 17 00:00:00 2001 From: OSBotify Date: Tue, 11 Jun 2024 02:32:48 +0000 Subject: [PATCH 207/230] Update version to 1.4.81-8 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- ios/NotificationServiceExtension/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index ba41291038f3..e243f65c9efb 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -107,8 +107,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001048107 - versionName "1.4.81-7" + versionCode 1001048108 + versionName "1.4.81-8" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index e782155beee7..54b78cd6289c 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.81.7 + 1.4.81.8 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index c18015ff0858..9f63f7ae9e98 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.4.81.7 + 1.4.81.8 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 2feda22f8a36..af2cb1e4a29c 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 1.4.81 CFBundleVersion - 1.4.81.7 + 1.4.81.8 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index 6fb017197b28..66015a4955a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.81-7", + "version": "1.4.81-8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.81-7", + "version": "1.4.81-8", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index dcd8fd60863c..697b9168dcd6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.81-7", + "version": "1.4.81-8", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From b330d98e7b95c0ed2420e068b716016934100e9a Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Tue, 11 Jun 2024 09:09:51 +0200 Subject: [PATCH 208/230] fix typing in usePreferredCurrency --- src/hooks/usePreferredCurrency.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/usePreferredCurrency.ts b/src/hooks/usePreferredCurrency.ts index 32809e7d70c8..79f4057c384e 100644 --- a/src/hooks/usePreferredCurrency.ts +++ b/src/hooks/usePreferredCurrency.ts @@ -9,7 +9,7 @@ type PreferredCurrency = ValueOf; /** * Get user's preferred currency in the following order: * - * 1. Default card currency + * 1. Payment card currency * 2. User's local currency (if it's a valid payment card currency) * 3. USD (default currency) * From 08a87cdd2185761c39cb2277363827eb600c544a Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 11 Jun 2024 09:43:57 +0200 Subject: [PATCH 209/230] fix: show persona chat only for onboarded by persona users --- src/libs/ReportUtils.ts | 14 ++------------ src/libs/SidebarUtils.ts | 7 +++++++ 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 6cade245b067..4de976aaf160 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -5347,6 +5347,8 @@ function shouldReportBeInOptionList({ report?.reportName === undefined || // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing report?.isHidden || + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + participantAccountIDs.includes(CONST.ACCOUNT_ID.NOTIFICATIONS) || (participantAccountIDs.length === 0 && !isChatThread(report) && !isPublicRoom(report) && @@ -5435,17 +5437,6 @@ function shouldReportBeInOptionList({ return true; } -/** - * Returns the system report from the list of reports. - */ -function getSystemChat(): OnyxEntry { - if (!allReports) { - return null; - } - - return Object.values(allReports ?? {}).find((report) => report?.chatType === CONST.REPORT.CHAT_TYPE.SYSTEM) ?? null; -} - /** * Attempts to find a report in onyx with the provided list of participants. Does not include threads, task, expense, room, and policy expense chat. */ @@ -7067,7 +7058,6 @@ export { getRoomWelcomeMessage, getRootParentReport, getRouteFromLink, - getSystemChat, getTaskAssigneeChatOnyxData, getTransactionDetails, getTransactionReportName, diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 3df823db22df..119bbc3ba12a 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -12,6 +12,7 @@ import type PriorityMode from '@src/types/onyx/PriorityMode'; import type Report from '@src/types/onyx/Report'; import type ReportAction from '@src/types/onyx/ReportAction'; import type DeepValueOf from '@src/types/utils/DeepValueOf'; +import AccountUtils from './AccountUtils'; import * as CollectionUtils from './CollectionUtils'; import {hasValidDraftComment} from './DraftCommentUtils'; import localeCompare from './LocaleCompare'; @@ -104,6 +105,12 @@ function getOrderedReportIDs( return false; } + const participantAccountIDs = Object.keys(report?.participants ?? {}).map(Number); + + if (currentUserAccountID && AccountUtils.isAccountIDOddNumber(currentUserAccountID) && participantAccountIDs.includes(CONST.ACCOUNT_ID.NOTIFICATIONS)) { + return true; + } + return ReportUtils.shouldReportBeInOptionList({ report, currentReportId: currentReportId ?? '', From 774a19678a0c6a8747fd6876f88f6e2b67757a84 Mon Sep 17 00:00:00 2001 From: Daniel Silva Date: Tue, 11 Jun 2024 13:51:44 +0200 Subject: [PATCH 210/230] subscribe to onyx for lastUpdateID key --- src/libs/API/index.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/libs/API/index.ts b/src/libs/API/index.ts index bfa1b95836f8..427a77fee261 100644 --- a/src/libs/API/index.ts +++ b/src/libs/API/index.ts @@ -11,6 +11,7 @@ import type OnyxRequest from '@src/types/onyx/Request'; import type Response from '@src/types/onyx/Response'; import pkg from '../../../package.json'; import type {ApiRequest, ApiRequestCommandParameters, ReadCommand, SideEffectRequestCommand, WriteCommand} from './types'; +import ONYXKEYS from '@src/ONYXKEYS'; // Setup API middlewares. Each request made will pass through a series of middleware functions that will get called in sequence (each one passing the result of the previous to the next). // Note: The ordering here is intentional as we want to Log, Recheck Connection, Reauthenticate, and Save the Response in Onyx. Errors thrown in one middleware will bubble to the next. @@ -39,6 +40,14 @@ type OnyxData = { finallyData?: OnyxUpdate[]; }; +// For all write requests, we'll send the lastUpdateID that is applied to this client. This will +// allow us to calculate previousUpdateID faster. +let lastUpdateIDAppliedToClient = 0; +Onyx.connect({ + key: ONYXKEYS.ONYX_UPDATES_LAST_UPDATE_ID_APPLIED_TO_CLIENT, + callback: (value) => (lastUpdateIDAppliedToClient = value ?? 0), +}); + /** * All calls to API.write() will be persisted to disk as JSON with the params, successData, and failureData (or finallyData, if included in place of the former two values). * This is so that if the network is unavailable or the app is closed, we can send the WRITE request later. From dd6bbb22817c85d7a39076d470686a13e7bcf4aa Mon Sep 17 00:00:00 2001 From: Daniel Silva Date: Tue, 11 Jun 2024 13:52:39 +0200 Subject: [PATCH 211/230] adding parameter on write and makeRequestWithSideEffects --- src/libs/API/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libs/API/index.ts b/src/libs/API/index.ts index 427a77fee261..20d0a47a09f8 100644 --- a/src/libs/API/index.ts +++ b/src/libs/API/index.ts @@ -91,6 +91,7 @@ function write(command: TCommand, apiCommandParam // This should be removed once we are no longer using deprecatedAPI https://github.com/Expensify/Expensify/issues/215650 shouldRetry: true, canCancel: true, + clientUpdateID: lastUpdateIDAppliedToClient, }, ...onyxDataWithoutOptimisticData, }; @@ -139,6 +140,7 @@ function makeRequestWithSideEffects Date: Tue, 11 Jun 2024 13:59:50 +0200 Subject: [PATCH 212/230] prettier --- src/libs/API/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/API/index.ts b/src/libs/API/index.ts index 20d0a47a09f8..fc4585b9ef68 100644 --- a/src/libs/API/index.ts +++ b/src/libs/API/index.ts @@ -7,11 +7,11 @@ import * as Pusher from '@libs/Pusher/pusher'; import * as Request from '@libs/Request'; import * as PersistedRequests from '@userActions/PersistedRequests'; import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; import type OnyxRequest from '@src/types/onyx/Request'; import type Response from '@src/types/onyx/Response'; import pkg from '../../../package.json'; import type {ApiRequest, ApiRequestCommandParameters, ReadCommand, SideEffectRequestCommand, WriteCommand} from './types'; -import ONYXKEYS from '@src/ONYXKEYS'; // Setup API middlewares. Each request made will pass through a series of middleware functions that will get called in sequence (each one passing the result of the previous to the next). // Note: The ordering here is intentional as we want to Log, Recheck Connection, Reauthenticate, and Save the Response in Onyx. Errors thrown in one middleware will bubble to the next. From 5dc7f6c0f3d243d39fda45c97021e76a7a99d026 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 11 Jun 2024 14:21:29 +0200 Subject: [PATCH 213/230] fix: lint and typecheck --- src/libs/ReportUtils.ts | 12 ++++++++++++ src/libs/actions/Report.ts | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 4de976aaf160..740ed75b7e1f 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -5437,6 +5437,17 @@ function shouldReportBeInOptionList({ return true; } +/** + * Returns the system report from the list of reports. + */ +function getSystemChat(): OnyxEntry { + if (!allReports) { + return null; + } + + return Object.values(allReports ?? {}).find((report) => report?.chatType === CONST.REPORT.CHAT_TYPE.SYSTEM) ?? null; +} + /** * Attempts to find a report in onyx with the provided list of participants. Does not include threads, task, expense, room, and policy expense chat. */ @@ -7058,6 +7069,7 @@ export { getRoomWelcomeMessage, getRootParentReport, getRouteFromLink, + getSystemChat, getTaskAssigneeChatOnyxData, getTransactionDetails, getTransactionReportName, diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index daf1e5c29d81..46845dce8a1e 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -2014,7 +2014,7 @@ function navigateToConciergeChat(shouldDismissModal = false, checkIfCurrentPageA function navigateToSystemChat() { const systemChatReport = ReportUtils.getSystemChat(); - if (systemChatReport && systemChatReport.reportID) { + if (systemChatReport?.reportID) { Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(systemChatReport.reportID)); } } From a565567e0692fc698564ab2984a8718f58b4bc20 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Tue, 11 Jun 2024 14:21:31 +0200 Subject: [PATCH 214/230] Add multiple search bottom tabs --- src/libs/Navigation/linkTo/index.ts | 5 +++-- .../linkingConfig/getMatchingBottomTabRouteForState.ts | 6 +++++- src/libs/Navigation/types.ts | 8 +++++++- src/pages/Search/useCustomBackHandler/index.android.ts | 10 +--------- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/libs/Navigation/linkTo/index.ts b/src/libs/Navigation/linkTo/index.ts index 313f525f390b..b1c5d072cb64 100644 --- a/src/libs/Navigation/linkTo/index.ts +++ b/src/libs/Navigation/linkTo/index.ts @@ -1,6 +1,6 @@ import {getActionFromState} from '@react-navigation/core'; -import {findFocusedRoute} from '@react-navigation/native'; import type {NavigationContainerRef, NavigationState, PartialState} from '@react-navigation/native'; +import {findFocusedRoute} from '@react-navigation/native'; import {omitBy} from 'lodash'; import getIsNarrowLayout from '@libs/getIsNarrowLayout'; import extractPolicyIDsFromState from '@libs/Navigation/linkingConfig/extractPolicyIDsFromState'; @@ -86,9 +86,10 @@ export default function linkTo(navigation: NavigationContainerRef)?.policyID !== (matchingBottomTabRoute?.params as Record)?.policyID; - if (topmostBottomTabRoute && (topmostBottomTabRoute.name !== matchingBottomTabRoute.name || isNewPolicyID)) { + if (topmostBottomTabRoute && (topmostBottomTabRoute.name !== matchingBottomTabRoute.name || isNewPolicyID || matchingBottomTabRoute.name === SCREENS.SEARCH.BOTTOM_TAB)) { root.dispatch({ type: CONST.NAVIGATION.ACTION_TYPE.PUSH, payload: matchingBottomTabRoute, diff --git a/src/libs/Navigation/linkingConfig/getMatchingBottomTabRouteForState.ts b/src/libs/Navigation/linkingConfig/getMatchingBottomTabRouteForState.ts index fd45685acf23..7fac455f2c5a 100644 --- a/src/libs/Navigation/linkingConfig/getMatchingBottomTabRouteForState.ts +++ b/src/libs/Navigation/linkingConfig/getMatchingBottomTabRouteForState.ts @@ -6,7 +6,7 @@ import {CENTRAL_PANE_TO_TAB_MAPPING} from './TAB_TO_CENTRAL_PANE_MAPPING'; // Get the route that matches the topmost central pane route in the navigation stack. e.g REPORT -> HOME function getMatchingBottomTabRouteForState(state: State, policyID?: string): NavigationPartialRoute { - const paramsWithPolicyID = policyID ? {policyID} : undefined; + let paramsWithPolicyID = policyID ? {policyID} : undefined; const defaultRoute = {name: SCREENS.HOME, params: paramsWithPolicyID}; const isFullScreenNavigatorOpened = state.routes.some((route) => route.name === NAVIGATORS.FULL_SCREEN_NAVIGATOR); @@ -21,6 +21,10 @@ function getMatchingBottomTabRouteForState(state: State, pol } const tabName = CENTRAL_PANE_TO_TAB_MAPPING[topmostCentralPaneRoute.name]; + + if (tabName === SCREENS.SEARCH.BOTTOM_TAB) { + paramsWithPolicyID = {...topmostCentralPaneRoute.params, ...paramsWithPolicyID}; + } return {name: tabName, params: paramsWithPolicyID}; } diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 5597e7ce00da..e87f0380a2da 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -854,7 +854,13 @@ type WelcomeVideoModalNavigatorParamList = { type BottomTabNavigatorParamList = { [SCREENS.HOME]: {policyID?: string}; - [SCREENS.SEARCH.BOTTOM_TAB]: {policyID?: string}; + [SCREENS.SEARCH.BOTTOM_TAB]: { + query: string; + policyID?: string; + offset?: number; + sortBy?: SearchColumnType; + sortOrder?: SortOrder; + }; [SCREENS.SETTINGS.ROOT]: {policyID?: string}; }; diff --git a/src/pages/Search/useCustomBackHandler/index.android.ts b/src/pages/Search/useCustomBackHandler/index.android.ts index f168cb0b9008..ebf2525eef98 100644 --- a/src/pages/Search/useCustomBackHandler/index.android.ts +++ b/src/pages/Search/useCustomBackHandler/index.android.ts @@ -1,11 +1,8 @@ import {StackActions, useFocusEffect} from '@react-navigation/native'; import {useCallback} from 'react'; import {BackHandler} from 'react-native'; -import getTopmostCentralPaneRoute from '@libs/Navigation/getTopmostCentralPaneRoute'; import navigationRef from '@libs/Navigation/navigationRef'; -import type {RootStackParamList, State} from '@libs/Navigation/types'; import NAVIGATORS from '@src/NAVIGATORS'; -import SCREENS from '@src/SCREENS'; // We need to make sure that the central pane screen and bottom tab won't be desynchronized after using the physical back button on Android. // To achieve that we will call additional POP on the bottom tab navigator if the search page would disappear from the central pane. @@ -14,13 +11,8 @@ function useCustomBackHandler() { useCallback(() => { const onBackPress = () => { const rootState = navigationRef.getRootState(); - const bottomTabRoute = rootState.routes.find((route) => route.name === NAVIGATORS.BOTTOM_TAB_NAVIGATOR); - const centralPaneRouteAfterPop = getTopmostCentralPaneRoute({routes: [rootState.routes.at(-2)]} as State); - - if (bottomTabRoute && bottomTabRoute.state && (!centralPaneRouteAfterPop || centralPaneRouteAfterPop.name !== SCREENS.SEARCH.CENTRAL_PANE)) { - navigationRef.dispatch({...StackActions.pop(), target: bottomTabRoute.state.key}); - } + navigationRef.dispatch({...StackActions.pop(), target: bottomTabRoute?.state?.key}); return false; }; From b04f8f73db6d0fa46db33af200dd27fe24c19822 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Tue, 11 Jun 2024 14:38:20 +0200 Subject: [PATCH 215/230] Refactor getMatchingBottomTabRouteForState --- src/libs/Navigation/linkTo/index.ts | 4 +++- .../linkingConfig/getMatchingBottomTabRouteForState.ts | 9 +++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/libs/Navigation/linkTo/index.ts b/src/libs/Navigation/linkTo/index.ts index b1c5d072cb64..bd304ec28abe 100644 --- a/src/libs/Navigation/linkTo/index.ts +++ b/src/libs/Navigation/linkTo/index.ts @@ -89,7 +89,9 @@ export default function linkTo(navigation: NavigationContainerRef)?.policyID !== (matchingBottomTabRoute?.params as Record)?.policyID; - if (topmostBottomTabRoute && (topmostBottomTabRoute.name !== matchingBottomTabRoute.name || isNewPolicyID || matchingBottomTabRoute.name === SCREENS.SEARCH.BOTTOM_TAB)) { + const isOpeningSearch = matchingBottomTabRoute.name === SCREENS.SEARCH.BOTTOM_TAB; + + if (topmostBottomTabRoute && (topmostBottomTabRoute.name !== matchingBottomTabRoute.name || isNewPolicyID || isOpeningSearch)) { root.dispatch({ type: CONST.NAVIGATION.ACTION_TYPE.PUSH, payload: matchingBottomTabRoute, diff --git a/src/libs/Navigation/linkingConfig/getMatchingBottomTabRouteForState.ts b/src/libs/Navigation/linkingConfig/getMatchingBottomTabRouteForState.ts index 7fac455f2c5a..4b4ed25959f0 100644 --- a/src/libs/Navigation/linkingConfig/getMatchingBottomTabRouteForState.ts +++ b/src/libs/Navigation/linkingConfig/getMatchingBottomTabRouteForState.ts @@ -6,7 +6,7 @@ import {CENTRAL_PANE_TO_TAB_MAPPING} from './TAB_TO_CENTRAL_PANE_MAPPING'; // Get the route that matches the topmost central pane route in the navigation stack. e.g REPORT -> HOME function getMatchingBottomTabRouteForState(state: State, policyID?: string): NavigationPartialRoute { - let paramsWithPolicyID = policyID ? {policyID} : undefined; + const paramsWithPolicyID = policyID ? {policyID} : undefined; const defaultRoute = {name: SCREENS.HOME, params: paramsWithPolicyID}; const isFullScreenNavigatorOpened = state.routes.some((route) => route.name === NAVIGATORS.FULL_SCREEN_NAVIGATOR); @@ -23,7 +23,12 @@ function getMatchingBottomTabRouteForState(state: State, pol const tabName = CENTRAL_PANE_TO_TAB_MAPPING[topmostCentralPaneRoute.name]; if (tabName === SCREENS.SEARCH.BOTTOM_TAB) { - paramsWithPolicyID = {...topmostCentralPaneRoute.params, ...paramsWithPolicyID}; + const topmostCentralPaneRouteParams = topmostCentralPaneRoute.params as Record; + delete topmostCentralPaneRouteParams?.policyIDs; + if (policyID) { + topmostCentralPaneRouteParams.policyID = policyID; + } + return {name: tabName, params: topmostCentralPaneRouteParams}; } return {name: tabName, params: paramsWithPolicyID}; } From e393f8525073d29ce4e3d83e812dbad3ec987a6e Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Tue, 11 Jun 2024 15:57:34 +0200 Subject: [PATCH 216/230] update usePreferredCurrency hook --- src/hooks/usePreferredCurrency.ts | 6 +++--- src/types/onyx/BankAccount.ts | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/hooks/usePreferredCurrency.ts b/src/hooks/usePreferredCurrency.ts index 79f4057c384e..c801d8007749 100644 --- a/src/hooks/usePreferredCurrency.ts +++ b/src/hooks/usePreferredCurrency.ts @@ -19,10 +19,10 @@ function usePreferredCurrency(): PreferredCurrency { const [session] = useOnyx(ONYXKEYS.SESSION); const [fundList] = useOnyx(ONYXKEYS.FUND_LIST); - const defaultCardCurrency = useMemo(() => Object.values(fundList ?? {}).find((card) => card.isDefault)?.accountData?.currency, [fundList]); + const paymentCardCurrency = useMemo(() => Object.values(fundList ?? {}).find((card) => card.accountData?.additionalData?.isBillingCard)?.accountData?.currency, [fundList]); - if (defaultCardCurrency) { - return defaultCardCurrency; + if (paymentCardCurrency) { + return paymentCardCurrency; } const currentUserLocalCurrency = (personalDetails?.[session?.accountID ?? '-1']?.localCurrencyCode ?? CONST.PAYMENT_CARD_CURRENCY.USD) as PreferredCurrency; diff --git a/src/types/onyx/BankAccount.ts b/src/types/onyx/BankAccount.ts index 862b5aa45a4e..e727ffc9c70e 100644 --- a/src/types/onyx/BankAccount.ts +++ b/src/types/onyx/BankAccount.ts @@ -22,6 +22,9 @@ type BankAccountAdditionalData = { /** In which country is the bank account */ country?: string; + + /** Is billing card */ + isBillingCard?: boolean; }; /** Model of bank account */ From 5ffec6fb6ba47639bb2ed60042084322a364c126 Mon Sep 17 00:00:00 2001 From: Yauheni Pasiukevich Date: Tue, 11 Jun 2024 16:30:06 +0200 Subject: [PATCH 217/230] update ui --- .../Subscription/CardSection/BillingBanner.tsx | 13 +++++++++---- .../Subscription/CardSection/CardSection.tsx | 10 ++++++++++ src/styles/index.ts | 4 ++++ src/styles/theme/themes/dark.ts | 1 + src/styles/theme/themes/light.ts | 1 + src/styles/theme/types.ts | 1 + 6 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/pages/settings/Subscription/CardSection/BillingBanner.tsx b/src/pages/settings/Subscription/CardSection/BillingBanner.tsx index 062ab5c8db58..163c43aa1359 100644 --- a/src/pages/settings/Subscription/CardSection/BillingBanner.tsx +++ b/src/pages/settings/Subscription/CardSection/BillingBanner.tsx @@ -13,22 +13,27 @@ type BillingBannerProps = { subtitle?: string; isError?: boolean; shouldShowRedDotIndicator?: boolean; + isTrialActive?: boolean; }; -function BillingBanner({title, subtitle, isError, shouldShowRedDotIndicator}: BillingBannerProps) { +function BillingBanner({title, subtitle, isError, shouldShowRedDotIndicator, isTrialActive}: BillingBannerProps) { const styles = useThemeStyles(); const theme = useTheme(); + const backgroundStyle = isTrialActive ? styles.trialBannerBackgroundColor : styles.hoveredComponentBG; + + const subtitleStyle = isTrialActive ? [] : styles.textSupporting; + return ( - + - {title && {title}} - {subtitle && {subtitle}} + {title && {title}} + {subtitle && {subtitle}} {isError && shouldShowRedDotIndicator && ( + } > {!isEmptyObject(defaultCard?.accountData) && ( diff --git a/src/styles/index.ts b/src/styles/index.ts index 7b49b90505ba..f4b8d0b0b91d 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -2848,6 +2848,10 @@ const styles = (theme: ThemeColors) => width: variables.iconSizeExtraLarge, }, + trialBannerBackgroundColor: { + backgroundColor: theme.trialBannerBackgroundColor, + }, + selectCircle: { width: variables.componentSizeSmall, height: variables.componentSizeSmall, diff --git a/src/styles/theme/themes/dark.ts b/src/styles/theme/themes/dark.ts index f0493a815747..418d45b8ab64 100644 --- a/src/styles/theme/themes/dark.ts +++ b/src/styles/theme/themes/dark.ts @@ -83,6 +83,7 @@ const darkTheme = { ourMentionBG: colors.green600, tooltipSupportingText: colors.productLight800, tooltipPrimaryText: colors.productLight900, + trialBannerBackgroundColor: colors.green700, skeletonLHNIn: colors.productDark400, skeletonLHNOut: colors.productDark600, QRLogo: colors.green400, diff --git a/src/styles/theme/themes/light.ts b/src/styles/theme/themes/light.ts index cf490a90a7f7..12f059738e13 100644 --- a/src/styles/theme/themes/light.ts +++ b/src/styles/theme/themes/light.ts @@ -83,6 +83,7 @@ const lightTheme = { ourMentionBG: colors.green100, tooltipSupportingText: colors.productDark800, tooltipPrimaryText: colors.productDark900, + trialBannerBackgroundColor: colors.green100, skeletonLHNIn: colors.productLight400, skeletonLHNOut: colors.productLight600, QRLogo: colors.green400, diff --git a/src/styles/theme/types.ts b/src/styles/theme/types.ts index 8be9f3226fe5..a388d06bc51e 100644 --- a/src/styles/theme/types.ts +++ b/src/styles/theme/types.ts @@ -85,6 +85,7 @@ type ThemeColors = { ourMentionBG: Color; tooltipSupportingText: Color; tooltipPrimaryText: Color; + trialBannerBackgroundColor: Color; skeletonLHNIn: Color; skeletonLHNOut: Color; QRLogo: Color; From 6b6937338c39e4a29a28c34081fdbade4a4ebf83 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Tue, 11 Jun 2024 08:31:02 -0600 Subject: [PATCH 218/230] fix keyboard/composer --- src/pages/home/ReportScreen.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index c557229aca72..5d3314a8ec9f 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -663,7 +663,7 @@ function ReportScreen({ Date: Tue, 11 Jun 2024 21:52:50 +0700 Subject: [PATCH 219/230] Fix list item skeleton displayed when ordering search result Signed-off-by: Tsaqif --- src/components/Search.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Search.tsx b/src/components/Search.tsx index 3d295e72c09e..ac9ec485af73 100644 --- a/src/components/Search.tsx +++ b/src/components/Search.tsx @@ -66,7 +66,7 @@ function Search({query, policyIDs, sortBy, sortOrder}: SearchProps) { }, [hash, isOffline]); const isLoadingItems = (!isOffline && isLoadingOnyxValue(searchResultsMeta)) || searchResults?.data === undefined; - const isLoadingMoreItems = !isLoadingItems && searchResults?.search?.isLoading; + const isLoadingMoreItems = !isLoadingItems && searchResults?.search?.isLoading && searchResults?.search?.offset > 0; const shouldShowEmptyState = !isLoadingItems && isEmptyObject(searchResults?.data); if (isLoadingItems) { From 06bdf11c4e06a6cd3a7a0a2144107627eba047fd Mon Sep 17 00:00:00 2001 From: OSBotify Date: Tue, 11 Jun 2024 14:54:54 +0000 Subject: [PATCH 220/230] Update version to 1.4.81-9 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- ios/NotificationServiceExtension/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index e243f65c9efb..cd0c4e34fa12 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -107,8 +107,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001048108 - versionName "1.4.81-8" + versionCode 1001048109 + versionName "1.4.81-9" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 54b78cd6289c..bdcec002251a 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.81.8 + 1.4.81.9 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 9f63f7ae9e98..643c5d7073f4 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.4.81.8 + 1.4.81.9 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index af2cb1e4a29c..4a6b512afc5f 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 1.4.81 CFBundleVersion - 1.4.81.8 + 1.4.81.9 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index 62ba83dbe357..7d3e59157afa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.81-8", + "version": "1.4.81-9", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.81-8", + "version": "1.4.81-9", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index fa80cd4defb3..599b9c7e438b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.81-8", + "version": "1.4.81-9", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From e736a6f1eaf6a001c4ce481151bd29329ffcccb4 Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Tue, 11 Jun 2024 18:29:47 +0200 Subject: [PATCH 221/230] fix linkTo and isNewPolicyID --- src/libs/Navigation/linkTo/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/Navigation/linkTo/index.ts b/src/libs/Navigation/linkTo/index.ts index bd304ec28abe..7d6d62b9a5aa 100644 --- a/src/libs/Navigation/linkTo/index.ts +++ b/src/libs/Navigation/linkTo/index.ts @@ -86,10 +86,10 @@ export default function linkTo(navigation: NavigationContainerRef)?.policyID !== (matchingBottomTabRoute?.params as Record)?.policyID; const isOpeningSearch = matchingBottomTabRoute.name === SCREENS.SEARCH.BOTTOM_TAB; + const isNewPolicyID = + ((topmostBottomTabRoute?.params as Record)?.policyID ?? '') !== + ((matchingBottomTabRoute?.params as Record)?.policyID ?? ''); if (topmostBottomTabRoute && (topmostBottomTabRoute.name !== matchingBottomTabRoute.name || isNewPolicyID || isOpeningSearch)) { root.dispatch({ From 5fde996b5e54d73aa01c3bdb1ba9a077b0861d94 Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Tue, 11 Jun 2024 18:31:08 +0200 Subject: [PATCH 222/230] remove old useCustomBackHandler --- src/pages/Search/SearchPage.tsx | 2 -- .../useCustomBackHandler/index.android.ts | 26 ------------------- .../Search/useCustomBackHandler/index.ts | 3 --- 3 files changed, 31 deletions(-) delete mode 100644 src/pages/Search/useCustomBackHandler/index.android.ts delete mode 100644 src/pages/Search/useCustomBackHandler/index.ts diff --git a/src/pages/Search/SearchPage.tsx b/src/pages/Search/SearchPage.tsx index 8decc6477e21..4e80a4a0b619 100644 --- a/src/pages/Search/SearchPage.tsx +++ b/src/pages/Search/SearchPage.tsx @@ -14,7 +14,6 @@ import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type {SearchQuery} from '@src/types/onyx/SearchResults'; import type IconAsset from '@src/types/utils/IconAsset'; -import useCustomBackHandler from './useCustomBackHandler'; type SearchPageProps = StackScreenProps; @@ -37,7 +36,6 @@ function SearchPage({route}: SearchPageProps) { const handleOnBackButtonPress = () => Navigation.goBack(ROUTES.SEARCH.getRoute(CONST.TAB_SEARCH.ALL)); // We need to override default back button behavior on Android because we need to pop two screens, from the central pane and from the bottom tab. - useCustomBackHandler(); // On small screens this page is not displayed, the configuration is in the file: src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.tsx // To avoid calling hooks in the Search component when this page isn't visible, we return null here. diff --git a/src/pages/Search/useCustomBackHandler/index.android.ts b/src/pages/Search/useCustomBackHandler/index.android.ts deleted file mode 100644 index ebf2525eef98..000000000000 --- a/src/pages/Search/useCustomBackHandler/index.android.ts +++ /dev/null @@ -1,26 +0,0 @@ -import {StackActions, useFocusEffect} from '@react-navigation/native'; -import {useCallback} from 'react'; -import {BackHandler} from 'react-native'; -import navigationRef from '@libs/Navigation/navigationRef'; -import NAVIGATORS from '@src/NAVIGATORS'; - -// We need to make sure that the central pane screen and bottom tab won't be desynchronized after using the physical back button on Android. -// To achieve that we will call additional POP on the bottom tab navigator if the search page would disappear from the central pane. -function useCustomBackHandler() { - useFocusEffect( - useCallback(() => { - const onBackPress = () => { - const rootState = navigationRef.getRootState(); - const bottomTabRoute = rootState.routes.find((route) => route.name === NAVIGATORS.BOTTOM_TAB_NAVIGATOR); - navigationRef.dispatch({...StackActions.pop(), target: bottomTabRoute?.state?.key}); - return false; - }; - - const subscription = BackHandler.addEventListener('hardwareBackPress', onBackPress); - - return () => subscription.remove(); - }, []), - ); -} - -export default useCustomBackHandler; diff --git a/src/pages/Search/useCustomBackHandler/index.ts b/src/pages/Search/useCustomBackHandler/index.ts deleted file mode 100644 index be753de818a6..000000000000 --- a/src/pages/Search/useCustomBackHandler/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -function useCustomBackHandler() {} - -export default useCustomBackHandler; From 67fed6a7f07f17a419921be58e744243e1975bd7 Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Tue, 11 Jun 2024 18:32:38 +0200 Subject: [PATCH 223/230] add new back handler for android --- src/libs/Navigation/NavigationRoot.tsx | 3 + .../index.android.ts | 68 +++++++++++++++++++ .../setupCustomAndroidBackHandler/index.ts | 4 ++ 3 files changed, 75 insertions(+) create mode 100644 src/libs/Navigation/setupCustomAndroidBackHandler/index.android.ts create mode 100644 src/libs/Navigation/setupCustomAndroidBackHandler/index.ts diff --git a/src/libs/Navigation/NavigationRoot.tsx b/src/libs/Navigation/NavigationRoot.tsx index 06a3dce8d59a..e4927c6d5f0d 100644 --- a/src/libs/Navigation/NavigationRoot.tsx +++ b/src/libs/Navigation/NavigationRoot.tsx @@ -18,6 +18,7 @@ import linkingConfig from './linkingConfig'; import customGetPathFromState from './linkingConfig/customGetPathFromState'; import getAdaptedStateFromPath from './linkingConfig/getAdaptedStateFromPath'; import Navigation, {navigationRef} from './Navigation'; +import setupCustomAndroidBackHandler from './setupCustomAndroidBackHandler'; import type {RootStackParamList} from './types'; type NavigationRootProps = { @@ -109,6 +110,8 @@ function NavigationRoot({authenticated, lastVisitedPath, initialUrl, onReady}: N useEffect(() => { if (firstRenderRef.current) { + setupCustomAndroidBackHandler(); + // we don't want to make the report back button go back to LHN if the user // started on the small screen so we don't set it on the first render // making it only work on consecutive changes of the screen size diff --git a/src/libs/Navigation/setupCustomAndroidBackHandler/index.android.ts b/src/libs/Navigation/setupCustomAndroidBackHandler/index.android.ts new file mode 100644 index 000000000000..ac272155289d --- /dev/null +++ b/src/libs/Navigation/setupCustomAndroidBackHandler/index.android.ts @@ -0,0 +1,68 @@ +import {findFocusedRoute, StackActions} from '@react-navigation/native'; +import type {StackScreenProps} from '@react-navigation/stack'; +import {BackHandler} from 'react-native'; +import getTopmostCentralPaneRoute from '@navigation/getTopmostCentralPaneRoute'; +import navigationRef from '@navigation/navigationRef'; +import type {BottomTabNavigatorParamList, RootStackParamList, State} from '@navigation/types'; +import NAVIGATORS from '@src/NAVIGATORS'; +import SCREENS from '@src/SCREENS'; + +type SearchPageProps = StackScreenProps; + +// We need to do some custom handling for the back button on Android for actions related to the search page. +function setupCustomAndroidBackHandler() { + const onBackPress = () => { + const rootState = navigationRef.getRootState(); + + const bottomTabRoute = rootState.routes.find((route) => route.name === NAVIGATORS.BOTTOM_TAB_NAVIGATOR); + const bottomTabRoutes = bottomTabRoute?.state?.routes; + const focusedRoute = findFocusedRoute(rootState); + + // Shoudn't happen but for type safety. + if (!bottomTabRoutes) { + return false; + } + + // Handle back press on the search page. + // We need to pop two screens, from the central pane and from the bottom tab. + if (bottomTabRoutes[bottomTabRoutes.length - 1].name === SCREENS.SEARCH.BOTTOM_TAB && focusedRoute?.name === SCREENS.SEARCH.CENTRAL_PANE) { + navigationRef.dispatch({...StackActions.pop(), target: bottomTabRoute?.state?.key}); + navigationRef.dispatch({...StackActions.pop()}); + + const centralPaneRouteAfterPop = getTopmostCentralPaneRoute({routes: [rootState.routes.at(-2)]} as State); + const bottomTabRouteAfterPop = bottomTabRoutes.at(-2); + + // It's possible that central pane search is desynchronized with the bottom tab search. + // e.g. opening a tab different than search will wipe out central pane screens. + // In that case we have to push the proper one. + if ( + bottomTabRouteAfterPop && + bottomTabRouteAfterPop.name === SCREENS.SEARCH.BOTTOM_TAB && + (!centralPaneRouteAfterPop || centralPaneRouteAfterPop.name !== SCREENS.SEARCH.CENTRAL_PANE) + ) { + const {policyID, ...restParams} = bottomTabRoutes[bottomTabRoutes.length - 2].params as SearchPageProps['route']['params']; + navigationRef.dispatch({...StackActions.push(NAVIGATORS.CENTRAL_PANE_NAVIGATOR, {screen: SCREENS.SEARCH.CENTRAL_PANE, params: {...restParams, policyIDs: policyID}})}); + } + + return true; + } + + // Handle back press to go back to the search page. + // It's possible that central pane search is desynchronized with the bottom tab search. + // e.g. opening a tab different than search will wipe out central pane screens. + // In that case we have to push the proper one. + if (bottomTabRoutes && bottomTabRoutes?.length >= 2 && bottomTabRoutes[bottomTabRoutes.length - 2].name === SCREENS.SEARCH.BOTTOM_TAB && rootState.routes.length === 1) { + const {policyID, ...restParams} = bottomTabRoutes[bottomTabRoutes.length - 2].params as SearchPageProps['route']['params']; + navigationRef.dispatch({...StackActions.push(NAVIGATORS.CENTRAL_PANE_NAVIGATOR, {screen: SCREENS.SEARCH.CENTRAL_PANE, params: {...restParams, policyIDs: policyID}})}); + navigationRef.dispatch({...StackActions.pop(), target: bottomTabRoute?.state?.key}); + return true; + } + + // Handle all other cases with default handler. + return false; + }; + + BackHandler.addEventListener('hardwareBackPress', onBackPress); +} + +export default setupCustomAndroidBackHandler; diff --git a/src/libs/Navigation/setupCustomAndroidBackHandler/index.ts b/src/libs/Navigation/setupCustomAndroidBackHandler/index.ts new file mode 100644 index 000000000000..aa9077e1220f --- /dev/null +++ b/src/libs/Navigation/setupCustomAndroidBackHandler/index.ts @@ -0,0 +1,4 @@ +// Do nothing for platforms different than Android. +function setupCustomAndroidBackHandler() {} + +export default setupCustomAndroidBackHandler; From b849a8ebe8b006bc1edad8f4bfdf27a144159edc Mon Sep 17 00:00:00 2001 From: Adam Grzybowski <67908363+adamgrzybowski@users.noreply.github.com> Date: Tue, 11 Jun 2024 18:59:30 +0200 Subject: [PATCH 224/230] Update src/pages/Search/SearchPage.tsx Co-authored-by: Carlos Martins --- src/pages/Search/SearchPage.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/Search/SearchPage.tsx b/src/pages/Search/SearchPage.tsx index 4e80a4a0b619..8f85c355c03c 100644 --- a/src/pages/Search/SearchPage.tsx +++ b/src/pages/Search/SearchPage.tsx @@ -35,7 +35,6 @@ function SearchPage({route}: SearchPageProps) { const handleOnBackButtonPress = () => Navigation.goBack(ROUTES.SEARCH.getRoute(CONST.TAB_SEARCH.ALL)); - // We need to override default back button behavior on Android because we need to pop two screens, from the central pane and from the bottom tab. // On small screens this page is not displayed, the configuration is in the file: src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.tsx // To avoid calling hooks in the Search component when this page isn't visible, we return null here. From 149847bd94855bef3ba4bdabb652827f2af32f57 Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Tue, 11 Jun 2024 19:03:21 +0200 Subject: [PATCH 225/230] remove empty line --- src/pages/Search/SearchPage.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/Search/SearchPage.tsx b/src/pages/Search/SearchPage.tsx index 8f85c355c03c..f7b21592b090 100644 --- a/src/pages/Search/SearchPage.tsx +++ b/src/pages/Search/SearchPage.tsx @@ -35,7 +35,6 @@ function SearchPage({route}: SearchPageProps) { const handleOnBackButtonPress = () => Navigation.goBack(ROUTES.SEARCH.getRoute(CONST.TAB_SEARCH.ALL)); - // On small screens this page is not displayed, the configuration is in the file: src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.tsx // To avoid calling hooks in the Search component when this page isn't visible, we return null here. if (isSmallScreenWidth) { From 270248e01b25f3485577bcf28e9eb883f297efb7 Mon Sep 17 00:00:00 2001 From: OSBotify Date: Tue, 11 Jun 2024 17:40:39 +0000 Subject: [PATCH 226/230] Update version to 1.4.81-10 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- ios/NotificationServiceExtension/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index cd0c4e34fa12..e4e5479744ad 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -107,8 +107,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001048109 - versionName "1.4.81-9" + versionCode 1001048110 + versionName "1.4.81-10" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index bdcec002251a..d74d7bbe5145 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.81.9 + 1.4.81.10 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 643c5d7073f4..914940d7514f 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.4.81.9 + 1.4.81.10 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 4a6b512afc5f..ec16d0661750 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 1.4.81 CFBundleVersion - 1.4.81.9 + 1.4.81.10 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index 7d3e59157afa..710fefa342ac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.81-9", + "version": "1.4.81-10", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.81-9", + "version": "1.4.81-10", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 599b9c7e438b..0209c75f4829 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.81-9", + "version": "1.4.81-10", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From 81caf9878c4edf205bdb7f3df5b8fbb45220537e Mon Sep 17 00:00:00 2001 From: OSBotify Date: Tue, 11 Jun 2024 17:50:53 +0000 Subject: [PATCH 227/230] Update version to 1.4.81-11 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- ios/NotificationServiceExtension/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index e4e5479744ad..50436cb8dabd 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -107,8 +107,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001048110 - versionName "1.4.81-10" + versionCode 1001048111 + versionName "1.4.81-11" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index d74d7bbe5145..8c78ca548d67 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.81.10 + 1.4.81.11 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 914940d7514f..ac8e9f4a1cb7 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.4.81.10 + 1.4.81.11 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index ec16d0661750..41eaa2953efb 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 1.4.81 CFBundleVersion - 1.4.81.10 + 1.4.81.11 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index 710fefa342ac..71049ab15f26 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.81-10", + "version": "1.4.81-11", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.81-10", + "version": "1.4.81-11", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 0209c75f4829..c4f89e2f1421 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.81-10", + "version": "1.4.81-11", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From 1baaae034c150e41279c3329ea39e8891367d9d8 Mon Sep 17 00:00:00 2001 From: Yauheni Pasiukevich Date: Tue, 11 Jun 2024 19:51:08 +0200 Subject: [PATCH 228/230] remove dummy code --- .../settings/Subscription/CardSection/CardSection.tsx | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/pages/settings/Subscription/CardSection/CardSection.tsx b/src/pages/settings/Subscription/CardSection/CardSection.tsx index 43411737604f..da65c9d74f07 100644 --- a/src/pages/settings/Subscription/CardSection/CardSection.tsx +++ b/src/pages/settings/Subscription/CardSection/CardSection.tsx @@ -11,7 +11,6 @@ import useThemeStyles from '@hooks/useThemeStyles'; import DateUtils from '@libs/DateUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; -import BillingBanner from './BillingBanner'; import CardSectionActions from './CardSectionActions'; import CardSectionDataEmpty from './CardSectionDataEmpty'; @@ -32,15 +31,6 @@ function CardSection() { isCentralPane titleStyles={styles.textStrong} subtitleMuted - banner={ - - } > {!isEmptyObject(defaultCard?.accountData) && ( From c1da163fee63644fa5b95918de8e76e1f25ec8db Mon Sep 17 00:00:00 2001 From: James Dean Date: Tue, 11 Jun 2024 12:02:30 -0700 Subject: [PATCH 229/230] Update es.ts --- src/languages/es.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index a6d317c623e0..80bf199d4a6b 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2537,7 +2537,7 @@ export default { VBANoECardCopy: 'Añade tu correo electrónico de trabajo para emitir Tarjetas Expensify ilimitadas para los miembros de tu espacio de trabajo y acceder a todas estas increíbles ventajas:', VBAWithECardCopy: 'Acceda a estos increíbles beneficios y más:', - benefit1: 'Hasta un 2% de devolución en tus gastos', + benefit1: 'Devolución de dinero en cada compra en Estados Unidos', benefit2: 'Tarjetas digitales y físicas', benefit3: 'Sin responsabilidad personal', benefit4: 'Límites personalizables', From 8ed1e042af77ac0674941db9c80b67979426f7e8 Mon Sep 17 00:00:00 2001 From: OSBotify Date: Tue, 11 Jun 2024 21:53:32 +0000 Subject: [PATCH 230/230] Update version to 1.4.82-0 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 4 ++-- ios/NewExpensifyTests/Info.plist | 4 ++-- ios/NotificationServiceExtension/Info.plist | 4 ++-- package-lock.json | 4 ++-- package.json | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 50436cb8dabd..a450714056fb 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -107,8 +107,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001048111 - versionName "1.4.81-11" + versionCode 1001048200 + versionName "1.4.82-0" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 8c78ca548d67..5a5c622228d7 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.4.81 + 1.4.82 CFBundleSignature ???? CFBundleURLTypes @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.81.11 + 1.4.82.0 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index ac8e9f4a1cb7..29ca21528b16 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.4.81 + 1.4.82 CFBundleSignature ???? CFBundleVersion - 1.4.81.11 + 1.4.82.0 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 41eaa2953efb..c3fd5ca6f44e 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -11,9 +11,9 @@ CFBundleName $(PRODUCT_NAME) CFBundleShortVersionString - 1.4.81 + 1.4.82 CFBundleVersion - 1.4.81.11 + 1.4.82.0 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index 71049ab15f26..6aa6334cc94e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.81-11", + "version": "1.4.82-0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.81-11", + "version": "1.4.82-0", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index c4f89e2f1421..8c4e878882f4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.81-11", + "version": "1.4.82-0", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.",