feat: add Media Server Selection to Setup Page

Introduce the ability to select the media server type on the setup page. Users can now choose their
preferred media server (e.g., Plex through the Plex sign-in or Emby/Jellyfin sign-in to select
either Emby or Jellyfin). The selected media server type is then reflected in the application
settings. This enhancement provides users with increased flexibility and customization options
during the initial setup process, eliminating the need to rely on environment variables (which
cannot be set if using platforms like snaps). Existing Emby users, who use the environment variable,
should log out and log back in after updating to set their mediaServerType to Emby.

BREAKING CHANGE: This commit deprecates the JELLYFIN_TYPE variable to identify Emby media server and
instead rely on the mediaServerType that is set in the `settings.json`. Existing environment
variable users can log out and log back in to set the mediaServerType to `3` (Emby).
This commit is contained in:
fallenbagel
2023-11-18 02:20:05 +05:00
parent 4005397f3d
commit 395a91c2f0
24 changed files with 412 additions and 197 deletions

View File

@@ -2,4 +2,5 @@ export enum UserType {
PLEX = 1,
LOCAL = 2,
JELLYFIN = 3,
EMBY = 4,
}

View File

@@ -210,7 +210,9 @@ class Media {
}
} else {
const pageName =
process.env.JELLYFIN_TYPE === 'emby' ? 'item' : 'details';
getSettings().main.mediaServerType == MediaServerType.EMBY
? 'item'
: 'details';
const { serverId, hostname, externalHostname } = getSettings().jellyfin;
let jellyfinHost =
externalHostname && externalHostname.length > 0

View File

@@ -219,14 +219,18 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
password?: string;
hostname?: string;
email?: string;
selectedservice?: string;
};
//Make sure jellyfin login is enabled, but only if jellyfin is not already configured
//Make sure jellyfin login is enabled, but only if jellyfin && Emby is not already configured
if (
settings.main.mediaServerType !== MediaServerType.JELLYFIN &&
settings.main.mediaServerType !== MediaServerType.EMBY &&
settings.jellyfin.hostname !== ''
) {
return res.status(500).json({ error: 'Jellyfin login is disabled' });
} else if (!body.selectedservice) {
return res.status(500).json({ error: 'No server type provided.' });
} else if (!body.username) {
return res.status(500).json({ error: 'You must provide an username' });
} else if (settings.jellyfin.hostname !== '' && body.hostname) {
@@ -292,6 +296,13 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
if (user.username === account.User.Name) {
user.username = '';
}
// If JELLYFIN_TYPE is set to 'emby' then set mediaServerType to EMBY
if (process.env.JELLYFIN_TYPE === 'emby') {
settings.main.mediaServerType = MediaServerType.EMBY;
settings.save();
}
await userRepository.save(user);
} else if (!settings.main.newPlexLogin) {
logger.warn(
@@ -320,6 +331,7 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
jellyfinUsername: account.User.Name,
}
);
user = new User({
email: body.email,
jellyfinUsername: account.User.Name,
@@ -337,7 +349,16 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
//Update hostname in settings if it doesn't exist (initial configuration)
//Also set mediaservertype to JELLYFIN
if (settings.jellyfin.hostname === '') {
settings.main.mediaServerType = MediaServerType.JELLYFIN;
// If JELLYFIN_TYPE is set to 'emby' then set mediaServerType to EMBY
if (
process.env.JELLYFIN_TYPE === 'emby' ||
body.selectedservice === 'Emby'
) {
settings.main.mediaServerType = MediaServerType.EMBY;
} else if (body.selectedservice === 'Jellyfin') {
settings.main.mediaServerType = MediaServerType.JELLYFIN;
}
settings.jellyfin.hostname = body.hostname ?? '';
settings.jellyfin.serverId = account.User.ServerId;
settings.save();
@@ -350,6 +371,13 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
throw new Error('add_email');
}
if (
!body.selectedservice &&
(body.selectedservice !== 'Emby' || 'Jellyfin')
) {
throw new Error('select_server_type');
}
user = new User({
email: body.email,
jellyfinUsername: account.User.Name,
@@ -400,6 +428,11 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
status: 406,
message: 'CREDENTIAL_ERROR_ADD_EMAIL',
});
} else if (e.message === 'select_server_type') {
return next({
status: 406,
message: 'CREDENTIAL_ERROR_NO_SERVER_TYPE',
});
} else {
logger.error(e.message, { label: 'Auth' });
return next({

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg version="1.1" id="svg2" viewBox="0 0 712.60077 712.5481" xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs id="defs4" />
<metadata id="metadata7">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<rect style="opacity:0;fill:#52b54b;stroke-width:4.12543" id="rect249" width="712.60077" height="712.5481"
x="-0.00071160076" y="2.0223413e-11" />
<rect style="fill:#52b54b" id="rect289" width="230.18982" height="229.82355" x="241.20476" y="241.36227" />
<g id="layer1" transform="matrix(0.70249853,0,0,0.70249853,88.770447,96.84571)">
<path id="path3427"
d="m 327.06546,642.18589 c -45.39663,-45.86009 -82.73776,-83.3683 -82.98029,-83.3516 -0.24253,0.0167 -7.23324,6.65975 -15.53493,14.7623 l -15.09396,14.73193 -40.13624,-40.38805 C 151.24511,525.72706 108.73555,482.86504 78.854363,452.69158 l -54.329437,-54.86086 83.720394,-82.90796 83.72039,-82.90797 -15.19316,-15.20441 -15.19315,-15.20443 95.18008,-94.29313 c 52.34904,-51.86121 95.35849,-94.293118 95.57653,-94.293118 0.21805,0 37.39519,37.357576 82.61589,83.016832 45.22068,45.659256 82.53772,83.131956 82.92673,83.272666 0.38901,0.14071 7.46336,-6.49498 15.72077,-14.746 l 15.01348,-15.00184 7.14591,7.19902 c 73.95232,74.50189 181.50599,183.56427 181.36678,183.9109 -0.10065,0.25064 -37.54056,37.44106 -83.19981,82.64536 -45.65926,45.2043 -83.10802,82.41429 -83.21946,82.68884 -0.11145,0.27456 6.50478,7.34753 14.70272,15.71771 l 14.90534,15.21851 -15.3888,15.28883 c -21.09609,20.95904 -162.95155,161.27018 -169.79551,167.947 l -5.52526,5.39033 z m 89.8523,-204.1566 c 64.84836,-37.53366 117.81919,-68.54793 117.71294,-68.92058 -0.15927,-0.55862 -233.55022,-136.2489 -236.27084,-137.3646 -0.68441,-0.28068 -0.85761,27.45642 -0.85761,137.33982 0,99.83563 0.20749,137.62237 0.75471,137.43996 0.41509,-0.13837 53.81245,-30.96093 118.6608,-68.4946 z"
style="fill:#ffffff;fill-opacity:1;stroke:none" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- ***** BEGIN LICENSE BLOCK *****
- Part of the Jellyfin project (https://jellyfin.media)
-
- All copyright belongs to the Jellyfin contributors; a full list can
- be found in the file CONTRIBUTORS.md
-
- This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License.
- To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/4.0/.
- ***** END LICENSE BLOCK ***** -->
<svg version="1.1" id="icon-transparent" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 512 512">
<defs>
<linearGradient id="linear-gradient" gradientUnits="userSpaceOnUse" x1="110.25" y1="213.3" x2="496.14"
y2="436.09">
<stop offset="0" style="stop-color:#ffffff" />
<stop offset="1" style="stop-color:#ffffff" />
</linearGradient>
</defs>
<title>icon-transparent</title>
<g id="icon-transparent">
<path id="inner-shape" d="M256,201.6c-20.4,0-86.2,119.3-76.2,139.4s142.5,19.9,152.4,0S276.5,201.6,256,201.6z"
fill="url(#linear-gradient)" />
<path id="outer-shape" d="M256,23.3c-61.6,0-259.8,359.4-229.6,420.1s429.3,60,459.2,0S317.6,23.3,256,23.3z
M406.5,390.8c-19.6,39.3-281.1,39.8-300.9,0s110.1-275.3,150.4-275.3S426.1,351.4,406.5,390.8z"
fill="url(#linear-gradient)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- ***** BEGIN LICENSE BLOCK *****
- Part of the Jellyfin project (https://jellyfin.media)
-
- All copyright belongs to the Jellyfin contributors; a full list can
- be found in the file CONTRIBUTORS.md
-
- This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License.
- To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/4.0/.
- ***** END LICENSE BLOCK ***** -->
<svg version="1.1" id="icon-transparent" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512">
<defs>
<linearGradient id="linear-gradient" gradientUnits="userSpaceOnUse" x1="110.25" y1="213.3" x2="496.14" y2="436.09">
<stop offset="0" style="stop-color:#AA5CC3"/>
<stop offset="1" style="stop-color:#00A4DC"/>
</linearGradient>
</defs>
<title>icon-transparent</title>
<g id="icon-transparent">
<path id="inner-shape" d="M256,201.6c-20.4,0-86.2,119.3-76.2,139.4s142.5,19.9,152.4,0S276.5,201.6,256,201.6z" fill="url(#linear-gradient)"/>
<path id="outer-shape" d="M256,23.3c-61.6,0-259.8,359.4-229.6,420.1s429.3,60,459.2,0S317.6,23.3,256,23.3z
M406.5,390.8c-19.6,39.3-281.1,39.8-300.9,0s110.1-275.3,150.4-275.3S426.1,351.4,406.5,390.8z" fill="url(#linear-gradient)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -10,7 +10,6 @@ import useLocale from '@app/hooks/useLocale';
import useSettings from '@app/hooks/useSettings';
import { MediaType } from '@server/constants/media';
import { MediaServerType } from '@server/constants/server';
import getConfig from 'next/config';
interface ExternalLinkBlockProps {
mediaType: 'movie' | 'tv';
@@ -30,7 +29,6 @@ const ExternalLinkBlock = ({
mediaUrl,
}: ExternalLinkBlockProps) => {
const settings = useSettings();
const { publicRuntimeConfig } = getConfig();
const { locale } = useLocale();
return (
@@ -44,7 +42,8 @@ const ExternalLinkBlock = ({
>
{settings.currentSettings.mediaServerType === MediaServerType.PLEX ? (
<PlexLogo />
) : publicRuntimeConfig.JELLYFIN_TYPE == 'emby' ? (
) : settings.currentSettings.mediaServerType ===
MediaServerType.EMBY ? (
<EmbyLogo />
) : (
<JellyfinLogo />

View File

@@ -28,7 +28,6 @@ import type { MovieDetails } from '@server/models/Movie';
import type { TvDetails } from '@server/models/Tv';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import getConfig from 'next/config';
import Link from 'next/link';
import { useRouter } from 'next/router';
import { useState } from 'react';
@@ -107,7 +106,6 @@ const IssueDetails = () => {
(opt) => opt.issueType === issueData?.issueType
);
const settings = useSettings();
const { publicRuntimeConfig } = getConfig();
if (!data && !error) {
return <LoadingSpinner />;
@@ -375,7 +373,8 @@ const IssueDetails = () => {
>
<PlayIcon />
<span>
{publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
{settings.currentSettings.mediaServerType ===
MediaServerType.EMBY
? intl.formatMessage(messages.playonplex, {
mediaServerName: 'Emby',
})
@@ -422,16 +421,17 @@ const IssueDetails = () => {
>
<PlayIcon />
<span>
{publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
? intl.formatMessage(messages.play4konplex, {
{settings.currentSettings.mediaServerType ===
MediaServerType.EMBY
? intl.formatMessage(messages.playonplex, {
mediaServerName: 'Emby',
})
: settings.currentSettings.mediaServerType ===
MediaServerType.PLEX
? intl.formatMessage(messages.play4konplex, {
? intl.formatMessage(messages.playonplex, {
mediaServerName: 'Plex',
})
: intl.formatMessage(messages.play4konplex, {
: intl.formatMessage(messages.playonplex, {
mediaServerName: 'Jellyfin',
})}
</span>
@@ -639,7 +639,8 @@ const IssueDetails = () => {
>
<PlayIcon />
<span>
{publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
{settings.currentSettings.mediaServerType ===
MediaServerType.EMBY
? intl.formatMessage(messages.playonplex, {
mediaServerName: 'Emby',
})
@@ -685,16 +686,17 @@ const IssueDetails = () => {
>
<PlayIcon />
<span>
{publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
? intl.formatMessage(messages.play4konplex, {
{settings.currentSettings.mediaServerType ===
MediaServerType.EMBY
? intl.formatMessage(messages.playonplex, {
mediaServerName: 'Emby',
})
: settings.currentSettings.mediaServerType ===
MediaServerType.PLEX
? intl.formatMessage(messages.play4konplex, {
? intl.formatMessage(messages.playonplex, {
mediaServerName: 'Plex',
})
: intl.formatMessage(messages.play4konplex, {
: intl.formatMessage(messages.playonplex, {
mediaServerName: 'Jellyfin',
})}
</span>

View File

@@ -1,10 +1,14 @@
import EmbyLogoInverted from '@app/assets/services/emby-inverted.svg';
import EmbyLogo from '@app/assets/services/emby.svg';
import JellyfinLogoInverted from '@app/assets/services/jellyfin-icon-only-inverted.svg';
import JellyfinLogo from '@app/assets/services/jellyfin-icon-only.svg';
import Button from '@app/components/Common/Button';
import Tooltip from '@app/components/Common/Tooltip';
import useSettings from '@app/hooks/useSettings';
import { InformationCircleIcon } from '@heroicons/react/24/solid';
import { MediaServerType } from '@server/constants/server';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import getConfig from 'next/config';
import type React from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications';
@@ -23,6 +27,7 @@ const messages = defineMessages({
validationemailformat: 'Valid email required',
validationusernamerequired: 'Username required',
validationpasswordrequired: 'Password required',
validationselectedservicerequired: 'Please select a server type',
loginerror: 'Something went wrong while trying to sign in.',
credentialerror: 'The username or password is incorrect.',
signingin: 'Signing in…',
@@ -30,21 +35,25 @@ const messages = defineMessages({
initialsigningin: 'Connecting…',
initialsignin: 'Connect',
forgotpassword: 'Forgot Password?',
servertype: 'Server Type',
});
interface JellyfinLoginProps {
revalidate: () => void;
initial?: boolean;
onToggle?: (option: string) => void;
selectedService?: string;
}
const JellyfinLogin: React.FC<JellyfinLoginProps> = ({
revalidate,
initial,
onToggle,
selectedService,
}) => {
const toasts = useToasts();
const intl = useIntl();
const settings = useSettings();
const { publicRuntimeConfig } = getConfig();
if (initial) {
const LoginSchema = Yup.object().shape({
@@ -55,8 +64,7 @@ const JellyfinLogin: React.FC<JellyfinLoginProps> = ({
)
.required(
intl.formatMessage(messages.validationhostrequired, {
mediaServerName:
publicRuntimeConfig.JELLYFIN_TYPE == 'emby' ? 'Emby' : 'Jellyfin',
mediaServerName: selectedService,
})
),
email: Yup.string()
@@ -66,11 +74,18 @@ const JellyfinLogin: React.FC<JellyfinLoginProps> = ({
intl.formatMessage(messages.validationusernamerequired)
),
password: Yup.string(),
selectedservice: Yup.string().required(
intl.formatMessage(messages.validationselectedservicerequired)
),
});
const mediaServerFormatValues = {
mediaServerName:
publicRuntimeConfig.JELLYFIN_TYPE == 'emby' ? 'Emby' : 'Jellyfin',
selectedService === 'Jellyfin'
? 'Jellyfin'
: selectedService === 'Emby'
? 'Emby'
: 'Media Server',
};
return (
<Formik
@@ -79,15 +94,24 @@ const JellyfinLogin: React.FC<JellyfinLoginProps> = ({
password: '',
host: '',
email: '',
selectedservice: '',
}}
initialErrors={{ selectedservice: 'Please select a server type' }} // Initialize errors with an empty object
initialTouched={{ selectedservice: true }}
validationSchema={LoginSchema}
onSubmit={async (values) => {
try {
// Check if selectedService is either 'Jellyfin' or 'Emby'
// if (selectedService !== 'Jellyfin' && selectedService !== 'Emby') {
// throw new Error('Invalid selectedService'); // You can customize the error message
// }
await axios.post('/api/v1/auth/jellyfin', {
username: values.username,
password: values.password,
hostname: values.host,
email: values.email,
selectedservice: selectedService,
});
} catch (e) {
toasts.addToast(
@@ -106,9 +130,66 @@ const JellyfinLogin: React.FC<JellyfinLoginProps> = ({
}
}}
>
{({ errors, touched, isSubmitting, isValid }) => (
{({
errors,
touched,
isSubmitting,
isValid,
values,
setFieldValue,
}) => (
<Form>
<div className="sm:border-t sm:border-gray-800">
<label htmlFor="servertype" className="text-label">
{intl.formatMessage(messages.servertype)}
</label>
<div className="mt-1 mb-2 sm:col-span-2 sm:mt-0">
<div className="flex space-x-4 rounded-md shadow-sm">
<button
type="button"
className={`server-type-button jellyfin-server ${
selectedService === 'Jellyfin'
? 'bg-gradient-to-r from-[#AA5CC3] to-[#00A4DC]'
: ''
}`}
onClick={() => {
if (onToggle) {
onToggle('Jellyfin');
}
setFieldValue('selectedservice', 'Jellyfin');
}}
>
{selectedService === 'Jellyfin' ? (
<JellyfinLogoInverted />
) : (
<JellyfinLogo />
)}
</button>
<button
type="button"
className={`server-type-button emby-server ${
selectedService === 'Emby' ? 'bg-[#51B44A]' : ''
}`}
onClick={() => {
if (onToggle) {
onToggle('Emby');
}
setFieldValue('selectedservice', 'Emby');
}}
>
{selectedService === 'Emby' ? (
<EmbyLogoInverted />
) : (
<EmbyLogo />
)}
</button>
</div>
{/* Hidden field */}
<Field type="hidden" name="selectedservice" />
{!values.selectedservice && errors.selectedservice && (
<div className="error">{errors.selectedservice}</div>
)}
</div>
<label htmlFor="host" className="text-label">
{intl.formatMessage(messages.host, mediaServerFormatValues)}
</label>
@@ -299,7 +380,8 @@ const JellyfinLogin: React.FC<JellyfinLoginProps> = ({
as="a"
buttonType="ghost"
href={`${baseUrl}/web/index.html#!/${
process.env.JELLYFIN_TYPE === 'emby'
settings.currentSettings.mediaServerType ===
MediaServerType.EMBY
? 'startup/'
: ''
}forgotpassword.html`}

View File

@@ -10,7 +10,6 @@ import { Transition } from '@headlessui/react';
import { XCircleIcon } from '@heroicons/react/24/solid';
import { MediaServerType } from '@server/constants/server';
import axios from 'axios';
import getConfig from 'next/config';
import { useRouter } from 'next/dist/client/router';
import { useEffect, useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
@@ -33,7 +32,6 @@ const Login = () => {
const { user, revalidate } = useUser();
const router = useRouter();
const settings = useSettings();
const { publicRuntimeConfig } = getConfig();
// Effect that is triggered when the `authToken` comes back from the Plex OAuth
// We take the token and attempt to sign in. If we get a success message, we will
@@ -72,6 +70,15 @@ const Login = () => {
revalidateOnFocus: false,
});
const mediaServerFormatValues = {
mediaServerName:
settings.currentSettings.mediaServerType === MediaServerType.JELLYFIN
? 'Jellyfin'
: settings.currentSettings.mediaServerType === MediaServerType.EMBY
? 'Emby'
: undefined,
};
return (
<div className="relative flex min-h-screen flex-col bg-gray-900 py-14">
<PageTitle title={intl.formatMessage(messages.signin)} />
@@ -136,12 +143,10 @@ const Login = () => {
{settings.currentSettings.mediaServerType ==
MediaServerType.PLEX
? intl.formatMessage(messages.signinwithplex)
: intl.formatMessage(messages.signinwithjellyfin, {
mediaServerName:
publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
? 'Emby'
: 'Jellyfin',
})}
: intl.formatMessage(
messages.signinwithjellyfin,
mediaServerFormatValues
)}
</button>
<AccordionContent isOpen={openIndexes.includes(0)}>
<div className="px-10 py-8">

View File

@@ -26,7 +26,6 @@ import type { RadarrSettings, SonarrSettings } from '@server/lib/settings';
import type { MovieDetails } from '@server/models/Movie';
import type { TvDetails } from '@server/models/Tv';
import axios from 'axios';
import getConfig from 'next/config';
import Link from 'next/link';
import { defineMessages, useIntl } from 'react-intl';
import useSWR from 'swr';
@@ -94,7 +93,6 @@ const ManageSlideOver = ({
const { user: currentUser, hasPermission } = useUser();
const intl = useIntl();
const settings = useSettings();
const { publicRuntimeConfig } = getConfig();
const { data: watchData } = useSWR<MediaWatchDataResponse>(
settings.currentSettings.mediaServerType === MediaServerType.PLEX &&
data.mediaInfo &&
@@ -638,7 +636,8 @@ const ManageSlideOver = ({
mediaType === 'movie' ? messages.movie : messages.tvshow
),
mediaServerName:
publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
settings.currentSettings.mediaServerType ===
MediaServerType.EMBY
? 'Emby'
: settings.currentSettings.mediaServerType ===
MediaServerType.PLEX

View File

@@ -49,7 +49,6 @@ import type { MovieDetails as MovieDetailsType } from '@server/models/Movie';
import { hasFlag } from 'country-flag-icons';
import 'country-flag-icons/3x2/flags.css';
import { uniqBy } from 'lodash';
import getConfig from 'next/config';
import Link from 'next/link';
import { useRouter } from 'next/router';
import { useEffect, useMemo, useState } from 'react';
@@ -111,7 +110,6 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
const minStudios = 3;
const [showMoreStudios, setShowMoreStudios] = useState(false);
const [showIssueModal, setShowIssueModal] = useState(false);
const { publicRuntimeConfig } = getConfig();
const {
data,
@@ -259,7 +257,7 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
?.flatrate ?? [];
function getAvalaibleMediaServerName() {
if (publicRuntimeConfig.JELLYFIN_TYPE === 'emby') {
if (settings.currentSettings.mediaServerType === MediaServerType.EMBY) {
return intl.formatMessage(messages.play, { mediaServerName: 'Emby' });
}
@@ -271,8 +269,8 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
}
function getAvalaible4kMediaServerName() {
if (publicRuntimeConfig.JELLYFIN_TYPE === 'emby') {
return intl.formatMessage(messages.play4k, { mediaServerName: 'Emby' });
if (settings.currentSettings.mediaServerType === MediaServerType.EMBY) {
return intl.formatMessage(messages.play, { mediaServerName: 'Emby' });
}
if (settings.currentSettings.mediaServerType === MediaServerType.PLEX) {

View File

@@ -138,19 +138,13 @@ export const PermissionEdit = ({
mediaServerName:
settings.currentSettings.mediaServerType === MediaServerType.PLEX
? 'Plex'
: settings.currentSettings.mediaServerType ===
MediaServerType.JELLYFIN
? 'Jellyfin'
: 'Emby',
: 'Local',
}),
description: intl.formatMessage(messages.viewwatchlistsDescription, {
mediaServerName:
settings.currentSettings.mediaServerType === MediaServerType.PLEX
? 'Plex'
: settings.currentSettings.mediaServerType ===
MediaServerType.JELLYFIN
? 'Jellyfin'
: 'Emby',
: 'Local',
}),
permission: Permission.WATCHLIST_VIEW,
},
@@ -213,43 +207,47 @@ export const PermissionEdit = ({
},
],
},
{
id: 'autorequest',
name: intl.formatMessage(messages.autorequest),
description: intl.formatMessage(messages.autorequestDescription),
permission: Permission.AUTO_REQUEST,
requires: [{ permissions: [Permission.REQUEST] }],
children: [
{
id: 'autorequestmovies',
name: intl.formatMessage(messages.autorequestMovies),
description: intl.formatMessage(
messages.autorequestMoviesDescription
),
permission: Permission.AUTO_REQUEST_MOVIE,
requires: [
{
permissions: [Permission.REQUEST, Permission.REQUEST_MOVIE],
type: 'or',
},
],
},
{
id: 'autorequesttv',
name: intl.formatMessage(messages.autorequestSeries),
description: intl.formatMessage(
messages.autorequestSeriesDescription
),
permission: Permission.AUTO_REQUEST_TV,
requires: [
{
permissions: [Permission.REQUEST, Permission.REQUEST_TV],
type: 'or',
},
],
},
],
},
...(settings.currentSettings.mediaServerType === MediaServerType.PLEX
? [
{
id: 'autorequest',
name: intl.formatMessage(messages.autorequest),
description: intl.formatMessage(messages.autorequestDescription),
permission: Permission.AUTO_REQUEST,
requires: [{ permissions: [Permission.REQUEST] }],
children: [
{
id: 'autorequestmovies',
name: intl.formatMessage(messages.autorequestMovies),
description: intl.formatMessage(
messages.autorequestMoviesDescription
),
permission: Permission.AUTO_REQUEST_MOVIE,
requires: [
{
permissions: [Permission.REQUEST, Permission.REQUEST_MOVIE],
type: 'or',
},
],
},
{
id: 'autorequesttv',
name: intl.formatMessage(messages.autorequestSeries),
description: intl.formatMessage(
messages.autorequestSeriesDescription
),
permission: Permission.AUTO_REQUEST_TV,
requires: [
{
permissions: [Permission.REQUEST, Permission.REQUEST_TV],
type: 'or',
},
],
},
],
} as PermissionItem,
]
: []),
{
id: 'request4k',
name: intl.formatMessage(messages.request4k),

View File

@@ -2,12 +2,13 @@ import Badge from '@app/components/Common/Badge';
import Button from '@app/components/Common/Button';
import LoadingSpinner from '@app/components/Common/LoadingSpinner';
import LibraryItem from '@app/components/Settings/LibraryItem';
import useSettings from '@app/hooks/useSettings';
import globalMessages from '@app/i18n/globalMessages';
import { ArrowDownOnSquareIcon } from '@heroicons/react/24/outline';
import { MediaServerType } from '@server/constants/server';
import type { JellyfinSettings } from '@server/lib/settings';
import axios from 'axios';
import { Field, Formik } from 'formik';
import getConfig from 'next/config';
import type React from 'react';
import { useState } from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
@@ -83,7 +84,7 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
);
const intl = useIntl();
const { addToast } = useToasts();
const { publicRuntimeConfig } = getConfig();
const settings = useSettings();
const JellyfinSettingsSchema = Yup.object().shape({
jellyfinExternalUrl: Yup.string().matches(
@@ -165,26 +166,29 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
return <LoadingSpinner />;
}
const mediaServerFormatValues = {
mediaServerName:
settings.currentSettings.mediaServerType === MediaServerType.JELLYFIN
? 'Jellyfin'
: settings.currentSettings.mediaServerType === MediaServerType.EMBY
? 'Emby'
: undefined,
};
return (
<>
<div className="mb-6">
<h3 className="heading">
{publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
? intl.formatMessage(messages.jellyfinlibraries, {
mediaServerName: 'Emby',
})
: intl.formatMessage(messages.jellyfinlibraries, {
mediaServerName: 'Jellyfin',
})}
{intl.formatMessage(
messages.jellyfinlibraries,
mediaServerFormatValues
)}
</h3>
<p className="description">
{publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
? intl.formatMessage(messages.jellyfinlibrariesDescription, {
mediaServerName: 'Emby',
})
: intl.formatMessage(messages.jellyfinlibrariesDescription, {
mediaServerName: 'Jellyfin',
})}
{intl.formatMessage(
messages.jellyfinlibrariesDescription,
mediaServerFormatValues
)}
</p>
</div>
<div className="section">
@@ -221,13 +225,10 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
<FormattedMessage {...messages.manualscanJellyfin} />
</h3>
<p className="description">
{publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
? intl.formatMessage(messages.manualscanDescriptionJellyfin, {
mediaServerName: 'Emby',
})
: intl.formatMessage(messages.manualscanDescriptionJellyfin, {
mediaServerName: 'Jellyfin',
})}
{intl.formatMessage(
messages.manualscanDescriptionJellyfin,
mediaServerFormatValues
)}
</p>
</div>
<div className="section">
@@ -331,22 +332,16 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
<>
<div className="mt-10 mb-6">
<h3 className="heading">
{publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
? intl.formatMessage(messages.jellyfinSettings, {
mediaServerName: 'Emby',
})
: intl.formatMessage(messages.jellyfinSettings, {
mediaServerName: 'Jellyfin',
})}
{intl.formatMessage(
messages.jellyfinSettings,
mediaServerFormatValues
)}
</h3>
<p className="description">
{publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
? intl.formatMessage(messages.jellyfinSettingsDescription, {
mediaServerName: 'Emby',
})
: intl.formatMessage(messages.jellyfinSettingsDescription, {
mediaServerName: 'Jellyfin',
})}
{intl.formatMessage(
messages.jellyfinSettingsDescription,
mediaServerFormatValues
)}
</p>
</div>
<Formik
@@ -365,7 +360,8 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
addToast(
intl.formatMessage(messages.jellyfinSettingsSuccess, {
mediaServerName:
publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
settings.currentSettings.mediaServerType ===
MediaServerType.EMBY
? 'Emby'
: 'Jellyfin',
}),
@@ -376,12 +372,10 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
);
} catch (e) {
addToast(
intl.formatMessage(messages.jellyfinSettingsFailure, {
mediaServerName:
publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
? 'Emby'
: 'Jellyfin',
}),
intl.formatMessage(
messages.jellyfinSettingsFailure,
mediaServerFormatValues
),
{
autoDismiss: true,
appearance: 'error',

View File

@@ -7,6 +7,7 @@ import PageTitle from '@app/components/Common/PageTitle';
import Table from '@app/components/Common/Table';
import useLocale from '@app/hooks/useLocale';
import useSettings from '@app/hooks/useSettings';
import globalMessages from '@app/i18n/globalMessages';
import { formatBytes } from '@app/utils/numberHelpers';
import { Transition } from '@headlessui/react';
@@ -55,8 +56,8 @@ const messages: { [messageName: string]: MessageDescriptor } = defineMessages({
'plex-recently-added-scan': 'Plex Recently Added Scan',
'plex-full-scan': 'Plex Full Library Scan',
'plex-watchlist-sync': 'Plex Watchlist Sync',
'jellyfin-recently-added-scan': 'Jellyfin Recently Added Scan',
'jellyfin-full-scan': 'Jellyfin Full Library Scan',
'jellyfin-recently-added-scan': 'Jellyfin Recently Added Scan',
'availability-sync': 'Media Availability Sync',
'radarr-scan': 'Radarr Scan',
'sonarr-scan': 'Sonarr Scan',
@@ -164,6 +165,20 @@ const SettingsJobs = () => {
const [isSaving, setIsSaving] = useState(false);
const settings = useSettings();
if (settings.currentSettings.mediaServerType === MediaServerType.EMBY) {
messages['jellyfin-recently-added-scan'] = {
id: 'jellyfin-recently-added-scan',
defaultMessage: 'Emby Recently Added Scan',
};
}
if (settings.currentSettings.mediaServerType === MediaServerType.EMBY) {
messages['jellyfin-full-scan'] = {
id: 'jellyfin-full-scan',
defaultMessage: 'Emby Full Library Scan',
};
}
if (!data && !error) {
return <LoadingSpinner />;
}

View File

@@ -4,7 +4,6 @@ import SettingsTabs from '@app/components/Common/SettingsTabs';
import useSettings from '@app/hooks/useSettings';
import globalMessages from '@app/i18n/globalMessages';
import { MediaServerType } from '@server/constants/server';
import getConfig from 'next/config';
import type React from 'react';
import { defineMessages, useIntl } from 'react-intl';
@@ -26,7 +25,6 @@ type SettingsLayoutProps = {
const SettingsLayout = ({ children }: SettingsLayoutProps) => {
const intl = useIntl();
const { publicRuntimeConfig } = getConfig();
const settings = useSettings();
const settingsRoutes: SettingsRoute[] = [
{
@@ -89,7 +87,11 @@ const SettingsLayout = ({ children }: SettingsLayoutProps) => {
function getAvailableMediaServerName() {
return intl.formatMessage(messages.menuJellyfinSettings, {
mediaServerName:
publicRuntimeConfig.JELLYFIN_TYPE === 'emby' ? 'Emby' : 'Jellyfin',
settings.currentSettings.mediaServerType === MediaServerType.JELLYFIN
? 'Jellyfin'
: settings.currentSettings.mediaServerType === MediaServerType.EMBY
? 'Emby'
: undefined,
});
}
};

View File

@@ -10,7 +10,6 @@ import { MediaServerType } from '@server/constants/server';
import type { MainSettings } from '@server/lib/settings';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import getConfig from 'next/config';
import { defineMessages, useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications';
import useSWR, { mutate } from 'swr';
@@ -42,12 +41,20 @@ const SettingsUsers = () => {
mutate: revalidate,
} = useSWR<MainSettings>('/api/v1/settings/main');
const settings = useSettings();
const { publicRuntimeConfig } = getConfig();
if (!data && !error) {
return <LoadingSpinner />;
}
const mediaServerFormatValues = {
mediaServerName:
settings.currentSettings.mediaServerType === MediaServerType.JELLYFIN
? 'Jellyfin'
: settings.currentSettings.mediaServerType === MediaServerType.EMBY
? 'Emby'
: undefined,
};
return (
<>
<PageTitle
@@ -114,16 +121,10 @@ const SettingsUsers = () => {
<label htmlFor="localLogin" className="checkbox-label">
{intl.formatMessage(messages.localLogin)}
<span className="label-tip">
{intl.formatMessage(messages.localLoginTip, {
mediaServerName:
settings.currentSettings.mediaServerType ===
MediaServerType.PLEX
? 'Plex'
: settings.currentSettings.mediaServerType ===
MediaServerType.JELLYFIN
? 'Jellyfin'
: 'Emby',
})}
{intl.formatMessage(
messages.localLoginTip,
mediaServerFormatValues
)}
</span>
</label>
<div className="form-input-area">
@@ -139,25 +140,15 @@ const SettingsUsers = () => {
</div>
<div className="form-row">
<label htmlFor="newPlexLogin" className="checkbox-label">
{intl.formatMessage(messages.newPlexLogin, {
mediaServerName:
publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
? 'Emby'
: settings.currentSettings.mediaServerType ===
MediaServerType.PLEX
? 'Plex'
: 'Jellyfin',
})}
{intl.formatMessage(
messages.newPlexLogin,
mediaServerFormatValues
)}
<span className="label-tip">
{intl.formatMessage(messages.newPlexLoginTip, {
mediaServerName:
publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
? 'Emby'
: settings.currentSettings.mediaServerType ===
MediaServerType.PLEX
? 'Plex'
: 'Jellyfin',
})}
{intl.formatMessage(
messages.newPlexLoginTip,
mediaServerFormatValues
)}
</span>
</label>
<div className="form-input-area">

View File

@@ -4,7 +4,6 @@ import PlexLoginButton from '@app/components/PlexLoginButton';
import { useUser } from '@app/hooks/useUser';
import { MediaServerType } from '@server/constants/server';
import axios from 'axios';
import getConfig from 'next/config';
import type React from 'react';
import { useEffect, useState } from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
@@ -27,7 +26,16 @@ const SetupLogin: React.FC<LoginWithMediaServerProps> = ({ onComplete }) => {
);
const { user, revalidate } = useUser();
const intl = useIntl();
const { publicRuntimeConfig } = getConfig();
const [selectedService, setSelectedService] = useState<string | undefined>(
undefined
);
// Function to handle toggle changes
const handleToggle = (option: string) => {
// Toggle between 'emby' and 'jellyfin'
setSelectedService(option);
};
// Effect that is triggered when the `authToken` comes back from the Plex OAuth
// We take the token and attempt to login. If we get a success message, we will
// ask swr to revalidate the user which _shouid_ come back with a valid user.
@@ -94,20 +102,21 @@ const SetupLogin: React.FC<LoginWithMediaServerProps> = ({ onComplete }) => {
}`}
onClick={() => handleClick(1)}
>
{publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
? intl.formatMessage(messages.signinWithJellyfin, {
mediaServerName: 'Emby',
})
: intl.formatMessage(messages.signinWithJellyfin, {
mediaServerName: 'Jellyfin',
})}
{intl.formatMessage(messages.signinWithJellyfin, {
mediaServerName: selectedService ?? 'Jellyfin / Emby',
})}
</button>
<AccordionContent isOpen={openIndexes.includes(1)}>
<div
className="rounded-b-lg px-10 py-8"
style={{ backgroundColor: 'rgba(0,0,0,0.3)' }}
>
<JellyfinLogin initial={true} revalidate={revalidate} />
<JellyfinLogin
initial={true}
revalidate={revalidate}
selectedService={selectedService}
onToggle={handleToggle}
/>
</div>
</AccordionContent>
</div>

View File

@@ -8,7 +8,6 @@ import globalMessages from '@app/i18n/globalMessages';
import { MediaStatus } from '@server/constants/media';
import { MediaServerType } from '@server/constants/server';
import type { DownloadingItem } from '@server/lib/downloadtracker';
import getConfig from 'next/config';
import { defineMessages, useIntl } from 'react-intl';
const messages = defineMessages({
@@ -46,7 +45,6 @@ const StatusBadge = ({
const intl = useIntl();
const { hasPermission } = useUser();
const settings = useSettings();
const { publicRuntimeConfig } = getConfig();
let mediaLink: string | undefined;
let mediaLinkDescription: string | undefined;
@@ -84,7 +82,7 @@ const StatusBadge = ({
mediaLink = plexUrl;
mediaLinkDescription = intl.formatMessage(messages.playonplex, {
mediaServerName:
publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
settings.currentSettings.mediaServerType === MediaServerType.EMBY
? 'Emby'
: settings.currentSettings.mediaServerType === MediaServerType.PLEX
? 'Plex'

View File

@@ -49,7 +49,6 @@ import type { Crew } from '@server/models/common';
import type { TvDetails as TvDetailsType } from '@server/models/Tv';
import { hasFlag } from 'country-flag-icons';
import 'country-flag-icons/3x2/flags.css';
import getConfig from 'next/config';
import Link from 'next/link';
import { useRouter } from 'next/router';
import { useEffect, useMemo, useState } from 'react';
@@ -105,7 +104,6 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
router.query.manage == '1' ? true : false
);
const [showIssueModal, setShowIssueModal] = useState(false);
const { publicRuntimeConfig } = getConfig();
const {
data,
@@ -274,7 +272,7 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
?.flatrate ?? [];
function getAvalaibleMediaServerName() {
if (publicRuntimeConfig.JELLYFIN_TYPE === 'emby') {
if (settings.currentSettings.mediaServerType === MediaServerType.EMBY) {
return intl.formatMessage(messages.play, { mediaServerName: 'Emby' });
}
@@ -286,15 +284,15 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
}
function getAvalaible4kMediaServerName() {
if (publicRuntimeConfig.JELLYFIN_TYPE === 'emby') {
return intl.formatMessage(messages.play4k, { mediaServerName: 'Emby' });
if (settings.currentSettings.mediaServerType === MediaServerType.EMBY) {
return intl.formatMessage(messages.play, { mediaServerName: 'Emby' });
}
if (settings.currentSettings.mediaServerType === MediaServerType.PLEX) {
return intl.formatMessage(messages.play4k, { mediaServerName: 'Plex' });
}
return intl.formatMessage(messages.play4k, { mediaServerName: 'Jellyfin' });
return intl.formatMessage(messages.play, { mediaServerName: 'Jellyfin' });
}
return (

View File

@@ -2,9 +2,9 @@ import Alert from '@app/components/Common/Alert';
import Modal from '@app/components/Common/Modal';
import useSettings from '@app/hooks/useSettings';
import globalMessages from '@app/i18n/globalMessages';
import { MediaServerType } from '@server/constants/server';
import type { UserResultsResponse } from '@server/interfaces/api/userInterfaces';
import axios from 'axios';
import getConfig from 'next/config';
import type React from 'react';
import { useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
@@ -36,7 +36,6 @@ const JellyfinImportModal: React.FC<JellyfinImportProps> = ({
}) => {
const intl = useIntl();
const settings = useSettings();
const { publicRuntimeConfig } = getConfig();
const { addToast } = useToasts();
const [isImporting, setImporting] = useState(false);
const [selectedUsers, setSelectedUsers] = useState<string[]>([]);
@@ -82,7 +81,9 @@ const JellyfinImportModal: React.FC<JellyfinImportProps> = ({
userCount: createdUsers.length,
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
mediaServerName:
publicRuntimeConfig.JELLYFIN_TYPE == 'emby' ? 'Emby' : 'Jellyfin',
settings.currentSettings.mediaServerType === MediaServerType.EMBY
? 'Emby'
: 'Jellyfin',
}),
{
autoDismiss: true,
@@ -97,7 +98,9 @@ const JellyfinImportModal: React.FC<JellyfinImportProps> = ({
addToast(
intl.formatMessage(messages.importfromJellyfinerror, {
mediaServerName:
publicRuntimeConfig.JELLYFIN_TYPE == 'emby' ? 'Emby' : 'Jellyfin',
settings.currentSettings.mediaServerType === MediaServerType.EMBY
? 'Emby'
: 'Jellyfin',
}),
{
autoDismiss: true,
@@ -135,7 +138,9 @@ const JellyfinImportModal: React.FC<JellyfinImportProps> = ({
loading={!data && !error}
title={intl.formatMessage(messages.importfromJellyfin, {
mediaServerName:
publicRuntimeConfig.JELLYFIN_TYPE == 'emby' ? 'Emby' : 'Jellyfin',
settings.currentSettings.mediaServerType === MediaServerType.EMBY
? 'Emby'
: 'Jellyfin',
})}
onOk={() => {
importUsers();
@@ -152,7 +157,8 @@ const JellyfinImportModal: React.FC<JellyfinImportProps> = ({
<Alert
title={intl.formatMessage(messages.newJellyfinsigninenabled, {
mediaServerName:
publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
settings.currentSettings.mediaServerType ===
MediaServerType.EMBY
? 'Emby'
: 'Jellyfin',
strong: (msg: React.ReactNode) => (
@@ -269,7 +275,9 @@ const JellyfinImportModal: React.FC<JellyfinImportProps> = ({
<Alert
title={intl.formatMessage(messages.noJellyfinuserstoimport, {
mediaServerName:
publicRuntimeConfig.JELLYFIN_TYPE == 'emby' ? 'Emby' : 'Jellyfin',
settings.currentSettings.mediaServerType === MediaServerType.EMBY
? 'Emby'
: 'Jellyfin',
})}
type="info"
/>

View File

@@ -28,7 +28,6 @@ import type { UserResultsResponse } from '@server/interfaces/api/userInterfaces'
import { hasPermission } from '@server/lib/permissions';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import getConfig from 'next/config';
import Link from 'next/link';
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
@@ -88,7 +87,6 @@ const UserList = () => {
const intl = useIntl();
const router = useRouter();
const settings = useSettings();
const { publicRuntimeConfig } = getConfig();
const { addToast } = useToasts();
const { user: currentUser, hasPermission: currentHasPermission } = useUser();
const [currentSort, setCurrentSort] = useState<Sort>('displayname');
@@ -514,7 +512,8 @@ const UserList = () => {
>
<InboxArrowDownIcon />
<span>
{publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
{settings.currentSettings.mediaServerType ===
MediaServerType.EMBY
? intl.formatMessage(messages.importfrommediaserver, {
mediaServerName: 'Emby',
})
@@ -659,7 +658,7 @@ const UserList = () => {
<Badge badgeType="default">
{intl.formatMessage(messages.localuser)}
</Badge>
) : publicRuntimeConfig.JELLYFIN_TYPE == 'emby' ? (
) : user.userType === UserType.EMBY ? (
<Badge badgeType="success">
{intl.formatMessage(messages.mediaServerUser, {
mediaServerName: 'Emby',

View File

@@ -16,7 +16,6 @@ import { ArrowDownOnSquareIcon } from '@heroicons/react/24/outline';
import type { UserSettingsGeneralResponse } from '@server/interfaces/api/userSettingsInterfaces';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import getConfig from 'next/config';
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
@@ -64,7 +63,6 @@ const messages = defineMessages({
const UserGeneralSettings = () => {
const intl = useIntl();
const { publicRuntimeConfig } = getConfig();
const { addToast } = useToasts();
const { locale, setLocale } = useLocale();
const [movieQuotaEnabled, setMovieQuotaEnabled] = useState(false);
@@ -206,7 +204,7 @@ const UserGeneralSettings = () => {
<Badge badgeType="default">
{intl.formatMessage(messages.localuser)}
</Badge>
) : publicRuntimeConfig.JELLYFIN_TYPE == 'emby' ? (
) : user?.userType === UserType.EMBY ? (
<Badge badgeType="success">
{intl.formatMessage(messages.mediaServerUser, {
mediaServerName: 'Emby',

View File

@@ -60,6 +60,16 @@
background: #f19a30;
}
.server-type-button {
@apply rounded-md border border-gray-500 bg-gray-700 px-4 py-2 text-white transition duration-150 ease-in-out hover:bg-gray-500;
}
.jellyfin-server svg {
@apply h-6 w-6;
}
.emby-server svg {
@apply h-7 w-7;
}
ul.cards-vertical,
ul.cards-horizontal {
@apply grid gap-4;