import { 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 { getBlocks } from '@/lib/api/friends';
import { getMyPrivacy, updateMyPrivacy, type AddMeMethods } from '@/lib/api/profile';
const ADD_ME_LABELS: { key: keyof AddMeMethods; label: string }[] = [
{ key: 'account', label: '通过用户 ID' },
{ key: 'phone', label: '通过手机号' },
{ key: 'qr', label: '通过二维码' },
{ key: 'group', label: '通过群聊' },
];
const VISIBILITY_OPTIONS = [
{ value: 'everyone', label: '所有人' },
{ value: 'friends', label: '仅好友' },
{ value: 'nobody', label: '不展示' },
];
function Toggle({ checked, onChange, disabled }: { checked: boolean; onChange: () => void; disabled?: boolean }) {
return (
);
}
/**
* screens/07-profile-settings.dc.html#1c: 添加我的方式 (4 toggles, ungrouped -- no dividers between
* them, just the section label above) sits in its own card; 黑名单 sits in a second card alone
* (that mockup also puts "加我为好友时需要验证" there, but that toggle has NO backing field anywhere
* in this response shape or the group-invite backend logic -- confirmed absent, not built). The
* visibility/read-receipts rows below have no mockup at all (added once the real backend fields were
* found during research) -- given their own third card rather than folding them into either of the
* above, so a real capability the design never anticipated doesn't look like it was crammed in.
*/
export function PrivacySettingsPage() {
const navigate = useNavigate();
const queryClient = useQueryClient();
const [error, setError] = useState('');
const query = useQuery({ queryKey: ['my-privacy'], queryFn: getMyPrivacy });
const blocksQuery = useQuery({ queryKey: ['blocks'], queryFn: () => fetchAllPages((cursor) => getBlocks(cursor)) });
const mutation = useMutation({
mutationFn: (patch: Parameters[0]) => updateMyPrivacy(patch),
// Merge, don't replace -- the doc's own worked example for this exact endpoint (line 2494)
// returns a PARTIAL object (`{add_me_methods,read_receipts,version,updated_at}`, missing
// `profile_visibility`/`last_seen_visibility`/`editable`). A blind replace would erase those
// from the cache after every single save, most importantly `editable`: once it silently
// disappeared, `const editable = query.data.editable || {}` re-derives an EMPTY object, which
// re-enables every field a tenant policy had actually locked, until an unrelated refetch happens
// to restore it.
onSuccess: (data) => queryClient.setQueryData(['my-privacy'], (prev: typeof query.data) => (prev ? { ...prev, ...data } : data)),
// Same reasoning as ProfileSettingsPage's own saveMutation: a version conflict shouldn't leave
// the cached `version` stale, or every retry just resends the same rejected value forever.
onError: (err) => {
setError(err instanceof Error ? err.message : '设置失败');
queryClient.invalidateQueries({ queryKey: ['my-privacy'] });
},
});
const patch = (partial: Partial[0], 'version'>>) => {
if (!query.data) return;
mutation.mutate({ ...partial, version: query.data.version });
};
if (query.isLoading) return
加载中…
;
// `isLoading` alone used to gate this: it turns false once the fetch SETTLES, including into an
// error, but `query.data` stays undefined either way -- the page was falling through to the same
// "加载中…" text forever on a genuine failure (a slow/dropped connection, not just "still loading"),
// with no retry affordance at all. Same fix already applied to SystemMessagesView's own query.
if (query.isError || !query.data) {
return (