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
</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) => {
let titleCard: React.ReactNode;
@ -47,6 +47,7 @@ const ListView: React.FC<ListViewProps> = ({
userScore={title.voteAverage}
year={title.releaseDate}
mediaType={title.mediaType}
canExpand
/>
);
break;
@ -61,12 +62,17 @@ const ListView: React.FC<ListViewProps> = ({
userScore={title.voteAverage}
year={title.firstAirDate}
mediaType={title.mediaType}
canExpand
/>
);
break;
case 'person':
titleCard = (
<PersonCard name={title.name} profilePath={title.profilePath} />
<PersonCard
name={title.name}
profilePath={title.profilePath}
canExpand
/>
);
break;
}
@ -82,12 +88,12 @@ const ListView: React.FC<ListViewProps> = ({
})}
{isLoading &&
!isReachingEnd &&
[...Array(10)].map((_item, i) => (
[...Array(20)].map((_item, i) => (
<li
key={`placeholder-${i}`}
className="col-span-1 flex flex-col text-center items-center"
>
<TitleCard.Placeholder />
<TitleCard.Placeholder canExpand />
</li>
))}
</ul>

@ -4,9 +4,10 @@ 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({
discovermovies: 'Discover Movies',
discovermovies: 'Popular Movies',
});
interface SearchResult {
@ -55,13 +56,9 @@ const DiscoverMovies: React.FC = () => {
return (
<>
<div className="md:flex md:items-center md:justify-between mb-8 mt-6">
<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} />
</h2>
</div>
</div>
<Header>
<FormattedMessage {...messages.discovermovies} />
</Header>
<ListView
items={titles}
isEmpty={isEmpty}

@ -1,12 +1,13 @@
import React, { useContext } from 'react';
import { useSWRInfinite } from 'swr';
import { TvResult } from '../../../server/models/Search';
import type { TvResult } from '../../../server/models/Search';
import ListView from '../Common/ListView';
import { defineMessages, FormattedMessage } from 'react-intl';
import { LanguageContext } from '../../context/LanguageContext';
import Header from '../Common/Header';
const messages = defineMessages({
discovertv: 'Discover Series',
discovertv: 'Popular Series',
});
interface SearchResult {
@ -52,13 +53,9 @@ const DiscoverTv: React.FC = () => {
return (
<>
<div className="md:flex md:items-center md:justify-between mb-8 mt-6">
<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} />
</h2>
</div>
</div>
<Header>
<FormattedMessage {...messages.discovertv} />
</Header>
<ListView
items={titles}
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="flex-1 min-w-0">
<Link href="/recent">
<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>
<FormattedMessage {...messages.recentlyAdded} />
</span>
<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 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>
<FormattedMessage {...messages.recentlyAdded} />
</span>
</div>
</div>
</div>
<Slider

@ -4,15 +4,21 @@ interface PersonCardProps {
name: string;
subName?: string;
profilePath?: string;
canExpand?: boolean;
}
const PersonCard: React.FC<PersonCardProps> = ({
name,
subName,
profilePath,
canExpand = false,
}) => {
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 className="absolute inset-0 flex flex-col items-center justify-center">
{profilePath && (

@ -1,8 +1,16 @@
import React from 'react';
const Placeholder: React.FC = () => {
interface PlaceholderProps {
canExpand?: boolean;
}
const Placeholder: React.FC<PlaceholderProps> = ({ canExpand = false }) => {
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>
);

@ -19,7 +19,7 @@ interface TitleCardProps {
userScore: number;
mediaType: MediaType;
status?: MediaStatus;
requestId?: number;
canExpand?: boolean;
}
const TitleCard: React.FC<TitleCardProps> = ({
@ -30,7 +30,7 @@ const TitleCard: React.FC<TitleCardProps> = ({
title,
status,
mediaType,
requestId,
canExpand = false,
}) => {
const [isUpdating, setIsUpdating] = useState(false);
const [currentStatus, setCurrentStatus] = useState(status);
@ -55,7 +55,7 @@ const TitleCard: React.FC<TitleCardProps> = ({
const closeModal = useCallback(() => setShowRequestModal(false), []);
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
tmdbId={id}
show={showRequestModal}

@ -7,6 +7,7 @@ import Button from '../Common/Button';
import { useIntl, defineMessages, FormattedMessage } from 'react-intl';
import axios from 'axios';
import { useToasts } from 'react-toast-notifications';
import Header from '../Common/Header';
const messages = defineMessages({
edituser: 'Edit User',
@ -143,184 +144,183 @@ const UserEdit: React.FC = () => {
];
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">
<div className="flex-1 min-w-0">
<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-grow space-y-6">
<div className="space-y-1">
<label
htmlFor="username"
className="block text-sm font-medium leading-5 text-cool-gray-400"
>
<FormattedMessage {...messages.username} />
</label>
<div className="rounded-md shadow-sm flex">
<input
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
/>
<>
<Header extraMargin={4}>Edit User</Header>
<div className="px-4 space-y-6 sm:p-6 lg:pb-8">
<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="space-y-1">
<label
htmlFor="username"
className="block text-sm font-medium leading-5 text-cool-gray-400"
>
<FormattedMessage {...messages.username} />
</label>
<div className="rounded-md shadow-sm flex">
<input
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>
</div>
<div className="flex-grow space-y-1 lg:flex-grow-0 lg:flex-shrink-0">
<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"
<div className="space-y-1">
<label
htmlFor="email"
className="block text-sm font-medium leading-5 text-cool-gray-400"
>
<img
className="rounded-full h-full w-full"
src={user?.avatar}
alt=""
<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="hidden relative rounded-full overflow-hidden lg:block transition duration-150 ease-in-out">
<img
className="relative rounded-full w-40 h-40"
src={user?.avatar}
alt=""
/>
</div>
</div>
</div>
<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 className="flex-grow space-y-1 lg:flex-grow-0 lg:flex-shrink-0">
<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="text-base leading-6 font-medium sm:text-sm sm:leading-5"
id="label-permissions"
className="flex-shrink-0 inline-block rounded-full overflow-hidden h-12 w-12"
aria-hidden="true"
>
<FormattedMessage {...messages.permissions} />
<img
className="rounded-full h-full w-full"
src={user?.avatar}
alt=""
/>
</div>
</div>
<div className="mt-4 sm:mt-0 sm:col-span-2">
<div className="max-w-lg">
{permissionList.map((permissionOption) => (
<div
className={`relative flex items-start first:mt-0 mt-4 ${
(permissionOption.permission !== Permission.ADMIN &&
hasPermission(Permission.ADMIN, currentPermission)) ||
(currentUser?.id !== 1 &&
permissionOption.permission === Permission.ADMIN) ||
(!currentHasPermission(Permission.MANAGE_SETTINGS) &&
permissionOption.permission ===
Permission.MANAGE_SETTINGS)
? 'opacity-50'
: ''
}`}
key={`permission-option-${permissionOption.id}`}
>
<div className="flex items-center h-5">
<input
id={permissionOption.id}
name="permissions"
type="checkbox"
className="form-checkbox h-4 w-4 text-indigo-600 transition duration-150 ease-in-out"
disabled={
(permissionOption.permission !== Permission.ADMIN &&
hasPermission(
Permission.ADMIN,
currentPermission
)) ||
(currentUser?.id !== 1 &&
permissionOption.permission ===
Permission.ADMIN) ||
(!currentHasPermission(
Permission.MANAGE_SETTINGS
) &&
permissionOption.permission ===
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 className="hidden relative rounded-full overflow-hidden lg:block transition duration-150 ease-in-out">
<img
className="relative rounded-full w-40 h-40"
src={user?.avatar}
alt=""
/>
</div>
</div>
</div>
<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
className="text-base leading-6 font-medium sm:text-sm sm:leading-5"
id="label-permissions"
>
<FormattedMessage {...messages.permissions} />
</div>
</div>
<div className="mt-4 sm:mt-0 sm:col-span-2">
<div className="max-w-lg">
{permissionList.map((permissionOption) => (
<div
className={`relative flex items-start first:mt-0 mt-4 ${
(permissionOption.permission !== Permission.ADMIN &&
hasPermission(
Permission.ADMIN,
currentPermission
)) ||
(currentUser?.id !== 1 &&
permissionOption.permission === Permission.ADMIN) ||
(!currentHasPermission(Permission.MANAGE_SETTINGS) &&
permissionOption.permission ===
Permission.MANAGE_SETTINGS)
? 'opacity-50'
: ''
}`}
key={`permission-option-${permissionOption.id}`}
>
<div className="flex items-center h-5">
<input
id={permissionOption.id}
name="permissions"
type="checkbox"
className="form-checkbox h-4 w-4 text-indigo-600 transition duration-150 ease-in-out"
disabled={
(permissionOption.permission !==
Permission.ADMIN &&
hasPermission(
Permission.ADMIN,
currentPermission
)) ||
(currentUser?.id !== 1 &&
permissionOption.permission ===
Permission.ADMIN) ||
(!currentHasPermission(
Permission.MANAGE_SETTINGS
) &&
permissionOption.permission ===
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 className="mt-8 border-t border-cool-gray-700 pt-5">
<div className="flex justify-end">
<span className="ml-3 inline-flex rounded-md shadow-sm">
<Button
buttonType="primary"
type="submit"
disabled={isUpdating}
onClick={() => updateUser()}
>
{isUpdating
? intl.formatMessage(messages.saving)
: intl.formatMessage(messages.save)}
</Button>
</span>
<div className="mt-8 border-t border-cool-gray-700 pt-5">
<div className="flex justify-end">
<span className="ml-3 inline-flex rounded-md shadow-sm">
<Button
buttonType="primary"
type="submit"
disabled={isUpdating}
onClick={() => updateUser()}
>
{isUpdating
? intl.formatMessage(messages.saving)
: intl.formatMessage(messages.save)}
</Button>
</span>
</div>
</div>
</div>
</div>
</div>
</>
);
};

@ -8,6 +8,7 @@ import Button from '../Common/Button';
import { hasPermission } from '../../../server/lib/permissions';
import { Permission } from '../../hooks/useUser';
import { useRouter } from 'next/router';
import Header from '../Common/Header';
const UserList: React.FC = () => {
const router = useRouter();
@ -19,13 +20,7 @@ const UserList: React.FC = () => {
return (
<>
<div className="md:flex md:items-center md:justify-between mt-8 mb-6">
<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>
<Header extraMargin={4}>User List</Header>
<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="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.discovertv": "Discover Series",
"components.Discover.discovermovies": "Popular Movies",
"components.Discover.discovertv": "Popular Series",
"components.Discover.nopending": "No Pending Requests",
"components.Discover.popularmovies": "Popular Movies",
"components.Discover.populartv": "Popular Series",
"components.Discover.recentlyAdded": "Recently Added",
"components.Discover.recentrequests": "Recent Requests",
"components.Discover.trending": "Trending",
"components.Discover.upcoming": "Upcoming Movies",
"components.Layout.LanguagePicker.changelanguage": "Change Language",
"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.settings": "Settings",
"components.Layout.Sidebar.users": "Users",
@ -76,6 +77,29 @@
"components.TvDetails.status": "Status",
"components.TvDetails.unavailable": "Unavailable",
"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.oops": "Oops",
"pages.pageNotFound": "404 - Page Not Found",

@ -6,6 +6,7 @@
"components.Discover.populartv": "人気のテレビ番組",
"components.Discover.recentlyAdded": "",
"components.Discover.recentrequests": "最近のリクエスト",
"components.Discover.trending": "",
"components.Discover.upcoming": "",
"components.Layout.LanguagePicker.changelanguage": "言語",
"components.Layout.SearchInput.searchPlaceholder": "作品名で検索",
@ -76,6 +77,29 @@
"components.TvDetails.status": "",
"components.TvDetails.unavailable": "",
"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.oops": "ああ",
"pages.pageNotFound": "",

@ -1,6 +1,6 @@
import React from 'react';
import { NextPage } from 'next';
import DiscoverMovies from '../../components/Discover/DiscoverMovies';
import DiscoverMovies from '../../../components/Discover/DiscoverMovies';
const DiscoverMoviesPage: NextPage = () => {
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