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
17 changes: 13 additions & 4 deletions src/components/Modal/Global/DecisionModalWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@ import type {ModalProps} from './ModalContext';

import {ModalActions} from './ModalContext';

type DecisionModalWrapperProps = ModalProps & Omit<DecisionModalProps, 'onClose' | 'onSecondOptionSubmit' | 'onFirstOptionSubmit' | 'isVisible' | 'isSmallScreenWidth'>;
type DecisionModalWrapperProps = ModalProps &
Omit<DecisionModalProps, 'onClose' | 'onSecondOptionSubmit' | 'onFirstOptionSubmit' | 'isVisible' | 'isSmallScreenWidth'> & {
/** Whether dismissing the modal (backdrop, Escape, or Back) resolves with `ModalActions.DISMISS` instead of the second option's `ModalActions.CLOSE` */
shouldReportDismiss?: boolean;
};

function DecisionModalWrapper({closeModal, onModalHide, ...props}: DecisionModalWrapperProps) {
function DecisionModalWrapper({closeModal, onModalHide, shouldReportDismiss = false, ...props}: DecisionModalWrapperProps) {
const [isVisible, setIsVisible] = useState(true);
const [closeAction, setCloseAction] = useState<typeof ModalActions.CONFIRM | typeof ModalActions.CLOSE>(ModalActions.CLOSE);
const [closeAction, setCloseAction] = useState<typeof ModalActions.CONFIRM | typeof ModalActions.CLOSE | typeof ModalActions.DISMISS>(ModalActions.CLOSE);
// We need to use isSmallScreenWidth here because the DecisionModal breaks in RHP with shouldUseNarrowLayout.
// eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth
const {isSmallScreenWidth} = useResponsiveLayout();
Expand All @@ -28,6 +32,11 @@ function DecisionModalWrapper({closeModal, onModalHide, ...props}: DecisionModal
setIsVisible(false);
};

const handleDismiss = () => {
setCloseAction(shouldReportDismiss ? ModalActions.DISMISS : ModalActions.CLOSE);
setIsVisible(false);
};

const handleModalHide = () => {
if (isVisible) {
return;
Expand All @@ -45,7 +54,7 @@ function DecisionModalWrapper({closeModal, onModalHide, ...props}: DecisionModal
isSmallScreenWidth={isSmallScreenWidth}
onFirstOptionSubmit={handleFirstOption}
onSecondOptionSubmit={handleSecondOption}
onClose={handleSecondOption}
onClose={handleDismiss}
onModalHide={handleModalHide}
/>
);
Expand Down
1 change: 1 addition & 0 deletions src/components/Modal/Global/ModalContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import React, {useContext, useEffect, useRef, useState} from 'react';
const ModalActions = {
CONFIRM: 'CONFIRM',
CLOSE: 'CLOSE',
DISMISS: 'DISMISS',
} as const;

type ModalAction = (typeof ModalActions)[keyof typeof ModalActions];
Expand Down
7 changes: 4 additions & 3 deletions src/hooks/useAssignCard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,11 @@ type UseAssignCardProps = {
/** The ID of the workspace/policy */
policyID: string;

setShouldShowOfflineModal: (shouldShow: boolean) => void;
/** Called when the user tries to assign a card from a direct feed while offline */
onOfflineAssign: () => void;
};

function useAssignCard({feedName, policyID, setShouldShowOfflineModal}: UseAssignCardProps) {
function useAssignCard({feedName, policyID, onOfflineAssign}: UseAssignCardProps) {
const [cardFeeds] = useCardFeeds(policyID);
const companyFeeds = getCompanyFeeds(cardFeeds);
const currentFeedData = feedName ? companyFeeds?.[feedName] : ({} as CombinedCardFeed);
Expand Down Expand Up @@ -70,7 +71,7 @@ function useAssignCard({feedName, policyID, setShouldShowOfflineModal}: UseAssig
// If the feed is a direct feed (not a commercial feed) and the user is offline,
// show the offline alert modal to inform them of the connectivity issue.
if (!isCommercialFeed && isOffline) {
setShouldShowOfflineModal(true);
onOfflineAssign();
return;
}

Expand Down
244 changes: 111 additions & 133 deletions src/pages/domain/Members/DomainMemberDetailsPage.tsx

Large diffs are not rendered by default.

100 changes: 39 additions & 61 deletions src/pages/domain/Members/DomainMembersPage.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import Button from '@components/Button';
import ButtonWithDropdownMenu from '@components/ButtonWithDropdownMenu';
import type {DomainMemberBulkActionType, DropdownOption} from '@components/ButtonWithDropdownMenu/types';
import DecisionModal from '@components/DecisionModal';
import type {FeatureListItem} from '@components/FeatureList';
import FeatureList from '@components/FeatureList';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
Expand All @@ -14,6 +13,7 @@ import type {DomainMemberRowData} from '@components/Tables/DomainMembersTable';
import useCleanupSelectedOptions from '@hooks/useCleanupSelectedOptions';
import useClearSelectedDomainMembersOnMoveComplete from '@hooks/useClearSelectedDomainMembersOnMoveComplete';
import useConfirmModal from '@hooks/useConfirmModal';
import useDecisionModal from '@hooks/useDecisionModal';
import useDomainDocumentTitle from '@hooks/useDomainDocumentTitle';
import useDomainGroupFilter from '@hooks/useDomainGroupFilter';
import {useMemoizedLazyExpensifyIcons, useMemoizedLazyIllustrations} from '@hooks/useLazyAsset';
Expand Down Expand Up @@ -73,16 +73,12 @@ function DomainMembersPage({route}: DomainMembersPageProps) {

const [domainErrors] = useOnyx(`${ONYXKEYS.COLLECTION.DOMAIN_ERRORS}${domainAccountID}`);
const [domainPendingActions] = useOnyx(`${ONYXKEYS.COLLECTION.DOMAIN_PENDING_ACTIONS}${domainAccountID}`, {selector: memberPendingActionSelector});
const [isModalVisible, setIsModalVisible] = useState(false);
const [shouldForceCloseAccount, setShouldForceCloseAccount] = useState<boolean>();
const {showConfirmModal} = useConfirmModal();
const {showDecisionModal} = useDecisionModal();
const [personalDetails] = useAllPersonalDetails();
const [domain] = useOnyx(`${ONYXKEYS.COLLECTION.DOMAIN}${domainAccountID}`);
const [domainName] = useOnyx(`${ONYXKEYS.COLLECTION.DOMAIN}${domainAccountID}`, {selector: domainNameSelector});
useDomainDocumentTitle(domainName, 'domain.domainMembers');
// We need to use isSmallScreenWidth here because the DecisionModal is opening from RHP and ShouldUseNarrowLayout layout will not work in this place.
// eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth
const {isSmallScreenWidth} = useResponsiveLayout();

const [memberIDs] = useOnyx(`${ONYXKEYS.COLLECTION.DOMAIN}${domainAccountID}`, {
selector: memberAccountIDsSelector,
Expand Down Expand Up @@ -140,21 +136,22 @@ function DomainMembersPage({route}: DomainMembersPageProps) {
onNavigationCallBack: () => Navigation.goBack(),
});

const handleForceCloseAccount = () => {
setShouldForceCloseAccount(true);
setIsModalVisible(false);
};

const handleSafeCloseAccount = () => {
setShouldForceCloseAccount(false);
setIsModalVisible(false);
};
const showCloseAccountModal = async () => {
const decision = await showDecisionModal({
title: translate('domain.members.closeAccount', {count: selectedMembers.length}),
prompt: translate('domain.members.closeAccountInfo', {count: selectedMembers.length}),
secondOptionText: translate('domain.members.safeCloseAccount', {count: selectedMembers.length}),
firstOptionText: translate('domain.members.forceCloseAccount', {count: selectedMembers.length}),
isFirstOptionDanger: true,
isSecondOptionSuccess: true,
shouldReportDismiss: true,
});

const handleCloseAccount = async () => {
if (shouldForceCloseAccount === undefined) {
if (decision.action === ModalActions.DISMISS) {
return;
}

const shouldForceCloseAccount = decision.action === ModalActions.CONFIRM;
const result = await showConfirmModal({
title: translate('domain.members.closeAccount', {count: selectedMembers.length}),
prompt: translate('domain.members.closeAccountPrompt'),
Expand All @@ -165,8 +162,7 @@ function DomainMembersPage({route}: DomainMembersPageProps) {
});

if (result.action !== ModalActions.CONFIRM) {
setIsModalVisible(true);
setShouldForceCloseAccount(undefined);
showCloseAccountModal();
return;
}

Expand All @@ -180,9 +176,7 @@ function DomainMembersPage({route}: DomainMembersPageProps) {
closeUserAccount(domainAccountID, domainName, memberLogin, accountID, securityGroupData, shouldForceCloseAccount);
}

setShouldForceCloseAccount(undefined);
clearSelectedMembers();
setIsModalVisible(false);
};

const getBulkActionsButtonOptions: () => Array<DropdownOption<DomainMemberBulkActionType>> = () => [
Expand All @@ -191,7 +185,9 @@ function DomainMembersPage({route}: DomainMembersPageProps) {
value: CONST.DOMAIN.MEMBERS.BULK_ACTION_TYPES.CLOSE_ACCOUNT,
icon: icons.RemoveMembers,
shouldSkipFocusRestore: true,
onSelected: () => setIsModalVisible(true),
onSelected: () => {
showCloseAccountModal();
},
},
{
text: translate('domain.members.moveToGroup'),
Expand Down Expand Up @@ -334,45 +330,27 @@ function DomainMembersPage({route}: DomainMembersPageProps) {
}

return (
<>
<BaseDomainMembersPage
domainAccountID={domainAccountID}
members={members}
headerTitle={translate('domain.members.title')}
headerContent={getHeaderButtons()}
selectedMembers={selectedMembers}
setSelectedMembers={setSelectedMembers}
useSelectionModeHeader={selectionModeHeader}
filterConfig={filterConfig}
isItemInFilter={isItemInFilter}
shouldShowGroupFilter={shouldShowGroupFilter}
shouldShowGroupColumn={shouldShowGroupColumn}
onBackButtonPress={() => {
if (isMobileSelectionModeEnabled) {
clearSelectedMembers();
turnOffMobileSelectionMode();
return;
}
Navigation.popToSidebar();
}}
/>
<DecisionModal
title={translate('domain.members.closeAccount', {count: selectedMembers.length})}
prompt={translate('domain.members.closeAccountInfo', {count: selectedMembers.length})}
isSmallScreenWidth={isSmallScreenWidth}
onFirstOptionSubmit={handleForceCloseAccount}
onSecondOptionSubmit={handleSafeCloseAccount}
secondOptionText={translate('domain.members.safeCloseAccount', {count: selectedMembers.length})}
firstOptionText={translate('domain.members.forceCloseAccount', {count: selectedMembers.length})}
isVisible={isModalVisible}
onClose={() => setIsModalVisible(false)}
onModalHide={() => {
handleCloseAccount();
}}
isFirstOptionDanger
isSecondOptionSuccess
/>
</>
<BaseDomainMembersPage
domainAccountID={domainAccountID}
members={members}
headerTitle={translate('domain.members.title')}
headerContent={getHeaderButtons()}
selectedMembers={selectedMembers}
setSelectedMembers={setSelectedMembers}
useSelectionModeHeader={selectionModeHeader}
filterConfig={filterConfig}
isItemInFilter={isItemInFilter}
shouldShowGroupFilter={shouldShowGroupFilter}
shouldShowGroupColumn={shouldShowGroupColumn}
onBackButtonPress={() => {
if (isMobileSelectionModeEnabled) {
clearSelectedMembers();
turnOffMobileSelectionMode();
return;
}
Navigation.popToSidebar();
}}
/>
);
}

Expand Down
25 changes: 11 additions & 14 deletions src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import DecisionModal from '@components/DecisionModal';
import WorkspaceCompanyCardsTable from '@components/Tables/WorkspaceCompanyCardsTable';
import type {WorkspaceCompanyCardsTableHandle} from '@components/Tables/WorkspaceCompanyCardsTable';

import useAssignCard from '@hooks/useAssignCard';
import useCompanyCards from '@hooks/useCompanyCards';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useDecisionModal from '@hooks/useDecisionModal';
import useLocalize from '@hooks/useLocalize';
import useMobileSelectionMode from '@hooks/useMobileSelectionMode';
import useNetwork from '@hooks/useNetwork';
Expand Down Expand Up @@ -32,7 +32,7 @@ import ONYXKEYS from '@src/ONYXKEYS';
import type SCREENS from '@src/SCREENS';
import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue';

import React, {useEffect, useEffectEvent, useRef, useState} from 'react';
import React, {useEffect, useEffectEvent, useRef} from 'react';

type WorkspaceCompanyCardsPageProps = PlatformStackScreenProps<WorkspaceSplitNavigatorParamList, typeof SCREENS.WORKSPACE.COMPANY_CARDS>;

Expand Down Expand Up @@ -133,8 +133,15 @@ function WorkspaceCompanyCardsPage({route}: WorkspaceCompanyCardsPageProps) {
loadPolicyCompanyCardsFeed();
}, [loadPolicyCompanyCardsFeed]);

const [shouldShowOfflineModal, setShouldShowOfflineModal] = useState(false);
const {assignCard, isAssigningCardDisabled} = useAssignCard({feedName, policyID, setShouldShowOfflineModal});
const {showDecisionModal} = useDecisionModal();
const showOfflineModal = () => {
showDecisionModal({
title: translate('common.youAppearToBeOffline'),
prompt: translate('common.offlinePrompt'),
secondOptionText: translate('common.buttonConfirm'),
});
};
const {assignCard, isAssigningCardDisabled} = useAssignCard({feedName, policyID, onOfflineAssign: showOfflineModal});
const canWriteCompanyCards = canMemberWrite(policy, currentUserLogin, CONST.POLICY.POLICY_FEATURE.COMPANY_CARDS);
const isSelectionModeEnabled = isMobileSelectionModeEnabled && shouldUseNarrowLayout;

Expand Down Expand Up @@ -179,16 +186,6 @@ function WorkspaceCompanyCardsPage({route}: WorkspaceCompanyCardsPageProps) {
onReloadFeed={loadPolicyCompanyCardsFeed}
/>
</WorkspacePageWithSections>

<DecisionModal
title={translate('common.youAppearToBeOffline')}
prompt={translate('common.offlinePrompt')}
isSmallScreenWidth={shouldUseNarrowLayout}
onSecondOptionSubmit={() => setShouldShowOfflineModal(false)}
secondOptionText={translate('common.buttonConfirm')}
isVisible={shouldShowOfflineModal}
onClose={() => setShouldShowOfflineModal(false)}
/>
</AccessOrNotFoundWrapper>
);
}
Expand Down
Loading
Loading