[#17] Now have a download button to download a podcast episode.

This commit is contained in:
Justin Walrath 2025-05-14 18:02:57 -04:00
parent 34539a16df
commit 5dfc18b22e
5 changed files with 122 additions and 5 deletions

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg width="24px" height="24px" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" color="#000000"><path d="M6 20L18 20" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M12 4V16M12 16L15.5 12.5M12 16L8.5 12.5" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg>

After

Width:  |  Height:  |  Size: 430 B

View File

@ -1,8 +1,9 @@
"use client";
import Image from 'next/image';
import { PodcastDto } from '../../../common/dtos/podcastDto';
import { PodcastDto } from '@/common/dtos/podcastDto';
import { useState } from 'react';
import DownloadButton from '@/app/common/components/buttons/DownloadButton';
export default function PodcastCard({
imageUrl = "https://placehold.co/400",
@ -40,9 +41,22 @@ export default function PodcastCard({
<p>By: {author}</p>
<p>Uploaded: {uploadDate}</p>
</div>
<div className="flex">
<a href={url} target="_blank" rel="noopener noreferrer" className="mt-4 text-blue-500 hover:underline text-sm">
Listen Now
</a>
{url &&
<DownloadButton
href={url}
iconSrc={`${process.env.NEXT_PUBLIC_API_BASE_URL}/icons/download.svg`}
iconAlt="Download"
text="Download"
download={`${title}-${url.split('/').pop()}`}
className="mt-4 ml-2"
debounceMs={2000}
/>
}
</div>
</div>
</div>
);

View File

@ -54,7 +54,6 @@ export default function StreamPublishForm({ stream }: { stream?: string | string
};
xhr.send(formData);
}
return (

View File

@ -0,0 +1,50 @@
"use client";
import IconExpandTextButton from "./IconExpandTextButton";
type DownloadButtonProps = {
href: string;
iconSrc: string;
iconAlt?: string;
text: string;
download?: boolean | string;
className?: string;
debounceMs?: number;
disabled?: boolean;
};
export default function DownloadButton({
href,
iconSrc,
iconAlt = "icon",
text,
download = false,
className = "",
debounceMs,
disabled = false
}: DownloadButtonProps) {
const onClick = (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
if (disabled) return;
const link = document.createElement("a");
link.href = href;
if (download) {
link.setAttribute('download', typeof download === 'string' ? download : '');
}
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};
return (
<IconExpandTextButton
iconSrc={iconSrc}
iconAlt={iconAlt}
text={text}
className={className}
debounceMs={debounceMs}
disabled={disabled}
onClick={onClick}
/>
);
}

View File

@ -0,0 +1,53 @@
"use client";
import { useState } from "react";
import Image from "next/image";
type IconExpandTextButtonProps = {
iconSrc: string;
iconAlt?: string;
text: string;
download?: boolean | string;
className?: string;
debounceMs?: number;
onClick?: (e: React.MouseEvent<HTMLAnchorElement>) => void;
disabled?: boolean;
};
export default function IconExpandTextButton({
iconSrc,
iconAlt = "icon",
text,
download = false,
className = "",
debounceMs,
onClick,
disabled = false
}: IconExpandTextButtonProps) {
const [debounced, setDebounced] = useState(false),
isDisabled = disabled || debounced,
handleClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
e.preventDefault();
if (disabled || debounced) return;
if (debounceMs != null) {
setDebounced(true);
setTimeout(() => setDebounced(false), debounceMs);
}
onClick?.(e);
};
return (
<a
download={download}
className={`flex items-center group transition-all duration-200 bg-transparent rounded-sm hover:bg-purple-400 hover:shadow pl-1 pr-2 py-1 text-sm select-none ${className}`}
onClick={handleClick}
tabIndex={isDisabled ? -1 : 0}
aria-disabled={isDisabled}
>
<Image src={iconSrc} alt={iconAlt} width={20} height={20} />
<span className="overflow-hidden max-w-0 group-hover:max-w-[80px] transition-all duration-200 whitespace-nowrap ml-1">
{text}
</span>
</a>
);
}