| @@ -0,0 +1,4 @@ | |||
| { | |||
| "12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true, | |||
| "40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true | |||
| } | |||
| @@ -0,0 +1,13 @@ | |||
| node_modules/ | |||
| .expo/ | |||
| npm-debug.* | |||
| *.jks | |||
| *.p8 | |||
| *.p12 | |||
| *.key | |||
| *.mobileprovision | |||
| *.orig.* | |||
| web-build/ | |||
| # macOS | |||
| .DS_Store | |||
| @@ -0,0 +1,60 @@ | |||
| import 'react-native-gesture-handler'; | |||
| import React from 'react'; | |||
| import { StyleSheet, Text, View } from 'react-native'; | |||
| import { NavigationContainer } from '@react-navigation/native'; | |||
| import { createBottomTabNavigator } from "@react-navigation/bottom-tabs"; | |||
| import { Provider } from 'react-redux'; | |||
| import { SafeAreaProvider } from 'react-native-safe-area-context'; | |||
| import store from "./store"; | |||
| // import { Counter } from './screens/Counter' | |||
| import { LogIn } from './screens/LogIn'; | |||
| import { createStackNavigator } from '@react-navigation/stack'; | |||
| const A = () => <View><Text>Home</Text></View> | |||
| const B = () => <View><Text>Settings</Text></View> | |||
| const C = () => <View><Text>C</Text></View> | |||
| const Tab = createBottomTabNavigator(); | |||
| const Stack = createStackNavigator(); | |||
| const Home = () => { | |||
| return ( | |||
| <Tab.Navigator> | |||
| <Tab.Screen name="A" component={A} /> | |||
| <Tab.Screen name="B" component={B} /> | |||
| </Tab.Navigator> | |||
| ) | |||
| } | |||
| function App() { | |||
| return ( | |||
| <NavigationContainer> | |||
| <Stack.Navigator> | |||
| <Stack.Screen name="Home" component={Home} /> | |||
| <Stack.Screen name="Profile" component={C} /> | |||
| </Stack.Navigator> | |||
| </NavigationContainer> | |||
| ); | |||
| } | |||
| const AppWrapper = () => { | |||
| return ( | |||
| <Provider store={store}> | |||
| <SafeAreaProvider> | |||
| <App /> | |||
| </SafeAreaProvider> | |||
| </Provider> | |||
| ); | |||
| }; | |||
| const styles = StyleSheet.create({ | |||
| container: { | |||
| flex: 1, | |||
| backgroundColor: '#fff', | |||
| alignItems: 'center', | |||
| justifyContent: 'center', | |||
| }, | |||
| }); | |||
| export default AppWrapper; | |||
| @@ -0,0 +1,32 @@ | |||
| { | |||
| "expo": { | |||
| "name": "native", | |||
| "slug": "native", | |||
| "version": "1.0.0", | |||
| "orientation": "portrait", | |||
| "icon": "./assets/icon.png", | |||
| "splash": { | |||
| "image": "./assets/splash.png", | |||
| "resizeMode": "contain", | |||
| "backgroundColor": "#ffffff" | |||
| }, | |||
| "updates": { | |||
| "fallbackToCacheTimeout": 0 | |||
| }, | |||
| "assetBundlePatterns": [ | |||
| "**/*" | |||
| ], | |||
| "ios": { | |||
| "supportsTablet": true | |||
| }, | |||
| "android": { | |||
| "adaptiveIcon": { | |||
| "foregroundImage": "./assets/adaptive-icon.png", | |||
| "backgroundColor": "#FFFFFF" | |||
| } | |||
| }, | |||
| "web": { | |||
| "favicon": "./assets/favicon.png" | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,6 @@ | |||
| module.exports = function(api) { | |||
| api.cache(true); | |||
| return { | |||
| presets: ['babel-preset-expo'], | |||
| }; | |||
| }; | |||
| @@ -0,0 +1,40 @@ | |||
| import React from 'react' | |||
| import { StyleSheet, Text, TouchableOpacity, View } from "react-native"; | |||
| import { heightPercentageToDP as hp, widthPercentageToDP as wp } from "react-native-responsive-screen"; | |||
| export default Button = ({ onPress, title, style }) => { | |||
| return ( | |||
| <TouchableOpacity | |||
| onPress={onPress} | |||
| style={[{ paddingHorizontal: hp("2.6%"), justifyContent: "space-between" }, styles.widgetBox, style]} | |||
| > | |||
| <View style={[styles.alignCenter, styles.row]}> | |||
| <Text trunk={true} numberOfLines={2} style={{ color: "#4C5A81", fontSize: hp("2.3%"), textAlign: 'center' }}>{title}</Text> | |||
| </View> | |||
| </TouchableOpacity> | |||
| ); | |||
| } | |||
| const styles = StyleSheet.create({ | |||
| alignCenter: { | |||
| alignItems: "center", | |||
| }, | |||
| row: { | |||
| flexDirection: "row", | |||
| }, | |||
| text: { | |||
| color: "#4C5A81", | |||
| fontSize: hp("2.3%"), | |||
| paddingLeft: wp("5%"), | |||
| width: wp("60%"), | |||
| }, | |||
| widgetBox: { | |||
| backgroundColor: "#fff", | |||
| borderRadius: wp("3%"), | |||
| marginVertical: hp("1.3%"), | |||
| paddingVertical: hp("2%"), | |||
| position: "relative", | |||
| flexDirection: "row", | |||
| alignItems: "center", | |||
| }, | |||
| }); | |||
| @@ -0,0 +1,16 @@ | |||
| const { getDefaultConfig } = require("expo/metro-config"); | |||
| module.exports = (async () => { | |||
| const { | |||
| resolver: { sourceExts, assetExts } | |||
| } = await getDefaultConfig(__dirname); | |||
| return { | |||
| transformer: { | |||
| babelTransformerPath: require.resolve("react-native-svg-transformer") | |||
| }, | |||
| resolver: { | |||
| assetExts: assetExts.filter(ext => ext !== "svg"), | |||
| sourceExts: [...sourceExts, "svg"] | |||
| } | |||
| }; | |||
| })(); | |||
| @@ -0,0 +1,37 @@ | |||
| { | |||
| "main": "node_modules/expo/AppEntry.js", | |||
| "scripts": { | |||
| "start": "expo start", | |||
| "android": "expo start --android", | |||
| "ios": "expo start --ios", | |||
| "web": "expo start --web", | |||
| "eject": "expo eject" | |||
| }, | |||
| "dependencies": { | |||
| "@react-native-community/masked-view": "0.1.10", | |||
| "@react-navigation/bottom-tabs": "^5.11.11", | |||
| "@react-navigation/native": "^5.9.4", | |||
| "@reduxjs/toolkit": "^1.5.1", | |||
| "axios": "^0.21.1", | |||
| "expo": "~41.0.1", | |||
| "expo-status-bar": "~1.0.4", | |||
| "react": "16.13.1", | |||
| "react-dom": "16.13.1", | |||
| "react-native": "https://github.com/expo/react-native/archive/sdk-41.0.0.tar.gz", | |||
| "react-native-gesture-handler": "~1.10.2", | |||
| "react-native-reanimated": "~2.1.0", | |||
| "react-native-responsive-screen": "^1.4.2", | |||
| "react-native-safe-area-context": "3.2.0", | |||
| "react-native-screens": "~3.0.0", | |||
| "react-native-svg": "12.1.0", | |||
| "react-native-svg-transformer": "^0.14.3", | |||
| "react-native-web": "~0.13.12", | |||
| "react-navigation-stack": "^2.10.4", | |||
| "react-redux": "^7.2.4", | |||
| "redux-thunk": "^2.3.0" | |||
| }, | |||
| "devDependencies": { | |||
| "@babel/core": "^7.9.0" | |||
| }, | |||
| "private": true | |||
| } | |||
| @@ -0,0 +1,47 @@ | |||
| const npmCheck = require('npm-check') | |||
| const compareVersions = require('compare-versions'); | |||
| const exec = require('child_process').exec; | |||
| npmCheck() | |||
| .then(currentState => { | |||
| packages = currentState.get('packages') | |||
| packages.forEach(p => { | |||
| const { moduleName, latest, installed } = p | |||
| if (moduleName === 'expo' && compareVersions(latest, installed) >= 1) { | |||
| console.log('\x1b[31m', `Latest version of expo is ${latest}, but you are running ${installed}. Please run: expo upgrade`, '\x1b[0m') | |||
| } | |||
| }) | |||
| }) | |||
| .then(() => new Promise((resolve) => { | |||
| exec('node -v', (err, nodeVersion, stdErr) => { | |||
| nodeVersion = nodeVersion.trim().substring(1) | |||
| const supportedNode = '14.17.0' | |||
| if (compareVersions(supportedNode, nodeVersion) >= 1) { | |||
| console.log('\x1b[31m', `You should have node version ${supportedNode} or later. Please download the latest version`, '\x1b[0m') | |||
| process.exit(1) | |||
| } | |||
| resolve() | |||
| }) | |||
| })) | |||
| .then(() => new Promise((resolve) => { | |||
| exec('npm -v', (err, npmVersion, stdErr) => { | |||
| npmVersion = npmVersion.trim() | |||
| const supportedNpm = '7.16.0' | |||
| if (compareVersions(supportedNpm, npmVersion) >= 1) { | |||
| console.log('\x1b[31m', `You should have npm version ${supportedNpm} or later. Please run: npm install -g npm@latest`, '\x1b[0m') | |||
| process.exit(1) | |||
| } | |||
| resolve() | |||
| }) | |||
| })) | |||
| .then(() => new Promise((resolve) => { | |||
| exec('expo-cli --version', (err, expoCliVersion, stdErr) => { | |||
| const supportedExpoCli = '4.5.2' | |||
| expoCliVersion = expoCliVersion.trim() | |||
| if (err || compareVersions(supportedExpoCli, expoCliVersion) >= 1) { | |||
| console.log('\x1b[31m', `You should have globally installed expo-cli version ${supportedExpoCli} or later. Please run: npm i -g expo-cli@latest`, '\x1b[0m') | |||
| process.exit(1) | |||
| } | |||
| resolve() | |||
| }) | |||
| })) | |||
| @@ -0,0 +1,49 @@ | |||
| import React from "react"; | |||
| import { StyleSheet, Text, TouchableOpacity, View } from "react-native"; | |||
| import { useDispatch, useSelector } from "react-redux"; | |||
| import { decrement, increment } from "../store/actions"; | |||
| export const Counter = () => { | |||
| const dispatch = useDispatch() | |||
| const value = useSelector(s => s.test.value) | |||
| return ( | |||
| <View style={[{ width: '75%' }]}> | |||
| <Text style={[styles.textAlignCenter]}>{value}</Text> | |||
| <View style={[styles.row, styles.justifySpaceBetween]}> | |||
| <TouchableOpacity style={[styles.button, styles.justifyCenter]} onPress={() => dispatch(increment())}> | |||
| <Text style={[styles.textAlignCenter]}>Increment</Text> | |||
| </TouchableOpacity> | |||
| <TouchableOpacity style={[styles.button, styles.justifyCenter]} onPress={() => dispatch(decrement())}> | |||
| <Text style={[styles.textAlignCenter]}>Decrement</Text> | |||
| </TouchableOpacity> | |||
| </View> | |||
| </View> | |||
| ) | |||
| } | |||
| const styles = StyleSheet.create({ | |||
| row: { | |||
| flexDirection: "row", | |||
| }, | |||
| flex: { | |||
| flex: 1 | |||
| }, | |||
| justifySpaceBetween: { | |||
| justifyContent: 'space-between' | |||
| }, | |||
| button: { | |||
| backgroundColor: "#D9F1EC", | |||
| padding: '3%', | |||
| marginTop: '10%' | |||
| }, | |||
| textAlignCenter: { | |||
| textAlign: 'center' | |||
| }, | |||
| justifyCenter: { | |||
| justifyContent: 'center' | |||
| }, | |||
| alignCenter: { | |||
| alignItems: 'center' | |||
| } | |||
| }) | |||
| @@ -0,0 +1,33 @@ | |||
| import React from "react"; | |||
| import { useState } from "react"; | |||
| import { View, TextInput } from "react-native"; | |||
| import Button from "../components/Buttons/Button"; | |||
| import { login } from "../thunks/user.thunk"; | |||
| export const LogIn = () => { | |||
| const [username, setUsername] = useState() | |||
| const [password, setPassword] = useState() | |||
| const [error, setError] = useState() | |||
| return ( | |||
| <View> | |||
| <TextInput focus={true} | |||
| style={{ marginTop: 20 }} | |||
| placeholder='Username' /> | |||
| <TextInput focus={true} | |||
| style={{ marginTop: 20 }} | |||
| placeholder='Password' | |||
| secureTextEntry /> | |||
| <Button onPress={() => dispatch( | |||
| login(username, password, function (result) { | |||
| if (!result.OK) { | |||
| setUsername(""); | |||
| setPassword(""); | |||
| setError(result.data.Message ? result.data.Message : "Error occurred during Login,please try again!"); | |||
| } | |||
| }) | |||
| )} title='Log In' style={{ backgroundColor: 'red' }} /> | |||
| </View> | |||
| ) | |||
| } | |||
| @@ -0,0 +1 @@ | |||
| export const API_ENDPOINT = __DEV__ ? "https://mystratadevapi.kalla.co/" : "https://my.kalla.co/"; | |||
| @@ -0,0 +1,111 @@ | |||
| import axios from "axios"; | |||
| import { setConnectionError } from "../store/actions"; | |||
| import store from "../store/store"; | |||
| import { API_ENDPOINT } from './endpointDef'; | |||
| import { refreshTokens } from "./tokenService/tokenApiClient"; | |||
| const axiosApiInstance = axios.create(); | |||
| const globalLog = false; | |||
| const defaultOptions = { log: false } | |||
| export const Get = (url, options) => { | |||
| return request("GET", url, { ...defaultOptions, ...options }); | |||
| } | |||
| export const Post = (url, data, options) => { | |||
| return request("POST", url, { ...defaultOptions, ...options, data }); | |||
| } | |||
| export const Put = (url, data, options) => { | |||
| return request("PUT", url, { ...defaultOptions, ...options, data }); | |||
| } | |||
| export const Delete = (url, options) => { | |||
| return request("DELETE", url, { ...defaultOptions, ...options }); | |||
| } | |||
| const isLogging = (log) => { | |||
| return (globalLog || log); | |||
| } | |||
| const request = (method, url, options) => { | |||
| const { data, log } = options; | |||
| if (isLogging(log)) console.log("REQUEST URL : ", url); | |||
| const requestObject = { | |||
| method, | |||
| url: API_ENDPOINT + url, | |||
| timeout: 60000 | |||
| } | |||
| if (data) { | |||
| if (isLogging(log)) console.log(`DATA FOR : ${url}`, data); | |||
| requestObject.data = data; | |||
| } | |||
| else { | |||
| requestObject.data = undefined; | |||
| } | |||
| // console.log("url", url) | |||
| return axiosApiInstance(requestObject).then((response) => { | |||
| if (isLogging(log)) console.log(`RESPONSE for ${url} : `, response); | |||
| return { ...response, OK: true }; | |||
| }) | |||
| .catch((error) => { | |||
| if (isLogging(log)) console.log(`RESPONSE for catch ${url} : `, (error.response ? error.response : error)); | |||
| //if we get a request timeout error | |||
| if (!error.response) { | |||
| store.dispatch(setConnectionError(error)); | |||
| return { error, OK: false }; | |||
| } | |||
| //if we get a response other than OK | |||
| if (error.response) { | |||
| return { ...error.response, OK: false }; | |||
| } | |||
| return { OK: false }; | |||
| }) | |||
| } | |||
| axiosApiInstance.interceptors.request.use( | |||
| (config) => { | |||
| // console.log('sending request') | |||
| const accessToken = store.getState().user.token; | |||
| config.headers = { | |||
| "Authorization": `Bearer ${accessToken}`, | |||
| "Accept": "application/json", | |||
| "Content-Type": "application/json", | |||
| "Referer": config.url | |||
| }; | |||
| return config; | |||
| }, | |||
| (error) => { | |||
| Promise.reject(error); | |||
| } | |||
| ); | |||
| axiosApiInstance.interceptors.response.use( | |||
| (response) => { | |||
| return response; | |||
| }, | |||
| async (error) => { | |||
| // console.log('********************************************************************', error) | |||
| const originalRequest = error.config | |||
| if (originalRequest._retried) { | |||
| console.log("Request second attempt failed.") | |||
| } | |||
| if (!originalRequest._retried && error.response && error.response.status === 401) { | |||
| try { | |||
| await refreshTokens() | |||
| } | |||
| catch (e) { | |||
| throw error; | |||
| } | |||
| return await axiosApiInstance({ ...originalRequest, _retried: true }) | |||
| } | |||
| throw error; | |||
| } | |||
| ); | |||
| export default axiosApiInstance; | |||
| @@ -0,0 +1,22 @@ | |||
| import store from '../../store/store'; | |||
| 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 | |||
| } | |||
| @@ -0,0 +1,36 @@ | |||
| import axios from "axios"; | |||
| import { batchActions } from 'redux-batched-actions'; | |||
| import { logOut, setRefreshToken, setToken } from "../../store/actions"; | |||
| import store from "../../store/store"; | |||
| 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 | |||
| } | |||
| @@ -0,0 +1,17 @@ | |||
| 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 | |||
| } | |||
| @@ -0,0 +1,6 @@ | |||
| import { Post } from './index'; | |||
| export const loginAPI = async (username, password) => { | |||
| const body = JSON.stringify({ Username: username, Password: password }); | |||
| return Post(`api/login`, body); | |||
| }; | |||
| @@ -0,0 +1,14 @@ | |||
| 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,79 @@ | |||
| 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 }; | |||
| @@ -0,0 +1,9 @@ | |||
| import { createStore, applyMiddleware } from "redux"; | |||
| import { enableBatching } from 'redux-batched-actions'; | |||
| import thunk from "redux-thunk"; | |||
| import reducers from "./reducers"; | |||
| import { init } from './authPersistor' | |||
| const store = createStore(enableBatching(reducers), applyMiddleware(thunk)); | |||
| init(store) | |||
| export default store; | |||
| @@ -0,0 +1,12 @@ | |||
| import { combineReducers } from "redux"; | |||
| // import { persistReducer } from "redux-persist"; | |||
| // import immutableTransform from "redux-persist-transform-immutable"; | |||
| import test from "./test"; | |||
| import user from "./user"; | |||
| const rootReducer = combineReducers({ | |||
| test, | |||
| user | |||
| }); | |||
| export default rootReducer; | |||
| @@ -0,0 +1,14 @@ | |||
| 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 | |||
| }) | |||
| }) | |||
| @@ -0,0 +1,36 @@ | |||
| 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,17 @@ | |||
| import { loginAPI, } from "../service/user"; | |||
| export const login = (username, password, callback) => { | |||
| return (dispatch) => | |||
| loginAPI(username, password) | |||
| .then((responseJson) => { | |||
| if (responseJson.OK && responseJson.data.Data.AccessToken != null) { | |||
| dispatch(batchActions([ | |||
| setToken(responseJson.data.Data.AccessToken), | |||
| setRefreshToken(responseJson.data.Data.RefreshToken), | |||
| setUsername(username)])) | |||
| } else callback(responseJson); | |||
| }) | |||
| .catch((error) => { | |||
| console.log("error", error); | |||
| }); | |||
| }; | |||