mirror of
https://github.com/sct/overseerr.git
synced 2025-09-17 17:24:35 +02:00
feat(frontend): add full crew page for movies/shows
This commit is contained in:
@@ -42,11 +42,11 @@ const MovieCast: React.FC = () => {
|
||||
{intl.formatMessage(messages.fullcast)}
|
||||
</Header>
|
||||
<ul className="grid grid-cols-2 gap-6 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-7 2xl:grid-cols-8">
|
||||
{data?.credits.cast.map((person) => {
|
||||
{data?.credits.cast.map((person, index) => {
|
||||
return (
|
||||
<li
|
||||
key={person.id}
|
||||
className="col-span-1 flex flex-col text-center items-center"
|
||||
key={`cast-${person.id}-${index}`}
|
||||
className="flex flex-col items-center col-span-1 text-center"
|
||||
>
|
||||
<PersonCard
|
||||
name={person.name}
|
||||
|
66
src/components/MovieDetails/MovieCrew/index.tsx
Normal file
66
src/components/MovieDetails/MovieCrew/index.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import React, { useContext } from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import useSWR from 'swr';
|
||||
import { MovieDetails } from '../../../../server/models/Movie';
|
||||
import { LanguageContext } from '../../../context/LanguageContext';
|
||||
import Error from '../../../pages/_error';
|
||||
import Header from '../../Common/Header';
|
||||
import LoadingSpinner from '../../Common/LoadingSpinner';
|
||||
import PersonCard from '../../PersonCard';
|
||||
|
||||
const messages = defineMessages({
|
||||
fullcrew: 'Full Crew',
|
||||
});
|
||||
|
||||
const MovieCrew: React.FC = () => {
|
||||
const router = useRouter();
|
||||
const intl = useIntl();
|
||||
const { locale } = useContext(LanguageContext);
|
||||
const { data, error } = useSWR<MovieDetails>(
|
||||
`/api/v1/movie/${router.query.movieId}?language=${locale}`
|
||||
);
|
||||
|
||||
if (!data && !error) {
|
||||
return <LoadingSpinner />;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return <Error statusCode={404} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header
|
||||
subtext={
|
||||
<Link href={`/movie/${data.id}`}>
|
||||
<a className="hover:underline">{data.title}</a>
|
||||
</Link>
|
||||
}
|
||||
>
|
||||
{intl.formatMessage(messages.fullcrew)}
|
||||
</Header>
|
||||
<ul className="grid grid-cols-2 gap-6 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-7 2xl:grid-cols-8">
|
||||
{data?.credits.crew.map((person, index) => {
|
||||
return (
|
||||
<li
|
||||
key={`crew-${person.id}-${index}`}
|
||||
className="flex flex-col items-center col-span-1 text-center"
|
||||
>
|
||||
<PersonCard
|
||||
name={person.name}
|
||||
personId={person.id}
|
||||
subName={person.job}
|
||||
profilePath={person.profilePath}
|
||||
canExpand
|
||||
/>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MovieCrew;
|
@@ -68,6 +68,7 @@ const messages = defineMessages({
|
||||
approve: 'Approve',
|
||||
decline: 'Decline',
|
||||
studio: 'Studio',
|
||||
viewfullcrew: 'View Full Crew',
|
||||
});
|
||||
|
||||
interface MovieDetailsProps {
|
||||
@@ -412,6 +413,29 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{sortedCrew.length > 0 && (
|
||||
<div className="flex justify-end mt-4">
|
||||
<Link href={`/movie/${data.id}/crew`}>
|
||||
<a className="flex items-center text-gray-400 transition duration-300 hover:text-gray-100">
|
||||
<span>{intl.formatMessage(messages.viewfullcrew)}</span>
|
||||
<svg
|
||||
className="inline-block w-5 h-5 ml-1"
|
||||
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 className="w-full mt-8 md:w-80 md:mt-0">
|
||||
<div className="bg-gray-900 border border-gray-800 rounded-lg shadow">
|
||||
|
66
src/components/TvDetails/TvCrew/index.tsx
Normal file
66
src/components/TvDetails/TvCrew/index.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import React, { useContext } from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import useSWR from 'swr';
|
||||
import type { TvDetails } from '../../../../server/models/Tv';
|
||||
import { LanguageContext } from '../../../context/LanguageContext';
|
||||
import Error from '../../../pages/_error';
|
||||
import Header from '../../Common/Header';
|
||||
import LoadingSpinner from '../../Common/LoadingSpinner';
|
||||
import PersonCard from '../../PersonCard';
|
||||
|
||||
const messages = defineMessages({
|
||||
fullseriescrew: 'Full Series Crew',
|
||||
});
|
||||
|
||||
const TvCrew: React.FC = () => {
|
||||
const router = useRouter();
|
||||
const intl = useIntl();
|
||||
const { locale } = useContext(LanguageContext);
|
||||
const { data, error } = useSWR<TvDetails>(
|
||||
`/api/v1/tv/${router.query.tvId}?language=${locale}`
|
||||
);
|
||||
|
||||
if (!data && !error) {
|
||||
return <LoadingSpinner />;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return <Error statusCode={404} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header
|
||||
subtext={
|
||||
<Link href={`/tv/${data.id}`}>
|
||||
<a className="hover:underline">{data.name}</a>
|
||||
</Link>
|
||||
}
|
||||
>
|
||||
{intl.formatMessage(messages.fullseriescrew)}
|
||||
</Header>
|
||||
<ul className="grid grid-cols-2 gap-6 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-7 2xl:grid-cols-8">
|
||||
{data?.credits.crew.map((person, index) => {
|
||||
return (
|
||||
<li
|
||||
key={`crew-${person.id}-${index}`}
|
||||
className="flex flex-col items-center col-span-1 text-center"
|
||||
>
|
||||
<PersonCard
|
||||
name={person.name}
|
||||
personId={person.id}
|
||||
subName={person.job}
|
||||
profilePath={person.profilePath}
|
||||
canExpand
|
||||
/>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default TvCrew;
|
@@ -63,6 +63,7 @@ const messages = defineMessages({
|
||||
showtype: 'Show Type',
|
||||
anime: 'Anime',
|
||||
network: 'Network',
|
||||
viewfullcrew: 'View Full Crew',
|
||||
});
|
||||
|
||||
interface TvDetailsProps {
|
||||
@@ -436,6 +437,29 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{sortedCrew.length > 0 && (
|
||||
<div className="flex justify-end mt-4">
|
||||
<Link href={`/tv/${data.id}/crew`}>
|
||||
<a className="flex items-center text-gray-400 transition duration-300 hover:text-gray-100">
|
||||
<span>{intl.formatMessage(messages.viewfullcrew)}</span>
|
||||
<svg
|
||||
className="inline-block w-5 h-5 ml-1"
|
||||
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 className="w-full mt-8 md:w-80 md:mt-0">
|
||||
<div className="bg-gray-900 border border-gray-800 rounded-lg shadow">
|
||||
|
@@ -19,6 +19,7 @@
|
||||
"components.Layout.alphawarning": "This is ALPHA software. Almost everything is bound to be nearly broken and/or unstable. Please report issues to the Overseerr GitHub!",
|
||||
"components.Login.signinplex": "Sign in to continue",
|
||||
"components.MovieDetails.MovieCast.fullcast": "Full Cast",
|
||||
"components.MovieDetails.MovieCrew.fullcrew": "Full Crew",
|
||||
"components.MovieDetails.approve": "Approve",
|
||||
"components.MovieDetails.available": "Available",
|
||||
"components.MovieDetails.budget": "Budget",
|
||||
@@ -46,9 +47,11 @@
|
||||
"components.MovieDetails.studio": "Studio",
|
||||
"components.MovieDetails.unavailable": "Unavailable",
|
||||
"components.MovieDetails.userrating": "User Rating",
|
||||
"components.MovieDetails.viewfullcrew": "View Full Crew",
|
||||
"components.MovieDetails.viewrequest": "View Request",
|
||||
"components.PersonDetails.appearsin": "Appears in",
|
||||
"components.PersonDetails.ascharacter": "as {character}",
|
||||
"components.PersonDetails.crewmember": "Crew Member",
|
||||
"components.PersonDetails.nobiography": "No biography available.",
|
||||
"components.PlexLoginButton.loading": "Loading…",
|
||||
"components.PlexLoginButton.loggingin": "Logging in…",
|
||||
@@ -277,6 +280,7 @@
|
||||
"components.TitleCard.movie": "Movie",
|
||||
"components.TitleCard.tvshow": "Series",
|
||||
"components.TvDetails.TvCast.fullseriescast": "Full Series Cast",
|
||||
"components.TvDetails.TvCrew.fullseriescrew": "Full Series Crew",
|
||||
"components.TvDetails.anime": "Anime",
|
||||
"components.TvDetails.approve": "Approve",
|
||||
"components.TvDetails.approverequests": "Approve {requestCount} {requestCount, plural, one {Request} other {Requests}}",
|
||||
@@ -305,6 +309,7 @@
|
||||
"components.TvDetails.status": "Status",
|
||||
"components.TvDetails.unavailable": "Unavailable",
|
||||
"components.TvDetails.userrating": "User Rating",
|
||||
"components.TvDetails.viewfullcrew": "View Full Crew",
|
||||
"components.UserEdit.admin": "Admin",
|
||||
"components.UserEdit.adminDescription": "Full administrator access. Bypasses all permission checks.",
|
||||
"components.UserEdit.autoapprove": "Auto Approve",
|
||||
|
9
src/pages/movie/[movieId]/crew.tsx
Normal file
9
src/pages/movie/[movieId]/crew.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import { NextPage } from 'next';
|
||||
import React from 'react';
|
||||
import MovieCrew from '../../../components/MovieDetails/MovieCrew';
|
||||
|
||||
const MovieCrewPage: NextPage = () => {
|
||||
return <MovieCrew />;
|
||||
};
|
||||
|
||||
export default MovieCrewPage;
|
9
src/pages/tv/[tvId]/crew.tsx
Normal file
9
src/pages/tv/[tvId]/crew.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import { NextPage } from 'next';
|
||||
import React from 'react';
|
||||
import TvCrew from '../../../components/TvDetails/TvCrew';
|
||||
|
||||
const TvCrewPage: NextPage = () => {
|
||||
return <TvCrew />;
|
||||
};
|
||||
|
||||
export default TvCrewPage;
|
Reference in New Issue
Block a user