diff --git a/.github/workflows/Node-js-CI.yml b/.github/workflows/Node-js-CI.yml index 80ddfa2..9223800 100644 --- a/.github/workflows/Node-js-CI.yml +++ b/.github/workflows/Node-js-CI.yml @@ -35,4 +35,12 @@ jobs: node-version: ${{ env.NODE_VERSION }} - name: Running Test - run: npm run test \ No newline at end of file + run: npm run test + env: + FIREBASE_API_KEY: ${{ secrets.FIREBASE_API_KEY }} + FIREBASE_AUTH_DOMAIN: ${{ secrets.FIREBASE_AUTH_DOMAIN }} + FIREBASE_PROJECT_ID: ${{ secrets.FIREBASE_PROJECT_ID }} + FIREBASE_STORAGE_BUCKET: ${{ secrets.FIREBASE_STORAGE_BUCKET }} + FIREBASE_MESSAGING_SENDER_ID: ${{ secrets.FIREBASE_MESSAGING_SENDER_ID }} + FIREBASE_APP_ID: ${{ secrets.FIREBASE_APP_ID }} + FIREBASE_MEASUREMENT_ID: ${{ secrets.FIREBASE_MEASUREMENT_ID }} \ No newline at end of file diff --git a/app/(Auth)/LogInModal.tsx b/app/(Auth)/LogInModal.tsx index 9fb908a..296bb1f 100644 --- a/app/(Auth)/LogInModal.tsx +++ b/app/(Auth)/LogInModal.tsx @@ -1,174 +1,219 @@ -import { Text, View, StyleSheet, Pressable, Modal } from "react-native" +import { Text, View, StyleSheet, Pressable, Modal } from "react-native"; import React, { useState } from "react"; -import { TextInput as PaperTextInput, IconButton } from 'react-native-paper'; +import { + TextInput as PaperTextInput, + IconButton, + Props, +} from "react-native-paper"; -type LogInProps = { - isVisible: boolean, - onClose: () => void - toggleSignUp: () => void -} +import { AuthError, User } from "firebase/auth"; -const LogInModal = (props: LogInProps) => { - const [emailText, setEmailText] = useState(''); - const [passwordText, setPasswordText] = useState(''); +import { emailVerification, logIn, logOut } from "./firebaseAuth"; - const [passwordVisible, setPasswordVisible] = useState(true); +type LogInProps = { + isVisible: boolean; + onClose: () => void; + toggleSignUp: () => void; +}; - const [emailBorderColor, setEmailBorderColor] = useState('gray'); - const [PasswordBorderColor, setPasswordBorderColor] = useState('gray'); +const LogInModal = (props: LogInProps) => { + const [emailText, setEmailText] = useState(""); + const [passwordText, setPasswordText] = useState(""); - return ( - - - e.stopPropagation()} > - - - - Email - setEmailBorderColor('black')} // border color on focus - onBlur={() => setEmailBorderColor('gray')} // border color on focus + const [passwordVisible, setPasswordVisible] = useState(true); - placeholder='Value' - placeholderTextColor="#a9a9a9" - value={emailText} - onChangeText={setEmailText} + const [emailBorderColor, setEmailBorderColor] = useState("gray"); + const [PasswordBorderColor, setPasswordBorderColor] = useState("gray"); - theme={{ colors: { primary: "transparent" } }} // this removes the underline - underlineColor="transparent" // this removes the any extra underline - /* obtain data here */ - /> - Password - setPasswordBorderColor('black')} // border color on focus - onBlur={() => setPasswordBorderColor('gray')} // border color on focus + const handleLogIn = async () => { + try { + const user = await logIn(emailText, passwordText); + await checkIfEmailVerified(user, props.onClose); + } catch (error: unknown) { + if ( + (error as AuthError).code === "auth/user-not-found" || + (error as AuthError).code === "auth/wrong-password" + ) { + alert("Email already in use"); + } else if ((error as AuthError).code === "auth/invalid-email") { + alert("No Email Found, please Sign Up!"); + } else if ((error as AuthError).code === "auth/too-many-request") { + alert("Too many unsuccessful login attempts. Please try again later."); + //invalid-credentials (just say wrong email or password) + } else { + console.log((error as AuthError).code); + alert("Sign In error: " + (error as Error).message); + } + } + }; - placeholder='Value' - placeholderTextColor="#a9a9a9" - secureTextEntry={passwordVisible} - value={passwordText} - onChangeText={setPasswordText} - right={ - setPasswordVisible(!passwordVisible)} - style={styles.icon} // this adjusts eye icon position - /> - } - theme={{ colors: { primary: "transparent" } }} // this removes the underline - underlineColor="transparent" // this removes any extra underline - /* obtain data here */ - /> - { - /* handle action here */ - }} - > - Sign In - - - { - /* handle action here */ - }} - > - Forgot password? - - - { - props.toggleSignUp() - console.log("Signup Pressed") - props.onClose() - }} - > - Need an account? Sign-up here. - - - - - ); + return ( + + + e.stopPropagation()}> + + + + Email + setEmailBorderColor("black")} // border color on focus + onBlur={() => setEmailBorderColor("gray")} // border color on focus + placeholder="Value" + placeholderTextColor="#a9a9a9" + value={emailText} + onChangeText={setEmailText} + theme={{ colors: { primary: "transparent" } }} // this removes the underline + underlineColor="transparent" // this removes the any extra underline + /* obtain data here */ + /> + Password + setPasswordBorderColor("black")} // border color on focus + onBlur={() => setPasswordBorderColor("gray")} // border color on focus + placeholder="Value" + placeholderTextColor="#a9a9a9" + secureTextEntry={passwordVisible} + value={passwordText} + onChangeText={setPasswordText} + right={ + setPasswordVisible(!passwordVisible)} + style={styles.icon} // this adjusts eye icon position + /> + } + theme={{ colors: { primary: "transparent" } }} // this removes the underline + underlineColor="transparent" // this removes any extra underline + /* obtain data here */ + /> + { + handleLogIn(); + }} + > + Sign In + + + { + /* handle action here */ + }} + > + Forgot password? + + + { + props.toggleSignUp(); + console.log("Signup Pressed"); + props.onClose(); + }} + > + Need an account? Sign-up here. + + + + + ); }; const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: 'rgba(0,0,0,0.5)', // making the background page transparent - justifyContent: 'center', - alignItems: 'center', - }, - box: { - backgroundColor: 'white', - borderRadius: 10, - padding: 15, - width: 400, - alignItems: "center", - }, - underline: { - textDecorationLine: 'underline', - color: 'black', // set this color of the underline 'black' - }, - button: { - backgroundColor: '#71E0BC', - borderRadius: 5, - padding: 10, - alignItems: 'center', - justifyContent: 'center', - marginVertical: 10, - width: 350, - }, - buttonText: { - color: 'black', // Set the text color to black - }, - textSpace: { - marginTop: 4, // Space before text - marginBottom: 8, // Space after text - }, - label: { - alignSelf: 'flex-start', // align labels to the start - marginLeft: 10, // Add some margin to the left to match the input margin - marginBottom: 1, // Space between label and input - }, - icon: { - marginTop: 20, - }, - paperInput: { - borderWidth: 2, - borderColor: '#777', - backgroundColor: 'white', - paddingHorizontal: 9, - paddingVertical: 8, - margin: 10, - borderRadius: 6, - width: 350, - height: 23, - // this adjust font size and line height to the standard - fontSize: 14, - }, - header: { - width: '100%', - height: 15, - alignItems: "flex-end", - justifyContent: 'center', - marginLeft: 30, - }, + container: { + flex: 1, + backgroundColor: "rgba(0,0,0,0.5)", // making the background page transparent + justifyContent: "center", + alignItems: "center", + }, + box: { + backgroundColor: "white", + borderRadius: 10, + padding: 15, + width: 400, + alignItems: "center", + }, + underline: { + textDecorationLine: "underline", + color: "black", // set this color of the underline 'black' + }, + button: { + backgroundColor: "#71E0BC", + borderRadius: 5, + padding: 10, + alignItems: "center", + justifyContent: "center", + marginVertical: 10, + width: 350, + }, + buttonText: { + color: "black", // Set the text color to black + }, + textSpace: { + marginTop: 4, // Space before text + marginBottom: 8, // Space after text + }, + label: { + alignSelf: "flex-start", // align labels to the start + marginLeft: 10, // Add some margin to the left to match the input margin + marginBottom: 1, // Space between label and input + }, + icon: { + marginTop: 20, + }, + paperInput: { + borderWidth: 2, + borderColor: "#777", + backgroundColor: "white", + paddingHorizontal: 9, + paddingVertical: 8, + margin: 10, + borderRadius: 6, + width: 350, + height: 23, + // this adjust font size and line height to the standard + fontSize: 14, + }, + header: { + width: "100%", + height: 15, + alignItems: "flex-end", + justifyContent: "center", + marginLeft: 30, + }, }); export default LogInModal; +const checkIfEmailVerified = async (user: User, onClose: () => void) => { + if (user) { + if (!user.emailVerified) { + // means that user is still not verified yet, need them to be verified + await emailVerification(); + await logOut(); + + //give a warning like an alert to ask to verify + alert("Please verify your email."); + } + //If verified, navigate to other place + console.log("Woo Verified, I'll navigate you later"); + + onClose(); + } else { + throw new Error("Failed to check User"); + } +}; + /* self-note: when clocking the navigation 'link', it erases the most components from the main background. figure out how to prevent that. setvisible prolly would work for the 'link' (?) -*/ \ No newline at end of file +*/ diff --git a/app/(Auth)/SignUpModal.tsx b/app/(Auth)/SignUpModal.tsx index 2a0b852..2144690 100644 --- a/app/(Auth)/SignUpModal.tsx +++ b/app/(Auth)/SignUpModal.tsx @@ -2,6 +2,9 @@ import { Text, View, TextInput, StyleSheet, Pressable, Modal } from "react-nativ import React, { useState } from "react"; import { TextInput as PaperTextInput, IconButton } from 'react-native-paper'; +import { AuthError } from 'firebase/auth'; +import { signUp } from "./firebaseAuth"; + type SignUpProps = { isVisible: boolean, onClose: () => void @@ -20,6 +23,26 @@ const SignUpModal = (props: SignUpProps) => { const [passwordbBorderColor, setPasswordBorderColor] = useState('gray'); const [ReEnterPasswordbBorderColor, setReEnterPasswordBorderColor] = useState('gray'); + const handleSignUp = async () => { + try { + const user = await signUp(emailText, password, reEnterPassword) + if(user) { + //use saveUserData from firebase if needed + const id = user.uid; + } + //should navigate to a loading screen, or ask to verify first before doing anything + } catch (error: unknown) { + if ((error as AuthError).code === 'auth/email-already-in-use') { + alert('Email already in use'); + } else if ((error as AuthError).code === 'auth/weak-password') { + alert('Weak Password. Please choose a stronger password') + //invalid email also + } else { + alert("Signup error: " + (error as Error).message) + } + }; + } + return ( { { - /* handle action here */ + handleSignUp() }} > Sign In diff --git a/app/(Auth)/firebaseAuth.tsx b/app/(Auth)/firebaseAuth.tsx index 13c0738..ac87ea1 100644 --- a/app/(Auth)/firebaseAuth.tsx +++ b/app/(Auth)/firebaseAuth.tsx @@ -2,12 +2,11 @@ import { createUserWithEmailAndPassword, signInWithEmailAndPassword, sendEmailVerification, - signOut, + signOut } from "firebase/auth"; import auth from "../../scripts/firebaseConfig"; - export const signUp = async ( email: string, password: string, diff --git a/jest.setup.js b/jest.setup.js new file mode 100644 index 0000000..355c7df --- /dev/null +++ b/jest.setup.js @@ -0,0 +1 @@ +require('dotenv').config({ path: './.env' }); diff --git a/package.json b/package.json index 6d24fa4..996286f 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,10 @@ "scripts" ], "transformIgnorePatterns": [ - "node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.|@expo-google-fonts/.|react-navigation|@react-navigation/.*|@sentry/react-native|native-base|react-native-svg|react-redux)" + "node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.|@expo-google-fonts/.|react-navigation|@react-navigation/.*|@sentry/react-native|native-base|react-native-svg|react-redux|@firebase|firebase)" + ], + "setupFiles": [ + "/jest.setup.js" ] }, "dependencies": { diff --git a/scripts/firebaseConfig.js b/scripts/firebaseConfig.js index 98da890..bc11f90 100644 --- a/scripts/firebaseConfig.js +++ b/scripts/firebaseConfig.js @@ -3,13 +3,13 @@ import { initializeApp } from "firebase/app"; import { getAuth } from "firebase/auth"; const firebaseConfig = { - apiKey: process.env.EXPO_PUBLIC_FIREBASE_API_KEY, - authDomain: process.env.EXPO_PUBLIC_FIREBASE_AUTH_DOMAIN, - projectId: process.env.EXPO_PUBLIC_FIREBASE_PROJECT_ID, - storageBucket: process.env.EXPO_PUBLIC_FIREBASE_STORAGE_BUCKET, - messagingSenderId: process.env.EXPO_PUBLIC_FIREBASE_MESSAGING_SENDER_ID, - appId: process.env.EXPO_PUBLIC_FIREBASE_APP_ID, - measurementId: process.env.EXPO_PUBLIC_FIREBASE_MEASUREMENT_ID, + apiKey: process.env.FIREBASE_API_KEY, + authDomain: process.env.FIREBASE_AUTH_DOMAIN, + projectId: process.env.FIREBASE_PROJECT_ID, + storageBucket: process.env.FIREBASE_STORAGE_BUCKET, + messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID, + appId: process.env.FIREBASE_APP_ID, + measurementId: process.env.FIREBASE_MEASUREMENT_ID, }; console.log(firebaseConfig) diff --git a/yarn.lock b/yarn.lock index 1af0cb6..382cac8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3010,6 +3010,11 @@ resolved "https://registry.npmjs.org/@react-native-community/masked-view/-/masked-view-0.1.11.tgz" integrity sha512-rQfMIGSR/1r/SyN87+VD8xHHzDYeHaJq6elOSCAD+0iLagXkSI2pfA0LmSXP21uw5i3em7GkkRjfJ8wpqWXZNw== +"@react-native-picker/picker@^2.8.1": + version "2.8.1" + resolved "https://registry.npmjs.org/@react-native-picker/picker/-/picker-2.8.1.tgz" + integrity sha512-iFhsKQzRh/z3GlmvJWSjJJ4333FdLE/PhXxlGlYllE7sFf+UTzziVY+ajatuJ+R5zDw2AxfJV4v/3tAzUJb0/A== + "@react-native/assets-registry@0.74.85": version "0.74.85" resolved "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.74.85.tgz" @@ -11563,6 +11568,7 @@ natural-compare@^1.4.0: dependencies: "@expo/vector-icons" "^14.0.2" "@react-native-community/masked-view" "^0.1.11" + "@react-native-picker/picker" "^2.8.1" "@react-navigation/native" "^6.1.18" "@react-navigation/stack" "^6.4.1" "@reduxjs/toolkit" "^2.2.6"