Update safety number change warning dialog

This commit is contained in:
Josh Perez 2020-06-25 20:08:58 -04:00 committed by Scott Nonnenberg
parent e87a0103cc
commit 5b83485c89
38 changed files with 1221 additions and 425 deletions

View file

@ -70,33 +70,35 @@ export const ConfirmationDialog = React.memo(
<div className="module-confirmation-dialog__container__content">
{children}
</div>
<div className="module-confirmation-dialog__container__buttons">
<button
onClick={handleCancel}
ref={focusRef}
className="module-confirmation-dialog__container__buttons__button"
>
{i18n('confirmation-dialog--Cancel')}
</button>
{actions.map((action, i) => (
{actions.length > 0 && (
<div className="module-confirmation-dialog__container__buttons">
<button
key={i}
onClick={handleAction}
data-action={i}
className={classNames(
'module-confirmation-dialog__container__buttons__button',
action.style === 'affirmative'
? 'module-confirmation-dialog__container__buttons__button--affirmative'
: null,
action.style === 'negative'
? 'module-confirmation-dialog__container__buttons__button--negative'
: null
)}
onClick={handleCancel}
ref={focusRef}
className="module-confirmation-dialog__container__buttons__button"
>
{action.text}
{i18n('confirmation-dialog--Cancel')}
</button>
))}
</div>
{actions.map((action, i) => (
<button
key={i}
onClick={handleAction}
data-action={i}
className={classNames(
'module-confirmation-dialog__container__buttons__button',
action.style === 'affirmative'
? 'module-confirmation-dialog__container__buttons__button--affirmative'
: null,
action.style === 'negative'
? 'module-confirmation-dialog__container__buttons__button--negative'
: null
)}
>
{action.text}
</button>
))}
</div>
)}
</div>
);
}

View file

@ -0,0 +1,76 @@
import * as React from 'react';
import { SafetyNumberChangeDialog } from './SafetyNumberChangeDialog';
import { ConversationType } from '../state/ducks/conversations';
// @ts-ignore
import { setup as setupI18n } from '../../js/modules/i18n';
// @ts-ignore
import enMessages from '../../_locales/en/messages.json';
import { action } from '@storybook/addon-actions';
import { storiesOf } from '@storybook/react';
const i18n = setupI18n('en', enMessages);
const contact = {
avatarPath: undefined,
color: 'signal-blue',
profileName: undefined,
name: 'Rick Sanchez',
phoneNumber: '3051234567',
} as ConversationType;
storiesOf('Components/SafetyNumberChangeDialog', module)
.add('Single Contact Dialog', () => {
return (
<SafetyNumberChangeDialog
contacts={[contact]}
i18n={i18n}
onCancel={action('cancel')}
onConfirm={action('confirm')}
renderSafetyNumber={() => {
action('renderSafetyNumber');
return <div>This is a mock Safety Number View</div>;
}}
/>
);
})
.add('Multi Contact Dialog', () => {
return (
<SafetyNumberChangeDialog
contacts={[contact, contact, contact, contact]}
i18n={i18n}
onCancel={action('cancel')}
onConfirm={action('confirm')}
renderSafetyNumber={() => {
action('renderSafetyNumber');
return <div>This is a mock Safety Number View</div>;
}}
/>
);
})
.add('Scroll Dialog', () => {
return (
<SafetyNumberChangeDialog
contacts={[
contact,
contact,
contact,
contact,
contact,
contact,
contact,
contact,
contact,
contact,
]}
i18n={i18n}
onCancel={action('cancel')}
onConfirm={action('confirm')}
renderSafetyNumber={() => {
action('renderSafetyNumber');
return <div>This is a mock Safety Number View</div>;
}}
/>
);
});

View file

@ -0,0 +1,135 @@
import * as React from 'react';
import { Avatar } from './Avatar';
import { ConfirmationModal } from './ConfirmationModal';
import { ConversationType } from '../state/ducks/conversations';
import { LocalizerType } from '../types/Util';
type SafetyNumberProps = {
contactID: string;
onClose?: () => void;
};
export type Props = {
readonly contacts: Array<ConversationType>;
readonly i18n: LocalizerType;
readonly onCancel: () => void;
readonly onConfirm: () => void;
readonly renderSafetyNumber: (props: SafetyNumberProps) => JSX.Element;
};
type SafetyDialogContentProps = Props & {
readonly onView: (contact: ConversationType) => void;
};
const SafetyDialogContents = ({
contacts,
i18n,
onCancel,
onConfirm,
onView,
}: SafetyDialogContentProps): JSX.Element => {
const cancelButtonRef = React.createRef<HTMLButtonElement>();
React.useEffect(() => {
if (cancelButtonRef && cancelButtonRef.current) {
cancelButtonRef.current.focus();
}
}, [contacts]);
return (
<>
<h1 className="module-sfn-dialog__title">
{i18n('safetyNumberChanges')}
</h1>
<div className="module-sfn-dialog__message">
{i18n('changedVerificationWarning')}
</div>
<ul className="module-sfn-dialog__contacts">
{contacts.map((contact: ConversationType) => (
<li className="module-sfn-dialog__contact" key={contact.phoneNumber}>
<Avatar
avatarPath={contact.avatarPath}
color={contact.color}
conversationType="direct"
i18n={i18n}
name={contact.name}
phoneNumber={contact.phoneNumber}
profileName={contact.profileName}
size={52}
/>
<div className="module-sfn-dialog__contact--wrapper">
{contact.name && (
<>
<div className="module-sfn-dialog__contact--name">
{contact.name}
</div>
<div className="module-sfn-dialog__contact--number">
{contact.phoneNumber}
</div>
</>
)}
{!contact.name && (
<div className="module-sfn-dialog__contact--name">
{contact.phoneNumber}
</div>
)}
</div>
<button
className="module-sfn-dialog__contact--view"
onClick={() => {
onView(contact);
}}
tabIndex={0}
>
{i18n('view')}
</button>
</li>
))}
</ul>
<div className="module-sfn-dialog__actions">
<button
className="module-sfn-dialog__actions--cancel"
onClick={onCancel}
ref={cancelButtonRef}
tabIndex={0}
>
{i18n('cancel')}
</button>
<button
className="module-sfn-dialog__actions--confirm"
onClick={onConfirm}
tabIndex={0}
>
{i18n('sendMessageToContact')}
</button>
</div>
</>
);
};
export const SafetyNumberChangeDialog = (props: Props): JSX.Element => {
const { i18n, onCancel, renderSafetyNumber } = props;
const [contact, setViewSafetyNumber] = React.useState<
ConversationType | undefined
>(undefined);
const onClose = contact
? () => {
setViewSafetyNumber(undefined);
}
: onCancel;
return (
<ConfirmationModal actions={[]} i18n={i18n} onClose={onClose}>
{contact && renderSafetyNumber({ contactID: contact.id, onClose })}
{!contact && (
<SafetyDialogContents
{...props}
onView={selectedContact => {
setViewSafetyNumber(selectedContact);
}}
/>
)}
</ConfirmationModal>
);
};

View file

@ -0,0 +1,85 @@
import * as React from 'react';
import { SafetyNumberViewer } from './SafetyNumberViewer';
import { ConversationType } from '../state/ducks/conversations';
// @ts-ignore
import { setup as setupI18n } from '../../js/modules/i18n';
// @ts-ignore
import enMessages from '../../_locales/en/messages.json';
import { action } from '@storybook/addon-actions';
import { boolean, text } from '@storybook/addon-knobs';
import { storiesOf } from '@storybook/react';
const i18n = setupI18n('en', enMessages);
const defaultProps = {
contact: {
title: 'Summer Smith',
isVerified: true,
} as ConversationType,
generateSafetyNumber: action('generate-safety-number'),
i18n,
safetyNumber: 'XXX',
safetyNumberChanged: false,
toggleVerified: action('toggle-verified'),
verificationDisabled: false,
};
const permutations = [
{
title: 'Safety Number',
props: {},
},
{
title: 'Safety Number (not verified)',
props: {
contact: {
title: 'Morty Smith',
isVerified: false,
} as ConversationType,
},
},
{
title: 'Verification Disabled',
props: {
verificationDisabled: true,
},
},
{
title: 'Safety Number Changed',
props: {
safetyNumberChanged: true,
},
},
{
title: 'Safety Number (dialog close)',
props: {
onClose: action('close'),
},
},
];
storiesOf('Components/SafetyNumberViewer', module)
.add('Knobs Playground', () => {
const safetyNumber = text('safetyNumber', 'XXX');
const safetyNumberChanged = boolean('safetyNumberChanged', false);
const verificationDisabled = boolean('verificationDisabled', false);
return (
<SafetyNumberViewer
{...defaultProps}
safetyNumber={safetyNumber}
safetyNumberChanged={safetyNumberChanged}
verificationDisabled={verificationDisabled}
/>
);
})
.add('Iterations', () => {
return permutations.map(({ props, title }) => (
<>
<h3>{title}</h3>
<SafetyNumberViewer {...defaultProps} {...props} />
</>
));
});

View file

@ -0,0 +1,82 @@
import React from 'react';
import { ConversationType } from '../state/ducks/conversations';
import { LocalizerType } from '../types/Util';
import { getPlaceholder } from '../util/safetyNumber';
type SafetyNumberViewerProps = {
contact?: ConversationType;
generateSafetyNumber: (contact: ConversationType) => void;
i18n: LocalizerType;
onClose?: () => void;
safetyNumber: string;
safetyNumberChanged?: boolean;
toggleVerified: (contact: ConversationType) => void;
verificationDisabled: boolean;
};
export const SafetyNumberViewer = ({
contact,
generateSafetyNumber,
i18n,
onClose,
safetyNumber,
safetyNumberChanged,
toggleVerified,
verificationDisabled,
}: SafetyNumberViewerProps): JSX.Element | null => {
if (!contact) {
return null;
}
React.useEffect(() => {
generateSafetyNumber(contact);
}, [safetyNumber]);
const name = contact.title;
const isVerified = contact.isVerified;
const verifiedStatus = isVerified
? i18n('isVerified', [name])
: i18n('isNotVerified', [name]);
const verifyButtonText = isVerified ? i18n('unverify') : i18n('verify');
return (
<div className="module-safety-number">
{onClose && (
<div className="module-safety-number__close-button">
<button onClick={onClose} tabIndex={0}>
<span />
</button>
</div>
)}
<div className="module-safety-number__verification-label">
{safetyNumberChanged
? i18n('changedRightAfterVerify', [name, name])
: i18n('yourSafetyNumberWith', [name])}
</div>
<div className="module-safety-number__number">
{safetyNumber || getPlaceholder()}
</div>
{i18n('verifyHelp', [name])}
<div className="module-safety-number__verification-status">
{isVerified ? (
<span className="module-safety-number__icon--verified" />
) : (
<span className="module-safety-number__icon--shield" />
)}
{verifiedStatus}
</div>
<div className="module-safety-number__verify-container">
<button
className="module-safety-number__button--verify"
disabled={verificationDisabled}
onClick={() => {
toggleVerified(contact);
}}
tabIndex={0}
>
{verifyButtonText}
</button>
</div>
</div>
);
};

View file

@ -37,7 +37,6 @@ const SENT = 'sent' as 'sent';
const START_NEW_CONVERSATION = 'start-new-conversation' as 'start-new-conversation';
const SMS_MMS_NOT_SUPPORTED = 'sms-mms-not-supported-text' as 'sms-mms-not-supported-text';
// tslint:disable-next-line no-backbone-get-set-outside-model
messageLookup.set('1-guid-guid-guid-guid-guid', {
id: '1-guid-guid-guid-guid-guid',
conversationId: '(202) 555-0015',
@ -56,7 +55,6 @@ messageLookup.set('1-guid-guid-guid-guid-guid', {
},
});
// tslint:disable-next-line no-backbone-get-set-outside-model
messageLookup.set('2-guid-guid-guid-guid-guid', {
id: '2-guid-guid-guid-guid-guid',
conversationId: '(202) 555-0016',
@ -73,7 +71,6 @@ messageLookup.set('2-guid-guid-guid-guid-guid', {
},
});
// tslint:disable-next-line no-backbone-get-set-outside-model
messageLookup.set('3-guid-guid-guid-guid-guid', {
id: '3-guid-guid-guid-guid-guid',
conversationId: 'EveryoneGroupID',
@ -91,7 +88,6 @@ messageLookup.set('3-guid-guid-guid-guid-guid', {
},
});
// tslint:disable-next-line no-backbone-get-set-outside-model
messageLookup.set('4-guid-guid-guid-guid-guid', {
id: '4-guid-guid-guid-guid-guid',
conversationId: 'EveryoneGroupID',