diff --git a/src/components/message/ChatDropdownMenu.tsx b/src/components/message/ChatDropdownMenu.tsx new file mode 100644 index 0000000..177e7c6 --- /dev/null +++ b/src/components/message/ChatDropdownMenu.tsx @@ -0,0 +1,66 @@ +'use client'; + +import { useRef, useEffect } from 'react'; + +export interface DropdownMenuItem { + label: string; + onClick: () => void; + danger?: boolean; +} + +interface ChatDropdownMenuProps { + items: DropdownMenuItem[]; + isOpen: boolean; + onClose: () => void; +} + +export function ChatDropdownMenu({ items, isOpen, onClose }: ChatDropdownMenuProps) { + const menuRef = useRef(null); + + useEffect(() => { + if (!isOpen) return; + + const handleClickOutside = (e: MouseEvent) => { + if (menuRef.current && !menuRef.current.contains(e.target as Node)) { + onClose(); + } + }; + + const handleEscape = (e: KeyboardEvent) => { + if (e.key === 'Escape') onClose(); + }; + + document.addEventListener('mousedown', handleClickOutside); + document.addEventListener('keydown', handleEscape); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + document.removeEventListener('keydown', handleEscape); + }; + }, [isOpen, onClose]); + + if (!isOpen) return null; + + return ( +
+ {items.map((item, index) => ( + + ))} +
+ ); +} diff --git a/src/components/message/ChatHeader.tsx b/src/components/message/ChatHeader.tsx index 347ac5c..4fba68c 100644 --- a/src/components/message/ChatHeader.tsx +++ b/src/components/message/ChatHeader.tsx @@ -1,6 +1,8 @@ 'use client'; +import { useState, useCallback } from 'react'; import Image from 'next/image'; +import { ChatDropdownMenu, type DropdownMenuItem } from './ChatDropdownMenu'; interface ChatHeaderProps { name: string; @@ -12,6 +14,8 @@ interface ChatHeaderProps { expertise?: string[]; isTyping?: boolean; typingText?: string; + onClearChat?: () => void; + onDeleteConversation?: () => void; } export function ChatHeader({ @@ -24,7 +28,42 @@ export function ChatHeader({ expertise = [], isTyping = false, typingText, + onClearChat, + onDeleteConversation, }: ChatHeaderProps) { + const [isMenuOpen, setIsMenuOpen] = useState(false); + const [isMuted, setIsMuted] = useState(false); + + const menuItems: DropdownMenuItem[] = [ + { + label: 'Clear Chat', + onClick: () => onClearChat?.(), + }, + { + label: isMuted ? 'Unmute Notifications' : 'Mute Notifications', + onClick: () => setIsMuted((prev) => !prev), + }, + { + label: 'Report User', + onClick: () => { + // Placeholder — no backend endpoint yet + }, + }, + { + label: 'Delete Conversation', + onClick: () => onDeleteConversation?.(), + danger: true, + }, + ]; + + const handleToggleMenu = useCallback(() => { + setIsMenuOpen((prev) => !prev); + }, []); + + const handleCloseMenu = useCallback(() => { + setIsMenuOpen(false); + }, []); + return (
{/* Top row - Name, status, actions */} @@ -46,14 +85,24 @@ export function ChatHeader({
- + - +
- diff --git a/src/components/message/MessagingPage.tsx b/src/components/message/MessagingPage.tsx index 044b0e6..412cd58 100644 --- a/src/components/message/MessagingPage.tsx +++ b/src/components/message/MessagingPage.tsx @@ -5,6 +5,7 @@ import Image from 'next/image'; import { useSession } from 'next-auth/react'; import { ChatHeader } from './ChatHeader'; import { MessageInput } from './MessageInput'; +import { ChatDropdownMenu, type DropdownMenuItem } from './ChatDropdownMenu'; import { useMessaging } from '@/hooks/useMessaging'; import { connectionRequestsService, type ConnectionRequest } from '@/services/connection-requests.service'; import { uploadService } from '@/services/upload.service'; @@ -107,9 +108,15 @@ export function MessagingPage() { loadMoreMessages, hasMoreMessages, startConversation, + markAllAsRead, + deleteConversation, + clearMessages, } = useMessaging(); const [searchQuery, setSearchQuery] = useState(''); + const [isPageMenuOpen, setIsPageMenuOpen] = useState(false); + const [isAllMuted, setIsAllMuted] = useState(false); + const [confirmDeleteId, setConfirmDeleteId] = useState(null); const [isUploading, setIsUploading] = useState(false); const [showScrollButton, setShowScrollButton] = useState(false); const [connectedAgents, setConnectedAgents] = useState([]); @@ -339,6 +346,36 @@ export function MessagingPage() { stopTyping(); }, [stopTyping]); + // Handle delete conversation with confirmation + const handleDeleteConversation = useCallback(async () => { + if (!currentConversation) return; + setConfirmDeleteId(currentConversation.id); + }, [currentConversation]); + + const confirmDelete = useCallback(async () => { + if (!confirmDeleteId) return; + try { + await deleteConversation(confirmDeleteId); + } catch (error) { + console.error('Failed to delete conversation:', error); + } + setConfirmDeleteId(null); + }, [confirmDeleteId, deleteConversation]); + + // Page header menu items + const pageMenuItems: DropdownMenuItem[] = [ + { + label: 'Mark All as Read', + onClick: () => { + markAllAsRead().catch(console.error); + }, + }, + { + label: isAllMuted ? 'Unmute All Notifications' : 'Mute All Notifications', + onClick: () => setIsAllMuted((prev) => !prev), + }, + ]; + // Handle scroll events const handleScroll = () => { if (messagesContainerRef.current) { @@ -420,6 +457,35 @@ export function MessagingPage() { return ( <> + + {/* Delete confirmation dialog */} + {confirmDeleteId && ( +
+
+

+ Delete Conversation +

+

+ Are you sure you want to delete this conversation? This action cannot be undone and all messages will be permanently removed. +

+
+ + +
+
+
+ )} +
{/* Header */}
@@ -456,14 +522,24 @@ export function MessagingPage() { {/* Header actions */}
- + setIsPageMenuOpen(false)} /> - +