From f7413bc626f962a5e419fd83e7f2a203d3fd2df7 Mon Sep 17 00:00:00 2001 From: benya Date: Sun, 8 Mar 2026 13:45:47 +0300 Subject: [PATCH] web: add avatar file upload in profile editors --- web/src/components/ChatList.tsx | 54 +++++++++++++++++++++++++- web/src/components/SettingsPanel.tsx | 58 +++++++++++++++++++++++++++- 2 files changed, 108 insertions(+), 4 deletions(-) diff --git a/web/src/components/ChatList.tsx b/web/src/components/ChatList.tsx index ef8b08e..d1df81a 100644 --- a/web/src/components/ChatList.tsx +++ b/web/src/components/ChatList.tsx @@ -1,11 +1,12 @@ import { useEffect, useRef, useState } from "react"; import { createPortal } from "react-dom"; -import { archiveChat, clearChat, createPrivateChat, deleteChat, getChats, getSavedMessagesChat, joinChat, leaveChat, pinChat, unarchiveChat, unpinChat } from "../api/chats"; +import { archiveChat, clearChat, createPrivateChat, deleteChat, getChats, getSavedMessagesChat, joinChat, leaveChat, pinChat, requestUploadUrl, unarchiveChat, unpinChat, uploadToPresignedUrl } from "../api/chats"; import { globalSearch } from "../api/search"; import type { DiscoverChat, Message, UserSearchItem } from "../chat/types"; import { addContact, addContactByEmail, listContacts, removeContact, updateMyProfile } from "../api/users"; import { useAuthStore } from "../store/authStore"; import { useChatStore } from "../store/chatStore"; +import { useUiStore } from "../store/uiStore"; import { NewChatPanel } from "./NewChatPanel"; import { SettingsPanel } from "./SettingsPanel"; import { applyAppearancePreferences, getAppPreferences } from "../utils/preferences"; @@ -18,6 +19,7 @@ export function ChatList() { const loadChats = useChatStore((s) => s.loadChats); const me = useAuthStore((s) => s.me); const logout = useAuthStore((s) => s.logout); + const showToast = useUiStore((s) => s.showToast); const [search, setSearch] = useState(""); const [userResults, setUserResults] = useState([]); const [discoverResults, setDiscoverResults] = useState([]); @@ -46,6 +48,7 @@ export function ChatList() { const [profileAllowPrivateMessages, setProfileAllowPrivateMessages] = useState(true); const [profileError, setProfileError] = useState(null); const [profileSaving, setProfileSaving] = useState(false); + const [profileAvatarUploading, setProfileAvatarUploading] = useState(false); const sidebarRef = useRef(null); const burgerMenuRef = useRef(null); const burgerButtonRef = useRef(null); @@ -205,6 +208,22 @@ export function ChatList() { setProfileAllowPrivateMessages(me.allow_private_messages ?? true); }, [me]); + async function uploadProfileAvatar(file: File) { + setProfileAvatarUploading(true); + setProfileError(null); + try { + const upload = await requestUploadUrl(file); + await uploadToPresignedUrl(upload.upload_url, upload.required_headers, file); + setProfileAvatarUrl(upload.file_url); + showToast("Avatar uploaded"); + } catch { + setProfileError("Failed to upload avatar"); + showToast("Avatar upload failed"); + } finally { + setProfileAvatarUploading(false); + } + } + async function openSavedMessages() { const saved = await getSavedMessagesChat(); const updatedChats = await getChats(); @@ -754,6 +773,37 @@ export function ChatList() {

Edit profile

+
+ {profileAvatarUrl.trim() ? ( + avatar preview + ) : ( +
No avatar
+ )} +
+ + {profileAvatarUrl.trim() ? ( + + ) : null} +
+
setProfileName(e.target.value)} /> setProfileUsername(e.target.value.replace("@", ""))} /> setProfileBio(e.target.value)} /> @@ -771,7 +821,7 @@ export function ChatList() {
+ ) : null} +
+
setProfileDraft((prev) => ({ ...prev, avatarUrl: e.target.value }))} />