diff --git a/src/components/Common/ButtonWithDropdown/index.tsx b/src/components/Common/ButtonWithDropdown/index.tsx index 0219f0bf9..7a9940b39 100644 --- a/src/components/Common/ButtonWithDropdown/index.tsx +++ b/src/components/Common/ButtonWithDropdown/index.tsx @@ -77,10 +77,10 @@ const ButtonWithDropdown: React.FC = ({ )} diff --git a/src/components/Discover/index.tsx b/src/components/Discover/index.tsx index cdc76ee77..15ea54c81 100644 --- a/src/components/Discover/index.tsx +++ b/src/components/Discover/index.tsx @@ -8,13 +8,14 @@ import type { import TitleCard from '../TitleCard'; import PersonCard from '../PersonCard'; import { MediaRequest } from '../../../server/entity/MediaRequest'; -import RequestCard from '../TitleCard/RequestCard'; +import TmdbTitleCard from '../TitleCard/TmdbTitleCard'; import Slider from '../Slider'; import Link from 'next/link'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { LanguageContext } from '../../context/LanguageContext'; import type Media from '../../../server/entity/Media'; import type { MediaResultsResponse } from '../../../server/interfaces/api/mediaInterfaces'; +import RequestCard from '../RequestCard'; const messages = defineMessages({ recentrequests: 'Recent Requests', @@ -89,7 +90,7 @@ const Discover: React.FC = () => { isLoading={!media && !mediaError} isEmpty={!!media && !mediaError && media.results.length === 0} items={media?.results?.map((item) => ( - { items={requests?.map((request) => ( ))} + placeholder={} emptyMessage={intl.formatMessage(messages.nopending)} />
diff --git a/src/components/RequestCard/index.tsx b/src/components/RequestCard/index.tsx new file mode 100644 index 000000000..47a770f76 --- /dev/null +++ b/src/components/RequestCard/index.tsx @@ -0,0 +1,167 @@ +import React, { useContext, useState } from 'react'; +import type { MediaRequest } from '../../../server/entity/MediaRequest'; +import type { TvDetails } from '../../../server/models/Tv'; +import type { MovieDetails } from '../../../server/models/Movie'; +import useSWR from 'swr'; +import { LanguageContext } from '../../context/LanguageContext'; +import { + MediaStatus, + MediaRequestStatus, +} from '../../../server/constants/media'; +import Badge from '../Common/Badge'; +import { useUser, Permission } from '../../hooks/useUser'; +import axios from 'axios'; +import Button from '../Common/Button'; +import { withProperties } from '../../utils/typeHelpers'; + +const isMovie = (movie: MovieDetails | TvDetails): movie is MovieDetails => { + return (movie as MovieDetails).title !== undefined; +}; + +const RequestCardPlaceholder: React.FC = () => { + return ( +
+
+
+
+
+ ); +}; + +interface RequestCardProps { + request: MediaRequest; +} + +const RequestCard: React.FC = ({ request }) => { + const { hasPermission } = useUser(); + const { locale } = useContext(LanguageContext); + const url = + request.type === 'movie' + ? `/api/v1/movie/${request.media.tmdbId}` + : `/api/v1/tv/${request.media.tmdbId}`; + const { data: title, error } = useSWR( + `${url}?language=${locale}` + ); + const { data: requestData, error: requestError, revalidate } = useSWR< + MediaRequest + >(`/api/v1/request/${request.id}`, { + initialData: request, + }); + + const modifyRequest = async (type: 'approve' | 'decline') => { + const response = await axios.get(`/api/v1/request/${request.id}/${type}`); + + if (response) { + revalidate(); + } + }; + + if (!title && !error) { + return ; + } + + if (!requestData && !requestError) { + return ; + } + + if (!title || !requestData) { + return ; + } + + return ( +
+
+

+ {isMovie(title) ? title.title : title.name} +

+
+ Requested by {requestData.requestedBy.username} +
+
+ {requestData.media.status === MediaStatus.AVAILABLE && ( + Available + )} + {requestData.media.status === MediaStatus.PROCESSING && ( + Unavailable + )} + {requestData.media.status === MediaStatus.PENDING && ( + Pending + )} +
+ {request.seasons.length > 0 && ( +
+ Seasons + {request.seasons.map((season) => ( + + {season.seasonNumber} + + ))} +
+ )} + {requestData.status === MediaRequestStatus.PENDING && + hasPermission(Permission.MANAGE_REQUESTS) && ( +
+ + + + + + +
+ )} +
+
+ +
+
+ ); +}; + +export default withProperties(RequestCard, { + Placeholder: RequestCardPlaceholder, +}); diff --git a/src/components/Slider/index.tsx b/src/components/Slider/index.tsx index a43ad137e..5f7202514 100644 --- a/src/components/Slider/index.tsx +++ b/src/components/Slider/index.tsx @@ -14,6 +14,7 @@ interface SliderProps { isLoading: boolean; isEmpty: boolean; emptyMessage?: string; + placeholder?: ReactNode; } enum Direction { @@ -27,6 +28,7 @@ const Slider: React.FC = ({ isLoading, isEmpty, emptyMessage, + placeholder = , }) => { const containerRef = useRef(null); const [scrollPos, setScrollPos] = useState({ isStart: true, isEnd: false }); @@ -204,14 +206,20 @@ const Slider: React.FC = ({ onScroll={onScroll} > {items?.map((item, index) => ( -
+
{item}
))} {isLoading && [...Array(10)].map((_item, i) => ( -
- +
+ {placeholder}
))} {isEmpty && ( diff --git a/src/components/TitleCard/RequestCard.tsx b/src/components/TitleCard/TmdbTitleCard.tsx similarity index 93% rename from src/components/TitleCard/RequestCard.tsx rename to src/components/TitleCard/TmdbTitleCard.tsx index 87b2e2741..8370a8125 100644 --- a/src/components/TitleCard/RequestCard.tsx +++ b/src/components/TitleCard/TmdbTitleCard.tsx @@ -14,7 +14,7 @@ const isMovie = (movie: MovieDetails | TvDetails): movie is MovieDetails => { return (movie as MovieDetails).title !== undefined; }; -const RequestCard: React.FC = ({ tmdbId, type }) => { +const TmdbTitleCard: React.FC = ({ tmdbId, type }) => { const { locale } = useContext(LanguageContext); const url = type === 'movie' ? `/api/v1/movie/${tmdbId}` : `/api/v1/tv/${tmdbId}`; @@ -55,4 +55,4 @@ const RequestCard: React.FC = ({ tmdbId, type }) => { ); }; -export default RequestCard; +export default TmdbTitleCard;