fix(react links): changed every <Link> instance containing a <a> to only a <Link>

Ported Links to work for the new React version installed
pull/3800/head
Anatole Sot 2 months ago
parent 468bd25e59
commit d750d736fa

@ -166,10 +166,9 @@ const CollectionDetails = ({ collection }: CollectionDetailsProps) => {
<Link <Link
href={`/discover/movies/genre/${genreId}`} href={`/discover/movies/genre/${genreId}`}
key={`genre-${genreId}`} key={`genre-${genreId}`}
className="hover:underline"
> >
<a className="hover:underline"> {genres.find((g) => g.id === genreId)?.name}
{genres.find((g) => g.id === genreId)?.name}
</a>
</Link> </Link>
)) ))
.reduce((prev, curr) => ( .reduce((prev, curr) => (

@ -93,13 +93,12 @@ const Badge = (
); );
} else if (href) { } else if (href) {
return ( return (
<Link href={href}> <Link
<a href={href}
className={badgeStyle.join(' ')} className={badgeStyle.join(' ')}
ref={ref as React.Ref<HTMLAnchorElement>} ref={ref as React.Ref<HTMLAnchorElement>}
> >
{children} {children}
</a>
</Link> </Link>
); );
} else { } else {

@ -55,15 +55,14 @@ const SettingsLink = ({
} }
return ( return (
<Link href={route}> <Link
<a href={route}
className={`${linkClasses} ${ className={`${linkClasses} ${
currentPath.match(regex) ? activeLinkColor : inactiveLinkColor currentPath.match(regex) ? activeLinkColor : inactiveLinkColor
}`} }`}
aria-current="page" aria-current="page"
> >
{children} {children}
</a>
</Link> </Link>
); );
}; };

@ -12,40 +12,39 @@ const CompanyCard = ({ image, url, name }: CompanyCardProps) => {
const [isHovered, setHovered] = useState(false); const [isHovered, setHovered] = useState(false);
return ( return (
<Link href={url}> <Link
<a href={url}
className={`relative flex h-32 w-56 transform-gpu cursor-pointer items-center justify-center p-8 shadow ring-1 transition duration-300 ease-in-out sm:h-36 sm:w-72 ${ className={`relative flex h-32 w-56 transform-gpu cursor-pointer items-center justify-center p-8 shadow ring-1 transition duration-300 ease-in-out sm:h-36 sm:w-72 ${
isHovered isHovered
? 'scale-105 bg-gray-700 ring-gray-500' ? 'scale-105 bg-gray-700 ring-gray-500'
: 'scale-100 bg-gray-800 ring-gray-700' : 'scale-100 bg-gray-800 ring-gray-700'
} rounded-xl`} } rounded-xl`}
onMouseEnter={() => { onMouseEnter={() => {
setHovered(true);
}}
onMouseLeave={() => setHovered(false)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
setHovered(true); setHovered(true);
}} }
onMouseLeave={() => setHovered(false)} }}
onKeyDown={(e) => { role="link"
if (e.key === 'Enter') { tabIndex={0}
setHovered(true); >
} <div className="relative h-full w-full">
}} <CachedImage
role="link" src={image}
tabIndex={0} alt={name}
> className="relative z-40 h-full w-full"
<div className="relative h-full w-full"> layout="fill"
<CachedImage objectFit="contain"
src={image}
alt={name}
className="relative z-40 h-full w-full"
layout="fill"
objectFit="contain"
/>
</div>
<div
className={`absolute bottom-0 left-0 right-0 z-0 h-12 rounded-b-xl bg-gradient-to-t ${
isHovered ? 'from-gray-800' : 'from-gray-900'
}`}
/> />
</a> </div>
<div
className={`absolute bottom-0 left-0 right-0 z-0 h-12 rounded-b-xl bg-gradient-to-t ${
isHovered ? 'from-gray-800' : 'from-gray-900'
}`}
/>
</Link> </Link>
); );
}; };

@ -57,8 +57,8 @@ const DiscoverWatchlist = () => {
<Header <Header
subtext={ subtext={
router.query.userId ? ( router.query.userId ? (
<Link href={`/users/${user?.id}`}> <Link href={`/users/${user?.id}`} className="hover:underline">
<a className="hover:underline">{user?.displayName}</a> {user?.displayName}
</Link> </Link>
) : ( ) : (
'' ''

@ -25,11 +25,9 @@ const MovieGenreSlider = () => {
return ( return (
<> <>
<div className="slider-header"> <div className="slider-header">
<Link href="/discover/movies/genres"> <Link href="/discover/movies/genres" className="slider-title">
<a className="slider-title"> <span>{intl.formatMessage(messages.moviegenres)}</span>
<span>{intl.formatMessage(messages.moviegenres)}</span> <ArrowRightCircleIcon />
<ArrowRightCircleIcon />
</a>
</Link> </Link>
</div> </div>
<Slider <Slider

@ -40,11 +40,9 @@ const PlexWatchlistSlider = () => {
return ( return (
<> <>
<div className="slider-header"> <div className="slider-header">
<Link href="/discover/watchlist"> <Link href="/discover/watchlist" className="slider-title">
<a className="slider-title"> <span>{intl.formatMessage(messages.plexwatchlist)}</span>
<span>{intl.formatMessage(messages.plexwatchlist)}</span> <ArrowRightCircleIcon />
<ArrowRightCircleIcon />
</a>
</Link> </Link>
</div> </div>
<Slider <Slider

@ -24,11 +24,9 @@ const RecentRequestsSlider = () => {
return ( return (
<> <>
<div className="slider-header"> <div className="slider-header">
<Link href="/requests?filter=all"> <Link href="/requests?filter=all" className="slider-title">
<a className="slider-title"> <span>{intl.formatMessage(sliderTitles.recentrequests)}</span>
<span>{intl.formatMessage(sliderTitles.recentrequests)}</span> <ArrowRightCircleIcon />
<ArrowRightCircleIcon />
</a>
</Link> </Link>
</div> </div>
<Slider <Slider

@ -25,11 +25,9 @@ const TvGenreSlider = () => {
return ( return (
<> <>
<div className="slider-header"> <div className="slider-header">
<Link href="/discover/tv/genres"> <Link href="/discover/tv/genres" className="slider-title">
<a className="slider-title"> <span>{intl.formatMessage(messages.tvgenres)}</span>
<span>{intl.formatMessage(messages.tvgenres)}</span> <ArrowRightCircleIcon />
<ArrowRightCircleIcon />
</a>
</Link> </Link>
</div> </div>
<Slider <Slider

@ -14,37 +14,36 @@ const GenreCard = ({ image, url, name, canExpand = false }: GenreCardProps) => {
const [isHovered, setHovered] = useState(false); const [isHovered, setHovered] = useState(false);
return ( return (
<Link href={url}> <Link
<a href={url}
className={`relative flex h-32 items-center justify-center sm:h-36 ${ className={`relative flex h-32 items-center justify-center sm:h-36 ${
canExpand ? 'w-full' : 'w-56 sm:w-72' canExpand ? 'w-full' : 'w-56 sm:w-72'
} transform-gpu cursor-pointer p-8 shadow ring-1 transition duration-300 ease-in-out ${ } transform-gpu cursor-pointer p-8 shadow ring-1 transition duration-300 ease-in-out ${
isHovered isHovered
? 'scale-105 bg-gray-700 bg-opacity-100 ring-gray-500' ? 'scale-105 bg-gray-700 bg-opacity-100 ring-gray-500'
: 'scale-100 bg-gray-800 bg-opacity-80 ring-gray-700' : 'scale-100 bg-gray-800 bg-opacity-80 ring-gray-700'
} overflow-hidden rounded-xl bg-cover bg-center`} } overflow-hidden rounded-xl bg-cover bg-center`}
onMouseEnter={() => { onMouseEnter={() => {
setHovered(true);
}}
onMouseLeave={() => setHovered(false)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
setHovered(true); setHovered(true);
}} }
onMouseLeave={() => setHovered(false)} }}
onKeyDown={(e) => { role="link"
if (e.key === 'Enter') { tabIndex={0}
setHovered(true); >
} <CachedImage src={image} alt="" layout="fill" objectFit="cover" />
}} <div
role="link" className={`absolute inset-0 z-10 h-full w-full bg-gray-800 transition duration-300 ${
tabIndex={0} isHovered ? 'bg-opacity-10' : 'bg-opacity-30'
> }`}
<CachedImage src={image} alt="" layout="fill" objectFit="cover" /> />
<div <div className="relative z-20 w-full truncate whitespace-normal text-center text-2xl font-bold text-white sm:text-3xl">
className={`absolute inset-0 z-10 h-full w-full bg-gray-800 transition duration-300 ${ {name}
isHovered ? 'bg-opacity-10' : 'bg-opacity-30' </div>
}`}
/>
<div className="relative z-20 w-full truncate whitespace-normal text-center text-2xl font-bold text-white sm:text-3xl">
{name}
</div>
</a>
</Link> </Link>
); );
}; };

@ -45,10 +45,9 @@ const IssueBlock = ({ issue }: IssueBlockProps) => {
? '/profile' ? '/profile'
: `/users/${issue.createdBy.id}` : `/users/${issue.createdBy.id}`
} }
className="font-semibold text-gray-100 transition duration-300 hover:text-white hover:underline"
> >
<a className="font-semibold text-gray-100 transition duration-300 hover:text-white hover:underline"> {issue.createdBy.displayName}
{issue.createdBy.displayName}
</a>
</Link> </Link>
</span> </span>
</div> </div>

@ -84,13 +84,11 @@ const IssueComment = ({
</Modal> </Modal>
</Transition> </Transition>
<Link href={isActiveUser ? '/profile' : `/users/${comment.user.id}`}> <Link href={isActiveUser ? '/profile' : `/users/${comment.user.id}`}>
<a> <img
<img src={comment.user.avatar}
src={comment.user.avatar} alt=""
alt="" className="h-10 w-10 scale-100 transform-gpu rounded-full object-cover ring-1 ring-gray-500 transition duration-300 hover:scale-105"
className="h-10 w-10 scale-100 transform-gpu rounded-full object-cover ring-1 ring-gray-500 transition duration-300 hover:scale-105" />
/>
</a>
</Link> </Link>
<div className="relative flex-1"> <div className="relative flex-1">
<div className="w-full rounded-md shadow ring-1 ring-gray-500"> <div className="w-full rounded-md shadow ring-1 ring-gray-500">
@ -242,10 +240,9 @@ const IssueComment = ({
href={ href={
isActiveUser ? '/profile' : `/users/${comment.user.id}` isActiveUser ? '/profile' : `/users/${comment.user.id}`
} }
className="font-semibold text-gray-100 transition duration-300 hover:text-white hover:underline"
> >
<a className="font-semibold text-gray-100 transition duration-300 hover:text-white hover:underline"> {comment.user.displayName}
{comment.user.displayName}
</a>
</Link> </Link>
), ),
relativeTime: ( relativeTime: (

@ -251,8 +251,9 @@ const IssueDetails = () => {
href={`/${ href={`/${
issueData.media.mediaType === MediaType.MOVIE ? 'movie' : 'tv' issueData.media.mediaType === MediaType.MOVIE ? 'movie' : 'tv'
}/${data.id}`} }/${data.id}`}
className="hover:underline"
> >
<a className="hover:underline">{title}</a> {title}
</Link>{' '} </Link>{' '}
{releaseYear && ( {releaseYear && (
<span className="media-year">({releaseYear.slice(0, 4)})</span> <span className="media-year">({releaseYear.slice(0, 4)})</span>
@ -268,17 +269,16 @@ const IssueDetails = () => {
? '/profile' ? '/profile'
: `/users/${issueData.createdBy.id}` : `/users/${issueData.createdBy.id}`
} }
className="group ml-1 inline-flex h-full items-center xl:ml-1.5"
> >
<a className="group ml-1 inline-flex h-full items-center xl:ml-1.5"> <img
<img className="mr-0.5 h-5 w-5 scale-100 transform-gpu rounded-full object-cover transition duration-300 group-hover:scale-105 xl:mr-1 xl:h-6 xl:w-6"
className="mr-0.5 h-5 w-5 scale-100 transform-gpu rounded-full object-cover transition duration-300 group-hover:scale-105 xl:mr-1 xl:h-6 xl:w-6" src={issueData.createdBy.avatar}
src={issueData.createdBy.avatar} alt=""
alt="" />
/> <span className="font-semibold text-gray-100 transition duration-300 group-hover:text-white group-hover:underline">
<span className="font-semibold text-gray-100 transition duration-300 group-hover:text-white group-hover:underline"> {issueData.createdBy.displayName}
{issueData.createdBy.displayName} </span>
</span>
</a>
</Link> </Link>
), ),
relativeTime: ( relativeTime: (

@ -133,21 +133,20 @@ const IssueItem = ({ issue }: IssueItemProps) => {
? `/movie/${issue.media.tmdbId}` ? `/movie/${issue.media.tmdbId}`
: `/tv/${issue.media.tmdbId}` : `/tv/${issue.media.tmdbId}`
} }
className="relative h-auto w-12 flex-shrink-0 scale-100 transform-gpu overflow-hidden rounded-md transition duration-300 hover:scale-105"
> >
<a className="relative h-auto w-12 flex-shrink-0 scale-100 transform-gpu overflow-hidden rounded-md transition duration-300 hover:scale-105"> <CachedImage
<CachedImage src={
src={ title.posterPath
title.posterPath ? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${title.posterPath}`
? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${title.posterPath}` : '/images/overseerr_poster_not_found.png'
: '/images/overseerr_poster_not_found.png' }
} alt=""
alt="" layout="responsive"
layout="responsive" width={600}
width={600} height={900}
height={900} objectFit="cover"
objectFit="cover" />
/>
</a>
</Link> </Link>
<div className="flex flex-col justify-center overflow-hidden pl-2 xl:pl-4"> <div className="flex flex-col justify-center overflow-hidden pl-2 xl:pl-4">
<div className="pt-0.5 text-xs text-white sm:pt-1"> <div className="pt-0.5 text-xs text-white sm:pt-1">
@ -162,10 +161,9 @@ const IssueItem = ({ issue }: IssueItemProps) => {
? `/movie/${issue.media.tmdbId}` ? `/movie/${issue.media.tmdbId}`
: `/tv/${issue.media.tmdbId}` : `/tv/${issue.media.tmdbId}`
} }
className="mr-2 min-w-0 truncate text-lg font-bold text-white hover:underline xl:text-xl"
> >
<a className="mr-2 min-w-0 truncate text-lg font-bold text-white hover:underline xl:text-xl"> {isMovie(title) ? title.title : title.name}
{isMovie(title) ? title.title : title.name}
</a>
</Link> </Link>
{problemSeasonEpisodeLine.length > 0 && ( {problemSeasonEpisodeLine.length > 0 && (
<div className="card-field"> <div className="card-field">
@ -222,17 +220,18 @@ const IssueItem = ({ issue }: IssueItemProps) => {
/> />
), ),
user: ( user: (
<Link href={`/users/${issue.createdBy.id}`}> <Link
<a className="group flex items-center truncate"> href={`/users/${issue.createdBy.id}`}
<img className="group flex items-center truncate"
src={issue.createdBy.avatar} >
alt="" <img
className="avatar-sm ml-1.5 object-cover" src={issue.createdBy.avatar}
/> alt=""
<span className="truncate text-sm font-semibold group-hover:text-white group-hover:underline"> className="avatar-sm ml-1.5 object-cover"
{issue.createdBy.displayName} />
</span> <span className="truncate text-sm font-semibold group-hover:text-white group-hover:underline">
</a> {issue.createdBy.displayName}
</span>
</Link> </Link>
), ),
})} })}

@ -151,25 +151,25 @@ const MobileMenu = () => {
{filteredLinks.map((link) => { {filteredLinks.map((link) => {
const isActive = router.pathname.match(link.activeRegExp); const isActive = router.pathname.match(link.activeRegExp);
return ( return (
<Link key={`mobile-menu-link-${link.href}`} href={link.href}> <Link
<a key={`mobile-menu-link-${link.href}`}
className={`flex items-center space-x-2 ${ href={link.href}
isActive ? 'text-indigo-500' : '' className={`flex items-center space-x-2 ${
}`} isActive ? 'text-indigo-500' : ''
onKeyDown={(e) => { }`}
if (e.key === 'Enter') { onKeyDown={(e) => {
setIsOpen(false); if (e.key === 'Enter') {
} setIsOpen(false);
}} }
onClick={() => setIsOpen(false)} }}
role="button" onClick={() => setIsOpen(false)}
tabIndex={0} role="button"
> tabIndex={0}
{cloneElement(isActive ? link.svgIconSelected : link.svgIcon, { >
className: 'h-5 w-5', {cloneElement(isActive ? link.svgIconSelected : link.svgIcon, {
})} className: 'h-5 w-5',
<span>{link.content}</span> })}
</a> <span>{link.content}</span>
</Link> </Link>
); );
})} })}
@ -182,19 +182,19 @@ const MobileMenu = () => {
const isActive = const isActive =
router.pathname.match(link.activeRegExp) && !isOpen; router.pathname.match(link.activeRegExp) && !isOpen;
return ( return (
<Link key={`mobile-menu-link-${link.href}`} href={link.href}> <Link
<a key={`mobile-menu-link-${link.href}`}
className={`flex flex-col items-center space-y-1 ${ href={link.href}
isActive ? 'text-indigo-500' : '' className={`flex flex-col items-center space-y-1 ${
}`} isActive ? 'text-indigo-500' : ''
> }`}
{cloneElement( >
isActive ? link.svgIconSelected : link.svgIcon, {cloneElement(
{ isActive ? link.svgIconSelected : link.svgIcon,
className: 'h-6 w-6', {
} className: 'h-6 w-6',
)} }
</a> )}
</Link> </Link>
); );
})} })}

@ -176,32 +176,27 @@ const Sidebar = ({ open, setClosed }: SidebarProps) => {
key={`mobile-${sidebarLink.messagesKey}`} key={`mobile-${sidebarLink.messagesKey}`}
href={sidebarLink.href} href={sidebarLink.href}
as={sidebarLink.as} as={sidebarLink.as}
onClick={() => setClosed()}
onKeyDown={(e) => {
if (e.key === 'Enter') {
setClosed();
}
}}
role="button"
tabIndex={0}
className={`flex items-center rounded-md px-2 py-2 text-base font-medium leading-6 text-white transition duration-150 ease-in-out focus:outline-none
${
router.pathname.match(sidebarLink.activeRegExp)
? 'bg-gradient-to-br from-indigo-600 to-purple-600 hover:from-indigo-500 hover:to-purple-500'
: 'hover:bg-gray-700 focus:bg-gray-700'
}
`}
data-testid={`${sidebarLink.dataTestId}-mobile`}
> >
<a {sidebarLink.svgIcon}
onClick={() => setClosed()} {intl.formatMessage(
onKeyDown={(e) => { menuMessages[sidebarLink.messagesKey]
if (e.key === 'Enter') { )}
setClosed();
}
}}
role="button"
tabIndex={0}
className={`flex items-center rounded-md px-2 py-2 text-base font-medium leading-6 text-white transition duration-150 ease-in-out focus:outline-none
${
router.pathname.match(
sidebarLink.activeRegExp
)
? 'bg-gradient-to-br from-indigo-600 to-purple-600 hover:from-indigo-500 hover:to-purple-500'
: 'hover:bg-gray-700 focus:bg-gray-700'
}
`}
data-testid={`${sidebarLink.dataTestId}-mobile`}
>
{sidebarLink.svgIcon}
{intl.formatMessage(
menuMessages[sidebarLink.messagesKey]
)}
</a>
</Link> </Link>
); );
})} })}
@ -246,24 +241,19 @@ const Sidebar = ({ open, setClosed }: SidebarProps) => {
key={`desktop-${sidebarLink.messagesKey}`} key={`desktop-${sidebarLink.messagesKey}`}
href={sidebarLink.href} href={sidebarLink.href}
as={sidebarLink.as} as={sidebarLink.as}
className={`group flex items-center rounded-md px-2 py-2 text-lg font-medium leading-6 text-white transition duration-150 ease-in-out focus:outline-none
${
router.pathname.match(sidebarLink.activeRegExp)
? 'bg-gradient-to-br from-indigo-600 to-purple-600 hover:from-indigo-500 hover:to-purple-500'
: 'hover:bg-gray-700 focus:bg-gray-700'
}
`}
data-testid={sidebarLink.dataTestId}
> >
<a {sidebarLink.svgIcon}
className={`group flex items-center rounded-md px-2 py-2 text-lg font-medium leading-6 text-white transition duration-150 ease-in-out focus:outline-none {intl.formatMessage(
${ menuMessages[sidebarLink.messagesKey]
router.pathname.match( )}
sidebarLink.activeRegExp
)
? 'bg-gradient-to-br from-indigo-600 to-purple-600 hover:from-indigo-500 hover:to-purple-500'
: 'hover:bg-gray-700 focus:bg-gray-700'
}
`}
data-testid={sidebarLink.dataTestId}
>
{sidebarLink.svgIcon}
{intl.formatMessage(
menuMessages[sidebarLink.messagesKey]
)}
</a>
</Link> </Link>
); );
})} })}

@ -24,10 +24,8 @@ const ForwardedLink = forwardRef<
LinkProps & React.ComponentPropsWithoutRef<'a'> LinkProps & React.ComponentPropsWithoutRef<'a'>
>(({ href, children, ...rest }, ref) => { >(({ href, children, ...rest }, ref) => {
return ( return (
<Link href={href}> <Link href={href} ref={ref} {...rest}>
<a ref={ref} {...rest}> {children}
{children}
</a>
</Link> </Link>
); );
}); });

@ -39,49 +39,48 @@ const VersionStatus = ({ onClick }: VersionStatusProps) => {
: intl.formatMessage(messages.streamstable); : intl.formatMessage(messages.streamstable);
return ( return (
<Link href="/settings/about"> <Link
<a href="/settings/about"
onClick={onClick} onClick={onClick}
onKeyDown={(e) => { onKeyDown={(e) => {
if (e.key === 'Enter' && onClick) { if (e.key === 'Enter' && onClick) {
onClick(); onClick();
} }
}} }}
role="button" role="button"
tabIndex={0} tabIndex={0}
className={`mx-2 flex items-center rounded-lg p-2 text-xs ring-1 ring-gray-700 transition duration-300 ${ className={`mx-2 flex items-center rounded-lg p-2 text-xs ring-1 ring-gray-700 transition duration-300 ${
data.updateAvailable data.updateAvailable
? 'bg-yellow-500 text-white hover:bg-yellow-400' ? 'bg-yellow-500 text-white hover:bg-yellow-400'
: 'bg-gray-900 text-gray-300 hover:bg-gray-800' : 'bg-gray-900 text-gray-300 hover:bg-gray-800'
}`} }`}
> >
{data.commitTag === 'local' ? ( {data.commitTag === 'local' ? (
<CodeBracketIcon className="h-6 w-6" /> <CodeBracketIcon className="h-6 w-6" />
) : data.version.startsWith('develop-') ? ( ) : data.version.startsWith('develop-') ? (
<BeakerIcon className="h-6 w-6" /> <BeakerIcon className="h-6 w-6" />
) : ( ) : (
<ServerIcon className="h-6 w-6" /> <ServerIcon className="h-6 w-6" />
)} )}
<div className="flex min-w-0 flex-1 flex-col truncate px-2 last:pr-0"> <div className="flex min-w-0 flex-1 flex-col truncate px-2 last:pr-0">
<span className="font-bold">{versionStream}</span> <span className="font-bold">{versionStream}</span>
<span className="truncate"> <span className="truncate">
{data.commitTag === 'local' ? ( {data.commitTag === 'local' ? (
'(⌐■_■)' '(⌐■_■)'
) : data.commitsBehind > 0 ? ( ) : data.commitsBehind > 0 ? (
intl.formatMessage(messages.commitsbehind, { intl.formatMessage(messages.commitsbehind, {
commitsBehind: data.commitsBehind, commitsBehind: data.commitsBehind,
}) })
) : data.commitsBehind === -1 ? ( ) : data.commitsBehind === -1 ? (
intl.formatMessage(messages.outofdate) intl.formatMessage(messages.outofdate)
) : ( ) : (
<code className="bg-transparent p-0"> <code className="bg-transparent p-0">
{data.version.replace('develop-', '')} {data.version.replace('develop-', '')}
</code> </code>
)} )}
</span> </span>
</div> </div>
{data.updateAvailable && <ArrowUpCircleIcon className="h-6 w-6" />} {data.updateAvailable && <ArrowUpCircleIcon className="h-6 w-6" />}
</a>
</Link> </Link>
); );
}; };

@ -275,19 +275,18 @@ const ManageSlideOver = ({
: `/users/${user.id}` : `/users/${user.id}`
} }
key={`watch-user-${user.id}`} key={`watch-user-${user.id}`}
className="z-0 mb-1 -mr-2 shrink-0 hover:z-50"
> >
<a className="z-0 mb-1 -mr-2 shrink-0 hover:z-50"> <Tooltip
<Tooltip key={`watch-user-${user.id}`}
key={`watch-user-${user.id}`} content={user.displayName}
content={user.displayName} >
> <img
<img src={user.avatar}
src={user.avatar} alt={user.displayName}
alt={user.displayName} className="h-8 w-8 scale-100 transform-gpu rounded-full object-cover ring-1 ring-gray-500 transition duration-300 hover:scale-105"
className="h-8 w-8 scale-100 transform-gpu rounded-full object-cover ring-1 ring-gray-500 transition duration-300 hover:scale-105" />
/> </Tooltip>
</Tooltip>
</a>
</Link> </Link>
))} ))}
</span> </span>
@ -401,19 +400,18 @@ const ManageSlideOver = ({
: `/users/${user.id}` : `/users/${user.id}`
} }
key={`watch-user-${user.id}`} key={`watch-user-${user.id}`}
className="z-0 mb-1 -mr-2 shrink-0 hover:z-50"
> >
<a className="z-0 mb-1 -mr-2 shrink-0 hover:z-50"> <Tooltip
<Tooltip key={`watch-user-${user.id}`}
key={`watch-user-${user.id}`} content={user.displayName}
content={user.displayName} >
> <img
<img src={user.avatar}
src={user.avatar} alt={user.displayName}
alt={user.displayName} className="h-8 w-8 scale-100 transform-gpu rounded-full object-cover ring-1 ring-gray-500 transition duration-300 hover:scale-105"
className="h-8 w-8 scale-100 transform-gpu rounded-full object-cover ring-1 ring-gray-500 transition duration-300 hover:scale-105" />
/> </Tooltip>
</Tooltip>
</a>
</Link> </Link>
))} ))}
</span> </span>

@ -30,79 +30,78 @@ const ShowMoreCard = ({ url, posters }: ShowMoreCardProps) => {
} }
return ( return (
<Link href={url}> <Link
<a href={url}
className={'w-36 sm:w-36 md:w-44'} className={'w-36 sm:w-36 md:w-44'}
onMouseEnter={() => { onMouseEnter={() => {
setHovered(true);
}}
onMouseLeave={() => setHovered(false)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
setHovered(true); setHovered(true);
}} }
onMouseLeave={() => setHovered(false)} }}
onKeyDown={(e) => { role="link"
if (e.key === 'Enter') { tabIndex={0}
setHovered(true); >
} <div
}} className={`relative w-36 transform-gpu cursor-pointer
role="link"
tabIndex={0}
>
<div
className={`relative w-36 transform-gpu cursor-pointer
overflow-hidden rounded-xl text-white shadow-lg ring-1 transition duration-150 ease-in-out sm:w-36 md:w-44 ${ overflow-hidden rounded-xl text-white shadow-lg ring-1 transition duration-150 ease-in-out sm:w-36 md:w-44 ${
isHovered isHovered
? 'scale-105 bg-gray-600 ring-gray-500' ? 'scale-105 bg-gray-600 ring-gray-500'
: 'scale-100 bg-gray-800 ring-gray-700' : 'scale-100 bg-gray-800 ring-gray-700'
}`} }`}
> >
<div style={{ paddingBottom: '150%' }}> <div style={{ paddingBottom: '150%' }}>
<div className="absolute inset-0 flex h-full w-full flex-col items-center p-2"> <div className="absolute inset-0 flex h-full w-full flex-col items-center p-2">
<div className="relative z-10 flex h-full flex-wrap items-center justify-center opacity-30"> <div className="relative z-10 flex h-full flex-wrap items-center justify-center opacity-30">
{posters[0] && ( {posters[0] && (
<div className="w-1/2 p-1"> <div className="w-1/2 p-1">
<img <img
src={`//image.tmdb.org/t/p/w300_and_h450_face${posters[0]}`} src={`//image.tmdb.org/t/p/w300_and_h450_face${posters[0]}`}
alt="" alt=""
className="w-full rounded-md" className="w-full rounded-md"
/> />
</div> </div>
)} )}
{posters[1] && ( {posters[1] && (
<div className="w-1/2 p-1"> <div className="w-1/2 p-1">
<img <img
src={`//image.tmdb.org/t/p/w300_and_h450_face${posters[1]}`} src={`//image.tmdb.org/t/p/w300_and_h450_face${posters[1]}`}
alt="" alt=""
className="w-full rounded-md" className="w-full rounded-md"
/> />
</div>
)}
{posters[2] && (
<div className="w-1/2 p-1">
<img
src={`//image.tmdb.org/t/p/w300_and_h450_face${posters[2]}`}
alt=""
className="w-full rounded-md"
/>
</div>
)}
{posters[3] && (
<div className="w-1/2 p-1">
<img
src={`//image.tmdb.org/t/p/w300_and_h450_face${posters[3]}`}
alt=""
className="w-full rounded-md"
/>
</div>
)}
</div>
<div className="absolute inset-0 z-20 flex flex-col items-center justify-center text-white">
<ArrowRightCircleIcon className="w-14" />
<div className="mt-2 font-extrabold">
{intl.formatMessage(messages.seemore)}
</div> </div>
)}
{posters[2] && (
<div className="w-1/2 p-1">
<img
src={`//image.tmdb.org/t/p/w300_and_h450_face${posters[2]}`}
alt=""
className="w-full rounded-md"
/>
</div>
)}
{posters[3] && (
<div className="w-1/2 p-1">
<img
src={`//image.tmdb.org/t/p/w300_and_h450_face${posters[3]}`}
alt=""
className="w-full rounded-md"
/>
</div>
)}
</div>
<div className="absolute inset-0 z-20 flex flex-col items-center justify-center text-white">
<ArrowRightCircleIcon className="w-14" />
<div className="mt-2 font-extrabold">
{intl.formatMessage(messages.seemore)}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</a> </div>
</Link> </Link>
); );
}; };

@ -148,11 +148,9 @@ const MediaSlider = ({
<> <>
<div className="slider-header"> <div className="slider-header">
{linkUrl ? ( {linkUrl ? (
<Link href={linkUrl}> <Link href={linkUrl} className="slider-title min-w-0 pr-16">
<a className="slider-title min-w-0 pr-16"> <span className="truncate">{title}</span>
<span className="truncate">{title}</span> <ArrowRightCircleIcon />
<ArrowRightCircleIcon />
</a>
</Link> </Link>
) : ( ) : (
<div className="slider-title"> <div className="slider-title">

@ -34,8 +34,8 @@ const MovieCast = () => {
<div className="mt-1 mb-5"> <div className="mt-1 mb-5">
<Header <Header
subtext={ subtext={
<Link href={`/movie/${data.id}`}> <Link href={`/movie/${data.id}`} className="hover:underline">
<a className="hover:underline">{data.title}</a> {data.title}
</Link> </Link>
} }
> >

@ -34,8 +34,8 @@ const MovieCrew = () => {
<div className="mt-1 mb-5"> <div className="mt-1 mb-5">
<Header <Header
subtext={ subtext={
<Link href={`/movie/${data.id}`}> <Link href={`/movie/${data.id}`} className="hover:underline">
<a className="hover:underline">{data.title}</a> {data.title}
</Link> </Link>
} }
> >

@ -44,8 +44,8 @@ const MovieRecommendations = () => {
<div className="mt-1 mb-5"> <div className="mt-1 mb-5">
<Header <Header
subtext={ subtext={
<Link href={`/movie/${movieData?.id}`}> <Link href={`/movie/${movieData?.id}`} className="hover:underline">
<a className="hover:underline">{movieData?.title}</a> {movieData?.title}
</Link> </Link>
} }
> >

@ -42,8 +42,8 @@ const MovieSimilar = () => {
<div className="mt-1 mb-5"> <div className="mt-1 mb-5">
<Header <Header
subtext={ subtext={
<Link href={`/movie/${movieData?.id}`}> <Link href={`/movie/${movieData?.id}`} className="hover:underline">
<a className="hover:underline">{movieData?.title}</a> {movieData?.title}
</Link> </Link>
} }
> >

@ -233,8 +233,12 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
movieAttributes.push( movieAttributes.push(
data.genres data.genres
.map((g) => ( .map((g) => (
<Link href={`/discover/movies?genre=${g.id}`} key={`genre-${g.id}`}> <Link
<a className="hover:underline">{g.name}</a> href={`/discover/movies?genre=${g.id}`}
key={`genre-${g.id}`}
className="hover:underline"
>
{g.name}
</Link> </Link>
)) ))
.reduce((prev, curr) => ( .reduce((prev, curr) => (
@ -448,18 +452,19 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
{sortedCrew.slice(0, 6).map((person) => ( {sortedCrew.slice(0, 6).map((person) => (
<li key={`crew-${person.job}-${person.id}`}> <li key={`crew-${person.job}-${person.id}`}>
<span>{person.job}</span> <span>{person.job}</span>
<Link href={`/person/${person.id}`}> <Link href={`/person/${person.id}`} className="crew-name">
<a className="crew-name">{person.name}</a> {person.name}
</Link> </Link>
</li> </li>
))} ))}
</ul> </ul>
<div className="mt-4 flex justify-end"> <div className="mt-4 flex justify-end">
<Link href={`/movie/${data.id}/crew`}> <Link
<a className="flex items-center text-gray-400 transition duration-300 hover:text-gray-100"> href={`/movie/${data.id}/crew`}
<span>{intl.formatMessage(messages.viewfullcrew)}</span> className="flex items-center text-gray-400 transition duration-300 hover:text-gray-100"
<ArrowRightCircleIcon className="ml-1.5 inline-block h-5 w-5" /> >
</a> <span>{intl.formatMessage(messages.viewfullcrew)}</span>
<ArrowRightCircleIcon className="ml-1.5 inline-block h-5 w-5" />
</Link> </Link>
</div> </div>
</> </>
@ -470,10 +475,9 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
<Link <Link
href={`/discover/movies?keywords=${keyword.id}`} href={`/discover/movies?keywords=${keyword.id}`}
key={`keyword-id-${keyword.id}`} key={`keyword-id-${keyword.id}`}
className="mb-2 mr-2 inline-flex last:mr-0"
> >
<a className="mb-2 mr-2 inline-flex last:mr-0"> <Tag>{keyword.name}</Tag>
<Tag>{keyword.name}</Tag>
</a>
</Link> </Link>
))} ))}
</div> </div>
@ -483,31 +487,29 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
{data.collection && ( {data.collection && (
<div className="mb-6"> <div className="mb-6">
<Link href={`/collection/${data.collection.id}`}> <Link href={`/collection/${data.collection.id}`}>
<a> <div className="group relative z-0 scale-100 transform-gpu cursor-pointer overflow-hidden rounded-lg bg-gray-800 bg-cover bg-center shadow-md ring-1 ring-gray-700 transition duration-300 hover:scale-105 hover:ring-gray-500">
<div className="group relative z-0 scale-100 transform-gpu cursor-pointer overflow-hidden rounded-lg bg-gray-800 bg-cover bg-center shadow-md ring-1 ring-gray-700 transition duration-300 hover:scale-105 hover:ring-gray-500"> <div className="absolute inset-0 z-0">
<div className="absolute inset-0 z-0"> <CachedImage
<CachedImage src={`https://image.tmdb.org/t/p/w1440_and_h320_multi_faces/${data.collection.backdropPath}`}
src={`https://image.tmdb.org/t/p/w1440_and_h320_multi_faces/${data.collection.backdropPath}`} alt=""
alt="" layout="fill"
layout="fill" objectFit="cover"
objectFit="cover" />
/> <div
<div className="absolute inset-0"
className="absolute inset-0" style={{
style={{ backgroundImage:
backgroundImage: 'linear-gradient(180deg, rgba(31, 41, 55, 0.47) 0%, rgba(31, 41, 55, 0.80) 100%)',
'linear-gradient(180deg, rgba(31, 41, 55, 0.47) 0%, rgba(31, 41, 55, 0.80) 100%)', }}
}} />
/>
</div>
<div className="relative z-10 flex h-full items-center justify-between p-4 text-gray-200 transition duration-300 group-hover:text-white">
<div>{data.collection.name}</div>
<Button buttonSize="sm">
{intl.formatMessage(globalMessages.view)}
</Button>
</div>
</div> </div>
</a> <div className="relative z-10 flex h-full items-center justify-between p-4 text-gray-200 transition duration-300 group-hover:text-white">
<div>{data.collection.name}</div>
<Button buttonSize="sm">
{intl.formatMessage(globalMessages.view)}
</Button>
</div>
</div>
</Link> </Link>
</div> </div>
)} )}
@ -704,15 +706,13 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
<Link <Link
href={`/discover/movies/language/${data.originalLanguage}`} href={`/discover/movies/language/${data.originalLanguage}`}
> >
<a> {intl.formatDisplayName(data.originalLanguage, {
{intl.formatDisplayName(data.originalLanguage, { type: 'language',
type: 'language', fallback: 'none',
fallback: 'none', }) ??
}) ?? data.spokenLanguages.find(
data.spokenLanguages.find( (lng) => lng.iso_639_1 === data.originalLanguage
(lng) => lng.iso_639_1 === data.originalLanguage )?.name}
)?.name}
</a>
</Link> </Link>
</span> </span>
</div> </div>
@ -768,8 +768,9 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
<Link <Link
href={`/discover/movies/studio/${s.id}`} href={`/discover/movies/studio/${s.id}`}
key={`studio-${s.id}`} key={`studio-${s.id}`}
className="block"
> >
<a className="block">{s.name}</a> {s.name}
</Link> </Link>
); );
})} })}
@ -827,11 +828,13 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
{data.credits.cast.length > 0 && ( {data.credits.cast.length > 0 && (
<> <>
<div className="slider-header"> <div className="slider-header">
<Link href="/movie/[movieId]/cast" as={`/movie/${data.id}/cast`}> <Link
<a className="slider-title"> href="/movie/[movieId]/cast"
<span>{intl.formatMessage(messages.cast)}</span> as={`/movie/${data.id}/cast`}
<ArrowRightCircleIcon /> className="slider-title"
</a> >
<span>{intl.formatMessage(messages.cast)}</span>
<ArrowRightCircleIcon />
</Link> </Link>
</div> </div>
<Slider <Slider

@ -264,10 +264,9 @@ const ArtistDetails = ({ artist }: ArtistDetailsProp) => {
<Link <Link
href={`/discover/music?keywords=${keyword}`} href={`/discover/music?keywords=${keyword}`}
key={`keyword-id-${idx}`} key={`keyword-id-${idx}`}
className="mb-2 mr-2 inline-flex last:mr-0"
> >
<a className="mb-2 mr-2 inline-flex last:mr-0"> <Tag>{keyword}</Tag>
<Tag>{keyword}</Tag>
</a>
</Link> </Link>
))} ))}
</div> </div>

@ -168,8 +168,11 @@ const ReleaseDetails = ({ release }: ReleaseDetailsProp) => {
{data.artist.map((artist, index) => ( {data.artist.map((artist, index) => (
<div key={`artist-${index}`}> <div key={`artist-${index}`}>
{' '} {' '}
<Link href={`/music/artist/${artist.id}`}> <Link
<a className="hover:underline">{artist.name}</a> href={`/music/artist/${artist.id}`}
className="hover:underline"
>
{artist.name}
</Link> </Link>
{index < data.artist.length - 1 ? ', ' : ''} {index < data.artist.length - 1 ? ', ' : ''}
</div> </div>
@ -212,10 +215,9 @@ const ReleaseDetails = ({ release }: ReleaseDetailsProp) => {
<Link <Link
href={`/discover/music?keywords=${keyword}`} href={`/discover/music?keywords=${keyword}`}
key={`keyword-id-${idx}`} key={`keyword-id-${idx}`}
className="mb-2 mr-2 inline-flex last:mr-0"
> >
<a className="mb-2 mr-2 inline-flex last:mr-0"> <Tag>{keyword}</Tag>
<Tag>{keyword}</Tag>
</a>
</Link> </Link>
))} ))}
</div> </div>

@ -21,71 +21,68 @@ const PersonCard = ({
const [isHovered, setHovered] = useState(false); const [isHovered, setHovered] = useState(false);
return ( return (
<Link href={`/person/${personId}`}> <Link
<a href={`/person/${personId}`}
className={canExpand ? 'w-full' : 'w-36 sm:w-36 md:w-44'} className={canExpand ? 'w-full' : 'w-36 sm:w-36 md:w-44'}
onMouseEnter={() => { onMouseEnter={() => {
setHovered(true);
}}
onMouseLeave={() => setHovered(false)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
setHovered(true); setHovered(true);
}} }
onMouseLeave={() => setHovered(false)} }}
onKeyDown={(e) => { role="link"
if (e.key === 'Enter') { tabIndex={0}
setHovered(true); >
} <div
}} className={`relative ${
role="link" canExpand ? 'w-full' : 'w-36 sm:w-36 md:w-44'
tabIndex={0} } transform-gpu cursor-pointer rounded-xl text-white shadow ring-1 transition duration-150 ease-in-out ${
isHovered
? 'scale-105 bg-gray-700 ring-gray-500'
: 'scale-100 bg-gray-800 ring-gray-700'
}`}
> >
<div <div style={{ paddingBottom: '150%' }}>
className={`relative ${ <div className="absolute inset-0 flex h-full w-full flex-col items-center p-2">
canExpand ? 'w-full' : 'w-36 sm:w-36 md:w-44' <div className="relative mt-2 mb-4 flex h-1/2 w-full justify-center">
} transform-gpu cursor-pointer rounded-xl text-white shadow ring-1 transition duration-150 ease-in-out ${ {profilePath ? (
isHovered <div className="relative h-full w-3/4 overflow-hidden rounded-full ring-1 ring-gray-700">
? 'scale-105 bg-gray-700 ring-gray-500' <CachedImage
: 'scale-100 bg-gray-800 ring-gray-700' src={`https://image.tmdb.org/t/p/w600_and_h900_bestv2${profilePath}`}
}`} alt=""
> layout="fill"
<div style={{ paddingBottom: '150%' }}> objectFit="cover"
<div className="absolute inset-0 flex h-full w-full flex-col items-center p-2"> />
<div className="relative mt-2 mb-4 flex h-1/2 w-full justify-center">
{profilePath ? (
<div className="relative h-full w-3/4 overflow-hidden rounded-full ring-1 ring-gray-700">
<CachedImage
src={`https://image.tmdb.org/t/p/w600_and_h900_bestv2${profilePath}`}
alt=""
layout="fill"
objectFit="cover"
/>
</div>
) : (
<UserCircleIcon className="h-full" />
)}
</div>
<div className="w-full truncate text-center font-bold">
{name}
</div>
{subName && (
<div
className="overflow-hidden whitespace-normal text-center text-sm text-gray-300"
style={{
WebkitLineClamp: 2,
display: '-webkit-box',
overflow: 'hidden',
WebkitBoxOrient: 'vertical',
}}
>
{subName}
</div> </div>
) : (
<UserCircleIcon className="h-full" />
)} )}
<div
className={`absolute bottom-0 left-0 right-0 h-12 rounded-b-xl bg-gradient-to-t ${
isHovered ? 'from-gray-800' : 'from-gray-900'
}`}
/>
</div> </div>
<div className="w-full truncate text-center font-bold">{name}</div>
{subName && (
<div
className="overflow-hidden whitespace-normal text-center text-sm text-gray-300"
style={{
WebkitLineClamp: 2,
display: '-webkit-box',
overflow: 'hidden',
WebkitBoxOrient: 'vertical',
}}
>
{subName}
</div>
)}
<div
className={`absolute bottom-0 left-0 right-0 h-12 rounded-b-xl bg-gradient-to-t ${
isHovered ? 'from-gray-800' : 'from-gray-900'
}`}
/>
</div> </div>
</div> </div>
</a> </div>
</Link> </Link>
); );
}; };

@ -101,10 +101,9 @@ const RequestBlock = ({ request, onUpdate }: RequestBlockProps) => {
? '/profile' ? '/profile'
: `/users/${request.requestedBy.id}` : `/users/${request.requestedBy.id}`
} }
className="font-semibold text-gray-100 transition duration-300 hover:text-white hover:underline"
> >
<a className="font-semibold text-gray-100 transition duration-300 hover:text-white hover:underline"> {request.requestedBy.displayName}
{request.requestedBy.displayName}
</a>
</Link> </Link>
</span> </span>
</div> </div>
@ -120,10 +119,9 @@ const RequestBlock = ({ request, onUpdate }: RequestBlockProps) => {
? '/profile' ? '/profile'
: `/users/${request.modifiedBy.id}` : `/users/${request.modifiedBy.id}`
} }
className="font-semibold text-gray-100 transition duration-300 hover:text-white hover:underline"
> >
<a className="font-semibold text-gray-100 transition duration-300 hover:text-white hover:underline"> {request.modifiedBy.displayName}
{request.modifiedBy.displayName}
</a>
</Link> </Link>
</span> </span>
</div> </div>

@ -131,17 +131,18 @@ const RequestCardError = ({ requestData }: RequestCardErrorProps) => {
{ type: 'or' } { type: 'or' }
) && ( ) && (
<div className="card-field !hidden sm:!block"> <div className="card-field !hidden sm:!block">
<Link href={`/users/${requestData.requestedBy.id}`}> <Link
<a className="group flex items-center"> href={`/users/${requestData.requestedBy.id}`}
<img className="group flex items-center"
src={requestData.requestedBy.avatar} >
alt="" <img
className="avatar-sm" src={requestData.requestedBy.avatar}
/> alt=""
<span className="truncate group-hover:underline"> className="avatar-sm"
{requestData.requestedBy.displayName} />
</span> <span className="truncate group-hover:underline">
</a> {requestData.requestedBy.displayName}
</span>
</Link> </Link>
</div> </div>
)} )}
@ -409,27 +410,27 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => {
? `/tv/${requestData.media.tmdbId}` ? `/tv/${requestData.media.tmdbId}`
: `/music/${requestData.media.secondaryType}/${requestData.media.mbId}` : `/music/${requestData.media.secondaryType}/${requestData.media.mbId}`
} }
className="overflow-hidden overflow-ellipsis whitespace-nowrap text-base font-bold text-white hover:underline sm:text-lg"
> >
<a className="overflow-hidden overflow-ellipsis whitespace-nowrap text-base font-bold text-white hover:underline sm:text-lg"> {isMovie(title) || isRelease(title) ? title.title : title.name}
{isMovie(title) || isRelease(title) ? title.title : title.name}
</a>
</Link> </Link>
{hasPermission( {hasPermission(
[Permission.MANAGE_REQUESTS, Permission.REQUEST_VIEW], [Permission.MANAGE_REQUESTS, Permission.REQUEST_VIEW],
{ type: 'or' } { type: 'or' }
) && ( ) && (
<div className="card-field"> <div className="card-field">
<Link href={`/users/${requestData.requestedBy.id}`}> <Link
<a className="group flex items-center"> href={`/users/${requestData.requestedBy.id}`}
<img className="group flex items-center"
src={requestData.requestedBy.avatar} >
alt="" <img
className="avatar-sm object-cover" src={requestData.requestedBy.avatar}
/> alt=""
<span className="truncate font-semibold group-hover:text-white group-hover:underline"> className="avatar-sm object-cover"
{requestData.requestedBy.displayName} />
</span> <span className="truncate font-semibold group-hover:text-white group-hover:underline">
</a> {requestData.requestedBy.displayName}
</span>
</Link> </Link>
</div> </div>
)} )}
@ -633,24 +634,23 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => {
? `/tv/${requestData.media.tmdbId}` ? `/tv/${requestData.media.tmdbId}`
: `/music/${requestData.media.secondaryType}/${requestData.media.mbId}` : `/music/${requestData.media.secondaryType}/${requestData.media.mbId}`
} }
className="w-20 flex-shrink-0 scale-100 transform-gpu cursor-pointer overflow-hidden rounded-md shadow-sm transition duration-300 hover:scale-105 hover:shadow-md sm:w-28"
> >
<a className="w-20 flex-shrink-0 scale-100 transform-gpu cursor-pointer overflow-hidden rounded-md shadow-sm transition duration-300 hover:scale-105 hover:shadow-md sm:w-28"> <CachedImage
<CachedImage src={
src={ isMovie(title) || isTv(title)
isMovie(title) || isTv(title) ? title.posterPath
? title.posterPath ? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${title.posterPath}`
? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${title.posterPath}`
: '/images/overseerr_poster_not_found.png'
: title.posterPath
? title.posterPath
: '/images/overseerr_poster_not_found.png' : '/images/overseerr_poster_not_found.png'
} : title.posterPath
alt="" ? title.posterPath
layout="responsive" : '/images/overseerr_poster_not_found.png'
width={isMovie(title) || isTv(title) ? 600 : 900} }
height={900} alt=""
/> layout="responsive"
</a> width={isMovie(title) || isTv(title) ? 600 : 900}
height={900}
/>
</Link> </Link>
</div> </div>
</> </>

@ -205,17 +205,18 @@ const RequestItemError = ({
/> />
), ),
user: ( user: (
<Link href={`/users/${requestData.requestedBy.id}`}> <Link
<a className="group flex items-center truncate"> href={`/users/${requestData.requestedBy.id}`}
<img className="group flex items-center truncate"
src={requestData.requestedBy.avatar} >
alt="" <img
className="avatar-sm ml-1.5" src={requestData.requestedBy.avatar}
/> alt=""
<span className="truncate text-sm group-hover:underline"> className="avatar-sm ml-1.5"
{requestData.requestedBy.displayName} />
</span> <span className="truncate text-sm group-hover:underline">
</a> {requestData.requestedBy.displayName}
</span>
</Link> </Link>
), ),
})} })}
@ -259,17 +260,18 @@ const RequestItemError = ({
/> />
), ),
user: ( user: (
<Link href={`/users/${requestData.modifiedBy.id}`}> <Link
<a className="group flex items-center truncate"> href={`/users/${requestData.modifiedBy.id}`}
<img className="group flex items-center truncate"
src={requestData.modifiedBy.avatar} >
alt="" <img
className="avatar-sm ml-1.5" src={requestData.modifiedBy.avatar}
/> alt=""
<span className="truncate text-sm group-hover:underline"> className="avatar-sm ml-1.5"
{requestData.modifiedBy.displayName} />
</span> <span className="truncate text-sm group-hover:underline">
</a> {requestData.modifiedBy.displayName}
</span>
</Link> </Link>
), ),
})} })}
@ -431,22 +433,21 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
? `/tv/${requestData.media.tmdbId}` ? `/tv/${requestData.media.tmdbId}`
: `/music/${requestData.secondaryType}/${requestData.media.mbId}` : `/music/${requestData.secondaryType}/${requestData.media.mbId}`
} }
className="relative h-auto w-12 flex-shrink-0 scale-100 transform-gpu overflow-hidden rounded-md transition duration-300 hover:scale-105"
> >
<a className="relative h-auto w-12 flex-shrink-0 scale-100 transform-gpu overflow-hidden rounded-md transition duration-300 hover:scale-105"> <CachedImage
<CachedImage src={
src={ title.posterPath && (isMovie(title) || isTv(title))
title.posterPath && (isMovie(title) || isTv(title)) ? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${title.posterPath}`
? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${title.posterPath}` : title.posterPath ??
: title.posterPath ?? '/images/overseerr_poster_not_found.png'
'/images/overseerr_poster_not_found.png' }
} alt=""
alt="" layout="responsive"
layout="responsive" width={600}
width={600} height={900}
height={900} objectFit="cover"
objectFit="cover" />
/>
</a>
</Link> </Link>
<div className="flex flex-col justify-center overflow-hidden pl-2 xl:pl-4"> <div className="flex flex-col justify-center overflow-hidden pl-2 xl:pl-4">
<div className="pt-0.5 text-xs font-medium text-white sm:pt-1"> <div className="pt-0.5 text-xs font-medium text-white sm:pt-1">
@ -467,12 +468,9 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
? `/tv/${requestData.media.tmdbId}` ? `/tv/${requestData.media.tmdbId}`
: `/music/${requestData.secondaryType}/${requestData.media.mbId}` : `/music/${requestData.secondaryType}/${requestData.media.mbId}`
} }
className="mr-2 min-w-0 truncate text-lg font-bold text-white hover:underline xl:text-xl"
> >
<a className="mr-2 min-w-0 truncate text-lg font-bold text-white hover:underline xl:text-xl"> {isMovie(title) || isRelease(title) ? title.title : title.name}
{isMovie(title) || isRelease(title)
? title.title
: title.name}
</a>
</Link> </Link>
{isTv(title) && request.seasons.length > 0 && ( {isTv(title) && request.seasons.length > 0 && (
<div className="card-field"> <div className="card-field">
@ -570,17 +568,18 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
/> />
), ),
user: ( user: (
<Link href={`/users/${requestData.requestedBy.id}`}> <Link
<a className="group flex items-center truncate"> href={`/users/${requestData.requestedBy.id}`}
<img className="group flex items-center truncate"
src={requestData.requestedBy.avatar} >
alt="" <img
className="avatar-sm ml-1.5 object-cover" src={requestData.requestedBy.avatar}
/> alt=""
<span className="truncate text-sm font-semibold group-hover:text-white group-hover:underline"> className="avatar-sm ml-1.5 object-cover"
{requestData.requestedBy.displayName} />
</span> <span className="truncate text-sm font-semibold group-hover:text-white group-hover:underline">
</a> {requestData.requestedBy.displayName}
</span>
</Link> </Link>
), ),
})} })}
@ -624,17 +623,18 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
/> />
), ),
user: ( user: (
<Link href={`/users/${requestData.modifiedBy.id}`}> <Link
<a className="group flex items-center truncate"> href={`/users/${requestData.modifiedBy.id}`}
<img className="group flex items-center truncate"
src={requestData.modifiedBy.avatar} >
alt="" <img
className="avatar-sm ml-1.5 object-cover" src={requestData.modifiedBy.avatar}
/> alt=""
<span className="truncate text-sm font-semibold group-hover:text-white group-hover:underline"> className="avatar-sm ml-1.5 object-cover"
{requestData.modifiedBy.displayName} />
</span> <span className="truncate text-sm font-semibold group-hover:text-white group-hover:underline">
</a> {requestData.modifiedBy.displayName}
</span>
</Link> </Link>
), ),
})} })}

@ -122,12 +122,12 @@ const RequestList = () => {
<Header <Header
subtext={ subtext={
router.pathname.startsWith('/profile') ? ( router.pathname.startsWith('/profile') ? (
<Link href={`/profile`}> <Link href={`/profile`} className="hover:underline">
<a className="hover:underline">{currentUser?.displayName}</a> {currentUser?.displayName}
</Link> </Link>
) : router.query.userId ? ( ) : router.query.userId ? (
<Link href={`/users/${user?.id}`}> <Link href={`/users/${user?.id}`} className="hover:underline">
<a className="hover:underline">{user?.displayName}</a> {user?.displayName}
</Link> </Link>
) : ( ) : (
'' ''

@ -133,10 +133,9 @@ const QuotaDisplay = ({
ProfileLink: (msg: React.ReactNode) => ( ProfileLink: (msg: React.ReactNode) => (
<Link <Link
href={userOverride ? `/users/${userOverride}` : '/profile'} href={userOverride ? `/users/${userOverride}` : '/profile'}
className="text-white transition duration-300 hover:underline"
> >
<a className="text-white transition duration-300 hover:underline"> {msg}
{msg}
</a>
</Link> </Link>
), ),
} }

@ -212,60 +212,55 @@ const TitleCard = ({
? `/${mediaType}/${id}` ? `/${mediaType}/${id}`
: `/music/${mediaType}/${id as string}` : `/music/${mediaType}/${id as string}`
} }
className="absolute inset-0 h-full w-full cursor-pointer overflow-hidden text-left"
style={{
background:
'linear-gradient(180deg, rgba(45, 55, 72, 0.4) 0%, rgba(45, 55, 72, 0.9) 100%)',
}}
> >
<a <div className="flex h-full w-full items-end">
className="absolute inset-0 h-full w-full cursor-pointer overflow-hidden text-left" <div
style={{ className={`px-2 text-white ${
background: !showRequestButton ||
'linear-gradient(180deg, rgba(45, 55, 72, 0.4) 0%, rgba(45, 55, 72, 0.9) 100%)', (currentStatus && currentStatus !== MediaStatus.UNKNOWN)
}} ? 'pb-2'
> : 'pb-11'
<div className="flex h-full w-full items-end"> }`}
>
{year && <div className="text-sm font-medium">{year}</div>}
<h1
className="whitespace-normal text-xl font-bold leading-tight"
style={{
WebkitLineClamp: 3,
display: '-webkit-box',
overflow: 'hidden',
WebkitBoxOrient: 'vertical',
wordBreak: 'break-word',
}}
data-testid="title-card-title"
>
{title}
</h1>
<div <div
className={`px-2 text-white ${ className="whitespace-normal text-xs"
!showRequestButton || style={{
(currentStatus && currentStatus !== MediaStatus.UNKNOWN) WebkitLineClamp:
? 'pb-2' !showRequestButton ||
: 'pb-11' (currentStatus &&
}`} currentStatus !== MediaStatus.UNKNOWN)
? 5
: 3,
display: '-webkit-box',
overflow: 'hidden',
WebkitBoxOrient: 'vertical',
wordBreak: 'break-word',
}}
> >
{year && ( {summary}
<div className="text-sm font-medium">{year}</div>
)}
<h1
className="whitespace-normal text-xl font-bold leading-tight"
style={{
WebkitLineClamp: 3,
display: '-webkit-box',
overflow: 'hidden',
WebkitBoxOrient: 'vertical',
wordBreak: 'break-word',
}}
data-testid="title-card-title"
>
{title}
</h1>
<div
className="whitespace-normal text-xs"
style={{
WebkitLineClamp:
!showRequestButton ||
(currentStatus &&
currentStatus !== MediaStatus.UNKNOWN)
? 5
: 3,
display: '-webkit-box',
overflow: 'hidden',
WebkitBoxOrient: 'vertical',
wordBreak: 'break-word',
}}
>
{summary}
</div>
</div> </div>
</div> </div>
</a> </div>
</Link> </Link>
<div className="absolute bottom-0 left-0 right-0 flex justify-between px-2 py-2"> <div className="absolute bottom-0 left-0 right-0 flex justify-between px-2 py-2">

@ -34,8 +34,8 @@ const TvCast = () => {
<div className="mt-1 mb-5"> <div className="mt-1 mb-5">
<Header <Header
subtext={ subtext={
<Link href={`/tv/${data.id}`}> <Link href={`/tv/${data.id}`} className="hover:underline">
<a className="hover:underline">{data.name}</a> {data.name}
</Link> </Link>
} }
> >

@ -34,8 +34,8 @@ const TvCrew = () => {
<div className="mt-1 mb-5"> <div className="mt-1 mb-5">
<Header <Header
subtext={ subtext={
<Link href={`/tv/${data.id}`}> <Link href={`/tv/${data.id}`} className="hover:underline">
<a className="hover:underline">{data.name}</a> {data.name}
</Link> </Link>
} }
> >

@ -40,8 +40,8 @@ const TvRecommendations = () => {
<div className="mt-1 mb-5"> <div className="mt-1 mb-5">
<Header <Header
subtext={ subtext={
<Link href={`/tv/${tvData?.id}`}> <Link href={`/tv/${tvData?.id}`} className="hover:underline">
<a className="hover:underline">{tvData?.name}</a> {tvData?.name}
</Link> </Link>
} }
> >

@ -38,8 +38,8 @@ const TvSimilar = () => {
<div className="mt-1 mb-5"> <div className="mt-1 mb-5">
<Header <Header
subtext={ subtext={
<Link href={`/tv/${tvData?.id}`}> <Link href={`/tv/${tvData?.id}`} className="hover:underline">
<a className="hover:underline">{tvData?.name}</a> {tvData?.name}
</Link> </Link>
} }
> >

@ -214,8 +214,12 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
seriesAttributes.push( seriesAttributes.push(
data.genres data.genres
.map((g) => ( .map((g) => (
<Link href={`/discover/tv?genre=${g.id}`} key={`genre-${g.id}`}> <Link
<a className="hover:underline">{g.name}</a> href={`/discover/tv?genre=${g.id}`}
key={`genre-${g.id}`}
className="hover:underline"
>
{g.name}
</Link> </Link>
)) ))
.reduce((prev, curr) => ( .reduce((prev, curr) => (
@ -487,18 +491,19 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
.map((person) => ( .map((person) => (
<li key={`crew-${person.job}-${person.id}`}> <li key={`crew-${person.job}-${person.id}`}>
<span>{person.job}</span> <span>{person.job}</span>
<Link href={`/person/${person.id}`}> <Link href={`/person/${person.id}`} className="crew-name">
<a className="crew-name">{person.name}</a> {person.name}
</Link> </Link>
</li> </li>
))} ))}
</ul> </ul>
<div className="mt-4 flex justify-end"> <div className="mt-4 flex justify-end">
<Link href={`/tv/${data.id}/crew`}> <Link
<a className="flex items-center text-gray-400 transition duration-300 hover:text-gray-100"> href={`/tv/${data.id}/crew`}
<span>{intl.formatMessage(messages.viewfullcrew)}</span> className="flex items-center text-gray-400 transition duration-300 hover:text-gray-100"
<ArrowRightCircleIcon className="ml-1.5 inline-block h-5 w-5" /> >
</a> <span>{intl.formatMessage(messages.viewfullcrew)}</span>
<ArrowRightCircleIcon className="ml-1.5 inline-block h-5 w-5" />
</Link> </Link>
</div> </div>
</> </>
@ -509,10 +514,9 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
<Link <Link
href={`/discover/tv?keywords=${keyword.id}`} href={`/discover/tv?keywords=${keyword.id}`}
key={`keyword-id-${keyword.id}`} key={`keyword-id-${keyword.id}`}
className="mb-2 mr-2 inline-flex last:mr-0"
> >
<a className="mb-2 mr-2 inline-flex last:mr-0"> <Tag>{keyword.name}</Tag>
<Tag>{keyword.name}</Tag>
</a>
</Link> </Link>
))} ))}
</div> </div>
@ -887,15 +891,13 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
<span>{intl.formatMessage(messages.originallanguage)}</span> <span>{intl.formatMessage(messages.originallanguage)}</span>
<span className="media-fact-value"> <span className="media-fact-value">
<Link href={`/discover/tv/language/${data.originalLanguage}`}> <Link href={`/discover/tv/language/${data.originalLanguage}`}>
<a> {intl.formatDisplayName(data.originalLanguage, {
{intl.formatDisplayName(data.originalLanguage, { type: 'language',
type: 'language', fallback: 'none',
fallback: 'none', }) ??
}) ?? data.spokenLanguages.find(
data.spokenLanguages.find( (lng) => lng.iso_639_1 === data.originalLanguage
(lng) => lng.iso_639_1 === data.originalLanguage )?.name}
)?.name}
</a>
</Link> </Link>
</span> </span>
</div> </div>
@ -945,7 +947,7 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
href={`/discover/tv/network/${n.id}`} href={`/discover/tv/network/${n.id}`}
key={`network-${n.id}`} key={`network-${n.id}`}
> >
<a>{n.name}</a> {n.name}
</Link> </Link>
)) ))
.reduce((prev, curr) => ( .reduce((prev, curr) => (
@ -989,11 +991,13 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
{data.credits.cast.length > 0 && ( {data.credits.cast.length > 0 && (
<> <>
<div className="slider-header"> <div className="slider-header">
<Link href="/tv/[tvId]/cast" as={`/tv/${data.id}/cast`}> <Link
<a className="slider-title"> href="/tv/[tvId]/cast"
<span>{intl.formatMessage(messages.cast)}</span> as={`/tv/${data.id}/cast`}
<ArrowRightCircleIcon /> className="slider-title"
</a> >
<span>{intl.formatMessage(messages.cast)}</span>
<ArrowRightCircleIcon />
</Link> </Link>
</div> </div>
<Slider <Slider

@ -579,23 +579,23 @@ const UserList = () => {
</Table.TD> </Table.TD>
<Table.TD> <Table.TD>
<div className="flex items-center"> <div className="flex items-center">
<Link href={`/users/${user.id}`}> <Link
<a className="h-10 w-10 flex-shrink-0"> href={`/users/${user.id}`}
<img className="h-10 w-10 flex-shrink-0"
className="h-10 w-10 rounded-full object-cover" >
src={user.avatar} <img
alt="" className="h-10 w-10 rounded-full object-cover"
/> src={user.avatar}
</a> alt=""
/>
</Link> </Link>
<div className="ml-4"> <div className="ml-4">
<Link href={`/users/${user.id}`}> <Link
<a href={`/users/${user.id}`}
className="text-base font-bold leading-5 transition duration-300 hover:underline" className="text-base font-bold leading-5 transition duration-300 hover:underline"
data-testid="user-list-username-link" data-testid="user-list-username-link"
> >
{user.displayName} {user.displayName}
</a>
</Link> </Link>
{user.displayName.toLowerCase() !== user.email && ( {user.displayName.toLowerCase() !== user.email && (
<div className="text-sm leading-5 text-gray-300"> <div className="text-sm leading-5 text-gray-300">
@ -611,10 +611,11 @@ const UserList = () => {
[Permission.MANAGE_REQUESTS, Permission.REQUEST_VIEW], [Permission.MANAGE_REQUESTS, Permission.REQUEST_VIEW],
{ type: 'or' } { type: 'or' }
) ? ( ) ? (
<Link href={`/users/${user.id}/requests`}> <Link
<a className="text-sm leading-5 transition duration-300 hover:underline"> href={`/users/${user.id}/requests`}
{user.requestCount} className="text-sm leading-5 transition duration-300 hover:underline"
</a> >
{user.requestCount}
</Link> </Link>
) : ( ) : (
user.requestCount user.requestCount

@ -57,10 +57,9 @@ const ProfileHeader = ({ user, isSettingsPage }: ProfileHeaderProps) => {
href={ href={
user.id === loggedInUser?.id ? '/profile' : `/users/${user.id}` user.id === loggedInUser?.id ? '/profile' : `/users/${user.id}`
} }
className="text-overseerr text-lg font-bold 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>
</Link> </Link>
{user.email && user.displayName.toLowerCase() !== user.email && ( {user.email && user.displayName.toLowerCase() !== user.email && (
<span className="text-sm text-gray-400 sm:ml-2 sm:text-lg"> <span className="text-sm text-gray-400 sm:ml-2 sm:text-lg">

@ -159,7 +159,7 @@ const UserProfile = () => {
: `/users/${user?.id}/requests?filter=all` : `/users/${user?.id}/requests?filter=all`
} }
> >
<a>{intl.formatNumber(user.requestCount)}</a> {intl.formatNumber(user.requestCount)}
</Link> </Link>
</dd> </dd>
</div> </div>
@ -291,11 +291,10 @@ const UserProfile = () => {
? '/profile/requests?filter=all' ? '/profile/requests?filter=all'
: `/users/${user?.id}/requests?filter=all` : `/users/${user?.id}/requests?filter=all`
} }
className="slider-title"
> >
<a className="slider-title"> <span>{intl.formatMessage(messages.recentrequests)}</span>
<span>{intl.formatMessage(messages.recentrequests)}</span> <ArrowRightCircleIcon />
<ArrowRightCircleIcon />
</a>
</Link> </Link>
</div> </div>
<Slider <Slider
@ -332,11 +331,10 @@ const UserProfile = () => {
? '/profile/watchlist' ? '/profile/watchlist'
: `/users/${user?.id}/watchlist` : `/users/${user?.id}/watchlist`
} }
className="slider-title"
> >
<a className="slider-title"> <span>{intl.formatMessage(messages.plexwatchlist)}</span>
<span>{intl.formatMessage(messages.plexwatchlist)}</span> <ArrowRightCircleIcon />
<ArrowRightCircleIcon />
</a>
</Link> </Link>
</div> </div>
<Slider <Slider

@ -21,11 +21,9 @@ const Custom404 = () => {
error: intl.formatMessage(messages.pagenotfound), error: intl.formatMessage(messages.pagenotfound),
})} })}
</div> </div>
<Link href="/"> <Link href="/" className="mt-2 flex">
<a className="mt-2 flex"> {intl.formatMessage(messages.returnHome)}
{intl.formatMessage(messages.returnHome)} <ArrowRightCircleIcon className="ml-2 h-6 w-6" />
<ArrowRightCircleIcon className="ml-2 h-6 w-6" />
</a>
</Link> </Link>
</div> </div>
); );

@ -44,11 +44,9 @@ const Error: NextPage<ErrorProps> = ({ statusCode }) => {
}) })
: getErrorMessage(statusCode)} : getErrorMessage(statusCode)}
</div> </div>
<Link href="/"> <Link href="/" className="mt-2 flex">
<a className="mt-2 flex"> {intl.formatMessage(messages.returnHome)}
{intl.formatMessage(messages.returnHome)} <ArrowRightCircleIcon className="ml-2 h-6 w-6" />
<ArrowRightCircleIcon className="ml-2 h-6 w-6" />
</a>
</Link> </Link>
</div> </div>
); );

Loading…
Cancel
Save