import React, { useState, useEffect, useRef } from "react"; import { View, Alert, TextInput } from "react-native"; import { useForm, Controller } from "react-hook-form"; import { MyButton, MyText, MyTextInput, tw, StorageServiceCasual, colors, MyTouchableOpacity } from "common-ui"; import { useAuth } from "@/src/contexts/AuthContext"; import { trpc } from '@/src/trpc-client'; import GoogleSignInPKCE from "common-ui/src/components/google-sign-in"; import { LinearGradient } from "expo-linear-gradient"; import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view"; import { SafeAreaView } from "react-native-safe-area-context"; interface LoginFormInputs { mobile: string; otp?: string; password?: string; } function Login() { const { loginWithToken } = useAuth(); const [isLoading, setIsLoading] = useState(false); const [step, setStep] = useState<'mobile' | 'choice' | 'otp' | 'password'>('mobile'); const [selectedMobile, setSelectedMobile] = useState(''); const [canResend, setCanResend] = useState(false); const [resendCountdown, setResendCountdown] = useState(0); const [otpCells, setOtpCells] = useState(['', '', '', '']); const intervalRef = useRef(null); const inputRefs = useRef<(TextInput | null)[]>([null, null, null, null]); const loginMutation = trpc.user.auth.login.useMutation(); // const loginMutation = useLogin(); // Check for stored OTP timestamp on mount useEffect(() => { const checkStoredOtpTime = async () => { const storedTime = await StorageServiceCasual.getItem('otp_sent_time'); if (storedTime) { const timeDiff = Date.now() - parseInt(storedTime); const remainingTime = Math.max(0, 120 - Math.floor(timeDiff / 1000)); if (remainingTime > 0) { setResendCountdown(remainingTime); setCanResend(false); } else { setCanResend(true); setResendCountdown(0); } } else { setCanResend(true); } }; checkStoredOtpTime(); }, []); // Cleanup interval on unmount useEffect(() => { return () => { if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = null; } }; }, []); // Countdown timer effect useEffect(() => { // Clear existing interval if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = null; } if (resendCountdown > 0) { // Set new interval and attach to ref intervalRef.current = setInterval(() => { setResendCountdown((prev) => { if (prev <= 1) { setCanResend(true); return 0; } return prev - 1; }); }, 1000); } return () => { // Cleanup on unmount or dependency change if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = null; } }; }, [resendCountdown]); const sendOtpMutation = trpc.user.auth.sendOtp.useMutation({ onSuccess: async (data) => { if (data.success) { // Save the current timestamp for resend cooldown await StorageServiceCasual.setItem('otp_sent_time', Date.now().toString()); setResendCountdown(120); // 2 minutes setCanResend(false); setStep('otp'); Alert.alert('Success', data.message); } else { Alert.alert('Error', data.message); } }, onError: (error: any) => { Alert.alert('Error', error.message || 'Failed to send OTP'); }, }); const verifyOtpMutation = trpc.user.auth.verifyOtp.useMutation({ onSuccess: (data) => { if (data.success && data.token && data.user) { loginWithToken(data.token, data.user); } else { Alert.alert('Error', 'Verification failed'); } }, onError: (error: any) => { Alert.alert('Error', error.message || 'Invalid OTP'); }, }); const { control, handleSubmit, formState: { errors }, setError, clearErrors, setValue, } = useForm({ defaultValues: { mobile: "", otp: "", password: "" }, }); const validateMobile = (mobile: string): boolean => { // Remove all non-digit characters const cleanMobile = mobile.replace(/\D/g, ''); // Check if it's a valid Indian mobile number (10 digits, starts with 6-9) return cleanMobile.length === 10 && /^[6-9]/.test(cleanMobile); }; const handleOtpChange = (index: number, text: string) => { // Handle paste (multiple characters) if (text.length > 1) { const digits = text.replace(/\D/g, '').slice(0, 4); const newCells = digits.split('').concat(['', '', '', '']).slice(0, 4); setOtpCells(newCells); const combined = newCells.join(''); setValue('otp', combined); // Focus last filled cell const lastIndex = Math.min(digits.length - 1, 3); inputRefs.current[lastIndex]?.focus(); return; } // Handle single digit input const newCells = [...otpCells]; newCells[index] = text; setOtpCells(newCells); const combined = newCells.join(''); setValue('otp', combined); // Auto-focus logic if (text && index < 3) { // Move to next cell inputRefs.current[index + 1]?.focus(); } else if (!text && index > 0) { // Move to previous cell on delete inputRefs.current[index - 1]?.focus(); } }; const onSubmit = async (data: LoginFormInputs) => { clearErrors(); if (step === 'mobile') { const mobile = data.mobile.trim(); // Validate mobile number if (!mobile) { console.log('no mobile number found') setError("mobile", { type: "manual", message: "Mobile number is required", }); return; } if (!validateMobile(data.mobile)) { setError("mobile", { type: "manual", message: "Please enter a valid 10-digit mobile number", }); return; } const cleanMobile = data.mobile.replace(/\D/g, ''); setSelectedMobile(cleanMobile); sendOtpMutation.mutate({ mobile }); } else if (step === 'otp') { // Verify OTP if (!data.otp || data.otp.length < 4) { setError("otp", { type: "manual", message: "Please enter a valid OTP", }); return; } verifyOtpMutation.mutate({ mobile: selectedMobile, otp: data.otp, }); } else if (step === 'password') { // Login with password if (!data.password || data.password.length < 6) { setError("password", { type: "manual", message: "Password must be at least 6 characters", }); return; } try { console.log('calling the login function') const response = await loginMutation.mutateAsync({ identifier: selectedMobile, password: data.password, }); loginWithToken(response.data.token, response.data.user); } catch (error: any) { Alert.alert('Error', error.message || 'Login failed'); } } }; return ( Welcome Sign in to continue your journey {step === 'mobile' && ( <> ( { // Format mobile number as user types const clean = text.replace(/\D/g, ''); if (clean.length <= 10) { onChange(clean); } }} onBlur={onBlur} keyboardType="phone-pad" maxLength={10} style={tw`bg-gray-50`} error={!!errors.mobile} /> )} /> {errors.mobile && ( {errors.mobile.message} )} )} {step === 'choice' && ( Choose your login method for {selectedMobile} setStep('password')} fillColor="gray1" textColor="black1" style={tw`flex-1 mr-2 border border-gray-200`} /> sendOtpMutation.mutate({ mobile: selectedMobile })} fillColor="brand500" textColor="white1" style={tw`flex-1 ml-2 shadow-sm`} disabled={sendOtpMutation.isPending} /> { setStep('mobile'); setValue('mobile', ''); clearErrors(); }} style={tw`mt-2`} > Change Number )} {step === 'otp' && ( <> Enter 4-digit OTP {[0, 1, 2, 3].map((i) => ( { inputRefs.current[i] = ref; }} style={tw`w-14 h-14 ${errors.otp ? 'border-red-500' : 'border-gray-200'} border-2 rounded-xl text-center text-2xl font-bold ${otpCells[i] ? 'bg-blue-50 border-brand500 text-brand700' : 'bg-gray-50'}`} keyboardType="numeric" maxLength={1} value={otpCells[i]} onChangeText={(text) => handleOtpChange(i, text)} selectionColor={colors.brand500} /> ))} {errors.otp && ( {errors.otp.message} )} { setStep('password'); setValue('otp', ''); setOtpCells(['', '', '', '']); clearErrors(); }} style={tw`mb-6`} > Or login with Password { setStep('choice'); setValue('otp', ''); setOtpCells(['', '', '', '']); clearErrors(); }} > Back sendOtpMutation.mutate({ mobile: selectedMobile })} disabled={!canResend || sendOtpMutation.isPending} > {sendOtpMutation.isPending ? 'Sending...' : canResend ? 'Resend OTP' : `Resend in ${resendCountdown}s` } )} {step === 'password' && ( <> ( )} /> {errors.password && ( {errors.password.message} )} { setStep('choice'); setValue('password', ''); clearErrors(); }} > Back to options )} {(step === 'mobile' || step === 'otp' || step === 'password') && ( {isLoading || sendOtpMutation.isPending || verifyOtpMutation.isPending || loginMutation.isPending ? (step === 'otp' ? "Verifying..." : step === 'password' ? "Logging in..." : "Processing...") : (step === 'otp' ? "Verify & Login" : step === 'password' ? "Login" : "Continue")} )} ); } export default Login;