Skip to content

Commit

Permalink
feat(requests): add edit button to request block and request list items
Browse files Browse the repository at this point in the history
Also adds route to update movie requests
  • Loading branch information
sct committed Jan 16, 2021
1 parent 6392fd5 commit b96f0d7
Show file tree
Hide file tree
Showing 12 changed files with 421 additions and 39 deletions.
20 changes: 20 additions & 0 deletions overseerr-api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2507,6 +2507,26 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/MediaRequest'
put:
summary: Update a specific MediaRequest
description: Updats a specific media request and returns the request in JSON format. Requires the `MANAGE_REQUESTS` permission.
tags:
- request
parameters:
- in: path
name: requestId
description: Request ID
required: true
example: 1
schema:
type: string
responses:
'200':
description: Succesfully updated request
content:
application/json:
schema:
$ref: '#/components/schemas/MediaRequest'
delete:
summary: Delete a request
description: Removes a request. If the user has the `MANAGE_REQUESTS` permission, then any request can be removed. Otherwise, only pending requests can be removed.
Expand Down
29 changes: 29 additions & 0 deletions server/routes/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,35 @@ requestRoutes.get('/:requestId', async (req, res, next) => {
}
});

requestRoutes.put<{ requestId: string }>(
'/:requestId',
isAuthenticated(Permission.MANAGE_REQUESTS),
async (req, res, next) => {
const requestRepository = getRepository(MediaRequest);
try {
const request = await requestRepository.findOne(
Number(req.params.requestId)
);

if (!request) {
return next({ status: 404, message: 'Request not found' });
}

if (req.body.mediaType === 'movie') {
request.serverId = req.body.serverId;
request.profileId = req.body.profileId;
request.rootFolder = req.body.rootFolder;

requestRepository.save(request);
}

return res.status(200).json(request);
} catch (e) {
next({ status: 500, message: e.message });
}
}
);

requestRoutes.delete('/:requestId', async (req, res, next) => {
const requestRepository = getRepository(MediaRequest);

Expand Down
4 changes: 2 additions & 2 deletions src/components/Common/Alert/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ const Alert: React.FC<AlertProps> = ({ title, children, type }) => {
<div className="flex">
<div className={`flex-shrink-0 ${design.titleColor}`}>{design.svg}</div>
<div className="ml-3">
<h3 className={`text-sm font-medium ${design.titleColor}`}>
<div className={`text-sm font-medium ${design.titleColor}`}>
{title}
</h3>
</div>
<div className={`mt-2 text-sm ${design.textColor}`}>{children}</div>
</div>
</div>
Expand Down
19 changes: 14 additions & 5 deletions src/components/Common/SlideOver/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/* eslint-disable jsx-a11y/click-events-have-key-events */
import React, { useState, useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';
import Transition from '../../Transition';
import { useLockBodyScroll } from '../../../hooks/useLockBodyScroll';
import useClickOutside from '../../../hooks/useClickOutside';

interface SlideOverProps {
show?: boolean;
Expand All @@ -21,9 +21,6 @@ const SlideOver: React.FC<SlideOverProps> = ({
const [isMounted, setIsMounted] = useState(false);
const slideoverRef = useRef(null);
useLockBodyScroll(show);
useClickOutside(slideoverRef, () => {
onClose();
});

useEffect(() => {
setIsMounted(true);
Expand All @@ -44,8 +41,15 @@ const SlideOver: React.FC<SlideOverProps> = ({
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
<div
className={`z-50 fixed inset-0 overflow-hidden bg-opacity-50 bg-gray-800`}
onClick={() => onClose()}
onKeyDown={(e) => {
if (e.key === 'Escape') {
onClose();
}
}}
>
<div className="absolute inset-0 overflow-hidden">
<section className="absolute inset-y-0 right-0 flex max-w-full pl-10">
Expand All @@ -59,7 +63,12 @@ const SlideOver: React.FC<SlideOverProps> = ({
leaveFrom="translate-x-0"
leaveTo="translate-x-full"
>
<div className="w-screen max-w-md" ref={slideoverRef}>
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
<div
className="w-screen max-w-md"
ref={slideoverRef}
onClick={(e) => e.stopPropagation()}
>
<div className="flex flex-col h-full overflow-y-scroll bg-gray-700 shadow-xl">
<header className="px-4 py-6 space-y-1 bg-indigo-600">
<div className="flex items-center justify-between space-x-3">
Expand Down
73 changes: 72 additions & 1 deletion src/components/RequestBlock/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,15 @@ import { MediaRequestStatus } from '../../../server/constants/media';
import Button from '../Common/Button';
import axios from 'axios';
import globalMessages from '../../i18n/globalMessages';
import RequestModal from '../RequestModal';
import useRequestOverride from '../../hooks/useRequestOverride';

const messages = defineMessages({
seasons: 'Seasons',
requestoverrides: 'Request Overrides',
server: 'Server',
profilechanged: 'Profile Changed',
rootfolder: 'Root Folder',
});

interface RequestBlockProps {
Expand All @@ -19,6 +25,8 @@ interface RequestBlockProps {
const RequestBlock: React.FC<RequestBlockProps> = ({ request, onUpdate }) => {
const intl = useIntl();
const [isUpdating, setIsUpdating] = useState(false);
const [showEditModal, setShowEditModal] = useState(false);
const { profile, rootFolder, server } = useRequestOverride(request);

const updateRequest = async (type: 'approve' | 'decline'): Promise<void> => {
setIsUpdating(true);
Expand All @@ -43,6 +51,20 @@ const RequestBlock: React.FC<RequestBlockProps> = ({ request, onUpdate }) => {

return (
<div className="block">
<RequestModal
show={showEditModal}
tmdbId={request.media.tmdbId}
type={request.type}
is4k={request.is4k}
editRequest={request.id}
onCancel={() => setShowEditModal(false)}
onComplete={() => {
if (onUpdate) {
onUpdate();
}
setShowEditModal(false);
}}
/>
<div className="px-4 py-4">
<div className="flex items-center justify-between">
<div className="flex-col items-center flex-1 min-w-0 mr-6 text-sm leading-5 text-gray-300">
Expand Down Expand Up @@ -107,7 +129,7 @@ const RequestBlock: React.FC<RequestBlockProps> = ({ request, onUpdate }) => {
</svg>
</Button>
</span>
<span>
<span className="mr-1">
<Button
buttonType="danger"
onClick={() => updateRequest('decline')}
Expand All @@ -127,6 +149,22 @@ const RequestBlock: React.FC<RequestBlockProps> = ({ request, onUpdate }) => {
</svg>
</Button>
</span>
<span>
<Button
buttonType="primary"
onClick={() => setShowEditModal(true)}
disabled={isUpdating}
>
<svg
className="w-4 h-4"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z" />
</svg>
</Button>
</span>
</>
)}
{request.status !== MediaRequestStatus.PENDING && (
Expand Down Expand Up @@ -209,6 +247,39 @@ const RequestBlock: React.FC<RequestBlockProps> = ({ request, onUpdate }) => {
</div>
</div>
)}
{(server || profile || rootFolder) && (
<>
<div className="mt-4 mb-1 text-sm">
{intl.formatMessage(messages.requestoverrides)}
</div>
<ul className="px-2 text-xs bg-gray-800 divide-y divide-gray-700 rounded-md">
{server && (
<li className="flex justify-between px-1 py-2">
<span className="font-bold">
{intl.formatMessage(messages.server)}
</span>
<span>{server}</span>
</li>
)}
{profile !== null && (
<li className="flex justify-between px-1 py-2">
<span className="font-bold">
{intl.formatMessage(messages.profilechanged)}
</span>
<span>ID {profile}</span>
</li>
)}
{rootFolder && (
<li className="flex justify-between px-1 py-2">
<span className="mr-2 font-bold">
{intl.formatMessage(messages.rootfolder)}
</span>
<span>{rootFolder}</span>
</li>
)}
</ul>
</>
)}
</div>
</div>
);
Expand Down
35 changes: 34 additions & 1 deletion src/components/RequestList/RequestItem/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import axios from 'axios';
import globalMessages from '../../../i18n/globalMessages';
import Link from 'next/link';
import { useToasts } from 'react-toast-notifications';
import RequestModal from '../../RequestModal';

const messages = defineMessages({
requestedby: 'Requested by {username}',
Expand Down Expand Up @@ -51,6 +52,7 @@ const RequestItem: React.FC<RequestItemProps> = ({
const { addToast } = useToasts();
const intl = useIntl();
const { hasPermission } = useUser();
const [showEditModal, setShowEditModal] = useState(false);
const { locale } = useContext(LanguageContext);
const url =
request.type === 'movie'
Expand Down Expand Up @@ -116,6 +118,18 @@ const RequestItem: React.FC<RequestItemProps> = ({

return (
<tr className="relative w-full h-24 p-2 text-white bg-gray-800">
<RequestModal
show={showEditModal}
tmdbId={request.media.tmdbId}
type={request.type}
is4k={request.is4k}
editRequest={request.id}
onCancel={() => setShowEditModal(false)}
onComplete={() => {
revalidateList();
setShowEditModal(false);
}}
/>
<Table.TD>
<div className="flex items-center">
<Link
Expand Down Expand Up @@ -276,7 +290,7 @@ const RequestItem: React.FC<RequestItemProps> = ({
</span>
</Button>
</span>
<span>
<span className="mr-2">
<Button
buttonType="danger"
buttonSize="sm"
Expand All @@ -299,6 +313,25 @@ const RequestItem: React.FC<RequestItemProps> = ({
</span>
</Button>
</span>
<span>
<Button
buttonType="primary"
buttonSize="sm"
onClick={() => setShowEditModal(true)}
>
<svg
className="w-4 h-4 mr-0 sm:mr-1"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z" />
</svg>
<span className="hidden sm:block">
{intl.formatMessage(globalMessages.edit)}
</span>
</Button>
</span>
</>
)}
</Table.TD>
Expand Down
Loading

0 comments on commit b96f0d7

Please sign in to comment.