cleanup forms
This commit is contained in:
@@ -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 && (
|
||||
<>
|
||||
|
||||
@@ -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
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -21,3 +21,8 @@ export type ClientForm = {
|
||||
ignore_querylog: boolean;
|
||||
ignore_statistics: boolean;
|
||||
};
|
||||
|
||||
export type SubmitClientForm = Omit<ClientForm, 'ids' | 'tags'> & {
|
||||
ids: string[];
|
||||
tags: string[];
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>}
|
||||
|
||||
Reference in New Issue
Block a user