Commit 158081b7 authored by isstuev's avatar isstuev

add some validations and form fixes

parent 17ea4d81
// maybe it depends on the network??
export const ADDRESS_REGEXP = /^0x[a-fA-F\d]{40}$/;
export const ADDRESS_LENGTH = 42;
// maybe it depends on the network??
export const TRANSACTION_HASH_REGEXP = /^0x[a-fA-F\d]{64}$/;
export const TRANSACTION_HASH_LENGTH = 66;
...@@ -7,7 +7,7 @@ import { ...@@ -7,7 +7,7 @@ import {
useColorModeValue, useColorModeValue,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useMutation, useQueryClient } from '@tanstack/react-query';
import React, { useCallback, useEffect } from 'react'; import React, { useCallback } from 'react';
import type { SubmitHandler, ControllerRenderProps } from 'react-hook-form'; import type { SubmitHandler, ControllerRenderProps } from 'react-hook-form';
import { useForm, Controller } from 'react-hook-form'; import { useForm, Controller } from 'react-hook-form';
...@@ -23,19 +23,19 @@ type Inputs = { ...@@ -23,19 +23,19 @@ type Inputs = {
name: string; name: string;
} }
// idk, maybe there is no limit const NAME_MAX_LENGTH = 255;
const NAME_MAX_LENGTH = 100;
const ApiKeyForm: React.FC<Props> = ({ data, onClose }) => { const ApiKeyForm: React.FC<Props> = ({ data, onClose }) => {
const { control, handleSubmit, formState: { errors }, setValue } = useForm<Inputs>(); const { control, handleSubmit, formState: { errors } } = useForm<Inputs>({
mode: 'all',
defaultValues: {
token: data?.api_key || '',
name: data?.name || '',
},
});
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const formBackgroundColor = useColorModeValue('white', 'gray.900'); const formBackgroundColor = useColorModeValue('white', 'gray.900');
useEffect(() => {
setValue('token', data?.api_key || '');
setValue('name', data?.name || '');
}, [ setValue, data ]);
const updateApiKey = (data: Inputs) => { const updateApiKey = (data: Inputs) => {
const body = JSON.stringify({ name: data.name }); const body = JSON.stringify({ name: data.name });
......
...@@ -4,16 +4,16 @@ import { ...@@ -4,16 +4,16 @@ import {
useColorModeValue, useColorModeValue,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useMutation, useQueryClient } from '@tanstack/react-query';
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useState } from 'react';
import type { SubmitHandler, ControllerRenderProps } from 'react-hook-form'; import type { SubmitHandler, ControllerRenderProps } from 'react-hook-form';
import { useForm, Controller } from 'react-hook-form'; import { useForm, Controller } from 'react-hook-form';
import type { AddressTag } from 'types/api/account'; import type { AddressTag } from 'types/api/account';
import { ADDRESS_REGEXP } from 'lib/addressValidations';
import AddressInput from 'ui/shared/AddressInput'; import AddressInput from 'ui/shared/AddressInput';
import TagInput from 'ui/shared/TagInput'; import TagInput from 'ui/shared/TagInput';
const ADDRESS_LENGTH = 42;
const TAG_MAX_LENGTH = 35; const TAG_MAX_LENGTH = 35;
type Props = { type Props = {
...@@ -28,13 +28,15 @@ type Inputs = { ...@@ -28,13 +28,15 @@ type Inputs = {
const AddressForm: React.FC<Props> = ({ data, onClose }) => { const AddressForm: React.FC<Props> = ({ data, onClose }) => {
const [ pending, setPending ] = useState(false); const [ pending, setPending ] = useState(false);
const { control, handleSubmit, formState: { errors }, setValue } = useForm<Inputs>(); const { control, handleSubmit, formState: { errors } } = useForm<Inputs>({
const formBackgroundColor = useColorModeValue('white', 'gray.900'); mode: 'all',
defaultValues: {
address: data?.address_hash || '',
tag: data?.name || '',
},
});
useEffect(() => { const formBackgroundColor = useColorModeValue('white', 'gray.900');
setValue('address', data?.address_hash || '');
setValue('tag', data?.name || '');
}, [ setValue, data ]);
const queryClient = useQueryClient(); const queryClient = useQueryClient();
...@@ -83,8 +85,7 @@ const AddressForm: React.FC<Props> = ({ data, onClose }) => { ...@@ -83,8 +85,7 @@ const AddressForm: React.FC<Props> = ({ data, onClose }) => {
name="address" name="address"
control={ control } control={ control }
rules={{ rules={{
maxLength: ADDRESS_LENGTH, pattern: ADDRESS_REGEXP,
minLength: ADDRESS_LENGTH,
}} }}
render={ renderAddressInput } render={ renderAddressInput }
/> />
......
...@@ -4,16 +4,16 @@ import { ...@@ -4,16 +4,16 @@ import {
useColorModeValue, useColorModeValue,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useMutation, useQueryClient } from '@tanstack/react-query';
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useState } from 'react';
import type { SubmitHandler, ControllerRenderProps } from 'react-hook-form'; import type { SubmitHandler, ControllerRenderProps } from 'react-hook-form';
import { useForm, Controller } from 'react-hook-form'; import { useForm, Controller } from 'react-hook-form';
import type { TransactionTag } from 'types/api/account'; import type { TransactionTag } from 'types/api/account';
import { TRANSACTION_HASH_LENGTH, TRANSACTION_HASH_REGEXP } from 'lib/transactionValidations';
import TagInput from 'ui/shared/TagInput'; import TagInput from 'ui/shared/TagInput';
import TransactionInput from 'ui/shared/TransactionInput'; import TransactionInput from 'ui/shared/TransactionInput';
const HASH_LENGTH = 66;
const TAG_MAX_LENGTH = 35; const TAG_MAX_LENGTH = 35;
type Props = { type Props = {
...@@ -28,13 +28,15 @@ type Inputs = { ...@@ -28,13 +28,15 @@ type Inputs = {
const TransactionForm: React.FC<Props> = ({ data, onClose }) => { const TransactionForm: React.FC<Props> = ({ data, onClose }) => {
const [ pending, setPending ] = useState(false); const [ pending, setPending ] = useState(false);
const { control, handleSubmit, formState: { errors }, setValue } = useForm<Inputs>();
const formBackgroundColor = useColorModeValue('white', 'gray.900'); const formBackgroundColor = useColorModeValue('white', 'gray.900');
useEffect(() => { const { control, handleSubmit, formState: { errors } } = useForm<Inputs>({
setValue('transaction', data?.transaction_hash || ''); mode: 'all',
setValue('tag', data?.name || ''); defaultValues: {
}, [ setValue, data ]); transaction: data?.transaction_hash || '',
tag: data?.name || '',
},
});
const queryClient = useQueryClient(); const queryClient = useQueryClient();
...@@ -84,8 +86,9 @@ const TransactionForm: React.FC<Props> = ({ data, onClose }) => { ...@@ -84,8 +86,9 @@ const TransactionForm: React.FC<Props> = ({ data, onClose }) => {
name="transaction" name="transaction"
control={ control } control={ control }
rules={{ rules={{
maxLength: HASH_LENGTH, maxLength: TRANSACTION_HASH_LENGTH,
minLength: HASH_LENGTH, minLength: TRANSACTION_HASH_LENGTH,
pattern: TRANSACTION_HASH_REGEXP,
}} }}
render={ renderTransactionInput } render={ renderTransactionInput }
/> />
......
...@@ -5,6 +5,7 @@ import { Controller } from 'react-hook-form'; ...@@ -5,6 +5,7 @@ import { Controller } from 'react-hook-form';
import MinusIcon from 'icons/minus.svg'; import MinusIcon from 'icons/minus.svg';
import PlusIcon from 'icons/plus.svg'; import PlusIcon from 'icons/plus.svg';
import { ADDRESS_REGEXP } from 'lib/addressValidations';
import AddressInput from 'ui/shared/AddressInput'; import AddressInput from 'ui/shared/AddressInput';
import type { Inputs } from './PublicTagsForm'; import type { Inputs } from './PublicTagsForm';
...@@ -38,6 +39,7 @@ export default function PublicTagFormAction({ control, index, fieldsLength, hasE ...@@ -38,6 +39,7 @@ export default function PublicTagFormAction({ control, index, fieldsLength, hasE
name={ `addresses.${ index }.address` } name={ `addresses.${ index }.address` }
control={ control } control={ control }
render={ renderAddressInput } render={ renderAddressInput }
rules={{ pattern: ADDRESS_REGEXP }}
/> />
{ index === fieldsLength - 1 && fieldsLength < MAX_INPUTS_NUM && ( { index === fieldsLength - 1 && fieldsLength < MAX_INPUTS_NUM && (
<IconButton <IconButton
......
...@@ -5,6 +5,8 @@ import { Controller } from 'react-hook-form'; ...@@ -5,6 +5,8 @@ import { Controller } from 'react-hook-form';
import type { Inputs } from './PublicTagsForm'; import type { Inputs } from './PublicTagsForm';
const TEXT_INPUT_MAX_LENGTH = 255;
interface Props { interface Props {
control: Control<Inputs>; control: Control<Inputs>;
} }
...@@ -27,6 +29,7 @@ export default function PublicTagFormComment({ control }: Props) { ...@@ -27,6 +29,7 @@ export default function PublicTagFormComment({ control }: Props) {
name="comment" name="comment"
control={ control } control={ control }
render={ renderComment } render={ renderComment }
rules={{ maxLength: TEXT_INPUT_MAX_LENGTH }}
/> />
); );
} }
...@@ -50,15 +50,16 @@ const ADDRESS_INPUT_BUTTONS_WIDTH = 170; ...@@ -50,15 +50,16 @@ const ADDRESS_INPUT_BUTTONS_WIDTH = 170;
const PublicTagsForm = ({ changeToDataScreen, data }: Props) => { const PublicTagsForm = ({ changeToDataScreen, data }: Props) => {
const { control, handleSubmit, formState: { errors } } = useForm<Inputs>({ const { control, handleSubmit, formState: { errors } } = useForm<Inputs>({
defaultValues: { defaultValues: {
userName: data?.userName, userName: data?.userName || '',
userEmail: data?.userEmail, userEmail: data?.userEmail || '',
companyName: data?.companyName, companyName: data?.companyName || '',
companyUrl: data?.companyUrl, companyUrl: data?.companyUrl || '',
tag: data?.tags.map((tag: TPublicTag) => tag.name).join('; '), tag: data?.tags.map((tag: TPublicTag) => tag.name).join('; ') || '',
addresses: data?.addresses.map((adr: TPublicTagAddress, index: number) => ({ name: `address.${ index }.address`, address: adr.address })) || addresses: data?.addresses.map((adr: TPublicTagAddress, index: number) => ({ name: `address.${ index }.address`, address: adr.address })) ||
[ { name: 'address.0.address', address: '' } ], [ { name: 'address.0.address', address: '' } ],
comment: data?.comment, comment: data?.comment || '',
}, },
mode: 'all',
}); });
const { fields, append, remove } = useFieldArray({ const { fields, append, remove } = useFieldArray({
...@@ -78,16 +79,36 @@ const PublicTagsForm = ({ changeToDataScreen, data }: Props) => { ...@@ -78,16 +79,36 @@ const PublicTagsForm = ({ changeToDataScreen, data }: Props) => {
<Text size="sm" variant="secondary" paddingBottom={ 5 }>Company info</Text> <Text size="sm" variant="secondary" paddingBottom={ 5 }>Company info</Text>
<Grid templateColumns="1fr 1fr" rowGap={ 4 } columnGap={ 5 }> <Grid templateColumns="1fr 1fr" rowGap={ 4 } columnGap={ 5 }>
<GridItem> <GridItem>
<PublicTagsFormInput<Inputs> fieldName="userName" control={ control } label={ placeholders.userName } required/> <PublicTagsFormInput<Inputs>
fieldName="userName"
control={ control }
label={ placeholders.userName }
required
/>
</GridItem> </GridItem>
<GridItem> <GridItem>
<PublicTagsFormInput<Inputs> fieldName="companyName" control={ control } label={ placeholders.companyName }/> <PublicTagsFormInput<Inputs>
fieldName="companyName"
control={ control }
label={ placeholders.companyName }
/>
</GridItem> </GridItem>
<GridItem> <GridItem>
<PublicTagsFormInput<Inputs> fieldName="userEmail" control={ control } label={ placeholders.userEmail } required/> <PublicTagsFormInput<Inputs>
fieldName="userEmail"
control={ control }
label={ placeholders.userEmail }
pattern={ /^[\w.%+-]+@[a-zA-Z\d-]+(?:\.[a-zA-Z\d-]+)+$/ }
hasError={ Boolean(errors.userEmail) }
required
/>
</GridItem> </GridItem>
<GridItem> <GridItem>
<PublicTagsFormInput<Inputs> fieldName="companyUrl" control={ control } label={ placeholders.companyUrl }/> <PublicTagsFormInput<Inputs>
fieldName="companyUrl"
control={ control }
label={ placeholders.companyUrl }
/>
</GridItem> </GridItem>
</Grid> </Grid>
<Box marginTop={ 4 } marginBottom={ 8 }> <Box marginTop={ 4 } marginBottom={ 8 }>
...@@ -95,14 +116,19 @@ const PublicTagsForm = ({ changeToDataScreen, data }: Props) => { ...@@ -95,14 +116,19 @@ const PublicTagsForm = ({ changeToDataScreen, data }: Props) => {
</Box> </Box>
<Text size="sm" variant="secondary" marginBottom={ 5 }>Public tags (2 tags maximum, please use &quot;;&quot; as a divider)</Text> <Text size="sm" variant="secondary" marginBottom={ 5 }>Public tags (2 tags maximum, please use &quot;;&quot; as a divider)</Text>
<Box marginBottom={ 4 }> <Box marginBottom={ 4 }>
<PublicTagsFormInput<Inputs> fieldName="tag" control={ control } label={ placeholders.tag } required/> <PublicTagsFormInput<Inputs>
fieldName="tag"
control={ control }
label={ placeholders.tag }
hasError={ Boolean(errors.tag) }
required/>
</Box> </Box>
{ fields.map((field, index) => { { fields.map((field, index) => {
return ( return (
<Box position="relative" key={ field.id } marginBottom={ 4 }> <Box position="relative" key={ field.id } marginBottom={ 4 }>
<PublicTagFormAddressInput <PublicTagFormAddressInput
control={ control } control={ control }
hasError={ Boolean(errors.addresses) } hasError={ Boolean(errors?.addresses?.[index]) }
index={ index } index={ index }
fieldsLength={ fields.length } fieldsLength={ fields.length }
onAddFieldClick={ onAddFieldClick } onAddFieldClick={ onAddFieldClick }
......
...@@ -3,14 +3,25 @@ import React, { useCallback } from 'react'; ...@@ -3,14 +3,25 @@ import React, { useCallback } from 'react';
import type { ControllerRenderProps, FieldValues, Path, Control } from 'react-hook-form'; import type { ControllerRenderProps, FieldValues, Path, Control } from 'react-hook-form';
import { Controller } from 'react-hook-form'; import { Controller } from 'react-hook-form';
const TEXT_INPUT_MAX_LENGTH = 255;
interface Props<TInputs extends FieldValues> { interface Props<TInputs extends FieldValues> {
fieldName: Path<TInputs>; fieldName: Path<TInputs>;
label: string; label: string;
required?: boolean; required?: boolean;
control: Control<TInputs, object>; control: Control<TInputs, object>;
pattern?: RegExp;
hasError?: boolean;
} }
export default function PublicTagsFormInput<Inputs extends FieldValues>({ label, control, required, fieldName }: Props<Inputs>) { export default function PublicTagsFormInput<Inputs extends FieldValues>({
label,
control,
required,
fieldName,
pattern,
hasError,
}: Props<Inputs>) {
const renderInput = useCallback(({ field }: {field: ControllerRenderProps<Inputs, typeof fieldName>}) => { const renderInput = useCallback(({ field }: {field: ControllerRenderProps<Inputs, typeof fieldName>}) => {
return ( return (
<FormControl variant="floating" id={ field.name } isRequired={ required } size="lg"> <FormControl variant="floating" id={ field.name } isRequired={ required } size="lg">
...@@ -18,16 +29,19 @@ export default function PublicTagsFormInput<Inputs extends FieldValues>({ label, ...@@ -18,16 +29,19 @@ export default function PublicTagsFormInput<Inputs extends FieldValues>({ label,
{ ...field } { ...field }
size="lg" size="lg"
required={ required } required={ required }
isInvalid={ hasError }
maxLength={ TEXT_INPUT_MAX_LENGTH }
/> />
<FormLabel>{ label }</FormLabel> <FormLabel>{ label }</FormLabel>
</FormControl> </FormControl>
); );
}, [ label, required ]); }, [ label, required, hasError ]);
return ( return (
<Controller <Controller
name={ fieldName } name={ fieldName }
control={ control } control={ control }
render={ renderInput } render={ renderInput }
rules={{ pattern }}
/> />
); );
} }
...@@ -6,7 +6,7 @@ import { ...@@ -6,7 +6,7 @@ import {
import React from 'react'; import React from 'react';
import type { ControllerRenderProps, FieldValues, Path } from 'react-hook-form'; import type { ControllerRenderProps, FieldValues, Path } from 'react-hook-form';
const ADDRESS_LENGTH = 42; import { ADDRESS_LENGTH } from 'lib/addressValidations';
type Props<TInputs extends FieldValues, TInputName extends Path<TInputs>> = { type Props<TInputs extends FieldValues, TInputName extends Path<TInputs>> = {
field: ControllerRenderProps<TInputs, TInputName>; field: ControllerRenderProps<TInputs, TInputName>;
......
...@@ -8,18 +8,20 @@ import { ...@@ -8,18 +8,20 @@ import {
useColorModeValue, useColorModeValue,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useMutation, useQueryClient } from '@tanstack/react-query';
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useState } from 'react';
import type { SubmitHandler, ControllerRenderProps } from 'react-hook-form'; import type { SubmitHandler, ControllerRenderProps } from 'react-hook-form';
import { useForm, Controller } from 'react-hook-form'; import { useForm, Controller } from 'react-hook-form';
import type { TWatchlistItem } from 'types/client/account'; import type { TWatchlistItem } from 'types/client/account';
import { ADDRESS_REGEXP } from 'lib/addressValidations';
import AddressInput from 'ui/shared/AddressInput'; import AddressInput from 'ui/shared/AddressInput';
import TagInput from 'ui/shared/TagInput'; import TagInput from 'ui/shared/TagInput';
// does it depend on the network?
const NOTIFICATIONS = [ 'native', 'ERC-20', 'ERC-721' ] as const; const NOTIFICATIONS = [ 'native', 'ERC-20', 'ERC-721' ] as const;
const NOTIFICATIONS_NAMES = [ 'xDAI', 'ERC-20', 'ERC-721, ERC-1155 (NFT)' ]; const NOTIFICATIONS_NAMES = [ 'xDAI', 'ERC-20', 'ERC-721, ERC-1155 (NFT)' ];
const ADDRESS_LENGTH = 42;
const TAG_MAX_LENGTH = 35; const TAG_MAX_LENGTH = 35;
type Props = { type Props = {
...@@ -59,9 +61,25 @@ type Checkboxes = 'notification' | ...@@ -59,9 +61,25 @@ type Checkboxes = 'notification' |
const AddressForm: React.FC<Props> = ({ data, onClose }) => { const AddressForm: React.FC<Props> = ({ data, onClose }) => {
const [ pending, setPending ] = useState(false); const [ pending, setPending ] = useState(false);
const { control, handleSubmit, formState: { errors }, setValue } = useForm<Inputs>();
const formBackgroundColor = useColorModeValue('white', 'gray.900'); const formBackgroundColor = useColorModeValue('white', 'gray.900');
let notificationsDefault = {} as Inputs['notification_settings'];
if (!data) {
NOTIFICATIONS.forEach(n => notificationsDefault[n] = { incoming: true, outcoming: true });
} else {
notificationsDefault = data.notification_settings;
}
const { control, handleSubmit, formState: { errors } } = useForm<Inputs>({
defaultValues: {
address: data?.address_hash || '',
tag: data?.name || '',
notification: data ? data.notification_methods.email : true,
notification_settings: notificationsDefault,
},
mode: 'all',
});
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { mutate } = useMutation((formData: Inputs) => { const { mutate } = useMutation((formData: Inputs) => {
...@@ -98,15 +116,6 @@ const AddressForm: React.FC<Props> = ({ data, onClose }) => { ...@@ -98,15 +116,6 @@ const AddressForm: React.FC<Props> = ({ data, onClose }) => {
mutate(formData); mutate(formData);
}; };
useEffect(() => {
const notificationsDefault = {} as Inputs['notification_settings'];
NOTIFICATIONS.forEach(n => notificationsDefault[n] = { incoming: true, outcoming: true });
setValue('address', data?.address_hash || '');
setValue('tag', data?.name || '');
setValue('notification', data ? data.notification_methods.email : true);
setValue('notification_settings', data ? data.notification_settings : notificationsDefault);
}, [ setValue, data ]);
const renderAddressInput = useCallback(({ field }: {field: ControllerRenderProps<Inputs, 'address'>}) => { const renderAddressInput = useCallback(({ field }: {field: ControllerRenderProps<Inputs, 'address'>}) => {
return <AddressInput<Inputs, 'address'> field={ field } isInvalid={ Boolean(errors.address) } backgroundColor={ formBackgroundColor }/>; return <AddressInput<Inputs, 'address'> field={ field } isInvalid={ Boolean(errors.address) } backgroundColor={ formBackgroundColor }/>;
}, [ errors, formBackgroundColor ]); }, [ errors, formBackgroundColor ]);
...@@ -135,8 +144,7 @@ const AddressForm: React.FC<Props> = ({ data, onClose }) => { ...@@ -135,8 +144,7 @@ const AddressForm: React.FC<Props> = ({ data, onClose }) => {
name="address" name="address"
control={ control } control={ control }
rules={{ rules={{
maxLength: ADDRESS_LENGTH, pattern: ADDRESS_REGEXP,
minLength: ADDRESS_LENGTH,
}} }}
render={ renderAddressInput } render={ renderAddressInput }
/> />
......
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