diff --git a/src/components/nostr/kinds/ZapstoreAppDetailRenderer.tsx b/src/components/nostr/kinds/ZapstoreAppDetailRenderer.tsx index 2465308..9fddd1c 100644 --- a/src/components/nostr/kinds/ZapstoreAppDetailRenderer.tsx +++ b/src/components/nostr/kinds/ZapstoreAppDetailRenderer.tsx @@ -147,6 +147,7 @@ function PlatformItem({ platform }: { platform: Platform }) { export function ZapstoreAppDetailRenderer({ event, }: ZapstoreAppDetailRendererProps) { + const { addWindow } = useGrimoire(); const appName = getAppName(event); const summary = getAppSummary(event); const iconUrl = getAppIcon(event); @@ -186,6 +187,19 @@ export function ZapstoreAppDetailRenderer({ }); }, [releases]); + // Get the latest release for the header download button + const latestRelease = sortedReleases[0] || null; + const latestFileEventId = latestRelease + ? getReleaseFileEventId(latestRelease) + : null; + const latestVersion = latestRelease ? getReleaseVersion(latestRelease) : null; + + const handleDownloadLatest = () => { + if (latestFileEventId) { + addWindow("open", { pointer: { id: latestFileEventId } }); + } + }; + return (
{summary}
)} diff --git a/src/components/nostr/kinds/ZapstoreAppRenderer.tsx b/src/components/nostr/kinds/ZapstoreAppRenderer.tsx index 204bb7a..c49cfa2 100644 --- a/src/components/nostr/kinds/ZapstoreAppRenderer.tsx +++ b/src/components/nostr/kinds/ZapstoreAppRenderer.tsx @@ -6,28 +6,92 @@ import { import { getAppName, getAppSummary, + getAppIdentifier, detectPlatforms, + getReleaseVersion, + getReleaseFileEventId, } from "@/lib/zapstore-helpers"; import { PlatformIcon } from "./zapstore/PlatformIcon"; +import { use$ } from "applesauce-react/hooks"; +import eventStore from "@/services/event-store"; +import { useMemo } from "react"; +import { useGrimoire } from "@/core/state"; +import { FileDown } from "lucide-react"; /** * Renderer for Kind 32267 - App Metadata - * Clean feed view with app name, summary, and platform icons + * Clean feed view with app name, summary, platform icons, and download button */ export function ZapstoreAppRenderer({ event }: BaseEventProps) { + const { addWindow } = useGrimoire(); const appName = getAppName(event); const summary = getAppSummary(event); + const identifier = getAppIdentifier(event); const platforms = detectPlatforms(event); + // Query for releases that reference this app + const releasesFilter = useMemo(() => { + if (!identifier) { + return { kinds: [30063], ids: [] }; + } + return { + kinds: [30063], + "#a": [`32267:${event.pubkey}:${identifier}`], + }; + }, [event.pubkey, identifier]); + + const releases = use$( + () => eventStore.timeline(releasesFilter), + [releasesFilter], + ); + + // Get the latest release (by version or created_at) + const latestRelease = useMemo(() => { + if (!releases || releases.length === 0) return null; + return [...releases].sort((a, b) => { + const versionA = getReleaseVersion(a); + const versionB = getReleaseVersion(b); + if (versionA && versionB) { + return versionB.localeCompare(versionA, undefined, { numeric: true }); + } + return b.created_at - a.created_at; + })[0]; + }, [releases]); + + const latestFileEventId = latestRelease + ? getReleaseFileEventId(latestRelease) + : null; + const latestVersion = latestRelease ? getReleaseVersion(latestRelease) : null; + + const handleDownload = (e: React.MouseEvent) => { + e.stopPropagation(); + if (latestFileEventId) { + addWindow("open", { pointer: { id: latestFileEventId } }); + } + }; + return (