feat: implement API endpoints for podcasts page

This commit is contained in:
jinhojang6 2023-08-18 12:15:47 +09:00
parent c635c16291
commit c80c3c8eec
8 changed files with 141 additions and 174 deletions

View File

@ -1,19 +1,18 @@
import styled from '@emotion/styled'
import { LPE } from '../../types/lpe.types'
import { PodcastType, PostCard } from '@/components/PostCard/PostCard'
import { PostCard } from '@/components/PostCard/PostCard'
interface Props {
header?: React.ReactNode
episodes: LPE.Podcast.Document[]
podcast: PodcastType
show?: LPE.Podcast.Show
}
export default function EpisodesList({ header, episodes, podcast }: Props) {
export default function EpisodesList({ header, episodes, show }: Props) {
return (
<EpisodeListContainer>
{header}
<EpisodesContainer>
{/* Featured */}
{episodes.slice(0, 2).map((episode) => (
<PostCard
key={episode.id}
@ -21,48 +20,22 @@ export default function EpisodesList({ header, episodes, podcast }: Props) {
data={{
authors: episode.authors,
date: episode.publishedAt ? new Date(episode.publishedAt) : null,
slug: `${podcast}/${episode.slug}`,
slug: episode.show?.slug
? `${episode.show?.slug}/${episode.slug}`
: `${show?.slug}/${episode.slug}`,
title: episode.title,
coverImage: episode.coverImage,
tags: episode.tags,
podcastShowDetails: {
title: episode.title,
slug: `${podcast}/${episode.slug}`,
slug: `${episode.show?.slug}`,
episodeNumber: episode.episodeNumber,
podcast: podcast,
podcast: episode.show as LPE.Podcast.Show,
},
}}
/>
))}
</EpisodesContainer>
{episodes.length > 2 && (
<EpisodesContainer>
{episodes.slice(2, 6).map((episode) => (
<PostContainer key={episode.id}>
<PostCard
key={episode.id}
contentType={LPE.PostTypes.Podcast}
data={{
authors: episode.authors,
date: episode.publishedAt
? new Date(episode.publishedAt)
: null,
slug: `${podcast}/${episode.slug}`,
title: episode.title,
coverImage: episode.coverImage,
tags: episode.tags,
podcastShowDetails: {
title: episode.title,
slug: `${podcast}/${episode.slug}`,
episodeNumber: episode.episodeNumber,
podcast: podcast,
},
}}
/>
</PostContainer>
))}
</EpisodesContainer>
)}
</EpisodeListContainer>
)
}
@ -79,8 +52,3 @@ const EpisodesContainer = styled.div`
gap: 16px;
padding-top: 24px;
`
const PostContainer = styled.div`
border-top: 1px solid rgb(var(--lsd-border-primary));
padding-top: 24px;
`

View File

@ -0,0 +1,42 @@
import { Typography } from '@acid-info/lsd-react'
import styled from '@emotion/styled'
import React from 'react'
import { LPE } from '../../types/lpe.types'
interface Props {
show: LPE.Podcast.Show
}
export default function PodcastHost({ show }: Props) {
return (
<HostedBy>
<Typography variant="body2">
Hosted by:
{show?.hosts?.map((host) => (
<Host key={host.name} variant="body2">
{host.name}
</Host>
))}
</Typography>
</HostedBy>
)
}
const HostedBy = styled.div`
display: flex;
flex-direction: row;
align-items: center;
gap: 8px;
`
const Host = styled(Typography)`
margin-left: 8px;
&:not(:last-child) {
&:after {
content: '•';
margin-left: 8px;
text-decoration: none;
display: inline-block;
}
}
`

View File

@ -1,12 +1,10 @@
import { Tags } from '@/components/Tags'
import { Typography } from '@acid-info/lsd-react'
import styled from '@emotion/styled'
import Link from 'next/link'
import React, { useMemo } from 'react'
import React from 'react'
import { LPE } from '../../types/lpe.types'
import { ResponsiveImage } from '../ResponsiveImage/ResponsiveImage'
import { LogosCircleIcon } from '../Icons/LogosCircleIcon'
import PodcastHost from './Podcast.Host'
export enum Size {
SMALL = 'small',
@ -23,16 +21,7 @@ export default function PodcastShowCard({ show }: Props) {
<LogosCircleIcon width={73} height={73} />
<ShowData>
<Typography variant="h3">{show.title}</Typography>
<HostedBy>
<Typography variant="body2">
Hosted by:
{show.hosts.map((host) => (
<Host key={host.name} variant="body2">
{host.name}
</Host>
))}
</Typography>
</HostedBy>
<PodcastHost show={show} />
<Description variant="body2">{show.description}</Description>
</ShowData>
</Container>
@ -53,26 +42,6 @@ const ShowData = styled.div`
align-items: center;
`
const HostedBy = styled.div`
display: flex;
flex-direction: row;
align-items: center;
gap: 8px;
margin-top: 16px;
`
const Host = styled(Typography)`
margin-left: 8px;
&:not(:last-child) {
&:after {
content: '•';
margin-left: 8px;
text-decoration: none;
display: inline-block;
}
}
`
const Description = styled(Typography)`
margin-top: 16px;
`

View File

@ -2,29 +2,28 @@ import styled from '@emotion/styled'
import { LPE } from '../../types/lpe.types'
import { Button, Typography } from '@acid-info/lsd-react'
import Link from 'next/link'
import { LogosCircleIcon } from '../Icons/LogosCircleIcon'
import { HashingItOutIcon } from '../Icons/HashingItOutIcon'
import PodcastHost from './Podcast.Host'
import Image from 'next/image'
interface Props {
shows: LPE.Podcast.Show[]
}
export default function PodcastsList({ shows }: Props) {
export default function PodcastsLists({ shows }: Props) {
return (
<PodcastsContainer>
<PodcastListsContainer>
{shows &&
shows.map((show) => (
<PodcastCard key={show.id}>
{show.slug === 'network-state' ? (
<LogosCircleIcon width={56} height={56} />
) : (
<HashingItOutIcon width={56} height={56} />
)}
<Image
src={show.logo.url}
width={56}
height={56}
alt={show.logo.alt}
/>
<Typography variant="h3">{show.title}</Typography>
<Row>
<Typography variant="body2">
Hosted by: {show.hosts[0].name}
</Typography>
<PodcastHost show={show} />
<Typography variant="body2"></Typography>
<Typography variant="body2">
{show.numberOfEpisodes} EP
@ -36,11 +35,11 @@ export default function PodcastsList({ shows }: Props) {
</Link>
</PodcastCard>
))}
</PodcastsContainer>
</PodcastListsContainer>
)
}
const PodcastsContainer = styled.article`
const PodcastListsContainer = styled.div`
display: flex;
gap: 16px;
`

View File

@ -1,37 +1,39 @@
import { LPE } from '@/types/lpe.types'
import Link from 'next/link'
import { PodcastType } from './PostCard'
import { Typography } from '@acid-info/lsd-react'
import { LogosCircleIcon } from '../Icons/LogosCircleIcon'
import styled from '@emotion/styled'
import Image from 'next/image'
export interface PostCardShowDetailsProps {
title: string
slug: string
episodeNumber: number
logo?: LPE.Image.Document
podcast: PodcastType
podcast: LPE.Podcast.Show
}
// TODO
export const PostCardShowDetails = (props: PostCardShowDetailsProps) => {
const { slug, episodeNumber, podcast } = props
const isNetWokrState = podcast === PodcastType.NETWORK_STATE
return (
<CustomLink href={`/podcasts/${slug}`}>
<Container>
{isNetWokrState ? (
<LogosCircleIcon width={38} height={38} />
) : (
<LogosCircleIcon width={38} height={38} />
{podcast && (
<>
<Image
src={podcast?.logo?.url}
width={38}
height={38}
alt={podcast.logo.alt}
/>
<PodcastInfo>
<Typography variant="body2">{podcast.title}</Typography>
<Typography variant="body3">{episodeNumber} EP</Typography>
</PodcastInfo>
</>
)}
<PodcastInfo>
<Typography variant="body2">
{isNetWokrState ? 'State of Network' : 'Hashing It Out'}
</Typography>
<Typography variant="body3">{episodeNumber} EP</Typography>
</PodcastInfo>
</Container>
</CustomLink>
)

View File

@ -21,7 +21,6 @@ const PodcastShowContainer = (props: Props) => {
<EpisodesList
header={<Typography variant="body2">Latest Episodes</Typography>}
episodes={latestEpisodes}
podcast={PodcastType.NETWORK_STATE}
/>
</PodcastsBodyContainer>
</PodcastsGrid>

View File

@ -1,91 +1,56 @@
import { Grid, GridItem } from '@/components/Grid/Grid'
import styled from '@emotion/styled'
import { LPE } from '../types/lpe.types'
import PodcastsList from '@/components/Podcasts/Podcasts.List'
import PodcastsLists from '@/components/Podcasts/Podcasts.Lists'
import EpisodesList from '@/components/Podcasts/Episodes.List'
import { Button, Typography } from '@acid-info/lsd-react'
import { LogosCircleIcon } from '@/components/Icons/LogosCircleIcon'
import Link from 'next/link'
import { HashingItOutIcon } from '@/components/Icons/HashingItOutIcon'
import { PodcastType } from '@/components/PostCard/PostCard'
interface Props {
shows: LPE.Podcast.Show[]
latestEpisodes: LPE.Podcast.Document[]
highlightedEpisodes: LPE.Podcast.Document[]
}
const PodcastsContainer = (props: Props) => {
const { shows, latestEpisodes } = props
const podcast = 'network-state' // TODO : get from API
const networkState =
shows.find((show) => show.slug === PodcastType.NETWORK_STATE) ?? shows[0]
const hashingItOut =
shows.find((show) => show.slug === PodcastType.HASHING_IT_OUT) ?? shows[1]
const { shows, highlightedEpisodes } = props
return (
<PodcastsGrid>
<PodcastsBodyContainer className={'w-16'}>
<PodcastsList shows={shows} />
<PodcastsLists shows={shows} />
<Section>
<EpisodesList
header={<EpisodeListHeader>Latest Episodes</EpisodeListHeader>}
episodes={latestEpisodes}
podcast={podcast as PodcastType}
episodes={highlightedEpisodes}
/>
</Section>
<Section>
<EpisodesList
header={
<EpisodeListHeader>
<Show>
<LogosCircleIcon width={38} height={38} />
<PodcastInfo>
<Typography variant="body1">
{networkState.title}
</Typography>
<Typography variant="body3">
{networkState.numberOfEpisodes} EP
</Typography>
</PodcastInfo>
</Show>
<Link href={`/podcasts/${networkState.slug}`}>
<Button size="small">See all episodes</Button>
</Link>
</EpisodeListHeader>
}
episodes={latestEpisodes.slice(0, 4)}
podcast={PodcastType.NETWORK_STATE}
/>
</Section>
<Section>
<EpisodesList
header={
<EpisodeListHeader>
<Show>
<HashingItOutIcon width={38} height={38} />
<PodcastInfo>
<Typography variant="body1">
{hashingItOut.title}
</Typography>
<Typography variant="body3">
{hashingItOut.numberOfEpisodes} EP
</Typography>
</PodcastInfo>
</Show>
<Link href={`/podcasts/${hashingItOut.slug}`}>
<Button size="small">See all episodes</Button>
</Link>
</EpisodeListHeader>
}
episodes={latestEpisodes.slice(0, 4)}
podcast={PodcastType.HASHING_IT_OUT}
/>
</Section>
{shows.map((show) => (
<Section key={show.id}>
<EpisodesList
header={
<EpisodeListHeader>
<Show>
<LogosCircleIcon width={38} height={38} />
<PodcastInfo>
<Typography variant="body1">{show.title}</Typography>
<Typography variant="body3">
{show.numberOfEpisodes} EP
</Typography>
</PodcastInfo>
</Show>
<Link href={`/podcasts/${show.slug}`}>
<Button size="small">See all episodes</Button>
</Link>
</EpisodeListHeader>
}
episodes={show.episodes as LPE.Podcast.Document[]}
show={show}
/>
</Section>
))}
</PodcastsBodyContainer>
</PodcastsGrid>
)

View File

@ -6,14 +6,19 @@ import PodcastsLayout from '@/layouts/PodcastsLayout/Podcasts.layout'
import PodcastsContainer from '@/containers/PodcastsContainer'
import TEMP_DATA from './podcasts-temp-data.json'
import unbodyApi from '@/services/unbody/unbody.service'
type PodcastsProps = {
shows: LPE.Podcast.Show[]
latestEpisodes: LPE.Podcast.Document[]
highlightedEpisodes: LPE.Podcast.Document[]
errors: string | null
}
const PodcastShowPage = ({ shows, latestEpisodes, errors }: PodcastsProps) => {
const PodcastShowPage = ({
shows,
highlightedEpisodes,
errors,
}: PodcastsProps) => {
if (!shows) return null
if (errors) return <div>{errors}</div>
@ -26,25 +31,43 @@ const PodcastShowPage = ({ shows, latestEpisodes, errors }: PodcastsProps) => {
pagePath={`/podcasts`}
tags={[]}
/>
<PodcastsContainer shows={shows} latestEpisodes={latestEpisodes} />
<PodcastsContainer
shows={shows}
highlightedEpisodes={highlightedEpisodes}
/>
</>
)
}
export const getStaticProps = async ({ params }: GetStaticPropsContext) => {
const { shows, latestEpisodes } = TEMP_DATA
// TODO : error handling
const { data: podcastShowsData, errors: podcastShowsErrors } =
await unbodyApi.getPodcastShows({ populateEpisodes: true })
if (!shows) {
// TODO : error handling
const { data: highlightedEpisodesData, errors: highlightedEpisodesErrors } =
await unbodyApi.getHighlightedEpisodes({})
if (!podcastShowsData) {
return {
notFound: true,
props: { why: 'no article' },
props: { why: 'no podcasts' },
}
}
const podcastShows = JSON.parse(
JSON.stringify(podcastShowsData).replace(/null/g, '""'),
)
const highlightedEpisodes = JSON.parse(
JSON.stringify(highlightedEpisodesData).replace(/null/g, '""'),
)
return {
props: {
shows,
latestEpisodes,
shows: podcastShows,
highlightedEpisodes,
// errors,
},
}
}