Commit 05e389dd authored by tom's avatar tom

tuple type fields

parent a8448fef
import type { Abi } from 'abitype';
export type SmartContractMethodArgType = 'address' | 'uint256' | 'bool' | 'string' | 'bytes' | 'bytes32' | 'bytes32[]';
export type SmartContractMethodArgType = 'address' | 'uint256' | 'bool' | 'string' | 'bytes' | 'bytes32' | 'bytes32[]' | 'tuple';
export type SmartContractMethodStateMutability = 'view' | 'nonpayable' | 'payable';
export interface SmartContract {
......@@ -88,6 +88,7 @@ export interface SmartContractMethodInput {
internalType?: SmartContractMethodArgType;
name: string;
type: SmartContractMethodArgType;
components?: Array<SmartContractMethodInput>;
}
export interface SmartContractMethodOutput extends SmartContractMethodInput {
......
import { Box, Button, chakra, Flex, Icon, Text } from '@chakra-ui/react';
import _fromPairs from 'lodash/fromPairs';
// import _fromPairs from 'lodash/fromPairs';
import React from 'react';
import type { SubmitHandler } from 'react-hook-form';
import { useForm } from 'react-hook-form';
import { useForm, FormProvider } from 'react-hook-form';
import type { MethodFormFields, ContractMethodCallResult } from './types';
import type { SmartContractMethodInput, SmartContractMethod, SmartContractMethodArgType } from 'types/api/contract';
import type { SmartContractMethodInput, SmartContractMethod } from 'types/api/contract';
import arrowIcon from 'icons/arrows/down-right.svg';
import * as mixpanel from 'lib/mixpanel/index';
import ContractMethodField from './ContractMethodField';
import ContractMethodFieldArray from './ContractMethodFieldArray';
import { ARRAY_REGEXP } from './utils';
import ContractMethodCallableRow from './ContractMethodCallableRow';
interface ResultComponentProps<T extends SmartContractMethod> {
item: T;
......@@ -27,8 +25,11 @@ interface Props<T extends SmartContractMethod> {
isWrite?: boolean;
}
// TODO @tom2drum remove this
const getFieldName = (name: string | undefined, index: number): string => name || String(index);
const getFormFieldName = (inputName: string, inputIndex: number, group?: string) => `${ group ? `${ group }_` : '' }${ inputName || 'input' }-${ inputIndex }}`;
const sortFields = (data: Array<SmartContractMethodInput>) => (
[ a ]: [string, string | Array<string>],
[ b ]: [string, string | Array<string>],
......@@ -87,8 +88,9 @@ const ContractMethodCallable = <T extends SmartContractMethod>({ data, onSubmit,
];
}, [ data ]);
const { control, handleSubmit, setValue, getValues } = useForm<MethodFormFields>({
defaultValues: _fromPairs(inputs.map(({ name }, index) => [ getFieldName(name, index), '' ])),
const formApi = useForm<MethodFormFields>({
// TODO @tom2drum rewrite this
// defaultValues: _fromPairs(inputs.map(({ name }, index) => [ getFieldName(name, index), '' ])),
mode: 'onBlur',
});
......@@ -101,6 +103,10 @@ const ContractMethodCallable = <T extends SmartContractMethod>({ data, onSubmit,
}, [ result ]);
const onFormSubmit: SubmitHandler<MethodFormFields> = React.useCallback(async(formData) => {
// console.log('__>__ formData', formData);
// debugger;
const args = Object.entries(formData)
.sort(sortFields(inputs))
.map(castFieldValue(inputs))
......@@ -127,56 +133,65 @@ const ContractMethodCallable = <T extends SmartContractMethod>({ data, onSubmit,
return (
<Box>
<FormProvider { ...formApi }>
<chakra.form
noValidate
display="grid"
columnGap={ 3 }
rowGap={{ base: 2, lg: 3 }}
gridTemplateColumns={{ base: 'minmax(0, 1fr)', lg: 'minmax(min-content, 180px) minmax(0, 1fr)' }}
onSubmit={ handleSubmit(onFormSubmit) }
gridTemplateColumns={{ base: 'minmax(0, 1fr)', lg: 'minmax(min-content, 250px) minmax(0, 1fr)' }}
onSubmit={ formApi.handleSubmit(onFormSubmit) }
onChange={ handleFormChange }
>
{ inputs.map(({ type, name }, index) => {
const fieldName = getFieldName(name, index);
const arrayTypeMatch = type.match(ARRAY_REGEXP);
const content = arrayTypeMatch ? (
<ContractMethodFieldArray
name={ fieldName }
valueType={ arrayTypeMatch[1] as SmartContractMethodArgType }
size={ Number(arrayTypeMatch[2] || Infinity) }
control={ control }
setValue={ setValue }
getValues={ getValues }
isDisabled={ isLoading }
onChange={ handleFormChange }
/>
) : (
<ContractMethodField
name={ fieldName }
valueType={ type }
placeholder={ `${ name }(${ type })` }
control={ control }
setValue={ setValue }
getValues={ getValues }
isDisabled={ isLoading }
onChange={ handleFormChange }
/>
);
{ inputs.map((input, index) => {
const fieldName = getFormFieldName(input.name, index);
if (input.type === 'tuple' && input.components) {
return (
<React.Fragment key={ index }>
<React.Fragment key={ fieldName }>
{ index !== 0 && <><Box h={{ base: 0, lg: 3 }}/><div/></> }
<Box
fontWeight={ 500 }
lineHeight="20px"
py={{ lg: '6px' }}
fontSize="sm"
wordBreak="break-word"
>
{ name } ({ type })
{ input.name } ({ input.type })
</Box>
{ content }
<div/>
{ input.components.map((component, componentIndex) => {
const fieldName = getFormFieldName(component.name, componentIndex, input.name);
return (
<ContractMethodCallableRow
key={ fieldName }
fieldName={ fieldName }
argName={ component.name }
argType={ component.type }
isDisabled={ isLoading }
onChange={ handleFormChange }
isGrouped
/>
);
}) }
{ index !== inputs.length - 1 && <><Box h={{ base: 0, lg: 3 }}/><div/></> }
</React.Fragment>
);
}
return (
<ContractMethodCallableRow
key={ fieldName }
fieldName={ fieldName }
argName={ input.name }
argType={ input.type }
isDisabled={ isLoading }
onChange={ handleFormChange }
/>
);
}) }
<div/>
<Button
isLoading={ isLoading }
loadingText={ isWrite ? 'Write' : 'Read' }
......@@ -190,6 +205,7 @@ const ContractMethodCallable = <T extends SmartContractMethod>({ data, onSubmit,
{ isWrite ? 'Write' : 'Read' }
</Button>
</chakra.form>
</FormProvider>
{ 'outputs' in data && !isWrite && data.outputs.length > 0 && (
<Flex mt={ 3 }>
<Icon as={ arrowIcon } boxSize={ 5 } mr={ 1 }/>
......
import { Box } from '@chakra-ui/react';
import React from 'react';
import { useFormContext } from 'react-hook-form';
import type { MethodFormFields } from './types';
import type { SmartContractMethodArgType } from 'types/api/contract';
import ContractMethodField from './ContractMethodField';
import ContractMethodFieldArray from './ContractMethodFieldArray';
import { ARRAY_REGEXP } from './utils';
interface Props {
fieldName: string;
argName: string;
argType: SmartContractMethodArgType;
onChange: () => void;
isDisabled: boolean;
isGrouped?: boolean;
}
const ContractMethodCallableRow = ({ argName, fieldName, argType, onChange, isDisabled, isGrouped }: Props) => {
const { control, getValues, setValue } = useFormContext<MethodFormFields>();
const arrayTypeMatch = argType.match(ARRAY_REGEXP);
const content = arrayTypeMatch ? (
<ContractMethodFieldArray
name={ fieldName }
argType={ arrayTypeMatch[1] as SmartContractMethodArgType }
size={ Number(arrayTypeMatch[2] || Infinity) }
control={ control }
setValue={ setValue }
getValues={ getValues }
isDisabled={ isDisabled }
onChange={ onChange }
/>
) : (
<ContractMethodField
name={ fieldName }
argType={ argType }
placeholder={ argType }
control={ control }
setValue={ setValue }
getValues={ getValues }
isDisabled={ isDisabled }
onChange={ onChange }
/>
);
return (
<>
<Box
fontWeight={ 500 }
lineHeight="20px"
py={{ lg: '6px' }}
fontSize="sm"
color={ isGrouped ? 'text_secondary' : 'initial' }
wordBreak="break-word"
>
{ argName } ({ argType })
</Box>
{ content }
</>
);
};
export default React.memo(ContractMethodCallableRow);
......@@ -25,7 +25,7 @@ interface Props {
index?: number;
groupName?: string;
placeholder: string;
valueType: SmartContractMethodArgType;
argType: SmartContractMethodArgType;
control: Control<MethodFormFields>;
setValue: UseFormSetValue<MethodFormFields>;
getValues: UseFormGetValues<MethodFormFields>;
......@@ -33,7 +33,7 @@ interface Props {
onChange: () => void;
}
const ContractMethodField = ({ control, name, groupName, index, valueType, placeholder, setValue, getValues, isDisabled, onChange }: Props) => {
const ContractMethodField = ({ control, name, groupName, index, argType, placeholder, setValue, getValues, isDisabled, onChange }: Props) => {
const ref = React.useRef<HTMLInputElement>(null);
const handleClear = React.useCallback(() => {
......@@ -51,7 +51,7 @@ const ContractMethodField = ({ control, name, groupName, index, valueType, place
}, [ getValues, groupName, index, name, onChange, setValue ]);
const intMatch = React.useMemo(() => {
const match = valueType.match(INT_REGEXP);
const match = argType.match(INT_REGEXP);
if (!match) {
return null;
}
......@@ -60,11 +60,11 @@ const ContractMethodField = ({ control, name, groupName, index, valueType, place
const [ min, max ] = getIntBoundaries(Number(power), Boolean(isUnsigned));
return { isUnsigned, power, min, max };
}, [ valueType ]);
}, [ argType ]);
const bytesMatch = React.useMemo(() => {
return valueType.match(BYTES_REGEXP);
}, [ valueType ]);
return argType.match(BYTES_REGEXP);
}, [ argType ]);
const renderInput = React.useCallback((
{ field, formState }: { field: ControllerRenderProps<MethodFormFields>; formState: UseFormStateReturn<MethodFormFields> },
......@@ -111,11 +111,11 @@ const ContractMethodField = ({ control, name, groupName, index, valueType, place
}, [ index, groupName, name, intMatch, isDisabled, placeholder, handleClear, handleAddZeroesClick ]);
const validate = React.useCallback((value: string | Array<string>) => {
if (typeof value === 'object') {
if (typeof value === 'object' || !value) {
return;
}
if (valueType === 'address') {
if (argType === 'address') {
return !isAddress(value) ? 'Invalid address format' : true;
}
......@@ -135,7 +135,7 @@ const ContractMethodField = ({ control, name, groupName, index, valueType, place
return true;
}
if (valueType === 'bool') {
if (argType === 'bool') {
const formattedValue = formatBooleanValue(value);
if (formattedValue === undefined) {
return 'Invalid boolean format. Allowed values: 0, 1, true, false';
......@@ -160,7 +160,7 @@ const ContractMethodField = ({ control, name, groupName, index, valueType, place
}
return true;
}, [ bytesMatch, intMatch, valueType ]);
}, [ bytesMatch, intMatch, argType ]);
return (
<Controller
......
......@@ -14,7 +14,7 @@ import ContractMethodField from './ContractMethodField';
interface Props {
name: string;
size: number;
valueType: SmartContractMethodArgType;
argType: SmartContractMethodArgType;
control: Control<MethodFormFields>;
setValue: UseFormSetValue<MethodFormFields>;
getValues: UseFormGetValues<MethodFormFields>;
......@@ -22,7 +22,7 @@ interface Props {
onChange: () => void;
}
const ContractMethodFieldArray = ({ control, name, setValue, getValues, isDisabled, valueType, onChange }: Props) => {
const ContractMethodFieldArray = ({ control, name, setValue, getValues, isDisabled, argType, onChange }: Props) => {
const { fields, append, remove } = useFieldArray({
name: name as never,
control,
......@@ -52,8 +52,8 @@ const ContractMethodFieldArray = ({ control, name, setValue, getValues, isDisabl
name={ `${ name }[${ index }]` }
groupName={ name }
index={ index }
valueType={ valueType }
placeholder={ valueType }
argType={ argType }
placeholder={ argType }
control={ control }
setValue={ setValue }
getValues={ getValues }
......
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