@umituz/react-native-auth 4.3.76 → 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.
Files changed (52) hide show
  1. package/package.json +1 -4
  2. package/src/exports/domain.ts +32 -0
  3. package/src/exports/infrastructure.ts +80 -0
  4. package/src/exports/presentation.ts +115 -0
  5. package/src/exports/shared.ts +93 -0
  6. package/src/exports/utils.ts +10 -0
  7. package/src/index.ts +7 -132
  8. package/src/infrastructure/utils/listener/anonymousSignIn/attemptAnonymousSignIn.ts +81 -0
  9. package/src/infrastructure/utils/listener/anonymousSignIn/constants.ts +7 -0
  10. package/src/infrastructure/utils/listener/anonymousSignIn/createAnonymousSignInHandler.ts +59 -0
  11. package/src/infrastructure/utils/listener/anonymousSignIn/index.ts +16 -0
  12. package/src/infrastructure/utils/listener/anonymousSignIn/types.ts +21 -0
  13. package/src/presentation/components/RegisterForm/RegisterForm.tsx +91 -0
  14. package/src/presentation/components/RegisterForm/RegisterFormFields.tsx +117 -0
  15. package/src/presentation/components/RegisterForm/index.ts +7 -0
  16. package/src/presentation/components/RegisterForm/styles.ts +11 -0
  17. package/src/presentation/components/RegisterForm/types.ts +34 -0
  18. package/src/presentation/hooks/auth/index.ts +7 -0
  19. package/src/presentation/hooks/auth/types.ts +26 -0
  20. package/src/presentation/hooks/auth/useAuthBottomSheet.ts +110 -0
  21. package/src/presentation/hooks/auth/useSocialAuthHandlers.ts +56 -0
  22. package/src/shared/error-handling/handlers/ErrorHandler.ts +70 -0
  23. package/src/shared/error-handling/handlers/FormErrorHandler.ts +99 -0
  24. package/src/shared/error-handling/handlers/index.ts +7 -0
  25. package/src/shared/error-handling/index.ts +19 -0
  26. package/src/shared/error-handling/mappers/ErrorMapper.ts +115 -0
  27. package/src/shared/error-handling/mappers/FieldErrorMapper.ts +97 -0
  28. package/src/shared/error-handling/mappers/index.ts +6 -0
  29. package/src/shared/error-handling/types/ErrorTypes.ts +23 -0
  30. package/src/shared/error-handling/types/index.ts +10 -0
  31. package/src/shared/form/builders/FieldBuilder.ts +90 -0
  32. package/src/shared/form/builders/FormBuilder.ts +126 -0
  33. package/src/shared/form/builders/index.ts +9 -0
  34. package/src/shared/form/index.ts +51 -0
  35. package/src/shared/form/state/FormState.ts +114 -0
  36. package/src/shared/form/state/index.ts +16 -0
  37. package/src/shared/form/types/FormTypes.ts +30 -0
  38. package/src/shared/form/types/index.ts +11 -0
  39. package/src/shared/form/utils/fieldUtils.ts +115 -0
  40. package/src/shared/form/utils/formUtils.ts +113 -0
  41. package/src/shared/form/utils/index.ts +6 -0
  42. package/src/shared/validation/index.ts +23 -0
  43. package/src/shared/validation/rules/ValidationRule.ts +71 -0
  44. package/src/shared/validation/rules/index.ts +5 -0
  45. package/src/shared/validation/sanitizers/EmailSanitizer.ts +22 -0
  46. package/src/shared/validation/sanitizers/PasswordSanitizer.ts +24 -0
  47. package/src/shared/validation/sanitizers/index.ts +7 -0
  48. package/src/shared/validation/types.ts +26 -0
  49. package/src/shared/validation/validators/EmailValidator.ts +61 -0
  50. package/src/shared/validation/validators/NameValidator.ts +52 -0
  51. package/src/shared/validation/validators/PasswordValidator.ts +92 -0
  52. 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,6 @@
1
+ /**
2
+ * Error Mappers Public API
3
+ */
4
+
5
+ export { ErrorMapper, DEFAULT_AUTH_ERROR_MAPPINGS } from './ErrorMapper';
6
+ export { FieldErrorMapper } from './FieldErrorMapper';
@@ -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,10 @@
1
+ /**
2
+ * Error Types Public API
3
+ */
4
+
5
+ export type {
6
+ FieldError,
7
+ FormFieldErrors,
8
+ ErrorMap,
9
+ ErrorMappingConfig,
10
+ } from './ErrorTypes';
@@ -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
+ }