goobs-frontend 0.7.58 → 0.7.59

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.58",
3
+ "version": "0.7.59",
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",
@@ -25,29 +25,29 @@
25
25
  "@emotion/cache": "^11.11.0",
26
26
  "@emotion/react": "^11.11.4",
27
27
  "@emotion/styled": "^11.11.5",
28
- "@mui/icons-material": "^5.15.20",
29
- "@mui/material": "^5.15.20",
30
- "@types/lodash": "^4.17.5",
31
- "goobs-cache": "^1.2.1",
32
- "highlight.js": "^11.9.0",
28
+ "@mui/icons-material": "^5.16.0",
29
+ "@mui/material": "^5.16.0",
30
+ "@types/lodash": "^4.17.6",
31
+ "goobs-cache": "^1.3.1",
32
+ "highlight.js": "^11.10.0",
33
33
  "lodash": "^4.17.21",
34
- "next": "14.2.4"
34
+ "next": "14.2.5"
35
35
  },
36
36
  "devDependencies": {
37
- "@next/eslint-plugin-next": "^14.2.4",
38
- "@types/node": "^20.14.9",
39
- "@types/react": "18.3.0",
37
+ "@next/eslint-plugin-next": "^14.2.5",
38
+ "@types/node": "^20.14.10",
39
+ "@types/react": "18.3.3",
40
40
  "@types/react-dom": "^18.3.0",
41
- "@typescript-eslint/eslint-plugin": "^7.15.0",
42
- "@typescript-eslint/parser": "^7.15.0",
41
+ "@typescript-eslint/eslint-plugin": "^7.16.0",
42
+ "@typescript-eslint/parser": "^7.16.0",
43
43
  "eslint": "^8.57.0",
44
- "eslint-config-next": "^14.2.4",
44
+ "eslint-config-next": "^14.2.5",
45
45
  "eslint-config-prettier": "^9.1.0",
46
46
  "eslint-plugin-prettier": "^5.1.3",
47
47
  "prettier": "^3.3.2",
48
48
  "react": "^18.3.1",
49
49
  "react-dom": "^18.3.1",
50
- "typescript": "^5.5.2"
50
+ "typescript": "^5.5.3"
51
51
  },
52
52
  "files": [
53
53
  "src"
@@ -7,46 +7,24 @@ import Typography from '../Typography'
7
7
  import { get, JSONValue } from 'goobs-cache'
8
8
  import { red } from '../../styles/palette'
9
9
 
10
- /**
11
- * Defines the possible alignment options for button content.
12
- */
13
10
  export type ButtonAlignment = 'left' | 'center' | 'right'
14
11
 
15
- /**
16
- * Defines the structure of helper footer messages used for form validation.
17
- */
18
12
  export interface HelperFooterMessage {
19
- /** Indicates whether the message represents an error or success state */
20
13
  status: 'error' | 'success'
21
- /** The message to display in the status area */
22
14
  statusMessage: string
23
- /** The message to spread across multiple components */
24
15
  spreadMessage: string
25
- /** Priority of the spread message for determining which message to show */
26
16
  spreadMessagePriority: number
27
- /** The name of the form this message is associated with */
28
17
  formname: string
29
- /** Indicates if the field associated with this message is required */
30
18
  required: boolean
31
19
  }
32
20
 
33
- /**
34
- * Props for the CustomButton component.
35
- * Extends ButtonProps from Material-UI, omitting 'color' and 'variant'.
36
- */
37
21
  export interface CustomButtonProps
38
22
  extends Omit<ButtonProps, 'color' | 'variant'> {
39
- /** Text to display on the button */
40
23
  text?: string
41
- /** Background color of the button */
42
24
  backgroundcolor?: string
43
- /** Color of the button's outline */
44
25
  outlinecolor?: string
45
- /** Color of the button's text */
46
26
  fontcolor?: string
47
- /** Alignment of the button's text */
48
27
  fontlocation?: ButtonAlignment
49
- /** Typography variant for the button's text */
50
28
  fontvariant?:
51
29
  | 'arapeyh1'
52
30
  | 'arapeyh2'
@@ -75,32 +53,18 @@ export interface CustomButtonProps
75
53
  | 'merriparagraph'
76
54
  | 'merrihelperheader'
77
55
  | 'merrihelperfooter'
78
- /** Icon to display on the button. Set to false to hide the icon */
79
56
  icon?: React.ReactNode | false
80
- /** Color of the icon */
81
57
  iconcolor?: string
82
- /** Size of the icon */
83
58
  iconsize?: string
84
- /** Position of the icon relative to the text */
85
59
  iconlocation?: 'left' | 'top' | 'right'
86
- /** Style variant of the button */
87
60
  variant?: 'text' | 'outlined' | 'contained'
88
- /** Function to call when the button is clicked */
89
61
  onClick?: () => void
90
- /** Helper footer message for form validation */
91
62
  helperfooter?: HelperFooterMessage
92
- /** Width of the button */
93
63
  width?: string
94
- /** Name of the form this button is associated with */
95
64
  formname?: string
96
- /** Name attribute for the button element */
97
65
  name?: string
98
66
  }
99
67
 
100
- /**
101
- * CustomButton component renders a customizable button with integrated form validation.
102
- * It displays error messages based on helper footers and form validation status.
103
- */
104
68
  const CustomButton: React.FC<CustomButtonProps> = props => {
105
69
  const {
106
70
  text,
@@ -121,24 +85,16 @@ const CustomButton: React.FC<CustomButtonProps> = props => {
121
85
  width,
122
86
  } = props
123
87
 
124
- /** State for storing the current error message */
125
88
  const [errorMessage, setErrorMessage] = useState<string | undefined>(
126
89
  undefined
127
90
  )
128
- /** State for tracking whether the associated form is valid */
129
91
  const [isFormValid, setIsFormValid] = useState<boolean>(true)
130
- /** State for storing helper footer messages */
131
92
  const [helperFooterValue, setHelperFooterValue] = useState<
132
93
  Record<string, HelperFooterMessage>
133
94
  >({})
134
95
 
135
- /**
136
- * Updates the form validation status and error message based on helper footers.
137
- * This function filters relevant footers, checks for errors and empty required fields,
138
- * and updates the error message and form validity accordingly.
139
- */
140
96
  const updateFormValidation = useCallback(() => {
141
- if (formname) {
97
+ if (formname && helperFooterValue) {
142
98
  const relevantFooters = Object.values(helperFooterValue).filter(
143
99
  footer => footer?.formname === formname
144
100
  )
@@ -168,13 +124,12 @@ const CustomButton: React.FC<CustomButtonProps> = props => {
168
124
  setErrorMessage(undefined)
169
125
  setIsFormValid(true)
170
126
  }
127
+ } else {
128
+ setErrorMessage(undefined)
129
+ setIsFormValid(true)
171
130
  }
172
131
  }, [formname, helperFooterValue])
173
132
 
174
- /**
175
- * Fetches helper footer data from the cache when formname changes.
176
- * This effect runs whenever the formname prop changes.
177
- */
178
133
  useEffect(() => {
179
134
  const fetchHelperFooter = async () => {
180
135
  const helperFooterResult = await get('helperFooter', 'client')
@@ -189,28 +144,18 @@ const CustomButton: React.FC<CustomButtonProps> = props => {
189
144
  HelperFooterMessage
190
145
  >
191
146
  )
147
+ } else {
148
+ setHelperFooterValue({})
192
149
  }
193
150
  }
194
151
 
195
152
  fetchHelperFooter()
196
153
  }, [formname])
197
154
 
198
- /**
199
- * Triggers form validation whenever helperFooterValue changes.
200
- * This effect ensures that the form validation is updated whenever
201
- * the helper footer messages change.
202
- */
203
155
  useEffect(() => {
204
156
  updateFormValidation()
205
157
  }, [updateFormValidation])
206
158
 
207
- /**
208
- * Renders the icon element based on the provided icon prop.
209
- * If the icon prop is false, it returns null.
210
- * If the icon is a valid React element, it clones it with the specified size.
211
- * Otherwise, it renders a default StarIcon.
212
- * @returns The rendered icon element or null.
213
- */
214
159
  const renderIcon = () => {
215
160
  if (icon === false) {
216
161
  return null
@@ -223,10 +168,6 @@ const CustomButton: React.FC<CustomButtonProps> = props => {
223
168
  return <StarIcon style={{ fontSize: iconsize }} />
224
169
  }
225
170
 
226
- /**
227
- * Handles the button click event.
228
- * If the form is valid and an onClick handler is provided, it calls the handler.
229
- */
230
171
  const handleButtonClick = async () => {
231
172
  if (!isFormValid) {
232
173
  return
@@ -1,172 +1,187 @@
1
- // File: src/components/PopupForm/index.tsx
2
1
  'use client'
3
- import React, { forwardRef, useImperativeHandle, useRef } from 'react'
2
+
3
+ import React, {
4
+ forwardRef,
5
+ useImperativeHandle,
6
+ useRef,
7
+ useMemo,
8
+ useCallback,
9
+ } from 'react'
4
10
  import { Close } from '@mui/icons-material'
5
11
  import { Dialog, IconButton, Box } from '@mui/material'
6
12
  import ContentSection, { ContentSectionProps } from '../../Content'
7
13
  import { formContainerStyle } from './../../../styles/Form'
8
14
  import { ExtendedTypographyProps } from '../../Content/Structure/typography/useGridTypography'
9
- import Typography from '../../Typography'
10
15
 
11
16
  /**
12
17
  * Props for the PopupForm component.
18
+ * @interface PopupFormProps
13
19
  */
14
20
  export interface PopupFormProps {
15
- /** Title of the popup form */
21
+ /** The title of the popup form */
16
22
  title?: string
17
- /** Description of the popup form */
23
+ /** The description of the popup form */
18
24
  description?: string
19
- /** Boolean to control the open state of the dialog */
20
- open: boolean
21
- /** Function to call when closing the dialog */
22
- onClose: () => void
23
- /** ContentSectionProps to render the form content */
25
+ /** The grid configuration for the form content */
24
26
  grids?: ContentSectionProps['grids']
25
- /** Optional function to handle form submission */
26
- onSubmit?: () => void
27
- /** Optional direct content to render */
27
+ /** Callback function to handle form submission */
28
+ onSubmit?: (event: React.FormEvent<HTMLFormElement>) => void
29
+ /** Custom content to render inside the form */
28
30
  content?: React.ReactNode
31
+ /** The type of popup to render ('dialog' or 'modal') */
32
+ popupType: 'dialog' | 'modal'
33
+ /** Whether the popup is open (only applicable for 'dialog' type) */
34
+ open?: boolean
35
+ /** Callback function to handle closing the popup (only applicable for 'dialog' type) */
36
+ onClose?: () => void
29
37
  }
30
38
 
31
39
  /**
32
- * PopupForm component renders a popup form with a title, description, and content sections.
33
- * It uses the ContentSection component to render the form content within a Material-UI Dialog.
34
- * Handles form submission and displays submitted data internally.
40
+ * PopupForm Component
41
+ *
42
+ * A flexible popup form component that can be rendered as either a dialog or a modal.
43
+ * It supports custom content, grids, and header configuration.
35
44
  *
36
- * @param props The props for the PopupForm component.
37
- * @param ref Ref forwarded to the form element.
38
- * @returns The rendered popup form.
45
+ * @component
46
+ * @example
47
+ * <PopupForm
48
+ * title="Login"
49
+ * description="Please enter your credentials"
50
+ * popupType="dialog"
51
+ * open={isOpen}
52
+ * onClose={handleClose}
53
+ * onSubmit={handleSubmit}
54
+ * content={<LoginForm />}
55
+ * />
39
56
  */
40
57
  const PopupForm = forwardRef<HTMLFormElement, PopupFormProps>(
41
- ({ title, description, open, onClose, grids, onSubmit, content }, ref) => {
42
- const [submittedData, setSubmittedData] = React.useState<
43
- Record<string, string>
44
- >({})
58
+ (
59
+ { title, description, grids, onSubmit, content, popupType, open, onClose },
60
+ ref
61
+ ) => {
45
62
  const internalFormRef = useRef<HTMLFormElement>(null)
46
63
 
64
+ // Expose the internal form ref to the parent component
47
65
  useImperativeHandle(ref, () => internalFormRef.current as HTMLFormElement)
48
66
 
49
67
  /**
50
- * headerGrid contains the grid configuration for the form header.
51
- * It includes the title and description as typography items.
68
+ * Memoized header grid configuration
52
69
  */
53
- const headerGrid: ContentSectionProps['grids'][0] = {
54
- grid: {
55
- gridconfig: {
56
- gridname: 'formHeader',
57
- marginbottom: 1,
58
- gridwidth: '100%',
59
- },
60
- },
61
- typography: [
62
- {
63
- text: title,
64
- fontvariant: 'merrih5',
65
- fontcolor: 'black',
66
- columnconfig: {
67
- column: 1,
70
+ const headerGrid: ContentSectionProps['grids'][0] = useMemo(
71
+ () => ({
72
+ grid: {
73
+ gridconfig: {
68
74
  gridname: 'formHeader',
69
- columnwidth: '100%',
70
- alignment: 'left',
71
- marginbottom: 0.5,
75
+ marginbottom: 1,
76
+ gridwidth: '100%',
72
77
  },
73
78
  },
74
- {
75
- text: description,
76
- fontvariant: 'merriparagraph',
77
- fontcolor: 'black',
78
- columnconfig: {
79
- column: 1,
80
- gridname: 'formHeader',
81
- columnwidth: '100%',
79
+ typography: [
80
+ {
81
+ text: title,
82
+ fontvariant: 'merrih5',
83
+ fontcolor: 'black',
84
+ columnconfig: {
85
+ row: 1,
86
+ column: 1,
87
+ gridname: 'formHeader',
88
+ columnwidth: '100%',
89
+ alignment: 'left',
90
+ marginbottom: 1.5,
91
+ },
82
92
  },
83
- },
84
- ] as ExtendedTypographyProps[],
85
- }
86
-
87
- /** Combine the header grid with the provided content grids */
88
- const contentSectionGrids: ContentSectionProps['grids'] = [
89
- headerGrid,
90
- ...(grids || []),
91
- ]
93
+ {
94
+ text: description,
95
+ fontvariant: 'merriparagraph',
96
+ fontcolor: 'black',
97
+ columnconfig: {
98
+ row: 2,
99
+ column: 1,
100
+ alignment: 'left',
101
+ gridname: 'formHeader',
102
+ columnwidth: '100%',
103
+ },
104
+ },
105
+ ] as ExtendedTypographyProps[],
106
+ }),
107
+ [title, description]
108
+ )
92
109
 
93
110
  /**
94
- * Handles form submission and processes form data internally.
95
- * @param event - The form submission event
111
+ * Handle form submission
112
+ * @param {React.FormEvent<HTMLFormElement>} event - The form submission event
96
113
  */
97
- const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
98
- event.preventDefault()
99
- const formData = new FormData(event.currentTarget)
100
- const data: Record<string, string> = {}
101
-
102
- formData.forEach((value, key) => {
103
- data[key] = value.toString()
104
- })
105
-
106
- setSubmittedData(data)
107
-
108
- if (onSubmit) {
109
- onSubmit()
110
- }
111
- }
114
+ const handleSubmit = useCallback(
115
+ (event: React.FormEvent<HTMLFormElement>) => {
116
+ event.preventDefault()
117
+ if (onSubmit) {
118
+ onSubmit(event)
119
+ }
120
+ },
121
+ [onSubmit]
122
+ )
112
123
 
113
124
  /**
114
- * Renders the header typography elements
125
+ * Memoized header render function
115
126
  */
116
- const renderHeaderTypography = () => {
117
- if (Array.isArray(headerGrid.typography)) {
118
- return headerGrid.typography.map(
119
- (typo: ExtendedTypographyProps, index: number) => (
120
- <Typography key={index} {...typo} />
121
- )
122
- )
123
- } else if (headerGrid.typography) {
124
- return <Typography {...headerGrid.typography} />
125
- }
126
- return null
127
- }
127
+ const renderHeader = useMemo(
128
+ () => <ContentSection grids={[headerGrid]} />,
129
+ [headerGrid]
130
+ )
128
131
 
129
- return (
130
- <Dialog open={open} onClose={onClose} fullWidth maxWidth="sm">
131
- <IconButton
132
- size="small"
133
- onClick={onClose}
134
- sx={{
135
- position: 'absolute',
136
- right: 8,
137
- top: 8,
138
- color: theme => theme.palette.grey[500],
139
- }}
140
- >
141
- <Close />
142
- </IconButton>
132
+ /**
133
+ * Memoized content render function
134
+ */
135
+ const renderContent = useMemo(
136
+ () => (
143
137
  <Box
144
138
  // @ts-ignore
145
139
  sx={formContainerStyle}
146
140
  >
141
+ <Box mb={0}>{renderHeader}</Box>
147
142
  <form onSubmit={handleSubmit} ref={internalFormRef}>
148
- {content ? (
149
- <>
150
- {renderHeaderTypography()}
151
- {content}
152
- </>
153
- ) : (
154
- <ContentSection grids={contentSectionGrids} />
155
- )}
143
+ {React.isValidElement(content)
144
+ ? React.cloneElement(content as React.ReactElement, {
145
+ onSubmit: handleSubmit,
146
+ })
147
+ : content || (grids && <ContentSection grids={grids} />)}
156
148
  </form>
157
- {Object.keys(submittedData).length > 0 && (
158
- <Box mt={2}>
159
- <Typography fontvariant="merrih6" text="Submitted Data:" />
160
- {Object.entries(submittedData).map(([key, value]) => (
161
- <Typography
162
- key={key}
163
- fontvariant="merriparagraph"
164
- text={`${key}: ${value}`}
165
- />
166
- ))}
167
- </Box>
168
- )}
169
149
  </Box>
150
+ ),
151
+ [renderHeader, handleSubmit, content, grids]
152
+ )
153
+
154
+ if (popupType === 'modal') {
155
+ return (
156
+ <Dialog
157
+ open={true}
158
+ fullWidth
159
+ maxWidth="sm"
160
+ disableEscapeKeyDown
161
+ hideBackdrop
162
+ >
163
+ {renderContent}
164
+ </Dialog>
165
+ )
166
+ }
167
+
168
+ return (
169
+ <Dialog open={open || false} onClose={onClose} fullWidth maxWidth="sm">
170
+ {onClose && (
171
+ <IconButton
172
+ size="small"
173
+ onClick={onClose}
174
+ sx={{
175
+ position: 'absolute',
176
+ right: 8,
177
+ top: 8,
178
+ color: theme => theme.palette.grey[500],
179
+ }}
180
+ >
181
+ <Close />
182
+ </IconButton>
183
+ )}
184
+ {renderContent}
170
185
  </Dialog>
171
186
  )
172
187
  }
@@ -1,22 +1,31 @@
1
1
  'use client'
2
2
 
3
- import { useCallback } from 'react'
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 React from 'react'
6
+ import { get, set, StringValue } from 'goobs-cache'
6
7
 
7
8
  /**
8
- * Validates if the given 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
9
+ * Validates if the given string is a valid email format.
10
+ *
11
+ * @param {string} email - The email string to validate.
12
+ * @returns {boolean} True if the email is valid, false otherwise.
11
13
  */
12
14
  const isValidEmailFormat = (email: string): boolean => {
13
15
  const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
14
- const result = emailRegex.test(email)
15
- return result
16
+ return emailRegex.test(email)
16
17
  }
17
18
 
18
19
  /**
19
- * Interface for helper footer messages
20
+ * Represents a message for the helper footer.
21
+ *
22
+ * @interface HelperFooterMessage
23
+ * @property {('error' | 'success')} status - The status of the message.
24
+ * @property {string} statusMessage - The detailed status message.
25
+ * @property {string} spreadMessage - A condensed version of the message for spreading.
26
+ * @property {number} spreadMessagePriority - The priority of the spread message.
27
+ * @property {string} formname - The name of the form this message is associated with.
28
+ * @property {boolean} required - Indicates if the field associated with this message is required.
20
29
  */
21
30
  export interface HelperFooterMessage {
22
31
  status: 'error' | 'success'
@@ -28,12 +37,25 @@ export interface HelperFooterMessage {
28
37
  }
29
38
 
30
39
  /**
31
- * Custom hook for form validation and helper footer messages
32
- * @returns {Object} An object containing the validateField function
40
+ * A custom hook for managing form validation and helper messages.
41
+ *
42
+ * @returns {Object} An object containing validation functions and helper footer state.
33
43
  */
34
44
  export const useHelperFooter = () => {
45
+ const [helperFooterValue, setHelperFooterValue] = useState<
46
+ Record<string, HelperFooterMessage>
47
+ >({})
48
+ const helperFooterRef = useRef<Record<string, HelperFooterMessage>>({})
49
+
35
50
  /**
36
- * Handles generic error creation for form fields
51
+ * Handles creation of generic error messages for form fields.
52
+ *
53
+ * @param {FormData} formData - The form data to validate.
54
+ * @param {string} name - The name of the form field.
55
+ * @param {string} label - The label of the form field.
56
+ * @param {boolean} required - Whether the field is required.
57
+ * @param {string} formname - The name of the form.
58
+ * @returns {HelperFooterMessage | undefined} An error message object if there's an error, undefined otherwise.
37
59
  */
38
60
  const handleGenericErrorCreation = useCallback(
39
61
  (
@@ -43,9 +65,10 @@ export const useHelperFooter = () => {
43
65
  required: boolean,
44
66
  formname: string
45
67
  ): HelperFooterMessage | undefined => {
68
+ console.log(`Handling generic error creation for ${name}`)
46
69
  const value = formData.get(name) as string
47
-
48
70
  if (required && (!value || !value.trim())) {
71
+ console.log(`Error: ${label} is required`)
49
72
  return {
50
73
  status: 'error',
51
74
  statusMessage: `${label} is required. Please enter a ${label.toLowerCase()}.`,
@@ -55,14 +78,19 @@ export const useHelperFooter = () => {
55
78
  required,
56
79
  }
57
80
  }
58
-
81
+ console.log(`No error for ${name}`)
59
82
  return undefined
60
83
  },
61
84
  []
62
85
  )
63
86
 
64
87
  /**
65
- * Handles email validation and error creation
88
+ * Handles creation of email-specific error messages.
89
+ *
90
+ * @param {FormData} formData - The form data to validate.
91
+ * @param {boolean} required - Whether the email field is required.
92
+ * @param {string} formname - The name of the form.
93
+ * @returns {HelperFooterMessage | undefined} An error or success message object, or undefined if no message is needed.
66
94
  */
67
95
  const handleEmailErrorCreation = useCallback(
68
96
  (
@@ -70,9 +98,10 @@ export const useHelperFooter = () => {
70
98
  required: boolean,
71
99
  formname: string
72
100
  ): HelperFooterMessage | undefined => {
101
+ console.log('Handling email error creation')
73
102
  const email = formData.get('email') as string
74
-
75
103
  if (required && (!email || !email.trim())) {
104
+ console.log('Error: Email is required')
76
105
  return {
77
106
  status: 'error',
78
107
  statusMessage: 'Please enter an email address.',
@@ -82,12 +111,9 @@ export const useHelperFooter = () => {
82
111
  required,
83
112
  }
84
113
  }
85
-
86
- if (!email) {
87
- return undefined
88
- }
89
-
114
+ if (!email) return undefined
90
115
  if (email && !isValidEmailFormat(email)) {
116
+ console.log('Error: Invalid email format')
91
117
  return {
92
118
  status: 'error',
93
119
  statusMessage: 'Please enter a valid email address.',
@@ -97,7 +123,7 @@ export const useHelperFooter = () => {
97
123
  required,
98
124
  }
99
125
  }
100
-
126
+ console.log('Email is valid')
101
127
  return {
102
128
  status: 'success',
103
129
  statusMessage: 'Email is valid.',
@@ -111,25 +137,61 @@ export const useHelperFooter = () => {
111
137
  )
112
138
 
113
139
  /**
114
- * Handles password validation and error creation
140
+ * Handles creation of password-specific error messages and stores the password in the client cache.
141
+ *
142
+ * @param {FormData} formData - The form data to validate.
143
+ * @param {boolean} required - Whether the password field is required.
144
+ * @param {string} formname - The name of the form.
145
+ * @returns {HelperFooterMessage | undefined} An error or success message object, or undefined if no message is needed.
115
146
  */
116
147
  const handlePasswordErrorCreation = useCallback(
117
- async (
148
+ (
118
149
  formData: FormData,
119
150
  required: boolean,
120
151
  formname: string
121
- ): Promise<HelperFooterMessage | undefined> => {
152
+ ): HelperFooterMessage | undefined => {
153
+ console.log('Handling password error creation')
122
154
  const password = formData.get('verifyPassword') as string
155
+ console.log('Password received:', password)
156
+
157
+ const debouncedPasswordStorage = debounce(async () => {
158
+ console.log('Attempting to store password in goobs-cache client store')
159
+ try {
160
+ if (password) {
161
+ console.log('Setting password in goobs-cache:', password)
162
+ await set(
163
+ 'verifyPassword',
164
+ { type: 'string', value: password } as StringValue,
165
+ new Date(Date.now() + 30 * 60 * 1000),
166
+ 'client'
167
+ )
168
+ console.log('Password set operation completed')
169
+
170
+ // Immediately retrieve the password to verify it was stored
171
+ console.log('Attempting to retrieve password from client store')
172
+ const storedPassword = await get('verifyPassword', 'client')
173
+ console.log('Raw stored password result:', storedPassword)
174
+
175
+ if (storedPassword && storedPassword.value) {
176
+ console.log(
177
+ 'Immediately retrieved password from client store:',
178
+ storedPassword.value
179
+ )
180
+ } else {
181
+ console.log('Retrieved password is null or undefined')
182
+ }
183
+ } else {
184
+ console.log('Password is null or empty, not storing in goobs-cache')
185
+ }
186
+ } catch (error) {
187
+ console.error('Error interacting with goobs-cache:', error)
188
+ }
189
+ }, 2000)
123
190
 
124
- // Store the password in the cache
125
- await set(
126
- 'verifyPassword',
127
- { type: 'string', value: password } as StringValue,
128
- new Date(Date.now() + 30 * 60 * 1000),
129
- 'client'
130
- )
191
+ debouncedPasswordStorage()
131
192
 
132
193
  if (required && (!password || !password.trim())) {
194
+ console.log('Error: Password is required')
133
195
  return {
134
196
  status: 'error',
135
197
  statusMessage: 'Password is required.',
@@ -139,11 +201,7 @@ export const useHelperFooter = () => {
139
201
  required,
140
202
  }
141
203
  }
142
-
143
- if (!password) {
144
- return undefined
145
- }
146
-
204
+ if (!password) return undefined
147
205
  const passwordRegex =
148
206
  /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/
149
207
  const passwordComplexityStatus: 'error' | 'success' = passwordRegex.test(
@@ -151,8 +209,8 @@ export const useHelperFooter = () => {
151
209
  )
152
210
  ? 'success'
153
211
  : 'error'
154
-
155
212
  if (passwordComplexityStatus === 'error') {
213
+ console.log('Error: Invalid password complexity')
156
214
  return {
157
215
  status: 'error',
158
216
  statusMessage:
@@ -163,7 +221,7 @@ export const useHelperFooter = () => {
163
221
  required,
164
222
  }
165
223
  }
166
-
224
+ console.log('Password meets all requirements')
167
225
  return {
168
226
  status: 'success',
169
227
  statusMessage: 'Password meets all requirements.',
@@ -177,7 +235,12 @@ export const useHelperFooter = () => {
177
235
  )
178
236
 
179
237
  /**
180
- * Handles password confirmation validation and error creation
238
+ * Handles creation of confirm password error messages by comparing with the stored password.
239
+ *
240
+ * @param {FormData} formData - The form data to validate.
241
+ * @param {boolean} required - Whether the confirm password field is required.
242
+ * @param {string} formname - The name of the form.
243
+ * @returns {Promise<HelperFooterMessage | undefined>} A promise that resolves to an error or success message object, or undefined if no message is needed.
181
244
  */
182
245
  const handleConfirmPasswordErrorCreation = useCallback(
183
246
  async (
@@ -185,9 +248,12 @@ export const useHelperFooter = () => {
185
248
  required: boolean,
186
249
  formname: string
187
250
  ): Promise<HelperFooterMessage | undefined> => {
251
+ console.log('Handling confirm password error creation')
188
252
  const confirmPassword = formData.get('confirmPassword') as string
253
+ console.log('Confirm password received:', confirmPassword)
189
254
 
190
255
  if (required && (!confirmPassword || !confirmPassword.trim())) {
256
+ console.log('Error: Password confirmation is required')
191
257
  return {
192
258
  status: 'error',
193
259
  statusMessage: 'Please confirm your password.',
@@ -197,23 +263,38 @@ export const useHelperFooter = () => {
197
263
  required,
198
264
  }
199
265
  }
266
+ if (!confirmPassword) return undefined
200
267
 
201
- if (!confirmPassword) {
202
- return undefined
268
+ console.log(
269
+ 'Attempting to retrieve verify password from goobs-cache client store'
270
+ )
271
+ let verifyPasswordResult
272
+ try {
273
+ verifyPasswordResult = await get('verifyPassword', 'client')
274
+ console.log('Retrieved verify password result:', verifyPasswordResult)
275
+ } catch (error) {
276
+ console.error('Error retrieving password from goobs-cache:', error)
203
277
  }
204
278
 
205
- const verifyPasswordResult = await get('verifyPassword', 'client')
206
279
  let verifyPassword: string | undefined
207
-
208
280
  if (
209
281
  verifyPasswordResult &&
210
282
  typeof verifyPasswordResult === 'object' &&
211
- 'value' in verifyPasswordResult
283
+ 'value' in verifyPasswordResult &&
284
+ verifyPasswordResult.value &&
285
+ typeof verifyPasswordResult.value === 'object' &&
286
+ 'value' in verifyPasswordResult.value
212
287
  ) {
213
- verifyPassword = (verifyPasswordResult as StringValue).value
288
+ verifyPassword = verifyPasswordResult.value.value
289
+ console.log('Verify password from client store:', verifyPassword)
290
+ } else {
291
+ console.log(
292
+ 'Verify password not found in client store or in unexpected format'
293
+ )
214
294
  }
215
295
 
216
296
  if (!verifyPassword) {
297
+ console.log('Error: Password not set')
217
298
  return {
218
299
  status: 'error',
219
300
  statusMessage: 'Please enter your password first.',
@@ -225,6 +306,7 @@ export const useHelperFooter = () => {
225
306
  }
226
307
 
227
308
  if (confirmPassword !== verifyPassword) {
309
+ console.log('Error: Passwords do not match')
228
310
  return {
229
311
  status: 'error',
230
312
  statusMessage: 'Passwords do not match.',
@@ -235,6 +317,7 @@ export const useHelperFooter = () => {
235
317
  }
236
318
  }
237
319
 
320
+ console.log('Passwords match')
238
321
  return {
239
322
  status: 'success',
240
323
  statusMessage: 'Passwords match.',
@@ -248,7 +331,12 @@ export const useHelperFooter = () => {
248
331
  )
249
332
 
250
333
  /**
251
- * Handles phone number validation and error creation
334
+ * Handles creation of phone number error messages.
335
+ *
336
+ * @param {FormData} formData - The form data to validate.
337
+ * @param {boolean} required - Whether the phone number field is required.
338
+ * @param {string} formname - The name of the form.
339
+ * @returns {HelperFooterMessage | undefined} An error or success message object, or undefined if no message is needed.
252
340
  */
253
341
  const handlePhoneNumberErrorCreation = useCallback(
254
342
  (
@@ -256,9 +344,10 @@ export const useHelperFooter = () => {
256
344
  required: boolean,
257
345
  formname: string
258
346
  ): HelperFooterMessage | undefined => {
347
+ console.log('Handling phone number error creation')
259
348
  const phoneNumber = formData.get('phoneNumber') as string
260
-
261
349
  if (required && (!phoneNumber || !phoneNumber.trim())) {
350
+ console.log('Error: Phone number is required')
262
351
  return {
263
352
  status: 'error',
264
353
  statusMessage:
@@ -269,18 +358,14 @@ export const useHelperFooter = () => {
269
358
  required,
270
359
  }
271
360
  }
272
-
273
- if (!phoneNumber) {
274
- return undefined
275
- }
276
-
361
+ if (!phoneNumber) return undefined
277
362
  const digitsOnly = phoneNumber.replace(/[^\d]/g, '')
278
363
  const length = digitsOnly.length
279
-
280
364
  if (
281
365
  (length === 10 && !digitsOnly.startsWith('1')) ||
282
366
  (length === 11 && digitsOnly.startsWith('1'))
283
367
  ) {
368
+ console.log('Phone number is valid')
284
369
  return {
285
370
  status: 'success',
286
371
  statusMessage: 'Phone number is valid.',
@@ -290,6 +375,7 @@ export const useHelperFooter = () => {
290
375
  required,
291
376
  }
292
377
  } else {
378
+ console.log('Error: Invalid phone number format')
293
379
  return {
294
380
  status: 'error',
295
381
  statusMessage:
@@ -305,7 +391,13 @@ export const useHelperFooter = () => {
305
391
  )
306
392
 
307
393
  /**
308
- * Validates a form field and updates the helper footer messages
394
+ * Validates a specific field in the form.
395
+ *
396
+ * @param {string} name - The name of the field to validate.
397
+ * @param {FormData} formData - The form data to validate.
398
+ * @param {string} label - The label of the field.
399
+ * @param {boolean} required - Whether the field is required.
400
+ * @param {string} formname - The name of the form.
309
401
  */
310
402
  const validateField = useCallback(
311
403
  (
@@ -315,10 +407,9 @@ export const useHelperFooter = () => {
315
407
  required: boolean,
316
408
  formname: string
317
409
  ) => {
318
- const debouncedValidation = debounce(async () => {
410
+ console.log(`Validating field: ${name}`)
411
+ const validation = () => {
319
412
  let validationResult: HelperFooterMessage | undefined
320
-
321
- // Determine which validation function to use based on the field name
322
413
  switch (name) {
323
414
  case 'email':
324
415
  validationResult = handleEmailErrorCreation(
@@ -328,19 +419,22 @@ export const useHelperFooter = () => {
328
419
  )
329
420
  break
330
421
  case 'verifyPassword':
331
- validationResult = await handlePasswordErrorCreation(
422
+ validationResult = handlePasswordErrorCreation(
332
423
  formData,
333
424
  required,
334
425
  formname
335
426
  )
336
427
  break
337
428
  case 'confirmPassword':
338
- validationResult = await handleConfirmPasswordErrorCreation(
429
+ handleConfirmPasswordErrorCreation(
339
430
  formData,
340
431
  required,
341
432
  formname
342
- )
343
- break
433
+ ).then(result => {
434
+ validationResult = result
435
+ updateHelperFooter(name, validationResult)
436
+ })
437
+ return
344
438
  case 'phoneNumber':
345
439
  validationResult = handlePhoneNumberErrorCreation(
346
440
  formData,
@@ -358,33 +452,10 @@ export const useHelperFooter = () => {
358
452
  )
359
453
  }
360
454
 
361
- // Update the helper footer messages in the cache
362
- const helperFooterResult = await get('helperFooter', 'client')
363
- let currentHelperFooter: Record<string, HelperFooterMessage> = {}
364
- if (
365
- helperFooterResult &&
366
- typeof helperFooterResult === 'object' &&
367
- 'value' in helperFooterResult
368
- ) {
369
- currentHelperFooter = (helperFooterResult as JSONValue)
370
- .value as Record<string, HelperFooterMessage>
371
- }
372
-
373
- if (validationResult) {
374
- currentHelperFooter[name] = validationResult
375
- } else {
376
- delete currentHelperFooter[name]
377
- }
378
-
379
- await set(
380
- 'helperFooter',
381
- { type: 'json', value: currentHelperFooter } as JSONValue,
382
- new Date(Date.now() + 30 * 60 * 1000), // 30 minutes expiration
383
- 'client'
384
- )
385
- }, 300)
455
+ updateHelperFooter(name, validationResult)
456
+ }
386
457
 
387
- debouncedValidation()
458
+ validation()
388
459
  },
389
460
  [
390
461
  handleEmailErrorCreation,
@@ -395,7 +466,72 @@ export const useHelperFooter = () => {
395
466
  ]
396
467
  )
397
468
 
398
- return {
399
- validateField,
469
+ const updateHelperFooter = (
470
+ name: string,
471
+ validationResult: HelperFooterMessage | undefined
472
+ ) => {
473
+ if (validationResult) {
474
+ helperFooterRef.current = {
475
+ ...helperFooterRef.current,
476
+ [name]: validationResult,
477
+ }
478
+ } else {
479
+ helperFooterRef.current = Object.fromEntries(
480
+ Object.entries(helperFooterRef.current).filter(([key]) => key !== name)
481
+ )
482
+ }
483
+
484
+ console.log('Updated helperFooterRef:', helperFooterRef.current)
485
+ setHelperFooterValue({ ...helperFooterRef.current })
486
+ console.log('Helper footer updated in state')
487
+ }
488
+
489
+ const validateRequiredField = useCallback(
490
+ (required: boolean, formname: string, name: string, label: string) => {
491
+ console.log('validateRequiredField: Starting', {
492
+ required,
493
+ formname,
494
+ name,
495
+ label,
496
+ })
497
+ if (required && formname && name && label) {
498
+ console.log('validateRequiredField: Validating required field')
499
+ const emptyFormData = new FormData()
500
+ emptyFormData.append(name, '')
501
+ validateField(name, emptyFormData, label, required, formname)
502
+ } else {
503
+ console.log(
504
+ 'validateRequiredField: Not validating (missing required props)'
505
+ )
506
+ }
507
+ },
508
+ [validateField]
509
+ )
510
+
511
+ const useShowErrorEffect = (
512
+ formSubmitted: boolean,
513
+ hasInput: boolean,
514
+ isFocused: boolean,
515
+ setShowError: React.Dispatch<React.SetStateAction<boolean>>
516
+ ) => {
517
+ useEffect(() => {
518
+ const showError = formSubmitted || (hasInput && !isFocused)
519
+ console.log('useShowErrorEffect: Setting showError to', showError, {
520
+ formSubmitted,
521
+ hasInput,
522
+ isFocused,
523
+ })
524
+ setShowError(showError)
525
+ }, [formSubmitted, hasInput, isFocused, setShowError])
400
526
  }
527
+
528
+ return useMemo(
529
+ () => ({
530
+ validateField,
531
+ validateRequiredField,
532
+ helperFooterValue,
533
+ useShowErrorEffect,
534
+ }),
535
+ [validateField, validateRequiredField, helperFooterValue]
536
+ )
401
537
  }
@@ -1,6 +1,6 @@
1
1
  'use client'
2
2
 
3
- import React, { useState, useRef } from 'react'
3
+ import React, { useState, useRef, useEffect } from 'react'
4
4
  import { Box, InputLabel, OutlinedInput, styled } from '@mui/material'
5
5
  import { useDropdown } from './hooks/useDropdown'
6
6
  import { usePhoneNumber } from './hooks/usePhoneNumber'
@@ -14,13 +14,7 @@ import {
14
14
  HelperFooterMessage,
15
15
  } from './helperfooter/useHelperFooter'
16
16
  import labelStyles from '../../styles/StyledComponent/Label'
17
- import {
18
- useHelperFooterEffect,
19
- useHasInputEffect,
20
- usePreventAutocompleteEffect,
21
- useValidateRequiredEffect,
22
- useShowErrorEffect,
23
- } from './useEffects'
17
+ import { useHasInputEffect, usePreventAutocompleteEffect } from './useEffects'
24
18
 
25
19
  /**
26
20
  * Props interface for the StyledComponent
@@ -118,10 +112,16 @@ const StyledComponent: React.FC<StyledComponentProps> = props => {
118
112
  'aria-describedby': ariaDescribedBy,
119
113
  } = props
120
114
 
121
- const { validateField } = useHelperFooter()
122
- const [helperFooterValue, setHelperFooterValue] = useState<
123
- Record<string, HelperFooterMessage>
124
- >({})
115
+ console.log('StyledComponent: Initializing with props', {
116
+ name,
117
+ label,
118
+ required,
119
+ formname,
120
+ formSubmitted,
121
+ })
122
+
123
+ const { validateField, validateRequiredField, helperFooterValue } =
124
+ useHelperFooter()
125
125
  const [isFocused, setIsFocused] = useState(false)
126
126
  const [hasInput, setHasInput] = useState(false)
127
127
  const [showError, setShowError] = useState(false)
@@ -142,13 +142,25 @@ const StyledComponent: React.FC<StyledComponentProps> = props => {
142
142
  } = useSplitButton(props)
143
143
 
144
144
  // useEffect hooks
145
- useHelperFooterEffect(setHelperFooterValue)
146
145
  useHasInputEffect(value, valuestatus, setHasInput)
147
146
  usePreventAutocompleteEffect(inputRefInternal)
148
- useValidateRequiredEffect(required, formname, name, label)
149
- useShowErrorEffect(formSubmitted, hasInput, isFocused, setShowError)
147
+
148
+ useEffect(() => {
149
+ if (required && formname && name && label) {
150
+ validateRequiredField(required, formname, name, label)
151
+ }
152
+ }, [required, formname, name, label, validateRequiredField])
153
+
154
+ useEffect(() => {
155
+ const timer = setTimeout(() => {
156
+ setShowError(formSubmitted || hasInput)
157
+ }, 1000)
158
+
159
+ return () => clearTimeout(timer)
160
+ }, [formSubmitted, hasInput])
150
161
 
151
162
  const currentHelperFooter = name ? helperFooterValue[name] : undefined
163
+ console.log('StyledComponent: Current helper footer', currentHelperFooter)
152
164
 
153
165
  /**
154
166
  * Handle the change event of the input element.
@@ -157,6 +169,11 @@ const StyledComponent: React.FC<StyledComponentProps> = props => {
157
169
  const handleChange = (
158
170
  e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
159
171
  ) => {
172
+ console.log('StyledComponent: handleChange called', {
173
+ name: e.target.name,
174
+ value: e.target.value,
175
+ })
176
+
160
177
  if (componentvariant === 'phonenumber') {
161
178
  handlePhoneNumberChange(e)
162
179
  } else if (componentvariant === 'splitbutton') {
@@ -170,6 +187,12 @@ const StyledComponent: React.FC<StyledComponentProps> = props => {
170
187
  const formData = new FormData()
171
188
  formData.append(e.target.name, e.target.value)
172
189
  if (name && label && formname) {
190
+ console.log('StyledComponent: Calling validateField', {
191
+ name,
192
+ label,
193
+ required,
194
+ formname,
195
+ })
173
196
  validateField(name, formData, label, required, formname)
174
197
  }
175
198
  }
@@ -178,6 +201,7 @@ const StyledComponent: React.FC<StyledComponentProps> = props => {
178
201
  * Handle the focus event of the input element.
179
202
  */
180
203
  const handleFocus = () => {
204
+ console.log('StyledComponent: handleFocus called')
181
205
  setIsFocused(true)
182
206
  }
183
207
 
@@ -185,8 +209,15 @@ const StyledComponent: React.FC<StyledComponentProps> = props => {
185
209
  * Handle the blur event of the input element.
186
210
  */
187
211
  const handleBlur = () => {
212
+ console.log('StyledComponent: handleBlur called')
188
213
  setIsFocused(false)
189
214
  if (name && label && !hasInput && formname) {
215
+ console.log('StyledComponent: Calling validateField on blur', {
216
+ name,
217
+ label,
218
+ required,
219
+ formname,
220
+ })
190
221
  const formData = new FormData()
191
222
  formData.append(name, '')
192
223
  validateField(name, formData, label, required, formname)
@@ -213,6 +244,14 @@ const StyledComponent: React.FC<StyledComponentProps> = props => {
213
244
  hasInput ||
214
245
  componentvariant === 'phonenumber'
215
246
 
247
+ console.log('StyledComponent: Rendering', {
248
+ name,
249
+ showError,
250
+ hasHelperFooter: !!currentHelperFooter,
251
+ helperFooterStatus: currentHelperFooter?.status,
252
+ helperFooterMessage: currentHelperFooter?.statusMessage,
253
+ })
254
+
216
255
  return (
217
256
  <Box
218
257
  sx={{
@@ -1,76 +1,54 @@
1
1
  import React, { useEffect } from 'react'
2
- import { get, JSONValue } from 'goobs-cache'
3
- import {
4
- HelperFooterMessage,
5
- useHelperFooter,
6
- } from '../helperfooter/useHelperFooter'
7
-
8
- export const useHelperFooterEffect = (
9
- setHelperFooterValue: React.Dispatch<
10
- React.SetStateAction<Record<string, HelperFooterMessage>>
11
- >
12
- ) => {
13
- useEffect(() => {
14
- const fetchHelperFooter = async () => {
15
- const result = await get('helperFooter', 'client')
16
- if (result && typeof result === 'object' && 'value' in result) {
17
- setHelperFooterValue(
18
- (result as JSONValue).value as Record<string, HelperFooterMessage>
19
- )
20
- }
21
- }
22
- fetchHelperFooter()
23
- }, [setHelperFooterValue])
24
- }
25
2
 
3
+ /**
4
+ * Custom hook that tracks whether an input field has a value or a specific status.
5
+ *
6
+ * @param {string | undefined} value - The current value of the input field.
7
+ * @param {boolean | undefined} valuestatus - A boolean status associated with the input field.
8
+ * @param {React.Dispatch<React.SetStateAction<boolean>>} setHasInput - State setter function to update the hasInput state.
9
+ *
10
+ * @example
11
+ * const [hasInput, setHasInput] = useState(false);
12
+ * useHasInputEffect(inputValue, inputStatus, setHasInput);
13
+ */
26
14
  export const useHasInputEffect = (
27
15
  value: string | undefined,
28
16
  valuestatus: boolean | undefined,
29
17
  setHasInput: React.Dispatch<React.SetStateAction<boolean>>
30
18
  ) => {
31
19
  useEffect(() => {
32
- setHasInput(!!value || !!valuestatus)
20
+ const hasInput = !!value || !!valuestatus
21
+ console.log('useHasInputEffect: Setting hasInput to', hasInput)
22
+ setHasInput(hasInput)
33
23
  }, [value, valuestatus, setHasInput])
34
24
  }
35
25
 
26
+ /**
27
+ * Custom hook that prevents autocomplete, autocorrect, autocapitalize, and spellcheck on an input field.
28
+ * This is particularly useful for password fields or other sensitive inputs.
29
+ *
30
+ * @param {React.RefObject<HTMLInputElement>} inputRefInternal - React ref object for the input element.
31
+ *
32
+ * @example
33
+ * const inputRef = useRef<HTMLInputElement>(null);
34
+ * usePreventAutocompleteEffect(inputRef);
35
+ * // In your JSX:
36
+ * <input ref={inputRef} ... />
37
+ */
36
38
  export const usePreventAutocompleteEffect = (
37
39
  inputRefInternal: React.RefObject<HTMLInputElement>
38
40
  ) => {
39
41
  useEffect(() => {
42
+ console.log('usePreventAutocompleteEffect: Starting effect')
40
43
  const input = inputRefInternal.current
41
44
  if (input) {
45
+ console.log('usePreventAutocompleteEffect: Setting input attributes')
42
46
  input.setAttribute('autocomplete', 'new-password')
43
47
  input.setAttribute('autocorrect', 'off')
44
48
  input.setAttribute('autocapitalize', 'none')
45
49
  input.setAttribute('spellcheck', 'false')
50
+ } else {
51
+ console.log('usePreventAutocompleteEffect: Input ref is null')
46
52
  }
47
53
  }, [inputRefInternal])
48
54
  }
49
-
50
- export const useValidateRequiredEffect = (
51
- required: boolean | undefined,
52
- formname: string | undefined,
53
- name: string | undefined,
54
- label: string | undefined
55
- ) => {
56
- const { validateField } = useHelperFooter()
57
-
58
- useEffect(() => {
59
- if (required && formname && name && label) {
60
- const emptyFormData = new FormData()
61
- emptyFormData.append(name, '')
62
- validateField(name, emptyFormData, label, required, formname)
63
- }
64
- }, [required, formname, name, label, validateField])
65
- }
66
-
67
- export const useShowErrorEffect = (
68
- formSubmitted: boolean,
69
- hasInput: boolean,
70
- isFocused: boolean,
71
- setShowError: React.Dispatch<React.SetStateAction<boolean>>
72
- ) => {
73
- useEffect(() => {
74
- setShowError(formSubmitted || (hasInput && !isFocused))
75
- }, [formSubmitted, hasInput, isFocused, setShowError])
76
- }