diff --git a/src/components/DecisionModal.tsx b/src/components/DecisionModal.tsx
index 77a291c1c2efa..8cf23cb6d2b29 100644
--- a/src/components/DecisionModal.tsx
+++ b/src/components/DecisionModal.tsx
@@ -5,7 +5,8 @@ import CONST from '@src/CONST';
import Button from './Button';
import Header from './Header';
import Modal from './Modal';
-import Text from './Text';
+import RenderHTML from './RenderHTML';
+import ScrollView from './ScrollView';
type DecisionModalProps = {
/** Title describing purpose of modal */
@@ -20,6 +21,18 @@ type DecisionModalProps = {
/** Text content used in second button */
secondOptionText: string;
+ /** Whether the first option uses a success-themed button */
+ isFirstOptionSuccess?: boolean;
+
+ /** Whether the second option uses a success-themed button */
+ isSecondOptionSuccess?: boolean;
+
+ /** Whether the first option uses a danger-themed button */
+ isFirstOptionDanger?: boolean;
+
+ /** Whether the second option uses a danger-themed button */
+ isSecondOptionDanger?: boolean;
+
/** onSubmit callback fired after clicking on first button */
onFirstOptionSubmit?: () => void;
@@ -32,11 +45,29 @@ type DecisionModalProps = {
/** Callback for closing modal */
onClose: () => void;
+ /** Callback when modal has fully disappeared */
+ onModalHide?: () => void;
+
/** Whether modal is visible */
isVisible: boolean;
};
-function DecisionModal({title, prompt = '', firstOptionText, secondOptionText, onFirstOptionSubmit, onSecondOptionSubmit, isSmallScreenWidth, onClose, isVisible}: DecisionModalProps) {
+function DecisionModal({
+ title,
+ prompt = '',
+ firstOptionText,
+ secondOptionText,
+ onFirstOptionSubmit,
+ onSecondOptionSubmit,
+ isSmallScreenWidth,
+ onClose,
+ onModalHide,
+ isVisible,
+ isFirstOptionDanger = false,
+ isFirstOptionSuccess = true,
+ isSecondOptionSuccess = false,
+ isSecondOptionDanger = false,
+}: DecisionModalProps) {
const styles = useThemeStyles();
return (
@@ -45,21 +76,23 @@ function DecisionModal({title, prompt = '', firstOptionText, secondOptionText, o
isVisible={isVisible}
type={isSmallScreenWidth ? CONST.MODAL.MODAL_TYPE.BOTTOM_DOCKED : CONST.MODAL.MODAL_TYPE.CONFIRM}
innerContainerStyle={styles.pv0}
+ onModalHide={onModalHide}
>
-
+
- {prompt}
+
{!!firstOptionText && (
-
+
);
}
diff --git a/src/languages/de.ts b/src/languages/de.ts
index ceba918031952..80d03a82eb5b3 100644
--- a/src/languages/de.ts
+++ b/src/languages/de.ts
@@ -8306,7 +8306,14 @@ Hier ist ein *Testbeleg*, um dir zu zeigen, wie es funktioniert:`,
findMember: 'Mitglied finden',
addMember: 'Mitglied hinzufügen',
email: 'E-Mail-Adresse',
- errors: {
+ closeAccount: 'Konto schließen',
+ closeAccountPrompt: 'Bist du sicher? Diese Aktion ist dauerhaft.',
+ forceCloseAccount: 'Konto zwangsweise schließen',
+ safeCloseAccount: 'Konto sicher schließen',
+ closeAccountInfo:
+ 'Wir empfehlen, das Konto sicher zu schließen, um das Schließen zu überspringen, falls Folgendes vorliegt:
Ausstehende Genehmigungen
Aktive Erstattungen
Keine alternativen Anmeldemethoden
Andernfalls können Sie die oben genannten Sicherheitsvorkehrungen ignorieren und das ausgewählte Konto zwangsweise schließen.',
+ error: {
+ removeMember: 'Dieser Benutzer kann nicht entfernt werden. Bitte versuche es erneut.',
addMember: 'Dieses Mitglied kann nicht hinzugefügt werden. Bitte versuche es erneut.',
},
},
diff --git a/src/languages/en.ts b/src/languages/en.ts
index c5c091039372a..f055552c341df 100644
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -8221,8 +8221,15 @@ const translations = {
findMember: 'Find member',
addMember: 'Add member',
email: 'Email address',
- errors: {
+ closeAccount: 'Close account',
+ closeAccountPrompt: 'Are you sure? This action is permanent.',
+ forceCloseAccount: 'Force close account',
+ safeCloseAccount: 'Close account safely',
+ closeAccountInfo:
+ 'We recommend closing the account safely to skip closing it in case there are:
Pending approvals
Active reimbursements
No alternative login methods
Otherwise, you can ignore the safety precautions above and force close the selected account.',
+ error: {
addMember: 'Unable to add this member. Please try again.',
+ removeMember: 'Unable to remove this user. Please try again.',
},
},
},
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 790dcfd88ee14..90f4cad056961 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -8376,7 +8376,14 @@ ${amount} para ${merchant} - ${date}`,
findMember: 'Buscar miembro',
addMember: 'Añadir miembro',
email: 'Dirección de correo electrónico',
- errors: {
+ closeAccount: 'Cerrar cuenta',
+ closeAccountPrompt: '¿Estás seguro? Esta acción es permanente.',
+ forceCloseAccount: 'Forzar cierre de cuenta',
+ safeCloseAccount: 'Cierre seguro de cuenta',
+ closeAccountInfo:
+ 'Recomendamos cerrar la cuenta de forma segura para omitir el cierre si hay:
Aprobaciones pendientes
Reembolsos en curso
No hay otro método de inicio de sesión
De lo contrario, puedes ignorar las precauciones de seguridad anteriores y forzar el cierre de las cuentas seleccionadas.',
+ error: {
+ removeMember: 'No se pudo eliminar a este usuario. Por favor, inténtalo de nuevo.',
addMember: 'No se pudo añadir este miembro. Por favor, inténtalo de nuevo.',
},
},
diff --git a/src/languages/fr.ts b/src/languages/fr.ts
index 4c6e483d58d4e..96d7ca4b7ba0a 100644
--- a/src/languages/fr.ts
+++ b/src/languages/fr.ts
@@ -8320,7 +8320,14 @@ Voici un *reçu test* pour vous montrer comment ça fonctionne :`,
findMember: 'Trouver un membre',
addMember: 'Ajouter un membre',
email: 'Adresse e-mail',
- errors: {
+ closeAccount: 'Fermer le compte',
+ closeAccountPrompt: 'Êtes-vous sûr(e) ? Cette action est définitive.',
+ forceCloseAccount: 'Forcer la fermeture du compte',
+ safeCloseAccount: 'Fermer le compte en toute sécurité',
+ closeAccountInfo:
+ 'Nous recommandons de fermer le compte en toute sécurité afin d’éviter de le fermer dans le cas où il y aurait :
Des validations en attente
Des remboursements actifs
Aucune autre méthode de connexion
Sinon, vous pouvez ignorer les précautions de sécurité ci-dessus et forcer la fermeture du compte sélectionné.',
+ error: {
+ removeMember: 'Impossible de supprimer cet utilisateur. Veuillez réessayer.',
addMember: 'Impossible d’ajouter ce membre. Veuillez réessayer.',
},
},
diff --git a/src/languages/it.ts b/src/languages/it.ts
index 40016d0dcdee3..208a171f1f497 100644
--- a/src/languages/it.ts
+++ b/src/languages/it.ts
@@ -8287,7 +8287,14 @@ Ecco una *ricevuta di prova* per mostrarti come funziona:`,
findMember: 'Trova membro',
addMember: 'Aggiungi membro',
email: 'Indirizzo email',
- errors: {
+ closeAccount: 'Chiudi account',
+ closeAccountPrompt: 'Sei sicuro? Questa azione è permanente.',
+ forceCloseAccount: 'Forza chiusura account',
+ safeCloseAccount: 'Chiudi il conto in sicurezza',
+ closeAccountInfo:
+ 'Consigliamo di chiudere l’account in modo sicuro per evitare problemi in caso di:
Approvazioni in sospeso
Rimborsi attivi
Nessun metodo di accesso alternativo
In caso contrario, puoi ignorare le precauzioni di sicurezza sopra indicate e forzare la chiusura dell’account selezionato.',
+ error: {
+ removeMember: 'Impossibile rimuovere questo utente. Riprova.',
addMember: 'Impossibile aggiungere questo membro. Riprova.',
},
},
diff --git a/src/languages/ja.ts b/src/languages/ja.ts
index 6c534f1014af0..d2ee55bc81f9c 100644
--- a/src/languages/ja.ts
+++ b/src/languages/ja.ts
@@ -8218,8 +8218,15 @@ ${reportName}
findMember: 'メンバーを検索',
addMember: 'メンバーを追加',
email: 'メールアドレス',
- errors: {
- addMember: 'このメンバーを追加できません。もう一度お試しください。',
+ closeAccount: 'アカウントを閉じる',
+ closeAccountPrompt: '本当によろしいですか?この操作は元に戻せません。',
+ forceCloseAccount: 'アカウントを強制的に閉鎖',
+ safeCloseAccount: 'アカウントを安全に閉じる',
+ closeAccountInfo:
+ '保留中の承認、処理中の精算、代替ログイン方法がない場合などでもアカウントを閉鎖できるように、安全にアカウントを閉鎖することを推奨します:
保留中の承認
進行中の払い戻し
代替ログイン方法なし
それ以外の場合は、上記の安全上の注意を無視して、選択したアカウントを強制的に閉鎖できます。',
+ error: {
+ removeMember: 'このユーザーを削除できません。もう一度お試しください。',
+ addMember: 'このメンバーを追加できませんでした。もう一度お試しください。',
},
},
},
diff --git a/src/languages/nl.ts b/src/languages/nl.ts
index 689dd98a2bde7..d920fa997a4f5 100644
--- a/src/languages/nl.ts
+++ b/src/languages/nl.ts
@@ -8274,7 +8274,14 @@ Hier is een *proefbon* om je te laten zien hoe het werkt:`,
findMember: 'Lid zoeken',
addMember: 'Lid toevoegen',
email: 'E-mailadres',
- errors: {
+ closeAccount: 'Account sluiten',
+ closeAccountPrompt: 'Weet je het zeker? Deze actie is permanent.',
+ forceCloseAccount: 'Account geforceerd sluiten',
+ safeCloseAccount: 'Account veilig sluiten',
+ closeAccountInfo:
+ 'We raden aan om de account veilig te sluiten om te vermijden dat je deze moet sluiten als er zijn:
Openstaande goedkeuringen
Actieve terugbetalingen
Geen alternatieve inlogmethoden
Anders kun je de bovenstaande veiligheidsmaatregelen negeren en de geselecteerde account geforceerd sluiten.',
+ error: {
+ removeMember: 'Kan deze gebruiker niet verwijderen. Probeer het opnieuw.',
addMember: 'Kan dit lid niet toevoegen. Probeer het opnieuw.',
},
},
diff --git a/src/languages/pl.ts b/src/languages/pl.ts
index edc0984c2f0f4..1880a1a0bf39a 100644
--- a/src/languages/pl.ts
+++ b/src/languages/pl.ts
@@ -8254,7 +8254,14 @@ Oto *paragon testowy*, żeby pokazać Ci, jak to działa:`,
findMember: 'Znajdź członka',
addMember: 'Dodaj członka',
email: 'Adres e-mail',
- errors: {
+ closeAccount: 'Zamknij konto',
+ closeAccountPrompt: 'Czy na pewno? Ta czynność jest nieodwracalna.',
+ forceCloseAccount: 'Wymuś zamknięcie konta',
+ safeCloseAccount: 'Zamknij konto bezpiecznie',
+ closeAccountInfo:
+ 'Zalecamy bezpieczne zamknięcie konta, aby pominąć jego zamykanie w przypadku, gdy występują:
Oczekujące zatwierdzenia
Aktywne zwroty kosztów
Brak alternatywnych metod logowania
W przeciwnym razie możesz zignorować powyższe środki ostrożności i wymusić zamknięcie wybranego konta.',
+ error: {
+ removeMember: 'Nie można usunąć tego użytkownika. Spróbuj ponownie.',
addMember: 'Nie można dodać tego członka. Spróbuj ponownie.',
},
},
diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts
index 62a696d2c3d87..a184302c7acba 100644
--- a/src/languages/pt-BR.ts
+++ b/src/languages/pt-BR.ts
@@ -8255,7 +8255,14 @@ Aqui está um *comprovante de teste* para mostrar como funciona:`,
findMember: 'Encontrar membro',
addMember: 'Adicionar membro',
email: 'Endereço de e-mail',
- errors: {
+ closeAccount: 'Encerrar conta',
+ closeAccountPrompt: 'Tem certeza? Esta ação é permanente.',
+ forceCloseAccount: 'Forçar encerramento da conta',
+ safeCloseAccount: 'Fechar conta com segurança',
+ closeAccountInfo:
+ 'Recomendamos fechar a conta com segurança para evitar o fechamento caso haja:
aprovações pendentes
reembolsos ativos
nenhum método de login alternativo
Caso contrário, você pode ignorar as precauções de segurança acima e forçar o fechamento da conta selecionada.',
+ error: {
+ removeMember: 'Não foi possível remover este usuário. Tente novamente.',
addMember: 'Não foi possível adicionar este membro. Tente novamente.',
},
},
diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts
index 549d324177992..02807e82b42e5 100644
--- a/src/languages/zh-hans.ts
+++ b/src/languages/zh-hans.ts
@@ -8096,8 +8096,15 @@ ${reportName}
title: '成员',
findMember: '查找成员',
addMember: '添加成员',
- email: '邮箱地址',
- errors: {
+ email: '电子邮箱地址',
+ closeAccount: '关闭账户',
+ closeAccountPrompt: '您确定吗?此操作是永久性的。',
+ forceCloseAccount: '强制关闭账户',
+ safeCloseAccount: '安全关闭账户',
+ closeAccountInfo:
+ '我们建议安全地关闭该账户,以便在存在以下情况时跳过关闭操作:
待处理的审批
正在进行的报销
没有其他登录方式
否则,您可以忽略上述安全预防措施并强制关闭所选账户。',
+ error: {
+ removeMember: '无法移除此用户。请重试。',
addMember: '无法添加此成员。请重试。',
},
},
diff --git a/src/libs/API/parameters/DeleteDomainMemberParams.ts b/src/libs/API/parameters/DeleteDomainMemberParams.ts
new file mode 100644
index 0000000000000..d33c94769bd77
--- /dev/null
+++ b/src/libs/API/parameters/DeleteDomainMemberParams.ts
@@ -0,0 +1,7 @@
+type DeleteDomainMemberParams = {
+ targetEmail: string;
+ domain: string;
+ overrideProcessingReports: boolean;
+};
+
+export default DeleteDomainMemberParams;
diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts
index 5cdbc9202dc4f..b177eafa1ef9b 100644
--- a/src/libs/API/parameters/index.ts
+++ b/src/libs/API/parameters/index.ts
@@ -469,6 +469,7 @@ export type {default as SetPolicyRequireCompanyCardsEnabledParams} from './SetPo
export type {default as SetTechnicalContactEmailParams} from './SetTechnicalContactEmailParams';
export type {default as ToggleConsolidatedDomainBillingParams} from './ToggleConsolidatedDomainBillingParams';
export type {default as RemoveDomainAdminParams} from './RemoveDomainAdminParams';
+export type {default as DeleteDomainMemberParams} from './DeleteDomainMemberParams';
export type {default as DeleteDomainParams} from './DeleteDomainParams';
export type {default as GetDuplicateTransactionDetailsParams} from './GetDuplicateTransactionDetailsParams';
export type {default as SetPolicyCodingRuleParams} from './SetPolicyCodingRuleParams';
diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts
index 4bc6c6027aae5..063c11fb04edc 100644
--- a/src/libs/API/types.ts
+++ b/src/libs/API/types.ts
@@ -547,6 +547,7 @@ const WRITE_COMMANDS = {
ADD_DOMAIN_ADMIN: 'AddDomainAdmin',
REMOVE_DOMAIN_ADMIN: 'RemoveDomainAdmin',
DELETE_DOMAIN: 'DeleteDomain',
+ DELETE_DOMAIN_MEMBER: 'DeleteDomainMember',
} as const;
type WriteCommand = ValueOf;
@@ -1113,6 +1114,7 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.SET_TECHNICAL_CONTACT_EMAIL]: Parameters.SetTechnicalContactEmailParams;
[WRITE_COMMANDS.TOGGLE_CONSOLIDATED_DOMAIN_BILLING]: Parameters.ToggleConsolidatedDomainBillingParams;
[WRITE_COMMANDS.REMOVE_DOMAIN_ADMIN]: Parameters.RemoveDomainAdminParams;
+ [WRITE_COMMANDS.DELETE_DOMAIN_MEMBER]: Parameters.DeleteDomainMemberParams;
[WRITE_COMMANDS.DELETE_DOMAIN]: Parameters.DeleteDomainParams;
[WRITE_COMMANDS.ADD_DOMAIN_ADMIN]: Parameters.AddAdminToDomainParams;
[WRITE_COMMANDS.ADD_DOMAIN_MEMBER]: Parameters.AddMemberToDomainParams;
diff --git a/src/libs/actions/Domain.ts b/src/libs/actions/Domain.ts
index 016f7474e5b9e..c9254533e8b28 100644
--- a/src/libs/actions/Domain.ts
+++ b/src/libs/actions/Domain.ts
@@ -4,6 +4,7 @@ import * as API from '@libs/API';
import type {
AddAdminToDomainParams,
AddMemberToDomainParams,
+ DeleteDomainMemberParams,
DeleteDomainParams,
RemoveDomainAdminParams,
SetTechnicalContactEmailParams,
@@ -14,8 +15,8 @@ import {getMicroSecondOnyxErrorWithTranslationKey} from '@libs/ErrorUtils';
import {generateAccountID} from '@libs/UserUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
-import type {Domain} from '@src/types/onyx';
-import type {DomainSecurityGroup} from '@src/types/onyx/Domain';
+import type {Domain, DomainSecurityGroup, UserSecurityGroupData} from '@src/types/onyx';
+import type {PendingAction} from '@src/types/onyx/OnyxCommon';
import type PrefixedRecord from '@src/types/utils/PrefixedRecord';
import type {ScimTokenWithState} from './ScimToken/ScimTokenUtils';
import {ScimTokenState} from './ScimToken/ScimTokenUtils';
@@ -780,7 +781,7 @@ function resetDomain(domainAccountID: number, domainName: string, domain: Domain
}
/**
- * Clears errors after trying to reset domain
+ * Clears domain related errors and pending actions.
*/
function clearDomainErrors(domainAccountID: number) {
Onyx.merge(`${ONYXKEYS.COLLECTION.DOMAIN_ERRORS}${domainAccountID}`, {
@@ -791,6 +792,12 @@ function clearDomainErrors(domainAccountID: number) {
});
}
+/**
+ * Adds a member to a domain
+ * @param domainAccountID Account ID of a domain
+ * @param email Email of a user to be added
+ * @param defaultSecurityGroupID Security group ID to be used for optimistic updates
+ */
function addMemberToDomain(domainAccountID: number, email: string, defaultSecurityGroupID: string) {
const DOMAIN_SECURITY_GROUP = `${CONST.DOMAIN.DOMAIN_SECURITY_GROUP_PREFIX}${defaultSecurityGroupID}`;
const optimisticAccountID = generateAccountID(email);
@@ -892,7 +899,7 @@ function addMemberToDomain(domainAccountID: number, email: string, defaultSecuri
value: {
memberErrors: {
[email]: {
- errors: getMicroSecondOnyxErrorWithTranslationKey('domain.members.errors.addMember'),
+ errors: getMicroSecondOnyxErrorWithTranslationKey('domain.members.error.addMember'),
},
},
},
@@ -921,9 +928,19 @@ function addMemberToDomain(domainAccountID: number, email: string, defaultSecuri
/**
* Removes an error and pending actions after trying to add member. It clears errors for both email and accountID
*/
-function clearAddMemberError(domainAccountID: number, accountID: number, email: string, defaultSecurityGroupID: string) {
+function clearDomainMemberError(domainAccountID: number, accountID: number, email: string, defaultSecurityGroupID: string, pendingAction?: PendingAction) {
const DOMAIN_SECURITY_GROUP = `${CONST.DOMAIN.DOMAIN_SECURITY_GROUP_PREFIX}${defaultSecurityGroupID}`;
+ if (pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD) {
+ Onyx.merge(`${ONYXKEYS.COLLECTION.DOMAIN}${domainAccountID}`, {
+ [DOMAIN_SECURITY_GROUP]: {
+ shared: {
+ [accountID]: null,
+ },
+ },
+ } as PrefixedRecord>);
+ }
+
Onyx.merge(`${ONYXKEYS.COLLECTION.DOMAIN_ERRORS}${domainAccountID}`, {
memberErrors: {
[email]: null,
@@ -931,13 +948,96 @@ function clearAddMemberError(domainAccountID: number, accountID: number, email:
},
});
- Onyx.merge(`${ONYXKEYS.COLLECTION.DOMAIN}${domainAccountID}`, {
- [DOMAIN_SECURITY_GROUP]: {
- shared: {
- [accountID]: null,
+ Onyx.merge(`${ONYXKEYS.COLLECTION.DOMAIN_PENDING_ACTIONS}${domainAccountID}`, {
+ member: {
+ [email]: null,
+ [accountID]: null,
+ },
+ });
+}
+
+/**
+ * Sends a request to remove a user from a domain and close their account
+ * @param domainAccountID Account ID of a domain
+ * @param domain Domain name
+ * @param targetEmail Email of a user to be removed
+ * @param securityGroupsData Data of a security group user is in
+ * @param overrideProcessingReports "Force" flag. If true user will be removed regardless of if they have outstanding reports
+ */
+function closeUserAccount(domainAccountID: number, domain: string, targetEmail: string, securityGroupsData: UserSecurityGroupData, overrideProcessingReports = false) {
+ const optimisticData: OnyxUpdate[] = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.DOMAIN_PENDING_ACTIONS}${domainAccountID}`,
+ value: {
+ member: {[targetEmail]: {pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE}},
+ },
+ },
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.DOMAIN_ERRORS}${domainAccountID}`,
+ value: {
+ memberErrors: {
+ [targetEmail]: null,
+ },
},
},
- } as PrefixedRecord>);
+ ];
+
+ const successData: OnyxUpdate[] = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.DOMAIN_PENDING_ACTIONS}${domainAccountID}`,
+ value: {
+ member: {[targetEmail]: null},
+ },
+ },
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.DOMAIN_ERRORS}${domainAccountID}`,
+ value: {
+ memberErrors: {
+ [targetEmail]: null,
+ },
+ },
+ },
+ ];
+
+ const failureData: OnyxUpdate[] = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.DOMAIN_ERRORS}${domainAccountID}`,
+ value: {
+ memberErrors: {
+ [targetEmail]: {errors: getMicroSecondOnyxErrorWithTranslationKey('domain.members.error.removeMember')},
+ },
+ },
+ },
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.DOMAIN_PENDING_ACTIONS}${domainAccountID}`,
+ value: {
+ member: {[targetEmail]: null},
+ },
+ },
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.DOMAIN}${domainAccountID}`,
+ value: (securityGroupsData?.key
+ ? {
+ [securityGroupsData.key]: securityGroupsData.securityGroup,
+ }
+ : {}) as PrefixedRecord>,
+ },
+ ];
+
+ const parameters: DeleteDomainMemberParams = {
+ domain,
+ targetEmail,
+ overrideProcessingReports,
+ };
+
+ API.write(WRITE_COMMANDS.DELETE_DOMAIN_MEMBER, parameters, {optimisticData, successData, failureData});
}
export {
@@ -964,5 +1064,6 @@ export {
resetDomain,
clearDomainErrors,
addMemberToDomain,
- clearAddMemberError,
+ clearDomainMemberError,
+ closeUserAccount,
};
diff --git a/src/pages/domain/Admins/DomainAdminDetailsPage.tsx b/src/pages/domain/Admins/DomainAdminDetailsPage.tsx
index 76656a6f6d809..a43d86560efd0 100644
--- a/src/pages/domain/Admins/DomainAdminDetailsPage.tsx
+++ b/src/pages/domain/Admins/DomainAdminDetailsPage.tsx
@@ -91,6 +91,4 @@ function DomainAdminDetailsPage({route}: DomainAdminDetailsPageProps) {
);
}
-DomainAdminDetailsPage.displayName = 'DomainAdminDetailsPage';
-
export default DomainAdminDetailsPage;
diff --git a/src/pages/domain/BaseDomainMemberDetailsComponent.tsx b/src/pages/domain/BaseDomainMemberDetailsComponent.tsx
index de64be477c5ae..fa08d102c6d1a 100644
--- a/src/pages/domain/BaseDomainMemberDetailsComponent.tsx
+++ b/src/pages/domain/BaseDomainMemberDetailsComponent.tsx
@@ -31,9 +31,12 @@ type BaseDomainMemberDetailsComponentProps = {
/** List of additional fields (e.g., force 2FA) */
children?: React.ReactNode;
+
+ /** Button to display below avatar picture */
+ avatarButton?: React.ReactNode;
};
-function BaseDomainMemberDetailsComponent({domainAccountID, accountID, children}: BaseDomainMemberDetailsComponentProps) {
+function BaseDomainMemberDetailsComponent({domainAccountID, accountID, children, avatarButton}: BaseDomainMemberDetailsComponentProps) {
const styles = useThemeStyles();
const {translate, formatPhoneNumber} = useLocalize();
const icons = useMemoizedLazyExpensifyIcons(['Info']);
@@ -83,6 +86,7 @@ function BaseDomainMemberDetailsComponent({domainAccountID, accountID, children}
{displayName}
)}
+ {avatarButton}
React.ReactNode;
/** Function to return additional row-specific properties like errors or pending actions */
- getCustomRowProps?: (accountID: number, email?: string) => {errors?: Errors; pendingAction?: PendingAction};
+ getCustomRowProps?: (accountID: number, accountEmail?: string) => {errors?: Errors; pendingAction?: PendingAction};
/** Callback fired when the user dismisses an error message for a specific row */
onDismissError?: (item: MemberOption) => void;
diff --git a/src/pages/domain/Members/DomainMemberDetailsPage.tsx b/src/pages/domain/Members/DomainMemberDetailsPage.tsx
index d57f13edbc699..549f422ee463a 100644
--- a/src/pages/domain/Members/DomainMemberDetailsPage.tsx
+++ b/src/pages/domain/Members/DomainMemberDetailsPage.tsx
@@ -1,22 +1,118 @@
-import React from 'react';
+import {domainNameSelector, selectSecurityGroupForAccount} from '@selectors/Domain';
+import {personalDetailsSelector} from '@selectors/PersonalDetails';
+import React, {useState} from 'react';
+import Button from '@components/Button';
+import DecisionModal from '@components/DecisionModal';
+import {ModalActions} from '@components/Modal/Global/ModalContext';
+import useConfirmModal from '@hooks/useConfirmModal';
+import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
+import useLocalize from '@hooks/useLocalize';
+import useOnyx from '@hooks/useOnyx';
+import useResponsiveLayout from '@hooks/useResponsiveLayout';
+import useThemeStyles from '@hooks/useThemeStyles';
+import {closeUserAccount} from '@libs/actions/Domain';
+import Navigation from '@navigation/Navigation';
import type {PlatformStackScreenProps} from '@navigation/PlatformStackNavigation/types';
import type {SettingsNavigatorParamList} from '@navigation/types';
import BaseDomainMemberDetailsComponent from '@pages/domain/BaseDomainMemberDetailsComponent';
+import ONYXKEYS from '@src/ONYXKEYS';
import type SCREENS from '@src/SCREENS';
type DomainMemberDetailsPageProps = PlatformStackScreenProps;
function DomainMemberDetailsPage({route}: DomainMemberDetailsPageProps) {
const {domainAccountID, accountID} = route.params;
+ const styles = useThemeStyles();
+ const {translate} = useLocalize();
+ const icons = useMemoizedLazyExpensifyIcons(['RemoveMembers']);
+ const [isModalVisible, setIsModalVisible] = useState(false);
+ const [shouldForceCloseAccount, setShouldForceCloseAccount] = useState();
+ // 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 {showConfirmModal} = useConfirmModal();
- return (
- {
+ if (!userSecurityGroup || shouldForceCloseAccount === undefined) {
+ return;
+ }
+
+ const result = await showConfirmModal({
+ title: translate('domain.members.closeAccount'),
+ prompt: translate('domain.members.closeAccountPrompt'),
+ confirmText: translate('domain.members.closeAccount'),
+ cancelText: translate('common.cancel'),
+ danger: true,
+ shouldShowCancelButton: true,
+ });
+ if (result.action !== ModalActions.CONFIRM) {
+ setIsModalVisible(true);
+ setShouldForceCloseAccount(undefined);
+ return;
+ }
+ closeUserAccount(domainAccountID, domainName ?? '', memberLogin, userSecurityGroup, shouldForceCloseAccount);
+ setShouldForceCloseAccount(undefined);
+ Navigation.dismissModal();
+ };
+
+ const handleForceCloseAccount = () => {
+ setShouldForceCloseAccount(true);
+ setIsModalVisible(false);
+ };
+
+ const handleSafeCloseAccount = () => {
+ setShouldForceCloseAccount(false);
+ setIsModalVisible(false);
+ };
+
+ const avatarButton = (
+