diff --git a/src/components/Inbox/ConversationItem.tsx b/src/components/Inbox/ConversationItem.tsx index 4aed7553..02a23e00 100644 --- a/src/components/Inbox/ConversationItem.tsx +++ b/src/components/Inbox/ConversationItem.tsx @@ -3,7 +3,7 @@ import { formatTimestamp } from '@/lib/timestamp' import { cn } from '@/lib/utils' import client from '@/services/client.service' import { TConversation, TProfile } from '@/types' -import { Lock, Users } from 'lucide-react' +import { Lock, Users, X } from 'lucide-react' import { useEffect, useState } from 'react' interface ConversationItemProps { @@ -11,13 +11,15 @@ interface ConversationItemProps { isActive: boolean isFollowing: boolean onClick: () => void + onClose?: () => void } export default function ConversationItem({ conversation, isActive, isFollowing, - onClick + onClick, + onClose }: ConversationItemProps) { const [profile, setProfile] = useState(null) @@ -58,7 +60,21 @@ export default function ConversationItem({ )} - {formattedTime} +
+ {formattedTime} + {isActive && onClose && ( + + )} +
diff --git a/src/components/Inbox/ConversationList.tsx b/src/components/Inbox/ConversationList.tsx index 5d6b75a4..01781991 100644 --- a/src/components/Inbox/ConversationList.tsx +++ b/src/components/Inbox/ConversationList.tsx @@ -1,3 +1,5 @@ +import { toDMConversation } from '@/lib/link' +import { useSecondaryPage } from '@/PageManager' import { useDM } from '@/providers/DMProvider' import { useFollowList } from '@/providers/FollowListProvider' import { useMuteList } from '@/providers/MuteListProvider' @@ -17,6 +19,7 @@ import ConversationItem from './ConversationItem' export default function ConversationList() { const { t } = useTranslation() + const { push, pop } = useSecondaryPage() const { conversations, currentConversation, @@ -128,7 +131,17 @@ export default function ConversationList() { conversation={conversation} isActive={currentConversation === conversation.partnerPubkey} isFollowing={followingSet.has(conversation.partnerPubkey)} - onClick={() => selectConversation(conversation.partnerPubkey)} + onClick={() => { + // If already viewing a different conversation, pop first to replace + if (currentConversation && currentConversation !== conversation.partnerPubkey) { + pop() + } + push(toDMConversation(conversation.partnerPubkey)) + }} + onClose={() => { + selectConversation(null) + pop() + }} /> ))} {/* Sentinel element for infinite scroll */} diff --git a/src/components/Inbox/InboxContent.tsx b/src/components/Inbox/InboxContent.tsx index 4718f829..68199a8c 100644 --- a/src/components/Inbox/InboxContent.tsx +++ b/src/components/Inbox/InboxContent.tsx @@ -1,27 +1,14 @@ import { useDM } from '@/providers/DMProvider' import { Loader2, RefreshCw } from 'lucide-react' -import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import ConversationList from './ConversationList' -import MessageView from './MessageView' import { Button } from '../ui/button' export default function InboxContent() { const { t } = useTranslation() - const { isLoading, error, refreshConversations, currentConversation, selectConversation } = - useDM() - const [isMobileView, setIsMobileView] = useState(false) + const { isLoading, error, refreshConversations } = useDM() - useEffect(() => { - const checkMobile = () => { - setIsMobileView(window.innerWidth < 768) - } - checkMobile() - window.addEventListener('resize', checkMobile) - return () => window.removeEventListener('resize', checkMobile) - }, []) - - if (isLoading && !currentConversation) { + if (isLoading) { return (
@@ -44,37 +31,10 @@ export default function InboxContent() { ) } - // Mobile view: show either list or conversation - if (isMobileView) { - if (currentConversation) { - return ( -
- selectConversation(null)} /> -
- ) - } - return ( -
- -
- ) - } - - // Desktop view: split pane + // Conversations list - clicking opens in secondary panel (or overlay on mobile) return ( -
-
- -
-
- {currentConversation ? ( - - ) : ( -
-

{t('Select a conversation to view messages')}

-
- )} -
+
+
) } diff --git a/src/components/Inbox/MessageComposer.tsx b/src/components/Inbox/MessageComposer.tsx index 9f06d74e..ba545ce0 100644 --- a/src/components/Inbox/MessageComposer.tsx +++ b/src/components/Inbox/MessageComposer.tsx @@ -1,6 +1,8 @@ +import { cn } from '@/lib/utils' import { useDM } from '@/providers/DMProvider' -import { AlertCircle, Loader2, Send } from 'lucide-react' -import { useRef, useState } from 'react' +import { useNostr } from '@/providers/NostrProvider' +import { AlertCircle, ChevronDown, ChevronUp, Loader2, Send } from 'lucide-react' +import { useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { Button } from '../ui/button' import { Textarea } from '../ui/textarea' @@ -8,18 +10,47 @@ import { Textarea } from '../ui/textarea' export default function MessageComposer() { const { t } = useTranslation() const { sendMessage, currentConversation } = useDM() + const { relayList } = useNostr() const [message, setMessage] = useState('') const [isSending, setIsSending] = useState(false) const [error, setError] = useState(null) + const [showRelays, setShowRelays] = useState(false) + const [selectedRelays, setSelectedRelays] = useState>(new Set()) const textareaRef = useRef(null) + // Get user's write relays + const writeRelays = useMemo(() => relayList?.write || [], [relayList]) + + // Initialize selected relays when write relays change + useEffect(() => { + if (writeRelays.length > 0 && selectedRelays.size === 0) { + setSelectedRelays(new Set(writeRelays)) + } + }, [writeRelays]) + + const toggleRelay = (url: string) => { + setSelectedRelays((prev) => { + const next = new Set(prev) + if (next.has(url)) { + // Don't allow deselecting all relays + if (next.size > 1) { + next.delete(url) + } + } else { + next.add(url) + } + return next + }) + } + const handleSend = async () => { if (!message.trim() || !currentConversation || isSending) return setIsSending(true) setError(null) try { - await sendMessage(message.trim()) + const relaysToUse = Array.from(selectedRelays) + await sendMessage(message.trim(), relaysToUse.length > 0 ? relaysToUse : undefined) setMessage('') // Return focus to input after sending textareaRef.current?.focus() @@ -38,6 +69,11 @@ export default function MessageComposer() { } } + // Format relay URL for display + const formatRelayUrl = (url: string) => { + return url.replace(/^wss?:\/\//, '').replace(/\/$/, '') + } + return (
{error && ( @@ -46,6 +82,41 @@ export default function MessageComposer() { {error}
)} + + {/* Relay selector */} + {writeRelays.length > 0 && ( +
+ + {showRelays && ( +
+ {writeRelays.map((url) => ( + + ))} +
+ )} +
+ )} +