| import "react-native-gesture-handler"; | import "react-native-gesture-handler"; | ||||
| import React, { useEffect } from "react"; | import React, { useEffect } from "react"; | ||||
| import { NavigationContainer } from "@react-navigation/native"; | 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 { ThemeProvider } from "@styles"; | ||||
| import '@i18n' | |||||
| import "@i18n"; | |||||
| import { useFonts } from "expo-font"; | import { useFonts } from "expo-font"; | ||||
| import RootNavigation from "./navigation/RootNavigation"; | 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 { useTranslation } from "react-i18next"; | ||||
| import { getObjectData } from "@service/asyncStorage"; | |||||
| import { LANGUAGE } from "@constants/localStorage"; | |||||
| function App() { | function App() { | ||||
| const { i18n } = useTranslation(); | const { i18n } = useTranslation(); | ||||
| const dispatch = useDispatch(); | |||||
| const initialSetup = async () => { | const initialSetup = async () => { | ||||
| const token = await getData(JWT_TOKEN); | |||||
| const refreshToken = await getData(JWT_REFRESH_TOKEN); | |||||
| const language = await getObjectData(LANGUAGE); | const language = await getObjectData(LANGUAGE); | ||||
| if (language !== null) { | if (language !== null) { | ||||
| await i18n.changeLanguage(language.code); | await i18n.changeLanguage(language.code); | ||||
| } | } | ||||
| if (token) { | |||||
| addHeaderToken(token); | |||||
| } | |||||
| if (token !== undefined && refreshToken !== undefined) { | |||||
| dispatch(fetchUserSuccess({ jwt: token, refreshToken })); | |||||
| } | |||||
| }; | }; | ||||
| useEffect(() => { | useEffect(() => { | ||||
| return ( | return ( | ||||
| <Provider store={store}> | <Provider store={store}> | ||||
| <ThemeProvider> | |||||
| <App /> | |||||
| </ThemeProvider> | |||||
| <PersistGate loading={null} persistor={persistor}> | |||||
| <ThemeProvider> | |||||
| <App /> | |||||
| </ThemeProvider> | |||||
| </PersistGate> | |||||
| </Provider> | </Provider> | ||||
| ); | ); | ||||
| }; | }; |
| "resizeMode": "contain", | "resizeMode": "contain", | ||||
| "backgroundColor": "#ffffff" | "backgroundColor": "#ffffff" | ||||
| }, | }, | ||||
| "scheme": "com.diligent.template", | |||||
| "scheme": "diligent", | |||||
| "updates": { | "updates": { | ||||
| "fallbackToCacheTimeout": 0 | "fallbackToCacheTimeout": 0 | ||||
| }, | }, |
| module.exports = function(api) { | |||||
| module.exports = function (api) { | |||||
| api.cache(true); | api.cache(true); | ||||
| return { | return { | ||||
| presets: ['babel-preset-expo'], | |||||
| presets: ["babel-preset-expo"], | |||||
| plugins: [ | plugins: [ | ||||
| 'react-native-reanimated/plugin', | |||||
| "react-native-reanimated/plugin", | |||||
| [ | [ | ||||
| "module-resolver", | "module-resolver", | ||||
| { | { | ||||
| "@components": "./components", | "@components": "./components", | ||||
| "@screens": "./screens", | "@screens": "./screens", | ||||
| "@assets": "./assets", | "@assets": "./assets", | ||||
| "@store": './store', | |||||
| "@styles": './styles', | |||||
| "@store": "./store", | |||||
| "@styles": "./styles", | |||||
| "@utils": "./utils", | "@utils": "./utils", | ||||
| "@schemas": "./schemas", | "@schemas": "./schemas", | ||||
| "@initialValues": "./initialValues", | "@initialValues": "./initialValues", | ||||
| "@constants": "./constants", | "@constants": "./constants", | ||||
| "@service": "./service", | "@service": "./service", | ||||
| "@i18n": "./i18n", | "@i18n": "./i18n", | ||||
| "@request": "./request" | |||||
| } | |||||
| } | |||||
| ] | |||||
| ] | |||||
| "@request": "./request", | |||||
| "@features": "./features", | |||||
| "@hooks": "./hooks", | |||||
| }, | |||||
| }, | |||||
| ], | |||||
| ], | |||||
| }; | }; | ||||
| }; | }; |
| import { MaterialIcons } from "@expo/vector-icons"; | import { MaterialIcons } from "@expo/vector-icons"; | ||||
| import { Ionicons } from "@expo/vector-icons"; | import { Ionicons } from "@expo/vector-icons"; | ||||
| import { useDispatch } from "react-redux"; | import { useDispatch } from "react-redux"; | ||||
| import { logoutUser } from "@store/actions/login/loginActions"; | |||||
| import { logOut } from "@features/auth/authSlice"; | |||||
| import useAuthHook from "../../hooks/useAuthHook"; | import useAuthHook from "../../hooks/useAuthHook"; | ||||
| import { useTheme } from "@styles"; | import { useTheme } from "@styles"; | ||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| const handleLogout = async () => { | const handleLogout = async () => { | ||||
| logoutAuthProvider(); | logoutAuthProvider(); | ||||
| dispatch(logoutUser()); | |||||
| dispatch(logOut()); | |||||
| }; | }; | ||||
| return ( | return ( |
| 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) => ({}), | |||||
| }); |
| 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; |
| 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 | |||||
| ); |
| 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; |
| 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); |
| import React from "react"; | |||||
| import * as Google from "expo-auth-session/providers/google"; | import * as Google from "expo-auth-session/providers/google"; | ||||
| import { getData } from "../service/asyncStorage"; | import { getData } from "../service/asyncStorage"; | ||||
| import { ACCESS_TOKEN } from "../constants/localStorage"; | import { ACCESS_TOKEN } from "../constants/localStorage"; |
| "@constants/*": ["./constants/*"], | "@constants/*": ["./constants/*"], | ||||
| "@service/*": ["./service/*"], | "@service/*": ["./service/*"], | ||||
| "@i18n/*": ["./i18n/*"], | "@i18n/*": ["./i18n/*"], | ||||
| "@request/*": ["./request/*"] | |||||
| "@request/*": ["./request/*"], | |||||
| "@features/*": ["./features/*"], | |||||
| "@hooks/*": ["./hooks/*"] | |||||
| } | } | ||||
| }, | }, | ||||
| "exclude": ["node_modules", "dist"] | "exclude": ["node_modules", "dist"] |
| name="Home" | name="Home" | ||||
| component={TabNavigator} | component={TabNavigator} | ||||
| options={{ | options={{ | ||||
| unmountOnBlur: true, | |||||
| title:t("sidebar.home"), | title:t("sidebar.home"), | ||||
| drawerIcon: ({ color }) => ( | drawerIcon: ({ color }) => ( | ||||
| <Ionicons name="home-outline" size={22} color={color} /> | <Ionicons name="home-outline" size={22} color={color} /> |
| import AuthStack from "./AuthStack"; | import AuthStack from "./AuthStack"; | ||||
| import { SafeAreaView } from "react-native-safe-area-context"; | import { SafeAreaView } from "react-native-safe-area-context"; | ||||
| import { useSelector } from "react-redux"; | import { useSelector } from "react-redux"; | ||||
| import { selectTokens } from "@store/selectors/loginSelectors"; | |||||
| import { selectCurrentToken } from "@features/auth/authSlice"; | |||||
| import { StatusBar } from "expo-status-bar"; | import { StatusBar } from "expo-status-bar"; | ||||
| import { useTheme } from "@styles"; | import { useTheme } from "@styles"; | ||||
| const RootNavigation = () => { | const RootNavigation = () => { | ||||
| const { isDark, colors } = useTheme(); | const { isDark, colors } = useTheme(); | ||||
| const tokens = useSelector(selectTokens); | |||||
| const tokens = useSelector(selectCurrentToken); | |||||
| return !tokens.JwtToken ? ( | |||||
| return !tokens ? ( | |||||
| <SafeAreaView style={{ flex: 1, backgroundColor: colors.background }}> | <SafeAreaView style={{ flex: 1, backgroundColor: colors.background }}> | ||||
| <StatusBar | <StatusBar | ||||
| backgroundColor={colors.background} | backgroundColor={colors.background} |
| "@react-navigation/drawer": "^6.5.5", | "@react-navigation/drawer": "^6.5.5", | ||||
| "@react-navigation/native": "^6.0.16", | "@react-navigation/native": "^6.0.16", | ||||
| "@react-navigation/native-stack": "^6.9.4", | "@react-navigation/native-stack": "^6.9.4", | ||||
| "@reduxjs/toolkit": "^1.9.5", | |||||
| "axios": "^1.2.1", | "axios": "^1.2.1", | ||||
| "expo": "^48.0.19", | "expo": "^48.0.19", | ||||
| "expo-auth-session": "~4.0.3", | "expo-auth-session": "~4.0.3", | ||||
| "react-native-svg-transformer": "^1.0.0", | "react-native-svg-transformer": "^1.0.0", | ||||
| "react-native-vector-icons": "^9.2.0", | "react-native-vector-icons": "^9.2.0", | ||||
| "react-native-web": "~0.18.7", | "react-native-web": "~0.18.7", | ||||
| "react-redux": "^8.0.5", | |||||
| "react-redux": "^8.1.1", | |||||
| "redux": "^4.2.0", | "redux": "^4.2.0", | ||||
| "redux-persist": "^6.0.0", | |||||
| "redux-saga": "^1.2.2", | "redux-saga": "^1.2.2", | ||||
| "reselect": "^4.1.8", | |||||
| "yup": "^0.32.11" | "yup": "^0.32.11" | ||||
| }, | }, | ||||
| "devDependencies": { | "devDependencies": { | ||||
| "resolved": "https://registry.npmjs.org/@redux-saga/types/-/types-1.2.1.tgz", | "resolved": "https://registry.npmjs.org/@redux-saga/types/-/types-1.2.1.tgz", | ||||
| "integrity": "sha512-1dgmkh+3so0+LlBWRhGA33ua4MYr7tUOj+a9Si28vUi0IUFNbff1T3sgpeDJI/LaC75bBYnQ0A3wXjn0OrRNBA==" | "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": { | "node_modules/@segment/loosely-validate-event": { | ||||
| "version": "2.0.0", | "version": "2.0.0", | ||||
| "resolved": "https://registry.npmjs.org/@segment/loosely-validate-event/-/loosely-validate-event-2.0.0.tgz", | "resolved": "https://registry.npmjs.org/@segment/loosely-validate-event/-/loosely-validate-event-2.0.0.tgz", | ||||
| "node": ">=4.0" | "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": { | "node_modules/import-fresh": { | ||||
| "version": "2.0.0", | "version": "2.0.0", | ||||
| "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", | "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", | ||||
| } | } | ||||
| }, | }, | ||||
| "node_modules/react-redux": { | "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": { | "dependencies": { | ||||
| "@babel/runtime": "^7.12.1", | "@babel/runtime": "^7.12.1", | ||||
| "@types/hoist-non-react-statics": "^3.3.1", | "@types/hoist-non-react-statics": "^3.3.1", | ||||
| "react": "^16.8 || ^17.0 || ^18.0", | "react": "^16.8 || ^17.0 || ^18.0", | ||||
| "react-dom": "^16.8 || ^17.0 || ^18.0", | "react-dom": "^16.8 || ^17.0 || ^18.0", | ||||
| "react-native": ">=0.59", | "react-native": ">=0.59", | ||||
| "redux": "^4" | |||||
| "redux": "^4 || ^5.0.0-beta.0" | |||||
| }, | }, | ||||
| "peerDependenciesMeta": { | "peerDependenciesMeta": { | ||||
| "@types/react": { | "@types/react": { | ||||
| "@babel/runtime": "^7.9.2" | "@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": { | "node_modules/redux-saga": { | ||||
| "version": "1.2.3", | "version": "1.2.3", | ||||
| "resolved": "https://registry.npmjs.org/redux-saga/-/redux-saga-1.2.3.tgz", | "resolved": "https://registry.npmjs.org/redux-saga/-/redux-saga-1.2.3.tgz", | ||||
| "@redux-saga/core": "^1.2.3" | "@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": { | "node_modules/regenerate": { | ||||
| "version": "1.4.2", | "version": "1.4.2", | ||||
| "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", | "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", | ||||
| "resolved": "https://registry.npmjs.org/@redux-saga/types/-/types-1.2.1.tgz", | "resolved": "https://registry.npmjs.org/@redux-saga/types/-/types-1.2.1.tgz", | ||||
| "integrity": "sha512-1dgmkh+3so0+LlBWRhGA33ua4MYr7tUOj+a9Si28vUi0IUFNbff1T3sgpeDJI/LaC75bBYnQ0A3wXjn0OrRNBA==" | "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": { | "@segment/loosely-validate-event": { | ||||
| "version": "2.0.0", | "version": "2.0.0", | ||||
| "resolved": "https://registry.npmjs.org/@segment/loosely-validate-event/-/loosely-validate-event-2.0.0.tgz", | "resolved": "https://registry.npmjs.org/@segment/loosely-validate-event/-/loosely-validate-event-2.0.0.tgz", | ||||
| "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.6.3.tgz", | "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.6.3.tgz", | ||||
| "integrity": "sha512-47xSUiQioGaB96nqtp5/q55m0aBQSQdyIloMOc/x+QVTDZLNmXE892IIDrJ0hM1A5vcNUDD5tDffkSP5lCaIIA==" | "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": { | "import-fresh": { | ||||
| "version": "2.0.0", | "version": "2.0.0", | ||||
| "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", | "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", | ||||
| } | } | ||||
| }, | }, | ||||
| "react-redux": { | "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": { | "requires": { | ||||
| "@babel/runtime": "^7.12.1", | "@babel/runtime": "^7.12.1", | ||||
| "@types/hoist-non-react-statics": "^3.3.1", | "@types/hoist-non-react-statics": "^3.3.1", | ||||
| "@babel/runtime": "^7.9.2" | "@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": { | "redux-saga": { | ||||
| "version": "1.2.3", | "version": "1.2.3", | ||||
| "resolved": "https://registry.npmjs.org/redux-saga/-/redux-saga-1.2.3.tgz", | "resolved": "https://registry.npmjs.org/redux-saga/-/redux-saga-1.2.3.tgz", | ||||
| "@redux-saga/core": "^1.2.3" | "@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": { | "regenerate": { | ||||
| "version": "1.4.2", | "version": "1.4.2", | ||||
| "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", | "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", |
| "@react-navigation/drawer": "^6.5.5", | "@react-navigation/drawer": "^6.5.5", | ||||
| "@react-navigation/native": "^6.0.16", | "@react-navigation/native": "^6.0.16", | ||||
| "@react-navigation/native-stack": "^6.9.4", | "@react-navigation/native-stack": "^6.9.4", | ||||
| "@reduxjs/toolkit": "^1.9.5", | |||||
| "axios": "^1.2.1", | "axios": "^1.2.1", | ||||
| "expo": "^48.0.19", | "expo": "^48.0.19", | ||||
| "expo-auth-session": "~4.0.3", | "expo-auth-session": "~4.0.3", | ||||
| "react-native-svg-transformer": "^1.0.0", | "react-native-svg-transformer": "^1.0.0", | ||||
| "react-native-vector-icons": "^9.2.0", | "react-native-vector-icons": "^9.2.0", | ||||
| "react-native-web": "~0.18.7", | "react-native-web": "~0.18.7", | ||||
| "react-redux": "^8.0.5", | |||||
| "react-redux": "^8.1.1", | |||||
| "redux": "^4.2.0", | "redux": "^4.2.0", | ||||
| "redux-persist": "^6.0.0", | |||||
| "redux-saga": "^1.2.2", | "redux-saga": "^1.2.2", | ||||
| "reselect": "^4.1.8", | |||||
| "yup": "^0.32.11" | "yup": "^0.32.11" | ||||
| }, | }, | ||||
| "devDependencies": { | "devDependencies": { |
| import ListItem from "@components/ListItem/ListItem"; | import ListItem from "@components/ListItem/ListItem"; | ||||
| import filter from "lodash.filter"; | import filter from "lodash.filter"; | ||||
| import { globalStyles } from "@styles/global"; | import { globalStyles } from "@styles/global"; | ||||
| import { getRequest } from "@request/index"; | |||||
| import Layout from "@components/Layout/Layout"; | import Layout from "@components/Layout/Layout"; | ||||
| import { useTheme } from "@styles"; | import { useTheme } from "@styles"; | ||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| import { useAllPostsQuery } from "@features/posts/postsApiSlice"; | |||||
| import Loader from "@components/Loader"; | |||||
| const HomeScreen = ({ navigation }) => { | const HomeScreen = ({ navigation }) => { | ||||
| const { colors } = useTheme(); | const { colors } = useTheme(); | ||||
| const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
| const [posts, setPosts] = useState([]); | |||||
| const { data, isLoading } = useAllPostsQuery(); | |||||
| const [query, setQuery] = useState(""); | const [query, setQuery] = useState(""); | ||||
| const [filteredData, setFilteredData] = useState(posts); | |||||
| const [posts, setPosts] = useState([]); | |||||
| const [filteredData, setFilteredData] = useState([]); | |||||
| const contains = (name, search) => { | const contains = (name, search) => { | ||||
| if (name.includes(search)) { | if (name.includes(search)) { | ||||
| setQuery(formatedText); | setQuery(formatedText); | ||||
| }; | }; | ||||
| const fetchAll = async () => { | |||||
| const { data } = await getRequest("api/posts?populate=*"); | |||||
| useEffect(() => { | |||||
| if (data?.data) { | if (data?.data) { | ||||
| setPosts(data?.data); | setPosts(data?.data); | ||||
| } | } | ||||
| }; | |||||
| useEffect(() => { | |||||
| fetchAll(); | |||||
| }, []); | |||||
| }, [data]); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| if (posts) { | if (posts) { | ||||
| }, [posts]); | }, [posts]); | ||||
| return ( | return ( | ||||
| <Layout> | <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 | <Text | ||||
| style={[globalStyles.boldText, { color: colors.textPrimary }]} | style={[globalStyles.boldText, { color: colors.textPrimary }]} | ||||
| > | > | ||||
| </Text> | </Text> | ||||
| </View> | </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> | </Layout> | ||||
| ); | ); | ||||
| }; | }; | ||||
| flexDirection: "row", | flexDirection: "row", | ||||
| justifyContent: "space-between", | justifyContent: "space-between", | ||||
| marginBottom: 20, | marginBottom: 20, | ||||
| padding: 18 | |||||
| padding: 18, | |||||
| }, | }, | ||||
| imageBackground: { | imageBackground: { | ||||
| width: 35, | width: 35, | ||||
| paddingHorizontal: 10, | paddingHorizontal: 10, | ||||
| paddingVertical: 8, | paddingVertical: 8, | ||||
| marginBottom: 20, | marginBottom: 20, | ||||
| marginHorizontal: 18 | |||||
| marginHorizontal: 18, | |||||
| }, | }, | ||||
| }); | }); | ||||
| import React, { useEffect } from "react"; | |||||
| import React, { useEffect, useState } from "react"; | |||||
| import { View, Text, TouchableOpacity, StyleSheet } from "react-native"; | import { View, Text, TouchableOpacity, StyleSheet } from "react-native"; | ||||
| import MaterialIcons from "@expo/vector-icons/MaterialIcons"; | import MaterialIcons from "@expo/vector-icons/MaterialIcons"; | ||||
| import Ionicons from "@expo/vector-icons/Ionicons"; | import Ionicons from "@expo/vector-icons/Ionicons"; | ||||
| import LoginSVG from "@assets/images/login.svg"; | import LoginSVG from "@assets/images/login.svg"; | ||||
| import GoogleSVG from "@assets/images/google.svg"; | import GoogleSVG from "@assets/images/google.svg"; | ||||
| import FacebookSVG from "@assets/images/facebook.svg"; | import FacebookSVG from "@assets/images/facebook.svg"; | ||||
| import TwitterSVG from "@assets/images/twitter.svg"; | import TwitterSVG from "@assets/images/twitter.svg"; | ||||
| import CustomButton from "@components/Buttons/CustomButton"; | import CustomButton from "@components/Buttons/CustomButton"; | ||||
| import InputField from "@components/InputField"; | import InputField from "@components/InputField"; | ||||
| import { globalStyles } from "@styles/global"; | import { globalStyles } from "@styles/global"; | ||||
| import Loader from "@components/Loader"; | import Loader from "@components/Loader"; | ||||
| import { Formik } from "formik"; | import { Formik } from "formik"; | ||||
| import { loginSchema } from "@schemas/loginSchema"; | 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 useAuthHook from "../hooks/useAuthHook"; | ||||
| import { storeData } from "@service/asyncStorage"; | |||||
| import { ACCESS_TOKEN } from "@constants/localStorage"; | |||||
| import Layout from "@components/Layout/Layout"; | import Layout from "@components/Layout/Layout"; | ||||
| import { useTheme } from "@styles"; | import { useTheme } from "@styles"; | ||||
| import { useTranslation } from "react-i18next"; | 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 LoginScreen = ({ navigation }) => { | ||||
| const [authProvider, { isLoading: isLoadingProvider }] = | |||||
| useAuthProviderMutation(); | |||||
| const [currentProvider, setCurrentProvider] = useState(""); | |||||
| const { colors } = useTheme(); | const { colors } = useTheme(); | ||||
| const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
| const [login, { isLoading, error }] = useLoginMutation(); | |||||
| const { response, promptAsync } = useAuthHook(); | const { response, promptAsync } = useAuthHook(); | ||||
| const dispatch = useDispatch(); | 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") { | if (response?.type === "success") { | ||||
| const accessToken = response.authentication.accessToken; | const accessToken = response.authentication.accessToken; | ||||
| if (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 = () => { | 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 ( | return ( | ||||
| <Layout> | <Layout> | ||||
| <Loader visible={isLoading} /> | |||||
| <Loader visible={isLoading || isLoadingProvider} /> | |||||
| <View style={{ paddingHorizontal: 25 }}> | <View style={{ paddingHorizontal: 25 }}> | ||||
| <View style={{ alignItems: "center" }}> | <View style={{ alignItems: "center" }}> | ||||
| <LoginSVG height={300} width={300} /> | <LoginSVG height={300} width={300} /> | ||||
| <Text | <Text | ||||
| style={[globalStyles.boldText, { color: colors.textPrimary }]} | style={[globalStyles.boldText, { color: colors.textPrimary }]} | ||||
| > | > | ||||
| {t('login.signIn')} | |||||
| {t("login.signIn")} | |||||
| </Text> | </Text> | ||||
| <InputField | <InputField | ||||
| name="email" | name="email" | ||||
| label={t('login.email')} | |||||
| label={t("login.email")} | |||||
| keyboardType="email-address" | keyboardType="email-address" | ||||
| onChangeText={handleChange("email")} | onChangeText={handleChange("email")} | ||||
| text={values.email} | text={values.email} | ||||
| )} | )} | ||||
| <InputField | <InputField | ||||
| name="password" | name="password" | ||||
| label={t('login.password')} | |||||
| filedButtonLabel={t('register.forgot')} | |||||
| label={t("login.password")} | |||||
| filedButtonLabel={t("register.forgot")} | |||||
| fieldButtonFunction={() => {}} | fieldButtonFunction={() => {}} | ||||
| inputType="password" | inputType="password" | ||||
| handleBlur={handleBlur("password")} | handleBlur={handleBlur("password")} | ||||
| {errors.password && ( | {errors.password && ( | ||||
| <Text style={styles.errorMessage}>{errors.password}</Text> | <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> | </Formik> | ||||
| { color: colors.textPrimary }, | { color: colors.textPrimary }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| {t('login.orLoginWith')} | |||||
| {t("login.orLoginWith")} | |||||
| </Text> | </Text> | ||||
| <View style={styles.providersContainer}> | <View style={styles.providersContainer}> | ||||
| <TouchableOpacity | <TouchableOpacity | ||||
| onPress={handleGoogleAuth} | |||||
| onPress={() => { | |||||
| setCurrentProvider("google"); | |||||
| handleGoogleAuth(); | |||||
| }} | |||||
| style={globalStyles.iconButton} | style={globalStyles.iconButton} | ||||
| > | > | ||||
| <GoogleSVG height={24} width={24} /> | <GoogleSVG height={24} width={24} /> | ||||
| <Text | <Text | ||||
| style={[globalStyles.regularText, { color: colors.textPrimary }]} | style={[globalStyles.regularText, { color: colors.textPrimary }]} | ||||
| > | > | ||||
| {t('login.needAccount')}{" "} | |||||
| {t("login.needAccount")}{" "} | |||||
| </Text> | </Text> | ||||
| <TouchableOpacity onPress={() => navigation.navigate("Register")}> | <TouchableOpacity onPress={() => navigation.navigate("Register")}> | ||||
| <Text style={styles.registerButtonText}>{t('login.signUp')}</Text> | |||||
| <Text style={styles.registerButtonText}>{t("login.signUp")}</Text> | |||||
| </TouchableOpacity> | </TouchableOpacity> | ||||
| </View> | </View> | ||||
| </View> | </View> |
| import React, { useEffect, useState } from "react"; | import React, { useEffect, useState } from "react"; | ||||
| import { Text, Image, StyleSheet } from "react-native"; | import { Text, Image, StyleSheet } from "react-native"; | ||||
| import { getRequest } from "@request/index"; | |||||
| import { globalStyles } from "@styles/global"; | import { globalStyles } from "@styles/global"; | ||||
| import { windowWidth } from "@utils/Dimensions"; | import { windowWidth } from "@utils/Dimensions"; | ||||
| import Layout from "@components/Layout/Layout"; | import Layout from "@components/Layout/Layout"; | ||||
| import { useTheme } from "@styles"; | import { useTheme } from "@styles"; | ||||
| import { useSinglePostQuery } from "@features/posts/postsApiSlice"; | |||||
| import Loader from "@components/Loader"; | |||||
| const PostDetailsScreen = ({ navigation, route }) => { | const PostDetailsScreen = ({ navigation, route }) => { | ||||
| const [post, setPost] = useState({}); | |||||
| const { colors } = useTheme(); | 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 ( | return ( | ||||
| <Layout> | <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> | </Layout> | ||||
| ); | ); | ||||
| }; | }; |
| import { useTheme } from "@styles"; | import { useTheme } from "@styles"; | ||||
| const ProfileScreen = () => { | const ProfileScreen = () => { | ||||
| const { colors } = useTheme(); | |||||
| return ( | |||||
| <Layout> | |||||
| const { colors } = useTheme(); | |||||
| return ( | |||||
| <Layout> | |||||
| <View style={styles.container}> | <View style={styles.container}> | ||||
| <Text style={{color: colors.textPrimary}}>Profile</Text> | |||||
| <Text style={{ color: colors.textPrimary }}>Profile</Text> | |||||
| </View> | </View> | ||||
| </Layout> | </Layout> | ||||
| ) | |||||
| } | |||||
| ); | |||||
| }; | |||||
| const styles = StyleSheet.create({ | const styles = StyleSheet.create({ | ||||
| container: { | |||||
| flex: 1, | |||||
| justifyContent: 'center', | |||||
| alignItems: 'center' | |||||
| } | |||||
| }) | |||||
| container: { | |||||
| flex: 1, | |||||
| justifyContent: "center", | |||||
| alignItems: "center", | |||||
| }, | |||||
| }); | |||||
| export default ProfileScreen; | |||||
| export default ProfileScreen; |
| import React, { useEffect } from "react"; | |||||
| import React, { useEffect, useState } from "react"; | |||||
| import { | import { | ||||
| ScrollView, | ScrollView, | ||||
| View, | View, | ||||
| StyleSheet, | StyleSheet, | ||||
| Alert, | Alert, | ||||
| } from "react-native"; | } from "react-native"; | ||||
| import InputField from "@components/InputField"; | import InputField from "@components/InputField"; | ||||
| import MaterialIcons from "@expo/vector-icons/MaterialIcons"; | import MaterialIcons from "@expo/vector-icons/MaterialIcons"; | ||||
| import Ionicons from "@expo/vector-icons/Ionicons"; | import Ionicons from "@expo/vector-icons/Ionicons"; | ||||
| import RegistrationSVG from "@assets/images/registration.svg"; | import RegistrationSVG from "@assets/images/registration.svg"; | ||||
| import GoogleSVG from "@assets/images/google.svg"; | import GoogleSVG from "@assets/images/google.svg"; | ||||
| import FacebookSVG from "@assets/images/facebook.svg"; | import FacebookSVG from "@assets/images/facebook.svg"; | ||||
| import TwitterSVG from "@assets/images/twitter.svg"; | import TwitterSVG from "@assets/images/twitter.svg"; | ||||
| import CustomButton from "@components/Buttons/CustomButton"; | import CustomButton from "@components/Buttons/CustomButton"; | ||||
| import { globalStyles } from "@styles/global"; | import { globalStyles } from "@styles/global"; | ||||
| import Loader from "@components/Loader"; | import Loader from "@components/Loader"; | ||||
| import { Formik } from "formik"; | import { Formik } from "formik"; | ||||
| import { registerSchema } from "@schemas/registerSchema"; | 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 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 Layout from "@components/Layout/Layout"; | ||||
| import { useTheme } from "@styles"; | import { useTheme } from "@styles"; | ||||
| import { useTranslation } from "react-i18next"; | 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 RegisterScreen = ({ navigation }) => { | ||||
| const [authProvider, { isLoading: isLoadingProvider }] = | |||||
| useAuthProviderMutation(); | |||||
| const [currentProvider, setCurrentProvider] = useState(""); | |||||
| const { colors } = useTheme(); | const { colors } = useTheme(); | ||||
| const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
| const [register, { isLoading, error }] = useRegisterMutation(); | |||||
| const { response, promptAsync } = useAuthHook(); | const { response, promptAsync } = useAuthHook(); | ||||
| const dispatch = useDispatch(); | 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 = () => { | const handleApiResponseSuccess = () => { | ||||
| Alert.alert(t("common.success"), t("register.successRegisterAccount"), [ | Alert.alert(t("common.success"), t("register.successRegisterAccount"), [ | ||||
| ]); | ]); | ||||
| }; | }; | ||||
| 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") { | if (response?.type === "success") { | ||||
| const accessToken = response.authentication.accessToken; | const accessToken = response.authentication.accessToken; | ||||
| if (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 ( | return ( | ||||
| <Layout> | <Layout> | ||||
| </Text> | </Text> | ||||
| <View style={styles.providersContainer}> | <View style={styles.providersContainer}> | ||||
| <TouchableOpacity | <TouchableOpacity | ||||
| onPress={handleGoogleAuth} | |||||
| onPress={() => { | |||||
| setCurrentProvider("google"); | |||||
| handleGoogleAuth(); | |||||
| }} | |||||
| style={globalStyles.iconButton} | style={globalStyles.iconButton} | ||||
| > | > | ||||
| <GoogleSVG height={24} width={24} /> | <GoogleSVG height={24} width={24} /> | ||||
| {errors.confirmPassword} | {errors.confirmPassword} | ||||
| </Text> | </Text> | ||||
| )} | )} | ||||
| {error && <Text style={styles.errorMessage}>{error}</Text>} | |||||
| {error && ( | |||||
| <Text style={styles.errorMessage}> | |||||
| {error?.data?.error?.message} | |||||
| </Text> | |||||
| )} | |||||
| <CustomButton | <CustomButton | ||||
| label={t("register.signUp")} | label={t("register.signUp")} | ||||
| onPress={handleSubmit} | onPress={handleSubmit} |
| 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); |
| 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"); |
| import { ADD_LOADER, REMOVE_LOADER } from "./appActionConstants"; | |||||
| export const addLoader = (payload) => ({ | |||||
| type: ADD_LOADER, | |||||
| payload, | |||||
| }); | |||||
| export const removeLoader = (payload) => ({ | |||||
| type: REMOVE_LOADER, | |||||
| payload, | |||||
| }); |
| 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); |
| 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, | |||||
| }); |
| 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); |
| 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 | |||||
| }); |
| 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"; |
| 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, | |||||
| }); |
| export const SET_USER = "SET_USER"; | |||||
| export const SET_USER_ERROR = "SET_USER_ERROR"; | |||||
| export const RESET_USER_STATE = "RESET_USER_STATE"; |
| 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, | |||||
| }); |
| 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); |
| 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); | |||||
| }; |
| 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); | |||||
| }; |
| 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); | |||||
| }; |
| 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); | |||||
| }; |
| 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); | |||||
| }; |
| 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, | |||||
| }; | |||||
| } |
| 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, | |||||
| }); |
| 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, | |||||
| }; | |||||
| } |
| 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: '', | |||||
| }; | |||||
| } |
| 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: '', | |||||
| }; | |||||
| } |
| 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; | |||||
| } |
| 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)]); | |||||
| } |
| 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()]); | |||||
| } |
| 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), | |||||
| ]); | |||||
| } |
| 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)]); | |||||
| } |
| import createSelector from "reselect"; | |||||
| const authProviderSelector = (state) => state.authProvider; | |||||
| export const selectLoginError = createSelector( | |||||
| authProviderSelector, | |||||
| (state) => state.errorMessage | |||||
| ); |
| 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 | |||||
| ); |
| 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, | |||||
| ); |
| import { createSelector } from 'reselect'; | |||||
| const registerSelector = (state) => state.register; | |||||
| export const selectRegisterError = createSelector( | |||||
| registerSelector, | |||||
| (state) => state.errorMessage, | |||||
| ); |
| 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, | |||||
| ); |
| const createReducer = (handlers, initialState) => { | |||||
| return (state = initialState, action) => { | |||||
| const reducer = handlers[action.type]; | |||||
| if (!reducer) { | |||||
| return state; | |||||
| } | |||||
| return reducer(state, action); | |||||
| }; | |||||
| }; | |||||
| export default createReducer; |