PasskeyEntry.tsx

4.8 KB · raw

1import { createSignal, Show } from "solid-js";23import type { PasskeyEntry } from "../../shared/types";45interface PasskeyEntryProps {6  entry: PasskeyEntry | null;7  onDelete: () => void | Promise<void>;8}910function formatDate(timestamp?: number): string {11  if (!timestamp) return "Never";12  return new Date(timestamp).toLocaleDateString();13}1415function formatUsedCount(count?: number): string {16  const used = count ?? 0;17  return `used ${used} time${used === 1 ? "" : "s"}`;18}1920function algorithmLabel(algorithm: -7 | -8): string {21  return algorithm === -8 ? "EdDSA (Ed25519)" : "ES256 (P-256)";22}2324export function PasskeyEntryView(props: PasskeyEntryProps) {25  const [deleteConfirmPending, setDeleteConfirmPending] = createSignal(false);2627  return (28    <div class="peach-stage" style="position:relative;">29      <Show when={props.entry} fallback={<div class="peach-list-empty"><span>no passkey selected</span></div>}>30        {(entry) => (31          <>32            <div33              class="peach-ghost"34              aria-hidden="true"35              style="position:absolute;top:30px;left:12px;right:0;font-family:'Cormorant Garamond',Georgia,serif;font-size:150px;font-style:italic;font-weight:400;color:var(--t0);opacity:0.03;pointer-events:none;line-height:1;overflow:hidden;white-space:nowrap;z-index:0;"36            >37              {entry().title}38            </div>3940            <div class="peach-entry-name-wrap" style="position:relative;z-index:1;">41              <h1 class="peach-entry-name" aria-label={entry().title}>42                <span class="peach-entry-name-text">{entry().title}</span>43              </h1>44            </div>4546            <div class="peach-domain" style="position:relative;z-index:1;">47              <div class="peach-domain-row">48                <span class="peach-domain-value">{entry().rpId}</span>49                <span class="peach-passkey-badge">passkey</span>50              </div>51            </div>5253            <section class="peach-fields" style="position:relative;z-index:1;">54              <div class="peach-field-card">55                <span class="peach-label">username</span>56                <div class="peach-field-value-row">57                  <span class="peach-field-value">{entry().userName}</span>58                </div>59              </div>6061              <div class="peach-field-card">62                <span class="peach-label">display name</span>63                <div class="peach-field-value-row">64                  <span class="peach-field-value">{entry().userDisplayName}</span>65                </div>66              </div>6768              <div class="peach-field-card">69                <span class="peach-label">algorithm</span>70                <div class="peach-field-value-row">71                  <span class="peach-field-value">{algorithmLabel(entry().algorithm)}</span>72                </div>73              </div>7475              <div class="peach-field-card">76                <span class="peach-label">activity</span>77                <div class="peach-field-value-row peach-passkey-activity">78                  <span class="peach-field-value">{formatUsedCount(entry().signCount)}</span>79                  <span class="peach-passkey-meta">created {formatDate(entry().createdAt)}</span>80                  <span class="peach-passkey-meta">last used {formatDate(entry().lastUsedAt)}</span>81                </div>82              </div>83            </section>8485            <div class="peach-passkey-actions">86              <div class="peach-info-notice">87                The private key is stored encrypted in your Peach vault and is never shown in the UI.88              </div>89              <Show90                when={deleteConfirmPending()}91                fallback={92                  <button93                    class="peach-btn peach-btn--ghost peach-btn--sm peach-passkey-delete-btn"94                    onClick={() => setDeleteConfirmPending(true)}95                  >96                    remove passkey97                  </button>98                }99              >100                <div class="peach-passkey-delete-confirm">101                  <span class="peach-passkey-delete-confirm-copy">Remove this passkey?</span>102                  <button103                    class="peach-btn peach-btn--ghost peach-btn--sm"104                    onClick={() => setDeleteConfirmPending(false)}105                  >106                    cancel107                  </button>108                  <button109                    class="peach-btn peach-btn--ghost peach-btn--sm peach-passkey-delete-btn--confirm"110                    onClick={() => {111                      setDeleteConfirmPending(false);112                      void props.onDelete();113                    }}114                  >115                    remove116                  </button>117                </div>118              </Show>119            </div>120          </>121        )}122      </Show>123    </div>124  );125}