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: {
name: '',
ip: '127.0.0.1',
ip: '',
port: 32400,
useSsl: false,
libraries: [],
@ -272,7 +272,7 @@ class Settings {
types: 0,
options: {
emailFrom: '',
smtpHost: '127.0.0.1',
smtpHost: '',
smtpPort: 587,
secure: 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"
placeholder={intl.formatMessage(messages.searchPlaceholder)}
type="search"
inputMode="search"
value={searchValue}
onChange={(e) => setSearchValue(e.target.value)}
onFocus={() => setIsOpen(true)}

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

@ -107,7 +107,7 @@ const ResetPassword: React.FC = () => {
id="email"
name="email"
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"
/>
</div>

@ -15,7 +15,8 @@ const messages = defineMessages({
botUsername: 'Bot Username',
botAvatarUrl: 'Bot Avatar 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!',
discordsettingsfailed: 'Discord notification settings failed to save.',
toastDiscordTestSending: 'Sending Discord test notification…',
@ -137,65 +138,75 @@ const NotificationsDiscord: React.FC = () => {
<div className="form-row">
<label htmlFor="enabled" className="checkbox-label">
{intl.formatMessage(messages.agentenabled)}
<span className="label-required">*</span>
</label>
<div className="form-input">
<Field type="checkbox" id="enabled" name="enabled" />
</div>
</div>
<div className="form-row">
<label htmlFor="botUsername" className="text-label">
{intl.formatMessage(messages.botUsername)}
<label htmlFor="name" className="text-label">
{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>
<div className="form-input">
<div className="form-input-field">
<Field
id="botUsername"
name="botUsername"
id="webhookUrl"
name="webhookUrl"
type="text"
placeholder={intl.formatMessage(messages.botUsername)}
inputMode="url"
/>
</div>
{errors.botUsername && touched.botUsername && (
<div className="error">{errors.botUsername}</div>
{errors.webhookUrl && touched.webhookUrl && (
<div className="error">{errors.webhookUrl}</div>
)}
</div>
</div>
<div className="form-row">
<label htmlFor="botAvatarUrl" className="text-label">
{intl.formatMessage(messages.botAvatarUrl)}
<label htmlFor="botUsername" className="text-label">
{intl.formatMessage(messages.botUsername)}
</label>
<div className="form-input">
<div className="form-input-field">
<Field
id="botAvatarUrl"
name="botAvatarUrl"
type="text"
placeholder={intl.formatMessage(messages.botAvatarUrl)}
/>
<Field id="botUsername" name="botUsername" type="text" />
</div>
{errors.botAvatarUrl && touched.botAvatarUrl && (
<div className="error">{errors.botAvatarUrl}</div>
{errors.botUsername && touched.botUsername && (
<div className="error">{errors.botUsername}</div>
)}
</div>
</div>
<div className="form-row">
<label htmlFor="name" className="text-label">
{intl.formatMessage(messages.webhookUrl)}
<span className="label-required">*</span>
<label htmlFor="botAvatarUrl" className="text-label">
{intl.formatMessage(messages.botAvatarUrl)}
</label>
<div className="form-input">
<div className="form-input-field">
<Field
id="webhookUrl"
name="webhookUrl"
id="botAvatarUrl"
name="botAvatarUrl"
type="text"
placeholder={intl.formatMessage(
messages.webhookUrlPlaceholder
)}
inputMode="url"
/>
</div>
{errors.webhookUrl && touched.webhookUrl && (
<div className="error">{errors.webhookUrl}</div>
{errors.botAvatarUrl && touched.botAvatarUrl && (
<div className="error">{errors.botAvatarUrl}</div>
)}
</div>
</div>

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

@ -13,6 +13,8 @@ import NotificationTypeSelector from '../../../NotificationTypeSelector';
const messages = defineMessages({
agentenabled: 'Enable Agent',
webhookUrl: 'Webhook URL',
webhookUrlTip:
'Your user- or device-based <LunaSeaLink>notification webhook URL</LunaSeaLink>',
validationWebhookUrl: 'You must provide a valid URL',
profileName: 'Profile Name',
profileNameTip: 'Only required if not using the <code>default</code> profile',
@ -129,6 +131,7 @@ const NotificationsLunaSea: React.FC = () => {
<div className="form-row">
<label htmlFor="enabled" className="checkbox-label">
{intl.formatMessage(messages.agentenabled)}
<span className="label-required">*</span>
</label>
<div className="form-input">
<Field type="checkbox" id="enabled" name="enabled" />
@ -138,10 +141,31 @@ const NotificationsLunaSea: React.FC = () => {
<label htmlFor="name" className="text-label">
{intl.formatMessage(messages.webhookUrl)}
<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>
<div className="form-input">
<div className="form-input-field">
<Field id="webhookUrl" name="webhookUrl" type="text" />
<Field
id="webhookUrl"
name="webhookUrl"
type="text"
inputMode="url"
/>
</div>
{errors.webhookUrl && touched.webhookUrl && (
<div className="error">{errors.webhookUrl}</div>

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

@ -6,15 +6,18 @@ import { useToasts } from 'react-toast-notifications';
import useSWR from 'swr';
import * as Yup from 'yup';
import globalMessages from '../../../../i18n/globalMessages';
import Alert from '../../../Common/Alert';
import Button from '../../../Common/Button';
import LoadingSpinner from '../../../Common/LoadingSpinner';
import NotificationTypeSelector from '../../../NotificationTypeSelector';
const messages = defineMessages({
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',
userTokenTip:
'Your 30-character <UsersGroupsLink>user or group identifier</UsersGroupsLink>',
validationAccessTokenRequired: 'You must provide a valid application token',
validationUserTokenRequired: 'You must provide a valid user key',
pushoversettingssaved: 'Pushover notification settings saved successfully!',
@ -22,8 +25,6 @@ const messages = defineMessages({
toastPushoverTestSending: 'Sending Pushover test notification…',
toastPushoverTestSuccess: 'Pushover test notification sent!',
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 = () => {
@ -143,118 +144,112 @@ const NotificationsPushover: React.FC = () => {
};
return (
<>
<Alert
title={intl.formatMessage(messages.settinguppushoverDescription, {
RegisterApplicationLink: function RegisterApplicationLink(msg) {
return (
<a
href="https://pushover.net/apps/build"
className="text-white transition duration-300 hover:underline"
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>
<Form className="section">
<div className="form-row">
<label htmlFor="enabled" className="checkbox-label">
{intl.formatMessage(messages.agentenabled)}
<span className="label-required">*</span>
</label>
<div className="form-input">
<Field type="checkbox" id="enabled" name="enabled" />
</div>
<div className="form-row">
<label htmlFor="accessToken" className="text-label">
{intl.formatMessage(messages.accessToken)}
<span className="label-required">*</span>
</label>
<div className="form-input">
<div className="form-input-field">
<Field
id="accessToken"
name="accessToken"
type="text"
placeholder={intl.formatMessage(messages.accessToken)}
/>
</div>
{errors.accessToken && touched.accessToken && (
<div className="error">{errors.accessToken}</div>
)}
</div>
<div className="form-row">
<label htmlFor="accessToken" className="text-label">
{intl.formatMessage(messages.accessToken)}
<span className="label-required">*</span>
<span className="label-tip">
{intl.formatMessage(messages.accessTokenTip, {
ApplicationRegistrationLink: function ApplicationRegistrationLink(
msg
) {
return (
<a
href="https://pushover.net/api#registration"
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="accessToken" name="accessToken" type="text" />
</div>
{errors.accessToken && touched.accessToken && (
<div className="error">{errors.accessToken}</div>
)}
</div>
<div className="form-row">
<label htmlFor="userToken" className="text-label">
{intl.formatMessage(messages.userToken)}
<span className="label-required">*</span>
</label>
<div className="form-input">
<div className="form-input-field">
<Field
id="userToken"
name="userToken"
type="text"
placeholder={intl.formatMessage(messages.userToken)}
/>
</div>
{errors.userToken && touched.userToken && (
<div className="error">{errors.userToken}</div>
)}
</div>
<div className="form-row">
<label htmlFor="userToken" className="text-label">
{intl.formatMessage(messages.userToken)}
<span className="label-required">*</span>
<span className="label-tip">
{intl.formatMessage(messages.userTokenTip, {
UsersGroupsLink: function UsersGroupsLink(msg) {
return (
<a
href="https://pushover.net/api#identifiers"
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="userToken" name="userToken" type="text" />
</div>
{errors.userToken && touched.userToken && (
<div className="error">{errors.userToken}</div>
)}
</div>
<NotificationTypeSelector
currentTypes={values.types}
onUpdate={(newTypes) => setFieldValue('types', newTypes)}
/>
<div className="actions">
<div className="flex justify-end">
<span className="inline-flex ml-3 rounded-md shadow-sm">
<Button
buttonType="warning"
disabled={isSubmitting || !isValid || isTesting}
onClick={(e) => {
e.preventDefault();
testSettings();
}}
>
{isTesting
? intl.formatMessage(globalMessages.testing)
: intl.formatMessage(globalMessages.test)}
</Button>
</span>
<span className="inline-flex ml-3 rounded-md shadow-sm">
<Button
buttonType="primary"
type="submit"
disabled={isSubmitting || !isValid || isTesting}
>
{isSubmitting
? intl.formatMessage(globalMessages.saving)
: intl.formatMessage(globalMessages.save)}
</Button>
</span>
</div>
</div>
<NotificationTypeSelector
currentTypes={values.types}
onUpdate={(newTypes) => setFieldValue('types', newTypes)}
/>
<div className="actions">
<div className="flex justify-end">
<span className="inline-flex ml-3 rounded-md shadow-sm">
<Button
buttonType="warning"
disabled={isSubmitting || !isValid || isTesting}
onClick={(e) => {
e.preventDefault();
testSettings();
}}
>
{isTesting
? intl.formatMessage(globalMessages.testing)
: intl.formatMessage(globalMessages.test)}
</Button>
</span>
<span className="inline-flex ml-3 rounded-md shadow-sm">
<Button
buttonType="primary"
type="submit"
disabled={isSubmitting || !isValid || isTesting}
>
{isSubmitting
? intl.formatMessage(globalMessages.saving)
: intl.formatMessage(globalMessages.save)}
</Button>
</span>
</div>
</Form>
</>
</div>
</Form>
);
}}
</Formik>

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

@ -6,7 +6,6 @@ import { useToasts } from 'react-toast-notifications';
import useSWR from 'swr';
import * as Yup from 'yup';
import globalMessages from '../../../i18n/globalMessages';
import Alert from '../../Common/Alert';
import Button from '../../Common/Button';
import LoadingSpinner from '../../Common/LoadingSpinner';
import SensitiveInput from '../../Common/SensitiveInput';
@ -16,18 +15,20 @@ const messages = defineMessages({
agentenabled: 'Enable Agent',
botUsername: 'Bot Username',
botUsernameTip:
'Allow users to start a chat with the bot and configure their own personal notifications',
botAPI: 'Bot Authentication Token',
'Allow users to also start a chat with your bot and configure their own notifications',
botAPI: 'Bot Authorization Token',
botApiTip:
'<CreateBotLink>Create a bot</CreateBotLink> for use with Overseerr',
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',
telegramsettingssaved: 'Telegram notification settings saved successfully!',
telegramsettingsfailed: 'Telegram notification settings failed to save.',
toastTelegramTestSending: 'Sending Telegram test notification…',
toastTelegramTestSuccess: 'Telegram test notification sent!',
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',
sendSilentlyTip: 'Send notifications with no sound',
});
@ -151,158 +152,141 @@ const NotificationsTelegram: React.FC = () => {
};
return (
<>
<Alert
title={intl.formatMessage(messages.settinguptelegramDescription, {
CreateBotLink: function CreateBotLink(msg) {
return (
<a
href="https://core.telegram.org/bots#6-botfather"
className="text-white transition duration-300 hover:underline"
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>
<Form className="section">
<div className="form-row">
<label htmlFor="enabled" className="checkbox-label">
{intl.formatMessage(messages.agentenabled)}
<span className="label-required">*</span>
</label>
<div className="form-input">
<Field type="checkbox" id="enabled" name="enabled" />
</div>
<div className="form-row">
<label htmlFor="botUsername" className="text-label">
{intl.formatMessage(messages.botUsername)}
<span className="label-tip">
{intl.formatMessage(messages.botUsernameTip)}
</span>
</label>
<div className="form-input">
<div className="form-input-field">
<Field
id="botUsername"
name="botUsername"
type="text"
placeholder={intl.formatMessage(messages.botUsername)}
/>
</div>
{errors.botUsername && touched.botUsername && (
<div className="error">{errors.botUsername}</div>
)}
</div>
<div className="form-row">
<label htmlFor="botAPI" className="text-label">
{intl.formatMessage(messages.botAPI)}
<span className="label-required">*</span>
<span className="label-tip">
{intl.formatMessage(messages.botApiTip, {
CreateBotLink: function CreateBotLink(msg) {
return (
<a
href="https://core.telegram.org/bots#6-botfather"
className="text-white transition duration-300 hover:underline"
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>;
},
})}
</span>
</label>
<div className="form-input">
<div className="form-input-field">
<SensitiveInput
as="field"
id="botAPI"
name="botAPI"
autoComplete="one-time-code"
/>
</div>
{errors.botAPI && touched.botAPI && (
<div className="error">{errors.botAPI}</div>
)}
</div>
<div className="form-row">
<label htmlFor="botAPI" className="text-label">
{intl.formatMessage(messages.botAPI)}
<span className="label-required">*</span>
</label>
<div className="form-input">
<div className="form-input-field">
<SensitiveInput
as="field"
id="botAPI"
name="botAPI"
type="text"
placeholder={intl.formatMessage(messages.botAPI)}
/>
</div>
{errors.botAPI && touched.botAPI && (
<div className="error">{errors.botAPI}</div>
)}
</div>
<div className="form-row">
<label htmlFor="botUsername" className="text-label">
{intl.formatMessage(messages.botUsername)}
<span className="label-tip">
{intl.formatMessage(messages.botUsernameTip)}
</span>
</label>
<div className="form-input">
<div className="form-input-field">
<Field id="botUsername" name="botUsername" type="text" />
</div>
{errors.botUsername && touched.botUsername && (
<div className="error">{errors.botUsername}</div>
)}
</div>
<div className="form-row">
<label htmlFor="chatId" className="text-label">
{intl.formatMessage(messages.chatId)}
<span className="label-required">*</span>
</label>
<div className="form-input">
<div className="form-input-field">
<Field
id="chatId"
name="chatId"
type="text"
placeholder={intl.formatMessage(messages.chatId)}
/>
</div>
{errors.chatId && touched.chatId && (
<div className="error">{errors.chatId}</div>
)}
</div>
<div className="form-row">
<label htmlFor="chatId" className="text-label">
{intl.formatMessage(messages.chatId)}
<span className="label-required">*</span>
</label>
<div className="form-input">
<div className="form-input-field">
<Field id="chatId" name="chatId" type="text" />
</div>
{errors.chatId && touched.chatId && (
<div className="error">{errors.chatId}</div>
)}
</div>
<div className="form-row">
<label htmlFor="sendSilently" className="checkbox-label">
<span>{intl.formatMessage(messages.sendSilently)}</span>
<span className="label-tip">
{intl.formatMessage(messages.sendSilentlyTip)}
</span>
</label>
<div className="form-input">
<Field
type="checkbox"
id="sendSilently"
name="sendSilently"
/>
</div>
</div>
<div className="form-row">
<label htmlFor="sendSilently" className="checkbox-label">
<span>{intl.formatMessage(messages.sendSilently)}</span>
<span className="label-tip">
{intl.formatMessage(messages.sendSilentlyTip)}
</span>
</label>
<div className="form-input">
<Field type="checkbox" id="sendSilently" name="sendSilently" />
</div>
<NotificationTypeSelector
currentTypes={values.types}
onUpdate={(newTypes) => setFieldValue('types', newTypes)}
/>
<div className="actions">
<div className="flex justify-end">
<span className="inline-flex ml-3 rounded-md shadow-sm">
<Button
buttonType="warning"
disabled={isSubmitting || !isValid || isTesting}
onClick={(e) => {
e.preventDefault();
testSettings();
}}
>
{isTesting
? intl.formatMessage(globalMessages.testing)
: intl.formatMessage(globalMessages.test)}
</Button>
</span>
<span className="inline-flex ml-3 rounded-md shadow-sm">
<Button
buttonType="primary"
type="submit"
disabled={isSubmitting || !isValid || isTesting}
>
{isSubmitting
? intl.formatMessage(globalMessages.saving)
: intl.formatMessage(globalMessages.save)}
</Button>
</span>
</div>
</div>
<NotificationTypeSelector
currentTypes={values.types}
onUpdate={(newTypes) => setFieldValue('types', newTypes)}
/>
<div className="actions">
<div className="flex justify-end">
<span className="inline-flex ml-3 rounded-md shadow-sm">
<Button
buttonType="warning"
disabled={isSubmitting || !isValid || isTesting}
onClick={(e) => {
e.preventDefault();
testSettings();
}}
>
{isTesting
? intl.formatMessage(globalMessages.testing)
: intl.formatMessage(globalMessages.test)}
</Button>
</span>
<span className="inline-flex ml-3 rounded-md shadow-sm">
<Button
buttonType="primary"
type="submit"
disabled={isSubmitting || !isValid || isTesting}
>
{isSubmitting
? intl.formatMessage(globalMessages.saving)
: intl.formatMessage(globalMessages.save)}
</Button>
</span>
</div>
</Form>
</>
</div>
</Form>
);
}}
</Formik>

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

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

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

@ -222,7 +222,6 @@ const SettingsMain: React.FC = () => {
id="applicationTitle"
name="applicationTitle"
type="text"
placeholder="Overseerr"
/>
</div>
{errors.applicationTitle && touched.applicationTitle && (
@ -240,7 +239,7 @@ const SettingsMain: React.FC = () => {
id="applicationUrl"
name="applicationUrl"
type="text"
placeholder="https://os.example.com"
inputMode="url"
/>
</div>
{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.',
servername: 'Server Name',
servernameTip: 'Automatically retrieved from Plex after saving',
servernamePlaceholder: 'Plex Server Name',
serverpreset: 'Server',
serverpresetPlaceholder: 'Plex Server',
serverLocal: 'local',
serverRemote: 'remote',
serverSecure: 'secure',
@ -281,7 +279,7 @@ const SettingsPlex: React.FC<SettingsPlexProps> = ({ onComplete }) => {
<Formik
initialValues={{
hostname: data?.ip,
port: data?.port,
port: data?.port ?? 32400,
useSsl: data?.useSsl,
selectedPreset: undefined,
}}
@ -354,9 +352,6 @@ const SettingsPlex: React.FC<SettingsPlexProps> = ({ onComplete }) => {
id="name"
name="name"
className="cursor-not-allowed"
placeholder={intl.formatMessage(
messages.servernamePlaceholder
)}
value={data?.name}
readOnly
/>
@ -372,9 +367,6 @@ const SettingsPlex: React.FC<SettingsPlexProps> = ({ onComplete }) => {
<select
id="preset"
name="preset"
placeholder={intl.formatMessage(
messages.serverpresetPlaceholder
)}
value={values.selectedPreset}
disabled={!availableServers || isRefreshingPresets}
className="rounded-l-only"
@ -453,9 +445,9 @@ const SettingsPlex: React.FC<SettingsPlexProps> = ({ onComplete }) => {
</span>
<Field
type="text"
inputMode="url"
id="hostname"
name="hostname"
placeholder="127.0.0.1"
className="rounded-r-only"
/>
</div>
@ -472,9 +464,9 @@ const SettingsPlex: React.FC<SettingsPlexProps> = ({ onComplete }) => {
<div className="form-input">
<Field
type="text"
inputMode="numeric"
id="port"
name="port"
placeholder="32400"
className="short"
/>
{errors.port && touched.port && (

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

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

@ -258,28 +258,29 @@
"components.Settings.Notifications.NotificationsLunaSea.toastLunaSeaTestSuccess": "LunaSea test notification sent!",
"components.Settings.Notifications.NotificationsLunaSea.validationWebhookUrl": "You must provide a valid 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.accessTokenTip": "Create a token from your <PushbulletSettingsLink>Account Settings</PushbulletSettingsLink>",
"components.Settings.Notifications.NotificationsPushbullet.agentEnabled": "Enable Agent",
"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.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.toastPushbulletTestSending": "Sending Pushbullet test notification…",
"components.Settings.Notifications.NotificationsPushbullet.toastPushbulletTestSuccess": "Pushbullet test notification sent!",
"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.pushoversettingsfailed": "Pushover notification settings failed to save.",
"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.toastPushoverTestSending": "Sending Pushover test notification…",
"components.Settings.Notifications.NotificationsPushover.toastPushoverTestSuccess": "Pushover test notification sent!",
"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.validationUserTokenRequired": "You must provide a valid user key",
"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.slacksettingssaved": "Slack notification settings saved successfully!",
"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.validationWebhookUrl": "You must provide a valid 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.toastWebPushTestFailed": "Web push test notification failed to send.",
"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.authPass": "SMTP Password",
"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.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.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.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.",
@ -331,7 +335,6 @@
"components.Settings.Notifications.sendSilently": "Send Silently",
"components.Settings.Notifications.sendSilentlyTip": "Send notifications with no sound",
"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.smtpPort": "SMTP Port",
"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.toastTelegramTestSending": "Sending Telegram test notification…",
"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.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",
@ -355,12 +358,10 @@
"components.Settings.Notifications.validationSmtpPortRequired": "You must provide a valid port number",
"components.Settings.Notifications.validationUrl": "You must provide a valid 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.apiKey": "API Key",
"components.Settings.RadarrModal.apiKeyPlaceholder": "Your Radarr API key",
"components.Settings.RadarrModal.baseUrl": "Base URL",
"components.Settings.RadarrModal.baseUrlPlaceholder": "Example: /radarr",
"components.Settings.RadarrModal.baseUrl": "URL Base",
"components.Settings.RadarrModal.create4kradarr": "Add New 4K Radarr Server",
"components.Settings.RadarrModal.createradarr": "Add New Radarr Server",
"components.Settings.RadarrModal.default4kserver": "Default 4K Server",
@ -369,7 +370,6 @@
"components.Settings.RadarrModal.editradarr": "Edit Radarr Server",
"components.Settings.RadarrModal.enableSearch": "Enable Automatic Search",
"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.loadingTags": "Loading tags…",
"components.Settings.RadarrModal.loadingprofiles": "Loading quality profiles…",
@ -385,7 +385,6 @@
"components.Settings.RadarrModal.selecttags": "Select tags",
"components.Settings.RadarrModal.server4k": "4K Server",
"components.Settings.RadarrModal.servername": "Server Name",
"components.Settings.RadarrModal.servernamePlaceholder": "A Radarr Server",
"components.Settings.RadarrModal.ssl": "Enable SSL",
"components.Settings.RadarrModal.syncEnabled": "Enable Scan",
"components.Settings.RadarrModal.tags": "Tags",
@ -490,9 +489,7 @@
"components.Settings.SonarrModal.animequalityprofile": "Anime Quality Profile",
"components.Settings.SonarrModal.animerootfolder": "Anime Root Folder",
"components.Settings.SonarrModal.apiKey": "API Key",
"components.Settings.SonarrModal.apiKeyPlaceholder": "Your Sonarr API key",
"components.Settings.SonarrModal.baseUrl": "Base URL",
"components.Settings.SonarrModal.baseUrlPlaceholder": "Example: /sonarr",
"components.Settings.SonarrModal.baseUrl": "URL Base",
"components.Settings.SonarrModal.create4ksonarr": "Add New 4K Sonarr Server",
"components.Settings.SonarrModal.createsonarr": "Add New Sonarr Server",
"components.Settings.SonarrModal.default4kserver": "Default 4K Server",
@ -501,7 +498,6 @@
"components.Settings.SonarrModal.editsonarr": "Edit Sonarr Server",
"components.Settings.SonarrModal.enableSearch": "Enable Automatic Search",
"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.languageprofile": "Language Profile",
"components.Settings.SonarrModal.loadingTags": "Loading tags…",
@ -519,7 +515,6 @@
"components.Settings.SonarrModal.selecttags": "Select tags",
"components.Settings.SonarrModal.server4k": "4K Server",
"components.Settings.SonarrModal.servername": "Server Name",
"components.Settings.SonarrModal.servernamePlaceholder": "A Sonarr Server",
"components.Settings.SonarrModal.ssl": "Enable SSL",
"components.Settings.SonarrModal.syncEnabled": "Enable Scan",
"components.Settings.SonarrModal.tags": "Tags",
@ -605,12 +600,10 @@
"components.Settings.serverRemote": "remote",
"components.Settings.serverSecure": "secure",
"components.Settings.servername": "Server Name",
"components.Settings.servernamePlaceholder": "Plex Server Name",
"components.Settings.servernameTip": "Automatically retrieved from Plex after saving",
"components.Settings.serverpreset": "Server",
"components.Settings.serverpresetLoad": "Press the button to load available servers",
"components.Settings.serverpresetManualMessage": "Manual configuration",
"components.Settings.serverpresetPlaceholder": "Plex Server",
"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.services": "Services",

Loading…
Cancel
Save