Skip to content

Commit f9e95ef

Browse files
ahtesham-quraishAhtesham Quraish
andauthored
feat: add podcast episode page and few fixes in podcast page (#3283)
* feat: add podcast episode page and few fixes in podcast page --------- Co-authored-by: Ahtesham Quraish <ahtesham.quraish@192.168.1.22>
1 parent 7814c13 commit f9e95ef

12 files changed

Lines changed: 1138 additions & 246 deletions

File tree

frontends/main/src/app-pages/PodcastPage/PodcastDetailPage.tsx

Lines changed: 52 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
"use client"
22

3-
import React, { useState, useEffect } from "react"
3+
import React, { useState, useEffect, useRef } from "react"
44
import { Breadcrumbs, Typography, styled, useMediaQuery } from "ol-components"
55
import type { Theme } from "ol-components"
66
import { Button, ActionButton } from "@mitodl/smoot-design"
7-
import { RiPlayFill } from "@remixicon/react"
7+
import { RiPlayFill, RiPauseFill } from "@remixicon/react"
88
import PodcastPlayer, { PLAYER_HEIGHT } from "./PodcastPlayer"
9-
import type { PodcastTrack } from "./PodcastPlayer"
9+
import type { PodcastTrack, PodcastPlayerHandle } from "./PodcastPlayer"
1010
import {
1111
useLearningResourcesDetail,
1212
useInfiniteLearningResourceItems,
@@ -156,13 +156,18 @@ const EpisodeList = styled.ul({
156156
gridTemplateColumns: "1fr",
157157
})
158158

159-
const EpisodeRow = styled.li(({ theme }) => ({
159+
const EpisodeRow = styled("li", {
160+
shouldForwardProp: (prop) => prop !== "isEpisodePage",
161+
})<{ isEpisodePage?: boolean }>(({ theme, isEpisodePage }) => ({
160162
margin: 0,
161163
display: "flex",
162164
flexDirection: "row",
163165
alignItems: "center",
164166
justifyContent: "space-between",
165-
padding: "28px 16px",
167+
padding: !isEpisodePage ? "28px 16px" : "28px 0px",
168+
...(isEpisodePage && {
169+
"&:first-of-type": { paddingTop: 0, boxShadow: "none" },
170+
}),
166171
boxShadow: `0 -1px 0 ${theme.custom.colors.lightGray2}`,
167172
gap: "16px",
168173
"&:last-child": {
@@ -199,14 +204,17 @@ const EpisodeTitleLink = styled.span(({ theme }) => ({
199204
color: theme.custom.colors.darkGray2,
200205
textDecoration: "none",
201206
display: "block",
202-
marginBottom: "8px",
203207
fontSize: "18px",
204208
fontStyle: "normal",
205209
fontWeight: theme.typography.fontWeightBold,
206210
lineHeight: "26px",
207211
}))
208212

209213
const StyledButton = styled(Button)(({ theme }) => ({
214+
padding: "16px 20px",
215+
...theme.typography.body1,
216+
fontWeight: theme.typography.fontWeightMedium,
217+
lineHeight: "16px",
210218
[theme.breakpoints.down("sm")]: {
211219
width: "100%",
212220
},
@@ -225,8 +233,13 @@ const StyledShowMore = styled(Button)(({ theme }) => ({
225233
},
226234
}))
227235

236+
const StyledIcon = styled(RiPlayFill)({
237+
width: "24px !important",
238+
height: "24px !important",
239+
})
240+
228241
const BreadcrumbBar = styled.div(({ theme }) => ({
229-
padding: "32px 0 16px 0",
242+
padding: "20px 0 4px 0",
230243
borderBottom: `2px solid ${theme.custom.colors.red}`,
231244
[theme.breakpoints.down("sm")]: {
232245
padding: "16px 0 0px 0",
@@ -254,7 +267,7 @@ const StyledDot = styled.span(({ theme }) => ({
254267
}))
255268

256269
const PageSection = styled.div(({ theme }) => ({
257-
backgroundColor: theme.custom.colors.lightGray1,
270+
backgroundColor: theme.custom.colors.white,
258271
}))
259272

260273
const EpisodeMeta = styled(Typography)(({ theme }) => ({
@@ -269,7 +282,10 @@ const PlayButton = styled(ActionButton, {
269282
isPlaying: boolean
270283
}>(({ theme, isPlaying }) => [
271284
{
285+
width: "48px",
286+
height: "48px",
272287
color: theme.custom.colors.darkGray2,
288+
backgroundColor: theme.custom.colors.white,
273289
borderColor: "currentColor",
274290
"&:hover:not(:disabled)": {
275291
color: theme.custom.colors.red,
@@ -287,18 +303,22 @@ const PlayButton = styled(ActionButton, {
287303

288304
/* ── Episode row component ── */
289305

290-
type EpisodeItemProps = {
306+
export type EpisodeItemProps = {
291307
episode: LearningResource
292308
onPlayClick: (episode: LearningResource) => void
309+
onPauseClick?: () => void
293310
isPlaying: boolean
294311
isPlayable: boolean
312+
isEpisodePage?: boolean
295313
}
296314

297-
const EpisodeItem: React.FC<EpisodeItemProps> = ({
315+
export const EpisodeItem: React.FC<EpisodeItemProps> = ({
298316
episode,
299317
onPlayClick,
318+
onPauseClick,
300319
isPlaying,
301320
isPlayable,
321+
isEpisodePage = false,
302322
}) => {
303323
const podcastEpisode =
304324
episode.resource_type === "podcast_episode" ? episode.podcast_episode : null
@@ -314,7 +334,10 @@ const EpisodeItem: React.FC<EpisodeItemProps> = ({
314334
const metaParts = [duration ? `${duration} min` : null, date].filter(Boolean)
315335

316336
return (
317-
<EpisodeRow onClick={() => onPlayClick(episode)}>
337+
<EpisodeRow
338+
onClick={() => (isPlaying ? onPauseClick?.() : onPlayClick(episode))}
339+
isEpisodePage={isEpisodePage}
340+
>
318341
<EpisodeInfo>
319342
<EpisodeTitleLink className="episode-title">
320343
{episode.title}
@@ -333,13 +356,15 @@ const EpisodeItem: React.FC<EpisodeItemProps> = ({
333356
</EpisodeMeta>
334357
)}
335358
<PlayButton
336-
aria-label={`Play ${episode.title}`}
359+
aria-label={
360+
isPlaying ? `Pause ${episode.title}` : `Play ${episode.title}`
361+
}
337362
isPlaying={isPlaying}
338363
disabled={!isPlayable}
339364
variant="secondary"
340365
className="play-button"
341366
>
342-
<RiPlayFill size={20} />
367+
{isPlaying ? <RiPauseFill size={20} /> : <RiPlayFill size={20} />}
343368
</PlayButton>
344369
</EpisodeRight>
345370
</EpisodeRow>
@@ -366,6 +391,8 @@ export const PodcastDetailPage: React.FC<PodcastDetailPageProps> = ({
366391
const [playingEpisode, setPlayingEpisode] = useState<LearningResource | null>(
367392
null,
368393
)
394+
const [isAudioPlaying, setIsAudioPlaying] = useState(false)
395+
const playerRef = useRef<PodcastPlayerHandle>(null)
369396

370397
const { data: resource } = useLearningResourcesDetail(id)
371398

@@ -425,7 +452,11 @@ export const PodcastDetailPage: React.FC<PodcastDetailPageProps> = ({
425452

426453
const handlePlayClick = (episode: LearningResource) => {
427454
if (!getEpisodeAudioUrl(episode)) return
428-
setPlayingEpisode(episode)
455+
if (playingEpisode?.id === episode.id) {
456+
playerRef.current?.resume()
457+
} else {
458+
setPlayingEpisode(episode)
459+
}
429460
}
430461

431462
const currentTrack: PodcastTrack | null = playingEpisode
@@ -517,7 +548,7 @@ export const PodcastDetailPage: React.FC<PodcastDetailPageProps> = ({
517548
<StyledButton
518549
onClick={() => handlePlayClick(latestEpisode)}
519550
variant="primary"
520-
startIcon={<RiPlayFill />}
551+
startIcon={<StyledIcon />}
521552
disabled={!getEpisodeAudioUrl(latestEpisode)}
522553
>
523554
Play Latest Episode
@@ -540,7 +571,10 @@ export const PodcastDetailPage: React.FC<PodcastDetailPageProps> = ({
540571
key={episode.id}
541572
episode={episode}
542573
onPlayClick={handlePlayClick}
543-
isPlaying={playingEpisode?.id === episode.id}
574+
onPauseClick={() => playerRef.current?.pause()}
575+
isPlaying={
576+
playingEpisode?.id === episode.id && isAudioPlaying
577+
}
544578
isPlayable={Boolean(getEpisodeAudioUrl(episode))}
545579
/>
546580
))}
@@ -568,8 +602,10 @@ export const PodcastDetailPage: React.FC<PodcastDetailPageProps> = ({
568602
</PageSection>
569603
{currentTrack && (
570604
<PodcastPlayer
605+
ref={playerRef}
571606
track={currentTrack}
572607
onClose={() => setPlayingEpisode(null)}
608+
onPlayStateChange={setIsAudioPlaying}
573609
/>
574610
)}
575611
</>

0 commit comments

Comments
 (0)