mirror of
https://github.com/hoornet/vega.git
synced 2026-05-11 06:39:10 -07:00
Add network reaction counts to note cards
- fetchReactionCount (kind 7 #e filter) in nostr lib - useReactionCount hook with module-level cache to avoid refetching - NoteCard shows count next to like button; increments optimistically on like - Falls back to "like"/"liked" text when count is zero or still loading Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import { useState, useRef } from "react";
|
import { useState, useRef } from "react";
|
||||||
import { NDKEvent } from "@nostr-dev-kit/ndk";
|
import { NDKEvent } from "@nostr-dev-kit/ndk";
|
||||||
import { useProfile } from "../../hooks/useProfile";
|
import { useProfile } from "../../hooks/useProfile";
|
||||||
|
import { useReactionCount } from "../../hooks/useReactionCount";
|
||||||
import { useUserStore } from "../../stores/user";
|
import { useUserStore } from "../../stores/user";
|
||||||
import { useUIStore } from "../../stores/ui";
|
import { useUIStore } from "../../stores/ui";
|
||||||
import { timeAgo, shortenPubkey } from "../../lib/utils";
|
import { timeAgo, shortenPubkey } from "../../lib/utils";
|
||||||
@@ -27,6 +28,7 @@ export function NoteCard({ event }: NoteCardProps) {
|
|||||||
};
|
};
|
||||||
const [liked, setLiked] = useState(() => getLiked().has(event.id));
|
const [liked, setLiked] = useState(() => getLiked().has(event.id));
|
||||||
const [liking, setLiking] = useState(false);
|
const [liking, setLiking] = useState(false);
|
||||||
|
const [reactionCount, adjustReactionCount] = useReactionCount(event.id);
|
||||||
const [showReply, setShowReply] = useState(false);
|
const [showReply, setShowReply] = useState(false);
|
||||||
const [replyText, setReplyText] = useState("");
|
const [replyText, setReplyText] = useState("");
|
||||||
const [replying, setReplying] = useState(false);
|
const [replying, setReplying] = useState(false);
|
||||||
@@ -43,6 +45,7 @@ export function NoteCard({ event }: NoteCardProps) {
|
|||||||
liked.add(event.id);
|
liked.add(event.id);
|
||||||
localStorage.setItem(likedKey, JSON.stringify(Array.from(liked)));
|
localStorage.setItem(likedKey, JSON.stringify(Array.from(liked)));
|
||||||
setLiked(true);
|
setLiked(true);
|
||||||
|
adjustReactionCount(1);
|
||||||
} finally {
|
} finally {
|
||||||
setLiking(false);
|
setLiking(false);
|
||||||
}
|
}
|
||||||
@@ -134,7 +137,7 @@ export function NoteCard({ event }: NoteCardProps) {
|
|||||||
liked ? "text-accent" : "text-text-dim hover:text-accent"
|
liked ? "text-accent" : "text-text-dim hover:text-accent"
|
||||||
} disabled:cursor-default`}
|
} disabled:cursor-default`}
|
||||||
>
|
>
|
||||||
{liked ? "♥ liked" : "♡ like"}
|
{liked ? "♥" : "♡"}{reactionCount !== null && reactionCount > 0 ? ` ${reactionCount}` : liked ? " liked" : " like"}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
29
src/hooks/useReactionCount.ts
Normal file
29
src/hooks/useReactionCount.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { fetchReactionCount } from "../lib/nostr";
|
||||||
|
|
||||||
|
const cache = new Map<string, number>();
|
||||||
|
|
||||||
|
export function useReactionCount(eventId: string): [number | null, (delta: number) => void] {
|
||||||
|
const [count, setCount] = useState<number | null>(() => cache.get(eventId) ?? null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (cache.has(eventId)) {
|
||||||
|
setCount(cache.get(eventId)!);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fetchReactionCount(eventId).then((n) => {
|
||||||
|
cache.set(eventId, n);
|
||||||
|
setCount(n);
|
||||||
|
});
|
||||||
|
}, [eventId]);
|
||||||
|
|
||||||
|
const adjust = (delta: number) => {
|
||||||
|
setCount((prev) => {
|
||||||
|
const next = (prev ?? 0) + delta;
|
||||||
|
cache.set(eventId, next);
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return [count, adjust];
|
||||||
|
}
|
||||||
@@ -191,6 +191,18 @@ export async function fetchUserNotes(pubkey: string, limit = 30): Promise<NDKEve
|
|||||||
return Array.from(events).sort((a, b) => (b.created_at ?? 0) - (a.created_at ?? 0));
|
return Array.from(events).sort((a, b) => (b.created_at ?? 0) - (a.created_at ?? 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fetchReactionCount(eventId: string): Promise<number> {
|
||||||
|
const instance = getNDK();
|
||||||
|
const filter: NDKFilter = {
|
||||||
|
kinds: [NDKKind.Reaction],
|
||||||
|
"#e": [eventId],
|
||||||
|
};
|
||||||
|
const events = await instance.fetchEvents(filter, {
|
||||||
|
cacheUsage: NDKSubscriptionCacheUsage.ONLY_RELAY,
|
||||||
|
});
|
||||||
|
return events.size;
|
||||||
|
}
|
||||||
|
|
||||||
export async function publishContactList(pubkeys: string[]): Promise<void> {
|
export async function publishContactList(pubkeys: string[]): Promise<void> {
|
||||||
const instance = getNDK();
|
const instance = getNDK();
|
||||||
if (!instance.signer) throw new Error("Not logged in");
|
if (!instance.signer) throw new Error("Not logged in");
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export { getNDK, connectToRelays, fetchGlobalFeed, fetchFollowFeed, fetchReplies, publishNote, publishArticle, publishProfile, publishReaction, publishReply, publishContactList, fetchUserNotes, fetchProfile } from "./client";
|
export { getNDK, connectToRelays, fetchGlobalFeed, fetchFollowFeed, fetchReplies, publishNote, publishArticle, publishProfile, publishReaction, publishReply, publishContactList, fetchReactionCount, fetchUserNotes, fetchProfile } from "./client";
|
||||||
|
|||||||
Reference in New Issue
Block a user