88 lines
2.1 KiB
TypeScript
88 lines
2.1 KiB
TypeScript
|
|
import { useState, useEffect } from 'react'
|
|||
|
|
import { usersApi } from '@/api'
|
|||
|
|
|
|||
|
|
// Глобальный кэш для blob URL аватарок
|
|||
|
|
const avatarCache = new Map<number, string>()
|
|||
|
|
|
|||
|
|
interface UserAvatarProps {
|
|||
|
|
userId: number
|
|||
|
|
hasAvatar: boolean // Есть ли у пользователя avatar_url
|
|||
|
|
nickname: string
|
|||
|
|
size?: 'sm' | 'md' | 'lg'
|
|||
|
|
className?: string
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const sizeClasses = {
|
|||
|
|
sm: 'w-8 h-8 text-xs',
|
|||
|
|
md: 'w-12 h-12 text-sm',
|
|||
|
|
lg: 'w-24 h-24 text-xl',
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function UserAvatar({ userId, hasAvatar, nickname, size = 'md', className = '' }: UserAvatarProps) {
|
|||
|
|
const [blobUrl, setBlobUrl] = useState<string | null>(null)
|
|||
|
|
const [failed, setFailed] = useState(false)
|
|||
|
|
|
|||
|
|
useEffect(() => {
|
|||
|
|
if (!hasAvatar) {
|
|||
|
|
setBlobUrl(null)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Проверяем кэш
|
|||
|
|
const cached = avatarCache.get(userId)
|
|||
|
|
if (cached) {
|
|||
|
|
setBlobUrl(cached)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Загружаем аватарку
|
|||
|
|
let cancelled = false
|
|||
|
|
usersApi.getAvatarUrl(userId)
|
|||
|
|
.then(url => {
|
|||
|
|
if (!cancelled) {
|
|||
|
|
avatarCache.set(userId, url)
|
|||
|
|
setBlobUrl(url)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
.catch(() => {
|
|||
|
|
if (!cancelled) {
|
|||
|
|
setFailed(true)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
return () => {
|
|||
|
|
cancelled = true
|
|||
|
|
}
|
|||
|
|
}, [userId, hasAvatar])
|
|||
|
|
|
|||
|
|
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)
|
|||
|
|
}
|
|||
|
|
}
|