Commit 70bea3dd authored by tom's avatar tom

crud operations

parent daf122d9
...@@ -4,13 +4,14 @@ import { ...@@ -4,13 +4,14 @@ import {
FormControl, FormControl,
FormLabel, FormLabel,
Input, Input,
Textarea,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
// import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useMutation, useQueryClient } from '@tanstack/react-query';
import React, { useCallback, useEffect } from 'react'; import React, { useCallback, useEffect } from 'react';
import type { ControllerRenderProps } from 'react-hook-form'; import type { ControllerRenderProps, SubmitHandler } from 'react-hook-form';
import { useForm, Controller } from 'react-hook-form'; import { useForm, Controller } from 'react-hook-form';
import type { CustomAbi } from 'types/api/account'; import type { CustomAbi, CustomAbis } from 'types/api/account';
type Props = { type Props = {
data?: CustomAbi; data?: CustomAbi;
...@@ -20,59 +21,61 @@ type Props = { ...@@ -20,59 +21,61 @@ type Props = {
type Inputs = { type Inputs = {
contract_address_hash: string; contract_address_hash: string;
name: string; name: string;
abi: string;
} }
// idk, maybe there is no limit // idk, maybe there is no limit
const NAME_MAX_LENGTH = 100; const NAME_MAX_LENGTH = 100;
const CustomAbiForm: React.FC<Props> = ({ data }) => { const CustomAbiForm: React.FC<Props> = ({ data, onClose }) => {
const { control, formState: { errors }, setValue } = useForm<Inputs>(); const { control, formState: { errors }, setValue, handleSubmit } = useForm<Inputs>();
// const queryClient = useQueryClient(); const queryClient = useQueryClient();
useEffect(() => { useEffect(() => {
setValue('contract_address_hash', data?.contract_address_hash || ''); setValue('contract_address_hash', data?.contract_address_hash || '');
setValue('name', data?.name || ''); setValue('name', data?.name || '');
setValue('abi', JSON.stringify(data?.abi) || '');
}, [ setValue, data ]); }, [ setValue, data ]);
// const updateApiKey = (data: Inputs) => { const customAbiKey = (data: Inputs & { id?: number }) => {
// const body = JSON.stringify({ name: data.name }); const body = JSON.stringify({ name: data.name, contract_address_hash: data.contract_address_hash, abi: data.abi });
// if (!data.token) { if (!data.id) {
// return fetch('/api/account/api-keys', { method: 'POST', body }); return fetch('/api/account/custom-abis', { method: 'POST', body });
// } }
// return fetch(`/api/account/api-keys/${ data.token }`, { method: 'PUT', body }); return fetch(`/api/account/custom-abis/${ data.id }`, { method: 'PUT', body });
// }; };
// const mutation = useMutation(updateApiKey, { const mutation = useMutation(customAbiKey, {
// onSuccess: async(data) => { onSuccess: async(data) => {
// const response: CustomAbi = await data.json(); const response: CustomAbi = await data.json();
// queryClient.setQueryData([ 'api-keys' ], (prevData: CustomAbis | undefined) => { queryClient.setQueryData([ 'custom-abis' ], (prevData: CustomAbis | undefined) => {
// const isExisting = prevData && prevData.some((item) => item.api_key === response.api_key); const isExisting = prevData && prevData.some((item) => item.id === response.id);
// if (isExisting) { if (isExisting) {
// return prevData.map((item) => { return prevData.map((item) => {
// if (item.api_key === response.api_key) { if (item.id === response.id) {
// return response; return response;
// } }
// return item; return item;
// }); });
// } }
// return [ ...(prevData || []), response ]; return [ ...(prevData || []), response ];
// }); });
// onClose(); onClose();
// }, },
// // eslint-disable-next-line no-console // eslint-disable-next-line no-console
// onError: console.error, onError: console.error,
// }); });
// const onSubmit: SubmitHandler<Inputs> = useCallback((data) => { const onSubmit: SubmitHandler<Inputs> = useCallback((formData) => {
// mutation.mutate(data); mutation.mutate({ ...formData, id: data?.id });
// }, [ mutation ]); }, [ mutation, data ]);
const renderContractAddressInput = useCallback(({ field }: {field: ControllerRenderProps<Inputs, 'contract_address_hash'>}) => { const renderContractAddressInput = useCallback(({ field }: {field: ControllerRenderProps<Inputs, 'contract_address_hash'>}) => {
return ( return (
...@@ -99,16 +102,29 @@ const CustomAbiForm: React.FC<Props> = ({ data }) => { ...@@ -99,16 +102,29 @@ const CustomAbiForm: React.FC<Props> = ({ data }) => {
); );
}, [ errors ]); }, [ errors ]);
const renderAbiInput = useCallback(({ field }: {field: ControllerRenderProps<Inputs, 'abi'>}) => {
return (
<FormControl variant="floating" id="abi" isRequired>
<Textarea
{ ...field }
size="lg"
isInvalid={ Boolean(errors.abi) }
/>
<FormLabel>{ `Custom ABI [{...}] (JSON format)` }</FormLabel>
</FormControl>
);
}, [ errors ]);
return ( return (
<> <>
<Box marginBottom={ 5 }> <Box>
<Controller <Controller
name="contract_address_hash" name="contract_address_hash"
control={ control } control={ control }
render={ renderContractAddressInput } render={ renderContractAddressInput }
/> />
</Box> </Box>
<Box marginBottom={ 8 }> <Box marginTop={ 5 }>
<Controller <Controller
name="name" name="name"
control={ control } control={ control }
...@@ -118,13 +134,20 @@ const CustomAbiForm: React.FC<Props> = ({ data }) => { ...@@ -118,13 +134,20 @@ const CustomAbiForm: React.FC<Props> = ({ data }) => {
render={ renderNameInput } render={ renderNameInput }
/> />
</Box> </Box>
<Box marginTop={ 5 }>
<Controller
name="abi"
control={ control }
render={ renderAbiInput }
/>
</Box>
<Box marginTop={ 8 }> <Box marginTop={ 8 }>
<Button <Button
size="lg" size="lg"
variant="primary" variant="primary"
// onClick={ handleSubmit(onSubmit) } onClick={ handleSubmit(onSubmit) }
disabled={ Object.keys(errors).length > 0 } disabled={ Object.keys(errors).length > 0 }
// isLoading={ mutation.isLoading } isLoading={ mutation.isLoading }
> >
{ data ? 'Save' : 'Create custom ABI' } { data ? 'Save' : 'Create custom ABI' }
</Button> </Button>
......
import {
Table,
Thead,
Tbody,
Tr,
Th,
TableContainer,
} from '@chakra-ui/react';
import React from 'react';
import type { CustomAbis, CustomAbi } from 'types/api/account';
import CustomAbiTableItem from './CustomAbiTableItem';
interface Props {
data: CustomAbis;
onEditClick: (item: CustomAbi) => void;
onDeleteClick: (item: CustomAbi) => void;
}
const CustomAbiTable = ({ data, onDeleteClick, onEditClick }: Props) => {
return (
<TableContainer width="100%">
<Table variant="simple" minWidth="600px">
<Thead>
<Tr>
<Th>ABI for Smart contract address (0x...)</Th>
<Th width="108px"></Th>
</Tr>
</Thead>
<Tbody>
{ data.map((item) => (
<CustomAbiTableItem
item={ item }
key={ item.id }
onDeleteClick={ onDeleteClick }
onEditClick={ onEditClick }
/>
)) }
</Tbody>
</Table>
</TableContainer>
);
};
export default React.memo(CustomAbiTable);
import {
Tr,
Td,
HStack,
Text,
} from '@chakra-ui/react';
import React, { useCallback } from 'react';
import type { CustomAbi } from 'types/api/account';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import DeleteButton from 'ui/shared/DeleteButton';
import EditButton from 'ui/shared/EditButton';
interface Props {
item: CustomAbi;
onEditClick: (item: CustomAbi) => void;
onDeleteClick: (item: CustomAbi) => void;
}
const CustomAbiTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
const onItemEditClick = useCallback(() => {
return onEditClick(item);
}, [ item, onEditClick ]);
const onItemDeleteClick = useCallback(() => {
return onDeleteClick(item);
}, [ item, onDeleteClick ]);
return (
<Tr alignItems="top" key={ item.id }>
<Td>
<HStack>
<Text fontSize="md" fontWeight={ 600 }>{ item.contract_address_hash }</Text>
<CopyToClipboard text={ item.contract_address_hash }/>
</HStack>
<Text fontSize="sm" marginTop={ 0.5 } variant="secondary">{ item.name }</Text>
</Td>
<Td>
<HStack spacing={ 6 }>
<EditButton onClick={ onItemEditClick }/>
<DeleteButton onClick={ onItemDeleteClick }/>
</HStack>
</Td>
</Tr>
);
};
export default React.memo(CustomAbiTableItem);
import { Text } from '@chakra-ui/react';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import React, { useCallback } from 'react';
import type { CustomAbi, CustomAbis } from 'types/api/account';
import DeleteModal from 'ui/shared/DeleteModal';
type Props = {
isOpen: boolean;
onClose: () => void;
data: CustomAbi;
}
const DeleteCustomAbiModal: React.FC<Props> = ({ isOpen, onClose, data }) => {
const queryClient = useQueryClient();
const deleteApiKey = () => {
return fetch(`/api/account/custom-abis/${ data.id }`, { method: 'DELETE' });
};
const mutation = useMutation(deleteApiKey, {
onSuccess: async() => {
queryClient.setQueryData([ 'custom-abis' ], (prevData: CustomAbis | undefined) => {
return prevData?.filter((item) => item.id !== data.id);
});
onClose();
},
// eslint-disable-next-line no-console
onError: console.error,
});
const onDelete = useCallback(() => {
mutation.mutate(data);
}, [ data, mutation ]);
const renderText = useCallback(() => {
return (
<Text display="flex">Custom ABI for<Text fontWeight="600" whiteSpace="pre">{ ` "${ data.name || 'name' }" ` }</Text>will be deleted</Text>
);
}, [ data.name ]);
return (
<DeleteModal
isOpen={ isOpen }
onClose={ onClose }
onDelete={ onDelete }
title="Remove custom ABI"
renderContent={ renderText }
pending={ mutation.isLoading }
/>
);
};
export default React.memo(DeleteCustomAbiModal);
...@@ -4,20 +4,18 @@ import React, { useCallback, useState } from 'react'; ...@@ -4,20 +4,18 @@ import React, { useCallback, useState } from 'react';
import type { CustomAbi, CustomAbis } from 'types/api/account'; import type { CustomAbi, CustomAbis } from 'types/api/account';
// import ApiKeyTable from 'ui/apiKey/ApiKeyTable/ApiKeyTable';
// import DeleteApiKeyModal from 'ui/apiKey/DeleteApiKeyModal';
import CustomAbiModal from 'ui/customAbi/CustomAbiModal/CustomAbiModal'; import CustomAbiModal from 'ui/customAbi/CustomAbiModal/CustomAbiModal';
import CustomAbiTable from 'ui/customAbi/CustomAbiTable/CustomAbiTable';
import DeleteCustomAbiModal from 'ui/customAbi/DeleteCustomAbiModal';
import AccountPageHeader from 'ui/shared/AccountPageHeader'; import AccountPageHeader from 'ui/shared/AccountPageHeader';
import Page from 'ui/shared/Page/Page'; import Page from 'ui/shared/Page/Page';
const DATA_LIMIT = 3;
const CustomAbiPage: React.FC = () => { const CustomAbiPage: React.FC = () => {
const customAbiModalProps = useDisclosure(); const customAbiModalProps = useDisclosure();
// const deleteModalProps = useDisclosure(); const deleteModalProps = useDisclosure();
const [ customAbiModalData, setCustomAbiModalData ] = useState<CustomAbi>(); const [ customAbiModalData, setCustomAbiModalData ] = useState<CustomAbi>();
// const [ deleteModalData, setDeleteModalData ] = useState<CustomAbi>(); const [ deleteModalData, setDeleteModalData ] = useState<CustomAbi>();
const { data, isLoading, isError } = useQuery<unknown, unknown, CustomAbis>([ 'custom-abis' ], async() => { const { data, isLoading, isError } = useQuery<unknown, unknown, CustomAbis>([ 'custom-abis' ], async() => {
const response = await fetch('/api/account/custom-abis'); const response = await fetch('/api/account/custom-abis');
...@@ -27,56 +25,48 @@ const CustomAbiPage: React.FC = () => { ...@@ -27,56 +25,48 @@ const CustomAbiPage: React.FC = () => {
return response.json(); return response.json();
}); });
// const onEditClick = useCallback((data: CustomAbi) => { const onEditClick = useCallback((data: CustomAbi) => {
// setCustomAbiModalData(data); setCustomAbiModalData(data);
// customAbiModalProps.onOpen(); customAbiModalProps.onOpen();
// }, [ customAbiModalProps ]); }, [ customAbiModalProps ]);
const onCustomAbiModalClose = useCallback(() => { const onCustomAbiModalClose = useCallback(() => {
setCustomAbiModalData(undefined); setCustomAbiModalData(undefined);
customAbiModalProps.onClose(); customAbiModalProps.onClose();
}, [ customAbiModalProps ]); }, [ customAbiModalProps ]);
// const onDeleteClick = useCallback((data: CustomAbi) => { const onDeleteClick = useCallback((data: CustomAbi) => {
// setDeleteModalData(data); setDeleteModalData(data);
// deleteModalProps.onOpen(); deleteModalProps.onOpen();
// }, [ deleteModalProps ]); }, [ deleteModalProps ]);
// const onDeleteModalClose = useCallback(() => { const onDeleteModalClose = useCallback(() => {
// setDeleteModalData(undefined); setDeleteModalData(undefined);
// deleteModalProps.onClose(); deleteModalProps.onClose();
// }, [ deleteModalProps ]); }, [ deleteModalProps ]);
const content = (() => { const content = (() => {
if (isLoading || isError) { if (isLoading || isError) {
return <Spinner/>; return <Spinner/>;
} }
const canAdd = data.length < DATA_LIMIT;
return ( return (
<> <>
{ /* { data.length > 0 && ( { data.length > 0 && (
<ApiKeyTable <CustomAbiTable
data={ data } data={ data }
onDeleteClick={ onDeleteClick } onDeleteClick={ onDeleteClick }
onEditClick={ onEditClick } onEditClick={ onEditClick }
limit={ DATA_LIMIT }
/> />
) } */ } ) }
<HStack marginTop={ 8 } spacing={ 5 }> <HStack marginTop={ 8 } spacing={ 5 }>
<Button <Button
variant="primary" variant="primary"
size="lg" size="lg"
onClick={ customAbiModalProps.onOpen } onClick={ customAbiModalProps.onOpen }
disabled={ !canAdd }
> >
Add custom ABI Add custom ABI
</Button> </Button>
{ !canAdd && (
<Text fontSize="sm" variant="secondary">
{ `You have added the maximum number of API keys (${ DATA_LIMIT }). Contact us to request additional keys.` }
</Text>
) }
</HStack> </HStack>
</> </>
); );
...@@ -92,7 +82,7 @@ const CustomAbiPage: React.FC = () => { ...@@ -92,7 +82,7 @@ const CustomAbiPage: React.FC = () => {
{ content } { content }
</Box> </Box>
<CustomAbiModal { ...customAbiModalProps } onClose={ onCustomAbiModalClose } data={ customAbiModalData }/> <CustomAbiModal { ...customAbiModalProps } onClose={ onCustomAbiModalClose } data={ customAbiModalData }/>
{ /* { deleteModalData && <DeleteApiKeyModal { ...deleteModalProps } onClose={ onDeleteModalClose } data={ deleteModalData }/> } */ } { deleteModalData && <DeleteCustomAbiModal { ...deleteModalProps } onClose={ onDeleteModalClose } data={ deleteModalData }/> }
</Page> </Page>
); );
}; };
......
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