feat: Add user profile page and new modular profile components, integrating them into the agent dashboard.

This commit is contained in:
pradeepkumar
2026-01-20 12:17:47 +05:30
parent c8647fbf5c
commit a4dc8bb0fa
14 changed files with 826 additions and 2 deletions

View File

@@ -0,0 +1,67 @@
'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

@@ -0,0 +1,83 @@
'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

@@ -0,0 +1,17 @@
'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

@@ -0,0 +1,172 @@
'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[];
showEditButton?: boolean;
editHref?: string;
messageHref?: string;
requestsHref?: string;
}
export function ProfileCard({
firstName,
lastName,
isVerified,
title,
location,
memberSince,
bio,
expertise,
showEditButton = false,
editHref = '/agent/edit',
messageHref,
requestsHref,
}: 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>
</>
)}
{showEditButton && (
<Link href={editHref} 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">
{messageHref ? (
<Link
href={messageHref}
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>
) : (
<button
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
</button>
)}
{requestsHref ? (
<Link
href={requestsHref}
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="Connect"
width={14}
height={13}
/>
{showEditButton ? 'Requests' : 'Connect'}
</Link>
) : (
<button
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="Connect"
width={14}
height={13}
/>
{showEditButton ? 'Requests' : 'Connect'}
</button>
)}
</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

@@ -0,0 +1,36 @@
'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

@@ -0,0 +1,91 @@
'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

@@ -0,0 +1,23 @@
'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

@@ -0,0 +1,30 @@
'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

@@ -0,0 +1,22 @@
'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

@@ -0,0 +1,34 @@
'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

@@ -0,0 +1,38 @@
'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

@@ -0,0 +1,12 @@
// Shared Profile Components
export { StarRating } from './StarRating';
export { Tag } from './Tag';
export { InfoCard } from './InfoCard';
export { SpecializationCard } from './SpecializationCard';
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';