Fix avatars upload
This commit is contained in:
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user