mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-12 16:37:06 +02:00
refactor: Improve Profile Badges UX
Feed view: - Show all badge thumbnails (removed 4-badge limit) - Entire feed item is clickable to open detail view - Badge count displayed inline Detail view: - Change from grid to vertical list layout - Show one badge per row with horizontal layout - Display: awarded by author, badge image, name, and description - Better readability for badge information
This commit is contained in:
@@ -35,9 +35,9 @@ function parseAddress(aTagValue: string): AddressPointer | null {
|
||||
}
|
||||
|
||||
/**
|
||||
* Single badge card component with image, name, and description
|
||||
* Single badge row component with author, image, name, and description
|
||||
*/
|
||||
function BadgeCard({
|
||||
function BadgeRow({
|
||||
badgeAddress,
|
||||
awardEventId,
|
||||
}: {
|
||||
@@ -70,63 +70,55 @@ function BadgeCard({
|
||||
const displayTitle = badgeName || badgeIdentifier || "Badge";
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-3 p-4 rounded-lg border border-border bg-card hover:bg-muted/50 transition-colors">
|
||||
<div className="flex gap-4 items-start p-4 rounded-lg border border-border bg-card hover:bg-muted/50 transition-colors">
|
||||
{/* Badge Image */}
|
||||
<div className="flex items-center justify-center">
|
||||
{badgeImageUrl ? (
|
||||
<img
|
||||
src={badgeImageUrl}
|
||||
alt={displayTitle}
|
||||
className="size-20 rounded-lg object-cover"
|
||||
loading="lazy"
|
||||
/>
|
||||
) : (
|
||||
<div className="size-20 rounded-lg bg-muted flex items-center justify-center">
|
||||
<Award className="size-10 text-muted-foreground" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{badgeImageUrl ? (
|
||||
<img
|
||||
src={badgeImageUrl}
|
||||
alt={displayTitle}
|
||||
className="size-16 rounded-lg object-cover flex-shrink-0"
|
||||
loading="lazy"
|
||||
/>
|
||||
) : (
|
||||
<div className="size-16 rounded-lg bg-muted flex items-center justify-center flex-shrink-0">
|
||||
<Award className="size-8 text-muted-foreground" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Badge Info */}
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex flex-col gap-2 flex-1 min-w-0">
|
||||
{/* Awarded by */}
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs text-muted-foreground">Awarded by</span>
|
||||
{awardEvent && <UserName pubkey={awardEvent.pubkey} />}
|
||||
</div>
|
||||
|
||||
{/* Badge Name */}
|
||||
{badgeEvent ? (
|
||||
<ClickableEventTitle
|
||||
event={badgeEvent}
|
||||
className="font-semibold text-foreground text-center line-clamp-2"
|
||||
className="text-lg font-semibold text-foreground"
|
||||
>
|
||||
{displayTitle}
|
||||
</ClickableEventTitle>
|
||||
) : (
|
||||
<h3 className="font-semibold text-foreground text-center line-clamp-2">
|
||||
<h3 className="text-lg font-semibold text-foreground">
|
||||
{displayTitle}
|
||||
</h3>
|
||||
)}
|
||||
|
||||
{/* Badge Description */}
|
||||
{badgeDescription && (
|
||||
<p className="text-xs text-muted-foreground text-center line-clamp-2">
|
||||
{badgeDescription}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">{badgeDescription}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Award Info */}
|
||||
{awardEvent && (
|
||||
<div className="flex flex-col gap-1 pt-2 border-t border-border">
|
||||
<p className="text-xs text-muted-foreground text-center">
|
||||
Awarded by
|
||||
</p>
|
||||
<div className="flex justify-center">
|
||||
<UserName pubkey={awardEvent.pubkey} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detail renderer for Kind 30008 - Profile Badges (NIP-58)
|
||||
* Shows all badges in a grid layout
|
||||
* Shows all badges in a vertical list
|
||||
*/
|
||||
export function ProfileBadgesDetailRenderer({
|
||||
event,
|
||||
@@ -134,7 +126,7 @@ export function ProfileBadgesDetailRenderer({
|
||||
const badgePairs = getProfileBadgePairs(event);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6 p-6 max-w-6xl mx-auto">
|
||||
<div className="flex flex-col gap-6 p-6 max-w-4xl mx-auto">
|
||||
{/* Header */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<h1 className="text-3xl font-bold">Profile Badges</h1>
|
||||
@@ -147,11 +139,11 @@ export function ProfileBadgesDetailRenderer({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Badges Grid */}
|
||||
{/* Badges List */}
|
||||
{badgePairs.length > 0 ? (
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
|
||||
<div className="flex flex-col gap-3">
|
||||
{badgePairs.map((pair, idx) => (
|
||||
<BadgeCard
|
||||
<BadgeRow
|
||||
key={idx}
|
||||
badgeAddress={pair.badgeAddress}
|
||||
awardEventId={pair.awardEventId}
|
||||
|
||||
@@ -74,13 +74,10 @@ function BadgeItem({ badgeAddress }: { badgeAddress: string }) {
|
||||
|
||||
/**
|
||||
* Renderer for Kind 30008 - Profile Badges (NIP-58)
|
||||
* Shows a few badges with "and N others" pattern
|
||||
* Shows all badge thumbnails, clickable to open detail view
|
||||
*/
|
||||
export function ProfileBadgesRenderer({ event }: BaseEventProps) {
|
||||
const badgePairs = getProfileBadgePairs(event);
|
||||
const MAX_VISIBLE = 4;
|
||||
const visibleBadges = badgePairs.slice(0, MAX_VISIBLE);
|
||||
const remainingCount = Math.max(0, badgePairs.length - MAX_VISIBLE);
|
||||
|
||||
if (badgePairs.length === 0) {
|
||||
return (
|
||||
@@ -95,23 +92,20 @@ export function ProfileBadgesRenderer({ event }: BaseEventProps) {
|
||||
|
||||
return (
|
||||
<BaseEventContainer event={event}>
|
||||
<div className="flex items-center gap-3 flex-wrap">
|
||||
{/* Badge Icons */}
|
||||
<div className="flex items-center gap-2">
|
||||
{visibleBadges.map((pair, idx) => (
|
||||
<BadgeItem key={idx} badgeAddress={pair.badgeAddress} />
|
||||
))}
|
||||
</div>
|
||||
<ClickableEventTitle
|
||||
event={event}
|
||||
className="flex items-center gap-2 flex-wrap hover:opacity-80 transition-opacity"
|
||||
>
|
||||
{/* All Badge Thumbnails */}
|
||||
{badgePairs.map((pair, idx) => (
|
||||
<BadgeItem key={idx} badgeAddress={pair.badgeAddress} />
|
||||
))}
|
||||
|
||||
{/* Badge Count */}
|
||||
<ClickableEventTitle
|
||||
event={event}
|
||||
className="text-sm text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<span className="text-sm text-muted-foreground ml-1">
|
||||
{badgePairs.length} {badgePairs.length === 1 ? "badge" : "badges"}
|
||||
{remainingCount > 0 && ` (${remainingCount} more)`}
|
||||
</ClickableEventTitle>
|
||||
</div>
|
||||
</span>
|
||||
</ClickableEventTitle>
|
||||
</BaseEventContainer>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user