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 {
useColorModeValue,
} from '@chakra-ui/react';
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 { useForm, Controller } from 'react-hook-form';
......@@ -23,19 +23,19 @@ type Inputs = {
name: string;
}
// idk, maybe there is no limit
const NAME_MAX_LENGTH = 100;
const NAME_MAX_LENGTH = 255;
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 formBackgroundColor = useColorModeValue('white', 'gray.900');
useEffect(() => {
setValue('token', data?.api_key || '');
setValue('name', data?.name || '');
}, [ setValue, data ]);
const updateApiKey = (data: Inputs) => {
const body = JSON.stringify({ name: data.name });
......
......@@ -4,16 +4,16 @@ import {
useColorModeValue,
} from '@chakra-ui/react';
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 { useForm, Controller } from 'react-hook-form';
import type { AddressTag } from 'types/api/account';
import { ADDRESS_REGEXP } from 'lib/addressValidations';
import AddressInput from 'ui/shared/AddressInput';
import TagInput from 'ui/shared/TagInput';
const ADDRESS_LENGTH = 42;
const TAG_MAX_LENGTH = 35;
type Props = {
......@@ -28,13 +28,15 @@ type Inputs = {
const AddressForm: React.FC<Props> = ({ data, onClose }) => {
const [ pending, setPending ] = useState(false);
const { control, handleSubmit, formState: { errors }, setValue } = useForm<Inputs>();
const formBackgroundColor = useColorModeValue('white', 'gray.900');
const { control, handleSubmit, formState: { errors } } = useForm<Inputs>({
mode: 'all',
defaultValues: {
address: data?.address_hash || '',
tag: data?.name || '',
},
});
useEffect(() => {
setValue('address', data?.address_hash || '');
setValue('tag', data?.name || '');
}, [ setValue, data ]);
const formBackgroundColor = useColorModeValue('white', 'gray.900');
const queryClient = useQueryClient();
......@@ -83,8 +85,7 @@ const AddressForm: React.FC<Props> = ({ data, onClose }) => {
name="address"
control={ control }
rules={{
maxLength: ADDRESS_LENGTH,
minLength: ADDRESS_LENGTH,
pattern: ADDRESS_REGEXP,
}}
render={ renderAddressInput }
/>
......
......@@ -4,16 +4,16 @@ import {
useColorModeValue,
} from '@chakra-ui/react';
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 { useForm, Controller } from 'react-hook-form';
import type { TransactionTag } from 'types/api/account';
import { TRANSACTION_HASH_LENGTH, TRANSACTION_HASH_REGEXP } from 'lib/transactionValidations';
import TagInput from 'ui/shared/TagInput';
import TransactionInput from 'ui/shared/TransactionInput';
const HASH_LENGTH = 66;
const TAG_MAX_LENGTH = 35;
type Props = {
......@@ -28,13 +28,15 @@ type Inputs = {
const TransactionForm: React.FC<Props> = ({ data, onClose }) => {
const [ pending, setPending ] = useState(false);
const { control, handleSubmit, formState: { errors }, setValue } = useForm<Inputs>();
const formBackgroundColor = useColorModeValue('white', 'gray.900');
useEffect(() => {
setValue('transaction', data?.transaction_hash || '');
setValue('tag', data?.name || '');
}, [ setValue, data ]);
const { control, handleSubmit, formState: { errors } } = useForm<Inputs>({
mode: 'all',
defaultValues: {
transaction: data?.transaction_hash || '',
tag: data?.name || '',
},
});
const queryClient = useQueryClient();
......@@ -84,8 +86,9 @@ const TransactionForm: React.FC<Props> = ({ data, onClose }) => {
name="transaction"
control={ control }
rules={{
maxLength: HASH_LENGTH,
minLength: HASH_LENGTH,
maxLength: TRANSACTION_HASH_LENGTH,
minLength: TRANSACTION_HASH_LENGTH,
pattern: TRANSACTION_HASH_REGEXP,
}}
render={ renderTransactionInput }
/>
......
......@@ -5,6 +5,7 @@ import { Controller } from 'react-hook-form';
import MinusIcon from 'icons/minus.svg';
import PlusIcon from 'icons/plus.svg';
import { ADDRESS_REGEXP } from 'lib/addressValidations';
import AddressInput from 'ui/shared/AddressInput';
import type { Inputs } from './PublicTagsForm';
......@@ -38,6 +39,7 @@ export default function PublicTagFormAction({ control, index, fieldsLength, hasE
name={ `addresses.${ index }.address` }
control={ control }
render={ renderAddressInput }
rules={{ pattern: ADDRESS_REGEXP }}
/>
{ index === fieldsLength - 1 && fieldsLength < MAX_INPUTS_NUM && (
<IconButton
......
......@@ -5,6 +5,8 @@ import { Controller } from 'react-hook-form';
import type { Inputs } from './PublicTagsForm';
const TEXT_INPUT_MAX_LENGTH = 255;
interface Props {
control: Control<Inputs>;
}
......@@ -27,6 +29,7 @@ export default function PublicTagFormComment({ control }: Props) {
name="comment"
control={ control }
render={ renderComment }
rules={{ maxLength: TEXT_INPUT_MAX_LENGTH }}
/>
);
}
......@@ -50,15 +50,16 @@ 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('; '),
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,
comment: data?.comment || '',
},
mode: 'all',
});
const { fields, append, remove } = useFieldArray({
......@@ -78,16 +79,36 @@ const PublicTagsForm = ({ changeToDataScreen, data }: Props) => {
<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/>
<PublicTagsFormInput<Inputs>
fieldName="userName"
control={ control }
label={ placeholders.userName }
required
/>
</GridItem>
<GridItem>
<PublicTagsFormInput<Inputs> fieldName="companyName" control={ control } label={ placeholders.companyName }/>
<PublicTagsFormInput<Inputs>
fieldName="companyName"
control={ control }
label={ placeholders.companyName }
/>
</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>
<PublicTagsFormInput<Inputs> fieldName="companyUrl" control={ control } label={ placeholders.companyUrl }/>
<PublicTagsFormInput<Inputs>
fieldName="companyUrl"
control={ control }
label={ placeholders.companyUrl }
/>
</GridItem>
</Grid>
<Box marginTop={ 4 } marginBottom={ 8 }>
......@@ -95,14 +116,19 @@ const PublicTagsForm = ({ changeToDataScreen, data }: Props) => {
</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/>
<PublicTagsFormInput<Inputs>
fieldName="tag"
control={ control }
label={ placeholders.tag }
hasError={ Boolean(errors.tag) }
required/>
</Box>
{ fields.map((field, index) => {
return (
<Box position="relative" key={ field.id } marginBottom={ 4 }>
<PublicTagFormAddressInput
control={ control }
hasError={ Boolean(errors.addresses) }
hasError={ Boolean(errors?.addresses?.[index]) }
index={ index }
fieldsLength={ fields.length }
onAddFieldClick={ onAddFieldClick }
......
......@@ -3,14 +3,25 @@ import React, { useCallback } from 'react';
import type { ControllerRenderProps, FieldValues, Path, Control } from 'react-hook-form';
import { Controller } from 'react-hook-form';
const TEXT_INPUT_MAX_LENGTH = 255;
interface Props<TInputs extends FieldValues> {
fieldName: Path<TInputs>;
label: string;
required?: boolean;
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>}) => {
return (
<FormControl variant="floating" id={ field.name } isRequired={ required } size="lg">
......@@ -18,16 +29,19 @@ export default function PublicTagsFormInput<Inputs extends FieldValues>({ label,
{ ...field }
size="lg"
required={ required }
isInvalid={ hasError }
maxLength={ TEXT_INPUT_MAX_LENGTH }
/>
<FormLabel>{ label }</FormLabel>
</FormControl>
);
}, [ label, required ]);
}, [ label, required, hasError ]);
return (
<Controller
name={ fieldName }
control={ control }
render={ renderInput }
rules={{ pattern }}
/>
);
}
......@@ -6,7 +6,7 @@ import {
import React from 'react';
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>> = {
field: ControllerRenderProps<TInputs, TInputName>;
......
......@@ -8,18 +8,20 @@ import {
useColorModeValue,
} from '@chakra-ui/react';
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 { useForm, Controller } from 'react-hook-form';
import type { TWatchlistItem } from 'types/client/account';
import { ADDRESS_REGEXP } from 'lib/addressValidations';
import AddressInput from 'ui/shared/AddressInput';
import TagInput from 'ui/shared/TagInput';
// does it depend on the network?
const NOTIFICATIONS = [ 'native', 'ERC-20', 'ERC-721' ] as const;
const NOTIFICATIONS_NAMES = [ 'xDAI', 'ERC-20', 'ERC-721, ERC-1155 (NFT)' ];
const ADDRESS_LENGTH = 42;
const TAG_MAX_LENGTH = 35;
type Props = {
......@@ -59,9 +61,25 @@ type Checkboxes = 'notification' |
const AddressForm: React.FC<Props> = ({ data, onClose }) => {
const [ pending, setPending ] = useState(false);
const { control, handleSubmit, formState: { errors }, setValue } = useForm<Inputs>();
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 { mutate } = useMutation((formData: Inputs) => {
......@@ -98,15 +116,6 @@ const AddressForm: React.FC<Props> = ({ data, onClose }) => {
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'>}) => {
return <AddressInput<Inputs, 'address'> field={ field } isInvalid={ Boolean(errors.address) } backgroundColor={ formBackgroundColor }/>;
}, [ errors, formBackgroundColor ]);
......@@ -135,8 +144,7 @@ const AddressForm: React.FC<Props> = ({ data, onClose }) => {
name="address"
control={ control }
rules={{
maxLength: ADDRESS_LENGTH,
minLength: ADDRESS_LENGTH,
pattern: ADDRESS_REGEXP,
}}
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