Update contact spoofing banner & dialog
This commit is contained in:
parent
4b7f1dbada
commit
7b039fa526
5 changed files with 178 additions and 126 deletions
|
@ -3,7 +3,6 @@
|
|||
|
||||
import type { ReactChild, ReactNode } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { concat, orderBy } from 'lodash';
|
||||
|
||||
import type { LocalizerType, ThemeType } from '../../types/Util';
|
||||
import type { ConversationType } from '../../state/ducks/conversations';
|
||||
|
@ -235,115 +234,140 @@ export function ContactSpoofingReviewDialog(props: PropsType): JSX.Element {
|
|||
}
|
||||
case ContactSpoofingType.MultipleGroupMembersWithSameTitle: {
|
||||
const { group, collisionInfoByTitle } = props;
|
||||
|
||||
const unsortedConversationInfos = concat(
|
||||
// This empty array exists to appease Lodash's type definitions.
|
||||
[],
|
||||
...Object.values(collisionInfoByTitle)
|
||||
const sharedTitles = Object.keys(collisionInfoByTitle);
|
||||
const numSharedTitles = sharedTitles.length;
|
||||
const totalConversations = Object.values(collisionInfoByTitle).reduce(
|
||||
(sum, conversationInfos) => sum + conversationInfos.length,
|
||||
0
|
||||
);
|
||||
const conversationInfos = orderBy(unsortedConversationInfos, [
|
||||
// We normally use an `Intl.Collator` to sort by title. We do this instead, as
|
||||
// we only really care about stability (not perfect ordering).
|
||||
'title',
|
||||
'id',
|
||||
]);
|
||||
|
||||
title = i18n('icu:ContactSpoofingReviewDialog__group__title');
|
||||
contents = (
|
||||
<>
|
||||
<p>
|
||||
{i18n('icu:ContactSpoofingReviewDialog__group__description', {
|
||||
count: conversationInfos.length,
|
||||
})}
|
||||
<p className="module-ContactSpoofingReviewDialog__description">
|
||||
{numSharedTitles > 1
|
||||
? i18n(
|
||||
'icu:ContactSpoofingReviewDialog__group__multiple-conflicts__description',
|
||||
{
|
||||
count: numSharedTitles,
|
||||
}
|
||||
)
|
||||
: i18n('icu:ContactSpoofingReviewDialog__group__description', {
|
||||
count: totalConversations,
|
||||
})}
|
||||
</p>
|
||||
<h2>
|
||||
{i18n('icu:ContactSpoofingReviewDialog__group__members-header')}
|
||||
</h2>
|
||||
{conversationInfos.map((conversationInfo, index) => {
|
||||
let button: ReactNode;
|
||||
if (group.areWeAdmin) {
|
||||
button = (
|
||||
<Button
|
||||
variant={ButtonVariant.SecondaryAffirmative}
|
||||
onClick={() => {
|
||||
setConfirmationState({
|
||||
type: ConfirmationStateType.ConfirmingGroupRemoval,
|
||||
affectedConversation: conversationInfo.conversation,
|
||||
group,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{i18n('icu:RemoveGroupMemberConfirmation__remove-button')}
|
||||
</Button>
|
||||
);
|
||||
} else if (conversationInfo.conversation.isBlocked) {
|
||||
button = (
|
||||
<Button
|
||||
variant={ButtonVariant.SecondaryAffirmative}
|
||||
onClick={() => {
|
||||
acceptConversation(conversationInfo.conversation.id);
|
||||
}}
|
||||
>
|
||||
{i18n('icu:MessageRequests--unblock')}
|
||||
</Button>
|
||||
);
|
||||
} else if (!isInSystemContacts(conversationInfo.conversation)) {
|
||||
button = (
|
||||
<Button
|
||||
variant={ButtonVariant.SecondaryDestructive}
|
||||
onClick={() => {
|
||||
setConfirmationState({
|
||||
type: ConfirmationStateType.ConfirmingBlock,
|
||||
affectedConversation: conversationInfo.conversation,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{i18n('icu:MessageRequests--block')}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
const { oldName } = conversationInfo;
|
||||
const newName =
|
||||
conversationInfo.conversation.profileName ||
|
||||
conversationInfo.conversation.title;
|
||||
{Object.values(collisionInfoByTitle).map(
|
||||
(conversationInfos, titleIdx) => {
|
||||
return (
|
||||
<>
|
||||
<h2>
|
||||
{i18n(
|
||||
'icu:ContactSpoofingReviewDialog__group__members-header'
|
||||
)}
|
||||
</h2>
|
||||
{conversationInfos.map(
|
||||
(conversationInfo, conversationIdx) => {
|
||||
let button: ReactNode;
|
||||
if (group.areWeAdmin) {
|
||||
button = (
|
||||
<Button
|
||||
variant={ButtonVariant.SecondaryAffirmative}
|
||||
onClick={() => {
|
||||
setConfirmationState({
|
||||
type: ConfirmationStateType.ConfirmingGroupRemoval,
|
||||
affectedConversation:
|
||||
conversationInfo.conversation,
|
||||
group,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{i18n(
|
||||
'icu:RemoveGroupMemberConfirmation__remove-button'
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
} else if (conversationInfo.conversation.isBlocked) {
|
||||
button = (
|
||||
<Button
|
||||
variant={ButtonVariant.SecondaryAffirmative}
|
||||
onClick={() => {
|
||||
acceptConversation(
|
||||
conversationInfo.conversation.id
|
||||
);
|
||||
}}
|
||||
>
|
||||
{i18n('icu:MessageRequests--unblock')}
|
||||
</Button>
|
||||
);
|
||||
} else if (
|
||||
!isInSystemContacts(conversationInfo.conversation)
|
||||
) {
|
||||
button = (
|
||||
<Button
|
||||
variant={ButtonVariant.SecondaryDestructive}
|
||||
onClick={() => {
|
||||
setConfirmationState({
|
||||
type: ConfirmationStateType.ConfirmingBlock,
|
||||
affectedConversation:
|
||||
conversationInfo.conversation,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{i18n('icu:MessageRequests--block')}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
let callout: JSX.Element | undefined;
|
||||
if (oldName && oldName !== newName) {
|
||||
callout = (
|
||||
<div className="module-ContactSpoofingReviewDialogPerson__info__property module-ContactSpoofingReviewDialogPerson__info__property--callout">
|
||||
<Intl
|
||||
i18n={i18n}
|
||||
id="icu:ContactSpoofingReviewDialog__group__name-change-info"
|
||||
components={{
|
||||
oldName: <UserText text={oldName} />,
|
||||
newName: <UserText text={newName} />,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const { oldName } = conversationInfo;
|
||||
const newName =
|
||||
conversationInfo.conversation.profileName ||
|
||||
conversationInfo.conversation.title;
|
||||
|
||||
return (
|
||||
<>
|
||||
{index !== 0 && <hr />}
|
||||
<ContactSpoofingReviewDialogPerson
|
||||
key={conversationInfo.conversation.id}
|
||||
conversation={conversationInfo.conversation}
|
||||
getPreferredBadge={getPreferredBadge}
|
||||
i18n={i18n}
|
||||
theme={theme}
|
||||
>
|
||||
{callout}
|
||||
{button && (
|
||||
<div className="module-ContactSpoofingReviewDialog__buttons">
|
||||
{button}
|
||||
</div>
|
||||
let callout: JSX.Element | undefined;
|
||||
if (oldName && oldName !== newName) {
|
||||
callout = (
|
||||
<div className="module-ContactSpoofingReviewDialogPerson__info__property module-ContactSpoofingReviewDialogPerson__info__property--callout">
|
||||
<Intl
|
||||
i18n={i18n}
|
||||
id="icu:ContactSpoofingReviewDialog__group__name-change-info"
|
||||
components={{
|
||||
oldName: <UserText text={oldName} />,
|
||||
newName: <UserText text={newName} />,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<ContactSpoofingReviewDialogPerson
|
||||
key={conversationInfo.conversation.id}
|
||||
conversation={conversationInfo.conversation}
|
||||
getPreferredBadge={getPreferredBadge}
|
||||
i18n={i18n}
|
||||
theme={theme}
|
||||
>
|
||||
{callout}
|
||||
{button && (
|
||||
<div className="module-ContactSpoofingReviewDialog__buttons">
|
||||
{button}
|
||||
</div>
|
||||
)}
|
||||
</ContactSpoofingReviewDialogPerson>
|
||||
{titleIdx < sharedTitles.length - 1 ||
|
||||
conversationIdx < conversationInfos.length - 1 ? (
|
||||
<hr />
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</ContactSpoofingReviewDialogPerson>
|
||||
</>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</>
|
||||
);
|
||||
break;
|
||||
|
|
|
@ -19,6 +19,7 @@ import { clearTimeoutIfNecessary } from '../../util/clearTimeoutIfNecessary';
|
|||
import { WidthBreakpoint } from '../_util';
|
||||
|
||||
import { ErrorBoundary } from './ErrorBoundary';
|
||||
import type { FullJSXType } from '../Intl';
|
||||
import { Intl } from '../Intl';
|
||||
import { TimelineWarning } from './TimelineWarning';
|
||||
import { TimelineWarnings } from './TimelineWarnings';
|
||||
|
@ -941,29 +942,42 @@ export class Timeline extends React.Component<
|
|||
break;
|
||||
case ContactSpoofingType.MultipleGroupMembersWithSameTitle: {
|
||||
const { groupNameCollisions } = warning;
|
||||
text = (
|
||||
<Intl
|
||||
i18n={i18n}
|
||||
id="icu:ContactSpoofing__same-name-in-group--link"
|
||||
components={{
|
||||
count: Object.values(groupNameCollisions).reduce(
|
||||
(result, conversations) => result + conversations.length,
|
||||
0
|
||||
),
|
||||
// This is a render props, not a component
|
||||
// eslint-disable-next-line react/no-unstable-nested-components
|
||||
reviewRequestLink: parts => (
|
||||
<TimelineWarning.Link
|
||||
onClick={() => {
|
||||
reviewGroupMemberNameCollision(id);
|
||||
}}
|
||||
>
|
||||
{parts}
|
||||
</TimelineWarning.Link>
|
||||
),
|
||||
const numberOfSharedNames = Object.keys(groupNameCollisions).length;
|
||||
const reviewRequestLink: FullJSXType = parts => (
|
||||
<TimelineWarning.Link
|
||||
onClick={() => {
|
||||
reviewGroupMemberNameCollision(id);
|
||||
}}
|
||||
/>
|
||||
>
|
||||
{parts}
|
||||
</TimelineWarning.Link>
|
||||
);
|
||||
if (numberOfSharedNames === 1) {
|
||||
text = (
|
||||
<Intl
|
||||
i18n={i18n}
|
||||
id="icu:ContactSpoofing__same-name-in-group--link"
|
||||
components={{
|
||||
count: Object.values(groupNameCollisions).reduce(
|
||||
(result, conversations) => result + conversations.length,
|
||||
0
|
||||
),
|
||||
reviewRequestLink,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
text = (
|
||||
<Intl
|
||||
i18n={i18n}
|
||||
id="icu:ContactSpoofing__same-names-in-group--link"
|
||||
components={{
|
||||
count: numberOfSharedNames,
|
||||
reviewRequestLink,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
onClose = () => {
|
||||
acknowledgeGroupMemberNameCollisions(id, groupNameCollisions);
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue