422 lines
14 KiB
TypeScript
422 lines
14 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useRef, useEffect } from 'react';
|
|
import Image from 'next/image';
|
|
import { ChatHeader, MessageInput } from './component';
|
|
|
|
// Custom scrollbar styles
|
|
const customScrollbarStyles = `
|
|
.custom-scrollbar::-webkit-scrollbar {
|
|
width: 8px;
|
|
}
|
|
.custom-scrollbar::-webkit-scrollbar-track {
|
|
background: transparent;
|
|
}
|
|
.custom-scrollbar::-webkit-scrollbar-thumb {
|
|
background: rgba(0, 41, 61, 0.5);
|
|
border-radius: 10px;
|
|
}
|
|
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
|
background: rgba(0, 41, 61, 0.7);
|
|
}
|
|
`;
|
|
|
|
// Mock data for conversations
|
|
const conversations = [
|
|
{
|
|
id: '1',
|
|
name: 'Pradeep Ram',
|
|
avatar: '/assets/icons/user-placeholder-icon.svg',
|
|
lastMessage: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
|
|
date: 'Dec 4',
|
|
isOnline: true,
|
|
},
|
|
{
|
|
id: '2',
|
|
name: 'Pradeep',
|
|
avatar: '/assets/icons/user-placeholder-icon.svg',
|
|
lastMessage: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
|
|
date: 'Dec 4',
|
|
isOnline: true,
|
|
},
|
|
{
|
|
id: '3',
|
|
name: 'Gokulraj',
|
|
avatar: '/assets/icons/user-placeholder-icon.svg',
|
|
lastMessage: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
|
|
date: 'Dec 4',
|
|
isOnline: true,
|
|
},
|
|
{
|
|
id: '4',
|
|
name: 'Suriya s',
|
|
avatar: '/assets/icons/user-placeholder-icon.svg',
|
|
lastMessage: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
|
|
date: 'Dec 4',
|
|
isOnline: true,
|
|
},
|
|
{
|
|
id: '5',
|
|
name: 'Sanjay',
|
|
avatar: '/assets/icons/user-placeholder-icon.svg',
|
|
lastMessage: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
|
|
date: 'Dec 4',
|
|
isOnline: true,
|
|
},
|
|
{
|
|
id: '6',
|
|
name: 'Pradeep Ram',
|
|
avatar: '/assets/icons/user-placeholder-icon.svg',
|
|
lastMessage: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
|
|
date: 'Dec 4',
|
|
isOnline: false,
|
|
},
|
|
{
|
|
id: '7',
|
|
name: 'Rahul Kumar',
|
|
avatar: '/assets/icons/user-placeholder-icon.svg',
|
|
lastMessage: 'Thanks for your help with the property listing!',
|
|
date: 'Dec 3',
|
|
isOnline: true,
|
|
},
|
|
{
|
|
id: '8',
|
|
name: 'Anita Singh',
|
|
avatar: '/assets/icons/user-placeholder-icon.svg',
|
|
lastMessage: 'Can we schedule a viewing for tomorrow?',
|
|
date: 'Dec 3',
|
|
isOnline: false,
|
|
},
|
|
{
|
|
id: '9',
|
|
name: 'Vikram Patel',
|
|
avatar: '/assets/icons/user-placeholder-icon.svg',
|
|
lastMessage: 'The documents have been submitted.',
|
|
date: 'Dec 2',
|
|
isOnline: true,
|
|
},
|
|
{
|
|
id: '10',
|
|
name: 'Meera Sharma',
|
|
avatar: '/assets/icons/user-placeholder-icon.svg',
|
|
lastMessage: 'Looking forward to our meeting next week.',
|
|
date: 'Dec 2',
|
|
isOnline: false,
|
|
},
|
|
{
|
|
id: '11',
|
|
name: 'Arjun Reddy',
|
|
avatar: '/assets/icons/user-placeholder-icon.svg',
|
|
lastMessage: 'Please send me the property details.',
|
|
date: 'Dec 1',
|
|
isOnline: true,
|
|
},
|
|
{
|
|
id: '12',
|
|
name: 'Sneha Nair',
|
|
avatar: '/assets/icons/user-placeholder-icon.svg',
|
|
lastMessage: 'I have a few questions about the contract.',
|
|
date: 'Dec 1',
|
|
isOnline: false,
|
|
},
|
|
];
|
|
|
|
// Mock data for selected user
|
|
const selectedUserData = {
|
|
name: 'Pradeep Ram',
|
|
role: 'Advisor',
|
|
lastSeen: '21h Ago',
|
|
avatar: '/assets/icons/user-placeholder-icon.svg',
|
|
isOnline: true,
|
|
pronouns: 'He/Him',
|
|
expertise: ['Sales', 'Analytics', 'Inspection', 'Residential', 'Commercial'],
|
|
};
|
|
|
|
// Mock messages data
|
|
const messages = [
|
|
{
|
|
id: '1',
|
|
senderId: '1',
|
|
text: 'Hi! I saw your profile and I think you might be a great fit for what I\'m looking for.',
|
|
timestamp: '10:30 AM',
|
|
isOwn: false,
|
|
},
|
|
{
|
|
id: '2',
|
|
senderId: 'me',
|
|
text: 'Hello! Thank you for reaching out. I\'d be happy to help. What kind of property are you looking for?',
|
|
timestamp: '10:32 AM',
|
|
isOwn: true,
|
|
},
|
|
{
|
|
id: '3',
|
|
senderId: '1',
|
|
text: 'I\'m looking for a residential property in the downtown area. Preferably a 3-bedroom apartment with modern amenities.',
|
|
timestamp: '10:35 AM',
|
|
isOwn: false,
|
|
},
|
|
{
|
|
id: '4',
|
|
senderId: 'me',
|
|
text: 'That sounds great! I have several listings that might interest you. Do you have a specific budget range in mind?',
|
|
timestamp: '10:38 AM',
|
|
isOwn: true,
|
|
},
|
|
{
|
|
id: '5',
|
|
senderId: '1',
|
|
text: 'My budget is around $500,000 to $700,000. I\'m also interested in properties with good investment potential.',
|
|
timestamp: '10:40 AM',
|
|
isOwn: false,
|
|
},
|
|
{
|
|
id: '6',
|
|
senderId: 'me',
|
|
text: 'Perfect! I have a few properties in that range. Would you like to schedule a viewing this weekend?',
|
|
timestamp: '10:42 AM',
|
|
isOwn: true,
|
|
},
|
|
{
|
|
id: '7',
|
|
senderId: '1',
|
|
text: 'Yes, that would be great! Saturday afternoon works best for me.',
|
|
timestamp: '10:45 AM',
|
|
isOwn: false,
|
|
},
|
|
{
|
|
id: '8',
|
|
senderId: 'me',
|
|
text: 'Saturday at 2 PM works for me. I\'ll send you the addresses and details of the properties we\'ll be visiting.',
|
|
timestamp: '10:48 AM',
|
|
isOwn: true,
|
|
},
|
|
{
|
|
id: '9',
|
|
senderId: '1',
|
|
text: 'Sounds perfect! Looking forward to it. Thank you for your quick response.',
|
|
timestamp: '10:50 AM',
|
|
isOwn: false,
|
|
},
|
|
{
|
|
id: '10',
|
|
senderId: 'me',
|
|
text: 'You\'re welcome! See you on Saturday. Feel free to reach out if you have any questions before then.',
|
|
timestamp: '10:52 AM',
|
|
isOwn: true,
|
|
},
|
|
];
|
|
|
|
export default function MessagePage() {
|
|
const [selectedConversation, setSelectedConversation] = useState<string | null>('1');
|
|
const [searchQuery, setSearchQuery] = useState('');
|
|
const [showScrollButton, setShowScrollButton] = useState(false);
|
|
const messagesContainerRef = useRef<HTMLDivElement>(null);
|
|
|
|
const handleSendMessage = (message: string) => {
|
|
console.log('Sending message:', message);
|
|
// In production, this would send the message to the API
|
|
};
|
|
|
|
const handleScroll = () => {
|
|
if (messagesContainerRef.current) {
|
|
const { scrollTop, scrollHeight, clientHeight } = messagesContainerRef.current;
|
|
// Show button if not at bottom (with 100px threshold)
|
|
setShowScrollButton(scrollHeight - scrollTop - clientHeight > 100);
|
|
}
|
|
};
|
|
|
|
const scrollToBottom = () => {
|
|
if (messagesContainerRef.current) {
|
|
messagesContainerRef.current.scrollTo({
|
|
top: messagesContainerRef.current.scrollHeight,
|
|
behavior: 'smooth'
|
|
});
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
// Scroll to bottom on initial load
|
|
scrollToBottom();
|
|
}, [selectedConversation]);
|
|
|
|
const filteredConversations = conversations.filter((conv) =>
|
|
conv.name.toLowerCase().includes(searchQuery.toLowerCase())
|
|
);
|
|
|
|
return (
|
|
<>
|
|
<style>{customScrollbarStyles}</style>
|
|
<div className="border border-[#00293d]/10 rounded-[20px] bg-white overflow-hidden">
|
|
{/* Header */}
|
|
<div className="flex items-center gap-4 p-4 border-b border-[#00293d]/10">
|
|
<h1 className="font-fractul font-bold text-[18px] leading-[22px] text-[#00293D]">
|
|
Messaging
|
|
</h1>
|
|
|
|
{/* Search bar */}
|
|
<div className="flex-1 max-w-[600px]">
|
|
<div className="flex items-center gap-2 border border-[#00293d]/10 rounded-[15px] px-4 py-2">
|
|
<Image
|
|
src="/assets/icons/search-icon.svg"
|
|
alt="Search"
|
|
width={20}
|
|
height={20}
|
|
/>
|
|
<input
|
|
type="text"
|
|
value={searchQuery}
|
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
placeholder="Search Message"
|
|
className="flex-1 font-serif font-normal text-[18px] leading-[24px] text-[#00293D] placeholder-[#00293D]/50 focus:outline-none"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Header actions */}
|
|
<div className="flex items-center gap-2 ml-auto">
|
|
<button className="p-2 hover:bg-gray-100 rounded-lg transition-colors cursor-pointer">
|
|
<Image
|
|
src="/assets/icons/three-dots-horizontal-icon.svg"
|
|
alt="More options"
|
|
width={24}
|
|
height={24}
|
|
/>
|
|
</button>
|
|
<button className="p-2 hover:bg-gray-100 rounded-lg transition-colors cursor-pointer">
|
|
<Image
|
|
src="/assets/icons/compose-icon.svg"
|
|
alt="Compose"
|
|
width={24}
|
|
height={24}
|
|
/>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Main content */}
|
|
<div className="flex h-[700px]">
|
|
{/* Left sidebar - Conversation list */}
|
|
<div className="w-[380px] border-r border-[#00293d]/10 flex-shrink-0 overflow-hidden flex flex-col">
|
|
<div className="flex-1 overflow-y-auto p-2 space-y-2 custom-scrollbar">
|
|
{filteredConversations.map((conversation) => (
|
|
<div
|
|
key={conversation.id}
|
|
onClick={() => setSelectedConversation(conversation.id)}
|
|
className={`relative flex items-start gap-3 p-4 rounded-[15px] cursor-pointer transition-colors ${
|
|
selectedConversation === conversation.id ? 'bg-[#00293d]/5' : 'hover:bg-[#00293d]/5'
|
|
}`}
|
|
style={{ border: '1px solid rgba(0, 41, 61, 0.1)' }}
|
|
>
|
|
{/* Active indicator line */}
|
|
{selectedConversation === conversation.id && (
|
|
<div className="absolute left-0 top-1/2 -translate-y-1/2 w-[4px] h-[70px] bg-[#E58625] rounded-r-[4px]" />
|
|
)}
|
|
{/* Avatar with online indicator */}
|
|
<div className="relative flex-shrink-0">
|
|
<div className="w-[70px] h-[70px] rounded-full overflow-hidden border border-[#00293D]/20">
|
|
<Image
|
|
src={conversation.avatar}
|
|
alt={conversation.name}
|
|
width={70}
|
|
height={70}
|
|
className="w-full h-full object-cover rounded-full"
|
|
/>
|
|
</div>
|
|
{conversation.isOnline && (
|
|
<div className="absolute bottom-1 right-1 w-3 h-3 bg-green-500 rounded-full border-2 border-white" />
|
|
)}
|
|
</div>
|
|
|
|
{/* Content */}
|
|
<div className="flex-1 min-w-0">
|
|
<p className="font-fractul font-medium text-[14px] leading-[17px] text-[#00293D] mb-1">
|
|
{conversation.name}
|
|
</p>
|
|
<p className="font-serif font-normal text-[14px] leading-[19px] text-[#00293D] line-clamp-2">
|
|
{conversation.lastMessage}
|
|
</p>
|
|
</div>
|
|
|
|
{/* Date */}
|
|
<p className="font-serif font-normal text-[14px] leading-[19px] text-[#00293D] flex-shrink-0">
|
|
{conversation.date}
|
|
</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Right panel - Chat area */}
|
|
<div className="flex-1 flex flex-col p-4">
|
|
{selectedConversation ? (
|
|
<>
|
|
{/* Chat header */}
|
|
<ChatHeader {...selectedUserData} />
|
|
|
|
{/* Messages area wrapper */}
|
|
<div className="flex-1 relative">
|
|
<div
|
|
ref={messagesContainerRef}
|
|
onScroll={handleScroll}
|
|
className="absolute inset-0 overflow-y-auto py-4 custom-scrollbar"
|
|
>
|
|
<div className="space-y-4 px-2">
|
|
{messages.map((message) => (
|
|
<div
|
|
key={message.id}
|
|
className={`flex ${message.isOwn ? 'justify-end' : 'justify-start'}`}
|
|
>
|
|
<div
|
|
className={`max-w-[70%] rounded-[15px] px-4 py-3 ${
|
|
message.isOwn
|
|
? 'bg-[#e58625] text-[#00293D]'
|
|
: 'bg-[#00293d]/10 text-[#00293D]'
|
|
}`}
|
|
>
|
|
<p className="font-serif font-normal text-[14px] leading-[19px]">
|
|
{message.text}
|
|
</p>
|
|
<p
|
|
className={`font-serif font-normal text-[12px] leading-[16px] mt-1 ${
|
|
message.isOwn ? 'text-[#00293D]/70' : 'text-[#00293D]/50'
|
|
}`}
|
|
>
|
|
{message.timestamp}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Scroll to bottom button - only shows when scrolled up */}
|
|
{showScrollButton && (
|
|
<button
|
|
onClick={scrollToBottom}
|
|
className="absolute bottom-4 right-4 w-[50px] h-[50px] bg-[#00293d]/10 rounded-full flex items-center justify-center hover:bg-[#00293d]/20 transition-all cursor-pointer shadow-lg z-10"
|
|
>
|
|
<Image
|
|
src="/assets/icons/arrow-down-icon.svg"
|
|
alt="Scroll to bottom"
|
|
width={20}
|
|
height={20}
|
|
/>
|
|
</button>
|
|
)}
|
|
</div>
|
|
|
|
{/* Message input */}
|
|
<MessageInput onSend={handleSendMessage} />
|
|
</>
|
|
) : (
|
|
<div className="flex items-center justify-center h-full text-[#00293D]/50 font-serif">
|
|
Select a conversation to start messaging
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|