mirror of
https://github.com/seerr-team/seerr.git
synced 2026-10-12 02:22:17 +00:00
fix: improve local login UX (#2849)
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import Badge from '@app/components/Common/Badge';
|
||||
import UserWarnings from '@app/components/Layout/UserWarnings';
|
||||
import VersionStatus from '@app/components/Layout/VersionStatus';
|
||||
import useClickOutside from '@app/hooks/useClickOutside';
|
||||
import { Permission, useUser } from '@app/hooks/useUser';
|
||||
@@ -233,10 +232,6 @@ const Sidebar = ({
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
<div className="px-2">
|
||||
<UserWarnings onClick={() => setClosed()} />
|
||||
</div>
|
||||
|
||||
{hasPermission(Permission.ADMIN) && (
|
||||
<div className="px-2">
|
||||
<VersionStatus onClick={() => setClosed()} />
|
||||
@@ -322,9 +317,6 @@ const Sidebar = ({
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
<div className="px-2">
|
||||
<UserWarnings />
|
||||
</div>
|
||||
{hasPermission(Permission.ADMIN) && (
|
||||
<div className="px-2">
|
||||
<VersionStatus />
|
||||
|
||||
@@ -1,63 +1,72 @@
|
||||
import AddEmailModal from '@app/components/Login/AddEmailModal';
|
||||
import { useUser } from '@app/hooks/useUser';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import { ExclamationTriangleIcon } from '@heroicons/react/24/outline';
|
||||
import Link from 'next/link';
|
||||
import {
|
||||
ChevronRightIcon,
|
||||
ExclamationTriangleIcon,
|
||||
} from '@heroicons/react/24/outline';
|
||||
import { useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
const messages = defineMessages('components.Layout.UserWarnings', {
|
||||
emailRequired: 'An email address is required.',
|
||||
emailInvalid: 'Email address is invalid.',
|
||||
passwordRequired: 'A password is required.',
|
||||
profileIncomplete: 'Profile is incomplete',
|
||||
});
|
||||
|
||||
interface UserWarningsProps {
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
const UserWarnings: React.FC<UserWarningsProps> = ({ onClick }) => {
|
||||
const UserWarnings: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const { user } = useUser();
|
||||
//check if a user has warnings
|
||||
const { user, revalidate } = useUser();
|
||||
const [showEmailModal, setShowEmailModal] = useState(false);
|
||||
|
||||
if (!user || !user.warnings || user.warnings.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let res = null;
|
||||
let warningText = '';
|
||||
let showSetEmail = false;
|
||||
|
||||
user.warnings.forEach((warning) => {
|
||||
let link = '';
|
||||
let warningText = '';
|
||||
let warningTitle = '';
|
||||
for (const warning of user.warnings) {
|
||||
switch (warning) {
|
||||
case 'userEmailRequired':
|
||||
link = '/profile/settings/';
|
||||
warningTitle = 'Profile is incomplete';
|
||||
warningText = intl.formatMessage(messages.emailRequired);
|
||||
showSetEmail = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
res = (
|
||||
<Link
|
||||
href={link}
|
||||
onClick={onClick}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' && onClick) {
|
||||
onClick();
|
||||
}
|
||||
}}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className="mx-2 mb-2 flex items-center rounded-lg bg-yellow-500 p-2 text-xs text-white ring-1 ring-gray-700 transition duration-300 hover:bg-yellow-400"
|
||||
if (!warningText) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{showEmailModal && (
|
||||
<AddEmailModal
|
||||
onClose={() => setShowEmailModal(false)}
|
||||
onSave={() => {
|
||||
setShowEmailModal(false);
|
||||
revalidate();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<button
|
||||
onClick={showSetEmail ? () => setShowEmailModal(true) : undefined}
|
||||
className="service-error-banner mb-4 w-full cursor-pointer transition duration-300 hover:border-yellow-400 hover:bg-yellow-500/30"
|
||||
>
|
||||
<ExclamationTriangleIcon className="h-6 w-6" />
|
||||
<div className="flex min-w-0 flex-1 flex-col truncate px-2 last:pr-0">
|
||||
<span className="font-bold">{warningTitle}</span>
|
||||
<span className="truncate">{warningText}</span>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
});
|
||||
|
||||
return res;
|
||||
<ExclamationTriangleIcon className="h-5 w-5 flex-shrink-0" />
|
||||
<span className="flex-1 text-left">
|
||||
<span className="font-bold">
|
||||
{intl.formatMessage(messages.profileIncomplete)}
|
||||
</span>
|
||||
{': '}
|
||||
{warningText}
|
||||
</span>
|
||||
<ChevronRightIcon className="h-5 w-5 flex-shrink-0" />
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserWarnings;
|
||||
|
||||
@@ -3,6 +3,7 @@ import PullToRefresh from '@app/components/Layout/PullToRefresh';
|
||||
import SearchInput from '@app/components/Layout/SearchInput';
|
||||
import Sidebar from '@app/components/Layout/Sidebar';
|
||||
import UserDropdown from '@app/components/Layout/UserDropdown';
|
||||
import UserWarnings from '@app/components/Layout/UserWarnings';
|
||||
import useLocale from '@app/hooks/useLocale';
|
||||
import useSettings from '@app/hooks/useSettings';
|
||||
import { useUser } from '@app/hooks/useUser';
|
||||
@@ -124,7 +125,10 @@ const Layout = ({ children }: LayoutProps) => {
|
||||
|
||||
<main className="relative top-16 z-0 focus:outline-none" tabIndex={0}>
|
||||
<div className="mb-6">
|
||||
<div className="max-w-8xl mx-auto px-4">{children}</div>
|
||||
<div className="max-w-8xl mx-auto px-4">
|
||||
<UserWarnings />
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@@ -1,41 +1,40 @@
|
||||
import Modal from '@app/components/Common/Modal';
|
||||
import useSettings from '@app/hooks/useSettings';
|
||||
import { useUser } from '@app/hooks/useUser';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import { Transition } from '@headlessui/react';
|
||||
import { ApiErrorCode } from '@server/constants/error';
|
||||
import axios from 'axios';
|
||||
import { Field, Formik } from 'formik';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useToasts } from 'react-toast-notifications';
|
||||
import { mutate } from 'swr';
|
||||
import validator from 'validator';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
const messages = defineMessages('components.Login', {
|
||||
title: 'Add Email',
|
||||
description:
|
||||
'Since this is your first time logging into {applicationName}, you are required to add a valid email address.',
|
||||
'Add a valid email address to complete your profile. This will be used for notifications and local sign-in.',
|
||||
email: 'Email address',
|
||||
emailAlreadyTaken: 'This email is already in use.',
|
||||
validationEmailRequired: 'You must provide an email',
|
||||
validationEmailFormat: 'Invalid email',
|
||||
saving: 'Adding…',
|
||||
save: 'Add',
|
||||
saveFailed: 'Something went wrong while saving.',
|
||||
});
|
||||
|
||||
interface AddEmailModalProps {
|
||||
username: string;
|
||||
password: string;
|
||||
onClose: () => void;
|
||||
onSave: () => void;
|
||||
}
|
||||
|
||||
const AddEmailModal: React.FC<AddEmailModalProps> = ({
|
||||
onClose,
|
||||
username,
|
||||
password,
|
||||
onSave,
|
||||
}) => {
|
||||
const AddEmailModal: React.FC<AddEmailModalProps> = ({ onClose, onSave }) => {
|
||||
const intl = useIntl();
|
||||
const settings = useSettings();
|
||||
const { addToast } = useToasts();
|
||||
const { user } = useUser();
|
||||
|
||||
const EmailSettingsSchema = Yup.object().shape({
|
||||
const EmailSchema = Yup.object().shape({
|
||||
email: Yup.string()
|
||||
.test(
|
||||
'email',
|
||||
@@ -51,66 +50,81 @@ const AddEmailModal: React.FC<AddEmailModalProps> = ({
|
||||
show
|
||||
enter="transition ease-in-out duration-300 transform opacity-0"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacuty-100"
|
||||
enterTo="opacity-100"
|
||||
leave="transition ease-in-out duration-300 transform opacity-100"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Formik
|
||||
initialValues={{
|
||||
email: '',
|
||||
}}
|
||||
validationSchema={EmailSettingsSchema}
|
||||
initialValues={{ email: '' }}
|
||||
validationSchema={EmailSchema}
|
||||
onSubmit={async (values) => {
|
||||
if (!user?.id) {
|
||||
addToast(intl.formatMessage(messages.saveFailed), {
|
||||
autoDismiss: true,
|
||||
appearance: 'error',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await axios.post('/api/v1/auth/jellyfin', {
|
||||
username: username,
|
||||
password: password,
|
||||
const { data: current } = await axios.get(
|
||||
`/api/v1/user/${user.id}/settings/main`
|
||||
);
|
||||
await axios.post(`/api/v1/user/${user.id}/settings/main`, {
|
||||
...current,
|
||||
email: values.email,
|
||||
});
|
||||
|
||||
await mutate(`/api/v1/user/${user.id}/settings/main`);
|
||||
onSave();
|
||||
} catch {
|
||||
// set error here
|
||||
} catch (e) {
|
||||
addToast(
|
||||
intl.formatMessage(
|
||||
axios.isAxiosError(e) &&
|
||||
e.response?.data?.message === ApiErrorCode.InvalidEmail
|
||||
? messages.emailAlreadyTaken
|
||||
: messages.saveFailed
|
||||
),
|
||||
{
|
||||
autoDismiss: true,
|
||||
appearance: 'error',
|
||||
}
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{({ errors, touched, handleSubmit, isSubmitting, isValid }) => {
|
||||
return (
|
||||
<Modal
|
||||
onCancel={onClose}
|
||||
okButtonType="primary"
|
||||
okText={
|
||||
isSubmitting
|
||||
? intl.formatMessage(messages.saving)
|
||||
: intl.formatMessage(messages.save)
|
||||
}
|
||||
okDisabled={isSubmitting || !isValid}
|
||||
onOk={() => handleSubmit()}
|
||||
title={intl.formatMessage(messages.title)}
|
||||
>
|
||||
{intl.formatMessage(messages.description, {
|
||||
applicationName: settings.currentSettings.applicationTitle,
|
||||
})}
|
||||
<label htmlFor="email" className="text-label">
|
||||
{intl.formatMessage(messages.email)}
|
||||
</label>
|
||||
<div className="mb-2 mt-1 sm:col-span-2 sm:mt-0">
|
||||
<div className="flex rounded-md shadow-sm">
|
||||
<Field
|
||||
id="email"
|
||||
name="email"
|
||||
type="text"
|
||||
placeholder={intl.formatMessage(messages.email)}
|
||||
/>
|
||||
</div>
|
||||
{errors.email && touched.email && (
|
||||
<div className="error">{errors.email}</div>
|
||||
)}
|
||||
{({ errors, touched, handleSubmit, isSubmitting, isValid }) => (
|
||||
<Modal
|
||||
onCancel={onClose}
|
||||
okButtonType="primary"
|
||||
okText={
|
||||
isSubmitting
|
||||
? intl.formatMessage(messages.saving)
|
||||
: intl.formatMessage(messages.save)
|
||||
}
|
||||
okDisabled={isSubmitting || !isValid}
|
||||
onOk={() => handleSubmit()}
|
||||
title={intl.formatMessage(messages.title)}
|
||||
>
|
||||
{intl.formatMessage(messages.description)}
|
||||
<label htmlFor="email" className="text-label">
|
||||
{intl.formatMessage(messages.email)}
|
||||
</label>
|
||||
<div className="mb-2 mt-1 sm:col-span-2 sm:mt-0">
|
||||
<div className="flex rounded-md shadow-sm">
|
||||
<Field
|
||||
id="email"
|
||||
name="email"
|
||||
type="text"
|
||||
placeholder={intl.formatMessage(messages.email)}
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}}
|
||||
{errors.email && touched.email && (
|
||||
<div className="error">{errors.email}</div>
|
||||
)}
|
||||
</div>
|
||||
</Modal>
|
||||
)}
|
||||
</Formik>
|
||||
</Transition>
|
||||
);
|
||||
|
||||
@@ -4,6 +4,7 @@ import useSettings from '@app/hooks/useSettings';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import { ArrowLeftOnRectangleIcon } from '@heroicons/react/24/outline';
|
||||
import { ExclamationTriangleIcon } from '@heroicons/react/24/solid';
|
||||
import { MediaServerType } from '@server/constants/server';
|
||||
import axios from 'axios';
|
||||
import { Field, Form, Formik } from 'formik';
|
||||
import Link from 'next/link';
|
||||
@@ -18,7 +19,10 @@ const messages = defineMessages('components.Login', {
|
||||
password: 'Password',
|
||||
validationemailrequired: 'You must provide a valid email address',
|
||||
validationpasswordrequired: 'You must provide a password',
|
||||
jellyfinLocalLoginHint:
|
||||
"If you haven't set an email address in your profile, use your {mediaServerName} username instead.",
|
||||
loginerror: 'Something went wrong while trying to sign in.',
|
||||
credentialerror: 'The email address or password is incorrect.',
|
||||
tipEmailHasTrailingWhitespace: 'The email ends with whitespace',
|
||||
signingin: 'Signing In…',
|
||||
signin: 'Sign In',
|
||||
@@ -61,8 +65,14 @@ const LocalLogin = ({ revalidate }: LocalLoginProps) => {
|
||||
email: values.email,
|
||||
password: values.password,
|
||||
});
|
||||
} catch {
|
||||
setLoginError(intl.formatMessage(messages.loginerror));
|
||||
} catch (e) {
|
||||
setLoginError(
|
||||
intl.formatMessage(
|
||||
axios.isAxiosError(e) && e.response?.status === 403
|
||||
? messages.credentialerror
|
||||
: messages.loginerror
|
||||
)
|
||||
);
|
||||
} finally {
|
||||
revalidate();
|
||||
}
|
||||
@@ -84,9 +94,7 @@ const LocalLogin = ({ revalidate }: LocalLoginProps) => {
|
||||
<Field
|
||||
id="email"
|
||||
name="email"
|
||||
placeholder={`${intl.formatMessage(
|
||||
messages.email
|
||||
)} / ${intl.formatMessage(messages.username)}`}
|
||||
placeholder={intl.formatMessage(messages.email)}
|
||||
type="text"
|
||||
inputMode="email"
|
||||
data-testid="email"
|
||||
@@ -107,6 +115,20 @@ const LocalLogin = ({ revalidate }: LocalLoginProps) => {
|
||||
typeof errors.email === 'string' && (
|
||||
<div className="error">{errors.email}</div>
|
||||
)}
|
||||
{(settings.currentSettings.mediaServerType ===
|
||||
MediaServerType.JELLYFIN ||
|
||||
settings.currentSettings.mediaServerType ===
|
||||
MediaServerType.EMBY) && (
|
||||
<div className="mt-1 text-xs text-gray-400">
|
||||
{intl.formatMessage(messages.jellyfinLocalLoginHint, {
|
||||
mediaServerName:
|
||||
settings.currentSettings.mediaServerType ===
|
||||
MediaServerType.JELLYFIN
|
||||
? 'Jellyfin'
|
||||
: 'Emby',
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="mb-2 mt-1">
|
||||
<div className="form-input-field">
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import Alert from '@app/components/Common/Alert';
|
||||
import Button from '@app/components/Common/Button';
|
||||
import LabeledCheckbox from '@app/components/Common/LabeledCheckbox';
|
||||
import LoadingSpinner from '@app/components/Common/LoadingSpinner';
|
||||
@@ -39,6 +40,8 @@ const messages = defineMessages('components.Settings.SettingsUsers', {
|
||||
tvRequestLimitLabel: 'Global Series Request Limit',
|
||||
defaultPermissions: 'Default Permissions',
|
||||
defaultPermissionsTip: 'Initial permissions assigned to new users',
|
||||
disabledMediaServerLoginWarning:
|
||||
'Some users may not have a {applicationTitle} password set. Disabling {mediaServerName} sign-in could lock them out. Affected users will need to set a password from their profile or via a password reset link.',
|
||||
});
|
||||
|
||||
const SettingsUsers = () => {
|
||||
@@ -200,6 +203,21 @@ const SettingsUsers = () => {
|
||||
)
|
||||
}
|
||||
/>
|
||||
{!values.mediaServerLogin && values.localLogin && (
|
||||
<div className="mt-4">
|
||||
<Alert
|
||||
title={intl.formatMessage(
|
||||
messages.disabledMediaServerLoginWarning,
|
||||
{
|
||||
applicationTitle:
|
||||
settings.currentSettings.applicationTitle,
|
||||
...mediaServerFormatValues,
|
||||
}
|
||||
)}
|
||||
type="warning"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -24,6 +24,8 @@ const messages = defineMessages('components.UserList', {
|
||||
'Something went wrong while importing {mediaServerName} users.',
|
||||
importedfromJellyfin:
|
||||
'<strong>{userCount}</strong> {mediaServerName} {userCount, plural, one {user} other {users}} imported successfully!',
|
||||
importedUsersNoPassword:
|
||||
'Imported users do not have a {applicationTitle} password set. If you disable {mediaServerName} sign-in, they will need to set a password from their profile or via a password reset link.',
|
||||
user: 'User',
|
||||
noJellyfinuserstoimport: 'There are no {mediaServerName} users to import.',
|
||||
newJellyfinsigninenabled:
|
||||
@@ -84,6 +86,20 @@ const JellyfinImportModal: React.FC<JellyfinImportProps> = ({
|
||||
}
|
||||
);
|
||||
|
||||
addToast(
|
||||
intl.formatMessage(messages.importedUsersNoPassword, {
|
||||
applicationTitle: settings.currentSettings.applicationTitle,
|
||||
mediaServerName:
|
||||
settings.currentSettings.mediaServerType === MediaServerType.EMBY
|
||||
? 'Emby'
|
||||
: 'Jellyfin',
|
||||
}),
|
||||
{
|
||||
autoDismiss: false,
|
||||
appearance: 'warning',
|
||||
}
|
||||
);
|
||||
|
||||
if (onComplete) {
|
||||
onComplete();
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ const messages = defineMessages('components.UserList', {
|
||||
importfromplexerror: 'Something went wrong while importing Plex users.',
|
||||
importedfromplex:
|
||||
'<strong>{userCount}</strong> Plex {userCount, plural, one {user} other {users}} imported successfully!',
|
||||
importedPlexUsersNoPassword:
|
||||
'Imported users do not have a {applicationTitle} password set. If you disable Plex sign-in, they will need to set a password from their profile or via a password reset link.',
|
||||
user: 'User',
|
||||
nouserstoimport: 'There are no Plex users to import.',
|
||||
newplexsigninenabled:
|
||||
@@ -68,6 +70,16 @@ const PlexImportModal = ({ onCancel, onComplete }: PlexImportProps) => {
|
||||
}
|
||||
);
|
||||
|
||||
addToast(
|
||||
intl.formatMessage(messages.importedPlexUsersNoPassword, {
|
||||
applicationTitle: settings.currentSettings.applicationTitle,
|
||||
}),
|
||||
{
|
||||
autoDismiss: false,
|
||||
appearance: 'warning',
|
||||
}
|
||||
);
|
||||
|
||||
if (onComplete) {
|
||||
onComplete();
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import Button from '@app/components/Common/Button';
|
||||
import LoadingSpinner from '@app/components/Common/LoadingSpinner';
|
||||
import PageTitle from '@app/components/Common/PageTitle';
|
||||
import SensitiveInput from '@app/components/Common/SensitiveInput';
|
||||
import useSettings from '@app/hooks/useSettings';
|
||||
import { Permission, useUser } from '@app/hooks/useUser';
|
||||
import globalMessages from '@app/i18n/globalMessages';
|
||||
import ErrorPage from '@app/pages/_error';
|
||||
@@ -19,6 +20,8 @@ import * as Yup from 'yup';
|
||||
const messages = defineMessages(
|
||||
'components.UserProfile.UserSettings.UserPasswordChange',
|
||||
{
|
||||
localPasswordDescription:
|
||||
'This password is used for signing in with the {applicationTitle} local login form. It is separate from your media server password.',
|
||||
password: 'Password',
|
||||
currentpassword: 'Current Password',
|
||||
newpassword: 'New Password',
|
||||
@@ -44,6 +47,7 @@ const messages = defineMessages(
|
||||
|
||||
const UserPasswordChange = () => {
|
||||
const intl = useIntl();
|
||||
const settings = useSettings();
|
||||
const { addToast } = useToasts();
|
||||
const router = useRouter();
|
||||
const { user: currentUser } = useUser();
|
||||
@@ -112,6 +116,11 @@ const UserPasswordChange = () => {
|
||||
/>
|
||||
<div className="mb-6">
|
||||
<h3 className="heading">{intl.formatMessage(messages.password)}</h3>
|
||||
<p className="mt-1 text-sm text-gray-400">
|
||||
{intl.formatMessage(messages.localPasswordDescription, {
|
||||
applicationTitle: settings.currentSettings.applicationTitle,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
<Formik
|
||||
initialValues={{
|
||||
|
||||
@@ -239,6 +239,7 @@
|
||||
"components.Layout.UserWarnings.emailInvalid": "Email address is invalid.",
|
||||
"components.Layout.UserWarnings.emailRequired": "An email address is required.",
|
||||
"components.Layout.UserWarnings.passwordRequired": "A password is required.",
|
||||
"components.Layout.UserWarnings.profileIncomplete": "Profile is incomplete",
|
||||
"components.Layout.VersionStatus.commitsbehind": "{commitsBehind} {commitsBehind, plural, one {commit} other {commits}} behind",
|
||||
"components.Layout.VersionStatus.outofdate": "Out of Date",
|
||||
"components.Layout.VersionStatus.streamdevelop": "Seerr Develop",
|
||||
@@ -246,8 +247,9 @@
|
||||
"components.Login.adminerror": "You must use an admin account to sign in.",
|
||||
"components.Login.back": "Go back",
|
||||
"components.Login.credentialerror": "The username or password is incorrect.",
|
||||
"components.Login.description": "Since this is your first time logging into {applicationName}, you are required to add a valid email address.",
|
||||
"components.Login.description": "Add a valid email address to complete your profile. This will be used for notifications and local sign-in.",
|
||||
"components.Login.email": "Email Address",
|
||||
"components.Login.emailAlreadyTaken": "This email is already in use.",
|
||||
"components.Login.emailtooltip": "Address does not need to be associated with your {mediaServerName} instance.",
|
||||
"components.Login.enablessl": "Use SSL",
|
||||
"components.Login.forgotpassword": "Forgot Password?",
|
||||
@@ -255,6 +257,7 @@
|
||||
"components.Login.initialsignin": "Connect",
|
||||
"components.Login.initialsigningin": "Connecting…",
|
||||
"components.Login.invalidurlerror": "Unable to connect to {mediaServerName} server.",
|
||||
"components.Login.jellyfinLocalLoginHint": "If you haven't set an email address in your profile, use your {mediaServerName} username instead.",
|
||||
"components.Login.loginerror": "Something went wrong while trying to sign in.",
|
||||
"components.Login.loginwithapp": "Login with {appName}",
|
||||
"components.Login.noadminerror": "No admin user found on the server.",
|
||||
@@ -262,6 +265,7 @@
|
||||
"components.Login.password": "Password",
|
||||
"components.Login.port": "Port",
|
||||
"components.Login.save": "Add",
|
||||
"components.Login.saveFailed": "Something went wrong while saving.",
|
||||
"components.Login.saving": "Adding…",
|
||||
"components.Login.servertype": "Server Type",
|
||||
"components.Login.signin": "Sign In",
|
||||
@@ -1058,6 +1062,7 @@
|
||||
"components.Settings.SettingsUsers.atLeastOneAuth": "At least one authentication method must be selected.",
|
||||
"components.Settings.SettingsUsers.defaultPermissions": "Default Permissions",
|
||||
"components.Settings.SettingsUsers.defaultPermissionsTip": "Initial permissions assigned to new users",
|
||||
"components.Settings.SettingsUsers.disabledMediaServerLoginWarning": "Some users may not have a {applicationTitle} password set. Disabling {mediaServerName} sign-in could lock them out. Affected users will need to set a password from their profile or via a password reset link.",
|
||||
"components.Settings.SettingsUsers.localLogin": "Enable Local Sign-In",
|
||||
"components.Settings.SettingsUsers.localLoginTip": "Allow users to sign in using their email address and password",
|
||||
"components.Settings.SettingsUsers.loginMethods": "Login Methods",
|
||||
@@ -1378,6 +1383,8 @@
|
||||
"components.UserList.descending": "descending",
|
||||
"components.UserList.edituser": "Edit User Permissions",
|
||||
"components.UserList.email": "Email Address",
|
||||
"components.UserList.importedPlexUsersNoPassword": "Imported users do not have a {applicationTitle} password set. If you disable Plex sign-in, they will need to set a password from their profile or via a password reset link.",
|
||||
"components.UserList.importedUsersNoPassword": "Imported users do not have a {applicationTitle} password set. If you disable {mediaServerName} sign-in, they will need to set a password from their profile or via a password reset link.",
|
||||
"components.UserList.importedfromJellyfin": "<strong>{userCount}</strong> {mediaServerName} {userCount, plural, one {user} other {users}} imported successfully!",
|
||||
"components.UserList.importedfromplex": "<strong>{userCount}</strong> Plex {userCount, plural, one {user} other {users}} imported successfully!",
|
||||
"components.UserList.importfromJellyfin": "Import {mediaServerName} Users",
|
||||
@@ -1545,6 +1552,7 @@
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.webpush": "Web Push",
|
||||
"components.UserProfile.UserSettings.UserPasswordChange.confirmpassword": "Confirm Password",
|
||||
"components.UserProfile.UserSettings.UserPasswordChange.currentpassword": "Current Password",
|
||||
"components.UserProfile.UserSettings.UserPasswordChange.localPasswordDescription": "This password is used for signing in with the {applicationTitle} local login form. It is separate from your media server password.",
|
||||
"components.UserProfile.UserSettings.UserPasswordChange.newpassword": "New Password",
|
||||
"components.UserProfile.UserSettings.UserPasswordChange.noPasswordSet": "This user account currently does not have a password set. Configure a password below to enable this account to sign in as a \"local user.\"",
|
||||
"components.UserProfile.UserSettings.UserPasswordChange.noPasswordSetOwnAccount": "Your account currently does not have a password set. Configure a password below to enable sign-in as a \"local user\" using your email address.",
|
||||
|
||||
Reference in New Issue
Block a user