cleanup forms
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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 = {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user