web: add inline block and unblock actions in contacts panel
Some checks are pending
CI / test (push) Has started running
Some checks are pending
CI / test (push) Has started running
This commit is contained in:
@@ -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)
|
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)
|
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)
|
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)
|
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)
|
6. Chat Types - `DONE` (private/group/channel)
|
||||||
7. Chat Creation - `DONE` (private/group/channel)
|
7. Chat Creation - `DONE` (private/group/channel)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import axios from "axios";
|
|||||||
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, unarchiveChat, unpinChat } from "../api/chats";
|
||||||
import { globalSearch } from "../api/search";
|
import { globalSearch } from "../api/search";
|
||||||
import type { DiscoverChat, Message, UserSearchItem } from "../chat/types";
|
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 { useAuthStore } from "../store/authStore";
|
||||||
import { useChatStore } from "../store/chatStore";
|
import { useChatStore } from "../store/chatStore";
|
||||||
import { NewChatPanel } from "./NewChatPanel";
|
import { NewChatPanel } from "./NewChatPanel";
|
||||||
@@ -33,6 +33,7 @@ export function ChatList() {
|
|||||||
const [contactsSearch, setContactsSearch] = useState("");
|
const [contactsSearch, setContactsSearch] = useState("");
|
||||||
const [contactEmail, setContactEmail] = useState("");
|
const [contactEmail, setContactEmail] = useState("");
|
||||||
const [contactEmailError, setContactEmailError] = useState<string | null>(null);
|
const [contactEmailError, setContactEmailError] = useState<string | null>(null);
|
||||||
|
const [blockedContactIds, setBlockedContactIds] = useState<Set<number>>(new Set());
|
||||||
const [ctxChatId, setCtxChatId] = useState<number | null>(null);
|
const [ctxChatId, setCtxChatId] = useState<number | null>(null);
|
||||||
const [ctxPos, setCtxPos] = useState<{ x: number; y: number } | null>(null);
|
const [ctxPos, setCtxPos] = useState<{ x: number; y: number } | null>(null);
|
||||||
const [deleteModalChatId, setDeleteModalChatId] = useState<number | null>(null);
|
const [deleteModalChatId, setDeleteModalChatId] = useState<number | null>(null);
|
||||||
@@ -96,13 +97,15 @@ export function ChatList() {
|
|||||||
setContactsLoading(true);
|
setContactsLoading(true);
|
||||||
void (async () => {
|
void (async () => {
|
||||||
try {
|
try {
|
||||||
const rows = await listContacts();
|
const [rows, blocked] = await Promise.all([listContacts(), listBlockedUsers()]);
|
||||||
if (!cancelled) {
|
if (!cancelled) {
|
||||||
setContacts(rows);
|
setContacts(rows);
|
||||||
|
setBlockedContactIds(new Set(blocked.map((item) => item.id)));
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
if (!cancelled) {
|
if (!cancelled) {
|
||||||
setContacts([]);
|
setContacts([]);
|
||||||
|
setBlockedContactIds(new Set());
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (!cancelled) {
|
if (!cancelled) {
|
||||||
@@ -619,6 +622,31 @@ export function ChatList() {
|
|||||||
>
|
>
|
||||||
Remove
|
Remove
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
className={`rounded px-2 py-1 text-[10px] hover:bg-slate-600 ${
|
||||||
|
blockedContactIds.has(user.id) ? "bg-emerald-700/80 text-emerald-100" : "bg-slate-700 text-amber-200"
|
||||||
|
}`}
|
||||||
|
onClick={async () => {
|
||||||
|
if (blockedContactIds.has(user.id)) {
|
||||||
|
await unblockUser(user.id);
|
||||||
|
setBlockedContactIds((prev) => {
|
||||||
|
const next = new Set(prev);
|
||||||
|
next.delete(user.id);
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await blockUser(user.id);
|
||||||
|
setBlockedContactIds((prev) => {
|
||||||
|
const next = new Set(prev);
|
||||||
|
next.add(user.id);
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
{blockedContactIds.has(user.id) ? "Unblock" : "Block"}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
: null}
|
: null}
|
||||||
|
|||||||
Reference in New Issue
Block a user