diff --git a/src/UI/UIWindowEmailConfirmationRequired.js b/src/UI/UIWindowEmailConfirmationRequired.js
index 4c472f7f3..61defb1bc 100644
--- a/src/UI/UIWindowEmailConfirmationRequired.js
+++ b/src/UI/UIWindowEmailConfirmationRequired.js
@@ -26,7 +26,8 @@ function UIWindowEmailConfirmationRequired(options){
let final_code = '';
let is_checking_code = false;
- const submit_btn_txt = 'Confirm Email'
+ const submit_btn_txt = 'Confirm Email';
+
let h = '';
h += `
`;
diff --git a/src/UI/UIWindowLogin.js b/src/UI/UIWindowLogin.js
index cafd17928..e3e78ada8 100644
--- a/src/UI/UIWindowLogin.js
+++ b/src/UI/UIWindowLogin.js
@@ -20,6 +20,7 @@
import UIWindow from './UIWindow.js'
import UIWindowSignup from './UIWindowSignup.js'
import UIWindowRecoverPassword from './UIWindowRecoverPassword.js'
+import UIWindowVerificationCode from './UIWindowVerificationCode.js';
async function UIWindowLogin(options){
options = options ?? {};
@@ -163,7 +164,12 @@ async function UIWindowLogin(options){
headers: headers,
contentType: "application/json",
data: data,
- success: function (data){
+ success: async function (data){
+ if ( data.next_step === 'otp' ) {
+ const value = await UIWindowVerificationCode();
+ console.log('got value', value);
+ }
+
window.update_auth_data(data.token, data.user);
if(options.reload_on_success){
@@ -234,4 +240,4 @@ async function UIWindowLogin(options){
})
}
-export default UIWindowLogin
\ No newline at end of file
+export default UIWindowLogin
diff --git a/src/UI/UIWindowVerificationCode.js b/src/UI/UIWindowVerificationCode.js
new file mode 100644
index 000000000..2d82bd193
--- /dev/null
+++ b/src/UI/UIWindowVerificationCode.js
@@ -0,0 +1,181 @@
+import TeePromise from "../util/TeePromise.js";
+import UIWindow from "./UIWindow.js";
+
+const UIWindowVerificationCode = async function UIWindowVerificationCode ( options ) {
+ options = options ?? {};
+ let final_code = '';
+ let is_checking_code = false;
+
+ const html_title = i18n(options.title_key || 'confirm_code_generic_title');
+ const html_confirm = i18n(options.confirm_key || 'confirm_code_generic_confirm');
+ const html_instruction = i18n(options.instruction_key || 'confirm_code_generic_instruction');
+ const submit_btn_txt = i18n(options.submit_btn_key || 'confirm_code_generic_submit');
+
+ let h = '';
+ h += `
×
`;
+ h += `
`;
+ h += `
${ html_title }
`;
+ h += `
`;
+ h += `
`;
+ h += `what is this text`;
+ h += `
`;
+ h += `
`;
+
+ const el_window = await UIWindow({
+ title: null,
+ icon: null,
+ uid: null,
+ is_dir: false,
+ body_content: h,
+ has_head: false,
+ selectable_body: false,
+ draggable_body: true,
+ allow_context_menu: false,
+ is_draggable: options.is_draggable ?? true,
+ is_droppable: false,
+ is_resizable: false,
+ stay_on_top: options.stay_on_top ?? false,
+ allow_native_ctxmenu: true,
+ allow_user_select: true,
+ backdrop: true,
+ width: 390,
+ dominant: true,
+ onAppend: function(el_window){
+ $(el_window).find('.digit-input').first().focus();
+ },
+ window_class: 'window-item-properties',
+ window_css:{
+ height: 'initial',
+ },
+ body_css: {
+ padding: '30px',
+ width: 'initial',
+ height: 'initial',
+ 'background-color': 'rgb(247 251 255)',
+ 'backdrop-filter': 'blur(3px)',
+ }
+ });
+
+
+ const p = new TeePromise();
+
+ $(el_window).find('.digit-input').first().focus();
+
+ $(el_window).find('.code-confirm-btn').on('click submit', function(e){
+ e.preventDefault();
+ e.stopPropagation();
+
+ $(el_window).find('.code-confirm-btn').prop('disabled', true);
+ $(el_window).find('.error').hide();
+
+ // Check if already checking code to prevent multiple requests
+ if(is_checking_code)
+ return;
+ // Confirm button
+ is_checking_code = true;
+
+ // set animation
+ $(el_window).find('.code-confirm-btn').html(`
`);
+
+ setTimeout(() => {
+ console.log('final code', final_code);
+ p.resolve(final_code);
+ }, 1000);
+ })
+
+ // Elements
+ const numberCodeForm = document.querySelector('[data-number-code-form]');
+ const numberCodeInputs = [...numberCodeForm.querySelectorAll('[data-number-code-input]')];
+
+ // Event listeners
+ numberCodeForm.addEventListener('input', ({ target }) => {
+ if(!target.value.length) { return target.value = null; }
+ const inputLength = target.value.length;
+ let currentIndex = Number(target.dataset.numberCodeInput);
+ if(inputLength === 2){
+ const inputValues = target.value.split('');
+ target.value = inputValues[0];
+ }
+ else if (inputLength > 1) {
+ const inputValues = target.value.split('');
+
+ inputValues.forEach((value, valueIndex) => {
+ const nextValueIndex = currentIndex + valueIndex;
+
+ if (nextValueIndex >= numberCodeInputs.length) { return; }
+
+ numberCodeInputs[nextValueIndex].value = value;
+ });
+ currentIndex += inputValues.length - 2;
+ }
+
+ const nextIndex = currentIndex + 1;
+
+ if (nextIndex < numberCodeInputs.length) {
+ numberCodeInputs[nextIndex].focus();
+ }
+
+ // Concatenate all inputs into one string to create the final code
+ final_code = '';
+ for(let i=0; i< numberCodeInputs.length; i++){
+ final_code += numberCodeInputs[i].value;
+ }
+ // Automatically submit if 6 digits entered
+ if(final_code.length === 6){
+ $(el_window).find('.code-confirm-btn').prop('disabled', false);
+ $(el_window).find('.code-confirm-btn').trigger('click');
+ }
+ });
+
+ numberCodeForm.addEventListener('keydown', (e) => {
+ const { code, target } = e;
+
+ const currentIndex = Number(target.dataset.numberCodeInput);
+ const previousIndex = currentIndex - 1;
+ const nextIndex = currentIndex + 1;
+
+ const hasPreviousIndex = previousIndex >= 0;
+ const hasNextIndex = nextIndex <= numberCodeInputs.length - 1
+
+ switch (code) {
+ case 'ArrowLeft':
+ case 'ArrowUp':
+ if (hasPreviousIndex) {
+ numberCodeInputs[previousIndex].focus();
+ }
+ e.preventDefault();
+ break;
+
+ case 'ArrowRight':
+ case 'ArrowDown':
+ if (hasNextIndex) {
+ numberCodeInputs[nextIndex].focus();
+ }
+ e.preventDefault();
+ break;
+ case 'Backspace':
+ if (!e.target.value.length && hasPreviousIndex) {
+ numberCodeInputs[previousIndex].value = null;
+ numberCodeInputs[previousIndex].focus();
+ }
+ break;
+ default:
+ break;
+ }
+ });
+}
+
+export default UIWindowVerificationCode;
diff --git a/src/css/style.css b/src/css/style.css
index aa162e538..cd0d35a34 100644
--- a/src/css/style.css
+++ b/src/css/style.css
@@ -1946,6 +1946,10 @@ label {
cursor: pointer;
}
+.send-conf-code {
+ cursor: pointer;
+}
+
.email-confirm-code-hyphen {
display: inline-block;
width: 14%;
@@ -1954,10 +1958,22 @@ label {
font-weight: 300;
}
+.confirm-code-hyphen {
+ display: inline-block;
+ width: 14%;
+ text-align: center;
+ font-size: 40px;
+ font-weight: 300;
+}
+
.send-conf-email:hover, .conf-email-log-out:hover {
text-decoration: underline;
}
+.send-conf-code:hover {
+ text-decoration: underline;
+}
+
.remove-permission-link, .disassociate-website-link {
cursor: pointer;
color: red;
diff --git a/src/i18n/translations/en.js b/src/i18n/translations/en.js
index 7e46af1a7..37601523f 100644
--- a/src/i18n/translations/en.js
+++ b/src/i18n/translations/en.js
@@ -47,6 +47,8 @@ const en = {
color: 'Color',
hue: 'Hue',
confirm_account_for_free_referral_storage_c2a: 'Create an account and confirm your email address to receive 1 GB of free storage. Your friend will get 1 GB of free storage too.',
+ confirm_code_generic_title: "Enter Confirmation Code",
+ confirm_code_2fa_title: "Enter 2FA Code",
confirm_delete_multiple_items: 'Are you sure you want to permanently delete these items?',
confirm_delete_single_item: 'Do you want to permanently delete this item?',
confirm_open_apps_log_out: 'You have open apps. Are you sure you want to log out?',
diff --git a/src/util/TeePromise.js b/src/util/TeePromise.js
new file mode 100644
index 000000000..94f9b9d6f
--- /dev/null
+++ b/src/util/TeePromise.js
@@ -0,0 +1,43 @@
+export default class TeePromise {
+ static STATUS_PENDING = {};
+ static STATUS_RUNNING = {};
+ static STATUS_DONE = {};
+ constructor () {
+ this.status_ = this.constructor.STATUS_PENDING;
+ this.donePromise = new Promise((resolve, reject) => {
+ this.doneResolve = resolve;
+ this.doneReject = reject;
+ });
+ }
+ get status () {
+ return this.status_;
+ }
+ set status (status) {
+ this.status_ = status;
+ if ( status === this.constructor.STATUS_DONE ) {
+ this.doneResolve();
+ }
+ }
+ resolve (value) {
+ this.status_ = this.constructor.STATUS_DONE;
+ this.doneResolve(value);
+ }
+ awaitDone () {
+ return this.donePromise;
+ }
+ then (fn, rfn) {
+ return this.donePromise.then(fn, rfn);
+ }
+
+ reject (err) {
+ this.status_ = this.constructor.STATUS_DONE;
+ this.doneReject(err);
+ }
+
+ /**
+ * @deprecated use then() instead
+ */
+ onComplete(fn) {
+ return this.then(fn);
+ }
+}