feat: revamp login page and support disabling media server login (#1286)

* feat: support disabling jellyfin login

* feat: revamp login screen

Update the login screen for better usability, especially with OpenID
Connect and Plex login, allowing one-click login and removing the
accordion layout. Additionally, ensures that media server login is
hidden when disabled in the settings.

* test: update cypress login command
This commit is contained in:
Michael Thomas
2025-02-22 11:40:38 -05:00
committed by GitHub
parent 9712f56054
commit 73d8efaa54
23 changed files with 921 additions and 639 deletions

View File

@@ -1,4 +1,5 @@
import Button from '@app/components/Common/Button';
import LabeledCheckbox from '@app/components/Common/LabeledCheckbox';
import LoadingSpinner from '@app/components/Common/LoadingSpinner';
import PageTitle from '@app/components/Common/PageTitle';
import PermissionEdit from '@app/components/PermissionEdit';
@@ -13,6 +14,7 @@ import { Field, Form, Formik } from 'formik';
import { useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications';
import useSWR, { mutate } from 'swr';
import * as yup from 'yup';
const messages = defineMessages('components.Settings.SettingsUsers', {
users: 'Users',
@@ -20,9 +22,15 @@ const messages = defineMessages('components.Settings.SettingsUsers', {
userSettingsDescription: 'Configure global and default user settings.',
toastSettingsSuccess: 'User settings saved successfully!',
toastSettingsFailure: 'Something went wrong while saving settings.',
loginMethods: 'Login Methods',
loginMethodsTip: 'Configure login methods for users.',
localLogin: 'Enable Local Sign-In',
localLoginTip:
'Allow users to sign in using their email address and password, instead of {mediaServerName} OAuth',
'Allow users to sign in using their email address and password',
mediaServerLogin: 'Enable {mediaServerName} Sign-In',
mediaServerLoginTip:
'Allow users to sign in using their {mediaServerName} account',
atLeastOneAuth: 'At least one authentication method must be selected.',
newPlexLogin: 'Enable New {mediaServerName} Sign-In',
newPlexLoginTip:
'Allow {mediaServerName} users to sign in without first being imported',
@@ -42,6 +50,27 @@ const SettingsUsers = () => {
} = useSWR<MainSettings>('/api/v1/settings/main');
const settings = useSettings();
const schema = yup
.object()
.shape({
localLogin: yup.boolean(),
mediaServerLogin: yup.boolean(),
})
.test({
name: 'atLeastOneAuth',
test: function (values) {
const isValid = ['localLogin', 'mediaServerLogin'].some(
(field) => !!values[field]
);
if (isValid) return true;
return this.createError({
path: 'localLogin | mediaServerLogin',
message: intl.formatMessage(messages.atLeastOneAuth),
});
},
});
if (!data && !error) {
return <LoadingSpinner />;
}
@@ -52,6 +81,8 @@ const SettingsUsers = () => {
? 'Jellyfin'
: settings.currentSettings.mediaServerType === MediaServerType.EMBY
? 'Emby'
: settings.currentSettings.mediaServerType === MediaServerType.PLEX
? 'Plex'
: undefined,
};
@@ -73,6 +104,7 @@ const SettingsUsers = () => {
<Formik
initialValues={{
localLogin: data?.localLogin,
mediaServerLogin: data?.mediaServerLogin,
newPlexLogin: data?.newPlexLogin,
movieQuotaLimit: data?.defaultQuotas.movie.quotaLimit ?? 0,
movieQuotaDays: data?.defaultQuotas.movie.quotaDays ?? 7,
@@ -80,6 +112,7 @@ const SettingsUsers = () => {
tvQuotaDays: data?.defaultQuotas.tv.quotaDays ?? 7,
defaultPermissions: data?.defaultPermissions ?? 0,
}}
validationSchema={schema}
enableReinitialize
onSubmit={async (values) => {
try {
@@ -90,6 +123,7 @@ const SettingsUsers = () => {
},
body: JSON.stringify({
localLogin: values.localLogin,
mediaServerLogin: values.mediaServerLogin,
newPlexLogin: values.newPlexLogin,
defaultQuotas: {
movie: {
@@ -121,30 +155,61 @@ const SettingsUsers = () => {
}
}}
>
{({ isSubmitting, values, setFieldValue }) => {
{({ isSubmitting, isValid, values, errors, setFieldValue }) => {
return (
<Form className="section">
<div className="form-row">
<label htmlFor="localLogin" className="checkbox-label">
{intl.formatMessage(messages.localLogin)}
<span className="label-tip">
{intl.formatMessage(
messages.localLoginTip,
mediaServerFormatValues
<div
role="group"
aria-labelledby="group-label"
className="form-group"
>
<div className="form-row">
<span id="group-label" className="group-label">
{intl.formatMessage(messages.loginMethods)}
<span className="label-tip">
{intl.formatMessage(messages.loginMethodsTip)}
</span>
{'localLogin | mediaServerLogin' in errors && (
<span className="error">
{errors['localLogin | mediaServerLogin'] as string}
</span>
)}
</span>
</label>
<div className="form-input-area">
<Field
type="checkbox"
id="localLogin"
name="localLogin"
onChange={() => {
setFieldValue('localLogin', !values.localLogin);
}}
/>
<div className="form-input-area max-w-lg">
<LabeledCheckbox
id="localLogin"
label={intl.formatMessage(messages.localLogin)}
description={intl.formatMessage(
messages.localLoginTip,
mediaServerFormatValues
)}
onChange={() =>
setFieldValue('localLogin', !values.localLogin)
}
/>
<LabeledCheckbox
id="mediaServerLogin"
className="mt-4"
label={intl.formatMessage(
messages.mediaServerLogin,
mediaServerFormatValues
)}
description={intl.formatMessage(
messages.mediaServerLoginTip,
mediaServerFormatValues
)}
onChange={() =>
setFieldValue(
'mediaServerLogin',
!values.mediaServerLogin
)
}
/>
</div>
</div>
</div>
<div className="form-row">
<label htmlFor="newPlexLogin" className="checkbox-label">
{intl.formatMessage(
@@ -229,7 +294,7 @@ const SettingsUsers = () => {
<Button
buttonType="primary"
type="submit"
disabled={isSubmitting}
disabled={isSubmitting || !isValid}
>
<ArrowDownOnSquareIcon />
<span>