import { useState } from 'react'; import { useQueryClient } from '@tanstack/react-query'; import { Link, useNavigate } from 'react-router-dom'; import { Icon } from '@/app/Icon'; import { ImApiError } from '@/lib/api/client'; import { resolveTenant } from '@/lib/api/tenant'; import { BASE_APP_NAME } from '@/lib/config'; import { resetTinode } from '@/lib/tinode/connection'; import { requestConfirm } from '@/stores/confirmDialog'; 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(''); const [submitting, setSubmitting] = useState(false); const submit = async (e: React.FormEvent) => { e.preventDefault(); if (!code.trim() || submitting) return; // Reachable by navigating back here while already logged in (no route guard keeps an // authenticated user out of /tenant) -- resetTinode() below would otherwise silently tear down // that live session with no warning at all. `myUserId` alone isn't enough: it's an in-memory // Zustand value that resets to null on any fresh mount of this module (a page reload while // sitting on /tenant, or opening this URL directly in a new tab) even though the session token // is still sitting in sessionStorage and would otherwise restore fine -- confirmed by reading // RequireAuth.tsx, which is the ONLY place that re-populates `myUserId` from that token, and it // never mounts for this route. Checking the token directly closes that gap. const hasLiveSession = myUserId || sessionStorage.getItem('im-session-token'); if (hasLiveSession && !(await requestConfirm({ message: '切换企业将退出当前登录,确定继续吗?' }))) return; setSubmitting(true); setError(''); try { const res = await resolveTenant(code.trim()); const { host, secure } = parseWebsocketEndpoint(res.endpoints.websocket); // Without this, switching tenants mid-session silently keeps talking to the OLD tenant's // 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(); // 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 ` 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 : '企业码不存在或已停用,请核实后重试'); } finally { setSubmitting(false); } }; return (
企业注册
{BASE_APP_NAME}
{error ? (
{error}
) : null}
遇到问题?
); }