From d0cc0a8e7a1f104d8a7a1e3acba17baedb3a0f9b Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Sat, 17 Apr 2021 09:32:16 +0900
Subject: [PATCH 01/13] docs: add Dabu-dot as a contributor (#1447) [skip ci]
* docs: update README.md [skip ci]
* docs: update .all-contributorsrc [skip ci]
Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
---
.all-contributorsrc | 9 +++++++++
README.md | 5 ++++-
2 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index c0f24e34a4..7902f4aaaf 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -458,6 +458,15 @@
"contributions": [
"translation"
]
+ },
+ {
+ "login": "Dabu-dot",
+ "name": "Dabu-dot",
+ "avatar_url": "https://avatars.githubusercontent.com/u/52525576?v=4",
+ "profile": "https://github.com/Dabu-dot",
+ "contributions": [
+ "translation"
+ ]
}
],
"badgeTemplate": " -orange.svg\"/> ",
diff --git a/README.md b/README.md
index 2fa7035857..3bd9f66ea4 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@
-
+
@@ -137,6 +137,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
= ({
title={intl.formatMessage(
is4k ? messages.requestcollection4k : messages.requestcollection
)}
- iconSvg={
-
-
-
- }
+ iconSvg={ }
>
{intl.formatMessage(
@@ -355,20 +341,7 @@ const CollectionDetails: React.FC = ({
}}
text={
<>
-
-
-
+
{intl.formatMessage(
hasRequestable
@@ -393,20 +366,7 @@ const CollectionDetails: React.FC = ({
setIs4k(true);
}}
>
-
-
-
+
{intl.formatMessage(messages.requestcollection4k)}
diff --git a/src/components/Common/Alert/index.tsx b/src/components/Common/Alert/index.tsx
index b29513cc6b..9f62131a3e 100644
--- a/src/components/Common/Alert/index.tsx
+++ b/src/components/Common/Alert/index.tsx
@@ -1,3 +1,8 @@
+import {
+ ExclamationIcon,
+ InformationCircleIcon,
+ XCircleIcon,
+} from '@heroicons/react/solid';
import React from 'react';
interface AlertProps {
@@ -10,21 +15,7 @@ const Alert: React.FC = ({ title, children, type }) => {
bgColor: 'bg-yellow-600',
titleColor: 'text-yellow-200',
textColor: 'text-yellow-300',
- svg: (
-
-
-
- ),
+ svg: ,
};
switch (type) {
@@ -33,22 +24,7 @@ const Alert: React.FC = ({ title, children, type }) => {
bgColor: 'bg-indigo-600',
titleColor: 'text-indigo-200',
textColor: 'text-indigo-300',
- svg: (
-
-
-
- ),
+ svg: ,
};
break;
case 'error':
@@ -56,22 +32,7 @@ const Alert: React.FC = ({ title, children, type }) => {
bgColor: 'bg-red-600',
titleColor: 'text-red-200',
textColor: 'text-red-300',
- svg: (
-
-
-
- ),
+ svg: ,
};
break;
}
diff --git a/src/components/Common/ButtonWithDropdown/index.tsx b/src/components/Common/ButtonWithDropdown/index.tsx
index d429e11481..65a8c7417f 100644
--- a/src/components/Common/ButtonWithDropdown/index.tsx
+++ b/src/components/Common/ButtonWithDropdown/index.tsx
@@ -1,13 +1,14 @@
+import { ChevronDownIcon } from '@heroicons/react/solid';
import React, {
- useState,
- useRef,
AnchorHTMLAttributes,
- ReactNode,
ButtonHTMLAttributes,
+ ReactNode,
+ useRef,
+ useState,
} from 'react';
import useClickOutside from '../../../hooks/useClickOutside';
-import Transition from '../../Transition';
import { withProperties } from '../../../utils/typeHelpers';
+import Transition from '../../Transition';
interface DropdownItemProps extends AnchorHTMLAttributes {
buttonType?: 'primary' | 'ghost';
@@ -102,18 +103,7 @@ const ButtonWithDropdown: React.FC = ({
{dropdownIcon ? (
dropdownIcon
) : (
-
-
-
+
)}
= ({ links }) => {
buttonType="ghost"
text={
<>
-
-
-
-
+
{links[0].text}
>
}
diff --git a/src/components/Common/SlideOver/index.tsx b/src/components/Common/SlideOver/index.tsx
index 79a9a0bb7c..4dac751a77 100644
--- a/src/components/Common/SlideOver/index.tsx
+++ b/src/components/Common/SlideOver/index.tsx
@@ -1,4 +1,5 @@
/* eslint-disable jsx-a11y/click-events-have-key-events */
+import { XIcon } from '@heroicons/react/outline';
import React, { useEffect, useRef, useState } from 'react';
import ReactDOM from 'react-dom';
import { useLockBodyScroll } from '../../../hooks/useLockBodyScroll';
@@ -81,20 +82,7 @@ const SlideOver: React.FC = ({
className="text-indigo-200 transition duration-150 ease-in-out hover:text-white"
onClick={() => onClose()}
>
-
-
-
+
diff --git a/src/components/Discover/MovieGenreSlider/index.tsx b/src/components/Discover/MovieGenreSlider/index.tsx
index 6ebbed11eb..56abf7d9ac 100644
--- a/src/components/Discover/MovieGenreSlider/index.tsx
+++ b/src/components/Discover/MovieGenreSlider/index.tsx
@@ -1,12 +1,13 @@
+import { ArrowCircleRightIcon } from '@heroicons/react/outline';
+import Link from 'next/link';
import React, { useContext } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import useSWR from 'swr';
-import GenreCard from '../../GenreCard';
-import Slider from '../../Slider';
import { GenreSliderItem } from '../../../../server/interfaces/api/discoverInterfaces';
import { LanguageContext } from '../../../context/LanguageContext';
+import GenreCard from '../../GenreCard';
+import Slider from '../../Slider';
import { genreColorMap } from '../constants';
-import Link from 'next/link';
const messages = defineMessages({
moviegenres: 'Movie Genres',
@@ -29,20 +30,7 @@ const MovieGenreSlider: React.FC = () => {
{intl.formatMessage(messages.moviegenres)}
-
-
-
+
diff --git a/src/components/Discover/TvGenreSlider/index.tsx b/src/components/Discover/TvGenreSlider/index.tsx
index 20b1b1e269..37f1ee18be 100644
--- a/src/components/Discover/TvGenreSlider/index.tsx
+++ b/src/components/Discover/TvGenreSlider/index.tsx
@@ -1,12 +1,13 @@
+import { ArrowCircleRightIcon } from '@heroicons/react/outline';
+import Link from 'next/link';
import React, { useContext } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import useSWR from 'swr';
-import GenreCard from '../../GenreCard';
-import Slider from '../../Slider';
import { GenreSliderItem } from '../../../../server/interfaces/api/discoverInterfaces';
import { LanguageContext } from '../../../context/LanguageContext';
+import GenreCard from '../../GenreCard';
+import Slider from '../../Slider';
import { genreColorMap } from '../constants';
-import Link from 'next/link';
const messages = defineMessages({
tvgenres: 'Series Genres',
@@ -29,20 +30,7 @@ const TvGenreSlider: React.FC = () => {
{intl.formatMessage(messages.tvgenres)}
-
-
-
+
diff --git a/src/components/Discover/index.tsx b/src/components/Discover/index.tsx
index 8560a53534..bb80d08b89 100644
--- a/src/components/Discover/index.tsx
+++ b/src/components/Discover/index.tsx
@@ -1,3 +1,4 @@
+import { ArrowCircleRightIcon } from '@heroicons/react/outline';
import Link from 'next/link';
import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
@@ -66,20 +67,7 @@ const Discover: React.FC = () => {
{intl.formatMessage(messages.recentrequests)}
-
-
-
+
diff --git a/src/components/Layout/LanguagePicker/index.tsx b/src/components/Layout/LanguagePicker/index.tsx
index 25111f302f..683fe5f439 100644
--- a/src/components/Layout/LanguagePicker/index.tsx
+++ b/src/components/Layout/LanguagePicker/index.tsx
@@ -1,3 +1,4 @@
+import { TranslateIcon } from '@heroicons/react/solid';
import React, { useContext, useRef, useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import {
@@ -100,18 +101,7 @@ const LanguagePicker: React.FC = () => {
aria-label="Language Picker"
onClick={() => setDropdownOpen(true)}
>
-
-
-
+
{
@@ -6,19 +7,7 @@ const Notifications: React.FC = () => {
className="p-1 text-gray-400 rounded-full hover:bg-gray-500 hover:text-white focus:outline-none focus:ring focus:text-white"
aria-label="Notifications"
>
-
-
-
+
);
};
diff --git a/src/components/Layout/SearchInput/index.tsx b/src/components/Layout/SearchInput/index.tsx
index 9d0aa6348d..9042ef45c5 100644
--- a/src/components/Layout/SearchInput/index.tsx
+++ b/src/components/Layout/SearchInput/index.tsx
@@ -1,7 +1,8 @@
+import { XCircleIcon } from '@heroicons/react/outline';
+import { SearchIcon } from '@heroicons/react/solid';
import React from 'react';
-import useSearchInput from '../../../hooks/useSearchInput';
import { defineMessages, useIntl } from 'react-intl';
-import ClearButton from '../../../assets/xcircle.svg';
+import useSearchInput from '../../../hooks/useSearchInput';
const messages = defineMessages({
searchPlaceholder: 'Search Movies & TV',
@@ -18,13 +19,7 @@ const SearchInput: React.FC = () => {
{
className="absolute inset-y-0 p-1 m-auto text-gray-400 transition border-none outline-none right-2 h-7 w-7 focus:outline-none focus:border-none hover:text-white"
onClick={() => clear()}
>
-
+
)}
diff --git a/src/components/Layout/Sidebar/index.tsx b/src/components/Layout/Sidebar/index.tsx
index 7558b95ba6..dbd156252d 100644
--- a/src/components/Layout/Sidebar/index.tsx
+++ b/src/components/Layout/Sidebar/index.tsx
@@ -1,3 +1,10 @@
+import {
+ ClockIcon,
+ CogIcon,
+ SparklesIcon,
+ XIcon,
+} from '@heroicons/react/outline';
+import { UsersIcon } from '@heroicons/react/solid';
import Link from 'next/link';
import { useRouter } from 'next/router';
import React, { ReactNode, useRef } from 'react';
@@ -33,20 +40,7 @@ const SidebarLinks: SidebarLinkProps[] = [
href: '/',
messagesKey: 'dashboard',
svgIcon: (
-
-
-
+
),
activeRegExp: /^\/(discover\/?(movies|tv)?)?$/,
},
@@ -54,20 +48,7 @@ const SidebarLinks: SidebarLinkProps[] = [
href: '/requests',
messagesKey: 'requests',
svgIcon: (
-
-
-
+
),
activeRegExp: /^\/requests/,
},
@@ -75,14 +56,7 @@ const SidebarLinks: SidebarLinkProps[] = [
href: '/users',
messagesKey: 'users',
svgIcon: (
-
-
-
+
),
activeRegExp: /^\/users/,
requiredPermission: Permission.MANAGE_USERS,
@@ -91,26 +65,7 @@ const SidebarLinks: SidebarLinkProps[] = [
href: '/settings',
messagesKey: 'settings',
svgIcon: (
-
-
-
-
+
),
activeRegExp: /^\/settings/,
requiredPermission: Permission.MANAGE_SETTINGS,
@@ -157,19 +112,7 @@ const Sidebar: React.FC = ({ open, setClosed }) => {
aria-label="Close sidebar"
onClick={() => setClosed()}
>
-
-
-
+
diff --git a/src/components/Layout/VersionStatus/index.tsx b/src/components/Layout/VersionStatus/index.tsx
index e5e07869c1..0a2ac43c8f 100644
--- a/src/components/Layout/VersionStatus/index.tsx
+++ b/src/components/Layout/VersionStatus/index.tsx
@@ -1,3 +1,9 @@
+import {
+ ArrowCircleUpIcon,
+ BeakerIcon,
+ CodeIcon,
+ ServerIcon,
+} from '@heroicons/react/outline';
import Link from 'next/link';
import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
@@ -51,50 +57,11 @@ const VersionStatus: React.FC = ({ onClick }) => {
}`}
>
{data.commitTag === 'local' ? (
-
-
-
+
) : data.version.startsWith('develop-') ? (
-
-
-
+
) : (
-
-
-
+
)}
{versionStream}
@@ -114,22 +81,7 @@ const VersionStatus: React.FC = ({ onClick }) => {
)}
- {data.updateAvailable && (
-
-
-
- )}
+ {data.updateAvailable && }
);
diff --git a/src/components/Layout/index.tsx b/src/components/Layout/index.tsx
index 18519734b9..7ea9ac64d9 100644
--- a/src/components/Layout/index.tsx
+++ b/src/components/Layout/index.tsx
@@ -1,3 +1,5 @@
+import { MenuAlt2Icon } from '@heroicons/react/outline';
+import { InformationCircleIcon } from '@heroicons/react/solid';
import { useRouter } from 'next/router';
import React, { useEffect, useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
@@ -57,19 +59,7 @@ const Layout: React.FC = ({ children }) => {
aria-label="Open sidebar"
onClick={() => setSidebarOpen(true)}
>
-
-
-
+
@@ -87,18 +77,7 @@ const Layout: React.FC = ({ children }) => {
diff --git a/src/components/Login/LocalLogin.tsx b/src/components/Login/LocalLogin.tsx
index 4216bf4a33..1dc6006deb 100644
--- a/src/components/Login/LocalLogin.tsx
+++ b/src/components/Login/LocalLogin.tsx
@@ -1,3 +1,4 @@
+import { LoginIcon, SupportIcon } from '@heroicons/react/outline';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import Link from 'next/link';
@@ -103,6 +104,7 @@ const LocalLogin: React.FC = ({ revalidate }) => {
+
{intl.formatMessage(messages.forgotpassword)}
@@ -113,6 +115,7 @@ const LocalLogin: React.FC = ({ revalidate }) => {
type="submit"
disabled={isSubmitting || !isValid}
>
+
{isSubmitting
? intl.formatMessage(messages.signingin)
: intl.formatMessage(messages.signin)}
diff --git a/src/components/Login/index.tsx b/src/components/Login/index.tsx
index 0474b9da14..d877e16054 100644
--- a/src/components/Login/index.tsx
+++ b/src/components/Login/index.tsx
@@ -1,3 +1,4 @@
+import { XCircleIcon } from '@heroicons/react/solid';
import axios from 'axios';
import { useRouter } from 'next/dist/client/router';
import React, { useEffect, useState } from 'react';
@@ -100,19 +101,7 @@ const Login: React.FC = () => {
diff --git a/src/components/MediaSlider/ShowMoreCard/index.tsx b/src/components/MediaSlider/ShowMoreCard/index.tsx
index e229ad7ef9..c429096697 100644
--- a/src/components/MediaSlider/ShowMoreCard/index.tsx
+++ b/src/components/MediaSlider/ShowMoreCard/index.tsx
@@ -1,3 +1,4 @@
+import { ArrowCircleRightIcon } from '@heroicons/react/solid';
import Link from 'next/link';
import React, { useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
@@ -79,18 +80,7 @@ const ShowMoreCard: React.FC = ({ url, posters }) => {
)}
-
-
-
+
{intl.formatMessage(messages.seemore)}
diff --git a/src/components/MediaSlider/index.tsx b/src/components/MediaSlider/index.tsx
index c46e16bcf8..64aa79153d 100644
--- a/src/components/MediaSlider/index.tsx
+++ b/src/components/MediaSlider/index.tsx
@@ -1,3 +1,4 @@
+import { ArrowCircleRightIcon } from '@heroicons/react/outline';
import Link from 'next/link';
import React, { useContext, useEffect } from 'react';
import { useSWRInfinite } from 'swr';
@@ -140,20 +141,7 @@ const MediaSlider: React.FC
= ({
{title}
-
-
-
+
) : (
diff --git a/src/components/MovieDetails/index.tsx b/src/components/MovieDetails/index.tsx
index dadd9c1ad6..baa39a8e5c 100644
--- a/src/components/MovieDetails/index.tsx
+++ b/src/components/MovieDetails/index.tsx
@@ -1,3 +1,9 @@
+import { ArrowCircleRightIcon, CogIcon } from '@heroicons/react/outline';
+import {
+ CheckCircleIcon,
+ DocumentRemoveIcon,
+ ExternalLinkIcon,
+} from '@heroicons/react/solid';
import axios from 'axios';
import Link from 'next/link';
import { useRouter } from 'next/router';
@@ -50,7 +56,7 @@ const messages = defineMessages({
manageModalTitle: 'Manage Movie',
manageModalRequests: 'Requests',
manageModalNoRequests: 'No requests.',
- manageModalClearMedia: 'Clear All Media Data',
+ manageModalClearMedia: 'Clear Media Data',
manageModalClearMediaWarning:
'* This will irreversibly remove all data for this movie, including any requests. If this item exists in your Plex library, the media information will be recreated during the next scan.',
studio: '{studioCount, plural, one {Studio} other {Studios}}',
@@ -266,18 +272,7 @@ const MovieDetails: React.FC = ({ movie }) => {
className="w-full sm:mb-0"
buttonType="success"
>
-
-
-
+
{intl.formatMessage(messages.markavailable)}
@@ -291,18 +286,7 @@ const MovieDetails: React.FC
= ({ movie }) => {
className="w-full sm:mb-0"
buttonType="success"
>
-
-
-
+
{intl.formatMessage(messages.mark4kavailable)}
@@ -341,15 +325,7 @@ const MovieDetails: React.FC = ({ movie }) => {
className="block mb-2 last:mb-0"
>
-
-
-
-
+
{intl.formatMessage(messages.openradarr)}
@@ -361,15 +337,7 @@ const MovieDetails: React.FC = ({ movie }) => {
rel="noreferrer"
>
-
-
-
-
+
{intl.formatMessage(messages.openradarr4k)}
@@ -383,6 +351,7 @@ const MovieDetails: React.FC = ({ movie }) => {
confirmText={intl.formatMessage(globalMessages.areyousure)}
className="w-full"
>
+
{intl.formatMessage(messages.manageModalClearMedia)}
@@ -463,27 +432,7 @@ const MovieDetails: React.FC
= ({ movie }) => {
className="ml-2 first:ml-0"
onClick={() => setShowManager(true)}
>
-
-
-
-
+
)}
@@ -513,20 +462,7 @@ const MovieDetails: React.FC = ({ movie }) => {
{intl.formatMessage(messages.viewfullcrew)}
-
-
-
+
@@ -709,20 +645,7 @@ const MovieDetails: React.FC
= ({ movie }) => {
{intl.formatMessage(messages.cast)}
-
-
-
+
diff --git a/src/components/PersonCard/index.tsx b/src/components/PersonCard/index.tsx
index 67cdf43ffb..85e41ff33f 100644
--- a/src/components/PersonCard/index.tsx
+++ b/src/components/PersonCard/index.tsx
@@ -1,3 +1,4 @@
+import { UserCircleIcon } from '@heroicons/react/solid';
import Link from 'next/link';
import React, { useState } from 'react';
import CachedImage from '../Common/CachedImage';
@@ -57,18 +58,7 @@ const PersonCard: React.FC = ({
/>
) : (
-
-
-
+
)}
{name}
diff --git a/src/components/PlexLoginButton/index.tsx b/src/components/PlexLoginButton/index.tsx
index f6bbb5b794..2125f00537 100644
--- a/src/components/PlexLoginButton/index.tsx
+++ b/src/components/PlexLoginButton/index.tsx
@@ -1,3 +1,4 @@
+import { LoginIcon } from '@heroicons/react/outline';
import React, { useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import globalMessages from '../../i18n/globalMessages';
@@ -48,6 +49,7 @@ const PlexLoginButton: React.FC
= ({
disabled={loading || isProcessing}
className="plex-button"
>
+
{loading
? intl.formatMessage(globalMessages.loading)
: isProcessing
diff --git a/src/components/RegionSelector/index.tsx b/src/components/RegionSelector/index.tsx
index 2cbdc43515..dbbae3f935 100644
--- a/src/components/RegionSelector/index.tsx
+++ b/src/components/RegionSelector/index.tsx
@@ -1,11 +1,12 @@
-import React, { useEffect, useMemo, useState } from 'react';
import { Listbox, Transition } from '@headlessui/react';
+import { CheckIcon, ChevronDownIcon } from '@heroicons/react/solid';
+import { hasFlag } from 'country-flag-icons';
+import 'country-flag-icons/3x2/flags.css';
+import React, { useEffect, useMemo, useState } from 'react';
+import { defineMessages, useIntl } from 'react-intl';
import useSWR from 'swr';
import type { Region } from '../../../server/lib/settings';
-import { defineMessages, useIntl } from 'react-intl';
import useSettings from '../../hooks/useSettings';
-import { hasFlag } from 'country-flag-icons';
-import 'country-flag-icons/3x2/flags.css';
const messages = defineMessages({
regionDefault: 'All Regions',
@@ -125,20 +126,7 @@ const RegionSelector: React.FC = ({
: intl.formatMessage(messages.regionDefault)}
-
-
-
+
@@ -196,18 +184,7 @@ const RegionSelector: React.FC = ({
active ? 'text-white' : 'text-indigo-600'
} absolute inset-y-0 left-0 flex items-center pl-1.5`}
>
-
-
-
+
)}
@@ -234,18 +211,7 @@ const RegionSelector: React.FC
= ({
active ? 'text-white' : 'text-indigo-600'
} absolute inset-y-0 left-0 flex items-center pl-1.5`}
>
-
-
-
+
)}
@@ -286,18 +252,7 @@ const RegionSelector: React.FC = ({
active ? 'text-white' : 'text-indigo-600'
} absolute inset-y-0 left-0 flex items-center pl-1.5`}
>
-
-
-
+
)}
diff --git a/src/components/RequestBlock/index.tsx b/src/components/RequestBlock/index.tsx
index c23370e949..d003bcf729 100644
--- a/src/components/RequestBlock/index.tsx
+++ b/src/components/RequestBlock/index.tsx
@@ -1,3 +1,12 @@
+import {
+ CalendarIcon,
+ CheckIcon,
+ EyeIcon,
+ PencilIcon,
+ TrashIcon,
+ UserIcon,
+ XIcon,
+} from '@heroicons/react/solid';
import axios from 'axios';
import React, { useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
@@ -69,37 +78,14 @@ const RequestBlock: React.FC = ({ request, onUpdate }) => {
-
-
-
+
{request.requestedBy.displayName}
{request.modifiedBy && (
-
-
-
-
+
{request.modifiedBy?.displayName}
@@ -115,18 +101,7 @@ const RequestBlock: React.FC
= ({ request, onUpdate }) => {
onClick={() => updateRequest('approve')}
disabled={isUpdating}
>
-
-
-
+
@@ -135,18 +110,7 @@ const RequestBlock: React.FC = ({ request, onUpdate }) => {
onClick={() => updateRequest('decline')}
disabled={isUpdating}
>
-
-
-
+
@@ -155,14 +119,7 @@ const RequestBlock: React.FC = ({ request, onUpdate }) => {
onClick={() => setShowEditModal(true)}
disabled={isUpdating}
>
-
-
-
+
>
@@ -173,18 +130,7 @@ const RequestBlock: React.FC = ({ request, onUpdate }) => {
onClick={() => deleteRequest()}
disabled={isUpdating}
>
-
-
-
+
)}
@@ -215,18 +161,7 @@ const RequestBlock: React.FC
= ({ request, onUpdate }) => {
-
-
-
+
{intl.formatDate(request.createdAt, {
year: 'numeric',
diff --git a/src/components/RequestButton/index.tsx b/src/components/RequestButton/index.tsx
index 2c26c694f7..c5fef6fd7d 100644
--- a/src/components/RequestButton/index.tsx
+++ b/src/components/RequestButton/index.tsx
@@ -1,3 +1,9 @@
+import { DownloadIcon } from '@heroicons/react/outline';
+import {
+ CheckIcon,
+ InformationCircleIcon,
+ XIcon,
+} from '@heroicons/react/solid';
import axios from 'axios';
import React, { useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
@@ -117,22 +123,7 @@ const RequestButton: React.FC = ({
action: () => {
setShowRequestModal(true);
},
- svg: (
-
-
-
- ),
+ svg: ,
});
}
@@ -150,22 +141,7 @@ const RequestButton: React.FC = ({
action: () => {
setShowRequestModal(true);
},
- svg: (
-
-
-
- ),
+ svg: ,
});
}
@@ -183,22 +159,7 @@ const RequestButton: React.FC = ({
action: () => {
setShowRequest4kModal(true);
},
- svg: (
-
-
-
- ),
+ svg: ,
});
}
@@ -218,22 +179,7 @@ const RequestButton: React.FC = ({
action: () => {
setShowRequest4kModal(true);
},
- svg: (
-
-
-
- ),
+ svg: ,
});
}
@@ -246,20 +192,7 @@ const RequestButton: React.FC = ({
id: 'active-request',
text: intl.formatMessage(messages.viewrequest),
action: () => setShowRequestModal(true),
- svg: (
-
-
-
- ),
+ svg: ,
});
}
@@ -273,20 +206,7 @@ const RequestButton: React.FC = ({
id: 'active-4k-request',
text: intl.formatMessage(messages.viewrequest4k),
action: () => setShowRequest4kModal(true),
- svg: (
-
-
-
- ),
+ svg: ,
});
}
@@ -302,20 +222,7 @@ const RequestButton: React.FC = ({
action: () => {
modifyRequest(activeRequest, 'approve');
},
- svg: (
-
-
-
- ),
+ svg: ,
},
{
id: 'decline-request',
@@ -323,20 +230,7 @@ const RequestButton: React.FC = ({
action: () => {
modifyRequest(activeRequest, 'decline');
},
- svg: (
-
-
-
- ),
+ svg: ,
}
);
}
@@ -356,20 +250,7 @@ const RequestButton: React.FC = ({
action: () => {
modifyRequests(activeRequests, 'approve');
},
- svg: (
-
-
-
- ),
+ svg: ,
},
{
id: 'decline-request-batch',
@@ -379,20 +260,7 @@ const RequestButton: React.FC = ({
action: () => {
modifyRequests(activeRequests, 'decline');
},
- svg: (
-
-
-
- ),
+ svg: ,
}
);
}
@@ -409,20 +277,7 @@ const RequestButton: React.FC = ({
action: () => {
modifyRequest(active4kRequest, 'approve');
},
- svg: (
-
-
-
- ),
+ svg: ,
},
{
id: 'decline-4k-request',
@@ -430,20 +285,7 @@ const RequestButton: React.FC = ({
action: () => {
modifyRequest(active4kRequest, 'decline');
},
- svg: (
-
-
-
- ),
+ svg: ,
}
);
}
@@ -456,50 +298,24 @@ const RequestButton: React.FC = ({
) {
buttons.push(
{
- id: 'approve-request-batch',
+ id: 'approve-4k-request-batch',
text: intl.formatMessage(messages.approve4krequests, {
requestCount: active4kRequests.length,
}),
action: () => {
modifyRequests(active4kRequests, 'approve');
},
- svg: (
-
-
-
- ),
+ svg: ,
},
{
- id: 'decline-request-batch',
+ id: 'decline-4k-request-batch',
text: intl.formatMessage(messages.decline4krequests, {
requestCount: active4kRequests.length,
}),
action: () => {
modifyRequests(active4kRequests, 'decline');
},
- svg: (
-
-
-
- ),
+ svg: ,
}
);
}
diff --git a/src/components/RequestCard/index.tsx b/src/components/RequestCard/index.tsx
index e6baf6b1bf..7e71813e30 100644
--- a/src/components/RequestCard/index.tsx
+++ b/src/components/RequestCard/index.tsx
@@ -1,3 +1,4 @@
+import { CheckIcon, TrashIcon, XIcon } from '@heroicons/react/solid';
import axios from 'axios';
import Link from 'next/link';
import React, { useContext, useEffect } from 'react';
@@ -68,20 +69,7 @@ const RequestCardError: React.FC = ({ mediaId }) => {
buttonSize="sm"
onClick={() => deleteRequest()}
>
-
-
-
+
{intl.formatMessage(messages.deleterequest)}
@@ -261,18 +249,7 @@ const RequestCard: React.FC = ({ request, onTitleData }) => {
buttonSize="sm"
onClick={() => modifyRequest('approve')}
>
-
-
-
+
{intl.formatMessage(globalMessages.approve)}
@@ -284,18 +261,7 @@ const RequestCard: React.FC = ({ request, onTitleData }) => {
buttonSize="sm"
onClick={() => modifyRequest('decline')}
>
-
-
-
+
{intl.formatMessage(globalMessages.decline)}
diff --git a/src/components/RequestList/RequestItem/index.tsx b/src/components/RequestList/RequestItem/index.tsx
index f9f512c962..13b7c01eeb 100644
--- a/src/components/RequestList/RequestItem/index.tsx
+++ b/src/components/RequestList/RequestItem/index.tsx
@@ -1,3 +1,10 @@
+import {
+ CheckIcon,
+ PencilIcon,
+ RefreshIcon,
+ TrashIcon,
+ XIcon,
+} from '@heroicons/react/solid';
import axios from 'axios';
import Link from 'next/link';
import React, { useContext, useState } from 'react';
@@ -66,20 +73,7 @@ const RequestItemError: React.FC = ({
buttonSize="sm"
onClick={() => deleteRequest()}
>
-
-
-
+
{intl.formatMessage(messages.deleterequest)}
@@ -377,18 +371,7 @@ const RequestItem: React.FC = ({
confirmText={intl.formatMessage(globalMessages.areyousure)}
className="w-full"
>
-
-
-
+
{intl.formatMessage(messages.cancelRequest)}
@@ -404,19 +387,14 @@ const RequestItem: React.FC = ({
disabled={isRetrying}
onClick={() => retryRequest()}
>
-
-
-
-
+
- {intl.formatMessage(globalMessages.retry)}
+ {intl.formatMessage(
+ isRetrying ? globalMessages.retrying : globalMessages.retry
+ )}
)}
@@ -427,18 +405,7 @@ const RequestItem: React.FC = ({
confirmText={intl.formatMessage(globalMessages.areyousure)}
className="w-full"
>
-
-
-
+
{intl.formatMessage(globalMessages.delete)}
@@ -454,18 +421,7 @@ const RequestItem: React.FC = ({
buttonType="success"
onClick={() => modifyRequest('approve')}
>
-
-
-
+
{intl.formatMessage(globalMessages.approve)}
@@ -477,18 +433,7 @@ const RequestItem: React.FC = ({
buttonType="danger"
onClick={() => modifyRequest('decline')}
>
-
-
-
+
{intl.formatMessage(globalMessages.decline)}
@@ -501,14 +446,7 @@ const RequestItem: React.FC = ({
buttonType="primary"
onClick={() => setShowEditModal(true)}
>
-
-
-
+
{intl.formatMessage(globalMessages.edit)}
diff --git a/src/components/RequestList/index.tsx b/src/components/RequestList/index.tsx
index d0177e641d..fd32810159 100644
--- a/src/components/RequestList/index.tsx
+++ b/src/components/RequestList/index.tsx
@@ -1,3 +1,4 @@
+import { FilterIcon, SortDescendingIcon } from '@heroicons/react/solid';
import Link from 'next/link';
import { useRouter } from 'next/router';
import React, { useEffect, useState } from 'react';
@@ -119,18 +120,7 @@ const RequestList: React.FC = () => {
-
-
-
+
= ({
return (
<>
-
-
-
-
+
{intl.formatMessage(messages.advancedoptions)}
@@ -526,19 +523,7 @@ const AdvancedRequester: React.FC
= ({
-
-
-
+
@@ -592,18 +577,7 @@ const AdvancedRequester: React.FC = ({
: 'text-indigo-600'
} absolute inset-y-0 left-0 flex items-center pl-1.5`}
>
-
-
-
+
)}
diff --git a/src/components/RequestModal/MovieRequestModal.tsx b/src/components/RequestModal/MovieRequestModal.tsx
index 536dc5a791..c150d242da 100644
--- a/src/components/RequestModal/MovieRequestModal.tsx
+++ b/src/components/RequestModal/MovieRequestModal.tsx
@@ -1,3 +1,4 @@
+import { DownloadIcon } from '@heroicons/react/outline';
import axios from 'axios';
import React, { useCallback, useEffect, useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
@@ -11,7 +12,6 @@ import { MediaRequest } from '../../../server/entity/MediaRequest';
import { QuotaResponse } from '../../../server/interfaces/api/userInterfaces';
import { Permission } from '../../../server/lib/permissions';
import { MovieDetails } from '../../../server/models/Movie';
-import DownloadIcon from '../../assets/download.svg';
import { useUser } from '../../hooks/useUser';
import globalMessages from '../../i18n/globalMessages';
import Alert from '../Common/Alert';
diff --git a/src/components/RequestModal/QuotaDisplay/index.tsx b/src/components/RequestModal/QuotaDisplay/index.tsx
index 5d3decdcc5..223540d5d6 100644
--- a/src/components/RequestModal/QuotaDisplay/index.tsx
+++ b/src/components/RequestModal/QuotaDisplay/index.tsx
@@ -1,3 +1,4 @@
+import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/solid';
import Link from 'next/link';
import React, { useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
@@ -90,31 +91,9 @@ const QuotaDisplay: React.FC = ({
{showDetails ? (
-
-
-
+
) : (
-
-
-
+
)}
diff --git a/src/components/RequestModal/SearchByNameModal/index.tsx b/src/components/RequestModal/SearchByNameModal/index.tsx
index 50df046918..111d6137f4 100644
--- a/src/components/RequestModal/SearchByNameModal/index.tsx
+++ b/src/components/RequestModal/SearchByNameModal/index.tsx
@@ -1,3 +1,4 @@
+import { DownloadIcon } from '@heroicons/react/outline';
import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
import useSWR from 'swr';
@@ -51,22 +52,7 @@ const SearchByNameModal: React.FC = ({
okText={intl.formatMessage(globalMessages.next)}
okDisabled={!tvdbId}
okButtonType="primary"
- iconSvg={
-
-
-
- }
+ iconSvg={ }
>
= ({
? intl.formatMessage(globalMessages.back)
: intl.formatMessage(globalMessages.cancel)
}
- iconSvg={
-
-
-
- }
+ iconSvg={ }
>
{hasPermission(
[
diff --git a/src/components/ResetPassword/RequestResetLink.tsx b/src/components/ResetPassword/RequestResetLink.tsx
index f9359772d0..74c342fa28 100644
--- a/src/components/ResetPassword/RequestResetLink.tsx
+++ b/src/components/ResetPassword/RequestResetLink.tsx
@@ -1,3 +1,4 @@
+import { AtSymbolIcon } from '@heroicons/react/outline';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import Link from 'next/link';
@@ -123,6 +124,7 @@ const ResetPassword: React.FC = () => {
type="submit"
disabled={isSubmitting || !isValid}
>
+
{intl.formatMessage(messages.emailresetlink)}
diff --git a/src/components/Settings/CopyButton.tsx b/src/components/Settings/CopyButton.tsx
index abc5d09de9..4ae21190af 100644
--- a/src/components/Settings/CopyButton.tsx
+++ b/src/components/Settings/CopyButton.tsx
@@ -1,7 +1,8 @@
+import { ClipboardCopyIcon } from '@heroicons/react/solid';
import React, { useEffect } from 'react';
-import useClipboard from 'react-use-clipboard';
-import { useToasts } from 'react-toast-notifications';
import { defineMessages, useIntl } from 'react-intl';
+import { useToasts } from 'react-toast-notifications';
+import useClipboard from 'react-use-clipboard';
const messages = defineMessages({
copied: 'Copied API key to clipboard.',
@@ -29,17 +30,9 @@ const CopyButton: React.FC<{ textToCopy: string }> = ({ textToCopy }) => {
e.preventDefault();
setCopied();
}}
- className="-ml-px relative inline-flex items-center px-4 py-2 border border-gray-500 text-sm leading-5 font-medium text-white bg-indigo-600 hover:bg-indigo-500 focus:outline-none focus:ring-blue focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150"
+ className="relative inline-flex items-center px-4 py-2 -ml-px text-sm font-medium leading-5 text-white transition duration-150 ease-in-out bg-indigo-600 border border-gray-500 hover:bg-indigo-500 focus:outline-none focus:ring-blue focus:border-blue-300 active:bg-gray-100 active:text-gray-700"
>
-
-
-
-
+
);
};
diff --git a/src/components/Settings/LibraryItem.tsx b/src/components/Settings/LibraryItem.tsx
index d758645424..a1accde475 100644
--- a/src/components/Settings/LibraryItem.tsx
+++ b/src/components/Settings/LibraryItem.tsx
@@ -1,3 +1,4 @@
+import { CheckIcon, XIcon } from '@heroicons/react/solid';
import React from 'react';
interface LibraryItemProps {
@@ -12,8 +13,8 @@ const LibraryItem: React.FC = ({
onToggle,
}) => {
return (
-
-
+
+
diff --git a/src/components/Settings/RadarrModal/index.tsx b/src/components/Settings/RadarrModal/index.tsx
index bf3b0f20d4..8ea9093a1a 100644
--- a/src/components/Settings/RadarrModal/index.tsx
+++ b/src/components/Settings/RadarrModal/index.tsx
@@ -1,3 +1,4 @@
+import { PencilIcon, PlusIcon } from '@heroicons/react/solid';
import axios from 'axios';
import { Field, Formik } from 'formik';
import dynamic from 'next/dynamic';
@@ -356,6 +357,13 @@ const RadarrModal: React.FC = ({
values.is4k ? messages.edit4kradarr : messages.editradarr
)
}
+ iconSvg={
+ !radarr ? (
+
+ ) : (
+
+ )
+ }
>
diff --git a/src/components/Settings/SettingsAbout/Releases/index.tsx b/src/components/Settings/SettingsAbout/Releases/index.tsx
index 1230185e37..49a0c88b90 100644
--- a/src/components/Settings/SettingsAbout/Releases/index.tsx
+++ b/src/components/Settings/SettingsAbout/Releases/index.tsx
@@ -1,3 +1,4 @@
+import { DocumentTextIcon } from '@heroicons/react/outline';
import React, { useState } from 'react';
import { defineMessages, FormattedRelativeTime, useIntl } from 'react-intl';
import ReactMarkdown from 'react-markdown';
@@ -70,22 +71,7 @@ const Release: React.FC
= ({
>
setModalOpen(false)}
- iconSvg={
-
-
-
- }
+ iconSvg={ }
title={intl.formatMessage(messages.versionChangelog)}
cancelText={intl.formatMessage(globalMessages.close)}
okText={intl.formatMessage(messages.viewongithub)}
@@ -126,6 +112,7 @@ const Release: React.FC = ({
setModalOpen(true)}>
+
{intl.formatMessage(messages.viewchangelog)}
diff --git a/src/components/Settings/SettingsJobsCache/index.tsx b/src/components/Settings/SettingsJobsCache/index.tsx
index 46d0cd9d78..ffd3446ff0 100644
--- a/src/components/Settings/SettingsJobsCache/index.tsx
+++ b/src/components/Settings/SettingsJobsCache/index.tsx
@@ -1,3 +1,4 @@
+import { PlayIcon, StopIcon, XCircleIcon } from '@heroicons/react/solid';
import axios from 'axios';
import React from 'react';
import {
@@ -146,12 +147,12 @@ const SettingsJobs: React.FC = () => {
- {job.running && }
{intl.formatMessage(
messages[job.id] ?? messages.unknownJob
)}
+ {job.running && }
@@ -180,10 +181,12 @@ const SettingsJobs: React.FC = () => {
{job.running ? (
cancelJob(job)}>
+
{intl.formatMessage(messages.canceljob)}
) : (
runJob(job)}>
+
{intl.formatMessage(messages.runnow)}
)}
@@ -223,6 +226,7 @@ const SettingsJobs: React.FC = () => {
{formatBytes(cache.stats.vsize)}
flushCache(cache)}>
+
{intl.formatMessage(messages.flushcache)}
diff --git a/src/components/Settings/SettingsLogs/index.tsx b/src/components/Settings/SettingsLogs/index.tsx
index fe1845c279..a4aaf755e1 100644
--- a/src/components/Settings/SettingsLogs/index.tsx
+++ b/src/components/Settings/SettingsLogs/index.tsx
@@ -1,3 +1,10 @@
+import {
+ ClipboardCopyIcon,
+ DocumentSearchIcon,
+ FilterIcon,
+ PauseIcon,
+ PlayIcon,
+} from '@heroicons/react/solid';
import copy from 'copy-to-clipboard';
import { useRouter } from 'next/router';
import React, { useEffect, useState } from 'react';
@@ -135,6 +142,7 @@ const SettingsLogs: React.FC = () => {
>
}
onCancel={() => setActiveLog(null)}
cancelText={intl.formatMessage(globalMessages.close)}
onOk={() => (activeLog ? copyLogString(activeLog) : undefined)}
@@ -237,31 +245,9 @@ const SettingsLogs: React.FC = () => {
>
{refreshInterval ? (
-
-
-
+
) : (
-
-
-
+
)}
@@ -273,18 +259,7 @@ const SettingsLogs: React.FC = () => {
-
-
-
+
{
onClick={() => setActiveLog(row)}
className="mr-2"
>
-
-
-
-
+
)}
{
buttonSize="sm"
onClick={() => copyLogString(row)}
>
-
-
-
-
+
diff --git a/src/components/Settings/SettingsMain.tsx b/src/components/Settings/SettingsMain.tsx
index 177efd53f9..d3a6693170 100644
--- a/src/components/Settings/SettingsMain.tsx
+++ b/src/components/Settings/SettingsMain.tsx
@@ -1,3 +1,4 @@
+import { RefreshIcon } from '@heroicons/react/solid';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import React, { useMemo } from 'react';
@@ -204,18 +205,7 @@ const SettingsMain: React.FC = () => {
}}
className="relative inline-flex items-center px-4 py-2 -ml-px text-sm font-medium leading-5 text-white transition duration-150 ease-in-out bg-indigo-600 border border-gray-500 rounded-r-md hover:bg-indigo-500 focus:outline-none focus:ring-blue focus:border-blue-300 active:bg-gray-100 active:text-gray-700"
>
-
-
-
+
diff --git a/src/components/Settings/SettingsNotifications.tsx b/src/components/Settings/SettingsNotifications.tsx
index e5a3bf9aa3..3c73c001b3 100644
--- a/src/components/Settings/SettingsNotifications.tsx
+++ b/src/components/Settings/SettingsNotifications.tsx
@@ -1,6 +1,7 @@
+import { AtSymbolIcon } from '@heroicons/react/outline';
+import { LightningBoltIcon } from '@heroicons/react/solid';
import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
-import Bolt from '../../assets/bolt.svg';
import DiscordLogo from '../../assets/extlogos/discord.svg';
import PushbulletLogo from '../../assets/extlogos/pushbullet.svg';
import PushoverLogo from '../../assets/extlogos/pushover.svg';
@@ -27,20 +28,7 @@ const SettingsNotifications: React.FC = ({ children }) => {
text: intl.formatMessage(messages.email),
content: (
-
-
-
+
{intl.formatMessage(messages.email)}
),
@@ -106,7 +94,7 @@ const SettingsNotifications: React.FC = ({ children }) => {
text: intl.formatMessage(messages.webhook),
content: (
-
+
{intl.formatMessage(messages.webhook)}
),
diff --git a/src/components/Settings/SettingsPlex.tsx b/src/components/Settings/SettingsPlex.tsx
index d410bb4933..05e07352cb 100644
--- a/src/components/Settings/SettingsPlex.tsx
+++ b/src/components/Settings/SettingsPlex.tsx
@@ -1,3 +1,4 @@
+import { RefreshIcon, SearchIcon, XIcon } from '@heroicons/react/solid';
import axios from 'axios';
import { Field, Formik } from 'formik';
import React, { useMemo, useState } from 'react';
@@ -7,7 +8,6 @@ import useSWR from 'swr';
import * as Yup from 'yup';
import type { PlexDevice } from '../../../server/interfaces/api/plexInterfaces';
import type { PlexSettings } from '../../../server/lib/settings';
-import Spinner from '../../assets/spinner.svg';
import globalMessages from '../../i18n/globalMessages';
import Alert from '../Common/Alert';
import Badge from '../Common/Badge';
@@ -433,22 +433,12 @@ const SettingsPlex: React.FC = ({ onComplete }) => {
}}
className="relative inline-flex items-center px-4 py-2 -ml-px text-sm font-medium leading-5 text-white transition duration-150 ease-in-out bg-indigo-600 border border-gray-500 rounded-r-md hover:bg-indigo-500 focus:outline-none focus:ring-blue focus:border-blue-300 active:bg-gray-100 active:text-gray-700"
>
- {isRefreshingPresets ? (
-
- ) : (
-
-
-
- )}
+
@@ -538,18 +528,10 @@ const SettingsPlex: React.FC = ({ onComplete }) => {
syncLibraries()} disabled={isSyncing}>
-
-
-
+
{isSyncing
? intl.formatMessage(messages.scanning)
: intl.formatMessage(messages.scan)}
@@ -623,40 +605,14 @@ const SettingsPlex: React.FC = ({ onComplete }) => {
{!dataSync?.running && (
startScan()}>
-
-
-
+
{intl.formatMessage(messages.startscan)}
)}
{dataSync?.running && (
cancelScan()}>
-
-
-
+
{intl.formatMessage(messages.cancelscan)}
)}
diff --git a/src/components/Settings/SettingsServices.tsx b/src/components/Settings/SettingsServices.tsx
index c4801a03ef..abc4f66ad1 100644
--- a/src/components/Settings/SettingsServices.tsx
+++ b/src/components/Settings/SettingsServices.tsx
@@ -1,3 +1,4 @@
+import { PencilIcon, PlusIcon, TrashIcon } from '@heroicons/react/solid';
import axios from 'axios';
import React, { useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
@@ -130,17 +131,8 @@ const ServerInstance: React.FC
= ({
onClick={() => onEdit()}
className="relative inline-flex items-center justify-center flex-1 w-0 py-4 -mr-px text-sm font-medium leading-5 text-gray-200 transition duration-150 ease-in-out border border-transparent rounded-bl-lg hover:text-white focus:outline-none focus:ring-blue focus:border-gray-500 focus:z-10"
>
-
-
-
-
- {intl.formatMessage(globalMessages.edit)}
-
+
+ {intl.formatMessage(globalMessages.edit)}
@@ -148,21 +140,8 @@ const ServerInstance: React.FC
= ({
onClick={() => onDelete()}
className="relative inline-flex items-center justify-center flex-1 w-0 py-4 text-sm font-medium leading-5 text-gray-200 transition duration-150 ease-in-out border border-transparent rounded-br-lg hover:text-white focus:outline-none focus:ring-blue focus:border-gray-500 focus:z-10"
>
-
-
-
-
- {intl.formatMessage(globalMessages.delete)}
-
+
+ {intl.formatMessage(globalMessages.delete)}
@@ -278,6 +257,7 @@ const SettingsServices: React.FC = () => {
})
}
title="Delete Server"
+ iconSvg={ }
>
{intl.formatMessage(messages.deleteserverconfirm)}
@@ -343,18 +323,7 @@ const SettingsServices: React.FC = () => {
setEditRadarrModal({ open: true, radarr: null })
}
>
-
-
-
+
{intl.formatMessage(messages.addradarr)}
@@ -434,18 +403,7 @@ const SettingsServices: React.FC = () => {
setEditSonarrModal({ open: true, sonarr: null })
}
>
-
-
-
+
{intl.formatMessage(messages.addsonarr)}
diff --git a/src/components/Settings/SonarrModal/index.tsx b/src/components/Settings/SonarrModal/index.tsx
index 429a0fb627..ad73e45e44 100644
--- a/src/components/Settings/SonarrModal/index.tsx
+++ b/src/components/Settings/SonarrModal/index.tsx
@@ -1,3 +1,4 @@
+import { PencilIcon, PlusIcon } from '@heroicons/react/solid';
import axios from 'axios';
import { Field, Formik } from 'formik';
import dynamic from 'next/dynamic';
@@ -387,6 +388,13 @@ const SonarrModal: React.FC = ({
values.is4k ? messages.edit4ksonarr : messages.editsonarr
)
}
+ iconSvg={
+ !sonarr ? (
+
+ ) : (
+
+ )
+ }
>
diff --git a/src/components/Setup/SetupSteps.tsx b/src/components/Setup/SetupSteps.tsx
index 2429e3ca28..df65f56b48 100644
--- a/src/components/Setup/SetupSteps.tsx
+++ b/src/components/Setup/SetupSteps.tsx
@@ -1,3 +1,4 @@
+import { CheckIcon } from '@heroicons/react/solid';
import React from 'react';
interface CurrentStep {
@@ -17,26 +18,13 @@ const SetupSteps: React.FC
= ({
}) => {
return (
-
+
- {completed && (
-
-
-
- )}
+ {completed &&
}
{!completed && (
{stepNumber}
@@ -53,9 +41,9 @@ const SetupSteps: React.FC = ({
{!isLastStep && (
-
+
= ({
onClick={() => slide(Direction.LEFT)}
disabled={scrollPos.isStart}
>
-
-
-
+
= ({
onClick={() => slide(Direction.RIGHT)}
disabled={scrollPos.isEnd}
>
-
-
-
+
{
show={data.commitTag !== process.env.commitTag}
>
-
-
- }
+ iconSvg={ }
title={intl.formatMessage(messages.newversionavailable)}
onOk={() => location.reload()}
okText={intl.formatMessage(messages.reloadOverseerr)}
diff --git a/src/components/TitleCard/index.tsx b/src/components/TitleCard/index.tsx
index d009d6fe8e..a99dee1d84 100644
--- a/src/components/TitleCard/index.tsx
+++ b/src/components/TitleCard/index.tsx
@@ -1,3 +1,5 @@
+import { DownloadIcon } from '@heroicons/react/outline';
+import { BellIcon, CheckIcon, ClockIcon } from '@heroicons/react/solid';
import Link from 'next/link';
import React, { useCallback, useEffect, useState } from 'react';
import { useIntl } from 'react-intl';
@@ -128,32 +130,12 @@ const TitleCard: React.FC = ({
{(currentStatus === MediaStatus.AVAILABLE ||
currentStatus === MediaStatus.PARTIALLY_AVAILABLE) && (
)}
{currentStatus === MediaStatus.PENDING && (
)}
{currentStatus === MediaStatus.PROCESSING && (
@@ -161,18 +143,7 @@ const TitleCard: React.FC = ({
{inProgress ? (
) : (
-
-
-
+
)}
)}
@@ -265,20 +236,7 @@ const TitleCard: React.FC
= ({
}}
className="flex items-center justify-center w-full text-white transition duration-150 ease-in-out bg-indigo-600 rounded-md h-7 hover:bg-indigo-500 focus:border-indigo-700 focus:ring-indigo active:bg-indigo-700"
>
-
-
-
+
{intl.formatMessage(globalMessages.request)}
diff --git a/src/components/Toast/index.tsx b/src/components/Toast/index.tsx
index 5e94e3e142..aaad91a335 100644
--- a/src/components/Toast/index.tsx
+++ b/src/components/Toast/index.tsx
@@ -1,98 +1,41 @@
+import {
+ CheckCircleIcon,
+ ExclamationCircleIcon,
+ ExclamationIcon,
+ InformationCircleIcon,
+} from '@heroicons/react/outline';
+import { XIcon } from '@heroicons/react/solid';
import React from 'react';
import type { ToastProps } from 'react-toast-notifications';
const Toast: React.FC = ({ appearance, children, onDismiss }) => {
return (
-
-
-
+
+
+
{appearance === 'success' && (
-
-
-
+
)}
-
{appearance === 'error' && (
-
-
-
+
)}
-
{appearance === 'info' && (
-
-
-
+
)}
-
{appearance === 'warning' && (
-
-
-
+
)}
-
{children}
-
+
{children}
+
onDismiss()}
- className="inline-flex text-gray-400 focus:outline-none focus:text-gray-500 transition ease-in-out duration-150"
+ className="inline-flex text-gray-400 transition duration-150 ease-in-out focus:outline-none focus:text-gray-500"
>
-
-
-
+
diff --git a/src/components/TvDetails/index.tsx b/src/components/TvDetails/index.tsx
index 9632c56b5d..1d7d98fcb2 100644
--- a/src/components/TvDetails/index.tsx
+++ b/src/components/TvDetails/index.tsx
@@ -1,3 +1,9 @@
+import { ArrowCircleRightIcon, CogIcon } from '@heroicons/react/outline';
+import {
+ CheckCircleIcon,
+ DocumentRemoveIcon,
+ ExternalLinkIcon,
+} from '@heroicons/react/solid';
import axios from 'axios';
import Link from 'next/link';
import { useRouter } from 'next/router';
@@ -50,7 +56,7 @@ const messages = defineMessages({
manageModalTitle: 'Manage Series',
manageModalRequests: 'Requests',
manageModalNoRequests: 'No requests.',
- manageModalClearMedia: 'Clear All Media Data',
+ manageModalClearMedia: 'Clear Media Data',
manageModalClearMediaWarning:
'* This will irreversibly remove all data for this TV series, including any requests. If this item exists in your Plex library, the media information will be recreated during the next scan.',
originaltitle: 'Original Title',
@@ -302,18 +308,7 @@ const TvDetails: React.FC
= ({ tv }) => {
className="w-full sm:mb-0"
buttonType="success"
>
-
-
-
+
{intl.formatMessage(messages.markavailable)}
@@ -327,18 +322,7 @@ const TvDetails: React.FC
= ({ tv }) => {
className="w-full sm:mb-0"
buttonType="success"
>
-
-
-
+
{intl.formatMessage(messages.mark4kavailable)}
@@ -380,15 +364,7 @@ const TvDetails: React.FC = ({ tv }) => {
className="block mb-2 last:mb-0"
>
-
-
-
-
+
{intl.formatMessage(messages.opensonarr)}
@@ -400,15 +376,7 @@ const TvDetails: React.FC = ({ tv }) => {
rel="noreferrer"
>
-
-
-
-
+
{intl.formatMessage(messages.opensonarr4k)}
@@ -422,6 +390,7 @@ const TvDetails: React.FC = ({ tv }) => {
confirmText={intl.formatMessage(globalMessages.areyousure)}
className="w-full"
>
+
{intl.formatMessage(messages.manageModalClearMedia)}
@@ -501,26 +470,7 @@ const TvDetails: React.FC
= ({ tv }) => {
className="ml-2 first:ml-0"
onClick={() => setShowManager(true)}
>
-
-
-
-
+
)}
@@ -564,20 +514,7 @@ const TvDetails: React.FC = ({ tv }) => {
{intl.formatMessage(messages.viewfullcrew)}
-
-
-
+
@@ -734,20 +671,7 @@ const TvDetails: React.FC
= ({ tv }) => {
{intl.formatMessage(messages.cast)}
-
-
-
+
diff --git a/src/components/UserList/BulkEditModal.tsx b/src/components/UserList/BulkEditModal.tsx
index 96e80524a6..17473ec6b3 100644
--- a/src/components/UserList/BulkEditModal.tsx
+++ b/src/components/UserList/BulkEditModal.tsx
@@ -1,3 +1,4 @@
+import { PencilIcon } from '@heroicons/react/solid';
import axios from 'axios';
import React, { useEffect, useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
@@ -84,6 +85,7 @@ const BulkEditModal: React.FC
= ({
return (
}
onOk={() => {
updateUsers();
}}
diff --git a/src/components/UserList/index.tsx b/src/components/UserList/index.tsx
index 50f10d70ae..8d49fc6626 100644
--- a/src/components/UserList/index.tsx
+++ b/src/components/UserList/index.tsx
@@ -1,3 +1,10 @@
+import { TrashIcon } from '@heroicons/react/outline';
+import {
+ InboxInIcon,
+ PencilIcon,
+ SortDescendingIcon,
+ UserAddIcon,
+} from '@heroicons/react/solid';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import Link from 'next/link';
@@ -10,7 +17,6 @@ import * as Yup from 'yup';
import type { UserResultsResponse } from '../../../server/interfaces/api/userInterfaces';
import { UserSettingsNotificationsResponse } from '../../../server/interfaces/api/userSettingsInterfaces';
import { hasPermission } from '../../../server/lib/permissions';
-import AddUserIcon from '../../assets/useradd.svg';
import { useUpdateQueryParams } from '../../hooks/useUpdateQueryParams';
import { Permission, User, UserType, useUser } from '../../hooks/useUser';
import globalMessages from '../../i18n/globalMessages';
@@ -265,22 +271,7 @@ const UserList: React.FC = () => {
okButtonType="danger"
onCancel={() => setDeleteModal({ isOpen: false })}
title={intl.formatMessage(messages.deleteuser)}
- iconSvg={
-
-
-
- }
+ iconSvg={ }
>
{intl.formatMessage(messages.deleteconfirm)}
@@ -335,7 +326,7 @@ const UserList: React.FC = () => {
return (
}
+ iconSvg={ }
onOk={() => handleSubmit()}
okText={
isSubmitting
@@ -451,6 +442,7 @@ const UserList: React.FC = () => {
buttonType="primary"
onClick={() => setCreateModal({ isOpen: true })}
>
+
{intl.formatMessage(messages.createlocaluser)}
{
disabled={isImporting}
onClick={() => importFromPlex()}
>
+
{intl.formatMessage(messages.importfromplex)}
-
-
-
+
{
onClick={() => setShowBulkEditModal(true)}
disabled={selectedUsers.length === 0}
>
+
{intl.formatMessage(messages.bulkedit)}
)}
diff --git a/src/components/UserProfile/ProfileHeader/index.tsx b/src/components/UserProfile/ProfileHeader/index.tsx
index 774935a7e3..a7f26f4c04 100644
--- a/src/components/UserProfile/ProfileHeader/index.tsx
+++ b/src/components/UserProfile/ProfileHeader/index.tsx
@@ -1,3 +1,4 @@
+import { CogIcon, UserIcon } from '@heroicons/react/solid';
import Link from 'next/link';
import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
@@ -92,18 +93,7 @@ const ProfileHeader: React.FC = ({
passHref
>
-
-
-
+
{intl.formatMessage(messages.settings)}
@@ -116,18 +106,7 @@ const ProfileHeader: React.FC = ({
passHref
>
-
-
-
+
{intl.formatMessage(messages.profile)}
diff --git a/src/components/UserProfile/UserSettings/UserNotificationSettings/index.tsx b/src/components/UserProfile/UserSettings/UserNotificationSettings/index.tsx
index b52db48131..e0d68421d4 100644
--- a/src/components/UserProfile/UserSettings/UserNotificationSettings/index.tsx
+++ b/src/components/UserProfile/UserSettings/UserNotificationSettings/index.tsx
@@ -1,3 +1,4 @@
+import { AtSymbolIcon } from '@heroicons/react/outline';
import { useRouter } from 'next/router';
import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
@@ -33,20 +34,7 @@ const UserNotificationSettings: React.FC = ({ children }) => {
text: intl.formatMessage(messages.email),
content: (
-
-
-
+
{intl.formatMessage(messages.email)}
),
diff --git a/src/components/UserProfile/index.tsx b/src/components/UserProfile/index.tsx
index f9a6d311dc..e4dd601de3 100644
--- a/src/components/UserProfile/index.tsx
+++ b/src/components/UserProfile/index.tsx
@@ -1,3 +1,4 @@
+import { ArrowCircleRightIcon } from '@heroicons/react/outline';
import Link from 'next/link';
import { useRouter } from 'next/router';
import React, { useCallback, useEffect, useState } from 'react';
@@ -236,20 +237,7 @@ const UserProfile: React.FC = () => {
{intl.formatMessage(messages.recentrequests)}
-
-
-
+
diff --git a/src/i18n/globalMessages.ts b/src/i18n/globalMessages.ts
index 884acc4ad9..58d260e74d 100644
--- a/src/i18n/globalMessages.ts
+++ b/src/i18n/globalMessages.ts
@@ -24,6 +24,7 @@ const globalMessages = defineMessages({
decline: 'Decline',
delete: 'Delete',
retry: 'Retry',
+ retrying: 'Retrying…',
view: 'View',
deleting: 'Deleting…',
test: 'Test',
diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json
index 60c7ce3e5d..629deec273 100644
--- a/src/i18n/locale/en.json
+++ b/src/i18n/locale/en.json
@@ -64,7 +64,7 @@
"components.MovieDetails.budget": "Budget",
"components.MovieDetails.cast": "Cast",
"components.MovieDetails.downloadstatus": "Download Status",
- "components.MovieDetails.manageModalClearMedia": "Clear All Media Data",
+ "components.MovieDetails.manageModalClearMedia": "Clear Media Data",
"components.MovieDetails.manageModalClearMediaWarning": "* This will irreversibly remove all data for this movie, including any requests. If this item exists in your Plex library, the media information will be recreated during the next scan.",
"components.MovieDetails.manageModalNoRequests": "No requests.",
"components.MovieDetails.manageModalRequests": "Requests",
@@ -620,7 +620,7 @@
"components.TvDetails.episodeRuntime": "Episode Runtime",
"components.TvDetails.episodeRuntimeMinutes": "{runtime} minutes",
"components.TvDetails.firstAirDate": "First Air Date",
- "components.TvDetails.manageModalClearMedia": "Clear All Media Data",
+ "components.TvDetails.manageModalClearMedia": "Clear Media Data",
"components.TvDetails.manageModalClearMediaWarning": "* This will irreversibly remove all data for this TV series, including any requests. If this item exists in your Plex library, the media information will be recreated during the next scan.",
"components.TvDetails.manageModalNoRequests": "No requests.",
"components.TvDetails.manageModalRequests": "Requests",
@@ -799,6 +799,7 @@
"i18n.requesting": "Requesting…",
"i18n.resultsperpage": "Display {pageSize} results per page",
"i18n.retry": "Retry",
+ "i18n.retrying": "Retrying…",
"i18n.save": "Save Changes",
"i18n.saving": "Saving…",
"i18n.settings": "Settings",
diff --git a/src/pages/404.tsx b/src/pages/404.tsx
index 900f17af3b..da7507505c 100644
--- a/src/pages/404.tsx
+++ b/src/pages/404.tsx
@@ -1,3 +1,4 @@
+import { ArrowCircleRightIcon } from '@heroicons/react/outline';
import Link from 'next/link';
import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
@@ -22,22 +23,9 @@ const Custom404: React.FC = () => {
})}
-
+
{intl.formatMessage(messages.returnHome)}
-
-
-
+
diff --git a/src/pages/_error.tsx b/src/pages/_error.tsx
index bb24b57fb3..c1960638cc 100644
--- a/src/pages/_error.tsx
+++ b/src/pages/_error.tsx
@@ -1,9 +1,10 @@
-import React from 'react';
+import { ArrowCircleRightIcon } from '@heroicons/react/outline';
import type { NextPage } from 'next';
import Link from 'next/link';
-import type { Undefinable } from '../utils/typeHelpers';
+import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
import PageTitle from '../components/Common/PageTitle';
+import type { Undefinable } from '../utils/typeHelpers';
interface ErrorProps {
statusCode?: number;
@@ -45,22 +46,9 @@ const Error: NextPage
= ({ statusCode }) => {
: getErrorMessage(statusCode)}
-
+
{intl.formatMessage(messages.returnHome)}
-
-
-
+
diff --git a/yarn.lock b/yarn.lock
index 91f368f1b4..333bc24302 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1533,6 +1533,11 @@
resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.0.0.tgz#661b50ebfd25041abb45d8eedd85e7559056bcaf"
integrity sha512-mjqRJrgkbcHQBfAHnqH0yRxO/y/22jYrdltpE7WkurafREKZ+pj5bPBwYHMt935Sdz/n16yRcVmsSCqDFHee9A==
+"@heroicons/react@^1.0.1":
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/@heroicons/react/-/react-1.0.1.tgz#66d25f6441920bd5c2146ea27fd33995885452dd"
+ integrity sha512-uikw2gKCmqnvjVxitecWfFLMOKyL9BTFcU4VM3hHj9OMwpkCr5Ke+MRMyY2/aQVmsYs4VTq7NCFX05MYwAHi3g==
+
"@iarna/cli@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@iarna/cli/-/cli-1.2.0.tgz#0f7af5e851afe895104583c4ca07377a8094d641"
From db077700e42ab1d2c870213fd55bbdee74002775 Mon Sep 17 00:00:00 2001
From: TheCatLady <52870424+TheCatLady@users.noreply.github.com>
Date: Sat, 17 Apr 2021 06:07:37 -0400
Subject: [PATCH 03/13] fix(plex): add support for plex.direct URLs (#1437)
* fix(plex): add support for plex.direct URLs
* fix(ui): mark HTTPS Plex connections as secure
---
server/routes/settings/index.ts | 25 ++++++++++++++++++++++--
src/components/Settings/SettingsPlex.tsx | 23 +++++++++++-----------
src/i18n/locale/en.json | 2 +-
3 files changed, 36 insertions(+), 14 deletions(-)
diff --git a/server/routes/settings/index.ts b/server/routes/settings/index.ts
index 719e8c9f70..514aa1de91 100644
--- a/server/routes/settings/index.ts
+++ b/server/routes/settings/index.ts
@@ -4,11 +4,13 @@ import fs from 'fs';
import { merge, omit } from 'lodash';
import path from 'path';
import { getRepository } from 'typeorm';
+import { URL } from 'url';
import PlexAPI from '../../api/plexapi';
import PlexTvAPI from '../../api/plextv';
import Media from '../../entity/Media';
import { MediaRequest } from '../../entity/MediaRequest';
import { User } from '../../entity/User';
+import { PlexConnection } from '../../interfaces/api/plexInterfaces';
import {
LogMessage,
LogsResultsResponse,
@@ -129,13 +131,32 @@ settingsRoutes.get('/plex/devices/servers', async (req, res, next) => {
if (devices) {
await Promise.all(
devices.map(async (device) => {
+ const plexDirectConnections: PlexConnection[] = [];
+
+ device.connection.forEach((connection) => {
+ const url = new URL(connection.uri);
+
+ if (url.hostname !== connection.address) {
+ const plexDirectConnection = { ...connection };
+ plexDirectConnection.address = url.hostname;
+ plexDirectConnections.push(plexDirectConnection);
+
+ // Connect to IP addresses over HTTP
+ connection.protocol = 'http';
+ }
+ });
+
+ plexDirectConnections.forEach((plexDirectConnection) => {
+ device.connection.push(plexDirectConnection);
+ });
+
await Promise.all(
device.connection.map(async (connection) => {
const plexDeviceSettings = {
...settings.plex,
ip: connection.address,
port: connection.port,
- useSsl: !connection.local && connection.protocol === 'https',
+ useSsl: connection.protocol === 'https',
};
const plexClient = new PlexAPI({
plexToken: admin.plexToken,
@@ -149,7 +170,7 @@ settingsRoutes.get('/plex/devices/servers', async (req, res, next) => {
connection.message = 'OK';
} catch (e) {
connection.status = 500;
- connection.message = e.message;
+ connection.message = e.message.split(':')[0];
}
})
);
diff --git a/src/components/Settings/SettingsPlex.tsx b/src/components/Settings/SettingsPlex.tsx
index 05e07352cb..59ba75af45 100644
--- a/src/components/Settings/SettingsPlex.tsx
+++ b/src/components/Settings/SettingsPlex.tsx
@@ -1,6 +1,7 @@
import { RefreshIcon, SearchIcon, XIcon } from '@heroicons/react/solid';
import axios from 'axios';
import { Field, Formik } from 'formik';
+import { orderBy } from 'lodash';
import React, { useMemo, useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications';
@@ -28,7 +29,7 @@ const messages = defineMessages({
serverpresetPlaceholder: 'Plex Server',
serverLocal: 'local',
serverRemote: 'remote',
- serverConnected: 'connected',
+ serverSecure: 'secure',
serverpresetManualMessage: 'Manual configuration',
serverpresetRefreshing: 'Retrieving servers…',
serverpresetLoad: 'Press the button to load available servers',
@@ -131,7 +132,7 @@ const SettingsPlex: React.FC = ({ onComplete }) => {
dev.connection.forEach((conn) =>
finalPresets.push({
name: dev.name,
- ssl: !conn.local && conn.protocol === 'https',
+ ssl: conn.protocol === 'https',
uri: conn.uri,
address: conn.address,
port: conn.port,
@@ -141,14 +142,8 @@ const SettingsPlex: React.FC = ({ onComplete }) => {
})
);
});
- finalPresets.sort((a, b) => {
- if (a.status && !b.status) {
- return -1;
- } else {
- return 1;
- }
- });
- return finalPresets;
+
+ return orderBy(finalPresets, ['status', 'ssl'], ['desc', 'desc']);
}, [availableServers]);
const syncLibraries = async () => {
@@ -420,7 +415,13 @@ const SettingsPlex: React.FC = ({ onComplete }) => {
server.local
? intl.formatMessage(messages.serverLocal)
: intl.formatMessage(messages.serverRemote)
- }]
+ }]${
+ server.ssl
+ ? ` [${intl.formatMessage(
+ messages.serverSecure
+ )}]`
+ : ''
+ }
${server.status ? '' : '(' + server.message + ')'}
`}
diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json
index 629deec273..7807ff6295 100644
--- a/src/i18n/locale/en.json
+++ b/src/i18n/locale/en.json
@@ -560,9 +560,9 @@
"components.Settings.regionTip": "Filter content by regional availability",
"components.Settings.scan": "Sync Libraries",
"components.Settings.scanning": "Syncing…",
- "components.Settings.serverConnected": "connected",
"components.Settings.serverLocal": "local",
"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",
From 4449241a8f63fdaeaa4995aa7ec34127c322b9dd Mon Sep 17 00:00:00 2001
From: TheCatLady <52870424+TheCatLady@users.noreply.github.com>
Date: Sat, 17 Apr 2021 11:07:38 -0400
Subject: [PATCH 04/13] fix(api): add check for 4K request perms to request
creation endpoint (#1450)
---
server/routes/request.ts | 30 ++++++++++++++++++++++++++++++
1 file changed, 30 insertions(+)
diff --git a/server/routes/request.ts b/server/routes/request.ts
index 6ad4ac053d..9fb572631e 100644
--- a/server/routes/request.ts
+++ b/server/routes/request.ts
@@ -175,6 +175,36 @@ requestRoutes.post(
});
}
+ if (req.body.is4k) {
+ if (
+ req.body.mediaType === MediaType.MOVIE &&
+ !req.user?.hasPermission(
+ [Permission.REQUEST_4K, Permission.REQUEST_4K_MOVIE],
+ {
+ type: 'or',
+ }
+ )
+ ) {
+ return next({
+ status: 403,
+ message: 'You do not have permission to make 4K movie requests.',
+ });
+ } else if (
+ req.body.mediaType === MediaType.TV &&
+ !req.user?.hasPermission(
+ [Permission.REQUEST_4K, Permission.REQUEST_4K_TV],
+ {
+ type: 'or',
+ }
+ )
+ ) {
+ return next({
+ status: 403,
+ message: 'You do not have permission to make 4K series requests.',
+ });
+ }
+ }
+
const quotas = await requestUser.getQuota();
if (req.body.mediaType === MediaType.MOVIE && quotas.movie.restricted) {
From 5d1b741f55665c528e299a09464dff6d66f72666 Mon Sep 17 00:00:00 2001
From: TheCatLady <52870424+TheCatLady@users.noreply.github.com>
Date: Sun, 18 Apr 2021 04:41:02 -0400
Subject: [PATCH 05/13] fix(ui): adjust user list buttons on mobile (#1452)
---
src/components/UserList/index.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/components/UserList/index.tsx b/src/components/UserList/index.tsx
index 8d49fc6626..63f5bf5fd9 100644
--- a/src/components/UserList/index.tsx
+++ b/src/components/UserList/index.tsx
@@ -436,9 +436,9 @@ const UserList: React.FC = () => {
{intl.formatMessage(messages.userlist)}
-
+
setCreateModal({ isOpen: true })}
>
From 1702acf61c8268da8637f305154370b5f9c764ad Mon Sep 17 00:00:00 2001
From: TheCatLady <52870424+TheCatLady@users.noreply.github.com>
Date: Sun, 18 Apr 2021 05:56:07 -0400
Subject: [PATCH 06/13] refactor(ui): add icons to PlayButton dropdown (#1457)
---
src/components/Common/PlayButton/index.tsx | 9 +++++----
src/components/MovieDetails/index.tsx | 10 +++++++++-
src/components/TvDetails/index.tsx | 10 +++++++++-
3 files changed, 23 insertions(+), 6 deletions(-)
diff --git a/src/components/Common/PlayButton/index.tsx b/src/components/Common/PlayButton/index.tsx
index 614e2247a2..6ca9facebf 100644
--- a/src/components/Common/PlayButton/index.tsx
+++ b/src/components/Common/PlayButton/index.tsx
@@ -1,5 +1,4 @@
-import { PlayIcon } from '@heroicons/react/outline';
-import React from 'react';
+import React, { ReactNode } from 'react';
import ButtonWithDropdown from '../ButtonWithDropdown';
interface PlayButtonProps {
@@ -9,6 +8,7 @@ interface PlayButtonProps {
export interface PlayButtonLink {
text: string;
url: string;
+ svg: ReactNode;
}
const PlayButton: React.FC = ({ links }) => {
@@ -21,8 +21,8 @@ const PlayButton: React.FC = ({ links }) => {
buttonType="ghost"
text={
<>
-
- {links[0].text}
+ {links[0].svg}
+ {links[0].text}
>
}
onClick={() => {
@@ -39,6 +39,7 @@ const PlayButton: React.FC = ({ links }) => {
}}
buttonType="ghost"
>
+ {link.svg}
{link.text}
);
diff --git a/src/components/MovieDetails/index.tsx b/src/components/MovieDetails/index.tsx
index baa39a8e5c..7db6b94658 100644
--- a/src/components/MovieDetails/index.tsx
+++ b/src/components/MovieDetails/index.tsx
@@ -1,4 +1,9 @@
-import { ArrowCircleRightIcon, CogIcon } from '@heroicons/react/outline';
+import {
+ ArrowCircleRightIcon,
+ CogIcon,
+ FilmIcon,
+ PlayIcon,
+} from '@heroicons/react/outline';
import {
CheckCircleIcon,
DocumentRemoveIcon,
@@ -111,6 +116,7 @@ const MovieDetails: React.FC = ({ movie }) => {
mediaLinks.push({
text: intl.formatMessage(messages.playonplex),
url: data.mediaInfo?.plexUrl,
+ svg: ,
});
}
@@ -123,6 +129,7 @@ const MovieDetails: React.FC = ({ movie }) => {
mediaLinks.push({
text: intl.formatMessage(messages.play4konplex),
url: data.mediaInfo?.plexUrl4k,
+ svg: ,
});
}
@@ -135,6 +142,7 @@ const MovieDetails: React.FC = ({ movie }) => {
mediaLinks.push({
text: intl.formatMessage(messages.watchtrailer),
url: trailerUrl,
+ svg: ,
});
}
diff --git a/src/components/TvDetails/index.tsx b/src/components/TvDetails/index.tsx
index 1d7d98fcb2..69c0e9c39a 100644
--- a/src/components/TvDetails/index.tsx
+++ b/src/components/TvDetails/index.tsx
@@ -1,4 +1,9 @@
-import { ArrowCircleRightIcon, CogIcon } from '@heroicons/react/outline';
+import {
+ ArrowCircleRightIcon,
+ CogIcon,
+ FilmIcon,
+ PlayIcon,
+} from '@heroicons/react/outline';
import {
CheckCircleIcon,
DocumentRemoveIcon,
@@ -119,6 +124,7 @@ const TvDetails: React.FC = ({ tv }) => {
mediaLinks.push({
text: intl.formatMessage(messages.playonplex),
url: data.mediaInfo?.plexUrl,
+ svg: ,
});
}
@@ -131,6 +137,7 @@ const TvDetails: React.FC = ({ tv }) => {
mediaLinks.push({
text: intl.formatMessage(messages.play4konplex),
url: data.mediaInfo?.plexUrl4k,
+ svg: ,
});
}
@@ -143,6 +150,7 @@ const TvDetails: React.FC = ({ tv }) => {
mediaLinks.push({
text: intl.formatMessage(messages.watchtrailer),
url: trailerUrl,
+ svg: ,
});
}
From 3ae7d0098b225562499d7c8a74b8b6c3e8893ad9 Mon Sep 17 00:00:00 2001
From: TheCatLady <52870424+TheCatLady@users.noreply.github.com>
Date: Sun, 18 Apr 2021 06:05:02 -0400
Subject: [PATCH 07/13] fix(radarr): search in addition to monitoring existing
movies (#1449)
---
server/api/servarr/radarr.ts | 41 +++++++++++++++++++++++++++++++-----
server/api/servarr/sonarr.ts | 35 ++++++++++++++++++++++--------
2 files changed, 62 insertions(+), 14 deletions(-)
diff --git a/server/api/servarr/radarr.ts b/server/api/servarr/radarr.ts
index 59407720c2..0e0a41f184 100644
--- a/server/api/servarr/radarr.ts
+++ b/server/api/servarr/radarr.ts
@@ -72,7 +72,8 @@ class RadarrAPI extends ServarrBase<{ movieId: number }> {
} catch (e) {
logger.error('Error retrieving movie by TMDb ID', {
label: 'Radarr API',
- message: e.message,
+ errorMessage: e.message,
+ tmdbId: id,
});
throw new Error('Movie not found');
}
@@ -89,12 +90,13 @@ class RadarrAPI extends ServarrBase<{ movieId: number }> {
'Title already exists and is available. Skipping add and returning success',
{
label: 'Radarr',
+ movie,
}
);
return movie;
}
- // movie exists in radarr but is neither downloaded nor monitored
+ // movie exists in Radarr but is neither downloaded nor monitored
if (movie.id && !movie.monitored) {
const response = await this.axios.put(`/movie`, {
...movie,
@@ -115,16 +117,25 @@ class RadarrAPI extends ServarrBase<{ movieId: number }> {
if (response.data.monitored) {
logger.info(
- 'Found existing title in Radarr and set it to monitored. Returning success',
- { label: 'Radarr' }
+ 'Found existing title in Radarr and set it to monitored.',
+ {
+ label: 'Radarr',
+ movieId: response.data.id,
+ movieTitle: response.data.title,
+ }
);
logger.debug('Radarr update details', {
label: 'Radarr',
movie: response.data,
});
+
+ if (options.searchNow) {
+ this.searchMovie(response.data.id);
+ }
+
return response.data;
} else {
- logger.error('Failed to update existing movie in Radarr', {
+ logger.error('Failed to update existing movie in Radarr.', {
label: 'Radarr',
options,
});
@@ -183,6 +194,26 @@ class RadarrAPI extends ServarrBase<{ movieId: number }> {
throw new Error('Failed to add movie to Radarr');
}
};
+
+ public async searchMovie(movieId: number): Promise {
+ logger.info('Executing movie search command', {
+ label: 'Radarr API',
+ movieId,
+ });
+
+ try {
+ await this.runCommand('MoviesSearch', { movieIds: [movieId] });
+ } catch (e) {
+ logger.error(
+ 'Something went wrong while executing Radarr movie search.',
+ {
+ label: 'Radarr API',
+ errorMessage: e.message,
+ movieId,
+ }
+ );
+ }
+ }
}
export default RadarrAPI;
diff --git a/server/api/servarr/sonarr.ts b/server/api/servarr/sonarr.ts
index 1233783980..b6793ed3ff 100644
--- a/server/api/servarr/sonarr.ts
+++ b/server/api/servarr/sonarr.ts
@@ -113,7 +113,8 @@ class SonarrAPI extends ServarrBase<{ seriesId: number; episodeId: number }> {
} catch (e) {
logger.error('Error retrieving series by series title', {
label: 'Sonarr API',
- message: e.message,
+ errorMessage: e.message,
+ title,
});
throw new Error('No series found');
}
@@ -135,7 +136,8 @@ class SonarrAPI extends ServarrBase<{ seriesId: number; episodeId: number }> {
} catch (e) {
logger.error('Error retrieving series by tvdb ID', {
label: 'Sonarr API',
- message: e.message,
+ errorMessage: e.message,
+ tvdbId: id,
});
throw new Error('Series not found');
}
@@ -156,16 +158,21 @@ class SonarrAPI extends ServarrBase<{ seriesId: number; episodeId: number }> {
);
if (newSeriesResponse.data.id) {
- logger.info('Sonarr accepted request. Updated existing series', {
+ logger.info('Updated existing series in Sonarr.', {
label: 'Sonarr',
+ seriesId: newSeriesResponse.data.id,
+ seriesTitle: newSeriesResponse.data.title,
});
logger.debug('Sonarr update details', {
label: 'Sonarr',
movie: newSeriesResponse.data,
});
+
if (options.searchNow) {
this.searchSeries(newSeriesResponse.data.id);
}
+
+ return newSeriesResponse.data;
} else {
logger.error('Failed to update series in Sonarr', {
label: 'Sonarr',
@@ -173,8 +180,6 @@ class SonarrAPI extends ServarrBase<{ seriesId: number; episodeId: number }> {
});
throw new Error('Failed to update series in Sonarr');
}
-
- return newSeriesResponse.data;
}
const createdSeriesResponse = await this.axios.post(
@@ -223,7 +228,7 @@ class SonarrAPI extends ServarrBase<{ seriesId: number; episodeId: number }> {
logger.error('Something went wrong while adding a series to Sonarr.', {
label: 'Sonarr API',
errorMessage: e.message,
- error: e,
+ options,
response: e?.response?.data,
});
throw new Error('Failed to add series');
@@ -244,7 +249,7 @@ class SonarrAPI extends ServarrBase<{ seriesId: number; episodeId: number }> {
'Something went wrong while retrieving Sonarr language profiles.',
{
label: 'Sonarr API',
- message: e.message,
+ errorMessage: e.message,
}
);
@@ -253,11 +258,23 @@ class SonarrAPI extends ServarrBase<{ seriesId: number; episodeId: number }> {
}
public async searchSeries(seriesId: number): Promise {
- logger.info('Executing series search command', {
+ logger.info('Executing series search command.', {
label: 'Sonarr API',
seriesId,
});
- await this.runCommand('SeriesSearch', { seriesId });
+
+ try {
+ await this.runCommand('SeriesSearch', { seriesId });
+ } catch (e) {
+ logger.error(
+ 'Something went wrong while executing Sonarr series search.',
+ {
+ label: 'Sonarr API',
+ errorMessage: e.message,
+ seriesId,
+ }
+ );
+ }
}
private buildSeasonList(
From 4e98f567534a650e26b0244990b7ca549cecbe89 Mon Sep 17 00:00:00 2001
From: TheCatLady <52870424+TheCatLady@users.noreply.github.com>
Date: Sun, 18 Apr 2021 06:11:56 -0400
Subject: [PATCH 08/13] fix(notif): include year in notifications (#1439)
* fix(notif): include year in notifications
* fix(lang): fix shared overwritten strings
* fix: check if email agent is actually enabled when generating passwords
---
server/entity/MediaRequest.ts | 26 ++++++++++++++-----
server/lib/notifications/index.ts | 5 ++--
server/lib/settings.ts | 2 --
server/routes/user/index.ts | 5 +++-
.../Notifications/NotificationsDiscord.tsx | 4 +--
.../Notifications/NotificationsEmail.tsx | 4 +--
.../Notifications/NotificationsTelegram.tsx | 4 +--
src/i18n/locale/en.json | 4 ++-
8 files changed, 35 insertions(+), 19 deletions(-)
diff --git a/server/entity/MediaRequest.ts b/server/entity/MediaRequest.ts
index 167d1db066..be28e35d0b 100644
--- a/server/entity/MediaRequest.ts
+++ b/server/entity/MediaRequest.ts
@@ -142,7 +142,9 @@ export class MediaRequest {
if (this.type === MediaType.MOVIE) {
const movie = await tmdb.getMovie({ movieId: media.tmdbId });
notificationManager.sendNotification(Notification.MEDIA_PENDING, {
- subject: movie.title,
+ subject: `${movie.title}${
+ movie.release_date ? ` (${movie.release_date.slice(0, 4)})` : ''
+ }`,
message: movie.overview,
image: `https://image.tmdb.org/t/p/w600_and_h900_bestv2${movie.poster_path}`,
media,
@@ -153,7 +155,9 @@ export class MediaRequest {
if (this.type === MediaType.TV) {
const tv = await tmdb.getTvShow({ tvId: media.tmdbId });
notificationManager.sendNotification(Notification.MEDIA_PENDING, {
- subject: tv.name,
+ subject: `${tv.name}${
+ tv.first_air_date ? ` (${tv.first_air_date.slice(0, 4)})` : ''
+ }`,
message: tv.overview,
image: `https://image.tmdb.org/t/p/w600_and_h900_bestv2${tv.poster_path}`,
media,
@@ -210,7 +214,9 @@ export class MediaRequest {
: Notification.MEDIA_APPROVED
: Notification.MEDIA_DECLINED,
{
- subject: movie.title,
+ subject: `${movie.title}${
+ movie.release_date ? ` (${movie.release_date.slice(0, 4)})` : ''
+ }`,
message: movie.overview,
image: `https://image.tmdb.org/t/p/w600_and_h900_bestv2${movie.poster_path}`,
notifyUser: autoApproved ? undefined : this.requestedBy,
@@ -227,7 +233,9 @@ export class MediaRequest {
: Notification.MEDIA_APPROVED
: Notification.MEDIA_DECLINED,
{
- subject: tv.name,
+ subject: `${tv.name}${
+ tv.first_air_date ? ` (${tv.first_air_date.slice(0, 4)})` : ''
+ }`,
message: tv.overview,
image: `https://image.tmdb.org/t/p/w600_and_h900_bestv2${tv.poster_path}`,
notifyUser: autoApproved ? undefined : this.requestedBy,
@@ -492,7 +500,9 @@ export class MediaRequest {
);
notificationManager.sendNotification(Notification.MEDIA_FAILED, {
- subject: movie.title,
+ subject: `${movie.title}${
+ movie.release_date ? ` (${movie.release_date.slice(0, 4)})` : ''
+ }`,
message: movie.overview,
media,
image: `https://image.tmdb.org/t/p/w600_and_h900_bestv2${movie.poster_path}`,
@@ -700,7 +710,11 @@ export class MediaRequest {
);
notificationManager.sendNotification(Notification.MEDIA_FAILED, {
- subject: series.name,
+ subject: `${series.name}${
+ series.first_air_date
+ ? ` (${series.first_air_date.slice(0, 4)})`
+ : ''
+ }`,
message: series.overview,
image: `https://image.tmdb.org/t/p/w600_and_h900_bestv2${series.poster_path}`,
media,
diff --git a/server/lib/notifications/index.ts b/server/lib/notifications/index.ts
index f1f237f5ea..70af56ba23 100644
--- a/server/lib/notifications/index.ts
+++ b/server/lib/notifications/index.ts
@@ -1,5 +1,4 @@
import logger from '../../logger';
-import { getSettings } from '../settings';
import type { NotificationAgent, NotificationPayload } from './agents/agent';
export enum Notification {
@@ -45,13 +44,13 @@ class NotificationManager {
type: Notification,
payload: NotificationPayload
): void {
- const settings = getSettings().notifications;
logger.info(`Sending notification(s) for ${Notification[type]}`, {
label: 'Notifications',
subject: payload.subject,
});
+
this.activeAgents.forEach((agent) => {
- if (settings.enabled && agent.shouldSend(type)) {
+ if (agent.shouldSend(type)) {
agent.send(type, payload);
}
});
diff --git a/server/lib/settings.ts b/server/lib/settings.ts
index bb82c7ef94..290d404067 100644
--- a/server/lib/settings.ts
+++ b/server/lib/settings.ts
@@ -179,7 +179,6 @@ interface NotificationAgents {
}
interface NotificationSettings {
- enabled: boolean;
agents: NotificationAgents;
}
@@ -234,7 +233,6 @@ class Settings {
initialized: false,
},
notifications: {
- enabled: true,
agents: {
email: {
enabled: false,
diff --git a/server/routes/user/index.ts b/server/routes/user/index.ts
index 0c90469188..6546d666f5 100644
--- a/server/routes/user/index.ts
+++ b/server/routes/user/index.ts
@@ -84,7 +84,10 @@ router.post(
const passedExplicitPassword = body.password && body.password.length > 0;
const avatar = gravatarUrl(body.email, { default: 'mm', size: 200 });
- if (!passedExplicitPassword && !settings.notifications.agents.email) {
+ if (
+ !passedExplicitPassword &&
+ !settings.notifications.agents.email.enabled
+ ) {
throw new Error('Email notifications must be enabled');
}
diff --git a/src/components/Settings/Notifications/NotificationsDiscord.tsx b/src/components/Settings/Notifications/NotificationsDiscord.tsx
index ecaff4b058..b70baf2869 100644
--- a/src/components/Settings/Notifications/NotificationsDiscord.tsx
+++ b/src/components/Settings/Notifications/NotificationsDiscord.tsx
@@ -18,7 +18,7 @@ const messages = defineMessages({
webhookUrlPlaceholder: 'Server Settings → Integrations → Webhooks',
discordsettingssaved: 'Discord notification settings saved successfully!',
discordsettingsfailed: 'Discord notification settings failed to save.',
- testsent: 'Discord test notification sent!',
+ discordtestsent: 'Discord test notification sent!',
validationUrl: 'You must provide a valid URL',
});
@@ -96,7 +96,7 @@ const NotificationsDiscord: React.FC = () => {
},
});
- addToast(intl.formatMessage(messages.testsent), {
+ addToast(intl.formatMessage(messages.discordtestsent), {
appearance: 'info',
autoDismiss: true,
});
diff --git a/src/components/Settings/Notifications/NotificationsEmail.tsx b/src/components/Settings/Notifications/NotificationsEmail.tsx
index 1daca614f3..ac45e3e6b8 100644
--- a/src/components/Settings/Notifications/NotificationsEmail.tsx
+++ b/src/components/Settings/Notifications/NotificationsEmail.tsx
@@ -24,7 +24,7 @@ const messages = defineMessages({
authPass: 'SMTP Password',
emailsettingssaved: 'Email notification settings saved successfully!',
emailsettingsfailed: 'Email notification settings failed to save.',
- testsent: 'Email test notification sent!',
+ emailtestsent: 'Email test notification sent!',
allowselfsigned: 'Allow Self-Signed Certificates',
ssldisabletip:
'SSL should be disabled on standard TLS connections (port 587)',
@@ -188,7 +188,7 @@ const NotificationsEmail: React.FC = () => {
},
});
- addToast(intl.formatMessage(messages.testsent), {
+ addToast(intl.formatMessage(messages.emailtestsent), {
appearance: 'info',
autoDismiss: true,
});
diff --git a/src/components/Settings/Notifications/NotificationsTelegram.tsx b/src/components/Settings/Notifications/NotificationsTelegram.tsx
index 72b8d4dda4..6f8c230be5 100644
--- a/src/components/Settings/Notifications/NotificationsTelegram.tsx
+++ b/src/components/Settings/Notifications/NotificationsTelegram.tsx
@@ -22,7 +22,7 @@ const messages = defineMessages({
validationChatIdRequired: 'You must provide a valid chat ID',
telegramsettingssaved: 'Telegram notification settings saved successfully!',
telegramsettingsfailed: 'Telegram notification settings failed to save.',
- testsent: 'Telegram test notification sent!',
+ telegramtestsent: 'Telegram test notification sent!',
settinguptelegramDescription:
'To configure Telegram notifications, you will need to create a bot 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 @get_id_bot to the chat and issuing the /my_id
command.',
sendSilently: 'Send Silently',
@@ -113,7 +113,7 @@ const NotificationsTelegram: React.FC = () => {
},
});
- addToast(intl.formatMessage(messages.testsent), {
+ addToast(intl.formatMessage(messages.telegramtestsent), {
appearance: 'info',
autoDismiss: true,
});
diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json
index 7807ff6295..bab294b3f4 100644
--- a/src/i18n/locale/en.json
+++ b/src/i18n/locale/en.json
@@ -290,11 +290,13 @@
"components.Settings.Notifications.chatId": "Chat ID",
"components.Settings.Notifications.discordsettingsfailed": "Discord notification settings failed to save.",
"components.Settings.Notifications.discordsettingssaved": "Discord notification settings saved successfully!",
+ "components.Settings.Notifications.discordtestsent": "Discord test notification sent!",
"components.Settings.Notifications.emailNotificationTypesAlertDescription": "Media Requested , Media Automatically Approved , and Media Failed email notifications are sent to all users with the Manage Requests permission.",
"components.Settings.Notifications.emailNotificationTypesAlertDescriptionPt2": "Media Approved , Media Declined , and Media Available email notifications are sent to the user who submitted the request.",
"components.Settings.Notifications.emailsender": "Sender Address",
"components.Settings.Notifications.emailsettingsfailed": "Email notification settings failed to save.",
"components.Settings.Notifications.emailsettingssaved": "Email notification settings saved successfully!",
+ "components.Settings.Notifications.emailtestsent": "Email test notification sent!",
"components.Settings.Notifications.enableSsl": "Enable SSL",
"components.Settings.Notifications.pgpPassword": "PGP Password",
"components.Settings.Notifications.pgpPasswordTip": "Sign encrypted email messages using OpenPGP ",
@@ -309,7 +311,7 @@
"components.Settings.Notifications.ssldisabletip": "SSL should be disabled on standard TLS connections (port 587)",
"components.Settings.Notifications.telegramsettingsfailed": "Telegram notification settings failed to save.",
"components.Settings.Notifications.telegramsettingssaved": "Telegram notification settings saved successfully!",
- "components.Settings.Notifications.testsent": "Telegram test notification sent!",
+ "components.Settings.Notifications.telegramtestsent": "Telegram test notification sent!",
"components.Settings.Notifications.validationBotAPIRequired": "You must provide a bot authentication token",
"components.Settings.Notifications.validationChatIdRequired": "You must provide a valid chat ID",
"components.Settings.Notifications.validationEmail": "You must provide a valid email address",
From eb5d1528869959cdf642e6fefc1a8f4dcf51b84e Mon Sep 17 00:00:00 2001
From: sct
Date: Sun, 18 Apr 2021 19:39:24 +0900
Subject: [PATCH 09/13] fix(ui): align icons in user dropdown
also changed icons on the jobs & cache page to be outlined (and switched to trash icon for flush
cache)
---
src/components/Layout/UserDropdown/index.tsx | 12 ++++++------
src/components/Settings/SettingsJobsCache/index.tsx | 4 ++--
2 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/src/components/Layout/UserDropdown/index.tsx b/src/components/Layout/UserDropdown/index.tsx
index b766bc4c3a..209dfa4d7d 100644
--- a/src/components/Layout/UserDropdown/index.tsx
+++ b/src/components/Layout/UserDropdown/index.tsx
@@ -67,7 +67,7 @@ const UserDropdown: React.FC = () => {
>
{
@@ -78,12 +78,12 @@ const UserDropdown: React.FC = () => {
onClick={() => setDropdownOpen(false)}
>
- {intl.formatMessage(messages.myprofile)}
+ {intl.formatMessage(messages.myprofile)}
{
@@ -94,17 +94,17 @@ const UserDropdown: React.FC = () => {
onClick={() => setDropdownOpen(false)}
>
- {intl.formatMessage(messages.settings)}
+ {intl.formatMessage(messages.settings)}
logout()}
>
- {intl.formatMessage(messages.signout)}
+ {intl.formatMessage(messages.signout)}
diff --git a/src/components/Settings/SettingsJobsCache/index.tsx b/src/components/Settings/SettingsJobsCache/index.tsx
index ffd3446ff0..992e3ac47b 100644
--- a/src/components/Settings/SettingsJobsCache/index.tsx
+++ b/src/components/Settings/SettingsJobsCache/index.tsx
@@ -1,4 +1,4 @@
-import { PlayIcon, StopIcon, XCircleIcon } from '@heroicons/react/solid';
+import { PlayIcon, StopIcon, TrashIcon } from '@heroicons/react/outline';
import axios from 'axios';
import React from 'react';
import {
@@ -226,7 +226,7 @@ const SettingsJobs: React.FC = () => {
{formatBytes(cache.stats.vsize)}
flushCache(cache)}>
-
+
{intl.formatMessage(messages.flushcache)}
From f13f1c94515b5bd51382fa18ad96a2ccfd06e50d Mon Sep 17 00:00:00 2001
From: sct
Date: Sun, 18 Apr 2021 19:53:55 +0900
Subject: [PATCH 10/13] fix: better error message when creating a user with an
existing email
fixes #1441
---
server/routes/user/index.ts | 12 ++++++++++++
src/components/UserList/index.tsx | 17 +++++++++++++----
2 files changed, 25 insertions(+), 4 deletions(-)
diff --git a/server/routes/user/index.ts b/server/routes/user/index.ts
index 6546d666f5..a0dab71c83 100644
--- a/server/routes/user/index.ts
+++ b/server/routes/user/index.ts
@@ -81,6 +81,18 @@ router.post(
const body = req.body;
const userRepository = getRepository(User);
+ const existingUser = await userRepository.findOne({
+ where: { email: body.email },
+ });
+
+ if (existingUser) {
+ return next({
+ status: 409,
+ message: 'User already exists with submitted email.',
+ errors: ['USER_EXISTS'],
+ });
+ }
+
const passedExplicitPassword = body.password && body.password.length > 0;
const avatar = gravatarUrl(body.email, { default: 'mm', size: 200 });
diff --git a/src/components/UserList/index.tsx b/src/components/UserList/index.tsx
index 63f5bf5fd9..95516d488c 100644
--- a/src/components/UserList/index.tsx
+++ b/src/components/UserList/index.tsx
@@ -62,6 +62,8 @@ const messages = defineMessages({
validationpasswordminchars:
'Password is too short; should be a minimum of 8 characters',
usercreatedfailed: 'Something went wrong while creating the user.',
+ usercreatedfailedexisting:
+ 'Provided email is already in use by another user.',
usercreatedsuccess: 'User created successfully!',
email: 'Email Address',
password: 'Password',
@@ -305,10 +307,17 @@ const UserList: React.FC = () => {
});
setCreateModal({ isOpen: false });
} catch (e) {
- addToast(intl.formatMessage(messages.usercreatedfailed), {
- appearance: 'error',
- autoDismiss: true,
- });
+ addToast(
+ intl.formatMessage(
+ e.response.data.errors?.includes('USER_EXISTS')
+ ? messages.usercreatedfailedexisting
+ : messages.usercreatedfailed
+ ),
+ {
+ appearance: 'error',
+ autoDismiss: true,
+ }
+ );
} finally {
revalidate();
}
From 89455ad9b783d04d993a0009c351b1096f2b222e Mon Sep 17 00:00:00 2001
From: TheCatLady <52870424+TheCatLady@users.noreply.github.com>
Date: Sun, 18 Apr 2021 23:12:05 -0400
Subject: [PATCH 11/13] fix: set editRequest attribute as necessary, allow
users to edit their own pending requests, and show 'View Request' button on
series pages (#1446)
* fix: set editRequest attribute for RequestModal
* fix: remove now-unneeded conditional
* fix(ui): only show 'View Request' for user's own requests if they don't have MANAGE_REQUESTS perm
* fix(ui): show edit button on request list for own requests & 'View Request' button on series pages
* fix(ui): do not show 'Request More' if user already has a pending request
* fix: address PR comments
* fix(lang): edit usercreatedfaileexisting string & generate translation key
* fix: users should always be able to view/edit their own requests even if their perms have changed
also fixed capitalization of 'Signing In...' string
---
server/routes/request.ts | 34 ++--
src/components/Login/LocalLogin.tsx | 2 +-
src/components/PlexLoginButton/index.tsx | 2 +-
src/components/RequestButton/index.tsx | 146 ++++++++++--------
.../RequestList/RequestItem/index.tsx | 99 ++++++------
.../RequestModal/MovieRequestModal.tsx | 84 +++++-----
.../RequestModal/TvRequestModal.tsx | 33 +++-
src/components/UserList/index.tsx | 2 +-
src/i18n/globalMessages.ts | 2 +-
src/i18n/locale/en.json | 24 +--
10 files changed, 246 insertions(+), 182 deletions(-)
diff --git a/server/routes/request.ts b/server/routes/request.ts
index 9fb572631e..df0b55453d 100644
--- a/server/routes/request.ts
+++ b/server/routes/request.ts
@@ -493,7 +493,6 @@ requestRoutes.get('/:requestId', async (req, res, next) => {
requestRoutes.put<{ requestId: string }>(
'/:requestId',
- isAuthenticated(Permission.MANAGE_REQUESTS),
async (req, res, next) => {
const requestRepository = getRepository(MediaRequest);
const userRepository = getRepository(User);
@@ -503,17 +502,30 @@ requestRoutes.put<{ requestId: string }>(
);
if (!request) {
- return next({ status: 404, message: 'Request not found' });
+ return next({ status: 404, message: 'Request not found.' });
+ }
+
+ if (
+ (request.requestedBy.id !== req.user?.id ||
+ (req.body.mediaType !== 'tv' &&
+ !req.user?.hasPermission(Permission.REQUEST_ADVANCED))) &&
+ !req.user?.hasPermission(Permission.MANAGE_REQUESTS)
+ ) {
+ return next({
+ status: 403,
+ message: 'You do not have permission to modify this request.',
+ });
}
let requestUser = req.user;
if (
req.body.userId &&
- !(
- req.user?.hasPermission(Permission.MANAGE_USERS) &&
- req.user?.hasPermission(Permission.MANAGE_REQUESTS)
- )
+ req.body.userId !== req.user?.id &&
+ !req.user?.hasPermission([
+ Permission.MANAGE_USERS,
+ Permission.MANAGE_REQUESTS,
+ ])
) {
return next({
status: 403,
@@ -546,7 +558,7 @@ requestRoutes.put<{ requestId: string }>(
if (!requestedSeasons || requestedSeasons.length === 0) {
throw new Error(
- 'Missing seasons. If you want to cancel a tv request, use the DELETE method.'
+ 'Missing seasons. If you want to cancel a series request, use the DELETE method.'
);
}
@@ -633,7 +645,7 @@ requestRoutes.delete('/:requestId', async (req, res, next) => {
) {
return next({
status: 401,
- message: 'You do not have permission to remove this request',
+ message: 'You do not have permission to delete this request.',
});
}
@@ -642,7 +654,7 @@ requestRoutes.delete('/:requestId', async (req, res, next) => {
return res.status(204).send();
} catch (e) {
logger.error(e.message);
- next({ status: 404, message: 'Request not found' });
+ next({ status: 404, message: 'Request not found.' });
}
});
@@ -668,7 +680,7 @@ requestRoutes.post<{
label: 'Media Request',
message: e.message,
});
- next({ status: 404, message: 'Request not found' });
+ next({ status: 404, message: 'Request not found.' });
}
}
);
@@ -712,7 +724,7 @@ requestRoutes.post<{
label: 'Media Request',
message: e.message,
});
- next({ status: 404, message: 'Request not found' });
+ next({ status: 404, message: 'Request not found.' });
}
}
);
diff --git a/src/components/Login/LocalLogin.tsx b/src/components/Login/LocalLogin.tsx
index 1dc6006deb..6444635f25 100644
--- a/src/components/Login/LocalLogin.tsx
+++ b/src/components/Login/LocalLogin.tsx
@@ -13,7 +13,7 @@ const messages = defineMessages({
validationemailrequired: 'You must provide a valid email address',
validationpasswordrequired: 'You must provide a password',
loginerror: 'Something went wrong while trying to sign in.',
- signingin: 'Signing in…',
+ signingin: 'Signing In…',
signin: 'Sign In',
forgotpassword: 'Forgot Password?',
});
diff --git a/src/components/PlexLoginButton/index.tsx b/src/components/PlexLoginButton/index.tsx
index 2125f00537..c85fa78c6e 100644
--- a/src/components/PlexLoginButton/index.tsx
+++ b/src/components/PlexLoginButton/index.tsx
@@ -6,7 +6,7 @@ import PlexOAuth from '../../utils/plex';
const messages = defineMessages({
signinwithplex: 'Sign In',
- signingin: 'Signing in…',
+ signingin: 'Signing In…',
});
const plexOAuth = new PlexOAuth();
diff --git a/src/components/RequestButton/index.tsx b/src/components/RequestButton/index.tsx
index c5fef6fd7d..75f2b6c6d1 100644
--- a/src/components/RequestButton/index.tsx
+++ b/src/components/RequestButton/index.tsx
@@ -5,7 +5,7 @@ import {
XIcon,
} from '@heroicons/react/solid';
import axios from 'axios';
-import React, { useState } from 'react';
+import React, { useMemo, useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import {
MediaRequestStatus,
@@ -23,19 +23,19 @@ const messages = defineMessages({
viewrequest: 'View Request',
viewrequest4k: 'View 4K Request',
requestmore: 'Request More',
- requestmore4k: 'Request More 4K',
+ requestmore4k: 'Request More in 4K',
approverequest: 'Approve Request',
approverequest4k: 'Approve 4K Request',
declinerequest: 'Decline Request',
declinerequest4k: 'Decline 4K Request',
approverequests:
- 'Approve {requestCount} {requestCount, plural, one {Request} other {Requests}}',
+ 'Approve {requestCount, plural, one {Request} other {{requestCount} Requests}}',
declinerequests:
- 'Decline {requestCount} {requestCount, plural, one {Request} other {Requests}}',
+ 'Decline {requestCount, plural, one {Request} other {{requestCount} Requests}}',
approve4krequests:
- 'Approve {requestCount} 4K {requestCount, plural, one {Request} other {Requests}}',
+ 'Approve {requestCount, plural, one {Request} other {{requestCount} 4K Requests}}',
decline4krequests:
- 'Decline {requestCount} 4K {requestCount, plural, one {Request} other {Requests}}',
+ 'Decline {requestCount, plural, one {Request} other {{requestCount} 4K Requests}}',
});
interface ButtonOption {
@@ -64,26 +64,34 @@ const RequestButton: React.FC = ({
}) => {
const intl = useIntl();
const settings = useSettings();
- const { hasPermission } = useUser();
+ const { user, hasPermission } = useUser();
const [showRequestModal, setShowRequestModal] = useState(false);
const [showRequest4kModal, setShowRequest4kModal] = useState(false);
+ const [editRequest, setEditRequest] = useState(false);
- const activeRequest = media?.requests.find(
- (request) => request.status === MediaRequestStatus.PENDING && !request.is4k
- );
- const active4kRequest = media?.requests.find(
- (request) => request.status === MediaRequestStatus.PENDING && request.is4k
- );
-
- // All pending
+ // All pending requests
const activeRequests = media?.requests.filter(
(request) => request.status === MediaRequestStatus.PENDING && !request.is4k
);
-
const active4kRequests = media?.requests.filter(
(request) => request.status === MediaRequestStatus.PENDING && request.is4k
);
+ const activeRequest = useMemo(() => {
+ return activeRequests && activeRequests.length > 0
+ ? activeRequests.find((request) => request.requestedBy.id === user?.id) ??
+ activeRequests[0]
+ : undefined;
+ }, [activeRequests, user]);
+
+ const active4kRequest = useMemo(() => {
+ return active4kRequests && active4kRequests.length > 0
+ ? active4kRequests.find(
+ (request) => request.requestedBy.id === user?.id
+ ) ?? active4kRequests[0]
+ : undefined;
+ }, [active4kRequests, user]);
+
const modifyRequest = async (
request: MediaRequest,
type: 'approve' | 'decline'
@@ -121,24 +129,7 @@ const RequestButton: React.FC = ({
id: 'request',
text: intl.formatMessage(globalMessages.request),
action: () => {
- setShowRequestModal(true);
- },
- svg: ,
- });
- }
-
- if (
- hasPermission(Permission.REQUEST) &&
- mediaType === 'tv' &&
- media &&
- media.status !== MediaStatus.AVAILABLE &&
- media.status !== MediaStatus.UNKNOWN &&
- !isShowComplete
- ) {
- buttons.push({
- id: 'request-more',
- text: intl.formatMessage(messages.requestmore),
- action: () => {
+ setEditRequest(false);
setShowRequestModal(true);
},
svg: ,
@@ -157,26 +148,7 @@ const RequestButton: React.FC = ({
id: 'request4k',
text: intl.formatMessage(globalMessages.request4k),
action: () => {
- setShowRequest4kModal(true);
- },
- svg: ,
- });
- }
-
- if (
- mediaType === 'tv' &&
- (hasPermission(Permission.REQUEST_4K) ||
- (mediaType === 'tv' && hasPermission(Permission.REQUEST_4K_TV))) &&
- media &&
- media.status4k !== MediaStatus.AVAILABLE &&
- media.status4k !== MediaStatus.UNKNOWN &&
- !is4kShowComplete &&
- settings.currentSettings.series4kEnabled
- ) {
- buttons.push({
- id: 'request-more-4k',
- text: intl.formatMessage(messages.requestmore4k),
- action: () => {
+ setEditRequest(false);
setShowRequest4kModal(true);
},
svg: ,
@@ -185,27 +157,34 @@ const RequestButton: React.FC = ({
if (
activeRequest &&
- mediaType === 'movie' &&
- hasPermission(Permission.REQUEST)
+ (activeRequest.requestedBy.id === user?.id ||
+ (activeRequests?.length === 1 &&
+ hasPermission(Permission.MANAGE_REQUESTS)))
) {
buttons.push({
id: 'active-request',
text: intl.formatMessage(messages.viewrequest),
- action: () => setShowRequestModal(true),
+ action: () => {
+ setEditRequest(true);
+ setShowRequestModal(true);
+ },
svg: ,
});
}
if (
active4kRequest &&
- mediaType === 'movie' &&
- (hasPermission(Permission.REQUEST_4K) ||
- hasPermission(Permission.REQUEST_4K_MOVIE))
+ (active4kRequest.requestedBy.id === user?.id ||
+ (active4kRequests?.length === 1 &&
+ hasPermission(Permission.MANAGE_REQUESTS)))
) {
buttons.push({
id: 'active-4k-request',
text: intl.formatMessage(messages.viewrequest4k),
- action: () => setShowRequest4kModal(true),
+ action: () => {
+ setEditRequest(true);
+ setShowRequest4kModal(true);
+ },
svg: ,
});
}
@@ -320,6 +299,49 @@ const RequestButton: React.FC = ({
);
}
+ if (
+ mediaType === 'tv' &&
+ (!activeRequest || activeRequest.requestedBy.id !== user?.id) &&
+ hasPermission(Permission.REQUEST) &&
+ media &&
+ media.status !== MediaStatus.AVAILABLE &&
+ media.status !== MediaStatus.UNKNOWN &&
+ !isShowComplete
+ ) {
+ buttons.push({
+ id: 'request-more',
+ text: intl.formatMessage(messages.requestmore),
+ action: () => {
+ setEditRequest(false);
+ setShowRequestModal(true);
+ },
+ svg: ,
+ });
+ }
+
+ if (
+ mediaType === 'tv' &&
+ (!active4kRequest || active4kRequest.requestedBy.id !== user?.id) &&
+ hasPermission([Permission.REQUEST_4K, Permission.REQUEST_4K_TV], {
+ type: 'or',
+ }) &&
+ media &&
+ media.status4k !== MediaStatus.AVAILABLE &&
+ media.status4k !== MediaStatus.UNKNOWN &&
+ !is4kShowComplete &&
+ settings.currentSettings.series4kEnabled
+ ) {
+ buttons.push({
+ id: 'request-more-4k',
+ text: intl.formatMessage(messages.requestmore4k),
+ action: () => {
+ setEditRequest(false);
+ setShowRequest4kModal(true);
+ },
+ svg: ,
+ });
+ }
+
const [buttonOne, ...others] = buttons;
if (!buttonOne) {
@@ -332,6 +354,7 @@ const RequestButton: React.FC = ({
tmdbId={tmdbId}
show={showRequestModal}
type={mediaType}
+ editRequest={editRequest ? activeRequest : undefined}
onComplete={() => {
onUpdate();
setShowRequestModal(false);
@@ -342,6 +365,7 @@ const RequestButton: React.FC = ({
tmdbId={tmdbId}
show={showRequest4kModal}
type={mediaType}
+ editRequest={editRequest ? active4kRequest : undefined}
is4k
onComplete={() => {
onUpdate();
diff --git a/src/components/RequestList/RequestItem/index.tsx b/src/components/RequestList/RequestItem/index.tsx
index 13b7c01eeb..01fb1ddc1f 100644
--- a/src/components/RequestList/RequestItem/index.tsx
+++ b/src/components/RequestList/RequestItem/index.tsx
@@ -36,6 +36,7 @@ const messages = defineMessages({
modified: 'Modified',
modifieduserdate: '{date} by {user}',
mediaerror: 'The associated title for this request is no longer available.',
+ editrequest: 'Edit Request',
deleterequest: 'Delete Request',
cancelRequest: 'Cancel Request',
});
@@ -363,20 +364,6 @@ const RequestItem: React.FC = ({
- {requestData.status === MediaRequestStatus.PENDING &&
- !hasPermission(Permission.MANAGE_REQUESTS) &&
- requestData.requestedBy.id === user?.id && (
-
deleteRequest()}
- confirmText={intl.formatMessage(globalMessages.areyousure)}
- className="w-full"
- >
-
-
- {intl.formatMessage(messages.cancelRequest)}
-
-
- )}
{requestData.media[requestData.is4k ? 'status4k' : 'status'] ===
MediaStatus.UNKNOWN &&
requestData.status !== MediaRequestStatus.DECLINED &&
@@ -407,52 +394,70 @@ const RequestItem: React.FC
= ({
>
- {intl.formatMessage(globalMessages.delete)}
+ {intl.formatMessage(messages.deleterequest)}
)}
{requestData.status === MediaRequestStatus.PENDING &&
hasPermission(Permission.MANAGE_REQUESTS) && (
- <>
-
-
- modifyRequest('approve')}
- >
-
-
- {intl.formatMessage(globalMessages.approve)}
-
-
-
-
- modifyRequest('decline')}
- >
-
-
- {intl.formatMessage(globalMessages.decline)}
-
-
-
-
+
setShowEditModal(true)}
+ buttonType="success"
+ onClick={() => modifyRequest('approve')}
>
-
+
- {intl.formatMessage(globalMessages.edit)}
+ {intl.formatMessage(globalMessages.approve)}
- >
+
+ modifyRequest('decline')}
+ >
+
+
+ {intl.formatMessage(globalMessages.decline)}
+
+
+
+
+ )}
+ {requestData.status === MediaRequestStatus.PENDING &&
+ (hasPermission(Permission.MANAGE_REQUESTS) ||
+ (requestData.requestedBy.id === user?.id &&
+ (requestData.type === 'tv' ||
+ hasPermission(Permission.REQUEST_ADVANCED)))) && (
+
+ setShowEditModal(true)}
+ >
+
+
+ {intl.formatMessage(messages.editrequest)}
+
+
+
+ )}
+ {requestData.status === MediaRequestStatus.PENDING &&
+ !hasPermission(Permission.MANAGE_REQUESTS) &&
+ requestData.requestedBy.id === user?.id && (
+ deleteRequest()}
+ confirmText={intl.formatMessage(globalMessages.areyousure)}
+ className="w-full"
+ >
+
+
+ {intl.formatMessage(messages.cancelRequest)}
+
+
)}
diff --git a/src/components/RequestModal/MovieRequestModal.tsx b/src/components/RequestModal/MovieRequestModal.tsx
index c150d242da..bd2512aeef 100644
--- a/src/components/RequestModal/MovieRequestModal.tsx
+++ b/src/components/RequestModal/MovieRequestModal.tsx
@@ -4,10 +4,7 @@ import React, { useCallback, useEffect, useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications';
import useSWR from 'swr';
-import {
- MediaRequestStatus,
- MediaStatus,
-} from '../../../server/constants/media';
+import { MediaStatus } from '../../../server/constants/media';
import { MediaRequest } from '../../../server/entity/MediaRequest';
import { QuotaResponse } from '../../../server/interfaces/api/userInterfaces';
import { Permission } from '../../../server/lib/permissions';
@@ -25,11 +22,11 @@ const messages = defineMessages({
requestCancel: 'Request for
{title} canceled.',
requesttitle: 'Request {title}',
request4ktitle: 'Request {title} in 4K',
+ edit: 'Edit Request',
cancel: 'Cancel Request',
pendingrequest: 'Pending Request for {title}',
- pending4krequest: 'Pending Request for {title} in 4K',
- requestfrom: 'There is currently a pending request from {username}.',
- request4kfrom: 'There is currently a pending 4K request from {username}.',
+ pending4krequest: 'Pending 4K Request for {title}',
+ requestfrom: "{username}'s request is pending approval.",
errorediting: 'Something went wrong while editing the request.',
requestedited: 'Request for
{title} edited successfully!',
requesterror: 'Something went wrong while submitting the request.',
@@ -130,18 +127,14 @@ const MovieRequestModal: React.FC
= ({
} finally {
setIsUpdating(false);
}
- }, [data, onComplete, addToast, requestOverrides]);
-
- const activeRequest = data?.mediaInfo?.requests?.find(
- (request) => request.is4k === !!is4k
- );
+ }, [data, onComplete, addToast, requestOverrides, hasPermission, intl, is4k]);
const cancelRequest = async () => {
setIsUpdating(true);
try {
const response = await axios.delete(
- `/api/v1/request/${activeRequest?.id}`
+ `/api/v1/request/${editRequest?.id}`
);
if (response.status === 204) {
@@ -206,11 +199,15 @@ const MovieRequestModal: React.FC = ({
}
};
- const isOwner = activeRequest
- ? activeRequest.requestedBy.id === user?.id
- : false;
+ if (editRequest) {
+ const isOwner = editRequest.requestedBy.id === user?.id;
+ const showEditButton = hasPermission(
+ [Permission.MANAGE_REQUESTS, Permission.REQUEST_ADVANCED],
+ {
+ type: 'or',
+ }
+ );
- if (activeRequest?.status === MediaRequestStatus.PENDING) {
return (
= ({
onCancel={onCancel}
title={intl.formatMessage(
is4k ? messages.pending4krequest : messages.pendingrequest,
- {
- title: data?.title,
- }
+ { title: data?.title }
)}
- onOk={() => (isOwner ? cancelRequest() : updateRequest())}
+ onOk={() => (showEditButton ? updateRequest() : cancelRequest())}
okDisabled={isUpdating}
okText={
- isOwner
- ? isUpdating
- ? intl.formatMessage(globalMessages.canceling)
- : intl.formatMessage(messages.cancel)
- : intl.formatMessage(globalMessages.edit)
+ showEditButton
+ ? intl.formatMessage(messages.edit)
+ : intl.formatMessage(messages.cancel)
}
- okButtonType={isOwner ? 'danger' : 'primary'}
+ okButtonType={showEditButton ? 'primary' : 'danger'}
+ onSecondary={
+ isOwner && showEditButton ? () => cancelRequest() : undefined
+ }
+ secondaryDisabled={isUpdating}
+ secondaryText={
+ isOwner && showEditButton
+ ? intl.formatMessage(messages.cancel)
+ : undefined
+ }
+ secondaryButtonType="danger"
cancelText={intl.formatMessage(globalMessages.close)}
iconSvg={ }
>
{isOwner
? intl.formatMessage(messages.pendingapproval)
- : intl.formatMessage(
- is4k ? messages.request4kfrom : messages.requestfrom,
- {
- username: activeRequest.requestedBy.displayName,
- }
- )}
+ : intl.formatMessage(messages.requestfrom, {
+ username: editRequest.requestedBy.displayName,
+ })}
{(hasPermission(Permission.REQUEST_ADVANCED) ||
hasPermission(Permission.MANAGE_REQUESTS)) && (
{
setRequestOverrides(overrides);
}}
diff --git a/src/components/RequestModal/TvRequestModal.tsx b/src/components/RequestModal/TvRequestModal.tsx
index 69b9da1a44..215a7abc78 100644
--- a/src/components/RequestModal/TvRequestModal.tsx
+++ b/src/components/RequestModal/TvRequestModal.tsx
@@ -29,6 +29,11 @@ const messages = defineMessages({
requestSuccess: '{title} requested successfully!',
requesttitle: 'Request {title}',
request4ktitle: 'Request {title} in 4K',
+ edit: 'Edit Request',
+ cancel: 'Cancel Request',
+ pendingrequest: 'Pending Request for {title}',
+ pending4krequest: 'Pending 4K Request for {title}',
+ requestfrom: "{username}'s request is pending approval.",
requestseasons:
'Request {seasonCount} {seasonCount, plural, one {Season} other {Seasons}}',
requestall: 'Request All Seasons',
@@ -43,6 +48,7 @@ const messages = defineMessages({
requestcancelled: 'Request for {title} canceled.',
autoapproval: 'Automatic Approval',
requesterror: 'Something went wrong while submitting the request.',
+ pendingapproval: 'Your request is pending approval.',
});
interface RequestModalProps extends React.HTMLAttributes {
@@ -342,6 +348,8 @@ const TvRequestModal: React.FC = ({
return seasonRequest;
};
+ const isOwner = editRequest && editRequest.requestedBy.id === user?.id;
+
return !data?.externalIds.tvdbId && searchModal.show ? (
= ({
onCancel={tvdbId ? () => setSearchModal({ show: true }) : onCancel}
onOk={() => (editRequest ? updateRequest() : sendRequest())}
title={intl.formatMessage(
- is4k ? messages.request4ktitle : messages.requesttitle,
+ editRequest
+ ? is4k
+ ? messages.pending4krequest
+ : messages.pendingrequest
+ : is4k
+ ? messages.request4ktitle
+ : messages.requesttitle,
{ title: data?.name }
)}
okText={
- editRequest && selectedSeasons.length === 0
- ? 'Cancel Request'
+ editRequest
+ ? selectedSeasons.length === 0
+ ? intl.formatMessage(messages.cancel)
+ : intl.formatMessage(messages.edit)
: getAllRequestedSeasons().length >= getAllSeasons().length
? intl.formatMessage(messages.alreadyrequested)
: !settings.currentSettings.partialRequestsEnabled
@@ -397,12 +413,21 @@ const TvRequestModal: React.FC = ({
: `primary`
}
cancelText={
- tvdbId
+ editRequest
+ ? intl.formatMessage(globalMessages.close)
+ : tvdbId
? intl.formatMessage(globalMessages.back)
: intl.formatMessage(globalMessages.cancel)
}
iconSvg={ }
>
+ {editRequest
+ ? isOwner
+ ? intl.formatMessage(messages.pendingapproval)
+ : intl.formatMessage(messages.requestfrom, {
+ username: editRequest?.requestedBy.displayName,
+ })
+ : null}
{hasPermission(
[
Permission.MANAGE_REQUESTS,
diff --git a/src/components/UserList/index.tsx b/src/components/UserList/index.tsx
index 95516d488c..a0a6675828 100644
--- a/src/components/UserList/index.tsx
+++ b/src/components/UserList/index.tsx
@@ -63,7 +63,7 @@ const messages = defineMessages({
'Password is too short; should be a minimum of 8 characters',
usercreatedfailed: 'Something went wrong while creating the user.',
usercreatedfailedexisting:
- 'Provided email is already in use by another user.',
+ 'The provided email address is already in use by another user.',
usercreatedsuccess: 'User created successfully!',
email: 'Email Address',
password: 'Password',
diff --git a/src/i18n/globalMessages.ts b/src/i18n/globalMessages.ts
index 58d260e74d..4c0ef7905c 100644
--- a/src/i18n/globalMessages.ts
+++ b/src/i18n/globalMessages.ts
@@ -9,7 +9,7 @@ const globalMessages = defineMessages({
requested: 'Requested',
requesting: 'Requesting…',
request: 'Request',
- request4k: 'Request 4K',
+ request4k: 'Request in 4K',
failed: 'Failed',
pending: 'Pending',
declined: 'Declined',
diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json
index bab294b3f4..2a7725c82f 100644
--- a/src/i18n/locale/en.json
+++ b/src/i18n/locale/en.json
@@ -52,7 +52,7 @@
"components.Login.loginerror": "Something went wrong while trying to sign in.",
"components.Login.password": "Password",
"components.Login.signin": "Sign In",
- "components.Login.signingin": "Signing in…",
+ "components.Login.signingin": "Signing In…",
"components.Login.signinheader": "Sign in to continue",
"components.Login.signinwithoverseerr": "Use your {applicationTitle} account",
"components.Login.signinwithplex": "Use your Plex account",
@@ -140,7 +140,7 @@
"components.PersonDetails.birthdate": "Born {birthdate}",
"components.PersonDetails.crewmember": "Crew",
"components.PersonDetails.lifespan": "{birthdate} – {deathdate}",
- "components.PlexLoginButton.signingin": "Signing in…",
+ "components.PlexLoginButton.signingin": "Signing In…",
"components.PlexLoginButton.signinwithplex": "Sign In",
"components.QuotaSelector.movieRequestLimit": "{quotaLimit} movie(s) per {quotaDays} day(s)",
"components.QuotaSelector.tvRequestLimit": "{quotaLimit} season(s) per {quotaDays} day(s)",
@@ -152,16 +152,16 @@
"components.RequestBlock.rootfolder": "Root Folder",
"components.RequestBlock.seasons": "{seasonCount, plural, one {Season} other {Seasons}}",
"components.RequestBlock.server": "Destination Server",
- "components.RequestButton.approve4krequests": "Approve {requestCount} 4K {requestCount, plural, one {Request} other {Requests}}",
+ "components.RequestButton.approve4krequests": "Approve {requestCount, plural, one {Request} other {{requestCount} 4K Requests}}",
"components.RequestButton.approverequest": "Approve Request",
"components.RequestButton.approverequest4k": "Approve 4K Request",
- "components.RequestButton.approverequests": "Approve {requestCount} {requestCount, plural, one {Request} other {Requests}}",
- "components.RequestButton.decline4krequests": "Decline {requestCount} 4K {requestCount, plural, one {Request} other {Requests}}",
+ "components.RequestButton.approverequests": "Approve {requestCount, plural, one {Request} other {{requestCount} Requests}}",
+ "components.RequestButton.decline4krequests": "Decline {requestCount, plural, one {Request} other {{requestCount} 4K Requests}}",
"components.RequestButton.declinerequest": "Decline Request",
"components.RequestButton.declinerequest4k": "Decline 4K Request",
- "components.RequestButton.declinerequests": "Decline {requestCount} {requestCount, plural, one {Request} other {Requests}}",
+ "components.RequestButton.declinerequests": "Decline {requestCount, plural, one {Request} other {{requestCount} Requests}}",
"components.RequestButton.requestmore": "Request More",
- "components.RequestButton.requestmore4k": "Request More 4K",
+ "components.RequestButton.requestmore4k": "Request More in 4K",
"components.RequestButton.viewrequest": "View Request",
"components.RequestButton.viewrequest4k": "View 4K Request",
"components.RequestCard.deleterequest": "Delete Request",
@@ -169,6 +169,7 @@
"components.RequestCard.seasons": "{seasonCount, plural, one {Season} other {Seasons}}",
"components.RequestList.RequestItem.cancelRequest": "Cancel Request",
"components.RequestList.RequestItem.deleterequest": "Delete Request",
+ "components.RequestList.RequestItem.editrequest": "Edit Request",
"components.RequestList.RequestItem.failedretry": "Something went wrong while retrying the request.",
"components.RequestList.RequestItem.mediaerror": "The associated title for this request is no longer available.",
"components.RequestList.RequestItem.modified": "Modified",
@@ -208,13 +209,13 @@
"components.RequestModal.alreadyrequested": "Already Requested",
"components.RequestModal.autoapproval": "Automatic Approval",
"components.RequestModal.cancel": "Cancel Request",
+ "components.RequestModal.edit": "Edit Request",
"components.RequestModal.errorediting": "Something went wrong while editing the request.",
"components.RequestModal.extras": "Extras",
"components.RequestModal.numberofepisodes": "# of Episodes",
- "components.RequestModal.pending4krequest": "Pending Request for {title} in 4K",
+ "components.RequestModal.pending4krequest": "Pending 4K Request for {title}",
"components.RequestModal.pendingapproval": "Your request is pending approval.",
"components.RequestModal.pendingrequest": "Pending Request for {title}",
- "components.RequestModal.request4kfrom": "There is currently a pending 4K request from {username}.",
"components.RequestModal.request4ktitle": "Request {title} in 4K",
"components.RequestModal.requestCancel": "Request for {title} canceled.",
"components.RequestModal.requestSuccess": "{title} requested successfully!",
@@ -223,7 +224,7 @@
"components.RequestModal.requestcancelled": "Request for {title} canceled.",
"components.RequestModal.requestedited": "Request for {title} edited successfully!",
"components.RequestModal.requesterror": "Something went wrong while submitting the request.",
- "components.RequestModal.requestfrom": "There is currently a pending request from {username}.",
+ "components.RequestModal.requestfrom": "{username}'s request is pending approval.",
"components.RequestModal.requestseasons": "Request {seasonCount} {seasonCount, plural, one {Season} other {Seasons}}",
"components.RequestModal.requesttitle": "Request {title}",
"components.RequestModal.season": "Season",
@@ -677,6 +678,7 @@
"components.UserList.totalrequests": "Total Requests",
"components.UserList.user": "User",
"components.UserList.usercreatedfailed": "Something went wrong while creating the user.",
+ "components.UserList.usercreatedfailedexisting": "The provided email address is already in use by another user.",
"components.UserList.usercreatedsuccess": "User created successfully!",
"components.UserList.userdeleted": "User deleted successfully!",
"components.UserList.userdeleteerror": "Something went wrong while deleting the user.",
@@ -796,7 +798,7 @@
"i18n.previous": "Previous",
"i18n.processing": "Processing",
"i18n.request": "Request",
- "i18n.request4k": "Request 4K",
+ "i18n.request4k": "Request in 4K",
"i18n.requested": "Requested",
"i18n.requesting": "Requesting…",
"i18n.resultsperpage": "Display {pageSize} results per page",
From a822b019220e86e362a2570e7024289450b4ed46 Mon Sep 17 00:00:00 2001
From: TheCatLady <52870424+TheCatLady@users.noreply.github.com>
Date: Tue, 20 Apr 2021 19:03:05 -0400
Subject: [PATCH 12/13] fix(lang): add missing '4K' from singular case of
approve/deny 4K request strings (#1481)
---
src/components/RequestButton/index.tsx | 4 ++--
src/i18n/locale/en.json | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/components/RequestButton/index.tsx b/src/components/RequestButton/index.tsx
index 75f2b6c6d1..a528bba439 100644
--- a/src/components/RequestButton/index.tsx
+++ b/src/components/RequestButton/index.tsx
@@ -33,9 +33,9 @@ const messages = defineMessages({
declinerequests:
'Decline {requestCount, plural, one {Request} other {{requestCount} Requests}}',
approve4krequests:
- 'Approve {requestCount, plural, one {Request} other {{requestCount} 4K Requests}}',
+ 'Approve {requestCount, plural, one {4K Request} other {{requestCount} 4K Requests}}',
decline4krequests:
- 'Decline {requestCount, plural, one {Request} other {{requestCount} 4K Requests}}',
+ 'Decline {requestCount, plural, one {4K Request} other {{requestCount} 4K Requests}}',
});
interface ButtonOption {
diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json
index 2a7725c82f..74557d173a 100644
--- a/src/i18n/locale/en.json
+++ b/src/i18n/locale/en.json
@@ -152,11 +152,11 @@
"components.RequestBlock.rootfolder": "Root Folder",
"components.RequestBlock.seasons": "{seasonCount, plural, one {Season} other {Seasons}}",
"components.RequestBlock.server": "Destination Server",
- "components.RequestButton.approve4krequests": "Approve {requestCount, plural, one {Request} other {{requestCount} 4K Requests}}",
+ "components.RequestButton.approve4krequests": "Approve {requestCount, plural, one {4K Request} other {{requestCount} 4K Requests}}",
"components.RequestButton.approverequest": "Approve Request",
"components.RequestButton.approverequest4k": "Approve 4K Request",
"components.RequestButton.approverequests": "Approve {requestCount, plural, one {Request} other {{requestCount} Requests}}",
- "components.RequestButton.decline4krequests": "Decline {requestCount, plural, one {Request} other {{requestCount} 4K Requests}}",
+ "components.RequestButton.decline4krequests": "Decline {requestCount, plural, one {4K Request} other {{requestCount} 4K Requests}}",
"components.RequestButton.declinerequest": "Decline Request",
"components.RequestButton.declinerequest4k": "Decline 4K Request",
"components.RequestButton.declinerequests": "Decline {requestCount, plural, one {Request} other {{requestCount} Requests}}",
From 1a311d211d78731c9089e66ed5387c1b5afe33c0 Mon Sep 17 00:00:00 2001
From: TheCatLady <52870424+TheCatLady@users.noreply.github.com>
Date: Tue, 20 Apr 2021 20:21:25 -0400
Subject: [PATCH 13/13] fix(ui): change 'Disable Auto-Search' checkbox to
'Enable Automatic Search' (#1476)
* fix(ui): change 'Disable Auto-Search' checkbox to 'Enable Automatic Search'
* docs: update *arr setting documentation to reflect changes
* fix: apply form-input class
---
docs/using-overseerr/settings/README.md | 6 +++---
src/components/Settings/RadarrModal/index.tsx | 16 ++++++++--------
src/components/Settings/SonarrModal/index.tsx | 16 ++++++++--------
src/i18n/locale/en.json | 4 ++--
4 files changed, 21 insertions(+), 21 deletions(-)
diff --git a/docs/using-overseerr/settings/README.md b/docs/using-overseerr/settings/README.md
index 1753c94476..bdf9cd3434 100644
--- a/docs/using-overseerr/settings/README.md
+++ b/docs/using-overseerr/settings/README.md
@@ -178,11 +178,11 @@ If the hostname or IP address you configured above is not accessible outside you
#### Enable Scan
-Tick this box if you would like to scan your Radarr/Sonarr server for existing media/request status. It is recommended that you enable this setting, so that users cannot submit requests for media which has already been requested or is already available.
+Enable this setting if you would like to scan your Radarr/Sonarr server for existing media/request status. It is recommended that you enable this setting, so that users cannot submit requests for media which has already been requested or is already available.
-#### Disable Auto-Search
+#### Enable Automatic Search
-If you do not want Radarr/Sonarr to automatically search for media upon submission of a request, you can disable this setting.
+Enable this setting to have Radarr/Sonarr to automatically search for media upon approval of a request.
## Notifications
diff --git a/src/components/Settings/RadarrModal/index.tsx b/src/components/Settings/RadarrModal/index.tsx
index 8ea9093a1a..5ff9b711f7 100644
--- a/src/components/Settings/RadarrModal/index.tsx
+++ b/src/components/Settings/RadarrModal/index.tsx
@@ -63,7 +63,7 @@ const messages = defineMessages({
loadingTags: 'Loading tags…',
testFirstTags: 'Test connection to load tags',
tags: 'Tags',
- preventSearch: 'Disable Auto-Search',
+ enableSearch: 'Enable Automatic Search',
validationApplicationUrl: 'You must provide a valid URL',
validationApplicationUrlTrailingSlash: 'URL must not end in a trailing slash',
validationBaseUrlLeadingSlash: 'Base URL must have a leading slash',
@@ -257,8 +257,8 @@ const RadarrModal: React.FC = ({
isDefault: radarr?.isDefault ?? false,
is4k: radarr?.is4k ?? false,
externalUrl: radarr?.externalUrl,
- syncEnabled: radarr?.syncEnabled,
- preventSearch: radarr?.preventSearch,
+ syncEnabled: radarr?.syncEnabled ?? false,
+ enableSearch: !radarr?.preventSearch,
}}
validationSchema={RadarrSettingsSchema}
onSubmit={async (values) => {
@@ -283,7 +283,7 @@ const RadarrModal: React.FC = ({
isDefault: values.isDefault,
externalUrl: values.externalUrl,
syncEnabled: values.syncEnabled,
- preventSearch: values.preventSearch,
+ preventSearch: !values.enableSearch,
};
if (!radarr) {
await axios.post('/api/v1/settings/radarr', submission);
@@ -709,14 +709,14 @@ const RadarrModal: React.FC = ({
-
- {intl.formatMessage(messages.preventSearch)}
+
+ {intl.formatMessage(messages.enableSearch)}
diff --git a/src/components/Settings/SonarrModal/index.tsx b/src/components/Settings/SonarrModal/index.tsx
index ad73e45e44..02c8b0c3a6 100644
--- a/src/components/Settings/SonarrModal/index.tsx
+++ b/src/components/Settings/SonarrModal/index.tsx
@@ -67,7 +67,7 @@ const messages = defineMessages({
syncEnabled: 'Enable Scan',
externalUrl: 'External URL',
externalUrlPlaceholder: 'External URL pointing to your Sonarr server',
- preventSearch: 'Disable Auto-Search',
+ enableSearch: 'Enable Automatic Search',
validationApplicationUrl: 'You must provide a valid URL',
validationApplicationUrlTrailingSlash: 'URL must not end in a trailing slash',
validationBaseUrlLeadingSlash: 'Base URL must have a leading slash',
@@ -274,7 +274,7 @@ const SonarrModal: React.FC
= ({
enableSeasonFolders: sonarr?.enableSeasonFolders ?? false,
externalUrl: sonarr?.externalUrl,
syncEnabled: sonarr?.syncEnabled ?? false,
- preventSearch: sonarr?.preventSearch ?? false,
+ enableSearch: !sonarr?.preventSearch,
}}
validationSchema={SonarrSettingsSchema}
onSubmit={async (values) => {
@@ -314,7 +314,7 @@ const SonarrModal: React.FC = ({
enableSeasonFolders: values.enableSeasonFolders,
externalUrl: values.externalUrl,
syncEnabled: values.syncEnabled,
- preventSearch: values.preventSearch,
+ preventSearch: !values.enableSearch,
};
if (!sonarr) {
await axios.post('/api/v1/settings/sonarr', submission);
@@ -961,14 +961,14 @@ const SonarrModal: React.FC = ({
-
- {intl.formatMessage(messages.preventSearch)}
+
+ {intl.formatMessage(messages.enableSearch)}
-
diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json
index 74557d173a..c095ced79b 100644
--- a/src/i18n/locale/en.json
+++ b/src/i18n/locale/en.json
@@ -334,6 +334,7 @@
"components.Settings.RadarrModal.defaultserver": "Default Server",
"components.Settings.RadarrModal.edit4kradarr": "Edit 4K Radarr Server",
"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",
@@ -343,7 +344,6 @@
"components.Settings.RadarrModal.minimumAvailability": "Minimum Availability",
"components.Settings.RadarrModal.notagoptions": "No tags.",
"components.Settings.RadarrModal.port": "Port",
- "components.Settings.RadarrModal.preventSearch": "Disable Auto-Search",
"components.Settings.RadarrModal.qualityprofile": "Quality Profile",
"components.Settings.RadarrModal.rootfolder": "Root Folder",
"components.Settings.RadarrModal.selectMinimumAvailability": "Select minimum availability",
@@ -464,6 +464,7 @@
"components.Settings.SonarrModal.defaultserver": "Default Server",
"components.Settings.SonarrModal.edit4ksonarr": "Edit 4K Sonarr Server",
"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",
@@ -474,7 +475,6 @@
"components.Settings.SonarrModal.loadingrootfolders": "Loading root folders…",
"components.Settings.SonarrModal.notagoptions": "No tags.",
"components.Settings.SonarrModal.port": "Port",
- "components.Settings.SonarrModal.preventSearch": "Disable Auto-Search",
"components.Settings.SonarrModal.qualityprofile": "Quality Profile",
"components.Settings.SonarrModal.rootfolder": "Root Folder",
"components.Settings.SonarrModal.seasonfolders": "Season Folders",