chore: refactor, move things around for better maintainability

fix: add semantic tags to account for screen readers
This commit is contained in:
2024-04-21 23:25:11 +02:00
parent cff65c7516
commit e29482de20
17 changed files with 125 additions and 95 deletions

View File

@@ -1,77 +0,0 @@
import { Link, useParams } from "react-router-dom";
import { usePodcastQuery } from "../services/podcasts/podcast.hooks";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeadCell,
TableRow,
} from "./ui/table/table";
import { useTitle } from "../hooks/use-title";
export function PodcastEpisodesList() {
const { podcastId } = useParams<{ podcastId: string }>();
const { data: podcast } = usePodcastQuery(podcastId);
useTitle(podcast?.title ?? "Podcast");
return (
<div>
<div className={"p-3 text-xl font-bold shadow-md"}>
Episodes: {podcast?.trackCount}
</div>
<div className={"mt-6 p-3 shadow-md"}>
<Table>
<TableHead>
<TableRow className={"!bg-slate-50"}>
<TableHeadCell className={"text-start"}>Title</TableHeadCell>
<TableHeadCell className={"text-start"}>Date</TableHeadCell>
<TableHeadCell className={"text-end"}>Duration</TableHeadCell>
</TableRow>
</TableHead>
<TableBody>
{podcast?.episodes?.map((episode) => {
const formattedDate = formatDate(episode.releaseDate);
const formattedDuration = formatDuration(episode.durationSeconds);
const url = `/podcast/${podcastId}/episode/${episode.id}`;
return (
<TableRow key={episode.id}>
<TableCell>
<Link
to={url}
className={"text-indigo-500 hover:underline"}
>
{episode.title}
</Link>
</TableCell>
<TableCell>{formattedDate}</TableCell>
<TableCell className={"text-end"}>
{formattedDuration}
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</div>
</div>
);
}
function formatDuration(duration?: number) {
if (!duration) {
return "N/A";
}
const minutes = Math.floor(duration / 60);
const seconds = Math.floor(duration % 60);
return `${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
}
function formatDate(date: string) {
return new Date(date).toLocaleDateString("es-ES", {
month: "2-digit",
day: "2-digit",
year: "numeric",
});
}

View File

@@ -0,0 +1,22 @@
import { useParams } from "react-router-dom";
import { usePodcastQuery } from "../../services/podcasts/podcast.hooks";
import { useTitle } from "../../hooks/use-title";
import { PodcastEpisodesTable } from "./podcast-episodes-table";
export function PodcastEpisodesList() {
const { podcastId } = useParams<{ podcastId: string }>();
const { data: podcast } = usePodcastQuery(podcastId);
useTitle(podcast?.title ?? "Podcast");
return (
<section>
<header className={"p-3 text-xl font-bold shadow-md"}>
Episodes: {podcast?.trackCount}
</header>
<article className={"mt-6 p-3 shadow-md"}>
<PodcastEpisodesTable episodes={podcast?.episodes} />
</article>
</section>
);
}

View File

@@ -0,0 +1,52 @@
import { memo } from "react";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeadCell,
TableRow,
} from "../ui/table/table";
import { formatDate, formatDuration } from "../../utils";
import { Link } from "react-router-dom";
import { Episode } from "../../services/rss-parser";
type Props = {
episodes: Episode[] | undefined;
};
export const PodcastEpisodesTable = memo(({ episodes }: Props) => {
return (
<Table className={"w-full"}>
<TableHead>
<TableRow className={"!bg-slate-50"}>
<TableHeadCell>Title</TableHeadCell>
<TableHeadCell>Date</TableHeadCell>
<TableHeadCell className={"text-end"}>Duration</TableHeadCell>
</TableRow>
</TableHead>
<TableBody>
{episodes?.map((episode) => {
const formattedDate = formatDate(episode.releaseDate);
const dateTime = new Date(episode.releaseDate).toISOString();
const formattedDuration = formatDuration(episode.durationSeconds);
const url = `episode/${episode.id}`;
return (
<TableRow key={episode.id}>
<TableCell>
<Link to={url} className={"text-indigo-500 hover:underline"}>
{episode.title}
</Link>
</TableCell>
<TableCell>
<time dateTime={dateTime}>{formattedDate}</time>
</TableCell>
<TableCell className={"text-end"}>{formattedDuration}</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
);
});

View File

@@ -1,5 +1,5 @@
import { Link, useParams } from "react-router-dom";
import { Wrap } from "./wrap";
import { Wrap } from "../utils/wrap";
type Props = {
title: string;
@@ -39,10 +39,10 @@ export function PodcastInfoCard({
<em className={"text-sm"}>by {author}</em>
</Wrap>
</div>
<div>
<div className={"px-2 py-2"}>
<strong className={"text-sm"}>Description:</strong>
<div
className={"break-words text-sm italic"}
className={"mt-1 break-words text-sm italic"}
dangerouslySetInnerHTML={{ __html: description }}
></div>
</div>

View File

@@ -0,0 +1,16 @@
import { ComponentPropsWithoutRef, ElementRef, forwardRef } from "react";
import { clsx } from "clsx";
type InputProps = ComponentPropsWithoutRef<"input">;
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,
);
return <input ref={ref} className={classes} {...props} />;
},
);

View File

@@ -1,5 +1,5 @@
import { Link, Outlet } from "react-router-dom";
import { Spinner } from "./spinner";
import { Spinner } from "../spinner/spinner";
import { useIsFetching } from "@tanstack/react-query";
export function Layout() {

View File

@@ -35,7 +35,7 @@ export const TableHeadCell = forwardRef<
ElementRef<"th">,
ComponentPropsWithoutRef<"th">
>(({ children, className, ...rest }, ref) => {
const classes = clsx(className, "py-3 px-4");
const classes = clsx(className, "py-3 px-4 text-start");
return (
<th className={classes} {...rest} ref={ref}>

View File

@@ -17,17 +17,17 @@ export function Episode() {
useTitle(episode?.title ?? "Episode");
return (
<div className={"h-fit w-full p-4 pb-6 shadow-md"}>
<h2 className={"text-2xl font-bold tracking-tight"}>{episode?.title}</h2>
<div
<section className={"h-fit w-full p-4 pb-6 shadow-md"}>
<header className={"text-2xl font-bold tracking-tight"}>
{episode?.title}
</header>
<article
className={"prose mt-2 max-w-full border-b pb-4 leading-snug"}
dangerouslySetInnerHTML={{ __html: episode?.description ?? "" }}
/>
<div>
<audio controls src={episode?.audioUrl} className={"mt-4 w-full"}>
Audio is not supported by your browser
</audio>
</div>
</div>
<audio controls src={episode?.audioUrl} className={"mt-4 w-full"}>
Audio is not supported by your browser
</audio>
</section>
);
}

View File

@@ -1,6 +1,6 @@
import { useState } from "react";
import { PodcastPreviewCard } from "../components/podcast-preview-card";
import { PodcastPreviewCard } from "../components/podcast/podcast-preview-card";
import { useTopPodcastsQuery } from "../services/podcasts/podcast.hooks";
export function Home() {

View File

@@ -1,6 +1,6 @@
import { Outlet, useParams } from "react-router-dom";
import { usePodcastQuery } from "../services/podcasts/podcast.hooks";
import { PodcastInfoCard } from "../components/podcast-info-card";
import { PodcastInfoCard } from "../components/podcast/podcast-info-card";
export function Podcast() {
const { podcastId } = useParams<{ podcastId: string }>();

View File

@@ -2,8 +2,8 @@ import { createBrowserRouter, RouterProvider } from "react-router-dom";
import { Home } from "./pages/home";
import { Podcast } from "./pages/podcast";
import { Episode } from "./pages/episode";
import { PodcastEpisodesList } from "./components/podcast-episodes-list";
import { Layout } from "./components/layout";
import { PodcastEpisodesList } from "./components/podcast/podcast-episodes-list";
import { Layout } from "./components/ui/layout/layout";
const router = createBrowserRouter([
{

16
src/utils/datetime.ts Normal file
View File

@@ -0,0 +1,16 @@
export function formatDate(date: string) {
return new Date(date).toLocaleDateString("es-ES", {
month: "2-digit",
day: "2-digit",
year: "numeric",
});
}
export function formatDuration(duration?: number) {
if (!duration) {
return "N/A";
}
const minutes = Math.floor(duration / 60);
const seconds = Math.floor(duration % 60);
return `${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
}

1
src/utils/index.ts Normal file
View File

@@ -0,0 +1 @@
export * from "./datetime";