Commit ffb25aa1 authored by N's avatar N Committed by GitHub

Merge pull request #29 from blockscout/form

react hook form
parents fa6c57ab f5db3914
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
"next-react-svg": "1.1.3", "next-react-svg": "1.1.3",
"react": "18.1.0", "react": "18.1.0",
"react-dom": "18.1.0", "react-dom": "18.1.0",
"react-hook-form": "^7.33.1",
"react-jazzicon": "^1.0.4" "react-jazzicon": "^1.0.4"
}, },
"devDependencies": { "devDependencies": {
......
import React, { useCallback, useEffect } from 'react';
import type { SubmitHandler, ControllerRenderProps } from 'react-hook-form';
import { useForm, Controller } from 'react-hook-form';
import {
Box,
Button,
Checkbox,
Text,
Grid,
GridItem,
} from '@chakra-ui/react';
import AddressInput from './AddressInput';
import TagInput from './TagInput';
import type { TWatchlistItem } from '../../data/watchlist';
const NOTIFICATIONS = [ 'xDAI', 'ERC-20', 'ERC-721, ERC-1155 (NFT)' ];
const ADDRESS_LENGTH = 42;
const TAG_MAX_LENGTH = 35;
type Props = {
data?: TWatchlistItem;
}
type Inputs = {
address: string;
tag: string;
notification: boolean;
}
const AddressModal: React.FC<Props> = ({ data }) => {
const { control, handleSubmit, formState: { errors }, setValue } = useForm<Inputs>();
useEffect(() => {
setValue('address', data?.address || '');
setValue('tag', data?.tag || '');
setValue('notification', Boolean(data?.notification));
}, [ setValue, data ]);
// eslint-disable-next-line no-console
const onSubmit: SubmitHandler<Inputs> = data => console.log(data);
const renderAddressInput = useCallback(({ field }: {field: ControllerRenderProps<Inputs, 'address'>}) => {
return <AddressInput field={ field } isInvalid={ Boolean(errors.address) }/>
}, [ errors ]);
const renderTagInput = useCallback(({ field }: {field: ControllerRenderProps<Inputs, 'tag'>}) => {
return <TagInput field={ field } isInvalid={ Boolean(errors.tag) }/>
}, [ errors ]);
const renderCheckbox = useCallback(({ field }: {field: ControllerRenderProps<Inputs, 'notification'>}) => (
<Checkbox
isChecked={ field.value }
onChange={ field.onChange }
ref={ field.ref }
colorScheme="blue"
size="lg"
>
Email notifications
</Checkbox>
), []);
return (
<>
<Box marginBottom={ 5 }>
<Controller
name="address"
control={ control }
rules={{
maxLength: ADDRESS_LENGTH,
minLength: ADDRESS_LENGTH,
}}
render={ renderAddressInput }
/>
</Box>
<Controller
name="tag"
control={ control }
rules={{
maxLength: TAG_MAX_LENGTH,
}}
render={ renderTagInput }
/>
<Text color="gray.500" fontSize="sm" marginBottom={ 8 }>
Please select what types of notifications you will receive:
</Text>
<Box marginBottom={ 8 }>
{ /* add them to the form later */ }
<Grid templateColumns="repeat(3, max-content)" gap="20px 24px">
{ NOTIFICATIONS.map((notification: string) => {
return (
<>
<GridItem>{ notification }</GridItem>
<GridItem><Checkbox colorScheme="blue" size="lg">Incoming</Checkbox></GridItem>
<GridItem><Checkbox colorScheme="blue" size="lg">Outgoing</Checkbox></GridItem>
</>
)
}) }
</Grid>
</Box>
<Text color="gray.500" fontSize="sm" marginBottom={ 5 }>Notification methods:</Text>
<Controller
name="notification"
control={ control }
render={ renderCheckbox }
/>
<Box marginTop={ 8 }>
<Button
variant="primary"
onClick={ handleSubmit(onSubmit) }
disabled={ Object.keys(errors).length > 0 }
>
{ data ? 'Save changes' : 'Add address' }
</Button>
</Box>
</>
)
}
export default AddressModal;
import React from 'react'
import type { ControllerRenderProps } from 'react-hook-form';
import {
Input,
FormControl,
FormLabel,
} from '@chakra-ui/react';
const ADDRESS_LENGTH = 42;
type Props = {
field: ControllerRenderProps<any, 'address'>;
isInvalid: boolean;
}
const AddressInput: React.FC<Props> = ({ field, isInvalid }) => {
return (
<FormControl variant="floating" id="address" isRequired>
<Input
{ ...field }
placeholder=" "
isInvalid={ isInvalid }
maxLength={ ADDRESS_LENGTH }
/>
<FormLabel>Address (0x...)</FormLabel>
</FormControl>
)
}
export default AddressInput
import React, { useCallback, useEffect, useState } from 'react'; import React from 'react';
import { import {
Box,
Button,
Modal, Modal,
ModalOverlay, ModalOverlay,
ModalContent, ModalContent,
ModalHeader, ModalHeader,
ModalFooter,
ModalBody, ModalBody,
ModalCloseButton, ModalCloseButton,
Input,
Checkbox,
Text, Text,
Grid,
GridItem,
FormControl,
FormLabel,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import type { TWatchlistItem } from '../../data/watchlist'; import type { TWatchlistItem } from '../../data/watchlist';
const NOTIFICATIONS = [ 'xDAI', 'ERC-20', 'ERC-721, ERC-1155 (NFT)' ]; import AddressForm from './AddressForm';
const ADDRESS_LENGTH = 42;
type Props = { type Props = {
isOpen: boolean; isOpen: boolean;
onClose: () => void; onClose: () => void;
getDisclosureProps: () => any;
data?: TWatchlistItem; data?: TWatchlistItem;
} }
const AddressModal: React.FC<Props> = ({ isOpen, onClose, data }) => { const AddressModal: React.FC<Props> = ({ isOpen, onClose, data }) => {
// надо чето придумать с формой
// потом доделаем
const [ address, setAddress ] = useState<string>();
const [ tag, setTag ] = useState<string>();
const [ notification, setNotification ] = useState<boolean>();
const [ addressError, setAddressError ] = useState<boolean>(false);
const isValidAddress = (address: string) => address.length === ADDRESS_LENGTH;
useEffect(() => {
setAddress(data?.address);
setAddressError(false);
setTag(data?.tag);
setNotification(data?.notification);
}, [ data ]);
const onAddressChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
if (addressError && isValidAddress(event.target.value)) {
setAddressError(false);
}
setAddress(event.target.value);
}, [ addressError ]);
const validateAddress = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
if (!isValidAddress(event.target.value)) {
setAddressError(true);
}
}, [ ]);
const onTagChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
setTag(event.target.value)
}, [ ]);
const onNotificationChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => setNotification(event.target.checked), [ setNotification ]);
const onButtonClick = useCallback(() => {
// eslint-disable-next-line no-console
console.log(address, tag, notification);
onClose()
}, [ address, tag, notification, onClose ]);
const title = data ? 'Edit watchlist address' : 'New Address to Watchlist'; const title = data ? 'Edit watchlist address' : 'New Address to Watchlist';
return ( return (
...@@ -84,56 +32,11 @@ const AddressModal: React.FC<Props> = ({ isOpen, onClose, data }) => { ...@@ -84,56 +32,11 @@ const AddressModal: React.FC<Props> = ({ isOpen, onClose, data }) => {
<ModalBody> <ModalBody>
{ !data && ( { !data && (
<Text lineHeight="30px" marginBottom={ 12 }> <Text lineHeight="30px" marginBottom={ 12 }>
An Email notification can be sent to you when an address on your watch list sends or receives any transactions. An Email notification can be sent to you when an address on your watch list sends or receives any transactions.
</Text> </Text>
) } ) }
<FormControl variant="floating" id="address" marginBottom={ 5 } isRequired> <AddressForm data={ data }/>
<Input
placeholder=" "
onChange={ onAddressChange }
value={ address || '' }
isInvalid={ addressError }
onBlur={ validateAddress }
maxLength={ ADDRESS_LENGTH }
/>
<FormLabel>Address (0x...)</FormLabel>
</FormControl>
<FormControl variant="floating" id="tag" marginBottom={ 8 } isRequired>
<Input placeholder=" " onChange={ onTagChange } value={ tag || '' } maxLength={ 35 }/>
<FormLabel>Private tag (max 35 characters)</FormLabel>
</FormControl>
<Text color="gray.500" fontSize="sm" marginBottom={ 8 }>
Please select what types of notifications you will receive:
</Text>
<Box marginBottom={ 8 }>
<Grid templateColumns="repeat(3, max-content)" gap="20px 24px">
{ NOTIFICATIONS.map((notification: string) => {
return (
<>
<GridItem>{ notification }</GridItem>
<GridItem><Checkbox colorScheme="blue" size="lg">Incoming</Checkbox></GridItem>
<GridItem><Checkbox colorScheme="blue" size="lg">Outgoing</Checkbox></GridItem>
</>
)
}) }
</Grid>
</Box>
<Text color="gray.500" fontSize="sm" marginBottom={ 5 }>Notification methods:</Text>
<Checkbox
isChecked={ notification }
colorScheme="blue"
onChange={ onNotificationChange }
size="lg"
>
Email notifications
</Checkbox>
</ModalBody> </ModalBody>
<ModalFooter justifyContent="flex-start">
<Button variant="primary" onClick={ onButtonClick } disabled={ addressError }>
{ data ? 'Save changes' : 'Add address' }
</Button>
</ModalFooter>
</ModalContent> </ModalContent>
</Modal> </Modal>
) )
......
import React from 'react';
import type { ControllerRenderProps } from 'react-hook-form';
import {
Input,
FormControl,
FormLabel,
} from '@chakra-ui/react';
const TAG_MAX_LENGTH = 35;
type Props = {
field: ControllerRenderProps<any, 'tag'>;
isInvalid: boolean;
}
const TagInput: React.FC<Props> = ({ field, isInvalid }) => {
return (
<FormControl variant="floating" id="tag" isRequired>
<Input
{ ...field }
placeholder=" "
isInvalid={ isInvalid }
maxLength={ TAG_MAX_LENGTH }
/>
<FormLabel>Private tag (max 35 characters)</FormLabel>
</FormControl>
)
}
export default TagInput;
...@@ -13,7 +13,6 @@ import { ...@@ -13,7 +13,6 @@ import {
type Props = { type Props = {
isOpen: boolean; isOpen: boolean;
onClose: () => void; onClose: () => void;
getDisclosureProps: () => any;
address?: string; address?: string;
} }
......
...@@ -2773,6 +2773,11 @@ react-focus-lock@^2.9.1: ...@@ -2773,6 +2773,11 @@ react-focus-lock@^2.9.1:
use-callback-ref "^1.3.0" use-callback-ref "^1.3.0"
use-sidecar "^1.1.2" use-sidecar "^1.1.2"
react-hook-form@^7.33.1:
version "7.33.1"
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.33.1.tgz#8c4410e3420788d3b804d62cc4c142915c2e46d0"
integrity sha512-ydTfTxEJdvgjCZBj5DDXRc58oTEfnFupEwwTAQ9FSKzykEJkX+3CiAkGtAMiZG7IPWHuzgT6AOBfogiKhUvKgg==
react-is@^16.13.1, react-is@^16.7.0: react-is@^16.13.1, react-is@^16.7.0:
version "16.13.1" version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
......
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