Commit 04ca8bcc authored by tom's avatar tom

pass csrf token for state changing requests

parent 13ae1199
import * as Sentry from '@sentry/nextjs';
export interface ErrorType<T> {
error?: T;
status: Response['status'];
statusText: Response['statusText'];
}
export default function clientFetch<Success, Error>(path: string, init?: RequestInit): Promise<Success | ErrorType<Error>> {
return fetch(path, init).then(response => {
if (!response.ok) {
return response.json().then(
(jsonError) => Promise.reject({
error: jsonError as Error,
status: response.status,
statusText: response.statusText,
}),
() => {
const error = {
status: response.status,
statusText: response.statusText,
};
Sentry.captureException(new Error('Client fetch failed'), { extra: error, tags: { source: 'fetch' } });
return Promise.reject(error);
},
);
} else {
return response.json() as Promise<Success>;
}
});
}
import * as Sentry from '@sentry/nextjs';
import { useQueryClient } from '@tanstack/react-query';
import React from 'react';
import type { CsrfData } from 'types/client/account';
export interface ErrorType<T> {
error?: T;
status: Response['status'];
statusText: Response['statusText'];
}
interface Params {
method?: RequestInit['method'];
body?: Record<string, unknown>;
}
export default function useFetch() {
const queryClient = useQueryClient();
const { token } = queryClient.getQueryData<CsrfData>([ 'csrf' ]) || {};
return React.useCallback(<Success, Error>(path: string, params?: Params): Promise<Success | ErrorType<Error>> => {
const reqParams = {
...params,
body: params?.method && ![ 'GET', 'HEAD' ].includes(params.method) ?
JSON.stringify({ ...params?.body, _csrf_token: token }) :
undefined,
};
return fetch(path, reqParams).then(response => {
if (!response.ok) {
return response.json().then(
(jsonError) => Promise.reject({
error: jsonError as Error,
status: response.status,
statusText: response.statusText,
}),
() => {
const error = {
status: response.status,
statusText: response.statusText,
};
Sentry.captureException(new Error('Client fetch failed'), { extra: error, tags: { source: 'fetch' } });
return Promise.reject(error);
},
);
} else {
return response.json() as Promise<Success>;
}
});
}, [ token ]);
}
...@@ -2,7 +2,7 @@ import { useQuery } from '@tanstack/react-query'; ...@@ -2,7 +2,7 @@ import { useQuery } from '@tanstack/react-query';
import type { UserInfo } from 'types/api/account'; import type { UserInfo } from 'types/api/account';
import fetch from 'lib/client/fetch'; import useFetch from 'lib/hooks/useFetch';
interface Error { interface Error {
error?: { error?: {
...@@ -12,6 +12,8 @@ interface Error { ...@@ -12,6 +12,8 @@ interface Error {
} }
export default function useFetchProfileInfo() { export default function useFetchProfileInfo() {
const fetch = useFetch();
return useQuery<unknown, Error, UserInfo>([ 'profile' ], async() => { return useQuery<unknown, Error, UserInfo>([ 'profile' ], async() => {
return fetch('/api/account/profile'); return fetch('/api/account/profile');
}, { }, {
......
import type { NextApiRequest, NextApiResponse } from 'next';
import fetchFactory from 'lib/api/fetch';
import getUrlWithNetwork from 'lib/api/getUrlWithNetwork';
export default async function csrfHandler(_req: NextApiRequest, res: NextApiResponse) {
const url = getUrlWithNetwork(_req, `api/account/v1/get_csrf`);
const fetch = fetchFactory(_req);
const response = await fetch(url);
if (response.status === 200) {
const token = response.headers.get('x-bs-account-csrf');
res.status(200).json({ token });
return;
}
res.status(500).json({ statusText: response.statusText, status: response.status });
}
...@@ -3,3 +3,7 @@ import type { WatchlistAddress } from '../api/account'; ...@@ -3,3 +3,7 @@ import type { WatchlistAddress } from '../api/account';
export type TWatchlistItem = WatchlistAddress & {tokens_count: number}; export type TWatchlistItem = WatchlistAddress & {tokens_count: number};
export type TWatchlist = Array<TWatchlistItem>; export type TWatchlist = Array<TWatchlistItem>;
export interface CsrfData {
token: string;
}
...@@ -13,10 +13,10 @@ import { useForm, Controller } from 'react-hook-form'; ...@@ -13,10 +13,10 @@ import { useForm, Controller } from 'react-hook-form';
import type { ApiKey, ApiKeys, ApiKeyErrors } from 'types/api/account'; import type { ApiKey, ApiKeys, ApiKeyErrors } from 'types/api/account';
import type { ErrorType } from 'lib/client/fetch';
import fetch from 'lib/client/fetch';
import getErrorMessage from 'lib/getErrorMessage'; import getErrorMessage from 'lib/getErrorMessage';
import getPlaceholderWithError from 'lib/getPlaceholderWithError'; import getPlaceholderWithError from 'lib/getPlaceholderWithError';
import type { ErrorType } from 'lib/hooks/useFetch';
import useFetch from 'lib/hooks/useFetch';
type Props = { type Props = {
data?: ApiKey; data?: ApiKey;
...@@ -39,11 +39,12 @@ const ApiKeyForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => { ...@@ -39,11 +39,12 @@ const ApiKeyForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
name: data?.name || '', name: data?.name || '',
}, },
}); });
const fetch = useFetch();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const formBackgroundColor = useColorModeValue('white', 'gray.900'); const formBackgroundColor = useColorModeValue('white', 'gray.900');
const updateApiKey = (data: Inputs) => { const updateApiKey = (data: Inputs) => {
const body = JSON.stringify({ name: data.name }); const body = { name: data.name };
if (!data.token) { if (!data.token) {
return fetch('/api/account/api-keys', { method: 'POST', body }); return fetch('/api/account/api-keys', { method: 'POST', body });
......
...@@ -4,7 +4,7 @@ import React, { useCallback } from 'react'; ...@@ -4,7 +4,7 @@ import React, { useCallback } from 'react';
import type { ApiKey, ApiKeys } from 'types/api/account'; import type { ApiKey, ApiKeys } from 'types/api/account';
import fetch from 'lib/client/fetch'; import useFetch from 'lib/hooks/useFetch';
import DeleteModal from 'ui/shared/DeleteModal'; import DeleteModal from 'ui/shared/DeleteModal';
type Props = { type Props = {
...@@ -15,10 +15,11 @@ type Props = { ...@@ -15,10 +15,11 @@ type Props = {
const DeleteAddressModal: React.FC<Props> = ({ isOpen, onClose, data }) => { const DeleteAddressModal: React.FC<Props> = ({ isOpen, onClose, data }) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const fetch = useFetch();
const mutationFn = useCallback(() => { const mutationFn = useCallback(() => {
return fetch(`/api/account/api-keys/${ data.api_key }`, { method: 'DELETE' }); return fetch(`/api/account/api-keys/${ data.api_key }`, { method: 'DELETE' });
}, [ data ]); }, [ data.api_key, fetch ]);
const onSuccess = useCallback(async() => { const onSuccess = useCallback(async() => {
queryClient.setQueryData([ 'api-keys' ], (prevData: ApiKeys | undefined) => { queryClient.setQueryData([ 'api-keys' ], (prevData: ApiKeys | undefined) => {
......
...@@ -14,10 +14,10 @@ import { useForm, Controller } from 'react-hook-form'; ...@@ -14,10 +14,10 @@ import { useForm, Controller } from 'react-hook-form';
import type { CustomAbi, CustomAbis, CustomAbiErrors } from 'types/api/account'; import type { CustomAbi, CustomAbis, CustomAbiErrors } from 'types/api/account';
import type { ErrorType } from 'lib/client/fetch';
import fetch from 'lib/client/fetch';
import getErrorMessage from 'lib/getErrorMessage'; import getErrorMessage from 'lib/getErrorMessage';
import getPlaceholderWithError from 'lib/getPlaceholderWithError'; import getPlaceholderWithError from 'lib/getPlaceholderWithError';
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';
...@@ -46,9 +46,10 @@ const CustomAbiForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => { ...@@ -46,9 +46,10 @@ const CustomAbiForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
}); });
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const fetch = useFetch();
const customAbiKey = (data: Inputs & { id?: number }) => { const customAbiKey = (data: Inputs & { id?: number }) => {
const body = JSON.stringify({ name: data.name, contract_address_hash: data.contract_address_hash, abi: data.abi }); const body = { name: data.name, contract_address_hash: data.contract_address_hash, abi: data.abi };
if (!data.id) { if (!data.id) {
return fetch<CustomAbi, CustomAbiErrors>('/api/account/custom-abis', { method: 'POST', body }); return fetch<CustomAbi, CustomAbiErrors>('/api/account/custom-abis', { method: 'POST', body });
......
...@@ -4,7 +4,7 @@ import React, { useCallback, useState } from 'react'; ...@@ -4,7 +4,7 @@ import React, { useCallback, useState } from 'react';
import type { ApiKey, ApiKeys } from 'types/api/account'; import type { ApiKey, ApiKeys } from 'types/api/account';
import fetch from 'lib/client/fetch'; import useFetch from 'lib/hooks/useFetch';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import { space } from 'lib/html-entities'; import { space } from 'lib/html-entities';
import ApiKeyModal from 'ui/apiKey/ApiKeyModal/ApiKeyModal'; import ApiKeyModal from 'ui/apiKey/ApiKeyModal/ApiKeyModal';
...@@ -25,6 +25,7 @@ const ApiKeysPage: React.FC = () => { ...@@ -25,6 +25,7 @@ const ApiKeysPage: React.FC = () => {
const apiKeyModalProps = useDisclosure(); const apiKeyModalProps = useDisclosure();
const deleteModalProps = useDisclosure(); const deleteModalProps = useDisclosure();
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const fetch = useFetch();
const [ apiKeyModalData, setApiKeyModalData ] = useState<ApiKey>(); const [ apiKeyModalData, setApiKeyModalData ] = useState<ApiKey>();
const [ deleteModalData, setDeleteModalData ] = useState<ApiKey>(); const [ deleteModalData, setDeleteModalData ] = useState<ApiKey>();
......
...@@ -4,7 +4,7 @@ import React, { useCallback, useState } from 'react'; ...@@ -4,7 +4,7 @@ import React, { useCallback, useState } from 'react';
import type { CustomAbi, CustomAbis } from 'types/api/account'; import type { CustomAbi, CustomAbis } from 'types/api/account';
import fetch from 'lib/client/fetch'; import useFetch from 'lib/hooks/useFetch';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import CustomAbiModal from 'ui/customAbi/CustomAbiModal/CustomAbiModal'; import CustomAbiModal from 'ui/customAbi/CustomAbiModal/CustomAbiModal';
import CustomAbiListItem from 'ui/customAbi/CustomAbiTable/CustomAbiListItem'; import CustomAbiListItem from 'ui/customAbi/CustomAbiTable/CustomAbiListItem';
...@@ -22,6 +22,7 @@ const CustomAbiPage: React.FC = () => { ...@@ -22,6 +22,7 @@ const CustomAbiPage: React.FC = () => {
const customAbiModalProps = useDisclosure(); const customAbiModalProps = useDisclosure();
const deleteModalProps = useDisclosure(); const deleteModalProps = useDisclosure();
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const fetch = useFetch();
const [ customAbiModalData, setCustomAbiModalData ] = useState<CustomAbi>(); const [ customAbiModalData, setCustomAbiModalData ] = useState<CustomAbi>();
const [ deleteModalData, setDeleteModalData ] = useState<CustomAbi>(); const [ deleteModalData, setDeleteModalData ] = useState<CustomAbi>();
......
...@@ -4,7 +4,7 @@ import React, { useCallback, useState } from 'react'; ...@@ -4,7 +4,7 @@ import React, { useCallback, useState } from 'react';
import type { TWatchlist, TWatchlistItem } from 'types/client/account'; import type { TWatchlist, TWatchlistItem } from 'types/client/account';
import fetch from 'lib/client/fetch'; import useFetch from 'lib/hooks/useFetch';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import AccountPageDescription from 'ui/shared/AccountPageDescription'; import AccountPageDescription from 'ui/shared/AccountPageDescription';
import DataFetchAlert from 'ui/shared/DataFetchAlert'; import DataFetchAlert from 'ui/shared/DataFetchAlert';
...@@ -24,6 +24,7 @@ const WatchList: React.FC = () => { ...@@ -24,6 +24,7 @@ const WatchList: React.FC = () => {
const addressModalProps = useDisclosure(); const addressModalProps = useDisclosure();
const deleteModalProps = useDisclosure(); const deleteModalProps = useDisclosure();
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const fetch = useFetch();
const [ addressModalData, setAddressModalData ] = useState<TWatchlistItem>(); const [ addressModalData, setAddressModalData ] = useState<TWatchlistItem>();
const [ deleteModalData, setDeleteModalData ] = useState<TWatchlistItem>(); const [ deleteModalData, setDeleteModalData ] = useState<TWatchlistItem>();
......
...@@ -10,9 +10,9 @@ import { useForm, Controller } from 'react-hook-form'; ...@@ -10,9 +10,9 @@ import { useForm, Controller } from 'react-hook-form';
import type { AddressTag, AddressTagErrors } from 'types/api/account'; import type { AddressTag, AddressTagErrors } from 'types/api/account';
import type { ErrorType } from 'lib/client/fetch';
import fetch from 'lib/client/fetch';
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 TagInput from 'ui/shared/TagInput'; import TagInput from 'ui/shared/TagInput';
...@@ -31,6 +31,7 @@ type Inputs = { ...@@ -31,6 +31,7 @@ type Inputs = {
} }
const AddressForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => { const AddressForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
const fetch = useFetch();
const [ pending, setPending ] = useState(false); const [ pending, setPending ] = useState(false);
const { control, handleSubmit, formState: { errors, isValid }, setError } = useForm<Inputs>({ const { control, handleSubmit, formState: { errors, isValid }, setError } = useForm<Inputs>({
mode: 'all', mode: 'all',
...@@ -45,10 +46,10 @@ const AddressForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => { ...@@ -45,10 +46,10 @@ const AddressForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { mutate } = useMutation((formData: Inputs) => { const { mutate } = useMutation((formData: Inputs) => {
const body = JSON.stringify({ const body = {
name: formData?.tag, name: formData?.tag,
address_hash: formData?.address, address_hash: formData?.address,
}); };
const isEdit = data?.id; const isEdit = data?.id;
if (isEdit) { if (isEdit) {
......
...@@ -4,6 +4,7 @@ import React, { useCallback } from 'react'; ...@@ -4,6 +4,7 @@ import React, { useCallback } from 'react';
import type { AddressTag, TransactionTag, AddressTags, TransactionTags } from 'types/api/account'; import type { AddressTag, TransactionTag, AddressTags, TransactionTags } from 'types/api/account';
import useFetch from 'lib/hooks/useFetch';
import DeleteModal from 'ui/shared/DeleteModal'; import DeleteModal from 'ui/shared/DeleteModal';
type Props = { type Props = {
...@@ -18,18 +19,19 @@ const DeletePrivateTagModal: React.FC<Props> = ({ isOpen, onClose, data, type }) ...@@ -18,18 +19,19 @@ const DeletePrivateTagModal: React.FC<Props> = ({ isOpen, onClose, data, type })
const id = data.id; const id = data.id;
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const fetch = useFetch();
const mutationFn = useCallback(() => { const mutationFn = useCallback(() => {
return fetch(`/api/account/private-tags/${ type }/${ id }`, { method: 'DELETE' }); return fetch(`/api/account/private-tags/${ type }/${ id }`, { method: 'DELETE' });
}, [ type, id ]); }, [ fetch, type, id ]);
const onSuccess = useCallback(async() => { const onSuccess = useCallback(async() => {
if (type === 'address') { if (type === 'address') {
queryClient.setQueryData([ type ], (prevData: AddressTags | undefined) => { queryClient.setQueryData([ 'address-tags' ], (prevData: AddressTags | undefined) => {
return prevData?.filter((item: AddressTag) => item.id !== id); return prevData?.filter((item: AddressTag) => item.id !== id);
}); });
} else { } else {
queryClient.setQueryData([ type ], (prevData: TransactionTags | undefined) => { queryClient.setQueryData([ 'transaction-tags' ], (prevData: TransactionTags | undefined) => {
return prevData?.filter((item: TransactionTag) => item.id !== id); return prevData?.filter((item: TransactionTag) => item.id !== id);
}); });
} }
......
...@@ -4,7 +4,7 @@ import React, { useCallback, useState } from 'react'; ...@@ -4,7 +4,7 @@ import React, { useCallback, useState } from 'react';
import type { AddressTags, AddressTag } from 'types/api/account'; import type { AddressTags, AddressTag } from 'types/api/account';
import fetch from 'lib/client/fetch'; import useFetch from 'lib/hooks/useFetch';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import AccountPageDescription from 'ui/shared/AccountPageDescription'; import AccountPageDescription from 'ui/shared/AccountPageDescription';
import DataFetchAlert from 'ui/shared/DataFetchAlert'; import DataFetchAlert from 'ui/shared/DataFetchAlert';
...@@ -23,6 +23,7 @@ const PrivateAddressTags = () => { ...@@ -23,6 +23,7 @@ const PrivateAddressTags = () => {
const addressModalProps = useDisclosure(); const addressModalProps = useDisclosure();
const deleteModalProps = useDisclosure(); const deleteModalProps = useDisclosure();
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const fetch = useFetch();
const [ addressModalData, setAddressModalData ] = useState<AddressTag>(); const [ addressModalData, setAddressModalData ] = useState<AddressTag>();
const [ deleteModalData, setDeleteModalData ] = useState<AddressTag>(); const [ deleteModalData, setDeleteModalData ] = useState<AddressTag>();
......
...@@ -4,7 +4,7 @@ import React, { useCallback, useState } from 'react'; ...@@ -4,7 +4,7 @@ import React, { useCallback, useState } from 'react';
import type { TransactionTags, TransactionTag } from 'types/api/account'; import type { TransactionTags, TransactionTag } from 'types/api/account';
import fetch from 'lib/client/fetch'; import useFetch from 'lib/hooks/useFetch';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import AccountPageDescription from 'ui/shared/AccountPageDescription'; import AccountPageDescription from 'ui/shared/AccountPageDescription';
import DataFetchAlert from 'ui/shared/DataFetchAlert'; import DataFetchAlert from 'ui/shared/DataFetchAlert';
...@@ -23,6 +23,7 @@ const PrivateTransactionTags = () => { ...@@ -23,6 +23,7 @@ const PrivateTransactionTags = () => {
const transactionModalProps = useDisclosure(); const transactionModalProps = useDisclosure();
const deleteModalProps = useDisclosure(); const deleteModalProps = useDisclosure();
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const fetch = useFetch();
const [ transactionModalData, setTransactionModalData ] = useState<TransactionTag>(); const [ transactionModalData, setTransactionModalData ] = useState<TransactionTag>();
const [ deleteModalData, setDeleteModalData ] = useState<TransactionTag>(); const [ deleteModalData, setDeleteModalData ] = useState<TransactionTag>();
......
...@@ -10,9 +10,9 @@ import { useForm, Controller } from 'react-hook-form'; ...@@ -10,9 +10,9 @@ import { useForm, Controller } from 'react-hook-form';
import type { TransactionTag, TransactionTagErrors } from 'types/api/account'; import type { TransactionTag, TransactionTagErrors } from 'types/api/account';
import type { ErrorType } from 'lib/client/fetch';
import fetch from 'lib/client/fetch';
import getErrorMessage from 'lib/getErrorMessage'; import getErrorMessage from 'lib/getErrorMessage';
import type { ErrorType } from 'lib/hooks/useFetch';
import useFetch from 'lib/hooks/useFetch';
import { TRANSACTION_HASH_REGEXP } from 'lib/validations/transaction'; import { TRANSACTION_HASH_REGEXP } from 'lib/validations/transaction';
import TagInput from 'ui/shared/TagInput'; import TagInput from 'ui/shared/TagInput';
import TransactionInput from 'ui/shared/TransactionInput'; import TransactionInput from 'ui/shared/TransactionInput';
...@@ -43,12 +43,13 @@ const TransactionForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => ...@@ -43,12 +43,13 @@ const TransactionForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) =>
}); });
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const fetch = useFetch();
const { mutate } = useMutation((formData: Inputs) => { const { mutate } = useMutation((formData: Inputs) => {
const body = JSON.stringify({ const body = {
name: formData?.tag, name: formData?.tag,
transaction_hash: formData?.transaction, transaction_hash: formData?.transaction,
}); };
const isEdit = data?.id; const isEdit = data?.id;
if (isEdit) { if (isEdit) {
......
...@@ -5,7 +5,7 @@ import type { ChangeEvent } from 'react'; ...@@ -5,7 +5,7 @@ import type { ChangeEvent } from 'react';
import type { PublicTags, PublicTag } from 'types/api/account'; import type { PublicTags, PublicTag } from 'types/api/account';
import fetch from 'lib/client/fetch'; import useFetch from 'lib/hooks/useFetch';
import DeleteModal from 'ui/shared/DeleteModal'; import DeleteModal from 'ui/shared/DeleteModal';
type Props = { type Props = {
...@@ -22,12 +22,13 @@ const DeletePublicTagModal: React.FC<Props> = ({ isOpen, onClose, data, onDelete ...@@ -22,12 +22,13 @@ const DeletePublicTagModal: React.FC<Props> = ({ isOpen, onClose, data, onDelete
const tags = data.tags.split(';'); const tags = data.tags.split(';');
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const fetch = useFetch();
const formBackgroundColor = useColorModeValue('white', 'gray.900'); const formBackgroundColor = useColorModeValue('white', 'gray.900');
const deleteApiKey = useCallback(() => { const deleteApiKey = useCallback(() => {
const body = JSON.stringify({ remove_reason: reason }); const body = { remove_reason: reason };
return fetch(`/api/account/public-tags/${ data.id }`, { method: 'DELETE', body }); return fetch(`/api/account/public-tags/${ data.id }`, { method: 'DELETE', body });
}, [ data, reason ]); }, [ data.id, fetch, reason ]);
const onSuccess = useCallback(async() => { const onSuccess = useCallback(async() => {
onDeleteSuccess(); onDeleteSuccess();
......
...@@ -4,7 +4,7 @@ import React, { useCallback, useState } from 'react'; ...@@ -4,7 +4,7 @@ import React, { useCallback, useState } from 'react';
import type { PublicTags, PublicTag } from 'types/api/account'; import type { PublicTags, PublicTag } from 'types/api/account';
import fetch from 'lib/client/fetch'; import useFetch from 'lib/hooks/useFetch';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import PublicTagListItem from 'ui/publicTags/PublicTagTable/PublicTagListItem'; import PublicTagListItem from 'ui/publicTags/PublicTagTable/PublicTagListItem';
import AccountPageDescription from 'ui/shared/AccountPageDescription'; import AccountPageDescription from 'ui/shared/AccountPageDescription';
...@@ -24,6 +24,7 @@ const PublicTagsData = ({ changeToFormScreen, onTagDelete }: Props) => { ...@@ -24,6 +24,7 @@ const PublicTagsData = ({ changeToFormScreen, onTagDelete }: Props) => {
const deleteModalProps = useDisclosure(); const deleteModalProps = useDisclosure();
const [ deleteModalData, setDeleteModalData ] = useState<PublicTag>(); const [ deleteModalData, setDeleteModalData ] = useState<PublicTag>();
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const fetch = useFetch();
const { data, isLoading, isError } = useQuery<unknown, unknown, PublicTags>([ 'public-tags' ], async() => await fetch('/api/account/public-tags')); const { data, isLoading, isError } = useQuery<unknown, unknown, PublicTags>([ 'public-tags' ], async() => await fetch('/api/account/public-tags'));
......
...@@ -13,9 +13,9 @@ import { useForm, useFieldArray } from 'react-hook-form'; ...@@ -13,9 +13,9 @@ import { useForm, useFieldArray } from 'react-hook-form';
import type { PublicTags, PublicTag, PublicTagNew, PublicTagErrors } from 'types/api/account'; import type { PublicTags, PublicTag, PublicTagNew, PublicTagErrors } from 'types/api/account';
import type { ErrorType } from 'lib/client/fetch';
import fetch from 'lib/client/fetch';
import getErrorMessage from 'lib/getErrorMessage'; import getErrorMessage from 'lib/getErrorMessage';
import type { ErrorType } from 'lib/hooks/useFetch';
import useFetch from 'lib/hooks/useFetch';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import { EMAIL_REGEXP } from 'lib/validations/email'; import { EMAIL_REGEXP } from 'lib/validations/email';
import FormSubmitAlert from 'ui/shared/FormSubmitAlert'; import FormSubmitAlert from 'ui/shared/FormSubmitAlert';
...@@ -58,6 +58,7 @@ const ADDRESS_INPUT_BUTTONS_WIDTH = 100; ...@@ -58,6 +58,7 @@ const ADDRESS_INPUT_BUTTONS_WIDTH = 100;
const PublicTagsForm = ({ changeToDataScreen, data }: Props) => { const PublicTagsForm = ({ changeToDataScreen, data }: Props) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const fetch = useFetch();
const inputSize = isMobile ? 'md' : 'lg'; const inputSize = isMobile ? 'md' : 'lg';
const { control, handleSubmit, formState: { errors, isValid }, setError } = useForm<Inputs>({ const { control, handleSubmit, formState: { errors, isValid }, setError } = useForm<Inputs>({
...@@ -87,7 +88,7 @@ const PublicTagsForm = ({ changeToDataScreen, data }: Props) => { ...@@ -87,7 +88,7 @@ const PublicTagsForm = ({ changeToDataScreen, data }: Props) => {
const onRemoveFieldClick = useCallback((index: number) => () => remove(index), [ remove ]); const onRemoveFieldClick = useCallback((index: number) => () => remove(index), [ remove ]);
const updatePublicTag = (formData: Inputs) => { const updatePublicTag = (formData: Inputs) => {
const payload: PublicTagNew = { const body: PublicTagNew = {
full_name: formData.fullName || '', full_name: formData.fullName || '',
email: formData.email || '', email: formData.email || '',
company: formData.companyName || '', company: formData.companyName || '',
...@@ -97,7 +98,6 @@ const PublicTagsForm = ({ changeToDataScreen, data }: Props) => { ...@@ -97,7 +98,6 @@ const PublicTagsForm = ({ changeToDataScreen, data }: Props) => {
tags: formData.tags?.split(';').map((s) => s.trim()).join(';') || '', tags: formData.tags?.split(';').map((s) => s.trim()).join(';') || '',
additional_comment: formData.comment || '', additional_comment: formData.comment || '',
}; };
const body = JSON.stringify(payload);
if (!data?.id) { if (!data?.id) {
return fetch<PublicTag, PublicTagErrors>('/api/account/public-tags', { method: 'POST', body }); return fetch<PublicTag, PublicTagErrors>('/api/account/public-tags', { method: 'POST', body });
......
import { Box, HStack, VStack } from '@chakra-ui/react'; import { Box, HStack, VStack } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import * as cookies from 'lib/cookies'; import * as cookies from 'lib/cookies';
import useFetch from 'lib/hooks/useFetch';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import Header from 'ui/blocks/header/Header'; import Header from 'ui/blocks/header/Header';
import NavigationDesktop from 'ui/blocks/navigation/NavigationDesktop'; import NavigationDesktop from 'ui/blocks/navigation/NavigationDesktop';
...@@ -14,9 +16,13 @@ interface Props { ...@@ -14,9 +16,13 @@ interface Props {
const Page = ({ children }: Props) => { const Page = ({ children }: Props) => {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const router = useRouter(); const router = useRouter();
const fetch = useFetch();
const networkType = router.query.network_type; const networkType = router.query.network_type;
const networkSubType = router.query.network_sub_type; const networkSubType = router.query.network_sub_type;
useQuery<unknown, unknown, unknown>([ 'csrf' ], async() => await fetch('/api/account/csrf'));
React.useEffect(() => { React.useEffect(() => {
if (typeof networkType === 'string') { if (typeof networkType === 'string') {
cookies.set(cookies.NAMES.NETWORK_TYPE, networkType); cookies.set(cookies.NAMES.NETWORK_TYPE, networkType);
......
...@@ -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 { ErrorType } from 'lib/client/fetch';
import fetch from 'lib/client/fetch';
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,9 +83,10 @@ const AddressForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => { ...@@ -83,9 +83,10 @@ const AddressForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
}); });
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const fetch = useFetch();
function updateWatchlist(formData: Inputs) { function updateWatchlist(formData: Inputs) {
const requestParams = { const body = {
name: formData?.tag, name: formData?.tag,
address_hash: formData?.address, address_hash: formData?.address,
notification_settings: formData.notification_settings, notification_settings: formData.notification_settings,
...@@ -95,11 +96,11 @@ const AddressForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => { ...@@ -95,11 +96,11 @@ const AddressForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
}; };
if (data) { if (data) {
// edit address // edit address
return fetch<TWatchlistItem, WatchlistErrors>(`/api/account/watchlist/${ data.id }`, { method: 'PUT', body: JSON.stringify(requestParams) }); return fetch<TWatchlistItem, WatchlistErrors>(`/api/account/watchlist/${ data.id }`, { method: 'PUT', body });
} else { } else {
// add address // add address
return fetch<TWatchlistItem, WatchlistErrors>('/api/account/watchlist', { method: 'POST', body: JSON.stringify(requestParams) }); return fetch<TWatchlistItem, WatchlistErrors>('/api/account/watchlist', { method: 'POST', body });
} }
} }
......
...@@ -4,7 +4,7 @@ import React, { useCallback } from 'react'; ...@@ -4,7 +4,7 @@ import React, { useCallback } from 'react';
import type { TWatchlistItem, TWatchlist } from 'types/client/account'; import type { TWatchlistItem, TWatchlist } from 'types/client/account';
import fetch from 'lib/client/fetch'; import useFetch from 'lib/hooks/useFetch';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import DeleteModal from 'ui/shared/DeleteModal'; import DeleteModal from 'ui/shared/DeleteModal';
...@@ -17,10 +17,11 @@ type Props = { ...@@ -17,10 +17,11 @@ type Props = {
const DeleteAddressModal: React.FC<Props> = ({ isOpen, onClose, data }) => { const DeleteAddressModal: React.FC<Props> = ({ isOpen, onClose, data }) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const fetch = useFetch();
const mutationFn = useCallback(() => { const mutationFn = useCallback(() => {
return fetch(`/api/account1/watchlist/${ data?.id }`, { method: 'DELETE' }); return fetch(`/api/account1/watchlist/${ data?.id }`, { method: 'DELETE' });
}, [ data ]); }, [ data?.id, fetch ]);
const onSuccess = useCallback(async() => { const onSuccess = useCallback(async() => {
queryClient.setQueryData([ 'watchlist' ], (prevData: TWatchlist | undefined) => { queryClient.setQueryData([ 'watchlist' ], (prevData: TWatchlist | undefined) => {
......
...@@ -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 fetch from 'lib/client/fetch'; import useFetch from 'lib/hooks/useFetch';
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,6 +34,7 @@ const WatchlistTableItem = ({ item, onEditClick, onDeleteClick }: Props) => { ...@@ -34,6 +34,7 @@ const WatchlistTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
}, [ item, onDeleteClick ]); }, [ item, onDeleteClick ]);
const toast = useToast(); const toast = useToast();
const fetch = useFetch();
const showToast = useCallback(() => { const showToast = useCallback(() => {
toast({ toast({
...@@ -49,9 +50,9 @@ const WatchlistTableItem = ({ item, onEditClick, onDeleteClick }: Props) => { ...@@ -49,9 +50,9 @@ const WatchlistTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
const { mutate } = useMutation(() => { const { mutate } = useMutation(() => {
setSwitchDisabled(true); setSwitchDisabled(true);
const data = { ...item, notification_methods: { email: !notificationEnabled } }; const body = { ...item, notification_methods: { email: !notificationEnabled } };
setNotificationEnabled(prevState => !prevState); setNotificationEnabled(prevState => !prevState);
return fetch(`/api/account1/watchlist/${ item.id }`, { method: 'PUT', body: JSON.stringify(data) }); return fetch(`/api/account1/watchlist/${ item.id }`, { method: 'PUT', body });
}, { }, {
onError: () => { onError: () => {
showToast(); showToast();
......
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