mirror of
https://github.com/seerr-team/seerr.git
synced 2026-06-15 20:09:33 -04:00
feat(settings): add a proper modal for switching
This commit is contained in:
@@ -1,28 +1,50 @@
|
||||
import ConfirmButton from '@app/components/Common/ConfirmButton';
|
||||
import Alert from '@app/components/Common/Alert';
|
||||
import Button from '@app/components/Common/Button';
|
||||
import Modal from '@app/components/Common/Modal';
|
||||
import useSettings from '@app/hooks/useSettings';
|
||||
import globalMessages from '@app/i18n/globalMessages';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import { Transition } from '@headlessui/react';
|
||||
import { MediaServerType } from '@server/constants/server';
|
||||
import axios from 'axios';
|
||||
import { useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { Fragment, useState } from 'react';
|
||||
import { FormattedMessage, useIntl } from 'react-intl';
|
||||
import { useToasts } from 'react-toast-notifications';
|
||||
|
||||
type SwitchTargetServerType = 'jellyfin' | 'emby';
|
||||
type SwitchTargetServerType = 'jellyfin' | 'emby' | 'plex';
|
||||
|
||||
const messages = defineMessages('components.Settings', {
|
||||
switchMediaServerError:
|
||||
'Something went wrong while switching media server. Please try again.',
|
||||
switchMediaServerSuccess:
|
||||
'Media server switched. All users logged out. Restart the server, then sign in again.',
|
||||
switchMediaServerStepsPlex:
|
||||
'1) Have users link Jellyfin or Emby in {profile} → {linkedAccounts}.\n2) Optionally check {users} to see who has linked.\n3) Choose the target below and switch.',
|
||||
switchMediaServerStepsJellyfinEmby:
|
||||
'1) Configure Plex in the Plex tab.\n2) Have users link Plex in {profile} → {linkedAccounts}.\n3) Optionally check {users}.\n4) Choose the target below and switch.',
|
||||
switchMediaServerWarning:
|
||||
'Everyone will be logged out. You must restart the server after switching.',
|
||||
switchTargetAfter: 'New media server:',
|
||||
switchMediaServerButton: 'Switch media server',
|
||||
checkUsersLink: 'Users',
|
||||
});
|
||||
|
||||
const SwitchMediaServerSection = () => {
|
||||
const settings = useSettings();
|
||||
const intl = useIntl();
|
||||
const { addToast } = useToasts();
|
||||
const isPlex =
|
||||
settings.currentSettings.mediaServerType === MediaServerType.PLEX;
|
||||
const isJellyfin =
|
||||
settings.currentSettings.mediaServerType === MediaServerType.JELLYFIN;
|
||||
const isEmby =
|
||||
settings.currentSettings.mediaServerType === MediaServerType.EMBY;
|
||||
|
||||
const [isModalOpen, setModalOpen] = useState(false);
|
||||
const [isSubmitting, setSubmitting] = useState(false);
|
||||
const [switchTargetServerType, setSwitchTargetServerType] =
|
||||
useState<SwitchTargetServerType>('jellyfin');
|
||||
useState<SwitchTargetServerType>(isPlex ? 'jellyfin' : 'plex');
|
||||
|
||||
if (
|
||||
settings.currentSettings.mediaServerType === MediaServerType.NOT_CONFIGURED
|
||||
@@ -30,113 +52,146 @@ const SwitchMediaServerSection = () => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const targetPayload = { targetServerType: switchTargetServerType };
|
||||
|
||||
const handleSwitch = async () => {
|
||||
setSubmitting(true);
|
||||
try {
|
||||
const { data } = await axios.post<{ message?: string }>(
|
||||
'/api/v1/settings/switch-media-server',
|
||||
targetPayload
|
||||
);
|
||||
addToast(
|
||||
data?.message ?? intl.formatMessage(messages.switchMediaServerSuccess),
|
||||
{ appearance: 'success' }
|
||||
);
|
||||
setModalOpen(false);
|
||||
window.location.reload();
|
||||
} catch (err: unknown) {
|
||||
const extracted = axios.isAxiosError(err)
|
||||
? (err.response?.data?.error ??
|
||||
err.response?.data?.message ??
|
||||
err.message)
|
||||
: err instanceof Error
|
||||
? err.message
|
||||
: null;
|
||||
const message =
|
||||
extracted != null && String(extracted).trim() !== ''
|
||||
? String(extracted)
|
||||
: intl.formatMessage(messages.switchMediaServerError);
|
||||
addToast(message, { appearance: 'error' });
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const linkValues = {
|
||||
profile: <strong className="font-semibold text-gray-300">Profile</strong>,
|
||||
linkedAccounts: (
|
||||
<strong className="font-semibold text-gray-300">Linked accounts</strong>
|
||||
),
|
||||
users: (
|
||||
<Link
|
||||
href="/settings/users"
|
||||
className="font-medium text-indigo-400 hover:text-indigo-300 hover:underline"
|
||||
>
|
||||
{intl.formatMessage(messages.checkUsersLink)}
|
||||
</Link>
|
||||
),
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mt-10 border-t border-gray-700 pt-8">
|
||||
<h3 className="text-lg font-medium text-red-400">
|
||||
<FormattedMessage
|
||||
id="components.Settings.switchMediaServer"
|
||||
defaultMessage="Switch media server"
|
||||
/>
|
||||
</h3>
|
||||
<p className="mt-1 text-sm text-gray-400">
|
||||
{settings.currentSettings.mediaServerType === MediaServerType.PLEX ? (
|
||||
<FormattedMessage
|
||||
id="components.Settings.switchMediaServerDescriptionPlex"
|
||||
defaultMessage="Have users link Jellyfin or Emby in {profile} => {linkedAccounts} first, then choose the target below and switch. All users will be logged out. Restart the server after switching."
|
||||
values={{
|
||||
profile: (
|
||||
<strong className="font-semibold text-gray-300">Profile</strong>
|
||||
),
|
||||
linkedAccounts: (
|
||||
<strong className="font-semibold text-gray-300">
|
||||
Linked accounts
|
||||
</strong>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="components.Settings.switchMediaServerDescriptionJellyfinEmby"
|
||||
defaultMessage="Configure Plex in the Plex tab, then have users link Plex in {profile} => {linkedAccounts}, then switch. This clears the current server. All users will be logged out. Restart the server after switching."
|
||||
values={{
|
||||
profile: (
|
||||
<strong className="font-semibold text-gray-300">Profile</strong>
|
||||
),
|
||||
linkedAccounts: (
|
||||
<strong className="font-semibold text-gray-300">
|
||||
Linked accounts
|
||||
</strong>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</p>
|
||||
{settings.currentSettings.mediaServerType === MediaServerType.PLEX && (
|
||||
<div className="mt-3 flex items-center gap-2">
|
||||
<label
|
||||
htmlFor="switch-target-server"
|
||||
className="text-sm text-gray-400"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="components.Settings.switchTargetServerType"
|
||||
defaultMessage="After switch, use:"
|
||||
/>
|
||||
</label>
|
||||
<select
|
||||
id="switch-target-server"
|
||||
className="rounded-md border border-gray-600 bg-gray-800 px-3 py-1.5 text-sm text-white focus:border-indigo-500 focus:ring-indigo-500"
|
||||
value={switchTargetServerType}
|
||||
onChange={(e) =>
|
||||
setSwitchTargetServerType(
|
||||
e.target.value as SwitchTargetServerType
|
||||
)
|
||||
}
|
||||
>
|
||||
<option value="jellyfin">Jellyfin</option>
|
||||
<option value="emby">Emby</option>
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
<ConfirmButton
|
||||
className="mt-4"
|
||||
confirmText={intl.formatMessage(globalMessages.areyousure)}
|
||||
onClick={async () => {
|
||||
try {
|
||||
const { data } = await axios.post<{ message?: string }>(
|
||||
'/api/v1/settings/switch-media-server',
|
||||
settings.currentSettings.mediaServerType === MediaServerType.PLEX
|
||||
? { targetServerType: switchTargetServerType }
|
||||
: undefined
|
||||
);
|
||||
addToast(
|
||||
data?.message ??
|
||||
intl.formatMessage(messages.switchMediaServerSuccess),
|
||||
{
|
||||
appearance: 'success',
|
||||
}
|
||||
);
|
||||
window.location.reload();
|
||||
} catch (err: unknown) {
|
||||
const extracted = axios.isAxiosError(err)
|
||||
? (err.response?.data?.error ??
|
||||
err.response?.data?.message ??
|
||||
err.message)
|
||||
: err instanceof Error
|
||||
? err.message
|
||||
: null;
|
||||
const message =
|
||||
extracted != null && String(extracted).trim() !== ''
|
||||
? String(extracted)
|
||||
: intl.formatMessage(messages.switchMediaServerError);
|
||||
addToast(message, { appearance: 'error' });
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Button buttonType="danger" onClick={() => setModalOpen(true)}>
|
||||
<FormattedMessage
|
||||
id="components.Settings.switchMediaServerButton"
|
||||
defaultMessage="Switch media server"
|
||||
defaultMessage={messages.switchMediaServerButton.defaultMessage}
|
||||
/>
|
||||
</ConfirmButton>
|
||||
</Button>
|
||||
|
||||
<Transition
|
||||
as={Fragment}
|
||||
show={isModalOpen}
|
||||
enter="transition-opacity ease-in-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="transition-opacity ease-in-out duration-300"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Modal
|
||||
title={intl.formatMessage(messages.switchMediaServerButton)}
|
||||
onCancel={() => !isSubmitting && setModalOpen(false)}
|
||||
onOk={handleSwitch}
|
||||
okText={intl.formatMessage(messages.switchMediaServerButton)}
|
||||
okButtonType="danger"
|
||||
cancelText={intl.formatMessage(globalMessages.cancel)}
|
||||
loading={isSubmitting}
|
||||
okDisabled={isSubmitting}
|
||||
>
|
||||
<p className="whitespace-pre-line text-gray-300">
|
||||
{isPlex ? (
|
||||
<FormattedMessage
|
||||
id="components.Settings.switchMediaServerStepsPlex"
|
||||
defaultMessage={
|
||||
messages.switchMediaServerStepsPlex.defaultMessage
|
||||
}
|
||||
values={linkValues}
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="components.Settings.switchMediaServerStepsJellyfinEmby"
|
||||
defaultMessage={
|
||||
messages.switchMediaServerStepsJellyfinEmby.defaultMessage
|
||||
}
|
||||
values={linkValues}
|
||||
/>
|
||||
)}
|
||||
</p>
|
||||
<div className="mt-3">
|
||||
<Alert
|
||||
title={intl.formatMessage(messages.switchMediaServerWarning)}
|
||||
type="warning"
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-3 flex flex-wrap items-center gap-2">
|
||||
<label
|
||||
htmlFor="switch-target-server-modal"
|
||||
className="text-sm text-gray-400"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="components.Settings.switchTargetAfter"
|
||||
defaultMessage={messages.switchTargetAfter.defaultMessage}
|
||||
/>
|
||||
</label>
|
||||
<select
|
||||
id="switch-target-server-modal"
|
||||
className="rounded-md border border-gray-600 bg-gray-800 px-3 py-1.5 text-sm text-white focus:border-indigo-500 focus:ring-indigo-500"
|
||||
value={switchTargetServerType}
|
||||
onChange={(e) =>
|
||||
setSwitchTargetServerType(
|
||||
e.target.value as SwitchTargetServerType
|
||||
)
|
||||
}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
{isPlex && (
|
||||
<>
|
||||
<option value="jellyfin">Jellyfin</option>
|
||||
<option value="emby">Emby</option>
|
||||
</>
|
||||
)}
|
||||
{(isJellyfin || isEmby) && (
|
||||
<>
|
||||
<option value="plex">Plex</option>
|
||||
{isEmby && <option value="jellyfin">Jellyfin</option>}
|
||||
{isJellyfin && <option value="emby">Emby</option>}
|
||||
</>
|
||||
)}
|
||||
</select>
|
||||
</div>
|
||||
</Modal>
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -37,7 +37,7 @@ import { Field, Form, Formik } from 'formik';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { FormattedMessage, useIntl } from 'react-intl';
|
||||
import useSWR from 'swr';
|
||||
import validator from 'validator';
|
||||
import * as Yup from 'yup';
|
||||
@@ -95,6 +95,8 @@ const messages = defineMessages('components.UserList', {
|
||||
'The <strong>Enable Local Sign-In</strong> setting is currently disabled.',
|
||||
linkedToPlex: 'Plex linked',
|
||||
linkedToJellyfinEmby: 'Jellyfin/Emby linked',
|
||||
switchMediaServerTip:
|
||||
'Users with "Plex linked" or "Jellyfin/Emby linked" can sign in after you switch media server in {generalSettings}.',
|
||||
});
|
||||
|
||||
type Sort =
|
||||
@@ -696,6 +698,29 @@ const UserList = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{(settings.currentSettings.mediaServerType === MediaServerType.PLEX ||
|
||||
settings.currentSettings.mediaServerType === MediaServerType.JELLYFIN ||
|
||||
settings.currentSettings.mediaServerType === MediaServerType.EMBY) && (
|
||||
<p className="mb-4 text-sm text-gray-400">
|
||||
<FormattedMessage
|
||||
id="components.UserList.switchMediaServerTip"
|
||||
defaultMessage={messages.switchMediaServerTip.defaultMessage}
|
||||
values={{
|
||||
generalSettings: (
|
||||
<Link
|
||||
href="/settings/main"
|
||||
className="font-medium text-indigo-400 hover:text-indigo-300 hover:underline"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="components.UserList.generalSettingsLinkText"
|
||||
defaultMessage="General"
|
||||
/>
|
||||
</Link>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
)}
|
||||
<Table>
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
@@ -1162,6 +1162,7 @@
|
||||
"components.Settings.blocklistedTagImportTitle": "Import Blocklisted Tag Configuration",
|
||||
"components.Settings.blocklistedTagsText": "Blocklisted Tags",
|
||||
"components.Settings.cancelscan": "Cancel Scan",
|
||||
"components.Settings.checkUsersLink": "Users",
|
||||
"components.Settings.chooseProvider": "Choose metadata providers for different content types",
|
||||
"components.Settings.clearBlocklistedTagsConfirm": "Are you sure you want to clear the blocklisted tags?",
|
||||
"components.Settings.clickTest": "Click on the \"Test\" button to check connectivity with metadata providers",
|
||||
@@ -1267,8 +1268,13 @@
|
||||
"components.Settings.ssl": "SSL",
|
||||
"components.Settings.startscan": "Start Scan",
|
||||
"components.Settings.starttyping": "Starting typing to search.",
|
||||
"components.Settings.switchMediaServerButton": "Switch media server",
|
||||
"components.Settings.switchMediaServerError": "Something went wrong while switching media server. Please try again.",
|
||||
"components.Settings.switchMediaServerStepsJellyfinEmby": "1) Configure Plex in the Plex tab.\\n2) Have users link Plex in {profile} → {linkedAccounts}.\\n3) Optionally check {users}.\\n4) Choose the target below and switch.",
|
||||
"components.Settings.switchMediaServerStepsPlex": "1) Have users link Jellyfin or Emby in {profile} → {linkedAccounts}.\\n2) Optionally check {users} to see who has linked.\\n3) Choose the target below and switch.",
|
||||
"components.Settings.switchMediaServerSuccess": "Media server switched. All users logged out. Restart the server, then sign in again.",
|
||||
"components.Settings.switchMediaServerWarning": "Everyone will be logged out. You must restart the server after switching.",
|
||||
"components.Settings.switchTargetAfter": "New media server:",
|
||||
"components.Settings.syncJellyfin": "Sync Libraries",
|
||||
"components.Settings.syncing": "Syncing",
|
||||
"components.Settings.tautulliApiKey": "API Key",
|
||||
@@ -1425,6 +1431,7 @@
|
||||
"components.UserList.sortByUser": "Sort by username",
|
||||
"components.UserList.toggleSortDirection": "Click again to sort {direction}",
|
||||
"components.UserList.toggleSortDirectionAria": "Toggle sort direction",
|
||||
"components.UserList.switchMediaServerTip": "Users with \"Plex linked\" or \"Jellyfin/Emby linked\" can sign in after you switch media server in {generalSettings}.",
|
||||
"components.UserList.totalrequests": "Requests",
|
||||
"components.UserList.user": "User",
|
||||
"components.UserList.usercreatedfailed": "Something went wrong while creating the user.",
|
||||
@@ -1675,4 +1682,4 @@
|
||||
"pages.returnHome": "Return Home",
|
||||
"pages.serviceunavailable": "Service Unavailable",
|
||||
"pages.somethingwentwrong": "Something Went Wrong"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user