feat: avatar upload for profile/chats, fix session persistence on refresh

- Profile modal: upload/remove user avatar, edit name/bio/phone, change password
- Chat info panel: upload/remove chat photo (owner/admin only)
- Avatar component: render photo when available, fallback to initials
- Session fix: initialize Zustand store from localStorage to prevent redirect on refresh
- Backend: avatar upload endpoints for users and chats, migration adds avatar columns

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Ai
2026-05-22 11:55:11 +03:00
parent 01331d0263
commit 3f54dbf375
9 changed files with 421 additions and 45 deletions

View File

@@ -11,6 +11,7 @@ import ChatHeader from '../components/ChatHeader';
import ChatInfoPanel from '../components/ChatInfoPanel';
import NewChatModal from '../components/NewChatModal';
import AdminPanel from '../components/admin/AdminPanel';
import ProfileModal from '../components/ProfileModal';
import { usePushNotifications } from '../hooks/usePushNotifications';
import { Chat } from '../types';
@@ -21,6 +22,7 @@ export default function MainLayout() {
const [showNew, setShowNew] = useState(false);
const [showAdmin, setShowAdmin] = useState(false);
const [showInfo, setShowInfo] = useState(false);
const [showProfile, setShowProfile] = useState(false);
const [mobileChatOpen, setMobileChatOpen] = useState(false);
const [fullChat, setFullChat] = useState<Chat | null>(null);
@@ -91,7 +93,8 @@ export default function MainLayout() {
{/* Sidebar Header */}
<div className="px-4 py-3 border-b border-gray-100">
<div className="flex items-center gap-3 mb-3">
<Avatar name={user?.displayName || ''} color={user?.avatarColor || '#3b82f6'} size="sm" />
<Avatar name={user?.displayName || ''} color={user?.avatarColor || '#3b82f6'} avatar={user?.avatar} size="sm"
onClick={() => setShowProfile(true)} className="cursor-pointer" />
<div className="flex-1 min-w-0">
<div className="font-semibold text-gray-900 text-sm truncate">{user?.displayName}</div>
<div className="flex items-center gap-1 text-xs text-gray-400">
@@ -108,6 +111,10 @@ export default function MainLayout() {
<Shield className="w-4 h-4" />
</button>
)}
<button onClick={() => setShowProfile(true)}
className="p-1.5 rounded-lg hover:bg-gray-100 text-gray-400 hover:text-gray-600" title="Профиль">
<Settings className="w-4 h-4" />
</button>
<button onClick={() => setShowNew(true)}
className="p-1.5 rounded-lg hover:bg-gray-100 text-gray-400 hover:text-gray-600" title="Новый чат">
<Edit className="w-4 h-4" />
@@ -201,6 +208,7 @@ export default function MainLayout() {
if (chat) openChat(chat);
}} />}
{showAdmin && <AdminPanel onClose={() => setShowAdmin(false)} />}
{showProfile && <ProfileModal onClose={() => setShowProfile(false)} />}
</div>
);
}