Commit bb7d3592 authored by tom's avatar tom

private tags page refactoring

parent fcde6016
......@@ -4,12 +4,12 @@ import React from 'react';
import PageNextJs from 'nextjs/PageNextJs';
// const PrivateTags = dynamic(() => import('ui/pages/PrivateTags'), { ssr: false });
const PrivateTags = dynamic(() => import('ui/pages/PrivateTags'), { ssr: false });
const Page: NextPage = () => {
return (
<PageNextJs pathname="/account/tag-address">
{ /* <PrivateTags/> */ }
<PrivateTags/>
</PageNextJs>
);
};
......
......@@ -418,10 +418,10 @@ const semanticTokens: ThemingConfig['semanticTokens'] = {
DEFAULT: { value: '{colors.red.500}' },
_dark: { value: '{colors.red.500}' },
},
dialog_bg: {
DEFAULT: { value: '{colors.white}' },
_dark: { value: '{colors.gray.900}' },
},
// dialog_bg: {
// DEFAULT: { value: '{colors.white}' },
// _dark: { value: '{colors.gray.900}' },
// },
},
shadows: {
popover: {
......
......@@ -4,7 +4,7 @@ export const recipe = defineSlotRecipe({
slots: [ 'backdrop', 'positioner', 'content', 'header', 'body', 'footer', 'title', 'description' ],
base: {
backdrop: {
bg: 'blackAlpha.500',
bg: 'blackAlpha.800',
pos: 'fixed',
left: 0,
top: 0,
......@@ -70,9 +70,9 @@ export const recipe = defineSlotRecipe({
alignItems: 'center',
justifyContent: 'flex-end',
gap: '3',
px: '6',
px: '0',
pt: '2',
pb: '4',
pb: '0',
},
title: {
textStyle: 'heading.md',
......
......@@ -25,6 +25,7 @@ import AlertsShowcase from 'ui/showcases/Alerts';
import BadgesShowcase from 'ui/showcases/Badges';
import ButtonShowcase from 'ui/showcases/Button';
import CheckboxesShowcase from 'ui/showcases/Checkbox';
import DialogsShowcase from 'ui/showcases/Dialog';
import LinksShowcase from 'ui/showcases/Links';
import MenusShowcase from 'ui/showcases/Menu';
import PaginationShowcase from 'ui/showcases/Pagination';
......@@ -53,6 +54,7 @@ const ChakraShowcases = () => {
<TabsTrigger value="badges">Badges</TabsTrigger>
<TabsTrigger value="buttons">Buttons</TabsTrigger>
<TabsTrigger value="checkboxes">Checkboxes</TabsTrigger>
<TabsTrigger value="dialogs">Dialogs</TabsTrigger>
<TabsTrigger value="links">Links</TabsTrigger>
<TabsTrigger value="menus">Menus</TabsTrigger>
<TabsTrigger value="pagination">Pagination</TabsTrigger>
......@@ -67,6 +69,7 @@ const ChakraShowcases = () => {
<AlertsShowcase/>
<BadgesShowcase/>
<ButtonShowcase/>
<DialogsShowcase/>
<CheckboxesShowcase/>
<LinksShowcase/>
<MenusShowcase/>
......
......@@ -2,10 +2,10 @@ import React from 'react';
import type { RoutedTab } from 'ui/shared/Tabs/types';
import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs';
import PrivateAddressTags from 'ui/privateTags/PrivateAddressTags';
import PrivateTransactionTags from 'ui/privateTags/PrivateTransactionTags';
import PageTitle from 'ui/shared/Page/PageTitle';
import RoutedTabs from 'ui/shared/Tabs/RoutedTabs';
import useRedirectForInvalidAuthToken from 'ui/snippets/auth/useRedirectForInvalidAuthToken';
const TABS: Array<RoutedTab> = [
......
import {
Box,
Button,
} from '@chakra-ui/react';
import { useMutation } from '@tanstack/react-query';
import React, { useState } from 'react';
import type { SubmitHandler } from 'react-hook-form';
......@@ -12,6 +8,7 @@ import type { AddressTag, AddressTagErrors } from 'types/api/account';
import type { ResourceErrorAccount } from 'lib/api/resources';
import useApiFetch from 'lib/api/useApiFetch';
import getErrorMessage from 'lib/getErrorMessage';
import { Button } from 'toolkit/chakra/button';
import FormFieldAddress from 'ui/shared/forms/fields/FormFieldAddress';
import FormFieldText from 'ui/shared/forms/fields/FormFieldText';
......@@ -19,7 +16,7 @@ const TAG_MAX_LENGTH = 35;
type Props = {
data?: Partial<AddressTag>;
onClose: () => void;
onOpenChange: ({ open }: { open: boolean }) => void;
onSuccess: () => Promise<void>;
setAlertVisible: (isAlertVisible: boolean) => void;
};
......@@ -29,7 +26,7 @@ type Inputs = {
tag: string;
};
const AddressForm: React.FC<Props> = ({ data, onClose, onSuccess, setAlertVisible }) => {
const AddressForm: React.FC<Props> = ({ data, onOpenChange, onSuccess, setAlertVisible }) => {
const apiFetch = useApiFetch();
const [ pending, setPending ] = useState(false);
const formApi = useForm<Inputs>({
......@@ -71,7 +68,7 @@ const AddressForm: React.FC<Props> = ({ data, onClose, onSuccess, setAlertVisibl
},
onSuccess: async() => {
await onSuccess();
onClose();
onOpenChange({ open: false });
setPending(false);
},
});
......@@ -87,30 +84,28 @@ const AddressForm: React.FC<Props> = ({ data, onClose, onSuccess, setAlertVisibl
<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 }
/>
<Box marginTop={ 8 }>
<Button
size="lg"
type="submit"
isDisabled={ !formApi.formState.isDirty }
isLoading={ pending }
>
{ data ? 'Save changes' : 'Add tag' }
</Button>
</Box>
<Button
size="lg"
type="submit"
disabled={ !formApi.formState.isDirty }
loading={ pending }
>
{ data ? 'Save changes' : 'Add tag' }
</Button>
</form>
</FormProvider>
);
......
......@@ -8,25 +8,25 @@ import FormModal from 'ui/shared/FormModal';
import AddressForm from './AddressForm';
type Props = {
isOpen: boolean;
onClose: () => void;
open: boolean;
onOpenChange: ({ open }: { open: boolean }) => void;
onSuccess: () => Promise<void>;
data?: Partial<AddressTag>;
pageType: string;
};
const AddressModal: React.FC<Props> = ({ isOpen, onClose, onSuccess, data, pageType }) => {
const AddressModal: React.FC<Props> = ({ open, onOpenChange, onSuccess, data, pageType }) => {
const title = data?.id ? 'Edit address tag' : 'New address tag';
const text = !data?.id ? 'Label any address with a private address tag (up to 35 chars) to customize your explorer experience.' : '';
const [ isAlertVisible, setAlertVisible ] = useState(false);
React.useEffect(() => {
isOpen && !data?.id && mixpanel.logEvent(
open && !data?.id && mixpanel.logEvent(
mixpanel.EventTypes.PRIVATE_TAG,
{ Action: 'Form opened', 'Page type': pageType, 'Tag type': 'Address' },
);
}, [ data?.id, isOpen, pageType ]);
}, [ data?.id, open, pageType ]);
const handleSuccess = React.useCallback(() => {
if (!data?.id) {
......@@ -39,12 +39,12 @@ const AddressModal: React.FC<Props> = ({ isOpen, onClose, onSuccess, data, pageT
}, [ data?.id, onSuccess, pageType ]);
const renderForm = useCallback(() => {
return <AddressForm data={ data } onClose={ onClose } onSuccess={ handleSuccess } setAlertVisible={ setAlertVisible }/>;
}, [ data, onClose, handleSuccess ]);
return <AddressForm data={ data } onOpenChange={ onOpenChange } onSuccess={ handleSuccess } setAlertVisible={ setAlertVisible }/>;
}, [ data, onOpenChange, handleSuccess ]);
return (
<FormModal<AddressTag>
isOpen={ isOpen }
onClose={ onClose }
open={ open }
onOpenChange={ onOpenChange }
title={ title }
text={ text }
renderForm={ renderForm }
......
import { Tag, Flex, HStack, Text } from '@chakra-ui/react';
import { Flex, HStack, Text } from '@chakra-ui/react';
import React, { useCallback } from 'react';
import type { AddressTag } from 'types/api/account';
import Skeleton from 'ui/shared/chakra/Skeleton';
import { Skeleton } from 'toolkit/chakra/skeleton';
import { Tag } from 'toolkit/chakra/tag';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile';
import TableItemActionButtons from 'ui/shared/TableItemActionButtons';
......@@ -33,9 +34,9 @@ const AddressTagListItem = ({ item, onEditClick, onDeleteClick, isLoading }: Pro
fontWeight="600"
w="100%"
/>
<HStack spacing={ 3 } mt={ 4 }>
<Text fontSize="sm" fontWeight={ 500 }>Private tag</Text>
<Skeleton isLoaded={ !isLoading } display="inline-block" borderRadius="sm">
<HStack gap={ 3 } mt={ 4 }>
<Text textStyle="sm" fontWeight="medium">Private tag</Text>
<Skeleton loading={ isLoading } display="inline-block" borderRadius="sm">
<Tag>
{ item.name }
</Tag>
......
import {
Table,
Tbody,
Tr,
Th,
} from '@chakra-ui/react';
import React from 'react';
import type { AddressTags, AddressTag } from 'types/api/account';
import TheadSticky from 'ui/shared/TheadSticky';
import { TableBody, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table';
import AddressTagTableItem from './AddressTagTableItem';
......@@ -22,15 +16,15 @@ interface Props {
const AddressTagTable = ({ data, onDeleteClick, onEditClick, isLoading, top }: Props) => {
return (
<Table minWidth="600px">
<TheadSticky top={ top }>
<Tr>
<Th width="60%">Address</Th>
<Th width="40%">Private tag</Th>
<Th width="116px"></Th>
</Tr>
</TheadSticky>
<Tbody>
<TableRoot minWidth="600px">
<TableHeaderSticky top={ top }>
<TableRow>
<TableColumnHeader width="60%">Address</TableColumnHeader>
<TableColumnHeader width="40%">Private tag</TableColumnHeader>
<TableColumnHeader width="116px"></TableColumnHeader>
</TableRow>
</TableHeaderSticky>
<TableBody>
{ data?.map((item: AddressTag, index: number) => (
<AddressTagTableItem
item={ item }
......@@ -40,8 +34,8 @@ const AddressTagTable = ({ data, onDeleteClick, onEditClick, isLoading, top }: P
isLoading={ isLoading }
/>
)) }
</Tbody>
</Table>
</TableBody>
</TableRoot>
);
};
......
import {
Tr,
Td,
} from '@chakra-ui/react';
import React, { useCallback } from 'react';
import type { AddressTag } from 'types/api/account';
import Tag from 'ui/shared/chakra/Tag';
import { TableCell, TableRow } from 'toolkit/chakra/table';
import { Tag } from 'toolkit/chakra/tag';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import TableItemActionButtons from 'ui/shared/TableItemActionButtons';
......@@ -27,22 +24,22 @@ const AddressTagTableItem = ({ item, onEditClick, onDeleteClick, isLoading }: Pr
}, [ item, onDeleteClick ]);
return (
<Tr alignItems="top" key={ item.id }>
<Td>
<TableRow alignItems="top" key={ item.id }>
<TableCell>
<AddressEntity
address={ item.address }
isLoading={ isLoading }
fontWeight="600"
py="2px"
/>
</Td>
<Td whiteSpace="nowrap">
<Tag isLoading={ isLoading } isTruncated>{ item.name }</Tag>
</Td>
<Td>
</TableCell>
<TableCell whiteSpace="nowrap">
<Tag loading={ isLoading } truncated>{ item.name }</Tag>
</TableCell>
<TableCell>
<TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick } isLoading={ isLoading }/>
</Td>
</Tr>
</TableCell>
</TableRow>
);
};
......
......@@ -9,13 +9,13 @@ import { getResourceKey } from 'lib/api/useApiQuery';
import DeleteModal from 'ui/shared/DeleteModal';
type Props = {
isOpen: boolean;
onClose: () => void;
open: boolean;
onOpenChange: ({ open }: { open: boolean }) => void;
data: AddressTag | TransactionTag;
type: 'address' | 'transaction';
};
const DeletePrivateTagModal: React.FC<Props> = ({ isOpen, onClose, data, type }) => {
const DeletePrivateTagModal: React.FC<Props> = ({ open, onOpenChange, data, type }) => {
const tag = data.name;
const id = data.id;
......@@ -53,8 +53,8 @@ const DeletePrivateTagModal: React.FC<Props> = ({ isOpen, onClose, data, type })
return (
<DeleteModal
isOpen={ isOpen }
onClose={ onClose }
open={ open }
onOpenChange={ onOpenChange }
title="Removal of private tag"
renderContent={ renderText }
mutationFn={ mutationFn }
......
import { Box, Button, useDisclosure } from '@chakra-ui/react';
import { Box } from '@chakra-ui/react';
import React, { useCallback, useState } from 'react';
import type { AddressTag } from 'types/api/account';
import { PAGE_TYPE_DICT } from 'lib/mixpanel/getPageType';
import { PRIVATE_TAG_ADDRESS } 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 Pagination from 'ui/shared/pagination/Pagination';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
......@@ -41,9 +43,9 @@ const PrivateAddressTags = () => {
await refetch();
}, [ refetch ]);
const onAddressModalClose = useCallback(() => {
setAddressModalData(undefined);
addressModalProps.onClose();
const onAddressModalOpenChange = useCallback(({ open }: { open: boolean }) => {
!open && setAddressModalData(undefined);
addressModalProps.onOpenChange({ open });
}, [ addressModalProps ]);
const onDeleteClick = useCallback((data: AddressTag) => {
......@@ -51,36 +53,11 @@ const PrivateAddressTags = () => {
deleteModalProps.onOpen();
}, [ deleteModalProps ]);
const onDeleteModalClose = useCallback(() => {
setDeleteModalData(undefined);
deleteModalProps.onClose();
const onDeleteModalOpenChange = useCallback(({ open }: { open: boolean }) => {
!open && setDeleteModalData(undefined);
deleteModalProps.onOpenChange({ open });
}, [ deleteModalProps ]);
const list = (
<>
<Box display={{ base: 'block', lg: 'none' }}>
{ addressTagsData?.items.map((item: AddressTag, index: number) => (
<AddressTagListItem
item={ item }
key={ item.id + (isPlaceholderData ? String(index) : '') }
onDeleteClick={ onDeleteClick }
onEditClick={ onEditClick }
isLoading={ isPlaceholderData }
/>
)) }
</Box>
<Box display={{ base: 'none', lg: 'block' }}>
<AddressTagTable
isLoading={ isPlaceholderData }
data={ addressTagsData?.items }
onDeleteClick={ onDeleteClick }
onEditClick={ onEditClick }
top={ pagination.isVisible ? ACTION_BAR_HEIGHT_DESKTOP : 0 }
/>
</Box>
</>
);
const actionBar = pagination.isVisible ? (
<ActionBar mt={ -6 }>
<Pagination ml="auto" { ...pagination }/>
......@@ -95,12 +72,32 @@ const PrivateAddressTags = () => {
</AccountPageDescription>
<DataListDisplay
isError={ isError }
items={ addressTagsData?.items }
itemsNum={ addressTagsData?.items.length }
emptyText=""
content={ list }
actionBar={ actionBar }
/>
<Skeleton mt={ 8 } isLoaded={ !isPlaceholderData } display="inline-block">
>
<Box display={{ base: 'block', lg: 'none' }}>
{ addressTagsData?.items.map((item: AddressTag, index: number) => (
<AddressTagListItem
item={ item }
key={ item.id + (isPlaceholderData ? String(index) : '') }
onDeleteClick={ onDeleteClick }
onEditClick={ onEditClick }
isLoading={ isPlaceholderData }
/>
)) }
</Box>
<Box display={{ base: 'none', lg: 'block' }}>
<AddressTagTable
isLoading={ isPlaceholderData }
data={ addressTagsData?.items }
onDeleteClick={ onDeleteClick }
onEditClick={ onEditClick }
top={ pagination.isVisible ? ACTION_BAR_HEIGHT_DESKTOP : 0 }
/>
</Box>
</DataListDisplay>
<Skeleton mt={ 8 } loading={ isPlaceholderData } display="inline-block">
<Button
size="lg"
onClick={ addressModalProps.onOpen }
......@@ -112,13 +109,13 @@ const PrivateAddressTags = () => {
{ ...addressModalProps }
data={ addressModalData }
pageType={ PAGE_TYPE_DICT['/account/tag-address'] }
onClose={ onAddressModalClose }
onOpenChange={ onAddressModalOpenChange }
onSuccess={ onAddOrEditSuccess }
/>
{ deleteModalData && (
<DeletePrivateTagModal
{ ...deleteModalProps }
onClose={ onDeleteModalClose }
onOpenChange={ onDeleteModalOpenChange }
data={ deleteModalData }
type="address"
/>
......
import { Box, Button, useDisclosure } from '@chakra-ui/react';
import { Box } from '@chakra-ui/react';
import React, { useCallback, useState } from 'react';
import type { TransactionTag } from 'types/api/account';
import { PRIVATE_TAG_TX } 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 Pagination from 'ui/shared/pagination/Pagination';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
......@@ -36,9 +38,9 @@ const PrivateTransactionTags = () => {
transactionModalProps.onOpen();
}, [ transactionModalProps ]);
const onAddressModalClose = useCallback(() => {
setTransactionModalData(undefined);
transactionModalProps.onClose();
const onAddressModalOpenChange = useCallback(({ open }: { open: boolean }) => {
!open && setTransactionModalData(undefined);
transactionModalProps.onOpenChange({ open });
}, [ transactionModalProps ]);
const onDeleteClick = useCallback((data: TransactionTag) => {
......@@ -46,9 +48,9 @@ const PrivateTransactionTags = () => {
deleteModalProps.onOpen();
}, [ deleteModalProps ]);
const onDeleteModalClose = useCallback(() => {
setDeleteModalData(undefined);
deleteModalProps.onClose();
const onDeleteModalOpenChange = useCallback(({ open }: { open: boolean }) => {
!open && setDeleteModalData(undefined);
deleteModalProps.onOpenChange({ open });
}, [ deleteModalProps ]);
const description = (
......@@ -58,30 +60,6 @@ const PrivateTransactionTags = () => {
</AccountPageDescription>
);
const list = (
<>
<Box display={{ base: 'block', lg: 'none' }}>
{ transactionTagsData?.items.map((item, index) => (
<TransactionTagListItem
key={ item.id + (isPlaceholderData ? String(index) : '') }
item={ item }
isLoading={ isPlaceholderData }
onDeleteClick={ onDeleteClick }
onEditClick={ onEditClick }
/>
)) }
</Box>
<Box display={{ base: 'none', lg: 'block' }}>
<TransactionTagTable
data={ transactionTagsData?.items }
isLoading={ isPlaceholderData }
onDeleteClick={ onDeleteClick }
onEditClick={ onEditClick }
top={ pagination.isVisible ? ACTION_BAR_HEIGHT_DESKTOP : 0 }
/>
</Box>
</>
);
const actionBar = pagination.isVisible ? (
<ActionBar mt={ -6 }>
<Pagination ml="auto" { ...pagination }/>
......@@ -93,12 +71,32 @@ const PrivateTransactionTags = () => {
{ description }
<DataListDisplay
isError={ isError }
items={ transactionTagsData?.items }
itemsNum={ transactionTagsData?.items.length }
emptyText=""
content={ list }
actionBar={ actionBar }
/>
<Skeleton mt={ 8 } isLoaded={ !isPlaceholderData } display="inline-block">
>
<Box display={{ base: 'block', lg: 'none' }}>
{ transactionTagsData?.items.map((item, index) => (
<TransactionTagListItem
key={ item.id + (isPlaceholderData ? String(index) : '') }
item={ item }
isLoading={ isPlaceholderData }
onDeleteClick={ onDeleteClick }
onEditClick={ onEditClick }
/>
)) }
</Box>
<Box display={{ base: 'none', lg: 'block' }}>
<TransactionTagTable
data={ transactionTagsData?.items }
isLoading={ isPlaceholderData }
onDeleteClick={ onDeleteClick }
onEditClick={ onEditClick }
top={ pagination.isVisible ? ACTION_BAR_HEIGHT_DESKTOP : 0 }
/>
</Box>
</DataListDisplay>
<Skeleton mt={ 8 } loading={ isPlaceholderData } display="inline-block">
<Button
size="lg"
onClick={ transactionModalProps.onOpen }
......@@ -106,11 +104,15 @@ const PrivateTransactionTags = () => {
Add transaction tag
</Button>
</Skeleton>
<TransactionModal { ...transactionModalProps } onClose={ onAddressModalClose } data={ transactionModalData }/>
<TransactionModal
{ ...transactionModalProps }
onOpenChange={ onAddressModalOpenChange }
data={ transactionModalData }
/>
{ deleteModalData && (
<DeletePrivateTagModal
{ ...deleteModalProps }
onClose={ onDeleteModalClose }
onOpenChange={ onDeleteModalOpenChange }
data={ deleteModalData }
type="transaction"
/>
......
import {
Box,
Button,
} from '@chakra-ui/react';
import { Box } from '@chakra-ui/react';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import React, { useState } from 'react';
import type { SubmitHandler } from 'react-hook-form';
......@@ -13,6 +10,7 @@ import type { ResourceErrorAccount } from 'lib/api/resources';
import { resourceKey } from 'lib/api/resources';
import useApiFetch from 'lib/api/useApiFetch';
import getErrorMessage from 'lib/getErrorMessage';
import { Button } from 'toolkit/chakra/button';
import FormFieldText from 'ui/shared/forms/fields/FormFieldText';
import { TRANSACTION_HASH_LENGTH, TRANSACTION_HASH_REGEXP } from 'ui/shared/forms/validators/transaction';
......@@ -20,7 +18,7 @@ const TAG_MAX_LENGTH = 35;
type Props = {
data?: Partial<TransactionTag>;
onClose: () => void;
onOpenChange: ({ open }: { open: boolean }) => void;
onSuccess: () => Promise<void>;
setAlertVisible: (isAlertVisible: boolean) => void;
};
......@@ -30,7 +28,7 @@ type Inputs = {
tag: string;
};
const TransactionForm: React.FC<Props> = ({ data, onClose, onSuccess, setAlertVisible }) => {
const TransactionForm: React.FC<Props> = ({ data, onOpenChange, onSuccess, setAlertVisible }) => {
const [ pending, setPending ] = useState(false);
const formApi = useForm<Inputs>({
......@@ -76,7 +74,7 @@ const TransactionForm: React.FC<Props> = ({ data, onClose, onSuccess, setAlertVi
onSuccess: async() => {
await queryClient.refetchQueries({ queryKey: [ resourceKey('private_tags_tx') ] });
await onSuccess();
onClose();
onOpenChange({ open: false });
setPending(false);
},
});
......@@ -92,30 +90,30 @@ const TransactionForm: React.FC<Props> = ({ data, onClose, onSuccess, setAlertVi
<FormFieldText<Inputs>
name="transaction"
placeholder="Transaction hash (0x...)"
isRequired
required
rules={{
maxLength: TRANSACTION_HASH_LENGTH,
pattern: TRANSACTION_HASH_REGEXP,
}}
bgColor="dialog_bg"
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 }
/>
<Box marginTop={ 8 }>
<Button
size="lg"
type="submit"
isDisabled={ !formApi.formState.isDirty }
isLoading={ pending }
disabled={ !formApi.formState.isDirty }
loading={ pending }
>
{ data ? 'Save changes' : 'Add tag' }
</Button>
......
......@@ -9,24 +9,24 @@ import FormModal from 'ui/shared/FormModal';
import TransactionForm from './TransactionForm';
type Props = {
isOpen: boolean;
onClose: () => void;
open: boolean;
onOpenChange: ({ open }: { open: boolean }) => void;
onSuccess?: () => Promise<void>;
data?: Partial<TransactionTag>;
};
const AddressModal: React.FC<Props> = ({ isOpen, onClose, onSuccess, data }) => {
const TransactionModal: React.FC<Props> = ({ open, onOpenChange, onSuccess, data }) => {
const title = data ? 'Edit transaction tag' : 'New transaction tag';
const text = !data ? 'Label any transaction with a private transaction tag (up to 35 chars) to customize your explorer experience.' : '';
const [ isAlertVisible, setAlertVisible ] = useState(false);
React.useEffect(() => {
isOpen && !data?.id && mixpanel.logEvent(
open && !data?.id && mixpanel.logEvent(
mixpanel.EventTypes.PRIVATE_TAG,
{ Action: 'Form opened', 'Page type': PAGE_TYPE_DICT['/account/tag-address'], 'Tag type': 'Tx' },
);
}, [ data?.id, isOpen ]);
}, [ data?.id, open ]);
const handleSuccess = React.useCallback(async() => {
onSuccess?.();
......@@ -39,12 +39,12 @@ const AddressModal: React.FC<Props> = ({ isOpen, onClose, onSuccess, data }) =>
}, [ data?.id, onSuccess ]);
const renderForm = useCallback(() => {
return <TransactionForm data={ data } onClose={ onClose } onSuccess={ handleSuccess } setAlertVisible={ setAlertVisible }/>;
}, [ data, handleSuccess, onClose ]);
return <TransactionForm data={ data } onOpenChange={ onOpenChange } onSuccess={ handleSuccess } setAlertVisible={ setAlertVisible }/>;
}, [ data, handleSuccess, onOpenChange ]);
return (
<FormModal<TransactionTag>
isOpen={ isOpen }
onClose={ onClose }
open={ open }
onOpenChange={ onOpenChange }
title={ title }
text={ text }
renderForm={ renderForm }
......@@ -54,4 +54,4 @@ const AddressModal: React.FC<Props> = ({ isOpen, onClose, onSuccess, data }) =>
);
};
export default AddressModal;
export default TransactionModal;
......@@ -3,7 +3,7 @@ import React, { useCallback } from 'react';
import type { TransactionTag } from 'types/api/account';
import Tag from 'ui/shared/chakra/Tag';
import { Tag } from 'toolkit/chakra/tag';
import TxEntity from 'ui/shared/entities/tx/TxEntity';
import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile';
import TableItemActionButtons from 'ui/shared/TableItemActionButtons';
......@@ -34,9 +34,9 @@ const TransactionTagListItem = ({ item, isLoading, onEditClick, onDeleteClick }:
fontWeight={ 600 }
maxW="100%"
/>
<HStack spacing={ 3 } mt={ 4 }>
<Text fontSize="sm" fontWeight={ 500 }>Private tag</Text>
<Tag isLoading={ isLoading } isTruncated>{ item.name }</Tag>
<HStack gap={ 3 } mt={ 4 }>
<Text textStyle="sm" fontWeight={ 500 }>Private tag</Text>
<Tag loading={ isLoading } truncated>{ item.name }</Tag>
</HStack>
</Flex>
<TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick } isLoading={ isLoading }/>
......
import {
Table,
Tbody,
Tr,
Th,
} from '@chakra-ui/react';
import React from 'react';
import type { TransactionTags, TransactionTag } from 'types/api/account';
import TheadSticky from 'ui/shared/TheadSticky';
import { TableBody, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table';
import TransactionTagTableItem from './TransactionTagTableItem';
......@@ -20,17 +14,17 @@ interface Props {
top: number;
}
const AddressTagTable = ({ data, isLoading, onDeleteClick, onEditClick, top }: Props) => {
const TransactionTagTable = ({ data, isLoading, onDeleteClick, onEditClick, top }: Props) => {
return (
<Table minWidth="600px">
<TheadSticky top={ top }>
<Tr>
<Th width="75%">Transaction</Th>
<Th width="25%">Private tag</Th>
<Th width="108px"></Th>
</Tr>
</TheadSticky>
<Tbody>
<TableRoot minWidth="600px">
<TableHeaderSticky top={ top }>
<TableRow>
<TableColumnHeader width="75%">Transaction</TableColumnHeader>
<TableColumnHeader width="25%">Private tag</TableColumnHeader>
<TableColumnHeader width="108px"></TableColumnHeader>
</TableRow>
</TableHeaderSticky>
<TableBody>
{ data?.map((item, index) => (
<TransactionTagTableItem
key={ item.id + (isLoading ? String(index) : '') }
......@@ -40,9 +34,9 @@ const AddressTagTable = ({ data, isLoading, onDeleteClick, onEditClick, top }: P
onEditClick={ onEditClick }
/>
)) }
</Tbody>
</Table>
</TableBody>
</TableRoot>
);
};
export default AddressTagTable;
export default TransactionTagTable;
import {
Tr,
Td,
} from '@chakra-ui/react';
import React, { useCallback } from 'react';
import type { TransactionTag } from 'types/api/account';
import Tag from 'ui/shared/chakra/Tag';
import { TableCell, TableRow } from 'toolkit/chakra/table';
import { Tag } from 'toolkit/chakra/tag';
import TxEntity from 'ui/shared/entities/tx/TxEntity';
import TableItemActionButtons from 'ui/shared/TableItemActionButtons';
......@@ -27,22 +24,22 @@ const TransactionTagTableItem = ({ item, isLoading, onEditClick, onDeleteClick }
}, [ item, onDeleteClick ]);
return (
<Tr alignItems="top" key={ item.id }>
<Td>
<TableRow alignItems="top" key={ item.id }>
<TableCell>
<TxEntity
hash={ item.transaction_hash }
isLoading={ isLoading }
noCopy={ false }
fontWeight={ 600 }
/>
</Td>
<Td>
<Tag isLoading={ isLoading } isTruncated>{ item.name }</Tag>
</Td>
<Td>
</TableCell>
<TableCell>
<Tag loading={ isLoading } truncated>{ item.name }</Tag>
</TableCell>
<TableCell>
<TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick } isLoading={ isLoading }/>
</Td>
</Tr>
</TableCell>
</TableRow>
);
};
......
......@@ -50,10 +50,10 @@ const AccountActionsMenu = ({ isLoading, className, showUpdateMetadataItem }: Pr
// render: (props: ItemProps) => <TokenInfoMenuItem { ...props }/>,
// enabled: config.features.account.isEnabled && isTokenPage && config.features.addressVerification.isEnabled && !userWithoutEmail,
// },
// {
// render: (props: ItemProps) => <PrivateTagMenuItem { ...props } entityType={ isTxPage ? 'tx' : 'address' }/>,
// enabled: config.features.account.isEnabled,
// },
{
render: (props: ItemProps) => <PrivateTagMenuItem { ...props } entityType={ isTxPage ? 'tx' : 'address' }/>,
enabled: config.features.account.isEnabled,
},
{
render: (props: ItemProps) => <PublicTagMenuItem { ...props }/>,
enabled: config.features.account.isEnabled && !isTxPage && config.features.publicTagsSubmission.isEnabled,
......
......@@ -46,8 +46,8 @@ const PrivateTagMenuItem = ({ hash, entityType = 'address', type }: Props) => {
const pageType = getPageType(router.pathname);
const modalProps = {
isOpen: modal.open,
onClose: modal.onClose,
open: modal.open,
onOpenChange: modal.onOpenChange,
onSuccess: handleAddPrivateTag,
pageType,
};
......
import { Box, useColorModeValue } from '@chakra-ui/react';
import { Box } from '@chakra-ui/react';
import { debounce } from 'es-toolkit';
import React, { useRef, useEffect, useState, useCallback } from 'react';
......@@ -37,11 +37,6 @@ const AccountPageDescription = ({ children, allowCut = true }: { children: React
setExpanded(true);
}, []);
const gradient = useColorModeValue(
'linear-gradient(360deg, rgba(255, 255, 255, 0.8) 38.1%, rgba(255, 255, 255, 0) 166.67%)',
'linear-gradient(360deg, rgba(16, 17, 18, 0.8) 38.1%, rgba(16, 17, 18, 0) 166.67%)',
);
return (
<Box position="relative" marginBottom={{ base: 6, lg: 8 }}>
<Box
......@@ -59,7 +54,10 @@ const AccountPageDescription = ({ children, allowCut = true }: { children: React
left={ 0 }
width="100%"
height="63px"
style={{ background: gradient }}
bgGradient={{
_light: 'linear-gradient(360deg, rgba(255, 255, 255, 0.8) 38.1%, rgba(255, 255, 255, 0) 166.67%)',
_dark: 'linear-gradient(360deg, rgba(16, 17, 18, 0.8) 38.1%, rgba(16, 17, 18, 0) 166.67%)',
}}
onClick={ expand }
>
</Box>
......
import {
Box,
Button,
Modal,
ModalOverlay,
ModalContent,
ModalFooter,
ModalHeader,
ModalBody,
ModalCloseButton,
} from '@chakra-ui/react';
import { Box } from '@chakra-ui/react';
import { useMutation } from '@tanstack/react-query';
import React, { useCallback, useState } from 'react';
import useIsMobile from 'lib/hooks/useIsMobile';
import { Button } from 'toolkit/chakra/button';
import { DialogBody, DialogContent, DialogFooter, DialogHeader, DialogRoot } from 'toolkit/chakra/dialog';
import FormSubmitAlert from 'ui/shared/FormSubmitAlert';
type Props = {
isOpen: boolean;
onClose: () => void;
open: boolean;
onOpenChange: ({ open }: { open: boolean }) => void;
title: string;
renderContent: () => React.JSX.Element;
mutationFn: () => Promise<unknown>;
......@@ -25,8 +17,8 @@ type Props = {
};
const DeleteModal: React.FC<Props> = ({
isOpen,
onClose,
open,
onOpenChange,
title,
renderContent,
mutationFn,
......@@ -34,16 +26,16 @@ const DeleteModal: React.FC<Props> = ({
}) => {
const [ isAlertVisible, setAlertVisible ] = useState(false);
const onModalClose = useCallback(() => {
setAlertVisible(false);
onClose();
}, [ onClose, setAlertVisible ]);
const onModalOpenChange = useCallback(({ open }: { open: boolean }) => {
!open && setAlertVisible(false);
onOpenChange({ open });
}, [ onOpenChange, setAlertVisible ]);
const { mutate, isPending } = useMutation({
mutationFn,
onSuccess: async() => {
onSuccess();
onClose();
onOpenChange({ open: false });
},
onError: () => {
setAlertVisible(true);
......@@ -58,28 +50,24 @@ const DeleteModal: React.FC<Props> = ({
const isMobile = useIsMobile();
return (
<Modal isOpen={ isOpen } onClose={ onModalClose } size={ isMobile ? 'full' : 'md' }>
<ModalOverlay/>
<ModalContent>
<ModalHeader fontWeight="500" textStyle="h3">{ title }</ModalHeader>
<ModalCloseButton/>
<ModalBody>
<DialogRoot open={ open } onOpenChange={ onModalOpenChange } size={ isMobile ? 'full' : 'md' }>
<DialogContent>
<DialogHeader fontWeight="500" textStyle="h3">{ title }</DialogHeader>
<DialogBody>
{ isAlertVisible && <Box mb={ 4 }><FormSubmitAlert/></Box> }
{ renderContent() }
</ModalBody>
<ModalFooter>
</DialogBody>
<DialogFooter>
<Button
size="lg"
onClick={ onDeleteClick }
isLoading={ isPending }
// FIXME: chackra's button is disabled when isLoading
isDisabled={ false }
loading={ isPending }
>
Delete
</Button>
</ModalFooter>
</ModalContent>
</Modal>
</DialogFooter>
</DialogContent>
</DialogRoot>
);
};
......
import {
Box,
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalBody,
ModalCloseButton,
Text,
} from '@chakra-ui/react';
import { Box, Text } from '@chakra-ui/react';
import React, { useCallback } from 'react';
import useIsMobile from 'lib/hooks/useIsMobile';
import { DialogBody, DialogContent, DialogHeader, DialogRoot } from 'toolkit/chakra/dialog';
import FormSubmitAlert from 'ui/shared/FormSubmitAlert';
interface Props<TData> {
isOpen: boolean;
onClose: () => void;
open: boolean;
onOpenChange: ({ open }: { open: boolean }) => void;
data?: TData;
title: string;
text?: string;
......@@ -25,8 +16,8 @@ interface Props<TData> {
}
export default function FormModal<TData>({
isOpen,
onClose,
open,
onOpenChange,
title,
text,
renderForm,
......@@ -34,20 +25,16 @@ export default function FormModal<TData>({
setAlertVisible,
}: Props<TData>) {
const onModalClose = useCallback(() => {
setAlertVisible && setAlertVisible(false);
onClose();
}, [ onClose, setAlertVisible ]);
const isMobile = useIsMobile();
const handleOpenChange = useCallback(({ open }: { open: boolean }) => {
!open && setAlertVisible?.(false);
onOpenChange({ open });
}, [ onOpenChange, setAlertVisible ]);
return (
<Modal isOpen={ isOpen } onClose={ onModalClose } size={ isMobile ? 'full' : 'md' }>
<ModalOverlay/>
<ModalContent>
<ModalHeader fontWeight="500" textStyle="h3">{ title }</ModalHeader>
<ModalCloseButton/>
<ModalBody>
<DialogRoot open={ open } onOpenChange={ handleOpenChange } size={{ lgDown: 'full', lg: 'md' }}>
<DialogContent>
<DialogHeader>{ title }</DialogHeader>
<DialogBody>
{ (isAlertVisible || text) && (
<Box marginBottom={{ base: 6, lg: 8 }}>
{ text && (
......@@ -59,8 +46,8 @@ export default function FormModal<TData>({
</Box>
) }
{ renderForm() }
</ModalBody>
</ModalContent>
</Modal>
</DialogBody>
</DialogContent>
</DialogRoot>
);
}
import { Alert, AlertDescription } from '@chakra-ui/react';
import React from 'react';
import { Alert } from 'toolkit/chakra/alert';
const FormSubmitAlert = () => {
return (
<Alert status="error">
<AlertDescription>
There has been an error processing your request
</AlertDescription>
There has been an error processing your request
</Alert>
);
};
......
import { Tooltip, IconButton, HStack } from '@chakra-ui/react';
import { HStack } from '@chakra-ui/react';
import React from 'react';
import usePreventFocusAfterModalClosing from 'lib/hooks/usePreventFocusAfterModalClosing';
import Skeleton from 'ui/shared/chakra/Skeleton';
import { IconButton } from 'toolkit/chakra/icon-button';
import { Tooltip } from 'toolkit/chakra/tooltip';
import IconSvg from 'ui/shared/IconSvg';
type Props = {
......@@ -14,42 +15,37 @@ type Props = {
const TableItemActionButtons = ({ onEditClick, onDeleteClick, isLoading }: Props) => {
const onFocusCapture = usePreventFocusAfterModalClosing();
if (isLoading) {
return (
<HStack spacing={ 6 } alignSelf="flex-end">
<Skeleton boxSize={ 5 } flexShrink={ 0 } borderRadius="sm"/>
<Skeleton boxSize={ 5 } flexShrink={ 0 } borderRadius="sm"/>
</HStack>
);
}
return (
<HStack spacing={ 6 } alignSelf="flex-end">
<Tooltip label="Edit">
<HStack gap={ 6 } alignSelf="flex-end">
<Tooltip content="Edit">
<IconButton
aria-label="edit"
variant="simple"
variant="link"
boxSize={ 5 }
onClick={ onEditClick }
icon={ <IconSvg name="edit" boxSize={ 5 }/> }
onFocusCapture={ onFocusCapture }
loading={ isLoading }
display="inline-block"
flexShrink={ 0 }
borderRadius="none"
/>
>
<IconSvg name="edit" boxSize={ 5 }/>
</IconButton>
</Tooltip>
<Tooltip label="Delete">
<Tooltip content="Delete">
<IconButton
aria-label="delete"
variant="simple"
variant="link"
boxSize={ 5 }
onClick={ onDeleteClick }
icon={ <IconSvg name="delete" boxSize={ 5 }/> }
onFocusCapture={ onFocusCapture }
loading={ isLoading }
display="inline-block"
flexShrink={ 0 }
borderRadius="none"
/>
>
<IconSvg name="delete" boxSize={ 5 }/>
</IconButton>
</Tooltip>
</HStack>
);
......
import type { ChakraProps } from '@chakra-ui/react';
import React from 'react';
import type { FieldValues, Path } from 'react-hook-form';
import type { FieldValues } from 'react-hook-form';
import type { FormFieldPropsBase } from './types';
import type { PartialBy } from 'types/utils';
......@@ -31,9 +30,4 @@ const FormFieldAddress = <FormFields extends FieldValues>(
);
};
export type WrappedComponent = <
FormFields extends FieldValues,
Name extends Path<FormFields> = Path<FormFields>,
>(props: PartialBy<FormFieldPropsBase<FormFields, Name>, 'placeholder'> & ChakraProps) => React.JSX.Element;
export default React.memo(FormFieldAddress) as WrappedComponent;
export default React.memo(FormFieldAddress) as typeof FormFieldAddress;
......@@ -4,7 +4,7 @@ import type { FieldValues } from 'react-hook-form';
import type { FormFieldPropsBase } from './types';
import { urlValidator } from '../validators/url';
import FormFieldText, { type WrappedComponent } from './FormFieldText';
import FormFieldText from './FormFieldText';
const FormFieldUrl = <FormFields extends FieldValues>(
props: FormFieldPropsBase<FormFields>,
......@@ -23,4 +23,4 @@ const FormFieldUrl = <FormFields extends FieldValues>(
return <FormFieldText { ...props } rules={ rules }/>;
};
export default React.memo(FormFieldUrl) as WrappedComponent;
export default React.memo(FormFieldUrl) as typeof FormFieldUrl;
/* eslint-disable max-len */
import React from 'react';
import { Button } from 'toolkit/chakra/button';
import { DialogActionTrigger, DialogBody, DialogContent, DialogFooter, DialogHeader, DialogRoot, DialogTitle, DialogTrigger } from 'toolkit/chakra/dialog';
import { Section, Container, SectionHeader, SamplesStack, Sample } from './parts';
const CONTENT = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.';
const DialogsShowcase = () => {
return (
<Container value="dialogs">
<Section>
<SectionHeader>Size</SectionHeader>
<SamplesStack>
<Sample label="size: sm">
<DialogRoot size="sm">
<DialogTrigger asChild>
<Button size="sm">
Open Dialog
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Dialog Title</DialogTitle>
</DialogHeader>
<DialogBody>
<p>{ CONTENT }</p>
</DialogBody>
<DialogFooter>
<DialogActionTrigger asChild>
<Button variant="outline">Cancel</Button>
</DialogActionTrigger>
<Button>Save</Button>
</DialogFooter>
</DialogContent>
</DialogRoot>
</Sample>
<Sample label="size: md">
<DialogRoot size="md">
<DialogTrigger asChild>
<Button size="sm">
Open Dialog
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Dialog Title</DialogTitle>
</DialogHeader>
<DialogBody>
<p>{ CONTENT }</p>
</DialogBody>
<DialogFooter>
<DialogActionTrigger asChild>
<Button variant="outline">Cancel</Button>
</DialogActionTrigger>
<Button>Save</Button>
</DialogFooter>
</DialogContent>
</DialogRoot>
</Sample>
<Sample label="size: full">
<DialogRoot size="full">
<DialogTrigger asChild>
<Button size="sm">
Open Dialog
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Dialog Title</DialogTitle>
</DialogHeader>
<DialogBody>
<p>{ CONTENT }</p>
</DialogBody>
<DialogFooter>
<DialogActionTrigger asChild>
<Button variant="outline">Cancel</Button>
</DialogActionTrigger>
<Button>Save</Button>
</DialogFooter>
</DialogContent>
</DialogRoot>
</Sample>
</SamplesStack>
</Section>
</Container>
);
};
export default React.memo(DialogsShowcase);
......@@ -33,6 +33,7 @@ const HeaderMobile = ({ hideSearchBar, renderSearchBar }: Props) => {
left={ 0 }
zIndex="sticky2"
pt="1px"
height="56px"
>
<Flex
as="header"
......
......@@ -18,8 +18,7 @@ const NetworkMenu = () => {
>
<PopoverTrigger>
<IconButton
color="link.primary"
_hover={{ color: 'link.primary.hover' }}
variant="link"
aria-label="Network menu"
borderRadius="sm"
onClick={ menu.onToggle }
......
......@@ -21,8 +21,7 @@ const Settings = () => {
>
<PopoverTrigger>
<IconButton
color="link.primary"
_hover={{ color: 'link.primary.hover' }}
variant="link"
borderRadius="sm"
aria-label="User settings"
>
......
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