diff --git a/src/app/(agent)/agent/dashboard/page.tsx b/src/app/(agent)/agent/dashboard/page.tsx index 966ee3d..f345254 100644 --- a/src/app/(agent)/agent/dashboard/page.tsx +++ b/src/app/(agent)/agent/dashboard/page.tsx @@ -75,6 +75,7 @@ export default function AgentDashboard() { const [error, setError] = useState(null); const [isPhotoModalOpen, setIsPhotoModalOpen] = useState(false); const [imageError, setImageError] = useState(false); + const [imageLoaded, setImageLoaded] = useState(false); const [avatarUrl, setAvatarUrl] = useState(null); const [isAvailable, setIsAvailable] = useState(true); const [isTogglingAvailability, setIsTogglingAvailability] = useState(false); @@ -334,16 +335,21 @@ export default function AgentDashboard() { {/* Profile Image */}
- {/* Initials base layer */} -
- {agentProfile?.firstName?.[0]?.toUpperCase() || '?'} -
+ {/* Shimmer while loading, initials only on error */} + {imageError ? ( +
+ {agentProfile?.firstName?.[0]?.toUpperCase() || '?'} +
+ ) : !imageLoaded ? ( +
+ ) : null} {/* eslint-disable-next-line @next/next/no-img-element */} Profile { (e.target as HTMLImageElement).style.display = 'none'; }} + className={`absolute inset-0 w-full h-full object-cover transition-opacity duration-300 ${imageLoaded ? 'opacity-100' : 'opacity-0'}`} + onLoad={() => setImageLoaded(true)} + onError={() => setImageError(true)} /> {/* Gradient Overlay */}
diff --git a/src/app/(agent)/agent/network/component/ConnectionCard.tsx b/src/app/(agent)/agent/network/component/ConnectionCard.tsx index 60653df..6b780c2 100644 --- a/src/app/(agent)/agent/network/component/ConnectionCard.tsx +++ b/src/app/(agent)/agent/network/component/ConnectionCard.tsx @@ -37,10 +37,14 @@ export function ConnectionCard({ {/* Avatar */}
- {/* Initials base layer */} -
- {name?.[0]?.toUpperCase() || '?'} -
+ {/* Shimmer while loading, initials only on error */} + {avatarError || isPlaceholder ? ( +
+ {name?.[0]?.toUpperCase() || '?'} +
+ ) : !avatarLoaded ? ( +
+ ) : null} {avatar && !isPlaceholder && !avatarError && ( { if (el?.complete && el.naturalWidth > 0) setAvatarLoaded(true); }} diff --git a/src/app/(agent)/agent/network/component/InvitationCard.tsx b/src/app/(agent)/agent/network/component/InvitationCard.tsx index ac9ad5a..232e1b4 100644 --- a/src/app/(agent)/agent/network/component/InvitationCard.tsx +++ b/src/app/(agent)/agent/network/component/InvitationCard.tsx @@ -51,10 +51,14 @@ export function InvitationCard({ {/* Avatar */}
- {/* Initials base layer */} -
- {name?.[0]?.toUpperCase() || '?'} -
+ {/* Shimmer while loading, initials only on error */} + {avatarError || isPlaceholder ? ( +
+ {name?.[0]?.toUpperCase() || '?'} +
+ ) : !avatarLoaded ? ( +
+ ) : null} {avatar && !isPlaceholder && !avatarError && ( { if (el?.complete && el.naturalWidth > 0) setAvatarLoaded(true); }} diff --git a/src/app/(user)/user/profile/[id]/page.tsx b/src/app/(user)/user/profile/[id]/page.tsx index 57e5a0c..8aa80ef 100644 --- a/src/app/(user)/user/profile/[id]/page.tsx +++ b/src/app/(user)/user/profile/[id]/page.tsx @@ -286,15 +286,16 @@ export default function AgentProfileView() { {/* Profile Image */}
- {/* Initials base layer */} -
- {agentProfile?.firstName?.[0]?.toUpperCase() || '?'} -
+ {/* Shimmer while loading, initials only on error/no image */} + {imageError || !getProfileImageUrl() ? ( +
+ {agentProfile?.firstName?.[0]?.toUpperCase() || '?'} +
+ ) : !imageLoaded ? ( +
+ ) : null} {getProfileImageUrl() && ( <> - {!imageLoaded && ( -
- )} {/* eslint-disable-next-line @next/next/no-img-element */} { if (el?.complete) { if (el.naturalWidth > 0) setImageLoaded(true); else setImageError(true); } }} diff --git a/src/components/home/TopProfessionals.tsx b/src/components/home/TopProfessionals.tsx index e9a5da0..73b80a8 100644 --- a/src/components/home/TopProfessionals.tsx +++ b/src/components/home/TopProfessionals.tsx @@ -39,10 +39,14 @@ function ProfessionalCard({
{/* Image */}
- {/* Initials base layer */} -
- {name?.[0]?.toUpperCase() || '?'} -
+ {/* Shimmer while loading, initials only on error */} + {imgError || isPlaceholder ? ( +
+ {name?.[0]?.toUpperCase() || '?'} +
+ ) : !imgLoaded ? ( +
+ ) : null} {!isPlaceholder && !imgError && ( {/* Avatar */}
- {/* Initials base layer */} -
- {userName?.[0]?.toUpperCase() || '?'} -
+ {/* Shimmer while loading, initials only on error */} + {avatarError || !userImage ? ( +
+ {userName?.[0]?.toUpperCase() || '?'} +
+ ) : !avatarLoaded ? ( +
+ ) : null} {userImage && !avatarError && ( { if (el?.complete && el.naturalWidth > 0) setAvatarLoaded(true); }} diff --git a/src/components/message/ChatHeader.tsx b/src/components/message/ChatHeader.tsx index d97d99f..556e3e3 100644 --- a/src/components/message/ChatHeader.tsx +++ b/src/components/message/ChatHeader.tsx @@ -152,10 +152,14 @@ export function ChatHeader({ {/* Avatar */}
- {/* Initials base layer */} -
- {name?.[0]?.toUpperCase() || '?'} -
+ {/* Shimmer while loading, initials only on error */} + {avatarError || isPlaceholderAvatar || !avatar ? ( +
+ {name?.[0]?.toUpperCase() || '?'} +
+ ) : !avatarLoaded ? ( +
+ ) : null} {avatar && !isPlaceholderAvatar && !avatarError && ( { if (el?.complete && el.naturalWidth > 0) setAvatarLoaded(true); }} diff --git a/src/components/message/MessagingPage.tsx b/src/components/message/MessagingPage.tsx index d47d304..53d7f8e 100644 --- a/src/components/message/MessagingPage.tsx +++ b/src/components/message/MessagingPage.tsx @@ -92,10 +92,14 @@ function AvatarWithPlaceholder({ src, alt, size, name }: { src: string; alt: str return ( <> - {/* Initials base layer - always rendered behind image */} -
- {initial} -
+ {/* Shimmer while loading, initials only on error/no image */} + {imgError || isPlaceholder ? ( +
+ {initial} +
+ ) : !loaded ? ( +
+ ) : null} {src && !isPlaceholder && !imgError && ( { if (el?.complete && el.naturalWidth > 0) setLoaded(true); }} diff --git a/src/components/settings/PrivacyForm.tsx b/src/components/settings/PrivacyForm.tsx index 443b22b..213ec8f 100644 --- a/src/components/settings/PrivacyForm.tsx +++ b/src/components/settings/PrivacyForm.tsx @@ -228,6 +228,16 @@ export function PrivacyForm({ onSave, onDeleteAccount }: PrivacyFormProps) { ); } + const role = (session?.user as any)?.role; + const isAgent = role === 'AGENT'; + + // Filter settings: Profile Visibility only for agents, Activity Status for both + const visibleSettings = privacySettings.filter((item) => { + if (item.id === 'profile_visibility') return isAgent; + if (item.id === 'activity_status') return true; + return false; + }); + return ( <> {/* Error Message */} @@ -245,118 +255,63 @@ export function PrivacyForm({ onSave, onDeleteAccount }: PrivacyFormProps) { )} {/* Privacy Settings */} -
-
-

- Privacy Settings -

-

- Control your privacy and visibility preferences -

-
+ {visibleSettings.length > 0 && ( +
+
+

+ Privacy Settings +

+

+ Control your privacy and visibility preferences +

+
-
- {privacySettings.map((item) => ( -
-
-
-

- {item.label} -

-

- {item.description} -

+
+ {visibleSettings.map((item) => ( +
+
+
+

+ {item.label} +

+

+ {item.description} +

+
+
-
-
- ))} -
-
- - {/* Data & Personalization */} -
-
-

- Data & Personalization -

-

- Manage how your data is used -

-
- -
-
-
-

- Share Analytics -

-

- Help improve our service by sharing usage data -

-
- handleDataSettingChange('shareAnalytics')} - /> + ))}
-
-
-

- Personalized Ads -

-

- See ads tailored to your interests -

-
- handleDataSettingChange('personalizedAds')} - /> -
- -
-
-

- Third-Party Data Sharing -

-

- Allow sharing data with third-party partners -

-
- handleDataSettingChange('thirdPartySharing')} - /> + {/* Save Button */} +
+
-
- - {/* Danger Zone */} -
-
-

- Danger Zone -

-

- Irreversible and destructive actions -

-
+ )} + {/* Delete Account */} +

@@ -375,17 +330,6 @@ export function PrivacyForm({ onSave, onDeleteAccount }: PrivacyFormProps) {

- - {/* Save Button */} -
- -
); } diff --git a/src/components/settings/SettingsSidebar.tsx b/src/components/settings/SettingsSidebar.tsx index ed952b7..bca3cc9 100644 --- a/src/components/settings/SettingsSidebar.tsx +++ b/src/components/settings/SettingsSidebar.tsx @@ -22,6 +22,7 @@ export function SettingsSidebar({ const pathname = usePathname(); const { profileImage, profileName, profileTitle, avatarLoaded, setAvatarLoaded } = useHeaderData(); const [localAvatarLoaded, setLocalAvatarLoaded] = useState(false); + const [avatarError, setAvatarError] = useState(false); // Use header context's avatarLoaded or local state const isAvatarLoaded = avatarLoaded || localAvatarLoaded; @@ -71,26 +72,40 @@ export function SettingsSidebar({
- {(!profileImage || !isAvatarLoaded) && ( + {/* Shimmer while loading, initials only on error */} + {avatarError || !profileImage ? ( +
+ {profileName?.[0]?.toUpperCase() || '?'} +
+ ) : !isAvatarLoaded ? (
- )} - {profileImage && ( + ) : null} + {profileImage && !avatarError && ( { if (el?.complete && el.naturalWidth > 0) { setAvatarLoaded(true); setLocalAvatarLoaded(true); } }} src={profileImage} alt={profileName || 'User'} className={`absolute inset-0 w-full h-full object-cover transition-opacity duration-300 ${isAvatarLoaded ? 'opacity-100' : 'opacity-0'}`} onLoad={() => { setAvatarLoaded(true); setLocalAvatarLoaded(true); }} + onError={() => setAvatarError(true)} /> )}
-

- {profileName || 'User'} -

-

- {profileTitle || (isAgent ? 'Real Estate Agent' : 'Member')} -

+ {profileName ? ( +

+ {profileName} +

+ ) : ( +
+ )} + {profileTitle ? ( +

+ {profileTitle} +

+ ) : ( +
+ )}