import { useState, useRef } from "react"; import { NDKEvent } from "@nostr-dev-kit/ndk"; import { useProfile } from "../../hooks/useProfile"; import { useReactionCount } from "../../hooks/useReactionCount"; import { useUserStore } from "../../stores/user"; import { useUIStore } from "../../stores/ui"; import { timeAgo, shortenPubkey } from "../../lib/utils"; import { publishReaction, publishReply, getNDK } from "../../lib/nostr"; import { NoteContent } from "./NoteContent"; import { ZapModal } from "../zap/ZapModal"; interface NoteCardProps { event: NDKEvent; } export function NoteCard({ event }: NoteCardProps) { const profile = useProfile(event.pubkey); const name = profile?.displayName || profile?.name || shortenPubkey(event.pubkey); const avatar = profile?.picture; const nip05 = profile?.nip05; const time = event.created_at ? timeAgo(event.created_at) : ""; const { loggedIn } = useUserStore(); const { openProfile, openThread, currentView } = useUIStore(); const likedKey = "wrystr_liked"; const getLiked = () => { try { return new Set(JSON.parse(localStorage.getItem(likedKey) || "[]")); } catch { return new Set(); } }; const [liked, setLiked] = useState(() => getLiked().has(event.id)); const [liking, setLiking] = useState(false); const [reactionCount, adjustReactionCount] = useReactionCount(event.id); const [showReply, setShowReply] = useState(false); const [replyText, setReplyText] = useState(""); const [replying, setReplying] = useState(false); const [replyError, setReplyError] = useState(null); const [replySent, setReplySent] = useState(false); const replyRef = useRef(null); const [showZap, setShowZap] = useState(false); const handleLike = async () => { if (!loggedIn || liked || liking) return; setLiking(true); try { await publishReaction(event.id, event.pubkey); const liked = getLiked(); liked.add(event.id); localStorage.setItem(likedKey, JSON.stringify(Array.from(liked))); setLiked(true); adjustReactionCount(1); } finally { setLiking(false); } }; const handleReply = () => { setShowReply((v) => !v); if (!showReply) setTimeout(() => replyRef.current?.focus(), 50); }; const handleReplySubmit = async () => { if (!replyText.trim() || replying) return; setReplying(true); setReplyError(null); try { await publishReply(replyText.trim(), { id: event.id, pubkey: event.pubkey }); setReplyText(""); setReplySent(true); setTimeout(() => { setShowReply(false); setReplySent(false); }, 1500); } catch (err) { setReplyError(`Failed: ${err}`); } finally { setReplying(false); } }; const handleReplyKeyDown = (e: React.KeyboardEvent) => { if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) handleReplySubmit(); if (e.key === "Escape") setShowReply(false); }; return (
{/* Avatar */}
openProfile(event.pubkey)}> {avatar ? ( { (e.target as HTMLImageElement).style.display = "none"; }} /> ) : (
{name.charAt(0).toUpperCase()}
)}
{/* Content */}
openProfile(event.pubkey)} >{name} {nip05 && ( {nip05} )} {time}
openThread(event, currentView as "feed" | "profile")} >
{/* Actions */} {loggedIn && !!getNDK().signer && (
)} {showZap && ( setShowZap(false)} /> )} {/* Inline reply box */} {showReply && (