diff --git a/server/constants/user.ts b/server/constants/user.ts index 5a0a4bd56..90b33dc8d 100644 --- a/server/constants/user.ts +++ b/server/constants/user.ts @@ -2,4 +2,5 @@ export enum UserType { PLEX = 1, LOCAL = 2, JELLYFIN = 3, + EMBY = 4, } diff --git a/server/entity/Media.ts b/server/entity/Media.ts index 68a5622c0..183712ee3 100644 --- a/server/entity/Media.ts +++ b/server/entity/Media.ts @@ -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 diff --git a/server/routes/auth.ts b/server/routes/auth.ts index 7adcc73ab..b709bd78b 100644 --- a/server/routes/auth.ts +++ b/server/routes/auth.ts @@ -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({ diff --git a/src/assets/services/emby-inverted.svg b/src/assets/services/emby-inverted.svg new file mode 100644 index 000000000..8f33a5862 --- /dev/null +++ b/src/assets/services/emby-inverted.svg @@ -0,0 +1,22 @@ + + + + + + + image/svg+xml + + + + + + + + + + \ No newline at end of file diff --git a/src/assets/services/jellyfin-icon-only-inverted.svg b/src/assets/services/jellyfin-icon-only-inverted.svg new file mode 100644 index 000000000..1826872f1 --- /dev/null +++ b/src/assets/services/jellyfin-icon-only-inverted.svg @@ -0,0 +1,28 @@ + + + + + + + + + + icon-transparent + + + + + \ No newline at end of file diff --git a/src/assets/services/jellyfin-icon-only.svg b/src/assets/services/jellyfin-icon-only.svg new file mode 100644 index 000000000..d4d7f0172 --- /dev/null +++ b/src/assets/services/jellyfin-icon-only.svg @@ -0,0 +1,24 @@ + + + + + + + + + + icon-transparent + + + + + diff --git a/src/components/ExternalLinkBlock/index.tsx b/src/components/ExternalLinkBlock/index.tsx index 1de0b5a36..b27ea6956 100644 --- a/src/components/ExternalLinkBlock/index.tsx +++ b/src/components/ExternalLinkBlock/index.tsx @@ -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 ? ( - ) : publicRuntimeConfig.JELLYFIN_TYPE == 'emby' ? ( + ) : settings.currentSettings.mediaServerType === + MediaServerType.EMBY ? ( ) : ( diff --git a/src/components/IssueDetails/index.tsx b/src/components/IssueDetails/index.tsx index c978ff237..d8162e9c5 100644 --- a/src/components/IssueDetails/index.tsx +++ b/src/components/IssueDetails/index.tsx @@ -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 ; @@ -375,7 +373,8 @@ const IssueDetails = () => { > - {publicRuntimeConfig.JELLYFIN_TYPE == 'emby' + {settings.currentSettings.mediaServerType === + MediaServerType.EMBY ? intl.formatMessage(messages.playonplex, { mediaServerName: 'Emby', }) @@ -422,16 +421,17 @@ const IssueDetails = () => { > - {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', })} @@ -639,7 +639,8 @@ const IssueDetails = () => { > - {publicRuntimeConfig.JELLYFIN_TYPE == 'emby' + {settings.currentSettings.mediaServerType === + MediaServerType.EMBY ? intl.formatMessage(messages.playonplex, { mediaServerName: 'Emby', }) @@ -685,16 +686,17 @@ const IssueDetails = () => { > - {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', })} diff --git a/src/components/Login/JellyfinLogin.tsx b/src/components/Login/JellyfinLogin.tsx index 126fa4f76..b743edb5d 100644 --- a/src/components/Login/JellyfinLogin.tsx +++ b/src/components/Login/JellyfinLogin.tsx @@ -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 = ({ 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 = ({ ) .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 = ({ 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 ( = ({ 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 = ({ } }} > - {({ errors, touched, isSubmitting, isValid }) => ( + {({ + errors, + touched, + isSubmitting, + isValid, + values, + setFieldValue, + }) => (
+ +
+
+ + +
+ {/* Hidden field */} + + {!values.selectedservice && errors.selectedservice && ( +
{errors.selectedservice}
+ )} +
@@ -299,7 +380,8 @@ const JellyfinLogin: React.FC = ({ as="a" buttonType="ghost" href={`${baseUrl}/web/index.html#!/${ - process.env.JELLYFIN_TYPE === 'emby' + settings.currentSettings.mediaServerType === + MediaServerType.EMBY ? 'startup/' : '' }forgotpassword.html`} diff --git a/src/components/Login/index.tsx b/src/components/Login/index.tsx index da4344efe..70c1458d1 100644 --- a/src/components/Login/index.tsx +++ b/src/components/Login/index.tsx @@ -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 (
@@ -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 + )}
diff --git a/src/components/ManageSlideOver/index.tsx b/src/components/ManageSlideOver/index.tsx index e64871950..1a8a99186 100644 --- a/src/components/ManageSlideOver/index.tsx +++ b/src/components/ManageSlideOver/index.tsx @@ -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( 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 diff --git a/src/components/MovieDetails/index.tsx b/src/components/MovieDetails/index.tsx index b7dc59172..108791070 100644 --- a/src/components/MovieDetails/index.tsx +++ b/src/components/MovieDetails/index.tsx @@ -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) { diff --git a/src/components/PermissionEdit/index.tsx b/src/components/PermissionEdit/index.tsx index 82971bf03..b328cc780 100644 --- a/src/components/PermissionEdit/index.tsx +++ b/src/components/PermissionEdit/index.tsx @@ -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), diff --git a/src/components/Settings/SettingsJellyfin.tsx b/src/components/Settings/SettingsJellyfin.tsx index 5d56431fb..fb439a562 100644 --- a/src/components/Settings/SettingsJellyfin.tsx +++ b/src/components/Settings/SettingsJellyfin.tsx @@ -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 = ({ ); 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 = ({ return ; } + const mediaServerFormatValues = { + mediaServerName: + settings.currentSettings.mediaServerType === MediaServerType.JELLYFIN + ? 'Jellyfin' + : settings.currentSettings.mediaServerType === MediaServerType.EMBY + ? 'Emby' + : undefined, + }; + return ( <>

- {publicRuntimeConfig.JELLYFIN_TYPE == 'emby' - ? intl.formatMessage(messages.jellyfinlibraries, { - mediaServerName: 'Emby', - }) - : intl.formatMessage(messages.jellyfinlibraries, { - mediaServerName: 'Jellyfin', - })} + {intl.formatMessage( + messages.jellyfinlibraries, + mediaServerFormatValues + )}

- {publicRuntimeConfig.JELLYFIN_TYPE == 'emby' - ? intl.formatMessage(messages.jellyfinlibrariesDescription, { - mediaServerName: 'Emby', - }) - : intl.formatMessage(messages.jellyfinlibrariesDescription, { - mediaServerName: 'Jellyfin', - })} + {intl.formatMessage( + messages.jellyfinlibrariesDescription, + mediaServerFormatValues + )}

@@ -221,13 +225,10 @@ const SettingsJellyfin: React.FC = ({

- {publicRuntimeConfig.JELLYFIN_TYPE == 'emby' - ? intl.formatMessage(messages.manualscanDescriptionJellyfin, { - mediaServerName: 'Emby', - }) - : intl.formatMessage(messages.manualscanDescriptionJellyfin, { - mediaServerName: 'Jellyfin', - })} + {intl.formatMessage( + messages.manualscanDescriptionJellyfin, + mediaServerFormatValues + )}

@@ -331,22 +332,16 @@ const SettingsJellyfin: React.FC = ({ <>

- {publicRuntimeConfig.JELLYFIN_TYPE == 'emby' - ? intl.formatMessage(messages.jellyfinSettings, { - mediaServerName: 'Emby', - }) - : intl.formatMessage(messages.jellyfinSettings, { - mediaServerName: 'Jellyfin', - })} + {intl.formatMessage( + messages.jellyfinSettings, + mediaServerFormatValues + )}

- {publicRuntimeConfig.JELLYFIN_TYPE == 'emby' - ? intl.formatMessage(messages.jellyfinSettingsDescription, { - mediaServerName: 'Emby', - }) - : intl.formatMessage(messages.jellyfinSettingsDescription, { - mediaServerName: 'Jellyfin', - })} + {intl.formatMessage( + messages.jellyfinSettingsDescription, + mediaServerFormatValues + )}

= ({ 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 = ({ ); } catch (e) { addToast( - intl.formatMessage(messages.jellyfinSettingsFailure, { - mediaServerName: - publicRuntimeConfig.JELLYFIN_TYPE == 'emby' - ? 'Emby' - : 'Jellyfin', - }), + intl.formatMessage( + messages.jellyfinSettingsFailure, + mediaServerFormatValues + ), { autoDismiss: true, appearance: 'error', diff --git a/src/components/Settings/SettingsJobsCache/index.tsx b/src/components/Settings/SettingsJobsCache/index.tsx index 1686fce22..b367c64b4 100644 --- a/src/components/Settings/SettingsJobsCache/index.tsx +++ b/src/components/Settings/SettingsJobsCache/index.tsx @@ -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 ; } diff --git a/src/components/Settings/SettingsLayout.tsx b/src/components/Settings/SettingsLayout.tsx index cddc35ef6..06604e267 100644 --- a/src/components/Settings/SettingsLayout.tsx +++ b/src/components/Settings/SettingsLayout.tsx @@ -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, }); } }; diff --git a/src/components/Settings/SettingsUsers/index.tsx b/src/components/Settings/SettingsUsers/index.tsx index ff6126c5e..9d360cb33 100644 --- a/src/components/Settings/SettingsUsers/index.tsx +++ b/src/components/Settings/SettingsUsers/index.tsx @@ -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('/api/v1/settings/main'); const settings = useSettings(); - const { publicRuntimeConfig } = getConfig(); if (!data && !error) { return ; } + const mediaServerFormatValues = { + mediaServerName: + settings.currentSettings.mediaServerType === MediaServerType.JELLYFIN + ? 'Jellyfin' + : settings.currentSettings.mediaServerType === MediaServerType.EMBY + ? 'Emby' + : undefined, + }; + return ( <> {
@@ -139,25 +140,15 @@ const SettingsUsers = () => {
diff --git a/src/components/Setup/SetupLogin.tsx b/src/components/Setup/SetupLogin.tsx index 46bf72463..a87bcb337 100644 --- a/src/components/Setup/SetupLogin.tsx +++ b/src/components/Setup/SetupLogin.tsx @@ -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 = ({ onComplete }) => { ); const { user, revalidate } = useUser(); const intl = useIntl(); - const { publicRuntimeConfig } = getConfig(); + const [selectedService, setSelectedService] = useState( + 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 = ({ 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', + })}
- +
diff --git a/src/components/StatusBadge/index.tsx b/src/components/StatusBadge/index.tsx index 58e722bdd..869727bd3 100644 --- a/src/components/StatusBadge/index.tsx +++ b/src/components/StatusBadge/index.tsx @@ -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' diff --git a/src/components/TvDetails/index.tsx b/src/components/TvDetails/index.tsx index daceb9c84..cae7447ea 100644 --- a/src/components/TvDetails/index.tsx +++ b/src/components/TvDetails/index.tsx @@ -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 ( diff --git a/src/components/UserList/JellyfinImportModal.tsx b/src/components/UserList/JellyfinImportModal.tsx index 5d293e602..ddac823c8 100644 --- a/src/components/UserList/JellyfinImportModal.tsx +++ b/src/components/UserList/JellyfinImportModal.tsx @@ -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 = ({ }) => { const intl = useIntl(); const settings = useSettings(); - const { publicRuntimeConfig } = getConfig(); const { addToast } = useToasts(); const [isImporting, setImporting] = useState(false); const [selectedUsers, setSelectedUsers] = useState([]); @@ -82,7 +81,9 @@ const JellyfinImportModal: React.FC = ({ userCount: createdUsers.length, strong: (msg: React.ReactNode) => {msg}, mediaServerName: - publicRuntimeConfig.JELLYFIN_TYPE == 'emby' ? 'Emby' : 'Jellyfin', + settings.currentSettings.mediaServerType === MediaServerType.EMBY + ? 'Emby' + : 'Jellyfin', }), { autoDismiss: true, @@ -97,7 +98,9 @@ const JellyfinImportModal: React.FC = ({ 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 = ({ 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 = ({ ( @@ -269,7 +275,9 @@ const JellyfinImportModal: React.FC = ({ diff --git a/src/components/UserList/index.tsx b/src/components/UserList/index.tsx index d4901ed47..18f96b490 100644 --- a/src/components/UserList/index.tsx +++ b/src/components/UserList/index.tsx @@ -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('displayname'); @@ -514,7 +512,8 @@ const UserList = () => { > - {publicRuntimeConfig.JELLYFIN_TYPE == 'emby' + {settings.currentSettings.mediaServerType === + MediaServerType.EMBY ? intl.formatMessage(messages.importfrommediaserver, { mediaServerName: 'Emby', }) @@ -659,7 +658,7 @@ const UserList = () => { {intl.formatMessage(messages.localuser)} - ) : publicRuntimeConfig.JELLYFIN_TYPE == 'emby' ? ( + ) : user.userType === UserType.EMBY ? ( {intl.formatMessage(messages.mediaServerUser, { mediaServerName: 'Emby', diff --git a/src/components/UserProfile/UserSettings/UserGeneralSettings/index.tsx b/src/components/UserProfile/UserSettings/UserGeneralSettings/index.tsx index 960746adf..20d67265c 100644 --- a/src/components/UserProfile/UserSettings/UserGeneralSettings/index.tsx +++ b/src/components/UserProfile/UserSettings/UserGeneralSettings/index.tsx @@ -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 = () => { {intl.formatMessage(messages.localuser)} - ) : publicRuntimeConfig.JELLYFIN_TYPE == 'emby' ? ( + ) : user?.userType === UserType.EMBY ? ( {intl.formatMessage(messages.mediaServerUser, { mediaServerName: 'Emby', diff --git a/src/styles/globals.css b/src/styles/globals.css index 8110e87e0..d7176d41a 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -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;