Commit af2ec198 authored by tom's avatar tom

watchlist page

parent ba040562
import { Box, Button, Text, Skeleton, useDisclosure } from '@chakra-ui/react';
import { Box, Button, Skeleton, useDisclosure } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import React, { useCallback, useState } from 'react';
import type { TWatchlist, TWatchlistItem } from 'types/client/account';
import fetch from 'lib/client/fetch';
import useIsMobile from 'lib/hooks/useIsMobile';
import AccountPageDescription from 'ui/shared/AccountPageDescription';
import AccountPageHeader from 'ui/shared/AccountPageHeader';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import Page from 'ui/shared/Page/Page';
import SkeletonTable from 'ui/shared/SkeletonTable';
import AddressModal from 'ui/watchlist/AddressModal/AddressModal';
import DeleteAddressModal from 'ui/watchlist/DeleteAddressModal';
import WatchListItem from 'ui/watchlist/WatchlistTable/WatchListItem';
import WatchlistTable from 'ui/watchlist/WatchlistTable/WatchlistTable';
const WatchList: React.FC = () => {
......@@ -19,6 +22,7 @@ const WatchList: React.FC = () => {
const addressModalProps = useDisclosure();
const deleteModalProps = useDisclosure();
const isMobile = useIsMobile();
const [ addressModalData, setAddressModalData ] = useState<TWatchlistItem>();
const [ deleteModalData, setDeleteModalData ] = useState<TWatchlistItem>();
......@@ -44,9 +48,9 @@ const WatchList: React.FC = () => {
}, [ deleteModalProps ]);
const description = (
<Text marginBottom={ 12 }>
<AccountPageDescription>
An email notification can be sent to you when an address on your watch list sends or receives any transactions.
</Text>
</AccountPageDescription>
);
let content;
......@@ -61,15 +65,29 @@ const WatchList: React.FC = () => {
} else if (isError) {
content = <DataFetchAlert/>;
} else {
content = (
<>
{ Boolean(data?.length) && (
const list = isMobile ? (
<Box>
{ data.map((item) => (
<WatchListItem
item={ item }
key={ item.address_hash }
onDeleteClick={ onDeleteClick }
onEditClick={ onEditClick }
/>
)) }
</Box>
) : (
<WatchlistTable
data={ data }
onDeleteClick={ onDeleteClick }
onEditClick={ onEditClick }
/>
) }
);
content = (
<>
{ description }
{ Boolean(data?.length) && list }
<Box marginTop={ 8 }>
<Button
variant="primary"
......
import { Tooltip, IconButton, Icon } from '@chakra-ui/react';
import React, { useCallback } from 'react';
import DeleteIcon from 'icons/delete.svg';
type Props = {
onClick: () => void;
}
const DeleteButton = ({ onClick }: Props) => {
const onFocusCapture = useCallback((e: React.SyntheticEvent) => e.stopPropagation(), []);
return (
<Tooltip label="Delete">
<IconButton
aria-label="delete"
variant="icon"
w="30px"
h="30px"
onClick={ onClick }
icon={ <Icon as={ DeleteIcon } w="20px" h="20px"/> }
onFocusCapture={ onFocusCapture }
/>
</Tooltip>
);
};
export default DeleteButton;
import { Tooltip, IconButton, Icon } from '@chakra-ui/react';
import React, { useCallback } from 'react';
import EditIcon from 'icons/edit.svg';
type Props = {
onClick: () => void;
}
const EditButton = ({ onClick }: Props) => {
const onFocusCapture = useCallback((e: React.SyntheticEvent) => e.stopPropagation(), []);
return (
<Tooltip label="Edit">
<IconButton
aria-label="edit"
variant="icon"
w="30px"
h="30px"
onClick={ onClick }
icon={ <Icon as={ EditIcon } w="20px" h="20px"/> }
onFocusCapture={ onFocusCapture }
/>
</Tooltip>
);
};
export default EditButton;
......@@ -46,7 +46,7 @@ export default function FormModal<TData>({
<ModalCloseButton/>
<ModalBody mb={ 0 }>
{ (isAlertVisible || text) && (
<Box marginBottom={ 12 }>
<Box marginBottom={{ base: 6, lg: 12 }}>
{ text && (
<Text lineHeight="30px" mb={ 3 }>
{ text }
......
......@@ -150,7 +150,7 @@ const AddressForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
return (
<>
<Box marginBottom={ 5 } marginTop={ 5 }>
<Box marginBottom={ 5 }>
<Controller
name="address"
control={ control }
......@@ -176,7 +176,7 @@ const AddressForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
<Box marginBottom={ 8 }>
<AddressFormNotifications control={ control }/>
</Box>
<Text variant="secondary" fontSize="sm" marginBottom={ 5 }>Notification methods</Text>
<Text variant="secondary" fontSize="sm" marginBottom={{ base: '10px', lg: 5 }}>Notification methods</Text>
<Controller
name={ 'notification' as Checkboxes }
control={ control }
......
......@@ -20,13 +20,21 @@ export default function AddressFormNotifications<Inputs extends FieldValues, Che
), []);
return (
<Grid templateColumns="repeat(3, max-content)" gap="20px 24px">
<Grid templateColumns={{ base: 'repeat(2, max-content)', lg: 'repeat(3, max-content)' }} gap={{ base: '10px 24px', lg: '20px 24px' }}>
{ NOTIFICATIONS.map((notification: string, index: number) => {
const incomingFieldName = `notification_settings.${ notification }.incoming` as Checkboxes;
const outgoingFieldName = `notification_settings.${ notification }.outcoming` as Checkboxes;
return (
<React.Fragment key={ notification }>
<GridItem>{ NOTIFICATIONS_NAMES[index] }</GridItem>
<GridItem
gridColumnStart={{ base: 1, lg: 1 }}
gridColumnEnd={{ base: 3, lg: 1 }}
_notFirst={{
mt: { base: 3, lg: 0 },
}}
>
{ NOTIFICATIONS_NAMES[index] }
</GridItem>
<GridItem>
<Controller
name={ incomingFieldName }
......
......@@ -5,6 +5,7 @@ import React, { useCallback } from 'react';
import type { TWatchlistItem, TWatchlist } from 'types/client/account';
import fetch from 'lib/client/fetch';
import useIsMobile from 'lib/hooks/useIsMobile';
import DeleteModal from 'ui/shared/DeleteModal';
type Props = {
......@@ -15,6 +16,7 @@ type Props = {
const DeleteAddressModal: React.FC<Props> = ({ isOpen, onClose, data }) => {
const queryClient = useQueryClient();
const isMobile = useIsMobile();
const mutationFn = useCallback(() => {
return fetch(`/api/account1/watchlist/${ data?.id }`, { method: 'DELETE' });
......@@ -29,10 +31,11 @@ const DeleteAddressModal: React.FC<Props> = ({ isOpen, onClose, data }) => {
const address = data?.address_hash;
const renderModalContent = useCallback(() => {
const addressString = isMobile ? [ address.slice(0, 4), address.slice(-4) ].join('...') : address;
return (
<Text display="flex">Address <Text fontWeight="600" whiteSpace="pre"> { address || 'address' } </Text> will be deleted</Text>
<Text display="flex">Address <Text fontWeight="600" whiteSpace="pre"> { addressString || 'address' } </Text> will be deleted</Text>
);
}, [ address ]);
}, [ address, isMobile ]);
return (
<DeleteModal
......
......@@ -6,8 +6,7 @@ import type { TWatchlistItem } from 'types/client/account';
import TokensIcon from 'icons/tokens.svg';
// import WalletIcon from 'icons/wallet.svg';
import { nbsp } from 'lib/html-entities';
import AddressIcon from 'ui/shared/AddressIcon';
import AddressLinkWithTooltip from 'ui/shared/AddressLinkWithTooltip';
import AddressSnippet from 'ui/shared/AddressSnippet';
// now this component works only for xDAI
// for other networks later we will use config or smth
......@@ -18,19 +17,18 @@ const WatchListAddressItem = ({ item }: {item: TWatchlistItem}) => {
const nativeBalance = ((item.address_balance || 0) / 10 ** DECIMALS).toFixed(1);
const nativeBalanceUSD = item.exchange_rate ? `$${ Number(nativeBalance) * item.exchange_rate } USD` : 'N/A';
const infoItemsPaddingLeft = { base: 0, lg: 10 };
return (
<HStack spacing={ 3 } align="top">
<AddressIcon address={ item.address_hash }/>
<VStack spacing={ 2 } align="stretch" overflow="hidden" fontWeight={ 500 } color="gray.700">
<AddressLinkWithTooltip address={ item.address_hash }/>
<HStack spacing={ 0 } fontSize="sm" h={ 6 }>
<AddressSnippet address={ item.address_hash }/>
<HStack spacing={ 0 } fontSize="sm" h={ 6 } pl={ infoItemsPaddingLeft }>
<Image src="/xdai.png" alt="chain-logo" marginRight="10px" w="16px" h="16px"/>
<Text color={ mainTextColor }>{ `xDAI balance:${ nbsp }` + nativeBalance }</Text>
<Text variant="secondary">{ `${ nbsp }(${ nativeBalanceUSD })` }</Text>
</HStack>
{ item.tokens_count && (
<HStack spacing={ 0 } fontSize="sm" h={ 6 }>
<HStack spacing={ 0 } fontSize="sm" h={ 6 } pl={ infoItemsPaddingLeft }>
<Icon as={ TokensIcon } marginRight="10px" w="17px" h="16px"/>
<Text color={ mainTextColor }>{ `Tokens:${ nbsp }` + item.tokens_count }</Text>
{ /* api does not provide token prices */ }
......@@ -40,14 +38,13 @@ const WatchListAddressItem = ({ item }: {item: TWatchlistItem}) => {
) }
{ /* api does not provide token prices */ }
{ /* { item.address_balance && (
<HStack spacing={ 0 } fontSize="sm" h={ 6 }>
<HStack spacing={ 0 } fontSize="sm" h={ 6 } pl={ infoItemsPaddingLeft }>
<Icon as={ WalletIcon } marginRight="10px" w="16px" h="16px"/>
<Text color={ mainTextColor }>{ `Net worth:${ nbsp }` }</Text>
<Link href="#">{ `$${ item.totalUSD } USD` }</Link>
</HStack>
) } */ }
</VStack>
</HStack>
);
};
......
import { Tag, Box, Switch, Text, HStack, Flex } from '@chakra-ui/react';
import { useMutation } from '@tanstack/react-query';
import React, { useCallback, useState } from 'react';
import type { TWatchlistItem } from 'types/client/account';
import AccountListItemMobile from 'ui/shared/AccountListItemMobile';
import TableItemActionButtons from 'ui/shared/TableItemActionButtons';
import WatchListAddressItem from './WatchListAddressItem';
interface Props {
item: TWatchlistItem;
onEditClick: (data: TWatchlistItem) => void;
onDeleteClick: (data: TWatchlistItem) => void;
}
const WatchListItem = ({ item, onEditClick, onDeleteClick }: Props) => {
const [ notificationEnabled, setNotificationEnabled ] = useState(item.notification_methods.email);
const onItemEditClick = useCallback(() => {
return onEditClick(item);
}, [ item, onEditClick ]);
const onItemDeleteClick = useCallback(() => {
return onDeleteClick(item);
}, [ item, onDeleteClick ]);
const { mutate } = useMutation(() => {
const data = { ...item, notification_methods: { email: !notificationEnabled } };
return fetch(`/api/account/watchlist/${ item.id }`, { method: 'PUT', body: JSON.stringify(data) });
}, {
onError: () => {
// eslint-disable-next-line no-console
console.log('error');
},
onSuccess: () => {
setNotificationEnabled(prevState => !prevState);
},
});
const onSwitch = useCallback(() => {
return mutate();
}, [ mutate ]);
return (
<AccountListItemMobile>
<Box maxW="100%">
<WatchListAddressItem item={ item }/>
<HStack spacing={ 3 } mt={ 6 }>
<Text fontSize="sm" fontWeight={ 500 }>Private tag</Text>
<Tag variant="gray" lineHeight="24px">
{ 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>
<Switch colorScheme="blue" size="md" isChecked={ notificationEnabled } onChange={ onSwitch }/>
</HStack>
<TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick }/>
</Flex>
</AccountListItemMobile>
);
};
export default WatchListItem;
......@@ -3,15 +3,13 @@ import {
Tr,
Td,
Switch,
HStack,
} from '@chakra-ui/react';
import { useMutation } from '@tanstack/react-query';
import React, { useCallback, useState } from 'react';
import type { TWatchlistItem } from 'types/client/account';
import DeleteButton from 'ui/shared/DeleteButton';
import EditButton from 'ui/shared/EditButton';
import TableItemActionButtons from 'ui/shared/TableItemActionButtons';
import TruncatedTextTooltip from 'ui/shared/TruncatedTextTooltip';
import WatchListAddressItem from './WatchListAddressItem';
......@@ -61,10 +59,7 @@ const WatchlistTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
</Td>
<Td><Switch colorScheme="blue" size="md" isChecked={ notificationEnabled } onChange={ onSwitch }/></Td>
<Td>
<HStack spacing={ 6 }>
<EditButton onClick={ onItemEditClick }/>
<DeleteButton onClick={ onItemDeleteClick }/>
</HStack>
<TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick }/>
</Td>
</Tr>
);
......
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