115 lines · 4.6 KB
import { useState } from "react";
import { beginPasskeyRegistration, deletePasskey, finishPasskeyRegistration } from "~/fns/account-security";
import { useAction } from "~/hooks/use-action";
import { usePasskeyRegistration } from "~/hooks/use-passkey";
import { Button } from "~/ui/Button";
import { TextField } from "~/ui/Field";
import { ErrorBanner } from "~/ui/Callout";
import { EmptyState } from "~/ui/EmptyState";
import { Panel } from "~/ui/Panel";
import { RelativeTime } from "~/ui/RelativeTime";
import { RecoveryCodes } from "./RecoveryCodes";

type Passkey = {
  id: string;
  name: string;
  lastUsedAt: number | null;
  createdAt: number;
};

export function PasskeysPanel({ passkeys, hasPassword }: { passkeys: Passkey[]; hasPassword: boolean }) {
  const register = usePasskeyRegistration(beginPasskeyRegistration, finishPasskeyRegistration);
  const remove = useAction(deletePasskey);
  const [removing, setRemoving] = useState<string | null>(null);
  const [codes, setCodes] = useState<string[] | null>(null);

  return (
    <Panel title="Passkeys">
      <div className="space-y-4 p-4 text-sm">
        <p className="text-kumo-subtle">
          Passkeys sign you in without a password and also count as a second factor. Adding one turns on two-factor
          checks for password sign-ins.
        </p>
        <ErrorBanner message={register.error ?? remove.error} />
        {codes && <RecoveryCodes codes={codes} />}
        {passkeys.length === 0 ? (
          <EmptyState title="No passkeys yet" icon="key" />
        ) : (
          <ul className="divide-y divide-kumo-hairline rounded-md border border-kumo-hairline">
            {passkeys.map((key) => (
              <li key={key.id} className="space-y-3 px-3 py-2.5">