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,24 +51,27 @@ 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 }) => (
<>
<AccordionItem
as="section"
value={ String(index) }
_first={{ borderTopWidth: 0 }}
_last={{ borderBottomWidth: 0 }}
display={ isVisible ? 'block' : 'none' }
>
<Element as="h2" name={ getElementName(data) }>
<AccordionButton
<AccordionItemTrigger
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">
<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
......@@ -74,6 +81,7 @@ const ContractAbiItem = ({ data, index, id, addressHash, sourceAddress, tab, onS
The fallback function always receives data, but in order to also receive Ether it must be marked payable.`
}
ml={ 1 }
as="div"
/>
) }
{ data.type === 'receive' && (
......@@ -86,20 +94,20 @@ const ContractAbiItem = ({ data, index, id, addressHash, sourceAddress, tab, onS
the contract cannot receive Ether through regular transactions and throws an exception.`
}
ml={ 1 }
as="div"
/>
) }
</Box>
<Tag colorScheme={ isRead ? 'black-purple' : 'black-blue' } flexShrink={ 0 }>{ isRead ? 'read' : 'write' }</Tag>
<Badge colorPalette={ isRead ? 'purple_alt' : 'blue_alt' } flexShrink={ 0 }>{ isRead ? 'read' : 'write' }</Badge>
{ 'method_id' in data && (
<Tag display="inline-flex" alignItems="center" flexShrink={ 0 }>
<Badge display="inline-flex" alignItems="center" flexShrink={ 0 }>
{ data.method_id }
<CopyToClipboard text={ data.method_id }/>
</Tag>
<CopyToClipboard text={ data.method_id } as="div"/>
</Badge>
) }
<AccordionIcon transform={ isExpanded ? 'rotate(0deg)' : 'rotate(-90deg)' } color="gray.500"/>
</AccordionButton>
</AccordionItemTrigger>
</Element>
<AccordionPanel pb={ 4 } pr={ 0 } pl="28px" w="calc(100% - 6px)">
<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>
) : (
......@@ -109,12 +117,10 @@ const ContractAbiItem = ({ data, index, id, addressHash, sourceAddress, tab, onS
attempt={ attempt }
onSubmit={ onSubmit }
onReset={ handleReset }
isOpen={ isExpanded }
isOpen={ isOpen }
/>
) }
</AccordionPanel>
</>
) }
</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"
<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' }}
>
<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 }>
<Box textStyle="sm" fontWeight={ 700 } mr="auto" 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">
{ 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 }
</AccordionPanel>
</>
) }
</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,70 +129,31 @@ const ContractMethodFieldInput = ({ data, hideLabel, path: name, className, isDi
const error = fieldState.error;
return (
<Flex
className={ className }
flexDir={{ base: 'column', md: 'row' }}
alignItems="flex-start"
columnGap={ 3 }
w="100%"
bgColor={ isNativeCoin ? nativeCoinRowBgColor : undefined }
borderRadius="base"
px="6px"
py={ isNativeCoin ? 1 : 0 }
>
{ !hideLabel && <ContractMethodFieldLabel data={ data } isOptional={ isOptional } level={ level }/> }
<FormControl isDisabled={ isDisabled }>
<InputGroup size="xs">
<Input
{ ...field }
{ ...(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 }
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 }/> }
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"
colorScheme="gray"
size="xs"
fontSize="normal"
textStyle="md"
fontWeight={ 500 }
ml={ 1 }
onClick={ handleTimestampButtonClick }
isDisabled={ isDisabled }
disabled={ isDisabled }
>
Now+1h
</Button>
) : (
<Button
variant="subtle"
colorScheme="gray"
size="xs"
fontSize="normal"
textStyle="md"
fontWeight={ 500 }
ml={ 1 }
onClick={ handleMaxIntButtonClick }
isDisabled={ isDisabled }
disabled={ isDisabled }
>
Max
</Button>
......@@ -201,10 +166,53 @@ const ContractMethodFieldInput = ({ data, hideLabel, path: name, className, isDi
onChange={ setIntPower }
/>
) }
</InputRightElement>
</Flex>
);
return (
<Flex
className={ className }
flexDir={{ base: 'column', md: 'row' }}
alignItems="flex-start"
columnGap={ 3 }
w="100%"
bgColor={ isNativeCoin ? nativeCoinRowBgColor : undefined }
borderRadius="base"
px="6px"
py={ isNativeCoin ? 1 : 0 }
>
{ !hideLabel && <ContractMethodFieldLabel data={ data } isOptional={ isOptional } level={ level }/> }
<Field invalid={ Boolean(error) } errorText={ error?.message } disabled={ isDisabled }>
<InputGroup
endElement={ inputEndElement }
endElementProps={{ pl: 0, pr: 1 }}
>
<Input
{ ...field }
// 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 }
placeholder={ data.type }
autoComplete="off"
data-1p-ignore
bgColor={ inputBgColor }
paddingRight={ hasMultiplyButton ? '120px' : '40px' }
/>
</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>
<Flex flexDir="row" gap={ 3 }>
{ secondaryButton }
{ primaryButton }
{ copyCallDataButton }
{ result && !isLoading && (
<Button
variant="simple"
colorScheme="blue"
variant="link"
size="sm"
onClick={ onReset }
ml={ 1 }
gap={ 1 }
>
<IconSvg name="repeat" boxSize={ 5 } mr={ 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,55 +48,50 @@ 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>
<PopoverBody textStyle="md" py={ 2 }>
<List.Root>
{ [ 8, 12, 16, 18, 20 ].map((id) => (
<ListItem
<List.Item
key={ id }
py={ 2 }
data-id={ id }
......@@ -119,7 +103,7 @@ const ContractMethodMultiplyButton = ({ onClick, isDisabled, initialValue, onCha
>
<span>10*{ id }</span>
{ selectedOption === id && <IconSvg name="check" boxSize={ 6 } color="blue.600"/> }
</ListItem>
</List.Item>
)) }
<ListItem
py={ 2 }
......@@ -133,16 +117,15 @@ const ContractMethodMultiplyButton = ({ onClick, isDisabled, initialValue, onCha
min={ 0 }
max={ 100 }
ml={ 3 }
size="xs"
size="sm"
onChange={ handleInputChange }
value={ customValue || '' }
/>
</ListItem>
</List>
</List.Root>
</PopoverBody>
</PopoverContent>
</Portal>
</Popover>
</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