diff --git a/extension/src/helpers/hooks/useGetHistory.tsx b/extension/src/helpers/hooks/useGetHistory.tsx new file mode 100644 index 0000000000..3e8aaccef5 --- /dev/null +++ b/extension/src/helpers/hooks/useGetHistory.tsx @@ -0,0 +1,85 @@ +import { useReducer } from "react"; + +import { getAccountHistory } from "@shared/api/internal"; +import { NetworkDetails } from "@shared/constants/stellar"; +import { ServerApi } from "stellar-sdk/lib/horizon"; + +enum RequestState { + IDLE = "IDLE", + LOADING = "LOADING", + SUCCESS = "SUCCESS", + ERROR = "ERROR", +} + +interface SuccessState { + state: RequestState.SUCCESS; + data: ServerApi.OperationRecord[]; + error: null; +} + +interface ErrorState { + state: RequestState.ERROR; + data: null; + error: unknown; +} + +interface IdleState { + state: RequestState.IDLE; + data: null; + error: null; +} + +interface LoadingState { + state: RequestState.LOADING; + data: null; + error: null; +} + +type State = IdleState | LoadingState | SuccessState | ErrorState; + +type Action = + | { type: "FETCH_DATA_START" } + | { type: "FETCH_DATA_SUCCESS"; payload: SuccessState["data"] } + | { type: "FETCH_DATA_ERROR"; payload: ErrorState["error"] }; + +const initialState: IdleState = { + state: RequestState.IDLE, + data: null, + error: null, +}; + +const reducer = (state: State, action: Action): State => { + switch (action.type) { + case "FETCH_DATA_START": + return { state: RequestState.LOADING, error: null, data: null }; + case "FETCH_DATA_SUCCESS": + return { error: null, state: RequestState.SUCCESS, data: action.payload }; + case "FETCH_DATA_ERROR": + return { data: null, state: RequestState.ERROR, error: action.payload }; + default: + return state; + } +}; + +function useGetHistory(publicKey: string, networkDetails: NetworkDetails) { + const [state, dispatch] = useReducer(reducer, initialState); + + const fetchData = async () => { + dispatch({ type: "FETCH_DATA_START" }); + try { + const data = await getAccountHistory(publicKey, networkDetails); + dispatch({ type: "FETCH_DATA_SUCCESS", payload: data }); + return data; + } catch (error) { + dispatch({ type: "FETCH_DATA_ERROR", payload: error }); + return error; + } + }; + + return { + state, + fetchData, + }; +} + +export { useGetHistory, RequestState }; diff --git a/extension/src/popup/views/AccountHistory/index.tsx b/extension/src/popup/views/AccountHistory/index.tsx index c441177018..22bab890b7 100644 --- a/extension/src/popup/views/AccountHistory/index.tsx +++ b/extension/src/popup/views/AccountHistory/index.tsx @@ -1,12 +1,9 @@ import React, { useEffect, useState } from "react"; -import { useSelector, useDispatch } from "react-redux"; +import { useSelector } from "react-redux"; import { useTranslation } from "react-i18next"; -import { Loader } from "@stellar/design-system"; import { Horizon } from "stellar-sdk"; import BigNumber from "bignumber.js"; -import { getAccountHistory } from "@shared/api/internal"; -import { ActionStatus } from "@shared/api/types"; import { SorobanTokenInterface } from "@shared/constants/soroban/token"; import { publicKeySelector } from "popup/ducks/accountServices"; @@ -33,8 +30,10 @@ import { TransactionDetailProps, } from "popup/components/accountHistory/TransactionDetail"; import { View } from "popup/basics/layout/View"; +import { RequestState, useGetHistory } from "helpers/hooks/useGetHistory"; import "./styles.scss"; +import { Loading } from "popup/components/Loading"; enum SELECTOR_OPTIONS { ALL = "ALL", @@ -55,13 +54,14 @@ export const AccountHistory = () => { | null; const { t } = useTranslation(); - const dispatch = useDispatch(); const publicKey = useSelector(publicKeySelector); const networkDetails = useSelector(settingsNetworkDetailsSelector); - const { accountBalances, accountBalanceStatus } = useSelector( - transactionSubmissionSelector, - ); + const { accountBalances } = useSelector(transactionSubmissionSelector); const { isHideDustEnabled } = useSelector(settingsSelector); + const { state: getHistoryState, fetchData } = useGetHistory( + publicKey, + networkDetails, + ); const [selectedSegment, setSelectedSegment] = useState(SELECTOR_OPTIONS.ALL); const [historySegments, setHistorySegments] = useState( @@ -76,14 +76,16 @@ export const AccountHistory = () => { const [detailViewProps, setDetailViewProps] = useState( defaultDetailViewProps, ); - const [isLoading, setIsLoading] = useState(false); const stellarExpertUrl = getStellarExpertUrl(networkDetails); - const isAccountHistoryLoading = - historySegments === null || - accountBalanceStatus === ActionStatus.IDLE || - accountBalanceStatus === ActionStatus.PENDING; + useEffect(() => { + const getData = async () => { + await fetchData(); + }; + getData(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); useEffect(() => { const createSegments = ( @@ -141,86 +143,75 @@ export const AccountHistory = () => { return segments; }; - const fetchAccountHistory = async () => { - try { - const operations = await getAccountHistory(publicKey, networkDetails); - setHistorySegments(createSegments(operations)); - } catch (e) { - console.error(e); - } - }; - - const getData = async () => { - setIsLoading(true); - await fetchAccountHistory(); - setIsLoading(false); - }; - - getData(); - }, [publicKey, networkDetails, dispatch, isHideDustEnabled]); - - return isDetailViewShowing ? ( - - ) : ( - <> - -
- {isLoading ? ( -
- + if (getHistoryState.state === RequestState.SUCCESS) { + setHistorySegments(createSegments(getHistoryState.data)); + } + }, [ + getHistoryState.state, + getHistoryState.data, + publicKey, + networkDetails, + isHideDustEnabled, + ]); + + const isLoaderShowing = + getHistoryState.state === RequestState.IDLE || + getHistoryState.state === RequestState.LOADING; + + if (isDetailViewShowing) { + return ; + } + + if (isLoaderShowing) { + return ; + } + + const hasEmptyHistory = !getHistoryState?.data?.length; + + return ( + +
+
{t("Transactions")}
+
+ {Object.values(SELECTOR_OPTIONS).map((option) => ( +
setSelectedSegment(option)} + > + {t(option)}
- ) : ( - <> -
- {t("Transactions")} -
-
- {Object.values(SELECTOR_OPTIONS).map((option) => ( -
setSelectedSegment(option)} - > - {t(option)} -
- ))} -
-
- {historySegments?.[SELECTOR_OPTIONS[selectedSegment]].length ? ( - - <> - {historySegments[SELECTOR_OPTIONS[selectedSegment]].map( - (operation: HistoryItemOperation) => ( - - ), - )} - - - ) : ( -
- {isAccountHistoryLoading - ? null - : t("No transactions to show")} -
+ ))} +
+
+ {historySegments?.[SELECTOR_OPTIONS[selectedSegment]].length ? ( + + <> + {historySegments[SELECTOR_OPTIONS[selectedSegment]].map( + (operation: HistoryItemOperation) => ( + + ), )} -
- + + + ) : ( +
{hasEmptyHistory ? t("No transactions to show") : null}
)}
- - +
+
); }; diff --git a/extension/src/popup/views/AccountHistory/styles.scss b/extension/src/popup/views/AccountHistory/styles.scss index 2512fd7a47..558d05c825 100644 --- a/extension/src/popup/views/AccountHistory/styles.scss +++ b/extension/src/popup/views/AccountHistory/styles.scss @@ -1,17 +1,4 @@ .AccountHistory { - &__loader { - height: 100%; - width: 100%; - z-index: calc(var(--back--button-z-index) + 1); - position: absolute; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - top: 0; - left: 0; - } - &__header { font-size: 1.25rem; margin-bottom: 1.5rem;