import { useLayoutEffect, useState } from 'react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useNavigate } from 'react-router-dom'; import { Icon } from '@/app/Icon'; import { Toast } from '@/app/Toast'; import { fetchAllPages } from '@/lib/api/client'; import { blockUser, deleteFriend, getBlocks, getConversationSettings, getFriends, getUserProfile, unblockUser, updateConversationSettings, updateFriendRemark, } from '@/lib/api/friends'; import type { MediaType } from '@/lib/api/messages'; import { avatarBgClass, avatarLetter } from '@/lib/avatar'; import { useTinode } from '@/lib/tinode/hooks'; import { useCallStore } from '@/stores/calls'; interface DetailColumnProps { topicId: string; peerName: string; onClose?: () => void; onOpenHistory: (tab: 'text' | MediaType) => void; } function Toggle({ checked, onChange, disabled }: { checked: boolean; onChange: () => void; disabled?: boolean }) { return ( ); } export function DetailColumn({ topicId, peerName, onClose, onOpenHistory }: DetailColumnProps) { const navigate = useNavigate(); const tinode = useTinode(); const queryClient = useQueryClient(); const [remarkEditing, setRemarkEditing] = useState(false); const [remarkDraft, setRemarkDraft] = useState(''); const [errorToast, setErrorToast] = useState(''); const showError = (err: unknown, fallback: string) => setErrorToast(err instanceof Error ? err.message : fallback); // DetailColumn is reused across every contact when docked (no `key={topicId}`, same instance), // so without this an in-progress remark edit for the PREVIOUS contact stays open and could be // saved against the NEW one, and a leftover error toast would keep showing over the new contact // misattributing the failure. useLayoutEffect (not useEffect) so this resolves before paint -- // this component never remounts on topicId change, so a post-paint effect can let one frame // render with the previous contact's stale edit box still open. useLayoutEffect(() => { setRemarkEditing(false); setRemarkDraft(''); setErrorToast(''); }, [topicId]); const friendsQuery = useQuery({ queryKey: ['friends'], queryFn: () => fetchAllPages((cursor) => getFriends('', cursor)) }); const friend = friendsQuery.data?.find((f) => f.user_id === topicId); const profileQuery = useQuery({ queryKey: ['user-profile', topicId], queryFn: () => getUserProfile(topicId) }); const idLine = [profileQuery.data?.public_account && `ID:${profileQuery.data.public_account}`, profileQuery.data?.region] .filter(Boolean) .join(' · '); const settingsQuery = useQuery({ queryKey: ['conversation-settings', topicId], queryFn: () => getConversationSettings(topicId), }); const updateSettings = useMutation({ mutationFn: (patch: { pinned?: boolean; mute_notifications?: boolean }) => updateConversationSettings(topicId, { ...patch, version: settingsQuery.data?.version ?? 0 }), onSuccess: (data) => queryClient.setQueryData(['conversation-settings', topicId], data), onError: (err) => showError(err, '设置失败,请重试'), }); const remarkMutation = useMutation({ mutationFn: (remark: string) => updateFriendRemark(topicId, remark), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['friends'] }); setRemarkEditing(false); }, onError: (err) => showError(err, '备注保存失败'), }); const blockMutation = useMutation({ mutationFn: (blocked: boolean) => (blocked ? blockUser(topicId) : unblockUser(topicId)), onSuccess: () => queryClient.invalidateQueries({ queryKey: ['blocks'] }), onError: (err) => showError(err, '设置失败,请重试'), }); const blocksQuery = useQuery({ queryKey: ['blocks'], queryFn: () => fetchAllPages((cursor) => getBlocks(cursor)) }); const isBlocked = !!blocksQuery.data?.some((b) => b.user.user_id === topicId); const deleteFriendMutation = useMutation({ mutationFn: () => deleteFriend(topicId), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['friends'] }); navigate('/messages'); }, onError: (err) => showError(err, '删除好友失败'), }); const clearHistory = () => { if (!window.confirm('确定清空与该联系人的聊天记录吗?此操作仅清除本地记录,不影响对方。')) return; tinode.getTopic(topicId).delMessagesAll(false); }; const startDeleteFriend = () => { if (!window.confirm(`确定删除好友 ${peerName} 吗?`)) return; deleteFriendMutation.mutate(); }; const reportAbuse = () => { window.alert('该功能暂未开放,如需投诉举报请联系客服。'); }; return ( ); }