goobs-frontend 0.7.63 → 0.7.65

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,21 +1,12 @@
1
- import React, { useState, useEffect, useCallback } from 'react'
1
+ import React, { useMemo, useCallback, useEffect, useState } 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
8
  export type ButtonAlignment = 'left' | 'center' | 'right'
9
9
 
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
-
19
10
  export interface CustomButtonProps
20
11
  extends Omit<ButtonProps, 'color' | 'variant'> {
21
12
  text?: string
@@ -57,191 +48,138 @@ export interface CustomButtonProps
57
48
  iconlocation?: 'left' | 'top' | 'right'
58
49
  variant?: 'text' | 'outlined' | 'contained'
59
50
  onClick?: () => void
60
- helperfooter?: HelperFooterMessage
61
51
  width?: string
62
52
  formname?: string
63
53
  name?: string
64
54
  }
65
55
 
66
- const CustomButton: React.FC<CustomButtonProps> = props => {
67
- const {
68
- text,
69
- variant,
70
- fontvariant = 'merriparagraph',
71
- icon,
72
- iconlocation,
73
- iconsize,
74
- type,
75
- onClick,
76
- fontcolor,
77
- name,
78
- formname,
79
- outlinecolor,
80
- backgroundcolor,
81
- fontlocation,
82
- iconcolor,
83
- width,
84
- } = props
56
+ const CustomButton: React.FC<CustomButtonProps> = React.memo(
57
+ props => {
58
+ const {
59
+ text,
60
+ variant,
61
+ fontvariant = 'merriparagraph',
62
+ icon,
63
+ iconlocation,
64
+ iconsize,
65
+ type,
66
+ onClick,
67
+ fontcolor,
68
+ name,
69
+ formname,
70
+ outlinecolor,
71
+ backgroundcolor,
72
+ fontlocation,
73
+ iconcolor,
74
+ width,
75
+ } = props
85
76
 
86
- const [errorMessage, setErrorMessage] = useState<string | undefined>(
87
- undefined
88
- )
89
- const [, setIsFormValid] = useState<boolean>(true)
90
- const [hasBeenClicked, setHasBeenClicked] = useState<boolean>(false)
77
+ const [isFormFinished, setIsFormFinished] = useState<boolean>(false)
78
+ const [isCheckingForm, setIsCheckingForm] = useState<boolean>(true)
91
79
 
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)
80
+ const {
81
+ updateFormValidation,
82
+ checkFormStatus,
83
+ getEmptyRequiredFields,
84
+ fetchHelperFooters,
85
+ } = useHelperFooter(formname)
100
86
 
101
- // Wait for 2 seconds before fetching to allow time for cache update
102
- await new Promise(resolve => setTimeout(resolve, 3000))
87
+ const checkFormState = useCallback(async (): Promise<void> => {
88
+ console.log('CustomButton: Checking form state...')
89
+ setIsCheckingForm(true)
103
90
 
104
- const helperFooterResult = await get('helperfooter', formname, 'client')
105
- console.log('CustomButton: Helper footer result:', helperFooterResult)
91
+ const formStatus = await checkFormStatus()
92
+ const emptyFields = await getEmptyRequiredFields()
93
+ const helperFooters = await fetchHelperFooters()
106
94
 
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)
95
+ console.log('CustomButton: Form status:', formStatus)
96
+ console.log('CustomButton: Empty fields:', emptyFields)
97
+ console.log('CustomButton: Helper footers:', helperFooters)
141
98
 
142
- console.log('CustomButton: Valid helper footers:', helperFooters)
143
- return helperFooters
144
- }
99
+ const newIsFormFinished =
100
+ formStatus &&
101
+ emptyFields.length === 0 &&
102
+ (!helperFooters || Object.keys(helperFooters).length === 0)
145
103
 
146
- console.log('CustomButton: No valid helper footers found in cache')
147
- return []
148
- }, [formname])
104
+ setIsFormFinished(newIsFormFinished)
105
+ setIsCheckingForm(false)
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
107
+ console.log(
108
+ 'CustomButton: Form status changed. Is form finished:',
109
+ newIsFormFinished
173
110
  )
174
- setErrorMessage(highestPriorityError.spreadMessage)
175
- setIsFormValid(false)
176
- return false
177
- }
111
+ }, [checkFormStatus, getEmptyRequiredFields, fetchHelperFooters])
178
112
 
179
- console.log('CustomButton: No error footers found, form is valid')
180
- setErrorMessage(undefined)
181
- setIsFormValid(true)
182
- return true
183
- }, [fetchHelperFooters])
113
+ useEffect(() => {
114
+ console.log('CustomButton: Performing initial check')
115
+ checkFormState()
116
+ }, [checkFormState])
184
117
 
185
- useEffect(() => {
186
- console.log('CustomButton: Running effect to update form validation')
187
- void updateFormValidation()
188
- }, [updateFormValidation])
118
+ const handleButtonClick = useCallback(
119
+ async (event: React.MouseEvent<HTMLButtonElement>): Promise<void> => {
120
+ event.preventDefault()
121
+ console.log(
122
+ 'CustomButton: Button clicked. Is form finished:',
123
+ isFormFinished,
124
+ 'Is checking form:',
125
+ isCheckingForm
126
+ )
127
+ if (!isFormFinished || isCheckingForm) return
189
128
 
190
- const renderIcon = (): React.ReactNode => {
191
- if (icon === false) {
192
- return null
193
- }
194
- if (React.isValidElement(icon)) {
195
- return React.cloneElement(icon as React.ReactElement, {
196
- style: { fontSize: iconsize },
197
- })
198
- }
199
- return <StarIcon style={{ fontSize: iconsize }} />
200
- }
129
+ const validationResult = await updateFormValidation()
130
+ console.log('CustomButton: Validation result:', validationResult)
131
+ if (validationResult && onClick) {
132
+ onClick()
133
+ }
134
+ checkFormState()
135
+ },
136
+ [
137
+ isFormFinished,
138
+ isCheckingForm,
139
+ updateFormValidation,
140
+ onClick,
141
+ checkFormState,
142
+ ]
143
+ )
201
144
 
202
- const handleButtonClick = async (
203
- event: React.MouseEvent<HTMLButtonElement>
204
- ): Promise<void> => {
205
- console.log('CustomButton: Button clicked')
206
- event.preventDefault()
207
- setHasBeenClicked(true)
145
+ const renderIcon = useCallback((): React.ReactNode => {
146
+ if (icon === false) {
147
+ return null
148
+ }
149
+ if (React.isValidElement(icon)) {
150
+ return React.cloneElement(icon as React.ReactElement, {
151
+ style: { fontSize: iconsize },
152
+ })
153
+ }
154
+ return <StarIcon style={{ fontSize: iconsize }} />
155
+ }, [icon, iconsize])
208
156
 
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
- }
157
+ const buttonStyle = useMemo(
158
+ () => ({
159
+ minWidth: text ? 'auto' : 'fit-content',
160
+ paddingLeft: text ? '8px' : '0',
161
+ paddingRight: text ? '8px' : '0',
162
+ justifyContent: fontlocation || 'center',
163
+ backgroundColor: backgroundcolor,
164
+ border: outlinecolor ? `1px solid ${outlinecolor}` : undefined,
165
+ color: iconcolor,
166
+ width: width,
167
+ opacity: !isFormFinished || isCheckingForm ? 0.5 : 1,
168
+ }),
169
+ [
170
+ text,
171
+ fontlocation,
172
+ backgroundcolor,
173
+ outlinecolor,
174
+ iconcolor,
175
+ width,
176
+ isFormFinished,
177
+ isCheckingForm,
178
+ ]
179
+ )
218
180
 
219
- return (
220
- <Box
221
- display="flex"
222
- flexDirection="column"
223
- alignItems="center"
224
- width={width}
225
- >
226
- <Button
227
- disableElevation
228
- variant={variant}
229
- startIcon={null}
230
- endIcon={null}
231
- type={type}
232
- name={name}
233
- 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
- }}
244
- >
181
+ const buttonContent = useMemo(
182
+ () => (
245
183
  <Box display="flex" alignItems="center">
246
184
  {iconlocation === 'left' && renderIcon()}
247
185
  {text && (
@@ -253,20 +191,77 @@ const CustomButton: React.FC<CustomButtonProps> = props => {
253
191
  )}
254
192
  {iconlocation === 'right' && renderIcon()}
255
193
  </Box>
256
- </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
- )}
268
- </Box>
269
- )
270
- }
194
+ ),
195
+ [iconlocation, renderIcon, text, fontvariant, fontcolor]
196
+ )
197
+
198
+ const messageComponent = useMemo(
199
+ () =>
200
+ !isFormFinished || isCheckingForm ? (
201
+ <Typography
202
+ fontvariant="merrihelperfooter"
203
+ fontcolor={red.main}
204
+ text="Fill in required fields"
205
+ marginTop={0.5}
206
+ marginBottom={0}
207
+ align="center"
208
+ width="100%"
209
+ />
210
+ ) : null,
211
+ [isFormFinished, isCheckingForm]
212
+ )
213
+
214
+ console.log(
215
+ `CustomButton: Rendering. Is form finished: ${isFormFinished} Is checking form: ${isCheckingForm}`
216
+ )
217
+
218
+ return (
219
+ <Box
220
+ display="flex"
221
+ flexDirection="column"
222
+ alignItems="center"
223
+ width={width}
224
+ >
225
+ <Button
226
+ disableElevation
227
+ variant={variant}
228
+ startIcon={null}
229
+ endIcon={null}
230
+ type={type}
231
+ name={name}
232
+ onClick={handleButtonClick}
233
+ style={buttonStyle}
234
+ disabled={!isFormFinished || isCheckingForm}
235
+ >
236
+ {buttonContent}
237
+ </Button>
238
+ {messageComponent}
239
+ </Box>
240
+ )
241
+ },
242
+ (prevProps, nextProps) => {
243
+ const propsAreEqual =
244
+ prevProps.text === nextProps.text &&
245
+ prevProps.variant === nextProps.variant &&
246
+ prevProps.fontvariant === nextProps.fontvariant &&
247
+ prevProps.icon === nextProps.icon &&
248
+ prevProps.iconlocation === nextProps.iconlocation &&
249
+ prevProps.iconsize === nextProps.iconsize &&
250
+ prevProps.type === nextProps.type &&
251
+ prevProps.onClick === nextProps.onClick &&
252
+ prevProps.fontcolor === nextProps.fontcolor &&
253
+ prevProps.name === nextProps.name &&
254
+ prevProps.formname === nextProps.formname &&
255
+ prevProps.outlinecolor === nextProps.outlinecolor &&
256
+ prevProps.backgroundcolor === nextProps.backgroundcolor &&
257
+ prevProps.fontlocation === nextProps.fontlocation &&
258
+ prevProps.iconcolor === nextProps.iconcolor &&
259
+ prevProps.width === nextProps.width
260
+ console.log('CustomButton: Props changed:', !propsAreEqual)
261
+ return propsAreEqual
262
+ }
263
+ )
264
+
265
+ CustomButton.displayName = 'CustomButton'
271
266
 
272
267
  export default CustomButton
@@ -4,7 +4,7 @@ import { Input, Box } from '@mui/material'
4
4
  import { useCodeConfirmation } from './utils/useCodeConfirmation'
5
5
  import { columnconfig } from '../../components/Grid'
6
6
  import { red, green } from '../../styles/palette'
7
- import { set } from 'goobs-cache'
7
+ import { session } from 'goobs-cache'
8
8
 
9
9
  export interface ConfirmationCodeInputsProps {
10
10
  identifier?: string
@@ -30,6 +30,9 @@ const ConfirmationCodeInputs: React.FC<ConfirmationCodeInputsProps> = ({
30
30
  'aria-invalid': ariaInvalid,
31
31
  ...props
32
32
  }) => {
33
+ const verificationCodeAtom = session.atom<string>('')
34
+ const [, setVerificationCode] = session.useAtom(verificationCodeAtom)
35
+
33
36
  const { handleCodeChange, handleKeyDown, combinedCode } = useCodeConfirmation(
34
37
  {
35
38
  codeLength,
@@ -73,19 +76,14 @@ const ConfirmationCodeInputs: React.FC<ConfirmationCodeInputsProps> = ({
73
76
  }
74
77
 
75
78
  /**
76
- * useEffect hook is used to set the verification code into an atom using goobs-cache.
79
+ * useEffect hook is used to set the verification code into the session atom using goobs-cache.
77
80
  * It sets the code whenever the combinedCode changes and the code is valid.
78
81
  */
79
82
  React.useEffect(() => {
80
83
  if (isValid) {
81
- set(
82
- 'verificationCode',
83
- 'codeStore',
84
- { type: 'string', value: combinedCode },
85
- 'client'
86
- )
84
+ setVerificationCode(combinedCode)
87
85
  }
88
- }, [combinedCode, isValid])
86
+ }, [combinedCode, isValid, setVerificationCode])
89
87
 
90
88
  return (
91
89
  <Box
@@ -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
  ),
@@ -1,7 +1,7 @@
1
1
  'use client'
2
- import React, { useState, useEffect } from 'react'
2
+ import React, { useEffect } from 'react'
3
3
  import { Box, Tabs, Tab } from '@mui/material'
4
- import { get, set, JSONValue } from 'goobs-cache'
4
+ import { session } from 'goobs-cache'
5
5
  import { NavProps, SubNav, View } from '../index'
6
6
 
7
7
  /**
@@ -47,58 +47,32 @@ function HorizontalVariant({
47
47
  /**
48
48
  * State to keep track of active tab values for different navigation components.
49
49
  */
50
- const [activeTabValues, setActiveTabValues] = useState<
50
+ const activeTabValuesAtom = session.atom<
51
51
  Record<string, ActiveTabValue | null>
52
52
  >({})
53
+ const [activeTabValues, setActiveTabValues] =
54
+ session.useAtom(activeTabValuesAtom)
53
55
 
54
56
  /**
55
- * Effect hook to fetch and set the active tab values when the component mounts.
57
+ * Effect hook to initialize the active tab values when the component mounts.
56
58
  */
57
59
  useEffect(() => {
58
- /**
59
- * Asynchronously fetches the active tab values from the cache.
60
- */
61
- const fetchActiveTabValues = async () => {
62
- const result = await get('activeTabValues', 'tabStore', 'client')
63
- if (
64
- result &&
65
- typeof result === 'object' &&
66
- 'type' in result &&
67
- result.type === 'json' &&
68
- 'value' in result &&
69
- typeof result.value === 'object'
70
- ) {
71
- setActiveTabValues(
72
- result.value as Record<string, ActiveTabValue | null>
73
- )
74
- }
75
- }
76
-
77
- fetchActiveTabValues()
60
+ // The initialization is now handled by the session atom
61
+ // No need to fetch from cache as it's automatically handled by goobs-cache
78
62
  }, [])
79
63
 
80
64
  /**
81
65
  * Handles tab change events.
82
- * Updates the active tab values in the state and cache.
66
+ * Updates the active tab values in the state.
83
67
  *
84
68
  * @param {React.SyntheticEvent} event - The event object.
85
69
  * @param {string} newValue - The new value of the selected tab.
86
70
  */
87
- const handleTabChange = async (
88
- event: React.SyntheticEvent,
89
- newValue: string
90
- ) => {
91
- const updatedActiveTabValues = {
92
- ...activeTabValues,
71
+ const handleTabChange = (event: React.SyntheticEvent, newValue: string) => {
72
+ setActiveTabValues(prev => ({
73
+ ...prev,
93
74
  [navname ?? '']: { tabId: newValue },
94
- }
95
- setActiveTabValues(updatedActiveTabValues)
96
- await set(
97
- 'activeTabValues',
98
- 'tabStore',
99
- { type: 'json', value: updatedActiveTabValues } as JSONValue,
100
- 'client'
101
- )
75
+ }))
102
76
  }
103
77
 
104
78
  /**