Commit 2d99aba9 authored by tom's avatar tom

watchlist page refactoring

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