feat: sync profile data with session and implement visibility-based refetching in ProfileSettingsForm
This commit is contained in:
@@ -87,83 +87,114 @@ export function ProfileSettingsForm({
|
|||||||
// Track if initial profile fetch is done to avoid re-showing loading spinner
|
// Track if initial profile fetch is done to avoid re-showing loading spinner
|
||||||
const hasFetchedRef = useRef(false);
|
const hasFetchedRef = useRef(false);
|
||||||
|
|
||||||
// Fetch profile data on mount based on user role
|
const fetchProfile = async () => {
|
||||||
useEffect(() => {
|
try {
|
||||||
const fetchProfile = async () => {
|
// Only show loading spinner on initial fetch, not on session-triggered re-fetches
|
||||||
try {
|
if (!hasFetchedRef.current) {
|
||||||
// Only show loading spinner on initial fetch, not on session-triggered re-fetches
|
setIsLoading(true);
|
||||||
if (!hasFetchedRef.current) {
|
|
||||||
setIsLoading(true);
|
|
||||||
}
|
|
||||||
const role = (session?.user as any)?.role;
|
|
||||||
|
|
||||||
if (role === 'AGENT') {
|
|
||||||
const profile = await agentsService.getMyProfile();
|
|
||||||
const profileData = {
|
|
||||||
firstName: profile.firstName || '',
|
|
||||||
lastName: profile.lastName || '',
|
|
||||||
career: profile.headline || profile.agentType?.name || 'Real Estate Agent',
|
|
||||||
email: profile.email || session?.user?.email || '',
|
|
||||||
phone: profile.phone || '',
|
|
||||||
};
|
|
||||||
setFormData(profileData);
|
|
||||||
setOriginalData(profileData); // Store original for cancel
|
|
||||||
|
|
||||||
if (profile.avatar) {
|
|
||||||
try {
|
|
||||||
const presignedUrl = await uploadService.getPresignedDownloadUrl(profile.avatar);
|
|
||||||
setAvatarUrl(presignedUrl);
|
|
||||||
} catch {
|
|
||||||
setAvatarUrl(profile.avatar);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (role === 'USER') {
|
|
||||||
const profile = await usersService.getMyProfile();
|
|
||||||
const profileData = {
|
|
||||||
firstName: profile.firstName || '',
|
|
||||||
lastName: profile.lastName || '',
|
|
||||||
career: '', // Users don't have career/agent type
|
|
||||||
email: profile.email || session?.user?.email || '',
|
|
||||||
phone: profile.phone || '',
|
|
||||||
};
|
|
||||||
setFormData(profileData);
|
|
||||||
setOriginalData(profileData); // Store original for cancel
|
|
||||||
|
|
||||||
if (profile.avatar) {
|
|
||||||
try {
|
|
||||||
const presignedUrl = await uploadService.getPresignedDownloadUrl(profile.avatar);
|
|
||||||
setAvatarUrl(presignedUrl);
|
|
||||||
} catch {
|
|
||||||
setAvatarUrl(profile.avatar);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Failed to fetch profile:', err);
|
|
||||||
// Use session data as fallback
|
|
||||||
if (session?.user) {
|
|
||||||
const nameParts = (session.user?.name || '').split(' ');
|
|
||||||
setFormData(prev => ({
|
|
||||||
...prev,
|
|
||||||
firstName: nameParts[0] || prev.firstName,
|
|
||||||
lastName: nameParts.slice(1).join(' ') || prev.lastName,
|
|
||||||
email: session.user?.email || prev.email,
|
|
||||||
}));
|
|
||||||
if (session.user.image) {
|
|
||||||
setAvatarUrl(session.user.image);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
hasFetchedRef.current = true;
|
|
||||||
}
|
}
|
||||||
};
|
const role = (session?.user as any)?.role;
|
||||||
|
|
||||||
|
if (role === 'AGENT') {
|
||||||
|
const profile = await agentsService.getMyProfile();
|
||||||
|
const profileData = {
|
||||||
|
firstName: profile.firstName || '',
|
||||||
|
lastName: profile.lastName || '',
|
||||||
|
career: profile.headline || profile.agentType?.name || 'Real Estate Agent',
|
||||||
|
email: profile.email || session?.user?.email || '',
|
||||||
|
phone: profile.phone || '',
|
||||||
|
};
|
||||||
|
setFormData(profileData);
|
||||||
|
setOriginalData(profileData);
|
||||||
|
|
||||||
|
// If backend email diverges from session (email change verified in another tab),
|
||||||
|
// refresh the next-auth JWT so header/other places also pick up the new address.
|
||||||
|
if (profile.email && session?.user?.email && profile.email !== session.user.email) {
|
||||||
|
try {
|
||||||
|
await updateSession({ user: { email: profile.email } });
|
||||||
|
} catch {
|
||||||
|
// updateSession is best-effort
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (profile.avatar) {
|
||||||
|
try {
|
||||||
|
const presignedUrl = await uploadService.getPresignedDownloadUrl(profile.avatar);
|
||||||
|
setAvatarUrl(presignedUrl);
|
||||||
|
} catch {
|
||||||
|
setAvatarUrl(profile.avatar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (role === 'USER') {
|
||||||
|
const profile = await usersService.getMyProfile();
|
||||||
|
const profileData = {
|
||||||
|
firstName: profile.firstName || '',
|
||||||
|
lastName: profile.lastName || '',
|
||||||
|
career: '',
|
||||||
|
email: profile.email || session?.user?.email || '',
|
||||||
|
phone: profile.phone || '',
|
||||||
|
};
|
||||||
|
setFormData(profileData);
|
||||||
|
setOriginalData(profileData);
|
||||||
|
|
||||||
|
if (profile.email && session?.user?.email && profile.email !== session.user.email) {
|
||||||
|
try {
|
||||||
|
await updateSession({ user: { email: profile.email } });
|
||||||
|
} catch {
|
||||||
|
// best-effort
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (profile.avatar) {
|
||||||
|
try {
|
||||||
|
const presignedUrl = await uploadService.getPresignedDownloadUrl(profile.avatar);
|
||||||
|
setAvatarUrl(presignedUrl);
|
||||||
|
} catch {
|
||||||
|
setAvatarUrl(profile.avatar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to fetch profile:', err);
|
||||||
|
if (session?.user) {
|
||||||
|
const nameParts = (session.user?.name || '').split(' ');
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
firstName: nameParts[0] || prev.firstName,
|
||||||
|
lastName: nameParts.slice(1).join(' ') || prev.lastName,
|
||||||
|
email: session.user?.email || prev.email,
|
||||||
|
}));
|
||||||
|
if (session.user.image) {
|
||||||
|
setAvatarUrl(session.user.image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
hasFetchedRef.current = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initial fetch on mount
|
||||||
|
useEffect(() => {
|
||||||
if (session) {
|
if (session) {
|
||||||
// Skip re-fetching on session changes after initial load (e.g. after updateSession in handleSave)
|
|
||||||
if (hasFetchedRef.current) return;
|
if (hasFetchedRef.current) return;
|
||||||
fetchProfile();
|
fetchProfile();
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [session]);
|
||||||
|
|
||||||
|
// Re-fetch when the tab becomes visible again (e.g. user verified email
|
||||||
|
// change in another tab and returned). Ensures email/phone/avatar stay in
|
||||||
|
// sync with the source of truth.
|
||||||
|
useEffect(() => {
|
||||||
|
const onVisible = () => {
|
||||||
|
if (document.visibilityState === 'visible' && session && hasFetchedRef.current) {
|
||||||
|
fetchProfile();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener('visibilitychange', onVisible);
|
||||||
|
return () => document.removeEventListener('visibilitychange', onVisible);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [session]);
|
}, [session]);
|
||||||
|
|
||||||
const handleChange = (field: string, value: string) => {
|
const handleChange = (field: string, value: string) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user