Fix avatars upload
This commit is contained in:
@@ -3,6 +3,7 @@ import { useNavigate } from 'react-router-dom'
|
||||
import { feedApi } from '@/api'
|
||||
import type { Activity, ActivityType } from '@/types'
|
||||
import { Loader2, ChevronDown, Bell, ExternalLink, AlertTriangle } from 'lucide-react'
|
||||
import { UserAvatar } from '@/components/ui'
|
||||
import {
|
||||
formatRelativeTime,
|
||||
getActivityIcon,
|
||||
@@ -212,19 +213,12 @@ function ActivityItem({ activity }: ActivityItemProps) {
|
||||
<div className="flex items-start gap-3">
|
||||
{/* Avatar */}
|
||||
<div className="flex-shrink-0">
|
||||
{activity.user.avatar_url ? (
|
||||
<img
|
||||
src={activity.user.avatar_url}
|
||||
alt={activity.user.nickname}
|
||||
className="w-8 h-8 rounded-full object-cover"
|
||||
/>
|
||||
) : (
|
||||
<div className="w-8 h-8 rounded-full bg-gray-700 flex items-center justify-center">
|
||||
<span className="text-xs text-gray-400 font-medium">
|
||||
{activity.user.nickname.charAt(0).toUpperCase()}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<UserAvatar
|
||||
userId={activity.user.id}
|
||||
hasAvatar={!!activity.user.avatar_url}
|
||||
nickname={activity.user.nickname}
|
||||
size="sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
|
||||
@@ -173,11 +173,11 @@ export function TelegramLink() {
|
||||
{/* User Profile Card */}
|
||||
<div className="p-4 bg-gradient-to-br from-gray-700/50 to-gray-800/50 rounded-xl border border-gray-600/50">
|
||||
<div className="flex items-center gap-4">
|
||||
{/* Avatar - prefer Telegram avatar */}
|
||||
{/* Avatar - prefer uploaded avatar */}
|
||||
<div className="relative">
|
||||
{user?.telegram_avatar_url || user?.avatar_url ? (
|
||||
{user?.avatar_url || user?.telegram_avatar_url ? (
|
||||
<img
|
||||
src={user.telegram_avatar_url || user.avatar_url || ''}
|
||||
src={user.avatar_url || user.telegram_avatar_url || ''}
|
||||
alt={user.nickname}
|
||||
className="w-16 h-16 rounded-full object-cover border-2 border-blue-500/50"
|
||||
/>
|
||||
|
||||
87
frontend/src/components/ui/UserAvatar.tsx
Normal file
87
frontend/src/components/ui/UserAvatar.tsx
Normal file
@@ -0,0 +1,87 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -3,3 +3,4 @@ export { Input } from './Input'
|
||||
export { Card, CardHeader, CardTitle, CardContent } from './Card'
|
||||
export { ToastContainer } from './Toast'
|
||||
export { ConfirmModal } from './ConfirmModal'
|
||||
export { UserAvatar, clearAvatarCache } from './UserAvatar'
|
||||
|
||||
Reference in New Issue
Block a user