From 9e0613ee6cbfac64f8d71eae351c4ff4f5b94b89 Mon Sep 17 00:00:00 2001 From: Iveta Date: Thu, 12 Oct 2023 14:06:25 -0400 Subject: [PATCH] SDP-786: State refactor: statistics (#25) * State refactor: statistics * Keep 5 min token check --- src/api/getStatistics.ts | 15 ----- src/apiQueries/useStatistics.ts | 20 +++++++ src/components/DashboardAnalytics.tsx | 46 +++++++-------- src/helpers/fetchApi.ts | 1 + src/helpers/formatStatistics.ts | 21 +++++++ src/store/ducks/statistics.ts | 85 --------------------------- src/store/index.ts | 2 - src/types/index.ts | 7 --- 8 files changed, 64 insertions(+), 133 deletions(-) delete mode 100644 src/api/getStatistics.ts create mode 100644 src/apiQueries/useStatistics.ts create mode 100644 src/helpers/formatStatistics.ts delete mode 100644 src/store/ducks/statistics.ts diff --git a/src/api/getStatistics.ts b/src/api/getStatistics.ts deleted file mode 100644 index 55f2e85..0000000 --- a/src/api/getStatistics.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { handleApiResponse } from "api/handleApiResponse"; -import { API_URL } from "constants/settings"; -import { ApiStatistics } from "types"; - -export const getStatistics = async (token: string): Promise => { - const response = await fetch(`${API_URL}/statistics`, { - method: "GET", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${token}`, - }, - }); - - return handleApiResponse(response); -}; diff --git a/src/apiQueries/useStatistics.ts b/src/apiQueries/useStatistics.ts new file mode 100644 index 0000000..f35badb --- /dev/null +++ b/src/apiQueries/useStatistics.ts @@ -0,0 +1,20 @@ +import { useQuery } from "@tanstack/react-query"; +import { API_URL } from "constants/settings"; +import { fetchApi } from "helpers/fetchApi"; +import { formatStatistics } from "helpers/formatStatistics"; +import { ApiStatistics, AppError } from "types"; + +export const useStatistics = (isAuthenticated: boolean) => { + const query = useQuery({ + queryKey: ["statistics"], + queryFn: async () => { + return await fetchApi(`${API_URL}/statistics`); + }, + enabled: Boolean(isAuthenticated), + }); + + return { + ...query, + data: query.data ? formatStatistics(query.data) : undefined, + }; +}; diff --git a/src/components/DashboardAnalytics.tsx b/src/components/DashboardAnalytics.tsx index c22c5fc..e7e25a7 100644 --- a/src/components/DashboardAnalytics.tsx +++ b/src/components/DashboardAnalytics.tsx @@ -1,24 +1,21 @@ -import { useEffect } from "react"; -import { useDispatch } from "react-redux"; import { Card, Notification } from "@stellar/design-system"; import { InfoTooltip } from "components/InfoTooltip"; import { AssetAmount } from "components/AssetAmount"; +import { useStatistics } from "apiQueries/useStatistics"; import { percent } from "helpers/formatIntlNumber"; import { renderNumberOrDash } from "helpers/renderNumberOrDash"; import { useRedux } from "hooks/useRedux"; -import { - clearStatisticsAction, - getStatisticsAction, -} from "store/ducks/statistics"; -import { AppDispatch } from "store"; export const DashboardAnalytics = () => { - const { statistics, userAccount } = useRedux("statistics", "userAccount"); - const { stats } = statistics; - const dispatch: AppDispatch = useDispatch(); + const { userAccount } = useRedux("userAccount"); - const apiErrorStats = statistics.status === "ERROR" && statistics.errorString; + const { + data: stats, + error, + isLoading, + isFetching, + } = useStatistics(userAccount.isAuthenticated); const calculateRate = () => { if (stats?.paymentsSuccessfulCounts && stats?.paymentsTotalCount) { @@ -28,24 +25,22 @@ export const DashboardAnalytics = () => { return 0; }; - useEffect(() => { - if (userAccount.isAuthenticated) { - dispatch(getStatisticsAction()); - } - - return () => { - dispatch(clearStatisticsAction()); - }; - }, [dispatch, userAccount.isAuthenticated]); - - if (apiErrorStats) { + if (error) { return ( - {apiErrorStats} + {error.message} ); } + if (isLoading || isFetching) { + return ( +
+
Loading…
+
+ ); + } + return (
{/* TODO: add disbursement volume chart */} @@ -122,7 +117,10 @@ export const DashboardAnalytics = () => { {stats?.assets.map((a) => (
- +
diff --git a/src/helpers/fetchApi.ts b/src/helpers/fetchApi.ts index 229450c..ddb5e46 100644 --- a/src/helpers/fetchApi.ts +++ b/src/helpers/fetchApi.ts @@ -35,6 +35,7 @@ export const fetchApi = async ( sessionExpired(); } else if (minRemaining < 5) { token = await refreshToken(token); + localStorageSessionToken.set(token); } config.headers = { diff --git a/src/helpers/formatStatistics.ts b/src/helpers/formatStatistics.ts new file mode 100644 index 0000000..d862eb2 --- /dev/null +++ b/src/helpers/formatStatistics.ts @@ -0,0 +1,21 @@ +import { ApiStatistics, HomeStatistics } from "types"; + +export const formatStatistics = (statistics: ApiStatistics): HomeStatistics => { + return { + paymentsSuccessfulCounts: statistics.payment_counters.success, + paymentsFailedCount: statistics.payment_counters.failed, + paymentsRemainingCount: Number( + statistics.payment_counters.total - + statistics.payment_counters.success - + statistics.payment_counters.failed, + ), + paymentsTotalCount: statistics.payment_counters.total, + walletsTotalCount: statistics.receiver_wallets_counters.total, + individualsTotalCount: statistics.total_receivers, + assets: statistics.payment_amounts_by_asset.map((a) => ({ + assetCode: a.asset_code, + success: a.payment_amounts.success.toString(), + average: a.payment_amounts.average.toString(), + })), + }; +}; diff --git a/src/store/ducks/statistics.ts b/src/store/ducks/statistics.ts deleted file mode 100644 index aa32fc3..0000000 --- a/src/store/ducks/statistics.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; -import { getStatistics } from "api/getStatistics"; -import { handleApiErrorString } from "api/handleApiErrorString"; -import { RootState } from "store"; -import { endSessionIfTokenInvalid } from "helpers/endSessionIfTokenInvalid"; -import { refreshSessionToken } from "helpers/refreshSessionToken"; -import { - ApiError, - HomeStatistics, - RejectMessage, - StatisticsInitialState, -} from "types"; - -export const getStatisticsAction = createAsyncThunk< - HomeStatistics, - undefined, - { rejectValue: RejectMessage; state: RootState } ->( - "statistics/getStatisticsAction", - async (_, { rejectWithValue, getState, dispatch }) => { - const { token } = getState().userAccount; - - try { - const statistics = await getStatistics(token); - refreshSessionToken(dispatch); - - return { - paymentsSuccessfulCounts: statistics.payment_counters.success, - paymentsFailedCount: statistics.payment_counters.failed, - paymentsRemainingCount: Number( - statistics.payment_counters.total - - statistics.payment_counters.success - - statistics.payment_counters.failed, - ), - paymentsTotalCount: statistics.payment_counters.total, - walletsTotalCount: statistics.receiver_wallets_counters.total, - individualsTotalCount: statistics.total_receivers, - assets: statistics.payment_amounts_by_asset.map((a) => ({ - assetCode: a.asset_code, - success: a.payment_amounts.success.toString(), - average: a.payment_amounts.average.toString(), - })), - }; - } catch (error: unknown) { - const errorString = handleApiErrorString(error as ApiError); - endSessionIfTokenInvalid(errorString, dispatch); - - return rejectWithValue({ - errorString: `Error fetching statistics: ${errorString}`, - }); - } - }, -); - -const initialState: StatisticsInitialState = { - stats: undefined, - status: undefined, - errorString: undefined, -}; - -const statisticsSlice = createSlice({ - name: "statistics", - initialState, - reducers: { - clearStatisticsAction: () => initialState, - }, - extraReducers: (builder) => { - builder.addCase(getStatisticsAction.pending, (state = initialState) => { - state.status = "PENDING"; - }); - builder.addCase(getStatisticsAction.fulfilled, (state, action) => { - state.stats = action.payload; - state.status = "SUCCESS"; - state.errorString = undefined; - }); - builder.addCase(getStatisticsAction.rejected, (state, action) => { - state.status = "ERROR"; - state.errorString = action.payload?.errorString; - }); - }, -}); - -export const statisticsSelector = (state: RootState) => state.statistics; -export const { reducer } = statisticsSlice; -export const { clearStatisticsAction } = statisticsSlice.actions; diff --git a/src/store/index.ts b/src/store/index.ts index bfe9cb2..3012560 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -19,7 +19,6 @@ import { reducer as organization } from "store/ducks/organization"; import { reducer as profile } from "store/ducks/profile"; import { reducer as receiverDetails } from "store/ducks/receiverDetails"; import { reducer as receiverPayments } from "store/ducks/receiverPayments"; -import { reducer as statistics } from "store/ducks/statistics"; import { reducer as userAccount } from "store/ducks/userAccount"; import { reducer as users } from "store/ducks/users"; import { reducer as wallets } from "store/ducks/wallets"; @@ -49,7 +48,6 @@ const reducers = combineReducers({ profile, receiverDetails, receiverPayments, - statistics, userAccount, users, wallets, diff --git a/src/types/index.ts b/src/types/index.ts index 4e92070..fc5f042 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -114,12 +114,6 @@ export type ForgotPasswordInitialState = { errorExtras?: AnyObject; }; -export type StatisticsInitialState = { - stats: HomeStatistics | undefined; - status: ActionStatus | undefined; - errorString?: string; -}; - export type ReceiverDetailsInitialState = { id: string; phoneNumber: string; @@ -207,7 +201,6 @@ export interface Store { profile: ProfileInitialState; receiverDetails: ReceiverDetailsInitialState; receiverPayments: ReceiverPaymentsInitialState; - statistics: StatisticsInitialState; userAccount: UserAccountInitialState; users: UsersInitialState; wallets: WalletsInitialState;