fix(ui): improve form usability (#1563)

* fix(ui): improve form usability

* refactor: remove unnecessary <> and </> tags

* fix(ui): set url inputmode for *arr URL base fields
pull/1565/head
TheCatLady 3 years ago committed by GitHub
parent b05b177776
commit 26580eaa21
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -255,7 +255,7 @@ class Settings {
}, },
plex: { plex: {
name: '', name: '',
ip: '127.0.0.1', ip: '',
port: 32400, port: 32400,
useSsl: false, useSsl: false,
libraries: [], libraries: [],
@ -272,7 +272,7 @@ class Settings {
types: 0, types: 0,
options: { options: {
emailFrom: '', emailFrom: '',
smtpHost: '127.0.0.1', smtpHost: '',
smtpPort: 587, smtpPort: 587,
secure: false, secure: false,
allowSelfSigned: false, allowSelfSigned: false,

@ -27,6 +27,7 @@ const SearchInput: React.FC = () => {
className="block w-full py-2 pl-10 text-white placeholder-gray-300 bg-gray-900 border border-gray-600 rounded-full focus:border-gray-500 hover:border-gray-500 focus:outline-none focus:ring-0 focus:placeholder-gray-400 sm:text-base" className="block w-full py-2 pl-10 text-white placeholder-gray-300 bg-gray-900 border border-gray-600 rounded-full focus:border-gray-500 hover:border-gray-500 focus:outline-none focus:ring-0 focus:placeholder-gray-400 sm:text-base"
placeholder={intl.formatMessage(messages.searchPlaceholder)} placeholder={intl.formatMessage(messages.searchPlaceholder)}
type="search" type="search"
inputMode="search"
value={searchValue} value={searchValue}
onChange={(e) => setSearchValue(e.target.value)} onChange={(e) => setSearchValue(e.target.value)}
onFocus={() => setIsOpen(true)} onFocus={() => setIsOpen(true)}

@ -6,6 +6,7 @@ import React, { useState } from 'react';
import { defineMessages, useIntl } from 'react-intl'; import { defineMessages, useIntl } from 'react-intl';
import * as Yup from 'yup'; import * as Yup from 'yup';
import Button from '../Common/Button'; import Button from '../Common/Button';
import SensitiveInput from '../Common/SensitiveInput';
const messages = defineMessages({ const messages = defineMessages({
email: 'Email Address', email: 'Email Address',
@ -69,7 +70,7 @@ const LocalLogin: React.FC<LocalLoginProps> = ({ revalidate }) => {
id="email" id="email"
name="email" name="email"
type="text" type="text"
placeholder="name@example.com" inputMode="email"
/> />
</div> </div>
{errors.email && touched.email && ( {errors.email && touched.email && (
@ -81,12 +82,12 @@ const LocalLogin: React.FC<LocalLoginProps> = ({ revalidate }) => {
</label> </label>
<div className="mt-1 mb-2 sm:mt-0 sm:col-span-2"> <div className="mt-1 mb-2 sm:mt-0 sm:col-span-2">
<div className="form-input-field"> <div className="form-input-field">
<Field <SensitiveInput
as="field"
id="password" id="password"
name="password" name="password"
type="password" type="password"
autoComplete="current-password" autoComplete="current-password"
placeholder={intl.formatMessage(messages.password)}
/> />
</div> </div>
{errors.password && touched.password && ( {errors.password && touched.password && (

@ -107,7 +107,7 @@ const ResetPassword: React.FC = () => {
id="email" id="email"
name="email" name="email"
type="text" type="text"
placeholder="name@example.com" inputMode="email"
className="flex-1 block w-full min-w-0 text-white transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5" className="flex-1 block w-full min-w-0 text-white transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5"
/> />
</div> </div>

@ -15,7 +15,8 @@ const messages = defineMessages({
botUsername: 'Bot Username', botUsername: 'Bot Username',
botAvatarUrl: 'Bot Avatar URL', botAvatarUrl: 'Bot Avatar URL',
webhookUrl: 'Webhook URL', webhookUrl: 'Webhook URL',
webhookUrlPlaceholder: 'Server Settings → Integrations → Webhooks', webhookUrlTip:
'Create a <DiscordWebhookLink>webhook integration</DiscordWebhookLink> in your server',
discordsettingssaved: 'Discord notification settings saved successfully!', discordsettingssaved: 'Discord notification settings saved successfully!',
discordsettingsfailed: 'Discord notification settings failed to save.', discordsettingsfailed: 'Discord notification settings failed to save.',
toastDiscordTestSending: 'Sending Discord test notification…', toastDiscordTestSending: 'Sending Discord test notification…',
@ -137,65 +138,75 @@ const NotificationsDiscord: React.FC = () => {
<div className="form-row"> <div className="form-row">
<label htmlFor="enabled" className="checkbox-label"> <label htmlFor="enabled" className="checkbox-label">
{intl.formatMessage(messages.agentenabled)} {intl.formatMessage(messages.agentenabled)}
<span className="label-required">*</span>
</label> </label>
<div className="form-input"> <div className="form-input">
<Field type="checkbox" id="enabled" name="enabled" /> <Field type="checkbox" id="enabled" name="enabled" />
</div> </div>
</div> </div>
<div className="form-row"> <div className="form-row">
<label htmlFor="botUsername" className="text-label"> <label htmlFor="name" className="text-label">
{intl.formatMessage(messages.botUsername)} {intl.formatMessage(messages.webhookUrl)}
<span className="label-required">*</span>
<span className="label-tip">
{intl.formatMessage(messages.webhookUrlTip, {
DiscordWebhookLink: function DiscordWebhookLink(msg) {
return (
<a
href="https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks"
className="text-white transition duration-300 hover:underline"
target="_blank"
rel="noreferrer"
>
{msg}
</a>
);
},
})}
</span>
</label> </label>
<div className="form-input"> <div className="form-input">
<div className="form-input-field"> <div className="form-input-field">
<Field <Field
id="botUsername" id="webhookUrl"
name="botUsername" name="webhookUrl"
type="text" type="text"
placeholder={intl.formatMessage(messages.botUsername)} inputMode="url"
/> />
</div> </div>
{errors.botUsername && touched.botUsername && ( {errors.webhookUrl && touched.webhookUrl && (
<div className="error">{errors.botUsername}</div> <div className="error">{errors.webhookUrl}</div>
)} )}
</div> </div>
</div> </div>
<div className="form-row"> <div className="form-row">
<label htmlFor="botAvatarUrl" className="text-label"> <label htmlFor="botUsername" className="text-label">
{intl.formatMessage(messages.botAvatarUrl)} {intl.formatMessage(messages.botUsername)}
</label> </label>
<div className="form-input"> <div className="form-input">
<div className="form-input-field"> <div className="form-input-field">
<Field <Field id="botUsername" name="botUsername" type="text" />
id="botAvatarUrl"
name="botAvatarUrl"
type="text"
placeholder={intl.formatMessage(messages.botAvatarUrl)}
/>
</div> </div>
{errors.botAvatarUrl && touched.botAvatarUrl && ( {errors.botUsername && touched.botUsername && (
<div className="error">{errors.botAvatarUrl}</div> <div className="error">{errors.botUsername}</div>
)} )}
</div> </div>
</div> </div>
<div className="form-row"> <div className="form-row">
<label htmlFor="name" className="text-label"> <label htmlFor="botAvatarUrl" className="text-label">
{intl.formatMessage(messages.webhookUrl)} {intl.formatMessage(messages.botAvatarUrl)}
<span className="label-required">*</span>
</label> </label>
<div className="form-input"> <div className="form-input">
<div className="form-input-field"> <div className="form-input-field">
<Field <Field
id="webhookUrl" id="botAvatarUrl"
name="webhookUrl" name="botAvatarUrl"
type="text" type="text"
placeholder={intl.formatMessage( inputMode="url"
messages.webhookUrlPlaceholder
)}
/> />
</div> </div>
{errors.webhookUrl && touched.webhookUrl && ( {errors.botAvatarUrl && touched.botAvatarUrl && (
<div className="error">{errors.webhookUrl}</div> <div className="error">{errors.botAvatarUrl}</div>
)} )}
</div> </div>
</div> </div>

@ -131,7 +131,7 @@ const NotificationsEmail: React.FC = () => {
types: data.types, types: data.types,
emailFrom: data.options.emailFrom, emailFrom: data.options.emailFrom,
smtpHost: data.options.smtpHost, smtpHost: data.options.smtpHost,
smtpPort: data.options.smtpPort, smtpPort: data.options.smtpPort ?? 587,
secure: data.options.secure, secure: data.options.secure,
authUser: data.options.authUser, authUser: data.options.authUser,
authPass: data.options.authPass, authPass: data.options.authPass,
@ -266,11 +266,22 @@ const NotificationsEmail: React.FC = () => {
<div className="form-row"> <div className="form-row">
<label htmlFor="enabled" className="checkbox-label"> <label htmlFor="enabled" className="checkbox-label">
{intl.formatMessage(messages.agentenabled)} {intl.formatMessage(messages.agentenabled)}
<span className="label-required">*</span>
</label> </label>
<div className="form-input"> <div className="form-input">
<Field type="checkbox" id="enabled" name="enabled" /> <Field type="checkbox" id="enabled" name="enabled" />
</div> </div>
</div> </div>
<div className="form-row">
<label htmlFor="senderName" className="text-label">
{intl.formatMessage(messages.senderName)}
</label>
<div className="form-input">
<div className="form-input-field">
<Field id="senderName" name="senderName" type="text" />
</div>
</div>
</div>
<div className="form-row"> <div className="form-row">
<label htmlFor="emailFrom" className="text-label"> <label htmlFor="emailFrom" className="text-label">
{intl.formatMessage(messages.emailsender)} {intl.formatMessage(messages.emailsender)}
@ -282,7 +293,7 @@ const NotificationsEmail: React.FC = () => {
id="emailFrom" id="emailFrom"
name="emailFrom" name="emailFrom"
type="text" type="text"
placeholder="no-reply@example.com" inputMode="email"
/> />
</div> </div>
{errors.emailFrom && touched.emailFrom && ( {errors.emailFrom && touched.emailFrom && (
@ -290,21 +301,6 @@ const NotificationsEmail: React.FC = () => {
)} )}
</div> </div>
</div> </div>
<div className="form-row">
<label htmlFor="senderName" className="text-label">
{intl.formatMessage(messages.senderName)}
</label>
<div className="form-input">
<div className="form-input-field">
<Field
id="senderName"
name="senderName"
placeholder="Overseerr"
type="text"
/>
</div>
</div>
</div>
<div className="form-row"> <div className="form-row">
<label htmlFor="smtpHost" className="text-label"> <label htmlFor="smtpHost" className="text-label">
{intl.formatMessage(messages.smtpHost)} {intl.formatMessage(messages.smtpHost)}
@ -316,7 +312,7 @@ const NotificationsEmail: React.FC = () => {
id="smtpHost" id="smtpHost"
name="smtpHost" name="smtpHost"
type="text" type="text"
placeholder="localhost" inputMode="url"
/> />
</div> </div>
{errors.smtpHost && touched.smtpHost && ( {errors.smtpHost && touched.smtpHost && (
@ -334,7 +330,7 @@ const NotificationsEmail: React.FC = () => {
id="smtpPort" id="smtpPort"
name="smtpPort" name="smtpPort"
type="text" type="text"
placeholder="465" inputMode="numeric"
className="short" className="short"
/> />
{errors.smtpPort && touched.smtpPort && ( {errors.smtpPort && touched.smtpPort && (
@ -385,8 +381,7 @@ const NotificationsEmail: React.FC = () => {
as="field" as="field"
id="authPass" id="authPass"
name="authPass" name="authPass"
type="password" autoComplete="one-time-code"
autoComplete="off"
/> />
</div> </div>
</div> </div>
@ -441,8 +436,7 @@ const NotificationsEmail: React.FC = () => {
as="field" as="field"
id="pgpPassword" id="pgpPassword"
name="pgpPassword" name="pgpPassword"
type="password" autoComplete="one-time-code"
autoComplete="off"
/> />
</div> </div>
{errors.pgpPassword && touched.pgpPassword && ( {errors.pgpPassword && touched.pgpPassword && (

@ -13,6 +13,8 @@ import NotificationTypeSelector from '../../../NotificationTypeSelector';
const messages = defineMessages({ const messages = defineMessages({
agentenabled: 'Enable Agent', agentenabled: 'Enable Agent',
webhookUrl: 'Webhook URL', webhookUrl: 'Webhook URL',
webhookUrlTip:
'Your user- or device-based <LunaSeaLink>notification webhook URL</LunaSeaLink>',
validationWebhookUrl: 'You must provide a valid URL', validationWebhookUrl: 'You must provide a valid URL',
profileName: 'Profile Name', profileName: 'Profile Name',
profileNameTip: 'Only required if not using the <code>default</code> profile', profileNameTip: 'Only required if not using the <code>default</code> profile',
@ -129,6 +131,7 @@ const NotificationsLunaSea: React.FC = () => {
<div className="form-row"> <div className="form-row">
<label htmlFor="enabled" className="checkbox-label"> <label htmlFor="enabled" className="checkbox-label">
{intl.formatMessage(messages.agentenabled)} {intl.formatMessage(messages.agentenabled)}
<span className="label-required">*</span>
</label> </label>
<div className="form-input"> <div className="form-input">
<Field type="checkbox" id="enabled" name="enabled" /> <Field type="checkbox" id="enabled" name="enabled" />
@ -138,10 +141,31 @@ const NotificationsLunaSea: React.FC = () => {
<label htmlFor="name" className="text-label"> <label htmlFor="name" className="text-label">
{intl.formatMessage(messages.webhookUrl)} {intl.formatMessage(messages.webhookUrl)}
<span className="label-required">*</span> <span className="label-required">*</span>
<span className="label-tip">
{intl.formatMessage(messages.webhookUrlTip, {
LunaSeaLink: function LunaSeaLink(msg) {
return (
<a
href="https://docs.lunasea.app/lunasea/notifications/overseerr"
className="text-white transition duration-300 hover:underline"
target="_blank"
rel="noreferrer"
>
{msg}
</a>
);
},
})}
</span>
</label> </label>
<div className="form-input"> <div className="form-input">
<div className="form-input-field"> <div className="form-input-field">
<Field id="webhookUrl" name="webhookUrl" type="text" /> <Field
id="webhookUrl"
name="webhookUrl"
type="text"
inputMode="url"
/>
</div> </div>
{errors.webhookUrl && touched.webhookUrl && ( {errors.webhookUrl && touched.webhookUrl && (
<div className="error">{errors.webhookUrl}</div> <div className="error">{errors.webhookUrl}</div>

@ -6,7 +6,6 @@ import { useToasts } from 'react-toast-notifications';
import useSWR from 'swr'; import useSWR from 'swr';
import * as Yup from 'yup'; import * as Yup from 'yup';
import globalMessages from '../../../../i18n/globalMessages'; import globalMessages from '../../../../i18n/globalMessages';
import Alert from '../../../Common/Alert';
import Button from '../../../Common/Button'; import Button from '../../../Common/Button';
import LoadingSpinner from '../../../Common/LoadingSpinner'; import LoadingSpinner from '../../../Common/LoadingSpinner';
import SensitiveInput from '../../../Common/SensitiveInput'; import SensitiveInput from '../../../Common/SensitiveInput';
@ -15,6 +14,8 @@ import NotificationTypeSelector from '../../../NotificationTypeSelector';
const messages = defineMessages({ const messages = defineMessages({
agentEnabled: 'Enable Agent', agentEnabled: 'Enable Agent',
accessToken: 'Access Token', accessToken: 'Access Token',
accessTokenTip:
'Create a token from your <PushbulletSettingsLink>Account Settings</PushbulletSettingsLink>',
validationAccessTokenRequired: 'You must provide an access token', validationAccessTokenRequired: 'You must provide an access token',
pushbulletSettingsSaved: pushbulletSettingsSaved:
'Pushbullet notification settings saved successfully!', 'Pushbullet notification settings saved successfully!',
@ -22,8 +23,6 @@ const messages = defineMessages({
toastPushbulletTestSending: 'Sending Pushbullet test notification…', toastPushbulletTestSending: 'Sending Pushbullet test notification…',
toastPushbulletTestSuccess: 'Pushbullet test notification sent!', toastPushbulletTestSuccess: 'Pushbullet test notification sent!',
toastPushbulletTestFailed: 'Pushbullet test notification failed to send.', toastPushbulletTestFailed: 'Pushbullet test notification failed to send.',
settingUpPushbulletDescription:
'To configure Pushbullet notifications, you will need to <CreateAccessTokenLink>create an access token</CreateAccessTokenLink>.',
}); });
const NotificationsPushbullet: React.FC = () => { const NotificationsPushbullet: React.FC = () => {
@ -123,91 +122,87 @@ const NotificationsPushbullet: React.FC = () => {
}; };
return ( return (
<> <Form className="section">
<Alert <div className="form-row">
title={intl.formatMessage( <label htmlFor="enabled" className="checkbox-label">
messages.settingUpPushbulletDescription, {intl.formatMessage(messages.agentEnabled)}
{ <span className="label-required">*</span>
CreateAccessTokenLink: function CreateAccessTokenLink(msg) { </label>
return ( <div className="form-input">
<a <Field type="checkbox" id="enabled" name="enabled" />
href="https://www.pushbullet.com/#settings"
className="text-white transition duration-300 hover:underline"
target="_blank"
rel="noreferrer"
>
{msg}
</a>
);
},
}
)}
type="info"
/>
<Form className="section">
<div className="form-row">
<label htmlFor="enabled" className="checkbox-label">
{intl.formatMessage(messages.agentEnabled)}
</label>
<div className="form-input">
<Field type="checkbox" id="enabled" name="enabled" />
</div>
</div> </div>
<div className="form-row"> </div>
<label htmlFor="accessToken" className="text-label"> <div className="form-row">
{intl.formatMessage(messages.accessToken)} <label htmlFor="accessToken" className="text-label">
<span className="label-required">*</span> {intl.formatMessage(messages.accessToken)}
</label> <span className="label-required">*</span>
<div className="form-input"> <span className="label-tip">
<div className="form-input-field"> {intl.formatMessage(messages.accessTokenTip, {
<SensitiveInput PushbulletSettingsLink: function PushbulletSettingsLink(
as="field" msg
id="accessToken" ) {
name="accessToken" return (
type="text" <a
placeholder={intl.formatMessage(messages.accessToken)} href="https://www.pushbullet.com/#settings/account"
/> className="text-white transition duration-300 hover:underline"
</div> target="_blank"
{errors.accessToken && touched.accessToken && ( rel="noreferrer"
<div className="error">{errors.accessToken}</div> >
)} {msg}
</a>
);
},
})}
</span>
</label>
<div className="form-input">
<div className="form-input-field">
<SensitiveInput
as="field"
id="accessToken"
name="accessToken"
autoComplete="one-time-code"
/>
</div> </div>
{errors.accessToken && touched.accessToken && (
<div className="error">{errors.accessToken}</div>
)}
</div> </div>
<NotificationTypeSelector </div>
currentTypes={values.types} <NotificationTypeSelector
onUpdate={(newTypes) => setFieldValue('types', newTypes)} currentTypes={values.types}
/> onUpdate={(newTypes) => setFieldValue('types', newTypes)}
<div className="actions"> />
<div className="flex justify-end"> <div className="actions">
<span className="inline-flex ml-3 rounded-md shadow-sm"> <div className="flex justify-end">
<Button <span className="inline-flex ml-3 rounded-md shadow-sm">
buttonType="warning" <Button
disabled={isSubmitting || !isValid || isTesting} buttonType="warning"
onClick={(e) => { disabled={isSubmitting || !isValid || isTesting}
e.preventDefault(); onClick={(e) => {
testSettings(); e.preventDefault();
}} testSettings();
> }}
{isTesting >
? intl.formatMessage(globalMessages.testing) {isTesting
: intl.formatMessage(globalMessages.test)} ? intl.formatMessage(globalMessages.testing)
</Button> : intl.formatMessage(globalMessages.test)}
</span> </Button>
<span className="inline-flex ml-3 rounded-md shadow-sm"> </span>
<Button <span className="inline-flex ml-3 rounded-md shadow-sm">
buttonType="primary" <Button
type="submit" buttonType="primary"
disabled={isSubmitting || !isValid || isTesting} type="submit"
> disabled={isSubmitting || !isValid || isTesting}
{isSubmitting >
? intl.formatMessage(globalMessages.saving) {isSubmitting
: intl.formatMessage(globalMessages.save)} ? intl.formatMessage(globalMessages.saving)
</Button> : intl.formatMessage(globalMessages.save)}
</span> </Button>
</div> </span>
</div> </div>
</Form> </div>
</> </Form>
); );
}} }}
</Formik> </Formik>

@ -6,15 +6,18 @@ import { useToasts } from 'react-toast-notifications';
import useSWR from 'swr'; import useSWR from 'swr';
import * as Yup from 'yup'; import * as Yup from 'yup';
import globalMessages from '../../../../i18n/globalMessages'; import globalMessages from '../../../../i18n/globalMessages';
import Alert from '../../../Common/Alert';
import Button from '../../../Common/Button'; import Button from '../../../Common/Button';
import LoadingSpinner from '../../../Common/LoadingSpinner'; import LoadingSpinner from '../../../Common/LoadingSpinner';
import NotificationTypeSelector from '../../../NotificationTypeSelector'; import NotificationTypeSelector from '../../../NotificationTypeSelector';
const messages = defineMessages({ const messages = defineMessages({
agentenabled: 'Enable Agent', agentenabled: 'Enable Agent',
accessToken: 'Application/API Token', accessToken: 'Application API Token',
accessTokenTip:
'<ApplicationRegistrationLink>Register an application</ApplicationRegistrationLink> for use with Overseerr',
userToken: 'User or Group Key', userToken: 'User or Group Key',
userTokenTip:
'Your 30-character <UsersGroupsLink>user or group identifier</UsersGroupsLink>',
validationAccessTokenRequired: 'You must provide a valid application token', validationAccessTokenRequired: 'You must provide a valid application token',
validationUserTokenRequired: 'You must provide a valid user key', validationUserTokenRequired: 'You must provide a valid user key',
pushoversettingssaved: 'Pushover notification settings saved successfully!', pushoversettingssaved: 'Pushover notification settings saved successfully!',
@ -22,8 +25,6 @@ const messages = defineMessages({
toastPushoverTestSending: 'Sending Pushover test notification…', toastPushoverTestSending: 'Sending Pushover test notification…',
toastPushoverTestSuccess: 'Pushover test notification sent!', toastPushoverTestSuccess: 'Pushover test notification sent!',
toastPushoverTestFailed: 'Pushover test notification failed to send.', toastPushoverTestFailed: 'Pushover test notification failed to send.',
settinguppushoverDescription:
'To configure Pushover notifications, you will need to <RegisterApplicationLink>register an application</RegisterApplicationLink>. (You can use one of the <IconLink>official Overseerr icons on GitHub</IconLink>.)',
}); });
const NotificationsPushover: React.FC = () => { const NotificationsPushover: React.FC = () => {
@ -143,118 +144,112 @@ const NotificationsPushover: React.FC = () => {
}; };
return ( return (
<> <Form className="section">
<Alert <div className="form-row">
title={intl.formatMessage(messages.settinguppushoverDescription, { <label htmlFor="enabled" className="checkbox-label">
RegisterApplicationLink: function RegisterApplicationLink(msg) { {intl.formatMessage(messages.agentenabled)}
return ( <span className="label-required">*</span>
<a </label>
href="https://pushover.net/apps/build" <div className="form-input">
className="text-white transition duration-300 hover:underline" <Field type="checkbox" id="enabled" name="enabled" />
target="_blank"
rel="noreferrer"
>
{msg}
</a>
);
},
IconLink: function IconLink(msg) {
return (
<a
href="https://github.com/sct/overseerr/tree/develop/public"
className="text-white transition duration-300 hover:underline"
target="_blank"
rel="noreferrer"
>
{msg}
</a>
);
},
})}
type="info"
/>
<Form className="section">
<div className="form-row">
<label htmlFor="enabled" className="checkbox-label">
{intl.formatMessage(messages.agentenabled)}
</label>
<div className="form-input">
<Field type="checkbox" id="enabled" name="enabled" />
</div>
</div> </div>
<div className="form-row"> </div>
<label htmlFor="accessToken" className="text-label"> <div className="form-row">
{intl.formatMessage(messages.accessToken)} <label htmlFor="accessToken" className="text-label">
<span className="label-required">*</span> {intl.formatMessage(messages.accessToken)}
</label> <span className="label-required">*</span>
<div className="form-input"> <span className="label-tip">
<div className="form-input-field"> {intl.formatMessage(messages.accessTokenTip, {
<Field ApplicationRegistrationLink: function ApplicationRegistrationLink(
id="accessToken" msg
name="accessToken" ) {
type="text" return (
placeholder={intl.formatMessage(messages.accessToken)} <a
/> href="https://pushover.net/api#registration"
</div> className="text-white transition duration-300 hover:underline"
{errors.accessToken && touched.accessToken && ( target="_blank"
<div className="error">{errors.accessToken}</div> rel="noreferrer"
)} >
{msg}
</a>
);
},
})}
</span>
</label>
<div className="form-input">
<div className="form-input-field">
<Field id="accessToken" name="accessToken" type="text" />
</div> </div>
{errors.accessToken && touched.accessToken && (
<div className="error">{errors.accessToken}</div>
)}
</div> </div>
<div className="form-row"> </div>
<label htmlFor="userToken" className="text-label"> <div className="form-row">
{intl.formatMessage(messages.userToken)} <label htmlFor="userToken" className="text-label">
<span className="label-required">*</span> {intl.formatMessage(messages.userToken)}
</label> <span className="label-required">*</span>
<div className="form-input"> <span className="label-tip">
<div className="form-input-field"> {intl.formatMessage(messages.userTokenTip, {
<Field UsersGroupsLink: function UsersGroupsLink(msg) {
id="userToken" return (
name="userToken" <a
type="text" href="https://pushover.net/api#identifiers"
placeholder={intl.formatMessage(messages.userToken)} className="text-white transition duration-300 hover:underline"
/> target="_blank"
</div> rel="noreferrer"
{errors.userToken && touched.userToken && ( >
<div className="error">{errors.userToken}</div> {msg}
)} </a>
);
},
})}
</span>
</label>
<div className="form-input">
<div className="form-input-field">
<Field id="userToken" name="userToken" type="text" />
</div> </div>
{errors.userToken && touched.userToken && (
<div className="error">{errors.userToken}</div>
)}
</div> </div>
<NotificationTypeSelector </div>
currentTypes={values.types} <NotificationTypeSelector
onUpdate={(newTypes) => setFieldValue('types', newTypes)} currentTypes={values.types}
/> onUpdate={(newTypes) => setFieldValue('types', newTypes)}
<div className="actions"> />
<div className="flex justify-end"> <div className="actions">
<span className="inline-flex ml-3 rounded-md shadow-sm"> <div className="flex justify-end">
<Button <span className="inline-flex ml-3 rounded-md shadow-sm">
buttonType="warning" <Button
disabled={isSubmitting || !isValid || isTesting} buttonType="warning"
onClick={(e) => { disabled={isSubmitting || !isValid || isTesting}
e.preventDefault(); onClick={(e) => {
testSettings(); e.preventDefault();
}} testSettings();
> }}
{isTesting >
? intl.formatMessage(globalMessages.testing) {isTesting
: intl.formatMessage(globalMessages.test)} ? intl.formatMessage(globalMessages.testing)
</Button> : intl.formatMessage(globalMessages.test)}
</span> </Button>
<span className="inline-flex ml-3 rounded-md shadow-sm"> </span>
<Button <span className="inline-flex ml-3 rounded-md shadow-sm">
buttonType="primary" <Button
type="submit" buttonType="primary"
disabled={isSubmitting || !isValid || isTesting} type="submit"
> disabled={isSubmitting || !isValid || isTesting}
{isSubmitting >
? intl.formatMessage(globalMessages.saving) {isSubmitting
: intl.formatMessage(globalMessages.save)} ? intl.formatMessage(globalMessages.saving)
</Button> : intl.formatMessage(globalMessages.save)}
</span> </Button>
</div> </span>
</div> </div>
</Form> </div>
</> </Form>
); );
}} }}
</Formik> </Formik>

@ -6,7 +6,6 @@ import { useToasts } from 'react-toast-notifications';
import useSWR from 'swr'; import useSWR from 'swr';
import * as Yup from 'yup'; import * as Yup from 'yup';
import globalMessages from '../../../../i18n/globalMessages'; import globalMessages from '../../../../i18n/globalMessages';
import Alert from '../../../Common/Alert';
import Button from '../../../Common/Button'; import Button from '../../../Common/Button';
import LoadingSpinner from '../../../Common/LoadingSpinner'; import LoadingSpinner from '../../../Common/LoadingSpinner';
import NotificationTypeSelector from '../../../NotificationTypeSelector'; import NotificationTypeSelector from '../../../NotificationTypeSelector';
@ -14,13 +13,13 @@ import NotificationTypeSelector from '../../../NotificationTypeSelector';
const messages = defineMessages({ const messages = defineMessages({
agentenabled: 'Enable Agent', agentenabled: 'Enable Agent',
webhookUrl: 'Webhook URL', webhookUrl: 'Webhook URL',
webhookUrlTip:
'Create an <WebhookLink>Incoming Webhook</WebhookLink> integration',
slacksettingssaved: 'Slack notification settings saved successfully!', slacksettingssaved: 'Slack notification settings saved successfully!',
slacksettingsfailed: 'Slack notification settings failed to save.', slacksettingsfailed: 'Slack notification settings failed to save.',
toastSlackTestSending: 'Sending Slack test notification…', toastSlackTestSending: 'Sending Slack test notification…',
toastSlackTestSuccess: 'Slack test notification sent!', toastSlackTestSuccess: 'Slack test notification sent!',
toastSlackTestFailed: 'Slack test notification failed to send.', toastSlackTestFailed: 'Slack test notification failed to send.',
settingupslackDescription:
'To configure Slack notifications, you will need to create an <WebhookLink>Incoming Webhook</WebhookLink> integration and enter the webhook URL below.',
validationWebhookUrl: 'You must provide a valid URL', validationWebhookUrl: 'You must provide a valid URL',
}); });
@ -49,166 +48,162 @@ const NotificationsSlack: React.FC = () => {
} }
return ( return (
<> <Formik
<Alert initialValues={{
title={intl.formatMessage(messages.settingupslackDescription, { enabled: data.enabled,
WebhookLink: function WebhookLink(msg) { types: data.types,
return ( webhookUrl: data.options.webhookUrl,
<a }}
href="https://my.slack.com/services/new/incoming-webhook/" validationSchema={NotificationsSlackSchema}
className="text-white transition duration-300 hover:underline" onSubmit={async (values) => {
target="_blank" try {
rel="noreferrer" await axios.post('/api/v1/settings/notifications/slack', {
> enabled: values.enabled,
{msg} types: values.types,
</a> options: {
); webhookUrl: values.webhookUrl,
}, },
})} });
type="info" addToast(intl.formatMessage(messages.slacksettingssaved), {
/> appearance: 'success',
<Formik autoDismiss: true,
initialValues={{ });
enabled: data.enabled, } catch (e) {
types: data.types, addToast(intl.formatMessage(messages.slacksettingsfailed), {
webhookUrl: data.options.webhookUrl, appearance: 'error',
}} autoDismiss: true,
validationSchema={NotificationsSlackSchema} });
onSubmit={async (values) => { } finally {
revalidate();
}
}}
>
{({ errors, touched, isSubmitting, values, isValid, setFieldValue }) => {
const testSettings = async () => {
setIsTesting(true);
let toastId: string | undefined;
try { try {
await axios.post('/api/v1/settings/notifications/slack', { addToast(
enabled: values.enabled, intl.formatMessage(messages.toastSlackTestSending),
{
autoDismiss: false,
appearance: 'info',
},
(id) => {
toastId = id;
}
);
await axios.post('/api/v1/settings/notifications/slack/test', {
enabled: true,
types: values.types, types: values.types,
options: { options: {
webhookUrl: values.webhookUrl, webhookUrl: values.webhookUrl,
}, },
}); });
addToast(intl.formatMessage(messages.slacksettingssaved), {
appearance: 'success', if (toastId) {
removeToast(toastId);
}
addToast(intl.formatMessage(messages.toastSlackTestSuccess), {
autoDismiss: true, autoDismiss: true,
appearance: 'success',
}); });
} catch (e) { } catch (e) {
addToast(intl.formatMessage(messages.slacksettingsfailed), { if (toastId) {
appearance: 'error', removeToast(toastId);
}
addToast(intl.formatMessage(messages.toastSlackTestFailed), {
autoDismiss: true, autoDismiss: true,
appearance: 'error',
}); });
} finally { } finally {
revalidate(); setIsTesting(false);
} }
}} };
>
{({
errors,
touched,
isSubmitting,
values,
isValid,
setFieldValue,
}) => {
const testSettings = async () => {
setIsTesting(true);
let toastId: string | undefined;
try {
addToast(
intl.formatMessage(messages.toastSlackTestSending),
{
autoDismiss: false,
appearance: 'info',
},
(id) => {
toastId = id;
}
);
await axios.post('/api/v1/settings/notifications/slack/test', {
enabled: true,
types: values.types,
options: {
webhookUrl: values.webhookUrl,
},
});
if (toastId) {
removeToast(toastId);
}
addToast(intl.formatMessage(messages.toastSlackTestSuccess), {
autoDismiss: true,
appearance: 'success',
});
} catch (e) {
if (toastId) {
removeToast(toastId);
}
addToast(intl.formatMessage(messages.toastSlackTestFailed), {
autoDismiss: true,
appearance: 'error',
});
} finally {
setIsTesting(false);
}
};
return ( return (
<Form className="section"> <Form className="section">
<div className="form-row"> <div className="form-row">
<label htmlFor="isDefault" className="checkbox-label"> <label htmlFor="isDefault" className="checkbox-label">
{intl.formatMessage(messages.agentenabled)} {intl.formatMessage(messages.agentenabled)}
</label> <span className="label-required">*</span>
<div className="form-input"> </label>
<Field type="checkbox" id="enabled" name="enabled" /> <div className="form-input">
</div> <Field type="checkbox" id="enabled" name="enabled" />
</div> </div>
<div className="form-row"> </div>
<label htmlFor="name" className="text-label"> <div className="form-row">
{intl.formatMessage(messages.webhookUrl)} <label htmlFor="name" className="text-label">
<span className="label-required">*</span> {intl.formatMessage(messages.webhookUrl)}
</label> <span className="label-required">*</span>
<div className="form-input"> <span className="label-tip">
<div className="form-input-field"> {intl.formatMessage(messages.webhookUrlTip, {
<Field id="webhookUrl" name="webhookUrl" type="text" /> WebhookLink: function WebhookLink(msg) {
</div> return (
{errors.webhookUrl && touched.webhookUrl && ( <a
<div className="error">{errors.webhookUrl}</div> href="https://my.slack.com/services/new/incoming-webhook/"
)} className="text-white transition duration-300 hover:underline"
target="_blank"
rel="noreferrer"
>
{msg}
</a>
);
},
})}
</span>
</label>
<div className="form-input">
<div className="form-input-field">
<Field
id="webhookUrl"
name="webhookUrl"
type="text"
inputMode="url"
/>
</div> </div>
{errors.webhookUrl && touched.webhookUrl && (
<div className="error">{errors.webhookUrl}</div>
)}
</div> </div>
<NotificationTypeSelector </div>
currentTypes={values.types} <NotificationTypeSelector
onUpdate={(newTypes) => setFieldValue('types', newTypes)} currentTypes={values.types}
/> onUpdate={(newTypes) => setFieldValue('types', newTypes)}
<div className="actions"> />
<div className="flex justify-end"> <div className="actions">
<span className="inline-flex ml-3 rounded-md shadow-sm"> <div className="flex justify-end">
<Button <span className="inline-flex ml-3 rounded-md shadow-sm">
buttonType="warning" <Button
disabled={isSubmitting || !isValid || isTesting} buttonType="warning"
onClick={(e) => { disabled={isSubmitting || !isValid || isTesting}
e.preventDefault(); onClick={(e) => {
testSettings(); e.preventDefault();
}} testSettings();
> }}
{isTesting >
? intl.formatMessage(globalMessages.testing) {isTesting
: intl.formatMessage(globalMessages.test)} ? intl.formatMessage(globalMessages.testing)
</Button> : intl.formatMessage(globalMessages.test)}
</span> </Button>
<span className="inline-flex ml-3 rounded-md shadow-sm"> </span>
<Button <span className="inline-flex ml-3 rounded-md shadow-sm">
buttonType="primary" <Button
type="submit" buttonType="primary"
disabled={isSubmitting || !isValid || isTesting} type="submit"
> disabled={isSubmitting || !isValid || isTesting}
{isSubmitting >
? intl.formatMessage(globalMessages.saving) {isSubmitting
: intl.formatMessage(globalMessages.save)} ? intl.formatMessage(globalMessages.saving)
</Button> : intl.formatMessage(globalMessages.save)}
</span> </Button>
</div> </span>
</div> </div>
</Form> </div>
); </Form>
}} );
</Formik> }}
</> </Formik>
); );
}; };

@ -6,7 +6,6 @@ import { useToasts } from 'react-toast-notifications';
import useSWR from 'swr'; import useSWR from 'swr';
import * as Yup from 'yup'; import * as Yup from 'yup';
import globalMessages from '../../../i18n/globalMessages'; import globalMessages from '../../../i18n/globalMessages';
import Alert from '../../Common/Alert';
import Button from '../../Common/Button'; import Button from '../../Common/Button';
import LoadingSpinner from '../../Common/LoadingSpinner'; import LoadingSpinner from '../../Common/LoadingSpinner';
import SensitiveInput from '../../Common/SensitiveInput'; import SensitiveInput from '../../Common/SensitiveInput';
@ -16,18 +15,20 @@ const messages = defineMessages({
agentenabled: 'Enable Agent', agentenabled: 'Enable Agent',
botUsername: 'Bot Username', botUsername: 'Bot Username',
botUsernameTip: botUsernameTip:
'Allow users to start a chat with the bot and configure their own personal notifications', 'Allow users to also start a chat with your bot and configure their own notifications',
botAPI: 'Bot Authentication Token', botAPI: 'Bot Authorization Token',
botApiTip:
'<CreateBotLink>Create a bot</CreateBotLink> for use with Overseerr',
chatId: 'Chat ID', chatId: 'Chat ID',
validationBotAPIRequired: 'You must provide a bot authentication token', chatIdTip:
'Start a chat with your bot, add <GetIdBotLink>@get_id_bot</GetIdBotLink>, and issue the <code>/my_id</code> command',
validationBotAPIRequired: 'You must provide a bot authorization token',
validationChatIdRequired: 'You must provide a valid chat ID', validationChatIdRequired: 'You must provide a valid chat ID',
telegramsettingssaved: 'Telegram notification settings saved successfully!', telegramsettingssaved: 'Telegram notification settings saved successfully!',
telegramsettingsfailed: 'Telegram notification settings failed to save.', telegramsettingsfailed: 'Telegram notification settings failed to save.',
toastTelegramTestSending: 'Sending Telegram test notification…', toastTelegramTestSending: 'Sending Telegram test notification…',
toastTelegramTestSuccess: 'Telegram test notification sent!', toastTelegramTestSuccess: 'Telegram test notification sent!',
toastTelegramTestFailed: 'Telegram test notification failed to send.', toastTelegramTestFailed: 'Telegram test notification failed to send.',
settinguptelegramDescription:
'To configure Telegram notifications, you will need to <CreateBotLink>create a bot</CreateBotLink> and get the bot API key. Additionally, you will need the chat ID for the chat to which you would like to send notifications. You can find this by adding <GetIdBotLink>@get_id_bot</GetIdBotLink> to the chat and issuing the <code>/my_id</code> command.',
sendSilently: 'Send Silently', sendSilently: 'Send Silently',
sendSilentlyTip: 'Send notifications with no sound', sendSilentlyTip: 'Send notifications with no sound',
}); });
@ -151,158 +152,141 @@ const NotificationsTelegram: React.FC = () => {
}; };
return ( return (
<> <Form className="section">
<Alert <div className="form-row">
title={intl.formatMessage(messages.settinguptelegramDescription, { <label htmlFor="enabled" className="checkbox-label">
CreateBotLink: function CreateBotLink(msg) { {intl.formatMessage(messages.agentenabled)}
return ( <span className="label-required">*</span>
<a </label>
href="https://core.telegram.org/bots#6-botfather" <div className="form-input">
className="text-white transition duration-300 hover:underline" <Field type="checkbox" id="enabled" name="enabled" />
target="_blank"
rel="noreferrer"
>
{msg}
</a>
);
},
GetIdBotLink: function GetIdBotLink(msg) {
return (
<a
href="https://telegram.me/get_id_bot"
className="text-white transition duration-300 hover:underline"
target="_blank"
rel="noreferrer"
>
{msg}
</a>
);
},
code: function code(msg) {
return <code className="bg-opacity-50">{msg}</code>;
},
})}
type="info"
/>
<Form className="section">
<div className="form-row">
<label htmlFor="enabled" className="checkbox-label">
{intl.formatMessage(messages.agentenabled)}
</label>
<div className="form-input">
<Field type="checkbox" id="enabled" name="enabled" />
</div>
</div> </div>
<div className="form-row"> </div>
<label htmlFor="botUsername" className="text-label"> <div className="form-row">
{intl.formatMessage(messages.botUsername)} <label htmlFor="botAPI" className="text-label">
<span className="label-tip"> {intl.formatMessage(messages.botAPI)}
{intl.formatMessage(messages.botUsernameTip)} <span className="label-required">*</span>
</span> <span className="label-tip">
</label> {intl.formatMessage(messages.botApiTip, {
<div className="form-input"> CreateBotLink: function CreateBotLink(msg) {
<div className="form-input-field"> return (
<Field <a
id="botUsername" href="https://core.telegram.org/bots#6-botfather"
name="botUsername" className="text-white transition duration-300 hover:underline"
type="text" target="_blank"
placeholder={intl.formatMessage(messages.botUsername)} rel="noreferrer"
/> >
</div> {msg}
{errors.botUsername && touched.botUsername && ( </a>
<div className="error">{errors.botUsername}</div> );
)} },
GetIdBotLink: function GetIdBotLink(msg) {
return (
<a
href="https://telegram.me/get_id_bot"
className="text-white transition duration-300 hover:underline"
target="_blank"
rel="noreferrer"
>
{msg}
</a>
);
},
code: function code(msg) {
return <code className="bg-opacity-50">{msg}</code>;
},
})}
</span>
</label>
<div className="form-input">
<div className="form-input-field">
<SensitiveInput
as="field"
id="botAPI"
name="botAPI"
autoComplete="one-time-code"
/>
</div> </div>
{errors.botAPI && touched.botAPI && (
<div className="error">{errors.botAPI}</div>
)}
</div> </div>
<div className="form-row"> </div>
<label htmlFor="botAPI" className="text-label"> <div className="form-row">
{intl.formatMessage(messages.botAPI)} <label htmlFor="botUsername" className="text-label">
<span className="label-required">*</span> {intl.formatMessage(messages.botUsername)}
</label> <span className="label-tip">
<div className="form-input"> {intl.formatMessage(messages.botUsernameTip)}
<div className="form-input-field"> </span>
<SensitiveInput </label>
as="field" <div className="form-input">
id="botAPI" <div className="form-input-field">
name="botAPI" <Field id="botUsername" name="botUsername" type="text" />
type="text"
placeholder={intl.formatMessage(messages.botAPI)}
/>
</div>
{errors.botAPI && touched.botAPI && (
<div className="error">{errors.botAPI}</div>
)}
</div> </div>
{errors.botUsername && touched.botUsername && (
<div className="error">{errors.botUsername}</div>
)}
</div> </div>
<div className="form-row"> </div>
<label htmlFor="chatId" className="text-label"> <div className="form-row">
{intl.formatMessage(messages.chatId)} <label htmlFor="chatId" className="text-label">
<span className="label-required">*</span> {intl.formatMessage(messages.chatId)}
</label> <span className="label-required">*</span>
<div className="form-input"> </label>
<div className="form-input-field"> <div className="form-input">
<Field <div className="form-input-field">
id="chatId" <Field id="chatId" name="chatId" type="text" />
name="chatId"
type="text"
placeholder={intl.formatMessage(messages.chatId)}
/>
</div>
{errors.chatId && touched.chatId && (
<div className="error">{errors.chatId}</div>
)}
</div> </div>
{errors.chatId && touched.chatId && (
<div className="error">{errors.chatId}</div>
)}
</div> </div>
<div className="form-row"> </div>
<label htmlFor="sendSilently" className="checkbox-label"> <div className="form-row">
<span>{intl.formatMessage(messages.sendSilently)}</span> <label htmlFor="sendSilently" className="checkbox-label">
<span className="label-tip"> <span>{intl.formatMessage(messages.sendSilently)}</span>
{intl.formatMessage(messages.sendSilentlyTip)} <span className="label-tip">
</span> {intl.formatMessage(messages.sendSilentlyTip)}
</label> </span>
<div className="form-input"> </label>
<Field <div className="form-input">
type="checkbox" <Field type="checkbox" id="sendSilently" name="sendSilently" />
id="sendSilently"
name="sendSilently"
/>
</div>
</div> </div>
<NotificationTypeSelector </div>
currentTypes={values.types} <NotificationTypeSelector
onUpdate={(newTypes) => setFieldValue('types', newTypes)} currentTypes={values.types}
/> onUpdate={(newTypes) => setFieldValue('types', newTypes)}
<div className="actions"> />
<div className="flex justify-end"> <div className="actions">
<span className="inline-flex ml-3 rounded-md shadow-sm"> <div className="flex justify-end">
<Button <span className="inline-flex ml-3 rounded-md shadow-sm">
buttonType="warning" <Button
disabled={isSubmitting || !isValid || isTesting} buttonType="warning"
onClick={(e) => { disabled={isSubmitting || !isValid || isTesting}
e.preventDefault(); onClick={(e) => {
testSettings(); e.preventDefault();
}} testSettings();
> }}
{isTesting >
? intl.formatMessage(globalMessages.testing) {isTesting
: intl.formatMessage(globalMessages.test)} ? intl.formatMessage(globalMessages.testing)
</Button> : intl.formatMessage(globalMessages.test)}
</span> </Button>
<span className="inline-flex ml-3 rounded-md shadow-sm"> </span>
<Button <span className="inline-flex ml-3 rounded-md shadow-sm">
buttonType="primary" <Button
type="submit" buttonType="primary"
disabled={isSubmitting || !isValid || isTesting} type="submit"
> disabled={isSubmitting || !isValid || isTesting}
{isSubmitting >
? intl.formatMessage(globalMessages.saving) {isSubmitting
: intl.formatMessage(globalMessages.save)} ? intl.formatMessage(globalMessages.saving)
</Button> : intl.formatMessage(globalMessages.save)}
</span> </Button>
</div> </span>
</div> </div>
</Form> </div>
</> </Form>
); );
}} }}
</Formik> </Formik>

@ -105,6 +105,7 @@ const NotificationsWebPush: React.FC = () => {
<div className="form-row"> <div className="form-row">
<label htmlFor="enabled" className="checkbox-label"> <label htmlFor="enabled" className="checkbox-label">
{intl.formatMessage(messages.agentenabled)} {intl.formatMessage(messages.agentenabled)}
<span className="label-required">*</span>
</label> </label>
<div className="form-input"> <div className="form-input">
<Field type="checkbox" id="enabled" name="enabled" /> <Field type="checkbox" id="enabled" name="enabled" />

@ -209,6 +209,7 @@ const NotificationsWebhook: React.FC = () => {
<div className="form-row"> <div className="form-row">
<label htmlFor="enabled" className="checkbox-label"> <label htmlFor="enabled" className="checkbox-label">
{intl.formatMessage(messages.agentenabled)} {intl.formatMessage(messages.agentenabled)}
<span className="label-required">*</span>
</label> </label>
<div className="form-input"> <div className="form-input">
<Field type="checkbox" id="enabled" name="enabled" /> <Field type="checkbox" id="enabled" name="enabled" />
@ -221,7 +222,12 @@ const NotificationsWebhook: React.FC = () => {
</label> </label>
<div className="form-input"> <div className="form-input">
<div className="form-input-field"> <div className="form-input-field">
<Field id="webhookUrl" name="webhookUrl" type="text" /> <Field
id="webhookUrl"
name="webhookUrl"
type="text"
inputMode="url"
/>
</div> </div>
{errors.webhookUrl && touched.webhookUrl && ( {errors.webhookUrl && touched.webhookUrl && (
<div className="error">{errors.webhookUrl}</div> <div className="error">{errors.webhookUrl}</div>

@ -39,17 +39,13 @@ const messages = defineMessages({
defaultserver: 'Default Server', defaultserver: 'Default Server',
default4kserver: 'Default 4K Server', default4kserver: 'Default 4K Server',
servername: 'Server Name', servername: 'Server Name',
servernamePlaceholder: 'A Radarr Server',
hostname: 'Hostname or IP Address', hostname: 'Hostname or IP Address',
port: 'Port', port: 'Port',
ssl: 'Enable SSL', ssl: 'Enable SSL',
apiKey: 'API Key', apiKey: 'API Key',
apiKeyPlaceholder: 'Your Radarr API key', baseUrl: 'URL Base',
baseUrl: 'Base URL',
baseUrlPlaceholder: 'Example: /radarr',
syncEnabled: 'Enable Scan', syncEnabled: 'Enable Scan',
externalUrl: 'External URL', externalUrl: 'External URL',
externalUrlPlaceholder: 'External URL pointing to your Radarr server',
qualityprofile: 'Quality Profile', qualityprofile: 'Quality Profile',
rootfolder: 'Root Folder', rootfolder: 'Root Folder',
minimumAvailability: 'Minimum Availability', minimumAvailability: 'Minimum Availability',
@ -247,7 +243,7 @@ const RadarrModal: React.FC<RadarrModalProps> = ({
initialValues={{ initialValues={{
name: radarr?.name, name: radarr?.name,
hostname: radarr?.hostname, hostname: radarr?.hostname,
port: radarr?.port, port: radarr?.port ?? 7878,
ssl: radarr?.useSsl ?? false, ssl: radarr?.useSsl ?? false,
apiKey: radarr?.apiKey, apiKey: radarr?.apiKey,
baseUrl: radarr?.baseUrl, baseUrl: radarr?.baseUrl,
@ -392,9 +388,6 @@ const RadarrModal: React.FC<RadarrModalProps> = ({
id="name" id="name"
name="name" name="name"
type="text" type="text"
placeholder={intl.formatMessage(
messages.servernamePlaceholder
)}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => { onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setIsValidated(false); setIsValidated(false);
setFieldValue('name', e.target.value); setFieldValue('name', e.target.value);
@ -420,7 +413,7 @@ const RadarrModal: React.FC<RadarrModalProps> = ({
id="hostname" id="hostname"
name="hostname" name="hostname"
type="text" type="text"
placeholder="127.0.0.1" inputMode="url"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => { onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setIsValidated(false); setIsValidated(false);
setFieldValue('hostname', e.target.value); setFieldValue('hostname', e.target.value);
@ -443,7 +436,7 @@ const RadarrModal: React.FC<RadarrModalProps> = ({
id="port" id="port"
name="port" name="port"
type="text" type="text"
placeholder="7878" inputMode="numeric"
className="short" className="short"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => { onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setIsValidated(false); setIsValidated(false);
@ -482,10 +475,7 @@ const RadarrModal: React.FC<RadarrModalProps> = ({
as="field" as="field"
id="apiKey" id="apiKey"
name="apiKey" name="apiKey"
type="text" autoComplete="one-time-code"
placeholder={intl.formatMessage(
messages.apiKeyPlaceholder
)}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => { onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setIsValidated(false); setIsValidated(false);
setFieldValue('apiKey', e.target.value); setFieldValue('apiKey', e.target.value);
@ -507,9 +497,7 @@ const RadarrModal: React.FC<RadarrModalProps> = ({
id="baseUrl" id="baseUrl"
name="baseUrl" name="baseUrl"
type="text" type="text"
placeholder={intl.formatMessage( inputMode="url"
messages.baseUrlPlaceholder
)}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => { onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setIsValidated(false); setIsValidated(false);
setFieldValue('baseUrl', e.target.value); setFieldValue('baseUrl', e.target.value);
@ -682,9 +670,7 @@ const RadarrModal: React.FC<RadarrModalProps> = ({
id="externalUrl" id="externalUrl"
name="externalUrl" name="externalUrl"
type="text" type="text"
placeholder={intl.formatMessage( inputMode="url"
messages.externalUrlPlaceholder
)}
/> />
</div> </div>
{errors.externalUrl && touched.externalUrl && ( {errors.externalUrl && touched.externalUrl && (

@ -222,7 +222,6 @@ const SettingsMain: React.FC = () => {
id="applicationTitle" id="applicationTitle"
name="applicationTitle" name="applicationTitle"
type="text" type="text"
placeholder="Overseerr"
/> />
</div> </div>
{errors.applicationTitle && touched.applicationTitle && ( {errors.applicationTitle && touched.applicationTitle && (
@ -240,7 +239,7 @@ const SettingsMain: React.FC = () => {
id="applicationUrl" id="applicationUrl"
name="applicationUrl" name="applicationUrl"
type="text" type="text"
placeholder="https://os.example.com" inputMode="url"
/> />
</div> </div>
{errors.applicationUrl && touched.applicationUrl && ( {errors.applicationUrl && touched.applicationUrl && (

@ -24,9 +24,7 @@ const messages = defineMessages({
'Configure the settings for your Plex server. Overseerr scans your Plex libraries to determine content availability.', 'Configure the settings for your Plex server. Overseerr scans your Plex libraries to determine content availability.',
servername: 'Server Name', servername: 'Server Name',
servernameTip: 'Automatically retrieved from Plex after saving', servernameTip: 'Automatically retrieved from Plex after saving',
servernamePlaceholder: 'Plex Server Name',
serverpreset: 'Server', serverpreset: 'Server',
serverpresetPlaceholder: 'Plex Server',
serverLocal: 'local', serverLocal: 'local',
serverRemote: 'remote', serverRemote: 'remote',
serverSecure: 'secure', serverSecure: 'secure',
@ -281,7 +279,7 @@ const SettingsPlex: React.FC<SettingsPlexProps> = ({ onComplete }) => {
<Formik <Formik
initialValues={{ initialValues={{
hostname: data?.ip, hostname: data?.ip,
port: data?.port, port: data?.port ?? 32400,
useSsl: data?.useSsl, useSsl: data?.useSsl,
selectedPreset: undefined, selectedPreset: undefined,
}} }}
@ -354,9 +352,6 @@ const SettingsPlex: React.FC<SettingsPlexProps> = ({ onComplete }) => {
id="name" id="name"
name="name" name="name"
className="cursor-not-allowed" className="cursor-not-allowed"
placeholder={intl.formatMessage(
messages.servernamePlaceholder
)}
value={data?.name} value={data?.name}
readOnly readOnly
/> />
@ -372,9 +367,6 @@ const SettingsPlex: React.FC<SettingsPlexProps> = ({ onComplete }) => {
<select <select
id="preset" id="preset"
name="preset" name="preset"
placeholder={intl.formatMessage(
messages.serverpresetPlaceholder
)}
value={values.selectedPreset} value={values.selectedPreset}
disabled={!availableServers || isRefreshingPresets} disabled={!availableServers || isRefreshingPresets}
className="rounded-l-only" className="rounded-l-only"
@ -453,9 +445,9 @@ const SettingsPlex: React.FC<SettingsPlexProps> = ({ onComplete }) => {
</span> </span>
<Field <Field
type="text" type="text"
inputMode="url"
id="hostname" id="hostname"
name="hostname" name="hostname"
placeholder="127.0.0.1"
className="rounded-r-only" className="rounded-r-only"
/> />
</div> </div>
@ -472,9 +464,9 @@ const SettingsPlex: React.FC<SettingsPlexProps> = ({ onComplete }) => {
<div className="form-input"> <div className="form-input">
<Field <Field
type="text" type="text"
inputMode="numeric"
id="port" id="port"
name="port" name="port"
placeholder="32400"
className="short" className="short"
/> />
{errors.port && touched.port && ( {errors.port && touched.port && (

@ -38,14 +38,11 @@ const messages = defineMessages({
defaultserver: 'Default Server', defaultserver: 'Default Server',
default4kserver: 'Default 4K Server', default4kserver: 'Default 4K Server',
servername: 'Server Name', servername: 'Server Name',
servernamePlaceholder: 'A Sonarr Server',
hostname: 'Hostname or IP Address', hostname: 'Hostname or IP Address',
port: 'Port', port: 'Port',
ssl: 'Enable SSL', ssl: 'Enable SSL',
apiKey: 'API Key', apiKey: 'API Key',
apiKeyPlaceholder: 'Your Sonarr API key', baseUrl: 'URL Base',
baseUrl: 'Base URL',
baseUrlPlaceholder: 'Example: /sonarr',
qualityprofile: 'Quality Profile', qualityprofile: 'Quality Profile',
languageprofile: 'Language Profile', languageprofile: 'Language Profile',
rootfolder: 'Root Folder', rootfolder: 'Root Folder',
@ -67,7 +64,6 @@ const messages = defineMessages({
testFirstTags: 'Test connection to load tags', testFirstTags: 'Test connection to load tags',
syncEnabled: 'Enable Scan', syncEnabled: 'Enable Scan',
externalUrl: 'External URL', externalUrl: 'External URL',
externalUrlPlaceholder: 'External URL pointing to your Sonarr server',
enableSearch: 'Enable Automatic Search', enableSearch: 'Enable Automatic Search',
validationApplicationUrl: 'You must provide a valid URL', validationApplicationUrl: 'You must provide a valid URL',
validationApplicationUrlTrailingSlash: 'URL must not end in a trailing slash', validationApplicationUrlTrailingSlash: 'URL must not end in a trailing slash',
@ -258,7 +254,7 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
initialValues={{ initialValues={{
name: sonarr?.name, name: sonarr?.name,
hostname: sonarr?.hostname, hostname: sonarr?.hostname,
port: sonarr?.port, port: sonarr?.port ?? 8989,
ssl: sonarr?.useSsl ?? false, ssl: sonarr?.useSsl ?? false,
apiKey: sonarr?.apiKey, apiKey: sonarr?.apiKey,
baseUrl: sonarr?.baseUrl, baseUrl: sonarr?.baseUrl,
@ -423,9 +419,6 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
id="name" id="name"
name="name" name="name"
type="text" type="text"
placeholder={intl.formatMessage(
messages.servernamePlaceholder
)}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => { onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setIsValidated(false); setIsValidated(false);
setFieldValue('name', e.target.value); setFieldValue('name', e.target.value);
@ -451,7 +444,7 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
id="hostname" id="hostname"
name="hostname" name="hostname"
type="text" type="text"
placeholder="127.0.0.1" inputMode="url"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => { onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setIsValidated(false); setIsValidated(false);
setFieldValue('hostname', e.target.value); setFieldValue('hostname', e.target.value);
@ -474,7 +467,7 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
id="port" id="port"
name="port" name="port"
type="text" type="text"
placeholder="8989" inputMode="numeric"
className="short" className="short"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => { onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setIsValidated(false); setIsValidated(false);
@ -513,10 +506,7 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
as="field" as="field"
id="apiKey" id="apiKey"
name="apiKey" name="apiKey"
type="text" autoComplete="one-time-code"
placeholder={intl.formatMessage(
messages.apiKeyPlaceholder
)}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => { onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setIsValidated(false); setIsValidated(false);
setFieldValue('apiKey', e.target.value); setFieldValue('apiKey', e.target.value);
@ -538,9 +528,7 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
id="baseUrl" id="baseUrl"
name="baseUrl" name="baseUrl"
type="text" type="text"
placeholder={intl.formatMessage( inputMode="url"
messages.baseUrlPlaceholder
)}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => { onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setIsValidated(false); setIsValidated(false);
setFieldValue('baseUrl', e.target.value); setFieldValue('baseUrl', e.target.value);
@ -934,9 +922,7 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
id="externalUrl" id="externalUrl"
name="externalUrl" name="externalUrl"
type="text" type="text"
placeholder={intl.formatMessage( inputMode="url"
messages.externalUrlPlaceholder
)}
/> />
</div> </div>
{errors.externalUrl && touched.externalUrl && ( {errors.externalUrl && touched.externalUrl && (

@ -364,7 +364,7 @@ const UserList: React.FC = () => {
id="email" id="email"
name="email" name="email"
type="text" type="text"
placeholder="name@example.com" inputMode="email"
/> />
</div> </div>
{errors.email && touched.email && ( {errors.email && touched.email && (
@ -404,6 +404,7 @@ const UserList: React.FC = () => {
<div className="form-input"> <div className="form-input">
<div className="form-input-field"> <div className="form-input-field">
<SensitiveInput <SensitiveInput
as="field"
id="password" id="password"
name="password" name="password"
type="password" type="password"

@ -258,28 +258,29 @@
"components.Settings.Notifications.NotificationsLunaSea.toastLunaSeaTestSuccess": "LunaSea test notification sent!", "components.Settings.Notifications.NotificationsLunaSea.toastLunaSeaTestSuccess": "LunaSea test notification sent!",
"components.Settings.Notifications.NotificationsLunaSea.validationWebhookUrl": "You must provide a valid URL", "components.Settings.Notifications.NotificationsLunaSea.validationWebhookUrl": "You must provide a valid URL",
"components.Settings.Notifications.NotificationsLunaSea.webhookUrl": "Webhook URL", "components.Settings.Notifications.NotificationsLunaSea.webhookUrl": "Webhook URL",
"components.Settings.Notifications.NotificationsLunaSea.webhookUrlTip": "Your user- or device-based <LunaSeaLink>notification webhook URL</LunaSeaLink>",
"components.Settings.Notifications.NotificationsPushbullet.accessToken": "Access Token", "components.Settings.Notifications.NotificationsPushbullet.accessToken": "Access Token",
"components.Settings.Notifications.NotificationsPushbullet.accessTokenTip": "Create a token from your <PushbulletSettingsLink>Account Settings</PushbulletSettingsLink>",
"components.Settings.Notifications.NotificationsPushbullet.agentEnabled": "Enable Agent", "components.Settings.Notifications.NotificationsPushbullet.agentEnabled": "Enable Agent",
"components.Settings.Notifications.NotificationsPushbullet.pushbulletSettingsFailed": "Pushbullet notification settings failed to save.", "components.Settings.Notifications.NotificationsPushbullet.pushbulletSettingsFailed": "Pushbullet notification settings failed to save.",
"components.Settings.Notifications.NotificationsPushbullet.pushbulletSettingsSaved": "Pushbullet notification settings saved successfully!", "components.Settings.Notifications.NotificationsPushbullet.pushbulletSettingsSaved": "Pushbullet notification settings saved successfully!",
"components.Settings.Notifications.NotificationsPushbullet.settingUpPushbulletDescription": "To configure Pushbullet notifications, you will need to <CreateAccessTokenLink>create an access token</CreateAccessTokenLink>.",
"components.Settings.Notifications.NotificationsPushbullet.toastPushbulletTestFailed": "Pushbullet test notification failed to send.", "components.Settings.Notifications.NotificationsPushbullet.toastPushbulletTestFailed": "Pushbullet test notification failed to send.",
"components.Settings.Notifications.NotificationsPushbullet.toastPushbulletTestSending": "Sending Pushbullet test notification…", "components.Settings.Notifications.NotificationsPushbullet.toastPushbulletTestSending": "Sending Pushbullet test notification…",
"components.Settings.Notifications.NotificationsPushbullet.toastPushbulletTestSuccess": "Pushbullet test notification sent!", "components.Settings.Notifications.NotificationsPushbullet.toastPushbulletTestSuccess": "Pushbullet test notification sent!",
"components.Settings.Notifications.NotificationsPushbullet.validationAccessTokenRequired": "You must provide an access token", "components.Settings.Notifications.NotificationsPushbullet.validationAccessTokenRequired": "You must provide an access token",
"components.Settings.Notifications.NotificationsPushover.accessToken": "Application/API Token", "components.Settings.Notifications.NotificationsPushover.accessToken": "Application API Token",
"components.Settings.Notifications.NotificationsPushover.accessTokenTip": "<ApplicationRegistrationLink>Register an application</ApplicationRegistrationLink> for use with Overseerr",
"components.Settings.Notifications.NotificationsPushover.agentenabled": "Enable Agent", "components.Settings.Notifications.NotificationsPushover.agentenabled": "Enable Agent",
"components.Settings.Notifications.NotificationsPushover.pushoversettingsfailed": "Pushover notification settings failed to save.", "components.Settings.Notifications.NotificationsPushover.pushoversettingsfailed": "Pushover notification settings failed to save.",
"components.Settings.Notifications.NotificationsPushover.pushoversettingssaved": "Pushover notification settings saved successfully!", "components.Settings.Notifications.NotificationsPushover.pushoversettingssaved": "Pushover notification settings saved successfully!",
"components.Settings.Notifications.NotificationsPushover.settinguppushoverDescription": "To configure Pushover notifications, you will need to <RegisterApplicationLink>register an application</RegisterApplicationLink>. (You can use one of the <IconLink>official Overseerr icons on GitHub</IconLink>.)",
"components.Settings.Notifications.NotificationsPushover.toastPushoverTestFailed": "Pushover test notification failed to send.", "components.Settings.Notifications.NotificationsPushover.toastPushoverTestFailed": "Pushover test notification failed to send.",
"components.Settings.Notifications.NotificationsPushover.toastPushoverTestSending": "Sending Pushover test notification…", "components.Settings.Notifications.NotificationsPushover.toastPushoverTestSending": "Sending Pushover test notification…",
"components.Settings.Notifications.NotificationsPushover.toastPushoverTestSuccess": "Pushover test notification sent!", "components.Settings.Notifications.NotificationsPushover.toastPushoverTestSuccess": "Pushover test notification sent!",
"components.Settings.Notifications.NotificationsPushover.userToken": "User or Group Key", "components.Settings.Notifications.NotificationsPushover.userToken": "User or Group Key",
"components.Settings.Notifications.NotificationsPushover.userTokenTip": "Your 30-character <UsersGroupsLink>user or group identifier</UsersGroupsLink>",
"components.Settings.Notifications.NotificationsPushover.validationAccessTokenRequired": "You must provide a valid application token", "components.Settings.Notifications.NotificationsPushover.validationAccessTokenRequired": "You must provide a valid application token",
"components.Settings.Notifications.NotificationsPushover.validationUserTokenRequired": "You must provide a valid user key", "components.Settings.Notifications.NotificationsPushover.validationUserTokenRequired": "You must provide a valid user key",
"components.Settings.Notifications.NotificationsSlack.agentenabled": "Enable Agent", "components.Settings.Notifications.NotificationsSlack.agentenabled": "Enable Agent",
"components.Settings.Notifications.NotificationsSlack.settingupslackDescription": "To configure Slack notifications, you will need to create an <WebhookLink>Incoming Webhook</WebhookLink> integration and enter the webhook URL below.",
"components.Settings.Notifications.NotificationsSlack.slacksettingsfailed": "Slack notification settings failed to save.", "components.Settings.Notifications.NotificationsSlack.slacksettingsfailed": "Slack notification settings failed to save.",
"components.Settings.Notifications.NotificationsSlack.slacksettingssaved": "Slack notification settings saved successfully!", "components.Settings.Notifications.NotificationsSlack.slacksettingssaved": "Slack notification settings saved successfully!",
"components.Settings.Notifications.NotificationsSlack.toastSlackTestFailed": "Slack test notification failed to send.", "components.Settings.Notifications.NotificationsSlack.toastSlackTestFailed": "Slack test notification failed to send.",
@ -287,6 +288,7 @@
"components.Settings.Notifications.NotificationsSlack.toastSlackTestSuccess": "Slack test notification sent!", "components.Settings.Notifications.NotificationsSlack.toastSlackTestSuccess": "Slack test notification sent!",
"components.Settings.Notifications.NotificationsSlack.validationWebhookUrl": "You must provide a valid URL", "components.Settings.Notifications.NotificationsSlack.validationWebhookUrl": "You must provide a valid URL",
"components.Settings.Notifications.NotificationsSlack.webhookUrl": "Webhook URL", "components.Settings.Notifications.NotificationsSlack.webhookUrl": "Webhook URL",
"components.Settings.Notifications.NotificationsSlack.webhookUrlTip": "Create an <WebhookLink>Incoming Webhook</WebhookLink> integration",
"components.Settings.Notifications.NotificationsWebPush.agentenabled": "Enable Agent", "components.Settings.Notifications.NotificationsWebPush.agentenabled": "Enable Agent",
"components.Settings.Notifications.NotificationsWebPush.toastWebPushTestFailed": "Web push test notification failed to send.", "components.Settings.Notifications.NotificationsWebPush.toastWebPushTestFailed": "Web push test notification failed to send.",
"components.Settings.Notifications.NotificationsWebPush.toastWebPushTestSending": "Sending web push test notification…", "components.Settings.Notifications.NotificationsWebPush.toastWebPushTestSending": "Sending web push test notification…",
@ -311,11 +313,13 @@
"components.Settings.Notifications.allowselfsigned": "Allow Self-Signed Certificates", "components.Settings.Notifications.allowselfsigned": "Allow Self-Signed Certificates",
"components.Settings.Notifications.authPass": "SMTP Password", "components.Settings.Notifications.authPass": "SMTP Password",
"components.Settings.Notifications.authUser": "SMTP Username", "components.Settings.Notifications.authUser": "SMTP Username",
"components.Settings.Notifications.botAPI": "Bot Authentication Token", "components.Settings.Notifications.botAPI": "Bot Authorization Token",
"components.Settings.Notifications.botApiTip": "<CreateBotLink>Create a bot</CreateBotLink> for use with Overseerr",
"components.Settings.Notifications.botAvatarUrl": "Bot Avatar URL", "components.Settings.Notifications.botAvatarUrl": "Bot Avatar URL",
"components.Settings.Notifications.botUsername": "Bot Username", "components.Settings.Notifications.botUsername": "Bot Username",
"components.Settings.Notifications.botUsernameTip": "Allow users to start a chat with the bot and configure their own personal notifications", "components.Settings.Notifications.botUsernameTip": "Allow users to also start a chat with your bot and configure their own notifications",
"components.Settings.Notifications.chatId": "Chat ID", "components.Settings.Notifications.chatId": "Chat ID",
"components.Settings.Notifications.chatIdTip": "Start a chat with your bot, add <GetIdBotLink>@get_id_bot</GetIdBotLink>, and issue the <code>/my_id</code> command",
"components.Settings.Notifications.discordsettingsfailed": "Discord notification settings failed to save.", "components.Settings.Notifications.discordsettingsfailed": "Discord notification settings failed to save.",
"components.Settings.Notifications.discordsettingssaved": "Discord notification settings saved successfully!", "components.Settings.Notifications.discordsettingssaved": "Discord notification settings saved successfully!",
"components.Settings.Notifications.emailNotificationTypesAlertDescription": "<strong>Media Requested</strong>, <strong>Media Automatically Approved</strong>, and <strong>Media Failed</strong> email notifications are sent to all users with the <strong>Manage Requests</strong> permission.", "components.Settings.Notifications.emailNotificationTypesAlertDescription": "<strong>Media Requested</strong>, <strong>Media Automatically Approved</strong>, and <strong>Media Failed</strong> email notifications are sent to all users with the <strong>Manage Requests</strong> permission.",
@ -331,7 +335,6 @@
"components.Settings.Notifications.sendSilently": "Send Silently", "components.Settings.Notifications.sendSilently": "Send Silently",
"components.Settings.Notifications.sendSilentlyTip": "Send notifications with no sound", "components.Settings.Notifications.sendSilentlyTip": "Send notifications with no sound",
"components.Settings.Notifications.senderName": "Sender Name", "components.Settings.Notifications.senderName": "Sender Name",
"components.Settings.Notifications.settinguptelegramDescription": "To configure Telegram notifications, you will need to <CreateBotLink>create a bot</CreateBotLink> and get the bot API key. Additionally, you will need the chat ID for the chat to which you would like to send notifications. You can find this by adding <GetIdBotLink>@get_id_bot</GetIdBotLink> to the chat and issuing the <code>/my_id</code> command.",
"components.Settings.Notifications.smtpHost": "SMTP Host", "components.Settings.Notifications.smtpHost": "SMTP Host",
"components.Settings.Notifications.smtpPort": "SMTP Port", "components.Settings.Notifications.smtpPort": "SMTP Port",
"components.Settings.Notifications.ssldisabletip": "SSL should be disabled on standard TLS connections (port 587)", "components.Settings.Notifications.ssldisabletip": "SSL should be disabled on standard TLS connections (port 587)",
@ -346,7 +349,7 @@
"components.Settings.Notifications.toastTelegramTestFailed": "Telegram test notification failed to send.", "components.Settings.Notifications.toastTelegramTestFailed": "Telegram test notification failed to send.",
"components.Settings.Notifications.toastTelegramTestSending": "Sending Telegram test notification…", "components.Settings.Notifications.toastTelegramTestSending": "Sending Telegram test notification…",
"components.Settings.Notifications.toastTelegramTestSuccess": "Telegram test notification sent!", "components.Settings.Notifications.toastTelegramTestSuccess": "Telegram test notification sent!",
"components.Settings.Notifications.validationBotAPIRequired": "You must provide a bot authentication token", "components.Settings.Notifications.validationBotAPIRequired": "You must provide a bot authorization token",
"components.Settings.Notifications.validationChatIdRequired": "You must provide a valid chat ID", "components.Settings.Notifications.validationChatIdRequired": "You must provide a valid chat ID",
"components.Settings.Notifications.validationEmail": "You must provide a valid email address", "components.Settings.Notifications.validationEmail": "You must provide a valid email address",
"components.Settings.Notifications.validationPgpPassword": "You must provide a PGP password if a PGP private key is entered", "components.Settings.Notifications.validationPgpPassword": "You must provide a PGP password if a PGP private key is entered",
@ -355,12 +358,10 @@
"components.Settings.Notifications.validationSmtpPortRequired": "You must provide a valid port number", "components.Settings.Notifications.validationSmtpPortRequired": "You must provide a valid port number",
"components.Settings.Notifications.validationUrl": "You must provide a valid URL", "components.Settings.Notifications.validationUrl": "You must provide a valid URL",
"components.Settings.Notifications.webhookUrl": "Webhook URL", "components.Settings.Notifications.webhookUrl": "Webhook URL",
"components.Settings.Notifications.webhookUrlPlaceholder": "Server Settings → Integrations → Webhooks", "components.Settings.Notifications.webhookUrlTip": "Create a <DiscordWebhookLink>webhook integration</DiscordWebhookLink> in your server",
"components.Settings.RadarrModal.add": "Add Server", "components.Settings.RadarrModal.add": "Add Server",
"components.Settings.RadarrModal.apiKey": "API Key", "components.Settings.RadarrModal.apiKey": "API Key",
"components.Settings.RadarrModal.apiKeyPlaceholder": "Your Radarr API key", "components.Settings.RadarrModal.baseUrl": "URL Base",
"components.Settings.RadarrModal.baseUrl": "Base URL",
"components.Settings.RadarrModal.baseUrlPlaceholder": "Example: /radarr",
"components.Settings.RadarrModal.create4kradarr": "Add New 4K Radarr Server", "components.Settings.RadarrModal.create4kradarr": "Add New 4K Radarr Server",
"components.Settings.RadarrModal.createradarr": "Add New Radarr Server", "components.Settings.RadarrModal.createradarr": "Add New Radarr Server",
"components.Settings.RadarrModal.default4kserver": "Default 4K Server", "components.Settings.RadarrModal.default4kserver": "Default 4K Server",
@ -369,7 +370,6 @@
"components.Settings.RadarrModal.editradarr": "Edit Radarr Server", "components.Settings.RadarrModal.editradarr": "Edit Radarr Server",
"components.Settings.RadarrModal.enableSearch": "Enable Automatic Search", "components.Settings.RadarrModal.enableSearch": "Enable Automatic Search",
"components.Settings.RadarrModal.externalUrl": "External URL", "components.Settings.RadarrModal.externalUrl": "External URL",
"components.Settings.RadarrModal.externalUrlPlaceholder": "External URL pointing to your Radarr server",
"components.Settings.RadarrModal.hostname": "Hostname or IP Address", "components.Settings.RadarrModal.hostname": "Hostname or IP Address",
"components.Settings.RadarrModal.loadingTags": "Loading tags…", "components.Settings.RadarrModal.loadingTags": "Loading tags…",
"components.Settings.RadarrModal.loadingprofiles": "Loading quality profiles…", "components.Settings.RadarrModal.loadingprofiles": "Loading quality profiles…",
@ -385,7 +385,6 @@
"components.Settings.RadarrModal.selecttags": "Select tags", "components.Settings.RadarrModal.selecttags": "Select tags",
"components.Settings.RadarrModal.server4k": "4K Server", "components.Settings.RadarrModal.server4k": "4K Server",
"components.Settings.RadarrModal.servername": "Server Name", "components.Settings.RadarrModal.servername": "Server Name",
"components.Settings.RadarrModal.servernamePlaceholder": "A Radarr Server",
"components.Settings.RadarrModal.ssl": "Enable SSL", "components.Settings.RadarrModal.ssl": "Enable SSL",
"components.Settings.RadarrModal.syncEnabled": "Enable Scan", "components.Settings.RadarrModal.syncEnabled": "Enable Scan",
"components.Settings.RadarrModal.tags": "Tags", "components.Settings.RadarrModal.tags": "Tags",
@ -490,9 +489,7 @@
"components.Settings.SonarrModal.animequalityprofile": "Anime Quality Profile", "components.Settings.SonarrModal.animequalityprofile": "Anime Quality Profile",
"components.Settings.SonarrModal.animerootfolder": "Anime Root Folder", "components.Settings.SonarrModal.animerootfolder": "Anime Root Folder",
"components.Settings.SonarrModal.apiKey": "API Key", "components.Settings.SonarrModal.apiKey": "API Key",
"components.Settings.SonarrModal.apiKeyPlaceholder": "Your Sonarr API key", "components.Settings.SonarrModal.baseUrl": "URL Base",
"components.Settings.SonarrModal.baseUrl": "Base URL",
"components.Settings.SonarrModal.baseUrlPlaceholder": "Example: /sonarr",
"components.Settings.SonarrModal.create4ksonarr": "Add New 4K Sonarr Server", "components.Settings.SonarrModal.create4ksonarr": "Add New 4K Sonarr Server",
"components.Settings.SonarrModal.createsonarr": "Add New Sonarr Server", "components.Settings.SonarrModal.createsonarr": "Add New Sonarr Server",
"components.Settings.SonarrModal.default4kserver": "Default 4K Server", "components.Settings.SonarrModal.default4kserver": "Default 4K Server",
@ -501,7 +498,6 @@
"components.Settings.SonarrModal.editsonarr": "Edit Sonarr Server", "components.Settings.SonarrModal.editsonarr": "Edit Sonarr Server",
"components.Settings.SonarrModal.enableSearch": "Enable Automatic Search", "components.Settings.SonarrModal.enableSearch": "Enable Automatic Search",
"components.Settings.SonarrModal.externalUrl": "External URL", "components.Settings.SonarrModal.externalUrl": "External URL",
"components.Settings.SonarrModal.externalUrlPlaceholder": "External URL pointing to your Sonarr server",
"components.Settings.SonarrModal.hostname": "Hostname or IP Address", "components.Settings.SonarrModal.hostname": "Hostname or IP Address",
"components.Settings.SonarrModal.languageprofile": "Language Profile", "components.Settings.SonarrModal.languageprofile": "Language Profile",
"components.Settings.SonarrModal.loadingTags": "Loading tags…", "components.Settings.SonarrModal.loadingTags": "Loading tags…",
@ -519,7 +515,6 @@
"components.Settings.SonarrModal.selecttags": "Select tags", "components.Settings.SonarrModal.selecttags": "Select tags",
"components.Settings.SonarrModal.server4k": "4K Server", "components.Settings.SonarrModal.server4k": "4K Server",
"components.Settings.SonarrModal.servername": "Server Name", "components.Settings.SonarrModal.servername": "Server Name",
"components.Settings.SonarrModal.servernamePlaceholder": "A Sonarr Server",
"components.Settings.SonarrModal.ssl": "Enable SSL", "components.Settings.SonarrModal.ssl": "Enable SSL",
"components.Settings.SonarrModal.syncEnabled": "Enable Scan", "components.Settings.SonarrModal.syncEnabled": "Enable Scan",
"components.Settings.SonarrModal.tags": "Tags", "components.Settings.SonarrModal.tags": "Tags",
@ -605,12 +600,10 @@
"components.Settings.serverRemote": "remote", "components.Settings.serverRemote": "remote",
"components.Settings.serverSecure": "secure", "components.Settings.serverSecure": "secure",
"components.Settings.servername": "Server Name", "components.Settings.servername": "Server Name",
"components.Settings.servernamePlaceholder": "Plex Server Name",
"components.Settings.servernameTip": "Automatically retrieved from Plex after saving", "components.Settings.servernameTip": "Automatically retrieved from Plex after saving",
"components.Settings.serverpreset": "Server", "components.Settings.serverpreset": "Server",
"components.Settings.serverpresetLoad": "Press the button to load available servers", "components.Settings.serverpresetLoad": "Press the button to load available servers",
"components.Settings.serverpresetManualMessage": "Manual configuration", "components.Settings.serverpresetManualMessage": "Manual configuration",
"components.Settings.serverpresetPlaceholder": "Plex Server",
"components.Settings.serverpresetRefreshing": "Retrieving servers…", "components.Settings.serverpresetRefreshing": "Retrieving servers…",
"components.Settings.serviceSettingsDescription": "Configure your {serverType} server(s) below. You can connect multiple {serverType} servers, but only two of them can be marked as defaults (one non-4K and one 4K). Administrators are able to override the server used to process new requests prior to approval.", "components.Settings.serviceSettingsDescription": "Configure your {serverType} server(s) below. You can connect multiple {serverType} servers, but only two of them can be marked as defaults (one non-4K and one 4K). Administrators are able to override the server used to process new requests prior to approval.",
"components.Settings.services": "Services", "components.Settings.services": "Services",

Loading…
Cancel
Save