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();
},
}),
],