Move user settings menu to react (#6675)

* Move user settings menu to react

* Add default href value to LinkButton

* Use loading component while data is pending
This commit is contained in:
Bill Thornton
2025-04-01 15:10:30 -04:00
committed by GitHub
parent 2685950934
commit d4995834df
12 changed files with 393 additions and 229 deletions
@@ -2,14 +2,15 @@ import { AsyncRoute } from 'components/router/AsyncRoute';
import { AppType } from 'constants/appType';
export const ASYNC_USER_ROUTES: AsyncRoute[] = [
{ path: 'home', page: 'home', type: AppType.Experimental },
{ path: 'quickconnect', page: 'quickConnect' },
{ path: 'search', page: 'search' },
{ path: 'userprofile', page: 'user/userprofile' },
{ path: 'movies', page: 'movies', type: AppType.Experimental },
{ path: 'tv', page: 'shows', type: AppType.Experimental },
{ path: 'music', page: 'music', type: AppType.Experimental },
{ path: 'livetv', page: 'livetv', type: AppType.Experimental },
{ path: 'home', type: AppType.Experimental },
{ path: 'homevideos', type: AppType.Experimental },
{ path: 'livetv', type: AppType.Experimental },
{ path: 'movies', type: AppType.Experimental },
{ path: 'music', type: AppType.Experimental },
{ path: 'mypreferencesdisplay', page: 'user/display', type: AppType.Experimental },
{ path: 'homevideos', page: 'homevideos', type: AppType.Experimental }
{ path: 'mypreferencesmenu', page: 'user/settings' },
{ path: 'quickconnect', page: 'quickConnect' },
{ path: 'search' },
{ path: 'tv', page: 'shows', type: AppType.Experimental },
{ path: 'userprofile', page: 'user/userprofile' }
];
@@ -19,12 +19,6 @@ export const LEGACY_USER_ROUTES: LegacyRoute[] = [
controller: 'lyrics',
view: 'lyrics.html'
}
}, {
path: 'mypreferencesmenu',
pageProps: {
controller: 'user/menu/index',
view: 'user/menu/index.html'
}
}, {
path: 'mypreferencescontrols',
pageProps: {
@@ -1,6 +1,7 @@
import { AsyncRoute } from '../../../../components/router/AsyncRoute';
export const ASYNC_USER_ROUTES: AsyncRoute[] = [
{ path: 'mypreferencesmenu', page: 'user/settings' },
{ path: 'quickconnect', page: 'quickConnect' },
{ path: 'search', page: 'search' },
{ path: 'userprofile', page: 'user/userprofile' }
@@ -31,12 +31,6 @@ export const LEGACY_USER_ROUTES: LegacyRoute[] = [
controller: 'music/musicrecommended',
view: 'music/music.html'
}
}, {
path: 'mypreferencesmenu',
pageProps: {
controller: 'user/menu/index',
view: 'user/menu/index.html'
}
}, {
path: 'mypreferencescontrols',
pageProps: {
@@ -0,0 +1,361 @@
import type { UserDto } from '@jellyfin/sdk/lib/generated-client/models/user-dto';
import React, { useEffect, useMemo, useState, type FC } from 'react';
import { useSearchParams } from 'react-router-dom';
import { appHost } from 'components/apphost';
import layoutManager from 'components/layoutManager';
import Loading from 'components/loading/LoadingComponent';
import Page from 'components/Page';
import LinkButton from 'elements/emby-button/LinkButton';
import { useApi } from 'hooks/useApi';
import { useQuickConnectEnabled } from 'hooks/useQuickConnect';
import { useUsers } from 'hooks/useUsers';
import globalize from 'lib/globalize';
import browser from 'scripts/browser';
import Dashboard from 'utils/dashboard';
import shell from 'scripts/shell';
const UserSettingsPage: FC = () => {
const { user: currentUser } = useApi();
const [ searchParams ] = useSearchParams();
const {
data: isQuickConnectEnabled,
isPending: isQuickConnectEnabledPending
} = useQuickConnectEnabled();
const { data: users } = useUsers();
const [ user, setUser ] = useState<UserDto>();
const userId = useMemo(() => (
searchParams.get('userId') || currentUser?.Id
), [ currentUser, searchParams ]);
const isLoggedInUser = useMemo(() => (
userId && userId === currentUser?.Id
), [ currentUser, userId ]);
useEffect(() => {
if (userId) {
if (userId === currentUser?.Id) setUser(currentUser);
else setUser(users?.find(({ Id }) => userId === Id));
}
}, [ currentUser, userId, users ]);
if (!userId || !user || isQuickConnectEnabledPending) {
return (
<Loading />
);
}
return (
<Page
id='myPreferencesMenuPage'
className='libraryPage userPreferencesPage noSecondaryNavPage mainAnimatedPage'
title={globalize.translate('Settings')}
shouldAutoFocus
>
<div className='padded-left padded-right padded-bottom-page padded-top'>
<div
className='readOnlyContent'
style={{
margin: '0 auto'
}}
>
<div className='verticalSection verticalSection-extrabottompadding'>
<h2
className='sectionTitle headerUsername'
style={{
paddingLeft: '0.25em'
}}
>
{user.Name}
</h2>
<LinkButton
href={`#/userprofile?userId=${userId}`}
className='lnkUserProfile listItem-border'
style={{
display: 'block',
margin: 0,
padding: 0
}}
>
<div className='listItem'>
<span className='material-icons listItemIcon listItemIcon-transparent person' aria-hidden='true' />
<div className='listItemBody'>
<div className='listItemBodyText'>
{globalize.translate('Profile')}
</div>
</div>
</div>
</LinkButton>
{isQuickConnectEnabled && (
<LinkButton
href={`#/quickconnect?userId=${userId}`}
className='lnkQuickConnectPreferences listItem-border'
style={{
display: 'block',
margin: 0,
padding: 0
}}
>
<div className='listItem'>
<span className='material-icons listItemIcon listItemIcon-transparent phonelink_lock' aria-hidden='true' />
<div className='listItemBody'>
<div className='listItemBodyText'>
{globalize.translate('QuickConnect')}
</div>
</div>
</div>
</LinkButton>
)}
<LinkButton
href={`#/mypreferencesdisplay?userId=${userId}`}
className='lnkDisplayPreferences listItem-border'
style={{
display: 'block',
margin: 0,
padding: 0
}}
>
<div className='listItem'>
<span className='material-icons listItemIcon listItemIcon-transparent tv' aria-hidden='true' />
<div className='listItemBody'>
<div className='listItemBodyText'>
{globalize.translate('Display')}
</div>
</div>
</div>
</LinkButton>
<LinkButton
href={`#/mypreferenceshome?userId=${userId}`}
className='lnkHomePreferences listItem-border'
style={{
display: 'block',
margin: 0,
padding: 0
}}
>
<div className='listItem'>
<span className='material-icons listItemIcon listItemIcon-transparent home' aria-hidden='true' />
<div className='listItemBody'>
<div className='listItemBodyText'>
{globalize.translate('Home')}
</div>
</div>
</div>
</LinkButton>
<LinkButton
href={`#/mypreferencesplayback?userId=${userId}`}
className='lnkPlaybackPreferences listItem-border'
style={{
display: 'block',
margin: 0,
padding: 0
}}
>
<div className='listItem'>
<span className='material-icons listItemIcon listItemIcon-transparent play_circle_filled' aria-hidden='true' />
<div className='listItemBody'>
<div className='listItemBodyText'>
{globalize.translate('TitlePlayback')}
</div>
</div>
</div>
</LinkButton>
<LinkButton
href={`#/mypreferencessubtitles?userId=${userId}`}
className='lnkSubtitlePreferences listItem-border'
style={{
display: 'block',
margin: 0,
padding: 0
}}
>
<div className='listItem'>
<span className='material-icons listItemIcon listItemIcon-transparent closed_caption' aria-hidden='true' />
<div className='listItemBody'>
<div className='listItemBodyText'>
{globalize.translate('Subtitles')}
</div>
</div>
</div>
</LinkButton>
{appHost.supports('clientsettings') && (
<LinkButton
onClick={shell.openClientSettings}
className='clientSettings listItem-border'
style={{
display: 'block',
margin: 0,
padding: 0
}}
>
<div className='listItem'>
<span className='material-icons listItemIcon listItemIcon-transparent devices_other' aria-hidden='true' />
<div className='listItemBody'>
<div className='listItemBodyText'>
{globalize.translate('ClientSettings')}
</div>
</div>
</div>
</LinkButton>
)}
{isLoggedInUser && !browser.mobile && (
<LinkButton
href={`#/mypreferencescontrols?userId=${userId}`}
className='lnkControlsPreferences listItem-border'
style={{
display: 'block',
margin: 0,
padding: 0
}}
>
<div className='listItem'>
<span className='material-icons listItemIcon listItemIcon-transparent keyboard' aria-hidden='true' />
<div className='listItemBody'>
<div className='listItemBodyText'>
{globalize.translate('Controls')}
</div>
</div>
</div>
</LinkButton>
)}
</div>
{isLoggedInUser && user.Policy?.IsAdministrator && !layoutManager.tv && (
<div className='adminSection verticalSection verticalSection-extrabottompadding'>
<h2
className='sectionTitle headerUsername'
style={{
paddingLeft: '0.25em'
}}
>
{globalize.translate('HeaderAdmin')}
</h2>
<LinkButton
href='#/dashboard'
className='listItem-border'
style={{
display: 'block',
margin: 0,
padding: 0
}}
>
<div className='listItem'>
<span className='material-icons listItemIcon listItemIcon-transparent dashboard' aria-hidden='true' />
<div className='listItemBody'>
<div className='listItemBodyText'>
{globalize.translate('TabDashboard')}
</div>
</div>
</div>
</LinkButton>
<LinkButton
href='#/metadata'
className='listItem-border'
style={{
display: 'block',
margin: 0,
padding: 0
}}
>
<div className='listItem'>
<span className='material-icons listItemIcon listItemIcon-transparent mode_edit' aria-hidden='true' />
<div className='listItemBody'>
<div className='listItemBodyText'>
{globalize.translate('MetadataManager')}
</div>
</div>
</div>
</LinkButton>
</div>
)}
{isLoggedInUser && (
<div className='userSection verticalSection verticalSection-extrabottompadding'>
<h2
className='sectionTitle headerUsername'
style={{
paddingLeft: '0.25em'
}}
>
{globalize.translate('HeaderUser')}
</h2>
{appHost.supports('multiserver') && (
<LinkButton
onClick={Dashboard.selectServer}
className='selectServer listItem-border'
style={{
display: 'block',
margin: 0,
padding: 0
}}
>
<div className='listItem'>
<span className='material-icons listItemIcon listItemIcon-transparent storage' aria-hidden='true' />
<div className='listItemBody'>
<div className='listItemBodyText'>
{globalize.translate('SelectServer')}
</div>
</div>
</div>
</LinkButton>
)}
<LinkButton
onClick={Dashboard.logout}
className='btnLogout listItem-border'
style={{
display: 'block',
margin: 0,
padding: 0
}}
>
<div className='listItem'>
<span className='material-icons listItemIcon listItemIcon-transparent exit_to_app' aria-hidden='true' />
<div className='listItemBody'>
<div className='listItemBodyText'>
{globalize.translate('ButtonSignOut')}
</div>
</div>
</div>
</LinkButton>
{appHost.supports('exitmenu') && (
<LinkButton
onClick={appHost.exit}
className='exitApp listItem-border'
style={{
display: 'block',
margin: 0,
padding: 0
}}
>
<div className='listItem'>
<span className='material-icons listItemIcon listItemIcon-transparent close' aria-hidden='true' />
<div className='listItemBody'>
<div className='listItemBodyText'>
{globalize.translate('ButtonExitApp')}
</div>
</div>
</div>
</LinkButton>
)}
</div>
)}
</div>
</div>
</Page>
);
};
export default UserSettingsPage;