mirror of
https://github.com/luxfi/explore.git
synced 2026-09-24 00:28:18 +00:00
watchlist page refactoring
This commit is contained in:
+1
-1
@@ -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 }
|
||||
|
||||
+38
-41
@@ -1,4 +1,4 @@
|
||||
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 }
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
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;
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
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 ? '>' : '' }
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
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>
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user