goobs-frontend 0.7.63 → 0.7.64

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "goobs-frontend",
3
- "version": "0.7.63",
3
+ "version": "0.7.64",
4
4
  "description": "A comprehensive React-based UI library built on Material-UI, offering a wide range of customizable components including grids, typography, buttons, cards, forms, navigation, pricing tables, steppers, tooltips, accordions, and more. Designed for building responsive and consistent user interfaces with advanced features like form validation, theming, and code syntax highlighting.",
5
5
  "license": "MIT",
6
6
  "main": "./src/index.ts",
@@ -0,0 +1,251 @@
1
+ import { useState, useEffect, useCallback, useMemo, useRef } from 'react'
2
+ import { get } from 'goobs-cache'
3
+
4
+ /**
5
+ * Represents the structure of a helper footer message.
6
+ * @interface HelperFooterMessage
7
+ */
8
+ export interface HelperFooterMessage {
9
+ /** The status of the message, either 'error' or 'success'. */
10
+ status: 'error' | 'success'
11
+ /** A message describing the status. */
12
+ statusMessage: string
13
+ /** A message to be displayed to the user. */
14
+ spreadMessage: string
15
+ /** A number indicating the priority of the message. Lower numbers indicate higher priority. */
16
+ spreadMessagePriority: number
17
+ /** The name of the form associated with this message. */
18
+ formname: string
19
+ /** Indicates whether this message is required to be addressed. */
20
+ required: boolean
21
+ }
22
+
23
+ /**
24
+ * A type definition for the interval ID returned by setInterval.
25
+ */
26
+ type IntervalID = ReturnType<typeof setInterval>
27
+
28
+ /**
29
+ * A custom hook for managing helper footer messages and form validation.
30
+ *
31
+ * @param {string} [initialFormname] - The initial name of the form to fetch helper footers for.
32
+ * @returns {Object} An object containing the current error message, form validity state, and functions to update and fetch form validation.
33
+ */
34
+ const useHelperFooter = (initialFormname?: string) => {
35
+ /**
36
+ * State for storing the current error message.
37
+ */
38
+ const [errorMessage, setErrorMessage] = useState<string | undefined>(
39
+ undefined
40
+ )
41
+
42
+ /**
43
+ * State for storing the current form validity.
44
+ */
45
+ const [isFormValid, setIsFormValid] = useState<boolean>(true)
46
+
47
+ /**
48
+ * State for storing the current helper footers.
49
+ */
50
+ const [helperFooters, setHelperFooters] = useState<HelperFooterMessage[]>([])
51
+
52
+ /**
53
+ * Ref for storing the previous helper footers to compare against.
54
+ */
55
+ const prevHelperFooters = useRef<HelperFooterMessage[]>([])
56
+
57
+ /**
58
+ * Ref for storing the previous error message to compare against.
59
+ */
60
+ const prevErrorMessage = useRef<string | undefined>(undefined)
61
+
62
+ /**
63
+ * Ref for storing the previous form validity to compare against.
64
+ */
65
+ const prevIsFormValid = useRef<boolean>(true)
66
+
67
+ /**
68
+ * Ref for storing the interval ID for periodic helper footer fetching.
69
+ */
70
+ const intervalIdRef = useRef<IntervalID | null>(null)
71
+
72
+ /**
73
+ * Fetches helper footer messages from the cache.
74
+ *
75
+ * @param {string} [formname] - The name of the form to fetch helper footers for.
76
+ * @returns {Promise<HelperFooterMessage[]>} A promise that resolves to an array of HelperFooterMessage objects.
77
+ */
78
+ const fetchHelperFooters = useCallback(
79
+ async (formname?: string): Promise<HelperFooterMessage[]> => {
80
+ const currentFormname = formname || initialFormname
81
+ if (!currentFormname) {
82
+ return []
83
+ }
84
+
85
+ const helperFooterResult = await get(
86
+ 'helperfooter',
87
+ currentFormname,
88
+ 'client'
89
+ )
90
+
91
+ if (
92
+ helperFooterResult &&
93
+ typeof helperFooterResult === 'object' &&
94
+ 'type' in helperFooterResult &&
95
+ helperFooterResult.type === 'json' &&
96
+ 'value' in helperFooterResult &&
97
+ typeof helperFooterResult.value === 'object' &&
98
+ helperFooterResult.value !== null
99
+ ) {
100
+ const fetchedHelperFooters = Object.entries(
101
+ helperFooterResult.value as Record<string, unknown>
102
+ )
103
+ .map(([key, value]): HelperFooterMessage | null => {
104
+ if (
105
+ typeof value === 'object' &&
106
+ value !== null &&
107
+ 'status' in value &&
108
+ 'statusMessage' in value &&
109
+ 'spreadMessage' in value &&
110
+ 'spreadMessagePriority' in value &&
111
+ 'required' in value
112
+ ) {
113
+ return {
114
+ status: value.status as 'error' | 'success',
115
+ statusMessage: String(value.statusMessage),
116
+ spreadMessage: String(value.spreadMessage),
117
+ spreadMessagePriority: Number(value.spreadMessagePriority),
118
+ required: Boolean(value.required),
119
+ formname: key,
120
+ }
121
+ }
122
+ return null
123
+ })
124
+ .filter((value): value is HelperFooterMessage => value !== null)
125
+
126
+ if (
127
+ JSON.stringify(fetchedHelperFooters) !==
128
+ JSON.stringify(prevHelperFooters.current)
129
+ ) {
130
+ setHelperFooters(fetchedHelperFooters)
131
+ prevHelperFooters.current = fetchedHelperFooters
132
+ }
133
+ return fetchedHelperFooters
134
+ }
135
+
136
+ if (helperFooters.length > 0) {
137
+ setHelperFooters([])
138
+ prevHelperFooters.current = []
139
+ }
140
+ return []
141
+ },
142
+ [initialFormname, helperFooters]
143
+ )
144
+
145
+ /**
146
+ * Updates the form validation state based on the fetched helper footers.
147
+ *
148
+ * @param {string} [formname] - The name of the form to update validation for.
149
+ * @returns {Promise<boolean>} A promise that resolves to a boolean indicating whether the form is valid.
150
+ */
151
+ const updateFormValidation = useCallback(
152
+ async (formname?: string): Promise<boolean> => {
153
+ const fetchedHelperFooters = await fetchHelperFooters(formname)
154
+
155
+ if (fetchedHelperFooters.length === 0) {
156
+ setErrorMessage(undefined)
157
+ setIsFormValid(true)
158
+ return true
159
+ }
160
+
161
+ const errorFooters = fetchedHelperFooters.filter(
162
+ footer => footer.status === 'error' && footer.required
163
+ )
164
+
165
+ if (errorFooters.length > 0) {
166
+ const highestPriorityError = errorFooters.reduce((prev, current) =>
167
+ prev.spreadMessagePriority < current.spreadMessagePriority
168
+ ? prev
169
+ : current
170
+ )
171
+ setErrorMessage(highestPriorityError.spreadMessage)
172
+ setIsFormValid(false)
173
+ return false
174
+ }
175
+
176
+ setErrorMessage(undefined)
177
+ setIsFormValid(true)
178
+ return true
179
+ },
180
+ [fetchHelperFooters]
181
+ )
182
+
183
+ /**
184
+ * Effect to run form validation when the initial form name changes.
185
+ */
186
+ useEffect(() => {
187
+ void updateFormValidation()
188
+ }, [initialFormname, updateFormValidation])
189
+
190
+ /**
191
+ * Effect to set up periodic helper footer fetching.
192
+ */
193
+ useEffect(() => {
194
+ if (initialFormname) {
195
+ const fetchAndUpdateHelperFooters = async () => {
196
+ await updateFormValidation(initialFormname)
197
+ }
198
+
199
+ void fetchAndUpdateHelperFooters()
200
+ intervalIdRef.current = setInterval(fetchAndUpdateHelperFooters, 1000)
201
+
202
+ return () => {
203
+ if (intervalIdRef.current) {
204
+ clearInterval(intervalIdRef.current)
205
+ }
206
+ }
207
+ }
208
+ }, [initialFormname, updateFormValidation])
209
+
210
+ /**
211
+ * Effect to update refs when error message or form validity changes.
212
+ */
213
+ useEffect(() => {
214
+ if (
215
+ errorMessage !== prevErrorMessage.current ||
216
+ isFormValid !== prevIsFormValid.current
217
+ ) {
218
+ prevErrorMessage.current = errorMessage
219
+ prevIsFormValid.current = isFormValid
220
+ }
221
+ }, [errorMessage, isFormValid])
222
+
223
+ /**
224
+ * Memoized helper footers to prevent unnecessary re-renders.
225
+ */
226
+ const memoizedHelperFooters = useMemo(() => helperFooters, [helperFooters])
227
+
228
+ /**
229
+ * Memoized return value of the hook to prevent unnecessary re-renders.
230
+ */
231
+ const returnValue = useMemo(
232
+ () => ({
233
+ errorMessage,
234
+ isFormValid,
235
+ updateFormValidation,
236
+ fetchHelperFooters,
237
+ helperFooters: memoizedHelperFooters,
238
+ }),
239
+ [
240
+ errorMessage,
241
+ isFormValid,
242
+ updateFormValidation,
243
+ fetchHelperFooters,
244
+ memoizedHelperFooters,
245
+ ]
246
+ )
247
+
248
+ return returnValue
249
+ }
250
+
251
+ export default useHelperFooter
@@ -1,28 +1,32 @@
1
- import React, { useState, useEffect, useCallback } from 'react'
1
+ import React, { useMemo, useCallback } from 'react'
2
2
  import { Button, Box, ButtonProps } from '@mui/material'
3
3
  import StarIcon from '@mui/icons-material/Star'
4
4
  import Typography from '../Typography'
5
5
  import { red } from '../../styles/palette'
6
- import { get } from 'goobs-cache'
6
+ import useHelperFooter from './hook/useHelperFooter'
7
7
 
8
+ /**
9
+ * Defines the possible alignment options for the button text.
10
+ */
8
11
  export type ButtonAlignment = 'left' | 'center' | 'right'
9
12
 
10
- export interface HelperFooterMessage {
11
- status: 'error' | 'success'
12
- statusMessage: string
13
- spreadMessage: string
14
- spreadMessagePriority: number
15
- formname: string
16
- required: boolean
17
- }
18
-
13
+ /**
14
+ * Interface for the CustomButton component props.
15
+ * Extends ButtonProps from Material-UI, omitting 'color' and 'variant'.
16
+ */
19
17
  export interface CustomButtonProps
20
18
  extends Omit<ButtonProps, 'color' | 'variant'> {
19
+ /** The text to display on the button */
21
20
  text?: string
21
+ /** The background color of the button */
22
22
  backgroundcolor?: string
23
+ /** The outline color of the button */
23
24
  outlinecolor?: string
25
+ /** The font color of the button text */
24
26
  fontcolor?: string
27
+ /** The alignment of the button text */
25
28
  fontlocation?: ButtonAlignment
29
+ /** The variant of the font to use for the button text */
26
30
  fontvariant?:
27
31
  | 'arapeyh1'
28
32
  | 'arapeyh2'
@@ -51,18 +55,32 @@ export interface CustomButtonProps
51
55
  | 'merriparagraph'
52
56
  | 'merrihelperheader'
53
57
  | 'merrihelperfooter'
58
+ /** The icon to display on the button */
54
59
  icon?: React.ReactNode | false
60
+ /** The color of the icon */
55
61
  iconcolor?: string
62
+ /** The size of the icon */
56
63
  iconsize?: string
64
+ /** The location of the icon relative to the text */
57
65
  iconlocation?: 'left' | 'top' | 'right'
66
+ /** The variant of the button */
58
67
  variant?: 'text' | 'outlined' | 'contained'
68
+ /** The function to call when the button is clicked */
59
69
  onClick?: () => void
60
- helperfooter?: HelperFooterMessage
70
+ /** The width of the button */
61
71
  width?: string
72
+ /** The name of the form associated with this button */
62
73
  formname?: string
74
+ /** The name attribute of the button */
63
75
  name?: string
64
76
  }
65
77
 
78
+ /**
79
+ * CustomButton component that extends Material-UI's Button with additional styling and functionality.
80
+ *
81
+ * @param props - The props for the CustomButton component
82
+ * @returns A React functional component
83
+ */
66
84
  const CustomButton: React.FC<CustomButtonProps> = props => {
67
85
  const {
68
86
  text,
@@ -83,111 +101,15 @@ const CustomButton: React.FC<CustomButtonProps> = props => {
83
101
  width,
84
102
  } = props
85
103
 
86
- const [errorMessage, setErrorMessage] = useState<string | undefined>(
87
- undefined
88
- )
89
- const [, setIsFormValid] = useState<boolean>(true)
90
- const [hasBeenClicked, setHasBeenClicked] = useState<boolean>(false)
91
-
92
- const fetchHelperFooters = useCallback(async (): Promise<
93
- HelperFooterMessage[]
94
- > => {
95
- if (!formname) {
96
- console.log('CustomButton: No formname provided, returning empty array')
97
- return []
98
- }
99
- console.log('CustomButton: Fetching helper footers for formname:', formname)
100
-
101
- // Wait for 2 seconds before fetching to allow time for cache update
102
- await new Promise(resolve => setTimeout(resolve, 3000))
103
-
104
- const helperFooterResult = await get('helperfooter', formname, 'client')
105
- console.log('CustomButton: Helper footer result:', helperFooterResult)
106
-
107
- if (
108
- helperFooterResult &&
109
- typeof helperFooterResult === 'object' &&
110
- 'type' in helperFooterResult &&
111
- helperFooterResult.type === 'json' &&
112
- 'value' in helperFooterResult &&
113
- typeof helperFooterResult.value === 'object' &&
114
- helperFooterResult.value !== null
115
- ) {
116
- const helperFooters = Object.entries(
117
- helperFooterResult.value as Record<string, unknown>
118
- )
119
- .map(([key, value]): HelperFooterMessage | null => {
120
- if (
121
- typeof value === 'object' &&
122
- value !== null &&
123
- 'status' in value &&
124
- 'statusMessage' in value &&
125
- 'spreadMessage' in value &&
126
- 'spreadMessagePriority' in value &&
127
- 'required' in value
128
- ) {
129
- return {
130
- status: value.status as 'error' | 'success',
131
- statusMessage: String(value.statusMessage),
132
- spreadMessage: String(value.spreadMessage),
133
- spreadMessagePriority: Number(value.spreadMessagePriority),
134
- required: Boolean(value.required),
135
- formname: key,
136
- }
137
- }
138
- return null
139
- })
140
- .filter((value): value is HelperFooterMessage => value !== null)
141
-
142
- console.log('CustomButton: Valid helper footers:', helperFooters)
143
- return helperFooters
144
- }
145
-
146
- console.log('CustomButton: No valid helper footers found in cache')
147
- return []
148
- }, [formname])
104
+ const { errorMessage, isFormValid, updateFormValidation } =
105
+ useHelperFooter(formname)
149
106
 
150
- const updateFormValidation = useCallback(async (): Promise<boolean> => {
151
- console.log('CustomButton: Starting form validation')
152
- const helperFooters = await fetchHelperFooters()
153
- console.log('CustomButton: Fetched helper footers:', helperFooters)
154
-
155
- if (helperFooters.length === 0) {
156
- console.log('CustomButton: No helper footers found, form is valid')
157
- setErrorMessage(undefined)
158
- setIsFormValid(true)
159
- return true
160
- }
161
-
162
- const errorFooters = helperFooters.filter(
163
- footer => footer.status === 'error'
164
- )
165
- console.log('CustomButton: Error footers:', errorFooters)
166
-
167
- if (errorFooters.length > 0) {
168
- console.log('CustomButton: Found error footers, form is invalid')
169
- const highestPriorityError = errorFooters.reduce((prev, current) =>
170
- prev.spreadMessagePriority < current.spreadMessagePriority
171
- ? prev
172
- : current
173
- )
174
- setErrorMessage(highestPriorityError.spreadMessage)
175
- setIsFormValid(false)
176
- return false
177
- }
178
-
179
- console.log('CustomButton: No error footers found, form is valid')
180
- setErrorMessage(undefined)
181
- setIsFormValid(true)
182
- return true
183
- }, [fetchHelperFooters])
184
-
185
- useEffect(() => {
186
- console.log('CustomButton: Running effect to update form validation')
187
- void updateFormValidation()
188
- }, [updateFormValidation])
189
-
190
- const renderIcon = (): React.ReactNode => {
107
+ /**
108
+ * Renders the icon for the button based on the provided props.
109
+ *
110
+ * @returns {React.ReactNode} The rendered icon or null
111
+ */
112
+ const renderIcon = useCallback((): React.ReactNode => {
191
113
  if (icon === false) {
192
114
  return null
193
115
  }
@@ -197,24 +119,80 @@ const CustomButton: React.FC<CustomButtonProps> = props => {
197
119
  })
198
120
  }
199
121
  return <StarIcon style={{ fontSize: iconsize }} />
200
- }
122
+ }, [icon, iconsize])
123
+
124
+ /**
125
+ * Handles the button click event. Prevents default behavior, validates the form,
126
+ * and calls the onClick prop if the form is valid.
127
+ *
128
+ * @param event - The mouse event from clicking the button
129
+ */
130
+ const handleButtonClick = useCallback(
131
+ async (event: React.MouseEvent<HTMLButtonElement>): Promise<void> => {
132
+ event.preventDefault()
133
+ const validationResult = await updateFormValidation(formname)
134
+ if (validationResult && onClick) {
135
+ onClick()
136
+ }
137
+ },
138
+ [updateFormValidation, onClick, formname]
139
+ )
201
140
 
202
- const handleButtonClick = async (
203
- event: React.MouseEvent<HTMLButtonElement>
204
- ): Promise<void> => {
205
- console.log('CustomButton: Button clicked')
206
- event.preventDefault()
207
- setHasBeenClicked(true)
141
+ /**
142
+ * Memoized style object for the button.
143
+ */
144
+ const buttonStyle = useMemo(
145
+ () => ({
146
+ minWidth: text ? 'auto' : 'fit-content',
147
+ paddingLeft: text ? '8px' : '0',
148
+ paddingRight: text ? '8px' : '0',
149
+ justifyContent: fontlocation || 'center',
150
+ backgroundColor: backgroundcolor,
151
+ border: outlinecolor ? `1px solid ${outlinecolor}` : undefined,
152
+ color: iconcolor,
153
+ width: width,
154
+ }),
155
+ [text, fontlocation, backgroundcolor, outlinecolor, iconcolor, width]
156
+ )
208
157
 
209
- const isValid = await updateFormValidation()
210
- console.log('CustomButton: Form validation result:', isValid)
211
- if (isValid && onClick) {
212
- console.log('CustomButton: Form is valid, calling onClick')
213
- onClick()
214
- } else {
215
- console.log('CustomButton: Form is invalid or no onClick provided')
216
- }
217
- }
158
+ /**
159
+ * Memoized content for the button, including icon and text.
160
+ */
161
+ const buttonContent = useMemo(
162
+ () => (
163
+ <Box display="flex" alignItems="center">
164
+ {iconlocation === 'left' && renderIcon()}
165
+ {text && (
166
+ <Typography
167
+ fontvariant={fontvariant}
168
+ fontcolor={fontcolor}
169
+ text={text}
170
+ />
171
+ )}
172
+ {iconlocation === 'right' && renderIcon()}
173
+ </Box>
174
+ ),
175
+ [iconlocation, renderIcon, text, fontvariant, fontcolor]
176
+ )
177
+
178
+ /**
179
+ * Memoized error message component that displays when the form is invalid.
180
+ */
181
+ const errorMessageComponent = useMemo(
182
+ () =>
183
+ !isFormValid && errorMessage ? (
184
+ <Typography
185
+ fontvariant="merrihelperfooter"
186
+ fontcolor={red.main}
187
+ text={errorMessage}
188
+ marginTop={0.5}
189
+ marginBottom={0}
190
+ align="center"
191
+ width="100%"
192
+ />
193
+ ) : null,
194
+ [errorMessage, isFormValid]
195
+ )
218
196
 
219
197
  return (
220
198
  <Box
@@ -231,42 +209,13 @@ const CustomButton: React.FC<CustomButtonProps> = props => {
231
209
  type={type}
232
210
  name={name}
233
211
  onClick={handleButtonClick}
234
- style={{
235
- minWidth: text ? 'auto' : 'fit-content',
236
- paddingLeft: text ? '8px' : '0',
237
- paddingRight: text ? '8px' : '0',
238
- justifyContent: fontlocation || 'center',
239
- backgroundColor: backgroundcolor,
240
- border: outlinecolor ? `1px solid ${outlinecolor}` : undefined,
241
- color: iconcolor,
242
- width: width,
243
- }}
212
+ style={buttonStyle}
244
213
  >
245
- <Box display="flex" alignItems="center">
246
- {iconlocation === 'left' && renderIcon()}
247
- {text && (
248
- <Typography
249
- fontvariant={fontvariant}
250
- fontcolor={fontcolor}
251
- text={text}
252
- />
253
- )}
254
- {iconlocation === 'right' && renderIcon()}
255
- </Box>
214
+ {buttonContent}
256
215
  </Button>
257
- {hasBeenClicked && errorMessage && (
258
- <Typography
259
- fontvariant="merrihelperfooter"
260
- fontcolor={red.main}
261
- text={errorMessage}
262
- marginTop={0.5}
263
- marginBottom={0}
264
- align="center"
265
- width="100%"
266
- />
267
- )}
216
+ {errorMessageComponent}
268
217
  </Box>
269
218
  )
270
219
  }
271
220
 
272
- export default CustomButton
221
+ export default React.memo(CustomButton)
@@ -33,7 +33,6 @@ const useButton = (grid: {
33
33
  iconlocation,
34
34
  variant,
35
35
  onClick,
36
- helperfooter,
37
36
  columnconfig: itemColumnConfig,
38
37
  cellconfig,
39
38
  ...restProps
@@ -72,7 +71,6 @@ const useButton = (grid: {
72
71
  iconlocation={iconlocation}
73
72
  variant={variant}
74
73
  onClick={onClick}
75
- helperfooter={helperfooter}
76
74
  {...restProps}
77
75
  />
78
76
  ),
@@ -2,27 +2,53 @@
2
2
 
3
3
  import { useCallback, useState, useMemo, useRef, useEffect } from 'react'
4
4
  import { debounce } from 'lodash'
5
- import { get, set, StringValue, JSONValue } from 'goobs-cache'
5
+ import { get, set, remove, StringValue, JSONValue } from 'goobs-cache'
6
6
 
7
+ /**
8
+ * Validates if the given email string is in a valid email format.
9
+ * @param {string} email - The email string to validate.
10
+ * @returns {boolean} True if the email is valid, false otherwise.
11
+ */
7
12
  const isValidEmailFormat = (email: string): boolean => {
8
13
  const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
9
14
  return emailRegex.test(email)
10
15
  }
11
16
 
17
+ /**
18
+ * Represents the structure of a helper footer message.
19
+ */
12
20
  export interface HelperFooterMessage {
21
+ /** The status of the message: 'error', 'success', or 'emptyAndRequired'. */
13
22
  status: 'error' | 'success' | 'emptyAndRequired'
23
+ /** A detailed message describing the status. */
14
24
  statusMessage: string
25
+ /** A concise message for display purposes. */
15
26
  spreadMessage: string
27
+ /** A number indicating the priority of the message. Lower numbers indicate higher priority. */
16
28
  spreadMessagePriority: number
29
+ /** Indicates whether this field is required. */
17
30
  required: boolean
18
31
  }
19
32
 
33
+ /**
34
+ * A custom hook for managing helper footer messages and form validation.
35
+ * @returns {Object} An object containing functions and state for managing helper footers.
36
+ */
20
37
  export const useHelperFooter = () => {
21
38
  const [helperFooterValue, setHelperFooterValue] = useState<
22
39
  Record<string, HelperFooterMessage>
23
40
  >({})
24
41
  const helperFooterRef = useRef<Record<string, HelperFooterMessage>>({})
25
42
 
43
+ /**
44
+ * Handles the creation of error messages for generic fields.
45
+ * @param {FormData} formData - The form data containing the field value.
46
+ * @param {string} name - The name of the field.
47
+ * @param {string} label - The label of the field.
48
+ * @param {boolean} required - Whether the field is required.
49
+ * @param {string} formname - The name of the form.
50
+ * @returns {Promise<HelperFooterMessage | undefined>} A promise that resolves to a HelperFooterMessage or undefined.
51
+ */
26
52
  const handleGenericErrorCreation = useCallback(
27
53
  async (
28
54
  formData: FormData,
@@ -53,18 +79,22 @@ export const useHelperFooter = () => {
53
79
  },
54
80
  }
55
81
  await set('helperfooter', formname, jsonValue, 'client')
56
- console.log(
57
- `Stored helper footer for ${name}:`,
58
- message,
59
- `storeName: ${formname}`
60
- )
61
82
  return message
83
+ } else {
84
+ await remove('helperfooter', formname, 'client')
62
85
  }
63
86
  return undefined
64
87
  },
65
88
  []
66
89
  )
67
90
 
91
+ /**
92
+ * Handles the creation of error messages for email fields.
93
+ * @param {FormData} formData - The form data containing the email value.
94
+ * @param {boolean} required - Whether the email field is required.
95
+ * @param {string} formname - The name of the form.
96
+ * @returns {Promise<HelperFooterMessage | undefined>} A promise that resolves to a HelperFooterMessage or undefined.
97
+ */
68
98
  const handleEmailErrorCreation = useCallback(
69
99
  async (
70
100
  formData: FormData,
@@ -101,24 +131,23 @@ export const useHelperFooter = () => {
101
131
  }
102
132
 
103
133
  if (message) {
104
- const jsonValue: JSONValue = {
105
- type: 'json',
106
- value: {
107
- email: {
108
- status: message.status,
109
- statusMessage: message.statusMessage,
110
- spreadMessage: message.spreadMessage,
111
- spreadMessagePriority: message.spreadMessagePriority,
112
- required: message.required,
134
+ if (message.status === 'success') {
135
+ await remove('helperfooter', formname, 'client')
136
+ } else {
137
+ const jsonValue: JSONValue = {
138
+ type: 'json',
139
+ value: {
140
+ email: {
141
+ status: message.status,
142
+ statusMessage: message.statusMessage,
143
+ spreadMessage: message.spreadMessage,
144
+ spreadMessagePriority: message.spreadMessagePriority,
145
+ required: message.required,
146
+ },
113
147
  },
114
- },
148
+ }
149
+ await set('helperfooter', formname, jsonValue, 'client')
115
150
  }
116
- await set('helperfooter', formname, jsonValue, 'client')
117
- console.log(
118
- `Stored helper footer for email:`,
119
- message,
120
- `storeName: ${formname}`
121
- )
122
151
  }
123
152
 
124
153
  return message
@@ -126,6 +155,13 @@ export const useHelperFooter = () => {
126
155
  []
127
156
  )
128
157
 
158
+ /**
159
+ * Handles the creation of error messages for password fields.
160
+ * @param {FormData} formData - The form data containing the password value.
161
+ * @param {boolean} required - Whether the password field is required.
162
+ * @param {string} formname - The name of the form.
163
+ * @returns {Promise<HelperFooterMessage | undefined>} A promise that resolves to a HelperFooterMessage or undefined.
164
+ */
129
165
  const handlePasswordErrorCreation = useCallback(
130
166
  async (
131
167
  formData: FormData,
@@ -133,24 +169,15 @@ export const useHelperFooter = () => {
133
169
  formname: string
134
170
  ): Promise<HelperFooterMessage | undefined> => {
135
171
  const password = formData.get('verifyPassword') as string
136
- console.log('handlePasswordErrorCreation - Password:', password)
137
172
 
138
173
  const debouncedPasswordStorage = debounce(async () => {
139
- try {
140
- if (password) {
141
- console.log('Storing password in goobs-cache...')
142
- await set(
143
- 'validate',
144
- formname,
145
- { type: 'string', value: password } as StringValue,
146
- 'client'
147
- )
148
- console.log('Password stored successfully')
149
- } else {
150
- console.log('No password to store')
151
- }
152
- } catch (error) {
153
- console.error('Error interacting with goobs-cache:', error)
174
+ if (password) {
175
+ await set(
176
+ 'validate',
177
+ formname,
178
+ { type: 'string', value: password } as StringValue,
179
+ 'client'
180
+ )
154
181
  }
155
182
  }, 2000)
156
183
 
@@ -193,24 +220,23 @@ export const useHelperFooter = () => {
193
220
  }
194
221
 
195
222
  if (message) {
196
- const jsonValue: JSONValue = {
197
- type: 'json',
198
- value: {
199
- verifyPassword: {
200
- status: message.status,
201
- statusMessage: message.statusMessage,
202
- spreadMessage: message.spreadMessage,
203
- spreadMessagePriority: message.spreadMessagePriority,
204
- required: message.required,
223
+ if (message.status === 'success') {
224
+ await remove('helperfooter', formname, 'client')
225
+ } else {
226
+ const jsonValue: JSONValue = {
227
+ type: 'json',
228
+ value: {
229
+ verifyPassword: {
230
+ status: message.status,
231
+ statusMessage: message.statusMessage,
232
+ spreadMessage: message.spreadMessage,
233
+ spreadMessagePriority: message.spreadMessagePriority,
234
+ required: message.required,
235
+ },
205
236
  },
206
- },
237
+ }
238
+ await set('helperfooter', formname, jsonValue, 'client')
207
239
  }
208
- await set('helperfooter', formname, jsonValue, 'client')
209
- console.log(
210
- `Stored helper footer for verifyPassword:`,
211
- message,
212
- `storeName: ${formname}`
213
- )
214
240
  }
215
241
 
216
242
  return message
@@ -218,6 +244,13 @@ export const useHelperFooter = () => {
218
244
  []
219
245
  )
220
246
 
247
+ /**
248
+ * Handles the creation of error messages for password confirmation fields.
249
+ * @param {FormData} formData - The form data containing the confirmation password value.
250
+ * @param {boolean} required - Whether the confirmation password field is required.
251
+ * @param {string} formname - The name of the form.
252
+ * @returns {Promise<HelperFooterMessage | undefined>} A promise that resolves to a HelperFooterMessage or undefined.
253
+ */
221
254
  const handleConfirmPasswordErrorCreation = useCallback(
222
255
  async (
223
256
  formData: FormData,
@@ -225,10 +258,6 @@ export const useHelperFooter = () => {
225
258
  formname: string
226
259
  ): Promise<HelperFooterMessage | undefined> => {
227
260
  const confirmPassword = formData.get('confirmPassword') as string
228
- console.log(
229
- 'handleConfirmPasswordErrorCreation - Confirm Password:',
230
- confirmPassword
231
- )
232
261
 
233
262
  let message: HelperFooterMessage | undefined
234
263
 
@@ -243,11 +272,9 @@ export const useHelperFooter = () => {
243
272
  } else if (confirmPassword) {
244
273
  let verifyPasswordResult
245
274
  try {
246
- console.log('Retrieving password from goobs-cache...')
247
275
  verifyPasswordResult = await get('validate', formname, 'client')
248
- console.log('Retrieved password result:', verifyPasswordResult)
249
276
  } catch (error) {
250
- console.error('Error retrieving password from goobs-cache:', error)
277
+ // Error handling
251
278
  }
252
279
 
253
280
  let verifyPassword: string | undefined
@@ -260,9 +287,6 @@ export const useHelperFooter = () => {
260
287
  typeof verifyPasswordResult.value === 'string'
261
288
  ) {
262
289
  verifyPassword = verifyPasswordResult.value
263
- console.log('Verify password retrieved:', verifyPassword)
264
- } else {
265
- console.log('Invalid or missing verify password result')
266
290
  }
267
291
 
268
292
  if (!verifyPassword) {
@@ -293,24 +317,23 @@ export const useHelperFooter = () => {
293
317
  }
294
318
 
295
319
  if (message) {
296
- const jsonValue: JSONValue = {
297
- type: 'json',
298
- value: {
299
- confirmPassword: {
300
- status: message.status,
301
- statusMessage: message.statusMessage,
302
- spreadMessage: message.spreadMessage,
303
- spreadMessagePriority: message.spreadMessagePriority,
304
- required: message.required,
320
+ if (message.status === 'success') {
321
+ await remove('helperfooter', formname, 'client')
322
+ } else {
323
+ const jsonValue: JSONValue = {
324
+ type: 'json',
325
+ value: {
326
+ confirmPassword: {
327
+ status: message.status,
328
+ statusMessage: message.statusMessage,
329
+ spreadMessage: message.spreadMessage,
330
+ spreadMessagePriority: message.spreadMessagePriority,
331
+ required: message.required,
332
+ },
305
333
  },
306
- },
334
+ }
335
+ await set('helperfooter', formname, jsonValue, 'client')
307
336
  }
308
- await set('helperfooter', formname, jsonValue, 'client')
309
- console.log(
310
- `Stored helper footer for confirmPassword:`,
311
- message,
312
- `storeName: ${formname}`
313
- )
314
337
  }
315
338
 
316
339
  return message
@@ -318,6 +341,13 @@ export const useHelperFooter = () => {
318
341
  []
319
342
  )
320
343
 
344
+ /**
345
+ * Handles the creation of error messages for phone number fields.
346
+ * @param {FormData} formData - The form data containing the phone number value.
347
+ * @param {boolean} required - Whether the phone number field is required.
348
+ * @param {string} formname - The name of the form.
349
+ * @returns {Promise<HelperFooterMessage | undefined>} A promise that resolves to a HelperFooterMessage or undefined.
350
+ */
321
351
  const handlePhoneNumberErrorCreation = useCallback(
322
352
  async (
323
353
  formData: FormData,
@@ -363,24 +393,23 @@ export const useHelperFooter = () => {
363
393
  }
364
394
 
365
395
  if (message) {
366
- const jsonValue: JSONValue = {
367
- type: 'json',
368
- value: {
369
- phoneNumber: {
370
- status: message.status,
371
- statusMessage: message.statusMessage,
372
- spreadMessage: message.spreadMessage,
373
- spreadMessagePriority: message.spreadMessagePriority,
374
- required: message.required,
396
+ if (message.status === 'success') {
397
+ await remove('helperfooter', formname, 'client')
398
+ } else {
399
+ const jsonValue: JSONValue = {
400
+ type: 'json',
401
+ value: {
402
+ phoneNumber: {
403
+ status: message.status,
404
+ statusMessage: message.statusMessage,
405
+ spreadMessage: message.spreadMessage,
406
+ spreadMessagePriority: message.spreadMessagePriority,
407
+ required: message.required,
408
+ },
375
409
  },
376
- },
410
+ }
411
+ await set('helperfooter', formname, jsonValue, 'client')
377
412
  }
378
- await set('helperfooter', formname, jsonValue, 'client')
379
- console.log(
380
- `Stored helper footer for phoneNumber:`,
381
- message,
382
- `storeName: ${formname}`
383
- )
384
413
  }
385
414
 
386
415
  return message
@@ -388,6 +417,11 @@ export const useHelperFooter = () => {
388
417
  []
389
418
  )
390
419
 
420
+ /**
421
+ * Updates the helper footer state with new validation results.
422
+ * @param {string} name - The name of the field.
423
+ * @param {HelperFooterMessage | undefined} validationResult - The validation result for the field.
424
+ */
391
425
  const updateHelperFooter = useCallback(
392
426
  (name: string, validationResult: HelperFooterMessage | undefined): void => {
393
427
  if (validationResult) {
@@ -410,9 +444,12 @@ export const useHelperFooter = () => {
410
444
  []
411
445
  )
412
446
 
447
+ /**
448
+ * Initializes required fields for a given form.
449
+ * @param {string} formname - The name of the form.
450
+ */
413
451
  const initializeRequiredFields = useCallback(
414
452
  async (formname: string) => {
415
- console.log(`Initializing required fields for ${formname}`)
416
453
  const fields = await get('helperfooter', formname, 'client')
417
454
  if (
418
455
  fields &&
@@ -425,7 +462,6 @@ export const useHelperFooter = () => {
425
462
  ([field, message]) => {
426
463
  if (message && typeof message === 'object' && 'status' in message) {
427
464
  updateHelperFooter(field, message as HelperFooterMessage)
428
- console.log(`Initialized required field ${field}:`, message)
429
465
  }
430
466
  }
431
467
  )
@@ -434,6 +470,14 @@ export const useHelperFooter = () => {
434
470
  [updateHelperFooter]
435
471
  )
436
472
 
473
+ /**
474
+ * Validates a specific field in the form.
475
+ * @param {string} name - The name of the field.
476
+ * @param {FormData} formData - The form data containing the field value.
477
+ * @param {string} label - The label of the field.
478
+ * @param {boolean} required - Whether the field is required.
479
+ * @param {string} formname - The name of the form.
480
+ */
437
481
  const validateField = useCallback(
438
482
  async (
439
483
  name: string,
@@ -442,7 +486,6 @@ export const useHelperFooter = () => {
442
486
  required: boolean,
443
487
  formname: string
444
488
  ) => {
445
- console.log(`Validating field: ${name}`)
446
489
  let validationResult: HelperFooterMessage | undefined
447
490
 
448
491
  switch (name) {
@@ -485,7 +528,6 @@ export const useHelperFooter = () => {
485
528
  }
486
529
 
487
530
  updateHelperFooter(name, validationResult)
488
- console.log(`Validation result for ${name}:`, validationResult)
489
531
  },
490
532
  [
491
533
  handleEmailErrorCreation,
@@ -497,9 +539,15 @@ export const useHelperFooter = () => {
497
539
  ]
498
540
  )
499
541
 
542
+ /**
543
+ * Validates a required field in the form.
544
+ * @param {boolean} required - Whether the field is required.
545
+ * @param {string} formname - The name of the form.
546
+ * @param {string} name - The name of the field.
547
+ * @param {string} label - The label of the field.
548
+ */
500
549
  const validateRequiredField = useCallback(
501
550
  (required: boolean, formname: string, name: string, label: string) => {
502
- console.log(`Validating required field: ${name}`)
503
551
  if (required && formname && name && label) {
504
552
  const emptyFormData = new FormData()
505
553
  emptyFormData.append(name, '')
@@ -509,6 +557,13 @@ export const useHelperFooter = () => {
509
557
  [validateField]
510
558
  )
511
559
 
560
+ /**
561
+ * A custom hook to determine whether to show an error message.
562
+ * @param {boolean} formSubmitted - Whether the form has been submitted.
563
+ * @param {boolean} hasInput - Whether the field has input.
564
+ * @param {boolean} isFocused - Whether the field is currently focused.
565
+ * @returns {boolean} Whether to show the error message.
566
+ */
512
567
  const useShowErrorEffect = (
513
568
  formSubmitted: boolean,
514
569
  hasInput: boolean,
@@ -519,15 +574,18 @@ export const useHelperFooter = () => {
519
574
  useEffect(() => {
520
575
  const shouldShowError = formSubmitted || (hasInput && !isFocused)
521
576
  setShowError(shouldShowError)
522
- console.log('Show error state updated:', shouldShowError)
523
577
  }, [formSubmitted, hasInput, isFocused])
524
578
 
525
579
  return showError
526
580
  }
527
581
 
582
+ /**
583
+ * Fetches helper footer messages for a given form.
584
+ * @param {string} formname - The name of the form.
585
+ * @returns {Promise<HelperFooterMessage[]>} A promise that resolves to an array of HelperFooterMessages.
586
+ */
528
587
  const fetchHelperFooters = useCallback(
529
588
  async (formname: string): Promise<HelperFooterMessage[]> => {
530
- console.log(`Fetching helper footers for ${formname}`)
531
589
  const helperFooters = await get('helperfooter', formname, 'client')
532
590
  if (
533
591
  helperFooters &&
@@ -1,10 +1,11 @@
1
- import React, { useState, useCallback } from 'react'
1
+ import React, { useState, useCallback, useEffect } from 'react'
2
2
 
3
3
  /**
4
- * Formats a string of digits into a US phone number format with a "+1" country code.
4
+ * Formats a string of digits into a US phone number format.
5
+ * The "+1" country code is always added at the beginning.
5
6
  *
6
7
  * @param {string} value - The input string to be formatted.
7
- * @returns {string} A formatted phone number string in the format "+1 xxx-xxx-xxxx".
8
+ * @returns {string} A formatted phone number string.
8
9
  *
9
10
  * @example
10
11
  * formatPhoneNumber("1234567890") // returns "+1 123-456-7890"
@@ -14,6 +15,7 @@ export const formatPhoneNumber = (value: string): string => {
14
15
  const digits = value.replace(/\D/g, '')
15
16
  const limitedDigits = digits.slice(0, 10)
16
17
  let formattedNumber = '+1 '
18
+
17
19
  if (limitedDigits.length > 0) {
18
20
  formattedNumber += limitedDigits.slice(0, 3)
19
21
  if (limitedDigits.length > 3) {
@@ -23,13 +25,14 @@ export const formatPhoneNumber = (value: string): string => {
23
25
  }
24
26
  }
25
27
  }
26
- return formattedNumber
28
+ return formattedNumber.trim()
27
29
  }
28
30
 
29
31
  /**
30
32
  * A custom React hook for managing and formatting a phone number input.
31
33
  *
32
34
  * @param {string} [initialValue=''] - The initial value of the phone number.
35
+ * @param {string} [componentvariant=''] - The variant of the component.
33
36
  * @returns {Object} An object containing the current phone number state and functions to update it.
34
37
  * @property {string} phoneNumber - The current formatted phone number.
35
38
  * @property {function} handlePhoneNumberChange - A function to handle changes to the phone number input.
@@ -38,11 +41,10 @@ export const formatPhoneNumber = (value: string): string => {
38
41
  * @example
39
42
  * const { phoneNumber, handlePhoneNumberChange, updatePhoneNumber } = usePhoneNumber();
40
43
  */
41
- export const usePhoneNumber = (initialValue: string = '') => {
42
- /**
43
- * The current state of the formatted phone number.
44
- * @type {[string, function]}
45
- */
44
+ export const usePhoneNumber = (
45
+ initialValue: string = '',
46
+ componentvariant: string = ''
47
+ ) => {
46
48
  const [phoneNumber, setPhoneNumber] = useState(
47
49
  formatPhoneNumber(initialValue)
48
50
  )
@@ -55,9 +57,14 @@ export const usePhoneNumber = (initialValue: string = '') => {
55
57
  const handlePhoneNumberChange = useCallback(
56
58
  (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
57
59
  const input = e.target.value
58
- // Remove the "+1 " prefix if it exists
59
- const strippedInput = input.startsWith('+1 ') ? input.slice(3) : input
60
- const formattedValue = formatPhoneNumber(strippedInput)
60
+ let strippedInput = input.replace(/^\+1\s?/, '').replace(/\D/g, '')
61
+
62
+ // Ensure we don't exceed 10 digits
63
+ strippedInput = strippedInput.slice(0, 10)
64
+
65
+ // Only format if there's actual input beyond "+1 "
66
+ const formattedValue =
67
+ strippedInput.length > 0 ? formatPhoneNumber(strippedInput) : '+1 '
61
68
  setPhoneNumber(formattedValue)
62
69
  },
63
70
  []
@@ -72,6 +79,15 @@ export const usePhoneNumber = (initialValue: string = '') => {
72
79
  setPhoneNumber(formatPhoneNumber(newValue))
73
80
  }, [])
74
81
 
82
+ /**
83
+ * Update phone number when componentvariant is 'phonenumber' and value changes
84
+ */
85
+ useEffect(() => {
86
+ if (componentvariant === 'phonenumber' && initialValue) {
87
+ updatePhoneNumber(initialValue)
88
+ }
89
+ }, [componentvariant, initialValue, updatePhoneNumber])
90
+
75
91
  return {
76
92
  phoneNumber,
77
93
  handlePhoneNumberChange,
@@ -164,8 +164,10 @@ const StyledComponent: React.FC<StyledComponentProps> = props => {
164
164
  inputBoxRef,
165
165
  onOptionSelect
166
166
  )
167
- const { phoneNumber, handlePhoneNumberChange, updatePhoneNumber } =
168
- usePhoneNumber(value || '')
167
+ const { phoneNumber, handlePhoneNumberChange } = usePhoneNumber(
168
+ value || '',
169
+ componentvariant
170
+ )
169
171
  const {
170
172
  value: splitButtonValue,
171
173
  handleIncrement,
@@ -204,15 +206,6 @@ const StyledComponent: React.FC<StyledComponentProps> = props => {
204
206
  return () => clearTimeout(timer)
205
207
  }, [formSubmitted, hasInput])
206
208
 
207
- /**
208
- * Update phone number when componentvariant is 'phonenumber' and value changes
209
- */
210
- useEffect(() => {
211
- if (componentvariant === 'phonenumber' && value) {
212
- updatePhoneNumber(value)
213
- }
214
- }, [componentvariant, value, updatePhoneNumber])
215
-
216
209
  const currentHelperFooter = name ? helperFooterValue[name] : undefined
217
210
 
218
211
  /**