@@ -18,7 +18,8 @@ pub fn configuration_page() -> Html {
1818 let show_restart_modal = use_state ( || false ) ;
1919 let save_message = use_state ( || None :: < ( bool , String ) > ) ;
2020
21- let expanded_sections = use_state ( || vec ! [ true , false , false , false , false , false , false , false ] ) ;
21+ let expanded_sections =
22+ use_state ( || vec ! [ true , false , false , false , false , false , false , false ] ) ;
2223
2324 let permissions = use_state ( || None :: < Vec < PanelPermission > > ) ;
2425 {
@@ -120,21 +121,35 @@ pub fn configuration_page() -> Html {
120121 let show_restart_modal = show_restart_modal. clone ( ) ;
121122 let save_message = save_message. clone ( ) ;
122123 let i18n = i18n. clone ( ) ;
124+ let config = config. clone ( ) ;
123125
124126 Callback :: from ( move |new_config : ConfigResponse | {
125127 let show_restart_modal = show_restart_modal. clone ( ) ;
126128 let save_message = save_message. clone ( ) ;
127129 let i18n = i18n. clone ( ) ;
130+ let config = config. clone ( ) ;
128131
129132 spawn_local ( async move {
130133 match Request :: put ( "/api/bot/config" ) . json ( & new_config) {
131134 Ok ( req) => match req. send ( ) . await {
132135 Ok ( resp) => {
133136 if resp. ok ( ) {
134- save_message. set ( Some ( ( true , i18n. t ( "panel.configuration.save_success" ) ) ) ) ;
137+ save_message
138+ . set ( Some ( ( true , i18n. t ( "panel.configuration.save_success" ) ) ) ) ;
135139 show_restart_modal. set ( true ) ;
140+ if let Ok ( resp) = Request :: get ( "/api/bot/config" ) . send ( ) . await {
141+ if resp. ok ( ) {
142+ if let Ok ( config_data) = resp. json :: < ConfigResponse > ( ) . await
143+ {
144+ config. set ( Some ( config_data) ) ;
145+ }
146+ }
147+ }
136148 } else {
137- let error_msg = resp. text ( ) . await . unwrap_or_else ( |_| "Erreur inconnue" . to_string ( ) ) ;
149+ let error_msg = resp
150+ . text ( )
151+ . await
152+ . unwrap_or_else ( |_| "Erreur inconnue" . to_string ( ) ) ;
138153 save_message. set ( Some ( ( false , error_msg) ) ) ;
139154 }
140155 }
@@ -334,8 +349,24 @@ struct ConfigFormProps {
334349fn config_form ( props : & ConfigFormProps ) -> Html {
335350 let ( i18n, _set_language) = use_translation ( ) ;
336351 let config = use_state ( || props. config . clone ( ) ) ;
352+ let is_saving = use_state ( || false ) ;
353+
354+ let has_changes = * config != props. config ;
355+ let original_config = props. config . clone ( ) ;
356+
357+ {
358+ let is_saving = is_saving. clone ( ) ;
359+ let config_matches = * config == props. config ;
360+ use_effect_with ( config_matches, move |matches| {
361+ if * matches {
362+ is_saving. set ( false ) ;
363+ }
364+ || ( )
365+ } ) ;
366+ }
337367
338368 html ! {
369+ <>
339370 <div class="space-y-4" >
340371 <AccordionSection
341372 title={ i18n. t( "panel.configuration.sections.bot" ) }
@@ -424,24 +455,66 @@ fn config_form(props: &ConfigFormProps) -> Html {
424455 >
425456 <LogsReminderSection config={ config. clone( ) } />
426457 </AccordionSection >
458+ </div>
427459
428- <div class="pt-4 border-t border-slate-600" >
429- <button
430- onclick={ {
431- let config = ( * config) . clone( ) ;
432- let on_save = props. on_save. clone( ) ;
433- move |_| on_save. emit( config. clone( ) )
434- } }
435- class="w-full px-4 py-3 bg-blue-600 hover:bg-blue-700 text-white rounded-md transition font-semibold flex items-center justify-center gap-2"
460+ { if has_changes && !* is_saving {
461+ html! {
462+ <div
463+ class="fixed bottom-8 left-1/2 -translate-x-1/2 z-50 transition-all duration-300 ease-out"
464+ style="animation: slideUp 0.3s ease-out;"
436465 >
437- <i class="bi bi-save" ></i>
438- { i18n. t( "panel.configuration.save" ) }
439- </button>
440- <p class="mt-2 text-xs text-gray-500 text-center" >
441- { i18n. t( "panel.configuration.save_help" ) }
442- </p>
443- </div>
444- </div>
466+ <style>
467+ { "
468+ @keyframes slideUp {
469+ from {
470+ opacity: 0;
471+ transform: translateX(-50%) translateY(20px);
472+ }
473+ to {
474+ opacity: 1;
475+ transform: translateX(-50%) translateY(0);
476+ }
477+ }
478+ " }
479+ </style>
480+ <div class="flex items-center gap-3 px-6 py-3 bg-blue-600 hover:bg-blue-700 rounded-full shadow-2xl transition-all" >
481+ <button
482+ onclick={ {
483+ let config = config. clone( ) ;
484+ let original_config = original_config. clone( ) ;
485+ Callback :: from( move |_| {
486+ config. set( original_config. clone( ) ) ;
487+ } )
488+ } }
489+ class="text-white hover:text-gray-200 transition text-sm font-medium"
490+ >
491+ { i18n. t( "panel.configuration.reset" ) }
492+ </button>
493+ <div class="w-px h-6 bg-white/30" ></div>
494+ <button
495+ onclick={ {
496+ let config = ( * config) . clone( ) ;
497+ let on_save = props. on_save. clone( ) ;
498+ let is_saving = is_saving. clone( ) ;
499+ move |_| {
500+ is_saving. set( true ) ;
501+ on_save. emit( config. clone( ) ) ;
502+ }
503+ } }
504+ class="text-white font-semibold text-sm flex items-center gap-2"
505+ >
506+ { i18n. t( "panel.configuration.unsaved_changes" ) }
507+ <span class="px-3 py-1 bg-white/20 hover:bg-white/30 rounded-full transition" >
508+ { i18n. t( "panel.configuration.save_button" ) }
509+ </span>
510+ </button>
511+ </div>
512+ </div>
513+ }
514+ } else {
515+ html! { }
516+ } }
517+ </>
445518 }
446519}
447520
@@ -502,7 +575,10 @@ struct TextInputProps {
502575
503576#[ function_component( TextInput ) ]
504577fn text_input ( props : & TextInputProps ) -> Html {
505- let input_type = props. input_type . clone ( ) . unwrap_or_else ( || "text" . to_string ( ) ) ;
578+ let input_type = props
579+ . input_type
580+ . clone ( )
581+ . unwrap_or_else ( || "text" . to_string ( ) ) ;
506582 let placeholder = props. placeholder . clone ( ) . unwrap_or_default ( ) ;
507583
508584 html ! {
0 commit comments