import { PasswordChangeRequest, type SessionInfo } from "@umt/contracts"; import { KeyRound, LoaderCircle, LogOut, ShieldCheck, UserRound } from "lucide-react"; import { useState } from "react"; import { useNavigate } from "react-router"; import { toast } from "sonner"; import { z } from "zod"; import { useChangePassword, useLogout } from "~/api/auth.ts"; import { Button } from "~/components/ui/button.tsx"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "~/components/ui/dialog.tsx"; import { Input } from "~/components/ui/input.tsx"; import { Popover, PopoverContent, PopoverTrigger } from "~/components/ui/popover.tsx"; import { errorMessage, LOGIN_PATH } from "~/lib/api.ts"; import { formatDateTime } from "~/lib/utils.ts"; import { FormField, zodFieldErrors } from "./form-field.tsx"; const PasswordForm = PasswordChangeRequest.extend({ confirm: z.string() }).refine((v) => v.newPassword === v.confirm, { message: "两次输入的新密码不一致", path: ["confirm"], }); function ChangePasswordDialog({ open, onOpenChange }: { open: boolean; onOpenChange: (o: boolean) => void }) { const change = useChangePassword(); const [form, setForm] = useState({ currentPassword: "", newPassword: "", confirm: "" }); const [errors, setErrors] = useState>({}); const submit = async (e: React.FormEvent) => { e.preventDefault(); const parsed = PasswordForm.safeParse(form); if (!parsed.success) { setErrors(zodFieldErrors(parsed.error.issues)); return; } setErrors({}); try { const r = await change.mutateAsync({ currentPassword: form.currentPassword, newPassword: form.newPassword }); toast.success(r.revokedSessions ? `密码已修改,其他 ${r.revokedSessions} 个会话已退出` : "密码已修改"); setForm({ currentPassword: "", newPassword: "", confirm: "" }); onOpenChange(false); } catch (err) { const msg = errorMessage(err); if (/旧密码|当前密码|password/i.test(msg)) setErrors({ currentPassword: "当前密码不正确" }); else toast.error(msg); } }; const set = (k: keyof typeof form) => (e: React.ChangeEvent) => setForm((f) => ({ ...f, [k]: e.target.value })); return ( !change.isPending && onOpenChange(o)}> 修改密码 其他设备上的会话会退出登录,当前会话保留。
); } /** 右上角账户弹层:当前身份、会话到期、改密、二次验证、登出 */ export function AccountMenu({ session }: { session: SessionInfo }) { const [open, setOpen] = useState(false); const [pwdOpen, setPwdOpen] = useState(false); const navigate = useNavigate(); const logout = useLogout(); const actor = session.actor; const isCookie = !!session.expiresAt; const doLogout = async () => { try { await logout.mutateAsync(); } catch { // 会话已失效也照样回登录页 } window.location.assign(LOGIN_PATH); }; return ( <>

当前账户

{actor?.name ?? "—"}

{actor?.type === "api_key" ? "API Key 调用" : "管理员"} {isCookie ? ` · 会话到 ${formatDateTime(session.expiresAt)}` : ""}

{isCookie ? ( ) : null} {isCookie ? ( ) : null}
); }