RyanHub – file viewer
filename: app/Onboarding.jsx
branch: master
back to repo
import { StyleSheet, Text, View, TouchableOpacity, TextInput, ActivityIndicator, KeyboardAvoidingView, Animated } from 'react-native'
import { useState, useRef } from 'react';
import { AntDesign } from '@expo/vector-icons';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { useAppTheme } from '../hooks/colorScheme';
import FeaturesSlideshow from '../components/FeaturesSlideshow'
import Purchases from 'react-native-purchases';
import React from 'react'
import { doc, setDoc } from 'firebase/firestore';
import { FIREBASE_DB } from '../FirebaseConfig';
import { FIREBASE_AUTH } from '../FirebaseConfig'
import { createUserWithEmailAndPassword } from 'firebase/auth'

const Onboarding = ({ navigation }) => {
    const colors = useAppTheme();
    const [email, setEmail] = useState('')
    const [password, setPassword] = useState('')
    const [loading, setLoading] = useState(false)
    const [error, setError] = useState('')
    const fadeAnim = useRef(new Animated.Value(0)).current;
    const auth = FIREBASE_AUTH

    const showError = (message) => {
        setError(message);
        fadeAnim.setValue(0);
        Animated.timing(fadeAnim, {
            toValue: 1,
            duration: 200,
            useNativeDriver: true,
        }).start();
    };

    const handleEmailChange = (text) => {
        setEmail(text);
        if (error) setError('');
    };

    const handlePasswordChange = (text) => {
        setPassword(text);
        if (error) setError('');
    };

    const getDetailedErrorMessage = (error) => {
        const errorCode = error.code?.replace('auth/', '') || 'internal-error';
        
        const errorMessages = {
            'invalid-email': 'Please enter a valid email address.',
            'email-already-in-use': 'This email is already registered. Please try signing in instead.',
            'weak-password': 'Password should be at least 6 characters long.',
            'operation-not-allowed': 'Email/password sign up is not enabled. Please contact support.',
            'network-request-failed': 'Connection failed. Please check your internet and try again.',
            'too-many-requests': 'Too many attempts. Please try again later.',
            'internal-error': 'An unexpected error occurred. Please try again.',
            'invalid-credential': 'Invalid signup credentials. Please check your information.',
            'user-disabled': 'This account has been disabled. Please contact support.',
            'missing-email': 'Please enter an email address.',
            'missing-password': 'Please enter a password.',
        };

        return errorMessages[errorCode] || 
               'Something went wrong. Please try again or contact support if the problem persists.';
    };

    const handleFirebaseError = (error) => {
        const message = getDetailedErrorMessage(error);
        showError(message);
    };

    const validateForm = () => {
        if (!email.trim()) {
            showError('Please enter an email address');
            return false;
        }
        if (!password.trim()) {
            showError('Please enter a password');
            return false;
        }
        // Basic email format validation
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        if (!emailRegex.test(email)) {
            showError('Please enter a valid email address');
            return false;
        }
        // Basic password strength validation
        if (password.length < 6) {
            showError('Password must be at least 6 characters long');
            return false;
        }
        return true;
    };

    const signUp = async () => {
        if (!validateForm()) return;
        
        setLoading(true);
        try {
            const userCredential = await createUserWithEmailAndPassword(auth, email, password);
            await AsyncStorage.setItem('email', email);
            await AsyncStorage.setItem('password', password);
            await AsyncStorage.setItem('isLoggedOut', 'false');
            await addUserToCollection(email, userCredential.user.uid);
        } catch (error) {
            handleFirebaseError(error);
            console.error('Signup error:', error);
        } finally {
            setLoading(false);
        }
    };

    const addUserToCollection = async (email, uid) => {
        const userDocRef = doc(FIREBASE_DB, "users", uid);
        await setDoc(userDocRef, {
            email: email,
            createdAt: new Date(),
            uid: uid,
        });
        
        // set default goals if there are none already
        if (!(await AsyncStorage.getItem('userGoals'))) {
            
            const defaultGoals = {
                protein: '150',
                carbs: '150',
                fats: '100',
                dailyCalories: '2100',
            
                transFat: '2',
                saturatedFat: '20',
                polyunsaturatedFat: '17',
                monounsaturatedFat: '44',
            
                netCarbs: '53',
                sugar: '25',
                fiber: '28',
            
                cholesterol: '300',
                sodium: '2300',
                calcium: '1000',
                magnesium: '400',
                phosphorus: '700',
                potassium: '3400',
            
                iron: '18',
                copper: '900',
                zinc: '11',
                manganese: '2.3',
                selenium: '55',
            
                vitaminA: '900',
                vitaminD: '20',
                vitaminE: '15',
                vitaminK: '120',
                
                vitaminC: '90',
                vitaminB1: '1.2',
                vitaminB12: '2.4',
                vitaminB2: '1.3',
                vitaminB3: '16',
                vitaminB5: '5',
                vitaminB6: '1.7',
                folate: '400',
            };
            await AsyncStorage.setItem('userGoals', JSON.stringify(defaultGoals));
        }
    }

    return (
        <View style={[styles.container, {backgroundColor: colors.background}]}>
            <KeyboardAvoidingView behavior='padding' style={styles.mainContainer}>
                <View style={styles.intro}>
                    <Text style={[styles.subtitle, {color:colors.text}]}>You're too intelligent for all the BS</Text>
                    <Text style={{color: colors.text, fontSize: 16}}>Let's get you started with a free account</Text>
                </View>

                <View style={styles.inputContainer}>
                    <View style={styles.inputWrapper}>
                        <Text style={[styles.inputLabel, {color: colors.text}]}>Email</Text>
                        <TextInput 
                            style={[styles.input, {
                                color: colors.text, 
                                backgroundColor: colors.boxes,
                                borderColor: error ? colors.accent : colors.boxes,
                                borderWidth: error ? 2 : 1,
                            }]}
                            placeholder="Enter your email"
                            placeholderTextColor={colors.text + '40'}
                            value={email}
                            onChangeText={handleEmailChange}
                            keyboardType='email-address'
                            autoCapitalize='none'
                        />
                    </View>
                    <View style={styles.inputWrapper}>
                        <Text style={[styles.inputLabel, {color: colors.text}]}>Password</Text>
                        <TextInput 
                            style={[styles.input, {
                                color: colors.text, 
                                backgroundColor: colors.boxes,
                                borderColor: error ? colors.accent : colors.boxes,
                                borderWidth: error ? 2 : 1,
                            }]}
                            placeholder="Create a password"
                            placeholderTextColor={colors.text + '40'}
                            value={password}
                            onChangeText={handlePasswordChange}
                            secureTextEntry
                        />
                    </View>
                </View>

                <TouchableOpacity
                    style={[styles.getStartedBtn, { opacity: loading ? 0.7 : 1}]}
                    onPress={signUp}
                    disabled={loading}
                >
                    {loading ? (
                        <ActivityIndicator size="small" color={colors.accent} />
                    ) : (
                        <View style={{marginHorizontal: 'auto', flexDirection: 'row', alignItems: 'center'}}>
                            <Text style={[styles.actionText, {color:colors.accent}]}>Sign Up</Text>
                            <AntDesign name="right" size={24} color={colors.accent} />
                        </View>
                    )}
                </TouchableOpacity>


                <View style={styles.errorOuterContainer}>
                    <Animated.View style={[styles.errorContainer, { 
                        opacity: fadeAnim,
                        backgroundColor: error ? colors.accent + '15' : 'transparent',
                        transform: [{
                            translateY: fadeAnim.interpolate({
                                inputRange: [0, 1],
                                outputRange: [5, 0]
                            })
                        }]
                    }]}>
                        {error ? (
                            <View style={styles.errorWrapper}>
                                <Text style={[styles.errorText, { color: colors.accent }]} numberOfLines={3}>
                                    {error}
                                </Text>
                            </View>
                        ) : null}
                    </Animated.View>
                </View>

            </KeyboardAvoidingView>

            <TouchableOpacity
                style={styles.getStartedBtn}
                onPress={() => navigation.navigate('Login')}
            >
                <Text style={[{color:colors.text, fontSize: 16}]}>Already have an account? Sign in</Text>
                <AntDesign name="right" size={16} color={colors.text} />
            </TouchableOpacity>

        </View>
    )
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        marginHorizontal: 'auto',
        justifyContent: 'center'
    },
    intro: {
        gap: 10,
        alignItems: 'center',
        textAlign: 'center',
        width: '100%',
        padding: 5,
    },
    getStartedBtn: {
        flexDirection: 'row',
        justifyContent: 'center',
        alignItems: 'center',
        width: '100%',
        padding: 16,
        borderRadius: 12,
        minHeight: 56,
    },
    actionText: {
        fontSize: 24,
        fontWeight: 'bold',
    },
    subtitle: {
        fontSize: 20,
        fontWeight: 'bold'
    },
    mainContainer: {
        width: '100%',
        padding: 30,
        alignItems: 'center',
        gap: 25,
    },
    inputContainer: {
        width: '100%',
        gap: 20,
        marginTop: 20,
        minHeight: 180,
    },
    inputWrapper: {
        width: '100%',
        minHeight: 85,
    },
    inputLabel: {
        fontSize: 16,
        fontWeight: '600',
        marginBottom: 8,
        marginLeft: 4,
    },
    input: {
        width: 300,
        padding: 16,
        borderRadius: 12,
        fontSize: 16,
        minHeight: 56,
        shadowColor: '#000',
        shadowOffset: {
            width: 0,
            height: 2,
        },
        shadowOpacity: 0.05,
        shadowRadius: 4,
        elevation: 2,
    },
    errorOuterContainer: {
        width: '100%',
        minHeight: 70,
        maxHeight: 100,
        marginVertical: 8,
        justifyContent: 'center',
    },
    errorContainer: {
        width: '100%',
        borderRadius: 12,
        padding: 12,
        justifyContent: 'center',
        alignItems: 'center',
    },
    errorWrapper: {
        width: '100%',
        paddingHorizontal: 16,
    },
    errorText: {
        fontSize: 14,
        fontWeight: '500',
        textAlign: 'center',
        lineHeight: 20,
        flexWrap: 'wrap',
    },
})

export default Onboarding