pull/3470/merge
Brandon Cohen 2 weeks ago committed by GitHub
commit 2271c9df43
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -31,7 +31,7 @@ import { useRouter } from 'next/router';
import { useState } from 'react';
import { defineMessages, FormattedRelativeTime, useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications';
import useSWR from 'swr';
import useSWR, { mutate } from 'swr';
import * as Yup from 'yup';
const messages = defineMessages({
@ -144,6 +144,7 @@ const IssueDetails = () => {
autoDismiss: true,
});
revalidateIssue();
mutate('/api/v1/issue/count');
} catch (e) {
addToast(intl.formatMessage(messages.toaststatusupdatefailed), {
appearance: 'error',
@ -155,6 +156,7 @@ const IssueDetails = () => {
const deleteIssue = async () => {
try {
await axios.delete(`/api/v1/issue/${issueData.id}`);
mutate('/api/v1/issue/count');
addToast(intl.formatMessage(messages.toastissuedeleted), {
appearance: 'success',

@ -1,3 +1,4 @@
import Badge from '@app/components/Common/Badge';
import { menuMessages } from '@app/components/Layout/Sidebar';
import useClickOutside from '@app/hooks/useClickOutside';
import { Permission, useUser } from '@app/hooks/useUser';
@ -27,6 +28,11 @@ import { useRouter } from 'next/router';
import { cloneElement, useRef, useState } from 'react';
import { useIntl } from 'react-intl';
interface MobileMenuProps {
pendingRequestsCount: number;
openIssuesCount: number;
}
interface MenuLink {
href: string;
svgIcon: JSX.Element;
@ -39,7 +45,10 @@ interface MenuLink {
dataTestId?: string;
}
const MobileMenu = () => {
const MobileMenu = ({
pendingRequestsCount,
openIssuesCount,
}: MobileMenuProps) => {
const ref = useRef<HTMLDivElement>(null);
const intl = useIntl();
const [isOpen, setIsOpen] = useState(false);
@ -144,7 +153,7 @@ const MobileMenu = () => {
return (
<Link key={`mobile-menu-link-${link.href}`} href={link.href}>
<a
className={`flex items-center space-x-2 ${
className={`flex items-center ${
isActive ? 'text-indigo-500' : ''
}`}
onKeyDown={(e) => {
@ -159,7 +168,25 @@ const MobileMenu = () => {
{cloneElement(isActive ? link.svgIconSelected : link.svgIcon, {
className: 'h-5 w-5',
})}
<span>{link.content}</span>
<span className="ml-2">{link.content}</span>
{link.href === '/requests' &&
pendingRequestsCount > 0 &&
hasPermission(Permission.MANAGE_REQUESTS) && (
<div className="ml-auto">
<Badge className="rounded-md border-indigo-500 bg-gradient-to-br from-indigo-600 to-purple-600">
{pendingRequestsCount}
</Badge>
</div>
)}
{link.href === '/issues' &&
openIssuesCount > 0 &&
hasPermission(Permission.MANAGE_ISSUES) && (
<div className="ml-auto">
<Badge className="rounded-md border-indigo-500 bg-gradient-to-br from-indigo-600 to-purple-600">
{openIssuesCount}
</Badge>
</div>
)}
</a>
</Link>
);
@ -175,7 +202,7 @@ const MobileMenu = () => {
return (
<Link key={`mobile-menu-link-${link.href}`} href={link.href}>
<a
className={`flex flex-col items-center space-y-1 ${
className={`relative flex flex-col items-center space-y-1 ${
isActive ? 'text-indigo-500' : ''
}`}
>
@ -185,6 +212,21 @@ const MobileMenu = () => {
className: 'h-6 w-6',
}
)}
{link.href === '/requests' &&
pendingRequestsCount > 0 &&
hasPermission(Permission.MANAGE_REQUESTS) && (
<div className="absolute left-3 bottom-3">
<Badge
className={`bg-gradient-to-br ${
router.pathname.match(link.activeRegExp)
? 'border-indigo-600 from-indigo-700 to-purple-700'
: 'border-indigo-500 from-indigo-600 to-purple-600'
} !px-1 leading-none`}
>
{pendingRequestsCount}
</Badge>
</div>
)}
</a>
</Link>
);

@ -1,3 +1,4 @@
import Badge from '@app/components/Common/Badge';
import VersionStatus from '@app/components/Layout/VersionStatus';
import useClickOutside from '@app/hooks/useClickOutside';
import { Permission, useUser } from '@app/hooks/useUser';
@ -30,6 +31,8 @@ export const menuMessages = defineMessages({
interface SidebarProps {
open?: boolean;
setClosed: () => void;
pendingRequestsCount: number;
openIssuesCount: number;
}
interface SidebarLinkProps {
@ -98,7 +101,12 @@ const SidebarLinks: SidebarLinkProps[] = [
},
];
const Sidebar = ({ open, setClosed }: SidebarProps) => {
const Sidebar = ({
open,
setClosed,
pendingRequestsCount,
openIssuesCount,
}: SidebarProps) => {
const navRef = useRef<HTMLDivElement>(null);
const router = useRouter();
const intl = useIntl();
@ -254,6 +262,40 @@ const Sidebar = ({ open, setClosed }: SidebarProps) => {
{intl.formatMessage(
menuMessages[sidebarLink.messagesKey]
)}
{sidebarLink.messagesKey === 'requests' &&
pendingRequestsCount > 0 &&
hasPermission(Permission.MANAGE_REQUESTS) && (
<div className="ml-auto">
<Badge
className={`rounded-md bg-gradient-to-br ${
router.pathname.match(
sidebarLink.activeRegExp
)
? 'border-indigo-600 from-indigo-700 to-purple-700'
: 'border-indigo-500 from-indigo-600 to-purple-600'
}`}
>
{pendingRequestsCount}
</Badge>
</div>
)}
{sidebarLink.messagesKey === 'issues' &&
openIssuesCount > 0 &&
hasPermission(Permission.MANAGE_ISSUES) && (
<div className="ml-auto">
<Badge
className={`rounded-md bg-gradient-to-br ${
router.pathname.match(
sidebarLink.activeRegExp
)
? 'border-indigo-600 from-indigo-700 to-purple-700'
: 'border-indigo-500 from-indigo-600 to-purple-600'
}`}
>
{openIssuesCount}
</Badge>
</div>
)}
</a>
</Link>
);

@ -10,6 +10,7 @@ import { useUser } from '@app/hooks/useUser';
import { ArrowLeftIcon, Bars3BottomLeftIcon } from '@heroicons/react/24/solid';
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
import useSWR from 'swr';
type LayoutProps = {
children: React.ReactNode;
@ -22,6 +23,8 @@ const Layout = ({ children }: LayoutProps) => {
const router = useRouter();
const { currentSettings } = useSettings();
const { setLocale } = useLocale();
const { data: requestResponse } = useSWR('/api/v1/request/count');
const { data: issueResponse } = useSWR('/api/v1/issue/count');
useEffect(() => {
if (setLocale && user) {
@ -55,9 +58,17 @@ const Layout = ({ children }: LayoutProps) => {
<div className="absolute top-0 h-64 w-full bg-gradient-to-bl from-gray-800 to-gray-900">
<div className="relative inset-0 h-full w-full bg-gradient-to-t from-gray-900 to-transparent" />
</div>
<Sidebar open={isSidebarOpen} setClosed={() => setSidebarOpen(false)} />
<Sidebar
open={isSidebarOpen}
setClosed={() => setSidebarOpen(false)}
pendingRequestsCount={requestResponse?.pending}
openIssuesCount={issueResponse?.open}
/>
<div className="sm:hidden">
<MobileMenu />
<MobileMenu
pendingRequestsCount={requestResponse?.pending}
openIssuesCount={issueResponse?.open}
/>
</div>
<div className="relative mb-16 flex w-0 min-w-0 flex-1 flex-col lg:ml-64">

@ -20,6 +20,7 @@ import axios from 'axios';
import Link from 'next/link';
import { useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { mutate } from 'swr';
const messages = defineMessages({
seasons: '{seasonCount, plural, one {Season} other {Seasons}}',
@ -56,6 +57,7 @@ const RequestBlock = ({ request, onUpdate }: RequestBlockProps) => {
if (onUpdate) {
onUpdate();
mutate('/api/v1/request/count');
}
setIsUpdating(false);
};
@ -66,6 +68,7 @@ const RequestBlock = ({ request, onUpdate }: RequestBlockProps) => {
if (onUpdate) {
onUpdate();
mutate('/api/v1/request/count');
}
setIsUpdating(false);

@ -15,6 +15,7 @@ import type { MediaRequest } from '@server/entity/MediaRequest';
import axios from 'axios';
import { useMemo, useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { mutate } from 'swr';
const messages = defineMessages({
viewrequest: 'View Request',
@ -97,6 +98,7 @@ const RequestButton = ({
if (response) {
onUpdate();
mutate('/api/v1/request/count');
}
};
@ -115,6 +117,7 @@ const RequestButton = ({
);
onUpdate();
mutate('/api/v1/request/count');
};
const buttons: ButtonOption[] = [];

@ -75,6 +75,7 @@ const RequestCardError = ({ requestData }: RequestCardErrorProps) => {
await axios.delete(`/api/v1/media/${requestData?.media.id}`);
mutate('/api/v1/media?filter=allavailable&take=20&sort=mediaAdded');
mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0');
mutate('/api/v1/request/count');
};
return (
@ -252,12 +253,14 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => {
if (response) {
revalidate();
mutate('/api/v1/request/count');
}
};
const deleteRequest = async () => {
await axios.delete(`/api/v1/request/${request.id}`);
mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0');
mutate('/api/v1/request/count');
};
const retryRequest = async () => {

@ -25,7 +25,7 @@ import { useState } from 'react';
import { useInView } from 'react-intersection-observer';
import { defineMessages, FormattedRelativeTime, useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications';
import useSWR from 'swr';
import useSWR, { mutate } from 'swr';
const messages = defineMessages({
seasons: '{seasonCount, plural, one {Season} other {Seasons}}',
@ -62,6 +62,7 @@ const RequestItemError = ({
const deleteRequest = async () => {
await axios.delete(`/api/v1/media/${requestData?.media.id}`);
revalidateList();
mutate('/api/v1/request/count');
};
const { plexUrl, plexUrl4k } = useDeepLinks({
@ -311,6 +312,7 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
if (response) {
revalidate();
mutate('/api/v1/request/count');
}
};
@ -318,6 +320,7 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
await axios.delete(`/api/v1/request/${request.id}`);
revalidateList();
mutate('/api/v1/request/count');
};
const retryRequest = async () => {

@ -16,7 +16,7 @@ import axios from 'axios';
import { useCallback, useEffect, useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications';
import useSWR from 'swr';
import useSWR, { mutate } from 'swr';
const messages = defineMessages({
requestadmin: 'This request will be approved automatically.',
@ -211,6 +211,7 @@ const CollectionRequestModal = ({
? MediaStatus.UNKNOWN
: MediaStatus.PARTIALLY_AVAILABLE
);
mutate('/api/v1/request/count');
}
addToast(
@ -230,7 +231,16 @@ const CollectionRequestModal = ({
} finally {
setIsUpdating(false);
}
}, [requestOverrides, data, onComplete, addToast, intl, selectedParts, is4k]);
}, [
requestOverrides,
data?.parts,
data?.name,
onComplete,
addToast,
intl,
selectedParts,
is4k,
]);
const hasAutoApprove = hasPermission(
[

@ -95,6 +95,7 @@ const MovieRequestModal = ({
...overrideParams,
});
mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0');
mutate('/api/v1/request/count');
if (response.data) {
if (onComplete) {
@ -129,7 +130,16 @@ const MovieRequestModal = ({
} finally {
setIsUpdating(false);
}
}, [data, onComplete, addToast, requestOverrides, hasPermission, intl, is4k]);
}, [
requestOverrides,
data?.id,
data?.title,
is4k,
onComplete,
addToast,
intl,
hasPermission,
]);
const cancelRequest = async () => {
setIsUpdating(true);
@ -139,6 +149,7 @@ const MovieRequestModal = ({
`/api/v1/request/${editRequest?.id}`
);
mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0');
mutate('/api/v1/request/count');
if (response.status === 204) {
if (onComplete) {
@ -176,6 +187,7 @@ const MovieRequestModal = ({
await axios.post(`/api/v1/request/${editRequest?.id}/approve`);
}
mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0');
mutate('/api/v1/request/count');
addToast(
<span>

@ -106,6 +106,7 @@ const TvRequestModal = ({
if (onUpdating) {
onUpdating(true);
mutate('/api/v1/request/count');
}
try {
@ -128,6 +129,7 @@ const TvRequestModal = ({
await axios.delete(`/api/v1/request/${editRequest.id}`);
}
mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0');
mutate('/api/v1/request/count');
addToast(
<span>
@ -176,6 +178,7 @@ const TvRequestModal = ({
if (onUpdating) {
onUpdating(true);
mutate('/api/v1/request/count');
}
try {

Loading…
Cancel
Save