refactor: refactor podcast components

This commit is contained in:
Hossein Mehrabi 2023-08-22 04:35:52 +03:30
parent 1f33c01035
commit 8db3cfebed
No known key found for this signature in database
GPG Key ID: 45C04964191AFAA1
17 changed files with 188 additions and 167 deletions

View File

@ -8,9 +8,6 @@ const EpisodeTranscript = ({ episode }: { episode: LPE.Podcast.Document }) => {
return (
<>
<EpisodeDivider />
<Title component="h6" variant="h6">
Transcript
</Title>
<EpisodeBlocks data={episode} />
</>
)

View File

@ -28,25 +28,10 @@ const RelatedEpisodes = ({ podcastSlug, relatedEpisodes }: props) => {
{relatedEpisodes.slice(0, showIndex).map((episode, idx: number) => (
<PostCardContainer className="w-8" key={'related-episode' + idx}>
<PostCard
size="xsmall"
displayPodcastShow={false}
contentType={LPE.PostTypes.Podcast}
data={{
authors: episode.authors,
date: episode.publishedAt
? new Date(episode.publishedAt)
: null,
slug: episode.show?.slug
? `${episode.show?.slug}/${episode.slug}`
: `${podcastSlug}/${episode.slug}`,
title: episode.title,
coverImage: episode.coverImage,
tags: episode.tags,
podcastShowDetails: {
title: episode.title,
slug: `${episode.show?.slug}`,
episodeNumber: episode.episodeNumber,
podcast: episode.show as LPE.Podcast.Show,
},
}}
data={{ ...PostCard.toData(episode), tags: [] }}
/>
</PostCardContainer>
))}

View File

@ -1,57 +1,37 @@
import styled from '@emotion/styled'
import { LPE } from '../../types/lpe.types'
import { PostCard } from '@/components/PostCard/PostCard'
import { Grid, GridItem } from '../Grid/Grid'
import { PostsGrid, PostsGridProps } from '../PostsGrid'
interface Props {
header?: React.ReactNode
episodes: LPE.Podcast.Document[]
show?: LPE.Podcast.Show
divider?: boolean
isFeatured?: boolean
shows?: LPE.Podcast.Show[]
bordered?: boolean
size?: PostsGridProps['size']
cols?: number
displayShow?: boolean
}
export default function EpisodesList({
shows = [],
header,
episodes,
show,
divider = false,
isFeatured = false,
bordered = false,
cols = 4,
size = 'small',
displayShow = true,
}: Props) {
return (
<EpisodeListContainer>
{header}
<EpisodesContainer>
{episodes.map((episode) => (
<PostCardContainer
key={episode.id}
divider={divider}
className={isFeatured ? 'w-8' : 'w-4'}
>
<PostCard
contentType={LPE.PostTypes.Podcast}
data={{
authors: episode.authors,
date: episode.publishedAt
? new Date(episode.publishedAt)
: null,
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: `${episode.show?.slug}`,
episodeNumber: episode.episodeNumber,
podcast: episode.show as LPE.Podcast.Show,
},
}}
/>
</PostCardContainer>
))}
</EpisodesContainer>
<PostsGrid
shows={shows}
posts={episodes}
bordered={bordered}
cols={cols}
size={size}
displayPodcastShow={displayShow}
/>
</EpisodeListContainer>
)
}
@ -61,13 +41,3 @@ const EpisodeListContainer = styled.div`
flex-direction: column;
padding-top: 16px;
`
const EpisodesContainer = styled(Grid)`
gap: 36px 16px;
`
const PostCardContainer = styled(GridItem)<{ divider: boolean }>`
padding-block: 24px;
border-top: ${({ divider }) =>
divider ? '1px solid rgb(var(--lsd-border-primary))' : 'none'};
`

View File

@ -25,7 +25,10 @@ export default function PodcastShowCard({
<ShowData>
<Title variant="h3">{show.title}</Title>
<PodcastHost show={show} />
<Description variant="body2">{show.description}</Description>
<Description
variant="body2"
dangerouslySetInnerHTML={{ __html: show.description }}
/>
</ShowData>
</Container>
)

View File

@ -31,7 +31,10 @@ export default function PodcastsLists({ shows }: Props) {
{show.numberOfEpisodes} EP
</Typography>
</Row>
<Description variant="body2">{show.description}</Description>
<Description
variant="body2"
dangerouslySetInnerHTML={{ __html: show.description }}
/>
<Link href={`/podcasts/${show.slug}`}>
<Button>Go to the show page</Button>
</Link>

View File

@ -39,6 +39,7 @@ export type PostCardProps = CommonProps &
data: PostDataProps
contentType: LPE.PostType
size?: 'xxsmall' | 'xsmall' | 'small' | 'medium' | 'large'
displayPodcastShow?: boolean
}
export const PostCard = (_props: PostCardProps) => {
@ -56,6 +57,7 @@ export const PostCard = (_props: PostCardProps) => {
},
size = 'small',
contentType,
displayPodcastShow = true,
...props
} = _props
@ -118,7 +120,7 @@ export const PostCard = (_props: PostCardProps) => {
/>
)
const showElement = podcastShowDetails && (
const showElement = displayPodcastShow && podcastShowDetails && (
<PostCardShowDetails
{...podcastShowDetails}
size={size === 'large' ? 'medium' : 'small'}
@ -160,7 +162,7 @@ export const PostCard = (_props: PostCardProps) => {
PostCard.toData = (post: LPE.Post.Document, shows: LPE.Podcast.Show[] = []) => {
const show =
post.type === 'podcast'
? post.show || shows.find((show) => show.id === post.showId)
? post.show || shows.find((show) => show.id === post.showId) || shows[0]
: undefined
return {

View File

@ -6,14 +6,18 @@ import { chunkArray } from '../../utils/array.utils'
import { PostCard, PostCardProps } from '../PostCard'
export type PostsGridProps = Partial<React.ComponentProps<typeof Container>> & {
shows?: LPE.Podcast.Show[]
posts?: LPE.Post.Document[]
displayPodcastShow?: boolean
}
export const PostsGrid: React.FC<PostsGridProps> = ({
cols = 4,
size = 'small',
posts = [],
shows = [],
bordered = false,
displayPodcastShow = true,
...props
}) => {
const groups = useMemo(() => chunkArray(posts, cols), [posts, cols])
@ -28,7 +32,8 @@ export const PostsGrid: React.FC<PostsGridProps> = ({
size={size}
className="post-card"
contentType={post.type}
data={PostCard.toData(post)}
displayPodcastShow={displayPodcastShow}
data={PostCard.toData(post, shows)}
/>
</div>
))}
@ -50,7 +55,7 @@ const Container = styled.div<{
> .row {
display: grid;
grid-template-columns: repeat(${props.cols}, 1fr);
gap: 0 32px;
gap: 0 16px;
& > div {
padding: 24px 0;

View File

@ -1,12 +1,11 @@
import { Grid, GridItem } from '@/components/Grid/Grid'
import styled from '@emotion/styled'
import { LPE } from '../types/lpe.types'
import EpisodesList from '@/components/Podcasts/Episodes.List'
import { Button, Typography } from '@acid-info/lsd-react'
import PodcastShowCard from '@/components/Podcasts/PodcastShowCard'
import PodcastSection from '@/components/Podcasts/Podcast.Section'
import { api } from '@/services/api.service'
import { useState } from 'react'
import PodcastShowCard from '@/components/Podcasts/PodcastShowCard'
import { Button, Typography } from '@acid-info/lsd-react'
import styled from '@emotion/styled'
import { useRecentEpisodes } from '../queries/useRecentEpisodes.query'
import { LPE } from '../types/lpe.types'
interface Props {
show: LPE.Podcast.Show
@ -16,25 +15,12 @@ interface Props {
const PodcastShowContainer = (props: Props) => {
const { show, latestEpisodes, highlightedEpisodes } = props
const [latest, setLatest] = useState(latestEpisodes)
const [page, setPage] = useState(2)
const [showSeeMore, setShowSeeMore] = useState(true)
const handleLoadMore = async () => {
const response = await api.getLatestEpisodes({
page: page,
limit: 9,
showSlug: show.slug,
})
if (response.data.length === 0) {
setShowSeeMore(false)
return
}
setLatest((prev) => [...prev, ...response.data])
setPage((prev) => prev + 1)
}
const query = useRecentEpisodes({
limit: 8,
showSlug: show.slug,
initialData: latestEpisodes,
})
return (
<>
@ -43,17 +29,26 @@ const PodcastShowContainer = (props: Props) => {
<PodcastShowCard show={show} />
<PodcastSection>
<EpisodesList
header={<Typography variant="body2">All episodes</Typography>}
cols={2}
size="medium"
shows={[show]}
displayShow={false}
episodes={highlightedEpisodes}
show={show}
isFeatured={true}
header={<Typography variant="body2">All episodes</Typography>}
/>
</PodcastSection>
<EpisodesList episodes={latest} divider={true} show={show} />
<EpisodesList
cols={4}
bordered
size="small"
shows={[show]}
displayShow={false}
episodes={query.posts}
/>
</PodcastsBodyContainer>
</PodcastsGrid>
{showSeeMore && (
<SeeMoreButton onClick={handleLoadMore}>
{query.hasNextPage && (
<SeeMoreButton onClick={() => query.fetchNextPage()}>
See more episodes
</SeeMoreButton>
)}

View File

@ -82,11 +82,15 @@ export const PodcastShowsPreview: React.FC<PodcastShowsPreviewProps> = ({
<div className="podcasts__show-episodes">
<PostsGrid
posts={(show.episodes || []).slice(0, 2)}
displayPodcastShow={false}
shows={shows}
size="xsmall"
cols={2}
/>
<PostsGrid
posts={(show.episodes || []).slice(2, 4)}
displayPodcastShow={false}
shows={shows}
size="xsmall"
cols={2}
/>

View File

@ -1,12 +1,12 @@
import { Grid, GridItem } from '@/components/Grid/Grid'
import styled from '@emotion/styled'
import { LPE } from '../types/lpe.types'
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 EpisodesList from '@/components/Podcasts/Episodes.List'
import PodcastSection from '@/components/Podcasts/Podcast.Section'
import PodcastsLists from '@/components/Podcasts/Podcasts.Lists'
import { Button, Typography } from '@acid-info/lsd-react'
import styled from '@emotion/styled'
import Link from 'next/link'
import { LPE } from '../types/lpe.types'
interface Props {
shows: LPE.Podcast.Show[]
@ -23,13 +23,16 @@ const PodcastsContainer = (props: Props) => {
<PodcastSection>
<EpisodesList
header={<EpisodeListHeader>Latest Episodes</EpisodeListHeader>}
cols={2}
size="medium"
episodes={highlightedEpisodes.slice(0, 2)}
isFeatured={true}
header={<EpisodeListHeader>Latest Episodes</EpisodeListHeader>}
/>
<EpisodesList
cols={4}
size="small"
bordered
episodes={highlightedEpisodes.slice(2)}
divider={true}
/>
</PodcastSection>
@ -52,8 +55,9 @@ const PodcastsContainer = (props: Props) => {
</Link>
</EpisodeListHeader>
}
shows={[show]}
displayShow={false}
episodes={show.episodes as LPE.Podcast.Document[]}
show={show}
/>
</PodcastSection>
))}

View File

@ -7,11 +7,11 @@ export default async function handler(
res: NextApiResponse<any>,
) {
const {
query: { page = 1, limit = 10, showSlug },
query: { skip = 0, limit = 10, showSlug },
} = req
const response = await unbodyApi.getLatestEpisodes({
page: parseInt(page, 1),
skip: parseInt(skip, 0),
limit: parseInt(limit, 10),
showSlug: Array.isArray(showSlug) ? showSlug[0] : showSlug,
})

View File

@ -1,9 +1,9 @@
import { SEO } from '@/components/SEO'
import PodcastsLayout from '@/layouts/PodcastsLayout/Podcasts.layout'
import { GetStaticPropsContext } from 'next'
import { useRouter } from 'next/router'
import { ReactNode } from 'react'
import { LPE } from '../../../types/lpe.types'
import PodcastsLayout from '@/layouts/PodcastsLayout/Podcasts.layout'
import PodcastShowContainer from '@/containers/PodcastShowContainer'
import unbodyApi from '@/services/unbody/unbody.service'
@ -83,15 +83,13 @@ export const getStaticProps = async ({ params }: GetStaticPropsContext) => {
const { data: latestEpisodes, errors: latestEpisodesErros } =
await unbodyApi.getLatestEpisodes({
showSlug: showSlug as string,
page: 1,
limit: 9,
limit: 8,
})
// TODO : error handling
const { data: highlightedEpisodes, errors: highlightedEpisodesErrors } =
await unbodyApi.getHighlightedEpisodes({
showSlug: showSlug as string,
page: 1,
limit: 2,
})

View File

@ -0,0 +1,58 @@
import { useInfiniteQuery } from '@tanstack/react-query'
import { useMemo } from 'react'
import { api } from '../services/api.service'
import { LPE } from '../types/lpe.types'
export const useRecentEpisodes = ({
initialData = [],
limit = 10,
showSlug,
}: {
initialData?: LPE.Podcast.Document[]
showSlug: string
limit?: number
}) => {
const query = useInfiniteQuery(
['recent-episodes', showSlug, initialData, limit],
async ({ pageParam }) => {
const firstPageLimit = initialData.length
const _limit = pageParam === 1 ? firstPageLimit : limit
const skip =
pageParam === 1 ? 0 : (pageParam - 2) * limit + firstPageLimit
return api
.getLatestEpisodes({ skip, limit: _limit, showSlug })
.then((res) => ({
page: pageParam,
posts: res.data,
hasMore: res.data.length !== 0,
}))
},
{
initialData: {
pageParams: [1],
pages: [
{
page: 1,
hasMore: true,
posts: initialData,
},
],
},
getNextPageParam: (lastPage, all) =>
lastPage.hasMore ? lastPage.page + 1 : undefined,
getPreviousPageParam: (firstPage) =>
firstPage ? firstPage.page - 1 : undefined,
},
)
const posts = useMemo(
() => (query.data?.pages || []).flatMap((page) => page.posts),
[query.data],
)
return {
posts,
...query,
}
}

View File

@ -21,7 +21,7 @@ export const useRecentPosts = ({
return api.getRecentPosts({ skip, limit: _limit }).then((res) => ({
page: pageParam,
posts: res.data,
hasMore: res.data.length === _limit,
hasMore: res.data.length !== 0,
}))
},
{

View File

@ -17,15 +17,15 @@ export class ApiService {
})
getLatestEpisodes = async ({
page = 1,
skip = 0,
limit = 10,
showSlug,
}: {
page?: number
skip?: number
limit?: number
showSlug: string
}): Promise<ApiResponse<LPE.Podcast.Document[]>> =>
fetch(`/api/podcasts/${showSlug}/episodes?page=${page}&limit=${limit}`)
fetch(`/api/podcasts/${showSlug}/episodes?skip=${skip}&limit=${limit}`)
.then((res) => res.json())
.catch((e) => {
console.error(e)

View File

@ -19,9 +19,9 @@ export class UnbodyHelpers {
static args = {
limit: (value: number): any => String(value),
skip: (value: number): any => String(value),
page: (page: number, limit: number = 10) => ({
page: (skip: number, limit: number = 10) => ({
limit: this.args.limit(limit),
skip: this.args.skip(Math.max(0, page - 1) * limit),
skip: this.args.skip(skip),
}),
wherePath: (path: Array<string | null | undefined | false>) => {
const input = path.filter((p) => p && typeof p === 'string') as string[]

View File

@ -130,13 +130,11 @@ export class UnbodyService {
private fetchAllEpisodes = async () => {
const result: LPE.Podcast.Document[] = []
let page = 0
let skip = 0
const limit = 50
while (true) {
page++
const { data } = await this.getPodcastEpisodes({
page,
skip,
limit,
highlighted: 'exclude',
includeDrafts: false,
@ -153,6 +151,8 @@ export class UnbodyService {
)
if (data.length === 0) break
skip += 50
}
return result
@ -161,13 +161,11 @@ export class UnbodyService {
private fetchAllArticles = async () => {
const result: LPE.Article.Data[] = []
let page = 0
let skip = 0
const limit = 50
while (true) {
page++
const { data } = await this.getArticles({
page,
skip,
limit,
highlighted: 'exclude',
includeDrafts: false,
@ -183,6 +181,7 @@ export class UnbodyService {
)
if (data.length === 0) break
skip += limit
}
return result
@ -233,7 +232,7 @@ export class UnbodyService {
}, 0)
getArticles = ({
page = 1,
skip = 0,
limit = 10,
slug,
toc = false,
@ -244,7 +243,7 @@ export class UnbodyService {
highlighted = 'include',
}: {
slug?: string
page?: number
skip?: number
limit?: number
toc?: boolean
filter?: GetPostsQueryVariables['filter']
@ -273,7 +272,7 @@ export class UnbodyService {
},
}
: {}),
...this.helpers.args.page(page, limit),
...this.helpers.args.page(skip, limit),
filter: {
operator: 'And',
operands: [
@ -327,7 +326,6 @@ export class UnbodyService {
async () =>
this.getArticles({
limit: 1,
page: 1,
slug,
toc: true,
includeDrafts,
@ -339,15 +337,15 @@ export class UnbodyService {
)
getHighlightedArticles = ({
page = 1,
skip = 0,
limit = 10,
}: {
page?: number
skip?: number
limit?: number
}) =>
this.getArticles({
limit,
page,
skip,
toc: true,
highlighted: 'only',
textBlocks: true,
@ -356,16 +354,16 @@ export class UnbodyService {
getRelatedArticles = ({
id,
page = 1,
skip = 0,
limit = 10,
}: {
id: string
page?: number
skip?: number
limit?: number
}) =>
this.getArticles({
limit,
page,
skip,
toc: true,
nearObject: id,
textBlocks: true,
@ -395,7 +393,7 @@ export class UnbodyService {
'index',
showSlug && showSlug,
]),
...this.helpers.args.page(1, 50),
...this.helpers.args.page(0, 50),
imageBlocks: true,
textBlocks: true,
mentions: true,
@ -414,7 +412,6 @@ export class UnbodyService {
episodes: populateEpisodes
? await this.getPodcastEpisodes({
showSlug: show.slug,
page: 1,
limit: episodesLimit,
highlighted: 'include',
}).then((res) => res.data)
@ -448,8 +445,8 @@ export class UnbodyService {
)
getPodcastEpisodes = ({
page = 1,
limit = 10,
skip = 0,
slug,
showSlug = '',
toc = false,
@ -463,7 +460,7 @@ export class UnbodyService {
}: {
slug?: string
showSlug?: string
page?: number
skip?: number
limit?: number
toc?: boolean
filter?: GetPostsQueryVariables['filter']
@ -493,7 +490,7 @@ export class UnbodyService {
},
}
: {}),
...this.helpers.args.page(page, limit),
...this.helpers.args.page(skip, limit),
filter: {
operator: 'And',
operands: [
@ -570,7 +567,7 @@ export class UnbodyService {
const { data } = await this.getPodcastEpisodes({
slug,
showSlug,
page: 1,
skip: 0,
limit: 1,
toc,
textBlocks,
@ -584,19 +581,19 @@ export class UnbodyService {
}, null)
getHighlightedEpisodes = ({
page = 1,
skip = 0,
limit = 10,
showSlug = '',
textBlocks = false,
}: {
page?: number
skip?: number
limit?: number
showSlug?: string
textBlocks?: boolean
}) =>
this.handleRequest<LPE.Podcast.Document[]>(async () => {
const { data } = await this.getPodcastEpisodes({
page,
skip,
limit,
showSlug,
textBlocks,
@ -611,7 +608,7 @@ export class UnbodyService {
getRelatedEpisodes = ({
id,
page = 1,
skip = 0,
limit = 10,
showSlug = '',
toc = false,
@ -620,7 +617,7 @@ export class UnbodyService {
}: {
id: string
toc?: boolean
page?: number
skip?: number
limit?: number
showSlug?: string
textBlocks?: boolean
@ -629,7 +626,7 @@ export class UnbodyService {
}) =>
this.handleRequest<LPE.Podcast.Document[]>(async () => {
const { data } = await this.getPodcastEpisodes({
page,
skip,
limit,
toc,
showSlug,
@ -646,15 +643,15 @@ export class UnbodyService {
getLatestEpisodes = async ({
showSlug,
page = 1,
skip = 0,
limit = 10,
}: {
showSlug?: string
page?: number
skip?: number
limit?: number
}) =>
this.getPodcastEpisodes({
page,
skip,
limit,
showSlug,
highlighted: 'include',
@ -691,7 +688,7 @@ export class UnbodyService {
const { data } = await this.client.query({
query: GetHomepagePostsDocument,
variables: {
...this.helpers.args.page(1, 10),
...this.helpers.args.page(0, 10),
filter: this.helpers.args.wherePublished(true),
},
})
@ -715,14 +712,14 @@ export class UnbodyService {
this.handleRequest<{ slug: string }[]>(async () => {
const result: { slug: string }[] = []
let page = 1
let skip = 0
while (true) {
const res = await this.client.query({
query: GetAllArticleSlugsDocument,
variables: {
filter: UnbodyHelpers.args.wherePublished(true),
...this.helpers.args.page(page, 50),
...this.helpers.args.page(skip, 50),
},
})
@ -732,7 +729,7 @@ export class UnbodyService {
result.push(...docs.map((doc) => ({ slug: doc!.slug! })))
page++
skip += 50
}
return result
@ -783,7 +780,7 @@ export class UnbodyService {
UnbodyHelpers.args.wherePublished(published),
],
},
...this.helpers.args.page(1, 10),
...this.helpers.args.page(0, 10),
},
})
@ -843,7 +840,7 @@ export class UnbodyService {
} = await this.client.query({
query: SearchBlocksDocument,
variables: {
...this.helpers.args.page(1, 20),
...this.helpers.args.page(0, 20),
imageFilter: filter,
textFilter: filter,
...(nearText