mirror of
https://github.com/sct/overseerr.git
synced 2025-09-17 17:24:35 +02:00
feat: upcoming/trending list views and larger title cards
This commit is contained in:
21
src/components/Common/Header/index.tsx
Normal file
21
src/components/Common/Header/index.tsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface HeaderProps {
|
||||||
|
extraMargin?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Header: React.FC<HeaderProps> = ({ children, extraMargin = 0 }) => {
|
||||||
|
return (
|
||||||
|
<div className="md:flex md:items-center md:justify-between mt-8 mb-8">
|
||||||
|
<div className={`flex-1 min-w-0 mx-${extraMargin}`}>
|
||||||
|
<h2 className="text-2xl font-bold leading-7 text-cool-gray-100 sm:text-4xl sm:leading-9 truncate sm:overflow-visible">
|
||||||
|
<span className="bg-clip-text text-transparent bg-gradient-to-br from-indigo-400 to-purple-400">
|
||||||
|
{children}
|
||||||
|
</span>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Header;
|
@@ -31,7 +31,7 @@ const ListView: React.FC<ListViewProps> = ({
|
|||||||
No Results
|
No Results
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<ul className="grid grid-cols-2 gap-6 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5">
|
<ul className="grid grid-cols-2 gap-6 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-7">
|
||||||
{items?.map((title) => {
|
{items?.map((title) => {
|
||||||
let titleCard: React.ReactNode;
|
let titleCard: React.ReactNode;
|
||||||
|
|
||||||
@@ -47,6 +47,7 @@ const ListView: React.FC<ListViewProps> = ({
|
|||||||
userScore={title.voteAverage}
|
userScore={title.voteAverage}
|
||||||
year={title.releaseDate}
|
year={title.releaseDate}
|
||||||
mediaType={title.mediaType}
|
mediaType={title.mediaType}
|
||||||
|
canExpand
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
@@ -61,12 +62,17 @@ const ListView: React.FC<ListViewProps> = ({
|
|||||||
userScore={title.voteAverage}
|
userScore={title.voteAverage}
|
||||||
year={title.firstAirDate}
|
year={title.firstAirDate}
|
||||||
mediaType={title.mediaType}
|
mediaType={title.mediaType}
|
||||||
|
canExpand
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case 'person':
|
case 'person':
|
||||||
titleCard = (
|
titleCard = (
|
||||||
<PersonCard name={title.name} profilePath={title.profilePath} />
|
<PersonCard
|
||||||
|
name={title.name}
|
||||||
|
profilePath={title.profilePath}
|
||||||
|
canExpand
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -82,12 +88,12 @@ const ListView: React.FC<ListViewProps> = ({
|
|||||||
})}
|
})}
|
||||||
{isLoading &&
|
{isLoading &&
|
||||||
!isReachingEnd &&
|
!isReachingEnd &&
|
||||||
[...Array(10)].map((_item, i) => (
|
[...Array(20)].map((_item, i) => (
|
||||||
<li
|
<li
|
||||||
key={`placeholder-${i}`}
|
key={`placeholder-${i}`}
|
||||||
className="col-span-1 flex flex-col text-center items-center"
|
className="col-span-1 flex flex-col text-center items-center"
|
||||||
>
|
>
|
||||||
<TitleCard.Placeholder />
|
<TitleCard.Placeholder canExpand />
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
@@ -4,9 +4,10 @@ import type { MovieResult } from '../../../server/models/Search';
|
|||||||
import ListView from '../Common/ListView';
|
import ListView from '../Common/ListView';
|
||||||
import { LanguageContext } from '../../context/LanguageContext';
|
import { LanguageContext } from '../../context/LanguageContext';
|
||||||
import { defineMessages, FormattedMessage } from 'react-intl';
|
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||||
|
import Header from '../Common/Header';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
discovermovies: 'Discover Movies',
|
discovermovies: 'Popular Movies',
|
||||||
});
|
});
|
||||||
|
|
||||||
interface SearchResult {
|
interface SearchResult {
|
||||||
@@ -55,13 +56,9 @@ const DiscoverMovies: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="md:flex md:items-center md:justify-between mb-8 mt-6">
|
<Header>
|
||||||
<div className="flex-1 min-w-0">
|
|
||||||
<h2 className="text-xl leading-7 text-white sm:text-2xl sm:leading-9 sm:truncate">
|
|
||||||
<FormattedMessage {...messages.discovermovies} />
|
<FormattedMessage {...messages.discovermovies} />
|
||||||
</h2>
|
</Header>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ListView
|
<ListView
|
||||||
items={titles}
|
items={titles}
|
||||||
isEmpty={isEmpty}
|
isEmpty={isEmpty}
|
||||||
|
@@ -1,12 +1,13 @@
|
|||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { useSWRInfinite } from 'swr';
|
import { useSWRInfinite } from 'swr';
|
||||||
import { TvResult } from '../../../server/models/Search';
|
import type { TvResult } from '../../../server/models/Search';
|
||||||
import ListView from '../Common/ListView';
|
import ListView from '../Common/ListView';
|
||||||
import { defineMessages, FormattedMessage } from 'react-intl';
|
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||||
import { LanguageContext } from '../../context/LanguageContext';
|
import { LanguageContext } from '../../context/LanguageContext';
|
||||||
|
import Header from '../Common/Header';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
discovertv: 'Discover Series',
|
discovertv: 'Popular Series',
|
||||||
});
|
});
|
||||||
|
|
||||||
interface SearchResult {
|
interface SearchResult {
|
||||||
@@ -52,13 +53,9 @@ const DiscoverTv: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="md:flex md:items-center md:justify-between mb-8 mt-6">
|
<Header>
|
||||||
<div className="flex-1 min-w-0">
|
|
||||||
<h2 className="text-xl leading-7 text-white sm:text-2xl sm:leading-9 sm:truncate">
|
|
||||||
<FormattedMessage {...messages.discovertv} />
|
<FormattedMessage {...messages.discovertv} />
|
||||||
</h2>
|
</Header>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ListView
|
<ListView
|
||||||
items={titles}
|
items={titles}
|
||||||
isEmpty={isEmpty}
|
isEmpty={isEmpty}
|
||||||
|
81
src/components/Discover/Trending.tsx
Normal file
81
src/components/Discover/Trending.tsx
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import React, { useContext } from 'react';
|
||||||
|
import { useSWRInfinite } from 'swr';
|
||||||
|
import type {
|
||||||
|
MovieResult,
|
||||||
|
TvResult,
|
||||||
|
PersonResult,
|
||||||
|
} from '../../../server/models/Search';
|
||||||
|
import ListView from '../Common/ListView';
|
||||||
|
import { LanguageContext } from '../../context/LanguageContext';
|
||||||
|
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||||
|
import Header from '../Common/Header';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
trending: 'Trending',
|
||||||
|
});
|
||||||
|
|
||||||
|
interface SearchResult {
|
||||||
|
page: number;
|
||||||
|
totalResults: number;
|
||||||
|
totalPages: number;
|
||||||
|
results: (MovieResult | TvResult | PersonResult)[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const Trending: React.FC = () => {
|
||||||
|
const { locale } = useContext(LanguageContext);
|
||||||
|
const { data, error, size, setSize } = useSWRInfinite<SearchResult>(
|
||||||
|
(pageIndex: number, previousPageData: SearchResult | null) => {
|
||||||
|
if (previousPageData && pageIndex + 1 > previousPageData.totalPages) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `/api/v1/discover/trending?page=${
|
||||||
|
pageIndex + 1
|
||||||
|
}&language=${locale}`;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
initialSize: 3,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const isLoadingInitialData = !data && !error;
|
||||||
|
const isLoadingMore =
|
||||||
|
isLoadingInitialData ||
|
||||||
|
(size > 0 && data && typeof data[size - 1] === 'undefined');
|
||||||
|
|
||||||
|
const fetchMore = () => {
|
||||||
|
setSize(size + 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <div>{error}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const titles = data?.reduce(
|
||||||
|
(a, v) => [...a, ...v.results],
|
||||||
|
[] as (MovieResult | TvResult | PersonResult)[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const isEmpty = !isLoadingInitialData && titles?.length === 0;
|
||||||
|
const isReachingEnd =
|
||||||
|
isEmpty || (data && data[data.length - 1]?.results.length < 20);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Header>
|
||||||
|
<FormattedMessage {...messages.trending} />
|
||||||
|
</Header>
|
||||||
|
<ListView
|
||||||
|
items={titles}
|
||||||
|
isEmpty={isEmpty}
|
||||||
|
isLoading={
|
||||||
|
isLoadingInitialData || (isLoadingMore && (titles?.length ?? 0) > 0)
|
||||||
|
}
|
||||||
|
isReachingEnd={isReachingEnd}
|
||||||
|
onScrollBottom={fetchMore}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Trending;
|
77
src/components/Discover/Upcoming.tsx
Normal file
77
src/components/Discover/Upcoming.tsx
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import React, { useContext } from 'react';
|
||||||
|
import { useSWRInfinite } from 'swr';
|
||||||
|
import type { MovieResult } from '../../../server/models/Search';
|
||||||
|
import ListView from '../Common/ListView';
|
||||||
|
import { LanguageContext } from '../../context/LanguageContext';
|
||||||
|
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||||
|
import Header from '../Common/Header';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
upcomingmovies: 'Upcoming Movies',
|
||||||
|
});
|
||||||
|
|
||||||
|
interface SearchResult {
|
||||||
|
page: number;
|
||||||
|
totalResults: number;
|
||||||
|
totalPages: number;
|
||||||
|
results: MovieResult[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const UpcomingMovies: React.FC = () => {
|
||||||
|
const { locale } = useContext(LanguageContext);
|
||||||
|
const { data, error, size, setSize } = useSWRInfinite<SearchResult>(
|
||||||
|
(pageIndex: number, previousPageData: SearchResult | null) => {
|
||||||
|
if (previousPageData && pageIndex + 1 > previousPageData.totalPages) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `/api/v1/discover/movies/upcoming?page=${
|
||||||
|
pageIndex + 1
|
||||||
|
}&language=${locale}`;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
initialSize: 3,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const isLoadingInitialData = !data && !error;
|
||||||
|
const isLoadingMore =
|
||||||
|
isLoadingInitialData ||
|
||||||
|
(size > 0 && data && typeof data[size - 1] === 'undefined');
|
||||||
|
|
||||||
|
const fetchMore = () => {
|
||||||
|
setSize(size + 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <div>{error}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const titles = data?.reduce(
|
||||||
|
(a, v) => [...a, ...v.results],
|
||||||
|
[] as MovieResult[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const isEmpty = !isLoadingInitialData && titles?.length === 0;
|
||||||
|
const isReachingEnd =
|
||||||
|
isEmpty || (data && data[data.length - 1]?.results.length < 20);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Header>
|
||||||
|
<FormattedMessage {...messages.upcomingmovies} />
|
||||||
|
</Header>
|
||||||
|
<ListView
|
||||||
|
items={titles}
|
||||||
|
isEmpty={isEmpty}
|
||||||
|
isLoading={
|
||||||
|
isLoadingInitialData || (isLoadingMore && (titles?.length ?? 0) > 0)
|
||||||
|
}
|
||||||
|
isReachingEnd={isReachingEnd}
|
||||||
|
onScrollBottom={fetchMore}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UpcomingMovies;
|
@@ -77,27 +77,11 @@ const Discover: React.FC = () => {
|
|||||||
<>
|
<>
|
||||||
<div className="md:flex md:items-center md:justify-between mb-4 mt-6">
|
<div className="md:flex md:items-center md:justify-between mb-4 mt-6">
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<Link href="/recent">
|
<div className="inline-flex text-xl leading-7 text-cool-gray-300 hover:text-white sm:text-2xl sm:leading-9 sm:truncate items-center">
|
||||||
<a className="inline-flex text-xl leading-7 text-cool-gray-300 hover:text-white sm:text-2xl sm:leading-9 sm:truncate items-center">
|
|
||||||
<span>
|
<span>
|
||||||
<FormattedMessage {...messages.recentlyAdded} />
|
<FormattedMessage {...messages.recentlyAdded} />
|
||||||
</span>
|
</span>
|
||||||
<svg
|
</div>
|
||||||
className="w-6 h-6 ml-2"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeWidth={2}
|
|
||||||
d="M13 9l3 3m0 0l-3 3m3-3H8m13 0a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
</Link>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Slider
|
<Slider
|
||||||
|
@@ -4,15 +4,21 @@ interface PersonCardProps {
|
|||||||
name: string;
|
name: string;
|
||||||
subName?: string;
|
subName?: string;
|
||||||
profilePath?: string;
|
profilePath?: string;
|
||||||
|
canExpand?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PersonCard: React.FC<PersonCardProps> = ({
|
const PersonCard: React.FC<PersonCardProps> = ({
|
||||||
name,
|
name,
|
||||||
subName,
|
subName,
|
||||||
profilePath,
|
profilePath,
|
||||||
|
canExpand = false,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className="relative w-36 sm:w-36 md:w-44 bg-cool-gray-600 rounded-lg text-white shadow-lg hover:bg-cool-gray-500 transition ease-in-out duration-150 cursor-pointer">
|
<div
|
||||||
|
className={`relative ${
|
||||||
|
canExpand ? 'w-full' : 'w-36 sm:w-36 md:w-44'
|
||||||
|
} bg-cool-gray-600 rounded-lg text-white shadow-lg hover:bg-cool-gray-500 transition ease-in-out duration-150 cursor-pointer`}
|
||||||
|
>
|
||||||
<div style={{ paddingBottom: '150%' }}>
|
<div style={{ paddingBottom: '150%' }}>
|
||||||
<div className="absolute inset-0 flex flex-col items-center justify-center">
|
<div className="absolute inset-0 flex flex-col items-center justify-center">
|
||||||
{profilePath && (
|
{profilePath && (
|
||||||
|
@@ -1,8 +1,16 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const Placeholder: React.FC = () => {
|
interface PlaceholderProps {
|
||||||
|
canExpand?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Placeholder: React.FC<PlaceholderProps> = ({ canExpand = false }) => {
|
||||||
return (
|
return (
|
||||||
<div className="relative animate-pulse rounded-lg bg-cool-gray-700 w-36 sm:w-36 md:w-44 ">
|
<div
|
||||||
|
className={`relative animate-pulse rounded-lg bg-cool-gray-700 ${
|
||||||
|
canExpand ? 'w-full' : 'w-36 sm:w-36 md:w-44'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
<div className="w-full" style={{ paddingBottom: '150%' }} />
|
<div className="w-full" style={{ paddingBottom: '150%' }} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@@ -19,7 +19,7 @@ interface TitleCardProps {
|
|||||||
userScore: number;
|
userScore: number;
|
||||||
mediaType: MediaType;
|
mediaType: MediaType;
|
||||||
status?: MediaStatus;
|
status?: MediaStatus;
|
||||||
requestId?: number;
|
canExpand?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TitleCard: React.FC<TitleCardProps> = ({
|
const TitleCard: React.FC<TitleCardProps> = ({
|
||||||
@@ -30,7 +30,7 @@ const TitleCard: React.FC<TitleCardProps> = ({
|
|||||||
title,
|
title,
|
||||||
status,
|
status,
|
||||||
mediaType,
|
mediaType,
|
||||||
requestId,
|
canExpand = false,
|
||||||
}) => {
|
}) => {
|
||||||
const [isUpdating, setIsUpdating] = useState(false);
|
const [isUpdating, setIsUpdating] = useState(false);
|
||||||
const [currentStatus, setCurrentStatus] = useState(status);
|
const [currentStatus, setCurrentStatus] = useState(status);
|
||||||
@@ -55,7 +55,7 @@ const TitleCard: React.FC<TitleCardProps> = ({
|
|||||||
const closeModal = useCallback(() => setShowRequestModal(false), []);
|
const closeModal = useCallback(() => setShowRequestModal(false), []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-36 sm:w-36 md:w-44">
|
<div className={canExpand ? 'w-full' : 'w-36 sm:w-36 md:w-44'}>
|
||||||
<RequestModal
|
<RequestModal
|
||||||
tmdbId={id}
|
tmdbId={id}
|
||||||
show={showRequestModal}
|
show={showRequestModal}
|
||||||
|
@@ -7,6 +7,7 @@ import Button from '../Common/Button';
|
|||||||
import { useIntl, defineMessages, FormattedMessage } from 'react-intl';
|
import { useIntl, defineMessages, FormattedMessage } from 'react-intl';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { useToasts } from 'react-toast-notifications';
|
import { useToasts } from 'react-toast-notifications';
|
||||||
|
import Header from '../Common/Header';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
edituser: 'Edit User',
|
edituser: 'Edit User',
|
||||||
@@ -143,15 +144,9 @@ const UserEdit: React.FC = () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="py-6 px-4 space-y-6 sm:p-6 lg:pb-8">
|
<>
|
||||||
<div className="md:flex md:items-center md:justify-between mt-8 mb-6">
|
<Header extraMargin={4}>Edit User</Header>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="px-4 space-y-6 sm:p-6 lg:pb-8">
|
||||||
<h2 className="text-2xl font-bold leading-7 text-cool-gray-100 sm:text-3xl sm:leading-9 sm:truncate">
|
|
||||||
<FormattedMessage {...messages.edituser} />
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-col space-y-6 lg:flex-row lg:space-y-0 lg:space-x-6 text-white">
|
<div className="flex flex-col space-y-6 lg:flex-row lg:space-y-0 lg:space-x-6 text-white">
|
||||||
<div className="flex-grow space-y-6">
|
<div className="flex-grow space-y-6">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
@@ -237,7 +232,10 @@ const UserEdit: React.FC = () => {
|
|||||||
<div
|
<div
|
||||||
className={`relative flex items-start first:mt-0 mt-4 ${
|
className={`relative flex items-start first:mt-0 mt-4 ${
|
||||||
(permissionOption.permission !== Permission.ADMIN &&
|
(permissionOption.permission !== Permission.ADMIN &&
|
||||||
hasPermission(Permission.ADMIN, currentPermission)) ||
|
hasPermission(
|
||||||
|
Permission.ADMIN,
|
||||||
|
currentPermission
|
||||||
|
)) ||
|
||||||
(currentUser?.id !== 1 &&
|
(currentUser?.id !== 1 &&
|
||||||
permissionOption.permission === Permission.ADMIN) ||
|
permissionOption.permission === Permission.ADMIN) ||
|
||||||
(!currentHasPermission(Permission.MANAGE_SETTINGS) &&
|
(!currentHasPermission(Permission.MANAGE_SETTINGS) &&
|
||||||
@@ -255,7 +253,8 @@ const UserEdit: React.FC = () => {
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
className="form-checkbox h-4 w-4 text-indigo-600 transition duration-150 ease-in-out"
|
className="form-checkbox h-4 w-4 text-indigo-600 transition duration-150 ease-in-out"
|
||||||
disabled={
|
disabled={
|
||||||
(permissionOption.permission !== Permission.ADMIN &&
|
(permissionOption.permission !==
|
||||||
|
Permission.ADMIN &&
|
||||||
hasPermission(
|
hasPermission(
|
||||||
Permission.ADMIN,
|
Permission.ADMIN,
|
||||||
currentPermission
|
currentPermission
|
||||||
@@ -321,6 +320,7 @@ const UserEdit: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -8,6 +8,7 @@ import Button from '../Common/Button';
|
|||||||
import { hasPermission } from '../../../server/lib/permissions';
|
import { hasPermission } from '../../../server/lib/permissions';
|
||||||
import { Permission } from '../../hooks/useUser';
|
import { Permission } from '../../hooks/useUser';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
|
import Header from '../Common/Header';
|
||||||
|
|
||||||
const UserList: React.FC = () => {
|
const UserList: React.FC = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -19,13 +20,7 @@ const UserList: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="md:flex md:items-center md:justify-between mt-8 mb-6">
|
<Header extraMargin={4}>User List</Header>
|
||||||
<div className="flex-1 min-w-0 mx-4">
|
|
||||||
<h2 className="text-2xl font-bold leading-7 text-cool-gray-100 sm:text-3xl sm:leading-9 sm:truncate">
|
|
||||||
User List
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className="my-2 overflow-x-auto -mx-6 sm:-mx-6 md:mx-4 lg:mx-4">
|
<div className="my-2 overflow-x-auto -mx-6 sm:-mx-6 md:mx-4 lg:mx-4">
|
||||||
<div className="py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8">
|
<div className="py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8">
|
||||||
|
@@ -1,15 +1,16 @@
|
|||||||
{
|
{
|
||||||
"components.Discover.discovermovies": "Discover Movies",
|
"components.Discover.discovermovies": "Popular Movies",
|
||||||
"components.Discover.discovertv": "Discover Series",
|
"components.Discover.discovertv": "Popular Series",
|
||||||
"components.Discover.nopending": "No Pending Requests",
|
"components.Discover.nopending": "No Pending Requests",
|
||||||
"components.Discover.popularmovies": "Popular Movies",
|
"components.Discover.popularmovies": "Popular Movies",
|
||||||
"components.Discover.populartv": "Popular Series",
|
"components.Discover.populartv": "Popular Series",
|
||||||
"components.Discover.recentlyAdded": "Recently Added",
|
"components.Discover.recentlyAdded": "Recently Added",
|
||||||
"components.Discover.recentrequests": "Recent Requests",
|
"components.Discover.recentrequests": "Recent Requests",
|
||||||
|
"components.Discover.trending": "Trending",
|
||||||
"components.Discover.upcoming": "Upcoming Movies",
|
"components.Discover.upcoming": "Upcoming Movies",
|
||||||
"components.Layout.LanguagePicker.changelanguage": "Change Language",
|
"components.Layout.LanguagePicker.changelanguage": "Change Language",
|
||||||
"components.Layout.SearchInput.searchPlaceholder": "Search Movies & TV",
|
"components.Layout.SearchInput.searchPlaceholder": "Search Movies & TV",
|
||||||
"components.Layout.Sidebar.dashboard": "Dashboard",
|
"components.Layout.Sidebar.dashboard": "Discover",
|
||||||
"components.Layout.Sidebar.requests": "Requests",
|
"components.Layout.Sidebar.requests": "Requests",
|
||||||
"components.Layout.Sidebar.settings": "Settings",
|
"components.Layout.Sidebar.settings": "Settings",
|
||||||
"components.Layout.Sidebar.users": "Users",
|
"components.Layout.Sidebar.users": "Users",
|
||||||
@@ -76,6 +77,29 @@
|
|||||||
"components.TvDetails.status": "Status",
|
"components.TvDetails.status": "Status",
|
||||||
"components.TvDetails.unavailable": "Unavailable",
|
"components.TvDetails.unavailable": "Unavailable",
|
||||||
"components.TvDetails.userrating": "User Rating",
|
"components.TvDetails.userrating": "User Rating",
|
||||||
|
"components.UserEdit.admin": "Admin",
|
||||||
|
"components.UserEdit.adminDescription": "Full administrator access. Bypasses all permission checks.",
|
||||||
|
"components.UserEdit.autoapprove": "Auto Approve",
|
||||||
|
"components.UserEdit.autoapproveDescription": "Grants auto approval for any requests made by this user.",
|
||||||
|
"components.UserEdit.avatar": "Avatar",
|
||||||
|
"components.UserEdit.edituser": "Edit User",
|
||||||
|
"components.UserEdit.email": "Email",
|
||||||
|
"components.UserEdit.managerequests": "Manage Requests",
|
||||||
|
"components.UserEdit.managerequestsDescription": "Grants permission to manage Overseerr requests. This includes approving and denying requests.",
|
||||||
|
"components.UserEdit.permissions": "Permissions",
|
||||||
|
"components.UserEdit.request": "Request",
|
||||||
|
"components.UserEdit.requestDescription": "Grants permission to make requests for movies or tv shows.",
|
||||||
|
"components.UserEdit.save": "Save",
|
||||||
|
"components.UserEdit.saving": "Saving...",
|
||||||
|
"components.UserEdit.settings": "Manage Settings",
|
||||||
|
"components.UserEdit.settingsDescription": "Grants permission to modify all Overseerr settings. User must have this permission to be able to grant it to others.",
|
||||||
|
"components.UserEdit.userfail": "Something went wrong saving the user.",
|
||||||
|
"components.UserEdit.username": "Username",
|
||||||
|
"components.UserEdit.users": "Manage Users",
|
||||||
|
"components.UserEdit.usersDescription": "Grants permission to manage Overseerr users. Users with this permission cannot modify users with Administrator privilege, or grant it.",
|
||||||
|
"components.UserEdit.usersaved": "User succesfully saved",
|
||||||
|
"components.UserEdit.vote": "Vote",
|
||||||
|
"components.UserEdit.voteDescription": "Grants permission to vote on requests (voting not yet implemented)",
|
||||||
"pages.internalServerError": "{statusCode} - Internal Server Error",
|
"pages.internalServerError": "{statusCode} - Internal Server Error",
|
||||||
"pages.oops": "Oops",
|
"pages.oops": "Oops",
|
||||||
"pages.pageNotFound": "404 - Page Not Found",
|
"pages.pageNotFound": "404 - Page Not Found",
|
||||||
|
@@ -6,6 +6,7 @@
|
|||||||
"components.Discover.populartv": "人気のテレビ番組",
|
"components.Discover.populartv": "人気のテレビ番組",
|
||||||
"components.Discover.recentlyAdded": "",
|
"components.Discover.recentlyAdded": "",
|
||||||
"components.Discover.recentrequests": "最近のリクエスト",
|
"components.Discover.recentrequests": "最近のリクエスト",
|
||||||
|
"components.Discover.trending": "",
|
||||||
"components.Discover.upcoming": "",
|
"components.Discover.upcoming": "",
|
||||||
"components.Layout.LanguagePicker.changelanguage": "言語",
|
"components.Layout.LanguagePicker.changelanguage": "言語",
|
||||||
"components.Layout.SearchInput.searchPlaceholder": "作品名で検索",
|
"components.Layout.SearchInput.searchPlaceholder": "作品名で検索",
|
||||||
@@ -76,6 +77,29 @@
|
|||||||
"components.TvDetails.status": "",
|
"components.TvDetails.status": "",
|
||||||
"components.TvDetails.unavailable": "",
|
"components.TvDetails.unavailable": "",
|
||||||
"components.TvDetails.userrating": "",
|
"components.TvDetails.userrating": "",
|
||||||
|
"components.UserEdit.admin": "",
|
||||||
|
"components.UserEdit.adminDescription": "",
|
||||||
|
"components.UserEdit.autoapprove": "",
|
||||||
|
"components.UserEdit.autoapproveDescription": "",
|
||||||
|
"components.UserEdit.avatar": "",
|
||||||
|
"components.UserEdit.edituser": "",
|
||||||
|
"components.UserEdit.email": "",
|
||||||
|
"components.UserEdit.managerequests": "",
|
||||||
|
"components.UserEdit.managerequestsDescription": "",
|
||||||
|
"components.UserEdit.permissions": "",
|
||||||
|
"components.UserEdit.request": "",
|
||||||
|
"components.UserEdit.requestDescription": "",
|
||||||
|
"components.UserEdit.save": "",
|
||||||
|
"components.UserEdit.saving": "",
|
||||||
|
"components.UserEdit.settings": "",
|
||||||
|
"components.UserEdit.settingsDescription": "",
|
||||||
|
"components.UserEdit.userfail": "",
|
||||||
|
"components.UserEdit.username": "",
|
||||||
|
"components.UserEdit.users": "",
|
||||||
|
"components.UserEdit.usersDescription": "",
|
||||||
|
"components.UserEdit.usersaved": "",
|
||||||
|
"components.UserEdit.vote": "",
|
||||||
|
"components.UserEdit.voteDescription": "",
|
||||||
"pages.internalServerError": "",
|
"pages.internalServerError": "",
|
||||||
"pages.oops": "ああ",
|
"pages.oops": "ああ",
|
||||||
"pages.pageNotFound": "",
|
"pages.pageNotFound": "",
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { NextPage } from 'next';
|
import { NextPage } from 'next';
|
||||||
import DiscoverMovies from '../../components/Discover/DiscoverMovies';
|
import DiscoverMovies from '../../../components/Discover/DiscoverMovies';
|
||||||
|
|
||||||
const DiscoverMoviesPage: NextPage = () => {
|
const DiscoverMoviesPage: NextPage = () => {
|
||||||
return <DiscoverMovies />;
|
return <DiscoverMovies />;
|
9
src/pages/discover/movies/upcoming.tsx
Normal file
9
src/pages/discover/movies/upcoming.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { NextPage } from 'next';
|
||||||
|
import UpcomingMovies from '../../../components/Discover/Upcoming';
|
||||||
|
|
||||||
|
const UpcomingMoviesPage: NextPage = () => {
|
||||||
|
return <UpcomingMovies />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UpcomingMoviesPage;
|
9
src/pages/discover/trending.tsx
Normal file
9
src/pages/discover/trending.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import type { NextPage } from 'next';
|
||||||
|
import Trending from '../../components/Discover/Trending';
|
||||||
|
|
||||||
|
const TrendingPage: NextPage = () => {
|
||||||
|
return <Trending />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TrendingPage;
|
Reference in New Issue
Block a user