diff --git a/src/components/settings/ProfileSettingsForm.tsx b/src/components/settings/ProfileSettingsForm.tsx index de2f39f..3b6d010 100644 --- a/src/components/settings/ProfileSettingsForm.tsx +++ b/src/components/settings/ProfileSettingsForm.tsx @@ -87,83 +87,114 @@ export function ProfileSettingsForm({ // Track if initial profile fetch is done to avoid re-showing loading spinner const hasFetchedRef = useRef(false); - // Fetch profile data on mount based on user role - useEffect(() => { - const fetchProfile = async () => { - try { - // Only show loading spinner on initial fetch, not on session-triggered re-fetches - 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 fetchProfile = async () => { + try { + // Only show loading spinner on initial fetch, not on session-triggered re-fetches + 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); + + // 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) { - // Skip re-fetching on session changes after initial load (e.g. after updateSession in handleSave) if (hasFetchedRef.current) return; 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]); const handleChange = (field: string, value: string) => {