From c9d13d0c5e7897dbfcc0d438940304afe98a95d8 Mon Sep 17 00:00:00 2001 From: truph01 Date: Wed, 14 Jan 2026 17:47:58 +0700 Subject: [PATCH 01/13] fix: refactor modals --- src/pages/settings/AboutPage/ConsolePage.tsx | 28 +++--- .../Contacts/ContactMethodDetailsPage.tsx | 92 +++++++++---------- .../CustomStatus/VacationDelegatePage.tsx | 87 +++++++++--------- src/pages/settings/Report/VisibilityPage.tsx | 54 +++++------ .../settings/Wallet/WalletPage/index.tsx | 39 ++++---- 5 files changed, 145 insertions(+), 155 deletions(-) diff --git a/src/pages/settings/AboutPage/ConsolePage.tsx b/src/pages/settings/AboutPage/ConsolePage.tsx index 2906f35798a3..e760549f1921 100644 --- a/src/pages/settings/AboutPage/ConsolePage.tsx +++ b/src/pages/settings/AboutPage/ConsolePage.tsx @@ -5,7 +5,6 @@ import {View} from 'react-native'; import type {ListRenderItem, ListRenderItemInfo} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import Button from '@components/Button'; -import ConfirmModal from '@components/ConfirmModal'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; // eslint-disable-next-line no-restricted-imports import * as Expensicons from '@components/Icon/Expensicons'; @@ -14,6 +13,7 @@ import type {PopoverMenuItem} from '@components/PopoverMenu'; import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; +import useConfirmModal from '@hooks/useConfirmModal'; import useIsAuthenticated from '@hooks/useIsAuthenticated'; import useKeyboardShortcut from '@hooks/useKeyboardShortcut'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; @@ -47,7 +47,6 @@ function ConsolePage() { const [shouldStoreLogs] = useOnyx(ONYXKEYS.SHOULD_STORE_LOGS, {canBeMissing: true}); const [input, setInput] = useState(''); const [isGeneratingLogsFile, setIsGeneratingLogsFile] = useState(false); - const [isLimitModalVisible, setIsLimitModalVisible] = useState(false); const [activeFilterIndex, setActiveFilterIndex] = useState(filterBy.all); const {translate} = useLocalize(); const styles = useThemeStyles(); @@ -55,6 +54,18 @@ function ConsolePage() { const route = useRoute>(); const isAuthenticated = useIsAuthenticated(); + const {showConfirmModal} = useConfirmModal(); + const showLogSizeTooLargeModal = useCallback(() => { + return showConfirmModal({ + title: translate('initialSettingsPage.debugConsole.shareLog'), + prompt: translate('initialSettingsPage.debugConsole.logSizeTooLarge', { + size: CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE / 1024 / 1024, + }), + confirmText: translate('common.ok'), + shouldShowCancelButton: false, + }); + }, [showConfirmModal, translate]); + const menuItems: PopoverMenuItem[] = useMemo( () => [ { @@ -131,7 +142,7 @@ function ConsolePage() { // if the file size is too large to send it as an attachment, show a modal and return if (size > CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE) { - setIsLimitModalVisible(true); + showLogSizeTooLargeModal(); return; } @@ -212,17 +223,6 @@ function ConsolePage() { large /> - setIsLimitModalVisible(false)} - onCancel={() => setIsLimitModalVisible(false)} - prompt={translate('initialSettingsPage.debugConsole.logSizeTooLarge', { - size: CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE / 1024 / 1024, - })} - shouldShowCancelButton={false} - confirmText={translate('common.ok')} - /> ); } diff --git a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx index 47e31d3d748e..7f9cf0c2ff28 100644 --- a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx +++ b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx @@ -2,19 +2,20 @@ import {Str} from 'expensify-common'; import React, {useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react'; import {InteractionManager, Keyboard} from 'react-native'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; -import ConfirmModal from '@components/ConfirmModal'; import {DelegateNoAccessContext} from '@components/DelegateNoAccessModalProvider'; import ErrorMessageRow from '@components/ErrorMessageRow'; import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import {LockedAccountContext} from '@components/LockedAccountModalProvider'; import MenuItem from '@components/MenuItem'; +import {ModalActions} from '@components/Modal/Global/ModalContext'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; import Text from '@components/Text'; import ValidateCodeActionForm from '@components/ValidateCodeActionForm'; import type {ValidateCodeFormHandle} from '@components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm'; +import useConfirmModal from '@hooks/useConfirmModal'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; @@ -66,7 +67,6 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) { const themeStyles = useThemeStyles(); const icons = useMemoizedLazyExpensifyIcons(['Star', 'Trashcan']); - const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const validateCodeFormRef = useRef(null); const backTo = route.params.backTo; @@ -120,29 +120,6 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) { return !securityGroups?.[`${ONYXKEYS.COLLECTION.SECURITY_GROUP}${primaryDomainSecurityGroupID}`]?.enableRestrictedPrimaryLogin; }, [isDefaultContactMethod, loginData?.validatedDate, session?.email, myDomainSecurityGroups, securityGroups]); - /** - * Toggle delete confirm modal visibility - */ - const toggleDeleteModal = useCallback((isOpen: boolean) => { - if (canUseTouchScreen() && isOpen) { - // eslint-disable-next-line @typescript-eslint/no-deprecated - InteractionManager.runAfterInteractions(() => { - setIsDeleteModalOpen(isOpen); - }); - Keyboard.dismiss(); - } else { - setIsDeleteModalOpen(isOpen); - } - }, []); - - /** - * Delete the contact method and hide the modal - */ - const confirmDeleteAndHideModal = useCallback(() => { - toggleDeleteModal(false); - deleteContactMethod(contactMethod, loginList ?? {}, backTo); - }, [contactMethod, loginList, toggleDeleteModal, backTo]); - const prevValidatedDate = usePrevious(loginData?.validatedDate); useEffect(() => { // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing @@ -168,17 +145,56 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) { // eslint-disable-next-line react-hooks/exhaustive-deps -- The prevPendingDeletedLogin is a ref, so no need to add it to dependencies. }, [contactMethod, loginData?.partnerUserID, loginData?.validatedDate]); + const {showConfirmModal} = useConfirmModal(); + const showRemoveContactMethodModal = useCallback(() => { + return showConfirmModal({ + title: translate('contacts.removeContactMethod'), + prompt: translate('contacts.removeAreYouSure'), + confirmText: translate('common.yesContinue'), + cancelText: translate('common.cancel'), + shouldShowCancelButton: true, + danger: true, + }); + }, [showConfirmModal, translate]); + + /** + * Toggle delete confirm modal visibility + */ + const turnOnDeleteModal = useCallback(() => { + const openDeleteModal = async () => { + const result = await showRemoveContactMethodModal(); + // eslint-disable-next-line @typescript-eslint/no-deprecated + InteractionManager.runAfterInteractions(() => { + validateCodeFormRef.current?.focusLastSelected?.(); + }); + if (result.action !== ModalActions.CONFIRM) { + return; + } + + deleteContactMethod(contactMethod, loginList ?? {}, backTo); + }; + + if (canUseTouchScreen()) { + // eslint-disable-next-line @typescript-eslint/no-deprecated + InteractionManager.runAfterInteractions(openDeleteModal); + Keyboard.dismiss(); + return; + } + + openDeleteModal(); + }, [contactMethod, loginList, backTo, showRemoveContactMethodModal]); + const getThreeDotsMenuItems = useCallback(() => { const menuItems = []; if (isValidateCodeFormVisible && !isDefaultContactMethod) { menuItems.push({ icon: icons.Trashcan, text: translate('common.remove'), - onSelected: () => close(() => toggleDeleteModal(true)), + onSelected: () => close(turnOnDeleteModal), }); } return menuItems; - }, [isValidateCodeFormVisible, translate, toggleDeleteModal, isDefaultContactMethod, icons.Trashcan]); + }, [isValidateCodeFormVisible, translate, turnOnDeleteModal, isDefaultContactMethod, icons.Trashcan]); if (isLoadingOnyxValues || (isLoadingReportData && isEmptyObject(loginList))) { return ; @@ -204,25 +220,6 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) { const isFailedRemovedContactMethod = !!loginData.errorFields?.deletedLogin; const shouldSkipInitialValidation = route.params?.shouldSkipInitialValidation === 'true'; - const getDeleteConfirmationModal = () => ( - toggleDeleteModal(false)} - onModalHide={() => { - // eslint-disable-next-line @typescript-eslint/no-deprecated - InteractionManager.runAfterInteractions(() => { - validateCodeFormRef.current?.focusLastSelected?.(); - }); - }} - prompt={translate('contacts.removeAreYouSure')} - confirmText={translate('common.yesContinue')} - cancelText={translate('common.cancel')} - isVisible={isDeleteModalOpen && !isDefaultContactMethod} - danger - /> - ); - const getMenuItems = () => ( <> {canChangeDefaultContactMethod ? ( @@ -262,7 +259,7 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) { showDelegateNoAccessModal(); return; } - toggleDeleteModal(true); + turnOnDeleteModal(); }} /> @@ -355,7 +352,6 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) { )} {!isValidateCodeFormVisible && !!loginData.validatedDate && getMenuItems()} - {getDeleteConfirmationModal()} ); diff --git a/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx b/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx index 2d09aa9960f7..e124b271b0bf 100644 --- a/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx +++ b/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx @@ -1,11 +1,12 @@ import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {View} from 'react-native'; -import ConfirmModal from '@components/ConfirmModal'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import {ModalActions} from '@components/Modal/Global/ModalContext'; import ScreenWrapper from '@components/ScreenWrapper'; // eslint-disable-next-line no-restricted-imports import SelectionList from '@components/SelectionListWithSections'; import UserListItem from '@components/SelectionListWithSections/UserListItem'; +import useConfirmModal from '@hooks/useConfirmModal'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; @@ -26,7 +27,6 @@ import type {Participant} from '@src/types/onyx/IOU'; function VacationDelegatePage() { const {translate} = useLocalize(); const styles = useThemeStyles(); - const [isWarningModalVisible, setIsWarningModalVisible] = useState(false); const [newVacationDelegate, setNewVacationDelegate] = useState(''); const {login: currentUserLogin} = useCurrentUserPersonalDetails(); const [countryCode = CONST.DEFAULT_COUNTRY_CODE] = useOnyx(ONYXKEYS.COUNTRY_CODE, {canBeMissing: false}); @@ -45,6 +45,17 @@ function VacationDelegatePage() { [currentVacationDelegate], ); + const {showConfirmModal} = useConfirmModal(); + const showVacationDelegateWarningModal = () => { + return showConfirmModal({ + title: translate('common.headsUp'), + prompt: translate('statusPage.vacationDelegateWarning', {nameOrEmail: getPersonalDetailByEmail(newVacationDelegate)?.displayName ?? newVacationDelegate}), + confirmText: translate('common.confirm'), + cancelText: translate('common.cancel'), + shouldShowCancelButton: true, + }); + }; + const {searchTerm, debouncedSearchTerm, setSearchTerm, availableOptions, areOptionsInitialized, onListEndReached} = useSearchSelector({ selectionMode: CONST.SEARCH_SELECTOR.SELECTION_MODE_SINGLE, maxRecentReportsToShow: CONST.IOU.MAX_RECENT_REPORTS_TO_SHOW, @@ -142,14 +153,19 @@ function VacationDelegatePage() { return; } - setVacationDelegate(currentUserLogin ?? '', option?.login ?? '', false, vacationDelegate?.delegate).then((response) => { + setVacationDelegate(currentUserLogin ?? '', option?.login ?? '', false, vacationDelegate?.delegate).then(async (response) => { if (!response?.jsonCode) { Navigation.goBack(ROUTES.SETTINGS_STATUS); return; } if (response.jsonCode === CONST.JSON_CODE.POLICY_DIFF_WARNING) { - setIsWarningModalVisible(true); + const result = await showVacationDelegateWarningModal(); + if (result.action === ModalActions.CONFIRM) { + setVacationDelegate(currentUserLogin ?? '', newVacationDelegate, true, vacationDelegate?.delegate).then(() => Navigation.goBack(ROUTES.SETTINGS_STATUS)); + } else { + clearVacationDelegateError(vacationDelegate?.previousDelegate); + } setNewVacationDelegate(option?.login ?? ''); return; } @@ -165,47 +181,30 @@ function VacationDelegatePage() { }, [debouncedSearchTerm]); return ( - <> - - Navigation.goBack(ROUTES.SETTINGS_STATUS)} - /> - - - - - { - setIsWarningModalVisible(false); - setVacationDelegate(currentUserLogin ?? '', newVacationDelegate, true, vacationDelegate?.delegate).then(() => Navigation.goBack(ROUTES.SETTINGS_STATUS)); - }} - onCancel={() => { - setIsWarningModalVisible(false); - clearVacationDelegateError(vacationDelegate?.previousDelegate); - }} - confirmText={translate('common.confirm')} - cancelText={translate('common.cancel')} + + Navigation.goBack(ROUTES.SETTINGS_STATUS)} /> - + + + + ); } diff --git a/src/pages/settings/Report/VisibilityPage.tsx b/src/pages/settings/Report/VisibilityPage.tsx index 1ff3560c597d..d61d3f87511b 100644 --- a/src/pages/settings/Report/VisibilityPage.tsx +++ b/src/pages/settings/Report/VisibilityPage.tsx @@ -1,11 +1,12 @@ import {useRoute} from '@react-navigation/native'; -import React, {useCallback, useMemo, useRef, useState} from 'react'; +import React, {useCallback, useMemo} from 'react'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; -import ConfirmModal from '@components/ConfirmModal'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import {ModalActions} from '@components/Modal/Global/ModalContext'; import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; import RadioListItem from '@components/SelectionList/ListItem/RadioListItem'; +import useConfirmModal from '@hooks/useConfirmModal'; import useLocalize from '@hooks/useLocalize'; import useReportIsArchived from '@hooks/useReportIsArchived'; import type {PlatformStackRouteProp, PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; @@ -22,12 +23,12 @@ type VisibilityProps = WithReportOrNotFoundProps & PlatformStackScreenProps>(); - const [showConfirmModal, setShowConfirmModal] = useState(false); - const shouldGoBackToDetailsPage = useRef(false); const isReportArchived = useReportIsArchived(report?.reportID); const shouldDisableVisibility = isArchivedNonExpenseReport(report, isReportArchived); const {translate} = useLocalize(); + const {showConfirmModal} = useConfirmModal(); + const visibilityOptions = useMemo( () => Object.values(CONST.REPORT.VISIBILITY) @@ -52,18 +53,25 @@ function VisibilityPage({report}: VisibilityProps) { return; } updateRoomVisibility(report.reportID, report.visibility, newVisibility); - if (showConfirmModal) { - shouldGoBackToDetailsPage.current = true; - } else { - goBack(); - } + goBack(); }, [report, showConfirmModal, goBack], ); - const hideModal = useCallback(() => { - setShowConfirmModal(false); - }, []); + const showPublicVisibilityModal = async () => { + const result = await showConfirmModal({ + title: translate('common.areYouSure'), + prompt: translate('newRoomPage.publicDescription'), + confirmText: translate('common.yes'), + cancelText: translate('common.no'), + shouldShowCancelButton: true, + danger: true, + }); + if (result.action !== ModalActions.CONFIRM) { + return; + } + changeVisibility(CONST.REPORT.VISIBILITY.PUBLIC); + }; return ( { if (option.value === CONST.REPORT.VISIBILITY.PUBLIC) { - setShowConfirmModal(true); + showPublicVisibilityModal(); return; } changeVisibility(option.value); @@ -89,26 +97,6 @@ function VisibilityPage({report}: VisibilityProps) { initiallyFocusedItemKey={visibilityOptions.find((visibility) => visibility.isSelected)?.keyForList} ListItem={RadioListItem} /> - { - changeVisibility(CONST.REPORT.VISIBILITY.PUBLIC); - hideModal(); - }} - onModalHide={() => { - if (!shouldGoBackToDetailsPage.current) { - return; - } - shouldGoBackToDetailsPage.current = false; - goBack(); - }} - onCancel={hideModal} - title={translate('common.areYouSure')} - prompt={translate('newRoomPage.publicDescription')} - confirmText={translate('common.yes')} - cancelText={translate('common.no')} - danger - /> ); diff --git a/src/pages/settings/Wallet/WalletPage/index.tsx b/src/pages/settings/Wallet/WalletPage/index.tsx index cb87a2b8ee83..2424a30cac94 100644 --- a/src/pages/settings/Wallet/WalletPage/index.tsx +++ b/src/pages/settings/Wallet/WalletPage/index.tsx @@ -5,7 +5,6 @@ import React, {useCallback, useContext, useEffect, useMemo, useRef, useState} fr import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import ActivityIndicator from '@components/ActivityIndicator'; -import ConfirmModal from '@components/ConfirmModal'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import Icon from '@components/Icon'; @@ -15,11 +14,13 @@ import type {PaymentMethodType, Source} from '@components/KYCWall/types'; import {LockedAccountContext} from '@components/LockedAccountModalProvider'; import MenuItem from '@components/MenuItem'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; +import {ModalActions} from '@components/Modal/Global/ModalContext'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; import Section from '@components/Section'; import Text from '@components/Text'; +import useConfirmModal from '@hooks/useConfirmModal'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import {useMemoizedLazyExpensifyIcons, useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; @@ -84,7 +85,6 @@ function WalletPage() { const {paymentMethod, setPaymentMethod, resetSelectedPaymentMethodData} = usePaymentMethodState(); const [shouldShowLoadingSpinner, setShouldShowLoadingSpinner] = useState(false); const paymentMethodButtonRef = useRef(null); - const [showConfirmDeleteModal, setShowConfirmDeleteModal] = useState(false); const [shouldShowShareButton, setShouldShowShareButton] = useState(false); const [shouldShowUnshareButton, setShouldShowUnshareButton] = useState(false); const kycWallRef = useContext(KYCWallContext); @@ -219,7 +219,6 @@ function WalletPage() { } else if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.DEBIT_CARD && fundID) { deletePaymentCard(fundID); } - setShowConfirmDeleteModal(false); resetSelectedPaymentMethodData(); }, [ paymentMethod.selectedPaymentMethod.bankAccountID, @@ -311,6 +310,23 @@ function WalletPage() { [paymentMethod.formattedSelectedPaymentMethod, styles.mb4, styles.ph5, styles.pt3], ); + const {showConfirmModal} = useConfirmModal(); + const showDeleteAccountModal = useCallback(async () => { + const result = await showConfirmModal({ + title: translate('walletPage.deleteAccount'), + prompt: translate('walletPage.deleteConfirmation'), + confirmText: translate('common.delete'), + cancelText: translate('common.cancel'), + shouldShowCancelButton: true, + danger: true, + }); + resetSelectedPaymentMethodData(); + if (result.action !== ModalActions.CONFIRM) { + return; + } + deletePaymentMethod(); + }, [showConfirmModal, translate, resetSelectedPaymentMethodData, deletePaymentMethod]); + const threeDotMenuItems = useMemo( () => [ ...(shouldUseNarrowLayout ? [bottomMountItem] : []), @@ -368,7 +384,9 @@ function WalletPage() { closeModal(() => showLockedAccountModal()); return; } - closeModal(() => setShowConfirmDeleteModal(true)); + closeModal(() => { + showDeleteAccountModal(); + }); }, }, ...(shouldShowEnableGlobalReimbursementsButton @@ -405,6 +423,7 @@ function WalletPage() { makeDefaultPaymentMethod, showLockedAccountModal, paymentMethod.selectedPaymentMethod.bankAccountID, + showDeleteAccountModal, ], ); @@ -633,18 +652,6 @@ function WalletPage() { - setShowConfirmDeleteModal(false)} - title={translate('walletPage.deleteAccount')} - prompt={translate('walletPage.deleteConfirmation')} - confirmText={translate('common.delete')} - cancelText={translate('common.cancel')} - shouldShowCancelButton - danger - onModalHide={resetSelectedPaymentMethodData} - /> ); } From a0346b4fda6e5c5d4b60891e82855361b25f3673 Mon Sep 17 00:00:00 2001 From: truph01 Date: Thu, 29 Jan 2026 16:56:29 +0700 Subject: [PATCH 02/13] fix: remove useCallback --- src/pages/settings/Report/VisibilityPage.tsx | 23 +++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/pages/settings/Report/VisibilityPage.tsx b/src/pages/settings/Report/VisibilityPage.tsx index 9133498e783e..b98e832a858d 100644 --- a/src/pages/settings/Report/VisibilityPage.tsx +++ b/src/pages/settings/Report/VisibilityPage.tsx @@ -1,5 +1,5 @@ import {useRoute} from '@react-navigation/native'; -import React, {useCallback, useMemo} from 'react'; +import React, {useMemo} from 'react'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import {ModalActions} from '@components/Modal/Global/ModalContext'; @@ -43,20 +43,17 @@ function VisibilityPage({report}: VisibilityProps) { [translate, report?.visibility], ); - const goBack = useCallback(() => { + const goBack = () => { goBackToDetailsPage(report, route.params.backTo); - }, [report, route.params.backTo]); + }; - const changeVisibility = useCallback( - (newVisibility: RoomVisibility) => { - if (!report) { - return; - } - updateRoomVisibility(report.reportID, report.visibility, newVisibility); - goBack(); - }, - [report, showConfirmModal, goBack], - ); + const changeVisibility = (newVisibility: RoomVisibility) => { + if (!report) { + return; + } + updateRoomVisibility(report.reportID, report.visibility, newVisibility); + goBack(); + }; const showPublicVisibilityModal = async () => { const result = await showConfirmModal({ From a2d4a34a0f9776fa02af1ad09160d631935bc8a1 Mon Sep 17 00:00:00 2001 From: truph01 Date: Mon, 9 Feb 2026 14:26:13 +0700 Subject: [PATCH 03/13] fix: lint --- .../CustomStatus/VacationDelegatePage.tsx | 47 +++++++++---------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx b/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx index cdc7f2e32854..fd06ad28cff7 100644 --- a/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx +++ b/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx @@ -3,12 +3,9 @@ import {View} from 'react-native'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import {ModalActions} from '@components/Modal/Global/ModalContext'; import ScreenWrapper from '@components/ScreenWrapper'; -// eslint-disable-next-line no-restricted-imports -import SelectionList from '@components/SelectionListWithSections'; -import UserListItem from '@components/SelectionListWithSections/UserListItem'; -import useConfirmModal from '@hooks/useConfirmModal'; import UserListItem from '@components/SelectionList/ListItem/UserListItem'; import SelectionList from '@components/SelectionList/SelectionListWithSections'; +import useConfirmModal from '@hooks/useConfirmModal'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; @@ -187,28 +184,28 @@ function VacationDelegatePage() { return ( - Navigation.goBack(ROUTES.SETTINGS_STATUS)} - /> - - + Navigation.goBack(ROUTES.SETTINGS_STATUS)} /> - - + + + + ); } From 41f6506484ef907dd0530f0f7e3778a7d60fcb7c Mon Sep 17 00:00:00 2001 From: truph01 Date: Mon, 9 Feb 2026 14:27:08 +0700 Subject: [PATCH 04/13] fix: lint --- Mobile-Expensify | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mobile-Expensify b/Mobile-Expensify index 92e561a804b6..68c675aff235 160000 --- a/Mobile-Expensify +++ b/Mobile-Expensify @@ -1 +1 @@ -Subproject commit 92e561a804b64bb97375b152fe91f8b1ef90b08e +Subproject commit 68c675aff235ac3c81f04a20a1e8fe019502bebd From 8983b1d0ca28103633c895fad4fb7c5a2418592f Mon Sep 17 00:00:00 2001 From: truph01 Date: Mon, 9 Feb 2026 14:56:02 +0700 Subject: [PATCH 05/13] fix: navigation issue when confirming visibility modal --- src/pages/settings/Report/VisibilityPage.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/settings/Report/VisibilityPage.tsx b/src/pages/settings/Report/VisibilityPage.tsx index b98e832a858d..4cbbe57f3ab6 100644 --- a/src/pages/settings/Report/VisibilityPage.tsx +++ b/src/pages/settings/Report/VisibilityPage.tsx @@ -9,6 +9,7 @@ import RadioListItem from '@components/SelectionList/ListItem/RadioListItem'; import useConfirmModal from '@hooks/useConfirmModal'; import useLocalize from '@hooks/useLocalize'; import useReportIsArchived from '@hooks/useReportIsArchived'; +import setNavigationActionToMicrotaskQueue from '@libs/Navigation/helpers/setNavigationActionToMicrotaskQueue'; import type {PlatformStackRouteProp, PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {ReportSettingsNavigatorParamList} from '@libs/Navigation/types'; import {goBackToDetailsPage, isArchivedNonExpenseReport} from '@libs/ReportUtils'; @@ -52,7 +53,7 @@ function VisibilityPage({report}: VisibilityProps) { return; } updateRoomVisibility(report.reportID, report.visibility, newVisibility); - goBack(); + setNavigationActionToMicrotaskQueue(goBack); }; const showPublicVisibilityModal = async () => { From 566c3b0f0d80aac7e795ba61b2ccac1a9f5898c6 Mon Sep 17 00:00:00 2001 From: truph01 Date: Mon, 9 Feb 2026 15:27:31 +0700 Subject: [PATCH 06/13] fix: try to fix lint --- .../settings/Profile/CustomStatus/VacationDelegatePage.tsx | 6 +++--- src/pages/settings/Wallet/WalletPage/index.tsx | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx b/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx index fd06ad28cff7..1f67788b75d0 100644 --- a/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx +++ b/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx @@ -45,7 +45,7 @@ function VacationDelegatePage() { ); const {showConfirmModal} = useConfirmModal(); - const showVacationDelegateWarningModal = () => { + const showVacationDelegateWarningModal = useCallback(() => { return showConfirmModal({ title: translate('common.headsUp'), prompt: translate('statusPage.vacationDelegateWarning', {nameOrEmail: getPersonalDetailByEmail(newVacationDelegate)?.displayName ?? newVacationDelegate}), @@ -53,7 +53,7 @@ function VacationDelegatePage() { cancelText: translate('common.cancel'), shouldShowCancelButton: true, }); - }; + }, [showConfirmModal, translate, newVacationDelegate]); const {searchTerm, debouncedSearchTerm, setSearchTerm, availableOptions, areOptionsInitialized, onListEndReached} = useSearchSelector({ selectionMode: CONST.SEARCH_SELECTOR.SELECTION_MODE_SINGLE, @@ -162,7 +162,7 @@ function VacationDelegatePage() { Navigation.goBack(ROUTES.SETTINGS_STATUS); }); }, - [currentUserLogin, vacationDelegate, setSearchTerm], + [currentUserLogin, vacationDelegate, setSearchTerm, showVacationDelegateWarningModal, newVacationDelegate], ); useEffect(() => { diff --git a/src/pages/settings/Wallet/WalletPage/index.tsx b/src/pages/settings/Wallet/WalletPage/index.tsx index 8c4021e982ec..a5315237660e 100644 --- a/src/pages/settings/Wallet/WalletPage/index.tsx +++ b/src/pages/settings/Wallet/WalletPage/index.tsx @@ -274,6 +274,7 @@ function WalletPage() { useEffect(() => { // If the user was previously offline, skip debouncing showing the loader if (!network.isOffline) { + // eslint-disable-next-line react-hooks/set-state-in-effect updateShouldShowLoadingSpinner(); } else { debounceSetShouldShowLoadingSpinner(); From a805e2388d840b9b2b1432d70533f98dd1dca279 Mon Sep 17 00:00:00 2001 From: truph01 Date: Mon, 9 Feb 2026 15:37:21 +0700 Subject: [PATCH 07/13] fix: revert comment --- src/pages/settings/Wallet/WalletPage/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/settings/Wallet/WalletPage/index.tsx b/src/pages/settings/Wallet/WalletPage/index.tsx index a5315237660e..8c4021e982ec 100644 --- a/src/pages/settings/Wallet/WalletPage/index.tsx +++ b/src/pages/settings/Wallet/WalletPage/index.tsx @@ -274,7 +274,6 @@ function WalletPage() { useEffect(() => { // If the user was previously offline, skip debouncing showing the loader if (!network.isOffline) { - // eslint-disable-next-line react-hooks/set-state-in-effect updateShouldShowLoadingSpinner(); } else { debounceSetShouldShowLoadingSpinner(); From 9b529ec76a3b2c4618fe723c8fb8d8e608c15c90 Mon Sep 17 00:00:00 2001 From: truph01 Date: Tue, 17 Feb 2026 10:32:54 +0700 Subject: [PATCH 08/13] fix: conflicts --- Mobile-Expensify | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mobile-Expensify b/Mobile-Expensify index 68c675aff235..db0883a40063 160000 --- a/Mobile-Expensify +++ b/Mobile-Expensify @@ -1 +1 @@ -Subproject commit 68c675aff235ac3c81f04a20a1e8fe019502bebd +Subproject commit db0883a40063c1097b8fc5da68b6305e14dee223 From d711335039024f513380e53127f672402ed85e68 Mon Sep 17 00:00:00 2001 From: truph01 Date: Tue, 17 Feb 2026 10:34:07 +0700 Subject: [PATCH 09/13] fix: conflicts --- src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx b/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx index c0dfc6d9a6d4..7b2067bee35a 100644 --- a/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx +++ b/src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx @@ -5,7 +5,6 @@ import MenuItem from '@components/MenuItem'; import {ModalActions} from '@components/Modal/Global/ModalContext'; import ScreenWrapper from '@components/ScreenWrapper'; import UserListItem from '@components/SelectionList/ListItem/UserListItem'; -import SelectionList from '@components/SelectionList/SelectionListWithSections'; import SelectionListWithSections from '@components/SelectionList/SelectionListWithSections'; import Text from '@components/Text'; import useConfirmModal from '@hooks/useConfirmModal'; From d6fc58fe0800e76193847adb2cf96e13da87bb04 Mon Sep 17 00:00:00 2001 From: truph01 Date: Sun, 22 Feb 2026 22:57:12 +0700 Subject: [PATCH 10/13] fix: conflicts --- src/pages/settings/AboutPage/ConsolePage.tsx | 228 ------------------- 1 file changed, 228 deletions(-) delete mode 100644 src/pages/settings/AboutPage/ConsolePage.tsx diff --git a/src/pages/settings/AboutPage/ConsolePage.tsx b/src/pages/settings/AboutPage/ConsolePage.tsx deleted file mode 100644 index 547741c01b77..000000000000 --- a/src/pages/settings/AboutPage/ConsolePage.tsx +++ /dev/null @@ -1,228 +0,0 @@ -import {useRoute} from '@react-navigation/native'; -import {format} from 'date-fns'; -import React, {useCallback, useMemo, useRef, useState} from 'react'; -import {View} from 'react-native'; -import type {ListRenderItem, ListRenderItemInfo} from 'react-native'; -import type {OnyxEntry} from 'react-native-onyx'; -import Button from '@components/Button'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; -// eslint-disable-next-line no-restricted-imports -import * as Expensicons from '@components/Icon/Expensicons'; -import InvertedFlatList from '@components/InvertedFlatList'; -import type {PopoverMenuItem} from '@components/PopoverMenu'; -import ScreenWrapper from '@components/ScreenWrapper'; -import Text from '@components/Text'; -import TextInput from '@components/TextInput'; -import useConfirmModal from '@hooks/useConfirmModal'; -import useIsAuthenticated from '@hooks/useIsAuthenticated'; -import useKeyboardShortcut from '@hooks/useKeyboardShortcut'; -import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; -import useLocalize from '@hooks/useLocalize'; -import useOnyx from '@hooks/useOnyx'; -import useTheme from '@hooks/useTheme'; -import useThemeStyles from '@hooks/useThemeStyles'; -import {addLog} from '@libs/actions/Console'; -import {createLog, parseStringifiedMessages, sanitizeConsoleInput} from '@libs/Console'; -import type {Log} from '@libs/Console'; -import localFileCreate from '@libs/localFileCreate'; -import localFileDownload from '@libs/localFileDownload'; -import Navigation from '@libs/Navigation/Navigation'; -import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types'; -import type {SettingsNavigatorParamList} from '@navigation/types'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; -import type SCREENS from '@src/SCREENS'; -import type {CapturedLogs} from '@src/types/onyx'; - -const filterBy = { - all: '', - network: '[Network]', -} as const; -type FilterBy = (typeof filterBy)[keyof typeof filterBy]; - -function ConsolePage() { - const icons = useMemoizedLazyExpensifyIcons(['All', 'Download', 'Globe', 'UploadAlt']); - const [capturedLogs] = useOnyx(ONYXKEYS.LOGS, {canBeMissing: false}); - const [shouldStoreLogs] = useOnyx(ONYXKEYS.SHOULD_STORE_LOGS, {canBeMissing: true}); - const [input, setInput] = useState(''); - const [isGeneratingLogsFile, setIsGeneratingLogsFile] = useState(false); - const [activeFilterIndex, setActiveFilterIndex] = useState(filterBy.all); - const {translate} = useLocalize(); - const styles = useThemeStyles(); - const theme = useTheme(); - const route = useRoute>(); - const isAuthenticated = useIsAuthenticated(); - - const {showConfirmModal} = useConfirmModal(); - const showLogSizeTooLargeModal = useCallback(() => { - return showConfirmModal({ - title: translate('initialSettingsPage.debugConsole.shareLog'), - prompt: translate('initialSettingsPage.debugConsole.logSizeTooLarge', CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE / 1024 / 1024), - confirmText: translate('common.ok'), - shouldShowCancelButton: false, - }); - }, [showConfirmModal, translate]); - - const menuItems: PopoverMenuItem[] = useMemo( - () => [ - { - text: translate('common.filterLogs'), - disabled: true, - }, - { - icon: icons.All, - text: translate('common.all'), - iconFill: activeFilterIndex === filterBy.all ? theme.iconSuccessFill : theme.icon, - iconRight: Expensicons.Checkmark, - shouldShowRightIcon: activeFilterIndex === filterBy.all, - success: activeFilterIndex === filterBy.all, - onSelected: () => { - setActiveFilterIndex(filterBy.all); - }, - }, - { - icon: icons.Globe, - text: translate('common.network'), - iconFill: activeFilterIndex === filterBy.network ? theme.iconSuccessFill : theme.icon, - iconRight: Expensicons.CheckCircle, - shouldShowRightIcon: activeFilterIndex === filterBy.network, - success: activeFilterIndex === filterBy.network, - onSelected: () => { - setActiveFilterIndex(filterBy.network); - }, - }, - ], - [activeFilterIndex, icons.All, icons.Globe, theme.icon, theme.iconSuccessFill, translate], - ); - - const prevLogs = useRef>({}); - const getLogs = useCallback(() => { - if (!shouldStoreLogs) { - return []; - } - - prevLogs.current = {...prevLogs.current, ...capturedLogs}; - return Object.entries(prevLogs.current ?? {}) - .map(([key, value]) => ({key, ...value})) - .reverse(); - }, [capturedLogs, shouldStoreLogs]); - - const logsList = useMemo(() => getLogs(), [getLogs]); - - const filteredLogsList = useMemo(() => logsList.filter((log) => log.message.includes(activeFilterIndex)), [activeFilterIndex, logsList]); - - const executeArbitraryCode = () => { - const sanitizedInput = sanitizeConsoleInput(input); - - const output = createLog(sanitizedInput); - for (const log of output) { - addLog(log); - } - setInput(''); - }; - - useKeyboardShortcut(CONST.KEYBOARD_SHORTCUTS.ENTER, executeArbitraryCode); - - const saveLogs = () => { - const logsWithParsedMessages = parseStringifiedMessages(filteredLogsList); - - localFileDownload('logs', JSON.stringify(logsWithParsedMessages, null, 2), translate); - }; - - const shareLogs = () => { - setIsGeneratingLogsFile(true); - const logsWithParsedMessages = parseStringifiedMessages(filteredLogsList); - - // Generate a file with the logs and pass its path to the list of reports to share it with - localFileCreate('logs', JSON.stringify(logsWithParsedMessages, null, 2)).then(({path, size}) => { - setIsGeneratingLogsFile(false); - - // if the file size is too large to send it as an attachment, show a modal and return - if (size > CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE) { - showLogSizeTooLargeModal(); - - return; - } - - Navigation.navigate(ROUTES.SETTINGS_SHARE_LOG.getRoute(path)); - }); - }; - - const renderItem: ListRenderItem = useCallback( - ({item}: ListRenderItemInfo) => { - if (!item) { - return null; - } - - return ( - - {`${format(new Date(item.time), CONST.DATE.FNS_DB_FORMAT_STRING)} ${item.message}`} - - ); - }, - [styles.mb2], - ); - - return ( - - Navigation.goBack(route.params?.backTo)} - shouldShowThreeDotsButton - threeDotsMenuItems={menuItems} - threeDotsMenuIcon={Expensicons.Filter} - threeDotsMenuIconFill={theme.icon} - /> - - {translate('initialSettingsPage.debugConsole.noLogsAvailable')}} - /> - - -