Commit cced383d authored by tom's avatar tom

watchlist resource

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