import { FormEvent, useMemo, useState } from "react"; import { createChat, createPrivateChat, createPublicChat, getChats, getSavedMessagesChat, joinByInvite } from "../api/chats"; import { searchUsers } from "../api/users"; import type { ChatType, UserSearchItem } from "../chat/types"; import { useChatStore } from "../store/chatStore"; type CreateMode = "group" | "channel"; type DialogMode = "none" | "private" | "group" | "channel" | "invite"; export function NewChatPanel() { const [dialog, setDialog] = useState("none"); const [query, setQuery] = useState(""); const [title, setTitle] = useState(""); const [handle, setHandle] = useState(""); const [description, setDescription] = useState(""); const [inviteToken, setInviteToken] = useState(""); const [isPublic, setIsPublic] = useState(false); const [results, setResults] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [menuOpen, setMenuOpen] = useState(false); const setActiveChatId = useChatStore((s) => s.setActiveChatId); const normalizedQuery = useMemo(() => query.trim(), [query]); async function handleSearchUsers(value: string) { setQuery(value); setError(null); if (value.trim().replace("@", "").length < 2) { setResults([]); return; } try { const users = await searchUsers(value); setResults(users); } catch { setError("Search failed"); } } async function refreshChatsAndSelect(chatId?: number) { const chats = await getChats(); useChatStore.setState({ chats }); if (chatId) { setActiveChatId(chatId); return; } if (chats[0]) { setActiveChatId(chats[0].id); } } async function openSavedMessages() { const saved = await getSavedMessagesChat(); await refreshChatsAndSelect(saved.id); } async function createPrivate(userId: number) { setLoading(true); setError(null); try { const chat = await createPrivateChat(userId); await refreshChatsAndSelect(chat.id); setDialog("none"); setQuery(""); setResults([]); } catch { setError("Failed to create private chat"); } finally { setLoading(false); } } async function createByType(event: FormEvent, mode: CreateMode) { event.preventDefault(); if (!title.trim()) { setError("Title is required"); return; } setLoading(true); setError(null); try { let chat; if (isPublic) { const normalizedHandle = handle.trim().replace("@", "").toLowerCase(); if (!normalizedHandle) { setError("Public chat requires @handle"); setLoading(false); return; } chat = await createPublicChat(mode, title.trim(), normalizedHandle, description.trim() || undefined); } else { chat = await createChat(mode as ChatType, title.trim(), []); } await refreshChatsAndSelect(chat.id); setDialog("none"); setTitle(""); setHandle(""); setDescription(""); setIsPublic(false); } catch { setError("Failed to create chat"); } finally { setLoading(false); } } function closeDialog() { setDialog("none"); setError(null); setQuery(""); setResults([]); setIsPublic(false); setInviteToken(""); } return ( <>
{menuOpen ? (
) : null}
{dialog !== "none" ? (

{dialog === "private" ? "New Message" : dialog === "group" ? "New Group" : dialog === "channel" ? "New Channel" : "Join by Link"}

{dialog === "private" ? (
void handleSearchUsers(e.target.value)} />
{results.map((user) => ( ))} {normalizedQuery && results.length === 0 ?

No users

: null}
) : null} {dialog === "group" || dialog === "channel" ? (
void createByType(e, dialog)}> setTitle(e.target.value)} /> {isPublic ? ( setHandle(e.target.value)} /> ) : null} setDescription(e.target.value)} />
) : null} {dialog === "invite" ? (
{ e.preventDefault(); if (!inviteToken.trim()) { setError("Invite token is required"); return; } setLoading(true); setError(null); try { const raw = inviteToken.trim(); const match = raw.match(/[?&]token=([^&]+)/i); const token = match ? decodeURIComponent(match[1]) : raw; const chat = await joinByInvite(token); await refreshChatsAndSelect(chat.id); closeDialog(); } catch { setError("Failed to join by invite"); } finally { setLoading(false); } }} > setInviteToken(e.target.value)} />
) : null} {error ?

{error}

: null}
) : null} ); }