Skip to content

Commit

Permalink
#106, #103: add similar, recommendations, cast, crew tab for detail p…
Browse files Browse the repository at this point in the history
…age (#123)
  • Loading branch information
Khanhtran47 committed Aug 21, 2022
1 parent 98d7d38 commit b7e18f6
Show file tree
Hide file tree
Showing 26 changed files with 743 additions and 166 deletions.
62 changes: 54 additions & 8 deletions app/routes/movies/$movieId/cast.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,56 @@
import { Text } from '@nextui-org/react';

const CastPage = () => (
<Text b h4 css={{ paddingLeft: '88px' }}>
{' '}
In development
</Text>
);
/* eslint-disable @typescript-eslint/no-throw-literal */
import { LoaderFunction, json } from '@remix-run/node';
import { useLoaderData } from '@remix-run/react';
import { Row } from '@nextui-org/react';
import { getCredits } from '~/services/tmdb/tmdb.server';
import { ICast } from '~/services/tmdb/tmdb.types';
import PeopleList from '~/src/components/people/PeopleList';

type LoaderData = {
cast: ICast[];
};

export const loader: LoaderFunction = async ({ params }) => {
const { movieId } = params;
const mid = Number(movieId);

if (!mid) throw new Response('Not found', { status: 404 });
const credits = await getCredits('movie', mid);

if (!credits) throw new Response('Not found', { status: 404 });

return json<LoaderData>({ cast: credits.cast });
};

const CastPage = () => {
const { cast } = useLoaderData<LoaderData>();

return (
<Row
fluid
justify="center"
align="center"
css={{
flexDirection: 'column',
'@xsMax': {
paddingLeft: 'calc(var(--nextui-space-sm))',
paddingRight: 'calc(var(--nextui-space-sm))',
},
'@xs': {
paddingLeft: '88px',
paddingRight: '1rem',
},
}}
>
{/*
TODO: Need react virtual to load this list
This list has a lot of items, so i limit it to 24 items until we install react virtual
*/}
{cast && cast.length > 0 && (
<PeopleList listType="grid" items={cast.slice(0, 24)} listName="Cast" />
)}
</Row>
);
};

export default CastPage;
62 changes: 54 additions & 8 deletions app/routes/movies/$movieId/crew.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,56 @@
import { Text } from '@nextui-org/react';

const CrewPage = () => (
<Text b h4 css={{ paddingLeft: '88px' }}>
{' '}
In development
</Text>
);
/* eslint-disable @typescript-eslint/no-throw-literal */
import { LoaderFunction, json } from '@remix-run/node';
import { useLoaderData } from '@remix-run/react';
import { Row } from '@nextui-org/react';
import { getCredits } from '~/services/tmdb/tmdb.server';
import { ICrew } from '~/services/tmdb/tmdb.types';
import PeopleList from '~/src/components/people/PeopleList';

type LoaderData = {
crew: ICrew[];
};

export const loader: LoaderFunction = async ({ params }) => {
const { movieId } = params;
const mid = Number(movieId);

if (!mid) throw new Response('Not found', { status: 404 });
const credits = await getCredits('movie', mid);

if (!credits) throw new Response('Not found', { status: 404 });

return json<LoaderData>({ crew: credits.crew });
};

const CrewPage = () => {
const { crew } = useLoaderData<LoaderData>();

return (
<Row
fluid
justify="center"
align="center"
css={{
flexDirection: 'column',
'@xsMax': {
paddingLeft: 'calc(var(--nextui-space-sm))',
paddingRight: 'calc(var(--nextui-space-sm))',
},
'@xs': {
paddingLeft: '88px',
paddingRight: '1rem',
},
}}
>
{/*
TODO: Need react virtual to load this list
This list has a lot of items, so i limit it to 24 items until we install react virtual
*/}
{crew && crew.length > 0 && (
<PeopleList listType="grid" items={crew.slice(0, 24)} listName="Crew" />
)}
</Row>
);
};

export default CrewPage;
93 changes: 60 additions & 33 deletions app/routes/movies/$movieId/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@ import { LoaderFunction, json } from '@remix-run/node';
import { useLoaderData, useNavigate } from '@remix-run/react';
import { Text, Row, Col, Spacer, Divider } from '@nextui-org/react';
import { useRouteData } from 'remix-utils';
import { getSimilar, getVideos, getCredits } from '~/services/tmdb/tmdb.server';
import { IMovieDetail } from '~/services/tmdb/tmdb.types';
import { getSimilar, getVideos, getCredits, getRecommendation } from '~/services/tmdb/tmdb.server';
import { IMovieDetail, ICast, ICrew } from '~/services/tmdb/tmdb.types';
import MediaList from '~/src/components/Media/MediaList';
import PeopleList from '~/src/components/people/PeopleList';
import useMediaQuery from '~/hooks/useMediaQuery';

type LoaderData = {
videos: Awaited<ReturnType<typeof getVideos>>;
credits: Awaited<ReturnType<typeof getCredits>>;
similar: Awaited<ReturnType<typeof getSimilar>>;
recommendations: Awaited<ReturnType<typeof getRecommendation>>;
topBilledCast: ICast[];
directors: ICrew[];
};

export const loader: LoaderFunction = async ({ params }) => {
Expand All @@ -24,21 +26,27 @@ export const loader: LoaderFunction = async ({ params }) => {
const similar = await getSimilar('movie', mid);
const videos = await getVideos('movie', mid);
const credits = await getCredits('movie', mid);
const recommendations = await getRecommendation('movie', mid);

if (!similar || !videos || !credits) throw new Response('Not Found', { status: 404 });
if (!similar || !videos || !credits || !recommendations)
throw new Response('Not Found', { status: 404 });

return json<LoaderData>({
videos,
credits,
similar,
recommendations,
topBilledCast: credits && credits.cast && credits.cast.slice(0, 9),
directors: credits && credits.crew && credits.crew.filter(({ job }) => job === 'Director'),
});
};

const Overview = () => {
const {
similar,
credits,
// videos,
recommendations,
topBilledCast,
directors,
} = useLoaderData<LoaderData>();
const movieData: { detail: IMovieDetail } | undefined = useRouteData('routes/movies/$movieId');
const detail = movieData && movieData.detail;
Expand All @@ -48,9 +56,7 @@ const Overview = () => {
const isSm = useMediaQuery(650, 'max');
// const isMd = useMediaQuery(960, 'max');
// const isMdLand = useMediaQuery(960, 'max', 'landscape');

const directors = credits?.crew.filter(({ job }) => job === 'Director');
const onClickViewMore = (type: 'cast' | 'similar') => {
const onClickViewMore = (type: 'cast' | 'similar' | 'recommendations') => {
navigate(`/movies/${detail?.id}/${type}`);
};
return (
Expand Down Expand Up @@ -271,38 +277,59 @@ const Overview = () => {
<Spacer y={1} />
<Divider x={1} css={{ m: 0 }} />
<Spacer y={1} />
{credits?.cast && credits.cast.length > 0 && (
<PeopleList
listType="slider-card"
items={credits?.cast.slice(0, 9)}
listName="Top Billed Cast"
showMoreList
onClickViewMore={() => onClickViewMore('cast')}
cardType="cast"
/>
{topBilledCast && topBilledCast.length > 0 && (
<>
<PeopleList
listType="slider-card"
items={topBilledCast}
listName="Top Billed Cast"
showMoreList
onClickViewMore={() => onClickViewMore('cast')}
cardType="cast"
/>
<Spacer y={1} />
<Divider x={1} css={{ m: 0 }} />
<Spacer y={1} />
</>
)}
<Spacer y={1} />
<Divider x={1} css={{ m: 0 }} />
<Spacer y={1} />

{/*
TODO: Videos
<Spacer y={1} />
<Divider x={1} css={{ m: 0 }} />
<Spacer y={1} />
*/}
{similar.items && similar.items.length > 0 && (
<MediaList
listType="slider-card"
items={similar.items}
listName="Similar Movies"
showMoreList
onClickViewMore={() => onClickViewMore('similar')}
cardType="similar-movie"
/>
{recommendations && recommendations.items && recommendations.items.length > 0 && (
<>
<MediaList
listType="slider-card"
items={recommendations.items}
listName="Recommendations"
showMoreList
onClickViewMore={() => onClickViewMore('recommendations')}
cardType="similar-movie"
/>
<Spacer y={1} />
<Divider x={1} css={{ m: 0 }} />
<Spacer y={1} />
</>
)}

{similar && similar.items && similar.items.length > 0 && (
<>
<MediaList
listType="slider-card"
items={similar.items}
listName="Similar Movies"
showMoreList
onClickViewMore={() => onClickViewMore('similar')}
cardType="similar-movie"
/>
<Spacer y={1} />
<Divider x={1} css={{ m: 0 }} />
<Spacer y={1} />
</>
)}
<Spacer y={1} />
<Divider x={1} css={{ m: 0 }} />
<Spacer y={1} />
</Col>
</Row>
);
Expand Down
80 changes: 80 additions & 0 deletions app/routes/movies/$movieId/recommendations.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/* eslint-disable @typescript-eslint/no-throw-literal */
import { LoaderFunction, json } from '@remix-run/node';
import { useLoaderData, useNavigate, Link, RouteMatch, useParams } from '@remix-run/react';
import { Row, Pagination } from '@nextui-org/react';
import { getRecommendation } from '~/services/tmdb/tmdb.server';
import MediaList from '~/src/components/Media/MediaList';
import useMediaQuery from '~/hooks/useMediaQuery';
import i18next from '~/i18n/i18next.server';

type LoaderData = {
recommendations: Awaited<ReturnType<typeof getRecommendation>>;
};

export const loader: LoaderFunction = async ({ request, params }) => {
const { movieId } = params;
const mid = Number(movieId);
if (!mid) throw new Response('Not Found', { status: 404 });

const locale = await i18next.getLocale(request);
const url = new URL(request.url);
let page = Number(url.searchParams.get('page')) || undefined;
if (page && (page < 1 || page > 1000)) page = 1;

const recommendations = await getRecommendation('movie', mid, page, locale);
if (!recommendations) throw new Response('Not Found', { status: 404 });

return json<LoaderData>({
recommendations,
});
};

export const handle = {
breadcrumb: (match: RouteMatch) => (
<Link to={`/movies/${match.params.movieId}/recommendations`}>Recommendations</Link>
),
};

const RecommendationsPage = () => {
const { movieId } = useParams();
const { recommendations } = useLoaderData<LoaderData>();
const navigate = useNavigate();
const isXs = useMediaQuery(650);
const paginationChangeHandler = (page: number) =>
navigate(`/movies/${movieId}/recommendations?page=${page}`);

return (
<Row
fluid
justify="center"
align="center"
css={{
flexDirection: 'column',
'@xsMax': {
paddingLeft: 'calc(var(--nextui-space-sm))',
paddingRight: 'calc(var(--nextui-space-sm))',
},
'@xs': {
paddingLeft: '88px',
paddingRight: '1rem',
},
}}
>
{recommendations && recommendations.items && recommendations.items.length > 0 && (
<>
<MediaList listType="grid" items={recommendations.items} listName="Recommendations" />
<Pagination
total={recommendations.totalPages}
initialPage={recommendations.page}
shadow
onChange={paginationChangeHandler}
css={{ marginTop: '30px' }}
{...(isXs && { size: 'xs' })}
/>
</>
)}
</Row>
);
};

export default RecommendationsPage;

1 comment on commit b7e18f6

@vercel
Copy link

@vercel vercel bot commented on b7e18f6 Aug 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.