import { RouteProp, useNavigation, useRoute } from "@react-navigation/native"; import axios from "axios"; import React, { useEffect, useRef, useState } from "react"; import { View, Text, StyleSheet, TextInput, Pressable, Animated, Platform, NativeSyntheticEvent, TextInputKeyPressEventData, KeyboardAvoidingView, StatusBar, Alert, ToastAndroid, } from "react-native"; import { RootStackParamList } from "../navigation/AppNavigator"; import * as Keychain from "react-native-keychain"; import { BASE_URL, LOGIN_URL, VARIFY_OTP_URL } from "../utils/ApiUrls"; import { StackNavigationProp } from "@react-navigation/stack"; import colors from "../components/colors"; import { useDispatch } from "react-redux"; import { setUserData } from "../redux/userSlice"; import { fetchUserProfile } from "../utils/HelperFunction"; const theme = { primary: "#111111", accent: colors.primary, background: "#F9F9F9", text: "#1A1A1A", subText: "#6B6B6B", outline: "#E5E5E5", }; const CELL_COUNT = 6; type OtpVerifyRouteProp = RouteProp; export default function OtpVerificationScreen() { const [code, setCode] = useState(Array(CELL_COUNT).fill("")); const [timer, setTimer] = useState(30); const [isResending, setIsResending] = useState(false); const inputs = useRef>([]); const route = useRoute(); const { mobile } = route.params; const navigation = useNavigation>(); // animations const fade = useRef(new Animated.Value(0)).current; const slide = useRef(new Animated.Value(24)).current; const ctaScale = useRef(new Animated.Value(1)).current; useEffect(() => { Animated.parallel([ Animated.timing(fade, { toValue: 1, duration: 600, useNativeDriver: true }), Animated.timing(slide, { toValue: 0, duration: 600, useNativeDriver: true }), ]).start(); }, [fade, slide]); // Countdown useEffect(() => { if (timer <= 0) return; const interval = setInterval(() => { setTimer((prev) => prev - 1); }, 1000); return () => clearInterval(interval); }, [timer]); const saveToken = async (token: string) => { try { await Keychain.setGenericPassword("auth", token, { accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED, }); console.log("✅ Token saved securely"); } catch (error) { console.error("❌ Error saving token:", error); } }; const onSubmit = async (otp: string, mobile: string) => { try { const payload = { mobile, otp }; const res = await axios.post( VARIFY_OTP_URL, payload, { headers: { "Content-Type": "application/json", Accept: "*/*" } } ); console.log("✅ OTP Verified:", res.data); if (res.data.statusCode === 200) { saveToken(res.data.data); fetchUserProfile() navigation.navigate('Home') ToastAndroid.show("OTP Verified Successfully!", ToastAndroid.SHORT); } } catch (err: any) { console.error("❌ OTP Verification Error:", err?.response?.data || err.message); ToastAndroid.show(err.message || "Invalid OTP. Please try again.", ToastAndroid.SHORT); } }; const resendOtp = async () => { try { setIsResending(true); setTimer(30); // reset timer const res = await axios.get( LOGIN_URL + mobile, { headers: { "Content-Type": "application/json", Accept: "*/*" } } ); console.log("📩 OTP Resent:", res); setCode(Array(CELL_COUNT).fill("")); inputs.current[0]?.focus(); ToastAndroid.show("OTP Sent", ToastAndroid.SHORT); } catch (err: any) { console.error("❌ Resend OTP Error:", err?.response?.data || err.message); ToastAndroid.show("Failed to resend OTP. Try again later.", ToastAndroid.SHORT); } finally { setIsResending(false); } }; const submitIfComplete = (arr: string[]) => { const joined = arr.join(""); if (joined.length === CELL_COUNT) { onSubmit(joined, mobile); } }; const updateIndex = (idx: number, val: string) => { const v = val.replace(/[^0-9]/g, ""); const next = [...code]; next[idx] = v.slice(-1); setCode(next); if (v && idx < CELL_COUNT - 1) inputs.current[idx + 1]?.focus(); submitIfComplete(next); }; const handleFirstCellChange = (val: string) => { const digits = val.replace(/\D/g, ""); if (digits.length <= 1) { updateIndex(0, digits); return; } const next = Array(CELL_COUNT) .fill("") .map((_, i) => digits[i] ?? ""); setCode(next); const firstEmpty = next.findIndex((c) => !c); if (firstEmpty >= 0) inputs.current[firstEmpty]?.focus(); else inputs.current[CELL_COUNT - 1]?.blur(); submitIfComplete(next); }; const handleKeyPress = (idx: number, e: NativeSyntheticEvent) => { if (e.nativeEvent.key === "Backspace" && !code[idx] && idx > 0) { inputs.current[idx - 1]?.focus(); } }; return ( {/* Top Section */} Verify Code 🔒 We’ve sent a 6-digit verification code to {mobile}. {Array.from({ length: CELL_COUNT }).map((_, i) => ( { inputs.current[i] = r; }} style={[styles.cell, code[i] ? styles.cellFilled : null]} keyboardType="number-pad" maxLength={1} value={code[i]} onChangeText={(t) => (i === 0 ? handleFirstCellChange(t) : updateIndex(i, t))} onKeyPress={(e) => handleKeyPress(i, e)} placeholder="•" placeholderTextColor="#BDBDBD" textContentType="oneTimeCode" /> ))} {/* Bottom Section */} Animated.spring(ctaScale, { toValue: 0.97, useNativeDriver: true }).start() } onPressOut={() => Animated.spring(ctaScale, { toValue: 1, useNativeDriver: true }).start() } onPress={() => onSubmit(code.join(""), mobile)} style={styles.button} > Continue {timer > 0 ? ( Resend code in 00:{timer.toString().padStart(2, "0")} ) : ( {isResending ? "Resending..." : "Resend Code"} )} ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: theme.background, padding: 20 }, topSection: { flex: 1, justifyContent: "center" }, title: { fontSize: 26, fontWeight: "800", color: theme.text, marginBottom: 8 }, subtitle: { fontSize: 15, color: theme.subText, marginBottom: 24, lineHeight: 22, }, otpRow: { flexDirection: "row", justifyContent: "space-between" }, cell: { width: 50, height: 58, borderRadius: 12, borderWidth: 1, borderColor: theme.outline, backgroundColor: "#fff", textAlign: "center", fontSize: 20, color: theme.text, }, cellFilled: { borderColor: "#D1D1D1" }, bottomSection: { marginBottom: 30 }, button: { backgroundColor: colors.primary, paddingVertical: 18, borderRadius: 14, alignItems: "center", justifyContent: "center", }, buttonText: { color: "#000", fontWeight: "700", fontSize: 16 }, footerText: { textAlign: "center", color: theme.subText, fontSize: 13, marginTop: 14, }, });