mirror of
https://github.com/luxfi/explore.git
synced 2026-09-24 00:28:18 +00:00
display table of public tags
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
import type { NextApiRequest } from 'next';
|
||||
|
||||
import handler from 'lib/api/handler';
|
||||
|
||||
const getUrl = (req: NextApiRequest) => {
|
||||
return `/account/v1/user/public_tags/${ req.query.id }`;
|
||||
};
|
||||
|
||||
const publicTagsHandler = handler(getUrl, [ 'DELETE', 'PUT' ]);
|
||||
|
||||
export default publicTagsHandler;
|
||||
@@ -0,0 +1,7 @@
|
||||
import type { PublicTags } from 'types/api/account';
|
||||
|
||||
import handler from 'lib/api/handler';
|
||||
|
||||
const publicKeysHandler = handler<PublicTags>(() => '/account/v1/user/public_tags', [ 'GET', 'POST' ]);
|
||||
|
||||
export default publicKeysHandler;
|
||||
@@ -71,3 +71,21 @@ export interface WatchlistAddressNew {
|
||||
}
|
||||
|
||||
export type WatchlistAddresses = Array<WatchlistAddress>
|
||||
|
||||
export interface PublicTag {
|
||||
website: string;
|
||||
tags: string; // tag_1;tag_2;tag_3 etc.
|
||||
is_owner: boolean;
|
||||
id: number;
|
||||
full_name: string;
|
||||
email: string;
|
||||
company: string;
|
||||
addresses: string; // address_1;<address_2;address_3 etc.
|
||||
additional_comment: string;
|
||||
}
|
||||
|
||||
export type PublicTagNew = Omit<PublicTag, 'addresses' | 'id'> & {
|
||||
addresses_array: Array<string>;
|
||||
}
|
||||
|
||||
export type PublicTags = Array<PublicTag>;
|
||||
|
||||
@@ -54,7 +54,7 @@ const ApiKeysPage: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
<SkeletonTable columns={ [ '100%', '108px' ] }/>
|
||||
<Skeleton height="44px" width="156px" marginTop={ 8 }/>
|
||||
<Skeleton height="48px" width="156px" marginTop={ 8 }/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@ import {
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { animateScroll } from 'react-scroll';
|
||||
|
||||
import type { TPublicTagItem } from 'data/publicTags';
|
||||
import type { PublicTag } from 'types/api/account';
|
||||
|
||||
import PublicTagsData from 'ui/publicTags/PublicTagsData';
|
||||
import PublicTagsForm from 'ui/publicTags/PublicTagsForm/PublicTagsForm';
|
||||
import AccountPageHeader from 'ui/shared/AccountPageHeader';
|
||||
@@ -20,9 +21,9 @@ const toastDescriptions = {
|
||||
removed: 'Tags have been removed.',
|
||||
} as Record<TToastAction, string>;
|
||||
|
||||
const PublicTags: React.FC = () => {
|
||||
const PublicTagsComponent: React.FC = () => {
|
||||
const [ screen, setScreen ] = useState<TScreen>('data');
|
||||
const [ formData, setFormData ] = useState<TPublicTagItem>();
|
||||
const [ formData, setFormData ] = useState<PublicTag>();
|
||||
|
||||
const toast = useToast();
|
||||
|
||||
@@ -39,7 +40,7 @@ const PublicTags: React.FC = () => {
|
||||
});
|
||||
}, [ toast ]);
|
||||
|
||||
const changeToFormScreen = useCallback((data?: TPublicTagItem) => {
|
||||
const changeToFormScreen = useCallback((data?: PublicTag) => {
|
||||
setFormData(data);
|
||||
setScreen('form');
|
||||
animateScroll.scrollToTop({
|
||||
@@ -82,4 +83,4 @@ const PublicTags: React.FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default PublicTags;
|
||||
export default PublicTagsComponent;
|
||||
|
||||
@@ -2,22 +2,26 @@ import { Flex, Text, FormControl, FormLabel, Textarea } from '@chakra-ui/react';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import type { ChangeEvent } from 'react';
|
||||
|
||||
import type { TPublicTag } from 'data/publicTags';
|
||||
import type { PublicTag } from 'types/api/account';
|
||||
|
||||
import DeleteModal from 'ui/shared/DeleteModal';
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
tags: Array<TPublicTag>;
|
||||
data: PublicTag;
|
||||
onDeleteSuccess: () => void;
|
||||
}
|
||||
|
||||
const DeletePublicTagModal: React.FC<Props> = ({ isOpen, onClose, tags = [], onDeleteSuccess }) => {
|
||||
const DeletePublicTagModal: React.FC<Props> = ({ isOpen, onClose, data, onDeleteSuccess }) => {
|
||||
|
||||
const tags = data.tags.split(';');
|
||||
|
||||
const onDelete = useCallback(() => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('delete', tags);
|
||||
console.log('delete', data);
|
||||
onDeleteSuccess();
|
||||
}, [ tags, onDeleteSuccess ]);
|
||||
}, [ data, onDeleteSuccess ]);
|
||||
|
||||
const [ reason, setReason ] = useState<string>('');
|
||||
|
||||
@@ -31,7 +35,7 @@ const DeletePublicTagModal: React.FC<Props> = ({ isOpen, onClose, tags = [], onD
|
||||
text = (
|
||||
<>
|
||||
<Text display="flex">Public tag</Text>
|
||||
<Text fontWeight="600" whiteSpace="pre">{ ` "${ tags[0].name }" ` }</Text>
|
||||
<Text fontWeight="600" whiteSpace="pre">{ ` "${ tags[0] }" ` }</Text>
|
||||
<Text>will be removed.</Text>
|
||||
</>
|
||||
);
|
||||
@@ -40,15 +44,15 @@ const DeletePublicTagModal: React.FC<Props> = ({ isOpen, onClose, tags = [], onD
|
||||
const tagsText: Array<JSX.Element | string> = [];
|
||||
tags.forEach((tag, index) => {
|
||||
if (index < tags.length - 2) {
|
||||
tagsText.push(<Text fontWeight="600" whiteSpace="pre">{ ` "${ tag.name }"` }</Text>);
|
||||
tagsText.push(<Text fontWeight="600" whiteSpace="pre">{ ` "${ tag }"` }</Text>);
|
||||
tagsText.push(',');
|
||||
}
|
||||
if (index === tags.length - 2) {
|
||||
tagsText.push(<Text fontWeight="600" whiteSpace="pre">{ ` "${ tag.name }" ` }</Text>);
|
||||
tagsText.push(<Text fontWeight="600" whiteSpace="pre">{ ` "${ tag }" ` }</Text>);
|
||||
tagsText.push('and');
|
||||
}
|
||||
if (index === tags.length - 1) {
|
||||
tagsText.push(<Text fontWeight="600" whiteSpace="pre">{ ` "${ tag.name }" ` }</Text>);
|
||||
tagsText.push(<Text fontWeight="600" whiteSpace="pre">{ ` "${ tag }" ` }</Text>);
|
||||
}
|
||||
});
|
||||
text = (
|
||||
@@ -85,4 +89,4 @@ const DeletePublicTagModal: React.FC<Props> = ({ isOpen, onClose, tags = [], onD
|
||||
);
|
||||
};
|
||||
|
||||
export default DeletePublicTagModal;
|
||||
export default React.memo(DeletePublicTagModal);
|
||||
|
||||
@@ -8,14 +8,14 @@ import {
|
||||
} from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import type { TPublicTagItem, TPublicTags } from 'data/publicTags';
|
||||
import type { PublicTags, PublicTag } from 'types/api/account';
|
||||
|
||||
import PublicTagTableItem from './PublicTagTableItem';
|
||||
|
||||
interface Props {
|
||||
data: TPublicTags;
|
||||
onEditClick: (data: TPublicTagItem) => void;
|
||||
onDeleteClick: (data: TPublicTagItem) => void;
|
||||
data: PublicTags;
|
||||
onEditClick: (data: PublicTag) => void;
|
||||
onDeleteClick: (data: PublicTag) => void;
|
||||
}
|
||||
|
||||
const PublicTagTable = ({ data, onEditClick, onDeleteClick }: Props) => {
|
||||
@@ -26,12 +26,12 @@ const PublicTagTable = ({ data, onEditClick, onDeleteClick }: Props) => {
|
||||
<Tr>
|
||||
<Th width="60%">Smart contract / Address (0x...)</Th>
|
||||
<Th width="40%">Public tag</Th>
|
||||
<Th width="200px">Submission date</Th>
|
||||
<Th width="200px">Request status</Th>
|
||||
<Th width="108px"></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{ data.map((item: TPublicTagItem) => (
|
||||
{ data.map((item) => (
|
||||
<PublicTagTableItem
|
||||
item={ item }
|
||||
key={ item.id }
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import {
|
||||
Box,
|
||||
Tag,
|
||||
Text,
|
||||
Tr,
|
||||
Td,
|
||||
HStack,
|
||||
VStack,
|
||||
useColorModeValue,
|
||||
Text,
|
||||
} from '@chakra-ui/react';
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import type { TPublicTagItem, TPublicTagAddress, TPublicTag } from 'data/publicTags';
|
||||
import type { PublicTag } from 'types/api/account';
|
||||
|
||||
import AddressIcon from 'ui/shared/AddressIcon';
|
||||
import AddressLinkWithTooltip from 'ui/shared/AddressLinkWithTooltip';
|
||||
import DeleteButton from 'ui/shared/DeleteButton';
|
||||
@@ -18,9 +18,9 @@ import EditButton from 'ui/shared/EditButton';
|
||||
import TruncatedTextTooltip from 'ui/shared/TruncatedTextTooltip';
|
||||
|
||||
interface Props {
|
||||
item: TPublicTagItem;
|
||||
onEditClick: (data: TPublicTagItem) => void;
|
||||
onDeleteClick: (data: TPublicTagItem) => void;
|
||||
item: PublicTag;
|
||||
onEditClick: (data: PublicTag) => void;
|
||||
onDeleteClick: (data: PublicTag) => void;
|
||||
}
|
||||
|
||||
const PublicTagTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
|
||||
@@ -32,19 +32,18 @@ const PublicTagTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
|
||||
return onDeleteClick(item);
|
||||
}, [ item, onDeleteClick ]);
|
||||
|
||||
const secondaryColor = useColorModeValue('gray.500', 'gray.400');
|
||||
|
||||
return (
|
||||
<Tr alignItems="top" key={ item.id }>
|
||||
<Td>
|
||||
<VStack spacing={ 4 } alignItems="unset">
|
||||
{ item.addresses.map((adr: TPublicTagAddress) => {
|
||||
{ item.addresses.split(';').map((address) => {
|
||||
return (
|
||||
<HStack spacing={ 4 } key={ adr.address } overflow="hidden" alignItems="start">
|
||||
<AddressIcon address={ adr.address }/>
|
||||
<HStack spacing={ 4 } key={ address } overflow="hidden" alignItems="start">
|
||||
<AddressIcon address={ address }/>
|
||||
<Box overflow="hidden">
|
||||
<AddressLinkWithTooltip address={ adr.address }/>
|
||||
{ adr.addressName && <Text fontSize="sm" color={ secondaryColor } mt={ 0.5 }>{ adr.addressName }</Text> }
|
||||
<AddressLinkWithTooltip address={ address }/>
|
||||
{ /* todo_tom add address name */ }
|
||||
<Text fontSize="sm" variant="secondary" mt={ 0.5 }>Address Name</Text>
|
||||
</Box>
|
||||
</HStack>
|
||||
);
|
||||
@@ -53,19 +52,23 @@ const PublicTagTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
|
||||
</Td>
|
||||
<Td>
|
||||
<VStack spacing={ 2 } alignItems="baseline">
|
||||
{ item.tags.map((tag: TPublicTag) => {
|
||||
{ item.tags.split(';').map((tag) => {
|
||||
return (
|
||||
<TruncatedTextTooltip label={ tag.name } key={ tag.name }>
|
||||
<Tag color={ tag.colorHex || 'gray.600' } background={ tag.backgroundHex || 'gray.200' } lineHeight="24px">
|
||||
{ tag.name }
|
||||
<TruncatedTextTooltip label={ tag } key={ tag }>
|
||||
<Tag variant="gray" lineHeight="24px">
|
||||
{ tag }
|
||||
</Tag>
|
||||
</TruncatedTextTooltip>
|
||||
);
|
||||
}) }
|
||||
</VStack>
|
||||
</Td>
|
||||
{ /* todo_tom update tag date and status */ }
|
||||
<Td>
|
||||
<Text fontSize="sm" color={ secondaryColor }>{ item.date }</Text>
|
||||
<VStack alignItems="flex-start">
|
||||
<Text fontSize="sm" color="green.500" fontWeight="500">Approved</Text>
|
||||
<Text fontSize="sm" variant="secondary">Jun 10, 2022</Text>
|
||||
</VStack>
|
||||
</Td>
|
||||
<Td>
|
||||
<HStack spacing={ 6 }>
|
||||
@@ -77,4 +80,4 @@ const PublicTagTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default PublicTagTableItem;
|
||||
export default React.memo(PublicTagTableItem);
|
||||
|
||||
@@ -1,23 +1,33 @@
|
||||
import { Box, Text, Button, useDisclosure } from '@chakra-ui/react';
|
||||
import { Box, Text, Button, Skeleton, useDisclosure } from '@chakra-ui/react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
import type { TPublicTagItem, TPublicTag } from 'data/publicTags';
|
||||
import { publicTags } from 'data/publicTags';
|
||||
import type { PublicTags, PublicTag } from 'types/api/account';
|
||||
|
||||
import SkeletonTable from 'ui/shared/SkeletonTable';
|
||||
|
||||
import DeletePublicTagModal from './DeletePublicTagModal';
|
||||
import PublicTagTable from './PublicTagTable/PublicTagTable';
|
||||
|
||||
type Props = {
|
||||
changeToFormScreen: (data?: TPublicTagItem) => void;
|
||||
changeToFormScreen: (data?: PublicTag) => void;
|
||||
onTagDelete: () => void;
|
||||
}
|
||||
|
||||
const PublicTagsData = ({ changeToFormScreen, onTagDelete }: Props) => {
|
||||
const deleteModalProps = useDisclosure();
|
||||
const [ deleteModalData, setDeleteModalData ] = useState<Array<TPublicTag>>([]);
|
||||
const [ deleteModalData, setDeleteModalData ] = useState<PublicTag>();
|
||||
|
||||
const { data, isLoading, isError } = useQuery<unknown, unknown, PublicTags>([ 'public-tags' ], async() => {
|
||||
const response = await fetch('/api/account/public-tags');
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
return response.json();
|
||||
});
|
||||
|
||||
const onDeleteModalClose = useCallback(() => {
|
||||
setDeleteModalData([]);
|
||||
setDeleteModalData(undefined);
|
||||
deleteModalProps.onClose();
|
||||
}, [ deleteModalProps ]);
|
||||
|
||||
@@ -25,15 +35,41 @@ const PublicTagsData = ({ changeToFormScreen, onTagDelete }: Props) => {
|
||||
changeToFormScreen();
|
||||
}, [ changeToFormScreen ]);
|
||||
|
||||
const onItemEditClick = useCallback((item: TPublicTagItem) => {
|
||||
const onItemEditClick = useCallback((item: PublicTag) => {
|
||||
changeToFormScreen(item);
|
||||
}, [ changeToFormScreen ]);
|
||||
|
||||
const onItemDeleteClick = useCallback((item: TPublicTagItem) => {
|
||||
setDeleteModalData(item.tags);
|
||||
const onItemDeleteClick = useCallback((item: PublicTag) => {
|
||||
setDeleteModalData(item);
|
||||
deleteModalProps.onOpen();
|
||||
}, [ deleteModalProps ]);
|
||||
|
||||
const content = (() => {
|
||||
if (isLoading || isError) {
|
||||
return (
|
||||
<>
|
||||
<SkeletonTable columns={ [ '60%', '40%', '200px', '108px' ] }/>
|
||||
<Skeleton height="48px" width="270px" marginTop={ 8 }/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{ data.length > 0 && <PublicTagTable data={ data } onEditClick={ onItemEditClick } onDeleteClick={ onItemDeleteClick }/> }
|
||||
<Box marginTop={ 8 }>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="lg"
|
||||
onClick={ changeToForm }
|
||||
>
|
||||
Request to add public tag
|
||||
</Button>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
})();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Text marginBottom={ 12 }>
|
||||
@@ -42,22 +78,15 @@ const PublicTagsData = ({ changeToFormScreen, onTagDelete }: Props) => {
|
||||
Clicking a tag opens a page with related information and helps provide context and data organization.
|
||||
Requests are sent to a moderator for review and approval. This process can take several days.
|
||||
</Text>
|
||||
<PublicTagTable data={ publicTags } onEditClick={ onItemEditClick } onDeleteClick={ onItemDeleteClick }/>
|
||||
<Box marginTop={ 8 }>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="lg"
|
||||
onClick={ changeToForm }
|
||||
>
|
||||
Request to add public tag
|
||||
</Button>
|
||||
</Box>
|
||||
<DeletePublicTagModal
|
||||
{ ...deleteModalProps }
|
||||
onClose={ onDeleteModalClose }
|
||||
tags={ deleteModalData }
|
||||
onDeleteSuccess={ onTagDelete }
|
||||
/>
|
||||
{ content }
|
||||
{ deleteModalData && (
|
||||
<DeletePublicTagModal
|
||||
{ ...deleteModalProps }
|
||||
onClose={ onDeleteModalClose }
|
||||
data={ deleteModalData }
|
||||
onDeleteSuccess={ onTagDelete }
|
||||
/>
|
||||
) }
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@ import React, { useCallback } from 'react';
|
||||
import type { Path } from 'react-hook-form';
|
||||
import { useForm, useFieldArray } from 'react-hook-form';
|
||||
|
||||
import type { TPublicTagItem, TPublicTag, TPublicTagAddress } from 'data/publicTags';
|
||||
import type { PublicTag } from 'types/api/account';
|
||||
|
||||
import PublicTagFormAction from './PublicTagFormAction';
|
||||
import PublicTagFormAddressInput from './PublicTagFormAddressInput';
|
||||
@@ -19,7 +19,7 @@ import PublicTagsFormInput from './PublicTagsFormInput';
|
||||
|
||||
type Props = {
|
||||
changeToDataScreen: (success?: boolean) => void;
|
||||
data?: TPublicTagItem;
|
||||
data?: PublicTag;
|
||||
}
|
||||
|
||||
export type Inputs = {
|
||||
@@ -50,14 +50,14 @@ const ADDRESS_INPUT_BUTTONS_WIDTH = 170;
|
||||
const PublicTagsForm = ({ changeToDataScreen, data }: Props) => {
|
||||
const { control, handleSubmit, formState: { errors } } = useForm<Inputs>({
|
||||
defaultValues: {
|
||||
userName: data?.userName,
|
||||
userEmail: data?.userEmail,
|
||||
companyName: data?.companyName,
|
||||
companyUrl: data?.companyUrl,
|
||||
tag: data?.tags.map((tag: TPublicTag) => tag.name).join('; '),
|
||||
addresses: data?.addresses.map((adr: TPublicTagAddress, index: number) => ({ name: `address.${ index }.address`, address: adr.address })) ||
|
||||
userName: data?.full_name,
|
||||
userEmail: data?.email,
|
||||
companyName: data?.company,
|
||||
companyUrl: data?.website,
|
||||
tag: data?.tags.split(';').map((tag) => tag).join('; '),
|
||||
addresses: data?.addresses.split(';').map((address, index: number) => ({ name: `address.${ index }.address`, address })) ||
|
||||
[ { name: 'address.0.address', address: '' } ],
|
||||
comment: data?.comment,
|
||||
comment: data?.additional_comment,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -135,4 +135,4 @@ const PublicTagsForm = ({ changeToDataScreen, data }: Props) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default PublicTagsForm;
|
||||
export default React.memo(PublicTagsForm);
|
||||
|
||||
Reference in New Issue
Block a user