* 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). * feat(api): add severType to the api BREAKING CHANGE: This adds a serverType to the `/auth/jellyfin` which requires a serverType to be set (`jellyfin`/`emby`) * refactor: use enums for serverType and rename selectedservice to serverType * refactor(auth): jellyfin/emby authentication to set MediaServerType * fix: issue page formatMessage for 4k media * refactor: cleaner way of handling serverType change using MediaServerType instead of strings instead of using strings now it will use MediaServerType enums for serverType * revert: removed conditional render of the auto-request permission reverts the conditional render toshow the auto-request permission if the mediaServerType was set to Plex as this should be handled in a different PR and Cypress tests should be modified accordingly(currently cypress test would fail if this conditional check is there) * feat: add server type step to setup * feat: migrate existing emby setups to use emby mediaServerType * fix: scan jobs not running when media server type is emby * fix: emby media server type migration * refactor: change emby logo to full logo * style: decrease emby logo size in setup screen * refactor: use title case for servertype i18n message * refactor(i18n): fix a typo * refactor: use enums instead of numbers * fix: remove old references to JELLYFIN_TYPE environment variable * fix: go back to the last step when refresh the setup page * fix: move "scanning in background" tip next to the scanning section * fix: redirect the setup page when Jellyseerr is already setup --------- Co-authored-by: Gauthier <mail@gauthierth.fr>
277 lines
11 KiB
TypeScript
277 lines
11 KiB
TypeScript
import EmbyLogo from '@app/assets/services/emby.svg';
|
|
import JellyfinLogo from '@app/assets/services/jellyfin.svg';
|
|
import PlexLogo from '@app/assets/services/plex.svg';
|
|
import AppDataWarning from '@app/components/AppDataWarning';
|
|
import Button from '@app/components/Common/Button';
|
|
import ImageFader from '@app/components/Common/ImageFader';
|
|
import PageTitle from '@app/components/Common/PageTitle';
|
|
import LanguagePicker from '@app/components/Layout/LanguagePicker';
|
|
import SettingsJellyfin from '@app/components/Settings/SettingsJellyfin';
|
|
import SettingsPlex from '@app/components/Settings/SettingsPlex';
|
|
import SettingsServices from '@app/components/Settings/SettingsServices';
|
|
import SetupSteps from '@app/components/Setup/SetupSteps';
|
|
import useLocale from '@app/hooks/useLocale';
|
|
import useSettings from '@app/hooks/useSettings';
|
|
import defineMessages from '@app/utils/defineMessages';
|
|
import { MediaServerType } from '@server/constants/server';
|
|
import Image from 'next/image';
|
|
import { useRouter } from 'next/router';
|
|
import { useEffect, useState } from 'react';
|
|
import { useIntl } from 'react-intl';
|
|
import useSWR, { mutate } from 'swr';
|
|
import SetupLogin from './SetupLogin';
|
|
|
|
const messages = defineMessages('components.Setup', {
|
|
welcome: 'Welcome to Jellyseerr',
|
|
subtitle: 'Get started by choosing your media server',
|
|
configjellyfin: 'Configure Jellyfin',
|
|
configplex: 'Configure Plex',
|
|
configemby: 'Configure Emby',
|
|
setup: 'Setup',
|
|
finish: 'Finish Setup',
|
|
finishing: 'Finishing…',
|
|
continue: 'Continue',
|
|
servertype: 'Choose Server Type',
|
|
signin: 'Sign In',
|
|
configuremediaserver: 'Configure Media Server',
|
|
configureservices: 'Configure Services',
|
|
});
|
|
|
|
const Setup = () => {
|
|
const intl = useIntl();
|
|
const [isUpdating, setIsUpdating] = useState(false);
|
|
const [currentStep, setCurrentStep] = useState(1);
|
|
const [mediaServerSettingsComplete, setMediaServerSettingsComplete] =
|
|
useState(false);
|
|
const [mediaServerType, setMediaServerType] = useState(
|
|
MediaServerType.NOT_CONFIGURED
|
|
);
|
|
const router = useRouter();
|
|
const { locale } = useLocale();
|
|
const settings = useSettings();
|
|
|
|
const finishSetup = async () => {
|
|
setIsUpdating(true);
|
|
const res = await fetch('/api/v1/settings/initialize', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
});
|
|
if (!res.ok) throw new Error();
|
|
const data: { initialized: boolean } = await res.json();
|
|
|
|
setIsUpdating(false);
|
|
if (data.initialized) {
|
|
const mainRes = await fetch('/api/v1/settings/main', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({ locale }),
|
|
});
|
|
if (!mainRes.ok) throw new Error();
|
|
|
|
mutate('/api/v1/settings/public');
|
|
router.push('/');
|
|
}
|
|
};
|
|
|
|
const { data: backdrops } = useSWR<string[]>('/api/v1/backdrops', {
|
|
refreshInterval: 0,
|
|
refreshWhenHidden: false,
|
|
revalidateOnFocus: false,
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (settings.currentSettings.initialized) {
|
|
router.push('/');
|
|
}
|
|
if (
|
|
settings.currentSettings.mediaServerType !==
|
|
MediaServerType.NOT_CONFIGURED
|
|
) {
|
|
setCurrentStep(3);
|
|
setMediaServerType(settings.currentSettings.mediaServerType);
|
|
}
|
|
}, [
|
|
settings.currentSettings.mediaServerType,
|
|
settings.currentSettings.initialized,
|
|
router,
|
|
]);
|
|
|
|
return (
|
|
<div className="relative flex min-h-screen flex-col justify-center bg-gray-900 py-12">
|
|
<PageTitle title={intl.formatMessage(messages.setup)} />
|
|
<ImageFader
|
|
backgroundImages={
|
|
backdrops?.map(
|
|
(backdrop) => `https://image.tmdb.org/t/p/original${backdrop}`
|
|
) ?? []
|
|
}
|
|
/>
|
|
<div className="absolute top-4 right-4 z-50">
|
|
<LanguagePicker />
|
|
</div>
|
|
<div className="relative z-40 px-4 sm:mx-auto sm:w-full sm:max-w-4xl">
|
|
<div className="relative mb-10 h-48 max-w-full sm:mx-auto sm:h-64 sm:max-w-md">
|
|
<Image src="/logo_stacked.svg" alt="Logo" fill />
|
|
</div>
|
|
<AppDataWarning />
|
|
<nav className="relative z-50">
|
|
<ul
|
|
className="divide-y divide-gray-600 rounded-md border border-gray-600 bg-gray-800 bg-opacity-50 md:flex md:divide-y-0"
|
|
style={{ backdropFilter: 'blur(5px)' }}
|
|
>
|
|
<SetupSteps
|
|
stepNumber={1}
|
|
description={intl.formatMessage(messages.servertype)}
|
|
active={currentStep === 1}
|
|
completed={currentStep > 1}
|
|
/>
|
|
<SetupSteps
|
|
stepNumber={2}
|
|
description={intl.formatMessage(messages.signin)}
|
|
active={currentStep === 2}
|
|
completed={currentStep > 2}
|
|
/>
|
|
<SetupSteps
|
|
stepNumber={3}
|
|
description={intl.formatMessage(messages.configuremediaserver)}
|
|
active={currentStep === 3}
|
|
completed={currentStep > 3}
|
|
/>
|
|
<SetupSteps
|
|
stepNumber={4}
|
|
description={intl.formatMessage(messages.configureservices)}
|
|
active={currentStep === 4}
|
|
isLastStep
|
|
/>
|
|
</ul>
|
|
</nav>
|
|
<div className="mt-10 w-full rounded-md border border-gray-600 bg-gray-800 bg-opacity-50 p-4 text-white">
|
|
{currentStep === 1 && (
|
|
<div className="flex flex-col items-center pb-6">
|
|
<div className="mb-2 flex justify-center text-xl font-bold">
|
|
{intl.formatMessage(messages.welcome)}
|
|
</div>
|
|
<div className="mb-2 flex justify-center pb-6 text-sm">
|
|
{intl.formatMessage(messages.subtitle)}
|
|
</div>
|
|
<div className="grid grid-cols-3">
|
|
<div className="flex flex-col divide-y divide-gray-600 rounded-l border border-gray-600 py-2">
|
|
<div className="mb-2 flex flex-1 items-center justify-center py-2 px-2">
|
|
<JellyfinLogo className="h-10" />
|
|
</div>
|
|
<div className="px-2 pt-2">
|
|
<button
|
|
onClick={() => {
|
|
setMediaServerType(MediaServerType.JELLYFIN);
|
|
setCurrentStep(2);
|
|
}}
|
|
className="button-md relative z-10 inline-flex h-full w-full items-center justify-center rounded-md border border-gray-600 bg-transparent px-4 py-2 text-sm font-medium leading-5 text-white transition duration-150 ease-in-out hover:z-20 hover:border-gray-200 focus:z-20 focus:border-gray-100 focus:outline-none active:border-gray-100"
|
|
>
|
|
{intl.formatMessage(messages.configjellyfin)}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div className="flex flex-col divide-y divide-gray-600 border-y border-gray-600 py-2">
|
|
<div className="mb-2 flex flex-1 items-center justify-center py-2 px-2">
|
|
<PlexLogo className="h-8" />
|
|
</div>
|
|
<div className="px-2 pt-2">
|
|
<button
|
|
onClick={() => {
|
|
setMediaServerType(MediaServerType.PLEX);
|
|
setCurrentStep(2);
|
|
}}
|
|
className="button-md relative z-10 inline-flex h-full w-full items-center justify-center rounded-md border border-gray-600 bg-transparent px-4 py-2 text-sm font-medium leading-5 text-white transition duration-150 ease-in-out hover:z-20 hover:border-gray-200 focus:z-20 focus:border-gray-100 focus:outline-none active:border-gray-100"
|
|
>
|
|
{intl.formatMessage(messages.configplex)}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div className="flex flex-col divide-y divide-gray-600 rounded-r border border-gray-600 py-2">
|
|
<div className="mb-2 flex flex-1 items-center justify-center py-2 px-2">
|
|
<EmbyLogo className="h-9" />
|
|
</div>
|
|
<div className="px-2 pt-2">
|
|
<button
|
|
onClick={() => {
|
|
setMediaServerType(MediaServerType.EMBY);
|
|
setCurrentStep(2);
|
|
}}
|
|
className="button-md relative z-10 inline-flex h-full w-full items-center justify-center rounded-md border border-gray-600 bg-transparent px-4 py-2 text-sm font-medium leading-5 text-white transition duration-150 ease-in-out hover:z-20 hover:border-gray-200 focus:z-20 focus:border-gray-100 focus:outline-none active:border-gray-100"
|
|
>
|
|
{intl.formatMessage(messages.configemby)}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{currentStep === 2 && (
|
|
<SetupLogin
|
|
serverType={mediaServerType}
|
|
onCancel={() => {
|
|
setMediaServerType(MediaServerType.NOT_CONFIGURED);
|
|
setCurrentStep(1);
|
|
}}
|
|
onComplete={() => setCurrentStep(3)}
|
|
/>
|
|
)}
|
|
{currentStep === 3 && (
|
|
<div className="p-2">
|
|
{mediaServerType === MediaServerType.PLEX ? (
|
|
<SettingsPlex
|
|
onComplete={() => setMediaServerSettingsComplete(true)}
|
|
/>
|
|
) : (
|
|
<SettingsJellyfin
|
|
isSetupSettings
|
|
onComplete={() => setMediaServerSettingsComplete(true)}
|
|
/>
|
|
)}
|
|
<div className="actions">
|
|
<div className="flex justify-end">
|
|
<span className="ml-3 inline-flex rounded-md shadow-sm">
|
|
<Button
|
|
buttonType="primary"
|
|
disabled={!mediaServerSettingsComplete}
|
|
onClick={() => setCurrentStep(4)}
|
|
>
|
|
{intl.formatMessage(messages.continue)}
|
|
</Button>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{currentStep === 4 && (
|
|
<div>
|
|
<SettingsServices />
|
|
<div className="actions">
|
|
<div className="flex justify-end">
|
|
<span className="ml-3 inline-flex rounded-md shadow-sm">
|
|
<Button
|
|
buttonType="primary"
|
|
onClick={() => finishSetup()}
|
|
disabled={isUpdating}
|
|
>
|
|
{isUpdating
|
|
? intl.formatMessage(messages.finishing)
|
|
: intl.formatMessage(messages.finish)}
|
|
</Button>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default Setup;
|