Various changes

* #30 key update on map function
* #26 Formatted dates on the podcast cards
* #33 Listen now button is a styled link button
* Moved Add button to buttons component
* #34 Now sort the episodes by default in reverse order by timestamp.
This commit is contained in:
Justin Walrath 2025-05-16 08:06:32 -04:00
parent f99c04f005
commit 857455fa6d
7 changed files with 74 additions and 15 deletions

View File

@ -3,7 +3,8 @@
import Image from 'next/image'; import Image from 'next/image';
import { PodcastDto } from '@/common/dtos/podcastDto'; import { PodcastDto } from '@/common/dtos/podcastDto';
import { useState } from 'react'; import { useState } from 'react';
import DownloadButton from '@/app/common/components/buttons/DownloadButton'; import DownloadIconButton from '@/app/common/components/buttons/downloadIconButton';
import LinkIconButton from '@/app/common/components/buttons/linkIconButton';
export default function PodcastCard({ export default function PodcastCard({
imageUrl, imageUrl,
@ -39,20 +40,34 @@ export default function PodcastCard({
</button> </button>
<div className="mt-2 text-sm text-gray-500"> <div className="mt-2 text-sm text-gray-500">
<p>By: {author}</p> <p>By: {author}</p>
<p>Uploaded: {uploadDate}</p> <p>Uploaded: {uploadDate ?
new Date(Number(uploadDate)).toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric"
}) : "Unknown"}</p>
</div> </div>
<div className="flex"> <div className="flex">
{url &&
<LinkIconButton
href={url}
iconSrc={`${process.env.NEXT_PUBLIC_API_BASE_URL}/icons/play.svg`}
iconAlt="Listen Now"
text="Listen Now"
className="mt-4"
/>
}
<a href={url} target="_blank" rel="noopener noreferrer" className="mt-4 text-blue-500 hover:underline text-sm"> <a href={url} target="_blank" rel="noopener noreferrer" className="mt-4 text-blue-500 hover:underline text-sm">
Listen Now
</a> </a>
{url && {url &&
<DownloadButton <DownloadIconButton
href={url} href={url}
iconSrc={`${process.env.NEXT_PUBLIC_API_BASE_URL}/icons/download.svg`} iconSrc={`${process.env.NEXT_PUBLIC_API_BASE_URL}/icons/download.svg`}
iconAlt="Download" iconAlt="Download"
text="Download" text="Download"
download={`${title}-${url.split('/').pop()}`} download={`${title}-${url.split('/').pop()}`}
className="mt-4 ml-2" className="mt-4"
debounceMs={2000} debounceMs={2000}
/> />
} }

View File

@ -2,8 +2,8 @@
import PodcastCard from './podcastCard'; import PodcastCard from './podcastCard';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { PodcastDto } from '../../../common/dtos/podcastDto'; import { PodcastDto } from '@/common/dtos/podcastDto';
import AddPodcastCard from './addPodcastCard'; import AddButton from '@/app/common/components/buttons/addButton';
type PodcastListProps = { type PodcastListProps = {
stream?: string | string[]; stream?: string | string[];
@ -20,7 +20,7 @@ export default function PodcastList({ stream }: PodcastListProps) {
return ( return (
<div className="max-w-[800px] mx-auto w-full px-4"> <div className="max-w-[800px] mx-auto w-full px-4">
<AddPodcastCard stream={stream}/> <AddButton stream={stream} />
{ {
podcastData.map((podcast, index) => ( podcastData.map((podcast, index) => (
<div key={index} className="flex justify-center"> <div key={index} className="flex justify-center">

View File

@ -3,7 +3,7 @@
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
export default function AddPodcastCard({ stream }: { stream?: string | string[] }) { export default function AddButton({ stream }: { stream?: string | string[] }) {
const router = useRouter(), const router = useRouter(),
defaultBottomOffset = 16, defaultBottomOffset = 16,
[bottomOffset, setBottomOffset] = useState(defaultBottomOffset); [bottomOffset, setBottomOffset] = useState(defaultBottomOffset);

View File

@ -1,8 +1,8 @@
"use client"; "use client";
import IconExpandTextButton from "./IconExpandTextButton"; import IconExpandTextButton from "./iconExpandTextButton";
type DownloadButtonProps = { type DownloadIconButtonProps = {
href: string; href: string;
iconSrc: string; iconSrc: string;
iconAlt?: string; iconAlt?: string;
@ -13,7 +13,7 @@ type DownloadButtonProps = {
disabled?: boolean; disabled?: boolean;
}; };
export default function DownloadButton({ export default function DownloadIconButton({
href, href,
iconSrc, iconSrc,
iconAlt = "icon", iconAlt = "icon",
@ -22,7 +22,7 @@ export default function DownloadButton({
className = "", className = "",
debounceMs, debounceMs,
disabled = false disabled = false
}: DownloadButtonProps) { }: DownloadIconButtonProps) {
const onClick = (e: React.MouseEvent<HTMLAnchorElement>) => { const onClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
e.preventDefault(); e.preventDefault();
if (disabled) return; if (disabled) return;

View File

@ -0,0 +1,42 @@
"use client";
import IconExpandTextButton from "./iconExpandTextButton";
import { useRouter } from "next/navigation";
type LinkIconButtonProps = {
href: string;
iconSrc: string;
iconAlt?: string;
text: string;
className?: string;
debounceMs?: number;
disabled?: boolean;
};
export default function LinkIconButton({
href,
iconSrc,
iconAlt = "icon",
text,
className = "",
debounceMs,
disabled = false
}: LinkIconButtonProps) {
const router = useRouter(),
onClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
e.preventDefault();
if (disabled || href == null) return;
router.push(href);
};
return (
<IconExpandTextButton
iconSrc={iconSrc}
iconAlt={iconAlt}
text={text}
className={className}
debounceMs={debounceMs}
disabled={disabled}
onClick={onClick}
/>
);
}

View File

@ -79,7 +79,7 @@ export default function Home() {
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-6"> <div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-6">
{podcasts.map(podcast => ( {podcasts.map(podcast => (
<button <button
key={podcast.streamId} key={podcast.podcastId}
type='button' type='button'
onClick={() => handlePodcastClick(podcast)} onClick={() => handlePodcastClick(podcast)}
className="aspect-square bg-purple-100 rounded-lg flex items-center justify-center overflow-hidden shadow focus:outline-none focus:ring-2 focus:ring-purple-400 transition" className="aspect-square bg-purple-100 rounded-lg flex items-center justify-center overflow-hidden shadow focus:outline-none focus:ring-2 focus:ring-purple-400 transition"

View File

@ -9,7 +9,9 @@ import { preparePodcastItem } from '../../../common/helpers/data';
const get = async (req: NextApiRequest, res: NextApiResponse<PodcastDto[]>) => { const get = async (req: NextApiRequest, res: NextApiResponse<PodcastDto[]>) => {
const stream = req.query.stream as string, const stream = req.query.stream as string,
podcasts = (await getPodcasts(stream as string)).map(preparePodcastItem); podcasts = (await getPodcasts(stream as string))
.map(preparePodcastItem)
.sort((a, b) => Number(b.uploadDate) - Number(a.uploadDate));
res.status(200).json(podcasts); res.status(200).json(podcasts);
}; };