Commit cced383d authored by tom's avatar tom

watchlist resource

parent 375a8b46
......@@ -12,6 +12,14 @@ export const RESOURCES = {
custom_abi: {
path: '/api/account/v1/user/custom_abis/:id?',
},
watchlist: {
path: '/api/account/v1/user/watchlist/:id?',
},
// DEPRECATED
old_api: {
path: '/api',
},
};
export const resourceKey = (x: keyof typeof RESOURCES) => x;
......
......@@ -6,7 +6,7 @@ import useFetch from 'lib/hooks/useFetch';
import buildUrl from './buildUrl';
import type { RESOURCES, ResourcePayload, ResourceError } from './resources';
interface Params {
export interface Params {
pathParams?: Record<string, string>;
queryParams?: Record<string, string>;
fetchParams?: Pick<FetchParams, 'body' | 'method'>;
......
import type { UseQueryOptions } from '@tanstack/react-query';
import { useQuery } from '@tanstack/react-query';
import useFetch from 'lib/hooks/useFetch';
import buildUrl from './buildUrl';
import type { RESOURCES, ResourcePayload, ResourceError } from './resources';
import type { Params as ApiFetchParams } from './useApiFetch';
import useApiFetch from './useApiFetch';
interface Params<R extends keyof typeof RESOURCES> {
interface Params<R extends keyof typeof RESOURCES> extends ApiFetchParams {
queryOptions?: Omit<UseQueryOptions<unknown, ResourceError, ResourcePayload<R>>, 'queryKey' | 'queryFn'>;
}
export default function useApiQuery<R extends keyof typeof RESOURCES>(
resource: R,
{ queryOptions }: Params<R> = {},
{ queryOptions, pathParams, queryParams, fetchParams }: Params<R> = {},
) {
const fetch = useFetch();
const apiFetch = useApiFetch();
return useQuery<unknown, ResourceError, ResourcePayload<R>>([ resource ], async() => {
const url = buildUrl(resource);
return fetch(url, { credentials: 'include' });
return apiFetch(resource, { pathParams, queryParams, fetchParams });
}, queryOptions);
}
......@@ -67,6 +67,12 @@ export interface WatchlistAddress {
address?: AddressParam;
}
export interface WatchlistTokensResponse {
message: string;
result?: Array<unknown>;
status: string;
}
export interface WatchlistAddressNew {
addressName: string;
notificationSettings: NotificationSettings;
......
......@@ -2,10 +2,11 @@ import { Box, Button, Skeleton, useDisclosure } from '@chakra-ui/react';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import React, { useCallback, useState } from 'react';
import type { WatchlistAddress, WatchlistTokensResponse } from 'types/api/account';
import type { TWatchlist, TWatchlistItem } from 'types/client/account';
import { QueryKeys } from 'types/client/accountQueries';
import useFetch from 'lib/hooks/useFetch';
import { resourceKey } from 'lib/api/resources';
import useApiFetch from 'lib/api/useApiFetch';
import useIsMobile from 'lib/hooks/useIsMobile';
import useRedirectForInvalidAuthToken from 'lib/hooks/useRedirectForInvalidAuthToken';
import AccountPageDescription from 'ui/shared/AccountPageDescription';
......@@ -20,14 +21,38 @@ import WatchListItem from 'ui/watchlist/WatchlistTable/WatchListItem';
import WatchlistTable from 'ui/watchlist/WatchlistTable/WatchlistTable';
const WatchList: React.FC = () => {
const { data, isLoading, isError } =
useQuery<unknown, unknown, TWatchlist>([ QueryKeys.watchlist ], async() => fetch('/node-api/account/watchlist/get-with-tokens'));
const apiFetch = useApiFetch();
const { data, isLoading, isError } = useQuery<unknown, unknown, TWatchlist>([ resourceKey('watchlist') ], async() => {
try {
const watchlistAddresses = await apiFetch<'watchlist', Array<WatchlistAddress>>('watchlist');
if (!Array.isArray(watchlistAddresses)) {
throw Error();
}
const watchlistTokens = await Promise.all(watchlistAddresses.map(({ address }) => {
if (!address?.hash) {
return Promise.resolve(0);
}
return apiFetch<'old_api', WatchlistTokensResponse>('old_api', { queryParams: { address: address.hash, module: 'account', action: 'tokenlist' } })
.then((response) => {
if ('result' in response && Array.isArray(response.result)) {
return response.result.length;
}
return 0;
});
}));
return watchlistAddresses.map((item, index) => ({ ...item, tokens_count: watchlistTokens[index] }));
} catch (error) {
return error;
}
});
const queryClient = useQueryClient();
const addressModalProps = useDisclosure();
const deleteModalProps = useDisclosure();
const isMobile = useIsMobile();
const fetch = useFetch();
useRedirectForInvalidAuthToken();
const [ addressModalData, setAddressModalData ] = useState<TWatchlistItem>();
......@@ -44,7 +69,7 @@ const WatchList: React.FC = () => {
}, [ addressModalProps ]);
const onAddOrEditSuccess = useCallback(async() => {
await queryClient.refetchQueries([ QueryKeys.watchlist ]);
await queryClient.refetchQueries([ resourceKey('watchlist') ]);
setAddressModalData(undefined);
addressModalProps.onClose();
}, [ addressModalProps, queryClient ]);
......@@ -60,7 +85,7 @@ const WatchList: React.FC = () => {
}, [ deleteModalProps ]);
const onDeleteSuccess = useCallback(async() => {
queryClient.setQueryData([ QueryKeys.watchlist ], (prevData: TWatchlist | undefined) => {
queryClient.setQueryData([ resourceKey('watchlist') ], (prevData: TWatchlist | undefined) => {
return prevData?.filter((item) => item.id !== deleteModalData?.id);
});
}, [ deleteModalData?.id, queryClient ]);
......
......@@ -12,9 +12,9 @@ import { useForm, Controller } from 'react-hook-form';
import type { WatchlistErrors } from 'types/api/account';
import type { TWatchlistItem } from 'types/client/account';
import type { ResourceError } 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 CheckboxInput from 'ui/shared/CheckboxInput';
......@@ -83,7 +83,7 @@ const AddressForm: React.FC<Props> = ({ data, onSuccess, setAlertVisible, isAdd
mode: 'onTouched',
});
const fetch = useFetch();
const apiFetch = useApiFetch();
function updateWatchlist(formData: Inputs) {
const body = {
......@@ -96,11 +96,14 @@ const AddressForm: React.FC<Props> = ({ data, onSuccess, setAlertVisible, isAdd
};
if (!isAdd && data) {
// edit address
return fetch<TWatchlistItem, WatchlistErrors>(`/node-api/account/watchlist/${ data.id }`, { method: 'PUT', body });
return apiFetch('watchlist', {
pathParams: { id: data?.id || '' },
fetchParams: { method: 'PUT', body },
});
} else {
// add address
return fetch<TWatchlistItem, WatchlistErrors>('/node-api/account/watchlist', { method: 'POST', body });
return apiFetch('watchlist', { fetchParams: { method: 'POST', body } });
}
}
......@@ -109,13 +112,14 @@ const AddressForm: React.FC<Props> = ({ data, onSuccess, setAlertVisible, isAdd
await onSuccess();
setPending(false);
},
onError: (e: ErrorType<WatchlistErrors>) => {
onError: (error: ResourceError<{errors: WatchlistErrors}>) => {
setPending(false);
if (e?.error?.address_hash || e?.error?.name) {
e?.error?.address_hash && setError('address', { type: 'custom', message: getErrorMessage(e.error, 'address_hash') });
e?.error?.name && setError('tag', { type: 'custom', message: getErrorMessage(e.error, 'name') });
} else if (e?.error?.watchlist_id) {
setError('address', { type: 'custom', message: getErrorMessage(e.error, 'watchlist_id') });
const errorMap = error.payload?.errors;
if (errorMap?.address_hash || errorMap?.name) {
errorMap?.address_hash && setError('address', { type: 'custom', message: getErrorMessage(errorMap, 'address_hash') });
errorMap?.name && setError('tag', { type: 'custom', message: getErrorMessage(errorMap, 'name') });
} else if (errorMap?.watchlist_id) {
setError('address', { type: 'custom', message: getErrorMessage(errorMap, 'watchlist_id') });
} else {
setAlertVisible(true);
}
......
......@@ -3,7 +3,7 @@ import React, { useCallback } from 'react';
import type { TWatchlistItem } from 'types/client/account';
import useFetch from 'lib/hooks/useFetch';
import useApiFetch from 'lib/api/useApiFetch';
import useIsMobile from 'lib/hooks/useIsMobile';
import DeleteModal from 'ui/shared/DeleteModal';
......@@ -16,11 +16,14 @@ type Props = {
const DeleteAddressModal: React.FC<Props> = ({ isOpen, onClose, onSuccess, data }) => {
const isMobile = useIsMobile();
const fetch = useFetch();
const apiFetch = useApiFetch();
const mutationFn = useCallback(() => {
return fetch(`/node-api/account/watchlist/${ data?.id }`, { method: 'DELETE' });
}, [ data?.id, fetch ]);
return apiFetch('custom_abi', {
pathParams: { id: data.id },
fetchParams: { method: 'DELETE' },
});
}, [ data?.id, apiFetch ]);
const address = data?.address_hash;
......
......@@ -4,7 +4,7 @@ import React, { useCallback, useState } from 'react';
import type { TWatchlistItem } from 'types/client/account';
import useFetch from 'lib/hooks/useFetch';
import useApiFetch from 'lib/api/useApiFetch';
import useToast from 'lib/hooks/useToast';
import AccountListItemMobile from 'ui/shared/AccountListItemMobile';
import TableItemActionButtons from 'ui/shared/TableItemActionButtons';
......@@ -29,7 +29,7 @@ const WatchListItem = ({ item, onEditClick, onDeleteClick }: Props) => {
}, [ item, onDeleteClick ]);
const errorToast = useToast();
const fetch = useFetch();
const apiFetch = useApiFetch();
const showErrorToast = useCallback(() => {
errorToast({
......@@ -61,7 +61,10 @@ const WatchListItem = ({ item, onEditClick, onDeleteClick }: Props) => {
setSwitchDisabled(true);
const body = { ...item, notification_methods: { email: !notificationEnabled } };
setNotificationEnabled(prevState => !prevState);
return fetch(`/node-api/account/watchlist/${ item.id }`, { method: 'PUT', body });
return apiFetch('watchlist', {
pathParams: { id: item.id },
fetchParams: { method: 'PUT', body },
});
}, {
onError: () => {
showErrorToast();
......
......@@ -9,7 +9,7 @@ import React, { useCallback, useState } from 'react';
import type { TWatchlistItem } from 'types/client/account';
import useFetch from 'lib/hooks/useFetch';
import useApiFetch from 'lib/api/useApiFetch';
import useToast from 'lib/hooks/useToast';
import TableItemActionButtons from 'ui/shared/TableItemActionButtons';
import TruncatedTextTooltip from 'ui/shared/TruncatedTextTooltip';
......@@ -34,7 +34,7 @@ const WatchlistTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
}, [ item, onDeleteClick ]);
const errorToast = useToast();
const fetch = useFetch();
const apiFetch = useApiFetch();
const showErrorToast = useCallback(() => {
errorToast({
......@@ -66,7 +66,10 @@ const WatchlistTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
setSwitchDisabled(true);
const body = { ...item, notification_methods: { email: !notificationEnabled } };
setNotificationEnabled(prevState => !prevState);
return fetch(`/node-api/account/watchlist/${ item.id }`, { method: 'PUT', body });
return apiFetch('watchlist', {
pathParams: { id: item.id },
fetchParams: { method: 'PUT', body },
});
}, {
onError: () => {
showErrorToast();
......
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