Update contact spoofing banner & dialog
This commit is contained in:
parent
4b7f1dbada
commit
7b039fa526
5 changed files with 178 additions and 126 deletions
|
@ -9455,9 +9455,13 @@
|
||||||
"description": "(deleted 04/04/2023) Shown in the timeline warning when you multiple group members have the same name"
|
"description": "(deleted 04/04/2023) Shown in the timeline warning when you multiple group members have the same name"
|
||||||
},
|
},
|
||||||
"icu:ContactSpoofing__same-name-in-group--link": {
|
"icu:ContactSpoofing__same-name-in-group--link": {
|
||||||
"messageformat": "{count, plural, one {# group member has} other {# group members have}} the same name. <reviewRequestLink>Review request</reviewRequestLink>",
|
"messageformat": "{count, plural, one {# group member has} other {# group members have}} the same name. <reviewRequestLink>Review members</reviewRequestLink>",
|
||||||
"description": "Shown in the timeline warning when you multiple group members have the same name"
|
"description": "Shown in the timeline warning when you multiple group members have the same name"
|
||||||
},
|
},
|
||||||
|
"icu:ContactSpoofing__same-names-in-group--link": {
|
||||||
|
"messageformat": "{count, plural, one {# name conflict was} other {# name conflicts were}} found in this group. <reviewRequestLink>Review members</reviewRequestLink>",
|
||||||
|
"description": "Shown in the timeline warning when multiple names are shared by members of a group."
|
||||||
|
},
|
||||||
"ContactSpoofing__same-name__link": {
|
"ContactSpoofing__same-name__link": {
|
||||||
"message": "Review request",
|
"message": "Review request",
|
||||||
"description": "(deleted 03/29/2023) Shown in the timeline warning when you have a message request from someone with the same name as someone else"
|
"description": "(deleted 03/29/2023) Shown in the timeline warning when you have a message request from someone with the same name as someone else"
|
||||||
|
@ -9472,7 +9476,7 @@
|
||||||
},
|
},
|
||||||
"icu:ContactSpoofing__same-name-in-group__link": {
|
"icu:ContactSpoofing__same-name-in-group__link": {
|
||||||
"messageformat": "Click to review",
|
"messageformat": "Click to review",
|
||||||
"description": "Shown in the timeline warning when you multiple group members have the same name"
|
"description": "(deleted 05/04/2023) Shown in the timeline warning when you multiple group members have the same name"
|
||||||
},
|
},
|
||||||
"ContactSpoofingReviewDialog__title": {
|
"ContactSpoofingReviewDialog__title": {
|
||||||
"message": "Review request",
|
"message": "Review request",
|
||||||
|
@ -9522,6 +9526,10 @@
|
||||||
"messageformat": "{count, plural, one {# group member} other {# group members}} have similar names. Review the members below or choose to take action.",
|
"messageformat": "{count, plural, one {# group member} other {# group members}} have similar names. Review the members below or choose to take action.",
|
||||||
"description": "Description for the group contact spoofing review dialog"
|
"description": "Description for the group contact spoofing review dialog"
|
||||||
},
|
},
|
||||||
|
"icu:ContactSpoofingReviewDialog__group__multiple-conflicts__description": {
|
||||||
|
"messageformat": "{count, plural, one {# name conflict was} other {# name conflicts were}} found in this group. Review the members below or choose to take action.",
|
||||||
|
"description": "Description for the group contact spoofing review dialog when there are multiple shared names"
|
||||||
|
},
|
||||||
"ContactSpoofingReviewDialog__group__members-header": {
|
"ContactSpoofingReviewDialog__group__members-header": {
|
||||||
"message": "Members",
|
"message": "Members",
|
||||||
"description": "(deleted 03/29/2023) Header in the group contact spoofing review dialog. After this header, there will be a list of members"
|
"description": "(deleted 03/29/2023) Header in the group contact spoofing review dialog. After this header, there will be a list of members"
|
||||||
|
|
|
@ -16,14 +16,20 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__description {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
@include font-body-1-bold;
|
@include font-body-1-bold;
|
||||||
|
margin-top: 28px;
|
||||||
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
hr {
|
hr {
|
||||||
border: 0;
|
border: 0;
|
||||||
height: 1px;
|
height: 1px;
|
||||||
margin-block: 16px;
|
margin-block: 20px;
|
||||||
margin-inline: 0;
|
margin-inline: 0;
|
||||||
|
|
||||||
@include light-theme {
|
@include light-theme {
|
||||||
|
@ -35,7 +41,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&__buttons {
|
&__buttons {
|
||||||
margin-top: 8px;
|
margin-top: 12px;
|
||||||
|
|
||||||
.module-Button:not(:last-child) {
|
.module-Button:not(:last-child) {
|
||||||
margin-inline-end: 12px;
|
margin-inline-end: 12px;
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
margin-inline-start: 12px;
|
margin-inline-start: 12px;
|
||||||
|
|
||||||
&__contact-name {
|
&__contact-name {
|
||||||
@include font-body-1;
|
@include font-body-1-bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__property {
|
&__property {
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
import type { ReactChild, ReactNode } from 'react';
|
import type { ReactChild, ReactNode } from 'react';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { concat, orderBy } from 'lodash';
|
|
||||||
|
|
||||||
import type { LocalizerType, ThemeType } from '../../types/Util';
|
import type { LocalizerType, ThemeType } from '../../types/Util';
|
||||||
import type { ConversationType } from '../../state/ducks/conversations';
|
import type { ConversationType } from '../../state/ducks/conversations';
|
||||||
|
@ -235,115 +234,140 @@ export function ContactSpoofingReviewDialog(props: PropsType): JSX.Element {
|
||||||
}
|
}
|
||||||
case ContactSpoofingType.MultipleGroupMembersWithSameTitle: {
|
case ContactSpoofingType.MultipleGroupMembersWithSameTitle: {
|
||||||
const { group, collisionInfoByTitle } = props;
|
const { group, collisionInfoByTitle } = props;
|
||||||
|
const sharedTitles = Object.keys(collisionInfoByTitle);
|
||||||
const unsortedConversationInfos = concat(
|
const numSharedTitles = sharedTitles.length;
|
||||||
// This empty array exists to appease Lodash's type definitions.
|
const totalConversations = Object.values(collisionInfoByTitle).reduce(
|
||||||
[],
|
(sum, conversationInfos) => sum + conversationInfos.length,
|
||||||
...Object.values(collisionInfoByTitle)
|
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');
|
title = i18n('icu:ContactSpoofingReviewDialog__group__title');
|
||||||
contents = (
|
contents = (
|
||||||
<>
|
<>
|
||||||
<p>
|
<p className="module-ContactSpoofingReviewDialog__description">
|
||||||
{i18n('icu:ContactSpoofingReviewDialog__group__description', {
|
{numSharedTitles > 1
|
||||||
count: conversationInfos.length,
|
? i18n(
|
||||||
})}
|
'icu:ContactSpoofingReviewDialog__group__multiple-conflicts__description',
|
||||||
|
{
|
||||||
|
count: numSharedTitles,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
: i18n('icu:ContactSpoofingReviewDialog__group__description', {
|
||||||
|
count: totalConversations,
|
||||||
|
})}
|
||||||
</p>
|
</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;
|
{Object.values(collisionInfoByTitle).map(
|
||||||
const newName =
|
(conversationInfos, titleIdx) => {
|
||||||
conversationInfo.conversation.profileName ||
|
return (
|
||||||
conversationInfo.conversation.title;
|
<>
|
||||||
|
<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;
|
const { oldName } = conversationInfo;
|
||||||
if (oldName && oldName !== newName) {
|
const newName =
|
||||||
callout = (
|
conversationInfo.conversation.profileName ||
|
||||||
<div className="module-ContactSpoofingReviewDialogPerson__info__property module-ContactSpoofingReviewDialogPerson__info__property--callout">
|
conversationInfo.conversation.title;
|
||||||
<Intl
|
|
||||||
i18n={i18n}
|
|
||||||
id="icu:ContactSpoofingReviewDialog__group__name-change-info"
|
|
||||||
components={{
|
|
||||||
oldName: <UserText text={oldName} />,
|
|
||||||
newName: <UserText text={newName} />,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
let callout: JSX.Element | undefined;
|
||||||
<>
|
if (oldName && oldName !== newName) {
|
||||||
{index !== 0 && <hr />}
|
callout = (
|
||||||
<ContactSpoofingReviewDialogPerson
|
<div className="module-ContactSpoofingReviewDialogPerson__info__property module-ContactSpoofingReviewDialogPerson__info__property--callout">
|
||||||
key={conversationInfo.conversation.id}
|
<Intl
|
||||||
conversation={conversationInfo.conversation}
|
i18n={i18n}
|
||||||
getPreferredBadge={getPreferredBadge}
|
id="icu:ContactSpoofingReviewDialog__group__name-change-info"
|
||||||
i18n={i18n}
|
components={{
|
||||||
theme={theme}
|
oldName: <UserText text={oldName} />,
|
||||||
>
|
newName: <UserText text={newName} />,
|
||||||
{callout}
|
}}
|
||||||
{button && (
|
/>
|
||||||
<div className="module-ContactSpoofingReviewDialog__buttons">
|
</div>
|
||||||
{button}
|
);
|
||||||
</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;
|
break;
|
||||||
|
|
|
@ -19,6 +19,7 @@ import { clearTimeoutIfNecessary } from '../../util/clearTimeoutIfNecessary';
|
||||||
import { WidthBreakpoint } from '../_util';
|
import { WidthBreakpoint } from '../_util';
|
||||||
|
|
||||||
import { ErrorBoundary } from './ErrorBoundary';
|
import { ErrorBoundary } from './ErrorBoundary';
|
||||||
|
import type { FullJSXType } from '../Intl';
|
||||||
import { Intl } from '../Intl';
|
import { Intl } from '../Intl';
|
||||||
import { TimelineWarning } from './TimelineWarning';
|
import { TimelineWarning } from './TimelineWarning';
|
||||||
import { TimelineWarnings } from './TimelineWarnings';
|
import { TimelineWarnings } from './TimelineWarnings';
|
||||||
|
@ -941,29 +942,42 @@ export class Timeline extends React.Component<
|
||||||
break;
|
break;
|
||||||
case ContactSpoofingType.MultipleGroupMembersWithSameTitle: {
|
case ContactSpoofingType.MultipleGroupMembersWithSameTitle: {
|
||||||
const { groupNameCollisions } = warning;
|
const { groupNameCollisions } = warning;
|
||||||
text = (
|
const numberOfSharedNames = Object.keys(groupNameCollisions).length;
|
||||||
<Intl
|
const reviewRequestLink: FullJSXType = parts => (
|
||||||
i18n={i18n}
|
<TimelineWarning.Link
|
||||||
id="icu:ContactSpoofing__same-name-in-group--link"
|
onClick={() => {
|
||||||
components={{
|
reviewGroupMemberNameCollision(id);
|
||||||
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>
|
|
||||||
),
|
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
|
{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 = () => {
|
onClose = () => {
|
||||||
acknowledgeGroupMemberNameCollisions(id, groupNameCollisions);
|
acknowledgeGroupMemberNameCollisions(id, groupNameCollisions);
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue