From fc9715653b8420de66e5972d60efe299d253bd0c Mon Sep 17 00:00:00 2001 From: KirillDemyanenko Date: Sat, 23 Dec 2023 15:52:05 +0300 Subject: [PATCH 1/4] fix(app): loading --- src/components/App/App.tsx | 47 ++++++++++++++++++-------------------- src/store/rtk/userApi.ts | 3 +-- 2 files changed, 23 insertions(+), 27 deletions(-) diff --git a/src/components/App/App.tsx b/src/components/App/App.tsx index 4359953..cfec32f 100644 --- a/src/components/App/App.tsx +++ b/src/components/App/App.tsx @@ -1,12 +1,13 @@ import './App.scss'; import { useDispatch, useSelector } from 'react-redux'; -import { useCallback, useEffect } from 'react'; +import { useEffect } from 'react'; +import { FetchBaseQueryError } from '@reduxjs/toolkit/query'; import Routes from '../../routes'; import { AppContextProvider } from '../../context/AppContext'; import { useRefreshTokenMutation } from '../../store/rtk/tokensApi'; import { clearStorage, readStorage } from '../../store/slices/tokens'; import { useGetUserMutation } from '../../store/rtk/userApi'; -import { store, AppDispatch, RootState } from '../../store'; +import type { AppDispatch, RootState } from '../../store'; import Loader from '../Loader/Loader'; function App() { @@ -14,32 +15,28 @@ function App() { const { refresh, access } = useSelector((state: RootState) => state.tokens); const { isLoading } = useSelector((state: RootState) => state.user); const [updateToken] = useRefreshTokenMutation(); - const [getUser, { isError }] = useGetUserMutation(); + const [getUser] = useGetUserMutation(); + function isFetchBaseQueryError(error: unknown): error is FetchBaseQueryError { + return typeof error === 'object' && error != null && 'status' in error; + } - const readInitialData = useCallback(() => { - if (!isLoading) { - if (!access) { - dispatch(readStorage()); - } else { - getUser(null) - .unwrap() - .then(() => { - if (isError && store.getState().user.requestCounter === 0) { - if (refresh && access) { - updateToken({ refresh }) - .unwrap() - .catch(() => dispatch(clearStorage())); - } + useEffect(() => { + if (!access) { + dispatch(readStorage()); + } else { + getUser(null) + .then((data) => { + if (isFetchBaseQueryError(data)) { + if (refresh && access) { + updateToken({ refresh }) + .unwrap() + .catch(() => dispatch(clearStorage())); } - }) - .catch(() => {}); - } + } + }) + .catch(() => {}); } - }, [access, dispatch, getUser, isError, isLoading, refresh, updateToken]); - - useEffect(() => { - readInitialData(); - }, [readInitialData]); + }, [dispatch, getUser, access, refresh, updateToken]); return isLoading ? ( diff --git a/src/store/rtk/userApi.ts b/src/store/rtk/userApi.ts index 6b9d97b..0ff142f 100644 --- a/src/store/rtk/userApi.ts +++ b/src/store/rtk/userApi.ts @@ -25,12 +25,11 @@ export const userApi = createApi({ url: '/me/', method: 'GET', }), - extraOptions: { maxRetries: 3 }, transformResponse: (response: UserDTO) => response, transformErrorResponse: (response: { status: number; data?: UserErrorMessage; - }) => response, + }) => response.data, }), updateCoordinates: builder.mutation({ query: (location) => ({ From f0d45aeb34fc3393dd3c5b1b8238c5a64f8d07bf Mon Sep 17 00:00:00 2001 From: KirillDemyanenko Date: Sat, 23 Dec 2023 16:31:42 +0300 Subject: [PATCH 2/4] fix(app): loading --- index.html | 5 +++- src/components/App/App.tsx | 47 ++++++++++++++++++++++++++++------- src/pages/Welcome/Welcome.tsx | 9 ++++++- src/store/rtk/tokensApi.ts | 21 ++++++++++++---- src/store/slices/user.ts | 2 +- 5 files changed, 67 insertions(+), 17 deletions(-) diff --git a/index.html b/index.html index e8729f3..60139b2 100644 --- a/index.html +++ b/index.html @@ -5,7 +5,10 @@ FLApp - + diff --git a/src/components/App/App.tsx b/src/components/App/App.tsx index cfec32f..7d15e89 100644 --- a/src/components/App/App.tsx +++ b/src/components/App/App.tsx @@ -4,7 +4,10 @@ import { useEffect } from 'react'; import { FetchBaseQueryError } from '@reduxjs/toolkit/query'; import Routes from '../../routes'; import { AppContextProvider } from '../../context/AppContext'; -import { useRefreshTokenMutation } from '../../store/rtk/tokensApi'; +import { + useRefreshTokenMutation, + useVerifyTokenMutation, +} from '../../store/rtk/tokensApi'; import { clearStorage, readStorage } from '../../store/slices/tokens'; import { useGetUserMutation } from '../../store/rtk/userApi'; import type { AppDispatch, RootState } from '../../store'; @@ -16,6 +19,8 @@ function App() { const { isLoading } = useSelector((state: RootState) => state.user); const [updateToken] = useRefreshTokenMutation(); const [getUser] = useGetUserMutation(); + const [verifyToken] = useVerifyTokenMutation(); + function isFetchBaseQueryError(error: unknown): error is FetchBaseQueryError { return typeof error === 'object' && error != null && 'status' in error; } @@ -24,19 +29,43 @@ function App() { if (!access) { dispatch(readStorage()); } else { - getUser(null) - .then((data) => { - if (isFetchBaseQueryError(data)) { - if (refresh && access) { - updateToken({ refresh }) - .unwrap() - .catch(() => dispatch(clearStorage())); + verifyToken(access) + .then((verification) => { + if (isFetchBaseQueryError(verification)) { + if (refresh) { + verifyToken(refresh) + .then((refreshVerification) => { + if (isFetchBaseQueryError(refreshVerification)) { + dispatch(clearStorage()); + } else { + getUser(null) + .then((data) => { + if (isFetchBaseQueryError(data)) { + updateToken({ refresh }) + .unwrap() + .catch(() => dispatch(clearStorage())); + } + }) + .catch(() => {}); + } + }) + .catch(() => {}); } + } else { + getUser(null) + .then((data) => { + if (isFetchBaseQueryError(data)) { + updateToken({ refresh }) + .unwrap() + .catch(() => dispatch(clearStorage())); + } + }) + .catch(() => {}); } }) .catch(() => {}); } - }, [dispatch, getUser, access, refresh, updateToken]); + }, [dispatch, getUser, access, refresh, updateToken, verifyToken]); return isLoading ? ( diff --git a/src/pages/Welcome/Welcome.tsx b/src/pages/Welcome/Welcome.tsx index 6725ea4..eb46d94 100644 --- a/src/pages/Welcome/Welcome.tsx +++ b/src/pages/Welcome/Welcome.tsx @@ -1,11 +1,18 @@ import './Welcome.scss'; import { Link } from 'react-router-dom'; +import { useSelector } from 'react-redux'; import Button from '../../components/Button/Button'; import Carousel from '../../components/Carousel/Carousel'; import RoutesPath from '../../constants/enums/routesPath'; +import type { RootState } from '../../store'; +import Loader from '../../components/Loader/Loader'; function Welcome() { - return ( + const { isLoading } = useSelector((state: RootState) => state.user); + + return isLoading ? ( + + ) : ( <>
diff --git a/src/store/rtk/tokensApi.ts b/src/store/rtk/tokensApi.ts index cda2246..0018761 100644 --- a/src/store/rtk/tokensApi.ts +++ b/src/store/rtk/tokensApi.ts @@ -30,7 +30,6 @@ export const tokensApi = createApi({ query: (body: RefreshTokenDTO) => ({ url: '/refresh/', method: 'POST', - headers: { 'Content-Type': 'application/json' }, body, }), transformResponse: (response: AccessTokenDTO) => response, @@ -40,14 +39,26 @@ export const tokensApi = createApi({ }) => response.data, }), verifyToken: builder.mutation({ - query: (body: string) => ({ + query: (token: string) => ({ url: '/verify/', method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body, + body: { token }, }), + transformResponse: (response: { status: number; data: unknown }) => { + return response.status === 200; + }, + transformErrorResponse: (response: { + status: number; + data: TokenErrorMessage; + }) => { + return response.status === 200; + }, }), }), }); -export const { useCreateTokenMutation, useRefreshTokenMutation } = tokensApi; +export const { + useCreateTokenMutation, + useRefreshTokenMutation, + useVerifyTokenMutation, +} = tokensApi; diff --git a/src/store/slices/user.ts b/src/store/slices/user.ts index 5918658..7923659 100644 --- a/src/store/slices/user.ts +++ b/src/store/slices/user.ts @@ -17,7 +17,7 @@ const initialState: UserState = { email: '', userpic: null, status: '', - isLoading: false, + isLoading: true, errorMessage: undefined, registerSuccess: false, isAuthenticated: false, From a2829beeecfeb7c2a1faa64b7d6dfafe3063e84b Mon Sep 17 00:00:00 2001 From: KirillDemyanenko Date: Sat, 23 Dec 2023 16:48:10 +0300 Subject: [PATCH 3/4] fix(app): endless loading when no data in local storage --- src/components/App/App.tsx | 2 ++ src/store/slices/user.ts | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/App/App.tsx b/src/components/App/App.tsx index 7d15e89..58afd70 100644 --- a/src/components/App/App.tsx +++ b/src/components/App/App.tsx @@ -12,6 +12,7 @@ import { clearStorage, readStorage } from '../../store/slices/tokens'; import { useGetUserMutation } from '../../store/rtk/userApi'; import type { AppDispatch, RootState } from '../../store'; import Loader from '../Loader/Loader'; +import { stopLoading } from '../../store/slices/user'; function App() { const dispatch = useDispatch(); @@ -28,6 +29,7 @@ function App() { useEffect(() => { if (!access) { dispatch(readStorage()); + dispatch(stopLoading()); } else { verifyToken(access) .then((verification) => { diff --git a/src/store/slices/user.ts b/src/store/slices/user.ts index 7923659..6b8eb2b 100644 --- a/src/store/slices/user.ts +++ b/src/store/slices/user.ts @@ -43,6 +43,9 @@ const userSlice = createSlice({ errorMessage: action.payload.errorMessage, }; }, + stopLoading(state) { + return { ...state, isLoading: false }; + }, }, extraReducers: (builder) => { builder.addMatcher( @@ -140,4 +143,4 @@ const userSlice = createSlice({ export default userSlice.reducer; -export const { logout, setLocationError } = userSlice.actions; +export const { logout, stopLoading, setLocationError } = userSlice.actions; From c4e19e515a9196b3169354f09ee35c49b59b0b53 Mon Sep 17 00:00:00 2001 From: KirillDemyanenko Date: Sat, 23 Dec 2023 16:58:03 +0300 Subject: [PATCH 4/4] fix(app): logout error --- src/store/slices/user.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/store/slices/user.ts b/src/store/slices/user.ts index 6b8eb2b..a1316f4 100644 --- a/src/store/slices/user.ts +++ b/src/store/slices/user.ts @@ -33,6 +33,7 @@ const userSlice = createSlice({ localStorage.removeItem('refresh_token'); return { ...initialState, + isLoading: false, }; }, setLocationError(state, action: { payload: { errorMessage: string } }) {