feat: add saved messages, public chat discovery/join, and chat delete options
All checks were successful
CI / test (push) Successful in 19s

- add Saved Messages system chat with dedicated API

- add public group/channel metadata and discover/join endpoints

- add chat delete flow with for_all option and channel-wide delete

- switch message actions to context menu and improve reply/forward visuals

- improve microphone permission handling for voice recording
This commit is contained in:
2026-03-08 00:41:35 +03:00
parent b5a7d733c6
commit b9f71b9528
12 changed files with 529 additions and 119 deletions

View File

@@ -1,4 +1,5 @@
import { useEffect, useState } from "react";
import { deleteChat } from "../api/chats";
import { useAuthStore } from "../store/authStore";
import { useChatStore } from "../store/chatStore";
import { NewChatPanel } from "./NewChatPanel";
@@ -12,6 +13,10 @@ export function ChatList() {
const me = useAuthStore((s) => s.me);
const [search, setSearch] = useState("");
const [tab, setTab] = useState<"all" | "people" | "groups" | "channels">("all");
const [ctxChatId, setCtxChatId] = useState<number | null>(null);
const [ctxPos, setCtxPos] = useState<{ x: number; y: number } | null>(null);
const [deleteModalChatId, setDeleteModalChatId] = useState<number | null>(null);
const [deleteForAll, setDeleteForAll] = useState(false);
useEffect(() => {
const timer = setTimeout(() => {
@@ -41,7 +46,7 @@ export function ChatList() {
];
return (
<aside className="relative flex h-full w-full max-w-xs flex-col bg-slate-900/60">
<aside className="relative flex h-full w-full max-w-xs flex-col bg-slate-900/60" onClick={() => { setCtxChatId(null); setCtxPos(null); }}>
<div className="border-b border-slate-700/50 px-3 py-3">
<div className="mb-2 flex items-center gap-2">
<button className="rounded-lg bg-slate-700/70 px-2 py-2 text-xs"></button>
@@ -77,6 +82,11 @@ export function ChatList() {
}`}
key={chat.id}
onClick={() => setActiveChatId(chat.id)}
onContextMenu={(e) => {
e.preventDefault();
setCtxChatId(chat.id);
setCtxPos({ x: e.clientX, y: e.clientY });
}}
>
<div className="flex items-start gap-3">
<div className="mt-0.5 flex h-10 w-10 items-center justify-center rounded-full bg-sky-500/30 text-sm font-semibold uppercase text-sky-100">
@@ -96,6 +106,52 @@ export function ChatList() {
))}
</div>
<NewChatPanel />
{ctxChatId && ctxPos ? (
<div className="fixed z-50 w-44 rounded-lg border border-slate-700/80 bg-slate-900/95 p-1 shadow-2xl" style={{ left: ctxPos.x, top: ctxPos.y }} onClick={(e) => e.stopPropagation()}>
<button
className="block w-full rounded px-2 py-1.5 text-left text-sm text-red-300 hover:bg-slate-800"
onClick={() => {
setDeleteModalChatId(ctxChatId);
setCtxChatId(null);
setCtxPos(null);
setDeleteForAll(false);
}}
>
Delete chat
</button>
</div>
) : null}
{deleteModalChatId ? (
<div className="absolute inset-0 z-50 flex items-end justify-center bg-slate-950/55 p-3">
<div className="w-full rounded-xl border border-slate-700/80 bg-slate-900 p-3">
<p className="mb-2 text-sm font-semibold">Delete chat #{deleteModalChatId}</p>
<label className="mb-3 flex items-center gap-2 text-sm">
<input checked={deleteForAll} onChange={(e) => setDeleteForAll(e.target.checked)} type="checkbox" />
Delete for everyone
</label>
<div className="flex gap-2">
<button
className="flex-1 rounded bg-red-500 px-3 py-2 text-sm font-semibold text-white"
onClick={async () => {
await deleteChat(deleteModalChatId, deleteForAll);
await loadChats(search.trim() ? search : undefined);
if (activeChatId === deleteModalChatId) {
setActiveChatId(null);
}
setDeleteModalChatId(null);
}}
>
Delete
</button>
<button className="flex-1 rounded bg-slate-700 px-3 py-2 text-sm" onClick={() => setDeleteModalChatId(null)}>
Cancel
</button>
</div>
</div>
</div>
) : null}
</aside>
);
}