Initial commit from remix
This commit is contained in:
931
src/pages/dashboard/Messages.tsx
Normal file
931
src/pages/dashboard/Messages.tsx
Normal file
@@ -0,0 +1,931 @@
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { useAuth } from '@/contexts/AuthContext';
|
||||
import { useChat } from '@/hooks/useChat';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/ui/accordion';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { toast } from 'sonner';
|
||||
import {
|
||||
MessageCircle,
|
||||
Users,
|
||||
UserCheck,
|
||||
Bell,
|
||||
Phone,
|
||||
Video,
|
||||
Paperclip,
|
||||
Smile,
|
||||
Send,
|
||||
Search,
|
||||
MoreVertical,
|
||||
Plus,
|
||||
X,
|
||||
Settings,
|
||||
UserPlus,
|
||||
Circle,
|
||||
Palette,
|
||||
Edit3
|
||||
} from 'lucide-react';
|
||||
|
||||
const Messages = () => {
|
||||
const { user } = useAuth();
|
||||
const [activeTab, setActiveTab] = useState('chats');
|
||||
const [selectedChatId, setSelectedChatId] = useState<string | null>(null);
|
||||
const [messageText, setMessageText] = useState('');
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [searchResults, setSearchResults] = useState<any[]>([]);
|
||||
const [showUserSearch, setShowUserSearch] = useState(false);
|
||||
const [showChatDetails, setShowChatDetails] = useState(true);
|
||||
const [selectedColor, setSelectedColor] = useState('green');
|
||||
const [isAutoBot, setIsAutoBot] = useState(true);
|
||||
const [newNickname, setNewNickname] = useState('');
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const {
|
||||
chats,
|
||||
messages,
|
||||
onlineUsers,
|
||||
loading,
|
||||
error,
|
||||
loadMessages,
|
||||
sendMessage,
|
||||
searchUsers,
|
||||
createChat,
|
||||
getChatById,
|
||||
clearError
|
||||
} = useChat();
|
||||
|
||||
const selectedChat = selectedChatId ? getChatById(selectedChatId) : chats[0];
|
||||
|
||||
// Auto-scroll to bottom of messages
|
||||
const scrollToBottom = () => {
|
||||
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
scrollToBottom();
|
||||
}, [messages]);
|
||||
|
||||
// Load messages when chat is selected
|
||||
useEffect(() => {
|
||||
if (selectedChatId) {
|
||||
loadMessages(selectedChatId);
|
||||
}
|
||||
}, [selectedChatId, loadMessages]);
|
||||
|
||||
// Auto-select first chat
|
||||
useEffect(() => {
|
||||
if (chats.length > 0 && !selectedChatId) {
|
||||
setSelectedChatId(chats[0].id);
|
||||
}
|
||||
}, [chats, selectedChatId]);
|
||||
|
||||
// Handle search
|
||||
const handleSearch = async (query: string) => {
|
||||
setSearchQuery(query);
|
||||
if (query.trim()) {
|
||||
const results = await searchUsers(query);
|
||||
setSearchResults(results);
|
||||
} else {
|
||||
setSearchResults([]);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle send message
|
||||
const handleSendMessage = async () => {
|
||||
if (!messageText.trim() || !selectedChatId) return;
|
||||
|
||||
try {
|
||||
await sendMessage(selectedChatId, messageText);
|
||||
setMessageText('');
|
||||
} catch (err) {
|
||||
toast.error('Failed to send message');
|
||||
}
|
||||
};
|
||||
|
||||
// Handle create chat with user
|
||||
const handleCreateChat = async (userId: string) => {
|
||||
try {
|
||||
const newChat = await createChat([userId]);
|
||||
setSelectedChatId(newChat.id);
|
||||
setShowUserSearch(false);
|
||||
setSearchQuery('');
|
||||
setSearchResults([]);
|
||||
toast.success('Chat created successfully');
|
||||
} catch (err) {
|
||||
toast.error('Failed to create chat');
|
||||
}
|
||||
};
|
||||
|
||||
// Get notifications count
|
||||
const getNotificationsCount = () => {
|
||||
return chats.reduce((total, chat) => total + chat.unreadCount, 0);
|
||||
};
|
||||
|
||||
// Format time
|
||||
const formatTime = (timestamp: string) => {
|
||||
const date = new Date(timestamp);
|
||||
const now = new Date();
|
||||
const diffInHours = (now.getTime() - date.getTime()) / (1000 * 60 * 60);
|
||||
|
||||
if (diffInHours < 24) {
|
||||
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||||
} else if (diffInHours < 168) {
|
||||
return date.toLocaleDateString([], { weekday: 'short' });
|
||||
} else {
|
||||
return date.toLocaleDateString();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-screen bg-background overflow-hidden">
|
||||
{/* Chat Container */}
|
||||
<div className="chat-container m-0 overflow-hidden relative rounded-lg flex w-full">
|
||||
{/* Chat List Sidebar */}
|
||||
<div className="chat-list__sidebar p-0 w-80 bg-background border-r border-border flex flex-col">
|
||||
{/* Search Section */}
|
||||
<div className="chat-list__search relative p-4 border-b border-border">
|
||||
<form className="relative">
|
||||
<Input
|
||||
type="search"
|
||||
className="pl-4 pr-16"
|
||||
placeholder="People, Groups and Messages"
|
||||
value={searchQuery}
|
||||
onChange={(e) => handleSearch(e.target.value)}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="absolute right-12 top-1/2 transform -translate-y-1/2 p-1 hover:bg-accent rounded"
|
||||
>
|
||||
<Search className="h-5 w-5 text-muted-foreground" />
|
||||
</button>
|
||||
</form>
|
||||
<button
|
||||
className="absolute right-4 top-1/2 transform -translate-y-1/2 p-2 hover:bg-accent rounded"
|
||||
onClick={() => setShowUserSearch(!showUserSearch)}
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Sidebar Tabs */}
|
||||
<ul className="chat-list__sidebar-tabs flex border-b border-border bg-muted/30" role="tablist">
|
||||
<li className="flex-1" role="presentation">
|
||||
<button
|
||||
className={`w-full p-3 text-center border-0 bg-transparent transition-colors ${
|
||||
activeTab === 'chats'
|
||||
? 'bg-background border-b-2 border-primary text-primary'
|
||||
: 'text-muted-foreground hover:text-foreground'
|
||||
}`}
|
||||
onClick={() => setActiveTab('chats')}
|
||||
role="tab"
|
||||
aria-selected={activeTab === 'chats'}
|
||||
>
|
||||
<div className="relative inline-block">
|
||||
<MessageCircle className="h-6 w-6 mx-auto mb-1" />
|
||||
{chats.length > 0 && (
|
||||
<div className="absolute -top-1 -right-1 bg-orange-500 text-white text-xs rounded-full h-5 w-5 flex items-center justify-center">
|
||||
{chats.length}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<span className="block text-xs font-semibold">Chats</span>
|
||||
</button>
|
||||
</li>
|
||||
<li className="flex-1" role="presentation">
|
||||
<button
|
||||
className={`w-full p-3 text-center border-0 bg-transparent transition-colors ${
|
||||
activeTab === 'online'
|
||||
? 'bg-background border-b-2 border-primary text-primary'
|
||||
: 'text-muted-foreground hover:text-foreground'
|
||||
}`}
|
||||
onClick={() => setActiveTab('online')}
|
||||
role="tab"
|
||||
aria-selected={activeTab === 'online'}
|
||||
>
|
||||
<Users className="h-6 w-6 mx-auto mb-1" />
|
||||
<span className="block text-xs font-semibold">Online users</span>
|
||||
</button>
|
||||
</li>
|
||||
<li className="flex-1" role="presentation">
|
||||
<button
|
||||
className={`w-full p-3 text-center border-0 bg-transparent transition-colors ${
|
||||
activeTab === 'contacts'
|
||||
? 'bg-background border-b-2 border-primary text-primary'
|
||||
: 'text-muted-foreground hover:text-foreground'
|
||||
}`}
|
||||
onClick={() => setActiveTab('contacts')}
|
||||
role="tab"
|
||||
aria-selected={activeTab === 'contacts'}
|
||||
>
|
||||
<UserCheck className="h-6 w-6 mx-auto mb-1" />
|
||||
<span className="block text-xs font-semibold">Contacts</span>
|
||||
</button>
|
||||
</li>
|
||||
<li className="flex-1" role="presentation">
|
||||
<button
|
||||
className={`w-full p-3 text-center border-0 bg-transparent transition-colors ${
|
||||
activeTab === 'notifications'
|
||||
? 'bg-background border-b-2 border-primary text-primary'
|
||||
: 'text-muted-foreground hover:text-foreground'
|
||||
}`}
|
||||
onClick={() => setActiveTab('notifications')}
|
||||
role="tab"
|
||||
aria-selected={activeTab === 'notifications'}
|
||||
>
|
||||
<div className="relative inline-block">
|
||||
<Bell className="h-6 w-6 mx-auto mb-1" />
|
||||
{getNotificationsCount() > 0 && (
|
||||
<div className="absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full h-5 w-5 flex items-center justify-center">
|
||||
{getNotificationsCount()}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<span className="block text-xs font-semibold">Notifications</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{/* Tab Content */}
|
||||
<div className="flex-1 overflow-hidden">
|
||||
{/* Search Results */}
|
||||
{showUserSearch && searchResults.length > 0 && (
|
||||
<div className="p-3 border-b border-border">
|
||||
<h3 className="text-sm font-medium mb-2">Search Results</h3>
|
||||
{searchResults.map((user) => (
|
||||
<div
|
||||
key={user.id}
|
||||
className="flex items-center p-2 hover:bg-accent rounded-lg cursor-pointer"
|
||||
onClick={() => handleCreateChat(user.id)}
|
||||
>
|
||||
<Avatar className="h-8 w-8 mr-3">
|
||||
<AvatarImage src={user.avatar} />
|
||||
<AvatarFallback>{user.name?.charAt(0) || 'U'}</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="flex-1">
|
||||
<p className="text-sm font-medium">{user.name}</p>
|
||||
<p className="text-xs text-muted-foreground">{user.email}</p>
|
||||
</div>
|
||||
<Plus className="h-4 w-4 text-muted-foreground" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Chats Tab */}
|
||||
{activeTab === 'chats' && (
|
||||
<div className="chat-list__in relative">
|
||||
<ScrollArea className="h-full">
|
||||
<div className="p-4">
|
||||
<h2 className="text-lg font-semibold mb-4">Recent Chat</h2>
|
||||
<div className="nav chat-list space-y-1">
|
||||
{chats.map((chat) => (
|
||||
<div
|
||||
key={chat.id}
|
||||
className={`item-list item-list__chat flex items-start p-3 rounded-lg cursor-pointer transition-colors ${
|
||||
selectedChatId === chat.id
|
||||
? 'bg-primary/10 border-l-4 border-primary'
|
||||
: 'hover:bg-accent unseen'
|
||||
} ${chat.unreadCount > 0 ? 'unseen' : 'seen'}`}
|
||||
onClick={() => setSelectedChatId(chat.id)}
|
||||
>
|
||||
<div className="avatar relative mr-3">
|
||||
<Avatar className="h-12 w-12">
|
||||
<AvatarImage src={chat.avatar} />
|
||||
<AvatarFallback>{chat.name?.charAt(0) || 'C'}</AvatarFallback>
|
||||
</Avatar>
|
||||
{chat.online ? (
|
||||
<div className="status online absolute bottom-0 right-0 h-3 w-3 bg-green-500 rounded-full border-2 border-background" />
|
||||
) : (
|
||||
<div className="status offline absolute bottom-0 right-0 h-3 w-3 bg-gray-400 rounded-full border-2 border-background" />
|
||||
)}
|
||||
{chat.unreadCount > 0 && (
|
||||
<div className="new absolute -top-1 -left-1 h-6 w-6 bg-yellow-500 text-white text-xs rounded-full flex items-center justify-center">
|
||||
{chat.unreadCount > 99 ? '99+' : chat.unreadCount}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="info-text flex-1 min-w-0">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<h5 className="font-semibold text-sm truncate">{chat.name}</h5>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{chat.lastMessage ? formatTime(chat.lastMessage.timestamp) : ''}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground truncate">
|
||||
{chat.lastMessage?.content || 'No messages yet'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Online Users Tab */}
|
||||
{activeTab === 'online' && (
|
||||
<div className="chat-list__in relative">
|
||||
<ScrollArea className="h-full">
|
||||
<div className="p-4">
|
||||
<h2 className="text-lg font-semibold mb-4">Online Users</h2>
|
||||
<div className="online-visitor space-y-2">
|
||||
{onlineUsers.map((user) => (
|
||||
<div
|
||||
key={user.id}
|
||||
className="visitor-history flex items-center p-3 hover:bg-accent rounded-lg cursor-pointer"
|
||||
onClick={() => handleCreateChat(user.id)}
|
||||
>
|
||||
<div className="relative mr-3">
|
||||
<Avatar className="h-10 w-10">
|
||||
<AvatarImage src={user.avatar} />
|
||||
<AvatarFallback>{user.name?.charAt(0) || 'U'}</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="absolute -bottom-1 -right-1 h-3 w-3 bg-green-500 rounded-full border-2 border-background" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="font-medium text-sm">{user.name}</div>
|
||||
<div className="text-xs text-muted-foreground">{user.email}</div>
|
||||
</div>
|
||||
<MessageCircle className="h-4 w-4 text-muted-foreground" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Contacts Tab */}
|
||||
{activeTab === 'contacts' && (
|
||||
<div className="chat-list__in relative">
|
||||
<ScrollArea className="h-full">
|
||||
<div className="p-4">
|
||||
<h2 className="text-lg font-semibold mb-4">Contacts</h2>
|
||||
<div className="nav contact-list space-y-1">
|
||||
{chats.map((chat) => (
|
||||
<div
|
||||
key={chat.id}
|
||||
className="item-list item-list__contact flex items-center p-3 hover:bg-accent rounded-lg cursor-pointer"
|
||||
onClick={() => setSelectedChatId(chat.id)}
|
||||
>
|
||||
<div className="avatar relative mr-3">
|
||||
<Avatar className="h-12 w-12">
|
||||
<AvatarImage src={chat.avatar} />
|
||||
<AvatarFallback>{chat.name?.charAt(0) || 'C'}</AvatarFallback>
|
||||
</Avatar>
|
||||
{chat.online ? (
|
||||
<div className="status online absolute bottom-0 right-0 h-3 w-3 bg-green-500 rounded-full border-2 border-background" />
|
||||
) : (
|
||||
<div className="status offline absolute bottom-0 right-0 h-3 w-3 bg-gray-400 rounded-full border-2 border-background" />
|
||||
)}
|
||||
</div>
|
||||
<div className="info-text flex-1">
|
||||
<h5 className="font-semibold text-sm">{chat.name}</h5>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{chat.participants[0]?.email || 'No email'}
|
||||
</p>
|
||||
</div>
|
||||
<div className="person-add">
|
||||
<UserCheck className="h-5 w-5 text-green-500" />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Notifications Tab */}
|
||||
{activeTab === 'notifications' && (
|
||||
<div className="chat-list__in relative">
|
||||
<ScrollArea className="h-full">
|
||||
<div className="p-4">
|
||||
<h2 className="text-lg font-semibold mb-4">Notifications</h2>
|
||||
<div className="nav notification-list space-y-1">
|
||||
{chats.filter(chat => chat.unreadCount > 0).map((chat) => (
|
||||
<div
|
||||
key={chat.id}
|
||||
className="item-list item-list__contact flex items-center p-3 hover:bg-accent rounded-lg cursor-pointer"
|
||||
onClick={() => setSelectedChatId(chat.id)}
|
||||
>
|
||||
<div className="avatar mr-3">
|
||||
<Avatar className="h-12 w-12">
|
||||
<AvatarImage src={chat.avatar} />
|
||||
<AvatarFallback>{chat.name?.charAt(0) || 'C'}</AvatarFallback>
|
||||
</Avatar>
|
||||
</div>
|
||||
<div className="info-text flex-1">
|
||||
<h5 className="font-semibold text-sm">
|
||||
{chat.name} sent you a message
|
||||
</h5>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{chat.lastMessage ? formatTime(chat.lastMessage.timestamp) : ''}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Chat Area */}
|
||||
<div className="flex-1 flex flex-col">
|
||||
{selectedChat ? (
|
||||
<>
|
||||
{/* Chat Header */}
|
||||
<div className="p-4 border-b border-border flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<Avatar className="h-10 w-10 mr-3">
|
||||
<AvatarImage src={selectedChat.avatar} />
|
||||
<AvatarFallback>{selectedChat.name?.charAt(0) || 'C'}</AvatarFallback>
|
||||
</Avatar>
|
||||
<div>
|
||||
<h2 className="font-semibold">{selectedChat.name || 'Unknown'}</h2>
|
||||
<div className="flex items-center text-sm text-muted-foreground">
|
||||
<Circle className="h-2 w-2 fill-green-500 text-green-500 mr-1" />
|
||||
{selectedChat.online ? 'Online' : 'Last seen 12 hour ago'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button size="sm" variant="outline">
|
||||
<Phone className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button size="sm" variant="outline">
|
||||
<Video className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button size="sm" variant="outline">
|
||||
<Settings className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button size="sm" variant="outline">
|
||||
<MoreVertical className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Messages */}
|
||||
<ScrollArea className="flex-1">
|
||||
<div className="message-content message-content-scroll bg-background">
|
||||
<div className="relative">
|
||||
{messages.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center h-full text-center p-8">
|
||||
<div className="w-32 h-32 bg-orange-100 rounded-lg flex items-center justify-center mb-4">
|
||||
<MessageCircle className="h-16 w-16 text-orange-500" />
|
||||
</div>
|
||||
<h3 className="text-lg font-medium mb-2">This chat is empty.</h3>
|
||||
<p className="text-muted-foreground">Be the first one to start it.</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{/* Date Divider */}
|
||||
<div className="date flex items-center my-6">
|
||||
<hr className="flex-1 border-border" />
|
||||
<span className="px-4 text-sm text-muted-foreground bg-background">Yesterday</span>
|
||||
<hr className="flex-1 border-border" />
|
||||
</div>
|
||||
|
||||
{/* Sample Messages */}
|
||||
<div className="message flex items-start mb-4 px-4">
|
||||
<Avatar className="h-10 w-10 mr-3">
|
||||
<AvatarImage src={selectedChat.avatar} />
|
||||
<AvatarFallback>{selectedChat.name?.charAt(0) || 'U'}</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="text-main flex-1">
|
||||
<span className="time-ago text-xs text-muted-foreground mb-2 block">09:46 AM</span>
|
||||
<div className="text-group">
|
||||
<div className="text bg-muted p-3 rounded-lg max-w-md">
|
||||
<p className="text-sm">It is a long established fact that a reader will be.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="message me flex items-start mb-4 px-4 justify-end">
|
||||
<div className="text-main flex-1 flex flex-col items-end">
|
||||
<span className="time-ago text-xs text-muted-foreground mb-2 block">11:32 AM</span>
|
||||
<div className="text-group me">
|
||||
<div className="text me bg-primary text-primary-foreground p-3 rounded-lg max-w-md">
|
||||
<p className="text-sm">By the readable content of a page when looking at its?</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="message flex items-start mb-4 px-4">
|
||||
<Avatar className="h-10 w-10 mr-3">
|
||||
<AvatarImage src={selectedChat.avatar} />
|
||||
<AvatarFallback>{selectedChat.name?.charAt(0) || 'U'}</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="text-main flex-1">
|
||||
<span className="time-ago text-xs text-muted-foreground mb-2 block">02:56 PM</span>
|
||||
<div className="text-group">
|
||||
<div className="text bg-muted p-3 rounded-lg max-w-md">
|
||||
<p className="text-sm">The point of using Lorem Ipsum is that it has a more-or-less normal distribution.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="message me flex items-start mb-4 px-4 justify-end">
|
||||
<div className="text-main flex-1 flex flex-col items-end">
|
||||
<span className="time-ago text-xs text-muted-foreground mb-2 block">10:21 PM</span>
|
||||
<div className="text-group me space-y-2">
|
||||
<div className="text me bg-primary text-primary-foreground p-3 rounded-lg max-w-md">
|
||||
<p className="text-sm">Roger that boss!</p>
|
||||
</div>
|
||||
<div className="text me bg-primary text-primary-foreground p-3 rounded-lg max-w-md">
|
||||
<p className="text-sm">Many desktop publishing packages and web page editors now use Lorem Ipsum as their!</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="message flex items-start mb-4 px-4">
|
||||
<Avatar className="h-10 w-10 mr-3">
|
||||
<AvatarImage src={selectedChat.avatar} />
|
||||
<AvatarFallback>{selectedChat.name?.charAt(0) || 'U'}</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="text-main flex-1">
|
||||
<span className="time-ago text-xs text-muted-foreground mb-2 block">11:07 PM</span>
|
||||
<div className="text-group">
|
||||
<div className="text bg-muted p-3 rounded-lg max-w-md">
|
||||
<p className="text-sm">Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like)!</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Today Date Divider */}
|
||||
<div className="date flex items-center my-6">
|
||||
<hr className="flex-1 border-border" />
|
||||
<span className="px-4 text-sm text-muted-foreground bg-background">Today</span>
|
||||
<hr className="flex-1 border-border" />
|
||||
</div>
|
||||
|
||||
{/* File Attachment Message */}
|
||||
<div className="message flex items-start mb-4 px-4">
|
||||
<Avatar className="h-10 w-10 mr-3">
|
||||
<AvatarImage src={selectedChat.avatar} />
|
||||
<AvatarFallback>{selectedChat.name?.charAt(0) || 'U'}</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="text-main flex-1">
|
||||
<span className="time-ago text-xs text-muted-foreground mb-2 block">11:07 PM</span>
|
||||
<div className="text-group">
|
||||
<div className="text bg-muted p-3 rounded-lg max-w-md">
|
||||
<div className="attachment flex items-center gap-3">
|
||||
<Button size="sm" className="p-2 h-auto">
|
||||
<Paperclip className="h-5 w-5" />
|
||||
</Button>
|
||||
<div className="file">
|
||||
<h5 className="text-sm font-medium">
|
||||
<a href="#" className="text-primary hover:underline">Documentations.pdf</a>
|
||||
</h5>
|
||||
<span className="text-xs text-muted-foreground">21kb Document</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* My Message with Read Receipt */}
|
||||
<div className="message me flex items-start mb-4 px-4 justify-end">
|
||||
<div className="text-main flex-1 flex flex-col items-end">
|
||||
<span className="time-ago text-xs text-muted-foreground mb-2 flex items-center gap-1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className="bi bi-check2-all" viewBox="0 0 16 16">
|
||||
<path d="M12.354 4.354a.5.5 0 0 0-.708-.708L5 10.293 1.854 7.146a.5.5 0 1 0-.708.708l3.5 3.5a.5.5 0 0 0 .708 0l7-7zm-4.208 7-.896-.897.707-.707.543.543 6.646-6.647a.5.5 0 0 1 .708.708l-7 7a.5.5 0 0 1-.708 0z"></path>
|
||||
<path d="m5.354 7.146.896.897-.707.707-.897-.896a.5.5 0 1 1 .708-.708"></path>
|
||||
</svg>
|
||||
10:21 PM
|
||||
</span>
|
||||
<div className="text-group me">
|
||||
<div className="text me bg-primary text-primary-foreground p-3 rounded-lg max-w-md">
|
||||
<p className="text-sm">If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing!</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Typing Indicator */}
|
||||
<div className="message flex items-start mb-4 px-4">
|
||||
<Avatar className="h-10 w-10 mr-3">
|
||||
<AvatarImage src={selectedChat.avatar} />
|
||||
<AvatarFallback>{selectedChat.name?.charAt(0) || 'U'}</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="text-main flex-1">
|
||||
<div className="text-group">
|
||||
<div className="text typing bg-muted p-3 rounded-lg w-16">
|
||||
<div className="wave flex items-center justify-center space-x-1">
|
||||
<span className="dot w-2 h-2 bg-muted-foreground rounded-full animate-bounce [animation-delay:-0.3s]"></span>
|
||||
<span className="dot w-2 h-2 bg-muted-foreground rounded-full animate-bounce [animation-delay:-0.15s]"></span>
|
||||
<span className="dot w-2 h-2 bg-muted-foreground rounded-full animate-bounce"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Render dynamic messages */}
|
||||
{messages.map((message) => (
|
||||
<div
|
||||
key={message.id}
|
||||
className={`message flex items-start mb-4 px-4 ${message.isOwn ? 'me justify-end' : ''}`}
|
||||
>
|
||||
{!message.isOwn && (
|
||||
<Avatar className="h-10 w-10 mr-3">
|
||||
<AvatarImage src={message.senderAvatar} />
|
||||
<AvatarFallback>{message.senderName?.charAt(0) || 'U'}</AvatarFallback>
|
||||
</Avatar>
|
||||
)}
|
||||
<div className={`text-main flex-1 ${message.isOwn ? 'flex flex-col items-end' : ''}`}>
|
||||
<span className="time-ago text-xs text-muted-foreground mb-2 block">{message.timestamp}</span>
|
||||
<div className={`text-group ${message.isOwn ? 'me' : ''}`}>
|
||||
<div className={`text p-3 rounded-lg max-w-md ${
|
||||
message.isOwn
|
||||
? 'me bg-primary text-primary-foreground'
|
||||
: 'bg-muted'
|
||||
}`}>
|
||||
<p className="text-sm">{message.content}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div ref={messagesEndRef} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
|
||||
{/* Message Input */}
|
||||
<div className="p-4 border-t border-border">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button size="sm" variant="outline">
|
||||
<Paperclip className="h-4 w-4" />
|
||||
</Button>
|
||||
<div className="flex-1">
|
||||
<Input
|
||||
placeholder="Type a message here..."
|
||||
value={messageText}
|
||||
onChange={(e) => setMessageText(e.target.value)}
|
||||
onKeyPress={(e) => {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
handleSendMessage();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Button size="sm" variant="outline">
|
||||
<Smile className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={handleSendMessage}
|
||||
disabled={!messageText.trim()}
|
||||
>
|
||||
<Send className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="flex-1 flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<MessageCircle className="h-16 w-16 text-muted-foreground mx-auto mb-4" />
|
||||
<h3 className="text-lg font-medium mb-2">Select a conversation</h3>
|
||||
<p className="text-muted-foreground">Choose from your existing conversations or start a new one</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Chat Details Sidebar - Right */}
|
||||
{selectedChat && showChatDetails && (
|
||||
<div className="chat-list__sidebar--right w-80 border-l border-border bg-background">
|
||||
<ScrollArea className="h-full">
|
||||
<div className="p-4">
|
||||
{/* User Info */}
|
||||
<div className="chat-user__info flex items-center mb-6">
|
||||
<div className="avatar relative mr-3">
|
||||
<Avatar className="h-12 w-12">
|
||||
<AvatarImage src={selectedChat.avatar} />
|
||||
<AvatarFallback>{selectedChat.name?.charAt(0) || 'C'}</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="status online absolute bottom-0 right-0 h-3 w-3 bg-green-500 rounded-full border-2 border-background" />
|
||||
</div>
|
||||
<div className="info-text flex-1">
|
||||
<h5 className="text-lg font-semibold m-0">{selectedChat.name || 'Unknown'}</h5>
|
||||
<p className="text-sm text-muted-foreground writing">{selectedChat.name} typing a message</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Auto Bot/Manual Toggle */}
|
||||
<div className="chatting_indicate bg-muted/50 p-4 rounded-lg mb-4">
|
||||
<h5 className="text-base font-semibold mb-2">Conversation With Auto bot or manual</h5>
|
||||
<p className="text-sm text-muted-foreground mb-4">Everyone in this conversation will see this.</p>
|
||||
<div className="flex items-center justify-center space-x-4">
|
||||
<Label
|
||||
className={`cursor-pointer px-3 py-1 rounded ${isAutoBot ? 'bg-primary text-primary-foreground' : 'text-muted-foreground'}`}
|
||||
htmlFor="autobot"
|
||||
>
|
||||
Auto bot
|
||||
</Label>
|
||||
<div className="toggle">
|
||||
<Switch
|
||||
id="switcher"
|
||||
checked={!isAutoBot}
|
||||
onCheckedChange={(checked) => setIsAutoBot(!checked)}
|
||||
/>
|
||||
</div>
|
||||
<Label
|
||||
className={`cursor-pointer px-3 py-1 rounded ${!isAutoBot ? 'bg-primary text-primary-foreground' : 'text-muted-foreground'}`}
|
||||
htmlFor="manual"
|
||||
>
|
||||
Manual
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Accordion */}
|
||||
<Accordion type="single" collapsible defaultValue="change-color">
|
||||
{/* User Details */}
|
||||
<AccordionItem value="user-details">
|
||||
<AccordionTrigger className="text-left">
|
||||
<div className="flex items-center">
|
||||
<UserCheck className="h-5 w-5 mr-2" />
|
||||
User Details
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div className="user-info">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full border border-border rounded">
|
||||
<tbody>
|
||||
<tr className="border-b border-border">
|
||||
<td className="user-info-first p-3 font-medium bg-muted/50">Name</td>
|
||||
<td className="p-3">{selectedChat.name || 'Unknown'}</td>
|
||||
</tr>
|
||||
<tr className="border-b border-border">
|
||||
<td className="user-info-first p-3 font-medium bg-muted/50">ID</td>
|
||||
<td className="p-3">{selectedChat.id}</td>
|
||||
</tr>
|
||||
<tr className="border-b border-border">
|
||||
<td className="user-info-first p-3 font-medium bg-muted/50">E-mail</td>
|
||||
<td className="p-3">{selectedChat.participants[0]?.email || 'example@email.com'}</td>
|
||||
</tr>
|
||||
<tr className="border-b border-border">
|
||||
<td className="user-info-first p-3 font-medium bg-muted/50">URL</td>
|
||||
<td className="p-3">
|
||||
<a href="#" className="text-primary hover:underline">https://easital.com/</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="user-info-first p-3 font-medium bg-muted/50">Browser</td>
|
||||
<td className="p-3">Chrome</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
|
||||
{/* Edit Name */}
|
||||
<AccordionItem value="edit-name">
|
||||
<AccordionTrigger className="text-left">
|
||||
<div className="flex items-center">
|
||||
<Edit3 className="h-5 w-5 mr-2" />
|
||||
Edit name
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div className="space-y-4">
|
||||
<h5 className="font-semibold">Edit Nickname for {selectedChat.name}</h5>
|
||||
<p className="text-sm text-muted-foreground">Everyone in this conversation will see this.</p>
|
||||
<div className="space-y-3">
|
||||
<Input
|
||||
type="text"
|
||||
placeholder={selectedChat.name || 'Enter nickname'}
|
||||
value={newNickname}
|
||||
onChange={(e) => setNewNickname(e.target.value)}
|
||||
/>
|
||||
<div className="flex justify-end">
|
||||
<Button size="sm" variant="default">
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
|
||||
{/* Change Color */}
|
||||
<AccordionItem value="change-color">
|
||||
<AccordionTrigger className="text-left">
|
||||
<div className="flex items-center">
|
||||
<Palette className="h-5 w-5 mr-2" />
|
||||
Change color
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div className="space-y-4">
|
||||
<h5 className="font-semibold">Pick a color for this conversation</h5>
|
||||
<p className="text-sm text-muted-foreground">Everyone in this conversation will see this.</p>
|
||||
<div className="radio-list change-bg-color grid grid-cols-5 gap-3">
|
||||
{[
|
||||
{ id: 'red', color: 'bg-red-500' },
|
||||
{ id: 'green', color: 'bg-green-500' },
|
||||
{ id: 'yellow', color: 'bg-yellow-500' },
|
||||
{ id: 'orange', color: 'bg-orange-500' },
|
||||
{ id: 'teal', color: 'bg-teal-500' },
|
||||
{ id: 'blue', color: 'bg-blue-500' },
|
||||
{ id: 'violet', color: 'bg-violet-500' },
|
||||
{ id: 'purple', color: 'bg-purple-500' },
|
||||
{ id: 'pink', color: 'bg-pink-500' },
|
||||
{ id: 'gray', color: 'bg-gray-500' }
|
||||
].map((colorOption) => (
|
||||
<label
|
||||
key={colorOption.id}
|
||||
className="cursor-pointer"
|
||||
htmlFor={colorOption.id}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="color"
|
||||
id={colorOption.id}
|
||||
value={colorOption.id}
|
||||
checked={selectedColor === colorOption.id}
|
||||
onChange={(e) => setSelectedColor(e.target.value)}
|
||||
className="sr-only"
|
||||
/>
|
||||
<span
|
||||
className={`block w-8 h-8 rounded-full ${colorOption.color} ${
|
||||
selectedColor === colorOption.id ? 'ring-2 ring-offset-2 ring-primary' : ''
|
||||
}`}
|
||||
/>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
|
||||
{/* Notifications */}
|
||||
<AccordionItem value="notifications">
|
||||
<AccordionTrigger className="text-left">
|
||||
<div className="flex items-center">
|
||||
<Bell className="h-5 w-5 mr-2" />
|
||||
Notifications
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div className="space-y-4">
|
||||
<h5 className="font-semibold">Conversation Notifications</h5>
|
||||
<p className="text-sm text-muted-foreground">Everyone in this conversation will see this.</p>
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Switch id="notifications1" />
|
||||
<Label htmlFor="notifications1" className="text-sm">
|
||||
Receive notifications for new messages
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Switch id="notifications2" />
|
||||
<Label htmlFor="notifications2" className="text-sm">
|
||||
Receive notifications for reactions
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex justify-end">
|
||||
<Button size="sm" variant="default">
|
||||
Done
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Messages;
|
||||
Reference in New Issue
Block a user