From c6a6ad9b4bfc42c28f30361852c441e9c24d8c2a Mon Sep 17 00:00:00 2001 From: Akbar Abdrakhmanov Date: Mon, 30 Oct 2023 23:48:29 +0200 Subject: [PATCH] [MM-50821] Notify about successful login only once via screen reader (#24979) * [MM-50821] Notify about successful login only once via screen reader * clear timeout via useEffect instead of inside setTimeout in AdvanceTextEditor * declare timeout as ref in AdvancedTextEditor --------- Co-authored-by: Mattermost Build --- .../advanced_text_editor.tsx | 53 +++++++++++++------ .../channels/src/components/login/login.tsx | 5 ++ .../src/stores/local_storage_store.ts | 11 ++++ 3 files changed, 54 insertions(+), 15 deletions(-) diff --git a/webapp/channels/src/components/advanced_text_editor/advanced_text_editor.tsx b/webapp/channels/src/components/advanced_text_editor/advanced_text_editor.tsx index 6c84ac6ee9..27a635f3da 100644 --- a/webapp/channels/src/components/advanced_text_editor/advanced_text_editor.tsx +++ b/webapp/channels/src/components/advanced_text_editor/advanced_text_editor.tsx @@ -2,7 +2,7 @@ // See LICENSE.txt for license information. import classNames from 'classnames'; -import React, {useCallback, useEffect, useRef, useState} from 'react'; +import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {FormattedMessage, useIntl} from 'react-intl'; import {useDispatch} from 'react-redux'; @@ -13,6 +13,7 @@ import type {ServerError} from '@mattermost/types/errors'; import type {FileInfo} from '@mattermost/types/files'; import {emitShortcutReactToLastPostFrom} from 'actions/post_actions'; +import LocalStorageStore from 'stores/local_storage_store'; import AutoHeightSwitcher from 'components/common/auto_height_switcher'; import EmojiPickerOverlay from 'components/emoji_picker/emoji_picker_overlay'; @@ -191,6 +192,7 @@ const AdvanceTextEditor = ({ const emojiPickerRef = useRef(null); const editorActionsRef = useRef(null); const editorBodyRef = useRef(null); + const timeout = useRef(); const [renderScrollbar, setRenderScrollbar] = useState(false); const [showFormattingSpacer, setShowFormattingSpacer] = useState(shouldShowPreview); @@ -605,6 +607,29 @@ const AdvanceTextEditor = ({ } }, [handleWidthChange, message]); + useEffect(() => { + return () => timeout.current && clearTimeout(timeout.current); + }, []); + + const wasNotifiedOfLogIn = LocalStorageStore.getWasNotifiedOfLogIn(); + + const ariaLabel = useMemo(() => { + let label; + if (!wasNotifiedOfLogIn) { + label = Utils.localizeMessage( + 'channelView.login.successfull', + 'Login Successful', + ); + + // set timeout to make sure aria-label is read by a screen reader, + // and then set the flag to "true" to make sure it's not read again until a user logs back in + timeout.current = setTimeout(() => { + LocalStorageStore.setWasNotifiedOfLogIn(true); + }, 3000); + } + return label ? `${label} ${ariaLabelMessageInput}` : ariaLabelMessageInput; + }, [ariaLabelMessageInput, wasNotifiedOfLogIn]); + const formattingBar = ( -
- -
+ {!wasNotifiedOfLogIn && ( +
+ +
+ )}
diff --git a/webapp/channels/src/components/login/login.tsx b/webapp/channels/src/components/login/login.tsx index 43bb3b34ca..de57dde41f 100644 --- a/webapp/channels/src/components/login/login.tsx +++ b/webapp/channels/src/components/login/login.tsx @@ -659,6 +659,11 @@ const Login = ({onCustomizeHeader}: LoginProps) => { // Record a successful login to local storage. If an unintentional logout occurs, e.g. // via session expiration, this bit won't get reset and we can notify the user as such. LocalStorageStore.setWasLoggedIn(true); + + // After a user has just logged in, we set the following flag to "false" so that after + // a user is notified of successful login, we can set it back to "true" + LocalStorageStore.setWasNotifiedOfLogIn(false); + if (redirectTo && redirectTo.match(/^\/([^/]|$)/)) { history.push(redirectTo); } else if (team) { diff --git a/webapp/channels/src/stores/local_storage_store.ts b/webapp/channels/src/stores/local_storage_store.ts index dc52cfba68..ee25ecca37 100644 --- a/webapp/channels/src/stores/local_storage_store.ts +++ b/webapp/channels/src/stores/local_storage_store.ts @@ -16,6 +16,7 @@ export const getPenultimateChannelNameKey = (userId: string, teamId: string) => const getRecentEmojisKey = (userId: string) => ['recent_emojis', userId].join(':'); const getWasLoggedInKey = () => 'was_logged_in'; const teamIdJoinedOnLoadKey = 'teamIdJoinedOnLoad'; +const wasNotifiedOfLogInKey = 'was_notified_of_login'; const getPathScopedKey = (path: string, key: string) => { if (path === '' || path === '/') { @@ -156,6 +157,16 @@ class LocalStorageStoreClass { getWasLoggedIn() { return this.getItem(getWasLoggedInKey()) === 'true'; } + + // the following flag's setter and getter are used to make sure a user is notified (via aria-label) + // about a successful login only once (MM-50821) + setWasNotifiedOfLogIn(wasNotified: boolean) { + this.setItem(wasNotifiedOfLogInKey, String(wasNotified)); + } + + getWasNotifiedOfLogIn() { + return this.getItem(wasNotifiedOfLogInKey) === 'true'; + } } const LocalStorageStore = new LocalStorageStoreClass();