From b0dfb9fe80c99db453569703f2edc4eec0f902a2 Mon Sep 17 00:00:00 2001 From: John Fletcher Date: Thu, 26 Feb 2026 22:24:01 -0700 Subject: [PATCH 1/2] fix: replace manage pushes; close sidebar without back reopening bug Fix media manage sidebar history handling so closing it does not create extra browser history entries that reopen the panel on Back. Use router.replace when dismissing the movie/TV manage slide-over, add replace support to shared badge links, and have status-badge manage links use replace only when already on the same media details page. Add a Cypress regression test that verifies opening ?manage=1, closing the panel, and pressing Back keeps the panel closed instead of reopening it. --- cypress/e2e/movie-details.cy.ts | 16 ++++++++++++++++ src/components/Common/Badge/index.tsx | 10 +++++++++- src/components/MovieDetails/index.tsx | 2 +- src/components/StatusBadge/index.tsx | 17 +++++++++++++++-- src/components/TvDetails/index.tsx | 2 +- 5 files changed, 42 insertions(+), 5 deletions(-) diff --git a/cypress/e2e/movie-details.cy.ts b/cypress/e2e/movie-details.cy.ts index 1d3ecf3f13..0c2eeea56d 100644 --- a/cypress/e2e/movie-details.cy.ts +++ b/cypress/e2e/movie-details.cy.ts @@ -9,4 +9,20 @@ describe('Movie Details', () => { 'Minions: The Rise of Gru (2022)' ); }); + + it('does not reopen the manager panel after closing and going back', () => { + cy.loginAsAdmin(); + + cy.visit('/movie/438148'); + cy.visit('/movie/438148?manage=1'); + + cy.get('button[aria-label="Close panel"]').should('be.visible').click(); + cy.location('search').should('eq', ''); + cy.get('button[aria-label="Close panel"]').should('not.exist'); + + cy.go('back'); + + cy.location('search').should('eq', ''); + cy.get('button[aria-label="Close panel"]').should('not.exist'); + }); }); diff --git a/src/components/Common/Badge/index.tsx b/src/components/Common/Badge/index.tsx index 5ee23bdfd0..33ac9e6a5a 100644 --- a/src/components/Common/Badge/index.tsx +++ b/src/components/Common/Badge/index.tsx @@ -12,11 +12,18 @@ interface BadgeProps { | 'light'; className?: string; href?: string; + replace?: boolean; children: React.ReactNode; } const Badge = ( - { badgeType = 'default', className, href, children }: BadgeProps, + { + badgeType = 'default', + className, + href, + replace = false, + children, + }: BadgeProps, ref?: React.Ref ) => { const badgeStyle = [ @@ -93,6 +100,7 @@ const Badge = ( return ( } > diff --git a/src/components/MovieDetails/index.tsx b/src/components/MovieDetails/index.tsx index 56a3c0a1f7..d7d1ba8548 100644 --- a/src/components/MovieDetails/index.tsx +++ b/src/components/MovieDetails/index.tsx @@ -469,7 +469,7 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => { mediaType="movie" onClose={() => { setShowManager(false); - router.push({ + router.replace({ pathname: router.pathname, query: { movieId: router.query.movieId }, }); diff --git a/src/components/StatusBadge/index.tsx b/src/components/StatusBadge/index.tsx index faea1fb5c2..2dc3b1d149 100644 --- a/src/components/StatusBadge/index.tsx +++ b/src/components/StatusBadge/index.tsx @@ -9,6 +9,7 @@ import defineMessages from '@app/utils/defineMessages'; import { MediaStatus } from '@server/constants/media'; import { MediaServerType } from '@server/constants/server'; import type { DownloadingItem } from '@server/lib/downloadtracker'; +import { useRouter } from 'next/router'; import { useIntl } from 'react-intl'; const messages = defineMessages('components.StatusBadge', { @@ -47,11 +48,13 @@ const StatusBadge = ({ statusLabelOverride, }: StatusBadgeProps) => { const intl = useIntl(); + const router = useRouter(); const { hasPermission } = useUser(); const settings = useSettings(); let mediaLink: string | undefined; let mediaLinkDescription: string | undefined; + let mediaLinkReplace = false; const calculateDownloadProgress = (media: DownloadingItem) => { return Math.round(((media?.size - media?.sizeLeft) / media?.size) * 100); @@ -95,6 +98,8 @@ const StatusBadge = ({ } else if (hasPermission(Permission.MANAGE_REQUESTS)) { if (mediaType && tmdbId) { mediaLink = `/${mediaType}/${tmdbId}?manage=1`; + mediaLinkReplace = + router.asPath.split('?')[0] === `/${mediaType}/${tmdbId}`; mediaLinkDescription = intl.formatMessage(messages.managemedia, { mediaType: intl.formatMessage( mediaType === 'movie' ? globalMessages.movie : globalMessages.tvshow @@ -169,6 +174,7 @@ const StatusBadge = ({ - + {intl.formatMessage(is4k ? messages.status4k : messages.status, { status: intl.formatMessage(globalMessages.pending), })} @@ -364,7 +376,7 @@ const StatusBadge = ({ case MediaStatus.BLOCKLISTED: return ( - + {intl.formatMessage(is4k ? messages.status4k : messages.status, { status: statusLabelOverride ?? @@ -388,6 +400,7 @@ const StatusBadge = ({ { mediaType="tv" onClose={() => { setShowManager(false); - router.push({ + router.replace({ pathname: router.pathname, query: { tvId: router.query.tvId }, }); From cd45ccad1190e5cad957d07175a42260473c3d70 Mon Sep 17 00:00:00 2001 From: gauthier-th Date: Wed, 20 May 2026 11:56:07 +0200 Subject: [PATCH 2/2] refactor: replace the badge attribute by a useEffeect --- src/components/Common/Badge/index.tsx | 10 +--------- src/components/MovieDetails/index.tsx | 14 +++++++++----- src/components/StatusBadge/index.tsx | 17 ++--------------- src/components/TvDetails/index.tsx | 12 +++++++++--- 4 files changed, 21 insertions(+), 32 deletions(-) diff --git a/src/components/Common/Badge/index.tsx b/src/components/Common/Badge/index.tsx index 33ac9e6a5a..5ee23bdfd0 100644 --- a/src/components/Common/Badge/index.tsx +++ b/src/components/Common/Badge/index.tsx @@ -12,18 +12,11 @@ interface BadgeProps { | 'light'; className?: string; href?: string; - replace?: boolean; children: React.ReactNode; } const Badge = ( - { - badgeType = 'default', - className, - href, - replace = false, - children, - }: BadgeProps, + { badgeType = 'default', className, href, children }: BadgeProps, ref?: React.Ref ) => { const badgeStyle = [ @@ -100,7 +93,6 @@ const Badge = ( return ( } > diff --git a/src/components/MovieDetails/index.tsx b/src/components/MovieDetails/index.tsx index d7d1ba8548..80aded586e 100644 --- a/src/components/MovieDetails/index.tsx +++ b/src/components/MovieDetails/index.tsx @@ -118,9 +118,7 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => { const router = useRouter(); const intl = useIntl(); const { locale } = useLocale(); - const [showManager, setShowManager] = useState( - router.query.manage == '1' ? true : false - ); + const [showManager, setShowManager] = useState(false); const minStudios = 3; const [showMoreStudios, setShowMoreStudios] = useState(false); const [showIssueModal, setShowIssueModal] = useState(false); @@ -158,8 +156,14 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => { ); useEffect(() => { - setShowManager(router.query.manage == '1' ? true : false); - }, [router.query.manage]); + if (router.query.manage === '1') { + setShowManager(true); + router.replace({ + pathname: router.pathname, + query: { movieId: router.query.movieId }, + }); + } + }, [router, router.query.manage]); const closeBlocklistModal = useCallback( () => setShowBlocklistModal(false), diff --git a/src/components/StatusBadge/index.tsx b/src/components/StatusBadge/index.tsx index 2dc3b1d149..faea1fb5c2 100644 --- a/src/components/StatusBadge/index.tsx +++ b/src/components/StatusBadge/index.tsx @@ -9,7 +9,6 @@ import defineMessages from '@app/utils/defineMessages'; import { MediaStatus } from '@server/constants/media'; import { MediaServerType } from '@server/constants/server'; import type { DownloadingItem } from '@server/lib/downloadtracker'; -import { useRouter } from 'next/router'; import { useIntl } from 'react-intl'; const messages = defineMessages('components.StatusBadge', { @@ -48,13 +47,11 @@ const StatusBadge = ({ statusLabelOverride, }: StatusBadgeProps) => { const intl = useIntl(); - const router = useRouter(); const { hasPermission } = useUser(); const settings = useSettings(); let mediaLink: string | undefined; let mediaLinkDescription: string | undefined; - let mediaLinkReplace = false; const calculateDownloadProgress = (media: DownloadingItem) => { return Math.round(((media?.size - media?.sizeLeft) / media?.size) * 100); @@ -98,8 +95,6 @@ const StatusBadge = ({ } else if (hasPermission(Permission.MANAGE_REQUESTS)) { if (mediaType && tmdbId) { mediaLink = `/${mediaType}/${tmdbId}?manage=1`; - mediaLinkReplace = - router.asPath.split('?')[0] === `/${mediaType}/${tmdbId}`; mediaLinkDescription = intl.formatMessage(messages.managemedia, { mediaType: intl.formatMessage( mediaType === 'movie' ? globalMessages.movie : globalMessages.tvshow @@ -174,7 +169,6 @@ const StatusBadge = ({ - + {intl.formatMessage(is4k ? messages.status4k : messages.status, { status: intl.formatMessage(globalMessages.pending), })} @@ -376,7 +364,7 @@ const StatusBadge = ({ case MediaStatus.BLOCKLISTED: return ( - + {intl.formatMessage(is4k ? messages.status4k : messages.status, { status: statusLabelOverride ?? @@ -400,7 +388,6 @@ const StatusBadge = ({ { const intl = useIntl(); const { locale } = useLocale(); const [showRequestModal, setShowRequestModal] = useState(false); - const [showManager, setShowManager] = useState(router.query.manage == '1'); + const [showManager, setShowManager] = useState(false); const [showIssueModal, setShowIssueModal] = useState(false); const [isUpdating, setIsUpdating] = useState(false); const [toggleWatchlist, setToggleWatchlist] = useState( @@ -154,8 +154,14 @@ const TvDetails = ({ tv }: TvDetailsProps) => { ); useEffect(() => { - setShowManager(router.query.manage == '1'); - }, [router.query.manage]); + if (router.query.manage === '1') { + setShowManager(true); + router.replace({ + pathname: router.pathname, + query: { tvId: router.query.tvId }, + }); + } + }, [router, router.query.manage]); const closeBlocklistModal = useCallback( () => setShowBlocklistModal(false),