Skip to content

Commit 1702604

Browse files
viownthornbill
authored andcommitted
Replace toast with snackbar on dashboard
1 parent ae56c9e commit 1702604

8 files changed

Lines changed: 118 additions & 26 deletions

File tree

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import React, { useCallback } from 'react';
2+
import Snackbar, { SnackbarProps } from '@mui/material/Snackbar';
3+
import IconButton from '@mui/material/IconButton';
4+
import CloseIcon from '@mui/icons-material/Close';
5+
6+
const Toast = (props: SnackbarProps) => {
7+
const onCloseClick = useCallback((e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
8+
props.onClose?.(e, 'clickaway');
9+
}, [ props ]);
10+
11+
const action = (
12+
<IconButton
13+
size='small'
14+
color='inherit'
15+
onClick={onCloseClick}
16+
>
17+
<CloseIcon fontSize='small' />
18+
</IconButton>
19+
);
20+
21+
return (
22+
<Snackbar
23+
autoHideDuration={3300}
24+
action={action}
25+
{ ...props }
26+
/>
27+
);
28+
};
29+
30+
export default Toast;

src/apps/dashboard/features/backups/components/BackupInfoDialog.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import DialogContent from '@mui/material/DialogContent';
77
import DialogTitle from '@mui/material/DialogTitle';
88
import Box from '@mui/material/Box';
99
import globalize from 'lib/globalize';
10-
import React, { FunctionComponent, useCallback } from 'react';
10+
import React, { FunctionComponent, useCallback, useState } from 'react';
1111
import Stack from '@mui/material/Stack';
1212
import FormGroup from '@mui/material/FormGroup';
1313
import FormControl from '@mui/material/FormControl';
@@ -16,7 +16,7 @@ import Checkbox from '@mui/material/Checkbox';
1616
import ContentCopy from '@mui/icons-material/ContentCopy';
1717
import IconButton from '@mui/material/IconButton';
1818
import { copy } from 'scripts/clipboard';
19-
import toast from 'components/toast/toast';
19+
import Toast from 'apps/dashboard/components/Toast';
2020

2121
type IProps = {
2222
backup: BackupManifestDto;
@@ -25,10 +25,16 @@ type IProps = {
2525
};
2626

2727
const BackupInfoDialog: FunctionComponent<IProps> = ({ backup, open, onClose }: IProps) => {
28+
const [ isCopiedToastOpen, setIsCopiedToastOpen ] = useState(false);
29+
30+
const handleToastClose = useCallback(() => {
31+
setIsCopiedToastOpen(false);
32+
}, []);
33+
2834
const copyPath = useCallback(async () => {
2935
if (backup.Path) {
3036
await copy(backup.Path);
31-
toast({ text: globalize.translate('Copied') });
37+
setIsCopiedToastOpen(true);
3238
}
3339
}, [ backup.Path ]);
3440

@@ -39,6 +45,11 @@ const BackupInfoDialog: FunctionComponent<IProps> = ({ backup, open, onClose }:
3945
maxWidth={'sm'}
4046
fullWidth
4147
>
48+
<Toast
49+
open={isCopiedToastOpen}
50+
onClose={handleToastClose}
51+
message={globalize.translate('Copied')}
52+
/>
4253
<DialogTitle>
4354
{backup.DateCreated}
4455
</DialogTitle>

src/apps/dashboard/routes/logs/file.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Loading from 'components/loading/LoadingComponent';
22
import Page from 'components/Page';
3-
import React, { useCallback } from 'react';
3+
import React, { useCallback, useState } from 'react';
44
import { useParams } from 'react-router-dom';
55
import { useServerLog } from 'apps/dashboard/features/logs/api/useServerLog';
66
import Alert from '@mui/material/Alert';
@@ -13,8 +13,8 @@ import Typography from '@mui/material/Typography';
1313
import ContentCopy from '@mui/icons-material/ContentCopy';
1414
import FileDownload from '@mui/icons-material/FileDownload';
1515
import globalize from 'lib/globalize';
16-
import toast from 'components/toast/toast';
1716
import { copy } from 'scripts/clipboard';
17+
import Toast from 'apps/dashboard/components/Toast';
1818

1919
export const Component = () => {
2020
const { file: fileName } = useParams();
@@ -24,13 +24,18 @@ export const Component = () => {
2424
data: log,
2525
refetch
2626
} = useServerLog(fileName ?? '');
27+
const [ isCopiedToastOpen, setIsCopiedToastOpen ] = useState(false);
2728

2829
const retry = useCallback(() => refetch(), [refetch]);
2930

31+
const handleToastClose = useCallback(() => {
32+
setIsCopiedToastOpen(false);
33+
}, []);
34+
3035
const copyToClipboard = useCallback(async () => {
3136
if (log) {
3237
await copy(log);
33-
toast({ text: globalize.translate('CopyLogSuccess') });
38+
setIsCopiedToastOpen(true);
3439
}
3540
}, [log]);
3641

@@ -52,6 +57,11 @@ export const Component = () => {
5257
title={fileName}
5358
className='mainAnimatedPage type-interior'
5459
>
60+
<Toast
61+
open={isCopiedToastOpen}
62+
onClose={handleToastClose}
63+
message={globalize.translate('CopyLogSuccess')}
64+
/>
5565
<Container className='content-primary'>
5666
<Box>
5767
<Typography variant='h1'>{fileName}</Typography>

src/apps/dashboard/routes/users/access.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ import { useSearchParams } from 'react-router-dom';
44

55
import loading from '../../../../components/loading/loading';
66
import globalize from '../../../../lib/globalize';
7-
import toast from '../../../../components/toast/toast';
87
import SectionTabs from '../../../../components/dashboard/users/SectionTabs';
98
import Button from '../../../../elements/emby-button/Button';
109
import SectionTitleContainer from '../../../../elements/SectionTitleContainer';
1110
import AccessContainer from '../../../../components/dashboard/users/AccessContainer';
1211
import CheckBoxElement from '../../../../elements/CheckBoxElement';
1312
import Page from '../../../../components/Page';
13+
import Toast from 'apps/dashboard/components/Toast';
1414

1515
type ItemsArr = {
1616
Name?: string | null;
@@ -23,6 +23,7 @@ type ItemsArr = {
2323
const UserLibraryAccess = () => {
2424
const [ searchParams ] = useSearchParams();
2525
const userId = searchParams.get('userId');
26+
const [ isSettingsSavedToastOpen, setIsSettingsSavedToastOpen ] = useState(false);
2627
const [ userName, setUserName ] = useState('');
2728
const [channelsItems, setChannelsItems] = useState<ItemsArr[]>([]);
2829
const [mediaFoldersItems, setMediaFoldersItems] = useState<ItemsArr[]>([]);
@@ -31,6 +32,10 @@ const UserLibraryAccess = () => {
3132

3233
const element = useRef<HTMLDivElement>(null);
3334

35+
const handleToastClose = useCallback(() => {
36+
setIsSettingsSavedToastOpen(false);
37+
}, []);
38+
3439
const triggerChange = (select: HTMLInputElement) => {
3540
const evt = new Event('change', { bubbles: false, cancelable: true });
3641
select.dispatchEvent(evt);
@@ -220,7 +225,7 @@ const UserLibraryAccess = () => {
220225

221226
const onSaveComplete = () => {
222227
loading.hide();
223-
toast(globalize.translate('SettingsSaved'));
228+
setIsSettingsSavedToastOpen(true);
224229
};
225230

226231
(page.querySelector('.chkEnableAllDevices') as HTMLInputElement).addEventListener('change', function (this: HTMLInputElement) {
@@ -243,6 +248,11 @@ const UserLibraryAccess = () => {
243248
id='userLibraryAccessPage'
244249
className='mainAnimatedPage type-interior'
245250
>
251+
<Toast
252+
open={isSettingsSavedToastOpen}
253+
onClose={handleToastClose}
254+
message={globalize.translate('SettingsSaved')}
255+
/>
246256
<div ref={element} className='content-primary'>
247257
<div className='verticalSection'>
248258
<SectionTitleContainer

src/apps/dashboard/routes/users/add.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ import React, { useCallback, useEffect, useState, useRef } from 'react';
44
import Dashboard from '../../../../utils/dashboard';
55
import globalize from '../../../../lib/globalize';
66
import loading from '../../../../components/loading/loading';
7-
import toast from '../../../../components/toast/toast';
87
import SectionTitleContainer from '../../../../elements/SectionTitleContainer';
98
import Input from '../../../../elements/emby-input/Input';
109
import Button from '../../../../elements/emby-button/Button';
1110
import AccessContainer from '../../../../components/dashboard/users/AccessContainer';
1211
import CheckBoxElement from '../../../../elements/CheckBoxElement';
1312
import Page from '../../../../components/Page';
13+
import Toast from 'apps/dashboard/components/Toast';
1414

1515
type UserInput = {
1616
Name?: string;
@@ -25,8 +25,13 @@ type ItemsArr = {
2525
const UserNew = () => {
2626
const [ channelsItems, setChannelsItems ] = useState<ItemsArr[]>([]);
2727
const [ mediaFoldersItems, setMediaFoldersItems ] = useState<ItemsArr[]>([]);
28+
const [ isErrorToastOpen, setIsErrorToastOpen ] = useState(false);
2829
const element = useRef<HTMLDivElement>(null);
2930

31+
const handleToastClose = useCallback(() => {
32+
setIsErrorToastOpen(false);
33+
}, []);
34+
3035
const getItemsResult = (items: BaseItemDto[]) => {
3136
return items.map(item =>
3237
({
@@ -150,7 +155,7 @@ const UserNew = () => {
150155
console.error('[usernew] failed to update user policy', err);
151156
});
152157
}, function () {
153-
toast(globalize.translate('ErrorDefault'));
158+
setIsErrorToastOpen(true);
154159
loading.hide();
155160
});
156161
};
@@ -185,6 +190,11 @@ const UserNew = () => {
185190
id='newUserPage'
186191
className='mainAnimatedPage type-interior'
187192
>
193+
<Toast
194+
open={isErrorToastOpen}
195+
onClose={handleToastClose}
196+
message={globalize.translate('ErrorDefault')}
197+
/>
188198
<div ref={element} className='content-primary'>
189199
<div className='verticalSection'>
190200
<SectionTitleContainer

src/apps/dashboard/routes/users/index.tsx

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { UserDto } from '@jellyfin/sdk/lib/generated-client';
2-
import React, { useEffect, useState, useRef } from 'react';
2+
import React, { useEffect, useState, useRef, useCallback } from 'react';
33

44
import Dashboard from '../../../../utils/dashboard';
55
import globalize from '../../../../lib/globalize';
@@ -14,6 +14,8 @@ import '../../../../components/cardbuilder/card.scss';
1414
import '../../../../components/indicators/indicators.scss';
1515
import '../../../../styles/flexstyles.scss';
1616
import Page from '../../../../components/Page';
17+
import { useLocation } from 'react-router-dom';
18+
import Toast from 'apps/dashboard/components/Toast';
1719

1820
type MenuEntry = {
1921
name?: string;
@@ -22,10 +24,16 @@ type MenuEntry = {
2224
};
2325

2426
const UserProfiles = () => {
27+
const location = useLocation();
28+
const [ isSettingsSavedToastOpen, setIsSettingsSavedToastOpen ] = useState(false);
2529
const [ users, setUsers ] = useState<UserDto[]>([]);
2630

2731
const element = useRef<HTMLDivElement>(null);
2832

33+
const handleToastClose = useCallback(() => {
34+
setIsSettingsSavedToastOpen(false);
35+
}, []);
36+
2937
const loadData = () => {
3038
loading.show();
3139
window.ApiClient.getUsers().then(function (result) {
@@ -39,6 +47,11 @@ const UserProfiles = () => {
3947
useEffect(() => {
4048
const page = element.current;
4149

50+
if (location.state?.openSavedToast) {
51+
setIsSettingsSavedToastOpen(true);
52+
window.history.replaceState({}, '');
53+
}
54+
4255
if (!page) {
4356
console.error('Unexpected null reference');
4457
return;
@@ -161,6 +174,11 @@ const UserProfiles = () => {
161174
className='mainAnimatedPage type-interior userProfilesPage fullWidthContent'
162175
title={globalize.translate('HeaderUsers')}
163176
>
177+
<Toast
178+
open={isSettingsSavedToastOpen}
179+
onClose={handleToastClose}
180+
message={globalize.translate('SettingsSaved')}
181+
/>
164182
<div ref={element} className='content-primary'>
165183
<div className='verticalSection'>
166184
<SectionTitleContainer

src/apps/dashboard/routes/users/parentalcontrol.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ import Button from '../../../../elements/emby-button/Button';
1212
import SectionTitleContainer from '../../../../elements/SectionTitleContainer';
1313
import SectionTabs from '../../../../components/dashboard/users/SectionTabs';
1414
import loading from '../../../../components/loading/loading';
15-
import toast from '../../../../components/toast/toast';
1615
import CheckBoxElement from '../../../../elements/CheckBoxElement';
1716
import SelectElement from '../../../../elements/SelectElement';
1817
import Page from '../../../../components/Page';
1918
import prompt from '../../../../components/prompt/prompt';
2019
import { ServerConnections } from 'lib/jellyfin-apiclient';
20+
import Toast from 'apps/dashboard/components/Toast';
2121

2222
type NamedItem = {
2323
name: string;
@@ -74,11 +74,16 @@ const UserParentalControl = () => {
7474
const [ accessSchedules, setAccessSchedules ] = useState<AccessSchedule[]>([]);
7575
const [ allowedTags, setAllowedTags ] = useState<string[]>([]);
7676
const [ blockedTags, setBlockedTags ] = useState<string[]>([]);
77+
const [ isSettingsSavedToastOpen, setIsSettingsSavedToastOpen ] = useState(false);
7778
const libraryMenu = useMemo(async () => ((await import('../../../../scripts/libraryMenu')).default), []);
7879

7980
const element = useRef<HTMLDivElement>(null);
8081
const parentalRatingsRef = useRef<ParentalRating[]>([]);
8182

83+
const handleToastClose = useCallback(() => {
84+
setIsSettingsSavedToastOpen(false);
85+
}, []);
86+
8287
const loadUnratedItems = useCallback((user: UserDto) => {
8388
const page = element.current;
8489

@@ -300,7 +305,7 @@ const UserParentalControl = () => {
300305

301306
const onSaveComplete = () => {
302307
loading.hide();
303-
toast(globalize.translate('SettingsSaved'));
308+
setIsSettingsSavedToastOpen(true);
304309
};
305310

306311
const saveUser = handleSaveUser(page, parentalRatingsRef, getSchedulesFromPage, getAllowedTagsFromPage, getBlockedTagsFromPage, onSaveComplete);
@@ -387,6 +392,11 @@ const UserParentalControl = () => {
387392
id='userParentalControlPage'
388393
className='mainAnimatedPage type-interior'
389394
>
395+
<Toast
396+
open={isSettingsSavedToastOpen}
397+
onClose={handleToastClose}
398+
message={globalize.translate('SettingsSaved')}
399+
/>
390400
<div ref={element} className='content-primary'>
391401
<div className='verticalSection'>
392402
<SectionTitleContainer

src/apps/dashboard/routes/users/profile.tsx

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import type { BaseItemDto, NameIdPair, SyncPlayUserAccessType, UserDto } from '@jellyfin/sdk/lib/generated-client';
22
import escapeHTML from 'escape-html';
33
import React, { useCallback, useEffect, useState, useRef, useMemo } from 'react';
4-
import { useSearchParams } from 'react-router-dom';
4+
import { useNavigate, useSearchParams } from 'react-router-dom';
55

6-
import Dashboard from '../../../../utils/dashboard';
76
import globalize from '../../../../lib/globalize';
87
import Button from '../../../../elements/emby-button/Button';
98
import CheckBoxElement from '../../../../elements/CheckBoxElement';
@@ -12,7 +11,6 @@ import Input from '../../../../elements/emby-input/Input';
1211
import SectionTitleContainer from '../../../../elements/SectionTitleContainer';
1312
import SectionTabs from '../../../../components/dashboard/users/SectionTabs';
1413
import loading from '../../../../components/loading/loading';
15-
import toast from '../../../../components/toast/toast';
1614
import SelectElement from '../../../../elements/SelectElement';
1715
import Page from '../../../../components/Page';
1816

@@ -25,16 +23,8 @@ const getCheckedElementDataIds = (elements: NodeListOf<Element>) => (
2523
.map(e => e.getAttribute('data-id'))
2624
);
2725

28-
function onSaveComplete() {
29-
Dashboard.navigate('/dashboard/users')
30-
.catch(err => {
31-
console.error('[useredit] failed to navigate to user profile', err);
32-
});
33-
loading.hide();
34-
toast(globalize.translate('SettingsSaved'));
35-
}
36-
3726
const UserEdit = () => {
27+
const navigate = useNavigate();
3828
const [ searchParams ] = useSearchParams();
3929
const userId = searchParams.get('userId');
4030
const [ userDto, setUserDto ] = useState<UserDto>();
@@ -228,7 +218,10 @@ const UserEdit = () => {
228218
window.ApiClient.updateUser(user).then(() => (
229219
window.ApiClient.updateUserPolicy(user.Id || '', user.Policy || { PasswordResetProviderId: '', AuthenticationProviderId: '' })
230220
)).then(() => {
231-
onSaveComplete();
221+
navigate('/dashboard/users', {
222+
state: { openSavedToast: true }
223+
});
224+
loading.hide();
232225
}).catch(err => {
233226
console.error('[useredit] failed to update user', err);
234227
});

0 commit comments

Comments
 (0)