Streamlined system messages
This commit is contained in:
parent
1973224adb
commit
2b08cbfdfe
57 changed files with 864 additions and 937 deletions
|
@ -11,20 +11,21 @@ const story = storiesOf('Components/Button', module);
|
|||
|
||||
story.add('Kitchen sink', () => (
|
||||
<>
|
||||
{[ButtonSize.Medium, ButtonSize.Small].map(size => (
|
||||
<React.Fragment key={size}>
|
||||
{[
|
||||
ButtonVariant.Primary,
|
||||
ButtonVariant.Secondary,
|
||||
ButtonVariant.SecondaryAffirmative,
|
||||
ButtonVariant.SecondaryDestructive,
|
||||
ButtonVariant.Destructive,
|
||||
ButtonVariant.Calling,
|
||||
].map(variant => (
|
||||
<React.Fragment key={variant}>
|
||||
{[
|
||||
ButtonVariant.Primary,
|
||||
ButtonVariant.Secondary,
|
||||
ButtonVariant.SecondaryAffirmative,
|
||||
ButtonVariant.SecondaryDestructive,
|
||||
ButtonVariant.Destructive,
|
||||
ButtonVariant.Calling,
|
||||
ButtonVariant.SystemMessage,
|
||||
].map(variant => (
|
||||
<React.Fragment key={variant}>
|
||||
{[ButtonSize.Medium, ButtonSize.Small].map(size => (
|
||||
<React.Fragment key={size}>
|
||||
<p>
|
||||
<Button onClick={action('onClick')} size={size} variant={variant}>
|
||||
Hello world
|
||||
{variant}
|
||||
</Button>
|
||||
</p>
|
||||
<p>
|
||||
|
@ -34,7 +35,7 @@ story.add('Kitchen sink', () => (
|
|||
size={size}
|
||||
variant={variant}
|
||||
>
|
||||
Hello world
|
||||
{variant}
|
||||
</Button>
|
||||
</p>
|
||||
</React.Fragment>
|
||||
|
|
|
@ -12,12 +12,13 @@ export enum ButtonSize {
|
|||
}
|
||||
|
||||
export enum ButtonVariant {
|
||||
Primary,
|
||||
Secondary,
|
||||
SecondaryAffirmative,
|
||||
SecondaryDestructive,
|
||||
Destructive,
|
||||
Calling,
|
||||
Primary = 'Primary',
|
||||
Secondary = 'Secondary',
|
||||
SecondaryAffirmative = 'SecondaryAffirmative',
|
||||
SecondaryDestructive = 'SecondaryDestructive',
|
||||
Destructive = 'Destructive',
|
||||
Calling = 'Calling',
|
||||
SystemMessage = 'SystemMessage',
|
||||
}
|
||||
|
||||
type PropsType = {
|
||||
|
@ -68,6 +69,7 @@ const VARIANT_CLASS_NAMES = new Map<ButtonVariant, string>([
|
|||
],
|
||||
[ButtonVariant.Destructive, 'module-Button--destructive'],
|
||||
[ButtonVariant.Calling, 'module-Button--calling'],
|
||||
[ButtonVariant.SystemMessage, 'module-Button--system-message'],
|
||||
]);
|
||||
|
||||
export const Button = React.forwardRef<HTMLButtonElement, PropsType>(
|
||||
|
|
|
@ -2,13 +2,17 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import Measure from 'react-measure';
|
||||
import { noop } from 'lodash';
|
||||
|
||||
import { Button, ButtonSize, ButtonVariant } from '../Button';
|
||||
import { Timestamp } from './Timestamp';
|
||||
import { LocalizerType } from '../../types/Util';
|
||||
import { CallMode } from '../../types/Calling';
|
||||
import {
|
||||
CallingNotificationType,
|
||||
getCallingIcon,
|
||||
getCallingNotificationText,
|
||||
} from '../../util/callingNotification';
|
||||
import { usePrevious } from '../../util/hooks';
|
||||
|
@ -48,16 +52,18 @@ export const CallingNotification: React.FC<PropsType> = React.memo(props => {
|
|||
}
|
||||
}, [height, previousHeight, conversationId, messageId, messageSizeChanged]);
|
||||
|
||||
let hasButton = false;
|
||||
let timestamp: number;
|
||||
let callType: 'audio' | 'video';
|
||||
let wasMissed = false;
|
||||
switch (props.callMode) {
|
||||
case CallMode.Direct:
|
||||
timestamp = props.acceptedTime || props.endedTime;
|
||||
callType = props.wasVideoCall ? 'video' : 'audio';
|
||||
wasMissed =
|
||||
props.wasIncoming && !props.acceptedTime && !props.wasDeclined;
|
||||
break;
|
||||
case CallMode.Group:
|
||||
hasButton = !props.ended;
|
||||
timestamp = props.startedTime;
|
||||
callType = 'video';
|
||||
break;
|
||||
default:
|
||||
window.log.error(
|
||||
|
@ -66,6 +72,8 @@ export const CallingNotification: React.FC<PropsType> = React.memo(props => {
|
|||
return null;
|
||||
}
|
||||
|
||||
const icon = getCallingIcon(props);
|
||||
|
||||
return (
|
||||
<Measure
|
||||
bounds
|
||||
|
@ -79,24 +87,33 @@ export const CallingNotification: React.FC<PropsType> = React.memo(props => {
|
|||
>
|
||||
{({ measureRef }) => (
|
||||
<div
|
||||
className={`module-message-calling--notification module-message-calling--${callType}`}
|
||||
className={classNames('SystemMessage', 'SystemMessage--multiline', {
|
||||
'SystemMessage--error': wasMissed,
|
||||
})}
|
||||
ref={measureRef}
|
||||
>
|
||||
<div className={`module-message-calling--${callType}__icon`} />
|
||||
{getCallingNotificationText(props, i18n)}
|
||||
<div>
|
||||
<Timestamp
|
||||
i18n={i18n}
|
||||
timestamp={timestamp}
|
||||
extended
|
||||
direction="outgoing"
|
||||
withImageNoCaption={false}
|
||||
withSticker={false}
|
||||
withTapToViewExpired={false}
|
||||
module="module-message__metadata__date"
|
||||
<div className="SystemMessage__line">
|
||||
<div
|
||||
className={`SystemMessage__icon SystemMessage__icon--${icon}`}
|
||||
/>
|
||||
<div>
|
||||
{getCallingNotificationText(props, i18n)} ·{' '}
|
||||
<Timestamp
|
||||
direction="outgoing"
|
||||
extended
|
||||
i18n={i18n}
|
||||
timestamp={timestamp}
|
||||
withImageNoCaption={false}
|
||||
withSticker={false}
|
||||
withTapToViewExpired={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<CallingNotificationButton {...props} />
|
||||
{hasButton ? (
|
||||
<div className="SystemMessage__line">
|
||||
<CallingNotificationButton {...props} />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</Measure>
|
||||
|
@ -120,7 +137,7 @@ function CallingNotificationButton(props: PropsType) {
|
|||
|
||||
let buttonText: string;
|
||||
let disabledTooltipText: undefined | string;
|
||||
let onClick: undefined | (() => void);
|
||||
let onClick: () => void;
|
||||
if (activeCallConversationId) {
|
||||
if (activeCallConversationId === conversationId) {
|
||||
buttonText = i18n('calling__return');
|
||||
|
@ -130,6 +147,7 @@ function CallingNotificationButton(props: PropsType) {
|
|||
disabledTooltipText = i18n(
|
||||
'calling__call-notification__button__in-another-call-tooltip'
|
||||
);
|
||||
onClick = noop;
|
||||
}
|
||||
} else if (deviceCount >= maxDevices) {
|
||||
buttonText = i18n('calling__call-is-full');
|
||||
|
@ -137,6 +155,7 @@ function CallingNotificationButton(props: PropsType) {
|
|||
'calling__call-notification__button__call-full-tooltip',
|
||||
[String(deviceCount)]
|
||||
);
|
||||
onClick = noop;
|
||||
} else {
|
||||
buttonText = i18n('calling__join');
|
||||
onClick = () => {
|
||||
|
@ -145,14 +164,14 @@ function CallingNotificationButton(props: PropsType) {
|
|||
}
|
||||
|
||||
const button = (
|
||||
<button
|
||||
className="module-message-calling--notification__button"
|
||||
<Button
|
||||
disabled={Boolean(disabledTooltipText)}
|
||||
onClick={onClick}
|
||||
type="button"
|
||||
size={ButtonSize.Small}
|
||||
variant={ButtonVariant.SystemMessage}
|
||||
>
|
||||
{buttonText}
|
||||
</button>
|
||||
</Button>
|
||||
);
|
||||
|
||||
if (disabledTooltipText) {
|
||||
|
|
|
@ -21,14 +21,12 @@ export type PropsHousekeeping = {
|
|||
|
||||
export type Props = PropsData & PropsHousekeeping;
|
||||
|
||||
const CSS_MODULE = 'module-change-number-notification';
|
||||
|
||||
export const ChangeNumberNotification: React.FC<Props> = props => {
|
||||
const { i18n, sender, timestamp } = props;
|
||||
|
||||
return (
|
||||
<div className={CSS_MODULE}>
|
||||
<span className={`${CSS_MODULE}__icon`} />
|
||||
<div className="SystemMessage">
|
||||
<span className="SystemMessage__icon SystemMessage__icon--phone" />
|
||||
<Intl
|
||||
id="ChangeNumber--notification"
|
||||
components={{
|
||||
|
|
|
@ -5,6 +5,7 @@ import React, { useCallback, useState, ReactElement } from 'react';
|
|||
|
||||
import { LocalizerType } from '../../types/Util';
|
||||
|
||||
import { Button, ButtonSize, ButtonVariant } from '../Button';
|
||||
import { ChatSessionRefreshedDialog } from './ChatSessionRefreshedDialog';
|
||||
|
||||
type PropsHousekeepingType = {
|
||||
|
@ -36,18 +37,20 @@ export function ChatSessionRefreshedNotification(
|
|||
}, [contactSupport, setIsDialogOpen]);
|
||||
|
||||
return (
|
||||
<div className="module-chat-session-refreshed-notification">
|
||||
<div className="module-chat-session-refreshed-notification__first-line">
|
||||
<span className="module-chat-session-refreshed-notification__icon" />
|
||||
<div className="SystemMessage SystemMessage--multiline">
|
||||
<div className="SystemMessage__line">
|
||||
<span className="SystemMessage__icon SystemMessage__icon--session-refresh" />
|
||||
{i18n('ChatRefresh--notification')}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={openDialog}
|
||||
className="module-chat-session-refreshed-notification__button"
|
||||
>
|
||||
{i18n('ChatRefresh--learnMore')}
|
||||
</button>
|
||||
<div className="SystemMessage__line">
|
||||
<Button
|
||||
onClick={openDialog}
|
||||
size={ButtonSize.Small}
|
||||
variant={ButtonVariant.SystemMessage}
|
||||
>
|
||||
{i18n('ChatRefresh--learnMore')}
|
||||
</Button>
|
||||
</div>
|
||||
{isDialogOpen ? (
|
||||
<ChatSessionRefreshedDialog
|
||||
onClose={closeDialog}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
import React, { useCallback, useState, ReactElement } from 'react';
|
||||
|
||||
import { Button, ButtonSize, ButtonVariant } from '../Button';
|
||||
import { ConversationType } from '../../state/ducks/conversations';
|
||||
import { LocalizerType } from '../../types/Util';
|
||||
import { Intl } from '../Intl';
|
||||
|
@ -45,9 +46,9 @@ export function DeliveryIssueNotification(
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="module-delivery-issue-notification">
|
||||
<div className="module-delivery-issue-notification__first-line">
|
||||
<span className="module-delivery-issue-notification__icon" />
|
||||
<div className="SystemMessage SystemMessage--multiline">
|
||||
<div className="SystemMessage__line">
|
||||
<span className="SystemMessage__icon SystemMessage__icon--info" />
|
||||
<Intl
|
||||
id="DeliveryIssue--notification"
|
||||
components={{
|
||||
|
@ -56,13 +57,15 @@ export function DeliveryIssueNotification(
|
|||
i18n={i18n}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={openDialog}
|
||||
className="module-delivery-issue-notification__button"
|
||||
>
|
||||
{i18n('DeliveryIssue--learnMore')}
|
||||
</button>
|
||||
<div className="SystemMessage__line">
|
||||
<Button
|
||||
onClick={openDialog}
|
||||
size={ButtonSize.Small}
|
||||
variant={ButtonVariant.SystemMessage}
|
||||
>
|
||||
{i18n('DeliveryIssue--learnMore')}
|
||||
</Button>
|
||||
</div>
|
||||
{isDialogOpen ? (
|
||||
<DeliveryIssueDialog
|
||||
i18n={i18n}
|
||||
|
|
|
@ -157,19 +157,21 @@ export class GroupNotification extends React.Component<Props> {
|
|||
);
|
||||
|
||||
return (
|
||||
<div className="module-group-notification">
|
||||
{isLeftOnly ? null : (
|
||||
<>
|
||||
{fromLabel}
|
||||
<br />
|
||||
</>
|
||||
)}
|
||||
{(changes || []).map((change, i) => (
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
<div key={i} className="module-group-notification__change">
|
||||
{this.renderChange(change, from)}
|
||||
</div>
|
||||
))}
|
||||
<div className="SystemMessage">
|
||||
<div>
|
||||
{isLeftOnly ? null : (
|
||||
<>
|
||||
{fromLabel}
|
||||
<br />
|
||||
</>
|
||||
)}
|
||||
{(changes || []).map((change, i) => (
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
<div key={i} className="module-group-notification__change">
|
||||
{this.renderChange(change, from)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
import * as React from 'react';
|
||||
|
||||
import { Button, ButtonSize, ButtonVariant } from '../Button';
|
||||
import { LocalizerType } from '../../types/Util';
|
||||
import { ConversationType } from '../../state/ducks/conversations';
|
||||
import { Intl } from '../Intl';
|
||||
|
@ -34,28 +35,40 @@ export function GroupV1Migration(props: PropsType): React.ReactElement {
|
|||
}, [setShowingDialog]);
|
||||
|
||||
return (
|
||||
<div className="module-group-v1-migration">
|
||||
<div className="module-group-v1-migration--icon" />
|
||||
<div className="module-group-v1-migration--text">
|
||||
{i18n('GroupV1--Migration--was-upgraded')}
|
||||
</div>
|
||||
{areWeInvited ? (
|
||||
<div className="module-group-v1-migration--text">
|
||||
{i18n('GroupV1--Migration--invited--you')}
|
||||
<div className="SystemMessage SystemMessage--multiline">
|
||||
<div className="SystemMessage__line">
|
||||
<div className="SystemMessage__icon SystemMessage__icon--group" />
|
||||
<div>
|
||||
<div>{i18n('GroupV1--Migration--was-upgraded')}</div>
|
||||
<div>
|
||||
{areWeInvited ? (
|
||||
i18n('GroupV1--Migration--invited--you')
|
||||
) : (
|
||||
<>
|
||||
{renderUsers(
|
||||
invitedMembers,
|
||||
i18n,
|
||||
'GroupV1--Migration--invited'
|
||||
)}
|
||||
{renderUsers(
|
||||
droppedMembers,
|
||||
i18n,
|
||||
'GroupV1--Migration--removed'
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{renderUsers(invitedMembers, i18n, 'GroupV1--Migration--invited')}
|
||||
{renderUsers(droppedMembers, i18n, 'GroupV1--Migration--removed')}
|
||||
</>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="module-group-v1-migration--button"
|
||||
onClick={showDialog}
|
||||
>
|
||||
{i18n('GroupV1--Migration--learn-more')}
|
||||
</button>
|
||||
</div>
|
||||
<div className="SystemMessage__line">
|
||||
<Button
|
||||
onClick={showDialog}
|
||||
size={ButtonSize.Small}
|
||||
variant={ButtonVariant.SystemMessage}
|
||||
>
|
||||
{i18n('GroupV1--Migration--learn-more')}
|
||||
</Button>
|
||||
</div>
|
||||
{showingDialog ? (
|
||||
<GroupV1MigrationDialog
|
||||
areWeInvited={areWeInvited}
|
||||
|
|
|
@ -55,10 +55,19 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
type: 'avatar',
|
||||
removed: false,
|
||||
},
|
||||
{
|
||||
type: 'description',
|
||||
description:
|
||||
'This is a long description.\n\nWe need a dialog to view it all!\n\nIt has a link to https://example.com',
|
||||
},
|
||||
{
|
||||
type: 'member-add',
|
||||
conversationId: OUR_ID,
|
||||
},
|
||||
{
|
||||
type: 'description',
|
||||
description: 'Another description',
|
||||
},
|
||||
{
|
||||
type: 'member-privilege',
|
||||
conversationId: OUR_ID,
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React, { ReactElement, useState } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { get } from 'lodash';
|
||||
|
||||
import { ReplacementValuesType } from '../../types/I18N';
|
||||
import { FullJSXType, Intl } from '../Intl';
|
||||
|
@ -9,7 +11,7 @@ import { LocalizerType } from '../../types/Util';
|
|||
import { GroupDescriptionText } from '../GroupDescriptionText';
|
||||
import { Button, ButtonSize, ButtonVariant } from '../Button';
|
||||
|
||||
import { GroupV2ChangeType, GroupV2DescriptionChangeType } from '../../groups';
|
||||
import { GroupV2ChangeType, GroupV2ChangeDetailType } from '../../groups';
|
||||
|
||||
import { renderChange, SmartContactRendererType } from '../../groupChange';
|
||||
import { Modal } from '../Modal';
|
||||
|
@ -35,53 +37,141 @@ function renderStringToIntl(
|
|||
return <Intl id={id} i18n={i18n} components={components} />;
|
||||
}
|
||||
|
||||
export function GroupV2Change(props: PropsType): ReactElement {
|
||||
const { change, groupName, i18n, ourConversationId, renderContact } = props;
|
||||
type GroupIconType =
|
||||
| 'group'
|
||||
| 'group-access'
|
||||
| 'group-add'
|
||||
| 'group-approved'
|
||||
| 'group-avatar'
|
||||
| 'group-decline'
|
||||
| 'group-edit'
|
||||
| 'group-leave'
|
||||
| 'group-remove';
|
||||
|
||||
const [
|
||||
isGroupDescriptionDialogOpen,
|
||||
setIsGroupDescriptionDialogOpen,
|
||||
] = useState<boolean>(false);
|
||||
const changeToIconMap = new Map<string, GroupIconType>([
|
||||
['access-attributes', 'group-access'],
|
||||
['access-invite-link', 'group-access'],
|
||||
['access-members', 'group-access'],
|
||||
['admin-approval-add-one', 'group-add'],
|
||||
['admin-approval-remove-one', 'group-decline'],
|
||||
['announcements-only', 'group-access'],
|
||||
['avatar', 'group-avatar'],
|
||||
['description', 'group-edit'],
|
||||
['group-link-add', 'group-access'],
|
||||
['group-link-remove', 'group-access'],
|
||||
['group-link-reset', 'group-access'],
|
||||
['member-add', 'group-add'],
|
||||
['member-add-from-admin-approval', 'group-approved'],
|
||||
['member-add-from-invite', 'group-add'],
|
||||
['member-add-from-link', 'group-add'],
|
||||
['member-privilege', 'group-access'],
|
||||
['member-remove', 'group-remove'],
|
||||
['pending-add-many', 'group-add'],
|
||||
['pending-add-one', 'group-add'],
|
||||
['pending-remove-many', 'group-decline'],
|
||||
['pending-remove-one', 'group-decline'],
|
||||
['title', 'group-edit'],
|
||||
]);
|
||||
|
||||
const newGroupDescription = change.details.find(
|
||||
(item): item is GroupV2DescriptionChangeType =>
|
||||
Boolean(item.type === 'description' && item.description)
|
||||
)?.description;
|
||||
function getIcon(
|
||||
detail: GroupV2ChangeDetailType,
|
||||
fromId?: string
|
||||
): GroupIconType {
|
||||
const changeType = detail.type;
|
||||
let possibleIcon = changeToIconMap.get(changeType);
|
||||
const isSameId = fromId === get(detail, 'conversationId', null);
|
||||
if (isSameId) {
|
||||
if (changeType === 'member-remove') {
|
||||
possibleIcon = 'group-leave';
|
||||
}
|
||||
if (changeType === 'member-add-from-invite') {
|
||||
possibleIcon = 'group-approved';
|
||||
}
|
||||
}
|
||||
return possibleIcon || 'group';
|
||||
}
|
||||
|
||||
function GroupV2Detail({
|
||||
detail,
|
||||
i18n,
|
||||
fromId,
|
||||
onButtonClick,
|
||||
text,
|
||||
}: {
|
||||
detail: GroupV2ChangeDetailType;
|
||||
i18n: LocalizerType;
|
||||
fromId?: string;
|
||||
onButtonClick: (x: string) => unknown;
|
||||
text: FullJSXType;
|
||||
}): JSX.Element {
|
||||
const icon = getIcon(detail, fromId);
|
||||
|
||||
const newGroupDescription =
|
||||
detail.type === 'description' && get(detail, 'description');
|
||||
|
||||
return (
|
||||
<div className="module-group-v2-change">
|
||||
<div className="module-group-v2-change--icon" />
|
||||
{renderChange(change, {
|
||||
i18n,
|
||||
ourConversationId,
|
||||
renderContact,
|
||||
renderString: renderStringToIntl,
|
||||
}).map((item: FullJSXType, index: number) => (
|
||||
// Difficult to find a unique key for this type
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
<div key={index}>{item}</div>
|
||||
))}
|
||||
<div
|
||||
className={classNames('SystemMessage', {
|
||||
'SystemMessage--multiline': Boolean(newGroupDescription),
|
||||
})}
|
||||
>
|
||||
<div className="SystemMessage__line">
|
||||
<div className={`SystemMessage__icon SystemMessage__icon--${icon}`} />
|
||||
<div className="SystemMessage__text">{text}</div>
|
||||
</div>
|
||||
{newGroupDescription ? (
|
||||
<div className="module-group-v2-change--button-container">
|
||||
<div className="SystemMessage__line">
|
||||
<Button
|
||||
onClick={() => onButtonClick(newGroupDescription)}
|
||||
size={ButtonSize.Small}
|
||||
variant={ButtonVariant.SecondaryAffirmative}
|
||||
onClick={() => setIsGroupDescriptionDialogOpen(true)}
|
||||
variant={ButtonVariant.SystemMessage}
|
||||
>
|
||||
{i18n('view')}
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
{newGroupDescription && isGroupDescriptionDialogOpen ? (
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function GroupV2Change(props: PropsType): ReactElement {
|
||||
const { change, groupName, i18n, ourConversationId, renderContact } = props;
|
||||
|
||||
const [groupDescription, setGroupDescription] = useState<
|
||||
string | undefined
|
||||
>();
|
||||
|
||||
return (
|
||||
<>
|
||||
{renderChange(change, {
|
||||
i18n,
|
||||
ourConversationId,
|
||||
renderContact,
|
||||
renderString: renderStringToIntl,
|
||||
}).map((text: FullJSXType, index: number) => (
|
||||
<GroupV2Detail
|
||||
detail={change.details[index]}
|
||||
fromId={change.from}
|
||||
i18n={i18n}
|
||||
// Difficult to find a unique key for this type
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
key={index}
|
||||
onButtonClick={nextGroupDescription =>
|
||||
setGroupDescription(nextGroupDescription)
|
||||
}
|
||||
text={text}
|
||||
/>
|
||||
))}
|
||||
{groupDescription ? (
|
||||
<Modal
|
||||
hasXButton
|
||||
i18n={i18n}
|
||||
title={groupName}
|
||||
onClose={() => setIsGroupDescriptionDialogOpen(false)}
|
||||
onClose={() => setGroupDescription(undefined)}
|
||||
>
|
||||
<GroupDescriptionText text={newGroupDescription} />
|
||||
<GroupDescriptionText text={groupDescription} />
|
||||
</Modal>
|
||||
) : null}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -22,8 +22,8 @@ export function ProfileChangeNotification(props: PropsType): JSX.Element {
|
|||
const message = getStringForProfileChange(change, changedContact, i18n);
|
||||
|
||||
return (
|
||||
<div className="module-profile-change-notification">
|
||||
<div className="module-profile-change-notification--icon" />
|
||||
<div className="SystemMessage">
|
||||
<div className="SystemMessage__icon SystemMessage__icon--profile" />
|
||||
<Emojify text={message} />
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -10,7 +10,5 @@ export type Props = {
|
|||
};
|
||||
|
||||
export const ResetSessionNotification = ({ i18n }: Props): JSX.Element => (
|
||||
<div className="module-reset-session-notification">
|
||||
{i18n('sessionEnded')}
|
||||
</div>
|
||||
<div className="SystemMessage">{i18n('sessionEnded')}</div>
|
||||
);
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import { Button, ButtonSize, ButtonVariant } from '../Button';
|
||||
import { ContactName } from './ContactName';
|
||||
import { Intl } from '../Intl';
|
||||
import { LocalizerType } from '../../types/Util';
|
||||
|
@ -41,38 +42,42 @@ export const SafetyNumberNotification = ({
|
|||
: 'safetyNumberChanged';
|
||||
|
||||
return (
|
||||
<div className="module-safety-number-notification">
|
||||
<div className="module-safety-number-notification__icon" />
|
||||
<div className="module-safety-number-notification__text">
|
||||
<Intl
|
||||
id={changeKey}
|
||||
components={[
|
||||
<span
|
||||
key="external-1"
|
||||
className="module-safety-number-notification__contact"
|
||||
>
|
||||
<ContactName
|
||||
name={contact.name}
|
||||
profileName={contact.profileName}
|
||||
phoneNumber={contact.phoneNumber}
|
||||
title={contact.title}
|
||||
module="module-safety-number-notification__contact"
|
||||
i18n={i18n}
|
||||
/>
|
||||
</span>,
|
||||
]}
|
||||
i18n={i18n}
|
||||
/>
|
||||
<div className="SystemMessage SystemMessage--multiline">
|
||||
<div className="SystemMessage__line">
|
||||
<div className="SystemMessage__icon SystemMessage__icon--safety-number" />
|
||||
<span>
|
||||
<Intl
|
||||
id={changeKey}
|
||||
components={[
|
||||
<span
|
||||
key="external-1"
|
||||
className="module-safety-number-notification__contact"
|
||||
>
|
||||
<ContactName
|
||||
name={contact.name}
|
||||
profileName={contact.profileName}
|
||||
phoneNumber={contact.phoneNumber}
|
||||
title={contact.title}
|
||||
module="module-safety-number-notification__contact"
|
||||
i18n={i18n}
|
||||
/>
|
||||
</span>,
|
||||
]}
|
||||
i18n={i18n}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div className="SystemMessage__line">
|
||||
<Button
|
||||
onClick={() => {
|
||||
showIdentity(contact.id);
|
||||
}}
|
||||
size={ButtonSize.Small}
|
||||
variant={ButtonVariant.SystemMessage}
|
||||
>
|
||||
{i18n('verifyNewNumber')}
|
||||
</Button>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
showIdentity(contact.id);
|
||||
}}
|
||||
className="module-safety-number-notification__button"
|
||||
>
|
||||
{i18n('verifyNewNumber')}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -118,19 +118,26 @@ storiesOf('Components/Conversation/TimelineItem', module)
|
|||
type: 'fromOther',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'universalTimerNotification',
|
||||
data: null,
|
||||
},
|
||||
{
|
||||
type: 'chatSessionRefreshed',
|
||||
},
|
||||
{
|
||||
type: 'safetyNumberNotification',
|
||||
data: {
|
||||
isGroup: false,
|
||||
contact: getDefaultConversation(),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'deliveryIssue',
|
||||
data: {
|
||||
sender: getDefaultConversation(),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'universalTimerNotification',
|
||||
data: null,
|
||||
},
|
||||
{
|
||||
type: 'changeNumberNotification',
|
||||
data: {
|
||||
|
@ -255,7 +262,7 @@ storiesOf('Components/Conversation/TimelineItem', module)
|
|||
{
|
||||
type: 'callHistory',
|
||||
data: {
|
||||
// missed (neither accepted nor declined) outgoing audio
|
||||
// unanswered (neither accepted nor declined) outgoing audio
|
||||
callMode: CallMode.Direct,
|
||||
wasDeclined: false,
|
||||
wasIncoming: false,
|
||||
|
@ -266,7 +273,7 @@ storiesOf('Components/Conversation/TimelineItem', module)
|
|||
{
|
||||
type: 'callHistory',
|
||||
data: {
|
||||
// missed (neither accepted nor declined) outgoing video
|
||||
// unanswered (neither accepted nor declined) outgoing video
|
||||
callMode: CallMode.Direct,
|
||||
wasDeclined: false,
|
||||
wasIncoming: false,
|
||||
|
@ -390,6 +397,71 @@ storiesOf('Components/Conversation/TimelineItem', module)
|
|||
startedTime: Date.now(),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'linkNotification',
|
||||
data: null,
|
||||
},
|
||||
{
|
||||
type: 'profileChange',
|
||||
data: {
|
||||
change: {
|
||||
type: 'name',
|
||||
oldName: 'Fred',
|
||||
newName: 'John',
|
||||
},
|
||||
changedContact: getDefaultConversation(),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'resetSessionNotification',
|
||||
data: null,
|
||||
},
|
||||
{
|
||||
type: 'unsupportedMessage',
|
||||
data: {
|
||||
canProcessNow: true,
|
||||
contact: getDefaultConversation(),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'unsupportedMessage',
|
||||
data: {
|
||||
canProcessNow: false,
|
||||
contact: getDefaultConversation(),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'verificationNotification',
|
||||
data: {
|
||||
type: 'markVerified',
|
||||
isLocal: false,
|
||||
contact: getDefaultConversation(),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'verificationNotification',
|
||||
data: {
|
||||
type: 'markVerified',
|
||||
isLocal: true,
|
||||
contact: getDefaultConversation(),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'verificationNotification',
|
||||
data: {
|
||||
type: 'markNotVerified',
|
||||
isLocal: false,
|
||||
contact: getDefaultConversation(),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'verificationNotification',
|
||||
data: {
|
||||
type: 'markNotVerified',
|
||||
isLocal: true,
|
||||
contact: getDefaultConversation(),
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
|
|
|
@ -246,8 +246,8 @@ export class TimelineItem extends React.PureComponent<PropsType> {
|
|||
);
|
||||
} else if (item.type === 'linkNotification') {
|
||||
notification = (
|
||||
<div className="module-message-unsynced">
|
||||
<div className="module-message-unsynced__icon" />
|
||||
<div className="SystemMessage">
|
||||
<div className="SystemMessage__icon SystemMessage__icon--unsynced" />
|
||||
{i18n('messageHistoryUnsynced')}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -95,17 +95,15 @@ export const TimerNotification: FunctionComponent<Props> = props => {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="module-timer-notification">
|
||||
<div className="module-timer-notification__icon-container">
|
||||
<div
|
||||
className={classNames(
|
||||
'module-timer-notification__icon',
|
||||
disabled ? 'module-timer-notification__icon--disabled' : null
|
||||
)}
|
||||
/>
|
||||
<div className="module-timer-notification__icon-label">{timespan}</div>
|
||||
</div>
|
||||
<div className="module-timer-notification__message">{message}</div>
|
||||
<div className="SystemMessage">
|
||||
<div
|
||||
className={classNames(
|
||||
'SystemMessage__icon',
|
||||
'SystemMessage__icon--timer',
|
||||
disabled ? 'SystemMessage__icon--timer-disabled' : null
|
||||
)}
|
||||
/>
|
||||
<div>{message}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -11,8 +11,6 @@ export type Props = {
|
|||
expireTimer: number;
|
||||
};
|
||||
|
||||
const CSS_MODULE = 'module-universal-timer-notification';
|
||||
|
||||
export const UniversalTimerNotification: React.FC<Props> = props => {
|
||||
const { i18n, expireTimer } = props;
|
||||
|
||||
|
@ -23,15 +21,14 @@ export const UniversalTimerNotification: React.FC<Props> = props => {
|
|||
const timeValue = expirationTimer.format(i18n, expireTimer);
|
||||
|
||||
return (
|
||||
<div className={CSS_MODULE}>
|
||||
<div className={`${CSS_MODULE}__icon-container`}>
|
||||
<div className={`${CSS_MODULE}__icon`} />
|
||||
<div className={`${CSS_MODULE}__icon-label`}>{timeValue}</div>
|
||||
</div>
|
||||
<div className={`${CSS_MODULE}__message`}>
|
||||
{i18n('UniversalTimerNotification__text', {
|
||||
timeValue,
|
||||
})}
|
||||
<div className="SystemMessage">
|
||||
<div className="SystemMessage__icon SystemMessage__icon--timer" />
|
||||
<div className="SystemMessage__text">
|
||||
<div>
|
||||
{i18n('UniversalTimerNotification__text', {
|
||||
timeValue,
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { Button, ButtonSize, ButtonVariant } from '../Button';
|
||||
import { ContactName } from './ContactName';
|
||||
import { Intl } from '../Intl';
|
||||
import { LocalizerType } from '../../types/Util';
|
||||
|
@ -49,44 +50,51 @@ export const UnsupportedMessage = ({
|
|||
const stringId = isMe ? meStringId : otherStringId;
|
||||
|
||||
return (
|
||||
<div className="module-unsupported-message">
|
||||
<div
|
||||
className={classNames(
|
||||
'module-unsupported-message__icon',
|
||||
canProcessNow ? 'module-unsupported-message__icon--can-process' : null
|
||||
)}
|
||||
/>
|
||||
<div className="module-unsupported-message__text">
|
||||
<Intl
|
||||
id={stringId}
|
||||
components={[
|
||||
<span
|
||||
key="external-1"
|
||||
className="module-unsupported-message__contact"
|
||||
>
|
||||
<ContactName
|
||||
name={contact.name}
|
||||
profileName={contact.profileName}
|
||||
phoneNumber={contact.phoneNumber}
|
||||
title={contact.title}
|
||||
module="module-unsupported-message__contact"
|
||||
i18n={i18n}
|
||||
/>
|
||||
</span>,
|
||||
]}
|
||||
i18n={i18n}
|
||||
<div className="SystemMessage SystemMessage--multiline">
|
||||
<div className="SystemMessage__line SystemMessage__text">
|
||||
<div
|
||||
className={classNames(
|
||||
'SystemMessage__icon',
|
||||
'SystemMessage__icon--unsupported',
|
||||
{
|
||||
'SystemMessage__icon--unsupported--can-process': canProcessNow,
|
||||
}
|
||||
)}
|
||||
/>
|
||||
<span>
|
||||
<Intl
|
||||
id={stringId}
|
||||
components={[
|
||||
<span
|
||||
key="external-1"
|
||||
className="module-unsupported-message__contact"
|
||||
>
|
||||
<ContactName
|
||||
name={contact.name}
|
||||
profileName={contact.profileName}
|
||||
phoneNumber={contact.phoneNumber}
|
||||
title={contact.title}
|
||||
module="module-unsupported-message__contact"
|
||||
i18n={i18n}
|
||||
/>
|
||||
</span>,
|
||||
]}
|
||||
i18n={i18n}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
{canProcessNow ? null : (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
downloadNewVersion();
|
||||
}}
|
||||
className="module-unsupported-message__button"
|
||||
>
|
||||
{i18n('Message--update-signal')}
|
||||
</button>
|
||||
<div className="SystemMessage__line">
|
||||
<Button
|
||||
onClick={() => {
|
||||
downloadNewVersion();
|
||||
}}
|
||||
size={ButtonSize.Small}
|
||||
variant={ButtonVariant.SystemMessage}
|
||||
>
|
||||
{i18n('Message--update-signal')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -52,32 +52,33 @@ export class VerificationNotification extends React.Component<Props> {
|
|||
const id = this.getStringId();
|
||||
|
||||
return (
|
||||
<Intl
|
||||
id={id}
|
||||
components={[
|
||||
<ContactName
|
||||
key="external-1"
|
||||
name={contact.name}
|
||||
profileName={contact.profileName}
|
||||
phoneNumber={contact.phoneNumber}
|
||||
title={contact.title}
|
||||
module="module-verification-notification__contact"
|
||||
i18n={i18n}
|
||||
/>,
|
||||
]}
|
||||
i18n={i18n}
|
||||
/>
|
||||
<div className="SystemMessage__text">
|
||||
<Intl
|
||||
id={id}
|
||||
components={[
|
||||
<ContactName
|
||||
key="external-1"
|
||||
name={contact.name}
|
||||
profileName={contact.profileName}
|
||||
phoneNumber={contact.phoneNumber}
|
||||
title={contact.title}
|
||||
module="module-verification-notification__contact"
|
||||
i18n={i18n}
|
||||
/>,
|
||||
]}
|
||||
i18n={i18n}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
const { type } = this.props;
|
||||
const suffix =
|
||||
type === 'markVerified' ? 'mark-verified' : 'mark-not-verified';
|
||||
const suffix = type === 'markVerified' ? 'verified' : 'verified-not';
|
||||
|
||||
return (
|
||||
<div className="module-verification-notification">
|
||||
<div className={`module-verification-notification__icon--${suffix}`} />
|
||||
<div className="SystemMessage">
|
||||
<div className={`SystemMessage__icon SystemMessage__icon--${suffix}`} />
|
||||
{this.renderContents()}
|
||||
</div>
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue