This commit is contained in:
pacnpal
2025-10-27 17:30:25 +00:00
parent 270017955c
commit ad8f7a4101
2 changed files with 106 additions and 1 deletions

View File

@@ -1,7 +1,7 @@
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Button } from '@/components/ui/button';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { UserAvatar } from '@/components/ui/user-avatar';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
import { User, Settings, LogOut } from 'lucide-react';
import { useAuth } from '@/hooks/useAuth';
@@ -71,6 +71,7 @@ export function AuthButtons() {
return <DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="relative h-8 w-8 rounded-full">
<<<<<<< HEAD
<Avatar className="h-8 w-8" key={profile?.avatar_url || 'fallback'}>
<AvatarImage
src={profile?.avatar_url || undefined}
@@ -83,6 +84,14 @@ export function AuthButtons() {
{(profile?.display_name || profile?.username || user.email || 'U').charAt(0).toUpperCase()}
</AvatarFallback>
</Avatar>
=======
<UserAvatar
key={profile?.avatar_url || 'no-avatar'}
avatarUrl={profile?.avatar_url}
fallbackText={profile?.display_name || profile?.username || user.email || 'U'}
size="sm"
/>
>>>>>>> b91d798 (Improve user avatar display to ensure consistency and reliability)
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56" align="end" forceMount>

View File

@@ -0,0 +1,96 @@
import { useState, useEffect } from 'react';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { cn } from '@/lib/utils';
interface UserAvatarProps {
avatarUrl?: string | null;
fallbackText: string;
className?: string;
size?: 'sm' | 'md' | 'lg';
}
const sizeClasses = {
sm: 'h-8 w-8 text-xs',
md: 'h-10 w-10 text-sm',
lg: 'h-16 w-16 text-lg'
};
export function UserAvatar({
avatarUrl,
fallbackText,
className,
size = 'md'
}: UserAvatarProps) {
const [imageUrl, setImageUrl] = useState<string | null>(null);
const [retryCount, setRetryCount] = useState(0);
const [isLoading, setIsLoading] = useState(false);
const [hasError, setHasError] = useState(false);
const MAX_RETRIES = 2;
// Reset state when avatarUrl changes
useEffect(() => {
if (avatarUrl) {
setImageUrl(avatarUrl);
setRetryCount(0);
setIsLoading(true);
setHasError(false);
} else {
setImageUrl(null);
setIsLoading(false);
setHasError(false);
}
}, [avatarUrl]);
const handleImageError = () => {
console.warn('[UserAvatar] Image load failed:', imageUrl, 'Retry count:', retryCount);
if (retryCount < MAX_RETRIES && avatarUrl) {
// Add cache-busting parameter and retry
const cacheBuster = `?retry=${retryCount + 1}&t=${Date.now()}`;
const urlWithCacheBuster = avatarUrl.includes('?')
? `${avatarUrl}&retry=${retryCount + 1}&t=${Date.now()}`
: `${avatarUrl}${cacheBuster}`;
console.log('[UserAvatar] Retrying with cache buster:', urlWithCacheBuster);
setRetryCount(prev => prev + 1);
setImageUrl(urlWithCacheBuster);
} else {
// All retries exhausted, show fallback
console.warn('[UserAvatar] All retries exhausted, showing fallback');
setHasError(true);
setIsLoading(false);
}
};
const handleImageLoad = () => {
console.log('[UserAvatar] Image loaded successfully:', imageUrl);
setIsLoading(false);
setHasError(false);
};
const fallbackInitial = fallbackText.charAt(0).toUpperCase();
return (
<Avatar className={cn(sizeClasses[size], className)}>
{imageUrl && !hasError && (
<AvatarImage
src={imageUrl}
alt={fallbackText}
onError={handleImageError}
onLoad={handleImageLoad}
className={isLoading ? 'opacity-0' : 'opacity-100 transition-opacity duration-200'}
/>
)}
<AvatarFallback className="bg-primary/10 text-primary font-medium">
{isLoading ? (
<div className="animate-pulse">
{fallbackInitial}
</div>
) : (
fallbackInitial
)}
</AvatarFallback>
</Avatar>
);
}