Files
frontend/src/components/providers/header-provider.tsx

160 lines
5.4 KiB
TypeScript
Raw Normal View History

'use client';
import { createContext, useContext, useState, useEffect, useRef, useCallback } from 'react';
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 { messagesService } from '@/services/messages.service';
import { socketService } from '@/services';
interface HeaderData {
profileImage: string | null;
profileName: string | null;
profileTitle: string | null;
avatarLoaded: boolean;
setAvatarLoaded: (loaded: boolean) => void;
notificationCount: number;
messageCount: number;
}
const HeaderContext = createContext<HeaderData>({
profileImage: null,
profileName: null,
profileTitle: null,
avatarLoaded: false,
setAvatarLoaded: () => {},
notificationCount: 0,
messageCount: 0,
});
export function useHeaderData() {
return useContext(HeaderContext);
}
export function HeaderProvider({ children }: { children: React.ReactNode }) {
const { data: session, status } = useSession();
const [profileImage, setProfileImage] = useState<string | null>(null);
const [profileName, setProfileName] = useState<string | null>(null);
const [profileTitle, setProfileTitle] = useState<string | null>(null);
const [avatarLoaded, setAvatarLoaded] = useState(false);
const [notificationCount, setNotificationCount] = useState(0);
const [messageCount, setMessageCount] = useState(0);
const lastAvatarKeyRef = useRef<string | null>(null);
const hasFetchedRef = useRef(false);
const fetchProfileData = useCallback(async (forceRefresh = false) => {
try {
const role = (session?.user as any)?.role;
let avatar: string | null = null;
let name: string | null = null;
let title: string | null = null;
if (role === 'AGENT') {
const profile = await agentsService.getMyProfile();
avatar = profile.avatar;
name = profile.firstName ? `${profile.firstName} ${profile.lastName || ''}`.trim() : null;
title = profile.agentType?.name || 'Real Estate Agent';
} else if (role === 'USER') {
const profile = await usersService.getMyProfile();
avatar = profile.avatar;
name = profile.firstName ? `${profile.firstName} ${profile.lastName || ''}`.trim() : null;
title = 'Member';
}
if (name) setProfileName(name);
if (title) setProfileTitle(title);
if (avatar && (avatar !== lastAvatarKeyRef.current || forceRefresh)) {
lastAvatarKeyRef.current = 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]);
// Fetch once on auth
useEffect(() => {
if (status === 'authenticated' && session?.user && !hasFetchedRef.current) {
hasFetchedRef.current = true;
fetchProfileData();
}
if (status === 'unauthenticated') {
hasFetchedRef.current = false;
setProfileImage(null);
setProfileName(null);
setProfileTitle(null);
setAvatarLoaded(false);
lastAvatarKeyRef.current = null;
}
}, [status, session, fetchProfileData]);
// Listen for profile-updated events
useEffect(() => {
const handleProfileUpdate = () => {
lastAvatarKeyRef.current = null;
fetchProfileData(true);
};
window.addEventListener('profile-updated', handleProfileUpdate);
return () => window.removeEventListener('profile-updated', handleProfileUpdate);
}, [fetchProfileData]);
// Notification count polling + real-time socket updates
useEffect(() => {
if (status !== 'authenticated') return;
const fetchCount = async () => {
try {
const count = await notificationsApiService.getUnreadCount();
setNotificationCount(count);
} catch {
// Silently fail
}
};
const fetchMessageCount = async () => {
try {
const count = await messagesService.getUnreadCount();
setMessageCount(count);
} catch {
// Silently fail
}
};
fetchCount();
fetchMessageCount();
const interval = setInterval(fetchCount, 30000);
const messageInterval = setInterval(fetchMessageCount, 30000);
const handleNotification = () => fetchCount();
window.addEventListener('notification-received', handleNotification);
// Listen to socket events for immediate notification count refresh
const unsubConnectionReq = socketService.onConnectionRequest(() => fetchCount());
const unsubConnectionRes = socketService.onConnectionResponse(() => fetchCount());
const unsubNewMessage = socketService.onNewMessage(() => { fetchCount(); fetchMessageCount(); });
return () => {
clearInterval(interval);
clearInterval(messageInterval);
window.removeEventListener('notification-received', handleNotification);
unsubConnectionReq();
unsubConnectionRes();
unsubNewMessage();
};
}, [status]);
return (
<HeaderContext.Provider value={{ profileImage, profileName, profileTitle, avatarLoaded, setAvatarLoaded, notificationCount, messageCount }}>
{children}
</HeaderContext.Provider>
);
}