feat: upcoming/trending list views and larger title cards

pull/179/head
sct 4 years ago
parent 185ac2648f
commit 94eaaf96b4

@ -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"> <FormattedMessage {...messages.discovermovies} />
<h2 className="text-xl leading-7 text-white sm:text-2xl sm:leading-9 sm:truncate"> </Header>
<FormattedMessage {...messages.discovermovies} />
</h2>
</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"> <FormattedMessage {...messages.discovertv} />
<h2 className="text-xl leading-7 text-white sm:text-2xl sm:leading-9 sm:truncate"> </Header>
<FormattedMessage {...messages.discovertv} />
</h2>
</div>
</div>
<ListView <ListView
items={titles} items={titles}
isEmpty={isEmpty} isEmpty={isEmpty}

@ -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;

@ -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> </div>
<svg
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,184 +144,183 @@ 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"> <div className="flex flex-col space-y-6 lg:flex-row lg:space-y-0 lg:space-x-6 text-white">
<FormattedMessage {...messages.edituser} /> <div className="flex-grow space-y-6">
</h2> <div className="space-y-1">
</div> <label
</div> htmlFor="username"
className="block text-sm font-medium leading-5 text-cool-gray-400"
<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"> <FormattedMessage {...messages.username} />
<div className="space-y-1"> </label>
<label <div className="rounded-md shadow-sm flex">
htmlFor="username" <input
className="block text-sm font-medium leading-5 text-cool-gray-400" id="username"
> className="form-input flex-grow block w-full min-w-0 rounded-md transition duration-150 ease-in-out sm:text-sm sm:leading-5 bg-cool-gray-700 border border-cool-gray-500"
<FormattedMessage {...messages.username} /> value={user?.username}
</label> readOnly
<div className="rounded-md shadow-sm flex"> />
<input </div>
id="username"
className="form-input flex-grow block w-full min-w-0 rounded-md transition duration-150 ease-in-out sm:text-sm sm:leading-5 bg-cool-gray-700 border border-cool-gray-500"
value={user?.username}
readOnly
/>
</div>
</div>
<div className="space-y-1">
<label
htmlFor="email"
className="block text-sm font-medium leading-5 text-cool-gray-400"
>
<FormattedMessage {...messages.email} />
</label>
<div className="rounded-md shadow-sm flex">
<input
id="email"
className="form-input flex-grow block w-full min-w-0 rounded-md transition duration-150 ease-in-out sm:text-sm sm:leading-5 bg-cool-gray-700 border border-cool-gray-500"
value={user?.email}
readOnly
/>
</div> </div>
</div> <div className="space-y-1">
</div> <label
htmlFor="email"
<div className="flex-grow space-y-1 lg:flex-grow-0 lg:flex-shrink-0"> className="block text-sm font-medium leading-5 text-cool-gray-400"
<p
className="block text-sm leading-5 font-medium text-cool-gray-400"
aria-hidden="true"
>
<FormattedMessage {...messages.avatar} />
</p>
<div className="lg:hidden">
<div className="flex items-center">
<div
className="flex-shrink-0 inline-block rounded-full overflow-hidden h-12 w-12"
aria-hidden="true"
> >
<img <FormattedMessage {...messages.email} />
className="rounded-full h-full w-full" </label>
src={user?.avatar} <div className="rounded-md shadow-sm flex">
alt="" <input
id="email"
className="form-input flex-grow block w-full min-w-0 rounded-md transition duration-150 ease-in-out sm:text-sm sm:leading-5 bg-cool-gray-700 border border-cool-gray-500"
value={user?.email}
readOnly
/> />
</div> </div>
</div> </div>
</div> </div>
<div className="hidden relative rounded-full overflow-hidden lg:block transition duration-150 ease-in-out"> <div className="flex-grow space-y-1 lg:flex-grow-0 lg:flex-shrink-0">
<img <p
className="relative rounded-full w-40 h-40" className="block text-sm leading-5 font-medium text-cool-gray-400"
src={user?.avatar} aria-hidden="true"
alt="" >
/> <FormattedMessage {...messages.avatar} />
</div> </p>
</div> <div className="lg:hidden">
</div> <div className="flex items-center">
<div className="text-white">
<div className="sm:border-t sm:border-gray-200 sm:pt-5">
<div role="group" aria-labelledby="label-permissions">
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-baseline">
<div>
<div <div
className="text-base leading-6 font-medium sm:text-sm sm:leading-5" className="flex-shrink-0 inline-block rounded-full overflow-hidden h-12 w-12"
id="label-permissions" aria-hidden="true"
> >
<FormattedMessage {...messages.permissions} /> <img
className="rounded-full h-full w-full"
src={user?.avatar}
alt=""
/>
</div> </div>
</div> </div>
<div className="mt-4 sm:mt-0 sm:col-span-2"> </div>
<div className="max-w-lg">
{permissionList.map((permissionOption) => ( <div className="hidden relative rounded-full overflow-hidden lg:block transition duration-150 ease-in-out">
<div <img
className={`relative flex items-start first:mt-0 mt-4 ${ className="relative rounded-full w-40 h-40"
(permissionOption.permission !== Permission.ADMIN && src={user?.avatar}
hasPermission(Permission.ADMIN, currentPermission)) || alt=""
(currentUser?.id !== 1 && />
permissionOption.permission === Permission.ADMIN) || </div>
(!currentHasPermission(Permission.MANAGE_SETTINGS) && </div>
permissionOption.permission === </div>
Permission.MANAGE_SETTINGS) <div className="text-white">
? 'opacity-50' <div className="sm:border-t sm:border-gray-200 sm:pt-5">
: '' <div role="group" aria-labelledby="label-permissions">
}`} <div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-baseline">
key={`permission-option-${permissionOption.id}`} <div>
> <div
<div className="flex items-center h-5"> className="text-base leading-6 font-medium sm:text-sm sm:leading-5"
<input id="label-permissions"
id={permissionOption.id} >
name="permissions" <FormattedMessage {...messages.permissions} />
type="checkbox" </div>
className="form-checkbox h-4 w-4 text-indigo-600 transition duration-150 ease-in-out" </div>
disabled={ <div className="mt-4 sm:mt-0 sm:col-span-2">
(permissionOption.permission !== Permission.ADMIN && <div className="max-w-lg">
hasPermission( {permissionList.map((permissionOption) => (
Permission.ADMIN, <div
currentPermission className={`relative flex items-start first:mt-0 mt-4 ${
)) || (permissionOption.permission !== Permission.ADMIN &&
(currentUser?.id !== 1 && hasPermission(
permissionOption.permission === Permission.ADMIN,
Permission.ADMIN) || currentPermission
(!currentHasPermission( )) ||
Permission.MANAGE_SETTINGS (currentUser?.id !== 1 &&
) && permissionOption.permission === Permission.ADMIN) ||
permissionOption.permission === (!currentHasPermission(Permission.MANAGE_SETTINGS) &&
Permission.MANAGE_SETTINGS) permissionOption.permission ===
} Permission.MANAGE_SETTINGS)
onClick={() => { ? 'opacity-50'
setCurrentPermission((current) => : ''
hasPermission( }`}
permissionOption.permission, key={`permission-option-${permissionOption.id}`}
currentPermission >
) <div className="flex items-center h-5">
? current - permissionOption.permission <input
: current + permissionOption.permission id={permissionOption.id}
); name="permissions"
}} type="checkbox"
checked={hasPermission( className="form-checkbox h-4 w-4 text-indigo-600 transition duration-150 ease-in-out"
permissionOption.permission, disabled={
currentPermission (permissionOption.permission !==
)} Permission.ADMIN &&
/> hasPermission(
</div> Permission.ADMIN,
<div className="ml-3 text-sm leading-5"> currentPermission
<label )) ||
htmlFor={permissionOption.id} (currentUser?.id !== 1 &&
className="font-medium" permissionOption.permission ===
> Permission.ADMIN) ||
{permissionOption.name} (!currentHasPermission(
</label> Permission.MANAGE_SETTINGS
<p className="text-gray-500"> ) &&
{permissionOption.description} permissionOption.permission ===
</p> Permission.MANAGE_SETTINGS)
}
onClick={() => {
setCurrentPermission((current) =>
hasPermission(
permissionOption.permission,
currentPermission
)
? current - permissionOption.permission
: current + permissionOption.permission
);
}}
checked={hasPermission(
permissionOption.permission,
currentPermission
)}
/>
</div>
<div className="ml-3 text-sm leading-5">
<label
htmlFor={permissionOption.id}
className="font-medium"
>
{permissionOption.name}
</label>
<p className="text-gray-500">
{permissionOption.description}
</p>
</div>
</div> </div>
</div> ))}
))} </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> <div className="mt-8 border-t border-cool-gray-700 pt-5">
<div className="mt-8 border-t border-cool-gray-700 pt-5"> <div className="flex justify-end">
<div className="flex justify-end"> <span className="ml-3 inline-flex rounded-md shadow-sm">
<span className="ml-3 inline-flex rounded-md shadow-sm"> <Button
<Button buttonType="primary"
buttonType="primary" type="submit"
type="submit" disabled={isUpdating}
disabled={isUpdating} onClick={() => updateUser()}
onClick={() => updateUser()} >
> {isUpdating
{isUpdating ? intl.formatMessage(messages.saving)
? intl.formatMessage(messages.saving) : intl.formatMessage(messages.save)}
: intl.formatMessage(messages.save)} </Button>
</Button> </span>
</span> </div>
</div> </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 />;

@ -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;

@ -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;
Loading…
Cancel
Save