33
resources/client/people/known-for-compact.tsx
Executable file
33
resources/client/people/known-for-compact.tsx
Executable file
@@ -0,0 +1,33 @@
|
||||
import {Person} from '@app/titles/models/person';
|
||||
import {BulletSeparatedItems} from '@app/titles/bullet-separated-items';
|
||||
import {Trans} from '@common/i18n/trans';
|
||||
import {TitleLink} from '@app/titles/title-link';
|
||||
import {HTMLAttributeAnchorTarget} from 'react';
|
||||
import {BaseMediaLinkProps} from '@app/base-media-link';
|
||||
|
||||
interface Props {
|
||||
person: Person;
|
||||
linkTarget?: HTMLAttributeAnchorTarget;
|
||||
linkColor?: BaseMediaLinkProps['color'];
|
||||
}
|
||||
export function KnownForCompact({
|
||||
person,
|
||||
linkTarget,
|
||||
linkColor = 'primary',
|
||||
}: Props) {
|
||||
return (
|
||||
<BulletSeparatedItems>
|
||||
{person.known_for ? <Trans message={person.known_for} /> : null}
|
||||
{person.primary_credit ? (
|
||||
<TitleLink
|
||||
target={linkTarget}
|
||||
color={linkColor}
|
||||
title={person.primary_credit}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</BulletSeparatedItems>
|
||||
);
|
||||
}
|
||||
43
resources/client/people/person-age.tsx
Executable file
43
resources/client/people/person-age.tsx
Executable file
@@ -0,0 +1,43 @@
|
||||
import {Person} from '@app/titles/models/person';
|
||||
import {FormattedDateTimeRange} from '@common/i18n/formatted-date-time-range';
|
||||
import {Fragment, memo} from 'react';
|
||||
|
||||
interface Props {
|
||||
person: Person;
|
||||
showRange?: boolean;
|
||||
}
|
||||
export const PersonAge = memo(({person, showRange}: Props) => {
|
||||
if (showRange && person.birth_date && person.death_date) {
|
||||
return (
|
||||
<FormattedDateTimeRange
|
||||
start={person.birth_date}
|
||||
end={person.death_date}
|
||||
options={{year: 'numeric'}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (person.birth_date) {
|
||||
return (
|
||||
<Fragment>
|
||||
{calculateAgeFromBirthDate(person.birth_date, person.death_date)}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
function calculateAgeFromBirthDate(
|
||||
_birthDate: string,
|
||||
_deathDate?: string
|
||||
): number {
|
||||
const until = _deathDate ? new Date(_deathDate) : new Date();
|
||||
const birthDate = new Date(_birthDate);
|
||||
let age = until.getFullYear() - birthDate.getFullYear();
|
||||
const m = until.getMonth() - birthDate.getMonth();
|
||||
if (m < 0 || (m === 0 && until.getDate() < birthDate.getDate())) {
|
||||
age--;
|
||||
}
|
||||
return age;
|
||||
}
|
||||
51
resources/client/people/person-link.tsx
Executable file
51
resources/client/people/person-link.tsx
Executable file
@@ -0,0 +1,51 @@
|
||||
import {Link, LinkProps} from 'react-router-dom';
|
||||
import clsx from 'clsx';
|
||||
import React, {ReactNode, useMemo} from 'react';
|
||||
import {slugifyString} from '@common/utils/string/slugify-string';
|
||||
import {getBootstrapData} from '@common/core/bootstrap-data/use-backend-bootstrap-data';
|
||||
import {Person} from '@app/titles/models/person';
|
||||
|
||||
interface Props extends Omit<LinkProps, 'to'> {
|
||||
person: Person;
|
||||
className?: string;
|
||||
children?: ReactNode;
|
||||
color?: 'primary' | 'inherit';
|
||||
}
|
||||
export function PersonLink({
|
||||
person,
|
||||
className,
|
||||
children,
|
||||
color = 'inherit',
|
||||
...linkProps
|
||||
}: Props) {
|
||||
const finalUri = useMemo(() => {
|
||||
return getPersonLink(person);
|
||||
}, [person]);
|
||||
|
||||
return (
|
||||
<Link
|
||||
{...linkProps}
|
||||
className={clsx(
|
||||
color === 'primary'
|
||||
? 'text-primary hover:text-primary-dark'
|
||||
: 'text-inherit',
|
||||
'hover:underline outline-none focus-visible:underline overflow-x-hidden overflow-ellipsis transition-colors',
|
||||
className
|
||||
)}
|
||||
to={finalUri}
|
||||
>
|
||||
{children ?? person.name}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
export function getPersonLink(
|
||||
person: Person,
|
||||
{absolute}: {absolute?: boolean} = {}
|
||||
): string {
|
||||
let link = `/people/${person.id}/${slugifyString(person.name)}`;
|
||||
if (absolute) {
|
||||
link = `${getBootstrapData().settings.base_url}${link}`;
|
||||
}
|
||||
return link;
|
||||
}
|
||||
24
resources/client/people/person-page/character-or-job.tsx
Executable file
24
resources/client/people/person-page/character-or-job.tsx
Executable file
@@ -0,0 +1,24 @@
|
||||
import {EpisodeCredit, PersonCredit} from '@app/titles/models/title';
|
||||
import {Trans} from '@common/i18n/trans';
|
||||
|
||||
interface Props {
|
||||
credit: PersonCredit | EpisodeCredit;
|
||||
className?: string;
|
||||
}
|
||||
export function CharacterOrJob({credit, className}: Props) {
|
||||
return (
|
||||
<div className={className}>
|
||||
{credit.pivot?.department === 'actors' ? (
|
||||
credit.pivot?.character ?? <Trans message="Unknown" />
|
||||
) : (
|
||||
<span className="capitalize">
|
||||
{credit.pivot?.job ? (
|
||||
<Trans message={credit.pivot?.job} />
|
||||
) : (
|
||||
<Trans message="Unknown" />
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
111
resources/client/people/person-page/person-page-aside.tsx
Executable file
111
resources/client/people/person-page/person-page-aside.tsx
Executable file
@@ -0,0 +1,111 @@
|
||||
import {
|
||||
DetailItem,
|
||||
TitlePageAsideLayout,
|
||||
} from '@app/titles/pages/title-page/title-page-aside-layout';
|
||||
import {PersonPoster} from '@app/people/person-poster/person-poster';
|
||||
import {Trans} from '@common/i18n/trans';
|
||||
import {FormattedDate} from '@common/i18n/formatted-date';
|
||||
import React from 'react';
|
||||
import {PersonAge} from '@app/people/person-age';
|
||||
import {GetPersonResponse} from '@app/people/requests/use-person';
|
||||
import {ShareMenuTrigger} from '@app/sharing/share-menu-trigger';
|
||||
import {Button} from '@common/ui/buttons/button';
|
||||
import {ShareIcon} from '@common/icons/material/Share';
|
||||
import {Person} from '@app/titles/models/person';
|
||||
import {getPersonLink} from '@app/people/person-link';
|
||||
import {IconButton} from '@common/ui/buttons/icon-button';
|
||||
import {Link} from 'react-router-dom';
|
||||
import {EditIcon} from '@common/icons/material/Edit';
|
||||
import {useAuth} from '@common/auth/use-auth';
|
||||
|
||||
interface Props {
|
||||
data: GetPersonResponse;
|
||||
}
|
||||
export function PersonPageAside({data: {person, total_credits_count}}: Props) {
|
||||
const {hasPermission} = useAuth();
|
||||
const age = (
|
||||
<Trans
|
||||
message=":count years old"
|
||||
values={{count: <PersonAge person={person} />}}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<TitlePageAsideLayout
|
||||
className="max-md:flex"
|
||||
poster={
|
||||
<div>
|
||||
<div className="relative">
|
||||
<PersonPoster person={person} size="w-140 md:w-full" srcSize="lg" />
|
||||
{hasPermission('titles.update') && (
|
||||
<IconButton
|
||||
elementType={Link}
|
||||
to={`/admin/people/${person.id}/edit`}
|
||||
className="absolute bottom-6 right-4"
|
||||
color="white"
|
||||
>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
</div>
|
||||
<ShareButton person={person} />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<dl className="mt-12 md:mt-24">
|
||||
{person.known_for && (
|
||||
<DetailItem label={<Trans message="Known for" />}>
|
||||
<Trans message={person.known_for} />
|
||||
</DetailItem>
|
||||
)}
|
||||
{person.gender && (
|
||||
<DetailItem label={<Trans message="Gender" />}>
|
||||
<span className="capitalize">
|
||||
<Trans message={person.gender} />
|
||||
</span>
|
||||
</DetailItem>
|
||||
)}
|
||||
{total_credits_count ? (
|
||||
<DetailItem label={<Trans message="Known credits" />}>
|
||||
{total_credits_count}
|
||||
</DetailItem>
|
||||
) : null}
|
||||
{person.birth_date ? (
|
||||
<DetailItem label={<Trans message="Born" />}>
|
||||
<FormattedDate date={person.birth_date} />{' '}
|
||||
{!person.death_date && age}
|
||||
</DetailItem>
|
||||
) : null}
|
||||
{person.birth_place ? (
|
||||
<DetailItem label={<Trans message="Birthplace" />}>
|
||||
{person.birth_place}
|
||||
</DetailItem>
|
||||
) : null}
|
||||
{person.death_date ? (
|
||||
<DetailItem label={<Trans message="Died" />}>
|
||||
<FormattedDate date={person.death_date} /> ({age})
|
||||
</DetailItem>
|
||||
) : null}
|
||||
</dl>
|
||||
</TitlePageAsideLayout>
|
||||
);
|
||||
}
|
||||
|
||||
interface ShareButtonProps {
|
||||
person: Person;
|
||||
}
|
||||
function ShareButton({person}: ShareButtonProps) {
|
||||
const link = getPersonLink(person, {absolute: true});
|
||||
return (
|
||||
<ShareMenuTrigger link={link}>
|
||||
<Button
|
||||
variant="outline"
|
||||
color="primary"
|
||||
startIcon={<ShareIcon />}
|
||||
className="mt-14 md:min-h-40 md:w-full"
|
||||
>
|
||||
<Trans message="Share" />
|
||||
</Button>
|
||||
</ShareMenuTrigger>
|
||||
);
|
||||
}
|
||||
151
resources/client/people/person-page/person-page-credits.tsx
Executable file
151
resources/client/people/person-page/person-page-credits.tsx
Executable file
@@ -0,0 +1,151 @@
|
||||
import {GetPersonResponse} from '@app/people/requests/use-person';
|
||||
import {Accordion, AccordionItem} from '@common/ui/accordion/accordion';
|
||||
import {Trans} from '@common/i18n/trans';
|
||||
import {TitlePoster} from '@app/titles/title-poster/title-poster';
|
||||
import {SiteSectionHeading} from '@app/titles/site-section-heading';
|
||||
import {TitleLink} from '@app/titles/title-link';
|
||||
import {PersonCredit} from '@app/titles/models/title';
|
||||
import {BulletSeparatedItems} from '@app/titles/bullet-separated-items';
|
||||
import {CompactSeasonEpisode} from '@app/episodes/compact-season-episode';
|
||||
import {EpisodeLink} from '@app/episodes/episode-link';
|
||||
import {Button} from '@common/ui/buttons/button';
|
||||
import {Fragment, useState} from 'react';
|
||||
import {useFullPersonCreditsForTitle} from '@app/people/requests/use-full-person-credits-for-title';
|
||||
import {CharacterOrJob} from '@app/people/person-page/character-or-job';
|
||||
import {Person} from '@app/titles/models/person';
|
||||
|
||||
interface Props {
|
||||
data: GetPersonResponse;
|
||||
}
|
||||
export function PersonPageCredits({data: {credits, person}}: Props) {
|
||||
return (
|
||||
<div className="mt-34">
|
||||
<SiteSectionHeading fontSize="text-xl">
|
||||
<Trans message="Credits" />
|
||||
</SiteSectionHeading>
|
||||
<Accordion mode="multiple" defaultExpandedValues={[0]} isLazy>
|
||||
{Object.entries(credits).map(([department, credits]) => (
|
||||
<AccordionItem
|
||||
labelClassName="font-semibold text-base"
|
||||
description={
|
||||
<Trans
|
||||
message="(:count credits)"
|
||||
values={{count: credits.length}}
|
||||
/>
|
||||
}
|
||||
key={department}
|
||||
label={
|
||||
<span className="capitalize">
|
||||
<Trans
|
||||
message={department === 'actors' ? 'Acting' : department}
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
>
|
||||
{credits.map((credit, index) => {
|
||||
const isLast = credit === credits[credits.length - 1];
|
||||
return (
|
||||
<Fragment key={credit.id}>
|
||||
<div className="flex items-start py-6">
|
||||
<TitlePoster
|
||||
title={credit}
|
||||
size="w-40"
|
||||
className="mr-12"
|
||||
lazy
|
||||
srcSize="sm"
|
||||
/>
|
||||
<div className="mr-24 pt-2">
|
||||
<div className="font-semibold text-base">
|
||||
<TitleLink title={credit} />
|
||||
</div>
|
||||
<CharacterOrJob
|
||||
className="text-sm text-muted"
|
||||
credit={credit}
|
||||
/>
|
||||
{credit.credited_episode_count ? (
|
||||
<EpisodeList
|
||||
credit={credit}
|
||||
department={department}
|
||||
person={person}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="text-sm text-muted ml-auto">
|
||||
{credit.year}
|
||||
</div>
|
||||
</div>
|
||||
{!isLast && credit.year !== credits[index + 1]?.year && (
|
||||
<div className="h-1 w-full bg-divider my-8" />
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</AccordionItem>
|
||||
))}
|
||||
</Accordion>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface EpisodeListProps {
|
||||
credit: PersonCredit;
|
||||
department: string;
|
||||
person: Person;
|
||||
}
|
||||
function EpisodeList({credit, department, person}: EpisodeListProps) {
|
||||
const [loadMoreEpisodes, setLoadMoreEpisodes] = useState(false);
|
||||
const query = useFullPersonCreditsForTitle(
|
||||
{person, department, credit},
|
||||
{
|
||||
enabled: loadMoreEpisodes,
|
||||
}
|
||||
);
|
||||
const allEpisodesLoaded =
|
||||
credit.episodes.length === credit.credited_episode_count ||
|
||||
query.data != null;
|
||||
const isLoadingMore = query.isLoading && query.fetchStatus !== 'idle';
|
||||
const shouldShowLoadMoreBtn = isLoadingMore || !allEpisodesLoaded;
|
||||
const episodeCredits = query.data?.credits.length
|
||||
? query.data.credits
|
||||
: credit.episodes;
|
||||
|
||||
return (
|
||||
<div className="mt-4">
|
||||
<div>
|
||||
{episodeCredits.map(episodeCredit => (
|
||||
<div className="text-xs pl-10 mb-4" key={episodeCredit.id}>
|
||||
<BulletSeparatedItems>
|
||||
<span>
|
||||
-{' '}
|
||||
<EpisodeLink
|
||||
title={credit}
|
||||
episode={episodeCredit}
|
||||
seasonNumber={episodeCredit.season_number}
|
||||
/>{' '}
|
||||
({episodeCredit.year})
|
||||
</span>
|
||||
<CompactSeasonEpisode episode={episodeCredit} />
|
||||
<CharacterOrJob credit={episodeCredit} />
|
||||
</BulletSeparatedItems>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{shouldShowLoadMoreBtn && (
|
||||
<div className="mt-8">
|
||||
<Button
|
||||
size="xs"
|
||||
disabled={isLoadingMore}
|
||||
onClick={() => {
|
||||
setLoadMoreEpisodes(true);
|
||||
}}
|
||||
>
|
||||
<Trans
|
||||
message="Show all :count episodes"
|
||||
values={{count: credit.credited_episode_count}}
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
98
resources/client/people/person-page/person-page.tsx
Executable file
98
resources/client/people/person-page/person-page.tsx
Executable file
@@ -0,0 +1,98 @@
|
||||
import {PageStatus} from '@common/http/page-status';
|
||||
import {PageMetaTags} from '@common/http/page-meta-tags';
|
||||
import React, {Fragment} from 'react';
|
||||
import {SitePageLayout} from '@app/site-page-layout';
|
||||
import {GetPersonResponse, usePerson} from '@app/people/requests/use-person';
|
||||
import {Person} from '@app/titles/models/person';
|
||||
import {Trans} from '@common/i18n/trans';
|
||||
import {TitlePageHeaderLayout} from '@app/titles/pages/title-page/title-page-header-layout';
|
||||
import {SiteSectionHeading} from '@app/titles/site-section-heading';
|
||||
import {PersonPageAside} from '@app/people/person-page/person-page-aside';
|
||||
import {ContentGridLayout} from '@app/channels/content-grid/content-grid-layout';
|
||||
import {PersonCredit} from '@app/titles/models/title';
|
||||
import {TitlePortraitGridItem} from '@app/channels/content-grid/title-grid-item';
|
||||
import {PersonPageCredits} from '@app/people/person-page/person-page-credits';
|
||||
import {TruncatedDescription} from '@common/ui/truncated-description';
|
||||
import {CharacterOrJob} from '@app/people/person-page/character-or-job';
|
||||
import {AdHost} from '@common/admin/ads/ad-host';
|
||||
|
||||
export function PersonPage() {
|
||||
const query = usePerson('personPage');
|
||||
|
||||
const content = query.data ? (
|
||||
<Fragment>
|
||||
<PageMetaTags query={query} />
|
||||
<PageContent data={query.data} />
|
||||
</Fragment>
|
||||
) : (
|
||||
<PageStatus query={query} loaderClassName="absolute inset-0 m-auto" />
|
||||
);
|
||||
|
||||
return <SitePageLayout>{content}</SitePageLayout>;
|
||||
}
|
||||
|
||||
interface PageContentProps {
|
||||
data: GetPersonResponse;
|
||||
}
|
||||
function PageContent({data}: PageContentProps) {
|
||||
const {person, knownFor} = data;
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="container mx-auto mt-14 px-14 md:mt-40 md:px-24">
|
||||
<div className="items-start gap-54 md:flex">
|
||||
<PersonPageAside data={data} />
|
||||
<main className="flex-auto @container max-md:mt-34">
|
||||
<TitlePageHeaderLayout name={person.name} />
|
||||
<Biography person={person} />
|
||||
<AdHost slot="person_top" className="pt-48" />
|
||||
<KnowForList items={knownFor} />
|
||||
<PersonPageCredits data={data} />
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
interface BiographyProps {
|
||||
person: Person;
|
||||
}
|
||||
function Biography({person}: BiographyProps) {
|
||||
if (!person.description) return null;
|
||||
return (
|
||||
<Fragment>
|
||||
<SiteSectionHeading fontSize="text-xl">
|
||||
<Trans message="Biography" />
|
||||
</SiteSectionHeading>
|
||||
<TruncatedDescription
|
||||
className="text-sm"
|
||||
description={person.description}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
interface KnownForProps {
|
||||
items: PersonCredit[];
|
||||
}
|
||||
function KnowForList({items}: KnownForProps) {
|
||||
if (!items?.length) return null;
|
||||
return (
|
||||
<div className="mt-34">
|
||||
<SiteSectionHeading fontSize="text-xl">
|
||||
<Trans message="Known for" />
|
||||
</SiteSectionHeading>
|
||||
<ContentGridLayout variant="portrait">
|
||||
{items.slice(0, 4).map(item => (
|
||||
<TitlePortraitGridItem
|
||||
key={item.id}
|
||||
item={item}
|
||||
description={
|
||||
<CharacterOrJob className="text-muted" credit={item} />
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</ContentGridLayout>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
58
resources/client/people/person-poster/person-poster.tsx
Executable file
58
resources/client/people/person-poster/person-poster.tsx
Executable file
@@ -0,0 +1,58 @@
|
||||
import {useTrans} from '@common/i18n/use-trans';
|
||||
import {message} from '@common/i18n/message';
|
||||
import clsx from 'clsx';
|
||||
import {Person} from '@app/titles/models/person';
|
||||
import {PersonLink} from '@app/people/person-link';
|
||||
import {ImageSize, useImageSrc} from '@app/images/use-image-src';
|
||||
import {PersonIcon} from '@common/icons/material/Person';
|
||||
|
||||
interface Props {
|
||||
person: Person;
|
||||
className?: string;
|
||||
size?: string;
|
||||
lazy?: boolean;
|
||||
srcSize?: ImageSize;
|
||||
rounded?: boolean;
|
||||
}
|
||||
export function PersonPoster({
|
||||
person,
|
||||
className,
|
||||
size,
|
||||
srcSize,
|
||||
lazy = true,
|
||||
rounded = false,
|
||||
}: Props) {
|
||||
const {trans} = useTrans();
|
||||
const src = useImageSrc(person?.poster, {size: srcSize});
|
||||
|
||||
const imageClassName = clsx(
|
||||
className,
|
||||
size,
|
||||
'bg-fg-base/4 object-cover',
|
||||
rounded ? 'aspect-square rounded-full' : 'aspect-poster rounded',
|
||||
!src ? 'flex items-center justify-center' : 'block'
|
||||
);
|
||||
|
||||
const image = src ? (
|
||||
<img
|
||||
decoding="async"
|
||||
className={imageClassName}
|
||||
draggable={false}
|
||||
loading={lazy ? 'lazy' : 'eager'}
|
||||
src={src}
|
||||
alt={trans(
|
||||
message('Cover image for :name', {values: {name: person.name}})
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<span className={imageClassName}>
|
||||
<PersonIcon className="max-w-[60%] text-divider" size="text-5xl" />
|
||||
</span>
|
||||
);
|
||||
|
||||
return (
|
||||
<PersonLink person={person} className="flex-shrink-0">
|
||||
{image}
|
||||
</PersonLink>
|
||||
);
|
||||
}
|
||||
48
resources/client/people/requests/use-full-person-credits-for-title.ts
Executable file
48
resources/client/people/requests/use-full-person-credits-for-title.ts
Executable file
@@ -0,0 +1,48 @@
|
||||
import {useQuery} from '@tanstack/react-query';
|
||||
import {apiClient} from '@common/http/query-client';
|
||||
import {BackendResponse} from '@common/http/backend-response/backend-response';
|
||||
import {EpisodeCredit, PersonCredit} from '@app/titles/models/title';
|
||||
import {Person} from '@app/titles/models/person';
|
||||
|
||||
export interface GetFullTitleCreditsResponse extends BackendResponse {
|
||||
credits: EpisodeCredit[];
|
||||
}
|
||||
|
||||
interface Params {
|
||||
credit: PersonCredit;
|
||||
department: string;
|
||||
person: Person;
|
||||
}
|
||||
|
||||
interface Options {
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
export function useFullPersonCreditsForTitle(
|
||||
{person, credit, department}: Params,
|
||||
options: Options,
|
||||
) {
|
||||
return useQuery({
|
||||
queryKey: [
|
||||
'people',
|
||||
`${person.id}`,
|
||||
'full-credits',
|
||||
`${credit.id}`,
|
||||
`${department}`,
|
||||
],
|
||||
queryFn: () => fetchCredits(person.id, credit.id, department),
|
||||
enabled: options.enabled,
|
||||
});
|
||||
}
|
||||
|
||||
function fetchCredits(
|
||||
personId: number | string,
|
||||
titleId: number | string,
|
||||
department: string,
|
||||
) {
|
||||
return apiClient
|
||||
.get<GetFullTitleCreditsResponse>(
|
||||
`people/${personId}/full-credits/${titleId}/${department}`,
|
||||
)
|
||||
.then(response => response.data);
|
||||
}
|
||||
35
resources/client/people/requests/use-person.ts
Executable file
35
resources/client/people/requests/use-person.ts
Executable file
@@ -0,0 +1,35 @@
|
||||
import {useQuery} from '@tanstack/react-query';
|
||||
import {apiClient} from '@common/http/query-client';
|
||||
import {useParams} from 'react-router-dom';
|
||||
import {BackendResponse} from '@common/http/backend-response/backend-response';
|
||||
import {Person} from '@app/titles/models/person';
|
||||
import {PersonCredit} from '@app/titles/models/title';
|
||||
import {getBootstrapData} from '@common/core/bootstrap-data/use-backend-bootstrap-data';
|
||||
|
||||
export interface GetPersonResponse extends BackendResponse {
|
||||
person: Person;
|
||||
knownFor: PersonCredit[];
|
||||
credits: Record<string, PersonCredit[]>;
|
||||
total_credits_count: number;
|
||||
}
|
||||
|
||||
export function usePerson(loader: 'personPage' | 'editPersonPage') {
|
||||
const {personId} = useParams();
|
||||
return useQuery({
|
||||
queryKey: ['people', `${personId}`, loader],
|
||||
queryFn: () => fetchPerson(personId!, loader),
|
||||
initialData: () => {
|
||||
const data = getBootstrapData().loaders?.[loader];
|
||||
if (data?.person?.id == personId) {
|
||||
return data;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function fetchPerson(personId: number | string, loader: string) {
|
||||
return apiClient
|
||||
.get<GetPersonResponse>(`people/${personId}`, {params: {loader}})
|
||||
.then(response => response.data);
|
||||
}
|
||||
Reference in New Issue
Block a user