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