mirror of
https://github.com/ershisan99/podcaster.git
synced 2025-12-16 20:59:26 +00:00
chore: refactor imports/exports and some semantics
This commit is contained in:
@@ -14,5 +14,6 @@ module.exports = {
|
||||
"warn",
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
"no-duplicate-imports": ["error", { "includeExports": true }]
|
||||
},
|
||||
};
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
"idb-keyval": "^6.2.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router-dom": "^6.22.3"
|
||||
"react-router-dom": "^6.22.3",
|
||||
"tailwind-merge": "^2.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/typography": "^0.5.12",
|
||||
|
||||
23
pnpm-lock.yaml
generated
23
pnpm-lock.yaml
generated
@@ -29,6 +29,9 @@ importers:
|
||||
react-router-dom:
|
||||
specifier: ^6.22.3
|
||||
version: 6.22.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
tailwind-merge:
|
||||
specifier: ^2.3.0
|
||||
version: 2.3.0
|
||||
devDependencies:
|
||||
'@tailwindcss/typography':
|
||||
specifier: ^0.5.12
|
||||
@@ -89,6 +92,10 @@ packages:
|
||||
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
'@babel/runtime@7.24.4':
|
||||
resolution: {integrity: sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@esbuild/aix-ppc64@0.20.2':
|
||||
resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -1235,6 +1242,9 @@ packages:
|
||||
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
|
||||
engines: {node: '>=8.10.0'}
|
||||
|
||||
regenerator-runtime@0.14.1:
|
||||
resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
|
||||
|
||||
resolve-from@4.0.0:
|
||||
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -1320,6 +1330,9 @@ packages:
|
||||
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
tailwind-merge@2.3.0:
|
||||
resolution: {integrity: sha512-vkYrLpIP+lgR0tQCG6AP7zZXCTLc1Lnv/CCRT3BqJ9CZ3ui2++GPaGb1x/ILsINIMSYqqvrpqjUFsMNLlW99EA==}
|
||||
|
||||
tailwindcss@3.4.3:
|
||||
resolution: {integrity: sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
@@ -1435,6 +1448,10 @@ snapshots:
|
||||
|
||||
'@alloc/quick-lru@5.2.0': {}
|
||||
|
||||
'@babel/runtime@7.24.4':
|
||||
dependencies:
|
||||
regenerator-runtime: 0.14.1
|
||||
|
||||
'@esbuild/aix-ppc64@0.20.2':
|
||||
optional: true
|
||||
|
||||
@@ -2431,6 +2448,8 @@ snapshots:
|
||||
dependencies:
|
||||
picomatch: 2.3.1
|
||||
|
||||
regenerator-runtime@0.14.1: {}
|
||||
|
||||
resolve-from@4.0.0: {}
|
||||
|
||||
resolve@1.22.8:
|
||||
@@ -2529,6 +2548,10 @@ snapshots:
|
||||
|
||||
supports-preserve-symlinks-flag@1.0.0: {}
|
||||
|
||||
tailwind-merge@2.3.0:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.4
|
||||
|
||||
tailwindcss@3.4.3:
|
||||
dependencies:
|
||||
'@alloc/quick-lru': 5.2.0
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Router } from "./router";
|
||||
import { QueryClient } from "@tanstack/react-query";
|
||||
import { createIDBPersister } from "./services/infrastructure/persisters/create-idb-persister";
|
||||
import { PersistQueryClientProvider } from "@tanstack/react-query-persist-client";
|
||||
import { Router } from "./router";
|
||||
import { createIDBPersister } from "./services/infrastructure";
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useParams } from "react-router-dom";
|
||||
import { usePodcastQuery } from "../../services/podcasts/podcast.hooks";
|
||||
import { useTitle } from "../../hooks/use-title";
|
||||
import { usePodcastQuery } from "../../services";
|
||||
import { useTitle } from "../../hooks";
|
||||
|
||||
export function Episode() {
|
||||
const { podcastId, episodeId } = useParams<{
|
||||
|
||||
3
src/components/index.ts
Normal file
3
src/components/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from "./podcast";
|
||||
export * from "./ui";
|
||||
export * from "./episode";
|
||||
4
src/components/podcast/index.ts
Normal file
4
src/components/podcast/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from "./podcast-episodes-list";
|
||||
export * from "./podcast-episodes-table";
|
||||
export * from "./podcast-info-card";
|
||||
export * from "./podcast-preview-card";
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useParams } from "react-router-dom";
|
||||
import { usePodcastQuery } from "../../services/podcasts/podcast.hooks";
|
||||
import { useTitle } from "../../hooks/use-title";
|
||||
import { usePodcastQuery } from "../../services";
|
||||
import { useTitle } from "../../hooks";
|
||||
import { PodcastEpisodesTable } from "./podcast-episodes-table";
|
||||
|
||||
export function PodcastEpisodesList() {
|
||||
|
||||
@@ -6,13 +6,13 @@ import {
|
||||
TableHead,
|
||||
TableHeadCell,
|
||||
TableRow,
|
||||
} from "../ui/table/table";
|
||||
} from "../ui";
|
||||
import { formatDate, formatDuration } from "../../utils";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Episode } from "../../services/rss-parser";
|
||||
import { RssEpisode } from "../../services/rss-parser";
|
||||
|
||||
type Props = {
|
||||
episodes: Episode[] | undefined;
|
||||
episodes: RssEpisode[] | undefined;
|
||||
};
|
||||
|
||||
export const PodcastEpisodesTable = memo(({ episodes }: Props) => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Link, useParams } from "react-router-dom";
|
||||
import { Wrap } from "../utils/wrap";
|
||||
import { Wrap } from "../utils";
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
|
||||
@@ -14,7 +14,7 @@ export function PodcastPreviewCard({
|
||||
detailUrl,
|
||||
}: Props) {
|
||||
return (
|
||||
<Link to={detailUrl} className={"w-full"}>
|
||||
<Link to={detailUrl} className={"w-full"} role={"listitem"}>
|
||||
<div className={"relative -mb-12 mt-12 h-full p-3 pt-12 shadow-md"}>
|
||||
<img
|
||||
src={imageUrl}
|
||||
|
||||
4
src/components/ui/index.ts
Normal file
4
src/components/ui/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from "./input";
|
||||
export * from "./spinner";
|
||||
export * from "./layout";
|
||||
export * from "./table";
|
||||
1
src/components/ui/input/index.ts
Normal file
1
src/components/ui/input/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./input";
|
||||
@@ -1,16 +1,30 @@
|
||||
import { ComponentPropsWithoutRef, ElementRef, forwardRef } from "react";
|
||||
import { clsx } from "clsx";
|
||||
import {
|
||||
ChangeEvent,
|
||||
ComponentPropsWithoutRef,
|
||||
ElementRef,
|
||||
forwardRef,
|
||||
} from "react";
|
||||
import { cn } from "../../../utils";
|
||||
|
||||
type InputProps = ComponentPropsWithoutRef<"input">;
|
||||
type InputProps = ComponentPropsWithoutRef<"input"> & {
|
||||
onValueChange?: (value: string) => void;
|
||||
};
|
||||
type InputRef = ElementRef<"input">;
|
||||
|
||||
export const Input = forwardRef<InputRef, InputProps>(
|
||||
({ className, ...props }, ref) => {
|
||||
const classes = clsx(
|
||||
"w-1/3 rounded-md border border-gray-300 p-2",
|
||||
({ className, onChange, onValueChange, ...props }, ref) => {
|
||||
const classes = cn(
|
||||
"w-full rounded-md border border-gray-300 p-2",
|
||||
className,
|
||||
);
|
||||
|
||||
return <input ref={ref} className={classes} {...props} />;
|
||||
function handleChange(e: ChangeEvent<HTMLInputElement>) {
|
||||
onChange?.(e);
|
||||
onValueChange?.(e.target.value);
|
||||
}
|
||||
|
||||
return (
|
||||
<input ref={ref} className={classes} onChange={handleChange} {...props} />
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
1
src/components/ui/layout/index.ts
Normal file
1
src/components/ui/layout/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./layout";
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Link, Outlet } from "react-router-dom";
|
||||
import { Spinner } from "../spinner/spinner";
|
||||
import { Spinner } from "../spinner";
|
||||
import { useIsFetching } from "@tanstack/react-query";
|
||||
|
||||
export function Layout() {
|
||||
|
||||
1
src/components/ui/spinner/index.ts
Normal file
1
src/components/ui/spinner/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./spinner";
|
||||
1
src/components/ui/table/index.ts
Normal file
1
src/components/ui/table/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./table";
|
||||
@@ -1,11 +1,11 @@
|
||||
import { ComponentPropsWithoutRef, ElementRef, forwardRef } from "react";
|
||||
import { clsx } from "clsx";
|
||||
import { cn } from "../../../utils";
|
||||
|
||||
export const Table = forwardRef<
|
||||
HTMLTableElement,
|
||||
ComponentPropsWithoutRef<"table">
|
||||
>(({ className, ...rest }, ref) => {
|
||||
const classes = clsx(className, "w-full border-collapse");
|
||||
const classes = cn(className, "w-full border-collapse");
|
||||
|
||||
return <table className={classes} {...rest} ref={ref} />;
|
||||
});
|
||||
@@ -27,7 +27,7 @@ export const TableRow = forwardRef<
|
||||
ElementRef<"tr">,
|
||||
ComponentPropsWithoutRef<"tr">
|
||||
>(({ className, ...rest }, ref) => {
|
||||
const classes = clsx(className, "odd:bg-gray-100");
|
||||
const classes = cn(className, "odd:bg-gray-100");
|
||||
return <tr className={classes} {...rest} ref={ref} />;
|
||||
});
|
||||
|
||||
@@ -35,7 +35,7 @@ export const TableHeadCell = forwardRef<
|
||||
ElementRef<"th">,
|
||||
ComponentPropsWithoutRef<"th">
|
||||
>(({ children, className, ...rest }, ref) => {
|
||||
const classes = clsx(className, "py-3 px-4 text-start");
|
||||
const classes = cn(className, "py-3 px-4 text-start");
|
||||
|
||||
return (
|
||||
<th className={classes} {...rest} ref={ref}>
|
||||
@@ -48,7 +48,7 @@ export const TableCell = forwardRef<
|
||||
ElementRef<"td">,
|
||||
ComponentPropsWithoutRef<"td">
|
||||
>(({ className, ...rest }, ref) => {
|
||||
const classes = clsx(className, "py-3 px-4 border-t border-slate-200");
|
||||
const classes = cn(className, "py-3 px-4 border-t border-slate-200");
|
||||
|
||||
return <td className={classes} {...rest} ref={ref} />;
|
||||
});
|
||||
|
||||
1
src/components/utils/index.ts
Normal file
1
src/components/utils/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./wrap";
|
||||
1
src/hooks/index.ts
Normal file
1
src/hooks/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./use-title";
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState } from "react";
|
||||
|
||||
import { PodcastPreviewCard } from "../components/podcast/podcast-preview-card";
|
||||
import { useTopPodcastsQuery } from "../services/podcasts/podcast.hooks";
|
||||
import { PodcastPreviewCard, Input } from "../components";
|
||||
import { useTopPodcastsQuery } from "../services";
|
||||
|
||||
export function Home() {
|
||||
const { data, isLoading } = useTopPodcastsQuery();
|
||||
@@ -28,15 +28,14 @@ export function Home() {
|
||||
>
|
||||
{filteredData?.length}
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
<Input
|
||||
className={"w-1/3"}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
onValueChange={setSearch}
|
||||
placeholder="Filter podcasts..."
|
||||
className={"w-1/3 rounded-md border border-gray-300 p-2"}
|
||||
/>
|
||||
</div>
|
||||
<div className={"grid grid-cols-4 gap-x-6 gap-y-24"}>
|
||||
<section role={"list"} className={"grid grid-cols-4 gap-x-6 gap-y-24"}>
|
||||
{filteredData?.map((podcast) => (
|
||||
<PodcastPreviewCard
|
||||
detailUrl={`/podcast/${podcast.id}`}
|
||||
@@ -46,7 +45,7 @@ export function Home() {
|
||||
imageUrl={podcast.images.large}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Outlet, useParams } from "react-router-dom";
|
||||
import { usePodcastQuery } from "../services/podcasts/podcast.hooks";
|
||||
import { PodcastInfoCard } from "../components/podcast/podcast-info-card";
|
||||
import { usePodcastQuery } from "../services";
|
||||
import { PodcastInfoCard } from "../components";
|
||||
|
||||
export function Podcast() {
|
||||
const { podcastId } = useParams<{ podcastId: string }>();
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { createBrowserRouter, RouterProvider } from "react-router-dom";
|
||||
import { Home } from "./pages/home";
|
||||
import { Podcast } from "./pages/podcast";
|
||||
import { Episode } from "./components/episode";
|
||||
import { PodcastEpisodesList } from "./components/podcast/podcast-episodes-list";
|
||||
import { Layout } from "./components/ui/layout/layout";
|
||||
import { PodcastEpisodesList, Episode, Layout } from "./components";
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
|
||||
1
src/services/index.ts
Normal file
1
src/services/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./podcasts";
|
||||
1
src/services/infrastructure/index.ts
Normal file
1
src/services/infrastructure/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./persisters";
|
||||
1
src/services/infrastructure/persisters/index.ts
Normal file
1
src/services/infrastructure/persisters/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./create-idb-persister";
|
||||
3
src/services/podcasts/dto/index.ts
Normal file
3
src/services/podcasts/dto/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from "./episode.dto";
|
||||
export * from "./podcast.dto";
|
||||
export * from "./podcast-extra.dto";
|
||||
3
src/services/podcasts/index.ts
Normal file
3
src/services/podcasts/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from "./podcast.hooks";
|
||||
export * from "./dto";
|
||||
export * from "./podcasts.types";
|
||||
@@ -1,7 +1,6 @@
|
||||
import { GetEpisodesResponse, GetTopPodcastsResponse } from "./podcasts.types";
|
||||
import { PodcastDTO } from "./dto/podcast.dto";
|
||||
import { PodcastDTO, PodcastExtraDTO } from "./dto";
|
||||
import { RssParser } from "../rss-parser";
|
||||
import { PodcastExtraDTO } from "./dto/podcast-extra.dto";
|
||||
import { bypassCorsService } from "../bypass-cors";
|
||||
|
||||
class PodcastsService {
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export * from "./rss-parser";
|
||||
export * from "./rss-parser.types";
|
||||
|
||||
@@ -1,26 +1,14 @@
|
||||
export interface Episode {
|
||||
id: string;
|
||||
releaseDate: string;
|
||||
audioUrl: string;
|
||||
title: string;
|
||||
durationSeconds: number;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface Podcast {
|
||||
description: string;
|
||||
episodes: Episode[];
|
||||
}
|
||||
import { RssEpisode, RssPodcast } from "./rss-parser.types";
|
||||
|
||||
export class RssParser {
|
||||
public static parse(rss: string): Podcast {
|
||||
public static parse(rss: string): RssPodcast {
|
||||
const parser = new DOMParser();
|
||||
const xmlDoc = parser.parseFromString(rss, "text/xml");
|
||||
|
||||
const channel = xmlDoc.querySelector("channel");
|
||||
const description = this.getElementInnerHtml(channel, "description");
|
||||
|
||||
const episodes: Episode[] = [];
|
||||
const episodes: RssEpisode[] = [];
|
||||
|
||||
const items = Array.from(xmlDoc.querySelectorAll("item"));
|
||||
|
||||
@@ -55,6 +43,7 @@ export class RssParser {
|
||||
episodes,
|
||||
};
|
||||
}
|
||||
|
||||
public static parseDuration(durationStr: string): number {
|
||||
if (durationStr.includes(":")) {
|
||||
const parts = durationStr.split(":").map((part) => parseInt(part, 10));
|
||||
@@ -71,6 +60,7 @@ export class RssParser {
|
||||
return parseInt(durationStr, 10);
|
||||
}
|
||||
}
|
||||
|
||||
public static getElementByTagName(element: Element | null, tagName: string) {
|
||||
return element?.getElementsByTagName(tagName)[0];
|
||||
}
|
||||
|
||||
13
src/services/rss-parser/rss-parser.types.ts
Normal file
13
src/services/rss-parser/rss-parser.types.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export interface RssEpisode {
|
||||
id: string;
|
||||
releaseDate: string;
|
||||
audioUrl: string;
|
||||
title: string;
|
||||
durationSeconds: number;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface RssPodcast {
|
||||
description: string;
|
||||
episodes: RssEpisode[];
|
||||
}
|
||||
6
src/utils/classnames.ts
Normal file
6
src/utils/classnames.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { clsx, type ClassValue } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
export * from "./datetime";
|
||||
export * from "./classnames";
|
||||
|
||||
Reference in New Issue
Block a user