diff --git a/4_Shop_App/App.js b/4_Shop_App/App.js
index b47ca82..2bc2a60 100644
--- a/4_Shop_App/App.js
+++ b/4_Shop_App/App.js
@@ -1,15 +1,14 @@
-import React, { useState } from 'react';
-import { createStore, combineReducers, applyMiddleware } from 'redux';
-import { Provider } from 'react-redux';
import { AppLoading } from 'expo';
import * as Font from 'expo-font';
+import React, { useState } from 'react';
+import { Provider } from 'react-redux';
+import { applyMiddleware, combineReducers, createStore } from 'redux';
import ReduxThunk from 'redux-thunk';
-
-import productsReducer from './store/reducers/products';
+import NavigationContainer from './navigation/NavigationContainer';
+import authReducer from './store/reducers/auth';
import cartReducer from './store/reducers/cart';
import ordersReducer from './store/reducers/orders';
-import authReducer from './store/reducers/auth';
-import ShopNavigator from './navigation/ShopNavigator';
+import productsReducer from './store/reducers/products';
const rootReducer = combineReducers({
products: productsReducer,
@@ -43,7 +42,7 @@ export default function App() {
return (
-
+
);
}
diff --git a/4_Shop_App/README.md b/4_Shop_App/README.md
index d641e2d..48ea7cb 100644
--- a/4_Shop_App/README.md
+++ b/4_Shop_App/README.md
@@ -22,6 +22,7 @@
- [When The App Launches](#whenapplaunches)
- [Authentication Screen](#authscreen)
- [Auth Config](#actionauth)
+- [Local Storage - AsyncStorage](#localstorage)
Shop App
@@ -905,3 +906,69 @@
export default authReducer;
```
+
+Local Storage - AsyncStorage
+
+[Go Back to Summary](#summary)
+
+- In `store/actions/auth.js`
+
+ - Import **AsyncStorage** from `react-native`
+ - `removeItem('key')` to remove key/value from local storage
+ - `setItem('key', 'value')` to add key/value pair in the local storage
+ - It has to be a string, so we have to JSON.stringify()
+ - `getItem('key')` to get an item from local storage
+
+ ```JavaScript
+ import { AsyncStorage } from 'react-native';
+ import { FIREBASE_KEY } from 'react-native-dotenv';
+ export const AUTHENTICATE = 'AUTHENTICATE';
+ export const LOGOUT = 'LOGOUT';
+ let timer;
+
+ export const authenticate = (userId, token, expiryTime) => {
+ return (dispatch) => {
+ dispatch(setLogoutTimer(expiryTime));
+ dispatch({ type: AUTHENTICATE, userId, token });
+ };
+ };
+
+ export const signup = (email, password) => {
+ return async (dispatch) => {...};
+ };
+
+ export const login = (email, password) => {
+ return async (dispatch) => {...};
+ };
+
+ export const logout = () => {
+ clearLogoutTimer();
+ AsyncStorage.removeItem('userData');
+ return { type: LOGOUT };
+ };
+
+ const clearLogoutTimer = () => {
+ if (timer) {
+ clearTimeout(timer);
+ }
+ };
+
+ const setLogoutTimer = (expirationTime) => {
+ return (dispatch) => {
+ timer = setTimeout(() => {
+ dispatch(logout());
+ }, expirationTime);
+ };
+ };
+
+ const saveDataToStorage = (token, userId, expirationDate) => {
+ AsyncStorage.setItem(
+ 'userData',
+ JSON.stringify({
+ token,
+ userId,
+ expiryDate: expirationDate.toISOString(),
+ }),
+ );
+ };
+ ```
diff --git a/4_Shop_App/navigation/NavigationContainer.js b/4_Shop_App/navigation/NavigationContainer.js
new file mode 100644
index 0000000..283fa77
--- /dev/null
+++ b/4_Shop_App/navigation/NavigationContainer.js
@@ -0,0 +1,19 @@
+import React, { useEffect, useRef } from 'react';
+import { NavigationActions } from 'react-navigation';
+import { useSelector } from 'react-redux';
+import ShopNavigator from './ShopNavigator';
+
+function NavigationContainer(props) {
+ const navRef = useRef();
+ const isAuth = useSelector((state) => !!state.auth.token);
+
+ useEffect(() => {
+ if (!isAuth) {
+ navRef.current.dispatch(NavigationActions.navigate({ routeName: 'Auth' }));
+ }
+ }, [isAuth]);
+
+ return ;
+}
+
+export default NavigationContainer;
diff --git a/4_Shop_App/navigation/ShopNavigator.js b/4_Shop_App/navigation/ShopNavigator.js
index 8429059..573f999 100644
--- a/4_Shop_App/navigation/ShopNavigator.js
+++ b/4_Shop_App/navigation/ShopNavigator.js
@@ -1,19 +1,20 @@
+import { Ionicons } from '@expo/vector-icons';
import React from 'react';
-import { createStackNavigator } from 'react-navigation-stack';
+import { Button, Platform, SafeAreaView, StyleSheet, View } from 'react-native';
import { createAppContainer, createSwitchNavigator } from 'react-navigation';
-import { createDrawerNavigator } from 'react-navigation-drawer';
-import { Platform } from 'react-native';
-import { Ionicons } from '@expo/vector-icons';
-
+import { createDrawerNavigator, DrawerNavigatorItems } from 'react-navigation-drawer';
+import { createStackNavigator } from 'react-navigation-stack';
+import { useDispatch } from 'react-redux';
import Colors from '../css/Colors';
-
-import ProductsOverviewScreen from '../screens/shop/ProductsOverviewScreen';
-import ProductDetailsScreen from '../screens/shop/ProductDetailsScreen';
import CartScreen from '../screens/shop/CartScreen';
import OrdersScreen from '../screens/shop/OrdersScreen';
-import UserProductsScreen from '../screens/user/UserProductsScreen';
-import EditProductScreen from '../screens/user/EditProductScreen';
+import ProductDetailsScreen from '../screens/shop/ProductDetailsScreen';
+import ProductsOverviewScreen from '../screens/shop/ProductsOverviewScreen';
+import StartupScreen from '../screens/StartupScreen';
import AuthScreen from '../screens/user/AuthScreen';
+import EditProductScreen from '../screens/user/EditProductScreen';
+import UserProductsScreen from '../screens/user/UserProductsScreen';
+import * as authActions from '../store/actions/auth';
const defaultNavOptions = {
headerStyle: {
@@ -104,12 +105,38 @@ const ShopNavigator = createDrawerNavigator(
contentOptions: {
activeTintColor: Colors.primary,
},
+ contentComponent: (props) => {
+ const dispatch = useDispatch();
+ return (
+
+
+
+
+
+ );
+ },
},
);
const MainNavigator = createSwitchNavigator({
+ Startup: StartupScreen,
Auth: AuthNavigator,
Shop: ShopNavigator,
});
+const styles = StyleSheet.create({
+ drawerButton: {
+ flex: 1,
+ paddingTop: 20,
+ },
+});
+
export default createAppContainer(MainNavigator);
diff --git a/4_Shop_App/screens/StartupScreen.js b/4_Shop_App/screens/StartupScreen.js
new file mode 100644
index 0000000..a5f1b30
--- /dev/null
+++ b/4_Shop_App/screens/StartupScreen.js
@@ -0,0 +1,52 @@
+import React, { useEffect } from 'react';
+import { ActivityIndicator, AsyncStorage, StyleSheet, View } from 'react-native';
+import { useDispatch } from 'react-redux';
+import Colors from '../css/Colors';
+import * as authActions from '../store/actions/auth';
+
+function StartupScreen({ navigation }) {
+ const dispatch = useDispatch();
+
+ useEffect(() => {
+ const tryLogin = async () => {
+ const userData = await AsyncStorage.getItem('userData');
+
+ if (!userData) {
+ navigation.navigate('Auth');
+ return;
+ }
+
+ const transformedData = JSON.parse(userData);
+ const { token, userId, expiryDate } = transformedData;
+ const expirationDate = new Date(expiryDate);
+
+ if (expirationDate <= new Date() || !token || !userId) {
+ navigation.navigate('Auth');
+ return;
+ }
+
+ const expirationTime = expirationDate.getTime() - new Date().getTime();
+
+ navigation.navigate('Shop');
+ dispatch(authActions.authenticate(userId, token, expirationTime));
+ };
+
+ tryLogin();
+ }, [dispatch]);
+
+ return (
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ screen: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+});
+
+export default StartupScreen;
diff --git a/4_Shop_App/store/actions/auth.js b/4_Shop_App/store/actions/auth.js
index 7bcf2f8..24fe07d 100644
--- a/4_Shop_App/store/actions/auth.js
+++ b/4_Shop_App/store/actions/auth.js
@@ -1,6 +1,15 @@
+import { AsyncStorage } from 'react-native';
import { FIREBASE_KEY } from 'react-native-dotenv';
-export const SIGNUP = 'SIGNUP';
-export const LOGIN = 'LOGIN';
+export const AUTHENTICATE = 'AUTHENTICATE';
+export const LOGOUT = 'LOGOUT';
+let timer;
+
+export const authenticate = (userId, token, expiryTime) => {
+ return (dispatch) => {
+ dispatch(setLogoutTimer(expiryTime));
+ dispatch({ type: AUTHENTICATE, userId, token });
+ };
+};
export const signup = (email, password) => {
return async (dispatch) => {
@@ -40,12 +49,9 @@ export const signup = (email, password) => {
}
const resData = await response.json();
-
- dispatch({
- type: SIGNUP,
- token: resData.idToken,
- userId: resData.localId,
- });
+ dispatch(authenticate(resData.localId, resData.idToken, parseInt(resData.expiresIn) * 1000));
+ const expirationDate = new Date(new Date().getTime() + parseInt(resData.expiresIn) * 1000);
+ saveDataToStorage(resData.idToken, resData.localId, expirationDate);
} catch (error) {
throw error;
}
@@ -93,13 +99,42 @@ export const login = (email, password) => {
}
const resData = await response.json();
- dispatch({
- type: LOGIN,
- token: resData.idToken,
- userId: resData.localId,
- });
+ dispatch(authenticate(resData.localId, resData.idToken, parseInt(resData.expiresIn) * 1000));
+ const expirationDate = new Date(new Date().getTime() + parseInt(resData.expiresIn) * 1000);
+ saveDataToStorage(resData.idToken, resData.localId, expirationDate);
} catch (error) {
throw error;
}
};
};
+
+export const logout = () => {
+ clearLogoutTimer();
+ AsyncStorage.removeItem('userData');
+ return { type: LOGOUT };
+};
+
+const clearLogoutTimer = () => {
+ if (timer) {
+ clearTimeout(timer);
+ }
+};
+
+const setLogoutTimer = (expirationTime) => {
+ return (dispatch) => {
+ timer = setTimeout(() => {
+ dispatch(logout());
+ }, expirationTime);
+ };
+};
+
+const saveDataToStorage = (token, userId, expirationDate) => {
+ AsyncStorage.setItem(
+ 'userData',
+ JSON.stringify({
+ token,
+ userId,
+ expiryDate: expirationDate.toISOString(),
+ }),
+ );
+};
diff --git a/4_Shop_App/store/reducers/auth.js b/4_Shop_App/store/reducers/auth.js
index 4388025..a753548 100644
--- a/4_Shop_App/store/reducers/auth.js
+++ b/4_Shop_App/store/reducers/auth.js
@@ -1,4 +1,4 @@
-import { LOGIN, SIGNUP } from '../actions/auth';
+import { AUTHENTICATE, LOGOUT } from '../actions/auth';
const initialState = {
token: null,
@@ -7,16 +7,13 @@ const initialState = {
const authReducer = (state = initialState, action) => {
switch (action.type) {
- case LOGIN:
- return {
- token: action.token,
- userId: action.userId,
- };
- case SIGNUP:
+ case AUTHENTICATE:
return {
token: action.token,
userId: action.userId,
};
+ case LOGOUT:
+ return initialState;
default:
return state;
}