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

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
@@ -159,7 +166,9 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
159
166
  disabled={option.isDisabled}
160
167
  onClick={() => setTimeout(resetToDefault, 300)}
161
168
  className={`form-listbox-option ${
162
- selected ? " bg-gray-100 text-gray-900" : "bg-white text-gray-700"
169
+ selected
170
+ ? " bg-gray-100 text-gray-900"
171
+ : "hover:bg-gray-100 text-gray-700"
163
172
  }`}
164
173
  value={option.value}
165
174
  >
@@ -178,7 +187,8 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
178
187
  )}
179
188
  {option.label}
180
189
  </span>
181
- {isTypeahead ? (
190
+ {isTypeahead &&
191
+ !fieldConfig.dropdownFieldConfig?.isSuggestionBox ? (
182
192
  <input
183
193
  type="checkbox"
184
194
  className="form-checkbox"
@@ -195,7 +205,7 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
195
205
  )}
196
206
  </div>
197
207
  {option.helpText && (
198
- <div className="mt-0 text-sm text-gray-500 font-normal truncate w-full !max-w-[150px]">
208
+ <div className="mt-0 text-xs text-gray-500 font-normal truncate w-full !max-w-[150px]">
199
209
  {option.helpText}
200
210
  </div>
201
211
  )}
@@ -259,51 +269,53 @@ const RenderListOptions = forwardRef<HTMLUListElement, RenderListOptionsProps>(
259
269
  }
260
270
  }}
261
271
  className="form-input !pr-[36px] Search-field"
262
- placeholder="Search"
272
+ placeholder={
273
+ fieldConfig.dropdownFieldConfig?.placeHolder || "Search"
274
+ }
263
275
  value={query}
264
276
  autoFocus
265
277
  type="text"
266
278
  />
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>
279
+ {!fieldConfig.dropdownFieldConfig?.hideIcon && (
280
+ <div className="pointer-events-none absolute inset-y-0 right-0 flex items-center mr-3">
281
+ <svg
282
+ xmlns="http://www.w3.org/2000/svg"
283
+ viewBox="0 0 16 16"
284
+ fill="currentColor"
285
+ aria-hidden="true"
286
+ data-slot="icon"
287
+ className="h-5 w-5 text-gray-400"
288
+ >
289
+ <path
290
+ fillRule="evenodd"
291
+ 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"
292
+ clipRule="evenodd"
293
+ ></path>
294
+ </svg>
295
+ </div>
296
+ )}
283
297
  </div>
284
298
  )}
285
299
 
286
- <div className="form-listbox-options-container">
287
- {loading ? (
300
+ <div className="form-listbox-options-container">
301
+ {loading ? (
288
302
  <div className="form-listbox-option">
289
303
  <div className="flex items-center justify-center p-3">
290
304
  <LoaderWithText />
291
305
  </div>
292
306
  </div>
293
- ) : (
307
+ ) : (
294
308
  renderList
295
- )}
309
+ )}
296
310
 
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
- ))}
311
+ {Object.keys(groupedOptions).length > 0 &&
312
+ Object.keys(groupedOptions).map((groupName) => (
313
+ <div key={groupName}>
314
+ <h2 className="pb-1 pt-2 font-bold">{groupName}</h2>
315
+ {groupedOptions[groupName].map(renderOption)}
316
+ </div>
317
+ ))}
304
318
  </div>
305
-
306
-
307
319
  </ListboxOptions>
308
320
  );
309
321
  }