From 81125c94f8deb61a726e4efdb6f1af18bcc2ee5e Mon Sep 17 00:00:00 2001 From: Brandon Cohen Date: Thu, 25 May 2023 22:46:31 -0400 Subject: [PATCH 1/3] feat: request and issue count added to sidebar/mobile menu --- src/components/Common/Badge/index.tsx | 8 +++- src/components/IssueDetails/index.tsx | 12 ++++++ src/components/Layout/MobileMenu/index.tsx | 42 +++++++++++++++++-- src/components/Layout/Sidebar/index.tsx | 32 +++++++++++++- src/components/Layout/index.tsx | 24 ++++++++--- src/components/RequestBlock/index.tsx | 13 ++++++ src/components/RequestButton/index.tsx | 13 ++++++ src/components/RequestCard/index.tsx | 13 ++++++ .../RequestList/RequestItem/index.tsx | 13 ++++++ .../RequestModal/CollectionRequestModal.tsx | 15 ++++++- .../RequestModal/MovieRequestModal.tsx | 17 +++++++- .../RequestModal/TvRequestModal.tsx | 5 +++ src/components/RequestModal/index.tsx | 15 +++++++ 13 files changed, 209 insertions(+), 13 deletions(-) diff --git a/src/components/Common/Badge/index.tsx b/src/components/Common/Badge/index.tsx index 17eda5b1..ec13c05d 100644 --- a/src/components/Common/Badge/index.tsx +++ b/src/components/Common/Badge/index.tsx @@ -9,7 +9,8 @@ interface BadgeProps { | 'warning' | 'success' | 'dark' - | 'light'; + | 'light' + | 'gradient'; className?: string; href?: string; children: React.ReactNode; @@ -66,6 +67,11 @@ const Badge = ( badgeStyle.push('hover:bg-gray-600'); } break; + case 'gradient': + badgeStyle.push( + 'border border-white bg-gradient-to-br from-indigo-600 to-purple-600 !text-indigo-100 shadow-black' + ); + break; default: badgeStyle.push( 'bg-indigo-500 bg-opacity-80 border border-indigo-500 !text-indigo-100' diff --git a/src/components/IssueDetails/index.tsx b/src/components/IssueDetails/index.tsx index 712d3379..4fbe7145 100644 --- a/src/components/IssueDetails/index.tsx +++ b/src/components/IssueDetails/index.tsx @@ -32,6 +32,7 @@ import { useState } from 'react'; import { defineMessages, FormattedRelativeTime, useIntl } from 'react-intl'; import { useToasts } from 'react-toast-notifications'; import useSWR from 'swr'; +import useSWRMutation from 'swr/mutation'; import * as Yup from 'yup'; const messages = defineMessages({ @@ -104,6 +105,16 @@ const IssueDetails = () => { (opt) => opt.issueType === issueData?.issueType ); + const fetchIssuesCount = async () => { + const response = await axios.get('/api/v1/request/count'); + return response.data; + }; + + const { trigger: issueTrigger } = useSWRMutation( + '/api/v1/issue/count', + fetchIssuesCount + ); + if (!data && !error) { return ; } @@ -144,6 +155,7 @@ const IssueDetails = () => { autoDismiss: true, }); revalidateIssue(); + issueTrigger(); } catch (e) { addToast(intl.formatMessage(messages.toaststatusupdatefailed), { appearance: 'error', diff --git a/src/components/Layout/MobileMenu/index.tsx b/src/components/Layout/MobileMenu/index.tsx index f6c574f4..6e6dbee6 100644 --- a/src/components/Layout/MobileMenu/index.tsx +++ b/src/components/Layout/MobileMenu/index.tsx @@ -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(null); const intl = useIntl(); const [isOpen, setIsOpen] = useState(false); @@ -144,7 +153,7 @@ const MobileMenu = () => { return ( { @@ -159,7 +168,23 @@ const MobileMenu = () => { {cloneElement(isActive ? link.svgIconSelected : link.svgIcon, { className: 'h-5 w-5', })} - {link.content} + {link.content} + {link.href === '/requests' && pendingRequestsCount && ( +
+ + {pendingRequestsCount < 100 + ? pendingRequestsCount + : '99+'} + +
+ )} + {link.href === '/issues' && openIssuesCount && ( +
+ + {openIssuesCount < 100 ? openIssuesCount : '99+'} + +
+ )}
); @@ -175,7 +200,7 @@ const MobileMenu = () => { return ( @@ -185,6 +210,15 @@ const MobileMenu = () => { className: 'h-6 w-6', } )} + {link.href === '/requests' && pendingRequestsCount && ( +
+ + {pendingRequestsCount < 100 + ? pendingRequestsCount + : '99+'} + +
+ )}
); diff --git a/src/components/Layout/Sidebar/index.tsx b/src/components/Layout/Sidebar/index.tsx index 81ebb86c..63596a78 100644 --- a/src/components/Layout/Sidebar/index.tsx +++ b/src/components/Layout/Sidebar/index.tsx @@ -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(null); const router = useRouter(); const intl = useIntl(); @@ -254,6 +262,28 @@ const Sidebar = ({ open, setClosed }: SidebarProps) => { {intl.formatMessage( menuMessages[sidebarLink.messagesKey] )} + {sidebarLink.messagesKey === 'requests' && + pendingRequestsCount && + pendingRequestsCount > 0 && ( +
+ + {pendingRequestsCount < 100 + ? pendingRequestsCount + : '99+'} + +
+ )} + {sidebarLink.messagesKey === 'issues' && + openIssuesCount && + openIssuesCount > 0 && ( +
+ + {openIssuesCount < 100 + ? openIssuesCount + : '99+'} + +
+ )} ); diff --git a/src/components/Layout/index.tsx b/src/components/Layout/index.tsx index 878f27b1..c3a3e224 100644 --- a/src/components/Layout/index.tsx +++ b/src/components/Layout/index.tsx @@ -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,11 +58,22 @@ const Layout = ({ children }: LayoutProps) => {
- setSidebarOpen(false)} /> -
- -
- + {requestResponse && issueResponse && ( + setSidebarOpen(false)} + pendingRequestsCount={requestResponse.pending} + openIssuesCount={issueResponse.open} + /> + )} + {requestResponse && issueResponse && ( +
+ +
+ )}
{ const { profile, rootFolder, server, languageProfile } = useRequestOverride(request); + const fetchRequestsCount = async () => { + const response = await axios.get('/api/v1/request/count'); + return response.data; + }; + + const { trigger: requestTrigger } = useSWRMutation( + '/api/v1/request/count', + fetchRequestsCount + ); + const updateRequest = async (type: 'approve' | 'decline'): Promise => { setIsUpdating(true); await axios.post(`/api/v1/request/${request.id}/${type}`); if (onUpdate) { onUpdate(); + requestTrigger(); } setIsUpdating(false); }; @@ -66,6 +78,7 @@ const RequestBlock = ({ request, onUpdate }: RequestBlockProps) => { if (onUpdate) { onUpdate(); + requestTrigger(); } setIsUpdating(false); diff --git a/src/components/RequestButton/index.tsx b/src/components/RequestButton/index.tsx index 56e91810..bf52398b 100644 --- a/src/components/RequestButton/index.tsx +++ b/src/components/RequestButton/index.tsx @@ -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 useSWRMutation from 'swr/mutation'; const messages = defineMessages({ viewrequest: 'View Request', @@ -89,6 +90,16 @@ const RequestButton = ({ : undefined; }, [active4kRequests, user]); + const fetchRequestsCount = async () => { + const response = await axios.get('/api/v1/request/count'); + return response.data; + }; + + const { trigger: requestTrigger } = useSWRMutation( + '/api/v1/request/count', + fetchRequestsCount + ); + const modifyRequest = async ( request: MediaRequest, type: 'approve' | 'decline' @@ -97,6 +108,7 @@ const RequestButton = ({ if (response) { onUpdate(); + requestTrigger(); } }; @@ -115,6 +127,7 @@ const RequestButton = ({ ); onUpdate(); + requestTrigger(); }; const buttons: ButtonOption[] = []; diff --git a/src/components/RequestCard/index.tsx b/src/components/RequestCard/index.tsx index 44abd555..7a9368a7 100644 --- a/src/components/RequestCard/index.tsx +++ b/src/components/RequestCard/index.tsx @@ -27,6 +27,7 @@ import { useInView } from 'react-intersection-observer'; import { defineMessages, useIntl } from 'react-intl'; import { useToasts } from 'react-toast-notifications'; import useSWR, { mutate } from 'swr'; +import useSWRMutation from 'swr/mutation'; const messages = defineMessages({ seasons: '{seasonCount, plural, one {Season} other {Seasons}}', @@ -247,17 +248,29 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => { iOSPlexUrl4k: requestData?.media?.iOSPlexUrl4k, }); + const fetchRequestsCount = async () => { + const response = await axios.get('/api/v1/request/count'); + return response.data; + }; + + const { trigger: requestTrigger } = useSWRMutation( + '/api/v1/request/count', + fetchRequestsCount + ); + const modifyRequest = async (type: 'approve' | 'decline') => { const response = await axios.post(`/api/v1/request/${request.id}/${type}`); if (response) { revalidate(); + requestTrigger(); } }; const deleteRequest = async () => { await axios.delete(`/api/v1/request/${request.id}`); mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0'); + requestTrigger(); }; const retryRequest = async () => { diff --git a/src/components/RequestList/RequestItem/index.tsx b/src/components/RequestList/RequestItem/index.tsx index a42483ab..9f3b540f 100644 --- a/src/components/RequestList/RequestItem/index.tsx +++ b/src/components/RequestList/RequestItem/index.tsx @@ -26,6 +26,7 @@ import { useInView } from 'react-intersection-observer'; import { defineMessages, FormattedRelativeTime, useIntl } from 'react-intl'; import { useToasts } from 'react-toast-notifications'; import useSWR from 'swr'; +import useSWRMutation from 'swr/mutation'; const messages = defineMessages({ seasons: '{seasonCount, plural, one {Season} other {Seasons}}', @@ -306,11 +307,22 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => { const [isRetrying, setRetrying] = useState(false); + const fetchRequestsCount = async () => { + const response = await axios.get('/api/v1/request/count'); + return response.data; + }; + + const { trigger: requestTrigger } = useSWRMutation( + '/api/v1/request/count', + fetchRequestsCount + ); + const modifyRequest = async (type: 'approve' | 'decline') => { const response = await axios.post(`/api/v1/request/${request.id}/${type}`); if (response) { revalidate(); + requestTrigger(); } }; @@ -318,6 +330,7 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => { await axios.delete(`/api/v1/request/${request.id}`); revalidateList(); + requestTrigger(); }; const retryRequest = async () => { diff --git a/src/components/RequestModal/CollectionRequestModal.tsx b/src/components/RequestModal/CollectionRequestModal.tsx index 614a00da..e2c30100 100644 --- a/src/components/RequestModal/CollectionRequestModal.tsx +++ b/src/components/RequestModal/CollectionRequestModal.tsx @@ -36,6 +36,7 @@ interface RequestModalProps extends React.HTMLAttributes { onCancel?: () => void; onComplete?: (newStatus: MediaStatus) => void; onUpdating?: (isUpdating: boolean) => void; + requestTrigger: () => void; } const CollectionRequestModal = ({ @@ -44,6 +45,7 @@ const CollectionRequestModal = ({ tmdbId, onUpdating, is4k = false, + requestTrigger, }: RequestModalProps) => { const [isUpdating, setIsUpdating] = useState(false); const [requestOverrides, setRequestOverrides] = @@ -211,6 +213,7 @@ const CollectionRequestModal = ({ ? MediaStatus.UNKNOWN : MediaStatus.PARTIALLY_AVAILABLE ); + requestTrigger(); } addToast( @@ -230,7 +233,17 @@ const CollectionRequestModal = ({ } finally { setIsUpdating(false); } - }, [requestOverrides, data, onComplete, addToast, intl, selectedParts, is4k]); + }, [ + requestOverrides, + data?.parts, + data?.name, + onComplete, + addToast, + intl, + selectedParts, + is4k, + requestTrigger, + ]); const hasAutoApprove = hasPermission( [ diff --git a/src/components/RequestModal/MovieRequestModal.tsx b/src/components/RequestModal/MovieRequestModal.tsx index 76a12638..770bcc6a 100644 --- a/src/components/RequestModal/MovieRequestModal.tsx +++ b/src/components/RequestModal/MovieRequestModal.tsx @@ -42,6 +42,7 @@ interface RequestModalProps extends React.HTMLAttributes { onCancel?: () => void; onComplete?: (newStatus: MediaStatus) => void; onUpdating?: (isUpdating: boolean) => void; + requestTrigger: () => void; } const MovieRequestModal = ({ @@ -51,6 +52,7 @@ const MovieRequestModal = ({ onUpdating, editRequest, is4k = false, + requestTrigger, }: RequestModalProps) => { const [isUpdating, setIsUpdating] = useState(false); const [requestOverrides, setRequestOverrides] = @@ -95,6 +97,7 @@ const MovieRequestModal = ({ ...overrideParams, }); mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0'); + requestTrigger(); if (response.data) { if (onComplete) { @@ -129,7 +132,17 @@ const MovieRequestModal = ({ } finally { setIsUpdating(false); } - }, [data, onComplete, addToast, requestOverrides, hasPermission, intl, is4k]); + }, [ + requestOverrides, + data?.id, + data?.title, + is4k, + requestTrigger, + onComplete, + addToast, + intl, + hasPermission, + ]); const cancelRequest = async () => { setIsUpdating(true); @@ -139,6 +152,7 @@ const MovieRequestModal = ({ `/api/v1/request/${editRequest?.id}` ); mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0'); + requestTrigger(); if (response.status === 204) { if (onComplete) { @@ -176,6 +190,7 @@ const MovieRequestModal = ({ await axios.post(`/api/v1/request/${editRequest?.id}/approve`); } mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0'); + requestTrigger(); addToast( diff --git a/src/components/RequestModal/TvRequestModal.tsx b/src/components/RequestModal/TvRequestModal.tsx index 25c8fd3c..3e520369 100644 --- a/src/components/RequestModal/TvRequestModal.tsx +++ b/src/components/RequestModal/TvRequestModal.tsx @@ -56,6 +56,7 @@ interface RequestModalProps extends React.HTMLAttributes { onCancel?: () => void; onComplete?: (newStatus: MediaStatus) => void; onUpdating?: (isUpdating: boolean) => void; + requestTrigger: () => void; is4k?: boolean; editRequest?: MediaRequest; } @@ -67,6 +68,7 @@ const TvRequestModal = ({ onUpdating, editRequest, is4k = false, + requestTrigger, }: RequestModalProps) => { const settings = useSettings(); const { addToast } = useToasts(); @@ -106,6 +108,7 @@ const TvRequestModal = ({ if (onUpdating) { onUpdating(true); + requestTrigger(); } try { @@ -128,6 +131,7 @@ const TvRequestModal = ({ await axios.delete(`/api/v1/request/${editRequest.id}`); } mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0'); + requestTrigger(); addToast( @@ -176,6 +180,7 @@ const TvRequestModal = ({ if (onUpdating) { onUpdating(true); + requestTrigger(); } try { diff --git a/src/components/RequestModal/index.tsx b/src/components/RequestModal/index.tsx index 9ef6b405..828aa443 100644 --- a/src/components/RequestModal/index.tsx +++ b/src/components/RequestModal/index.tsx @@ -4,6 +4,8 @@ import TvRequestModal from '@app/components/RequestModal/TvRequestModal'; import { Transition } from '@headlessui/react'; import type { MediaStatus } from '@server/constants/media'; import type { MediaRequest } from '@server/entity/MediaRequest'; +import axios from 'axios'; +import useSWRMutation from 'swr/mutation'; interface RequestModalProps { show: boolean; @@ -26,6 +28,16 @@ const RequestModal = ({ onUpdating, onCancel, }: RequestModalProps) => { + const fetchRequestsCount = async () => { + const response = await axios.get('/api/v1/request/count'); + return response.data; + }; + + const { trigger: requestTrigger } = useSWRMutation( + '/api/v1/request/count', + fetchRequestsCount + ); + return ( ) : type === 'tv' ? ( ) : ( )} From 630ebe3f2a46f6625a9b64126a9ef59c892e5239 Mon Sep 17 00:00:00 2001 From: Brandon Date: Thu, 25 May 2023 23:07:19 -0400 Subject: [PATCH 2/3] fix: added permission check for count visibility --- src/components/Layout/MobileMenu/index.tsx | 58 ++++++++++++---------- src/components/Layout/Sidebar/index.tsx | 6 ++- 2 files changed, 37 insertions(+), 27 deletions(-) diff --git a/src/components/Layout/MobileMenu/index.tsx b/src/components/Layout/MobileMenu/index.tsx index 6e6dbee6..c8b1f30b 100644 --- a/src/components/Layout/MobileMenu/index.tsx +++ b/src/components/Layout/MobileMenu/index.tsx @@ -169,22 +169,28 @@ const MobileMenu = ({ className: 'h-5 w-5', })} {link.content} - {link.href === '/requests' && pendingRequestsCount && ( -
- - {pendingRequestsCount < 100 - ? pendingRequestsCount - : '99+'} - -
- )} - {link.href === '/issues' && openIssuesCount && ( -
- - {openIssuesCount < 100 ? openIssuesCount : '99+'} - -
- )} + {link.href === '/requests' && + pendingRequestsCount && + pendingRequestsCount > 0 && + hasPermission(Permission.MANAGE_REQUESTS) && ( +
+ + {pendingRequestsCount < 100 + ? pendingRequestsCount + : '99+'} + +
+ )} + {link.href === '/issues' && + openIssuesCount && + openIssuesCount && + hasPermission(Permission.MANAGE_ISSUES) && ( +
+ + {openIssuesCount < 100 ? openIssuesCount : '99+'} + +
+ )} ); @@ -210,15 +216,17 @@ const MobileMenu = ({ className: 'h-6 w-6', } )} - {link.href === '/requests' && pendingRequestsCount && ( -
- - {pendingRequestsCount < 100 - ? pendingRequestsCount - : '99+'} - -
- )} + {link.href === '/requests' && + pendingRequestsCount && + hasPermission(Permission.MANAGE_REQUESTS) && ( +
+ + {pendingRequestsCount < 100 + ? pendingRequestsCount + : '99+'} + +
+ )} ); diff --git a/src/components/Layout/Sidebar/index.tsx b/src/components/Layout/Sidebar/index.tsx index 63596a78..ce85f69a 100644 --- a/src/components/Layout/Sidebar/index.tsx +++ b/src/components/Layout/Sidebar/index.tsx @@ -264,7 +264,8 @@ const Sidebar = ({ )} {sidebarLink.messagesKey === 'requests' && pendingRequestsCount && - pendingRequestsCount > 0 && ( + pendingRequestsCount > 0 && + hasPermission(Permission.MANAGE_REQUESTS) && (
{pendingRequestsCount < 100 @@ -275,7 +276,8 @@ const Sidebar = ({ )} {sidebarLink.messagesKey === 'issues' && openIssuesCount && - openIssuesCount > 0 && ( + openIssuesCount > 0 && + hasPermission(Permission.MANAGE_ISSUES) && (
{openIssuesCount < 100 From b0160828be59084c2dd06a6d64cc14e05517c4df Mon Sep 17 00:00:00 2001 From: Brandon Cohen Date: Fri, 27 Oct 2023 11:47:47 -0400 Subject: [PATCH 3/3] refactor: modified badge design for count --- src/components/Common/Badge/index.tsx | 8 +---- src/components/IssueDetails/index.tsx | 16 ++------- src/components/Layout/MobileMenu/index.tsx | 36 +++++++++---------- src/components/Layout/Sidebar/index.tsx | 34 +++++++++++------- src/components/Layout/index.tsx | 27 +++++++------- src/components/RequestBlock/index.tsx | 16 ++------- src/components/RequestButton/index.tsx | 16 ++------- src/components/RequestCard/index.tsx | 16 ++------- .../RequestList/RequestItem/index.tsx | 18 +++------- .../RequestModal/CollectionRequestModal.tsx | 7 ++-- .../RequestModal/MovieRequestModal.tsx | 9 ++--- .../RequestModal/TvRequestModal.tsx | 8 ++--- src/components/RequestModal/index.tsx | 15 -------- 13 files changed, 77 insertions(+), 149 deletions(-) diff --git a/src/components/Common/Badge/index.tsx b/src/components/Common/Badge/index.tsx index ec13c05d..17eda5b1 100644 --- a/src/components/Common/Badge/index.tsx +++ b/src/components/Common/Badge/index.tsx @@ -9,8 +9,7 @@ interface BadgeProps { | 'warning' | 'success' | 'dark' - | 'light' - | 'gradient'; + | 'light'; className?: string; href?: string; children: React.ReactNode; @@ -67,11 +66,6 @@ const Badge = ( badgeStyle.push('hover:bg-gray-600'); } break; - case 'gradient': - badgeStyle.push( - 'border border-white bg-gradient-to-br from-indigo-600 to-purple-600 !text-indigo-100 shadow-black' - ); - break; default: badgeStyle.push( 'bg-indigo-500 bg-opacity-80 border border-indigo-500 !text-indigo-100' diff --git a/src/components/IssueDetails/index.tsx b/src/components/IssueDetails/index.tsx index 4fbe7145..171f90a8 100644 --- a/src/components/IssueDetails/index.tsx +++ b/src/components/IssueDetails/index.tsx @@ -31,8 +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 useSWRMutation from 'swr/mutation'; +import useSWR, { mutate } from 'swr'; import * as Yup from 'yup'; const messages = defineMessages({ @@ -105,16 +104,6 @@ const IssueDetails = () => { (opt) => opt.issueType === issueData?.issueType ); - const fetchIssuesCount = async () => { - const response = await axios.get('/api/v1/request/count'); - return response.data; - }; - - const { trigger: issueTrigger } = useSWRMutation( - '/api/v1/issue/count', - fetchIssuesCount - ); - if (!data && !error) { return ; } @@ -155,7 +144,7 @@ const IssueDetails = () => { autoDismiss: true, }); revalidateIssue(); - issueTrigger(); + mutate('/api/v1/issue/count'); } catch (e) { addToast(intl.formatMessage(messages.toaststatusupdatefailed), { appearance: 'error', @@ -167,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', diff --git a/src/components/Layout/MobileMenu/index.tsx b/src/components/Layout/MobileMenu/index.tsx index c8b1f30b..e288b826 100644 --- a/src/components/Layout/MobileMenu/index.tsx +++ b/src/components/Layout/MobileMenu/index.tsx @@ -29,8 +29,8 @@ import { cloneElement, useRef, useState } from 'react'; import { useIntl } from 'react-intl'; interface MobileMenuProps { - pendingRequestsCount?: number; - openIssuesCount?: number; + pendingRequestsCount: number; + openIssuesCount: number; } interface MenuLink { @@ -170,24 +170,20 @@ const MobileMenu = ({ })} {link.content} {link.href === '/requests' && - pendingRequestsCount && pendingRequestsCount > 0 && hasPermission(Permission.MANAGE_REQUESTS) && (
- - {pendingRequestsCount < 100 - ? pendingRequestsCount - : '99+'} + + {pendingRequestsCount}
)} {link.href === '/issues' && - openIssuesCount && - openIssuesCount && + openIssuesCount > 0 && hasPermission(Permission.MANAGE_ISSUES) && (
- - {openIssuesCount < 100 ? openIssuesCount : '99+'} + + {openIssuesCount}
)} @@ -217,14 +213,18 @@ const MobileMenu = ({ } )} {link.href === '/requests' && - pendingRequestsCount && + pendingRequestsCount > 0 && hasPermission(Permission.MANAGE_REQUESTS) && ( -
- - {pendingRequestsCount < 100 - ? pendingRequestsCount - : '99+'} - +
+ + {pendingRequestsCount} +
)} diff --git a/src/components/Layout/Sidebar/index.tsx b/src/components/Layout/Sidebar/index.tsx index ce85f69a..9aa2a5ee 100644 --- a/src/components/Layout/Sidebar/index.tsx +++ b/src/components/Layout/Sidebar/index.tsx @@ -31,8 +31,8 @@ export const menuMessages = defineMessages({ interface SidebarProps { open?: boolean; setClosed: () => void; - pendingRequestsCount?: number; - openIssuesCount?: number; + pendingRequestsCount: number; + openIssuesCount: number; } interface SidebarLinkProps { @@ -263,26 +263,36 @@ const Sidebar = ({ menuMessages[sidebarLink.messagesKey] )} {sidebarLink.messagesKey === 'requests' && - pendingRequestsCount && pendingRequestsCount > 0 && hasPermission(Permission.MANAGE_REQUESTS) && (
- - {pendingRequestsCount < 100 - ? pendingRequestsCount - : '99+'} + + {pendingRequestsCount}
)} {sidebarLink.messagesKey === 'issues' && - openIssuesCount && openIssuesCount > 0 && hasPermission(Permission.MANAGE_ISSUES) && (
- - {openIssuesCount < 100 - ? openIssuesCount - : '99+'} + + {openIssuesCount}
)} diff --git a/src/components/Layout/index.tsx b/src/components/Layout/index.tsx index c3a3e224..fd2c8c77 100644 --- a/src/components/Layout/index.tsx +++ b/src/components/Layout/index.tsx @@ -58,22 +58,19 @@ const Layout = ({ children }: LayoutProps) => {
- {requestResponse && issueResponse && ( - setSidebarOpen(false)} - pendingRequestsCount={requestResponse.pending} - openIssuesCount={issueResponse.open} + setSidebarOpen(false)} + pendingRequestsCount={requestResponse?.pending} + openIssuesCount={issueResponse?.open} + /> +
+ - )} - {requestResponse && issueResponse && ( -
- -
- )} +
+
{ const { profile, rootFolder, server, languageProfile } = useRequestOverride(request); - const fetchRequestsCount = async () => { - const response = await axios.get('/api/v1/request/count'); - return response.data; - }; - - const { trigger: requestTrigger } = useSWRMutation( - '/api/v1/request/count', - fetchRequestsCount - ); - const updateRequest = async (type: 'approve' | 'decline'): Promise => { setIsUpdating(true); await axios.post(`/api/v1/request/${request.id}/${type}`); if (onUpdate) { onUpdate(); - requestTrigger(); + mutate('/api/v1/request/count'); } setIsUpdating(false); }; @@ -78,7 +68,7 @@ const RequestBlock = ({ request, onUpdate }: RequestBlockProps) => { if (onUpdate) { onUpdate(); - requestTrigger(); + mutate('/api/v1/request/count'); } setIsUpdating(false); diff --git a/src/components/RequestButton/index.tsx b/src/components/RequestButton/index.tsx index bf52398b..5726df24 100644 --- a/src/components/RequestButton/index.tsx +++ b/src/components/RequestButton/index.tsx @@ -15,7 +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 useSWRMutation from 'swr/mutation'; +import { mutate } from 'swr'; const messages = defineMessages({ viewrequest: 'View Request', @@ -90,16 +90,6 @@ const RequestButton = ({ : undefined; }, [active4kRequests, user]); - const fetchRequestsCount = async () => { - const response = await axios.get('/api/v1/request/count'); - return response.data; - }; - - const { trigger: requestTrigger } = useSWRMutation( - '/api/v1/request/count', - fetchRequestsCount - ); - const modifyRequest = async ( request: MediaRequest, type: 'approve' | 'decline' @@ -108,7 +98,7 @@ const RequestButton = ({ if (response) { onUpdate(); - requestTrigger(); + mutate('/api/v1/request/count'); } }; @@ -127,7 +117,7 @@ const RequestButton = ({ ); onUpdate(); - requestTrigger(); + mutate('/api/v1/request/count'); }; const buttons: ButtonOption[] = []; diff --git a/src/components/RequestCard/index.tsx b/src/components/RequestCard/index.tsx index 7a9368a7..4288af67 100644 --- a/src/components/RequestCard/index.tsx +++ b/src/components/RequestCard/index.tsx @@ -27,7 +27,6 @@ import { useInView } from 'react-intersection-observer'; import { defineMessages, useIntl } from 'react-intl'; import { useToasts } from 'react-toast-notifications'; import useSWR, { mutate } from 'swr'; -import useSWRMutation from 'swr/mutation'; const messages = defineMessages({ seasons: '{seasonCount, plural, one {Season} other {Seasons}}', @@ -76,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 ( @@ -248,29 +248,19 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => { iOSPlexUrl4k: requestData?.media?.iOSPlexUrl4k, }); - const fetchRequestsCount = async () => { - const response = await axios.get('/api/v1/request/count'); - return response.data; - }; - - const { trigger: requestTrigger } = useSWRMutation( - '/api/v1/request/count', - fetchRequestsCount - ); - const modifyRequest = async (type: 'approve' | 'decline') => { const response = await axios.post(`/api/v1/request/${request.id}/${type}`); if (response) { revalidate(); - requestTrigger(); + 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'); - requestTrigger(); + mutate('/api/v1/request/count'); }; const retryRequest = async () => { diff --git a/src/components/RequestList/RequestItem/index.tsx b/src/components/RequestList/RequestItem/index.tsx index 9f3b540f..ee44d877 100644 --- a/src/components/RequestList/RequestItem/index.tsx +++ b/src/components/RequestList/RequestItem/index.tsx @@ -25,8 +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 useSWRMutation from 'swr/mutation'; +import useSWR, { mutate } from 'swr'; const messages = defineMessages({ seasons: '{seasonCount, plural, one {Season} other {Seasons}}', @@ -63,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({ @@ -307,22 +307,12 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => { const [isRetrying, setRetrying] = useState(false); - const fetchRequestsCount = async () => { - const response = await axios.get('/api/v1/request/count'); - return response.data; - }; - - const { trigger: requestTrigger } = useSWRMutation( - '/api/v1/request/count', - fetchRequestsCount - ); - const modifyRequest = async (type: 'approve' | 'decline') => { const response = await axios.post(`/api/v1/request/${request.id}/${type}`); if (response) { revalidate(); - requestTrigger(); + mutate('/api/v1/request/count'); } }; @@ -330,7 +320,7 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => { await axios.delete(`/api/v1/request/${request.id}`); revalidateList(); - requestTrigger(); + mutate('/api/v1/request/count'); }; const retryRequest = async () => { diff --git a/src/components/RequestModal/CollectionRequestModal.tsx b/src/components/RequestModal/CollectionRequestModal.tsx index e2c30100..26dc410d 100644 --- a/src/components/RequestModal/CollectionRequestModal.tsx +++ b/src/components/RequestModal/CollectionRequestModal.tsx @@ -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.', @@ -36,7 +36,6 @@ interface RequestModalProps extends React.HTMLAttributes { onCancel?: () => void; onComplete?: (newStatus: MediaStatus) => void; onUpdating?: (isUpdating: boolean) => void; - requestTrigger: () => void; } const CollectionRequestModal = ({ @@ -45,7 +44,6 @@ const CollectionRequestModal = ({ tmdbId, onUpdating, is4k = false, - requestTrigger, }: RequestModalProps) => { const [isUpdating, setIsUpdating] = useState(false); const [requestOverrides, setRequestOverrides] = @@ -213,7 +211,7 @@ const CollectionRequestModal = ({ ? MediaStatus.UNKNOWN : MediaStatus.PARTIALLY_AVAILABLE ); - requestTrigger(); + mutate('/api/v1/request/count'); } addToast( @@ -242,7 +240,6 @@ const CollectionRequestModal = ({ intl, selectedParts, is4k, - requestTrigger, ]); const hasAutoApprove = hasPermission( diff --git a/src/components/RequestModal/MovieRequestModal.tsx b/src/components/RequestModal/MovieRequestModal.tsx index 770bcc6a..cd196576 100644 --- a/src/components/RequestModal/MovieRequestModal.tsx +++ b/src/components/RequestModal/MovieRequestModal.tsx @@ -42,7 +42,6 @@ interface RequestModalProps extends React.HTMLAttributes { onCancel?: () => void; onComplete?: (newStatus: MediaStatus) => void; onUpdating?: (isUpdating: boolean) => void; - requestTrigger: () => void; } const MovieRequestModal = ({ @@ -52,7 +51,6 @@ const MovieRequestModal = ({ onUpdating, editRequest, is4k = false, - requestTrigger, }: RequestModalProps) => { const [isUpdating, setIsUpdating] = useState(false); const [requestOverrides, setRequestOverrides] = @@ -97,7 +95,7 @@ const MovieRequestModal = ({ ...overrideParams, }); mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0'); - requestTrigger(); + mutate('/api/v1/request/count'); if (response.data) { if (onComplete) { @@ -137,7 +135,6 @@ const MovieRequestModal = ({ data?.id, data?.title, is4k, - requestTrigger, onComplete, addToast, intl, @@ -152,7 +149,7 @@ const MovieRequestModal = ({ `/api/v1/request/${editRequest?.id}` ); mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0'); - requestTrigger(); + mutate('/api/v1/request/count'); if (response.status === 204) { if (onComplete) { @@ -190,7 +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'); - requestTrigger(); + mutate('/api/v1/request/count'); addToast( diff --git a/src/components/RequestModal/TvRequestModal.tsx b/src/components/RequestModal/TvRequestModal.tsx index 3e520369..166288c7 100644 --- a/src/components/RequestModal/TvRequestModal.tsx +++ b/src/components/RequestModal/TvRequestModal.tsx @@ -56,7 +56,6 @@ interface RequestModalProps extends React.HTMLAttributes { onCancel?: () => void; onComplete?: (newStatus: MediaStatus) => void; onUpdating?: (isUpdating: boolean) => void; - requestTrigger: () => void; is4k?: boolean; editRequest?: MediaRequest; } @@ -68,7 +67,6 @@ const TvRequestModal = ({ onUpdating, editRequest, is4k = false, - requestTrigger, }: RequestModalProps) => { const settings = useSettings(); const { addToast } = useToasts(); @@ -108,7 +106,7 @@ const TvRequestModal = ({ if (onUpdating) { onUpdating(true); - requestTrigger(); + mutate('/api/v1/request/count'); } try { @@ -131,7 +129,7 @@ const TvRequestModal = ({ await axios.delete(`/api/v1/request/${editRequest.id}`); } mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0'); - requestTrigger(); + mutate('/api/v1/request/count'); addToast( @@ -180,7 +178,7 @@ const TvRequestModal = ({ if (onUpdating) { onUpdating(true); - requestTrigger(); + mutate('/api/v1/request/count'); } try { diff --git a/src/components/RequestModal/index.tsx b/src/components/RequestModal/index.tsx index 828aa443..9ef6b405 100644 --- a/src/components/RequestModal/index.tsx +++ b/src/components/RequestModal/index.tsx @@ -4,8 +4,6 @@ import TvRequestModal from '@app/components/RequestModal/TvRequestModal'; import { Transition } from '@headlessui/react'; import type { MediaStatus } from '@server/constants/media'; import type { MediaRequest } from '@server/entity/MediaRequest'; -import axios from 'axios'; -import useSWRMutation from 'swr/mutation'; interface RequestModalProps { show: boolean; @@ -28,16 +26,6 @@ const RequestModal = ({ onUpdating, onCancel, }: RequestModalProps) => { - const fetchRequestsCount = async () => { - const response = await axios.get('/api/v1/request/count'); - return response.data; - }; - - const { trigger: requestTrigger } = useSWRMutation( - '/api/v1/request/count', - fetchRequestsCount - ); - return ( ) : type === 'tv' ? ( ) : ( )}