Commit 2d99aba9 authored by tom's avatar tom

watchlist page refactoring

parent bb7d3592
...@@ -32,7 +32,7 @@ const RESTRICTED_MODULES = { ...@@ -32,7 +32,7 @@ const RESTRICTED_MODULES = {
{ {
name: '@chakra-ui/react', name: '@chakra-ui/react',
importNames: [ importNames: [
'Menu', 'useToast', 'useDisclosure', 'useClipboard', 'Tooltip', 'Skeleton', 'IconButton', 'Button', 'Link', 'Tag', 'Menu', 'useToast', 'useDisclosure', 'useClipboard', 'Tooltip', 'Skeleton', 'IconButton', 'Button', 'Link', 'Tag', 'Switch',
'Image', 'Popover', 'PopoverTrigger', 'PopoverContent', 'PopoverBody', 'PopoverFooter', 'Image', 'Popover', 'PopoverTrigger', 'PopoverContent', 'PopoverBody', 'PopoverFooter',
'DrawerRoot', 'DrawerBody', 'DrawerContent', 'DrawerOverlay', 'DrawerBackdrop', 'DrawerTrigger', 'Drawer', 'DrawerRoot', 'DrawerBody', 'DrawerContent', 'DrawerOverlay', 'DrawerBackdrop', 'DrawerTrigger', 'Drawer',
'Alert', 'AlertIcon', 'AlertTitle', 'AlertDescription', 'Alert', 'AlertIcon', 'AlertTitle', 'AlertDescription',
......
...@@ -4,12 +4,12 @@ import React from 'react'; ...@@ -4,12 +4,12 @@ import React from 'react';
import PageNextJs from 'nextjs/PageNextJs'; import PageNextJs from 'nextjs/PageNextJs';
// const WatchList = dynamic(() => import('ui/pages/Watchlist'), { ssr: false }); const WatchList = dynamic(() => import('ui/pages/Watchlist'), { ssr: false });
const Page: NextPage = () => { const Page: NextPage = () => {
return ( return (
<PageNextJs pathname="/account/watchlist"> <PageNextJs pathname="/account/watchlist">
{ /* <WatchList/> */ } <WatchList/>
</PageNextJs> </PageNextJs>
); );
}; };
......
...@@ -14,7 +14,7 @@ const baseStyleDialog = defineStyle(() => { ...@@ -14,7 +14,7 @@ const baseStyleDialog = defineStyle(() => {
return { return {
padding: 8, padding: 8,
borderRadius: 'lg', borderRadius: 'lg',
bg: 'dialog_bg', bg: 'dialog.bg',
margin: 'auto', margin: 'auto',
}; };
}); });
......
...@@ -4,7 +4,7 @@ import getOutlinedFieldStyles from '../utils/getOutlinedFieldStyles'; ...@@ -4,7 +4,7 @@ import getOutlinedFieldStyles from '../utils/getOutlinedFieldStyles';
const baseStyle = defineStyle({ const baseStyle = defineStyle({
textAlign: 'center', textAlign: 'center',
bgColor: 'dialog_bg', bgColor: 'dialog.bg',
}); });
const sizes = { const sizes = {
......
...@@ -31,7 +31,7 @@ const semanticTokens = { ...@@ -31,7 +31,7 @@ const semanticTokens = {
'default': 'red.500', 'default': 'red.500',
_dark: 'red.500', _dark: 'red.500',
}, },
dialog_bg: { dialog.bg: {
'default': 'white', 'default': 'white',
_dark: 'gray.900', _dark: 'gray.900',
}, },
......
...@@ -39,7 +39,7 @@ const TokenSelectMenu = ({ erc20sort, erc1155sort, erc404sort, filteredData, onI ...@@ -39,7 +39,7 @@ const TokenSelectMenu = ({ erc20sort, erc1155sort, erc404sort, filteredData, onI
placeholder="Search by token name" placeholder="Search by token name"
ml="1px" ml="1px"
onChange={ onInputChange } onChange={ onInputChange }
bgColor="dialog_bg" bgColor="dialog.bg"
/> />
</InputGroup> </InputGroup>
<Flex flexDir="column" rowGap={ 6 }> <Flex flexDir="column" rowGap={ 6 }>
......
...@@ -106,7 +106,7 @@ const AddressVerificationStepAddress = ({ defaultAddress, onContinue }: Props) = ...@@ -106,7 +106,7 @@ const AddressVerificationStepAddress = ({ defaultAddress, onContinue }: Props) =
<FormFieldAddress<Fields> <FormFieldAddress<Fields>
name="address" name="address"
isRequired isRequired
bgColor="dialog_bg" bgColor="dialog.bg"
placeholder="Smart contract address (0x...)" placeholder="Smart contract address (0x...)"
mt={ 8 } mt={ 8 }
/> />
......
...@@ -221,7 +221,7 @@ const AddressVerificationStepSignature = ({ address, signingMessage, contractCre ...@@ -221,7 +221,7 @@ const AddressVerificationStepSignature = ({ address, signingMessage, contractCre
asComponent="Textarea" asComponent="Textarea"
isReadOnly isReadOnly
maxH={{ base: '140px', lg: '80px' }} maxH={{ base: '140px', lg: '80px' }}
bgColor="dialog_bg" bgColor="dialog.bg"
/> />
</div> </div>
{ !noWeb3Provider && ( { !noWeb3Provider && (
...@@ -236,7 +236,7 @@ const AddressVerificationStepSignature = ({ address, signingMessage, contractCre ...@@ -236,7 +236,7 @@ const AddressVerificationStepSignature = ({ address, signingMessage, contractCre
placeholder="Signature hash" placeholder="Signature hash"
isRequired isRequired
rules={{ pattern: SIGNATURE_REGEXP }} rules={{ pattern: SIGNATURE_REGEXP }}
bgColor="dialog_bg" bgColor="dialog.bg"
/> />
) } ) }
</Flex> </Flex>
......
...@@ -100,7 +100,7 @@ const ApiKeyForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => { ...@@ -100,7 +100,7 @@ const ApiKeyForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
name="token" name="token"
placeholder="Auto-generated API key token" placeholder="Auto-generated API key token"
isReadOnly isReadOnly
bgColor="dialog_bg" bgColor="dialog.bg"
mb={ 5 } mb={ 5 }
/> />
) } ) }
...@@ -111,7 +111,7 @@ const ApiKeyForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => { ...@@ -111,7 +111,7 @@ const ApiKeyForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
rules={{ rules={{
maxLength: NAME_MAX_LENGTH, maxLength: NAME_MAX_LENGTH,
}} }}
bgColor="dialog_bg" bgColor="dialog.bg"
mb={ 8 } mb={ 8 }
/> />
<Box marginTop={ 8 }> <Box marginTop={ 8 }>
......
...@@ -111,7 +111,7 @@ const CustomAbiForm: React.FC<Props> = ({ data, onClose, onSuccess, setAlertVisi ...@@ -111,7 +111,7 @@ const CustomAbiForm: React.FC<Props> = ({ data, onClose, onSuccess, setAlertVisi
name="contract_address_hash" name="contract_address_hash"
placeholder="Smart contract address (0x...)" placeholder="Smart contract address (0x...)"
isRequired isRequired
bgColor="dialog_bg" bgColor="dialog.bg"
isReadOnly={ Boolean(data && 'contract_address_hash' in data) } isReadOnly={ Boolean(data && 'contract_address_hash' in data) }
mb={ 5 } mb={ 5 }
/> />
...@@ -122,7 +122,7 @@ const CustomAbiForm: React.FC<Props> = ({ data, onClose, onSuccess, setAlertVisi ...@@ -122,7 +122,7 @@ const CustomAbiForm: React.FC<Props> = ({ data, onClose, onSuccess, setAlertVisi
rules={{ rules={{
maxLength: NAME_MAX_LENGTH, maxLength: NAME_MAX_LENGTH,
}} }}
bgColor="dialog_bg" bgColor="dialog.bg"
mb={ 5 } mb={ 5 }
/> />
<FormFieldText<Inputs> <FormFieldText<Inputs>
...@@ -130,7 +130,7 @@ const CustomAbiForm: React.FC<Props> = ({ data, onClose, onSuccess, setAlertVisi ...@@ -130,7 +130,7 @@ const CustomAbiForm: React.FC<Props> = ({ data, onClose, onSuccess, setAlertVisi
placeholder="Custom ABI [{...}] (JSON format)" placeholder="Custom ABI [{...}] (JSON format)"
isRequired isRequired
asComponent="Textarea" asComponent="Textarea"
bgColor="dialog_bg" bgColor="dialog.bg"
size="lg" size="lg"
minH="300px" minH="300px"
mb={ 8 } mb={ 8 }
......
import { Box, Button, useDisclosure } from '@chakra-ui/react'; import { Box } from '@chakra-ui/react';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import React, { useCallback, useState } from 'react'; import React, { useCallback, useState } from 'react';
...@@ -7,9 +7,11 @@ import type { WatchlistAddress, WatchlistResponse } from 'types/api/account'; ...@@ -7,9 +7,11 @@ import type { WatchlistAddress, WatchlistResponse } from 'types/api/account';
import { resourceKey } from 'lib/api/resources'; import { resourceKey } from 'lib/api/resources';
import { getResourceKey } from 'lib/api/useApiQuery'; import { getResourceKey } from 'lib/api/useApiQuery';
import { WATCH_LIST_ITEM_WITH_TOKEN_INFO } from 'stubs/account'; import { WATCH_LIST_ITEM_WITH_TOKEN_INFO } from 'stubs/account';
import { Button } from 'toolkit/chakra/button';
import { Skeleton } from 'toolkit/chakra/skeleton';
import { useDisclosure } from 'toolkit/hooks/useDisclosure';
import AccountPageDescription from 'ui/shared/AccountPageDescription'; import AccountPageDescription from 'ui/shared/AccountPageDescription';
import ActionBar, { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar'; import ActionBar, { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar';
import Skeleton from 'ui/shared/chakra/Skeleton';
import DataListDisplay from 'ui/shared/DataListDisplay'; import DataListDisplay from 'ui/shared/DataListDisplay';
import PageTitle from 'ui/shared/Page/PageTitle'; import PageTitle from 'ui/shared/Page/PageTitle';
import Pagination from 'ui/shared/pagination/Pagination'; import Pagination from 'ui/shared/pagination/Pagination';
...@@ -44,9 +46,9 @@ const WatchList: React.FC = () => { ...@@ -44,9 +46,9 @@ const WatchList: React.FC = () => {
addressModalProps.onOpen(); addressModalProps.onOpen();
}, [ addressModalProps ]); }, [ addressModalProps ]);
const onAddressModalClose = useCallback(() => { const onAddressModalOpenChange = useCallback(({ open }: { open: boolean }) => {
setAddressModalData(undefined); !open && setAddressModalData(undefined);
addressModalProps.onClose(); addressModalProps.onOpenChange({ open });
}, [ addressModalProps ]); }, [ addressModalProps ]);
const onAddOrEditSuccess = useCallback(async() => { const onAddOrEditSuccess = useCallback(async() => {
...@@ -60,9 +62,9 @@ const WatchList: React.FC = () => { ...@@ -60,9 +62,9 @@ const WatchList: React.FC = () => {
deleteModalProps.onOpen(); deleteModalProps.onOpen();
}, [ deleteModalProps ]); }, [ deleteModalProps ]);
const onDeleteModalClose = useCallback(() => { const onDeleteModalOpenChange = useCallback(({ open }: { open: boolean }) => {
setDeleteModalData(undefined); !open && setDeleteModalData(undefined);
deleteModalProps.onClose(); deleteModalProps.onOpenChange({ open });
}, [ deleteModalProps ]); }, [ deleteModalProps ]);
const onDeleteSuccess = useCallback(async() => { const onDeleteSuccess = useCallback(async() => {
...@@ -86,8 +88,15 @@ const WatchList: React.FC = () => { ...@@ -86,8 +88,15 @@ const WatchList: React.FC = () => {
</ActionBar> </ActionBar>
) : null; ) : null;
const list = ( return (
<> <>
{ description }
<DataListDisplay
isError={ isError }
itemsNum={ data?.items.length }
emptyText=""
actionBar={ actionBar }
>
<Box display={{ base: 'block', lg: 'none' }}> <Box display={{ base: 'block', lg: 'none' }}>
{ data?.items.map((item, index) => ( { data?.items.map((item, index) => (
<WatchListItem <WatchListItem
...@@ -110,20 +119,8 @@ const WatchList: React.FC = () => { ...@@ -110,20 +119,8 @@ const WatchList: React.FC = () => {
hasEmail={ Boolean(profileQuery.data?.email) } hasEmail={ Boolean(profileQuery.data?.email) }
/> />
</Box> </Box>
</> </DataListDisplay>
); <Skeleton mt={ 8 } loading={ isPlaceholderData } display="inline-block">
return (
<>
{ description }
<DataListDisplay
isError={ isError }
items={ data?.items }
emptyText=""
content={ list }
actionBar={ actionBar }
/>
<Skeleton mt={ 8 } isLoaded={ !isPlaceholderData } display="inline-block">
<Button <Button
size="lg" size="lg"
onClick={ addressModalProps.onOpen } onClick={ addressModalProps.onOpen }
...@@ -133,7 +130,7 @@ const WatchList: React.FC = () => { ...@@ -133,7 +130,7 @@ const WatchList: React.FC = () => {
</Skeleton> </Skeleton>
<AddressModal <AddressModal
{ ...addressModalProps } { ...addressModalProps }
onClose={ onAddressModalClose } onOpenChange={ onAddressModalOpenChange }
onSuccess={ onAddOrEditSuccess } onSuccess={ onAddOrEditSuccess }
data={ addressModalData } data={ addressModalData }
isAdd={ !addressModalData } isAdd={ !addressModalData }
...@@ -141,7 +138,7 @@ const WatchList: React.FC = () => { ...@@ -141,7 +138,7 @@ const WatchList: React.FC = () => {
{ deleteModalData && ( { deleteModalData && (
<DeleteAddressModal <DeleteAddressModal
{ ...deleteModalProps } { ...deleteModalProps }
onClose={ onDeleteModalClose } onOpenChange={ onDeleteModalOpenChange }
onSuccess={ onDeleteSuccess } onSuccess={ onDeleteSuccess }
data={ deleteModalData } data={ deleteModalData }
/> />
......
...@@ -21,7 +21,6 @@ const TableItemActionButtons = ({ onEditClick, onDeleteClick, isLoading }: Props ...@@ -21,7 +21,6 @@ const TableItemActionButtons = ({ onEditClick, onDeleteClick, isLoading }: Props
<IconButton <IconButton
aria-label="edit" aria-label="edit"
variant="link" variant="link"
boxSize={ 5 }
onClick={ onEditClick } onClick={ onEditClick }
onFocusCapture={ onFocusCapture } onFocusCapture={ onFocusCapture }
loading={ isLoading } loading={ isLoading }
...@@ -36,7 +35,6 @@ const TableItemActionButtons = ({ onEditClick, onDeleteClick, isLoading }: Props ...@@ -36,7 +35,6 @@ const TableItemActionButtons = ({ onEditClick, onDeleteClick, isLoading }: Props
<IconButton <IconButton
aria-label="delete" aria-label="delete"
variant="link" variant="link"
boxSize={ 5 }
onClick={ onDeleteClick } onClick={ onDeleteClick }
onFocusCapture={ onFocusCapture } onFocusCapture={ onFocusCapture }
loading={ isLoading } loading={ isLoading }
......
import type { ChakraProps } from '@chakra-ui/react';
import { chakra, Checkbox } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import { useController, useFormContext, type FieldValues, type Path } from 'react-hook-form'; import { useController, useFormContext, type FieldValues, type Path } from 'react-hook-form';
import type { FormFieldPropsBase } from './types'; import type { FormFieldPropsBase } from './types';
import type { CheckboxProps } from 'toolkit/chakra/checkbox';
import { Checkbox } from 'toolkit/chakra/checkbox';
interface Props< interface Props<
FormFields extends FieldValues, FormFields extends FieldValues,
Name extends Path<FormFields> = Path<FormFields>, Name extends Path<FormFields> = Path<FormFields>,
> extends Omit<FormFieldPropsBase<FormFields, Name>, 'size' | 'bgColor' | 'placeholder'> { > extends Pick<FormFieldPropsBase<FormFields, Name>, 'rules' | 'name' | 'onChange' | 'readOnly'>, Omit<CheckboxProps, 'name' | 'onChange'> {
label: string; label: string;
} }
...@@ -20,8 +21,8 @@ const FormFieldCheckbox = < ...@@ -20,8 +21,8 @@ const FormFieldCheckbox = <
label, label,
rules, rules,
onChange, onChange,
isReadOnly, readOnly,
className, ...rest
}: Props<FormFields, Name>) => { }: Props<FormFields, Name>) => {
const { control } = useFormContext<FormFields>(); const { control } = useFormContext<FormFields>();
const { field, formState } = useController<FormFields, typeof name>({ const { field, formState } = useController<FormFields, typeof name>({
...@@ -32,32 +33,23 @@ const FormFieldCheckbox = < ...@@ -32,32 +33,23 @@ const FormFieldCheckbox = <
const isDisabled = formState.isSubmitting; const isDisabled = formState.isSubmitting;
const handleChange: typeof field.onChange = React.useCallback((...args) => { const handleChange: typeof field.onChange = React.useCallback(({ checked }: { checked: boolean }) => {
field.onChange(...args); field.onChange(checked);
onChange?.(); onChange?.();
}, [ field, onChange ]); }, [ field, onChange ]);
return ( return (
<Checkbox <Checkbox
ref={ field.ref } ref={ field.ref }
isChecked={ field.value } checked={ field.value }
className={ className } onCheckedChange={ handleChange }
onChange={ handleChange } size="md"
colorScheme="blue" disabled={ isDisabled }
size="lg" { ...rest }
isDisabled={ isDisabled }
isReadOnly={ isReadOnly }
> >
{ label } { label }
</Checkbox> </Checkbox>
); );
}; };
const WrappedFormFieldCheckbox = chakra(FormFieldCheckbox); export default React.memo(FormFieldCheckbox) as typeof FormFieldCheckbox;
export type WrappedComponent = <
FormFields extends FieldValues,
Name extends Path<FormFields> = Path<FormFields>,
>(props: Props<FormFields, Name> & ChakraProps) => React.JSX.Element;
export default React.memo(WrappedFormFieldCheckbox) as WrappedComponent;
import { import { Box, Text } from '@chakra-ui/react';
Alert,
Box,
Button,
Text,
useDisclosure,
} from '@chakra-ui/react';
import { useMutation } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query';
import React, { useState } from 'react'; import React, { useState } from 'react';
import type { SubmitHandler } from 'react-hook-form'; import type { SubmitHandler } from 'react-hook-form';
...@@ -15,6 +9,9 @@ import type { WatchlistAddress, WatchlistErrors } from 'types/api/account'; ...@@ -15,6 +9,9 @@ import type { WatchlistAddress, WatchlistErrors } from 'types/api/account';
import type { ResourceErrorAccount } from 'lib/api/resources'; import type { ResourceErrorAccount } from 'lib/api/resources';
import useApiFetch from 'lib/api/useApiFetch'; import useApiFetch from 'lib/api/useApiFetch';
import getErrorMessage from 'lib/getErrorMessage'; import getErrorMessage from 'lib/getErrorMessage';
import { Alert } from 'toolkit/chakra/alert';
import { Button } from 'toolkit/chakra/button';
import { useDisclosure } from 'toolkit/hooks/useDisclosure';
import FormFieldAddress from 'ui/shared/forms/fields/FormFieldAddress'; import FormFieldAddress from 'ui/shared/forms/fields/FormFieldAddress';
import FormFieldCheckbox from 'ui/shared/forms/fields/FormFieldCheckbox'; import FormFieldCheckbox from 'ui/shared/forms/fields/FormFieldCheckbox';
import FormFieldText from 'ui/shared/forms/fields/FormFieldText'; import FormFieldText from 'ui/shared/forms/fields/FormFieldText';
...@@ -138,25 +135,24 @@ const AddressForm: React.FC<Props> = ({ data, onSuccess, setAlertVisible, isAdd ...@@ -138,25 +135,24 @@ const AddressForm: React.FC<Props> = ({ data, onSuccess, setAlertVisible, isAdd
<form noValidate onSubmit={ formApi.handleSubmit(onSubmit) }> <form noValidate onSubmit={ formApi.handleSubmit(onSubmit) }>
<FormFieldAddress<Inputs> <FormFieldAddress<Inputs>
name="address" name="address"
isRequired required
bgColor="dialog_bg" bgColor="dialog.bg"
mb={ 5 } mb={ 5 }
/> />
<FormFieldText<Inputs> <FormFieldText<Inputs>
name="tag" name="tag"
placeholder="Private tag (max 35 characters)" placeholder="Private tag (max 35 characters)"
isRequired required
rules={{ rules={{
maxLength: TAG_MAX_LENGTH, maxLength: TAG_MAX_LENGTH,
}} }}
bgColor="dialog_bg" bgColor="dialog.bg"
mb={ 8 } mb={ 8 }
/> />
{ userWithoutEmail ? ( { userWithoutEmail ? (
<> <>
<Alert <Alert
status="info" status="info"
colorScheme="gray"
display="flex" display="flex"
flexDirection={{ base: 'column', md: 'row' }} flexDirection={{ base: 'column', md: 'row' }}
alignItems={{ base: 'flex-start', lg: 'center' }} alignItems={{ base: 'flex-start', lg: 'center' }}
...@@ -167,17 +163,17 @@ const AddressForm: React.FC<Props> = ({ data, onSuccess, setAlertVisible, isAdd ...@@ -167,17 +163,17 @@ const AddressForm: React.FC<Props> = ({ data, onSuccess, setAlertVisible, isAdd
To receive notifications you need to add an email to your profile. To receive notifications you need to add an email to your profile.
<Button variant="outline" size="sm" onClick={ authModal.onOpen }>Add email</Button> <Button variant="outline" size="sm" onClick={ authModal.onOpen }>Add email</Button>
</Alert> </Alert>
{ authModal.isOpen && <AuthModal initialScreen={{ type: 'email', isAuth: true }} onClose={ authModal.onClose }/> } { authModal.open && <AuthModal initialScreen={{ type: 'email', isAuth: true }} onClose={ authModal.onClose }/> }
</> </>
) : ( ) : (
<> <>
<Text variant="secondary" fontSize="sm" marginBottom={ 5 }> <Text color="text.secondary" fontSize="sm" marginBottom={ 5 }>
Please select what types of notifications you will receive Please select what types of notifications you will receive
</Text> </Text>
<Box marginBottom={ 8 }> <Box marginBottom={ 8 }>
<AddressFormNotifications/> <AddressFormNotifications/>
</Box> </Box>
<Text variant="secondary" fontSize="sm" marginBottom={{ base: '10px', lg: 5 }}>Notification methods</Text> <Text color="text.secondary" fontSize="sm" marginBottom={{ base: '10px', lg: 5 }}>Notification methods</Text>
<FormFieldCheckbox<Inputs, 'notification'> <FormFieldCheckbox<Inputs, 'notification'>
name="notification" name="notification"
label="Email notifications" label="Email notifications"
...@@ -188,8 +184,8 @@ const AddressForm: React.FC<Props> = ({ data, onSuccess, setAlertVisible, isAdd ...@@ -188,8 +184,8 @@ const AddressForm: React.FC<Props> = ({ data, onSuccess, setAlertVisible, isAdd
<Button <Button
size="lg" size="lg"
type="submit" type="submit"
isLoading={ pending } loading={ pending }
isDisabled={ !formApi.formState.isDirty } disabled={ !formApi.formState.isDirty }
> >
{ !isAdd ? 'Save changes' : 'Add address' } { !isAdd ? 'Save changes' : 'Add address' }
</Button> </Button>
......
...@@ -8,13 +8,13 @@ import AddressForm from './AddressForm'; ...@@ -8,13 +8,13 @@ import AddressForm from './AddressForm';
type Props = { type Props = {
isAdd: boolean; isAdd: boolean;
isOpen: boolean; open: boolean;
onClose: () => void; onOpenChange: ({ open }: { open: boolean }) => void;
onSuccess: () => Promise<void>; onSuccess: () => Promise<void>;
data?: Partial<WatchlistAddress>; data?: Partial<WatchlistAddress>;
}; };
const AddressModal: React.FC<Props> = ({ isOpen, onClose, onSuccess, data, isAdd }) => { const AddressModal: React.FC<Props> = ({ open, onOpenChange, onSuccess, data, isAdd }) => {
const title = !isAdd ? 'Edit watch list address' : 'New address to watch list'; const title = !isAdd ? 'Edit watch list address' : 'New address to watch list';
const text = isAdd ? 'An email notification can be sent to you when an address on your watch list sends or receives any transactions.' : ''; const text = isAdd ? 'An email notification can be sent to you when an address on your watch list sends or receives any transactions.' : '';
...@@ -26,8 +26,8 @@ const AddressModal: React.FC<Props> = ({ isOpen, onClose, onSuccess, data, isAdd ...@@ -26,8 +26,8 @@ const AddressModal: React.FC<Props> = ({ isOpen, onClose, onSuccess, data, isAdd
return ( return (
<FormModal<WatchlistAddress> <FormModal<WatchlistAddress>
isOpen={ isOpen } open={ open }
onClose={ onClose } onOpenChange={ onOpenChange }
title={ title } title={ title }
text={ text } text={ text }
renderForm={ renderForm } renderForm={ renderForm }
......
...@@ -8,13 +8,13 @@ import useIsMobile from 'lib/hooks/useIsMobile'; ...@@ -8,13 +8,13 @@ import useIsMobile from 'lib/hooks/useIsMobile';
import DeleteModal from 'ui/shared/DeleteModal'; import DeleteModal from 'ui/shared/DeleteModal';
type Props = { type Props = {
isOpen: boolean; open: boolean;
onClose: () => void; onOpenChange: ({ open }: { open: boolean }) => void;
onSuccess: () => Promise<void>; onSuccess: () => Promise<void>;
data: Pick<WatchlistAddress, 'address_hash' | 'id'>; data: Pick<WatchlistAddress, 'address_hash' | 'id'>;
}; };
const DeleteAddressModal: React.FC<Props> = ({ isOpen, onClose, onSuccess, data }) => { const DeleteAddressModal: React.FC<Props> = ({ open, onOpenChange, onSuccess, data }) => {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const apiFetch = useApiFetch(); const apiFetch = useApiFetch();
...@@ -36,8 +36,8 @@ const DeleteAddressModal: React.FC<Props> = ({ isOpen, onClose, onSuccess, data ...@@ -36,8 +36,8 @@ const DeleteAddressModal: React.FC<Props> = ({ isOpen, onClose, onSuccess, data
return ( return (
<DeleteModal <DeleteModal
isOpen={ isOpen } open={ open }
onClose={ onClose } onOpenChange={ onOpenChange }
title="Remove address from watch list" title="Remove address from watch list"
renderContent={ renderModalContent } renderContent={ renderModalContent }
mutationFn={ mutationFn } mutationFn={ mutationFn }
......
...@@ -8,7 +8,7 @@ import config from 'configs/app'; ...@@ -8,7 +8,7 @@ import config from 'configs/app';
import getCurrencyValue from 'lib/getCurrencyValue'; import getCurrencyValue from 'lib/getCurrencyValue';
import { nbsp } from 'lib/html-entities'; import { nbsp } from 'lib/html-entities';
import { currencyUnits } from 'lib/units'; import { currencyUnits } from 'lib/units';
import Skeleton from 'ui/shared/chakra/Skeleton'; import { Skeleton } from 'toolkit/chakra/skeleton';
import CurrencyValue from 'ui/shared/CurrencyValue'; import CurrencyValue from 'ui/shared/CurrencyValue';
import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import * as TokenEntity from 'ui/shared/entities/token/TokenEntity'; import * as TokenEntity from 'ui/shared/entities/token/TokenEntity';
...@@ -26,7 +26,7 @@ const WatchListAddressItem = ({ item, isLoading }: { item: WatchlistAddress; isL ...@@ -26,7 +26,7 @@ const WatchListAddressItem = ({ item, isLoading }: { item: WatchlistAddress; isL
const { usdBn: usdNative } = getCurrencyValue({ value: item.address_balance, accuracy: 2, accuracyUsd: 2, exchangeRate: item.exchange_rate }); const { usdBn: usdNative } = getCurrencyValue({ value: item.address_balance, accuracy: 2, accuracyUsd: 2, exchangeRate: item.exchange_rate });
return ( return (
<VStack spacing={ 3 } align="stretch" fontWeight={ 500 }> <VStack gap={ 3 } align="stretch" fontWeight={ 500 }>
<AddressEntity <AddressEntity
address={ item.address } address={ item.address }
isLoading={ isLoading } isLoading={ isLoading }
...@@ -38,7 +38,7 @@ const WatchListAddressItem = ({ item, isLoading }: { item: WatchlistAddress; isL ...@@ -38,7 +38,7 @@ const WatchListAddressItem = ({ item, isLoading }: { item: WatchlistAddress; isL
token={ nativeTokenData } token={ nativeTokenData }
isLoading={ isLoading } isLoading={ isLoading }
/> />
<Skeleton isLoaded={ !isLoading } whiteSpace="pre" display="inline-flex"> <Skeleton loading={ isLoading } whiteSpace="pre" display="inline-flex">
<span>{ currencyUnits.ether } balance: </span> <span>{ currencyUnits.ether } balance: </span>
<CurrencyValue <CurrencyValue
value={ item.address_balance } value={ item.address_balance }
...@@ -49,19 +49,19 @@ const WatchListAddressItem = ({ item, isLoading }: { item: WatchlistAddress; isL ...@@ -49,19 +49,19 @@ const WatchListAddressItem = ({ item, isLoading }: { item: WatchlistAddress; isL
/> />
</Skeleton> </Skeleton>
</Flex> </Flex>
{ item.tokens_count && ( { Boolean(item.tokens_count) && (
<HStack spacing={ 2 } fontSize="sm" pl={ 7 }> <HStack gap={ 2 } fontSize="sm" pl={ 7 }>
<IconSvg name="tokens" boxSize={ 5 } isLoading={ isLoading } borderRadius="sm"/> <IconSvg name="tokens" boxSize={ 5 } isLoading={ isLoading } borderRadius="sm"/>
<Skeleton isLoaded={ !isLoading } display="inline-flex"> <Skeleton loading={ isLoading } display="inline-flex">
<span>{ `Tokens:${ nbsp }` + item.tokens_count + (item.tokens_overflow ? '+' : '') }</span> <span>{ `Tokens:${ nbsp }` + item.tokens_count + (item.tokens_overflow ? '+' : '') }</span>
<Text variant="secondary" fontWeight={ 400 }>{ `${ nbsp }($${ BigNumber(item.tokens_fiat_value).toFormat(2) })` }</Text> <Text color="text.secondary">{ `${ nbsp }($${ BigNumber(item.tokens_fiat_value).toFormat(2) })` }</Text>
</Skeleton> </Skeleton>
</HStack> </HStack>
) } ) }
{ item.tokens_fiat_value && ( { Boolean(item.tokens_fiat_value) && (
<HStack spacing={ 2 } fontSize="sm" pl={ 7 }> <HStack gap={ 2 } fontSize="sm" pl={ 7 }>
<IconSvg boxSize={ 5 } name="wallet" isLoading={ isLoading }/> <IconSvg boxSize={ 5 } name="wallet" isLoading={ isLoading }/>
<Skeleton isLoaded={ !isLoading } display="inline-flex"> <Skeleton loading={ isLoading } display="inline-flex">
<Text>{ `Net worth:${ nbsp }` } <Text>{ `Net worth:${ nbsp }` }
{ {
`${ item.tokens_overflow ? '>' : '' } `${ item.tokens_overflow ? '>' : '' }
......
import { Box, Switch, Text, HStack, Flex } from '@chakra-ui/react'; import { Box, Text, HStack, Flex } from '@chakra-ui/react';
import { useMutation } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query';
import React, { useCallback, useState } from 'react'; import React, { useCallback, useState } from 'react';
import type { WatchlistAddress } from 'types/api/account'; import type { WatchlistAddress } from 'types/api/account';
import useApiFetch from 'lib/api/useApiFetch'; import useApiFetch from 'lib/api/useApiFetch';
import useToast from 'lib/hooks/useToast'; import { Skeleton } from 'toolkit/chakra/skeleton';
import Skeleton from 'ui/shared/chakra/Skeleton'; import { Switch } from 'toolkit/chakra/switch';
import Tag from 'ui/shared/chakra/Tag'; import { Tag } from 'toolkit/chakra/tag';
import { toaster } from 'toolkit/chakra/toaster';
import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile';
import TableItemActionButtons from 'ui/shared/TableItemActionButtons'; import TableItemActionButtons from 'ui/shared/TableItemActionButtons';
...@@ -32,34 +33,21 @@ const WatchListItem = ({ item, isLoading, onEditClick, onDeleteClick, hasEmail } ...@@ -32,34 +33,21 @@ const WatchListItem = ({ item, isLoading, onEditClick, onDeleteClick, hasEmail }
return onDeleteClick(item); return onDeleteClick(item);
}, [ item, onDeleteClick ]); }, [ item, onDeleteClick ]);
const errorToast = useToast();
const apiFetch = useApiFetch(); const apiFetch = useApiFetch();
const showErrorToast = useCallback(() => { const showErrorToast = useCallback(() => {
errorToast({ toaster.error({
position: 'top-right', title: 'Error',
description: 'There has been an error processing your request', description: 'There has been an error processing your request',
colorScheme: 'red',
status: 'error',
variant: 'subtle',
isClosable: true,
icon: null,
}); });
}, [ errorToast ]); }, [ ]);
const notificationToast = useToast();
const showNotificationToast = useCallback((isOn: boolean) => { const showNotificationToast = useCallback((isOn: boolean) => {
notificationToast({ toaster.success({
position: 'top-right',
description: !isOn ? 'Email notification is ON' : 'Email notification is OFF',
colorScheme: 'green',
status: 'success',
variant: 'subtle',
title: 'Success', title: 'Success',
isClosable: true, description: !isOn ? 'Email notification is ON' : 'Email notification is OFF',
icon: null,
}); });
}, [ notificationToast ]); }, [ ]);
const { mutate } = useMutation({ const { mutate } = useMutation({
mutationFn: () => { mutationFn: () => {
...@@ -90,22 +78,22 @@ const WatchListItem = ({ item, isLoading, onEditClick, onDeleteClick, hasEmail } ...@@ -90,22 +78,22 @@ const WatchListItem = ({ item, isLoading, onEditClick, onDeleteClick, hasEmail }
<ListItemMobile> <ListItemMobile>
<Box maxW="100%"> <Box maxW="100%">
<WatchListAddressItem item={ item } isLoading={ isLoading }/> <WatchListAddressItem item={ item } isLoading={ isLoading }/>
<HStack spacing={ 3 } mt={ 6 }> <HStack gap={ 3 } mt={ 6 }>
<Text fontSize="sm" fontWeight={ 500 }>Private tag</Text> <Text textStyle="sm" fontWeight={ 500 }>Private tag</Text>
<Tag isLoading={ isLoading } isTruncated>{ item.name }</Tag> <Tag loading={ isLoading } truncated>{ item.name }</Tag>
</HStack> </HStack>
</Box> </Box>
<Flex alignItems="center" justifyContent="space-between" mt={ 6 } w="100%"> <Flex alignItems="center" justifyContent="space-between" mt={ 6 } w="100%">
<HStack spacing={ 3 }> <HStack gap={ 3 }>
<Text fontSize="sm" fontWeight={ 500 }>Email notification</Text> <Text textStyle="sm" fontWeight={ 500 }>Email notification</Text>
<Skeleton isLoaded={ !isLoading } display="inline-block"> <Skeleton loading={ isLoading } display="inline-block">
<Switch <Switch
colorScheme="blue" colorScheme="blue"
size="md" size="md"
isChecked={ notificationEnabled } checked={ notificationEnabled }
onChange={ onSwitch } onCheckedChange={ onSwitch }
aria-label="Email notification" aria-label="Email notification"
isDisabled={ !hasEmail || switchDisabled } disabled={ !hasEmail || switchDisabled }
/> />
</Skeleton> </Skeleton>
</HStack> </HStack>
......
import {
Tr,
Td,
Switch,
} from '@chakra-ui/react';
import { useMutation } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query';
import React, { useCallback, useState } from 'react'; import React, { useCallback, useState } from 'react';
import type { WatchlistAddress } from 'types/api/account'; import type { WatchlistAddress } from 'types/api/account';
import useApiFetch from 'lib/api/useApiFetch'; import useApiFetch from 'lib/api/useApiFetch';
import useToast from 'lib/hooks/useToast'; import { Skeleton } from 'toolkit/chakra/skeleton';
import Skeleton from 'ui/shared/chakra/Skeleton'; import { Switch } from 'toolkit/chakra/switch';
import Tag from 'ui/shared/chakra/Tag'; import { TableCell, TableRow } from 'toolkit/chakra/table';
import { Tag } from 'toolkit/chakra/tag';
import { toaster } from 'toolkit/chakra/toaster';
import TableItemActionButtons from 'ui/shared/TableItemActionButtons'; import TableItemActionButtons from 'ui/shared/TableItemActionButtons';
import WatchListAddressItem from './WatchListAddressItem'; import WatchListAddressItem from './WatchListAddressItem';
...@@ -35,34 +32,21 @@ const WatchlistTableItem = ({ item, isLoading, onEditClick, onDeleteClick, hasEm ...@@ -35,34 +32,21 @@ const WatchlistTableItem = ({ item, isLoading, onEditClick, onDeleteClick, hasEm
return onDeleteClick(item); return onDeleteClick(item);
}, [ item, onDeleteClick ]); }, [ item, onDeleteClick ]);
const errorToast = useToast();
const apiFetch = useApiFetch(); const apiFetch = useApiFetch();
const showErrorToast = useCallback(() => { const showErrorToast = useCallback(() => {
errorToast({ toaster.error({
position: 'top-right', title: 'Error',
description: 'There has been an error processing your request', description: 'There has been an error processing your request',
colorScheme: 'red',
status: 'error',
variant: 'subtle',
isClosable: true,
icon: null,
}); });
}, [ errorToast ]); }, [ ]);
const notificationToast = useToast();
const showNotificationToast = useCallback((isOn: boolean) => { const showNotificationToast = useCallback((isOn: boolean) => {
notificationToast({ toaster.success({
position: 'top-right',
description: !isOn ? 'Email notification is ON' : 'Email notification is OFF',
colorScheme: 'green',
status: 'success',
variant: 'subtle',
title: 'Success', title: 'Success',
isClosable: true, description: !isOn ? 'Email notification is ON' : 'Email notification is OFF',
icon: null,
}); });
}, [ notificationToast ]); }, [ ]);
const { mutate } = useMutation({ const { mutate } = useMutation({
mutationFn: () => { mutationFn: () => {
...@@ -90,27 +74,27 @@ const WatchlistTableItem = ({ item, isLoading, onEditClick, onDeleteClick, hasEm ...@@ -90,27 +74,27 @@ const WatchlistTableItem = ({ item, isLoading, onEditClick, onDeleteClick, hasEm
}, [ mutate ]); }, [ mutate ]);
return ( return (
<Tr alignItems="top" key={ item.address_hash }> <TableRow alignItems="top" key={ item.address_hash }>
<Td><WatchListAddressItem item={ item } isLoading={ isLoading }/></Td> <TableCell><WatchListAddressItem item={ item } isLoading={ isLoading }/></TableCell>
<Td> <TableCell>
<Tag isLoading={ isLoading } isTruncated>{ item.name }</Tag> <Tag loading={ isLoading } truncated>{ item.name }</Tag>
</Td> </TableCell>
<Td> <TableCell>
<Skeleton isLoaded={ !isLoading } display="inline-block"> <Skeleton loading={ isLoading } display="inline-block">
<Switch <Switch
colorScheme="blue" colorScheme="blue"
size="md" size="md"
isChecked={ notificationEnabled } checked={ notificationEnabled }
onChange={ onSwitch } onCheckedChange={ onSwitch }
isDisabled={ !hasEmail || switchDisabled } disabled={ !hasEmail || switchDisabled }
aria-label="Email notification" aria-label="Email notification"
/> />
</Skeleton> </Skeleton>
</Td> </TableCell>
<Td> <TableCell>
<TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick } isLoading={ isLoading }/> <TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick } isLoading={ isLoading }/>
</Td> </TableCell>
</Tr> </TableRow>
); );
}; };
......
import {
Table,
Tbody,
Tr,
Th,
} from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { WatchlistAddress } from 'types/api/account'; import type { WatchlistAddress } from 'types/api/account';
import TheadSticky from 'ui/shared/TheadSticky'; import { TableBody, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table';
import WatchlistTableItem from './WatchListTableItem'; import WatchlistTableItem from './WatchListTableItem';
...@@ -23,16 +17,16 @@ interface Props { ...@@ -23,16 +17,16 @@ interface Props {
const WatchlistTable = ({ data, isLoading, onDeleteClick, onEditClick, top, hasEmail }: Props) => { const WatchlistTable = ({ data, isLoading, onDeleteClick, onEditClick, top, hasEmail }: Props) => {
return ( return (
<Table minWidth="600px"> <TableRoot minWidth="600px">
<TheadSticky top={ top }> <TableHeaderSticky top={ top }>
<Tr> <TableRow>
<Th width="70%">Address</Th> <TableColumnHeader width="70%">Address</TableColumnHeader>
<Th width="30%">Private tag</Th> <TableColumnHeader width="30%">Private tag</TableColumnHeader>
<Th width="160px">Email notification</Th> <TableColumnHeader width="160px">Email notification</TableColumnHeader>
<Th width="108px"></Th> <TableColumnHeader width="108px"></TableColumnHeader>
</Tr> </TableRow>
</TheadSticky> </TableHeaderSticky>
<Tbody> <TableBody>
{ data?.map((item, index) => ( { data?.map((item, index) => (
<WatchlistTableItem <WatchlistTableItem
key={ item.address_hash + (isLoading ? index : '') } key={ item.address_hash + (isLoading ? index : '') }
...@@ -43,8 +37,8 @@ const WatchlistTable = ({ data, isLoading, onDeleteClick, onEditClick, top, hasE ...@@ -43,8 +37,8 @@ const WatchlistTable = ({ data, isLoading, onDeleteClick, onEditClick, top, hasE
hasEmail={ hasEmail } hasEmail={ hasEmail }
/> />
)) } )) }
</Tbody> </TableBody>
</Table> </TableRoot>
); );
}; };
......
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