diff --git a/src/gui/src/UI/Components/RecoveryCodeEntryView.js b/src/gui/src/UI/Components/RecoveryCodeEntryView.js deleted file mode 100644 index 67d362a68..000000000 --- a/src/gui/src/UI/Components/RecoveryCodeEntryView.js +++ /dev/null @@ -1,105 +0,0 @@ -/** - * Copyright (C) 2024-present Puter Technologies Inc. - * - * This file is part of Puter. - * - * Puter is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -const Component = use('util.Component'); - -export default def(class RecoveryCodeEntryView extends Component { - static ID = 'ui.component.RecoveryCodeEntryView'; - static PROPERTIES = { - value: {}, - length: { value: 8 }, - error: {}, - }; - - static CSS = /*css*/` - fieldset { - display: flex; - } - .recovery-code-input { - flex-grow: 1; - box-sizing: border-box; - height: 50px; - font-size: 25px; - text-align: center; - border-radius: 0.5rem; - font-family: 'Courier New', Courier, monospace; - } - - /* TODO: I'd rather not duplicate this */ - .error { - display: none; - color: red; - border: 1px solid red; - border-radius: 4px; - padding: 9px; - margin-bottom: 15px; - text-align: center; - font-size: 13px; - } - .error-message { - display: none; - color: rgb(215 2 2); - font-size: 14px; - margin-top: 10px; - margin-bottom: 10px; - padding: 10px; - border-radius: 4px; - border: 1px solid rgb(215 2 2); - text-align: center; - } - `; - - create_template ({ template }) { - $(template).html(/*html*/` -
-
-
-
- -
-
-
- `); - } - - on_focus () { - $(this.dom_).find('input').focus(); - } - - on_ready ({ listen }) { - listen('error', (error) => { - if ( ! error ) return $(this.dom_).find('.error').hide(); - $(this.dom_).find('.error').text(error).show(); - }); - - listen('value', (value) => { - // clear input - if ( value === undefined ) { - $(this.dom_).find('input').val(''); - } - }); - - const input = $(this.dom_).find('input'); - input.on('input', () => { - if ( input.val().length === this.get('length') ) { - this.set('value', input.val()); - } - }); - } -}); diff --git a/src/gui/src/UI/UIWindowLogin.js b/src/gui/src/UI/UIWindowLogin.js index dc3eb8985..573627489 100644 --- a/src/gui/src/UI/UIWindowLogin.js +++ b/src/gui/src/UI/UIWindowLogin.js @@ -22,7 +22,6 @@ import Button from './Components/Button.js'; import CodeEntryView from './Components/CodeEntryView.js'; import Flexer from './Components/Flexer.js'; import JustHTML from './Components/JustHTML.js'; -import RecoveryCodeEntryView from './Components/RecoveryCodeEntryView.js'; import StepView from './Components/StepView.js'; import UIComponentWindow from './UIComponentWindow.js'; import UIWindow from './UIWindow.js'; @@ -330,52 +329,81 @@ async function UIWindowLogin (options) { }

`, }), - new RecoveryCodeEntryView({ - _ref: me => recovery_entry = me, - async 'property.value' (value, { component }) { - let error_i18n_key = 'something_went_wrong'; - if ( ! value ) return; - try { - const resp = await fetch(`${window.api_origin}/login/recovery-code`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - token: data.otp_jwt_token, - code: value, - }), - }); - - if ( resp.status === 429 ) { - error_i18n_key = 'confirm_code_generic_too_many_requests'; - throw new Error('expected error'); + new JustHTML({ + _ref: me => { + recovery_entry = me; + const set_error = (msg) => { + const err = me.dom_.querySelector('.error'); + if ( ! err ) return; + if ( ! msg ) { + err.style.display = 'none'; + err.textContent = ''; + } else { + err.textContent = msg; + err.style.display = 'block'; } + }; + me.clear_input = () => { + const input = me.dom_.querySelector('.recovery-code-input'); + if ( input ) input.value = ''; + }; + me.clear_error = () => set_error(undefined); + me.dom_.addEventListener('input', async (e) => { + if ( ! e.target.matches('.recovery-code-input') ) return; + const value = e.target.value; + if ( value.length !== 8 ) return; + let error_i18n_key = 'something_went_wrong'; + try { + const resp = await fetch(`${window.api_origin}/login/recovery-code`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + token: data.otp_jwt_token, + code: value, + }), + }); - const next_data = await resp.json(); + if ( resp.status === 429 ) { + error_i18n_key = 'confirm_code_generic_too_many_requests'; + throw new Error('expected error'); + } - if ( ! next_data.proceed ) { - error_i18n_key = 'confirm_code_generic_incorrect'; - throw new Error('expected error'); + const next_data = await resp.json(); + + if ( ! next_data.proceed ) { + error_i18n_key = 'confirm_code_generic_incorrect'; + throw new Error('expected error'); + } + + data = next_data; + + $(win).close(); + p.resolve(); + } catch (e) { + set_error(i18n(error_i18n_key)); } - - data = next_data; - - $(win).close(); - p.resolve(); - } catch (e) { - // keeping this log; useful in screenshots - component.set('error', i18n(error_i18n_key)); - } + }); }, + html: ` +
+
+ +
+ +
+
+
+ `, }), new Button({ label: i18n('login2fa_recovery_back'), style: 'link', on_click: async () => { stepper.back(); - recovery_entry.set('value', undefined); - recovery_entry.set('error', undefined); + recovery_entry.clear_input(); + recovery_entry.clear_error(); }, }), ],