mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 08:31:12 -05:00
update
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { Button } from '@/components/ui/button';
|
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 { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
|
||||||
import { User, Settings, LogOut } from 'lucide-react';
|
import { User, Settings, LogOut } from 'lucide-react';
|
||||||
import { useAuth } from '@/hooks/useAuth';
|
import { useAuth } from '@/hooks/useAuth';
|
||||||
@@ -71,6 +71,7 @@ export function AuthButtons() {
|
|||||||
return <DropdownMenu>
|
return <DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button variant="ghost" className="relative h-8 w-8 rounded-full">
|
<Button variant="ghost" className="relative h-8 w-8 rounded-full">
|
||||||
|
<<<<<<< HEAD
|
||||||
<Avatar className="h-8 w-8" key={profile?.avatar_url || 'fallback'}>
|
<Avatar className="h-8 w-8" key={profile?.avatar_url || 'fallback'}>
|
||||||
<AvatarImage
|
<AvatarImage
|
||||||
src={profile?.avatar_url || undefined}
|
src={profile?.avatar_url || undefined}
|
||||||
@@ -83,6 +84,14 @@ export function AuthButtons() {
|
|||||||
{(profile?.display_name || profile?.username || user.email || 'U').charAt(0).toUpperCase()}
|
{(profile?.display_name || profile?.username || user.email || 'U').charAt(0).toUpperCase()}
|
||||||
</AvatarFallback>
|
</AvatarFallback>
|
||||||
</Avatar>
|
</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>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent className="w-56" align="end" forceMount>
|
<DropdownMenuContent className="w-56" align="end" forceMount>
|
||||||
|
|||||||
96
src/components/ui/user-avatar.tsx
Normal file
96
src/components/ui/user-avatar.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user