Files
nginx-proxy-manager/frontend/src/components/Form/DomainNamesField.tsx
2025-09-23 10:14:01 +10:00

122 lines
3.0 KiB
TypeScript

import { Field, useFormikContext } from "formik";
import type { ActionMeta, MultiValue } from "react-select";
import CreatableSelect from "react-select/creatable";
import { intl } from "src/locale";
export type SelectOption = {
label: string;
value: string;
color?: string;
};
interface Props {
id?: string;
maxDomains?: number;
isWildcardPermitted?: boolean;
dnsProviderWildcardSupported?: boolean;
name?: string;
label?: string;
}
export function DomainNamesField({
name = "domainNames",
label = "domain-names",
id = "domainNames",
maxDomains,
isWildcardPermitted,
dnsProviderWildcardSupported,
}: Props) {
const { values, setFieldValue } = useFormikContext();
const getDomainCount = (v: string[] | undefined): number => {
if (v?.length) {
return v.length;
}
return 0;
};
const handleChange = (v: MultiValue<SelectOption>, _actionMeta: ActionMeta<SelectOption>) => {
const doms = v?.map((i: SelectOption) => {
return i.value;
});
setFieldValue(name, doms);
};
const isDomainValid = (d: string): boolean => {
const dom = d.trim().toLowerCase();
const v: any = values;
// Deny if the list of domains is hit
if (maxDomains && getDomainCount(v?.[name]) >= maxDomains) {
return false;
}
if (dom.length < 3) {
return false;
}
// Prevent wildcards
if ((!isWildcardPermitted || !dnsProviderWildcardSupported) && dom.indexOf("*") !== -1) {
return false;
}
// Prevent duplicate * in domain
if ((dom.match(/\*/g) || []).length > 1) {
return false;
}
// Prevent some invalid characters
if ((dom.match(/(@|,|!|&|\$|#|%|\^|\(|\))/g) || []).length > 0) {
return false;
}
// This will match *.com type domains,
return dom.match(/\*\.[^.]+$/m) === null;
};
const helperTexts: string[] = [];
if (maxDomains) {
helperTexts.push(intl.formatMessage({ id: "domain_names.max" }, { count: maxDomains }));
}
if (!isWildcardPermitted) {
helperTexts.push(intl.formatMessage({ id: "wildcards-not-permitted" }));
} else if (!dnsProviderWildcardSupported) {
helperTexts.push(intl.formatMessage({ id: "wildcards-not-supported" }));
}
return (
<Field name={name}>
{({ field, form }: any) => (
<div className="mb-3">
<label className="form-label" htmlFor={id}>
{intl.formatMessage({ id: label })}
</label>
<CreatableSelect
className="react-select-container"
classNamePrefix="react-select"
name={field.name}
id={id}
closeMenuOnSelect={true}
isClearable={false}
isValidNewOption={isDomainValid}
isMulti
placeholder="Start typing to add domain..."
onChange={handleChange}
value={field.value?.map((d: string) => ({ label: d, value: d }))}
/>
{form.errors[field.name] ? (
<div className="invalid-feedback">
{form.errors[field.name] && form.touched[field.name] ? form.errors[field.name] : null}
</div>
) : helperTexts.length ? (
helperTexts.map((i) => (
<div key={i} className="invalid-feedback text-info">
{i}
</div>
))
) : null}
</div>
)}
</Field>
);
}