| @@ -1,14 +1,34 @@ | |||
| import "react-native-gesture-handler"; | |||
| import React, { useContext } from "react"; | |||
| import React, { useEffect } from "react"; | |||
| import { NavigationContainer } from "@react-navigation/native"; | |||
| import { Provider } from "react-redux"; | |||
| import { Provider, useDispatch } from "react-redux"; | |||
| import store from "./store"; | |||
| import { useFonts } from "expo-font"; | |||
| import { AuthProvider } from "./context/AuthContext"; | |||
| import RootNavigation from "./navigation/RootNavigation"; | |||
| import { getData } from "./service/asyncStorage"; | |||
| import { JWT_REFRESH_TOKEN, JWT_TOKEN } from "./constants/localStorage"; | |||
| import { fetchUserSuccess } from "./store/actions/login/loginActions"; | |||
| import { addHeaderToken } from "./request"; | |||
| function App() { | |||
| const dispatch = useDispatch(); | |||
| const getToken = async () => { | |||
| const token = await getData(JWT_TOKEN); | |||
| const refreshToken = await getData(JWT_REFRESH_TOKEN); | |||
| if (token) { | |||
| addHeaderToken(token); | |||
| } | |||
| if (token !== undefined && refreshToken !== undefined) { | |||
| dispatch(fetchUserSuccess({ jwt: token, refreshToken })); | |||
| } | |||
| }; | |||
| useEffect(() => { | |||
| getToken(); | |||
| }, []); | |||
| return ( | |||
| <NavigationContainer> | |||
| <RootNavigation /> | |||
| @@ -27,11 +47,9 @@ const AppWrapper = () => { | |||
| } | |||
| return ( | |||
| <AuthProvider> | |||
| <Provider store={store}> | |||
| <App /> | |||
| </Provider> | |||
| </AuthProvider> | |||
| <Provider store={store}> | |||
| <App /> | |||
| </Provider> | |||
| ); | |||
| }; | |||
| @@ -1,4 +1,4 @@ | |||
| import React, { useContext } from "react"; | |||
| import React from "react"; | |||
| import { | |||
| View, | |||
| Text, | |||
| @@ -12,17 +12,25 @@ import { | |||
| DrawerContentScrollView, | |||
| DrawerItemList, | |||
| } from "@react-navigation/drawer"; | |||
| import { AuthContext } from "../../context/AuthContext"; | |||
| import Loader from "../Loader"; | |||
| import { MaterialIcons } from "@expo/vector-icons"; | |||
| import { Ionicons } from "@expo/vector-icons"; | |||
| import { useDispatch } from "react-redux"; | |||
| import { logoutUser } from "../../store/actions/login/loginActions"; | |||
| import useAuthHook from "../../utils/hooks/useAuthHook"; | |||
| const CustomDrawer = (props) => { | |||
| const { logout, isLoading } = useContext(AuthContext); | |||
| const {logoutAuthProvider} = useAuthHook() | |||
| const dispatch = useDispatch(); | |||
| const handleLogout = async () => { | |||
| logoutAuthProvider() | |||
| dispatch(logoutUser()); | |||
| } | |||
| return ( | |||
| <View style={styles.container}> | |||
| <Loader visible={isLoading} /> | |||
| <DrawerContentScrollView {...props} contentContainerStyle={styles.drawer}> | |||
| <ImageBackground | |||
| source={require("../../assets/images/menu-bg.jpeg")} | |||
| @@ -48,7 +56,7 @@ const CustomDrawer = (props) => { | |||
| <Text style={styles.footerButtonText}>Contact Us</Text> | |||
| </View> | |||
| </TouchableOpacity> | |||
| <TouchableOpacity onPress={logout} style={styles.footerButton}> | |||
| <TouchableOpacity onPress={handleLogout} style={styles.footerButton}> | |||
| <View style={styles.buttonContent}> | |||
| <MaterialIcons name="logout" size={22} color="#333" /> | |||
| <Text style={styles.footerButtonText}>Sign Out</Text> | |||
| @@ -0,0 +1,3 @@ | |||
| export const JWT_TOKEN = 'JwtToken'; | |||
| export const JWT_REFRESH_TOKEN = 'JwtRefreshToken'; | |||
| export const ACCESS_TOKEN = "AccessToken"; | |||
| @@ -1,126 +0,0 @@ | |||
| import AsyncStorage from "@react-native-async-storage/async-storage"; | |||
| import * as Google from "expo-auth-session/providers/google"; | |||
| import React, { createContext, useEffect, useState } from "react"; | |||
| import { googleApi, loginApi, registerApi } from "../service/user"; | |||
| import { revokeAsync } from "expo-auth-session"; | |||
| export const AuthContext = createContext(); | |||
| export const AuthProvider = ({ children }) => { | |||
| const [userInfo, setUserInfo] = useState({}); | |||
| const [isLoading, setIsLoading] = useState(false); | |||
| // Google Auth | |||
| const [request, response, promptAsync] = Google.useAuthRequest({ | |||
| androidClientId: | |||
| "1032296921439-dpgvgkss3ggds0egvo6puf0r7un9em6c.apps.googleusercontent.com", | |||
| iosClientId: | |||
| "1032296921439-j7jfahhm7q3l07aj04qvdblmcns77oul.apps.googleusercontent.com", | |||
| expoClientId: | |||
| "1032296921439-vfs9k28sn7kei4nft998ck3067m8ksiq.apps.googleusercontent.com", | |||
| }); | |||
| useEffect(() => { | |||
| if (response?.type === "success") { | |||
| googleApi(response.authentication.accessToken) | |||
| .then((res) => { | |||
| if (res.user) { | |||
| setUserInfo(res); | |||
| AsyncStorage.setItem("userInfo", JSON.stringify(res)); | |||
| setIsLoading(false); | |||
| } else { | |||
| callback(res); | |||
| setIsLoading(false); | |||
| } | |||
| }) | |||
| .catch((e) => { | |||
| console.log("error", e); | |||
| setIsLoading(false); | |||
| }); | |||
| } | |||
| }, [response]); | |||
| const login = (email, password, callback) => { | |||
| setIsLoading(true); | |||
| loginApi({ identifier: email, password }) | |||
| .then((res) => { | |||
| if (res.user) { | |||
| setUserInfo(res); | |||
| AsyncStorage.setItem("userInfo", JSON.stringify(res)); | |||
| AsyncStorage.setItem("jwt-token", res.jwt); | |||
| setIsLoading(false); | |||
| } else { | |||
| callback(res); | |||
| setIsLoading(false); | |||
| } | |||
| }) | |||
| .catch((e) => { | |||
| console.log("error", e); | |||
| setIsLoading(false); | |||
| }); | |||
| }; | |||
| const googleAuth = () => { | |||
| promptAsync({ useProxy: true, showInRecents: true }); | |||
| }; | |||
| const register = (username, email, password, callback) => { | |||
| setIsLoading(true); | |||
| registerApi({ username, email, password }) | |||
| .then((res) => { | |||
| if (res.user) { | |||
| setUserInfo(res); | |||
| AsyncStorage.setItem("userInfo", JSON.stringify(res)); | |||
| AsyncStorage.setItem("jwt-token", res.jwt); | |||
| setIsLoading(false); | |||
| } else { | |||
| callback(res); | |||
| setIsLoading(false); | |||
| } | |||
| }) | |||
| .catch((e) => { | |||
| console.log("error", e); | |||
| setIsLoading(false); | |||
| }); | |||
| }; | |||
| const logout = async () => { | |||
| setIsLoading(true); | |||
| AsyncStorage.removeItem("userInfo"); | |||
| AsyncStorage.removeItem("jwt-token"); | |||
| setUserInfo({}); | |||
| if (userInfo.user.provider === "google") { | |||
| await revokeAsync( | |||
| { token: response.authentication.accessToken }, | |||
| { revocationEndpoint: "https://oauth2.googleapis.com/revoke" } | |||
| ); | |||
| } | |||
| setIsLoading(false); | |||
| }; | |||
| const isLoggedIn = async () => { | |||
| try { | |||
| let userInfo = await AsyncStorage.getItem("userInfo"); | |||
| userInfo = JSON.parse(userInfo); | |||
| if (userInfo) { | |||
| setUserInfo(userInfo); | |||
| } | |||
| } catch (e) { | |||
| console.log(e); | |||
| } | |||
| }; | |||
| useEffect(() => { | |||
| isLoggedIn(); | |||
| }, []); | |||
| return ( | |||
| <AuthContext.Provider | |||
| value={{ isLoading, login, logout, userInfo, register, googleAuth }} | |||
| > | |||
| {children} | |||
| </AuthContext.Provider> | |||
| ); | |||
| }; | |||
| @@ -1,15 +1,13 @@ | |||
| import React, {useContext} from 'react' | |||
| import { AuthContext } from '../context/AuthContext' | |||
| import AppStack from './AppStack'; | |||
| import AuthStack from './AuthStack'; | |||
| import React from "react"; | |||
| import AppStack from "./AppStack"; | |||
| import AuthStack from "./AuthStack"; | |||
| import { useSelector } from "react-redux"; | |||
| import { selectTokens } from "../store/selectors/loginSelectors"; | |||
| const RootNavigation = () => { | |||
| const {userInfo} = useContext(AuthContext); | |||
| return ( | |||
| !userInfo.jwt ? <AuthStack /> : <AppStack /> | |||
| ) | |||
| const tokens = useSelector(selectTokens); | |||
| } | |||
| return !tokens.JwtToken ? <AuthStack /> : <AppStack />; | |||
| }; | |||
| export default RootNavigation | |||
| export default RootNavigation; | |||
| @@ -17,13 +17,15 @@ | |||
| "@reduxjs/toolkit": "^1.9.1", | |||
| "axios": "^1.2.1", | |||
| "expo": "~47.0.8", | |||
| "expo-auth-session": "~3.7.3", | |||
| "expo-auth-session": "~3.8.0", | |||
| "expo-random": "~13.0.0", | |||
| "expo-splash-screen": "~0.17.5", | |||
| "expo-status-bar": "~1.4.2", | |||
| "expo-web-browser": "~12.0.0", | |||
| "formik": "^2.2.9", | |||
| "jwt-decode": "^3.1.2", | |||
| "lodash.filter": "^4.6.0", | |||
| "lodash.isempty": "^4.4.0", | |||
| "react": "18.1.0", | |||
| "react-dom": "18.1.0", | |||
| "react-native": "0.70.5", | |||
| @@ -39,9 +41,9 @@ | |||
| "react-native-vector-icons": "^9.2.0", | |||
| "react-native-web": "~0.18.7", | |||
| "react-navigation-stack": "^2.10.4", | |||
| "react-redux": "^8.0.5", | |||
| "redux-batched-actions": "^0.5.0", | |||
| "redux-thunk": "^2.4.2", | |||
| "react-redux": "^7.2.4", | |||
| "redux": "^4.1.0", | |||
| "redux-saga": "^1.1.3", | |||
| "yup": "^0.32.11" | |||
| }, | |||
| "devDependencies": { | |||
| @@ -0,0 +1,11 @@ | |||
| export default { | |||
| authentications: { | |||
| login: "api/auth/local", | |||
| register: "api/auth/local/register", | |||
| refreshToken: "api/token/refresh", | |||
| authProvider: "api/auth/google/callback?access_token={accessToken}", | |||
| }, | |||
| users: { | |||
| logout: "api/auth/logout", | |||
| }, | |||
| }; | |||
| @@ -0,0 +1,112 @@ | |||
| import axios from "axios"; | |||
| const request = axios.create({ | |||
| baseURL: "http://localhost:1337/", | |||
| headers: { | |||
| "Content-Type": "application/json", | |||
| }, | |||
| }); | |||
| export const getRequest = (url, params = null, options = null) => | |||
| request.get(url, { params, ...options }); | |||
| export const postRequest = (url, data, params = null, options = null) => | |||
| request.post(url, data, { params, ...options }); | |||
| export const putRequest = (url, data, params = null, options = null) => | |||
| request.put(url, data, { params, ...options }); | |||
| export const patchRequest = (url, data, params = null, options = null) => | |||
| request.patch(url, data, { params, ...options }); | |||
| export const deleteRequest = (url, params = null, options = null) => | |||
| request.delete(url, { params, ...options }); | |||
| export const downloadRequest = (url, params = null, options = null) => | |||
| request.get(url, { params, ...options, responseType: "blob" }); | |||
| export const replaceInUrl = (url, pathVariables = {}) => { | |||
| const keys = Object.keys(pathVariables); | |||
| if (!keys.length) { | |||
| return url; | |||
| } | |||
| return keys.reduce( | |||
| (acc, key) => acc.replace(`{${key}}`, pathVariables[`${key}`]), | |||
| url | |||
| ); | |||
| }; | |||
| export const addHeaderToken = (token) => { | |||
| request.defaults.headers.Authorization = `Bearer ${token}`; | |||
| }; | |||
| export const addHeaderCookie = (key, value) => { | |||
| request.defaults.headers[`${key}`] = value; | |||
| }; | |||
| export const removeHeaderToken = () => { | |||
| delete request.defaults.headers.Authorization; | |||
| }; | |||
| // If you pass function to interceptor of axios, it only adds that function | |||
| // to existing array of interceptor functions. That causes that same function | |||
| // of interceptors getting called multiple times instead of just one time, as it | |||
| // is supposed to do. Thats why there is 'global' axios interceptor array, which indicates | |||
| // axios to eject previous interceptor. This approach requires that every middleware has its | |||
| // unique name from which it is being recognized. Every object in those arrays contains | |||
| // interceptor name and ID of interceptor function. | |||
| let axiosInterceptorRequests = []; | |||
| let axiosInterceptorResponses = []; | |||
| export const attachPostRequestListener = ( | |||
| postRequestListener, | |||
| interceptorName | |||
| ) => { | |||
| let previousAxiosInterceptor = axiosInterceptorResponses.find( | |||
| (item) => item.name === interceptorName | |||
| ); | |||
| let previousAxiosInterceptorResponses = axiosInterceptorResponses; | |||
| if (previousAxiosInterceptor !== undefined) { | |||
| request.interceptors.response.eject(previousAxiosInterceptor.interceptorID); | |||
| previousAxiosInterceptorResponses = axiosInterceptorResponses.filter( | |||
| (item) => item.interceptorID !== previousAxiosInterceptor.interceptorID | |||
| ); | |||
| } | |||
| let axiosInterceptorID = request.interceptors.response.use( | |||
| (response) => response, | |||
| (response) => postRequestListener(response) | |||
| ); | |||
| previousAxiosInterceptorResponses.push({ | |||
| name: interceptorName, | |||
| interceptorID: axiosInterceptorID, | |||
| }); | |||
| axiosInterceptorResponses = [...previousAxiosInterceptorResponses]; | |||
| }; | |||
| export const attachBeforeRequestListener = ( | |||
| beforeRequestListener, | |||
| interceptorName | |||
| ) => { | |||
| let previousAxiosInterceptor = axiosInterceptorRequests.find( | |||
| (item) => item.name === interceptorName | |||
| ); | |||
| let previousAxiosInterceptorRequests = axiosInterceptorRequests; | |||
| if (previousAxiosInterceptor !== undefined) { | |||
| request.interceptors.request.eject(previousAxiosInterceptor.interceptorID); | |||
| previousAxiosInterceptorRequests = axiosInterceptorRequests.filter( | |||
| (item) => item.interceptorID !== previousAxiosInterceptor.interceptorID | |||
| ); | |||
| } | |||
| let axiosInterceptorID = request.interceptors.request.use( | |||
| (response) => beforeRequestListener(response), | |||
| (response) => response | |||
| ); | |||
| previousAxiosInterceptorRequests.push({ | |||
| name: interceptorName, | |||
| interceptorID: axiosInterceptorID, | |||
| }); | |||
| axiosInterceptorRequests = [...previousAxiosInterceptorRequests]; | |||
| }; | |||
| export const apiDefaultUrl = request.defaults.baseURL; | |||
| @@ -0,0 +1,18 @@ | |||
| import { getRequest, postRequest, replaceInUrl } from "./index"; | |||
| import apiEndpoints from "./apiEndpoints"; | |||
| export const attemptLogin = (payload) => | |||
| postRequest(apiEndpoints.authentications.login, payload); | |||
| export const attemptAuthProvider = (accessToken) => | |||
| getRequest( | |||
| replaceInUrl(apiEndpoints.authentications.authProvider, { accessToken }) | |||
| ); | |||
| export const attemptRegister = (payload) => | |||
| postRequest(apiEndpoints.authentications.register, payload); | |||
| export const refreshTokenRequest = (payload) => | |||
| postRequest(apiEndpoints.authentications.refreshToken, payload); | |||
| export const logoutUserRequest = () => getRequest(apiEndpoints.users.logout); | |||
| @@ -1,4 +1,4 @@ | |||
| import React, { useContext, useEffect, useState } from "react"; | |||
| import React, { useEffect, useState } from "react"; | |||
| import { | |||
| View, | |||
| Text, | |||
| @@ -10,15 +10,13 @@ import { | |||
| StyleSheet, | |||
| } from "react-native"; | |||
| import Feather from "@expo/vector-icons/Feather"; | |||
| import { postsApi } from "../service/post"; | |||
| import { AuthContext } from "../context/AuthContext"; | |||
| import ListItem from "../components/ListItem/ListItem"; | |||
| import filter from "lodash.filter"; | |||
| import { globalStyles } from "../styles/global"; | |||
| import { getRequest } from "../request"; | |||
| const HomeScreen = ({ navigation }) => { | |||
| const [posts, setPosts] = useState([]); | |||
| const { userInfo } = useContext(AuthContext); | |||
| const [query, setQuery] = useState(""); | |||
| const [filteredData, setFilteredData] = useState(posts); | |||
| @@ -40,11 +38,11 @@ const HomeScreen = ({ navigation }) => { | |||
| setQuery(formatedText); | |||
| }; | |||
| const fetchAll = () => { | |||
| if (userInfo.jwt) { | |||
| postsApi(userInfo.jwt) | |||
| .then((res) => setPosts(res.data)) | |||
| .catch((e) => console.log(e)); | |||
| const fetchAll = async () => { | |||
| const { data } = await getRequest("api/posts?populate=*"); | |||
| if (data.data) { | |||
| setPosts(data.data); | |||
| } | |||
| }; | |||
| @@ -56,7 +54,7 @@ const HomeScreen = ({ navigation }) => { | |||
| if (posts) { | |||
| setFilteredData(posts); | |||
| } | |||
| }, [posts]) | |||
| }, [posts]); | |||
| return ( | |||
| <SafeAreaView style={{ flex: 1, backgroundColor: "#fff" }}> | |||
| <ScrollView style={{ padding: 20 }}> | |||
| @@ -1,4 +1,4 @@ | |||
| import React, { useState, useContext } from "react"; | |||
| import React, { useEffect } from "react"; | |||
| import { | |||
| SafeAreaView, | |||
| View, | |||
| @@ -17,20 +17,56 @@ import TwitterSVG from "../assets/images/twitter.svg"; | |||
| import CustomButton from "../components/Buttons/CustomButton"; | |||
| import InputField from "../components/InputField"; | |||
| import { globalStyles } from "../styles/global"; | |||
| import { AuthContext } from "../context/AuthContext"; | |||
| import Loader from "../components/Loader"; | |||
| import { Formik } from "formik"; | |||
| import { loginSchema } from "../schemas/loginSchema"; | |||
| import { useDispatch, useSelector } from "react-redux"; | |||
| import { selectLoginError } from "../store/selectors/loginSelectors"; | |||
| import { | |||
| clearLoginErrors, | |||
| fetchUser, | |||
| } from "../store/actions/login/loginActions"; | |||
| import { selectIsLoadingByActionType } from "../store/selectors/loadingSelectors"; | |||
| import { LOGIN_USER_SCOPE } from "../store/actions/login/loginActionConstants"; | |||
| import { fetchAuthProvider } from "../store/actions/authProvider/authProviderActions"; | |||
| import useAuthHook from "../utils/hooks/useAuthHook"; | |||
| import { storeData } from "../service/asyncStorage"; | |||
| import { ACCESS_TOKEN } from "../constants/localStorage"; | |||
| const LoginScreen = ({ navigation }) => { | |||
| const [error, setError] = useState(null); | |||
| const { response, promptAsync } = useAuthHook(); | |||
| const dispatch = useDispatch(); | |||
| const error = useSelector(selectLoginError); | |||
| const isLoading = useSelector(selectIsLoadingByActionType(LOGIN_USER_SCOPE)); | |||
| const storeToken = async (token) => { | |||
| await storeData(ACCESS_TOKEN, token); | |||
| } | |||
| const { isLoading, login, googleAuth } = useContext(AuthContext); | |||
| useEffect(() => { | |||
| if (response?.type === "success") { | |||
| const accessToken = response.authentication.accessToken; | |||
| if (accessToken) { | |||
| storeToken(accessToken); | |||
| dispatch(fetchAuthProvider({ accessToken })); | |||
| } | |||
| } | |||
| }, [response]); | |||
| const handleGoogleAuth = () => { | |||
| promptAsync({ useProxy: true, showInRecents: true }); | |||
| }; | |||
| const handleLogin = (values) => { | |||
| login(values.email, values.password, function (result) { | |||
| setError(result.error.message); | |||
| }); | |||
| const { email, password } = values; | |||
| dispatch(clearLoginErrors()); | |||
| dispatch( | |||
| fetchUser({ | |||
| identifier: email, | |||
| password, | |||
| }) | |||
| ); | |||
| }; | |||
| return ( | |||
| @@ -50,16 +86,23 @@ const LoginScreen = ({ navigation }) => { | |||
| validateOnChange={false} | |||
| validateOnBlur={false} | |||
| > | |||
| {({ handleChange, handleBlur, handleSubmit, values, isValid, errors }) => ( | |||
| {({ | |||
| handleChange, | |||
| handleBlur, | |||
| handleSubmit, | |||
| values, | |||
| isValid, | |||
| errors, | |||
| }) => ( | |||
| <> | |||
| <Text style={globalStyles.boldText}>Sign In</Text> | |||
| <InputField | |||
| name="email" | |||
| label={"Email"} | |||
| keyboardType="email-address" | |||
| onChangeText={handleChange('email')} | |||
| onChangeText={handleChange("email")} | |||
| text={values.email} | |||
| handleBlur={handleBlur('email')} | |||
| handleBlur={handleBlur("email")} | |||
| icon={ | |||
| <MaterialIcons | |||
| name="alternate-email" | |||
| @@ -69,16 +112,18 @@ const LoginScreen = ({ navigation }) => { | |||
| /> | |||
| } | |||
| /> | |||
| {errors.email && <Text style={styles.errorMessage}>{errors.email}</Text>} | |||
| {errors.email && ( | |||
| <Text style={styles.errorMessage}>{errors.email}</Text> | |||
| )} | |||
| <InputField | |||
| name="password" | |||
| label={"Password"} | |||
| filedButtonLabel={"Forgot?"} | |||
| fieldButtonFunction={() => {}} | |||
| inputType="password" | |||
| handleBlur={handleBlur('password')} | |||
| handleBlur={handleBlur("password")} | |||
| text={values.password} | |||
| onChangeText={handleChange('password')} | |||
| onChangeText={handleChange("password")} | |||
| icon={ | |||
| <Ionicons | |||
| name="ios-lock-closed-outline" | |||
| @@ -88,7 +133,9 @@ const LoginScreen = ({ navigation }) => { | |||
| /> | |||
| } | |||
| /> | |||
| {errors.password && <Text style={styles.errorMessage}>{errors.password}</Text>} | |||
| {errors.password && ( | |||
| <Text style={styles.errorMessage}>{errors.password}</Text> | |||
| )} | |||
| {error && <Text style={styles.errorMessage}>{error}</Text>} | |||
| <CustomButton label={"Login"} onPress={handleSubmit} /> | |||
| </> | |||
| @@ -97,7 +144,7 @@ const LoginScreen = ({ navigation }) => { | |||
| <Text style={globalStyles.regularCenteredText}>Or login with ...</Text> | |||
| <View style={styles.providersContainer}> | |||
| <TouchableOpacity | |||
| onPress={googleAuth} | |||
| onPress={handleGoogleAuth} | |||
| style={globalStyles.iconButton} | |||
| > | |||
| <GoogleSVG height={24} width={24} /> | |||
| @@ -1,19 +1,16 @@ | |||
| import React, { useContext, useEffect, useState } from "react"; | |||
| import { View, Text, Image, StyleSheet } from "react-native"; | |||
| import { AuthContext } from "../context/AuthContext"; | |||
| import { singlePostApi } from "../service/post"; | |||
| import { getRequest } from "../request"; | |||
| import { globalStyles } from "../styles/global"; | |||
| import { windowWidth } from "../utils/Dimensions"; | |||
| const PostDetailsScreen = ({ navigation, route }) => { | |||
| const { userInfo } = useContext(AuthContext); | |||
| const [post, setPost] = useState({}); | |||
| const fetchPost = async () => { | |||
| if (userInfo.jwt && route.params.id) { | |||
| singlePostApi(userInfo.jwt, route.params?.id) | |||
| .then((res) => setPost(res.data)) | |||
| .catch((e) => console.log(e)); | |||
| const { data } = await getRequest(`api/posts/${route.params.id}?populate=*`); | |||
| if (data) { | |||
| setPost(data.data); | |||
| } | |||
| }; | |||
| @@ -1,13 +1,12 @@ | |||
| import React, { useState, useContext } from "react"; | |||
| import React, { useEffect } from "react"; | |||
| import { | |||
| SafeAreaView, | |||
| ScrollView, | |||
| View, | |||
| Text, | |||
| TextInput, | |||
| TouchableOpacity, | |||
| StyleSheet, | |||
| Platform, | |||
| Alert, | |||
| } from "react-native"; | |||
| // import DateTimePicker from "@react-native-community/datetimepicker"; | |||
| @@ -23,40 +22,70 @@ import TwitterSVG from "../assets/images/twitter.svg"; | |||
| import CustomButton from "../components/Buttons/CustomButton"; | |||
| import { globalStyles } from "../styles/global"; | |||
| import { AuthContext } from "../context/AuthContext"; | |||
| import Loader from "../components/Loader"; | |||
| import { Formik } from "formik"; | |||
| import { registerSchema } from "../schemas/registerSchema"; | |||
| import { useDispatch, useSelector } from "react-redux"; | |||
| import { selectRegisterError } from "../store/selectors/registerSelectors"; | |||
| import { selectIsLoadingByActionType } from "../store/selectors/loadingSelectors"; | |||
| import { REGISTER_USER_SCOPE } from "../store/actions/register/registerActionConstants"; | |||
| import { | |||
| clearRegisterErrors, | |||
| registerUser, | |||
| } from "../store/actions/register/registerActions"; | |||
| import useAuthHook from "../utils/hooks/useAuthHook"; | |||
| import { fetchAuthProvider } from "../store/actions/authProvider/authProviderActions"; | |||
| import { ACCESS_TOKEN } from "../constants/localStorage"; | |||
| import { storeData } from "../service/asyncStorage"; | |||
| const RegisterScreen = ({ navigation }) => { | |||
| const { isLoading, register, googleAuth } = useContext(AuthContext); | |||
| const { response, promptAsync } = useAuthHook(); | |||
| const dispatch = useDispatch(); | |||
| const error = useSelector(selectRegisterError); | |||
| const isLoading = useSelector( | |||
| selectIsLoadingByActionType(REGISTER_USER_SCOPE) | |||
| ); | |||
| const [date, setDate] = useState(new Date()); | |||
| const [open, setOpen] = useState(false); | |||
| // const [dateOfBirthLabel, setDateOfBirthLabel] = useState("Date of Birth"); | |||
| const [error, setError] = useState(null); | |||
| const storeToken = async (token) => { | |||
| await storeData(ACCESS_TOKEN, token); | |||
| } | |||
| // BIRTHDAY DATETIME PICKER | |||
| // const onChange = (event, selectedDate) => { | |||
| // const currentDate = selectedDate || date; | |||
| // setDate(currentDate); | |||
| // let tempDate = new Date(currentDate); | |||
| // let fDate = | |||
| // tempDate.getDate() + | |||
| // "/" + | |||
| // (tempDate.getMonth() + 1) + | |||
| // "/" + | |||
| // tempDate.getFullYear(); | |||
| // setDateOfBirthLabel(fDate); | |||
| // setOpen(false); | |||
| // }; | |||
| const handleApiResponseSuccess = () => { | |||
| Alert.alert( | |||
| "Success", | |||
| "Successfully registered account, now you can log in", | |||
| [ | |||
| { | |||
| text: "OK", | |||
| onPress: () => navigation.navigate("Login"), | |||
| }, | |||
| ] | |||
| ); | |||
| }; | |||
| const handleSignup = (values) => { | |||
| register(values.username, values.email, values.password, function (result) { | |||
| setError(result.error.message); | |||
| }); | |||
| const { username, email, password } = values; | |||
| dispatch(clearRegisterErrors()); | |||
| dispatch( | |||
| registerUser({ username, email, password, handleApiResponseSuccess }) | |||
| ); | |||
| }; | |||
| const handleGoogleAuth = () => { | |||
| promptAsync({ useProxy: true, showInRecents: true }); | |||
| }; | |||
| useEffect(() => { | |||
| if (response?.type === "success") { | |||
| const accessToken = response.authentication.accessToken; | |||
| if (accessToken) { | |||
| storeToken(accessToken); | |||
| dispatch(fetchAuthProvider({ accessToken })); | |||
| } | |||
| } | |||
| }, [response]); | |||
| return ( | |||
| <SafeAreaView style={globalStyles.safeArea}> | |||
| <Loader visible={isLoading} /> | |||
| @@ -70,7 +99,7 @@ const RegisterScreen = ({ navigation }) => { | |||
| <Text style={globalStyles.boldText}>Sign Up</Text> | |||
| <View style={styles.providersContainer}> | |||
| <TouchableOpacity | |||
| onPress={googleAuth} | |||
| onPress={handleGoogleAuth} | |||
| style={globalStyles.iconButton} | |||
| > | |||
| <GoogleSVG height={24} width={24} /> | |||
| @@ -178,14 +207,11 @@ const RegisterScreen = ({ navigation }) => { | |||
| } | |||
| inputType="password" | |||
| /> | |||
| {errors.confirmPassword && <Text style={styles.errorMessage}>{errors.confirmPassword}</Text>} | |||
| {/* <View style={styles.dateOfBirthContainer}> | |||
| <Ionicons name='calendar-outline' size={20} color="#666" style={{marginRight: 5}} /> | |||
| <TouchableOpacity onPress={() => setOpen(true)}> | |||
| <Text style={styles.dateOfBirthLabel}>{dateOfBirthLabel}</Text> | |||
| {open && <DateTimePicker testID='dateTimePicker' value={date} mode="date" display='spinner' onChange={onChange} textColor="#000" />} | |||
| </TouchableOpacity> | |||
| </View> */} | |||
| {errors.confirmPassword && ( | |||
| <Text style={styles.errorMessage}> | |||
| {errors.confirmPassword} | |||
| </Text> | |||
| )} | |||
| {error && <Text style={styles.errorMessage}>{error}</Text>} | |||
| <CustomButton label={"Sign Up"} onPress={handleSubmit} /> | |||
| </> | |||
| @@ -1,6 +0,0 @@ | |||
| export default { | |||
| login: 'api/auth/local', | |||
| register: 'api/auth/local/register', | |||
| googleAuth: 'api/auth/google/callback?access_token=', | |||
| fetchPosts: 'api/posts?populate=*', | |||
| } | |||
| @@ -24,3 +24,9 @@ export const removeData = async (key) => { | |||
| // error reading value | |||
| } | |||
| }; | |||
| export const clearAll = async () => { | |||
| try { | |||
| await AsyncStorage.clear(); | |||
| } catch (e) {} | |||
| }; | |||
| @@ -1 +0,0 @@ | |||
| export const API_ENDPOINT ="http://localhost:1337/"; | |||
| @@ -1,41 +0,0 @@ | |||
| import axios from "axios"; | |||
| import { API_ENDPOINT } from "./endpointDef"; | |||
| const request = axios.create({ | |||
| baseURL: API_ENDPOINT, | |||
| headers: { | |||
| "Content-Type": "application/json", | |||
| }, | |||
| }); | |||
| export const getRequest = (url, params = null, options = null) => | |||
| request.get(url, { params, ...options }); | |||
| export const postRequest = (url, data, params = null, options = null) => | |||
| request.post(url, data, { params, ...options }); | |||
| export const putRequest = (url, data, params = null, options = null) => | |||
| request.put(url, data, { params, ...options }); | |||
| export const patchRequest = (url, data, params = null, options = null) => | |||
| request.patch(url, data, { params, ...options }); | |||
| export const deleteRequest = (url, params = null, options = null) => | |||
| request.delete(url, { params, ...options }); | |||
| export const addHeaderToken = (token) => { | |||
| request.defaults.headers.Authorization = `Bearer ${token}`; | |||
| }; | |||
| export const removeHeaderToken = () => { | |||
| delete request.defaults.headers.Authorization; | |||
| }; | |||
| export const attachPostRequestListener = (postRequestListener) => { | |||
| request.interceptors.response.use( | |||
| (response) => response, | |||
| (response) => postRequestListener(response) | |||
| ); | |||
| }; | |||
| export const apiDefaultUrl = request.defaults.baseURL; | |||
| @@ -1,30 +0,0 @@ | |||
| import { API_ENDPOINT } from "./endpointDef"; | |||
| import apiEndpoints from "./apiEndpoints"; | |||
| export const postsApi = async (jwt) => { | |||
| const result = await fetch(`${API_ENDPOINT}${apiEndpoints.fetchPosts}`, { | |||
| method: "GET", | |||
| headers: { | |||
| "Content-Type": "application/json", | |||
| 'Authorization': "Bearer " + jwt, | |||
| }, | |||
| }); | |||
| const res = await result.json(); | |||
| return res; | |||
| }; | |||
| export const singlePostApi = async (jwt, postId) => { | |||
| const result = await fetch(`${API_ENDPOINT}api/posts/${postId}?populate=*`, { | |||
| method: "GET", | |||
| headers: { | |||
| "Content-Type": "application/json", | |||
| 'Authorization': "Bearer " + jwt, | |||
| }, | |||
| }); | |||
| const res = await result.json(); | |||
| return res; | |||
| }; | |||
| @@ -1,22 +0,0 @@ | |||
| import store from '../../store/index'; | |||
| import { refreshTokens } from './tokenApiClient' | |||
| import { validateAccessToken } from './validator' | |||
| let lock; | |||
| export async function getAccessToken() { | |||
| if (lock) { | |||
| await lock; | |||
| } | |||
| else { | |||
| lock = new Promise((resolve, reject) => { | |||
| }) | |||
| } | |||
| if (!validateAccessToken()) { | |||
| console.log("REFRESHING TOKEN") | |||
| await refreshTokens(); | |||
| } | |||
| return store.getState().user.token | |||
| } | |||
| @@ -1,36 +0,0 @@ | |||
| import axios from "axios"; | |||
| import { batchActions } from 'redux-batched-actions'; | |||
| import { logOut, setRefreshToken, setToken } from "../../store/actions"; | |||
| import store from "../../store/index"; | |||
| import { API_ENDPOINT } from '../endpointDef'; | |||
| let refreshingPromise = null | |||
| export async function refreshTokens() { | |||
| // console.log("RefreshTokens") | |||
| if (!refreshingPromise) { | |||
| refreshingPromise = new Promise(async (resolve, reject) => { | |||
| // console.log("Sending the requst for new tokens") | |||
| const refreshToken = store.getState().user.refreshToken | |||
| const username = store.getState().user.username | |||
| try { | |||
| const { data } = await axios.post(`${API_ENDPOINT}api/token/refresh`, { Token: refreshToken, Username: username, }); | |||
| // console.log("Dispatch from refresh", data.Data.AccessToken, data.Data.RefreshToken) | |||
| store.dispatch(batchActions([setToken(data.Data.AccessToken), setRefreshToken(data.Data.RefreshToken)])) | |||
| resolve() | |||
| } | |||
| catch (e) { | |||
| // console.log("Could not refresh token.") | |||
| store.dispatch(logOut()) | |||
| reject(e) | |||
| } | |||
| finally { | |||
| refreshingPromise = null | |||
| } | |||
| }) | |||
| } | |||
| // else { | |||
| // console.log("Someone is refreshing...") | |||
| // } | |||
| await refreshingPromise | |||
| } | |||
| @@ -1,17 +0,0 @@ | |||
| import store from "../../store/store"; | |||
| import jwt_decode from "jwt-decode"; | |||
| export function validateAccessToken() { | |||
| const accessToken = store.getState().user.token | |||
| if (!accessToken) { | |||
| return false; | |||
| } | |||
| const decoded = jwt_decode(accessToken); | |||
| // console.log("DECODED", decoded) | |||
| const exp = decoded.exp * 1000 | |||
| const isExpired = exp - (Date.now() + 60 * 1000) | |||
| // console.log("isExpired", isExpired) | |||
| if (isExpired <= 0) | |||
| return false | |||
| return true | |||
| } | |||
| @@ -1,40 +0,0 @@ | |||
| import { API_ENDPOINT } from "./endpointDef"; | |||
| import apiEndpoints from "./apiEndpoints"; | |||
| export const loginApi = async (payload) => { | |||
| const result = await fetch(`${API_ENDPOINT}${apiEndpoints.login}`, { | |||
| method: 'POST', | |||
| headers: { | |||
| 'Content-Type': 'application/json' | |||
| }, | |||
| body: JSON.stringify(payload) | |||
| }) | |||
| const res = await result.json(); | |||
| return res; | |||
| }; | |||
| export const registerApi = async (payload) => { | |||
| const result = await fetch(`${API_ENDPOINT}${apiEndpoints.register}`, { | |||
| method: 'POST', | |||
| headers: { | |||
| 'Content-Type': 'application/json' | |||
| }, | |||
| body: JSON.stringify(payload) | |||
| }) | |||
| const res = await result.json(); | |||
| return res; | |||
| }; | |||
| export const googleApi = async (accessToken) => { | |||
| const result = await fetch(`${API_ENDPOINT}${apiEndpoints.googleAuth}${accessToken}`); | |||
| const res = await result.json(); | |||
| return res; | |||
| }; | |||
| @@ -0,0 +1,21 @@ | |||
| export const FETCH = '[FETCH]'; | |||
| export const UPDATE = '[UPDATE]'; | |||
| export const SUCCESS = '[SUCCESS]'; | |||
| export const SET = '[SET]'; | |||
| export const ERROR = '[ERROR]'; | |||
| export const SUBMIT = '[SUBMIT]'; | |||
| export const CLEAR = '[CLEAR]'; | |||
| export const DELETE = '[DELETE]'; | |||
| export const LOADING = '[LOADING]'; | |||
| const createType = (typeDescription) => (type) => `${typeDescription}${type}`; | |||
| export const createFetchType = createType(FETCH); | |||
| export const createUpdateType = createType(UPDATE); | |||
| export const createSuccessType = createType(SUCCESS); | |||
| export const createSetType = createType(SET); | |||
| export const createErrorType = createType(ERROR); | |||
| export const createSubmitType = createType(SUBMIT); | |||
| export const createClearType = createType(CLEAR); | |||
| export const createDeleteType = createType(DELETE); | |||
| export const createLoadingType = createType(LOADING); | |||
| @@ -0,0 +1,5 @@ | |||
| import { createLoadingType } from "../actionHelpers"; | |||
| export const APP_LOADING = createLoadingType("APP_LOADING"); | |||
| export const ADD_LOADER = createLoadingType("ADD_LOADER"); | |||
| export const REMOVE_LOADER = createLoadingType("REMOVE_LOADER"); | |||
| @@ -0,0 +1,11 @@ | |||
| import { ADD_LOADER, REMOVE_LOADER } from "./appActionConstants"; | |||
| export const addLoader = (payload) => ({ | |||
| type: ADD_LOADER, | |||
| payload, | |||
| }); | |||
| export const removeLoader = (payload) => ({ | |||
| type: REMOVE_LOADER, | |||
| payload, | |||
| }); | |||
| @@ -0,0 +1,10 @@ | |||
| import { | |||
| createErrorType, | |||
| createFetchType, | |||
| createSuccessType, | |||
| } from "../actionHelpers"; | |||
| export const AUTH_PROVIDER_SCOPE = "AUTH_PROVIDER"; | |||
| export const AUTH_PROVIDER_FETCH = createFetchType(AUTH_PROVIDER_SCOPE); | |||
| export const AUTH_PROVIDER_SUCCESS = createSuccessType(AUTH_PROVIDER_SCOPE); | |||
| export const AUTH_PROVIDER_ERROR = createErrorType(AUTH_PROVIDER_SCOPE); | |||
| @@ -0,0 +1,20 @@ | |||
| import { | |||
| AUTH_PROVIDER_ERROR, | |||
| AUTH_PROVIDER_FETCH, | |||
| AUTH_PROVIDER_SUCCESS, | |||
| } from "./authProviderActionConstants"; | |||
| export const fetchAuthProvider = (payload) => ({ | |||
| type: AUTH_PROVIDER_FETCH, | |||
| payload, | |||
| }); | |||
| export const fetchAuthProviderSuccess = (payload) => ({ | |||
| type: AUTH_PROVIDER_SUCCESS, | |||
| payload, | |||
| }); | |||
| export const fetchAuthProviderError = (payload) => ({ | |||
| type: AUTH_PROVIDER_ERROR, | |||
| payload, | |||
| }); | |||
| @@ -1,14 +0,0 @@ | |||
| import { createAction } from "@reduxjs/toolkit"; | |||
| const createTestAction = (name) => createAction(`TEST_${name}`) | |||
| export const increment = createTestAction('INCREMENT') | |||
| export const decrement = createTestAction('DECREMENT') | |||
| const createUserAction = (name) => createAction(`USER_${name}`) | |||
| export const setToken = createUserAction('TOKEN') | |||
| export const setRefreshToken = createUserAction('REFRESHTOKEN') | |||
| export const authLoaded = createUserAction('AUTH_LOADED') | |||
| export const logOut = createUserAction('LOGOUT') | |||
| export const setUsername = createUserAction('USERNAME') | |||
| export const logInSuccess = createUserAction('LOGIN_SUCCESS') | |||
| export const setConnectionError = createUserAction('SET_CONNECTION_ERROR') | |||
| @@ -0,0 +1,30 @@ | |||
| import { | |||
| createClearType, | |||
| createErrorType, | |||
| createFetchType, | |||
| createLoadingType, | |||
| createSuccessType, | |||
| createSubmitType, | |||
| } from '../actionHelpers'; | |||
| export const LOGIN_USER_SCOPE = 'LOGIN_USER'; | |||
| export const LOGIN_USER_FETCH = createFetchType(LOGIN_USER_SCOPE); | |||
| export const LOGIN_USER_SUCCESS = createSuccessType(LOGIN_USER_SCOPE); | |||
| export const LOGIN_USER_ERROR = createErrorType(LOGIN_USER_SCOPE); | |||
| export const CLEAR_LOGIN_USER_ERROR = createClearType( | |||
| `${LOGIN_USER_SCOPE}_ERROR`, | |||
| ); | |||
| export const LOGIN_USER_LOADING = createLoadingType(LOGIN_USER_SCOPE); | |||
| export const UPDATE_USER_JWT_TOKEN = 'UPDATE_USER_JWT_TOKEN'; | |||
| export const RESET_LOGIN_STATE = 'RESET_LOGIN_STATE'; | |||
| export const AUTHENTICATE_USER = 'AUTHENTICATE_USER'; | |||
| export const LOGOUT_USER = 'LOGOUT_USER'; | |||
| export const REFRESH_TOKEN = 'REFRESH_TOKEN'; | |||
| const GENERATE_TOKEN_SCOPE = 'GENERATE_TOKEN'; | |||
| export const GENERATE_TOKEN = createSubmitType(GENERATE_TOKEN_SCOPE); | |||
| export const GENERATE_TOKEN_SUCCESS = createSuccessType(GENERATE_TOKEN_SCOPE); | |||
| export const GENERATE_TOKEN_ERROR = createErrorType(GENERATE_TOKEN_SCOPE); | |||
| @@ -0,0 +1,48 @@ | |||
| import { | |||
| CLEAR_LOGIN_USER_ERROR, | |||
| LOGIN_USER_ERROR, | |||
| LOGIN_USER_FETCH, | |||
| LOGIN_USER_SUCCESS, | |||
| LOGOUT_USER, | |||
| RESET_LOGIN_STATE, | |||
| UPDATE_USER_JWT_TOKEN, | |||
| REFRESH_TOKEN, | |||
| } from './loginActionConstants'; | |||
| export const fetchUser = (payload) => ({ | |||
| type: LOGIN_USER_FETCH, | |||
| payload, | |||
| }); | |||
| export const fetchUserSuccess = (payload) => ({ | |||
| type: LOGIN_USER_SUCCESS, | |||
| payload, | |||
| }); | |||
| export const fetchUserError = (payload) => ({ | |||
| type: LOGIN_USER_ERROR, | |||
| payload, | |||
| }); | |||
| export const updateUserToken = (payload) => ({ | |||
| type: UPDATE_USER_JWT_TOKEN, | |||
| payload, | |||
| }); | |||
| export const resetLoginState = () => ({ | |||
| type: RESET_LOGIN_STATE, | |||
| }); | |||
| export const clearLoginErrors = () => ({ | |||
| type: CLEAR_LOGIN_USER_ERROR, | |||
| }); | |||
| export const logoutUser = () => ({ | |||
| type: LOGOUT_USER, | |||
| }); | |||
| export const refreshUserToken = (payload) => ({ | |||
| type: REFRESH_TOKEN, | |||
| payload | |||
| }); | |||
| @@ -0,0 +1,18 @@ | |||
| import { | |||
| createClearType, | |||
| createErrorType, | |||
| createFetchType, | |||
| createLoadingType, | |||
| createSuccessType, | |||
| } from "../actionHelpers"; | |||
| export const REGISTER_USER_SCOPE = "REGISTER_USER"; | |||
| export const REGISTER_USER_FETCH = createFetchType(REGISTER_USER_SCOPE); | |||
| export const REGISTER_USER_SUCCESS = createSuccessType(REGISTER_USER_SCOPE); | |||
| export const REGISTER_USER_ERROR = createErrorType(REGISTER_USER_SCOPE); | |||
| export const CLEAR_REGISTER_USER_ERROR = createClearType( | |||
| `${REGISTER_USER_SCOPE}_ERROR` | |||
| ); | |||
| export const REGISTER_USER_LOADING = createLoadingType(REGISTER_USER_SCOPE); | |||
| export const RESET_REGISTER_STATE = "RESET_REGISTER_STATE"; | |||
| @@ -0,0 +1,30 @@ | |||
| import { | |||
| CLEAR_REGISTER_USER_ERROR, | |||
| REGISTER_USER_ERROR, | |||
| REGISTER_USER_FETCH, | |||
| REGISTER_USER_SUCCESS, | |||
| RESET_REGISTER_STATE, | |||
| } from "./registerActionConstants"; | |||
| export const registerUser = (payload) => ({ | |||
| type: REGISTER_USER_FETCH, | |||
| payload, | |||
| }); | |||
| export const registerUserSuccess = (payload) => ({ | |||
| type: REGISTER_USER_SUCCESS, | |||
| payload, | |||
| }); | |||
| export const registerUserError = (payload) => ({ | |||
| type: REGISTER_USER_ERROR, | |||
| payload, | |||
| }); | |||
| export const resetRegisterState = () => ({ | |||
| type: RESET_REGISTER_STATE, | |||
| }); | |||
| export const clearRegisterErrors = () => ({ | |||
| type: CLEAR_REGISTER_USER_ERROR, | |||
| }); | |||
| @@ -0,0 +1,3 @@ | |||
| export const SET_USER = 'SET_USER'; | |||
| export const SET_USER_ERROR = 'SET_USER_ERROR'; | |||
| @@ -0,0 +1,14 @@ | |||
| import { | |||
| SET_USER, | |||
| SET_USER_ERROR, | |||
| } from './userActionConstants'; | |||
| export const setUser = (payload) => ({ | |||
| type: SET_USER, | |||
| payload, | |||
| }); | |||
| export const setUserError = (payload) => ({ | |||
| type: SET_USER_ERROR, | |||
| payload, | |||
| }); | |||
| @@ -1,79 +0,0 @@ | |||
| import AsyncStorage from '@react-native-async-storage/async-storage'; | |||
| import { batchActions } from 'redux-batched-actions'; | |||
| import { authLoaded, setRefreshToken, setToken, setUsername } from './actions' | |||
| // AsyncStorage.getAllKeys() | |||
| // .then((keys) => AsyncStorage.multiGet(keys) | |||
| // .then((data) => console.log(data))); | |||
| async function safeSetStorage(key, value) { | |||
| if (value) { | |||
| await AsyncStorage.setItem(key, value) | |||
| } | |||
| else { | |||
| await AsyncStorage.removeItem(key) | |||
| } | |||
| } | |||
| const accessTokenKey = 'userToken' | |||
| const refreshTokenKey = 'refreshToken' | |||
| const userNameKey = 'userName' | |||
| function subscibeOnAuthChanges(store) { | |||
| let lastAccessToken = store.getState().user.token; | |||
| let lastRefreshToken = store.getState().user.refreshToken; | |||
| let lastUsername = store.getState().user.username; | |||
| // console.log("In subscribe function", lastAccessToken, lastRefreshToken, lastUsername) | |||
| store.subscribe(async () => { | |||
| const state = store.getState(); | |||
| // console.log("In subscribe...") | |||
| const newAccessToken = state.user.token; | |||
| const newRefreshToken = state.user.refreshToken; | |||
| const newUsername = state.user.username; | |||
| // console.log("In subscribe", newAccessToken, newRefreshToken, newUsername) | |||
| if (lastAccessToken !== newAccessToken) { | |||
| lastAccessToken = newAccessToken | |||
| await safeSetStorage(accessTokenKey, newAccessToken) | |||
| } | |||
| if (lastRefreshToken !== newRefreshToken) { | |||
| lastRefreshToken = newRefreshToken | |||
| await safeSetStorage(refreshTokenKey, newRefreshToken) | |||
| } | |||
| if (lastUsername !== newUsername) { | |||
| lastUsername = newUsername | |||
| await safeSetStorage(userNameKey, newUsername) | |||
| } | |||
| }) | |||
| } | |||
| async function loadFromStorage(store) { | |||
| try { | |||
| // leave comments this for testing | |||
| // console.log("getting Token from storage"); | |||
| // await AsyncStorage.setItem(accessTokenKey, "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI4MmVlNjNkMi0yZDRjLTRkZjQtYTQ0MS1hZWRkZjhiNGEwMmYiLCJzdWIiOiJpbGlqYSt1Y2lAcHJvZml0b3B0aWNzLmNvbSIsInVuaXF1ZV9uYW1lIjoiaWxpamErdWNpQHByb2ZpdG9wdGljcy5jb20iLCJVdGNPZmZzZXRNaW51dGVzIjoiLTI0MCIsIkFncmVlbWVudEFjY2VwdGVkIjoiVHJ1ZSIsIk9yZ2FuaXphdGlvbklkIjoiMTkiLCJPcmdhbml6YXRpb25Ib3N0bmFtZSI6Im15c3RyYXRhZGV2Mi5wcm9maXRvcHRpY3MuY29tIiwibmJmIjoxNjE2Njk0NTI2LCJleHAiOjE2MTY2OTYzMjYsImlzcyI6InN0cmF0YWNsZWFyLmNvbSIsImF1ZCI6IkNsaWVudCJ9.kk0TQENidNJfQpkLy6uVUZULHp1r46gA7IyhAMHkqsI"); | |||
| userToken = await AsyncStorage.getItem(accessTokenKey); | |||
| // leave this for testing | |||
| // await AsyncStorage.setItem(refreshTokenKey, "IBFRYNvviwI0+C3CVy1sViQlvFDTXsTUZWa8fgzv6oLQtYTng3A+8xc4gMqpX+y1lR8WIhN4uDlEV19uu+prWEVpi8qkjp999Li0t+eiEoe7OdeK+vQwecowvvJZRt1WK/Qb3biMC76IhElZta/riy2yZ9jQRBhiEfPs+yK+GJI="); | |||
| // await AsyncStorage.setItem(refreshTokenKey, "TV+VsT+Qwb9hh1oi8mG4CBX0gnN9y9aNfXT/QUPZsPwCJ3n1svOO/KwT14Z5UZxhU+8h5ocuvipj4dIGDJa7wJHkV7j5/mBKfSgVqMvX43VdFXkp19Dg0h4Tg9Elh6GpR0N6FSCDJl3igv83W3OpC4UWXylQKW+0gr+tjOPmT78="); | |||
| refreshToken = await AsyncStorage.getItem(refreshTokenKey); | |||
| // leave this for testing | |||
| // await AsyncStorage.setItem(userNameKey, "ilija+uci@profitoptics.com"); | |||
| userName = await AsyncStorage.getItem(userNameKey); | |||
| // console.log('Loaded from storage', userToken) | |||
| store.dispatch(batchActions([setToken(userToken), setRefreshToken(refreshToken), setUsername(userName), authLoaded()])) | |||
| } catch (eror) { | |||
| console.error("There was an error getting token from storage", eror); | |||
| } | |||
| } | |||
| function init(store) { | |||
| loadFromStorage(store) | |||
| .then(() => subscibeOnAuthChanges(store)) | |||
| } | |||
| export { init }; | |||
| @@ -1,9 +1,32 @@ | |||
| import { createStore, applyMiddleware } from "redux"; | |||
| import { enableBatching } from 'redux-batched-actions'; | |||
| import thunk from "redux-thunk"; | |||
| import reducers from "./reducers"; | |||
| import { init } from './authPersistor' | |||
| import { applyMiddleware, compose, createStore } from "redux"; | |||
| import createSagaMiddleware from "redux-saga"; | |||
| import rootReducer from "./reducers"; | |||
| import rootSaga from "./saga"; | |||
| import loadingMiddleware from "./middleware/loadingMiddleware"; | |||
| import requestStatusMiddleware from "./middleware/requestStatusMiddleware"; | |||
| import accessTokenMiddleware from "./middleware/accessTokenMiddleware"; | |||
| // import authenticationMiddleware from "./middleware/authenticationMiddleware"; | |||
| // import internalServerErrorMiddleware from "./middleware/internalServerErrorMiddleware"; | |||
| const composeEnhancers = | |||
| (window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ && | |||
| window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ | |||
| trace: true, | |||
| traceLimit: 25, | |||
| })) || | |||
| compose; | |||
| const sagaMiddleware = createSagaMiddleware(); | |||
| export default createStore( | |||
| rootReducer, | |||
| composeEnhancers( | |||
| applyMiddleware( | |||
| sagaMiddleware, | |||
| loadingMiddleware, | |||
| requestStatusMiddleware, | |||
| // internalServerErrorMiddleware, | |||
| accessTokenMiddleware, | |||
| // authenticationMiddleware | |||
| ) | |||
| ) | |||
| ); | |||
| const store = createStore(enableBatching(reducers), applyMiddleware(thunk)); | |||
| init(store) | |||
| export default store; | |||
| sagaMiddleware.run(rootSaga); | |||
| @@ -0,0 +1,51 @@ | |||
| import axios from "axios"; | |||
| import jwt_decode from "jwt-decode"; | |||
| import { JWT_REFRESH_TOKEN, JWT_TOKEN } from "../../constants/localStorage"; | |||
| import { attachBeforeRequestListener } from "../../request/index"; | |||
| import { getData } from "../../service/asyncStorage"; | |||
| import { logoutUser, refreshUserToken } from "../actions/login/loginActions"; | |||
| export const accessTokensMiddlewareInterceptorName = "ACCESS_TOKEN_INTERCEPTOR"; | |||
| export default ({ dispatch }) => | |||
| (next) => | |||
| (action) => { | |||
| attachBeforeRequestListener(async (response) => { | |||
| const jwtToken = await getData(JWT_TOKEN); | |||
| const refresh = await getData(JWT_REFRESH_TOKEN); | |||
| if (!jwtToken || !refresh) return Promise.resolve(response); | |||
| const jwtTokenDecoded = jwt_decode(jwtToken); | |||
| const refreshTokenDecoded = jwt_decode(refresh); | |||
| if (!response.headers?.Authorization) { | |||
| response.headers.Authorization = `Bearer ${jwtToken}`; | |||
| } | |||
| // If refresh token is expired, log out user | |||
| if (new Date() > new Date(refreshTokenDecoded?.exp * 1000)) { | |||
| dispatch(logoutUser()); | |||
| return Promise.resolve(response); | |||
| } | |||
| // If access token is expired, refresh access token | |||
| if (new Date() > new Date(jwtTokenDecoded.exp * 1000)) { | |||
| const axiosResponse = await axios.post( | |||
| "http://localhost:1337/api/token/refresh", | |||
| { | |||
| refreshToken: refresh, | |||
| }, | |||
| { | |||
| headers: { Authorization: `Bearer ${jwtToken}` }, | |||
| } | |||
| ); | |||
| const newToken = axiosResponse.data; | |||
| response.headers.Authorization = `Bearer ${newToken.jwt}`; | |||
| dispatch(refreshUserToken(newToken)); | |||
| } | |||
| return Promise.resolve(response); | |||
| }, accessTokensMiddlewareInterceptorName); | |||
| next(action); | |||
| }; | |||
| @@ -0,0 +1,22 @@ | |||
| import { attachPostRequestListener } from "../../request"; | |||
| import { logoutUser } from "../actions/login/loginActions"; | |||
| export const authenticationMiddlewareInterceptorName = | |||
| "AUTHENTICATION_MIDDLEWARE"; | |||
| export default ({ dispatch }) => | |||
| (next) => | |||
| (action) => { | |||
| attachPostRequestListener((error) => { | |||
| if (!error.response) { | |||
| return Promise.reject(error); | |||
| } | |||
| if (error.response.status === 401) { | |||
| dispatch(logoutUser()); | |||
| return Promise.reject(error); | |||
| } | |||
| return Promise.resolve(); | |||
| }, authenticationMiddlewareInterceptorName); | |||
| next(action); | |||
| }; | |||
| @@ -0,0 +1,20 @@ | |||
| import { attachPostRequestListener } from "../../request"; | |||
| import { makeErrorToastMessage } from "../../util/helpers/toastMessage"; | |||
| import i18next from "i18next"; | |||
| export const serverErrorMiddlewareInterceptorName = | |||
| "INTERNAL_SERVER_ERROR_MIDDLEWARE_INTERCEPTOR"; | |||
| export default () => (next) => (action) => { | |||
| attachPostRequestListener((error) => { | |||
| if (!error.response) { | |||
| return makeErrorToastMessage(i18next.t("apiErrors.SomethingWentWrong")); | |||
| } | |||
| if (error.response.status === 500) { | |||
| return makeErrorToastMessage(i18next.t("apiErrors.SomethingWentWrong")); | |||
| } | |||
| return Promise.reject(error); | |||
| }, serverErrorMiddlewareInterceptorName); | |||
| next(action); | |||
| }; | |||
| @@ -0,0 +1,30 @@ | |||
| import { | |||
| DELETE, | |||
| ERROR, | |||
| FETCH, | |||
| SUCCESS, | |||
| UPDATE, | |||
| SUBMIT, | |||
| } from '../actions/actionHelpers'; | |||
| import { addLoader, removeLoader } from '../actions/app/appActions'; | |||
| const promiseTypes = [FETCH, UPDATE, DELETE, SUBMIT]; | |||
| export default ({ dispatch }) => (next) => (action) => { | |||
| const promiseType = promiseTypes.find((promiseType) => | |||
| action.type.includes(promiseType), | |||
| ); | |||
| if (promiseType) { | |||
| dispatch(addLoader(action.type)); | |||
| return next(action); | |||
| } | |||
| if (action.type.includes(SUCCESS) || action.type.includes(ERROR)) { | |||
| // const actionType = action.type.includes(SUCCESS) | |||
| // ? action.type.replace(SUCCESS, '[LOADING]') | |||
| // : action.type.replace(ERROR, '[LOADING]'); | |||
| dispatch(removeLoader(action.type)); | |||
| return next(action); | |||
| } | |||
| next(action); | |||
| }; | |||
| @@ -0,0 +1,27 @@ | |||
| import { attachPostRequestListener } from "../../request"; | |||
| import apiEndpoints from "../../request/apiEndpoints"; | |||
| import { logoutUser } from "../actions/login/loginActions"; | |||
| export const requestStatusMiddlewareInterceptorName = | |||
| "REQUEST_STATUS_MIDDLEWARE_INTERCEPTOR"; | |||
| export default ({ dispatch }) => | |||
| (next) => | |||
| (action) => { | |||
| attachPostRequestListener((error) => { | |||
| if (!error.response) { | |||
| return Promise.reject(error); | |||
| } | |||
| if ( | |||
| error.response.config.url !== apiEndpoints.authentications.login && | |||
| error.response.config.url !== | |||
| apiEndpoints.authentications.confirmSecurityQuestion && | |||
| error.response.status === 401 | |||
| ) { | |||
| return dispatch(logoutUser()); | |||
| } | |||
| return Promise.reject(error); | |||
| }, requestStatusMiddlewareInterceptorName); | |||
| next(action); | |||
| }; | |||
| @@ -0,0 +1,33 @@ | |||
| import createReducer from "../../utils/createReducer"; | |||
| import { | |||
| AUTH_PROVIDER_ERROR, | |||
| AUTH_PROVIDER_SUCCESS, | |||
| } from "../../actions/authProvider/authProviderActionConstants"; | |||
| const initialState = { | |||
| success: "", | |||
| errorMessage: "", | |||
| }; | |||
| export default createReducer( | |||
| { | |||
| [AUTH_PROVIDER_SUCCESS]: setProvider, | |||
| [AUTH_PROVIDER_ERROR]: setProviderError, | |||
| }, | |||
| initialState | |||
| ); | |||
| function setProvider(state, action) { | |||
| return { | |||
| ...state, | |||
| success: action.payload, | |||
| }; | |||
| } | |||
| function setProviderError(state, action) { | |||
| return { | |||
| ...state, | |||
| errorMessage: action.payload, | |||
| }; | |||
| } | |||
| @@ -1,12 +1,14 @@ | |||
| import { combineReducers } from "redux"; | |||
| // import { persistReducer } from "redux-persist"; | |||
| // import immutableTransform from "redux-persist-transform-immutable"; | |||
| import test from "./test"; | |||
| import user from "./user"; | |||
| import loginReducer from "./login/loginReducer"; | |||
| import loadingReducer from "./loading/loadingReducer"; | |||
| import userReducer from "./user/userReducer"; | |||
| import registerReducer from "./register/registerReducer"; | |||
| import authProviderReducer from "./authProvider/authProviderReducer"; | |||
| const rootReducer = combineReducers({ | |||
| test, | |||
| user | |||
| export default combineReducers({ | |||
| login: loginReducer, | |||
| user: userReducer, | |||
| loading: loadingReducer, | |||
| register: registerReducer, | |||
| authProvider: authProviderReducer, | |||
| }); | |||
| export default rootReducer; | |||
| @@ -0,0 +1,45 @@ | |||
| import createReducer from "../../utils/createReducer"; | |||
| import { | |||
| ADD_LOADER, | |||
| REMOVE_LOADER, | |||
| } from "../../actions/app/appActionConstants"; | |||
| const initialState = { | |||
| loaderCount: 0, | |||
| }; | |||
| export default createReducer( | |||
| { | |||
| [ADD_LOADER]: addLoader, | |||
| [REMOVE_LOADER]: removeLoader, | |||
| }, | |||
| initialState | |||
| ); | |||
| function addLoader(state, action) { | |||
| let loaderCount = state.loaderCount; | |||
| let actionType = action.payload.replace("[FETCH]", ""); | |||
| if (!state[actionType]) { | |||
| loaderCount++; | |||
| } | |||
| return { | |||
| ...state, | |||
| [actionType]: true, | |||
| loaderCount, | |||
| }; | |||
| } | |||
| function removeLoader(state, action) { | |||
| let actionType = action.payload | |||
| .replace("[SUCCESS]", "") | |||
| .replace("[ERROR]", ""); | |||
| let loaderCount = state.loaderCount; | |||
| if (state[actionType] === true) { | |||
| loaderCount--; | |||
| } | |||
| return { | |||
| ...state, | |||
| [actionType]: false, | |||
| loaderCount, | |||
| }; | |||
| } | |||
| @@ -0,0 +1,69 @@ | |||
| import createReducer from '../../utils/createReducer'; | |||
| import { | |||
| CLEAR_LOGIN_USER_ERROR, | |||
| LOGIN_USER_ERROR, | |||
| LOGIN_USER_SUCCESS, | |||
| RESET_LOGIN_STATE, | |||
| UPDATE_USER_JWT_TOKEN, | |||
| } from '../../actions/login/loginActionConstants'; | |||
| const initialState = { | |||
| email: '', | |||
| token: { | |||
| JwtRefreshToken: '', | |||
| JwtToken: '', | |||
| }, | |||
| errorMessage: '', | |||
| }; | |||
| export default createReducer( | |||
| { | |||
| [LOGIN_USER_SUCCESS]: setUser, | |||
| [UPDATE_USER_JWT_TOKEN]: setUserJwtToken, | |||
| [RESET_LOGIN_STATE]: resetLoginState, | |||
| [LOGIN_USER_ERROR]: setError, | |||
| [CLEAR_LOGIN_USER_ERROR]: clearLoginErrors, | |||
| }, | |||
| initialState, | |||
| ); | |||
| function setUser(state, action) { | |||
| return { | |||
| ...state, | |||
| token: { | |||
| ...state.token, | |||
| JwtToken: action.payload.jwt, | |||
| JwtRefreshToken: action.payload.refreshToken | |||
| }, | |||
| }; | |||
| } | |||
| function setUserJwtToken(state, action) { | |||
| return { | |||
| ...state, | |||
| token: { | |||
| ...state.token, | |||
| JwtToken: action.payload, | |||
| }, | |||
| }; | |||
| } | |||
| function setError(state, action) { | |||
| return { | |||
| ...state, | |||
| errorMessage: action.payload, | |||
| }; | |||
| } | |||
| function resetLoginState() { | |||
| return initialState; | |||
| } | |||
| function clearLoginErrors(state) { | |||
| return { | |||
| ...state, | |||
| errorMessage: '', | |||
| }; | |||
| } | |||
| @@ -0,0 +1,54 @@ | |||
| import createReducer from '../../utils/createReducer'; | |||
| import { | |||
| CLEAR_REGISTER_USER_ERROR, | |||
| REGISTER_USER_ERROR, | |||
| REGISTER_USER_SUCCESS, | |||
| RESET_REGISTER_STATE, | |||
| } from '../../actions/register/registerActionConstants'; | |||
| const initialState = { | |||
| token: { | |||
| JwtToken: '', | |||
| }, | |||
| errorMessage: '', | |||
| }; | |||
| export default createReducer( | |||
| { | |||
| [REGISTER_USER_SUCCESS]: setUser, | |||
| [RESET_REGISTER_STATE]: resetRegisterState, | |||
| [REGISTER_USER_ERROR]: setError, | |||
| [CLEAR_REGISTER_USER_ERROR]: clearRegisterErrors, | |||
| }, | |||
| initialState, | |||
| ); | |||
| function setUser(state, action) { | |||
| return { | |||
| ...state, | |||
| token: { | |||
| ...state.token, | |||
| JwtToken: action.payload.jwt, | |||
| }, | |||
| }; | |||
| } | |||
| function setError(state, action) { | |||
| return { | |||
| ...state, | |||
| errorMessage: action.payload, | |||
| }; | |||
| } | |||
| function resetRegisterState() { | |||
| return initialState; | |||
| } | |||
| function clearRegisterErrors(state) { | |||
| return { | |||
| ...state, | |||
| errorMessage: '', | |||
| }; | |||
| } | |||
| @@ -1,14 +0,0 @@ | |||
| import { createReducer } from '@reduxjs/toolkit' | |||
| import { increment, decrement } from '../actions/index' | |||
| export default createReducer({ | |||
| value: 0 | |||
| }, builder => { | |||
| builder | |||
| .addCase(increment, (state) => { | |||
| state.value += 1 | |||
| }) | |||
| .addCase(decrement, (state) => { | |||
| state.value -= 1 | |||
| }) | |||
| }) | |||
| @@ -1,36 +0,0 @@ | |||
| import {createReducer} from '@reduxjs/toolkit' | |||
| import { setToken, setRefreshToken, authLoaded, logOut, setUsername, logInSuccess, setConnectionError } from "../actions/index"; | |||
| export default createReducer({ | |||
| token: null, | |||
| refreshToken: null, | |||
| username: "", | |||
| logInSuccess: "", | |||
| connectionError: "", | |||
| isAuthLoadedFromStorage: false | |||
| }, builder => { | |||
| builder | |||
| .addCase(setToken, (state, { payload }) => { | |||
| state.token = payload | |||
| }) | |||
| .addCase(setRefreshToken, (state, { payload }) => { | |||
| state.refreshToken = payload | |||
| }) | |||
| .addCase(authLoaded, (state) => { | |||
| state.isAuthLoadedFromStorage = true | |||
| }) | |||
| .addCase(logOut, (state) => { | |||
| state.username = null; | |||
| state.token = null; | |||
| state.refreshToken = null; | |||
| }) | |||
| .addCase(setUsername, (state, { payload }) => { | |||
| state.username = payload | |||
| }) | |||
| .addCase(logInSuccess, (state, { payload }) => { | |||
| state.logInSuccess = payload | |||
| }) | |||
| .addCase(setConnectionError, (state) => { | |||
| state.connectionError = payload | |||
| }) | |||
| }) | |||
| @@ -0,0 +1,31 @@ | |||
| import createReducer from '../../utils/createReducer'; | |||
| import { | |||
| SET_USER, | |||
| SET_USER_ERROR, | |||
| } from '../../actions/user/userActionConstants'; | |||
| const initialState = { | |||
| user: {}, | |||
| }; | |||
| export default createReducer( | |||
| { | |||
| [SET_USER]: setUser, | |||
| [SET_USER_ERROR]: setUserError, | |||
| }, | |||
| initialState, | |||
| ); | |||
| function setUser(state, action) { | |||
| return { | |||
| ...state, | |||
| user: action.payload, | |||
| }; | |||
| } | |||
| function setUserError(state, action) { | |||
| return { | |||
| ...state, | |||
| errorMessage: action.payload, | |||
| }; | |||
| } | |||
| @@ -0,0 +1,42 @@ | |||
| import { all, call, put, takeLatest } from "@redux-saga/core/effects"; | |||
| import { attemptAuthProvider } from "../../request/loginRequest"; | |||
| import { | |||
| fetchUserError, | |||
| fetchUserSuccess, | |||
| } from "../actions/login/loginActions"; | |||
| import { setUser } from "../actions/user/userActions"; | |||
| import { addHeaderToken } from "../../request"; | |||
| import { ACCESS_TOKEN, JWT_REFRESH_TOKEN, JWT_TOKEN } from "../../constants/localStorage"; | |||
| import { storeData } from "../../service/asyncStorage"; | |||
| import { rejectErrorCodeHelper } from "../../utils/rejectErrorMessageHelper"; | |||
| import { AUTH_PROVIDER_FETCH } from "../actions/authProvider/authProviderActionConstants"; | |||
| import { | |||
| fetchAuthProviderError, | |||
| fetchAuthProviderSuccess, | |||
| } from "../actions/authProvider/authProviderActions"; | |||
| function* fetchAuthProvider({ payload }) { | |||
| try { | |||
| const { data } = yield call(attemptAuthProvider, payload.accessToken); | |||
| if (data?.jwt) { | |||
| const user = data?.user; | |||
| yield call(storeData, JWT_TOKEN, data.jwt); | |||
| yield call(storeData, JWT_REFRESH_TOKEN, data?.refreshToken); | |||
| yield call(storeData, ACCESS_TOKEN, payload.accessToken); | |||
| yield call(addHeaderToken, data?.jwt); | |||
| yield put(setUser(user)); | |||
| } | |||
| yield put(fetchUserSuccess(data)); | |||
| yield put(fetchAuthProviderSuccess("Success")); | |||
| } catch (e) { | |||
| if (e.response && e.response.data) { | |||
| const errorMessage = yield call(rejectErrorCodeHelper, e); | |||
| yield put(fetchUserError(errorMessage)); | |||
| yield put(fetchAuthProviderError('Error')); | |||
| } | |||
| } | |||
| } | |||
| export default function* authProviderSaga() { | |||
| yield all([takeLatest(AUTH_PROVIDER_FETCH, fetchAuthProvider)]); | |||
| } | |||
| @@ -0,0 +1,8 @@ | |||
| import { all } from "redux-saga/effects"; | |||
| import loginSaga from "./loginSaga"; | |||
| import registerSaga from "./registerSaga"; | |||
| import authProviderSaga from "./authProviderSaga"; | |||
| export default function* rootSaga() { | |||
| yield all([loginSaga(), registerSaga(), authProviderSaga()]); | |||
| } | |||
| @@ -0,0 +1,80 @@ | |||
| import { all, call, put, takeLatest } from "@redux-saga/core/effects"; | |||
| import jwt_decode from "jwt-decode"; | |||
| import { | |||
| LOGIN_USER_FETCH, | |||
| LOGOUT_USER, | |||
| REFRESH_TOKEN, | |||
| } from "../actions/login/loginActionConstants"; | |||
| import { attemptLogin, logoutUserRequest } from "../../request/loginRequest"; | |||
| import { | |||
| fetchUserError, | |||
| fetchUserSuccess, | |||
| resetLoginState, | |||
| } from "../actions/login/loginActions"; | |||
| import { setUser } from "../actions/user/userActions"; | |||
| import { addHeaderToken, removeHeaderToken } from "../../request"; | |||
| import { | |||
| JWT_REFRESH_TOKEN, | |||
| JWT_TOKEN, | |||
| } from "../../constants/localStorage"; | |||
| import { storeData, getData, clearAll } from "../../service/asyncStorage"; | |||
| import { rejectErrorCodeHelper } from "../../utils/rejectErrorMessageHelper"; | |||
| function* fetchUser({ payload }) { | |||
| try { | |||
| const { data } = yield call(attemptLogin, payload); | |||
| if (data?.jwt) { | |||
| const user = data?.user; | |||
| yield call(storeData, JWT_TOKEN, data.jwt); | |||
| yield call(storeData, JWT_REFRESH_TOKEN, data?.refreshToken); | |||
| yield call(addHeaderToken, data?.jwt); | |||
| yield put(setUser(user)); | |||
| } | |||
| yield put(fetchUserSuccess(data)); | |||
| } catch (e) { | |||
| if (e.response && e.response.data) { | |||
| const errorMessage = yield call(rejectErrorCodeHelper, e); | |||
| yield put(fetchUserError(errorMessage)); | |||
| } | |||
| } | |||
| } | |||
| function* logoutUser() { | |||
| try { | |||
| const token = yield call(getData, JWT_REFRESH_TOKEN); | |||
| const user = yield call(jwt_decode, token); | |||
| if (user) { | |||
| yield call(logoutUserRequest); | |||
| } | |||
| } catch (error) { | |||
| console.log(error); // eslint-disable-line | |||
| } finally { | |||
| yield call(removeHeaderToken); | |||
| yield call(clearAll); | |||
| yield put(resetLoginState()); | |||
| } | |||
| } | |||
| export function* refreshToken({ payload }) { | |||
| try { | |||
| const newTokenDecoded = jwt_decode(payload.jwt); | |||
| yield call(storeData, JWT_TOKEN, payload.jwt); | |||
| yield call(storeData, JWT_REFRESH_TOKEN, payload.refreshToken); | |||
| addHeaderToken(payload.jwt); | |||
| yield put(setUser(newTokenDecoded)); | |||
| yield put(fetchUserSuccess(payload)); | |||
| return true; | |||
| } catch (error) { | |||
| console.log(error); // eslint-disable-line | |||
| return false; | |||
| } | |||
| } | |||
| export default function* loginSaga() { | |||
| yield all([ | |||
| takeLatest(LOGIN_USER_FETCH, fetchUser), | |||
| takeLatest(LOGOUT_USER, logoutUser), | |||
| takeLatest(REFRESH_TOKEN, refreshToken), | |||
| ]); | |||
| } | |||
| @@ -0,0 +1,32 @@ | |||
| import { all, call, put, takeLatest } from "@redux-saga/core/effects"; | |||
| import { REGISTER_USER_FETCH } from "../actions/register/registerActionConstants"; | |||
| import { attemptRegister } from "../../request/loginRequest"; | |||
| import { | |||
| registerUserError, | |||
| registerUserSuccess, | |||
| } from "../actions/register/registerActions"; | |||
| import { JWT_TOKEN } from "../../constants/localStorage"; | |||
| import { storeData } from "../../service/asyncStorage"; | |||
| import { rejectErrorCodeHelper } from "../../utils/rejectErrorMessageHelper"; | |||
| function* registerUser({ payload }) { | |||
| try { | |||
| const { data } = yield call(attemptRegister, payload); | |||
| if (data?.jwt) { | |||
| yield call(storeData, JWT_TOKEN, data.jwt); | |||
| } | |||
| yield put(registerUserSuccess(data.jwt)); | |||
| if (payload.handleApiResponseSuccess) { | |||
| yield call(payload.handleApiResponseSuccess); | |||
| } | |||
| } catch (e) { | |||
| if (e.response && e.response.data) { | |||
| const errorMessage = yield call(rejectErrorCodeHelper, e); | |||
| yield put(registerUserError(errorMessage)); | |||
| } | |||
| } | |||
| } | |||
| export default function* registerSaga() { | |||
| yield all([takeLatest(REGISTER_USER_FETCH, registerUser)]); | |||
| } | |||
| @@ -0,0 +1,8 @@ | |||
| import createSelector from "reselect"; | |||
| const authProviderSelector = (state) => state.authProvider; | |||
| export const selectLoginError = createSelector( | |||
| authProviderSelector, | |||
| (state) => state.errorMessage | |||
| ); | |||
| @@ -0,0 +1,16 @@ | |||
| import { createSelector } from "reselect"; | |||
| const loadingSelector = (state) => state.loading; | |||
| export const selectIsLoadingByActionType = (loadingActionType) => | |||
| createSelector(loadingSelector, (state) => state[`${loadingActionType}`]); | |||
| export const selectIsLoadingByActionTypes = (actionTypes) => | |||
| createSelector(loadingSelector, (state) => | |||
| actionTypes.some((actionType) => state[`${actionType}`]) | |||
| ); | |||
| export const selectLoaderCount = createSelector( | |||
| loadingSelector, | |||
| (state) => state.loaderCount | |||
| ); | |||
| @@ -0,0 +1,29 @@ | |||
| import { createSelector } from 'reselect'; | |||
| const loginSelector = (state) => state.login; | |||
| export const selectLoginEmail = createSelector( | |||
| loginSelector, | |||
| (state) => state.email, | |||
| ); | |||
| export const selectUsernames = createSelector( | |||
| loginSelector, | |||
| (state) => state.usernames, | |||
| ); | |||
| export const selectTokens = createSelector( | |||
| loginSelector, | |||
| (state) => state.token, | |||
| ); | |||
| export const selectJWTToken = createSelector( | |||
| loginSelector, | |||
| (state) => state.token.JwtToken, | |||
| ); | |||
| export const selectLoginError = createSelector( | |||
| loginSelector, | |||
| (state) => state.errorMessage, | |||
| ); | |||
| @@ -0,0 +1,8 @@ | |||
| import { createSelector } from 'reselect'; | |||
| const registerSelector = (state) => state.register; | |||
| export const selectRegisterError = createSelector( | |||
| registerSelector, | |||
| (state) => state.errorMessage, | |||
| ); | |||
| @@ -0,0 +1,28 @@ | |||
| import { createSelector } from 'reselect'; | |||
| export const userSelector = (state) => state.user; | |||
| export const selectAuthUser = createSelector( | |||
| userSelector, | |||
| (state) => state.user, | |||
| ); | |||
| export const getUserSecurityQuestion = createSelector( | |||
| userSelector, | |||
| (state) => state.securityQuestion, | |||
| ); | |||
| export const getForgotPasswordRequest = createSelector( | |||
| userSelector, | |||
| (state) => state.user, | |||
| ); | |||
| export const selectForgotPasswordError = createSelector( | |||
| userSelector, | |||
| (state) => state.errorMessage, | |||
| ); | |||
| export const getResetPasswordRequest = createSelector( | |||
| userSelector, | |||
| (state) => state.user, | |||
| ); | |||
| @@ -0,0 +1,13 @@ | |||
| const createReducer = (handlers, initialState) => { | |||
| return (state = initialState, action) => { | |||
| const reducer = handlers[action.type]; | |||
| if (!reducer) { | |||
| return state; | |||
| } | |||
| return reducer(state, action); | |||
| }; | |||
| }; | |||
| export default createReducer; | |||
| @@ -1,36 +0,0 @@ | |||
| import { batchActions } from "redux-batched-actions"; | |||
| import { removeData, storeData } from "../service/asyncStorage"; | |||
| import { loginApi } from "../service/user"; | |||
| import { logInSuccess, setToken, setUsername } from "../store/actions"; | |||
| export const login = (email, password, callback) => { | |||
| return async (dispatch) => { | |||
| const result = await loginApi({ identifier: email, password }); | |||
| if (result.data === null) { | |||
| callback(result); | |||
| }else { | |||
| await removeData("TOKEN"); | |||
| const token = await storeData('TOKEN', result.jwt); | |||
| console.log(result.user); | |||
| if (token) { | |||
| dispatch(batchActions([ | |||
| setToken(result.jwt), | |||
| setUsername(result.user.username), | |||
| logInSuccess(true) | |||
| ])) | |||
| } | |||
| } | |||
| }; | |||
| }; | |||
| export const logout = () => { | |||
| return async (dispatch) => { | |||
| await removeData('TOKEN'); | |||
| dispatch(batchActions([ | |||
| setToken(null), | |||
| setUsername(null), | |||
| logInSuccess(false) | |||
| ])) | |||
| }; | |||
| }; | |||
| @@ -0,0 +1,33 @@ | |||
| import React from "react"; | |||
| import * as Google from "expo-auth-session/providers/google"; | |||
| import { getData } from "../../service/asyncStorage"; | |||
| import { ACCESS_TOKEN } from "../../constants/localStorage"; | |||
| import { revokeAsync } from "expo-auth-session"; | |||
| import variables from "../variables"; | |||
| const useAuthHook = () => { | |||
| const [request, response, promptAsync] = Google.useAuthRequest({ | |||
| androidClientId: variables.androidClientId, | |||
| iosClientId: variables.iosClientId, | |||
| expoClientId: variables.expoClientId, | |||
| }); | |||
| const logoutAuthProvider = async () => { | |||
| const token = await getData(ACCESS_TOKEN); | |||
| if (token !== null) { | |||
| await revokeAsync( | |||
| { token: token }, | |||
| { revocationEndpoint: variables.revocationEndpoint } | |||
| ); | |||
| } | |||
| }; | |||
| return { | |||
| request, | |||
| response, | |||
| promptAsync, | |||
| logoutAuthProvider, | |||
| }; | |||
| }; | |||
| export default useAuthHook; | |||
| @@ -0,0 +1,7 @@ | |||
| export const rejectErrorCodeHelper = (error) => { | |||
| if (error?.response?.data?.error) { | |||
| const errorMessage = error?.response?.data?.error.message; | |||
| return errorMessage; | |||
| } | |||
| }; | |||
| @@ -0,0 +1,9 @@ | |||
| export default { | |||
| androidClientId: | |||
| "1032296921439-dpgvgkss3ggds0egvo6puf0r7un9em6c.apps.googleusercontent.com", | |||
| iosClientId: | |||
| "1032296921439-j7jfahhm7q3l07aj04qvdblmcns77oul.apps.googleusercontent.com", | |||
| expoClientId: | |||
| "1032296921439-vfs9k28sn7kei4nft998ck3067m8ksiq.apps.googleusercontent.com", | |||
| revocationEndpoint: "https://oauth2.googleapis.com/revoke", | |||
| }; | |||
| @@ -1017,13 +1017,20 @@ | |||
| pirates "^4.0.5" | |||
| source-map-support "^0.5.16" | |||
| "@babel/runtime@^7.0.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.14.0", "@babel/runtime@^7.15.4", "@babel/runtime@^7.18.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": | |||
| "@babel/runtime@^7.0.0", "@babel/runtime@^7.14.0", "@babel/runtime@^7.15.4", "@babel/runtime@^7.18.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": | |||
| version "7.20.6" | |||
| resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.6.tgz#facf4879bfed9b5326326273a64220f099b0fce3" | |||
| integrity sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA== | |||
| dependencies: | |||
| regenerator-runtime "^0.13.11" | |||
| "@babel/runtime@^7.6.3": | |||
| version "7.20.7" | |||
| resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.7.tgz#fcb41a5a70550e04a7b708037c7c32f7f356d8fd" | |||
| integrity sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ== | |||
| dependencies: | |||
| regenerator-runtime "^0.13.11" | |||
| "@babel/template@^7.0.0", "@babel/template@^7.18.10": | |||
| version "7.18.10" | |||
| resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.10.tgz#6f9134835970d1dbf0835c0d100c9f38de0c5e71" | |||
| @@ -1820,6 +1827,50 @@ | |||
| dependencies: | |||
| nanoid "^3.1.23" | |||
| "@redux-saga/core@^1.2.2": | |||
| version "1.2.2" | |||
| resolved "https://registry.yarnpkg.com/@redux-saga/core/-/core-1.2.2.tgz#99b1daac93a42feecd9bab449f452f56f3155fea" | |||
| integrity sha512-0qr5oleOAmI5WoZLRA6FEa30M4qKZcvx+ZQOQw+RqFeH8t20bvhE329XSPsNfTVP8C6qyDsXOSjuoV+g3+8zkg== | |||
| dependencies: | |||
| "@babel/runtime" "^7.6.3" | |||
| "@redux-saga/deferred" "^1.2.1" | |||
| "@redux-saga/delay-p" "^1.2.1" | |||
| "@redux-saga/is" "^1.1.3" | |||
| "@redux-saga/symbols" "^1.1.3" | |||
| "@redux-saga/types" "^1.2.1" | |||
| redux "^4.0.4" | |||
| typescript-tuple "^2.2.1" | |||
| "@redux-saga/deferred@^1.2.1": | |||
| version "1.2.1" | |||
| resolved "https://registry.yarnpkg.com/@redux-saga/deferred/-/deferred-1.2.1.tgz#aca373a08ccafd6f3481037f2f7ee97f2c87c3ec" | |||
| integrity sha512-cmin3IuuzMdfQjA0lG4B+jX+9HdTgHZZ+6u3jRAOwGUxy77GSlTi4Qp2d6PM1PUoTmQUR5aijlA39scWWPF31g== | |||
| "@redux-saga/delay-p@^1.2.1": | |||
| version "1.2.1" | |||
| resolved "https://registry.yarnpkg.com/@redux-saga/delay-p/-/delay-p-1.2.1.tgz#e72ac4731c5080a21f75b61bedc31cb639d9e446" | |||
| integrity sha512-MdiDxZdvb1m+Y0s4/hgdcAXntpUytr9g0hpcOO1XFVyyzkrDu3SKPgBFOtHn7lhu7n24ZKIAT1qtKyQjHqRd+w== | |||
| dependencies: | |||
| "@redux-saga/symbols" "^1.1.3" | |||
| "@redux-saga/is@^1.1.3": | |||
| version "1.1.3" | |||
| resolved "https://registry.yarnpkg.com/@redux-saga/is/-/is-1.1.3.tgz#b333f31967e87e32b4e6b02c75b78d609dd4ad73" | |||
| integrity sha512-naXrkETG1jLRfVfhOx/ZdLj0EyAzHYbgJWkXbB3qFliPcHKiWbv/ULQryOAEKyjrhiclmr6AMdgsXFyx7/yE6Q== | |||
| dependencies: | |||
| "@redux-saga/symbols" "^1.1.3" | |||
| "@redux-saga/types" "^1.2.1" | |||
| "@redux-saga/symbols@^1.1.3": | |||
| version "1.1.3" | |||
| resolved "https://registry.yarnpkg.com/@redux-saga/symbols/-/symbols-1.1.3.tgz#b731d56201719e96dc887dc3ae9016e761654367" | |||
| integrity sha512-hCx6ZvU4QAEUojETnX8EVg4ubNLBFl1Lps4j2tX7o45x/2qg37m3c6v+kSp8xjDJY+2tJw4QB3j8o8dsl1FDXg== | |||
| "@redux-saga/types@^1.2.1": | |||
| version "1.2.1" | |||
| resolved "https://registry.yarnpkg.com/@redux-saga/types/-/types-1.2.1.tgz#9403f51c17cae37edf870c6bc0c81c1ece5ccef8" | |||
| integrity sha512-1dgmkh+3so0+LlBWRhGA33ua4MYr7tUOj+a9Si28vUi0IUFNbff1T3sgpeDJI/LaC75bBYnQ0A3wXjn0OrRNBA== | |||
| "@reduxjs/toolkit@^1.9.1": | |||
| version "1.9.1" | |||
| resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.9.1.tgz#4c34dc4ddcec161535288c60da5c19c3ef15180e" | |||
| @@ -1962,7 +2013,7 @@ | |||
| resolved "https://registry.yarnpkg.com/@types/hammerjs/-/hammerjs-2.0.41.tgz#f6ecf57d1b12d2befcce00e928a6a097c22980aa" | |||
| integrity sha512-ewXv/ceBaJprikMcxCmWU1FKyMAQ2X7a9Gtmzw8fcg2kIePI1crERDM818W+XYrxqdBBOdlf2rm137bU+BltCA== | |||
| "@types/hoist-non-react-statics@^3.3.1": | |||
| "@types/hoist-non-react-statics@^3.3.0": | |||
| version "3.3.1" | |||
| resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" | |||
| integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA== | |||
| @@ -2019,6 +2070,16 @@ | |||
| resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" | |||
| integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== | |||
| "@types/react-redux@^7.1.20": | |||
| version "7.1.25" | |||
| resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.25.tgz#de841631205b24f9dfb4967dd4a7901e048f9a88" | |||
| integrity sha512-bAGh4e+w5D8dajd6InASVIyCo4pZLJ66oLb80F9OBLO1gKESbZcRCJpTT6uLXX+HAB57zw1WTdwJdAsewuTweg== | |||
| dependencies: | |||
| "@types/hoist-non-react-statics" "^3.3.0" | |||
| "@types/react" "*" | |||
| hoist-non-react-statics "^3.3.0" | |||
| redux "^4.0.0" | |||
| "@types/react@*": | |||
| version "18.0.26" | |||
| resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.26.tgz#8ad59fc01fef8eaf5c74f4ea392621749f0b7917" | |||
| @@ -2033,11 +2094,6 @@ | |||
| resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" | |||
| integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== | |||
| "@types/use-sync-external-store@^0.0.3": | |||
| version "0.0.3" | |||
| resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz#b6725d5f4af24ace33b36fafd295136e75509f43" | |||
| integrity sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA== | |||
| "@types/yargs-parser@*": | |||
| version "21.0.0" | |||
| resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" | |||
| @@ -3453,14 +3509,14 @@ expo-asset@~8.6.2: | |||
| path-browserify "^1.0.0" | |||
| url-parse "^1.5.9" | |||
| expo-auth-session@~3.7.3: | |||
| version "3.7.3" | |||
| resolved "https://registry.yarnpkg.com/expo-auth-session/-/expo-auth-session-3.7.3.tgz#ded1afbbf88e0c6e4098e59920b4f13927fc1f3d" | |||
| integrity sha512-0mX47j6WdpEoaFVxU36VBRkEjJKwAkqnRyNdd5g+Ab1fHXQJJK3nzjOjxuBzgE8mKSS9NGHr/At9IGY6MLrg2g== | |||
| expo-auth-session@~3.8.0: | |||
| version "3.8.0" | |||
| resolved "https://registry.yarnpkg.com/expo-auth-session/-/expo-auth-session-3.8.0.tgz#ad929f1bcc2e1dc9d0b265f9555d3f967bda6bcc" | |||
| integrity sha512-pQ8GryTTZL/JKHvifUGD4GGlZWo7RrcoQlvQ0O5m5edYfoa7fMHCg20MBX4Da4P3eVgJlqWZWCHfBC2fZxcRfA== | |||
| dependencies: | |||
| expo-constants "~14.0.0" | |||
| expo-crypto "~12.0.0" | |||
| expo-linking "~3.2.0" | |||
| expo-linking "~3.3.0" | |||
| expo-web-browser "~12.0.0" | |||
| invariant "^2.2.4" | |||
| qs "6.9.1" | |||
| @@ -3502,10 +3558,10 @@ expo-keep-awake@~11.0.1: | |||
| resolved "https://registry.yarnpkg.com/expo-keep-awake/-/expo-keep-awake-11.0.1.tgz#ee354465892a94040ffe09901b85b469e7d54fb3" | |||
| integrity sha512-44ZjgLE4lnce2d40Pv8xsjMVc6R5GvgHOwZfkLYtGmgYG9TYrEJeEj5UfSeweXPL3pBFhXKfFU8xpGYMaHdP0A== | |||
| expo-linking@~3.2.0: | |||
| version "3.2.3" | |||
| resolved "https://registry.yarnpkg.com/expo-linking/-/expo-linking-3.2.3.tgz#7b493a7fea2aadafc88a42e2fc6a5a4ba6d47df9" | |||
| integrity sha512-PgiWCao9TecLOPdtWyiNSY+UQGAwdjFx4KbHd1YsF0KnM1CJ2idcaHpDRlQPWSNmDebUZYN461/dVtJi9b2krg== | |||
| expo-linking@~3.3.0: | |||
| version "3.3.0" | |||
| resolved "https://registry.yarnpkg.com/expo-linking/-/expo-linking-3.3.0.tgz#3860af9da374f0187db75036f353ca6fe9231a15" | |||
| integrity sha512-wXPzI2kijnql2L2F6i8zP1zINTkYlcRXyh1iV3P6Bt57v6yZiiniZBnb6grJVj19LOmluNs0PYrbX1ZsHBChCg== | |||
| dependencies: | |||
| "@types/qs" "^6.5.3" | |||
| expo-constants "~14.0.0" | |||
| @@ -4692,6 +4748,11 @@ jsonfile@^6.0.1: | |||
| optionalDependencies: | |||
| graceful-fs "^4.1.6" | |||
| jwt-decode@^3.1.2: | |||
| version "3.1.2" | |||
| resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-3.1.2.tgz#3fb319f3675a2df0c2895c8f5e9fa4b67b04ed59" | |||
| integrity sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A== | |||
| kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: | |||
| version "3.2.2" | |||
| resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" | |||
| @@ -4775,6 +4836,11 @@ lodash.filter@^4.6.0: | |||
| resolved "https://registry.yarnpkg.com/lodash.filter/-/lodash.filter-4.6.0.tgz#668b1d4981603ae1cc5a6fa760143e480b4c4ace" | |||
| integrity sha512-pXYUy7PR8BCLwX5mgJ/aNtyOvuJTdZAo9EQFUvMIYugqmJxnrYaANvTbgndOzHSCSR0wnlBBfRXJL5SbWxo3FQ== | |||
| lodash.isempty@^4.4.0: | |||
| version "4.4.0" | |||
| resolved "https://registry.yarnpkg.com/lodash.isempty/-/lodash.isempty-4.4.0.tgz#6f86cbedd8be4ec987be9aaf33c9684db1b31e7e" | |||
| integrity sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg== | |||
| lodash.isequal@^4.5.0: | |||
| version "4.5.0" | |||
| resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" | |||
| @@ -6032,7 +6098,7 @@ react-freeze@^1.0.0: | |||
| resolved "https://registry.yarnpkg.com/react-freeze/-/react-freeze-1.0.3.tgz#5e3ca90e682fed1d73a7cb50c2c7402b3e85618d" | |||
| integrity sha512-ZnXwLQnGzrDpHBHiC56TXFXvmolPeMjTn1UOm610M4EXGzbEDR7oOIyS2ZiItgbs6eZc4oU/a0hpk8PrcKvv5g== | |||
| "react-is@^16.12.0 || ^17.0.0 || ^18.0.0", react-is@^18.0.0: | |||
| "react-is@^16.12.0 || ^17.0.0 || ^18.0.0": | |||
| version "18.2.0" | |||
| resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" | |||
| integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== | |||
| @@ -6042,7 +6108,7 @@ react-is@^16.13.0, react-is@^16.13.1, react-is@^16.7.0: | |||
| resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" | |||
| integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== | |||
| react-is@^17.0.1: | |||
| react-is@^17.0.1, react-is@^17.0.2: | |||
| version "17.0.2" | |||
| resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" | |||
| integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== | |||
| @@ -6205,17 +6271,17 @@ react-navigation-stack@^2.10.4: | |||
| color "^3.1.3" | |||
| react-native-iphone-x-helper "^1.3.0" | |||
| react-redux@^8.0.5: | |||
| version "8.0.5" | |||
| resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-8.0.5.tgz#e5fb8331993a019b8aaf2e167a93d10af469c7bd" | |||
| integrity sha512-Q2f6fCKxPFpkXt1qNRZdEDLlScsDWyrgSj0mliK59qU6W5gvBiKkdMEG2lJzhd1rCctf0hb6EtePPLZ2e0m1uw== | |||
| react-redux@^7.2.4: | |||
| version "7.2.9" | |||
| resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.9.tgz#09488fbb9416a4efe3735b7235055442b042481d" | |||
| integrity sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ== | |||
| dependencies: | |||
| "@babel/runtime" "^7.12.1" | |||
| "@types/hoist-non-react-statics" "^3.3.1" | |||
| "@types/use-sync-external-store" "^0.0.3" | |||
| "@babel/runtime" "^7.15.4" | |||
| "@types/react-redux" "^7.1.20" | |||
| hoist-non-react-statics "^3.3.2" | |||
| react-is "^18.0.0" | |||
| use-sync-external-store "^1.0.0" | |||
| loose-envify "^1.4.0" | |||
| prop-types "^15.7.2" | |||
| react-is "^17.0.2" | |||
| react-refresh@^0.4.0: | |||
| version "0.4.3" | |||
| @@ -6274,17 +6340,19 @@ recast@^0.20.4: | |||
| source-map "~0.6.1" | |||
| tslib "^2.0.1" | |||
| redux-batched-actions@^0.5.0: | |||
| version "0.5.0" | |||
| resolved "https://registry.yarnpkg.com/redux-batched-actions/-/redux-batched-actions-0.5.0.tgz#d3f0e359b2a95c7d80bab442df450bfafd57d122" | |||
| integrity sha512-6orZWyCnIQXMGY4DUGM0oj0L7oYnwTACsfsru/J7r94RM3P9eS7SORGpr3LCeRCMoIMQcpfKZ7X4NdyFHBS8Eg== | |||
| redux-saga@^1.1.3: | |||
| version "1.2.2" | |||
| resolved "https://registry.yarnpkg.com/redux-saga/-/redux-saga-1.2.2.tgz#4b9b30e022cf94ed1450605e9afd45998c3e8ac1" | |||
| integrity sha512-6xAHWgOqRP75MFuLq88waKK9/+6dCdMQjii2TohDMARVHeQ6HZrZoJ9HZ3dLqMWCZ9kj4iuS6CDsujgnovn11A== | |||
| dependencies: | |||
| "@redux-saga/core" "^1.2.2" | |||
| redux-thunk@^2.4.2: | |||
| version "2.4.2" | |||
| resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.2.tgz#b9d05d11994b99f7a91ea223e8b04cf0afa5ef3b" | |||
| integrity sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q== | |||
| redux@^4.2.0: | |||
| redux@^4.0.0, redux@^4.0.4, redux@^4.1.0, redux@^4.2.0: | |||
| version "4.2.0" | |||
| resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.0.tgz#46f10d6e29b6666df758780437651eeb2b969f13" | |||
| integrity sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA== | |||
| @@ -7247,6 +7315,25 @@ type-is@~1.6.17: | |||
| media-typer "0.3.0" | |||
| mime-types "~2.1.24" | |||
| typescript-compare@^0.0.2: | |||
| version "0.0.2" | |||
| resolved "https://registry.yarnpkg.com/typescript-compare/-/typescript-compare-0.0.2.tgz#7ee40a400a406c2ea0a7e551efd3309021d5f425" | |||
| integrity sha512-8ja4j7pMHkfLJQO2/8tut7ub+J3Lw2S3061eJLFQcvs3tsmJKp8KG5NtpLn7KcY2w08edF74BSVN7qJS0U6oHA== | |||
| dependencies: | |||
| typescript-logic "^0.0.0" | |||
| typescript-logic@^0.0.0: | |||
| version "0.0.0" | |||
| resolved "https://registry.yarnpkg.com/typescript-logic/-/typescript-logic-0.0.0.tgz#66ebd82a2548f2b444a43667bec120b496890196" | |||
| integrity sha512-zXFars5LUkI3zP492ls0VskH3TtdeHCqu0i7/duGt60i5IGPIpAHE/DWo5FqJ6EjQ15YKXrt+AETjv60Dat34Q== | |||
| typescript-tuple@^2.2.1: | |||
| version "2.2.1" | |||
| resolved "https://registry.yarnpkg.com/typescript-tuple/-/typescript-tuple-2.2.1.tgz#7d9813fb4b355f69ac55032e0363e8bb0f04dad2" | |||
| integrity sha512-Zcr0lbt8z5ZdEzERHAMAniTiIKerFCMgd7yjq1fPnDJ43et/k9twIFQMUYff9k5oXcsQ0WpvFcgzK2ZKASoW6Q== | |||
| dependencies: | |||
| typescript-compare "^0.0.2" | |||
| ua-parser-js@^0.7.30: | |||
| version "0.7.32" | |||
| resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.32.tgz#cd8c639cdca949e30fa68c44b7813ef13e36d211" | |||