From f2345b4d9fd5b9f9446be045c9d0e6ad04eba28c Mon Sep 17 00:00:00 2001 From: highperfocused Date: Sun, 20 Apr 2025 22:47:54 +0200 Subject: [PATCH] feat: add ChatOverview component with conversation list and search functionality --- app/chat/page.tsx | 15 +++ components/chat/ChatOverview.tsx | 164 +++++++++++++++++++++++++++++++ 2 files changed, 179 insertions(+) create mode 100644 app/chat/page.tsx create mode 100644 components/chat/ChatOverview.tsx diff --git a/app/chat/page.tsx b/app/chat/page.tsx new file mode 100644 index 0000000..e985983 --- /dev/null +++ b/app/chat/page.tsx @@ -0,0 +1,15 @@ +import { Metadata } from 'next'; +import ChatOverview from '@/components/chat/ChatOverview'; + +export const metadata: Metadata = { + title: 'Chats | Lumina', + description: 'Your conversations on Nostr', +}; + +export default function ChatPage() { + return ( +
+ +
+ ); +} \ No newline at end of file diff --git a/components/chat/ChatOverview.tsx b/components/chat/ChatOverview.tsx new file mode 100644 index 0000000..acbe3db --- /dev/null +++ b/components/chat/ChatOverview.tsx @@ -0,0 +1,164 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { useNostrEvents, dateToUnix } from 'nostr-react'; +import { useRouter } from 'next/navigation'; +import { Search } from 'lucide-react'; +import { Input } from '@/components/ui/input'; +import { Button } from '@/components/ui/button'; +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; +import { ScrollArea } from '@/components/ui/scroll-area'; + +export default function ChatOverview() { + const router = useRouter(); + const [searchQuery, setSearchQuery] = useState(''); + const [conversations, setConversations] = useState([]); + const now = new Date(); + + // In a real implementation, this would fetch direct messages (kind 4) + // from various relays and process them to show conversations + const { events } = useNostrEvents({ + filter: { + kinds: [4], // Kind 4 is direct messages in Nostr + since: dateToUnix(new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000)), // Last 30 days + }, + }); + + // For demo purposes, let's add some dummy conversations + useEffect(() => { + // These would be actual pubkeys in a real implementation + const dummyConversations = [ + { + id: '1', + pubkey: 'npub1abcdefghijklmnopqrstuvwxyz123456789abcdefghijklmnopqrst', + name: 'Alice', + avatar: '', + lastMessage: 'Hey, how are you doing?', + timestamp: new Date(now.getTime() - 30 * 60 * 1000), // 30 minutes ago + unread: 2 + }, + { + id: '2', + pubkey: 'npub2abcdefghijklmnopqrstuvwxyz123456789abcdefghijklmnopqrst', + name: 'Bob', + avatar: '', + lastMessage: 'Did you see the new Nostr client everyone is talking about?', + timestamp: new Date(now.getTime() - 2 * 60 * 60 * 1000), // 2 hours ago + unread: 0 + }, + { + id: '3', + pubkey: 'npub3abcdefghijklmnopqrstuvwxyz123456789abcdefghijklmnopqrst', + name: 'Charlie', + avatar: '', + lastMessage: 'Thanks for sharing that article about Bitcoin!', + timestamp: new Date(now.getTime() - 1 * 24 * 60 * 60 * 1000), // 1 day ago + unread: 0 + }, + { + id: '4', + pubkey: 'npub4abcdefghijklmnopqrstuvwxyz123456789abcdefghijklmnopqrst', + name: 'Diana', + avatar: '', + lastMessage: 'Check out this cool zap feature I just discovered!', + timestamp: new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000), // 2 days ago + unread: 1 + }, + { + id: '5', + pubkey: 'npub5abcdefghijklmnopqrstuvwxyz123456789abcdefghijklmnopqrst', + name: 'Evan', + avatar: '', + lastMessage: 'When are you planning to start your Nostr relay?', + timestamp: new Date(now.getTime() - 5 * 24 * 60 * 60 * 1000), // 5 days ago + unread: 0 + }, + ]; + + setConversations(dummyConversations); + }, []); + + const filteredConversations = conversations.filter(convo => + convo.name.toLowerCase().includes(searchQuery.toLowerCase()) || + convo.lastMessage.toLowerCase().includes(searchQuery.toLowerCase()) + ); + + const navigateToChat = (pubkey: string) => { + // Convert npub to hex format if needed + let hexPubkey = pubkey; + if (pubkey.startsWith('npub')) { + try { + // This would actually convert using nostr-tools in real implementation + // For demo, we'll just use the pubkey as is + hexPubkey = pubkey; + } catch (e) { + console.error('Failed to convert pubkey', e); + } + } + + router.push(`/chat/${hexPubkey}`); + }; + + return ( +
+
+

Messages

+
+ + setSearchQuery(e.target.value)} + /> +
+
+ + +
+ {filteredConversations.length > 0 ? ( + filteredConversations.map((convo) => ( +
navigateToChat(convo.pubkey)} + > + + + {convo.name.substring(0, 2).toUpperCase()} + + +
+
+

{convo.name}

+ + {new Date(convo.timestamp).toLocaleTimeString([], { + hour: '2-digit', + minute: '2-digit', + })} + +
+

{convo.lastMessage}

+
+ + {convo.unread > 0 && ( +
+ {convo.unread} +
+ )} +
+ )) + ) : ( +

No conversations found

+ )} +
+
+ +
+ +
+
+ ); +} \ No newline at end of file