From 94eaaf96b4302a832c52ccb72009b3593452c779 Mon Sep 17 00:00:00 2001 From: sct Date: Tue, 17 Nov 2020 09:18:45 +0000 Subject: [PATCH] feat: upcoming/trending list views and larger title cards --- src/components/Common/Header/index.tsx | 21 ++ src/components/Common/ListView/index.tsx | 14 +- src/components/Discover/DiscoverMovies.tsx | 13 +- src/components/Discover/DiscoverTv.tsx | 15 +- src/components/Discover/Trending.tsx | 81 +++++ src/components/Discover/Upcoming.tsx | 77 +++++ src/components/Discover/index.tsx | 26 +- src/components/PersonCard/index.tsx | 8 +- src/components/TitleCard/Placeholder.tsx | 12 +- src/components/TitleCard/index.tsx | 6 +- src/components/UserEdit/index.tsx | 318 +++++++++--------- src/components/UserList/index.tsx | 9 +- src/i18n/locale/en.json | 30 +- src/i18n/locale/ja.json | 24 ++ .../discover/{movies.tsx => movies/index.tsx} | 2 +- src/pages/discover/movies/upcoming.tsx | 9 + src/pages/discover/trending.tsx | 9 + 17 files changed, 456 insertions(+), 218 deletions(-) create mode 100644 src/components/Common/Header/index.tsx create mode 100644 src/components/Discover/Trending.tsx create mode 100644 src/components/Discover/Upcoming.tsx rename src/pages/discover/{movies.tsx => movies/index.tsx} (70%) create mode 100644 src/pages/discover/movies/upcoming.tsx create mode 100644 src/pages/discover/trending.tsx diff --git a/src/components/Common/Header/index.tsx b/src/components/Common/Header/index.tsx new file mode 100644 index 00000000..fc33bec5 --- /dev/null +++ b/src/components/Common/Header/index.tsx @@ -0,0 +1,21 @@ +import React from 'react'; + +interface HeaderProps { + extraMargin?: number; +} + +const Header: React.FC = ({ children, extraMargin = 0 }) => { + return ( +
+
+

+ + {children} + +

+
+
+ ); +}; + +export default Header; diff --git a/src/components/Common/ListView/index.tsx b/src/components/Common/ListView/index.tsx index 8f757f4f..f2fe94b7 100644 --- a/src/components/Common/ListView/index.tsx +++ b/src/components/Common/ListView/index.tsx @@ -31,7 +31,7 @@ const ListView: React.FC = ({ No Results )} -
    +
      {items?.map((title) => { let titleCard: React.ReactNode; @@ -47,6 +47,7 @@ const ListView: React.FC = ({ userScore={title.voteAverage} year={title.releaseDate} mediaType={title.mediaType} + canExpand /> ); break; @@ -61,12 +62,17 @@ const ListView: React.FC = ({ userScore={title.voteAverage} year={title.firstAirDate} mediaType={title.mediaType} + canExpand /> ); break; case 'person': titleCard = ( - + ); break; } @@ -82,12 +88,12 @@ const ListView: React.FC = ({ })} {isLoading && !isReachingEnd && - [...Array(10)].map((_item, i) => ( + [...Array(20)].map((_item, i) => (
    • - +
    • ))}
    diff --git a/src/components/Discover/DiscoverMovies.tsx b/src/components/Discover/DiscoverMovies.tsx index 6a2660dc..fc8a5096 100644 --- a/src/components/Discover/DiscoverMovies.tsx +++ b/src/components/Discover/DiscoverMovies.tsx @@ -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 ( <> -
    -
    -

    - -

    -
    -
    +
    + +
    { return ( <> -
    -
    -

    - -

    -
    -
    +
    + +
    { + const { locale } = useContext(LanguageContext); + const { data, error, size, setSize } = useSWRInfinite( + (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
    {error}
    ; + } + + 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 ( + <> +
    + +
    + 0) + } + isReachingEnd={isReachingEnd} + onScrollBottom={fetchMore} + /> + + ); +}; + +export default Trending; diff --git a/src/components/Discover/Upcoming.tsx b/src/components/Discover/Upcoming.tsx new file mode 100644 index 00000000..1687d0bc --- /dev/null +++ b/src/components/Discover/Upcoming.tsx @@ -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( + (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
    {error}
    ; + } + + 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 ( + <> +
    + +
    + 0) + } + isReachingEnd={isReachingEnd} + onScrollBottom={fetchMore} + /> + + ); +}; + +export default UpcomingMovies; diff --git a/src/components/Discover/index.tsx b/src/components/Discover/index.tsx index 41d63a41..0ffa024b 100644 --- a/src/components/Discover/index.tsx +++ b/src/components/Discover/index.tsx @@ -77,27 +77,11 @@ const Discover: React.FC = () => { <>
    - - - - - - - - - - +
    + + + +
    = ({ name, subName, profilePath, + canExpand = false, }) => { return ( -
    +
    {profilePath && ( diff --git a/src/components/TitleCard/Placeholder.tsx b/src/components/TitleCard/Placeholder.tsx index 99ce58f4..524262d1 100644 --- a/src/components/TitleCard/Placeholder.tsx +++ b/src/components/TitleCard/Placeholder.tsx @@ -1,8 +1,16 @@ import React from 'react'; -const Placeholder: React.FC = () => { +interface PlaceholderProps { + canExpand?: boolean; +} + +const Placeholder: React.FC = ({ canExpand = false }) => { return ( -
    +
    ); diff --git a/src/components/TitleCard/index.tsx b/src/components/TitleCard/index.tsx index f6fa1514..8d4d514c 100644 --- a/src/components/TitleCard/index.tsx +++ b/src/components/TitleCard/index.tsx @@ -19,7 +19,7 @@ interface TitleCardProps { userScore: number; mediaType: MediaType; status?: MediaStatus; - requestId?: number; + canExpand?: boolean; } const TitleCard: React.FC = ({ @@ -30,7 +30,7 @@ const TitleCard: React.FC = ({ title, status, mediaType, - requestId, + canExpand = false, }) => { const [isUpdating, setIsUpdating] = useState(false); const [currentStatus, setCurrentStatus] = useState(status); @@ -55,7 +55,7 @@ const TitleCard: React.FC = ({ const closeModal = useCallback(() => setShowRequestModal(false), []); return ( -
    +
    { ]; return ( -
    -
    -
    -

    - -

    -
    -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - + <> +
    Edit User
    +
    +
    +
    +
    + +
    + +
    -
    -
    - -
    - -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    -
    -
    +
    + +
    +
    -
    -
    - {permissionList.map((permissionOption) => ( -
    -
    - { - setCurrentPermission((current) => - hasPermission( - permissionOption.permission, - currentPermission - ) - ? current - permissionOption.permission - : current + permissionOption.permission - ); - }} - checked={hasPermission( - permissionOption.permission, - currentPermission - )} - /> -
    -
    - -

    - {permissionOption.description} -

    +
    + +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    + {permissionList.map((permissionOption) => ( +
    +
    + { + setCurrentPermission((current) => + hasPermission( + permissionOption.permission, + currentPermission + ) + ? current - permissionOption.permission + : current + permissionOption.permission + ); + }} + checked={hasPermission( + permissionOption.permission, + currentPermission + )} + /> +
    +
    + +

    + {permissionOption.description} +

    +
    -
    - ))} + ))} +
    -
    -
    -
    - - - +
    +
    + + + +
    -
    + ); }; diff --git a/src/components/UserList/index.tsx b/src/components/UserList/index.tsx index 08b43bd9..86d6f048 100644 --- a/src/components/UserList/index.tsx +++ b/src/components/UserList/index.tsx @@ -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 ( <> -
    -
    -

    - User List -

    -
    -
    +
    User List
    diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 11a37542..6043a253 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -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", diff --git a/src/i18n/locale/ja.json b/src/i18n/locale/ja.json index 68dbc8e0..5bf0d888 100644 --- a/src/i18n/locale/ja.json +++ b/src/i18n/locale/ja.json @@ -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": "", diff --git a/src/pages/discover/movies.tsx b/src/pages/discover/movies/index.tsx similarity index 70% rename from src/pages/discover/movies.tsx rename to src/pages/discover/movies/index.tsx index ca5e75f4..823898e8 100644 --- a/src/pages/discover/movies.tsx +++ b/src/pages/discover/movies/index.tsx @@ -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 ; diff --git a/src/pages/discover/movies/upcoming.tsx b/src/pages/discover/movies/upcoming.tsx new file mode 100644 index 00000000..13c62cc0 --- /dev/null +++ b/src/pages/discover/movies/upcoming.tsx @@ -0,0 +1,9 @@ +import React from 'react'; +import { NextPage } from 'next'; +import UpcomingMovies from '../../../components/Discover/Upcoming'; + +const UpcomingMoviesPage: NextPage = () => { + return ; +}; + +export default UpcomingMoviesPage; diff --git a/src/pages/discover/trending.tsx b/src/pages/discover/trending.tsx new file mode 100644 index 00000000..1ab2e951 --- /dev/null +++ b/src/pages/discover/trending.tsx @@ -0,0 +1,9 @@ +import React from 'react'; +import type { NextPage } from 'next'; +import Trending from '../../components/Discover/Trending'; + +const TrendingPage: NextPage = () => { + return ; +}; + +export default TrendingPage;