From 75ccfd78ed8d8b49b94e54932b08141143868c21 Mon Sep 17 00:00:00 2001 From: TerryM Date: Fri, 5 Jun 2026 19:36:54 +0800 Subject: [PATCH] fix: download button on cards opens the post page On compact cards the user cannot see which file is attached, especially for multi-attachment posts. Make the small download button on PopularRankRow, RecommendedCard, and LatestUpdateCard navigate to the post detail page instead of triggering an immediate download, so the user lands in the full post and picks the exact attachment to download. --- src/components/LatestUpdateCard.tsx | 52 +++++------------------- src/components/PopularRankList.tsx | 55 +++++++------------------ src/components/RecommendedCard.tsx | 62 ++++++----------------------- 3 files changed, 38 insertions(+), 131 deletions(-) diff --git a/src/components/LatestUpdateCard.tsx b/src/components/LatestUpdateCard.tsx index ef30bff..16d62b8 100644 --- a/src/components/LatestUpdateCard.tsx +++ b/src/components/LatestUpdateCard.tsx @@ -1,17 +1,10 @@ -import { LoaderCircle, Play } from "lucide-react"; -import { useState } from "react"; -import { Link } from "react-router-dom"; +import { Play } from "lucide-react"; +import { Link, useNavigate } from "react-router-dom"; import { FavoriteButton } from "../favorites/FavoriteButton"; import { useI18n } from "../i18n"; import { useLocalizedPath } from "../useLocalizedPath"; import type { Attachment, Post } from "../types/post"; import { DownloadCloudIcon } from "./icons/DownloadCloudIcon"; -import { - mediaSaveKindFromAttachment, - useSaveToAlbumGuide, -} from "./SaveToAlbumGuide"; -import { useToast } from "./Toast"; -import { downloadAttachment } from "./messageStream/utils/downloadFile"; import { fileIcon } from "./messageStream/utils/fileIcon"; import { filenameWithExtension, @@ -30,24 +23,11 @@ function LatestActions({ attachment?: Attachment; }) { const { t } = useI18n(); - const { showToast } = useToast(); - const { showSaveToAlbumGuide } = useSaveToAlbumGuide(); - const [isDownloading, setIsDownloading] = useState(false); + const lp = useLocalizedPath(); + const navigate = useNavigate(); - const handleDownload = async () => { - if (!attachment || isDownloading) return; - setIsDownloading(true); - try { - await downloadAttachment(post.id, attachment.id, attachment.filename, { - sizeBytes: attachment.sizeBytes, - }); - const mediaKind = mediaSaveKindFromAttachment(attachment); - if (mediaKind) showSaveToAlbumGuide(mediaKind); - } catch { - showToast(t("downloadFail"), "error"); - } finally { - setIsDownloading(false); - } + const goToPost = () => { + navigate(lp(`/browse?post=${encodeURIComponent(post.id)}`)); }; return ( @@ -59,23 +39,13 @@ function LatestActions({ onClick={(event) => { event.preventDefault(); event.stopPropagation(); - void handleDownload(); + goToPost(); }} - disabled={isDownloading} - aria-label={ - isDownloading ? t("downloading") : `Download ${attachment.filename}` - } - aria-busy={isDownloading} - className="relative z-20 flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-[#191921] text-white outline-none transition hover:bg-[#22232D] focus-visible:ring-2 focus-visible:ring-ark-gold/70 disabled:cursor-wait" + aria-label={t("download")} + title={t("download")} + className="relative z-20 flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-[#191921] text-white outline-none transition hover:bg-[#22232D] focus-visible:ring-2 focus-visible:ring-ark-gold/70" > - {isDownloading ? ( - - ) : ( - - )} + ) : null} diff --git a/src/components/PopularRankList.tsx b/src/components/PopularRankList.tsx index c939352..12123df 100644 --- a/src/components/PopularRankList.tsx +++ b/src/components/PopularRankList.tsx @@ -4,7 +4,6 @@ import { FileText, Image as ImageIcon, Link as LinkIcon, - LoaderCircle, Music, Presentation, Video, @@ -21,9 +20,6 @@ import { cleanCategoryDisplayName } from "../utils/categoryDisplay"; import { formatDateYmd } from "../utils/format"; import { postToResource } from "../utils/postResourceAdapter"; import type { Post } from "../types/post"; -import { downloadAttachment } from "./messageStream/utils/downloadFile"; -import { mediaSaveKindFromType, useSaveToAlbumGuide } from "./SaveToAlbumGuide"; -import { useToast } from "./Toast"; import { FavoriteButton } from "../favorites/FavoriteButton"; const MEDALS = ["🥇", "🥈", "🥉"]; @@ -105,31 +101,18 @@ export function PopularRankRow({ const { t, lang } = useI18n(); const navigate = useNavigate(); const lp = useLocalizedPath(); - const { showToast } = useToast(); - const { showSaveToAlbumGuide } = useSaveToAlbumGuide(); - const [isDownloading, setIsDownloading] = useState(false); const [coverFailed, setCoverFailed] = useState(false); const r = postToResource(post, lang, categories); const cover = r.coverImage && !coverFailed ? assetUrl(r.coverImage) : ""; const isTop3 = showRank && index < MEDALS.length; - const handleDownload = async () => { - if (isDownloading || !r.downloadPostId || !r.downloadAttachmentId) return; - setIsDownloading(true); - try { - await downloadAttachment( - r.downloadPostId, - r.downloadAttachmentId, - r.title, - ); - const mediaKind = mediaSaveKindFromType(r.type); - if (mediaKind) showSaveToAlbumGuide(mediaKind); - } catch { - showToast(t("downloadFail"), "error"); - } finally { - setIsDownloading(false); - } + const goToPost = () => { + const params = new URLSearchParams(); + if (browseSort) params.set("sort", browseSort); + params.set("post", post.id); + if (singlePostLink) params.set("single", "1"); + navigate(lp(`/browse?${params.toString()}`)); }; return ( @@ -142,13 +125,7 @@ export function PopularRankRow({ > ) : null} diff --git a/src/components/RecommendedCard.tsx b/src/components/RecommendedCard.tsx index f3566ca..2dbbb2b 100644 --- a/src/components/RecommendedCard.tsx +++ b/src/components/RecommendedCard.tsx @@ -1,20 +1,13 @@ -import { Download, LoaderCircle } from "lucide-react"; import { m } from "framer-motion"; -import { Link } from "react-router-dom"; +import { Link, useNavigate } from "react-router-dom"; import type { Resource } from "../api"; import { assetUrl } from "../api"; import { useI18n } from "../i18n"; import { useLocalizedPath } from "../useLocalizedPath"; -import { useMemo, useState } from "react"; +import { useMemo } from "react"; import { formatDateYmd } from "../utils/format"; import { DownloadCloudIcon } from "./icons/DownloadCloudIcon"; import { officialRecommendationCoverFallbacks } from "./FigmaBanner"; -import { - downloadAttachment, - downloadFile, -} from "./messageStream/utils/downloadFile"; -import { mediaSaveKindFromType, useSaveToAlbumGuide } from "./SaveToAlbumGuide"; -import { useToast } from "./Toast"; import { FavoriteButton } from "../favorites/FavoriteButton"; function isPlaceholderAsset(path: string | undefined | null) { @@ -53,9 +46,7 @@ export function RecommendedCard({ }) { const { t } = useI18n(); const lp = useLocalizedPath(); - const { showToast } = useToast(); - const { showSaveToAlbumGuide } = useSaveToAlbumGuide(); - const [isDownloading, setIsDownloading] = useState(false); + const navigate = useNavigate(); const figmaCover = officialRecommendationCoverFallbacks[ visualIndex % officialRecommendationCoverFallbacks.length @@ -77,26 +68,10 @@ export function RecommendedCard({ ? assetUrl(r.fileUrl || r.previewUrl) : ""; - const handleDownload = async () => { - if (isDownloading) return; - setIsDownloading(true); - try { - if (r.downloadPostId && r.downloadAttachmentId) { - await downloadAttachment( - r.downloadPostId, - r.downloadAttachmentId, - displayTitle, - ); - } else { - await downloadFile(dl, displayTitle); - } - const mediaKind = mediaSaveKindFromType(r.type); - if (mediaKind) showSaveToAlbumGuide(mediaKind); - } catch { - showToast(t("downloadFail"), "error"); - } finally { - setIsDownloading(false); - } + const goToPost = () => { + // Same destination as the card-wide overlay link, so the user lands on the + // post and can choose exactly which attachment to download. + navigate(lp(`/resource/${r.id}`)); }; return ( @@ -193,29 +168,18 @@ export function RecommendedCard({ type="button" className={ useFigmaDesign - ? "relative z-20 flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-[#191921] text-white outline-none transition hover:bg-[#22232D] active:scale-95 focus-visible:ring-2 focus-visible:ring-ark-gold/80 disabled:cursor-wait" - : "relative z-20 shrink-0 rounded-lg p-1 text-white outline-none transition hover:bg-ark-gold/10 active:scale-95 focus-visible:ring-2 focus-visible:ring-ark-gold/80 disabled:cursor-wait" + ? "relative z-20 flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-[#191921] text-white outline-none transition hover:bg-[#22232D] active:scale-95 focus-visible:ring-2 focus-visible:ring-ark-gold/80" + : "relative z-20 shrink-0 rounded-lg p-1 text-white outline-none transition hover:bg-ark-gold/10 active:scale-95 focus-visible:ring-2 focus-visible:ring-ark-gold/80" } - title={isDownloading ? t("downloading") : t("download")} - aria-label={isDownloading ? t("downloading") : t("download")} - aria-busy={isDownloading} - disabled={isDownloading} + title={t("download")} + aria-label={t("download")} onClick={(e) => { e.preventDefault(); e.stopPropagation(); - void handleDownload(); + goToPost(); }} > - {isDownloading ? ( - - ) : useFigmaDesign ? ( - - ) : ( - - )} + ) : null}