@@ -7,6 +7,7 @@ import { showMessage } from '/js/shared.js';
77
88const csrfHeader = getCsrfHeaderName ( ) ;
99const csrfToken = getCsrfToken ( ) ;
10+ let renameModalInstance ;
1011
1112/**
1213 * Load and display user's passkeys.
@@ -35,6 +36,15 @@ export async function loadPasskeys() {
3536 }
3637}
3738
39+ /**
40+ * Format a date string safely, returning 'Unknown' for invalid values.
41+ */
42+ function formatDate ( dateStr ) {
43+ if ( ! dateStr ) return 'Unknown' ;
44+ const date = new Date ( dateStr ) ;
45+ return isNaN ( date ) ? 'Unknown' : date . toLocaleDateString ( ) ;
46+ }
47+
3848/**
3949 * Display credentials in UI.
4050 */
@@ -51,19 +61,19 @@ function displayCredentials(container, credentials) {
5161 <strong class="d-inline-block text-truncate" style="max-width: 100%;">${ escapeHtml ( cred . label || 'Unnamed Passkey' ) } </strong>
5262 <br>
5363 <small class="text-muted">
54- Created: ${ new Date ( cred . created ) . toLocaleDateString ( ) }
55- ${ cred . lastUsed ? ' | Last used: ' + new Date ( cred . lastUsed ) . toLocaleDateString ( ) : ' | Never used' }
64+ Created: ${ formatDate ( cred . created ) }
65+ ${ cred . lastUsed ? ' | Last used: ' + formatDate ( cred . lastUsed ) : ' | Never used' }
5666 </small>
5767 <br>
5868 ${ cred . backupEligible
5969 ? '<span class="badge bg-success">Synced</span>'
6070 : '<span class="badge bg-warning text-dark">Device-bound</span>' }
6171 </div>
6272 <div class="flex-shrink-0">
63- <button class="btn btn-sm btn-outline-secondary me-1" onclick="window.renamePasskey(' ${ escapeHtml ( cred . id ) } ', ' ${ escapeHtml ( cred . label || '' ) } ') ">
73+ <button class="btn btn-sm btn-outline-secondary me-1" data-action="rename" data-id=" ${ escapeHtml ( cred . id ) } " data-label=" ${ escapeHtml ( cred . label || '' ) } ">
6474 <i class="bi bi-pencil"></i> Rename
6575 </button>
66- <button class="btn btn-sm btn-outline-danger" onclick="window.deletePasskey(' ${ escapeHtml ( cred . id ) } ') ">
76+ <button class="btn btn-sm btn-outline-danger" data-action="delete" data-id=" ${ escapeHtml ( cred . id ) } ">
6777 <i class="bi bi-trash"></i> Delete
6878 </button>
6979 </div>
@@ -87,9 +97,11 @@ function renamePasskey(credentialId, currentLabel) {
8797 errorEl . classList . add ( 'd-none' ) ;
8898 input . classList . remove ( 'is-invalid' ) ;
8999
90- // Show modal
91- const modal = new bootstrap . Modal ( document . getElementById ( 'renamePasskeyModal' ) ) ;
92- modal . show ( ) ;
100+ // Show modal (reuse cached instance)
101+ if ( ! renameModalInstance ) {
102+ renameModalInstance = new bootstrap . Modal ( document . getElementById ( 'renamePasskeyModal' ) ) ;
103+ }
104+ renameModalInstance . show ( ) ;
93105
94106 // Focus input when modal is shown
95107 document . getElementById ( 'renamePasskeyModal' ) . addEventListener ( 'shown.bs.modal' , ( ) => {
@@ -145,7 +157,7 @@ function renamePasskey(credentialId, currentLabel) {
145157 throw new Error ( data . message || 'Failed to rename passkey' ) ;
146158 }
147159
148- modal . hide ( ) ;
160+ renameModalInstance . hide ( ) ;
149161 if ( globalMessage ) {
150162 showMessage ( globalMessage , 'Passkey renamed successfully.' , 'alert-success' ) ;
151163 }
@@ -198,7 +210,7 @@ async function deletePasskey(credentialId) {
198210 } catch ( error ) {
199211 console . error ( 'Failed to delete passkey:' , error ) ;
200212 if ( globalMessage ) {
201- showMessage ( globalMessage , error . message , 'alert-danger' ) ;
213+ showMessage ( globalMessage , 'Failed to delete passkey. Please try again.' , 'alert-danger' ) ;
202214 }
203215 }
204216}
@@ -221,15 +233,11 @@ async function handleRegisterPasskey() {
221233 } catch ( error ) {
222234 console . error ( 'Registration error:' , error ) ;
223235 if ( globalMessage ) {
224- showMessage ( globalMessage , 'Failed to register passkey: ' + error . message , 'alert-danger' ) ;
236+ showMessage ( globalMessage , 'Failed to register passkey. Please try again.' , 'alert-danger' ) ;
225237 }
226238 }
227239}
228240
229- // Expose to global scope for onclick handlers in the credential list
230- window . renamePasskey = renamePasskey ;
231- window . deletePasskey = deletePasskey ;
232-
233241// Initialize on page load
234242document . addEventListener ( 'DOMContentLoaded' , async ( ) => {
235243 const passkeySection = document . getElementById ( 'passkey-section' ) ;
@@ -240,6 +248,22 @@ document.addEventListener('DOMContentLoaded', async () => {
240248 return ;
241249 }
242250
251+ // Event delegation for credential list actions
252+ const passkeysList = document . getElementById ( 'passkeys-list' ) ;
253+ if ( passkeysList ) {
254+ passkeysList . addEventListener ( 'click' , ( event ) => {
255+ const button = event . target . closest ( 'button[data-action]' ) ;
256+ if ( ! button ) return ;
257+
258+ const { action, id, label } = button . dataset ;
259+ if ( action === 'rename' ) {
260+ renamePasskey ( id , label ) ;
261+ } else if ( action === 'delete' ) {
262+ deletePasskey ( id ) ;
263+ }
264+ } ) ;
265+ }
266+
243267 // Wire up register button
244268 const registerBtn = document . getElementById ( 'registerPasskeyBtn' ) ;
245269 if ( registerBtn ) {
0 commit comments