DeliveryIssueDialog: Add learn more button linking to support
This commit is contained in:
parent
d0bf9f929b
commit
bf7da5ca2e
23 changed files with 515 additions and 89 deletions
|
@ -23,7 +23,6 @@ export type Props = {
|
|||
} & AvatarProps;
|
||||
|
||||
export const AvatarPopup = (props: Props): JSX.Element => {
|
||||
const focusRef = React.useRef<HTMLButtonElement>(null);
|
||||
const {
|
||||
i18n,
|
||||
name,
|
||||
|
@ -42,7 +41,7 @@ export const AvatarPopup = (props: Props): JSX.Element => {
|
|||
// Note: mechanisms to dismiss this view are all in its host, MainHeader
|
||||
|
||||
// Focus first button after initial render, restore focus on teardown
|
||||
useRestoreFocus(focusRef);
|
||||
const [focusRef] = useRestoreFocus();
|
||||
|
||||
return (
|
||||
<div style={style} className="module-avatar-popup">
|
||||
|
|
|
@ -39,6 +39,7 @@ export function CaptchaDialog(props: Readonly<PropsType>): JSX.Element {
|
|||
moduleClassName="module-Modal"
|
||||
i18n={i18n}
|
||||
title={i18n('CaptchaDialog--can-close__title')}
|
||||
onClose={() => setIsClosing(false)}
|
||||
>
|
||||
<section>
|
||||
<p>{i18n('CaptchaDialog--can-close__body')}</p>
|
||||
|
|
|
@ -29,6 +29,7 @@ export const NeedsScreenRecordingPermissionsModal = ({
|
|||
i18n={i18n}
|
||||
title={i18n('calling__presenting--permission-title')}
|
||||
theme={Theme.Dark}
|
||||
onClose={toggleScreenRecordingPermissionsDialog}
|
||||
>
|
||||
<p>{i18n('calling__presenting--macos-permission-description')}</p>
|
||||
<ol style={{ paddingLeft: 16 }}>
|
||||
|
|
|
@ -54,7 +54,7 @@ export const SafetyNumberChangeDialog = ({
|
|||
|
||||
if (selectedContact) {
|
||||
return (
|
||||
<Modal i18n={i18n}>
|
||||
<Modal i18n={i18n} onClose={onClose}>
|
||||
{renderSafetyNumber({ contactID: selectedContact.id, onClose })}
|
||||
</Modal>
|
||||
);
|
||||
|
|
|
@ -205,12 +205,11 @@ const CALLING_SHORTCUTS: Array<ShortcutType> = [
|
|||
];
|
||||
|
||||
export const ShortcutGuide = (props: Props): JSX.Element => {
|
||||
const focusRef = React.useRef<HTMLDivElement>(null);
|
||||
const { i18n, close, hasInstalledStickers, platform } = props;
|
||||
const isMacOS = platform === 'darwin';
|
||||
|
||||
// Restore focus on teardown
|
||||
useRestoreFocus(focusRef);
|
||||
const [focusRef] = useRestoreFocus();
|
||||
|
||||
return (
|
||||
<div className="module-shortcut-guide">
|
||||
|
|
|
@ -6,6 +6,8 @@ import classNames from 'classnames';
|
|||
|
||||
import { Modal } from '../Modal';
|
||||
|
||||
import { useRestoreFocus } from '../../util/hooks';
|
||||
|
||||
import { LocalizerType } from '../../types/Util';
|
||||
|
||||
export type PropsType = {
|
||||
|
@ -19,8 +21,11 @@ export function ChatSessionRefreshedDialog(
|
|||
): React.ReactElement {
|
||||
const { i18n, contactSupport, onClose } = props;
|
||||
|
||||
// Focus first button after initial render, restore focus on teardown
|
||||
const [focusRef] = useRestoreFocus();
|
||||
|
||||
return (
|
||||
<Modal hasXButton={false} i18n={i18n}>
|
||||
<Modal hasXButton={false} onClose={onClose} i18n={i18n}>
|
||||
<div className="module-chat-session-refreshed-dialog">
|
||||
<div className="module-chat-session-refreshed-dialog__image">
|
||||
<img
|
||||
|
@ -50,6 +55,7 @@ export function ChatSessionRefreshedDialog(
|
|||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
ref={focusRef}
|
||||
className="module-chat-session-refreshed-dialog__button"
|
||||
>
|
||||
{i18n('Confirmation--confirm')}
|
||||
|
|
|
@ -21,6 +21,7 @@ storiesOf('Components/Conversation/DeliveryIssueDialog', module).add(
|
|||
i18n={i18n}
|
||||
sender={sender}
|
||||
inGroup={false}
|
||||
learnMoreAboutDeliveryIssue={action('learnMoreAboutDeliveryIssue')}
|
||||
onClose={action('onClose')}
|
||||
/>
|
||||
);
|
||||
|
@ -35,6 +36,7 @@ storiesOf('Components/Conversation/DeliveryIssueDialog', module).add(
|
|||
i18n={i18n}
|
||||
sender={sender}
|
||||
inGroup
|
||||
learnMoreAboutDeliveryIssue={action('learnMoreAboutDeliveryIssue')}
|
||||
onClose={action('onClose')}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -8,24 +8,30 @@ import { Modal } from '../Modal';
|
|||
import { Intl } from '../Intl';
|
||||
import { Emojify } from './Emojify';
|
||||
|
||||
import { useRestoreFocus } from '../../util/hooks';
|
||||
|
||||
import { LocalizerType } from '../../types/Util';
|
||||
|
||||
export type PropsType = {
|
||||
i18n: LocalizerType;
|
||||
sender: ConversationType;
|
||||
inGroup: boolean;
|
||||
learnMoreAboutDeliveryIssue: () => unknown;
|
||||
onClose: () => unknown;
|
||||
};
|
||||
|
||||
export function DeliveryIssueDialog(props: PropsType): React.ReactElement {
|
||||
const { i18n, inGroup, sender, onClose } = props;
|
||||
const { i18n, inGroup, learnMoreAboutDeliveryIssue, sender, onClose } = props;
|
||||
|
||||
const key = inGroup
|
||||
? 'DeliveryIssue--summary--group'
|
||||
: 'DeliveryIssue--summary';
|
||||
|
||||
// Focus first button after initial render, restore focus on teardown
|
||||
const [focusRef] = useRestoreFocus();
|
||||
|
||||
return (
|
||||
<Modal hasXButton={false} i18n={i18n}>
|
||||
<Modal hasXButton={false} onClose={onClose} i18n={i18n}>
|
||||
<div className="module-delivery-issue-dialog">
|
||||
<div className="module-delivery-issue-dialog__image">
|
||||
<img
|
||||
|
@ -48,10 +54,18 @@ export function DeliveryIssueDialog(props: PropsType): React.ReactElement {
|
|||
/>
|
||||
</div>
|
||||
<div className="module-delivery-issue-dialog__buttons">
|
||||
<button
|
||||
type="button"
|
||||
onClick={learnMoreAboutDeliveryIssue}
|
||||
className="module-delivery-issue-dialog__learn-more-button"
|
||||
>
|
||||
{i18n('DeliveryIssue--learnMore')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="module-delivery-issue-dialog__button"
|
||||
ref={focusRef}
|
||||
className="module-delivery-issue-dialog__close-button"
|
||||
>
|
||||
{i18n('Confirmation--confirm')}
|
||||
</button>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
import * as React from 'react';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
import { setup as setupI18n } from '../../../js/modules/i18n';
|
||||
import enMessages from '../../../_locales/en/messages.json';
|
||||
|
@ -19,7 +20,12 @@ storiesOf('Components/Conversation/DeliveryIssueNotification', module).add(
|
|||
'Default',
|
||||
() => {
|
||||
return (
|
||||
<DeliveryIssueNotification i18n={i18n} inGroup={false} sender={sender} />
|
||||
<DeliveryIssueNotification
|
||||
i18n={i18n}
|
||||
inGroup={false}
|
||||
learnMoreAboutDeliveryIssue={action('learnMoreAboutDeliveryIssue')}
|
||||
sender={sender}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
@ -27,6 +33,13 @@ storiesOf('Components/Conversation/DeliveryIssueNotification', module).add(
|
|||
storiesOf('Components/Conversation/DeliveryIssueNotification', module).add(
|
||||
'In Group',
|
||||
() => {
|
||||
return <DeliveryIssueNotification i18n={i18n} inGroup sender={sender} />;
|
||||
return (
|
||||
<DeliveryIssueNotification
|
||||
i18n={i18n}
|
||||
inGroup
|
||||
learnMoreAboutDeliveryIssue={action('learnMoreAboutDeliveryIssue')}
|
||||
sender={sender}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -15,16 +15,22 @@ export type PropsDataType = {
|
|||
inGroup: boolean;
|
||||
};
|
||||
|
||||
export type PropsActionsType = {
|
||||
learnMoreAboutDeliveryIssue: () => unknown;
|
||||
};
|
||||
|
||||
type PropsHousekeepingType = {
|
||||
i18n: LocalizerType;
|
||||
};
|
||||
|
||||
export type PropsType = PropsDataType & PropsHousekeepingType;
|
||||
export type PropsType = PropsDataType &
|
||||
PropsActionsType &
|
||||
PropsHousekeepingType;
|
||||
|
||||
export function DeliveryIssueNotification(
|
||||
props: PropsType
|
||||
): ReactElement | null {
|
||||
const { i18n, inGroup, sender } = props;
|
||||
const { i18n, inGroup, sender, learnMoreAboutDeliveryIssue } = props;
|
||||
const [isDialogOpen, setIsDialogOpen] = useState<boolean>(false);
|
||||
|
||||
const openDialog = useCallback(() => {
|
||||
|
@ -61,6 +67,7 @@ export function DeliveryIssueNotification(
|
|||
<DeliveryIssueDialog
|
||||
i18n={i18n}
|
||||
inGroup={inGroup}
|
||||
learnMoreAboutDeliveryIssue={learnMoreAboutDeliveryIssue}
|
||||
sender={sender}
|
||||
onClose={closeDialog}
|
||||
/>
|
||||
|
|
|
@ -40,7 +40,6 @@ export const ReactionPicker = React.forwardRef<HTMLDivElement, Props>(
|
|||
ref
|
||||
) => {
|
||||
const [pickingOther, setPickingOther] = React.useState(false);
|
||||
const focusRef = React.useRef<HTMLButtonElement>(null);
|
||||
|
||||
// Handle escape key
|
||||
React.useEffect(() => {
|
||||
|
@ -70,7 +69,7 @@ export const ReactionPicker = React.forwardRef<HTMLDivElement, Props>(
|
|||
);
|
||||
|
||||
// Focus first button and restore focus on unmount
|
||||
useRestoreFocus(focusRef);
|
||||
const [focusRef] = useRestoreFocus();
|
||||
|
||||
const otherSelected = selected && !emojis.includes(selected);
|
||||
|
||||
|
|
|
@ -124,8 +124,6 @@ export const ReactionViewer = React.forwardRef<HTMLDivElement, Props>(
|
|||
selectedReactionCategory,
|
||||
setSelectedReactionCategory,
|
||||
] = React.useState(pickedReaction || 'all');
|
||||
const focusRef = React.useRef<HTMLButtonElement>(null);
|
||||
|
||||
// Handle escape key
|
||||
React.useEffect(() => {
|
||||
const handler = (e: KeyboardEvent) => {
|
||||
|
@ -142,7 +140,7 @@ export const ReactionViewer = React.forwardRef<HTMLDivElement, Props>(
|
|||
}, [onClose]);
|
||||
|
||||
// Focus first button and restore focus on unmount
|
||||
useRestoreFocus(focusRef);
|
||||
const [focusRef] = useRestoreFocus();
|
||||
|
||||
// If we have previously selected a reaction type that is no longer present
|
||||
// (removed on another device, for instance) we should select another
|
||||
|
|
|
@ -304,6 +304,7 @@ const actions = () => ({
|
|||
),
|
||||
setLoadCountdownStart: action('setLoadCountdownStart'),
|
||||
setIsNearBottom: action('setIsNearBottom'),
|
||||
learnMoreAboutDeliveryIssue: action('learnMoreAboutDeliveryIssue'),
|
||||
loadAndScroll: action('loadAndScroll'),
|
||||
loadOlderMessages: action('loadOlderMessages'),
|
||||
loadNewerMessages: action('loadNewerMessages'),
|
||||
|
|
|
@ -135,6 +135,7 @@ type PropsActionsType = {
|
|||
}>
|
||||
) => void;
|
||||
|
||||
learnMoreAboutDeliveryIssue: () => unknown;
|
||||
loadAndScroll: (messageId: string) => unknown;
|
||||
loadOlderMessages: (messageId: string) => unknown;
|
||||
loadNewerMessages: (messageId: string) => unknown;
|
||||
|
|
|
@ -55,6 +55,7 @@ const getDefaultProps = () => ({
|
|||
deleteMessage: action('deleteMessage'),
|
||||
deleteMessageForEveryone: action('deleteMessageForEveryone'),
|
||||
kickOffAttachmentDownload: action('kickOffAttachmentDownload'),
|
||||
learnMoreAboutDeliveryIssue: action('learnMoreAboutDeliveryIssue'),
|
||||
markAttachmentAsCorrupted: action('markAttachmentAsCorrupted'),
|
||||
showMessageDetail: action('showMessageDetail'),
|
||||
openConversation: action('openConversation'),
|
||||
|
|
|
@ -21,6 +21,7 @@ import {
|
|||
} from './ChatSessionRefreshedNotification';
|
||||
import {
|
||||
DeliveryIssueNotification,
|
||||
PropsActionsType as DeliveryIssueActionProps,
|
||||
PropsDataType as DeliveryIssueProps,
|
||||
} from './DeliveryIssueNotification';
|
||||
import { CallingNotificationType } from '../../util/callingNotification';
|
||||
|
@ -156,6 +157,7 @@ type PropsLocalType = {
|
|||
|
||||
type PropsActionsType = MessageActionsType &
|
||||
CallingNotificationActionsType &
|
||||
DeliveryIssueActionProps &
|
||||
PropsChatSessionRefreshedActionsType &
|
||||
UnsupportedMessageActionsType &
|
||||
SafetyNumberActionsType;
|
||||
|
@ -226,7 +228,9 @@ export class TimelineItem extends React.PureComponent<PropsType> {
|
|||
/>
|
||||
);
|
||||
} else if (item.type === 'deliveryIssue') {
|
||||
notification = <DeliveryIssueNotification {...item.data} i18n={i18n} />;
|
||||
notification = (
|
||||
<DeliveryIssueNotification {...item.data} {...this.props} i18n={i18n} />
|
||||
);
|
||||
} else if (item.type === 'linkNotification') {
|
||||
notification = (
|
||||
<div className="module-message-unsynced">
|
||||
|
|
|
@ -70,7 +70,6 @@ export const StickerPicker = React.memo(
|
|||
}: Props,
|
||||
ref
|
||||
) => {
|
||||
const focusRef = React.useRef<HTMLButtonElement>(null);
|
||||
const tabIds = React.useMemo(
|
||||
() => ['recents', ...packs.map(({ id }) => id)],
|
||||
[packs]
|
||||
|
@ -124,7 +123,7 @@ export const StickerPicker = React.memo(
|
|||
}, [onClose]);
|
||||
|
||||
// Focus popup on after initial render, restore focus on teardown
|
||||
useRestoreFocus(focusRef);
|
||||
const [focusRef] = useRestoreFocus();
|
||||
|
||||
const isEmpty = stickers.length === 0;
|
||||
const addPackRef = isEmpty ? focusRef : undefined;
|
||||
|
|
|
@ -76,12 +76,11 @@ export const StickerPreviewModal = React.memo((props: Props) => {
|
|||
installStickerPack,
|
||||
uninstallStickerPack,
|
||||
} = props;
|
||||
const focusRef = React.useRef<HTMLButtonElement>(null);
|
||||
const [root, setRoot] = React.useState<HTMLElement | null>(null);
|
||||
const [confirmingUninstall, setConfirmingUninstall] = React.useState(false);
|
||||
|
||||
// Restore focus on teardown
|
||||
useRestoreFocus(focusRef, root);
|
||||
const [focusRef] = useRestoreFocus();
|
||||
|
||||
React.useEffect(() => {
|
||||
const div = document.createElement('div');
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue