mirror of
https://github.com/luxfi/explore.git
synced 2026-09-24 00:28:18 +00:00
* refactor widgets on home page * coming soon placeholder component * top accounts page * contract data * address ERC-20 tokens list * chain filter for address ERC-20 tokens * token select for address detailed view * address details fields * tokens table * stats widgets on home page * chain indicators graph on the home page * quick search * search results all tab * tabs and chain select on search results page * search results mobile view * add contract info to search and address details * add text filter for tokens and search improvements * load all results inside tab on search results page * token balance info * add support for domains in the full search * Multichain explorer Resolves #2989 * add envs validator for multichain * add some tests * fix search bar on search results page on mobile * home stats fixes * fix ts * optimize amount of queries on address page * add more contract info on address page * add enter key hint to search input * fix tests * fix test * update bg.primary css-variable on color theme change * sitemap for multichain * txs page: render first tab if blob tab is not available on the chain * filter out in active chains from chain select on address page and user ops page * add chain icon to advanced filter table * fix tooltip on entity with shield * fix ts * show contract implementations * fixes * add chain icon to all lists * refactor zetachain chains config type * use chain id as primary option instead of chain slug * refactor chain config type for multichain and essential dapps * refactor chain icon and chain select * rename chain-slug in chain_slug * change default radius of chain icon and update screenshots for multichain tests * change conditions for "isContract" and "isVerified" * test fixes * make shield optional for external chain entities * add chain icon to approvals lists * add stats page for multichain * add chain postfix to chart .png and .csv assets name * fix tests * sync verified contracts language filter and fix loading state in the search results page * fix sol2uml diagram and csv export * regcred from helmfile for review * link fixes * fix state for unknown address * fix incorrect chain in the context during spa transition to the same route on different chain * add links to stats widgets * add mixpanel to L2 demo values * show message of on-demand contract verification * fix screenshots --------- Co-authored-by: Nick Zenchik <[email protected]>
227 lines
7.5 KiB
TypeScript
227 lines
7.5 KiB
TypeScript
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 type { ContractAbiItemInput } from '../types';
|
|
|
|
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 'toolkit/components/buttons/ClearButton';
|
|
import { HOUR, SECOND } from 'toolkit/utils/consts';
|
|
|
|
import ContractMethodAddressButton from './ContractMethodAddressButton';
|
|
import ContractMethodFieldLabel from './ContractMethodFieldLabel';
|
|
import ContractMethodMultiplyButton from './ContractMethodMultiplyButton';
|
|
import useFormatFieldValue from './useFormatFieldValue';
|
|
import useValidateField from './useValidateField';
|
|
import { matchInt } from './utils';
|
|
|
|
const TIMESTAMP_BUTTON_REGEXP = /time|deadline|expiration|expiry/i;
|
|
|
|
interface Props {
|
|
data: ContractAbiItemInput;
|
|
hideLabel?: boolean;
|
|
path: string;
|
|
className?: string;
|
|
isDisabled: boolean;
|
|
isOptional?: boolean;
|
|
level: number;
|
|
}
|
|
|
|
const ContractMethodFieldInput = ({ data, hideLabel, path: name, className, isDisabled, isOptional: isOptionalProp, level }: Props) => {
|
|
const ref = React.useRef<HTMLInputElement>(null);
|
|
|
|
const [ intPower, setIntPower ] = React.useState<number>(18);
|
|
|
|
const isNativeCoin = data.fieldType === 'native_coin';
|
|
const isOptional = isOptionalProp || isNativeCoin;
|
|
|
|
const argTypeMatchInt = React.useMemo(() => matchInt(data.type), [ data.type ]);
|
|
const hasTimestampButton = React.useMemo(() => TIMESTAMP_BUTTON_REGEXP.test(data.name || ''), [ data.name ]);
|
|
const validate = useValidateField({ isOptional, argType: data.type, argTypeMatchInt });
|
|
const format = useFormatFieldValue({ argType: data.type, argTypeMatchInt });
|
|
|
|
const { control, setValue, getValues } = useFormContext();
|
|
const { field, fieldState } = useController({ control, name, rules: { validate } });
|
|
|
|
const nativeCoinRowBgColor = { _light: 'gray.100', _dark: 'gray.700' };
|
|
|
|
const hasMultiplyButton = argTypeMatchInt && Number(argTypeMatchInt.power) >= 64;
|
|
|
|
React.useImperativeHandle(field.ref, () => ref.current);
|
|
|
|
const handleChange = React.useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
|
|
const formattedValue = format(event.target.value);
|
|
field.onChange(formattedValue); // data send back to hook form
|
|
setValue(name, formattedValue); // UI state
|
|
}, [ field, name, setValue, format ]);
|
|
|
|
const handleClear = React.useCallback(() => {
|
|
setValue(name, '');
|
|
ref.current?.focus();
|
|
}, [ name, setValue ]);
|
|
|
|
const handleMultiplyButtonClick = React.useCallback((power: number) => {
|
|
const zeroes = Array(power).fill('0').join('');
|
|
const value = getValues(name);
|
|
const newValue = format(value ? value + zeroes : '1' + zeroes);
|
|
setValue(name, newValue, { shouldValidate: true });
|
|
}, [ format, getValues, name, setValue ]);
|
|
|
|
const handleMaxIntButtonClick = React.useCallback(() => {
|
|
if (!argTypeMatchInt) {
|
|
return;
|
|
}
|
|
|
|
const newValue = format(argTypeMatchInt.max.toString());
|
|
setValue(name, newValue, { shouldValidate: true });
|
|
}, [ format, name, setValue, argTypeMatchInt ]);
|
|
|
|
const handleAddressButtonClick = React.useCallback((address: string) => {
|
|
const newValue = format(address);
|
|
setValue(name, newValue, { shouldValidate: true });
|
|
}, [ format, name, setValue ]);
|
|
|
|
const handleTimestampButtonClick = React.useCallback(() => {
|
|
const newValue = format(String(Math.floor((Date.now() + HOUR) / SECOND)));
|
|
setValue(name, newValue, { shouldValidate: true });
|
|
}, [ format, name, setValue ]);
|
|
|
|
const handlePaste = React.useCallback((event: React.ClipboardEvent<HTMLInputElement>) => {
|
|
if (!argTypeMatchInt || !hasMultiplyButton) {
|
|
return;
|
|
}
|
|
|
|
const value = Number(event.clipboardData.getData('text'));
|
|
|
|
if (Object.is(value, NaN)) {
|
|
return;
|
|
}
|
|
|
|
const isFloat = Number.isFinite(value) && !Number.isInteger(value);
|
|
|
|
if (!isFloat) {
|
|
return;
|
|
}
|
|
|
|
event.preventDefault();
|
|
|
|
if (field.value) {
|
|
return;
|
|
}
|
|
|
|
const newValue = value * 10 ** intPower;
|
|
const formattedValue = format(newValue.toString());
|
|
|
|
field.onChange(formattedValue);
|
|
setValue(name, formattedValue, { shouldValidate: true });
|
|
window.setTimeout(() => {
|
|
// move cursor to the end of the input
|
|
// but we have to wait for the input to get the new value
|
|
const END_OF_INPUT = 100;
|
|
ref.current?.setSelectionRange(END_OF_INPUT, END_OF_INPUT);
|
|
}, 100);
|
|
}, [ argTypeMatchInt, hasMultiplyButton, intPower, format, field, setValue, name ]);
|
|
|
|
const error = fieldState.error;
|
|
|
|
const inputEndElement = (
|
|
<Flex alignItems="center">
|
|
<ClearButton onClick={ handleClear } disabled={ isDisabled } visible={ field.value !== undefined && field.value !== '' }/>
|
|
{ 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>
|
|
);
|
|
|
|
const inputProps = {
|
|
...field,
|
|
size: 'sm' as const,
|
|
bgColor: 'bg.primary',
|
|
onChange: handleChange,
|
|
required: !isOptional,
|
|
placeholder: data.type,
|
|
autoComplete: 'off',
|
|
'data-1p-ignore': true,
|
|
};
|
|
|
|
const getInputRef = React.useCallback((element: HTMLInputElement) => {
|
|
ref.current = element;
|
|
}, []);
|
|
|
|
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 }}
|
|
>
|
|
{ argTypeMatchInt ? (
|
|
<Input
|
|
{ ...inputProps }
|
|
{ ...(error ? { 'data-invalid': true } : {}) }
|
|
onPaste={ handlePaste }
|
|
disabled={ isDisabled }
|
|
asChild
|
|
>
|
|
<NumericFormat
|
|
thousandSeparator=" "
|
|
decimalScale={ 0 }
|
|
allowNegative={ !argTypeMatchInt.isUnsigned }
|
|
getInputRef={ getInputRef }
|
|
/>
|
|
</Input>
|
|
) : <Input { ...inputProps } ref={ ref as React.LegacyRef<HTMLInputElement> | undefined }/> }
|
|
</InputGroup>
|
|
</Field>
|
|
</Flex>
|
|
);
|
|
};
|
|
|
|
export default React.memo(chakra(ContractMethodFieldInput));
|