import Alert from '@app/components/Common/Alert'; import Modal from '@app/components/Common/Modal'; import type { RequestOverrides } from '@app/components/RequestModal/AdvancedRequester'; import AdvancedRequester from '@app/components/RequestModal/AdvancedRequester'; import QuotaDisplay from '@app/components/RequestModal/QuotaDisplay'; import { useUser } from '@app/hooks/useUser'; import globalMessages from '@app/i18n/globalMessages'; import defineMessages from '@app/utils/defineMessages'; import { MediaStatus } from '@server/constants/media'; import type { MediaRequest } from '@server/entity/MediaRequest'; import type { NonFunctionProperties } from '@server/interfaces/api/common'; import type { QuotaResponse } from '@server/interfaces/api/userInterfaces'; import { Permission } from '@server/lib/permissions'; import type { MovieDetails } from '@server/models/Movie'; import { useCallback, useEffect, useState } from 'react'; import { useIntl } from 'react-intl'; import { useToasts } from 'react-toast-notifications'; import useSWR, { mutate } from 'swr'; const messages = defineMessages('components.RequestModal', { requestadmin: 'This request will be approved automatically.', requestSuccess: '{title} requested successfully!', requestCancel: 'Request for {title} canceled.', requestmovietitle: 'Request Movie', requestmovie4ktitle: 'Request Movie in 4K', edit: 'Edit Request', approve: 'Approve Request', cancel: 'Cancel Request', pendingrequest: 'Pending Movie Request', pending4krequest: 'Pending 4K Movie Request', requestfrom: "{username}'s request is pending approval.", errorediting: 'Something went wrong while editing the request.', requestedited: 'Request for {title} edited successfully!', requestApproved: 'Request for {title} approved!', requesterror: 'Something went wrong while submitting the request.', pendingapproval: 'Your request is pending approval.', }); interface RequestModalProps extends React.HTMLAttributes { tmdbId: number; is4k?: boolean; editRequest?: NonFunctionProperties; onCancel?: () => void; onComplete?: (newStatus: MediaStatus) => void; onUpdating?: (isUpdating: boolean) => void; } const MovieRequestModal = ({ onCancel, onComplete, tmdbId, onUpdating, editRequest, is4k = false, }: RequestModalProps) => { const [isUpdating, setIsUpdating] = useState(false); const [requestOverrides, setRequestOverrides] = useState(null); const { addToast } = useToasts(); const { data, error } = useSWR(`/api/v1/movie/${tmdbId}`, { revalidateOnMount: true, }); const intl = useIntl(); const { user, hasPermission } = useUser(); const { data: quota } = useSWR( user && (!requestOverrides?.user?.id || hasPermission(Permission.MANAGE_USERS)) ? `/api/v1/user/${requestOverrides?.user?.id ?? user.id}/quota` : null ); useEffect(() => { if (onUpdating) { onUpdating(isUpdating); } }, [isUpdating, onUpdating]); const sendRequest = useCallback(async () => { setIsUpdating(true); try { let overrideParams = {}; if (requestOverrides) { overrideParams = { serverId: requestOverrides.server, profileId: requestOverrides.profile, rootFolder: requestOverrides.folder, userId: requestOverrides.user?.id, tags: requestOverrides.tags, }; } const res = await fetch('/api/v1/request', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ mediaId: data?.id, mediaType: 'movie', is4k, ...overrideParams, }), }); if (!res.ok) throw new Error(); const mediaRequest: MediaRequest = await res.json(); mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0'); mutate('/api/v1/request/count'); if (mediaRequest) { if (onComplete) { onComplete( hasPermission( is4k ? Permission.AUTO_APPROVE_4K : Permission.AUTO_APPROVE ) || hasPermission( is4k ? Permission.AUTO_APPROVE_4K_MOVIE : Permission.AUTO_APPROVE_MOVIE ) ? MediaStatus.PROCESSING : MediaStatus.PENDING ); } addToast( {intl.formatMessage(messages.requestSuccess, { title: data?.title, strong: (msg: React.ReactNode) => {msg}, })} , { appearance: 'success', autoDismiss: true } ); } } catch (e) { addToast(intl.formatMessage(messages.requesterror), { appearance: 'error', autoDismiss: true, }); } finally { setIsUpdating(false); } }, [ requestOverrides, data?.id, data?.title, is4k, onComplete, addToast, intl, hasPermission, ]); const cancelRequest = async () => { setIsUpdating(true); try { const res = await fetch(`/api/v1/request/${editRequest?.id}`, { method: 'DELETE', }); if (!res.ok) throw new Error(); mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0'); mutate('/api/v1/request/count'); if (res.status === 204) { if (onComplete) { onComplete(MediaStatus.UNKNOWN); } addToast( {intl.formatMessage(messages.requestCancel, { title: data?.title, strong: (msg: React.ReactNode) => {msg}, })} , { appearance: 'success', autoDismiss: true } ); } } catch (e) { setIsUpdating(false); } }; const updateRequest = async (alsoApproveRequest = false) => { setIsUpdating(true); try { const res = await fetch(`/api/v1/request/${editRequest?.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ mediaType: 'movie', serverId: requestOverrides?.server, profileId: requestOverrides?.profile, rootFolder: requestOverrides?.folder, userId: requestOverrides?.user?.id, tags: requestOverrides?.tags, }), }); if (!res.ok) throw new Error(); if (alsoApproveRequest) { const res = await fetch(`/api/v1/request/${editRequest?.id}/approve`, { method: 'POST', }); if (!res.ok) throw new Error(); } mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0'); mutate('/api/v1/request/count'); addToast( {intl.formatMessage( alsoApproveRequest ? messages.requestApproved : messages.requestedited, { title: data?.title, strong: (msg: React.ReactNode) => {msg}, } )} , { appearance: 'success', autoDismiss: true, } ); if (onComplete) { onComplete(MediaStatus.PENDING); } } catch (e) { addToast({intl.formatMessage(messages.errorediting)}, { appearance: 'error', autoDismiss: true, }); } finally { setIsUpdating(false); } }; if (editRequest) { const isOwner = editRequest.requestedBy.id === user?.id; return ( hasPermission(Permission.MANAGE_REQUESTS) ? updateRequest(true) : hasPermission(Permission.REQUEST_ADVANCED) ? updateRequest() : cancelRequest() } okDisabled={isUpdating} okText={ hasPermission(Permission.MANAGE_REQUESTS) ? intl.formatMessage(messages.approve) : hasPermission(Permission.REQUEST_ADVANCED) ? intl.formatMessage(messages.edit) : intl.formatMessage(messages.cancel) } okButtonType={ hasPermission(Permission.MANAGE_REQUESTS) ? 'success' : hasPermission(Permission.REQUEST_ADVANCED) ? 'primary' : 'danger' } onSecondary={ isOwner && hasPermission( [Permission.REQUEST_ADVANCED, Permission.MANAGE_REQUESTS], { type: 'or' } ) ? () => cancelRequest() : undefined } secondaryDisabled={isUpdating} secondaryText={ isOwner && hasPermission( [Permission.REQUEST_ADVANCED, Permission.MANAGE_REQUESTS], { type: 'or' } ) ? intl.formatMessage(messages.cancel) : undefined } secondaryButtonType="danger" cancelText={intl.formatMessage(globalMessages.close)} backdrop={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${data?.backdropPath}`} > {isOwner ? intl.formatMessage(messages.pendingapproval) : intl.formatMessage(messages.requestfrom, { username: editRequest.requestedBy.displayName, })} {(hasPermission(Permission.REQUEST_ADVANCED) || hasPermission(Permission.MANAGE_REQUESTS)) && ( { setRequestOverrides(overrides); }} /> )} ); } const hasAutoApprove = hasPermission( [ Permission.MANAGE_REQUESTS, is4k ? Permission.AUTO_APPROVE_4K : Permission.AUTO_APPROVE, is4k ? Permission.AUTO_APPROVE_4K_MOVIE : Permission.AUTO_APPROVE_MOVIE, ], { type: 'or' } ); return ( {hasAutoApprove && !quota?.movie.restricted && (
)} {(quota?.movie.limit ?? 0) > 0 && ( )} {(hasPermission(Permission.REQUEST_ADVANCED) || hasPermission(Permission.MANAGE_REQUESTS)) && ( { setRequestOverrides(overrides); }} /> )}
); }; export default MovieRequestModal;