From f8b377904e4c1f439c57347903220f1f6f042674 Mon Sep 17 00:00:00 2001 From: benya Date: Sun, 8 Mar 2026 22:44:12 +0300 Subject: [PATCH] web: add inline block and unblock actions in contacts panel --- docs/core-checklist-status.md | 2 +- web/src/components/ChatList.tsx | 32 ++++++++++++++++++++++++++++++-- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/docs/core-checklist-status.md b/docs/core-checklist-status.md index 7052de9..8d03abd 100644 --- a/docs/core-checklist-status.md +++ b/docs/core-checklist-status.md @@ -10,7 +10,7 @@ Legend: 1. Account - `DONE` (email auth, JWT, refresh, logout, reset, sessions; web handles `/verify-email?token=...` links with auth-page feedback; integration tests cover resend-verification replacement, password-reset login flow, and `check-email` status transitions) 2. User Profile - `DONE` (username, name, avatar, bio, update) 3. User Status - `PARTIAL` (online/last seen/offline; web now formats `just now/today/yesterday/recently`, backend-side presence heuristics still limited) -4. Contacts - `PARTIAL` (list/search/add/remove/block/unblock; `add by email` flow covered by integration tests including `success/not found/blocked conflict`; web now surfaces specific add-by-email errors (`not found` vs `blocked`); UX moved to menu) +4. Contacts - `PARTIAL` (list/search/add/remove/block/unblock; `add by email` flow covered by integration tests including `success/not found/blocked conflict`; web now surfaces specific add-by-email errors (`not found` vs `blocked`); UX moved to menu; Contacts panel now includes inline `Block/Unblock` actions per user) 5. Chat List - `DONE` (all/pinned/archive/sort/unread; saved-messages delete behavior covered: clear history without deleting chat; regression test covers `GET /chats/{saved_id}` detail response) 6. Chat Types - `DONE` (private/group/channel) 7. Chat Creation - `DONE` (private/group/channel) diff --git a/web/src/components/ChatList.tsx b/web/src/components/ChatList.tsx index def24cd..4752e83 100644 --- a/web/src/components/ChatList.tsx +++ b/web/src/components/ChatList.tsx @@ -4,7 +4,7 @@ import axios from "axios"; import { archiveChat, clearChat, createPrivateChat, deleteChat, getChats, getSavedMessagesChat, joinChat, leaveChat, pinChat, unarchiveChat, unpinChat } from "../api/chats"; import { globalSearch } from "../api/search"; import type { DiscoverChat, Message, UserSearchItem } from "../chat/types"; -import { addContact, addContactByEmail, listContacts, removeContact } from "../api/users"; +import { addContact, addContactByEmail, blockUser, listBlockedUsers, listContacts, removeContact, unblockUser } from "../api/users"; import { useAuthStore } from "../store/authStore"; import { useChatStore } from "../store/chatStore"; import { NewChatPanel } from "./NewChatPanel"; @@ -33,6 +33,7 @@ export function ChatList() { const [contactsSearch, setContactsSearch] = useState(""); const [contactEmail, setContactEmail] = useState(""); const [contactEmailError, setContactEmailError] = useState(null); + const [blockedContactIds, setBlockedContactIds] = useState>(new Set()); const [ctxChatId, setCtxChatId] = useState(null); const [ctxPos, setCtxPos] = useState<{ x: number; y: number } | null>(null); const [deleteModalChatId, setDeleteModalChatId] = useState(null); @@ -96,13 +97,15 @@ export function ChatList() { setContactsLoading(true); void (async () => { try { - const rows = await listContacts(); + const [rows, blocked] = await Promise.all([listContacts(), listBlockedUsers()]); if (!cancelled) { setContacts(rows); + setBlockedContactIds(new Set(blocked.map((item) => item.id))); } } catch { if (!cancelled) { setContacts([]); + setBlockedContactIds(new Set()); } } finally { if (!cancelled) { @@ -619,6 +622,31 @@ export function ChatList() { > Remove + )) : null}