import React, { useEffect, useMemo, useRef, useState } from 'react'; import { View, TextInput, Text, TouchableOpacity, StyleSheet, ActivityIndicator, SafeAreaView, KeyboardAvoidingView, Platform, Dimensions, Animated, Easing, Keyboard } from 'react-native'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { LinearGradient } from 'expo-linear-gradient'; import { MaterialIcons } from '@expo/vector-icons'; import { useTheme } from '../contexts/ThemeContext'; import { useAccount } from '../contexts/AccountContext'; import { useNavigation, useRoute } from '@react-navigation/native'; import * as Haptics from 'expo-haptics'; import ToastManager, { Toast } from 'toastify-react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; const { width, height } = Dimensions.get('window'); const AuthScreen: React.FC = () => { const { currentTheme } = useTheme(); const { signIn, signUp } = useAccount(); const navigation = useNavigation(); const route = useRoute(); const fromOnboarding = !!route?.params?.fromOnboarding; const insets = useSafeAreaInsets(); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); const [showPassword, setShowPassword] = useState(false); const [showConfirm, setShowConfirm] = useState(false); const [mode, setMode] = useState<'signin' | 'signup'>('signin'); const signupDisabled = true; // Signup disabled due to upcoming system replacement const [error, setError] = useState(null); const [loading, setLoading] = useState(false); const [showWarningDetails, setShowWarningDetails] = useState(false); const authCardOpacity = useRef(new Animated.Value(1)).current; // Subtle, performant animations const introOpacity = useRef(new Animated.Value(0)).current; const introTranslateY = useRef(new Animated.Value(10)).current; const cardOpacity = useRef(new Animated.Value(0)).current; const cardTranslateY = useRef(new Animated.Value(12)).current; const ctaScale = useRef(new Animated.Value(1)).current; const titleOpacity = useRef(new Animated.Value(1)).current; const titleTranslateY = useRef(new Animated.Value(0)).current; const ctaTextOpacity = useRef(new Animated.Value(1)).current; const ctaTextTranslateY = useRef(new Animated.Value(0)).current; const modeAnim = useRef(new Animated.Value(0)).current; // 0 = signin, 1 = signup const [switchWidth, setSwitchWidth] = useState(0); // Legacy local toast state removed in favor of global toast const [headerHeight, setHeaderHeight] = useState(0); const headerHideAnim = useRef(new Animated.Value(0)).current; // 0 visible, 1 hidden const [keyboardHeight, setKeyboardHeight] = useState(0); useEffect(() => { Animated.parallel([ Animated.timing(introOpacity, { toValue: 1, duration: 300, easing: Easing.out(Easing.cubic), useNativeDriver: true, }), Animated.timing(introTranslateY, { toValue: 0, duration: 300, easing: Easing.out(Easing.cubic), useNativeDriver: true, }), Animated.timing(cardOpacity, { toValue: 1, duration: 360, delay: 90, easing: Easing.out(Easing.cubic), useNativeDriver: true, }), Animated.timing(cardTranslateY, { toValue: 0, duration: 360, delay: 90, easing: Easing.out(Easing.cubic), useNativeDriver: true, }), ]).start(); }, [cardOpacity, cardTranslateY, introOpacity, introTranslateY]); // Animate on mode change useEffect(() => { Animated.timing(modeAnim, { toValue: mode === 'signin' ? 0 : 1, duration: 220, easing: Easing.out(Easing.cubic), useNativeDriver: true, }).start(); Animated.sequence([ Animated.parallel([ Animated.timing(titleOpacity, { toValue: 0, duration: 120, useNativeDriver: true }), Animated.timing(titleTranslateY, { toValue: -6, duration: 120, useNativeDriver: true }), Animated.timing(ctaTextOpacity, { toValue: 0, duration: 120, useNativeDriver: true }), Animated.timing(ctaTextTranslateY, { toValue: -4, duration: 120, useNativeDriver: true }), ]), Animated.parallel([ Animated.timing(titleOpacity, { toValue: 1, duration: 180, useNativeDriver: true }), Animated.timing(titleTranslateY, { toValue: 0, duration: 180, useNativeDriver: true }), Animated.timing(ctaTextOpacity, { toValue: 1, duration: 180, useNativeDriver: true }), Animated.timing(ctaTextTranslateY, { toValue: 0, duration: 180, useNativeDriver: true }), ]), ]).start(); }, [mode, ctaTextOpacity, ctaTextTranslateY, modeAnim, titleOpacity, titleTranslateY]); // Hide/show header when keyboard toggles useEffect(() => { const showEvt = Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow'; const hideEvt = Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide'; const onShow = (e: any) => { const kh = e?.endCoordinates?.height ?? 0; setKeyboardHeight(kh); Animated.timing(headerHideAnim, { toValue: 1, duration: 180, easing: Easing.out(Easing.cubic), useNativeDriver: true, }).start(); }; const onHide = () => { setKeyboardHeight(0); Animated.timing(headerHideAnim, { toValue: 0, duration: 180, easing: Easing.out(Easing.cubic), useNativeDriver: true, }).start(); }; const subShow = Keyboard.addListener(showEvt, onShow as any); const subHide = Keyboard.addListener(hideEvt, onHide as any); return () => { subShow.remove(); subHide.remove(); }; }, [headerHideAnim]); const isEmailValid = useMemo(() => /\S+@\S+\.\S+/.test(email.trim()), [email]); const isPasswordValid = useMemo(() => password.length >= 6, [password]); const isConfirmValid = useMemo(() => (mode === 'signin') || confirmPassword.length >= 6, [confirmPassword, mode]); const passwordsMatch = useMemo(() => (mode === 'signin') || confirmPassword === password, [confirmPassword, password, mode]); const canSubmit = isEmailValid && isPasswordValid && (mode === 'signin' || (isConfirmValid && passwordsMatch)); const handleSubmit = async () => { if (loading) return; // Prevent signup if disabled if (mode === 'signup' && signupDisabled) { const msg = 'Sign up is currently disabled due to upcoming system changes'; setError(msg); Toast.error(msg); Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error).catch(() => {}); return; } if (!isEmailValid) { const msg = 'Enter a valid email address'; setError(msg); Toast.error(msg); Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error).catch(() => {}); return; } if (!isPasswordValid) { const msg = 'Password must be at least 6 characters'; setError(msg); Toast.error(msg); Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error).catch(() => {}); return; } if (mode === 'signup' && !passwordsMatch) { const msg = 'Passwords do not match'; setError(msg); Toast.error(msg); Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error).catch(() => {}); return; } setLoading(true); setError(null); const err = mode === 'signin' ? await signIn(email.trim(), password) : await signUp(email.trim(), password); if (err) { setError(err); Toast.error(err); Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error).catch(() => {}); } else { const msg = mode === 'signin' ? 'Logged in successfully' : 'Sign up successful'; Toast.success(msg); Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {}); // Navigate to main tabs after successful authentication navigation.reset({ index: 0, routes: [{ name: 'MainTabs' as never }] } as any); } setLoading(false); }; const handleSkipAuth = async () => { try { await AsyncStorage.setItem('showLoginHintToastOnce', 'true'); } catch {} navigation.reset({ index: 0, routes: [{ name: 'MainTabs' as never }] } as any); }; const toggleWarningDetails = () => { if (showWarningDetails) { // Fade in auth card Animated.timing(authCardOpacity, { toValue: 1, duration: 300, easing: Easing.out(Easing.cubic), useNativeDriver: true, }).start(); } else { // Fade out auth card Animated.timing(authCardOpacity, { toValue: 0, duration: 300, easing: Easing.out(Easing.cubic), useNativeDriver: true, }).start(); } setShowWarningDetails(!showWarningDetails); }; // showToast helper replaced with direct calls to toast.* API return ( {Platform.OS !== 'android' ? ( ) : ( )} {/* Background Pattern (iOS only) */} {Platform.OS !== 'android' && ( {Array.from({ length: 20 }).map((_, i) => ( ))} )} {/* Header outside KeyboardAvoidingView to avoid being overlapped */} setHeaderHeight(e.nativeEvent.layout.height)} style={[ styles.header, { opacity: Animated.multiply( introOpacity, headerHideAnim.interpolate({ inputRange: [0, 1], outputRange: [1, 0] }) ), transform: [ { translateY: Animated.add( introTranslateY, headerHideAnim.interpolate({ inputRange: [0, 1], outputRange: [0, -12] }) ), }, ], }, ]} > {navigation.canGoBack() && ( navigation.goBack()} style={[styles.backButton, Platform.OS === 'android' ? { top: Math.max(insets.top + 6, 18) } : null]} hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}> )} {mode === 'signin' ? 'Welcome back' : 'Create your account'} Sync your addons, progress and settings across devices {/* Important Warning Message */} Important Notice This authentication system will be completely replaced by local backup/restore functionality by October 8th. Please create backup files as your cloud data will be permanently destroyed. Read more {showWarningDetails ? '▼' : '▶'} {/* Expanded Details */} {showWarningDetails && ( Why is this system being discontinued? • Lack of real-time support for addon synchronization{'\n'} • Database synchronization issues with addons and settings{'\n'} • Unreliable cloud data management{'\n'} • Performance problems with remote data access Benefits of Local Backup System: • Instant addon synchronization across devices{'\n'} • Reliable offline access to all your data{'\n'} • Complete control over your backup files{'\n'} • Faster performance with local data storage{'\n'} • No dependency on external servers{'\n'} • Easy migration between devices )} {/* Main Card - Hide when warning details are expanded */} {/* Mode Toggle */} setSwitchWidth(e.nativeEvent.layout.width)} style={[styles.switchRow, { backgroundColor: Platform.OS === 'android' ? '#1a1a1a' : 'rgba(255,255,255,0.04)' }]} > {/* Animated indicator */} setMode('signin')} activeOpacity={0.8} > Sign In !signupDisabled && setMode('signup')} activeOpacity={signupDisabled ? 1 : 0.8} disabled={signupDisabled} > Sign Up {signupDisabled && '(Disabled)'} {/* Email Input */} {Platform.OS !== 'android' && isEmailValid && ( )} {/* Password Input */} setShowPassword(p => !p)} style={styles.eyeButton}> {Platform.OS !== 'android' && isPasswordValid && ( )} {/* Confirm Password (signup only) */} {mode === 'signup' && ( setShowConfirm(p => !p)} style={styles.eyeButton}> {Platform.OS !== 'android' && passwordsMatch && isConfirmValid && ( )} )} {/* Error */} {!!error && ( {error} )} {/* Submit Button */} { Animated.spring(ctaScale, { toValue: 0.98, useNativeDriver: true, speed: 20, bounciness: 0, }).start(); }} onPressOut={() => { Animated.spring(ctaScale, { toValue: 1, useNativeDriver: true, speed: 20, bounciness: 6, }).start(); }} activeOpacity={0.85} disabled={loading} > {loading ? ( ) : ( {mode === 'signin' ? 'Sign In' : 'Create Account'} )} {/* Switch Mode */} {!signupDisabled && ( setMode(mode === 'signin' ? 'signup' : 'signin')} activeOpacity={0.7} style={{ marginTop: 16 }} > {mode === 'signin' ? "Don't have an account? " : 'Already have an account? '} {mode === 'signin' ? 'Sign up' : 'Sign in'} )} {/* Signup disabled message */} {signupDisabled && mode === 'signin' && ( New account creation is temporarily disabled )} {/* Skip sign in - more prominent when coming from onboarding */} Continue without an account {/* Toasts rendered globally in App root */} ); }; const styles = StyleSheet.create({ backgroundPattern: { ...StyleSheet.absoluteFillObject, zIndex: 0, }, patternDot: { position: 'absolute', width: 2, height: 2, backgroundColor: '#fff', borderRadius: 1, }, header: { alignItems: 'center', paddingTop: 64, paddingBottom: 8, }, logoContainer: { position: 'relative', alignItems: 'center', justifyContent: 'center', marginBottom: 16, }, logo: { width: 180, height: 54, zIndex: 2, }, logoGlow: { position: 'absolute', width: 200, height: 70, backgroundColor: 'rgba(229, 9, 20, 0.1)', borderRadius: 35, zIndex: 1, }, heading: { fontSize: 28, fontWeight: '800', letterSpacing: -0.5, marginBottom: 4, }, subheading: { fontSize: 13, lineHeight: 18, textAlign: 'center', paddingHorizontal: 20, marginTop: 1, }, centerContainer: { flex: 1, alignItems: 'center', justifyContent: 'flex-start', paddingHorizontal: 20, paddingTop: 20, paddingBottom: 28, }, card: { width: '100%', maxWidth: 400, padding: 24, borderRadius: 20, borderWidth: 1, elevation: 20, }, switchRow: { flexDirection: 'row', borderRadius: 14, padding: 3, marginBottom: 24, position: 'relative', overflow: 'hidden', }, switchIndicator: { position: 'absolute', top: 3, bottom: 3, left: 3, borderRadius: 12, }, switchButton: { flex: 1, paddingVertical: 12, borderRadius: 12, alignItems: 'center', elevation: 0, }, switchText: { fontWeight: '700', fontSize: 15, letterSpacing: 0.2, }, inputContainer: { marginBottom: 16, }, inputRow: { height: 56, flexDirection: 'row', alignItems: 'center', borderRadius: 14, paddingHorizontal: 4, }, iconContainer: { width: 40, height: 40, borderRadius: 10, alignItems: 'center', justifyContent: 'center', marginRight: 12, }, input: { flex: 1, fontSize: 16, paddingVertical: 0, fontWeight: '500', }, eyeButton: { width: 40, height: 40, alignItems: 'center', justifyContent: 'center', marginRight: 4, }, errorRow: { flexDirection: 'row', alignItems: 'center', marginBottom: 16, paddingHorizontal: 4, }, errorText: { color: '#ff6b6b', marginLeft: 8, fontSize: 13, fontWeight: '500', }, ctaButton: { height: 56, borderRadius: 14, alignItems: 'center', justifyContent: 'center', marginTop: 8, elevation: 0, }, ctaText: { color: '#fff', fontWeight: '700', fontSize: 16, letterSpacing: 0.3, }, backButton: { position: 'absolute', left: 16, top: 8, }, switchModeText: { textAlign: 'center', fontSize: 14, fontWeight: '500', }, warningContainer: { paddingHorizontal: 20, marginTop: 24, marginBottom: 8, }, warningCard: { flexDirection: 'row', padding: 16, borderRadius: 12, borderWidth: 1, alignItems: 'flex-start', }, warningIcon: { marginRight: 12, marginTop: 2, }, warningContent: { flex: 1, }, warningTitle: { fontSize: 16, fontWeight: '700', marginBottom: 6, }, warningText: { fontSize: 14, lineHeight: 20, fontWeight: '500', }, disabledButton: { opacity: 0.5, }, readMoreText: { fontSize: 14, fontWeight: '600', marginTop: 8, alignSelf: 'flex-start', }, warningDetails: { marginTop: 8, borderRadius: 12, borderWidth: 1, overflow: 'hidden', }, detailsContent: { padding: 16, }, detailsTitle: { fontSize: 15, fontWeight: '700', marginBottom: 8, }, detailsText: { fontSize: 13, lineHeight: 18, fontWeight: '500', }, }); export default AuthScreen;