feat: Implement useImageStatus hook and enhance image loading status handling across various components to address cached images and loading timeouts.
This commit is contained in:
@@ -1,14 +1,10 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import Link from 'next/link';
|
||||
import Image from 'next/image';
|
||||
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { agentsService } from '@/services/agents.service';
|
||||
import { usersService } from '@/services/users.service';
|
||||
import { uploadService } from '@/services/upload.service';
|
||||
import { notificationsApiService } from '@/services/notifications-api.service';
|
||||
import { useHeaderData } from '@/components/providers/header-provider';
|
||||
|
||||
const navLinks = [
|
||||
{ label: 'Education', href: '/education' },
|
||||
@@ -18,16 +14,13 @@ const navLinks = [
|
||||
|
||||
export function CommonHeader() {
|
||||
const { data: session } = useSession();
|
||||
const { profileImage, profileName, avatarLoaded, setAvatarLoaded, notificationCount } = useHeaderData();
|
||||
const [showProfileMenu, setShowProfileMenu] = useState(false);
|
||||
const [showGuestMenu, setShowGuestMenu] = useState(false);
|
||||
const [showMobileMenu, setShowMobileMenu] = useState(false);
|
||||
const profileMenuRef = useRef<HTMLDivElement>(null);
|
||||
const guestMenuRef = useRef<HTMLDivElement>(null);
|
||||
const mobileMenuRef = useRef<HTMLDivElement>(null);
|
||||
const [profileImage, setProfileImage] = useState<string | null>(null);
|
||||
const [profileName, setProfileName] = useState<string | null>(null);
|
||||
const [avatarLoaded, setAvatarLoaded] = useState(false);
|
||||
const [notificationCount, setNotificationCount] = useState(0);
|
||||
|
||||
// Close dropdowns when clicking outside
|
||||
useEffect(() => {
|
||||
@@ -52,86 +45,6 @@ export function CommonHeader() {
|
||||
};
|
||||
}, [showProfileMenu, showGuestMenu, showMobileMenu]);
|
||||
|
||||
// Fetch profile data (image and name) from backend based on user role
|
||||
const fetchProfileData = useCallback(async () => {
|
||||
try {
|
||||
const role = (session?.user as any)?.role;
|
||||
let avatar: string | null = null;
|
||||
let name: string | null = null;
|
||||
|
||||
if (role === 'AGENT') {
|
||||
const profile = await agentsService.getMyProfile();
|
||||
avatar = profile.avatar;
|
||||
name = profile.firstName ? `${profile.firstName} ${profile.lastName || ''}`.trim() : null;
|
||||
} else if (role === 'USER') {
|
||||
const profile = await usersService.getMyProfile();
|
||||
avatar = profile.avatar;
|
||||
name = profile.firstName ? `${profile.firstName} ${profile.lastName || ''}`.trim() : null;
|
||||
}
|
||||
|
||||
if (name) {
|
||||
setProfileName(name);
|
||||
}
|
||||
|
||||
if (avatar) {
|
||||
setAvatarLoaded(false);
|
||||
try {
|
||||
const avatarUrl = await uploadService.getPresignedDownloadUrl(avatar);
|
||||
setProfileImage(avatarUrl);
|
||||
} catch {
|
||||
setProfileImage(avatar);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch profile data:', err);
|
||||
}
|
||||
}, [session]);
|
||||
|
||||
useEffect(() => {
|
||||
if (session) {
|
||||
fetchProfileData();
|
||||
}
|
||||
}, [session, fetchProfileData]);
|
||||
|
||||
// Fetch notification unread count
|
||||
const fetchUnreadCount = useCallback(async () => {
|
||||
try {
|
||||
const count = await notificationsApiService.getUnreadCount();
|
||||
setNotificationCount(count);
|
||||
} catch {
|
||||
// Silently fail - user may not be authenticated yet
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (session) {
|
||||
fetchUnreadCount();
|
||||
// Poll every 30 seconds for new notifications
|
||||
const interval = setInterval(fetchUnreadCount, 30000);
|
||||
// Also refresh when a foreground notification arrives
|
||||
const handleNotification = () => fetchUnreadCount();
|
||||
window.addEventListener('notification-received', handleNotification);
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
window.removeEventListener('notification-received', handleNotification);
|
||||
};
|
||||
}
|
||||
}, [session, fetchUnreadCount]);
|
||||
|
||||
// Listen for profile update events to refresh the header
|
||||
useEffect(() => {
|
||||
const handleProfileUpdate = () => {
|
||||
if (session) {
|
||||
fetchProfileData();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('profile-updated', handleProfileUpdate);
|
||||
return () => {
|
||||
window.removeEventListener('profile-updated', handleProfileUpdate);
|
||||
};
|
||||
}, [session, fetchProfileData]);
|
||||
|
||||
// Use fetched profile name, fallback to session name
|
||||
const userName = profileName || session?.user?.name;
|
||||
const userEmail = session?.user?.email;
|
||||
@@ -207,6 +120,7 @@ export function CommonHeader() {
|
||||
)}
|
||||
{userImage && (
|
||||
<img
|
||||
ref={(el) => { if (el?.complete && el.naturalWidth > 0) setAvatarLoaded(true); }}
|
||||
src={userImage}
|
||||
alt="Profile"
|
||||
className={`w-full h-full object-cover transition-opacity duration-300 ${avatarLoaded ? 'opacity-100' : 'opacity-0'}`}
|
||||
@@ -237,6 +151,7 @@ export function CommonHeader() {
|
||||
)}
|
||||
{userImage && (
|
||||
<img
|
||||
ref={(el) => { if (el?.complete && el.naturalWidth > 0) setAvatarLoaded(true); }}
|
||||
src={userImage}
|
||||
alt="Profile"
|
||||
className={`w-full h-full object-cover transition-opacity duration-300 ${avatarLoaded ? 'opacity-100' : 'opacity-0'}`}
|
||||
|
||||
Reference in New Issue
Block a user