| @@ -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> | |||
| ); | |||
| }; | |||
| @@ -10,7 +10,7 @@ | |||
| "resizeMode": "contain", | |||
| "backgroundColor": "#ffffff" | |||
| }, | |||
| "scheme": "com.diligent.template", | |||
| "scheme": "diligent", | |||
| "updates": { | |||
| "fallbackToCacheTimeout": 0 | |||
| }, | |||
| @@ -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", | |||
| }, | |||
| }, | |||
| ], | |||
| ], | |||
| }; | |||
| }; | |||
| @@ -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 ( | |||
| @@ -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) => ({}), | |||
| }); | |||
| @@ -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; | |||
| @@ -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 | |||
| ); | |||
| @@ -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; | |||
| @@ -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); | |||
| @@ -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"; | |||
| @@ -14,7 +14,9 @@ | |||
| "@constants/*": ["./constants/*"], | |||
| "@service/*": ["./service/*"], | |||
| "@i18n/*": ["./i18n/*"], | |||
| "@request/*": ["./request/*"] | |||
| "@request/*": ["./request/*"], | |||
| "@features/*": ["./features/*"], | |||
| "@hooks/*": ["./hooks/*"] | |||
| } | |||
| }, | |||
| "exclude": ["node_modules", "dist"] | |||
| @@ -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,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} | |||
| @@ -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", | |||
| @@ -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": { | |||
| @@ -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, | |||
| }, | |||
| }); | |||
| @@ -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> | |||
| @@ -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> | |||
| ); | |||
| }; | |||
| @@ -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; | |||
| @@ -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} | |||
| @@ -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); | |||
| @@ -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"); | |||
| @@ -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, | |||
| }); | |||
| @@ -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); | |||
| @@ -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, | |||
| }); | |||
| @@ -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); | |||
| @@ -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 | |||
| }); | |||
| @@ -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"; | |||
| @@ -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, | |||
| }); | |||
| @@ -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"; | |||
| @@ -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, | |||
| }); | |||
| @@ -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); | |||
| @@ -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); | |||
| }; | |||
| @@ -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); | |||
| }; | |||
| @@ -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); | |||
| }; | |||
| @@ -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); | |||
| }; | |||
| @@ -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); | |||
| }; | |||
| @@ -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, | |||
| }; | |||
| } | |||
| @@ -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, | |||
| }); | |||
| @@ -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, | |||
| }; | |||
| } | |||
| @@ -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: '', | |||
| }; | |||
| } | |||
| @@ -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: '', | |||
| }; | |||
| } | |||
| @@ -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; | |||
| } | |||
| @@ -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)]); | |||
| } | |||
| @@ -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()]); | |||
| } | |||
| @@ -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), | |||
| ]); | |||
| } | |||
| @@ -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)]); | |||
| } | |||
| @@ -1,8 +0,0 @@ | |||
| import createSelector from "reselect"; | |||
| const authProviderSelector = (state) => state.authProvider; | |||
| export const selectLoginError = createSelector( | |||
| authProviderSelector, | |||
| (state) => state.errorMessage | |||
| ); | |||
| @@ -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 | |||
| ); | |||
| @@ -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, | |||
| ); | |||
| @@ -1,8 +0,0 @@ | |||
| import { createSelector } from 'reselect'; | |||
| const registerSelector = (state) => state.register; | |||
| export const selectRegisterError = createSelector( | |||
| registerSelector, | |||
| (state) => state.errorMessage, | |||
| ); | |||
| @@ -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, | |||
| ); | |||
| @@ -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; | |||