From b7166c0a5be46a4508a4de60019ccc638397c9fc Mon Sep 17 00:00:00 2001 From: Nikita Yutanov Date: Thu, 5 Oct 2023 22:25:04 +0300 Subject: [PATCH] Add network switch without page reload (#1423) --- idea/frontend/package.json | 2 +- idea/frontend/src/app/App.tsx | 20 ++++----- .../src/app/providers/api/Provider.tsx | 10 ++--- .../frontend/src/app/providers/app/Context.ts | 7 --- .../src/app/providers/app/Provider.tsx | 21 --------- idea/frontend/src/app/providers/app/index.ts | 4 -- idea/frontend/src/app/providers/app/types.ts | 5 --- .../src/app/providers/blocks/Provider.tsx | 25 +++++------ .../src/app/providers/chain/Provider.tsx | 32 +++++++------- .../src/app/providers/events/Provider.tsx | 37 +++++++--------- .../src/app/providers/withProviders.tsx | 2 - idea/frontend/src/features/api/consts.ts | 7 +++ idea/frontend/src/features/api/index.ts | 3 ++ .../app/helpers.ts => features/api/utils.ts} | 6 +-- .../src/features/explorer/ui/block/block.tsx | 36 ++++++++------- .../local-indexer/hooks/use-local-program.ts | 9 ++-- .../local-indexer/hooks/use-local-programs.ts | 9 ++-- idea/frontend/src/features/mailbox/hooks.ts | 18 +++++--- .../features/metadata/hooks/use-metadata.ts | 6 +-- .../features/nodesSwitch/ui/NodesSwitch.tsx | 38 ++++++++++------ .../src/features/nodesSwitch/ui/node/Node.tsx | 2 +- .../ui/nodesList/NodesList.module.scss | 17 ------- .../nodesSwitch/ui/nodesList/NodesList.tsx | 44 ------------------- .../nodesSwitch/ui/nodesList/index.ts | 3 -- .../ui/nodesPopup/NodesPopup.module.scss | 29 +++++++++++- .../nodesSwitch/ui/nodesPopup/NodesPopup.tsx | 44 ++++++++++++++----- .../ui/section/Section.module.scss | 17 ------- .../nodesSwitch/ui/section/Section.tsx | 41 ----------------- .../features/nodesSwitch/ui/section/index.ts | 3 -- .../features/recentBlocks/ui/RecentBlocks.tsx | 6 ++- .../voucher/hooks/use-issue-voucher.ts | 6 ++- idea/frontend/src/hooks/context.ts | 4 +- idea/frontend/src/hooks/index.ts | 5 +-- idea/frontend/src/hooks/useBalanceTransfer.ts | 8 +++- .../src/hooks/useCodeUpload/useCodeUpload.tsx | 17 ++++--- idea/frontend/src/hooks/useCodes.ts | 18 +++++--- .../useEventSubscriptions.ts | 6 +-- .../hooks/useGasCalculate/useGasCalculate.ts | 6 ++- idea/frontend/src/hooks/useGasMultiplier.ts | 6 ++- idea/frontend/src/hooks/useIsProgramExists.ts | 22 ---------- .../useMessageActions/useMessageActions.ts | 10 ++++- .../hooks/useMessageClaim/useMessageClaim.ts | 9 +++- idea/frontend/src/hooks/useMetaOnUpload.tsx | 11 +++-- .../useMetadataUpload/useMetadataUpload.ts | 5 ++- idea/frontend/src/hooks/useNodeVersion.tsx | 12 +++-- .../useProgramActions/useProgramActions.tsx | 12 ++++- idea/frontend/src/hooks/useStateRead.ts | 12 +++-- idea/frontend/src/hooks/useWaitlist.ts | 11 +++-- .../ui/InitializeProgram.tsx | 6 ++- .../ui/form/balance-unit/BalanceUnit.tsx | 4 +- .../src/widgets/footer/ui/Footer.module.scss | 28 ++++++++++-- .../frontend/src/widgets/footer/ui/Footer.tsx | 19 ++++++-- .../footer/ui/dotButton/DotButton.module.scss | 21 --------- .../widgets/footer/ui/dotButton/DotButton.tsx | 20 --------- .../src/widgets/footer/ui/dotButton/index.ts | 3 -- .../frontend/src/widgets/header/ui/Header.tsx | 5 ++- .../src/widgets/header/ui/topSide/TopSide.tsx | 13 +++--- .../widgets/messageForm/ui/MessageForm.tsx | 6 +-- .../widgets/programForm/ui/ProgramForm.tsx | 6 +-- yarn.lock | 10 ++--- 60 files changed, 373 insertions(+), 451 deletions(-) delete mode 100644 idea/frontend/src/app/providers/app/Context.ts delete mode 100644 idea/frontend/src/app/providers/app/Provider.tsx delete mode 100644 idea/frontend/src/app/providers/app/index.ts delete mode 100644 idea/frontend/src/app/providers/app/types.ts create mode 100644 idea/frontend/src/features/api/consts.ts create mode 100644 idea/frontend/src/features/api/index.ts rename idea/frontend/src/{app/providers/app/helpers.ts => features/api/utils.ts} (79%) delete mode 100644 idea/frontend/src/features/nodesSwitch/ui/nodesList/NodesList.module.scss delete mode 100644 idea/frontend/src/features/nodesSwitch/ui/nodesList/NodesList.tsx delete mode 100644 idea/frontend/src/features/nodesSwitch/ui/nodesList/index.ts delete mode 100644 idea/frontend/src/features/nodesSwitch/ui/section/Section.module.scss delete mode 100644 idea/frontend/src/features/nodesSwitch/ui/section/Section.tsx delete mode 100644 idea/frontend/src/features/nodesSwitch/ui/section/index.ts delete mode 100644 idea/frontend/src/hooks/useIsProgramExists.ts delete mode 100644 idea/frontend/src/widgets/footer/ui/dotButton/DotButton.module.scss delete mode 100644 idea/frontend/src/widgets/footer/ui/dotButton/DotButton.tsx delete mode 100644 idea/frontend/src/widgets/footer/ui/dotButton/index.ts diff --git a/idea/frontend/package.json b/idea/frontend/package.json index d5a2b9db47..836469cc3d 100644 --- a/idea/frontend/package.json +++ b/idea/frontend/package.json @@ -10,7 +10,7 @@ }, "dependencies": { "@gear-js/api": "0.33.6", - "@gear-js/react-hooks": "0.6.10", + "@gear-js/react-hooks": "0.8.0", "@gear-js/ui": "0.5.19", "@hcaptcha/react-hcaptcha": "^1.4.4", "@mantine/form": "^5.4.0", diff --git a/idea/frontend/src/app/App.tsx b/idea/frontend/src/app/App.tsx index 2afa6abde0..98ee1b70cd 100644 --- a/idea/frontend/src/app/App.tsx +++ b/idea/frontend/src/app/App.tsx @@ -4,7 +4,7 @@ import { ErrorBoundary } from 'react-error-boundary'; import { useAccount, useApi } from '@gear-js/react-hooks'; import 'simplebar-react/dist/simplebar.min.css'; -import { useApp, useChain, useEventSubscriptions, useMobileDisclaimer } from 'hooks'; +import { useChain, useEventSubscriptions, useMobileDisclaimer } from 'hooks'; import { Menu } from 'widgets/menu'; import { Header } from 'widgets/header'; import { Footer } from 'widgets/footer'; @@ -13,12 +13,12 @@ import { Routing } from 'pages'; import { LocalStorage, NODE_ADRESS_URL_PARAM } from 'shared/config'; import { Loader } from 'shared/ui/loader'; import { ErrorFallback } from 'shared/ui/errorFallback'; +import { INITIAL_ENDPOINT } from 'features/api'; import { withProviders } from './providers'; import './App.scss'; const App = withProviders(() => { - const { nodeAddress } = useApp(); const { pathname } = useLocation(); const [searchParams, setSearchParams] = useSearchParams(); @@ -34,17 +34,17 @@ const App = withProviders(() => { useEffect(() => { const urlNodeAddress = searchParams.get(NODE_ADRESS_URL_PARAM); - if (!urlNodeAddress) { - searchParams.set(NODE_ADRESS_URL_PARAM, nodeAddress); - setSearchParams(searchParams, { replace: true }); - } + if (urlNodeAddress) return; + + searchParams.set(NODE_ADRESS_URL_PARAM, isApiReady ? api.provider.endpoint : INITIAL_ENDPOINT); + setSearchParams(searchParams, { replace: true }); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [searchParams]); + }, [isApiReady, searchParams]); useEffect(() => { - if (isApiReady) { - localStorage.setItem(LocalStorage.Genesis, api.genesisHash.toHex()); - } + if (!isApiReady) return; + + localStorage.setItem(LocalStorage.Genesis, api.genesisHash.toHex()); // eslint-disable-next-line react-hooks/exhaustive-deps }, [isApiReady]); diff --git a/idea/frontend/src/app/providers/api/Provider.tsx b/idea/frontend/src/app/providers/api/Provider.tsx index 5ebf796459..3bfa072020 100644 --- a/idea/frontend/src/app/providers/api/Provider.tsx +++ b/idea/frontend/src/app/providers/api/Provider.tsx @@ -1,11 +1,9 @@ import { ProviderProps, ApiProvider as GearApiProvider } from '@gear-js/react-hooks'; -import { useApp } from 'hooks'; +import { INITIAL_ENDPOINT } from 'features/api'; -const ApiProvider = ({ children }: ProviderProps) => { - const { nodeAddress } = useApp(); - - return {children}; -}; +const ApiProvider = ({ children }: ProviderProps) => ( + {children} +); export { ApiProvider }; diff --git a/idea/frontend/src/app/providers/app/Context.ts b/idea/frontend/src/app/providers/app/Context.ts deleted file mode 100644 index e72ebd9977..0000000000 --- a/idea/frontend/src/app/providers/app/Context.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { createContext } from 'react'; - -import { AppValues } from './types'; - -const AppContext = createContext({} as AppValues); - -export { AppContext }; diff --git a/idea/frontend/src/app/providers/app/Provider.tsx b/idea/frontend/src/app/providers/app/Provider.tsx deleted file mode 100644 index 261586545a..0000000000 --- a/idea/frontend/src/app/providers/app/Provider.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { ReactNode, useMemo } from 'react'; - -import { LocalStorage, NODE_ADDRESS } from 'shared/config'; - -import { getNodeAddressFromUrl } from './helpers'; -import { AppContext } from './Context'; - -type Props = { - children: ReactNode; -}; - -const AppProvider = ({ children }: Props) => { - const values = useMemo( - () => ({ nodeAddress: getNodeAddressFromUrl() || localStorage[LocalStorage.Node] || NODE_ADDRESS }), - [], - ); - - return {children}; -}; - -export { AppProvider }; diff --git a/idea/frontend/src/app/providers/app/index.ts b/idea/frontend/src/app/providers/app/index.ts deleted file mode 100644 index 92a7aab14a..0000000000 --- a/idea/frontend/src/app/providers/app/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { AppContext } from './Context'; -import { AppProvider } from './Provider'; - -export { AppContext, AppProvider }; diff --git a/idea/frontend/src/app/providers/app/types.ts b/idea/frontend/src/app/providers/app/types.ts deleted file mode 100644 index 2e8fb55341..0000000000 --- a/idea/frontend/src/app/providers/app/types.ts +++ /dev/null @@ -1,5 +0,0 @@ -type AppValues = { - nodeAddress: string; -}; - -export type { AppValues }; diff --git a/idea/frontend/src/app/providers/blocks/Provider.tsx b/idea/frontend/src/app/providers/blocks/Provider.tsx index 3677231f4b..3438997118 100644 --- a/idea/frontend/src/app/providers/blocks/Provider.tsx +++ b/idea/frontend/src/app/providers/blocks/Provider.tsx @@ -1,6 +1,6 @@ import { ReactNode, useState, useEffect } from 'react'; -import { Header } from '@polkadot/types/interfaces'; import { useApi } from '@gear-js/react-hooks'; +import { Header } from '@polkadot/types/interfaces'; import { IChainBlock } from 'entities/chainBlock'; @@ -12,7 +12,7 @@ type Props = { }; const BlocksProvider = ({ children }: Props) => { - const { api } = useApi(); + const { api, isApiReady } = useApi(); const [blocks, setBlocks] = useState([]); const updateBlocks = (block: IChainBlock) => @@ -22,25 +22,22 @@ const BlocksProvider = ({ children }: Props) => { return [block, ...blocksTail]; }); - const handleSubscription = (header: Header) => - api.blocks - .getBlockTimestamp(header.hash) - .then((timestamp) => getTime(timestamp.toNumber())) - .then((time) => getBlock(header, time)) - .then(updateBlocks); - useEffect(() => { - if (!api) { - return; - } + if (!isApiReady) return; - const unsub = api.gearEvents.subscribeToNewBlocks(handleSubscription); + const unsub = api.blocks.subscribeNewHeads((header: Header) => + api.blocks + .getBlockTimestamp(header.hash) + .then((timestamp) => getTime(timestamp.toNumber())) + .then((time) => getBlock(header, time)) + .then((result) => updateBlocks(result)), + ); return () => { unsub.then((unsubscribe) => unsubscribe()); }; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [api]); + }, [isApiReady]); return {children}; }; diff --git a/idea/frontend/src/app/providers/chain/Provider.tsx b/idea/frontend/src/app/providers/chain/Provider.tsx index bbca08bd79..a5fe172ee7 100644 --- a/idea/frontend/src/app/providers/chain/Provider.tsx +++ b/idea/frontend/src/app/providers/chain/Provider.tsx @@ -14,30 +14,30 @@ const ChainProvider = ({ children }: ProviderProps) => { const [isDevChain, setIsDevChain] = useState(); const [isTestBalanceAvailable, setIsTestBalanceAvailable] = useState(); - const isChainRequestReady = isDevChain !== undefined && isTestBalanceAvailable !== undefined; useEffect(() => { - if (genesis) { - const apiRequest = new RPCService(); + setIsDevChain(undefined); - apiRequest.callRPC(RpcMethods.NetworkData, { genesis }).then(({ result }) => setIsDevChain(!result)); - } + if (!genesis) return; + + new RPCService().callRPC(RpcMethods.NetworkData, { genesis }).then(({ result }) => setIsDevChain(!result)); }, [genesis]); useEffect(() => { - if (isDevChain !== undefined) { - if (isDevChain) { - setIsTestBalanceAvailable(true); - } else { - const apiRequest = new RPCService(); - - apiRequest - .callRPC(RpcMethods.TestBalanceAvailable, { genesis }) - .then(({ result }) => setIsTestBalanceAvailable(result)); - } + setIsTestBalanceAvailable(undefined); + + if (isDevChain === undefined) return; + + if (isDevChain) { + setIsTestBalanceAvailable(true); + } else { + new RPCService() + .callRPC(RpcMethods.TestBalanceAvailable, { genesis }) + .then(({ result }) => setIsTestBalanceAvailable(result)); } - }, [isDevChain, genesis]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isDevChain]); return {children}; }; diff --git a/idea/frontend/src/app/providers/events/Provider.tsx b/idea/frontend/src/app/providers/events/Provider.tsx index aee35d3438..c15be308c1 100644 --- a/idea/frontend/src/app/providers/events/Provider.tsx +++ b/idea/frontend/src/app/providers/events/Provider.tsx @@ -1,43 +1,36 @@ import { useState, useEffect } from 'react'; import { useApi, ProviderProps } from '@gear-js/react-hooks'; -import { EventRecords, IdeaEvent, Section } from 'features/explorer'; +import { IdeaEvent, Section } from 'features/explorer'; -import { UnsubscribePromise } from '@polkadot/api/types'; import { EventsContext } from './Context'; const EventsProvider = ({ children }: ProviderProps) => { - const { api } = useApi(); + const { api, isApiReady } = useApi(); const [events, setEvents] = useState(); - const subscribeToEvents = () => - api.query.system.events(async (records: EventRecords) => { - const { createdAtHash } = records; - - if (createdAtHash) { - const blockNumber = await api.blocks.getBlockNumber(createdAtHash); + useEffect(() => { + if (!isApiReady) return; - const newEvents = records - .map(({ event }) => new IdeaEvent(event, blockNumber)) - .filter(({ section }) => section !== Section.System) - .reverse(); + const unsub = api.query.system.events(async (records) => { + const { createdAtHash } = records; + if (!createdAtHash) return; - setEvents((prevEvents) => (prevEvents ? [...newEvents, ...prevEvents] : newEvents)); - } - }) as unknown as UnsubscribePromise; + const blockNumber = await api.blocks.getBlockNumber(createdAtHash); - useEffect(() => { - if (!api) { - return; - } + const newEvents = records + .map(({ event }) => new IdeaEvent(event, blockNumber)) + .filter(({ section }) => section !== Section.System) + .reverse(); - const unsub = subscribeToEvents(); + setEvents((prevEvents) => (prevEvents ? [...newEvents, ...prevEvents] : newEvents)); + }); return () => { unsub.then((unsubscribe) => unsubscribe()); }; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [api]); + }, [isApiReady]); return {children}; }; diff --git a/idea/frontend/src/app/providers/withProviders.tsx b/idea/frontend/src/app/providers/withProviders.tsx index 1e63ffa8f7..a7d502fdce 100644 --- a/idea/frontend/src/app/providers/withProviders.tsx +++ b/idea/frontend/src/app/providers/withProviders.tsx @@ -2,7 +2,6 @@ import { ComponentType } from 'react'; import { BrowserRouter } from 'react-router-dom'; import { AccountProvider } from '@gear-js/react-hooks'; -import { AppProvider } from './app'; import { ApiProvider } from './api'; import { AlertProvider } from './alert'; import { BlocksProvider } from './blocks'; @@ -13,7 +12,6 @@ import { OnboardingProvider } from './onboarding'; const providers = [ BrowserRouter, - AppProvider, AlertProvider, ApiProvider, AccountProvider, diff --git a/idea/frontend/src/features/api/consts.ts b/idea/frontend/src/features/api/consts.ts new file mode 100644 index 0000000000..950d7a1e67 --- /dev/null +++ b/idea/frontend/src/features/api/consts.ts @@ -0,0 +1,7 @@ +import { LocalStorage, NODE_ADDRESS } from 'shared/config'; + +import { getNodeAddressFromUrl } from './utils'; + +const INITIAL_ENDPOINT = getNodeAddressFromUrl() || (localStorage[LocalStorage.Node] as string | null) || NODE_ADDRESS; + +export { INITIAL_ENDPOINT }; diff --git a/idea/frontend/src/features/api/index.ts b/idea/frontend/src/features/api/index.ts new file mode 100644 index 0000000000..5cd4297417 --- /dev/null +++ b/idea/frontend/src/features/api/index.ts @@ -0,0 +1,3 @@ +import { INITIAL_ENDPOINT } from './consts'; + +export { INITIAL_ENDPOINT }; diff --git a/idea/frontend/src/app/providers/app/helpers.ts b/idea/frontend/src/features/api/utils.ts similarity index 79% rename from idea/frontend/src/app/providers/app/helpers.ts rename to idea/frontend/src/features/api/utils.ts index 28d9fd55ee..49068c6a1a 100644 --- a/idea/frontend/src/app/providers/app/helpers.ts +++ b/idea/frontend/src/features/api/utils.ts @@ -1,13 +1,11 @@ -import { isNodeAddressValid } from 'shared/helpers'; import { NODE_ADRESS_URL_PARAM } from 'shared/config'; +import { isNodeAddressValid } from 'shared/helpers'; const getNodeAddressFromUrl = () => { const searchParams = new URLSearchParams(window.location.search); const nodeAddress = searchParams.get(NODE_ADRESS_URL_PARAM); - if (nodeAddress && isNodeAddressValid(nodeAddress)) { - return nodeAddress; - } + if (nodeAddress && isNodeAddressValid(nodeAddress)) return nodeAddress; }; export { getNodeAddressFromUrl }; diff --git a/idea/frontend/src/features/explorer/ui/block/block.tsx b/idea/frontend/src/features/explorer/ui/block/block.tsx index 85d07789c9..d8220ce336 100644 --- a/idea/frontend/src/features/explorer/ui/block/block.tsx +++ b/idea/frontend/src/features/explorer/ui/block/block.tsx @@ -10,10 +10,12 @@ import { MainTable } from '../main-table'; import { System } from '../system'; import styles from './block.module.scss'; -type Params = { blockId: string }; +type Params = { + blockId: string; +}; const Block = () => { - const { api } = useApi(); + const { api, isApiReady } = useApi(); const { blockId } = useParams() as Params; @@ -30,21 +32,21 @@ const Block = () => { }; useEffect(() => { - if (api) { - resetState(); - - const isBlockHash = isHex(blockId); - const id = isBlockHash ? (blockId as `0x${string}`) : Number(blockId); - - api.blocks - .get(id) - .then(({ block: newBlock }) => { - api.blocks.getEvents(newBlock.hash).then(setEventRecords); - setBlock(newBlock); - }) - .catch(({ message }: Error) => setError(message)); - } - }, [api, blockId]); + if (!isApiReady) return; + + resetState(); + + const id = isHex(blockId) ? blockId : Number(blockId); + + api.blocks + .get(id) + .then((result) => { + api.blocks.getEvents(result.block.hash).then((recordsResult) => setEventRecords(recordsResult)); + setBlock(result.block); + }) + .catch(({ message }: Error) => setError(message)); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isApiReady, blockId]); return (
diff --git a/idea/frontend/src/features/local-indexer/hooks/use-local-program.ts b/idea/frontend/src/features/local-indexer/hooks/use-local-program.ts index 00103241e8..efed5a0776 100644 --- a/idea/frontend/src/features/local-indexer/hooks/use-local-program.ts +++ b/idea/frontend/src/features/local-indexer/hooks/use-local-program.ts @@ -7,13 +7,14 @@ import { IProgram, useProgramStatus } from 'features/program'; import { isState, useMetadata } from 'features/metadata'; function useLocalProgram() { - const { api } = useApi(); - const genesis = api?.genesisHash.toHex(); + const { api, isApiReady } = useApi(); const { getMetadata } = useMetadata(); const { getProgramStatus } = useProgramStatus(); const getChainProgram = async (id: HexString) => { + if (!isApiReady) return Promise.reject(new Error('API is not initialized')); + const name = id; const status = await getProgramStatus(id); @@ -52,10 +53,12 @@ function useLocalProgram() { }; const getLocalProgram = async (id: HexString) => { + if (!isApiReady) return Promise.reject(new Error('API is not initialized')); + const localForageProgram = await PROGRAMS_LOCAL_FORAGE.getItem(id); const isProgramInChain = id === localForageProgram?.id; - const isProgramFromChain = genesis === localForageProgram?.genesis; + const isProgramFromChain = api.genesisHash.toHex() === localForageProgram?.genesis; return isProgramInChain && isProgramFromChain ? localForageProgram : getChainProgram(id); }; diff --git a/idea/frontend/src/features/local-indexer/hooks/use-local-programs.ts b/idea/frontend/src/features/local-indexer/hooks/use-local-programs.ts index 2b4da204cd..a16b1f2f10 100644 --- a/idea/frontend/src/features/local-indexer/hooks/use-local-programs.ts +++ b/idea/frontend/src/features/local-indexer/hooks/use-local-programs.ts @@ -7,7 +7,7 @@ import { LocalProgram } from '../types'; import { useLocalProgram } from './use-local-program'; function useLocalPrograms() { - const { api } = useApi(); + const { api, isApiReady } = useApi(); const { getLocalProgram } = useLocalProgram(); const getFilteredPrograms = (programs: (IProgram | LocalProgram)[], params: FetchProgramsParams) => { @@ -32,14 +32,17 @@ function useLocalPrograms() { (program, nextProgram) => Date.parse(nextProgram.timestamp || '0') - Date.parse(program.timestamp || '0'), ); - const getLocalPrograms = (params: FetchProgramsParams) => - api.program + const getLocalPrograms = (params: FetchProgramsParams) => { + if (!isApiReady) return Promise.reject(new Error('API is not initialized')); + + return api.program .allUploadedPrograms() .then((ids) => ids.map((id) => getLocalProgram(id))) .then((result) => Promise.all(result)) .then((result) => getFilteredPrograms(result, params)) .then((result) => getSortedPrograms(result)) .then((programs) => ({ result: { programs, count: programs.length } })); + }; return { getLocalPrograms }; } diff --git a/idea/frontend/src/features/mailbox/hooks.ts b/idea/frontend/src/features/mailbox/hooks.ts index 6212d29f03..67cebd8897 100644 --- a/idea/frontend/src/features/mailbox/hooks.ts +++ b/idea/frontend/src/features/mailbox/hooks.ts @@ -5,7 +5,7 @@ import { useEffect, useState } from 'react'; import { MailboxItem } from './types'; function useMailbox() { - const { api } = useApi(); + const { api, isApiReady } = useApi(); const { account } = useAccount(); const { decodedAddress } = account || {}; @@ -13,13 +13,16 @@ function useMailbox() { const [mailbox, setMailbox] = useState(); useEffect(() => { - if (!decodedAddress) return setMailbox(undefined); + setMailbox(undefined); + + if (!isApiReady || !decodedAddress) return; api.mailbox .read(decodedAddress) .then((items) => items.map((item) => item.toHuman() as MailboxItem)) .then((result) => setMailbox(result)); - }, [api, decodedAddress]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isApiReady, decodedAddress]); // hide message on value claim e.g. const removeMessage = (id: HexString) => @@ -29,7 +32,7 @@ function useMailbox() { } function useMailboxItem(messageId: HexString | undefined) { - const { api } = useApi(); + const { api, isApiReady } = useApi(); const { account } = useAccount(); const { decodedAddress } = account || {}; @@ -37,7 +40,9 @@ function useMailboxItem(messageId: HexString | undefined) { const [mailboxItem, setMailboxItem] = useState(); useEffect(() => { - if (!decodedAddress || !messageId) return setMailboxItem(undefined); + setMailboxItem(undefined); + + if (!isApiReady || !decodedAddress || !messageId) return; // TODO: error should be thrown in @gear-js/api api.mailbox @@ -46,7 +51,8 @@ function useMailboxItem(messageId: HexString | undefined) { Array.isArray(item) ? (item.toHuman() as MailboxItem) : Promise.reject(new Error('Message not found')), ) .then((result) => setMailboxItem(result)); - }, [api, decodedAddress, messageId]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isApiReady, decodedAddress, messageId]); return mailboxItem; } diff --git a/idea/frontend/src/features/metadata/hooks/use-metadata.ts b/idea/frontend/src/features/metadata/hooks/use-metadata.ts index b40e82a014..2e3a85f0f7 100644 --- a/idea/frontend/src/features/metadata/hooks/use-metadata.ts +++ b/idea/frontend/src/features/metadata/hooks/use-metadata.ts @@ -12,19 +12,19 @@ function useMetadata(hash?: HexString | null | undefined) { const { isDevChain } = useChain(); const [metadata, setMetadata] = useState(); - const [isMetadataReady, setisMetadataReady] = useState(false); + const [isMetadataReady, setIsMetadataReady] = useState(false); const getMetadata = (params: { hash: HexString }) => isDevChain ? getLocalMetadata(params).catch(() => fetchMetadata(params)) : fetchMetadata(params); useEffect(() => { - if (hash === null) return setisMetadataReady(true); + if (hash === null) return setIsMetadataReady(true); if (!hash) return; getMetadata({ hash }) .then(({ result }) => result.hex && setMetadata(ProgramMetadata.from(result.hex))) .catch(({ message, code }: RPCError) => code !== RPCErrorCode.MetadataNotFound && alert.error(message)) - .finally(() => setisMetadataReady(true)); + .finally(() => setIsMetadataReady(true)); // eslint-disable-next-line react-hooks/exhaustive-deps }, [hash]); diff --git a/idea/frontend/src/features/nodesSwitch/ui/NodesSwitch.tsx b/idea/frontend/src/features/nodesSwitch/ui/NodesSwitch.tsx index 77764d93f4..f59ad97a35 100644 --- a/idea/frontend/src/features/nodesSwitch/ui/NodesSwitch.tsx +++ b/idea/frontend/src/features/nodesSwitch/ui/NodesSwitch.tsx @@ -1,14 +1,14 @@ -import { useApi } from '@gear-js/react-hooks'; -import { useState } from 'react'; +import { useAlert, useApi } from '@gear-js/react-hooks'; +import { useEffect, useState } from 'react'; import { useSearchParams } from 'react-router-dom'; import { CSSTransition } from 'react-transition-group'; -import { useApp, useModal, useOutsideClick } from 'hooks'; +import { useModal, useOutsideClick } from 'hooks'; import { AnimationTimeout, LocalStorage, NODE_ADRESS_URL_PARAM } from 'shared/config'; - import { useNodes } from 'widgets/menu/helpers/useNodes'; - import { OnboardingTooltip } from 'shared/ui/onboardingTooltip'; +import { INITIAL_ENDPOINT } from 'features/api'; + import { NodesButton } from './nodesButton'; import { NodesPopup } from './nodesPopup'; @@ -17,27 +17,30 @@ type Props = { }; const NodesSwitch = ({ isButtonFullWidth }: Props) => { - const { api, isApiReady } = useApi(); - const { nodeSections, isNodesLoading, addLocalNode, removeLocalNode } = useNodes(); + const { api, isApiReady, switchNetwork } = useApi(); + const nodeAddress = api?.provider.endpoint; - const { nodeAddress } = useApp(); + const alert = useAlert(); + + const { nodeSections, isNodesLoading, addLocalNode, removeLocalNode } = useNodes(); const { showModal, closeModal } = useModal(); const [searchParams, setSearchParams] = useSearchParams(); const [isNodesOpen, setIsNodesOpen] = useState(false); - const [selectedNode, setSelectedNode] = useState(nodeAddress); + const [selectedNode, setSelectedNode] = useState(INITIAL_ENDPOINT); const [isModalHide, setIsModalHidden] = useState(true); const close = () => setIsNodesOpen(false); const ref = useOutsideClick(close, isModalHide); - const chain = api?.runtimeChain.toHuman(); - const specName = api?.runtimeVersion.specName.toHuman(); - const specVersion = api?.runtimeVersion.specVersion.toHuman(); + const chain = isApiReady ? api.runtimeChain.toHuman() : 'Loading...'; + const specName = isApiReady ? api.runtimeVersion.specName.toHuman() : 'Loading...'; + const specVersion = isApiReady ? api.runtimeVersion.specVersion.toHuman() : 'Loading...'; const toggleNodesPopup = () => setIsNodesOpen((prevState) => !prevState); + const closeNodesPopup = () => setIsNodesOpen(false); const closeNetworkModal = () => { closeModal(); @@ -53,13 +56,14 @@ const NodesSwitch = ({ isButtonFullWidth }: Props) => { }; const switchNode = () => { - // remove param to update it during nodeApi init searchParams.set(NODE_ADRESS_URL_PARAM, selectedNode); setSearchParams(searchParams); localStorage.setItem(LocalStorage.Node, selectedNode); - window.location.reload(); + switchNetwork({ endpoint: selectedNode }) + .then(() => closeNodesPopup()) + .catch(({ message }: Error) => alert.error(message)); }; const showAddNodeModal = () => { @@ -68,6 +72,12 @@ const NodesSwitch = ({ isButtonFullWidth }: Props) => { showModal('network', { nodeSections, addNetwork: handleAddButtonClick, onClose: closeNetworkModal }); }; + useEffect(() => { + if (!nodeAddress) return; + + setSelectedNode(nodeAddress); + }, [nodeAddress]); + return (
diff --git a/idea/frontend/src/features/nodesSwitch/ui/node/Node.tsx b/idea/frontend/src/features/nodesSwitch/ui/node/Node.tsx index 997348e670..dda38834c8 100644 --- a/idea/frontend/src/features/nodesSwitch/ui/node/Node.tsx +++ b/idea/frontend/src/features/nodesSwitch/ui/node/Node.tsx @@ -11,7 +11,7 @@ import { ICON } from 'widgets/menu/model/consts'; import styles from './Node.module.scss'; type Props = NodeType & { - nodeAddress: string; + nodeAddress: string | undefined; selectedNode: string; selectNode: (address: string) => void; removeLocalNode: (address: string) => void; diff --git a/idea/frontend/src/features/nodesSwitch/ui/nodesList/NodesList.module.scss b/idea/frontend/src/features/nodesSwitch/ui/nodesList/NodesList.module.scss deleted file mode 100644 index 68bc0d4fa1..0000000000 --- a/idea/frontend/src/features/nodesSwitch/ui/nodesList/NodesList.module.scss +++ /dev/null @@ -1,17 +0,0 @@ -@use 'shared/assets/styles/_shared.scss' as *; -@use 'shared/assets/styles/_mixins.scss' as *; -@use 'shared/assets/styles/_variables.scss' as *; - -.simpleBar { - @include customSimplebar; - - flex: 1 1 440px; - // needed for scrolling with flex - min-height: 1px; - - .list { - @include childrenMargin($marginMedium); - - padding-right: toRem(22); - } -} diff --git a/idea/frontend/src/features/nodesSwitch/ui/nodesList/NodesList.tsx b/idea/frontend/src/features/nodesSwitch/ui/nodesList/NodesList.tsx deleted file mode 100644 index 5957522130..0000000000 --- a/idea/frontend/src/features/nodesSwitch/ui/nodesList/NodesList.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { useEffect } from 'react'; -import SimpleBar from 'simplebar-react'; - -import { NodeSection } from 'entities/node'; - -import styles from './NodesList.module.scss'; -import { Section } from '../section'; - -type Props = { - nodeAddress: string; - nodeSections: NodeSection[]; - selectedNode: string; - selectNode: (address: string) => void; - removeLocalNode: (address: string) => void; -}; - -const NodesList = (props: Props) => { - const { nodeAddress, nodeSections, selectedNode, selectNode, removeLocalNode } = props; - - useEffect(() => { - document.getElementById(selectedNode)?.scrollIntoView(false); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - return ( - -
    - {nodeSections.map((section, index) => ( -
    - ))} -
-
- ); -}; - -export { NodesList }; diff --git a/idea/frontend/src/features/nodesSwitch/ui/nodesList/index.ts b/idea/frontend/src/features/nodesSwitch/ui/nodesList/index.ts deleted file mode 100644 index 7d3c30db65..0000000000 --- a/idea/frontend/src/features/nodesSwitch/ui/nodesList/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { NodesList } from './NodesList'; - -export { NodesList }; diff --git a/idea/frontend/src/features/nodesSwitch/ui/nodesPopup/NodesPopup.module.scss b/idea/frontend/src/features/nodesSwitch/ui/nodesPopup/NodesPopup.module.scss index f6cbf6b63a..73a92366cd 100644 --- a/idea/frontend/src/features/nodesSwitch/ui/nodesPopup/NodesPopup.module.scss +++ b/idea/frontend/src/features/nodesSwitch/ui/nodesPopup/NodesPopup.module.scss @@ -2,7 +2,7 @@ @use 'shared/assets/styles/mixins' as *; @use 'shared/assets/styles/variables' as *; @use 'shared/assets/styles/animations' as *; -@use '@gear-js/ui/variables' as *; +@use '@gear-js/ui/variables' as ui; $popupWidth: 340px; @@ -20,7 +20,7 @@ $popupWidth: 340px; box-shadow: inset 0px 1px 0px $gray005; border-radius: 16px; height: 630px; - border: $borderModal; + border: ui.$borderModal; &.loading { @include loading(); @@ -65,3 +65,28 @@ $popupWidth: 340px; margin-top: 26px; } } + +.simpleBar { + @include customSimplebar; + flex: 1 1 440px; + min-height: 1px; // needed for scrolling with flex + + .list { + @include childrenMargin($marginMedium); + padding-right: toRem(22); + + .caption { + font-family: 'Kanit'; + font-weight: 600; + font-size: toRem(20); + line-height: 1.5; + color: $textColor; + text-transform: capitalize; + margin-bottom: $marginTiny; + } + + .sectionList { + @include childrenMargin(10px); + } + } +} diff --git a/idea/frontend/src/features/nodesSwitch/ui/nodesPopup/NodesPopup.tsx b/idea/frontend/src/features/nodesSwitch/ui/nodesPopup/NodesPopup.tsx index 532509a975..74140e463d 100644 --- a/idea/frontend/src/features/nodesSwitch/ui/nodesPopup/NodesPopup.tsx +++ b/idea/frontend/src/features/nodesSwitch/ui/nodesPopup/NodesPopup.tsx @@ -1,6 +1,7 @@ import { Button } from '@gear-js/ui'; import clsx from 'clsx'; import { CSSTransition } from 'react-transition-group'; +import SimpleBar from 'simplebar-react'; import { AnimationTimeout } from 'shared/config'; import { ReactComponent as plusSVG } from 'shared/assets/images/actions/plus.svg'; @@ -8,13 +9,13 @@ import { ReactComponent as closeSVG } from 'shared/assets/images/actions/close.s import { ReactComponent as switchSVG } from 'shared/assets/images/actions/switch.svg'; import { NodeSection } from 'entities/node'; -import { NodesList } from '../nodesList'; +import { Node as NodeItem } from '../node'; import styles from './NodesPopup.module.scss'; type Props = { chain: string | undefined; isLoading: boolean; - nodeAddress: string; + nodeAddress: string | undefined; nodeSections: NodeSection[]; selectedNode: string; selectNode: (address: string) => void; @@ -40,24 +41,47 @@ const NodesPopup = (props: Props) => { const isCurrentNode = selectedNode === nodeAddress; + const getNodes = (section: NodeSection) => + section.nodes.map((node, index) => ( + + )); + + const getSections = () => + nodeSections.map((section, index) => ( + // eslint-disable-next-line react/no-array-index-key +
  • +

    {section.caption}

    +
      {getNodes(section)}
    +
  • + )); + return ( ); diff --git a/idea/frontend/src/features/nodesSwitch/ui/section/Section.module.scss b/idea/frontend/src/features/nodesSwitch/ui/section/Section.module.scss deleted file mode 100644 index b94064f342..0000000000 --- a/idea/frontend/src/features/nodesSwitch/ui/section/Section.module.scss +++ /dev/null @@ -1,17 +0,0 @@ -@use 'shared/assets/styles/_shared.scss' as *; -@use 'shared/assets/styles/_mixins.scss' as *; -@use 'shared/assets/styles/_variables.scss' as *; - -.caption { - font-family: 'Kanit'; - font-weight: 600; - font-size: toRem(20); - line-height: 1.5; - color: $textColor; - text-transform: capitalize; - margin-bottom: $marginTiny; -} - -.sectionList { - @include childrenMargin(10px) -} \ No newline at end of file diff --git a/idea/frontend/src/features/nodesSwitch/ui/section/Section.tsx b/idea/frontend/src/features/nodesSwitch/ui/section/Section.tsx deleted file mode 100644 index 8baa1abbcc..0000000000 --- a/idea/frontend/src/features/nodesSwitch/ui/section/Section.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { memo } from 'react'; -import { NodeSection } from 'entities/node'; - -import styles from './Section.module.scss'; -import { Node as NodeItem } from '../node'; - -type Props = { - section: NodeSection; - nodeAddress: string; - selectedNode: string; - selectNode: (address: string) => void; - removeLocalNode: (address: string) => void; -}; - -const Section = memo((props: Props) => { - const { section, nodeAddress, selectedNode, selectNode, removeLocalNode } = props; - - const getNodes = () => - section.nodes.map((node, index) => ( - - )); - - return ( -
  • -

    {section.caption}

    -
      {getNodes()}
    -
  • - ); -}); - -export { Section }; diff --git a/idea/frontend/src/features/nodesSwitch/ui/section/index.ts b/idea/frontend/src/features/nodesSwitch/ui/section/index.ts deleted file mode 100644 index 201790a8f6..0000000000 --- a/idea/frontend/src/features/nodesSwitch/ui/section/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { Section } from './Section'; - -export { Section }; diff --git a/idea/frontend/src/features/recentBlocks/ui/RecentBlocks.tsx b/idea/frontend/src/features/recentBlocks/ui/RecentBlocks.tsx index 8b1030dea8..bc70693e6b 100644 --- a/idea/frontend/src/features/recentBlocks/ui/RecentBlocks.tsx +++ b/idea/frontend/src/features/recentBlocks/ui/RecentBlocks.tsx @@ -18,7 +18,7 @@ import { Graph } from './graph'; import { RecentBlocksList } from './recentBlocksList'; const RecentBlocks = () => { - const { api } = useApi(); + const { api, isApiReady } = useApi(); const blocks = useBlocks(); const [block, setBlock] = useState(); @@ -58,10 +58,12 @@ const RecentBlocks = () => { }, [blocks]); useEffect(() => { + if (!isApiReady) return; + api.query.gear.blockNumber((result: U128) => setGearBlock(result.toNumber())); // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [isApiReady]); useEffect(() => { closeList(); diff --git a/idea/frontend/src/features/voucher/hooks/use-issue-voucher.ts b/idea/frontend/src/features/voucher/hooks/use-issue-voucher.ts index 18fc4ee4ba..3a2c5e3dea 100644 --- a/idea/frontend/src/features/voucher/hooks/use-issue-voucher.ts +++ b/idea/frontend/src/features/voucher/hooks/use-issue-voucher.ts @@ -9,11 +9,13 @@ import { PROGRAM_ERRORS } from 'shared/config'; import { getExtrinsicFailedMessage } from 'shared/helpers'; function useIssueVoucher() { - const { api } = useApi(); + const { api, isApiReady } = useApi(); const { account } = useAccount(); const alert = useAlert(); const handleEventsStatus = (events: EventRecord[], onSuccess: () => void) => { + if (!isApiReady) return Promise.reject(new Error('API is not initialized')); + events.forEach(({ event }) => { const { method, section } = event; const alertOptions = { title: `${section}.${method}` }; @@ -34,7 +36,7 @@ function useIssueVoucher() { }; const issueVoucher = async (address: HexString, programId: HexString, value: string, onSuccess: () => void) => { - if (!account) return; + if (!isApiReady || !account) return; try { const { extrinsic } = api.voucher.issue(address, programId, value); diff --git a/idea/frontend/src/hooks/context.ts b/idea/frontend/src/hooks/context.ts index 611ae10ff9..f3acc18fcc 100644 --- a/idea/frontend/src/hooks/context.ts +++ b/idea/frontend/src/hooks/context.ts @@ -1,17 +1,15 @@ import { useContext } from 'react'; -import { AppContext } from 'app/providers/app'; import { BlocksContext } from 'app/providers/blocks'; import { ChainContext } from 'app/providers/chain'; import { EventsContext } from 'app/providers/events'; import { ModalContext } from 'app/providers/modal'; import { OnboardingContext } from 'app/providers/onboarding'; -const useApp = () => useContext(AppContext); const useBlocks = () => useContext(BlocksContext); const useEvents = () => useContext(EventsContext); const useModal = () => useContext(ModalContext); const useChain = () => useContext(ChainContext); const useOnboarding = () => useContext(OnboardingContext); -export { useApp, useBlocks, useEvents, useModal, useChain, useOnboarding }; +export { useBlocks, useEvents, useModal, useChain, useOnboarding }; diff --git a/idea/frontend/src/hooks/index.ts b/idea/frontend/src/hooks/index.ts index 609df11ec3..77a9359815 100644 --- a/idea/frontend/src/hooks/index.ts +++ b/idea/frontend/src/hooks/index.ts @@ -1,4 +1,4 @@ -import { useApp, useModal, useBlocks, useEvents, useChain, useOnboarding } from './context'; +import { useModal, useBlocks, useEvents, useChain, useOnboarding } from './context'; import { useMessage } from './useMessage'; import { useOutsideClick } from './useOutsideClick'; import { useChangeEffect } from './useChangeEffect'; @@ -19,7 +19,6 @@ import { useElementSizes } from './useElementSizes'; import { useMessages } from './useMessages'; import { useWaitlist } from './useWaitlist'; import { useCodes } from './useCodes'; -import { useIsProgramExists } from './useIsProgramExists'; import { useNodeVersion } from './useNodeVersion'; import { useMobileDisclaimer } from './useMobileDisclaimer'; import { useMetaOnUpload } from './useMetaOnUpload'; @@ -28,7 +27,6 @@ import { useBalanceMultiplier } from './useBalanceMultiplier'; import { useGasMultiplier } from './useGasMultiplier'; export { - useApp, useModal, useBlocks, useEvents, @@ -53,7 +51,6 @@ export { useBalanceTransfer, useEventSubscriptions, useCodes, - useIsProgramExists, useOnboarding, useNodeVersion, useMobileDisclaimer, diff --git a/idea/frontend/src/hooks/useBalanceTransfer.ts b/idea/frontend/src/hooks/useBalanceTransfer.ts index 23f43d3b2c..8f59d3cbc8 100644 --- a/idea/frontend/src/hooks/useBalanceTransfer.ts +++ b/idea/frontend/src/hooks/useBalanceTransfer.ts @@ -1,10 +1,10 @@ import { AddressOrPair } from '@polkadot/api/types'; import { EventRecord } from '@polkadot/types/interfaces'; +import { web3FromSource } from '@polkadot/extension-dapp'; import { useApi, useAlert } from '@gear-js/react-hooks'; import { Method } from 'features/explorer'; import { getExtrinsicFailedMessage } from 'shared/helpers'; -import { web3FromSource } from '@polkadot/extension-dapp'; type Options = { signSource?: string; @@ -12,10 +12,12 @@ type Options = { }; const useBalanceTransfer = () => { + const { api, isApiReady } = useApi(); const alert = useAlert(); - const { api } = useApi(); const handleEventsStatus = (events: EventRecord[], onSuccess?: () => void) => { + if (!isApiReady) return Promise.reject(new Error('API is not initialized')); + events.forEach(({ event }) => { const { method, section } = event; @@ -33,6 +35,8 @@ const useBalanceTransfer = () => { const transferBalance = (from: AddressOrPair, to: string, value: string, options?: Options) => { try { + if (!isApiReady) throw new Error('API is not initialized'); + const { signSource, onSuccess } = options || {}; // TODO: replace to api.balance.transfer after api update to support string value diff --git a/idea/frontend/src/hooks/useCodeUpload/useCodeUpload.tsx b/idea/frontend/src/hooks/useCodeUpload/useCodeUpload.tsx index c67fd5b932..f78345727a 100644 --- a/idea/frontend/src/hooks/useCodeUpload/useCodeUpload.tsx +++ b/idea/frontend/src/hooks/useCodeUpload/useCodeUpload.tsx @@ -14,18 +14,14 @@ import { addMetadata, addCodeName } from 'api'; import { ParamsToUploadCode, ParamsToSignAndSend } from './types'; const useCodeUpload = () => { - const { api } = useApi(); + const { api, isApiReady } = useApi(); const alert = useAlert(); const { account } = useAccount(); const { showModal } = useModal(); - const submit = async (optBuffer: Buffer) => { - const { codeHash } = await api.code.upload(optBuffer); - - return codeHash; - }; - const handleEventsStatus = (events: EventRecord[], codeHash: HexString, resolve?: () => void) => { + if (!isApiReady) throw new Error('API is not initialized'); + events.forEach(({ event }) => { const { method, section } = event; const alertOptions = { title: `${section}.${method}` }; @@ -44,6 +40,8 @@ const useCodeUpload = () => { const alertId = alert.loading('SignIn', { title: TransactionName.SubmitCode }); try { + if (!isApiReady) throw new Error('API is not initialized'); + await api.code.signAndSend(account!.address, { signer }, ({ events, status }) => { if (status.isReady) { alert.update(alertId, TransactionStatus.Ready); @@ -75,15 +73,16 @@ const useCodeUpload = () => { const uploadCode = useCallback( async ({ optBuffer, name, metaHex, resolve }: ParamsToUploadCode) => { try { + if (!isApiReady) throw new Error('API is not initialized'); checkWallet(account); const { address, meta } = account!; - const [codeId, { signer }] = await Promise.all([submit(optBuffer), web3FromSource(meta.source)]); + const [{ codeHash }, { signer }] = await Promise.all([api.code.upload(optBuffer), web3FromSource(meta.source)]); const { partialFee } = await api.code.paymentInfo(address, { signer }); - const handleConfirm = () => signAndSend({ signer, name, codeId, metaHex, resolve }); + const handleConfirm = () => signAndSend({ signer, name, codeId: codeHash, metaHex, resolve }); showModal('transaction', { fee: partialFee.toHuman(), diff --git a/idea/frontend/src/hooks/useCodes.ts b/idea/frontend/src/hooks/useCodes.ts index d0813c7c9f..b7bf5ae7e9 100644 --- a/idea/frontend/src/hooks/useCodes.ts +++ b/idea/frontend/src/hooks/useCodes.ts @@ -9,7 +9,7 @@ import { DEFAULT_LIMIT } from 'shared/config'; import { useChain } from 'hooks'; const useCodes = (initLoading = true) => { - const { api } = useApi(); + const { api, isApiReady } = useApi(); const alert = useAlert(); const { isDevChain } = useChain(); @@ -23,6 +23,16 @@ const useCodes = (initLoading = true) => { setCodes((prevState) => (isReset ? data.listCode : prevState.concat(data.listCode))); }; + const getChainCodes = (isReset: boolean) => { + if (!isApiReady) return Promise.reject(new Error('API is not initialized')); + + return api.code + .all() + .then((ids) => ids.map((id) => ({ id, name: id }))) + .then((listCode) => ({ listCode: listCode as ICode[], count: listCode.length })) + .then((result) => setCodesData(result, isReset)); + }; + const fetchCodes = (params?: PaginationModel, isReset = false) => { if (isReset) { setTotalCount(0); @@ -32,11 +42,7 @@ const useCodes = (initLoading = true) => { setIsLoading(true); const promise = isDevChain - ? api.code - .all() - .then((ids) => ids.map((id) => ({ id, name: id }))) - .then((listCode) => ({ listCode: listCode as ICode[], count: listCode.length })) - .then((result) => setCodesData(result, isReset)) + ? getChainCodes(isReset) : getCodes({ limit: DEFAULT_LIMIT, ...params }).then(({ result }) => setCodesData(result, isReset)); return promise diff --git a/idea/frontend/src/hooks/useEventSubscriptions/useEventSubscriptions.ts b/idea/frontend/src/hooks/useEventSubscriptions/useEventSubscriptions.ts index 7bf2359cbf..1bc4f3ea1a 100644 --- a/idea/frontend/src/hooks/useEventSubscriptions/useEventSubscriptions.ts +++ b/idea/frontend/src/hooks/useEventSubscriptions/useEventSubscriptions.ts @@ -6,16 +6,14 @@ import { Method } from 'features/explorer'; import { transferEventsHandler, messageSentEventsHandler } from './helpers'; const useEventSubscriptions = () => { - const alert = useAlert(); const { api, isApiReady } = useApi(); const { account } = useAccount(); + const alert = useAlert(); const { address, decodedAddress } = account || {}; useEffect(() => { - if (!isApiReady || !decodedAddress || !address) { - return; - } + if (!isApiReady || !decodedAddress || !address) return; const unsubs: UnsubscribePromise[] = []; diff --git a/idea/frontend/src/hooks/useGasCalculate/useGasCalculate.ts b/idea/frontend/src/hooks/useGasCalculate/useGasCalculate.ts index 366b4849a9..e08d828263 100644 --- a/idea/frontend/src/hooks/useGasCalculate/useGasCalculate.ts +++ b/idea/frontend/src/hooks/useGasCalculate/useGasCalculate.ts @@ -9,9 +9,9 @@ import { Values, Code, Result } from './types'; import { preparedGasInfo } from './helpers'; const useGasCalculate = () => { - const alert = useAlert(); - const { api } = useApi(); + const { api, isApiReady } = useApi(); const { account } = useAccount(); + const alert = useAlert(); const calculateGas = async ( method: T, @@ -23,6 +23,8 @@ const useGasCalculate = () => { const { value, payload } = values; try { + if (!isApiReady) throw new Error('API is not initialized'); + const isPayloadEmpty = isPlainObject(payload) && Object.keys(payload as object).length === 0; if (isPayloadEmpty) throw new Error(`Payload can't be empty`); diff --git a/idea/frontend/src/hooks/useGasMultiplier.ts b/idea/frontend/src/hooks/useGasMultiplier.ts index bf8e9b1a72..9ca7b68055 100644 --- a/idea/frontend/src/hooks/useGasMultiplier.ts +++ b/idea/frontend/src/hooks/useGasMultiplier.ts @@ -4,16 +4,18 @@ import { useMemo } from 'react'; import { useBalanceMultiplier } from './useBalanceMultiplier'; function useGasMultiplier() { - const { api } = useApi(); + const { api, isApiReady } = useApi(); // '1000' is an old runtime fallback const valuePerGas = useMemo(() => { try { + if (!isApiReady) throw new Error('API is not initialized'); + return api.valuePerGas.toString(); } catch { return '1000'; } - }, [api]); + }, [api, isApiReady]); const { balanceMultiplier } = useBalanceMultiplier(); const gasMultiplier = balanceMultiplier.dividedBy(valuePerGas); diff --git a/idea/frontend/src/hooks/useIsProgramExists.ts b/idea/frontend/src/hooks/useIsProgramExists.ts deleted file mode 100644 index 4f9240f19f..0000000000 --- a/idea/frontend/src/hooks/useIsProgramExists.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { useApi } from '@gear-js/react-hooks'; -import { HexString } from '@polkadot/util/types'; -import { useState } from 'react'; - -function useIsProgramExists(programId: HexString) { - const { api } = useApi(); - - const [isReady, setIsReady] = useState(true); - - const enableLoading = () => setIsReady(false); - const disableLoading = () => setIsReady(true); - - const isProgramExists = () => { - enableLoading(); - - return api.program.exists(programId).finally(disableLoading); - }; - - return { isProgramExists, isProgramExistenceReady: isReady }; -} - -export { useIsProgramExists }; diff --git a/idea/frontend/src/hooks/useMessageActions/useMessageActions.ts b/idea/frontend/src/hooks/useMessageActions/useMessageActions.ts index b89427ffa0..3c50543550 100644 --- a/idea/frontend/src/hooks/useMessageActions/useMessageActions.ts +++ b/idea/frontend/src/hooks/useMessageActions/useMessageActions.ts @@ -12,12 +12,14 @@ import { PROGRAM_ERRORS, TransactionStatus, TransactionName } from 'shared/confi import { ParamsToSendMessage, ParamsToSignAndSend, ParamsToReplyMessage } from './types'; const useMessageActions = () => { - const alert = useAlert(); - const { api } = useApi(); + const { api, isApiReady } = useApi(); const { account } = useAccount(); + const alert = useAlert(); const { showModal } = useModal(); const handleEventsStatus = (events: EventRecord[], { reject, resolve }: OperationCallbacks) => { + if (!isApiReady) throw new Error('API is not initialized'); + events.forEach(({ event }) => { const { method, section } = event; const alertOptions = { title: `${section}.${method}` }; @@ -38,6 +40,8 @@ const useMessageActions = () => { const alertId = alert.loading('SignIn', { title }); try { + if (!isApiReady) throw new Error('API is not initialized'); + await api.message.signAndSend(account!.address, { signer }, ({ events, status }) => { if (status.isReady) { alert.update(alertId, TransactionStatus.Ready); @@ -64,6 +68,7 @@ const useMessageActions = () => { const sendMessage = useCallback( async ({ metadata, message, payloadType, reject, resolve }: ParamsToSendMessage) => { try { + if (!isApiReady) throw new Error('API is not initialized'); checkWallet(account); const { meta, address } = account!; @@ -102,6 +107,7 @@ const useMessageActions = () => { const replyMessage = useCallback( async ({ reply, metadata, payloadType, reject, resolve }: ParamsToReplyMessage) => { try { + if (!isApiReady) throw new Error('API is not initialized'); checkWallet(account); const { meta, address } = account!; diff --git a/idea/frontend/src/hooks/useMessageClaim/useMessageClaim.ts b/idea/frontend/src/hooks/useMessageClaim/useMessageClaim.ts index 6819213406..5663f1f7db 100644 --- a/idea/frontend/src/hooks/useMessageClaim/useMessageClaim.ts +++ b/idea/frontend/src/hooks/useMessageClaim/useMessageClaim.ts @@ -13,12 +13,14 @@ import { checkWallet, getExtrinsicFailedMessage } from 'shared/helpers'; import { ParamsToClaimMessage } from './types'; const useMessageClaim = () => { - const alert = useAlert(); - const { api } = useApi(); + const { api, isApiReady } = useApi(); const { account } = useAccount(); + const alert = useAlert(); const { showModal } = useModal(); const handleEventsStatus = (events: EventRecord[], reject: OperationCallbacks['resolve']) => { + if (!isApiReady) throw new Error('API is not initialized'); + events.forEach(({ event }) => { const { method, section, data } = event as UserMessageRead; const alertOptions = { title: `${section}.${method}` }; @@ -43,6 +45,8 @@ const useMessageClaim = () => { const alertId = alert.loading('SignIn', { title: TransactionName.ClaimMessage }); try { + if (!isApiReady) throw new Error('API is not initialized'); + await api.claimValueFromMailbox.signAndSend(account!.address, { signer }, ({ status, events }) => { if (status.isReady) { alert.update(alertId, TransactionStatus.Ready); @@ -71,6 +75,7 @@ const useMessageClaim = () => { const claimMessage = useCallback( async ({ messageId, reject, resolve }: ParamsToClaimMessage) => { try { + if (!isApiReady) throw new Error('API is not initialized'); checkWallet(account); const { meta, address } = account!; diff --git a/idea/frontend/src/hooks/useMetaOnUpload.tsx b/idea/frontend/src/hooks/useMetaOnUpload.tsx index 1e23080084..14691505ac 100644 --- a/idea/frontend/src/hooks/useMetaOnUpload.tsx +++ b/idea/frontend/src/hooks/useMetaOnUpload.tsx @@ -34,7 +34,7 @@ const getCodeExistsAlert = (codeId: HexString) => ( ); const useMetaOnUpload = (isCode?: boolean) => { - const { api } = useApi(); + const { api, isApiReady } = useApi(); const { isDevChain } = useChain(); const alert = useAlert(); @@ -76,7 +76,7 @@ const useMetaOnUpload = (isCode?: boolean) => { }, [optFile]); useEffect(() => { - if (!isCode || !optBuffer) return; + if (!isApiReady || !isCode || !optBuffer) return; setIsCodeExists(undefined); @@ -92,12 +92,11 @@ const useMetaOnUpload = (isCode?: boolean) => { }); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [optBuffer]); + }, [isApiReady, optBuffer]); useEffect(() => { const isCodeCheckReady = isCodeExists !== undefined; - - if (!optBuffer || (isCode && !isCodeCheckReady) || isCodeExists) return; + if (!isApiReady || !optBuffer || (isCode && !isCodeCheckReady) || isCodeExists) return; setIsUploadedMetaReady(false); @@ -117,7 +116,7 @@ const useMetaOnUpload = (isCode?: boolean) => { .finally(() => setIsUploadedMetaReady(true)); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [optBuffer, isCodeExists]); + }, [isApiReady, optBuffer, isCodeExists]); return { optFile, diff --git a/idea/frontend/src/hooks/useMetadataUpload/useMetadataUpload.ts b/idea/frontend/src/hooks/useMetadataUpload/useMetadataUpload.ts index 0796f6cc0a..fe6449d486 100644 --- a/idea/frontend/src/hooks/useMetadataUpload/useMetadataUpload.ts +++ b/idea/frontend/src/hooks/useMetadataUpload/useMetadataUpload.ts @@ -9,9 +9,9 @@ import { useChain } from '../context'; import { ParamsToUploadMeta } from './types'; const useMetadataUpload = () => { - const { api } = useApi(); - const alert = useAlert(); + const { api, isApiReady } = useApi(); const { account } = useAccount(); + const alert = useAlert(); const { isDevChain } = useChain(); const upload = async (params: ParamsToUploadMeta) => { @@ -38,6 +38,7 @@ const useMetadataUpload = () => { const { metaHex, codeHash, programId, name, reject, resolve } = params; try { + if (!isApiReady) throw new Error('API is not initialized'); if (!account) throw new Error(ACCOUNT_ERRORS.WALLET_NOT_CONNECTED); if (isDevChain) { diff --git a/idea/frontend/src/hooks/useNodeVersion.tsx b/idea/frontend/src/hooks/useNodeVersion.tsx index e76e689c21..8fb73acb05 100644 --- a/idea/frontend/src/hooks/useNodeVersion.tsx +++ b/idea/frontend/src/hooks/useNodeVersion.tsx @@ -2,19 +2,25 @@ import { useApi } from '@gear-js/react-hooks'; import { useEffect, useState } from 'react'; function useNodeVersion() { - const { api } = useApi(); + const { api, isApiReady } = useApi(); const [nodeVersion, setNodeVersion] = useState(''); const [commitHash, setCommitHash] = useState(''); useEffect(() => { - api?.nodeVersion().then((result) => { + setNodeVersion(''); + setCommitHash(''); + + if (!isApiReady) return; + + api.nodeVersion().then((result) => { const [, commitHashResult] = result.split('-'); setNodeVersion(result); setCommitHash(commitHashResult); }); - }, [api]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isApiReady]); return { nodeVersion, commitHash }; } diff --git a/idea/frontend/src/hooks/useProgramActions/useProgramActions.tsx b/idea/frontend/src/hooks/useProgramActions/useProgramActions.tsx index be22d429d3..fe84223d17 100644 --- a/idea/frontend/src/hooks/useProgramActions/useProgramActions.tsx +++ b/idea/frontend/src/hooks/useProgramActions/useProgramActions.tsx @@ -29,7 +29,7 @@ import { Payload, ParamsToCreate, ParamsToUpload, ParamsToSignAndUpload } from ' const useProgramActions = () => { const alert = useAlert(); - const { api } = useApi(); + const { api, isApiReady } = useApi(); const { account } = useAccount(); const { isDevChain } = useChain(); @@ -44,6 +44,8 @@ const useProgramActions = () => { ); const createProgram = (codeId: HexString, payload: Payload) => { + if (!isApiReady) throw new Error('API is not initialized'); + const { gasLimit, value, initPayload, metadata, payloadType } = payload; const program = { value, codeId, gasLimit, initPayload }; @@ -54,6 +56,8 @@ const useProgramActions = () => { }; const uploadProgram = async (optBuffer: Buffer, payload: Payload) => { + if (!isApiReady) throw new Error('API is not initialized'); + const { gasLimit, value, initPayload, metadata, payloadType } = payload; const program = { code: optBuffer, value, gasLimit, initPayload }; @@ -64,6 +68,8 @@ const useProgramActions = () => { }; const handleEventsStatus = (events: EventRecord[], { reject }: OperationCallbacks) => { + if (!isApiReady) throw new Error('API is not initialized'); + events.forEach(({ event }) => { const { method, section } = event; const alertOptions = { title: `${section}.${method}` }; @@ -91,6 +97,8 @@ const useProgramActions = () => { const programMessage = getProgramMessage(programId); try { + if (!isApiReady) throw new Error('API is not initialized'); + await api.program.signAndSend(account!.address, { signer }, ({ status, events }) => { if (status.isReady) { alert.update(alertId, TransactionStatus.Ready); @@ -157,6 +165,7 @@ const useProgramActions = () => { const create = useCallback( async ({ codeId, payload, reject, resolve }: ParamsToCreate) => { try { + if (!isApiReady) throw new Error('API is not initialized'); checkWallet(account); const { meta, address } = account!; @@ -201,6 +210,7 @@ const useProgramActions = () => { const upload = useCallback( async ({ optBuffer, payload, name, reject, resolve }: ParamsToUpload) => { try { + if (!isApiReady) throw new Error('API is not initialized'); checkWallet(account); const { meta, address } = account!; diff --git a/idea/frontend/src/hooks/useStateRead.ts b/idea/frontend/src/hooks/useStateRead.ts index 6ffbdfcf8d..cd384c5c68 100644 --- a/idea/frontend/src/hooks/useStateRead.ts +++ b/idea/frontend/src/hooks/useStateRead.ts @@ -5,8 +5,8 @@ import { useApi, useAlert } from '@gear-js/react-hooks'; import { HexString } from '@polkadot/util/types'; const useStateRead = (programId: HexString) => { + const { api, isApiReady } = useApi(); const alert = useAlert(); - const { api } = useApi(); const [state, setState] = useState(); const [isStateRead, setIsStateRead] = useState(true); @@ -20,8 +20,11 @@ const useStateRead = (programId: HexString) => { .finally(() => setIsStateRead(true)); }; - const readFullState = (metadata: ProgramMetadata, payload: AnyJson) => + const readFullState = (metadata: ProgramMetadata, payload: AnyJson) => { + if (!isApiReady) return; + handleStateRead(() => api.programState.read({ programId, payload }, metadata)); + }; const readWasmState = ( wasm: Buffer, @@ -29,12 +32,15 @@ const useStateRead = (programId: HexString) => { fn_name: string, argument: AnyJson, payload: AnyJson, - ) => + ) => { + if (!isApiReady) return; + handleStateRead(() => getStateMetadata(wasm).then((stateMetadata) => api.programState.readUsingWasm({ programId, wasm, fn_name, argument, payload }, stateMetadata, programMetadata), ), ); + }; const resetState = () => setState(undefined); const isState = state !== undefined; // could be null diff --git a/idea/frontend/src/hooks/useWaitlist.ts b/idea/frontend/src/hooks/useWaitlist.ts index fb01dd4456..3c87f245c6 100644 --- a/idea/frontend/src/hooks/useWaitlist.ts +++ b/idea/frontend/src/hooks/useWaitlist.ts @@ -4,18 +4,21 @@ import { useApi, useAlert } from '@gear-js/react-hooks'; import { HexString } from '@polkadot/util/types'; const useWaitlist = () => { + const { api, isApiReady } = useApi(); const alert = useAlert(); - const { api } = useApi(); const [waitlist, setWaitlist] = useState([]); const [isLoading, setIsLoading] = useState(true); - const fetchWaitlist = (programId: HexString) => - api.waitlist + const fetchWaitlist = (programId: HexString) => { + if (!isApiReady) return Promise.reject(new Error('API is not initialized')); + + return api.waitlist .read(programId) - .then(setWaitlist) + .then((result) => setWaitlist(result)) .catch(({ message }: Error) => alert.error(message)) .finally(() => setIsLoading(false)); + }; return { waitlist, isLoading, fetchWaitlist }; }; diff --git a/idea/frontend/src/pages/initializeProgram/ui/InitializeProgram.tsx b/idea/frontend/src/pages/initializeProgram/ui/InitializeProgram.tsx index cdff29408b..c9ffbbb6f9 100644 --- a/idea/frontend/src/pages/initializeProgram/ui/InitializeProgram.tsx +++ b/idea/frontend/src/pages/initializeProgram/ui/InitializeProgram.tsx @@ -22,7 +22,7 @@ import styles from './InitializeProgram.module.scss'; const InitializeProgram = () => { const { codeId } = useParams() as PageParams; - const { api } = useApi(); + const { api, isApiReady } = useApi(); const alert = useAlert(); const { isDevChain } = useChain(); @@ -68,6 +68,8 @@ const InitializeProgram = () => { ); useEffect(() => { + if (!isApiReady) return; + const codeHash = codeId; const getMetadata = () => @@ -84,7 +86,7 @@ const InitializeProgram = () => { .finally(() => setIsUploadedMetaReady(true)); // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [isApiReady]); return (
    diff --git a/idea/frontend/src/shared/ui/form/balance-unit/BalanceUnit.tsx b/idea/frontend/src/shared/ui/form/balance-unit/BalanceUnit.tsx index c38a6cc3b5..ce4cdfe9c3 100644 --- a/idea/frontend/src/shared/ui/form/balance-unit/BalanceUnit.tsx +++ b/idea/frontend/src/shared/ui/form/balance-unit/BalanceUnit.tsx @@ -2,8 +2,8 @@ import { useApi } from '@gear-js/react-hooks'; import styles from './BalanceUnit.module.scss'; const BalanceUnit = () => { - const { api } = useApi(); - const [unit] = api.registry.chainTokens; + const { api, isApiReady } = useApi(); + const [unit] = isApiReady ? api.registry.chainTokens : ['Unit']; return {unit}; }; diff --git a/idea/frontend/src/widgets/footer/ui/Footer.module.scss b/idea/frontend/src/widgets/footer/ui/Footer.module.scss index b3a5bf9890..d435fca923 100644 --- a/idea/frontend/src/widgets/footer/ui/Footer.module.scss +++ b/idea/frontend/src/widgets/footer/ui/Footer.module.scss @@ -1,7 +1,9 @@ @use '@gear-js/ui/breakpoints' as *; -@use 'shared/assets/styles/_mixins.scss' as *; -@use 'shared/assets/styles/_shared.scss' as *; -@use 'shared/assets/styles/_variables.scss' as *; +@use '@gear-js/ui/mixins' as *; +@use '@gear-js/ui/headings' as *; +@use 'shared/assets/styles/mixins' as *; +@use 'shared/assets/styles/shared' as *; +@use 'shared/assets/styles/variables' as *; .footer { display: flex; @@ -24,3 +26,23 @@ margin-left: auto; margin-right: 32px; } + +.dotButton { + @include childrenMargin(10px, right); + @include transition; + @extend %headingFont; + color: $gray800; + font-size: 16px; + font-weight: 500; + display: flex; + align-items: center; + + &:hover { + opacity: 0.5; + } + + &.disabled { + pointer-events: none; + opacity: 0.5; + } +} diff --git a/idea/frontend/src/widgets/footer/ui/Footer.tsx b/idea/frontend/src/widgets/footer/ui/Footer.tsx index 17248ce830..f7746e2c63 100644 --- a/idea/frontend/src/widgets/footer/ui/Footer.tsx +++ b/idea/frontend/src/widgets/footer/ui/Footer.tsx @@ -1,20 +1,31 @@ import { useMemo } from 'react'; -import { useApp } from 'hooks'; +import cx from 'clsx'; +import { useApi } from '@gear-js/react-hooks'; + +import { ReactComponent as DotSVG } from 'shared/assets/images/logos/dotLogo.svg'; -import { DotButton } from './dotButton/DotButton'; import { Socials } from './socials/Socials'; import styles from './Footer.module.scss'; const Footer = () => { - const { nodeAddress } = useApp(); + const { api, isApiReady } = useApi(); const year = useMemo(() => new Date().getFullYear(), []); return ( ); }; diff --git a/idea/frontend/src/widgets/footer/ui/dotButton/DotButton.module.scss b/idea/frontend/src/widgets/footer/ui/dotButton/DotButton.module.scss deleted file mode 100644 index a4ed4dfa7e..0000000000 --- a/idea/frontend/src/widgets/footer/ui/dotButton/DotButton.module.scss +++ /dev/null @@ -1,21 +0,0 @@ -@use 'shared/assets/styles/variables' as *; -@use '@gear-js/ui/mixins' as *; -@use '@gear-js/ui/headings' as *; - -.link { - @include transition; - @extend %headingFont; - color: $gray800; - font-size: 16px; - font-weight: 500; - display: flex; - align-items: center; - - &:hover { - opacity: 0.5; - } - - .icon { - margin-right: 10px; - } -} diff --git a/idea/frontend/src/widgets/footer/ui/dotButton/DotButton.tsx b/idea/frontend/src/widgets/footer/ui/dotButton/DotButton.tsx deleted file mode 100644 index fa0944bb2e..0000000000 --- a/idea/frontend/src/widgets/footer/ui/dotButton/DotButton.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { ReactComponent as DotSVG } from 'shared/assets/images/logos/dotLogo.svg'; - -import styles from './DotButton.module.scss'; - -type Props = { - nodeAddress: string; -}; - -const DotButton = ({ nodeAddress }: Props) => ( - - - Polkadot Explorer - -); - -export { DotButton }; diff --git a/idea/frontend/src/widgets/footer/ui/dotButton/index.ts b/idea/frontend/src/widgets/footer/ui/dotButton/index.ts deleted file mode 100644 index 64edce3f8c..0000000000 --- a/idea/frontend/src/widgets/footer/ui/dotButton/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { DotButton } from './DotButton'; - -export { DotButton }; diff --git a/idea/frontend/src/widgets/header/ui/Header.tsx b/idea/frontend/src/widgets/header/ui/Header.tsx index a3e6599240..c4e237c40c 100644 --- a/idea/frontend/src/widgets/header/ui/Header.tsx +++ b/idea/frontend/src/widgets/header/ui/Header.tsx @@ -1,7 +1,7 @@ import { useMemo, useEffect } from 'react'; import { CSSTransition } from 'react-transition-group'; import { useLocation, matchPath } from 'react-router-dom'; -import { useAccount } from '@gear-js/react-hooks'; +import { useAccount, useApi } from '@gear-js/react-hooks'; import { AnimationTimeout } from 'shared/config'; @@ -11,6 +11,7 @@ import { TopSide } from './topSide'; import { BottomSide } from './bottomSide'; const Header = () => { + const { isApiReady } = useApi(); const { account } = useAccount(); const { pathname } = useLocation(); @@ -19,7 +20,7 @@ const Header = () => { [pathname], ); - const isBottomSideVisible = withBottomSide && Boolean(account); + const isBottomSideVisible = withBottomSide && Boolean(isApiReady) && Boolean(account); useEffect(() => { const currentHeight = isBottomSideVisible ? FULL_HEADER_HEIGHT : SHORT_HEADER_HEIGHT; diff --git a/idea/frontend/src/widgets/header/ui/topSide/TopSide.tsx b/idea/frontend/src/widgets/header/ui/topSide/TopSide.tsx index 17ecdd4696..f305bb9d59 100644 --- a/idea/frontend/src/widgets/header/ui/topSide/TopSide.tsx +++ b/idea/frontend/src/widgets/header/ui/topSide/TopSide.tsx @@ -64,11 +64,12 @@ const TopSide = () => { useEffect(handleExpire, [address]); useEffect(() => { - if (isApiReady) { - api.totalIssuance().then((result) => setTotalIssuance(result.slice(0, 5))); - } + if (!isApiReady) return; + + api.totalIssuance().then((result) => setTotalIssuance(result.slice(0, 5))); + // eslint-disable-next-line react-hooks/exhaustive-deps - }, [api, isApiReady]); + }, [isApiReady]); useEffect(() => { if (captchaToken) getBalanceFromService(); @@ -102,7 +103,7 @@ const TopSide = () => { )}
    - {account && ( + {isApiReady && account && (
    {isTestBalanceAvailable && ( @@ -127,7 +128,7 @@ const TopSide = () => { )} - {isAccountReady && ( + {isApiReady && isAccountReady && ( diff --git a/idea/frontend/src/widgets/messageForm/ui/MessageForm.tsx b/idea/frontend/src/widgets/messageForm/ui/MessageForm.tsx index 0b4bcd462c..ab5f895cc8 100644 --- a/idea/frontend/src/widgets/messageForm/ui/MessageForm.tsx +++ b/idea/frontend/src/widgets/messageForm/ui/MessageForm.tsx @@ -33,7 +33,7 @@ type Props = { }; const MessageForm = ({ id, programId, isReply, metadata, isLoading }: Props) => { - const { api } = useApi(); + const { api, isApiReady } = useApi(); const { account } = useAccount(); const calculateGas = useGasCalculate(); @@ -47,8 +47,8 @@ const MessageForm = ({ id, programId, isReply, metadata, isLoading }: Props) => const formApi = useRef>(); - const deposit = api.existentialDeposit.toString(); - const maxGasLimit = api.blockGasLimit.toString(); + const deposit = isApiReady ? api.existentialDeposit.toString() : ''; + const maxGasLimit = isApiReady ? api.blockGasLimit.toString() : ''; const method = isReply ? GasMethod.Reply : GasMethod.Handle; const typeIndex = isReply ? metadata?.types.reply : metadata?.types.handle.input; diff --git a/idea/frontend/src/widgets/programForm/ui/ProgramForm.tsx b/idea/frontend/src/widgets/programForm/ui/ProgramForm.tsx index cd4b7b2aeb..ccc39aa2c2 100644 --- a/idea/frontend/src/widgets/programForm/ui/ProgramForm.tsx +++ b/idea/frontend/src/widgets/programForm/ui/ProgramForm.tsx @@ -32,7 +32,7 @@ type Props = { const ProgramForm = (props: Props) => { const { gasMethod, metaHex, metadata, source, renderButtons, onSubmit } = props; - const { api } = useApi(); + const { api, isApiReady } = useApi(); const formApi = useRef>(); @@ -87,8 +87,8 @@ const ProgramForm = (props: Props) => { onSubmit(data, { enableButtons: () => setIsDisables(false), resetForm: formApi.current.reset }); }; - const deposit = api.existentialDeposit.toNumber(); - const maxGasLimit = api.blockGasLimit.toNumber(); + const deposit = isApiReady ? api.existentialDeposit.toNumber() : 0; + const maxGasLimit = isApiReady ? api.blockGasLimit.toNumber() : 0; const typeIndex = metadata?.types.init.input; const isTypeIndex = typeIndex !== undefined && typeIndex !== null; diff --git a/yarn.lock b/yarn.lock index 0f93710973..77bb7b347c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2296,7 +2296,7 @@ __metadata: resolution: "@gear-js/frontend@workspace:idea/frontend" dependencies: "@gear-js/api": 0.33.6 - "@gear-js/react-hooks": 0.6.10 + "@gear-js/react-hooks": 0.8.0 "@gear-js/ui": 0.5.19 "@hcaptcha/react-hcaptcha": ^1.4.4 "@mantine/form": ^5.4.0 @@ -2400,16 +2400,16 @@ __metadata: languageName: unknown linkType: soft -"@gear-js/react-hooks@npm:0.6.10": - version: 0.6.10 - resolution: "@gear-js/react-hooks@npm:0.6.10" +"@gear-js/react-hooks@npm:0.8.0": + version: 0.8.0 + resolution: "@gear-js/react-hooks@npm:0.8.0" peerDependencies: "@gear-js/api": 0.33.6 "@polkadot/api": 10.9.1 "@polkadot/extension-dapp": 0.46.5 react: ^18.2.0 react-transition-group: 4.4.5 - checksum: 2faa613074aa01941ef44a464023c42691c479ce88fce887b82859c59346045c9205147940bf32e4b3eca04dcdde56f9c2e92a463759230f4c04c018398f3426 + checksum: da785a1221a240585ef7774ad3660ae422f01d0c57d8bc60191dcc8e6975fc0eaedadaac80faae0959cd333e9dcaed45ad0d2e003622eceb4cd1831a4dc8e49a languageName: node linkType: hard