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}2324function titleSizeClass(title: string): string {25 if (title.length >= 28) return "peach-entry-name--xxl";26 if (title.length >= 20) return "peach-entry-name--xl";27 if (title.length >= 14) return "peach-entry-name--lg";28 return "";29}3031export function PasskeyEntryView(props: PasskeyEntryProps) {32 const [deleteConfirmPending, setDeleteConfirmPending] = createSignal(false);3334 return (35 <div class="peach-stage" style="position:relative;">36 <Show when={props.entry} fallback={<div class="peach-list-empty"><span>no passkey selected</span></div>}>37 {(entry) => (38 <>39 <div40 class="peach-ghost"41 aria-hidden="true"42 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;"43 >44 {entry().title}45 </div>4647 <div class="peach-entry-name-wrap" style="position:relative;z-index:1;">48 <h1 class={`peach-entry-name ${titleSizeClass(entry().title)}`} aria-label={entry().title}>49 <span class="peach-entry-name-text">{entry().title}</span>50 </h1>51 </div>5253 <div class="peach-domain" style="position:relative;z-index:1;">54 <div class="peach-domain-row">55 <span class="peach-domain-value">{entry().rpId}</span>56 <span class="peach-passkey-badge">passkey</span>57 </div>58 </div>5960 <section class="peach-fields" style="position:relative;z-index:1;">61 <div class="peach-field-card">62 <span class="peach-label">username</span>63 <div class="peach-field-value-row">64 <span class="peach-field-value">{entry().userName}</span>65 </div>66 </div>6768 <div class="peach-field-card">69 <span class="peach-label">display name</span>70 <div class="peach-field-value-row">71 <span class="peach-field-value">{entry().userDisplayName}</span>72 </div>73 </div>7475 <div class="peach-field-card">76 <span class="peach-label">algorithm</span>77 <div class="peach-field-value-row">78 <span class="peach-field-value">{algorithmLabel(entry().algorithm)}</span>79 </div>80 </div>8182 <div class="peach-field-card">83 <span class="peach-label">activity</span>84 <div class="peach-field-value-row peach-passkey-activity">85 <span class="peach-field-value">{formatUsedCount(entry().signCount)}</span>86 <span class="peach-passkey-meta">created {formatDate(entry().createdAt)}</span>87 <span class="peach-passkey-meta">last used {formatDate(entry().lastUsedAt)}</span>88 </div>89 </div>90 </section>9192 <div class="peach-passkey-actions">93 <div class="peach-info-notice">94 The private key is stored encrypted in your Peach vault and is never shown in the UI.95 </div>96 <Show97 when={deleteConfirmPending()}98 fallback={99 <button100 class="peach-btn peach-btn--ghost peach-btn--sm peach-passkey-delete-btn"101 onClick={() => setDeleteConfirmPending(true)}102 >103 remove passkey104 </button>105 }106 >107 <div class="peach-passkey-delete-confirm">108 <span class="peach-passkey-delete-confirm-copy">Remove this passkey?</span>109 <button110 class="peach-btn peach-btn--ghost peach-btn--sm"111 onClick={() => setDeleteConfirmPending(false)}112 >113 cancel114 </button>115 <button116 class="peach-btn peach-btn--ghost peach-btn--sm peach-passkey-delete-btn--confirm"117 onClick={() => {118 setDeleteConfirmPending(false);119 void props.onDelete();120 }}121 >122 remove123 </button>124 </div>125 </Show>126 </div>127 </>128 )}129 </Show>130 </div>131 );132}