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/130] 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 e343aa68acc529461e7612572b753169ee736c0e Mon Sep 17 00:00:00 2001 From: burczu Date: Mon, 22 Apr 2024 11:24:12 +0200 Subject: [PATCH 002/130] 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 003/130] 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 004/130] 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 005/130] 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 006/130] 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 0410d8ff2bb5ac120aeb3bf9fe5d98a5b579132f Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 30 Apr 2024 16:01:49 +0200 Subject: [PATCH 007/130] 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 008/130] 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 009/130] 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 010/130] 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 011/130] 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 012/130] 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 013/130] 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 014/130] 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 5590c17316a5ed71394d2dddad65b1ab13708a03 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 10 May 2024 12:20:17 +0200 Subject: [PATCH 015/130] 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 016/130] 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 017/130] 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 8f019fd2507569145f0fcf179c767d46d249e3fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barbara=20Gawe=C5=82-Kucab?= Date: Wed, 22 May 2024 15:11:28 +0200 Subject: [PATCH 018/130] enable no-unsafe-call rule --- .eslintrc.js | 1 - .../javascript/getGraphiteString/getGraphiteString.ts | 3 ++- .../ComposerWithSuggestions/ComposerWithSuggestions.tsx | 6 +++++- tests/unit/markPullRequestsAsDeployedTest.ts | 2 +- workflow_tests/utils/ExtendedAct.ts | 3 ++- workflow_tests/utils/preGenerateTest.ts | 3 ++- 6 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index eb50d1fcc5f3..2cf5ba68aa9a 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -100,7 +100,6 @@ module.exports = { __DEV__: 'readonly', }, rules: { - '@typescript-eslint/no-unsafe-call': 'off', '@typescript-eslint/no-unsafe-member-access': 'off', '@typescript-eslint/no-unsafe-assignment': 'off', diff --git a/.github/actions/javascript/getGraphiteString/getGraphiteString.ts b/.github/actions/javascript/getGraphiteString/getGraphiteString.ts index c486fdbd39f3..15b5e885c921 100644 --- a/.github/actions/javascript/getGraphiteString/getGraphiteString.ts +++ b/.github/actions/javascript/getGraphiteString/getGraphiteString.ts @@ -32,7 +32,8 @@ const run = () => { } if (current.name && current.meanDuration && current.meanCount && timestamp) { - const formattedName = current.name.split(' ').join('-'); + const currentName = current.name as string; + const formattedName = currentName.split(' ').join('-'); const renderDurationString = `${GRAPHITE_PATH}.${formattedName}.renderDuration ${current.meanDuration} ${timestamp}`; const renderCountString = `${GRAPHITE_PATH}.${formattedName}.renderCount ${current.meanCount} ${timestamp}`; diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx index 0b585de8f059..1a490a26867a 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx @@ -182,7 +182,11 @@ type SwitchToCurrentReportProps = { callback: () => void; }; -const {RNTextInputReset} = NativeModules; +type RNTextInputResetProps = { + resetKeyboardInput: (nodeHandle: number | null) => void; +}; + +const RNTextInputReset: RNTextInputResetProps = NativeModules.RNTextInputReset; const isIOSNative = getPlatform() === CONST.PLATFORM.IOS; diff --git a/tests/unit/markPullRequestsAsDeployedTest.ts b/tests/unit/markPullRequestsAsDeployedTest.ts index 24a6733d1244..8d8b25968a28 100644 --- a/tests/unit/markPullRequestsAsDeployedTest.ts +++ b/tests/unit/markPullRequestsAsDeployedTest.ts @@ -35,7 +35,7 @@ type CommitData = { }; }; -let run; +let run: () => Promise; const mockGetInput = jest.fn(); const mockGetPullRequest = jest.fn(); diff --git a/workflow_tests/utils/ExtendedAct.ts b/workflow_tests/utils/ExtendedAct.ts index e2bb12ec8e01..7b35eb260ba3 100644 --- a/workflow_tests/utils/ExtendedAct.ts +++ b/workflow_tests/utils/ExtendedAct.ts @@ -17,7 +17,8 @@ type ActOptions = { // @ts-expect-error Override shouldn't be done on private methods wait until https://github.com/kiegroup/act-js/issues/77 is resolved or try to create a params workaround class ExtendedAct extends Act { async parseRunOpts(opts?: ExtendedActOpts): Promise { - const {cwd, actArguments, proxy} = await super['parseRunOpts'](opts); + const parseSuperRunOpts: (opts?: ExtendedActOpts) => Promise = super['parseRunOpts']; + const {cwd, actArguments, proxy} = await parseSuperRunOpts(opts); if (opts?.actor) { actArguments.push('--actor', opts.actor); diff --git a/workflow_tests/utils/preGenerateTest.ts b/workflow_tests/utils/preGenerateTest.ts index 1e7e7bb04184..eb1d2e8f9b69 100644 --- a/workflow_tests/utils/preGenerateTest.ts +++ b/workflow_tests/utils/preGenerateTest.ts @@ -219,7 +219,8 @@ const getMockFileContent = (workflowName: string, jobs: Record { - const stepMockName = `${workflowName.toUpperCase()}__${jobId.toUpperCase()}__${step.name + const stepName = step.name as string; + const stepMockName = `${workflowName.toUpperCase()}__${jobId.toUpperCase()}__${stepName .replaceAll(' ', '_') .replaceAll('-', '_') .replaceAll(',', '') From 549e647063a14d8435aaf5dd2df13e930e9b929d Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Fri, 24 May 2024 12:32:38 +0200 Subject: [PATCH 019/130] 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 020/130] 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 021/130] 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 022/130] 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: Mon, 27 May 2024 17:23:10 +0200 Subject: [PATCH 023/130] fix all text input ref errors --- src/components/AmountForm.tsx | 10 +++++++--- src/components/EmojiPicker/EmojiPickerMenu/index.tsx | 10 +++++++--- src/components/MoneyRequestAmountInput.tsx | 6 +++++- src/pages/iou/MoneyRequestAmountForm.tsx | 11 ++++++++--- src/pages/signin/LoginForm/BaseLoginForm.tsx | 6 +++++- 5 files changed, 32 insertions(+), 11 deletions(-) diff --git a/src/components/AmountForm.tsx b/src/components/AmountForm.tsx index a102b715d526..f5d460ed5caa 100644 --- a/src/components/AmountForm.tsx +++ b/src/components/AmountForm.tsx @@ -12,6 +12,7 @@ import * as MoneyRequestUtils from '@libs/MoneyRequestUtils'; import CONST from '@src/CONST'; import BigNumberPad from './BigNumberPad'; import FormHelpMessage from './FormHelpMessage'; +import type {AnimatedTextInputRef} from './RNTextInput'; import type {BaseTextInputProps, BaseTextInputRef} from './TextInput/BaseTextInput/types'; import TextInputWithCurrencySymbol from './TextInputWithCurrencySymbol'; import type TextInputWithCurrencySymbolProps from './TextInputWithCurrencySymbol/types'; @@ -51,6 +52,9 @@ const getNewSelection = (oldSelection: {start: number; end: number}, prevLength: return {start: cursorPosition, end: cursorPosition}; }; +const isAnimatedTextInputRef = (textInput: React.MutableRefObject) => + textInput.current && 'isFocused' in textInput.current && (textInput.current as AnimatedTextInputRef).isFocused(); + const AMOUNT_VIEW_ID = 'amountView'; const NUM_PAD_CONTAINER_VIEW_ID = 'numPadContainerView'; const NUM_PAD_VIEW_ID = 'numPadView'; @@ -94,7 +98,7 @@ function AmountForm( if (!textInput.current) { return; } - if (!textInput.current.isFocused()) { + if (!isAnimatedTextInputRef(textInput)) { textInput.current.focus(); } }; @@ -143,7 +147,7 @@ function AmountForm( */ const updateAmountNumberPad = useCallback( (key: string) => { - if (shouldUpdateSelection && !textInput.current?.isFocused()) { + if (shouldUpdateSelection && !isAnimatedTextInputRef(textInput)) { textInput.current?.focus(); } // Backspace button is pressed @@ -168,7 +172,7 @@ function AmountForm( */ const updateLongPressHandlerState = useCallback((value: boolean) => { setShouldUpdateSelection(!value); - if (!value && !textInput.current?.isFocused()) { + if (!value && !isAnimatedTextInputRef(textInput)) { textInput.current?.focus(); } }, []); diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.tsx b/src/components/EmojiPicker/EmojiPickerMenu/index.tsx index b8c31a61ecbf..87cd50bd3cdb 100755 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.tsx @@ -5,6 +5,7 @@ import type {ForwardedRef} from 'react'; import {View} from 'react-native'; import {scrollTo} from 'react-native-reanimated'; import EmojiPickerMenuItem from '@components/EmojiPicker/EmojiPickerMenuItem'; +import type {AnimatedTextInputRef} from '@components/RNTextInput'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types'; @@ -27,6 +28,9 @@ import useEmojiPickerMenu from './useEmojiPickerMenu'; const throttleTime = Browser.isMobile() ? 200 : 50; +const isAnimatedTextInputRef = (textInput: React.MutableRefObject) => + textInput.current && 'isFocused' in textInput.current && (textInput.current as AnimatedTextInputRef).isFocused(); + function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps, ref: ForwardedRef) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -84,7 +88,7 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps, r } // If the input is not focused and the new index is out of range, focus the input - if (newIndex < 0 && !searchInputRef.current?.isFocused() && shouldFocusInputOnScreenFocus) { + if (newIndex < 0 && !isAnimatedTextInputRef(searchInputRef) && shouldFocusInputOnScreenFocus) { searchInputRef.current?.focus(); } }, @@ -163,12 +167,12 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps, r // Enable keyboard movement if tab or enter is pressed or if shift is pressed while the input // is not focused, so that the navigation and tab cycling can be done using the keyboard without // interfering with the input behaviour. - if (keyBoardEvent.key === 'Tab' || keyBoardEvent.key === 'Enter' || (keyBoardEvent.key === 'Shift' && searchInputRef.current && !searchInputRef.current.isFocused())) { + if (keyBoardEvent.key === 'Tab' || keyBoardEvent.key === 'Enter' || (keyBoardEvent.key === 'Shift' && searchInputRef.current && !isAnimatedTextInputRef(searchInputRef))) { setIsUsingKeyboardMovement(true); } // We allow typing in the search box if any key is pressed apart from Arrow keys. - if (searchInputRef.current && !searchInputRef.current.isFocused() && ReportUtils.shouldAutoFocusOnKeyPress(keyBoardEvent)) { + if (searchInputRef.current && !isAnimatedTextInputRef(searchInputRef) && ReportUtils.shouldAutoFocusOnKeyPress(keyBoardEvent)) { searchInputRef.current.focus(); } }, diff --git a/src/components/MoneyRequestAmountInput.tsx b/src/components/MoneyRequestAmountInput.tsx index 9acd73beadba..5ac3ee347cb9 100644 --- a/src/components/MoneyRequestAmountInput.tsx +++ b/src/components/MoneyRequestAmountInput.tsx @@ -7,6 +7,7 @@ import * as CurrencyUtils from '@libs/CurrencyUtils'; import getOperatingSystem from '@libs/getOperatingSystem'; import * as MoneyRequestUtils from '@libs/MoneyRequestUtils'; import CONST from '@src/CONST'; +import type {AnimatedTextInputRef} from './RNTextInput'; import type {BaseTextInputRef} from './TextInput/BaseTextInput/types'; import TextInputWithCurrencySymbol from './TextInputWithCurrencySymbol'; @@ -89,6 +90,9 @@ const getNewSelection = (oldSelection: Selection, prevLength: number, newLength: return {start: cursorPosition, end: cursorPosition}; }; +const isAnimatedTextInputRef = (textInput: React.MutableRefObject) => + textInput.current && 'isFocused' in textInput.current && (textInput.current as AnimatedTextInputRef).isFocused(); + function MoneyRequestAmountInput( { amount = 0, @@ -179,7 +183,7 @@ function MoneyRequestAmountInput( })); useEffect(() => { - if (!currency || typeof amount !== 'number' || (formatAmountOnBlur && textInput.current?.isFocused())) { + if (!currency || typeof amount !== 'number' || (formatAmountOnBlur && isAnimatedTextInputRef(textInput))) { return; } const frontendAmount = formatAmountOnBlur ? CurrencyUtils.convertToDisplayStringWithoutCurrency(amount, currency) : CurrencyUtils.convertToFrontendAmount(amount).toString(); diff --git a/src/pages/iou/MoneyRequestAmountForm.tsx b/src/pages/iou/MoneyRequestAmountForm.tsx index 46bd34006550..b363d13af98f 100644 --- a/src/pages/iou/MoneyRequestAmountForm.tsx +++ b/src/pages/iou/MoneyRequestAmountForm.tsx @@ -8,6 +8,7 @@ import Button from '@components/Button'; import FormHelpMessage from '@components/FormHelpMessage'; import MoneyRequestAmountInput from '@components/MoneyRequestAmountInput'; import type {MoneyRequestAmountInputRef} from '@components/MoneyRequestAmountInput'; +import type {AnimatedTextInputRef} from '@components/RNTextInput'; import ScrollView from '@components/ScrollView'; import SettlementButton from '@components/SettlementButton'; import useLocalize from '@hooks/useLocalize'; @@ -66,6 +67,9 @@ type MoneyRequestAmountFormProps = { selectedTab?: SelectedTabRequest; }; +const isAnimatedTextInputRef = (textInput: React.MutableRefObject) => + textInput.current && 'isFocused' in textInput.current && (textInput.current as AnimatedTextInputRef).isFocused(); + const isAmountInvalid = (amount: string) => !amount.length || parseFloat(amount) < 0.01; const isTaxAmountInvalid = (currentAmount: string, taxAmount: number, isTaxAmountForm: boolean) => isTaxAmountForm && Number.parseFloat(currentAmount) > CurrencyUtils.convertToFrontendAmount(Math.abs(taxAmount)); @@ -126,7 +130,8 @@ function MoneyRequestAmountForm( if (!textInput.current) { return; } - if (!textInput.current.isFocused()) { + + if (!isAnimatedTextInputRef(textInput)) { textInput.current.focus(); } }; @@ -167,7 +172,7 @@ function MoneyRequestAmountForm( */ const updateAmountNumberPad = useCallback( (key: string) => { - if (shouldUpdateSelection && !textInput.current?.isFocused()) { + if (shouldUpdateSelection && !isAnimatedTextInputRef(textInput)) { textInput.current?.focus(); } const currentAmount = moneyRequestAmountInput.current?.getAmount() ?? ''; @@ -194,7 +199,7 @@ function MoneyRequestAmountForm( */ const updateLongPressHandlerState = useCallback((value: boolean) => { setShouldUpdateSelection(!value); - if (!value && !textInput.current?.isFocused()) { + if (!value && !isAnimatedTextInputRef(textInput)) { textInput.current?.focus(); } }, []); diff --git a/src/pages/signin/LoginForm/BaseLoginForm.tsx b/src/pages/signin/LoginForm/BaseLoginForm.tsx index 4286a2603341..780998cfb6e6 100644 --- a/src/pages/signin/LoginForm/BaseLoginForm.tsx +++ b/src/pages/signin/LoginForm/BaseLoginForm.tsx @@ -7,6 +7,7 @@ import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import DotIndicatorMessage from '@components/DotIndicatorMessage'; import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton'; +import type {AnimatedTextInputRef} from '@components/RNTextInput'; import AppleSignIn from '@components/SignInButtons/AppleSignIn'; import GoogleSignIn from '@components/SignInButtons/GoogleSignIn'; import Text from '@components/Text'; @@ -53,6 +54,9 @@ type BaseLoginFormProps = WithToggleVisibilityViewProps & BaseLoginFormOnyxProps const willBlurTextInputOnTapOutside = willBlurTextInputOnTapOutsideFunc(); +const isAnimatedTextInputRef = (textInput: React.MutableRefObject) => + textInput.current && 'isFocused' in textInput.current && (textInput.current as AnimatedTextInputRef).isFocused(); + function BaseLoginForm({account, credentials, closeAccount, blurOnSubmit = false, isVisible}: BaseLoginFormProps, ref: ForwardedRef) { const styles = useThemeStyles(); const {isOffline} = useNetwork(); @@ -200,7 +204,7 @@ function BaseLoginForm({account, credentials, closeAccount, blurOnSubmit = false if (!input.current) { return false; } - return input.current.isFocused() as boolean; + return (isAnimatedTextInputRef(input) ?? input.current.focus()) as boolean; }, clearDataAndFocus(clearLogin = true) { if (!input.current) { From 03e430243c749cb8c9b4ae4e1503773158cb59ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barbara=20Gawe=C5=82-Kucab?= Date: Tue, 28 May 2024 10:21:54 +0200 Subject: [PATCH 024/130] wip --- src/libs/Performance.tsx | 15 ++++++++++----- src/libs/StartupTimer/index.native.ts | 3 ++- src/libs/updateMultilineInputRange/index.ts | 6 +++--- tests/actions/ReportTest.ts | 2 +- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/libs/Performance.tsx b/src/libs/Performance.tsx index 9302b538a621..01525c7d2abd 100644 --- a/src/libs/Performance.tsx +++ b/src/libs/Performance.tsx @@ -46,6 +46,8 @@ type PerformanceModule = { subscribeToMeasurements: SubscribeToMeasurements; }; +type PerformanceObserverCallback = (entries: PerformanceObserverEntryList, observer: PerformanceObserver) => void; + let rnPerformance: RNPerformance; /** @@ -87,8 +89,11 @@ const Performance: PerformanceModule = { if (Metrics.canCapturePerformanceMetrics()) { const perfModule = require('react-native-performance'); - perfModule.setResourceLoggingEnabled(true); - rnPerformance = perfModule.default; + const {default: performance, setResourceLoggingEnabled, PerformanceObserver} = perfModule; + const perfObserver: new (callback: PerformanceObserverCallback) => PerformanceObserver = PerformanceObserver; + + (setResourceLoggingEnabled as (enabled?: boolean) => void)(true); + rnPerformance = performance; Performance.measureFailSafe = (measureName: string, startOrMeasureOptions: string, endMark?: string) => { try { @@ -123,7 +128,7 @@ if (Metrics.canCapturePerformanceMetrics()) { */ Performance.setupPerformanceObserver = () => { // Monitor some native marks that we want to put on the timeline - new perfModule.PerformanceObserver((list: PerformanceObserverEntryList, observer: PerformanceObserver) => { + new perfObserver((list: PerformanceObserverEntryList, observer: PerformanceObserver) => { list.getEntries().forEach((entry: PerformanceEntry) => { if (entry.name === 'nativeLaunchEnd') { Performance.measureFailSafe('nativeLaunch', 'nativeLaunchStart', 'nativeLaunchEnd'); @@ -150,7 +155,7 @@ if (Metrics.canCapturePerformanceMetrics()) { }).observe({type: 'react-native-mark', buffered: true}); // Monitor for "_end" marks and capture "_start" to "_end" measures - new perfModule.PerformanceObserver((list: PerformanceObserverEntryList) => { + new perfObserver((list: PerformanceObserverEntryList) => { list.getEntriesByType('mark').forEach((mark: PerformanceEntry) => { if (mark.name.endsWith('_end')) { const end = mark.name; @@ -195,7 +200,7 @@ if (Metrics.canCapturePerformanceMetrics()) { }; Performance.subscribeToMeasurements = (callback: PerformanceEntriesCallback) => { - new perfModule.PerformanceObserver((list: PerformanceObserverEntryList) => { + new perfObserver((list: PerformanceObserverEntryList) => { list.getEntriesByType('measure').forEach(callback); }).observe({type: 'measure', buffered: true}); }; diff --git a/src/libs/StartupTimer/index.native.ts b/src/libs/StartupTimer/index.native.ts index 8aa185d81146..4e9c91222104 100644 --- a/src/libs/StartupTimer/index.native.ts +++ b/src/libs/StartupTimer/index.native.ts @@ -6,7 +6,8 @@ import type StartupTimer from './types'; */ const startupTimer: StartupTimer = { stop: () => { - NativeModules.StartupTimer.stop(); + const {StartupTimer} = NativeModules; + (StartupTimer as StartupTimer).stop(); }, }; diff --git a/src/libs/updateMultilineInputRange/index.ts b/src/libs/updateMultilineInputRange/index.ts index a0ad76096922..3ee2ce6bb70c 100644 --- a/src/libs/updateMultilineInputRange/index.ts +++ b/src/libs/updateMultilineInputRange/index.ts @@ -15,9 +15,9 @@ const updateMultilineInputRange: UpdateMultilineInputRange = (input, shouldAutoF } if ('value' in input && input.value && input.setSelectionRange) { - const length = input.value.length; - if (shouldAutoFocus) { - input.setSelectionRange(length, length); + const length = input.value.length as number; + if (shouldAutoFocus && 'setSelectionRange' in input) { + (input as HTMLInputElement).setSelectionRange(length, length); } // eslint-disable-next-line no-param-reassign input.scrollTop = input.scrollHeight; diff --git a/tests/actions/ReportTest.ts b/tests/actions/ReportTest.ts index 4b49bdef5edd..cc6ad281e72d 100644 --- a/tests/actions/ReportTest.ts +++ b/tests/actions/ReportTest.ts @@ -186,7 +186,7 @@ describe('actions/Report', () => { .then(() => { // THEN only ONE call to AddComment will happen const URL_ARGUMENT_INDEX = 0; - const addCommentCalls = (global.fetch as jest.Mock).mock.calls.filter((callArguments) => callArguments[URL_ARGUMENT_INDEX].includes('AddComment')); + const addCommentCalls = (global.fetch as jest.Mock).mock.calls.filter((callArguments) => (callArguments[URL_ARGUMENT_INDEX] as string).includes('AddComment')); expect(addCommentCalls.length).toBe(1); }); }); From 6cd6194de98f08fe1545b42008eb36796dadfc5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barbara=20Gawe=C5=82-Kucab?= Date: Tue, 28 May 2024 10:40:49 +0200 Subject: [PATCH 025/130] move isAnimatedTextInputRef function to common file --- src/components/AmountForm.tsx | 5 +---- src/components/EmojiPicker/EmojiPickerMenu/index.tsx | 5 +---- src/components/MoneyRequestAmountInput.tsx | 5 +---- .../TextInput/BaseTextInput/isAnimatedTextInoutRef.ts | 6 ++++++ src/pages/iou/MoneyRequestAmountForm.tsx | 5 +---- src/pages/signin/LoginForm/BaseLoginForm.tsx | 7 ++----- 6 files changed, 12 insertions(+), 21 deletions(-) create mode 100644 src/components/TextInput/BaseTextInput/isAnimatedTextInoutRef.ts diff --git a/src/components/AmountForm.tsx b/src/components/AmountForm.tsx index f5d460ed5caa..0d05c54029cf 100644 --- a/src/components/AmountForm.tsx +++ b/src/components/AmountForm.tsx @@ -12,7 +12,7 @@ import * as MoneyRequestUtils from '@libs/MoneyRequestUtils'; import CONST from '@src/CONST'; import BigNumberPad from './BigNumberPad'; import FormHelpMessage from './FormHelpMessage'; -import type {AnimatedTextInputRef} from './RNTextInput'; +import isAnimatedTextInputRef from './TextInput/BaseTextInput/isAnimatedTextInoutRef'; import type {BaseTextInputProps, BaseTextInputRef} from './TextInput/BaseTextInput/types'; import TextInputWithCurrencySymbol from './TextInputWithCurrencySymbol'; import type TextInputWithCurrencySymbolProps from './TextInputWithCurrencySymbol/types'; @@ -52,9 +52,6 @@ const getNewSelection = (oldSelection: {start: number; end: number}, prevLength: return {start: cursorPosition, end: cursorPosition}; }; -const isAnimatedTextInputRef = (textInput: React.MutableRefObject) => - textInput.current && 'isFocused' in textInput.current && (textInput.current as AnimatedTextInputRef).isFocused(); - const AMOUNT_VIEW_ID = 'amountView'; const NUM_PAD_CONTAINER_VIEW_ID = 'numPadContainerView'; const NUM_PAD_VIEW_ID = 'numPadView'; diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.tsx b/src/components/EmojiPicker/EmojiPickerMenu/index.tsx index 87cd50bd3cdb..26a81f787123 100755 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.tsx @@ -5,9 +5,9 @@ import type {ForwardedRef} from 'react'; import {View} from 'react-native'; import {scrollTo} from 'react-native-reanimated'; import EmojiPickerMenuItem from '@components/EmojiPicker/EmojiPickerMenuItem'; -import type {AnimatedTextInputRef} from '@components/RNTextInput'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; +import isAnimatedTextInputRef from '@components/TextInput/BaseTextInput/isAnimatedTextInoutRef'; import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types'; import useArrowKeyFocusManager from '@hooks/useArrowKeyFocusManager'; import useLocalize from '@hooks/useLocalize'; @@ -28,9 +28,6 @@ import useEmojiPickerMenu from './useEmojiPickerMenu'; const throttleTime = Browser.isMobile() ? 200 : 50; -const isAnimatedTextInputRef = (textInput: React.MutableRefObject) => - textInput.current && 'isFocused' in textInput.current && (textInput.current as AnimatedTextInputRef).isFocused(); - function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps, ref: ForwardedRef) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); diff --git a/src/components/MoneyRequestAmountInput.tsx b/src/components/MoneyRequestAmountInput.tsx index 5ac3ee347cb9..03aaf460fe0f 100644 --- a/src/components/MoneyRequestAmountInput.tsx +++ b/src/components/MoneyRequestAmountInput.tsx @@ -7,7 +7,7 @@ import * as CurrencyUtils from '@libs/CurrencyUtils'; import getOperatingSystem from '@libs/getOperatingSystem'; import * as MoneyRequestUtils from '@libs/MoneyRequestUtils'; import CONST from '@src/CONST'; -import type {AnimatedTextInputRef} from './RNTextInput'; +import isAnimatedTextInputRef from './TextInput/BaseTextInput/isAnimatedTextInoutRef'; import type {BaseTextInputRef} from './TextInput/BaseTextInput/types'; import TextInputWithCurrencySymbol from './TextInputWithCurrencySymbol'; @@ -90,9 +90,6 @@ const getNewSelection = (oldSelection: Selection, prevLength: number, newLength: return {start: cursorPosition, end: cursorPosition}; }; -const isAnimatedTextInputRef = (textInput: React.MutableRefObject) => - textInput.current && 'isFocused' in textInput.current && (textInput.current as AnimatedTextInputRef).isFocused(); - function MoneyRequestAmountInput( { amount = 0, diff --git a/src/components/TextInput/BaseTextInput/isAnimatedTextInoutRef.ts b/src/components/TextInput/BaseTextInput/isAnimatedTextInoutRef.ts new file mode 100644 index 000000000000..e596b91d95b9 --- /dev/null +++ b/src/components/TextInput/BaseTextInput/isAnimatedTextInoutRef.ts @@ -0,0 +1,6 @@ +import type {AnimatedTextInputRef} from '@components/RNTextInput'; +import type {BaseTextInputRef} from './types'; + +export default function isAnimatedTextInputRef(textInput: React.MutableRefObject): boolean | null { + return textInput.current && 'isFocused' in textInput.current && (textInput.current as AnimatedTextInputRef).isFocused(); +} diff --git a/src/pages/iou/MoneyRequestAmountForm.tsx b/src/pages/iou/MoneyRequestAmountForm.tsx index b363d13af98f..755efc12ccdb 100644 --- a/src/pages/iou/MoneyRequestAmountForm.tsx +++ b/src/pages/iou/MoneyRequestAmountForm.tsx @@ -8,9 +8,9 @@ import Button from '@components/Button'; import FormHelpMessage from '@components/FormHelpMessage'; import MoneyRequestAmountInput from '@components/MoneyRequestAmountInput'; import type {MoneyRequestAmountInputRef} from '@components/MoneyRequestAmountInput'; -import type {AnimatedTextInputRef} from '@components/RNTextInput'; import ScrollView from '@components/ScrollView'; import SettlementButton from '@components/SettlementButton'; +import isAnimatedTextInputRef from '@components/TextInput/BaseTextInput/isAnimatedTextInoutRef'; import useLocalize from '@hooks/useLocalize'; import usePrevious from '@hooks/usePrevious'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -67,9 +67,6 @@ type MoneyRequestAmountFormProps = { selectedTab?: SelectedTabRequest; }; -const isAnimatedTextInputRef = (textInput: React.MutableRefObject) => - textInput.current && 'isFocused' in textInput.current && (textInput.current as AnimatedTextInputRef).isFocused(); - const isAmountInvalid = (amount: string) => !amount.length || parseFloat(amount) < 0.01; const isTaxAmountInvalid = (currentAmount: string, taxAmount: number, isTaxAmountForm: boolean) => isTaxAmountForm && Number.parseFloat(currentAmount) > CurrencyUtils.convertToFrontendAmount(Math.abs(taxAmount)); diff --git a/src/pages/signin/LoginForm/BaseLoginForm.tsx b/src/pages/signin/LoginForm/BaseLoginForm.tsx index 780998cfb6e6..07e1ef928ac2 100644 --- a/src/pages/signin/LoginForm/BaseLoginForm.tsx +++ b/src/pages/signin/LoginForm/BaseLoginForm.tsx @@ -7,11 +7,11 @@ import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import DotIndicatorMessage from '@components/DotIndicatorMessage'; import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton'; -import type {AnimatedTextInputRef} from '@components/RNTextInput'; import AppleSignIn from '@components/SignInButtons/AppleSignIn'; import GoogleSignIn from '@components/SignInButtons/GoogleSignIn'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; +import isAnimatedTextInputRef from '@components/TextInput/BaseTextInput/isAnimatedTextInoutRef'; import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types'; import withToggleVisibilityView from '@components/withToggleVisibilityView'; import type {WithToggleVisibilityViewProps} from '@components/withToggleVisibilityView'; @@ -54,9 +54,6 @@ type BaseLoginFormProps = WithToggleVisibilityViewProps & BaseLoginFormOnyxProps const willBlurTextInputOnTapOutside = willBlurTextInputOnTapOutsideFunc(); -const isAnimatedTextInputRef = (textInput: React.MutableRefObject) => - textInput.current && 'isFocused' in textInput.current && (textInput.current as AnimatedTextInputRef).isFocused(); - function BaseLoginForm({account, credentials, closeAccount, blurOnSubmit = false, isVisible}: BaseLoginFormProps, ref: ForwardedRef) { const styles = useThemeStyles(); const {isOffline} = useNetwork(); @@ -204,7 +201,7 @@ function BaseLoginForm({account, credentials, closeAccount, blurOnSubmit = false if (!input.current) { return false; } - return (isAnimatedTextInputRef(input) ?? input.current.focus()) as boolean; + return !!isAnimatedTextInputRef(input); }, clearDataAndFocus(clearLogin = true) { if (!input.current) { From ae1804a6bb74e98146653dd659d610d12bd87deb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barbara=20Gawe=C5=82-Kucab?= Date: Tue, 28 May 2024 10:44:55 +0200 Subject: [PATCH 026/130] simplify changes --- .../actions/javascript/getGraphiteString/getGraphiteString.ts | 3 +-- workflow_tests/utils/preGenerateTest.ts | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/actions/javascript/getGraphiteString/getGraphiteString.ts b/.github/actions/javascript/getGraphiteString/getGraphiteString.ts index 15b5e885c921..cf686b36cd3d 100644 --- a/.github/actions/javascript/getGraphiteString/getGraphiteString.ts +++ b/.github/actions/javascript/getGraphiteString/getGraphiteString.ts @@ -32,8 +32,7 @@ const run = () => { } if (current.name && current.meanDuration && current.meanCount && timestamp) { - const currentName = current.name as string; - const formattedName = currentName.split(' ').join('-'); + const formattedName = (current.name as string).split(' ').join('-'); const renderDurationString = `${GRAPHITE_PATH}.${formattedName}.renderDuration ${current.meanDuration} ${timestamp}`; const renderCountString = `${GRAPHITE_PATH}.${formattedName}.renderCount ${current.meanCount} ${timestamp}`; diff --git a/workflow_tests/utils/preGenerateTest.ts b/workflow_tests/utils/preGenerateTest.ts index eb1d2e8f9b69..e88f7ffd8ee5 100644 --- a/workflow_tests/utils/preGenerateTest.ts +++ b/workflow_tests/utils/preGenerateTest.ts @@ -219,8 +219,7 @@ const getMockFileContent = (workflowName: string, jobs: Record { - const stepName = step.name as string; - const stepMockName = `${workflowName.toUpperCase()}__${jobId.toUpperCase()}__${stepName + const stepMockName = `${workflowName.toUpperCase()}__${jobId.toUpperCase()}__${(step.name as string) .replaceAll(' ', '_') .replaceAll('-', '_') .replaceAll(',', '') From 48de655d4eca4724523af5ccadd9fd2c6cee0244 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barbara=20Gawe=C5=82-Kucab?= Date: Tue, 28 May 2024 12:00:50 +0200 Subject: [PATCH 027/130] wip --- .github/libs/GitUtils.ts | 4 ++-- src/components/Hoverable/ActiveHoverable.tsx | 13 ++++++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/.github/libs/GitUtils.ts b/.github/libs/GitUtils.ts index 684e7c76aa86..dc8ae037be28 100644 --- a/.github/libs/GitUtils.ts +++ b/.github/libs/GitUtils.ts @@ -66,11 +66,11 @@ function getCommitHistoryAsJSON(fromTag: string, toTag: string): Promise { + spawnedProcess.stdout.on('data', (chunk: Buffer) => { console.log(chunk.toString()); stdout += chunk.toString(); }); - spawnedProcess.stderr.on('data', (chunk) => { + spawnedProcess.stderr.on('data', (chunk: Buffer) => { console.error(chunk.toString()); stderr += chunk.toString(); }); diff --git a/src/components/Hoverable/ActiveHoverable.tsx b/src/components/Hoverable/ActiveHoverable.tsx index dd3f41c52578..227e4524eb02 100644 --- a/src/components/Hoverable/ActiveHoverable.tsx +++ b/src/components/Hoverable/ActiveHoverable.tsx @@ -8,6 +8,8 @@ import type HoverableProps from './types'; type ActiveHoverableProps = Omit; +type OnMouseEventProps = (e: MouseEvent) => void; + function ActiveHoverable({onHoverIn, onHoverOut, shouldHandleScroll, shouldFreezeCapture, children}: ActiveHoverableProps, outerRef: Ref) { const [isHovered, setIsHovered] = useState(false); @@ -98,9 +100,10 @@ function ActiveHoverable({onHoverIn, onHoverOut, shouldHandleScroll, shouldFreez const child = useMemo(() => getReturnValue(children, !isScrollingRef.current && isHovered), [children, isHovered]); - const childOnMouseEnter = child.props.onMouseEnter; - const childOnMouseLeave = child.props.onMouseLeave; - const childOnMouseMove = child.props.onMouseMove; + const childOnMouseEnter: OnMouseEventProps = child.props.onMouseEnter; + const childOnMouseLeave: OnMouseEventProps = child.props.onMouseLeave; + const childOnMouseMove: OnMouseEventProps = child.props.onMouseMove; + const childOnBlur: OnMouseEventProps = child.props.onBlur; const hoverAndForwardOnMouseEnter = useCallback( (e: MouseEvent) => { @@ -127,9 +130,9 @@ function ActiveHoverable({onHoverIn, onHoverOut, shouldHandleScroll, shouldFreez setIsHovered(false); } - child.props.onBlur?.(event); + childOnBlur?.(event); }, - [child.props], + [childOnBlur], ); const handleAndForwardOnMouseMove = useCallback( From 238abd67ce481c4c866ed94c8feed5a29f3df95d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barbara=20Gawe=C5=82-Kucab?= Date: Tue, 28 May 2024 14:06:23 +0200 Subject: [PATCH 028/130] rename text input function & change performance types --- .../getGraphiteString/getGraphiteString.ts | 15 +++++++++--- src/components/AmountForm.tsx | 8 +++---- .../EmojiPicker/EmojiPickerMenu/index.tsx | 8 +++---- src/components/MoneyRequestAmountInput.tsx | 4 ++-- ...utRef.ts => isAnimatedTextInputFocused.ts} | 2 +- src/libs/Performance.tsx | 23 ++++++++++--------- src/pages/iou/MoneyRequestAmountForm.tsx | 8 +++---- src/pages/signin/LoginForm/BaseLoginForm.tsx | 4 ++-- 8 files changed, 41 insertions(+), 31 deletions(-) rename src/components/TextInput/BaseTextInput/{isAnimatedTextInoutRef.ts => isAnimatedTextInputFocused.ts} (65%) diff --git a/.github/actions/javascript/getGraphiteString/getGraphiteString.ts b/.github/actions/javascript/getGraphiteString/getGraphiteString.ts index cf686b36cd3d..28f5fb40d5f2 100644 --- a/.github/actions/javascript/getGraphiteString/getGraphiteString.ts +++ b/.github/actions/javascript/getGraphiteString/getGraphiteString.ts @@ -1,6 +1,15 @@ import * as core from '@actions/core'; import fs from 'fs'; +type RegressionEntryProps = { + metadata?: { + creationDate: string; + }; + name: string; + meanDuration: number; + meanCount: number; +}; + const run = () => { // Prefix path to the graphite metric const GRAPHITE_PATH = 'reassure'; @@ -24,15 +33,15 @@ const run = () => { } try { - const current = JSON.parse(entry); + const current: RegressionEntryProps = JSON.parse(entry); // Extract timestamp, Graphite accepts timestamp in seconds if (current.metadata?.creationDate) { - timestamp = Math.floor(new Date(current.metadata.creationDate as string).getTime() / 1000); + timestamp = Math.floor(new Date(current.metadata.creationDate).getTime() / 1000); } if (current.name && current.meanDuration && current.meanCount && timestamp) { - const formattedName = (current.name as string).split(' ').join('-'); + const formattedName = current.name.split(' ').join('-'); const renderDurationString = `${GRAPHITE_PATH}.${formattedName}.renderDuration ${current.meanDuration} ${timestamp}`; const renderCountString = `${GRAPHITE_PATH}.${formattedName}.renderCount ${current.meanCount} ${timestamp}`; diff --git a/src/components/AmountForm.tsx b/src/components/AmountForm.tsx index 0d05c54029cf..c1640f769992 100644 --- a/src/components/AmountForm.tsx +++ b/src/components/AmountForm.tsx @@ -12,7 +12,7 @@ import * as MoneyRequestUtils from '@libs/MoneyRequestUtils'; import CONST from '@src/CONST'; import BigNumberPad from './BigNumberPad'; import FormHelpMessage from './FormHelpMessage'; -import isAnimatedTextInputRef from './TextInput/BaseTextInput/isAnimatedTextInoutRef'; +import isAnimatedTextInputFocused from './TextInput/BaseTextInput/isAnimatedTextInputFocused'; import type {BaseTextInputProps, BaseTextInputRef} from './TextInput/BaseTextInput/types'; import TextInputWithCurrencySymbol from './TextInputWithCurrencySymbol'; import type TextInputWithCurrencySymbolProps from './TextInputWithCurrencySymbol/types'; @@ -95,7 +95,7 @@ function AmountForm( if (!textInput.current) { return; } - if (!isAnimatedTextInputRef(textInput)) { + if (!isAnimatedTextInputFocused(textInput)) { textInput.current.focus(); } }; @@ -144,7 +144,7 @@ function AmountForm( */ const updateAmountNumberPad = useCallback( (key: string) => { - if (shouldUpdateSelection && !isAnimatedTextInputRef(textInput)) { + if (shouldUpdateSelection && !isAnimatedTextInputFocused(textInput)) { textInput.current?.focus(); } // Backspace button is pressed @@ -169,7 +169,7 @@ function AmountForm( */ const updateLongPressHandlerState = useCallback((value: boolean) => { setShouldUpdateSelection(!value); - if (!value && !isAnimatedTextInputRef(textInput)) { + if (!value && !isAnimatedTextInputFocused(textInput)) { textInput.current?.focus(); } }, []); diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.tsx b/src/components/EmojiPicker/EmojiPickerMenu/index.tsx index 26a81f787123..19ec28c6e207 100755 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.tsx @@ -7,7 +7,7 @@ import {scrollTo} from 'react-native-reanimated'; import EmojiPickerMenuItem from '@components/EmojiPicker/EmojiPickerMenuItem'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; -import isAnimatedTextInputRef from '@components/TextInput/BaseTextInput/isAnimatedTextInoutRef'; +import isAnimatedTextInputFocused from '@components/TextInput/BaseTextInput/isAnimatedTextInputFocused'; import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types'; import useArrowKeyFocusManager from '@hooks/useArrowKeyFocusManager'; import useLocalize from '@hooks/useLocalize'; @@ -85,7 +85,7 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps, r } // If the input is not focused and the new index is out of range, focus the input - if (newIndex < 0 && !isAnimatedTextInputRef(searchInputRef) && shouldFocusInputOnScreenFocus) { + if (newIndex < 0 && !isAnimatedTextInputFocused(searchInputRef) && shouldFocusInputOnScreenFocus) { searchInputRef.current?.focus(); } }, @@ -164,12 +164,12 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps, r // Enable keyboard movement if tab or enter is pressed or if shift is pressed while the input // is not focused, so that the navigation and tab cycling can be done using the keyboard without // interfering with the input behaviour. - if (keyBoardEvent.key === 'Tab' || keyBoardEvent.key === 'Enter' || (keyBoardEvent.key === 'Shift' && searchInputRef.current && !isAnimatedTextInputRef(searchInputRef))) { + if (keyBoardEvent.key === 'Tab' || keyBoardEvent.key === 'Enter' || (keyBoardEvent.key === 'Shift' && searchInputRef.current && !isAnimatedTextInputFocused(searchInputRef))) { setIsUsingKeyboardMovement(true); } // We allow typing in the search box if any key is pressed apart from Arrow keys. - if (searchInputRef.current && !isAnimatedTextInputRef(searchInputRef) && ReportUtils.shouldAutoFocusOnKeyPress(keyBoardEvent)) { + if (searchInputRef.current && !isAnimatedTextInputFocused(searchInputRef) && ReportUtils.shouldAutoFocusOnKeyPress(keyBoardEvent)) { searchInputRef.current.focus(); } }, diff --git a/src/components/MoneyRequestAmountInput.tsx b/src/components/MoneyRequestAmountInput.tsx index 03aaf460fe0f..6e5ced4fbcab 100644 --- a/src/components/MoneyRequestAmountInput.tsx +++ b/src/components/MoneyRequestAmountInput.tsx @@ -7,7 +7,7 @@ import * as CurrencyUtils from '@libs/CurrencyUtils'; import getOperatingSystem from '@libs/getOperatingSystem'; import * as MoneyRequestUtils from '@libs/MoneyRequestUtils'; import CONST from '@src/CONST'; -import isAnimatedTextInputRef from './TextInput/BaseTextInput/isAnimatedTextInoutRef'; +import isAnimatedTextInputFocused from './TextInput/BaseTextInput/isAnimatedTextInputFocused'; import type {BaseTextInputRef} from './TextInput/BaseTextInput/types'; import TextInputWithCurrencySymbol from './TextInputWithCurrencySymbol'; @@ -180,7 +180,7 @@ function MoneyRequestAmountInput( })); useEffect(() => { - if (!currency || typeof amount !== 'number' || (formatAmountOnBlur && isAnimatedTextInputRef(textInput))) { + if (!currency || typeof amount !== 'number' || (formatAmountOnBlur && isAnimatedTextInputFocused(textInput))) { return; } const frontendAmount = formatAmountOnBlur ? CurrencyUtils.convertToDisplayStringWithoutCurrency(amount, currency) : CurrencyUtils.convertToFrontendAmount(amount).toString(); diff --git a/src/components/TextInput/BaseTextInput/isAnimatedTextInoutRef.ts b/src/components/TextInput/BaseTextInput/isAnimatedTextInputFocused.ts similarity index 65% rename from src/components/TextInput/BaseTextInput/isAnimatedTextInoutRef.ts rename to src/components/TextInput/BaseTextInput/isAnimatedTextInputFocused.ts index e596b91d95b9..b9dd98041362 100644 --- a/src/components/TextInput/BaseTextInput/isAnimatedTextInoutRef.ts +++ b/src/components/TextInput/BaseTextInput/isAnimatedTextInputFocused.ts @@ -1,6 +1,6 @@ import type {AnimatedTextInputRef} from '@components/RNTextInput'; import type {BaseTextInputRef} from './types'; -export default function isAnimatedTextInputRef(textInput: React.MutableRefObject): boolean | null { +export default function isAnimatedTextInputFocused(textInput: React.MutableRefObject): boolean | null { return textInput.current && 'isFocused' in textInput.current && (textInput.current as AnimatedTextInputRef).isFocused(); } diff --git a/src/libs/Performance.tsx b/src/libs/Performance.tsx index 01525c7d2abd..5155722786cf 100644 --- a/src/libs/Performance.tsx +++ b/src/libs/Performance.tsx @@ -3,7 +3,7 @@ import isObject from 'lodash/isObject'; import lodashTransform from 'lodash/transform'; import React, {forwardRef, Profiler} from 'react'; import {Alert, InteractionManager} from 'react-native'; -import type {PerformanceEntry, PerformanceMark, PerformanceMeasure, Performance as RNPerformance} from 'react-native-performance'; +import type {PerformanceEntry, PerformanceMark, PerformanceMeasure, Performance as RNPerformance, PerformanceObserver as RNPerformanceObserver} from 'react-native-performance'; import type {PerformanceObserverEntryList} from 'react-native-performance/lib/typescript/performance-observer'; import CONST from '@src/CONST'; import isE2ETestSession from './E2E/isE2ETestSession'; @@ -46,7 +46,11 @@ type PerformanceModule = { subscribeToMeasurements: SubscribeToMeasurements; }; -type PerformanceObserverCallback = (entries: PerformanceObserverEntryList, observer: PerformanceObserver) => void; +type ReactNativePerformance = { + default: RNPerformance; + setResourceLoggingEnabled: (enabled?: boolean) => void; + PerformanceObserver: typeof RNPerformanceObserver; +}; let rnPerformance: RNPerformance; @@ -88,12 +92,9 @@ const Performance: PerformanceModule = { }; if (Metrics.canCapturePerformanceMetrics()) { - const perfModule = require('react-native-performance'); - const {default: performance, setResourceLoggingEnabled, PerformanceObserver} = perfModule; - const perfObserver: new (callback: PerformanceObserverCallback) => PerformanceObserver = PerformanceObserver; - - (setResourceLoggingEnabled as (enabled?: boolean) => void)(true); - rnPerformance = performance; + const perfModule: ReactNativePerformance = require('react-native-performance'); + perfModule.setResourceLoggingEnabled(true); + rnPerformance = perfModule.default; Performance.measureFailSafe = (measureName: string, startOrMeasureOptions: string, endMark?: string) => { try { @@ -128,7 +129,7 @@ if (Metrics.canCapturePerformanceMetrics()) { */ Performance.setupPerformanceObserver = () => { // Monitor some native marks that we want to put on the timeline - new perfObserver((list: PerformanceObserverEntryList, observer: PerformanceObserver) => { + new perfModule.PerformanceObserver((list: PerformanceObserverEntryList, observer: PerformanceObserver) => { list.getEntries().forEach((entry: PerformanceEntry) => { if (entry.name === 'nativeLaunchEnd') { Performance.measureFailSafe('nativeLaunch', 'nativeLaunchStart', 'nativeLaunchEnd'); @@ -155,7 +156,7 @@ if (Metrics.canCapturePerformanceMetrics()) { }).observe({type: 'react-native-mark', buffered: true}); // Monitor for "_end" marks and capture "_start" to "_end" measures - new perfObserver((list: PerformanceObserverEntryList) => { + new perfModule.PerformanceObserver((list: PerformanceObserverEntryList) => { list.getEntriesByType('mark').forEach((mark: PerformanceEntry) => { if (mark.name.endsWith('_end')) { const end = mark.name; @@ -200,7 +201,7 @@ if (Metrics.canCapturePerformanceMetrics()) { }; Performance.subscribeToMeasurements = (callback: PerformanceEntriesCallback) => { - new perfObserver((list: PerformanceObserverEntryList) => { + new perfModule.PerformanceObserver((list: PerformanceObserverEntryList) => { list.getEntriesByType('measure').forEach(callback); }).observe({type: 'measure', buffered: true}); }; diff --git a/src/pages/iou/MoneyRequestAmountForm.tsx b/src/pages/iou/MoneyRequestAmountForm.tsx index 755efc12ccdb..29932b5e5fcc 100644 --- a/src/pages/iou/MoneyRequestAmountForm.tsx +++ b/src/pages/iou/MoneyRequestAmountForm.tsx @@ -10,7 +10,7 @@ import MoneyRequestAmountInput from '@components/MoneyRequestAmountInput'; import type {MoneyRequestAmountInputRef} from '@components/MoneyRequestAmountInput'; import ScrollView from '@components/ScrollView'; import SettlementButton from '@components/SettlementButton'; -import isAnimatedTextInputRef from '@components/TextInput/BaseTextInput/isAnimatedTextInoutRef'; +import isAnimatedTextInputFocused from '@components/TextInput/BaseTextInput/isAnimatedTextInputFocused'; import useLocalize from '@hooks/useLocalize'; import usePrevious from '@hooks/usePrevious'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -128,7 +128,7 @@ function MoneyRequestAmountForm( return; } - if (!isAnimatedTextInputRef(textInput)) { + if (!isAnimatedTextInputFocused(textInput)) { textInput.current.focus(); } }; @@ -169,7 +169,7 @@ function MoneyRequestAmountForm( */ const updateAmountNumberPad = useCallback( (key: string) => { - if (shouldUpdateSelection && !isAnimatedTextInputRef(textInput)) { + if (shouldUpdateSelection && !isAnimatedTextInputFocused(textInput)) { textInput.current?.focus(); } const currentAmount = moneyRequestAmountInput.current?.getAmount() ?? ''; @@ -196,7 +196,7 @@ function MoneyRequestAmountForm( */ const updateLongPressHandlerState = useCallback((value: boolean) => { setShouldUpdateSelection(!value); - if (!value && !isAnimatedTextInputRef(textInput)) { + if (!value && !isAnimatedTextInputFocused(textInput)) { textInput.current?.focus(); } }, []); diff --git a/src/pages/signin/LoginForm/BaseLoginForm.tsx b/src/pages/signin/LoginForm/BaseLoginForm.tsx index 07e1ef928ac2..8c59d19f1105 100644 --- a/src/pages/signin/LoginForm/BaseLoginForm.tsx +++ b/src/pages/signin/LoginForm/BaseLoginForm.tsx @@ -11,7 +11,7 @@ import AppleSignIn from '@components/SignInButtons/AppleSignIn'; import GoogleSignIn from '@components/SignInButtons/GoogleSignIn'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; -import isAnimatedTextInputRef from '@components/TextInput/BaseTextInput/isAnimatedTextInoutRef'; +import isAnimatedTextInputFocused from '@components/TextInput/BaseTextInput/isAnimatedTextInputFocused'; import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types'; import withToggleVisibilityView from '@components/withToggleVisibilityView'; import type {WithToggleVisibilityViewProps} from '@components/withToggleVisibilityView'; @@ -201,7 +201,7 @@ function BaseLoginForm({account, credentials, closeAccount, blurOnSubmit = false if (!input.current) { return false; } - return !!isAnimatedTextInputRef(input); + return !!isAnimatedTextInputFocused(input); }, clearDataAndFocus(clearLogin = true) { if (!input.current) { From 9fc260da92c62bd0e48d6837648bd729e307122a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barbara=20Gawe=C5=82-Kucab?= Date: Tue, 28 May 2024 15:28:16 +0200 Subject: [PATCH 029/130] wip --- .storybook/webpack.config.ts | 9 ++++++--- config/webpack/webpack.common.ts | 3 ++- src/components/Tooltip/BaseTooltip/index.tsx | 3 ++- src/components/WalletStatementModal/index.tsx | 2 +- src/libs/Environment/betaChecker/index.android.ts | 2 +- src/libs/Environment/betaChecker/index.ios.ts | 5 +++-- src/libs/Environment/betaChecker/index.ts | 2 +- src/libs/Environment/betaChecker/types.ts | 6 +++++- src/libs/StartupTimer/index.native.ts | 3 +-- 9 files changed, 22 insertions(+), 13 deletions(-) diff --git a/.storybook/webpack.config.ts b/.storybook/webpack.config.ts index 4d638020cd42..a083c35d6ee7 100644 --- a/.storybook/webpack.config.ts +++ b/.storybook/webpack.config.ts @@ -3,6 +3,7 @@ /* eslint-disable no-param-reassign */ /* eslint-disable @typescript-eslint/naming-convention */ +import type Environment from 'config/webpack/types'; import dotenv from 'dotenv'; import path from 'path'; import {DefinePlugin} from 'webpack'; @@ -18,6 +19,8 @@ type CustomWebpackConfig = { }; }; +type CustomWebpackFunctionProps = ({file, platform}: Environment) => CustomWebpackConfig; + let envFile: string; switch (process.env.ENV) { case 'production': @@ -31,9 +34,9 @@ switch (process.env.ENV) { } const env = dotenv.config({path: path.resolve(__dirname, `../${envFile}`)}); -const custom: CustomWebpackConfig = require('../config/webpack/webpack.common').default({ - envFile, -}); +const customFunction: CustomWebpackFunctionProps = require('../config/webpack/webpack.common'); + +const custom: CustomWebpackConfig = customFunction({file: envFile}); const webpackConfig = ({config}: {config: Configuration}) => { if (!config.resolve) { diff --git a/config/webpack/webpack.common.ts b/config/webpack/webpack.common.ts index 9d397b9557a3..5dbc3f182f1a 100644 --- a/config/webpack/webpack.common.ts +++ b/config/webpack/webpack.common.ts @@ -1,3 +1,4 @@ +import type WebpackPlugin from '@vue/preload-webpack-plugin'; import {CleanWebpackPlugin} from 'clean-webpack-plugin'; import CopyPlugin from 'copy-webpack-plugin'; import dotenv from 'dotenv'; @@ -11,7 +12,7 @@ import CustomVersionFilePlugin from './CustomVersionFilePlugin'; import type Environment from './types'; // require is necessary, there are no types for this package and the declaration file can't be seen by the build process which causes an error. -const PreloadWebpackPlugin = require('@vue/preload-webpack-plugin'); +const PreloadWebpackPlugin: typeof WebpackPlugin = require('@vue/preload-webpack-plugin'); const includeModules = [ 'react-native-animatable', diff --git a/src/components/Tooltip/BaseTooltip/index.tsx b/src/components/Tooltip/BaseTooltip/index.tsx index 5f6c38d19bff..0bfaa689d948 100644 --- a/src/components/Tooltip/BaseTooltip/index.tsx +++ b/src/components/Tooltip/BaseTooltip/index.tsx @@ -190,7 +190,8 @@ function Tooltip( (e: MouseEvent) => { updateTargetAndMousePosition(e); if (React.isValidElement(children)) { - children.props.onMouseEnter?.(e); + const onMouseEnter: (e: MouseEvent) => void | undefined = children.props.onMouseEnter; + onMouseEnter?.(e); } }, [children, updateTargetAndMousePosition], diff --git a/src/components/WalletStatementModal/index.tsx b/src/components/WalletStatementModal/index.tsx index d469eedc9761..9bc715db706f 100644 --- a/src/components/WalletStatementModal/index.tsx +++ b/src/components/WalletStatementModal/index.tsx @@ -18,7 +18,7 @@ function WalletStatementModal({statementPageURL, session}: WalletStatementProps) /** * Handles in-app navigation for iframe links */ - const navigate = (event: MessageEvent) => { + const navigate = (event: MessageEvent<{url: string; type: string}>) => { if (!event.data?.type || (event.data.type !== CONST.WALLET.WEB_MESSAGE_TYPE.STATEMENT && event.data.type !== CONST.WALLET.WEB_MESSAGE_TYPE.CONCIERGE)) { return; } diff --git a/src/libs/Environment/betaChecker/index.android.ts b/src/libs/Environment/betaChecker/index.android.ts index da65ae1b7383..975cddf210c9 100644 --- a/src/libs/Environment/betaChecker/index.android.ts +++ b/src/libs/Environment/betaChecker/index.android.ts @@ -4,7 +4,7 @@ import * as AppUpdate from '@libs/actions/AppUpdate'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import pkg from '../../../../package.json'; -import type IsBetaBuild from './types'; +import type {IsBetaBuild} from './types'; let isLastSavedBeta = false; Onyx.connect({ diff --git a/src/libs/Environment/betaChecker/index.ios.ts b/src/libs/Environment/betaChecker/index.ios.ts index dae79dabfd2b..05e3c7244a2a 100644 --- a/src/libs/Environment/betaChecker/index.ios.ts +++ b/src/libs/Environment/betaChecker/index.ios.ts @@ -1,12 +1,13 @@ import {NativeModules} from 'react-native'; -import type IsBetaBuild from './types'; +import type {EnvironmentCheckerProps, IsBetaBuild} from './types'; /** * Check to see if the build is staging (TestFlight) or production */ function isBetaBuild(): IsBetaBuild { return new Promise((resolve) => { - NativeModules.EnvironmentChecker.isBeta().then((isBeta: boolean) => { + const {EnvironmentChecker} = NativeModules; + (EnvironmentChecker as EnvironmentCheckerProps).isBeta().then((isBeta: boolean) => { resolve(isBeta); }); }); diff --git a/src/libs/Environment/betaChecker/index.ts b/src/libs/Environment/betaChecker/index.ts index ce1668759c8c..eaa8bfbc1500 100644 --- a/src/libs/Environment/betaChecker/index.ts +++ b/src/libs/Environment/betaChecker/index.ts @@ -1,4 +1,4 @@ -import type IsBetaBuild from './types'; +import type {IsBetaBuild} from './types'; /** * There's no beta build in non native diff --git a/src/libs/Environment/betaChecker/types.ts b/src/libs/Environment/betaChecker/types.ts index 61ce4bc9cec4..c519e0cb67f7 100644 --- a/src/libs/Environment/betaChecker/types.ts +++ b/src/libs/Environment/betaChecker/types.ts @@ -1,3 +1,7 @@ type IsBetaBuild = Promise; -export default IsBetaBuild; +type EnvironmentCheckerProps = { + isBeta: () => IsBetaBuild; +}; + +export type {IsBetaBuild, EnvironmentCheckerProps}; diff --git a/src/libs/StartupTimer/index.native.ts b/src/libs/StartupTimer/index.native.ts index 4e9c91222104..341bdbf25d99 100644 --- a/src/libs/StartupTimer/index.native.ts +++ b/src/libs/StartupTimer/index.native.ts @@ -6,8 +6,7 @@ import type StartupTimer from './types'; */ const startupTimer: StartupTimer = { stop: () => { - const {StartupTimer} = NativeModules; - (StartupTimer as StartupTimer).stop(); + (NativeModules.StartupTimer as StartupTimer).stop(); }, }; From 6ae0cca35b8a926871e8b475bd2e5d68ccd7d2bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barbara=20Gawe=C5=82-Kucab?= Date: Tue, 28 May 2024 16:42:59 +0200 Subject: [PATCH 030/130] wip --- jest/setup.ts | 4 ++-- src/components/Tooltip/PopoverAnchorTooltip.tsx | 10 ++++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/jest/setup.ts b/jest/setup.ts index 174e59a7e493..416306ce8426 100644 --- a/jest/setup.ts +++ b/jest/setup.ts @@ -21,8 +21,8 @@ jest.mock('react-native-onyx/dist/storage', () => mockStorage); jest.mock('react-native/Libraries/EventEmitter/NativeEventEmitter'); // Turn off the console logs for timing events. They are not relevant for unit tests and create a lot of noise -jest.spyOn(console, 'debug').mockImplementation((...params) => { - if (params[0].indexOf('Timing:') === 0) { +jest.spyOn(console, 'debug').mockImplementation((...params: string[]) => { + if (params[0].startsWith('Timing:')) { return; } diff --git a/src/components/Tooltip/PopoverAnchorTooltip.tsx b/src/components/Tooltip/PopoverAnchorTooltip.tsx index 792977ff7c43..693de83fa5d7 100644 --- a/src/components/Tooltip/PopoverAnchorTooltip.tsx +++ b/src/components/Tooltip/PopoverAnchorTooltip.tsx @@ -10,8 +10,14 @@ function PopoverAnchorTooltip({shouldRender = true, children, ...props}: Tooltip const isPopoverRelatedToTooltipOpen = useMemo(() => { // eslint-disable-next-line @typescript-eslint/dot-notation - const tooltipNode = tooltipRef.current?.['_childNode'] ?? null; - if (isOpen && popover?.anchorRef?.current && tooltipNode && (tooltipNode.contains(popover.anchorRef.current) || tooltipNode === popover.anchorRef.current)) { + const tooltipNode: Node | null = tooltipRef.current?.['_childNode'] ?? null; + + if ( + isOpen && + popover?.anchorRef?.current && + tooltipNode && + ((popover.anchorRef.current instanceof Node && tooltipNode.contains(popover.anchorRef.current)) || tooltipNode === popover.anchorRef.current) + ) { return true; } From 2cbb5457f48c4f72827433d7af106e4c8fe6c7e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barbara=20Gawe=C5=82-Kucab?= Date: Tue, 28 May 2024 17:21:48 +0200 Subject: [PATCH 031/130] fix tests errors --- tests/unit/postTestBuildComment.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tests/unit/postTestBuildComment.ts b/tests/unit/postTestBuildComment.ts index 310809cd4f83..6a7f8182be1e 100644 --- a/tests/unit/postTestBuildComment.ts +++ b/tests/unit/postTestBuildComment.ts @@ -16,13 +16,17 @@ jest.spyOn(GithubUtils, 'octokit', 'get').mockReturnValue({ }, } as RestEndpointMethods); -// @ts-expect-error -- it's a static getter -jest.spyOn(GithubUtils, 'paginate', 'get').mockReturnValue((endpoint: (params: Record) => Promise<{data: TData}>, params: Record) => - endpoint(params).then((response) => response.data), -); +function mockImplementation(endpoint: (params: Record) => Promise<{data: TData}>, params: Record) { + return endpoint(params).then((response) => response.data); +} -// @ts-expect-error -- it's a static getter -jest.spyOn(GithubUtils, 'graphql', 'get').mockReturnValue(mockGraphql); +Object.defineProperty(GithubUtils, 'paginate', { + get: () => mockImplementation, +}); + +Object.defineProperty(GithubUtils, 'graphql', { + get: () => mockGraphql, +}); jest.mock('@actions/github', () => ({ context: { From b3f49a50777fd81e34c37d2e414ec01af7d241ba Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 30 May 2024 10:33:51 +0700 Subject: [PATCH 032/130] 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 033/130] 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 034/130] 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 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 035/130] 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 98d6036912c90eff30bd32d8823035c6d4e80c4a Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 3 Jun 2024 15:32:19 +0700 Subject: [PATCH 036/130] 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 23e36e637db60f1421deba05268b55e6a03d365b Mon Sep 17 00:00:00 2001 From: Cong Pham Date: Mon, 3 Jun 2024 21:13:19 +0700 Subject: [PATCH 037/130] 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 038/130] 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 039/130] 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 040/130] 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 e992245d7a2b00d9f8476b3f525fc3fe62c4a859 Mon Sep 17 00:00:00 2001 From: Cong Pham Date: Tue, 4 Jun 2024 07:48:31 +0700 Subject: [PATCH 041/130] 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 042/130] 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 043/130] 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 044/130] 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 045/130] 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 224ea5ae9f8fb816d2bf270207b9338aa5fc6459 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Tue, 4 Jun 2024 13:44:22 +0700 Subject: [PATCH 046/130] 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 8bdd994bac220eeeba23545f8453ed3ebb412c56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barbara=20Gawe=C5=82-Kucab?= Date: Tue, 4 Jun 2024 14:31:34 +0200 Subject: [PATCH 047/130] define types for NativeModules --- src/libs/Environment/betaChecker/index.ios.ts | 5 ++--- src/libs/Environment/betaChecker/types.ts | 4 ++-- src/libs/StartupTimer/index.native.ts | 2 +- .../ComposerWithSuggestions/ComposerWithSuggestions.tsx | 6 +----- src/types/modules/react-native.d.ts | 9 +++++++++ 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/libs/Environment/betaChecker/index.ios.ts b/src/libs/Environment/betaChecker/index.ios.ts index 05e3c7244a2a..3220d9b00e05 100644 --- a/src/libs/Environment/betaChecker/index.ios.ts +++ b/src/libs/Environment/betaChecker/index.ios.ts @@ -1,13 +1,12 @@ import {NativeModules} from 'react-native'; -import type {EnvironmentCheckerProps, IsBetaBuild} from './types'; +import type {IsBetaBuild} from './types'; /** * Check to see if the build is staging (TestFlight) or production */ function isBetaBuild(): IsBetaBuild { return new Promise((resolve) => { - const {EnvironmentChecker} = NativeModules; - (EnvironmentChecker as EnvironmentCheckerProps).isBeta().then((isBeta: boolean) => { + NativeModules.EnvironmentChecker.isBeta().then((isBeta: boolean) => { resolve(isBeta); }); }); diff --git a/src/libs/Environment/betaChecker/types.ts b/src/libs/Environment/betaChecker/types.ts index c519e0cb67f7..ef205425a309 100644 --- a/src/libs/Environment/betaChecker/types.ts +++ b/src/libs/Environment/betaChecker/types.ts @@ -1,7 +1,7 @@ type IsBetaBuild = Promise; -type EnvironmentCheckerProps = { +type EnvironmentCheckerModule = { isBeta: () => IsBetaBuild; }; -export type {IsBetaBuild, EnvironmentCheckerProps}; +export type {IsBetaBuild, EnvironmentCheckerModule}; diff --git a/src/libs/StartupTimer/index.native.ts b/src/libs/StartupTimer/index.native.ts index 341bdbf25d99..8aa185d81146 100644 --- a/src/libs/StartupTimer/index.native.ts +++ b/src/libs/StartupTimer/index.native.ts @@ -6,7 +6,7 @@ import type StartupTimer from './types'; */ const startupTimer: StartupTimer = { stop: () => { - (NativeModules.StartupTimer as StartupTimer).stop(); + NativeModules.StartupTimer.stop(); }, }; diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx index bca6ab36e54a..8112bdc6dae0 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx @@ -182,11 +182,7 @@ type SwitchToCurrentReportProps = { callback: () => void; }; -type RNTextInputResetProps = { - resetKeyboardInput: (nodeHandle: number | null) => void; -}; - -const RNTextInputReset: RNTextInputResetProps = NativeModules.RNTextInputReset; +const {RNTextInputReset} = NativeModules; const isIOSNative = getPlatform() === CONST.PLATFORM.IOS; diff --git a/src/types/modules/react-native.d.ts b/src/types/modules/react-native.d.ts index a9ff35c97343..c4389ec1c0b5 100644 --- a/src/types/modules/react-native.d.ts +++ b/src/types/modules/react-native.d.ts @@ -7,11 +7,17 @@ import type {CSSProperties, FocusEventHandler, KeyboardEventHandler, MouseEventHandler, PointerEventHandler, UIEventHandler, WheelEventHandler} from 'react'; import 'react-native'; import type {BootSplashModule} from '@libs/BootSplash/types'; +import type {EnvironmentCheckerModule} from '@libs/Environment/betaChecker/types'; +import type StartupTimer from '@libs/StartupTimer/types'; type HybridAppModule = { closeReactNativeApp: () => void; }; +type RNTextInputResetModule = { + resetKeyboardInput: (nodeHandle: number | null) => void; +}; + declare module 'react-native' { // <------ REACT NATIVE WEB (0.19.0) ------> // Extracted from react-native-web, packages/react-native-web/src/exports/View/types.js @@ -367,6 +373,9 @@ declare module 'react-native' { interface NativeModulesStatic { BootSplash: BootSplashModule; HybridAppModule: HybridAppModule; + StartupTimer: StartupTimer; + RNTextInputReset: RNTextInputResetModule; + EnvironmentChecker: EnvironmentCheckerModule; } namespace Animated { From 2b2ab093f6856a24418ac0b1c0e95161321c0d7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barbara=20Gawe=C5=82-Kucab?= Date: Tue, 4 Jun 2024 15:22:42 +0200 Subject: [PATCH 048/130] add JSDoc to isAnimatedTextInputFocused --- .../TextInput/BaseTextInput/isAnimatedTextInputFocused.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/TextInput/BaseTextInput/isAnimatedTextInputFocused.ts b/src/components/TextInput/BaseTextInput/isAnimatedTextInputFocused.ts index b9dd98041362..6d9bdca8b347 100644 --- a/src/components/TextInput/BaseTextInput/isAnimatedTextInputFocused.ts +++ b/src/components/TextInput/BaseTextInput/isAnimatedTextInputFocused.ts @@ -1,6 +1,7 @@ import type {AnimatedTextInputRef} from '@components/RNTextInput'; import type {BaseTextInputRef} from './types'; +/** Checks that text input has the isFocused method and is focused. */ export default function isAnimatedTextInputFocused(textInput: React.MutableRefObject): boolean | null { return textInput.current && 'isFocused' in textInput.current && (textInput.current as AnimatedTextInputRef).isFocused(); } From 55a69fd8d29255d92016a730821cfdf78c1b863e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barbara=20Gawe=C5=82-Kucab?= Date: Tue, 4 Jun 2024 16:12:40 +0200 Subject: [PATCH 049/130] fix webpack plugin props --- config/webpack/webpack.common.ts | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/config/webpack/webpack.common.ts b/config/webpack/webpack.common.ts index 5dbc3f182f1a..4402f135bd8e 100644 --- a/config/webpack/webpack.common.ts +++ b/config/webpack/webpack.common.ts @@ -1,18 +1,30 @@ -import type WebpackPlugin from '@vue/preload-webpack-plugin'; import {CleanWebpackPlugin} from 'clean-webpack-plugin'; import CopyPlugin from 'copy-webpack-plugin'; import dotenv from 'dotenv'; import fs from 'fs'; import HtmlWebpackPlugin from 'html-webpack-plugin'; import path from 'path'; -import type {Configuration} from 'webpack'; +import type {Compiler, Configuration} from 'webpack'; import {DefinePlugin, EnvironmentPlugin, IgnorePlugin, ProvidePlugin} from 'webpack'; import {BundleAnalyzerPlugin} from 'webpack-bundle-analyzer'; import CustomVersionFilePlugin from './CustomVersionFilePlugin'; import type Environment from './types'; +// importing anything from @vue/preload-webpack-plugin causes an error +type Options = { + rel: string; + as: string; + fileWhitelist: RegExp[]; + include: string; +}; + +type PreloadWebpackPluginProps = { + new (options?: Options): PreloadWebpackPluginProps; + apply: (compiler: Compiler) => void; +}; + // require is necessary, there are no types for this package and the declaration file can't be seen by the build process which causes an error. -const PreloadWebpackPlugin: typeof WebpackPlugin = require('@vue/preload-webpack-plugin'); +const PreloadWebpackPlugin: PreloadWebpackPluginProps = require('@vue/preload-webpack-plugin'); const includeModules = [ 'react-native-animatable', From 8365690feaa4fba5fda352ec3a69f80ac5f00645 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Tue, 4 Jun 2024 18:24:35 +0200 Subject: [PATCH 050/130] 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 051/130] 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 0723e439c04f4516c0a64c275f513d47252df210 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barbara=20Gawe=C5=82-Kucab?= Date: Wed, 5 Jun 2024 12:02:55 +0200 Subject: [PATCH 052/130] fix ts error --- src/components/MoneyRequestAmountInput.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyRequestAmountInput.tsx b/src/components/MoneyRequestAmountInput.tsx index 7ef39a1c5ae6..926bf6e6b566 100644 --- a/src/components/MoneyRequestAmountInput.tsx +++ b/src/components/MoneyRequestAmountInput.tsx @@ -197,7 +197,7 @@ function MoneyRequestAmountInput( })); useEffect(() => { - if (!currency || typeof amount !== 'number' || (formatAmountOnBlur && isAnimatedTextInputFocused(textInput)) || shouldKeepUserInput) { + if ((!currency || typeof amount !== 'number' || (formatAmountOnBlur && isAnimatedTextInputFocused(textInput))) ?? shouldKeepUserInput) { return; } const frontendAmount = onFormatAmount(amount, currency); From 42f1f6dec1f325d6953a05ce879960bd6b8a3a48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barbara=20Gawe=C5=82-Kucab?= Date: Wed, 5 Jun 2024 14:39:55 +0200 Subject: [PATCH 053/130] review adjustments --- .../javascript/getGraphiteString/getGraphiteString.ts | 4 ++-- .storybook/webpack.config.ts | 4 ++-- .../TextInput/BaseTextInput/isAnimatedTextInputFocused.ts | 2 +- src/components/WalletStatementModal/index.tsx | 4 ++-- src/components/WalletStatementModal/types.ts | 7 ++++++- 5 files changed, 13 insertions(+), 8 deletions(-) diff --git a/.github/actions/javascript/getGraphiteString/getGraphiteString.ts b/.github/actions/javascript/getGraphiteString/getGraphiteString.ts index 28f5fb40d5f2..5231caa79ed5 100644 --- a/.github/actions/javascript/getGraphiteString/getGraphiteString.ts +++ b/.github/actions/javascript/getGraphiteString/getGraphiteString.ts @@ -1,7 +1,7 @@ import * as core from '@actions/core'; import fs from 'fs'; -type RegressionEntryProps = { +type RegressionEntry = { metadata?: { creationDate: string; }; @@ -33,7 +33,7 @@ const run = () => { } try { - const current: RegressionEntryProps = JSON.parse(entry); + const current: RegressionEntry = JSON.parse(entry); // Extract timestamp, Graphite accepts timestamp in seconds if (current.metadata?.creationDate) { diff --git a/.storybook/webpack.config.ts b/.storybook/webpack.config.ts index a083c35d6ee7..2f758176cdca 100644 --- a/.storybook/webpack.config.ts +++ b/.storybook/webpack.config.ts @@ -19,7 +19,7 @@ type CustomWebpackConfig = { }; }; -type CustomWebpackFunctionProps = ({file, platform}: Environment) => CustomWebpackConfig; +type CustomWebpackFunction = ({file, platform}: Environment) => CustomWebpackConfig; let envFile: string; switch (process.env.ENV) { @@ -34,7 +34,7 @@ switch (process.env.ENV) { } const env = dotenv.config({path: path.resolve(__dirname, `../${envFile}`)}); -const customFunction: CustomWebpackFunctionProps = require('../config/webpack/webpack.common'); +const customFunction: CustomWebpackFunction = require('../config/webpack/webpack.common'); const custom: CustomWebpackConfig = customFunction({file: envFile}); diff --git a/src/components/TextInput/BaseTextInput/isAnimatedTextInputFocused.ts b/src/components/TextInput/BaseTextInput/isAnimatedTextInputFocused.ts index 6d9bdca8b347..1a487eeb3b11 100644 --- a/src/components/TextInput/BaseTextInput/isAnimatedTextInputFocused.ts +++ b/src/components/TextInput/BaseTextInput/isAnimatedTextInputFocused.ts @@ -1,7 +1,7 @@ import type {AnimatedTextInputRef} from '@components/RNTextInput'; import type {BaseTextInputRef} from './types'; -/** Checks that text input has the isFocused method and is focused. */ +/** Checks that text input has the isFocused method and is focused. */ export default function isAnimatedTextInputFocused(textInput: React.MutableRefObject): boolean | null { return textInput.current && 'isFocused' in textInput.current && (textInput.current as AnimatedTextInputRef).isFocused(); } diff --git a/src/components/WalletStatementModal/index.tsx b/src/components/WalletStatementModal/index.tsx index 9bc715db706f..e2b854842163 100644 --- a/src/components/WalletStatementModal/index.tsx +++ b/src/components/WalletStatementModal/index.tsx @@ -8,7 +8,7 @@ import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type {WalletStatementOnyxProps, WalletStatementProps} from './types'; +import type {WalletStatementMessage, WalletStatementOnyxProps, WalletStatementProps} from './types'; function WalletStatementModal({statementPageURL, session}: WalletStatementProps) { const styles = useThemeStyles(); @@ -18,7 +18,7 @@ function WalletStatementModal({statementPageURL, session}: WalletStatementProps) /** * Handles in-app navigation for iframe links */ - const navigate = (event: MessageEvent<{url: string; type: string}>) => { + const navigate = (event: MessageEvent) => { if (!event.data?.type || (event.data.type !== CONST.WALLET.WEB_MESSAGE_TYPE.STATEMENT && event.data.type !== CONST.WALLET.WEB_MESSAGE_TYPE.CONCIERGE)) { return; } diff --git a/src/components/WalletStatementModal/types.ts b/src/components/WalletStatementModal/types.ts index 567202730b7d..d9a6d299da65 100644 --- a/src/components/WalletStatementModal/types.ts +++ b/src/components/WalletStatementModal/types.ts @@ -11,4 +11,9 @@ type WalletStatementProps = WalletStatementOnyxProps & { statementPageURL: string; }; -export type {WalletStatementProps, WalletStatementOnyxProps}; +type WalletStatementMessage = { + url: string; + type: string; +}; + +export type {WalletStatementProps, WalletStatementOnyxProps, WalletStatementMessage}; From 633a2855a05b3524c2ec8068586c0519b264464e Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Wed, 5 Jun 2024 15:09:42 +0200 Subject: [PATCH 054/130] 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 15:51:03 +0200 Subject: [PATCH 055/130] fix storybook --- .storybook/webpack.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.storybook/webpack.config.ts b/.storybook/webpack.config.ts index 2f758176cdca..09d881846b1e 100644 --- a/.storybook/webpack.config.ts +++ b/.storybook/webpack.config.ts @@ -34,7 +34,7 @@ switch (process.env.ENV) { } const env = dotenv.config({path: path.resolve(__dirname, `../${envFile}`)}); -const customFunction: CustomWebpackFunction = require('../config/webpack/webpack.common'); +const customFunction: CustomWebpackFunction = require('../config/webpack/webpack.common').default; const custom: CustomWebpackConfig = customFunction({file: envFile}); From 044c287d8d357cee8c858018d1943cc203257269 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Wed, 5 Jun 2024 16:13:43 +0200 Subject: [PATCH 056/130] 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 729aaa17b362954ef9805a59a4e57d09d799878a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barbara=20Gawe=C5=82-Kucab?= Date: Wed, 5 Jun 2024 17:06:55 +0200 Subject: [PATCH 057/130] rename preload webpack plugin type --- config/webpack/webpack.common.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/webpack/webpack.common.ts b/config/webpack/webpack.common.ts index 4402f135bd8e..bedd7e50ef94 100644 --- a/config/webpack/webpack.common.ts +++ b/config/webpack/webpack.common.ts @@ -18,13 +18,13 @@ type Options = { include: string; }; -type PreloadWebpackPluginProps = { - new (options?: Options): PreloadWebpackPluginProps; +type PreloadWebpackPluginClass = { + new (options?: Options): PreloadWebpackPluginClass; apply: (compiler: Compiler) => void; }; // require is necessary, there are no types for this package and the declaration file can't be seen by the build process which causes an error. -const PreloadWebpackPlugin: PreloadWebpackPluginProps = require('@vue/preload-webpack-plugin'); +const PreloadWebpackPlugin: PreloadWebpackPluginClass = require('@vue/preload-webpack-plugin'); const includeModules = [ 'react-native-animatable', From 730a852ea0ddb0b426bd7d8205277d4db366e451 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 6 Jun 2024 10:27:58 +0700 Subject: [PATCH 058/130] 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 b18b68111d7c6f9894cc8866f5129ed0b9716754 Mon Sep 17 00:00:00 2001 From: Cong Pham Date: Thu, 6 Jun 2024 13:49:42 +0700 Subject: [PATCH 059/130] 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 060/130] 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 09:36:16 +0200 Subject: [PATCH 061/130] add create download queue types --- desktop/createDownloadQueue.ts | 1 + desktop/main.ts | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/desktop/createDownloadQueue.ts b/desktop/createDownloadQueue.ts index 132848c5da9e..6f73e28d5293 100644 --- a/desktop/createDownloadQueue.ts +++ b/desktop/createDownloadQueue.ts @@ -114,3 +114,4 @@ const createDownloadQueue = () => { }; export default createDownloadQueue; +export type {DownloadItem}; diff --git a/desktop/main.ts b/desktop/main.ts index 0f4774d3b73b..c4338e7d49b3 100644 --- a/desktop/main.ts +++ b/desktop/main.ts @@ -13,9 +13,15 @@ import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import type PlatformSpecificUpdater from '@src/setup/platformSetup/types'; import type {Locale} from '@src/types/onyx'; +import type {DownloadItem} from './createDownloadQueue'; import ELECTRON_EVENTS from './ELECTRON_EVENTS'; -const createDownloadQueue = require('./createDownloadQueue').default; +type CreateDownloadQueue = () => { + enqueueDownloadItem: (item: DownloadItem) => void; + dequeueDownloadItem: () => DownloadItem | undefined; +}; + +const createDownloadQueue: CreateDownloadQueue = require('./createDownloadQueue').default; const port = process.env.PORT ?? 8082; const {DESKTOP_SHORTCUT_ACCELERATOR, LOCALES} = CONST; @@ -617,7 +623,7 @@ const mainWindow = (): Promise => { const downloadQueue = createDownloadQueue(); ipcMain.on(ELECTRON_EVENTS.DOWNLOAD, (event, downloadData) => { - const downloadItem = { + const downloadItem: DownloadItem = { ...downloadData, win: browserWindow, }; From 193f149bdb50b48b4ae5dbdf20487d812216653b Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Thu, 6 Jun 2024 10:32:29 +0200 Subject: [PATCH 062/130] 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 063/130] 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 064/130] 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 065/130] 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 066/130] 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 067/130] 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 3bfc8c09c4fbf52186d25ccd6842c577533615e6 Mon Sep 17 00:00:00 2001 From: Daniel Gale-Rosen Date: Thu, 6 Jun 2024 12:35:11 -0400 Subject: [PATCH 068/130] remove check for pending actions --- 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 8b251cf159e4..025141dcf12f 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -944,7 +944,7 @@ function ReportActionItem({ const iouReportID = isIOUReport(action) && action.originalMessage.IOUReportID ? action.originalMessage.IOUReportID.toString() : '0'; const transactionsWithReceipts = ReportUtils.getTransactionsWithReceipts(iouReportID); - const isWhisper = whisperedTo.length > 0 && transactionsWithReceipts.length === 0 && !action.pendingAction; + const isWhisper = whisperedTo.length > 0 && transactionsWithReceipts.length === 0; const whisperedToPersonalDetails = isWhisper ? (Object.values(personalDetails ?? {}).filter((details) => whisperedTo.includes(details?.accountID ?? -1)) as OnyxTypes.PersonalDetails[]) : []; From ec9bc7c7b575b6277351ea6a8033eb5346d18d6e Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 7 Jun 2024 11:28:49 +0700 Subject: [PATCH 069/130] 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 6f67061b706b435d997ea96f4461a9c9b97af8db Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Fri, 7 Jun 2024 09:26:30 +0200 Subject: [PATCH 070/130] 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 071/130] 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 355deae246b1c972674769ca6e56ddc302c552d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barbara=20Gawe=C5=82-Kucab?= Date: Fri, 7 Jun 2024 12:01:18 +0200 Subject: [PATCH 072/130] change parse run opts --- workflow_tests/utils/ExtendedAct.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/workflow_tests/utils/ExtendedAct.ts b/workflow_tests/utils/ExtendedAct.ts index 7b35eb260ba3..76124c22c222 100644 --- a/workflow_tests/utils/ExtendedAct.ts +++ b/workflow_tests/utils/ExtendedAct.ts @@ -17,8 +17,7 @@ type ActOptions = { // @ts-expect-error Override shouldn't be done on private methods wait until https://github.com/kiegroup/act-js/issues/77 is resolved or try to create a params workaround class ExtendedAct extends Act { async parseRunOpts(opts?: ExtendedActOpts): Promise { - const parseSuperRunOpts: (opts?: ExtendedActOpts) => Promise = super['parseRunOpts']; - const {cwd, actArguments, proxy} = await parseSuperRunOpts(opts); + const {cwd, actArguments, proxy} = await (super['parseRunOpts'] as (opts?: ExtendedActOpts) => Promise)(opts); if (opts?.actor) { actArguments.push('--actor', opts.actor); From 615cc5c0d8f5fffdf1b729f8002c23822ff5e333 Mon Sep 17 00:00:00 2001 From: c3024 Date: Fri, 7 Jun 2024 15:56:47 +0530 Subject: [PATCH 073/130] correct last message text in selfDM --- src/libs/OptionsListUtils.ts | 2 +- src/libs/ReportActionsUtils.ts | 16 ++++------------ src/pages/home/ReportScreen.tsx | 2 +- 3 files changed, 6 insertions(+), 14 deletions(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index fef64b712ad6..4f6d4e0d97eb 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -289,7 +289,7 @@ Onyx.connect({ // If the report is a one-transaction report and has , we need to return the combined reportActions so that the LHN can display modifications // to the transaction thread or the report itself - const transactionThreadReportID = ReportActionUtils.getOneTransactionThreadReportID(reportID, actions[reportActions[0]], true); + const transactionThreadReportID = ReportActionUtils.getOneTransactionThreadReportID(reportID, actions[reportActions[0]]); if (transactionThreadReportID) { const transactionThreadReportActionsArray = Object.values(actions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReportID}`] ?? {}); sortedReportActions = ReportActionUtils.getCombinedReportActions(reportActionsArray, transactionThreadReportActionsArray, reportID); diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 1830edd91b68..b912009a4abf 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -872,18 +872,10 @@ function isTaskAction(reportAction: OnyxEntry): boolean { * Gets the reportID for the transaction thread associated with a report by iterating over the reportActions and identifying the IOU report actions. * Returns a reportID if there is exactly one transaction thread for the report, and null otherwise. */ -function getOneTransactionThreadReportID( - reportID: string, - reportActions: OnyxEntry | ReportAction[], - skipReportTypeCheck: boolean | undefined = undefined, - isOffline: boolean | undefined = undefined, -): string | null { - if (!skipReportTypeCheck) { - // If the report is not an IOU, Expense report, or Invoice, it shouldn't be treated as one-transaction report. - const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; - if (report?.type !== CONST.REPORT.TYPE.IOU && report?.type !== CONST.REPORT.TYPE.EXPENSE && report?.type !== CONST.REPORT.TYPE.INVOICE) { - return null; - } +function getOneTransactionThreadReportID(reportID: string, reportActions: OnyxEntry | ReportAction[], isOffline: boolean | undefined = undefined): string | null { + const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; + if (report?.type !== CONST.REPORT.TYPE.IOU && report?.type !== CONST.REPORT.TYPE.EXPENSE && report?.type !== CONST.REPORT.TYPE.INVOICE) { + return null; } const reportActionsArray = Object.values(reportActions ?? {}); diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 5d747a7cfa16..dcdb6a0e8882 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -332,7 +332,7 @@ function ReportScreen({ ); const transactionThreadReportID = useMemo( - () => ReportActionsUtils.getOneTransactionThreadReportID(report.reportID, reportActions ?? [], false, isOffline), + () => ReportActionsUtils.getOneTransactionThreadReportID(report.reportID, reportActions ?? [], isOffline), [report.reportID, reportActions, isOffline], ); From 07a3cd677bcb161d826418b34df169fb1fcf0f92 Mon Sep 17 00:00:00 2001 From: c3024 Date: Fri, 7 Jun 2024 17:31:25 +0530 Subject: [PATCH 074/130] add back the accidentally removed comment --- src/libs/ReportActionsUtils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index b912009a4abf..0a832d0cfd50 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -873,6 +873,7 @@ function isTaskAction(reportAction: OnyxEntry): boolean { * Returns a reportID if there is exactly one transaction thread for the report, and null otherwise. */ function getOneTransactionThreadReportID(reportID: string, reportActions: OnyxEntry | ReportAction[], isOffline: boolean | undefined = undefined): string | null { + // If the report is not an IOU, Expense report, or Invoice, it shouldn't be treated as one-transaction report. const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; if (report?.type !== CONST.REPORT.TYPE.IOU && report?.type !== CONST.REPORT.TYPE.EXPENSE && report?.type !== CONST.REPORT.TYPE.INVOICE) { return null; From a1b6cd6f7f4a6b9c0a4422adb037b4b4cfb79ec9 Mon Sep 17 00:00:00 2001 From: Alex Beaman Date: Fri, 7 Jun 2024 15:48:38 +0300 Subject: [PATCH 075/130] Set optimistic submit as instant --- src/libs/actions/Policy/Policy.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index 0e168f973078..8d72b0a5ac65 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -1452,6 +1452,7 @@ function createDraftInitialWorkspace(policyOwnerEmail = '', policyName = '', pol customUnits, makeMeAdmin, autoReporting: true, + autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.INSTANT, employeeList: { [sessionEmail]: { role: CONST.POLICY.ROLE.ADMIN, @@ -1574,6 +1575,7 @@ function buildPolicyData(policyOwnerEmail = '', makeMeAdmin = false, policyName outputCurrency, pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, autoReporting: true, + autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.INSTANT, approvalMode: CONST.POLICY.APPROVAL_MODE.OPTIONAL, harvesting: { enabled: true, @@ -1837,6 +1839,7 @@ function createDraftWorkspace(policyOwnerEmail = '', makeMeAdmin = false, policy outputCurrency, pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, autoReporting: true, + autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.INSTANT, approvalMode: CONST.POLICY.APPROVAL_MODE.OPTIONAL, harvesting: { enabled: true, @@ -2109,6 +2112,7 @@ function createWorkspaceFromIOUPayment(iouReport: Report | EmptyObject): string outputCurrency: CONST.CURRENCY.USD, pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, autoReporting: true, + autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.INSTANT, approvalMode: CONST.POLICY.APPROVAL_MODE.OPTIONAL, harvesting: { enabled: true, From 7f60a5d7db1d8710412a1a22a7eba0ef1d87bfec Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Fri, 7 Jun 2024 15:47:03 +0200 Subject: [PATCH 076/130] 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 077/130] 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 58fe38188973d11947d8912b7a30464e13230e6a Mon Sep 17 00:00:00 2001 From: Yauheni Pasiukevich Date: Fri, 7 Jun 2024 17:17:33 +0200 Subject: [PATCH 078/130] 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 24875929993f87d1263bcd2ff21151c99159ff67 Mon Sep 17 00:00:00 2001 From: Yauheni Pasiukevich Date: Fri, 7 Jun 2024 17:45:28 +0200 Subject: [PATCH 079/130] 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 d7714ee7e819d55f085d9f08ecefa5296a091651 Mon Sep 17 00:00:00 2001 From: Cong Pham Date: Sat, 8 Jun 2024 00:19:26 +0700 Subject: [PATCH 080/130] 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 081/130] 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 063a481d7594b7bfaed9f5f9cda9082dc37b8ac0 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Mon, 10 Jun 2024 10:04:38 +0700 Subject: [PATCH 082/130] 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 9d915406e6522f6b7d7dc2a832eb70dbcdd131af Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Mon, 10 Jun 2024 08:59:10 +0200 Subject: [PATCH 083/130] 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 084/130] 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 085/130] 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 77548f3f2963b3a62b217953c7ebc6e82f227fde Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Mon, 10 Jun 2024 10:05:11 +0200 Subject: [PATCH 086/130] 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 087/130] 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 088/130] 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 089/130] 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 090/130] 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 7d8dbc418aea0f0e5b54ebb425a3e7b4b90178ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barbara=20Gawe=C5=82-Kucab?= Date: Mon, 10 Jun 2024 10:58:03 +0200 Subject: [PATCH 091/130] rename text input focused function and mouse event type --- src/components/AmountForm.tsx | 8 ++++---- src/components/EmojiPicker/EmojiPickerMenu/index.tsx | 8 ++++---- src/components/Hoverable/ActiveHoverable.tsx | 10 +++++----- src/components/MoneyRequestAmountInput.tsx | 4 ++-- ...imatedTextInputFocused.ts => isTextInputFocused.ts} | 2 +- src/pages/iou/MoneyRequestAmountForm.tsx | 8 ++++---- src/pages/signin/LoginForm/BaseLoginForm.tsx | 4 ++-- 7 files changed, 22 insertions(+), 22 deletions(-) rename src/components/TextInput/BaseTextInput/{isAnimatedTextInputFocused.ts => isTextInputFocused.ts} (70%) diff --git a/src/components/AmountForm.tsx b/src/components/AmountForm.tsx index c1640f769992..3319a28c58b9 100644 --- a/src/components/AmountForm.tsx +++ b/src/components/AmountForm.tsx @@ -12,7 +12,7 @@ import * as MoneyRequestUtils from '@libs/MoneyRequestUtils'; import CONST from '@src/CONST'; import BigNumberPad from './BigNumberPad'; import FormHelpMessage from './FormHelpMessage'; -import isAnimatedTextInputFocused from './TextInput/BaseTextInput/isAnimatedTextInputFocused'; +import isTextInputFocused from './TextInput/BaseTextInput/isTextInputFocused'; import type {BaseTextInputProps, BaseTextInputRef} from './TextInput/BaseTextInput/types'; import TextInputWithCurrencySymbol from './TextInputWithCurrencySymbol'; import type TextInputWithCurrencySymbolProps from './TextInputWithCurrencySymbol/types'; @@ -95,7 +95,7 @@ function AmountForm( if (!textInput.current) { return; } - if (!isAnimatedTextInputFocused(textInput)) { + if (!isTextInputFocused(textInput)) { textInput.current.focus(); } }; @@ -144,7 +144,7 @@ function AmountForm( */ const updateAmountNumberPad = useCallback( (key: string) => { - if (shouldUpdateSelection && !isAnimatedTextInputFocused(textInput)) { + if (shouldUpdateSelection && !isTextInputFocused(textInput)) { textInput.current?.focus(); } // Backspace button is pressed @@ -169,7 +169,7 @@ function AmountForm( */ const updateLongPressHandlerState = useCallback((value: boolean) => { setShouldUpdateSelection(!value); - if (!value && !isAnimatedTextInputFocused(textInput)) { + if (!value && !isTextInputFocused(textInput)) { textInput.current?.focus(); } }, []); diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.tsx b/src/components/EmojiPicker/EmojiPickerMenu/index.tsx index 582997f366c1..5a59585ad72e 100755 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.tsx @@ -7,7 +7,7 @@ import {scrollTo} from 'react-native-reanimated'; import EmojiPickerMenuItem from '@components/EmojiPicker/EmojiPickerMenuItem'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; -import isAnimatedTextInputFocused from '@components/TextInput/BaseTextInput/isAnimatedTextInputFocused'; +import isTextInputFocused from '@components/TextInput/BaseTextInput/isTextInputFocused'; import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types'; import useArrowKeyFocusManager from '@hooks/useArrowKeyFocusManager'; import useLocalize from '@hooks/useLocalize'; @@ -87,7 +87,7 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps, r } // If the input is not focused and the new index is out of range, focus the input - if (newIndex < 0 && !isAnimatedTextInputFocused(searchInputRef) && shouldFocusInputOnScreenFocus) { + if (newIndex < 0 && !isTextInputFocused(searchInputRef) && shouldFocusInputOnScreenFocus) { searchInputRef.current?.focus(); } }, @@ -166,12 +166,12 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps, r // Enable keyboard movement if tab or enter is pressed or if shift is pressed while the input // is not focused, so that the navigation and tab cycling can be done using the keyboard without // interfering with the input behaviour. - if (keyBoardEvent.key === 'Tab' || keyBoardEvent.key === 'Enter' || (keyBoardEvent.key === 'Shift' && searchInputRef.current && !isAnimatedTextInputFocused(searchInputRef))) { + if (keyBoardEvent.key === 'Tab' || keyBoardEvent.key === 'Enter' || (keyBoardEvent.key === 'Shift' && searchInputRef.current && !isTextInputFocused(searchInputRef))) { setIsUsingKeyboardMovement(true); } // We allow typing in the search box if any key is pressed apart from Arrow keys. - if (searchInputRef.current && !isAnimatedTextInputFocused(searchInputRef) && ReportUtils.shouldAutoFocusOnKeyPress(keyBoardEvent)) { + if (searchInputRef.current && !isTextInputFocused(searchInputRef) && ReportUtils.shouldAutoFocusOnKeyPress(keyBoardEvent)) { searchInputRef.current.focus(); } }, diff --git a/src/components/Hoverable/ActiveHoverable.tsx b/src/components/Hoverable/ActiveHoverable.tsx index 227e4524eb02..b58433afb17c 100644 --- a/src/components/Hoverable/ActiveHoverable.tsx +++ b/src/components/Hoverable/ActiveHoverable.tsx @@ -8,7 +8,7 @@ import type HoverableProps from './types'; type ActiveHoverableProps = Omit; -type OnMouseEventProps = (e: MouseEvent) => void; +type OnMouseEvent = (e: MouseEvent) => void; function ActiveHoverable({onHoverIn, onHoverOut, shouldHandleScroll, shouldFreezeCapture, children}: ActiveHoverableProps, outerRef: Ref) { const [isHovered, setIsHovered] = useState(false); @@ -100,10 +100,10 @@ function ActiveHoverable({onHoverIn, onHoverOut, shouldHandleScroll, shouldFreez const child = useMemo(() => getReturnValue(children, !isScrollingRef.current && isHovered), [children, isHovered]); - const childOnMouseEnter: OnMouseEventProps = child.props.onMouseEnter; - const childOnMouseLeave: OnMouseEventProps = child.props.onMouseLeave; - const childOnMouseMove: OnMouseEventProps = child.props.onMouseMove; - const childOnBlur: OnMouseEventProps = child.props.onBlur; + const childOnMouseEnter: OnMouseEvent = child.props.onMouseEnter; + const childOnMouseLeave: OnMouseEvent = child.props.onMouseLeave; + const childOnMouseMove: OnMouseEvent = child.props.onMouseMove; + const childOnBlur: OnMouseEvent = child.props.onBlur; const hoverAndForwardOnMouseEnter = useCallback( (e: MouseEvent) => { diff --git a/src/components/MoneyRequestAmountInput.tsx b/src/components/MoneyRequestAmountInput.tsx index 926bf6e6b566..00dbfec960cb 100644 --- a/src/components/MoneyRequestAmountInput.tsx +++ b/src/components/MoneyRequestAmountInput.tsx @@ -8,7 +8,7 @@ import * as CurrencyUtils from '@libs/CurrencyUtils'; import getOperatingSystem from '@libs/getOperatingSystem'; import * as MoneyRequestUtils from '@libs/MoneyRequestUtils'; import CONST from '@src/CONST'; -import isAnimatedTextInputFocused from './TextInput/BaseTextInput/isAnimatedTextInputFocused'; +import isTextInputFocused from './TextInput/BaseTextInput/isTextInputFocused'; import type {BaseTextInputRef} from './TextInput/BaseTextInput/types'; import TextInputWithCurrencySymbol from './TextInputWithCurrencySymbol'; @@ -197,7 +197,7 @@ function MoneyRequestAmountInput( })); useEffect(() => { - if ((!currency || typeof amount !== 'number' || (formatAmountOnBlur && isAnimatedTextInputFocused(textInput))) ?? shouldKeepUserInput) { + if ((!currency || typeof amount !== 'number' || (formatAmountOnBlur && isTextInputFocused(textInput))) ?? shouldKeepUserInput) { return; } const frontendAmount = onFormatAmount(amount, currency); diff --git a/src/components/TextInput/BaseTextInput/isAnimatedTextInputFocused.ts b/src/components/TextInput/BaseTextInput/isTextInputFocused.ts similarity index 70% rename from src/components/TextInput/BaseTextInput/isAnimatedTextInputFocused.ts rename to src/components/TextInput/BaseTextInput/isTextInputFocused.ts index 1a487eeb3b11..bf7ae45fa6f4 100644 --- a/src/components/TextInput/BaseTextInput/isAnimatedTextInputFocused.ts +++ b/src/components/TextInput/BaseTextInput/isTextInputFocused.ts @@ -2,6 +2,6 @@ import type {AnimatedTextInputRef} from '@components/RNTextInput'; import type {BaseTextInputRef} from './types'; /** Checks that text input has the isFocused method and is focused. */ -export default function isAnimatedTextInputFocused(textInput: React.MutableRefObject): boolean | null { +export default function isTextInputFocused(textInput: React.MutableRefObject): boolean | null { return textInput.current && 'isFocused' in textInput.current && (textInput.current as AnimatedTextInputRef).isFocused(); } diff --git a/src/pages/iou/MoneyRequestAmountForm.tsx b/src/pages/iou/MoneyRequestAmountForm.tsx index 4c4cb34c7f82..1cb6e2f09c21 100644 --- a/src/pages/iou/MoneyRequestAmountForm.tsx +++ b/src/pages/iou/MoneyRequestAmountForm.tsx @@ -10,7 +10,7 @@ import MoneyRequestAmountInput from '@components/MoneyRequestAmountInput'; import type {MoneyRequestAmountInputRef} from '@components/MoneyRequestAmountInput'; import ScrollView from '@components/ScrollView'; import SettlementButton from '@components/SettlementButton'; -import isAnimatedTextInputFocused from '@components/TextInput/BaseTextInput/isAnimatedTextInputFocused'; +import isTextInputFocused from '@components/TextInput/BaseTextInput/isTextInputFocused'; import useLocalize from '@hooks/useLocalize'; import usePrevious from '@hooks/usePrevious'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -132,7 +132,7 @@ function MoneyRequestAmountForm( return; } - if (!isAnimatedTextInputFocused(textInput)) { + if (!isTextInputFocused(textInput)) { textInput.current.focus(); } }; @@ -173,7 +173,7 @@ function MoneyRequestAmountForm( */ const updateAmountNumberPad = useCallback( (key: string) => { - if (shouldUpdateSelection && !isAnimatedTextInputFocused(textInput)) { + if (shouldUpdateSelection && !isTextInputFocused(textInput)) { textInput.current?.focus(); } const currentAmount = moneyRequestAmountInput.current?.getAmount() ?? ''; @@ -200,7 +200,7 @@ function MoneyRequestAmountForm( */ const updateLongPressHandlerState = useCallback((value: boolean) => { setShouldUpdateSelection(!value); - if (!value && !isAnimatedTextInputFocused(textInput)) { + if (!value && !isTextInputFocused(textInput)) { textInput.current?.focus(); } }, []); diff --git a/src/pages/signin/LoginForm/BaseLoginForm.tsx b/src/pages/signin/LoginForm/BaseLoginForm.tsx index f7d5db569ecc..fdd34be0bbbe 100644 --- a/src/pages/signin/LoginForm/BaseLoginForm.tsx +++ b/src/pages/signin/LoginForm/BaseLoginForm.tsx @@ -11,7 +11,7 @@ import AppleSignIn from '@components/SignInButtons/AppleSignIn'; import GoogleSignIn from '@components/SignInButtons/GoogleSignIn'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; -import isAnimatedTextInputFocused from '@components/TextInput/BaseTextInput/isAnimatedTextInputFocused'; +import isTextInputFocused from '@components/TextInput/BaseTextInput/isTextInputFocused'; import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types'; import withToggleVisibilityView from '@components/withToggleVisibilityView'; import type {WithToggleVisibilityViewProps} from '@components/withToggleVisibilityView'; @@ -201,7 +201,7 @@ function BaseLoginForm({account, credentials, closeAccount, blurOnSubmit = false if (!input.current) { return false; } - return !!isAnimatedTextInputFocused(input); + return !!isTextInputFocused(input); }, clearDataAndFocus(clearLogin = true) { if (!input.current) { From 4541ce20ddf2acb690fe9dfb3a9e76c96337e2b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barbara=20Gawe=C5=82-Kucab?= Date: Mon, 10 Jun 2024 11:08:55 +0200 Subject: [PATCH 092/130] move CreateDownloadQueue type --- desktop/createDownloadQueue.ts | 9 +++++++-- desktop/main.ts | 7 +------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/desktop/createDownloadQueue.ts b/desktop/createDownloadQueue.ts index 6f73e28d5293..4403f989263c 100644 --- a/desktop/createDownloadQueue.ts +++ b/desktop/createDownloadQueue.ts @@ -17,6 +17,11 @@ type DownloadItem = { options: Options; }; +type CreateDownloadQueue = () => { + enqueueDownloadItem: (item: DownloadItem) => void; + dequeueDownloadItem: () => DownloadItem | undefined; +}; + /** * Returns the filename with extension based on the given name and MIME type. * @param name - The name of the file. @@ -28,7 +33,7 @@ const getFilenameFromMime = (name: string, mime: string): string => { return `${name}.${extensions}`; }; -const createDownloadQueue = () => { +const createDownloadQueue: CreateDownloadQueue = () => { const downloadItemProcessor = (item: DownloadItem): Promise => new Promise((resolve, reject) => { let downloadTimeout: NodeJS.Timeout; @@ -114,4 +119,4 @@ const createDownloadQueue = () => { }; export default createDownloadQueue; -export type {DownloadItem}; +export type {DownloadItem, CreateDownloadQueue}; diff --git a/desktop/main.ts b/desktop/main.ts index c4338e7d49b3..57ea647cc3e2 100644 --- a/desktop/main.ts +++ b/desktop/main.ts @@ -13,14 +13,9 @@ import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import type PlatformSpecificUpdater from '@src/setup/platformSetup/types'; import type {Locale} from '@src/types/onyx'; -import type {DownloadItem} from './createDownloadQueue'; +import type {CreateDownloadQueue, DownloadItem} from './createDownloadQueue'; import ELECTRON_EVENTS from './ELECTRON_EVENTS'; -type CreateDownloadQueue = () => { - enqueueDownloadItem: (item: DownloadItem) => void; - dequeueDownloadItem: () => DownloadItem | undefined; -}; - const createDownloadQueue: CreateDownloadQueue = require('./createDownloadQueue').default; const port = process.env.PORT ?? 8082; From 94f0a013cba1acb5c72407e2ef01ba523d951745 Mon Sep 17 00:00:00 2001 From: Yauheni Pasiukevich Date: Mon, 10 Jun 2024 11:26:16 +0200 Subject: [PATCH 093/130] 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 f0e23c9a8305d55afdd9ffb4ccace2c90946066a Mon Sep 17 00:00:00 2001 From: Yauheni Pasiukevich Date: Mon, 10 Jun 2024 11:44:25 +0200 Subject: [PATCH 094/130] 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 cbe212e75cffb39b07da66f99475f7ec43af6caa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barbara=20Gawe=C5=82-Kucab?= Date: Mon, 10 Jun 2024 11:49:53 +0200 Subject: [PATCH 095/130] remove redundant check --- src/libs/updateMultilineInputRange/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/updateMultilineInputRange/index.ts b/src/libs/updateMultilineInputRange/index.ts index 3ee2ce6bb70c..8bdccd83db90 100644 --- a/src/libs/updateMultilineInputRange/index.ts +++ b/src/libs/updateMultilineInputRange/index.ts @@ -16,7 +16,7 @@ const updateMultilineInputRange: UpdateMultilineInputRange = (input, shouldAutoF if ('value' in input && input.value && input.setSelectionRange) { const length = input.value.length as number; - if (shouldAutoFocus && 'setSelectionRange' in input) { + if (shouldAutoFocus) { (input as HTMLInputElement).setSelectionRange(length, length); } // eslint-disable-next-line no-param-reassign From d584ab3d5f81a247304e03649e58efc1d7f2d47d Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Mon, 10 Jun 2024 13:17:14 +0200 Subject: [PATCH 096/130] 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 097/130] 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 90552a4ebce455dca321756a8a3b56803e979af0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barbara=20Gawe=C5=82-Kucab?= Date: Mon, 10 Jun 2024 14:08:06 +0200 Subject: [PATCH 098/130] add module declaration for react-native-performance --- src/libs/Performance.tsx | 8 +------- src/types/modules/react-native-performance.d.ts | 12 ++++++++++++ 2 files changed, 13 insertions(+), 7 deletions(-) create mode 100644 src/types/modules/react-native-performance.d.ts diff --git a/src/libs/Performance.tsx b/src/libs/Performance.tsx index 5155722786cf..f5e84b8f6ef3 100644 --- a/src/libs/Performance.tsx +++ b/src/libs/Performance.tsx @@ -3,7 +3,7 @@ import isObject from 'lodash/isObject'; import lodashTransform from 'lodash/transform'; import React, {forwardRef, Profiler} from 'react'; import {Alert, InteractionManager} from 'react-native'; -import type {PerformanceEntry, PerformanceMark, PerformanceMeasure, Performance as RNPerformance, PerformanceObserver as RNPerformanceObserver} from 'react-native-performance'; +import type {PerformanceEntry, PerformanceMark, PerformanceMeasure, ReactNativePerformance, Performance as RNPerformance} from 'react-native-performance'; import type {PerformanceObserverEntryList} from 'react-native-performance/lib/typescript/performance-observer'; import CONST from '@src/CONST'; import isE2ETestSession from './E2E/isE2ETestSession'; @@ -46,12 +46,6 @@ type PerformanceModule = { subscribeToMeasurements: SubscribeToMeasurements; }; -type ReactNativePerformance = { - default: RNPerformance; - setResourceLoggingEnabled: (enabled?: boolean) => void; - PerformanceObserver: typeof RNPerformanceObserver; -}; - let rnPerformance: RNPerformance; /** diff --git a/src/types/modules/react-native-performance.d.ts b/src/types/modules/react-native-performance.d.ts new file mode 100644 index 000000000000..e42e7593b027 --- /dev/null +++ b/src/types/modules/react-native-performance.d.ts @@ -0,0 +1,12 @@ +import type {Performance, PerformanceEntry, PerformanceMark, PerformanceMeasure, PerformanceObserver} from 'react-native-performance'; + +declare module 'react-native-performance' { + // eslint-disable-next-line @typescript-eslint/consistent-type-definitions + interface ReactNativePerformance { + default: Performance; + setResourceLoggingEnabled: (enabled?: boolean) => void; + PerformanceObserver: typeof PerformanceObserver; + } + + export type {PerformanceEntry, PerformanceMark, PerformanceMeasure, Performance, ReactNativePerformance}; +} From 50880d2121347f0638eba1c743b85c29708637ee Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Mon, 10 Jun 2024 14:38:52 +0200 Subject: [PATCH 099/130] 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 0dd74d72ac48b16bd0e92ab19960e101b86e750e Mon Sep 17 00:00:00 2001 From: Yauheni Pasiukevich Date: Mon, 10 Jun 2024 20:45:23 +0200 Subject: [PATCH 100/130] 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 16:02:30 -0700 Subject: [PATCH 101/130] 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 102/130] 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 103/130] 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 104/130] 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 336db4e6dd2a06a000366e3056a442a2073b4dd5 Mon Sep 17 00:00:00 2001 From: rory Date: Mon, 10 Jun 2024 16:51:26 -0700 Subject: [PATCH 105/130] 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 106/130] 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 b330d98e7b95c0ed2420e068b716016934100e9a Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Tue, 11 Jun 2024 09:09:51 +0200 Subject: [PATCH 107/130] 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 108/130] 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 109/130] 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 110/130] 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 111/130] 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 112/130] 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 113/130] 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 114/130] 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 115/130] 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 116/130] 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 117/130] 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 118/130] 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 119/130] 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 120/130] 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 121/130] 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 122/130] 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 123/130] 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 124/130] 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 125/130] 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 126/130] 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 127/130] 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 14994f26ebced1bb32debcbffc261189fad37bf0 Mon Sep 17 00:00:00 2001 From: Francois Laithier Date: Tue, 11 Jun 2024 11:45:40 -0700 Subject: [PATCH 128/130] Remove unused `DISMMISSED_REASON` const --- src/CONST.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/CONST.ts b/src/CONST.ts index 62d838a63ae3..26d9af6bb8b8 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -597,7 +597,6 @@ const CONST = { ADMIN_POLICIES_URL: 'admin_policies', ADMIN_DOMAINS_URL: 'admin_domains', INBOX: 'inbox', - DISMMISSED_REASON: '?dismissedReason=missingFeatures', }, SIGN_IN_FORM_WIDTH: 300, From c1da163fee63644fa5b95918de8e76e1f25ec8db Mon Sep 17 00:00:00 2001 From: James Dean Date: Tue, 11 Jun 2024 12:02:30 -0700 Subject: [PATCH 129/130] 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 130/130] 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.",