@engagebay/engagebay-form-module 1.0.1 → 1.0.2-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,9 @@
1
1
  import React, {
2
- useCallback,
3
- useContext,
4
- useEffect,
5
- useRef,
6
- useState,
2
+ useCallback,
3
+ useContext,
4
+ useEffect,
5
+ useRef,
6
+ useState,
7
7
  } from "react";
8
8
 
9
9
  import { Listbox, ListboxButton } from "@headlessui/react";
@@ -12,192 +12,206 @@ import { RegisterOptions } from "react-hook-form";
12
12
  import { FormContext } from "../context/FormContext";
13
13
  import { getListOption, getListOptions } from "../FormFieldUtils";
14
14
  import {
15
- FieldOptionsSchema,
16
- FormFieldComponentPropSchema,
17
- FormFieldType,
15
+ FieldOptionsSchema,
16
+ FormFieldComponentPropSchema,
17
+ FormFieldType,
18
18
  } from "../schema/FormFieldSchema";
19
19
  import { handleChange, registerFormField } from "../util";
20
20
  import RenderFormField from "../util/RenderFormField";
21
21
  import RenderListOptions, {
22
- renderListBoxValue,
22
+ renderListBoxValue,
23
23
  } from "../util/RenderListOptions";
24
24
  // import _ from "lodash";
25
25
  import axios from "axios";
26
- import { reachoAPI } from "../../api";
26
+ import { getAxiosInstance } from "src/api";
27
27
 
28
28
  const TypeaheadMultiSelect: React.FC<FormFieldComponentPropSchema> = (
29
- props: FormFieldComponentPropSchema
29
+ props: FormFieldComponentPropSchema,
30
30
  ) => {
31
- const dynamicSelectRef = useRef<HTMLUListElement>(null);
32
- const formContext = useContext(FormContext);
33
- let registerOptions: RegisterOptions = registerFormField(props.fieldConfig);
34
- let hookProps = formContext.register(
31
+ const dynamicSelectRef = useRef<HTMLUListElement>(null);
32
+ const formContext = useContext(FormContext);
33
+ let registerOptions: RegisterOptions = registerFormField(props.fieldConfig);
34
+ let hookProps = formContext.register(props.fieldConfig.name, registerOptions);
35
+ const [listOptions, setListOptions] = useState<FieldOptionsSchema[]>([]);
36
+ const [selectedValues, setSelectedValues] = useState<FieldOptionsSchema[]>(
37
+ [],
38
+ );
39
+ const [loading, setLoading] = useState<boolean>(true);
40
+
41
+ useEffect(() => {
42
+ if (
43
+ !formContext.getValues(props.fieldConfig.name) &&
44
+ props.fieldConfig.defaultValue
45
+ ) {
46
+ formContext.setValue(
35
47
  props.fieldConfig.name,
36
- registerOptions
37
- );
38
- const [listOptions, setListOptions] = useState<FieldOptionsSchema[]>([]);
39
- const [selectedValues, setSelectedValues] = useState<FieldOptionsSchema[]>(
40
- []
41
- );
42
- const [loading, setLoading] = useState<boolean>(true);
43
-
44
- useEffect(() => {
45
- if (
46
- !formContext.getValues(props.fieldConfig.name) &&
47
- props.fieldConfig.defaultValue
48
- ) {
49
- formContext.setValue(
50
- props.fieldConfig.name,
51
- props.fieldConfig.defaultValue
52
- );
48
+ props.fieldConfig.defaultValue,
49
+ );
50
+ }
51
+ fetchData(undefined);
52
+ }, []);
53
+
54
+ const fetchData = useCallback(
55
+ async (_query: string | undefined) => {
56
+ setLoading(true);
57
+ try {
58
+ if (!props.fieldConfig.fetchUrl) return;
59
+
60
+ let url = props.fieldConfig.fetchUrl;
61
+ if (_query) {
62
+ url = url.includes("?") ? url + "&q=" + _query : url + "?q=" + _query;
53
63
  }
54
- fetchData(undefined);
55
- }, []);
56
-
57
- const fetchData = useCallback(
58
- async (_query: string | undefined) => {
59
- setLoading(true);
60
- try {
61
- if (!props.fieldConfig.fetchUrl) return;
62
-
63
- let url = props.fieldConfig.fetchUrl;
64
- if (_query) {
65
- url = url.includes("?")
66
- ? url + "&q=" + _query
67
- : url + "?q=" + _query;
68
- }
69
-
70
- let response = await (props.fieldConfig.disableHeaderInFetch
71
- ? axios.get(url)
72
- : reachoAPI.get(url));
73
- if (response.data) {
74
- const data: FieldOptionsSchema[] = getListOptions(
75
- response.data,
76
- props.fieldConfig.optionsConfig
77
- );
78
- setListOptions([...data]);
79
- let values = formContext.getValues(props.fieldConfig.name) || [];
80
-
81
- if (
82
- values.length > 0 && // Ensure 'values' is not empty
83
- (data.length === 0 || // If 'data' is empty
84
- !values.every((v: string) => data.some(i => i.value === v)) && // Ensure none of 'values' match 'data'
85
- !values.every((v: string) => selectedValues.some(i => i.value === v)) // Ensure none of 'values' match 'selectedValues'
86
- )) {
87
- fetchValue(values); // Call 'fetchValue()' if all conditions are met
88
- }
89
-
90
- if (props.fieldConfig.fetchCallback) {
91
- props.fieldConfig.fetchCallback(response);
92
- }
93
- } else {
94
- console.error(response.statusText);
95
- }
96
- } catch (err) {
97
- } finally {
98
- setLoading(false);
99
- }
100
- },
101
- [props.fieldConfig.fetchUrl]
102
- );
64
+ const axiosInstance = getAxiosInstance(
65
+ formContext.axiosInstance,
66
+ props.fieldConfig,
67
+ );
103
68
 
104
- const fetchValue = async (value: string) => {
105
- try {
106
- if (
107
- props.fieldConfig.ignoreFetchValue ||
108
- !props.fieldConfig.fetchUrl
109
- ) {
110
- return;
111
- }
112
-
113
- let url = props.fieldConfig.fetchUrl;
114
-
115
- url = url = url.includes("?")
116
- ? url + "&values=" + value
117
- : url + "?values=" + value;
118
-
119
- let response = await (props.fieldConfig.disableHeaderInFetch
120
- ? axios.get(url)
121
- : reachoAPI.get(url));
122
- if (response.data) {
123
- const data: FieldOptionsSchema[] = getListOptions(
124
- response.data,
125
- props.fieldConfig.optionsConfig
126
- );
127
- setSelectedValues([...data]);
128
- }
129
- } catch (err) { }
130
- };
131
-
132
- const updateListOptions = (data: any) => {
133
- const resData: FieldOptionsSchema = getListOption(
134
- data,
135
- props.fieldConfig.optionsConfig
69
+ let response = await axiosInstance.get(url);
70
+ if (response?.data) {
71
+ const data: FieldOptionsSchema[] = getListOptions(
72
+ response?.data,
73
+ props.fieldConfig.optionsConfig,
74
+ );
75
+ setListOptions([...data]);
76
+ let values = formContext.getValues(props.fieldConfig.name) || [];
77
+
78
+ if (
79
+ values.length > 0 && // Ensure 'values' is not empty
80
+ (data.length === 0 || // If 'data' is empty
81
+ (!values.every((v: string) => data.some((i) => i.value === v)) && // Ensure none of 'values' match 'data'
82
+ !values.every((v: string) =>
83
+ selectedValues.some((i) => i.value === v),
84
+ ))) // Ensure none of 'values' match 'selectedValues'
85
+ ) {
86
+ fetchValue(values); // Call 'fetchValue()' if all conditions are met
87
+ }
88
+
89
+ if (props.fieldConfig.fetchCallback) {
90
+ props.fieldConfig.fetchCallback(response);
91
+ }
92
+ } else {
93
+ console.error(response?.statusText);
94
+ }
95
+ } catch (err) {
96
+ } finally {
97
+ setLoading(false);
98
+ }
99
+ },
100
+ [props.fieldConfig.fetchUrl],
101
+ );
102
+
103
+ const fetchValue = async (value: string) => {
104
+ try {
105
+ if (props.fieldConfig.ignoreFetchValue || !props.fieldConfig.fetchUrl) {
106
+ return;
107
+ }
108
+
109
+ let url = props.fieldConfig.fetchUrl;
110
+
111
+ url = url = url.includes("?")
112
+ ? url + "&values=" + value
113
+ : url + "?values=" + value;
114
+
115
+ let response = await (props.fieldConfig.disableHeaderInFetch
116
+ ? axios.get(url)
117
+ : formContext.axiosInstance?.get(url));
118
+ if (response?.data) {
119
+ const data: FieldOptionsSchema[] = getListOptions(
120
+ response?.data,
121
+ props.fieldConfig.optionsConfig,
136
122
  );
137
- setSelectedValues((prev) => [...prev, resData]);
138
- let result = formContext.getValues(props.fieldConfig.name) || [];
139
- result = result.includes(resData.value) ? result.filter((v: string) => v != resData.value) : [...result, resData.value];
140
- formContext.setValue(props.fieldConfig.name, result);
141
- };
142
-
143
- const getInput = () => {
144
- return (
145
- <Listbox
146
- as={"div"}
147
- {...hookProps}
148
- value={formContext.getValues(props.fieldConfig.name) || []}
149
- name={props.fieldConfig.name}
150
- defaultValue={props.fieldConfig.defaultValue}
151
- key={props.fieldConfig.name}
152
- className={"relative form-listbox flex-1"}
153
- onChange={(selectedOptions) => {
154
- const chossenOptions = listOptions.filter(op => selectedOptions.includes(op.value));
155
- setSelectedValues(prev => [...prev, ...chossenOptions])
156
- handleChange(
157
- selectedOptions,
158
- formContext,
159
- props.fieldConfig,
160
- props.onChange
161
- )
162
- }
163
- }
164
- multiple>
165
- <ListboxButton
166
- className={
167
- props.fieldConfig.customClassNames?.fieldClassName
168
- ? "form-listbox-select " +
169
- props.fieldConfig.customClassNames?.fieldClassName
170
- : "form-listbox-select"
171
- }>
172
- {renderListBoxValue(
173
- formContext,
174
- props.fieldConfig,
175
- [...selectedValues, ...listOptions],
176
- props.onChange
177
- )}
178
- </ListboxButton>
179
- <RenderListOptions
180
- formContext={formContext}
181
- onChange={props.onChange}
182
- formField={FormFieldType.TYPEAHEAD_MULTI_SELECT}
183
- ref={dynamicSelectRef}
184
- fieldConfig={props.fieldConfig}
185
- listOptions={[...selectedValues, ...listOptions]}
186
- setListOptions={setListOptions}
187
- loading={loading}
188
- setLoading={setLoading}
189
- createCallback={(data) => updateListOptions(data)}
190
- queryCallback={(query) => fetchData(query)}
191
- />
192
- </Listbox>
123
+ let values: any[] = formContext.getValues(props.fieldConfig.name) || [];
124
+ setSelectedValues(
125
+ [
126
+ ...data,
127
+ props.fieldConfig.dropdownFieldConfig?.isSuggestionBox && values
128
+ ? values
129
+ .filter(
130
+ (value) =>
131
+ data.some((d) => d.value !== value) || data.length == 0,
132
+ )
133
+ .map((val) => ({ label: val, value: val }))
134
+ : [],
135
+ ].flat(),
193
136
  );
194
- };
195
- if (props.fieldConfig.hideWhenNoResults && listOptions.length == 0) {
196
- return <></>;
197
- }
198
-
137
+ }
138
+ } catch (err) {}
139
+ };
140
+
141
+ const updateListOptions = (data: any) => {
142
+ const resData: FieldOptionsSchema = getListOption(
143
+ data,
144
+ props.fieldConfig.optionsConfig,
145
+ );
146
+ setSelectedValues((prev) => [...prev, resData]);
147
+ let result = formContext.getValues(props.fieldConfig.name) || [];
148
+ result = result.includes(resData.value)
149
+ ? result.filter((v: string) => v != resData.value)
150
+ : [...result, resData.value];
151
+ formContext.setValue(props.fieldConfig.name, result);
152
+ };
153
+
154
+ const getInput = () => {
199
155
  return (
200
- <RenderFormField fieldConfig={props.fieldConfig} getInput={getInput} />
156
+ <Listbox
157
+ as={"div"}
158
+ {...hookProps}
159
+ value={formContext.getValues(props.fieldConfig.name) || []}
160
+ name={props.fieldConfig.name}
161
+ defaultValue={props.fieldConfig.defaultValue}
162
+ key={props.fieldConfig.name}
163
+ className={"relative form-listbox flex-1 overflow-hidden"}
164
+ onChange={(selectedOptions) => {
165
+ const chossenOptions = listOptions.filter((op) =>
166
+ selectedOptions.includes(op.value),
167
+ );
168
+ setSelectedValues((prev) => [...prev, ...chossenOptions]);
169
+ handleChange(
170
+ selectedOptions,
171
+ formContext,
172
+ props.fieldConfig,
173
+ props.onChange,
174
+ );
175
+ }}
176
+ multiple
177
+ >
178
+ <ListboxButton
179
+ className={
180
+ props.fieldConfig.customClassNames?.fieldClassName
181
+ ? "form-listbox-select " +
182
+ props.fieldConfig.customClassNames?.fieldClassName
183
+ : "form-listbox-select"
184
+ }
185
+ >
186
+ {renderListBoxValue(
187
+ formContext,
188
+ props.fieldConfig,
189
+ [...selectedValues, ...listOptions],
190
+ props.onChange,
191
+ )}
192
+ </ListboxButton>
193
+ <RenderListOptions
194
+ formContext={formContext}
195
+ onChange={props.onChange}
196
+ formField={FormFieldType.TYPEAHEAD_MULTI_SELECT}
197
+ ref={dynamicSelectRef}
198
+ fieldConfig={props.fieldConfig}
199
+ listOptions={[...selectedValues, ...listOptions]}
200
+ setListOptions={setListOptions}
201
+ loading={loading}
202
+ setLoading={setLoading}
203
+ createCallback={(data) => updateListOptions(data)}
204
+ queryCallback={(query) => fetchData(query)}
205
+ />
206
+ </Listbox>
201
207
  );
208
+ };
209
+ if (props.fieldConfig.hideWhenNoResults && listOptions.length == 0) {
210
+ return <></>;
211
+ }
212
+
213
+ return (
214
+ <RenderFormField fieldConfig={props.fieldConfig} getInput={getInput} />
215
+ );
202
216
  };
203
217
  export default TypeaheadMultiSelect;
@@ -10,6 +10,7 @@ import {
10
10
  } from "react-hook-form";
11
11
  import { Saga } from "redux-saga";
12
12
  import { emailIdsValidator, queryValidator } from "./CustomValidators";
13
+ import { AxiosInstance } from 'axios';
13
14
 
14
15
  export enum FormFieldType {
15
16
  INPUT = "INPUT",
@@ -75,6 +76,9 @@ export type DropdownFieldConfig = {
75
76
  showDeleteOption: boolean;
76
77
  showCreateOption: boolean;
77
78
  showSearchBox: boolean;
79
+ placeHolder?: string;
80
+ hideIcon?: boolean;
81
+ isSuggestionBox?: boolean;
78
82
  createLabelText?: string;
79
83
  isCaseSensitive?: boolean;
80
84
  };
@@ -136,6 +140,7 @@ export type FormFieldSchema = {
136
140
  forceUpdate?: boolean;
137
141
  disableHeaderInFetch?: boolean;
138
142
  dropdownFieldConfig?: DropdownFieldConfig;
143
+ axiosInstance?: AxiosInstance
139
144
 
140
145
  /**
141
146
  * Redux configuration attribute
@@ -301,6 +306,18 @@ export class FormFieldPatternsImpl implements FormFieldPatterns {
301
306
  /^[a-z][a-z0-9_]*$/
302
307
  );
303
308
 
309
+ static readonly NO_SPECIAL_CHAR_LOWERCASE_WITH_SPACES = new FormFieldPatternsImpl(
310
+ "NO_SPECIAL_CHAR_LOWERCASE_WITH_SPACES",
311
+ "Please enter only letters, numbers, and spaces.",
312
+ /^[A-Za-z0-9 ]+$/
313
+ );
314
+
315
+ static readonly VALID_SUBDOMAIN_NAME = new FormFieldPatternsImpl(
316
+ "VALID_SUBDOMAIN_NAME",
317
+ "Name should be between 3-30 characters. Cannot contain special characters.",
318
+ /^[a-z]+$/
319
+ );
320
+
304
321
  private constructor(
305
322
  private readonly patternName: string,
306
323
  private readonly message: string,
@@ -3,6 +3,7 @@ import React from "react";
3
3
  import { FieldAlignType, FormFieldSchema } from "../schema/FormFieldSchema";
4
4
  import ErrorContextHandler from "../formfields/ErrorContextHandler";
5
5
  import SVGIcon from "../../util/svg/SVGIcon";
6
+ import { useFormState } from "react-hook-form";
6
7
  /**
7
8
  * RenderFormField Component
8
9
  *
@@ -54,6 +55,7 @@ const RenderFormField = ({
54
55
  fieldConfig: FormFieldSchema;
55
56
  getInput: Function;
56
57
  }): JSX.Element => {
58
+
57
59
  function formTypeHorizontal() {
58
60
  return (
59
61
  <Field
@@ -85,7 +85,14 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
85
85
  }
86
86
  };
87
87
  let resultList = [...createdListItems, ...listOptions];
88
- resultList = [...resultList.filter((result) => result.label).filter((option, index, self) => index === self.findIndex((obj) => obj.value === option.value))]; // removing duplicate values
88
+ resultList = [
89
+ ...resultList
90
+ .filter((result) => result.label)
91
+ .filter(
92
+ (option, index, self) =>
93
+ index === self.findIndex((obj) => obj.value === option.value)
94
+ ),
95
+ ]; // removing duplicate values
89
96
 
90
97
  const caseSensitive =
91
98
  query && fieldConfig.dropdownFieldConfig?.isCaseSensitive
@@ -154,12 +161,15 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
154
161
  selected = option.value == formValue;
155
162
  }
156
163
  return (
157
- <ListboxOption
164
+ <Listbox.Option
165
+ static
158
166
  key={option.value}
159
167
  disabled={option.isDisabled}
160
168
  onClick={() => setTimeout(resetToDefault, 300)}
161
169
  className={`form-listbox-option ${
162
- selected ? " bg-gray-100 text-gray-900" : "bg-white text-gray-700"
170
+ selected
171
+ ? " bg-gray-100 text-gray-900"
172
+ : "hover:bg-gray-100 text-gray-700"
163
173
  }`}
164
174
  value={option.value}
165
175
  >
@@ -178,7 +188,8 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
178
188
  )}
179
189
  {option.label}
180
190
  </span>
181
- {isTypeahead ? (
191
+ {isTypeahead &&
192
+ !fieldConfig.dropdownFieldConfig?.isSuggestionBox ? (
182
193
  <input
183
194
  type="checkbox"
184
195
  className="form-checkbox"
@@ -195,7 +206,7 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
195
206
  )}
196
207
  </div>
197
208
  {option.helpText && (
198
- <div className="mt-0 text-sm text-gray-500 font-normal truncate w-full !max-w-[150px]">
209
+ <div className="mt-0 text-xs text-gray-500 font-normal truncate w-full !max-w-[150px]">
199
210
  {option.helpText}
200
211
  </div>
201
212
  )}
@@ -259,51 +270,53 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
259
270
  }
260
271
  }}
261
272
  className="form-input !pr-[36px] Search-field"
262
- placeholder="Search"
273
+ placeholder={
274
+ fieldConfig.dropdownFieldConfig?.placeHolder || "Search"
275
+ }
263
276
  value={query}
264
277
  autoFocus
265
278
  type="text"
266
279
  />
267
- <div className="pointer-events-none absolute inset-y-0 right-0 flex items-center mr-3">
268
- <svg
269
- xmlns="http://www.w3.org/2000/svg"
270
- viewBox="0 0 16 16"
271
- fill="currentColor"
272
- aria-hidden="true"
273
- data-slot="icon"
274
- className="h-5 w-5 text-gray-400"
275
- >
276
- <path
277
- fillRule="evenodd"
278
- d="M9.965 11.026a5 5 0 1 1 1.06-1.06l2.755 2.754a.75.75 0 1 1-1.06 1.06l-2.755-2.754ZM10.5 7a3.5 3.5 0 1 1-7 0 3.5 3.5 0 0 1 7 0Z"
279
- clipRule="evenodd"
280
- ></path>
281
- </svg>
282
- </div>
280
+ {!fieldConfig.dropdownFieldConfig?.hideIcon && (
281
+ <div className="pointer-events-none absolute inset-y-0 right-0 flex items-center mr-3">
282
+ <svg
283
+ xmlns="http://www.w3.org/2000/svg"
284
+ viewBox="0 0 16 16"
285
+ fill="currentColor"
286
+ aria-hidden="true"
287
+ data-slot="icon"
288
+ className="h-5 w-5 text-gray-400"
289
+ >
290
+ <path
291
+ fillRule="evenodd"
292
+ d="M9.965 11.026a5 5 0 1 1 1.06-1.06l2.755 2.754a.75.75 0 1 1-1.06 1.06l-2.755-2.754ZM10.5 7a3.5 3.5 0 1 1-7 0 3.5 3.5 0 0 1 7 0Z"
293
+ clipRule="evenodd"
294
+ ></path>
295
+ </svg>
296
+ </div>
297
+ )}
283
298
  </div>
284
299
  )}
285
300
 
286
- <div className="form-listbox-options-container">
287
- {loading ? (
301
+ <div className="form-listbox-options-container">
302
+ {loading ? (
288
303
  <div className="form-listbox-option">
289
304
  <div className="flex items-center justify-center p-3">
290
305
  <LoaderWithText />
291
306
  </div>
292
307
  </div>
293
- ) : (
308
+ ) : (
294
309
  renderList
295
- )}
310
+ )}
296
311
 
297
- {Object.keys(groupedOptions).length > 0 &&
298
- Object.keys(groupedOptions).map((groupName) => (
299
- <div key={groupName}>
300
- <h2 className="pb-1 pt-2 font-bold">{groupName}</h2>
301
- {groupedOptions[groupName].map(renderOption)}
302
- </div>
303
- ))}
312
+ {Object.keys(groupedOptions).length > 0 &&
313
+ Object.keys(groupedOptions).map((groupName) => (
314
+ <div key={groupName}>
315
+ <h2 className="pb-1 pt-2 font-bold">{groupName}</h2>
316
+ {groupedOptions[groupName].map(renderOption)}
317
+ </div>
318
+ ))}
304
319
  </div>
305
-
306
-
307
320
  </ListboxOptions>
308
321
  );
309
322
  }