diff --git a/react-native/App.tsx b/react-native/App.tsx
index 1331499..2344a93 100644
--- a/react-native/App.tsx
+++ b/react-native/App.tsx
@@ -8,6 +8,8 @@ import AppLoading from 'expo-app-loading';
import useFonts from './hooks/useFonts';
import { theme } from './core/theme';
+import { AuthProvider } from './contexts/Auth';
+
import LoginScreen from './screens/LoginScreen';
import JoinScreen from './screens/JoinScreen';
import HomeScreen from './screens/HomeScreen';
@@ -36,52 +38,54 @@ export default function App() {
}
return (
-
-
-
-
-
- ,
-
- headerTitle: (props) => ( // App Logo
-
- ),
- }}
- />
-
-
-
-
-
-
+
+
+
+
+
+
+ ,
+
+ headerTitle: (props) => ( // App Logo
+
+ ),
+ }}
+ />
+
+
+
+
+
+
+
);
}
diff --git a/react-native/components/LogoutButton.tsx b/react-native/components/LogoutButton.tsx
index ad5cc25..13213aa 100644
--- a/react-native/components/LogoutButton.tsx
+++ b/react-native/components/LogoutButton.tsx
@@ -2,9 +2,11 @@ import React, { useState } from 'react';
import { TouchableOpacity, Alert } from 'react-native';
import { useNavigation, StackActions } from '@react-navigation/native';
import { AntDesign } from '@expo/vector-icons';
+import { useAuth } from '../contexts/Auth';
const LogoutButton = () => {
const navigation = useNavigation();
+ const auth = useAuth();
const [showBox, setShowBox] = useState(true);
const LogoutConfirm = () => {
@@ -15,9 +17,7 @@ const LogoutButton = () => {
text: "Yes",
onPress: () => {
setShowBox(false);
- // await fetch('https://fetch.url/logout').then(
- // response => {
- // console.log(response);
+ auth.signOut();
navigation.dispatch(StackActions.popToTop())
},
}, {
diff --git a/react-native/contexts/Auth.tsx b/react-native/contexts/Auth.tsx
new file mode 100644
index 0000000..2a8e546
--- /dev/null
+++ b/react-native/contexts/Auth.tsx
@@ -0,0 +1,99 @@
+import React, { createContext, useState, useContext, useEffect } from 'react';
+import AsyncStorage from '@react-native-async-storage/async-storage';
+
+import { authService } from '../services/authService';
+import { AuthData, JoinData } from '../types';
+
+interface AuthContextData {
+ authData?: AuthData;
+ loading: boolean;
+ signUp(data: JoinData): Promise;
+ signIn(accessToken: string): Promise;
+ signOut(): void;
+};
+
+// Create the Auth Context with the data type specified
+// and a empty object
+const AuthContext = createContext({} as AuthContextData);
+
+const AuthProvider: React.FC = ({ children }) => {
+ const [authData, setAuthData] = useState();
+
+ // the AuthContext start with loading equals true
+ // and stay like this, until the data be load from Async Storage
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ // Every time the App is opened, this provider is rendered
+ // and call de loadStorage function.
+ loadStorageData();
+ }, []);
+
+ async function loadStorageData(): Promise {
+ try {
+ //Try get the data from Async Storage
+ const authDataSerialized = await AsyncStorage.getItem('@AuthData');
+ if (authDataSerialized) {
+ //If there are data, it's converted to an Object and the state is updated.
+ const _authData: AuthData = JSON.parse(authDataSerialized);
+ setAuthData(_authData);
+ }
+ } catch (error) {
+ } finally {
+ // loading finished
+ setLoading(false);
+ }
+ }
+
+ const signUp = async (data: JoinData) => {
+ const _authData = await authService.signUp(data);
+
+ setAuthData(_authData);
+
+ AsyncStorage.setItem('@AuthData', JSON.stringify(_authData));
+ };
+
+ const signIn = async (accessToken: string) => {
+ const _authData = await authService.signIn(accessToken);
+
+ // Set the data in the context, so the App can be notified
+ // and send the user to the AuthStack
+ setAuthData(_authData);
+
+ // Persist the data in the Async Storage
+ // to be recovered in the next user session.
+ AsyncStorage.setItem('@AuthData', JSON.stringify(_authData));
+ };
+
+ const signOut = async () => {
+ // Remove data from context, so the App can be notified
+ // and send the user to the AuthStack
+ setAuthData(undefined);
+
+ // Remove the data from Async Storage
+ // to NOT be recoverede in next session.
+ await AsyncStorage.removeItem('@AuthData');
+ };
+
+ return (
+ // This component will be used to encapsulate the whole App,
+ // so all components will have access to the Context
+
+ {children}
+
+ );
+};
+
+// A simple hooks to facilitate the access to the AuthContext
+// and permit components to subscribe to AuthContext updates
+function useAuth(): AuthContextData {
+ const context = useContext(AuthContext);
+
+ if (!context) {
+ throw new Error('useAuth must be used within an AuthProvider');
+ }
+
+ return context;
+}
+
+export {AuthContext, AuthProvider, useAuth};
\ No newline at end of file
diff --git a/react-native/core/utils.ts b/react-native/core/utils.ts
index 3f94b2e..ea4bcde 100644
--- a/react-native/core/utils.ts
+++ b/react-native/core/utils.ts
@@ -1,4 +1,4 @@
-export const emailValidator = (email: string) => {
+export const emailValidator = (email: string | undefined) => {
const re = /\S+@\S+\.\S+/;
if (!email || email.length <= 0) return 'Email cannot be empty.';
@@ -7,13 +7,13 @@ export const emailValidator = (email: string) => {
return '';
};
-export const passwordValidator = (password: string) => {
+export const passwordValidator = (password: string | undefined) => {
if (!password || password.length <= 0) return 'Password cannot be empty.';
return '';
};
-export const nameValidator = (name: string) => {
+export const nameValidator = (name: string | undefined) => {
if (!name || name.length <= 0) return 'Name cannot be empty.';
return '';
diff --git a/react-native/package.json b/react-native/package.json
index e858114..2d09f4c 100644
--- a/react-native/package.json
+++ b/react-native/package.json
@@ -11,16 +11,18 @@
},
"dependencies": {
"@expo-google-fonts/lora": "^0.2.2",
+ "@react-native-async-storage/async-storage": "~1.15.0",
"@react-navigation/native": "^6.0.8",
"@react-navigation/native-stack": "^6.5.0",
+ "axios": "^0.26.1",
"expo": "~44.0.0",
"expo-app-loading": "~1.3.0",
"expo-application": "^4.0.2",
"expo-auth-session": "~3.5.0",
"expo-camera": "^12.1.2",
"expo-font": "~10.0.4",
- "expo-splash-screen": "~0.14.1",
"expo-random": "~12.1.1",
+ "expo-splash-screen": "~0.14.1",
"expo-status-bar": "~1.2.0",
"expo-web-browser": "~10.1.0",
"metro-react-native-babel-preset": "^0.69.0",
@@ -39,17 +41,13 @@
"react-native-vector-icons": "^9.1.0",
"react-native-web": "0.17.1",
"react-navigation": "^4.4.4",
- "react-navigation-stack": "^2.10.4",
- "react-redux": "^7.2.6",
- "redux": "^4.1.2"
+ "react-navigation-stack": "^2.10.4"
},
"devDependencies": {
"@babel/core": "^7.12.9",
"@types/react": "~17.0.21",
"@types/react-native": "~0.64.12",
"@types/react-native-dotenv": "^0.2.0",
- "@types/react-redux": "^7.1.22",
- "@types/redux": "^3.6.0",
"typescript": "~4.3.5"
},
"private": true
diff --git a/react-native/screens/JoinScreen.tsx b/react-native/screens/JoinScreen.tsx
index 1c61817..411622e 100644
--- a/react-native/screens/JoinScreen.tsx
+++ b/react-native/screens/JoinScreen.tsx
@@ -1,54 +1,83 @@
-import React, { useState } from 'react';
+import React, { useState, useEffect } from 'react';
import { StyleSheet, View, KeyboardAvoidingView, Alert, Platform, ScrollView, Image, GestureResponderEvent } from 'react-native';
import { FormControl, Input, Button, VStack, Select, CheckIcon } from 'native-base';
import { nameValidator } from '../core/utils';
-import type { Navigation } from '../types';
+import type { Navigation, AuthData, JoinData } from '../types';
import { theme } from '../core/theme';
+import { useAuth } from '../contexts/Auth';
export default function JoinScreen({ navigation }: Navigation) {
- const [profileImg, setProfileImg] = useState(1);
- const [username, setUsername] = useState('');
- const [language, setLanguage] = useState('');
const [childrenNumber, setChildrenNumber] = useState('1');
- const [childrenName, setChildrenName] = useState([]);
const imgSource = [require(`../assets/images/profile-images/profile-1.png`), require(`../assets/images/profile-images/profile-2.png`), require(`../assets/images/profile-images/profile-3.png`),
require(`../assets/images/profile-images/profile-4.png`), require(`../assets/images/profile-images/profile-5.png`), require(`../assets/images/profile-images/profile-6.png`), require(`../assets/images/profile-images/profile-7.png`)];
- const childrenColor = [theme.colors.primary, theme.colors.secondary, theme.colors.skyblue, theme.colors.coral, theme.colors.gray, '#000']
-
+ const colors = [theme.colors.primary, theme.colors.secondary, theme.colors.skyblue, theme.colors.coral, theme.colors.gray, '#000']
+ const [joinForm, setJoinForm] = useState({
+ uid: undefined,
+ uprofileImg: 1,
+ username: '',
+ ulanguage: '',
+ uchildren: colors.map(color => ({'cname': '', 'color': color}))
+ })
+
+ const [user, setUser] = useState();
+ const auth = useAuth();
+
+ useEffect(() => {
+ if (auth?.authData?.uroleType==='USER') {
+ Alert.alert(
+ "Success",
+ "Congratulations, your account has been successfully created."
+ )
+ navigation.navigate('Home');
+ }
+ else if (auth?.authData?.uroleType==='GUEST') {
+ setUser(auth?.authData);
+ }
+ }, [auth]);
+
+ useEffect(() => {
+ if (user?.username) {
+ setJoinForm({ ...joinForm, ['username']: user.username });
+ }
+ }, [user]);
+
const errorAlert = (error: string) =>
Alert.alert(
- "Join Failed",
- error,
- [
- { text: "OK", onPress: () => console.log("OK Pressed") }
- ]
+ "Join Failed",
+ error,
+ [
+ { text: "OK", onPress: () => console.log("OK Pressed") }
+ ]
);
const handleProfileImg = (profileType: number) => (event: GestureResponderEvent) => {
- setProfileImg(profileType);
+ setJoinForm({ ...joinForm, ['uprofileImg']: profileType });
}
- const handleChildName = (childNum: number, text: string) => {
- let array = [...childrenName];
- array[childNum] = text;
- setChildrenName(array);
+ const handleChildren = (childNum: number, text: string) => {
+ let array = joinForm?.uchildren;
+ if (array) array[childNum].cname = text;
+ setJoinForm({ ...joinForm, ['uchildren']: array });
}
const onJoinPressed = () => {
- const usernameError = nameValidator(username);
- const childrenNameError = childrenName.length !== Number(childrenNumber);
+ if (user && joinForm) {
+ joinForm.uid = user?.uid;
- if (usernameError || childrenNameError || !language) {
- console.log(usernameError);
- errorAlert("Please fill in all the blanks!");
- return;
- }
+ let childrenArr = joinForm?.uchildren;
+ childrenArr = childrenArr?.slice(0, Number(childrenNumber));
+ joinForm.uchildren = childrenArr;
- Alert.alert(
- "Success",
- "Congratulations, your account has been successfully created."
- )
- navigation.navigate('Home');
+ const usernameError = nameValidator(joinForm.username);
+ const childrenNameError = joinForm.uchildren?.some(child => child.cname === '');
+
+ if (usernameError || childrenNameError || !joinForm.ulanguage) {
+ errorAlert("Please fill in all the blanks!");
+ return;
+ }
+
+ auth.signUp(joinForm);
+ }
};
return (
@@ -60,7 +89,7 @@ export default function JoinScreen({ navigation }: Navigation) {
{Array(7).fill(1).map((num, index) =>
)}
@@ -69,8 +98,8 @@ export default function JoinScreen({ navigation }: Navigation) {
Username
setUsername(text)}
+ value={joinForm.username}
+ onChangeText={(text) => setJoinForm({ ...joinForm, ['username']: text })}
autoFocus
autoCapitalize="none"
returnKeyType={"next"}
@@ -78,8 +107,8 @@ export default function JoinScreen({ navigation }: Navigation) {
Select Your Language
-