fix
This commit is contained in:
@@ -333,16 +333,17 @@ export default function AgentDashboard() {
|
|||||||
<div className="w-full lg:w-[280px] flex-shrink-0 space-y-4 flex flex-col items-center lg:items-start">
|
<div className="w-full lg:w-[280px] flex-shrink-0 space-y-4 flex flex-col items-center lg:items-start">
|
||||||
{/* Profile Image */}
|
{/* Profile Image */}
|
||||||
<div className="relative w-[200px] lg:w-[260px]">
|
<div className="relative w-[200px] lg:w-[260px]">
|
||||||
<div className="w-[200px] h-[200px] lg:w-[260px] lg:h-[260px] rounded-[15px] overflow-hidden">
|
<div className="w-[200px] h-[200px] lg:w-[260px] lg:h-[260px] rounded-[15px] overflow-hidden relative">
|
||||||
|
{/* Initials base layer */}
|
||||||
|
<div className="absolute inset-0 bg-[#c4d9d4] flex items-center justify-center rounded-[15px]">
|
||||||
|
<span className="font-bold text-[#00293d] text-[80px]">{agentProfile?.firstName?.[0]?.toUpperCase() || '?'}</span>
|
||||||
|
</div>
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
<img
|
<img
|
||||||
src={getProfileImageUrl()}
|
src={getProfileImageUrl()}
|
||||||
alt="Profile"
|
alt="Profile"
|
||||||
className="w-full h-full object-cover"
|
className="absolute inset-0 w-full h-full object-cover"
|
||||||
onError={(e) => {
|
onError={(e) => { (e.target as HTMLImageElement).style.display = 'none'; }}
|
||||||
const target = e.target as HTMLImageElement;
|
|
||||||
target.src = defaultImage;
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
{/* Gradient Overlay */}
|
{/* Gradient Overlay */}
|
||||||
<div className="absolute inset-0 bg-gradient-to-t from-black/50 via-black/20 to-transparent pointer-events-none" />
|
<div className="absolute inset-0 bg-gradient-to-t from-black/50 via-black/20 to-transparent pointer-events-none" />
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export function ConnectionCard({
|
|||||||
onMessage,
|
onMessage,
|
||||||
}: ConnectionCardProps) {
|
}: ConnectionCardProps) {
|
||||||
const [avatarLoaded, setAvatarLoaded] = useState(false);
|
const [avatarLoaded, setAvatarLoaded] = useState(false);
|
||||||
|
const [avatarError, setAvatarError] = useState(false);
|
||||||
const placeholder = '/assets/icons/user-placeholder-icon.svg';
|
const placeholder = '/assets/icons/user-placeholder-icon.svg';
|
||||||
const isPlaceholder = !avatar || avatar === placeholder;
|
const isPlaceholder = !avatar || avatar === placeholder;
|
||||||
|
|
||||||
@@ -36,20 +37,18 @@ export function ConnectionCard({
|
|||||||
{/* Avatar */}
|
{/* Avatar */}
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<div className="w-[80px] h-[80px] rounded-full overflow-hidden relative bg-[#e8e8e8]">
|
<div className="w-[80px] h-[80px] rounded-full overflow-hidden relative bg-[#e8e8e8]">
|
||||||
{isPlaceholder ? (
|
{/* Initials base layer */}
|
||||||
<div className="absolute inset-0 flex items-center justify-center bg-[#c4d9d4]">
|
<div className="absolute inset-0 rounded-full bg-[#c4d9d4] flex items-center justify-center">
|
||||||
<Image src={placeholder} alt={name} width={40} height={40} className="opacity-50" />
|
<span className="font-bold text-[#00293d] text-[32px]">{name?.[0]?.toUpperCase() || '?'}</span>
|
||||||
</div>
|
</div>
|
||||||
) : !avatarLoaded ? (
|
{avatar && !isPlaceholder && !avatarError && (
|
||||||
<div className="absolute inset-0 rounded-full shimmer-loading" />
|
|
||||||
) : null}
|
|
||||||
{avatar && !isPlaceholder && (
|
|
||||||
<img
|
<img
|
||||||
ref={(el) => { if (el?.complete && el.naturalWidth > 0) setAvatarLoaded(true); }}
|
ref={(el) => { if (el?.complete && el.naturalWidth > 0) setAvatarLoaded(true); }}
|
||||||
src={avatar}
|
src={avatar}
|
||||||
alt={name}
|
alt={name}
|
||||||
className={`absolute inset-0 w-full h-full object-cover rounded-full transition-opacity duration-300 ${avatarLoaded ? 'opacity-100' : 'opacity-0'}`}
|
className={`absolute inset-0 w-full h-full object-cover rounded-full transition-opacity duration-300 ${avatarLoaded ? 'opacity-100' : 'opacity-0'}`}
|
||||||
onLoad={() => setAvatarLoaded(true)}
|
onLoad={() => setAvatarLoaded(true)}
|
||||||
|
onError={() => setAvatarError(true)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ export function InvitationCard({
|
|||||||
onIgnore,
|
onIgnore,
|
||||||
}: InvitationCardProps) {
|
}: InvitationCardProps) {
|
||||||
const [avatarLoaded, setAvatarLoaded] = useState(false);
|
const [avatarLoaded, setAvatarLoaded] = useState(false);
|
||||||
|
const [avatarError, setAvatarError] = useState(false);
|
||||||
const placeholder = '/assets/icons/user-placeholder-icon.svg';
|
const placeholder = '/assets/icons/user-placeholder-icon.svg';
|
||||||
const isPlaceholder = !avatar || avatar === placeholder;
|
const isPlaceholder = !avatar || avatar === placeholder;
|
||||||
|
|
||||||
@@ -50,20 +51,18 @@ export function InvitationCard({
|
|||||||
{/* Avatar */}
|
{/* Avatar */}
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<div className="w-[80px] h-[80px] rounded-full overflow-hidden relative bg-[#e8e8e8]">
|
<div className="w-[80px] h-[80px] rounded-full overflow-hidden relative bg-[#e8e8e8]">
|
||||||
{isPlaceholder ? (
|
{/* Initials base layer */}
|
||||||
<div className="absolute inset-0 flex items-center justify-center bg-[#c4d9d4]">
|
<div className="absolute inset-0 rounded-full bg-[#c4d9d4] flex items-center justify-center">
|
||||||
<Image src={placeholder} alt={name} width={40} height={40} className="opacity-50" />
|
<span className="font-bold text-[#00293d] text-[32px]">{name?.[0]?.toUpperCase() || '?'}</span>
|
||||||
</div>
|
</div>
|
||||||
) : !avatarLoaded ? (
|
{avatar && !isPlaceholder && !avatarError && (
|
||||||
<div className="absolute inset-0 rounded-full shimmer-loading" />
|
|
||||||
) : null}
|
|
||||||
{avatar && !isPlaceholder && (
|
|
||||||
<img
|
<img
|
||||||
ref={(el) => { if (el?.complete && el.naturalWidth > 0) setAvatarLoaded(true); }}
|
ref={(el) => { if (el?.complete && el.naturalWidth > 0) setAvatarLoaded(true); }}
|
||||||
src={avatar}
|
src={avatar}
|
||||||
alt={name}
|
alt={name}
|
||||||
className={`absolute inset-0 w-full h-full object-cover rounded-full transition-opacity duration-300 ${avatarLoaded ? 'opacity-100' : 'opacity-0'}`}
|
className={`absolute inset-0 w-full h-full object-cover rounded-full transition-opacity duration-300 ${avatarLoaded ? 'opacity-100' : 'opacity-0'}`}
|
||||||
onLoad={() => setAvatarLoaded(true)}
|
onLoad={() => setAvatarLoaded(true)}
|
||||||
|
onError={() => setAvatarError(true)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -285,8 +285,12 @@ export default function AgentProfileView() {
|
|||||||
<div className="w-full lg:w-[280px] flex-shrink-0 space-y-4 flex flex-col items-center lg:items-start">
|
<div className="w-full lg:w-[280px] flex-shrink-0 space-y-4 flex flex-col items-center lg:items-start">
|
||||||
{/* Profile Image */}
|
{/* Profile Image */}
|
||||||
<div className="relative w-[200px] lg:w-[260px]">
|
<div className="relative w-[200px] lg:w-[260px]">
|
||||||
<div className="w-[200px] h-[200px] lg:w-[260px] lg:h-[260px] rounded-[15px] overflow-hidden bg-[#e8e8e8]">
|
<div className="w-[200px] h-[200px] lg:w-[260px] lg:h-[260px] rounded-[15px] overflow-hidden bg-[#e8e8e8] relative">
|
||||||
{getProfileImageUrl() ? (
|
{/* Initials base layer */}
|
||||||
|
<div className="absolute inset-0 bg-[#c4d9d4] flex items-center justify-center rounded-[15px]">
|
||||||
|
<span className="font-bold text-[#00293d] text-[80px]">{agentProfile?.firstName?.[0]?.toUpperCase() || '?'}</span>
|
||||||
|
</div>
|
||||||
|
{getProfileImageUrl() && (
|
||||||
<>
|
<>
|
||||||
{!imageLoaded && (
|
{!imageLoaded && (
|
||||||
<div className="absolute inset-0 shimmer-loading rounded-[15px]" />
|
<div className="absolute inset-0 shimmer-loading rounded-[15px]" />
|
||||||
@@ -296,21 +300,11 @@ export default function AgentProfileView() {
|
|||||||
ref={(el) => { if (el?.complete) { if (el.naturalWidth > 0) setImageLoaded(true); else setImageError(true); } }}
|
ref={(el) => { if (el?.complete) { if (el.naturalWidth > 0) setImageLoaded(true); else setImageError(true); } }}
|
||||||
src={getProfileImageUrl()!}
|
src={getProfileImageUrl()!}
|
||||||
alt="Profile"
|
alt="Profile"
|
||||||
className={`w-full h-full object-cover transition-opacity duration-300 ${imageLoaded ? 'opacity-100' : 'opacity-0'}`}
|
className={`absolute inset-0 w-full h-full object-cover transition-opacity duration-300 ${imageLoaded ? 'opacity-100' : 'opacity-0'}`}
|
||||||
onLoad={() => setImageLoaded(true)}
|
onLoad={() => setImageLoaded(true)}
|
||||||
onError={() => setImageError(true)}
|
onError={() => setImageError(true)}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
) : (
|
|
||||||
<div className="w-full h-full flex items-center justify-center">
|
|
||||||
<Image
|
|
||||||
src="/assets/icons/user-placeholder-icon.svg"
|
|
||||||
alt="Profile"
|
|
||||||
width={80}
|
|
||||||
height={80}
|
|
||||||
className="opacity-40"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
{/* Gradient Overlay */}
|
{/* Gradient Overlay */}
|
||||||
<div className="absolute inset-0 bg-gradient-to-t from-black/50 via-black/20 to-transparent pointer-events-none" />
|
<div className="absolute inset-0 bg-gradient-to-t from-black/50 via-black/20 to-transparent pointer-events-none" />
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ function ProfessionalCard({
|
|||||||
}: ProfessionalCardProps) {
|
}: ProfessionalCardProps) {
|
||||||
const starCount = rating ? Math.round(rating) : 0;
|
const starCount = rating ? Math.round(rating) : 0;
|
||||||
const [imgLoaded, setImgLoaded] = useState(false);
|
const [imgLoaded, setImgLoaded] = useState(false);
|
||||||
|
const [imgError, setImgError] = useState(false);
|
||||||
const placeholder = '/assets/icons/user-placeholder-icon.svg';
|
const placeholder = '/assets/icons/user-placeholder-icon.svg';
|
||||||
const isPlaceholder = imageUrl === placeholder;
|
const isPlaceholder = imageUrl === placeholder;
|
||||||
const INITIAL_TAG_COUNT = 4;
|
const INITIAL_TAG_COUNT = 4;
|
||||||
@@ -38,17 +39,20 @@ function ProfessionalCard({
|
|||||||
<div className="bg-white rounded-[15px] overflow-hidden shadow-[0px_4px_20px_rgba(0,0,0,0.08)] h-full flex flex-col">
|
<div className="bg-white rounded-[15px] overflow-hidden shadow-[0px_4px_20px_rgba(0,0,0,0.08)] h-full flex flex-col">
|
||||||
{/* Image */}
|
{/* Image */}
|
||||||
<div className="relative h-[226px] w-full overflow-hidden bg-gray-100">
|
<div className="relative h-[226px] w-full overflow-hidden bg-gray-100">
|
||||||
{!imgLoaded && !isPlaceholder && (
|
{/* Initials base layer */}
|
||||||
<div className="absolute inset-0 shimmer-loading" />
|
<div className="absolute inset-0 bg-[#c4d9d4] flex items-center justify-center">
|
||||||
|
<span className="font-bold text-[#00293d] text-[72px]">{name?.[0]?.toUpperCase() || '?'}</span>
|
||||||
|
</div>
|
||||||
|
{!isPlaceholder && !imgError && (
|
||||||
|
<Image
|
||||||
|
src={imageUrl}
|
||||||
|
alt={name}
|
||||||
|
fill
|
||||||
|
className={`object-cover transition-opacity duration-300 ${imgLoaded ? 'opacity-100' : 'opacity-0'}`}
|
||||||
|
onLoad={() => setImgLoaded(true)}
|
||||||
|
onError={() => setImgError(true)}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
<Image
|
|
||||||
src={imageUrl}
|
|
||||||
alt={name}
|
|
||||||
fill
|
|
||||||
className={`object-cover transition-opacity duration-300 ${isPlaceholder || imgLoaded ? 'opacity-100' : 'opacity-0'}`}
|
|
||||||
onLoad={() => setImgLoaded(true)}
|
|
||||||
onError={() => setImgLoaded(true)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export function CommonHeader() {
|
|||||||
const [showProfileMenu, setShowProfileMenu] = useState(false);
|
const [showProfileMenu, setShowProfileMenu] = useState(false);
|
||||||
const [showGuestMenu, setShowGuestMenu] = useState(false);
|
const [showGuestMenu, setShowGuestMenu] = useState(false);
|
||||||
const [showMobileMenu, setShowMobileMenu] = useState(false);
|
const [showMobileMenu, setShowMobileMenu] = useState(false);
|
||||||
|
const [avatarError, setAvatarError] = useState(false);
|
||||||
const profileMenuRef = useRef<HTMLDivElement>(null);
|
const profileMenuRef = useRef<HTMLDivElement>(null);
|
||||||
const guestMenuRef = useRef<HTMLDivElement>(null);
|
const guestMenuRef = useRef<HTMLDivElement>(null);
|
||||||
const mobileMenuRef = useRef<HTMLDivElement>(null);
|
const mobileMenuRef = useRef<HTMLDivElement>(null);
|
||||||
@@ -117,16 +118,18 @@ export function CommonHeader() {
|
|||||||
>
|
>
|
||||||
{/* Avatar */}
|
{/* Avatar */}
|
||||||
<div className="w-[32px] h-[32px] md:w-[35px] md:h-[35px] rounded-full overflow-hidden border-2 border-[#e58625] bg-gray-100 flex-shrink-0 relative">
|
<div className="w-[32px] h-[32px] md:w-[35px] md:h-[35px] rounded-full overflow-hidden border-2 border-[#e58625] bg-gray-100 flex-shrink-0 relative">
|
||||||
{(!userImage || !avatarLoaded) && (
|
{/* Initials base layer */}
|
||||||
<div className="absolute inset-0 rounded-full shimmer-loading" />
|
<div className="absolute inset-0 rounded-full bg-[#c4d9d4] flex items-center justify-center">
|
||||||
)}
|
<span className="font-bold text-[#00293d] text-[14px]">{userName?.[0]?.toUpperCase() || '?'}</span>
|
||||||
{userImage && (
|
</div>
|
||||||
|
{userImage && !avatarError && (
|
||||||
<img
|
<img
|
||||||
ref={(el) => { if (el?.complete && el.naturalWidth > 0) setAvatarLoaded(true); }}
|
ref={(el) => { if (el?.complete && el.naturalWidth > 0) setAvatarLoaded(true); }}
|
||||||
src={userImage}
|
src={userImage}
|
||||||
alt="Profile"
|
alt="Profile"
|
||||||
className={`w-full h-full object-cover transition-opacity duration-300 ${avatarLoaded ? 'opacity-100' : 'opacity-0'}`}
|
className={`absolute inset-0 w-full h-full object-cover transition-opacity duration-300 ${avatarLoaded ? 'opacity-100' : 'opacity-0'}`}
|
||||||
onLoad={() => setAvatarLoaded(true)}
|
onLoad={() => setAvatarLoaded(true)}
|
||||||
|
onError={() => setAvatarError(true)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -148,16 +151,18 @@ export function CommonHeader() {
|
|||||||
|
|
||||||
<div className="flex items-center gap-3 px-4 py-4 border-b border-black/10">
|
<div className="flex items-center gap-3 px-4 py-4 border-b border-black/10">
|
||||||
<div className="w-[42px] h-[42px] rounded-full overflow-hidden flex-shrink-0 bg-gray-100 relative">
|
<div className="w-[42px] h-[42px] rounded-full overflow-hidden flex-shrink-0 bg-gray-100 relative">
|
||||||
{(!userImage || !avatarLoaded) && (
|
{/* Initials base layer */}
|
||||||
<div className="absolute inset-0 rounded-full shimmer-loading" />
|
<div className="absolute inset-0 rounded-full bg-[#c4d9d4] flex items-center justify-center">
|
||||||
)}
|
<span className="font-bold text-[#00293d] text-[17px]">{userName?.[0]?.toUpperCase() || '?'}</span>
|
||||||
{userImage && (
|
</div>
|
||||||
|
{userImage && !avatarError && (
|
||||||
<img
|
<img
|
||||||
ref={(el) => { if (el?.complete && el.naturalWidth > 0) setAvatarLoaded(true); }}
|
ref={(el) => { if (el?.complete && el.naturalWidth > 0) setAvatarLoaded(true); }}
|
||||||
src={userImage}
|
src={userImage}
|
||||||
alt="Profile"
|
alt="Profile"
|
||||||
className={`w-full h-full object-cover transition-opacity duration-300 ${avatarLoaded ? 'opacity-100' : 'opacity-0'}`}
|
className={`absolute inset-0 w-full h-full object-cover transition-opacity duration-300 ${avatarLoaded ? 'opacity-100' : 'opacity-0'}`}
|
||||||
onLoad={() => setAvatarLoaded(true)}
|
onLoad={() => setAvatarLoaded(true)}
|
||||||
|
onError={() => setAvatarError(true)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ export function ChatHeader({
|
|||||||
}: ChatHeaderProps) {
|
}: ChatHeaderProps) {
|
||||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||||
const [avatarLoaded, setAvatarLoaded] = useState(false);
|
const [avatarLoaded, setAvatarLoaded] = useState(false);
|
||||||
|
const [avatarError, setAvatarError] = useState(false);
|
||||||
|
|
||||||
const placeholder = '/assets/icons/user-placeholder-icon.svg';
|
const placeholder = '/assets/icons/user-placeholder-icon.svg';
|
||||||
const isPlaceholderAvatar = !avatar || avatar === placeholder;
|
const isPlaceholderAvatar = !avatar || avatar === placeholder;
|
||||||
@@ -151,16 +152,18 @@ export function ChatHeader({
|
|||||||
{/* Avatar */}
|
{/* Avatar */}
|
||||||
<div className="relative flex-shrink-0">
|
<div className="relative flex-shrink-0">
|
||||||
<div className="w-[50px] h-[50px] rounded-full overflow-hidden border border-[#00293D]/20 relative">
|
<div className="w-[50px] h-[50px] rounded-full overflow-hidden border border-[#00293D]/20 relative">
|
||||||
{(!avatar || !avatarLoaded || isPlaceholderAvatar) && (
|
{/* Initials base layer */}
|
||||||
<div className="absolute inset-0 rounded-full shimmer-loading" />
|
<div className="absolute inset-0 rounded-full bg-[#c4d9d4] flex items-center justify-center">
|
||||||
)}
|
<span className="font-bold text-[#00293d] text-[20px]">{name?.[0]?.toUpperCase() || '?'}</span>
|
||||||
{avatar && !isPlaceholderAvatar && (
|
</div>
|
||||||
|
{avatar && !isPlaceholderAvatar && !avatarError && (
|
||||||
<img
|
<img
|
||||||
ref={(el) => { if (el?.complete && el.naturalWidth > 0) setAvatarLoaded(true); }}
|
ref={(el) => { if (el?.complete && el.naturalWidth > 0) setAvatarLoaded(true); }}
|
||||||
src={avatar}
|
src={avatar}
|
||||||
alt={name}
|
alt={name}
|
||||||
className={`absolute inset-0 w-full h-full object-cover transition-opacity duration-300 ${avatarLoaded ? 'opacity-100' : 'opacity-0'}`}
|
className={`absolute inset-0 w-full h-full object-cover transition-opacity duration-300 ${avatarLoaded ? 'opacity-100' : 'opacity-0'}`}
|
||||||
onLoad={() => setAvatarLoaded(true)}
|
onLoad={() => setAvatarLoaded(true)}
|
||||||
|
onError={() => setAvatarError(true)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -82,33 +82,28 @@ function formatLastSeen(lastSeenAt: string | null | undefined, isOnline: boolean
|
|||||||
return `Last seen ${diffDays}d ago`;
|
return `Last seen ${diffDays}d ago`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Avatar component with shimmer loading animation
|
// Avatar component with shimmer loading animation and initials fallback
|
||||||
function AvatarWithPlaceholder({ src, alt, size }: { src: string; alt: string; size: number }) {
|
function AvatarWithPlaceholder({ src, alt, size, name }: { src: string; alt: string; size: number; name?: string }) {
|
||||||
const [loaded, setLoaded] = useState(false);
|
const [loaded, setLoaded] = useState(false);
|
||||||
|
const [imgError, setImgError] = useState(false);
|
||||||
const placeholder = '/assets/icons/user-placeholder-icon.svg';
|
const placeholder = '/assets/icons/user-placeholder-icon.svg';
|
||||||
const isPlaceholder = !src || src === placeholder;
|
const isPlaceholder = !src || src === placeholder;
|
||||||
|
const initial = name?.[0]?.toUpperCase() || '?';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{(!src || !loaded) && (
|
{/* Initials base layer - always rendered behind image */}
|
||||||
<div className="absolute inset-0 rounded-full shimmer-loading" />
|
<div className="absolute inset-0 rounded-full bg-[#c4d9d4] flex items-center justify-center">
|
||||||
)}
|
<span className="font-bold text-[#00293d]" style={{ fontSize: size * 0.4 }}>{initial}</span>
|
||||||
{src && !isPlaceholder && (
|
</div>
|
||||||
|
{src && !isPlaceholder && !imgError && (
|
||||||
<img
|
<img
|
||||||
ref={(el) => { if (el?.complete && el.naturalWidth > 0) setLoaded(true); }}
|
ref={(el) => { if (el?.complete && el.naturalWidth > 0) setLoaded(true); }}
|
||||||
src={src}
|
src={src}
|
||||||
alt={alt}
|
alt={alt}
|
||||||
className={`absolute inset-0 w-full h-full object-cover transition-opacity duration-300 ${loaded ? 'opacity-100' : 'opacity-0'}`}
|
className={`absolute inset-0 w-full h-full object-cover transition-opacity duration-300 ${loaded ? 'opacity-100' : 'opacity-0'}`}
|
||||||
onLoad={() => setLoaded(true)}
|
onLoad={() => setLoaded(true)}
|
||||||
/>
|
onError={() => setImgError(true)}
|
||||||
)}
|
|
||||||
{src && isPlaceholder && (
|
|
||||||
<Image
|
|
||||||
src={placeholder}
|
|
||||||
alt={alt}
|
|
||||||
width={size}
|
|
||||||
height={size}
|
|
||||||
className="absolute inset-0 w-full h-full object-cover"
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
@@ -730,6 +725,7 @@ export function MessagingPage({ initialConversationId }: { initialConversationId
|
|||||||
src={getValidAvatarUrl(agent.avatarUrl)}
|
src={getValidAvatarUrl(agent.avatarUrl)}
|
||||||
alt={agent.name}
|
alt={agent.name}
|
||||||
size={50}
|
size={50}
|
||||||
|
name={agent.name}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -797,6 +793,7 @@ export function MessagingPage({ initialConversationId }: { initialConversationId
|
|||||||
src={getValidAvatarUrl(conversationAvatarUrls[conversation.id])}
|
src={getValidAvatarUrl(conversationAvatarUrls[conversation.id])}
|
||||||
alt={conversation.otherParty?.name || 'User'}
|
alt={conversation.otherParty?.name || 'User'}
|
||||||
size={70}
|
size={70}
|
||||||
|
name={conversation.otherParty?.name}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{conversation.otherParty?.isOnline && (
|
{conversation.otherParty?.isOnline && (
|
||||||
|
|||||||
@@ -130,20 +130,17 @@ export function NewConversationModal({
|
|||||||
className="w-full flex items-center gap-3 p-3 rounded-[12px] cursor-pointer transition-colors hover:bg-[#00293d]/5 disabled:opacity-50"
|
className="w-full flex items-center gap-3 p-3 rounded-[12px] cursor-pointer transition-colors hover:bg-[#00293d]/5 disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{/* Avatar */}
|
{/* Avatar */}
|
||||||
<div className="w-[44px] h-[44px] rounded-full overflow-hidden border border-[#00293D]/20 flex-shrink-0">
|
<div className="w-[44px] h-[44px] rounded-full overflow-hidden border border-[#00293D]/20 flex-shrink-0 relative">
|
||||||
{contact.avatarUrl && !contact.avatarUrl.endsWith('user-placeholder-icon.svg') ? (
|
{/* Initials base layer */}
|
||||||
|
<div className="absolute inset-0 rounded-full bg-[#c4d9d4] flex items-center justify-center">
|
||||||
|
<span className="font-bold text-[#00293d] text-[18px]">{contact.name?.[0]?.toUpperCase() || '?'}</span>
|
||||||
|
</div>
|
||||||
|
{contact.avatarUrl && !contact.avatarUrl.endsWith('user-placeholder-icon.svg') && (
|
||||||
<img
|
<img
|
||||||
src={getValidAvatarUrl(contact.avatarUrl)}
|
src={getValidAvatarUrl(contact.avatarUrl)}
|
||||||
alt={contact.name}
|
alt={contact.name}
|
||||||
className="w-full h-full object-cover"
|
className="absolute inset-0 w-full h-full object-cover"
|
||||||
/>
|
onError={(e) => { (e.target as HTMLImageElement).style.display = 'none'; }}
|
||||||
) : (
|
|
||||||
<Image
|
|
||||||
src="/assets/icons/user-placeholder-icon.svg"
|
|
||||||
alt={contact.name}
|
|
||||||
width={44}
|
|
||||||
height={44}
|
|
||||||
className="w-full h-full object-cover"
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user