mirror of
https://github.com/fallenbagel/jellyseerr.git
synced 2025-12-31 19:59:31 -05:00
feat: plex watchlist sync integration (#2885)
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { useIntl } from 'react-intl';
|
||||
import type { WatchlistItem } from '../../../../server/interfaces/api/discoverInterfaces';
|
||||
import type {
|
||||
MovieResult,
|
||||
PersonResult,
|
||||
@@ -8,14 +9,16 @@ import useVerticalScroll from '../../../hooks/useVerticalScroll';
|
||||
import globalMessages from '../../../i18n/globalMessages';
|
||||
import PersonCard from '../../PersonCard';
|
||||
import TitleCard from '../../TitleCard';
|
||||
import TmdbTitleCard from '../../TitleCard/TmdbTitleCard';
|
||||
|
||||
interface ListViewProps {
|
||||
type ListViewProps = {
|
||||
items?: (TvResult | MovieResult | PersonResult)[];
|
||||
plexItems?: WatchlistItem[];
|
||||
isEmpty?: boolean;
|
||||
isLoading?: boolean;
|
||||
isReachingEnd?: boolean;
|
||||
onScrollBottom: () => void;
|
||||
}
|
||||
};
|
||||
|
||||
const ListView = ({
|
||||
items,
|
||||
@@ -23,6 +26,7 @@ const ListView = ({
|
||||
isLoading,
|
||||
onScrollBottom,
|
||||
isReachingEnd,
|
||||
plexItems,
|
||||
}: ListViewProps) => {
|
||||
const intl = useIntl();
|
||||
useVerticalScroll(onScrollBottom, !isLoading && !isEmpty && !isReachingEnd);
|
||||
@@ -34,6 +38,18 @@ const ListView = ({
|
||||
</div>
|
||||
)}
|
||||
<ul className="cards-vertical">
|
||||
{plexItems?.map((title, index) => {
|
||||
return (
|
||||
<li key={`${title.ratingKey}-${index}`}>
|
||||
<TmdbTitleCard
|
||||
id={title.tmdbId}
|
||||
tmdbId={title.tmdbId}
|
||||
type={title.mediaType}
|
||||
canExpand
|
||||
/>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
{items?.map((title, index) => {
|
||||
let titleCard: React.ReactNode;
|
||||
|
||||
|
||||
@@ -143,7 +143,7 @@ const SettingsTabs = ({
|
||||
</div>
|
||||
) : (
|
||||
<div className="hide-scrollbar hidden overflow-x-scroll border-b border-gray-600 sm:block">
|
||||
<nav className="flex">
|
||||
<nav className="flex" data-testid="settings-nav-desktop">
|
||||
{settingsRoutes
|
||||
.filter(
|
||||
(route) =>
|
||||
|
||||
51
src/components/Discover/DiscoverWatchlist/index.tsx
Normal file
51
src/components/Discover/DiscoverWatchlist/index.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import ListView from '../../Common/ListView';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import Header from '../../Common/Header';
|
||||
import PageTitle from '../../Common/PageTitle';
|
||||
import useDiscover from '../../../hooks/useDiscover';
|
||||
import Error from '../../../pages/_error';
|
||||
import type { WatchlistItem } from '../../../../server/interfaces/api/discoverInterfaces';
|
||||
|
||||
const messages = defineMessages({
|
||||
discoverwatchlist: 'Your Plex Watchlist',
|
||||
});
|
||||
|
||||
const DiscoverWatchlist = () => {
|
||||
const intl = useIntl();
|
||||
|
||||
const {
|
||||
isLoadingInitialData,
|
||||
isEmpty,
|
||||
isLoadingMore,
|
||||
isReachingEnd,
|
||||
titles,
|
||||
fetchMore,
|
||||
error,
|
||||
} = useDiscover<WatchlistItem>('/api/v1/discover/watchlist');
|
||||
|
||||
if (error) {
|
||||
return <Error statusCode={500} />;
|
||||
}
|
||||
|
||||
const title = intl.formatMessage(messages.discoverwatchlist);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle title={title} />
|
||||
<div className="mt-1 mb-5">
|
||||
<Header>{title}</Header>
|
||||
</div>
|
||||
<ListView
|
||||
plexItems={titles}
|
||||
isEmpty={isEmpty}
|
||||
isLoading={
|
||||
isLoadingInitialData || (isLoadingMore && (titles?.length ?? 0) > 0)
|
||||
}
|
||||
isReachingEnd={isReachingEnd}
|
||||
onScrollBottom={fetchMore}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DiscoverWatchlist;
|
||||
@@ -2,9 +2,10 @@ import { ArrowCircleRightIcon } from '@heroicons/react/outline';
|
||||
import Link from 'next/link';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import useSWR from 'swr';
|
||||
import type { WatchlistItem } from '../../../server/interfaces/api/discoverInterfaces';
|
||||
import type { MediaResultsResponse } from '../../../server/interfaces/api/mediaInterfaces';
|
||||
import type { RequestResultsResponse } from '../../../server/interfaces/api/requestInterfaces';
|
||||
import { Permission, useUser } from '../../hooks/useUser';
|
||||
import { Permission, UserType, useUser } from '../../hooks/useUser';
|
||||
import PageTitle from '../Common/PageTitle';
|
||||
import MediaSlider from '../MediaSlider';
|
||||
import RequestCard from '../RequestCard';
|
||||
@@ -25,11 +26,12 @@ const messages = defineMessages({
|
||||
noRequests: 'No requests.',
|
||||
upcoming: 'Upcoming Movies',
|
||||
trending: 'Trending',
|
||||
plexwatchlist: 'Your Plex Watchlist',
|
||||
});
|
||||
|
||||
const Discover = () => {
|
||||
const intl = useIntl();
|
||||
const { hasPermission } = useUser();
|
||||
const { user, hasPermission } = useUser();
|
||||
|
||||
const { data: media, error: mediaError } = useSWR<MediaResultsResponse>(
|
||||
'/api/v1/media?filter=allavailable&take=20&sort=mediaAdded',
|
||||
@@ -44,6 +46,15 @@ const Discover = () => {
|
||||
}
|
||||
);
|
||||
|
||||
const { data: watchlistItems, error: watchlistError } = useSWR<{
|
||||
page: number;
|
||||
totalPages: number;
|
||||
totalResults: number;
|
||||
results: WatchlistItem[];
|
||||
}>(user?.userType === UserType.PLEX ? '/api/v1/discover/watchlist' : null, {
|
||||
revalidateOnMount: true,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle title={intl.formatMessage(messages.discover)} />
|
||||
@@ -93,6 +104,30 @@ const Discover = () => {
|
||||
placeholder={<RequestCard.Placeholder />}
|
||||
emptyMessage={intl.formatMessage(messages.noRequests)}
|
||||
/>
|
||||
{(!watchlistItems || !!watchlistItems.results.length) && !watchlistError && (
|
||||
<>
|
||||
<div className="slider-header">
|
||||
<Link href="/discover/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}
|
||||
/>
|
||||
))}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<MediaSlider
|
||||
sliderKey="trending"
|
||||
title={intl.formatMessage(messages.trending)}
|
||||
|
||||
@@ -71,9 +71,13 @@ interface ManageSlideOverTvProps extends ManageSlideOverProps {
|
||||
data: TvDetails;
|
||||
}
|
||||
|
||||
const ManageSlideOver: React.FC<
|
||||
ManageSlideOverMovieProps | ManageSlideOverTvProps
|
||||
> = ({ show, mediaType, onClose, data, revalidate }) => {
|
||||
const ManageSlideOver = ({
|
||||
show,
|
||||
mediaType,
|
||||
onClose,
|
||||
data,
|
||||
revalidate,
|
||||
}: ManageSlideOverMovieProps | ManageSlideOverTvProps) => {
|
||||
const { user: currentUser, hasPermission } = useUser();
|
||||
const intl = useIntl();
|
||||
const settings = useSettings();
|
||||
|
||||
@@ -50,6 +50,15 @@ export const messages = defineMessages({
|
||||
advancedrequest: 'Advanced Requests',
|
||||
advancedrequestDescription:
|
||||
'Grant permission to modify advanced media request options.',
|
||||
autorequest: 'Auto-Request',
|
||||
autorequestDescription:
|
||||
'Grant permission to automatically submit requests for non-4K media via Plex Watchlist.',
|
||||
autorequestMovies: 'Auto-Request Movies',
|
||||
autorequestMoviesDescription:
|
||||
'Grant permission to automatically submit requests for non-4K movies via Plex Watchlist.',
|
||||
autorequestSeries: 'Auto-Request Series',
|
||||
autorequestSeriesDescription:
|
||||
'Grant permission to automatically submit requests for non-4K series via Plex Watchlist.',
|
||||
viewrequests: 'View Requests',
|
||||
viewrequestsDescription:
|
||||
'Grant permission to view media requests submitted by other users.',
|
||||
@@ -176,6 +185,43 @@ export const PermissionEdit = ({
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'autorequest',
|
||||
name: intl.formatMessage(messages.autorequest),
|
||||
description: intl.formatMessage(messages.autorequestDescription),
|
||||
permission: Permission.AUTO_REQUEST,
|
||||
requires: [{ permissions: [Permission.REQUEST] }],
|
||||
children: [
|
||||
{
|
||||
id: 'autorequestmovies',
|
||||
name: intl.formatMessage(messages.autorequestMovies),
|
||||
description: intl.formatMessage(
|
||||
messages.autorequestMoviesDescription
|
||||
),
|
||||
permission: Permission.AUTO_REQUEST_MOVIE,
|
||||
requires: [
|
||||
{
|
||||
permissions: [Permission.REQUEST, Permission.REQUEST_MOVIE],
|
||||
type: 'or',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'autorequesttv',
|
||||
name: intl.formatMessage(messages.autorequestSeries),
|
||||
description: intl.formatMessage(
|
||||
messages.autorequestSeriesDescription
|
||||
),
|
||||
permission: Permission.AUTO_REQUEST_TV,
|
||||
requires: [
|
||||
{
|
||||
permissions: [Permission.REQUEST, Permission.REQUEST_TV],
|
||||
type: 'or',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'request4k',
|
||||
name: intl.formatMessage(messages.request4k),
|
||||
|
||||
@@ -47,6 +47,7 @@ const messages: { [messageName: string]: MessageDescriptor } = defineMessages({
|
||||
unknownJob: 'Unknown Job',
|
||||
'plex-recently-added-scan': 'Plex Recently Added Scan',
|
||||
'plex-full-scan': 'Plex Full Library Scan',
|
||||
'plex-watchlist-sync': 'Plex Watchlist Sync',
|
||||
'radarr-scan': 'Radarr Scan',
|
||||
'sonarr-scan': 'Sonarr Scan',
|
||||
'download-sync': 'Download Sync',
|
||||
|
||||
@@ -10,7 +10,7 @@ interface SliderProps {
|
||||
sliderKey: string;
|
||||
items?: JSX.Element[];
|
||||
isLoading: boolean;
|
||||
isEmpty: boolean;
|
||||
isEmpty?: boolean;
|
||||
emptyMessage?: string;
|
||||
placeholder?: React.ReactNode;
|
||||
}
|
||||
@@ -24,7 +24,7 @@ const Slider = ({
|
||||
sliderKey,
|
||||
items,
|
||||
isLoading,
|
||||
isEmpty,
|
||||
isEmpty = false,
|
||||
emptyMessage,
|
||||
placeholder = <TitleCard.Placeholder />,
|
||||
}: SliderProps) => {
|
||||
|
||||
@@ -10,13 +10,20 @@ export interface TmdbTitleCardProps {
|
||||
tmdbId: number;
|
||||
tvdbId?: number;
|
||||
type: 'movie' | 'tv';
|
||||
canExpand?: boolean;
|
||||
}
|
||||
|
||||
const isMovie = (movie: MovieDetails | TvDetails): movie is MovieDetails => {
|
||||
return (movie as MovieDetails).title !== undefined;
|
||||
};
|
||||
|
||||
const TmdbTitleCard = ({ id, tmdbId, tvdbId, type }: TmdbTitleCardProps) => {
|
||||
const TmdbTitleCard = ({
|
||||
id,
|
||||
tmdbId,
|
||||
tvdbId,
|
||||
type,
|
||||
canExpand,
|
||||
}: TmdbTitleCardProps) => {
|
||||
const { hasPermission } = useUser();
|
||||
|
||||
const { ref, inView } = useInView({
|
||||
@@ -31,7 +38,7 @@ const TmdbTitleCard = ({ id, tmdbId, tvdbId, type }: TmdbTitleCardProps) => {
|
||||
if (!title && !error) {
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<TitleCard.Placeholder />
|
||||
<TitleCard.Placeholder canExpand={canExpand} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -57,6 +64,7 @@ const TmdbTitleCard = ({ id, tmdbId, tvdbId, type }: TmdbTitleCardProps) => {
|
||||
userScore={title.voteAverage}
|
||||
year={title.releaseDate}
|
||||
mediaType={'movie'}
|
||||
canExpand={canExpand}
|
||||
/>
|
||||
) : (
|
||||
<TitleCard
|
||||
@@ -68,6 +76,7 @@ const TmdbTitleCard = ({ id, tmdbId, tvdbId, type }: TmdbTitleCardProps) => {
|
||||
userScore={title.voteAverage}
|
||||
year={title.firstAirDate}
|
||||
mediaType={'tv'}
|
||||
canExpand={canExpand}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -588,7 +588,10 @@ const UserList = () => {
|
||||
</Link>
|
||||
<div className="ml-4">
|
||||
<Link href={`/users/${user.id}`}>
|
||||
<a className="text-base font-bold leading-5 transition duration-300 hover:underline">
|
||||
<a
|
||||
className="text-base font-bold leading-5 transition duration-300 hover:underline"
|
||||
data-testid="user-list-username-link"
|
||||
>
|
||||
{user.displayName}
|
||||
</a>
|
||||
</Link>
|
||||
|
||||
@@ -49,6 +49,12 @@ const messages = defineMessages({
|
||||
discordIdTip:
|
||||
'The <FindDiscordIdLink>multi-digit ID number</FindDiscordIdLink> associated with your Discord user account',
|
||||
validationDiscordId: 'You must provide a valid Discord user ID',
|
||||
plexwatchlistsyncmovies: 'Auto-Request Movies',
|
||||
plexwatchlistsyncmoviestip:
|
||||
'Automatically request movies on your <PlexWatchlistSupportLink>Plex Watchlist</PlexWatchlistSupportLink>',
|
||||
plexwatchlistsyncseries: 'Auto-Request Series',
|
||||
plexwatchlistsyncseriestip:
|
||||
'Automatically request series on your <PlexWatchlistSupportLink>Plex Watchlist</PlexWatchlistSupportLink>',
|
||||
});
|
||||
|
||||
const UserGeneralSettings = () => {
|
||||
@@ -122,6 +128,8 @@ const UserGeneralSettings = () => {
|
||||
movieQuotaDays: data?.movieQuotaDays,
|
||||
tvQuotaLimit: data?.tvQuotaLimit,
|
||||
tvQuotaDays: data?.tvQuotaDays,
|
||||
watchlistSyncMovies: data?.watchlistSyncMovies,
|
||||
watchlistSyncTv: data?.watchlistSyncTv,
|
||||
}}
|
||||
validationSchema={UserGeneralSettingsSchema}
|
||||
enableReinitialize
|
||||
@@ -139,6 +147,8 @@ const UserGeneralSettings = () => {
|
||||
movieQuotaDays: movieQuotaEnabled ? values.movieQuotaDays : null,
|
||||
tvQuotaLimit: tvQuotaEnabled ? values.tvQuotaLimit : null,
|
||||
tvQuotaDays: tvQuotaEnabled ? values.tvQuotaDays : null,
|
||||
watchlistSyncMovies: values.watchlistSyncMovies,
|
||||
watchlistSyncTv: values.watchlistSyncTv,
|
||||
});
|
||||
|
||||
if (currentUser?.id === user?.id && setLocale) {
|
||||
@@ -409,6 +419,99 @@ const UserGeneralSettings = () => {
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{hasPermission(
|
||||
[Permission.AUTO_REQUEST, Permission.AUTO_REQUEST_MOVIE],
|
||||
{ type: 'or' }
|
||||
) &&
|
||||
user?.userType === UserType.PLEX && (
|
||||
<div className="form-row">
|
||||
<label
|
||||
htmlFor="watchlistSyncMovies"
|
||||
className="checkbox-label"
|
||||
>
|
||||
<span>
|
||||
{intl.formatMessage(messages.plexwatchlistsyncmovies)}
|
||||
</span>
|
||||
<span className="label-tip">
|
||||
{intl.formatMessage(
|
||||
messages.plexwatchlistsyncmoviestip,
|
||||
{
|
||||
PlexWatchlistSupportLink: (
|
||||
msg: React.ReactNode
|
||||
) => (
|
||||
<a
|
||||
href="https://support.plex.tv/articles/universal-watchlist/"
|
||||
className="text-white transition duration-300 hover:underline"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{msg}
|
||||
</a>
|
||||
),
|
||||
}
|
||||
)}
|
||||
</span>
|
||||
</label>
|
||||
<div className="form-input-area">
|
||||
<Field
|
||||
type="checkbox"
|
||||
id="watchlistSyncMovies"
|
||||
name="watchlistSyncMovies"
|
||||
onChange={() => {
|
||||
setFieldValue(
|
||||
'watchlistSyncMovies',
|
||||
!values.watchlistSyncMovies
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{hasPermission(
|
||||
[Permission.AUTO_REQUEST, Permission.AUTO_REQUEST_TV],
|
||||
{ type: 'or' }
|
||||
) &&
|
||||
user?.userType === UserType.PLEX && (
|
||||
<div className="form-row">
|
||||
<label htmlFor="watchlistSyncTv" className="checkbox-label">
|
||||
<span>
|
||||
{intl.formatMessage(messages.plexwatchlistsyncseries)}
|
||||
</span>
|
||||
<span className="label-tip">
|
||||
{intl.formatMessage(
|
||||
messages.plexwatchlistsyncseriestip,
|
||||
{
|
||||
PlexWatchlistSupportLink: (
|
||||
msg: React.ReactNode
|
||||
) => (
|
||||
<a
|
||||
href="https://support.plex.tv/articles/universal-watchlist/"
|
||||
className="text-white transition duration-300 hover:underline"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{msg}
|
||||
</a>
|
||||
),
|
||||
}
|
||||
)}
|
||||
</span>
|
||||
</label>
|
||||
<div className="form-input-area">
|
||||
<Field
|
||||
type="checkbox"
|
||||
id="watchlistSyncTv"
|
||||
name="watchlistSyncTv"
|
||||
onChange={() => {
|
||||
setFieldValue(
|
||||
'watchlistSyncTv',
|
||||
!values.watchlistSyncTv
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="actions">
|
||||
<div className="flex justify-end">
|
||||
<span className="ml-3 inline-flex rounded-md shadow-sm">
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"components.Discover.DiscoverStudio.studioMovies": "{studio} Movies",
|
||||
"components.Discover.DiscoverTvGenre.genreSeries": "{genre} Series",
|
||||
"components.Discover.DiscoverTvLanguage.languageSeries": "{language} Series",
|
||||
"components.Discover.DiscoverWatchlist.discoverwatchlist": "Your Plex Watchlist",
|
||||
"components.Discover.MovieGenreList.moviegenres": "Movie Genres",
|
||||
"components.Discover.MovieGenreSlider.moviegenres": "Movie Genres",
|
||||
"components.Discover.NetworkSlider.networks": "Networks",
|
||||
@@ -20,6 +21,7 @@
|
||||
"components.Discover.discovermovies": "Popular Movies",
|
||||
"components.Discover.discovertv": "Popular Series",
|
||||
"components.Discover.noRequests": "No requests.",
|
||||
"components.Discover.plexwatchlist": "Your Plex Watchlist",
|
||||
"components.Discover.popularmovies": "Popular Movies",
|
||||
"components.Discover.populartv": "Popular Series",
|
||||
"components.Discover.recentlyAdded": "Recently Added",
|
||||
@@ -230,6 +232,12 @@
|
||||
"components.PermissionEdit.autoapproveMoviesDescription": "Grant automatic approval for non-4K movie requests.",
|
||||
"components.PermissionEdit.autoapproveSeries": "Auto-Approve Series",
|
||||
"components.PermissionEdit.autoapproveSeriesDescription": "Grant automatic approval for non-4K series requests.",
|
||||
"components.PermissionEdit.autorequest": "Auto-Request",
|
||||
"components.PermissionEdit.autorequestDescription": "Grant permission to automatically submit requests for non-4K media via Plex Watchlist.",
|
||||
"components.PermissionEdit.autorequestMovies": "Auto-Request Movies",
|
||||
"components.PermissionEdit.autorequestMoviesDescription": "Grant permission to automatically submit requests for non-4K movies via Plex Watchlist.",
|
||||
"components.PermissionEdit.autorequestSeries": "Auto-Request Series",
|
||||
"components.PermissionEdit.autorequestSeriesDescription": "Grant permission to automatically submit requests for non-4K series via Plex Watchlist.",
|
||||
"components.PermissionEdit.createissues": "Report Issues",
|
||||
"components.PermissionEdit.createissuesDescription": "Grant permission to report media issues.",
|
||||
"components.PermissionEdit.manageissues": "Manage Issues",
|
||||
@@ -618,6 +626,7 @@
|
||||
"components.Settings.SettingsJobsCache.nextexecution": "Next Execution",
|
||||
"components.Settings.SettingsJobsCache.plex-full-scan": "Plex Full Library Scan",
|
||||
"components.Settings.SettingsJobsCache.plex-recently-added-scan": "Plex Recently Added Scan",
|
||||
"components.Settings.SettingsJobsCache.plex-watchlist-sync": "Plex Watchlist Sync",
|
||||
"components.Settings.SettingsJobsCache.process": "Process",
|
||||
"components.Settings.SettingsJobsCache.radarr-scan": "Radarr Scan",
|
||||
"components.Settings.SettingsJobsCache.runnow": "Run Now",
|
||||
@@ -920,6 +929,10 @@
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.originallanguageTip": "Filter content by original language",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.owner": "Owner",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.plexuser": "Plex User",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncmovies": "Auto-Request Movies",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncmoviestip": "Automatically request movies on your <PlexWatchlistSupportLink>Plex Watchlist</PlexWatchlistSupportLink>",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncseries": "Auto-Request Series",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncseriestip": "Automatically request series on your <PlexWatchlistSupportLink>Plex Watchlist</PlexWatchlistSupportLink>",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.region": "Discover Region",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.regionTip": "Filter content by regional availability",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.role": "Role",
|
||||
|
||||
8
src/pages/discover/watchlist.tsx
Normal file
8
src/pages/discover/watchlist.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { NextPage } from 'next';
|
||||
import DiscoverWatchlist from '../../components/Discover/DiscoverWatchlist';
|
||||
|
||||
const WatchlistPage: NextPage = () => {
|
||||
return <DiscoverWatchlist />;
|
||||
};
|
||||
|
||||
export default WatchlistPage;
|
||||
Reference in New Issue
Block a user