@umituz/react-native-auth 3.6.86 → 3.6.88

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 (33) hide show
  1. package/package.json +1 -1
  2. package/src/infrastructure/services/AnonymousModeService.ts +5 -2
  3. package/src/infrastructure/services/AuthService.ts +13 -1
  4. package/src/infrastructure/services/UserDocument.types.ts +60 -0
  5. package/src/infrastructure/services/UserDocumentService.ts +85 -0
  6. package/src/infrastructure/utils/AuthValidation.ts +8 -4
  7. package/src/infrastructure/utils/UserMapper.ts +7 -3
  8. package/src/infrastructure/utils/authStateHandler.ts +7 -0
  9. package/src/infrastructure/utils/error/errorCodeMapping.constants.ts +12 -109
  10. package/src/infrastructure/utils/error/mappings/actionCodeErrorMappings.ts +26 -0
  11. package/src/infrastructure/utils/error/mappings/authErrorMappings.ts +69 -0
  12. package/src/infrastructure/utils/error/mappings/configErrorMappings.ts +31 -0
  13. package/src/infrastructure/utils/error/mappings/errorMapping.types.ts +12 -0
  14. package/src/infrastructure/utils/error/mappings/networkErrorMappings.ts +22 -0
  15. package/src/infrastructure/utils/listener/anonymousHandler.ts +31 -0
  16. package/src/infrastructure/utils/listener/authStateHandler.ts +59 -0
  17. package/src/infrastructure/utils/listener/cleanupHandlers.ts +46 -0
  18. package/src/infrastructure/utils/listener/initializationHandlers.ts +26 -0
  19. package/src/infrastructure/utils/listener/listenerLifecycle.util.ts +19 -161
  20. package/src/infrastructure/utils/listener/listenerState.util.ts +21 -3
  21. package/src/infrastructure/utils/listener/setupListener.ts +63 -0
  22. package/src/infrastructure/utils/userDocumentBuilder.util.ts +110 -0
  23. package/src/presentation/hooks/registerForm/registerFormHandlers.ts +59 -0
  24. package/src/presentation/hooks/registerForm/registerFormSubmit.ts +64 -0
  25. package/src/presentation/hooks/registerForm/useRegisterForm.types.ts +39 -0
  26. package/src/presentation/hooks/useProfileEdit.ts +7 -4
  27. package/src/presentation/hooks/useRegisterForm.ts +31 -109
  28. package/src/presentation/utils/form/formValidation.util.ts +23 -114
  29. package/src/presentation/utils/form/useFormField.hook.ts +2 -2
  30. package/src/presentation/utils/form/validation/formValidation.hook.ts +30 -0
  31. package/src/presentation/utils/form/validation/formValidation.types.ts +31 -0
  32. package/src/presentation/utils/form/validation/formValidation.utils.ts +17 -0
  33. package/src/presentation/utils/form/validation/formValidators.ts +86 -0
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Cleanup Handlers
3
+ * Manages unsubscribe and cleanup logic for auth listener
4
+ */
5
+
6
+ import {
7
+ isListenerInitialized,
8
+ incrementRefCount,
9
+ decrementRefCount,
10
+ resetListenerState,
11
+ } from "./listenerState.util";
12
+
13
+ /**
14
+ * Create unsubscribe function that decrements ref count
15
+ */
16
+ export function createUnsubscribeHandler(): () => void {
17
+ return () => {
18
+ const { shouldCleanup } = decrementRefCount();
19
+
20
+ if (shouldCleanup) {
21
+ resetListenerState();
22
+ }
23
+ };
24
+ }
25
+
26
+ /**
27
+ * Return existing unsubscribe if already initialized
28
+ * Returns null if initialization is in progress
29
+ */
30
+ export function handleExistingInitialization(): (() => void) | null {
31
+ if (!isListenerInitialized()) {
32
+ return null;
33
+ }
34
+
35
+ incrementRefCount();
36
+ return createUnsubscribeHandler();
37
+ }
38
+
39
+ /**
40
+ * Return no-op if initialization is in progress
41
+ */
42
+ export function handleInitializationInProgress(): () => void {
43
+ return () => {
44
+ // No-op - handled by initial initialization
45
+ };
46
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Initialization Handlers
3
+ * Helper functions for listener initialization
4
+ */
5
+
6
+ import type { AuthActions } from "../../../types/auth-store.types";
7
+ import { completeInitialization } from "./listenerState.util";
8
+
9
+ type Store = AuthActions & { isAnonymous: boolean };
10
+
11
+ /**
12
+ * Handle case where Firebase auth is not available
13
+ */
14
+ export function handleNoFirebaseAuth(store: Store): () => void {
15
+ completeInitialization();
16
+ store.setLoading(false);
17
+ store.setInitialized(true);
18
+ return () => {};
19
+ }
20
+
21
+ /**
22
+ * Complete listener setup and mark as initialized
23
+ */
24
+ export function completeListenerSetup(): void {
25
+ completeInitialization();
26
+ }
@@ -1,168 +1,26 @@
1
1
  /**
2
- * Auth Listener Lifecycle Utilities
3
- * Handles subscription and cleanup logic for auth listener
2
+ * Auth Listener Lifecycle - Main Export
3
+ * Re-exports all listener lifecycle utilities from modular files
4
4
  */
5
5
 
6
- import type { Auth, User } from "firebase/auth";
7
- import type { AuthActions } from "../../../types/auth-store.types";
8
- import { createAnonymousSignInHandler } from "./anonymousSignInHandler";
9
- import {
10
- isListenerInitialized,
11
- completeInitialization,
12
- setUnsubscribe,
13
- incrementRefCount,
14
- decrementRefCount,
15
- startAnonymousSignIn,
16
- completeAnonymousSignIn,
17
- resetListenerState,
18
- } from "./listenerState.util";
19
- import { onIdTokenChanged } from "firebase/auth";
20
- import { getAuthService } from "../../../infrastructure/services/AuthService";
21
- import {
22
- createOrUpdateUserDocument,
23
- getFirestoreInstance
24
- } from "../../repositories/UserDocumentRepository";
6
+ // Cleanup handlers
7
+ export {
8
+ createUnsubscribeHandler,
9
+ handleExistingInitialization,
10
+ handleInitializationInProgress,
11
+ } from "./cleanupHandlers";
25
12
 
26
- type Store = AuthActions & { isAnonymous: boolean };
13
+ // Setup listener
14
+ export { setupAuthListener } from "./setupListener";
27
15
 
28
- /**
29
- * Create unsubscribe function that decrements ref count
30
- */
31
- export function createUnsubscribeHandler(): () => void {
32
- return () => {
33
- const { shouldCleanup } = decrementRefCount();
34
-
35
- if (shouldCleanup) {
36
- resetListenerState();
37
- }
38
- };
39
- }
40
-
41
- /**
42
- * Return existing unsubscribe if already initialized
43
- * Returns null if initialization is in progress
44
- */
45
- export function handleExistingInitialization(): (() => void) | null {
46
- if (!isListenerInitialized()) {
47
- return null;
48
- }
49
-
50
- incrementRefCount();
51
- return createUnsubscribeHandler();
52
- }
53
-
54
- /**
55
- * Return no-op if initialization is in progress
56
- */
57
- export function handleInitializationInProgress(): () => void {
58
- return () => {
59
- // No-op - handled by initial initialization
60
- };
61
- }
62
-
63
- /**
64
- * Setup Firebase auth listener
65
- */
66
- export function setupAuthListener(
67
- auth: Auth,
68
- store: Store,
69
- autoAnonymousSignIn: boolean,
70
- onAuthStateChange?: (user: User | null) => void
71
- ): void {
72
- const service = getAuthService();
73
-
74
- if (service) {
75
- const isAnonymous = service.getIsAnonymousMode();
76
- if (isAnonymous) {
77
- store.setIsAnonymous(true);
78
- }
79
- }
80
-
81
- try {
82
- const unsubscribe = onIdTokenChanged(auth, (user) => {
83
- handleAuthStateChange(user, store, auth, autoAnonymousSignIn, onAuthStateChange);
84
- });
16
+ // Auth state handler
17
+ export { handleAuthStateChange } from "./authStateHandler";
85
18
 
86
- setUnsubscribe(unsubscribe);
87
- } catch (error) {
88
- // If listener setup fails, ensure we clean up and mark as initialized
89
- console.error("[AuthListener] Failed to setup auth listener:", error);
90
- completeInitialization();
91
- store.setLoading(false);
92
- store.setInitialized(true);
93
- store.setError("Failed to initialize authentication listener");
94
- throw error;
95
- }
96
- }
97
-
98
- /**
99
- * Handle auth state change from Firebase
100
- */
101
- function handleAuthStateChange(
102
- user: User | null,
103
- store: Store,
104
- auth: Auth,
105
- autoAnonymousSignIn: boolean,
106
- onAuthStateChange?: (user: User | null) => void
107
- ): void {
108
- try {
109
- if (!user && autoAnonymousSignIn) {
110
- // Start anonymous sign-in without blocking
111
- void handleAnonymousMode(store, auth);
112
- store.setFirebaseUser(null);
113
- completeInitialization();
114
- return;
115
- }
116
-
117
- store.setFirebaseUser(user);
118
- store.setInitialized(true);
119
-
120
- // Create or update Firestore user document (best practice)
121
- if (user) {
122
- void createOrUpdateUserDocument(getFirestoreInstance(), user);
123
- }
124
-
125
- // Handle conversion from anonymous
126
- if (user && !user.isAnonymous && store.isAnonymous) {
127
- store.setIsAnonymous(false);
128
- }
129
-
130
- onAuthStateChange?.(user);
131
- } catch (error) {
132
- console.error("[AuthListener] Error handling auth state change:", error);
133
- // Ensure we don't leave the app in a bad state
134
- store.setInitialized(true);
135
- store.setLoading(false);
136
- }
137
- }
138
-
139
- /**
140
- * Handle anonymous mode sign-in
141
- */
142
- async function handleAnonymousMode(store: Store, auth: Auth): Promise<void> {
143
- if (!startAnonymousSignIn()) {
144
- return; // Already signing in
145
- }
146
-
147
- const handleAnonymousSignIn = createAnonymousSignInHandler(auth, store);
148
-
149
- try {
150
- await handleAnonymousSignIn();
151
- } finally {
152
- completeAnonymousSignIn();
153
- }
154
- }
155
-
156
- /**
157
- * Handle case where Firebase auth is not available
158
- */
159
- export function handleNoFirebaseAuth(store: Store): () => void {
160
- completeInitialization();
161
- store.setLoading(false);
162
- store.setInitialized(true);
163
- return () => {};
164
- }
19
+ // Anonymous mode handler
20
+ export { handleAnonymousMode } from "./anonymousHandler";
165
21
 
166
- export function completeListenerSetup() {
167
- completeInitialization();
168
- }
22
+ // Initialization handlers
23
+ export {
24
+ handleNoFirebaseAuth,
25
+ completeListenerSetup,
26
+ } from "./initializationHandlers";
@@ -8,6 +8,7 @@ export interface ListenerState {
8
8
  refCount: number;
9
9
  initializationInProgress: boolean;
10
10
  anonymousSignInInProgress: boolean;
11
+ cleanupInProgress: boolean;
11
12
  unsubscribe: (() => void) | null;
12
13
  }
13
14
 
@@ -16,6 +17,7 @@ const state: ListenerState = {
16
17
  refCount: 0,
17
18
  initializationInProgress: false,
18
19
  anonymousSignInInProgress: false,
20
+ cleanupInProgress: false,
19
21
  unsubscribe: null,
20
22
  };
21
23
 
@@ -60,11 +62,16 @@ export function startInitialization(): boolean {
60
62
 
61
63
  /**
62
64
  * Complete initialization
65
+ * If refCount is 0, set to 1 (first subscriber)
66
+ * Otherwise, keep existing refCount from concurrent subscribers
63
67
  */
64
68
  export function completeInitialization(): void {
65
69
  state.initializationInProgress = false;
66
70
  state.initialized = true;
67
- state.refCount = 1;
71
+ // Only set refCount to 1 if it's still 0 (no concurrent subscribers)
72
+ if (state.refCount === 0) {
73
+ state.refCount = 1;
74
+ }
68
75
  }
69
76
 
70
77
  /**
@@ -85,10 +92,20 @@ export function incrementRefCount(): number {
85
92
  /**
86
93
  * Decrement reference count when a subscriber leaves
87
94
  * Returns true if cleanup should be performed
95
+ * Uses cleanupInProgress flag to prevent concurrent cleanup attempts
88
96
  */
89
97
  export function decrementRefCount(): { shouldCleanup: boolean; count: number } {
90
98
  state.refCount--;
91
- const shouldCleanup = state.refCount <= 0 && state.unsubscribe !== null;
99
+ const shouldCleanup =
100
+ state.refCount <= 0 &&
101
+ state.unsubscribe !== null &&
102
+ !state.cleanupInProgress;
103
+
104
+ // If cleanup should happen, mark as in progress to prevent concurrent cleanup
105
+ if (shouldCleanup) {
106
+ state.cleanupInProgress = true;
107
+ }
108
+
92
109
  return { shouldCleanup, count: state.refCount };
93
110
  }
94
111
 
@@ -111,7 +128,7 @@ export function completeAnonymousSignIn(): void {
111
128
  }
112
129
 
113
130
  /**
114
- * Reset all state (for testing)
131
+ * Reset all state (for testing and cleanup)
115
132
  */
116
133
  export function resetListenerState(): void {
117
134
  if (state.unsubscribe) {
@@ -121,5 +138,6 @@ export function resetListenerState(): void {
121
138
  state.refCount = 0;
122
139
  state.initializationInProgress = false;
123
140
  state.anonymousSignInInProgress = false;
141
+ state.cleanupInProgress = false;
124
142
  state.unsubscribe = null;
125
143
  }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Setup Auth Listener
3
+ * Configures Firebase auth listener with timeout protection
4
+ */
5
+
6
+ import type { Auth, User } from "firebase/auth";
7
+ import { onIdTokenChanged } from "firebase/auth";
8
+ import type { AuthActions } from "../../../types/auth-store.types";
9
+ import { getAuthService } from "../../services/AuthService";
10
+ import { completeInitialization, setUnsubscribe } from "./listenerState.util";
11
+ import { handleAuthStateChange } from "./authStateHandler";
12
+
13
+ type Store = AuthActions & { isAnonymous: boolean };
14
+
15
+ /**
16
+ * Setup Firebase auth listener with timeout protection
17
+ */
18
+ export function setupAuthListener(
19
+ auth: Auth,
20
+ store: Store,
21
+ autoAnonymousSignIn: boolean,
22
+ onAuthStateChange?: (user: User | null) => void
23
+ ): void {
24
+ const service = getAuthService();
25
+
26
+ if (service) {
27
+ const isAnonymous = service.getIsAnonymousMode();
28
+ if (isAnonymous) {
29
+ store.setIsAnonymous(true);
30
+ }
31
+ }
32
+
33
+ // Safety timeout: if listener doesn't trigger within 10 seconds, mark as initialized
34
+ let hasTriggered = false;
35
+ const timeout = setTimeout(() => {
36
+ if (!hasTriggered) {
37
+ console.warn("[AuthListener] Auth listener timeout - marking as initialized");
38
+ store.setInitialized(true);
39
+ store.setLoading(false);
40
+ }
41
+ }, 10000);
42
+
43
+ try {
44
+ const unsubscribe = onIdTokenChanged(auth, (user) => {
45
+ if (!hasTriggered) {
46
+ hasTriggered = true;
47
+ clearTimeout(timeout);
48
+ }
49
+ handleAuthStateChange(user, store, auth, autoAnonymousSignIn, onAuthStateChange);
50
+ });
51
+
52
+ setUnsubscribe(unsubscribe);
53
+ } catch (error) {
54
+ clearTimeout(timeout);
55
+ // If listener setup fails, ensure we clean up and mark as initialized
56
+ console.error("[AuthListener] Failed to setup auth listener:", error);
57
+ completeInitialization();
58
+ store.setLoading(false);
59
+ store.setInitialized(true);
60
+ store.setError("Failed to initialize authentication listener");
61
+ throw error;
62
+ }
63
+ }
@@ -0,0 +1,110 @@
1
+ /**
2
+ * User Document Builder Utility
3
+ * Builds user document data for Firestore (max 100 lines)
4
+ */
5
+
6
+ import { serverTimestamp } from "firebase/firestore";
7
+ import type {
8
+ UserDocumentUser,
9
+ UserDocumentExtras,
10
+ } from "../services/UserDocument.types";
11
+
12
+ /**
13
+ * Gets the sign-up method from user provider data
14
+ */
15
+ export function getSignUpMethod(user: UserDocumentUser): string | undefined {
16
+ if (user.isAnonymous) return "anonymous";
17
+ if (user.email) {
18
+ const providerData = (
19
+ user as unknown as { providerData?: { providerId: string }[] }
20
+ ).providerData;
21
+ if (providerData && providerData.length > 0) {
22
+ const providerId = providerData[0]?.providerId;
23
+ if (providerId === "google.com") return "google";
24
+ if (providerId === "apple.com") return "apple";
25
+ if (providerId === "password") return "email";
26
+ }
27
+ return "email";
28
+ }
29
+ return undefined;
30
+ }
31
+
32
+ /**
33
+ * Builds base user data from user object and extras
34
+ */
35
+ export function buildBaseData(
36
+ user: UserDocumentUser,
37
+ extras?: UserDocumentExtras,
38
+ ): Record<string, unknown> {
39
+ const data: Record<string, unknown> = {
40
+ uid: user.uid,
41
+ displayName: user.displayName,
42
+ email: user.email,
43
+ photoURL: user.photoURL,
44
+ isAnonymous: user.isAnonymous,
45
+ };
46
+
47
+ if (extras) {
48
+ const internalFields = ["signUpMethod", "previousAnonymousUserId"];
49
+ Object.keys(extras).forEach((key) => {
50
+ if (!internalFields.includes(key)) {
51
+ const val = extras[key];
52
+ if (val !== undefined) {
53
+ data[key] = val;
54
+ }
55
+ }
56
+ });
57
+ }
58
+
59
+ return data;
60
+ }
61
+
62
+ /**
63
+ * Builds user document data for creation
64
+ */
65
+ export function buildCreateData(
66
+ baseData: Record<string, unknown>,
67
+ extraFields: Record<string, unknown> | undefined,
68
+ extras?: UserDocumentExtras,
69
+ ): Record<string, unknown> {
70
+ const createData: Record<string, unknown> = {
71
+ ...baseData,
72
+ ...extraFields,
73
+ createdAt: serverTimestamp(),
74
+ updatedAt: serverTimestamp(),
75
+ lastLoginAt: serverTimestamp(),
76
+ };
77
+
78
+ if (extras?.previousAnonymousUserId) {
79
+ createData.previousAnonymousUserId = extras.previousAnonymousUserId;
80
+ createData.convertedFromAnonymous = true;
81
+ createData.convertedAt = serverTimestamp();
82
+ }
83
+
84
+ if (extras?.signUpMethod) createData.signUpMethod = extras.signUpMethod;
85
+
86
+ return createData;
87
+ }
88
+
89
+ /**
90
+ * Builds user document data for update
91
+ */
92
+ export function buildUpdateData(
93
+ baseData: Record<string, unknown>,
94
+ extras?: UserDocumentExtras,
95
+ ): Record<string, unknown> {
96
+ const updateData: Record<string, unknown> = {
97
+ ...baseData,
98
+ lastLoginAt: serverTimestamp(),
99
+ updatedAt: serverTimestamp(),
100
+ };
101
+
102
+ if (extras?.previousAnonymousUserId) {
103
+ updateData.previousAnonymousUserId = extras.previousAnonymousUserId;
104
+ updateData.convertedFromAnonymous = true;
105
+ updateData.convertedAt = serverTimestamp();
106
+ if (extras?.signUpMethod) updateData.signUpMethod = extras.signUpMethod;
107
+ }
108
+
109
+ return updateData;
110
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Register Form Change Handlers
3
+ * Input field change handlers with error clearing
4
+ */
5
+
6
+ import { useCallback } from "react";
7
+ import type { FieldErrors } from "./useRegisterForm.types";
8
+ import { clearFieldError, clearFieldErrors } from "../../utils/form/formErrorUtils";
9
+
10
+ type RegisterFieldKey = "displayName" | "email" | "password" | "confirmPassword";
11
+
12
+ export function useRegisterFormHandlers(
13
+ updateField: (field: RegisterFieldKey, value: string) => void,
14
+ setFieldErrors: React.Dispatch<React.SetStateAction<FieldErrors>>,
15
+ clearLocalError: () => void
16
+ ) {
17
+ const handleDisplayNameChange = useCallback(
18
+ (text: string) => {
19
+ updateField("displayName", text);
20
+ clearFieldError(setFieldErrors, "displayName");
21
+ clearLocalError();
22
+ },
23
+ [updateField, clearLocalError]
24
+ );
25
+
26
+ const handleEmailChange = useCallback(
27
+ (text: string) => {
28
+ updateField("email", text);
29
+ clearFieldError(setFieldErrors, "email");
30
+ clearLocalError();
31
+ },
32
+ [updateField, clearLocalError]
33
+ );
34
+
35
+ const handlePasswordChange = useCallback(
36
+ (text: string) => {
37
+ updateField("password", text);
38
+ clearFieldErrors(setFieldErrors, ["password", "confirmPassword"]);
39
+ clearLocalError();
40
+ },
41
+ [updateField, clearLocalError]
42
+ );
43
+
44
+ const handleConfirmPasswordChange = useCallback(
45
+ (text: string) => {
46
+ updateField("confirmPassword", text);
47
+ clearFieldError(setFieldErrors, "confirmPassword");
48
+ clearLocalError();
49
+ },
50
+ [updateField, clearLocalError]
51
+ );
52
+
53
+ return {
54
+ handleDisplayNameChange,
55
+ handleEmailChange,
56
+ handlePasswordChange,
57
+ handleConfirmPasswordChange,
58
+ };
59
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Register Form Submit Logic
3
+ * Handles form validation and sign-up submission
4
+ */
5
+
6
+ import { useCallback } from "react";
7
+ import { alertService } from "@umituz/react-native-design-system";
8
+ import { DEFAULT_PASSWORD_CONFIG } from "../../../domain/value-objects/AuthConfig";
9
+ import {
10
+ validateRegisterForm,
11
+ errorsToFieldErrors,
12
+ } from "../../utils/form/formValidation.util";
13
+ import { getAuthErrorLocalizationKey } from "../../utils/getAuthErrorMessage";
14
+ import type { FieldErrors, RegisterFormTranslations } from "./useRegisterForm.types";
15
+
16
+ interface RegisterFormFields {
17
+ displayName: string;
18
+ email: string;
19
+ password: string;
20
+ confirmPassword: string;
21
+ }
22
+
23
+ export function useRegisterFormSubmit(
24
+ fields: RegisterFormFields,
25
+ signUp: (email: string, password: string, displayName?: string) => Promise<void>,
26
+ setFieldErrors: React.Dispatch<React.SetStateAction<FieldErrors>>,
27
+ setLocalError: React.Dispatch<React.SetStateAction<string | null>>,
28
+ clearFormErrors: () => void,
29
+ getErrorMessage: (key: string) => string,
30
+ translations?: RegisterFormTranslations
31
+ ) {
32
+ const handleSignUp = useCallback(async () => {
33
+ clearFormErrors();
34
+
35
+ const validation = validateRegisterForm(
36
+ {
37
+ displayName: fields.displayName.trim() || undefined,
38
+ email: fields.email.trim(),
39
+ password: fields.password,
40
+ confirmPassword: fields.confirmPassword,
41
+ },
42
+ getErrorMessage,
43
+ DEFAULT_PASSWORD_CONFIG
44
+ );
45
+
46
+ if (!validation.isValid) {
47
+ setFieldErrors(errorsToFieldErrors(validation.errors));
48
+ return;
49
+ }
50
+
51
+ try {
52
+ await signUp(fields.email.trim(), fields.password, fields.displayName.trim() || undefined);
53
+
54
+ if (translations) {
55
+ alertService.success(translations.successTitle, translations.signUpSuccess);
56
+ }
57
+ } catch (err: unknown) {
58
+ const localizationKey = getAuthErrorLocalizationKey(err);
59
+ setLocalError(getErrorMessage(localizationKey));
60
+ }
61
+ }, [fields, signUp, translations, getErrorMessage, clearFormErrors, setFieldErrors, setLocalError]);
62
+
63
+ return { handleSignUp };
64
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Register Form Types
3
+ * Type definitions for register form hook
4
+ */
5
+
6
+ export type FieldErrors = {
7
+ displayName?: string;
8
+ email?: string;
9
+ password?: string;
10
+ confirmPassword?: string;
11
+ };
12
+
13
+ export interface RegisterFormTranslations {
14
+ successTitle: string;
15
+ signUpSuccess: string;
16
+ errors: Record<string, string>;
17
+ }
18
+
19
+ export interface UseRegisterFormConfig {
20
+ translations: RegisterFormTranslations;
21
+ }
22
+
23
+ export interface UseRegisterFormResult {
24
+ displayName: string;
25
+ email: string;
26
+ password: string;
27
+ confirmPassword: string;
28
+ fieldErrors: FieldErrors;
29
+ localError: string | null;
30
+ loading: boolean;
31
+ passwordRequirements: { hasMinLength: boolean };
32
+ passwordsMatch: boolean;
33
+ handleDisplayNameChange: (text: string) => void;
34
+ handleEmailChange: (text: string) => void;
35
+ handlePasswordChange: (text: string) => void;
36
+ handleConfirmPasswordChange: (text: string) => void;
37
+ handleSignUp: () => Promise<void>;
38
+ displayError: string | null;
39
+ }
@@ -58,10 +58,13 @@ export const useProfileEdit = (
58
58
  isValid: boolean;
59
59
  errors: string[];
60
60
  } => {
61
- const result = validateProfileForm({
62
- displayName: formState.displayName,
63
- email: formState.email,
64
- });
61
+ const result = validateProfileForm(
62
+ {
63
+ displayName: formState.displayName,
64
+ email: formState.email,
65
+ },
66
+ (key) => key // Pass-through function - returns key as-is since caller handles translation
67
+ );
65
68
 
66
69
  return {
67
70
  isValid: result.isValid,