import { useEffect, useRef, useState } from "react"; import { NDKEvent } from "@nostr-dev-kit/ndk"; import { useUIStore } from "../../stores/ui"; import { useUserStore } from "../../stores/user"; import { useProfile } from "../../hooks/useProfile"; import { useReactionCount } from "../../hooks/useReactionCount"; import { useZapCount } from "../../hooks/useZapCount"; import { fetchReplies, publishReaction, publishReply, publishRepost, getNDK } from "../../lib/nostr"; import { QuoteModal } from "../feed/QuoteModal"; import { shortenPubkey, timeAgo } from "../../lib/utils"; import { NoteContent } from "../feed/NoteContent"; import { NoteCard } from "../feed/NoteCard"; import { ZapModal } from "../zap/ZapModal"; function RootNote({ event }: { event: NDKEvent }) { const { openProfile } = useUIStore(); const { loggedIn } = useUserStore(); 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 [reactionCount, adjustReactionCount] = useReactionCount(event.id); const zapData = useZapCount(event.id); const [liked, setLiked] = useState(() => { try { return new Set(JSON.parse(localStorage.getItem("wrystr_liked") || "[]")).has(event.id); } catch { return false; } }); const [liking, setLiking] = useState(false); const [showZap, setShowZap] = useState(false); const [reposting, setReposting] = useState(false); const [reposted, setReposted] = useState(false); const [showQuote, setShowQuote] = useState(false); const hasLightning = !!(profile?.lud16 || profile?.lud06); const handleLike = async () => { if (!loggedIn || liked || liking) return; setLiking(true); try { await publishReaction(event.id, event.pubkey); const likedSet = new Set(JSON.parse(localStorage.getItem("wrystr_liked") || "[]")); likedSet.add(event.id); localStorage.setItem("wrystr_liked", JSON.stringify(Array.from(likedSet))); setLiked(true); adjustReactionCount(1); } finally { setLiking(false); } }; const handleRepost = async () => { if (reposting || reposted) return; setReposting(true); try { await publishRepost(event); setReposted(true); } finally { setReposting(false); } }; return (
openProfile(event.pubkey)}> {avatar ? ( ) : (
{name.charAt(0).toUpperCase()}
)}
openProfile(event.pubkey)} >{name} {nip05 &&
{nip05}
}
{time}
{/* Action row */} {loggedIn && !!getNDK().signer && (
{hasLightning && ( )}
)} {showZap && ( setShowZap(false)} /> )} {showQuote && ( setShowQuote(false)} /> )}
); } export function ThreadView() { const { selectedNote, goBack } = useUIStore(); const { loggedIn } = useUserStore(); if (!selectedNote) { goBack(); return null; } const event = selectedNote; const [replies, setReplies] = useState([]); const [loading, setLoading] = useState(true); const [replyText, setReplyText] = useState(""); const [replying, setReplying] = useState(false); const [replySent, setReplySent] = useState(false); const replyRef = useRef(null); useEffect(() => { fetchReplies(event.id).then((r) => { setReplies(r); setLoading(false); }).catch(() => setLoading(false)); }, [event.id]); const handleReply = async () => { if (!replyText.trim() || replying) return; setReplying(true); try { const replyEvent = await publishReply(replyText.trim(), { id: event.id, pubkey: event.pubkey }); setReplyText(""); setReplySent(true); // Inject reply locally so it appears immediately setReplies((prev) => [...prev, replyEvent]); // Also try fetching from relay in background fetchReplies(event.id).then((updated) => setReplies(updated)); setTimeout(() => setReplySent(false), 2000); } finally { setReplying(false); } }; const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) handleReply(); if (e.key === "Escape") replyRef.current?.blur(); }; return (
{/* Header */}

Thread

{/* Root note */} {/* Reply composer */} {loggedIn && (