diff --git a/packages/website/src/features/Packages/Abi.tsx b/packages/website/src/features/Packages/Abi.tsx index c603f1f6c..5e82f5eeb 100644 --- a/packages/website/src/features/Packages/Abi.tsx +++ b/packages/website/src/features/Packages/Abi.tsx @@ -85,6 +85,7 @@ export const Abi: FC<{ const hasSubnav = useContext(SubnavContext); const containerRef = useRef(null); const [selectedSelector, setSelectedSelector] = useState(null); + const [scrollInitialized, setScrollInitialized] = useState(false); const allContractMethods = useMemo( () => @@ -116,17 +117,21 @@ export const Abi: FC<{ [allContractMethods] ); + const scrollOptions = useMemo( + () => ({ + duration: 1200, + smooth: true, + offset: (102 + (hasSubnav ? 65 : 0)) * -1, + }), + [hasSubnav] + ); + const onSelectedSelector = async (newSelector: string) => { // set the selector setSelectedSelector(newSelector); // scroll to the element - const adjust = 102 + (hasSubnav ? 65 : 0); - scroller.scrollTo(newSelector, { - duration: 1200, - smooth: true, - offset: adjust * -1, - }); + scroller.scrollTo(newSelector, scrollOptions); await router.push(`${router.asPath.split('#')[0]}#${newSelector}`); }; @@ -145,13 +150,29 @@ export const Abi: FC<{ scrollSpy.update(); }, []); - // Initialize the selector from the URL + // Make the auto scroll after the page loads if the url has a selector useEffect(() => { + if (scrollInitialized) return; + const urlSelectorFromPath = router.asPath.split('#')[1]; if (urlSelectorFromPath || !selectedSelector) { setSelectedSelector(urlSelectorFromPath); + + // Add a timeout to delay the scroll + const timeoutId = setTimeout(() => { + scroller.scrollTo(urlSelectorFromPath, scrollOptions); + setScrollInitialized(true); + }, 100); + + return () => clearTimeout(timeoutId); } - }, [router.asPath, selectedSelector]); + }, [ + hasSubnav, + router.asPath, + scrollOptions, + selectedSelector, + scrollInitialized, + ]); return ( @@ -195,7 +216,9 @@ export const Abi: FC<{ ) : ( readContractMethods - ?.filter((f) => f.name.includes(searchTerm)) + ?.filter((f) => + f.name.toLowerCase().includes(searchTerm.toLowerCase()) + ) .map((f, index) => ( ) : ( writeContractMethods - ?.filter((f) => f.name.includes(searchTerm)) + ?.filter((f) => + f.name.toLowerCase().includes(searchTerm.toLowerCase()) + ) .map((f, index) => ( {isLoading ? ( diff --git a/packages/website/src/features/Packages/Function.tsx b/packages/website/src/features/Packages/Function.tsx index 9203de93e..c2c9a73c3 100644 --- a/packages/website/src/features/Packages/Function.tsx +++ b/packages/website/src/features/Packages/Function.tsx @@ -31,7 +31,7 @@ import { useConnectModal } from '@rainbow-me/rainbowkit'; import { ChainArtifacts } from '@usecannon/builder'; import { Abi, AbiFunction } from 'abitype'; import { useRouter } from 'next/router'; -import React, { FC, useEffect, useMemo, useRef, useState } from 'react'; +import React, { FC, useEffect, useRef, useState } from 'react'; import { FaCode } from 'react-icons/fa6'; import { Address, @@ -45,6 +45,29 @@ import { } from 'viem'; import { useAccount, useSwitchChain, useWalletClient } from 'wagmi'; +const extractError = (e: any): string => { + return typeof e === 'string' + ? e + : e?.message || e?.error?.message || e?.error || e; +}; + +const _isReadOnly = (abiFunction: AbiFunction) => + abiFunction.stateMutability === 'view' || + abiFunction.stateMutability === 'pure'; + +const _isPayable = (abiFunction: AbiFunction) => + abiFunction.stateMutability === 'payable'; + +const StatusIcon = ({ error }: { error: boolean }) => ( + + {error ? ( + + ) : ( + + )} + +); + export const Function: FC<{ selected?: boolean; f: AbiFunction; @@ -74,7 +97,10 @@ export const Function: FC<{ const { asPath: pathname } = useRouter(); const [loading, setLoading] = useState(false); const [simulated, setSimulated] = useState(false); - const [error, setError] = useState(null); + const [methodCallOrQueuedResult, setMethodCallOrQueuedResult] = useState<{ + value: unknown; + error: string | null; + } | null>(null); const [hasExpandedSelected, setHasExpandedSelected] = useState(false); // TODO: don't know why, had to use a ref instead of an array to be able to @@ -125,7 +151,7 @@ export const Function: FC<{ chainId: chainId as number, })!; - const [readContractResult, fetchReadContractResult] = useContractCall( + const fetchReadContractResult = useContractCall( address, f.name, [...params], @@ -133,83 +159,71 @@ export const Function: FC<{ publicClient ); - const [writeContractResult, fetchWriteContractResult] = - useContractTransaction( - from as Address, - address as Address, - f.name, - [...params], - abi, - publicClient as any, // TODO: fix type - walletClient as any - ); - - const readOnly = useMemo( - () => f.stateMutability == 'view' || f.stateMutability == 'pure', - [f.stateMutability] - ); - - const isPayable = useMemo( - () => f.stateMutability == 'payable', - [f.stateMutability] + const fetchWriteContractResult = useContractTransaction( + from as Address, + address as Address, + f.name, + [...params], + abi, + publicClient, + walletClient as any ); - const result = useMemo( - () => - readOnly - ? readContractResult - : simulated - ? readContractResult - : writeContractResult, - [readOnly, simulated, readContractResult, writeContractResult] - ); + const isFunctionReadOnly = _isReadOnly(f); + const isFunctionPayable = _isPayable(f); - const submit = async (suppressError = false, simulate = false) => { + const submit = async ({ simulate = false }: { simulate?: boolean } = {}) => { setLoading(true); - setError(null); + setMethodCallOrQueuedResult(null); setSimulated(simulate); try { - if (readOnly) { - await fetchReadContractResult(from ?? zeroAddress); + if (isFunctionReadOnly) { + await handleReadFunction(); } else { - if (!isConnected) { - if (openConnectModal) openConnectModal(); - return; - } - - if (connectedChain?.id != chainId) { - await switchChain({ chainId: chainId }); - } - - if (simulate) { - await fetchReadContractResult(from); - } else { - await fetchWriteContractResult(); - } - } - } catch (e: any) { - if (!suppressError) { - setError( - typeof e === 'string' - ? e - : e?.message || e?.error?.message || e?.error || e - ); + await handleWriteFunction(simulate); } } finally { setLoading(false); } }; - const statusIcon = result ? ( - - {error ? ( - - ) : ( - - )} - - ) : null; + const handleReadFunction = async () => { + const result = await fetchReadContractResult(from ?? zeroAddress); + if (result.error) { + setMethodCallOrQueuedResult({ + value: null, + error: extractError(result.error), + }); + } else { + setMethodCallOrQueuedResult({ value: result.value, error: null }); + } + }; + + const handleWriteFunction = async (simulate: boolean) => { + if (!isConnected) { + if (openConnectModal) openConnectModal(); + return; + } + + if (connectedChain?.id !== chainId) { + await switchChain({ chainId: chainId }); + } + + if (simulate) { + await handleReadFunction(); + } else { + const result = await fetchWriteContractResult(); + if (result.error) { + setMethodCallOrQueuedResult({ + value: null, + error: extractError(result.error), + }); + } else { + setMethodCallOrQueuedResult({ value: result.value, error: null }); + } + } + }; const anchor = `#selector-${toFunctionSelector(f)}`; @@ -255,7 +269,7 @@ export const Function: FC<{ to: address, data: toFunctionSelector(f), value: - isPayable && value !== undefined + isFunctionPayable && value !== undefined ? parseEther(value.toString()) : undefined, }; @@ -268,12 +282,15 @@ export const Function: FC<{ args: params, }), value: - isPayable && value !== undefined + isFunctionPayable && value !== undefined ? parseEther(value.toString()) : undefined, }; - } catch (err: any) { - setError(err.message); + } catch (err: unknown) { + setMethodCallOrQueuedResult({ + value: null, + error: extractError(err), + }); return; } } @@ -297,6 +314,7 @@ export const Function: FC<{ ], safeId: `${currentSafe.chainId}:${currentSafe.address}`, }); + setLastQueuedTxnsId({ lastQueuedTxnsId: lastQueuedTxnsId + 1, safeId: `${currentSafe.chainId}:${currentSafe.address}`, @@ -387,7 +405,7 @@ export const Function: FC<{ ); })} - {isPayable && ( + {isFunctionPayable && ( Value @@ -431,7 +449,7 @@ export const Function: FC<{ )} - {readOnly && ( + {isFunctionReadOnly && ( )} - {!readOnly && ( + {!isFunctionReadOnly && ( <> - {simulated && statusIcon}