@engagebay/engagebay-form-module 1.0.0-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.
Files changed (50) hide show
  1. package/README.md +126 -0
  2. package/link.sh +2 -0
  3. package/package.json +30 -0
  4. package/src/api/index.ts +25 -0
  5. package/src/form/Form.tsx +157 -0
  6. package/src/form/FormField.tsx +80 -0
  7. package/src/form/FormFieldUtils.ts +241 -0
  8. package/src/form/FormFields.tsx +41 -0
  9. package/src/form/context/FormContext.tsx +66 -0
  10. package/src/form/formfields/ArrayField.tsx +169 -0
  11. package/src/form/formfields/BusinessHoursField.tsx +204 -0
  12. package/src/form/formfields/CheckboxButtonsField.tsx +97 -0
  13. package/src/form/formfields/CheckboxField.tsx +118 -0
  14. package/src/form/formfields/ColorPickerField.tsx +59 -0
  15. package/src/form/formfields/ComboMultiSelect.tsx +290 -0
  16. package/src/form/formfields/ComboSelect.tsx +278 -0
  17. package/src/form/formfields/DatePickerField.tsx +89 -0
  18. package/src/form/formfields/DateRangePickerField.tsx +104 -0
  19. package/src/form/formfields/DynamicMultiSelect.tsx +189 -0
  20. package/src/form/formfields/DynamicSelect.tsx +187 -0
  21. package/src/form/formfields/Error.tsx +15 -0
  22. package/src/form/formfields/ErrorContextHandler.tsx +77 -0
  23. package/src/form/formfields/FileUploadField.tsx +196 -0
  24. package/src/form/formfields/IframeField.tsx +65 -0
  25. package/src/form/formfields/InputField.tsx +67 -0
  26. package/src/form/formfields/InputGroupField.tsx +44 -0
  27. package/src/form/formfields/MultipleSelectField.tsx +98 -0
  28. package/src/form/formfields/NumberField.tsx +61 -0
  29. package/src/form/formfields/PasswordField.tsx +93 -0
  30. package/src/form/formfields/PhoneNumberField.tsx +163 -0
  31. package/src/form/formfields/RadioField.tsx +104 -0
  32. package/src/form/formfields/RadioGroupComponent.tsx +94 -0
  33. package/src/form/formfields/RangeField.tsx +53 -0
  34. package/src/form/formfields/SelectField.tsx +82 -0
  35. package/src/form/formfields/SwitchField.tsx +131 -0
  36. package/src/form/formfields/TextAreaField.tsx +48 -0
  37. package/src/form/formfields/TimeField.tsx +53 -0
  38. package/src/form/formfields/Typeahead.tsx +211 -0
  39. package/src/form/formfields/TypeaheadMultiSelect.tsx +203 -0
  40. package/src/form/formfields/UrlField.tsx +53 -0
  41. package/src/form/hooks/useDynamicReducer.tsx +42 -0
  42. package/src/form/schema/CustomValidators.ts +63 -0
  43. package/src/form/schema/FormFieldSchema.ts +342 -0
  44. package/src/form/util/RenderFormField.tsx +149 -0
  45. package/src/form/util/RenderListOptions.tsx +424 -0
  46. package/src/form/util/index.ts +185 -0
  47. package/src/util/LoaderWithText.tsx +28 -0
  48. package/src/util/svg/HELPER_ICONS.ts +16 -0
  49. package/src/util/svg/SVGIcon.tsx +23 -0
  50. package/tsconfig.json +25 -0
@@ -0,0 +1,203 @@
1
+ import React, {
2
+ useCallback,
3
+ useContext,
4
+ useEffect,
5
+ useRef,
6
+ useState,
7
+ } from "react";
8
+
9
+ import { Listbox, ListboxButton } from "@headlessui/react";
10
+ import { RegisterOptions } from "react-hook-form";
11
+
12
+ import { FormContext } from "../context/FormContext";
13
+ import { getListOption, getListOptions } from "../FormFieldUtils";
14
+ import {
15
+ FieldOptionsSchema,
16
+ FormFieldComponentPropSchema,
17
+ FormFieldType,
18
+ } from "../schema/FormFieldSchema";
19
+ import { handleChange, registerFormField } from "../util";
20
+ import RenderFormField from "../util/RenderFormField";
21
+ import RenderListOptions, {
22
+ renderListBoxValue,
23
+ } from "../util/RenderListOptions";
24
+ // import _ from "lodash";
25
+ import axios from "axios";
26
+ import { reachoAPI } from "../../api";
27
+
28
+ const TypeaheadMultiSelect: React.FC<FormFieldComponentPropSchema> = (
29
+ props: FormFieldComponentPropSchema
30
+ ) => {
31
+ const dynamicSelectRef = useRef<HTMLUListElement>(null);
32
+ const formContext = useContext(FormContext);
33
+ let registerOptions: RegisterOptions = registerFormField(props.fieldConfig);
34
+ let hookProps = formContext.register(
35
+ 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
+ );
53
+ }
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
+ );
103
+
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
136
+ );
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>
193
+ );
194
+ };
195
+ if (props.fieldConfig.hideWhenNoResults && listOptions.length == 0) {
196
+ return <></>;
197
+ }
198
+
199
+ return (
200
+ <RenderFormField fieldConfig={props.fieldConfig} getInput={getInput} />
201
+ );
202
+ };
203
+ export default TypeaheadMultiSelect;
@@ -0,0 +1,53 @@
1
+ import React, { useContext } from "react";
2
+ import { RegisterOptions } from "react-hook-form";
3
+ import { FormContext } from "../context/FormContext";
4
+ import { FormFieldComponentPropSchema } from "../schema/FormFieldSchema";
5
+ import { Input } from "@headlessui/react";
6
+ import RenderFormField from "../util/RenderFormField";
7
+ import { handleChange, registerFormField } from "../util";
8
+
9
+ const UrlField: React.FC<FormFieldComponentPropSchema> = (
10
+ props: FormFieldComponentPropSchema
11
+ ) => {
12
+ const formContext = useContext(FormContext);
13
+
14
+ let registerOptions: RegisterOptions = registerFormField(props.fieldConfig);
15
+
16
+ let hookProps = formContext.register(props.fieldConfig.name, registerOptions);
17
+
18
+ /**
19
+ * Raw Form Field
20
+ */
21
+ const getInput = () => {
22
+ return (
23
+ <Input
24
+ {...hookProps}
25
+ type="url"
26
+ placeholder={props.fieldConfig?.placeholder}
27
+ readOnly={props.fieldConfig?.readOnly}
28
+ disabled={props.fieldConfig?.disabled}
29
+ autoComplete={props.fieldConfig?.autoComplete}
30
+ defaultValue={props.fieldConfig.defaultValue as string}
31
+ className={`form-input ${
32
+ props.fieldConfig.customClassNames?.fieldClassName || "flex-1"
33
+ }`}
34
+ onKeyDown={(e) => {
35
+ props.fieldConfig.onKeyDown && props.fieldConfig.onKeyDown(e);
36
+ }}
37
+ onChange={(e) => {
38
+ handleChange(
39
+ e.target.value,
40
+ formContext,
41
+ props.fieldConfig,
42
+ props.onChange
43
+ );
44
+ }}
45
+ />
46
+ );
47
+ };
48
+
49
+ return (
50
+ <RenderFormField fieldConfig={props.fieldConfig} getInput={getInput} />
51
+ );
52
+ };
53
+ export default UrlField;
@@ -0,0 +1,42 @@
1
+ import { useContext, useEffect } from "react";
2
+ import { ReactReduxContext, useSelector } from "react-redux";
3
+ import {
4
+ ReduceAndSagaSchema,
5
+ StoreStateSchema,
6
+ } from "../schema/FormFieldSchema";
7
+
8
+ interface useDymanicReducerProps {
9
+ reducerConfig: ReduceAndSagaSchema;
10
+ }
11
+
12
+ function useDymanicReducer<T>({ reducerConfig }: useDymanicReducerProps) {
13
+ let state = useSelector(
14
+ (state: any) =>
15
+ state[
16
+ reducerConfig.reduceName as typeof reducerConfig.reduceName
17
+ ] as any as StoreStateSchema<T>
18
+ );
19
+
20
+ const redux = useContext(ReactReduxContext);
21
+
22
+ let store = redux && (redux.store as any);
23
+
24
+ useEffect(() => {
25
+ if (!state) {
26
+ inject();
27
+ }
28
+ }, []);
29
+
30
+ const inject = () => {
31
+ store?.injectReducer?.({
32
+ [reducerConfig.reduceName]: reducerConfig.reducer,
33
+ });
34
+ store?.injectSaga?.(reducerConfig.reduceName, reducerConfig.saga);
35
+ };
36
+
37
+ return {
38
+ state: state as StoreStateSchema<T>,
39
+ };
40
+ }
41
+
42
+ export default useDymanicReducer;
@@ -0,0 +1,63 @@
1
+
2
+ export const emailIdsValidator = (value: string) => {
3
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
4
+
5
+ const emailArray = value.split(',').filter(email => email.trim() !== '');
6
+
7
+ // Check for duplicate emails
8
+ const uniqueEmails = new Set<string>();
9
+ for (const email of emailArray) {
10
+ const trimmedEmail = email.trim();
11
+
12
+ // If any email is not valid, return an error message
13
+ if (!emailRegex.test(trimmedEmail)) {
14
+ return `Invalid email address: ${trimmedEmail}`;
15
+ }
16
+
17
+ // If the email is a duplicate, return an error message
18
+ if (uniqueEmails.has(trimmedEmail)) {
19
+ return `Duplicate email address: ${trimmedEmail}`;
20
+ }
21
+
22
+ uniqueEmails.add(trimmedEmail);
23
+ }
24
+ return undefined;
25
+
26
+ }
27
+
28
+ export const queryValidator = (value: string, formValues: any) => {
29
+ // Get the length of the collection
30
+ var max_number_allowed = formValues.rules.length; // $(".chained-item.well").length;
31
+ // var regex = /(\d+|and|or|\(|\))/g;
32
+ var valueArray = value.split(/(\(|\)|\s+)/).filter(function (token) {
33
+ return token.trim() !== ""
34
+ });
35
+ var preCount = 0, postCount = 0, isValid = true;
36
+
37
+ if (valueArray.length <= 2) {
38
+ return 'The Query is too short to be valid';
39
+ }
40
+
41
+ if (isNaN(parseInt(valueArray[0])) && (valueArray[0] !== '(')) {
42
+ return "Invalid start element of query. Should start with number.";
43
+ }
44
+
45
+ if (valueArray[valueArray.length - 1] !== ')' && isNaN(parseInt(valueArray[valueArray.length - 1]))) {
46
+ return "Invalid end element of query, Should end with number or closing parenthesis";
47
+ }
48
+
49
+ for (var i = 0; i < valueArray.length; i++) {
50
+ if (parseInt(valueArray[i]) > max_number_allowed) {
51
+ return "Query values exceeded maxing number of rules";
52
+ }
53
+ if (valueArray[i] === ')') {
54
+ preCount++;
55
+ } else if (valueArray[i] === '(') {
56
+ postCount++;
57
+ } else if (isNaN(parseInt(valueArray[i])) && !['and', 'or'].includes(valueArray[i].toLowerCase())) {
58
+ isValid = false;
59
+ }
60
+ }
61
+ // Input meets all criteria
62
+ return (preCount === postCount && isValid) ? undefined : "Please enter a valid query. The expression should contain only 'and' and 'or' words, numbers from 1 to the maximum allowed value, and have balanced parentheses."
63
+ }
@@ -0,0 +1,342 @@
1
+ import { ActionCreatorWithPayload, Reducer } from "@reduxjs/toolkit";
2
+ import { PropsWithChildren, ReactNode } from "react";
3
+ import {
4
+ UseFieldArrayAppend,
5
+ UseFieldArrayPrepend,
6
+ UseFieldArrayRemove,
7
+ UseFormGetValues,
8
+ UseFormSetValue,
9
+ ValidateResult,
10
+ } from "react-hook-form";
11
+ import { Saga } from "redux-saga";
12
+ import { emailIdsValidator, queryValidator } from "./CustomValidators";
13
+
14
+ export enum FormFieldType {
15
+ INPUT = "INPUT",
16
+ PASSWORD = "PASSWORD",
17
+
18
+ MULTI_SELECT = "MULTI_SELECT",
19
+ SELECT = "SELECT",
20
+
21
+ CHECKBOX = "CHECKBOX",
22
+ CHECKBOXBUTTONS = "CHECKBOXBUTTONS",
23
+ ARRAY = "ARRAY",
24
+ TIME = "TIME",
25
+ NUMBER = "NUMBER",
26
+ INPUT_GROUP = "INPUT_GROUP",
27
+ FILTER = "FILTER",
28
+ VIEW_FILTER = "VIEW_FILTER",
29
+ RADIO = "RADIO",
30
+ TEXTAREA = "TEXTAREA",
31
+ DATE_PICKER = "DATE_PICKER",
32
+ DATE_RANGE_PICKER = "DATE_RANGE_PICKER",
33
+
34
+ DYNAMIC_SELECT = "DYNAMIC_SELECT",
35
+ RADIO_GROUP = "RADIO_GROUP",
36
+ DYNAMIC_MULTI_SELECT = "DYNAMIC_MULTI_SELECT",
37
+
38
+ TYPEAHEAD = "TYPEAHEAD",
39
+ TYPEAHEAD_MULTI_SELECT = "TYPEAHEAD_MULTI_SELECT",
40
+ PHONE_NUMBER_INPUT = "PHONE_NUMBER_INPUT",
41
+ SWITCH = "SWITCH",
42
+ RANGE = "RANGE",
43
+ FILE_UPLOAD = "FILE_UPLOAD",
44
+ BROWSE_BOX = "BROWSE_BOX",
45
+ COLOR_PICKER = "COLOR_PICKER",
46
+ URL = "URL",
47
+ PROFILE_PICKER = "PROFILE_PICKER",
48
+ COMBO_SELECT = "COMBO_SELECT",
49
+ COMBO_MULTI_SELECT = "COMBO_MULTI_SELECT",
50
+ DATE = "DATE",
51
+ BUSINESS_HOURS = "BUSINESS_HOURS",
52
+
53
+ IFRAME = "IFRAME",
54
+ }
55
+
56
+ export enum FormFieldSubType {
57
+ TEXT,
58
+ EMAIL,
59
+ }
60
+
61
+ export interface FormFieldComponentPropSchema {
62
+ fieldConfig: FormFieldSchema;
63
+ onChange?: (data: any) => void;
64
+ }
65
+
66
+ export type formContainerProps = PropsWithChildren<{}>;
67
+
68
+ export enum OutputFormatType {
69
+ STRING,
70
+ ARRAY,
71
+ }
72
+
73
+ export type DropdownFieldConfig = {
74
+ showSelectedCount: boolean;
75
+ showDeleteOption: boolean;
76
+ showCreateOption: boolean;
77
+ showSearchBox: boolean;
78
+ createLabelText?: string;
79
+ isCaseSensitive?: boolean;
80
+ };
81
+
82
+ export type FormFieldSchema = {
83
+ /**
84
+ * Required attributes
85
+ */
86
+ name: string;
87
+ required: boolean;
88
+ formFieldType: FormFieldType;
89
+
90
+ /**
91
+ * Html attributes
92
+ */
93
+ label?: string;
94
+ readOnly?: boolean;
95
+ placeholder?: string;
96
+ helpText?: string;
97
+ disabled?: boolean;
98
+ autoComplete?: "on" | "off" | string;
99
+ maxLength?: number;
100
+ minLength?: number;
101
+ max?: number;
102
+ min?: number;
103
+ rows?: number;
104
+ defaultValue?: string | string[] | {} | boolean;
105
+ options?: FieldOptionsSchema[];
106
+ minDate?: Date | null | undefined;
107
+ maxDate?: Date | null | undefined;
108
+ // ref?:any
109
+
110
+ /**
111
+ * Custom Handling functions
112
+ */
113
+ onChange?: (data: any) => void;
114
+ onKeyDown?: (event: any) => void;
115
+ fetchCallback?: (data: any) => void;
116
+ onCreateTrigger?: (data: any, createCallBack: (data: any) => void) => void;
117
+
118
+ /**
119
+ * Custom attributes
120
+ */
121
+ validateSpaces?: boolean;
122
+ ignoreFetchValue?: boolean;
123
+ decimalAllowed?: boolean;
124
+ errorMessage?: string;
125
+ submitOnChange?: boolean;
126
+ formFieldPattern?: FormFieldPatternsImpl[];
127
+ fetchUrl?: string;
128
+ postUrl?: string;
129
+ fileAccept?: string;
130
+ icon?: ReactNode;
131
+ outputFormat?: OutputFormatType;
132
+ children?: FormFieldSchema[];
133
+ defaultOptions?: FieldOptionsSchema[];
134
+ optionsConfig?: OptionMappingConfig;
135
+ mapperType?: string;
136
+ forceUpdate?: boolean;
137
+ disableHeaderInFetch?: boolean;
138
+ dropdownFieldConfig?: DropdownFieldConfig;
139
+
140
+ /**
141
+ * Redux configuration attribute
142
+ */
143
+ store?: ReduceAndSagaSchema;
144
+ customUrlForRedux?: string;
145
+
146
+ /**
147
+ * Customise the default styles
148
+ */
149
+ customClassNames?: {
150
+ fieldClassName?: string;
151
+ labelClassName?: string;
152
+ wrapperClassName?: string;
153
+ optionClassName?: string;
154
+ optionsWrapperClassName?: string;
155
+ };
156
+ align?: FieldAlignType;
157
+ disableDefaultWrapper?: boolean;
158
+ hideWhenNoResults?: boolean;
159
+ fieldOptionWrapper?: React.FC<{ data: any; selected?: boolean }>;
160
+ seperateGroupElements?: boolean; // for input group component @component -> InputGroupField.tsx
161
+
162
+ /**
163
+ * Wrapper attributes
164
+ */
165
+ html?: React.FC<formContainerProps>;
166
+ fieldContainer?: React.FC<{}>;
167
+ wrapper?: React.FC<PropsWithChildren>;
168
+ arrayWrapper?: React.FC<
169
+ PropsWithChildren<{
170
+ getValues: UseFormGetValues<any>;
171
+ append: UseFieldArrayAppend<any, string>;
172
+ prepend: UseFieldArrayPrepend<any, string>;
173
+ remove: UseFieldArrayRemove;
174
+ }>
175
+ >;
176
+ arrayIndexWrapper?: React.FC<
177
+ PropsWithChildren<{
178
+ append: UseFieldArrayAppend<any, string>;
179
+ prepend: UseFieldArrayPrepend<any, string>;
180
+ remove: UseFieldArrayRemove;
181
+ index: number;
182
+ getValues: UseFormGetValues<any>;
183
+ setValue: UseFormSetValue<any>;
184
+ length: number;
185
+ mappedName: string;
186
+ childByFieldName: (
187
+ fieldName: string,
188
+ mapperType?: string
189
+ ) => JSX.Element | JSX.Element[];
190
+ }>
191
+ >;
192
+ };
193
+ export type FieldOptionsSchema = {
194
+ value: string | any;
195
+ label: string | any;
196
+ isDisabled?: boolean;
197
+ helpText?: string;
198
+ groupName?: string;
199
+ icon?: React.ReactNode;
200
+ };
201
+
202
+ export type OptionMappingConfig = {
203
+ valueKey: string;
204
+ labelKey: string;
205
+ listItemKey?: string;
206
+ groupKey?: string;
207
+ disabledKey?: string;
208
+ };
209
+
210
+ export enum FieldAlignType {
211
+ HORIZONTAL,
212
+ VERTICAL,
213
+ }
214
+
215
+ export interface FormFieldPatterns {
216
+ getMessage(): string;
217
+
218
+ getPattern(): RegExp | undefined;
219
+
220
+ getValidate(): (value: string, formValues: any) => ValidateResult;
221
+ }
222
+
223
+ export class FormFieldPatternsImpl implements FormFieldPatterns {
224
+ public static ALL_PATTERNS: { [name: string]: FormFieldPatterns } = {};
225
+
226
+ static readonly REQUIRED = new FormFieldPatternsImpl(
227
+ "REQUIRED",
228
+ "This field is required",
229
+ /abc/g
230
+ );
231
+ static readonly EMAIL = new FormFieldPatternsImpl(
232
+ "EMAIL",
233
+ "Please enter a valid email address",
234
+ /^([A-Za-z0-9\._%+\-]+@[A-Za-z0-9\.\-]+\.[A-Za-z]{2,})$/
235
+ );
236
+ // static readonly PASSWORD = new FormFieldPatternsImpl(
237
+ // "PASSWORD",
238
+ // "Please enter a valid password",
239
+ // /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/
240
+ // );
241
+ static readonly PASSWORD = new FormFieldPatternsImpl(
242
+ "PASSWORD",
243
+ "Password must be at least 8 characters long and include an uppercase letter, a lowercase letter, a digit, and a special character (!, @, #, $, %, ^, &, *).",
244
+ /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*])[A-Za-z\d!@#$%^&*]{8,}$/
245
+ );
246
+
247
+ static readonly URL = new FormFieldPatternsImpl(
248
+ "URL",
249
+ "Please enter a valid URL (e.g., http://example.com)",
250
+ /^(https?:\/\/)?(www\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(\/[a-zA-Z0-9\-._~:/?#[\]]*)?$/
251
+ );
252
+
253
+ static readonly CUSTOM_QUERY = new FormFieldPatternsImpl(
254
+ "CUSTOM_QUERY",
255
+ "",
256
+ undefined,
257
+ queryValidator
258
+ );
259
+ static readonly EMAIL_ID_COMA_SEPARATE = new FormFieldPatternsImpl(
260
+ "EMAIL_ID_COMA_SEPARATE",
261
+ "",
262
+ undefined,
263
+ emailIdsValidator
264
+ );
265
+ static readonly NO_SPECIAL_CHAR_EXCEPT_UNDERSCORE_LOWERCASE =
266
+ new FormFieldPatternsImpl(
267
+ "NO_SPECIAL_CHAR_EXCEPT_UNDERSCORE_LOWERCASE",
268
+ "Please enter a value with only lowercase letters, numbers, and underscores (no spaces or other special characters).",
269
+ /^[a-z0-9_]+$/
270
+ );
271
+ static readonly SHOPIFY_URL_PATTERN = new FormFieldPatternsImpl(
272
+ "SHOPIFY_URL_PATTERN",
273
+ "Please enter a valid shopify url.",
274
+ /^https?\:\/\/[a-zA-Z0-9][a-zA-Z0-9\-]*\.myshopify\.com\/?/
275
+ );
276
+ static readonly MFA_CODE_PATTERN = new FormFieldPatternsImpl(
277
+ "MFA_CODE_PATTERN",
278
+ "Please enter a valid MFA code.",
279
+ /^[0-9]{6}$/
280
+ );
281
+ static readonly SPACES_NOT_ALLOWED = new FormFieldPatternsImpl(
282
+ "SPACES_NOT_ALLOWED",
283
+ "Please enter a value without spaces.",
284
+ /^[^\s]+$/
285
+ );
286
+ static readonly PHONE_NUMBER = new FormFieldPatternsImpl(
287
+ "PHONE_NUMBER",
288
+ "Please enter a valid phone number.",
289
+ /^(?:(?:\+|0{0,2})91(\s*[\ -]\s*)?|[0]?)?[456789]\d{9}|(\d[ -]?){10}\d$/
290
+ );
291
+
292
+ static readonly DOMAIN_NAME_PATTERN = new FormFieldPatternsImpl(
293
+ "DOMAIN_NAME_PATTERN",
294
+ "Please enter a valid domain name.",
295
+ /^[a-z0-9-]{1,63}(\.[a-z0-9-]{1,63})+$/
296
+ );
297
+ static readonly LOWERCASE_UNDERSCORE_WITHOUT_LEADING_NUMBER =
298
+ new FormFieldPatternsImpl(
299
+ "LOWERCASE_UNDERSCORE_WITHOUT_LEADING_NUMBER",
300
+ "Please enter a value with only lowercase letters, numbers, and underscores. The first character cannot be a number or any other special character except underscore.",
301
+ /^[a-z][a-z0-9_]*$/
302
+ );
303
+
304
+ private constructor(
305
+ private readonly patternName: string,
306
+ private readonly message: string,
307
+ private readonly pattern: RegExp | undefined,
308
+ private readonly validator?: (a: string, b: any) => ValidateResult
309
+ ) {
310
+ FormFieldPatternsImpl.ALL_PATTERNS[patternName] = this;
311
+ }
312
+
313
+ getMessage(): string {
314
+ return this.message;
315
+ }
316
+
317
+ getPattern(): RegExp | undefined {
318
+ return this.pattern;
319
+ }
320
+
321
+ getValidate(): any {
322
+ return this.validator;
323
+ }
324
+ }
325
+
326
+ export type ReducerSchema = {
327
+ reduceName: string;
328
+ action: {
329
+ [key: string]: string;
330
+ };
331
+ };
332
+
333
+ export type ReduceAndSagaSchema = ReducerSchema & {
334
+ saga: Saga;
335
+ reducer: Reducer;
336
+ initialFetchReducerAction?: ActionCreatorWithPayload<any>;
337
+ };
338
+
339
+ export type StoreStateSchema<T> = {
340
+ data: T | T[] | undefined;
341
+ loading: boolean;
342
+ };