@@ -19,6 +19,9 @@ interface Settings {
1919 BATTERY_FULL_NOTIFY_ENABLED : string ;
2020 BATTERY_FULL_NOTIFY_BODY : string ;
2121 ABNORMAL_NOTIFY_BODY : string ;
22+ AUTH_ENABLED : string ;
23+ AUTH_USERNAME : string ;
24+ AUTH_PASSWORD : string ;
2225}
2326
2427const SettingsPopover = forwardRef < HTMLDivElement , SettingsPopoverProps > ( ( { onClose } , ref ) => {
@@ -28,6 +31,7 @@ const SettingsPopover = forwardRef<HTMLDivElement, SettingsPopoverProps>(({ onCl
2831 const [ loading , setLoading ] = useState ( true ) ;
2932 const [ saving , setSaving ] = useState ( false ) ;
3033 const [ message , setMessage ] = useState < { text : string , type : 'success' | 'error' } | null > ( null ) ;
34+ const [ passwordConfirm , setPasswordConfirm ] = useState < string > ( '' ) ;
3135
3236 useEffect ( ( ) => {
3337 fetchSettings ( ) ;
@@ -50,6 +54,9 @@ const SettingsPopover = forwardRef<HTMLDivElement, SettingsPopoverProps>(({ onCl
5054 BATTERY_FULL_NOTIFY_ENABLED : 'true' ,
5155 BATTERY_FULL_NOTIFY_BODY : 'Pin đã sạc đầy 100%. Có thể bật bình nóng lạnh để tối ưu sử dụng.' ,
5256 ABNORMAL_NOTIFY_BODY : 'Tiêu thụ điện bất thường, vui lòng kiểm tra xem vòi nước đã khoá chưa.'
57+ , AUTH_ENABLED : 'false'
58+ , AUTH_USERNAME : 'admin'
59+ , AUTH_PASSWORD : 'changeme'
5360 } ;
5461 const merged = { ...defaults , ...data } ;
5562 setSettings ( merged ) ;
@@ -66,18 +73,45 @@ const SettingsPopover = forwardRef<HTMLDivElement, SettingsPopoverProps>(({ onCl
6673 if ( ! settings ) return ;
6774 setSaving ( true ) ;
6875 setMessage ( null ) ;
76+ // If auth enabled, ensure username and password present and confirmation match
77+ if ( settings . AUTH_ENABLED === 'true' ) {
78+ if ( ! settings . AUTH_USERNAME || settings . AUTH_USERNAME . trim ( ) === '' ) {
79+ setMessage ( { text : t ( 'settings.authUsernameRequired' ) , type : 'error' } ) ;
80+ setSaving ( false ) ;
81+ return ;
82+ }
83+ if ( ! settings . AUTH_PASSWORD || settings . AUTH_PASSWORD === '' ) {
84+ setMessage ( { text : t ( 'settings.authPasswordRequired' ) , type : 'error' } ) ;
85+ setSaving ( false ) ;
86+ return ;
87+ }
88+ if ( settings . AUTH_PASSWORD !== passwordConfirm ) {
89+ setMessage ( { text : t ( 'settings.authPasswordMismatch' ) , type : 'error' } ) ;
90+ setSaving ( false ) ;
91+ return ;
92+ }
93+ }
6994 try {
95+ // Do not send password confirmation to backend
96+ const payload = { ...settings } as any ;
97+ delete payload . AUTH_PASSWORD_CONFIRM ;
7098 const res = await fetch ( `${ import . meta. env . VITE_API_BASE_URL } /settings` , {
7199 method : 'POST' ,
72100 headers : {
73101 'Content-Type' : 'application/json' ,
74102 } ,
75- body : JSON . stringify ( settings ) ,
103+ body : JSON . stringify ( payload ) ,
76104 } ) ;
77105 const data = await res . json ( ) ;
78106 if ( data . success ) {
107+ const prevAuthEnabled = originalSettings ? originalSettings . AUTH_ENABLED === 'true' : false ;
79108 setOriginalSettings ( settings ) ;
80109 setMessage ( { text : t ( 'settings.saveSuccess' ) , type : 'success' } ) ;
110+ // If auth changed from disabled -> enabled, reload page to ensure auth middleware and client state take effect
111+ if ( ! prevAuthEnabled && settings . AUTH_ENABLED === 'true' ) {
112+ // small delay to allow UI message to show briefly
113+ setTimeout ( ( ) => window . location . reload ( ) , 300 ) ;
114+ }
81115 } else {
82116 setMessage ( { text : t ( 'settings.saveError' ) , type : 'error' } ) ;
83117 }
@@ -91,6 +125,14 @@ const SettingsPopover = forwardRef<HTMLDivElement, SettingsPopoverProps>(({ onCl
91125
92126 const updateSetting = ( key : keyof Settings , value : string ) => {
93127 if ( settings ) {
128+ // If enabling auth, reset password fields so user must re-enter
129+ if ( key === 'AUTH_ENABLED' && value === 'true' && settings . AUTH_ENABLED !== 'true' ) {
130+ // When enabling auth from disabled state, require re-entering the password
131+ // but keep the existing username so user doesn't have to retype it.
132+ setPasswordConfirm ( '' ) ;
133+ setSettings ( { ...settings , AUTH_ENABLED : value , AUTH_PASSWORD : '' } ) ;
134+ return ;
135+ }
94136 setSettings ( { ...settings , [ key ] : value } ) ;
95137 }
96138 } ;
@@ -222,6 +264,53 @@ const SettingsPopover = forwardRef<HTMLDivElement, SettingsPopoverProps>(({ onCl
222264 </ div >
223265 </ div >
224266 < div className = "settings-section" >
267+ < h4 > { t ( "settings.authSection" ) } </ h4 >
268+ < div className = "setting-item" >
269+ < label >
270+ < input
271+ type = "checkbox"
272+ checked = { settings . AUTH_ENABLED === "true" }
273+ onChange = { ( e ) =>
274+ updateSetting (
275+ "AUTH_ENABLED" ,
276+ e . target . checked ? "true" : "false"
277+ )
278+ }
279+ />
280+ { t ( "settings.authEnabled" ) }
281+ </ label >
282+ </ div >
283+ < div className = "setting-item" >
284+ < label > { t ( "settings.authUsername" ) } </ label >
285+ < input
286+ type = "text"
287+ value = { settings . AUTH_USERNAME }
288+ onChange = { ( e ) => updateSetting ( "AUTH_USERNAME" , e . target . value ) }
289+ disabled = { settings . AUTH_ENABLED !== "true" }
290+ />
291+ </ div >
292+ < div className = "setting-item" >
293+ < label > { t ( "settings.authPassword" ) } </ label >
294+ < input
295+ type = "password"
296+ value = { settings . AUTH_PASSWORD }
297+ onChange = { ( e ) => updateSetting ( "AUTH_PASSWORD" , e . target . value ) }
298+ disabled = { settings . AUTH_ENABLED !== "true" }
299+ />
300+ </ div >
301+ < div className = "setting-item" >
302+ < label > { t ( "settings.authPasswordConfirm" ) } </ label >
303+ < input
304+ type = "password"
305+ value = { passwordConfirm }
306+ onChange = { ( e ) => setPasswordConfirm ( e . target . value ) }
307+ disabled = { settings . AUTH_ENABLED !== "true" }
308+ />
309+ </ div >
310+ < p className = "setting-descrtiption" >
311+ { t ( "settings.authDescription" ) }
312+ </ p >
313+
225314 < h4 > { t ( "settings.offGridWarningSection" ) } </ h4 >
226315 < div className = "setting-item" >
227316 < label >
@@ -280,7 +369,13 @@ const SettingsPopover = forwardRef<HTMLDivElement, SettingsPopoverProps>(({ onCl
280369 </ div >
281370 </ div >
282371 < div className = "settings-actions" >
283- < button onClick = { handleSave } disabled = { saving || ! hasChanges ( ) } >
372+ < button onClick = { handleSave } disabled = {
373+ saving || ! hasChanges ( ) || ( settings . AUTH_ENABLED === 'true' && (
374+ ! settings . AUTH_USERNAME || settings . AUTH_USERNAME . trim ( ) === '' ||
375+ ! settings . AUTH_PASSWORD || settings . AUTH_PASSWORD === '' ||
376+ settings . AUTH_PASSWORD !== passwordConfirm
377+ ) )
378+ } >
284379 { saving ? t ( "settings.saving" ) : t ( "settings.save" ) }
285380 </ button >
286381 </ div >
0 commit comments