import React, { useEffect, useMemo, useState } from "react";
import { API_BASE, emptyForm, formatIsoDateTimeReadable, parseEmailList } from "../config";
import icons from "../icons";
import { IconLabel, MailtoEmail, TableCell } from "./common";

export default function AdminPanel({ onReady }) {
  const queryToken = useMemo(() => {
    const params = new URLSearchParams(window.location.search);
    return params.get("token") || "";
  }, []);

  const [token, setToken] = useState(queryToken || localStorage.getItem("sproutgate_admin_token") || "");
  const [users, setUsers] = useState([]);
  const [form, setForm] = useState(emptyForm);
  const [selectedAccount, setSelectedAccount] = useState("");
  const [editorOpen, setEditorOpen] = useState(false);
  const [editorMode, setEditorMode] = useState("create");
  const [loading, setLoading] = useState(false);
  const [configLoading, setConfigLoading] = useState(false);
  const [configSaving, setConfigSaving] = useState(false);
  const [error, setError] = useState("");
  const [configError, setConfigError] = useState("");
  const [message, setMessage] = useState("");
  const [configMessage, setConfigMessage] = useState("");
  const [checkInConfig, setCheckInConfig] = useState({ rewardCoins: 1 });
  const [readySent, setReadySent] = useState(false);

  const [regLoading, setRegLoading] = useState(false);
  const [regSaving, setRegSaving] = useState(false);
  const [regInvCreating, setRegInvCreating] = useState(false);
  const [regError, setRegError] = useState("");
  const [regMessage, setRegMessage] = useState("");
  const [requireInviteReg, setRequireInviteReg] = useState(false);
  const [forbiddenAccountsReg, setForbiddenAccountsReg] = useState("");
  const [inviteRegisterRewardReg, setInviteRegisterRewardReg] = useState(10);
  const [inviteList, setInviteList] = useState([]);
  const [newInvNote, setNewInvNote] = useState("");
  const [newInvMax, setNewInvMax] = useState(0);
  const [newInvExp, setNewInvExp] = useState("");

  const [oauthLoading, setOauthLoading] = useState(false);
  const [oauthSaving, setOauthSaving] = useState(false);
  const [oauthError, setOauthError] = useState("");
  const [oauthMessage, setOauthMessage] = useState("");
  const [oauthGithubEnabled, setOauthGithubEnabled] = useState(false);
  const [oauthGiteaEnabled, setOauthGiteaEnabled] = useState(false);
  const [oauthGoogleEnabled, setOauthGoogleEnabled] = useState(false);
  const [oauthMicrosoftEnabled, setOauthMicrosoftEnabled] = useState(false);
  const [oauthGithubClientId, setOauthGithubClientId] = useState("");
  const [oauthGithubClientSecret, setOauthGithubClientSecret] = useState("");
  const [oauthGiteaBase, setOauthGiteaBase] = useState("https://git.shumengya.top");
  const [oauthGiteaClientId, setOauthGiteaClientId] = useState("");
  const [oauthGiteaClientSecret, setOauthGiteaClientSecret] = useState("");
  const [oauthGoogleClientId, setOauthGoogleClientId] = useState("");
  const [oauthGoogleClientSecret, setOauthGoogleClientSecret] = useState("");
  const [oauthMicrosoftClientId, setOauthMicrosoftClientId] = useState("");
  const [oauthMicrosoftClientSecret, setOauthMicrosoftClientSecret] = useState("");
  const [oauthMicrosoftTenant, setOauthMicrosoftTenant] = useState("common");
  const [oauthLinuxdoEnabled, setOauthLinuxdoEnabled] = useState(false);
  const [oauthLinuxdoConnectBase, setOauthLinuxdoConnectBase] = useState("https://connect.linux.do");
  const [oauthLinuxdoClientId, setOauthLinuxdoClientId] = useState("");
  const [oauthLinuxdoClientSecret, setOauthLinuxdoClientSecret] = useState("");
  const [oauthReturnPrefixes, setOauthReturnPrefixes] = useState("http://localhost:5173/\nhttp://127.0.0.1:5173/");
  const [oauthSignUpWithInvite, setOauthSignUpWithInvite] = useState(false);
  const [oauthGithubSecretSet, setOauthGithubSecretSet] = useState(false);
  const [oauthGiteaSecretSet, setOauthGiteaSecretSet] = useState(false);
  const [oauthGoogleSecretSet, setOauthGoogleSecretSet] = useState(false);
  const [oauthMicrosoftSecretSet, setOauthMicrosoftSecretSet] = useState(false);
  const [oauthLinuxdoSecretSet, setOauthLinuxdoSecretSet] = useState(false);
  const [oauthGithubLogoUrl, setOauthGithubLogoUrl] = useState("");
  const [oauthGiteaLogoUrl, setOauthGiteaLogoUrl] = useState("");
  const [oauthGoogleLogoUrl, setOauthGoogleLogoUrl] = useState("");
  const [oauthMicrosoftLogoUrl, setOauthMicrosoftLogoUrl] = useState("");
  const [oauthLinuxdoLogoUrl, setOauthLinuxdoLogoUrl] = useState("");

  const [tsLoading, setTsLoading] = useState(false);
  const [tsSaving, setTsSaving] = useState(false);
  const [tsError, setTsError] = useState("");
  const [tsMessage, setTsMessage] = useState("");
  const [tsEnabled, setTsEnabled] = useState(false);
  const [tsSiteKey, setTsSiteKey] = useState("");
  const [tsSecretKey, setTsSecretKey] = useState("");
  const [tsSecretKeySet, setTsSecretKeySet] = useState(false);

  useEffect(() => {
    if (token) {
      localStorage.setItem("sproutgate_admin_token", token);
      loadUsers();
      loadCheckInConfig();
      loadRegistration();
      loadOAuth();
      loadTurnstile();
    } else if (onReady && !readySent) {
      onReady();
      setReadySent(true);
    }
  }, [token, onReady, readySent]);

  const loadUsers = async () => {
    if (!token) return;
    setLoading(true);
    setError("");
    try {
      const res = await fetch(`${API_BASE}/api/admin/users`, {
        headers: { "X-Admin-Token": token }
      });
      const data = await res.json();
      if (!res.ok) throw new Error(data.error || "加载用户失败");
      setUsers(data.users || []);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
      if (onReady && !readySent) {
        onReady();
        setReadySent(true);
      }
    }
  };

  const loadCheckInConfig = async () => {
    if (!token) return;
    setConfigLoading(true);
    setConfigError("");
    try {
      const res = await fetch(`${API_BASE}/api/admin/check-in/config`, {
        headers: { "X-Admin-Token": token }
      });
      const data = await res.json();
      if (!res.ok) throw new Error(data.error || "加载签到配置失败");
      setCheckInConfig({ rewardCoins: Number(data.rewardCoins) || 1 });
    } catch (err) {
      setConfigError(err.message);
    } finally {
      setConfigLoading(false);
    }
  };

  const loadOAuth = async () => {
    if (!token) return;
    setOauthLoading(true);
    setOauthError("");
    try {
      const res = await fetch(`${API_BASE}/api/admin/oauth`, {
        headers: { "X-Admin-Token": token }
      });
      const data = await res.json();
      if (!res.ok) throw new Error(data.error || "加载 OAuth 配置失败");
      setOauthGithubEnabled(Boolean(data.githubEnabled));
      setOauthGiteaEnabled(Boolean(data.giteaEnabled));
      setOauthGithubClientId((data.githubClientId || "").trim());
      setOauthGithubClientSecret("");
      setOauthGithubSecretSet(Boolean(data.githubClientSecretSet));
      setOauthGiteaBase((data.giteaBaseUrl || "https://git.shumengya.top").trim());
      setOauthGiteaClientId((data.giteaClientId || "").trim());
      setOauthGiteaClientSecret("");
      setOauthGiteaSecretSet(Boolean(data.giteaClientSecretSet));
      setOauthGoogleEnabled(Boolean(data.googleEnabled));
      setOauthMicrosoftEnabled(Boolean(data.microsoftEnabled));
      setOauthGoogleClientId((data.googleClientId || "").trim());
      setOauthGoogleClientSecret("");
      setOauthGoogleSecretSet(Boolean(data.googleClientSecretSet));
      setOauthMicrosoftClientId((data.microsoftClientId || "").trim());
      setOauthMicrosoftClientSecret("");
      setOauthMicrosoftSecretSet(Boolean(data.microsoftClientSecretSet));
      setOauthMicrosoftTenant((data.microsoftTenant || "common").trim() || "common");
      setOauthLinuxdoEnabled(Boolean(data.linuxdoEnabled));
      setOauthLinuxdoConnectBase((data.linuxdoConnectBaseUrl || "https://connect.linux.do").trim());
      setOauthLinuxdoClientId((data.linuxdoClientId || "").trim());
      setOauthLinuxdoClientSecret("");
      setOauthLinuxdoSecretSet(Boolean(data.linuxdoClientSecretSet));
      setOauthGithubLogoUrl((data.githubLogoUrl || "").trim());
      setOauthGiteaLogoUrl((data.giteaLogoUrl || "").trim());
      setOauthGoogleLogoUrl((data.googleLogoUrl || "").trim());
      setOauthMicrosoftLogoUrl((data.microsoftLogoUrl || "").trim());
      setOauthLinuxdoLogoUrl((data.linuxdoLogoUrl || "").trim());
      const pfx = data.allowedReturnPrefixes;
      setOauthReturnPrefixes(
        Array.isArray(pfx) && pfx.length ? pfx.join("\n") : "http://localhost:5173/\nhttp://127.0.0.1:5173/"
      );
      setOauthSignUpWithInvite(Boolean(data.allowOAuthSignUpWhenInviteRequired));
    } catch (err) {
      setOauthError(err.message);
    } finally {
      setOauthLoading(false);
    }
  };

  const saveOAuth = async () => {
    if (!token) return;
    setOauthMessage("");
    setOauthError("");
    setOauthSaving(true);
    const prefixes = oauthReturnPrefixes
      .split(/[\n,]+/)
      .map((s) => s.trim())
      .filter(Boolean);
    try {
      const res = await fetch(`${API_BASE}/api/admin/oauth`, {
        method: "PUT",
        headers: { "Content-Type": "application/json", "X-Admin-Token": token },
        body: JSON.stringify({
          githubEnabled: oauthGithubEnabled,
          giteaEnabled: oauthGiteaEnabled,
          googleEnabled: oauthGoogleEnabled,
          microsoftEnabled: oauthMicrosoftEnabled,
          linuxdoEnabled: oauthLinuxdoEnabled,
          githubClientId: oauthGithubClientId.trim(),
          githubClientSecret: oauthGithubClientSecret,
          giteaBaseUrl: oauthGiteaBase.trim() || "https://git.shumengya.top",
          giteaClientId: oauthGiteaClientId.trim(),
          giteaClientSecret: oauthGiteaClientSecret,
          googleClientId: oauthGoogleClientId.trim(),
          googleClientSecret: oauthGoogleClientSecret,
          microsoftClientId: oauthMicrosoftClientId.trim(),
          microsoftClientSecret: oauthMicrosoftClientSecret,
          microsoftTenant: oauthMicrosoftTenant.trim() || "common",
          linuxdoConnectBaseUrl: oauthLinuxdoConnectBase.trim() || "https://connect.linux.do",
          linuxdoClientId: oauthLinuxdoClientId.trim(),
          linuxdoClientSecret: oauthLinuxdoClientSecret,
          githubLogoUrl: oauthGithubLogoUrl.trim(),
          giteaLogoUrl: oauthGiteaLogoUrl.trim(),
          googleLogoUrl: oauthGoogleLogoUrl.trim(),
          microsoftLogoUrl: oauthMicrosoftLogoUrl.trim(),
          linuxdoLogoUrl: oauthLinuxdoLogoUrl.trim(),
          allowedReturnPrefixes: prefixes,
          allowOAuthSignUpWhenInviteRequired: oauthSignUpWithInvite
        })
      });
      const data = await res.json();
      if (!res.ok) throw new Error(data.error || "保存失败");
      setOauthMessage("第三方登录配置已保存");
      setOauthGithubClientSecret("");
      setOauthGiteaClientSecret("");
      setOauthGoogleClientSecret("");
      setOauthMicrosoftClientSecret("");
      setOauthLinuxdoClientSecret("");
      setOauthGithubSecretSet(Boolean(data.githubClientSecretSet));
      setOauthGiteaSecretSet(Boolean(data.giteaClientSecretSet));
      setOauthGoogleSecretSet(Boolean(data.googleClientSecretSet));
      setOauthMicrosoftSecretSet(Boolean(data.microsoftClientSecretSet));
      setOauthLinuxdoSecretSet(Boolean(data.linuxdoClientSecretSet));
    } catch (err) {
      setOauthError(err.message);
    } finally {
      setOauthSaving(false);
    }
  };

  const loadTurnstile = async () => {
    if (!token) return;
    setTsLoading(true);
    setTsError("");
    try {
      const res = await fetch(`${API_BASE}/api/admin/turnstile`, {
        headers: { "X-Admin-Token": token }
      });
      const data = await res.json();
      if (!res.ok) throw new Error(data.error || "加载 Turnstile 配置失败");
      setTsEnabled(Boolean(data.enabled));
      setTsSiteKey((data.siteKey || "").trim());
      setTsSecretKey("");
      setTsSecretKeySet(Boolean(data.secretKeySet));
    } catch (err) {
      setTsError(err.message);
    } finally {
      setTsLoading(false);
    }
  };

  const saveTurnstile = async () => {
    if (!token) return;
    setTsMessage("");
    setTsError("");
    setTsSaving(true);
    try {
      const res = await fetch(`${API_BASE}/api/admin/turnstile`, {
        method: "PUT",
        headers: { "Content-Type": "application/json", "X-Admin-Token": token },
        body: JSON.stringify({
          enabled: tsEnabled,
          siteKey: tsSiteKey.trim(),
          secretKey: tsSecretKey
        })
      });
      const data = await res.json();
      if (!res.ok) throw new Error(data.error || "保存失败");
      setTsMessage("Turnstile 配置已保存");
      setTsSecretKey("");
      setTsSecretKeySet(Boolean(data.secretKeySet));
    } catch (err) {
      setTsError(err.message);
    } finally {
      setTsSaving(false);
    }
  };

  const loadRegistration = async () => {
    if (!token) return;
    setRegLoading(true);
    setRegError("");
    try {
      const res = await fetch(`${API_BASE}/api/admin/registration`, {
        headers: { "X-Admin-Token": token }
      });
      const data = await res.json();
      if (!res.ok) throw new Error(data.error || "加载注册策略失败");
      setRequireInviteReg(Boolean(data.requireInviteCode));
      setForbiddenAccountsReg(
        typeof data.forbiddenAccounts === "string" ? data.forbiddenAccounts : ""
      );
      const ir = Number(data.inviteRegisterRewardCoins);
      setInviteRegisterRewardReg(Number.isFinite(ir) && ir >= 0 ? ir : 10);
      setInviteList(data.invites || []);
    } catch (err) {
      setRegError(err.message);
    } finally {
      setRegLoading(false);
    }
  };

  const saveRegPolicy = async () => {
    if (!token) return;
    setRegMessage("");
    setRegError("");
    setRegSaving(true);
    try {
      const res = await fetch(`${API_BASE}/api/admin/registration`, {
        method: "PUT",
        headers: { "Content-Type": "application/json", "X-Admin-Token": token },
        body: JSON.stringify({
          requireInviteCode: requireInviteReg,
          forbiddenAccounts: forbiddenAccountsReg,
          inviteRegisterRewardCoins: Math.max(0, Math.floor(Number(inviteRegisterRewardReg)) || 0)
        })
      });
      const data = await res.json();
      if (!res.ok) throw new Error(data.error || "保存失败");
      setRequireInviteReg(Boolean(data.requireInviteCode));
      if (typeof data.forbiddenAccounts === "string") {
        setForbiddenAccountsReg(data.forbiddenAccounts);
      }
      const irs = Number(data.inviteRegisterRewardCoins);
      if (Number.isFinite(irs) && irs >= 0) setInviteRegisterRewardReg(irs);
      setRegMessage("注册策略已保存");
    } catch (err) {
      setRegError(err.message);
    } finally {
      setRegSaving(false);
    }
  };

  const handleCreateInvite = async () => {
    if (!token) return;
    setRegMessage("");
    setRegError("");
    let expiresAt = "";
    if (newInvExp.trim()) {
      const d = new Date(newInvExp);
      if (Number.isNaN(d.getTime())) {
        setRegError("过期时间无效");
        return;
      }
      expiresAt = d.toISOString();
    }
    setRegInvCreating(true);
    try {
      const res = await fetch(`${API_BASE}/api/admin/registration/invites`, {
        method: "POST",
        headers: { "Content-Type": "application/json", "X-Admin-Token": token },
        body: JSON.stringify({
          note: newInvNote.trim(),
          maxUses: Number(newInvMax) || 0,
          expiresAt
        })
      });
      const data = await res.json();
      if (!res.ok) throw new Error(data.error || "生成失败");
      const inv = data.invite;
      setInviteList((prev) => [...prev, inv]);
      setRegMessage(`已生成邀请码：${inv.code}（请复制保存）`);
      setNewInvNote("");
      setNewInvMax(0);
      setNewInvExp("");
    } catch (err) {
      setRegError(err.message);
    } finally {
      setRegInvCreating(false);
    }
  };

  const handleDeleteInvite = async (code) => {
    if (!token || !code) return;
    if (!window.confirm(`确认删除邀请码 ${code} 吗？`)) return;
    setRegMessage("");
    setRegError("");
    try {
      const res = await fetch(
        `${API_BASE}/api/admin/registration/invites/${encodeURIComponent(code)}`,
        { method: "DELETE", headers: { "X-Admin-Token": token } }
      );
      const data = await res.json();
      if (!res.ok) throw new Error(data.error || "删除失败");
      setInviteList((prev) => prev.filter((x) => x.code !== code));
      setRegMessage("已删除邀请码");
    } catch (err) {
      setRegError(err.message);
    }
  };

  const selectUser = (user) => {
    setSelectedAccount(user.account);
    setForm({
      account: user.account,
      password: "",
      username: user.username || "",
      email: user.email || "",
      level: user.level ?? 0,
      sproutCoins: user.sproutCoins || 0,
      secondaryEmails: (user.secondaryEmails || []).join(","),
      phone: user.phone || "",
      avatarUrl: user.customAvatarUrl ?? "",
      websiteUrl: user.websiteUrl || "",
      bio: user.bio || "",
      banned: Boolean(user.banned),
      banReason: user.banReason || ""
    });
    setMessage("");
    setError("");
    setEditorMode("edit");
    setEditorOpen(true);
  };

  const clearSelection = () => {
    setSelectedAccount("");
    setForm(emptyForm);
  };

  const openCreateUser = () => {
    clearSelection();
    setEditorMode("create");
    setMessage("");
    setError("");
    setEditorOpen(true);
  };

  const closeEditor = () => {
    setEditorOpen(false);
    clearSelection();
    setError("");
  };

  const handleChange = (field, value) => {
    setForm((prev) => ({ ...prev, [field]: value }));
  };

  const handleCheckInConfigChange = (value) => {
    setCheckInConfig({ rewardCoins: value });
  };

  const handleSaveCheckInConfig = async () => {
    setConfigMessage("");
    setConfigError("");
    const rewardCoins = Number(checkInConfig.rewardCoins) || 0;
    if (rewardCoins <= 0) { setConfigError("奖励萌芽币必须大于 0"); return; }
    try {
      setConfigSaving(true);
      const res = await fetch(`${API_BASE}/api/admin/check-in/config`, {
        method: "PUT",
        headers: { "Content-Type": "application/json", "X-Admin-Token": token },
        body: JSON.stringify({ rewardCoins })
      });
      const data = await res.json();
      if (!res.ok) throw new Error(data.error || "保存签到配置失败");
      setCheckInConfig({ rewardCoins: Number(data.rewardCoins) || rewardCoins });
      setConfigMessage("签到配置已保存");
    } catch (err) {
      setConfigError(err.message);
    } finally {
      setConfigSaving(false);
    }
  };

  const handleCreate = async () => {
    setMessage("");
    setError("");
    if (!form.account || !form.password) { setError("新建用户需要账户和密码"); return; }
    try {
      const res = await fetch(`${API_BASE}/api/admin/users`, {
        method: "POST",
        headers: { "Content-Type": "application/json", "X-Admin-Token": token },
        body: JSON.stringify({
          account: form.account,
          password: form.password,
          username: form.username,
          email: form.email,
          level: Number(form.level) || 0,
          sproutCoins: Number(form.sproutCoins) || 0,
          secondaryEmails: parseEmailList(form.secondaryEmails),
          phone: form.phone,
          avatarUrl: form.avatarUrl,
          websiteUrl: form.websiteUrl,
          bio: form.bio
        })
      });
      const data = await res.json();
      if (!res.ok) throw new Error(data.error || "创建失败");
      setMessage("创建成功");
      closeEditor();
      loadUsers();
    } catch (err) {
      setError(err.message);
    }
  };

  const handleUpdate = async () => {
    if (!selectedAccount) { setError("请选择需要更新的账户"); return; }
    setMessage("");
    setError("");
    const payload = {
      username: form.username,
      email: form.email,
      level: Number(form.level) || 0,
      sproutCoins: Number(form.sproutCoins) || 0,
      secondaryEmails: parseEmailList(form.secondaryEmails),
      phone: form.phone,
      avatarUrl: form.avatarUrl,
      websiteUrl: form.websiteUrl,
      bio: form.bio,
      banned: Boolean(form.banned),
      banReason: (form.banReason || "").trim()
    };
    if (form.password) payload.password = form.password;
    try {
      const res = await fetch(`${API_BASE}/api/admin/users/${encodeURIComponent(selectedAccount)}`, {
        method: "PUT",
        headers: { "Content-Type": "application/json", "X-Admin-Token": token },
        body: JSON.stringify(payload)
      });
      const data = await res.json();
      if (!res.ok) throw new Error(data.error || "更新失败");
      setMessage("更新成功");
      setForm((prev) => ({ ...prev, password: "" }));
      closeEditor();
      loadUsers();
    } catch (err) {
      setError(err.message);
    }
  };

  const handleDelete = async (account) => {
    if (!account) return;
    if (!window.confirm(`确认删除账户 ${account} 吗？`)) return;
    setMessage("");
    setError("");
    try {
      const res = await fetch(`${API_BASE}/api/admin/users/${encodeURIComponent(account)}`, {
        method: "DELETE",
        headers: { "X-Admin-Token": token }
      });
      const data = await res.json();
      if (!res.ok) throw new Error(data.error || "删除失败");
      setMessage("删除成功");
      if (account === selectedAccount) clearSelection();
      loadUsers();
    } catch (err) {
      setError(err.message);
    }
  };

  const editingUser = users.find((u) => u.account === selectedAccount);
  const editingAuthClients = editingUser?.authClients || [];

  const validAdminNav = useMemo(
    () => new Set(["general", "users", "checkin", "oauth", "turnstile", "registration"]),
    []
  );
  const [adminNav, setAdminNav] = useState(() => {
    if (typeof window === "undefined") return "general";
    const h = (window.location.hash || "").replace(/^#/, "");
    return validAdminNav.has(h) ? h : "general";
  });
  useEffect(() => {
    const onHash = () => {
      const h = (window.location.hash || "").replace(/^#/, "");
      if (validAdminNav.has(h)) setAdminNav(h);
    };
    window.addEventListener("hashchange", onHash);
    return () => window.removeEventListener("hashchange", onHash);
  }, [validAdminNav]);
  const goAdminNav = (id) => {
    if (!validAdminNav.has(id)) return;
    setAdminNav(id);
    if ((window.location.hash || "").replace(/^#/, "") !== id) {
      window.location.hash = id;
    }
  };

  return (
    <section className="panel admin-shell">
      <div className="admin-layout">
        <aside className="admin-sidebar" aria-label="管理模块">
          <div className="admin-sidebar-title">管理后台</div>
          <nav className="admin-sidebar-nav">
            {[
              { id: "general", label: "概览与身份" },
              { id: "users", label: "用户管理" },
              { id: "checkin", label: "签到设置" },
              { id: "oauth", label: "第三方登录" },
              { id: "turnstile", label: "人机验证" },
              { id: "registration", label: "注册与邀请" }
            ].map((item) => (
              <button
                key={item.id}
                type="button"
                className={adminNav === item.id ? "admin-sidebar-link is-active" : "admin-sidebar-link"}
                onClick={() => goAdminNav(item.id)}
              >
                {item.label}
              </button>
            ))}
          </nav>
        </aside>
        <div className="admin-main form admin-console">
          <h1 className="admin-main-title">管理员控制台</h1>

          {adminNav === "general" && (
        <div className="admin-section">
          <h2 className="admin-section-heading">管理员 Token</h2>
          <label>
            <IconLabel icon={icons.token} text="Token" />
            <input value={token} onChange={(e) => setToken(e.target.value.trim())} placeholder="请输入管理员 Token" />
          </label>
          <p className="hint">在左侧切换模块。填写 Token 后可使用「用户管理」等需鉴权的操作。</p>
        </div>
          )}

          {adminNav === "checkin" && (
        <div className="admin-section">
          <h2 className="admin-section-heading">签到设置</h2>
          <label>
            <IconLabel icon={icons.coins} text="签到奖励（萌芽币）" />
            <input
              type="number"
              min="1"
              value={checkInConfig.rewardCoins}
              onChange={(e) => handleCheckInConfigChange(e.target.value)}
              disabled={!token || configLoading}
            />
          </label>
          <div className="hint">用户每天首次签到会获得这里设置的奖励。</div>
          {configError && <div className="error">{configError}</div>}
          {configMessage && <div className="success">{configMessage}</div>}
          <div className="actions">
            <button className="primary" onClick={handleSaveCheckInConfig} disabled={!token || configSaving}>
              {configSaving ? "保存中..." : "保存设置"}
            </button>
          </div>
        </div>
          )}

          {adminNav === "oauth" && (
        <div className="admin-section">
          <h2 className="admin-section-heading">第三方登录（GitHub / Gitea / Google / Microsoft / LINUX DO）</h2>
          <p className="hint">
            在对应云控制台注册应用。回调地址在 API 公网根后加
            <code className="inline-code">/api/auth/oauth/github/callback</code>、
            <code className="inline-code">/api/auth/oauth/gitea/callback</code>、
            <code className="inline-code">/api/auth/oauth/google/callback</code>、
            <code className="inline-code">/api/auth/oauth/microsoft/callback</code>、
            <code className="inline-code">/api/auth/oauth/linuxdo/callback</code>。
            可设置
            <code className="inline-code">PUBLIC_API_BASE</code> 与 <code className="inline-code">GOOGLE_CLIENT_SECRET</code>、<code className="inline-code">MICROSOFT_CLIENT_SECRET</code> 等环境变量；见{" "}
            <code className="inline-code">docs/oauth-github-gitea-testing.md</code>。下方需配置「允许的回跳地址」。
          </p>
          <label className="admin-ban-row">
            <IconLabel icon={icons.link} text="启用 GitHub 登录" />
            <span className="admin-ban-toggle">
              <input
                type="checkbox"
                checked={oauthGithubEnabled}
                onChange={(e) => setOauthGithubEnabled(e.target.checked)}
                disabled={!token || oauthLoading}
              />
            </span>
          </label>
          <label>
            <IconLabel icon={icons.username} text="GitHub Client ID" />
            <input value={oauthGithubClientId} onChange={(e) => setOauthGithubClientId(e.target.value)} disabled={!token || oauthLoading} />
          </label>
          <label>
            <IconLabel icon={icons.token} text="GitHub Client Secret" hint={oauthGithubSecretSet ? "（已配置，留空则保留原值）" : ""} />
            <input
              type="password"
              autoComplete="new-password"
              value={oauthGithubClientSecret}
              onChange={(e) => setOauthGithubClientSecret(e.target.value)}
              placeholder={oauthGithubSecretSet ? "留空保留" : ""}
              disabled={!token || oauthLoading}
            />
          </label>
          <label>
            <IconLabel icon={icons.link} text="GitHub 登录按钮 Logo" hint="（选填，https 图片地址，用户中心胶囊按钮左侧）" />
            <input
              value={oauthGithubLogoUrl}
              onChange={(e) => setOauthGithubLogoUrl(e.target.value)}
              placeholder="https://example.com/github-mark.png"
              disabled={!token || oauthLoading}
            />
          </label>
          <label className="admin-ban-row">
            <IconLabel icon={icons.link} text="启用 Gitea 登录" />
            <span className="admin-ban-toggle">
              <input
                type="checkbox"
                checked={oauthGiteaEnabled}
                onChange={(e) => setOauthGiteaEnabled(e.target.checked)}
                disabled={!token || oauthLoading}
              />
            </span>
          </label>
          <label>
            <IconLabel icon={icons.link} text="Gitea 根地址" />
            <input value={oauthGiteaBase} onChange={(e) => setOauthGiteaBase(e.target.value)} placeholder="https://git.shumengya.top" disabled={!token || oauthLoading} />
          </label>
          <label>
            <IconLabel icon={icons.username} text="Gitea Client ID" />
            <input value={oauthGiteaClientId} onChange={(e) => setOauthGiteaClientId(e.target.value)} disabled={!token || oauthLoading} />
          </label>
          <label>
            <IconLabel icon={icons.token} text="Gitea Client Secret" hint={oauthGiteaSecretSet ? "（已配置，留空则保留原值）" : ""} />
            <input
              type="password"
              autoComplete="new-password"
              value={oauthGiteaClientSecret}
              onChange={(e) => setOauthGiteaClientSecret(e.target.value)}
              placeholder={oauthGiteaSecretSet ? "留空保留" : ""}
              disabled={!token || oauthLoading}
            />
          </label>
          <label>
            <IconLabel icon={icons.link} text="Gitea 登录按钮 Logo" hint="（选填，https 图片地址）" />
            <input
              value={oauthGiteaLogoUrl}
              onChange={(e) => setOauthGiteaLogoUrl(e.target.value)}
              placeholder="https://example.com/gitea.png"
              disabled={!token || oauthLoading}
            />
          </label>
          <label className="admin-ban-row">
            <IconLabel icon={icons.link} text="启用 Google 登录" />
            <span className="admin-ban-toggle">
              <input
                type="checkbox"
                checked={oauthGoogleEnabled}
                onChange={(e) => setOauthGoogleEnabled(e.target.checked)}
                disabled={!token || oauthLoading}
              />
            </span>
          </label>
          <label>
            <IconLabel icon={icons.username} text="Google Client ID" />
            <input value={oauthGoogleClientId} onChange={(e) => setOauthGoogleClientId(e.target.value)} disabled={!token || oauthLoading} />
          </label>
          <label>
            <IconLabel icon={icons.token} text="Google Client Secret" hint={oauthGoogleSecretSet ? "（已配置，留空则保留原值）" : ""} />
            <input
              type="password"
              autoComplete="new-password"
              value={oauthGoogleClientSecret}
              onChange={(e) => setOauthGoogleClientSecret(e.target.value)}
              placeholder={oauthGoogleSecretSet ? "留空保留" : ""}
              disabled={!token || oauthLoading}
            />
          </label>
          <label>
            <IconLabel icon={icons.link} text="Google 登录按钮 Logo" hint="（选填，https 图片地址）" />
            <input
              value={oauthGoogleLogoUrl}
              onChange={(e) => setOauthGoogleLogoUrl(e.target.value)}
              placeholder="https://example.com/google-g.png"
              disabled={!token || oauthLoading}
            />
          </label>
          <label className="admin-ban-row">
            <IconLabel icon={icons.link} text="启用 Microsoft 登录" />
            <span className="admin-ban-toggle">
              <input
                type="checkbox"
                checked={oauthMicrosoftEnabled}
                onChange={(e) => setOauthMicrosoftEnabled(e.target.checked)}
                disabled={!token || oauthLoading}
              />
            </span>
          </label>
          <label>
            <IconLabel icon={icons.username} text="Microsoft Client ID" />
            <input value={oauthMicrosoftClientId} onChange={(e) => setOauthMicrosoftClientId(e.target.value)} disabled={!token || oauthLoading} />
          </label>
          <label>
            <IconLabel
              icon={icons.link}
              text="Microsoft 租户"
              hint="（Entra 租户 ID 或 common，多租户+个人账户常用 common）"
            />
            <input
              value={oauthMicrosoftTenant}
              onChange={(e) => setOauthMicrosoftTenant(e.target.value)}
              placeholder="common"
              disabled={!token || oauthLoading}
            />
          </label>
          <label>
            <IconLabel icon={icons.token} text="Microsoft Client Secret" hint={oauthMicrosoftSecretSet ? "（已配置，留空则保留原值）" : ""} />
            <input
              type="password"
              autoComplete="new-password"
              value={oauthMicrosoftClientSecret}
              onChange={(e) => setOauthMicrosoftClientSecret(e.target.value)}
              placeholder={oauthMicrosoftSecretSet ? "留空保留" : ""}
              disabled={!token || oauthLoading}
            />
          </label>
          <label>
            <IconLabel icon={icons.link} text="Microsoft 登录按钮 Logo" hint="（选填，https 图片地址）" />
            <input
              value={oauthMicrosoftLogoUrl}
              onChange={(e) => setOauthMicrosoftLogoUrl(e.target.value)}
              placeholder="https://example.com/ms.png"
              disabled={!token || oauthLoading}
            />
          </label>
          <label className="admin-ban-row">
            <IconLabel icon={icons.link} text="启用 LINUX DO 登录" />
            <span className="admin-ban-toggle">
              <input
                type="checkbox"
                checked={oauthLinuxdoEnabled}
                onChange={(e) => setOauthLinuxdoEnabled(e.target.checked)}
                disabled={!token || oauthLoading}
              />
            </span>
          </label>
          <label>
            <IconLabel icon={icons.link} text="LINUX DO Connect 根地址" />
            <input
              value={oauthLinuxdoConnectBase}
              onChange={(e) => setOauthLinuxdoConnectBase(e.target.value)}
              placeholder="https://connect.linux.do"
              disabled={!token || oauthLoading}
            />
          </label>
          <label>
            <IconLabel icon={icons.username} text="LINUX DO Client ID" />
            <input value={oauthLinuxdoClientId} onChange={(e) => setOauthLinuxdoClientId(e.target.value)} disabled={!token || oauthLoading} />
          </label>
          <label>
            <IconLabel icon={icons.token} text="LINUX DO Client Secret" hint={oauthLinuxdoSecretSet ? "（已配置，留空则保留原值）" : ""} />
            <input
              type="password"
              autoComplete="new-password"
              value={oauthLinuxdoClientSecret}
              onChange={(e) => setOauthLinuxdoClientSecret(e.target.value)}
              placeholder={oauthLinuxdoSecretSet ? "留空保留" : ""}
              disabled={!token || oauthLoading}
            />
          </label>
          <label>
            <IconLabel icon={icons.link} text="LINUX DO 登录按钮 Logo" hint="（选填，https 图片地址）" />
            <input
              value={oauthLinuxdoLogoUrl}
              onChange={(e) => setOauthLinuxdoLogoUrl(e.target.value)}
              placeholder="https://example.com/linuxdo.png"
              disabled={!token || oauthLoading}
            />
          </label>
          <label className="full-span">
            <IconLabel icon={icons.link} text="允许的回跳地址前缀" hint="（每行一个，需含协议与尾斜杠，如 https://auth.shumengya.top/）" />
            <textarea
              value={oauthReturnPrefixes}
              onChange={(e) => setOauthReturnPrefixes(e.target.value)}
              rows={4}
              disabled={!token || oauthLoading}
            />
          </label>
          <label className="admin-ban-row">
            <IconLabel icon={icons.token} text="需邀请时仍允许 OAuth 新用户" />
            <span className="admin-ban-toggle">
              <input
                type="checkbox"
                checked={oauthSignUpWithInvite}
                onChange={(e) => setOauthSignUpWithInvite(e.target.checked)}
                disabled={!token || oauthLoading}
              />
              <span>开启后，即使注册策略要求邀请码，用户仍可用第三方登录直接注册新账户</span>
            </span>
          </label>
          {oauthError && <div className="error">{oauthError}</div>}
          {oauthMessage && <div className="success">{oauthMessage}</div>}
          <div className="actions compact">
            <button type="button" className="primary" onClick={saveOAuth} disabled={!token || oauthSaving || oauthLoading}>
              {oauthSaving ? "保存中…" : "保存第三方登录"}
            </button>
            <button type="button" className="ghost" onClick={loadOAuth} disabled={!token || oauthLoading}>
              {oauthLoading ? "加载中…" : "重新加载"}
            </button>
          </div>
        </div>
          )}

          {adminNav === "turnstile" && (
        <div className="admin-section">
          <h2 className="admin-section-heading">Cloudflare Turnstile 人机验证</h2>
          <p className="hint">
            在 <a href="https://dash.cloudflare.com/" target="_blank" rel="noopener noreferrer">Cloudflare Dashboard</a> → Turnstile 创建站点，将「Site Key」和「Secret Key」填入下方。
            启用后，登录与注册接口在提交前均需完成人机验证。
          </p>
          <label className="admin-ban-row">
            <IconLabel icon={icons.token} text="启用 Turnstile 人机验证" />
            <span className="admin-ban-toggle">
              <input
                type="checkbox"
                checked={tsEnabled}
                onChange={(e) => setTsEnabled(e.target.checked)}
                disabled={!token || tsLoading}
              />
              <span>开启后登录与注册均需通过 Cloudflare Turnstile 验证</span>
            </span>
          </label>
          <label>
            <IconLabel icon={icons.username} text="Site Key（前端展示用）" />
            <input
              value={tsSiteKey}
              onChange={(e) => setTsSiteKey(e.target.value)}
              placeholder="0x4AAAAAAAxxx..."
              disabled={!token || tsLoading}
            />
          </label>
          <label>
            <IconLabel icon={icons.token} text="Secret Key（后端验证用）" hint={tsSecretKeySet ? "（已配置，留空则保留原值）" : ""} />
            <input
              type="password"
              autoComplete="new-password"
              value={tsSecretKey}
              onChange={(e) => setTsSecretKey(e.target.value)}
              placeholder={tsSecretKeySet ? "留空保留" : "0x4AAAAAAAxxx..."}
              disabled={!token || tsLoading}
            />
          </label>
          {tsError && <div className="error">{tsError}</div>}
          {tsMessage && <div className="success">{tsMessage}</div>}
          <div className="actions compact">
            <button type="button" className="primary" onClick={saveTurnstile} disabled={!token || tsSaving || tsLoading}>
              {tsSaving ? "保存中…" : "保存 Turnstile 配置"}
            </button>
            <button type="button" className="ghost" onClick={loadTurnstile} disabled={!token || tsLoading}>
              {tsLoading ? "加载中…" : "重新加载"}
            </button>
          </div>
        </div>
          )}

          {adminNav === "registration" && (
        <div className="admin-section">
          <h2 className="admin-section-heading">注册与邀请码</h2>
          <label className="admin-ban-row">
            <IconLabel icon={icons.token} text="强制邀请码" />
            <span className="admin-ban-toggle">
              <input
                type="checkbox"
                checked={requireInviteReg}
                onChange={(e) => setRequireInviteReg(e.target.checked)}
                disabled={!token || regLoading}
              />
              <span>开启后，用户自助注册必须填写有效邀请码（管理员创建用户不受影响）</span>
            </span>
          </label>
          <label className="full-span">
            <IconLabel icon={icons.ban} text="禁止注册的账户名" hint="（逗号分隔，自助注册时完全匹配阻止；留空则使用内置默认列表）" />
            <textarea
              value={forbiddenAccountsReg}
              onChange={(e) => setForbiddenAccountsReg(e.target.value)}
              rows={3}
              placeholder="sb,mail,example"
              disabled={!token || regLoading}
            />
          </label>
          <label>
            <IconLabel icon={icons.coins} text="邀请码注册奖励" hint="（萌芽币，填写邀请码并完成邮箱验证后发放；0 表示不奖励）" />
            <input
              type="number"
              min="0"
              value={inviteRegisterRewardReg}
              onChange={(e) => setInviteRegisterRewardReg(Number(e.target.value))}
              disabled={!token || regLoading}
            />
          </label>
          <div className="actions compact">
            <button type="button" className="primary" onClick={saveRegPolicy} disabled={!token || regSaving || regLoading}>
              {regSaving ? "保存中…" : "保存注册策略"}
            </button>
            <button type="button" className="ghost" onClick={loadRegistration} disabled={!token || regLoading}>
              {regLoading ? "加载中…" : "刷新列表"}
            </button>
          </div>
          <div className="hint">公开接口 <code className="inline-code">GET /api/public/registration-policy</code> 供前端判断是否显示邀请码输入框及邀请注册奖励说明。</div>
          {regError && <div className="error">{regError}</div>}
          {regMessage && <div className="success">{regMessage}</div>}

          <h3 className="admin-subheading">生成新邀请码</h3>
          <label>
            <IconLabel icon={icons.username} text="备注（可选）" />
            <input value={newInvNote} onChange={(e) => setNewInvNote(e.target.value)} placeholder="例如：内测批次 A" disabled={!token || regInvCreating} />
          </label>
          <label>
            <IconLabel icon={icons.level} text="最大使用次数" hint="（0 表示不限）" />
            <input
              type="number"
              min="0"
              value={newInvMax}
              onChange={(e) => setNewInvMax(e.target.value)}
              disabled={!token || regInvCreating}
            />
          </label>
          <label>
            <IconLabel icon={icons.calendar} text="过期时间（可选）" />
            <input
              type="datetime-local"
              value={newInvExp}
              onChange={(e) => setNewInvExp(e.target.value)}
              disabled={!token || regInvCreating}
            />
          </label>
          <div className="actions">
            <button type="button" className="primary" onClick={handleCreateInvite} disabled={!token || regInvCreating}>
              {regInvCreating ? "生成中…" : "生成邀请码"}
            </button>
          </div>

          <h3 className="admin-subheading">已有邀请码</h3>
          {inviteList.length === 0 ? (
            <div className="hint">暂无邀请码</div>
          ) : (
            <div className="admin-invite-list">
              {inviteList.map((inv) => (
                <div key={inv.code} className="admin-invite-row">
                  <div>
                    <span className="mono admin-invite-code">{inv.code}</span>
                    {inv.note ? <span className="muted"> · {inv.note}</span> : null}
                    <div className="hint admin-invite-meta">
                      已用 {inv.uses ?? 0}
                      {inv.maxUses > 0 ? ` / 上限 ${inv.maxUses}` : " / 不限次数"}
                      {inv.expiresAt ? ` · 过期 ${formatIsoDateTimeReadable(inv.expiresAt)}` : ""}
                    </div>
                  </div>
                  <button type="button" className="danger ghost" onClick={() => handleDeleteInvite(inv.code)}>
                    删除
                  </button>
                </div>
              ))}
            </div>
          )}
        </div>
          )}

          {adminNav === "users" && (
        <div className="admin-section">
          <div className="list-header">
            <h2 className="admin-section-heading admin-section-heading-inline">用户列表</h2>
            <div className="actions compact">
              <button className="primary" onClick={openCreateUser}>添加用户</button>
              <button className="ghost" onClick={loadUsers} disabled={!token || loading}>
                {loading ? "加载中…" : "刷新列表"}
              </button>
            </div>
          </div>
          {message && <div className="success">{message}</div>}
          {users.length === 0 && <div className="hint">暂无用户</div>}
          <div className="table admin-table">
          <div className="table-row header">
            <TableCell icon={icons.account} dataLabel="账户">账户</TableCell>
            <TableCell icon={icons.username} dataLabel="用户名">用户名</TableCell>
            <TableCell icon={icons.email} dataLabel="邮箱">邮箱</TableCell>
            <TableCell icon={icons.level} dataLabel="等级">等级</TableCell>
            <TableCell icon={icons.coins} dataLabel="萌芽币">萌芽币</TableCell>
            <TableCell icon={icons.ban} dataLabel="状态">状态</TableCell>
            <TableCell icon={icons.visitIp} dataLabel="IP">最近 IP</TableCell>
            <TableCell icon={icons.visitGeo} dataLabel="位置">最近位置</TableCell>
            <span>操作</span>
          </div>
          {users.map((u) => (
            <div className="table-row" key={u.account}>
              <TableCell dataLabel="账户" icon={icons.account} onClick={() => selectUser(u)}>{u.account}</TableCell>
              <TableCell dataLabel="用户名" icon={icons.username}>{u.username || "-"}</TableCell>
              <TableCell dataLabel="邮箱" icon={icons.email}>
                <span className="admin-email-cell">
                  {u.email ? (
                    <MailtoEmail address={u.email} className="profile-external-link">{u.email}</MailtoEmail>
                  ) : (
                    <span>-</span>
                  )}
                  {(u.secondaryEmails || []).length > 0 && (
                    <>
                      <span className="muted"> / </span>
                      {(u.secondaryEmails || []).map((em, idx) => (
                        <React.Fragment key={em}>
                          {idx > 0 ? <span className="muted">, </span> : null}
                          <MailtoEmail address={em} className="profile-external-link">{em}</MailtoEmail>
                        </React.Fragment>
                      ))}
                    </>
                  )}
                </span>
              </TableCell>
              <TableCell dataLabel="等级" icon={icons.level}>{u.level ?? 0} 级</TableCell>
              <TableCell dataLabel="萌芽币" icon={icons.coins}>{u.sproutCoins}</TableCell>
              <TableCell dataLabel="状态" icon={icons.ban}>
                {u.banned ? (
                  <span className="admin-user-banned" title={u.banReason || "已封禁"}>封禁</span>
                ) : (
                  "正常"
                )}
              </TableCell>
              <TableCell dataLabel="最近 IP" icon={icons.visitIp}><span className="mono">{u.lastVisitIp || "-"}</span></TableCell>
              <TableCell dataLabel="位置" icon={icons.visitGeo}>{u.lastVisitDisplayLocation || "-"}</TableCell>
              <span className="row-actions" data-label="操作">
                <button className="ghost" onClick={() => selectUser(u)}>编辑</button>
                <button className="danger" onClick={() => handleDelete(u.account)}>删除</button>
              </span>
            </div>
          ))}
          </div>
        </div>
          )}
        </div>
      </div>

      {editorOpen && (
        <div className="modal-backdrop" onClick={closeEditor}>
          <div className="modal-card" onClick={(e) => e.stopPropagation()}>
            <div className="modal-header">
              <div>
                <h2>{editorMode === "edit" ? "编辑用户" : "新建用户配置"}</h2>
                <p>{editorMode === "edit" ? "修改账户资料后保存" : "点击保存后创建新用户"}</p>
              </div>
              <button className="ghost modal-close" onClick={closeEditor} type="button">关闭</button>
            </div>
            <div className="modal-body">
              <label>
                <IconLabel icon={icons.account} text="账户" />
                <input value={form.account} onChange={(e) => handleChange("account", e.target.value)} placeholder="唯一账户" disabled={editorMode === "edit"} />
              </label>
              <label>
                <IconLabel icon={icons.password} text="密码" hint={editorMode === "edit" ? "（留空不修改）" : ""} />
                <input type="password" value={form.password} onChange={(e) => handleChange("password", e.target.value)} placeholder={editorMode === "edit" ? "输入新密码" : "初始密码"} />
              </label>
              <label>
                <IconLabel icon={icons.username} text="用户名" />
                <input value={form.username} onChange={(e) => handleChange("username", e.target.value)} />
              </label>
              <label>
                <IconLabel icon={icons.email} text="邮箱" />
                <input value={form.email} onChange={(e) => handleChange("email", e.target.value)} />
              </label>
              <label>
                <IconLabel icon={icons.level} text="等级" />
                <input type="number" value={form.level} onChange={(e) => handleChange("level", e.target.value)} />
              </label>
              <label>
                <IconLabel icon={icons.coins} text="萌芽币" />
                <input type="number" value={form.sproutCoins} onChange={(e) => handleChange("sproutCoins", e.target.value)} />
              </label>
              <label>
                <IconLabel icon={icons.secondaryEmail} text="辅助邮箱（逗号分隔）" />
                <input value={form.secondaryEmails} onChange={(e) => handleChange("secondaryEmails", e.target.value)} placeholder="demo2@example.com, demo3@example.com" />
              </label>
              <label>
                <IconLabel icon={icons.phone} text="手机号" />
                <input value={form.phone} onChange={(e) => handleChange("phone", e.target.value)} />
              </label>
              <label>
                <IconLabel icon={icons.avatar} text="个人头像（链接）" />
                <input value={form.avatarUrl} onChange={(e) => handleChange("avatarUrl", e.target.value)} />
              </label>
              <label className="full-span">
                <IconLabel icon={icons.link} text="个人主页网站（http/https）" />
                <input value={form.websiteUrl} onChange={(e) => handleChange("websiteUrl", e.target.value)} placeholder="留空表示无" />
              </label>
              <label className="full-span">
                <IconLabel icon={icons.bio} text="个人简介（支持 Markdown）" />
                <textarea value={form.bio} onChange={(e) => handleChange("bio", e.target.value)} rows={4} />
              </label>
              {editorMode === "edit" && (
                <>
                  <label className="admin-ban-row">
                    <IconLabel icon={icons.ban} text="封禁账户" />
                    <span className="admin-ban-toggle">
                      <input
                        type="checkbox"
                        checked={Boolean(form.banned)}
                        onChange={(e) => handleChange("banned", e.target.checked)}
                      />
                      <span>禁止登录与使用需登录的接口</span>
                    </span>
                  </label>
                  <label className="full-span">
                    <IconLabel icon={icons.ban} text="封禁理由（对用户登录错误提示可见）" />
                    <textarea
                      value={form.banReason}
                      onChange={(e) => handleChange("banReason", e.target.value)}
                      rows={3}
                      placeholder="填写封禁原因；解封请取消勾选「封禁账户」并保存"
                      disabled={!form.banned}
                    />
                  </label>
                </>
              )}
              {editorMode === "edit" && editingAuthClients.length > 0 && (
                <div className="full-span admin-readonly-auth-clients">
                  <IconLabel icon={icons.apps} text="应用接入记录（只读）" />
                  <ul className="admin-auth-client-list">
                    {[...editingAuthClients]
                      .sort((a, b) => new Date(b.lastSeenAt || 0) - new Date(a.lastSeenAt || 0))
                      .map((row) => (
                        <li key={row.clientId}>
                          <strong>{row.clientId}</strong>
                          {row.displayName ? <span className="muted"> · {row.displayName}</span> : null}
                          <div className="muted admin-auth-client-meta">
                            首次 {formatIsoDateTimeReadable(row.firstSeenAt)} · 最近 {formatIsoDateTimeReadable(row.lastSeenAt)}
                          </div>
                        </li>
                      ))}
                  </ul>
                </div>
              )}
              {error && <div className="error">{error}</div>}
              {message && <div className="success">{message}</div>}
            </div>
            <div className="modal-actions">
              <button className="primary" onClick={editorMode === "edit" ? handleUpdate : handleCreate} type="button">
                {editorMode === "edit" ? "保存修改" : "创建用户"}
              </button>
              <button className="ghost" onClick={closeEditor} type="button">取消</button>
            </div>
          </div>
        </div>
      )}
    </section>
  );
}
