From 7b7baab86683622724112c189e8c01a9a261f684 Mon Sep 17 00:00:00 2001 From: Roger Takeshita Date: Fri, 12 Jun 2020 15:08:33 -0400 Subject: [PATCH] Add Authentication, local storage, auto login and auto logout - Auto logout is not very fancy, and should be refactored in the future https://github.com/facebook/react-native/issues/12981 * 4_Shop_App/App.js - Imported a new component a Wrapper just to have access to our store * 4_Shop_App/navigation/ShopNavigator.js - Create this new component to check first (after the splash screen) if there is a logged user. if not redirect to Auth screen - We need to create this component to have access to our store - With the help of useRef hook, we can have access to the properties of the component * 4_Shop_App/navigation/ShopNavigator.js - Added a react component to the side drawer, so we can click to logout - Added our StartupScreen as the first screen of the stack * 4_Shop_App/store/actions/auth.js - Changed SIGNUP and LOGIN to AUTHENTICATE - Added a helper function authenticate() to dispatch the (userId, token, expiryTime) - Added a logout(), inside this function we call AsyncStorage.removeItem('') to remove the item from our local storage - Added a auto logout, it checks for the expiration date, and creates a setTimeout with the remaining time - Added a saveDataToStorage() to save the user info in the local storage * 4_Shop_App/store/reducers/auth.js - Removed the cases LOGIN and SIGNUP - Added LOGOUT -> basically sets to the initial state * 4_Shop_App/screens/StartupScreen.js - This is the first page of the app - It's very fast, and we wont see it (it's just an ActivityIndicator) - Basically it checks if there is a user in the local storage - if yes, checks the expiration date - if everything is ok, redirects to "Shop" screen - if no, or expiration date is expired - redirects to "Auth" screen --- 4_Shop_App/App.js | 15 ++--- 4_Shop_App/README.md | 67 ++++++++++++++++++++ 4_Shop_App/navigation/NavigationContainer.js | 19 ++++++ 4_Shop_App/navigation/ShopNavigator.js | 47 +++++++++++--- 4_Shop_App/screens/StartupScreen.js | 52 +++++++++++++++ 4_Shop_App/store/actions/auth.js | 61 ++++++++++++++---- 4_Shop_App/store/reducers/auth.js | 11 ++-- 7 files changed, 234 insertions(+), 38 deletions(-) create mode 100644 4_Shop_App/navigation/NavigationContainer.js create mode 100644 4_Shop_App/screens/StartupScreen.js 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 ( + + + +