Bladeren bron

Added Redux Toolkit

master
Lazar Kostic 2 jaren geleden
bovenliggende
commit
aa0e6bf155
53 gewijzigde bestanden met toevoegingen van 523 en 1177 verwijderingen
  1. 11
    24
      App.js
  2. 1
    1
      app.json
  3. 12
    10
      babel.config.js
  4. 2
    2
      components/CustomDrawer/CustomDrawer.jsx
  5. 48
    0
      features/api/apiSlice.js
  6. 30
    0
      features/auth/authApiSlice.js
  7. 31
    0
      features/auth/authSlice.js
  8. 18
    0
      features/posts/postsApiSlice.js
  9. 34
    0
      features/store.js
  10. 0
    1
      hooks/useAuthHook.js
  11. 3
    1
      jsconfig.json
  12. 1
    0
      navigation/AppStack.js
  13. 3
    3
      navigation/RootNavigation.js
  14. 87
    8
      package-lock.json
  15. 4
    1
      package.json
  16. 79
    76
      screens/HomeScreen.jsx
  17. 57
    43
      screens/LoginScreen.jsx
  18. 33
    38
      screens/PostDetailsScreen.jsx
  19. 13
    13
      screens/ProfileScreen.jsx
  20. 56
    43
      screens/RegisterScreen.jsx
  21. 0
    21
      store/actions/actionHelpers/index.js
  22. 0
    5
      store/actions/app/appActionConstants.js
  23. 0
    11
      store/actions/app/appActions.js
  24. 0
    10
      store/actions/authProvider/authProviderActionConstants.js
  25. 0
    20
      store/actions/authProvider/authProviderActions.js
  26. 0
    30
      store/actions/login/loginActionConstants.js
  27. 0
    48
      store/actions/login/loginActions.js
  28. 0
    18
      store/actions/register/registerActionConstants.js
  29. 0
    30
      store/actions/register/registerActions.js
  30. 0
    3
      store/actions/user/userActionConstants.js
  31. 0
    19
      store/actions/user/userActions.js
  32. 0
    32
      store/index.js
  33. 0
    51
      store/middleware/accessTokenMiddleware.js
  34. 0
    22
      store/middleware/authenticationMiddleware.js
  35. 0
    20
      store/middleware/internalServerErrorMiddleware.js
  36. 0
    28
      store/middleware/loadingMiddleware.js
  37. 0
    27
      store/middleware/requestStatusMiddleware.js
  38. 0
    33
      store/reducers/authProvider/authProviderReducer.js
  39. 0
    14
      store/reducers/index.js
  40. 0
    45
      store/reducers/loading/loadingReducer.js
  41. 0
    69
      store/reducers/login/loginReducer.js
  42. 0
    54
      store/reducers/register/registerReducer.js
  43. 0
    37
      store/reducers/user/userReducer.js
  44. 0
    42
      store/saga/authProviderSaga.js
  45. 0
    8
      store/saga/index.js
  46. 0
    82
      store/saga/loginSaga.js
  47. 0
    32
      store/saga/registerSaga.js
  48. 0
    8
      store/selectors/authProviderSelectors.js
  49. 0
    16
      store/selectors/loadingSelectors.js
  50. 0
    29
      store/selectors/loginSelectors.js
  51. 0
    8
      store/selectors/registerSelectors.js
  52. 0
    28
      store/selectors/userSelectors.js
  53. 0
    13
      store/utils/createReducer.js

+ 11
- 24
App.js Bestand weergeven

@@ -1,41 +1,26 @@
import "react-native-gesture-handler";
import React, { useEffect } from "react";
import { NavigationContainer } from "@react-navigation/native";
import { Provider, useDispatch } from "react-redux";
import store from "./store";
import { Provider } from "react-redux";
import { store, persistor } from "@features/store";
import { PersistGate } from "redux-persist/integration/react";
import { ThemeProvider } from "@styles";
import '@i18n'
import "@i18n";

import { useFonts } from "expo-font";
import RootNavigation from "./navigation/RootNavigation";
import { getData, getObjectData } from "@service/asyncStorage";
import {
JWT_REFRESH_TOKEN,
JWT_TOKEN,
LANGUAGE,
} from "@constants/localStorage";
import { fetchUserSuccess } from "@store/actions/login/loginActions";
import { addHeaderToken } from "@request/index";
import { useTranslation } from "react-i18next";
import { getObjectData } from "@service/asyncStorage";
import { LANGUAGE } from "@constants/localStorage";

function App() {
const { i18n } = useTranslation();
const dispatch = useDispatch();
const initialSetup = async () => {
const token = await getData(JWT_TOKEN);
const refreshToken = await getData(JWT_REFRESH_TOKEN);
const language = await getObjectData(LANGUAGE);

if (language !== null) {
await i18n.changeLanguage(language.code);
}

if (token) {
addHeaderToken(token);
}
if (token !== undefined && refreshToken !== undefined) {
dispatch(fetchUserSuccess({ jwt: token, refreshToken }));
}
};

useEffect(() => {
@@ -61,9 +46,11 @@ const AppWrapper = () => {

return (
<Provider store={store}>
<ThemeProvider>
<App />
</ThemeProvider>
<PersistGate loading={null} persistor={persistor}>
<ThemeProvider>
<App />
</ThemeProvider>
</PersistGate>
</Provider>
);
};

+ 1
- 1
app.json Bestand weergeven

@@ -10,7 +10,7 @@
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"scheme": "com.diligent.template",
"scheme": "diligent",
"updates": {
"fallbackToCacheTimeout": 0
},

+ 12
- 10
babel.config.js Bestand weergeven

@@ -1,9 +1,9 @@
module.exports = function(api) {
module.exports = function (api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],
presets: ["babel-preset-expo"],
plugins: [
'react-native-reanimated/plugin',
"react-native-reanimated/plugin",
[
"module-resolver",
{
@@ -12,18 +12,20 @@ module.exports = function(api) {
"@components": "./components",
"@screens": "./screens",
"@assets": "./assets",
"@store": './store',
"@styles": './styles',
"@store": "./store",
"@styles": "./styles",
"@utils": "./utils",
"@schemas": "./schemas",
"@initialValues": "./initialValues",
"@constants": "./constants",
"@service": "./service",
"@i18n": "./i18n",
"@request": "./request"
}
}
]
]
"@request": "./request",
"@features": "./features",
"@hooks": "./hooks",
},
},
],
],
};
};

+ 2
- 2
components/CustomDrawer/CustomDrawer.jsx Bestand weergeven

@@ -15,7 +15,7 @@ import {
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 { logOut } from "@features/auth/authSlice";
import useAuthHook from "../../hooks/useAuthHook";
import { useTheme } from "@styles";
import { useTranslation } from "react-i18next";
@@ -30,7 +30,7 @@ const CustomDrawer = (props) => {

const handleLogout = async () => {
logoutAuthProvider();
dispatch(logoutUser());
dispatch(logOut());
};

return (

+ 48
- 0
features/api/apiSlice.js Bestand weergeven

@@ -0,0 +1,48 @@
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
import { logOut, setCredentials } from "../auth/authSlice";
import jwt_decode from "jwt-decode";

const baseQuery = fetchBaseQuery({
baseUrl: "https://strapi.dilig.net/api",
prepareHeaders: (headers, { getState }) => {
const token = getState().auth.token;
if (token) {
headers.set("Authorization", `Bearer ${token.jwt}`);
}
headers.set("Content-Type", "application/json");
return headers;
},
});

const baseQueryWithReauth = async (args, api, extraOptions) => {
let result = await baseQuery(args, api, extraOptions);
if (result?.error?.status === 401) {
const token = api.getState().auth.token;
const jwtTokenDecoded = jwt_decode(token.jwt);
if (new Date() > new Date(jwtTokenDecoded.exp * 1000)) {
const refreshResult = await baseQuery(
{
url: "/token/refresh",
method: "POST",
body: { refreshToken: token.refreshToken },
},
api,
extraOptions
);
if (refreshResult?.data) {
const user = api.getState().auth.user;
api.dispatch(setCredentials({ ...refreshResult.data, user }));
result = await baseQuery(args, api, extraOptions);
} else {
api.dispatch(logOut());
}
}
}
return result;
};

export const apiSlice = createApi({
baseQuery: baseQueryWithReauth,
// eslint-disable-next-line no-unused-vars
endpoints: (builder) => ({}),
});

+ 30
- 0
features/auth/authApiSlice.js Bestand weergeven

@@ -0,0 +1,30 @@
import { apiSlice } from "@features/api/apiSlice";

export const authApiSlice = apiSlice.injectEndpoints({
endpoints: (builder) => ({
login: builder.mutation({
providesTags: ["User"],
query: (credentials) => ({
url: "/auth/local",
method: "POST",
body: { ...credentials },
}),
}),
register: builder.mutation({
query: (credentials) => ({
url: "/auth/local/register",
method: "POST",
body: { ...credentials },
}),
}),
authProvider: builder.mutation({
query: ({ provider, accessToken }) => ({
url: `/auth/${provider}/callback?access_token=${accessToken}`,
method: 'GET'
}),
}),
}),
});

export const { useLoginMutation, useRegisterMutation, useAuthProviderMutation } =
authApiSlice;

+ 31
- 0
features/auth/authSlice.js Bestand weergeven

@@ -0,0 +1,31 @@
import { createSlice } from "@reduxjs/toolkit";
import { createSelector } from "reselect";

const authSlice = createSlice({
name: "auth",
initialState: { user: null, token: null },
reducers: {
setCredentials: (state, action) => {
const { user, jwt, refreshToken } = action.payload;
state.user = user;
state.token = { jwt, refreshToken };
},
logOut: (state) => {
state.user = null;
state.token = null;
},
},
});

export const { setCredentials, logOut } = authSlice.actions;
export default authSlice.reducer;

export const authSelector = (state) => state.auth;
export const selectCurrentUser = createSelector(
authSelector,
(state) => state.user
);
export const selectCurrentToken = createSelector(
authSelector,
(state) => state.token
);

+ 18
- 0
features/posts/postsApiSlice.js Bestand weergeven

@@ -0,0 +1,18 @@
import { apiSlice } from "@features/api/apiSlice";

export const postsApiSlice = apiSlice.injectEndpoints({
endpoints: (builder) => ({
allPosts: builder.query({
query: () => ({
url: "/posts?populate=*",
}),
}),
singlePost: builder.query({
query: (postId) => ({
url: `/posts/${postId}?populate=*`,
}),
}),
}),
});

export const { useAllPostsQuery, useSinglePostQuery } = postsApiSlice;

+ 34
- 0
features/store.js Bestand weergeven

@@ -0,0 +1,34 @@
import { configureStore } from "@reduxjs/toolkit";
import { apiSlice } from "./api/apiSlice";
import authReducer from "./auth/authSlice";
import {
persistReducer,
persistStore,
FLUSH,
REHYDRATE,
PAUSE,
PERSIST,
PURGE,
REGISTER,
} from "redux-persist";
import AsyncStorage from "@react-native-async-storage/async-storage";

const authPersistConfig = {
key: "auth",
storage: AsyncStorage,
};

export const store = configureStore({
reducer: {
[apiSlice.reducerPath]: apiSlice.reducer,
auth: persistReducer(authPersistConfig, authReducer),
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
},
}).concat(apiSlice.middleware),
});

export const persistor = persistStore(store);

+ 0
- 1
hooks/useAuthHook.js Bestand weergeven

@@ -1,4 +1,3 @@
import React from "react";
import * as Google from "expo-auth-session/providers/google";
import { getData } from "../service/asyncStorage";
import { ACCESS_TOKEN } from "../constants/localStorage";

+ 3
- 1
jsconfig.json Bestand weergeven

@@ -14,7 +14,9 @@
"@constants/*": ["./constants/*"],
"@service/*": ["./service/*"],
"@i18n/*": ["./i18n/*"],
"@request/*": ["./request/*"]
"@request/*": ["./request/*"],
"@features/*": ["./features/*"],
"@hooks/*": ["./hooks/*"]
}
},
"exclude": ["node_modules", "dist"]

+ 1
- 0
navigation/AppStack.js Bestand weergeven

@@ -32,6 +32,7 @@ const AppStack = () => {
name="Home"
component={TabNavigator}
options={{
unmountOnBlur: true,
title:t("sidebar.home"),
drawerIcon: ({ color }) => (
<Ionicons name="home-outline" size={22} color={color} />

+ 3
- 3
navigation/RootNavigation.js Bestand weergeven

@@ -3,15 +3,15 @@ import AppStack from "./AppStack";
import AuthStack from "./AuthStack";
import { SafeAreaView } from "react-native-safe-area-context";
import { useSelector } from "react-redux";
import { selectTokens } from "@store/selectors/loginSelectors";
import { selectCurrentToken } from "@features/auth/authSlice";
import { StatusBar } from "expo-status-bar";
import { useTheme } from "@styles";

const RootNavigation = () => {
const { isDark, colors } = useTheme();
const tokens = useSelector(selectTokens);
const tokens = useSelector(selectCurrentToken);

return !tokens.JwtToken ? (
return !tokens ? (
<SafeAreaView style={{ flex: 1, backgroundColor: colors.background }}>
<StatusBar
backgroundColor={colors.background}

+ 87
- 8
package-lock.json Bestand weergeven

@@ -15,6 +15,7 @@
"@react-navigation/drawer": "^6.5.5",
"@react-navigation/native": "^6.0.16",
"@react-navigation/native-stack": "^6.9.4",
"@reduxjs/toolkit": "^1.9.5",
"axios": "^1.2.1",
"expo": "^48.0.19",
"expo-auth-session": "~4.0.3",
@@ -42,9 +43,11 @@
"react-native-svg-transformer": "^1.0.0",
"react-native-vector-icons": "^9.2.0",
"react-native-web": "~0.18.7",
"react-redux": "^8.0.5",
"react-redux": "^8.1.1",
"redux": "^4.2.0",
"redux-persist": "^6.0.0",
"redux-saga": "^1.2.2",
"reselect": "^4.1.8",
"yup": "^0.32.11"
},
"devDependencies": {
@@ -5046,6 +5049,29 @@
"resolved": "https://registry.npmjs.org/@redux-saga/types/-/types-1.2.1.tgz",
"integrity": "sha512-1dgmkh+3so0+LlBWRhGA33ua4MYr7tUOj+a9Si28vUi0IUFNbff1T3sgpeDJI/LaC75bBYnQ0A3wXjn0OrRNBA=="
},
"node_modules/@reduxjs/toolkit": {
"version": "1.9.5",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.5.tgz",
"integrity": "sha512-Rt97jHmfTeaxL4swLRNPD/zV4OxTes4la07Xc4hetpUW/vc75t5m1ANyxG6ymnEQ2FsLQsoMlYB2vV1sO3m8tQ==",
"dependencies": {
"immer": "^9.0.21",
"redux": "^4.2.1",
"redux-thunk": "^2.4.2",
"reselect": "^4.1.8"
},
"peerDependencies": {
"react": "^16.9.0 || ^17.0.0 || ^18",
"react-redux": "^7.2.1 || ^8.0.2"
},
"peerDependenciesMeta": {
"react": {
"optional": true
},
"react-redux": {
"optional": true
}
}
},
"node_modules/@segment/loosely-validate-event": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@segment/loosely-validate-event/-/loosely-validate-event-2.0.0.tgz",
@@ -8776,6 +8802,15 @@
"node": ">=4.0"
}
},
"node_modules/immer": {
"version": "9.0.21",
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz",
"integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/immer"
}
},
"node_modules/import-fresh": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz",
@@ -12926,9 +12961,9 @@
}
},
"node_modules/react-redux": {
"version": "8.0.5",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.5.tgz",
"integrity": "sha512-Q2f6fCKxPFpkXt1qNRZdEDLlScsDWyrgSj0mliK59qU6W5gvBiKkdMEG2lJzhd1rCctf0hb6EtePPLZ2e0m1uw==",
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.1.tgz",
"integrity": "sha512-5W0QaKtEhj+3bC0Nj0NkqkhIv8gLADH/2kYFMTHxCVqQILiWzLv6MaLuV5wJU3BQEdHKzTfcvPN0WMS6SC1oyA==",
"dependencies": {
"@babel/runtime": "^7.12.1",
"@types/hoist-non-react-statics": "^3.3.1",
@@ -12943,7 +12978,7 @@
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0",
"react-native": ">=0.59",
"redux": "^4"
"redux": "^4 || ^5.0.0-beta.0"
},
"peerDependenciesMeta": {
"@types/react": {
@@ -13037,6 +13072,14 @@
"@babel/runtime": "^7.9.2"
}
},
"node_modules/redux-persist": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-6.0.0.tgz",
"integrity": "sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==",
"peerDependencies": {
"redux": ">4.0.0"
}
},
"node_modules/redux-saga": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/redux-saga/-/redux-saga-1.2.3.tgz",
@@ -13045,6 +13088,14 @@
"@redux-saga/core": "^1.2.3"
}
},
"node_modules/redux-thunk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz",
"integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==",
"peerDependencies": {
"redux": "^4"
}
},
"node_modules/regenerate": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@@ -18998,6 +19049,17 @@
"resolved": "https://registry.npmjs.org/@redux-saga/types/-/types-1.2.1.tgz",
"integrity": "sha512-1dgmkh+3so0+LlBWRhGA33ua4MYr7tUOj+a9Si28vUi0IUFNbff1T3sgpeDJI/LaC75bBYnQ0A3wXjn0OrRNBA=="
},
"@reduxjs/toolkit": {
"version": "1.9.5",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.5.tgz",
"integrity": "sha512-Rt97jHmfTeaxL4swLRNPD/zV4OxTes4la07Xc4hetpUW/vc75t5m1ANyxG6ymnEQ2FsLQsoMlYB2vV1sO3m8tQ==",
"requires": {
"immer": "^9.0.21",
"redux": "^4.2.1",
"redux-thunk": "^2.4.2",
"reselect": "^4.1.8"
}
},
"@segment/loosely-validate-event": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@segment/loosely-validate-event/-/loosely-validate-event-2.0.0.tgz",
@@ -21792,6 +21854,11 @@
"resolved": "https://registry.npmjs.org/image-size/-/image-size-0.6.3.tgz",
"integrity": "sha512-47xSUiQioGaB96nqtp5/q55m0aBQSQdyIloMOc/x+QVTDZLNmXE892IIDrJ0hM1A5vcNUDD5tDffkSP5lCaIIA=="
},
"immer": {
"version": "9.0.21",
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz",
"integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA=="
},
"import-fresh": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz",
@@ -24999,9 +25066,9 @@
}
},
"react-redux": {
"version": "8.0.5",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.5.tgz",
"integrity": "sha512-Q2f6fCKxPFpkXt1qNRZdEDLlScsDWyrgSj0mliK59qU6W5gvBiKkdMEG2lJzhd1rCctf0hb6EtePPLZ2e0m1uw==",
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.1.tgz",
"integrity": "sha512-5W0QaKtEhj+3bC0Nj0NkqkhIv8gLADH/2kYFMTHxCVqQILiWzLv6MaLuV5wJU3BQEdHKzTfcvPN0WMS6SC1oyA==",
"requires": {
"@babel/runtime": "^7.12.1",
"@types/hoist-non-react-statics": "^3.3.1",
@@ -25077,6 +25144,12 @@
"@babel/runtime": "^7.9.2"
}
},
"redux-persist": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-6.0.0.tgz",
"integrity": "sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==",
"requires": {}
},
"redux-saga": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/redux-saga/-/redux-saga-1.2.3.tgz",
@@ -25085,6 +25158,12 @@
"@redux-saga/core": "^1.2.3"
}
},
"redux-thunk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz",
"integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==",
"requires": {}
},
"regenerate": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",

+ 4
- 1
package.json Bestand weergeven

@@ -14,6 +14,7 @@
"@react-navigation/drawer": "^6.5.5",
"@react-navigation/native": "^6.0.16",
"@react-navigation/native-stack": "^6.9.4",
"@reduxjs/toolkit": "^1.9.5",
"axios": "^1.2.1",
"expo": "^48.0.19",
"expo-auth-session": "~4.0.3",
@@ -41,9 +42,11 @@
"react-native-svg-transformer": "^1.0.0",
"react-native-vector-icons": "^9.2.0",
"react-native-web": "~0.18.7",
"react-redux": "^8.0.5",
"react-redux": "^8.1.1",
"redux": "^4.2.0",
"redux-persist": "^6.0.0",
"redux-saga": "^1.2.2",
"reselect": "^4.1.8",
"yup": "^0.32.11"
},
"devDependencies": {

+ 79
- 76
screens/HomeScreen.jsx Bestand weergeven

@@ -12,18 +12,20 @@ import Feather from "@expo/vector-icons/Feather";
import ListItem from "@components/ListItem/ListItem";
import filter from "lodash.filter";
import { globalStyles } from "@styles/global";
import { getRequest } from "@request/index";
import Layout from "@components/Layout/Layout";
import { useTheme } from "@styles";
import { useTranslation } from "react-i18next";
import { useAllPostsQuery } from "@features/posts/postsApiSlice";
import Loader from "@components/Loader";

const HomeScreen = ({ navigation }) => {
const { colors } = useTheme();
const { t } = useTranslation();
const [posts, setPosts] = useState([]);
const { data, isLoading } = useAllPostsQuery();

const [query, setQuery] = useState("");
const [filteredData, setFilteredData] = useState(posts);
const [posts, setPosts] = useState([]);
const [filteredData, setFilteredData] = useState([]);

const contains = (name, search) => {
if (name.includes(search)) {
@@ -42,17 +44,11 @@ const HomeScreen = ({ navigation }) => {
setQuery(formatedText);
};

const fetchAll = async () => {
const { data } = await getRequest("api/posts?populate=*");

useEffect(() => {
if (data?.data) {
setPosts(data?.data);
}
};

useEffect(() => {
fetchAll();
}, []);
}, [data]);

useEffect(() => {
if (posts) {
@@ -61,38 +57,39 @@ const HomeScreen = ({ navigation }) => {
}, [posts]);
return (
<Layout>
<View style={styles.wrapper}>
<Text style={{ fontSize: 18, color: colors.textPrimary }}>
{t("common.hello")}, Diligent
</Text>
<TouchableOpacity onPress={() => navigation.openDrawer()}>
<ImageBackground
source={require("../assets/images/diligent-purple.png")}
style={styles.imageBackground}
imageStyle={{ borderRadius: 25 }}
/>
</TouchableOpacity>
</View>
<View style={styles.search}>
<Feather
name="search"
size={20}
color="#C6C6C6"
style={{ marginRight: 5 }}
/>
<TextInput
onChangeText={(text) => searchFilter(text)}
autoCapitalize="none"
autoCorrect={false}
placeholder={t('common.search')}
value={query}
placeholderTextColor="#C6C6C6"
style={{ flex: 1, color: colors.textPrimary }}
<Loader visible={isLoading} />
<View style={styles.wrapper}>
<Text style={{ fontSize: 18, color: colors.textPrimary }}>
{t("common.hello")}, Diligent
</Text>
<TouchableOpacity onPress={() => navigation.openDrawer()}>
<ImageBackground
source={require("../assets/images/diligent-purple.png")}
style={styles.imageBackground}
imageStyle={{ borderRadius: 25 }}
/>
</View>
<Text>
{filteredData.length === 0 && (
<View>
</TouchableOpacity>
</View>
<View style={styles.search}>
<Feather
name="search"
size={20}
color="#C6C6C6"
style={{ marginRight: 5 }}
/>
<TextInput
onChangeText={(text) => searchFilter(text)}
autoCapitalize="none"
autoCorrect={false}
placeholder={t("common.search")}
value={query}
placeholderTextColor="#C6C6C6"
style={{ flex: 1, color: colors.textPrimary }}
/>
</View>
<Text>
{filteredData?.length === 0 && (
<View style={{paddingHorizontal: 18}}>
<Text
style={[globalStyles.boldText, { color: colors.textPrimary }]}
>
@@ -100,38 +97,44 @@ const HomeScreen = ({ navigation }) => {
</Text>
</View>
)}
</Text>
<ScrollView style={{flex: 1,paddingHorizontal: 18}}>
{query.length === 0
? posts?.map((post) => (
<ListItem
key={post?.id}
title={post?.attributes?.title}
photo={post?.attributes?.profileImage?.data?.attributes?.formats}
publishedAt={post?.attributes?.publishedAt}
onPress={() =>
navigation.navigate("PostDetails", {
title: post?.attributes?.title,
id: post?.id,
})
}
/>
))
: filteredData.map((post) => (
<ListItem
key={post?.id}
title={post?.attributes?.title}
photo={post?.attributes?.profileImage?.data?.attributes?.formats}
publishedAt={post?.attributes?.publishedAt}
onPress={() =>
navigation.navigate("PostDetails", {
title: post?.attributes?.title,
id: post?.id,
})
}
/>
))}
</ScrollView>
{!isLoading && (
<ScrollView style={{ flex: 1, paddingHorizontal: 18 }}>
{query.length === 0
? posts?.map((post) => (
<ListItem
key={post?.id}
title={post?.attributes?.title}
photo={
post?.attributes?.profileImage?.data?.attributes?.formats
}
publishedAt={post?.attributes?.publishedAt}
onPress={() =>
navigation.navigate("PostDetails", {
title: post?.attributes?.title,
id: post?.id,
})
}
/>
))
: filteredData.map((post) => (
<ListItem
key={post?.id}
title={post?.attributes?.title}
photo={
post?.attributes?.profileImage?.data?.attributes?.formats
}
publishedAt={post?.attributes?.publishedAt}
onPress={() =>
navigation.navigate("PostDetails", {
title: post?.attributes?.title,
id: post?.id,
})
}
/>
))}
</ScrollView>
)}
</Text>
</Layout>
);
};
@@ -141,7 +144,7 @@ const styles = StyleSheet.create({
flexDirection: "row",
justifyContent: "space-between",
marginBottom: 20,
padding: 18
padding: 18,
},
imageBackground: {
width: 35,
@@ -155,7 +158,7 @@ const styles = StyleSheet.create({
paddingHorizontal: 10,
paddingVertical: 8,
marginBottom: 20,
marginHorizontal: 18
marginHorizontal: 18,
},
});


+ 57
- 43
screens/LoginScreen.jsx Bestand weergeven

@@ -1,73 +1,80 @@
import React, { useEffect } from "react";
import React, { useEffect, useState } from "react";
import { View, Text, TouchableOpacity, StyleSheet } from "react-native";
import MaterialIcons from "@expo/vector-icons/MaterialIcons";
import Ionicons from "@expo/vector-icons/Ionicons";

import LoginSVG from "@assets/images/login.svg";
import GoogleSVG from "@assets/images/google.svg";
import FacebookSVG from "@assets/images/facebook.svg";
import TwitterSVG from "@assets/images/twitter.svg";

import CustomButton from "@components/Buttons/CustomButton";
import InputField from "@components/InputField";
import { globalStyles } from "@styles/global";
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 { useDispatch } from "react-redux";
import useAuthHook from "../hooks/useAuthHook";
import { storeData } from "@service/asyncStorage";
import { ACCESS_TOKEN } from "@constants/localStorage";
import Layout from "@components/Layout/Layout";
import { useTheme } from "@styles";
import { useTranslation } from "react-i18next";
import { useLoginMutation } from "@features/auth/authApiSlice";
import { setCredentials } from "@features/auth/authSlice";
import { useAuthProviderMutation } from "@features/auth/authApiSlice";
import { storeData } from "@service/asyncStorage";
import { ACCESS_TOKEN } from "@constants/localStorage";

const LoginScreen = ({ navigation }) => {
const [authProvider, { isLoading: isLoadingProvider }] =
useAuthProviderMutation();
const [currentProvider, setCurrentProvider] = useState("");
const { colors } = useTheme();
const { t } = useTranslation();
const [login, { isLoading, error }] = useLoginMutation();
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);
};

useEffect(() => {
const authProviderHandler = async (response) => {
if (response?.type === "success") {
const accessToken = response.authentication.accessToken;
if (accessToken) {
storeToken(accessToken);
dispatch(fetchAuthProvider({ accessToken }));
await storeData(ACCESS_TOKEN, accessToken)
try {
const userResponse = await authProvider({
provider: currentProvider,
accessToken,
}).unwrap();
if (userResponse) {
dispatch(setCredentials(userResponse));
}
} catch (e) {
console.log(e);
}
}
}
}, [response]);
};

useEffect(() => {
authProviderHandler(response);
}, [response, currentProvider]);

const handleGoogleAuth = () => {
promptAsync({ useProxy: true, showInRecents: true });
promptAsync();
};

const handleLogin = (values) => {
const { email, password } = values;
dispatch(clearLoginErrors());
dispatch(
fetchUser({
identifier: email,
password,
})
);
const handleLogin = async (values) => {
const { email: identifier, password } = values;
try {
const userData = await login({ identifier, password }).unwrap();
dispatch(setCredentials(userData));
} catch (e) {
console.log("Login error", e);
}
};

return (
<Layout>
<Loader visible={isLoading} />
<Loader visible={isLoading || isLoadingProvider} />
<View style={{ paddingHorizontal: 25 }}>
<View style={{ alignItems: "center" }}>
<LoginSVG height={300} width={300} />
@@ -94,11 +101,11 @@ const LoginScreen = ({ navigation }) => {
<Text
style={[globalStyles.boldText, { color: colors.textPrimary }]}
>
{t('login.signIn')}
{t("login.signIn")}
</Text>
<InputField
name="email"
label={t('login.email')}
label={t("login.email")}
keyboardType="email-address"
onChangeText={handleChange("email")}
text={values.email}
@@ -118,8 +125,8 @@ const LoginScreen = ({ navigation }) => {
)}
<InputField
name="password"
label={t('login.password')}
filedButtonLabel={t('register.forgot')}
label={t("login.password")}
filedButtonLabel={t("register.forgot")}
fieldButtonFunction={() => {}}
inputType="password"
handleBlur={handleBlur("password")}
@@ -137,8 +144,12 @@ const LoginScreen = ({ navigation }) => {
{errors.password && (
<Text style={styles.errorMessage}>{errors.password}</Text>
)}
{error && <Text style={styles.errorMessage}>{error}</Text>}
<CustomButton label={t('login.login')} onPress={handleSubmit} />
{error && (
<Text style={styles.errorMessage}>
{error?.data?.error?.message}
</Text>
)}
<CustomButton label={t("login.login")} onPress={handleSubmit} />
</>
)}
</Formik>
@@ -148,11 +159,14 @@ const LoginScreen = ({ navigation }) => {
{ color: colors.textPrimary },
]}
>
{t('login.orLoginWith')}
{t("login.orLoginWith")}
</Text>
<View style={styles.providersContainer}>
<TouchableOpacity
onPress={handleGoogleAuth}
onPress={() => {
setCurrentProvider("google");
handleGoogleAuth();
}}
style={globalStyles.iconButton}
>
<GoogleSVG height={24} width={24} />
@@ -168,10 +182,10 @@ const LoginScreen = ({ navigation }) => {
<Text
style={[globalStyles.regularText, { color: colors.textPrimary }]}
>
{t('login.needAccount')}{" "}
{t("login.needAccount")}{" "}
</Text>
<TouchableOpacity onPress={() => navigation.navigate("Register")}>
<Text style={styles.registerButtonText}>{t('login.signUp')}</Text>
<Text style={styles.registerButtonText}>{t("login.signUp")}</Text>
</TouchableOpacity>
</View>
</View>

+ 33
- 38
screens/PostDetailsScreen.jsx Bestand weergeven

@@ -1,54 +1,49 @@
import React, { useEffect, useState } from "react";
import { Text, Image, StyleSheet } from "react-native";
import { getRequest } from "@request/index";
import { globalStyles } from "@styles/global";
import { windowWidth } from "@utils/Dimensions";
import Layout from "@components/Layout/Layout";
import { useTheme } from "@styles";
import { useSinglePostQuery } from "@features/posts/postsApiSlice";
import Loader from "@components/Loader";

const PostDetailsScreen = ({ navigation, route }) => {
const [post, setPost] = useState({});
const { colors } = useTheme();

const fetchPost = async () => {
const { data } = await getRequest(
`api/posts/${route.params.id}?populate=*`
);
if (data) {
setPost(data.data);
}
};

useEffect(() => {
fetchPost();
}, []);
const { data: post, isLoading } = useSinglePostQuery(route.params.id);

return (
<Layout>
<Image
style={styles.image}
source={{
uri: `https://strapi.dilig.net${post?.attributes?.profileImage.data.attributes.url}`,
}}
/>
<Text
style={[
globalStyles.boldText,
styles.title,
{ color: colors.textPrimary },
]}
>
{post?.attributes?.title}
</Text>
<Text
style={[
globalStyles.regularText,
styles.description,
{ color: colors.textPrimary },
]}
>
{post?.attributes?.description}
</Text>
{isLoading ? (
<Loader visible={isLoading} />
) : (
<>
<Image
style={styles.image}
source={{
uri: `https://strapi.dilig.net${post.data?.attributes?.profileImage.data.attributes.url}`,
}}
/>
<Text
style={[
globalStyles.boldText,
styles.title,
{ color: colors.textPrimary },
]}
>
{post?.data?.attributes?.title}
</Text>
<Text
style={[
globalStyles.regularText,
styles.description,
{ color: colors.textPrimary },
]}
>
{post?.data?.attributes?.description}
</Text>
</>
)}
</Layout>
);
};

+ 13
- 13
screens/ProfileScreen.jsx Bestand weergeven

@@ -4,22 +4,22 @@ import Layout from "@components/Layout/Layout";
import { useTheme } from "@styles";

const ProfileScreen = () => {
const { colors } = useTheme();
return (
<Layout>
const { colors } = useTheme();
return (
<Layout>
<View style={styles.container}>
<Text style={{color: colors.textPrimary}}>Profile</Text>
<Text style={{ color: colors.textPrimary }}>Profile</Text>
</View>
</Layout>
)
}
);
};

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
}
})
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
});

export default ProfileScreen;
export default ProfileScreen;

+ 56
- 43
screens/RegisterScreen.jsx Bestand weergeven

@@ -1,4 +1,4 @@
import React, { useEffect } from "react";
import React, { useEffect, useState } from "react";
import {
ScrollView,
View,
@@ -7,52 +7,40 @@ import {
StyleSheet,
Alert,
} from "react-native";

import InputField from "@components/InputField";

import MaterialIcons from "@expo/vector-icons/MaterialIcons";
import Ionicons from "@expo/vector-icons/Ionicons";

import RegistrationSVG from "@assets/images/registration.svg";
import GoogleSVG from "@assets/images/google.svg";
import FacebookSVG from "@assets/images/facebook.svg";
import TwitterSVG from "@assets/images/twitter.svg";

import CustomButton from "@components/Buttons/CustomButton";
import { globalStyles } from "@styles/global";
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 { useDispatch } from "react-redux";
import useAuthHook from "../hooks/useAuthHook";
import { fetchAuthProvider } from "@store/actions/authProvider/authProviderActions";
import { ACCESS_TOKEN } from "@constants/localStorage";
import { storeData } from "@service/asyncStorage";
import Layout from "@components/Layout/Layout";
import { useTheme } from "@styles";
import { useTranslation } from "react-i18next";
import {
useAuthProviderMutation,
useRegisterMutation,
} from "@features/auth/authApiSlice";
import { setCredentials } from "@features/auth/authSlice";
import { storeData } from "@service/asyncStorage";
import { ACCESS_TOKEN } from "@constants/localStorage";

const RegisterScreen = ({ navigation }) => {
const [authProvider, { isLoading: isLoadingProvider }] =
useAuthProviderMutation();
const [currentProvider, setCurrentProvider] = useState("");
const { colors } = useTheme();
const { t } = useTranslation();
const [register, { isLoading, error }] = useRegisterMutation();
const { response, promptAsync } = useAuthHook();
const dispatch = useDispatch();
const error = useSelector(selectRegisterError);

const isLoading = useSelector(
selectIsLoadingByActionType(REGISTER_USER_SCOPE)
);

const storeToken = async (token) => {
await storeData(ACCESS_TOKEN, token);
};

const handleApiResponseSuccess = () => {
Alert.alert(t("common.success"), t("register.successRegisterAccount"), [
@@ -63,27 +51,45 @@ const RegisterScreen = ({ navigation }) => {
]);
};

const handleSignup = (values) => {
const { username, email, password } = values;
dispatch(clearRegisterErrors());
dispatch(
registerUser({ username, email, password, handleApiResponseSuccess })
);
};

const handleGoogleAuth = () => {
promptAsync({ useProxy: true, showInRecents: true });
};

useEffect(() => {
const authProviderHandler = async (response) => {
if (response?.type === "success") {
const accessToken = response.authentication.accessToken;
if (accessToken) {
storeToken(accessToken);
dispatch(fetchAuthProvider({ accessToken }));
await storeData(ACCESS_TOKEN, accessToken);
try {
const userResponse = await authProvider({
provider: currentProvider,
accessToken,
}).unwrap();
if (userResponse) {
dispatch(setCredentials(userResponse));
}
} catch (e) {
console.log(e);
}
}
}
}, [response]);
};

useEffect(() => {
authProviderHandler(response);
}, [response, currentProvider]);

const handleSignup = async (values) => {
const { username, email, password } = values;
try {
const userData = await register({ username, email, password }).unwrap();
if (userData) {
handleApiResponseSuccess();
}
} catch (e) {
console.log(e?.data);
}
};

const handleGoogleAuth = () => {
promptAsync();
};

return (
<Layout>
@@ -100,7 +106,10 @@ const RegisterScreen = ({ navigation }) => {
</Text>
<View style={styles.providersContainer}>
<TouchableOpacity
onPress={handleGoogleAuth}
onPress={() => {
setCurrentProvider("google");
handleGoogleAuth();
}}
style={globalStyles.iconButton}
>
<GoogleSVG height={24} width={24} />
@@ -218,7 +227,11 @@ const RegisterScreen = ({ navigation }) => {
{errors.confirmPassword}
</Text>
)}
{error && <Text style={styles.errorMessage}>{error}</Text>}
{error && (
<Text style={styles.errorMessage}>
{error?.data?.error?.message}
</Text>
)}
<CustomButton
label={t("register.signUp")}
onPress={handleSubmit}

+ 0
- 21
store/actions/actionHelpers/index.js Bestand weergeven

@@ -1,21 +0,0 @@
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
- 5
store/actions/app/appActionConstants.js Bestand weergeven

@@ -1,5 +0,0 @@
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
- 11
store/actions/app/appActions.js Bestand weergeven

@@ -1,11 +0,0 @@
import { ADD_LOADER, REMOVE_LOADER } from "./appActionConstants";

export const addLoader = (payload) => ({
type: ADD_LOADER,
payload,
});

export const removeLoader = (payload) => ({
type: REMOVE_LOADER,
payload,
});

+ 0
- 10
store/actions/authProvider/authProviderActionConstants.js Bestand weergeven

@@ -1,10 +0,0 @@
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
- 20
store/actions/authProvider/authProviderActions.js Bestand weergeven

@@ -1,20 +0,0 @@
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,
});

+ 0
- 30
store/actions/login/loginActionConstants.js Bestand weergeven

@@ -1,30 +0,0 @@
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
- 48
store/actions/login/loginActions.js Bestand weergeven

@@ -1,48 +0,0 @@
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
- 18
store/actions/register/registerActionConstants.js Bestand weergeven

@@ -1,18 +0,0 @@
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
- 30
store/actions/register/registerActions.js Bestand weergeven

@@ -1,30 +0,0 @@
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
- 3
store/actions/user/userActionConstants.js Bestand weergeven

@@ -1,3 +0,0 @@
export const SET_USER = "SET_USER";
export const SET_USER_ERROR = "SET_USER_ERROR";
export const RESET_USER_STATE = "RESET_USER_STATE";

+ 0
- 19
store/actions/user/userActions.js Bestand weergeven

@@ -1,19 +0,0 @@
import {
RESET_USER_STATE,
SET_USER,
SET_USER_ERROR,
} from './userActionConstants';

export const setUser = (payload) => ({
type: SET_USER,
payload,
});

export const setUserError = (payload) => ({
type: SET_USER_ERROR,
payload,
});

export const resetUserState = () => ({
type: RESET_USER_STATE,
});

+ 0
- 32
store/index.js Bestand weergeven

@@ -1,32 +0,0 @@
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
)
)
);

sagaMiddleware.run(rootSaga);

+ 0
- 51
store/middleware/accessTokenMiddleware.js Bestand weergeven

@@ -1,51 +0,0 @@
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(
"https://strapi.dilig.net/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
- 22
store/middleware/authenticationMiddleware.js Bestand weergeven

@@ -1,22 +0,0 @@
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
- 20
store/middleware/internalServerErrorMiddleware.js Bestand weergeven

@@ -1,20 +0,0 @@
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
- 28
store/middleware/loadingMiddleware.js Bestand weergeven

@@ -1,28 +0,0 @@
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)) {
dispatch(removeLoader(action.type));
return next(action);
}
next(action);
};

+ 0
- 27
store/middleware/requestStatusMiddleware.js Bestand weergeven

@@ -1,27 +0,0 @@
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
- 33
store/reducers/authProvider/authProviderReducer.js Bestand weergeven

@@ -1,33 +0,0 @@
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,
};
}

+ 0
- 14
store/reducers/index.js Bestand weergeven

@@ -1,14 +0,0 @@
import { combineReducers } from "redux";
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";

export default combineReducers({
login: loginReducer,
user: userReducer,
loading: loadingReducer,
register: registerReducer,
authProvider: authProviderReducer,
});

+ 0
- 45
store/reducers/loading/loadingReducer.js Bestand weergeven

@@ -1,45 +0,0 @@
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
- 69
store/reducers/login/loginReducer.js Bestand weergeven

@@ -1,69 +0,0 @@
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
- 54
store/reducers/register/registerReducer.js Bestand weergeven

@@ -1,54 +0,0 @@
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: '',
};
}

+ 0
- 37
store/reducers/user/userReducer.js Bestand weergeven

@@ -1,37 +0,0 @@
import createReducer from "../../utils/createReducer";
import {
RESET_USER_STATE,
SET_USER,
SET_USER_ERROR,
} from "../../actions/user/userActionConstants";

const initialState = {
user: {},
};

export default createReducer(
{
[SET_USER]: setUser,
[SET_USER_ERROR]: setUserError,
[RESET_USER_STATE]: resetUser,
},
initialState
);

function setUser(state, action) {
return {
...state,
user: action.payload,
};
}

function setUserError(state, action) {
return {
...state,
errorMessage: action.payload,
};
}

function resetUser() {
return initialState;
}

+ 0
- 42
store/saga/authProviderSaga.js Bestand weergeven

@@ -1,42 +0,0 @@
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
- 8
store/saga/index.js Bestand weergeven

@@ -1,8 +0,0 @@
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
- 82
store/saga/loginSaga.js Bestand weergeven

@@ -1,82 +0,0 @@
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 { resetUserState, setUser } from "../actions/user/userActions";
import { addHeaderToken, removeHeaderToken } from "../../request";
import {
JWT_REFRESH_TOKEN,
JWT_TOKEN,
} from "@constants/localStorage";
import { storeData, getData, removeData } 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(removeData, JWT_REFRESH_TOKEN)
yield call(removeData, JWT_TOKEN);
yield put(resetLoginState());
yield put(resetUserState());
}
}

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
- 32
store/saga/registerSaga.js Bestand weergeven

@@ -1,32 +0,0 @@
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
- 8
store/selectors/authProviderSelectors.js Bestand weergeven

@@ -1,8 +0,0 @@
import createSelector from "reselect";

const authProviderSelector = (state) => state.authProvider;

export const selectLoginError = createSelector(
authProviderSelector,
(state) => state.errorMessage
);

+ 0
- 16
store/selectors/loadingSelectors.js Bestand weergeven

@@ -1,16 +0,0 @@
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
- 29
store/selectors/loginSelectors.js Bestand weergeven

@@ -1,29 +0,0 @@
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
- 8
store/selectors/registerSelectors.js Bestand weergeven

@@ -1,8 +0,0 @@
import { createSelector } from 'reselect';

const registerSelector = (state) => state.register;

export const selectRegisterError = createSelector(
registerSelector,
(state) => state.errorMessage,
);

+ 0
- 28
store/selectors/userSelectors.js Bestand weergeven

@@ -1,28 +0,0 @@
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
- 13
store/utils/createReducer.js Bestand weergeven

@@ -1,13 +0,0 @@
const createReducer = (handlers, initialState) => {
return (state = initialState, action) => {
const reducer = handlers[action.type];

if (!reducer) {
return state;
}

return reducer(state, action);
};
};

export default createReducer;

Laden…
Annuleren
Opslaan