cleanup forms

This commit is contained in:
Ildar Kamalov
2025-01-21 16:14:20 +03:00
parent edd9bf7b59
commit 70aeee3037
8 changed files with 125 additions and 139 deletions

View File

@@ -186,9 +186,7 @@ export const Form = ({
<div className="form__desc mt-0"> <div className="form__desc mt-0">
<Trans <Trans
components={[ components={[
<a href={CLIENT_ID_LINK} target="_blank" rel="noopener noreferrer" key="0"> <a href={CLIENT_ID_LINK} target="_blank" rel="noopener noreferrer" key="0" />,
text
</a>,
]}> ]}>
client_identifier_desc client_identifier_desc
</Trans> </Trans>

View File

@@ -1,7 +1,8 @@
import React from 'react'; import React, { useMemo } from 'react';
import { useFormContext } from 'react-hook-form'; import { useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { isValid } from 'date-fns';
import { UINT32_RANGE } from '../../../helpers/constants'; import { UINT32_RANGE } from '../../../helpers/constants';
import { import {
validateGatewaySubnetMask, validateGatewaySubnetMask,
@@ -32,7 +33,7 @@ const FormDHCPv4 = ({ processingConfig, ipv4placeholders, interfaces, onSubmit }
const { const {
register, register,
handleSubmit, handleSubmit,
formState: { errors, isSubmitting }, formState: { errors, isSubmitting, isValid },
watch, watch,
} = useFormContext<DhcpFormValues>(); } = useFormContext<DhcpFormValues>();
@@ -49,6 +50,10 @@ const FormDHCPv4 = ({ processingConfig, ipv4placeholders, interfaces, onSubmit }
} }
}; };
const isDisabled = useMemo(() => {
return isSubmitting || !isValid || processingConfig || !isInterfaceIncludesIpv4 || isEmptyConfig;
}, [isSubmitting, isValid, processingConfig, isInterfaceIncludesIpv4, isEmptyConfig]);
return ( return (
<form onSubmit={handleSubmit(handleFormSubmit)}> <form onSubmit={handleSubmit(handleFormSubmit)}>
<div className="row"> <div className="row">
@@ -169,16 +174,7 @@ const FormDHCPv4 = ({ processingConfig, ipv4placeholders, interfaces, onSubmit }
</div> </div>
<div className="btn-list"> <div className="btn-list">
<button <button type="submit" className="btn btn-success btn-standard" disabled={isDisabled}>
type="submit"
className="btn btn-success btn-standard"
disabled={
isSubmitting ||
processingConfig ||
!isInterfaceIncludesIpv4 ||
isEmptyConfig ||
Object.keys(errors).length > 0
}>
{t('save_config')} {t('save_config')}
</button> </button>
</div> </div>

View File

@@ -1,4 +1,4 @@
import React from 'react'; import React, { useMemo } from 'react';
import { useFormContext } from 'react-hook-form'; import { useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@@ -30,7 +30,7 @@ const FormDHCPv6 = ({ processingConfig, ipv6placeholders, interfaces, onSubmit }
const { const {
register, register,
handleSubmit, handleSubmit,
formState: { errors, isSubmitting }, formState: { errors, isSubmitting, isValid },
watch, watch,
} = useFormContext<DhcpFormValues>(); } = useFormContext<DhcpFormValues>();
@@ -46,6 +46,10 @@ const FormDHCPv6 = ({ processingConfig, ipv6placeholders, interfaces, onSubmit }
} }
}; };
const isDisabled = useMemo(() => {
return isSubmitting || !isValid || processingConfig || !isInterfaceIncludesIpv6 || isEmptyConfig;
}, [isSubmitting, isValid, processingConfig, isInterfaceIncludesIpv6, isEmptyConfig]);
return ( return (
<form onSubmit={handleSubmit(handleFormSubmit)}> <form onSubmit={handleSubmit(handleFormSubmit)}>
<div className="row"> <div className="row">
@@ -126,16 +130,7 @@ const FormDHCPv6 = ({ processingConfig, ipv6placeholders, interfaces, onSubmit }
</div> </div>
<div className="btn-list"> <div className="btn-list">
<button <button type="submit" className="btn btn-success btn-standard" disabled={isDisabled}>
type="submit"
className="btn btn-success btn-standard"
disabled={
isSubmitting ||
processingConfig ||
!isInterfaceIncludesIpv6 ||
isEmptyConfig ||
Object.keys(errors).length > 0
}>
{t('save_config')} {t('save_config')}
</button> </button>
</div> </div>

View File

@@ -4,6 +4,7 @@ import { Trans, useTranslation } from 'react-i18next';
import { CLIENT_ID_LINK } from '../../../../helpers/constants'; import { CLIENT_ID_LINK } from '../../../../helpers/constants';
import { removeEmptyLines, trimMultilineString } from '../../../../helpers/helpers'; import { removeEmptyLines, trimMultilineString } from '../../../../helpers/helpers';
import { Textarea } from '../../../ui/Controls/Textarea';
const fields = [ const fields = [
{ {
@@ -88,11 +89,7 @@ const Form = ({ initialValues, onSubmit, processingSet }: FormProps) => {
<div className="form__desc form__desc--top"> <div className="form__desc form__desc--top">
<Trans <Trans
components={{ components={{
a: ( a: <a href={CLIENT_ID_LINK} target="_blank" rel="noopener noreferrer" />,
<a href={CLIENT_ID_LINK} target="_blank" rel="noopener noreferrer">
{t('text')}
</a>
),
}}> }}>
{subtitle} {subtitle}
</Trans> </Trans>
@@ -102,14 +99,12 @@ const Form = ({ initialValues, onSubmit, processingSet }: FormProps) => {
name={id} name={id}
control={control} control={control}
render={({ field }) => ( render={({ field }) => (
<textarea <Textarea
{...field} {...field}
id={id} id={id}
className="form-control form-control--textarea font-monospace"
disabled={disabled || processingSet} disabled={disabled || processingSet}
onBlur={(e) => { onBlur={(e) => {
const normalized = normalizeOnBlur(e.target.value); field.onChange(normalizeOnBlur(e.target.value));
field.onChange(normalized);
}} }}
/> />
)} )}
@@ -120,7 +115,16 @@ const Form = ({ initialValues, onSubmit, processingSet }: FormProps) => {
return ( return (
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
{fields.map((f) => renderField(f as { id: keyof FormData; title: string; subtitle: string; normalizeOnBlur: (value: string) => string; }))} {fields.map((f) =>
renderField(
f as {
id: keyof FormData;
title: string;
subtitle: string;
normalizeOnBlur: (value: string) => string;
},
),
)}
<div className="card-actions"> <div className="card-actions">
<div className="btn-list"> <div className="btn-list">

View File

@@ -2,19 +2,24 @@ import React, { useEffect, useRef } from 'react';
import { Controller, useForm } from 'react-hook-form'; import { Controller, useForm } from 'react-hook-form';
import { Trans, useTranslation } from 'react-i18next'; import { Trans, useTranslation } from 'react-i18next';
import i18next from 'i18next';
import { toNumber } from '../../../helpers/form'; import { toNumber } from '../../../helpers/form';
import { FILTERS_INTERVALS_HOURS, FILTERS_RELATIVE_LINK } from '../../../helpers/constants'; import { DAY, FILTERS_INTERVALS_HOURS, FILTERS_RELATIVE_LINK } from '../../../helpers/constants';
import { Checkbox } from '../../ui/Controls/Checkbox'; import { Checkbox } from '../../ui/Controls/Checkbox';
const getTitleForInterval = (interval: any, t: any) => { const THREE_DAYS_INTERVAL = DAY * 3;
const SEVEN_DAYS_INTERVAL = DAY * 7;
const getTitleForInterval = (interval: number) => {
if (interval === 0) { if (interval === 0) {
return t('disabled'); return i18next.t('disabled');
}
if (interval === 72 || interval === 168) {
return t('interval_days', { count: interval / 24 });
} }
return t('interval_hours', { count: interval }); if (interval === THREE_DAYS_INTERVAL || interval === SEVEN_DAYS_INTERVAL) {
return i18next.t('interval_days', { count: interval / DAY });
}
return i18next.t('interval_hours', { count: interval });
}; };
export type FormValues = { export type FormValues = {

View File

@@ -4,16 +4,12 @@ import i18next from 'i18next';
import { Controller, useForm } from 'react-hook-form'; import { Controller, useForm } from 'react-hook-form';
import { trimLinesAndRemoveEmpty } from '../../../helpers/helpers'; import { trimLinesAndRemoveEmpty } from '../../../helpers/helpers';
import { import { QUERY_LOG_INTERVALS_DAYS, HOUR, DAY, RETENTION_CUSTOM, RETENTION_RANGE } from '../../../helpers/constants';
QUERY_LOG_INTERVALS_DAYS,
HOUR,
DAY,
RETENTION_CUSTOM,
RETENTION_RANGE,
CUSTOM_INTERVAL,
} from '../../../helpers/constants';
import '../FormButton.css'; import '../FormButton.css';
import { Checkbox } from '../../ui/Controls/Checkbox'; import { Checkbox } from '../../ui/Controls/Checkbox';
import { Input } from '../../ui/Controls/Input';
import { toNumber } from '../../../helpers/form';
import { Textarea } from '../../ui/Controls/Textarea';
const getIntervalTitle = (interval: number) => { const getIntervalTitle = (interval: number) => {
switch (interval) { switch (interval) {
@@ -48,7 +44,6 @@ export const Form = ({ initialValues, processing, processingReset, onSubmit, onR
const { t } = useTranslation(); const { t } = useTranslation();
const { const {
register,
handleSubmit, handleSubmit,
watch, watch,
setValue, setValue,
@@ -135,17 +130,23 @@ export const Form = ({ initialValues, processing, processingReset, onSubmit, onR
<div className="form__group--input"> <div className="form__group--input">
<div className="form__desc form__desc--top">{i18next.t('custom_rotation_input')}</div> <div className="form__desc form__desc--top">{i18next.t('custom_rotation_input')}</div>
<input <Controller
type="number" name="customInterval"
className="form-control" control={control}
name={CUSTOM_INTERVAL} render={({ field, fieldState }) => (
min={RETENTION_RANGE.MIN} <Input
max={RETENTION_RANGE.MAX} {...field}
disabled={processing} placeholder={t('encryption_certificates_input')}
{...register('customInterval')} disabled={processing}
onChange={(e) => { error={fieldState.error?.message}
setValue('customInterval', parseInt(e.target.value, 10)); min={RETENTION_RANGE.MIN}
}} max={RETENTION_RANGE.MAX}
onChange={(e) => {
const { value } = e.target;
field.onChange(toNumber(value));
}}
/>
)}
/> />
</div> </div>
)} )}
@@ -178,12 +179,19 @@ export const Form = ({ initialValues, processing, processingReset, onSubmit, onR
</div> </div>
<div className="form__group form__group--settings"> <div className="form__group form__group--settings">
<textarea <Controller
className="form-control form-control--textarea font-monospace text-input" name="ignored"
placeholder={i18next.t('ignore_domains')} control={control}
disabled={processing} render={({ field, fieldState }) => (
{...register('ignored')} <Textarea
onBlur={handleIgnoredBlur} {...field}
placeholder={t('ignore_domains')}
className="text-input"
disabled={processing}
error={fieldState.error?.message}
onBlur={handleIgnoredBlur}
/>
)}
/> />
</div> </div>

View File

@@ -3,17 +3,14 @@ import { Trans, useTranslation } from 'react-i18next';
import i18next from 'i18next'; import i18next from 'i18next';
import { Controller, useForm } from 'react-hook-form'; import { Controller, useForm } from 'react-hook-form';
import { import { STATS_INTERVALS_DAYS, DAY, RETENTION_CUSTOM, RETENTION_RANGE } from '../../../helpers/constants';
STATS_INTERVALS_DAYS,
DAY,
RETENTION_CUSTOM,
CUSTOM_INTERVAL,
RETENTION_RANGE,
} from '../../../helpers/constants';
import { trimLinesAndRemoveEmpty } from '../../../helpers/helpers'; import { trimLinesAndRemoveEmpty } from '../../../helpers/helpers';
import '../FormButton.css'; import '../FormButton.css';
import { Checkbox } from '../../ui/Controls/Checkbox'; import { Checkbox } from '../../ui/Controls/Checkbox';
import { Input } from '../../ui/Controls/Input';
import { toNumber } from '../../../helpers/form';
import { Textarea } from '../../ui/Controls/Textarea';
const getIntervalTitle = (interval: any) => { const getIntervalTitle = (interval: any) => {
switch (interval) { switch (interval) {
@@ -33,8 +30,15 @@ export type FormValues = {
ignored: string; ignored: string;
}; };
const defaultFormValues = {
enabled: false,
interval: DAY,
customInterval: null,
ignored: '',
};
type Props = { type Props = {
initialValues: Partial<FormValues>; initialValues: FormValues;
processing: boolean; processing: boolean;
processingReset: boolean; processingReset: boolean;
onSubmit: (values: FormValues) => void; onSubmit: (values: FormValues) => void;
@@ -45,7 +49,6 @@ export const Form = ({ initialValues, processing, processingReset, onSubmit, onR
const { t } = useTranslation(); const { t } = useTranslation();
const { const {
register,
handleSubmit, handleSubmit,
watch, watch,
setValue, setValue,
@@ -54,10 +57,8 @@ export const Form = ({ initialValues, processing, processingReset, onSubmit, onR
} = useForm<FormValues>({ } = useForm<FormValues>({
mode: 'onChange', mode: 'onChange',
defaultValues: { defaultValues: {
enabled: initialValues.enabled || false, ...defaultFormValues,
interval: initialValues.interval || DAY, ...initialValues,
customInterval: initialValues.customInterval || null,
ignored: initialValues.ignored || '',
}, },
}); });
@@ -120,43 +121,27 @@ export const Form = ({ initialValues, processing, processingReset, onSubmit, onR
<div className="form__group--input"> <div className="form__group--input">
<div className="form__desc form__desc--top">{i18next.t('custom_retention_input')}</div> <div className="form__desc form__desc--top">{i18next.t('custom_retention_input')}</div>
{/* <Field <Controller
key={RETENTION_CUSTOM_INPUT} name="customInterval"
name={CUSTOM_INTERVAL} control={control}
type="number" render={({ field, fieldState }) => (
className="form-control" <Input
component={renderInputField} {...field}
disabled={processing} placeholder={t('encryption_certificates_input')}
normalize={toFloatNumber} disabled={processing}
min={RETENTION_RANGE.MIN} error={fieldState.error?.message}
max={RETENTION_RANGE.MAX} min={RETENTION_RANGE.MIN}
/> */} max={RETENTION_RANGE.MAX}
onChange={(e) => {
<input const { value } = e.target;
name={CUSTOM_INTERVAL} field.onChange(toNumber(value));
type="number" }}
className="form-control" />
min={RETENTION_RANGE.MIN} )}
max={RETENTION_RANGE.MAX}
disabled={processing}
{...register('customInterval')}
onChange={(e) => {
setValue('customInterval', parseInt(e.target.value, 10));
}}
/> />
</div> </div>
)} )}
{STATS_INTERVALS_DAYS.map((interval) => ( {STATS_INTERVALS_DAYS.map((interval) => (
// <Field
// key={interval}
// name="interval"
// type="radio"
// component={renderRadioField}
// value={interval}
// placeholder={getIntervalTitle(interval, t)}
// normalize={toNumber}
// disabled={processing}
// />
<label key={interval} className="custom-control custom-radio"> <label key={interval} className="custom-control custom-radio">
<input <input
type="radio" type="radio"
@@ -184,21 +169,19 @@ export const Form = ({ initialValues, processing, processingReset, onSubmit, onR
</div> </div>
<div className="form__group form__group--settings"> <div className="form__group form__group--settings">
{/* <Field <Controller
name="ignored" name="ignored"
type="textarea" control={control}
className="form-control form-control--textarea font-monospace text-input" render={({ field, fieldState }) => (
component={renderTextareaField} <Textarea
placeholder={t('ignore_domains')} {...field}
disabled={processing} placeholder={t('ignore_domains')}
normalizeOnBlur={trimLinesAndRemoveEmpty} className="text-input"
/> */} disabled={processing}
<textarea error={fieldState.error?.message}
className="form-control form-control--textarea font-monospace text-input" onBlur={handleIgnoredBlur}
placeholder={i18next.t('ignore_domains')} />
disabled={processing} )}
{...register('ignored')}
onBlur={handleIgnoredBlur}
/> />
</div> </div>

View File

@@ -40,12 +40,6 @@ const getDownloadLink = (host: string, clientId: string, protocol: string, inval
); );
}; };
const githubLink = (
<a href={CLIENT_ID_LINK} target="_blank" rel="noopener noreferrer">
text
</a>
);
type FormValues = { type FormValues = {
host: string; host: string;
clientId: string; clientId: string;
@@ -150,7 +144,10 @@ export const MobileConfigForm = ({ initialValues }: Props) => {
</label> </label>
<div className="form__desc form__desc--top"> <div className="form__desc form__desc--top">
<Trans components={{ a: githubLink }}>client_id_desc</Trans> <Trans
components={{ a: <a href={CLIENT_ID_LINK} target="_blank" rel="noopener noreferrer" /> }}>
client_id_desc
</Trans>
</div> </div>
<Controller <Controller