From abc0b719155a56f9eee95c6512e9a6bac795620f Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 17 Jan 2026 20:10:41 +0000 Subject: [PATCH] feat: add useAccount hook for signing capability detection Created a centralized hook to check account signing capabilities and refactored components to distinguish between signing and read-only operations. New hook (src/hooks/useAccount.ts): - Returns account, pubkey, canSign, signer, isLoggedIn - Detects ReadonlyAccount vs signing accounts - Provides clear API for checking signing capability Refactored components: - ChatViewer: Use canSign for message composer, replying, actions - Show "Sign in to send messages" for read-only accounts - Disable message input for accounts without signing - SpellDialog: Use canSign for publishing spells - Show clear warning for read-only accounts - Updated error messages to mention read-only limitation - useEmojiSearch: Use pubkey for loading custom emoji lists - Works correctly with both signing and read-only accounts Benefits: - Clear separation between read (pubkey) and write (canSign, signer) operations - Read-only accounts can browse, view profiles, load data - Signing operations properly disabled for read-only accounts - Consistent pattern across the codebase for account checks - Better UX with specific messages about account capabilities --- src/components/ChatViewer.tsx | 31 ++++++------- src/components/nostr/SpellDialog.tsx | 22 +++++---- src/hooks/useAccount.ts | 69 ++++++++++++++++++++++++++++ src/hooks/useEmojiSearch.ts | 11 ++--- 4 files changed, 100 insertions(+), 33 deletions(-) create mode 100644 src/hooks/useAccount.ts diff --git a/src/components/ChatViewer.tsx b/src/components/ChatViewer.tsx index b164539..f76f83c 100644 --- a/src/components/ChatViewer.tsx +++ b/src/components/ChatViewer.tsx @@ -15,7 +15,6 @@ import { import { nip19 } from "nostr-tools"; import { getZapRequest } from "applesauce-common/helpers/zap"; import { toast } from "sonner"; -import accountManager from "@/services/accounts"; import eventStore from "@/services/event-store"; import type { ChatProtocol, @@ -51,6 +50,7 @@ import { import { useProfileSearch } from "@/hooks/useProfileSearch"; import { useEmojiSearch } from "@/hooks/useEmojiSearch"; import { useCopy } from "@/hooks/useCopy"; +import { useAccount } from "@/hooks/useAccount"; import { Label } from "./ui/label"; import { Tooltip, @@ -437,9 +437,8 @@ export function ChatViewer({ }: ChatViewerProps) { const { addWindow } = useGrimoire(); - // Get active account - const activeAccount = use$(accountManager.active$); - const hasActiveAccount = !!activeAccount; + // Get active account with signing capability + const { pubkey, canSign, signer } = useAccount(); // Profile search for mentions const { searchProfiles } = useProfileSearch(); @@ -513,14 +512,14 @@ export function ChatViewer({ async (query: string) => { const availableActions = adapter.getActions({ conversation: conversation || undefined, - activePubkey: activeAccount?.pubkey, + activePubkey: pubkey, }); const lowerQuery = query.toLowerCase(); return availableActions.filter((action) => action.name.toLowerCase().includes(lowerQuery), ); }, - [adapter, conversation, activeAccount], + [adapter, conversation, pubkey], ); // Cleanup subscriptions when conversation changes or component unmounts @@ -596,7 +595,7 @@ export function ChatViewer({ emojiTags?: EmojiTag[], blobAttachments?: BlobAttachment[], ) => { - if (!conversation || !hasActiveAccount || isSending) return; + if (!conversation || !canSign || isSending) return; // Check if this is a slash command const slashCmd = parseSlashCommand(content); @@ -605,8 +604,8 @@ export function ChatViewer({ setIsSending(true); try { const result = await adapter.executeAction(slashCmd.command, { - activePubkey: activeAccount.pubkey, - activeSigner: activeAccount.signer, + activePubkey: pubkey!, + activeSigner: signer!, conversation, }); @@ -649,13 +648,13 @@ export function ChatViewer({ // Handle command execution from autocomplete const handleCommandExecute = useCallback( async (action: ChatAction) => { - if (!conversation || !hasActiveAccount || isSending) return; + if (!conversation || !canSign || isSending) return; setIsSending(true); try { const result = await adapter.executeAction(action.name, { - activePubkey: activeAccount.pubkey, - activeSigner: activeAccount.signer, + activePubkey: pubkey!, + activeSigner: signer!, conversation, }); @@ -673,7 +672,7 @@ export function ChatViewer({ setIsSending(false); } }, - [conversation, hasActiveAccount, isSending, adapter, activeAccount], + [conversation, canSign, isSending, adapter, pubkey, signer], ); // Handle reply button click @@ -987,7 +986,7 @@ export function ChatViewer({ adapter={adapter} conversation={conversation} onReply={handleReply} - canReply={hasActiveAccount} + canReply={canSign} onScrollToMessage={handleScrollToMessage} /> ); @@ -1001,8 +1000,8 @@ export function ChatViewer({ )} - {/* Message composer - only show if user has active account */} - {hasActiveAccount ? ( + {/* Message composer - only show if user can sign */} + {canSign ? (
{replyTo && ( { if (!isFormValid) return; - // Check for active account - if (!activeAccount) { - setErrorMessage("No active account. Please sign in first."); + // Check for signing capability + if (!canSign) { + setErrorMessage( + "You need a signing account to publish. Read-only accounts cannot publish.", + ); setPublishingState("error"); return; } @@ -363,10 +364,11 @@ export function SpellDialog({
)} - {/* No account warning */} - {!activeAccount && ( + {/* No signing capability warning */} + {!canSign && (
- You need to sign in to publish spells. + You need a signing account to publish spells. Read-only accounts + cannot publish.
)} @@ -384,7 +386,7 @@ export function SpellDialog({