mirror of
https://github.com/sct/overseerr.git
synced 2025-09-17 17:24:35 +02:00
feat(frontend): cancel movie request modal
also includes tons of performance fixes for the modals
This commit is contained in:
1
src/assets/download.svg
Normal file
1
src/assets/download.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M3 17a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm3.293-7.707a1 1 0 011.414 0L9 10.586V3a1 1 0 112 0v7.586l1.293-1.293a1 1 0 111.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z" clip-rule="evenodd"></path></svg>
|
After Width: | Height: | Size: 326 B |
@@ -14,10 +14,10 @@ const Badge: React.FC<BadgeProps> = ({ badgeType = 'default', children }) => {
|
||||
badgeStyle.push('bg-red-600 text-red-100');
|
||||
break;
|
||||
case 'warning':
|
||||
badgeStyle.push('bg-orange-400 text-orange-100');
|
||||
badgeStyle.push('bg-orange-500 text-orange-100');
|
||||
break;
|
||||
case 'success':
|
||||
badgeStyle.push('bg-green-500 text-green-100');
|
||||
badgeStyle.push('bg-green-400 text-green-100');
|
||||
break;
|
||||
default:
|
||||
badgeStyle.push('bg-indigo-500 text-indigo-100');
|
||||
|
@@ -6,16 +6,23 @@ import { useLockBodyScroll } from '../../../hooks/useLockBodyScroll';
|
||||
import LoadingSpinner from '../LoadingSpinner';
|
||||
import useClickOutside from '../../../hooks/useClickOutside';
|
||||
|
||||
interface ModalProps {
|
||||
interface ModalProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
title?: string;
|
||||
onCancel?: (e?: MouseEvent<HTMLElement>) => void;
|
||||
onOk?: (e: MouseEvent<HTMLButtonElement>) => void;
|
||||
onOk?: (e?: MouseEvent<HTMLButtonElement>) => void;
|
||||
onSecondary?: (e?: MouseEvent<HTMLButtonElement>) => void;
|
||||
onTertiary?: (e?: MouseEvent<HTMLButtonElement>) => void;
|
||||
cancelText?: string;
|
||||
okText?: string;
|
||||
secondaryText?: string;
|
||||
tertiaryText?: string;
|
||||
okDisabled?: boolean;
|
||||
cancelButtonType?: ButtonType;
|
||||
okButtonType?: ButtonType;
|
||||
visible?: boolean;
|
||||
secondaryButtonType?: ButtonType;
|
||||
secondaryDisabled?: boolean;
|
||||
tertiaryDisabled?: boolean;
|
||||
tertiaryButtonType?: ButtonType;
|
||||
disableScrollLock?: boolean;
|
||||
backgroundClickable?: boolean;
|
||||
iconSvg?: ReactNode;
|
||||
@@ -29,14 +36,22 @@ const Modal: React.FC<ModalProps> = ({
|
||||
cancelText,
|
||||
okText,
|
||||
okDisabled = false,
|
||||
cancelButtonType,
|
||||
okButtonType,
|
||||
cancelButtonType = 'default',
|
||||
okButtonType = 'primary',
|
||||
children,
|
||||
visible,
|
||||
disableScrollLock,
|
||||
backgroundClickable = true,
|
||||
iconSvg,
|
||||
loading = false,
|
||||
secondaryButtonType = 'default',
|
||||
secondaryDisabled = false,
|
||||
onSecondary,
|
||||
secondaryText,
|
||||
tertiaryButtonType = 'default',
|
||||
tertiaryDisabled = false,
|
||||
tertiaryText,
|
||||
onTertiary,
|
||||
...props
|
||||
}) => {
|
||||
const modalRef = useRef<HTMLDivElement>(null);
|
||||
useClickOutside(modalRef, () => {
|
||||
@@ -44,39 +59,26 @@ const Modal: React.FC<ModalProps> = ({
|
||||
? onCancel()
|
||||
: undefined;
|
||||
});
|
||||
useLockBodyScroll(!!visible, disableScrollLock);
|
||||
const transitions = useTransition(visible, null, {
|
||||
from: { opacity: 0, backdropFilter: 'blur(0px)' },
|
||||
enter: { opacity: 1, backdropFilter: 'blur(3px)' },
|
||||
leave: { opacity: 0, backdropFilter: 'blur(0px)' },
|
||||
config: { tension: 500, velocity: 40, friction: 60 },
|
||||
});
|
||||
const containerTransitions = useTransition(visible && !loading, null, {
|
||||
useLockBodyScroll(true, disableScrollLock);
|
||||
const containerTransitions = useTransition(!loading, null, {
|
||||
from: { opacity: 0, transform: 'scale(0.5)' },
|
||||
enter: { opacity: 1, transform: 'scale(1)' },
|
||||
leave: { opacity: 0, transform: 'scale(0.5)' },
|
||||
config: { tension: 500, velocity: 40, friction: 60 },
|
||||
});
|
||||
const loadingTransitions = useTransition(visible && loading, null, {
|
||||
const loadingTransitions = useTransition(loading, null, {
|
||||
from: { opacity: 0, transform: 'scale(0.5)' },
|
||||
enter: { opacity: 1, transform: 'scale(1)' },
|
||||
leave: { opacity: 0, transform: 'scale(0.5)' },
|
||||
config: { tension: 500, velocity: 40, friction: 60 },
|
||||
});
|
||||
|
||||
const cancelType = cancelButtonType ?? 'default';
|
||||
const okType = okButtonType ?? 'primary';
|
||||
|
||||
return (
|
||||
<>
|
||||
{transitions.map(
|
||||
({ props, item, key }) =>
|
||||
item &&
|
||||
ReactDOM.createPortal(
|
||||
{ReactDOM.createPortal(
|
||||
<animated.div
|
||||
className="fixed top-0 left-0 right-0 bottom-0 bg-cool-gray-800 bg-opacity-50 w-full h-full z-50 flex justify-center items-center"
|
||||
style={props}
|
||||
key={key}
|
||||
style={props.style}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Escape') {
|
||||
typeof onCancel === 'function' && backgroundClickable
|
||||
@@ -132,11 +134,11 @@ const Modal: React.FC<ModalProps> = ({
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{(onCancel || onOk) && (
|
||||
{(onCancel || onOk || onSecondary || onTertiary) && (
|
||||
<div className="mt-5 sm:mt-4 flex justify-center sm:justify-start flex-row-reverse">
|
||||
{typeof onOk === 'function' && (
|
||||
<Button
|
||||
buttonType={okType}
|
||||
buttonType={okButtonType}
|
||||
onClick={onOk}
|
||||
className="ml-3"
|
||||
disabled={okDisabled}
|
||||
@@ -144,9 +146,29 @@ const Modal: React.FC<ModalProps> = ({
|
||||
{okText ? okText : 'Ok'}
|
||||
</Button>
|
||||
)}
|
||||
{typeof onSecondary === 'function' && secondaryText && (
|
||||
<Button
|
||||
buttonType={secondaryButtonType}
|
||||
onClick={onSecondary}
|
||||
className="ml-3"
|
||||
disabled={secondaryDisabled}
|
||||
>
|
||||
{secondaryText}
|
||||
</Button>
|
||||
)}
|
||||
{typeof onTertiary === 'function' && tertiaryText && (
|
||||
<Button
|
||||
buttonType={tertiaryButtonType}
|
||||
onClick={onTertiary}
|
||||
className="ml-3"
|
||||
disabled={tertiaryDisabled}
|
||||
>
|
||||
{tertiaryText}
|
||||
</Button>
|
||||
)}
|
||||
{typeof onCancel === 'function' && (
|
||||
<Button
|
||||
buttonType={cancelType}
|
||||
buttonType={cancelButtonType}
|
||||
onClick={onCancel}
|
||||
className="ml-3 sm:ml-0 sm:px-4"
|
||||
>
|
||||
@@ -160,7 +182,6 @@ const Modal: React.FC<ModalProps> = ({
|
||||
)}
|
||||
</animated.div>,
|
||||
document.body
|
||||
)
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
@@ -38,6 +38,7 @@ const messages = defineMessages({
|
||||
available: 'Available',
|
||||
unavailable: 'Unavailable',
|
||||
request: 'Request',
|
||||
viewrequest: 'View Request',
|
||||
pending: 'Pending',
|
||||
overviewunavailable: 'Overview unavailable',
|
||||
});
|
||||
@@ -53,13 +54,6 @@ interface SearchResult {
|
||||
results: MovieResult[];
|
||||
}
|
||||
|
||||
enum MediaRequestStatus {
|
||||
PENDING = 1,
|
||||
APPROVED,
|
||||
DECLINED,
|
||||
AVAILABLE,
|
||||
}
|
||||
|
||||
const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
||||
const { hasPermission } = useUser();
|
||||
const router = useRouter();
|
||||
@@ -87,6 +81,11 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
||||
return <div>Broken?</div>;
|
||||
}
|
||||
|
||||
console.log(MediaStatus);
|
||||
console.log(data);
|
||||
|
||||
const activeRequest = data?.mediaInfo?.requests?.[0];
|
||||
|
||||
return (
|
||||
<div
|
||||
className="bg-cover bg-center -mx-4 -mt-2 px-4 sm:px-8 pt-4 "
|
||||
@@ -144,11 +143,26 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
||||
</div>
|
||||
<div className="flex-1 flex justify-end mt-4 md:mt-0">
|
||||
{(!data.mediaInfo ||
|
||||
data.mediaInfo?.status === MediaStatus.UNKNOWN) && (
|
||||
data.mediaInfo?.status === MediaStatus.UNKNOWN ||
|
||||
activeRequest) && (
|
||||
<Button
|
||||
buttonType="primary"
|
||||
onClick={() => setShowRequestModal(true)}
|
||||
>
|
||||
{activeRequest ? (
|
||||
<svg
|
||||
className="w-4 mr-1"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
) : (
|
||||
<svg
|
||||
className="w-4 mr-1"
|
||||
fill="none"
|
||||
@@ -163,83 +177,12 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
||||
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
|
||||
/>
|
||||
</svg>
|
||||
<FormattedMessage {...messages.request} />
|
||||
)}
|
||||
<FormattedMessage
|
||||
{...(activeRequest ? messages.viewrequest : messages.request)}
|
||||
/>
|
||||
</Button>
|
||||
)}
|
||||
{data.mediaInfo?.status === MediaStatus.PENDING && (
|
||||
<Button buttonType="warning">
|
||||
<svg
|
||||
className="w-4 mr-2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"
|
||||
/>
|
||||
</svg>
|
||||
<FormattedMessage {...messages.pending} />
|
||||
</Button>
|
||||
)}
|
||||
{data.mediaInfo?.status === MediaStatus.PROCESSING && (
|
||||
<Button buttonType="danger">
|
||||
<svg
|
||||
className="w-5 mr-1"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
<FormattedMessage {...messages.unavailable} />
|
||||
</Button>
|
||||
)}
|
||||
{data.mediaInfo?.status === MediaStatus.AVAILABLE && (
|
||||
<Button buttonType="success">
|
||||
<svg
|
||||
className="w-5 mr-1"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
<FormattedMessage {...messages.available} />
|
||||
</Button>
|
||||
)}
|
||||
<Button buttonType="danger" className="ml-2">
|
||||
<svg
|
||||
className="w-5"
|
||||
style={{ height: 20 }}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
</Button>
|
||||
{hasPermission(Permission.MANAGE_REQUESTS) && (
|
||||
<Button buttonType="default" className="ml-2">
|
||||
<svg
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import Modal from '../Common/Modal';
|
||||
import { useUser } from '../../hooks/useUser';
|
||||
import { Permission } from '../../../server/lib/permissions';
|
||||
@@ -8,7 +8,11 @@ import useSWR from 'swr';
|
||||
import { MovieDetails } from '../../../server/models/Movie';
|
||||
import { useToasts } from 'react-toast-notifications';
|
||||
import axios from 'axios';
|
||||
import type { MediaStatus } from '../../../server/constants/media';
|
||||
import {
|
||||
MediaStatus,
|
||||
MediaRequestStatus,
|
||||
} from '../../../server/constants/media';
|
||||
import DownloadIcon from '../../assets/download.svg';
|
||||
|
||||
const messages = defineMessages({
|
||||
requestadmin:
|
||||
@@ -17,29 +21,28 @@ const messages = defineMessages({
|
||||
'This will remove your request. Are you sure you want to continue?',
|
||||
});
|
||||
|
||||
interface RequestModalProps {
|
||||
request?: MediaRequest;
|
||||
interface RequestModalProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
tmdbId: number;
|
||||
visible?: boolean;
|
||||
onCancel?: () => void;
|
||||
onComplete?: (newStatus: MediaStatus) => void;
|
||||
onUpdating?: (isUpdating: boolean) => void;
|
||||
}
|
||||
|
||||
const MovieRequestModal: React.FC<RequestModalProps> = ({
|
||||
visible,
|
||||
onCancel,
|
||||
onComplete,
|
||||
request,
|
||||
tmdbId,
|
||||
onUpdating,
|
||||
...props
|
||||
}) => {
|
||||
const { addToast } = useToasts();
|
||||
const { data, error } = useSWR<MovieDetails>(`/api/v1/movie/${tmdbId}`);
|
||||
const { data, error } = useSWR<MovieDetails>(`/api/v1/movie/${tmdbId}`, {
|
||||
revalidateOnMount: true,
|
||||
});
|
||||
const intl = useIntl();
|
||||
const { hasPermission } = useUser();
|
||||
const { user, hasPermission } = useUser();
|
||||
|
||||
const sendRequest = async () => {
|
||||
const sendRequest = useCallback(async () => {
|
||||
if (onUpdating) {
|
||||
onUpdating(true);
|
||||
}
|
||||
@@ -62,59 +65,76 @@ const MovieRequestModal: React.FC<RequestModalProps> = ({
|
||||
onUpdating(false);
|
||||
}
|
||||
}
|
||||
}, [data, onComplete, onUpdating, addToast]);
|
||||
|
||||
const activeRequest = data?.mediaInfo?.requests?.[0];
|
||||
|
||||
console.log(activeRequest);
|
||||
|
||||
const cancelRequest = async () => {
|
||||
if (onUpdating) {
|
||||
onUpdating(true);
|
||||
}
|
||||
const response = await axios.delete<MediaRequest>(
|
||||
`/api/v1/request/${activeRequest?.id}`
|
||||
);
|
||||
|
||||
if (response.data) {
|
||||
if (onComplete) {
|
||||
onComplete(MediaStatus.UNKNOWN);
|
||||
}
|
||||
addToast(
|
||||
<span>
|
||||
<strong>{data?.title}</strong> request cancelled!
|
||||
</span>,
|
||||
{ appearance: 'success', autoDismiss: true }
|
||||
);
|
||||
if (onUpdating) {
|
||||
onUpdating(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let text = hasPermission(Permission.MANAGE_REQUESTS)
|
||||
const isOwner = activeRequest
|
||||
? activeRequest.requestedBy.id === user?.id ||
|
||||
hasPermission(Permission.MANAGE_REQUESTS)
|
||||
: false;
|
||||
|
||||
const text = hasPermission(Permission.MANAGE_REQUESTS)
|
||||
? intl.formatMessage(messages.requestadmin)
|
||||
: undefined;
|
||||
|
||||
if (request) {
|
||||
text = intl.formatMessage(messages.cancelrequest);
|
||||
if (activeRequest?.status === MediaRequestStatus.PENDING) {
|
||||
return (
|
||||
<Modal
|
||||
loading={!data && !error}
|
||||
backgroundClickable
|
||||
onCancel={onCancel}
|
||||
onOk={isOwner ? () => cancelRequest() : undefined}
|
||||
title={`Pending request for ${data?.title}`}
|
||||
okText={'Cancel Request'}
|
||||
okButtonType={'danger'}
|
||||
cancelText="Close"
|
||||
iconSvg={<DownloadIcon className="w-6 h-6" />}
|
||||
{...props}
|
||||
>
|
||||
There is currently a pending request from{' '}
|
||||
<strong>{activeRequest.requestedBy.username}</strong>.
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={visible}
|
||||
loading={!data && !error}
|
||||
backgroundClickable
|
||||
onCancel={onCancel}
|
||||
onOk={() => sendRequest()}
|
||||
title={!request ? `Request ${data?.title}` : 'Cancel Request'}
|
||||
okText={!request ? 'Request' : 'Cancel Request'}
|
||||
okButtonType={!!request ? 'danger' : 'primary'}
|
||||
iconSvg={
|
||||
!request ? (
|
||||
<svg
|
||||
className="w-6 h-6"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
|
||||
/>
|
||||
</svg>
|
||||
) : (
|
||||
<svg
|
||||
className="w-6 h-6"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
onOk={sendRequest}
|
||||
title={`Request ${data?.title}`}
|
||||
okText={'Request'}
|
||||
okButtonType={'primary'}
|
||||
iconSvg={<DownloadIcon className="w-6 h-6" />}
|
||||
{...props}
|
||||
>
|
||||
{text}
|
||||
</Modal>
|
||||
|
@@ -21,27 +21,22 @@ const messages = defineMessages({
|
||||
'This will remove your request. Are you sure you want to continue?',
|
||||
});
|
||||
|
||||
interface RequestModalProps {
|
||||
request?: MediaRequest;
|
||||
interface RequestModalProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
tmdbId: number;
|
||||
visible?: boolean;
|
||||
onCancel?: () => void;
|
||||
onComplete?: (newStatus: MediaStatus) => void;
|
||||
onUpdating?: (isUpdating: boolean) => void;
|
||||
}
|
||||
|
||||
const TvRequestModal: React.FC<RequestModalProps> = ({
|
||||
visible,
|
||||
onCancel,
|
||||
onComplete,
|
||||
request,
|
||||
tmdbId,
|
||||
onUpdating,
|
||||
...props
|
||||
}) => {
|
||||
const { addToast } = useToasts();
|
||||
const { data, error } = useSWR<TvDetails>(
|
||||
visible ? `/api/v1/tv/${tmdbId}` : null
|
||||
);
|
||||
const { data, error } = useSWR<TvDetails>(`/api/v1/tv/${tmdbId}`);
|
||||
const [selectedSeasons, setSelectedSeasons] = useState<number[]>([]);
|
||||
const intl = useIntl();
|
||||
const { hasPermission } = useUser();
|
||||
@@ -164,7 +159,6 @@ const TvRequestModal: React.FC<RequestModalProps> = ({
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={visible}
|
||||
loading={!data && !error}
|
||||
backgroundClickable
|
||||
onCancel={onCancel}
|
||||
@@ -193,6 +187,7 @@ const TvRequestModal: React.FC<RequestModalProps> = ({
|
||||
/>
|
||||
</svg>
|
||||
}
|
||||
{...props}
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
<div className="-mx-4 sm:mx-0 overflow-auto max-h-96">
|
||||
|
@@ -4,6 +4,7 @@ import MovieRequestModal from './MovieRequestModal';
|
||||
import type { MediaRequest } from '../../../server/entity/MediaRequest';
|
||||
import type { MediaStatus } from '../../../server/constants/media';
|
||||
import TvRequestModal from './TvRequestModal';
|
||||
import { useTransition, animated } from 'react-spring';
|
||||
|
||||
interface RequestModalProps {
|
||||
show: boolean;
|
||||
@@ -24,26 +25,49 @@ const RequestModal: React.FC<RequestModalProps> = ({
|
||||
onUpdating,
|
||||
onCancel,
|
||||
}) => {
|
||||
const transitions = useTransition(show, null, {
|
||||
from: { opacity: 0, backdropFilter: 'blur(0px)' },
|
||||
enter: { opacity: 1, backdropFilter: 'blur(3px)' },
|
||||
leave: { opacity: 0, backdropFilter: 'blur(0px)' },
|
||||
config: { tension: 500, velocity: 40, friction: 60 },
|
||||
});
|
||||
|
||||
if (type === 'tv') {
|
||||
return (
|
||||
<>
|
||||
{transitions.map(
|
||||
({ props, item, key }) =>
|
||||
item && (
|
||||
<TvRequestModal
|
||||
onComplete={onComplete}
|
||||
onCancel={onCancel}
|
||||
visible={show}
|
||||
tmdbId={tmdbId}
|
||||
onUpdating={onUpdating}
|
||||
style={props}
|
||||
key={key}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{transitions.map(
|
||||
({ props, item, key }) =>
|
||||
item && (
|
||||
<MovieRequestModal
|
||||
onComplete={onComplete}
|
||||
onCancel={onCancel}
|
||||
visible={show}
|
||||
tmdbId={tmdbId}
|
||||
onUpdating={onUpdating}
|
||||
style={props}
|
||||
key={key}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import type { MediaType } from '../../../server/models/Search';
|
||||
import Available from '../../assets/available.svg';
|
||||
import Requested from '../../assets/requested.svg';
|
||||
@@ -42,18 +42,27 @@ const TitleCard: React.FC<TitleCardProps> = ({
|
||||
year = year.slice(0, 4);
|
||||
}
|
||||
|
||||
const requestComplete = useCallback((newStatus: MediaStatus) => {
|
||||
setCurrentStatus(newStatus);
|
||||
setShowRequestModal(false);
|
||||
}, []);
|
||||
|
||||
const requestUpdating = useCallback(
|
||||
(status: boolean) => setIsUpdating(status),
|
||||
[]
|
||||
);
|
||||
|
||||
const closeModal = useCallback(() => setShowRequestModal(false), []);
|
||||
|
||||
return (
|
||||
<div className="w-36 sm:w-36 md:w-44">
|
||||
<RequestModal
|
||||
tmdbId={id}
|
||||
show={showRequestModal}
|
||||
type={mediaType === 'movie' ? 'movie' : 'tv'}
|
||||
onComplete={(newStatus) => {
|
||||
setCurrentStatus(newStatus);
|
||||
setShowRequestModal(false);
|
||||
}}
|
||||
onUpdating={(status) => setIsUpdating(status)}
|
||||
onCancel={() => setShowRequestModal(false)}
|
||||
onComplete={requestComplete}
|
||||
onUpdating={requestUpdating}
|
||||
onCancel={closeModal}
|
||||
/>
|
||||
<div
|
||||
className="titleCard outline-none cursor-default"
|
||||
|
@@ -150,23 +150,6 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
|
||||
<FormattedMessage {...messages.request} />
|
||||
</Button>
|
||||
)}
|
||||
<Button buttonType="danger" className="ml-2">
|
||||
<svg
|
||||
className="w-5"
|
||||
style={{ height: 20 }}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
</Button>
|
||||
{hasPermission(Permission.MANAGE_REQUESTS) && (
|
||||
<Button buttonType="default" className="ml-2">
|
||||
<svg
|
||||
|
Reference in New Issue
Block a user