web: move contacts to dedicated burger-menu screen
Some checks failed
CI / test (push) Failing after 24s
Some checks failed
CI / test (push) Failing after 24s
This commit is contained in:
@@ -23,10 +23,12 @@ export function ChatList() {
|
||||
const [messageResults, setMessageResults] = useState<Message[]>([]);
|
||||
const [archivedChats, setArchivedChats] = useState<typeof chats>([]);
|
||||
const [searchLoading, setSearchLoading] = useState(false);
|
||||
const [tab, setTab] = useState<"all" | "contacts" | "people" | "groups" | "channels" | "archived">("all");
|
||||
const [tab, setTab] = useState<"all" | "people" | "groups" | "channels" | "archived">("all");
|
||||
const [archivedLoading, setArchivedLoading] = useState(false);
|
||||
const [contactsLoading, setContactsLoading] = useState(false);
|
||||
const [contacts, setContacts] = useState<UserSearchItem[]>([]);
|
||||
const [contactsOpen, setContactsOpen] = useState(false);
|
||||
const [contactsSearch, setContactsSearch] = useState("");
|
||||
const [contactEmail, setContactEmail] = useState("");
|
||||
const [contactEmailError, setContactEmailError] = useState<string | null>(null);
|
||||
const [ctxChatId, setCtxChatId] = useState<number | null>(null);
|
||||
@@ -79,7 +81,7 @@ export function ChatList() {
|
||||
}, [tab, chats.length]);
|
||||
|
||||
useEffect(() => {
|
||||
if (tab !== "contacts") {
|
||||
if (!contactsOpen) {
|
||||
return;
|
||||
}
|
||||
let cancelled = false;
|
||||
@@ -103,7 +105,7 @@ export function ChatList() {
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [tab]);
|
||||
}, [contactsOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
const term = search.trim();
|
||||
@@ -273,7 +275,7 @@ export function ChatList() {
|
||||
<button className="block w-full rounded px-3 py-2 text-left text-sm hover:bg-slate-800" onClick={() => { void openSavedMessages(); setMenuOpen(false); }}>
|
||||
Saved Messages
|
||||
</button>
|
||||
<button className="block w-full rounded px-3 py-2 text-left text-sm hover:bg-slate-800" onClick={() => { setTab("contacts"); setMenuOpen(false); }}>
|
||||
<button className="block w-full rounded px-3 py-2 text-left text-sm hover:bg-slate-800" onClick={() => { setContactsOpen(true); setMenuOpen(false); }}>
|
||||
Contacts
|
||||
</button>
|
||||
<button className="block w-full rounded px-3 py-2 text-left text-sm hover:bg-slate-800" onClick={() => { setSettingsOpen(true); setMenuOpen(false); }}>
|
||||
@@ -338,7 +340,7 @@ export function ChatList() {
|
||||
className="rounded bg-slate-700 px-2 py-1 text-[10px] hover:bg-slate-600"
|
||||
onClick={async () => {
|
||||
await addContact(user.id);
|
||||
if (tab === "contacts") {
|
||||
if (contactsOpen) {
|
||||
setContacts(await listContacts());
|
||||
}
|
||||
}}
|
||||
@@ -433,10 +435,38 @@ export function ChatList() {
|
||||
{tab === "archived" && !archivedLoading && archivedChats.length === 0 ? (
|
||||
<p className="px-4 py-3 text-xs text-slate-400">Archive is empty</p>
|
||||
) : null}
|
||||
{tab === "contacts" && contactsLoading ? (
|
||||
<p className="px-4 py-3 text-xs text-slate-400">Loading contacts...</p>
|
||||
{pinnedVisibleChats.length > 0 ? (
|
||||
<p className="px-4 py-2 text-[10px] uppercase tracking-wide text-slate-400">Pinned</p>
|
||||
) : null}
|
||||
{tab === "contacts" ? (
|
||||
{pinnedVisibleChats.map(renderChatRow)}
|
||||
{regularVisibleChats.length > 0 ? (
|
||||
<p className="px-4 py-2 text-[10px] uppercase tracking-wide text-slate-400">Chats</p>
|
||||
) : null}
|
||||
{regularVisibleChats.map(renderChatRow)}
|
||||
</div>
|
||||
{contactsOpen ? (
|
||||
<div className="absolute inset-0 z-30 flex flex-col bg-slate-900">
|
||||
<div className="border-b border-slate-700/50 px-4 py-3">
|
||||
<div className="mb-3 flex items-center gap-2">
|
||||
<button
|
||||
className="rounded-lg bg-slate-700/70 px-2 py-1.5 text-xs"
|
||||
onClick={() => {
|
||||
setContactsOpen(false);
|
||||
setContactsSearch("");
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
<p className="text-sm font-semibold">Contacts</p>
|
||||
</div>
|
||||
<input
|
||||
className="w-full rounded-full border border-slate-700/80 bg-slate-800/80 px-3 py-2 text-sm outline-none placeholder:text-slate-400 focus:border-sky-500"
|
||||
placeholder="Search contacts"
|
||||
value={contactsSearch}
|
||||
onChange={(e) => setContactsSearch(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="border-b border-slate-800/60 px-4 py-3">
|
||||
<p className="mb-2 text-[10px] uppercase tracking-wide text-slate-400">Add contact (Email)</p>
|
||||
<div className="flex gap-2">
|
||||
@@ -472,48 +502,69 @@ export function ChatList() {
|
||||
</div>
|
||||
{contactEmailError ? <p className="mt-1 text-[11px] text-red-400">{contactEmailError}</p> : null}
|
||||
</div>
|
||||
) : null}
|
||||
{tab === "contacts" && !contactsLoading && contacts.length === 0 ? (
|
||||
<p className="px-4 py-3 text-xs text-slate-400">No contacts yet</p>
|
||||
) : null}
|
||||
{tab === "contacts"
|
||||
? contacts.map((user) => (
|
||||
<div className="flex items-center gap-2 border-b border-slate-800/60 px-4 py-3" key={`contact-${user.id}`}>
|
||||
<button
|
||||
className="min-w-0 flex-1 text-left"
|
||||
onClick={async () => {
|
||||
const chat = await createPrivateChat(user.id);
|
||||
const updatedChats = await getChats();
|
||||
useChatStore.setState({ chats: updatedChats, activeChatId: chat.id });
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<p className="truncate text-sm font-semibold">{user.name}</p>
|
||||
<p className="truncate text-xs text-slate-400">{user.email || `@${user.username}`}</p>
|
||||
</button>
|
||||
<button
|
||||
className="rounded bg-slate-700 px-2 py-1 text-[10px] text-red-200 hover:bg-slate-600"
|
||||
onClick={async () => {
|
||||
await removeContact(user.id);
|
||||
setContacts((prev) => prev.filter((item) => item.id !== user.id));
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
</div>
|
||||
))
|
||||
: null}
|
||||
|
||||
{tab !== "contacts" && pinnedVisibleChats.length > 0 ? (
|
||||
<p className="px-4 py-2 text-[10px] uppercase tracking-wide text-slate-400">Pinned</p>
|
||||
) : null}
|
||||
{tab !== "contacts" ? pinnedVisibleChats.map(renderChatRow) : null}
|
||||
{tab !== "contacts" && regularVisibleChats.length > 0 ? (
|
||||
<p className="px-4 py-2 text-[10px] uppercase tracking-wide text-slate-400">Chats</p>
|
||||
) : null}
|
||||
{tab !== "contacts" ? regularVisibleChats.map(renderChatRow) : null}
|
||||
</div>
|
||||
<div className="tg-scrollbar flex-1 overflow-auto">
|
||||
{contactsLoading ? (
|
||||
<p className="px-4 py-3 text-xs text-slate-400">Loading contacts...</p>
|
||||
) : null}
|
||||
{!contactsLoading &&
|
||||
contacts.filter((user) => {
|
||||
const term = contactsSearch.trim().toLowerCase();
|
||||
if (!term) {
|
||||
return true;
|
||||
}
|
||||
return (
|
||||
(user.name || "").toLowerCase().includes(term) ||
|
||||
(user.username || "").toLowerCase().includes(term) ||
|
||||
(user.email || "").toLowerCase().includes(term)
|
||||
);
|
||||
}).length === 0 ? (
|
||||
<p className="px-4 py-3 text-xs text-slate-400">No contacts yet</p>
|
||||
) : null}
|
||||
{!contactsLoading
|
||||
? contacts
|
||||
.filter((user) => {
|
||||
const term = contactsSearch.trim().toLowerCase();
|
||||
if (!term) {
|
||||
return true;
|
||||
}
|
||||
return (
|
||||
(user.name || "").toLowerCase().includes(term) ||
|
||||
(user.username || "").toLowerCase().includes(term) ||
|
||||
(user.email || "").toLowerCase().includes(term)
|
||||
);
|
||||
})
|
||||
.map((user) => (
|
||||
<div className="flex items-center gap-2 border-b border-slate-800/60 px-4 py-3" key={`contact-${user.id}`}>
|
||||
<button
|
||||
className="min-w-0 flex-1 text-left"
|
||||
onClick={async () => {
|
||||
const chat = await createPrivateChat(user.id);
|
||||
const updatedChats = await getChats();
|
||||
useChatStore.setState({ chats: updatedChats, activeChatId: chat.id });
|
||||
setContactsOpen(false);
|
||||
setContactsSearch("");
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<p className="truncate text-sm font-semibold">{user.name}</p>
|
||||
<p className="truncate text-xs text-slate-400">{user.email || `@${user.username}`}</p>
|
||||
</button>
|
||||
<button
|
||||
className="rounded bg-slate-700 px-2 py-1 text-[10px] text-red-200 hover:bg-slate-600"
|
||||
onClick={async () => {
|
||||
await removeContact(user.id);
|
||||
setContacts((prev) => prev.filter((item) => item.id !== user.id));
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
</div>
|
||||
))
|
||||
: null}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<NewChatPanel />
|
||||
|
||||
{ctxChatId && ctxPos
|
||||
|
||||
Reference in New Issue
Block a user