Fix avatars upload

This commit is contained in:
2025-12-17 00:04:14 +07:00
parent 895e296f44
commit 1c07d8c5ff
9 changed files with 234 additions and 45 deletions

View File

@@ -7,7 +7,7 @@ import { usersApi, telegramApi, authApi } from '@/api'
import type { UserStats } from '@/types'
import { useToast } from '@/store/toast'
import {
Button, Input, Card, CardHeader, CardTitle, CardContent
Button, Input, Card, CardHeader, CardTitle, CardContent, clearAvatarCache
} from '@/components/ui'
import {
User, Camera, Trophy, Target, CheckCircle, Flame,
@@ -43,6 +43,8 @@ export function ProfilePage() {
const [showPasswordForm, setShowPasswordForm] = useState(false)
const [showCurrentPassword, setShowCurrentPassword] = useState(false)
const [showNewPassword, setShowNewPassword] = useState(false)
const [avatarBlobUrl, setAvatarBlobUrl] = useState<string | null>(null)
const [isLoadingAvatar, setIsLoadingAvatar] = useState(true)
// Telegram state
const [telegramLoading, setTelegramLoading] = useState(false)
@@ -70,6 +72,32 @@ export function ProfilePage() {
}
}, [])
// Загрузка аватарки через API
useEffect(() => {
if (user?.id && user?.avatar_url) {
loadAvatar(user.id)
} else {
setIsLoadingAvatar(false)
}
return () => {
if (avatarBlobUrl) {
URL.revokeObjectURL(avatarBlobUrl)
}
}
}, [user?.id, user?.avatar_url])
const loadAvatar = async (userId: number) => {
setIsLoadingAvatar(true)
try {
const url = await usersApi.getAvatarUrl(userId)
setAvatarBlobUrl(url)
} catch {
setAvatarBlobUrl(null)
} finally {
setIsLoadingAvatar(false)
}
}
// Обновляем форму никнейма при изменении user
useEffect(() => {
if (user?.nickname) {
@@ -122,6 +150,15 @@ export function ProfilePage() {
try {
const updatedUser = await usersApi.uploadAvatar(file)
updateUser({ avatar_url: updatedUser.avatar_url })
// Перезагружаем аватарку через API
if (user?.id) {
// Очищаем старый blob URL и глобальный кэш
if (avatarBlobUrl) {
URL.revokeObjectURL(avatarBlobUrl)
}
clearAvatarCache(user.id)
await loadAvatar(user.id)
}
toast.success('Аватар обновлен')
} catch {
toast.error('Не удалось загрузить аватар')
@@ -208,7 +245,8 @@ export function ProfilePage() {
}
const isLinked = !!user?.telegram_id
const displayAvatar = user?.telegram_avatar_url || user?.avatar_url
// Приоритет: загруженная аватарка (blob) > телеграм аватарка
const displayAvatar = avatarBlobUrl || user?.telegram_avatar_url
return (
<div className="max-w-2xl mx-auto space-y-6">
@@ -220,30 +258,34 @@ export function ProfilePage() {
<div className="flex items-start gap-6">
{/* Аватар */}
<div className="relative group flex-shrink-0">
<button
onClick={handleAvatarClick}
disabled={isUploadingAvatar}
className="relative w-24 h-24 rounded-full overflow-hidden bg-gray-700 hover:opacity-80 transition-opacity"
>
{displayAvatar ? (
<img
src={displayAvatar}
alt={user?.nickname}
className="w-full h-full object-cover"
/>
) : (
<div className="w-full h-full flex items-center justify-center">
<User className="w-12 h-12 text-gray-500" />
</div>
)}
<div className="absolute inset-0 bg-black/50 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity">
{isUploadingAvatar ? (
<Loader2 className="w-6 h-6 text-white animate-spin" />
{isLoadingAvatar ? (
<div className="w-24 h-24 rounded-full bg-gray-700 animate-pulse" />
) : (
<button
onClick={handleAvatarClick}
disabled={isUploadingAvatar}
className="relative w-24 h-24 rounded-full overflow-hidden bg-gray-700 hover:opacity-80 transition-opacity"
>
{displayAvatar ? (
<img
src={displayAvatar}
alt={user?.nickname}
className="w-full h-full object-cover"
/>
) : (
<Camera className="w-6 h-6 text-white" />
<div className="w-full h-full flex items-center justify-center">
<User className="w-12 h-12 text-gray-500" />
</div>
)}
</div>
</button>
<div className="absolute inset-0 bg-black/50 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity">
{isUploadingAvatar ? (
<Loader2 className="w-6 h-6 text-white animate-spin" />
) : (
<Camera className="w-6 h-6 text-white" />
)}
</div>
</button>
)}
<input
ref={fileInputRef}
type="file"
@@ -286,8 +328,14 @@ export function ProfilePage() {
</CardHeader>
<CardContent>
{isLoadingStats ? (
<div className="flex justify-center py-4">
<Loader2 className="w-6 h-6 animate-spin text-primary-500" />
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
{[...Array(4)].map((_, i) => (
<div key={i} className="bg-gray-900 rounded-lg p-4 text-center">
<div className="w-6 h-6 bg-gray-700 rounded mx-auto mb-2 animate-pulse" />
<div className="h-8 w-12 bg-gray-700 rounded mx-auto mb-2 animate-pulse" />
<div className="h-4 w-16 bg-gray-700 rounded mx-auto animate-pulse" />
</div>
))}
</div>
) : stats ? (
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">