import React, { useState, useEffect, useRef } from 'react'; import { StyleSheet, View, Text, TextInput, TouchableOpacity, KeyboardAvoidingView, Platform, ActivityIndicator, ScrollView, StatusBar, Animated, } from 'react-native'; import { useNavigation } from '@react-navigation/native'; import { supabase } from '../../lib/supabase'; import { COLORS, SHADOWS } from '../../styles/theme'; import { Mail, KeyRound, Lock, ShieldCheck, ArrowRight, ArrowLeft, CheckCircle2, Eye, EyeOff } from 'lucide-react-native'; import * as Haptics from 'expo-haptics'; import { useNotification } from '../../components/NotificationProvider'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; const TOTAL_STEPS = 3; const StepIndicator = ({ currentStep }) => ( {[1, 2, 3].map((s) => ( = s && styles.stepDotActive, currentStep > s && styles.stepDotDone]}> {currentStep > s ? : = s && styles.stepNumActive]}>{s}} {s < 3 && s && styles.stepLineActive]} />} ))} ); const ForgotPasswordScreen = () => { const navigation = useNavigation(); const { success, error: notifyError, info } = useNotification(); const insets = useSafeAreaInsets(); const [step, setStep] = useState(1); const [email, setEmail] = useState(''); const [otp, setOtp] = useState(''); const [newPassword, setNewPassword] = useState(''); const [showPassword, setShowPassword] = useState(false); const [loading, setLoading] = useState(false); // OTP countdown timer (15 minutes) const [timeLeft, setTimeLeft] = useState(900); const [timerExpired, setTimerExpired] = useState(false); const fadeAnim = useRef(new Animated.Value(0)).current; const slideAnim = useRef(new Animated.Value(20)).current; useEffect(() => { Animated.parallel([ Animated.timing(fadeAnim, { toValue: 1, duration: 500, useNativeDriver: true }), Animated.timing(slideAnim, { toValue: 0, duration: 500, useNativeDriver: true }), ]).start(); }, []); // Animate on step change const animateStep = () => { fadeAnim.setValue(0); slideAnim.setValue(20); Animated.parallel([ Animated.timing(fadeAnim, { toValue: 1, duration: 400, useNativeDriver: true }), Animated.timing(slideAnim, { toValue: 0, duration: 400, useNativeDriver: true }), ]).start(); }; // Timer for step 2 useEffect(() => { if (step !== 2) return; setTimerExpired(false); setTimeLeft(900); const timer = setInterval(() => { setTimeLeft((prev) => { if (prev <= 1) { clearInterval(timer); setTimerExpired(true); return 0; } return prev - 1; }); }, 1000); return () => clearInterval(timer); }, [step]); const formatTime = (s) => `${Math.floor(s / 60)}:${(s % 60).toString().padStart(2, '0')}`; const goToStep = (n) => { setStep(n); animateStep(); }; // Step 1: Send OTP const handleSendOtp = async () => { if (!email) { notifyError('Email Required', 'Please enter your email address.'); return; } setLoading(true); try { const { error } = await supabase.auth.resetPasswordForEmail(email); if (error) throw error; Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); info('Code Sent', `Check your email for the 6-digit recovery code.`); goToStep(2); } catch (err) { Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error); notifyError('Failed to Send', err.message || 'An error occurred.'); } finally { setLoading(false); } }; // Step 2: Verify OTP const handleVerifyOtp = async () => { if (!otp || otp.length !== 6) { notifyError('Invalid Code', 'Please enter the 6-digit code.'); return; } if (timerExpired) { notifyError('Code Expired', 'Please request a new code.'); return; } setLoading(true); try { const { error } = await supabase.auth.verifyOtp({ email, token: otp, type: 'recovery' }); if (error) throw error; Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); info('Code Verified', 'Now set your new password.'); goToStep(3); } catch (err) { Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error); notifyError('Invalid Code', 'The code is wrong or expired. Please try again.'); } finally { setLoading(false); } }; // Step 3: Update Password const handleUpdatePassword = async () => { if (!newPassword || newPassword.length < 6) { notifyError('Weak Password', 'Password must be at least 6 characters.'); return; } setLoading(true); try { const { error } = await supabase.auth.updateUser({ password: newPassword }); if (error) throw error; Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); success('Password Updated!', 'You can now sign in with your new password.'); goToStep(4); } catch (err) { Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error); notifyError('Update Failed', err.message || 'Failed to update password.'); } finally { setLoading(false); } }; // Step 4: Success if (step === 4) { return ( Password Updated! Your password has been successfully changed. You can now sign in with your new credentials. navigation.navigate('Login')} activeOpacity={0.85}> Back to Login ); } return ( {/* Back Button */} step === 1 ? navigation.goBack() : goToStep(step - 1)}> {/* Header */} Reset Password {step === 1 && 'Enter your email to receive a recovery code'} {step === 2 && 'Enter the 6-digit code sent to your email'} {step === 3 && 'Create a strong new password'} {/* Step Indicator */} {/* Card */} {/* Step 1 */} {step === 1 && ( <> EMAIL ADDRESS {loading ? : ( <> Send Recovery Code )} )} {/* Step 2 */} {step === 2 && ( <> Sent to {email} 6-DIGIT CODE {timerExpired ? 'EXPIRED' : formatTime(timeLeft)} {loading ? : ( <> Verify Code )} {timerExpired && ( goToStep(1)}> Resend Code )} )} {/* Step 3 */} {step === 3 && ( <> NEW PASSWORD setShowPassword(!showPassword)}> {showPassword ? : } {loading ? : ( <> Update Password )} )} ); }; const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#0b1120' }, bgBlob1: { position: 'absolute', top: -120, right: -80, width: 300, height: 300, borderRadius: 150, backgroundColor: COLORS.primary + '20' }, scroll: { flexGrow: 1, padding: 28 }, backBtn: { width: 44, height: 44, borderRadius: 14, backgroundColor: 'rgba(255,255,255,0.07)', justifyContent: 'center', alignItems: 'center', marginBottom: 24, borderWidth: 1, borderColor: 'rgba(255,255,255,0.09)' }, header: { marginBottom: 32 }, title: { fontSize: 30, fontWeight: '900', color: '#fff', letterSpacing: -0.8 }, subtitle: { fontSize: 15, color: 'rgba(255,255,255,0.45)', marginTop: 8, lineHeight: 22 }, // Step Indicator stepRow: { flexDirection: 'row', alignItems: 'center', marginBottom: 32 }, stepItem: { flexDirection: 'row', alignItems: 'center', flex: 1 }, stepDot: { width: 32, height: 32, borderRadius: 10, backgroundColor: 'rgba(255,255,255,0.08)', justifyContent: 'center', alignItems: 'center', borderWidth: 1, borderColor: 'rgba(255,255,255,0.1)' }, stepDotActive: { backgroundColor: COLORS.primary + '30', borderColor: COLORS.primary + '60' }, stepDotDone: { backgroundColor: COLORS.primary, borderColor: COLORS.primary }, stepNum: { fontSize: 13, fontWeight: '700', color: 'rgba(255,255,255,0.35)' }, stepNumActive: { color: COLORS.primary }, stepLine: { flex: 1, height: 2, backgroundColor: 'rgba(255,255,255,0.08)', marginHorizontal: 6 }, stepLineActive: { backgroundColor: COLORS.primary }, // Card card: { backgroundColor: 'rgba(255,255,255,0.05)', borderRadius: 28, padding: 24, borderWidth: 1, borderColor: 'rgba(255,255,255,0.09)', gap: 18 }, field: { gap: 8 }, label: { fontSize: 11, fontWeight: '700', color: 'rgba(255,255,255,0.45)', letterSpacing: 1.2 }, labelRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }, inputRow: { flexDirection: 'row', alignItems: 'center', backgroundColor: 'rgba(255,255,255,0.07)', borderRadius: 16, paddingHorizontal: 14, height: 56, borderWidth: 1, borderColor: 'rgba(255,255,255,0.08)' }, input: { flex: 1, fontSize: 15, color: '#fff' }, otpInput: { fontSize: 22, fontWeight: '700', letterSpacing: 8 }, btn: { backgroundColor: COLORS.primary, height: 60, borderRadius: 18, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 10, ...SHADOWS.medium }, btnText: { color: '#fff', fontSize: 17, fontWeight: '800' }, // OTP Step otpInfo: { flexDirection: 'row', alignItems: 'center', gap: 10, backgroundColor: COLORS.primary + '15', borderRadius: 14, padding: 14, borderWidth: 1, borderColor: COLORS.primary + '30' }, otpInfoText: { fontSize: 14, color: 'rgba(255,255,255,0.6)' }, timer: { fontSize: 13, fontWeight: '800', color: COLORS.primary, letterSpacing: 1 }, timerExpired: { color: '#ef4444' }, resendBtn: { alignItems: 'center', paddingVertical: 4 }, resendText: { color: COLORS.primary, fontWeight: '700', fontSize: 15 }, // Success successWrap: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 40, gap: 20 }, successIcon: { width: 100, height: 100, borderRadius: 30, backgroundColor: COLORS.primary + '20', justifyContent: 'center', alignItems: 'center', borderWidth: 1.5, borderColor: COLORS.primary + '50' }, successTitle: { fontSize: 26, fontWeight: '900', color: '#fff', textAlign: 'center' }, successMsg: { fontSize: 15, color: 'rgba(255,255,255,0.55)', textAlign: 'center', lineHeight: 26 }, }); export default ForgotPasswordScreen;