@umituz/react-native-auth 4.3.77 → 4.3.78
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 +1 -1
- package/src/exports/domain.ts +32 -0
- package/src/exports/infrastructure.ts +80 -0
- package/src/exports/presentation.ts +115 -0
- package/src/exports/shared.ts +93 -0
- package/src/exports/utils.ts +10 -0
- package/src/index.ts +7 -132
- package/src/infrastructure/utils/listener/anonymousSignIn/attemptAnonymousSignIn.ts +81 -0
- package/src/infrastructure/utils/listener/anonymousSignIn/constants.ts +7 -0
- package/src/infrastructure/utils/listener/anonymousSignIn/createAnonymousSignInHandler.ts +59 -0
- package/src/infrastructure/utils/listener/anonymousSignIn/index.ts +16 -0
- package/src/infrastructure/utils/listener/anonymousSignIn/types.ts +21 -0
- package/src/presentation/components/RegisterForm/RegisterForm.tsx +91 -0
- package/src/presentation/components/RegisterForm/RegisterFormFields.tsx +117 -0
- package/src/presentation/components/RegisterForm/index.ts +7 -0
- package/src/presentation/components/RegisterForm/styles.ts +11 -0
- package/src/presentation/components/RegisterForm/types.ts +34 -0
- package/src/presentation/hooks/auth/index.ts +7 -0
- package/src/presentation/hooks/auth/types.ts +26 -0
- package/src/presentation/hooks/auth/useAuthBottomSheet.ts +110 -0
- package/src/presentation/hooks/auth/useSocialAuthHandlers.ts +56 -0
- package/src/shared/error-handling/handlers/ErrorHandler.ts +70 -0
- package/src/shared/error-handling/handlers/FormErrorHandler.ts +99 -0
- package/src/shared/error-handling/handlers/index.ts +7 -0
- package/src/shared/error-handling/index.ts +19 -0
- package/src/shared/error-handling/mappers/ErrorMapper.ts +115 -0
- package/src/shared/error-handling/mappers/FieldErrorMapper.ts +97 -0
- package/src/shared/error-handling/mappers/index.ts +6 -0
- package/src/shared/error-handling/types/ErrorTypes.ts +23 -0
- package/src/shared/error-handling/types/index.ts +10 -0
- package/src/shared/form/builders/FieldBuilder.ts +90 -0
- package/src/shared/form/builders/FormBuilder.ts +126 -0
- package/src/shared/form/builders/index.ts +9 -0
- package/src/shared/form/index.ts +51 -0
- package/src/shared/form/state/FormState.ts +114 -0
- package/src/shared/form/state/index.ts +16 -0
- package/src/shared/form/types/FormTypes.ts +30 -0
- package/src/shared/form/types/index.ts +11 -0
- package/src/shared/form/utils/fieldUtils.ts +115 -0
- package/src/shared/form/utils/formUtils.ts +113 -0
- package/src/shared/form/utils/index.ts +6 -0
- package/src/shared/validation/index.ts +23 -0
- package/src/shared/validation/rules/ValidationRule.ts +71 -0
- package/src/shared/validation/rules/index.ts +5 -0
- package/src/shared/validation/sanitizers/EmailSanitizer.ts +22 -0
- package/src/shared/validation/sanitizers/PasswordSanitizer.ts +24 -0
- package/src/shared/validation/sanitizers/index.ts +7 -0
- package/src/shared/validation/types.ts +26 -0
- package/src/shared/validation/validators/EmailValidator.ts +61 -0
- package/src/shared/validation/validators/NameValidator.ts +52 -0
- package/src/shared/validation/validators/PasswordValidator.ts +92 -0
- package/src/shared/validation/validators/index.ts +8 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Mapper
|
|
3
|
+
* Maps errors to localization keys
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ErrorMap, ErrorMappingConfig } from '../types/ErrorTypes';
|
|
7
|
+
|
|
8
|
+
export class ErrorMapper {
|
|
9
|
+
private config: ErrorMappingConfig;
|
|
10
|
+
|
|
11
|
+
constructor(config: ErrorMappingConfig = {}) {
|
|
12
|
+
this.config = {
|
|
13
|
+
errorCodeMap: config.errorCodeMap || {},
|
|
14
|
+
errorNameMap: config.errorNameMap || {},
|
|
15
|
+
defaultKey: config.defaultKey || 'auth.errors.unknownError',
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Map error code to localization key
|
|
21
|
+
*/
|
|
22
|
+
getLocalizationKey(error: unknown): string {
|
|
23
|
+
// Handle non-Error types
|
|
24
|
+
if (!(error instanceof Error)) {
|
|
25
|
+
return this.config.defaultKey!;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const code = (error as any).code;
|
|
29
|
+
const name = error.name;
|
|
30
|
+
|
|
31
|
+
// First check by error name (most specific)
|
|
32
|
+
if (this.config.errorNameMap && name && this.config.errorNameMap[name]) {
|
|
33
|
+
return this.config.errorNameMap[name];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Then check by error code
|
|
37
|
+
if (this.config.errorCodeMap && code && this.config.errorCodeMap[code]) {
|
|
38
|
+
return this.config.errorCodeMap[code];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Default to unknown error
|
|
42
|
+
return this.config.defaultKey!;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Resolve error key to localized message
|
|
47
|
+
*/
|
|
48
|
+
resolveMessage(key: string, translations?: ErrorMap): string {
|
|
49
|
+
return translations?.[key] || key;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Get error message with translations
|
|
54
|
+
*/
|
|
55
|
+
getErrorMessage(error: unknown, translations?: ErrorMap): string {
|
|
56
|
+
const key = this.getLocalizationKey(error);
|
|
57
|
+
return this.resolveMessage(key, translations);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Set error mappings
|
|
62
|
+
*/
|
|
63
|
+
setMappings(config: Partial<ErrorMappingConfig>): void {
|
|
64
|
+
if (config.errorCodeMap) {
|
|
65
|
+
this.config.errorCodeMap = { ...this.config.errorCodeMap, ...config.errorCodeMap };
|
|
66
|
+
}
|
|
67
|
+
if (config.errorNameMap) {
|
|
68
|
+
this.config.errorNameMap = { ...this.config.errorNameMap, ...config.errorNameMap };
|
|
69
|
+
}
|
|
70
|
+
if (config.defaultKey) {
|
|
71
|
+
this.config.defaultKey = config.defaultKey;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Default auth error mappings
|
|
78
|
+
*/
|
|
79
|
+
export const DEFAULT_AUTH_ERROR_MAPPINGS: ErrorMappingConfig = {
|
|
80
|
+
errorCodeMap: {
|
|
81
|
+
AUTH_INVALID_EMAIL: 'auth.errors.invalidEmail',
|
|
82
|
+
AUTH_WEAK_PASSWORD: 'auth.errors.weakPassword',
|
|
83
|
+
AUTH_USER_NOT_FOUND: 'auth.errors.userNotFound',
|
|
84
|
+
AUTH_WRONG_PASSWORD: 'auth.errors.wrongPassword',
|
|
85
|
+
AUTH_EMAIL_ALREADY_IN_USE: 'auth.errors.emailAlreadyInUse',
|
|
86
|
+
AUTH_NETWORK_ERROR: 'auth.errors.networkError',
|
|
87
|
+
AUTH_CONFIG_ERROR: 'auth.errors.configurationError',
|
|
88
|
+
AUTH_TOO_MANY_REQUESTS: 'auth.errors.tooManyRequests',
|
|
89
|
+
AUTH_USER_DISABLED: 'auth.errors.userDisabled',
|
|
90
|
+
AUTH_NOT_INITIALIZED: 'auth.errors.authNotInitialized',
|
|
91
|
+
AUTH_INVALID_CREDENTIAL: 'auth.errors.invalidCredential',
|
|
92
|
+
// Firebase error codes
|
|
93
|
+
'auth/invalid-email': 'auth.errors.invalidEmail',
|
|
94
|
+
'auth/weak-password': 'auth.errors.weakPassword',
|
|
95
|
+
'auth/user-not-found': 'auth.errors.invalidCredential',
|
|
96
|
+
'auth/wrong-password': 'auth.errors.invalidCredential',
|
|
97
|
+
'auth/email-already-in-use': 'auth.errors.emailAlreadyInUse',
|
|
98
|
+
'auth/network-request-failed': 'auth.errors.networkError',
|
|
99
|
+
'auth/too-many-requests': 'auth.errors.tooManyRequests',
|
|
100
|
+
'auth/user-disabled': 'auth.errors.userDisabled',
|
|
101
|
+
'auth/invalid-credential': 'auth.errors.invalidCredential',
|
|
102
|
+
'auth/invalid-login-credentials': 'auth.errors.invalidCredential',
|
|
103
|
+
},
|
|
104
|
+
errorNameMap: {
|
|
105
|
+
AuthInvalidEmailError: 'auth.errors.invalidEmail',
|
|
106
|
+
AuthWeakPasswordError: 'auth.errors.weakPassword',
|
|
107
|
+
AuthUserNotFoundError: 'auth.errors.userNotFound',
|
|
108
|
+
AuthWrongPasswordError: 'auth.errors.wrongPassword',
|
|
109
|
+
AuthEmailAlreadyInUseError: 'auth.errors.emailAlreadyInUse',
|
|
110
|
+
AuthNetworkError: 'auth.errors.networkError',
|
|
111
|
+
AuthConfigurationError: 'auth.errors.configurationError',
|
|
112
|
+
AuthInitializationError: 'auth.errors.authNotInitialized',
|
|
113
|
+
},
|
|
114
|
+
defaultKey: 'auth.errors.unknownError',
|
|
115
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Field Error Mapper
|
|
3
|
+
* Maps field errors from validation results
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { FieldError, FormFieldErrors, ErrorMap } from '../types/ErrorTypes';
|
|
7
|
+
|
|
8
|
+
export class FieldErrorMapper {
|
|
9
|
+
/**
|
|
10
|
+
* Convert field errors array to record
|
|
11
|
+
*/
|
|
12
|
+
static toRecord(errors: FieldError[]): Record<string, string> {
|
|
13
|
+
const result: Record<string, string> = {};
|
|
14
|
+
for (const error of errors) {
|
|
15
|
+
result[error.field] = error.message;
|
|
16
|
+
}
|
|
17
|
+
return result;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Convert field errors array to form field errors (with null support)
|
|
22
|
+
*/
|
|
23
|
+
static toFormFieldErrors(errors: FieldError[], fields: string[]): FormFieldErrors {
|
|
24
|
+
const result: FormFieldErrors = {};
|
|
25
|
+
for (const field of fields) {
|
|
26
|
+
const fieldError = errors.find((e) => e.field === field);
|
|
27
|
+
result[field] = fieldError?.message ?? null;
|
|
28
|
+
}
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Extract single field error
|
|
34
|
+
*/
|
|
35
|
+
static extractFieldError(errors: FieldError[], field: string): string | null {
|
|
36
|
+
const fieldError = errors.find((e) => e.field === field);
|
|
37
|
+
return fieldError?.message ?? null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Check if validation has errors for specific fields
|
|
42
|
+
*/
|
|
43
|
+
static hasFieldErrors(errors: FieldError[], fields: string[]): boolean {
|
|
44
|
+
return errors.some((error) => fields.includes(error.field));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get first error message
|
|
49
|
+
*/
|
|
50
|
+
static getFirstErrorMessage(errors: FieldError[]): string | null {
|
|
51
|
+
if (errors.length === 0) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
return errors[0].message ?? null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Map error keys to messages using translation map
|
|
59
|
+
*/
|
|
60
|
+
static mapErrorsToMessages(
|
|
61
|
+
errors: FieldError[],
|
|
62
|
+
translations: ErrorMap
|
|
63
|
+
): FieldError[] {
|
|
64
|
+
return errors.map((error) => ({
|
|
65
|
+
...error,
|
|
66
|
+
message: translations[error.message] || error.message,
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Create field error
|
|
72
|
+
*/
|
|
73
|
+
static createFieldError(field: string, message: string): FieldError {
|
|
74
|
+
return { field, message };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Merge field errors
|
|
79
|
+
*/
|
|
80
|
+
static mergeFieldErrors(...errorArrays: FieldError[][]): FieldError[] {
|
|
81
|
+
return errorArrays.flat();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Filter errors by fields
|
|
86
|
+
*/
|
|
87
|
+
static filterByFields(errors: FieldError[], fields: string[]): FieldError[] {
|
|
88
|
+
return errors.filter((error) => fields.includes(error.field));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Exclude errors by fields
|
|
93
|
+
*/
|
|
94
|
+
static excludeFields(errors: FieldError[], fields: string[]): FieldError[] {
|
|
95
|
+
return errors.filter((error) => !fields.includes(error.field));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Types
|
|
3
|
+
* Core types for error handling system
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface FieldError {
|
|
7
|
+
field: string;
|
|
8
|
+
message: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface FormFieldErrors {
|
|
12
|
+
[fieldName: string]: string | null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ErrorMap {
|
|
16
|
+
[key: string]: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ErrorMappingConfig {
|
|
20
|
+
errorCodeMap?: ErrorMap;
|
|
21
|
+
errorNameMap?: ErrorMap;
|
|
22
|
+
defaultKey?: string;
|
|
23
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Field Builder
|
|
3
|
+
* Builds individual form field state and handlers
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useCallback, useRef } from 'react';
|
|
7
|
+
import type { FieldState, FieldChangeHandler, FormFieldConfig } from '../types/FormTypes';
|
|
8
|
+
|
|
9
|
+
export interface UseFieldOptions extends FormFieldConfig {
|
|
10
|
+
initialValue?: string;
|
|
11
|
+
onValueChange?: (value: string) => void;
|
|
12
|
+
onErrorClear?: () => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface UseFieldResult {
|
|
16
|
+
value: string;
|
|
17
|
+
error: string | null;
|
|
18
|
+
touched: boolean;
|
|
19
|
+
handleChange: FieldChangeHandler;
|
|
20
|
+
setError: (error: string | null) => void;
|
|
21
|
+
setTouched: (touched: boolean) => void;
|
|
22
|
+
reset: () => void;
|
|
23
|
+
clearError: () => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Hook for managing single form field
|
|
28
|
+
*/
|
|
29
|
+
export function useField(
|
|
30
|
+
options: UseFieldOptions = {}
|
|
31
|
+
): UseFieldResult {
|
|
32
|
+
const {
|
|
33
|
+
initialValue = '',
|
|
34
|
+
validateOnChange = false,
|
|
35
|
+
clearErrorOnChange = true,
|
|
36
|
+
onValueChange,
|
|
37
|
+
onErrorClear,
|
|
38
|
+
} = options;
|
|
39
|
+
|
|
40
|
+
const [value, setValue] = React.useState(initialValue);
|
|
41
|
+
const [error, setError] = React.useState<string | null>(null);
|
|
42
|
+
const [touched, setTouched] = React.useState(false);
|
|
43
|
+
|
|
44
|
+
// Store refs to avoid recreating handlers
|
|
45
|
+
const initialValueRef = useRef(initialValue);
|
|
46
|
+
const onValueChangeRef = useRef(onValueChange);
|
|
47
|
+
const onErrorClearRef = useRef(onErrorClear);
|
|
48
|
+
|
|
49
|
+
// Update refs
|
|
50
|
+
onValueChangeRef.current = onValueChange;
|
|
51
|
+
onErrorClearRef.current = onErrorClear;
|
|
52
|
+
|
|
53
|
+
const handleChange = useCallback((newValue: string) => {
|
|
54
|
+
setValue(newValue);
|
|
55
|
+
|
|
56
|
+
// Clear error on change if configured
|
|
57
|
+
if (clearErrorOnChange && error) {
|
|
58
|
+
setError(null);
|
|
59
|
+
onErrorClearRef.current?.();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Call external callback
|
|
63
|
+
onValueChangeRef.current?.(newValue);
|
|
64
|
+
}, [error, clearErrorOnChange]);
|
|
65
|
+
|
|
66
|
+
const reset = useCallback(() => {
|
|
67
|
+
setValue(initialValueRef.current);
|
|
68
|
+
setError(null);
|
|
69
|
+
setTouched(false);
|
|
70
|
+
}, []);
|
|
71
|
+
|
|
72
|
+
const clearError = useCallback(() => {
|
|
73
|
+
setError(null);
|
|
74
|
+
onErrorClearRef.current?.();
|
|
75
|
+
}, []);
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
value,
|
|
79
|
+
error,
|
|
80
|
+
touched,
|
|
81
|
+
handleChange,
|
|
82
|
+
setError,
|
|
83
|
+
setTouched,
|
|
84
|
+
reset,
|
|
85
|
+
clearError,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Import React for useState
|
|
90
|
+
import React from 'react';
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Form Builder
|
|
3
|
+
* Builds form state with multiple fields
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useCallback, useRef } from 'react';
|
|
7
|
+
import type { FormState, FormConfig, FieldChangeHandler } from '../types/FormTypes';
|
|
8
|
+
|
|
9
|
+
export interface UseFormOptions<T extends Record<string, string>> extends FormConfig {
|
|
10
|
+
initialValues: T;
|
|
11
|
+
onFieldChange?: (field: keyof T, value: string) => void;
|
|
12
|
+
onErrorsClear?: () => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface UseFormResult<T extends Record<string, string>> {
|
|
16
|
+
values: T;
|
|
17
|
+
errors: Record<keyof T, string | null>;
|
|
18
|
+
touched: Record<keyof T, boolean>;
|
|
19
|
+
handleChange: (field: keyof T) => (value: string) => void;
|
|
20
|
+
setFieldValue: (field: keyof T, value: string) => void;
|
|
21
|
+
setFieldError: (field: keyof T, error: string | null) => void;
|
|
22
|
+
setFieldTouched: (field: keyof T, touched: boolean) => void;
|
|
23
|
+
resetForm: () => void;
|
|
24
|
+
clearErrors: () => void;
|
|
25
|
+
clearFieldError: (field: keyof T) => void;
|
|
26
|
+
isDirty: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Hook for managing form with multiple fields
|
|
31
|
+
*/
|
|
32
|
+
export function useForm<T extends Record<string, string>>(
|
|
33
|
+
options: UseFormOptions<T>
|
|
34
|
+
): UseFormResult<T> {
|
|
35
|
+
const {
|
|
36
|
+
initialValues,
|
|
37
|
+
validateOnBlur = false,
|
|
38
|
+
clearErrorsOnSubmit = true,
|
|
39
|
+
onFieldChange,
|
|
40
|
+
onErrorsClear,
|
|
41
|
+
} = options;
|
|
42
|
+
|
|
43
|
+
type FieldKey = keyof T;
|
|
44
|
+
|
|
45
|
+
const [values, setValues] = React.useState<T>(initialValues);
|
|
46
|
+
const [errors, setErrors] = React.useState<Record<FieldKey, string | null>>(
|
|
47
|
+
{} as Record<FieldKey, string | null>
|
|
48
|
+
);
|
|
49
|
+
const [touched, setTouched] = React.useState<Record<FieldKey, boolean>>(
|
|
50
|
+
{} as Record<FieldKey, boolean>
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
// Store initial values in ref
|
|
54
|
+
const initialValuesRef = useRef(initialValues);
|
|
55
|
+
const onFieldChangeRef = useRef(onFieldChange);
|
|
56
|
+
const onErrorsClearRef = useRef(onErrorsClear);
|
|
57
|
+
|
|
58
|
+
// Update refs
|
|
59
|
+
onFieldChangeRef.current = onFieldChange;
|
|
60
|
+
onErrorsClearRef.current = onErrorsClear;
|
|
61
|
+
|
|
62
|
+
const handleChange = useCallback(
|
|
63
|
+
(field: FieldKey) => (value: string) => {
|
|
64
|
+
setValues((prev) => ({ ...prev, [field]: value }));
|
|
65
|
+
|
|
66
|
+
// Clear error when field changes (optional)
|
|
67
|
+
if (errors[field]) {
|
|
68
|
+
setErrors((prev) => ({ ...prev, [field]: null }));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Call external callback
|
|
72
|
+
onFieldChangeRef.current?.(field, value);
|
|
73
|
+
},
|
|
74
|
+
[errors]
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const setFieldValue = useCallback((field: FieldKey, value: string) => {
|
|
78
|
+
setValues((prev) => ({ ...prev, [field]: value }));
|
|
79
|
+
onFieldChangeRef.current?.(field, value);
|
|
80
|
+
}, []);
|
|
81
|
+
|
|
82
|
+
const setFieldError = useCallback((field: FieldKey, error: string | null) => {
|
|
83
|
+
setErrors((prev) => ({ ...prev, [field]: error }));
|
|
84
|
+
}, []);
|
|
85
|
+
|
|
86
|
+
const setFieldTouched = useCallback((field: FieldKey, isTouched: boolean) => {
|
|
87
|
+
setTouched((prev) => ({ ...prev, [field]: isTouched }));
|
|
88
|
+
}, []);
|
|
89
|
+
|
|
90
|
+
const resetForm = useCallback(() => {
|
|
91
|
+
setValues(initialValuesRef.current);
|
|
92
|
+
setErrors({} as Record<FieldKey, string | null>);
|
|
93
|
+
setTouched({} as Record<FieldKey, boolean>);
|
|
94
|
+
}, []);
|
|
95
|
+
|
|
96
|
+
const clearErrors = useCallback(() => {
|
|
97
|
+
setErrors({} as Record<FieldKey, string | null>);
|
|
98
|
+
onErrorsClearRef.current?.();
|
|
99
|
+
}, []);
|
|
100
|
+
|
|
101
|
+
const clearFieldError = useCallback((field: FieldKey) => {
|
|
102
|
+
setErrors((prev) => ({ ...prev, [field]: null }));
|
|
103
|
+
}, []);
|
|
104
|
+
|
|
105
|
+
// Check if form is dirty (has changes)
|
|
106
|
+
const isDirty = Object.keys(values).some(
|
|
107
|
+
(key) => values[key as FieldKey] !== initialValuesRef.current[key as FieldKey]
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
values,
|
|
112
|
+
errors,
|
|
113
|
+
touched,
|
|
114
|
+
handleChange,
|
|
115
|
+
setFieldValue,
|
|
116
|
+
setFieldError,
|
|
117
|
+
setFieldTouched,
|
|
118
|
+
resetForm,
|
|
119
|
+
clearErrors,
|
|
120
|
+
clearFieldError,
|
|
121
|
+
isDirty,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Import React for useState
|
|
126
|
+
import React from 'react';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Form Builders Public API
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { useField } from './FieldBuilder';
|
|
6
|
+
export type { UseFieldOptions, UseFieldResult } from './FieldBuilder';
|
|
7
|
+
|
|
8
|
+
export { useForm } from './FormBuilder';
|
|
9
|
+
export type { UseFormOptions, UseFormResult } from './FormBuilder';
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Form Module Public API
|
|
3
|
+
* Centralized form state management system
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Types
|
|
7
|
+
export type {
|
|
8
|
+
FieldState,
|
|
9
|
+
FormState,
|
|
10
|
+
FieldChangeHandler,
|
|
11
|
+
FormFieldConfig,
|
|
12
|
+
FormConfig,
|
|
13
|
+
} from './types';
|
|
14
|
+
|
|
15
|
+
// Builders
|
|
16
|
+
export { useField, useForm } from './builders';
|
|
17
|
+
export type { UseFieldOptions, UseFieldResult, UseFormOptions, UseFormResult } from './builders';
|
|
18
|
+
|
|
19
|
+
// State utilities
|
|
20
|
+
export {
|
|
21
|
+
isFormValid,
|
|
22
|
+
isFormDirty,
|
|
23
|
+
isFormTouched,
|
|
24
|
+
getFormErrors,
|
|
25
|
+
getFirstFormError,
|
|
26
|
+
countFormErrors,
|
|
27
|
+
getFieldError,
|
|
28
|
+
fieldHasError,
|
|
29
|
+
isFieldTouched,
|
|
30
|
+
resetFormState,
|
|
31
|
+
} from './state';
|
|
32
|
+
|
|
33
|
+
// Utils
|
|
34
|
+
export {
|
|
35
|
+
sanitizeFormValues,
|
|
36
|
+
extractFields,
|
|
37
|
+
areRequiredFieldsFilled,
|
|
38
|
+
getEmptyRequiredFields,
|
|
39
|
+
createFieldOptions,
|
|
40
|
+
mergeFormErrors,
|
|
41
|
+
clearFieldErrors,
|
|
42
|
+
createFieldChangeHandler,
|
|
43
|
+
createFieldBlurHandler,
|
|
44
|
+
debounceFieldChange,
|
|
45
|
+
isFieldValueEmpty,
|
|
46
|
+
sanitizeFieldValue,
|
|
47
|
+
formatFieldValue,
|
|
48
|
+
validateFieldValue,
|
|
49
|
+
getFieldDisplayValue,
|
|
50
|
+
truncateFieldValue,
|
|
51
|
+
} from './utils';
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Form State Utilities
|
|
3
|
+
* Utilities for managing form state
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { FormState } from '../types/FormTypes';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Check if form is valid (no errors)
|
|
10
|
+
*/
|
|
11
|
+
export function isFormValid<T extends Record<string, string>>(
|
|
12
|
+
formState: FormState<T>
|
|
13
|
+
): boolean {
|
|
14
|
+
return Object.values(formState.errors).every((error) => error === null);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Check if form is dirty (has changes)
|
|
19
|
+
*/
|
|
20
|
+
export function isFormDirty<T extends Record<string, string>>(
|
|
21
|
+
formState: FormState<T>,
|
|
22
|
+
initialValues: T
|
|
23
|
+
): boolean {
|
|
24
|
+
return Object.keys(formState.fields).some((key) => {
|
|
25
|
+
const fieldKey = key as keyof T;
|
|
26
|
+
return formState.fields[fieldKey] !== initialValues[fieldKey];
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Check if form is touched (any field touched)
|
|
32
|
+
*/
|
|
33
|
+
export function isFormTouched<T extends Record<string, string>>(
|
|
34
|
+
formState: FormState<T>
|
|
35
|
+
): boolean {
|
|
36
|
+
return Object.values(formState.touched).some((touched) => touched);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Get all errors as array
|
|
41
|
+
*/
|
|
42
|
+
export function getFormErrors<T extends Record<string, string>>(
|
|
43
|
+
formState: FormState<T>
|
|
44
|
+
): string[] {
|
|
45
|
+
return Object.values(formState.errors).filter((error): error is string => error !== null);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Get first error message
|
|
50
|
+
*/
|
|
51
|
+
export function getFirstFormError<T extends Record<string, string>>(
|
|
52
|
+
formState: FormState<T>
|
|
53
|
+
): string | null {
|
|
54
|
+
const errors = getFormErrors(formState);
|
|
55
|
+
return errors.length > 0 ? errors[0] : null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Count errors
|
|
60
|
+
*/
|
|
61
|
+
export function countFormErrors<T extends Record<string, string>>(
|
|
62
|
+
formState: FormState<T>
|
|
63
|
+
): number {
|
|
64
|
+
return getFormErrors(formState).length;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get error for specific field
|
|
69
|
+
*/
|
|
70
|
+
export function getFieldError<T extends Record<string, string>>(
|
|
71
|
+
formState: FormState<T>,
|
|
72
|
+
field: keyof T
|
|
73
|
+
): string | null {
|
|
74
|
+
return formState.errors[field] || null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Check if field has error
|
|
79
|
+
*/
|
|
80
|
+
export function fieldHasError<T extends Record<string, string>>(
|
|
81
|
+
formState: FormState<T>,
|
|
82
|
+
field: keyof T
|
|
83
|
+
): boolean {
|
|
84
|
+
return formState.errors[field] !== null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Check if field is touched
|
|
89
|
+
*/
|
|
90
|
+
export function isFieldTouched<T extends Record<string, string>>(
|
|
91
|
+
formState: FormState<T>,
|
|
92
|
+
field: keyof T
|
|
93
|
+
): boolean {
|
|
94
|
+
return formState.touched[field] || false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Reset form to initial state
|
|
99
|
+
*/
|
|
100
|
+
export function resetFormState<T extends Record<string, string>>(
|
|
101
|
+
initialValues: T
|
|
102
|
+
): FormState<T> {
|
|
103
|
+
return {
|
|
104
|
+
fields: initialValues,
|
|
105
|
+
errors: Object.keys(initialValues).reduce((acc, key) => {
|
|
106
|
+
acc[key as keyof T] = null;
|
|
107
|
+
return acc;
|
|
108
|
+
}, {} as Record<keyof T, string | null>),
|
|
109
|
+
touched: Object.keys(initialValues).reduce((acc, key) => {
|
|
110
|
+
acc[key as keyof T] = false;
|
|
111
|
+
return acc;
|
|
112
|
+
}, {} as Record<keyof T, boolean>),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Form State Public API
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export {
|
|
6
|
+
isFormValid,
|
|
7
|
+
isFormDirty,
|
|
8
|
+
isFormTouched,
|
|
9
|
+
getFormErrors,
|
|
10
|
+
getFirstFormError,
|
|
11
|
+
countFormErrors,
|
|
12
|
+
getFieldError,
|
|
13
|
+
fieldHasError,
|
|
14
|
+
isFieldTouched,
|
|
15
|
+
resetFormState,
|
|
16
|
+
} from './FormState';
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Form Types
|
|
3
|
+
* Core types for form system
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface FieldState {
|
|
7
|
+
value: string;
|
|
8
|
+
error: string | null;
|
|
9
|
+
touched: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface FormState<T extends Record<string, string>> {
|
|
13
|
+
fields: T;
|
|
14
|
+
errors: Record<keyof T, string | null>;
|
|
15
|
+
touched: Record<keyof T, boolean>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface FieldChangeHandler {
|
|
19
|
+
(value: string): void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface FormFieldConfig {
|
|
23
|
+
validateOnChange?: boolean;
|
|
24
|
+
clearErrorOnChange?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface FormConfig {
|
|
28
|
+
validateOnBlur?: boolean;
|
|
29
|
+
clearErrorsOnSubmit?: boolean;
|
|
30
|
+
}
|