feat: implement episode API

This commit is contained in:
jinhojang6 2023-08-18 13:31:10 +09:00
parent 028aa01682
commit 9439f26c60
9 changed files with 114 additions and 90 deletions

View File

@ -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>
)
}

View File

@ -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} />
</>
)
}

View File

@ -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>
)
}

View File

@ -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

View File

@ -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}

View File

@ -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>

View File

@ -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>
)

View File

@ -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,
},
}
}

View File

@ -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,