From de9eafdaa84ae002eebad116c0180e2bb8d46c1d Mon Sep 17 00:00:00 2001 From: Andres Date: Thu, 18 Apr 2024 16:48:22 +0200 Subject: [PATCH] feat: add podcasts service --- src/hooks/useQuery.ts | 28 ++++ src/pages/home.tsx | 22 +-- src/services/podcasts/podcast.dto.ts | 15 ++ src/services/podcasts/podcasts.service.ts | 17 +++ src/services/podcasts/podcasts.types.ts | 164 ++++++++++++++++++++++ 5 files changed, 231 insertions(+), 15 deletions(-) create mode 100644 src/hooks/useQuery.ts create mode 100644 src/services/podcasts/podcast.dto.ts create mode 100644 src/services/podcasts/podcasts.service.ts create mode 100644 src/services/podcasts/podcasts.types.ts diff --git a/src/hooks/useQuery.ts b/src/hooks/useQuery.ts new file mode 100644 index 0000000..b54ed6c --- /dev/null +++ b/src/hooks/useQuery.ts @@ -0,0 +1,28 @@ +import { useCallback, useEffect, useState } from "react"; + +export function useQuery( + callback: () => Promise, + /* eslint-disable-next-line @typescript-eslint/no-explicit-any*/ + dependencies: any[] = [], +) { + const [loading, setLoading] = useState(true); + const [error, setError] = useState(); + const [data, setData] = useState(); + + const callbackMemoized = useCallback(() => { + setLoading(true); + setError(undefined); + setData(undefined); + callback() + .then(setData) + .catch(setError) + .finally(() => setLoading(false)); + /* eslint-disable-next-line react-hooks/exhaustive-deps*/ + }, dependencies); + + useEffect(() => { + callbackMemoized(); + }, [callbackMemoized]); + + return { loading, error, data }; +} diff --git a/src/pages/home.tsx b/src/pages/home.tsx index 3e775c0..e5b4f5f 100644 --- a/src/pages/home.tsx +++ b/src/pages/home.tsx @@ -1,30 +1,22 @@ import { PodcastPreviewCard } from "../components/podcast-preview-card"; +import { podcastsService } from "../services/podcasts/podcasts.service"; +import { useQuery } from "../hooks/useQuery"; export function Home() { + const { data } = useQuery(() => podcastsService.getTopPodcasts()); + return (
- {podcasts.map((podcast) => ( + {data?.map((podcast) => ( ))}
); } - -const podcasts = [ - { - imageSrc: "https://picsum.photos/200/200", - title: "Podcast 1", - author: "Author 1", - }, - { - imageSrc: "https://picsum.photos/200/200", - title: "Podcast 2", - author: "Author 2", - }, -]; diff --git a/src/services/podcasts/podcast.dto.ts b/src/services/podcasts/podcast.dto.ts new file mode 100644 index 0000000..c70628e --- /dev/null +++ b/src/services/podcasts/podcast.dto.ts @@ -0,0 +1,15 @@ +import { Entry } from "./podcasts.types"; + +export class PodcastDTO { + id: string; + title: string; + author: string; + imageUrl: string; + + constructor(podcast: Entry) { + this.id = podcast.id.attributes["im:id"]; + this.title = podcast["im:name"].label; + this.author = podcast["im:artist"].label; + this.imageUrl = podcast["im:image"][0].label; + } +} diff --git a/src/services/podcasts/podcasts.service.ts b/src/services/podcasts/podcasts.service.ts new file mode 100644 index 0000000..a498189 --- /dev/null +++ b/src/services/podcasts/podcasts.service.ts @@ -0,0 +1,17 @@ +import { TopPodcastsResponse } from "./podcasts.types"; +import { PodcastDTO } from "./podcast.dto"; + +class PodcastsService { + baseUrl = "https://itunes.apple.com"; + + async getTopPodcasts(): Promise { + const response = await fetch( + `${this.baseUrl}/us/rss/toppodcasts/limit=100/genre=1310/json`, + ); + const data: TopPodcastsResponse = await response.json(); + + return data.feed.entry.map((podcast) => new PodcastDTO(podcast)); + } +} + +export const podcastsService = new PodcastsService(); diff --git a/src/services/podcasts/podcasts.types.ts b/src/services/podcasts/podcasts.types.ts new file mode 100644 index 0000000..43c1c32 --- /dev/null +++ b/src/services/podcasts/podcasts.types.ts @@ -0,0 +1,164 @@ +export interface TopPodcastsResponse { + feed: Feed; +} + +export interface Feed { + author: Author; + entry: Entry[]; + updated: Updated; + rights: Rights2; + title: Title2; + icon: Icon; + link: Link2[]; + id: Id2; +} + +export interface Author { + name: Name; + uri: Uri; +} + +export interface Name { + label: string; +} + +export interface Uri { + label: string; +} + +export interface Entry { + "im:name": ImName; + "im:image": ImImage[]; + summary: Summary; + "im:price": ImPrice; + "im:contentType": ImContentType; + rights?: Rights; + title: Title; + link: Link; + id: Id; + "im:artist": ImArtist; + category: Category; + "im:releaseDate": ImReleaseDate; +} + +export interface ImName { + label: string; +} + +export interface ImImage { + label: string; + attributes: Attributes; +} + +export interface Attributes { + height: string; +} + +export interface Summary { + label: string; +} + +export interface ImPrice { + label: string; + attributes: Attributes2; +} + +export interface Attributes2 { + amount: string; + currency: string; +} + +export interface ImContentType { + attributes: Attributes3; +} + +export interface Attributes3 { + term: string; + label: string; +} + +export interface Rights { + label: string; +} + +export interface Title { + label: string; +} + +export interface Link { + attributes: Attributes4; +} + +export interface Attributes4 { + rel: string; + type: string; + href: string; +} + +export interface Id { + label: string; + attributes: Attributes5; +} + +export interface Attributes5 { + "im:id": string; +} + +export interface ImArtist { + label: string; + attributes?: Attributes6; +} + +export interface Attributes6 { + href: string; +} + +export interface Category { + attributes: Attributes7; +} + +export interface Attributes7 { + "im:id": string; + term: string; + scheme: string; + label: string; +} + +export interface ImReleaseDate { + label: string; + attributes: Attributes8; +} + +export interface Attributes8 { + label: string; +} + +export interface Updated { + label: string; +} + +export interface Rights2 { + label: string; +} + +export interface Title2 { + label: string; +} + +export interface Icon { + label: string; +} + +export interface Link2 { + attributes: Attributes9; +} + +export interface Attributes9 { + rel: string; + type?: string; + href: string; +} + +export interface Id2 { + label: string; +}