Update safety number change warning dialog
This commit is contained in:
parent
e87a0103cc
commit
5b83485c89
38 changed files with 1221 additions and 425 deletions
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
76
ts/components/SafetyNumberChangeDialog.stories.tsx
Normal file
76
ts/components/SafetyNumberChangeDialog.stories.tsx
Normal 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>;
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
135
ts/components/SafetyNumberChangeDialog.tsx
Normal file
135
ts/components/SafetyNumberChangeDialog.tsx
Normal 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>
|
||||
);
|
||||
};
|
85
ts/components/SafetyNumberViewer.stories.tsx
Normal file
85
ts/components/SafetyNumberViewer.stories.tsx
Normal 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} />
|
||||
</>
|
||||
));
|
||||
});
|
82
ts/components/SafetyNumberViewer.tsx
Normal file
82
ts/components/SafetyNumberViewer.tsx
Normal 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>
|
||||
);
|
||||
};
|
|
@ -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',
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue