import type { PasskeyInfo } from "@umt/contracts"; import { Check, KeyRound, LoaderCircle, Pencil, Plus, Trash2, X } from "lucide-react"; import { useState } from "react"; import { toast } from "sonner"; import { isPasskeyCancel, passkeySupported, useDeletePasskey, usePasskeys, useRegisterPasskey, useRenamePasskey, } from "~/api/auth.ts"; import { ConfirmDialog } from "~/components/console/confirm-dialog.tsx"; import { useListPagination } from "~/components/console/use-list-pagination.ts"; import { Button } from "~/components/ui/button.tsx"; import { Input } from "~/components/ui/input.tsx"; import { Pagination } from "~/components/ui/pagination.tsx"; import { errorMessage } from "~/lib/api.ts"; import { formatDateTime } from "~/lib/utils.ts"; import { SettingsBlock } from "./settings-block.tsx"; // Passkey:登记(可以多把,各自命名)、改名、删除。登录页有 Passkey 时显示"用 Passkey 登录"。 /** 按浏览器平台给个默认名,方便区分 */ function defaultName(): string { const ua = typeof navigator === "undefined" ? "" : navigator.userAgent; if (/iPhone|iPad/.test(ua)) return "iPhone / iPad"; if (/Mac OS X/.test(ua)) return "Mac"; if (/Android/.test(ua)) return "Android"; if (/Windows/.test(ua)) return "Windows"; return "Passkey"; } function PasskeyRow({ item }: { item: PasskeyInfo }) { const rename = useRenamePasskey(); const del = useDeletePasskey(); const [editing, setEditing] = useState(false); const [name, setName] = useState(item.name); const save = async (e: React.FormEvent) => { e.preventDefault(); const v = name.trim(); if (!v || v === item.name) return setEditing(false); try { await rename.mutateAsync({ id: item.id, name: v }); setEditing(false); } catch (err) { toast.error(errorMessage(err)); } }; return (
{item.name}
{formatDateTime(item.createdAt)} 登记 ·{" "} {item.lastUsedAt ? `${formatDateTime(item.lastUsedAt)} 用过` : "还没用过"} {item.backedUp ? " · 已同步到云端钥匙串" : ""}
当前用 API Key 访问,登录会话才能修改。
; else body = (读取中…
) : items.length ? (还没有登记 Passkey。
)} {items.length > 0 ?这个浏览器用不了 Passkey:需要 HTTPS 或 localhost 访问。
) : adding ? ( ) : (