diff --git a/.eslintrc.js b/.eslintrc.js index 1ee29c7..7f693c0 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,5 +1,17 @@ require("@rushstack/eslint-patch/modern-module-resolution"); module.exports = { - extends: "@chainsafe" + extends: "@chainsafe" , + rules: { + "@typescript-eslint/explicit-function-return-type": "off", + }, + "overrides": [ + { + // enable the rule specifically for TypeScript files + "files": ["*.ts"], + "rules": { + "@typescript-eslint/explicit-function-return-type": "error", + }, + }, + ], } \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index 80a6fa5..fc3e1fa 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,6 @@ import CssBaseline from "@mui/material/CssBaseline" import { ThemeProvider } from "@mui/material/styles" import { Routes, Route, BrowserRouter as Router } from "react-router-dom" -import { Alert } from "@mui/material" import { ExplorerPage, DetailView } from "./pages" import { SygmaTheme } from "./themes/SygmaTheme" import { ExplorerProvider } from "./context" @@ -13,7 +12,6 @@ export const ROUTE_LINKS = { } function App(): JSX.Element { - // ADD HERE SHARED CONFIG SUPPORT return ( diff --git a/src/components/ExplorerTable/ExplorerTable.tsx b/src/components/ExplorerTable/ExplorerTable.tsx index c4043a0..4a904af 100644 --- a/src/components/ExplorerTable/ExplorerTable.tsx +++ b/src/components/ExplorerTable/ExplorerTable.tsx @@ -2,7 +2,7 @@ import React from "react" import { Table, TableHead, TableCell, TableBody, TableRow, CircularProgress } from "@mui/material" import clsx from "clsx" import { Link } from "react-router-dom" -import { EvmBridgeConfig, ResourceTypes, SharedConfigDomain, Transfer } from "../../types" +import { EvmBridgeConfig, ExplorerContextState, ResourceTypes, SharedConfigDomain, Transfer } from "../../types" import { getDisplayedStatuses, shortenAddress, @@ -23,11 +23,7 @@ type ExplorerTable = { active: boolean setActive: (state: boolean) => void chains: Array - state: { - transfers: Transfer[] - loading: "none" | "loading" | "done" - error: undefined | string - } + state: ExplorerContextState sharedConfig: SharedConfigDomain[] | [] } @@ -66,7 +62,7 @@ const ExplorerTable: React.FC = ({ state, sharedConfig }: Explore {txHash !== undefined ? ( - + {txHash} ) : ( @@ -144,8 +140,8 @@ const ExplorerTable: React.FC = ({ state, sharedConfig }: Explore Value - {state.loading === "done" && {renderTransferList(state.transfers)}} - {state.loading === "loading" && ( + {state.isLoading === "done" && {renderTransferList(state.transfers)}} + {state.isLoading === "loading" && ( diff --git a/src/context/ExplorerContext.tsx b/src/context/ExplorerContext.tsx index 4159ef1..00041ff 100644 --- a/src/context/ExplorerContext.tsx +++ b/src/context/ExplorerContext.tsx @@ -1,93 +1,78 @@ -import React, { useEffect } from "react"; -import { - ExplorerContextState, - ExplorerContext as ExplorerContextType, - ExplorerState, - PaginationParams, - SharedConfig, - SharedConfigDomain, -} from "../types"; -import { getAccount, getChainId } from "./connection"; -import { routes } from "./data"; -import { reducer } from "./reducer"; - -const ExplorerCtx = React.createContext( - undefined, -); - -const ExplorerProvider = ({ - children, -}: { - children: React.ReactNode | React.ReactNode[]; -}) => { - - // TO BE DEFINED - const loadMore = (options: PaginationParams) => null; +import React, { useEffect } from "react" +import type { ExplorerContextState, ExplorerContext as ExplorerContextType, ExplorerState, SharedConfig, SharedConfigDomain } from "../types" + +import { getAccount, getChainId } from "./connection" +import { routes } from "./data" +import { reducer } from "./reducer" +import { useGetTransferData } from "./useGetTransferData" + +const ExplorerCtx = React.createContext(undefined) + +const ExplorerProvider = ({ children }: { children: React.ReactNode | React.ReactNode[] }) => { const explorerPageContextState: ExplorerContextState = { queryParams: { - page: 1, //by default + page: 1, limit: 10, }, - isLoading: false, + isLoading: "none", transfers: [], - error: false, + error: undefined, chains: [], transferDetails: undefined, pillColorStatus: undefined, account: undefined, - }; + } - const [explorerContextState, explorerContextDispatcher] = React.useReducer( - reducer, - explorerPageContextState, - ); + const [explorerContextState, explorerContextDispatcher] = React.useReducer(reducer, explorerPageContextState) - const [chainId, setChainId] = React.useState(undefined); - const [account, setAccount] = React.useState(undefined); - const [explorerUrls, setExplorerUrls] = React.useState< - [] | ExplorerState["explorerUrls"] - >([]); + const [chainId, setChainId] = React.useState(undefined) + const [account, setAccount] = React.useState(undefined) + const [explorerUrls, setExplorerUrls] = React.useState<[] | ExplorerState["explorerUrls"]>([]) - const [sharedConfig, setSharedConfig] = React.useState< - SharedConfigDomain[] | [] - >([]); + const [sharedConfig, setSharedConfig] = React.useState([]) const getSharedConfig = async (): Promise => { - const reponse = await fetch(import.meta.env.VITE_SHARED_CONFIG_URL); - const domainsData = (await reponse.json()) as SharedConfig; + const reponse = await fetch(import.meta.env.VITE_SHARED_CONFIG_URL as string) + const domainsData = (await reponse.json()) as SharedConfig - setSharedConfig(domainsData.domains); - localStorage.setItem("sharedConfig", JSON.stringify(domainsData)); - }; + setSharedConfig(domainsData.domains) + localStorage.setItem("sharedConfig", JSON.stringify(domainsData)) + } + + const { search } = window.location + const urlParams = new URLSearchParams(search) + const page = urlParams.get("page") + + useGetTransferData(routes(), explorerContextDispatcher, explorerContextState, Number(page)) useEffect(() => { if (window.ethereum !== undefined) { - window.ethereum!.on("chainChanged", (chainId: unknown) => { - setChainId(Number(chainId as string)); - }); + window.ethereum.on("chainChanged", (chainId: unknown) => { + setChainId(Number(chainId as string)) + }) - window.ethereum!.on("accountsChanged", (accounts: unknown) => { - setAccount((accounts as Array)[0] as string); - }); + window.ethereum.on("accountsChanged", (accounts: unknown) => { + setAccount((accounts as Array)[0]) + }) } - getSharedConfig(); - - setExplorerUrls(JSON.parse(import.meta.env.VITE_EXPLORER_URLS)); + void getSharedConfig() + + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + setExplorerUrls(JSON.parse(import.meta.env.VITE_EXPLORER_URLS)) return () => { if (window.ethereum !== undefined) { - window.ethereum!.removeAllListeners("chainChanged"); - window.ethereum!.removeAllListeners("accountsChanged"); + window.ethereum.removeAllListeners("chainChanged") + window.ethereum.removeAllListeners("accountsChanged") } - }; - }, []); + } + }, []) return ( {children} - ); -}; + ) +} const useExplorer = () => { - const context = React.useContext(ExplorerCtx); + const context = React.useContext(ExplorerCtx) if (context === undefined) { - throw new Error("useExplorer must be used within a ExplorerProvider"); + throw new Error("useExplorer must be used within a ExplorerProvider") } - return context; -}; + return context +} -export { ExplorerProvider, useExplorer }; +export { ExplorerProvider, useExplorer } diff --git a/src/context/reducer.ts b/src/context/reducer.ts index 2f1e8a9..09ecf89 100644 --- a/src/context/reducer.ts +++ b/src/context/reducer.ts @@ -13,6 +13,26 @@ export function reducer(state: ExplorerContextState, action: Actions): ExplorerC account: action.payload, } } + case "fetch_transfers": + return { + ...state, + transfers: action.payload, + } + case "loading_done": + return { + ...state, + isLoading: "done", + } + case "loading_transfers": + return { + ...state, + isLoading: "loading", + } + case "fetch_transfer_error": + return { + ...state, + error: action.payload, + } default: return state } diff --git a/src/context/useGetTransferData.ts b/src/context/useGetTransferData.ts new file mode 100644 index 0000000..1c57288 --- /dev/null +++ b/src/context/useGetTransferData.ts @@ -0,0 +1,48 @@ +import { useEffect } from "react" +import { Actions, ExplorerContextState, Routes } from "../types" +import { sanitizeTransferData } from "../utils/Helpers" + +export function useGetTransferData(routes: Routes, dispatcher: React.Dispatch, state: ExplorerContextState, page: number): void { + const pageToUse = page !== 0 && page !== state.queryParams.page ? page : state.queryParams.page + + const fetchTransfers = async (): Promise => { + dispatcher({ + type: "loading_transfers", + }) + + try { + const transfers = await routes.transfers(`${pageToUse}`, `${state.queryParams.limit}`) + const sanitizedTransfers = sanitizeTransferData(transfers) + + dispatcher({ + type: "fetch_transfers", + payload: sanitizedTransfers, + }) + + dispatcher({ + type: "loading_done", + }) + } catch (e) { + dispatcher({ + type: "fetch_transfer_error", + payload: "Error fetching all the transfers", + }) + } + } + + useEffect(() => { + void fetchTransfers() + }, [state.queryParams]) + + useEffect(() => { + if (pageToUse !== state.queryParams.page) { + dispatcher({ + type: "set_query_params", + payload: { + page: pageToUse, + limit: state.queryParams.limit, + }, + }) + } + }, [page]) +} diff --git a/src/pages/DetailView/DetailView.tsx b/src/pages/DetailView/DetailView.tsx index 6675bf5..a98a43c 100644 --- a/src/pages/DetailView/DetailView.tsx +++ b/src/pages/DetailView/DetailView.tsx @@ -37,7 +37,7 @@ export default function DetailView() { const { classes } = useStyles() - const { state: transferId } = useLocation() as { state: { id: string } } + const { state: data } = useLocation() as { state: { id: string; page: number } } const initState: DetailViewState = { transferDetails: null, @@ -52,9 +52,9 @@ export default function DetailView() { useClipboard(state, dispatcher) - useFetchTransfer(routes, sharedConfig, setSharedConfig, transferId, dispatcher) + useFetchTransfer(routes, sharedConfig, setSharedConfig, data, dispatcher) - useUpdateInterval(state, dispatcher, transferId, routes) + useUpdateInterval(state, dispatcher, data, routes) // eslint-disable-next-line @typescript-eslint/explicit-function-return-type const renderTransferDetails = (transfer: Transfer | null) => { @@ -218,7 +218,7 @@ export default function DetailView() {
{ const explorerContext = useExplorer() - const { explorerContextDispatcher, explorerContextState, routes, sharedConfig } = explorerContext + const { explorerContextDispatcher, explorerContextState, sharedConfig } = explorerContext + + const { transfers } = explorerContextState const { chains } = explorerContextState const classes = useStyles() const [active, setActive] = useState(false) - const [state, dispatcher] = useReducer(reducer, initState) - - useGetTransferData(explorerContextState.queryParams.page, explorerContextState.queryParams.limit, routes, dispatcher, state, explorerContextState) - const handleRefreshTable = (): void => { const { account } = explorerContextState @@ -43,6 +32,8 @@ const ExplorerPage = (): JSX.Element => { payload: { page: 1, limit: 10 }, }) } + + history.replaceState(null, "", `/`) } return ( @@ -59,19 +50,19 @@ const ExplorerPage = (): JSX.Element => { borderRadius: "12px", display: "grid", gridTemplateRows: "repeat(1, 1fr)", - marginTop: state.transfers.length !== 0 ? "0px" : "10px", + marginTop: transfers.length !== 0 ? "0px" : "10px", }} > -
- {state.transfers.length !== 0 && sharedConfig.length !== 0 ? ( - +
+ {transfers.length !== 0 && sharedConfig.length !== 0 ? ( + ) : explorerContextState.account !== undefined ? ( No transactions for the selected account! ) : ( Loading transfers! )}
- {state.transfers.length !== 0 && ( + {transfers.length !== 0 && (