From b4d18d6bba8669ec16ea80db0a219754f5b9064d Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Jan 2026 20:55:14 +0000 Subject: [PATCH] Allow viewing chat without account Previously, chat required an active account to even view messages, blocking users from browsing public group chats or live activity chats before signing in. Changes: - Remove account requirement from NIP-29 adapter resolveConversation() - Remove account requirement from NIP-53 adapter resolveConversation() - Replace "Sign in to send messages" text with interactive login button - Add LoginDialog to ChatViewer for one-click sign-in Users can now view all chat messages without logging in, and only need to sign in when they want to send messages, join groups, or perform other write operations. --- src/components/ChatViewer.tsx | 455 ++++++++++++------------ src/lib/chat/adapters/nip-29-adapter.ts | 5 - src/lib/chat/adapters/nip-53-adapter.ts | 5 - 3 files changed, 236 insertions(+), 229 deletions(-) diff --git a/src/components/ChatViewer.tsx b/src/components/ChatViewer.tsx index 60eb711..5bf69a0 100644 --- a/src/components/ChatViewer.tsx +++ b/src/components/ChatViewer.tsx @@ -30,6 +30,7 @@ import { parseSlashCommand } from "@/lib/chat/slash-command-parser"; import { UserName } from "./nostr/UserName"; import { RichText } from "./nostr/RichText"; import Timestamp from "./Timestamp"; +import LoginDialog from "./nostr/LoginDialog"; import { ReplyPreview } from "./chat/ReplyPreview"; import { MembersDropdown } from "./chat/MembersDropdown"; import { RelaysDropdown } from "./chat/RelaysDropdown"; @@ -339,6 +340,9 @@ export function ChatViewer({ const activeAccount = use$(accountManager.active$); const hasActiveAccount = !!activeAccount; + // Login dialog state + const [showLogin, setShowLogin] = useState(false); + // Profile search for mentions const { searchProfiles } = useProfileSearch(); @@ -703,238 +707,251 @@ export function ChatViewer({ } return ( -
- {/* Header with conversation info and controls */} -
-
-
- {headerPrefix} - - - - - - -
- {/* Icon + Name */} -
- {conversation.metadata?.icon && ( - { - // Hide image if it fails to load - e.currentTarget.style.display = "none"; - }} - /> - )} - - {conversation.title} - -
- {/* Description */} - {conversation.metadata?.description && ( -

- {conversation.metadata.description} -

- )} - {/* Protocol Type - Clickable */} -
- {(conversation.type === "group" || - conversation.type === "live-chat") && ( - - )} - {(conversation.type === "group" || - conversation.type === "live-chat") && ( - - )} - - {conversation.type} - -
- {/* Live Activity Status */} - {liveActivity?.status && ( -
- - Status: + <> + +
+ {/* Header with conversation info and controls */} +
+
+
+ {headerPrefix} + + + + + + +
+ {/* Icon + Name */} +
+ {conversation.metadata?.icon && ( + { + // Hide image if it fails to load + e.currentTarget.style.display = "none"; + }} + /> + )} + + {conversation.title} -
- )} - {/* Host Info */} - {liveActivity?.hostPubkey && ( -
- Host: - + {/* Description */} + {conversation.metadata?.description && ( +

+ {conversation.metadata.description} +

+ )} + {/* Protocol Type - Clickable */} +
+ {(conversation.type === "group" || + conversation.type === "live-chat") && ( + + )} + {(conversation.type === "group" || + conversation.type === "live-chat") && ( + + )} + + {conversation.type} +
- )} -
- - - -
-
- - - {(conversation.type === "group" || - conversation.type === "live-chat") && ( - - )} + {/* Live Activity Status */} + {liveActivity?.status && ( +
+ + Status: + + +
+ )} + {/* Host Info */} + {liveActivity?.hostPubkey && ( +
+ Host: + +
+ )} +
+
+
+
+
+
+ + + {(conversation.type === "group" || + conversation.type === "live-chat") && ( + + )} +
-
- {/* Message timeline with virtualization */} -
- {messagesWithMarkers && messagesWithMarkers.length > 0 ? ( - - hasMore && conversationResult.status === "success" ? ( -
- +
+ ) : null, + }} + itemContent={(_index, item) => { + if (item.type === "day-marker") { + return ( +
- {isLoadingOlder ? ( - <> - - Loading... - - ) : ( - "Load older messages" - )} - -
- ) : null, - }} - itemContent={(_index, item) => { - if (item.type === "day-marker") { + +
+ ); + } return ( -
- -
+ ); - } - return ( - - ); - }} - style={{ height: "100%" }} - /> + }} + style={{ height: "100%" }} + /> + ) : ( +
+ No messages yet. Start the conversation! +
+ )} +
+ + {/* Message composer - only show if user has active account */} + {hasActiveAccount ? ( +
+ {replyTo && ( + setReplyTo(undefined)} + /> + )} +
+ + + + + + +

Attach media

+
+
+
+ { + if (content.trim()) { + handleSend(content, replyTo, emojiTags, blobAttachments); + } + }} + className="flex-1 min-w-0" + /> + +
+ {uploadDialog} +
) : ( -
- No messages yet. Start the conversation! +
+
)}
- - {/* Message composer - only show if user has active account */} - {hasActiveAccount ? ( -
- {replyTo && ( - setReplyTo(undefined)} - /> - )} -
- - - - - - -

Attach media

-
-
-
- { - if (content.trim()) { - handleSend(content, replyTo, emojiTags, blobAttachments); - } - }} - className="flex-1 min-w-0" - /> - -
- {uploadDialog} -
- ) : ( -
- Sign in to send messages -
- )} -
+ ); } diff --git a/src/lib/chat/adapters/nip-29-adapter.ts b/src/lib/chat/adapters/nip-29-adapter.ts index a4dcc30..3e50f46 100644 --- a/src/lib/chat/adapters/nip-29-adapter.ts +++ b/src/lib/chat/adapters/nip-29-adapter.ts @@ -116,11 +116,6 @@ export class Nip29Adapter extends ChatProtocolAdapter { throw new Error("NIP-29 groups require a relay URL"); } - const activePubkey = accountManager.active$.value?.pubkey; - if (!activePubkey) { - throw new Error("No active account"); - } - console.log( `[NIP-29] Fetching group metadata for ${groupId} from ${relayUrl}`, ); diff --git a/src/lib/chat/adapters/nip-53-adapter.ts b/src/lib/chat/adapters/nip-53-adapter.ts index 54dba8f..b943acd 100644 --- a/src/lib/chat/adapters/nip-53-adapter.ts +++ b/src/lib/chat/adapters/nip-53-adapter.ts @@ -93,11 +93,6 @@ export class Nip53Adapter extends ChatProtocolAdapter { const { pubkey, identifier: dTag } = identifier.value; const relayHints = identifier.relays || []; - const activePubkey = accountManager.active$.value?.pubkey; - if (!activePubkey) { - throw new Error("No active account"); - } - console.log( `[NIP-53] Fetching live activity ${dTag} by ${pubkey.slice(0, 8)}...`, );