cleanup forms

This commit is contained in:
Ildar Kamalov
2025-01-21 16:52:12 +03:00
parent 70aeee3037
commit 254b25a026
13 changed files with 143 additions and 143 deletions

View File

@@ -1,12 +1,14 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { useForm } from 'react-hook-form';
import { Controller, useForm } from 'react-hook-form';
import Card from '../../ui/Card';
import Info from './Info';
import { RootState } from '../../../initialState';
import { validateRequiredValue } from '../../../helpers/validators';
import { Input } from '../../ui/Controls/Input';
interface FormValues {
name: string;
@@ -14,7 +16,7 @@ interface FormValues {
type Props = {
onSubmit?: (data: FormValues) => void;
}
};
const Check = ({ onSubmit }: Props) => {
const { t } = useTranslation();
@@ -23,7 +25,7 @@ const Check = ({ onSubmit }: Props) => {
const hostname = useSelector((state: RootState) => state.filtering.check.hostname);
const {
register,
control,
handleSubmit,
formState: { isDirty, isValid },
} = useForm<FormValues>({
@@ -38,24 +40,29 @@ const Check = ({ onSubmit }: Props) => {
<form onSubmit={handleSubmit(onSubmit)}>
<div className="row">
<div className="col-12 col-md-6">
<div className="input-group">
<input
id="name"
type="text"
className="form-control"
placeholder={t('form_enter_host') ?? ''}
{...register('name', { required: t('form_error_required') })}
/>
<span className="input-group-append">
<button
className="btn btn-success btn-standard btn-large"
type="submit"
disabled={!isDirty || !isValid || processingCheck}
>
{t('check')}
</button>
</span>
</div>
<Controller
name="name"
control={control}
rules={{ validate: validateRequiredValue }}
render={({ field, fieldState }) => (
<Input
{...field}
type="text"
placeholder={t('form_enter_host')}
error={fieldState.error?.message}
rightAddon={
<span className="input-group-append">
<button
className="btn btn-success btn-standard btn-large"
type="submit"
disabled={!isDirty || !isValid || processingCheck}>
{t('check')}
</button>
</span>
}
/>
)}
/>
{hostname && (
<>

View File

@@ -6,6 +6,7 @@ import { validatePath, validateRequiredValue } from '../../helpers/validators';
import { MODAL_OPEN_TIMEOUT, MODAL_TYPE } from '../../helpers/constants';
import filtersCatalog from '../../helpers/filters/filters';
import { FiltersList } from './FiltersList';
import { Input } from '../ui/Controls/Input';
type FormValues = {
enabled: boolean;
@@ -15,7 +16,7 @@ type FormValues = {
type Props = {
closeModal: (...args: unknown[]) => void;
onSubmit: (...args: unknown[]) => void;
onSubmit: (values: FormValues) => void;
processingAddFilter: boolean;
processingConfigFilter: boolean;
whitelist?: boolean;
@@ -81,13 +82,7 @@ export const Form = ({
name="name"
control={control}
render={({ field }) => (
<input
{...field}
type="text"
className="form-control"
placeholder={t('enter_name_hint')}
onBlur={(e) => field.onChange(e.target.value.trim())}
/>
<Input {...field} type="text" placeholder={t('enter_name_hint')} trimOnBlur />
)}
/>
</div>
@@ -98,12 +93,11 @@ export const Form = ({
control={control}
rules={{ validate: { validateRequiredValue, validatePath } }}
render={({ field }) => (
<input
<Input
{...field}
type="text"
className="form-control"
placeholder={t('enter_url_or_path_hint')}
onBlur={(e) => field.onChange(e.target.value.trim())}
trimOnBlur
/>
)}
/>

View File

@@ -1,8 +1,9 @@
import React from 'react';
import { useForm } from 'react-hook-form';
import { Controller, useForm } from 'react-hook-form';
import { Trans, useTranslation } from 'react-i18next';
import { validateAnswer, validateDomain } from '../../../helpers/validators';
import { validateAnswer, validateDomain, validateRequiredValue } from '../../../helpers/validators';
import { Input } from '../../ui/Controls/Input';
interface FormValues {
domain: string;
@@ -11,21 +12,21 @@ interface FormValues {
type Props = {
processingAdd: boolean;
currentRewrite?: { answer: string, domain: string; };
currentRewrite?: { answer: string; domain: string };
toggleRewritesModal: () => void;
onSubmit?: (data: FormValues) => Promise<void> | void;
}
};
const Form = ({ processingAdd, currentRewrite, toggleRewritesModal, onSubmit }: Props) => {
const { t } = useTranslation();
const {
register,
handleSubmit,
reset,
formState: { isDirty, isSubmitting, errors },
control,
formState: { isDirty, isSubmitting },
} = useForm<FormValues>({
mode: 'onChange',
mode: 'onChange',
defaultValues: {
domain: currentRewrite?.domain || '',
answer: currentRewrite?.answer || '',
@@ -45,21 +46,24 @@ const Form = ({ processingAdd, currentRewrite, toggleRewritesModal, onSubmit }:
<Trans>domain_desc</Trans>
</div>
<div className="form__group">
<input
id="domain"
type="text"
className="form-control"
placeholder={t('form_domain')}
{...register('domain', {
validate: validateDomain,
required: t('form_error_required'),
})}
<Controller
name="domain"
control={control}
rules={{
validate: {
validate: validateDomain,
required: validateRequiredValue,
},
}}
render={({ field, fieldState }) => (
<Input
{...field}
type="text"
placeholder={t('form_domain')}
error={fieldState.error?.message}
/>
)}
/>
{errors.domain && (
<div className="form__message form__message--error">
{errors.domain.message}
</div>
)}
</div>
<Trans>examples_title</Trans>:
<ol className="leading-loose">
@@ -74,21 +78,24 @@ const Form = ({ processingAdd, currentRewrite, toggleRewritesModal, onSubmit }:
</li>
</ol>
<div className="form__group">
<input
id="answer"
type="text"
className="form-control"
placeholder={t('form_answer') ?? ''}
{...register('answer', {
validate: validateAnswer,
required: t('form_error_required'),
})}
<Controller
name="answer"
control={control}
rules={{
validate: {
validate: validateAnswer,
required: validateRequiredValue,
},
}}
render={({ field, fieldState }) => (
<Input
{...field}
type="text"
placeholder={t('form_answer')}
error={fieldState.error?.message}
/>
)}
/>
{errors.answer && (
<div className="form__message form__message--error">
{errors.answer.message}
</div>
)}
</div>
</div>
@@ -109,16 +116,14 @@ const Form = ({ processingAdd, currentRewrite, toggleRewritesModal, onSubmit }:
onClick={() => {
reset();
toggleRewritesModal();
}}
>
}}>
<Trans>cancel_btn</Trans>
</button>
<button
type="submit"
className="btn btn-success btn-standard"
disabled={isSubmitting || !isDirty || processingAdd}
>
disabled={isSubmitting || !isDirty || processingAdd}>
<Trans>save_btn</Trans>
</button>
</div>

View File

@@ -19,7 +19,7 @@ type FormValues = {
interface FormProps {
initialValues: Record<string, boolean>;
blockedServices: BlockedService[];
onSubmit: (...args: unknown[]) => void;
onSubmit: (values: FormValues) => void;
processing: boolean;
processingSet: boolean;
}

View File

@@ -2,52 +2,40 @@ import React from 'react';
import cn from 'classnames';
import { FieldValues, ControllerRenderProps } from 'react-hook-form';
interface ServiceFieldProps extends ControllerRenderProps<FieldValues> {
type Props = ControllerRenderProps<FieldValues> & {
placeholder: string;
disabled?: boolean;
className?: string;
icon?: string;
error?: string;
}
};
export const ServiceField = React.forwardRef<HTMLInputElement, ServiceFieldProps>(({
name,
value,
onChange,
onBlur,
placeholder,
disabled,
className,
icon,
error,
}, ref) => (
<>
<label className={cn('service custom-switch', className)}>
<input
name={name}
type="checkbox"
className="custom-switch-input"
checked={!!value}
onChange={onChange}
onBlur={onBlur}
ref={ref}
disabled={disabled}
/>
export const ServiceField = React.forwardRef<HTMLInputElement, Props>(
({ name, value, onChange, onBlur, placeholder, disabled, className, icon, error }, ref) => (
<>
<label className={cn('service custom-switch', className)}>
<input
name={name}
type="checkbox"
className="custom-switch-input"
checked={!!value}
onChange={onChange}
onBlur={onBlur}
ref={ref}
disabled={disabled}
/>
<span className="service__switch custom-switch-indicator"></span>
<span className="service__switch custom-switch-indicator"></span>
<span className="service__text" title={placeholder}>
{placeholder}
</span>
{icon && <div dangerouslySetInnerHTML={{ __html: window.atob(icon) }} className="service__icon" />}
</label>
<span className="service__text" title={placeholder}>
{placeholder}
</span>
{icon && <div dangerouslySetInnerHTML={{ __html: window.atob(icon) }} className="service__icon" />}
</label>
{!disabled && error && (
<span className="form__message form__message--error">
{error}
</span>
)}
</>
));
{!disabled && error && <span className="form__message form__message--error">{error}</span>}
</>
),
);
ServiceField.displayName = 'ServiceField';

View File

@@ -22,12 +22,12 @@ import { SearchField } from './SearchField';
export type FormValues = {
search: string;
response_status: string;
}
};
type Props = {
initialValues: FormValues;
className?: string;
setIsLoading: (...args: unknown[]) => unknown;
setIsLoading: (value: boolean) => void;
};
export const Form = ({ initialValues, className, setIsLoading }: Props) => {
@@ -35,11 +35,7 @@ export const Form = ({ initialValues, className, setIsLoading }: Props) => {
const dispatch = useDispatch();
const history = useHistory();
const {
register,
watch,
setValue,
} = useForm<FormValues>({
const { register, watch, setValue } = useForm<FormValues>({
mode: 'onChange',
defaultValues: {
search: initialValues.search || DEFAULT_LOGS_FILTER.search,
@@ -50,10 +46,7 @@ export const Form = ({ initialValues, className, setIsLoading }: Props) => {
const searchValue = watch('search');
const responseStatusValue = watch('response_status');
const [debouncedSearch, setDebouncedSearch] = useDebounce(
searchValue.trim(),
DEBOUNCE_FILTER_TIMEOUT
);
const [debouncedSearch, setDebouncedSearch] = useDebounce(searchValue.trim(), DEBOUNCE_FILTER_TIMEOUT);
useEffect(() => {
dispatch(
@@ -91,7 +84,7 @@ export const Form = ({ initialValues, className, setIsLoading }: Props) => {
value: e.target.value,
onChange: (v: string) => setValue('search', v),
},
(data: string) => data.trim()
(data: string) => data.trim(),
);
return (
@@ -99,8 +92,7 @@ export const Form = ({ initialValues, className, setIsLoading }: Props) => {
className="d-flex flex-wrap form-control--container"
onSubmit={(e) => {
e.preventDefault();
}}
>
}}>
<div className="field__search">
<SearchField
value={searchValue}
@@ -117,8 +109,7 @@ export const Form = ({ initialValues, className, setIsLoading }: Props) => {
<div className="field__select">
<select
{...register('response_status')}
className="form-control custom-select custom-select--logs custom-select__arrow--left form-control--transparent d-sm-block"
>
className="form-control custom-select custom-select--logs custom-select__arrow--left form-control--transparent d-sm-block">
{Object.values(RESPONSE_FILTER).map(({ QUERY, LABEL, disabled }: any) => (
<option key={LABEL} value={QUERY} disabled={disabled}>
{t(LABEL)}

View File

@@ -111,6 +111,12 @@ const ClientsTable = ({
config.tags = [];
}
if (values.ids) {
config.ids = values.ids.map((id) => id.name);
} else {
config.ids = [];
}
if (typeof values.upstreams_cache_size === 'string') {
config.upstreams_cache_size = 0;
}

View File

@@ -36,7 +36,7 @@ const defaultFormValues: ClientForm = {
};
type Props = {
onSubmit: (...args: unknown[]) => void;
onSubmit: (values: ClientForm) => void;
onClose: () => void;
useGlobalSettings?: boolean;
useGlobalServices?: boolean;
@@ -45,7 +45,7 @@ type Props = {
};
processingAdding: boolean;
processingUpdating: boolean;
tagsOptions: unknown[];
tagsOptions: { label: string; value: string }[];
initialValues?: ClientForm;
};
@@ -79,15 +79,6 @@ export const Form = ({
const safeSearchServices = { ...safe_search };
delete safeSearchServices.enabled;
const onFormSubmit = (values: ClientForm) => {
const data = {
...values,
ids: values.ids.map((idObj) => idObj.name),
};
onSubmit(data);
};
const [activeTabLabel, setActiveTabLabel] = useState('settings');
const tabs = {
@@ -113,7 +104,7 @@ export const Form = ({
return (
<FormProvider {...methods}>
<form onSubmit={handleSubmit(onFormSubmit)}>
<form onSubmit={handleSubmit(onSubmit)}>
<div className="modal-body">
<div className="form__group mb-0">
<div className="form__group">

View File

@@ -21,3 +21,8 @@ export type ClientForm = {
ignore_querylog: boolean;
ignore_statistics: boolean;
};
export type SubmitClientForm = Omit<ClientForm, 'ids' | 'tags'> & {
ids: string[];
tags: string[];
};

View File

@@ -52,7 +52,7 @@ interface ModalProps {
handleClose: (...args: unknown[]) => unknown;
processingAdding: boolean;
processingUpdating: boolean;
tagsOptions: unknown[];
tagsOptions: { label: string; value: string }[];
t: (...args: unknown[]) => string;
clientId?: string;
}

View File

@@ -2,7 +2,6 @@ import React, { useMemo } from 'react';
import { useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { isValid } from 'date-fns';
import { UINT32_RANGE } from '../../../helpers/constants';
import {
validateGatewaySubnetMask,

View File

@@ -93,7 +93,7 @@ export const FiltersConfig = ({ initialValues, setFiltersConfig, processing }: P
disabled={processing}>
{FILTERS_INTERVALS_HOURS.map((interval) => (
<option value={interval} key={interval}>
{getTitleForInterval(interval, t)}
{getTitleForInterval(interval)}
</option>
))}
</select>

View File

@@ -6,10 +6,11 @@ type Props = ComponentProps<'input'> & {
leftAddon?: ReactNode;
rightAddon?: ReactNode;
error?: string;
trimOnBlur?: boolean;
};
export const Input = forwardRef<HTMLInputElement, Props>(
({ name, label, className, leftAddon, rightAddon, error, ...rest }, ref) => (
({ name, label, className, leftAddon, rightAddon, error, trimOnBlur, onBlur, ...rest }, ref) => (
<div className={clsx('form-group', { 'has-error': !!error })}>
{label && (
<label className="form__label" htmlFor={name}>
@@ -18,7 +19,20 @@ export const Input = forwardRef<HTMLInputElement, Props>(
)}
<div className="input-group">
{leftAddon && <div>{leftAddon}</div>}
<input className={clsx('form-control', { 'is-invalid': !!error }, className)} ref={ref} {...rest} />
<input
className={clsx('form-control', { 'is-invalid': !!error }, className)}
ref={ref}
onBlur={(e) => {
if (trimOnBlur) {
e.target.value = e.target.value.trim();
rest.onChange(e);
}
if (onBlur) {
onBlur(e);
}
}}
{...rest}
/>
{rightAddon && <div>{rightAddon}</div>}
</div>
{error && <div className="form__message form__message--error mt-1">{error}</div>}