@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.
package/link.sh CHANGED
@@ -1,2 +1,2 @@
1
- sudo npm link ../../../reacho-frontend/node_modules/react
1
+ sudo npm link /home/eb137/IdeaProjects/engagebay-frontend/node_modules/react
2
2
  npm link
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@engagebay/engagebay-form-module",
3
- "version": "1.0.1",
3
+ "version": "1.0.2-beta.1",
4
4
  "description": "Provide base form components to reacho",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1"
@@ -26,5 +26,21 @@
26
26
  "react-redux": ">=8.0.4",
27
27
  "react-tailwindcss-datepicker": ">=1.6.6",
28
28
  "redux-saga": ">=1.3.0"
29
+ },
30
+ "devDependencies": {
31
+ "@headlessui/react": ">=2.1.2",
32
+ "@heroicons/react": ">=2.1.5",
33
+ "@reduxjs/toolkit": ">=2.2.7",
34
+ "@tippyjs/react": ">=4.2.6",
35
+ "@types/lodash": ">=4.17.7",
36
+ "@types/react-redux": ">=7.1.33",
37
+ "axios": ">=1.7.2",
38
+ "clsx": ">=2.1.1",
39
+ "moment": ">=2.30.1",
40
+ "react-hook-form": ">=7.52.1",
41
+ "react-popper": ">=2.3.0",
42
+ "react-redux": ">=8.0.4",
43
+ "react-tailwindcss-datepicker": ">=1.6.6",
44
+ "redux-saga": ">=1.3.0"
29
45
  }
30
46
  }
@@ -0,0 +1,53 @@
1
+ import React, { createContext, useContext, ReactNode } from 'react';
2
+ import { AxiosInstance } from 'axios';
3
+
4
+ interface AxiosConfigContextType {
5
+ axiosInstance: AxiosInstance;
6
+ baseURL?: string;
7
+ timeout?: number;
8
+ headers?: Record<string, string>;
9
+ }
10
+
11
+ const AxiosConfigContext = createContext<AxiosConfigContextType | null>(null);
12
+
13
+ interface AxiosConfigProviderProps {
14
+ children: ReactNode;
15
+ axiosInstance: AxiosInstance;
16
+ baseURL?: string;
17
+ timeout?: number;
18
+ headers?: Record<string, string>;
19
+ }
20
+
21
+ export const AxiosConfigProvider: React.FC<AxiosConfigProviderProps> = ({
22
+ children,
23
+ axiosInstance,
24
+ baseURL,
25
+ timeout,
26
+ headers,
27
+ }) => {
28
+ const config: AxiosConfigContextType = {
29
+ axiosInstance,
30
+ baseURL,
31
+ timeout,
32
+ headers,
33
+ };
34
+
35
+ return (
36
+ <AxiosConfigContext.Provider value={config}>
37
+ {children}
38
+ </AxiosConfigContext.Provider>
39
+ );
40
+ };
41
+
42
+ export const useAxiosConfig = (): AxiosConfigContextType => {
43
+ const context = useContext(AxiosConfigContext);
44
+ if (!context) {
45
+ throw new Error('useAxiosConfig must be used within an AxiosConfigProvider');
46
+ }
47
+ return context;
48
+ };
49
+
50
+ // Hook for optional usage (doesn't throw error if not provided)
51
+ export const useAxiosConfigOptional = (): AxiosConfigContextType | null => {
52
+ return useContext(AxiosConfigContext);
53
+ };
package/src/api/index.ts CHANGED
@@ -1,4 +1,5 @@
1
- import axios, { AxiosRequestConfig } from "axios";
1
+ import axios, {AxiosInstance, AxiosRequestConfig} from "axios";
2
+ import {FormFieldSchema} from "../form/schema/FormFieldSchema";
2
3
 
3
4
  const BASE_API: AxiosRequestConfig = {
4
5
  baseURL:
@@ -23,3 +24,16 @@ reachoAPI.interceptors.request.use(
23
24
  return Promise.reject(error);
24
25
  }
25
26
  );
27
+
28
+ export const getAxiosInstance = (axiosInstance: AxiosInstance | undefined, fieldConfig: FormFieldSchema): AxiosInstance => {
29
+
30
+ if (fieldConfig && fieldConfig.axiosInstance) {
31
+ return fieldConfig.axiosInstance;
32
+ }
33
+
34
+ if (axiosInstance) {
35
+ return axiosInstance;
36
+ }
37
+
38
+ return fieldConfig.disableHeaderInFetch ? axios : reachoAPI;
39
+ };
package/src/form/Form.tsx CHANGED
@@ -9,9 +9,11 @@ import {
9
9
  UseFormTrigger,
10
10
  UseFormUnregister,
11
11
  UseFormResetField,
12
+ Control,
12
13
  } from "react-hook-form";
13
14
  import { FormFieldSchema } from "./schema/FormFieldSchema";
14
15
  import { FormContext } from "./context/FormContext";
16
+ import { AxiosInstance } from 'axios';
15
17
 
16
18
  /**
17
19
  * Props for the Form component
@@ -26,6 +28,7 @@ import { FormContext } from "./context/FormContext";
26
28
  */
27
29
  type FormPropsSchema = {
28
30
  fieldSchema: FormFieldSchema[];
31
+ axiosInstance?: AxiosInstance
29
32
  formData: any;
30
33
  onError?: (error: any) => void;
31
34
  onSubmit?: (data: FormData) => void;
@@ -40,6 +43,7 @@ type FormPropsSchema = {
40
43
  trigger: UseFormTrigger<any>;
41
44
  unregister: UseFormUnregister<any>;
42
45
  resetField: UseFormResetField<any>;
46
+ control: Control<any, any, any>;
43
47
  }) => React.ReactNode;
44
48
  customClass?: string;
45
49
  };
@@ -132,6 +136,7 @@ export default function Form(props: FormPropsSchema): JSX.Element {
132
136
  setError,
133
137
  setFocus,
134
138
  unregister,
139
+ axiosInstance: props.axiosInstance,
135
140
  }}>
136
141
  {/* Form element */}
137
142
  <form
@@ -149,6 +154,7 @@ export default function Form(props: FormPropsSchema): JSX.Element {
149
154
  trigger,
150
155
  unregister,
151
156
  resetField,
157
+ control,
152
158
  })}
153
159
  </form>
154
160
  </FormContext.Provider>
@@ -17,6 +17,7 @@ import {
17
17
  UseFormWatch,
18
18
  } from "react-hook-form";
19
19
  import { FormFieldSchema } from "../schema/FormFieldSchema";
20
+ import { AxiosInstance } from "axios";
20
21
 
21
22
  export interface FormContextType {
22
23
  formFields: FormFieldSchema[];
@@ -43,6 +44,7 @@ export interface FormContextType {
43
44
  // control: Control<TFieldValues, TContext>;
44
45
  // register: UseFormRegister<TFieldValues>;
45
46
  setFocus: UseFormSetFocus<any>;
47
+ axiosInstance?: AxiosInstance;
46
48
  }
47
49
 
48
50
  export const FormContext = createContext({
@@ -1,204 +1,316 @@
1
1
  import {
2
- FieldAlignType,
3
- FieldOptionsSchema,
4
- FormFieldComponentPropSchema,
5
- FormFieldSchema,
6
- FormFieldType
2
+ FieldAlignType,
3
+ FieldOptionsSchema,
4
+ FormFieldComponentPropSchema,
5
+ FormFieldSchema,
6
+ FormFieldType,
7
7
  } from "../schema/FormFieldSchema";
8
- import React, {useCallback, useContext, useEffect, useMemo, useState} from "react";
9
- import {FormContext} from "../context/FormContext";
10
- import {RegisterOptions} from "react-hook-form";
11
- import {convertToTitleCase, registerFormField} from "../util";
8
+ import React, {
9
+ useCallback,
10
+ useContext,
11
+ useEffect,
12
+ useMemo,
13
+ useState,
14
+ } from "react";
15
+ import { FormContext } from "../context/FormContext";
16
+ import { RegisterOptions } from "react-hook-form";
17
+ import { convertToTitleCase, registerFormField } from "../util";
12
18
  import axios from "axios";
13
- import {reachoAPI} from "../../api";
14
19
  import SwitchField from "./SwitchField";
15
20
  import FormField from "../FormField";
16
21
  import RenderFormField from "../util/RenderFormField";
17
- import {TrashIcon, XMarkIcon} from "@heroicons/react/24/outline";
22
+ import { TrashIcon, XMarkIcon } from "@heroicons/react/24/outline";
18
23
  import Tippy from "@tippyjs/react";
24
+ import { set } from "lodash";
19
25
 
26
+ const defaultBusinessHours = {
27
+ MONDAY: {
28
+ enabledDay: true,
29
+ sessions: [
30
+ {
31
+ startTime: "10:00",
32
+ endTime: "18:00",
33
+ },
34
+ ],
35
+ },
36
+ TUESDAY: {
37
+ enabledDay: true,
38
+ sessions: [
39
+ {
40
+ startTime: "10:00",
41
+ endTime: "18:00",
42
+ },
43
+ ],
44
+ },
45
+ WEDNESDAY: {
46
+ enabledDay: true,
47
+ sessions: [
48
+ {
49
+ startTime: "10:00",
50
+ endTime: "18:00",
51
+ },
52
+ ],
53
+ },
54
+ THURSDAY: {
55
+ enabledDay: true,
56
+ sessions: [
57
+ {
58
+ startTime: "10:00",
59
+ endTime: "18:00",
60
+ },
61
+ ],
62
+ },
63
+ FRIDAY: {
64
+ enabledDay: true,
65
+ sessions: [
66
+ {
67
+ startTime: "10:00",
68
+ endTime: "18:00",
69
+ },
70
+ ],
71
+ },
72
+ SATURDAY: {
73
+ enabledDay: false,
74
+ sessions: [
75
+ {
76
+ startTime: "10:00",
77
+ endTime: "18:00",
78
+ },
79
+ ],
80
+ },
81
+ SUNDAY: {
82
+ enabledDay: false,
83
+ sessions: [
84
+ {
85
+ startTime: "10:00",
86
+ endTime: "18:00",
87
+ },
88
+ ],
89
+ },
90
+ };
20
91
 
21
- export const BusinessHoursField: React.FC<FormFieldComponentPropSchema> = (props: FormFieldComponentPropSchema) => {
22
- const formContext = useContext(FormContext);
23
- const [listOptions, setListOptions] = useState<any[]>([]);
24
- const [loading, setLoading] = useState<boolean>(false);
25
-
26
- let registerOptions: RegisterOptions = registerFormField(props.fieldConfig);
27
- const hookProps = useMemo(() => formContext.register(props.fieldConfig.name, registerOptions), [formContext, props.fieldConfig.name, registerOptions]);
28
-
29
- useEffect(() => {
30
- fetchData();
31
- }, []);
92
+ export const BusinessHoursField: React.FC<FormFieldComponentPropSchema> = (
93
+ props: FormFieldComponentPropSchema
94
+ ) => {
95
+ const formContext = useContext(FormContext);
96
+ const [listOptions, setListOptions] = useState<any>(defaultBusinessHours);
97
+ const [loading, setLoading] = useState<boolean>(false);
32
98
 
33
- const getTimeConfig = (mappedName: string): FormFieldSchema => {
34
- return {
35
- required: true,
36
- name: mappedName + ".sessions",
37
- wrapper: ({children}) => {
38
- return <div style={{border: '1px sold #ddd', padding: '0px'}}>{children}</div>;
39
- },
40
- arrayWrapper: ({children, append,getValues}) => {
41
- return (
42
- <>
43
- <div style={{border: '1px sold #ddd',}}>
44
- {children}
45
- </div>
46
- <div className='w-full sm:w-full text-end mr-[2.3em]'>
47
- <button type='button' className={`text-end text-primary cursor-pointer font-[13px] font-medium ${getValues(`${mappedName}.sessions`)?.length > 1 ? "mr-9" : ''}`}
48
- onClick={() => {
49
- const lastEndTime = getValues(`${mappedName}.sessions`)?.[getValues(`${mappedName}.sessions`).length - 1]?.endTime || '08:30';
99
+ let registerOptions: RegisterOptions = registerFormField(props.fieldConfig);
100
+ const hookProps = useMemo(
101
+ () => formContext.register(props.fieldConfig.name, registerOptions),
102
+ [formContext, props.fieldConfig.name, registerOptions]
103
+ );
50
104
 
51
- const newStartTime = lastEndTime; // Start new session at last session's end time
52
- const newEndTime = "23:00"; // Calculate end time, but cap at 23:00
105
+ useEffect(() => {
106
+ fetchData();
107
+ }, []);
53
108
 
54
- append({
55
- startTime: newStartTime,
56
- endTime: newEndTime,
57
- });
58
- }}
59
- >
60
- + Add
61
- </button>
62
- </div>
63
- </>
64
- );
65
- },
66
- arrayIndexWrapper: ({
67
- mappedName,
68
- getValues,
69
- children,
70
- childByFieldName,
71
- append,
72
- prepend,
73
- remove,
74
- index
75
- }) => {
76
- return (
77
- <div className="flex items-center gap-4 form-group !mb-2">
78
- <div>{childByFieldName('startTime')}</div>
79
- <p>To</p>
80
- <div>{childByFieldName('endTime')}</div>
81
- {index > 0 ?
82
- <button
83
- className="Button__StyledButton-sc-1l1v2a6-0 gVdTUT"
84
- type="button"
85
- onClick={() => {
86
- remove(index);
87
- }}
88
- >
89
- {/* <TrashIcon height={18} width={18} className=""/> */}
90
- <Tippy content="Delete">
91
- <XMarkIcon aria-hidden="true" height={18} width={18} className="" />
92
- </Tippy>
93
-
94
- </button> : <></>}
95
- </div>
109
+ const getTimeConfig = (mappedName: string): FormFieldSchema => {
110
+ return {
111
+ required: true,
112
+ name: mappedName + ".sessions",
113
+ wrapper: ({ children }) => {
114
+ return (
115
+ <div style={{ border: "1px sold #ddd", padding: "0px" }}>
116
+ {children}
117
+ </div>
118
+ );
119
+ },
120
+ arrayWrapper: ({ children, append, getValues }) => {
121
+ return (
122
+ <>
123
+ <div style={{ border: "1px sold #ddd" }}>{children}</div>
124
+ <div className="w-full sm:w-full text-end mr-[2.3em]">
125
+ <button
126
+ type="button"
127
+ className={`text-end text-primary cursor-pointer font-[13px] font-medium ${
128
+ getValues(`${mappedName}.sessions`)?.length > 1 ? "mr-9" : ""
129
+ }`}
130
+ onClick={() => {
131
+ const lastEndTime =
132
+ getValues(`${mappedName}.sessions`)?.[
133
+ getValues(`${mappedName}.sessions`).length - 1
134
+ ]?.endTime || "08:30";
96
135
 
97
- );
98
- },
99
- formFieldType: FormFieldType.ARRAY,
100
- align: FieldAlignType.HORIZONTAL,
101
- disableDefaultWrapper: true,
102
- defaultValue: {
103
- startTime: "08:30",
104
- endTime: "18:00"
105
- },
106
- children: [
107
- {
108
- required: false,
109
- formFieldType: FormFieldType.TIME,
110
- name: "startTime",
111
- disableDefaultWrapper:true,
112
- defaultValue: "08:30",
113
- customClassNames: {
114
- fieldClassName: "border-none bg-blue-100",
115
- },
116
- }, {
117
- required: false,
118
- formFieldType: FormFieldType.TIME,
119
- name: "endTime",
120
- disableDefaultWrapper: true,
121
- defaultValue: "18:00",
122
- customClassNames: {
123
- fieldClassName: "border-none bg-blue-100",
124
- },
125
- },
126
- ],
127
- }
128
- }
129
- ;
136
+ const newStartTime = lastEndTime; // Start new session at last session's end time
137
+ const newEndTime = "23:00"; // Calculate end time, but cap at 23:00
130
138
 
131
- const fetchData = useCallback(async () => {
132
- setLoading(true);
133
- // if (!props.fieldConfig.fetchUrl) return;
134
- let url = "/api/core/user-prefs/get-default-business-days";
135
- if (props.fieldConfig.fetchUrl) {
136
- url = props.fieldConfig.fetchUrl;
137
- }
138
- try {
139
- let response = await (props.fieldConfig.disableHeaderInFetch
140
- ? axios.get(url)
141
- : reachoAPI.get(url));
139
+ append({
140
+ startTime: newStartTime,
141
+ endTime: newEndTime,
142
+ });
143
+ }}
144
+ >
145
+ + Add
146
+ </button>
147
+ </div>
148
+ </>
149
+ );
150
+ },
151
+ arrayIndexWrapper: ({
152
+ mappedName,
153
+ getValues,
154
+ children,
155
+ childByFieldName,
156
+ append,
157
+ prepend,
158
+ remove,
159
+ index,
160
+ }) => {
161
+ return (
162
+ <div className="flex items-center gap-4 form-group !mb-2">
163
+ <div>{childByFieldName("startTime")}</div>
164
+ <p>To</p>
165
+ <div>{childByFieldName("endTime")}</div>
166
+ {index > 0 ? (
167
+ <button
168
+ className="Button__StyledButton-sc-1l1v2a6-0 gVdTUT"
169
+ type="button"
170
+ onClick={() => {
171
+ remove(index);
172
+ }}
173
+ >
174
+ {/* <TrashIcon height={18} width={18} className=""/> */}
175
+ <Tippy content="Delete">
176
+ <XMarkIcon
177
+ aria-hidden="true"
178
+ height={18}
179
+ width={18}
180
+ className=""
181
+ />
182
+ </Tippy>
183
+ </button>
184
+ ) : (
185
+ <></>
186
+ )}
187
+ </div>
188
+ );
189
+ },
190
+ formFieldType: FormFieldType.ARRAY,
191
+ align: FieldAlignType.HORIZONTAL,
192
+ disableDefaultWrapper: true,
193
+ defaultValue: {
194
+ startTime: "08:30",
195
+ endTime: "18:00",
196
+ },
197
+ children: [
198
+ {
199
+ required: false,
200
+ formFieldType: FormFieldType.TIME,
201
+ name: "startTime",
202
+ disableDefaultWrapper: true,
203
+ defaultValue: "08:30",
204
+ customClassNames: {
205
+ fieldClassName: "border-none bg-blue-100",
206
+ },
207
+ },
208
+ {
209
+ required: false,
210
+ formFieldType: FormFieldType.TIME,
211
+ name: "endTime",
212
+ disableDefaultWrapper: true,
213
+ defaultValue: "18:00",
214
+ customClassNames: {
215
+ fieldClassName: "border-none bg-blue-100",
216
+ },
217
+ },
218
+ ],
219
+ };
220
+ };
142
221
 
143
- if (response.data) {
144
- const data: FieldOptionsSchema[] = response.data;
145
- setListOptions(data);
146
- setLoading(false);
222
+ const timeConfigs = useMemo(() => {
223
+ const configs: Record<string, FormFieldSchema> = {};
224
+ Object.keys(listOptions || {}).forEach((day) => {
225
+ configs[day] = getTimeConfig(props.fieldConfig.name + "." + day);
226
+ });
227
+ return configs;
228
+ }, [props.fieldConfig.name, listOptions]);
147
229
 
148
- if (props.fieldConfig.fetchCallback) {
149
- props.fieldConfig.fetchCallback(response);
150
- }
151
- } else {
152
- console.error(response.statusText);
153
- setLoading(false);
154
- }
155
- } catch (error) {
156
- console.error('Fetch error:', error);
157
- setLoading(false);
158
- }
159
- },
160
- [props.fieldConfig.fetchUrl, props.fieldConfig.optionsConfig, props.fieldConfig.fetchCallback, props.fieldConfig.disableHeaderInFetch]
161
- );
230
+ const fetchData = useCallback(async () => {
231
+ setLoading(true);
232
+ // if (!props.fieldConfig.fetchUrl) return;
233
+ let url = "/api/core/user-prefs/get-default-business-days";
234
+ if (props.fieldConfig.fetchUrl) {
235
+ url = props.fieldConfig.fetchUrl;
236
+ }
237
+ try {
238
+ let response = await (props.fieldConfig.disableHeaderInFetch
239
+ ? axios.get(url)
240
+ : formContext.axiosInstance?.get(url));
162
241
 
163
- function getInput() {
164
- return <>
165
- {loading?<></>:
166
- listOptions && Object.keys(listOptions).map((day) => {
167
- const dayInfo = listOptions[day as any];
168
- return (
169
- <>
170
- <div className="flex baseline mb-6" data-testid="" key={day}>
171
- <div className="max-w-sm space-y-2">
172
- <div
173
- className="group flex items-center w-full gap-x-2 text-sm font-normal leading-none text-gray-700 mt-3">
174
- <SwitchField fieldConfig={{
175
- name: props.fieldConfig.name + "." + day + ".enabledDay",
176
- defaultValue: dayInfo.enabledDay,
177
- required: false,
178
- formFieldType: FormFieldType.SWITCH,
179
- disableDefaultWrapper: true,
180
- customClassNames: {
181
- fieldClassName: "!mb-0",
182
- },
183
- }}/>
184
- <p className="flex justify-between border-gray-900 text-sm text-gray-900 font-medium ml-2 w-24 leading-none">
185
- <span className="w-full">{convertToTitleCase(day)}</span>
186
- </p>
187
- </div>
188
- </div>
242
+ if (response?.data) {
243
+ const data: FieldOptionsSchema[] = response.data;
244
+ setListOptions(data);
245
+ setLoading(false);
189
246
 
190
- <div className=" flex items-center text-gray-500 focus-within:text-blue-500">
191
- <FormField fieldConfig={getTimeConfig(props.fieldConfig.name + "." + day)}/>
192
- </div>
193
- </div>
194
- </>
195
- );
196
- })
197
- }
198
- </>
247
+ if (props.fieldConfig.fetchCallback) {
248
+ props.fieldConfig.fetchCallback(response);
249
+ }
250
+ } else {
251
+ setListOptions(defaultBusinessHours);
252
+ console.error(response?.statusText);
253
+ setLoading(false);
254
+ }
255
+ } catch (error) {
256
+ console.error("Fetch error:", error);
257
+ setLoading(false);
199
258
  }
259
+ }, [
260
+ props.fieldConfig.fetchUrl,
261
+ props.fieldConfig.optionsConfig,
262
+ props.fieldConfig.fetchCallback,
263
+ props.fieldConfig.disableHeaderInFetch,
264
+ ]);
200
265
 
266
+ function getInput() {
201
267
  return (
202
- <RenderFormField fieldConfig={props.fieldConfig} getInput={getInput}/>
268
+ <>
269
+ {loading ? (
270
+ <></>
271
+ ) : (
272
+ listOptions &&
273
+ Object.keys(listOptions).map((day) => {
274
+ const dayInfo = listOptions[day as any];
275
+ return (
276
+ <>
277
+ <div className="flex baseline mb-6" data-testid="" key={day}>
278
+ <div className="max-w-sm space-y-2">
279
+ <div className="group flex items-center w-full gap-x-2 text-sm font-normal leading-none text-gray-700 mt-3">
280
+ <SwitchField
281
+ fieldConfig={{
282
+ name:
283
+ props.fieldConfig.name + "." + day + ".enabledDay",
284
+ defaultValue: dayInfo.enabledDay,
285
+ required: false,
286
+ formFieldType: FormFieldType.SWITCH,
287
+ disableDefaultWrapper: true,
288
+ customClassNames: {
289
+ fieldClassName: "!mb-0",
290
+ },
291
+ }}
292
+ />
293
+ <p className="flex justify-between border-gray-900 text-sm text-gray-900 font-medium ml-2 w-24 leading-none">
294
+ <span className="w-full">
295
+ {convertToTitleCase(day)}
296
+ </span>
297
+ </p>
298
+ </div>
299
+ </div>
300
+
301
+ <div className=" flex items-center text-gray-500 focus-within:text-blue-500">
302
+ <FormField fieldConfig={timeConfigs[day]} />
303
+ </div>
304
+ </div>
305
+ </>
306
+ );
307
+ })
308
+ )}
309
+ </>
203
310
  );
311
+ }
312
+
313
+ return (
314
+ <RenderFormField fieldConfig={props.fieldConfig} getInput={getInput} />
315
+ );
204
316
  };