mirror of
https://github.com/sct/overseerr.git
synced 2025-09-17 17:24:35 +02:00
feat(ui): link processing/requested status badges to service URL (#1761)
* feat(ui): link processing/requested status badges to service URL where available * refactor: add URL prop to Badge component * fix(css): tweak font weight of media rating values and request card link hover effect * fix: only set StatusBadge serviceUrl for admins
This commit is contained in:
@@ -3,36 +3,64 @@ import React from 'react';
|
|||||||
interface BadgeProps {
|
interface BadgeProps {
|
||||||
badgeType?: 'default' | 'primary' | 'danger' | 'warning' | 'success';
|
badgeType?: 'default' | 'primary' | 'danger' | 'warning' | 'success';
|
||||||
className?: string;
|
className?: string;
|
||||||
|
url?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Badge: React.FC<BadgeProps> = ({
|
const Badge: React.FC<BadgeProps> = ({
|
||||||
badgeType = 'default',
|
badgeType = 'default',
|
||||||
className,
|
className,
|
||||||
|
url,
|
||||||
children,
|
children,
|
||||||
}) => {
|
}) => {
|
||||||
const badgeStyle = [
|
const badgeStyle = [
|
||||||
'px-2 inline-flex text-xs leading-5 font-semibold rounded-full cursor-default',
|
'px-2 inline-flex text-xs leading-5 font-semibold rounded-full',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if (url) {
|
||||||
|
badgeStyle.push('transition cursor-pointer');
|
||||||
|
} else {
|
||||||
|
badgeStyle.push('cursor-default');
|
||||||
|
}
|
||||||
|
|
||||||
switch (badgeType) {
|
switch (badgeType) {
|
||||||
case 'danger':
|
case 'danger':
|
||||||
badgeStyle.push('bg-red-600 text-red-100');
|
badgeStyle.push('bg-red-600 text-red-100');
|
||||||
|
if (url) {
|
||||||
|
badgeStyle.push('hover:bg-red-500');
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 'warning':
|
case 'warning':
|
||||||
badgeStyle.push('bg-yellow-500 text-yellow-100');
|
badgeStyle.push('bg-yellow-500 text-yellow-100');
|
||||||
|
if (url) {
|
||||||
|
badgeStyle.push('hover:bg-yellow-400');
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 'success':
|
case 'success':
|
||||||
badgeStyle.push('bg-green-500 text-green-100');
|
badgeStyle.push('bg-green-500 text-green-100');
|
||||||
|
if (url) {
|
||||||
|
badgeStyle.push('hover:bg-green-400');
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
badgeStyle.push('bg-indigo-500 text-indigo-100');
|
badgeStyle.push('bg-indigo-500 text-indigo-100');
|
||||||
|
if (url) {
|
||||||
|
badgeStyle.push('hover:bg-indigo-400');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (className) {
|
if (className) {
|
||||||
badgeStyle.push(className);
|
badgeStyle.push(className);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <span className={badgeStyle.join(' ')}>{children}</span>;
|
if (url) {
|
||||||
|
return (
|
||||||
|
<a href={url} target="_blank" rel="noopener noreferrer">
|
||||||
|
<span className={badgeStyle.join(' ')}>{children}</span>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return <span className={badgeStyle.join(' ')}>{children}</span>;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Badge;
|
export default Badge;
|
||||||
|
@@ -420,6 +420,11 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
|||||||
status={data.mediaInfo?.status}
|
status={data.mediaInfo?.status}
|
||||||
inProgress={(data.mediaInfo?.downloadStatus ?? []).length > 0}
|
inProgress={(data.mediaInfo?.downloadStatus ?? []).length > 0}
|
||||||
plexUrl={data.mediaInfo?.plexUrl}
|
plexUrl={data.mediaInfo?.plexUrl}
|
||||||
|
serviceUrl={
|
||||||
|
hasPermission(Permission.ADMIN)
|
||||||
|
? data.mediaInfo?.serviceUrl
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
{settings.currentSettings.movie4kEnabled &&
|
{settings.currentSettings.movie4kEnabled &&
|
||||||
hasPermission(
|
hasPermission(
|
||||||
@@ -434,7 +439,12 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
|||||||
inProgress={
|
inProgress={
|
||||||
(data.mediaInfo?.downloadStatus4k ?? []).length > 0
|
(data.mediaInfo?.downloadStatus4k ?? []).length > 0
|
||||||
}
|
}
|
||||||
plexUrl4k={data.mediaInfo?.plexUrl4k}
|
plexUrl={data.mediaInfo?.plexUrl4k}
|
||||||
|
serviceUrl={
|
||||||
|
hasPermission(Permission.ADMIN)
|
||||||
|
? data.mediaInfo?.serviceUrl4k
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@@ -292,8 +292,18 @@ const RequestCard: React.FC<RequestCardProps> = ({ request, onTitleData }) => {
|
|||||||
).length > 0
|
).length > 0
|
||||||
}
|
}
|
||||||
is4k={requestData.is4k}
|
is4k={requestData.is4k}
|
||||||
plexUrl={requestData.media.plexUrl}
|
plexUrl={
|
||||||
plexUrl4k={requestData.media.plexUrl4k}
|
requestData.is4k
|
||||||
|
? requestData.media.plexUrl4k
|
||||||
|
: requestData.media.plexUrl
|
||||||
|
}
|
||||||
|
serviceUrl={
|
||||||
|
hasPermission(Permission.ADMIN)
|
||||||
|
? requestData.is4k
|
||||||
|
? requestData.media.serviceUrl4k
|
||||||
|
: requestData.media.serviceUrl
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@@ -294,8 +294,18 @@ const RequestItem: React.FC<RequestItemProps> = ({
|
|||||||
).length > 0
|
).length > 0
|
||||||
}
|
}
|
||||||
is4k={requestData.is4k}
|
is4k={requestData.is4k}
|
||||||
plexUrl={requestData.media.plexUrl}
|
plexUrl={
|
||||||
plexUrl4k={requestData.media.plexUrl4k}
|
requestData.is4k
|
||||||
|
? requestData.media.plexUrl4k
|
||||||
|
: requestData.media.plexUrl
|
||||||
|
}
|
||||||
|
serviceUrl={
|
||||||
|
hasPermission(Permission.ADMIN)
|
||||||
|
? requestData.is4k
|
||||||
|
? requestData.media.serviceUrl4k
|
||||||
|
: requestData.media.serviceUrl
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@@ -6,6 +6,7 @@ import globalMessages from '../../i18n/globalMessages';
|
|||||||
import Badge from '../Common/Badge';
|
import Badge from '../Common/Badge';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
|
status: '{status}',
|
||||||
status4k: '4K {status}',
|
status4k: '4K {status}',
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -14,7 +15,7 @@ interface StatusBadgeProps {
|
|||||||
is4k?: boolean;
|
is4k?: boolean;
|
||||||
inProgress?: boolean;
|
inProgress?: boolean;
|
||||||
plexUrl?: string;
|
plexUrl?: string;
|
||||||
plexUrl4k?: string;
|
serviceUrl?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StatusBadge: React.FC<StatusBadgeProps> = ({
|
const StatusBadge: React.FC<StatusBadgeProps> = ({
|
||||||
@@ -22,158 +23,64 @@ const StatusBadge: React.FC<StatusBadgeProps> = ({
|
|||||||
is4k = false,
|
is4k = false,
|
||||||
inProgress = false,
|
inProgress = false,
|
||||||
plexUrl,
|
plexUrl,
|
||||||
plexUrl4k,
|
serviceUrl,
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
if (is4k) {
|
|
||||||
switch (status) {
|
|
||||||
case MediaStatus.AVAILABLE:
|
|
||||||
if (plexUrl4k) {
|
|
||||||
return (
|
|
||||||
<a href={plexUrl4k} target="_blank" rel="noopener noreferrer">
|
|
||||||
<Badge
|
|
||||||
badgeType="success"
|
|
||||||
className="transition !cursor-pointer hover:bg-green-400"
|
|
||||||
>
|
|
||||||
{intl.formatMessage(messages.status4k, {
|
|
||||||
status: intl.formatMessage(globalMessages.available),
|
|
||||||
})}
|
|
||||||
</Badge>
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Badge badgeType="success">
|
|
||||||
{intl.formatMessage(messages.status4k, {
|
|
||||||
status: intl.formatMessage(globalMessages.available),
|
|
||||||
})}
|
|
||||||
</Badge>
|
|
||||||
);
|
|
||||||
case MediaStatus.PARTIALLY_AVAILABLE:
|
|
||||||
if (plexUrl4k) {
|
|
||||||
return (
|
|
||||||
<a href={plexUrl4k} target="_blank" rel="noopener noreferrer">
|
|
||||||
<Badge
|
|
||||||
badgeType="success"
|
|
||||||
className="transition !cursor-pointer hover:bg-green-400"
|
|
||||||
>
|
|
||||||
{intl.formatMessage(messages.status4k, {
|
|
||||||
status: intl.formatMessage(globalMessages.partiallyavailable),
|
|
||||||
})}
|
|
||||||
</Badge>
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Badge badgeType="success">
|
|
||||||
{intl.formatMessage(messages.status4k, {
|
|
||||||
status: intl.formatMessage(globalMessages.partiallyavailable),
|
|
||||||
})}
|
|
||||||
</Badge>
|
|
||||||
);
|
|
||||||
case MediaStatus.PROCESSING:
|
|
||||||
return (
|
|
||||||
<Badge badgeType="primary">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<span>
|
|
||||||
{intl.formatMessage(messages.status4k, {
|
|
||||||
status: inProgress
|
|
||||||
? intl.formatMessage(globalMessages.processing)
|
|
||||||
: intl.formatMessage(globalMessages.requested),
|
|
||||||
})}
|
|
||||||
</span>
|
|
||||||
{inProgress && <Spinner className="w-3 h-3 ml-1" />}
|
|
||||||
</div>
|
|
||||||
</Badge>
|
|
||||||
);
|
|
||||||
case MediaStatus.PENDING:
|
|
||||||
return (
|
|
||||||
<Badge badgeType="warning">
|
|
||||||
{intl.formatMessage(messages.status4k, {
|
|
||||||
status: intl.formatMessage(globalMessages.pending),
|
|
||||||
})}
|
|
||||||
</Badge>
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case MediaStatus.AVAILABLE:
|
case MediaStatus.AVAILABLE:
|
||||||
if (plexUrl) {
|
|
||||||
return (
|
|
||||||
<a href={plexUrl} target="_blank" rel="noopener noreferrer">
|
|
||||||
<Badge
|
|
||||||
badgeType="success"
|
|
||||||
className="transition !cursor-pointer hover:bg-green-400"
|
|
||||||
>
|
|
||||||
<div className="flex items-center">
|
|
||||||
<span>{intl.formatMessage(globalMessages.available)}</span>
|
|
||||||
{inProgress && <Spinner className="w-3 h-3 ml-1" />}
|
|
||||||
</div>
|
|
||||||
</Badge>
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Badge badgeType="success">
|
<Badge badgeType="success" url={plexUrl}>
|
||||||
<div className="flex items-center">
|
|
||||||
<span>{intl.formatMessage(globalMessages.available)}</span>
|
|
||||||
{inProgress && <Spinner className="w-3 h-3 ml-1" />}
|
|
||||||
</div>
|
|
||||||
</Badge>
|
|
||||||
);
|
|
||||||
case MediaStatus.PARTIALLY_AVAILABLE:
|
|
||||||
if (plexUrl) {
|
|
||||||
return (
|
|
||||||
<a href={plexUrl} target="_blank" rel="noopener noreferrer">
|
|
||||||
<Badge
|
|
||||||
badgeType="success"
|
|
||||||
className="transition !cursor-pointer hover:bg-green-400"
|
|
||||||
>
|
|
||||||
<div className="flex items-center">
|
|
||||||
<span>
|
|
||||||
{intl.formatMessage(globalMessages.partiallyavailable)}
|
|
||||||
</span>
|
|
||||||
{inProgress && <Spinner className="w-3 h-3 ml-1" />}
|
|
||||||
</div>
|
|
||||||
</Badge>
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Badge badgeType="success">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<span>{intl.formatMessage(globalMessages.partiallyavailable)}</span>
|
|
||||||
{inProgress && <Spinner className="w-3 h-3 ml-1" />}
|
|
||||||
</div>
|
|
||||||
</Badge>
|
|
||||||
);
|
|
||||||
case MediaStatus.PROCESSING:
|
|
||||||
return (
|
|
||||||
<Badge badgeType="primary">
|
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<span>
|
<span>
|
||||||
{inProgress
|
{intl.formatMessage(is4k ? messages.status4k : messages.status, {
|
||||||
? intl.formatMessage(globalMessages.processing)
|
status: intl.formatMessage(globalMessages.available),
|
||||||
: intl.formatMessage(globalMessages.requested)}
|
})}
|
||||||
</span>
|
</span>
|
||||||
{inProgress && <Spinner className="w-3 h-3 ml-1" />}
|
{inProgress && <Spinner className="w-3 h-3 ml-1" />}
|
||||||
</div>
|
</div>
|
||||||
</Badge>
|
</Badge>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
case MediaStatus.PARTIALLY_AVAILABLE:
|
||||||
|
return (
|
||||||
|
<Badge badgeType="success" url={plexUrl}>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<span>
|
||||||
|
{intl.formatMessage(is4k ? messages.status4k : messages.status, {
|
||||||
|
status: intl.formatMessage(globalMessages.partiallyavailable),
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
{inProgress && <Spinner className="w-3 h-3 ml-1" />}
|
||||||
|
</div>
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
|
||||||
|
case MediaStatus.PROCESSING:
|
||||||
|
return (
|
||||||
|
<Badge badgeType="primary" url={serviceUrl}>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<span>
|
||||||
|
{intl.formatMessage(is4k ? messages.status4k : messages.status, {
|
||||||
|
status: inProgress
|
||||||
|
? intl.formatMessage(globalMessages.processing)
|
||||||
|
: intl.formatMessage(globalMessages.requested),
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
{inProgress && <Spinner className="w-3 h-3 ml-1" />}
|
||||||
|
</div>
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
|
||||||
case MediaStatus.PENDING:
|
case MediaStatus.PENDING:
|
||||||
return (
|
return (
|
||||||
<Badge badgeType="warning">
|
<Badge badgeType="warning">
|
||||||
{intl.formatMessage(globalMessages.pending)}
|
{intl.formatMessage(is4k ? messages.status4k : messages.status, {
|
||||||
|
status: intl.formatMessage(globalMessages.pending),
|
||||||
|
})}
|
||||||
</Badge>
|
</Badge>
|
||||||
);
|
);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@@ -430,6 +430,11 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
|
|||||||
status={data.mediaInfo?.status}
|
status={data.mediaInfo?.status}
|
||||||
inProgress={(data.mediaInfo?.downloadStatus ?? []).length > 0}
|
inProgress={(data.mediaInfo?.downloadStatus ?? []).length > 0}
|
||||||
plexUrl={data.mediaInfo?.plexUrl}
|
plexUrl={data.mediaInfo?.plexUrl}
|
||||||
|
serviceUrl={
|
||||||
|
hasPermission(Permission.ADMIN)
|
||||||
|
? data.mediaInfo?.serviceUrl
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
{settings.currentSettings.series4kEnabled &&
|
{settings.currentSettings.series4kEnabled &&
|
||||||
hasPermission([Permission.REQUEST_4K, Permission.REQUEST_4K_TV], {
|
hasPermission([Permission.REQUEST_4K, Permission.REQUEST_4K_TV], {
|
||||||
@@ -441,7 +446,12 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
|
|||||||
inProgress={
|
inProgress={
|
||||||
(data.mediaInfo?.downloadStatus4k ?? []).length > 0
|
(data.mediaInfo?.downloadStatus4k ?? []).length > 0
|
||||||
}
|
}
|
||||||
plexUrl4k={data.mediaInfo?.plexUrl4k}
|
plexUrl={data.mediaInfo?.plexUrl4k}
|
||||||
|
serviceUrl={
|
||||||
|
hasPermission(Permission.ADMIN)
|
||||||
|
? data.mediaInfo?.serviceUrl4k
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@@ -673,6 +673,7 @@
|
|||||||
"components.Setup.signinMessage": "Get started by signing in with your Plex account",
|
"components.Setup.signinMessage": "Get started by signing in with your Plex account",
|
||||||
"components.Setup.tip": "Tip",
|
"components.Setup.tip": "Tip",
|
||||||
"components.Setup.welcome": "Welcome to Overseerr",
|
"components.Setup.welcome": "Welcome to Overseerr",
|
||||||
|
"components.StatusBadge.status": "{status}",
|
||||||
"components.StatusBadge.status4k": "4K {status}",
|
"components.StatusBadge.status4k": "4K {status}",
|
||||||
"components.StatusChacker.newversionDescription": "Overseerr has been updated! Please click the button below to reload the page.",
|
"components.StatusChacker.newversionDescription": "Overseerr has been updated! Please click the button below to reload the page.",
|
||||||
"components.StatusChacker.newversionavailable": "Application Update",
|
"components.StatusChacker.newversionavailable": "Application Update",
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
@tailwind base;
|
@tailwind base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
@tailwind screens;
|
|
||||||
|
|
||||||
html {
|
html {
|
||||||
min-height: calc(100% + env(safe-area-inset-top));
|
min-height: calc(100% + env(safe-area-inset-top));
|
||||||
@@ -182,7 +181,7 @@ a.crew-name,
|
|||||||
}
|
}
|
||||||
|
|
||||||
.media-ratings {
|
.media-ratings {
|
||||||
@apply flex items-center justify-center px-4 py-2 border-b border-gray-700 last:border-b-0;
|
@apply flex items-center justify-center px-4 py-2 font-medium border-b border-gray-700 last:border-b-0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.media-rating {
|
.media-rating {
|
||||||
@@ -213,6 +212,10 @@ img.avatar-sm {
|
|||||||
@apply mr-2 font-bold;
|
@apply mr-2 font-bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card-field a {
|
||||||
|
@apply transition duration-300 hover:text-white hover:underline;
|
||||||
|
}
|
||||||
|
|
||||||
.section {
|
.section {
|
||||||
@apply mt-6 mb-10 text-white;
|
@apply mt-6 mb-10 text-white;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user