import React, { useState, useEffect, useCallback, useRef } from "react"; import { View, ActivityIndicator, Pressable } from "react-native"; import { useSirouRouter } from "@sirou/react-native"; import { AppRoutes } from "@/lib/routes"; import { Text } from "@/components/ui/text"; import { useLocalSearchParams, Stack } from "expo-router"; import { ScreenWrapper } from "@/components/ScreenWrapper"; import { StandardHeader } from "@/components/StandardHeader"; import { Keypad } from "@/components/Keypad"; import { api } from "@/lib/api"; import { useAuthStore } from "@/lib/auth-store"; import { usePinStore } from "@/lib/pin-store"; import { toast } from "@/lib/toast-store"; export default function OtpScreen() { const nav = useSirouRouter(); const { phone, verificationId, expiresInSeconds } = useLocalSearchParams<{ phone: string; verificationId: string; expiresInSeconds?: string; }>(); const setAuth = useAuthStore((state) => state.setAuth); const mounted = useRef(true); useEffect(() => { return () => { mounted.current = false; }; }, []); const [code, setCode] = useState(""); const [loading, setLoading] = useState(false); const [timer, setTimer] = useState( expiresInSeconds ? parseInt(expiresInSeconds, 10) : 60, ); const formatTime = (seconds: number) => { if (seconds > 60) { const mins = Math.floor(seconds / 60); const secs = seconds % 60; return `${mins}:${secs < 10 ? "0" : ""}${secs}`; } return `${seconds}s`; }; useEffect(() => { let interval: any; if (timer > 0) { interval = setInterval(() => setTimer((t) => t - 1), 1000); } return () => clearInterval(interval); }, [timer]); useEffect(() => { if (code.length === 6 && !loading) handleVerify(); }, [code]); const handleVerify = async () => { setLoading(true); try { const response = await api.auth.verifyOtp({ body: { phone: phone as string, code, verificationId: verificationId as string, }, }); setAuth( response.user, response.accessToken, response.refreshToken, [], ); toast.success("Welcome!", "Login successful."); if (response.is_pin_set !== false) usePinStore.getState().unlock(); nav.go(response.is_pin_set === false ? "set-pin" : "(tabs)"); } catch (err: any) { setCode(""); toast.error("Verification Failed", err.message || "Invalid or expired code"); } finally { if (mounted.current) setLoading(false); } }; const handleResend = async () => { setLoading(true); try { const response = await api.auth.resendOtp({ body: { phone: phone as string }, }); toast.success("OTP Sent", response.message || "A new verification code has been sent."); if (response.expiresInSeconds) setTimer(response.expiresInSeconds); else setTimer(60); } catch (err: any) { const errMsg = err.message || "Failed to resend code"; toast.error("Wait", errMsg); const match = errMsg.match(/wait (\d+) seconds/i); if (match?.[1]) setTimer(parseInt(match[1], 10)); } finally { if (mounted.current) setLoading(false); } }; const handlePress = useCallback((key: string) => { setCode((prev) => (prev + key).slice(0, 6)); }, []); const handleDelete = useCallback(() => { setCode((prev) => prev.slice(0, -1)); }, []); const arr = code.split(""); return ( Verify your number Enter the 6-digit code we sent to{"\n"} {phone} {[0, 1, 2, 3, 4, 5].map((i) => ( {arr[i] ?? ""} ))} {loading && } {timer > 0 ? ( Resend code in{" "} {formatTime(timer)} ) : ( Resend Verification Code )} ); }