feat: view other users' watchlists (#2959)

* feat: view other users' watchlists

* test: add cypress tests

* feat(lang): translation keys

* refactor: yarn format

* fix: manage requests perm is parent of view watchlist perm
This commit is contained in:
TheCatLady
2022-08-21 22:50:27 -07:00
committed by GitHub
parent 950b1712b7
commit 0839718806
17 changed files with 346 additions and 57 deletions

View File

@@ -2,16 +2,25 @@ import Header from '@app/components/Common/Header';
import ListView from '@app/components/Common/ListView';
import PageTitle from '@app/components/Common/PageTitle';
import useDiscover from '@app/hooks/useDiscover';
import { useUser } from '@app/hooks/useUser';
import Error from '@app/pages/_error';
import type { WatchlistItem } from '@server/interfaces/api/discoverInterfaces';
import Link from 'next/link';
import { useRouter } from 'next/router';
import { defineMessages, useIntl } from 'react-intl';
const messages = defineMessages({
discoverwatchlist: 'Your Plex Watchlist',
watchlist: 'Plex Watchlist',
});
const DiscoverWatchlist = () => {
const intl = useIntl();
const router = useRouter();
const { user } = useUser({
id: Number(router.query.userId),
});
const { user: currentUser } = useUser();
const {
isLoadingInitialData,
@@ -21,19 +30,43 @@ const DiscoverWatchlist = () => {
titles,
fetchMore,
error,
} = useDiscover<WatchlistItem>('/api/v1/discover/watchlist');
} = useDiscover<WatchlistItem>(
`/api/v1/${
router.pathname.startsWith('/profile')
? `user/${currentUser?.id}`
: router.query.userId
? `user/${router.query.userId}`
: 'discover'
}/watchlist`
);
if (error) {
return <Error statusCode={500} />;
}
const title = intl.formatMessage(messages.discoverwatchlist);
const title = intl.formatMessage(
router.query.userId ? messages.watchlist : messages.discoverwatchlist
);
return (
<>
<PageTitle title={title} />
<PageTitle
title={[title, router.query.userId ? user?.displayName : '']}
/>
<div className="mt-1 mb-5">
<Header>{title}</Header>
<Header
subtext={
router.query.userId ? (
<Link href={`/users/${user?.id}`}>
<a className="hover:underline">{user?.displayName}</a>
</Link>
) : (
''
)
}
>
{title}
</Header>
</div>
<ListView
plexItems={titles}

View File

@@ -38,6 +38,7 @@ const UserDropdown = () => {
aria-label="User menu"
aria-haspopup="true"
onClick={() => setDropdownOpen(true)}
data-testid="user-menu"
>
<img
className="h-8 w-8 rounded-full object-cover sm:h-10 sm:w-10"
@@ -76,6 +77,7 @@ const UserDropdown = () => {
}
}}
onClick={() => setDropdownOpen(false)}
data-testid="user-menu-profile"
>
<UserIcon className="mr-2 inline h-5 w-5" />
<span>{intl.formatMessage(messages.myprofile)}</span>
@@ -92,6 +94,7 @@ const UserDropdown = () => {
}
}}
onClick={() => setDropdownOpen(false)}
data-testid="user-menu-settings"
>
<CogIcon className="mr-2 inline h-5 w-5" />
<span>{intl.formatMessage(messages.settings)}</span>

View File

@@ -72,6 +72,9 @@ export const messages = defineMessages({
viewrecent: 'View Recently Added',
viewrecentDescription:
'Grant permission to view the list of recently added media.',
viewwatchlists: 'View Plex Watchlists',
viewwatchlistsDescription:
"Grant permission to view other users' Plex Watchlists.",
});
interface PermissionEditProps {
@@ -126,6 +129,12 @@ export const PermissionEdit = ({
description: intl.formatMessage(messages.viewrecentDescription),
permission: Permission.RECENT_VIEW,
},
{
id: 'viewwatchlists',
name: intl.formatMessage(messages.viewwatchlists),
description: intl.formatMessage(messages.viewwatchlistsDescription),
permission: Permission.WATCHLIST_VIEW,
},
],
},
{

View File

@@ -9,6 +9,7 @@ import ProfileHeader from '@app/components/UserProfile/ProfileHeader';
import { Permission, UserType, useUser } from '@app/hooks/useUser';
import Error from '@app/pages/_error';
import { ArrowCircleRightIcon } from '@heroicons/react/outline';
import type { WatchlistResponse } from '@server/interfaces/api/discoverInterfaces';
import type {
QuotaResponse,
UserRequestsResponse,
@@ -33,6 +34,7 @@ const messages = defineMessages({
movierequests: 'Movie Requests',
seriesrequest: 'Series Requests',
recentlywatched: 'Recently Watched',
plexwatchlist: 'Plex Watchlist',
});
type MediaTitle = MovieDetails | TvDetails;
@@ -74,6 +76,21 @@ const UserProfile = () => {
? `/api/v1/user/${user.id}/watch_data`
: null
);
const { data: watchlistItems, error: watchlistError } =
useSWR<WatchlistResponse>(
user?.id === currentUser?.id ||
currentHasPermission(
[Permission.MANAGE_REQUESTS, Permission.WATCHLIST_VIEW],
{
type: 'or',
}
)
? `/api/v1/user/${user?.id}/watchlist`
: null,
{
revalidateOnMount: true,
}
);
const updateAvailableTitles = useCallback(
(requestId: number, mediaTitle: MediaTitle) => {
@@ -277,6 +294,36 @@ const UserProfile = () => {
/>
</>
)}
{(!watchlistItems || !!watchlistItems.results.length) && !watchlistError && (
<>
<div className="slider-header">
<Link
href={
user.id === currentUser?.id
? '/profile/watchlist'
: `/users/${user?.id}/watchlist`
}
>
<a className="slider-title">
<span>{intl.formatMessage(messages.plexwatchlist)}</span>
<ArrowCircleRightIcon />
</a>
</Link>
</div>
<Slider
sliderKey="watchlist"
isLoading={!watchlistItems && !watchlistError}
items={watchlistItems?.results.map((item) => (
<TmdbTitleCard
id={item.tmdbId}
key={`watchlist-slider-item-${item.ratingKey}`}
tmdbId={item.tmdbId}
type={item.mediaType}
/>
))}
/>
</>
)}
{(user.id === currentUser?.id ||
currentHasPermission(Permission.ADMIN)) &&
!!watchData?.recentlyWatched.length && (