DomainNamesField.tsx 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. import { Field, useFormikContext } from "formik";
  2. import type { ActionMeta, MultiValue } from "react-select";
  3. import CreatableSelect from "react-select/creatable";
  4. import { intl } from "src/locale";
  5. export type SelectOption = {
  6. label: string;
  7. value: string;
  8. color?: string;
  9. };
  10. interface Props {
  11. id?: string;
  12. maxDomains?: number;
  13. isWildcardPermitted?: boolean;
  14. dnsProviderWildcardSupported?: boolean;
  15. name?: string;
  16. label?: string;
  17. }
  18. export function DomainNamesField({
  19. name = "domainNames",
  20. label = "domain-names",
  21. id = "domainNames",
  22. maxDomains,
  23. isWildcardPermitted,
  24. dnsProviderWildcardSupported,
  25. }: Props) {
  26. const { values, setFieldValue } = useFormikContext();
  27. const getDomainCount = (v: string[] | undefined): number => {
  28. if (v?.length) {
  29. return v.length;
  30. }
  31. return 0;
  32. };
  33. const handleChange = (v: MultiValue<SelectOption>, _actionMeta: ActionMeta<SelectOption>) => {
  34. const doms = v?.map((i: SelectOption) => {
  35. return i.value;
  36. });
  37. setFieldValue(name, doms);
  38. };
  39. const isDomainValid = (d: string): boolean => {
  40. const dom = d.trim().toLowerCase();
  41. const v: any = values;
  42. // Deny if the list of domains is hit
  43. if (maxDomains && getDomainCount(v?.[name]) >= maxDomains) {
  44. return false;
  45. }
  46. if (dom.length < 3) {
  47. return false;
  48. }
  49. // Prevent wildcards
  50. if ((!isWildcardPermitted || !dnsProviderWildcardSupported) && dom.indexOf("*") !== -1) {
  51. return false;
  52. }
  53. // Prevent duplicate * in domain
  54. if ((dom.match(/\*/g) || []).length > 1) {
  55. return false;
  56. }
  57. // Prevent some invalid characters
  58. if ((dom.match(/(@|,|!|&|\$|#|%|\^|\(|\))/g) || []).length > 0) {
  59. return false;
  60. }
  61. // This will match *.com type domains,
  62. return dom.match(/\*\.[^.]+$/m) === null;
  63. };
  64. const helperTexts: string[] = [];
  65. if (maxDomains) {
  66. helperTexts.push(intl.formatMessage({ id: "domain_names.max" }, { count: maxDomains }));
  67. }
  68. if (!isWildcardPermitted) {
  69. helperTexts.push(intl.formatMessage({ id: "wildcards-not-permitted" }));
  70. } else if (!dnsProviderWildcardSupported) {
  71. helperTexts.push(intl.formatMessage({ id: "wildcards-not-supported" }));
  72. }
  73. return (
  74. <Field name={name}>
  75. {({ field, form }: any) => (
  76. <div className="mb-3">
  77. <label className="form-label" htmlFor={id}>
  78. {intl.formatMessage({ id: label })}
  79. </label>
  80. <CreatableSelect
  81. className="react-select-container"
  82. classNamePrefix="react-select"
  83. name={field.name}
  84. id={id}
  85. closeMenuOnSelect={true}
  86. isClearable={false}
  87. isValidNewOption={isDomainValid}
  88. isMulti
  89. placeholder="Start typing to add domain..."
  90. onChange={handleChange}
  91. value={field.value?.map((d: string) => ({ label: d, value: d }))}
  92. />
  93. {form.errors[field.name] ? (
  94. <div className="invalid-feedback">
  95. {form.errors[field.name] && form.touched[field.name] ? form.errors[field.name] : null}
  96. </div>
  97. ) : helperTexts.length ? (
  98. helperTexts.map((i) => (
  99. <div key={i} className="invalid-feedback text-info">
  100. {i}
  101. </div>
  102. ))
  103. ) : null}
  104. </div>
  105. )}
  106. </Field>
  107. );
  108. }