Files
game-marathon/frontend/src/components/ui/UserAvatar.tsx

108 lines
3.1 KiB
TypeScript
Raw Normal View History

2025-12-17 00:04:14 +07:00
import { useState, useEffect } from 'react'
import { usersApi } from '@/api'
// Глобальный кэш для blob URL аватарок
const avatarCache = new Map<number, string>()
2025-12-17 19:50:55 +07:00
// Пользователи, для которых нужно сбросить HTTP-кэш при следующем запросе
const needsCacheBust = new Set<number>()
2025-12-17 00:04:14 +07:00
interface UserAvatarProps {
userId: number
hasAvatar: boolean // Есть ли у пользователя avatar_url
nickname: string
size?: 'sm' | 'md' | 'lg'
className?: string
2025-12-17 19:50:55 +07:00
version?: number // Для принудительного обновления при смене аватара
2025-12-17 00:04:14 +07:00
}
const sizeClasses = {
sm: 'w-8 h-8 text-xs',
md: 'w-12 h-12 text-sm',
lg: 'w-24 h-24 text-xl',
}
2025-12-17 19:50:55 +07:00
export function UserAvatar({ userId, hasAvatar, nickname, size = 'md', className = '', version = 0 }: UserAvatarProps) {
2025-12-17 00:04:14 +07:00
const [blobUrl, setBlobUrl] = useState<string | null>(null)
const [failed, setFailed] = useState(false)
useEffect(() => {
if (!hasAvatar) {
setBlobUrl(null)
return
}
2025-12-17 19:50:55 +07:00
// Если version > 0, значит аватар обновился - сбрасываем кэш
const shouldBustCache = version > 0 || needsCacheBust.has(userId)
// Проверяем кэш только если не нужен bust
if (!shouldBustCache) {
const cached = avatarCache.get(userId)
if (cached) {
setBlobUrl(cached)
return
}
}
// Очищаем старый кэш если bust
if (shouldBustCache) {
const cached = avatarCache.get(userId)
if (cached) {
URL.revokeObjectURL(cached)
avatarCache.delete(userId)
}
needsCacheBust.delete(userId)
2025-12-17 00:04:14 +07:00
}
// Загружаем аватарку
let cancelled = false
2025-12-17 19:50:55 +07:00
usersApi.getAvatarUrl(userId, shouldBustCache)
2025-12-17 00:04:14 +07:00
.then(url => {
if (!cancelled) {
avatarCache.set(userId, url)
setBlobUrl(url)
}
})
.catch(() => {
if (!cancelled) {
setFailed(true)
}
})
return () => {
cancelled = true
}
2025-12-17 19:50:55 +07:00
}, [userId, hasAvatar, version])
2025-12-17 00:04:14 +07:00
const sizeClass = sizeClasses[size]
if (blobUrl && !failed) {
return (
<img
src={blobUrl}
alt={nickname}
className={`rounded-full object-cover ${sizeClass} ${className}`}
/>
)
}
// Fallback - первая буква никнейма
return (
<div className={`rounded-full bg-gray-700 flex items-center justify-center ${sizeClass} ${className}`}>
<span className="text-gray-400 font-medium">
{nickname.charAt(0).toUpperCase()}
</span>
</div>
)
}
// Функция для очистки кэша конкретного пользователя (после загрузки нового аватара)
export function clearAvatarCache(userId: number) {
const cached = avatarCache.get(userId)
if (cached) {
URL.revokeObjectURL(cached)
avatarCache.delete(userId)
}
2025-12-17 19:50:55 +07:00
// Помечаем, что при следующем запросе нужно сбросить HTTP-кэш браузера
needsCacheBust.add(userId)
2025-12-17 00:04:14 +07:00
}