feat(web): real P2P chat-settings detail panel
Replaces the detail-column placeholder with the actual "聊天设置" panel
(README §3.4): profile header, quick-action buttons, remark, pin/mute
toggles, blacklist, clear-history, delete-friend -- backed by real REST
endpoints (lib/api/friends.ts, ported from the old app's real
contact-management-view.jsx settings panel, not its unrelated mock
direct-message-view.jsx).
First real use of TanStack Query in this app (useQuery for
friends/conversation-settings/blocks, useMutation for the writes) --
previously only wired up in App.tsx with nothing consuming it yet.
Verified end to end: pin toggle PATCHes /conversations/{topic}/settings
and persists; remark PATCHes /friends/{userId} and reflects immediately
via query invalidation; blacklist PUT/DELETEs /blocks/{userId}.
Known minor gap: the drawer variant (960-1279px tier) stays mounted
(CSS-hidden) alongside the docked panel at >=1280px rather than
unmounting, so both instances' queries run -- harmless since TanStack
Query dedupes/shares the cache by key, but worth a follow-up cleanup.
Also still missing: the <960px "detail settings push as a full page"
variant the mobile spec calls for.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
f549248c08
commit
7f5756671d
@@ -2,24 +2,9 @@ import { useState } from 'react';
|
||||
|
||||
import { Outlet, useParams } from 'react-router-dom';
|
||||
|
||||
import { Icon } from '@/app/Icon';
|
||||
import { ChatListColumn } from '@/features/messages/ChatListColumn';
|
||||
|
||||
function DetailColumn({ onClose }: { onClose?: () => void }) {
|
||||
return (
|
||||
<aside className="flex h-full w-full flex-none flex-col overflow-y-auto border-l border-border-panel bg-white md:w-[300px]">
|
||||
<header className="flex h-15 flex-none items-center justify-between border-b border-list-item px-5">
|
||||
<strong className="text-[15px] font-semibold">聊天设置</strong>
|
||||
{onClose ? (
|
||||
<button type="button" onClick={onClose} className="grid h-8 w-8 place-items-center text-text-tertiary-4">
|
||||
<Icon name="close" />
|
||||
</button>
|
||||
) : null}
|
||||
</header>
|
||||
<div className="p-5 text-sm text-text-secondary">详情栏内容占位(阶段1实现)</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
import { DetailColumn } from '@/features/messages/DetailColumn';
|
||||
import { useChatList } from '@/lib/tinode/hooks';
|
||||
|
||||
/**
|
||||
* The 4-tier responsive split described in the design handoff:
|
||||
@@ -33,6 +18,8 @@ export function MessagesLayout() {
|
||||
const { topicId } = useParams();
|
||||
const [detailOpen, setDetailOpen] = useState(false);
|
||||
const showListOnNarrow = !topicId;
|
||||
const contacts = useChatList();
|
||||
const peerName = contacts.find((c) => c.topic === topicId)?.public?.fn || topicId || '';
|
||||
|
||||
return (
|
||||
<div className="relative flex h-full w-full">
|
||||
@@ -42,14 +29,16 @@ export function MessagesLayout() {
|
||||
<div className={`h-full min-w-0 flex-1 ${showListOnNarrow ? 'hidden md:block' : ''}`}>
|
||||
<Outlet context={{ onToggleDetail: () => setDetailOpen((v) => !v) }} />
|
||||
</div>
|
||||
<div className="hidden h-full lg:block">
|
||||
<DetailColumn />
|
||||
</div>
|
||||
{detailOpen ? (
|
||||
{topicId ? (
|
||||
<div className="hidden h-full lg:block">
|
||||
<DetailColumn topicId={topicId} peerName={peerName} />
|
||||
</div>
|
||||
) : null}
|
||||
{detailOpen && topicId ? (
|
||||
<div className="absolute inset-0 hidden md:block lg:hidden">
|
||||
<div className="absolute inset-0 bg-[#22262d]/25" onClick={() => setDetailOpen(false)} />
|
||||
<div className="absolute inset-y-0 right-0 shadow-[-8px_0_32px_rgba(0,0,0,0.12)]">
|
||||
<DetailColumn onClose={() => setDetailOpen(false)} />
|
||||
<DetailColumn topicId={topicId} peerName={peerName} onClose={() => setDetailOpen(false)} />
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
Reference in New Issue
Block a user