diff --git a/src/apps/dashboard/components/Toast.tsx b/src/apps/dashboard/components/Toast.tsx new file mode 100644 index 0000000000..373ba4157f --- /dev/null +++ b/src/apps/dashboard/components/Toast.tsx @@ -0,0 +1,30 @@ +import React, { useCallback } from 'react'; +import Snackbar, { SnackbarProps } from '@mui/material/Snackbar'; +import IconButton from '@mui/material/IconButton'; +import CloseIcon from '@mui/icons-material/Close'; + +const Toast = (props: SnackbarProps) => { + const onCloseClick = useCallback((e: React.MouseEvent) => { + props.onClose?.(e, 'clickaway'); + }, [ props ]); + + const action = ( + + + + ); + + return ( + + ); +}; + +export default Toast; diff --git a/src/apps/dashboard/features/backups/components/BackupInfoDialog.tsx b/src/apps/dashboard/features/backups/components/BackupInfoDialog.tsx index db0a3c8d67..35eb8f6e5a 100644 --- a/src/apps/dashboard/features/backups/components/BackupInfoDialog.tsx +++ b/src/apps/dashboard/features/backups/components/BackupInfoDialog.tsx @@ -7,7 +7,7 @@ import DialogContent from '@mui/material/DialogContent'; import DialogTitle from '@mui/material/DialogTitle'; import Box from '@mui/material/Box'; import globalize from 'lib/globalize'; -import React, { FunctionComponent, useCallback } from 'react'; +import React, { FunctionComponent, useCallback, useState } from 'react'; import Stack from '@mui/material/Stack'; import FormGroup from '@mui/material/FormGroup'; import FormControl from '@mui/material/FormControl'; @@ -16,7 +16,7 @@ import Checkbox from '@mui/material/Checkbox'; import ContentCopy from '@mui/icons-material/ContentCopy'; import IconButton from '@mui/material/IconButton'; import { copy } from 'scripts/clipboard'; -import toast from 'components/toast/toast'; +import Toast from 'apps/dashboard/components/Toast'; type IProps = { backup: BackupManifestDto; @@ -25,10 +25,16 @@ type IProps = { }; const BackupInfoDialog: FunctionComponent = ({ backup, open, onClose }: IProps) => { + const [ isCopiedToastOpen, setIsCopiedToastOpen ] = useState(false); + + const handleToastClose = useCallback(() => { + setIsCopiedToastOpen(false); + }, []); + const copyPath = useCallback(async () => { if (backup.Path) { await copy(backup.Path); - toast({ text: globalize.translate('Copied') }); + setIsCopiedToastOpen(true); } }, [ backup.Path ]); @@ -39,6 +45,11 @@ const BackupInfoDialog: FunctionComponent = ({ backup, open, onClose }: maxWidth={'sm'} fullWidth > + {backup.DateCreated} diff --git a/src/apps/dashboard/routes/logs/file.tsx b/src/apps/dashboard/routes/logs/file.tsx index 76dff514f0..bd41b8f0b3 100644 --- a/src/apps/dashboard/routes/logs/file.tsx +++ b/src/apps/dashboard/routes/logs/file.tsx @@ -1,6 +1,6 @@ import Loading from 'components/loading/LoadingComponent'; import Page from 'components/Page'; -import React, { useCallback } from 'react'; +import React, { useCallback, useState } from 'react'; import { useParams } from 'react-router-dom'; import { useServerLog } from 'apps/dashboard/features/logs/api/useServerLog'; import Alert from '@mui/material/Alert'; @@ -13,8 +13,8 @@ import Typography from '@mui/material/Typography'; import ContentCopy from '@mui/icons-material/ContentCopy'; import FileDownload from '@mui/icons-material/FileDownload'; import globalize from 'lib/globalize'; -import toast from 'components/toast/toast'; import { copy } from 'scripts/clipboard'; +import Toast from 'apps/dashboard/components/Toast'; export const Component = () => { const { file: fileName } = useParams(); @@ -24,13 +24,18 @@ export const Component = () => { data: log, refetch } = useServerLog(fileName ?? ''); + const [ isCopiedToastOpen, setIsCopiedToastOpen ] = useState(false); const retry = useCallback(() => refetch(), [refetch]); + const handleToastClose = useCallback(() => { + setIsCopiedToastOpen(false); + }, []); + const copyToClipboard = useCallback(async () => { if (log) { await copy(log); - toast({ text: globalize.translate('CopyLogSuccess') }); + setIsCopiedToastOpen(true); } }, [log]); @@ -52,6 +57,11 @@ export const Component = () => { title={fileName} className='mainAnimatedPage type-interior' > + {fileName} diff --git a/src/apps/dashboard/routes/users/access.tsx b/src/apps/dashboard/routes/users/access.tsx index cac8e71f30..3fa9e56328 100644 --- a/src/apps/dashboard/routes/users/access.tsx +++ b/src/apps/dashboard/routes/users/access.tsx @@ -4,13 +4,13 @@ import { useSearchParams } from 'react-router-dom'; import loading from '../../../../components/loading/loading'; import globalize from '../../../../lib/globalize'; -import toast from '../../../../components/toast/toast'; import SectionTabs from '../../../../components/dashboard/users/SectionTabs'; import Button from '../../../../elements/emby-button/Button'; import SectionTitleContainer from '../../../../elements/SectionTitleContainer'; import AccessContainer from '../../../../components/dashboard/users/AccessContainer'; import CheckBoxElement from '../../../../elements/CheckBoxElement'; import Page from '../../../../components/Page'; +import Toast from 'apps/dashboard/components/Toast'; type ItemsArr = { Name?: string | null; @@ -23,6 +23,7 @@ type ItemsArr = { const UserLibraryAccess = () => { const [ searchParams ] = useSearchParams(); const userId = searchParams.get('userId'); + const [ isSettingsSavedToastOpen, setIsSettingsSavedToastOpen ] = useState(false); const [ userName, setUserName ] = useState(''); const [channelsItems, setChannelsItems] = useState([]); const [mediaFoldersItems, setMediaFoldersItems] = useState([]); @@ -31,6 +32,10 @@ const UserLibraryAccess = () => { const element = useRef(null); + const handleToastClose = useCallback(() => { + setIsSettingsSavedToastOpen(false); + }, []); + const triggerChange = (select: HTMLInputElement) => { const evt = new Event('change', { bubbles: false, cancelable: true }); select.dispatchEvent(evt); @@ -220,7 +225,7 @@ const UserLibraryAccess = () => { const onSaveComplete = () => { loading.hide(); - toast(globalize.translate('SettingsSaved')); + setIsSettingsSavedToastOpen(true); }; (page.querySelector('.chkEnableAllDevices') as HTMLInputElement).addEventListener('change', function (this: HTMLInputElement) { @@ -243,6 +248,11 @@ const UserLibraryAccess = () => { id='userLibraryAccessPage' className='mainAnimatedPage type-interior' > +
{ const [ channelsItems, setChannelsItems ] = useState([]); const [ mediaFoldersItems, setMediaFoldersItems ] = useState([]); + const [ isErrorToastOpen, setIsErrorToastOpen ] = useState(false); const element = useRef(null); + const handleToastClose = useCallback(() => { + setIsErrorToastOpen(false); + }, []); + const getItemsResult = (items: BaseItemDto[]) => { return items.map(item => ({ @@ -150,7 +155,7 @@ const UserNew = () => { console.error('[usernew] failed to update user policy', err); }); }, function () { - toast(globalize.translate('ErrorDefault')); + setIsErrorToastOpen(true); loading.hide(); }); }; @@ -185,6 +190,11 @@ const UserNew = () => { id='newUserPage' className='mainAnimatedPage type-interior' > +
{ + const location = useLocation(); + const [ isSettingsSavedToastOpen, setIsSettingsSavedToastOpen ] = useState(false); const [ users, setUsers ] = useState([]); const element = useRef(null); + const handleToastClose = useCallback(() => { + setIsSettingsSavedToastOpen(false); + }, []); + const loadData = () => { loading.show(); window.ApiClient.getUsers().then(function (result) { @@ -39,6 +47,11 @@ const UserProfiles = () => { useEffect(() => { const page = element.current; + if (location.state?.openSavedToast) { + setIsSettingsSavedToastOpen(true); + window.history.replaceState({}, ''); + } + if (!page) { console.error('Unexpected null reference'); return; @@ -161,6 +174,11 @@ const UserProfiles = () => { className='mainAnimatedPage type-interior userProfilesPage fullWidthContent' title={globalize.translate('HeaderUsers')} > +
{ const [ accessSchedules, setAccessSchedules ] = useState([]); const [ allowedTags, setAllowedTags ] = useState([]); const [ blockedTags, setBlockedTags ] = useState([]); + const [ isSettingsSavedToastOpen, setIsSettingsSavedToastOpen ] = useState(false); const libraryMenu = useMemo(async () => ((await import('../../../../scripts/libraryMenu')).default), []); const element = useRef(null); const parentalRatingsRef = useRef([]); + const handleToastClose = useCallback(() => { + setIsSettingsSavedToastOpen(false); + }, []); + const loadUnratedItems = useCallback((user: UserDto) => { const page = element.current; @@ -300,7 +305,7 @@ const UserParentalControl = () => { const onSaveComplete = () => { loading.hide(); - toast(globalize.translate('SettingsSaved')); + setIsSettingsSavedToastOpen(true); }; const saveUser = handleSaveUser(page, parentalRatingsRef, getSchedulesFromPage, getAllowedTagsFromPage, getBlockedTagsFromPage, onSaveComplete); @@ -387,6 +392,11 @@ const UserParentalControl = () => { id='userParentalControlPage' className='mainAnimatedPage type-interior' > +
) => ( .map(e => e.getAttribute('data-id')) ); -function onSaveComplete() { - Dashboard.navigate('/dashboard/users') - .catch(err => { - console.error('[useredit] failed to navigate to user profile', err); - }); - loading.hide(); - toast(globalize.translate('SettingsSaved')); -} - const UserEdit = () => { + const navigate = useNavigate(); const [ searchParams ] = useSearchParams(); const userId = searchParams.get('userId'); const [ userDto, setUserDto ] = useState(); @@ -228,7 +218,10 @@ const UserEdit = () => { window.ApiClient.updateUser(user).then(() => ( window.ApiClient.updateUserPolicy(user.Id || '', user.Policy || { PasswordResetProviderId: '', AuthenticationProviderId: '' }) )).then(() => { - onSaveComplete(); + navigate('/dashboard/users', { + state: { openSavedToast: true } + }); + loading.hide(); }).catch(err => { console.error('[useredit] failed to update user', err); });