Initial Expo base app config with Redux
This commit is contained in:
10
.env.development
Normal file
10
.env.development
Normal file
@@ -0,0 +1,10 @@
|
||||
REACT_APP_GRAPHQL_ENDPOINT=https://bodyshop-dev-db.herokuapp.com/v1/graphql
|
||||
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://bodyshop-dev-db.herokuapp.com/v1/graphql
|
||||
REACT_APP_GA_CODE=231099835
|
||||
REACT_APP_FIREBASE_CONFIG={"apiKey":"AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc","authDomain":"imex-dev.firebaseapp.com","databaseURL":"https://imex-dev.firebaseio.com","projectId":"imex-dev","storageBucket":"imex-dev.appspot.com","messagingSenderId":"759548147434","appId":"1:759548147434:web:e8239868a48ceb36700993","measurementId":"G-K5XRBVVB4S"}
|
||||
REACT_APP_CLOUDINARY_ENDPOINT=https://api.cloudinary.com/v1_1/bodyshop/image
|
||||
REACT_APP_CLOUDINARY_IMAGE_ENDPOINT=https://res.cloudinary.com/bodyshop/image/upload
|
||||
REACT_APP_CLOUDINARY_API_KEY=473322739956866
|
||||
REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=h_200,w_200,c_thumb
|
||||
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BG3tzU7L2BXlGZ_3VLK4PNaRceoEXEnmHfxcVbRMF5o5g05ejslhVPki9kBM9cBBT-08Ad9kN3HSpS6JmrWD6h4'
|
||||
REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
|
||||
10
.env.production
Normal file
10
.env.production
Normal file
@@ -0,0 +1,10 @@
|
||||
REACT_APP_GRAPHQL_ENDPOINT=https://bodyshop-dev-db.herokuapp.com/v1/graphql
|
||||
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://bodyshop-dev-db.herokuapp.com/v1/graphql
|
||||
REACT_APP_GA_CODE=231103507
|
||||
REACT_APP_FIREBASE_CONFIG={"apiKey":"AIzaSyDSezy-jGJreo7ulgpLdlpOwAOrgcaEkhU","authDomain":"imex-prod.firebaseapp.com","databaseURL":"https://imex-prod.firebaseio.com","projectId":"imex-prod","storageBucket":"imex-prod.appspot.com","messagingSenderId":"253497221485","appId":"1:253497221485:web:3c81c483b94db84b227a64","measurementId":"G-NTWBKG2L0M"}
|
||||
REACT_APP_CLOUDINARY_ENDPOINT=https://api.cloudinary.com/v1_1/bodyshop/image
|
||||
REACT_APP_CLOUDINARY_IMAGE_ENDPOINT=https://res.cloudinary.com/bodyshop/image/upload
|
||||
REACT_APP_CLOUDINARY_API_KEY=473322739956866
|
||||
REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=h_200,w_200,c_thumb
|
||||
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BMgZT1NZztW2DsJl8Mg2L04hgY9FzAg6b8fbzgNAfww2VDzH3VE63Ot9EaP_U7KWS2JT-7HPHaw0T_Tw_5vkZc8'
|
||||
REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,6 +8,7 @@ npm-debug.*
|
||||
*.mobileprovision
|
||||
*.orig.*
|
||||
web-build/
|
||||
yarn-error.log
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
||||
36
App.js
36
App.js
@@ -1,21 +1,37 @@
|
||||
import { StatusBar } from 'expo-status-bar';
|
||||
import React from 'react';
|
||||
import { StyleSheet, Text, View } from 'react-native';
|
||||
import { StatusBar } from "expo-status-bar";
|
||||
import React from "react";
|
||||
import {
|
||||
SafeAreaView,
|
||||
StatusBar as rnStatusBar,
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
} from "react-native";
|
||||
import { store, persistor } from "./redux/store";
|
||||
import { Provider } from "react-redux";
|
||||
import { PersistGate } from "redux-persist/integration/react";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text>Open up App.js to start working on your app!</Text>
|
||||
<StatusBar style="auto" />
|
||||
</View>
|
||||
<Provider store={store}>
|
||||
<PersistGate persistor={persistor}>
|
||||
<SafeAreaView style={styles.container}>
|
||||
<View style={styles.container}>
|
||||
<Text>Open up App.js to start working on your app! ttt</Text>
|
||||
<StatusBar style="auto" />
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
</PersistGate>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#fff',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: "#fff",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
paddingTop: Platform.OS === "android" ? rnStatusBar.currentHeight : 0,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module.exports = function(api) {
|
||||
module.exports = function (api) {
|
||||
api.cache(true);
|
||||
return {
|
||||
presets: ['babel-preset-expo'],
|
||||
presets: ["babel-preset-expo"],
|
||||
};
|
||||
};
|
||||
|
||||
116
firebase/firebase.utils.js
Normal file
116
firebase/firebase.utils.js
Normal file
@@ -0,0 +1,116 @@
|
||||
import firebase from "firebase/app";
|
||||
import "firebase/firestore";
|
||||
import "firebase/auth";
|
||||
import "firebase/database";
|
||||
//import "firebase/analytics";
|
||||
import "firebase/messaging";
|
||||
import { store } from "../redux/store";
|
||||
|
||||
const config = JSON.parse(process.env.REACT_APP_FIREBASE_CONFIG);
|
||||
firebase.initializeApp(config);
|
||||
|
||||
export const auth = firebase.auth();
|
||||
export const firestore = firebase.firestore();
|
||||
//export const analytics = firebase.analytics();
|
||||
|
||||
export default firebase;
|
||||
|
||||
export const getCurrentUser = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const unsubscribe = auth.onAuthStateChanged((userAuth) => {
|
||||
unsubscribe();
|
||||
resolve(userAuth);
|
||||
}, reject);
|
||||
});
|
||||
};
|
||||
|
||||
export const updateCurrentUser = (userDetails) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const unsubscribe = auth.onAuthStateChanged((userAuth) => {
|
||||
userAuth.updateProfile(userDetails).then((r) => {
|
||||
unsubscribe();
|
||||
resolve(userAuth);
|
||||
});
|
||||
}, reject);
|
||||
});
|
||||
};
|
||||
|
||||
let messaging;
|
||||
try {
|
||||
messaging = firebase.messaging();
|
||||
// Project Settings => Cloud Messaging => Web Push certificates
|
||||
messaging.usePublicVapidKey(process.env.REACT_APP_FIREBASE_PUBLIC_VAPID_KEY);
|
||||
console.log("[FCM UTIL] FCM initialized successfully.");
|
||||
} catch {
|
||||
console.log("[FCM UTIL] Firebase Messaging is likely unsupported.");
|
||||
}
|
||||
|
||||
export { messaging };
|
||||
|
||||
export const logImEXEvent = (eventName, additionalParams, stateProp = null) => {
|
||||
const state = stateProp || store.getState();
|
||||
const eventParams = {
|
||||
shop:
|
||||
(state.user && state.user.bodyshop && state.user.bodyshop.shopname) ||
|
||||
null,
|
||||
user:
|
||||
(state.user && state.user.currentUser && state.user.currentUser.email) ||
|
||||
null,
|
||||
...additionalParams,
|
||||
};
|
||||
analytics.logEvent(eventName, eventParams);
|
||||
};
|
||||
|
||||
if (messaging) {
|
||||
messaging.onMessage(async (payload) => {
|
||||
console.log("[FCM] UTILS Message received. ", payload);
|
||||
navigator.serviceWorker.getRegistration().then((registration) => {
|
||||
return registration.showNotification(
|
||||
"[UTIL]" + payload.notification.title,
|
||||
payload.notification
|
||||
);
|
||||
});
|
||||
|
||||
// if (!payload.clientId) return;
|
||||
|
||||
// // Get the client.
|
||||
// const client = await clients.get(payload.clientId);
|
||||
// // Exit early if we don't get the client.
|
||||
// // Eg, if it closed.
|
||||
// if (!client) return;
|
||||
|
||||
// // Send a message to the client.
|
||||
// console.log("Posting to client.");
|
||||
// client.postMessage({
|
||||
// msg: "Hey I just got a fetch from you!",
|
||||
// url: payload.request.url,
|
||||
// });
|
||||
|
||||
// [START_EXCLUDE]
|
||||
// Update the UI to include the received message.
|
||||
//appendMessage(payload);
|
||||
|
||||
// [END_EXCLUDE]
|
||||
});
|
||||
|
||||
messaging.onTokenRefresh(() => {
|
||||
messaging
|
||||
.getToken()
|
||||
.then((refreshedToken) => {
|
||||
console.log("[FCM] Token refreshed.");
|
||||
// Indicate that the new Instance ID token has not yet been sent to the
|
||||
// app server.
|
||||
// setTokenSentToServer(false);
|
||||
// // Send Instance ID token to app server.
|
||||
// sendTokenToServer(refreshedToken);
|
||||
// // [START_EXCLUDE]
|
||||
// // Display new Instance ID token and clear UI of all previous messages.
|
||||
// resetUI();
|
||||
// [END_EXCLUDE]
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("[FCM] Unable to retrieve refreshed token ", err);
|
||||
// showToken("Unable to retrieve refreshed token ", err);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -8,12 +8,19 @@
|
||||
"eject": "expo eject"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-native-community/async-storage": "^1.11.0",
|
||||
"expo": "~38.0.8",
|
||||
"expo-status-bar": "^1.0.2",
|
||||
"react": "~16.11.0",
|
||||
"react-dom": "~16.11.0",
|
||||
"react-native": "https://github.com/expo/react-native/archive/sdk-38.0.2.tar.gz",
|
||||
"react-native-web": "~0.11.7"
|
||||
"react-native-web": "~0.11.7",
|
||||
"react-redux": "^7.2.1",
|
||||
"redux": "^4.0.5",
|
||||
"redux-logger": "^3.0.6",
|
||||
"redux-persist": "^6.0.0",
|
||||
"redux-saga": "^1.1.3",
|
||||
"reselect": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.8.6",
|
||||
|
||||
28
redux/root.reducer.js
Normal file
28
redux/root.reducer.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import { combineReducers } from "redux";
|
||||
import { persistReducer } from "redux-persist";
|
||||
import AsyncStorage from "@react-native-community/async-storage";
|
||||
|
||||
import userReducer from "./user/user.reducer";
|
||||
// import messagingReducer from "./messaging/messaging.reducer";
|
||||
// import emailReducer from "./email/email.reducer";
|
||||
// import modalsReducer from "./modals/modals.reducer";
|
||||
// import applicationReducer from "./application/application.reducer";
|
||||
// import techReducer from "./tech/tech.reducer";
|
||||
|
||||
const persistConfig = {
|
||||
key: "root",
|
||||
storage: AsyncStorage,
|
||||
// whitelist: ["messaging", "tech", "application"],
|
||||
// blacklist: ["user", "email", "modals"],
|
||||
};
|
||||
|
||||
const rootReducer = combineReducers({
|
||||
user: userReducer,
|
||||
// messaging: messagingReducer,
|
||||
// email: emailReducer,
|
||||
// modals: modalsReducer,
|
||||
// application: applicationReducer,
|
||||
// tech: techReducer,
|
||||
});
|
||||
|
||||
export default persistReducer(persistConfig, rootReducer);
|
||||
19
redux/root.saga.js
Normal file
19
redux/root.saga.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import { all, call } from "redux-saga/effects";
|
||||
|
||||
// import { userSagas } from "./user/user.sagas";
|
||||
// import { messagingSagas } from "./messaging/messaging.sagas";
|
||||
// import { emailSagas } from "./email/email.sagas";
|
||||
// import { modalsSagas } from "./modals/modals.sagas";
|
||||
// import { applicationSagas } from "./application/application.sagas";
|
||||
// import { techSagas } from "./tech/tech.sagas";
|
||||
|
||||
export default function* rootSaga() {
|
||||
yield all([
|
||||
// call(userSagas),
|
||||
// call(messagingSagas),
|
||||
// call(emailSagas),
|
||||
// call(modalsSagas),
|
||||
// call(applicationSagas),
|
||||
// call(techSagas),
|
||||
]);
|
||||
}
|
||||
26
redux/store.js
Normal file
26
redux/store.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import { createStore, applyMiddleware, compose } from "redux";
|
||||
import { persistStore } from "redux-persist";
|
||||
import { createLogger } from "redux-logger";
|
||||
import createSagaMiddleware from "redux-saga";
|
||||
|
||||
import rootReducer from "./root.reducer";
|
||||
import rootSaga from "./root.saga";
|
||||
|
||||
const sagaMiddleWare = createSagaMiddleware();
|
||||
|
||||
const middlewares = [sagaMiddleWare];
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
middlewares.push(createLogger({ collapsed: true, diff: true }));
|
||||
}
|
||||
|
||||
const enhancer = compose(
|
||||
applyMiddleware(...middlewares)
|
||||
// other store enhancers if any
|
||||
);
|
||||
|
||||
export const store = createStore(rootReducer, enhancer);
|
||||
sagaMiddleWare.run(rootSaga);
|
||||
|
||||
export const persistor = persistStore(store);
|
||||
|
||||
export default { store, persistStore };
|
||||
100
redux/user/user.actions.js
Normal file
100
redux/user/user.actions.js
Normal file
@@ -0,0 +1,100 @@
|
||||
import UserActionTypes from "./user.types";
|
||||
|
||||
export const signInSuccess = (user) => ({
|
||||
type: UserActionTypes.SIGN_IN_SUCCESS,
|
||||
payload: user,
|
||||
});
|
||||
export const signInFailure = (errorMsg) => ({
|
||||
type: UserActionTypes.SIGN_IN_FAILURE,
|
||||
payload: errorMsg,
|
||||
});
|
||||
|
||||
export const emailSignInStart = (emailAndPassword) => ({
|
||||
type: UserActionTypes.EMAIL_SIGN_IN_START,
|
||||
payload: emailAndPassword,
|
||||
});
|
||||
|
||||
export const checkUserSession = () => ({
|
||||
type: UserActionTypes.CHECK_USER_SESSION,
|
||||
});
|
||||
|
||||
export const signOutStart = () => ({
|
||||
type: UserActionTypes.SIGN_OUT_START,
|
||||
});
|
||||
export const signOutSuccess = () => ({
|
||||
type: UserActionTypes.SIGN_OUT_SUCCESS,
|
||||
});
|
||||
|
||||
export const signOutFailure = (error) => ({
|
||||
type: UserActionTypes.SIGN_OUT_FAILURE,
|
||||
payload: error,
|
||||
});
|
||||
|
||||
export const unauthorizedUser = () => ({
|
||||
type: UserActionTypes.UNAUTHORIZED_USER,
|
||||
});
|
||||
|
||||
export const setUserLanguage = (language) => ({
|
||||
type: UserActionTypes.SET_USER_LANGUAGE,
|
||||
payload: language,
|
||||
});
|
||||
|
||||
export const updateUserDetails = (userDetails) => ({
|
||||
type: UserActionTypes.UPDATE_USER_DETAILS,
|
||||
payload: userDetails,
|
||||
});
|
||||
|
||||
export const updateUserDetailsSuccess = (userDetails) => ({
|
||||
type: UserActionTypes.UPDATE_USER_DETAILS_SUCCESS,
|
||||
payload: userDetails,
|
||||
});
|
||||
|
||||
export const setBodyshop = (bodyshop) => ({
|
||||
type: UserActionTypes.SET_SHOP_DETAILS,
|
||||
payload: bodyshop,
|
||||
});
|
||||
|
||||
export const setInstanceId = (userInfo) => ({
|
||||
type: UserActionTypes.SET_INSTANCE_ID,
|
||||
payload: userInfo,
|
||||
});
|
||||
|
||||
export const checkInstanceId = (uid) => ({
|
||||
type: UserActionTypes.CHECK_INSTANCE_ID,
|
||||
payload: uid,
|
||||
});
|
||||
|
||||
export const setInstanceConflict = () => ({
|
||||
type: UserActionTypes.SET_INSTANCE_CONFLICT,
|
||||
});
|
||||
|
||||
export const setLocalFingerprint = (fingerprint) => ({
|
||||
type: UserActionTypes.SET_LOCAL_FINGERPRINT,
|
||||
payload: fingerprint,
|
||||
});
|
||||
|
||||
export const sendPasswordReset = (email) => ({
|
||||
type: UserActionTypes.SEND_PASSWORD_RESET_EMAIL_START,
|
||||
payload: email,
|
||||
});
|
||||
export const sendPasswordResetFailure = (error) => ({
|
||||
type: UserActionTypes.SEND_PASSWORD_RESET_EMAIL_FAILURE,
|
||||
payload: error,
|
||||
});
|
||||
export const sendPasswordResetSuccess = () => ({
|
||||
type: UserActionTypes.SEND_PASSWORD_RESET_EMAIL_SUCCESS,
|
||||
});
|
||||
|
||||
export const validatePasswordResetStart = (emailAndPin) => ({
|
||||
type: UserActionTypes.VALIDATE_PASSWORD_RESET_START,
|
||||
payload: emailAndPin,
|
||||
});
|
||||
|
||||
export const validatePasswordResetSuccess = () => ({
|
||||
type: UserActionTypes.VALIDATE_PASSWORD_RESET_SUCCESS,
|
||||
});
|
||||
|
||||
export const validatePasswordResetFailure = (error) => ({
|
||||
type: UserActionTypes.VALIDATE_PASSWORD_RESET_FAILURE,
|
||||
payload: error,
|
||||
});
|
||||
91
redux/user/user.reducer.js
Normal file
91
redux/user/user.reducer.js
Normal file
@@ -0,0 +1,91 @@
|
||||
// import UserActionTypes from "./user.types";
|
||||
|
||||
const INITIAL_STATE = {
|
||||
currentUser: {
|
||||
authorized: null,
|
||||
},
|
||||
bodyshop: null,
|
||||
fingerprint: null,
|
||||
error: null,
|
||||
conflict: false,
|
||||
passwordreset: {
|
||||
email: null,
|
||||
error: null,
|
||||
success: false,
|
||||
},
|
||||
};
|
||||
|
||||
const userReducer = (state = INITIAL_STATE, action) => {
|
||||
switch (action.type) {
|
||||
// case UserActionTypes.SET_LOCAL_FINGERPRINT:
|
||||
// return { ...state, fingerprint: action.payload };
|
||||
// case UserActionTypes.SET_INSTANCE_ID:
|
||||
// return { ...state, conflict: false };
|
||||
// case UserActionTypes.SET_INSTANCE_CONFLICT:
|
||||
// return { ...state, conflict: true };
|
||||
// case UserActionTypes.VALIDATE_PASSWORD_RESET_START:
|
||||
// case UserActionTypes.SEND_PASSWORD_RESET_EMAIL_START:
|
||||
// return {
|
||||
// ...state,
|
||||
// passwordreset: {
|
||||
// email: action.payload,
|
||||
// error: null,
|
||||
// success: false,
|
||||
// },
|
||||
// };
|
||||
// case UserActionTypes.VALIDATE_PASSWORD_RESET_FAILURE:
|
||||
// case UserActionTypes.SEND_PASSWORD_RESET_EMAIL_FAILURE:
|
||||
// return { ...state, passwordreset: { error: action.payload } };
|
||||
// case UserActionTypes.VALIDATE_PASSWORD_RESET_SUCCESS:
|
||||
// case UserActionTypes.SEND_PASSWORD_RESET_EMAIL_SUCCESS:
|
||||
// return {
|
||||
// ...state,
|
||||
// passwordreset: { ...state.passwordreset, success: true },
|
||||
// };
|
||||
// case UserActionTypes.SIGN_IN_SUCCESS:
|
||||
// return {
|
||||
// ...state,
|
||||
// currentUser: action.payload,
|
||||
// error: null,
|
||||
// };
|
||||
// case UserActionTypes.SIGN_OUT_SUCCESS:
|
||||
// return {
|
||||
// ...state,
|
||||
// currentUser: { authorized: false },
|
||||
// error: null,
|
||||
// };
|
||||
// case UserActionTypes.UNAUTHORIZED_USER:
|
||||
// return {
|
||||
// ...state,
|
||||
// error: null,
|
||||
// currentUser: { authorized: false },
|
||||
// };
|
||||
// case UserActionTypes.SET_USER_LANGUAGE:
|
||||
// return {
|
||||
// ...state,
|
||||
// language: action.payload,
|
||||
// };
|
||||
// case UserActionTypes.UPDATE_USER_DETAILS_SUCCESS:
|
||||
// return {
|
||||
// ...state,
|
||||
// currentUser: {
|
||||
// ...state.currentUser,
|
||||
// ...action.payload, //Spread current user details in.
|
||||
// },
|
||||
// };
|
||||
|
||||
// case UserActionTypes.SET_SHOP_DETAILS:
|
||||
// return { ...state, bodyshop: action.payload };
|
||||
// case UserActionTypes.SIGN_IN_FAILURE:
|
||||
// case UserActionTypes.SIGN_OUT_FAILURE:
|
||||
// case UserActionTypes.EMAIL_SIGN_UP_FAILURE:
|
||||
// return {
|
||||
// ...state,
|
||||
// error: action.payload,
|
||||
// };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default userReducer;
|
||||
214
redux/user/user.sagas.js
Normal file
214
redux/user/user.sagas.js
Normal file
@@ -0,0 +1,214 @@
|
||||
import Fingerprint2 from "fingerprintjs2";
|
||||
import LogRocket from "logrocket";
|
||||
import { all, call, delay, put, select, takeLatest } from "redux-saga/effects";
|
||||
import {
|
||||
auth,
|
||||
firestore,
|
||||
getCurrentUser,
|
||||
logImEXEvent,
|
||||
updateCurrentUser,
|
||||
} from "../../firebase/firebase.utils";
|
||||
import {
|
||||
checkInstanceId,
|
||||
setInstanceConflict,
|
||||
setInstanceId,
|
||||
setLocalFingerprint,
|
||||
signInFailure,
|
||||
signInSuccess,
|
||||
signOutFailure,
|
||||
signOutSuccess,
|
||||
unauthorizedUser,
|
||||
updateUserDetailsSuccess,
|
||||
sendPasswordResetFailure,
|
||||
sendPasswordResetSuccess,
|
||||
validatePasswordResetSuccess,
|
||||
validatePasswordResetFailure,
|
||||
} from "./user.actions";
|
||||
import UserActionTypes from "./user.types";
|
||||
|
||||
export function* onEmailSignInStart() {
|
||||
yield takeLatest(UserActionTypes.EMAIL_SIGN_IN_START, signInWithEmail);
|
||||
}
|
||||
export function* signInWithEmail({ payload: { email, password } }) {
|
||||
try {
|
||||
logImEXEvent("redux_sign_in_attempt", { user: email });
|
||||
|
||||
const { user } = yield auth.signInWithEmailAndPassword(email, password);
|
||||
|
||||
yield put(
|
||||
signInSuccess({
|
||||
uid: user.uid,
|
||||
email: user.email,
|
||||
displayName: user.displayName,
|
||||
photoURL: user.photoURL,
|
||||
authorized: true,
|
||||
})
|
||||
);
|
||||
} catch (error) {
|
||||
yield put(signInFailure(error));
|
||||
logImEXEvent("redux_sign_in_failure", { user: email, error });
|
||||
}
|
||||
}
|
||||
|
||||
export function* onCheckUserSession() {
|
||||
yield takeLatest(UserActionTypes.CHECK_USER_SESSION, isUserAuthenticated);
|
||||
}
|
||||
export function* isUserAuthenticated() {
|
||||
try {
|
||||
logImEXEvent("redux_auth_check");
|
||||
|
||||
const user = yield getCurrentUser();
|
||||
if (!user) {
|
||||
yield put(unauthorizedUser());
|
||||
return;
|
||||
}
|
||||
|
||||
LogRocket.identify(user.email);
|
||||
yield put(
|
||||
signInSuccess({
|
||||
uid: user.uid,
|
||||
email: user.email,
|
||||
displayName: user.displayName,
|
||||
photoURL: user.photoURL,
|
||||
authorized: true,
|
||||
})
|
||||
);
|
||||
} catch (error) {
|
||||
yield put(signInFailure(error));
|
||||
}
|
||||
}
|
||||
export function* onSignOutStart() {
|
||||
yield takeLatest(UserActionTypes.SIGN_OUT_START, signOutStart);
|
||||
}
|
||||
export function* signOutStart() {
|
||||
try {
|
||||
logImEXEvent("redux_sign_out");
|
||||
|
||||
yield auth.signOut();
|
||||
yield put(signOutSuccess());
|
||||
localStorage.removeItem("token");
|
||||
} catch (error) {
|
||||
yield put(signOutFailure(error.message));
|
||||
}
|
||||
}
|
||||
|
||||
export function* onUpdateUserDetails() {
|
||||
yield takeLatest(UserActionTypes.UPDATE_USER_DETAILS, updateUserDetails);
|
||||
}
|
||||
export function* updateUserDetails(userDetails) {
|
||||
try {
|
||||
yield updateCurrentUser(userDetails.payload);
|
||||
yield put(updateUserDetailsSuccess(userDetails.payload));
|
||||
} catch (error) {
|
||||
//yield put(signOutFailure(error.message));
|
||||
//TODO error handling
|
||||
}
|
||||
}
|
||||
export function* onSetInstanceId() {
|
||||
yield takeLatest(UserActionTypes.SET_INSTANCE_ID, setInstanceIdSaga);
|
||||
}
|
||||
export function* setInstanceIdSaga({ payload: uid }) {
|
||||
try {
|
||||
const userInstanceRef = firestore.doc(`userInstance/${uid}`);
|
||||
|
||||
const fingerprint = Fingerprint2.x64hash128(
|
||||
(yield Fingerprint2.getPromise({})).map((c) => c.value).join(""),
|
||||
31
|
||||
);
|
||||
|
||||
yield userInstanceRef.set({
|
||||
timestamp: new Date(),
|
||||
fingerprint,
|
||||
});
|
||||
|
||||
yield put(setLocalFingerprint(fingerprint));
|
||||
yield delay(5 * 60 * 1000);
|
||||
yield put(checkInstanceId(uid));
|
||||
} catch (error) {
|
||||
console.log("error", error);
|
||||
//yield put(signOutFailure(error.message));
|
||||
//TODO error handling
|
||||
}
|
||||
}
|
||||
|
||||
export function* onCheckInstanceId() {
|
||||
yield takeLatest(UserActionTypes.CHECK_INSTANCE_ID, checkInstanceIdSaga);
|
||||
}
|
||||
export function* checkInstanceIdSaga({ payload: uid }) {
|
||||
try {
|
||||
const userInstanceRef = firestore.doc(`userInstance/${uid}`);
|
||||
|
||||
const snapshot = yield userInstanceRef.get();
|
||||
let fingerprint = yield select((state) => state.user.fingerprint);
|
||||
|
||||
if (snapshot.data().fingerprint === fingerprint) {
|
||||
yield delay(5 * 60 * 1000);
|
||||
yield put(checkInstanceId(uid));
|
||||
} else {
|
||||
console.log("ERROR: Fingerprints do not match. Conflict detected.");
|
||||
logImEXEvent("instance_confict");
|
||||
yield put(setInstanceConflict());
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("error", error);
|
||||
//TODO error handling
|
||||
}
|
||||
}
|
||||
|
||||
export function* onSignInSuccess() {
|
||||
yield takeLatest(UserActionTypes.SIGN_IN_SUCCESS, signInSuccessSaga);
|
||||
}
|
||||
|
||||
export function* signInSuccessSaga({ payload }) {
|
||||
LogRocket.identify(payload.email);
|
||||
yield put(setInstanceId(payload.uid));
|
||||
yield logImEXEvent("redux_sign_in_success");
|
||||
}
|
||||
|
||||
export function* onSendPasswordResetStart() {
|
||||
yield takeLatest(
|
||||
UserActionTypes.SEND_PASSWORD_RESET_EMAIL_START,
|
||||
sendPasswordResetEmail
|
||||
);
|
||||
}
|
||||
export function* sendPasswordResetEmail({ payload }) {
|
||||
try {
|
||||
yield auth.sendPasswordResetEmail(payload, {
|
||||
url: "https://imex.online/passwordreset",
|
||||
});
|
||||
console.log("Good should send.");
|
||||
yield put(sendPasswordResetSuccess());
|
||||
} catch (error) {
|
||||
yield put(sendPasswordResetFailure(error.message));
|
||||
}
|
||||
}
|
||||
|
||||
export function* onValidatePasswordResetStart() {
|
||||
yield takeLatest(
|
||||
UserActionTypes.VALIDATE_PASSWORD_RESET_START,
|
||||
validatePasswordResetStart
|
||||
);
|
||||
}
|
||||
export function* validatePasswordResetStart({ payload: { password, code } }) {
|
||||
try {
|
||||
yield auth.confirmPasswordReset(code, password);
|
||||
yield put(validatePasswordResetSuccess());
|
||||
} catch (error) {
|
||||
console.log("function*validatePasswordResetStart -> error", error);
|
||||
yield put(validatePasswordResetFailure(error.message));
|
||||
}
|
||||
}
|
||||
|
||||
export function* userSagas() {
|
||||
yield all([
|
||||
call(onEmailSignInStart),
|
||||
call(onCheckUserSession),
|
||||
call(onSignOutStart),
|
||||
call(onUpdateUserDetails),
|
||||
call(onSetInstanceId),
|
||||
call(onCheckInstanceId),
|
||||
call(onSignInSuccess),
|
||||
call(onSendPasswordResetStart),
|
||||
call(onValidatePasswordResetStart),
|
||||
]);
|
||||
}
|
||||
28
redux/user/user.selectors.js
Normal file
28
redux/user/user.selectors.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import { createSelector } from "reselect";
|
||||
|
||||
const selectUser = (state) => state.user;
|
||||
|
||||
export const selectCurrentUser = createSelector(
|
||||
[selectUser],
|
||||
(user) => user.currentUser
|
||||
);
|
||||
|
||||
export const selectSignInError = createSelector(
|
||||
[selectUser],
|
||||
(user) => user.error
|
||||
);
|
||||
|
||||
export const selectBodyshop = createSelector(
|
||||
[selectUser],
|
||||
(user) => user.bodyshop
|
||||
);
|
||||
|
||||
export const selectInstanceConflict = createSelector(
|
||||
[selectUser],
|
||||
(user) => user.conflict
|
||||
);
|
||||
|
||||
export const selectPasswordReset = createSelector(
|
||||
[selectUser],
|
||||
(user) => user.passwordreset
|
||||
);
|
||||
30
redux/user/user.types.js
Normal file
30
redux/user/user.types.js
Normal file
@@ -0,0 +1,30 @@
|
||||
const UserActionTypes = {
|
||||
SET_CURRENT_USER: "SET_CURRENT_USER",
|
||||
GOOGLE_SIGN_IN_START: "GOOGLE_SIGN_IN_START",
|
||||
SIGN_IN_SUCCESS: "SIGN_IN_SUCCESS",
|
||||
SIGN_IN_FAILURE: "SIGN_IN_FAILURE",
|
||||
EMAIL_SIGN_IN_START: "EMAIL_SIGN_IN_START",
|
||||
CHECK_USER_SESSION: "CHECK_USER_SESSION",
|
||||
SIGN_OUT_START: "SIGN_OUT_START",
|
||||
SIGN_OUT_SUCCESS: "SIGN_OUT_SUCCESS",
|
||||
SIGN_OUT_FAILURE: "SIGN_OUT_FAILURE",
|
||||
EMAIL_SIGN_UP_START: "EMAIL_SIGN_UP_START",
|
||||
EMAIL_SIGN_UP_SUCCESS: "EMAIL_SIGN_UP_SUCCESS",
|
||||
EMAIL_SIGN_UP_FAILURE: "EMAIL_SIGN_UP_FAILURE",
|
||||
UNAUTHORIZED_USER: "UNAUTHORIZED_USER",
|
||||
SET_USER_LANGUAGE: "SET_USER_LANGUAGE",
|
||||
UPDATE_USER_DETAILS: "UPDATE_USER_DETAILS",
|
||||
UPDATE_USER_DETAILS_SUCCESS: "UPDATE_USER_DETAILS_SUCCESS",
|
||||
SET_SHOP_DETAILS: "SET_SHOP_DETAILS",
|
||||
SET_INSTANCE_ID: "SET_INSTANCE_ID",
|
||||
CHECK_INSTANCE_ID: "CHECK_INSTANCE_ID",
|
||||
SET_INSTANCE_CONFLICT: "SET_INSTANCE_CONFLICT",
|
||||
SET_LOCAL_FINGERPRINT: "SET_LOCAL_FINGERPRINT",
|
||||
SEND_PASSWORD_RESET_EMAIL_START: "SEND_PASSWORD_RESET_EMAIL_START",
|
||||
SEND_PASSWORD_RESET_EMAIL_FAILURE: "SEND_PASSWORD_RESET_EMAIL_FAILURE",
|
||||
SEND_PASSWORD_RESET_EMAIL_SUCCESS: "SEND_PASSWORD_RESET_EMAIL_SUCCESS",
|
||||
VALIDATE_PASSWORD_RESET_START: "VALIDATE_PASSWORD_RESET_START",
|
||||
VALIDATE_PASSWORD_RESET_SUCCESS: "VALIDATE_PASSWORD_RESET_SUCCESS",
|
||||
VALIDATE_PASSWORD_RESET_FAILURE: "VALIDATE_PASSWORD_RESET_FAILURE",
|
||||
};
|
||||
export default UserActionTypes;
|
||||
Reference in New Issue
Block a user