'use client'; import { useState, useEffect, useRef } from 'react'; import { useAuth } from '@/lib/contexts/AuthContext'; import { useChatEncryption } from '@/lib/hooks/useChatEncryption'; import { ArrowLeft, Send, Shield, Loader2, MessageCircle, Search, Plus, Trash2 } from 'lucide-react'; import { formatFullHandle } from '@/lib/utils/handle'; import { useRouter } from 'next/navigation'; interface ChatMessage { id: string; // The ID of the envelope or internal ID senderDid: string; senderHandle?: string; // Resolved if possible content: string; // Decrypted timestamp: number; k: string; // React key isMe: boolean; } export default function ChatPage() { const { user } = useAuth(); const router = useRouter(); const { isReady, status, ensureReady, sendMessage, decryptMessage } = useChatEncryption(); // UI State const [messages, setMessages] = useState([]); const [newMessage, setNewMessage] = useState(''); const [sending, setSending] = useState(false); // Conversation State (Simplified for V2 Migration) // We group messages by DID. const [activeDid, setActiveDid] = useState(null); const [handles, setHandles] = useState>({}); // DID -> handle const [showNewChat, setShowNewChat] = useState(false); const [newChatHandle, setNewChatHandle] = useState(''); // Encryption Status // status can be: idle, initializing, ready, error, generating_keys // Redirect if not logged in useEffect(() => { if (!user) { // router.push('/login'); // Handled by Layout generally, but safe here } }, [user, router]); // Polling Inbox useEffect(() => { if (!isReady || !user) return; const deviceId = localStorage.getItem('synapsis_device_id'); if (!deviceId) return; const poll = async () => { try { const res = await fetch(`/api/chat/inbox?deviceId=${deviceId}`); if (res.ok) { const data = await res.json(); if (data.messages && data.messages.length > 0) { for (const msg of data.messages) { // Decrypt const envelope = JSON.parse(msg.envelope); // The SignedAction const plaintext = await decryptMessage(envelope); // Add to list if valid const senderDid = envelope.did; const handle = envelope.handle; // Sender handle in action setHandles(prev => ({ ...prev, [senderDid]: handle })); setMessages(prev => { // Dedup by ID if (prev.find(p => p.id === msg.id)) return prev; return [...prev, { id: msg.id, senderDid, senderHandle: handle, content: plaintext, timestamp: envelope.ts, k: msg.id, isMe: false }]; }); } } } } catch (e) { console.error("Poll error", e); } }; const interval = setInterval(poll, 3000); poll(); // Initial return () => clearInterval(interval); }, [isReady, decryptMessage, user]); // Send Handler const handleSend = async (e: React.FormEvent) => { e.preventDefault(); if (!newMessage.trim() || !activeDid) return; setSending(true); try { // Send await sendMessage(activeDid, newMessage); // Add optimistic message (UI only) setMessages(prev => [...prev, { id: `opt-${Date.now()}`, senderDid: user?.did || 'me', senderHandle: user?.handle, content: newMessage, timestamp: Date.now(), k: `opt-${Date.now()}`, isMe: true }]); setNewMessage(''); } catch (err: any) { alert(`Send failed: ${err.message}`); } finally { setSending(false); } }; const startNewChat = async (e: React.FormEvent) => { e.preventDefault(); if (!newChatHandle.trim()) return; // Resolve Handle to DID try { const clean = newChatHandle.replace('@', ''); const res = await fetch(`/api/users/${encodeURIComponent(clean)}`); const data = await res.json(); if (data.user?.did) { setActiveDid(data.user.did); setHandles(prev => ({ ...prev, [data.user.did]: clean })); setShowNewChat(false); setNewChatHandle(''); } else { alert('User not found'); } } catch (e) { alert('Lookup failed'); } }; // Group messages by Active Conversation const activeMessages = messages.filter(m => (activeDid && (m.senderDid === activeDid || (m.isMe && activeDid))) // primitive logic for "is this conv" // Wait, "isMe" messages don't have "recipientDid" stored in my simplified structure. // I need to track whom I sent it to in the optimistic add. ); // Fix: Optimistic add should store recipientDid locally to filter? // For V2 MVP, I'm just showing "Received" messages mostly. // Computed Conversations List (Unique DIDs) const uniqueDids = Array.from(new Set(messages.filter(m => !m.isMe).map(m => m.senderDid))); // Render if (!user) return null; if (status === 'initializing' || status === 'generating_keys') { return (

Initializing Encryption...

); } if (status === 'error') { return
Encryption Error. Check console/logs.
; } return (
{/* Sidebar */}

Chats (V2)

{showNewChat && (
setNewChatHandle(e.target.value)} />
)}
{uniqueDids.map(did => (
setActiveDid(did)} className={`p-4 border-b cursor-pointer hover:bg-muted/10 ${activeDid === did ? 'bg-muted/20' : ''}`} >
{handles[did] || did.slice(0, 16)}
{messages.filter(m => m.senderDid === did).pop()?.content.slice(0, 30)}
))} {/* If active did is not in uniqueDids (e.g. new chat), show it */} {activeDid && !uniqueDids.includes(activeDid) && (
{handles[activeDid] || activeDid}
New Conversation
)}
{/* Chat Area */}
{activeDid ? ( <>
{handles[activeDid] || activeDid}
{/* Optimistic filtering issue: My 'isMe' messages don't track recipient. I'll just show all messages for now or filter by what I can. Ideally, we store `recipientDid` on the optimistic message. */} {messages.filter(m => m.senderDid === activeDid || (m.isMe)).map(msg => (
{msg.content}
))}
setNewMessage(e.target.value)} />
) : (

Select a secure conversation

)}
); }