feat: implement episode API
This commit is contained in:
parent
028aa01682
commit
9439f26c60
|
@ -7,11 +7,12 @@ import { playerState } from '../GlobalAudioPlayer/globalAudioPlayer.state'
|
|||
import { useHookstate } from '@hookstate/core'
|
||||
|
||||
interface Props {
|
||||
data: LPE.Podcast.Document
|
||||
episode: LPE.Podcast.Document
|
||||
relatedEpisodes: LPE.Podcast.Document[]
|
||||
}
|
||||
|
||||
export default function EpisodeBody({ data }: Props) {
|
||||
const youtube = data?.channels.find(
|
||||
export default function EpisodeBody({ episode, relatedEpisodes }: Props) {
|
||||
const youtube = episode?.channels.find(
|
||||
(channel) => channel?.name === LPE.Podcast.ChannelNames.Youtube,
|
||||
)
|
||||
|
||||
|
@ -21,12 +22,12 @@ export default function EpisodeBody({ data }: Props) {
|
|||
return (
|
||||
<EpisodeContainer>
|
||||
<EpisodeHeader
|
||||
{...data}
|
||||
{...episode}
|
||||
url={youtube?.url as string}
|
||||
duration={duration}
|
||||
/>
|
||||
<EpisodeTranscript data={data} />
|
||||
<EpisodeFooter data={data} />
|
||||
<EpisodeTranscript episode={episode} />
|
||||
<EpisodeFooter episode={episode} relatedEpisodes={relatedEpisodes} />
|
||||
</EpisodeContainer>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -4,14 +4,14 @@ import EpisodeBlocks from './Episode.Blocks'
|
|||
import { Typography } from '@acid-info/lsd-react'
|
||||
import EpisodeDivider from './Episode.Divider'
|
||||
|
||||
const EpisodeTranscript = ({ data }: { data: LPE.Podcast.Document }) => {
|
||||
const EpisodeTranscript = ({ episode }: { episode: LPE.Podcast.Document }) => {
|
||||
return (
|
||||
<>
|
||||
<EpisodeDivider />
|
||||
<Title component="h6" variant="h6">
|
||||
Transcript
|
||||
</Title>
|
||||
<EpisodeBlocks data={data} />
|
||||
<EpisodeBlocks data={episode} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -5,67 +5,30 @@ import RelatedEpisodes from './Episode.RelatedEpisodes'
|
|||
import { useMemo } from 'react'
|
||||
import EpisodeFootnotes from './Episode.Footnotes'
|
||||
|
||||
const TEMP_MORE_EPISODES = [
|
||||
{
|
||||
id: 1,
|
||||
thumbnail:
|
||||
'https://images.cdn.unbody.io/00f8908f-9dff-456e-9640-13defd9ae433/image/a04e5542-d027-44d5-b914-bd4cadf17d25_image1.png',
|
||||
publishedAt: '2023-07-11T20:30:00.000Z',
|
||||
title: 'Title 1',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
thumbnail:
|
||||
'https://images.cdn.unbody.io/00f8908f-9dff-456e-9640-13defd9ae433/image/a04e5542-d027-44d5-b914-bd4cadf17d25_image1.png',
|
||||
publishedAt: '2023-07-12T20:30:00.000Z',
|
||||
title: 'Title 2',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
thumbnail:
|
||||
'https://images.cdn.unbody.io/00f8908f-9dff-456e-9640-13defd9ae433/image/a04e5542-d027-44d5-b914-bd4cadf17d25_image1.png',
|
||||
publishedAt: '2023-07-13T20:30:00.000Z',
|
||||
title: 'Title 3',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
thumbnail:
|
||||
'https://images.cdn.unbody.io/00f8908f-9dff-456e-9640-13defd9ae433/image/a04e5542-d027-44d5-b914-bd4cadf17d25_image1.png',
|
||||
publishedAt: '2023-07-14T20:30:00.000Z',
|
||||
title: 'Title 4',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
thumbnail:
|
||||
'https://images.cdn.unbody.io/00f8908f-9dff-456e-9640-13defd9ae433/image/a04e5542-d027-44d5-b914-bd4cadf17d25_image1.png',
|
||||
publishedAt: '2023-07-14T20:30:00.000Z',
|
||||
title: 'Title 5',
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
thumbnail:
|
||||
'https://images.cdn.unbody.io/00f8908f-9dff-456e-9640-13defd9ae433/image/a04e5542-d027-44d5-b914-bd4cadf17d25_image1.png',
|
||||
publishedAt: '2023-07-14T20:30:00.000Z',
|
||||
title: 'Title 6',
|
||||
},
|
||||
]
|
||||
type Props = {
|
||||
episode: LPE.Podcast.Document
|
||||
relatedEpisodes: LPE.Podcast.Document[]
|
||||
}
|
||||
|
||||
const EpisodeFooter = ({ data }: { data: LPE.Podcast.Document }) => {
|
||||
const EpisodeFooter = ({ episode, relatedEpisodes }: Props) => {
|
||||
const footnotes = useMemo(() => {
|
||||
return (
|
||||
data.credits &&
|
||||
data.credits
|
||||
episode.credits &&
|
||||
episode.credits
|
||||
.filter((b) => b.footnotes.length)
|
||||
.map((b) => b.footnotes)
|
||||
.flat()
|
||||
)
|
||||
}, [data])
|
||||
}, [episode])
|
||||
|
||||
return (
|
||||
<EpisodeFooterContainer>
|
||||
{footnotes?.length && <EpisodeFootnotes footnotes={footnotes} />}
|
||||
{data?.credits?.length && <EpisodeCredits credits={data.credits} />}
|
||||
<RelatedEpisodes relatedEpisodes={TEMP_MORE_EPISODES} />
|
||||
{episode?.credits?.length && <EpisodeCredits credits={episode.credits} />}
|
||||
<RelatedEpisodes
|
||||
podcastSlug={episode.show?.slug as string}
|
||||
relatedEpisodes={relatedEpisodes}
|
||||
/>
|
||||
</EpisodeFooterContainer>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,20 +1,29 @@
|
|||
import { LPE } from '@/types/lpe.types'
|
||||
import { Typography } from '@acid-info/lsd-react'
|
||||
import styled from '@emotion/styled'
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
|
||||
type Props = {
|
||||
thumbnail: string
|
||||
coverImage: LPE.Image.Document
|
||||
title: string
|
||||
publishedAt: string
|
||||
slug: string
|
||||
}
|
||||
|
||||
const MoreEpisodesCard = ({ thumbnail, title, publishedAt }: Props) => {
|
||||
const MoreEpisodesCard = ({ coverImage, title, publishedAt, slug }: Props) => {
|
||||
const date = new Date(publishedAt)
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<ImageContainer>
|
||||
<Image src={thumbnail} fill alt={thumbnail} />
|
||||
</ImageContainer>
|
||||
{coverImage?.url && (
|
||||
<CustomLink href={`/podcasts/${slug}`}>
|
||||
<ImageContainer>
|
||||
<Image src={coverImage.url} fill alt={coverImage.alt} />
|
||||
</ImageContainer>
|
||||
</CustomLink>
|
||||
)}
|
||||
|
||||
<Row>
|
||||
<Typography variant="body3" genericFontFamily="sans-serif">
|
||||
PODCAST
|
||||
|
@ -29,9 +38,11 @@ const MoreEpisodesCard = ({ thumbnail, title, publishedAt }: Props) => {
|
|||
})}
|
||||
</Typography>
|
||||
</Row>
|
||||
<Typography variant="h6" genericFontFamily="serif">
|
||||
{title}
|
||||
</Typography>
|
||||
<CustomLink href={`/podcasts/${slug}`}>
|
||||
<Typography variant="h6" genericFontFamily="serif">
|
||||
{title}
|
||||
</Typography>
|
||||
</CustomLink>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
@ -58,4 +69,8 @@ const Row = styled.div`
|
|||
margin-bottom: 8px;
|
||||
`
|
||||
|
||||
const CustomLink = styled(Link)`
|
||||
text-decoration: none;
|
||||
`
|
||||
|
||||
export default MoreEpisodesCard
|
||||
|
|
|
@ -2,8 +2,14 @@ import { Button, Typography } from '@acid-info/lsd-react'
|
|||
import styled from '@emotion/styled'
|
||||
import MoreEpisodesCard from './Episode.MoreEpisodesCard'
|
||||
import { useState } from 'react'
|
||||
import { LPE } from '@/types/lpe.types'
|
||||
|
||||
const RelatedEpisodes = ({ relatedEpisodes }: any) => {
|
||||
type props = {
|
||||
podcastSlug: string
|
||||
relatedEpisodes: LPE.Podcast.Document[]
|
||||
}
|
||||
|
||||
const RelatedEpisodes = ({ podcastSlug, relatedEpisodes }: props) => {
|
||||
const [showMore, setShowMore] = useState(false)
|
||||
|
||||
return (
|
||||
|
@ -14,9 +20,10 @@ const RelatedEpisodes = ({ relatedEpisodes }: any) => {
|
|||
? relatedEpisodes.map((episode: any, idx: number) => (
|
||||
<MoreEpisodesCard
|
||||
key={'related-episode' + idx}
|
||||
thumbnail={episode.thumbnail}
|
||||
coverImage={episode.coverImage}
|
||||
title={episode.title}
|
||||
publishedAt={episode.publishedAt}
|
||||
slug={`${podcastSlug}/${episode.slug}`}
|
||||
/>
|
||||
))
|
||||
: relatedEpisodes && !showMore
|
||||
|
@ -25,9 +32,10 @@ const RelatedEpisodes = ({ relatedEpisodes }: any) => {
|
|||
.map((episode: any, idx: number) => (
|
||||
<MoreEpisodesCard
|
||||
key={'related-episode' + idx}
|
||||
thumbnail={episode.thumbnail}
|
||||
coverImage={episode.coverImage}
|
||||
title={episode.title}
|
||||
publishedAt={episode.publishedAt}
|
||||
slug={`${podcastSlug}/${episode.slug}`}
|
||||
/>
|
||||
))
|
||||
: null}
|
||||
|
|
|
@ -4,17 +4,18 @@ import styled from '@emotion/styled'
|
|||
import { LPE } from '../types/lpe.types'
|
||||
|
||||
interface Props {
|
||||
data: LPE.Podcast.Document
|
||||
episode: LPE.Podcast.Document
|
||||
relatedEpisodes: LPE.Podcast.Document[]
|
||||
}
|
||||
|
||||
const EpisodeContainer = (props: Props) => {
|
||||
const { data } = props
|
||||
const { episode, relatedEpisodes } = props
|
||||
|
||||
return (
|
||||
<EpisodeGrid>
|
||||
<Gap className={'w-4'} />
|
||||
<EpisodeBodyContainer className={'w-8'}>
|
||||
<EpisodeBody data={data} />
|
||||
<EpisodeBody episode={episode} relatedEpisodes={relatedEpisodes} />
|
||||
</EpisodeBodyContainer>
|
||||
<Gap className={'w-4'} />
|
||||
</EpisodeGrid>
|
||||
|
|
|
@ -20,14 +20,16 @@ const PodcastShowContainer = (props: Props) => {
|
|||
<PodcastsGrid>
|
||||
<PodcastsBodyContainer className={'w-16'}>
|
||||
<PodcastShowCard show={show} />
|
||||
|
||||
<PodcastSection>
|
||||
<EpisodesList
|
||||
header={<Typography variant="body2">All episodes</Typography>}
|
||||
episodes={highlightedEpisodes}
|
||||
show={show}
|
||||
/>
|
||||
</PodcastSection>
|
||||
|
||||
<EpisodesList episodes={latestEpisodes} divider={true} />
|
||||
<EpisodesList episodes={latestEpisodes} divider={true} show={show} />
|
||||
</PodcastsBodyContainer>
|
||||
</PodcastsGrid>
|
||||
)
|
||||
|
|
|
@ -7,67 +7,98 @@ import { LPE } from '../../../types/lpe.types'
|
|||
import EpisodeLayout from '@/layouts/EpisodeLayout/Episode.layout'
|
||||
import { EpisodeProvider } from '@/context/episode.context'
|
||||
|
||||
import TEMP_DATA from '../episode-temp-data.json'
|
||||
import unbodyApi from '@/services/unbody/unbody.service'
|
||||
|
||||
type EpisodeProps = {
|
||||
data: LPE.Podcast.Document
|
||||
episode: LPE.Podcast.Document
|
||||
relatedEpisodes: LPE.Podcast.Document[]
|
||||
errors: string | null
|
||||
}
|
||||
|
||||
const EpisodePage = ({ data, errors }: EpisodeProps) => {
|
||||
const EpisodePage = ({ episode, relatedEpisodes, errors }: EpisodeProps) => {
|
||||
const {
|
||||
query: { showSlug, epSlug },
|
||||
} = useRouter()
|
||||
|
||||
if (!data) return null
|
||||
if (!episode) return null
|
||||
if (errors) return <div>{errors}</div>
|
||||
|
||||
return (
|
||||
<>
|
||||
<SEO
|
||||
title={data.title}
|
||||
description={data.description}
|
||||
image={data.coverImage}
|
||||
title={episode.title}
|
||||
description={episode.description}
|
||||
image={episode.coverImage}
|
||||
imageUrl={undefined}
|
||||
pagePath={`/podcasts/${showSlug}/${epSlug}`}
|
||||
tags={[...data.tags, ...data.authors.map((author) => author.name)]}
|
||||
tags={[
|
||||
...episode.tags,
|
||||
...episode.authors.map((author) => author.name),
|
||||
]}
|
||||
/>
|
||||
<EpisodeContainer data={data} />
|
||||
<EpisodeContainer episode={episode} relatedEpisodes={relatedEpisodes} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export async function getStaticPaths() {
|
||||
// TODO : dynamic paths
|
||||
return {
|
||||
paths: [{ params: { showSlug: `hasing-it-out`, epSlug: `test` } }],
|
||||
paths: [
|
||||
{
|
||||
params: {
|
||||
showSlug: `hasing-it-out`,
|
||||
epSlug: `test-podcast-highlighted`,
|
||||
},
|
||||
},
|
||||
],
|
||||
fallback: true,
|
||||
}
|
||||
}
|
||||
|
||||
export const getStaticProps = async ({ params }: GetStaticPropsContext) => {
|
||||
const { epSlug } = params!
|
||||
const { showSlug, epSlug } = params!
|
||||
|
||||
if (!epSlug) {
|
||||
if (!epSlug || !showSlug) {
|
||||
return {
|
||||
notFound: true,
|
||||
props: { why: 'no slug' },
|
||||
}
|
||||
}
|
||||
|
||||
const { data, errors } = TEMP_DATA
|
||||
// TODO : error handling
|
||||
const { data: episodeData, errors: episodeErros } =
|
||||
await unbodyApi.getPodcastEpisode({
|
||||
showSlug: showSlug as string,
|
||||
slug: epSlug as string,
|
||||
textBlocks: true,
|
||||
})
|
||||
|
||||
if (!data) {
|
||||
// TODO : error handlings
|
||||
const { data: relatedEpisodesData, errors: relatedEpisodesErros } =
|
||||
await unbodyApi.getRelatedEpisodes({
|
||||
showSlug: showSlug as string,
|
||||
id: episodeData?.id as string,
|
||||
})
|
||||
|
||||
if (!episodeData) {
|
||||
return {
|
||||
notFound: true,
|
||||
props: { why: 'no article' },
|
||||
}
|
||||
}
|
||||
|
||||
// TODO : handle undefined values in JSON
|
||||
const episode = JSON.parse(JSON.stringify(episodeData).replace(/null/g, '""'))
|
||||
|
||||
// TODO : handle undefined values in JSON
|
||||
const relatedEpisodes = JSON.parse(
|
||||
JSON.stringify(relatedEpisodesData).replace(/null/g, '""'),
|
||||
)
|
||||
|
||||
return {
|
||||
props: {
|
||||
data: data,
|
||||
error: JSON.stringify(errors),
|
||||
episode,
|
||||
relatedEpisodes,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,11 +64,13 @@ export const getStaticProps = async ({ params }: GetStaticPropsContext) => {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO : error handling
|
||||
const { data: showData, errors: podcastShowDataErrors } =
|
||||
await unbodyApi.getPodcastShow({
|
||||
showSlug: showSlug as string,
|
||||
})
|
||||
|
||||
// TODO : error handling
|
||||
const { data: latestEpisodesData, errors: latestEpisodesErros } =
|
||||
await unbodyApi.getLatestEpisodes({
|
||||
showSlug: showSlug as string,
|
||||
|
@ -76,6 +78,7 @@ export const getStaticProps = async ({ params }: GetStaticPropsContext) => {
|
|||
limit: 12,
|
||||
})
|
||||
|
||||
// TODO : error handling
|
||||
const { data: highlightedEpisodesData, errors: highlightedEpisodesErrors } =
|
||||
await unbodyApi.getHighlightedEpisodes({
|
||||
showSlug: showSlug as string,
|
||||
|
|
Loading…
Reference in New Issue