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;