Skip to content

Commit

Permalink
feat: add contract address input field
Browse files Browse the repository at this point in the history
  • Loading branch information
marslavish committed Sep 19, 2024
1 parent 7c859c0 commit 029d260
Show file tree
Hide file tree
Showing 3 changed files with 196 additions and 4 deletions.
4 changes: 2 additions & 2 deletions examples/chain-template/components/contract/CodeIdField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { useCodeDetails } from '@/hooks';
import { useChainStore } from '@/contexts';
import { CodeInfo, isValidCodeId, resolvePermission } from '@/utils';

type Status = {
export type InputStatus = {
state: 'init' | 'loading' | 'success' | 'error';
message?: string;
};
Expand All @@ -25,7 +25,7 @@ export const CodeIdField = ({
readonly?: boolean;
defaultCodeId?: string;
}) => {
const [status, setStatus] = useState<Status>({ state: 'init' });
const [status, setStatus] = useState<InputStatus>({ state: 'init' });

const { selectedChain } = useChainStore();
const { address } = useChain(selectedChain);
Expand Down
148 changes: 148 additions & 0 deletions examples/chain-template/components/contract/ContractAddressField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import {
Box,
Combobox,
Icon,
Spinner,
Text,
TextProps,
} from '@interchain-ui/react';
import { InputField } from './InputField';
import { useContractInfo, useMyContracts } from '@/hooks';
import { shortenAddress, validateContractAddress } from '@/utils';
import { useEffect, useState } from 'react';
import { InputStatus } from './CodeIdField';
import { useChainStore } from '@/contexts';
import { useChain } from '@cosmos-kit/react';

type StatusDisplay = {
icon?: React.ReactNode;
text?: string;
textColor?: TextProps['color'];
};

const displayStatus = (status: InputStatus) => {
const statusMap: Record<InputStatus['state'], StatusDisplay> = {
loading: {
icon: <Spinner size="$lg" color="$textSecondary" />,
text: 'Checking contract address...',
textColor: '$textSecondary',
},

success: {
icon: <Icon name="checkboxCircle" size="18px" color="$textSuccess" />,
text: 'Valid contract address',
textColor: '$text',
},

error: {
icon: <Icon name="errorWarningLine" size="18px" color="$textDanger" />,
text: status.message || 'Invalid contract address',
textColor: '$textDanger',
},

init: {},
};

return statusMap[status.state];
};

type ContractAddressFieldProps = {
width?: string;
initialAddress?: string;
setContractAddress?: (address: string) => void;
};

export const ContractAddressField = ({
width = '560px',
initialAddress,
setContractAddress,
}: ContractAddressFieldProps) => {
const [input, setInput] = useState('');
const [status, setStatus] = useState<InputStatus>({ state: 'init' });

const { selectedChain } = useChainStore();
const { chain } = useChain(selectedChain);
const { refetch: fetchContractInfo } = useContractInfo({
contractAddress: input,
enabled: false,
});
const { data: myContracts = [] } = useMyContracts();

useEffect(() => {
if (initialAddress) {
setInput(initialAddress);
}
}, [initialAddress]);

useEffect(() => {
setStatus({ state: 'init' });
setContractAddress?.('');

if (input.length) {
const error = validateContractAddress(input, chain.bech32_prefix);

if (error) {
return setStatus({ state: 'error', message: error });
}

setStatus({ state: 'loading' });

const timer = setTimeout(() => {
fetchContractInfo().then(({ data }) => {
if (!data) {
return setStatus({
state: 'error',
message: 'This contract does not exist',
});
}

setStatus({ state: 'success' });
setContractAddress?.(input);
});
}, 500);

return () => clearTimeout(timer);
}
}, [input, fetchContractInfo, chain.bech32_prefix]);

const { icon, text, textColor } = displayStatus(status);

return (
<InputField title="Contract Address">
<Combobox
openOnFocus
allowsCustomValue
inputValue={input}
onInputChange={(input) => {
setInput(input);
}}
onSelectionChange={(value) => {
if (value) setInput(value as string);
}}
styleProps={{ width }}
>
{myContracts.map(({ address, contractInfo }) => (
<Combobox.Item key={address} textValue={address}>
<Box transform="translateY(2px)">
<Text>
{`${shortenAddress(address, 18)} (`}
<Text as="span" fontWeight="600">
{`${contractInfo?.label || 'Unnamed'}`}
</Text>
{')'}
</Text>
</Box>
</Combobox.Item>
))}
</Combobox>
{status.state !== 'init' && (
<Box display="flex" alignItems="center" gap="6px">
{icon}
<Text color={textColor} fontSize="12px">
{text}
</Text>
</Box>
)}
</InputField>
);
};
48 changes: 46 additions & 2 deletions examples/chain-template/components/contract/QueryTab.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Box, Text } from '@interchain-ui/react';
import { ContractAddressField } from './ContractAddressField';
import { useEffect, useRef, useState } from 'react';

type QueryTabProps = {
show: boolean;
Expand All @@ -11,9 +13,51 @@ export const QueryTab = ({
initialAddress,
clearInitAddress,
}: QueryTabProps) => {
const [contractAddress, setContractAddress] = useState('');
const [fieldWidth, setFieldWidth] = useState('560px');
const containerRef = useRef<HTMLDivElement>(null);

useEffect(() => {
const updateWidth = () => {
const newWidth = containerRef.current?.clientWidth;
if (newWidth) {
setFieldWidth(`${newWidth}px`);
}
};

updateWidth();
const timeoutId = setTimeout(updateWidth, 0);

window.addEventListener('resize', updateWidth);

return () => {
clearTimeout(timeoutId);
window.removeEventListener('resize', updateWidth);
};
}, [show]);

return (
<Box display={show ? 'block' : 'none'}>
<Text>Query Contract</Text>
<Box
display={show ? 'flex' : 'none'}
maxWidth="560px"
mx="auto"
flexDirection="column"
gap="20px"
ref={containerRef}
>
<Text
fontSize="24px"
fontWeight="500"
color="$blackAlpha600"
textAlign="center"
>
Query Contract
</Text>
<ContractAddressField
width={fieldWidth}
initialAddress={initialAddress}
setContractAddress={setContractAddress}
/>
</Box>
);
};

0 comments on commit 029d260

Please sign in to comment.