mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-10 23:47:12 +02:00
fix: improve NIP-75 goal rendering
- Remove icons from goal renderers - Remove "sats" suffixes from amounts - Use muted text instead of destructive color for closed goals - Content is the title, summary tag is the description - Only show description if summary tag exists
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
import { useMemo } from "react";
|
||||
import { NostrEvent } from "@/types/nostr";
|
||||
import { Target, Clock, Users } from "lucide-react";
|
||||
import { useTimeline } from "@/hooks/useTimeline";
|
||||
import {
|
||||
getGoalAmount,
|
||||
getGoalRelays,
|
||||
getGoalClosedAt,
|
||||
getGoalTitle,
|
||||
getGoalSummary,
|
||||
isGoalClosed,
|
||||
getGoalBeneficiaries,
|
||||
} from "@/lib/nip75-helpers";
|
||||
@@ -36,6 +36,7 @@ export function GoalDetailRenderer({ event }: { event: NostrEvent }) {
|
||||
const goalRelays = getGoalRelays(event);
|
||||
const closedAt = getGoalClosedAt(event);
|
||||
const title = getGoalTitle(event);
|
||||
const summary = getGoalSummary(event);
|
||||
const closed = isGoalClosed(event);
|
||||
const beneficiaries = getGoalBeneficiaries(event);
|
||||
|
||||
@@ -112,28 +113,20 @@ export function GoalDetailRenderer({ event }: { event: NostrEvent }) {
|
||||
<div className="flex flex-col gap-6 p-6 max-w-2xl mx-auto">
|
||||
{/* Header */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex items-start gap-3">
|
||||
<Target className="size-8 text-primary mt-1 shrink-0" />
|
||||
<h1 className="text-2xl font-bold text-foreground">{title}</h1>
|
||||
</div>
|
||||
<h1 className="text-2xl font-bold text-foreground">{title}</h1>
|
||||
|
||||
{/* Description */}
|
||||
{event.content && (
|
||||
<p className="text-muted-foreground whitespace-pre-wrap">
|
||||
{event.content}
|
||||
</p>
|
||||
{/* Description (summary tag only) */}
|
||||
{summary && (
|
||||
<p className="text-muted-foreground whitespace-pre-wrap">{summary}</p>
|
||||
)}
|
||||
|
||||
{/* Deadline */}
|
||||
{closedAt && (
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<Clock className="size-4" />
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{closed ? (
|
||||
<span className="text-destructive font-medium">
|
||||
Goal closed on {deadlineText}
|
||||
</span>
|
||||
<span>Closed on {deadlineText}</span>
|
||||
) : (
|
||||
<span className="text-muted-foreground">Ends {deadlineText}</span>
|
||||
<span>Ends {deadlineText}</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
@@ -147,7 +140,7 @@ export function GoalDetailRenderer({ event }: { event: NostrEvent }) {
|
||||
{raisedSats.toLocaleString()}
|
||||
</span>
|
||||
<span className="text-muted-foreground">
|
||||
of {targetSats.toLocaleString()} sats
|
||||
of {targetSats.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
<Progress value={progress} className="h-3" />
|
||||
@@ -178,12 +171,9 @@ export function GoalDetailRenderer({ event }: { event: NostrEvent }) {
|
||||
|
||||
{/* Contributors */}
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Users className="size-4 text-muted-foreground" />
|
||||
<h2 className="text-sm font-medium text-muted-foreground uppercase tracking-wide">
|
||||
Contributors
|
||||
</h2>
|
||||
</div>
|
||||
<h2 className="text-sm font-medium text-muted-foreground uppercase tracking-wide">
|
||||
Contributors
|
||||
</h2>
|
||||
|
||||
{loading && contributors.length === 0 ? (
|
||||
<div className="flex flex-col gap-2">
|
||||
@@ -219,7 +209,7 @@ export function GoalDetailRenderer({ event }: { event: NostrEvent }) {
|
||||
)}
|
||||
</div>
|
||||
<span className="font-mono text-sm font-medium">
|
||||
{amountSats.toLocaleString()} sats
|
||||
{amountSats.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -4,13 +4,13 @@ import {
|
||||
BaseEventContainer,
|
||||
ClickableEventTitle,
|
||||
} from "./BaseEventRenderer";
|
||||
import { Target, Clock } from "lucide-react";
|
||||
import { useTimeline } from "@/hooks/useTimeline";
|
||||
import {
|
||||
getGoalAmount,
|
||||
getGoalRelays,
|
||||
getGoalClosedAt,
|
||||
getGoalTitle,
|
||||
getGoalSummary,
|
||||
isGoalClosed,
|
||||
} from "@/lib/nip75-helpers";
|
||||
import { getZapAmount } from "applesauce-common/helpers/zap";
|
||||
@@ -31,6 +31,7 @@ export function GoalRenderer({ event }: BaseEventProps) {
|
||||
const goalRelays = getGoalRelays(event);
|
||||
const closedAt = getGoalClosedAt(event);
|
||||
const title = getGoalTitle(event);
|
||||
const summary = getGoalSummary(event);
|
||||
const closed = isGoalClosed(event);
|
||||
|
||||
// Fetch zaps for this goal from specified relays
|
||||
@@ -80,20 +81,17 @@ export function GoalRenderer({ event }: BaseEventProps) {
|
||||
<BaseEventContainer event={event}>
|
||||
<div className="flex flex-col gap-3">
|
||||
{/* Title */}
|
||||
<div className="flex items-start gap-2">
|
||||
<Target className="size-5 text-primary mt-0.5 shrink-0" />
|
||||
<ClickableEventTitle
|
||||
event={event}
|
||||
className="text-base font-semibold text-foreground leading-tight"
|
||||
>
|
||||
{title}
|
||||
</ClickableEventTitle>
|
||||
</div>
|
||||
<ClickableEventTitle
|
||||
event={event}
|
||||
className="text-base font-semibold text-foreground leading-tight"
|
||||
>
|
||||
{title}
|
||||
</ClickableEventTitle>
|
||||
|
||||
{/* Description (full content if different from title) */}
|
||||
{event.content && event.content.trim() !== title && (
|
||||
{/* Description (summary tag only) */}
|
||||
{summary && (
|
||||
<p className="text-sm text-muted-foreground line-clamp-2">
|
||||
{event.content}
|
||||
{summary}
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -111,7 +109,7 @@ export function GoalRenderer({ event }: BaseEventProps) {
|
||||
{raisedSats.toLocaleString()}
|
||||
</span>
|
||||
{" / "}
|
||||
{targetSats.toLocaleString()} sats
|
||||
{targetSats.toLocaleString()}
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
@@ -124,10 +122,9 @@ export function GoalRenderer({ event }: BaseEventProps) {
|
||||
|
||||
{/* Deadline */}
|
||||
{closedAt && (
|
||||
<div className="flex items-center gap-1.5 text-xs text-muted-foreground">
|
||||
<Clock className="size-3" />
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{closed ? (
|
||||
<span className="text-destructive">Closed</span>
|
||||
<span>Closed on {deadlineText}</span>
|
||||
) : (
|
||||
<span>Ends {deadlineText}</span>
|
||||
)}
|
||||
|
||||
@@ -103,24 +103,14 @@ export function isGoalClosed(event: NostrEvent): boolean {
|
||||
|
||||
/**
|
||||
* Get a display title for the goal
|
||||
* Falls back to summary, then content truncated, then "Untitled Goal"
|
||||
* Content is the goal title per NIP-75
|
||||
* @param event Goal event (kind 9041)
|
||||
* @returns Display title string
|
||||
*/
|
||||
export function getGoalTitle(event: NostrEvent): string {
|
||||
// First try summary tag
|
||||
const summary = getGoalSummary(event);
|
||||
if (summary) return summary;
|
||||
|
||||
// Fall back to content (first line, truncated)
|
||||
const content = event.content?.trim();
|
||||
if (content) {
|
||||
const firstLine = content.split("\n")[0];
|
||||
if (firstLine.length > 80) {
|
||||
return firstLine.slice(0, 77) + "...";
|
||||
}
|
||||
return firstLine;
|
||||
return content;
|
||||
}
|
||||
|
||||
return "Untitled Goal";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user