From d772b557a0e1b498ecbef51ac6eaa5a2914f8ca0 Mon Sep 17 00:00:00 2001 From: Nariman Jelveh Date: Thu, 30 Apr 2026 15:01:06 -0700 Subject: [PATCH] Replace 2FA buttons with toggle switch --- src/gui/src/UI/Dashboard/TabSecurity.js | 72 ++++++++++++------------- src/gui/src/css/dashboard.css | 58 ++++++++++++++++++++ 2 files changed, 91 insertions(+), 39 deletions(-) diff --git a/src/gui/src/UI/Dashboard/TabSecurity.js b/src/gui/src/UI/Dashboard/TabSecurity.js index 45244370e..dd70320e2 100644 --- a/src/gui/src/UI/Dashboard/TabSecurity.js +++ b/src/gui/src/UI/Dashboard/TabSecurity.js @@ -85,8 +85,10 @@ const TabSecurity = { h += `${i18n(user.otp ? 'two_factor_enabled' : 'two_factor_disabled')}`; h += ''; h += ''; - h += ``; - h += ``; + h += ''; h += ''; } @@ -124,47 +126,39 @@ const TabSecurity = { }); }); - $el_window.find('.dashboard-section-security .enable-2fa').on('click', async function (e) { - const { promise } = await UIWindow2FASetup({ - window_options: { - parent_uuid: $el_window.attr('data-element_uuid'), - backdrop: true, - close_on_backdrop_click: true, - stay_on_top: true, - has_head: false, - parent_center: true, - }, - }); - const tfa_was_enabled = await promise; + $el_window.find('.dashboard-section-security .toggle-2fa').on('change', async function (e) { + const $toggle = $(this); + const enabling = $toggle.is(':checked'); + $toggle.prop('disabled', true); - if ( tfa_was_enabled ) { - $el_window.find('.dashboard-section-security .enable-2fa').hide(); - $el_window.find('.dashboard-section-security .disable-2fa').show(); - $el_window.find('.dashboard-section-security .user-otp-state').text(i18n('two_factor_enabled')); - $el_window.find('.dashboard-section-security .dashboard-settings-card-2fa').removeClass('dashboard-settings-card-warning'); - $el_window.find('.dashboard-section-security .dashboard-settings-card-2fa').addClass('dashboard-settings-card-success'); + const window_options = { + parent_uuid: $el_window.attr('data-element_uuid'), + backdrop: true, + close_on_backdrop_click: true, + parent_center: true, + stay_on_top: true, + has_head: false, + }; + + const { promise } = enabling + ? await UIWindow2FASetup({ window_options }) + : await UIWindowDisable2FA({ window_options }); + const succeeded = await promise; + + $toggle.prop('disabled', false); + + if ( ! succeeded ) { + $toggle.prop('checked', ! enabling); + return; } - }); - $el_window.find('.dashboard-section-security .disable-2fa').on('click', async function (e) { - const { promise } = await UIWindowDisable2FA({ - window_options: { - parent_uuid: $el_window.attr('data-element_uuid'), - backdrop: true, - close_on_backdrop_click: true, - parent_center: true, - stay_on_top: true, - has_head: false, - }, - }); - const tfa_was_disabled = await promise; - - if ( tfa_was_disabled ) { - $el_window.find('.dashboard-section-security .enable-2fa').show(); - $el_window.find('.dashboard-section-security .disable-2fa').hide(); + const $card = $el_window.find('.dashboard-section-security .dashboard-settings-card-2fa'); + if ( enabling ) { + $el_window.find('.dashboard-section-security .user-otp-state').text(i18n('two_factor_enabled')); + $card.removeClass('dashboard-settings-card-warning').addClass('dashboard-settings-card-success'); + } else { $el_window.find('.dashboard-section-security .user-otp-state').text(i18n('two_factor_disabled')); - $el_window.find('.dashboard-section-security .dashboard-settings-card-2fa').removeClass('dashboard-settings-card-success'); - $el_window.find('.dashboard-section-security .dashboard-settings-card-2fa').addClass('dashboard-settings-card-warning'); + $card.removeClass('dashboard-settings-card-success').addClass('dashboard-settings-card-warning'); } }); }, diff --git a/src/gui/src/css/dashboard.css b/src/gui/src/css/dashboard.css index 380053f3d..5c5335a87 100644 --- a/src/gui/src/css/dashboard.css +++ b/src/gui/src/css/dashboard.css @@ -3236,6 +3236,64 @@ body { color: var(--dashboard-warning-text); } +/* Toggle switch */ +.dashboard-switch { + position: relative; + display: inline-block; + width: 44px; + height: 24px; + flex-shrink: 0; + margin: 0; +} + +.dashboard-switch input { + position: absolute; + opacity: 0; + width: 0; + height: 0; + pointer-events: none; +} + +.dashboard-switch-slider { + position: absolute; + inset: 0; + cursor: pointer; + background-color: var(--dashboard-border); + transition: background-color 0.2s ease; + border-radius: 24px; +} + +.dashboard-switch-slider::before { + content: ""; + position: absolute; + height: 18px; + width: 18px; + left: 3px; + top: 3px; + background-color: #fff; + border-radius: 50%; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); + transition: transform 0.2s ease; +} + +.dashboard-switch input:checked + .dashboard-switch-slider { + background-color: var(--dashboard-success-border); +} + +.dashboard-switch input:checked + .dashboard-switch-slider::before { + transform: translateX(20px); +} + +.dashboard-switch input:focus-visible + .dashboard-switch-slider { + outline: 2px solid var(--select-color); + outline-offset: 2px; +} + +.dashboard-switch input:disabled + .dashboard-switch-slider { + opacity: 0.6; + cursor: not-allowed; +} + /* Danger zone */ .dashboard-danger-zone { padding-top: 24px;