Skip to content

Commit b50fded

Browse files
committed
feat: refactor PodcastsClient and PodcastsContext for improved loading state management and add PodcastsList component
1 parent caf01e7 commit b50fded

File tree

9 files changed

+103
-36
lines changed

9 files changed

+103
-36
lines changed

src/app/podcasts/PodcastsClient.tsx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,10 @@
44
import React from "react";
55
import { PodcastsContextProvider } from "./PodcastsContext";
66
import MainWrapper from "../../components/MainWrapper/MainWrapper";
7-
import PodcastList from "../../components/Podcast/PodcastList";
87
import { DTOChannel, QueryParamChannels } from "podverse-helpers";
98
import type { MenuItem } from "../../components/FilterDropdown/FilterDropdown";
109
import PodcastsHeader from "./PodcastsHeader";
11-
import { useLoadingSpinnerGlobal } from "../../contexts/LoadingGlobal";
12-
import LoadingSpinnerOverlay from "../../components/LoadingSpinner/LoadingSpinnerOverlay";
10+
import PodcastsList from "./PodcastsList";
1311

1412
interface DropdownConfig {
1513
typeMenuItems: MenuItem[];
@@ -27,18 +25,16 @@ interface PodcastsClientProps {
2725

2826
export default function PodcastsClient(props: PodcastsClientProps) {
2927
const { initialQueryParams, ssrChannels, ssrTotalPages, dropdownConfig } = props;
30-
const { isLoadingGlobal } = useLoadingSpinnerGlobal();
3128

3229
return (
3330
<PodcastsContextProvider
3431
initialQueryParams={initialQueryParams}
3532
ssrChannels={ssrChannels}
3633
ssrTotalPages={ssrTotalPages}
3734
>
38-
{isLoadingGlobal && <LoadingSpinnerOverlay />}
3935
<PodcastsHeader dropdownConfig={dropdownConfig} />
4036
<MainWrapper>
41-
<PodcastList />
37+
<PodcastsList />
4238
</MainWrapper>
4339
</PodcastsContextProvider>
4440
);

src/app/podcasts/PodcastsContext.tsx

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22

33
import { CategoryMappingKeys, DTOChannel, QueryParamChannels, QueryParamsChannelsSort,
44
QueryParamsChannelsType, QueryParamsStatsRange } from "podverse-helpers";
5-
import React, { createContext, useContext, useState, ReactNode, useEffect } from "react";
5+
import React, { createContext, useContext, useState, ReactNode } from "react";
66
import { apiRequestService } from "../../factories/apiRequestService";
7-
import { useLoadingSpinnerGlobal } from "../../contexts/LoadingGlobal";
7+
import { useAccount } from "../../contexts/Account";
8+
import { useSkipInitialEffect } from "../../hooks/useSkipInitialEffect";
89

910
interface PodcastsContextType extends QueryParamChannels {
1011
setPage: (page: number) => void;
@@ -16,6 +17,10 @@ interface PodcastsContextType extends QueryParamChannels {
1617
setChannels: (channels: DTOChannel[]) => void;
1718
totalPages: number;
1819
setTotalPages: (totalPages: number) => void;
20+
isLoading: boolean;
21+
setIsLoading: (isLoading: boolean) => void;
22+
showSubscribeMessage: boolean;
23+
setShowSubscribeMessage: (show: boolean) => void;
1924
};
2025

2126
const PodcastsContext = createContext<PodcastsContextType | undefined>(undefined);
@@ -26,21 +31,31 @@ export const PodcastsContextProvider = ({ children, initialQueryParams, ssrChann
2631
ssrChannels: DTOChannel[],
2732
ssrTotalPages: number
2833
}) => {
29-
const { setIsLoadingGlobal } = useLoadingSpinnerGlobal();
3034
const [page, setPage] = useState(initialQueryParams.page);
3135
const [type, setType] = useState(initialQueryParams.type);
3236
const [sort, setSort] = useState(initialQueryParams.sort);
3337
const [range, setRange] = useState(initialQueryParams.range);
3438
const [category, setCategory] = useState<string | undefined>(initialQueryParams.category);
3539
const [channels, setChannels] = useState<DTOChannel[]>(ssrChannels || []);
3640
const [totalPages, setTotalPages] = useState<number>(ssrTotalPages || 1);
41+
const [isLoading, setIsLoading] = useState<boolean>(false);
42+
const [showSubscribeMessage, setShowSubscribeMessage] = useState<boolean>(false);
43+
const { loggedInAccount } = useAccount();
3744

38-
useEffect(() => {
45+
useSkipInitialEffect(() => {
3946
async function fetchChannels() {
40-
setIsLoadingGlobal(true);
47+
if (type === "subscribed") {
48+
if (!loggedInAccount) {
49+
setChannels([]);
50+
setShowSubscribeMessage(true);
51+
return;
52+
}
53+
}
54+
55+
setIsLoading(true);
4156
const channels = await apiRequestService.reqChannelGetMany({ page, type, sort, range, category });
4257
setChannels(channels.data);
43-
setIsLoadingGlobal(false);
58+
setIsLoading(false);
4459
}
4560
fetchChannels();
4661
}, [page, type, sort, range, category])
@@ -53,7 +68,9 @@ export const PodcastsContextProvider = ({ children, initialQueryParams, ssrChann
5368
range, setRange,
5469
category, setCategory,
5570
channels, setChannels,
56-
totalPages, setTotalPages
71+
totalPages, setTotalPages,
72+
isLoading, setIsLoading,
73+
showSubscribeMessage, setShowSubscribeMessage,
5774
}}>
5875
{children}
5976
</PodcastsContext.Provider>

src/app/podcasts/PodcastsList.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import React from "react";
2+
import ChannelList from "../../components/Channel/ChannelList";
3+
import { usePodcastsContext } from "./PodcastsContext";
4+
import LoadingSpinnerOverlay from "../../components/LoadingSpinner/LoadingSpinnerOverlay";
5+
6+
const PodcastsList: React.FC = () => {
7+
const { page = 1, setPage, channels, totalPages, isLoading } = usePodcastsContext();
8+
9+
return (
10+
<>
11+
<ChannelList
12+
page={page}
13+
setPage={setPage}
14+
channels={channels}
15+
totalPages={totalPages}
16+
/>
17+
<LoadingSpinnerOverlay isLoading={isLoading} />
18+
</>
19+
);
20+
};
21+
22+
export default PodcastsList;

src/components/Podcast/PodcastList.tsx renamed to src/components/Channel/ChannelList.tsx

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
"use client";
22

33
import React, { useRef } from "react";
4-
import PodcastListItem from "./PodcastListItem";
4+
import ChannelListItem from "./ChannelListItem";
55
import Pagination from "../Pagination/Pagination";
66
import { useSkipInitialEffect } from "../../hooks/useSkipInitialEffect";
7-
import { usePodcastsContext } from "../../app/podcasts/PodcastsContext";
7+
import { DTOChannel } from "podverse-helpers";
88

9-
const PodcastList: React.FC = () => {
10-
const { page = 1, setPage, channels, totalPages } = usePodcastsContext();
9+
type Props = {
10+
page: number;
11+
setPage: (page: number) => void;
12+
channels: DTOChannel[];
13+
totalPages: number;
14+
};
15+
16+
const ChannelList: React.FC<Props> = ({ page = 1, setPage, channels, totalPages }) => {
1117
const topRef = useRef<HTMLDivElement>(null);
1218

1319
useSkipInitialEffect(() => {
@@ -23,11 +29,11 @@ const PodcastList: React.FC = () => {
2329
totalPages={totalPages}
2430
onPageChange={setPage}>
2531
{channels.map((channel) => (
26-
<PodcastListItem key={channel.id} channel={channel} />
32+
<ChannelListItem key={channel.id} channel={channel} />
2733
))}
2834
</Pagination>
2935
</>
3036
);
3137
};
3238

33-
export default PodcastList;
39+
export default ChannelList;

src/components/Podcast/PodcastListItem.tsx renamed to src/components/Channel/ChannelListItem.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import Link from "next/link";
44
import React from "react";
55
import Image from "../Image/Image";
6-
import styles from "../../styles/components/Podcast/PodcastListItem.module.scss";
6+
import styles from "../../styles/components/Channel/ChannelListItem.module.scss";
77
import { ROUTES } from "../../constants/routes";
88
import { DTOChannel, formatDateAbbrev } from "podverse-helpers";
99
import { useTranslations } from "next-intl";
@@ -12,7 +12,7 @@ interface Props {
1212
channel: DTOChannel;
1313
}
1414

15-
const PodcastListItem: React.FC<Props> = ({ channel }) => {
15+
const ChannelListItem: React.FC<Props> = ({ channel }) => {
1616
const url = `${ROUTES.PODCAST}/${channel.id_text}`;
1717
const imageUrl = channel.channel_images?.[0]?.url;
1818
const tMedia = useTranslations("media");
@@ -22,7 +22,7 @@ const PodcastListItem: React.FC<Props> = ({ channel }) => {
2222
<div className={styles.podcastListItem}>
2323
<Image
2424
src={imageUrl}
25-
alt={channel.title || "Podcast Image"}
25+
alt={channel.title || "Channel Image"}
2626
width={80}
2727
height={80}
2828
className={styles.podcastImage}
@@ -44,4 +44,4 @@ const PodcastListItem: React.FC<Props> = ({ channel }) => {
4444
);
4545
};
4646

47-
export default PodcastListItem;
47+
export default ChannelListItem;

src/components/LoadingSpinner/LoadingSpinnerOverlay.tsx

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,19 @@ type Props = {
66
size?: "small" | "medium" | "large";
77
className?: string;
88
style?: React.CSSProperties;
9+
isLoading?: boolean;
910
};
1011

11-
const LoadingSpinnerOverlay: React.FC<Props> = ({
12-
size = "large",
13-
className = "",
14-
style = {},
15-
}) => (
16-
<div className={`${styles.overlay} ${className}`} style={style}>
17-
<div className={styles.spinnerWrapper}>
18-
<LoadingSpinner size={size} />
12+
const LoadingSpinnerOverlay: React.FC<Props> = ({ size = "large", className = "", style = {}, isLoading = false }) => {
13+
if (!isLoading) return null;
14+
15+
return (
16+
<div className={`${styles.overlay} ${className}`} style={style}>
17+
<div className={styles.spinnerWrapper}>
18+
<LoadingSpinner size={size} />
19+
</div>
1920
</div>
20-
</div>
21-
);
21+
);
22+
};
2223

2324
export default LoadingSpinnerOverlay;

src/components/MainWrapper/MainWrapper.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
1+
import React from "react";
12
import Footer from "../Footer/Footer";
23
import styles from "../../styles/components/MainWrapper/MainWrapper.module.scss";
34
import Divider from "../Divider/Divider";
45

56
type MainWrapperProps = {
67
children: React.ReactNode;
8+
emptyStateComponent?: React.ReactNode;
79
};
810

9-
const MainWrapper: React.FC<MainWrapperProps> = ({ children }) => (
11+
const MainWrapper: React.FC<MainWrapperProps> = ({ children, emptyStateComponent }) => (
1012
<div className={styles.mainOuterWrapper}>
1113
<main className={styles.main}>
12-
{children}
14+
{React.Children.count(children) > 0 ? (
15+
children
16+
) : emptyStateComponent ? (
17+
<div className={styles.emptyStateWrapper}>
18+
{emptyStateComponent}
19+
</div>
20+
) : null}
1321
</main>
1422
<Divider />
1523
<Footer />

src/styles/components/MainWrapper/MainWrapper.module.scss

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
@use 'breakpoints';
2+
13
.mainOuterWrapper {
24
display: flex;
35
flex: 1 1 auto;
@@ -14,4 +16,19 @@
1416
flex: 1 1 auto;
1517
display: flex;
1618
flex-direction: column;
17-
}
19+
}
20+
21+
.emptyStateWrapper {
22+
display: flex;
23+
align-items: center;
24+
justify-content: center;
25+
height: 100%;
26+
min-height: 300px;
27+
width: 100%;
28+
}
29+
30+
@media (max-width: breakpoints.$breakpoint-md) {
31+
.mainOuterWrapper {
32+
padding: 0 1rem;
33+
}
34+
}

0 commit comments

Comments
 (0)