Show "no groups in common" warning for relevant message requests

This commit is contained in:
Evan Hahn 2021-04-30 17:58:57 -05:00 committed by GitHub
parent 05703c2719
commit fe772af251
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 321 additions and 122 deletions

View file

@ -3142,6 +3142,10 @@
"message": "No groups in common", "message": "No groups in common",
"description": "Shown to indicate this user is not a member of any groups" "description": "Shown to indicate this user is not a member of any groups"
}, },
"no-groups-in-common-warning": {
"message": "No groups in common. Review requests carefully.",
"description": "When a user has no common groups, show this warning"
},
"acceptCall": { "acceptCall": {
"message": "Answer", "message": "Answer",
"description": "Shown in tooltip for the button to accept a call (audio or video)" "description": "Shown in tooltip for the button to accept a call (audio or video)"
@ -5165,6 +5169,18 @@
"message": "Continue", "message": "Continue",
"description": "aria-label for the 'next' button in the forward a message modal dialog" "description": "aria-label for the 'next' button in the forward a message modal dialog"
}, },
"MessageRequestWarning__learn-more": {
"message": "Learn more",
"description": "Shown on the message request warning. Clicking this button will open a dialog with more information"
},
"MessageRequestWarning__dialog__details": {
"message": "You have no groups in common with this person. Review requests carefully before accepting to avoid unwanted messages.",
"description": "Shown in the message request warning dialog. Gives more information about message requests"
},
"MessageRequestWarning__dialog__learn-even-more": {
"message": "About Message Requests",
"description": "Shown in the message request warning dialog. Clicking this button will open a page on Signal's support site"
},
"ContactSpoofing__same-name": { "ContactSpoofing__same-name": {
"message": "Review requests carefully. Signal found another contact with the same name. $link$", "message": "Review requests carefully. Signal found another contact with the same name. $link$",
"description": "Shown in the timeline warning when you have a message request from someone with the same name as someone else", "description": "Shown in the timeline warning when you have a message request from someone with the same name as someone else",

View file

@ -106,7 +106,12 @@
} }
} }
// Smooth scrolling // Utilities
@mixin rounded-corners() {
// This ensures the borders are completely rounded. (A value like 100% would make it an ellipse.)
border-radius: 9999px;
}
@mixin smooth-scroll() { @mixin smooth-scroll() {
scroll-behavior: smooth; scroll-behavior: smooth;
@ -472,7 +477,7 @@
} }
@mixin button-small { @mixin button-small {
border-radius: 9999px; // This ensures the borders are completely rounded. (A value like 100% would make it an ellipse.) @include rounded-corners;
padding: 7px 14px; padding: 7px 14px;
} }

View file

@ -3934,6 +3934,46 @@ button.module-conversation-details__action-button {
@include font-body-2-bold; @include font-body-2-bold;
} }
} }
&__message-request-warning {
@include font-body-2;
&__message {
display: flex;
margin-bottom: 12px;
align-items: center;
justify-content: center;
user-select: none;
@include light-theme {
color: $color-gray-60;
}
@include dark-theme {
color: $color-gray-25;
}
&::before {
content: '';
display: block;
height: 14px;
margin-right: 8px;
width: 14px;
@include light-theme {
@include color-svg(
'../images/icons/v2/info-outline-24.svg',
$color-gray-60
);
}
@include dark-theme {
@include color-svg(
'../images/icons/v2/info-solid-24.svg',
$color-gray-25
);
}
}
}
}
} }
// Module: Message Request Actions // Module: Message Request Actions

View file

@ -18,8 +18,6 @@
} }
@include button-reset; @include button-reset;
@include font-body-1-bold;
border-radius: 4px; border-radius: 4px;
padding: 8px 16px; padding: 8px 16px;
text-align: center; text-align: center;
@ -37,6 +35,16 @@
cursor: not-allowed; cursor: not-allowed;
} }
&--medium {
@include font-body-1-bold;
}
&--small {
@include font-body-2;
@include rounded-corners;
padding: 6px 12px;
}
&--primary { &--primary {
$color: $color-white; $color: $color-white;
$background-color: $ultramarine-ui-light; $background-color: $ultramarine-ui-light;

View file

@ -2,8 +2,8 @@
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
.module-ContactPill { .module-ContactPill {
@include rounded-corners;
align-items: center; align-items: center;
border-radius: 9999px; // This ensures the borders are completely rounded. (A value like 100% would make it an ellipse.)
display: inline-flex; display: inline-flex;
user-select: none; user-select: none;
overflow: hidden; overflow: hidden;

View file

@ -279,9 +279,9 @@
&--join-call { &--join-call {
@include font-body-1; @include font-body-1;
@include rounded-corners;
align-items: center; align-items: center;
background-color: $color-accent-green; background-color: $color-accent-green;
border-radius: 9999px; // This ensures the borders are completely rounded. (A value like 100% would make it an ellipse.)
color: $color-white; color: $color-white;
display: flex; display: flex;
outline: none; outline: none;

View file

@ -5,30 +5,39 @@ import React from 'react';
import { storiesOf } from '@storybook/react'; import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions'; import { action } from '@storybook/addon-actions';
import { Button, ButtonVariant } from './Button'; import { Button, ButtonSize, ButtonVariant } from './Button';
const story = storiesOf('Components/Button', module); const story = storiesOf('Components/Button', module);
story.add('Kitchen sink', () => ( story.add('Kitchen sink', () => (
<> <>
{[ {[ButtonSize.Medium, ButtonSize.Small].map(size => (
ButtonVariant.Primary, <React.Fragment key={size}>
ButtonVariant.Secondary, {[
ButtonVariant.SecondaryAffirmative, ButtonVariant.Primary,
ButtonVariant.SecondaryDestructive, ButtonVariant.Secondary,
ButtonVariant.Destructive, ButtonVariant.SecondaryAffirmative,
].map(variant => ( ButtonVariant.SecondaryDestructive,
<React.Fragment key={variant}> ButtonVariant.Destructive,
<p> ].map(variant => (
<Button onClick={action('onClick')} variant={variant}> <React.Fragment key={variant}>
Hello world <p>
</Button> <Button onClick={action('onClick')} size={size} variant={variant}>
</p> Hello world
<p> </Button>
<Button disabled onClick={action('onClick')} variant={variant}> </p>
Hello world <p>
</Button> <Button
</p> disabled
onClick={action('onClick')}
size={size}
variant={variant}
>
Hello world
</Button>
</p>
</React.Fragment>
))}
</React.Fragment> </React.Fragment>
))} ))}
</> </>

View file

@ -6,6 +6,11 @@ import classNames from 'classnames';
import { assert } from '../util/assert'; import { assert } from '../util/assert';
export enum ButtonSize {
Medium,
Small,
}
export enum ButtonVariant { export enum ButtonVariant {
Primary, Primary,
Secondary, Secondary,
@ -17,6 +22,7 @@ export enum ButtonVariant {
type PropsType = { type PropsType = {
className?: string; className?: string;
disabled?: boolean; disabled?: boolean;
size?: ButtonSize;
variant?: ButtonVariant; variant?: ButtonVariant;
} & ( } & (
| { | {
@ -41,6 +47,11 @@ type PropsType = {
} }
); );
const SIZE_CLASS_NAMES = new Map<ButtonSize, string>([
[ButtonSize.Medium, 'module-Button--medium'],
[ButtonSize.Small, 'module-Button--small'],
]);
const VARIANT_CLASS_NAMES = new Map<ButtonVariant, string>([ const VARIANT_CLASS_NAMES = new Map<ButtonVariant, string>([
[ButtonVariant.Primary, 'module-Button--primary'], [ButtonVariant.Primary, 'module-Button--primary'],
[ButtonVariant.Secondary, 'module-Button--secondary'], [ButtonVariant.Secondary, 'module-Button--secondary'],
@ -61,6 +72,7 @@ export const Button = React.forwardRef<HTMLButtonElement, PropsType>(
children, children,
className, className,
disabled = false, disabled = false,
size = ButtonSize.Medium,
variant = ButtonVariant.Primary, variant = ButtonVariant.Primary,
} = props; } = props;
const ariaLabel = props['aria-label']; const ariaLabel = props['aria-label'];
@ -75,13 +87,21 @@ export const Button = React.forwardRef<HTMLButtonElement, PropsType>(
({ type } = props); ({ type } = props);
} }
const sizeClassName = SIZE_CLASS_NAMES.get(size);
assert(sizeClassName, '<Button> size not found');
const variantClassName = VARIANT_CLASS_NAMES.get(variant); const variantClassName = VARIANT_CLASS_NAMES.get(variant);
assert(variantClassName, '<Button> variant not found'); assert(variantClassName, '<Button> variant not found');
return ( return (
<button <button
aria-label={ariaLabel} aria-label={ariaLabel}
className={classNames('module-Button', variantClassName, className)} className={classNames(
'module-Button',
sizeClassName,
variantClassName,
className
)}
disabled={disabled} disabled={disabled}
onClick={onClick} onClick={onClick}
ref={ref} ref={ref}

View file

@ -1,4 +1,4 @@
// Copyright 2020 Signal Messenger, LLC // Copyright 2020-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from 'react';
@ -20,12 +20,15 @@ const getAvatarPath = () =>
text('avatarPath', '/fixtures/kitten-4-112-112.jpg'); text('avatarPath', '/fixtures/kitten-4-112-112.jpg');
const getPhoneNumber = () => text('phoneNumber', '+1 (646) 327-2700'); const getPhoneNumber = () => text('phoneNumber', '+1 (646) 327-2700');
const updateSharedGroups = action('updateSharedGroups');
storiesOf('Components/Conversation/ConversationHero', module) storiesOf('Components/Conversation/ConversationHero', module)
.add('Direct (Three Other Groups)', () => { .add('Direct (Three Other Groups)', () => {
return ( return (
<div style={{ width: '480px' }}> <div style={{ width: '480px' }}>
<ConversationHero <ConversationHero
about={getAbout()} about={getAbout()}
acceptedMessageRequest
i18n={i18n} i18n={i18n}
title={getTitle()} title={getTitle()}
avatarPath={getAvatarPath()} avatarPath={getAvatarPath()}
@ -33,6 +36,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
profileName={getProfileName()} profileName={getProfileName()}
phoneNumber={getPhoneNumber()} phoneNumber={getPhoneNumber()}
conversationType="direct" conversationType="direct"
updateSharedGroups={updateSharedGroups}
sharedGroupNames={['NYC Rock Climbers', 'Dinner Party', 'Friends 🌿']} sharedGroupNames={['NYC Rock Climbers', 'Dinner Party', 'Friends 🌿']}
unblurAvatar={action('unblurAvatar')} unblurAvatar={action('unblurAvatar')}
/> />
@ -44,6 +48,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
<div style={{ width: '480px' }}> <div style={{ width: '480px' }}>
<ConversationHero <ConversationHero
about={getAbout()} about={getAbout()}
acceptedMessageRequest
i18n={i18n} i18n={i18n}
title={getTitle()} title={getTitle()}
avatarPath={getAvatarPath()} avatarPath={getAvatarPath()}
@ -51,6 +56,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
profileName={getProfileName()} profileName={getProfileName()}
phoneNumber={getPhoneNumber()} phoneNumber={getPhoneNumber()}
conversationType="direct" conversationType="direct"
updateSharedGroups={updateSharedGroups}
sharedGroupNames={['NYC Rock Climbers', 'Dinner Party']} sharedGroupNames={['NYC Rock Climbers', 'Dinner Party']}
unblurAvatar={action('unblurAvatar')} unblurAvatar={action('unblurAvatar')}
/> />
@ -62,6 +68,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
<div style={{ width: '480px' }}> <div style={{ width: '480px' }}>
<ConversationHero <ConversationHero
about={getAbout()} about={getAbout()}
acceptedMessageRequest
i18n={i18n} i18n={i18n}
title={getTitle()} title={getTitle()}
avatarPath={getAvatarPath()} avatarPath={getAvatarPath()}
@ -69,6 +76,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
profileName={getProfileName()} profileName={getProfileName()}
phoneNumber={getPhoneNumber()} phoneNumber={getPhoneNumber()}
conversationType="direct" conversationType="direct"
updateSharedGroups={updateSharedGroups}
sharedGroupNames={['NYC Rock Climbers']} sharedGroupNames={['NYC Rock Climbers']}
unblurAvatar={action('unblurAvatar')} unblurAvatar={action('unblurAvatar')}
/> />
@ -80,6 +88,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
<div style={{ width: '480px' }}> <div style={{ width: '480px' }}>
<ConversationHero <ConversationHero
about={getAbout()} about={getAbout()}
acceptedMessageRequest
i18n={i18n} i18n={i18n}
title={getTitle()} title={getTitle()}
avatarPath={getAvatarPath()} avatarPath={getAvatarPath()}
@ -87,6 +96,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
profileName={text('profileName', '')} profileName={text('profileName', '')}
phoneNumber={getPhoneNumber()} phoneNumber={getPhoneNumber()}
conversationType="direct" conversationType="direct"
updateSharedGroups={updateSharedGroups}
sharedGroupNames={[]} sharedGroupNames={[]}
unblurAvatar={action('unblurAvatar')} unblurAvatar={action('unblurAvatar')}
/> />
@ -98,6 +108,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
<div style={{ width: '480px' }}> <div style={{ width: '480px' }}>
<ConversationHero <ConversationHero
about={getAbout()} about={getAbout()}
acceptedMessageRequest
i18n={i18n} i18n={i18n}
title={text('title', 'Cayce Bollard (profile)')} title={text('title', 'Cayce Bollard (profile)')}
avatarPath={getAvatarPath()} avatarPath={getAvatarPath()}
@ -105,6 +116,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
profileName={getProfileName()} profileName={getProfileName()}
phoneNumber={getPhoneNumber()} phoneNumber={getPhoneNumber()}
conversationType="direct" conversationType="direct"
updateSharedGroups={updateSharedGroups}
sharedGroupNames={[]} sharedGroupNames={[]}
unblurAvatar={action('unblurAvatar')} unblurAvatar={action('unblurAvatar')}
/> />
@ -116,6 +128,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
<div style={{ width: '480px' }}> <div style={{ width: '480px' }}>
<ConversationHero <ConversationHero
about={getAbout()} about={getAbout()}
acceptedMessageRequest
i18n={i18n} i18n={i18n}
title={text('title', '+1 (646) 327-2700')} title={text('title', '+1 (646) 327-2700')}
avatarPath={getAvatarPath()} avatarPath={getAvatarPath()}
@ -123,6 +136,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
profileName={text('profileName', '')} profileName={text('profileName', '')}
phoneNumber={getPhoneNumber()} phoneNumber={getPhoneNumber()}
conversationType="direct" conversationType="direct"
updateSharedGroups={updateSharedGroups}
sharedGroupNames={[]} sharedGroupNames={[]}
unblurAvatar={action('unblurAvatar')} unblurAvatar={action('unblurAvatar')}
/> />
@ -135,6 +149,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
<ConversationHero <ConversationHero
i18n={i18n} i18n={i18n}
title={text('title', 'Unknown contact')} title={text('title', 'Unknown contact')}
acceptedMessageRequest
avatarPath={getAvatarPath()} avatarPath={getAvatarPath()}
name={text('name', '')} name={text('name', '')}
profileName={text('profileName', '')} profileName={text('profileName', '')}
@ -142,6 +157,26 @@ storiesOf('Components/Conversation/ConversationHero', module)
conversationType="direct" conversationType="direct"
sharedGroupNames={[]} sharedGroupNames={[]}
unblurAvatar={action('unblurAvatar')} unblurAvatar={action('unblurAvatar')}
updateSharedGroups={updateSharedGroups}
/>
</div>
);
})
.add('Direct (No Groups, No Data, Not Accepted)', () => {
return (
<div style={{ width: '480px' }}>
<ConversationHero
i18n={i18n}
title={text('title', 'Unknown contact')}
acceptedMessageRequest={false}
avatarPath={getAvatarPath()}
name={text('name', '')}
profileName={text('profileName', '')}
phoneNumber={text('phoneNumber', '')}
conversationType="direct"
sharedGroupNames={[]}
unblurAvatar={action('unblurAvatar')}
updateSharedGroups={updateSharedGroups}
/> />
</div> </div>
); );
@ -150,12 +185,14 @@ storiesOf('Components/Conversation/ConversationHero', module)
return ( return (
<div style={{ width: '480px' }}> <div style={{ width: '480px' }}>
<ConversationHero <ConversationHero
acceptedMessageRequest
i18n={i18n} i18n={i18n}
title={text('title', 'NYC Rock Climbers')} title={text('title', 'NYC Rock Climbers')}
name={text('groupName', 'NYC Rock Climbers')} name={text('groupName', 'NYC Rock Climbers')}
conversationType="group" conversationType="group"
membersCount={numberKnob('membersCount', 22)} membersCount={numberKnob('membersCount', 22)}
unblurAvatar={action('unblurAvatar')} unblurAvatar={action('unblurAvatar')}
updateSharedGroups={updateSharedGroups}
/> />
</div> </div>
); );
@ -164,12 +201,14 @@ storiesOf('Components/Conversation/ConversationHero', module)
return ( return (
<div style={{ width: '480px' }}> <div style={{ width: '480px' }}>
<ConversationHero <ConversationHero
acceptedMessageRequest
i18n={i18n} i18n={i18n}
title={text('title', 'NYC Rock Climbers')} title={text('title', 'NYC Rock Climbers')}
name={text('groupName', 'NYC Rock Climbers')} name={text('groupName', 'NYC Rock Climbers')}
conversationType="group" conversationType="group"
membersCount={1} membersCount={1}
unblurAvatar={action('unblurAvatar')} unblurAvatar={action('unblurAvatar')}
updateSharedGroups={updateSharedGroups}
/> />
</div> </div>
); );
@ -178,12 +217,14 @@ storiesOf('Components/Conversation/ConversationHero', module)
return ( return (
<div style={{ width: '480px' }}> <div style={{ width: '480px' }}>
<ConversationHero <ConversationHero
acceptedMessageRequest
i18n={i18n} i18n={i18n}
title={text('title', 'NYC Rock Climbers')} title={text('title', 'NYC Rock Climbers')}
name={text('groupName', 'NYC Rock Climbers')} name={text('groupName', 'NYC Rock Climbers')}
conversationType="group" conversationType="group"
membersCount={0} membersCount={0}
unblurAvatar={action('unblurAvatar')} unblurAvatar={action('unblurAvatar')}
updateSharedGroups={updateSharedGroups}
/> />
</div> </div>
); );
@ -192,12 +233,14 @@ storiesOf('Components/Conversation/ConversationHero', module)
return ( return (
<div style={{ width: '480px' }}> <div style={{ width: '480px' }}>
<ConversationHero <ConversationHero
acceptedMessageRequest
i18n={i18n} i18n={i18n}
title={text('title', 'Unknown group')} title={text('title', 'Unknown group')}
name={text('groupName', '')} name={text('groupName', '')}
conversationType="group" conversationType="group"
membersCount={0} membersCount={0}
unblurAvatar={action('unblurAvatar')} unblurAvatar={action('unblurAvatar')}
updateSharedGroups={updateSharedGroups}
/> />
</div> </div>
); );
@ -212,6 +255,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
conversationType="direct" conversationType="direct"
phoneNumber={getPhoneNumber()} phoneNumber={getPhoneNumber()}
unblurAvatar={action('unblurAvatar')} unblurAvatar={action('unblurAvatar')}
updateSharedGroups={updateSharedGroups}
/> />
</div> </div>
); );

View file

@ -1,12 +1,16 @@
// Copyright 2020-2021 Signal Messenger, LLC // Copyright 2020-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import React, { useEffect, useRef, useState } from 'react';
import Measure from 'react-measure';
import { Avatar, AvatarBlur, Props as AvatarProps } from '../Avatar'; import { Avatar, AvatarBlur, Props as AvatarProps } from '../Avatar';
import { ContactName } from './ContactName'; import { ContactName } from './ContactName';
import { About } from './About'; import { About } from './About';
import { SharedGroupNames } from '../SharedGroupNames'; import { SharedGroupNames } from '../SharedGroupNames';
import { LocalizerType } from '../../types/Util'; import { LocalizerType } from '../../types/Util';
import { ConfirmationDialog } from '../ConfirmationDialog';
import { Button, ButtonSize, ButtonVariant } from '../Button';
import { assert } from '../../util/assert';
import { shouldBlurAvatar } from '../../util/shouldBlurAvatar'; import { shouldBlurAvatar } from '../../util/shouldBlurAvatar';
export type Props = { export type Props = {
@ -14,25 +18,34 @@ export type Props = {
acceptedMessageRequest?: boolean; acceptedMessageRequest?: boolean;
i18n: LocalizerType; i18n: LocalizerType;
isMe?: boolean; isMe?: boolean;
sharedGroupNames?: Array<string>;
membersCount?: number; membersCount?: number;
phoneNumber?: string;
onHeightChange?: () => unknown; onHeightChange?: () => unknown;
phoneNumber?: string;
sharedGroupNames?: Array<string>;
unblurAvatar: () => void; unblurAvatar: () => void;
unblurredAvatarPath?: string; unblurredAvatarPath?: string;
updateSharedGroups?: () => unknown; updateSharedGroups: () => unknown;
} & Omit<AvatarProps, 'onClick' | 'size' | 'noteToSelf'>; } & Omit<AvatarProps, 'onClick' | 'size' | 'noteToSelf'>;
const renderMembershipRow = ({ const renderMembershipRow = ({
i18n, acceptedMessageRequest,
phoneNumber,
sharedGroupNames = [],
conversationType, conversationType,
i18n,
isMe, isMe,
onClickMessageRequestWarning,
phoneNumber,
sharedGroupNames,
}: Pick< }: Pick<
Props, Props,
'i18n' | 'phoneNumber' | 'sharedGroupNames' | 'conversationType' | 'isMe' | 'acceptedMessageRequest'
>) => { | 'conversationType'
| 'i18n'
| 'isMe'
| 'phoneNumber'
> &
Required<Pick<Props, 'sharedGroupNames'>> & {
onClickMessageRequestWarning: () => void;
}) => {
const className = 'module-conversation-hero__membership'; const className = 'module-conversation-hero__membership';
if (conversationType !== 'direct') { if (conversationType !== 'direct') {
@ -54,12 +67,27 @@ const renderMembershipRow = ({
</div> </div>
); );
} }
if (acceptedMessageRequest) {
if (!phoneNumber) { if (phoneNumber) {
return null;
}
return <div className={className}>{i18n('no-groups-in-common')}</div>; return <div className={className}>{i18n('no-groups-in-common')}</div>;
} }
return null; return (
<div className="module-conversation-hero__message-request-warning">
<div className="module-conversation-hero__message-request-warning__message">
{i18n('no-groups-in-common-warning')}
</div>
<Button
onClick={onClickMessageRequestWarning}
size={ButtonSize.Small}
variant={ButtonVariant.SecondaryAffirmative}
>
{i18n('MessageRequestWarning__learn-more')}
</Button>
</div>
);
}; };
export const ConversationHero = ({ export const ConversationHero = ({
@ -81,37 +109,31 @@ export const ConversationHero = ({
unblurredAvatarPath, unblurredAvatarPath,
updateSharedGroups, updateSharedGroups,
}: Props): JSX.Element => { }: Props): JSX.Element => {
const firstRenderRef = React.useRef(true); const firstRenderRef = useRef(true);
// TODO: DESKTOP-686 const [height, setHeight] = useState<undefined | number>();
/* eslint-disable react-hooks/exhaustive-deps */ const [
React.useEffect(() => { isShowingMessageRequestWarning,
// If any of the depenencies for this hook change then the height of this setIsShowingMessageRequestWarning,
// component may have changed. The cleanup function notifies listeners of ] = useState(false);
// any potential height changes. const closeMessageRequestWarning = () => {
return () => { setIsShowingMessageRequestWarning(false);
// Kick off the expensive hydration of the current sharedGroupNames };
if (updateSharedGroups) {
updateSharedGroups();
}
if (onHeightChange && !firstRenderRef.current) { useEffect(() => {
onHeightChange(); // Kick off the expensive hydration of the current sharedGroupNames
} else { updateSharedGroups();
firstRenderRef.current = false; }, [updateSharedGroups]);
}
}; useEffect(() => {
}, [ firstRenderRef.current = false;
firstRenderRef, }, []);
onHeightChange,
// Avoid collisions in these dependencies by prefixing them useEffect(() => {
// These dependencies may be dynamic, and therefore may cause height changes if (!firstRenderRef.current && onHeightChange) {
`mc-${membersCount}`, onHeightChange();
`n-${name}`, }
`pn-${profileName}`, }, [height, onHeightChange]);
sharedGroupNames.map(g => `g-${g}`).join(' '),
]);
/* eslint-enable react-hooks/exhaustive-deps */
let avatarBlur: AvatarBlur; let avatarBlur: AvatarBlur;
let avatarOnClick: undefined | (() => void); let avatarOnClick: undefined | (() => void);
@ -136,58 +158,92 @@ export const ConversationHero = ({
/* eslint-disable no-nested-ternary */ /* eslint-disable no-nested-ternary */
return ( return (
<div className="module-conversation-hero"> <>
<Avatar <Measure
i18n={i18n} bounds
blur={avatarBlur} onResize={({ bounds }) => {
color={color} assert(bounds, 'We should be measuring the bounds');
noteToSelf={isMe} setHeight(bounds.height);
avatarPath={avatarPath} }}
conversationType={conversationType} >
name={name} {({ measureRef }) => (
onClick={avatarOnClick} <div className="module-conversation-hero" ref={measureRef}>
profileName={profileName} <Avatar
title={title} i18n={i18n}
size={112} blur={avatarBlur}
className="module-conversation-hero__avatar" color={color}
/> noteToSelf={isMe}
<h1 className="module-conversation-hero__profile-name"> avatarPath={avatarPath}
{isMe ? ( conversationType={conversationType}
i18n('noteToSelf') name={name}
) : ( onClick={avatarOnClick}
<ContactName profileName={profileName}
title={title} title={title}
name={name} size={112}
profileName={profileName} className="module-conversation-hero__avatar"
phoneNumber={phoneNumber} />
i18n={i18n} <h1 className="module-conversation-hero__profile-name">
/> {isMe ? (
i18n('noteToSelf')
) : (
<ContactName
title={title}
name={name}
profileName={profileName}
phoneNumber={phoneNumber}
i18n={i18n}
/>
)}
</h1>
{about && !isMe && (
<div className="module-about__container">
<About text={about} />
</div>
)}
{!isMe ? (
<div className="module-conversation-hero__with">
{membersCount === 1
? i18n('ConversationHero--members-1')
: membersCount !== undefined
? i18n('ConversationHero--members', [`${membersCount}`])
: phoneNumberOnly
? null
: phoneNumber}
</div>
) : null}
{renderMembershipRow({
acceptedMessageRequest,
conversationType,
i18n,
isMe,
onClickMessageRequestWarning() {
setIsShowingMessageRequestWarning(true);
},
phoneNumber,
sharedGroupNames,
})}
</div>
)} )}
</h1> </Measure>
{about && !isMe && ( {isShowingMessageRequestWarning && (
<div className="module-about__container"> <ConfirmationDialog
<About text={about} /> i18n={i18n}
</div> onClose={closeMessageRequestWarning}
actions={[
{
text: i18n('MessageRequestWarning__dialog__learn-even-more'),
action: () => {
window.location.href =
'https://support.signal.org/hc/articles/360007459591';
closeMessageRequestWarning();
},
},
]}
>
{i18n('MessageRequestWarning__dialog__details')}
</ConfirmationDialog>
)} )}
{!isMe ? ( </>
<div className="module-conversation-hero__with">
{membersCount === 1
? i18n('ConversationHero--members-1')
: membersCount !== undefined
? i18n('ConversationHero--members', [`${membersCount}`])
: phoneNumberOnly
? null
: phoneNumber}
</div>
) : null}
{renderMembershipRow({
conversationType,
i18n,
isMe,
phoneNumber,
sharedGroupNames,
})}
</div>
); );
/* eslint-enable no-nested-ternary */ /* eslint-enable no-nested-ternary */
}; };

View file

@ -315,6 +315,7 @@ const renderHeroRow = () => (
conversationType="direct" conversationType="direct"
sharedGroupNames={['NYC Rock Climbers', 'Dinner Party']} sharedGroupNames={['NYC Rock Climbers', 'Dinner Party']}
unblurAvatar={action('unblurAvatar')} unblurAvatar={action('unblurAvatar')}
updateSharedGroups={noop}
/> />
); );
const renderLoadingRow = () => <TimelineLoadingRow state="loading" />; const renderLoadingRow = () => <TimelineLoadingRow state="loading" />;

View file

@ -16509,8 +16509,8 @@
{ {
"rule": "React-useRef", "rule": "React-useRef",
"path": "ts/components/conversation/ConversationHero.js", "path": "ts/components/conversation/ConversationHero.js",
"line": " const firstRenderRef = React.useRef(true);", "line": " const firstRenderRef = react_1.useRef(true);",
"lineNumber": 49, "lineNumber": 61,
"reasonCategory": "falseMatch", "reasonCategory": "falseMatch",
"updated": "2020-10-26T19:12:24.410Z", "updated": "2020-10-26T19:12:24.410Z",
"reasonDetail": "Doesn't refer to a DOM element." "reasonDetail": "Doesn't refer to a DOM element."
@ -16518,8 +16518,8 @@
{ {
"rule": "React-useRef", "rule": "React-useRef",
"path": "ts/components/conversation/ConversationHero.tsx", "path": "ts/components/conversation/ConversationHero.tsx",
"line": " const firstRenderRef = React.useRef(true);", "line": " const firstRenderRef = useRef(true);",
"lineNumber": 84, "lineNumber": 112,
"reasonCategory": "falseMatch", "reasonCategory": "falseMatch",
"updated": "2020-10-26T19:12:24.410Z", "updated": "2020-10-26T19:12:24.410Z",
"reasonDetail": "Doesn't refer to a DOM element." "reasonDetail": "Doesn't refer to a DOM element."