refactor: modal redesign and fix for transitions (#2987)

pull/2993/head
Ryan Cohen 2 years ago committed by GitHub
parent 4d56320870
commit 889caaa733
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -54,10 +54,7 @@ describe('User List', () => {
.contains('Delete') .contains('Delete')
.click(); .click();
cy.get('[data-testid=modal-title]').should( cy.get('[data-testid=modal-title]').should('contain', `Delete User`);
'contain',
`Delete ${testUser.displayName}`
);
cy.intercept('/api/v1/user?take=10&skip=0&sort=displayname').as('user'); cy.intercept('/api/v1/user?take=10&skip=0&sort=displayname').as('user');

@ -33,7 +33,7 @@
"@formatjs/intl-locale": "3.0.3", "@formatjs/intl-locale": "3.0.3",
"@formatjs/intl-pluralrules": "5.0.3", "@formatjs/intl-pluralrules": "5.0.3",
"@formatjs/intl-utils": "3.8.4", "@formatjs/intl-utils": "3.8.4",
"@headlessui/react": "1.6.6", "@headlessui/react": "^0.0.0-insiders.b301f04",
"@heroicons/react": "1.0.6", "@heroicons/react": "1.0.6",
"@supercharge/request-ip": "1.2.0", "@supercharge/request-ip": "1.2.0",
"@svgr/webpack": "6.3.1", "@svgr/webpack": "6.3.1",

@ -12,7 +12,8 @@ interface AlertProps {
const Alert = ({ title, children, type }: AlertProps) => { const Alert = ({ title, children, type }: AlertProps) => {
let design = { let design = {
bgColor: 'bg-yellow-600', bgColor:
'border border-yellow-500 backdrop-blur bg-yellow-400 bg-opacity-20',
titleColor: 'text-yellow-100', titleColor: 'text-yellow-100',
textColor: 'text-yellow-300', textColor: 'text-yellow-300',
svg: <ExclamationIcon className="h-5 w-5" />, svg: <ExclamationIcon className="h-5 w-5" />,
@ -21,9 +22,10 @@ const Alert = ({ title, children, type }: AlertProps) => {
switch (type) { switch (type) {
case 'info': case 'info':
design = { design = {
bgColor: 'bg-indigo-600', bgColor:
titleColor: 'text-indigo-100', 'border border-indigo-500 backdrop-blur bg-indigo-400 bg-opacity-20',
textColor: 'text-indigo-300', titleColor: 'text-gray-100',
textColor: 'text-gray-300',
svg: <InformationCircleIcon className="h-5 w-5" />, svg: <InformationCircleIcon className="h-5 w-5" />,
}; };
break; break;

@ -31,21 +31,27 @@ const Badge = (
switch (badgeType) { switch (badgeType) {
case 'danger': case 'danger':
badgeStyle.push('bg-red-600 !text-red-100'); badgeStyle.push(
'bg-red-600 bg-opacity-80 border-red-500 border !text-red-100'
);
if (href) { if (href) {
badgeStyle.push('hover:bg-red-500'); badgeStyle.push('hover:bg-red-500 bg-opacity-100');
} }
break; break;
case 'warning': case 'warning':
badgeStyle.push('bg-yellow-500 !text-yellow-100'); badgeStyle.push(
'bg-yellow-500 bg-opacity-80 border-yellow-500 border !text-yellow-100'
);
if (href) { if (href) {
badgeStyle.push('hover:bg-yellow-400'); badgeStyle.push('hover:bg-yellow-500 hover:bg-opacity-100');
} }
break; break;
case 'success': case 'success':
badgeStyle.push('bg-green-500 !text-green-100'); badgeStyle.push(
'bg-green-500 bg-opacity-80 border border-green-500 !text-green-100'
);
if (href) { if (href) {
badgeStyle.push('hover:bg-green-400'); badgeStyle.push('hover:bg-green-500 hover:bg-opacity-100');
} }
break; break;
case 'dark': case 'dark':
@ -61,9 +67,11 @@ const Badge = (
} }
break; break;
default: default:
badgeStyle.push('bg-indigo-500 !text-indigo-100'); badgeStyle.push(
'bg-indigo-500 bg-opacity-80 border border-indigo-500 !text-indigo-100'
);
if (href) { if (href) {
badgeStyle.push('hover:bg-indigo-400'); badgeStyle.push('hover:bg-indigo-500 bg-opacity-100');
} }
} }

@ -51,22 +51,22 @@ function Button<P extends ElementTypes = 'button'>(
switch (buttonType) { switch (buttonType) {
case 'primary': case 'primary':
buttonStyle.push( buttonStyle.push(
'text-white bg-indigo-600 border-indigo-600 hover:bg-indigo-500 hover:border-indigo-500 focus:border-indigo-700 focus:ring-indigo active:bg-indigo-700 active:border-indigo-700' 'text-white border border-indigo-500 bg-indigo-600 bg-opacity-80 hover:bg-opacity-100 hover:border-indigo-500 focus:border-indigo-700 focus:ring-indigo active:bg-opacity-100 active:border-indigo-700'
); );
break; break;
case 'danger': case 'danger':
buttonStyle.push( buttonStyle.push(
'text-white bg-red-600 border-red-600 hover:bg-red-500 hover:border-red-500 focus:border-red-700 focus:ring-red active:bg-red-700 active:border-red-700' 'text-white bg-red-600 bg-opacity-80 border-red-500 hover:bg-opacity-100 hover:border-red-500 focus:border-red-700 focus:ring-red active:bg-red-700 active:border-red-700'
); );
break; break;
case 'warning': case 'warning':
buttonStyle.push( buttonStyle.push(
'text-white bg-yellow-500 border-yellow-500 hover:bg-yellow-400 hover:border-yellow-400 focus:border-yellow-700 focus:ring-yellow active:bg-yellow-700 active:border-yellow-700' 'text-white border border-yellow-500 backdrop-blur bg-yellow-500 bg-opacity-80 hover:bg-opacity-100 hover:border-yellow-400 focus:border-yellow-700 focus:ring-yellow active:bg-opacity-100 active:border-yellow-700'
); );
break; break;
case 'success': case 'success':
buttonStyle.push( buttonStyle.push(
'text-white bg-green-500 border-green-500 hover:bg-green-400 hover:border-green-400 focus:border-green-700 focus:ring-green active:bg-green-700 active:border-green-700' 'text-white bg-green-500 bg-opacity-80 border-green-500 hover:bg-opacity-100 hover:border-green-400 focus:border-green-700 focus:ring-green active:bg-opacity-100 active:border-green-700'
); );
break; break;
case 'ghost': case 'ghost':
@ -76,7 +76,7 @@ function Button<P extends ElementTypes = 'button'>(
break; break;
default: default:
buttonStyle.push( buttonStyle.push(
'text-gray-200 bg-gray-600 border-gray-600 hover:text-white hover:bg-gray-500 hover:border-gray-500 group-hover:text-white group-hover:bg-gray-500 group-hover:border-gray-500 focus:border-blue-300 focus:ring-blue active:text-gray-200 active:bg-gray-500 active:border-gray-500' 'text-gray-200 bg-gray-800 bg-opacity-80 border-gray-600 hover:text-white hover:bg-gray-700 hover:border-gray-600 group-hover:text-white group-hover:bg-gray-700 group-hover:border-gray-600 focus:border-blue-300 focus:ring-blue active:text-gray-200 active:bg-gray-700 active:border-gray-600'
); );
} }

@ -70,9 +70,9 @@ const ButtonWithDropdown = ({
break; break;
default: default:
styleClasses.mainButtonClasses += styleClasses.mainButtonClasses +=
' bg-indigo-600 border-indigo-600 hover:bg-indigo-500 hover:border-indigo-500 active:bg-indigo-700 active:border-indigo-700 focus:ring-blue'; ' bg-indigo-600 border-indigo-500 bg-opacity-80 hover:bg-opacity-100 hover:border-indigo-500 active:bg-indigo-700 active:border-indigo-700 focus:ring-blue';
styleClasses.dropdownSideButtonClasses += styleClasses.dropdownSideButtonClasses +=
' bg-indigo-700 border-indigo-600 hover:bg-indigo-500 active:bg-indigo-700 focus:ring-blue'; ' bg-indigo-600 bg-opacity-80 border-indigo-500 hover:bg-opacity-100 active:bg-opacity-100 focus:ring-blue';
styleClasses.dropdownClasses += ' bg-indigo-600 p-1'; styleClasses.dropdownClasses += ' bg-indigo-600 p-1';
} }

@ -12,9 +12,7 @@ const Header = ({ children, extraMargin = 0, subtext }: HeaderProps) => {
className="mb-4 truncate text-2xl font-bold leading-7 text-gray-100 sm:overflow-visible sm:text-4xl sm:leading-9 md:mb-0" className="mb-4 truncate text-2xl font-bold leading-7 text-gray-100 sm:overflow-visible sm:text-4xl sm:leading-9 md:mb-0"
data-testid="page-header" data-testid="page-header"
> >
<span className="bg-gradient-to-br from-indigo-400 to-purple-400 bg-clip-text text-transparent"> <span className="text-overseerr">{children}</span>
{children}
</span>
</h2> </h2>
{subtext && <div className="mt-2 text-gray-400">{subtext}</div>} {subtext && <div className="mt-2 text-gray-400">{subtext}</div>}
</div> </div>

@ -13,6 +13,7 @@ import { useIntl } from 'react-intl';
interface ModalProps { interface ModalProps {
title?: string; title?: string;
subTitle?: string;
onCancel?: (e?: MouseEvent<HTMLElement>) => void; onCancel?: (e?: MouseEvent<HTMLElement>) => void;
onOk?: (e?: MouseEvent<HTMLButtonElement>) => void; onOk?: (e?: MouseEvent<HTMLButtonElement>) => void;
onSecondary?: (e?: MouseEvent<HTMLButtonElement>) => void; onSecondary?: (e?: MouseEvent<HTMLButtonElement>) => void;
@ -30,7 +31,6 @@ interface ModalProps {
tertiaryButtonType?: ButtonType; tertiaryButtonType?: ButtonType;
disableScrollLock?: boolean; disableScrollLock?: boolean;
backgroundClickable?: boolean; backgroundClickable?: boolean;
iconSvg?: React.ReactNode;
loading?: boolean; loading?: boolean;
backdrop?: string; backdrop?: string;
children?: React.ReactNode; children?: React.ReactNode;
@ -40,6 +40,7 @@ const Modal = React.forwardRef<HTMLDivElement, ModalProps>(
( (
{ {
title, title,
subTitle,
onCancel, onCancel,
onOk, onOk,
cancelText, cancelText,
@ -50,7 +51,6 @@ const Modal = React.forwardRef<HTMLDivElement, ModalProps>(
children, children,
disableScrollLock, disableScrollLock,
backgroundClickable = true, backgroundClickable = true,
iconSvg,
secondaryButtonType = 'default', secondaryButtonType = 'default',
secondaryDisabled = false, secondaryDisabled = false,
onSecondary, onSecondary,
@ -67,9 +67,9 @@ const Modal = React.forwardRef<HTMLDivElement, ModalProps>(
const intl = useIntl(); const intl = useIntl();
const modalRef = useRef<HTMLDivElement>(null); const modalRef = useRef<HTMLDivElement>(null);
useClickOutside(modalRef, () => { useClickOutside(modalRef, () => {
typeof onCancel === 'function' && backgroundClickable if (onCancel && backgroundClickable) {
? onCancel() onCancel();
: undefined; }
}); });
useLockBodyScroll(true, disableScrollLock); useLockBodyScroll(true, disableScrollLock);
@ -102,7 +102,7 @@ const Modal = React.forwardRef<HTMLDivElement, ModalProps>(
</div> </div>
</Transition> </Transition>
<Transition <Transition
className="hide-scrollbar relative inline-block w-full transform overflow-auto bg-gray-700 px-4 pt-5 pb-4 text-left align-bottom shadow-xl ring-1 ring-gray-500 transition-all sm:my-8 sm:max-w-3xl sm:rounded-lg sm:align-middle" className="hide-scrollbar relative inline-block w-full transform overflow-auto bg-gray-800 px-4 pt-4 pb-4 text-left align-bottom shadow-xl ring-1 ring-gray-700 transition-all sm:my-8 sm:max-w-3xl sm:rounded-lg sm:align-middle"
role="dialog" role="dialog"
aria-modal="true" aria-modal="true"
aria-labelledby="modal-headline" aria-labelledby="modal-headline"
@ -133,26 +133,36 @@ const Modal = React.forwardRef<HTMLDivElement, ModalProps>(
className="absolute inset-0" className="absolute inset-0"
style={{ style={{
backgroundImage: backgroundImage:
'linear-gradient(180deg, rgba(55, 65, 81, 0.85) 0%, rgba(55, 65, 81, 1) 100%)', 'linear-gradient(180deg, rgba(31, 41, 55, 0.75) 0%, rgba(31, 41, 55, 1) 100%)',
}} }}
/> />
</div> </div>
)} )}
<div className="relative overflow-x-hidden p-0.5 sm:flex sm:items-center"> <div className="relative -mx-4 overflow-x-hidden px-4 pt-0.5 sm:flex sm:items-center">
{iconSvg && <div className="modal-icon">{iconSvg}</div>}
<div <div
className={`mt-3 truncate text-center text-white sm:mt-0 sm:text-left ${ className={`mt-3 truncate text-center text-white sm:mt-0 sm:text-left`}
iconSvg ? 'sm:ml-4' : 'sm:mb-4'
}`}
> >
{title && ( {(title || subTitle) && (
<span <div className="flex flex-col space-y-1">
className="truncate text-lg font-bold leading-6" {title && (
id="modal-headline" <span
data-testid="modal-title" className="text-overseerr truncate pb-0.5 text-2xl font-bold leading-6"
> id="modal-headline"
{title} data-testid="modal-title"
</span> >
{title}
</span>
)}
{subTitle && (
<span
className="truncate text-lg font-semibold leading-6 text-gray-200"
id="modal-headline"
data-testid="modal-title"
>
{subTitle}
</span>
)}
</div>
)} )}
</div> </div>
</div> </div>

@ -74,7 +74,7 @@ const SlideOver = ({
<div className="hide-scrollbar flex h-full flex-col overflow-y-scroll rounded-lg bg-gray-800 bg-opacity-80 shadow-xl ring-1 ring-gray-700 backdrop-blur"> <div className="hide-scrollbar flex h-full flex-col overflow-y-scroll rounded-lg bg-gray-800 bg-opacity-80 shadow-xl ring-1 ring-gray-700 backdrop-blur">
<header className="space-y-1 border-b border-gray-700 py-4 px-4"> <header className="space-y-1 border-b border-gray-700 py-4 px-4">
<div className="flex items-center justify-between space-x-3"> <div className="flex items-center justify-between space-x-3">
<h2 className="bg-gradient-to-br from-indigo-400 to-purple-400 bg-clip-text text-2xl font-bold leading-7 text-transparent"> <h2 className="text-overseerr text-2xl font-bold leading-7">
{title} {title}
</h2> </h2>
<div className="flex h-7 items-center"> <div className="flex h-7 items-center">
@ -89,7 +89,9 @@ const SlideOver = ({
</div> </div>
{subText && ( {subText && (
<div> <div>
<p className="leading-5 text-gray-300">{subText}</p> <p className="font-semibold leading-5 text-gray-300">
{subText}
</p>
</div> </div>
)} )}
</header> </header>

@ -2,7 +2,6 @@ import Button from '@app/components/Common/Button';
import Modal from '@app/components/Common/Modal'; import Modal from '@app/components/Common/Modal';
import { Permission, useUser } from '@app/hooks/useUser'; import { Permission, useUser } from '@app/hooks/useUser';
import { Menu, Transition } from '@headlessui/react'; import { Menu, Transition } from '@headlessui/react';
import { ExclamationIcon } from '@heroicons/react/outline';
import { DotsVerticalIcon } from '@heroicons/react/solid'; import { DotsVerticalIcon } from '@heroicons/react/solid';
import type { default as IssueCommentType } from '@server/entity/IssueComment'; import type { default as IssueCommentType } from '@server/entity/IssueComment';
import axios from 'axios'; import axios from 'axios';
@ -65,7 +64,7 @@ const IssueComment = ({
} mt-4 space-x-4`} } mt-4 space-x-4`}
> >
<Transition <Transition
as="div" as={Fragment}
enter="transition opacity-0 duration-300" enter="transition opacity-0 duration-300"
enterFrom="opacity-0" enterFrom="opacity-0"
enterTo="opacity-100" enterTo="opacity-100"
@ -80,7 +79,6 @@ const IssueComment = ({
onOk={() => deleteComment()} onOk={() => deleteComment()}
okText={intl.formatMessage(messages.delete)} okText={intl.formatMessage(messages.delete)}
okButtonType="danger" okButtonType="danger"
iconSvg={<ExclamationIcon />}
> >
{intl.formatMessage(messages.areyousuredelete)} {intl.formatMessage(messages.areyousuredelete)}
</Modal> </Modal>

@ -14,7 +14,6 @@ import { Transition } from '@headlessui/react';
import { import {
ChatIcon, ChatIcon,
CheckCircleIcon, CheckCircleIcon,
ExclamationIcon,
PlayIcon, PlayIcon,
ServerIcon, ServerIcon,
} from '@heroicons/react/outline'; } from '@heroicons/react/outline';
@ -189,7 +188,6 @@ const IssueDetails = () => {
onOk={() => deleteIssue()} onOk={() => deleteIssue()}
okText={intl.formatMessage(messages.deleteissue)} okText={intl.formatMessage(messages.deleteissue)}
okButtonType="danger" okButtonType="danger"
iconSvg={<ExclamationIcon />}
> >
{intl.formatMessage(messages.deleteissueconfirm)} {intl.formatMessage(messages.deleteissueconfirm)}
</Modal> </Modal>

@ -5,7 +5,6 @@ import useSettings from '@app/hooks/useSettings';
import { Permission, useUser } from '@app/hooks/useUser'; import { Permission, useUser } from '@app/hooks/useUser';
import globalMessages from '@app/i18n/globalMessages'; import globalMessages from '@app/i18n/globalMessages';
import { RadioGroup } from '@headlessui/react'; import { RadioGroup } from '@headlessui/react';
import { ExclamationIcon } from '@heroicons/react/outline';
import { ArrowCircleRightIcon } from '@heroicons/react/solid'; import { ArrowCircleRightIcon } from '@heroicons/react/solid';
import { MediaStatus } from '@server/constants/media'; import { MediaStatus } from '@server/constants/media';
import type Issue from '@server/entity/Issue'; import type Issue from '@server/entity/Issue';
@ -150,23 +149,14 @@ const CreateIssueModal = ({
<Modal <Modal
backgroundClickable backgroundClickable
onCancel={onCancel} onCancel={onCancel}
iconSvg={<ExclamationIcon />}
title={intl.formatMessage(messages.reportissue)} title={intl.formatMessage(messages.reportissue)}
subTitle={data && isMovie(data) ? data?.title : data?.name}
cancelText={intl.formatMessage(globalMessages.close)} cancelText={intl.formatMessage(globalMessages.close)}
onOk={() => handleSubmit()} onOk={() => handleSubmit()}
okText={intl.formatMessage(messages.submitissue)} okText={intl.formatMessage(messages.submitissue)}
loading={!data && !error} loading={!data && !error}
backdrop={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${data?.backdropPath}`} backdrop={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${data?.backdropPath}`}
> >
{data && (
<div className="flex items-center">
<span className="mr-1 font-semibold">
{intl.formatMessage(messages.issomethingwrong, {
title: isMovie(data) ? data.title : data.name,
})}
</span>
</div>
)}
{mediaType === 'tv' && data && !isMovie(data) && ( {mediaType === 'tv' && data && !isMovie(data) && (
<> <>
<div className="form-row"> <div className="form-row">
@ -264,7 +254,7 @@ const CreateIssueModal = ({
? 'rounded-bl-md rounded-br-md' ? 'rounded-bl-md rounded-br-md'
: '', : '',
checked checked
? 'z-10 border-indigo-500 bg-indigo-600' ? 'z-10 border border-indigo-500 bg-indigo-400 bg-opacity-20'
: 'border-gray-500', : 'border-gray-500',
'relative flex cursor-pointer border p-4 focus:outline-none' 'relative flex cursor-pointer border p-4 focus:outline-none'
) )
@ -275,7 +265,7 @@ const CreateIssueModal = ({
<span <span
className={`${ className={`${
checked checked
? 'border-transparent bg-indigo-800' ? 'border-transparent bg-indigo-600'
: 'border-gray-300 bg-white' : 'border-gray-300 bg-white'
} ${ } ${
active ? 'ring-2 ring-indigo-300 ring-offset-2' : '' active ? 'ring-2 ring-indigo-300 ring-offset-2' : ''

@ -1,8 +1,8 @@
import 'ace-builds/src-noconflict/ace';
import 'ace-builds/src-noconflict/mode-json'; import 'ace-builds/src-noconflict/mode-json';
import 'ace-builds/src-noconflict/theme-dracula'; import 'ace-builds/src-noconflict/theme-dracula';
import type { HTMLAttributes } from 'react'; import type { HTMLAttributes } from 'react';
import AceEditor from 'react-ace'; import AceEditor from 'react-ace';
interface JSONEditorProps extends HTMLAttributes<HTMLDivElement> { interface JSONEditorProps extends HTMLAttributes<HTMLDivElement> {
name: string; name: string;
value: string; value: string;

@ -408,7 +408,7 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
{hasPermission(Permission.MANAGE_REQUESTS) && data.mediaInfo && ( {hasPermission(Permission.MANAGE_REQUESTS) && data.mediaInfo && (
<Tooltip content={intl.formatMessage(messages.managemovie)}> <Tooltip content={intl.formatMessage(messages.managemovie)}>
<Button <Button
buttonType="default" buttonType="ghost"
onClick={() => setShowManager(true)} onClick={() => setShowManager(true)}
className="relative ml-2 first:ml-0" className="relative ml-2 first:ml-0"
> >

@ -154,7 +154,7 @@ const RequestBlock = ({ request, onUpdate }: RequestBlockProps) => {
</Tooltip> </Tooltip>
<Tooltip content={intl.formatMessage(messages.edit)}> <Tooltip content={intl.formatMessage(messages.edit)}>
<Button <Button
buttonType="primary" buttonType="warning"
onClick={() => setShowEditModal(true)} onClick={() => setShowEditModal(true)}
disabled={isUpdating} disabled={isUpdating}
> >

@ -5,7 +5,6 @@ import { Permission, useUser } from '@app/hooks/useUser';
import globalMessages from '@app/i18n/globalMessages'; import globalMessages from '@app/i18n/globalMessages';
import { formatBytes } from '@app/utils/numberHelpers'; import { formatBytes } from '@app/utils/numberHelpers';
import { Listbox, Transition } from '@headlessui/react'; import { Listbox, Transition } from '@headlessui/react';
import { AdjustmentsIcon } from '@heroicons/react/outline';
import { CheckIcon, ChevronDownIcon } from '@heroicons/react/solid'; import { CheckIcon, ChevronDownIcon } from '@heroicons/react/solid';
import type { import type {
ServiceCommonServer, ServiceCommonServer,
@ -281,11 +280,10 @@ const AdvancedRequester = ({
return ( return (
<> <>
<div className="mt-4 mb-2 flex items-center font-bold tracking-wider"> <div className="mt-4 mb-2 flex items-center text-lg font-semibold">
<AdjustmentsIcon className="mr-1.5 h-5 w-5" />
{intl.formatMessage(messages.advancedoptions)} {intl.formatMessage(messages.advancedoptions)}
</div> </div>
<div className="rounded-md bg-gray-600 p-4 shadow"> <div className="rounded-md">
{!!data && selectedServer !== null && ( {!!data && selectedServer !== null && (
<div className="flex flex-col md:flex-row"> <div className="flex flex-col md:flex-row">
{data.filter((server) => server.is4k === is4k).length > 1 && ( {data.filter((server) => server.is4k === is4k).length > 1 && (
@ -561,7 +559,7 @@ const AdvancedRequester = ({
leave="transition ease-in duration-100" leave="transition ease-in duration-100"
leaveFrom="opacity-100" leaveFrom="opacity-100"
leaveTo="opacity-0" leaveTo="opacity-0"
className="mt-1 w-full rounded-md bg-gray-800 shadow-lg" className="mt-1 w-full rounded-md border border-gray-700 bg-gray-800 shadow-lg"
> >
<Listbox.Options <Listbox.Options
static static

@ -7,7 +7,6 @@ import AdvancedRequester from '@app/components/RequestModal/AdvancedRequester';
import QuotaDisplay from '@app/components/RequestModal/QuotaDisplay'; import QuotaDisplay from '@app/components/RequestModal/QuotaDisplay';
import { useUser } from '@app/hooks/useUser'; import { useUser } from '@app/hooks/useUser';
import globalMessages from '@app/i18n/globalMessages'; import globalMessages from '@app/i18n/globalMessages';
import { DownloadIcon } from '@heroicons/react/outline';
import { MediaRequestStatus, MediaStatus } from '@server/constants/media'; import { MediaRequestStatus, MediaStatus } from '@server/constants/media';
import type { MediaRequest } from '@server/entity/MediaRequest'; import type { MediaRequest } from '@server/entity/MediaRequest';
import type { QuotaResponse } from '@server/interfaces/api/userInterfaces'; import type { QuotaResponse } from '@server/interfaces/api/userInterfaces';
@ -22,8 +21,8 @@ import useSWR from 'swr';
const messages = defineMessages({ const messages = defineMessages({
requestadmin: 'This request will be approved automatically.', requestadmin: 'This request will be approved automatically.',
requestSuccess: '<strong>{title}</strong> requested successfully!', requestSuccess: '<strong>{title}</strong> requested successfully!',
requesttitle: 'Request {title}', requestcollectiontitle: 'Request Collection',
request4ktitle: 'Request {title} in 4K', requestcollection4ktitle: 'Request Collection in 4K',
requesterror: 'Something went wrong while submitting the request.', requesterror: 'Something went wrong while submitting the request.',
selectmovies: 'Select Movie(s)', selectmovies: 'Select Movie(s)',
requestmovies: 'Request {count} {count, plural, one {Movie} other {Movies}}', requestmovies: 'Request {count} {count, plural, one {Movie} other {Movies}}',
@ -249,9 +248,11 @@ const CollectionRequestModal = ({
onCancel={onCancel} onCancel={onCancel}
onOk={sendRequest} onOk={sendRequest}
title={intl.formatMessage( title={intl.formatMessage(
is4k ? messages.request4ktitle : messages.requesttitle, is4k
{ title: data?.name } ? messages.requestcollection4ktitle
: messages.requestcollectiontitle
)} )}
subTitle={data?.name}
okText={ okText={
isUpdating isUpdating
? intl.formatMessage(globalMessages.requesting) ? intl.formatMessage(globalMessages.requesting)
@ -266,7 +267,6 @@ const CollectionRequestModal = ({
} }
okDisabled={selectedParts.length === 0} okDisabled={selectedParts.length === 0}
okButtonType={'primary'} okButtonType={'primary'}
iconSvg={<DownloadIcon />}
backdrop={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${data?.backdropPath}`} backdrop={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${data?.backdropPath}`}
> >
{hasAutoApprove && !quota?.movie.restricted && ( {hasAutoApprove && !quota?.movie.restricted && (
@ -292,11 +292,11 @@ const CollectionRequestModal = ({
<div className="flex flex-col"> <div className="flex flex-col">
<div className="-mx-4 sm:mx-0"> <div className="-mx-4 sm:mx-0">
<div className="inline-block min-w-full py-2 align-middle"> <div className="inline-block min-w-full py-2 align-middle">
<div className="overflow-hidden shadow sm:rounded-lg"> <div className="overflow-hidden border border-gray-700 backdrop-blur sm:rounded-lg">
<table className="min-w-full"> <table className="min-w-full">
<thead> <thead>
<tr> <tr>
<th className="w-16 bg-gray-500 px-4 py-3"> <th className="w-16 bg-gray-700 bg-opacity-80 px-4 py-3">
<span <span
role="checkbox" role="checkbox"
tabIndex={0} tabIndex={0}
@ -328,15 +328,15 @@ const CollectionRequestModal = ({
></span> ></span>
</span> </span>
</th> </th>
<th className="bg-gray-500 px-1 py-3 text-left text-xs font-medium uppercase leading-4 tracking-wider text-gray-200 md:px-6"> <th className="bg-gray-700 bg-opacity-80 px-1 py-3 text-left text-xs font-medium uppercase leading-4 tracking-wider text-gray-200 md:px-6">
{intl.formatMessage(globalMessages.movie)} {intl.formatMessage(globalMessages.movie)}
</th> </th>
<th className="bg-gray-500 px-2 py-3 text-left text-xs font-medium uppercase leading-4 tracking-wider text-gray-200 md:px-6"> <th className="bg-gray-700 bg-opacity-80 px-2 py-3 text-left text-xs font-medium uppercase leading-4 tracking-wider text-gray-200 md:px-6">
{intl.formatMessage(globalMessages.status)} {intl.formatMessage(globalMessages.status)}
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody className="divide-y divide-gray-700 bg-gray-600"> <tbody className="divide-y divide-gray-700">
{data?.parts.map((part) => { {data?.parts.map((part) => {
const partRequest = getPartRequest(part.id); const partRequest = getPartRequest(part.id);
const partMedia = const partMedia =
@ -378,7 +378,7 @@ const CollectionRequestModal = ({
partRequest || partRequest ||
isSelectedPart(part.id) isSelectedPart(part.id)
? 'bg-indigo-500' ? 'bg-indigo-500'
: 'bg-gray-800' : 'bg-gray-700'
} absolute mx-auto h-4 w-9 rounded-full transition-colors duration-200 ease-in-out`} } absolute mx-auto h-4 w-9 rounded-full transition-colors duration-200 ease-in-out`}
></span> ></span>
<span <span

@ -5,7 +5,6 @@ import AdvancedRequester from '@app/components/RequestModal/AdvancedRequester';
import QuotaDisplay from '@app/components/RequestModal/QuotaDisplay'; import QuotaDisplay from '@app/components/RequestModal/QuotaDisplay';
import { useUser } from '@app/hooks/useUser'; import { useUser } from '@app/hooks/useUser';
import globalMessages from '@app/i18n/globalMessages'; import globalMessages from '@app/i18n/globalMessages';
import { DownloadIcon } from '@heroicons/react/outline';
import { MediaStatus } from '@server/constants/media'; import { MediaStatus } from '@server/constants/media';
import type { MediaRequest } from '@server/entity/MediaRequest'; import type { MediaRequest } from '@server/entity/MediaRequest';
import type { QuotaResponse } from '@server/interfaces/api/userInterfaces'; import type { QuotaResponse } from '@server/interfaces/api/userInterfaces';
@ -21,13 +20,13 @@ const messages = defineMessages({
requestadmin: 'This request will be approved automatically.', requestadmin: 'This request will be approved automatically.',
requestSuccess: '<strong>{title}</strong> requested successfully!', requestSuccess: '<strong>{title}</strong> requested successfully!',
requestCancel: 'Request for <strong>{title}</strong> canceled.', requestCancel: 'Request for <strong>{title}</strong> canceled.',
requesttitle: 'Request {title}', requestmovietitle: 'Request Movie',
request4ktitle: 'Request {title} in 4K', requestmovie4ktitle: 'Request Movie in 4K',
edit: 'Edit Request', edit: 'Edit Request',
approve: 'Approve Request', approve: 'Approve Request',
cancel: 'Cancel Request', cancel: 'Cancel Request',
pendingrequest: 'Pending Request for {title}', pendingrequest: 'Pending Movie Request',
pending4krequest: 'Pending 4K Request for {title}', pending4krequest: 'Pending 4K Movie Request',
requestfrom: "{username}'s request is pending approval.", requestfrom: "{username}'s request is pending approval.",
errorediting: 'Something went wrong while editing the request.', errorediting: 'Something went wrong while editing the request.',
requestedited: 'Request for <strong>{title}</strong> edited successfully!', requestedited: 'Request for <strong>{title}</strong> edited successfully!',
@ -218,9 +217,9 @@ const MovieRequestModal = ({
backgroundClickable backgroundClickable
onCancel={onCancel} onCancel={onCancel}
title={intl.formatMessage( title={intl.formatMessage(
is4k ? messages.pending4krequest : messages.pendingrequest, is4k ? messages.pending4krequest : messages.pendingrequest
{ title: data?.title }
)} )}
subTitle={data?.title}
onOk={() => onOk={() =>
hasPermission(Permission.MANAGE_REQUESTS) hasPermission(Permission.MANAGE_REQUESTS)
? updateRequest(true) ? updateRequest(true)
@ -264,7 +263,6 @@ const MovieRequestModal = ({
} }
secondaryButtonType="danger" secondaryButtonType="danger"
cancelText={intl.formatMessage(globalMessages.close)} cancelText={intl.formatMessage(globalMessages.close)}
iconSvg={<DownloadIcon />}
backdrop={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${data?.backdropPath}`} backdrop={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${data?.backdropPath}`}
> >
{isOwner {isOwner
@ -310,9 +308,9 @@ const MovieRequestModal = ({
onOk={sendRequest} onOk={sendRequest}
okDisabled={isUpdating || quota?.movie.restricted} okDisabled={isUpdating || quota?.movie.restricted}
title={intl.formatMessage( title={intl.formatMessage(
is4k ? messages.request4ktitle : messages.requesttitle, is4k ? messages.requestmovie4ktitle : messages.requestmovietitle
{ title: data?.title }
)} )}
subTitle={data?.title}
okText={ okText={
isUpdating isUpdating
? intl.formatMessage(globalMessages.requesting) ? intl.formatMessage(globalMessages.requesting)
@ -321,7 +319,6 @@ const MovieRequestModal = ({
) )
} }
okButtonType={'primary'} okButtonType={'primary'}
iconSvg={<DownloadIcon />}
backdrop={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${data?.backdropPath}`} backdrop={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${data?.backdropPath}`}
> >
{hasAutoApprove && !quota?.movie.restricted && ( {hasAutoApprove && !quota?.movie.restricted && (

@ -46,7 +46,7 @@ const QuotaDisplay = ({
const [showDetails, setShowDetails] = useState(false); const [showDetails, setShowDetails] = useState(false);
return ( return (
<div <div
className="my-4 flex flex-col rounded-md bg-gray-800 p-4" className="my-4 flex flex-col rounded-md border border-gray-700 p-4 backdrop-blur"
onClick={() => setShowDetails((s) => !s)} onClick={() => setShowDetails((s) => !s)}
onKeyDown={(e) => { onKeyDown={(e) => {
if (e.key === 'Enter') { if (e.key === 'Enter') {

@ -2,7 +2,6 @@ import Alert from '@app/components/Common/Alert';
import { SmallLoadingSpinner } from '@app/components/Common/LoadingSpinner'; import { SmallLoadingSpinner } from '@app/components/Common/LoadingSpinner';
import Modal from '@app/components/Common/Modal'; import Modal from '@app/components/Common/Modal';
import globalMessages from '@app/i18n/globalMessages'; import globalMessages from '@app/i18n/globalMessages';
import { DownloadIcon } from '@heroicons/react/outline';
import type { SonarrSeries } from '@server/api/servarr/sonarr'; import type { SonarrSeries } from '@server/api/servarr/sonarr';
import { defineMessages, useIntl } from 'react-intl'; import { defineMessages, useIntl } from 'react-intl';
import useSWR from 'swr'; import useSWR from 'swr';
@ -20,6 +19,7 @@ interface SearchByNameModalProps {
onCancel?: () => void; onCancel?: () => void;
closeModal: () => void; closeModal: () => void;
modalTitle: string; modalTitle: string;
modalSubTitle: string;
tmdbId: number; tmdbId: number;
} }
@ -30,6 +30,7 @@ const SearchByNameModal = ({
onCancel, onCancel,
closeModal, closeModal,
modalTitle, modalTitle,
modalSubTitle,
tmdbId, tmdbId,
}: SearchByNameModalProps) => { }: SearchByNameModalProps) => {
const intl = useIntl(); const intl = useIntl();
@ -48,10 +49,10 @@ const SearchByNameModal = ({
onCancel={onCancel} onCancel={onCancel}
onOk={closeModal} onOk={closeModal}
title={modalTitle} title={modalTitle}
subTitle={modalSubTitle}
okText={intl.formatMessage(globalMessages.next)} okText={intl.formatMessage(globalMessages.next)}
okDisabled={!tvdbId} okDisabled={!tvdbId}
okButtonType="primary" okButtonType="primary"
iconSvg={<DownloadIcon />}
> >
<Alert <Alert
title={intl.formatMessage(messages.notvdbiddescription)} title={intl.formatMessage(messages.notvdbiddescription)}

@ -8,7 +8,6 @@ import SearchByNameModal from '@app/components/RequestModal/SearchByNameModal';
import useSettings from '@app/hooks/useSettings'; import useSettings from '@app/hooks/useSettings';
import { useUser } from '@app/hooks/useUser'; import { useUser } from '@app/hooks/useUser';
import globalMessages from '@app/i18n/globalMessages'; import globalMessages from '@app/i18n/globalMessages';
import { DownloadIcon } from '@heroicons/react/outline';
import { ANIME_KEYWORD_ID } from '@server/api/themoviedb/constants'; import { ANIME_KEYWORD_ID } from '@server/api/themoviedb/constants';
import { MediaRequestStatus, MediaStatus } from '@server/constants/media'; import { MediaRequestStatus, MediaStatus } from '@server/constants/media';
import type { MediaRequest } from '@server/entity/MediaRequest'; import type { MediaRequest } from '@server/entity/MediaRequest';
@ -25,13 +24,13 @@ import useSWR, { mutate } from 'swr';
const messages = defineMessages({ const messages = defineMessages({
requestadmin: 'This request will be approved automatically.', requestadmin: 'This request will be approved automatically.',
requestSuccess: '<strong>{title}</strong> requested successfully!', requestSuccess: '<strong>{title}</strong> requested successfully!',
requesttitle: 'Request {title}', requestseriestitle: 'Request Series',
request4ktitle: 'Request {title} in 4K', requestseries4ktitle: 'Request Series in 4K',
edit: 'Edit Request', edit: 'Edit Request',
approve: 'Approve Request', approve: 'Approve Request',
cancel: 'Cancel Request', cancel: 'Cancel Request',
pendingrequest: 'Pending Request for {title}', pendingrequest: 'Pending Request',
pending4krequest: 'Pending 4K Request for {title}', pending4krequest: 'Pending 4K Request',
requestfrom: "{username}'s request is pending approval.", requestfrom: "{username}'s request is pending approval.",
requestseasons: requestseasons:
'Request {seasonCount} {seasonCount, plural, one {Season} other {Seasons}}', 'Request {seasonCount} {seasonCount, plural, one {Season} other {Seasons}}',
@ -367,9 +366,9 @@ const TvRequestModal = ({
loading={!error} loading={!error}
onCancel={onCancel} onCancel={onCancel}
modalTitle={intl.formatMessage( modalTitle={intl.formatMessage(
is4k ? messages.request4ktitle : messages.requesttitle, is4k ? messages.requestseries4ktitle : messages.requestseriestitle
{ title: data?.name }
)} )}
modalSubTitle={data.name}
tmdbId={tmdbId} tmdbId={tmdbId}
/> />
) : ( ) : (
@ -390,10 +389,10 @@ const TvRequestModal = ({
? messages.pending4krequest ? messages.pending4krequest
: messages.pendingrequest : messages.pendingrequest
: is4k : is4k
? messages.request4ktitle ? messages.requestseries4ktitle
: messages.requesttitle, : messages.requestseriestitle
{ title: data?.name }
)} )}
subTitle={data?.name}
okText={ okText={
editRequest editRequest
? selectedSeasons.length === 0 ? selectedSeasons.length === 0
@ -444,7 +443,6 @@ const TvRequestModal = ({
? intl.formatMessage(globalMessages.back) ? intl.formatMessage(globalMessages.back)
: intl.formatMessage(globalMessages.cancel) : intl.formatMessage(globalMessages.cancel)
} }
iconSvg={<DownloadIcon />}
backdrop={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${data?.backdropPath}`} backdrop={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${data?.backdropPath}`}
> >
{editRequest {editRequest
@ -502,12 +500,12 @@ const TvRequestModal = ({
<div className="flex flex-col"> <div className="flex flex-col">
<div className="-mx-4 sm:mx-0"> <div className="-mx-4 sm:mx-0">
<div className="inline-block min-w-full py-2 align-middle"> <div className="inline-block min-w-full py-2 align-middle">
<div className="overflow-hidden shadow sm:rounded-lg"> <div className="overflow-hidden border border-gray-700 shadow backdrop-blur sm:rounded-lg">
<table className="min-w-full"> <table className="min-w-full">
<thead> <thead>
<tr> <tr>
<th <th
className={`w-16 bg-gray-500 px-4 py-3 ${ className={`w-16 bg-gray-700 bg-opacity-80 px-4 py-3 ${
!settings.currentSettings.partialRequestsEnabled && !settings.currentSettings.partialRequestsEnabled &&
'hidden' 'hidden'
}`} }`}
@ -544,18 +542,18 @@ const TvRequestModal = ({
></span> ></span>
</span> </span>
</th> </th>
<th className="bg-gray-500 px-1 py-3 text-left text-xs font-medium uppercase leading-4 tracking-wider text-gray-200 md:px-6"> <th className="bg-gray-700 bg-opacity-80 px-1 py-3 text-left text-xs font-medium uppercase leading-4 tracking-wider text-gray-200 md:px-6">
{intl.formatMessage(messages.season)} {intl.formatMessage(messages.season)}
</th> </th>
<th className="bg-gray-500 px-5 py-3 text-left text-xs font-medium uppercase leading-4 tracking-wider text-gray-200 md:px-6"> <th className="bg-gray-700 bg-opacity-80 px-5 py-3 text-left text-xs font-medium uppercase leading-4 tracking-wider text-gray-200 md:px-6">
{intl.formatMessage(messages.numberofepisodes)} {intl.formatMessage(messages.numberofepisodes)}
</th> </th>
<th className="bg-gray-500 px-2 py-3 text-left text-xs font-medium uppercase leading-4 tracking-wider text-gray-200 md:px-6"> <th className="bg-gray-700 bg-opacity-80 px-2 py-3 text-left text-xs font-medium uppercase leading-4 tracking-wider text-gray-200 md:px-6">
{intl.formatMessage(globalMessages.status)} {intl.formatMessage(globalMessages.status)}
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody className="divide-y divide-gray-700 bg-gray-600"> <tbody className="divide-y divide-gray-700">
{data?.seasons {data?.seasons
.filter((season) => season.seasonNumber !== 0) .filter((season) => season.seasonNumber !== 0)
.map((season) => { .map((season) => {
@ -614,7 +612,7 @@ const TvRequestModal = ({
)) || )) ||
isSelectedSeason(season.seasonNumber) isSelectedSeason(season.seasonNumber)
? 'bg-indigo-500' ? 'bg-indigo-500'
: 'bg-gray-800' : 'bg-gray-700'
} absolute mx-auto h-4 w-9 rounded-full transition-colors duration-200 ease-in-out`} } absolute mx-auto h-4 w-9 rounded-full transition-colors duration-200 ease-in-out`}
></span> ></span>
<span <span

@ -14,7 +14,9 @@ import { useToasts } from 'react-toast-notifications';
import useSWR from 'swr'; import useSWR from 'swr';
import * as Yup from 'yup'; import * as Yup from 'yup';
const JSONEditor = dynamic(() => import('../../../JSONEditor'), { ssr: false }); const JSONEditor = dynamic(() => import('@app/components/JSONEditor'), {
ssr: false,
});
const defaultPayload = { const defaultPayload = {
notification_type: '{{notification_type}}', notification_type: '{{notification_type}}',

@ -2,7 +2,6 @@ import Modal from '@app/components/Common/Modal';
import SensitiveInput from '@app/components/Common/SensitiveInput'; import SensitiveInput from '@app/components/Common/SensitiveInput';
import globalMessages from '@app/i18n/globalMessages'; import globalMessages from '@app/i18n/globalMessages';
import { Transition } from '@headlessui/react'; import { Transition } from '@headlessui/react';
import { PencilIcon, PlusIcon } from '@heroicons/react/solid';
import type { RadarrSettings } from '@server/lib/settings'; import type { RadarrSettings } from '@server/lib/settings';
import axios from 'axios'; import axios from 'axios';
import { Field, Formik } from 'formik'; import { Field, Formik } from 'formik';
@ -340,7 +339,6 @@ const RadarrModal = ({ onClose, radarr, onSave }: RadarrModalProps) => {
values.is4k ? messages.edit4kradarr : messages.editradarr values.is4k ? messages.edit4kradarr : messages.editradarr
) )
} }
iconSvg={!radarr ? <PlusIcon /> : <PencilIcon />}
> >
<div className="mb-6"> <div className="mb-6">
<div className="form-row"> <div className="form-row">

@ -5,11 +5,18 @@ import Modal from '@app/components/Common/Modal';
import globalMessages from '@app/i18n/globalMessages'; import globalMessages from '@app/i18n/globalMessages';
import { Transition } from '@headlessui/react'; import { Transition } from '@headlessui/react';
import { DocumentTextIcon } from '@heroicons/react/outline'; import { DocumentTextIcon } from '@heroicons/react/outline';
import { useState } from 'react'; import dynamic from 'next/dynamic';
import { Fragment, useState } from 'react';
import { defineMessages, FormattedRelativeTime, useIntl } from 'react-intl'; import { defineMessages, FormattedRelativeTime, useIntl } from 'react-intl';
import ReactMarkdown from 'react-markdown';
import useSWR from 'swr'; import useSWR from 'swr';
// dyanmic is having trouble extracting the props for react-markdown here so we are just ignoring it since its really
// only children we are using
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const ReactMarkdown = dynamic<any>(() => import('react-markdown'), {
ssr: false,
});
const messages = defineMessages({ const messages = defineMessages({
releases: 'Releases', releases: 'Releases',
releasedataMissing: 'Release data is currently unavailable.', releasedataMissing: 'Release data is currently unavailable.',
@ -55,7 +62,7 @@ const Release = ({ currentVersion, release, isLatest }: ReleaseProps) => {
return ( return (
<div className="flex w-full flex-col space-y-3 rounded-md bg-gray-800 px-4 py-2 shadow-md ring-1 ring-gray-700 sm:flex-row sm:space-y-0 sm:space-x-3"> <div className="flex w-full flex-col space-y-3 rounded-md bg-gray-800 px-4 py-2 shadow-md ring-1 ring-gray-700 sm:flex-row sm:space-y-0 sm:space-x-3">
<Transition <Transition
as="div" as={Fragment}
enter="opacity-0 transition duration-300" enter="opacity-0 transition duration-300"
enterFrom="opacity-0" enterFrom="opacity-0"
enterTo="opacity-100" enterTo="opacity-100"
@ -66,7 +73,6 @@ const Release = ({ currentVersion, release, isLatest }: ReleaseProps) => {
> >
<Modal <Modal
onCancel={() => setModalOpen(false)} onCancel={() => setModalOpen(false)}
iconSvg={<DocumentTextIcon />}
title={intl.formatMessage(messages.versionChangelog, { title={intl.formatMessage(messages.versionChangelog, {
version: release.name, version: release.name,
})} })}

@ -60,19 +60,19 @@ const SettingsAbout = () => {
intl.formatMessage(globalMessages.settings), intl.formatMessage(globalMessages.settings),
]} ]}
/> />
<div className="mt-6 rounded-md bg-indigo-700 p-4"> <div className="mt-6 rounded-md border border-indigo-500 bg-indigo-400 bg-opacity-20 p-4 backdrop-blur">
<div className="flex"> <div className="flex">
<div className="flex-shrink-0"> <div className="flex-shrink-0">
<InformationCircleIcon className="h-5 w-5 text-white" /> <InformationCircleIcon className="h-5 w-5 text-gray-100" />
</div> </div>
<div className="ml-3 flex-1 md:flex md:justify-between"> <div className="ml-3 flex-1 md:flex md:justify-between">
<p className="text-sm leading-5 text-white"> <p className="text-sm leading-5 text-gray-100">
{intl.formatMessage(messages.betawarning)} {intl.formatMessage(messages.betawarning)}
</p> </p>
<p className="mt-3 text-sm leading-5 md:mt-0 md:ml-6"> <p className="mt-3 text-sm leading-5 md:mt-0 md:ml-6">
<a <a
href="http://github.com/sct/overseerr" href="http://github.com/sct/overseerr"
className="whitespace-nowrap font-medium text-indigo-100 transition duration-150 ease-in-out hover:text-white" className="whitespace-nowrap font-medium text-gray-100 transition duration-150 ease-in-out hover:text-white"
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
> >

@ -13,7 +13,7 @@ import { PencilIcon } from '@heroicons/react/solid';
import type { CacheItem } from '@server/interfaces/api/settingsInterfaces'; import type { CacheItem } from '@server/interfaces/api/settingsInterfaces';
import type { JobId } from '@server/lib/settings'; import type { JobId } from '@server/lib/settings';
import axios from 'axios'; import axios from 'axios';
import { useState } from 'react'; import { Fragment, useState } from 'react';
import type { MessageDescriptor } from 'react-intl'; import type { MessageDescriptor } from 'react-intl';
import { defineMessages, FormattedRelativeTime, useIntl } from 'react-intl'; import { defineMessages, FormattedRelativeTime, useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications'; import { useToasts } from 'react-toast-notifications';
@ -187,7 +187,7 @@ const SettingsJobs = () => {
]} ]}
/> />
<Transition <Transition
as="div" as={Fragment}
enter="opacity-0 transition duration-300" enter="opacity-0 transition duration-300"
enterFrom="opacity-0" enterFrom="opacity-0"
enterTo="opacity-100" enterTo="opacity-100"
@ -203,7 +203,6 @@ const SettingsJobs = () => {
? intl.formatMessage(globalMessages.saving) ? intl.formatMessage(globalMessages.saving)
: intl.formatMessage(globalMessages.save) : intl.formatMessage(globalMessages.save)
} }
iconSvg={<PencilIcon />}
onCancel={() => setJobEditModal({ isOpen: false })} onCancel={() => setJobEditModal({ isOpen: false })}
okDisabled={isSaving} okDisabled={isSaving}
onOk={() => scheduleJob()} onOk={() => scheduleJob()}
@ -325,7 +324,7 @@ const SettingsJobs = () => {
} }
> >
<PencilIcon /> <PencilIcon />
{intl.formatMessage(globalMessages.edit)} <span>{intl.formatMessage(globalMessages.edit)}</span>
</Button> </Button>
)} )}
{job.running ? ( {job.running ? (
@ -335,7 +334,7 @@ const SettingsJobs = () => {
</Button> </Button>
) : ( ) : (
<Button buttonType="primary" onClick={() => runJob(job)}> <Button buttonType="primary" onClick={() => runJob(job)}>
<PlayIcon className="mr-1 h-5 w-5" /> <PlayIcon />
<span>{intl.formatMessage(messages.runnow)}</span> <span>{intl.formatMessage(messages.runnow)}</span>
</Button> </Button>
)} )}

@ -24,7 +24,7 @@ import type {
} from '@server/interfaces/api/settingsInterfaces'; } from '@server/interfaces/api/settingsInterfaces';
import copy from 'copy-to-clipboard'; import copy from 'copy-to-clipboard';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useEffect, useState } from 'react'; import { Fragment, useEffect, useState } from 'react';
import { defineMessages, useIntl } from 'react-intl'; import { defineMessages, useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications'; import { useToasts } from 'react-toast-notifications';
import useSWR from 'swr'; import useSWR from 'swr';
@ -138,7 +138,7 @@ const SettingsLogs = () => {
]} ]}
/> />
<Transition <Transition
as="div" as={Fragment}
enter="opacity-0 transition duration-300" enter="opacity-0 transition duration-300"
enterFrom="opacity-0" enterFrom="opacity-0"
enterTo="opacity-100" enterTo="opacity-100"
@ -150,7 +150,6 @@ const SettingsLogs = () => {
> >
<Modal <Modal
title={intl.formatMessage(messages.logDetails)} title={intl.formatMessage(messages.logDetails)}
iconSvg={<DocumentSearchIcon />}
onCancel={() => setActiveLog({ log: activeLog.log, isOpen: false })} onCancel={() => setActiveLog({ log: activeLog.log, isOpen: false })}
cancelText={intl.formatMessage(globalMessages.close)} cancelText={intl.formatMessage(globalMessages.close)}
onOk={() => onOk={() =>

@ -269,7 +269,6 @@ const SettingsServices = () => {
serverType: serverType:
deleteServerModal.type === 'radarr' ? 'Radarr' : 'Sonarr', deleteServerModal.type === 'radarr' ? 'Radarr' : 'Sonarr',
})} })}
iconSvg={<TrashIcon />}
> >
{intl.formatMessage(messages.deleteserverconfirm)} {intl.formatMessage(messages.deleteserverconfirm)}
</Modal> </Modal>

@ -2,7 +2,6 @@ import Modal from '@app/components/Common/Modal';
import SensitiveInput from '@app/components/Common/SensitiveInput'; import SensitiveInput from '@app/components/Common/SensitiveInput';
import globalMessages from '@app/i18n/globalMessages'; import globalMessages from '@app/i18n/globalMessages';
import { Transition } from '@headlessui/react'; import { Transition } from '@headlessui/react';
import { PencilIcon, PlusIcon } from '@heroicons/react/solid';
import type { SonarrSettings } from '@server/lib/settings'; import type { SonarrSettings } from '@server/lib/settings';
import axios from 'axios'; import axios from 'axios';
import { Field, Formik } from 'formik'; import { Field, Formik } from 'formik';
@ -369,7 +368,6 @@ const SonarrModal = ({ onClose, sonarr, onSave }: SonarrModalProps) => {
values.is4k ? messages.edit4ksonarr : messages.editsonarr values.is4k ? messages.edit4ksonarr : messages.editsonarr
) )
} }
iconSvg={!sonarr ? <PlusIcon /> : <PencilIcon />}
> >
<div className="mb-6"> <div className="mb-6">
<div className="form-row"> <div className="form-row">

@ -3,9 +3,8 @@ import useSettings from '@app/hooks/useSettings';
import { Permission, useUser } from '@app/hooks/useUser'; import { Permission, useUser } from '@app/hooks/useUser';
import globalMessages from '@app/i18n/globalMessages'; import globalMessages from '@app/i18n/globalMessages';
import { Transition } from '@headlessui/react'; import { Transition } from '@headlessui/react';
import { RefreshIcon, SparklesIcon } from '@heroicons/react/outline';
import type { StatusResponse } from '@server/interfaces/api/settingsInterfaces'; import type { StatusResponse } from '@server/interfaces/api/settingsInterfaces';
import { useEffect, useState } from 'react'; import { Fragment, useEffect, useState } from 'react';
import { defineMessages, useIntl } from 'react-intl'; import { defineMessages, useIntl } from 'react-intl';
import useSWR from 'swr'; import useSWR from 'swr';
@ -44,7 +43,7 @@ const StatusChecker = () => {
return ( return (
<Transition <Transition
as="div" as={Fragment}
enter="opacity-0 transition duration-300" enter="opacity-0 transition duration-300"
enterFrom="opacity-0" enterFrom="opacity-0"
enterTo="opacity-100" enterTo="opacity-100"
@ -60,7 +59,6 @@ const StatusChecker = () => {
> >
{hasPermission(Permission.ADMIN) && data.restartRequired ? ( {hasPermission(Permission.ADMIN) && data.restartRequired ? (
<Modal <Modal
iconSvg={<RefreshIcon />}
title={intl.formatMessage(messages.restartRequired)} title={intl.formatMessage(messages.restartRequired)}
backgroundClickable={false} backgroundClickable={false}
onOk={() => { onOk={() => {
@ -75,7 +73,6 @@ const StatusChecker = () => {
</Modal> </Modal>
) : ( ) : (
<Modal <Modal
iconSvg={<SparklesIcon />}
title={intl.formatMessage(messages.appUpdated, { title={intl.formatMessage(messages.appUpdated, {
applicationTitle: settings.currentSettings.applicationTitle, applicationTitle: settings.currentSettings.applicationTitle,
})} })}

@ -418,7 +418,7 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
{hasPermission(Permission.MANAGE_REQUESTS) && data.mediaInfo && ( {hasPermission(Permission.MANAGE_REQUESTS) && data.mediaInfo && (
<Tooltip content={intl.formatMessage(messages.manageseries)}> <Tooltip content={intl.formatMessage(messages.manageseries)}>
<Button <Button
buttonType="default" buttonType="ghost"
onClick={() => setShowManager(true)} onClick={() => setShowManager(true)}
className="relative ml-2 first:ml-0" className="relative ml-2 first:ml-0"
> >

@ -3,7 +3,6 @@ import PermissionEdit from '@app/components/PermissionEdit';
import type { User } from '@app/hooks/useUser'; import type { User } from '@app/hooks/useUser';
import { useUser } from '@app/hooks/useUser'; import { useUser } from '@app/hooks/useUser';
import globalMessages from '@app/i18n/globalMessages'; import globalMessages from '@app/i18n/globalMessages';
import { PencilIcon } from '@heroicons/react/solid';
import axios from 'axios'; import axios from 'axios';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { defineMessages, useIntl } from 'react-intl'; import { defineMessages, useIntl } from 'react-intl';
@ -86,7 +85,6 @@ const BulkEditModal = ({
return ( return (
<Modal <Modal
title={intl.formatMessage(messages.edituser)} title={intl.formatMessage(messages.edituser)}
iconSvg={<PencilIcon />}
onOk={() => { onOk={() => {
updateUsers(); updateUsers();
}} }}

@ -2,7 +2,6 @@ import Alert from '@app/components/Common/Alert';
import Modal from '@app/components/Common/Modal'; import Modal from '@app/components/Common/Modal';
import useSettings from '@app/hooks/useSettings'; import useSettings from '@app/hooks/useSettings';
import globalMessages from '@app/i18n/globalMessages'; import globalMessages from '@app/i18n/globalMessages';
import { InboxInIcon } from '@heroicons/react/solid';
import axios from 'axios'; import axios from 'axios';
import { useState } from 'react'; import { useState } from 'react';
import { defineMessages, useIntl } from 'react-intl'; import { defineMessages, useIntl } from 'react-intl';
@ -105,7 +104,6 @@ const PlexImportModal = ({ onCancel, onComplete }: PlexImportProps) => {
<Modal <Modal
loading={!data && !error} loading={!data && !error}
title={intl.formatMessage(messages.importfromplex)} title={intl.formatMessage(messages.importfromplex)}
iconSvg={<InboxInIcon />}
onOk={() => { onOk={() => {
importUsers(); importUsers();
}} }}

@ -15,7 +15,6 @@ import type { User } from '@app/hooks/useUser';
import { Permission, UserType, useUser } from '@app/hooks/useUser'; import { Permission, UserType, useUser } from '@app/hooks/useUser';
import globalMessages from '@app/i18n/globalMessages'; import globalMessages from '@app/i18n/globalMessages';
import { Transition } from '@headlessui/react'; import { Transition } from '@headlessui/react';
import { TrashIcon } from '@heroicons/react/outline';
import { import {
ChevronLeftIcon, ChevronLeftIcon,
ChevronRightIcon, ChevronRightIcon,
@ -49,7 +48,7 @@ const messages = defineMessages({
owner: 'Owner', owner: 'Owner',
admin: 'Admin', admin: 'Admin',
plexuser: 'Plex User', plexuser: 'Plex User',
deleteuser: 'Delete {username}', deleteuser: 'Delete User',
userdeleted: 'User deleted successfully!', userdeleted: 'User deleted successfully!',
userdeleteerror: 'Something went wrong while deleting the user.', userdeleteerror: 'Something went wrong while deleting the user.',
deleteconfirm: deleteconfirm:
@ -249,10 +248,8 @@ const UserList = () => {
onCancel={() => onCancel={() =>
setDeleteModal({ isOpen: false, user: deleteModal.user }) setDeleteModal({ isOpen: false, user: deleteModal.user })
} }
title={intl.formatMessage(messages.deleteuser, { title={intl.formatMessage(messages.deleteuser)}
username: `${deleteModal.user?.displayName}`, subTitle={deleteModal.user?.displayName}
})}
iconSvg={<TrashIcon />}
> >
{intl.formatMessage(messages.deleteconfirm)} {intl.formatMessage(messages.deleteconfirm)}
</Modal> </Modal>
@ -317,7 +314,6 @@ const UserList = () => {
return ( return (
<Modal <Modal
title={intl.formatMessage(messages.createlocaluser)} title={intl.formatMessage(messages.createlocaluser)}
iconSvg={<UserAddIcon />}
onOk={() => handleSubmit()} onOk={() => handleSubmit()}
okText={ okText={
isSubmitting isSubmitting

@ -58,7 +58,7 @@ const ProfileHeader = ({ user, isSettingsPage }: ProfileHeaderProps) => {
user.id === loggedInUser?.id ? '/profile' : `/users/${user.id}` user.id === loggedInUser?.id ? '/profile' : `/users/${user.id}`
} }
> >
<a className="bg-gradient-to-br from-indigo-400 to-purple-400 bg-clip-text text-lg font-bold text-transparent hover:to-purple-200 sm:text-2xl"> <a className="text-overseerr text-lg font-bold hover:to-purple-200 sm:text-2xl">
{user.displayName} {user.displayName}
</a> </a>
</Link> </Link>

@ -380,23 +380,27 @@
"components.RequestModal.errorediting": "Something went wrong while editing the request.", "components.RequestModal.errorediting": "Something went wrong while editing the request.",
"components.RequestModal.extras": "Extras", "components.RequestModal.extras": "Extras",
"components.RequestModal.numberofepisodes": "# of Episodes", "components.RequestModal.numberofepisodes": "# of Episodes",
"components.RequestModal.pending4krequest": "Pending 4K Request for {title}", "components.RequestModal.pending4krequest": "Pending 4K Request",
"components.RequestModal.pendingapproval": "Your request is pending approval.", "components.RequestModal.pendingapproval": "Your request is pending approval.",
"components.RequestModal.pendingrequest": "Pending Request for {title}", "components.RequestModal.pendingrequest": "Pending Request",
"components.RequestModal.request4ktitle": "Request {title} in 4K",
"components.RequestModal.requestApproved": "Request for <strong>{title}</strong> approved!", "components.RequestModal.requestApproved": "Request for <strong>{title}</strong> approved!",
"components.RequestModal.requestCancel": "Request for <strong>{title}</strong> canceled.", "components.RequestModal.requestCancel": "Request for <strong>{title}</strong> canceled.",
"components.RequestModal.requestSuccess": "<strong>{title}</strong> requested successfully!", "components.RequestModal.requestSuccess": "<strong>{title}</strong> requested successfully!",
"components.RequestModal.requestadmin": "This request will be approved automatically.", "components.RequestModal.requestadmin": "This request will be approved automatically.",
"components.RequestModal.requestcancelled": "Request for <strong>{title}</strong> canceled.", "components.RequestModal.requestcancelled": "Request for <strong>{title}</strong> canceled.",
"components.RequestModal.requestcollection4ktitle": "Request Collection in 4K",
"components.RequestModal.requestcollectiontitle": "Request Collection",
"components.RequestModal.requestedited": "Request for <strong>{title}</strong> edited successfully!", "components.RequestModal.requestedited": "Request for <strong>{title}</strong> edited successfully!",
"components.RequestModal.requesterror": "Something went wrong while submitting the request.", "components.RequestModal.requesterror": "Something went wrong while submitting the request.",
"components.RequestModal.requestfrom": "{username}'s request is pending approval.", "components.RequestModal.requestfrom": "{username}'s request is pending approval.",
"components.RequestModal.requestmovie4ktitle": "Request Movie in 4K",
"components.RequestModal.requestmovies": "Request {count} {count, plural, one {Movie} other {Movies}}", "components.RequestModal.requestmovies": "Request {count} {count, plural, one {Movie} other {Movies}}",
"components.RequestModal.requestmovies4k": "Request {count} {count, plural, one {Movie} other {Movies}} in 4K", "components.RequestModal.requestmovies4k": "Request {count} {count, plural, one {Movie} other {Movies}} in 4K",
"components.RequestModal.requestmovietitle": "Request Movie",
"components.RequestModal.requestseasons": "Request {seasonCount} {seasonCount, plural, one {Season} other {Seasons}}", "components.RequestModal.requestseasons": "Request {seasonCount} {seasonCount, plural, one {Season} other {Seasons}}",
"components.RequestModal.requestseasons4k": "Request {seasonCount} {seasonCount, plural, one {Season} other {Seasons}} in 4K", "components.RequestModal.requestseasons4k": "Request {seasonCount} {seasonCount, plural, one {Season} other {Seasons}} in 4K",
"components.RequestModal.requesttitle": "Request {title}", "components.RequestModal.requestseries4ktitle": "Request Series in 4K",
"components.RequestModal.requestseriestitle": "Request Series",
"components.RequestModal.season": "Season", "components.RequestModal.season": "Season",
"components.RequestModal.seasonnumber": "Season {number}", "components.RequestModal.seasonnumber": "Season {number}",
"components.RequestModal.selectmovies": "Select Movie(s)", "components.RequestModal.selectmovies": "Select Movie(s)",
@ -920,7 +924,7 @@
"components.UserList.createlocaluser": "Create Local User", "components.UserList.createlocaluser": "Create Local User",
"components.UserList.creating": "Creating…", "components.UserList.creating": "Creating…",
"components.UserList.deleteconfirm": "Are you sure you want to delete this user? All of their request data will be permanently removed.", "components.UserList.deleteconfirm": "Are you sure you want to delete this user? All of their request data will be permanently removed.",
"components.UserList.deleteuser": "Delete {username}", "components.UserList.deleteuser": "Delete User",
"components.UserList.displayName": "Display Name", "components.UserList.displayName": "Display Name",
"components.UserList.edituser": "Edit User Permissions", "components.UserList.edituser": "Edit User Permissions",
"components.UserList.email": "Email Address", "components.UserList.email": "Email Address",

@ -307,7 +307,7 @@
} }
button.input-action { button.input-action {
@apply relative -ml-px inline-flex items-center border border-gray-500 bg-indigo-600 px-3 py-2 text-sm font-medium leading-5 text-white transition duration-150 ease-in-out last:rounded-r-md hover:bg-indigo-500 active:bg-gray-100 active:text-gray-700 sm:px-3.5; @apply relative -ml-px inline-flex items-center border border-gray-500 bg-indigo-600 bg-opacity-80 px-3 py-2 text-sm font-medium leading-5 text-white transition duration-150 ease-in-out last:rounded-r-md hover:bg-opacity-100 active:bg-gray-100 active:text-gray-700 sm:px-3.5;
} }
.button-md svg, .button-md svg,
@ -320,14 +320,6 @@
@apply ml-1.5 mr-1.5 h-4 w-4 first:ml-0 last:mr-0; @apply ml-1.5 mr-1.5 h-4 w-4 first:ml-0 last:mr-0;
} }
.modal-icon {
@apply mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-gray-800 text-white ring-1 ring-gray-500 sm:mx-0 sm:h-10 sm:w-10;
}
.modal-icon svg {
@apply h-6 w-6;
}
svg.icon-md { svg.icon-md {
@apply h-5 w-5; @apply h-5 w-5;
} }
@ -451,6 +443,10 @@
scrollbar-width: none; /* Firefox */ scrollbar-width: none; /* Firefox */
} }
.text-overseerr {
@apply bg-gradient-to-br from-indigo-400 to-purple-400 bg-clip-text text-transparent;
}
@media all and (display-mode: browser) { @media all and (display-mode: browser) {
.pwa-only { .pwa-only {
@apply hidden; @apply hidden;

@ -1809,10 +1809,10 @@
resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb"
integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ== integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==
"@headlessui/react@1.6.6": "@headlessui/react@^0.0.0-insiders.b301f04":
version "1.6.6" version "0.0.0-insiders.b301f04"
resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.6.6.tgz#3073c066b85535c9d28783da0a4d9288b5354d0c" resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-0.0.0-insiders.b301f04.tgz#12f209097f987cddbba72de40240a12648ebe12b"
integrity sha512-MFJtmj9Xh/hhBMhLccGbBoSk+sk61BlP6sJe4uQcVMtXZhCgGqd2GyIQzzmsdPdTEWGSF434CBi8mnhR6um46Q== integrity sha512-I+UUEUkdYp+AgKU1teWUZaICEg//OWl0R1UxCB50Jzfi3APu7aSY0Y+ABrgfEWDhBYTG9hNgXvMVTNCnFu49yA==
"@heroicons/react@1.0.6": "@heroicons/react@1.0.6":
version "1.0.6" version "1.0.6"

Loading…
Cancel
Save