diff --git a/src/components/ChatViewer.tsx b/src/components/ChatViewer.tsx index 8216063..8cf190a 100644 --- a/src/components/ChatViewer.tsx +++ b/src/components/ChatViewer.tsx @@ -2,16 +2,19 @@ import { useMemo, useState, memo, useCallback, useRef } from "react"; import { use$ } from "applesauce-react/hooks"; import { from } from "rxjs"; import { Virtuoso, VirtuosoHandle } from "react-virtuoso"; -import { Reply } from "lucide-react"; +import { Reply, Zap } from "lucide-react"; +import { getZapRequest } from "applesauce-common/helpers/zap"; import accountManager from "@/services/accounts"; import eventStore from "@/services/event-store"; import type { ChatProtocol, ProtocolIdentifier, Conversation, + LiveActivityMetadata, } from "@/types/chat"; // import { NipC7Adapter } from "@/lib/chat/adapters/nip-c7-adapter"; // Coming soon import { Nip29Adapter } from "@/lib/chat/adapters/nip-29-adapter"; +import { Nip53Adapter } from "@/lib/chat/adapters/nip-53-adapter"; import type { ChatProtocolAdapter } from "@/lib/chat/adapters/base-adapter"; import type { Message } from "@/types/chat"; import { UserName } from "./nostr/UserName"; @@ -20,6 +23,7 @@ import Timestamp from "./Timestamp"; import { ReplyPreview } from "./chat/ReplyPreview"; import { MembersDropdown } from "./chat/MembersDropdown"; import { RelaysDropdown } from "./chat/RelaysDropdown"; +import { StatusBadge } from "./live/StatusBadge"; import { useGrimoire } from "@/core/state"; import { Button } from "./ui/button"; import { @@ -168,6 +172,55 @@ const MessageItem = memo(function MessageItem({ ); } + // Zap messages have special styling with gradient border + if (message.type === "zap") { + const zapRequest = message.event ? getZapRequest(message.event) : null; + + return ( +
+
+
+
+ + + + {(message.metadata?.zapAmount || 0).toLocaleString("en", { + notation: "compact", + })} + + {message.metadata?.zapRecipient && ( + + )} + + + +
+ {message.content && ( + + )} +
+
+
+ ); + } + // Regular user messages return (
@@ -331,9 +384,47 @@ export function ChatViewer({ const handleNipClick = useCallback(() => { if (conversation?.protocol === "nip-29") { addWindow("nip", { number: 29 }); + } else if (conversation?.protocol === "nip-53") { + addWindow("nip", { number: 53 }); } }, [conversation?.protocol, addWindow]); + // Get live activity metadata if this is a NIP-53 chat + const liveActivity = conversation?.metadata?.liveActivity as + | LiveActivityMetadata + | undefined; + + // Derive participants from messages for live activities (unique pubkeys who have chatted) + const derivedParticipants = useMemo(() => { + if (conversation?.type !== "live-chat" || !messages) { + return conversation?.participants || []; + } + + const hostPubkey = liveActivity?.hostPubkey; + const participants: { pubkey: string; role: "host" | "member" }[] = []; + + // Host always first + if (hostPubkey) { + participants.push({ pubkey: hostPubkey, role: "host" }); + } + + // Add other participants from messages (excluding host) + const seen = new Set(hostPubkey ? [hostPubkey] : []); + for (const msg of messages) { + if (msg.type !== "system" && !seen.has(msg.author)) { + seen.add(msg.author); + participants.push({ pubkey: msg.author, role: "member" }); + } + } + + return participants; + }, [ + conversation?.type, + conversation?.participants, + messages, + liveActivity?.hostPubkey, + ]); + if (!conversation) { return (
@@ -348,11 +439,26 @@ export function ChatViewer({
-
-

+
+

{customTitle || conversation.title}

- {conversation.metadata?.description && ( + {/* Live activity status badge - small, icon only */} + {liveActivity?.status && ( + + )} + {/* Show host for live activities */} + {liveActivity?.hostPubkey && ( + + by{" "} + + + )} + {/* Show description for groups */} + {!liveActivity && conversation.metadata?.description && (

{conversation.metadata.description}

@@ -360,9 +466,10 @@ export function ChatViewer({

- + - {conversation.type === "group" && ( + {(conversation.type === "group" || + conversation.type === "live-chat") && (