diff --git a/src/components/InboxViewer.tsx b/src/components/InboxViewer.tsx index 0be0fef..5e7a46c 100644 --- a/src/components/InboxViewer.tsx +++ b/src/components/InboxViewer.tsx @@ -20,15 +20,30 @@ import { import { useProfile } from "@/hooks/useProfile"; import eventStore from "@/services/event-store"; import { getDisplayName } from "@/lib/nostr-utils"; -import { Settings, MessageSquare, ChevronDown, Radio } from "lucide-react"; +import { + Settings, + MessageSquare, + Radio, + ShieldCheck, + ShieldAlert, + Loader2, +} from "lucide-react"; import { toast } from "sonner"; import giftWrapManager from "@/services/gift-wrap"; import { RelayLink } from "@/components/nostr/RelayLink"; import { - Collapsible, - CollapsibleContent, - CollapsibleTrigger, -} from "@/components/ui/collapsible"; + DropdownMenu, + DropdownMenuContent, + DropdownMenuTrigger, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuLabel, +} from "@/components/ui/dropdown-menu"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/components/ui/tooltip"; type InboxViewerProps = Record; @@ -39,13 +54,11 @@ export function InboxViewer(_props: InboxViewerProps) { const { pubkey } = useAccount(); const stats = useGiftWrapStats(); const conversations = useGiftWrapConversations(); - const [showSettings, setShowSettings] = useState(false); const [conversationsPage, setConversationsPage] = useState(1); const [isLoadingOlder, setIsLoadingOlder] = useState(false); - const [relaysOpen, setRelaysOpen] = useState(false); const syncEnabled = state.giftWrapSettings?.syncEnabled ?? false; - const autoDecrypt = state.giftWrapSettings?.autoDecrypt ?? true; + const autoDecrypt = state.giftWrapSettings?.autoDecrypt ?? false; // Get DM relays (kind 10050) const dmRelayEvent = use$(() => { @@ -147,6 +160,25 @@ export function InboxViewer(_props: InboxViewerProps) { } }; + const [isBatchDecrypting, setIsBatchDecrypting] = useState(false); + + const handleBatchDecrypt = async () => { + setIsBatchDecrypting(true); + try { + const count = await giftWrapManager.batchDecryptPending(); + if (count > 0) { + toast.success(`Decrypted ${count} gift wraps`); + } else { + toast.info("No pending gift wraps to decrypt"); + } + } catch (error) { + console.error("[Inbox] Error batch decrypting:", error); + toast.error("Failed to batch decrypt"); + } finally { + setIsBatchDecrypting(false); + } + }; + if (!pubkey) { return (
@@ -161,125 +193,166 @@ export function InboxViewer(_props: InboxViewerProps) { return (
- {/* Header - Single Row with Heading, Stats, Relays, Settings */} -
-
- {/* Left: Heading */} -

Inbox

+ {/* Compact Header - Like req viewer */} +
+ {/* Left: Status */} +
+ + +
+ {syncEnabled ? ( + + ) : ( + + )} + + {syncEnabled ? "SYNC" : "OFF"} + +
+
+ +

+ {syncEnabled + ? "Gift wrap sync enabled" + : "Gift wrap sync disabled"} +

+
+
+
- {/* Center: Gift Wrap Stats */} -
-
-
{stats.totalGiftWraps}
-
Total
-
-
-
+ {/* Right: Stats + Controls */} +
+ {/* Stats - Compact numbers only */} + + + + {stats.totalGiftWraps} + + + +

Total gift wraps

+
+
+ + + + {stats.successfulDecryptions} -
-
Success
-
-
-
- {stats.failedDecryptions} -
-
Failed
-
-
-
+ + + +

Successfully decrypted

+
+ + + + + {stats.failedDecryptions} + + +

Failed decryptions

+
+
+ + + + {stats.pendingDecryptions} + + + +

Pending decryptions

+
+
+ + {/* Relay Dropdown (no chevron) */} + + + + + + DM Relays + +
+ {dmRelays.length > 0 ? ( + dmRelays.map((relay) => ( + + )) + ) : ( +

+ No DM relays configured. Using general relays from kind + 10002 or kind 3. +

+ )}
-
Pending
-
-
+ + - {/* Right: Relay dropdown + Settings */} -
- {/* Relay Icon + Count Dropdown */} - - - + + + Settings + +
+ + +
+ + + {isLoadingOlder ? "Loading..." : "Load Older"} + + {!autoDecrypt && stats.pendingDecryptions > 0 && ( + + {isBatchDecrypting ? ( + + + Decrypting... + + ) : ( + `Decrypt ${stats.pendingDecryptions} Pending` + )} + )} -
- - {/* Settings Icon */} - -
+ +
- {/* Settings Panel (Collapsible) */} - {showSettings && ( -
-
- - - -
-
- )} - {/* Conversations List */}
{conversationsList.length === 0 ? ( @@ -402,24 +475,21 @@ function ConversationRow({ return (
- {/* Avatar placeholder */} -
- {/* Name */} - + {displayName} {/* Message preview */} - + {preview} {truncated && "..."} {/* Timestamp */} - {timeStr} + {timeStr}
); } diff --git a/src/core/logic.ts b/src/core/logic.ts index d302f01..cdc5a4e 100644 --- a/src/core/logic.ts +++ b/src/core/logic.ts @@ -616,7 +616,7 @@ export const updateGiftWrapSettings = ( syncEnabled: settings.syncEnabled ?? state.giftWrapSettings?.syncEnabled ?? false, autoDecrypt: - settings.autoDecrypt ?? state.giftWrapSettings?.autoDecrypt ?? true, + settings.autoDecrypt ?? state.giftWrapSettings?.autoDecrypt ?? false, }, }; }; diff --git a/src/services/gift-wrap.ts b/src/services/gift-wrap.ts index ef048c7..12047f8 100644 --- a/src/services/gift-wrap.ts +++ b/src/services/gift-wrap.ts @@ -351,6 +351,75 @@ class GiftWrapManager { return count; } + /** + * Batch decrypt all pending gift wraps + * Returns the number of successfully decrypted gift wraps + */ + async batchDecryptPending(): Promise { + const account = accountManager.active$.value; + if (!account) { + console.log("[GiftWrap] No active account"); + return 0; + } + + const { pubkey } = account; + + // Get all pending decryptions + const pending = await db.giftWrapDecryptions + .where("decryptionState") + .equals("pending") + .and((d) => d.recipientPubkey === pubkey) + .toArray(); + + if (pending.length === 0) { + console.log("[GiftWrap] No pending decryptions"); + return 0; + } + + console.log( + `[GiftWrap] Batch decrypting ${pending.length} pending gift wraps`, + ); + + let successCount = 0; + + // Process each pending gift wrap + for (const decryption of pending) { + try { + // Get the gift wrap event from event store + const giftWrap = eventStore.getEvent(decryption.giftWrapId); + if (!giftWrap) { + console.warn( + `[GiftWrap] Gift wrap ${decryption.giftWrapId} not found in event store`, + ); + continue; + } + + // Attempt to decrypt + await this.processGiftWrap(giftWrap, pubkey); + + // Check if it succeeded + const updated = await db.giftWrapDecryptions.get(decryption.giftWrapId); + if (updated?.decryptionState === "success") { + successCount++; + } + } catch (error) { + console.error( + `[GiftWrap] Error batch decrypting ${decryption.giftWrapId.slice(0, 8)}:`, + error, + ); + } + } + + console.log( + `[GiftWrap] Batch decrypt completed: ${successCount}/${pending.length} succeeded`, + ); + + // Update stats + await this.updateStats(); + + return successCount; + } + /** * Clean up old gift wraps to prevent storage bloat * Removes decryption records older than MAX_STORAGE_DAYS