diff --git a/src/pages/settings/Security/CloseAccountPage.tsx b/src/pages/settings/Security/CloseAccountPage.tsx index e1c3737541e05..3b06bedd13bc7 100644 --- a/src/pages/settings/Security/CloseAccountPage.tsx +++ b/src/pages/settings/Security/CloseAccountPage.tsx @@ -1,15 +1,16 @@ import {Str} from 'expensify-common'; -import React, {useEffect, useState} from 'react'; +import React, {useEffect} from 'react'; import {View} from 'react-native'; -import ConfirmModal from '@components/ConfirmModal'; import DelegateNoAccessWrapper from '@components/DelegateNoAccessWrapper'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import {ModalActions} from '@components/Modal/Global/ModalContext'; import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; +import useConfirmModal from '@hooks/useConfirmModal'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -30,8 +31,18 @@ function CloseAccountPage() { const styles = useThemeStyles(); const {translate, formatPhoneNumber} = useLocalize(); - const [isConfirmModalVisible, setConfirmModalVisibility] = useState(false); - const [reasonForLeaving, setReasonForLeaving] = useState(''); + const {showConfirmModal} = useConfirmModal(); + const showCloseAccountWarningModal = () => { + return showConfirmModal({ + title: translate('closeAccountPage.closeAccountWarning'), + prompt: translate('closeAccountPage.closeAccountPermanentlyDeleteData'), + confirmText: translate('common.yesContinue'), + cancelText: translate('common.cancel'), + shouldShowCancelButton: true, + danger: true, + shouldDisableConfirmButtonWhenOffline: true, + }); + }; // If you are new to hooks this might look weird but basically it is something that only runs when the component unmounts // nothing runs on mount and we pass empty dependencies to prevent this from running on every re-render. @@ -39,18 +50,13 @@ function CloseAccountPage() { // here, we left this as is during refactor to limit the breaking changes. useEffect(() => () => clearError(), []); - const hideConfirmModal = () => { - setConfirmModalVisibility(false); - }; - - const onConfirm = () => { - closeAccount(reasonForLeaving); - hideConfirmModal(); - }; - - const showConfirmModal = (values: FormOnyxValues) => { - setConfirmModalVisibility(true); - setReasonForLeaving(values.reasonForLeaving); + const onSubmit = (values: FormOnyxValues) => { + showCloseAccountWarningModal().then((result) => { + if (result.action !== ModalActions.CONFIRM) { + return; + } + closeAccount(values.reasonForLeaving); + }); }; const userEmailOrPhone = session?.email ? formatPhoneNumber(session.email) : null; @@ -96,7 +102,7 @@ function CloseAccountPage() { - diff --git a/src/pages/settings/Security/SecuritySettingsPage.tsx b/src/pages/settings/Security/SecuritySettingsPage.tsx index 675a1cae4af30..bc569ced7c06f 100644 --- a/src/pages/settings/Security/SecuritySettingsPage.tsx +++ b/src/pages/settings/Security/SecuritySettingsPage.tsx @@ -3,13 +3,13 @@ import React, {useCallback, useEffect, useLayoutEffect, useMemo, useRef, useStat import type {RefObject} from 'react'; import {Dimensions, View} from 'react-native'; import type {GestureResponderEvent, StyleProp, ViewStyle} from 'react-native'; -import ConfirmModal from '@components/ConfirmModal'; import {useDelegateNoAccessActions, useDelegateNoAccessState} from '@components/DelegateNoAccessModalProvider'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import {useLockedAccountActions, useLockedAccountState} from '@components/LockedAccountModalProvider'; import MenuItem from '@components/MenuItem'; import type {MenuItemProps} from '@components/MenuItem'; import MenuItemList from '@components/MenuItemList'; +import {ModalActions} from '@components/Modal/Global/ModalContext'; import {usePersonalDetails} from '@components/OnyxListItemProvider'; import PopoverMenu from '@components/PopoverMenu'; import type {PopoverMenuItem} from '@components/PopoverMenu'; @@ -18,6 +18,7 @@ import ScrollView from '@components/ScrollView'; import Section from '@components/Section'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; +import useConfirmModal from '@hooks/useConfirmModal'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import {useMemoizedLazyExpensifyIcons, useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; @@ -83,10 +84,21 @@ function SecuritySettingsPage() { const delegateButtonRef = useRef(null); const [shouldShowDelegatePopoverMenu, setShouldShowDelegatePopoverMenu] = useState(false); - const [shouldShowRemoveDelegateModal, setShouldShowRemoveDelegateModal] = useState(false); const [selectedDelegate, setSelectedDelegate] = useState(); const [selectedEmail, setSelectedEmail] = useState(); + const {showConfirmModal} = useConfirmModal(); + const showRemoveCopilotModal = useCallback(() => { + return showConfirmModal({ + title: translate('delegate.removeCopilot'), + prompt: translate('delegate.removeCopilotConfirmation'), + confirmText: translate('delegate.removeCopilot'), + cancelText: translate('common.cancel'), + shouldShowCancelButton: true, + danger: true, + }); + }, [showConfirmModal, translate]); + const errorFields = account?.delegatedAccess?.errorFields ?? {}; const [anchorPosition, setAnchorPosition] = useState({ @@ -372,8 +384,19 @@ function SecuritySettingsPage() { } modalClose(() => { setShouldShowDelegatePopoverMenu(false); - setShouldShowRemoveDelegateModal(true); setSelectedEmail(undefined); + showRemoveCopilotModal().then((result) => { + if (result.action === ModalActions.CLOSE) { + setSelectedDelegate(undefined); + } else { + if (isActingAsDelegate) { + showDelegateNoAccessModal(); + return; + } + removeDelegate({email: selectedDelegate?.email ?? '', delegatedAccess: account?.delegatedAccess}); + setSelectedDelegate(undefined); + } + }); }); }, }, @@ -492,29 +515,6 @@ function SecuritySettingsPage() { setSelectedEmail(undefined); }} /> - { - if (isActingAsDelegate) { - setShouldShowRemoveDelegateModal(false); - showDelegateNoAccessModal(); - return; - } - removeDelegate({email: selectedDelegate?.email ?? '', delegatedAccess: account?.delegatedAccess}); - setShouldShowRemoveDelegateModal(false); - setSelectedDelegate(undefined); - }} - onCancel={() => { - setShouldShowRemoveDelegateModal(false); - setSelectedDelegate(undefined); - }} - confirmText={translate('delegate.removeCopilot')} - cancelText={translate('common.cancel')} - shouldShowCancelButton - /> diff --git a/src/pages/settings/Subscription/CardSection/CardSection.tsx b/src/pages/settings/Subscription/CardSection/CardSection.tsx index 98089779d7525..b97d0f45b19c2 100644 --- a/src/pages/settings/Subscription/CardSection/CardSection.tsx +++ b/src/pages/settings/Subscription/CardSection/CardSection.tsx @@ -1,11 +1,12 @@ import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {View} from 'react-native'; -import ConfirmModal from '@components/ConfirmModal'; import Icon from '@components/Icon'; import MenuItem from '@components/MenuItem'; +import {ModalActions} from '@components/Modal/Global/ModalContext'; import RenderHTML from '@components/RenderHTML'; import Section from '@components/Section'; import Text from '@components/Text'; +import useConfirmModal from '@hooks/useConfirmModal'; import useHasTeam2025Pricing from '@hooks/useHasTeam2025Pricing'; import {useMemoizedLazyExpensifyIcons, useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; @@ -41,7 +42,6 @@ import type {BillingStatusResult} from './utils'; import CardSectionUtils from './utils'; function CardSection() { - const [isRequestRefundModalVisible, setIsRequestRefundModalVisible] = useState(false); const {translate} = useLocalize(); const styles = useThemeStyles(); const theme = useTheme(); @@ -76,11 +76,25 @@ function CardSection() { const [ownerBillingGraceEndPeriod] = useOnyx(ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END); const requestRefund = useCallback(() => { requestRefundByUser(); - setIsRequestRefundModalVisible(false); Navigation.goBackToHome(); }, []); - const viewPurchases = useCallback(() => { + const {showConfirmModal} = useConfirmModal(); + const refundPrompt = ; + + const showRequestRefundModal = () => { + return showConfirmModal({ + title: translate('subscription.cardSection.requestRefund'), + prompt: refundPrompt, + confirmText: translate('subscription.cardSection.requestRefundModal.confirm'), + cancelText: translate('common.cancel'), + shouldShowCancelButton: true, + danger: true, + shouldHandleNavigationBack: false, + }); + }; + + const viewPurchases = () => { const query = buildQueryStringFromFilterFormValues({ type: CONST.SEARCH.DATA_TYPES.EXPENSE, status: CONST.SEARCH.STATUS.EXPENSE.ALL, @@ -89,7 +103,7 @@ function CardSection() { // rawQuery is needed to populate rawFilterList, which prevents useSuggestedSearchDefaultNavigation from auto-redirecting to actionable searches. Navigation.navigate(ROUTES.SEARCH_ROOT.getRoute({query, rawQuery: query})); - }, []); + }; const [billingStatus, setBillingStatus] = useState(() => CardSectionUtils.getBillingStatus({ @@ -118,6 +132,7 @@ function CardSection() { }); useEffect(() => { + // eslint-disable-next-line react-hooks/set-state-in-effect setBillingStatus( CardSectionUtils.getBillingStatus({ translate, @@ -196,109 +211,96 @@ function CardSection() { } return ( - <> -
- - {!isEmptyObject(defaultCard?.accountData) && ( - - - - {getPaymentMethodDescription(defaultCard?.accountType, defaultCard?.accountData, translate)} - - {translate( - 'subscription.cardSection.cardInfo', - defaultCard?.accountData?.addressName ?? '', - `${cardMonth} ${defaultCard?.accountData?.cardYear}`, - defaultCard?.accountData?.currency ?? '', - )} - - - +
+ + {!isEmptyObject(defaultCard?.accountData) && ( + + + + {getPaymentMethodDescription(defaultCard?.accountType, defaultCard?.accountData, translate)} + + {translate( + 'subscription.cardSection.cardInfo', + defaultCard?.accountData?.addressName ?? '', + `${cardMonth} ${defaultCard?.accountData?.cardYear}`, + defaultCard?.accountData?.currency ?? '', + )} + - )} - - - {isEmptyObject(defaultCard?.accountData) && } - {billingStatus?.isRetryAvailable !== undefined && ( - - )} - {hasCardAuthenticatedError(privateStripeCustomerID, amountOwed) && ( - - )} - - {!!account?.hasPurchases && ( - + + )} + - {!!(subscriptionPlan && account?.isEligibleForRefund) && ( - setIsRequestRefundModalVisible(true)} - sentryLabel={CONST.SENTRY_LABEL.SETTINGS_SUBSCRIPTION.REQUEST_REFUND} - /> - )} + {isEmptyObject(defaultCard?.accountData) && } + {billingStatus?.isRetryAvailable !== undefined && ( + + )} + {hasCardAuthenticatedError(privateStripeCustomerID, amountOwed) && ( + + )} - {!!(privateSubscription?.type === CONST.SUBSCRIPTION.TYPE.ANNUAL && account?.hasPurchases) && } -
+ {!!account?.hasPurchases && ( + + )} - {!!account?.isEligibleForRefund && ( - setIsRequestRefundModalVisible(false)} - prompt={ - - - - } - confirmText={translate('subscription.cardSection.requestRefundModal.confirm')} - cancelText={translate('common.cancel')} - danger + titleStyle={styles.textStrong} + disabled={isOffline} + onPress={async () => { + const result = await showRequestRefundModal(); + if (result.action !== ModalActions.CONFIRM) { + return; + } + requestRefund(); + }} + sentryLabel={CONST.SENTRY_LABEL.SETTINGS_SUBSCRIPTION.REQUEST_REFUND} /> )} - + + {!!(privateSubscription?.type === CONST.SUBSCRIPTION.TYPE.ANNUAL && account?.hasPurchases) && } +
); } diff --git a/src/pages/settings/Troubleshoot/TroubleshootPage.tsx b/src/pages/settings/Troubleshoot/TroubleshootPage.tsx index 714926376eaab..716cae2259b21 100644 --- a/src/pages/settings/Troubleshoot/TroubleshootPage.tsx +++ b/src/pages/settings/Troubleshoot/TroubleshootPage.tsx @@ -1,11 +1,11 @@ import {differenceInDays} from 'date-fns'; -import React, {useCallback, useEffect, useMemo, useState} from 'react'; +import React, {useCallback, useEffect, useState} from 'react'; import {View} from 'react-native'; -import ConfirmModal from '@components/ConfirmModal'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ImportOnyxState from '@components/ImportOnyxState'; import MenuItemList from '@components/MenuItemList'; +import {ModalActions} from '@components/Modal/Global/ModalContext'; import {useOptionsList} from '@components/OptionListContextProvider'; import RenderHTML from '@components/RenderHTML'; import ScreenWrapper from '@components/ScreenWrapper'; @@ -16,6 +16,7 @@ import SentryDebugToolMenu from '@components/SentryDebugToolMenu'; import Switch from '@components/Switch'; import TestToolMenu from '@components/TestToolMenu'; import TestToolRow from '@components/TestToolRow'; +import useConfirmModal from '@hooks/useConfirmModal'; import useEnvironment from '@hooks/useEnvironment'; import {useMemoizedLazyExpensifyIcons, useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; @@ -54,15 +55,30 @@ function TroubleshootPage() { const {translate} = useLocalize(); const styles = useThemeStyles(); const {isProduction, isDevelopment} = useEnvironment(); - const [isConfirmationModalVisible, setIsConfirmationModalVisible] = useState(false); const {shouldUseNarrowLayout} = useResponsiveLayout(); const [isLoading, setIsLoading] = useState(false); const [isTrackingGPS = false] = useOnyx(ONYXKEYS.GPS_DRAFT_DETAILS, {selector: isTrackingSelector}); const [shouldMaskOnyxState = true] = useOnyx(ONYXKEYS.SHOULD_MASK_ONYX_STATE); const {resetOptions} = useOptionsList({shouldInitialize: false}); const [tryNewDot] = useOnyx(ONYXKEYS.NVP_TRY_NEW_DOT); + const {showConfirmModal} = useConfirmModal(); const shouldOpenSurveyReasonPage = tryNewDot?.classicRedirect?.dismissed === false; const {setShouldResetSearchQuery} = useSearchActionsContext(); + const showResetAndRefreshModal = async () => { + const result = await showConfirmModal({ + title: translate('common.areYouSure'), + prompt: translate('initialSettingsPage.troubleshoot.confirmResetDescription'), + confirmText: translate('initialSettingsPage.troubleshoot.resetAndRefresh'), + cancelText: translate('common.cancel'), + shouldShowCancelButton: true, + }); + if (result.action !== ModalActions.CONFIRM) { + return; + } + resetOptions(); + setShouldResetSearchQuery(true); + clearOnyxAndResetApp(); + }; const exportOnyxState = useCallback(() => { ExportOnyxState.readFromOnyxDatabase().then((value: Record) => { const dataToShare = ExportOnyxState.maskOnyxState(value, shouldMaskOnyxState); @@ -70,7 +86,7 @@ function TroubleshootPage() { }); }, [shouldMaskOnyxState]); - const surveyCompletedWithinLastMonth = useMemo(() => { + const getSurveyCompletedWithinLastMonth = () => { const surveyThresholdInDays = 30; const {dismissedReasons} = tryNewDot?.classicRedirect ?? {}; if (dismissedReasons?.length === 0) { @@ -93,9 +109,10 @@ function TroubleshootPage() { const daysSinceLastSurvey = differenceInDays(new Date(), timestampToCheck); return daysSinceLastSurvey < surveyThresholdInDays; - }, [tryNewDot?.classicRedirect]); + }; + const surveyCompletedWithinLastMonth = getSurveyCompletedWithinLastMonth(); - const classicRedirectMenuItem: BaseMenuItem | null = useMemo(() => { + const getClassicRedirectMenuItem = (): BaseMenuItem | null => { if (tryNewDot?.classicRedirect?.isLockedToNewDot) { return null; } @@ -126,15 +143,16 @@ function TroubleshootPage() { }, }), }; - }, [tryNewDot?.classicRedirect?.isLockedToNewDot, icons.ExpensifyLogoNew, surveyCompletedWithinLastMonth, shouldOpenSurveyReasonPage, isTrackingGPS]); + }; + const classicRedirectMenuItem = getClassicRedirectMenuItem(); - const menuItems = useMemo(() => { + const getMenuItems = () => { const baseMenuItems: BaseMenuItem[] = [ { translationKey: 'initialSettingsPage.troubleshoot.clearCacheAndRestart', icon: icons.RotateLeft, sentryLabel: CONST.SENTRY_LABEL.SETTINGS_TROUBLESHOOT.CLEAR_CACHE, - action: () => setIsConfirmationModalVisible(true), + action: showResetAndRefreshModal, }, { translationKey: 'initialSettingsPage.troubleshoot.exportOnyxState', @@ -156,7 +174,8 @@ function TroubleshootPage() { sentryLabel: item.sentryLabel, })) .reverse(); - }, [icons.RotateLeft, icons.Download, exportOnyxState, classicRedirectMenuItem, translate, styles.sectionMenuItemTopDescription]); + }; + const menuItems = getMenuItems(); useEffect(() => { openTroubleshootSettingsPage(); @@ -221,21 +240,6 @@ function TroubleshootPage() { )} - - { - setIsConfirmationModalVisible(false); - resetOptions(); - setShouldResetSearchQuery(true); - clearOnyxAndResetApp(); - }} - onCancel={() => setIsConfirmationModalVisible(false)} - prompt={translate('initialSettingsPage.troubleshoot.confirmResetDescription')} - confirmText={translate('initialSettingsPage.troubleshoot.resetAndRefresh')} - cancelText={translate('common.cancel')} - />