Commit fea7d736 authored by Igor Stuev's avatar Igor Stuev Committed by GitHub

Merge pull request #401 from blockscout/fixes2

Fixes2
parents 39346abf f2438ff3
...@@ -22,7 +22,7 @@ const baseStyleDialog = defineStyle((props) => { ...@@ -22,7 +22,7 @@ const baseStyleDialog = defineStyle((props) => {
const baseStyleDialogContainer = defineStyle({ const baseStyleDialogContainer = defineStyle({
'::-webkit-scrollbar': { display: 'none' }, '::-webkit-scrollbar': { display: 'none' },
'scrollbar-width': 'none', 'scrollbar-width': 'none',
'@supports (height: -webkit-fill-available)': { height: '100vh' }, '@supports (height: -webkit-fill-available)': { height: '-webkit-fill-available' },
}); });
const baseStyleHeader = defineStyle((props) => ({ const baseStyleHeader = defineStyle((props) => ({
...@@ -63,6 +63,7 @@ const baseStyleOverlay = defineStyle({ ...@@ -63,6 +63,7 @@ const baseStyleOverlay = defineStyle({
const baseStyle = definePartsStyle((props) => ({ const baseStyle = definePartsStyle((props) => ({
dialog: runIfFn(baseStyleDialog, props), dialog: runIfFn(baseStyleDialog, props),
dialogContainer: baseStyleDialogContainer, dialogContainer: baseStyleDialogContainer,
header: runIfFn(baseStyleHeader, props), header: runIfFn(baseStyleHeader, props),
body: baseStyleBody, body: baseStyleBody,
footer: baseStyleFooter, footer: baseStyleFooter,
......
...@@ -34,7 +34,7 @@ const NAME_MAX_LENGTH = 255; ...@@ -34,7 +34,7 @@ const NAME_MAX_LENGTH = 255;
const ApiKeyForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => { const ApiKeyForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
const { control, handleSubmit, formState: { errors, isValid, isDirty }, setError } = useForm<Inputs>({ const { control, handleSubmit, formState: { errors, isValid, isDirty }, setError } = useForm<Inputs>({
mode: 'all', mode: 'onTouched',
defaultValues: { defaultValues: {
token: data?.api_key || '', token: data?.api_key || '',
name: data?.name || '', name: data?.name || '',
...@@ -113,7 +113,7 @@ const ApiKeyForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => { ...@@ -113,7 +113,7 @@ const ApiKeyForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
maxLength={ NAME_MAX_LENGTH } maxLength={ NAME_MAX_LENGTH }
/> />
<FormLabel> <FormLabel>
<InputPlaceholder text="Application name for API key (e.g Web3 project)" error={ errors.name?.message }/> <InputPlaceholder text="Application name for API key (e.g Web3 project)" error={ errors.name }/>
</FormLabel> </FormLabel>
</FormControl> </FormControl>
); );
......
...@@ -42,7 +42,7 @@ const CustomAbiForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => { ...@@ -42,7 +42,7 @@ const CustomAbiForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
name: data?.name || '', name: data?.name || '',
abi: JSON.stringify(data?.abi) || '', abi: JSON.stringify(data?.abi) || '',
}, },
mode: 'all', mode: 'onTouched',
}); });
const queryClient = useQueryClient(); const queryClient = useQueryClient();
...@@ -118,7 +118,7 @@ const CustomAbiForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => { ...@@ -118,7 +118,7 @@ const CustomAbiForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
isInvalid={ Boolean(errors.name) } isInvalid={ Boolean(errors.name) }
maxLength={ NAME_MAX_LENGTH } maxLength={ NAME_MAX_LENGTH }
/> />
<InputPlaceholder text="Project name" error={ errors.name?.message }/> <InputPlaceholder text="Project name" error={ errors.name }/>
</FormControl> </FormControl>
); );
}, [ errors, formBackgroundColor ]); }, [ errors, formBackgroundColor ]);
...@@ -132,7 +132,7 @@ const CustomAbiForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => { ...@@ -132,7 +132,7 @@ const CustomAbiForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
minH="300px" minH="300px"
isInvalid={ Boolean(errors.abi) } isInvalid={ Boolean(errors.abi) }
/> />
<InputPlaceholder text="Custom ABI [{...}] (JSON format)" error={ errors.abi?.message }/> <InputPlaceholder text="Custom ABI [{...}] (JSON format)" error={ errors.abi }/>
</FormControl> </FormControl>
); );
}, [ errors, formBackgroundColor ]); }, [ errors, formBackgroundColor ]);
......
...@@ -79,13 +79,13 @@ const PublicTagsComponent: React.FC = () => { ...@@ -79,13 +79,13 @@ const PublicTagsComponent: React.FC = () => {
return ( return (
<Page> <Page>
{ isMobile && screen === 'form' && ( { screen === 'form' && (
<Link display="inline-flex" alignItems="center" mb={ 6 } onClick={ onGoBack }> <Link display="inline-flex" alignItems="center" mb={ 6 } onClick={ onGoBack }>
<Icon as={ eastArrowIcon } boxSize={ 6 } transform="rotate(180deg)"/> <Icon as={ eastArrowIcon } boxSize={ 6 } transform="rotate(180deg)"/>
<Text variant="inherit" fontSize="sm" ml={ 2 }>Public tags</Text> { isMobile && <Text variant="inherit" fontSize="sm" ml={ 2 }>Public tags</Text> }
</Link> </Link>
) } ) }
<PageTitle text={ header }/> <PageTitle text={ header } display={{ base: 'block', lg: 'inline-flex' }} ml={{ base: 0, lg: 3 }}/>
{ content } { content }
</Page> </Page>
); );
......
...@@ -35,7 +35,7 @@ const AddressForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => { ...@@ -35,7 +35,7 @@ const AddressForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
const fetch = useFetch(); const fetch = useFetch();
const [ pending, setPending ] = useState(false); const [ pending, setPending ] = useState(false);
const { control, handleSubmit, formState: { errors, isValid, isDirty }, setError } = useForm<Inputs>({ const { control, handleSubmit, formState: { errors, isValid, isDirty }, setError } = useForm<Inputs>({
mode: 'all', mode: 'onTouched',
defaultValues: { defaultValues: {
address: data?.address_hash || '', address: data?.address_hash || '',
tag: data?.name || '', tag: data?.name || '',
......
...@@ -36,7 +36,7 @@ const TransactionForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => ...@@ -36,7 +36,7 @@ const TransactionForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) =>
const formBackgroundColor = useColorModeValue('white', 'gray.900'); const formBackgroundColor = useColorModeValue('white', 'gray.900');
const { control, handleSubmit, formState: { errors, isValid, isDirty }, setError } = useForm<Inputs>({ const { control, handleSubmit, formState: { errors, isValid, isDirty }, setError } = useForm<Inputs>({
mode: 'all', mode: 'onTouched',
defaultValues: { defaultValues: {
transaction: data?.transaction_hash || '', transaction: data?.transaction_hash || '',
tag: data?.name || '', tag: data?.name || '',
......
...@@ -25,7 +25,7 @@ export default function PublicTagFormComment({ control, error, size }: Props) { ...@@ -25,7 +25,7 @@ export default function PublicTagFormComment({ control, error, size }: Props) {
isInvalid={ Boolean(error) } isInvalid={ Boolean(error) }
/> />
<FormLabel> <FormLabel>
<InputPlaceholder text="Specify the reason for adding tags and color preference(s)" error={ error?.message }/> <InputPlaceholder text="Specify the reason for adding tags and color preference(s)" error={ error }/>
</FormLabel> </FormLabel>
</FormControl> </FormControl>
); );
......
...@@ -73,7 +73,7 @@ const PublicTagsForm = ({ changeToDataScreen, data }: Props) => { ...@@ -73,7 +73,7 @@ const PublicTagsForm = ({ changeToDataScreen, data }: Props) => {
comment: data?.additional_comment || '', comment: data?.additional_comment || '',
action: data?.is_owner === undefined || data?.is_owner ? 'add' : 'report', action: data?.is_owner === undefined || data?.is_owner ? 'add' : 'report',
}, },
mode: 'all', mode: 'onTouched',
}); });
const { fields, append, remove } = useFieldArray({ const { fields, append, remove } = useFieldArray({
...@@ -146,11 +146,6 @@ const PublicTagsForm = ({ changeToDataScreen, data }: Props) => { ...@@ -146,11 +146,6 @@ const PublicTagsForm = ({ changeToDataScreen, data }: Props) => {
mutation.mutate(data); mutation.mutate(data);
}, [ mutation ]); }, [ mutation ]);
const changeToData = useCallback(() => {
setAlertVisible(false);
changeToDataScreen(false);
}, [ changeToDataScreen ]);
return ( return (
<chakra.form <chakra.form
noValidate noValidate
...@@ -242,14 +237,6 @@ const PublicTagsForm = ({ changeToDataScreen, data }: Props) => { ...@@ -242,14 +237,6 @@ const PublicTagsForm = ({ changeToDataScreen, data }: Props) => {
> >
Send request Send request
</Button> </Button>
<Button
size="lg"
variant="outline"
onClick={ changeToData }
disabled={ mutation.isLoading }
>
Cancel
</Button>
</HStack> </HStack>
</chakra.form> </chakra.form>
); );
......
...@@ -36,7 +36,7 @@ export default function PublicTagsFormInput<Inputs extends FieldValues>({ ...@@ -36,7 +36,7 @@ export default function PublicTagsFormInput<Inputs extends FieldValues>({
isInvalid={ Boolean(error) } isInvalid={ Boolean(error) }
maxLength={ TEXT_INPUT_MAX_LENGTH } maxLength={ TEXT_INPUT_MAX_LENGTH }
/> />
<InputPlaceholder text={ label } error={ error?.message }/> <InputPlaceholder text={ label } error={ error }/>
</FormControl> </FormControl>
); );
}, [ label, required, error, size ]); }, [ label, required, error, size ]);
......
...@@ -32,7 +32,7 @@ export default function AddressInput<Inputs extends FieldValues, Name extends Pa ...@@ -32,7 +32,7 @@ export default function AddressInput<Inputs extends FieldValues, Name extends Pa
isInvalid={ Boolean(error) } isInvalid={ Boolean(error) }
maxLength={ ADDRESS_LENGTH } maxLength={ ADDRESS_LENGTH }
/> />
<InputPlaceholder text={ placeholder } error={ error?.message }/> <InputPlaceholder text={ placeholder } error={ error }/>
</FormControl> </FormControl>
); );
} }
...@@ -47,7 +47,7 @@ export default function FormModal<TData>({ ...@@ -47,7 +47,7 @@ export default function FormModal<TData>({
<ModalContent> <ModalContent>
<ModalHeader fontWeight="500" textStyle="h3">{ title }</ModalHeader> <ModalHeader fontWeight="500" textStyle="h3">{ title }</ModalHeader>
<ModalCloseButton/> <ModalCloseButton/>
<ModalBody mb={ 0 }> <ModalBody>
{ (isAlertVisible || text) && ( { (isAlertVisible || text) && (
<Box marginBottom={{ base: 6, lg: 8 }}> <Box marginBottom={{ base: 6, lg: 8 }}>
{ text && ( { text && (
......
import { FormLabel, chakra } from '@chakra-ui/react'; import { FormLabel, chakra } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { FieldError } from 'react-hook-form';
interface Props { interface Props {
text: string; text: string;
error?: string; error?: FieldError;
} }
const InputPlaceholder = ({ text, error }: Props) => { const InputPlaceholder = ({ text, error }: Props) => {
let errorMessage = error?.message;
if (!errorMessage && error?.type === 'pattern') {
errorMessage = 'Invalid format';
}
return ( return (
<FormLabel> <FormLabel>
<chakra.span>{ text }</chakra.span> <chakra.span>{ text }</chakra.span>
{ error && <chakra.span order={ 3 } whiteSpace="pre"> - { error }</chakra.span> } { errorMessage && <chakra.span order={ 3 } whiteSpace="pre"> - { errorMessage }</chakra.span> }
</FormLabel> </FormLabel>
); );
}; };
......
import { Heading } from '@chakra-ui/react'; import { Heading, chakra } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
const PageTitle = ({ text }: {text: string}) => { const PageTitle = ({ text, className }: {text: string; className?: string}) => {
return ( return (
<Heading as="h1" size="lg" marginBottom={ 6 }>{ text }</Heading> <Heading as="h1" size="lg" marginBottom={ 6 } className={ className }>{ text }</Heading>
); );
}; };
export default PageTitle; export default chakra(PageTitle);
...@@ -23,7 +23,7 @@ function TagInput<Inputs extends FieldValues, Name extends Path<Inputs>>({ field ...@@ -23,7 +23,7 @@ function TagInput<Inputs extends FieldValues, Name extends Path<Inputs>>({ field
isInvalid={ Boolean(error) } isInvalid={ Boolean(error) }
maxLength={ TAG_MAX_LENGTH } maxLength={ TAG_MAX_LENGTH }
/> />
<InputPlaceholder text="Private tag (max 35 characters)" error={ error?.message }/> <InputPlaceholder text="Private tag (max 35 characters)" error={ error }/>
</FormControl> </FormControl>
); );
} }
......
...@@ -22,7 +22,7 @@ function TransactionInput<Field extends Partial<ControllerRenderProps<FieldValue ...@@ -22,7 +22,7 @@ function TransactionInput<Field extends Partial<ControllerRenderProps<FieldValue
isInvalid={ Boolean(error) } isInvalid={ Boolean(error) }
maxLength={ TRANSACTION_HASH_LENGTH } maxLength={ TRANSACTION_HASH_LENGTH }
/> />
<InputPlaceholder text="Transaction hash (0x...)" error={ error?.message }/> <InputPlaceholder text="Transaction hash (0x...)" error={ error }/>
</FormControl> </FormControl>
); );
} }
......
...@@ -80,7 +80,7 @@ const AddressForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => { ...@@ -80,7 +80,7 @@ const AddressForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
notification: data ? data.notification_methods.email : true, notification: data ? data.notification_methods.email : true,
notification_settings: notificationsDefault, notification_settings: notificationsDefault,
}, },
mode: 'all', mode: 'onTouched',
}); });
const queryClient = useQueryClient(); const queryClient = useQueryClient();
......
...@@ -4,6 +4,8 @@ import React, { useCallback, useState } from 'react'; ...@@ -4,6 +4,8 @@ import React, { useCallback, useState } from 'react';
import type { TWatchlistItem } from 'types/client/account'; import type { TWatchlistItem } from 'types/client/account';
import useFetch from 'lib/hooks/useFetch';
import useToast from 'lib/hooks/useToast';
import AccountListItemMobile from 'ui/shared/AccountListItemMobile'; import AccountListItemMobile from 'ui/shared/AccountListItemMobile';
import TableItemActionButtons from 'ui/shared/TableItemActionButtons'; import TableItemActionButtons from 'ui/shared/TableItemActionButtons';
...@@ -17,6 +19,7 @@ interface Props { ...@@ -17,6 +19,7 @@ interface Props {
const WatchListItem = ({ item, onEditClick, onDeleteClick }: Props) => { const WatchListItem = ({ item, onEditClick, onDeleteClick }: Props) => {
const [ notificationEnabled, setNotificationEnabled ] = useState(item.notification_methods.email); const [ notificationEnabled, setNotificationEnabled ] = useState(item.notification_methods.email);
const [ switchDisabled, setSwitchDisabled ] = useState(false);
const onItemEditClick = useCallback(() => { const onItemEditClick = useCallback(() => {
return onEditClick(item); return onEditClick(item);
}, [ item, onEditClick ]); }, [ item, onEditClick ]);
...@@ -25,16 +28,49 @@ const WatchListItem = ({ item, onEditClick, onDeleteClick }: Props) => { ...@@ -25,16 +28,49 @@ const WatchListItem = ({ item, onEditClick, onDeleteClick }: Props) => {
return onDeleteClick(item); return onDeleteClick(item);
}, [ item, onDeleteClick ]); }, [ item, onDeleteClick ]);
const errorToast = useToast();
const fetch = useFetch();
const showErrorToast = useCallback(() => {
errorToast({
position: 'top-right',
description: 'There has been an error processing your request',
colorScheme: 'red',
status: 'error',
variant: 'subtle',
isClosable: true,
icon: null,
});
}, [ errorToast ]);
const notificationToast = useToast();
const showNotificationToast = useCallback((isOn: boolean) => {
notificationToast({
position: 'top-right',
description: isOn ? 'Email notification is ON' : 'Email notification is OFF',
colorScheme: 'green',
status: 'success',
variant: 'subtle',
title: 'Success',
isClosable: true,
icon: null,
});
}, [ notificationToast ]);
const { mutate } = useMutation(() => { const { mutate } = useMutation(() => {
const data = { ...item, notification_methods: { email: !notificationEnabled } }; setSwitchDisabled(true);
return fetch(`/node-api/account/watchlist/${ item.id }`, { method: 'PUT', body: JSON.stringify(data) }); const body = { ...item, notification_methods: { email: !notificationEnabled } };
setNotificationEnabled(prevState => !prevState);
return fetch(`/node-api/account/watchlist/${ item.id }`, { method: 'PUT', body });
}, { }, {
onError: () => { onError: () => {
// eslint-disable-next-line no-console showErrorToast();
console.log('error'); setNotificationEnabled(prevState => !prevState);
setSwitchDisabled(false);
}, },
onSuccess: () => { onSuccess: () => {
setNotificationEnabled(prevState => !prevState); setSwitchDisabled(false);
showNotificationToast(!notificationEnabled);
}, },
}); });
...@@ -56,7 +92,14 @@ const WatchListItem = ({ item, onEditClick, onDeleteClick }: Props) => { ...@@ -56,7 +92,14 @@ const WatchListItem = ({ item, onEditClick, onDeleteClick }: Props) => {
<Flex alignItems="center" justifyContent="space-between" mt={ 6 } w="100%"> <Flex alignItems="center" justifyContent="space-between" mt={ 6 } w="100%">
<HStack spacing={ 3 }> <HStack spacing={ 3 }>
<Text fontSize="sm" fontWeight={ 500 }>Email notification</Text> <Text fontSize="sm" fontWeight={ 500 }>Email notification</Text>
<Switch colorScheme="blue" size="md" isChecked={ notificationEnabled } onChange={ onSwitch } aria-label="Email notification"/> <Switch
colorScheme="blue"
size="md"
isChecked={ notificationEnabled }
onChange={ onSwitch }
aria-label="Email notification"
isDisabled={ switchDisabled }
/>
</HStack> </HStack>
<TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick }/> <TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick }/>
</Flex> </Flex>
......
...@@ -33,11 +33,11 @@ const WatchlistTableItem = ({ item, onEditClick, onDeleteClick }: Props) => { ...@@ -33,11 +33,11 @@ const WatchlistTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
return onDeleteClick(item); return onDeleteClick(item);
}, [ item, onDeleteClick ]); }, [ item, onDeleteClick ]);
const toast = useToast(); const errorToast = useToast();
const fetch = useFetch(); const fetch = useFetch();
const showToast = useCallback(() => { const showErrorToast = useCallback(() => {
toast({ errorToast({
position: 'top-right', position: 'top-right',
description: 'There has been an error processing your request', description: 'There has been an error processing your request',
colorScheme: 'red', colorScheme: 'red',
...@@ -46,7 +46,21 @@ const WatchlistTableItem = ({ item, onEditClick, onDeleteClick }: Props) => { ...@@ -46,7 +46,21 @@ const WatchlistTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
isClosable: true, isClosable: true,
icon: null, icon: null,
}); });
}, [ toast ]); }, [ errorToast ]);
const notificationToast = useToast();
const showNotificationToast = useCallback((isOn: boolean) => {
notificationToast({
position: 'top-right',
description: isOn ? 'Email notification is ON' : 'Email notification is OFF',
colorScheme: 'green',
status: 'success',
variant: 'subtle',
title: 'Success',
isClosable: true,
icon: null,
});
}, [ notificationToast ]);
const { mutate } = useMutation(() => { const { mutate } = useMutation(() => {
setSwitchDisabled(true); setSwitchDisabled(true);
...@@ -55,12 +69,13 @@ const WatchlistTableItem = ({ item, onEditClick, onDeleteClick }: Props) => { ...@@ -55,12 +69,13 @@ const WatchlistTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
return fetch(`/node-api/account/watchlist/${ item.id }`, { method: 'PUT', body }); return fetch(`/node-api/account/watchlist/${ item.id }`, { method: 'PUT', body });
}, { }, {
onError: () => { onError: () => {
showToast(); showErrorToast();
setNotificationEnabled(prevState => !prevState); setNotificationEnabled(prevState => !prevState);
setSwitchDisabled(false); setSwitchDisabled(false);
}, },
onSuccess: () => { onSuccess: () => {
setSwitchDisabled(false); setSwitchDisabled(false);
showNotificationToast(!notificationEnabled);
}, },
}); });
......
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