feat(web): complete groups/contacts/favorites/settings/calls/system domains + extensive review-round fixes
Rounds out the full rewrite with the remaining feature domains: group management (admin panel, mute, files, announcements, QR-based invite/join), contacts (friend profile, add-friend, blocklist, new-friends), favorites (real media rendering across text/image/video/voice/file/link), settings (profile/security/privacy/general/about), calls (Agora-backed audio/video signaling + call history), and system messages/file assistant. Also folds in a large batch of live-verified fixes surfaced by repeated fresh code-review passes and hands-on E2E testing against the real backend, including: WS reconnect not resubscribing any topic after a disconnect, missing x-im-client-mid on every send path (protocol compliance for retry dedup), group mute/member-settings/self-profile endpoint corrections, relationship-state type fixes across search/QR/ friend-profile, tenant-switch logout gap, password-policy now read from the real per-tenant config instead of guessed, NavRail highlight/ aria-current desync, several call-state-machine races, and responsive layout fixes across all four breakpoints (mobile/tablet/laptop/desktop) including a full mobile rebuild of favorites, call UI, and the settings home/sub-list split. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
72b7b79caf
commit
5c3f3b7653
@@ -1,5 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
|
||||
import { Icon } from '@/app/Icon';
|
||||
@@ -10,7 +11,9 @@ import { parseWebsocketEndpoint, useSessionStore } from '@/stores/session';
|
||||
|
||||
export function TenantEntryPage() {
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
const setTenant = useSessionStore((s) => s.setTenant);
|
||||
const setMyUserId = useSessionStore((s) => s.setMyUserId);
|
||||
const myUserId = useSessionStore((s) => s.myUserId);
|
||||
const [code, setCode] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
@@ -32,7 +35,26 @@ export function TenantEntryPage() {
|
||||
// host: getTinode() lazily creates one singleton and reuses it forever once created, and
|
||||
// createTinode() only ever reads useSessionStore's tenant at that one construction moment.
|
||||
resetTinode();
|
||||
setTenant({ code: res.tenant.code, name: res.tenant.name, endpoints: res.endpoints, host, secure });
|
||||
// The confirm() dialog above literally promises "切换企业将退出当前登录" -- without these,
|
||||
// that was a false promise: `myUserId` is a separate module-level store that switching
|
||||
// tenant never touched, so RequireAuth's `if (myUserId) return <Outlet/>` would let a
|
||||
// subsequent in-app navigation straight through with the OLD tenant's uid and the OLD
|
||||
// tenant's sessionStorage token, against a freshly-reset Tinode connection that has never
|
||||
// authenticated with the NEW tenant at all. Same teardown SettingsLayout's own logout and
|
||||
// SecuritySettingsPage's account deletion already do.
|
||||
sessionStorage.removeItem('im-session-token');
|
||||
setMyUserId(null);
|
||||
queryClient.clear();
|
||||
setTenant({
|
||||
code: res.tenant.code,
|
||||
name: res.tenant.name,
|
||||
endpoints: res.endpoints,
|
||||
host,
|
||||
secure,
|
||||
passwordPolicy: res.password_policy
|
||||
? { minLength: res.password_policy.min_length ?? 8, maxLength: res.password_policy.max_length ?? 64 }
|
||||
: undefined,
|
||||
});
|
||||
navigate('/login');
|
||||
} catch (err) {
|
||||
setError(err instanceof ImApiError ? err.message : '企业码不存在或已停用,请核实后重试');
|
||||
|
||||
Reference in New Issue
Block a user