mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2026-04-18 23:49:20 +01:00
Admin Delete
This commit is contained in:
@@ -1,101 +1,249 @@
|
||||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React from 'react';
|
||||
import type { ActionSpec } from './ConfirmationDialog.dom.js';
|
||||
import { ConfirmationDialog } from './ConfirmationDialog.dom.js';
|
||||
import type { MouseEvent } from 'react';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import type { LocalizerType } from '../types/Util.std.js';
|
||||
import type { ShowToastAction } from '../state/ducks/toast.preload.js';
|
||||
import { ToastType } from '../types/Toast.dom.js';
|
||||
import { AxoAlertDialog } from '../axo/AxoAlertDialog.dom.js';
|
||||
|
||||
export type DeleteMessagesModalProps = Readonly<{
|
||||
isMe: boolean;
|
||||
canDeleteForEveryone: boolean;
|
||||
needsAdminDelete: boolean;
|
||||
isDeletingOwnMessages: boolean;
|
||||
hasSeenAdminDeleteEducationDialog: boolean;
|
||||
i18n: LocalizerType;
|
||||
messageCount: number;
|
||||
onClose: () => void;
|
||||
onDeleteForMe: () => void;
|
||||
onDeleteForEveryone: () => void;
|
||||
onSeenAdminDeleteEducationDialog: () => void;
|
||||
showToast: ShowToastAction;
|
||||
}>;
|
||||
|
||||
const MAX_DELETE_FOR_EVERYONE = 30;
|
||||
|
||||
enum Step {
|
||||
SELECT_DELETE_TYPE,
|
||||
CONFIRM_ADMIN_DELETE,
|
||||
}
|
||||
|
||||
export default function DeleteMessagesModal({
|
||||
isMe,
|
||||
canDeleteForEveryone,
|
||||
needsAdminDelete,
|
||||
isDeletingOwnMessages,
|
||||
hasSeenAdminDeleteEducationDialog,
|
||||
i18n,
|
||||
messageCount,
|
||||
onClose,
|
||||
onDeleteForMe,
|
||||
onDeleteForEveryone,
|
||||
onSeenAdminDeleteEducationDialog,
|
||||
showToast,
|
||||
}: DeleteMessagesModalProps): React.JSX.Element {
|
||||
const actions: Array<ActionSpec> = [];
|
||||
const [step, setStep] = useState(Step.SELECT_DELETE_TYPE);
|
||||
|
||||
const syncNoteToSelfDelete = isMe;
|
||||
const tooManyMessages = messageCount > MAX_DELETE_FOR_EVERYONE;
|
||||
|
||||
let deleteForMeText = i18n('icu:DeleteMessagesModal--deleteForMe');
|
||||
if (syncNoteToSelfDelete) {
|
||||
deleteForMeText = i18n('icu:DeleteMessagesModal--noteToSelf--deleteSync');
|
||||
} else if (isMe) {
|
||||
deleteForMeText = i18n('icu:DeleteMessagesModal--deleteFromThisDevice');
|
||||
}
|
||||
const handleBackToSelectDeleteType = useCallback(() => {
|
||||
setStep(Step.SELECT_DELETE_TYPE);
|
||||
}, []);
|
||||
|
||||
actions.push({
|
||||
action: onDeleteForMe,
|
||||
style: 'negative',
|
||||
text: deleteForMeText,
|
||||
});
|
||||
const handleSelectDeleteForMe = useCallback(() => {
|
||||
onDeleteForMe();
|
||||
onClose();
|
||||
}, [onDeleteForMe, onClose]);
|
||||
|
||||
if (canDeleteForEveryone && !syncNoteToSelfDelete) {
|
||||
const tooManyMessages = messageCount > MAX_DELETE_FOR_EVERYONE;
|
||||
actions.push({
|
||||
'aria-disabled': tooManyMessages,
|
||||
autoClose: !tooManyMessages,
|
||||
action: () => {
|
||||
if (tooManyMessages) {
|
||||
showToast({
|
||||
toastType: ToastType.TooManyMessagesToDeleteForEveryone,
|
||||
parameters: { count: MAX_DELETE_FOR_EVERYONE },
|
||||
});
|
||||
} else {
|
||||
onDeleteForEveryone();
|
||||
}
|
||||
},
|
||||
style: 'negative',
|
||||
text: isMe
|
||||
? i18n('icu:DeleteMessagesModal--deleteFromAllDevices')
|
||||
: i18n('icu:DeleteMessagesModal--deleteForEveryone'),
|
||||
});
|
||||
}
|
||||
const handleConfirmDeleteForEveryone = useCallback(() => {
|
||||
onDeleteForEveryone();
|
||||
onClose();
|
||||
}, [onDeleteForEveryone, onClose]);
|
||||
|
||||
let descriptionText = i18n('icu:DeleteMessagesModal--description', {
|
||||
count: messageCount,
|
||||
});
|
||||
if (syncNoteToSelfDelete) {
|
||||
descriptionText = i18n(
|
||||
'icu:DeleteMessagesModal--description--noteToSelf--deleteSync',
|
||||
{ count: messageCount }
|
||||
);
|
||||
} else if (isMe) {
|
||||
descriptionText = i18n('icu:DeleteMessagesModal--description--noteToSelf', {
|
||||
count: messageCount,
|
||||
});
|
||||
}
|
||||
const handleSelectDeleteForEveryone = useCallback(() => {
|
||||
if (tooManyMessages) {
|
||||
showToast({
|
||||
toastType: ToastType.TooManyMessagesToDeleteForEveryone,
|
||||
parameters: { count: MAX_DELETE_FOR_EVERYONE },
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (
|
||||
needsAdminDelete &&
|
||||
!isDeletingOwnMessages &&
|
||||
!hasSeenAdminDeleteEducationDialog
|
||||
) {
|
||||
setStep(Step.CONFIRM_ADMIN_DELETE);
|
||||
return;
|
||||
}
|
||||
handleConfirmDeleteForEveryone();
|
||||
}, [
|
||||
tooManyMessages,
|
||||
needsAdminDelete,
|
||||
isDeletingOwnMessages,
|
||||
hasSeenAdminDeleteEducationDialog,
|
||||
showToast,
|
||||
handleConfirmDeleteForEveryone,
|
||||
]);
|
||||
|
||||
return (
|
||||
<ConfirmationDialog
|
||||
actions={actions}
|
||||
dialogName="ConfirmDeleteForMeModal"
|
||||
i18n={i18n}
|
||||
onClose={onClose}
|
||||
title={i18n('icu:DeleteMessagesModal--title', {
|
||||
count: messageCount,
|
||||
})}
|
||||
moduleClassName="DeleteMessagesModal"
|
||||
>
|
||||
{descriptionText}
|
||||
</ConfirmationDialog>
|
||||
<>
|
||||
<DeleteMessagesSelectDeleteTypeDialog
|
||||
isMe={isMe}
|
||||
canDeleteForEveryone={canDeleteForEveryone}
|
||||
i18n={i18n}
|
||||
messageCount={messageCount}
|
||||
tooManyMessages={tooManyMessages}
|
||||
open={step === Step.SELECT_DELETE_TYPE}
|
||||
onClose={onClose}
|
||||
onSelectDeleteForMe={handleSelectDeleteForMe}
|
||||
onSelectDeleteForEveryone={handleSelectDeleteForEveryone}
|
||||
/>
|
||||
<DeleteMessagesConfirmAdminDeleteDialog
|
||||
i18n={i18n}
|
||||
messageCount={messageCount}
|
||||
open={step === Step.CONFIRM_ADMIN_DELETE}
|
||||
onOpenChange={handleBackToSelectDeleteType}
|
||||
onCancel={handleBackToSelectDeleteType}
|
||||
onConfirm={onDeleteForEveryone}
|
||||
onClose={onClose}
|
||||
onSeenAdminDeleteEducationDialog={onSeenAdminDeleteEducationDialog}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function DeleteMessagesSelectDeleteTypeDialog(props: {
|
||||
isMe: boolean;
|
||||
canDeleteForEveryone: boolean;
|
||||
i18n: LocalizerType;
|
||||
messageCount: number;
|
||||
tooManyMessages: boolean;
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onSelectDeleteForMe: () => void;
|
||||
onSelectDeleteForEveryone: () => void;
|
||||
}) {
|
||||
const { i18n, onClose, onSelectDeleteForEveryone } = props;
|
||||
|
||||
const handleOpenChange = useCallback(
|
||||
(value: boolean) => {
|
||||
if (!value) {
|
||||
onClose();
|
||||
}
|
||||
},
|
||||
[onClose]
|
||||
);
|
||||
|
||||
const handleSelectDeleteForEveryone = useCallback(
|
||||
(event: MouseEvent) => {
|
||||
event.preventDefault();
|
||||
onSelectDeleteForEveryone();
|
||||
},
|
||||
[onSelectDeleteForEveryone]
|
||||
);
|
||||
|
||||
return (
|
||||
<AxoAlertDialog.Root open={props.open} onOpenChange={handleOpenChange}>
|
||||
<AxoAlertDialog.Content escape="cancel-is-noop">
|
||||
<AxoAlertDialog.Body>
|
||||
<AxoAlertDialog.Title>
|
||||
{i18n('icu:DeleteMessagesModal--title-2', {
|
||||
count: props.messageCount,
|
||||
})}
|
||||
</AxoAlertDialog.Title>
|
||||
<AxoAlertDialog.Description>
|
||||
{props.isMe
|
||||
? i18n(
|
||||
'icu:DeleteMessagesModal--description--noteToSelf--deleteSync',
|
||||
{ count: props.messageCount }
|
||||
)
|
||||
: i18n('icu:DeleteMessagesModal--description', {
|
||||
count: props.messageCount,
|
||||
})}
|
||||
</AxoAlertDialog.Description>
|
||||
</AxoAlertDialog.Body>
|
||||
<AxoAlertDialog.Footer>
|
||||
<AxoAlertDialog.Cancel>{i18n('icu:cancel')}</AxoAlertDialog.Cancel>
|
||||
<AxoAlertDialog.Action
|
||||
variant="subtle-destructive"
|
||||
onClick={props.onSelectDeleteForMe}
|
||||
>
|
||||
{props.isMe
|
||||
? i18n('icu:DeleteMessagesModal--noteToSelf--deleteSync')
|
||||
: i18n('icu:DeleteMessagesModal--deleteForMe')}
|
||||
</AxoAlertDialog.Action>
|
||||
{props.canDeleteForEveryone && !props.isMe && (
|
||||
<AxoAlertDialog.Action
|
||||
variant="subtle-destructive"
|
||||
onClick={handleSelectDeleteForEveryone}
|
||||
>
|
||||
{i18n('icu:DeleteMessagesModal--deleteForEveryone')}
|
||||
</AxoAlertDialog.Action>
|
||||
)}
|
||||
</AxoAlertDialog.Footer>
|
||||
</AxoAlertDialog.Content>
|
||||
</AxoAlertDialog.Root>
|
||||
);
|
||||
}
|
||||
|
||||
function DeleteMessagesConfirmAdminDeleteDialog(props: {
|
||||
i18n: LocalizerType;
|
||||
messageCount: number;
|
||||
open: boolean;
|
||||
onOpenChange: () => void;
|
||||
onCancel: () => void;
|
||||
onConfirm: () => void;
|
||||
onClose: () => void;
|
||||
onSeenAdminDeleteEducationDialog: () => void;
|
||||
}) {
|
||||
const {
|
||||
i18n,
|
||||
messageCount,
|
||||
onCancel,
|
||||
onConfirm,
|
||||
onClose,
|
||||
onSeenAdminDeleteEducationDialog,
|
||||
} = props;
|
||||
|
||||
const handleCancel = useCallback(() => {
|
||||
onCancel();
|
||||
}, [onCancel]);
|
||||
|
||||
const handleConfirm = useCallback(() => {
|
||||
onSeenAdminDeleteEducationDialog();
|
||||
onConfirm();
|
||||
onClose();
|
||||
}, [onConfirm, onClose, onSeenAdminDeleteEducationDialog]);
|
||||
|
||||
return (
|
||||
<AxoAlertDialog.Root open={props.open} onOpenChange={props.onOpenChange}>
|
||||
<AxoAlertDialog.Content escape="cancel-is-noop">
|
||||
<AxoAlertDialog.Body>
|
||||
<AxoAlertDialog.Title>
|
||||
{i18n('icu:DeleteMessagesModal--adminDeleteConfirmation--title')}
|
||||
</AxoAlertDialog.Title>
|
||||
<AxoAlertDialog.Description>
|
||||
{i18n(
|
||||
'icu:DeleteMessagesModal--adminDeleteConfirmation--description',
|
||||
{ count: messageCount }
|
||||
)}
|
||||
</AxoAlertDialog.Description>
|
||||
</AxoAlertDialog.Body>
|
||||
<AxoAlertDialog.Footer>
|
||||
<AxoAlertDialog.Action variant="secondary" onClick={handleCancel}>
|
||||
{i18n('icu:cancel')}
|
||||
</AxoAlertDialog.Action>
|
||||
<AxoAlertDialog.Action
|
||||
variant="subtle-destructive"
|
||||
onClick={handleConfirm}
|
||||
>
|
||||
{i18n('icu:DeleteMessagesModal--deleteForEveryone')}
|
||||
</AxoAlertDialog.Action>
|
||||
</AxoAlertDialog.Footer>
|
||||
</AxoAlertDialog.Content>
|
||||
</AxoAlertDialog.Root>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user