use common checkbox component

This commit is contained in:
Ildar Kamalov
2025-01-17 16:36:39 +03:00
parent 92c004d15d
commit bcf5fb2521
14 changed files with 365 additions and 394 deletions

14
client/package-lock.json generated vendored
View File

@@ -11,6 +11,7 @@
"@nivo/line": "^0.64.0", "@nivo/line": "^0.64.0",
"axios": "^0.19.2", "axios": "^0.19.2",
"classnames": "^2.5.1", "classnames": "^2.5.1",
"clsx": "^2.1.1",
"countries-and-timezones": "^3.6.0", "countries-and-timezones": "^3.6.0",
"date-fns": "^1.29.0", "date-fns": "^1.29.0",
"i18next": "^19.6.2", "i18next": "^19.6.2",
@@ -6389,6 +6390,14 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
"engines": {
"node": ">=6"
}
},
"node_modules/co": { "node_modules/co": {
"version": "4.6.0", "version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
@@ -24834,6 +24843,11 @@
"shallow-clone": "^3.0.0" "shallow-clone": "^3.0.0"
} }
}, },
"clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="
},
"co": { "co": {
"version": "4.6.0", "version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",

1
client/package.json vendored
View File

@@ -24,6 +24,7 @@
"@nivo/line": "^0.64.0", "@nivo/line": "^0.64.0",
"axios": "^0.19.2", "axios": "^0.19.2",
"classnames": "^2.5.1", "classnames": "^2.5.1",
"clsx": "^2.1.1",
"countries-and-timezones": "^3.6.0", "countries-and-timezones": "^3.6.0",
"date-fns": "^1.29.0", "date-fns": "^1.29.0",
"i18next": "^19.6.2", "i18next": "^19.6.2",

View File

@@ -1,7 +1,8 @@
import React from 'react'; import React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { useFormContext } from 'react-hook-form'; import { Controller, useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Checkbox } from '../ui/Controls/Checkbox';
const getIconsData = (homepage: string, source: string) => [ const getIconsData = (homepage: string, source: string) => [
{ {
@@ -49,7 +50,7 @@ type Props = {
export const FiltersList = ({ categories, filters, selectedSources }: Props) => { export const FiltersList = ({ categories, filters, selectedSources }: Props) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { register } = useFormContext(); const { control } = useFormContext();
return ( return (
<> <>
@@ -69,21 +70,19 @@ export const FiltersList = ({ categories, filters, selectedSources }: Props) =>
return ( return (
<div key={name} className="d-flex align-items-center pb-1"> <div key={name} className="d-flex align-items-center pb-1">
<label className="checkbox checkbox--settings"> <Controller
<span className="checkbox__marker" /> name={id}
<input control={control}
id={id} render={({ field: { value, onChange } }) => (
type="checkbox" <Checkbox
className="checkbox__input" name={id}
disabled={isSelected} title={name}
{...register(id)} value={value}
/> onChange={(value) => onChange(value)}
<span className="checkbox__label"> disabled={isSelected}
<span className="checkbox__label-text"> />
<span className="checkbox__label-title">{t(name)}</span> )}
</span> />
</span>
</label>
{renderIcons(iconsData)} {renderIcons(iconsData)}
</div> </div>
); );

View File

@@ -107,8 +107,6 @@ class Modal extends Component<ModalProps> {
const title = t(getTitle(modalType, whitelist)); const title = t(getTitle(modalType, whitelist));
console.log(modalType, initialValues);
return ( return (
<ReactModal <ReactModal
className="Modal__Bootstrap modal-dialog modal-dialog-centered" className="Modal__Bootstrap modal-dialog modal-dialog-centered"

View File

@@ -1,27 +1,28 @@
import React from 'react'; import React from 'react';
import { useForm } from 'react-hook-form'; import { Controller, useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import i18next from 'i18next';
validateIp, import { validateIp, validateIpv4, validateIpv6, validateRequiredValue } from '../../../../helpers/validators';
validateIpv4,
validateIpv6,
validateRequiredValue
} from '../../../../helpers/validators';
import { BLOCKING_MODES, UINT32_RANGE } from '../../../../helpers/constants'; import { BLOCKING_MODES, UINT32_RANGE } from '../../../../helpers/constants';
import { removeEmptyLines } from '../../../../helpers/helpers'; import { removeEmptyLines } from '../../../../helpers/helpers';
import { Checkbox } from '../../../ui/Controls/Checkbox';
const checkboxes = [ const checkboxes: {
name: 'dnssec_enabled' | 'disable_ipv6';
placeholder: string;
subtitle: string;
}[] = [
{ {
name: 'dnssec_enabled', name: 'dnssec_enabled',
placeholder: 'dnssec_enable', placeholder: i18next.t('dnssec_enable'),
subtitle: 'dnssec_enable_desc', subtitle: i18next.t('dnssec_enable_desc'),
}, },
{ {
name: 'disable_ipv6', name: 'disable_ipv6',
placeholder: 'disable_ipv6', placeholder: i18next.t('disable_ipv6'),
subtitle: 'disable_ipv6_desc', subtitle: i18next.t('disable_ipv6_desc'),
}, },
]; ];
@@ -52,13 +53,13 @@ type FormData = {
blocking_ipv4?: string; blocking_ipv4?: string;
blocking_ipv6?: string; blocking_ipv6?: string;
blocked_response_ttl: number; blocked_response_ttl: number;
} };
type Props = { type Props = {
processing?: boolean; processing?: boolean;
initialValues?: Partial<FormData>; initialValues?: Partial<FormData>;
onSubmit: (data: FormData) => void; onSubmit: (data: FormData) => void;
} };
const Form = ({ processing, initialValues, onSubmit }: Props) => { const Form = ({ processing, initialValues, onSubmit }: Props) => {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -67,6 +68,7 @@ const Form = ({ processing, initialValues, onSubmit }: Props) => {
register, register,
handleSubmit, handleSubmit,
watch, watch,
control,
formState: { errors, isSubmitting, isDirty }, formState: { errors, isSubmitting, isDirty },
} = useForm<FormData>({ } = useForm<FormData>({
mode: 'onChange', mode: 'onChange',
@@ -86,9 +88,7 @@ const Form = ({ processing, initialValues, onSubmit }: Props) => {
{t('rate_limit')} {t('rate_limit')}
</label> </label>
<div className="form__desc form__desc--top"> <div className="form__desc form__desc--top">{t('rate_limit_desc')}</div>
{t('rate_limit_desc')}
</div>
<input <input
id="ratelimit" id="ratelimit"
@@ -103,9 +103,7 @@ const Form = ({ processing, initialValues, onSubmit }: Props) => {
})} })}
/> />
{errors.ratelimit && ( {errors.ratelimit && (
<div className="form__message form__message--error"> <div className="form__message form__message--error">{errors.ratelimit.message}</div>
{errors.ratelimit.message}
</div>
)} )}
</div> </div>
</div> </div>
@@ -116,9 +114,7 @@ const Form = ({ processing, initialValues, onSubmit }: Props) => {
{t('rate_limit_subnet_len_ipv4')} {t('rate_limit_subnet_len_ipv4')}
</label> </label>
<div className="form__desc form__desc--top"> <div className="form__desc form__desc--top">{t('rate_limit_subnet_len_ipv4_desc')}</div>
{t('rate_limit_subnet_len_ipv4_desc')}
</div>
<input <input
id="ratelimit_subnet_len_ipv4" id="ratelimit_subnet_len_ipv4"
@@ -146,9 +142,7 @@ const Form = ({ processing, initialValues, onSubmit }: Props) => {
{t('rate_limit_subnet_len_ipv6')} {t('rate_limit_subnet_len_ipv6')}
</label> </label>
<div className="form__desc form__desc--top"> <div className="form__desc form__desc--top">{t('rate_limit_subnet_len_ipv6_desc')}</div>
{t('rate_limit_subnet_len_ipv6_desc')}
</div>
<input <input
id="ratelimit_subnet_len_ipv6" id="ratelimit_subnet_len_ipv6"
@@ -176,9 +170,7 @@ const Form = ({ processing, initialValues, onSubmit }: Props) => {
{t('rate_limit_whitelist')} {t('rate_limit_whitelist')}
</label> </label>
<div className="form__desc form__desc--top"> <div className="form__desc form__desc--top">{t('rate_limit_whitelist_desc')}</div>
{t('rate_limit_whitelist_desc')}
</div>
<textarea <textarea
id="ratelimit_whitelist" id="ratelimit_whitelist"
@@ -198,41 +190,37 @@ const Form = ({ processing, initialValues, onSubmit }: Props) => {
<div className="col-12"> <div className="col-12">
<div className="form__group form__group--settings"> <div className="form__group form__group--settings">
<label className="checkbox checkbox--settings"> <Controller
<span className="checkbox__marker" /> name="edns_cs_enabled"
<input control={control}
id="edns_cs_enabled" render={({ field: { name, value, onChange } }) => (
type="checkbox" <Checkbox
className="checkbox__input" name={name}
disabled={processing} title={t('edns_enable')}
{...register('edns_cs_enabled')} value={value}
/> onChange={(value) => onChange(value)}
<span className="checkbox__label"> disabled={processing}
<span className="checkbox__label-text"> />
<span className="checkbox__label-title">{t('edns_enable')}</span> )}
</span> />
</span>
</label>
</div> </div>
</div> </div>
<div className="col-12 form__group form__group--inner"> <div className="col-12 form__group form__group--inner">
<div className="form__group"> <div className="form__group">
<label className="checkbox checkbox--settings"> <Controller
<span className="checkbox__marker" /> name="edns_cs_use_custom"
<input control={control}
id="edns_cs_use_custom" render={({ field: { name, value, onChange } }) => (
type="checkbox" <Checkbox
className="checkbox__input" name={name}
disabled={processing || !edns_cs_enabled} title={t('edns_use_custom_ip')}
{...register('edns_cs_use_custom')} value={value}
/> onChange={(value) => onChange(value)}
<span className="checkbox__label"> disabled={processing || !edns_cs_enabled}
<span className="checkbox__label-text"> />
<span className="checkbox__label-subtitle">{t('edns_use_custom_ip')}</span> )}
</span> />
</span>
</label>
</div> </div>
{edns_cs_use_custom && ( {edns_cs_use_custom && (
@@ -252,42 +240,32 @@ const Form = ({ processing, initialValues, onSubmit }: Props) => {
{checkboxes.map(({ name, placeholder, subtitle }) => ( {checkboxes.map(({ name, placeholder, subtitle }) => (
<div className="col-12" key={name}> <div className="col-12" key={name}>
<div className="form__group form__group--settings"> <div className="form__group form__group--settings">
<label className="checkbox checkbox--settings"> <Controller
<span className="checkbox__marker" /> name={name}
<input control={control}
id={name} render={({ field: { name, value, onChange } }) => (
type="checkbox" <Checkbox
className="checkbox__input" name={name}
disabled={processing} title={placeholder}
{...register(name as keyof FormData)} subtitle={subtitle}
/> value={value}
<span className="checkbox__label"> onChange={(value) => onChange(value)}
<span className="checkbox__label-text"> disabled={processing}
<span className="checkbox__label-title">{t(placeholder)}</span> />
{subtitle && ( )}
<span className="checkbox__label-subtitle">{t(subtitle)}</span> />
)}
</span>
</span>
</label>
</div> </div>
</div> </div>
))} ))}
<div className="col-12"> <div className="col-12">
<div className="form__group form__group--settings mb-4"> <div className="form__group form__group--settings mb-4">
<label className="form__label form__label--with-desc"> <label className="form__label form__label--with-desc">{t('blocking_mode')}</label>
{t('blocking_mode')}
</label>
<div className="form__desc form__desc--top"> <div className="form__desc form__desc--top">
{Object.values(BLOCKING_MODES) {Object.values(BLOCKING_MODES).map((mode: any) => (
<li key={mode}>{t(`blocking_mode_${mode}`)}</li>
.map((mode: any) => ( ))}
<li key={mode}>
{t(`blocking_mode_${mode}`)}
</li>
))}
</div> </div>
<div className="custom-controls-stacked"> <div className="custom-controls-stacked">
@@ -315,9 +293,7 @@ const Form = ({ processing, initialValues, onSubmit }: Props) => {
{t(name)} {t(name)}
</label> </label>
<div className="form__desc form__desc--top"> <div className="form__desc form__desc--top">{t(description)}</div>
{t(description)}
</div>
<input <input
id={name} id={name}
@@ -346,9 +322,7 @@ const Form = ({ processing, initialValues, onSubmit }: Props) => {
{t('blocked_response_ttl')} {t('blocked_response_ttl')}
</label> </label>
<div className="form__desc form__desc--top"> <div className="form__desc form__desc--top">{t('blocked_response_ttl_desc')}</div>
{t('blocked_response_ttl_desc')}
</div>
<input <input
id="blocked_response_ttl" id="blocked_response_ttl"

View File

@@ -11,6 +11,7 @@ import { getTextareaCommentsHighlight, syncScroll } from '../../../../helpers/hi
import { RootState } from '../../../../initialState'; import { RootState } from '../../../../initialState';
import '../../../ui/texareaCommentsHighlight.css'; import '../../../ui/texareaCommentsHighlight.css';
import Examples from './Examples'; import Examples from './Examples';
import { Checkbox } from '../../../ui/Controls/Checkbox';
const UPSTREAM_DNS_NAME = 'upstream_dns'; const UPSTREAM_DNS_NAME = 'upstream_dns';
const UPSTREAM_MODE_NAME = 'upstream_mode'; const UPSTREAM_MODE_NAME = 'upstream_mode';
@@ -23,12 +24,12 @@ type FormData = {
local_ptr_upstreams: string; local_ptr_upstreams: string;
use_private_ptr_resolvers: boolean; use_private_ptr_resolvers: boolean;
resolve_clients: boolean; resolve_clients: boolean;
} };
type FormProps = { type FormProps = {
initialValues?: Partial<FormData>; initialValues?: Partial<FormData>;
onSubmit: (data: FormData) => void; onSubmit: (data: FormData) => void;
} };
const INPUT_FIELDS = [ const INPUT_FIELDS = [
{ {
@@ -96,37 +97,29 @@ const Form = ({ initialValues, onSubmit }: FormProps) => {
return ( return (
<form onSubmit={handleSubmit(onSubmit)} className="form--upstream"> <form onSubmit={handleSubmit(onSubmit)} className="form--upstream">
<div className="row"> <div className="row">
<label className="col form__label" htmlFor={UPSTREAM_DNS_NAME}> <label className="col form__label" htmlFor={UPSTREAM_DNS_NAME}>
<Trans <Trans
components={{ components={{
a: <a a: <a href={UPSTREAM_CONFIGURATION_WIKI_LINK} target="_blank" rel="noopener noreferrer" />,
href={UPSTREAM_CONFIGURATION_WIKI_LINK} }}>
target="_blank"
rel="noopener noreferrer"
/>,
}}
>
upstream_dns_help upstream_dns_help
</Trans> </Trans>{' '}
{' '}
<Trans <Trans
components={[ components={[
<a <a
href="https://link.adtidy.org/forward.html?action=dns_kb_providers&from=ui&app=home" href="https://link.adtidy.org/forward.html?action=dns_kb_providers&from=ui&app=home"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
key="0" key="0">
>
DNS providers DNS providers
</a> </a>,
]} ]}>
>
dns_providers dns_providers
</Trans> </Trans>
</label> </label>
<div className="col-12 mb-4"> <div className="col-12 mb-4">
<div className="text-edit-container"> <div className="text-edit-container">
<Controller <Controller
name="upstream_dns" name="upstream_dns"
@@ -188,9 +181,7 @@ const Form = ({ initialValues, onSubmit }: FormProps) => {
{t('fallback_dns_title')} {t('fallback_dns_title')}
</label> </label>
<div className="form__desc form__desc--top"> <div className="form__desc form__desc--top">{t('fallback_dns_desc')}</div>
{t('fallback_dns_desc')}
</div>
<Controller <Controller
name="fallback_dns" name="fallback_dns"
@@ -220,9 +211,7 @@ const Form = ({ initialValues, onSubmit }: FormProps) => {
{t('bootstrap_dns')} {t('bootstrap_dns')}
</label> </label>
<div className="form__desc form__desc--top"> <div className="form__desc form__desc--top">{t('bootstrap_dns_desc')}</div>
{t('bootstrap_dns_desc')}
</div>
<Controller <Controller
name="bootstrap_dns" name="bootstrap_dns"
@@ -252,13 +241,13 @@ const Form = ({ initialValues, onSubmit }: FormProps) => {
{t('local_ptr_title')} {t('local_ptr_title')}
</label> </label>
<div className="form__desc form__desc--top"> <div className="form__desc form__desc--top">{t('local_ptr_desc')}</div>
{t('local_ptr_desc')}
</div>
<div className="form__desc form__desc--top"> <div className="form__desc form__desc--top">
{defaultLocalPtrUpstreams?.length > 0 {defaultLocalPtrUpstreams?.length > 0
? t('local_ptr_default_resolver', { ip: defaultLocalPtrUpstreams.map((s: any) => `"${s}"`).join(', ') }) ? t('local_ptr_default_resolver', {
ip: defaultLocalPtrUpstreams.map((s: any) => `"${s}"`).join(', '),
})
: t('local_ptr_no_default_resolver')} : t('local_ptr_no_default_resolver')}
</div> </div>
@@ -284,28 +273,15 @@ const Form = ({ initialValues, onSubmit }: FormProps) => {
<Controller <Controller
name="use_private_ptr_resolvers" name="use_private_ptr_resolvers"
control={control} control={control}
render={({ field: { value, onChange, ...field } }) => ( render={({ field: { name, value, onChange } }) => (
<label className="checkbox"> <Checkbox
<span className="checkbox__marker" /> name={name}
<input title={t('use_private_ptr_resolvers_title')}
{...field} subtitle={t('use_private_ptr_resolvers_desc')}
type="checkbox" value={value}
checked={value} onChange={(value) => onChange(value)}
onChange={(e) => onChange(e.target.checked)} disabled={processingSetConfig}
className="checkbox__input" />
disabled={processingSetConfig}
/>
<span className="checkbox__label">
<span className="checkbox__label-text checkbox__label-text--long">
<span className="checkbox__label-title">
{t('use_private_ptr_resolvers_title')}
</span>
<span className="checkbox__label-subtitle">
{t('use_private_ptr_resolvers_desc')}
</span>
</span>
</span>
</label>
)} )}
/> />
</div> </div>
@@ -319,24 +295,15 @@ const Form = ({ initialValues, onSubmit }: FormProps) => {
<Controller <Controller
name="resolve_clients" name="resolve_clients"
control={control} control={control}
render={({ field: { value, onChange, ...field } }) => ( render={({ field: { name, value, onChange } }) => (
<label className="checkbox"> <Checkbox
<span className="checkbox__marker" /> name={name}
<input title={t('resolve_clients_title')}
{...field} subtitle={t('resolve_clients_desc')}
type="checkbox" value={value}
checked={value} onChange={(value) => onChange(value)}
onChange={(e) => onChange(e.target.checked)} disabled={processingSetConfig}
className="checkbox__input" />
disabled={processingSetConfig}
/>
<span className="checkbox__label">
<span className="checkbox__label-text checkbox__label-text--long">
<span className="checkbox__label-title">{t('resolve_clients_title')}</span>
<span className="checkbox__label-subtitle">{t('resolve_clients_desc')}</span>
</span>
</span>
</label>
)} )}
/> />
</div> </div>

View File

@@ -1,9 +1,10 @@
import React, { useEffect, useRef } from 'react'; import React, { useEffect, useRef } from 'react';
import { 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 { toNumber } from '../../../helpers/form'; import { toNumber } from '../../../helpers/form';
import { FILTERS_INTERVALS_HOURS, FILTERS_RELATIVE_LINK } from '../../../helpers/constants'; import { FILTERS_INTERVALS_HOURS, FILTERS_RELATIVE_LINK } from '../../../helpers/constants';
import { Checkbox } from '../../ui/Controls/Checkbox';
const getTitleForInterval = (interval: any, t: any) => { const getTitleForInterval = (interval: any, t: any) => {
if (interval === 0) { if (interval === 0) {
@@ -31,7 +32,7 @@ export const FiltersConfig = ({ initialValues, setFiltersConfig, processing }: P
const { t } = useTranslation(); const { t } = useTranslation();
const prevFormValuesRef = useRef<FormValues>(initialValues); const prevFormValuesRef = useRef<FormValues>(initialValues);
const { register, watch } = useForm({ const { register, watch, control } = useForm({
mode: 'onChange', mode: 'onChange',
defaultValues: initialValues, defaultValues: initialValues,
}); });
@@ -56,24 +57,20 @@ export const FiltersConfig = ({ initialValues, setFiltersConfig, processing }: P
<div className="row"> <div className="row">
<div className="col-12"> <div className="col-12">
<div className="form__group form__group--settings"> <div className="form__group form__group--settings">
<label className="checkbox"> <Controller
<span className="checkbox__marker" /> name="enabled"
control={control}
render={({ field: { name, value, onChange } }) => (
<Checkbox
name={name}
title={t('block_domain_use_filters_and_hosts')}
value={value}
onChange={(value) => onChange(value)}
disabled={processing}
/>
)}
/>
<input
type="checkbox"
className="checkbox__input"
{...register('enabled')}
disabled={processing}
/>
<span className="checkbox__label">
<span className="checkbox__label-text checkbox__label-text--long">
<span className="checkbox__label-title">
{t('block_domain_use_filters_and_hosts')}
</span>
</span>
</span>
</label>
<p> <p>
<Trans components={components}>filters_block_toggle_hint</Trans> <Trans components={components}>filters_block_toggle_hint</Trans>
</p> </p>

View File

@@ -1,7 +1,7 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { Trans } from 'react-i18next'; import { Trans, useTranslation } from 'react-i18next';
import i18next from 'i18next'; import i18next from 'i18next';
import { useForm } from 'react-hook-form'; import { Controller, useForm } from 'react-hook-form';
import { trimLinesAndRemoveEmpty } from '../../../helpers/helpers'; import { trimLinesAndRemoveEmpty } from '../../../helpers/helpers';
import { import {
@@ -13,6 +13,7 @@ import {
CUSTOM_INTERVAL, CUSTOM_INTERVAL,
} from '../../../helpers/constants'; } from '../../../helpers/constants';
import '../FormButton.css'; import '../FormButton.css';
import { Checkbox } from '../../ui/Controls/Checkbox';
const getIntervalTitle = (interval: number) => { const getIntervalTitle = (interval: number) => {
switch (interval) { switch (interval) {
@@ -33,7 +34,7 @@ export type FormValues = {
interval: number; interval: number;
customInterval?: number | null; customInterval?: number | null;
ignored: string; ignored: string;
} };
type Props = { type Props = {
initialValues: Partial<FormValues>; initialValues: Partial<FormValues>;
@@ -41,29 +42,26 @@ type Props = {
processingReset: boolean; processingReset: boolean;
onSubmit: (values: FormValues) => void; onSubmit: (values: FormValues) => void;
onReset: () => void; onReset: () => void;
} };
export const Form = ({ initialValues, processing, processingReset, onSubmit, onReset }: Props) => {
const { t } = useTranslation();
export const Form = ({
initialValues,
processing,
processingReset,
onSubmit,
onReset,
}: Props) => {
const { const {
register, register,
handleSubmit, handleSubmit,
watch, watch,
setValue, setValue,
control,
formState: { isSubmitting }, formState: { isSubmitting },
} = useForm<FormValues>({ } = useForm<FormValues>({
mode: 'onChange', mode: 'onChange',
defaultValues: { defaultValues: {
enabled: initialValues.enabled || false, enabled: initialValues.enabled || false,
anonymize_client_ip: initialValues.anonymize_client_ip || false, anonymize_client_ip: initialValues.anonymize_client_ip || false,
interval: initialValues.interval || DAY, interval: initialValues.interval || DAY,
customInterval: initialValues.customInterval || null, customInterval: initialValues.customInterval || null,
ignored: initialValues.ignored || '', ignored: initialValues.ignored || '',
}, },
}); });
@@ -85,50 +83,41 @@ export const Form = ({
setValue('ignored', trimmed); setValue('ignored', trimmed);
}; };
const disableSubmit = const disableSubmit = isSubmitting || processing || (intervalValue === RETENTION_CUSTOM && !customIntervalValue);
isSubmitting ||
processing ||
(intervalValue === RETENTION_CUSTOM && !customIntervalValue);
return ( return (
<form onSubmit={handleSubmit(onSubmitForm)}> <form onSubmit={handleSubmit(onSubmitForm)}>
<div className="form__group form__group--settings"> <div className="form__group form__group--settings">
<label className="checkbox"> <Controller
<span className="checkbox__marker" /> name="enabled"
control={control}
<input render={({ field: { name, value, onChange } }) => (
type="checkbox" <Checkbox
className="checkbox__input" name={name}
{...register('enabled')} title={t('query_log_enable')}
disabled={processing} value={value}
/> onChange={(value) => onChange(value)}
disabled={processing}
<span className="checkbox__label"> />
<span className="checkbox__label-text checkbox__label-text--long"> )}
<span className="checkbox__label-title">{i18next.t('query_log_enable')}</span> />
</span>
</span>
</label>
</div> </div>
<div className="form__group form__group--settings"> <div className="form__group form__group--settings">
<label className="checkbox"> <Controller
<span className="checkbox__marker" /> name="anonymize_client_ip"
control={control}
<input render={({ field: { name, value, onChange } }) => (
type="checkbox" <Checkbox
className="checkbox__input" name={name}
{...register('anonymize_client_ip')} title={t('anonymize_client_ip')}
disabled={processing} subtitle={t('anonymize_client_ip_desc')}
/> value={value}
onChange={(value) => onChange(value)}
<span className="checkbox__label"> disabled={processing}
<span className="checkbox__label-text checkbox__label-text--long"> />
<span className="checkbox__label-title">{i18next.t('anonymize_client_ip')}</span> )}
<span className="checkbox__label-subtitle">{i18next.t('anonymize_client_ip_desc')}</span> />
</span>
</span>
</label>
</div> </div>
<div className="form__label"> <div className="form__label">
@@ -180,7 +169,7 @@ export const Form = ({
value={interval} value={interval}
checked={intervalValue === interval} checked={intervalValue === interval}
onChange={(e) => { onChange={(e) => {
setValue("interval", parseInt(e.target.value, 10)); setValue('interval', parseInt(e.target.value, 10));
}} }}
/> />
@@ -209,11 +198,7 @@ export const Form = ({
</div> </div>
<div className="mt-5"> <div className="mt-5">
<button <button type="submit" className="btn btn-success btn-standard btn-large" disabled={disableSubmit}>
type="submit"
className="btn btn-success btn-standard btn-large"
disabled={disableSubmit}
>
<Trans>save_btn</Trans> <Trans>save_btn</Trans>
</button> </button>
@@ -221,8 +206,7 @@ export const Form = ({
type="button" type="button"
className="btn btn-outline-secondary btn-standard form__button" className="btn btn-outline-secondary btn-standard form__button"
onClick={onReset} onClick={onReset}
disabled={processingReset} disabled={processingReset}>
>
<Trans>query_log_clear</Trans> <Trans>query_log_clear</Trans>
</button> </button>
</div> </div>

View File

@@ -1,8 +1,8 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { Trans } from 'react-i18next'; import { Trans, useTranslation } from 'react-i18next';
import i18next from 'i18next'; import i18next from 'i18next';
import { useForm } from 'react-hook-form'; import { Controller, useForm } from 'react-hook-form';
import { import {
STATS_INTERVALS_DAYS, STATS_INTERVALS_DAYS,
DAY, DAY,
@@ -13,6 +13,7 @@ import {
import { trimLinesAndRemoveEmpty } from '../../../helpers/helpers'; import { trimLinesAndRemoveEmpty } from '../../../helpers/helpers';
import '../FormButton.css'; import '../FormButton.css';
import { Checkbox } from '../../ui/Controls/Checkbox';
const getIntervalTitle = (interval: any) => { const getIntervalTitle = (interval: any) => {
switch (interval) { switch (interval) {
@@ -30,7 +31,7 @@ export type FormValues = {
interval: number; interval: number;
customInterval?: number | null; customInterval?: number | null;
ignored: string; ignored: string;
} };
type Props = { type Props = {
initialValues: Partial<FormValues>; initialValues: Partial<FormValues>;
@@ -38,28 +39,25 @@ type Props = {
processingReset: boolean; processingReset: boolean;
onSubmit: (values: FormValues) => void; onSubmit: (values: FormValues) => void;
onReset: () => void; onReset: () => void;
} };
export const Form = ({ initialValues, processing, processingReset, onSubmit, onReset }: Props) => {
const { t } = useTranslation();
export const Form = ({
initialValues,
processing,
processingReset,
onSubmit,
onReset,
}: Props) => {
const { const {
register, register,
handleSubmit, handleSubmit,
watch, watch,
setValue, setValue,
control,
formState: { isSubmitting }, formState: { isSubmitting },
} = useForm<FormValues>({ } = useForm<FormValues>({
mode: 'onChange', mode: 'onChange',
defaultValues: { defaultValues: {
enabled: initialValues.enabled || false, enabled: initialValues.enabled || false,
interval: initialValues.interval || DAY, interval: initialValues.interval || DAY,
customInterval: initialValues.customInterval || null, customInterval: initialValues.customInterval || null,
ignored: initialValues.ignored || '', ignored: initialValues.ignored || '',
}, },
}); });
@@ -81,30 +79,24 @@ export const Form = ({
setValue('ignored', trimmed); setValue('ignored', trimmed);
}; };
const disableSubmit = const disableSubmit = isSubmitting || processing || (intervalValue === RETENTION_CUSTOM && !customIntervalValue);
isSubmitting ||
processing ||
(intervalValue === RETENTION_CUSTOM && !customIntervalValue);
return ( return (
<form onSubmit={handleSubmit(onSubmitForm)}> <form onSubmit={handleSubmit(onSubmitForm)}>
<div className="form__group form__group--settings"> <div className="form__group form__group--settings">
<label className="checkbox"> <Controller
<span className="checkbox__marker" /> name="enabled"
control={control}
<input render={({ field: { name, value, onChange } }) => (
type="checkbox" <Checkbox
className="checkbox__input" name={name}
{...register('enabled')} title={t('statistics_enable')}
disabled={processing} value={value}
/> onChange={(value) => onChange(value)}
disabled={processing}
<span className="checkbox__label"> />
<span className="checkbox__label-text checkbox__label-text--long"> )}
<span className="checkbox__label-title">{i18next.t('statistics_enable')}</span> />
</span>
</span>
</label>
</div> </div>
<div className="form__label form__label--with-desc"> <div className="form__label form__label--with-desc">
@@ -134,9 +126,7 @@ export const Form = ({
{!STATS_INTERVALS_DAYS.includes(intervalValue) && ( {!STATS_INTERVALS_DAYS.includes(intervalValue) && (
<div className="form__group--input"> <div className="form__group--input">
<div className="form__desc form__desc--top"> <div className="form__desc form__desc--top">{i18next.t('custom_retention_input')}</div>
{i18next.t('custom_retention_input')}
</div>
{/* <Field {/* <Field
key={RETENTION_CUSTOM_INPUT} key={RETENTION_CUSTOM_INPUT}
@@ -183,7 +173,7 @@ export const Form = ({
value={interval} value={interval}
checked={intervalValue === interval} checked={intervalValue === interval}
onChange={(e) => { onChange={(e) => {
setValue("interval", parseInt(e.target.value, 10)); setValue('interval', parseInt(e.target.value, 10));
}} }}
/> />
@@ -221,11 +211,7 @@ export const Form = ({
</div> </div>
<div className="mt-5"> <div className="mt-5">
<button <button type="submit" className="btn btn-success btn-standard btn-large" disabled={disableSubmit}>
type="submit"
className="btn btn-success btn-standard btn-large"
disabled={disableSubmit}
>
<Trans>save_btn</Trans> <Trans>save_btn</Trans>
</button> </button>

View File

@@ -1,13 +1,14 @@
import React, { Component, Fragment } from 'react'; import React, { Component, Fragment } from 'react';
import { withTranslation } from 'react-i18next'; import { withTranslation } from 'react-i18next';
import i18next from 'i18next';
import StatsConfig from './StatsConfig'; import StatsConfig from './StatsConfig';
import LogsConfig from './LogsConfig'; import LogsConfig from './LogsConfig';
import { FiltersConfig } from './FiltersConfig'; import { FiltersConfig } from './FiltersConfig';
import Checkbox from '../ui/Checkbox'; import { Checkbox } from '../ui/Controls/Checkbox';
import Loading from '../ui/Loading'; import Loading from '../ui/Loading';
@@ -24,14 +25,14 @@ const ORDER_KEY = 'order';
const SETTINGS = { const SETTINGS = {
safebrowsing: { safebrowsing: {
enabled: false, enabled: false,
title: 'use_adguard_browsing_sec', title: i18next.t('use_adguard_browsing_sec'),
subtitle: 'use_adguard_browsing_sec_hint', subtitle: i18next.t('use_adguard_browsing_sec_hint'),
[ORDER_KEY]: 0, [ORDER_KEY]: 0,
}, },
parental: { parental: {
enabled: false, enabled: false,
title: 'use_adguard_parental', title: i18next.t('use_adguard_parental'),
subtitle: 'use_adguard_parental_hint', subtitle: i18next.t('use_adguard_parental_hint'),
[ORDER_KEY]: 1, [ORDER_KEY]: 1,
}, },
}; };
@@ -89,9 +90,18 @@ class Settings extends Component<SettingsProps> {
renderSettings = (settings: any) => renderSettings = (settings: any) =>
getObjectKeysSorted(SETTINGS, ORDER_KEY).map((key: any) => { getObjectKeysSorted(SETTINGS, ORDER_KEY).map((key: any) => {
const setting = settings[key]; const setting = settings[key];
const { enabled } = setting; const { enabled, title, subtitle } = setting;
return <Checkbox {...setting} key={key} handleChange={() => this.props.toggleSetting(key, enabled)} />; return (
<div key={key} className="form__group form__group--checkbox">
<Checkbox
value={enabled}
title={title}
subtitle={subtitle}
onChange={(checked) => this.props.toggleSetting(key, !checked)}
/>
</div>
);
}); });
renderSafeSearch = () => { renderSafeSearch = () => {
@@ -106,27 +116,29 @@ class Settings extends Component<SettingsProps> {
return ( return (
<> <>
<Checkbox <div className="form__group form__group--checkbox">
enabled={enabled} <Checkbox
title="enforce_safe_search" value={enabled}
subtitle="enforce_save_search_hint" title={i18next.t('enforce_safe_search')}
handleChange={({ target: { checked: enabled } }) => subtitle={i18next.t('enforce_save_search_hint')}
this.props.toggleSetting('safesearch', { ...safesearch, enabled }) onChange={(checked) =>
} this.props.toggleSetting('safesearch', { ...safesearch, enabled: checked })
/> }
/>
</div>
<div className="form__group--inner"> <div className="form__group--inner">
{Object.keys(searches).map((searchKey) => ( {Object.keys(searches).map((searchKey) => (
<Checkbox <div key={searchKey} className="form__group form__group--checkbox">
key={searchKey} <Checkbox
enabled={searches[searchKey]} value={searches[searchKey]}
title={captitalizeWords(searchKey)} title={captitalizeWords(searchKey)}
subtitle="" disabled={!safesearch.enabled}
disabled={!safesearch.enabled} onChange={(checked) =>
handleChange={({ target: { checked } }: any) => this.props.toggleSetting('safesearch', { ...safesearch, [searchKey]: checked })
this.props.toggleSetting('safesearch', { ...safesearch, [searchKey]: checked }) }
} />
/> </div>
))} ))}
</div> </div>
</> </>

View File

@@ -1,59 +0,0 @@
import React, { Component } from 'react';
import { withTranslation } from 'react-i18next';
import './Checkbox.css';
interface CheckboxProps {
title: string;
subtitle: string;
enabled: boolean;
handleChange: (...args: unknown[]) => unknown;
disabled?: boolean;
t?: (...args: unknown[]) => string;
}
class Checkbox extends Component<CheckboxProps> {
render() {
const {
title,
subtitle,
enabled,
handleChange,
disabled,
t,
} = this.props;
return (
<div className="form__group form__group--checkbox">
<label className="checkbox checkbox--settings">
<span className="checkbox__marker" />
<input
type="checkbox"
className="checkbox__input"
onChange={handleChange}
checked={enabled}
disabled={disabled}
/>
<span className="checkbox__label">
<span className="checkbox__label-text">
<span className="checkbox__label-title">{t(title)}</span>
<span
className="checkbox__label-subtitle"
dangerouslySetInnerHTML={{ __html: t(subtitle) }}
/>
</span>
</span>
</label>
</div>
);
}
}
export default withTranslation()(Checkbox);

View File

@@ -0,0 +1,35 @@
import React, { ReactNode } from 'react';
import clsx from 'clsx';
import './checkbox.css';
type Props = {
title: string;
subtitle?: ReactNode;
value: boolean;
name?: string;
disabled?: boolean;
className?: string;
onChange: (value: boolean) => void;
};
export const Checkbox = ({ title, subtitle, value, name, disabled, className = 'checkbox--form', onChange }: Props) => (
<label className={clsx('checkbox', className)}>
<span className="checkbox__marker" />
<input
name={name}
type="checkbox"
className="checkbox__input"
disabled={disabled}
checked={value}
onChange={(e) => onChange(e.target.checked)}
/>
<span className="checkbox__label">
<span className="checkbox__label-text checkbox__label-text--long">
<span className="checkbox__label-title">{title}</span>
{subtitle && <span className="checkbox__label-subtitle">{subtitle}</span>}
</span>
</span>
</label>
);

View File

@@ -0,0 +1,63 @@
import React from 'react';
type Props = {
id?: string;
className?: string;
placeholder?: string;
type?: string;
disabled?: boolean;
autoComplete?: string;
isActionAvailable?: boolean;
removeField?: () => void;
normalizeOnBlur?: (value: string) => string;
value: string;
onChange: (value: string) => void;
onBlur: () => void;
};
export const InputGroup = ({
id,
className,
placeholder,
type,
disabled,
autoComplete,
isActionAvailable,
removeField,
normalizeOnBlur,
value,
onChange,
onBlur,
}: Props) => {
const handleBlur = (event: React.FocusEvent<HTMLInputElement>) => {
if (normalizeOnBlur) {
onChange(normalizeOnBlur(event.target.value));
}
onBlur();
};
return (
<div className="input-group">
<input
id={id}
placeholder={placeholder}
type={type}
className={className}
disabled={disabled}
autoComplete={autoComplete}
value={value}
onChange={(e) => onChange(e.target.value)}
onBlur={handleBlur}
/>
{isActionAvailable && (
<span className="input-group-append">
<button type="button" className="btn btn-secondary btn-icon btn-icon--green" onClick={removeField}>
<svg className="icon icon--24">
<use xlinkHref="#cross" />
</svg>
</button>
</span>
)}
</div>
);
};