refactor: Remove deprecated agent dashboard and message components, add FAQ page, and introduce a new generic messaging system.

This commit is contained in:
pradeepkumar
2026-01-20 12:26:27 +05:30
parent a4dc8bb0fa
commit f035129b6a
28 changed files with 693 additions and 1192 deletions

View File

@@ -1,67 +0,0 @@
'use client';
import { useState } from 'react';
import Image from 'next/image';
interface ContactInfoProps {
email: string;
phone: string;
}
// Helper function to mask email
function maskEmail(email: string): string {
const [localPart, domain] = email.split('@');
if (!domain) return '********';
const maskedLocal = localPart.slice(0, 2) + '********';
return `${maskedLocal}@${domain}`;
}
// Helper function to mask phone
function maskPhone(phone: string): string {
if (phone.length <= 4) return '********';
return phone.slice(0, -4).replace(/./g, '*') + phone.slice(-4);
}
export function ContactInfo({ email, phone }: ContactInfoProps) {
const [showEmail, setShowEmail] = useState(false);
const [showPhone, setShowPhone] = useState(false);
return (
<div className="w-full max-w-[354px] lg:max-w-none border border-[#00293d]/10 rounded-[15px] p-4">
<div className="flex items-center gap-2 mb-2">
<span className="font-bold text-[#00293d] text-sm">Email:</span>
<span className="text-[#00293d] text-sm font-serif">
{showEmail ? email : maskEmail(email)}
</span>
<button
className="p-1 hover:bg-gray-100 rounded ml-auto"
onClick={() => setShowEmail(!showEmail)}
>
<Image
src={showEmail ? "/assets/icons/eye-off-icon.svg" : "/assets/icons/eye-icon.svg"}
alt={showEmail ? "Hide email" : "Show email"}
width={16}
height={16}
/>
</button>
</div>
<div className="flex items-center gap-2">
<span className="font-bold text-[#00293d] text-sm">Ph.No:</span>
<span className="text-[#00293d] text-sm font-serif">
{showPhone ? phone : maskPhone(phone)}
</span>
<button
className="p-1 hover:bg-gray-100 rounded ml-auto"
onClick={() => setShowPhone(!showPhone)}
>
<Image
src={showPhone ? "/assets/icons/eye-off-icon.svg" : "/assets/icons/eye-icon.svg"}
alt={showPhone ? "Hide phone" : "Show phone"}
width={16}
height={16}
/>
</button>
</div>
</div>
);
}

View File

@@ -1,83 +0,0 @@
'use client';
import { Tag } from './Tag';
interface ExperienceData {
years: string;
contracts: string;
licensingAreas: string[];
expertiseYears: { area: string; years: string }[];
certifications: { name: string; org: string }[];
}
interface ExperienceSectionProps {
experience: ExperienceData;
}
export function ExperienceSection({ experience }: ExperienceSectionProps) {
return (
<div className="bg-white rounded-[20px] p-4 lg:p-5">
<h2 className="text-[20px] font-bold text-[#00293D] font-fractul leading-[24px] mb-4 text-center lg:text-left">Experience</h2>
<div className="flex flex-col lg:flex-row gap-4 lg:gap-8">
{/* Left Column */}
<div className="flex-1 space-y-4">
<div>
<p className="font-fractul font-bold text-[14px] leading-[17px] text-[#00293D] mb-2">Years in Experience</p>
<ul className="list-disc list-inside">
<li className="font-serif font-medium text-[15px] leading-[21px] text-[#00293D]">{experience.years}</li>
</ul>
</div>
{/* Horizontal divider */}
<div className="w-full h-0 border-t-[0.5px] border-solid border-[#00293D]/20" />
<div>
<p className="font-fractul font-bold text-[14px] leading-[17px] text-[#00293D] mb-2">Number of contract closed</p>
<ul className="list-disc list-inside">
<li className="font-serif font-medium text-[15px] leading-[21px] text-[#00293D]">{experience.contracts}</li>
</ul>
</div>
<div>
<p className="font-fractul font-bold text-[14px] leading-[17px] text-[#00293D] mb-3">Licensing &amp; Areas</p>
<div className="flex flex-wrap gap-2">
{experience.licensingAreas.map((area, idx) => (
<Tag key={idx} variant="light">
{area}
</Tag>
))}
<span className="inline-flex items-center justify-center h-[28px] px-3 rounded-[15px] border border-[#00293d]/10 text-[15px] font-light font-serif text-[#00293d] cursor-pointer">+3 More</span>
</div>
</div>
</div>
{/* Divider - Hidden on mobile, visible on desktop */}
<div className="hidden lg:block w-px bg-gray-200" />
<div className="lg:hidden border-t border-gray-200" />
{/* Right Column */}
<div className="flex-1 space-y-4">
<div>
<p className="font-fractul font-bold text-[14px] leading-[17px] text-[#00293D] mb-3">Areas in expertise &amp; Years</p>
<div className="flex flex-wrap gap-2">
{experience.expertiseYears.slice(0, 4).map((item, idx) => (
<Tag key={idx} variant="light">
{item.area} {item.years}
</Tag>
))}
<span className="inline-flex items-center justify-center h-[28px] px-3 rounded-[15px] border border-[#00293d]/10 text-[15px] font-light font-serif text-[#00293d] cursor-pointer">+3 More</span>
</div>
</div>
<div>
<p className="font-fractul font-bold text-[14px] leading-[17px] text-[#00293D] mb-3">Certifications</p>
<ul className="space-y-3">
{experience.certifications.map((cert, idx) => (
<li key={idx}>
<span className="font-serif font-normal text-[14px] leading-[19px] text-[#00293D]"> {cert.name}</span>
<p className="font-serif font-normal text-[14px] leading-[19px] text-[#00293D] opacity-50 ml-3">{cert.org}</p>
</li>
))}
</ul>
</div>
</div>
</div>
</div>
);
}

View File

@@ -1,17 +0,0 @@
'use client';
interface InfoCardProps {
title: string;
content: React.ReactNode;
icon: React.ReactNode;
}
export function InfoCard({ title, content, icon }: InfoCardProps) {
return (
<div className="bg-white rounded-[10px] shadow-[0px_10px_20px_0px_rgba(217,217,217,0.5)] p-6 text-center">
<div className="flex justify-center mb-3">{icon}</div>
{title && <h4 className="font-semibold text-[#00293D] text-[14px] leading-[19px] mb-3 font-serif">{title}</h4>}
<div className="text-[#00293d] text-sm font-serif">{content}</div>
</div>
);
}

View File

@@ -1,134 +0,0 @@
'use client';
import Image from 'next/image';
import Link from 'next/link';
import { Tag } from './Tag';
interface ProfileCardProps {
firstName: string;
lastName: string;
isVerified: boolean;
title: string;
location: string;
memberSince: string;
bio: string;
expertise: string[];
}
export function ProfileCard({
firstName,
lastName,
isVerified,
title,
location,
memberSince,
bio,
expertise,
}: ProfileCardProps) {
return (
<div className="bg-white rounded-[20px] p-4 lg:p-5">
{/* Name and Actions Row */}
<div className="flex flex-col lg:flex-row lg:items-start lg:justify-between gap-4 mb-4">
<div className="text-center lg:text-left">
<div className="flex items-center justify-center lg:justify-start gap-2 mb-1">
<h1 className="text-[18px] text-[#00293d] font-serif">
<span className="font-semibold">{firstName}</span>{' '}
<span className="font-normal">{lastName}</span>
</h1>
{isVerified && (
<>
<Image
src="/assets/icons/verified-icon.svg"
alt="Verified"
width={15}
height={14}
/>
<span className="text-[14px] text-[#638559] font-medium font-serif">
(Verified Expert)
</span>
</>
)}
<Link href="/agent/edit" className="p-1 hover:bg-gray-100 rounded transition-colors">
<Image
src="/assets/icons/edit-pencil-orange-icon.svg"
alt="Edit profile"
width={16}
height={16}
/>
</Link>
</div>
<p className="text-[#00293d] text-sm mb-2 font-fractul">{title}</p>
<div className="flex items-center justify-center lg:justify-start gap-4 text-sm text-[#00293d] font-fractul">
<span className="flex items-center gap-1">
<Image
src="/assets/icons/location-pin-icon.svg"
alt="Location"
width={21}
height={19}
/>
<span className="text-[14px] font-bold text-[#00293D] font-serif leading-[19px]">
{location}
</span>
</span>
<span className="flex items-center gap-1">
<Image
src="/assets/icons/calendar-icon.svg"
alt="Calendar"
width={23}
height={21}
/>
<span className="text-[13px] font-medium text-[#00293D] font-serif leading-[18px]">
Member Since {memberSince}
</span>
</span>
</div>
</div>
{/* Action Buttons */}
<div className="flex items-center justify-center lg:justify-start gap-3">
<Link
href="/agent/message"
className="flex items-center gap-2 px-4 py-1.5 bg-[#e58625] text-[#00293d] text-sm font-normal rounded-[15px] border border-[#00293d]/10 hover:bg-[#d47720] transition-colors font-serif cursor-pointer"
>
<Image
src="/assets/icons/message-dark-icon.svg"
alt="Message"
width={14}
height={13}
/>
Message
</Link>
<Link
href="/agent/network"
className="flex items-center gap-2 px-4 py-1.5 bg-[#e58625] text-[#00293d] text-sm font-normal rounded-[15px] border border-[#00293d]/10 hover:bg-[#d47720] transition-colors font-serif cursor-pointer"
>
<Image
src="/assets/icons/requests-dark-icon.svg"
alt="Requests"
width={14}
height={13}
/>
Requests
</Link>
</div>
</div>
{/* Bio */}
<p className="text-[14px] font-normal text-[#00293D] font-serif leading-[20px] mb-4 text-center lg:text-left">
{bio}
</p>
{/* Expertise Tags */}
<div>
<p className="text-[14px] font-bold text-[#00293D] font-fractul leading-[17px] mb-2 text-center lg:text-left">
Expertise:
</p>
<div className="flex flex-wrap gap-2 justify-center lg:justify-start">
{expertise.map((tag, idx) => (
<Tag key={idx}>{tag}</Tag>
))}
</div>
</div>
</div>
);
}

View File

@@ -1,81 +0,0 @@
'use client';
import Image from 'next/image';
interface ProfileSidebarProps {
profileImage: string;
email: string;
phone: string;
}
export function ProfileSidebar({ profileImage, email, phone }: ProfileSidebarProps) {
return (
<div className="w-full lg:w-[220px] flex-shrink-0 space-y-4 flex flex-col items-center lg:items-start">
{/* Profile Image */}
<div className="relative w-[200px] h-[200px]">
<div className="w-full h-full rounded-[15px] overflow-hidden border-4 border-[#5ba4a4]">
<Image
src={profileImage}
alt="Profile"
width={200}
height={200}
className="w-full h-full object-cover"
/>
</div>
{/* Gradient Overlay - subtle darkening at bottom */}
<div className="absolute inset-0 rounded-[15px] bg-gradient-to-t from-black/40 via-transparent to-transparent pointer-events-none" />
{/* Edit Icon - Center */}
<button className="absolute top-[calc(50%-18px)] left-[calc(50%-18px)] w-9 h-9 bg-white rounded-[10px] shadow-md flex items-center justify-center hover:bg-gray-50 transition-colors">
<Image
src="/assets/icons/edit-icon.svg"
alt="Edit profile"
width={20}
height={20}
/>
</button>
</div>
{/* Status Buttons */}
<div className="space-y-2">
<div className="flex items-center gap-2">
<div className="flex items-center gap-1.5">
<span className="w-2 h-2 bg-green-500 rounded-full" />
<span className="text-sm text-[#00293d]">Available.</span>
</div>
<button className="px-4 py-1.5 bg-[#5ba4a4] text-white text-sm rounded-full hover:bg-[#4a9393] transition-colors">
Connect
</button>
</div>
<div className="flex items-center gap-2">
<div className="flex items-center gap-1.5">
<span className="w-2 h-2 bg-white border border-gray-400 rounded-full" />
<span className="text-sm text-[#00293d]">Unavailable.</span>
</div>
<button className="px-4 py-1.5 bg-[#5ba4a4] text-white text-sm rounded-full hover:bg-[#4a9393] transition-colors">
Connect
</button>
</div>
</div>
{/* Contact Info */}
<div className="space-y-2 pt-2">
<div className="flex items-center gap-2 text-sm">
<span className="font-bold text-[#00293d]">Email:</span>
<span className="text-[#00293d]">{email}</span>
</div>
<div className="flex items-center gap-2 text-sm">
<span className="font-bold text-[#00293d]">Ph.No:</span>
<span className="text-[#00293d]">{phone}</span>
<button className="p-1 hover:bg-gray-100 rounded">
<Image
src="/assets/icons/edit-pencil-icon.svg"
alt="Edit phone"
width={16}
height={16}
/>
</button>
</div>
</div>
</div>
);
}

View File

@@ -1,36 +0,0 @@
'use client';
import Image from 'next/image';
interface SpecializationCardProps {
title: string;
items: string[];
icon: React.ReactNode;
}
export function SpecializationCard({ title, items, icon }: SpecializationCardProps) {
return (
<div className="bg-white rounded-[20px] p-6 text-center flex flex-col h-full border-[0.7px] border-[#E58625]">
<div className="flex justify-center mb-3">{icon}</div>
<h4 className="font-bold text-[#00293D] text-[14px] leading-[17px] mb-4 font-fractul">{title}</h4>
<div className="flex-1">
{items.map((item, idx) => (
<p key={idx} className="text-[#00293D] text-[14px] leading-[25px] font-normal font-serif text-center">
{item}
</p>
))}
</div>
<div className="mt-4 flex justify-center">
<button className="inline-flex items-center justify-center gap-1 h-[20px] w-[87px] border border-[#e58625] rounded-[20px] text-[#e58625] text-[10px] leading-[22px] font-bold font-serif hover:bg-[#e58625]/5 transition-colors">
Show More
<Image
src="/assets/icons/chevron-down-icon.svg"
alt="Show more"
width={10}
height={10}
/>
</button>
</div>
</div>
);
}

View File

@@ -1,91 +0,0 @@
'use client';
import Image from 'next/image';
import { SpecializationCard } from './SpecializationCard';
interface SpecializationData {
types: string[];
loanTypes: string[];
propertyTypes: string[];
hobbies: string[];
pricePoints: string[];
}
interface SpecializationSectionProps {
specialization: SpecializationData;
}
export function SpecializationSection({ specialization }: SpecializationSectionProps) {
return (
<div className="bg-[#e8e8e8] rounded-[20px] p-8">
<div className="text-center mb-8">
<h2 className="text-[20px] font-bold text-[#00293D] font-fractul leading-[24px]">Specialization</h2>
<p className="text-[14px] font-medium text-[#00293D] font-fractul leading-[17px]">Area Of Expertise and Focus</p>
</div>
<div className="flex flex-col lg:grid lg:grid-cols-3 gap-6 mb-6">
<SpecializationCard
title="Specialization"
items={specialization.types}
icon={
<Image
src="/assets/icons/home-icon.svg"
alt="Specialization"
width={24}
height={24}
/>
}
/>
<SpecializationCard
title="Loan Type"
items={specialization.loanTypes}
icon={
<Image
src="/assets/icons/wallet-icon.svg"
alt="Loan Type"
width={24}
height={24}
/>
}
/>
<SpecializationCard
title="Property Type"
items={specialization.propertyTypes}
icon={
<Image
src="/assets/icons/chart-icon.svg"
alt="Property Type"
width={24}
height={24}
/>
}
/>
</div>
<div className="flex flex-col lg:grid lg:grid-cols-2 gap-6 lg:max-w-2xl lg:mx-auto">
<SpecializationCard
title="Hobbies & Interests"
items={specialization.hobbies}
icon={
<Image
src="/assets/icons/heart-icon.svg"
alt="Hobbies"
width={24}
height={24}
/>
}
/>
<SpecializationCard
title="Price Point"
items={specialization.pricePoints}
icon={
<Image
src="/assets/icons/wallet-icon.svg"
alt="Price Point"
width={24}
height={24}
/>
}
/>
</div>
</div>
);
}

View File

@@ -1,23 +0,0 @@
'use client';
import Image from 'next/image';
interface StarRatingProps {
rating: number;
}
export function StarRating({ rating }: StarRatingProps) {
return (
<div className="flex gap-0.5">
{[1, 2, 3, 4, 5].map((star) => (
<Image
key={star}
src={star <= rating ? '/assets/icons/star-filled-icon.svg' : '/assets/icons/star-empty-icon.svg'}
alt={star <= rating ? 'Filled star' : 'Empty star'}
width={16}
height={16}
/>
))}
</div>
);
}

View File

@@ -1,30 +0,0 @@
'use client';
interface StatusButtonsProps {
isAvailable?: boolean;
}
export function StatusButtons({ isAvailable = true }: StatusButtonsProps) {
return (
<div className="w-full max-w-[354px] lg:max-w-none space-y-2">
<div className="flex items-center border border-[#00293d]/10 rounded-[15px] h-[50px] px-4">
<div className="flex items-center gap-2 flex-1">
<span className="w-3 h-3 bg-green-500 rounded-full" />
<span className="text-sm font-bold text-[#00293d] font-fractul">Available.</span>
</div>
<button className="px-5 py-1.5 bg-[#e58625] text-white text-sm rounded-[15px] hover:bg-[#d47720] transition-colors font-fractul">
Connect
</button>
</div>
<div className="flex items-center border border-[#00293d]/10 rounded-[15px] h-[50px] px-4">
<div className="flex items-center gap-2 flex-1">
<span className="w-3 h-3 bg-white border border-gray-400 rounded-full" />
<span className="text-sm font-bold text-[#00293d] font-fractul">Unavailable.</span>
</div>
<button className="px-5 py-1.5 bg-[#00293d] text-white text-sm rounded-[15px] hover:bg-[#001d2b] transition-colors font-fractul">
Connect
</button>
</div>
</div>
);
}

View File

@@ -1,22 +0,0 @@
'use client';
interface TagProps {
children: React.ReactNode;
variant?: 'default' | 'light' | 'orange';
}
export function Tag({ children, variant = 'default' }: TagProps) {
return (
<span
className={`inline-flex items-center justify-center px-3 h-[28px] rounded-[15px] text-[14px] font-medium font-serif ${
variant === 'light'
? 'border-[0.7px] border-[#00293d] text-[#00293d] bg-transparent'
: variant === 'orange'
? 'border-[0.7px] border-[#e58625] text-[#e58625] bg-transparent'
: 'border-[0.7px] border-[#00293d] text-[#00293d] bg-transparent'
}`}
>
{children}
</span>
);
}

View File

@@ -1,34 +0,0 @@
'use client';
import Image from 'next/image';
import { StarRating } from './StarRating';
interface TestimonialCardProps {
text: string;
author: string;
role: string;
rating: number;
}
export function TestimonialCard({ text, author, role, rating }: TestimonialCardProps) {
return (
<div className="p-4 border border-[#00293D]/10 rounded-[15px]">
<div className="mb-4">
<Image
src="/assets/icons/quote-icon.svg"
alt="Quote"
width={23}
height={13}
className="mb-3"
/>
<p className="font-medium text-[#00293D] text-[14px] leading-[17px] font-fractul">I have been working with Lorem .</p>
</div>
<p className="text-[#00293D] text-[14px] leading-[19px] font-normal font-serif mb-4">{text}</p>
<div className="pt-4 border-t border-gray-100 space-y-2">
<StarRating rating={rating} />
<p className="font-normal text-[#00293D] text-[14px] leading-[17px] font-fractul">{author}</p>
<p className="font-fractul font-medium text-[14px] leading-[17px] text-[#00293D]">{role}</p>
</div>
</div>
);
}

View File

@@ -1,38 +0,0 @@
'use client';
import { TestimonialCard } from './TestimonialCard';
interface Testimonial {
id: number;
text: string;
author: string;
role: string;
rating: number;
}
interface TestimonialsSectionProps {
testimonials: Testimonial[];
}
export function TestimonialsSection({ testimonials }: TestimonialsSectionProps) {
return (
<div className="bg-white rounded-[20px] p-8">
<h2 className="text-[20px] font-bold text-[#00293D] font-fractul leading-[24px] text-center mb-4">Testimonials</h2>
<div className="w-full h-px bg-[#00293D]/20 mb-4" />
<p className="font-serif font-normal text-[14px] leading-[19px] text-[#00293D] text-center mb-8">
Clients rate our real estate services 4.9 out of 5 on average, based on recent client reviews.
</p>
<div className="flex flex-col lg:grid lg:grid-cols-3 gap-6">
{testimonials.map((testimonial) => (
<TestimonialCard
key={testimonial.id}
text={testimonial.text}
author={testimonial.author}
role={testimonial.role}
rating={testimonial.rating}
/>
))}
</div>
</div>
);
}

View File

@@ -1,13 +0,0 @@
// Dashboard Components
export { StarRating } from './StarRating';
export { Tag } from './Tag';
export { InfoCard } from './InfoCard';
export { SpecializationCard } from './SpecializationCard';
export { ProfileSidebar } from './ProfileSidebar';
export { ProfileCard } from './ProfileCard';
export { ExperienceSection } from './ExperienceSection';
export { SpecializationSection } from './SpecializationSection';
export { TestimonialCard } from './TestimonialCard';
export { TestimonialsSection } from './TestimonialsSection';
export { StatusButtons } from './StatusButtons';
export { ContactInfo } from './ContactInfo';

View File

@@ -1,65 +0,0 @@
'use client';
import Image from 'next/image';
interface ConversationItemProps {
id: string;
name: string;
avatar: string;
lastMessage: string;
date: string;
isOnline?: boolean;
isSelected?: boolean;
onClick?: () => void;
}
export function ConversationItem({
id,
name,
avatar,
lastMessage,
date,
isOnline = false,
isSelected = false,
onClick,
}: ConversationItemProps) {
return (
<div
onClick={onClick}
className={`flex items-start gap-3 p-4 border border-[#00293d]/10 rounded-[15px] cursor-pointer transition-colors ${
isSelected ? 'bg-[#00293d]/5' : 'hover:bg-[#00293d]/5'
}`}
>
{/* Avatar with online indicator */}
<div className="relative flex-shrink-0">
<div className="w-[70px] h-[70px] rounded-full overflow-hidden">
<Image
src={avatar}
alt={name}
width={70}
height={70}
className="w-full h-full object-cover"
/>
</div>
{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">
{name}
</p>
<p className="font-serif font-normal text-[14px] leading-[19px] text-[#00293D] line-clamp-2">
{lastMessage}
</p>
</div>
{/* Date */}
<p className="font-serif font-normal text-[14px] leading-[19px] text-[#00293D] flex-shrink-0">
{date}
</p>
</div>
);
}

View File

@@ -1,41 +0,0 @@
'use client';
import Image from 'next/image';
import { ConversationItem } from './ConversationItem';
interface Conversation {
id: string;
name: string;
avatar: string;
lastMessage: string;
date: string;
isOnline?: boolean;
}
interface ConversationListProps {
conversations: Conversation[];
selectedId: string | null;
onSelect: (id: string) => void;
}
export function ConversationList({
conversations,
selectedId,
onSelect,
}: ConversationListProps) {
return (
<div className="border border-[#00293d]/10 rounded-[15px] bg-white h-full flex flex-col">
{/* Scrollable conversation list */}
<div className="flex-1 overflow-y-auto p-2 space-y-2">
{conversations.map((conversation) => (
<ConversationItem
key={conversation.id}
{...conversation}
isSelected={selectedId === conversation.id}
onClick={() => onSelect(conversation.id)}
/>
))}
</div>
</div>
);
}

View File

@@ -1,4 +0,0 @@
export { ConversationItem } from './ConversationItem';
export { ConversationList } from './ConversationList';
export { ChatHeader } from './ChatHeader';
export { MessageInput } from './MessageInput';

View File

@@ -1,419 +1,11 @@
'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())
);
import { MessagingPage } from '@/components/message';
export default function AgentMessagePage() {
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 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>
</>
<div className="max-w-7xl mx-auto px-4 lg:px-8 py-6">
<MessagingPage />
</div>
);
}

View File

@@ -0,0 +1,11 @@
'use client';
import { MessagingPage } from '@/components/message';
export default function UserMessagePage() {
return (
<div className="max-w-7xl mx-auto px-4 lg:px-8 py-6">
<MessagingPage />
</div>
);
}

View File

@@ -129,6 +129,7 @@ export default function AgentProfileView() {
bio={agentData.bio}
expertise={agentData.expertise}
showEditButton={false}
messageHref="/user/message"
/>
{/* Experience Section */}

217
src/app/faq/page.tsx Normal file
View File

@@ -0,0 +1,217 @@
'use client';
import Image from 'next/image';
import { useState } from 'react';
import { CommonHeader } from '@/components/layout/CommonHeader';
import { Footer } from '@/components/layout/Footer';
const categories = [
{ id: 'all', label: 'All Questions' },
{ id: 'agent', label: 'Agent Resources' },
{ id: 'buying', label: 'Buying a Home' },
{ id: 'selling', label: 'Selling a Home' },
{ id: 'platform', label: 'Platform & Account' },
];
const faqs = [
{
id: 1,
icon: '/assets/icons/home-icon.svg',
question: 'How do I update my listing status?',
answer: 'To update your listing status, log in to your Agent Dashboard. Locate the property in your "Active Listings" tab, click the three-dot menu on the right, and select "Update Status." You can change it to Pending, Sold, or Temporarily Off Market. Changes are reflected immediately.',
category: 'agent',
},
{
id: 2,
icon: '/assets/icons/wallet-icon.svg',
question: 'What is the pre-approval process?',
answer: 'The pre-approval process involves submitting your financial documents to a lender who will review your credit history, income, assets, and debts. Once approved, you\'ll receive a pre-approval letter stating how much you can borrow, which strengthens your position when making offers on properties.',
category: 'buying',
},
{
id: 3,
icon: '/assets/icons/calendar-orange-icon.svg',
question: 'How do I schedule an open house?',
answer: 'To schedule an open house, navigate to your Agent Dashboard and select the property you want to showcase. Click on "Schedule Open House" and choose your preferred date and time. You can add details about the event and it will be automatically listed on the property page for potential buyers to see.',
category: 'agent',
},
{
id: 4,
icon: '/assets/icons/reset-icon.svg',
question: 'How do I reset my password?',
answer: 'To reset your password, click on the "Login" button and then select "Forgot Password." Enter your registered email address and we\'ll send you a password reset link. Follow the link to create a new password. For security, the link expires after 24 hours.',
category: 'platform',
},
{
id: 5,
icon: '/assets/icons/contact-icon.svg',
question: 'How do I contact a listing agent directly?',
answer: 'You can contact a listing agent by visiting the property page and clicking the "Contact Agent" button. You\'ll be able to send a message directly to the agent through our secure messaging system. Alternatively, you can call the agent using the phone number displayed on their profile.',
category: 'buying',
},
{
id: 6,
icon: '/assets/icons/caution-icon.svg',
question: 'What if I find inaccurate data on a listing?',
answer: 'If you find inaccurate information on a listing, please use the "Report Issue" button on the property page. Describe the inaccuracy and our team will review and correct it within 24-48 hours. You can also contact the listing agent directly to request corrections.',
category: 'platform',
},
];
export default function FAQPage() {
const [activeCategory, setActiveCategory] = useState('all');
const [expandedFaq, setExpandedFaq] = useState<number | null>(1);
const filteredFaqs = activeCategory === 'all'
? faqs
: faqs.filter(faq => faq.category === activeCategory);
const toggleFaq = (id: number) => {
setExpandedFaq(expandedFaq === id ? null : id);
};
return (
<div className="min-h-screen bg-white flex flex-col">
{/* Header */}
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6 w-full">
<CommonHeader />
</div>
{/* Main Content */}
<main className="flex-1 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pb-8 w-full">
<div className="flex flex-col lg:flex-row gap-8">
{/* Categories Sidebar */}
<div className="lg:w-[280px] flex-shrink-0">
<div className="border border-[#00293d]/10 rounded-[15px] overflow-hidden shadow-sm">
{/* Sidebar Header */}
<div className="p-4 border-b border-[#00293d]/10">
<h2 className="font-fractul font-bold text-[20px] text-[#00293d]">
Categories
</h2>
</div>
{/* Category Items */}
<div className="py-2">
{categories.map((category) => (
<button
key={category.id}
onClick={() => setActiveCategory(category.id)}
className={`w-full px-6 py-3 flex items-center justify-between text-left transition-colors ${
activeCategory === category.id
? 'bg-[#e58625] text-white'
: 'text-[#00293d] hover:bg-gray-50'
}`}
>
<span className={`${activeCategory === category.id ? 'font-fractul font-semibold' : 'font-serif'} text-[14px]`}>
{category.label}
</span>
{activeCategory === category.id && (
<Image
src="/assets/icons/chevron-right-icon.svg"
alt=""
width={12}
height={12}
className="brightness-0 invert"
/>
)}
</button>
))}
</div>
</div>
</div>
{/* FAQ Content */}
<div className="flex-1">
{/* Title */}
<h1 className="font-fractul font-bold text-[24px] text-[#00293d] mb-6">
Frequently Asked Questions ?
</h1>
{/* FAQ Accordion */}
<div className="space-y-4">
{filteredFaqs.map((faq) => (
<div
key={faq.id}
className={`border border-[#00293d]/10 rounded-[15px] overflow-hidden transition-shadow ${
expandedFaq === faq.id ? 'shadow-md' : ''
}`}
>
<button
onClick={() => toggleFaq(faq.id)}
className="w-full px-6 py-5 flex items-center gap-4 text-left"
>
<Image
src={faq.icon}
alt=""
width={28}
height={28}
/>
<span className="flex-1 font-fractul font-semibold text-[18px] text-[#00293d]">
{faq.question}
</span>
<Image
src="/assets/icons/chevron-down-icon.svg"
alt=""
width={16}
height={16}
className={`transition-transform duration-200 ${
expandedFaq === faq.id ? 'rotate-180' : ''
}`}
/>
</button>
{expandedFaq === faq.id && (
<div className="px-6 pb-5">
<p className="font-serif text-[16px] text-[#00293d] leading-relaxed pl-11">
{faq.answer}
</p>
</div>
)}
</div>
))}
</div>
{/* Still Need Help Section */}
<div className="mt-8 border border-[#00293d]/10 rounded-[15px] p-8">
<div className="flex flex-col lg:flex-row items-start lg:items-center justify-between gap-6">
<div>
<h3 className="font-fractul font-bold text-[20px] text-[#00293d] mb-2">
Still Need Help ?
</h3>
<p className="font-serif text-[16px] text-[#00293d]">
Our Support Team Is Available<br />
Mon-Fri, 9am-6pm IST
</p>
</div>
<div className="flex flex-col gap-3">
<button className="flex items-center justify-center gap-2 w-[174px] h-[51px] border border-[#00293d] rounded-[7px] font-fractul text-[16px] text-[#00293d] hover:bg-gray-50 transition-colors">
<Image
src="/assets/icons/chat-icon.svg"
alt=""
width={20}
height={20}
/>
Start Live Chat
</button>
<button className="flex items-center justify-center gap-2 w-[174px] h-[51px] border border-[#00293d] rounded-[7px] font-fractul text-[16px] text-[#00293d] hover:bg-gray-50 transition-colors">
<Image
src="/assets/icons/email-icon.svg"
alt=""
width={20}
height={20}
/>
Email Support
</button>
</div>
</div>
</div>
</div>
</div>
</main>
{/* Footer */}
<Footer />
</div>
);
}

View File

@@ -0,0 +1,442 @@
'use client';
import { useState, useRef, useEffect } from 'react';
import Image from 'next/image';
import { ChatHeader } from './ChatHeader';
import { MessageInput } from './MessageInput';
// 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);
}
`;
// Types
interface Conversation {
id: string;
name: string;
avatar: string;
lastMessage: string;
date: string;
isOnline: boolean;
}
interface Message {
id: string;
senderId: string;
text: string;
timestamp: string;
isOwn: boolean;
}
interface SelectedUser {
name: string;
role: string;
lastSeen: string;
avatar: string;
isOnline: boolean;
pronouns?: string;
expertise?: string[];
}
interface MessagingPageProps {
conversations?: Conversation[];
messages?: Message[];
selectedUser?: SelectedUser;
}
// Mock data for conversations
const defaultConversations: Conversation[] = [
{
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,
},
];
// Mock data for selected user
const defaultSelectedUser: SelectedUser = {
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 defaultMessages: Message[] = [
{
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 function MessagingPage({
conversations = defaultConversations,
messages = defaultMessages,
selectedUser = defaultSelectedUser,
}: MessagingPageProps) {
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 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 {...selectedUser} />
{/* 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>
</>
);
}

View File

@@ -0,0 +1,4 @@
// Shared Message Components
export { ChatHeader } from './ChatHeader';
export { MessageInput } from './MessageInput';
export { MessagingPage } from './MessagingPage';