diff --git a/src/components/ProfileViewer.tsx b/src/components/ProfileViewer.tsx index e8e564f..4d5fb72 100644 --- a/src/components/ProfileViewer.tsx +++ b/src/components/ProfileViewer.tsx @@ -1,27 +1,106 @@ -import { useState } from "react"; import { useProfile } from "@/hooks/useProfile"; import { UserName } from "./nostr/UserName"; import Nip05 from "./nostr/nip05"; import { Copy, - Check, - ChevronDown, - ChevronRight, + CopyCheck, User as UserIcon, - Circle, Inbox, Send, + Wifi, + Loader2, + WifiOff, + XCircle, + ShieldCheck, + ShieldAlert, + ShieldX, + ShieldQuestion, + Shield, } from "lucide-react"; import { kinds, nip19 } from "nostr-tools"; import { useEventStore, useObservableMemo } from "applesauce-react/hooks"; import { getInboxes, getOutboxes } from "applesauce-core/helpers/mailboxes"; import { useCopy } from "../hooks/useCopy"; import { RichText } from "./nostr/RichText"; +import { RelayLink } from "./nostr/RelayLink"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "./ui/dropdown-menu"; +import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip"; +import { useRelayState } from "@/hooks/useRelayState"; +import type { RelayState } from "@/types/relay-state"; export interface ProfileViewerProps { pubkey: string; } +// Helper functions for relay status icons (from EventDetailViewer) +function getConnectionIcon(relay: RelayState | undefined) { + if (!relay) { + return { + icon: , + label: "Unknown", + }; + } + + const iconMap = { + connected: { + icon: , + label: "Connected", + }, + connecting: { + icon: , + label: "Connecting", + }, + disconnected: { + icon: , + label: "Disconnected", + }, + error: { + icon: , + label: "Connection Error", + }, + }; + return iconMap[relay.connectionState]; +} + +function getAuthIcon(relay: RelayState | undefined) { + if (!relay || relay.authStatus === "none") { + return null; + } + + const iconMap = { + authenticated: { + icon: , + label: "Authenticated", + }, + challenge_received: { + icon: , + label: "Challenge Received", + }, + authenticating: { + icon: , + label: "Authenticating", + }, + failed: { + icon: , + label: "Authentication Failed", + }, + rejected: { + icon: , + label: "Authentication Rejected", + }, + none: { + icon: , + label: "No Authentication", + }, + }; + return iconMap[relay.authStatus] || iconMap.none; +} + /** * ProfileViewer - Detailed view for a user profile * Shows profile metadata, inbox/outbox relays, and raw JSON @@ -29,8 +108,8 @@ export interface ProfileViewerProps { export function ProfileViewer({ pubkey }: ProfileViewerProps) { const profile = useProfile(pubkey); const eventStore = useEventStore(); - const [showInboxes, setShowInboxes] = useState(false); - const [showOutboxes, setShowOutboxes] = useState(false); + const { copy, copied } = useCopy(); + const { relays: relayStates } = useRelayState(); // Get mailbox relays (kind 10002) const mailboxEvent = useObservableMemo( @@ -48,11 +127,14 @@ export function ProfileViewer({ pubkey }: ProfileViewerProps) { [eventStore, pubkey], ); - const { copy, copied } = useCopy(); - // Combine all relays (inbox + outbox) for nprofile const allRelays = [...new Set([...inboxRelays, ...outboxRelays])]; + // Calculate connection count for relay dropdown + const connectedCount = allRelays.filter( + (url) => relayStates[url]?.connectionState === "connected", + ).length; + // Generate npub or nprofile depending on relay availability const identifier = allRelays.length > 0 @@ -71,9 +153,10 @@ export function ProfileViewer({ pubkey }: ProfileViewerProps) { onClick={() => copy(identifier)} className="flex items-center gap-1 text-muted-foreground hover:text-foreground transition-colors truncate min-w-0" title={identifier} + aria-label="Copy profile ID" > {copied ? ( - + ) : ( )} @@ -82,7 +165,7 @@ export function ProfileViewer({ pubkey }: ProfileViewerProps) { - {/* Right: Profile icon and Relay counts */} + {/* Right: Profile icon and Relay dropdown */}
@@ -90,81 +173,93 @@ export function ProfileViewer({ pubkey }: ProfileViewerProps) {
{allRelays.length > 0 && ( - <> - {inboxRelays.length > 0 && ( + + - )} + + + {allRelays.map((url) => { + const state = relayStates[url]; + const connIcon = getConnectionIcon(state); + const authIcon = getAuthIcon(state); + const isInbox = inboxRelays.includes(url); + const isOutbox = outboxRelays.includes(url); - {outboxRelays.length > 0 && ( - - )} - + return ( + +
+ {isInbox && ( + + + + + +

Inbox

+
+
+ )} + {isOutbox && ( + + + + + +

Outbox

+
+
+ )} + +
+
e.stopPropagation()} + > + {authIcon && ( + + +
{authIcon.icon}
+
+ +

{authIcon.label}

+
+
+ )} + + + +
{connIcon.icon}
+
+ +

{connIcon.label}

+
+
+
+
+ ); + })} +
+
)}
- {/* Expandable Inbox Relays */} - {showInboxes && inboxRelays.length > 0 && ( -
-
- Inbox Relays -
-
- {inboxRelays.map((relay) => ( -
- - - {relay} - -
- ))} -
-
- )} - - {/* Expandable Outbox Relays */} - {showOutboxes && outboxRelays.length > 0 && ( -
-
- Outbox Relays -
-
- {outboxRelays.map((relay) => ( -
- - - {relay} - -
- ))} -
-
- )} - {/* Profile Content */}
{!profile && !profileEvent && ( diff --git a/src/components/RelayViewer.tsx b/src/components/RelayViewer.tsx index 2f5507c..6e50325 100644 --- a/src/components/RelayViewer.tsx +++ b/src/components/RelayViewer.tsx @@ -1,4 +1,4 @@ -import { Copy, Check } from "lucide-react"; +import { Copy, CopyCheck } from "lucide-react"; import { useRelayInfo } from "../hooks/useRelayInfo"; import { useCopy } from "../hooks/useCopy"; import { Button } from "./ui/button"; @@ -24,13 +24,13 @@ export function RelayViewer({ url }: RelayViewerProps) {
{url}