@@ -4,79 +4,88 @@ import { showAlert } from './utils.js';
44// Enhanced OTP input setup with better visual feedback
55export function setupOtpInputs ( ) {
66 const inputs = document . querySelectorAll ( '.otp-inputs input' ) ;
7-
7+ const ANIMATION_DURATION = 200 ;
8+ const INPUT_CLASSES = {
9+ base : [ 'transition-all' , 'duration-200' , 'ease-in-out' , 'text-center' , 'text-xl' , 'font-medium' , 'bg-gray-800' ] ,
10+ focus : [ 'ring-2' , 'ring-purple-300' , 'border-purple-400' , 'shadow-lg' ] ,
11+ success : [ 'border-green-400' , 'bg-green-50' , 'transform' , 'scale-105' ] ,
12+ paste : [ 'bg-blue-50' , 'border-blue-400' , 'transform' , 'scale-105' ] ,
13+ disabled : [ 'verifying' , 'cursor-not-allowed' ]
14+ } ;
15+
16+ const applyClasses = ( element , classes , remove = false ) => {
17+ element . classList [ remove ? 'remove' : 'add' ] ( ...classes ) ;
18+ } ;
19+
20+ const animateInput = ( input , classes ) => {
21+ applyClasses ( input , classes ) ;
22+ setTimeout ( ( ) => applyClasses ( input , classes , true ) , ANIMATION_DURATION ) ;
23+ } ;
24+
825 inputs . forEach ( ( input , index ) => {
9- // Improved visual styling
10- input . classList . add (
11- 'transition-all' , 'duration-200' , 'ease-in-out' ,
12- 'text-center' , 'text-xl' , 'font-medium' ,
13- 'bg-gray-800' // Initial background color
14- ) ;
15-
16- // Enhanced paste handling
26+ // Apply base styling
27+ applyClasses ( input , INPUT_CLASSES . base ) ;
28+
29+ // Enhanced paste handling with validation
1730 input . addEventListener ( 'paste' , ( e ) => {
1831 e . preventDefault ( ) ;
1932 const pasteData = e . clipboardData . getData ( 'text' ) . trim ( ) ;
33+
2034 if ( / ^ \d { 6 } $ / . test ( pasteData ) ) {
2135 inputs . forEach ( ( inp , i ) => {
2236 inp . value = pasteData [ i ] || '' ;
23- inp . classList . remove ( 'border-green-400' , 'bg-green-50' ) ;
24- inp . classList . add ( 'bg-blue-50' , 'border-blue-400' , 'transform' , 'scale-105' ) ;
25- setTimeout ( ( ) => {
26- inp . classList . remove ( 'bg-blue-50' , 'border-blue-400' , 'transform' , 'scale-105' ) ;
27- } , 200 ) ;
37+ animateInput ( inp , INPUT_CLASSES . paste ) ;
2838 } ) ;
29- inputs [ 5 ] . focus ( ) ;
39+ inputs [ inputs . length - 1 ] . focus ( ) ;
3040 }
3141 } ) ;
3242
33- // Improved input handling with better feedback
43+ // Smart input handling with validation and auto-focus
3444 input . addEventListener ( 'input' , ( e ) => {
35- input . value = input . value . replace ( / \D / g, '' ) . slice ( 0 , 1 ) ;
36-
37- if ( input . value . length === 1 ) {
38- input . classList . add ( 'border-green-400' , 'bg-green-50' , 'transform' , 'scale-105' ) ;
39- setTimeout ( ( ) => input . classList . remove (
40- 'border-green-400' , 'bg-green-50' , 'transform' , 'scale-105'
41- ) , 200 ) ;
45+ const sanitizedValue = input . value . replace ( / \D / g, '' ) . slice ( 0 , 1 ) ;
46+ input . value = sanitizedValue ;
47+
48+ if ( sanitizedValue ) {
49+ animateInput ( input , INPUT_CLASSES . success ) ;
4250
4351 if ( index < inputs . length - 1 ) {
4452 inputs [ index + 1 ] . focus ( ) ;
45- }
46- }
47-
48- // Auto-submit when complete
49- if ( index === inputs . length - 1 && input . value . length === 1 ) {
50- const allFilled = Array . from ( inputs ) . every ( i => i . value . length === 1 ) ;
51- if ( allFilled ) {
52- inputs . forEach ( i => {
53- i . classList . add ( 'verifying' , 'cursor-not-allowed' ) ;
54- i . disabled = true ;
55- } ) ;
56- DOM . verifyOtpBtn . click ( ) ;
53+ } else {
54+ const isComplete = Array . from ( inputs ) . every ( i => i . value . length === 1 ) ;
55+ if ( isComplete ) {
56+ inputs . forEach ( i => {
57+ applyClasses ( i , INPUT_CLASSES . disabled ) ;
58+ i . disabled = true ;
59+ } ) ;
60+ requestAnimationFrame ( ( ) => DOM . verifyOtpBtn . click ( ) ) ;
61+ }
5762 }
5863 }
5964 } ) ;
6065
61- // Enhanced keyboard navigation
66+ // Advanced keyboard navigation
6267 input . addEventListener ( 'keydown' , ( e ) => {
63- if ( e . key === 'Backspace' && ! input . value && index > 0 ) {
64- inputs [ index - 1 ] . focus ( ) ;
65- } else if ( e . key === 'ArrowLeft' && index > 0 ) {
66- inputs [ index - 1 ] . focus ( ) ;
67- } else if ( e . key === 'ArrowRight' && index < inputs . length - 1 ) {
68- inputs [ index + 1 ] . focus ( ) ;
69- }
70- } ) ;
71-
72- // Improved focus states
73- input . addEventListener ( ' focus' , ( ) => {
74- input . classList . add ( 'ring-2' , 'ring-purple-300' , 'border-purple-400' , 'shadow-md' ) ;
75- } ) ;
76-
77- input . addEventListener ( 'blur' , ( ) => {
78- input . classList . remove ( 'ring-2' , 'ring-purple-300' , 'border-purple-400' , 'shadow-md' ) ;
68+ const keyHandlers = {
69+ 'Backspace' : ( ) => ! input . value && index > 0 && inputs [ index - 1 ] . focus ( ) ,
70+ 'ArrowLeft' : ( ) => index > 0 && inputs [ index - 1 ] . focus ( ) ,
71+ 'ArrowRight' : ( ) => index < inputs . length - 1 && inputs [ index + 1 ] . focus ( ) ,
72+ 'Tab' : ( e ) => {
73+ if ( e . shiftKey && index > 0 ) {
74+ e . preventDefault ( ) ;
75+ inputs [ index - 1 ] . focus ( ) ;
76+ } else if ( ! e . shiftKey && index < inputs . length - 1 ) {
77+ e . preventDefault ( ) ;
78+ inputs [ index + 1 ] . focus ( ) ;
79+ }
80+ }
81+ } ;
82+
83+ keyHandlers [ e . key ] ?. ( e ) ;
7984 } ) ;
85+
86+ // Enhanced focus management
87+ input . addEventListener ( 'focus' , ( ) => applyClasses ( input , INPUT_CLASSES . focus ) ) ;
88+ input . addEventListener ( 'blur' , ( ) => applyClasses ( input , INPUT_CLASSES . focus , true ) ) ;
8089 } ) ;
8190}
8291
@@ -159,7 +168,7 @@ async function handleOtpRequestError(message) {
159168 await showAlert ( 'error' , 'Error' , errorMessages [ message ] || message ) ;
160169
161170 // Restore original button content
162- DOM . requestOtpBtn . innerHTML = `<i class="fas fa-paper-plane"></i> Send OTP` ;
171+ DOM . requestOtpBtn . innerHTML = `<i class="fas fa-paper-plane"></i> Resend OTP` ;
163172}
164173// Enhanced OTP verification with better UI states
165174export async function verifyOtp ( ) {
@@ -207,24 +216,31 @@ export async function verifyOtp() {
207216
208217// Helper function: Start OTP countdown timer
209218function startOtpCountdown ( ) {
210- let secondsLeft = 30 ;
219+ let secondsLeft = 60 ;
211220 const originalBtnContent = DOM . requestOtpBtn . innerHTML ;
221+ let countdownInterval ;
222+
223+ // Clear any existing interval before starting new one
224+ if ( countdownInterval ) {
225+ clearInterval ( countdownInterval ) ;
226+ }
212227
213- const countdownInterval = setInterval ( ( ) => {
228+ countdownInterval = setInterval ( ( ) => {
229+ if ( secondsLeft <= 0 ) {
230+ clearInterval ( countdownInterval ) ;
231+ DOM . requestOtpBtn . disabled = false ;
232+ DOM . requestOtpBtn . classList . remove ( 'opacity-75' , 'cursor-not-allowed' ) ;
233+ DOM . requestOtpBtn . innerHTML = originalBtnContent ;
234+ return ;
235+ }
236+
214237 DOM . requestOtpBtn . innerHTML = `
215238 <span class="flex items-center justify-center gap-2">
216239 <i class="far fa-clock"></i>
217240 <span>Resend in ${ secondsLeft } s</span>
218241 </span>
219242 ` ;
220243 secondsLeft -- ;
221-
222- if ( secondsLeft < 0 ) {
223- clearInterval ( countdownInterval ) ;
224- DOM . requestOtpBtn . disabled = false ;
225- DOM . requestOtpBtn . classList . remove ( 'opacity-75' , 'cursor-not-allowed' ) ;
226- DOM . requestOtpBtn . innerHTML = originalBtnContent ;
227- }
228244 } , 1000 ) ;
229245}
230246
0 commit comments