Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 44 additions & 48 deletions src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {useDelegateNoAccessActions, useDelegateNoAccessState} 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';
Expand Down Expand Up @@ -67,7 +68,6 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) {
const themeStyles = useThemeStyles();
const icons = useMemoizedLazyExpensifyIcons(['Star', 'Trashcan']);

const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const validateCodeFormRef = useRef<ValidateCodeFormHandle>(null);
const backTo = route.params.backTo;

Expand Down Expand Up @@ -121,29 +121,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
Expand All @@ -169,17 +146,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 <FullscreenLoadingIndicator />;
Expand All @@ -205,25 +221,6 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) {
const isFailedRemovedContactMethod = !!loginData.errorFields?.deletedLogin;
const shouldSkipInitialValidation = route.params?.shouldSkipInitialValidation === 'true';

const getDeleteConfirmationModal = () => (
<ConfirmModal
title={translate('contacts.removeContactMethod')}
onConfirm={confirmDeleteAndHideModal}
onCancel={() => 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 ? (
Expand Down Expand Up @@ -263,7 +260,7 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) {
showDelegateNoAccessModal();
return;
}
toggleDeleteModal(true);
turnOnDeleteModal();
}}
/>
</OfflineWithFeedback>
Expand Down Expand Up @@ -356,7 +353,6 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) {
)}

{!isValidateCodeFormVisible && !!loginData.validatedDate && getMenuItems()}
{getDeleteConfirmationModal()}
</ScrollView>
</ScreenWrapper>
);
Expand Down
75 changes: 39 additions & 36 deletions src/pages/settings/Profile/CustomStatus/VacationDelegatePage.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React, {useCallback, useState} from 'react';
import React, {useCallback} from 'react';
import BaseVacationDelegateSelectionComponent from '@components/BaseVacationDelegateSelectionComponent';
import ConfirmModal from '@components/ConfirmModal';
import {ModalActions} from '@components/Modal/Global/ModalContext';
import ScreenWrapper from '@components/ScreenWrapper';
import useConfirmModal from '@hooks/useConfirmModal';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
Expand All @@ -15,12 +16,32 @@ import type {Participant} from '@src/types/onyx/IOU';

function VacationDelegatePage() {
const {translate} = useLocalize();
const [isWarningModalVisible, setIsWarningModalVisible] = useState(false);
const [newVacationDelegate, setNewVacationDelegate] = useState('');
const {login: currentUserLogin = ''} = useCurrentUserPersonalDetails();
const {showConfirmModal} = useConfirmModal();

const [vacationDelegate] = useOnyx(ONYXKEYS.NVP_PRIVATE_VACATION_DELEGATE, {canBeMissing: true});

const showWarningModal = useCallback(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NAB this component compiles with React Compiler, so useCallback isn't needed

async (delegateLogin: string) => {
const result = await showConfirmModal({
title: translate('common.headsUp'),
prompt: translate('statusPage.vacationDelegateWarning', {nameOrEmail: getPersonalDetailByEmail(delegateLogin)?.displayName ?? delegateLogin}),
confirmText: translate('common.confirm'),
cancelText: translate('common.cancel'),
shouldShowCancelButton: true,
});

if (result.action === ModalActions.CONFIRM) {
await setVacationDelegate(currentUserLogin, delegateLogin, true, vacationDelegate?.delegate);
Navigation.goBack(ROUTES.SETTINGS_STATUS);
return;
}

clearVacationDelegateError(vacationDelegate?.previousDelegate);
},
[showConfirmModal, translate, currentUserLogin, vacationDelegate?.previousDelegate, vacationDelegate?.delegate],
);

const onSelectRow = useCallback(
(option: Participant) => {
if (option?.login === vacationDelegate?.delegate) {
Expand All @@ -36,48 +57,30 @@ function VacationDelegatePage() {
}

if (response.jsonCode === CONST.JSON_CODE.POLICY_DIFF_WARNING) {
setIsWarningModalVisible(true);
setNewVacationDelegate(option?.login ?? '');
showWarningModal(option?.login ?? '');
return;
}

Navigation.goBack(ROUTES.SETTINGS_STATUS);
});
},
[currentUserLogin, vacationDelegate],
[currentUserLogin, vacationDelegate, showWarningModal],
);

return (
<>
<ScreenWrapper
includeSafeAreaPaddingBottom={false}
testID="VacationDelegatePage"
>
<BaseVacationDelegateSelectionComponent
vacationDelegate={vacationDelegate}
onSelectRow={onSelectRow}
headerTitle={translate('common.vacationDelegate')}
onBackButtonPress={() => Navigation.goBack(ROUTES.SETTINGS_STATUS)}
cannotSetDelegateMessage={translate('statusPage.cannotSetVacationDelegate')}
includeCurrentUser={false}
/>
</ScreenWrapper>
<ConfirmModal
isVisible={isWarningModalVisible}
title={translate('common.headsUp')}
prompt={translate('statusPage.vacationDelegateWarning', {nameOrEmail: getPersonalDetailByEmail(newVacationDelegate)?.displayName ?? newVacationDelegate})}
onConfirm={() => {
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')}
<ScreenWrapper
includeSafeAreaPaddingBottom={false}
testID="VacationDelegatePage"
>
<BaseVacationDelegateSelectionComponent
vacationDelegate={vacationDelegate}
onSelectRow={onSelectRow}
headerTitle={translate('common.vacationDelegate')}
onBackButtonPress={() => Navigation.goBack(ROUTES.SETTINGS_STATUS)}
cannotSetDelegateMessage={translate('statusPage.cannotSetVacationDelegate')}
includeCurrentUser={false}
/>
</>
</ScreenWrapper>
);
}

Expand Down
74 changes: 30 additions & 44 deletions src/pages/settings/Report/VisibilityPage.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import {useRoute} from '@react-navigation/native';
import React, {useCallback, useMemo, useRef, useState} from 'react';
import React, {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 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';
Expand All @@ -22,12 +24,12 @@ type VisibilityProps = WithReportOrNotFoundProps & PlatformStackScreenProps<Repo

function VisibilityPage({report}: VisibilityProps) {
const route = useRoute<PlatformStackRouteProp<ReportSettingsNavigatorParamList, typeof SCREENS.REPORT_SETTINGS.VISIBILITY>>();
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)
Expand All @@ -42,28 +44,32 @@ 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);
if (showConfirmModal) {
shouldGoBackToDetailsPage.current = true;
} else {
goBack();
}
},
[report, showConfirmModal, goBack],
);
const changeVisibility = (newVisibility: RoomVisibility) => {
if (!report) {
return;
}
updateRoomVisibility(report.reportID, report.visibility, newVisibility);
setNavigationActionToMicrotaskQueue(goBack);
};

const hideModal = useCallback(() => {
setShowConfirmModal(false);
}, []);
const showPublicVisibilityModal = async () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ CONSISTENCY-1 (docs)

The changeVisibility callback has showConfirmModal in its dependency array, but this function is now the hook function itself, not a boolean state variable. Since showConfirmModal is a stable function from the hook, it should not be in the dependency array, or this line needs to be updated.

Looking at the old code, showConfirmModal was a boolean state that determined whether to navigate back. The new refactored code should remove showConfirmModal from the dependency array since it's now a stable function reference.

Suggested fix:

const changeVisibility = useCallback(
    (newVisibility: RoomVisibility) => {
        if (!report) {
            return;
        }
        updateRoomVisibility(report.reportID, report.visibility, newVisibility);
        goBack();
    },
    [report, goBack],
);

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 (
<ScreenWrapper
Expand All @@ -80,7 +86,7 @@ function VisibilityPage({report}: VisibilityProps) {
data={visibilityOptions}
onSelectRow={(option) => {
if (option.value === CONST.REPORT.VISIBILITY.PUBLIC) {
setShowConfirmModal(true);
showPublicVisibilityModal();
return;
}
changeVisibility(option.value);
Expand All @@ -89,26 +95,6 @@ function VisibilityPage({report}: VisibilityProps) {
initiallyFocusedItemKey={visibilityOptions.find((visibility) => visibility.isSelected)?.keyForList}
ListItem={RadioListItem}
/>
<ConfirmModal
isVisible={showConfirmModal}
onConfirm={() => {
changeVisibility(CONST.REPORT.VISIBILITY.PUBLIC);
hideModal();
}}
onModalHide={() => {
if (!shouldGoBackToDetailsPage.current) {
return;
}
Comment thread
truph01 marked this conversation as resolved.
shouldGoBackToDetailsPage.current = false;
goBack();
}}
onCancel={hideModal}
title={translate('common.areYouSure')}
prompt={translate('newRoomPage.publicDescription')}
confirmText={translate('common.yes')}
cancelText={translate('common.no')}
danger
/>
</FullPageNotFoundView>
</ScreenWrapper>
);
Expand Down
Loading
Loading