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.
@@ -0,0 +1,567 @@
1
+ 'use client'
2
+
3
+ import { useCallback, useMemo, useEffect } from 'react'
4
+ import { debounce } from 'lodash'
5
+ import { session } from 'goobs-cache'
6
+
7
+ const isValidEmailFormat = (email: string): boolean => {
8
+ console.log('Validating email format:', email)
9
+ const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
10
+ const isValid = emailRegex.test(email)
11
+ console.log('Email format is valid:', isValid)
12
+ return isValid
13
+ }
14
+
15
+ export interface HelperFooterMessage {
16
+ status: 'error' | 'success' | 'emptyAndRequired'
17
+ statusMessage: string
18
+ spreadMessage: string
19
+ spreadMessagePriority: number
20
+ required: boolean
21
+ hasInput?: boolean
22
+ }
23
+
24
+ export const useInputHelperFooter = () => {
25
+ console.log('useInputHelperFooter hook called')
26
+
27
+ const helperFooterAtom = session.atom<
28
+ | Record<string, HelperFooterMessage>
29
+ | Promise<Record<string, HelperFooterMessage>>
30
+ >({})
31
+ const validateAtom = session.atom<string>('')
32
+
33
+ const handleGenericErrorCreation = useCallback(
34
+ async (
35
+ formData: FormData,
36
+ name: string,
37
+ label: string,
38
+ formname: string,
39
+ priority?: number
40
+ ): Promise<HelperFooterMessage | undefined> => {
41
+ console.log('handleGenericErrorCreation called:', {
42
+ name,
43
+ label,
44
+ formname,
45
+ priority,
46
+ })
47
+ const value = formData.get(name) as string
48
+ console.log('Form data value:', value)
49
+ if (!value || !value.trim()) {
50
+ console.log('Value is empty or whitespace')
51
+ const message: HelperFooterMessage = {
52
+ status: 'error',
53
+ statusMessage: `${label} is required. Please enter a ${label.toLowerCase()}.`,
54
+ spreadMessage: `${label} is required.`,
55
+ spreadMessagePriority: priority ?? 1,
56
+ required: true,
57
+ }
58
+ console.log('Created error message:', message)
59
+ const [, setHelperFooter] = session.useAtom(helperFooterAtom)
60
+ setHelperFooter(prev => {
61
+ if (prev instanceof Promise) {
62
+ return prev
63
+ }
64
+ return { ...prev, [name]: message }
65
+ })
66
+ return message
67
+ } else {
68
+ console.log('Value is not empty, removing helper footer from cache')
69
+ const [, setHelperFooter] = session.useAtom(helperFooterAtom)
70
+ setHelperFooter(prev => {
71
+ if (prev instanceof Promise) {
72
+ return prev
73
+ }
74
+ const newState = { ...prev }
75
+ delete newState[name]
76
+ return newState
77
+ })
78
+ }
79
+ console.log('Returning undefined (no error)')
80
+ return undefined
81
+ },
82
+ [helperFooterAtom]
83
+ )
84
+
85
+ const handleEmailErrorCreation = useCallback(
86
+ async (
87
+ formData: FormData,
88
+ formname: string
89
+ ): Promise<HelperFooterMessage | undefined> => {
90
+ console.log('handleEmailErrorCreation called:', { formname })
91
+ const email = formData.get('email') as string
92
+ console.log('Email value:', email)
93
+ let message: HelperFooterMessage | undefined
94
+
95
+ if (!email || !email.trim()) {
96
+ console.log('Email is empty or whitespace')
97
+ message = {
98
+ status: 'error',
99
+ statusMessage: 'Please enter an email address.',
100
+ spreadMessage: 'Email is required.',
101
+ spreadMessagePriority: 1,
102
+ required: true,
103
+ }
104
+ } else if (!isValidEmailFormat(email)) {
105
+ console.log('Email format is invalid')
106
+ message = {
107
+ status: 'error',
108
+ statusMessage: 'Please enter a valid email address.',
109
+ spreadMessage: 'Invalid email format.',
110
+ spreadMessagePriority: 1,
111
+ required: true,
112
+ }
113
+ } else {
114
+ console.log('Email is valid')
115
+ message = {
116
+ status: 'success',
117
+ statusMessage: 'Email is valid.',
118
+ spreadMessage: 'Email is valid.',
119
+ spreadMessagePriority: 1,
120
+ required: true,
121
+ }
122
+ }
123
+
124
+ if (message) {
125
+ console.log('Message created:', message)
126
+ const [, setHelperFooter] = session.useAtom(helperFooterAtom)
127
+ if (message.status === 'success') {
128
+ console.log('Removing helper footer from cache')
129
+ setHelperFooter(prev => {
130
+ if (prev instanceof Promise) {
131
+ return prev
132
+ }
133
+ const newState = { ...prev }
134
+ delete newState['email']
135
+ return newState
136
+ })
137
+ } else {
138
+ setHelperFooter(prev => {
139
+ if (prev instanceof Promise) {
140
+ return prev
141
+ }
142
+ return { ...prev, email: message! }
143
+ })
144
+ }
145
+ }
146
+
147
+ console.log('Returning message:', message)
148
+ return message
149
+ },
150
+ [helperFooterAtom]
151
+ )
152
+
153
+ const handlePasswordErrorCreation = useCallback(
154
+ async (
155
+ formData: FormData,
156
+ formname: string
157
+ ): Promise<HelperFooterMessage | undefined> => {
158
+ console.log('handlePasswordErrorCreation called:', { formname })
159
+ const password = formData.get('verifyPassword') as string
160
+ console.log('Password value:', password ? '[REDACTED]' : 'empty')
161
+
162
+ const [, setValidate] = session.useAtom(validateAtom)
163
+ const debouncedPasswordStorage = debounce(() => {
164
+ if (password) {
165
+ console.log('Storing password in cache (debounced)')
166
+ setValidate(password)
167
+ }
168
+ }, 2000)
169
+
170
+ debouncedPasswordStorage()
171
+
172
+ let message: HelperFooterMessage | undefined
173
+
174
+ if (!password || !password.trim()) {
175
+ console.log('Password is empty or whitespace')
176
+ message = {
177
+ status: 'error',
178
+ statusMessage: 'Password is required.',
179
+ spreadMessage: 'Password is required.',
180
+ spreadMessagePriority: 2,
181
+ required: true,
182
+ }
183
+ } else {
184
+ const passwordRegex =
185
+ /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/
186
+ const passwordComplexityStatus: 'error' | 'success' =
187
+ passwordRegex.test(password) ? 'success' : 'error'
188
+ console.log('Password complexity status:', passwordComplexityStatus)
189
+
190
+ if (passwordComplexityStatus === 'error') {
191
+ message = {
192
+ status: 'error',
193
+ statusMessage:
194
+ 'Password must include at least 8 characters, one uppercase letter, one lowercase letter, one number, and one special character.',
195
+ spreadMessage: 'Invalid password.',
196
+ spreadMessagePriority: 1,
197
+ required: true,
198
+ }
199
+ } else {
200
+ message = {
201
+ status: 'success',
202
+ statusMessage: 'Password meets all requirements.',
203
+ spreadMessage: 'Password is valid.',
204
+ spreadMessagePriority: 1,
205
+ required: true,
206
+ }
207
+ }
208
+ }
209
+
210
+ if (message) {
211
+ console.log('Message created:', message)
212
+ const [, setHelperFooter] = session.useAtom(helperFooterAtom)
213
+ if (message.status === 'success') {
214
+ console.log('Removing helper footer from cache')
215
+ setHelperFooter(prev => {
216
+ if (prev instanceof Promise) {
217
+ return prev
218
+ }
219
+ const newState = { ...prev }
220
+ delete newState['verifyPassword']
221
+ return newState
222
+ })
223
+ } else {
224
+ setHelperFooter(prev => {
225
+ if (prev instanceof Promise) {
226
+ return prev
227
+ }
228
+ return { ...prev, verifyPassword: message }
229
+ })
230
+ }
231
+ }
232
+
233
+ console.log('Returning message:', message)
234
+ return message
235
+ },
236
+ [helperFooterAtom, validateAtom]
237
+ )
238
+
239
+ const handleConfirmPasswordErrorCreation = useCallback(
240
+ async (
241
+ formData: FormData,
242
+ formname: string
243
+ ): Promise<HelperFooterMessage | undefined> => {
244
+ console.log('handleConfirmPasswordErrorCreation called:', { formname })
245
+ const confirmPassword = formData.get('confirmPassword') as string
246
+ console.log(
247
+ 'Confirm password value:',
248
+ confirmPassword ? '[REDACTED]' : 'empty'
249
+ )
250
+
251
+ let message: HelperFooterMessage | undefined
252
+
253
+ if (!confirmPassword || !confirmPassword.trim()) {
254
+ console.log('Confirm password is empty or whitespace')
255
+ message = {
256
+ status: 'error',
257
+ statusMessage: 'Please confirm your password.',
258
+ spreadMessage: 'Password confirmation is required.',
259
+ spreadMessagePriority: 3,
260
+ required: true,
261
+ }
262
+ } else {
263
+ const [verifyPassword] = session.useAtom(validateAtom)
264
+ console.log('Fetched verify password from session atom')
265
+
266
+ if (!verifyPassword) {
267
+ console.log('Verify password not found')
268
+ message = {
269
+ status: 'error',
270
+ statusMessage: 'Please enter your password first.',
271
+ spreadMessage: 'Password not set.',
272
+ spreadMessagePriority: 3,
273
+ required: true,
274
+ }
275
+ } else if (confirmPassword !== verifyPassword) {
276
+ console.log('Passwords do not match')
277
+ message = {
278
+ status: 'error',
279
+ statusMessage: 'Passwords do not match.',
280
+ spreadMessage: 'Passwords do not match.',
281
+ spreadMessagePriority: 3,
282
+ required: true,
283
+ }
284
+ } else {
285
+ console.log('Passwords match')
286
+ message = {
287
+ status: 'success',
288
+ statusMessage: 'Passwords match.',
289
+ spreadMessage: 'Passwords match.',
290
+ spreadMessagePriority: 3,
291
+ required: true,
292
+ }
293
+ }
294
+ }
295
+
296
+ if (message) {
297
+ console.log('Message created:', message)
298
+ const [, setHelperFooter] = session.useAtom(helperFooterAtom)
299
+ if (message.status === 'success') {
300
+ console.log('Removing helper footer from cache')
301
+ setHelperFooter(prev => {
302
+ if (prev instanceof Promise) {
303
+ return prev
304
+ }
305
+ const newState = { ...prev }
306
+ delete newState['confirmPassword']
307
+ return newState
308
+ })
309
+ } else {
310
+ setHelperFooter(prev => {
311
+ if (prev instanceof Promise) {
312
+ return prev
313
+ }
314
+ return { ...prev, confirmPassword: message }
315
+ })
316
+ }
317
+ }
318
+
319
+ console.log('Returning message:', message)
320
+ return message
321
+ },
322
+ [helperFooterAtom, validateAtom]
323
+ )
324
+
325
+ const handlePhoneNumberErrorCreation = useCallback(
326
+ async (
327
+ formData: FormData,
328
+ formname: string
329
+ ): Promise<HelperFooterMessage | undefined> => {
330
+ console.log('handlePhoneNumberErrorCreation called:', { formname })
331
+ const phoneNumber = formData.get('phoneNumber') as string
332
+ console.log('Phone number value:', phoneNumber)
333
+ let message: HelperFooterMessage | undefined
334
+
335
+ if (!phoneNumber || !phoneNumber.trim()) {
336
+ console.log('Phone number is empty or whitespace')
337
+ message = {
338
+ status: 'error',
339
+ statusMessage:
340
+ 'Phone number is required. Please enter a phone number.',
341
+ spreadMessage: 'Phone number is required.',
342
+ spreadMessagePriority: 3,
343
+ required: true,
344
+ }
345
+ } else {
346
+ const digitsOnly = phoneNumber.replace(/[^\d]/g, '')
347
+ const length = digitsOnly.length
348
+ console.log('Phone number length (digits only):', length)
349
+ if (
350
+ (length === 10 && !digitsOnly.startsWith('1')) ||
351
+ (length === 11 && digitsOnly.startsWith('1'))
352
+ ) {
353
+ console.log('Phone number is valid')
354
+ message = {
355
+ status: 'success',
356
+ statusMessage: 'Phone number is valid.',
357
+ spreadMessage: 'Phone number is valid.',
358
+ spreadMessagePriority: 1,
359
+ required: true,
360
+ }
361
+ } else {
362
+ console.log('Phone number is invalid')
363
+ message = {
364
+ status: 'error',
365
+ statusMessage:
366
+ 'Please enter a valid 10-digit phone number or a 10-digit number starting with 1.',
367
+ spreadMessage: 'Invalid phone number format.',
368
+ spreadMessagePriority: 1,
369
+ required: true,
370
+ }
371
+ }
372
+ }
373
+
374
+ if (message) {
375
+ console.log('Message created:', message)
376
+ const [, setHelperFooter] = session.useAtom(helperFooterAtom)
377
+ if (message.status === 'success') {
378
+ console.log('Removing helper footer from cache')
379
+ setHelperFooter(prev => {
380
+ if (prev instanceof Promise) {
381
+ return prev
382
+ }
383
+ const newState = { ...prev }
384
+ delete newState['phoneNumber']
385
+ return newState
386
+ })
387
+ } else {
388
+ setHelperFooter(prev => {
389
+ if (prev instanceof Promise) {
390
+ return prev
391
+ }
392
+ return { ...prev, phoneNumber: message }
393
+ })
394
+ }
395
+ }
396
+
397
+ console.log('Returning message:', message)
398
+ return message
399
+ },
400
+ [helperFooterAtom]
401
+ )
402
+
403
+ const updateHelperFooter = useCallback(
404
+ (name: string, validationResult: HelperFooterMessage | undefined): void => {
405
+ console.log('updateHelperFooter called:', { name, validationResult })
406
+ const [, setHelperFooter] = session.useAtom(helperFooterAtom)
407
+ if (validationResult) {
408
+ console.log('Updating helper footer with new validation result')
409
+ setHelperFooter(prev => {
410
+ if (prev instanceof Promise) {
411
+ return prev
412
+ }
413
+ return { ...prev, [name]: validationResult }
414
+ })
415
+ } else {
416
+ console.log('Removing field from helper footer')
417
+ setHelperFooter(prev => {
418
+ if (prev instanceof Promise) {
419
+ return prev
420
+ }
421
+ const newState = { ...prev }
422
+ delete newState[name]
423
+ return newState
424
+ })
425
+ }
426
+
427
+ console.log('Setting new helper footer value')
428
+ },
429
+ [helperFooterAtom]
430
+ )
431
+
432
+ const validateField = useCallback(
433
+ async (
434
+ name: string,
435
+ formData: FormData,
436
+ label: string,
437
+ formname: string,
438
+ priority?: number
439
+ ) => {
440
+ console.log('validateField called:', { name, label, formname, priority })
441
+ let validationResult: HelperFooterMessage | undefined
442
+
443
+ switch (name) {
444
+ case 'email':
445
+ console.log('Validating email field')
446
+ validationResult = await handleEmailErrorCreation(formData, formname)
447
+ break
448
+ case 'verifyPassword':
449
+ console.log('Validating password field')
450
+ validationResult = await handlePasswordErrorCreation(
451
+ formData,
452
+ formname
453
+ )
454
+ break
455
+ case 'confirmPassword':
456
+ console.log('Validating confirm password field')
457
+ validationResult = await handleConfirmPasswordErrorCreation(
458
+ formData,
459
+ formname
460
+ )
461
+ break
462
+ case 'phoneNumber':
463
+ console.log('Validating phone number field')
464
+ validationResult = await handlePhoneNumberErrorCreation(
465
+ formData,
466
+ formname
467
+ )
468
+ break
469
+ default:
470
+ console.log('Validating generic field')
471
+ validationResult = await handleGenericErrorCreation(
472
+ formData,
473
+ name,
474
+ label,
475
+ formname,
476
+ priority
477
+ )
478
+ }
479
+
480
+ console.log('Validation result:', validationResult)
481
+ updateHelperFooter(name, validationResult)
482
+ },
483
+ [
484
+ handleEmailErrorCreation,
485
+ handlePasswordErrorCreation,
486
+ handleConfirmPasswordErrorCreation,
487
+ handlePhoneNumberErrorCreation,
488
+ handleGenericErrorCreation,
489
+ updateHelperFooter,
490
+ ]
491
+ )
492
+
493
+ const useShowErrorEffect = (
494
+ formSubmitted: boolean,
495
+ hasInput: boolean,
496
+ isFocused: boolean
497
+ ): boolean => {
498
+ console.log('useShowErrorEffect called:', {
499
+ formSubmitted,
500
+ hasInput,
501
+ isFocused,
502
+ })
503
+ const showErrorAtom = session.atom<boolean>(false)
504
+ const [showError, setShowError] = session.useAtom(showErrorAtom)
505
+
506
+ useEffect(() => {
507
+ const shouldShowError = formSubmitted || (hasInput && !isFocused)
508
+ console.log('Calculating shouldShowError:', {
509
+ shouldShowError,
510
+ formSubmitted,
511
+ hasInput,
512
+ isFocused,
513
+ })
514
+ setShowError(shouldShowError)
515
+ }, [formSubmitted, hasInput, isFocused, setShowError])
516
+
517
+ console.log('Returning showError:', showError)
518
+ return showError
519
+ }
520
+
521
+ const fetchHelperFooters = useCallback(
522
+ async (formname: string): Promise<HelperFooterMessage[]> => {
523
+ console.log('fetchHelperFooters called:', { formname })
524
+ const [helperFooters] = session.useAtom(helperFooterAtom)
525
+ console.log('Fetched helper footers from session atom:', helperFooters)
526
+
527
+ if (helperFooters instanceof Promise) {
528
+ return []
529
+ }
530
+
531
+ const filteredHelperFooters = Object.values(helperFooters).filter(
532
+ (item): item is HelperFooterMessage => {
533
+ const isValidHelperFooter =
534
+ typeof item === 'object' &&
535
+ item !== null &&
536
+ 'status' in item &&
537
+ 'statusMessage' in item &&
538
+ 'spreadMessage' in item &&
539
+ 'spreadMessagePriority' in item &&
540
+ 'required' in item
541
+ console.log('Checking if item is valid HelperFooterMessage:', {
542
+ item,
543
+ isValid: isValidHelperFooter,
544
+ })
545
+ return isValidHelperFooter
546
+ }
547
+ )
548
+ console.log('Filtered helper footers:', filteredHelperFooters)
549
+ return filteredHelperFooters
550
+ },
551
+ [helperFooterAtom]
552
+ )
553
+
554
+ const result = useMemo(
555
+ () => ({
556
+ validateField,
557
+ useShowErrorEffect,
558
+ fetchHelperFooters,
559
+ }),
560
+ [validateField, fetchHelperFooters]
561
+ )
562
+
563
+ console.log('useInputHelperFooter returning result:', result)
564
+ return result
565
+ }
566
+
567
+ export default useInputHelperFooter
@@ -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,