Commit 4b35149a authored by Igor Stuev's avatar Igor Stuev Committed by GitHub

Merge pull request #58 from blockscout/public-tag

public tags page
parents 0854840b 1ab370a9
export const publicTags = [
{
addresses: [
{
address: '0x35317007D203b8a86CA727ad44E473E40450E377',
addressName: 'DarkForest',
},
{
address: '0x35317007D203b8a86CA727ad44E473E40450E378',
addressName: 'DarkForest2',
},
],
tags: [
{
name: 'darkforest',
// colorHex: '#4A5568',
// backgroundHex: '#E2E8F0',
},
],
date: 'Jun 10, 2022',
id: '123',
userName: 'Tatyana',
userEmail: 'sample@gmail.com',
companyName: 'Contract name',
companyUrl: 'contractname.com',
comment: 'Please use #ED8936 color for tag...',
},
{
addresses: [
{
address: '0x35317007D203b8a86CA727ad44E473E40450E377',
},
],
tags: [
{
name: 'OMNI',
colorHex: '#FFFFFF',
backgroundHex: '#1A202C',
},
{
name: '123456789012345678901237123123',
colorHex: '#FFFFFF',
backgroundHex: '#6B46C1',
},
],
date: 'Jun 5, 2022',
id: '456',
},
{
addresses: [
{
address: '0x35317007D203b8a86CA727ad44E473E40450E377',
addressName: 'Contract name',
},
],
tags: [
{
name: 'SANA',
colorHex: '#FFFFFF',
backgroundHex: '#ED8936',
},
],
date: 'Jun 1, 2022',
id: '789',
},
];
export type TPublicTags = Array<TPublicTagItem>
export type TPublicTagItem = {
addresses: Array<TPublicTagAddress>;
tags: Array<TPublicTag>;
// status: typeof STATUS;
date: string;
// id is for react element key, as tag or address may not be unique
id: string;
userName?: string;
userEmail?: string;
companyName?: string;
companyUrl?: string;
comment?: string;
}
export type TPublicTagAddress = {
address: string;
addressName?: string;
}
export type TPublicTag = {
name: string;
colorHex?: string;
backgroundHex?: string;
}
<svg width="16" height="16" viewBox="0 0 16 2" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M.5 1a.937.937 0 0 1 .938-.938h13.124a.938.938 0 0 1 0 1.875H1.438A.937.937 0 0 1 .5 1Z" fill="currentColor"/></svg>
\ No newline at end of file
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M8 .5a.937.937 0 0 1 .938.938v5.625h5.624a.937.937 0 0 1 0 1.875H8.939v5.624a.937.937 0 0 1-1.876 0V8.939H1.438a.937.937 0 1 1 0-1.876h5.625V1.438A.937.937 0 0 1 8 .5Z" fill="currentColor"/></svg>
\ No newline at end of file
import React from 'react';
import type { NextPage } from 'next';
import Head from 'next/head'
import PublicTags from 'ui/pages/PublicTags';
const PublicTagsPage: NextPage = () => {
return (
<>
<Head><title>Public tags</title></Head>
<PublicTags/>
</>
);
}
export default PublicTagsPage
......@@ -18,7 +18,6 @@ const variantPrimary = {
}
const variantSecondary = {
bg: 'white',
color: 'blue.600',
fontWeight: 600,
borderColor: 'blue.600',
......@@ -32,7 +31,7 @@ const variantSecondary = {
},
}
const variantIconBlue: SystemStyleFunction = (props) => {
const variantIcon: SystemStyleFunction = (props) => {
return {
color: mode('blue.600', 'blue.300')(props),
_hover: {
......@@ -41,10 +40,24 @@ const variantIconBlue: SystemStyleFunction = (props) => {
}
}
const variantIconBorder = {
color: 'blue.600',
borderColor: 'blue.600',
border: '2px solid',
_hover: {
color: 'blue.400',
borderColor: 'blue.400',
},
_disabled: {
opacity: 0.2,
},
}
const variants = {
primary: variantPrimary,
secondary: variantSecondary,
iconBlue: variantIconBlue,
icon: variantIcon,
iconBorder: variantIconBorder,
}
const Button: ComponentStyleConfig = {
......
import type { ComponentStyleConfig } from '@chakra-ui/theme';
import type { SystemStyleObject } from '@chakra-ui/theme-tools';
const baseStyleLabel: SystemStyleObject = {
_disabled: { opacity: 0.2 },
}
const Checkbox: ComponentStyleConfig = {
baseStyle: {
label: baseStyleLabel,
},
}
export default Checkbox;
......@@ -5,23 +5,21 @@ import type { StyleFunctionProps, PartsStyleFunction } from '@chakra-ui/theme-to
import type { Dict } from '@chakra-ui/utils';
import getDefaultFormColors from '../utils/getDefaultFormColors';
const activeInputStyles = {
paddingTop: '30px',
paddingBottom: '10px',
}
const getActiveLabelStyles = (theme: Dict, fc: string) => ({
color: getColor(theme, fc),
transform: 'scale(0.75) translateY(-10px)',
})
const getActiveInputStyles = (theme: Dict, fc: string) => ({
paddingTop: '30px',
paddingBottom: '10px',
borderColor: getColor(theme, fc),
})
});
const variantFloating: PartsStyleFunction<typeof parts> = (props: StyleFunctionProps) => {
const { theme } = props;
const { focusColor: fc, errorColor: ec } = getDefaultFormColors(props);
const activeLabelStyles = getActiveLabelStyles(theme, fc);
const activeInputStyles = getActiveInputStyles(theme, fc);
return {
container: {
......@@ -29,7 +27,7 @@ const variantFloating: PartsStyleFunction<typeof parts> = (props: StyleFunctionP
label: {
...activeLabelStyles,
},
input: {
'input, textarea': {
...activeInputStyles,
},
'label .chakra-form__required-indicator': {
......@@ -38,7 +36,6 @@ const variantFloating: PartsStyleFunction<typeof parts> = (props: StyleFunctionP
},
// label's styles
label: {
top: '20px',
left: '22px',
zIndex: 2,
position: 'absolute',
......@@ -50,27 +47,30 @@ const variantFloating: PartsStyleFunction<typeof parts> = (props: StyleFunctionP
fontSize: 'md',
lineHeight: '20px',
},
'input:not(:placeholder-shown) + label': {
'input + label': {
top: 'calc(50% - 10px);',
},
'textarea + label': {
top: '20px',
},
'input:not(:placeholder-shown) + label, textarea:not(:placeholder-shown) + label': {
...activeLabelStyles,
},
'input[aria-invalid=true] + label': {
'input[aria-invalid=true] + label, textarea[aria-invalid=true] + label': {
color: getColor(theme, ec),
},
// input's styles
input: {
'input, textarea': {
padding: '20px',
},
'input:not(:placeholder-shown)': {
'input:not(:placeholder-shown), textarea:not(:placeholder-shown)': {
...activeInputStyles,
},
'input[aria-invalid=true]': {
borderColor: getColor(theme, ec),
},
// indicator's styles
'input:not(:placeholder-shown) + label .chakra-form__required-indicator': {
'input:not(:placeholder-shown) + label .chakra-form__required-indicator, textarea:not(:placeholder-shown) + label .chakra-form__required-indicator': {
color: getColor(theme, fc),
},
'input[aria-invalid=true] + label .chakra-form__required-indicator': {
'input[aria-invalid=true] + label .chakra-form__required-indicator, textarea[aria-invalid=true] + label .chakra-form__required-indicator': {
color: getColor(theme, ec),
},
},
......
import type { inputAnatomy as parts } from '@chakra-ui/anatomy';
import type { ComponentStyleConfig } from '@chakra-ui/theme';
import type { PartsStyleFunction, SystemStyleObject } from '@chakra-ui/theme-tools';
import { getColor, mode } from '@chakra-ui/theme-tools';
import getDefaultFormColors from '../utils/getDefaultFormColors';
import { mode } from '@chakra-ui/theme-tools';
import getDefaultTransitionProps from '../utils/getDefaultTransitionProps';
import getOutlinedFieldStyles from '../utils/getOutlinedFieldStyles';
import { Input as InputComponent } from '@chakra-ui/react';
const sizes: Record<string, SystemStyleObject> = {
md: {
......@@ -14,42 +16,21 @@ const sizes: Record<string, SystemStyleObject> = {
h: '60px',
borderRadius: 'base',
},
lg: {
fontSize: 'md',
lineHeight: '20px',
px: '24px',
py: '28px',
h: '80px',
borderRadius: 'base',
},
}
const variantOutline: PartsStyleFunction<typeof parts> = (props) => {
const { theme } = props
const { focusColor: fc, errorColor: ec } = getDefaultFormColors(props);
const transitionProps = getDefaultTransitionProps();
return {
field: {
border: '2px solid',
bg: 'inherit',
borderColor: mode('gray.100', 'whiteAlpha.200')(props),
...transitionProps,
_hover: {
borderColor: mode('gray.300', 'whiteAlpha.400')(props),
},
_readOnly: {
boxShadow: 'none !important',
userSelect: 'all',
},
_disabled: {
opacity: 1,
background: mode('gray.200', 'whiteAlpha.400')(props),
border: 'none',
cursor: 'not-allowed',
},
_invalid: {
borderColor: getColor(theme, ec),
boxShadow: `none`,
},
_focusVisible: {
zIndex: 1,
borderColor: getColor(theme, fc),
boxShadow: '0px 4px 6px -1px rgba(0, 0, 0, 0.1), 0px 2px 4px -1px rgba(0, 0, 0, 0.06)',
},
},
field: getOutlinedFieldStyles(props),
addon: {
border: '2px solid',
borderColor: mode('gray.100', 'whiteAlpha.200')(props),
......@@ -65,6 +46,10 @@ const Input: ComponentStyleConfig = {
field: sizes.md,
addon: sizes.md,
},
lg: {
field: sizes.lg,
addon: sizes.lg,
},
},
defaultProps: {
size: 'md',
......@@ -74,4 +59,9 @@ const Input: ComponentStyleConfig = {
},
}
InputComponent.defaultProps = {
...InputComponent.defaultProps,
placeholder: ' ',
}
export default Input;
import type { ComponentStyleConfig } from '@chakra-ui/theme';
import type { SystemStyleObject } from '@chakra-ui/theme-tools';
const baseStyleLabel: SystemStyleObject = {
_disabled: { opacity: 0.2 },
}
const Radio: ComponentStyleConfig = {
baseStyle: {
label: baseStyleLabel,
},
}
export default Radio;
......@@ -33,7 +33,7 @@ const Table: ComponentMultiStyleConfig = {
th: {
textTransform: 'none',
fontFamily: 'body',
fontWeight: 'normal',
fontWeight: '500',
overflow: 'hidden',
color: 'gray.500',
letterSpacing: 'none',
......
import type { SystemStyleFunction } from '@chakra-ui/theme-tools';
import type { ComponentStyleConfig } from '@chakra-ui/theme';
import { mode } from '@chakra-ui/theme-tools';
const variantSecondary: SystemStyleFunction = (props) => ({
color: mode('gray.500', 'gray.400')(props),
});
const Text: ComponentStyleConfig = {
variants: {
secondary: variantSecondary,
},
}
export default Text;
import type {
SystemStyleObject,
} from '@chakra-ui/theme-tools'
import type { ComponentStyleConfig } from '@chakra-ui/theme';
import getOutlinedFieldStyles from '../utils/getOutlinedFieldStyles';
import { Textarea as TextareaComponent } from '@chakra-ui/react';
const sizes: Record<string, SystemStyleObject> = {
lg: {
fontSize: 'md',
lineHeight: '20px',
px: '24px',
py: '28px',
h: '160px',
borderRadius: 'base',
},
}
const Textarea: ComponentStyleConfig = {
sizes,
variants: {
outline: (props) => getOutlinedFieldStyles(props),
},
defaultProps: {
size: 'md',
variant: 'outline',
},
}
TextareaComponent.defaultProps = {
...TextareaComponent.defaultProps,
placeholder: ' ',
}
export default Textarea;
import Button from './Button';
import Checkbox from './Checkbox';
import Form from './Form';
import Heading from './Heading';
import Input from './Input';
import Link from './Link';
import Modal from './Modal';
import Radio from './Radio';
import Table from './Table';
import Tabs from './Tabs';
import Tag from './Tag';
import Text from './Text';
import Textarea from './Textarea';
import Tooltip from './Tooltip';
const components = {
Button,
Checkbox,
Heading,
Input,
Form,
Link,
Modal,
Radio,
Tabs,
Table,
Tag,
Text,
Textarea,
Tooltip,
}
......
......@@ -2,9 +2,10 @@ import type { StyleFunctionProps } from '@chakra-ui/theme-tools';
import { mode } from '@chakra-ui/theme-tools';
export default function getDefaultFormColors(props: StyleFunctionProps) {
const { focusBorderColor: fc, errorBorderColor: ec } = props
const { focusBorderColor: fc, errorBorderColor: ec, filledBorderColor: flc } = props
return {
focusColor: fc || mode('brand.700', 'brand.300')(props),
errorColor: ec || mode('red.400', 'red.300')(props),
filledColor: flc || mode('gray.300', 'gray.600')(props),
}
}
import type { StyleFunctionProps } from '@chakra-ui/theme-tools';
import { mode, getColor } from '@chakra-ui/theme-tools';
import getDefaultFormColors from './getDefaultFormColors';
import getDefaultTransitionProps from './getDefaultTransitionProps';
export default function getOutlinedFieldStyles(props: StyleFunctionProps) {
const { theme } = props
const { focusColor: fc, errorColor: ec, filledColor: flc } = getDefaultFormColors(props);
const transitionProps = getDefaultTransitionProps();
return {
border: '2px solid',
bg: 'inherit',
borderColor: getColor(theme, flc),
...transitionProps,
_hover: {
borderColor: mode('gray.200', 'whiteAlpha.400')(props),
},
_readOnly: {
boxShadow: 'none !important',
userSelect: 'all',
},
_disabled: {
opacity: 1,
background: mode('gray.200', 'whiteAlpha.400')(props),
border: 'none',
cursor: 'not-allowed',
},
_invalid: {
borderColor: getColor(theme, ec),
boxShadow: `none`,
},
_focusVisible: {
zIndex: 1,
borderColor: getColor(theme, fc),
boxShadow: '0px 4px 6px -1px rgba(0, 0, 0, 0.1), 0px 2px 4px -1px rgba(0, 0, 0, 0.06)',
},
':placeholder-shown:not(:focus-visible):not(:hover)': { borderColor: mode('gray.100', 'whiteAlpha.200')(props) },
':-webkit-autofill': { transition: 'background-color 5000s ease-in-out 0s' },
':-webkit-autofill:hover': { transition: 'background-color 5000s ease-in-out 0s' },
':-webkit-autofill:focus': { transition: 'background-color 5000s ease-in-out 0s' },
}
}
......@@ -40,7 +40,6 @@ const ApiKeyForm: React.FC<Props> = ({ data }) => {
<FormControl variant="floating" id="address" isRequired>
<Input
{ ...field }
placeholder=" "
disabled={ true }
/>
<FormLabel>Auto-generated API key token</FormLabel>
......@@ -53,7 +52,6 @@ const ApiKeyForm: React.FC<Props> = ({ data }) => {
<FormControl variant="floating" id="name" isRequired>
<Input
{ ...field }
placeholder=" "
isInvalid={ Boolean(errors.name) }
maxLength={ NAME_MAX_LENGTH }
/>
......
......@@ -5,7 +5,6 @@ import {
Td,
HStack,
Text,
useColorModeValue,
} from '@chakra-ui/react'
import EditButton from 'ui/shared/EditButton';
......@@ -30,7 +29,6 @@ const WatchlistTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
return onDeleteClick(item);
}, [ item, onDeleteClick ]);
const secondaryColor = useColorModeValue('gray.500', 'gray.400');
return (
<Tr alignItems="top" key={ item.token }>
<Td>
......@@ -38,7 +36,7 @@ const WatchlistTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
<Text fontSize="md" fontWeight={ 600 }>{ item.token }</Text>
<CopyToClipboard text={ item.token }/>
</HStack>
<Text fontSize="sm" marginTop={ 0.5 } color={ secondaryColor }>{ item.name }</Text>
<Text fontSize="sm" marginTop={ 0.5 } variant="secondary">{ item.name }</Text>
</Td>
<Td>
<HStack spacing={ 6 }>
......
......@@ -25,7 +25,7 @@ const DeleteAddressModal: React.FC<Props> = ({ isOpen, onClose, name }) => {
onClose={ onClose }
onDelete={ onDelete }
title="Remove API key"
renderText={ renderText }
renderContent={ renderText }
/>
)
}
......
import React, { useCallback, useState } from 'react';
import { Box, Button, Heading, HStack, Link, Text, useColorModeValue, useDisclosure } from '@chakra-ui/react';
import { Box, Button, HStack, Link, Text, useDisclosure } from '@chakra-ui/react';
import Page from 'ui/shared/Page/Page';
import AccountPageHeader from 'ui/shared/AccountPageHeader';
import ApiKeyTable from 'ui/apiKey/ApiKeyTable/ApiKeyTable';
import ApiKeyModal from 'ui/apiKey/ApiKeyModal/ApiKeyModal';
import DeleteApiKeyModal from 'ui/apiKey/DeleteApiKeyModal';
......@@ -42,14 +42,12 @@ const ApiKeys: React.FC = () => {
deleteModalProps.onClose();
}, [ deleteModalProps ]);
const captionColor = useColorModeValue('gray.500', 'gray.400');
const canAdd = apiKey.length < DATA_LIMIT
return (
<Page>
<Box h="100%">
<Heading as="h1" size="lg" marginBottom={ 8 }>API keys</Heading>
<AccountPageHeader text="API keys"/>
<Text marginBottom={ 12 }>
Create API keys to use for your RPC and EthRPC API requests. For more information, see { space }
<Link href="#">“How to use a Blockscout API key”</Link>.
......@@ -72,7 +70,7 @@ const ApiKeys: React.FC = () => {
Add API key
</Button>
{ !canAdd && (
<Text fontSize="sm" color={ captionColor }>
<Text fontSize="sm" variant="secondary">
{ `You have added the maximum number of API keys (${ DATA_LIMIT }). Contact us to request additional keys.` }
</Text>
) }
......
......@@ -2,7 +2,6 @@ import React from 'react';
import {
Box,
Heading,
Tab,
Tabs,
TabList,
......@@ -11,6 +10,7 @@ import {
} from '@chakra-ui/react';
import Page from 'ui/shared/Page/Page';
import AccountPageHeader from 'ui/shared/AccountPageHeader';
import PrivateAddressTags from 'ui/privateTags/PrivateAddressTags';
import PrivateTransactionTags from 'ui/privateTags/PrivateTransactionTags';
......@@ -22,7 +22,7 @@ const PrivateTags: React.FC = () => {
return (
<Page>
<Box h="100%">
<Heading as="h1" size="lg" marginBottom={ 8 }>Private tags</Heading>
<AccountPageHeader text="Private tags"/>
<Tabs variant="soft-rounded" colorScheme="blue" isLazy>
<TabList marginBottom={ 8 }>
<Tab>Address</Tab>
......
import React, { useCallback, useState } from 'react';
import { animateScroll } from 'react-scroll';
import {
Box,
useToast,
} from '@chakra-ui/react';
import Page from 'ui/shared/Page/Page';
import AccountPageHeader from 'ui/shared/AccountPageHeader';
import PublicTagsData from 'ui/publicTags/PublicTagsData';
import PublicTagsForm from 'ui/publicTags/PublicTagsForm/PublicTagsForm';
type TScreen = 'data' | 'form';
type TToastAction = 'added' | 'removed';
const toastDescriptions = {
added: 'Your request sent to moderator. Waiting for...',
removed: 'Tags have been removed.',
} as Record<TToastAction, string>;
const PublicTags: React.FC = () => {
const [ screen, setScreen ] = useState<TScreen>('data');
const [ formData, setFormData ] = useState();
const toast = useToast()
const showToast = useCallback((action: TToastAction) => {
toast({
position: 'top-right',
title: 'Success',
description: toastDescriptions[action],
colorScheme: 'green',
status: 'success',
variant: 'subtle',
isClosable: true,
icon: null,
});
}, [ toast ]);
const changeToFormScreen = useCallback((data: any) => {
setFormData(data);
setScreen('form');
animateScroll.scrollToTop({
duration: 500,
delay: 100,
});
}, []);
const changeToDataScreen = useCallback((success?: boolean) => {
if (success) {
showToast('added');
}
setScreen('data');
animateScroll.scrollToTop({
duration: 500,
delay: 100,
});
}, [ showToast ]);
const onTagDelete = useCallback(() => showToast('removed'), [ showToast ]);
let content;
let header;
if (screen === 'data') {
content = <PublicTagsData changeToFormScreen={ changeToFormScreen } onTagDelete={ onTagDelete }/>
header = 'Public tags'
} else {
content = <PublicTagsForm changeToDataScreen={ changeToDataScreen } data={ formData }/>
header = formData ? 'Request to edit a public tag/label' : 'Request a public tag/label';
}
return (
<Page>
<Box h="100%">
<AccountPageHeader text={ header }/>
{ content }
</Box>
</Page>
);
};
export default PublicTags;
import React, { useCallback, useState } from 'react';
import { Box, Button, Text, useDisclosure, Heading } from '@chakra-ui/react';
import { Box, Button, Text, useDisclosure } from '@chakra-ui/react';
import Page from 'ui/shared/Page/Page';
import AccountPageHeader from 'ui/shared/AccountPageHeader';
import WatchlistTable from 'ui/watchlist/WatchlistTable/WatchlistTable';
import AddressModal from 'ui/watchlist/AddressModal/AddressModal';
......@@ -41,7 +41,7 @@ const WatchList: React.FC = () => {
return (
<Page>
<Box h="100%">
<Heading as="h1" size="lg" marginBottom={ 8 }>Watch list</Heading>
<AccountPageHeader text="Watch list"/>
<Text marginBottom={ 12 }>An email notification can be sent to you when an address on your watch list sends or receives any transactions.</Text>
{ Boolean(watchlist.length) && (
<WatchlistTable
......
......@@ -36,7 +36,7 @@ const AddressForm: React.FC<Props> = ({ data }) => {
const onSubmit: SubmitHandler<Inputs> = data => console.log(data);
const renderAddressInput = useCallback(({ field }: {field: ControllerRenderProps<Inputs, 'address'>}) => {
return <AddressInput field={ field } isInvalid={ Boolean(errors.address) }/>
return <AddressInput<Inputs, 'address'> field={ field } isInvalid={ Boolean(errors.address) }/>
}, [ errors ]);
const renderTagInput = useCallback(({ field }: {field: ControllerRenderProps<Inputs, 'tag'>}) => {
......
......@@ -26,7 +26,7 @@ const DeletePrivateTagModal: React.FC<Props> = ({ isOpen, onClose, tag }) => {
onClose={ onClose }
onDelete={ onDelete }
title="Removal of private tag"
renderText={ renderText }
renderContent={ renderText }
/>
)
}
......
import React, { useCallback, useState } from 'react';
import type { ChangeEvent } from 'react';
import { Flex, Text, FormControl, FormLabel, Textarea } from '@chakra-ui/react';
import DeleteModal from 'ui/shared/DeleteModal';
import type { TPublicTag } from 'data/publicTags';
type Props = {
isOpen: boolean;
onClose: () => void;
tags: Array<TPublicTag>;
onDeleteSuccess: () => void;
}
const DeletePublicTagModal: React.FC<Props> = ({ isOpen, onClose, tags = [], onDeleteSuccess }) => {
const onDelete = useCallback(() => {
// eslint-disable-next-line no-console
console.log('delete', tags);
onDeleteSuccess();
}, [ tags, onDeleteSuccess ]);
const [ reason, setReason ] = useState<string>('');
const onFieldChange = useCallback((event: ChangeEvent<HTMLTextAreaElement>) => {
setReason(event.currentTarget.value);
}, []);
const renderContent = useCallback(() => {
let text;
if (tags.length === 1) {
text = (
<>
<Text display="flex">Public tag</Text>
<Text fontWeight="600" whiteSpace="pre">{ ` "${ tags[0].name }" ` }</Text>
<Text>will be removed.</Text>
</>
)
}
if (tags.length > 1) {
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(',');
}
if (index === tags.length - 2) {
tagsText.push(<Text fontWeight="600" whiteSpace="pre">{ ` "${ tag.name }" ` }</Text>);
tagsText.push('and');
}
if (index === tags.length - 1) {
tagsText.push(<Text fontWeight="600" whiteSpace="pre">{ ` "${ tag.name }" ` }</Text>);
}
})
text = (
<>
<Text>Public tags</Text>{ tagsText }<Text>will be removed.</Text>
</>
)
}
return (
<>
<Flex marginBottom={ 12 }>
{ text }
</Flex>
<FormControl variant="floating" id="tag-delete">
<Textarea
size="lg"
value={ reason }
onChange={ onFieldChange }
/>
<FormLabel>Why do you want to remove tags?</FormLabel>
</FormControl>
</>
)
}, [ tags, reason, onFieldChange ]);
return (
<DeleteModal
isOpen={ isOpen }
onClose={ onClose }
onDelete={ onDelete }
title="Request to remove a public tag"
renderContent={ renderContent }
/>
)
}
export default DeletePublicTagModal;
import React from 'react';
import {
Table,
Thead,
Tbody,
Tr,
Th,
TableContainer,
} from '@chakra-ui/react'
import type { TPublicTagItem, TPublicTags } from 'data/publicTags';
import PublicTagTableItem from './PublicTagTableItem';
interface Props {
data: TPublicTags;
onEditClick: (data: TPublicTagItem) => void;
onDeleteClick: (data: TPublicTagItem) => void;
}
const PublicTagTable = ({ data, onEditClick, onDeleteClick }: Props) => {
return (
<TableContainer width="100%">
<Table variant="simple" minWidth="600px">
<Thead>
<Tr>
<Th width="60%">Smart contract / Address (0x...)</Th>
<Th width="40%">Public tag</Th>
<Th width="200px">Submission date</Th>
<Th width="108px"></Th>
</Tr>
</Thead>
<Tbody>
{ data.map((item: TPublicTagItem) => (
<PublicTagTableItem
item={ item }
key={ item.id }
onDeleteClick={ onDeleteClick }
onEditClick={ onEditClick }
/>
)) }
</Tbody>
</Table>
</TableContainer>
);
};
export default PublicTagTable;
import React, { useCallback } from 'react';
import {
Box,
Tag,
Text,
Tr,
Td,
HStack,
VStack,
useColorModeValue,
} from '@chakra-ui/react'
import AddressIcon from 'ui/shared/AddressIcon';
import AddressLinkWithTooltip from 'ui/shared/AddressLinkWithTooltip';
import TruncatedTextTooltip from 'ui/shared/TruncatedTextTooltip';
import type { TPublicTagItem, TPublicTagAddress, TPublicTag } from 'data/publicTags';
import EditButton from 'ui/shared/EditButton';
import DeleteButton from 'ui/shared/DeleteButton';
interface Props {
item: TPublicTagItem;
onEditClick: (data: TPublicTagItem) => void;
onDeleteClick: (data: TPublicTagItem) => void;
}
const PublicTagTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
const onItemEditClick = useCallback(() => {
return onEditClick(item);
}, [ item, onEditClick ]);
const onItemDeleteClick = useCallback(() => {
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) => {
return (
<HStack spacing={ 4 } key={ adr.address } overflow="hidden" alignItems="start">
<AddressIcon address={ adr.address }/>
<Box overflow="hidden">
<AddressLinkWithTooltip address={ adr.address }/>
{ adr.addressName && <Text fontSize="sm" color={ secondaryColor } mt={ 0.5 }>{ adr.addressName }</Text> }
</Box>
</HStack>
)
}) }
</VStack>
</Td>
<Td>
<VStack spacing={ 2 } alignItems="baseline">
{ item.tags.map((tag: TPublicTag) => {
return (
<TruncatedTextTooltip label={ tag.name } key={ tag.name }>
<Tag color={ tag.colorHex || 'gray.600' } background={ tag.backgroundHex || 'gray.200' } lineHeight="24px">
{ tag.name }
</Tag>
</TruncatedTextTooltip>
)
}) }
</VStack>
</Td>
<Td>
<Text fontSize="sm" color={ secondaryColor }>{ item.date }</Text>
</Td>
<Td>
<HStack spacing={ 6 }>
<EditButton onClick={ onItemEditClick }/>
<DeleteButton onClick={ onItemDeleteClick }/>
</HStack>
</Td>
</Tr>
)
};
export default PublicTagTableItem;
import { Box, Text, Button, useDisclosure } from '@chakra-ui/react';
import React, { useCallback, useState } from 'react';
import type { TPublicTagItem, TPublicTag } from 'data/publicTags';
import { publicTags } from 'data/publicTags';
import PublicTagTable from './PublicTagTable/PublicTagTable';
import DeletePublicTagModal from './DeletePublicTagModal'
type Props = {
changeToFormScreen: (data?: any) => void;
onTagDelete: () => void;
}
const PublicTagsData = ({ changeToFormScreen, onTagDelete }: Props) => {
const deleteModalProps = useDisclosure();
const [ deleteModalData, setDeleteModalData ] = useState<Array<TPublicTag>>([]);
const onDeleteModalClose = useCallback(() => {
setDeleteModalData([]);
deleteModalProps.onClose();
}, [ deleteModalProps ]);
const changeToForm = useCallback(() => {
changeToFormScreen();
}, [ changeToFormScreen ]);
const onItemEditClick = useCallback((item: TPublicTagItem) => {
changeToFormScreen(item);
}, [ changeToFormScreen ])
const onItemDeleteClick = useCallback((item: TPublicTagItem) => {
setDeleteModalData(item.tags);
deleteModalProps.onOpen();
}, [ deleteModalProps ]);
return (
<>
<Text marginBottom={ 12 }>
You can request a public category tag which is displayed to all Blockscout users.
Public tags may be added to contract or external addresses, and any associated transactions will inherit that tag.
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 }
/>
</>
)
}
export default PublicTagsData;
import React, { useCallback } from 'react';
import type { ControllerRenderProps, Control } from 'react-hook-form';
import { RadioGroup, Radio, Stack } from '@chakra-ui/react';
import { Controller } from 'react-hook-form';
import type { Inputs } from './PublicTagsForm';
interface Props {
control: Control<Inputs>;
canReport: boolean;
}
export default function PublicTagFormAction({ control, canReport }: Props) {
const renderRadioGroup = useCallback(({ field }: {field: ControllerRenderProps<Inputs, 'action'>}) => {
return (
<RadioGroup defaultValue="add" value={ field.value } colorScheme="blue">
<Stack spacing={ 5 }>
<Radio value="add">
I want to add tags for my project
</Radio>
<Radio value="report" isDisabled={ canReport }>
I want to report an incorrect public tag
</Radio>
</Stack>
</RadioGroup>
)
}, [ canReport ])
return (
<Controller
name="action"
control={ control }
render={ renderRadioGroup }
/>
)
}
import React, { useCallback } from 'react';
import type { ControllerRenderProps, Control } from 'react-hook-form';
import { IconButton, Icon } from '@chakra-ui/react';
import { Controller } from 'react-hook-form';
import type { Inputs } from './PublicTagsForm';
import AddressInput from 'ui/shared/AddressInput';
import PlusIcon from 'icons/plus.svg';
import MinusIcon from 'icons/minus.svg';
interface Props {
control: Control<Inputs>;
index: number;
fieldsLength: number;
hasError: boolean;
onAddFieldClick: (e: React.SyntheticEvent) => void;
onRemoveFieldClick: (index: number) => (e: React.SyntheticEvent) => void;
}
const MAX_INPUTS_NUM = 10;
export default function PublicTagFormAction({ control, index, fieldsLength, hasError, onAddFieldClick, onRemoveFieldClick }: Props) {
const renderAddressInput = useCallback(({ field }: {field: ControllerRenderProps<Inputs, `addresses.${ number }.address`>}) => {
return (
<AddressInput<Inputs, `addresses.${ number }.address`>
field={ field }
isInvalid={ hasError }
size="lg"
placeholder="Smart contract / Address (0x...)"
/>
)
}, [ hasError ]);
return (
<>
<Controller
name={ `addresses.${ index }.address` }
control={ control }
render={ renderAddressInput }
/>
{ index === fieldsLength - 1 && fieldsLength < MAX_INPUTS_NUM && (
<IconButton
aria-label="add"
variant="iconBorder"
w="30px"
h="30px"
onClick={ onAddFieldClick }
icon={ <Icon as={ PlusIcon } w="20px" h="20px"/> }
position="absolute"
right={ index === 0 ? '-50px' : '-100px' }
top="25px"
/>
) }
{ fieldsLength > 1 && (
<IconButton
aria-label="delete"
variant="iconBorder"
w="30px"
h="30px"
onClick={ onRemoveFieldClick(index) }
icon={ <Icon as={ MinusIcon } w="20px" h="20px"/> }
position="absolute"
right="-50px"
top="25px"
/>
) }</>
)
}
import React, { useCallback } from 'react';
import type { ControllerRenderProps, Control } from 'react-hook-form';
import { FormControl, FormLabel, Textarea } from '@chakra-ui/react';
import { Controller } from 'react-hook-form';
import type { Inputs } from './PublicTagsForm';
interface Props {
control: Control<Inputs>;
}
export default function PublicTagFormComment({ control }: Props) {
const renderComment = useCallback(({ field }: {field: ControllerRenderProps<Inputs, 'comment'>}) => {
return (
<FormControl variant="floating" id={ field.name }>
<Textarea
{ ...field }
size="lg"
/>
<FormLabel>Specify the reason for adding tags and color preference(s).</FormLabel>
</FormControl>
)
}, [])
return (
<Controller
name="comment"
control={ control }
render={ renderComment }
/>
)
}
import {
Button,
Box,
Grid,
GridItem,
Text,
HStack,
} from '@chakra-ui/react';
import React, { useCallback } from 'react';
import type { TPublicTagItem, TPublicTag, TPublicTagAddress } from 'data/publicTags';
import type { Path } from 'react-hook-form';
import { useForm, useFieldArray } from 'react-hook-form';
import PublicTagFormAction from './PublicTagFormAction';
import PublicTagFormComment from './PublicTagFormComment';
import PublicTagsFormInput from './PublicTagsFormInput';
import PublicTagFormAddressInput from './PublicTagFormAddressInput';
type Props = {
changeToDataScreen: (success?: boolean) => void;
data?: TPublicTagItem;
}
export type Inputs = {
userName: string;
userEmail: string;
companyName: string;
companyUrl: string;
action: 'add' | 'report';
tag: string;
addresses: Array<{
name: string;
address: string;
}>;
comment: string;
}
const placeholders = {
userName: 'Your name',
userEmail: 'Email',
companyName: 'Company name',
companyUrl: 'Company website',
tag: 'Public tag (max 35 characters)',
comment: 'Specify the reason for adding tags and color preference(s).',
} as Record<Path<Inputs>, string>;
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 })) ||
[ { name: 'address.0.address', address: '' } ],
comment: data?.comment,
},
});
const { fields, append, remove } = useFieldArray({
name: 'addresses',
control,
});
const onAddFieldClick = useCallback(() => append({ address: '' }), [ append ]);
const onRemoveFieldClick = useCallback((index: number) => () => remove(index), [ remove ]);
const changeToData = useCallback(() => {
changeToDataScreen(true);
}, [ changeToDataScreen ]);
return (
<Box width={ `calc(100% - ${ ADDRESS_INPUT_BUTTONS_WIDTH }px)` } maxWidth="844px">
<Text size="sm" variant="secondary" paddingBottom={ 5 }>Company info</Text>
<Grid templateColumns="1fr 1fr" rowGap={ 4 } columnGap={ 5 }>
<GridItem>
<PublicTagsFormInput<Inputs> fieldName="userName" control={ control } label={ placeholders.userName } required/>
</GridItem>
<GridItem>
<PublicTagsFormInput<Inputs> fieldName="companyName" control={ control } label={ placeholders.companyName }/>
</GridItem>
<GridItem>
<PublicTagsFormInput<Inputs> fieldName="userEmail" control={ control } label={ placeholders.userEmail } required/>
</GridItem>
<GridItem>
<PublicTagsFormInput<Inputs> fieldName="companyUrl" control={ control } label={ placeholders.companyUrl }/>
</GridItem>
</Grid>
<Box marginTop={ 4 } marginBottom={ 8 }>
<PublicTagFormAction canReport={ Boolean(data) } control={ control }/>
</Box>
<Text size="sm" variant="secondary" marginBottom={ 5 }>Public tags (2 tags maximum, please use &quot;;&quot; as a divider)</Text>
<Box marginBottom={ 4 }>
<PublicTagsFormInput<Inputs> fieldName="tag" control={ control } label={ placeholders.tag } required/>
</Box>
{ fields.map((field, index) => {
return (
<Box position="relative" key={ field.id } marginBottom={ 4 }>
<PublicTagFormAddressInput
control={ control }
hasError={ Boolean(errors.addresses) }
index={ index }
fieldsLength={ fields.length }
onAddFieldClick={ onAddFieldClick }
onRemoveFieldClick={ onRemoveFieldClick }
/>
</Box>
)
}) }
<Box marginBottom={ 8 }>
<PublicTagFormComment control={ control }/>
</Box>
<HStack spacing={ 6 }>
<Button
size="lg"
variant="primary"
onClick={ handleSubmit(changeToData) }
disabled={ Object.keys(errors).length > 0 }
>
Send request
</Button>
<Button
size="lg"
variant="secondary"
onClick={ changeToData }
>
Cancel
</Button>
</HStack>
</Box>
)
}
export default PublicTagsForm;
import React, { useCallback } from 'react';
import type { ControllerRenderProps, FieldValues, Path, Control } from 'react-hook-form';
import { FormControl, FormLabel, Input } from '@chakra-ui/react';
import { Controller } from 'react-hook-form';
interface Props<TInputs extends FieldValues> {
fieldName: Path<TInputs>;
label: string;
required?: boolean;
control: Control<TInputs, object>;
}
export default function PublicTagsFormInput<Inputs extends FieldValues>({ label, control, required, fieldName }: Props<Inputs>) {
const renderInput = useCallback(({ field }: {field: ControllerRenderProps<Inputs, typeof fieldName>}) => {
return (
<FormControl variant="floating" id={ field.name } isRequired={ required }>
<Input
{ ...field }
size="lg"
required={ required }
/>
<FormLabel>{ label }</FormLabel>
</FormControl>
)
}, [ label, required ]);
return (
<Controller
name={ fieldName }
control={ control }
render={ renderInput }
/>
)
}
import React from 'react';
import { Heading } from '@chakra-ui/react';
const PageHeader = ({ text }: {text: string}) => {
return (
<Heading as="h1" size="lg" marginBottom={ 8 }>{ text }</Heading>
)
}
export default PageHeader;
import React from 'react'
import type { ControllerRenderProps } from 'react-hook-form';
import type { ControllerRenderProps, FieldValues, Path } from 'react-hook-form';
import {
Input,
......@@ -9,29 +9,29 @@ import {
const ADDRESS_LENGTH = 42;
type Props = {
field: ControllerRenderProps<any, 'address'>;
type Props<TInputs extends FieldValues, TInputName extends Path<TInputs>> = {
field: ControllerRenderProps<TInputs, TInputName>;
isInvalid: boolean;
size?: string;
placeholder?: string;
}
const AddressInput: React.FC<Props> = ({ field, isInvalid }) => {
export default function AddressInput<Inputs extends FieldValues, Name extends Path<Inputs>>(
{
field,
isInvalid,
size,
placeholder = 'Address (0x...)',
}: Props<Inputs, Name>) {
return (
<FormControl variant="floating" id="address" isRequired>
<Input
{ ...field }
placeholder=" "
isInvalid={ isInvalid }
maxLength={ ADDRESS_LENGTH }
// TODO: move this to input theme
css={{
':-webkit-autofill': { transition: 'background-color 5000s ease-in-out 0s' },
':-webkit-autofill:hover': { transition: 'background-color 5000s ease-in-out 0s' },
':-webkit-autofill:focus': { transition: 'background-color 5000s ease-in-out 0s' },
}}
size={ size }
/>
<FormLabel>Address (0x...)</FormLabel>
<FormLabel>{ placeholder }</FormLabel>
</FormControl>
)
}
export default AddressInput
......@@ -22,7 +22,7 @@ const CopyToClipboard = ({ text }: {text: string}) => {
icon={ <CopyIcon/> }
w="20px"
h="20px"
variant="iconBlue"
variant="icon"
onClick={ onCopy }
/>
</Tooltip>
......
......@@ -14,7 +14,7 @@ const DeleteButton = ({ onClick }: Props) => {
<Tooltip label="Delete">
<IconButton
aria-label="delete"
variant="iconBlue"
variant="icon"
w="30px"
h="30px"
onClick={ onClick }
......
......@@ -16,10 +16,10 @@ type Props = {
onClose: () => void;
onDelete: () => void;
title: string;
renderText: () => JSX.Element;
renderContent: () => JSX.Element;
}
const DeleteModal: React.FC<Props> = ({ isOpen, onClose, onDelete, title, renderText }) => {
const DeleteModal: React.FC<Props> = ({ isOpen, onClose, onDelete, title, renderContent }) => {
const onDeleteClick = useCallback(() => {
onDelete();
......@@ -33,7 +33,7 @@ const DeleteModal: React.FC<Props> = ({ isOpen, onClose, onDelete, title, render
<ModalHeader fontWeight="500" textStyle="h3">{ title }</ModalHeader>
<ModalCloseButton/>
<ModalBody>
{ renderText() }
{ renderContent() }
</ModalBody>
<ModalFooter>
<Button variant="primary" size="lg" onClick={ onDeleteClick }>
......
......@@ -14,7 +14,7 @@ const EditButton = ({ onClick }: Props) => {
<Tooltip label="Edit">
<IconButton
aria-label="edit"
variant="iconBlue"
variant="icon"
w="30px"
h="30px"
onClick={ onClick }
......
......@@ -20,7 +20,6 @@ const TagInput: React.FC<Props> = ({ field, isInvalid }) => {
<FormControl variant="floating" id="tag" isRequired>
<Input
{ ...field }
placeholder=" "
isInvalid={ isInvalid }
maxLength={ TAG_MAX_LENGTH }
/>
......
......@@ -19,7 +19,6 @@ const AddressInput: React.FC<Props> = ({ field, isInvalid }) => {
<FormControl variant="floating" id="transaction" isRequired>
<Input
{ ...field }
placeholder=" "
isInvalid={ isInvalid }
maxLength={ HASH_LENGTH }
/>
......
......@@ -43,7 +43,7 @@ const AddressForm: React.FC<Props> = ({ data }) => {
const onSubmit: SubmitHandler<Inputs> = data => console.log(data);
const renderAddressInput = useCallback(({ field }: {field: ControllerRenderProps<Inputs, 'address'>}) => {
return <AddressInput field={ field } isInvalid={ Boolean(errors.address) }/>
return <AddressInput<Inputs, 'address'> field={ field } isInvalid={ Boolean(errors.address) }/>
}, [ errors ]);
const renderTagInput = useCallback(({ field }: {field: ControllerRenderProps<Inputs, 'tag'>}) => {
......@@ -85,7 +85,7 @@ const AddressForm: React.FC<Props> = ({ data }) => {
render={ renderTagInput }
/>
</Box>
<Text color="gray.500" fontSize="sm" marginBottom={ 5 }>
<Text variant="secondary" fontSize="sm" marginBottom={ 5 }>
Please select what types of notifications you will receive
</Text>
<Box marginBottom={ 8 }>
......@@ -102,7 +102,7 @@ const AddressForm: React.FC<Props> = ({ data }) => {
}) }
</Grid>
</Box>
<Text color="gray.500" fontSize="sm" marginBottom={ 5 }>Notification methods</Text>
<Text variant="secondary" fontSize="sm" marginBottom={ 5 }>Notification methods</Text>
<Controller
name="notification"
control={ control }
......
......@@ -26,7 +26,7 @@ const DeleteAddressModal: React.FC<Props> = ({ isOpen, onClose, address }) => {
onClose={ onClose }
onDelete={ onDelete }
title="Remove address from watch list"
renderText={ renderText }
renderContent={ renderText }
/>
)
}
......
......@@ -11,7 +11,6 @@ import WalletIcon from 'icons/wallet.svg';
const WatchListAddressItem = ({ item }: {item: TWatchlistItem}) => {
const mainTextColor = useColorModeValue('gray.700', 'gray.50');
const secondaryTextColor = useColorModeValue('gray.500', 'gray.400');
return (
<HStack spacing={ 3 } align="top">
......@@ -22,14 +21,14 @@ const WatchListAddressItem = ({ item }: {item: TWatchlistItem}) => {
<HStack spacing={ 0 } fontSize="sm" h={ 6 }>
<Image src="./xdai.png" alt="chain-logo" marginRight="10px" w="16px" h="16px"/>
<Text color={ mainTextColor }>{ `xDAI balance:${ nbsp }` + item.tokenBalance }</Text>
<Text color={ secondaryTextColor }>{ `${ nbsp }($${ item.tokenBalanceUSD } USD)` }</Text>
<Text variant="secondary">{ `${ nbsp }($${ item.tokenBalanceUSD } USD)` }</Text>
</HStack>
) }
{ item.tokensAmount && (
<HStack spacing={ 0 } fontSize="sm" h={ 6 }>
<Icon as={ TokensIcon } marginRight="10px" w="17px" h="16px"/>
<Text color={ mainTextColor }>{ `Tokens:${ nbsp }` + item.tokensAmount }</Text>
<Text color={ secondaryTextColor }>{ `${ nbsp }($${ item.tokensUSD } USD)` }</Text>
<Text variant="secondary">{ `${ nbsp }($${ item.tokensUSD } USD)` }</Text>
</HStack>
) }
{ item.totalUSD && (
......
......@@ -919,6 +919,13 @@
dependencies:
"@types/react" "*"
"@types/react-scroll@^1.8.4":
version "1.8.4"
resolved "https://registry.yarnpkg.com/@types/react-scroll/-/react-scroll-1.8.4.tgz#2b6258fb34104d3fcc7a22e8eceaadc669ba3ad1"
integrity sha512-DpHA9PYw42/rBrfKbGE/kAEvHRfyDL/ACfKB/ORWUYuCLi/yGrntxSzYXmg/7TLgQsJ5ma13GCDOzFSOz+8XOA==
dependencies:
"@types/react" "*"
"@types/react@*", "@types/react@18.0.9":
version "18.0.9"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.9.tgz#d6712a38bd6cd83469603e7359511126f122e878"
......@@ -2413,6 +2420,11 @@ lodash.mergewith@4.6.2:
resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz#617121f89ac55f59047c7aec1ccd6654c6590f55"
integrity sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==
lodash.throttle@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4"
integrity sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==
lodash@^4.0.0:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
......@@ -2756,7 +2768,7 @@ prelude-ls@^1.2.1:
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
prop-types@^15.6.2, prop-types@^15.8.1:
prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
......@@ -2853,6 +2865,14 @@ react-remove-scroll@^2.5.4:
use-callback-ref "^1.3.0"
use-sidecar "^1.1.2"
react-scroll@^1.8.7:
version "1.8.7"
resolved "https://registry.yarnpkg.com/react-scroll/-/react-scroll-1.8.7.tgz#8020035329efad00f03964e18aff6822137de3aa"
integrity sha512-fBOIwweAlhicx8RqP9tQXn/Uhd+DTtVRjw+0VBsIn1Z+MjRYLhTZ0tMoTAU1vOD3dce8mI6copexI4yWII+Luw==
dependencies:
lodash.throttle "^4.1.1"
prop-types "^15.7.2"
react-style-singleton@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.2.1.tgz#f99e420492b2d8f34d38308ff660b60d0b1205b4"
......
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