Commit 03c9e582 authored by tom's avatar tom

contract method form

parent 3a9f3d18
......@@ -22,6 +22,13 @@ const globalCss: SystemConfig['globalCss'] = {
form: {
w: '100%',
},
input: {
// hide number input arrows in Google Chrome
'&::-webkit-outer-spin-button, &::-webkit-inner-spin-button': {
WebkitAppearance: 'none',
margin: 0,
},
},
...recaptcha,
...scrollbar,
...addressEntity,
......
......@@ -12,7 +12,7 @@ export const recipe = defineSlotRecipe({
display: 'flex',
width: '100%',
position: 'relative',
gap: '1.5',
gap: '1',
},
label: {
display: 'flex',
......@@ -36,11 +36,11 @@ export const recipe = defineSlotRecipe({
fontWeight: 'medium',
gap: '1',
color: 'input.fg.error',
textStyle: 'xs',
textStyle: 'sm',
},
helperText: {
color: 'fg.muted',
textStyle: 'xs',
textStyle: 'sm',
},
},
......
......@@ -6,7 +6,7 @@ export const recipe = defineRecipe({
minWidth: '0',
outline: '0',
position: 'relative',
appearance: 'none',
appearance: 'textfield',
textAlign: 'start',
borderRadius: 'base',
height: 'var(--input-height)',
......@@ -27,7 +27,7 @@ export const recipe = defineRecipe({
variants: {
size: {
sm: {
textStyle: 'sm',
textStyle: 'md',
px: '2',
'--input-height': 'sizes.8',
},
......
import { Accordion, Box, Flex, Link } from '@chakra-ui/react';
import { Box, Flex } from '@chakra-ui/react';
import { range } from 'es-toolkit';
import React from 'react';
......@@ -7,7 +7,8 @@ import type { SmartContractMethod } from './types';
import { route } from 'nextjs-routes';
import { apos } from 'lib/html-entities';
import LinkInternal from 'ui/shared/links/LinkInternal';
import { AccordionRoot } from 'toolkit/chakra/accordion';
import { Link } from 'toolkit/chakra/link';
import ContractAbiItem from './ContractAbiItem';
import useFormSubmit from './useFormSubmit';
......@@ -22,15 +23,15 @@ interface Props {
}
const ContractAbi = ({ abi, addressHash, sourceAddress, tab, visibleItems }: Props) => {
const [ expandedSections, setExpandedSections ] = React.useState<Array<number>>(abi.length === 1 ? [ 0 ] : []);
const [ expandedSections, setExpandedSections ] = React.useState<Array<string>>(abi.length === 1 ? [ '0' ] : []);
const [ id, setId ] = React.useState(0);
useScrollToMethod(abi, setExpandedSections);
const handleFormSubmit = useFormSubmit({ addressHash });
const handleAccordionStateChange = React.useCallback((newValue: Array<number>) => {
setExpandedSections(newValue);
const handleAccordionStateChange = React.useCallback(({ value }: { value: Array<string> }) => {
setExpandedSections(value);
}, []);
const handleExpandAll = React.useCallback(() => {
......@@ -39,7 +40,7 @@ const ContractAbi = ({ abi, addressHash, sourceAddress, tab, visibleItems }: Pro
}
if (expandedSections.length < abi.length) {
setExpandedSections(range(0, abi.length));
setExpandedSections(range(0, abi.length).map(String));
} else {
setExpandedSections([]);
}
......@@ -62,13 +63,14 @@ const ContractAbi = ({ abi, addressHash, sourceAddress, tab, visibleItems }: Pro
) }
<Link onClick={ handleReset } ml={ 3 }>Reset</Link>
</Flex>
<Accordion allowMultiple position="relative" onChange={ handleAccordionStateChange } index={ expandedSections }>
<AccordionRoot multiple lazyMount position="relative" onValueChange={ handleAccordionStateChange } value={ expandedSections }>
{ abi.map((item, index) => (
<ContractAbiItem
key={ index }
id={ id }
index={ index }
data={ item }
isOpen={ expandedSections.includes(String(index)) }
isVisible={ !visibleItems || visibleItems.includes(index) }
addressHash={ addressHash }
sourceAddress={ sourceAddress }
......@@ -76,18 +78,18 @@ const ContractAbi = ({ abi, addressHash, sourceAddress, tab, visibleItems }: Pro
onSubmit={ handleFormSubmit }
/>
)) }
</Accordion>
</AccordionRoot>
{ !hasVisibleItems && (
<div>
<div>Couldn{ apos }t find any method that matches your query.</div>
<div>
You can use custom ABI for this contract without verifying the contract in the{ ' ' }
<LinkInternal
<Link
href={ route({ pathname: '/address/[hash]', query: { hash: addressHash, tab: 'read_write_custom_methods' } }) }
scroll={ false }
>
Custom ABI
</LinkInternal>
</Link>
{ ' ' }tab.
</div>
</div>
......
import { AccordionButton, AccordionIcon, AccordionItem, AccordionPanel, Alert, Box, Tag } from '@chakra-ui/react';
import { Box } from '@chakra-ui/react';
import React from 'react';
import { Element } from 'react-scroll';
......@@ -7,6 +7,9 @@ import type { FormSubmitHandler, SmartContractMethod } from './types';
import { route } from 'nextjs-routes';
import config from 'configs/app';
import { AccordionItem, AccordionItemContent, AccordionItemTrigger } from 'toolkit/chakra/accordion';
import { Alert } from 'toolkit/chakra/alert';
import { Badge } from 'toolkit/chakra/badge';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import Hint from 'ui/shared/Hint';
......@@ -23,9 +26,10 @@ interface Props {
tab: string;
onSubmit: FormSubmitHandler;
isVisible?: boolean;
isOpen: boolean;
}
const ContractAbiItem = ({ data, index, id, addressHash, sourceAddress, tab, onSubmit, isVisible = true }: Props) => {
const ContractAbiItem = ({ data, index, id, addressHash, sourceAddress, tab, onSubmit, isVisible = true, isOpen }: Props) => {
const [ attempt, setAttempt ] = React.useState(0);
const url = React.useMemo(() => {
......@@ -47,74 +51,76 @@ const ContractAbiItem = ({ data, index, id, addressHash, sourceAddress, tab, onS
const isRead = isReadMethod(data);
return (
<AccordionItem as="section" _first={{ borderTopWidth: 0 }} _last={{ borderBottomWidth: 0 }} display={ isVisible ? 'block' : 'none' }>
{ ({ isExpanded }) => (
<>
<Element as="h2" name={ getElementName(data) }>
<AccordionButton
px={ 0 }
py={ 3 }
_hover={{ bgColor: 'inherit' }}
wordBreak="break-all"
textAlign="left"
as="div"
cursor="pointer"
display="flex"
alignItems="center"
columnGap={ 2 }
>
<CopyToClipboard text={ url } type="link" ml={ 0 } color="text_secondary"/>
<Box as="div" fontWeight={ 500 } display="flex" alignItems="center">
{ index + 1 }. { data.type === 'fallback' || data.type === 'receive' ? data.type : data.name }
{ data.type === 'fallback' && (
<Hint
label={
`The fallback function is executed on a call to the contract if none of the other functions match
the given function signature, or if no data was supplied at all and there is no receive Ether function.
<AccordionItem
as="section"
value={ String(index) }
_first={{ borderTopWidth: 0 }}
_last={{ borderBottomWidth: 0 }}
display={ isVisible ? 'block' : 'none' }
>
<Element as="h2" name={ getElementName(data) }>
<AccordionItemTrigger
px={ 0 }
py={ 3 }
_hover={{ bgColor: 'inherit' }}
wordBreak="break-all"
textAlign="left"
cursor="pointer"
display="flex"
alignItems="center"
columnGap={ 2 }
>
<CopyToClipboard text={ url } type="link" ml={ 0 } color="text_secondary" as="div"/>
<Box fontWeight={ 500 } display="flex" alignItems="center">
{ index + 1 }. { data.type === 'fallback' || data.type === 'receive' ? data.type : data.name }
{ data.type === 'fallback' && (
<Hint
label={
`The fallback function is executed on a call to the contract if none of the other functions match
the given function signature, or if no data was supplied at all and there is no receive Ether function.
The fallback function always receives data, but in order to also receive Ether it must be marked payable.`
}
ml={ 1 }
/>
) }
{ data.type === 'receive' && (
<Hint
label={
`The receive function is executed on a call to the contract with empty calldata.
This is the function that is executed on plain Ether transfers (e.g. via .send() or .transfer()).
If no such function exists, but a payable fallback function exists, the fallback function will be called on a plain Ether transfer.
If neither a receive Ether nor a payable fallback function is present,
}
ml={ 1 }
as="div"
/>
) }
{ data.type === 'receive' && (
<Hint
label={
`The receive function is executed on a call to the contract with empty calldata.
This is the function that is executed on plain Ether transfers (e.g. via .send() or .transfer()).
If no such function exists, but a payable fallback function exists, the fallback function will be called on a plain Ether transfer.
If neither a receive Ether nor a payable fallback function is present,
the contract cannot receive Ether through regular transactions and throws an exception.`
}
ml={ 1 }
/>
) }
</Box>
<Tag colorScheme={ isRead ? 'black-purple' : 'black-blue' } flexShrink={ 0 }>{ isRead ? 'read' : 'write' }</Tag>
{ 'method_id' in data && (
<Tag display="inline-flex" alignItems="center" flexShrink={ 0 }>
{ data.method_id }
<CopyToClipboard text={ data.method_id }/>
</Tag>
) }
<AccordionIcon transform={ isExpanded ? 'rotate(0deg)' : 'rotate(-90deg)' } color="gray.500"/>
</AccordionButton>
</Element>
<AccordionPanel pb={ 4 } pr={ 0 } pl="28px" w="calc(100% - 6px)">
{ 'is_invalid' in data && data.is_invalid ? (
<Alert status="warning">An error occurred while parsing the method signature.</Alert>
) : (
<ContractMethodForm
key={ id + '_' + index + '_' + attempt }
data={ data }
attempt={ attempt }
onSubmit={ onSubmit }
onReset={ handleReset }
isOpen={ isExpanded }
}
ml={ 1 }
as="div"
/>
) }
</AccordionPanel>
</>
) }
</Box>
<Badge colorPalette={ isRead ? 'purple_alt' : 'blue_alt' } flexShrink={ 0 }>{ isRead ? 'read' : 'write' }</Badge>
{ 'method_id' in data && (
<Badge display="inline-flex" alignItems="center" flexShrink={ 0 }>
{ data.method_id }
<CopyToClipboard text={ data.method_id } as="div"/>
</Badge>
) }
</AccordionItemTrigger>
</Element>
<AccordionItemContent pb={ 4 } pr={ 0 } pl="28px" w="calc(100% - 6px)">
{ 'is_invalid' in data && data.is_invalid ? (
<Alert status="warning">An error occurred while parsing the method signature.</Alert>
) : (
<ContractMethodForm
key={ id + '_' + index + '_' + attempt }
data={ data }
attempt={ attempt }
onSubmit={ onSubmit }
onReset={ handleReset }
isOpen={ isOpen }
/>
) }
</AccordionItemContent>
</AccordionItem>
);
};
......
......@@ -55,7 +55,7 @@ const ContractConnectWallet = ({ isLoading }: Props) => {
return (
<Skeleton loading={ isLoading }>
<Alert status={ web3Wallet.address ? 'success' : 'warning' }>
<Alert status={ web3Wallet.address ? 'success' : 'warning' } descriptionProps={{ alignItems: 'center' }}>
{ content }
</Alert>
</Skeleton>
......
......@@ -93,9 +93,9 @@ const ContractMethodsCustom = ({ isLoading: isLoadingProp }: Props) => {
onChange={ filters.onChange }
isLoading={ isLoading }
/>
{ /* <ContractMethodsContainer isLoading={ isLoading } isEmpty={ abi.length === 0 } type={ filters.methodType }>
<ContractMethodsContainer isLoading={ isLoading } isEmpty={ abi.length === 0 } type={ filters.methodType }>
<ContractAbi abi={ abi } tab={ tab } addressHash={ addressHash } visibleItems={ filters.visibleItems }/>
</ContractMethodsContainer> */ }
</ContractMethodsContainer>
</>
) : (
<>
......
......@@ -58,7 +58,7 @@ const ContractMethodsProxy = ({ implementations, isLoading: isInitialLoading }:
isLoading={ isInitialLoading }
/>
</div>
{ /* <ContractMethodsContainer
<ContractMethodsContainer
key={ selectedItem.address }
isLoading={ isInitialLoading || contractQuery.isPending }
isEmpty={ abi.length === 0 }
......@@ -72,7 +72,7 @@ const ContractMethodsProxy = ({ implementations, isLoading: isInitialLoading }:
visibleItems={ filters.visibleItems }
sourceAddress={ selectedItem.address }
/>
</ContractMethodsContainer> */ }
</ContractMethodsContainer>
</Flex>
);
};
......
......@@ -36,9 +36,9 @@ const ContractMethodsRegular = ({ abi, isLoading }: Props) => {
onChange={ filters.onChange }
isLoading={ isLoading }
/>
{ /* <ContractMethodsContainer isLoading={ isLoading } isEmpty={ formattedAbi.length === 0 } type={ filters.methodType }>
<ContractMethodsContainer isLoading={ isLoading } isEmpty={ formattedAbi.length === 0 } type={ filters.methodType }>
<ContractAbi abi={ formattedAbi } tab={ tab } addressHash={ addressHash } visibleItems={ filters.visibleItems }/>
</ContractMethodsContainer> */ }
</ContractMethodsContainer>
</Flex>
);
};
......
import { Button, Tooltip } from '@chakra-ui/react';
import React from 'react';
import useAccount from 'lib/web3/useAccount';
import { Button } from 'toolkit/chakra/button';
import { Tooltip } from 'toolkit/chakra/tooltip';
interface Props {
onClick: (address: string) => void;
......@@ -16,16 +17,15 @@ const ContractMethodAddressButton = ({ onClick, isDisabled }: Props) => {
}, [ address, onClick ]);
return (
<Tooltip label={ !address ? 'Connect your wallet to enter your address.' : undefined }>
<Tooltip content={ !address ? 'Connect your wallet to enter your address.' : undefined }>
<Button
variant="subtle"
colorScheme="gray"
size="xs"
fontSize="normal"
textStyle="md"
fontWeight={ 500 }
ml={ 1 }
onClick={ handleClick }
isDisabled={ isDisabled || !address }
disabled={ isDisabled || !address }
>
Self
</Button>
......
import { IconButton, chakra } from '@chakra-ui/react';
import { chakra } from '@chakra-ui/react';
import React from 'react';
import { IconButton } from 'toolkit/chakra/icon-button';
import IconSvg from 'ui/shared/IconSvg';
interface Props {
......@@ -12,19 +13,26 @@ interface Props {
}
const ContractMethodArrayButton = ({ className, type, index, onClick, isDisabled }: Props) => {
const handleClick = React.useCallback((event: React.MouseEvent<HTMLButtonElement>) => {
event.stopPropagation();
onClick(event);
}, [ onClick ]);
return (
<IconButton
as="div"
className={ className }
aria-label={ type }
data-index={ index }
variant="outline"
w="20px"
h="20px"
boxSize={ 5 }
flexShrink={ 0 }
onClick={ onClick }
icon={ <IconSvg name={ type === 'remove' ? 'minus' : 'plus' } boxSize={ 3 }/> }
isDisabled={ isDisabled }
/>
onClick={ handleClick }
disabled={ isDisabled }
>
<IconSvg name={ type === 'remove' ? 'minus' : 'plus' } boxSize={ 3 }/>
</IconButton>
);
};
......
import { Accordion, AccordionButton, AccordionIcon, AccordionItem, AccordionPanel, Box, useColorModeValue } from '@chakra-ui/react';
import { Box } from '@chakra-ui/react';
import React from 'react';
import { AccordionItem, AccordionItemContent, AccordionItemTrigger, AccordionRoot } from 'toolkit/chakra/accordion';
import ContractMethodArrayButton from './ContractMethodArrayButton';
export interface Props {
......@@ -14,37 +16,31 @@ export interface Props {
}
const ContractMethodFieldAccordion = ({ label, level, children, onAddClick, onRemoveClick, index, isInvalid }: Props) => {
const bgColorLevel0 = useColorModeValue('blackAlpha.50', 'whiteAlpha.50');
const bgColor = useColorModeValue('whiteAlpha.700', 'blackAlpha.700');
const bgColorLevel0 = { _light: 'blackAlpha.50', _dark: 'whiteAlpha.50' };
const bgColor = { _light: 'whiteAlpha.700', _dark: 'blackAlpha.700' };
return (
<Accordion allowToggle w="100%" bgColor={ level === 0 ? bgColorLevel0 : bgColor } borderRadius="base">
<AccordionItem _first={{ borderTopWidth: 0 }} _last={{ borderBottomWidth: 0 }}>
{ ({ isExpanded }) => (
<>
<AccordionButton
as="div"
cursor="pointer"
px="6px"
py="6px"
wordBreak="break-all"
textAlign="left"
_hover={{ bgColor: 'inherit' }}
>
<AccordionIcon transform={ isExpanded ? 'rotate(0deg)' : 'rotate(-90deg)' } color="gray.500"/>
<Box fontSize="sm" lineHeight={ 5 } fontWeight={ 700 } mr="auto" ml={ 1 } color={ isInvalid ? 'error' : undefined }>
{ label }
</Box>
{ onRemoveClick && <ContractMethodArrayButton index={ index } onClick={ onRemoveClick } type="remove"/> }
{ onAddClick && <ContractMethodArrayButton index={ index } onClick={ onAddClick } type="add" ml={ 2 }/> }
</AccordionButton>
<AccordionPanel display="flex" flexDir="column" rowGap={ 1 } pl="18px" pr="6px">
{ children }
</AccordionPanel>
</>
) }
<AccordionRoot w="100%" bgColor={ level === 0 ? bgColorLevel0 : bgColor } borderRadius="base" lazyMount>
<AccordionItem value="default" _first={{ borderTopWidth: 0 }} _last={{ borderBottomWidth: 0 }}>
<AccordionItemTrigger
indicatorPlacement="start"
px="6px"
py="6px"
wordBreak="break-all"
textAlign="left"
_hover={{ bgColor: 'inherit' }}
>
<Box textStyle="sm" fontWeight={ 700 } mr="auto" color={ isInvalid ? 'error' : undefined }>
{ label }
</Box>
{ onRemoveClick && index !== undefined && <ContractMethodArrayButton index={ index } onClick={ onRemoveClick } type="remove"/> }
{ onAddClick && index !== undefined && <ContractMethodArrayButton index={ index } onClick={ onAddClick } type="add" ml={ 2 }/> }
</AccordionItemTrigger>
<AccordionItemContent display="flex" flexDir="column" rowGap={ 1 } pl="18px" pr="6px">
{ children }
</AccordionItemContent>
</AccordionItem>
</Accordion>
</AccordionRoot>
);
};
......
import { Box, Button, Flex, FormControl, Input, InputGroup, InputRightElement, chakra, useColorModeValue } from '@chakra-ui/react';
import { Flex, chakra } from '@chakra-ui/react';
import React from 'react';
import { useController, useFormContext } from 'react-hook-form';
import { NumericFormat } from 'react-number-format';
// import { NumericFormat } from 'react-number-format';
import type { ContractAbiItemInput } from '../types';
import { HOUR, SECOND } from 'lib/consts';
import { Button } from 'toolkit/chakra/button';
import { Field } from 'toolkit/chakra/field';
import { Input } from 'toolkit/chakra/input';
import { InputGroup } from 'toolkit/chakra/input-group';
import ClearButton from 'ui/shared/ClearButton';
import ContractMethodAddressButton from './ContractMethodAddressButton';
......@@ -43,8 +47,8 @@ const ContractMethodFieldInput = ({ data, hideLabel, path: name, className, isDi
const { control, setValue, getValues } = useFormContext();
const { field, fieldState } = useController({ control, name, rules: { validate } });
const inputBgColor = useColorModeValue('white', 'black');
const nativeCoinRowBgColor = useColorModeValue('gray.100', 'gray.700');
const inputBgColor = { _light: 'white', _dark: 'black' };
const nativeCoinRowBgColor = { _light: 'gray.100', _dark: 'gray.700' };
const hasMultiplyButton = argTypeMatchInt && Number(argTypeMatchInt.power) >= 64;
......@@ -125,6 +129,46 @@ const ContractMethodFieldInput = ({ data, hideLabel, path: name, className, isDi
const error = fieldState.error;
const inputEndElement = (
<Flex alignItems="center">
{ field.value !== undefined && field.value !== '' && <ClearButton onClick={ handleClear } isDisabled={ isDisabled } boxSize={ 6 }/> }
{ data.type === 'address' && <ContractMethodAddressButton onClick={ handleAddressButtonClick } isDisabled={ isDisabled }/> }
{ argTypeMatchInt && !isNativeCoin && (hasTimestampButton ? (
<Button
variant="subtle"
size="xs"
textStyle="md"
fontWeight={ 500 }
ml={ 1 }
onClick={ handleTimestampButtonClick }
disabled={ isDisabled }
>
Now+1h
</Button>
) : (
<Button
variant="subtle"
size="xs"
textStyle="md"
fontWeight={ 500 }
ml={ 1 }
onClick={ handleMaxIntButtonClick }
disabled={ isDisabled }
>
Max
</Button>
)) }
{ hasMultiplyButton && (
<ContractMethodMultiplyButton
onClick={ handleMultiplyButtonClick }
isDisabled={ isDisabled }
initialValue={ intPower }
onChange={ setIntPower }
/>
) }
</Flex>
);
return (
<Flex
className={ className }
......@@ -138,73 +182,37 @@ const ContractMethodFieldInput = ({ data, hideLabel, path: name, className, isDi
py={ isNativeCoin ? 1 : 0 }
>
{ !hideLabel && <ContractMethodFieldLabel data={ data } isOptional={ isOptional } level={ level }/> }
<FormControl isDisabled={ isDisabled }>
<InputGroup size="xs">
<Field invalid={ Boolean(error) } errorText={ error?.message } disabled={ isDisabled }>
<InputGroup
endElement={ inputEndElement }
endElementProps={{ pl: 0, pr: 1 }}
>
<Input
{ ...field }
{ ...(argTypeMatchInt ? {
as: NumericFormat,
thousandSeparator: ' ',
decimalScale: 0,
allowNegative: !argTypeMatchInt.isUnsigned,
getInputRef: (element: HTMLInputElement) => {
ref.current = element;
},
} : {}) }
// TODO @tom2drum fix formatting of numeric input
// { ...(argTypeMatchInt ? {
// as: NumericFormat,
// thousandSeparator: ' ',
// decimalScale: 0,
// allowNegative: !argTypeMatchInt.isUnsigned,
// getInputRef: (element: HTMLInputElement) => {
// ref.current = element;
// },
// } : {}) }
// as we use mutable ref, we have to cast it to React.LegacyRef<HTMLInputElement> to trick chakra and typescript
ref={ ref as React.LegacyRef<HTMLInputElement> | undefined }
size="sm"
onChange={ handleChange }
onPaste={ handlePaste }
required={ !isOptional }
isInvalid={ Boolean(error) }
placeholder={ data.type }
autoComplete="off"
data-1p-ignore
bgColor={ inputBgColor }
paddingRight={ hasMultiplyButton ? '120px' : '40px' }
/>
<InputRightElement w="auto" right={ 1 } bgColor={ inputBgColor } h="calc(100% - 4px)" top="2px" borderRadius="base">
{ field.value !== undefined && field.value !== '' && <ClearButton onClick={ handleClear } isDisabled={ isDisabled }/> }
{ data.type === 'address' && <ContractMethodAddressButton onClick={ handleAddressButtonClick } isDisabled={ isDisabled }/> }
{ argTypeMatchInt && !isNativeCoin && (hasTimestampButton ? (
<Button
variant="subtle"
colorScheme="gray"
size="xs"
fontSize="normal"
fontWeight={ 500 }
ml={ 1 }
onClick={ handleTimestampButtonClick }
isDisabled={ isDisabled }
>
Now+1h
</Button>
) : (
<Button
variant="subtle"
colorScheme="gray"
size="xs"
fontSize="normal"
fontWeight={ 500 }
ml={ 1 }
onClick={ handleMaxIntButtonClick }
isDisabled={ isDisabled }
>
Max
</Button>
)) }
{ hasMultiplyButton && (
<ContractMethodMultiplyButton
onClick={ handleMultiplyButtonClick }
isDisabled={ isDisabled }
initialValue={ intPower }
onChange={ setIntPower }
/>
) }
</InputRightElement>
</InputGroup>
{ error && <Box color="error" fontSize="sm" lineHeight={ 5 } mt={ 1 }>{ error.message }</Box> }
</FormControl>
</Field>
</Flex>
);
};
......
import { Box, useColorModeValue } from '@chakra-ui/react';
import { Box } from '@chakra-ui/react';
import React from 'react';
import type { ContractAbiItemInput } from '../types';
......@@ -12,17 +12,14 @@ interface Props {
}
const ContractMethodFieldLabel = ({ data, isOptional, level }: Props) => {
const color = useColorModeValue('blackAlpha.600', 'whiteAlpha.600');
return (
<Box
w="250px"
fontSize="sm"
lineHeight={ 5 }
textStyle="sm"
py="6px"
flexShrink={ 0 }
fontWeight={ 500 }
color={ level > 1 ? color : undefined }
color={ level > 1 ? { _light: 'blackAlpha.600', _dark: 'whiteAlpha.600' } : undefined }
>
{ getFieldLabel(data, !isOptional) }
</Box>
......
import { Box, Button, Flex, Tooltip, chakra, useDisclosure } from '@chakra-ui/react';
import { Box, Flex, chakra } from '@chakra-ui/react';
import React from 'react';
import type { SubmitHandler } from 'react-hook-form';
import { useForm, FormProvider } from 'react-hook-form';
......@@ -9,6 +9,9 @@ import type { FormSubmitHandler, FormSubmitResult, MethodCallStrategy, SmartCont
import config from 'configs/app';
import { SECOND } from 'lib/consts';
import * as mixpanel from 'lib/mixpanel/index';
import { Button } from 'toolkit/chakra/button';
import { Tooltip } from 'toolkit/chakra/tooltip';
import { useDisclosure } from 'toolkit/hooks/useDisclosure';
import IconSvg from 'ui/shared/IconSvg';
import { isReadMethod } from '../utils';
......@@ -139,10 +142,10 @@ const ContractMethodForm = ({ data, attempt, onSubmit, onReset, isOpen }: Props)
const buttonCallStrategy = methodType === 'write' ? 'write' : 'read';
return (
<Tooltip label={ isDisabled ? NO_WALLET_CLIENT_TEXT : undefined } maxW="300px">
<Tooltip content={ NO_WALLET_CLIENT_TEXT } disabled={ !isDisabled }>
<Button
isLoading={ callStrategy === buttonCallStrategy && isLoading }
isDisabled={ isLoading || isDisabled }
loading={ callStrategy === buttonCallStrategy && isLoading }
disabled={ isLoading || isDisabled }
onClick={ handleButtonClick }
loadingText={ text }
variant="outline"
......@@ -174,8 +177,8 @@ const ContractMethodForm = ({ data, attempt, onSubmit, onReset, isOpen }: Props)
return (
<Button
isLoading={ callStrategy === buttonCallStrategy && isLoading }
isDisabled={ isLoading }
loading={ callStrategy === buttonCallStrategy && isLoading }
disabled={ isLoading }
onClick={ handleButtonClick }
loadingText={ text }
variant="outline"
......@@ -183,7 +186,6 @@ const ContractMethodForm = ({ data, attempt, onSubmit, onReset, isOpen }: Props)
flexShrink={ 0 }
width="min-content"
px={ 4 }
mr={ 3 }
type="submit"
data-call-strategy={ buttonCallStrategy }
>
......@@ -210,15 +212,14 @@ const ContractMethodForm = ({ data, attempt, onSubmit, onReset, isOpen }: Props)
return (
<Tooltip
isDisabled={ isDisabled }
label="Copied"
disabled={ isDisabled }
content="Copied"
closeDelay={ SECOND }
isOpen={ calldataButtonTooltip.isOpen }
onClose={ calldataButtonTooltip.onClose }
open={ calldataButtonTooltip.open }
>
<Button
isLoading={ callStrategy === buttonCallStrategy && isLoading }
isDisabled={ isDisabled }
loading={ callStrategy === buttonCallStrategy && isLoading }
disabled={ isDisabled }
onClick={ handleButtonClick }
loadingText={ text }
variant="outline"
......@@ -226,7 +227,6 @@ const ContractMethodForm = ({ data, attempt, onSubmit, onReset, isOpen }: Props)
flexShrink={ 0 }
width="min-content"
px={ 4 }
ml={ 3 }
type="submit"
data-call-strategy={ buttonCallStrategy }
>
......@@ -282,21 +282,22 @@ const ContractMethodForm = ({ data, attempt, onSubmit, onReset, isOpen }: Props)
return <ContractMethodFieldInput key={ index } { ...props } path={ `${ index }` }/>;
}) }
</Flex>
{ secondaryButton }
{ primaryButton }
{ copyCallDataButton }
{ result && !isLoading && (
<Button
variant="simple"
colorScheme="blue"
size="sm"
onClick={ onReset }
ml={ 1 }
>
<IconSvg name="repeat" boxSize={ 5 } mr={ 1 }/>
Reset
</Button>
) }
<Flex flexDir="row" gap={ 3 }>
{ secondaryButton }
{ primaryButton }
{ copyCallDataButton }
{ result && !isLoading && (
<Button
variant="link"
size="sm"
onClick={ onReset }
gap={ 1 }
>
<IconSvg name="repeat" boxSize={ 5 }/>
Reset
</Button>
) }
</Flex>
</chakra.form>
</FormProvider>
{ result && result.source === 'wallet_client' && (
......
import {
chakra,
PopoverBody,
PopoverContent,
PopoverTrigger,
Portal,
Button,
List,
ListItem,
useDisclosure,
Input,
useColorModeValue,
} from '@chakra-ui/react';
import { chakra, List, Input, ListItem } from '@chakra-ui/react';
import React from 'react';
import { times } from 'lib/html-entities';
import Popover from 'ui/shared/chakra/Popover';
import { Button } from 'toolkit/chakra/button';
import { IconButton } from 'toolkit/chakra/icon-button';
import { PopoverBody, PopoverContent, PopoverRoot, PopoverTrigger } from 'toolkit/chakra/popover';
import { useDisclosure } from 'toolkit/hooks/useDisclosure';
import IconSvg from 'ui/shared/IconSvg';
interface Props {
......@@ -27,19 +18,17 @@ interface Props {
const ContractMethodMultiplyButton = ({ onClick, isDisabled, initialValue, onChange }: Props) => {
const [ selectedOption, setSelectedOption ] = React.useState<number | undefined>(initialValue);
const [ customValue, setCustomValue ] = React.useState<number>();
const { isOpen, onToggle, onClose } = useDisclosure();
const dividerColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
const { open, onOpenChange } = useDisclosure();
const handleOptionClick = React.useCallback((event: React.MouseEvent) => {
const id = Number((event.currentTarget as HTMLDivElement).getAttribute('data-id'));
if (!Object.is(id, NaN)) {
setSelectedOption((prev) => prev === id ? undefined : id);
setCustomValue(undefined);
onClose();
onOpenChange({ open: false });
onChange(id);
}
}, [ onClose, onChange ]);
}, [ onOpenChange, onChange ]);
const handleInputChange = React.useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
const value = Number(event.target.value);
......@@ -59,90 +48,84 @@ const ContractMethodMultiplyButton = ({ onClick, isDisabled, initialValue, onCha
{ Boolean(value) && (
<Button
px={ 1 }
lineHeight={ 6 }
h={ 6 }
textStyle="md"
size="xs"
fontWeight={ 500 }
ml={ 1 }
variant="subtle"
colorScheme="gray"
display="inline"
onClick={ handleButtonClick }
isDisabled={ isDisabled }
disabled={ isDisabled }
borderBottomRightRadius={ 0 }
borderTopRightRadius={ 0 }
>
{ times }
<chakra.span>10</chakra.span>
<chakra.span fontSize="xs" lineHeight={ 4 } verticalAlign="super">{ value }</chakra.span>
<chakra.span fontSize="xs" lineHeight="16px" verticalAlign="super">{ value }</chakra.span>
</Button>
) }
<Popover placement="bottom-end" isLazy isOpen={ isOpen } onClose={ onClose }>
<PopoverRoot open={ open } onOpenChange={ onOpenChange } positioning={{ placement: 'bottom-end' }}>
<PopoverTrigger>
<Button
<IconButton
variant="subtle"
colorScheme="gray"
size="xs"
cursor="pointer"
p={ 0 }
onClick={ onToggle }
isActive={ isOpen }
isDisabled={ isDisabled }
disabled={ isDisabled }
borderBottomLeftRadius={ 0 }
borderTopLeftRadius={ 0 }
borderLeftWidth="1px"
borderLeftColor={ dividerColor }
borderLeftColor="border.divider"
>
<IconSvg
name="arrows/east-mini"
transitionDuration="fast"
transitionProperty="transform"
transitionTimingFunction="ease-in-out"
transform={ isOpen ? 'rotate(90deg)' : 'rotate(-90deg)' }
transform={ open ? 'rotate(90deg)' : 'rotate(-90deg)' }
boxSize={ 6 }
/>
</Button>
</IconButton>
</PopoverTrigger>
<Portal>
<PopoverContent w="110px">
<PopoverBody py={ 2 }>
<List>
{ [ 8, 12, 16, 18, 20 ].map((id) => (
<ListItem
key={ id }
py={ 2 }
data-id={ id }
onClick={ handleOptionClick }
display="flex"
justifyContent="space-between"
alignItems="center"
cursor="pointer"
>
<span>10*{ id }</span>
{ selectedOption === id && <IconSvg name="check" boxSize={ 6 } color="blue.600"/> }
</ListItem>
)) }
<ListItem
<PopoverContent w="110px">
<PopoverBody textStyle="md" py={ 2 }>
<List.Root>
{ [ 8, 12, 16, 18, 20 ].map((id) => (
<List.Item
key={ id }
py={ 2 }
data-id={ id }
onClick={ handleOptionClick }
display="flex"
justifyContent="space-between"
alignItems="center"
cursor="pointer"
>
<span>10*</span>
<Input
type="number"
min={ 0 }
max={ 100 }
ml={ 3 }
size="xs"
onChange={ handleInputChange }
value={ customValue || '' }
/>
</ListItem>
</List>
</PopoverBody>
</PopoverContent>
</Portal>
</Popover>
<span>10*{ id }</span>
{ selectedOption === id && <IconSvg name="check" boxSize={ 6 } color="blue.600"/> }
</List.Item>
)) }
<ListItem
py={ 2 }
display="flex"
justifyContent="space-between"
alignItems="center"
>
<span>10*</span>
<Input
type="number"
min={ 0 }
max={ 100 }
ml={ 3 }
size="sm"
onChange={ handleInputChange }
value={ customValue || '' }
/>
</ListItem>
</List.Root>
</PopoverBody>
</PopoverContent>
</PopoverRoot>
</>
);
};
......
import { Alert, Flex, useColorModeValue } from '@chakra-ui/react';
import { Flex } from '@chakra-ui/react';
import React from 'react';
import type { AbiFunction } from 'viem';
import type { FormSubmitResultPublicClient, ResultViewMode } from '../types';
import { Alert } from 'toolkit/chakra/alert';
import ResultItem from './resultPublicClient/Item';
export interface Props {
......@@ -14,8 +16,6 @@ export interface Props {
}
const ContractMethodResultPublicClient = ({ data, abiItem, onSettle, mode: modeProps }: Props) => {
const bgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50');
React.useEffect(() => {
if (modeProps === 'result') {
onSettle();
......@@ -32,7 +32,7 @@ const ContractMethodResultPublicClient = ({ data, abiItem, onSettle, mode: modeP
return (
<>
{ isError && (
<Alert status="error" mt={ 3 } p={ 4 } borderRadius="md" fontSize="sm" wordBreak="break-word" whiteSpace="pre-wrap">
<Alert status="error" mt={ 3 } p={ 4 } borderRadius="md" textStyle="sm" wordBreak="break-word" whiteSpace="pre-wrap">
{ 'shortMessage' in data && typeof data.shortMessage === 'string' ? data.shortMessage : data.message }
</Alert>
) }
......@@ -42,7 +42,7 @@ const ContractMethodResultPublicClient = ({ data, abiItem, onSettle, mode: modeP
mt={ 3 }
p={ 4 }
borderRadius="md"
bgColor={ bgColor }
bgColor={{ _light: 'blackAlpha.50', _dark: 'whiteAlpha.50' }}
color={ mode === 'preview' ? 'gray.500' : undefined }
fontSize="sm"
lineHeight="20px"
......
import { chakra, Spinner, Box, Alert } from '@chakra-ui/react';
import { chakra, Spinner, Box } from '@chakra-ui/react';
import React from 'react';
import type { UseWaitForTransactionReceiptReturnType } from 'wagmi';
import { useWaitForTransactionReceipt } from 'wagmi';
......@@ -7,7 +7,8 @@ import type { FormSubmitResultWalletClient } from '../types';
import { route } from 'nextjs-routes';
import LinkInternal from 'ui/shared/links/LinkInternal';
import { Alert } from 'toolkit/chakra/alert';
import { Link } from 'toolkit/chakra/link';
interface Props {
data: FormSubmitResultWalletClient['data'];
......@@ -45,13 +46,13 @@ export const ContractMethodResultWalletClientDumb = ({ data, onSettle, txInfo }:
const isErrorResult = 'message' in data;
const txLink = txHash ? (
<LinkInternal href={ route({ pathname: '/tx/[hash]', query: { hash: txHash } }) }>View transaction details</LinkInternal>
<Link href={ route({ pathname: '/tx/[hash]', query: { hash: txHash } }) }>View transaction details</Link>
) : null;
const content = (() => {
if (isErrorResult) {
return (
<Alert status="error">
<Alert status="error" textStyle="sm">
{ data.message }
</Alert>
);
......@@ -81,7 +82,7 @@ export const ContractMethodResultWalletClientDumb = ({ data, onSettle, txInfo }:
case 'error': {
return (
<Alert status="error" flexDir="column" alignItems="flex-start" rowGap={ 1 }>
<Alert status="error" textStyle="sm" descriptionProps={{ flexDir: 'column', alignItems: 'flex-start', rowGap: 1 }}>
Error: { txInfo.error ? txInfo.error.message : 'Something went wrong' } { txLink }
</Alert>
);
......@@ -91,7 +92,7 @@ export const ContractMethodResultWalletClientDumb = ({ data, onSettle, txInfo }:
return (
<Box
fontSize="sm"
textStyle="sm"
mt={ 3 }
alignItems="center"
whiteSpace="pre-wrap"
......
import { Tooltip } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import React from 'react';
import type { AbiParameter } from 'viem';
......@@ -6,8 +5,9 @@ import type { AbiParameter } from 'viem';
import { route } from 'nextjs-routes';
import { WEI } from 'lib/consts';
import { Link } from 'toolkit/chakra/link';
import { Tooltip } from 'toolkit/chakra/tooltip';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import LinkInternal from 'ui/shared/links/LinkInternal';
import { matchInt } from '../utils';
import ItemLabel from './ItemLabel';
......@@ -44,8 +44,8 @@ const ItemPrimitive = ({ abiParameter, data, level, hideLabel }: Props) => {
if (abiParameter.type === 'address' && typeof data === 'string') {
return (
<>
<LinkInternal href={ route({ pathname: '/address/[hash]', query: { hash: data } }) }>{ data }</LinkInternal>
<CopyToClipboard text={ data } size={ 4 } verticalAlign="sub"/>
<Link href={ route({ pathname: '/address/[hash]', query: { hash: data } }) }>{ data }</Link>
<CopyToClipboard text={ data } boxSize={ 4 } verticalAlign="sub"/>
</>
);
}
......@@ -54,7 +54,7 @@ const ItemPrimitive = ({ abiParameter, data, level, hideLabel }: Props) => {
if (intMatch && typeof data === 'bigint' && intMatch.max > INT_TOOLTIP_THRESHOLD && data > INT_TOOLTIP_THRESHOLD) {
const dividedValue = BigNumber(data.toString()).div(WEI);
return (
<Tooltip label={ dividedValue.toLocaleString() + ' ETH' }>
<Tooltip content={ dividedValue.toLocaleString() + ' ETH' }>
<span>{ castValueToString(data) }</span>
</Tooltip>
);
......
......@@ -16,7 +16,7 @@ interface Props {
const ItemTuple = ({ abiParameter, data, mode, level }: Props) => {
return (
<div>
<p>
<p>
<span>{ printRowOffset(level) }</span>
<chakra.span fontWeight={ 500 }>{ abiParameter.name || abiParameter.internalType }</chakra.span>
......@@ -48,7 +48,7 @@ const ItemTuple = ({ abiParameter, data, mode, level }: Props) => {
);
}) }
<p>{ printRowOffset(level) }{ '}' }</p>
</div>
</p>
);
};
......
......@@ -19,7 +19,7 @@ export const getElementName = (data: SmartContractMethod) => {
return `method_${ getElementId(data) }`;
};
export default function useScrollToMethod(data: Array<SmartContractMethod>, onScroll: (indices: Array<number>) => void) {
export default function useScrollToMethod(data: Array<SmartContractMethod>, onScroll: (indices: Array<string>) => void) {
React.useEffect(() => {
const hash = window.location.hash.replace('#', '');
......@@ -34,7 +34,7 @@ export default function useScrollToMethod(data: Array<SmartContractMethod>, onSc
smooth: true,
offset: -100,
});
onScroll([ index ]);
onScroll([ String(index) ]);
}
}, [ data, onScroll ]);
}
......@@ -11,9 +11,10 @@ interface Props {
className?: string;
tooltipProps?: Partial<TooltipProps>;
isLoading?: boolean;
as?: React.ElementType;
}
const Hint = ({ label, className, tooltipProps, isLoading }: Props) => {
const Hint = ({ label, className, tooltipProps, isLoading, as }: Props) => {
return (
<Tooltip
content={ label }
......@@ -27,6 +28,7 @@ const Hint = ({ label, className, tooltipProps, isLoading }: Props) => {
className={ className }
loading={ isLoading }
borderRadius="sm"
as={ as }
>
<IconSvg name="info" w="100%" h="100%" color="icon_info" _hover={{ color: 'link.primary.hover' }}/>
</IconButton>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment