@@ -60,7 +60,7 @@ function displayCredentials(container, credentials) {
6060 : '<span class="badge bg-warning text-dark">Device-bound</span>' }
6161 </div>
6262 <div class="flex-shrink-0">
63- <button class="btn btn-sm btn-outline-secondary me-1" onclick="window.renamePasskey('${ escapeHtml ( cred . id ) } ')">
63+ <button class="btn btn-sm btn-outline-secondary me-1" onclick="window.renamePasskey('${ escapeHtml ( cred . id ) } ', ' ${ escapeHtml ( cred . label || '' ) } ' )">
6464 <i class="bi bi-pencil"></i> Rename
6565 </button>
6666 <button class="btn btn-sm btn-outline-danger" onclick="window.deletePasskey('${ escapeHtml ( cred . id ) } ')">
@@ -73,47 +73,101 @@ function displayCredentials(container, credentials) {
7373}
7474
7575/**
76- * Rename a passkey.
76+ * Show the rename passkey modal .
7777 */
78- async function renamePasskey ( credentialId ) {
79- const newLabel = prompt ( 'Enter new name for this passkey (max 64 characters):' ) ;
80- if ( ! newLabel ) return ;
81-
82- if ( newLabel . length > 64 ) {
83- const globalMsg = document . getElementById ( 'passkeyMessage' ) ;
84- if ( globalMsg ) {
85- showMessage ( globalMsg , 'Passkey name is too long (max 64 characters).' , 'alert-danger' ) ;
78+ function renamePasskey ( credentialId , currentLabel ) {
79+ const input = document . getElementById ( 'renamePasskeyInput' ) ;
80+ const counter = document . getElementById ( 'renamePasskeyCount' ) ;
81+ const errorEl = document . getElementById ( 'renamePasskeyError' ) ;
82+ const confirmBtn = document . getElementById ( 'confirmRenameButton' ) ;
83+
84+ // Pre-fill with current label
85+ input . value = currentLabel || '' ;
86+ counter . textContent = `${ input . value . length } / 64` ;
87+ errorEl . classList . add ( 'd-none' ) ;
88+ input . classList . remove ( 'is-invalid' ) ;
89+
90+ // Show modal
91+ const modal = new bootstrap . Modal ( document . getElementById ( 'renamePasskeyModal' ) ) ;
92+ modal . show ( ) ;
93+
94+ // Focus input when modal is shown
95+ document . getElementById ( 'renamePasskeyModal' ) . addEventListener ( 'shown.bs.modal' , ( ) => {
96+ input . select ( ) ;
97+ } , { once : true } ) ;
98+
99+ // Character counter
100+ const onInput = ( ) => {
101+ counter . textContent = `${ input . value . length } / 64` ;
102+ if ( input . value . trim ( ) ) {
103+ errorEl . classList . add ( 'd-none' ) ;
104+ input . classList . remove ( 'is-invalid' ) ;
86105 }
87- return ;
88- }
89-
90- const globalMessage = document . getElementById ( 'passkeyMessage' ) ;
91-
92- try {
93- const response = await fetch ( `/user/webauthn/credentials/${ credentialId } /label` , {
94- method : 'PUT' ,
95- headers : {
96- 'Content-Type' : 'application/json' ,
97- [ csrfHeader ] : csrfToken
98- } ,
99- body : JSON . stringify ( { label : newLabel } )
100- } ) ;
101-
102- if ( ! response . ok ) {
103- const data = await response . json ( ) ;
104- throw new Error ( data . message || 'Failed to rename passkey' ) ;
106+ } ;
107+ input . addEventListener ( 'input' , onInput ) ;
108+
109+ // Submit on Enter key
110+ const onKeydown = ( e ) => {
111+ if ( e . key === 'Enter' ) {
112+ e . preventDefault ( ) ;
113+ confirmBtn . click ( ) ;
105114 }
106-
107- if ( globalMessage ) {
108- showMessage ( globalMessage , 'Passkey renamed successfully.' , 'alert-success' ) ;
115+ } ;
116+ input . addEventListener ( 'keydown' , onKeydown ) ;
117+
118+ // Handle confirm click
119+ const onConfirm = async ( ) => {
120+ const newLabel = input . value . trim ( ) ;
121+ if ( ! newLabel ) {
122+ errorEl . textContent = 'Please enter a name.' ;
123+ errorEl . classList . remove ( 'd-none' ) ;
124+ input . classList . add ( 'is-invalid' ) ;
125+ return ;
109126 }
110- loadPasskeys ( ) ;
111- } catch ( error ) {
112- console . error ( 'Failed to rename passkey:' , error ) ;
113- if ( globalMessage ) {
114- showMessage ( globalMessage , error . message , 'alert-danger' ) ;
127+
128+ confirmBtn . disabled = true ;
129+ confirmBtn . textContent = 'Saving...' ;
130+
131+ const globalMessage = document . getElementById ( 'passkeyMessage' ) ;
132+
133+ try {
134+ const response = await fetch ( `/user/webauthn/credentials/${ credentialId } /label` , {
135+ method : 'PUT' ,
136+ headers : {
137+ 'Content-Type' : 'application/json' ,
138+ [ csrfHeader ] : csrfToken
139+ } ,
140+ body : JSON . stringify ( { label : newLabel } )
141+ } ) ;
142+
143+ if ( ! response . ok ) {
144+ const data = await response . json ( ) ;
145+ throw new Error ( data . message || 'Failed to rename passkey' ) ;
146+ }
147+
148+ modal . hide ( ) ;
149+ if ( globalMessage ) {
150+ showMessage ( globalMessage , 'Passkey renamed successfully.' , 'alert-success' ) ;
151+ }
152+ loadPasskeys ( ) ;
153+ } catch ( error ) {
154+ console . error ( 'Failed to rename passkey:' , error ) ;
155+ errorEl . textContent = error . message ;
156+ errorEl . classList . remove ( 'd-none' ) ;
157+ input . classList . add ( 'is-invalid' ) ;
158+ } finally {
159+ confirmBtn . disabled = false ;
160+ confirmBtn . textContent = 'Save' ;
115161 }
116- }
162+ } ;
163+ confirmBtn . addEventListener ( 'click' , onConfirm ) ;
164+
165+ // Clean up listeners when modal is hidden
166+ document . getElementById ( 'renamePasskeyModal' ) . addEventListener ( 'hidden.bs.modal' , ( ) => {
167+ input . removeEventListener ( 'input' , onInput ) ;
168+ input . removeEventListener ( 'keydown' , onKeydown ) ;
169+ confirmBtn . removeEventListener ( 'click' , onConfirm ) ;
170+ } , { once : true } ) ;
117171}
118172
119173/**
0 commit comments