Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#106, #103: add similar, recommendations, cast, crew tab for detail page #123

Merged
merged 4 commits into from
Aug 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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),
Copy link
Owner Author

Choose a reason for hiding this comment

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

@ctuanle ở đây mình có thể return về data mình cần được nơi m 🤟

Copy link
Collaborator

Choose a reason for hiding this comment

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

@Khanhtran47 return gì k hiểu nơi, chỗ lấy data từ routes parent á ?

Copy link
Owner Author

Choose a reason for hiding this comment

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

@ctuanle kiểu cái m nói chỉ lấy data cần thiết như bên graphQL ý, bên remix cái return ở chỗ loader, mình có thể return cái gì cũng đc

Copy link
Owner Author

Choose a reason for hiding this comment

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

@ctuanle m đọc cái bài ni cho dễ hiểu hơn 😅
https://sergiodxa.com/articles/load-only-the-data-you-need-in-remix

Copy link
Collaborator

Choose a reason for hiding this comment

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

@Khanhtran47 t nhớ chỗ loader đúng là mình return cái gì cũng được thật 😅

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;