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}