import { useEffect, useMemo, useState } from "react"; import { createPortal } from "react-dom"; import { addChatMember, getChatNotificationSettings, getChatDetail, leaveChat, listChatMembers, removeChatMember, updateChatNotificationSettings, updateChatMemberRole, updateChatTitle } from "../api/chats"; import { blockUser, getUserById, listBlockedUsers, searchUsers, unblockUser } from "../api/users"; import type { AuthUser, ChatDetail, ChatMember, UserSearchItem } from "../chat/types"; import { useAuthStore } from "../store/authStore"; import { useChatStore } from "../store/chatStore"; interface Props { chatId: number | null; open: boolean; onClose: () => void; } export function ChatInfoPanel({ chatId, open, onClose }: Props) { const me = useAuthStore((s) => s.me); const loadChats = useChatStore((s) => s.loadChats); const setActiveChatId = useChatStore((s) => s.setActiveChatId); const [chat, setChat] = useState(null); const [members, setMembers] = useState([]); const [memberUsers, setMemberUsers] = useState>({}); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [titleDraft, setTitleDraft] = useState(""); const [savingTitle, setSavingTitle] = useState(false); const [searchQuery, setSearchQuery] = useState(""); const [searchResults, setSearchResults] = useState([]); const [muted, setMuted] = useState(false); const [savingMute, setSavingMute] = useState(false); const [counterpartBlocked, setCounterpartBlocked] = useState(false); const [savingBlock, setSavingBlock] = useState(false); const myRole = useMemo(() => members.find((m) => m.user_id === me?.id)?.role, [members, me?.id]); const isGroupLike = chat?.type === "group" || chat?.type === "channel"; const showMembersSection = Boolean(chat && isGroupLike && !chat.is_saved); const canManageMembers = Boolean(isGroupLike && (myRole === "owner" || myRole === "admin")); const canChangeRoles = Boolean(isGroupLike && myRole === "owner"); async function refreshMembers(targetChatId: number) { const nextMembers = await listChatMembers(targetChatId); setMembers(nextMembers); const ids = [...new Set(nextMembers.map((m) => m.user_id))]; const profiles = await Promise.all(ids.map((id) => getUserById(id))); const byId: Record = {}; for (const profile of profiles) { byId[profile.id] = profile; } setMemberUsers(byId); } useEffect(() => { if (!open || !chatId) { return; } let cancelled = false; setLoading(true); setError(null); void (async () => { try { const detail = await getChatDetail(chatId); if (cancelled) return; setChat(detail); setTitleDraft(detail.title ?? ""); const notificationSettings = await getChatNotificationSettings(chatId); if (!cancelled) { setMuted(notificationSettings.muted); } if (detail.type === "private" && !detail.is_saved && detail.counterpart_user_id) { const blocked = await listBlockedUsers(); if (!cancelled) { setCounterpartBlocked(blocked.some((u) => u.id === detail.counterpart_user_id)); } } else if (!cancelled) { setCounterpartBlocked(false); } await refreshMembers(chatId); } catch { if (!cancelled) setError("Failed to load chat info"); } finally { if (!cancelled) setLoading(false); } })(); return () => { cancelled = true; }; }, [open, chatId]); useEffect(() => { const onKeyDown = (event: KeyboardEvent) => { if (event.key === "Escape") { onClose(); } }; if (open) { window.addEventListener("keydown", onKeyDown); } return () => window.removeEventListener("keydown", onKeyDown); }, [open, onClose]); if (!open || !chatId) { return null; } return createPortal(
, document.body ); } function formatLastSeen(value: string): string { const date = new Date(value); if (Number.isNaN(date.getTime())) { return "recently"; } return date.toLocaleString(undefined, { day: "2-digit", month: "short", hour: "2-digit", minute: "2-digit" }); }