@@ -57,7 +57,11 @@ export default function Settings() {
5757 const [ cfg , setCfg ] = useState < Partial < AppConfig > | null > ( null ) ;
5858 const [ saved , setSaved ] = useState ( false ) ;
5959 const [ saving , setSaving ] = useState ( false ) ;
60+ const [ adminUsername , setAdminUsername ] = useState ( '' ) ;
61+ const [ currentPassword , setCurrentPassword ] = useState ( '' ) ;
6062 const [ newPassword , setNewPassword ] = useState ( '' ) ;
63+ const [ newUsername , setNewUsername ] = useState ( '' ) ;
64+ const [ changingPassword , setChangingPassword ] = useState ( false ) ;
6165 const [ uploadingAvatars , setUploadingAvatars ] = useState ( false ) ;
6266 const [ avatarUploadMessage , setAvatarUploadMessage ] = useState < string | null > ( null ) ;
6367 const [ availableAvatars , setAvailableAvatars ] = useState < string [ ] | null > ( null ) ;
@@ -70,6 +74,12 @@ export default function Settings() {
7074 . then ( ( r ) => r . json ( ) )
7175 . then ( setCfg ) ;
7276
77+ fetch ( '/api/admin/me' , { headers : { Authorization : `Bearer ${ token } ` } } )
78+ . then ( ( r ) => r . json ( ) )
79+ . then ( ( data ) => {
80+ if ( data . username ) setAdminUsername ( data . username ) ;
81+ } ) ;
82+
7383 fetch ( '/api/avatars' )
7484 . then ( ( r ) => {
7585 if ( ! r . ok ) throw new Error ( 'Failed to load avatars' ) ;
@@ -85,19 +95,61 @@ export default function Settings() {
8595 } ) ;
8696 } , [ token ] ) ;
8797
98+ async function changePassword ( ) {
99+ if ( ! currentPassword ) {
100+ alert ( 'Please enter your current password' ) ;
101+ return ;
102+ }
103+ if ( ! newPassword && ! newUsername ) {
104+ alert ( 'Please enter a new password or username to change' ) ;
105+ return ;
106+ }
107+
108+ setChangingPassword ( true ) ;
109+ try {
110+ const body : Record < string , string > = { currentPassword } ;
111+ if ( newPassword ) body . newPassword = newPassword ;
112+ if ( newUsername && newUsername !== adminUsername ) body . newUsername = newUsername ;
113+
114+ const response = await fetch ( '/api/admin/change-password' , {
115+ method : 'POST' ,
116+ headers,
117+ body : JSON . stringify ( body ) ,
118+ } ) ;
119+
120+ if ( ! response . ok ) {
121+ const error = await response . json ( ) ;
122+ alert ( error . error || 'Failed to change password' ) ;
123+ return ;
124+ }
125+
126+ alert ( 'Password changed successfully! Please log in again.' ) ;
127+ // Log out user
128+ localStorage . removeItem ( 'token' ) ;
129+ window . location . href = '/admin/login' ;
130+ } catch ( error ) {
131+ console . error ( 'Password change failed:' , error ) ;
132+ alert ( 'Failed to change password' ) ;
133+ } finally {
134+ setChangingPassword ( false ) ;
135+ setCurrentPassword ( '' ) ;
136+ setNewPassword ( '' ) ;
137+ setNewUsername ( '' ) ;
138+ }
139+ }
140+
88141 async function save ( ) {
89142 if ( ! cfg ) return ;
90143 setSaving ( true ) ;
91144 const payload : Record < string , unknown > = { ...cfg } ;
92- if ( newPassword . trim ( ) ) payload . adminPassword = newPassword . trim ( ) ;
145+ // Password changes now go through separate endpoint (changePassword function)
93146 await fetch ( '/api/admin/config' , {
94147 method : 'PUT' ,
95148 headers,
96149 body : JSON . stringify ( payload ) ,
97150 } ) ;
98151 setSaving ( false ) ;
99152 setSaved ( true ) ;
100- setNewPassword ( '' ) ;
101153 setTimeout ( ( ) => setSaved ( false ) , 2500 ) ;
102154 }
103155
@@ -343,24 +395,44 @@ export default function Settings() {
343395 { /* Admin credentials */ }
344396 < div className = "card" >
345397 < h2 className = "mb-4" > 🔐 Admin Credentials</ h2 >
398+ < p className = "text-sm text-muted mb-4" >
399+ Current username: < strong style = { { color : 'var(--text)' } } > { adminUsername || '...' } </ strong >
400+ </ p >
346401
347- < Input
348- label = "Admin Username"
349- value = { cfg . adminUsername ?? 'admin' }
350- onChange = { ( e ) => update ( 'adminUsername' , e . target . value ) }
351- />
352-
353- < Input
354- label = {
355- < >
356- New Password < span className = "text-muted" > (leave blank to keep current)</ span >
357- </ >
358- }
359- type = "password"
360- value = { newPassword }
361- placeholder = "Enter new password"
362- onChange = { ( e ) => setNewPassword ( e . target . value ) }
363- />
402+ < div className = "form-group" >
403+ < Input
404+ label = "Current Password (required to make changes)"
405+ type = "password"
406+ autoComplete = "off"
407+ value = { currentPassword }
408+ onChange = { ( e ) => setCurrentPassword ( e . target . value ) }
409+ placeholder = "Enter current password"
410+ />
411+ < Input
412+ label = "New Username (leave blank to keep current)"
413+ autoComplete = "off"
414+ value = { newUsername }
415+ onChange = { ( e ) => setNewUsername ( e . target . value ) }
416+ placeholder = { adminUsername || 'admin' }
417+ />
418+ < Input
419+ label = "New Password (leave blank to keep current)"
420+ type = "password"
421+ autoComplete = "new-password"
422+ value = { newPassword }
423+ onChange = { ( e ) => setNewPassword ( e . target . value ) }
424+ placeholder = "Enter new password"
425+ noMargin
426+ />
427+ < button
428+ type = "button"
429+ onClick = { changePassword }
430+ disabled = { changingPassword }
431+ className = "btn btn-primary mt-3"
432+ >
433+ { changingPassword ? 'Updating...' : 'Update Credentials' }
434+ </ button >
435+ </ div >
364436
365437 < div className = "alert alert-warn mt-4" style = { { fontSize : '0.85rem' } } >
366438 ⚠️ After changing credentials, you'll be logged out and need to log back in.
0 commit comments