Commit 4c8f5ae9 authored by tom's avatar tom

api keys, custom abi, vizualize and csv export pages

parent add0e9bb
......@@ -4,12 +4,12 @@ import React from 'react';
import PageNextJs from 'nextjs/PageNextJs';
// const ApiKeys = dynamic(() => import('ui/pages/ApiKeys'), { ssr: false });
const ApiKeys = dynamic(() => import('ui/pages/ApiKeys'), { ssr: false });
const Page: NextPage = () => {
return (
<PageNextJs pathname="/account/api-key">
{ /* <ApiKeys/> */ }
<ApiKeys/>
</PageNextJs>
);
};
......
......@@ -4,12 +4,12 @@ import React from 'react';
import PageNextJs from 'nextjs/PageNextJs';
// const CustomAbi = dynamic(() => import('ui/pages/CustomAbi'), { ssr: false });
const CustomAbi = dynamic(() => import('ui/pages/CustomAbi'), { ssr: false });
const Page: NextPage = () => {
return (
<PageNextJs pathname="/account/custom-abi">
{ /* <CustomAbi/> */ }
<CustomAbi/>
</PageNextJs>
);
};
......
......@@ -3,12 +3,12 @@ import React from 'react';
import PageNextJs from 'nextjs/PageNextJs';
// import CsvExport from 'ui/pages/CsvExport';
import CsvExport from 'ui/pages/CsvExport';
const Page: NextPage = () => {
return (
<PageNextJs pathname="/csv-export">
{ /* <CsvExport/> */ }
<CsvExport/>
</PageNextJs>
);
};
......
......@@ -3,12 +3,12 @@ import React from 'react';
import PageNextJs from 'nextjs/PageNextJs';
// import Sol2Uml from 'ui/pages/Sol2Uml';
import Sol2Uml from 'ui/pages/Sol2Uml';
const Page: NextPage = () => {
return (
<PageNextJs pathname="/visualize/sol2uml">
{ /* <Sol2Uml/> */ }
<Sol2Uml/>
</PageNextJs>
);
};
......
import { createSystem, defaultConfig, defineConfig } from '@chakra-ui/react';
// TODO @tom2drum migrate this to the new recipe system
// import components from './components/index';
// import config from './config';
import { keyframes } from './foundations/animations';
import * as borders from './foundations/borders';
import breakpoints from './foundations/breakpoints';
......@@ -39,8 +36,6 @@ const customConfig = defineConfig({
},
},
},
// components,
// config,
});
export default createSystem(defaultConfig, customConfig);
import {
Alert,
Box,
Button,
Flex,
Heading,
Modal,
ModalCloseButton,
ModalContent,
PopoverBody,
PopoverContent,
PopoverTrigger,
StackDivider,
useDisclosure,
VStack,
} from '@chakra-ui/react';
import { Box, Flex, Separator, VStack } from '@chakra-ui/react';
import React from 'react';
import type { SmartContractExternalLibrary } from 'types/api/contract';
import useIsMobile from 'lib/hooks/useIsMobile';
import { apos } from 'lib/html-entities';
import Popover from 'ui/shared/chakra/Popover';
import Skeleton from 'ui/shared/chakra/Skeleton';
import { Alert } from 'toolkit/chakra/alert';
import { Button } from 'toolkit/chakra/button';
import { DialogBody, DialogContent, DialogHeader, DialogRoot } from 'toolkit/chakra/dialog';
import { Heading } from 'toolkit/chakra/heading';
import { PopoverRoot, PopoverBody, PopoverContent, PopoverTrigger } from 'toolkit/chakra/popover';
import { Skeleton } from 'toolkit/chakra/skeleton';
import { useDisclosure } from 'toolkit/hooks/useDisclosure';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import IconSvg from 'ui/shared/IconSvg';
......@@ -47,11 +37,11 @@ const Item = (data: SmartContractExternalLibrary) => {
};
const ContractExternalLibraries = ({ className, data, isLoading }: Props) => {
const { isOpen, onToggle, onClose } = useDisclosure();
const { open, onToggle, onOpenChange } = useDisclosure();
const isMobile = useIsMobile();
if (isLoading) {
return <Skeleton h={ 8 } w="150px" borderRadius="base"/>;
return <Skeleton loading h={ 8 } w="150px" borderRadius="base"/>;
}
if (data.length === 0) {
......@@ -62,17 +52,17 @@ const ContractExternalLibraries = ({ className, data, isLoading }: Props) => {
<Button
className={ className }
size="sm"
variant="outline"
colorScheme="gray"
variant="dropdown"
onClick={ onToggle }
isActive={ isOpen }
expanded={ open }
fontWeight={ 600 }
px={ 2 }
gap={ 0 }
aria-label="View external libraries"
>
<span>{ data.length } { data.length > 1 ? 'Libraries' : 'Library' } </span>
<IconSvg name="status/warning" boxSize={ 5 } color="orange.400" ml="2px"/>
<IconSvg name="arrows/east-mini" transform={ isOpen ? 'rotate(90deg)' : 'rotate(-90deg)' } transitionDuration="faster" boxSize={ 5 } ml={ 2 }/>
<IconSvg name="arrows/east-mini" transform={ open ? 'rotate(90deg)' : 'rotate(-90deg)' } transitionDuration="faster" boxSize={ 5 } ml={ 2 }/>
</Button>
);
......@@ -84,8 +74,8 @@ const ContractExternalLibraries = ({ className, data, isLoading }: Props) => {
Check the source code at the library address (if any) if you want to be sure in case if there is any library linked
</Alert>
<VStack
divider={ <StackDivider borderColor="border.divider"/> }
spacing={ 2 }
separator={ <Separator/> }
gap={ 2 }
mt={ 4 }
maxH={{ lg: '50vh' }}
overflowY="scroll"
......@@ -99,18 +89,20 @@ const ContractExternalLibraries = ({ className, data, isLoading }: Props) => {
return (
<>
{ button }
<Modal isOpen={ isOpen } onClose={ onClose } size="full">
<ModalContent paddingTop={ 4 }>
<ModalCloseButton/>
<DialogRoot open={ open } onOpenChange={ onOpenChange } size="full">
<DialogContent paddingTop={ 4 }>
<DialogHeader/>
<DialogBody>
{ content }
</ModalContent>
</Modal>
</DialogBody>
</DialogContent>
</DialogRoot>
</>
);
}
return (
<Popover isOpen={ isOpen } onClose={ onClose } placement="bottom-start" isLazy>
<PopoverRoot open={ open } onOpenChange={ onOpenChange }>
<PopoverTrigger>
{ button }
</PopoverTrigger>
......@@ -119,7 +111,7 @@ const ContractExternalLibraries = ({ className, data, isLoading }: Props) => {
{ content }
</PopoverBody>
</PopoverContent>
</Popover>
</PopoverRoot>
);
};
......
......@@ -6,15 +6,17 @@ import React from 'react';
import type { Address } from 'types/api/address';
import { route } from 'nextjs-routes';
import { getResourceKey } from 'lib/api/useApiQuery';
import useIsMobile from 'lib/hooks/useIsMobile';
import * as mixpanel from 'lib/mixpanel/index';
import getQueryParamString from 'lib/router/getQueryParamString';
import { IconButton } from 'toolkit/chakra/icon-button';
import { Link } from 'toolkit/chakra/link';
import { Skeleton } from 'toolkit/chakra/skeleton';
import { Tooltip } from 'toolkit/chakra/tooltip';
import IconSvg from 'ui/shared/IconSvg';
import NextLink from 'ui/shared/NextLink';
import useFetchTokens from '../utils/useFetchTokens';
import TokenSelectDesktop from './TokenSelectDesktop';
......@@ -64,27 +66,20 @@ const TokenSelect = ({ onClick }: Props) => {
<TokenSelectDesktop data={ data } isLoading={ tokensIsFetching === 1 }/>
}
<Tooltip content="Show all tokens">
<Box>
{ /* TODO @tom2drum: replace with Link */ }
<NextLink
href={{ pathname: '/address/[hash]', query: { hash: addressHash, tab: 'tokens' } }}
passHref
legacyBehavior
<Link
href={ route({ pathname: '/address/[hash]', query: { hash: addressHash, tab: 'tokens' } }) }
asChild
scroll={ false }
>
<IconButton
aria-label="Show all tokens"
variant="outline"
size="sm"
pl="6px"
pr="6px"
as="a"
onClick={ handleIconButtonClick }
>
<IconSvg name="wallet" boxSize={ 5 }/>
</IconButton>
</NextLink>
</Box>
</Link>
</Tooltip>
</Flex>
);
......
import {
Box,
Button,
} from '@chakra-ui/react';
import { Box } from '@chakra-ui/react';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import React, { useCallback } from 'react';
import type { SubmitHandler } from 'react-hook-form';
......@@ -13,11 +10,12 @@ import type { ResourceErrorAccount } from 'lib/api/resources';
import { resourceKey } from 'lib/api/resources';
import useApiFetch from 'lib/api/useApiFetch';
import getErrorMessage from 'lib/getErrorMessage';
import { Button } from 'toolkit/chakra/button';
import FormFieldText from 'ui/shared/forms/fields/FormFieldText';
type Props = {
data?: ApiKey;
onClose: () => void;
onOpenChange: ({ open }: { open: boolean }) => void;
setAlertVisible: (isAlertVisible: boolean) => void;
};
......@@ -28,7 +26,7 @@ type Inputs = {
const NAME_MAX_LENGTH = 255;
const ApiKeyForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
const ApiKeyForm: React.FC<Props> = ({ data, onOpenChange, setAlertVisible }) => {
const formApi = useForm<Inputs>({
mode: 'onTouched',
defaultValues: {
......@@ -73,7 +71,7 @@ const ApiKeyForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
return [ response, ...(prevData || []) ];
});
onClose();
onOpenChange({ open: false });
},
onError: (error: ResourceErrorAccount<ApiKeyErrors>) => {
const errorMap = error.payload?.errors;
......@@ -99,15 +97,14 @@ const ApiKeyForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
<FormFieldText<Inputs>
name="token"
placeholder="Auto-generated API key token"
isReadOnly
bgColor="dialog.bg"
readOnly
mb={ 5 }
/>
) }
<FormFieldText<Inputs>
name="name"
placeholder="Application name for API key (e.g Web3 project)"
isRequired
required
rules={{
maxLength: NAME_MAX_LENGTH,
}}
......@@ -118,8 +115,8 @@ const ApiKeyForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
<Button
size="lg"
type="submit"
isDisabled={ !formApi.formState.isDirty }
isLoading={ isPending }
disabled={ !formApi.formState.isDirty }
loading={ isPending }
>
{ data ? 'Save' : 'Generate API key' }
</Button>
......
......@@ -7,24 +7,24 @@ import FormModal from 'ui/shared/FormModal';
import ApiKeyForm from './ApiKeyForm';
type Props = {
isOpen: boolean;
onClose: () => void;
open: boolean;
onOpenChange: ({ open }: { open: boolean }) => void;
data?: ApiKey;
};
const ApiKeyModal: React.FC<Props> = ({ isOpen, onClose, data }) => {
const ApiKeyModal: React.FC<Props> = ({ open, onOpenChange, data }) => {
const title = data ? 'Edit API key' : 'New API key';
const text = !data ? 'Add an application name to identify your API key. Click the button below to auto-generate the associated key.' : '';
const [ isAlertVisible, setAlertVisible ] = useState(false);
const renderForm = useCallback(() => {
return <ApiKeyForm data={ data } onClose={ onClose } setAlertVisible={ setAlertVisible }/>;
}, [ data, onClose ]);
return <ApiKeyForm data={ data } onOpenChange={ onOpenChange } setAlertVisible={ setAlertVisible }/>;
}, [ data, onOpenChange ]);
return (
<FormModal<ApiKey>
isOpen={ isOpen }
onClose={ onClose }
open={ open }
onOpenChange={ onOpenChange }
title={ title }
text={ text }
renderForm={ renderForm }
......
import {
Table,
Thead,
Tbody,
Tr,
Th,
} from '@chakra-ui/react';
import React from 'react';
import type { ApiKeys, ApiKey } from 'types/api/account';
import { TableBody, TableColumnHeader, TableHeader, TableRoot, TableRow } from 'toolkit/chakra/table';
import ApiKeyTableItem from './ApiKeyTableItem';
interface Props {
......@@ -21,14 +16,14 @@ interface Props {
const ApiKeyTable = ({ data, isLoading, onDeleteClick, onEditClick, limit }: Props) => {
return (
<Table minWidth="600px">
<Thead>
<Tr>
<Th>{ `API key token (limit ${ limit } keys)` }</Th>
<Th width="108px"></Th>
</Tr>
</Thead>
<Tbody>
<TableRoot minWidth="600px">
<TableHeader>
<TableRow>
<TableColumnHeader>{ `API key token (limit ${ limit } keys)` }</TableColumnHeader>
<TableColumnHeader width="108px"></TableColumnHeader>
</TableRow>
</TableHeader>
<TableBody>
{ data?.map((item, index) => (
<ApiKeyTableItem
key={ item.api_key + (isLoading ? index : '') }
......@@ -38,8 +33,8 @@ const ApiKeyTable = ({ data, isLoading, onDeleteClick, onEditClick, limit }: Pro
onEditClick={ onEditClick }
/>
)) }
</Tbody>
</Table>
</TableBody>
</TableRoot>
);
};
......
import {
Tr,
Td,
} from '@chakra-ui/react';
import React, { useCallback } from 'react';
import type { ApiKey } from 'types/api/account';
import { TableCell, TableRow } from 'toolkit/chakra/table';
import ApiKeySnippet from 'ui/shared/ApiKeySnippet';
import TableItemActionButtons from 'ui/shared/TableItemActionButtons';
......@@ -27,14 +24,14 @@ const ApiKeyTableItem = ({ item, isLoading, onEditClick, onDeleteClick }: Props)
}, [ item, onDeleteClick ]);
return (
<Tr alignItems="top" key={ item.api_key }>
<Td>
<TableRow alignItems="top" key={ item.api_key }>
<TableCell>
<ApiKeySnippet apiKey={ item.api_key } name={ item.name } isLoading={ isLoading }/>
</Td>
<Td>
</TableCell>
<TableCell>
<TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick } isLoading={ isLoading }/>
</Td>
</Tr>
</TableCell>
</TableRow>
);
};
......
......@@ -9,12 +9,12 @@ import useApiFetch from 'lib/api/useApiFetch';
import DeleteModal from 'ui/shared/DeleteModal';
type Props = {
isOpen: boolean;
onClose: () => void;
open: boolean;
onOpenChange: ({ open }: { open: boolean }) => void;
data: ApiKey;
};
const DeleteApiKeyModal: React.FC<Props> = ({ isOpen, onClose, data }) => {
const DeleteApiKeyModal: React.FC<Props> = ({ open, onOpenChange, data }) => {
const queryClient = useQueryClient();
const apiFetch = useApiFetch();
......@@ -39,8 +39,8 @@ const DeleteApiKeyModal: React.FC<Props> = ({ isOpen, onClose, data }) => {
return (
<DeleteModal
isOpen={ isOpen }
onClose={ onClose }
open={ open }
onOpenChange={ onOpenChange }
title="Remove API key"
renderContent={ renderText }
mutationFn={ mutationFn }
......
import { Alert, Button, chakra, Flex } from '@chakra-ui/react';
import { chakra, Flex } from '@chakra-ui/react';
import React from 'react';
import type { SubmitHandler } from 'react-hook-form';
import { useForm, FormProvider } from 'react-hook-form';
......@@ -11,7 +11,9 @@ import buildUrl from 'lib/api/buildUrl';
import type { ResourceName } from 'lib/api/resources';
import dayjs from 'lib/date/dayjs';
import downloadBlob from 'lib/downloadBlob';
import useToast from 'lib/hooks/useToast';
import { Alert } from 'toolkit/chakra/alert';
import { Button } from 'toolkit/chakra/button';
import { toaster } from 'toolkit/chakra/toaster';
import ReCaptcha from 'ui/shared/reCaptcha/ReCaptcha';
import useReCaptcha from 'ui/shared/reCaptcha/useReCaptcha';
......@@ -35,7 +37,6 @@ const CsvExportForm = ({ hash, resource, filterType, filterValue, fileNameTempla
},
});
const { handleSubmit, formState } = formApi;
const toast = useToast();
const recaptcha = useReCaptcha();
const onFormSubmit: SubmitHandler<FormFields> = React.useCallback(async(data) => {
......@@ -73,17 +74,13 @@ const CsvExportForm = ({ hash, resource, filterType, filterValue, fileNameTempla
downloadBlob(blob, fileName);
} catch (error) {
toast({
position: 'top-right',
toaster.error({
title: 'Error',
description: (error as Error)?.message || 'Something went wrong. Try again later.',
status: 'error',
variant: 'subtle',
isClosable: true,
});
}
}, [ recaptcha, resource, hash, exportType, filterType, filterValue, fileNameTemplate, toast ]);
}, [ recaptcha, resource, hash, exportType, filterType, filterValue, fileNameTemplate ]);
if (!config.services.reCaptchaV2.siteKey) {
return (
......@@ -110,9 +107,9 @@ const CsvExportForm = ({ hash, resource, filterType, filterValue, fileNameTempla
size="lg"
type="submit"
mt={ 8 }
isLoading={ formState.isSubmitting }
loading={ formState.isSubmitting }
loadingText="Download"
isDisabled={ Boolean(formState.errors.from || formState.errors.to) }
disabled={ Boolean(formState.errors.from || formState.errors.to) }
>
Download
</Button>
......
......@@ -38,12 +38,11 @@ const CsvExportFormField = ({ formApi, name }: Props) => {
return (
<FormFieldText<FormFields, typeof name>
name={ name }
type="date"
max={ dayjs().format('YYYY-MM-DD') }
inputProps={{ type: 'date', max: dayjs().format('YYYY-MM-DD') }}
placeholder={ capitalize(name) }
isRequired
required
rules={{ validate }}
size={{ base: 'md', lg: 'lg' }}
size="xl"
maxW={{ base: 'auto', lg: '220px' }}
/>
);
......
......@@ -3,7 +3,7 @@ import React, { useCallback } from 'react';
import type { CustomAbi } from 'types/api/account';
import Skeleton from 'ui/shared/chakra/Skeleton';
import { Skeleton } from 'toolkit/chakra/skeleton';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile';
import TableItemActionButtons from 'ui/shared/TableItemActionButtons';
......@@ -33,7 +33,7 @@ const CustomAbiListItem = ({ item, isLoading, onEditClick, onDeleteClick }: Prop
fontWeight="600"
isLoading={ isLoading }
/>
<Skeleton fontSize="sm" color="text_secondary" mt={ 0.5 } ml={ 8 } display="inline-block" isLoaded={ !isLoading }>
<Skeleton textStyle="sm" color="text.secondary" mt={ 0.5 } ml={ 8 } display="inline-block" loading={ isLoading }>
<span>{ item.name }</span>
</Skeleton>
</Box>
......
import {
Table,
Thead,
Tbody,
Tr,
Th,
} from '@chakra-ui/react';
import React from 'react';
import type { CustomAbis, CustomAbi } from 'types/api/account';
import { TableBody, TableColumnHeader, TableHeader, TableRoot, TableRow } from 'toolkit/chakra/table';
import CustomAbiTableItem from './CustomAbiTableItem';
interface Props {
......@@ -20,14 +15,14 @@ interface Props {
const CustomAbiTable = ({ data, isLoading, onDeleteClick, onEditClick }: Props) => {
return (
<Table minWidth="600px">
<Thead>
<Tr>
<Th>ABI for Smart contract address (0x...)</Th>
<Th width="108px"></Th>
</Tr>
</Thead>
<Tbody>
<TableRoot minWidth="600px">
<TableHeader>
<TableRow>
<TableColumnHeader>ABI for Smart contract address (0x...)</TableColumnHeader>
<TableColumnHeader width="108px"></TableColumnHeader>
</TableRow>
</TableHeader>
<TableBody>
{ data?.map((item, index) => (
<CustomAbiTableItem
key={ item.id + (isLoading ? String(index) : '') }
......@@ -37,8 +32,8 @@ const CustomAbiTable = ({ data, isLoading, onDeleteClick, onEditClick }: Props)
onEditClick={ onEditClick }
/>
)) }
</Tbody>
</Table>
</TableBody>
</TableRoot>
);
};
......
import {
Tr,
Td,
Box,
} from '@chakra-ui/react';
import { Box } from '@chakra-ui/react';
import React, { useCallback } from 'react';
import type { CustomAbi } from 'types/api/account';
import Skeleton from 'ui/shared/chakra/Skeleton';
import { Skeleton } from 'toolkit/chakra/skeleton';
import { TableCell, TableRow } from 'toolkit/chakra/table';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import TableItemActionButtons from 'ui/shared/TableItemActionButtons';
......@@ -29,23 +26,23 @@ const CustomAbiTableItem = ({ item, isLoading, onEditClick, onDeleteClick }: Pro
}, [ item, onDeleteClick ]);
return (
<Tr alignItems="top" key={ item.id }>
<Td>
<TableRow alignItems="top" key={ item.id }>
<TableCell>
<Box maxW="100%">
<AddressEntity
address={ item.contract_address }
fontWeight="600"
isLoading={ isLoading }
/>
<Skeleton fontSize="sm" color="text_secondary" mt={ 0.5 } ml={ 8 } display="inline-block" isLoaded={ !isLoading }>
<Skeleton textStyle="sm" color="text.secondary" mt={ 0.5 } ml={ 8 } display="inline-block" loading={ isLoading }>
<span>{ item.name }</span>
</Skeleton>
</Box>
</Td>
<Td>
</TableCell>
<TableCell>
<TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick } isLoading={ isLoading }/>
</Td>
</Tr>
</TableCell>
</TableRow>
);
};
......
......@@ -9,12 +9,12 @@ import useApiFetch from 'lib/api/useApiFetch';
import DeleteModal from 'ui/shared/DeleteModal';
type Props = {
isOpen: boolean;
onClose: () => void;
open: boolean;
onOpenChange: ({ open }: { open: boolean }) => void;
data: CustomAbi;
};
const DeleteCustomAbiModal: React.FC<Props> = ({ isOpen, onClose, data }) => {
const DeleteCustomAbiModal: React.FC<Props> = ({ open, onOpenChange, data }) => {
const queryClient = useQueryClient();
const apiFetch = useApiFetch();
......@@ -40,8 +40,8 @@ const DeleteCustomAbiModal: React.FC<Props> = ({ isOpen, onClose, data }) => {
return (
<DeleteModal
isOpen={ isOpen }
onClose={ onClose }
open={ open }
onOpenChange={ onOpenChange }
title="Remove custom ABI"
renderContent={ renderText }
mutationFn={ mutationFn }
......
import { Box, Button, Link, Text, useDisclosure } from '@chakra-ui/react';
import { Box, Text } from '@chakra-ui/react';
import React, { useCallback, useState } from 'react';
import type { ApiKey } from 'types/api/account';
......@@ -6,12 +6,15 @@ import type { ApiKey } from 'types/api/account';
import useApiQuery from 'lib/api/useApiQuery';
import { space } from 'lib/html-entities';
import { API_KEY } from 'stubs/account';
import { Button } from 'toolkit/chakra/button';
import { Link } from 'toolkit/chakra/link';
import { Skeleton } from 'toolkit/chakra/skeleton';
import { useDisclosure } from 'toolkit/hooks/useDisclosure';
import ApiKeyModal from 'ui/apiKey/ApiKeyModal/ApiKeyModal';
import ApiKeyListItem from 'ui/apiKey/ApiKeyTable/ApiKeyListItem';
import ApiKeyTable from 'ui/apiKey/ApiKeyTable/ApiKeyTable';
import DeleteApiKeyModal from 'ui/apiKey/DeleteApiKeyModal';
import AccountPageDescription from 'ui/shared/AccountPageDescription';
import Skeleton from 'ui/shared/chakra/Skeleton';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import PageTitle from 'ui/shared/Page/PageTitle';
import useRedirectForInvalidAuthToken from 'ui/snippets/auth/useRedirectForInvalidAuthToken';
......@@ -37,9 +40,9 @@ const ApiKeysPage: React.FC = () => {
apiKeyModalProps.onOpen();
}, [ apiKeyModalProps ]);
const onApiKeyModalClose = useCallback(() => {
setApiKeyModalData(undefined);
apiKeyModalProps.onClose();
const onApiKeyModalOpenChange = useCallback(({ open }: { open: boolean }) => {
!open && setApiKeyModalData(undefined);
apiKeyModalProps.onOpenChange({ open });
}, [ apiKeyModalProps ]);
const onDeleteClick = useCallback((data: ApiKey) => {
......@@ -47,9 +50,9 @@ const ApiKeysPage: React.FC = () => {
deleteModalProps.onOpen();
}, [ deleteModalProps ]);
const onDeleteModalClose = useCallback(() => {
setDeleteModalData(undefined);
deleteModalProps.onClose();
const onDeleteModalOpenChange = useCallback(({ open }: { open: boolean }) => {
!open && setDeleteModalData(undefined);
deleteModalProps.onOpenChange({ open });
}, [ deleteModalProps ]);
const description = (
......@@ -99,7 +102,7 @@ const ApiKeysPage: React.FC = () => {
marginTop={ 8 }
flexDir={{ base: 'column', lg: 'row' }}
alignItems={{ base: 'start', lg: 'center' }}
isLoaded={ !isPlaceholderData }
loading={ isPlaceholderData }
display="inline-flex"
columnGap={ 5 }
rowGap={ 5 }
......@@ -107,18 +110,18 @@ const ApiKeysPage: React.FC = () => {
<Button
size="lg"
onClick={ apiKeyModalProps.onOpen }
isDisabled={ !canAdd }
disabled={ !canAdd }
>
Add API key
</Button>
{ !canAdd && (
<Text fontSize="sm" variant="secondary">
<Text fontSize="sm" color="text.secondary">
{ `You have added the maximum number of API keys (${ DATA_LIMIT }). Contact us to request additional keys.` }
</Text>
) }
</Skeleton>
<ApiKeyModal { ...apiKeyModalProps } onClose={ onApiKeyModalClose } data={ apiKeyModalData }/>
{ deleteModalData && <DeleteApiKeyModal { ...deleteModalProps } onClose={ onDeleteModalClose } data={ deleteModalData }/> }
<ApiKeyModal open={ apiKeyModalProps.open } onOpenChange={ onApiKeyModalOpenChange } data={ apiKeyModalData }/>
{ deleteModalData && <DeleteApiKeyModal open={ deleteModalProps.open } onOpenChange={ onDeleteModalOpenChange } data={ deleteModalData }/> }
</>
);
})();
......
import { Box, Button, useDisclosure } from '@chakra-ui/react';
import { Box } from '@chakra-ui/react';
import React, { useCallback, useState } from 'react';
import type { CustomAbi } from 'types/api/account';
import useApiQuery from 'lib/api/useApiQuery';
import { CUSTOM_ABI } from 'stubs/account';
import { Button } from 'toolkit/chakra/button';
import { Skeleton } from 'toolkit/chakra/skeleton';
import { useDisclosure } from 'toolkit/hooks/useDisclosure';
import CustomAbiModal from 'ui/customAbi/CustomAbiModal/CustomAbiModal';
import CustomAbiListItem from 'ui/customAbi/CustomAbiTable/CustomAbiListItem';
import CustomAbiTable from 'ui/customAbi/CustomAbiTable/CustomAbiTable';
import DeleteCustomAbiModal from 'ui/customAbi/DeleteCustomAbiModal';
import AccountPageDescription from 'ui/shared/AccountPageDescription';
import Skeleton from 'ui/shared/chakra/Skeleton';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import PageTitle from 'ui/shared/Page/PageTitle';
import useRedirectForInvalidAuthToken from 'ui/snippets/auth/useRedirectForInvalidAuthToken';
......@@ -34,9 +36,9 @@ const CustomAbiPage: React.FC = () => {
customAbiModalProps.onOpen();
}, [ customAbiModalProps ]);
const onCustomAbiModalClose = useCallback(() => {
setCustomAbiModalData(undefined);
customAbiModalProps.onClose();
const onCustomAbiModalOpenChange = useCallback(({ open }: { open: boolean }) => {
!open && setCustomAbiModalData(undefined);
customAbiModalProps.onOpenChange({ open });
}, [ customAbiModalProps ]);
const onDeleteClick = useCallback((data: CustomAbi) => {
......@@ -44,9 +46,9 @@ const CustomAbiPage: React.FC = () => {
deleteModalProps.onOpen();
}, [ deleteModalProps ]);
const onDeleteModalClose = useCallback(() => {
setDeleteModalData(undefined);
deleteModalProps.onClose();
const onDeleteModalOpenChange = useCallback(({ open }: { open: boolean }) => {
!open && setDeleteModalData(undefined);
deleteModalProps.onOpenChange({ open });
}, [ deleteModalProps ]);
const description = (
......@@ -88,7 +90,7 @@ const CustomAbiPage: React.FC = () => {
<>
{ description }
{ Boolean(data?.length) && list }
<Skeleton mt={ 8 } isLoaded={ !isPlaceholderData } display="inline-block">
<Skeleton mt={ 8 } loading={ isPlaceholderData } display="inline-block">
<Button
size="lg"
onClick={ customAbiModalProps.onOpen }
......@@ -96,8 +98,8 @@ const CustomAbiPage: React.FC = () => {
Add custom ABI
</Button>
</Skeleton>
<CustomAbiModal { ...customAbiModalProps } onClose={ onCustomAbiModalClose } data={ customAbiModalData }/>
{ deleteModalData && <DeleteCustomAbiModal { ...deleteModalProps } onClose={ onDeleteModalClose } data={ deleteModalData }/> }
<CustomAbiModal open={ customAbiModalProps.open } onOpenChange={ onCustomAbiModalOpenChange } data={ customAbiModalData }/>
{ deleteModalData && <DeleteCustomAbiModal open={ deleteModalProps.open } onOpenChange={ onDeleteModalOpenChange } data={ deleteModalData }/> }
</>
);
})();
......
import { Box, HStack, Flex, useColorModeValue } from '@chakra-ui/react';
import { Box, HStack, Flex } from '@chakra-ui/react';
import React from 'react';
import Skeleton from 'ui/shared/chakra/Skeleton';
import { Skeleton } from 'toolkit/chakra/skeleton';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import IconSvg from 'ui/shared/IconSvg';
......@@ -13,17 +13,17 @@ interface Props {
const ApiKeySnippet = ({ apiKey, name, isLoading }: Props) => {
return (
<HStack spacing={ 2 } alignItems="start">
<IconSvg name="key" boxSize={ 6 } color={ useColorModeValue('gray.500', 'gray.400') } isLoading={ isLoading }/>
<HStack gap={ 2 } alignItems="start">
<IconSvg name="key" boxSize={ 6 } color={{ _light: 'gray.500', _dark: 'gray.400' }} isLoading={ isLoading }/>
<Box>
<Flex alignItems={{ base: 'flex-start', lg: 'center' }}>
<Skeleton isLoaded={ !isLoading } display="inline-block" fontWeight={ 600 } mr={ 1 }>
<Skeleton loading={ isLoading } display="inline-block" fontWeight={ 600 } mr={ 1 }>
<span>{ apiKey }</span>
</Skeleton>
<CopyToClipboard text={ apiKey } isLoading={ isLoading }/>
</Flex>
{ name && (
<Skeleton isLoaded={ !isLoading } display="inline-block" fontSize="sm" color="text_secondary" mt={ 1 }>
<Skeleton loading={ isLoading } display="inline-block" fontSize="sm" color="text_secondary" mt={ 1 }>
<span>{ name }</span>
</Skeleton>
) }
......
......@@ -18,15 +18,15 @@ type AdData = {
};
};
// const MOCK: AdData = {
// ad: {
// url: 'https://unsplash.com/s/photos/cute-kitten',
// thumbnail: 'https://placekitten.com/40/40',
// name: 'All about kitties',
// description_short: 'To see millions picture of cute kitties',
// cta_button: 'click here',
// },
// };
const MOCK: AdData = {
ad: {
url: 'https://unsplash.com/s/photos/cute-kitten',
thumbnail: 'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/gnosis.svg',
name: 'All about kitties',
description_short: 'To see millions picture of cute kitties',
cta_button: 'click here',
},
};
const CoinzillaTextAd = ({ className }: { className?: string }) => {
const [ adData, setAdData ] = React.useState<AdData | null>(null);
......@@ -44,7 +44,7 @@ const CoinzillaTextAd = ({ className }: { className?: string }) => {
}
})
.finally(() => {
// setAdData(MOCK);
setAdData(MOCK);
setIsLoading(false);
});
}
......@@ -87,7 +87,7 @@ const CoinzillaTextAd = ({ className }: { className?: string }) => {
src={ adData.ad.thumbnail }
width="20px"
height="20px"
mb="2px"
verticalAlign="text-bottom"
mr={ 1 }
display="inline-block"
alt=""
......
import { chakra, Tooltip, useColorModeValue } from '@chakra-ui/react';
import { chakra } from '@chakra-ui/react';
import React from 'react';
import type * as visualizer from '@blockscout/visualizer-types';
......@@ -8,6 +8,7 @@ import type { ResourceError } from 'lib/api/resources';
import useApiQuery from 'lib/api/useApiQuery';
import throwOnAbsentParamError from 'lib/errors/throwOnAbsentParamError';
import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError';
import { Tooltip } from 'toolkit/chakra/tooltip';
import ContentLoader from 'ui/shared/ContentLoader';
interface Props {
......@@ -53,7 +54,6 @@ const Sol2UmlDiagram = ({ addressHash }: Props) => {
});
const imgUrl = `data:image/svg+xml;base64,${ umlQuery.data?.svg }`;
const imgFilter = useColorModeValue('invert(0)', 'invert(1)');
const handleClick = React.useCallback(() => {
const image = new Image();
......@@ -76,13 +76,13 @@ const Sol2UmlDiagram = ({ addressHash }: Props) => {
}
return (
<Tooltip label="Click on image to zoom" placement="top">
<Tooltip content="Click on image to zoom" positioning={{ placement: 'top' }}>
<chakra.img
src={ imgUrl }
alt={ `Contract ${ contractQuery.data.name } UML diagram` }
onClick={ handleClick }
cursor="pointer"
filter={ imgFilter }
filter={{ _light: 'invert(0)', _dark: 'invert(1)' }}
/>
</Tooltip>
);
......
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