refactor: Simplify Zapstore app renderers with platform icons

Improve Zapstore app rendering for cleaner, more intuitive display:

Changes:
- Add detectPlatforms() helper to normalize architecture tags (e.g., "android-arm64-v8a" → "android")
- Replace verbose platform badges with clean platform icons (Android, iOS, Web, macOS, Windows, Linux)
- Remove screenshots from feed view (keep in detail view only)
- Remove repository links and license badges from feed view
- Update detail view to show "Available On" with icon+label platform items

Feed view now shows:
- App icon
- App name
- Summary (2 lines max)
- Platform icons (just icons, no text)

Detail view now shows:
- App icon, name, summary
- Publisher, Package ID, License, Repository (metadata grid)
- Available On (platforms with icons and labels)
- Screenshots gallery (unchanged)

All tests pass (744 total), build succeeds.
This commit is contained in:
Claude
2026-01-11 18:41:07 +00:00
parent c223245e36
commit 2c792ee5c0
3 changed files with 153 additions and 59 deletions

View File

@@ -4,21 +4,79 @@ import {
getAppSummary,
getAppIcon,
getAppImages,
getAppPlatforms,
detectPlatforms,
getAppRepository,
getAppLicense,
getAppIdentifier,
} from "@/lib/zapstore-helpers";
import { Badge } from "@/components/ui/badge";
import type { Platform } from "@/lib/zapstore-helpers";
import { UserName } from "../UserName";
import { ExternalLink } from "@/components/ExternalLink";
import { MediaEmbed } from "../MediaEmbed";
import { Package } from "lucide-react";
import {
Package,
Globe,
Smartphone,
TabletSmartphone,
Monitor,
Laptop,
} from "lucide-react";
interface ZapstoreAppDetailRendererProps {
event: NostrEvent;
}
/**
* Platform icon and label component
*/
function PlatformItem({ platform }: { platform: Platform }) {
const iconClass = "size-5";
const getPlatformName = () => {
switch (platform) {
case "android":
return "Android";
case "ios":
return "iOS";
case "web":
return "Web";
case "macos":
return "macOS";
case "windows":
return "Windows";
case "linux":
return "Linux";
default:
return platform;
}
};
const getIcon = () => {
switch (platform) {
case "android":
return <TabletSmartphone className={iconClass} />;
case "ios":
return <Smartphone className={iconClass} />;
case "web":
return <Globe className={iconClass} />;
case "macos":
return <Laptop className={iconClass} />;
case "windows":
case "linux":
return <Monitor className={iconClass} />;
default:
return null;
}
};
return (
<div className="flex items-center gap-2 px-3 py-2 bg-muted/30 rounded-lg">
{getIcon()}
<span className="text-sm font-medium">{getPlatformName()}</span>
</div>
);
}
/**
* Detail renderer for Kind 32267 - Zapstore App Metadata
* Shows comprehensive app information including screenshots
@@ -31,7 +89,7 @@ export function ZapstoreAppDetailRenderer({
const summary = getAppSummary(event);
const iconUrl = getAppIcon(event);
const images = getAppImages(event);
const platforms = getAppPlatforms(event);
const platforms = detectPlatforms(event);
const repository = getAppRepository(event);
const license = getAppLicense(event);
const identifier = getAppIdentifier(event);
@@ -103,18 +161,10 @@ export function ZapstoreAppDetailRenderer({
{/* Platforms Section */}
{platforms.length > 0 && (
<div className="flex flex-col gap-3">
<h2 className="text-xl font-semibold">
Platforms ({platforms.length})
</h2>
<h2 className="text-xl font-semibold">Available On</h2>
<div className="flex flex-wrap gap-2">
{platforms.map((platform) => (
<Badge
key={platform}
variant="secondary"
className="text-sm px-3 py-1"
>
{platform}
</Badge>
<PlatformItem key={platform} platform={platform} />
))}
</div>
</div>

View File

@@ -7,25 +7,50 @@ import {
getAppName,
getAppSummary,
getAppIcon,
getAppPlatforms,
getAppRepository,
getAppLicense,
detectPlatforms,
} from "@/lib/zapstore-helpers";
import { Badge } from "@/components/ui/badge";
import { ExternalLink } from "@/components/ExternalLink";
import { Package } from "lucide-react";
import {
Package,
Globe,
Smartphone,
TabletSmartphone,
Monitor,
Laptop,
} from "lucide-react";
import type { Platform } from "@/lib/zapstore-helpers";
/**
* Platform icon component
*/
function PlatformIcon({ platform }: { platform: Platform }) {
const iconClass = "size-4 text-muted-foreground";
switch (platform) {
case "android":
return <TabletSmartphone className={iconClass} />;
case "ios":
return <Smartphone className={iconClass} />;
case "web":
return <Globe className={iconClass} />;
case "macos":
return <Laptop className={iconClass} />;
case "windows":
case "linux":
return <Monitor className={iconClass} />;
default:
return null;
}
}
/**
* Renderer for Kind 32267 - Zapstore App Metadata
* Displays app name, icon, summary, and platforms in feed
* Displays app name, icon, summary, and platform icons in feed
*/
export function ZapstoreAppRenderer({ event }: BaseEventProps) {
const appName = getAppName(event);
const summary = getAppSummary(event);
const iconUrl = getAppIcon(event);
const platforms = getAppPlatforms(event);
const repository = getAppRepository(event);
const license = getAppLicense(event);
const platforms = detectPlatforms(event);
return (
<BaseEventContainer event={event}>
@@ -61,41 +86,13 @@ export function ZapstoreAppRenderer({ event }: BaseEventProps) {
</p>
)}
{/* Platforms & License */}
<div className="flex items-center gap-2 flex-wrap">
{platforms.length > 0 && (
<>
{platforms.slice(0, 4).map((platform) => (
<Badge
key={platform}
variant="secondary"
className="text-[10px] px-2 py-0.5"
>
{platform}
</Badge>
))}
{platforms.length > 4 && (
<Badge variant="outline" className="text-[10px] px-2 py-0">
+{platforms.length - 4} more
</Badge>
)}
</>
)}
{license && (
<Badge variant="outline" className="text-[10px] px-2 py-0.5">
{license}
</Badge>
)}
</div>
{/* Repository Link */}
{repository && (
<ExternalLink
href={repository}
className="text-xs truncate max-w-full"
>
{repository}
</ExternalLink>
{/* Platform Icons */}
{platforms.length > 0 && (
<div className="flex items-center gap-2">
{platforms.map((platform) => (
<PlatformIcon key={platform} platform={platform} />
))}
</div>
)}
</div>
</div>

View File

@@ -101,6 +101,53 @@ export function getAppPlatforms(event: NostrEvent): string[] {
return getTagValues(event, "f");
}
/**
* Platform names for display
*/
export type Platform =
| "android"
| "ios"
| "web"
| "linux"
| "windows"
| "macos";
/**
* Detect unique platforms from f tags
* Normalizes architecture-specific tags (e.g., "android-arm64-v8a" → "android")
*/
export function detectPlatforms(event: NostrEvent): Platform[] {
if (event.kind !== 32267 && event.kind !== 1063) return [];
const fTags = getTagValues(event, "f");
const platformSet = new Set<Platform>();
for (const tag of fTags) {
const lower = tag.toLowerCase();
if (lower.startsWith("android")) {
platformSet.add("android");
} else if (lower.startsWith("ios") || lower.includes("iphone")) {
platformSet.add("ios");
} else if (lower === "web" || lower.includes("web")) {
platformSet.add("web");
} else if (lower.includes("linux")) {
platformSet.add("linux");
} else if (lower.includes("windows") || lower.includes("win")) {
platformSet.add("windows");
} else if (
lower.includes("macos") ||
lower.includes("mac") ||
lower.includes("darwin")
) {
platformSet.add("macos");
}
}
// Sort for consistent order
return Array.from(platformSet).sort();
}
/**
* Get release artifact references from kind 32267 a tags (usually kind 30063)
*/