UIWindowVerificationCode.js 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. import TeePromise from "../util/TeePromise.js";
  2. import UIWindow from "./UIWindow.js";
  3. const UIWindowVerificationCode = async function UIWindowVerificationCode ( options ) {
  4. options = options ?? {};
  5. let final_code = '';
  6. let is_checking_code = false;
  7. const html_title = i18n(options.title_key || 'confirm_code_generic_title');
  8. const html_instruction = i18n(options.instruction_key || 'confirm_code_generic_instruction');
  9. const submit_btn_txt = i18n(options.submit_btn_key || 'confirm_code_generic_submit');
  10. let h = '';
  11. h += `<div class="qr-code-window-close-btn generic-close-window-button"> &times; </div>`;
  12. h += `<div style="-webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; color: #3e5362;">`;
  13. h += `<h3 style="text-align:center; font-weight: 500; font-size: 20px;">${ html_title }</h3>`;
  14. h += `<form>`;
  15. h += `<p style="text-align:center; padding: 0 20px;">${ html_instruction }</p>`;
  16. h += `<div class="error"></div>`;
  17. h += ` <fieldset name="number-code" style="border: none; padding:0;" data-number-code-form>
  18. <input class="digit-input" type="number" min='0' max='9' name='number-code-0' data-number-code-input='0' required />
  19. <input class="digit-input" type="number" min='0' max='9' name='number-code-1' data-number-code-input='1' required />
  20. <input class="digit-input" type="number" min='0' max='9' name='number-code-2' data-number-code-input='2' required />
  21. <span class="confirm-code-hyphen">-</span>
  22. <input class="digit-input" type="number" min='0' max='9' name='number-code-3' data-number-code-input='3' required />
  23. <input class="digit-input" type="number" min='0' max='9' name='number-code-4' data-number-code-input='4' required />
  24. <input class="digit-input" type="number" min='0' max='9' name='number-code-5' data-number-code-input='5' required />
  25. </fieldset>`;
  26. h += `<button type="submit" class="button button-block button-primary code-confirm-btn" style="margin-top:10px;" disabled>${submit_btn_txt}</button>`;
  27. h += `</form>`;
  28. h += `</div>`;
  29. const el_window = await UIWindow({
  30. title: null,
  31. icon: null,
  32. uid: null,
  33. is_dir: false,
  34. body_content: h,
  35. has_head: false,
  36. selectable_body: false,
  37. draggable_body: true,
  38. allow_context_menu: false,
  39. is_draggable: options.is_draggable ?? true,
  40. is_droppable: false,
  41. is_resizable: false,
  42. stay_on_top: options.stay_on_top ?? false,
  43. allow_native_ctxmenu: true,
  44. allow_user_select: true,
  45. backdrop: true,
  46. width: 390,
  47. dominant: true,
  48. onAppend: function(el_window){
  49. $(el_window).find('.digit-input').first().focus();
  50. },
  51. window_class: 'window-item-properties',
  52. window_css:{
  53. height: 'initial',
  54. },
  55. body_css: {
  56. padding: '30px',
  57. width: 'initial',
  58. height: 'initial',
  59. 'background-color': 'rgb(247 251 255)',
  60. 'backdrop-filter': 'blur(3px)',
  61. }
  62. });
  63. $(el_window).find('.digit-input').first().focus();
  64. const actions = {
  65. clear: () => {
  66. final_code = '';
  67. $(el_window).find('.code-confirm-btn').prop('disabled', false);
  68. $(el_window).find('.code-confirm-btn').html(submit_btn_txt);
  69. $(el_window).find('.digit-input').val('');
  70. $(el_window).find('.digit-input').first().focus();
  71. },
  72. show_error: (msg) => {
  73. $(el_window).find('.error').html(html_encode(msg));
  74. $(el_window).find('.error').fadeIn();
  75. }
  76. };
  77. $(el_window).find('.code-confirm-btn').on('click submit', function(e){
  78. e.preventDefault();
  79. e.stopPropagation();
  80. $(el_window).find('.code-confirm-btn').prop('disabled', true);
  81. $(el_window).find('.error').hide();
  82. // Check if already checking code to prevent multiple requests
  83. if(is_checking_code)
  84. return;
  85. // Confirm button
  86. is_checking_code = true;
  87. // set animation
  88. $(el_window).find('.code-confirm-btn').html(`<svg style="width:20px; margin-top: 5px;" xmlns="http://www.w3.org/2000/svg" height="24" width="24" viewBox="0 0 24 24"><title>circle anim</title><g fill="#fff" class="nc-icon-wrapper"><g class="nc-loop-circle-24-icon-f"><path d="M12 24a12 12 0 1 1 12-12 12.013 12.013 0 0 1-12 12zm0-22a10 10 0 1 0 10 10A10.011 10.011 0 0 0 12 2z" fill="#eee" opacity=".4"></path><path d="M24 12h-2A10.011 10.011 0 0 0 12 2V0a12.013 12.013 0 0 1 12 12z" data-color="color-2"></path></g><style>.nc-loop-circle-24-icon-f{--animation-duration:0.5s;transform-origin:12px 12px;animation:nc-loop-circle-anim var(--animation-duration) infinite linear}@keyframes nc-loop-circle-anim{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}</style></g></svg>`);
  89. setTimeout(() => {
  90. console.log('final code', final_code);
  91. options.on_value({
  92. actions,
  93. value: final_code,
  94. win: el_window
  95. });
  96. }, 1000);
  97. })
  98. // Elements
  99. const numberCodeForm = document.querySelector('[data-number-code-form]');
  100. const numberCodeInputs = [...numberCodeForm.querySelectorAll('[data-number-code-input]')];
  101. // Event listeners
  102. numberCodeForm.addEventListener('input', ({ target }) => {
  103. if(!target.value.length) { return target.value = null; }
  104. const inputLength = target.value.length;
  105. let currentIndex = Number(target.dataset.numberCodeInput);
  106. if(inputLength === 2){
  107. const inputValues = target.value.split('');
  108. target.value = inputValues[0];
  109. }
  110. else if (inputLength > 1) {
  111. const inputValues = target.value.split('');
  112. inputValues.forEach((value, valueIndex) => {
  113. const nextValueIndex = currentIndex + valueIndex;
  114. if (nextValueIndex >= numberCodeInputs.length) { return; }
  115. numberCodeInputs[nextValueIndex].value = value;
  116. });
  117. currentIndex += inputValues.length - 2;
  118. }
  119. const nextIndex = currentIndex + 1;
  120. if (nextIndex < numberCodeInputs.length) {
  121. numberCodeInputs[nextIndex].focus();
  122. }
  123. // Concatenate all inputs into one string to create the final code
  124. final_code = '';
  125. for(let i=0; i< numberCodeInputs.length; i++){
  126. final_code += numberCodeInputs[i].value;
  127. }
  128. // Automatically submit if 6 digits entered
  129. if(final_code.length === 6){
  130. $(el_window).find('.code-confirm-btn').prop('disabled', false);
  131. $(el_window).find('.code-confirm-btn').trigger('click');
  132. }
  133. });
  134. numberCodeForm.addEventListener('keydown', (e) => {
  135. const { code, target } = e;
  136. const currentIndex = Number(target.dataset.numberCodeInput);
  137. const previousIndex = currentIndex - 1;
  138. const nextIndex = currentIndex + 1;
  139. const hasPreviousIndex = previousIndex >= 0;
  140. const hasNextIndex = nextIndex <= numberCodeInputs.length - 1
  141. switch (code) {
  142. case 'ArrowLeft':
  143. case 'ArrowUp':
  144. if (hasPreviousIndex) {
  145. numberCodeInputs[previousIndex].focus();
  146. }
  147. e.preventDefault();
  148. break;
  149. case 'ArrowRight':
  150. case 'ArrowDown':
  151. if (hasNextIndex) {
  152. numberCodeInputs[nextIndex].focus();
  153. }
  154. e.preventDefault();
  155. break;
  156. case 'Backspace':
  157. if (!e.target.value.length && hasPreviousIndex) {
  158. numberCodeInputs[previousIndex].value = null;
  159. numberCodeInputs[previousIndex].focus();
  160. }
  161. break;
  162. default:
  163. break;
  164. }
  165. });
  166. }
  167. export default UIWindowVerificationCode;