feat: add SEO metadata and profile schema
This commit is contained in:
@@ -1,20 +1,27 @@
|
||||
'use client';
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import Link from 'next/link';
|
||||
import Image from 'next/image';
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { useHeaderData } from '@/components/providers/header-provider';
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { useSession } from "next-auth/react";
|
||||
import { useHeaderData } from "@/components/providers/header-provider";
|
||||
|
||||
const navLinks = [
|
||||
{ label: 'Education', href: '/education' },
|
||||
{ label: 'About Us', href: '/about' },
|
||||
{ label: "FAQ's", href: '/faq' },
|
||||
{ label: "Education", href: "/education" },
|
||||
{ label: "About Us", href: "/about" },
|
||||
{ label: "FAQ's", href: "/faq" },
|
||||
];
|
||||
|
||||
export function CommonHeader() {
|
||||
const { data: session, status } = useSession();
|
||||
const { profileImage, profileName, avatarLoaded, setAvatarLoaded, notificationCount, messageCount } = useHeaderData();
|
||||
const {
|
||||
profileImage,
|
||||
profileName,
|
||||
avatarLoaded,
|
||||
setAvatarLoaded,
|
||||
notificationCount,
|
||||
messageCount,
|
||||
} = useHeaderData();
|
||||
const [showProfileMenu, setShowProfileMenu] = useState(false);
|
||||
const [showGuestMenu, setShowGuestMenu] = useState(false);
|
||||
const [showMobileMenu, setShowMobileMenu] = useState(false);
|
||||
@@ -26,23 +33,32 @@ export function CommonHeader() {
|
||||
// Close dropdowns when clicking outside
|
||||
useEffect(() => {
|
||||
function handleClickOutside(event: MouseEvent) {
|
||||
if (profileMenuRef.current && !profileMenuRef.current.contains(event.target as Node)) {
|
||||
if (
|
||||
profileMenuRef.current &&
|
||||
!profileMenuRef.current.contains(event.target as Node)
|
||||
) {
|
||||
setShowProfileMenu(false);
|
||||
}
|
||||
if (guestMenuRef.current && !guestMenuRef.current.contains(event.target as Node)) {
|
||||
if (
|
||||
guestMenuRef.current &&
|
||||
!guestMenuRef.current.contains(event.target as Node)
|
||||
) {
|
||||
setShowGuestMenu(false);
|
||||
}
|
||||
if (mobileMenuRef.current && !mobileMenuRef.current.contains(event.target as Node)) {
|
||||
if (
|
||||
mobileMenuRef.current &&
|
||||
!mobileMenuRef.current.contains(event.target as Node)
|
||||
) {
|
||||
setShowMobileMenu(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (showProfileMenu || showGuestMenu || showMobileMenu) {
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
};
|
||||
}, [showProfileMenu, showGuestMenu, showMobileMenu]);
|
||||
|
||||
@@ -50,14 +66,18 @@ export function CommonHeader() {
|
||||
const userName = profileName || session?.user?.name;
|
||||
const userEmail = session?.user?.email;
|
||||
const userRole = (session?.user as any)?.role;
|
||||
const showProfessional = userRole === "AGENT" || userRole === "LENDER";
|
||||
// Use fetched profile image, fallback to session image
|
||||
const userImage = profileImage || session?.user?.image;
|
||||
|
||||
// Logo destination — always lands on the user dashboard regardless of role.
|
||||
const dashboardLink = '/user/dashboard';
|
||||
const dashboardLink = "/user/dashboard";
|
||||
|
||||
return (
|
||||
<header className="bg-[#648188] rounded-[20px] px-4 md:px-8 relative" ref={mobileMenuRef}>
|
||||
<header
|
||||
className="bg-[#648188] rounded-[20px] px-4 md:px-8 relative"
|
||||
ref={mobileMenuRef}
|
||||
>
|
||||
<div className="flex justify-between items-center h-[60px] md:h-[70px]">
|
||||
{/* Logo */}
|
||||
<Link href={dashboardLink} className="flex-shrink-0 pt-[5px]">
|
||||
@@ -72,7 +92,27 @@ export function CommonHeader() {
|
||||
</Link>
|
||||
|
||||
{/* Navigation - Desktop only */}
|
||||
{/* <nav className="hidden md:flex items-center gap-8 ml-auto mr-8">
|
||||
{navLinks.map((link) => (
|
||||
<Link
|
||||
key={link.href}
|
||||
href={link.href}
|
||||
className="font-fractul font-bold text-[14px] leading-[18px] text-[#00293D] hover:text-[#e58625] transition-colors"
|
||||
>
|
||||
{link.label}
|
||||
</Link>
|
||||
))}
|
||||
</nav> */}
|
||||
<nav className="hidden md:flex items-center gap-8 ml-auto mr-8">
|
||||
{showProfessional && (
|
||||
<Link
|
||||
href="/user/profiles"
|
||||
className="font-fractul font-bold text-[14px] leading-[18px] text-[#00293D] hover:text-[#e58625] transition-colors"
|
||||
>
|
||||
Professional
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{navLinks.map((link) => (
|
||||
<Link
|
||||
key={link.href}
|
||||
@@ -86,12 +126,12 @@ export function CommonHeader() {
|
||||
|
||||
{/* Right Side Icons */}
|
||||
<div className="flex items-center gap-2 md:gap-4">
|
||||
{status === 'loading' ? (
|
||||
{status === "loading" ? (
|
||||
<div className="w-[35px] h-[35px] rounded-full shimmer-loading" />
|
||||
) : session ? (
|
||||
<>
|
||||
{/* Message Icon - User role only */}
|
||||
{userRole === 'USER' && (
|
||||
{userRole === "USER" && (
|
||||
<Link
|
||||
href="/user/message"
|
||||
className="relative w-6 h-6 flex items-center justify-center hover:opacity-80 transition-opacity cursor-pointer"
|
||||
@@ -105,7 +145,7 @@ export function CommonHeader() {
|
||||
{messageCount > 0 && (
|
||||
<span className="absolute -top-1.5 -right-2 min-w-[18px] h-[18px] bg-red-500 rounded-full flex items-center justify-center px-1">
|
||||
<span className="text-white text-[10px] font-bold leading-none">
|
||||
{messageCount > 99 ? '99+' : messageCount}
|
||||
{messageCount > 99 ? "99+" : messageCount}
|
||||
</span>
|
||||
</span>
|
||||
)}
|
||||
@@ -114,7 +154,11 @@ export function CommonHeader() {
|
||||
|
||||
{/* Notification Bell */}
|
||||
<Link
|
||||
href={userRole === 'AGENT' ? '/agent/notifications' : '/user/notifications'}
|
||||
href={
|
||||
userRole === "AGENT"
|
||||
? "/agent/notifications"
|
||||
: "/user/notifications"
|
||||
}
|
||||
className="relative w-6 h-6 flex items-center justify-center hover:opacity-80 transition-opacity cursor-pointer"
|
||||
>
|
||||
<Image
|
||||
@@ -126,7 +170,7 @@ export function CommonHeader() {
|
||||
{notificationCount > 0 && (
|
||||
<span className="absolute -top-1.5 -right-2 min-w-[18px] h-[18px] bg-red-500 rounded-full flex items-center justify-center px-1">
|
||||
<span className="text-white text-[10px] font-bold leading-none">
|
||||
{notificationCount > 99 ? '99+' : notificationCount}
|
||||
{notificationCount > 99 ? "99+" : notificationCount}
|
||||
</span>
|
||||
</span>
|
||||
)}
|
||||
@@ -143,17 +187,22 @@ export function CommonHeader() {
|
||||
{/* Shimmer while loading, initials only on error */}
|
||||
{avatarError || !userImage ? (
|
||||
<div className="absolute inset-0 rounded-full bg-[#c4d9d4] flex items-center justify-center">
|
||||
<span className="font-bold text-[#00293d] text-[14px]">{userName?.[0]?.toUpperCase() || '?'}</span>
|
||||
<span className="font-bold text-[#00293d] text-[14px]">
|
||||
{userName?.[0]?.toUpperCase() || "?"}
|
||||
</span>
|
||||
</div>
|
||||
) : !avatarLoaded ? (
|
||||
<div className="absolute inset-0 rounded-full shimmer-loading" />
|
||||
) : null}
|
||||
{userImage && !avatarError && (
|
||||
<img
|
||||
ref={(el) => { if (el?.complete && el.naturalWidth > 0) setAvatarLoaded(true); }}
|
||||
ref={(el) => {
|
||||
if (el?.complete && el.naturalWidth > 0)
|
||||
setAvatarLoaded(true);
|
||||
}}
|
||||
src={userImage}
|
||||
alt="Profile"
|
||||
className={`absolute inset-0 w-full h-full object-cover transition-opacity duration-300 ${avatarLoaded ? 'opacity-100' : 'opacity-0'}`}
|
||||
className={`absolute inset-0 w-full h-full object-cover transition-opacity duration-300 ${avatarLoaded ? "opacity-100" : "opacity-0"}`}
|
||||
onLoad={() => setAvatarLoaded(true)}
|
||||
onError={() => setAvatarError(true)}
|
||||
/>
|
||||
@@ -162,7 +211,7 @@ export function CommonHeader() {
|
||||
{/* Greeting Text - hidden on mobile */}
|
||||
<div className="hidden sm:flex flex-col text-left">
|
||||
<span className="font-bold text-[14px] text-[#e58625] leading-tight">
|
||||
Hi {userName?.split(' ')[0] || 'User'} !
|
||||
Hi {userName?.split(" ")[0] || "User"} !
|
||||
</span>
|
||||
<span className="font-bold text-[9px] text-[#00293d] leading-tight">
|
||||
Welcome back !
|
||||
@@ -179,14 +228,19 @@ export function CommonHeader() {
|
||||
<div className="w-[42px] h-[42px] rounded-full overflow-hidden flex-shrink-0 bg-gray-100 relative">
|
||||
{/* Initials base layer */}
|
||||
<div className="absolute inset-0 rounded-full bg-[#c4d9d4] flex items-center justify-center">
|
||||
<span className="font-bold text-[#00293d] text-[17px]">{userName?.[0]?.toUpperCase() || '?'}</span>
|
||||
<span className="font-bold text-[#00293d] text-[17px]">
|
||||
{userName?.[0]?.toUpperCase() || "?"}
|
||||
</span>
|
||||
</div>
|
||||
{userImage && !avatarError && (
|
||||
<img
|
||||
ref={(el) => { if (el?.complete && el.naturalWidth > 0) setAvatarLoaded(true); }}
|
||||
ref={(el) => {
|
||||
if (el?.complete && el.naturalWidth > 0)
|
||||
setAvatarLoaded(true);
|
||||
}}
|
||||
src={userImage}
|
||||
alt="Profile"
|
||||
className={`absolute inset-0 w-full h-full object-cover transition-opacity duration-300 ${avatarLoaded ? 'opacity-100' : 'opacity-0'}`}
|
||||
className={`absolute inset-0 w-full h-full object-cover transition-opacity duration-300 ${avatarLoaded ? "opacity-100" : "opacity-0"}`}
|
||||
onLoad={() => setAvatarLoaded(true)}
|
||||
onError={() => setAvatarError(true)}
|
||||
/>
|
||||
@@ -194,7 +248,7 @@ export function CommonHeader() {
|
||||
</div>
|
||||
<div className="flex flex-col min-w-0 overflow-hidden">
|
||||
<p className="font-fractul font-medium text-[16px] leading-[20px] text-black">
|
||||
{userName?.split(' ')[0] || 'User'}
|
||||
{userName?.split(" ")[0] || "User"}
|
||||
</p>
|
||||
<p className="font-serif text-[14px] leading-[18px] text-black/50 truncate">
|
||||
{userEmail}
|
||||
@@ -203,46 +257,84 @@ export function CommonHeader() {
|
||||
</div>
|
||||
|
||||
<Link
|
||||
href={userRole === 'AGENT' ? '/agent/settings' : '/user/settings'}
|
||||
href={
|
||||
userRole === "AGENT"
|
||||
? "/agent/settings"
|
||||
: "/user/settings"
|
||||
}
|
||||
className="flex items-center gap-3 px-4 py-3 border-b border-black/10 hover:bg-black/5 transition-colors"
|
||||
onClick={() => setShowProfileMenu(false)}
|
||||
>
|
||||
<Image src="/assets/icons/settings-icon.svg" alt="Settings" width={24} height={24} />
|
||||
<Image
|
||||
src="/assets/icons/settings-icon.svg"
|
||||
alt="Settings"
|
||||
width={24}
|
||||
height={24}
|
||||
/>
|
||||
<div className="flex flex-col">
|
||||
<p className="font-fractul text-[16px] leading-[18px] text-black">Account Settings</p>
|
||||
<p className="font-serif text-[14px] leading-[18px] text-black/50">Profile, Security</p>
|
||||
<p className="font-fractul text-[16px] leading-[18px] text-black">
|
||||
Account Settings
|
||||
</p>
|
||||
<p className="font-serif text-[14px] leading-[18px] text-black/50">
|
||||
Profile, Security
|
||||
</p>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
href={userRole === 'AGENT' ? '/agent/settings/notifications' : '/user/settings/notifications'}
|
||||
href={
|
||||
userRole === "AGENT"
|
||||
? "/agent/settings/notifications"
|
||||
: "/user/settings/notifications"
|
||||
}
|
||||
className="flex items-center gap-3 px-4 py-3 border-b border-black/10 hover:bg-black/5 transition-colors"
|
||||
onClick={() => setShowProfileMenu(false)}
|
||||
>
|
||||
<Image src="/assets/icons/notification-settings-icon.svg" alt="Notifications" width={24} height={24} />
|
||||
<p className="font-fractul text-[16px] leading-[18px] text-black">Notification Settings</p>
|
||||
<Image
|
||||
src="/assets/icons/notification-settings-icon.svg"
|
||||
alt="Notifications"
|
||||
width={24}
|
||||
height={24}
|
||||
/>
|
||||
<p className="font-fractul text-[16px] leading-[18px] text-black">
|
||||
Notification Settings
|
||||
</p>
|
||||
</Link>
|
||||
|
||||
{userRole === 'AGENT' && (
|
||||
{userRole === "AGENT" && (
|
||||
<Link
|
||||
href="/agent/dashboard"
|
||||
className="flex items-center gap-3 px-4 py-3 border-b border-black/10 hover:bg-black/5 transition-colors"
|
||||
onClick={() => setShowProfileMenu(false)}
|
||||
>
|
||||
<Image src="/assets/icons/edit-icon.svg" alt="Edit Page" width={24} height={24} />
|
||||
<p className="font-fractul text-[16px] leading-[18px] text-black">Edit Page</p>
|
||||
<Image
|
||||
src="/assets/icons/edit-icon.svg"
|
||||
alt="Edit Page"
|
||||
width={24}
|
||||
height={24}
|
||||
/>
|
||||
<p className="font-fractul text-[16px] leading-[18px] text-black">
|
||||
Edit Page
|
||||
</p>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
setShowProfileMenu(false);
|
||||
window.location.href = '/logout';
|
||||
window.location.href = "/logout";
|
||||
}}
|
||||
className="w-full flex items-center gap-3 px-4 py-3 hover:bg-black/5 transition-colors"
|
||||
>
|
||||
<Image src="/assets/icons/logout-icon.svg" alt="Log Out" width={24} height={24} />
|
||||
<p className="font-fractul font-bold text-[16px] leading-[18px] text-[#e58625]">Log Out</p>
|
||||
<Image
|
||||
src="/assets/icons/logout-icon.svg"
|
||||
alt="Log Out"
|
||||
width={24}
|
||||
height={24}
|
||||
/>
|
||||
<p className="font-fractul font-bold text-[16px] leading-[18px] text-[#e58625]">
|
||||
Log Out
|
||||
</p>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
@@ -291,10 +383,19 @@ export function CommonHeader() {
|
||||
className="flex items-center gap-3 px-4 py-3 border-b border-black/10 hover:bg-black/5 transition-colors"
|
||||
onClick={() => setShowGuestMenu(false)}
|
||||
>
|
||||
<Image src="/assets/icons/settings-icon.svg" alt="Settings" width={24} height={24} />
|
||||
<Image
|
||||
src="/assets/icons/settings-icon.svg"
|
||||
alt="Settings"
|
||||
width={24}
|
||||
height={24}
|
||||
/>
|
||||
<div className="flex flex-col">
|
||||
<p className="font-fractul text-[16px] leading-[18px] text-black">Account Settings</p>
|
||||
<p className="font-serif text-[14px] leading-[18px] text-black/50">Profile, Security</p>
|
||||
<p className="font-fractul text-[16px] leading-[18px] text-black">
|
||||
Account Settings
|
||||
</p>
|
||||
<p className="font-serif text-[14px] leading-[18px] text-black/50">
|
||||
Profile, Security
|
||||
</p>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
@@ -303,8 +404,15 @@ export function CommonHeader() {
|
||||
className="flex items-center gap-3 px-4 py-3 border-b border-black/10 hover:bg-black/5 transition-colors"
|
||||
onClick={() => setShowGuestMenu(false)}
|
||||
>
|
||||
<Image src="/assets/icons/notification-settings-icon.svg" alt="Notifications" width={24} height={24} />
|
||||
<p className="font-fractul text-[16px] leading-[18px] text-black">Notification Settings</p>
|
||||
<Image
|
||||
src="/assets/icons/notification-settings-icon.svg"
|
||||
alt="Notifications"
|
||||
width={24}
|
||||
height={24}
|
||||
/>
|
||||
<p className="font-fractul text-[16px] leading-[18px] text-black">
|
||||
Notification Settings
|
||||
</p>
|
||||
</Link>
|
||||
|
||||
<div className="flex gap-3 px-4 py-4">
|
||||
@@ -340,9 +448,19 @@ export function CommonHeader() {
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
{showMobileMenu ? (
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
) : (
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M4 6h16M4 12h16M4 18h16"
|
||||
/>
|
||||
)}
|
||||
</svg>
|
||||
</button>
|
||||
@@ -352,7 +470,29 @@ export function CommonHeader() {
|
||||
{/* Mobile Navigation Menu */}
|
||||
{showMobileMenu && (
|
||||
<div className="md:hidden border-t border-white/20 py-3 pb-4">
|
||||
{/* <nav className="flex flex-col gap-1">
|
||||
{navLinks.map((link) => (
|
||||
<Link
|
||||
key={link.href}
|
||||
href={link.href}
|
||||
onClick={() => setShowMobileMenu(false)}
|
||||
className="font-fractul font-bold text-[14px] text-[#00293D] hover:text-[#e58625] transition-colors px-2 py-2 rounded-[10px] hover:bg-white/10"
|
||||
>
|
||||
{link.label}
|
||||
</Link>
|
||||
))}
|
||||
</nav> */}
|
||||
<nav className="flex flex-col gap-1">
|
||||
{showProfessional && (
|
||||
<Link
|
||||
href="/user/profiles"
|
||||
onClick={() => setShowMobileMenu(false)}
|
||||
className="font-fractul font-bold text-[14px] text-[#00293D] hover:text-[#e58625] transition-colors px-2 py-2 rounded-[10px] hover:bg-white/10"
|
||||
>
|
||||
Professional
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{navLinks.map((link) => (
|
||||
<Link
|
||||
key={link.href}
|
||||
|
||||
Reference in New Issue
Block a user