Commit 375a8b46 authored by tom's avatar tom

custom abi resources

parent 010916f1
import { compile } from 'path-to-regexp';
import appConfig from 'configs/app/config';
import { RESOURCES } from './resources';
export default function buildUrl(resource: keyof typeof RESOURCES, params?: Record<string, string>) {
export default function buildUrl(
resource: keyof typeof RESOURCES,
pathParams?: Record<string, string>,
queryParams?: Record<string, string>,
) {
const base = appConfig.host === 'localhost' ? appConfig.baseUrl : appConfig.api.endpoint;
const path = appConfig.host === 'localhost' ?
'/proxy' + appConfig.api.basePath + RESOURCES[resource].path :
appConfig.api.basePath + RESOURCES[resource].path;
const url = new URL(path, base);
const url = new URL(compile(path)(pathParams), base);
params && Object.entries(params).forEach(([ key, value ]) => {
queryParams && Object.entries(queryParams).forEach(([ key, value ]) => {
url.searchParams.append(key, value);
});
......
import type { UserInfo } from 'types/api/account';
import type { UserInfo, CustomAbis } from 'types/api/account';
import type { CsrfData } from 'types/client/account';
export const RESOURCES = {
// account
user_info: {
path: '/api/account/v1/user/info',
queryKey: 'user_info',
},
csrf: {
path: '/api/account/v1/get_csrf',
queryKey: 'csrf',
},
custom_abi: {
path: '/api/account/v1/user/custom_abis/:id?',
},
};
......@@ -16,11 +18,13 @@ export const resourceKey = (x: keyof typeof RESOURCES) => x;
export type ResourcePayload<Q extends keyof typeof RESOURCES> =
Q extends 'user_info' ? UserInfo :
Q extends 'csrf' ? CsrfData : never;
Q extends 'csrf' ? CsrfData :
Q extends 'custom_abi' ? CustomAbis :
never;
export interface ResourceError {
error?: {
status?: number;
statusText?: string;
};
export interface ResourceError<T = unknown> {
error?: T;
payload?: T;
status: Response['status'];
statusText: Response['statusText'];
}
import React from 'react';
import type { Params as FetchParams } from 'lib/hooks/useFetch';
import useFetch from 'lib/hooks/useFetch';
import buildUrl from './buildUrl';
import type { RESOURCES, ResourcePayload, ResourceError } from './resources';
interface Params {
pathParams?: Record<string, string>;
queryParams?: Record<string, string>;
fetchParams?: Pick<FetchParams, 'body' | 'method'>;
}
export default function useApiFetch() {
const fetch = useFetch();
return React.useCallback(<R extends keyof typeof RESOURCES, SuccessType = ResourcePayload<R>, ErrorType = ResourceError>(
resource: R,
{ pathParams, queryParams, fetchParams }: Params = {},
) => {
const url = buildUrl(resource, pathParams, queryParams);
return fetch<SuccessType, ErrorType>(url, { credentials: 'include', ...fetchParams });
}, [ fetch ]);
}
......@@ -4,15 +4,10 @@ import React from 'react';
import type { CsrfData } from 'types/client/account';
import type { ResourceError } from 'lib/api/resources';
import { resourceKey, RESOURCES } from 'lib/api/resources';
export interface ErrorType<T> {
error?: T;
status: Response['status'];
statusText: Response['statusText'];
}
interface Params {
export interface Params {
method?: RequestInit['method'];
body?: Record<string, unknown>;
credentials?: RequestCredentials;
......@@ -22,7 +17,7 @@ export default function useFetch() {
const queryClient = useQueryClient();
const { token } = queryClient.getQueryData<CsrfData>([ resourceKey('csrf') ]) || {};
return React.useCallback(<Success, Error>(path: string, params?: Params): Promise<Success | ErrorType<Error>> => {
return React.useCallback(<Success, Error>(path: string, params?: Params): Promise<Success | ResourceError<Error>> => {
const reqParams = {
...params,
body: params?.method && ![ 'GET', 'HEAD' ].includes(params.method) ?
......@@ -41,7 +36,9 @@ export default function useFetch() {
return response.json().then(
(jsonError) => Promise.reject({
// DEPRECATED
error: jsonError as Error,
payload: jsonError as Error,
status: response.status,
statusText: response.statusText,
}),
......
......@@ -12,11 +12,11 @@ import type { ControllerRenderProps, SubmitHandler } from 'react-hook-form';
import { useForm, Controller } from 'react-hook-form';
import type { CustomAbi, CustomAbis, CustomAbiErrors } from 'types/api/account';
import { QueryKeys } from 'types/client/accountQueries';
import type { ResourceError } from 'lib/api/resources';
import { resourceKey } from 'lib/api/resources';
import useApiFetch from 'lib/api/useApiFetch';
import getErrorMessage from 'lib/getErrorMessage';
import type { ErrorType } from 'lib/hooks/useFetch';
import useFetch from 'lib/hooks/useFetch';
import { ADDRESS_REGEXP } from 'lib/validations/address';
import AddressInput from 'ui/shared/AddressInput';
import InputPlaceholder from 'ui/shared/InputPlaceholder';
......@@ -46,16 +46,19 @@ const CustomAbiForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
});
const queryClient = useQueryClient();
const fetch = useFetch();
const apiFetch = useApiFetch();
const customAbiKey = (data: Inputs & { id?: number }) => {
const body = { name: data.name, contract_address_hash: data.contract_address_hash, abi: data.abi };
if (!data.id) {
return fetch<CustomAbi, CustomAbiErrors>('/node-api/account/custom-abis', { method: 'POST', body });
return apiFetch<'custom_abi', CustomAbi, CustomAbiErrors>('custom_abi', { fetchParams: { method: 'POST', body } });
}
return fetch<CustomAbi, CustomAbiErrors>(`/node-api/account/custom-abis/${ data.id }`, { method: 'PUT', body });
return apiFetch<'custom_abi', CustomAbi, CustomAbiErrors>('custom_abi', {
pathParams: { id: String(data.id) },
fetchParams: { method: 'PUT', body },
});
};
const formBackgroundColor = useColorModeValue('white', 'gray.900');
......@@ -63,7 +66,7 @@ const CustomAbiForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
const mutation = useMutation(customAbiKey, {
onSuccess: (data) => {
const response = data as unknown as CustomAbi;
queryClient.setQueryData([ QueryKeys.customAbis ], (prevData: CustomAbis | undefined) => {
queryClient.setQueryData([ resourceKey('custom_abi') ], (prevData: CustomAbis | undefined) => {
const isExisting = prevData && prevData.some((item) => item.id === response.id);
if (isExisting) {
......@@ -81,13 +84,14 @@ const CustomAbiForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
onClose();
},
onError: (e: ErrorType<CustomAbiErrors>) => {
if (e?.error?.address_hash || e?.error?.name || e?.error?.abi) {
e?.error?.address_hash && setError('contract_address_hash', { type: 'custom', message: getErrorMessage(e.error, 'address_hash') });
e?.error?.name && setError('name', { type: 'custom', message: getErrorMessage(e.error, 'name') });
e?.error?.abi && setError('abi', { type: 'custom', message: getErrorMessage(e.error, 'abi') });
} else if (e?.error?.identity_id) {
setError('contract_address_hash', { type: 'custom', message: getErrorMessage(e.error, 'identity_id') });
onError: (error: ResourceError<{ errors: CustomAbiErrors}>) => {
const errorMap = error.payload?.errors;
if (errorMap?.address_hash || errorMap?.name || errorMap?.abi) {
errorMap?.address_hash && setError('contract_address_hash', { type: 'custom', message: getErrorMessage(errorMap, 'address_hash') });
errorMap?.name && setError('name', { type: 'custom', message: getErrorMessage(errorMap, 'name') });
errorMap?.abi && setError('abi', { type: 'custom', message: getErrorMessage(errorMap, 'abi') });
} else if (errorMap?.identity_id) {
setError('contract_address_hash', { type: 'custom', message: getErrorMessage(errorMap, 'identity_id') });
} else {
setAlertVisible(true);
}
......
......@@ -3,8 +3,9 @@ import { useQueryClient } from '@tanstack/react-query';
import React, { useCallback } from 'react';
import type { CustomAbi, CustomAbis } from 'types/api/account';
import { QueryKeys } from 'types/client/accountQueries';
import { resourceKey } from 'lib/api/resources';
import useApiFetch from 'lib/api/useApiFetch';
import DeleteModal from 'ui/shared/DeleteModal';
type Props = {
......@@ -16,13 +17,17 @@ type Props = {
const DeleteCustomAbiModal: React.FC<Props> = ({ isOpen, onClose, data }) => {
const queryClient = useQueryClient();
const apiFetch = useApiFetch();
const mutationFn = useCallback(() => {
return fetch(`/node-api/account/custom-abis/${ data.id }`, { method: 'DELETE' });
}, [ data ]);
return apiFetch('custom_abi', {
pathParams: { id: String(data.id) },
fetchParams: { method: 'DELETE' },
});
}, [ apiFetch, data.id ]);
const onSuccess = useCallback(async() => {
queryClient.setQueryData([ QueryKeys.customAbis ], (prevData: CustomAbis | undefined) => {
queryClient.setQueryData([ resourceKey('custom_abi') ], (prevData: CustomAbis | undefined) => {
return prevData?.filter((item) => item.id !== data.id);
});
}, [ data, queryClient ]);
......
import { Box, Button, HStack, Skeleton, useDisclosure } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import React, { useCallback, useState } from 'react';
import type { CustomAbi, CustomAbis } from 'types/api/account';
import { QueryKeys } from 'types/client/accountQueries';
import type { CustomAbi } from 'types/api/account';
import useFetch from 'lib/hooks/useFetch';
import useApiQuery from 'lib/api/useApiQuery';
import useIsMobile from 'lib/hooks/useIsMobile';
import useRedirectForInvalidAuthToken from 'lib/hooks/useRedirectForInvalidAuthToken';
import CustomAbiModal from 'ui/customAbi/CustomAbiModal/CustomAbiModal';
......@@ -23,14 +21,12 @@ const CustomAbiPage: React.FC = () => {
const customAbiModalProps = useDisclosure();
const deleteModalProps = useDisclosure();
const isMobile = useIsMobile();
const fetch = useFetch();
useRedirectForInvalidAuthToken();
const [ customAbiModalData, setCustomAbiModalData ] = useState<CustomAbi>();
const [ deleteModalData, setDeleteModalData ] = useState<CustomAbi>();
const { data, isLoading, isError } = useQuery<unknown, unknown, CustomAbis>([ QueryKeys.customAbis ], async() =>
await fetch('/node-api/account/custom-abis'));
const { data, isLoading, isError } = useApiQuery('custom_abi');
const onEditClick = useCallback((data: CustomAbi) => {
setCustomAbiModalData(data);
......
......@@ -7737,6 +7737,11 @@ path-parse@^1.0.7:
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
path-to-regexp@^6.2.1:
version "6.2.1"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.1.tgz#d54934d6798eb9e5ef14e7af7962c945906918e5"
integrity sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==
path-type@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
......
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