mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-12 00:17:02 +02:00
refactor: Update Zapstore app set renderers with improved UX
- ZapstoreAppSetRenderer: Show ALL apps with compact spacing (gap-0.5) like relay lists, removed 5-app limit - ZapstoreAppSetDetailRenderer: Replace raw platform tags with normalized platform icons using detectPlatforms() - Both renderers now provide cleaner, more consistent UI following Grimoire patterns
This commit is contained in:
@@ -6,11 +6,9 @@ import {
|
||||
import {
|
||||
getAppName,
|
||||
getAppSummary,
|
||||
getAppIcon,
|
||||
detectPlatforms,
|
||||
} from "@/lib/zapstore-helpers";
|
||||
import {
|
||||
Package,
|
||||
Globe,
|
||||
Smartphone,
|
||||
TabletSmartphone,
|
||||
@@ -79,52 +77,34 @@ function PlatformIcon({ platform }: { platform: Platform }) {
|
||||
export function ZapstoreAppRenderer({ event }: BaseEventProps) {
|
||||
const appName = getAppName(event);
|
||||
const summary = getAppSummary(event);
|
||||
const iconUrl = getAppIcon(event);
|
||||
const platforms = detectPlatforms(event);
|
||||
|
||||
return (
|
||||
<BaseEventContainer event={event}>
|
||||
<div className="flex gap-3">
|
||||
{/* App Icon */}
|
||||
{iconUrl ? (
|
||||
<img
|
||||
src={iconUrl}
|
||||
alt={appName}
|
||||
className="size-12 rounded-lg object-cover flex-shrink-0"
|
||||
loading="lazy"
|
||||
/>
|
||||
) : (
|
||||
<div className="size-12 rounded-lg bg-muted flex items-center justify-center flex-shrink-0">
|
||||
<Package className="size-6 text-muted-foreground" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
{/* App Name */}
|
||||
<ClickableEventTitle
|
||||
event={event}
|
||||
className="text-base font-semibold text-foreground"
|
||||
>
|
||||
{appName}
|
||||
</ClickableEventTitle>
|
||||
|
||||
{/* Summary */}
|
||||
{summary && (
|
||||
<p className="text-sm text-muted-foreground line-clamp-2">
|
||||
{summary}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* App Info */}
|
||||
<div className="flex flex-col gap-2 flex-1 min-w-0">
|
||||
{/* App Name */}
|
||||
<ClickableEventTitle
|
||||
event={event}
|
||||
className="text-base font-semibold text-foreground"
|
||||
>
|
||||
{appName}
|
||||
</ClickableEventTitle>
|
||||
|
||||
{/* Summary */}
|
||||
{summary && (
|
||||
<p className="text-sm text-muted-foreground line-clamp-2">
|
||||
{summary}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Platform Icons */}
|
||||
{platforms.length > 0 && (
|
||||
<div className="flex items-center gap-2">
|
||||
{platforms.map((platform) => (
|
||||
<PlatformIcon key={platform} platform={platform} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* Platform Icons */}
|
||||
{platforms.length > 0 && (
|
||||
<div className="flex items-center gap-2">
|
||||
{platforms.map((platform) => (
|
||||
<PlatformIcon key={platform} platform={platform} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</BaseEventContainer>
|
||||
);
|
||||
|
||||
@@ -5,19 +5,79 @@ import {
|
||||
getAppName,
|
||||
getAppSummary,
|
||||
getAppIcon,
|
||||
getAppPlatforms,
|
||||
detectPlatforms,
|
||||
getCurationSetIdentifier,
|
||||
} from "@/lib/zapstore-helpers";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import type { Platform } from "@/lib/zapstore-helpers";
|
||||
import { useNostrEvent } from "@/hooks/useNostrEvent";
|
||||
import { useGrimoire } from "@/core/state";
|
||||
import { UserName } from "../UserName";
|
||||
import { Package } from "lucide-react";
|
||||
import {
|
||||
Package,
|
||||
Globe,
|
||||
Smartphone,
|
||||
TabletSmartphone,
|
||||
Monitor,
|
||||
Laptop,
|
||||
} from "lucide-react";
|
||||
|
||||
interface ZapstoreAppSetDetailRendererProps {
|
||||
event: NostrEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Platform icon component with label
|
||||
*/
|
||||
function PlatformIcon({ platform }: { platform: Platform }) {
|
||||
const iconClass = "size-4 text-muted-foreground";
|
||||
|
||||
const getPlatformLabel = () => {
|
||||
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-1.5">
|
||||
{getIcon()}
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{getPlatformLabel()}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expanded app card showing full app details
|
||||
*/
|
||||
@@ -45,7 +105,7 @@ function AppCard({
|
||||
const appName = getAppName(appEvent);
|
||||
const summary = getAppSummary(appEvent);
|
||||
const iconUrl = getAppIcon(appEvent);
|
||||
const platforms = getAppPlatforms(appEvent);
|
||||
const platforms = detectPlatforms(appEvent);
|
||||
|
||||
const handleClick = () => {
|
||||
addWindow("open", { pointer: address });
|
||||
@@ -84,23 +144,12 @@ function AppCard({
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Platforms */}
|
||||
{/* Platform Icons */}
|
||||
{platforms.length > 0 && (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{platforms.slice(0, 6).map((platform) => (
|
||||
<Badge
|
||||
key={platform}
|
||||
variant="secondary"
|
||||
className="text-[10px] px-2 py-0.5"
|
||||
>
|
||||
{platform}
|
||||
</Badge>
|
||||
<div className="flex items-center gap-2">
|
||||
{platforms.map((platform) => (
|
||||
<PlatformIcon key={platform} platform={platform} />
|
||||
))}
|
||||
{platforms.length > 6 && (
|
||||
<Badge variant="outline" className="text-[10px] px-2 py-0">
|
||||
+{platforms.length - 6} more
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -45,17 +45,12 @@ function AppItem({
|
||||
|
||||
/**
|
||||
* Renderer for Kind 30267 - Zapstore App Curation Set
|
||||
* Displays collection name and list of apps
|
||||
* Displays collection name and list of all apps with compact layout
|
||||
*/
|
||||
export function ZapstoreAppSetRenderer({ event }: BaseEventProps) {
|
||||
const setName = getCurationSetName(event);
|
||||
const apps = getAppReferences(event);
|
||||
|
||||
// Show max 5 apps in feed view
|
||||
const MAX_APPS_IN_FEED = 5;
|
||||
const displayApps = apps.slice(0, MAX_APPS_IN_FEED);
|
||||
const remainingCount = apps.length - displayApps.length;
|
||||
|
||||
return (
|
||||
<BaseEventContainer event={event}>
|
||||
<div className="flex flex-col gap-2">
|
||||
@@ -72,17 +67,12 @@ export function ZapstoreAppSetRenderer({ event }: BaseEventProps) {
|
||||
{apps.length} {apps.length === 1 ? "app" : "apps"}
|
||||
</p>
|
||||
|
||||
{/* App List */}
|
||||
{displayApps.length > 0 && (
|
||||
<div className="flex flex-col gap-1.5 pl-4 border-l-2 border-muted">
|
||||
{displayApps.map((ref, idx) => (
|
||||
{/* App List - Show all apps with compact spacing like relay lists */}
|
||||
{apps.length > 0 && (
|
||||
<div className="flex flex-col gap-0.5">
|
||||
{apps.map((ref, idx) => (
|
||||
<AppItem key={idx} address={ref.address} />
|
||||
))}
|
||||
{remainingCount > 0 && (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
+{remainingCount} more app{remainingCount > 1 ? "s" : ""}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -37,8 +37,10 @@ export function ZapstoreReleaseDetailRenderer({
|
||||
|
||||
// Fetch related events
|
||||
const appEvent = useNostrEvent(appPointer || undefined);
|
||||
// Load file event with release event as context for better relay selection
|
||||
const fileEvent = useNostrEvent(
|
||||
fileEventId ? { id: fileEventId } : undefined,
|
||||
event, // Pass release event as context to use author's relays
|
||||
);
|
||||
|
||||
const appName = appEvent ? getAppName(appEvent) : appPointer?.identifier;
|
||||
|
||||
@@ -58,14 +58,14 @@ export function ZapstoreReleaseRenderer({ event }: BaseEventProps) {
|
||||
|
||||
{/* Links */}
|
||||
<div className="flex items-center gap-3 flex-wrap text-sm">
|
||||
{/* App Link */}
|
||||
{/* App Link - show app name with icon */}
|
||||
{appName && (
|
||||
<button
|
||||
onClick={handleAppClick}
|
||||
className="flex items-center gap-1.5 text-primary hover:underline"
|
||||
>
|
||||
<Package className="size-3" />
|
||||
<span>View App</span>
|
||||
<span>{appName}</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
@@ -76,7 +76,7 @@ export function ZapstoreReleaseRenderer({ event }: BaseEventProps) {
|
||||
className="flex items-center gap-1.5 text-primary hover:underline"
|
||||
>
|
||||
<FileDown className="size-3" />
|
||||
<span>Download File</span>
|
||||
<span>Download</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user