From 153b99ac93a431be72011152cefdd05d2e6c4fd2 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Sat, 16 Nov 2024 03:39:02 +0300 Subject: [PATCH 001/131] add highlight feature for chat --- src/components/Search/index.tsx | 25 +++++-- src/components/SelectionList/ChatListItem.tsx | 12 +++- src/hooks/useSearchHighlightAndScroll.ts | 69 ++++++++++++++----- src/libs/SearchUIUtils.ts | 1 + 4 files changed, 81 insertions(+), 26 deletions(-) diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index f7ebeb6907fe..ec7ccbe9a923 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -63,7 +63,10 @@ function mapToItemWithSelectionInfo( shouldAnimateInHighlight: boolean, ) { if (SearchUIUtils.isReportActionListItemType(item)) { - return item; + return { + ...item, + shouldAnimateInHighlight, + }; } return SearchUIUtils.isTransactionListItemType(item) @@ -106,6 +109,8 @@ function Search({queryJSON, onSearchListScroll, isSearchScreenFocused, contentCo const [currentSearchResults] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`); const [transactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION); const previousTransactions = usePrevious(transactions); + const [reportActions] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS); + const previousReportActions = usePrevious(reportActions); useEffect(() => { if (!currentSearchResults?.search?.type) { @@ -183,6 +188,8 @@ function Search({queryJSON, onSearchListScroll, isSearchScreenFocused, contentCo previousTransactions, queryJSON, offset, + reportActions, + previousReportActions, }); // There's a race condition in Onyx which makes it return data from the previous Search, so in addition to checking that the data is loaded @@ -287,15 +294,21 @@ function Search({queryJSON, onSearchListScroll, isSearchScreenFocused, contentCo const ListItem = SearchUIUtils.getListItem(type, status); const sortedData = SearchUIUtils.getSortedSections(type, status, data, sortBy, sortOrder); + + const isExpense = type === CONST.SEARCH.DATA_TYPES.EXPENSE; const sortedSelectedData = sortedData.map((item) => { - const baseKey = `${ONYXKEYS.COLLECTION.TRANSACTION}${(item as TransactionListItemType).transactionID}`; + const baseKey = isExpense + ? `${ONYXKEYS.COLLECTION.TRANSACTION}${(item as TransactionListItemType).transactionID}` + : `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${(item as ReportActionListItemType).reportActionID}`; // Check if the base key matches the newSearchResultKey (TransactionListItemType) const isBaseKeyMatch = baseKey === newSearchResultKey; // Check if any transaction within the transactions array (ReportListItemType) matches the newSearchResultKey - const isAnyTransactionMatch = (item as ReportListItemType)?.transactions?.some((transaction) => { - const transactionKey = `${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`; - return transactionKey === newSearchResultKey; - }); + const isAnyTransactionMatch = + isExpense && + (item as ReportListItemType)?.transactions?.some((transaction) => { + const transactionKey = `${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`; + return transactionKey === newSearchResultKey; + }); // Determine if either the base key or any transaction key matches const shouldAnimateInHighlight = isBaseKeyMatch || isAnyTransactionMatch; diff --git a/src/components/SelectionList/ChatListItem.tsx b/src/components/SelectionList/ChatListItem.tsx index a3e04c9088f1..4f32994de59e 100644 --- a/src/components/SelectionList/ChatListItem.tsx +++ b/src/components/SelectionList/ChatListItem.tsx @@ -5,11 +5,13 @@ import MentionReportContext from '@components/HTMLEngineProvider/HTMLRenderers/M import MultipleAvatars from '@components/MultipleAvatars'; import {ShowContextMenuContext} from '@components/ShowContextMenuContext'; import TextWithTooltip from '@components/TextWithTooltip'; +import useAnimatedHighlightStyle from '@hooks/useAnimatedHighlightStyle'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import ReportActionItemDate from '@pages/home/report/ReportActionItemDate'; import ReportActionItemFragment from '@pages/home/report/ReportActionItemFragment'; +import variables from '@styles/variables'; import CONST from '@src/CONST'; import BaseListItem from './BaseListItem'; import type {ChatListItemProps, ListItem, ReportActionListItemType} from './types'; @@ -56,11 +58,16 @@ function ChatListItem({ const hoveredBackgroundColor = styles.sidebarLinkHover?.backgroundColor ? styles.sidebarLinkHover.backgroundColor : theme.sidebar; const mentionReportContextValue = useMemo(() => ({currentReportID: item?.reportID ?? '-1'}), [item.reportID]); - + const animatedHighlightStyle = useAnimatedHighlightStyle({ + borderRadius: variables.componentBorderRadius, + shouldHighlight: item?.shouldAnimateInHighlight ?? false, + highlightColor: theme.messageHighlightBG, + backgroundColor: theme.highlightBG, + }); return ( ({ keyForList={item.keyForList} onFocus={onFocus} shouldSyncFocus={shouldSyncFocus} + pressableWrapperStyle={[styles.mh5, animatedHighlightStyle]} hoverStyle={item.isSelected && styles.activeComponentBG} > {(hovered) => ( diff --git a/src/hooks/useSearchHighlightAndScroll.ts b/src/hooks/useSearchHighlightAndScroll.ts index 95a953139ebe..21437f36104e 100644 --- a/src/hooks/useSearchHighlightAndScroll.ts +++ b/src/hooks/useSearchHighlightAndScroll.ts @@ -3,6 +3,7 @@ import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import type {SearchQueryJSON} from '@components/Search/types'; import type {ReportActionListItemType, ReportListItemType, SelectionListHandle, TransactionListItemType} from '@components/SelectionList/types'; import * as SearchActions from '@libs/actions/Search'; +import {isReportActionEntry} from '@libs/SearchUIUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {SearchResults, Transaction} from '@src/types/onyx'; @@ -19,27 +20,33 @@ type UseSearchHighlightAndScroll = { /** * Hook used to trigger a search when a new transaction is added and handle highlighting and scrolling. */ -function useSearchHighlightAndScroll({searchResults, transactions, previousTransactions, queryJSON, offset}: UseSearchHighlightAndScroll) { +function useSearchHighlightAndScroll({searchResults, transactions, previousTransactions, reportActions, previousReportActions, queryJSON, offset}: UseSearchHighlightAndScroll) { // Ref to track if the search was triggered by this hook const triggeredByHookRef = useRef(false); const searchTriggeredRef = useRef(false); const previousSearchResults = usePrevious(searchResults?.data); const [newSearchResultKey, setNewSearchResultKey] = useState(null); - const highlightedTransactionIDs = useRef>(new Set()); + const highlightedIDs = useRef>(new Set()); const initializedRef = useRef(false); - + const type = queryJSON.type; + const isExpense = type === CONST.SEARCH.DATA_TYPES.EXPENSE; + const isChat = type === CONST.SEARCH.DATA_TYPES.CHAT; // Trigger search when a new transaction is added useEffect(() => { const previousTransactionsLength = previousTransactions && Object.keys(previousTransactions).length; const transactionsLength = transactions && Object.keys(transactions).length; + const reportActionsLength = reportActions && Object.values(reportActions).reduce((sum, curr) => sum + Object.keys(curr).length, 0); + const prevReportActionsLength = previousReportActions && Object.values(previousReportActions).reduce((sum, curr) => sum + Object.keys(curr).length, 0); // Return early if search was already triggered or there's no change in transactions length - if (searchTriggeredRef.current || previousTransactionsLength === transactionsLength) { + if (searchTriggeredRef.current || (isExpense && previousTransactionsLength === transactionsLength) || (isChat && reportActionsLength === prevReportActionsLength)) { return; } + const newTransactionAdded = transactionsLength && typeof previousTransactionsLength === 'number' && transactionsLength > previousTransactionsLength; + const newReportActionAdded = typeof reportActionsLength === 'number' && typeof prevReportActionsLength === 'number' && reportActionsLength > prevReportActionsLength; // Check if a new transaction was added - if (transactionsLength && typeof previousTransactionsLength === 'number' && transactionsLength > previousTransactionsLength) { + if ((isExpense && newTransactionAdded) || (isChat && newReportActionAdded)) { // Set the flag indicating the search is triggered by the hook triggeredByHookRef.current = true; @@ -54,7 +61,7 @@ function useSearchHighlightAndScroll({searchResults, transactions, previousTrans return () => { searchTriggeredRef.current = false; }; - }, [transactions, previousTransactions, queryJSON, offset]); + }, [transactions, previousTransactions, queryJSON, offset, reportActions, previousReportActions, isChat, isExpense]); // Initialize the set with existing transaction IDs only once useEffect(() => { @@ -63,7 +70,7 @@ function useSearchHighlightAndScroll({searchResults, transactions, previousTrans } const existingTransactionIDs = extractTransactionIDsFromSearchResults(searchResults.data); - highlightedTransactionIDs.current = new Set(existingTransactionIDs); + highlightedIDs.current = new Set(existingTransactionIDs); initializedRef.current = true; }, [searchResults?.data]); @@ -73,22 +80,41 @@ function useSearchHighlightAndScroll({searchResults, transactions, previousTrans return; } - const previousTransactionIDs = extractTransactionIDsFromSearchResults(previousSearchResults); - const currentTransactionIDs = extractTransactionIDsFromSearchResults(searchResults.data); + if (isExpense) { + const previousTransactionIDs = extractTransactionIDsFromSearchResults(previousSearchResults); + const currentTransactionIDs = extractTransactionIDsFromSearchResults(searchResults.data); - // Find new transaction IDs that are not in the previousTransactionIDs and not already highlighted - const newTransactionIDs = currentTransactionIDs.filter((id) => !previousTransactionIDs.includes(id) && !highlightedTransactionIDs.current.has(id)); + // Find new transaction IDs that are not in the previousTransactionIDs and not already highlighted + const newTransactionIDs = currentTransactionIDs.filter((id) => !previousTransactionIDs.includes(id) && !highlightedIDs.current.has(id)); - if (!triggeredByHookRef.current || newTransactionIDs.length === 0) { - return; + if (!triggeredByHookRef.current || newTransactionIDs.length === 0) { + return; + } + + const newTransactionID = newTransactionIDs.at(0) ?? ''; + const newTransactionKey = `${ONYXKEYS.COLLECTION.TRANSACTION}${newTransactionID}`; + + setNewSearchResultKey(newTransactionKey); + highlightedIDs.current.add(newTransactionID); } + if (isChat) { + const previousReportActionIDs = extractReportActionIDsFromSearchResults(previousSearchResults); + const currentReportActionIDs = extractReportActionIDsFromSearchResults(searchResults.data); - const newTransactionID = newTransactionIDs.at(0) ?? ''; - const newTransactionKey = `${ONYXKEYS.COLLECTION.TRANSACTION}${newTransactionID}`; + // Find new transaction IDs that are not in the previousTransactionIDs and not already highlighted + const newReportActionIDs = currentReportActionIDs.filter((id) => !previousReportActionIDs.includes(id) && !highlightedIDs.current.has(id)); - setNewSearchResultKey(newTransactionKey); - highlightedTransactionIDs.current.add(newTransactionID); - }, [searchResults, previousSearchResults]); + if (!triggeredByHookRef.current || newReportActionIDs.length === 0) { + return; + } + + const newReportActionID = newReportActionIDs.at(0) ?? ''; + const newReportActionKey = `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${newReportActionID}`; + + setNewSearchResultKey(newReportActionKey); + highlightedIDs.current.add(newReportActionID); + } + }, [searchResults?.data, previousSearchResults, isExpense, isChat]); // Reset newSearchResultKey after it's been used useEffect(() => { @@ -174,4 +200,11 @@ function extractTransactionIDsFromSearchResults(searchResultsData: Partial): string[] { + return Object.keys(searchResults ?? {}) + .filter(isReportActionEntry) + .map((key) => Object.keys(searchResults[key] ?? {})) + .flat(); +} + export default useSearchHighlightAndScroll; diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index a7ce065a6d23..89e2fa39d94b 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -521,4 +521,5 @@ export { getExpenseTypeTranslationKey, getOverflowMenu, isCorrectSearchUserName, + isReportActionEntry, }; From 7fa7e1f00f5602d1ebed96a651ecc02043744b38 Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Thu, 21 Nov 2024 15:20:16 +0100 Subject: [PATCH 002/131] override session of the user --- src/ONYXKEYS.ts | 4 ++++ src/components/ImportOnyxState/index.tsx | 8 ++++++-- src/components/ImportOnyxState/utils.ts | 2 +- src/libs/actions/App.ts | 19 +++++++++++++++++++ 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index b4510a2faeed..85b53ea8a7e8 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -458,6 +458,9 @@ const ONYXKEYS = { /** The user's Concierge reportID */ CONCIERGE_REPORT_ID: 'conciergeReportID', + /** The user's session that will be preserved when using imported state */ + PRESERVED_USER_SESSION: 'preservedUserSession', + /** Collection Keys */ COLLECTION: { DOWNLOAD: 'download_', @@ -1029,6 +1032,7 @@ type OnyxValuesMapping = { [ONYXKEYS.SHOULD_SHOW_SAVED_SEARCH_RENAME_TOOLTIP]: boolean; [ONYXKEYS.NVP_EXPENSIFY_COMPANY_CARDS_CUSTOM_NAMES]: Record; [ONYXKEYS.CONCIERGE_REPORT_ID]: string; + [ONYXKEYS.PRESERVED_USER_SESSION]: OnyxTypes.Session; }; type OnyxValues = OnyxValuesMapping & OnyxCollectionValuesMapping & OnyxFormValuesMapping & OnyxFormDraftValuesMapping; diff --git a/src/components/ImportOnyxState/index.tsx b/src/components/ImportOnyxState/index.tsx index 8add2d9172fd..e3c4fc6109f7 100644 --- a/src/components/ImportOnyxState/index.tsx +++ b/src/components/ImportOnyxState/index.tsx @@ -1,10 +1,11 @@ import React, {useState} from 'react'; -import Onyx from 'react-native-onyx'; +import Onyx, {useOnyx} from 'react-native-onyx'; import type {FileObject} from '@components/AttachmentModal'; -import {KEYS_TO_PRESERVE, setIsUsingImportedState} from '@libs/actions/App'; +import {KEYS_TO_PRESERVE, setIsUsingImportedState, setPreservedUserSession} from '@libs/actions/App'; import {setShouldForceOffline} from '@libs/actions/Network'; import Navigation from '@libs/Navigation/Navigation'; import type {OnyxValues} from '@src/ONYXKEYS'; +import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import BaseImportOnyxState from './BaseImportOnyxState'; import type ImportOnyxStateProps from './types'; @@ -12,6 +13,7 @@ import {cleanAndTransformState} from './utils'; export default function ImportOnyxState({setIsLoading, isLoading}: ImportOnyxStateProps) { const [isErrorModalVisible, setIsErrorModalVisible] = useState(false); + const [session] = useOnyx(ONYXKEYS.SESSION); const handleFileRead = (file: FileObject) => { if (!file.uri) { @@ -27,6 +29,8 @@ export default function ImportOnyxState({setIsLoading, isLoading}: ImportOnyxSta .then((text) => { const fileContent = text; const transformedState = cleanAndTransformState(fileContent); + const currentUserSessionCopy = {...session}; + setPreservedUserSession(currentUserSessionCopy); setShouldForceOffline(true); Onyx.clear(KEYS_TO_PRESERVE).then(() => { Onyx.multiSet(transformedState) diff --git a/src/components/ImportOnyxState/utils.ts b/src/components/ImportOnyxState/utils.ts index a5f24fa80714..94779868384d 100644 --- a/src/components/ImportOnyxState/utils.ts +++ b/src/components/ImportOnyxState/utils.ts @@ -3,7 +3,7 @@ import type {UnknownRecord} from 'type-fest'; import ONYXKEYS from '@src/ONYXKEYS'; // List of Onyx keys from the .txt file we want to keep for the local override -const keysToOmit = [ONYXKEYS.ACTIVE_CLIENTS, ONYXKEYS.FREQUENTLY_USED_EMOJIS, ONYXKEYS.NETWORK, ONYXKEYS.CREDENTIALS, ONYXKEYS.SESSION, ONYXKEYS.PREFERRED_THEME]; +const keysToOmit = [ONYXKEYS.ACTIVE_CLIENTS, ONYXKEYS.FREQUENTLY_USED_EMOJIS, ONYXKEYS.NETWORK, ONYXKEYS.CREDENTIALS, ONYXKEYS.PREFERRED_THEME]; function isRecord(value: unknown): value is Record { return typeof value === 'object' && !Array.isArray(value) && value !== null; diff --git a/src/libs/actions/App.ts b/src/libs/actions/App.ts index f1f46aee0a93..71697d293b05 100644 --- a/src/libs/actions/App.ts +++ b/src/libs/actions/App.ts @@ -89,6 +89,14 @@ Onyx.connect({ }, }); +let preservedUserSession: OnyxTypes.Session | undefined; +Onyx.connect({ + key: ONYXKEYS.PRESERVED_USER_SESSION, + callback: (value) => { + preservedUserSession = value; + }, +}); + const KEYS_TO_PRESERVE: OnyxKey[] = [ ONYXKEYS.ACCOUNT, ONYXKEYS.IS_CHECKING_PUBLIC_ROOM, @@ -102,6 +110,7 @@ const KEYS_TO_PRESERVE: OnyxKey[] = [ ONYXKEYS.PREFERRED_THEME, ONYXKEYS.NVP_PREFERRED_LOCALE, ONYXKEYS.CREDENTIALS, + ONYXKEYS.PRESERVED_USER_SESSION, ]; Onyx.connect({ @@ -521,6 +530,10 @@ function setIsUsingImportedState(usingImportedState: boolean) { Onyx.set(ONYXKEYS.IS_USING_IMPORTED_STATE, usingImportedState); } +function setPreservedUserSession(session: OnyxTypes.Session) { + Onyx.set(ONYXKEYS.PRESERVED_USER_SESSION, session); +} + function clearOnyxAndResetApp(shouldNavigateToHomepage?: boolean) { // The value of isUsingImportedState will be lost once Onyx is cleared, so we need to store it const isStateImported = isUsingImportedState; @@ -535,6 +548,11 @@ function clearOnyxAndResetApp(shouldNavigateToHomepage?: boolean) { Navigation.navigate(ROUTES.HOME); } + if (preservedUserSession) { + Onyx.set(ONYXKEYS.SESSION, preservedUserSession); + Onyx.set(ONYXKEYS.PRESERVED_USER_SESSION, null); + } + // Requests in a sequential queue should be called even if the Onyx state is reset, so we do not lose any pending data. // However, the OpenApp request must be called before any other request in a queue to ensure data consistency. // To do that, sequential queue is cleared together with other keys, and then it's restored once the OpenApp request is resolved. @@ -571,5 +589,6 @@ export { updateLastRoute, setIsUsingImportedState, clearOnyxAndResetApp, + setPreservedUserSession, KEYS_TO_PRESERVE, }; From 0c3a705ca56675b6c27f516a1e96fe00fa0de44f Mon Sep 17 00:00:00 2001 From: daledah Date: Tue, 26 Nov 2024 23:39:05 +0700 Subject: [PATCH 003/131] fix: Join button appear when open expense on search --- src/pages/home/HeaderView.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pages/home/HeaderView.tsx b/src/pages/home/HeaderView.tsx index 969e1a90ec3b..11f77b90cdfe 100644 --- a/src/pages/home/HeaderView.tsx +++ b/src/pages/home/HeaderView.tsx @@ -148,6 +148,7 @@ function HeaderView({report, parentReportAction, reportID, onNavigationMenuButto const shouldDisableDetailPage = ReportUtils.shouldDisableDetailPage(report); const shouldUseGroupTitle = isGroupChat && (!!report?.reportName || !isMultipleParticipant); const isLoading = !report?.reportID || !title; + const isParentReportLoading = !!report?.parentReportID && !parentReport; const isReportInRHP = route.name === SCREENS.SEARCH.REPORT_RHP; const shouldDisplaySearchRouter = !isReportInRHP || isSmallScreenWidth; @@ -291,7 +292,7 @@ function HeaderView({report, parentReportAction, reportID, onNavigationMenuButto {!shouldUseNarrowLayout && isChatUsedForOnboarding && freeTrialButton} {isTaskReport && !shouldUseNarrowLayout && ReportUtils.isOpenTaskReport(report, parentReportAction) && } - {canJoin && !shouldUseNarrowLayout && joinButton} + {!isParentReportLoading && canJoin && !shouldUseNarrowLayout && joinButton} {shouldDisplaySearchRouter && } @@ -312,7 +313,7 @@ function HeaderView({report, parentReportAction, reportID, onNavigationMenuButto )} - {!isLoading && canJoin && shouldUseNarrowLayout && {joinButton}} + {!isParentReportLoading && !isLoading && canJoin && shouldUseNarrowLayout && {joinButton}} {!isLoading && isChatUsedForOnboarding && shouldUseNarrowLayout && {freeTrialButton}} ); From 4624eb47376e33a505c6cdb1308c0c56b06b0f8c Mon Sep 17 00:00:00 2001 From: Kalydosos Date: Wed, 27 Nov 2024 14:05:21 +0100 Subject: [PATCH 004/131] fix #53003 on room invite sms shown --- src/pages/RoomMemberDetailsPage.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pages/RoomMemberDetailsPage.tsx b/src/pages/RoomMemberDetailsPage.tsx index 3a9d51a251a1..43dbce181815 100644 --- a/src/pages/RoomMemberDetailsPage.tsx +++ b/src/pages/RoomMemberDetailsPage.tsx @@ -17,6 +17,7 @@ import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import * as Report from '@libs/actions/Report'; import type {RoomMembersNavigatorParamList} from '@libs/Navigation/types'; +import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; import Navigation from '@navigation/Navigation'; @@ -33,7 +34,7 @@ type RoomMemberDetailsPagePageProps = WithReportOrNotFoundProps & StackScreenPro function RoomMemberDetailsPage({report, route}: RoomMemberDetailsPagePageProps) { const styles = useThemeStyles(); - const {translate} = useLocalize(); + const {formatPhoneNumber, translate} = useLocalize(); const StyleUtils = useStyleUtils(); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); @@ -47,7 +48,7 @@ function RoomMemberDetailsPage({report, route}: RoomMemberDetailsPagePageProps) const member = report?.participants?.[accountID]; const details = personalDetails?.[accountID] ?? ({} as PersonalDetails); const fallbackIcon = details.fallbackIcon ?? ''; - const displayName = details.displayName ?? ''; + const displayName = formatPhoneNumber(PersonalDetailsUtils.getDisplayNameOrDefault(details)); const isSelectedMemberCurrentUser = accountID === currentUserPersonalDetails?.accountID; const isSelectedMemberOwner = accountID === report.ownerAccountID; const shouldDisableRemoveUser = (ReportUtils.isPolicyExpenseChat(report) && PolicyUtils.isUserPolicyAdmin(policy, details.login)) || isSelectedMemberCurrentUser || isSelectedMemberOwner; From f9927db77abfc92da742e71e27fd9edb3bc8762a Mon Sep 17 00:00:00 2001 From: Kalydosos Date: Thu, 28 Nov 2024 02:25:31 +0100 Subject: [PATCH 005/131] generalize the usage of phone number formatting --- src/components/ArchivedReportFooter.tsx | 8 ++++---- src/libs/OptionsListUtils.ts | 16 ++++++++-------- src/libs/ReportUtils.ts | 10 +++++----- src/libs/actions/Task.ts | 2 +- src/pages/ProfilePage.tsx | 2 +- src/pages/ReportParticipantDetailsPage.tsx | 4 ++-- src/pages/home/report/ReportActionItem.tsx | 3 ++- src/pages/settings/Profile/ProfileAvatar.tsx | 3 ++- .../WorkspaceCompanyCardDetailsPage.tsx | 4 ++-- .../WorkspaceCompanyCardsListRow.tsx | 3 ++- .../expensifyCard/WorkspaceCardListRow.tsx | 4 ++-- .../WorkspaceExpensifyCardDetailsPage.tsx | 4 ++-- 12 files changed, 33 insertions(+), 30 deletions(-) diff --git a/src/components/ArchivedReportFooter.tsx b/src/components/ArchivedReportFooter.tsx index af77a20b4caa..ab5c41a4f1ce 100644 --- a/src/components/ArchivedReportFooter.tsx +++ b/src/components/ArchivedReportFooter.tsx @@ -28,12 +28,12 @@ type ArchivedReportFooterProps = ArchivedReportFooterOnyxProps & { function ArchivedReportFooter({report, reportClosedAction, personalDetails = {}}: ArchivedReportFooterProps) { const styles = useThemeStyles(); - const {translate} = useLocalize(); + const {formatPhoneNumber, translate} = useLocalize(); const originalMessage = ReportActionsUtils.isClosedAction(reportClosedAction) ? ReportActionsUtils.getOriginalMessage(reportClosedAction) : null; const archiveReason = originalMessage?.reason ?? CONST.REPORT.ARCHIVE_REASON.DEFAULT; const actorPersonalDetails = personalDetails?.[reportClosedAction?.actorAccountID ?? -1]; - let displayName = PersonalDetailsUtils.getDisplayNameOrDefault(actorPersonalDetails); + let displayName = formatPhoneNumber(PersonalDetailsUtils.getDisplayNameOrDefault(actorPersonalDetails)); let oldDisplayName: string | undefined; if (archiveReason === CONST.REPORT.ARCHIVE_REASON.ACCOUNT_MERGED) { @@ -59,8 +59,8 @@ function ArchivedReportFooter({report, reportClosedAction, personalDetails = {}} const text = shouldRenderHTML ? translate(`reportArchiveReasons.${archiveReason}`, { - displayName: `${displayName}`, - oldDisplayName: `${oldDisplayName}`, + displayName: `${formatPhoneNumber(displayName)}`, + oldDisplayName: `${formatPhoneNumber(oldDisplayName??'')}`, policyName: `${policyName}`, shouldUseYou: actorPersonalDetails?.accountID === getCurrentUserAccountID(), }) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index e8bbe392e9aa..3d322739bf13 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -358,7 +358,7 @@ function getParticipantsOption(participant: ReportUtils.OptionData | Participant const detail = getPersonalDetailsForAccountIDs([participant.accountID ?? -1], personalDetails)[participant.accountID ?? -1]; // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const login = detail?.login || participant.login || ''; - const displayName = PersonalDetailsUtils.getDisplayNameOrDefault(detail, LocalePhoneNumber.formatPhoneNumber(login) || participant.text); + const displayName = LocalePhoneNumber.formatPhoneNumber(PersonalDetailsUtils.getDisplayNameOrDefault(detail, login || participant.text)); return { keyForList: String(detail?.accountID), @@ -408,7 +408,7 @@ function uniqFast(items: string[]): string[] { function getLastActorDisplayName(lastActorDetails: Partial | null, hasMultipleParticipants: boolean) { return hasMultipleParticipants && lastActorDetails && lastActorDetails.accountID !== currentUserAccountID ? // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - lastActorDetails.firstName || PersonalDetailsUtils.getDisplayNameOrDefault(lastActorDetails) + lastActorDetails.firstName || LocalePhoneNumber.formatPhoneNumber(PersonalDetailsUtils.getDisplayNameOrDefault(lastActorDetails)) : ''; } @@ -506,7 +506,7 @@ function getLastMessageTextForReport(report: OnyxEntry, lastActorDetails case CONST.REPORT.ARCHIVE_REASON.REMOVED_FROM_POLICY: case CONST.REPORT.ARCHIVE_REASON.POLICY_DELETED: { lastMessageTextFromReport = Localize.translate(preferredLocale, `reportArchiveReasons.${archiveReason}`, { - displayName: PersonalDetailsUtils.getDisplayNameOrDefault(lastActorDetails), + displayName: LocalePhoneNumber.formatPhoneNumber(PersonalDetailsUtils.getDisplayNameOrDefault(lastActorDetails)), policyName: ReportUtils.getPolicyName(report, false, policy), }); break; @@ -1394,20 +1394,20 @@ function getShareLogOptions(options: OptionList, searchValue = '', betas: Beta[] * Build the IOUConfirmation options for showing the payee personalDetail */ function getIOUConfirmationOptionsFromPayeePersonalDetail(personalDetail: OnyxEntry, amountText?: string): PayeePersonalDetails { - const formattedLogin = LocalePhoneNumber.formatPhoneNumber(personalDetail?.login ?? ''); + const login = personalDetail?.login ?? ''; return { - text: PersonalDetailsUtils.getDisplayNameOrDefault(personalDetail, formattedLogin), - alternateText: formattedLogin || PersonalDetailsUtils.getDisplayNameOrDefault(personalDetail, '', false), + text: LocalePhoneNumber.formatPhoneNumber(PersonalDetailsUtils.getDisplayNameOrDefault(personalDetail, login)), + alternateText: LocalePhoneNumber.formatPhoneNumber(login || PersonalDetailsUtils.getDisplayNameOrDefault(personalDetail, '', false)), icons: [ { source: personalDetail?.avatar ?? FallbackAvatar, - name: personalDetail?.login ?? '', + name: LocalePhoneNumber.formatPhoneNumber(personalDetail?.login ?? ''), type: CONST.ICON_TYPE_AVATAR, id: personalDetail?.accountID, }, ], descriptiveText: amountText ?? '', - login: personalDetail?.login ?? '', + login: LocalePhoneNumber.formatPhoneNumber(personalDetail?.login ?? ''), accountID: personalDetail?.accountID ?? -1, keyForList: String(personalDetail?.accountID ?? -1), }; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index f2228636bddb..d3d45461b003 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2150,11 +2150,11 @@ function getDisplayNameForParticipant( // If the user's personal details (first name) should be hidden, make sure we return "hidden" instead of the short name if (shouldFallbackToHidden && longName === hiddenTranslation) { - return longName; + return LocalePhoneNumber.formatPhoneNumber(longName); } const shortName = personalDetails.firstName ? personalDetails.firstName : longName; - return shouldUseShortForm ? shortName : longName; + return LocalePhoneNumber.formatPhoneNumber(shouldUseShortForm ? shortName : longName); } function getParticipantsAccountIDsForDisplay(report: OnyxEntry, shouldExcludeHidden = false, shouldExcludeDeleted = false, shouldForceExcludeCurrentUser = false): number[] { @@ -2338,7 +2338,7 @@ function getIcons( const actorIcon = { id: actorAccountID, source: personalDetails?.[actorAccountID ?? -1]?.avatar ?? FallbackAvatar, - name: actorDisplayName, + name: LocalePhoneNumber.formatPhoneNumber(actorDisplayName), type: CONST.ICON_TYPE_AVATAR, fallbackIcon: personalDetails?.[parentReportAction?.actorAccountID ?? -1]?.fallbackIcon, }; @@ -3797,7 +3797,7 @@ function getInvoicePayerName(report: OnyxEntry, invoiceReceiverPolicy?: const isIndividual = invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL; if (isIndividual) { - return PersonalDetailsUtils.getDisplayNameOrDefault(allPersonalDetails?.[invoiceReceiver.accountID]); + return LocalePhoneNumber.formatPhoneNumber(PersonalDetailsUtils.getDisplayNameOrDefault(allPersonalDetails?.[invoiceReceiver.accountID])); } return getPolicyName(report, false, invoiceReceiverPolicy ?? allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${invoiceReceiver?.policyID}`]); @@ -3889,7 +3889,7 @@ function getInvoicesChatName(report: OnyxEntry, receiverPolicy: OnyxEntr } if (isIndividual) { - return PersonalDetailsUtils.getDisplayNameOrDefault(allPersonalDetails?.[invoiceReceiverAccountID]); + return LocalePhoneNumber.formatPhoneNumber(PersonalDetailsUtils.getDisplayNameOrDefault(allPersonalDetails?.[invoiceReceiverAccountID])); } return getPolicyName(report, false, invoiceReceiverPolicy); diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index ea87f5dd5cc6..43af15327a91 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -907,7 +907,7 @@ function getAssignee(assigneeAccountID: number, personalDetails: OnyxEntry { diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 399550069c0a..eeab0e91fba5 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -43,6 +43,7 @@ import ControlSelection from '@libs/ControlSelection'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import * as ErrorUtils from '@libs/ErrorUtils'; import focusComposerWithDelay from '@libs/focusComposerWithDelay'; +import * as LocalePhoneNumber from '@libs/LocalePhoneNumber'; import ModifiedExpenseMessage from '@libs/ModifiedExpenseMessage'; import Navigation from '@libs/Navigation/Navigation'; import Permissions from '@libs/Permissions'; @@ -609,7 +610,7 @@ function ReportActionItem({ const missingPaymentMethod = ReportUtils.getIndicatedMissingPaymentMethod(userWallet, linkedReport?.reportID ?? '-1', action); children = ( <> {missingPaymentMethod === 'bankAccount' && ( diff --git a/src/pages/settings/Profile/ProfileAvatar.tsx b/src/pages/settings/Profile/ProfileAvatar.tsx index 977719f63879..b10b375b74a6 100644 --- a/src/pages/settings/Profile/ProfileAvatar.tsx +++ b/src/pages/settings/Profile/ProfileAvatar.tsx @@ -3,6 +3,7 @@ import React, {useEffect} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import AttachmentModal from '@components/AttachmentModal'; +import * as LocalePhoneNumber from '@libs/LocalePhoneNumber'; import Navigation from '@libs/Navigation/Navigation'; import type {AuthScreensParamList} from '@libs/Navigation/types'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; @@ -37,7 +38,7 @@ function ProfileAvatar({route, personalDetails, personalDetailsMetadata, isLoadi return ( { diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardDetailsPage.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardDetailsPage.tsx index cc1649b51c89..da89b241fb2c 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardDetailsPage.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardDetailsPage.tsx @@ -46,7 +46,7 @@ function WorkspaceCompanyCardDetailsPage({route}: WorkspaceCompanyCardDetailsPag const workspaceAccountID = PolicyUtils.getWorkspaceAccountID(policyID); const policy = usePolicy(policyID); const [isUnassignModalVisible, setIsUnassignModalVisible] = useState(false); - const {translate} = useLocalize(); + const {formatPhoneNumber, translate} = useLocalize(); const styles = useThemeStyles(); const theme = useTheme(); const {isOffline} = useNetwork(); @@ -59,7 +59,7 @@ function WorkspaceCompanyCardDetailsPage({route}: WorkspaceCompanyCardDetailsPag const cardBank = card?.bank ?? ''; const cardholder = personalDetails?.[card?.accountID ?? -1]; - const displayName = PersonalDetailsUtils.getDisplayNameOrDefault(cardholder); + const displayName = formatPhoneNumber(PersonalDetailsUtils.getDisplayNameOrDefault(cardholder)); const exportMenuItem = getExportMenuItem(connectedIntegration, policyID, translate, policy, card); const unassignCard = () => { diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsListRow.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsListRow.tsx index 5c85d2e40ae0..2783758c978b 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsListRow.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsListRow.tsx @@ -3,6 +3,7 @@ import {View} from 'react-native'; import Avatar from '@components/Avatar'; import Text from '@components/Text'; import useThemeStyles from '@hooks/useThemeStyles'; +import * as LocalePhoneNumber from '@libs/LocalePhoneNumber'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import {getDefaultAvatarURL} from '@libs/UserUtils'; import CONST from '@src/CONST'; @@ -21,7 +22,7 @@ type WorkspaceCompanyCardsListRowProps = { function WorkspaceCompanyCardsListRow({cardholder, name, cardNumber}: WorkspaceCompanyCardsListRowProps) { const styles = useThemeStyles(); - const cardholderName = useMemo(() => PersonalDetailsUtils.getDisplayNameOrDefault(cardholder), [cardholder]); + const cardholderName = useMemo(() => LocalePhoneNumber.formatPhoneNumber(PersonalDetailsUtils.getDisplayNameOrDefault(cardholder)), [cardholder]); return ( diff --git a/src/pages/workspace/expensifyCard/WorkspaceCardListRow.tsx b/src/pages/workspace/expensifyCard/WorkspaceCardListRow.tsx index 0d59d5d9c762..0b7394c72d95 100644 --- a/src/pages/workspace/expensifyCard/WorkspaceCardListRow.tsx +++ b/src/pages/workspace/expensifyCard/WorkspaceCardListRow.tsx @@ -34,9 +34,9 @@ type WorkspacesListRowProps = { function WorkspaceCardListRow({limit, cardholder, lastFourPAN, name, currency, isVirtual}: WorkspacesListRowProps) { const {shouldUseNarrowLayout} = useResponsiveLayout(); const styles = useThemeStyles(); - const {translate} = useLocalize(); + const {formatPhoneNumber, translate} = useLocalize(); - const cardholderName = useMemo(() => PersonalDetailsUtils.getDisplayNameOrDefault(cardholder), [cardholder]); + const cardholderName = useMemo(() => formatPhoneNumber(PersonalDetailsUtils.getDisplayNameOrDefault(cardholder)), [cardholder]); const cardType = isVirtual ? translate('workspace.expensifyCard.virtual') : translate('workspace.expensifyCard.physical'); return ( diff --git a/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardDetailsPage.tsx b/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardDetailsPage.tsx index 0189f4a7e3c0..6762a70a715d 100644 --- a/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardDetailsPage.tsx +++ b/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardDetailsPage.tsx @@ -41,7 +41,7 @@ function WorkspaceExpensifyCardDetailsPage({route}: WorkspaceExpensifyCardDetail const [isDeactivateModalVisible, setIsDeactivateModalVisible] = useState(false); const [isOfflineModalVisible, setIsOfflineModalVisible] = useState(false); - const {translate} = useLocalize(); + const {formatPhoneNumber, translate} = useLocalize(); // We need to use isSmallScreenWidth instead of shouldUseNarrowLayout to use the correct modal type for the decision modal // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth const {isSmallScreenWidth} = useResponsiveLayout(); @@ -55,7 +55,7 @@ function WorkspaceExpensifyCardDetailsPage({route}: WorkspaceExpensifyCardDetail const isVirtual = !!card?.nameValuePairs?.isVirtual; const formattedAvailableSpendAmount = CurrencyUtils.convertToDisplayString(card?.availableSpend); const formattedLimit = CurrencyUtils.convertToDisplayString(card?.nameValuePairs?.unapprovedExpenseLimit); - const displayName = PersonalDetailsUtils.getDisplayNameOrDefault(cardholder); + const displayName = formatPhoneNumber(PersonalDetailsUtils.getDisplayNameOrDefault(cardholder)); const translationForLimitType = CardUtils.getTranslationKeyForLimitType(card?.nameValuePairs?.limitType); const fetchCardDetails = useCallback(() => { From 079e8c3a3c7e9a10a5faaec39313f61d93cb906a Mon Sep 17 00:00:00 2001 From: Kalydosos Date: Thu, 28 Nov 2024 02:42:26 +0100 Subject: [PATCH 006/131] generalize the usage of phone number formatting --- src/pages/home/report/ReportActionItem.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index eeab0e91fba5..7ad501d72cb7 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -604,13 +604,13 @@ function ReportActionItem({ ); } else if (ReportActionsUtils.isReimbursementQueuedAction(action)) { const linkedReport = ReportUtils.isChatThread(report) ? parentReport : report; - const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails[linkedReport?.ownerAccountID ?? -1]); + const submitterDisplayName = LocalePhoneNumber.formatPhoneNumber(PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails[linkedReport?.ownerAccountID ?? -1])); const paymentType = ReportActionsUtils.getOriginalMessage(action)?.paymentType ?? ''; const missingPaymentMethod = ReportUtils.getIndicatedMissingPaymentMethod(userWallet, linkedReport?.reportID ?? '-1', action); children = ( <> {missingPaymentMethod === 'bankAccount' && ( From b10f5fbe295d3807633923ae546801367dbe6823 Mon Sep 17 00:00:00 2001 From: Kalydosos Date: Thu, 28 Nov 2024 07:44:12 +0100 Subject: [PATCH 007/131] run prettier --- src/components/ArchivedReportFooter.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ArchivedReportFooter.tsx b/src/components/ArchivedReportFooter.tsx index ab5c41a4f1ce..0d5b291b2571 100644 --- a/src/components/ArchivedReportFooter.tsx +++ b/src/components/ArchivedReportFooter.tsx @@ -60,7 +60,7 @@ function ArchivedReportFooter({report, reportClosedAction, personalDetails = {}} const text = shouldRenderHTML ? translate(`reportArchiveReasons.${archiveReason}`, { displayName: `${formatPhoneNumber(displayName)}`, - oldDisplayName: `${formatPhoneNumber(oldDisplayName??'')}`, + oldDisplayName: `${formatPhoneNumber(oldDisplayName ?? '')}`, policyName: `${policyName}`, shouldUseYou: actorPersonalDetails?.accountID === getCurrentUserAccountID(), }) From 423332ce09d92be9c91fc8bf39f3ac9bde3f64c2 Mon Sep 17 00:00:00 2001 From: Kalydosos Date: Thu, 28 Nov 2024 09:25:39 +0100 Subject: [PATCH 008/131] usememo dependency fix --- src/pages/workspace/expensifyCard/WorkspaceCardListRow.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pages/workspace/expensifyCard/WorkspaceCardListRow.tsx b/src/pages/workspace/expensifyCard/WorkspaceCardListRow.tsx index 0b7394c72d95..619e55486cab 100644 --- a/src/pages/workspace/expensifyCard/WorkspaceCardListRow.tsx +++ b/src/pages/workspace/expensifyCard/WorkspaceCardListRow.tsx @@ -7,6 +7,7 @@ import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import * as CurrencyUtils from '@libs/CurrencyUtils'; +import * as LocalePhoneNumber from '@libs/LocalePhoneNumber'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import CONST from '@src/CONST'; import type {PersonalDetails} from '@src/types/onyx'; @@ -34,9 +35,9 @@ type WorkspacesListRowProps = { function WorkspaceCardListRow({limit, cardholder, lastFourPAN, name, currency, isVirtual}: WorkspacesListRowProps) { const {shouldUseNarrowLayout} = useResponsiveLayout(); const styles = useThemeStyles(); - const {formatPhoneNumber, translate} = useLocalize(); + const {translate} = useLocalize(); - const cardholderName = useMemo(() => formatPhoneNumber(PersonalDetailsUtils.getDisplayNameOrDefault(cardholder)), [cardholder]); + const cardholderName = useMemo(() => LocalePhoneNumber.formatPhoneNumber(PersonalDetailsUtils.getDisplayNameOrDefault(cardholder)), [cardholder]); const cardType = isVirtual ? translate('workspace.expensifyCard.virtual') : translate('workspace.expensifyCard.physical'); return ( From 3e28287f36a6f17a4b8ea2fc3299abe80454eda8 Mon Sep 17 00:00:00 2001 From: Kalydosos Date: Thu, 28 Nov 2024 15:06:47 +0100 Subject: [PATCH 009/131] update some tests with no-break space character --- tests/unit/OptionsListUtilsTest.ts | 76 +++++++++++++++--------------- tests/unit/ReportUtilsTest.ts | 20 ++++---- 2 files changed, 48 insertions(+), 48 deletions(-) diff --git a/tests/unit/OptionsListUtilsTest.ts b/tests/unit/OptionsListUtilsTest.ts index 0e66993bc2cf..2c9a9f3d9b17 100644 --- a/tests/unit/OptionsListUtilsTest.ts +++ b/tests/unit/OptionsListUtilsTest.ts @@ -428,14 +428,14 @@ describe('OptionsListUtils', () => { // All personal details including those that have reports should be returned // We should expect personal details sorted alphabetically - expect(results.personalDetails.at(0)?.text).toBe('Black Panther'); - expect(results.personalDetails.at(1)?.text).toBe('Black Widow'); - expect(results.personalDetails.at(2)?.text).toBe('Captain America'); - expect(results.personalDetails.at(3)?.text).toBe('Invisible Woman'); - expect(results.personalDetails.at(4)?.text).toBe('Mister Fantastic'); - expect(results.personalDetails.at(5)?.text).toBe('Mr Sinister'); - expect(results.personalDetails.at(6)?.text).toBe('Spider-Man'); - expect(results.personalDetails.at(7)?.text).toBe('The Incredible Hulk'); + expect(results.personalDetails.at(0)?.text).toBe('Black Panther'.replace(/ /g, '\u00A0')); + expect(results.personalDetails.at(1)?.text).toBe('Black Widow'.replace(/ /g, '\u00A0')); + expect(results.personalDetails.at(2)?.text).toBe('Captain America'.replace(/ /g, '\u00A0')); + expect(results.personalDetails.at(3)?.text).toBe('Invisible Woman'.replace(/ /g, '\u00A0')); + expect(results.personalDetails.at(4)?.text).toBe('Mister Fantastic'.replace(/ /g, '\u00A0')); + expect(results.personalDetails.at(5)?.text).toBe('Mr Sinister'.replace(/ /g, '\u00A0')); + expect(results.personalDetails.at(6)?.text).toBe('Spider-Man'.replace(/ /g, '\u00A0')); + expect(results.personalDetails.at(7)?.text).toBe('The Incredible Hulk'.replace(/ /g, '\u00A0')); expect(results.personalDetails.at(8)?.text).toBe('Thor'); // Then the result which has an existing report should also have the reportID attached @@ -446,10 +446,10 @@ describe('OptionsListUtils', () => { results = OptionsListUtils.getFilteredOptions({personalDetails: OPTIONS.personalDetails}); // We should expect personal details sorted alphabetically - expect(results.personalDetails.at(0)?.text).toBe('Black Panther'); - expect(results.personalDetails.at(1)?.text).toBe('Black Widow'); - expect(results.personalDetails.at(2)?.text).toBe('Captain America'); - expect(results.personalDetails.at(3)?.text).toBe('Invisible Woman'); + expect(results.personalDetails.at(0)?.text).toBe('Black Panther'.replace(/ /g, '\u00A0')); + expect(results.personalDetails.at(1)?.text).toBe('Black Widow'.replace(/ /g, '\u00A0')); + expect(results.personalDetails.at(2)?.text).toBe('Captain America'.replace(/ /g, '\u00A0')); + expect(results.personalDetails.at(3)?.text).toBe('Invisible Woman'.replace(/ /g, '\u00A0')); // When we don't include personal detail to the result results = OptionsListUtils.getFilteredOptions({ @@ -515,14 +515,14 @@ describe('OptionsListUtils', () => { // All personal details including those that have reports should be returned // We should expect personal details sorted alphabetically - expect(results.personalDetails.at(0)?.text).toBe('Black Panther'); - expect(results.personalDetails.at(1)?.text).toBe('Black Widow'); - expect(results.personalDetails.at(2)?.text).toBe('Captain America'); - expect(results.personalDetails.at(3)?.text).toBe('Invisible Woman'); - expect(results.personalDetails.at(4)?.text).toBe('Mister Fantastic'); - expect(results.personalDetails.at(5)?.text).toBe('Mr Sinister'); - expect(results.personalDetails.at(6)?.text).toBe('Spider-Man'); - expect(results.personalDetails.at(7)?.text).toBe('The Incredible Hulk'); + expect(results.personalDetails.at(0)?.text).toBe('Black Panther'.replace(/ /g, '\u00A0')); + expect(results.personalDetails.at(1)?.text).toBe('Black Widow'.replace(/ /g, '\u00A0')); + expect(results.personalDetails.at(2)?.text).toBe('Captain America'.replace(/ /g, '\u00A0')); + expect(results.personalDetails.at(3)?.text).toBe('Invisible Woman'.replace(/ /g, '\u00A0')); + expect(results.personalDetails.at(4)?.text).toBe('Mister Fantastic'.replace(/ /g, '\u00A0')); + expect(results.personalDetails.at(5)?.text).toBe('Mr Sinister'.replace(/ /g, '\u00A0')); + expect(results.personalDetails.at(6)?.text).toBe('Spider-Man'.replace(/ /g, '\u00A0')); + expect(results.personalDetails.at(7)?.text).toBe('The Incredible Hulk'.replace(/ /g, '\u00A0')); expect(results.personalDetails.at(8)?.text).toBe('Thor'); // And none of our personalDetails should include any of the users with recent reports @@ -631,10 +631,10 @@ describe('OptionsListUtils', () => { const results = OptionsListUtils.getMemberInviteOptions(OPTIONS.personalDetails, [], ''); // We should expect personal details to be sorted alphabetically - expect(results.personalDetails.at(0)?.text).toBe('Black Panther'); - expect(results.personalDetails.at(1)?.text).toBe('Black Widow'); - expect(results.personalDetails.at(2)?.text).toBe('Captain America'); - expect(results.personalDetails.at(3)?.text).toBe('Invisible Woman'); + expect(results.personalDetails.at(0)?.text).toBe('Black Panther'.replace(/ /g, '\u00A0')); + expect(results.personalDetails.at(1)?.text).toBe('Black Widow'.replace(/ /g, '\u00A0')); + expect(results.personalDetails.at(2)?.text).toBe('Captain America'.replace(/ /g, '\u00A0')); + expect(results.personalDetails.at(3)?.text).toBe('Invisible Woman'.replace(/ /g, '\u00A0')); }); it('formatMemberForList()', () => { @@ -669,10 +669,10 @@ describe('OptionsListUtils', () => { const filteredOptions = OptionsListUtils.filterOptions(options, searchText, {sortByReportTypeInSearch: true}); expect(filteredOptions.recentReports.length).toBe(4); - expect(filteredOptions.recentReports.at(0)?.text).toBe('Invisible Woman'); - expect(filteredOptions.recentReports.at(1)?.text).toBe('Spider-Man'); - expect(filteredOptions.recentReports.at(2)?.text).toBe('Black Widow'); - expect(filteredOptions.recentReports.at(3)?.text).toBe('Mister Fantastic, Invisible Woman'); + expect(filteredOptions.recentReports.at(0)?.text).toBe('Invisible Woman'.replace(/ /g, '\u00A0')); + expect(filteredOptions.recentReports.at(1)?.text).toBe('Spider-Man'.replace(/ /g, '\u00A0')); + expect(filteredOptions.recentReports.at(2)?.text).toBe('Black Widow'.replace(/ /g, '\u00A0')); + expect(filteredOptions.recentReports.at(3)?.text).toBe('Mister Fantastic'.replace(/ /g, '\u00A0') + ', ' + 'Invisible Woman'.replace(/ /g, '\u00A0')); }); it('should filter users by email', () => { @@ -682,7 +682,7 @@ describe('OptionsListUtils', () => { const filteredOptions = OptionsListUtils.filterOptions(options, searchText); expect(filteredOptions.recentReports.length).toBe(1); - expect(filteredOptions.recentReports.at(0)?.text).toBe('Mr Sinister'); + expect(filteredOptions.recentReports.at(0)?.text).toBe('Mr Sinister'.replace(/ /g, '\u00A0')); }); it('should find archived chats', () => { @@ -743,8 +743,8 @@ describe('OptionsListUtils', () => { const filteredOptions = OptionsListUtils.filterOptions(options, searchText); expect(filteredOptions.recentReports.length).toBe(2); - expect(filteredOptions.recentReports.at(0)?.text).toBe('Mister Fantastic'); - expect(filteredOptions.recentReports.at(1)?.text).toBe('Mister Fantastic, Invisible Woman'); + expect(filteredOptions.recentReports.at(0)?.text).toBe('Mister Fantastic'.replace(/ /g, '\u00A0')); + expect(filteredOptions.recentReports.at(1)?.text).toBe('Mister Fantastic'.replace(/ /g, '\u00A0') + ', ' + 'Invisible Woman'.replace(/ /g, '\u00A0')); }); it('should return the user to invite when the search value is a valid, non-existent email', () => { @@ -867,7 +867,7 @@ describe('OptionsListUtils', () => { const filteredOptions = OptionsListUtils.filterOptions(options, '.com'); expect(filteredOptions.recentReports.length).toBe(5); - expect(filteredOptions.recentReports.at(0)?.text).toBe('Captain America'); + expect(filteredOptions.recentReports.at(0)?.text).toBe('Captain America'.replace(/ /g, '\u00A0')); expect(filteredOptions.personalDetails.length).toBe(4); expect(filteredOptions.personalDetails.at(0)?.login).toBe('natasharomanoff@expensify.com'); @@ -960,9 +960,9 @@ describe('OptionsListUtils', () => { expect(filteredOptions.personalDetails.length).toBe(4); expect(filteredOptions.recentReports.length).toBe(5); expect(filteredOptions.personalDetails.at(0)?.login).toBe('natasharomanoff@expensify.com'); - expect(filteredOptions.recentReports.at(0)?.text).toBe('Captain America'); - expect(filteredOptions.recentReports.at(1)?.text).toBe('Mr Sinister'); - expect(filteredOptions.recentReports.at(2)?.text).toBe('Black Panther'); + expect(filteredOptions.recentReports.at(0)?.text).toBe('Captain America'.replace(/ /g, '\u00A0')); + expect(filteredOptions.recentReports.at(1)?.text).toBe('Mr Sinister'.replace(/ /g, '\u00A0')); + expect(filteredOptions.recentReports.at(2)?.text).toBe('Black Panther'.replace(/ /g, '\u00A0')); }); it('should return matching option when searching (getSearchOptions)', () => { @@ -978,8 +978,8 @@ describe('OptionsListUtils', () => { const filteredOptions = OptionsListUtils.filterOptions(options, 'fantastic'); expect(filteredOptions.recentReports.length).toBe(2); - expect(filteredOptions.recentReports.at(0)?.text).toBe('Mister Fantastic'); - expect(filteredOptions.recentReports.at(1)?.text).toBe('Mister Fantastic, Invisible Woman'); + expect(filteredOptions.recentReports.at(0)?.text).toBe('Mister Fantastic'.replace(/ /g, '\u00A0')); + expect(filteredOptions.recentReports.at(1)?.text).toBe('Mister Fantastic'.replace(/ /g, '\u00A0') + ', ' + 'Invisible Woman'.replace(/ /g, '\u00A0')); return waitForBatchedUpdates() .then(() => Onyx.set(ONYXKEYS.PERSONAL_DETAILS_LIST, PERSONAL_DETAILS_WITH_PERIODS)) @@ -989,7 +989,7 @@ describe('OptionsListUtils', () => { const filteredResults = OptionsListUtils.filterOptions(results, 'barry.allen@expensify.com', {sortByReportTypeInSearch: true}); expect(filteredResults.recentReports.length).toBe(1); - expect(filteredResults.recentReports.at(0)?.text).toBe('The Flash'); + expect(filteredResults.recentReports.at(0)?.text).toBe('The Flash'.replace(/ /g, '\u00A0')); }); }); }); diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts index dc752ae73b1c..818e1e8e6ae3 100644 --- a/tests/unit/ReportUtilsTest.ts +++ b/tests/unit/ReportUtilsTest.ts @@ -98,15 +98,15 @@ describe('ReportUtils', () => { const participants = ReportUtils.getDisplayNamesWithTooltips(participantsPersonalDetails, false); expect(participants).toHaveLength(5); - expect(participants.at(0)?.displayName).toBe('(833) 240-3627'); + expect(participants.at(0)?.displayName).toBe('(833) 240-3627'.replace(/ /g, '\u00A0')); expect(participants.at(0)?.login).toBe('+18332403627@expensify.sms'); - expect(participants.at(2)?.displayName).toBe('Lagertha Lothbrok'); + expect(participants.at(2)?.displayName).toBe('Lagertha Lothbrok'.replace(/ /g, '\u00A0')); expect(participants.at(2)?.login).toBe('lagertha@vikings.net'); expect(participants.at(2)?.accountID).toBe(3); expect(participants.at(2)?.pronouns).toBe('She/her'); - expect(participants.at(4)?.displayName).toBe('Ragnar Lothbrok'); + expect(participants.at(4)?.displayName).toBe('Ragnar Lothbrok'.replace(/ /g, '\u00A0')); expect(participants.at(4)?.login).toBe('ragnar@vikings.net'); expect(participants.at(4)?.accountID).toBe(1); expect(participants.at(4)?.pronouns).toBeUndefined(); @@ -121,7 +121,7 @@ describe('ReportUtils', () => { reportID: '', participants: ReportUtils.buildParticipantsFromAccountIDs([currentUserAccountID, 1]), }), - ).toBe('Ragnar Lothbrok'); + ).toBe('Ragnar Lothbrok'.replace(/ /g, '\u00A0')); }); test('no displayName', () => { @@ -139,7 +139,7 @@ describe('ReportUtils', () => { reportID: '', participants: ReportUtils.buildParticipantsFromAccountIDs([currentUserAccountID, 4]), }), - ).toBe('(833) 240-3627'); + ).toBe('(833) 240-3627'.replace(/ /g, '\u00A0')); }); }); @@ -149,7 +149,7 @@ describe('ReportUtils', () => { reportID: '', participants: ReportUtils.buildParticipantsFromAccountIDs([currentUserAccountID, 1, 2, 3, 4]), }), - ).toBe('Ragnar, floki@vikings.net, Lagertha, (833) 240-3627'); + ).toBe('Ragnar, floki@vikings.net, Lagertha, ' + '(833) 240-3627'.replace(/ /g, '\u00A0')); }); describe('Default Policy Room', () => { @@ -227,7 +227,7 @@ describe('ReportUtils', () => { isOwnPolicyExpenseChat: false, ownerAccountID: 1, }), - ).toBe('Ragnar Lothbrok'); + ).toBe('Ragnar Lothbrok'.replace(/ /g, '\u00A0')); }); }); @@ -263,10 +263,10 @@ describe('ReportUtils', () => { isOwnPolicyExpenseChat: false, }; - expect(ReportUtils.getReportName(adminArchivedPolicyExpenseChat)).toBe('Ragnar Lothbrok (archived)'); + expect(ReportUtils.getReportName(adminArchivedPolicyExpenseChat)).toBe('Ragnar Lothbrok'.replace(/ /g, '\u00A0') + ' (archived)'); return Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.ES).then(() => - expect(ReportUtils.getReportName(adminArchivedPolicyExpenseChat)).toBe('Ragnar Lothbrok (archivado)'), + expect(ReportUtils.getReportName(adminArchivedPolicyExpenseChat)).toBe('Ragnar Lothbrok'.replace(/ /g, '\u00A0') + ' (archivado)'), ); }); }); @@ -1114,7 +1114,7 @@ describe('ReportUtils', () => { it('Should use correct display name for participants', async () => { await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, participantsPersonalDetails); - expect(ReportUtils.getGroupChatName(fourParticipants, true)).toEqual('(833) 240-3627, floki@vikings.net, Lagertha, Ragnar'); + expect(ReportUtils.getGroupChatName(fourParticipants, true)).toEqual('(833) 240-3627'.replace(/ /g, '\u00A0') + ', floki@vikings.net, Lagertha, Ragnar'); }); }); From 30912b1a2dbf05b12e7686fd73663b9804c73dce Mon Sep 17 00:00:00 2001 From: Kalydosos Date: Thu, 28 Nov 2024 15:49:31 +0100 Subject: [PATCH 010/131] lint update --- tests/unit/OptionsListUtilsTest.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/unit/OptionsListUtilsTest.ts b/tests/unit/OptionsListUtilsTest.ts index 2c9a9f3d9b17..ff519ae8ab84 100644 --- a/tests/unit/OptionsListUtilsTest.ts +++ b/tests/unit/OptionsListUtilsTest.ts @@ -672,7 +672,7 @@ describe('OptionsListUtils', () => { expect(filteredOptions.recentReports.at(0)?.text).toBe('Invisible Woman'.replace(/ /g, '\u00A0')); expect(filteredOptions.recentReports.at(1)?.text).toBe('Spider-Man'.replace(/ /g, '\u00A0')); expect(filteredOptions.recentReports.at(2)?.text).toBe('Black Widow'.replace(/ /g, '\u00A0')); - expect(filteredOptions.recentReports.at(3)?.text).toBe('Mister Fantastic'.replace(/ /g, '\u00A0') + ', ' + 'Invisible Woman'.replace(/ /g, '\u00A0')); + expect(filteredOptions.recentReports.at(3)?.text).toBe(`${'Mister Fantastic'.replace(/ /g, '\u00A0')}, ${'Invisible Woman'.replace(/ /g, '\u00A0')}`); }); it('should filter users by email', () => { @@ -744,7 +744,7 @@ describe('OptionsListUtils', () => { expect(filteredOptions.recentReports.length).toBe(2); expect(filteredOptions.recentReports.at(0)?.text).toBe('Mister Fantastic'.replace(/ /g, '\u00A0')); - expect(filteredOptions.recentReports.at(1)?.text).toBe('Mister Fantastic'.replace(/ /g, '\u00A0') + ', ' + 'Invisible Woman'.replace(/ /g, '\u00A0')); + expect(filteredOptions.recentReports.at(1)?.text).toBe(`${'Mister Fantastic'.replace(/ /g, '\u00A0')}, ${'Invisible Woman'.replace(/ /g, '\u00A0')}`); }); it('should return the user to invite when the search value is a valid, non-existent email', () => { @@ -979,7 +979,7 @@ describe('OptionsListUtils', () => { expect(filteredOptions.recentReports.length).toBe(2); expect(filteredOptions.recentReports.at(0)?.text).toBe('Mister Fantastic'.replace(/ /g, '\u00A0')); - expect(filteredOptions.recentReports.at(1)?.text).toBe('Mister Fantastic'.replace(/ /g, '\u00A0') + ', ' + 'Invisible Woman'.replace(/ /g, '\u00A0')); + expect(filteredOptions.recentReports.at(1)?.text).toBe(`${'Mister Fantastic'.replace(/ /g, '\u00A0')}, ${'Invisible Woman'.replace(/ /g, '\u00A0')}`); return waitForBatchedUpdates() .then(() => Onyx.set(ONYXKEYS.PERSONAL_DETAILS_LIST, PERSONAL_DETAILS_WITH_PERIODS)) From b004644b8fd84b03e3b0882c7d14363b0b056d62 Mon Sep 17 00:00:00 2001 From: Kalydosos Date: Thu, 28 Nov 2024 15:50:01 +0100 Subject: [PATCH 011/131] lint update --- tests/unit/ReportUtilsTest.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts index 818e1e8e6ae3..4eb268df2138 100644 --- a/tests/unit/ReportUtilsTest.ts +++ b/tests/unit/ReportUtilsTest.ts @@ -149,7 +149,7 @@ describe('ReportUtils', () => { reportID: '', participants: ReportUtils.buildParticipantsFromAccountIDs([currentUserAccountID, 1, 2, 3, 4]), }), - ).toBe('Ragnar, floki@vikings.net, Lagertha, ' + '(833) 240-3627'.replace(/ /g, '\u00A0')); + ).toBe(`Ragnar, floki@vikings.net, Lagertha, ${'(833) 240-3627'.replace(/ /g, '\u00A0')}`); }); describe('Default Policy Room', () => { @@ -263,10 +263,10 @@ describe('ReportUtils', () => { isOwnPolicyExpenseChat: false, }; - expect(ReportUtils.getReportName(adminArchivedPolicyExpenseChat)).toBe('Ragnar Lothbrok'.replace(/ /g, '\u00A0') + ' (archived)'); + expect(ReportUtils.getReportName(adminArchivedPolicyExpenseChat)).toBe(`${'Ragnar Lothbrok'.replace(/ /g, '\u00A0')} (archived)`); return Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.ES).then(() => - expect(ReportUtils.getReportName(adminArchivedPolicyExpenseChat)).toBe('Ragnar Lothbrok'.replace(/ /g, '\u00A0') + ' (archivado)'), + expect(ReportUtils.getReportName(adminArchivedPolicyExpenseChat)).toBe(`${'Ragnar Lothbrok'.replace(/ /g, '\u00A0')} (archivado)`), ); }); }); @@ -1114,7 +1114,7 @@ describe('ReportUtils', () => { it('Should use correct display name for participants', async () => { await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, participantsPersonalDetails); - expect(ReportUtils.getGroupChatName(fourParticipants, true)).toEqual('(833) 240-3627'.replace(/ /g, '\u00A0') + ', floki@vikings.net, Lagertha, Ragnar'); + expect(ReportUtils.getGroupChatName(fourParticipants, true)).toEqual(`${'(833) 240-3627'.replace(/ /g, '\u00A0')}, floki@vikings.net, Lagertha, Ragnar`); }); }); From 10a75ac289e2926ee01da1fdbbb2f3dbe1cc376d Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Fri, 29 Nov 2024 21:06:34 +0300 Subject: [PATCH 012/131] added failure handling of update distance request --- .../ReportActionItem/MoneyRequestView.tsx | 2 +- .../ReportActionItemImage.tsx | 2 +- src/libs/actions/IOU.ts | 31 +++++++++++++++++++ src/libs/actions/Transaction.ts | 2 +- .../request/step/IOURequestStepDistance.tsx | 1 + src/types/onyx/Transaction.ts | 2 +- 6 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index ca50e93e536f..1c3a9336714f 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -390,7 +390,7 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals const shouldShowReceiptAudit = isReceiptAllowed && (shouldShowReceiptEmptyState || hasReceipt); const errors = { - ...(transaction?.errorFields?.route ?? transaction?.errors), + ...(transaction?.errorFields?.route ?? transaction?.errorFields?.waypoints ?? transaction?.errors), ...parentReportAction?.errors, }; diff --git a/src/components/ReportActionItem/ReportActionItemImage.tsx b/src/components/ReportActionItem/ReportActionItemImage.tsx index 668338440f73..ca54d759fb69 100644 --- a/src/components/ReportActionItem/ReportActionItemImage.tsx +++ b/src/components/ReportActionItem/ReportActionItemImage.tsx @@ -84,7 +84,7 @@ function ReportActionItemImage({ const {translate} = useLocalize(); const isDistanceRequest = !!transaction && TransactionUtils.isDistanceRequest(transaction); const hasPendingWaypoints = transaction && TransactionUtils.isFetchingWaypointsFromServer(transaction); - const hasErrors = !isEmptyObject(transaction?.errors) || !isEmptyObject(transaction?.errorFields); + const hasErrors = !isEmptyObject(transaction?.errors) || !isEmptyObject(transaction?.errorFields?.route) || !isEmptyObject(transaction?.errorFields?.waypoints); const showMapAsImage = isDistanceRequest && (hasErrors || hasPendingWaypoints); if (showMapAsImage) { diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 90719ffeed55..662ad4fc0592 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -3161,6 +3161,7 @@ type UpdateMoneyRequestDistanceParams = { policy?: OnyxEntry; policyTagList?: OnyxEntry; policyCategories?: OnyxEntry; + transactionBackup: OnyxEntry; }; /** Updates the waypoints of a distance expense */ @@ -3172,6 +3173,7 @@ function updateMoneyRequestDistance({ policy = {} as OnyxTypes.Policy, policyTagList = {}, policyCategories = {}, + transactionBackup, }: UpdateMoneyRequestDistanceParams) { const transactionChanges: TransactionChanges = { waypoints: sanitizeRecentWaypoints(waypoints), @@ -3195,6 +3197,35 @@ function updateMoneyRequestDistance({ value: recentServerValidatedWaypoints, }); + if (transactionBackup) { + const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; + // We need to have all the keys of the original waypoint in the failure data for onyx merge to properly reset + // waypoints keys that do not exist in the waypoint of the reverting failure data. + const allWaypointKeys = [...new Set([...Object.keys(transactionBackup.comment?.waypoints ?? {}), ...Object.keys(transaction?.comment?.waypoints ?? {})])]; + const onyxWaypoints = allWaypointKeys.reduce((acc: NullishDeep, key) => { + acc[key] = transactionBackup.comment?.waypoints?.[key] ? {...transactionBackup.comment?.waypoints?.[key]} : null; + return acc; + }, {}); + const allModifiedWaypointsKeys = [...new Set([...Object.keys(waypoints ?? {}), ...Object.keys(transaction?.modifiedWaypoints ?? {})])]; + const onyxModifiedWaypoints = allModifiedWaypointsKeys.reduce((acc: NullishDeep, key) => { + acc[key] = transactionBackup.modifiedWaypoints?.[key] ? {...transactionBackup.modifiedWaypoints?.[key]} : null; + return acc; + }, {}); + onyxData?.failureData?.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, + value: { + comment: { + waypoints: onyxWaypoints, + customUnit: { + quantity: transactionBackup?.comment?.customUnit?.quantity, + }, + }, + modifiedWaypoints: onyxModifiedWaypoints, + }, + }); + } + API.write(WRITE_COMMANDS.UPDATE_MONEY_REQUEST_DISTANCE, params, onyxData); } diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index 3cb6e3dc44ba..c8a007458242 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -458,7 +458,7 @@ function abandonReviewDuplicateTransactions() { } function clearError(transactionID: string) { - Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, {errors: null, errorFields: {route: null}}); + Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, {errors: null, errorFields: {route: null, waypoints: null, routes: null}}); } function markAsCash(transactionID: string, transactionThreadReportID: string) { diff --git a/src/pages/iou/request/step/IOURequestStepDistance.tsx b/src/pages/iou/request/step/IOURequestStepDistance.tsx index efe5d293036b..dd02df8f4177 100644 --- a/src/pages/iou/request/step/IOURequestStepDistance.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx @@ -465,6 +465,7 @@ function IOURequestStepDistance({ waypoints, ...(hasRouteChanged ? {routes: transaction?.routes} : {}), policy, + transactionBackup, }); navigateBack(); return; diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index 547e41463c70..2aa31904a35a 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -338,7 +338,7 @@ type Transaction = OnyxCommon.OnyxValueWithOfflineFeedback< errors?: OnyxCommon.Errors | ReceiptErrors; /** Server side errors keyed by microtime */ - errorFields?: OnyxCommon.ErrorFields<'route'>; + errorFields?: OnyxCommon.ErrorFields<'route'> & OnyxCommon.ErrorFields<'routes'> & OnyxCommon.ErrorFields<'waypoints'>; /** The name of the file used for a receipt (formerly receiptFilename) */ filename?: string; From cc36d8f10fef8e8be0d2d59fa1a697688f026ebd Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sat, 30 Nov 2024 02:56:40 +0530 Subject: [PATCH 013/131] feat: add product training tooltips and context management --- src/App.tsx | 2 + src/CONST.ts | 6 + .../LHNOptionsList/OptionRowLHN.tsx | 70 +++--- .../PRODUCT_TRAINING_TOOLTIP_DATA.ts | 84 +++++++ .../ProductTrainingContext.tsx | 233 ++++++++++++++++++ src/languages/en.ts | 3 + src/pages/Search/SearchTypeMenu.tsx | 20 +- src/types/onyx/DismissedProductTraining.ts | 20 ++ 8 files changed, 387 insertions(+), 51 deletions(-) create mode 100644 src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts create mode 100644 src/components/ProductTrainingContext/ProductTrainingContext.tsx diff --git a/src/App.tsx b/src/App.tsx index 52904e0a06c4..78b185dc9fe4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -18,6 +18,7 @@ import KeyboardProvider from './components/KeyboardProvider'; import {LocaleContextProvider} from './components/LocaleContextProvider'; import OnyxProvider from './components/OnyxProvider'; import PopoverContextProvider from './components/PopoverProvider'; +import {ProductTrainingContextProvider} from './components/ProductTrainingContext/ProductTrainingContext'; import SafeArea from './components/SafeArea'; import ScrollOffsetContextProvider from './components/ScrollOffsetContextProvider'; import {SearchRouterContextProvider} from './components/Search/SearchRouter/SearchRouterContext'; @@ -95,6 +96,7 @@ function App({url}: AppProps) { VideoPopoverMenuContextProvider, KeyboardProvider, SearchRouterContextProvider, + ProductTrainingContextProvider, ]} > diff --git a/src/CONST.ts b/src/CONST.ts index b57df98b486d..940cdc30789c 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -6372,6 +6372,12 @@ const CONST = { HYBRID_APP: { REORDERING_REACT_NATIVE_ACTIVITY_TO_FRONT: 'reorderingReactNativeActivityToFront', }, + PRODUCT_TRAINING_TOOLTIP_NAMES: { + CONCEIRGE_LHN_GBR: 'conciergeLHNGBR', + RENAME_SAVED_SEARCH: 'renameSavedSearch', + QUICK_ACTION_BUTTON: 'quickActionButton', + WORKSAPCE_CHAT_CREATE: 'workspaceChatCreate', + }, } as const; type Country = keyof typeof CONST.ALL_COUNTRIES; diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index 3e3f4d1b8e5d..92ee4f25b367 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -10,6 +10,7 @@ import * as Expensicons from '@components/Icon/Expensicons'; import MultipleAvatars from '@components/MultipleAvatars'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction'; +import {useProductTrainingContext} from '@components/ProductTrainingContext/ProductTrainingContext'; import SubscriptAvatar from '@components/SubscriptAvatar'; import Text from '@components/Text'; import Tooltip from '@components/Tooltip'; @@ -21,7 +22,6 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import DateUtils from '@libs/DateUtils'; import DomUtils from '@libs/DomUtils'; -import {hasCompletedGuidedSetupFlowSelector} from '@libs/onboardingSelectors'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import Parser from '@libs/Parser'; import Performance from '@libs/Performance'; @@ -31,7 +31,6 @@ import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportA import FreeTrial from '@pages/settings/Subscription/FreeTrial'; import variables from '@styles/variables'; import Timing from '@userActions/Timing'; -import * as User from '@userActions/User'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -47,11 +46,11 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${optionItem?.reportID || -1}`); - const [isFirstTimeNewExpensifyUser] = useOnyx(ONYXKEYS.NVP_IS_FIRST_TIME_NEW_EXPENSIFY_USER); - const [isOnboardingCompleted = true] = useOnyx(ONYXKEYS.NVP_ONBOARDING, { - selector: hasCompletedGuidedSetupFlowSelector, - }); - const [shouldHideGBRTooltip] = useOnyx(ONYXKEYS.NVP_SHOULD_HIDE_GBR_TOOLTIP, {initialValue: true}); + + const isConciergeChatReport = ReportUtils.isConciergeChatReport(report); + const tooltipToRender = isConciergeChatReport && isScreenFocused ? CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.CONCEIRGE_LHN_GBR : undefined; + + const {shouldShowProductTrainingElement, renderProductTourElement, hideElement} = useProductTrainingContext(tooltipToRender); const {translate} = useLocalize(); const [isContextMenuActive, setIsContextMenuActive] = useState(false); @@ -65,29 +64,29 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti }, []), ); - const renderGBRTooltip = useCallback( - () => ( - - - {translate('sidebarScreen.tooltip')} - - ), - [ - styles.alignItemsCenter, - styles.flexRow, - styles.justifyContentCenter, - styles.flexWrap, - styles.textAlignCenter, - styles.gap1, - styles.quickActionTooltipSubtitle, - theme.tooltipHighlightText, - translate, - ], - ); + // const renderGBRTooltip = useCallback( + // () => ( + // + // + // {translate('sidebarScreen.tooltip')} + // + // ), + // [ + // styles.alignItemsCenter, + // styles.flexRow, + // styles.justifyContentCenter, + // styles.flexWrap, + // styles.textAlignCenter, + // styles.gap1, + // styles.quickActionTooltipSubtitle, + // theme.tooltipHighlightText, + // translate, + // ], + // ); const isInFocusMode = viewMode === CONST.OPTION_MODE.COMPACT; const sidebarInnerRowStyle = StyleSheet.flatten( @@ -173,19 +172,15 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti needsOffscreenAlphaCompositing > @@ -195,6 +190,9 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti onPress={(event) => { Performance.markStart(CONST.TIMING.OPEN_REPORT); Timing.start(CONST.TIMING.OPEN_REPORT); + if (shouldShowProductTrainingElement) { + hideElement(); + } event?.preventDefault(); // Enable Composer to focus on clicking the same chat after opening the context menu. diff --git a/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts b/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts new file mode 100644 index 000000000000..274761e81228 --- /dev/null +++ b/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts @@ -0,0 +1,84 @@ +import {dismissProductTrainingElement} from '@libs/actions/Welcome'; +import CONST from '@src/CONST'; + +const {CONCEIRGE_LHN_GBR, RENAME_SAVED_SEARCH, WORKSAPCE_CHAT_CREATE, QUICK_ACTION_BUTTON} = CONST.PRODUCT_TRAINING_TOOLTIP_NAMES; + +type ShouldShowConditionProps = { + isDismissed: boolean; + isOnboardingCompleted: boolean; + hasBeenAddedToNudgeMigration: boolean; + shouldUseNarrowLayout: boolean; +}; + +const PRODUCT_TRAINING_TOOLTIP_DATA = { + [CONCEIRGE_LHN_GBR]: { + content: 'productTrainingTooltip.conciergeLHNGBR', + onHideElement: () => dismissProductTrainingElement(CONCEIRGE_LHN_GBR), + name: CONCEIRGE_LHN_GBR, + priority: 1300, + shouldShow: ({isDismissed, isOnboardingCompleted, hasBeenAddedToNudgeMigration, shouldUseNarrowLayout}: ShouldShowConditionProps) => { + if (isDismissed || !shouldUseNarrowLayout) { + return false; + } + + if (!isOnboardingCompleted && !hasBeenAddedToNudgeMigration) { + return false; + } + + return true; + }, + }, + [RENAME_SAVED_SEARCH]: { + content: 'search.saveSearchTooltipText', + onHideElement: () => dismissProductTrainingElement(RENAME_SAVED_SEARCH), + name: RENAME_SAVED_SEARCH, + priority: 1250, + shouldShow: ({isDismissed, isOnboardingCompleted, hasBeenAddedToNudgeMigration}: ShouldShowConditionProps) => { + if (isDismissed) { + return false; + } + + if (!isOnboardingCompleted && !hasBeenAddedToNudgeMigration) { + return false; + } + + return true; + }, + }, + [QUICK_ACTION_BUTTON]: { + content: '', + onHideElement: () => dismissProductTrainingElement(QUICK_ACTION_BUTTON), + name: QUICK_ACTION_BUTTON, + priority: 1200, + shouldShow: ({isDismissed, isOnboardingCompleted, hasBeenAddedToNudgeMigration}: ShouldShowConditionProps) => { + if (isDismissed) { + return false; + } + + if (!isOnboardingCompleted && !hasBeenAddedToNudgeMigration) { + return false; + } + + return true; + }, + }, + [WORKSAPCE_CHAT_CREATE]: { + content: '', + onHideElement: () => dismissProductTrainingElement(WORKSAPCE_CHAT_CREATE), + name: WORKSAPCE_CHAT_CREATE, + priority: 1100, + shouldShow: ({isDismissed, isOnboardingCompleted, hasBeenAddedToNudgeMigration}: ShouldShowConditionProps) => { + if (isDismissed) { + return false; + } + + if (!isOnboardingCompleted && !hasBeenAddedToNudgeMigration) { + return false; + } + + return true; + }, + }, +}; + +export default PRODUCT_TRAINING_TOOLTIP_DATA; diff --git a/src/components/ProductTrainingContext/ProductTrainingContext.tsx b/src/components/ProductTrainingContext/ProductTrainingContext.tsx new file mode 100644 index 000000000000..0364af640142 --- /dev/null +++ b/src/components/ProductTrainingContext/ProductTrainingContext.tsx @@ -0,0 +1,233 @@ +import React, {createContext, useCallback, useContext, useEffect, useMemo, useState} from 'react'; +import {View} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; +import type {ValueOf} from 'type-fest'; +import Icon from '@components/Icon'; +import * as Expensicons from '@components/Icon/Expensicons'; +import RenderHTML from '@components/RenderHTML'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; +import convertToLTR from '@libs/convertToLTR'; +import {hasCompletedGuidedSetupFlowSelector} from '@libs/onboardingSelectors'; +import type CONST from '@src/CONST'; +import type {TranslationPaths} from '@src/languages/types'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type ChildrenProps from '@src/types/utils/ChildrenProps'; +import PRODUCT_TRAINING_TOOLTIP_DATA from './PRODUCT_TRAINING_TOOLTIP_DATA'; + +type ProductTrainingElementName = ValueOf; + +type ProductTrainingContextType = { + shouldRenderElement: (elementName: ProductTrainingElementName) => boolean; + renderProductTrainingElement: (elementName: ProductTrainingElementName) => React.ReactNode | null; + registerTooltip: (elementName: ProductTrainingElementName) => void; + unregisterTooltip: (elementName: ProductTrainingElementName) => void; +}; + +const ProductTrainingContext = createContext({ + shouldRenderElement: () => false, + renderProductTrainingElement: () => null, + registerTooltip: () => {}, + unregisterTooltip: () => {}, +}); + +function ProductTrainingContextProvider({children}: ChildrenProps) { + const [tryNewDot] = useOnyx(ONYXKEYS.NVP_TRYNEWDOT); + const hasBeenAddedToNudgeMigration = !!tryNewDot?.nudgeMigration?.timestamp; + const [isOnboardingCompleted = true] = useOnyx(ONYXKEYS.NVP_ONBOARDING, { + selector: hasCompletedGuidedSetupFlowSelector, + }); + const [dismissedProductTraining] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING); + const {translate} = useLocalize(); + const {shouldUseNarrowLayout} = useResponsiveLayout(); + const styles = useThemeStyles(); + const theme = useTheme(); + + // Track active tooltips + const [activeTooltips, setActiveTooltips] = useState>(new Set()); + + const unregisterTooltip = useCallback( + (elementName: ProductTrainingElementName) => { + setActiveTooltips((prev) => { + const next = new Set(prev); + next.delete(elementName); + return next; + }); + }, + [setActiveTooltips], + ); + + const determineVisibleTooltip = useCallback(() => { + if (activeTooltips.size === 0) { + return null; + } + + const sortedTooltips = Array.from(activeTooltips) + .map((name) => ({ + name, + priority: PRODUCT_TRAINING_TOOLTIP_DATA[name]?.priority ?? 0, + })) + .sort((a, b) => b.priority - a.priority); + + const highestPriorityTooltip = sortedTooltips.at(0); + + if (!highestPriorityTooltip) { + return null; + } + + return highestPriorityTooltip.name; + }, [activeTooltips]); + + const shouldTooltipBeVisible = useCallback( + (elementName: ProductTrainingElementName) => { + const isDismissed = !!dismissedProductTraining?.[elementName]; + + const tooltipConfig = PRODUCT_TRAINING_TOOLTIP_DATA[elementName]; + + return tooltipConfig.shouldShow({ + isDismissed, + isOnboardingCompleted, + hasBeenAddedToNudgeMigration, + shouldUseNarrowLayout, + }); + }, + [dismissedProductTraining, hasBeenAddedToNudgeMigration, isOnboardingCompleted, shouldUseNarrowLayout], + ); + + const registerTooltip = useCallback( + (elementName: ProductTrainingElementName) => { + const shouldRegister = shouldTooltipBeVisible(elementName); + if (!shouldRegister) { + return; + } + setActiveTooltips((prev) => new Set([...prev, elementName])); + }, + [shouldTooltipBeVisible], + ); + + const shouldRenderElement = useCallback( + (elementName: ProductTrainingElementName) => { + // First check base conditions + const shouldShow = shouldTooltipBeVisible(elementName); + if (!shouldShow) { + return false; + } + const visibleTooltip = determineVisibleTooltip(); + + // If this is the highest priority visible tooltip, show it + if (elementName === visibleTooltip) { + return true; + } + + return false; + }, + [shouldTooltipBeVisible, determineVisibleTooltip], + ); + + const renderProductTourElement = useCallback( + (elementName: ProductTrainingElementName) => { + const element = PRODUCT_TRAINING_TOOLTIP_DATA[elementName]; + if (!element) { + return null; + } + const processedContent = () => { + const content = convertToLTR(translate(element.content as TranslationPaths)); + + return content ? `${content}` : ''; + }; + return ( + + + + + + + ); + }, + [ + styles.alignItemsCenter, + styles.flexRow, + styles.flexWrap, + styles.gap1, + styles.justifyContentCenter, + styles.p2, + styles.renderHTMLTitle, + styles.textAlignCenter, + theme.tooltipHighlightText, + translate, + ], + ); + + const contextValue = useMemo( + () => ({ + renderProductTrainingElement: renderProductTourElement, + shouldRenderElement, + registerTooltip, + unregisterTooltip, + }), + [shouldRenderElement, registerTooltip, unregisterTooltip, renderProductTourElement], + ); + + return {children}; +} + +const useProductTrainingContext = (elementName?: ProductTrainingElementName) => { + const context = useContext(ProductTrainingContext); + if (!context) { + throw new Error('useProductTourContext must be used within a ProductTourProvider'); + } + + const {shouldRenderElement, registerTooltip, unregisterTooltip, renderProductTrainingElement} = context; + + // Register this tooltip when the component mounts and unregister when it unmounts + useEffect(() => { + if (elementName) { + registerTooltip(elementName); + return () => { + unregisterTooltip(elementName); + }; + } + return undefined; + }, [elementName, registerTooltip, unregisterTooltip]); + + const shouldShowProductTrainingElement = useMemo(() => { + if (!elementName) { + return false; + } + return shouldRenderElement(elementName); + }, [elementName, shouldRenderElement]); + + const hideElement = useCallback(() => { + if (!elementName) { + return; + } + const element = PRODUCT_TRAINING_TOOLTIP_DATA[elementName]; + if (element?.onHideElement) { + element.onHideElement(); + } + unregisterTooltip(elementName); + }, [elementName, unregisterTooltip]); + + if (!elementName) { + return { + renderProductTourElement: () => null, + hideElement: () => {}, + shouldShowProductTrainingElement: false, + }; + } + + return { + renderProductTourElement: () => renderProductTrainingElement(elementName), + hideElement, + shouldShowProductTrainingElement, + }; +}; + +export {ProductTrainingContextProvider, useProductTrainingContext}; diff --git a/src/languages/en.ts b/src/languages/en.ts index 0bfc26408d05..8e427f131b39 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -5428,6 +5428,9 @@ const translations = { crossPlatform: 'Do everything from your phone or browser', }, }, + productTrainingTooltip: { + conciergeLHNGBR: 'Get started here!', + }, }; export default translations satisfies TranslationDeepObject; diff --git a/src/pages/Search/SearchTypeMenu.tsx b/src/pages/Search/SearchTypeMenu.tsx index 5c93a3877ff6..0b617b6bf6fb 100644 --- a/src/pages/Search/SearchTypeMenu.tsx +++ b/src/pages/Search/SearchTypeMenu.tsx @@ -8,6 +8,7 @@ import MenuItem from '@components/MenuItem'; import MenuItemList from '@components/MenuItemList'; import type {MenuItemWithLink} from '@components/MenuItemList'; import {usePersonalDetails} from '@components/OnyxProvider'; +import {useProductTrainingContext} from '@components/ProductTrainingContext/ProductTrainingContext'; import {ScrollOffsetContext} from '@components/ScrollOffsetContextProvider'; import ScrollView from '@components/ScrollView'; import type {SearchQueryJSON} from '@components/Search/types'; @@ -62,7 +63,7 @@ function SearchTypeMenu({queryJSON, searchName}: SearchTypeMenuProps) { const {singleExecution} = useSingleExecution(); const {translate} = useLocalize(); const [savedSearches] = useOnyx(ONYXKEYS.SAVED_SEARCHES); - const [shouldShowSavedSearchRenameTooltip] = useOnyx(ONYXKEYS.SHOULD_SHOW_SAVED_SEARCH_RENAME_TOOLTIP); + const {shouldShowProductTrainingElement, renderProductTourElement, hideElement} = useProductTrainingContext(CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.RENAME_SAVED_SEARCH); const {showDeleteModal, DeleteConfirmModal} = useDeleteSavedSearch(); const [session] = useOnyx(ONYXKEYS.SESSION); @@ -151,7 +152,7 @@ function SearchTypeMenu({queryJSON, searchName}: SearchTypeMenuProps) { if (!isNarrow) { return { ...baseMenuItem, - shouldRenderTooltip: index === 0 && shouldShowSavedSearchRenameTooltip === true, + shouldRenderTooltip: index === 0 && shouldShowProductTrainingElement, tooltipAnchorAlignment: { horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT, vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM, @@ -159,19 +160,8 @@ function SearchTypeMenu({queryJSON, searchName}: SearchTypeMenuProps) { tooltipShiftHorizontal: -32, tooltipShiftVertical: 15, tooltipWrapperStyle: [styles.bgPaleGreen, styles.mh4, styles.pv2], - onHideTooltip: SearchActions.dismissSavedSearchRenameTooltip, - renderTooltipContent: () => { - return ( - - - {translate('search.saveSearchTooltipText')} - - ); - }, + onHideTooltip: hideElement, + renderTooltipContent: renderProductTourElement, }; } diff --git a/src/types/onyx/DismissedProductTraining.ts b/src/types/onyx/DismissedProductTraining.ts index 644ba9db8f9b..3e55b3d9e9c4 100644 --- a/src/types/onyx/DismissedProductTraining.ts +++ b/src/types/onyx/DismissedProductTraining.ts @@ -6,6 +6,26 @@ type DismissedProductTraining = { * When user dismisses the nudgeMigration Welcome Modal, we store the timestamp here. */ nudgeMigrationWelcomeModal: Date; + + /** + * When user dismisses the conciergeLHNGBR product training tooltip, we store the timestamp here. + */ + conciergeLHNGBR: Date; + + /** + * When user dismisses the renameSavedSearch product training tooltip, we store the timestamp here. + */ + renameSavedSearch: Date; + + /** + * When user dismisses the workspaceChatCreate product training tooltip, we store the timestamp here. + */ + workspaceChatCreate: Date; + + /** + * When user dismisses the quickActionButton product training tooltip, we store the timestamp here. + */ + quickActionButton: Date; }; export default DismissedProductTraining; From b818d541098933d910be22a4853d380128b6e057 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Sat, 30 Nov 2024 02:42:42 +0300 Subject: [PATCH 014/131] fix typescript --- src/types/onyx/Transaction.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index 2aa31904a35a..594503af78c3 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -338,7 +338,7 @@ type Transaction = OnyxCommon.OnyxValueWithOfflineFeedback< errors?: OnyxCommon.Errors | ReceiptErrors; /** Server side errors keyed by microtime */ - errorFields?: OnyxCommon.ErrorFields<'route'> & OnyxCommon.ErrorFields<'routes'> & OnyxCommon.ErrorFields<'waypoints'>; + errorFields?: OnyxCommon.ErrorFields; /** The name of the file used for a receipt (formerly receiptFilename) */ filename?: string; From ab4b111cfedc2927d3f40f5fed792376e37aeb0c Mon Sep 17 00:00:00 2001 From: Kalydosos Date: Sat, 30 Nov 2024 15:39:51 +0100 Subject: [PATCH 015/131] remove an extra formatting --- src/components/ArchivedReportFooter.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ArchivedReportFooter.tsx b/src/components/ArchivedReportFooter.tsx index 0d5b291b2571..7c199e28bca8 100644 --- a/src/components/ArchivedReportFooter.tsx +++ b/src/components/ArchivedReportFooter.tsx @@ -33,7 +33,7 @@ function ArchivedReportFooter({report, reportClosedAction, personalDetails = {}} const originalMessage = ReportActionsUtils.isClosedAction(reportClosedAction) ? ReportActionsUtils.getOriginalMessage(reportClosedAction) : null; const archiveReason = originalMessage?.reason ?? CONST.REPORT.ARCHIVE_REASON.DEFAULT; const actorPersonalDetails = personalDetails?.[reportClosedAction?.actorAccountID ?? -1]; - let displayName = formatPhoneNumber(PersonalDetailsUtils.getDisplayNameOrDefault(actorPersonalDetails)); + let displayName = PersonalDetailsUtils.getDisplayNameOrDefault(actorPersonalDetails); let oldDisplayName: string | undefined; if (archiveReason === CONST.REPORT.ARCHIVE_REASON.ACCOUNT_MERGED) { From 7c500dabcee2d85b8886b798e6b34b3028ef2676 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sat, 30 Nov 2024 23:34:07 +0530 Subject: [PATCH 016/131] feat: update product training tooltips and refactor context imports --- src/App.tsx | 2 +- .../LHNOptionsList/OptionRowLHN.tsx | 28 +-- .../PRODUCT_TRAINING_TOOLTIP_DATA.ts | 2 +- .../ProductTrainingContext/index.tsx | 233 ++++++++++++++++++ src/pages/Search/SearchTypeMenu.tsx | 109 ++++---- 5 files changed, 300 insertions(+), 74 deletions(-) create mode 100644 src/components/ProductTrainingContext/index.tsx diff --git a/src/App.tsx b/src/App.tsx index 78b185dc9fe4..cc824b78fa4c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -18,7 +18,7 @@ import KeyboardProvider from './components/KeyboardProvider'; import {LocaleContextProvider} from './components/LocaleContextProvider'; import OnyxProvider from './components/OnyxProvider'; import PopoverContextProvider from './components/PopoverProvider'; -import {ProductTrainingContextProvider} from './components/ProductTrainingContext/ProductTrainingContext'; +import {ProductTrainingContextProvider} from './components/ProductTrainingContext'; import SafeArea from './components/SafeArea'; import ScrollOffsetContextProvider from './components/ScrollOffsetContextProvider'; import {SearchRouterContextProvider} from './components/Search/SearchRouter/SearchRouterContext'; diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index 92ee4f25b367..6aeb6085bfcc 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -10,7 +10,7 @@ import * as Expensicons from '@components/Icon/Expensicons'; import MultipleAvatars from '@components/MultipleAvatars'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction'; -import {useProductTrainingContext} from '@components/ProductTrainingContext/ProductTrainingContext'; +import {useProductTrainingContext} from '@components/ProductTrainingContext'; import SubscriptAvatar from '@components/SubscriptAvatar'; import Text from '@components/Text'; import Tooltip from '@components/Tooltip'; @@ -64,30 +64,6 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti }, []), ); - // const renderGBRTooltip = useCallback( - // () => ( - // - // - // {translate('sidebarScreen.tooltip')} - // - // ), - // [ - // styles.alignItemsCenter, - // styles.flexRow, - // styles.justifyContentCenter, - // styles.flexWrap, - // styles.textAlignCenter, - // styles.gap1, - // styles.quickActionTooltipSubtitle, - // theme.tooltipHighlightText, - // translate, - // ], - // ); - const isInFocusMode = viewMode === CONST.OPTION_MODE.COMPACT; const sidebarInnerRowStyle = StyleSheet.flatten( isInFocusMode @@ -178,7 +154,9 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT, vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP, }} + shouldUseOverlay shiftHorizontal={variables.gbrTooltipShiftHorizontal} + onHideTooltip={hideElement} shiftVertical={variables.composerTooltipShiftVertical} wrapperStyle={styles.quickActionTooltipWrapper} > diff --git a/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts b/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts index 274761e81228..915c8c136e53 100644 --- a/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts +++ b/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts @@ -63,7 +63,7 @@ const PRODUCT_TRAINING_TOOLTIP_DATA = { }, }, [WORKSAPCE_CHAT_CREATE]: { - content: '', + content: 'reportActionCompose.tooltip.subtitle', onHideElement: () => dismissProductTrainingElement(WORKSAPCE_CHAT_CREATE), name: WORKSAPCE_CHAT_CREATE, priority: 1100, diff --git a/src/components/ProductTrainingContext/index.tsx b/src/components/ProductTrainingContext/index.tsx new file mode 100644 index 000000000000..0364af640142 --- /dev/null +++ b/src/components/ProductTrainingContext/index.tsx @@ -0,0 +1,233 @@ +import React, {createContext, useCallback, useContext, useEffect, useMemo, useState} from 'react'; +import {View} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; +import type {ValueOf} from 'type-fest'; +import Icon from '@components/Icon'; +import * as Expensicons from '@components/Icon/Expensicons'; +import RenderHTML from '@components/RenderHTML'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; +import convertToLTR from '@libs/convertToLTR'; +import {hasCompletedGuidedSetupFlowSelector} from '@libs/onboardingSelectors'; +import type CONST from '@src/CONST'; +import type {TranslationPaths} from '@src/languages/types'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type ChildrenProps from '@src/types/utils/ChildrenProps'; +import PRODUCT_TRAINING_TOOLTIP_DATA from './PRODUCT_TRAINING_TOOLTIP_DATA'; + +type ProductTrainingElementName = ValueOf; + +type ProductTrainingContextType = { + shouldRenderElement: (elementName: ProductTrainingElementName) => boolean; + renderProductTrainingElement: (elementName: ProductTrainingElementName) => React.ReactNode | null; + registerTooltip: (elementName: ProductTrainingElementName) => void; + unregisterTooltip: (elementName: ProductTrainingElementName) => void; +}; + +const ProductTrainingContext = createContext({ + shouldRenderElement: () => false, + renderProductTrainingElement: () => null, + registerTooltip: () => {}, + unregisterTooltip: () => {}, +}); + +function ProductTrainingContextProvider({children}: ChildrenProps) { + const [tryNewDot] = useOnyx(ONYXKEYS.NVP_TRYNEWDOT); + const hasBeenAddedToNudgeMigration = !!tryNewDot?.nudgeMigration?.timestamp; + const [isOnboardingCompleted = true] = useOnyx(ONYXKEYS.NVP_ONBOARDING, { + selector: hasCompletedGuidedSetupFlowSelector, + }); + const [dismissedProductTraining] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING); + const {translate} = useLocalize(); + const {shouldUseNarrowLayout} = useResponsiveLayout(); + const styles = useThemeStyles(); + const theme = useTheme(); + + // Track active tooltips + const [activeTooltips, setActiveTooltips] = useState>(new Set()); + + const unregisterTooltip = useCallback( + (elementName: ProductTrainingElementName) => { + setActiveTooltips((prev) => { + const next = new Set(prev); + next.delete(elementName); + return next; + }); + }, + [setActiveTooltips], + ); + + const determineVisibleTooltip = useCallback(() => { + if (activeTooltips.size === 0) { + return null; + } + + const sortedTooltips = Array.from(activeTooltips) + .map((name) => ({ + name, + priority: PRODUCT_TRAINING_TOOLTIP_DATA[name]?.priority ?? 0, + })) + .sort((a, b) => b.priority - a.priority); + + const highestPriorityTooltip = sortedTooltips.at(0); + + if (!highestPriorityTooltip) { + return null; + } + + return highestPriorityTooltip.name; + }, [activeTooltips]); + + const shouldTooltipBeVisible = useCallback( + (elementName: ProductTrainingElementName) => { + const isDismissed = !!dismissedProductTraining?.[elementName]; + + const tooltipConfig = PRODUCT_TRAINING_TOOLTIP_DATA[elementName]; + + return tooltipConfig.shouldShow({ + isDismissed, + isOnboardingCompleted, + hasBeenAddedToNudgeMigration, + shouldUseNarrowLayout, + }); + }, + [dismissedProductTraining, hasBeenAddedToNudgeMigration, isOnboardingCompleted, shouldUseNarrowLayout], + ); + + const registerTooltip = useCallback( + (elementName: ProductTrainingElementName) => { + const shouldRegister = shouldTooltipBeVisible(elementName); + if (!shouldRegister) { + return; + } + setActiveTooltips((prev) => new Set([...prev, elementName])); + }, + [shouldTooltipBeVisible], + ); + + const shouldRenderElement = useCallback( + (elementName: ProductTrainingElementName) => { + // First check base conditions + const shouldShow = shouldTooltipBeVisible(elementName); + if (!shouldShow) { + return false; + } + const visibleTooltip = determineVisibleTooltip(); + + // If this is the highest priority visible tooltip, show it + if (elementName === visibleTooltip) { + return true; + } + + return false; + }, + [shouldTooltipBeVisible, determineVisibleTooltip], + ); + + const renderProductTourElement = useCallback( + (elementName: ProductTrainingElementName) => { + const element = PRODUCT_TRAINING_TOOLTIP_DATA[elementName]; + if (!element) { + return null; + } + const processedContent = () => { + const content = convertToLTR(translate(element.content as TranslationPaths)); + + return content ? `${content}` : ''; + }; + return ( + + + + + + + ); + }, + [ + styles.alignItemsCenter, + styles.flexRow, + styles.flexWrap, + styles.gap1, + styles.justifyContentCenter, + styles.p2, + styles.renderHTMLTitle, + styles.textAlignCenter, + theme.tooltipHighlightText, + translate, + ], + ); + + const contextValue = useMemo( + () => ({ + renderProductTrainingElement: renderProductTourElement, + shouldRenderElement, + registerTooltip, + unregisterTooltip, + }), + [shouldRenderElement, registerTooltip, unregisterTooltip, renderProductTourElement], + ); + + return {children}; +} + +const useProductTrainingContext = (elementName?: ProductTrainingElementName) => { + const context = useContext(ProductTrainingContext); + if (!context) { + throw new Error('useProductTourContext must be used within a ProductTourProvider'); + } + + const {shouldRenderElement, registerTooltip, unregisterTooltip, renderProductTrainingElement} = context; + + // Register this tooltip when the component mounts and unregister when it unmounts + useEffect(() => { + if (elementName) { + registerTooltip(elementName); + return () => { + unregisterTooltip(elementName); + }; + } + return undefined; + }, [elementName, registerTooltip, unregisterTooltip]); + + const shouldShowProductTrainingElement = useMemo(() => { + if (!elementName) { + return false; + } + return shouldRenderElement(elementName); + }, [elementName, shouldRenderElement]); + + const hideElement = useCallback(() => { + if (!elementName) { + return; + } + const element = PRODUCT_TRAINING_TOOLTIP_DATA[elementName]; + if (element?.onHideElement) { + element.onHideElement(); + } + unregisterTooltip(elementName); + }, [elementName, unregisterTooltip]); + + if (!elementName) { + return { + renderProductTourElement: () => null, + hideElement: () => {}, + shouldShowProductTrainingElement: false, + }; + } + + return { + renderProductTourElement: () => renderProductTrainingElement(elementName), + hideElement, + shouldShowProductTrainingElement, + }; +}; + +export {ProductTrainingContextProvider, useProductTrainingContext}; diff --git a/src/pages/Search/SearchTypeMenu.tsx b/src/pages/Search/SearchTypeMenu.tsx index 0b617b6bf6fb..bd42c3dc8ab6 100644 --- a/src/pages/Search/SearchTypeMenu.tsx +++ b/src/pages/Search/SearchTypeMenu.tsx @@ -8,7 +8,7 @@ import MenuItem from '@components/MenuItem'; import MenuItemList from '@components/MenuItemList'; import type {MenuItemWithLink} from '@components/MenuItemList'; import {usePersonalDetails} from '@components/OnyxProvider'; -import {useProductTrainingContext} from '@components/ProductTrainingContext/ProductTrainingContext'; +import {useProductTrainingContext} from '@components/ProductTrainingContext'; import {ScrollOffsetContext} from '@components/ScrollOffsetContextProvider'; import ScrollView from '@components/ScrollView'; import type {SearchQueryJSON} from '@components/Search/types'; @@ -119,54 +119,69 @@ function SearchTypeMenu({queryJSON, searchName}: SearchTypeMenuProps) { [showDeleteModal], ); - const createSavedSearchMenuItem = (item: SaveSearchItem, key: string, isNarrow: boolean, index: number) => { - let title = item.name; - if (title === item.query) { - const jsonQuery = SearchQueryUtils.buildSearchQueryJSON(item.query) ?? ({} as SearchQueryJSON); - title = SearchQueryUtils.buildUserReadableQueryString(jsonQuery, personalDetails, reports, taxRates); - } - - const baseMenuItem: SavedSearchMenuItem = { - key, - title, - hash: key, - query: item.query, - shouldShowRightComponent: true, - focused: Number(key) === hash, - onPress: () => { - SearchActions.clearAllFilters(); - Navigation.navigate(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: item?.query ?? '', name: item?.name})); - }, - rightComponent: ( - - ), - styles: [styles.alignItemsCenter], - pendingAction: item.pendingAction, - disabled: item.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, - shouldIconUseAutoWidthStyle: true, - }; + const createSavedSearchMenuItem = useCallback( + (item: SaveSearchItem, key: string, isNarrow: boolean, index: number) => { + let title = item.name; + if (title === item.query) { + const jsonQuery = SearchQueryUtils.buildSearchQueryJSON(item.query) ?? ({} as SearchQueryJSON); + title = SearchQueryUtils.buildUserReadableQueryString(jsonQuery, personalDetails, reports, taxRates); + } - if (!isNarrow) { - return { - ...baseMenuItem, - shouldRenderTooltip: index === 0 && shouldShowProductTrainingElement, - tooltipAnchorAlignment: { - horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT, - vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM, + const baseMenuItem: SavedSearchMenuItem = { + key, + title, + hash: key, + query: item.query, + shouldShowRightComponent: true, + focused: Number(key) === hash, + onPress: () => { + SearchActions.clearAllFilters(); + Navigation.navigate(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: item?.query ?? '', name: item?.name})); }, - tooltipShiftHorizontal: -32, - tooltipShiftVertical: 15, - tooltipWrapperStyle: [styles.bgPaleGreen, styles.mh4, styles.pv2], - onHideTooltip: hideElement, - renderTooltipContent: renderProductTourElement, + rightComponent: ( + + ), + styles: [styles.alignItemsCenter], + pendingAction: item.pendingAction, + disabled: item.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + shouldIconUseAutoWidthStyle: true, }; - } - return baseMenuItem; - }; + if (!isNarrow) { + return { + ...baseMenuItem, + shouldRenderTooltip: index === 0 && shouldShowProductTrainingElement, + tooltipAnchorAlignment: { + horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT, + vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM, + }, + tooltipShiftHorizontal: -32, + tooltipShiftVertical: 15, + tooltipWrapperStyle: [styles.bgPaleGreen, styles.mh4, styles.pv2], + onHideTooltip: hideElement, + renderTooltipContent: renderProductTourElement, + }; + } + return baseMenuItem; + }, + [ + hash, + getOverflowMenu, + styles.alignItemsCenter, + styles.bgPaleGreen, + styles.mh4, + styles.pv2, + personalDetails, + reports, + taxRates, + shouldShowProductTrainingElement, + hideElement, + renderProductTourElement, + ], + ); const route = useRoute(); const scrollViewRef = useRef(null); @@ -191,12 +206,12 @@ function SearchTypeMenu({queryJSON, searchName}: SearchTypeMenuProps) { scrollViewRef.current.scrollTo({y: scrollOffset, animated: false}); }, [getScrollOffset, route]); - const savedSearchesMenuItems = () => { + const savedSearchesMenuItems = useCallback(() => { if (!savedSearches) { return []; } return Object.entries(savedSearches).map(([key, item], index) => createSavedSearchMenuItem(item, key, shouldUseNarrowLayout, index)); - }; + }, [createSavedSearchMenuItem, savedSearches, shouldUseNarrowLayout]); const renderSavedSearchesSection = useCallback( (menuItems: MenuItemWithLink[]) => ( From 9de9bc7dc4a53ecd0bfc9b06fe00ee25a29128e5 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sat, 30 Nov 2024 23:34:38 +0530 Subject: [PATCH 017/131] feat: remove workspace tooltip from ReportScreen and ReportFooter, integrate product training context in ReportActionCompose --- .../ProductTrainingContext.tsx | 233 ------------------ src/pages/home/ReportScreen.tsx | 2 - .../ReportActionCompose.tsx | 44 +--- src/pages/home/report/ReportFooter.tsx | 7 +- 4 files changed, 9 insertions(+), 277 deletions(-) delete mode 100644 src/components/ProductTrainingContext/ProductTrainingContext.tsx diff --git a/src/components/ProductTrainingContext/ProductTrainingContext.tsx b/src/components/ProductTrainingContext/ProductTrainingContext.tsx deleted file mode 100644 index 0364af640142..000000000000 --- a/src/components/ProductTrainingContext/ProductTrainingContext.tsx +++ /dev/null @@ -1,233 +0,0 @@ -import React, {createContext, useCallback, useContext, useEffect, useMemo, useState} from 'react'; -import {View} from 'react-native'; -import {useOnyx} from 'react-native-onyx'; -import type {ValueOf} from 'type-fest'; -import Icon from '@components/Icon'; -import * as Expensicons from '@components/Icon/Expensicons'; -import RenderHTML from '@components/RenderHTML'; -import Text from '@components/Text'; -import useLocalize from '@hooks/useLocalize'; -import useResponsiveLayout from '@hooks/useResponsiveLayout'; -import useTheme from '@hooks/useTheme'; -import useThemeStyles from '@hooks/useThemeStyles'; -import convertToLTR from '@libs/convertToLTR'; -import {hasCompletedGuidedSetupFlowSelector} from '@libs/onboardingSelectors'; -import type CONST from '@src/CONST'; -import type {TranslationPaths} from '@src/languages/types'; -import ONYXKEYS from '@src/ONYXKEYS'; -import type ChildrenProps from '@src/types/utils/ChildrenProps'; -import PRODUCT_TRAINING_TOOLTIP_DATA from './PRODUCT_TRAINING_TOOLTIP_DATA'; - -type ProductTrainingElementName = ValueOf; - -type ProductTrainingContextType = { - shouldRenderElement: (elementName: ProductTrainingElementName) => boolean; - renderProductTrainingElement: (elementName: ProductTrainingElementName) => React.ReactNode | null; - registerTooltip: (elementName: ProductTrainingElementName) => void; - unregisterTooltip: (elementName: ProductTrainingElementName) => void; -}; - -const ProductTrainingContext = createContext({ - shouldRenderElement: () => false, - renderProductTrainingElement: () => null, - registerTooltip: () => {}, - unregisterTooltip: () => {}, -}); - -function ProductTrainingContextProvider({children}: ChildrenProps) { - const [tryNewDot] = useOnyx(ONYXKEYS.NVP_TRYNEWDOT); - const hasBeenAddedToNudgeMigration = !!tryNewDot?.nudgeMigration?.timestamp; - const [isOnboardingCompleted = true] = useOnyx(ONYXKEYS.NVP_ONBOARDING, { - selector: hasCompletedGuidedSetupFlowSelector, - }); - const [dismissedProductTraining] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING); - const {translate} = useLocalize(); - const {shouldUseNarrowLayout} = useResponsiveLayout(); - const styles = useThemeStyles(); - const theme = useTheme(); - - // Track active tooltips - const [activeTooltips, setActiveTooltips] = useState>(new Set()); - - const unregisterTooltip = useCallback( - (elementName: ProductTrainingElementName) => { - setActiveTooltips((prev) => { - const next = new Set(prev); - next.delete(elementName); - return next; - }); - }, - [setActiveTooltips], - ); - - const determineVisibleTooltip = useCallback(() => { - if (activeTooltips.size === 0) { - return null; - } - - const sortedTooltips = Array.from(activeTooltips) - .map((name) => ({ - name, - priority: PRODUCT_TRAINING_TOOLTIP_DATA[name]?.priority ?? 0, - })) - .sort((a, b) => b.priority - a.priority); - - const highestPriorityTooltip = sortedTooltips.at(0); - - if (!highestPriorityTooltip) { - return null; - } - - return highestPriorityTooltip.name; - }, [activeTooltips]); - - const shouldTooltipBeVisible = useCallback( - (elementName: ProductTrainingElementName) => { - const isDismissed = !!dismissedProductTraining?.[elementName]; - - const tooltipConfig = PRODUCT_TRAINING_TOOLTIP_DATA[elementName]; - - return tooltipConfig.shouldShow({ - isDismissed, - isOnboardingCompleted, - hasBeenAddedToNudgeMigration, - shouldUseNarrowLayout, - }); - }, - [dismissedProductTraining, hasBeenAddedToNudgeMigration, isOnboardingCompleted, shouldUseNarrowLayout], - ); - - const registerTooltip = useCallback( - (elementName: ProductTrainingElementName) => { - const shouldRegister = shouldTooltipBeVisible(elementName); - if (!shouldRegister) { - return; - } - setActiveTooltips((prev) => new Set([...prev, elementName])); - }, - [shouldTooltipBeVisible], - ); - - const shouldRenderElement = useCallback( - (elementName: ProductTrainingElementName) => { - // First check base conditions - const shouldShow = shouldTooltipBeVisible(elementName); - if (!shouldShow) { - return false; - } - const visibleTooltip = determineVisibleTooltip(); - - // If this is the highest priority visible tooltip, show it - if (elementName === visibleTooltip) { - return true; - } - - return false; - }, - [shouldTooltipBeVisible, determineVisibleTooltip], - ); - - const renderProductTourElement = useCallback( - (elementName: ProductTrainingElementName) => { - const element = PRODUCT_TRAINING_TOOLTIP_DATA[elementName]; - if (!element) { - return null; - } - const processedContent = () => { - const content = convertToLTR(translate(element.content as TranslationPaths)); - - return content ? `${content}` : ''; - }; - return ( - - - - - - - ); - }, - [ - styles.alignItemsCenter, - styles.flexRow, - styles.flexWrap, - styles.gap1, - styles.justifyContentCenter, - styles.p2, - styles.renderHTMLTitle, - styles.textAlignCenter, - theme.tooltipHighlightText, - translate, - ], - ); - - const contextValue = useMemo( - () => ({ - renderProductTrainingElement: renderProductTourElement, - shouldRenderElement, - registerTooltip, - unregisterTooltip, - }), - [shouldRenderElement, registerTooltip, unregisterTooltip, renderProductTourElement], - ); - - return {children}; -} - -const useProductTrainingContext = (elementName?: ProductTrainingElementName) => { - const context = useContext(ProductTrainingContext); - if (!context) { - throw new Error('useProductTourContext must be used within a ProductTourProvider'); - } - - const {shouldRenderElement, registerTooltip, unregisterTooltip, renderProductTrainingElement} = context; - - // Register this tooltip when the component mounts and unregister when it unmounts - useEffect(() => { - if (elementName) { - registerTooltip(elementName); - return () => { - unregisterTooltip(elementName); - }; - } - return undefined; - }, [elementName, registerTooltip, unregisterTooltip]); - - const shouldShowProductTrainingElement = useMemo(() => { - if (!elementName) { - return false; - } - return shouldRenderElement(elementName); - }, [elementName, shouldRenderElement]); - - const hideElement = useCallback(() => { - if (!elementName) { - return; - } - const element = PRODUCT_TRAINING_TOOLTIP_DATA[elementName]; - if (element?.onHideElement) { - element.onHideElement(); - } - unregisterTooltip(elementName); - }, [elementName, unregisterTooltip]); - - if (!elementName) { - return { - renderProductTourElement: () => null, - hideElement: () => {}, - shouldShowProductTrainingElement: false, - }; - } - - return { - renderProductTourElement: () => renderProductTrainingElement(elementName), - hideElement, - shouldShowProductTrainingElement, - }; -}; - -export {ProductTrainingContextProvider, useProductTrainingContext}; diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index cd0433da7353..9bf5db34fbb9 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -130,7 +130,6 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro selector: (parentReportActions) => getParentReportAction(parentReportActions, reportOnyx?.parentReportActionID ?? ''), }); const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP); - const [workspaceTooltip] = useOnyx(ONYXKEYS.NVP_WORKSPACE_TOOLTIP); const wasLoadingApp = usePrevious(isLoadingApp); const finishedLoadingApp = wasLoadingApp && !isLoadingApp; const isDeletedParentAction = ReportActionsUtils.isDeletedParentAction(parentReportAction); @@ -848,7 +847,6 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro isComposerFullSize={!!isComposerFullSize} isEmptyChat={isEmptyChat} lastReportAction={lastReportAction} - workspaceTooltip={workspaceTooltip} /> ) : null} diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index 59d1b4c00683..33b6f5b7e8f3 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -11,14 +11,12 @@ import type {FileObject} from '@components/AttachmentModal'; import AttachmentModal from '@components/AttachmentModal'; import EmojiPickerButton from '@components/EmojiPicker/EmojiPickerButton'; import ExceededCommentLength from '@components/ExceededCommentLength'; -import Icon from '@components/Icon'; -import * as Expensicons from '@components/Icon/Expensicons'; import ImportedStateIndicator from '@components/ImportedStateIndicator'; import type {Mention} from '@components/MentionSuggestions'; import OfflineIndicator from '@components/OfflineIndicator'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import {usePersonalDetails} from '@components/OnyxProvider'; -import Text from '@components/Text'; +import {useProductTrainingContext} from '@components/ProductTrainingContext'; import EducationalTooltip from '@components/Tooltip/EducationalTooltip'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useDebounce from '@hooks/useDebounce'; @@ -26,7 +24,6 @@ import useHandleExceedMaxCommentLength from '@hooks/useHandleExceedMaxCommentLen import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; -import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import canFocusInputOnScreenFocus from '@libs/canFocusInputOnScreenFocus'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; @@ -114,7 +111,6 @@ function ReportActionCompose({ onComposerFocus, onComposerBlur, }: ReportActionComposeProps) { - const theme = useTheme(); const styles = useThemeStyles(); const {translate} = useLocalize(); // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth @@ -127,6 +123,10 @@ function ReportActionCompose({ const [blockedFromConcierge] = useOnyx(ONYXKEYS.NVP_BLOCKED_FROM_CONCIERGE); const [shouldShowComposeInput = true] = useOnyx(ONYXKEYS.SHOULD_SHOW_COMPOSE_INPUT); + const {renderProductTourElement, hideElement, shouldShowProductTrainingElement} = useProductTrainingContext( + shouldShowEducationalTooltip ? CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.WORKSAPCE_CHAT_CREATE : undefined, + ); + /** * Updates the Highlight state of the composer */ @@ -366,34 +366,6 @@ function ReportActionCompose({ return reportActionComposeHeight - emojiOffsetWithComposeBox - CONST.MENU_POSITION_REPORT_ACTION_COMPOSE_BOTTOM; }, [styles]); - const renderWorkspaceChatTooltip = useCallback( - () => ( - - - - {translate('reportActionCompose.tooltip.title')} - {translate('reportActionCompose.tooltip.subtitle')} - - - ), - [ - styles.alignItemsCenter, - styles.flexRow, - styles.justifyContentCenter, - styles.flexWrap, - styles.textAlignCenter, - styles.gap1, - styles.quickActionTooltipTitle, - styles.quickActionTooltipSubtitle, - theme.tooltipHighlightText, - translate, - ], - ); - const onValueChange = useCallback( (value: string) => { if (value.length === 0 && isComposerFullSize) { @@ -417,10 +389,10 @@ function ReportActionCompose({ contentContainerStyle={isComposerFullSize ? styles.flex1 : {}} > ; - /** Whether to show educational tooltip in workspace chat for first-time user */ - workspaceTooltip: OnyxEntry; - /** Whether the chat is empty */ isEmptyChat?: boolean; @@ -76,7 +73,6 @@ function ReportFooter({ isEmptyChat = true, isReportReadyForDisplay = true, isComposerFullSize = false, - workspaceTooltip, onComposerBlur, onComposerFocus, }: ReportFooterProps) { @@ -118,7 +114,7 @@ function ReportFooter({ const isSystemChat = ReportUtils.isSystemChat(report); const isAdminsOnlyPostingRoom = ReportUtils.isAdminsOnlyPostingRoom(report); const isUserPolicyAdmin = PolicyUtils.isPolicyAdmin(policy); - const shouldShowEducationalTooltip = !!workspaceTooltip?.shouldShow && !isUserPolicyAdmin; + const shouldShowEducationalTooltip = PolicyUtils.isPaidGroupPolicy(policy) && !isUserPolicyAdmin; const allPersonalDetails = usePersonalDetails(); @@ -249,7 +245,6 @@ export default memo( prevProps.isEmptyChat === nextProps.isEmptyChat && prevProps.lastReportAction === nextProps.lastReportAction && prevProps.isReportReadyForDisplay === nextProps.isReportReadyForDisplay && - prevProps.workspaceTooltip?.shouldShow === nextProps.workspaceTooltip?.shouldShow && lodashIsEqual(prevProps.reportMetadata, nextProps.reportMetadata) && lodashIsEqual(prevProps.policy?.employeeList, nextProps.policy?.employeeList) && lodashIsEqual(prevProps.policy?.role, nextProps.policy?.role), From 3fa81f23b96c67f2740e3e95b6ba9eb044c30caa Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sun, 1 Dec 2024 00:51:49 +0530 Subject: [PATCH 018/131] tooltip for QAB --- .../PRODUCT_TRAINING_TOOLTIP_DATA.ts | 6 ++--- .../ProductTrainingContext/index.tsx | 3 +-- src/pages/home/report/ReportFooter.tsx | 2 +- .../FloatingActionButtonAndPopover.tsx | 25 ++++++++----------- 4 files changed, 15 insertions(+), 21 deletions(-) diff --git a/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts b/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts index 915c8c136e53..e30b78c73b8c 100644 --- a/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts +++ b/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts @@ -33,8 +33,8 @@ const PRODUCT_TRAINING_TOOLTIP_DATA = { onHideElement: () => dismissProductTrainingElement(RENAME_SAVED_SEARCH), name: RENAME_SAVED_SEARCH, priority: 1250, - shouldShow: ({isDismissed, isOnboardingCompleted, hasBeenAddedToNudgeMigration}: ShouldShowConditionProps) => { - if (isDismissed) { + shouldShow: ({isDismissed, isOnboardingCompleted, hasBeenAddedToNudgeMigration, shouldUseNarrowLayout}: ShouldShowConditionProps) => { + if (isDismissed || shouldUseNarrowLayout) { return false; } @@ -46,7 +46,7 @@ const PRODUCT_TRAINING_TOOLTIP_DATA = { }, }, [QUICK_ACTION_BUTTON]: { - content: '', + content: 'quickAction.tooltip.subtitle', onHideElement: () => dismissProductTrainingElement(QUICK_ACTION_BUTTON), name: QUICK_ACTION_BUTTON, priority: 1200, diff --git a/src/components/ProductTrainingContext/index.tsx b/src/components/ProductTrainingContext/index.tsx index 0364af640142..9b827ced556b 100644 --- a/src/components/ProductTrainingContext/index.tsx +++ b/src/components/ProductTrainingContext/index.tsx @@ -139,7 +139,7 @@ function ProductTrainingContextProvider({children}: ChildrenProps) { return content ? `${content}` : ''; }; return ( - + ( - - {translate('quickAction.tooltip.title')} - {translate('quickAction.tooltip.subtitle')} - - ), - [styles.quickActionTooltipTitle, styles.quickActionTooltipSubtitle, translate], - ); - const quickActionTitle = useMemo(() => { if (isEmptyObject(quickActionReport)) { return ''; @@ -446,8 +440,9 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu}: Fl }, tooltipShiftHorizontal: styles.popoverMenuItem.paddingHorizontal, tooltipShiftVertical: styles.popoverMenuItem.paddingVertical / 2, - renderTooltipContent: renderQuickActionTooltip, + renderTooltipContent: renderProductTourElement, tooltipWrapperStyle: styles.quickActionTooltipWrapper, + onHideTooltip: hideElement, }; if (quickAction?.action) { @@ -459,7 +454,7 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu}: Fl description: !hideQABSubtitle ? ReportUtils.getReportName(quickActionReport) ?? translate('quickAction.updateDestination') : '', onSelected: () => interceptAnonymousUser(() => navigateToQuickAction()), shouldShowSubscriptRightAvatar: ReportUtils.isPolicyExpenseChat(quickActionReport), - shouldRenderTooltip: quickAction.isFirstQuickAction, + shouldRenderTooltip: shouldShowProductTrainingElement, }, ]; } @@ -478,7 +473,7 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu}: Fl }, true); }), shouldShowSubscriptRightAvatar: true, - shouldRenderTooltip: false, + shouldRenderTooltip: shouldShowProductTrainingElement, }, ]; } @@ -490,13 +485,13 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu}: Fl styles.popoverMenuItem.paddingHorizontal, styles.popoverMenuItem.paddingVertical, styles.quickActionTooltipWrapper, - renderQuickActionTooltip, + renderProductTourElement, quickAction?.action, - quickAction?.isFirstQuickAction, policyChatForActivePolicy, quickActionTitle, hideQABSubtitle, quickActionReport, + shouldShowProductTrainingElement, navigateToQuickAction, selectOption, isValidReport, From cb8b8f628c8e6bf17958d0d3df728efc4021366a Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sun, 1 Dec 2024 01:24:35 +0530 Subject: [PATCH 019/131] remove unused import --- .../sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx index 6400978aad29..1a5298672546 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx @@ -12,7 +12,6 @@ import * as Expensicons from '@components/Icon/Expensicons'; import type {PopoverMenuItem} from '@components/PopoverMenu'; import PopoverMenu from '@components/PopoverMenu'; import {useProductTrainingContext} from '@components/ProductTrainingContext'; -import Text from '@components/Text'; import useEnvironment from '@hooks/useEnvironment'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; @@ -486,6 +485,7 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu}: Fl styles.popoverMenuItem.paddingVertical, styles.quickActionTooltipWrapper, renderProductTourElement, + hideElement, quickAction?.action, policyChatForActivePolicy, quickActionTitle, From a22aee1520c7c997531036c90cd8f0f26c1c21ef Mon Sep 17 00:00:00 2001 From: Someshwar Tripathi Date: Mon, 2 Dec 2024 05:58:10 +0530 Subject: [PATCH 020/131] Add checkboxes for RHP --- src/pages/workspace/categories/WorkspaceCategoriesPage.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx index 56ba8a5440b8..7d9306795be7 100644 --- a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx +++ b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx @@ -84,7 +84,7 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) { const currentConnectionName = PolicyUtils.getCurrentConnectionName(policy); const isQuickSettingsFlow = !!backTo; - const canSelectMultiple = shouldUseNarrowLayout ? selectionMode?.isEnabled : true; + const canSelectMultiple = isSmallScreenWidth ? selectionMode?.isEnabled : true; const fetchCategories = useCallback(() => { Category.openPolicyCategoriesPage(policyId); @@ -182,7 +182,7 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) { const options: Array>> = []; const isThereAnyAccountingConnection = Object.keys(policy?.connections ?? {}).length !== 0; - if (shouldUseNarrowLayout ? canSelectMultiple : selectedCategoriesArray.length > 0) { + if (isSmallScreenWidth ? canSelectMultiple : selectedCategoriesArray.length > 0) { if (!isThereAnyAccountingConnection) { options.push({ icon: Expensicons.Trashcan, @@ -408,7 +408,7 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) { {hasVisibleCategories && !isLoading && ( item && toggleCategory(item)} sections={[{data: categoryList, isDisabled: false}]} onCheckboxPress={toggleCategory} From 38fe4e068dea7bf4a01aea446c5c6c28fa70e520 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Mon, 2 Dec 2024 16:26:54 +0530 Subject: [PATCH 021/131] =?UTF-8?q?feat:=20Implement=20to=20use=20a=20?= =?UTF-8?q?=F0=9F=91=8Dicon=20next=20to=20approved=20report=20preview.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: krishna2323 --- src/components/ProcessMoneyReportHoldMenu.tsx | 3 + .../ReportActionItem/ReportPreview.tsx | 57 ++++++++++++++++--- .../AnimatedSettlementButton.tsx | 52 +++++++++++++---- src/libs/actions/IOU.ts | 3 +- 4 files changed, 96 insertions(+), 19 deletions(-) diff --git a/src/components/ProcessMoneyReportHoldMenu.tsx b/src/components/ProcessMoneyReportHoldMenu.tsx index f1a72cc7fb8e..246a57dccaf2 100644 --- a/src/components/ProcessMoneyReportHoldMenu.tsx +++ b/src/components/ProcessMoneyReportHoldMenu.tsx @@ -67,6 +67,9 @@ function ProcessMoneyReportHoldMenu({ const onSubmit = (full: boolean) => { if (isApprove) { + if (startAnimation) { + startAnimation(); + } IOU.approveMoneyRequest(moneyRequestReport, full); if (!full && isLinkedTransactionHeld(Navigation.getTopmostReportActionId() ?? '-1', moneyRequestReport?.reportID ?? '')) { Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(moneyRequestReport?.reportID ?? '')); diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index e3ddb91d0528..6bc64e3a1029 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -121,6 +121,7 @@ function ReportPreview({ ); const [isPaidAnimationRunning, setIsPaidAnimationRunning] = useState(false); + const [isApprovedAnimationRunning, setIsApprovedAnimationRunning] = useState(false); const [isHoldMenuVisible, setIsHoldMenuVisible] = useState(false); const [requestType, setRequestType] = useState(); const {nonHeldAmount, fullAmount, hasValidNonHeldAmount} = ReportUtils.getNonHeldAndFullAmount(iouReport, policy); @@ -140,12 +141,18 @@ function ReportPreview({ })); const checkMarkScale = useSharedValue(iouSettled ? 1 : 0); + const isApproved = ReportUtils.isReportApproved(iouReport, action); + const thumbsUpScale = useSharedValue(isApproved ? 1 : 0.25); + const thumbsUpStyle = useAnimatedStyle(() => ({ + ...styles.defaultCheckmarkWrapper, + transform: [{scale: thumbsUpScale.get()}], + })); + const moneyRequestComment = action?.childLastMoneyRequestComment ?? ''; const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(chatReport); const isInvoiceRoom = ReportUtils.isInvoiceRoom(chatReport); const isOpenExpenseReport = isPolicyExpenseChat && ReportUtils.isOpenExpenseReport(iouReport); - const isApproved = ReportUtils.isReportApproved(iouReport, action); const canAllowSettlement = ReportUtils.hasUpdatedTotal(iouReport, policy); const numberOfRequests = allTransactions.length; const transactionsWithReceipts = ReportUtils.getTransactionsWithReceipts(iouReportID); @@ -196,11 +203,19 @@ function ReportPreview({ const {isDelegateAccessRestricted, delegatorEmail} = useDelegateUserDetails(); const [isNoDelegateAccessMenuVisible, setIsNoDelegateAccessMenuVisible] = useState(false); - const stopAnimation = useCallback(() => setIsPaidAnimationRunning(false), []); + const stopAnimation = useCallback(() => { + setIsPaidAnimationRunning(false); + setIsApprovedAnimationRunning(false); + }, []); const startAnimation = useCallback(() => { setIsPaidAnimationRunning(true); HapticFeedback.longPress(); }, []); + const startApprovedAnimation = useCallback(() => { + setIsApprovedAnimationRunning(true); + HapticFeedback.longPress(); + }, []); + const confirmPayment = useCallback( (type: PaymentMethodType | undefined, payAsBusiness?: boolean) => { if (!type) { @@ -232,6 +247,8 @@ function ReportPreview({ } else if (ReportUtils.hasHeldExpenses(iouReport?.reportID)) { setIsHoldMenuVisible(true); } else { + setIsApprovedAnimationRunning(true); + HapticFeedback.longPress(); IOU.approveMoneyRequest(iouReport, true); } }; @@ -330,14 +347,16 @@ function ReportPreview({ const bankAccountRoute = ReportUtils.getBankAccountRoute(chatReport); const getCanIOUBePaid = useCallback( - (onlyShowPayElsewhere = false) => IOU.canIOUBePaid(iouReport, chatReport, policy, allTransactions, onlyShowPayElsewhere), + (onlyShowPayElsewhere = false, shouldCheckApprovedState = true) => + IOU.canIOUBePaid(iouReport, chatReport, policy, allTransactions, onlyShowPayElsewhere, undefined, undefined, shouldCheckApprovedState), [iouReport, chatReport, policy, allTransactions], ); const canIOUBePaid = useMemo(() => getCanIOUBePaid(), [getCanIOUBePaid]); + const canIOUBePaidAndApproved = useMemo(() => getCanIOUBePaid(false, false), [getCanIOUBePaid]); const onlyShowPayElsewhere = useMemo(() => !canIOUBePaid && getCanIOUBePaid(true), [canIOUBePaid, getCanIOUBePaid]); const shouldShowPayButton = isPaidAnimationRunning || canIOUBePaid || onlyShowPayElsewhere; - const shouldShowApproveButton = useMemo(() => IOU.canApproveIOU(iouReport, policy), [iouReport, policy]); + const shouldShowApproveButton = useMemo(() => IOU.canApproveIOU(iouReport, policy), [iouReport, policy]) || isApprovedAnimationRunning; const shouldDisableApproveButton = shouldShowApproveButton && !ReportUtils.isAllowedToApproveExpenseReport(iouReport); @@ -424,7 +443,7 @@ function ReportPreview({ const shouldShowExportIntegrationButton = !shouldShowPayButton && !shouldShowSubmitButton && connectedIntegration && isAdmin && ReportUtils.canBeExported(iouReport); useEffect(() => { - if (!isPaidAnimationRunning) { + if (!isPaidAnimationRunning || isApprovedAnimationRunning) { return; } @@ -445,6 +464,14 @@ function ReportPreview({ checkMarkScale.set(isPaidAnimationRunning ? withDelay(CONST.ANIMATION_PAID_CHECKMARK_DELAY, withSpring(1, {duration: CONST.ANIMATION_PAID_DURATION})) : 1); }, [isPaidAnimationRunning, iouSettled, checkMarkScale]); + useEffect(() => { + if (!isApproved) { + return; + } + + thumbsUpScale.set(isApprovedAnimationRunning ? withDelay(CONST.ANIMATION_PAID_CHECKMARK_DELAY, withSpring(1, {duration: CONST.ANIMATION_PAID_DURATION})) : 1); + }, [isApproved]); + return ( - {previewMessage} + {previewMessage} {shouldShowRBR && ( )} + {isApproved && ( + + + + )} {shouldShowSubtitle && !!supportText && ( @@ -532,6 +567,8 @@ function ReportPreview({ { + if (requestType === CONST.IOU.REPORT_ACTION_TYPE.APPROVE) { + startApprovedAnimation(); + } else { + startAnimation(); + } + }} /> )} diff --git a/src/components/SettlementButton/AnimatedSettlementButton.tsx b/src/components/SettlementButton/AnimatedSettlementButton.tsx index 65c2fd2f493b..9a9e6f8de01c 100644 --- a/src/components/SettlementButton/AnimatedSettlementButton.tsx +++ b/src/components/SettlementButton/AnimatedSettlementButton.tsx @@ -11,9 +11,18 @@ import type SettlementButtonProps from './types'; type AnimatedSettlementButtonProps = SettlementButtonProps & { isPaidAnimationRunning: boolean; onAnimationFinish: () => void; + isApprovedAnimationRunning: boolean; + canIOUBePaid: boolean; }; -function AnimatedSettlementButton({isPaidAnimationRunning, onAnimationFinish, isDisabled, ...settlementButtonProps}: AnimatedSettlementButtonProps) { +function AnimatedSettlementButton({ + isPaidAnimationRunning, + onAnimationFinish, + isApprovedAnimationRunning, + isDisabled, + canIOUBePaid, + ...settlementButtonProps +}: AnimatedSettlementButtonProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const buttonScale = useSharedValue(1); @@ -38,12 +47,13 @@ function AnimatedSettlementButton({isPaidAnimationRunning, onAnimationFinish, is overflow: 'hidden', marginTop: buttonMarginTop.get(), })); - const buttonDisabledStyle = isPaidAnimationRunning - ? { - opacity: 1, - ...styles.cursorDefault, - } - : undefined; + const buttonDisabledStyle = + isPaidAnimationRunning || isApprovedAnimationRunning + ? { + opacity: 1, + ...styles.cursorDefault, + } + : undefined; const resetAnimation = useCallback(() => { buttonScale.set(1); @@ -55,7 +65,7 @@ function AnimatedSettlementButton({isPaidAnimationRunning, onAnimationFinish, is }, [buttonScale, buttonOpacity, paymentCompleteTextScale, paymentCompleteTextOpacity, height, buttonMarginTop, styles.expenseAndReportPreviewTextButtonContainer.gap]); useEffect(() => { - if (!isPaidAnimationRunning) { + if (!isApprovedAnimationRunning && !isPaidAnimationRunning) { resetAnimation(); return; } @@ -65,15 +75,30 @@ function AnimatedSettlementButton({isPaidAnimationRunning, onAnimationFinish, is // Wait for the above animation + 1s delay before hiding the component const totalDelay = CONST.ANIMATION_PAID_DURATION + CONST.ANIMATION_PAID_BUTTON_HIDE_DELAY; + const willShowPaymentButton = canIOUBePaid && isApprovedAnimationRunning; height.set( withDelay( totalDelay, - withTiming(0, {duration: CONST.ANIMATION_PAID_DURATION}, () => runOnJS(onAnimationFinish)()), + withTiming(willShowPaymentButton ? variables.componentSizeNormal : 0, {duration: CONST.ANIMATION_PAID_DURATION}, () => runOnJS(onAnimationFinish)()), ), ); + buttonMarginTop.set(withDelay(totalDelay, withTiming(willShowPaymentButton ? styles.expenseAndReportPreviewTextButtonContainer.gap : 0, {duration: CONST.ANIMATION_PAID_DURATION}))); buttonMarginTop.set(withDelay(totalDelay, withTiming(0, {duration: CONST.ANIMATION_PAID_DURATION}))); paymentCompleteTextOpacity.set(withDelay(totalDelay, withTiming(0, {duration: CONST.ANIMATION_PAID_DURATION}))); - }, [isPaidAnimationRunning, onAnimationFinish, buttonOpacity, buttonScale, height, paymentCompleteTextOpacity, paymentCompleteTextScale, buttonMarginTop, resetAnimation]); + }, [ + isPaidAnimationRunning, + isApprovedAnimationRunning, + onAnimationFinish, + buttonOpacity, + buttonScale, + height, + paymentCompleteTextOpacity, + paymentCompleteTextScale, + buttonMarginTop, + resetAnimation, + canIOUBePaid, + styles.expenseAndReportPreviewTextButtonContainer.gap, + ]); return ( @@ -82,11 +107,16 @@ function AnimatedSettlementButton({isPaidAnimationRunning, onAnimationFinish, is {translate('iou.paymentComplete')} )} + {isApprovedAnimationRunning && ( + + {translate('iou.approved')} + + )} diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 90719ffeed55..fd72f8842634 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -6857,6 +6857,7 @@ function canIOUBePaid( onlyShowPayElsewhere = false, chatReportRNVP?: OnyxTypes.ReportNameValuePairs, invoiceReceiverPolicy?: SearchPolicy, + shouldCheckApprovedState = true, ) { const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(chatReport); const reportNameValuePairs = chatReportRNVP ?? ReportUtils.getReportNameValuePairs(chatReport?.reportID); @@ -6911,7 +6912,7 @@ function canIOUBePaid( reimbursableSpend !== 0 && !isChatReportArchived && !isAutoReimbursable && - !shouldBeApproved && + (!shouldBeApproved || !shouldCheckApprovedState) && !isPayAtEndExpenseReport ); } From ea3767eed89d9867dc8fe19a2e78a726046f0301 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Mon, 2 Dec 2024 16:30:12 +0530 Subject: [PATCH 022/131] minor update. Signed-off-by: krishna2323 --- src/components/ReportActionItem/ReportPreview.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 6bc64e3a1029..cd78fefc5fc0 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -142,7 +142,7 @@ function ReportPreview({ const checkMarkScale = useSharedValue(iouSettled ? 1 : 0); const isApproved = ReportUtils.isReportApproved(iouReport, action); - const thumbsUpScale = useSharedValue(isApproved ? 1 : 0.25); + const thumbsUpScale = useSharedValue(isApproved ? 1 : 0); const thumbsUpStyle = useAnimatedStyle(() => ({ ...styles.defaultCheckmarkWrapper, transform: [{scale: thumbsUpScale.get()}], From c1e009ce9da79a805a72e89e0aab479b6886632c Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Tue, 3 Dec 2024 22:02:04 +0530 Subject: [PATCH 023/131] fix merge conflicts. Signed-off-by: krishna2323 --- .../ReportActionItem/ReportPreview.tsx | 22 ++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 100776c52056..09269d8daf26 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -131,13 +131,18 @@ function ReportPreview({ const [paymentType, setPaymentType] = useState(); const getCanIOUBePaid = useCallback( - (onlyShowPayElsewhere = false) => IOU.canIOUBePaid(iouReport, chatReport, policy, allTransactions, onlyShowPayElsewhere), + (onlyShowPayElsewhere = false, shouldCheckApprovedState = true) => + IOU.canIOUBePaid(iouReport, chatReport, policy, allTransactions, onlyShowPayElsewhere, undefined, undefined, shouldCheckApprovedState), [iouReport, chatReport, policy, allTransactions], ); const canIOUBePaid = useMemo(() => getCanIOUBePaid(), [getCanIOUBePaid]); + const canIOUBePaidAndApproved = useMemo(() => getCanIOUBePaid(false, false), [getCanIOUBePaid]); const onlyShowPayElsewhere = useMemo(() => !canIOUBePaid && getCanIOUBePaid(true), [canIOUBePaid, getCanIOUBePaid]); const shouldShowPayButton = isPaidAnimationRunning || canIOUBePaid || onlyShowPayElsewhere; + const shouldShowApproveButton = useMemo(() => IOU.canApproveIOU(iouReport, policy), [iouReport, policy]) || isApprovedAnimationRunning; + + const shouldDisableApproveButton = shouldShowApproveButton && !ReportUtils.isAllowedToApproveExpenseReport(iouReport); const {nonHeldAmount, fullAmount, hasValidNonHeldAmount} = ReportUtils.getNonHeldAndFullAmount(iouReport, shouldShowPayButton); const hasOnlyHeldExpenses = ReportUtils.hasOnlyHeldExpenses(iouReport?.reportID ?? ''); @@ -357,19 +362,6 @@ function ReportPreview({ ]); const bankAccountRoute = ReportUtils.getBankAccountRoute(chatReport); - const getCanIOUBePaid = useCallback( - (onlyShowPayElsewhere = false, shouldCheckApprovedState = true) => - IOU.canIOUBePaid(iouReport, chatReport, policy, allTransactions, onlyShowPayElsewhere, undefined, undefined, shouldCheckApprovedState), - [iouReport, chatReport, policy, allTransactions], - ); - - const canIOUBePaid = useMemo(() => getCanIOUBePaid(), [getCanIOUBePaid]); - const canIOUBePaidAndApproved = useMemo(() => getCanIOUBePaid(false, false), [getCanIOUBePaid]); - const onlyShowPayElsewhere = useMemo(() => !canIOUBePaid && getCanIOUBePaid(true), [canIOUBePaid, getCanIOUBePaid]); - const shouldShowPayButton = isPaidAnimationRunning || canIOUBePaid || onlyShowPayElsewhere; - const shouldShowApproveButton = useMemo(() => IOU.canApproveIOU(iouReport, policy), [iouReport, policy]) || isApprovedAnimationRunning; - - const shouldDisableApproveButton = shouldShowApproveButton && !ReportUtils.isAllowedToApproveExpenseReport(iouReport); const shouldShowSettlementButton = (shouldShowPayButton || shouldShowApproveButton) && !showRTERViolationMessage && !shouldShowBrokenConnectionViolation; @@ -481,7 +473,7 @@ function ReportPreview({ } thumbsUpScale.set(isApprovedAnimationRunning ? withDelay(CONST.ANIMATION_PAID_CHECKMARK_DELAY, withSpring(1, {duration: CONST.ANIMATION_PAID_DURATION})) : 1); - }, [isApproved]); + }, [isApproved, isApprovedAnimationRunning, thumbsUpScale]); return ( Date: Tue, 3 Dec 2024 22:21:54 +0300 Subject: [PATCH 024/131] added comment --- src/components/SelectionList/ChatListItem.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/SelectionList/ChatListItem.tsx b/src/components/SelectionList/ChatListItem.tsx index 4f32994de59e..4807aa7760c8 100644 --- a/src/components/SelectionList/ChatListItem.tsx +++ b/src/components/SelectionList/ChatListItem.tsx @@ -64,10 +64,18 @@ function ChatListItem({ highlightColor: theme.messageHighlightBG, backgroundColor: theme.highlightBG, }); + const pressableStyle = [ + styles.selectionListPressableItemWrapper, + styles.textAlignLeft, + // Removing background style because they are added to the parent OpacityView via animatedHighlightStyle + styles.bgTransparent, + item.isSelected && styles.activeComponentBG, + item.cursorStyle, + ]; return ( Date: Tue, 3 Dec 2024 23:23:42 +0300 Subject: [PATCH 025/131] made some follow up changes --- src/components/Search/index.tsx | 11 ++-- src/hooks/useSearchHighlightAndScroll.ts | 83 ++++++++++++------------ 2 files changed, 48 insertions(+), 46 deletions(-) diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index 4ad63e8f8bbb..57e2a7da6bc6 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -329,17 +329,16 @@ function Search({queryJSON, onSearchListScroll, isSearchScreenFocused, contentCo const ListItem = SearchUIUtils.getListItem(type, status); const sortedData = SearchUIUtils.getSortedSections(type, status, data, sortBy, sortOrder); - - const isExpense = type === CONST.SEARCH.DATA_TYPES.EXPENSE; + const isChat = type === CONST.SEARCH.DATA_TYPES.CHAT; const sortedSelectedData = sortedData.map((item) => { - const baseKey = isExpense - ? `${ONYXKEYS.COLLECTION.TRANSACTION}${(item as TransactionListItemType).transactionID}` - : `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${(item as ReportActionListItemType).reportActionID}`; + const baseKey = isChat + ? `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${(item as ReportActionListItemType).reportActionID}` + : `${ONYXKEYS.COLLECTION.TRANSACTION}${(item as TransactionListItemType).transactionID}`; // Check if the base key matches the newSearchResultKey (TransactionListItemType) const isBaseKeyMatch = baseKey === newSearchResultKey; // Check if any transaction within the transactions array (ReportListItemType) matches the newSearchResultKey const isAnyTransactionMatch = - isExpense && + !isChat && (item as ReportListItemType)?.transactions?.some((transaction) => { const transactionKey = `${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`; return transactionKey === newSearchResultKey; diff --git a/src/hooks/useSearchHighlightAndScroll.ts b/src/hooks/useSearchHighlightAndScroll.ts index 21437f36104e..31d7658e165d 100644 --- a/src/hooks/useSearchHighlightAndScroll.ts +++ b/src/hooks/useSearchHighlightAndScroll.ts @@ -6,19 +6,21 @@ import * as SearchActions from '@libs/actions/Search'; import {isReportActionEntry} from '@libs/SearchUIUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {SearchResults, Transaction} from '@src/types/onyx'; +import type {ReportActions, SearchResults, Transaction} from '@src/types/onyx'; import usePrevious from './usePrevious'; type UseSearchHighlightAndScroll = { searchResults: OnyxEntry; transactions: OnyxCollection; previousTransactions: OnyxCollection; + reportActions: OnyxCollection; + previousReportActions: OnyxCollection; queryJSON: SearchQueryJSON; offset: number; }; /** - * Hook used to trigger a search when a new transaction is added and handle highlighting and scrolling. + * Hook used to trigger a search when a new transaction or report action is added and handle highlighting and scrolling. */ function useSearchHighlightAndScroll({searchResults, transactions, previousTransactions, reportActions, previousReportActions, queryJSON, offset}: UseSearchHighlightAndScroll) { // Ref to track if the search was triggered by this hook @@ -29,24 +31,24 @@ function useSearchHighlightAndScroll({searchResults, transactions, previousTrans const highlightedIDs = useRef>(new Set()); const initializedRef = useRef(false); const type = queryJSON.type; - const isExpense = type === CONST.SEARCH.DATA_TYPES.EXPENSE; const isChat = type === CONST.SEARCH.DATA_TYPES.CHAT; - // Trigger search when a new transaction is added + + // Trigger search when a new report action is added while on chat or when a new transaction is added for the other search types. useEffect(() => { const previousTransactionsLength = previousTransactions && Object.keys(previousTransactions).length; const transactionsLength = transactions && Object.keys(transactions).length; - const reportActionsLength = reportActions && Object.values(reportActions).reduce((sum, curr) => sum + Object.keys(curr).length, 0); - const prevReportActionsLength = previousReportActions && Object.values(previousReportActions).reduce((sum, curr) => sum + Object.keys(curr).length, 0); - // Return early if search was already triggered or there's no change in transactions length - if (searchTriggeredRef.current || (isExpense && previousTransactionsLength === transactionsLength) || (isChat && reportActionsLength === prevReportActionsLength)) { + const reportActionsLength = reportActions && Object.values(reportActions).reduce((sum, curr) => sum + Object.keys(curr ?? {}).length, 0); + const prevReportActionsLength = previousReportActions && Object.values(previousReportActions).reduce((sum, curr) => sum + Object.keys(curr ?? {}).length, 0); + // Return early if search was already triggered or there's no change in current and previous data length + if (searchTriggeredRef.current || (!isChat && previousTransactionsLength === transactionsLength) || (isChat && reportActionsLength === prevReportActionsLength)) { return; } const newTransactionAdded = transactionsLength && typeof previousTransactionsLength === 'number' && transactionsLength > previousTransactionsLength; + const newReportActionAdded = reportActionsLength && typeof prevReportActionsLength === 'number' && reportActionsLength > prevReportActionsLength; - const newReportActionAdded = typeof reportActionsLength === 'number' && typeof prevReportActionsLength === 'number' && reportActionsLength > prevReportActionsLength; - // Check if a new transaction was added - if ((isExpense && newTransactionAdded) || (isChat && newReportActionAdded)) { + // Check if a new transaction or report action was added + if ((!isChat && !!newTransactionAdded) || (isChat && !!newReportActionAdded)) { // Set the flag indicating the search is triggered by the hook triggeredByHookRef.current = true; @@ -57,30 +59,45 @@ function useSearchHighlightAndScroll({searchResults, transactions, previousTrans searchTriggeredRef.current = true; } - // Reset the ref when transactions are updated + // Reset the ref when transactions (or report actions in chat search type) are updated return () => { searchTriggeredRef.current = false; }; - }, [transactions, previousTransactions, queryJSON, offset, reportActions, previousReportActions, isChat, isExpense]); + }, [transactions, previousTransactions, queryJSON, offset, reportActions, previousReportActions, isChat]); - // Initialize the set with existing transaction IDs only once + // Initialize the set with existing IDs only once useEffect(() => { if (initializedRef.current || !searchResults?.data) { return; } - const existingTransactionIDs = extractTransactionIDsFromSearchResults(searchResults.data); - highlightedIDs.current = new Set(existingTransactionIDs); + const existingIDs = isChat ? extractReportActionIDsFromSearchResults(searchResults.data) : extractTransactionIDsFromSearchResults(searchResults.data); + highlightedIDs.current = new Set(existingIDs); initializedRef.current = true; - }, [searchResults?.data]); + }, [searchResults?.data, isChat]); - // Detect new transactions + // Detect new items (transactions or report actions) useEffect(() => { if (!previousSearchResults || !searchResults?.data) { return; } + if (isChat) { + const previousReportActionIDs = extractReportActionIDsFromSearchResults(previousSearchResults); + const currentReportActionIDs = extractReportActionIDsFromSearchResults(searchResults.data); - if (isExpense) { + // Find new report action IDs that are not in the previousReportActionIDs and not already highlighted + const newReportActionIDs = currentReportActionIDs.filter((id) => !previousReportActionIDs.includes(id) && !highlightedIDs.current.has(id)); + + if (!triggeredByHookRef.current || newReportActionIDs.length === 0) { + return; + } + + const newReportActionID = newReportActionIDs.at(0) ?? ''; + const newReportActionKey = `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${newReportActionID}`; + + setNewSearchResultKey(newReportActionKey); + highlightedIDs.current.add(newReportActionID); + } else { const previousTransactionIDs = extractTransactionIDsFromSearchResults(previousSearchResults); const currentTransactionIDs = extractTransactionIDsFromSearchResults(searchResults.data); @@ -97,24 +114,7 @@ function useSearchHighlightAndScroll({searchResults, transactions, previousTrans setNewSearchResultKey(newTransactionKey); highlightedIDs.current.add(newTransactionID); } - if (isChat) { - const previousReportActionIDs = extractReportActionIDsFromSearchResults(previousSearchResults); - const currentReportActionIDs = extractReportActionIDsFromSearchResults(searchResults.data); - - // Find new transaction IDs that are not in the previousTransactionIDs and not already highlighted - const newReportActionIDs = currentReportActionIDs.filter((id) => !previousReportActionIDs.includes(id) && !highlightedIDs.current.has(id)); - - if (!triggeredByHookRef.current || newReportActionIDs.length === 0) { - return; - } - - const newReportActionID = newReportActionIDs.at(0) ?? ''; - const newReportActionKey = `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${newReportActionID}`; - - setNewSearchResultKey(newReportActionKey); - highlightedIDs.current.add(newReportActionID); - } - }, [searchResults?.data, previousSearchResults, isExpense, isChat]); + }, [searchResults?.data, previousSearchResults, isChat]); // Reset newSearchResultKey after it's been used useEffect(() => { @@ -200,10 +200,13 @@ function extractTransactionIDsFromSearchResults(searchResultsData: Partial): string[] { - return Object.keys(searchResults ?? {}) +/** + * Helper function to extract report action IDs from search results data. + */ +function extractReportActionIDsFromSearchResults(searchResultsData: Partial): string[] { + return Object.keys(searchResultsData ?? {}) .filter(isReportActionEntry) - .map((key) => Object.keys(searchResults[key] ?? {})) + .map((key) => Object.keys(searchResultsData[key] ?? {})) .flat(); } From 0dff3ca9a0a5300245c3824cfa7ec6c3ce523550 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Tue, 3 Dec 2024 23:42:27 +0300 Subject: [PATCH 026/131] added the scrolling for report actions --- src/hooks/useSearchHighlightAndScroll.ts | 40 ++++++++++++++---------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/src/hooks/useSearchHighlightAndScroll.ts b/src/hooks/useSearchHighlightAndScroll.ts index 31d7658e165d..9803470d7c3e 100644 --- a/src/hooks/useSearchHighlightAndScroll.ts +++ b/src/hooks/useSearchHighlightAndScroll.ts @@ -140,35 +140,41 @@ function useSearchHighlightAndScroll({searchResults, transactions, previousTrans return; } - // Extract the transaction ID from the newSearchResultKey - const newTransactionID = newSearchResultKey.replace(ONYXKEYS.COLLECTION.TRANSACTION, ''); - - // Find the index of the new transaction in the data array - const indexOfNewTransaction = data.findIndex((item) => { - // Handle TransactionListItemType - if ('transactionID' in item && item.transactionID === newTransactionID) { - return true; - } - - // Handle ReportListItemType with transactions array - if ('transactions' in item && Array.isArray(item.transactions)) { - return item.transactions.some((transaction) => transaction?.transactionID === newTransactionID); + // Extract the transaction/report action ID from the newSearchResultKey + const newID = newSearchResultKey.replace(isChat ? ONYXKEYS.COLLECTION.REPORT_ACTIONS : ONYXKEYS.COLLECTION.TRANSACTION, ''); + + // Find the index of the new transaction/report action in the data array + const indexOfNewItem = data.findIndex((item) => { + if (isChat) { + if ('reportActionID' in item && item.reportActionID === newID) { + return true; + } + } else { + // Handle TransactionListItemType + if ('transactionID' in item && item.transactionID === newID) { + return true; + } + + // Handle ReportListItemType with transactions array + if ('transactions' in item && Array.isArray(item.transactions)) { + return item.transactions.some((transaction) => transaction?.transactionID === newID); + } } return false; }); - // Early return if the transaction is not found in the data array - if (indexOfNewTransaction <= 0) { + // Early return if the new item is not found in the data array + if (indexOfNewItem <= 0) { return; } // Perform the scrolling action - ref.scrollToIndex(indexOfNewTransaction); + ref.scrollToIndex(indexOfNewItem); // Reset the trigger flag to prevent unintended future scrolls and highlights triggeredByHookRef.current = false; }, - [newSearchResultKey], + [newSearchResultKey, isChat], ); return {newSearchResultKey, handleSelectionListScroll}; From 55e9738de4f134f8290316e3d59753c668638ca6 Mon Sep 17 00:00:00 2001 From: Kalydosos Date: Wed, 4 Dec 2024 00:01:57 +0100 Subject: [PATCH 027/131] update on iou confirmation options --- src/libs/OptionsListUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 477f5f201f4e..fa66d631eb17 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -1340,13 +1340,13 @@ function getIOUConfirmationOptionsFromPayeePersonalDetail(personalDetail: OnyxEn icons: [ { source: personalDetail?.avatar ?? FallbackAvatar, - name: LocalePhoneNumber.formatPhoneNumber(personalDetail?.login ?? ''), + name: personalDetail?.login ?? '', type: CONST.ICON_TYPE_AVATAR, id: personalDetail?.accountID, }, ], descriptiveText: amountText ?? '', - login: LocalePhoneNumber.formatPhoneNumber(personalDetail?.login ?? ''), + login: personalDetail?.login ?? '', accountID: personalDetail?.accountID ?? -1, keyForList: String(personalDetail?.accountID ?? -1), }; From 70cef3ca92e3e93f29e6e3bba0aab2ef7a4e22b3 Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Wed, 4 Dec 2024 13:28:09 +0100 Subject: [PATCH 028/131] create a fixed list of keys that explicitly need masking --- src/libs/ExportOnyxState/common.ts | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/libs/ExportOnyxState/common.ts b/src/libs/ExportOnyxState/common.ts index 46a47528f1fe..c0b382772542 100644 --- a/src/libs/ExportOnyxState/common.ts +++ b/src/libs/ExportOnyxState/common.ts @@ -3,6 +3,22 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type {Session} from '@src/types/onyx'; const MASKING_PATTERN = '***'; +const keysToMask = [ + 'plaidLinkToken', + 'plaidAccessToken', + 'plaidAccountID', + 'addressName', + 'addressCity', + 'addressStreet', + 'addressZipCode', + 'street', + 'city', + 'state', + 'zip', + 'edits', + 'lastMessageHtml', + 'lastMessageText', +]; const maskSessionDetails = (data: Record): Record => { const session = data.session as Session; @@ -40,7 +56,13 @@ const maskFragileData = (data: Record | unknown[] | null, paren const value = data[key]; - if (typeof value === 'string' && Str.isValidEmail(value)) { + if (keysToMask.includes(key)) { + if (Array.isArray(value)) { + maskedData[key] = value.map(() => MASKING_PATTERN); + } else { + maskedData[key] = MASKING_PATTERN; + } + } else if (typeof value === 'string' && Str.isValidEmail(value)) { maskedData[key] = MASKING_PATTERN; } else if (parentKey && parentKey.includes(ONYXKEYS.COLLECTION.REPORT_ACTIONS) && (key === 'text' || key === 'html')) { maskedData[key] = MASKING_PATTERN; From 9daae657f949dda8446bcb83dc4e7314c09e3df6 Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Wed, 4 Dec 2024 14:08:33 +0100 Subject: [PATCH 029/131] add test --- tests/unit/ExportOnyxStateTest.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/unit/ExportOnyxStateTest.ts b/tests/unit/ExportOnyxStateTest.ts index 70faa061cb2a..d239b4c90e43 100644 --- a/tests/unit/ExportOnyxStateTest.ts +++ b/tests/unit/ExportOnyxStateTest.ts @@ -3,6 +3,7 @@ import type * as OnyxTypes from '@src/types/onyx'; type ExampleOnyxState = { session: OnyxTypes.Session; + [key: string]: unknown; }; describe('maskOnyxState', () => { @@ -42,4 +43,17 @@ describe('maskOnyxState', () => { expect(result.session.encryptedAuthToken).toBe('***'); expect(result.session.email).toBe('***'); }); + + it('should mask keys that are in the fixed list', () => { + const input = { + session: mockSession, + edits: ['hey', 'hi'], + lastMessageHtml: 'hey', + }; + + const result = ExportOnyxState.maskOnyxState(input, true) as ExampleOnyxState; + + expect(result.edits).toEqual(['***', '***']); + expect(result.lastMessageHtml).toEqual('***'); + }); }); From ee9a380128d7879a242865478d85c8ed56572876 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Thu, 5 Dec 2024 02:05:04 +0530 Subject: [PATCH 030/131] format --- .../LHNOptionsList/OptionRowLHN.tsx | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index dfc80ef0e775..9defa10c04a8 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -1,17 +1,17 @@ -import { useFocusEffect } from '@react-navigation/native'; -import React, { useCallback, useRef, useState } from 'react'; -import type { GestureResponderEvent, ViewStyle } from 'react-native'; -import { StyleSheet, View } from 'react-native'; -import { useOnyx } from 'react-native-onyx'; +import {useFocusEffect} from '@react-navigation/native'; +import React, {useCallback, useRef, useState} from 'react'; +import type {GestureResponderEvent, ViewStyle} from 'react-native'; +import {StyleSheet, View} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; import DisplayNames from '@components/DisplayNames'; import Hoverable from '@components/Hoverable'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import MultipleAvatars from '@components/MultipleAvatars'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; -import { useSession } from '@components/OnyxProvider'; +import {useSession} from '@components/OnyxProvider'; import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction'; -import { useProductTrainingContext } from '@components/ProductTrainingContext'; +import {useProductTrainingContext} from '@components/ProductTrainingContext'; import SubscriptAvatar from '@components/SubscriptAvatar'; import Text from '@components/Text'; import Tooltip from '@components/Tooltip'; @@ -34,16 +34,16 @@ import variables from '@styles/variables'; import Timing from '@userActions/Timing'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import { isEmptyObject } from '@src/types/utils/EmptyObject'; -import type { OptionRowLHNProps } from './types'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import type {OptionRowLHNProps} from './types'; -function OptionRowLHN({ reportID, isFocused = false, onSelectRow = () => { }, optionItem, viewMode = 'default', style, onLayout = () => { }, hasDraftComment }: OptionRowLHNProps) { +function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, optionItem, viewMode = 'default', style, onLayout = () => {}, hasDraftComment}: OptionRowLHNProps) { const theme = useTheme(); const styles = useThemeStyles(); const popoverAnchor = useRef(null); const StyleUtils = useStyleUtils(); const [isScreenFocused, setIsScreenFocused] = useState(false); - const { shouldUseNarrowLayout } = useResponsiveLayout(); + const {shouldUseNarrowLayout} = useResponsiveLayout(); // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${optionItem?.reportID || -1}`); @@ -53,10 +53,10 @@ function OptionRowLHN({ reportID, isFocused = false, onSelectRow = () => { }, op // Guides are assigned for the MANAGE_TEAM onboarding action, except for emails that have a '+'. const isOnboardingGuideAssigned = introSelected?.choice === CONST.ONBOARDING_CHOICES.MANAGE_TEAM && !session?.email?.includes('+'); const shouldShowGetStartedTooltip = isOnboardingGuideAssigned ? ReportUtils.isAdminRoom(report) : ReportUtils.isConciergeChatReport(report); - const tooltipToRender = (shouldShowGetStartedTooltip && isScreenFocused) ? CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.CONCEIRGE_LHN_GBR : undefined; + const tooltipToRender = shouldShowGetStartedTooltip && isScreenFocused ? CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.CONCEIRGE_LHN_GBR : undefined; - const { shouldShowProductTrainingElement, renderProductTourElement, hideElement } = useProductTrainingContext(tooltipToRender); - const { translate } = useLocalize(); + const {shouldShowProductTrainingElement, renderProductTourElement, hideElement} = useProductTrainingContext(tooltipToRender); + const {translate} = useLocalize(); const [isContextMenuActive, setIsContextMenuActive] = useState(false); useFocusEffect( @@ -125,7 +125,7 @@ function OptionRowLHN({ reportID, isFocused = false, onSelectRow = () => { }, op '-1', reportID, undefined, - () => { }, + () => {}, () => setIsContextMenuActive(false), false, false, From 128411e3d1a190a8bd93e372887ce29fcab02e6c Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Thu, 5 Dec 2024 02:28:31 +0530 Subject: [PATCH 031/131] clean variable namings --- .../LHNOptionsList/OptionRowLHN.tsx | 12 +- .../PRODUCT_TRAINING_TOOLTIP_DATA.ts | 8 +- .../ProductTrainingContext/index.tsx | 104 +++++++++--------- src/pages/Search/SearchTypeMenu.tsx | 14 +-- .../ReportActionCompose.tsx | 8 +- .../FloatingActionButtonAndPopover.tsx | 16 +-- 6 files changed, 80 insertions(+), 82 deletions(-) diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index 9defa10c04a8..18306b4484d7 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -55,7 +55,7 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti const shouldShowGetStartedTooltip = isOnboardingGuideAssigned ? ReportUtils.isAdminRoom(report) : ReportUtils.isConciergeChatReport(report); const tooltipToRender = shouldShowGetStartedTooltip && isScreenFocused ? CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.CONCEIRGE_LHN_GBR : undefined; - const {shouldShowProductTrainingElement, renderProductTourElement, hideElement} = useProductTrainingContext(tooltipToRender); + const {shouldShowProductTrainingTooltip, renderProductTrainingTooltip, hideProductTrainingTooltip} = useProductTrainingContext(tooltipToRender); const {translate} = useLocalize(); const [isContextMenuActive, setIsContextMenuActive] = useState(false); @@ -152,15 +152,15 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti needsOffscreenAlphaCompositing > @@ -172,8 +172,8 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti onPress={(event) => { Performance.markStart(CONST.TIMING.OPEN_REPORT); Timing.start(CONST.TIMING.OPEN_REPORT); - if (shouldShowProductTrainingElement) { - hideElement(); + if (shouldShowProductTrainingTooltip) { + hideProductTrainingTooltip(); } event?.preventDefault(); diff --git a/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts b/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts index e30b78c73b8c..c2f896233a48 100644 --- a/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts +++ b/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts @@ -13,7 +13,7 @@ type ShouldShowConditionProps = { const PRODUCT_TRAINING_TOOLTIP_DATA = { [CONCEIRGE_LHN_GBR]: { content: 'productTrainingTooltip.conciergeLHNGBR', - onHideElement: () => dismissProductTrainingElement(CONCEIRGE_LHN_GBR), + onHideTooltip: () => dismissProductTrainingElement(CONCEIRGE_LHN_GBR), name: CONCEIRGE_LHN_GBR, priority: 1300, shouldShow: ({isDismissed, isOnboardingCompleted, hasBeenAddedToNudgeMigration, shouldUseNarrowLayout}: ShouldShowConditionProps) => { @@ -30,7 +30,7 @@ const PRODUCT_TRAINING_TOOLTIP_DATA = { }, [RENAME_SAVED_SEARCH]: { content: 'search.saveSearchTooltipText', - onHideElement: () => dismissProductTrainingElement(RENAME_SAVED_SEARCH), + onHideTooltip: () => dismissProductTrainingElement(RENAME_SAVED_SEARCH), name: RENAME_SAVED_SEARCH, priority: 1250, shouldShow: ({isDismissed, isOnboardingCompleted, hasBeenAddedToNudgeMigration, shouldUseNarrowLayout}: ShouldShowConditionProps) => { @@ -47,7 +47,7 @@ const PRODUCT_TRAINING_TOOLTIP_DATA = { }, [QUICK_ACTION_BUTTON]: { content: 'quickAction.tooltip.subtitle', - onHideElement: () => dismissProductTrainingElement(QUICK_ACTION_BUTTON), + onHideTooltip: () => dismissProductTrainingElement(QUICK_ACTION_BUTTON), name: QUICK_ACTION_BUTTON, priority: 1200, shouldShow: ({isDismissed, isOnboardingCompleted, hasBeenAddedToNudgeMigration}: ShouldShowConditionProps) => { @@ -64,7 +64,7 @@ const PRODUCT_TRAINING_TOOLTIP_DATA = { }, [WORKSAPCE_CHAT_CREATE]: { content: 'reportActionCompose.tooltip.subtitle', - onHideElement: () => dismissProductTrainingElement(WORKSAPCE_CHAT_CREATE), + onHideTooltip: () => dismissProductTrainingElement(WORKSAPCE_CHAT_CREATE), name: WORKSAPCE_CHAT_CREATE, priority: 1100, shouldShow: ({isDismissed, isOnboardingCompleted, hasBeenAddedToNudgeMigration}: ShouldShowConditionProps) => { diff --git a/src/components/ProductTrainingContext/index.tsx b/src/components/ProductTrainingContext/index.tsx index 9b827ced556b..42cd1d8e19db 100644 --- a/src/components/ProductTrainingContext/index.tsx +++ b/src/components/ProductTrainingContext/index.tsx @@ -18,18 +18,18 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; import PRODUCT_TRAINING_TOOLTIP_DATA from './PRODUCT_TRAINING_TOOLTIP_DATA'; -type ProductTrainingElementName = ValueOf; +type ProductTrainingTooltipName = ValueOf; type ProductTrainingContextType = { - shouldRenderElement: (elementName: ProductTrainingElementName) => boolean; - renderProductTrainingElement: (elementName: ProductTrainingElementName) => React.ReactNode | null; - registerTooltip: (elementName: ProductTrainingElementName) => void; - unregisterTooltip: (elementName: ProductTrainingElementName) => void; + shouldRenderTooltip: (tooltipName: ProductTrainingTooltipName) => boolean; + renderProductTrainingTooltip: (tooltipName: ProductTrainingTooltipName) => React.ReactNode | null; + registerTooltip: (tooltipName: ProductTrainingTooltipName) => void; + unregisterTooltip: (tooltipName: ProductTrainingTooltipName) => void; }; const ProductTrainingContext = createContext({ - shouldRenderElement: () => false, - renderProductTrainingElement: () => null, + shouldRenderTooltip: () => false, + renderProductTrainingTooltip: () => null, registerTooltip: () => {}, unregisterTooltip: () => {}, }); @@ -47,13 +47,13 @@ function ProductTrainingContextProvider({children}: ChildrenProps) { const theme = useTheme(); // Track active tooltips - const [activeTooltips, setActiveTooltips] = useState>(new Set()); + const [activeTooltips, setActiveTooltips] = useState>(new Set()); const unregisterTooltip = useCallback( - (elementName: ProductTrainingElementName) => { + (tooltipName: ProductTrainingTooltipName) => { setActiveTooltips((prev) => { const next = new Set(prev); - next.delete(elementName); + next.delete(tooltipName); return next; }); }, @@ -82,10 +82,10 @@ function ProductTrainingContextProvider({children}: ChildrenProps) { }, [activeTooltips]); const shouldTooltipBeVisible = useCallback( - (elementName: ProductTrainingElementName) => { - const isDismissed = !!dismissedProductTraining?.[elementName]; + (tooltipName: ProductTrainingTooltipName) => { + const isDismissed = !!dismissedProductTraining?.[tooltipName]; - const tooltipConfig = PRODUCT_TRAINING_TOOLTIP_DATA[elementName]; + const tooltipConfig = PRODUCT_TRAINING_TOOLTIP_DATA[tooltipName]; return tooltipConfig.shouldShow({ isDismissed, @@ -98,27 +98,27 @@ function ProductTrainingContextProvider({children}: ChildrenProps) { ); const registerTooltip = useCallback( - (elementName: ProductTrainingElementName) => { - const shouldRegister = shouldTooltipBeVisible(elementName); + (tooltipName: ProductTrainingTooltipName) => { + const shouldRegister = shouldTooltipBeVisible(tooltipName); if (!shouldRegister) { return; } - setActiveTooltips((prev) => new Set([...prev, elementName])); + setActiveTooltips((prev) => new Set([...prev, tooltipName])); }, [shouldTooltipBeVisible], ); - const shouldRenderElement = useCallback( - (elementName: ProductTrainingElementName) => { + const shouldRenderTooltip = useCallback( + (tooltipName: ProductTrainingTooltipName) => { // First check base conditions - const shouldShow = shouldTooltipBeVisible(elementName); + const shouldShow = shouldTooltipBeVisible(tooltipName); if (!shouldShow) { return false; } const visibleTooltip = determineVisibleTooltip(); // If this is the highest priority visible tooltip, show it - if (elementName === visibleTooltip) { + if (tooltipName === visibleTooltip) { return true; } @@ -127,14 +127,14 @@ function ProductTrainingContextProvider({children}: ChildrenProps) { [shouldTooltipBeVisible, determineVisibleTooltip], ); - const renderProductTourElement = useCallback( - (elementName: ProductTrainingElementName) => { - const element = PRODUCT_TRAINING_TOOLTIP_DATA[elementName]; - if (!element) { + const renderProductTrainingTooltip = useCallback( + (tooltipName: ProductTrainingTooltipName) => { + const tooltip = PRODUCT_TRAINING_TOOLTIP_DATA[tooltipName]; + if (!tooltip) { return null; } const processedContent = () => { - const content = convertToLTR(translate(element.content as TranslationPaths)); + const content = convertToLTR(translate(tooltip.content as TranslationPaths)); return content ? `${content}` : ''; }; @@ -166,66 +166,64 @@ function ProductTrainingContextProvider({children}: ChildrenProps) { const contextValue = useMemo( () => ({ - renderProductTrainingElement: renderProductTourElement, - shouldRenderElement, + renderProductTrainingTooltip, + shouldRenderTooltip, registerTooltip, unregisterTooltip, }), - [shouldRenderElement, registerTooltip, unregisterTooltip, renderProductTourElement], + [shouldRenderTooltip, registerTooltip, unregisterTooltip, renderProductTrainingTooltip], ); return {children}; } -const useProductTrainingContext = (elementName?: ProductTrainingElementName) => { +const useProductTrainingContext = (tooltipName?: ProductTrainingTooltipName) => { const context = useContext(ProductTrainingContext); if (!context) { throw new Error('useProductTourContext must be used within a ProductTourProvider'); } - const {shouldRenderElement, registerTooltip, unregisterTooltip, renderProductTrainingElement} = context; + const {shouldRenderTooltip, registerTooltip, unregisterTooltip, renderProductTrainingTooltip} = context; // Register this tooltip when the component mounts and unregister when it unmounts useEffect(() => { - if (elementName) { - registerTooltip(elementName); + if (tooltipName) { + registerTooltip(tooltipName); return () => { - unregisterTooltip(elementName); + unregisterTooltip(tooltipName); }; } return undefined; - }, [elementName, registerTooltip, unregisterTooltip]); + }, [tooltipName, registerTooltip, unregisterTooltip]); - const shouldShowProductTrainingElement = useMemo(() => { - if (!elementName) { + const shouldShowProductTrainingTooltip = useMemo(() => { + if (!tooltipName) { return false; } - return shouldRenderElement(elementName); - }, [elementName, shouldRenderElement]); + return shouldRenderTooltip(tooltipName); + }, [tooltipName, shouldRenderTooltip]); - const hideElement = useCallback(() => { - if (!elementName) { + const hideProductTrainingTooltip = useCallback(() => { + if (!tooltipName) { return; } - const element = PRODUCT_TRAINING_TOOLTIP_DATA[elementName]; - if (element?.onHideElement) { - element.onHideElement(); - } - unregisterTooltip(elementName); - }, [elementName, unregisterTooltip]); + const tooltip = PRODUCT_TRAINING_TOOLTIP_DATA[tooltipName]; + tooltip.onHideTooltip(); + unregisterTooltip(tooltipName); + }, [tooltipName, unregisterTooltip]); - if (!elementName) { + if (!tooltipName) { return { - renderProductTourElement: () => null, - hideElement: () => {}, - shouldShowProductTrainingElement: false, + renderProductTrainingTooltip: () => null, + hideProductTrainingTooltip: () => {}, + shouldShowProductTrainingTooltip: false, }; } return { - renderProductTourElement: () => renderProductTrainingElement(elementName), - hideElement, - shouldShowProductTrainingElement, + renderProductTrainingTooltip: () => renderProductTrainingTooltip(tooltipName), + hideProductTrainingTooltip, + shouldShowProductTrainingTooltip, }; }; diff --git a/src/pages/Search/SearchTypeMenu.tsx b/src/pages/Search/SearchTypeMenu.tsx index bd42c3dc8ab6..4a3c0881b5a6 100644 --- a/src/pages/Search/SearchTypeMenu.tsx +++ b/src/pages/Search/SearchTypeMenu.tsx @@ -63,7 +63,7 @@ function SearchTypeMenu({queryJSON, searchName}: SearchTypeMenuProps) { const {singleExecution} = useSingleExecution(); const {translate} = useLocalize(); const [savedSearches] = useOnyx(ONYXKEYS.SAVED_SEARCHES); - const {shouldShowProductTrainingElement, renderProductTourElement, hideElement} = useProductTrainingContext(CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.RENAME_SAVED_SEARCH); + const {shouldShowProductTrainingTooltip, renderProductTrainingTooltip, hideProductTrainingTooltip} = useProductTrainingContext(CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.RENAME_SAVED_SEARCH); const {showDeleteModal, DeleteConfirmModal} = useDeleteSavedSearch(); const [session] = useOnyx(ONYXKEYS.SESSION); @@ -153,7 +153,7 @@ function SearchTypeMenu({queryJSON, searchName}: SearchTypeMenuProps) { if (!isNarrow) { return { ...baseMenuItem, - shouldRenderTooltip: index === 0 && shouldShowProductTrainingElement, + shouldRenderTooltip: index === 0 && shouldShowProductTrainingTooltip, tooltipAnchorAlignment: { horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT, vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM, @@ -161,8 +161,8 @@ function SearchTypeMenu({queryJSON, searchName}: SearchTypeMenuProps) { tooltipShiftHorizontal: -32, tooltipShiftVertical: 15, tooltipWrapperStyle: [styles.bgPaleGreen, styles.mh4, styles.pv2], - onHideTooltip: hideElement, - renderTooltipContent: renderProductTourElement, + onHideTooltip: hideProductTrainingTooltip, + renderTooltipContent: renderProductTrainingTooltip, }; } return baseMenuItem; @@ -177,9 +177,9 @@ function SearchTypeMenu({queryJSON, searchName}: SearchTypeMenuProps) { personalDetails, reports, taxRates, - shouldShowProductTrainingElement, - hideElement, - renderProductTourElement, + shouldShowProductTrainingTooltip, + hideProductTrainingTooltip, + renderProductTrainingTooltip, ], ); diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index 33b6f5b7e8f3..ec5bd556cd72 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -123,7 +123,7 @@ function ReportActionCompose({ const [blockedFromConcierge] = useOnyx(ONYXKEYS.NVP_BLOCKED_FROM_CONCIERGE); const [shouldShowComposeInput = true] = useOnyx(ONYXKEYS.SHOULD_SHOW_COMPOSE_INPUT); - const {renderProductTourElement, hideElement, shouldShowProductTrainingElement} = useProductTrainingContext( + const {renderProductTrainingTooltip, hideProductTrainingTooltip, shouldShowProductTrainingTooltip} = useProductTrainingContext( shouldShowEducationalTooltip ? CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.WORKSAPCE_CHAT_CREATE : undefined, ); @@ -389,10 +389,10 @@ function ReportActionCompose({ contentContainerStyle={isComposerFullSize ? styles.flex1 : {}} > interceptAnonymousUser(() => navigateToQuickAction()), shouldShowSubscriptRightAvatar: ReportUtils.isPolicyExpenseChat(quickActionReport), - shouldRenderTooltip: shouldShowProductTrainingElement, + shouldRenderTooltip: shouldShowProductTrainingTooltip, }, ]; } @@ -470,7 +470,7 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu}: Fl }, true); }), shouldShowSubscriptRightAvatar: true, - shouldRenderTooltip: shouldShowProductTrainingElement, + shouldRenderTooltip: shouldShowProductTrainingTooltip, }, ]; } @@ -482,14 +482,14 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu}: Fl styles.popoverMenuItem.paddingHorizontal, styles.popoverMenuItem.paddingVertical, styles.quickActionTooltipWrapper, - renderProductTourElement, - hideElement, + renderProductTrainingTooltip, + hideProductTrainingTooltip, quickAction?.action, policyChatForActivePolicy, quickActionTitle, hideQABSubtitle, quickActionReport, - shouldShowProductTrainingElement, + shouldShowProductTrainingTooltip, navigateToQuickAction, selectOption, isValidReport, From 1df261f35d18a9ae8ed34af6fb7331a027a9efec Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Thu, 5 Dec 2024 02:30:16 +0530 Subject: [PATCH 032/131] remove unneccessary comment --- src/components/ProductTrainingContext/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/ProductTrainingContext/index.tsx b/src/components/ProductTrainingContext/index.tsx index 42cd1d8e19db..2df640e44bf6 100644 --- a/src/components/ProductTrainingContext/index.tsx +++ b/src/components/ProductTrainingContext/index.tsx @@ -46,7 +46,6 @@ function ProductTrainingContextProvider({children}: ChildrenProps) { const styles = useThemeStyles(); const theme = useTheme(); - // Track active tooltips const [activeTooltips, setActiveTooltips] = useState>(new Set()); const unregisterTooltip = useCallback( From 01226982948c7b719c6e2406f56deaaadc7b5aca Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Thu, 5 Dec 2024 02:52:05 +0530 Subject: [PATCH 033/131] fix translations --- .../PRODUCT_TRAINING_TOOLTIP_DATA.ts | 6 +++--- src/languages/en.ts | 12 +++--------- src/languages/es.ts | 15 ++++++--------- 3 files changed, 12 insertions(+), 21 deletions(-) diff --git a/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts b/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts index c2f896233a48..d2c8acb28e9e 100644 --- a/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts +++ b/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts @@ -29,7 +29,7 @@ const PRODUCT_TRAINING_TOOLTIP_DATA = { }, }, [RENAME_SAVED_SEARCH]: { - content: 'search.saveSearchTooltipText', + content: 'productTrainingTooltip.saveSearchTooltipText', onHideTooltip: () => dismissProductTrainingElement(RENAME_SAVED_SEARCH), name: RENAME_SAVED_SEARCH, priority: 1250, @@ -46,7 +46,7 @@ const PRODUCT_TRAINING_TOOLTIP_DATA = { }, }, [QUICK_ACTION_BUTTON]: { - content: 'quickAction.tooltip.subtitle', + content: 'productTrainingTooltip.quickActionButton', onHideTooltip: () => dismissProductTrainingElement(QUICK_ACTION_BUTTON), name: QUICK_ACTION_BUTTON, priority: 1200, @@ -63,7 +63,7 @@ const PRODUCT_TRAINING_TOOLTIP_DATA = { }, }, [WORKSAPCE_CHAT_CREATE]: { - content: 'reportActionCompose.tooltip.subtitle', + content: 'productTrainingTooltip.workspaceChatCreate', onHideTooltip: () => dismissProductTrainingElement(WORKSAPCE_CHAT_CREATE), name: WORKSAPCE_CHAT_CREATE, priority: 1100, diff --git a/src/languages/en.ts b/src/languages/en.ts index 18b624c09041..8a92f0f55e71 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -645,10 +645,6 @@ const translations = { emoji: 'Emoji', collapse: 'Collapse', expand: 'Expand', - tooltip: { - title: 'Get started!', - subtitle: ' Submit your first expense', - }, }, reportActionContextMenu: { copyToClipboard: 'Copy to clipboard', @@ -834,10 +830,6 @@ const translations = { trackDistance: 'Track distance', noLongerHaveReportAccess: 'You no longer have access to your previous quick action destination. Pick a new one below.', updateDestination: 'Update destination', - tooltip: { - title: 'Quick action! ', - subtitle: 'Just a tap away.', - }, }, iou: { amount: 'Amount', @@ -4542,7 +4534,6 @@ const translations = { }, }, saveSearch: 'Save search', - saveSearchTooltipText: 'You can rename your saved search', deleteSavedSearch: 'Delete saved search', deleteSavedSearchConfirm: 'Are you sure you want to delete this search?', searchName: 'Search name', @@ -5439,6 +5430,9 @@ const translations = { }, productTrainingTooltip: { conciergeLHNGBR: 'Get started here!', + saveSearchTooltipText: 'You can rename your saved search here!', + quickActionButton: 'Quick action! Just a tap away', + workspaceChatCreate: 'Get started! Submit your expenses here!', }, }; diff --git a/src/languages/es.ts b/src/languages/es.ts index 0d3426d21eda..06e8d6426bbd 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -637,10 +637,6 @@ const translations = { emoji: 'Emoji', collapse: 'Colapsar', expand: 'Expandir', - tooltip: { - title: '¡Empecemos!', - subtitle: ' Presenta tu primer gasto', - }, }, reportActionContextMenu: { copyToClipboard: 'Copiar al portapapeles', @@ -829,10 +825,6 @@ const translations = { trackDistance: 'Crear gasto por desplazamiento', noLongerHaveReportAccess: 'Ya no tienes acceso al destino previo de esta acción rápida. Escoge uno nuevo a continuación.', updateDestination: 'Actualiza el destino', - tooltip: { - title: '¡Acción rápida! ', - subtitle: 'A un click.', - }, }, iou: { amount: 'Importe', @@ -4591,7 +4583,6 @@ const translations = { }, }, saveSearch: 'Guardar búsqueda', - saveSearchTooltipText: 'Puedes cambiar el nombre de tu búsqueda guardada', savedSearchesMenuItemTitle: 'Guardadas', searchName: 'Nombre de la búsqueda', deleteSavedSearch: 'Eliminar búsqueda guardada', @@ -5957,6 +5948,12 @@ const translations = { crossPlatform: 'Do everything from your phone or browser', }, }, + productTrainingTooltip: { + conciergeLHNGBR: 'Get started here!', + saveSearchTooltipText: 'You can rename your saved search here!', + quickActionButton: 'Quick action! Just a tap away', + workspaceChatCreate: 'Get started! Submit your expenses here!', + }, }; export default translations satisfies TranslationDeepObject; From e308a4b41e4e6a8a19b554879d881cb57f1fbf04 Mon Sep 17 00:00:00 2001 From: Kalydosos Date: Wed, 4 Dec 2024 23:27:44 +0100 Subject: [PATCH 034/131] revert changes on cards --- .../companyCards/WorkspaceCompanyCardDetailsPage.tsx | 4 ++-- .../workspace/companyCards/WorkspaceCompanyCardsListRow.tsx | 3 +-- src/pages/workspace/expensifyCard/WorkspaceCardListRow.tsx | 3 +-- .../expensifyCard/WorkspaceExpensifyCardDetailsPage.tsx | 4 ++-- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardDetailsPage.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardDetailsPage.tsx index dcb7d629de37..015d018afb76 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardDetailsPage.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardDetailsPage.tsx @@ -46,7 +46,7 @@ function WorkspaceCompanyCardDetailsPage({route}: WorkspaceCompanyCardDetailsPag const workspaceAccountID = PolicyUtils.getWorkspaceAccountID(policyID); const policy = usePolicy(policyID); const [isUnassignModalVisible, setIsUnassignModalVisible] = useState(false); - const {formatPhoneNumber, translate} = useLocalize(); + const {translate} = useLocalize(); const styles = useThemeStyles(); const theme = useTheme(); const {isOffline} = useNetwork(); @@ -59,7 +59,7 @@ function WorkspaceCompanyCardDetailsPage({route}: WorkspaceCompanyCardDetailsPag const cardBank = card?.bank ?? ''; const cardholder = personalDetails?.[card?.accountID ?? -1]; - const displayName = formatPhoneNumber(PersonalDetailsUtils.getDisplayNameOrDefault(cardholder)); + const displayName = PersonalDetailsUtils.getDisplayNameOrDefault(cardholder); const exportMenuItem = getExportMenuItem(connectedIntegration, policyID, translate, policy, card); const unassignCard = () => { diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsListRow.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsListRow.tsx index 2783758c978b..5c85d2e40ae0 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsListRow.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsListRow.tsx @@ -3,7 +3,6 @@ import {View} from 'react-native'; import Avatar from '@components/Avatar'; import Text from '@components/Text'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as LocalePhoneNumber from '@libs/LocalePhoneNumber'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import {getDefaultAvatarURL} from '@libs/UserUtils'; import CONST from '@src/CONST'; @@ -22,7 +21,7 @@ type WorkspaceCompanyCardsListRowProps = { function WorkspaceCompanyCardsListRow({cardholder, name, cardNumber}: WorkspaceCompanyCardsListRowProps) { const styles = useThemeStyles(); - const cardholderName = useMemo(() => LocalePhoneNumber.formatPhoneNumber(PersonalDetailsUtils.getDisplayNameOrDefault(cardholder)), [cardholder]); + const cardholderName = useMemo(() => PersonalDetailsUtils.getDisplayNameOrDefault(cardholder), [cardholder]); return ( diff --git a/src/pages/workspace/expensifyCard/WorkspaceCardListRow.tsx b/src/pages/workspace/expensifyCard/WorkspaceCardListRow.tsx index 619e55486cab..0d59d5d9c762 100644 --- a/src/pages/workspace/expensifyCard/WorkspaceCardListRow.tsx +++ b/src/pages/workspace/expensifyCard/WorkspaceCardListRow.tsx @@ -7,7 +7,6 @@ import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import * as CurrencyUtils from '@libs/CurrencyUtils'; -import * as LocalePhoneNumber from '@libs/LocalePhoneNumber'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import CONST from '@src/CONST'; import type {PersonalDetails} from '@src/types/onyx'; @@ -37,7 +36,7 @@ function WorkspaceCardListRow({limit, cardholder, lastFourPAN, name, currency, i const styles = useThemeStyles(); const {translate} = useLocalize(); - const cardholderName = useMemo(() => LocalePhoneNumber.formatPhoneNumber(PersonalDetailsUtils.getDisplayNameOrDefault(cardholder)), [cardholder]); + const cardholderName = useMemo(() => PersonalDetailsUtils.getDisplayNameOrDefault(cardholder), [cardholder]); const cardType = isVirtual ? translate('workspace.expensifyCard.virtual') : translate('workspace.expensifyCard.physical'); return ( diff --git a/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardDetailsPage.tsx b/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardDetailsPage.tsx index fb6f0a5b82ca..5451c65e16a6 100644 --- a/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardDetailsPage.tsx +++ b/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardDetailsPage.tsx @@ -41,7 +41,7 @@ function WorkspaceExpensifyCardDetailsPage({route}: WorkspaceExpensifyCardDetail const [isDeactivateModalVisible, setIsDeactivateModalVisible] = useState(false); const [isOfflineModalVisible, setIsOfflineModalVisible] = useState(false); - const {formatPhoneNumber, translate} = useLocalize(); + const {translate} = useLocalize(); // We need to use isSmallScreenWidth instead of shouldUseNarrowLayout to use the correct modal type for the decision modal // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth const {isSmallScreenWidth} = useResponsiveLayout(); @@ -55,7 +55,7 @@ function WorkspaceExpensifyCardDetailsPage({route}: WorkspaceExpensifyCardDetail const isVirtual = !!card?.nameValuePairs?.isVirtual; const formattedAvailableSpendAmount = CurrencyUtils.convertToDisplayString(card?.availableSpend); const formattedLimit = CurrencyUtils.convertToDisplayString(card?.nameValuePairs?.unapprovedExpenseLimit); - const displayName = formatPhoneNumber(PersonalDetailsUtils.getDisplayNameOrDefault(cardholder)); + const displayName = PersonalDetailsUtils.getDisplayNameOrDefault(cardholder); const translationForLimitType = CardUtils.getTranslationKeyForLimitType(card?.nameValuePairs?.limitType); const fetchCardDetails = useCallback(() => { From a4f4c5255771cfa8c52b6fb1577bc8cdf5ca4d1f Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 5 Dec 2024 12:47:46 +0700 Subject: [PATCH 035/131] fix timezone isn't updated on the onyx storage --- src/libs/Navigation/AppNavigator/AuthScreens.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index 99cd1498511c..504aa5883620 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -168,7 +168,6 @@ Onyx.connect({ // If the current timezone is different than the user's timezone, and their timezone is set to automatic // then update their timezone. if (!isEmptyObject(currentTimezone) && timezone?.automatic && timezone?.selected !== currentTimezone) { - timezone.selected = currentTimezone; PersonalDetails.updateAutomaticTimezone({ automatic: true, selected: currentTimezone, From 3e474d644688be978fe37713887f5a6cfb3c86bf Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Thu, 5 Dec 2024 12:21:38 +0530 Subject: [PATCH 036/131] minor update. Signed-off-by: krishna2323 --- src/components/ReportActionItem/ReportPreview.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 8823ece3add6..4e2ba738df6d 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -472,7 +472,7 @@ function ReportPreview({ return; } - thumbsUpScale.set(isApprovedAnimationRunning ? withDelay(CONST.ANIMATION_PAID_CHECKMARK_DELAY, withSpring(1, {duration: CONST.ANIMATION_PAID_DURATION})) : 1); + thumbsUpScale.set(withSpring(1, {duration: CONST.ANIMATION_PAID_DURATION})); }, [isApproved, isApprovedAnimationRunning, thumbsUpScale]); const openReportFromPreview = useCallback(() => { From 8bbd98ee4002f337428fbb513d17195132b50a31 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Thu, 5 Dec 2024 12:24:10 +0530 Subject: [PATCH 037/131] change scale value. Signed-off-by: krishna2323 --- src/components/ReportActionItem/ReportPreview.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 4e2ba738df6d..b73eccc96b83 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -155,7 +155,7 @@ function ReportPreview({ const previewMessageStyle = useAnimatedStyle(() => ({ opacity: previewMessageOpacity.get(), })); - const checkMarkScale = useSharedValue(iouSettled ? 1 : 0); + const checkMarkScale = useSharedValue(iouSettled ? 1 : 0.25); const isApproved = ReportUtils.isReportApproved(iouReport, action); const thumbsUpScale = useSharedValue(isApproved ? 1 : 0); From 864293c3bf382f23c3f624985acaab2208be1ba1 Mon Sep 17 00:00:00 2001 From: Kalydosos Date: Thu, 5 Dec 2024 15:09:48 +0100 Subject: [PATCH 038/131] migrate some files from withOnyx to useOnyx --- src/components/ArchivedReportFooter.tsx | 30 +++++--------------- src/pages/ReportParticipantDetailsPage.tsx | 23 ++++----------- src/pages/settings/Profile/ProfileAvatar.tsx | 29 +++++-------------- 3 files changed, 19 insertions(+), 63 deletions(-) diff --git a/src/components/ArchivedReportFooter.tsx b/src/components/ArchivedReportFooter.tsx index 7c199e28bca8..43c9b63fb505 100644 --- a/src/components/ArchivedReportFooter.tsx +++ b/src/components/ArchivedReportFooter.tsx @@ -1,7 +1,6 @@ import lodashEscape from 'lodash/escape'; import React from 'react'; -import type {OnyxEntry} from 'react-native-onyx'; -import {withOnyx} from 'react-native-onyx'; +import {useOnyx} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import {getCurrentUserAccountID} from '@libs/actions/Report'; @@ -10,26 +9,20 @@ import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {PersonalDetailsList, Report, ReportAction} from '@src/types/onyx'; +import type {Report} from '@src/types/onyx'; import Banner from './Banner'; -type ArchivedReportFooterOnyxProps = { - /** The reason this report was archived */ - reportClosedAction: OnyxEntry; - - /** Personal details of all users */ - personalDetails: OnyxEntry; -}; - -type ArchivedReportFooterProps = ArchivedReportFooterOnyxProps & { +type ArchivedReportFooterProps = { /** The archived report */ report: Report; }; -function ArchivedReportFooter({report, reportClosedAction, personalDetails = {}}: ArchivedReportFooterProps) { +function ArchivedReportFooter({report}: ArchivedReportFooterProps) { const styles = useThemeStyles(); const {formatPhoneNumber, translate} = useLocalize(); + const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {initialValue: {}}); + const [reportClosedAction] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, {canEvict: false, selector: ReportActionsUtils.getLastClosedReportAction}); const originalMessage = ReportActionsUtils.isClosedAction(reportClosedAction) ? ReportActionsUtils.getOriginalMessage(reportClosedAction) : null; const archiveReason = originalMessage?.reason ?? CONST.REPORT.ARCHIVE_REASON.DEFAULT; const actorPersonalDetails = personalDetails?.[reportClosedAction?.actorAccountID ?? -1]; @@ -78,13 +71,4 @@ function ArchivedReportFooter({report, reportClosedAction, personalDetails = {}} ArchivedReportFooter.displayName = 'ArchivedReportFooter'; -export default withOnyx({ - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - }, - reportClosedAction: { - key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, - canEvict: false, - selector: ReportActionsUtils.getLastClosedReportAction, - }, -})(ArchivedReportFooter); +export default ArchivedReportFooter; diff --git a/src/pages/ReportParticipantDetailsPage.tsx b/src/pages/ReportParticipantDetailsPage.tsx index 50edc272296c..3f2255758f37 100644 --- a/src/pages/ReportParticipantDetailsPage.tsx +++ b/src/pages/ReportParticipantDetailsPage.tsx @@ -1,7 +1,6 @@ import React, {useCallback} from 'react'; import {View} from 'react-native'; -import type {OnyxEntry} from 'react-native-onyx'; -import {withOnyx} from 'react-native-onyx'; +import {useOnyx} from 'react-native-onyx'; import Avatar from '@components/Avatar'; import Button from '@components/Button'; import ConfirmModal from '@components/ConfirmModal'; @@ -31,19 +30,13 @@ import NotFoundPage from './ErrorPage/NotFoundPage'; import withReportOrNotFound from './home/report/withReportOrNotFound'; import type {WithReportOrNotFoundProps} from './home/report/withReportOrNotFound'; -type ReportParticipantDetailsOnyxProps = { - /** Personal details of all users */ - personalDetails: OnyxEntry; -}; +type ReportParticipantDetailsPageProps = WithReportOrNotFoundProps & PlatformStackScreenProps; -type ReportParticipantDetailsPageProps = WithReportOrNotFoundProps & - PlatformStackScreenProps & - ReportParticipantDetailsOnyxProps; - -function ReportParticipantDetails({personalDetails, report, route}: ReportParticipantDetailsPageProps) { +function ReportParticipantDetails({report, route}: ReportParticipantDetailsPageProps) { const styles = useThemeStyles(); const {formatPhoneNumber, translate} = useLocalize(); const StyleUtils = useStyleUtils(); + const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const [isRemoveMemberConfirmModalVisible, setIsRemoveMemberConfirmModalVisible] = React.useState(false); @@ -149,10 +142,4 @@ function ReportParticipantDetails({personalDetails, report, route}: ReportPartic ReportParticipantDetails.displayName = 'ReportParticipantDetails'; -export default withReportOrNotFound()( - withOnyx({ - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - }, - })(ReportParticipantDetails), -); +export default withReportOrNotFound()(ReportParticipantDetails); diff --git a/src/pages/settings/Profile/ProfileAvatar.tsx b/src/pages/settings/Profile/ProfileAvatar.tsx index c295984fb3ef..74b88f419e1d 100644 --- a/src/pages/settings/Profile/ProfileAvatar.tsx +++ b/src/pages/settings/Profile/ProfileAvatar.tsx @@ -1,6 +1,5 @@ import React, {useEffect} from 'react'; -import type {OnyxEntry} from 'react-native-onyx'; -import {withOnyx} from 'react-native-onyx'; +import {useOnyx} from 'react-native-onyx'; import AttachmentModal from '@components/AttachmentModal'; import * as LocalePhoneNumber from '@libs/LocalePhoneNumber'; import Navigation from '@libs/Navigation/Navigation'; @@ -12,17 +11,13 @@ import * as ValidationUtils from '@libs/ValidationUtils'; import * as PersonalDetails from '@userActions/PersonalDetails'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; -import type {PersonalDetailsList, PersonalDetailsMetadata} from '@src/types/onyx'; -type ProfileAvatarOnyxProps = { - personalDetails: OnyxEntry; - personalDetailsMetadata: OnyxEntry>; - isLoadingApp: OnyxEntry; -}; +type ProfileAvatarProps = PlatformStackScreenProps; -type ProfileAvatarProps = ProfileAvatarOnyxProps & PlatformStackScreenProps; - -function ProfileAvatar({route, personalDetails, personalDetailsMetadata, isLoadingApp = true}: ProfileAvatarProps) { +function ProfileAvatar({route}: ProfileAvatarProps) { + const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); + const [personalDetailsMetadata] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_METADATA); + const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP, {initialValue: true}); const personalDetail = personalDetails?.[route.params.accountID]; const avatarURL = personalDetail?.avatar ?? ''; const accountID = Number(route.params.accountID ?? '-1'); @@ -53,14 +48,4 @@ function ProfileAvatar({route, personalDetails, personalDetailsMetadata, isLoadi ProfileAvatar.displayName = 'ProfileAvatar'; -export default withOnyx({ - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - }, - personalDetailsMetadata: { - key: ONYXKEYS.PERSONAL_DETAILS_METADATA, - }, - isLoadingApp: { - key: ONYXKEYS.IS_LOADING_APP, - }, -})(ProfileAvatar); +export default ProfileAvatar; From 63e30c3509b12ded5387f8906e5b468e44bf3261 Mon Sep 17 00:00:00 2001 From: Kalydosos Date: Thu, 5 Dec 2024 15:15:15 +0100 Subject: [PATCH 039/131] migrate some files from withOnyx to useOnyx --- src/pages/ReportParticipantDetailsPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/ReportParticipantDetailsPage.tsx b/src/pages/ReportParticipantDetailsPage.tsx index 3f2255758f37..bfc4506818d1 100644 --- a/src/pages/ReportParticipantDetailsPage.tsx +++ b/src/pages/ReportParticipantDetailsPage.tsx @@ -25,7 +25,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -import type {PersonalDetails, PersonalDetailsList} from '@src/types/onyx'; +import type {PersonalDetails} from '@src/types/onyx'; import NotFoundPage from './ErrorPage/NotFoundPage'; import withReportOrNotFound from './home/report/withReportOrNotFound'; import type {WithReportOrNotFoundProps} from './home/report/withReportOrNotFound'; From 669de444be8018a6b8a52a868585c1e191fa194f Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Thu, 5 Dec 2024 19:10:24 +0300 Subject: [PATCH 040/131] reset route on failure data --- src/libs/actions/IOU.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 63b4e04a184c..32aa8d908b7c 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -3253,6 +3253,7 @@ function updateMoneyRequestDistance({ }, }, modifiedWaypoints: onyxModifiedWaypoints, + routes: null, }, }); } From 8c1fc5eb797928323f758e60b80b1f39ea76c56f Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Thu, 5 Dec 2024 19:26:09 +0300 Subject: [PATCH 041/131] updated comment --- src/libs/actions/IOU.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 32aa8d908b7c..506bfd06936d 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -3231,7 +3231,9 @@ function updateMoneyRequestDistance({ if (transactionBackup) { const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; // We need to have all the keys of the original waypoint in the failure data for onyx merge to properly reset - // waypoints keys that do not exist in the waypoint of the reverting failure data. + // waypoints keys that do not exist in the waypoint of the reverting failure data. For instance, if a waypoint had + // three keys and the waypoint we we want to revert to has 2 keys then the third key that doesn't exist in the failureData + // waypoint should be explicitly reset otherwise onyx merge will leave it intact. const allWaypointKeys = [...new Set([...Object.keys(transactionBackup.comment?.waypoints ?? {}), ...Object.keys(transaction?.comment?.waypoints ?? {})])]; const onyxWaypoints = allWaypointKeys.reduce((acc: NullishDeep, key) => { acc[key] = transactionBackup.comment?.waypoints?.[key] ? {...transactionBackup.comment?.waypoints?.[key]} : null; From d1b92d3d35c937e143df12934ef1be02436c9f32 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Thu, 5 Dec 2024 23:55:18 +0530 Subject: [PATCH 042/131] refactor ProductTrainingContextProvider styles and dependencies --- src/components/ProductTrainingContext/index.tsx | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/components/ProductTrainingContext/index.tsx b/src/components/ProductTrainingContext/index.tsx index 2df640e44bf6..49b3c975c4d2 100644 --- a/src/components/ProductTrainingContext/index.tsx +++ b/src/components/ProductTrainingContext/index.tsx @@ -138,7 +138,7 @@ function ProductTrainingContextProvider({children}: ChildrenProps) { return content ? `${content}` : ''; }; return ( - + ); }, - [ - styles.alignItemsCenter, - styles.flexRow, - styles.flexWrap, - styles.gap1, - styles.justifyContentCenter, - styles.renderHTMLTitle, - styles.textAlignCenter, - theme.tooltipHighlightText, - translate, - ], + [styles, theme.tooltipHighlightText, translate], ); const contextValue = useMemo( From 9684fdafd43ce4f6bf268fe230f3316d9a3829b7 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Fri, 6 Dec 2024 02:25:15 +0530 Subject: [PATCH 043/131] refactor tooltip visibility logic by removing unnecessary conditions --- .../PRODUCT_TRAINING_TOOLTIP_DATA.ts | 47 +++---------------- .../ProductTrainingContext/index.tsx | 10 ++-- 2 files changed, 13 insertions(+), 44 deletions(-) diff --git a/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts b/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts index d2c8acb28e9e..6aecda294c8a 100644 --- a/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts +++ b/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts @@ -4,9 +4,6 @@ import CONST from '@src/CONST'; const {CONCEIRGE_LHN_GBR, RENAME_SAVED_SEARCH, WORKSAPCE_CHAT_CREATE, QUICK_ACTION_BUTTON} = CONST.PRODUCT_TRAINING_TOOLTIP_NAMES; type ShouldShowConditionProps = { - isDismissed: boolean; - isOnboardingCompleted: boolean; - hasBeenAddedToNudgeMigration: boolean; shouldUseNarrowLayout: boolean; }; @@ -16,16 +13,8 @@ const PRODUCT_TRAINING_TOOLTIP_DATA = { onHideTooltip: () => dismissProductTrainingElement(CONCEIRGE_LHN_GBR), name: CONCEIRGE_LHN_GBR, priority: 1300, - shouldShow: ({isDismissed, isOnboardingCompleted, hasBeenAddedToNudgeMigration, shouldUseNarrowLayout}: ShouldShowConditionProps) => { - if (isDismissed || !shouldUseNarrowLayout) { - return false; - } - - if (!isOnboardingCompleted && !hasBeenAddedToNudgeMigration) { - return false; - } - - return true; + shouldShow: ({shouldUseNarrowLayout}: ShouldShowConditionProps) => { + return shouldUseNarrowLayout; }, }, [RENAME_SAVED_SEARCH]: { @@ -33,16 +22,8 @@ const PRODUCT_TRAINING_TOOLTIP_DATA = { onHideTooltip: () => dismissProductTrainingElement(RENAME_SAVED_SEARCH), name: RENAME_SAVED_SEARCH, priority: 1250, - shouldShow: ({isDismissed, isOnboardingCompleted, hasBeenAddedToNudgeMigration, shouldUseNarrowLayout}: ShouldShowConditionProps) => { - if (isDismissed || shouldUseNarrowLayout) { - return false; - } - - if (!isOnboardingCompleted && !hasBeenAddedToNudgeMigration) { - return false; - } - - return true; + shouldShow: ({shouldUseNarrowLayout}: ShouldShowConditionProps) => { + return !shouldUseNarrowLayout; }, }, [QUICK_ACTION_BUTTON]: { @@ -50,15 +31,7 @@ const PRODUCT_TRAINING_TOOLTIP_DATA = { onHideTooltip: () => dismissProductTrainingElement(QUICK_ACTION_BUTTON), name: QUICK_ACTION_BUTTON, priority: 1200, - shouldShow: ({isDismissed, isOnboardingCompleted, hasBeenAddedToNudgeMigration}: ShouldShowConditionProps) => { - if (isDismissed) { - return false; - } - - if (!isOnboardingCompleted && !hasBeenAddedToNudgeMigration) { - return false; - } - + shouldShow: () => { return true; }, }, @@ -67,15 +40,7 @@ const PRODUCT_TRAINING_TOOLTIP_DATA = { onHideTooltip: () => dismissProductTrainingElement(WORKSAPCE_CHAT_CREATE), name: WORKSAPCE_CHAT_CREATE, priority: 1100, - shouldShow: ({isDismissed, isOnboardingCompleted, hasBeenAddedToNudgeMigration}: ShouldShowConditionProps) => { - if (isDismissed) { - return false; - } - - if (!isOnboardingCompleted && !hasBeenAddedToNudgeMigration) { - return false; - } - + shouldShow: () => { return true; }, }, diff --git a/src/components/ProductTrainingContext/index.tsx b/src/components/ProductTrainingContext/index.tsx index 49b3c975c4d2..6d254a17d6da 100644 --- a/src/components/ProductTrainingContext/index.tsx +++ b/src/components/ProductTrainingContext/index.tsx @@ -84,12 +84,16 @@ function ProductTrainingContextProvider({children}: ChildrenProps) { (tooltipName: ProductTrainingTooltipName) => { const isDismissed = !!dismissedProductTraining?.[tooltipName]; + if (isDismissed) { + return false; + } const tooltipConfig = PRODUCT_TRAINING_TOOLTIP_DATA[tooltipName]; + if (!isOnboardingCompleted && !hasBeenAddedToNudgeMigration) { + return false; + } + return tooltipConfig.shouldShow({ - isDismissed, - isOnboardingCompleted, - hasBeenAddedToNudgeMigration, shouldUseNarrowLayout, }); }, From 0451ea15958c8a5f46a1c758333c6d16604ee3b2 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Fri, 6 Dec 2024 02:26:02 +0530 Subject: [PATCH 044/131] depreaciate saved search ONYXKEY --- src/ONYXKEYS.ts | 4 ---- src/libs/actions/Search.ts | 10 ---------- src/pages/Search/AdvancedSearchFilters.tsx | 5 ----- 3 files changed, 19 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 2de7ff5f1a0a..b582f5ffad6e 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -222,9 +222,6 @@ const ONYXKEYS = { /** The NVP containing the target url to navigate to when deleting a transaction */ NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL: 'nvp_deleteTransactionNavigateBackURL', - /** Whether to show save search rename tooltip */ - SHOULD_SHOW_SAVED_SEARCH_RENAME_TOOLTIP: 'shouldShowSavedSearchRenameTooltip', - /** Whether to hide gbr tooltip */ NVP_SHOULD_HIDE_GBR_TOOLTIP: 'nvp_should_hide_gbr_tooltip', @@ -1027,7 +1024,6 @@ type OnyxValuesMapping = { [ONYXKEYS.LAST_ROUTE]: string; [ONYXKEYS.IS_SINGLE_NEW_DOT_ENTRY]: boolean | undefined; [ONYXKEYS.IS_USING_IMPORTED_STATE]: boolean; - [ONYXKEYS.SHOULD_SHOW_SAVED_SEARCH_RENAME_TOOLTIP]: boolean; [ONYXKEYS.NVP_EXPENSIFY_COMPANY_CARDS_CUSTOM_NAMES]: Record; [ONYXKEYS.CONCIERGE_REPORT_ID]: string; [ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING]: OnyxTypes.DismissedProductTraining; diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts index bb64fe10db26..ec4688cbc080 100644 --- a/src/libs/actions/Search.ts +++ b/src/libs/actions/Search.ts @@ -351,14 +351,6 @@ function clearAdvancedFilters() { Onyx.merge(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM, values); } -function showSavedSearchRenameTooltip() { - Onyx.set(ONYXKEYS.SHOULD_SHOW_SAVED_SEARCH_RENAME_TOOLTIP, true); -} - -function dismissSavedSearchRenameTooltip() { - Onyx.set(ONYXKEYS.SHOULD_SHOW_SAVED_SEARCH_RENAME_TOOLTIP, false); -} - export { saveSearch, search, @@ -371,8 +363,6 @@ export { clearAllFilters, clearAdvancedFilters, deleteSavedSearch, - dismissSavedSearchRenameTooltip, - showSavedSearchRenameTooltip, payMoneyRequestOnSearch, approveMoneyRequestOnSearch, handleActionButtonPress, diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx index 60c01e6f75f0..c4f58133be7d 100644 --- a/src/pages/Search/AdvancedSearchFilters.tsx +++ b/src/pages/Search/AdvancedSearchFilters.tsx @@ -339,11 +339,6 @@ function AdvancedSearchFilters() { return; } - // We only want to show the tooltip once, the NVP will not be set if the user has not saved a search yet - if (!savedSearches) { - SearchActions.showSavedSearchRenameTooltip(); - } - SearchActions.saveSearch({ queryJSON, }); From 5fe13b32b8d97997f45cc404698e73b9c3b0530c Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Fri, 6 Dec 2024 02:35:58 +0530 Subject: [PATCH 045/131] remove unused onyx keys --- src/ONYXKEYS.ts | 8 -------- src/libs/actions/User.ts | 10 ---------- src/types/onyx/WorkspaceTooltip.ts | 9 --------- src/types/onyx/index.ts | 2 -- 4 files changed, 29 deletions(-) delete mode 100644 src/types/onyx/WorkspaceTooltip.ts diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index b582f5ffad6e..1a8bbe057312 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -216,15 +216,9 @@ const ONYXKEYS = { /** The end date (epoch timestamp) of the workspace owner’s grace period after the free trial ends. */ NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END: 'nvp_private_billingGracePeriodEnd', - /** The NVP containing all information related to educational tooltip in workspace chat */ - NVP_WORKSPACE_TOOLTIP: 'workspaceTooltip', - /** The NVP containing the target url to navigate to when deleting a transaction */ NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL: 'nvp_deleteTransactionNavigateBackURL', - /** Whether to hide gbr tooltip */ - NVP_SHOULD_HIDE_GBR_TOOLTIP: 'nvp_should_hide_gbr_tooltip', - /** Does this user have push notifications enabled for this device? */ PUSH_NOTIFICATIONS_ENABLED: 'pushNotificationsEnabled', @@ -1014,9 +1008,7 @@ type OnyxValuesMapping = { [ONYXKEYS.NVP_BILLING_FUND_ID]: number; [ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED]: number; [ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END]: number; - [ONYXKEYS.NVP_WORKSPACE_TOOLTIP]: OnyxTypes.WorkspaceTooltip; [ONYXKEYS.NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL]: string | undefined; - [ONYXKEYS.NVP_SHOULD_HIDE_GBR_TOOLTIP]: boolean; [ONYXKEYS.NVP_PRIVATE_CANCELLATION_DETAILS]: OnyxTypes.CancellationDetails[]; [ONYXKEYS.ROOM_MEMBERS_USER_SEARCH_PHRASE]: string; [ONYXKEYS.APPROVAL_WORKFLOW]: OnyxTypes.ApprovalWorkflowOnyx; diff --git a/src/libs/actions/User.ts b/src/libs/actions/User.ts index 20bca969468a..8f97ef22bb31 100644 --- a/src/libs/actions/User.ts +++ b/src/libs/actions/User.ts @@ -1361,14 +1361,6 @@ function dismissTrackTrainingModal() { }); } -function dismissWorkspaceTooltip() { - Onyx.merge(ONYXKEYS.NVP_WORKSPACE_TOOLTIP, {shouldShow: false}); -} - -function dismissGBRTooltip() { - Onyx.merge(ONYXKEYS.NVP_SHOULD_HIDE_GBR_TOOLTIP, true); -} - function requestRefund() { API.write(WRITE_COMMANDS.REQUEST_REFUND, null); } @@ -1389,7 +1381,6 @@ export { closeAccount, dismissReferralBanner, dismissTrackTrainingModal, - dismissWorkspaceTooltip, resendValidateCode, requestContactMethodValidateCode, updateNewsletterSubscription, @@ -1423,6 +1414,5 @@ export { addPendingContactMethod, clearValidateCodeActionError, subscribeToActiveGuides, - dismissGBRTooltip, setIsDebugModeEnabled, }; diff --git a/src/types/onyx/WorkspaceTooltip.ts b/src/types/onyx/WorkspaceTooltip.ts deleted file mode 100644 index 4371ac6533d8..000000000000 --- a/src/types/onyx/WorkspaceTooltip.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * The NVP containing all information related to educational tooltip in workspace chat. - */ -type WorkspaceTooltip = { - /** Should show educational tooltip in workspace chat for first-time user */ - shouldShow: boolean; -}; - -export default WorkspaceTooltip; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 20b7d047a092..eeda322f6205 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -110,7 +110,6 @@ import type WalletOnfido from './WalletOnfido'; import type WalletStatement from './WalletStatement'; import type WalletTerms from './WalletTerms'; import type WalletTransfer from './WalletTransfer'; -import type WorkspaceTooltip from './WorkspaceTooltip'; export type { TryNewDot, @@ -234,7 +233,6 @@ export type { CancellationDetails, ApprovalWorkflowOnyx, MobileSelectionMode, - WorkspaceTooltip, CardFeeds, SaveSearch, RecentSearchItem, From f97eab2b2f09d319cdb14eccce7a0f2ff9c1ed20 Mon Sep 17 00:00:00 2001 From: I Nyoman Jyotisa Date: Fri, 6 Dec 2024 11:22:07 +0800 Subject: [PATCH 046/131] Fix: Copilot - Selection checkmark is displayed an access level option that is not verified yet --- .../AddDelegate/UpdateDelegateRole/UpdateDelegateRolePage.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pages/settings/Security/AddDelegate/UpdateDelegateRole/UpdateDelegateRolePage.tsx b/src/pages/settings/Security/AddDelegate/UpdateDelegateRole/UpdateDelegateRolePage.tsx index 13e0adfc17bf..b66964280a08 100644 --- a/src/pages/settings/Security/AddDelegate/UpdateDelegateRole/UpdateDelegateRolePage.tsx +++ b/src/pages/settings/Security/AddDelegate/UpdateDelegateRole/UpdateDelegateRolePage.tsx @@ -21,7 +21,7 @@ type UpdateDelegateRolePageProps = PlatformStackScreenProps ({ @@ -72,7 +72,6 @@ function UpdateDelegateRolePage({route}: UpdateDelegateRolePageProps) { } requestValidationCode(); - setCurrentRole(option.value); Navigation.navigate(ROUTES.SETTINGS_UPDATE_DELEGATE_ROLE_MAGIC_CODE.getRoute(login, option.value)); }} sections={[{data: roleOptions}]} From ae22a433ae8df649f76dc9ca540f2d5f7e8e90b1 Mon Sep 17 00:00:00 2001 From: I Nyoman Jyotisa Date: Fri, 6 Dec 2024 11:55:12 +0800 Subject: [PATCH 047/131] lint fix --- .../AddDelegate/UpdateDelegateRole/UpdateDelegateRolePage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Security/AddDelegate/UpdateDelegateRole/UpdateDelegateRolePage.tsx b/src/pages/settings/Security/AddDelegate/UpdateDelegateRole/UpdateDelegateRolePage.tsx index b66964280a08..2fb1eb1ed98b 100644 --- a/src/pages/settings/Security/AddDelegate/UpdateDelegateRole/UpdateDelegateRolePage.tsx +++ b/src/pages/settings/Security/AddDelegate/UpdateDelegateRole/UpdateDelegateRolePage.tsx @@ -1,4 +1,4 @@ -import React, {useEffect, useState} from 'react'; +import React, {useEffect} from 'react'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; From 3b39a328e37002f00258d6e8c03789d9a527b807 Mon Sep 17 00:00:00 2001 From: Kalydosos Date: Fri, 6 Dec 2024 10:03:23 +0100 Subject: [PATCH 048/131] format phone number and display names --- src/components/ArchivedReportFooter.tsx | 6 +++--- src/pages/ReportParticipantsPage.tsx | 2 +- src/pages/RoomMembersPage.tsx | 2 +- src/pages/workspace/WorkspaceMembersPage.tsx | 2 +- src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx | 7 ++++--- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/components/ArchivedReportFooter.tsx b/src/components/ArchivedReportFooter.tsx index 43c9b63fb505..f8276603c875 100644 --- a/src/components/ArchivedReportFooter.tsx +++ b/src/components/ArchivedReportFooter.tsx @@ -19,7 +19,7 @@ type ArchivedReportFooterProps = { function ArchivedReportFooter({report}: ArchivedReportFooterProps) { const styles = useThemeStyles(); - const {formatPhoneNumber, translate} = useLocalize(); + const {translate} = useLocalize(); const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {initialValue: {}}); const [reportClosedAction] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, {canEvict: false, selector: ReportActionsUtils.getLastClosedReportAction}); @@ -52,8 +52,8 @@ function ArchivedReportFooter({report}: ArchivedReportFooterProps) { const text = shouldRenderHTML ? translate(`reportArchiveReasons.${archiveReason}`, { - displayName: `${formatPhoneNumber(displayName)}`, - oldDisplayName: `${formatPhoneNumber(oldDisplayName ?? '')}`, + displayName: `${displayName}`, + oldDisplayName: `${oldDisplayName ?? ''}`, policyName: `${policyName}`, shouldUseYou: actorPersonalDetails?.accountID === getCurrentUserAccountID(), }) diff --git a/src/pages/ReportParticipantsPage.tsx b/src/pages/ReportParticipantsPage.tsx index 31931aa45544..4c63cc4b4492 100755 --- a/src/pages/ReportParticipantsPage.tsx +++ b/src/pages/ReportParticipantsPage.tsx @@ -391,7 +391,7 @@ function ReportParticipantsPage({report, route}: ReportParticipantsPageProps) { onCancel={() => setRemoveMembersConfirmModalVisible(false)} prompt={translate('workspace.people.removeMembersPrompt', { count: selectedMembers.length, - memberName: PersonalDetailsUtils.getPersonalDetailsByIDs(selectedMembers, currentUserAccountID).at(0)?.displayName ?? '', + memberName: formatPhoneNumber(PersonalDetailsUtils.getPersonalDetailsByIDs(selectedMembers, currentUserAccountID).at(0)?.displayName ?? ''), })} confirmText={translate('common.remove')} cancelText={translate('common.cancel')} diff --git a/src/pages/RoomMembersPage.tsx b/src/pages/RoomMembersPage.tsx index 71f3117be927..f1aafee230a4 100644 --- a/src/pages/RoomMembersPage.tsx +++ b/src/pages/RoomMembersPage.tsx @@ -373,7 +373,7 @@ function RoomMembersPage({report, policies}: RoomMembersPageProps) { onCancel={() => setRemoveMembersConfirmModalVisible(false)} prompt={translate('roomMembersPage.removeMembersPrompt', { count: selectedMembers.length, - memberName: PersonalDetailsUtils.getPersonalDetailsByIDs(selectedMembers, currentUserAccountID).at(0)?.displayName ?? '', + memberName: formatPhoneNumber(PersonalDetailsUtils.getPersonalDetailsByIDs(selectedMembers, currentUserAccountID).at(0)?.displayName ?? ''), })} confirmText={translate('common.remove')} cancelText={translate('common.cancel')} diff --git a/src/pages/workspace/WorkspaceMembersPage.tsx b/src/pages/workspace/WorkspaceMembersPage.tsx index 18961a979c93..7e1b60c60948 100644 --- a/src/pages/workspace/WorkspaceMembersPage.tsx +++ b/src/pages/workspace/WorkspaceMembersPage.tsx @@ -110,7 +110,7 @@ function WorkspaceMembersPage({personalDetails, route, policy, currentUserPerson if (!approverAccountID) { return translate('workspace.people.removeMembersPrompt', { count: selectedEmployees.length, - memberName: PersonalDetailsUtils.getPersonalDetailsByIDs(selectedEmployees, currentUserAccountID).at(0)?.displayName ?? '', + memberName: formatPhoneNumber(PersonalDetailsUtils.getPersonalDetailsByIDs(selectedEmployees, currentUserAccountID).at(0)?.displayName ?? ''), }); } return translate('workspace.people.removeMembersWarningPrompt', { diff --git a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx index 6d667d786d5d..85a5d2372ee9 100644 --- a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx +++ b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx @@ -23,6 +23,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import * as CardUtils from '@libs/CardUtils'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; +import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; import shouldRenderTransferOwnerButton from '@libs/shouldRenderTransferOwnerButton'; import Navigation from '@navigation/Navigation'; @@ -58,7 +59,7 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM const styles = useThemeStyles(); const {isOffline} = useNetwork(); - const {translate} = useLocalize(); + const {formatPhoneNumber, translate} = useLocalize(); const StyleUtils = useStyleUtils(); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const [cards] = useOnyx(`${ONYXKEYS.CARD_LIST}`); @@ -75,13 +76,13 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM const prevMember = usePrevious(member); const details = personalDetails?.[accountID] ?? ({} as PersonalDetails); const fallbackIcon = details.fallbackIcon ?? ''; - const displayName = details.displayName ?? ''; + const displayName = formatPhoneNumber(PersonalDetailsUtils.getDisplayNameOrDefault(details)); const isSelectedMemberOwner = policy?.owner === details.login; const isSelectedMemberCurrentUser = accountID === currentUserPersonalDetails?.accountID; const isCurrentUserAdmin = policy?.employeeList?.[personalDetails?.[currentUserPersonalDetails?.accountID]?.login ?? '']?.role === CONST.POLICY.ROLE.ADMIN; const isCurrentUserOwner = policy?.owner === currentUserPersonalDetails?.login; const ownerDetails = personalDetails?.[policy?.ownerAccountID ?? -1] ?? ({} as PersonalDetails); - const policyOwnerDisplayName = ownerDetails.displayName ?? policy?.owner ?? ''; + const policyOwnerDisplayName = formatPhoneNumber(PersonalDetailsUtils.getDisplayNameOrDefault(ownerDetails)) ?? policy?.owner ?? ''; const hasMultipleFeeds = Object.values(CardUtils.getCompanyFeeds(cardFeeds)).filter((feed) => !feed.pending).length > 0; const paymentAccountID = cardSettings?.paymentBankAccountID ?? 0; From 6d9369080886a7a217e1ceffcb271b28e193275f Mon Sep 17 00:00:00 2001 From: Kalydosos Date: Fri, 6 Dec 2024 10:13:08 +0100 Subject: [PATCH 049/131] format phone number and display names --- src/pages/workspace/WorkspaceMembersPage.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/workspace/WorkspaceMembersPage.tsx b/src/pages/workspace/WorkspaceMembersPage.tsx index 7e1b60c60948..7e16ba18e2cc 100644 --- a/src/pages/workspace/WorkspaceMembersPage.tsx +++ b/src/pages/workspace/WorkspaceMembersPage.tsx @@ -30,6 +30,7 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as FormActions from '@libs/actions/FormActions'; +import * as LocalePhoneNumber from '@libs/LocalePhoneNumber'; import {turnOffMobileSelectionMode} from '@libs/actions/MobileSelectionMode'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import Log from '@libs/Log'; @@ -110,7 +111,7 @@ function WorkspaceMembersPage({personalDetails, route, policy, currentUserPerson if (!approverAccountID) { return translate('workspace.people.removeMembersPrompt', { count: selectedEmployees.length, - memberName: formatPhoneNumber(PersonalDetailsUtils.getPersonalDetailsByIDs(selectedEmployees, currentUserAccountID).at(0)?.displayName ?? ''), + memberName: LocalePhoneNumber.formatPhoneNumber(PersonalDetailsUtils.getPersonalDetailsByIDs(selectedEmployees, currentUserAccountID).at(0)?.displayName ?? ''), }); } return translate('workspace.people.removeMembersWarningPrompt', { From e79c9070ea224863a3fc5b7595fb1f826b6509b0 Mon Sep 17 00:00:00 2001 From: Kalydosos Date: Fri, 6 Dec 2024 10:22:07 +0100 Subject: [PATCH 050/131] format phone number and display names --- src/pages/workspace/WorkspaceMembersPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/WorkspaceMembersPage.tsx b/src/pages/workspace/WorkspaceMembersPage.tsx index 7e16ba18e2cc..cb702cfd48df 100644 --- a/src/pages/workspace/WorkspaceMembersPage.tsx +++ b/src/pages/workspace/WorkspaceMembersPage.tsx @@ -30,9 +30,9 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as FormActions from '@libs/actions/FormActions'; -import * as LocalePhoneNumber from '@libs/LocalePhoneNumber'; import {turnOffMobileSelectionMode} from '@libs/actions/MobileSelectionMode'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; +import * as LocalePhoneNumber from '@libs/LocalePhoneNumber'; import Log from '@libs/Log'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; From 3e216f83cecf0f104a30baf04ff5f5dbf2c8a8fe Mon Sep 17 00:00:00 2001 From: Kalydosos Date: Fri, 6 Dec 2024 16:30:02 +0100 Subject: [PATCH 051/131] use of formatphonenumber --- src/components/ArchivedReportFooter.tsx | 2 +- src/libs/ReportUtils.ts | 2 +- tests/unit/OptionsListUtilsTest.ts | 76 ++++++++++++------------- tests/unit/ReportUtilsTest.ts | 20 +++---- 4 files changed, 50 insertions(+), 50 deletions(-) diff --git a/src/components/ArchivedReportFooter.tsx b/src/components/ArchivedReportFooter.tsx index f8276603c875..fc5c77958635 100644 --- a/src/components/ArchivedReportFooter.tsx +++ b/src/components/ArchivedReportFooter.tsx @@ -53,7 +53,7 @@ function ArchivedReportFooter({report}: ArchivedReportFooterProps) { const text = shouldRenderHTML ? translate(`reportArchiveReasons.${archiveReason}`, { displayName: `${displayName}`, - oldDisplayName: `${oldDisplayName ?? ''}`, + oldDisplayName: `${oldDisplayName}`, policyName: `${policyName}`, shouldUseYou: actorPersonalDetails?.accountID === getCurrentUserAccountID(), }) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index fc2a48aa323b..93e8f362acbd 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2165,7 +2165,7 @@ function getDisplayNameForParticipant( } const shortName = personalDetails.firstName ? personalDetails.firstName : longName; - return LocalePhoneNumber.formatPhoneNumber(shouldUseShortForm ? shortName : longName); + return shouldUseShortForm ? shortName : longName; } function getParticipantsAccountIDsForDisplay(report: OnyxEntry, shouldExcludeHidden = false, shouldExcludeDeleted = false, shouldForceExcludeCurrentUser = false): number[] { diff --git a/tests/unit/OptionsListUtilsTest.ts b/tests/unit/OptionsListUtilsTest.ts index 43798f6c40cf..6018d2448812 100644 --- a/tests/unit/OptionsListUtilsTest.ts +++ b/tests/unit/OptionsListUtilsTest.ts @@ -427,14 +427,14 @@ describe('OptionsListUtils', () => { // All personal details including those that have reports should be returned // We should expect personal details sorted alphabetically - expect(results.personalDetails.at(0)?.text).toBe('Black Panther'.replace(/ /g, '\u00A0')); - expect(results.personalDetails.at(1)?.text).toBe('Black Widow'.replace(/ /g, '\u00A0')); - expect(results.personalDetails.at(2)?.text).toBe('Captain America'.replace(/ /g, '\u00A0')); - expect(results.personalDetails.at(3)?.text).toBe('Invisible Woman'.replace(/ /g, '\u00A0')); - expect(results.personalDetails.at(4)?.text).toBe('Mister Fantastic'.replace(/ /g, '\u00A0')); - expect(results.personalDetails.at(5)?.text).toBe('Mr Sinister'.replace(/ /g, '\u00A0')); - expect(results.personalDetails.at(6)?.text).toBe('Spider-Man'.replace(/ /g, '\u00A0')); - expect(results.personalDetails.at(7)?.text).toBe('The Incredible Hulk'.replace(/ /g, '\u00A0')); + expect(results.personalDetails.at(0)?.text).toBe('Black Panther'); + expect(results.personalDetails.at(1)?.text).toBe('Black Widow'); + expect(results.personalDetails.at(2)?.text).toBe('Captain America'); + expect(results.personalDetails.at(3)?.text).toBe('Invisible Woman'); + expect(results.personalDetails.at(4)?.text).toBe('Mister Fantastic'); + expect(results.personalDetails.at(5)?.text).toBe('Mr Sinister'); + expect(results.personalDetails.at(6)?.text).toBe('Spider-Man'); + expect(results.personalDetails.at(7)?.text).toBe('The Incredible Hulk'); expect(results.personalDetails.at(8)?.text).toBe('Thor'); // Then the result which has an existing report should also have the reportID attached @@ -445,10 +445,10 @@ describe('OptionsListUtils', () => { results = OptionsListUtils.getOptions({personalDetails: OPTIONS.personalDetails, reports: []}); // We should expect personal details sorted alphabetically - expect(results.personalDetails.at(0)?.text).toBe('Black Panther'.replace(/ /g, '\u00A0')); - expect(results.personalDetails.at(1)?.text).toBe('Black Widow'.replace(/ /g, '\u00A0')); - expect(results.personalDetails.at(2)?.text).toBe('Captain America'.replace(/ /g, '\u00A0')); - expect(results.personalDetails.at(3)?.text).toBe('Invisible Woman'.replace(/ /g, '\u00A0')); + expect(results.personalDetails.at(0)?.text).toBe('Black Panther'); + expect(results.personalDetails.at(1)?.text).toBe('Black Widow'); + expect(results.personalDetails.at(2)?.text).toBe('Captain America'); + expect(results.personalDetails.at(3)?.text).toBe('Invisible Woman'); // When we don't include personal detail to the result results = OptionsListUtils.getOptions( @@ -525,14 +525,14 @@ describe('OptionsListUtils', () => { // All personal details including those that have reports should be returned // We should expect personal details sorted alphabetically - expect(results.personalDetails.at(0)?.text).toBe('Black Panther'.replace(/ /g, '\u00A0')); - expect(results.personalDetails.at(1)?.text).toBe('Black Widow'.replace(/ /g, '\u00A0')); - expect(results.personalDetails.at(2)?.text).toBe('Captain America'.replace(/ /g, '\u00A0')); - expect(results.personalDetails.at(3)?.text).toBe('Invisible Woman'.replace(/ /g, '\u00A0')); - expect(results.personalDetails.at(4)?.text).toBe('Mister Fantastic'.replace(/ /g, '\u00A0')); - expect(results.personalDetails.at(5)?.text).toBe('Mr Sinister'.replace(/ /g, '\u00A0')); - expect(results.personalDetails.at(6)?.text).toBe('Spider-Man'.replace(/ /g, '\u00A0')); - expect(results.personalDetails.at(7)?.text).toBe('The Incredible Hulk'.replace(/ /g, '\u00A0')); + expect(results.personalDetails.at(0)?.text).toBe('Black Panther'); + expect(results.personalDetails.at(1)?.text).toBe('Black Widow'); + expect(results.personalDetails.at(2)?.text).toBe('Captain America'); + expect(results.personalDetails.at(3)?.text).toBe('Invisible Woman'); + expect(results.personalDetails.at(4)?.text).toBe('Mister Fantastic'); + expect(results.personalDetails.at(5)?.text).toBe('Mr Sinister'); + expect(results.personalDetails.at(6)?.text).toBe('Spider-Man'); + expect(results.personalDetails.at(7)?.text).toBe('The Incredible Hulk'); expect(results.personalDetails.at(8)?.text).toBe('Thor'); // And none of our personalDetails should include any of the users with recent reports @@ -649,10 +649,10 @@ describe('OptionsListUtils', () => { const results = OptionsListUtils.getMemberInviteOptions(OPTIONS.personalDetails, []); // We should expect personal details to be sorted alphabetically - expect(results.personalDetails.at(0)?.text).toBe('Black Panther'.replace(/ /g, '\u00A0')); - expect(results.personalDetails.at(1)?.text).toBe('Black Widow'.replace(/ /g, '\u00A0')); - expect(results.personalDetails.at(2)?.text).toBe('Captain America'.replace(/ /g, '\u00A0')); - expect(results.personalDetails.at(3)?.text).toBe('Invisible Woman'.replace(/ /g, '\u00A0')); + expect(results.personalDetails.at(0)?.text).toBe('Black Panther'); + expect(results.personalDetails.at(1)?.text).toBe('Black Widow'); + expect(results.personalDetails.at(2)?.text).toBe('Captain America'); + expect(results.personalDetails.at(3)?.text).toBe('Invisible Woman'); }); it('formatMemberForList()', () => { @@ -687,10 +687,10 @@ describe('OptionsListUtils', () => { const filteredOptions = OptionsListUtils.filterOptions(options, searchText, {sortByReportTypeInSearch: true}); expect(filteredOptions.recentReports.length).toBe(4); - expect(filteredOptions.recentReports.at(0)?.text).toBe('Invisible Woman'.replace(/ /g, '\u00A0')); - expect(filteredOptions.recentReports.at(1)?.text).toBe('Spider-Man'.replace(/ /g, '\u00A0')); - expect(filteredOptions.recentReports.at(2)?.text).toBe('Black Widow'.replace(/ /g, '\u00A0')); - expect(filteredOptions.recentReports.at(3)?.text).toBe(`${'Mister Fantastic'.replace(/ /g, '\u00A0')}, ${'Invisible Woman'.replace(/ /g, '\u00A0')}`); + expect(filteredOptions.recentReports.at(0)?.text).toBe('Invisible Woman'); + expect(filteredOptions.recentReports.at(1)?.text).toBe('Spider-Man'); + expect(filteredOptions.recentReports.at(2)?.text).toBe('Black Widow'); + expect(filteredOptions.recentReports.at(3)?.text).toBe('Mister Fantastic, Invisible Woman'); }); it('should filter users by email', () => { @@ -700,7 +700,7 @@ describe('OptionsListUtils', () => { const filteredOptions = OptionsListUtils.filterOptions(options, searchText); expect(filteredOptions.recentReports.length).toBe(1); - expect(filteredOptions.recentReports.at(0)?.text).toBe('Mr Sinister'.replace(/ /g, '\u00A0')); + expect(filteredOptions.recentReports.at(0)?.text).toBe('Mr Sinister'); }); it('should find archived chats', () => { @@ -761,8 +761,8 @@ describe('OptionsListUtils', () => { const filteredOptions = OptionsListUtils.filterOptions(options, searchText); expect(filteredOptions.recentReports.length).toBe(2); - expect(filteredOptions.recentReports.at(0)?.text).toBe('Mister Fantastic'.replace(/ /g, '\u00A0')); - expect(filteredOptions.recentReports.at(1)?.text).toBe(`${'Mister Fantastic'.replace(/ /g, '\u00A0')}, ${'Invisible Woman'.replace(/ /g, '\u00A0')}`); + expect(filteredOptions.recentReports.at(0)?.text).toBe('Mister Fantastic'); + expect(filteredOptions.recentReports.at(1)?.text).toBe(`${'Mister Fantastic'}, ${'Invisible Woman'}`); }); it('should return the user to invite when the search value is a valid, non-existent email', () => { @@ -885,7 +885,7 @@ describe('OptionsListUtils', () => { const filteredOptions = OptionsListUtils.filterOptions(options, '.com'); expect(filteredOptions.recentReports.length).toBe(5); - expect(filteredOptions.recentReports.at(0)?.text).toBe('Captain America'.replace(/ /g, '\u00A0')); + expect(filteredOptions.recentReports.at(0)?.text).toBe('Captain America'); // We expect that only personal details that are not in the reports are included here expect(filteredOptions.personalDetails.length).toBe(4); @@ -979,9 +979,9 @@ describe('OptionsListUtils', () => { expect(filteredOptions.personalDetails.length).toBe(4); expect(filteredOptions.recentReports.length).toBe(5); expect(filteredOptions.personalDetails.at(0)?.login).toBe('natasharomanoff@expensify.com'); - expect(filteredOptions.recentReports.at(0)?.text).toBe('Captain America'.replace(/ /g, '\u00A0')); - expect(filteredOptions.recentReports.at(1)?.text).toBe('Mr Sinister'.replace(/ /g, '\u00A0')); - expect(filteredOptions.recentReports.at(2)?.text).toBe('Black Panther'.replace(/ /g, '\u00A0')); + expect(filteredOptions.recentReports.at(0)?.text).toBe('Captain America'); + expect(filteredOptions.recentReports.at(1)?.text).toBe('Mr Sinister'); + expect(filteredOptions.recentReports.at(2)?.text).toBe('Black Panther'); }); it('should return matching option when searching (getSearchOptions)', () => { @@ -997,8 +997,8 @@ describe('OptionsListUtils', () => { const filteredOptions = OptionsListUtils.filterOptions(options, 'fantastic'); expect(filteredOptions.recentReports.length).toBe(2); - expect(filteredOptions.recentReports.at(0)?.text).toBe('Mister Fantastic'.replace(/ /g, '\u00A0')); - expect(filteredOptions.recentReports.at(1)?.text).toBe(`${'Mister Fantastic'.replace(/ /g, '\u00A0')}, ${'Invisible Woman'.replace(/ /g, '\u00A0')}`); + expect(filteredOptions.recentReports.at(0)?.text).toBe('Mister Fantastic'); + expect(filteredOptions.recentReports.at(1)?.text).toBe('Mister Fantastic, Invisible Woman'); return waitForBatchedUpdates() .then(() => Onyx.set(ONYXKEYS.PERSONAL_DETAILS_LIST, PERSONAL_DETAILS_WITH_PERIODS)) @@ -1008,7 +1008,7 @@ describe('OptionsListUtils', () => { const filteredResults = OptionsListUtils.filterOptions(results, 'barry.allen@expensify.com', {sortByReportTypeInSearch: true}); expect(filteredResults.recentReports.length).toBe(1); - expect(filteredResults.recentReports.at(0)?.text).toBe('The Flash'.replace(/ /g, '\u00A0')); + expect(filteredResults.recentReports.at(0)?.text).toBe('The Flash'); }); }); }); diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts index 4eb268df2138..49532e84b8ba 100644 --- a/tests/unit/ReportUtilsTest.ts +++ b/tests/unit/ReportUtilsTest.ts @@ -98,15 +98,15 @@ describe('ReportUtils', () => { const participants = ReportUtils.getDisplayNamesWithTooltips(participantsPersonalDetails, false); expect(participants).toHaveLength(5); - expect(participants.at(0)?.displayName).toBe('(833) 240-3627'.replace(/ /g, '\u00A0')); + expect(participants.at(0)?.displayName).toBe('(833) 240-3627'); expect(participants.at(0)?.login).toBe('+18332403627@expensify.sms'); - expect(participants.at(2)?.displayName).toBe('Lagertha Lothbrok'.replace(/ /g, '\u00A0')); + expect(participants.at(2)?.displayName).toBe('Lagertha Lothbrok'); expect(participants.at(2)?.login).toBe('lagertha@vikings.net'); expect(participants.at(2)?.accountID).toBe(3); expect(participants.at(2)?.pronouns).toBe('She/her'); - expect(participants.at(4)?.displayName).toBe('Ragnar Lothbrok'.replace(/ /g, '\u00A0')); + expect(participants.at(4)?.displayName).toBe('Ragnar Lothbrok'); expect(participants.at(4)?.login).toBe('ragnar@vikings.net'); expect(participants.at(4)?.accountID).toBe(1); expect(participants.at(4)?.pronouns).toBeUndefined(); @@ -121,7 +121,7 @@ describe('ReportUtils', () => { reportID: '', participants: ReportUtils.buildParticipantsFromAccountIDs([currentUserAccountID, 1]), }), - ).toBe('Ragnar Lothbrok'.replace(/ /g, '\u00A0')); + ).toBe('Ragnar Lothbrok'); }); test('no displayName', () => { @@ -139,7 +139,7 @@ describe('ReportUtils', () => { reportID: '', participants: ReportUtils.buildParticipantsFromAccountIDs([currentUserAccountID, 4]), }), - ).toBe('(833) 240-3627'.replace(/ /g, '\u00A0')); + ).toBe('(833) 240-3627'); }); }); @@ -149,7 +149,7 @@ describe('ReportUtils', () => { reportID: '', participants: ReportUtils.buildParticipantsFromAccountIDs([currentUserAccountID, 1, 2, 3, 4]), }), - ).toBe(`Ragnar, floki@vikings.net, Lagertha, ${'(833) 240-3627'.replace(/ /g, '\u00A0')}`); + ).toBe(`Ragnar, floki@vikings.net, Lagertha, ${'(833) 240-3627'}`); }); describe('Default Policy Room', () => { @@ -227,7 +227,7 @@ describe('ReportUtils', () => { isOwnPolicyExpenseChat: false, ownerAccountID: 1, }), - ).toBe('Ragnar Lothbrok'.replace(/ /g, '\u00A0')); + ).toBe('Ragnar Lothbrok'); }); }); @@ -263,10 +263,10 @@ describe('ReportUtils', () => { isOwnPolicyExpenseChat: false, }; - expect(ReportUtils.getReportName(adminArchivedPolicyExpenseChat)).toBe(`${'Ragnar Lothbrok'.replace(/ /g, '\u00A0')} (archived)`); + expect(ReportUtils.getReportName(adminArchivedPolicyExpenseChat)).toBe(`${'Ragnar Lothbrok'} (archived)`); return Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.ES).then(() => - expect(ReportUtils.getReportName(adminArchivedPolicyExpenseChat)).toBe(`${'Ragnar Lothbrok'.replace(/ /g, '\u00A0')} (archivado)`), + expect(ReportUtils.getReportName(adminArchivedPolicyExpenseChat)).toBe(`${'Ragnar Lothbrok'} (archivado)`), ); }); }); @@ -1114,7 +1114,7 @@ describe('ReportUtils', () => { it('Should use correct display name for participants', async () => { await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, participantsPersonalDetails); - expect(ReportUtils.getGroupChatName(fourParticipants, true)).toEqual(`${'(833) 240-3627'.replace(/ /g, '\u00A0')}, floki@vikings.net, Lagertha, Ragnar`); + expect(ReportUtils.getGroupChatName(fourParticipants, true)).toEqual(`${'(833) 240-3627'}, floki@vikings.net, Lagertha, Ragnar`); }); }); From de3750f2dc01487e7035cc4e27b3b605bbc14f6d Mon Sep 17 00:00:00 2001 From: Kalydosos Date: Fri, 6 Dec 2024 16:47:42 +0100 Subject: [PATCH 052/131] use of formatphonenumber --- tests/unit/OptionsListUtilsTest.ts | 2 +- tests/unit/ReportUtilsTest.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/unit/OptionsListUtilsTest.ts b/tests/unit/OptionsListUtilsTest.ts index 6018d2448812..747ae2cf1cda 100644 --- a/tests/unit/OptionsListUtilsTest.ts +++ b/tests/unit/OptionsListUtilsTest.ts @@ -762,7 +762,7 @@ describe('OptionsListUtils', () => { expect(filteredOptions.recentReports.length).toBe(2); expect(filteredOptions.recentReports.at(0)?.text).toBe('Mister Fantastic'); - expect(filteredOptions.recentReports.at(1)?.text).toBe(`${'Mister Fantastic'}, ${'Invisible Woman'}`); + expect(filteredOptions.recentReports.at(1)?.text).toBe('Mister Fantastic, Invisible Woman'); }); it('should return the user to invite when the search value is a valid, non-existent email', () => { diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts index 49532e84b8ba..e0eb8dcbc7a8 100644 --- a/tests/unit/ReportUtilsTest.ts +++ b/tests/unit/ReportUtilsTest.ts @@ -263,10 +263,10 @@ describe('ReportUtils', () => { isOwnPolicyExpenseChat: false, }; - expect(ReportUtils.getReportName(adminArchivedPolicyExpenseChat)).toBe(`${'Ragnar Lothbrok'} (archived)`); + expect(ReportUtils.getReportName(adminArchivedPolicyExpenseChat)).toBe('Ragnar Lothbrok (archived)'); return Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.ES).then(() => - expect(ReportUtils.getReportName(adminArchivedPolicyExpenseChat)).toBe(`${'Ragnar Lothbrok'} (archivado)`), + expect(ReportUtils.getReportName(adminArchivedPolicyExpenseChat)).toBe('Ragnar Lothbrok (archivado)'), ); }); }); @@ -1114,7 +1114,7 @@ describe('ReportUtils', () => { it('Should use correct display name for participants', async () => { await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, participantsPersonalDetails); - expect(ReportUtils.getGroupChatName(fourParticipants, true)).toEqual(`${'(833) 240-3627'}, floki@vikings.net, Lagertha, Ragnar`); + expect(ReportUtils.getGroupChatName(fourParticipants, true)).toEqual('(833) 240-3627, floki@vikings.net, Lagertha, Ragnar'); }); }); From e36f20c20c2a3ea469d94702986be2f8c8c42421 Mon Sep 17 00:00:00 2001 From: Kalydosos Date: Fri, 6 Dec 2024 16:56:24 +0100 Subject: [PATCH 053/131] use of formatphonenumber --- tests/unit/OptionsListUtilsTest.ts | 1 - tests/unit/ReportUtilsTest.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/unit/OptionsListUtilsTest.ts b/tests/unit/OptionsListUtilsTest.ts index 747ae2cf1cda..cad6c010bb67 100644 --- a/tests/unit/OptionsListUtilsTest.ts +++ b/tests/unit/OptionsListUtilsTest.ts @@ -884,7 +884,6 @@ describe('OptionsListUtils', () => { const options = OptionsListUtils.getOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {maxRecentReportsToShow: 5}); const filteredOptions = OptionsListUtils.filterOptions(options, '.com'); - expect(filteredOptions.recentReports.length).toBe(5); expect(filteredOptions.recentReports.at(0)?.text).toBe('Captain America'); // We expect that only personal details that are not in the reports are included here diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts index e0eb8dcbc7a8..dc752ae73b1c 100644 --- a/tests/unit/ReportUtilsTest.ts +++ b/tests/unit/ReportUtilsTest.ts @@ -149,7 +149,7 @@ describe('ReportUtils', () => { reportID: '', participants: ReportUtils.buildParticipantsFromAccountIDs([currentUserAccountID, 1, 2, 3, 4]), }), - ).toBe(`Ragnar, floki@vikings.net, Lagertha, ${'(833) 240-3627'}`); + ).toBe('Ragnar, floki@vikings.net, Lagertha, (833) 240-3627'); }); describe('Default Policy Room', () => { From b05d47b541d3627451378dfc33de94da54fc88c0 Mon Sep 17 00:00:00 2001 From: Kalydosos Date: Sat, 7 Dec 2024 01:01:00 +0100 Subject: [PATCH 054/131] resolve conflict on profileavatar --- src/pages/settings/Profile/ProfileAvatar.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pages/settings/Profile/ProfileAvatar.tsx b/src/pages/settings/Profile/ProfileAvatar.tsx index 91976214a718..08d953311876 100644 --- a/src/pages/settings/Profile/ProfileAvatar.tsx +++ b/src/pages/settings/Profile/ProfileAvatar.tsx @@ -19,7 +19,7 @@ function ProfileAvatar({route}: ProfileAvatarProps) { const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); const [personalDetailsMetadata] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_METADATA); const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP, {initialValue: true}); - + const personalDetail = personalDetails?.[route.params.accountID]; const avatarURL = personalDetail?.avatar ?? ''; const accountID = Number(route.params.accountID ?? '-1'); @@ -37,7 +37,6 @@ function ProfileAvatar({route}: ProfileAvatarProps) { { setTimeout(() => { From fa48b459840a759529ab48cf1287fda46cbb927f Mon Sep 17 00:00:00 2001 From: Kalydosos Date: Sat, 7 Dec 2024 01:05:39 +0100 Subject: [PATCH 055/131] resolve conflict on profileavatar --- src/pages/settings/Profile/ProfileAvatar.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/settings/Profile/ProfileAvatar.tsx b/src/pages/settings/Profile/ProfileAvatar.tsx index 08d953311876..8c66f592d0d0 100644 --- a/src/pages/settings/Profile/ProfileAvatar.tsx +++ b/src/pages/settings/Profile/ProfileAvatar.tsx @@ -19,7 +19,7 @@ function ProfileAvatar({route}: ProfileAvatarProps) { const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); const [personalDetailsMetadata] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_METADATA); const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP, {initialValue: true}); - + const personalDetail = personalDetails?.[route.params.accountID]; const avatarURL = personalDetail?.avatar ?? ''; const accountID = Number(route.params.accountID ?? '-1'); @@ -35,8 +35,8 @@ function ProfileAvatar({route}: ProfileAvatarProps) { return ( { setTimeout(() => { From 21da67d809020071722423ff4a93056e6382f150 Mon Sep 17 00:00:00 2001 From: Kalydosos Date: Sat, 7 Dec 2024 01:10:18 +0100 Subject: [PATCH 056/131] resolve conflict on profileavatar --- src/pages/settings/Profile/ProfileAvatar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Profile/ProfileAvatar.tsx b/src/pages/settings/Profile/ProfileAvatar.tsx index 8c66f592d0d0..661b44a6a653 100644 --- a/src/pages/settings/Profile/ProfileAvatar.tsx +++ b/src/pages/settings/Profile/ProfileAvatar.tsx @@ -19,7 +19,7 @@ function ProfileAvatar({route}: ProfileAvatarProps) { const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); const [personalDetailsMetadata] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_METADATA); const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP, {initialValue: true}); - + const personalDetail = personalDetails?.[route.params.accountID]; const avatarURL = personalDetail?.avatar ?? ''; const accountID = Number(route.params.accountID ?? '-1'); From 827b107383b2988b37d3694ac3c9cf1c91ad5747 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sun, 8 Dec 2024 07:22:32 +0530 Subject: [PATCH 057/131] minor update. Signed-off-by: krishna2323 --- src/components/ReportActionItem/ReportPreview.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index b73eccc96b83..4fc04e996523 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -155,10 +155,10 @@ function ReportPreview({ const previewMessageStyle = useAnimatedStyle(() => ({ opacity: previewMessageOpacity.get(), })); - const checkMarkScale = useSharedValue(iouSettled ? 1 : 0.25); + const checkMarkScale = useSharedValue(iouSettled ? 1 : 0); const isApproved = ReportUtils.isReportApproved(iouReport, action); - const thumbsUpScale = useSharedValue(isApproved ? 1 : 0); + const thumbsUpScale = useSharedValue(isApproved ? 1 : 0.25); const thumbsUpStyle = useAnimatedStyle(() => ({ ...styles.defaultCheckmarkWrapper, transform: [{scale: thumbsUpScale.get()}], From b9bcefd70e002d37f19d40684bd5da84b9ef70bc Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sun, 8 Dec 2024 07:36:42 +0530 Subject: [PATCH 058/131] update thumbsUpScale value. Signed-off-by: krishna2323 --- src/components/ReportActionItem/ReportPreview.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 83fce43395b4..1eb72090f97f 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -158,7 +158,7 @@ function ReportPreview({ const checkMarkScale = useSharedValue(iouSettled ? 1 : 0); const isApproved = ReportUtils.isReportApproved(iouReport, action); - const thumbsUpScale = useSharedValue(isApproved ? 1 : 0.25); + const thumbsUpScale = useSharedValue(isApproved ? 1 : 0); const thumbsUpStyle = useAnimatedStyle(() => ({ ...styles.defaultCheckmarkWrapper, transform: [{scale: thumbsUpScale.get()}], From 2eff2120434b95c402fa6cade54e74a982620916 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sun, 8 Dec 2024 07:41:36 +0530 Subject: [PATCH 059/131] update animation time of thumbs up icon. Signed-off-by: krishna2323 --- src/CONST.ts | 1 + src/components/ReportActionItem/ReportPreview.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/CONST.ts b/src/CONST.ts index ff77a7380a6b..8fcb9d0c4367 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -340,6 +340,7 @@ const CONST = { // Multiplier for gyroscope animation in order to make it a bit more subtle ANIMATION_GYROSCOPE_VALUE: 0.4, ANIMATION_PAID_DURATION: 200, + ANIMATION_THUMBSUP_DELAY: 250, ANIMATION_PAID_CHECKMARK_DELAY: 300, ANIMATION_PAID_BUTTON_HIDE_DELAY: 1000, BACKGROUND_IMAGE_TRANSITION_DURATION: 1000, diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 1eb72090f97f..f294281fb785 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -472,7 +472,7 @@ function ReportPreview({ return; } - thumbsUpScale.set(withSpring(1, {duration: CONST.ANIMATION_PAID_DURATION})); + thumbsUpScale.set(withSpring(1, {duration: CONST.ANIMATION_THUMBSUP_DELAY})); }, [isApproved, isApprovedAnimationRunning, thumbsUpScale]); const openReportFromPreview = useCallback(() => { From 24818db65ca1be28b2e55dfef6a53004101cb54c Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sun, 8 Dec 2024 08:14:11 +0530 Subject: [PATCH 060/131] add delay to thumbs-up icon. Signed-off-by: krishna2323 --- src/CONST.ts | 3 ++- src/components/ReportActionItem/ReportPreview.tsx | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 8fcb9d0c4367..f4cdf33128a4 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -340,8 +340,9 @@ const CONST = { // Multiplier for gyroscope animation in order to make it a bit more subtle ANIMATION_GYROSCOPE_VALUE: 0.4, ANIMATION_PAID_DURATION: 200, - ANIMATION_THUMBSUP_DELAY: 250, ANIMATION_PAID_CHECKMARK_DELAY: 300, + ANIMATION_THUMBSUP_DURATION: 250, + ANIMATION_THUMBSUP_DELAY: 200, ANIMATION_PAID_BUTTON_HIDE_DELAY: 1000, BACKGROUND_IMAGE_TRANSITION_DURATION: 1000, SCREEN_TRANSITION_END_TIMEOUT: 1000, diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index f294281fb785..67c0e7de5471 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -472,7 +472,7 @@ function ReportPreview({ return; } - thumbsUpScale.set(withSpring(1, {duration: CONST.ANIMATION_THUMBSUP_DELAY})); + thumbsUpScale.set(isApprovedAnimationRunning ? withDelay(CONST.ANIMATION_THUMBSUP_DELAY, withSpring(1, {duration: CONST.ANIMATION_THUMBSUP_DURATION})) : 1); }, [isApproved, isApprovedAnimationRunning, thumbsUpScale]); const openReportFromPreview = useCallback(() => { From 88d47069287451ac4d9ff8dcd6806e4d08f7863b Mon Sep 17 00:00:00 2001 From: Andrii Vitiv Date: Sat, 7 Dec 2024 15:09:51 +0200 Subject: [PATCH 061/131] Replace navigation blur listeners with `useFocusEffect` --- src/hooks/useWaitForNavigation.ts | 27 +++++++++---------- .../upgrade/WorkspaceUpgradePage.tsx | 25 +++++++++-------- 2 files changed, 24 insertions(+), 28 deletions(-) diff --git a/src/hooks/useWaitForNavigation.ts b/src/hooks/useWaitForNavigation.ts index 73c0eb2bb14c..05981ec3322b 100644 --- a/src/hooks/useWaitForNavigation.ts +++ b/src/hooks/useWaitForNavigation.ts @@ -1,5 +1,5 @@ -import {useNavigation} from '@react-navigation/native'; -import {useEffect, useRef} from 'react'; +import {useFocusEffect} from '@react-navigation/native'; +import {useCallback, useRef} from 'react'; type UseWaitForNavigation = (navigate: () => void) => () => Promise; @@ -8,21 +8,18 @@ type UseWaitForNavigation = (navigate: () => void) => () => Promise; * Only use when navigating by react-navigation */ export default function useWaitForNavigation(): UseWaitForNavigation { - const navigation = useNavigation(); const resolvePromises = useRef void>>([]); - useEffect(() => { - const unsubscribeBlur = navigation.addListener('blur', () => { - resolvePromises.current.forEach((resolve) => { - resolve(); - }); - resolvePromises.current = []; - }); - - return () => { - unsubscribeBlur(); - }; - }, [navigation]); + useFocusEffect( + useCallback(() => { + return () => { + resolvePromises.current.forEach((resolve) => { + resolve(); + }); + resolvePromises.current = []; + }; + }, []), + ); return (navigate: () => void) => () => { navigate(); diff --git a/src/pages/workspace/upgrade/WorkspaceUpgradePage.tsx b/src/pages/workspace/upgrade/WorkspaceUpgradePage.tsx index 3bccc9c90eb0..ae71166091d5 100644 --- a/src/pages/workspace/upgrade/WorkspaceUpgradePage.tsx +++ b/src/pages/workspace/upgrade/WorkspaceUpgradePage.tsx @@ -1,5 +1,5 @@ -import {useNavigation} from '@react-navigation/native'; -import React, {useCallback, useEffect} from 'react'; +import {useFocusEffect} from '@react-navigation/native'; +import React, {useCallback} from 'react'; import {useOnyx} from 'react-native-onyx'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; @@ -37,7 +37,6 @@ function getFeatureNameAlias(featureName: string) { } function WorkspaceUpgradePage({route}: WorkspaceUpgradePageProps) { - const navigation = useNavigation(); const styles = useThemeStyles(); const policyID = route.params.policyID; @@ -155,16 +154,16 @@ function WorkspaceUpgradePage({route}: WorkspaceUpgradePageProps) { route.params.featureName, ]); - useEffect(() => { - const unsubscribeListener = navigation.addListener('blur', () => { - if (!isUpgraded || !canPerformUpgrade) { - return; - } - confirmUpgrade(); - }); - - return unsubscribeListener; - }, [isUpgraded, canPerformUpgrade, confirmUpgrade, navigation]); + useFocusEffect( + useCallback(() => { + return () => { + if (!isUpgraded || !canPerformUpgrade) { + return; + } + confirmUpgrade(); + }; + }, [isUpgraded, canPerformUpgrade, confirmUpgrade]), + ); if (!canPerformUpgrade) { return ; From 8bbeaef850151446e00acc49ca316831dd9627a1 Mon Sep 17 00:00:00 2001 From: Andrii Vitiv Date: Sat, 7 Dec 2024 20:23:31 +0200 Subject: [PATCH 062/131] Fix infinite re-render Caused by `CONST.UPGRADE_FEATURE_INTRO_MAPPING` returning a new object on each invocation --- src/pages/workspace/upgrade/WorkspaceUpgradePage.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/workspace/upgrade/WorkspaceUpgradePage.tsx b/src/pages/workspace/upgrade/WorkspaceUpgradePage.tsx index ae71166091d5..5b9abd31b2d2 100644 --- a/src/pages/workspace/upgrade/WorkspaceUpgradePage.tsx +++ b/src/pages/workspace/upgrade/WorkspaceUpgradePage.tsx @@ -1,5 +1,5 @@ import {useFocusEffect} from '@react-navigation/native'; -import React, {useCallback} from 'react'; +import React, {useCallback, useMemo} from 'react'; import {useOnyx} from 'react-native-onyx'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; @@ -42,14 +42,14 @@ function WorkspaceUpgradePage({route}: WorkspaceUpgradePageProps) { const featureNameAlias = getFeatureNameAlias(route.params.featureName); - const feature = Object.values(CONST.UPGRADE_FEATURE_INTRO_MAPPING).find((f) => f.alias === featureNameAlias); + const feature = useMemo(() => Object.values(CONST.UPGRADE_FEATURE_INTRO_MAPPING).find((f) => f.alias === featureNameAlias), [featureNameAlias]); const {translate} = useLocalize(); const [policy] = useOnyx(`policy_${policyID}`); const qboConfig = policy?.connections?.quickbooksOnline?.config; const {isOffline} = useNetwork(); const canPerformUpgrade = !!feature && !!policy && PolicyUtils.isPolicyAdmin(policy); - const isUpgraded = React.useMemo(() => PolicyUtils.isControlPolicy(policy), [policy]); + const isUpgraded = useMemo(() => PolicyUtils.isControlPolicy(policy), [policy]); const perDiemCustomUnit = PolicyUtils.getPerDiemCustomUnit(policy); const categoryId = route.params?.categoryId; From 2f89f4ca3e4106539e1b753398fb99af35556fcb Mon Sep 17 00:00:00 2001 From: Andrii Vitiv Date: Sun, 8 Dec 2024 17:53:14 +0200 Subject: [PATCH 063/131] Add tests for upgrading workspace and enabling rules --- src/components/Button/index.tsx | 5 ++ src/components/ConfirmationPage.tsx | 1 + src/pages/workspace/upgrade/UpgradeIntro.tsx | 1 + tests/ui/WorkspaceUpgradeTest.tsx | 76 ++++++++++++++++++++ 4 files changed, 83 insertions(+) create mode 100644 tests/ui/WorkspaceUpgradeTest.tsx diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx index 07edd148778d..84767c6347e7 100644 --- a/src/components/Button/index.tsx +++ b/src/components/Button/index.tsx @@ -122,6 +122,9 @@ type ButtonProps = Partial & { /** Id to use for this button */ id?: string; + /** Used to locate this button in ui tests */ + testID?: string; + /** Accessibility label for the component */ accessibilityLabel?: string; @@ -237,6 +240,7 @@ function Button( shouldShowRightIcon = false, id = '', + testID = undefined, accessibilityLabel = '', isSplitButton = false, link = false, @@ -405,6 +409,7 @@ function Button( ]} disabledStyle={disabledStyle} id={id} + testID={testID} accessibilityLabel={accessibilityLabel} role={CONST.ROLE.BUTTON} hoverDimmingValue={1} diff --git a/src/components/ConfirmationPage.tsx b/src/components/ConfirmationPage.tsx index a95cf9bf87d2..7b55f2317d46 100644 --- a/src/components/ConfirmationPage.tsx +++ b/src/components/ConfirmationPage.tsx @@ -82,6 +82,7 @@ function ConfirmationPage({ success large text={buttonText} + testID="confirmation-button" style={styles.mt6} pressOnEnter onPress={onButtonPress} diff --git a/src/pages/workspace/upgrade/UpgradeIntro.tsx b/src/pages/workspace/upgrade/UpgradeIntro.tsx index d45e27905c28..029f8e78271f 100644 --- a/src/pages/workspace/upgrade/UpgradeIntro.tsx +++ b/src/pages/workspace/upgrade/UpgradeIntro.tsx @@ -75,6 +75,7 @@ function UpgradeIntro({feature, onUpgrade, buttonDisabled, loading, isCategorizi