@umituz/react-native-auth 2.7.2 → 2.7.5

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 (44) hide show
  1. package/package.json +33 -9
  2. package/src/application/ports/IAuthRepository.ts +11 -0
  3. package/src/infrastructure/adapters/StorageProviderAdapter.ts +26 -10
  4. package/src/infrastructure/adapters/UIProviderAdapter.ts +20 -9
  5. package/src/infrastructure/providers/FirebaseAuthProvider.ts +3 -2
  6. package/src/infrastructure/repositories/AuthRepository.ts +91 -0
  7. package/src/infrastructure/services/AuthPackage.ts +14 -6
  8. package/src/infrastructure/services/AuthService.ts +67 -119
  9. package/src/infrastructure/services/GuestModeService.ts +1 -6
  10. package/src/infrastructure/utils/AuthValidation.ts +15 -14
  11. package/src/infrastructure/utils/auth-tracker.util.ts +28 -0
  12. package/src/presentation/components/AccountActions.tsx +38 -50
  13. package/src/presentation/components/AuthBottomSheet.tsx +4 -4
  14. package/src/presentation/components/AuthDivider.tsx +0 -1
  15. package/src/presentation/components/AuthGradientBackground.tsx +1 -1
  16. package/src/presentation/components/AuthHeader.tsx +1 -1
  17. package/src/presentation/components/AuthLegalLinks.tsx +7 -8
  18. package/src/presentation/components/AuthLink.tsx +1 -1
  19. package/src/presentation/components/EditProfileActions.tsx +53 -0
  20. package/src/presentation/components/EditProfileAvatar.tsx +33 -0
  21. package/src/presentation/components/EditProfileForm.tsx +55 -0
  22. package/src/presentation/components/LoginForm.tsx +1 -1
  23. package/src/presentation/components/PasswordMatchIndicator.tsx +6 -14
  24. package/src/presentation/components/PasswordStrengthIndicator.tsx +11 -17
  25. package/src/presentation/components/ProfileBenefitsList.tsx +47 -0
  26. package/src/presentation/components/ProfileSection.tsx +20 -85
  27. package/src/presentation/components/RegisterForm.tsx +6 -6
  28. package/src/presentation/components/SocialLoginButtons.tsx +11 -15
  29. package/src/presentation/hooks/mutations/useAuthMutations.ts +50 -0
  30. package/src/presentation/hooks/useAccountManagement.ts +2 -0
  31. package/src/presentation/hooks/useAuthActions.ts +19 -35
  32. package/src/presentation/hooks/useAuthState.ts +4 -1
  33. package/src/presentation/hooks/useLoginForm.ts +6 -8
  34. package/src/presentation/hooks/useProfileUpdate.ts +7 -7
  35. package/src/presentation/hooks/useRegisterForm.ts +16 -17
  36. package/src/presentation/hooks/useUserProfile.ts +3 -3
  37. package/src/presentation/navigation/AuthNavigator.tsx +10 -6
  38. package/src/presentation/screens/AccountScreen.tsx +9 -1
  39. package/src/presentation/screens/EditProfileScreen.tsx +40 -185
  40. package/src/presentation/screens/LoginScreen.tsx +4 -6
  41. package/src/presentation/screens/RegisterScreen.tsx +4 -6
  42. package/src/presentation/stores/authModalStore.ts +2 -1
  43. package/src/types/external.d.ts +31 -45
  44. package/src/infrastructure/services/AuthCoreService.ts +0 -138
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Auth Mutations
3
+ * TanStack Query mutations for auth operations
4
+ */
5
+
6
+ import { useMutation } from "@umituz/react-native-tanstack";
7
+ import { getAuthService } from "../../../infrastructure/services/AuthService";
8
+ import type { SignUpParams, SignInParams } from "../../../application/ports/IAuthService";
9
+ import type { AuthUser } from "../../../domain/entities/AuthUser";
10
+
11
+ export const useSignUpMutation = () => {
12
+ return useMutation({
13
+ mutationFn: async (params: SignUpParams): Promise<AuthUser> => {
14
+ const service = getAuthService();
15
+ if (!service) throw new Error("Auth Service not initialized");
16
+ // Access repository directly
17
+ return service.getRepository().signUp(params);
18
+ },
19
+ });
20
+ };
21
+
22
+ export const useSignInMutation = () => {
23
+ return useMutation({
24
+ mutationFn: async (params: SignInParams): Promise<AuthUser> => {
25
+ const service = getAuthService();
26
+ if (!service) throw new Error("Auth Service not initialized");
27
+ return service.getRepository().signIn(params);
28
+ },
29
+ });
30
+ };
31
+
32
+ export const useSignOutMutation = () => {
33
+ return useMutation({
34
+ mutationFn: async (): Promise<void> => {
35
+ const service = getAuthService();
36
+ if (!service) throw new Error("Auth Service not initialized");
37
+ return service.getRepository().signOut();
38
+ },
39
+ });
40
+ };
41
+
42
+ export const useGuestModeMutation = () => {
43
+ return useMutation({
44
+ mutationFn: async (): Promise<void> => {
45
+ const service = getAuthService();
46
+ if (!service) throw new Error("Auth Service not initialized");
47
+ return service.setGuestMode();
48
+ },
49
+ });
50
+ };
@@ -4,8 +4,10 @@
4
4
  * Generic hook - reauthentication is handled via callback from calling app
5
5
  */
6
6
 
7
+ /* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument */
7
8
  import { useCallback, useState } from "react";
8
9
  import { useAuth } from "./useAuth";
10
+ // @ts-expect-error - Module def issue in node_modules vs types
9
11
  import { deleteCurrentUser } from "@umituz/react-native-firebase";
10
12
 
11
13
  export interface UseAccountManagementOptions {
@@ -1,11 +1,11 @@
1
- /**
2
- * useAuthActions Hook
3
- * Single Responsibility: Handle authentication actions
4
- */
5
-
6
1
  import { useCallback } from "react";
7
- import { getAuthService } from "../../infrastructure/services/AuthService";
8
2
  import type { UseAuthStateResult } from "./useAuthState";
3
+ import {
4
+ useSignInMutation,
5
+ useSignUpMutation,
6
+ useSignOutMutation,
7
+ useGuestModeMutation,
8
+ } from "./mutations/useAuthMutations";
9
9
 
10
10
  export interface UseAuthActionsResult {
11
11
  signUp: (email: string, password: string, displayName?: string) => Promise<void>;
@@ -17,18 +17,17 @@ export interface UseAuthActionsResult {
17
17
  export function useAuthActions(state: UseAuthStateResult): UseAuthActionsResult {
18
18
  const { isGuest, setIsGuest, setLoading, setError } = state;
19
19
 
20
+ const signInMutation = useSignInMutation();
21
+ const signUpMutation = useSignUpMutation();
22
+ const signOutMutation = useSignOutMutation();
23
+ const guestModeMutation = useGuestModeMutation();
24
+
20
25
  const signUp = useCallback(
21
26
  async (email: string, password: string, displayName?: string) => {
22
- const service = getAuthService();
23
- if (!service) {
24
- const err = "Auth service is not initialized";
25
- setError(err);
26
- throw new Error(err);
27
- }
28
27
  try {
29
28
  setLoading(true);
30
29
  setError(null);
31
- await service.signUp({ email, password, displayName });
30
+ await signUpMutation.mutateAsync({ email, password, displayName });
32
31
  if (isGuest) {
33
32
  setIsGuest(false);
34
33
  }
@@ -40,21 +39,15 @@ export function useAuthActions(state: UseAuthStateResult): UseAuthActionsResult
40
39
  setLoading(false);
41
40
  }
42
41
  },
43
- [isGuest, setIsGuest, setLoading, setError],
42
+ [isGuest, setIsGuest, setLoading, setError, signUpMutation],
44
43
  );
45
44
 
46
45
  const signIn = useCallback(
47
46
  async (email: string, password: string) => {
48
- const service = getAuthService();
49
- if (!service) {
50
- const err = "Auth service is not initialized";
51
- setError(err);
52
- throw new Error(err);
53
- }
54
47
  try {
55
48
  setLoading(true);
56
49
  setError(null);
57
- await service.signIn({ email, password });
50
+ await signInMutation.mutateAsync({ email, password });
58
51
  if (isGuest) {
59
52
  setIsGuest(false);
60
53
  }
@@ -66,38 +59,29 @@ export function useAuthActions(state: UseAuthStateResult): UseAuthActionsResult
66
59
  setLoading(false);
67
60
  }
68
61
  },
69
- [isGuest, setIsGuest, setLoading, setError],
62
+ [isGuest, setIsGuest, setLoading, setError, signInMutation],
70
63
  );
71
64
 
72
65
  const signOut = useCallback(async () => {
73
- const service = getAuthService();
74
- if (!service) return;
75
-
76
66
  try {
77
67
  setLoading(true);
78
- await service.signOut();
68
+ await signOutMutation.mutateAsync();
79
69
  } finally {
80
70
  setLoading(false);
81
71
  }
82
- }, [setLoading]);
72
+ }, [setLoading, signOutMutation]);
83
73
 
84
74
  const continueAsGuest = useCallback(async () => {
85
- const service = getAuthService();
86
- if (!service) {
87
- setIsGuest(true);
88
- return;
89
- }
90
-
91
75
  try {
92
76
  setLoading(true);
93
- await service.setGuestMode();
77
+ await guestModeMutation.mutateAsync();
94
78
  setIsGuest(true);
95
79
  } catch {
96
80
  setIsGuest(true);
97
81
  } finally {
98
82
  setLoading(false);
99
83
  }
100
- }, [setIsGuest, setLoading]);
84
+ }, [setIsGuest, setLoading, guestModeMutation]);
101
85
 
102
86
  return {
103
87
  signUp,
@@ -3,9 +3,11 @@
3
3
  * Single Responsibility: Manage authentication state
4
4
  */
5
5
 
6
+ /* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument */
6
7
  import { useState, useEffect, useRef, useMemo } from "react";
7
8
  import { DeviceEventEmitter } from "react-native";
8
9
  import { getAuthService } from "../../infrastructure/services/AuthService";
10
+ // @ts-expect-error - Module def issue in node_modules vs types
9
11
  import { useFirebaseAuth } from "@umituz/react-native-firebase";
10
12
  import { mapToAuthUser } from "../../infrastructure/utils/UserMapper";
11
13
  import type { AuthUser } from "../../domain/entities/AuthUser";
@@ -101,13 +103,14 @@ export function useAuthState(): UseAuthStateResult {
101
103
 
102
104
  const errorSubscription = DeviceEventEmitter.addListener(
103
105
  "auth-error",
104
- (payload: any) => {
106
+ (payload: { error?: string }) => {
105
107
  if (payload?.error) {
106
108
  setError(payload.error);
107
109
  }
108
110
  }
109
111
  );
110
112
 
113
+
111
114
  return () => {
112
115
  guestSubscription.remove();
113
116
  authSubscription.remove();
@@ -7,7 +7,7 @@ import { useState, useCallback } from "react";
7
7
  import { useLocalization } from "@umituz/react-native-localization";
8
8
  import { useAuth } from "./useAuth";
9
9
  import { getAuthErrorLocalizationKey } from "../utils/getAuthErrorMessage";
10
- import { validateEmail } from "../../infrastructure/utils/AuthValidation";
10
+ import { validateEmail, validatePasswordForLogin } from "../../infrastructure/utils/AuthValidation";
11
11
 
12
12
  export interface UseLoginFormResult {
13
13
  email: string;
@@ -59,16 +59,14 @@ export function useLoginForm(): UseLoginFormResult {
59
59
  let hasError = false;
60
60
 
61
61
  const emailResult = validateEmail(email.trim());
62
- if (!emailResult.isValid) {
63
- setEmailError(t("auth.errors.invalidEmail"));
62
+ if (!emailResult.isValid && emailResult.error) {
63
+ setEmailError(t(emailResult.error));
64
64
  hasError = true;
65
65
  }
66
66
 
67
- if (!password.trim()) {
68
- setPasswordError(t("auth.errors.weakPassword"));
69
- hasError = true;
70
- } else if (password.length < 6) {
71
- setPasswordError(t("auth.errors.weakPassword"));
67
+ const passwordResult = validatePasswordForLogin(password);
68
+ if (!passwordResult.isValid && passwordResult.error) {
69
+ setPasswordError(t(passwordResult.error));
72
70
  hasError = true;
73
71
  }
74
72
 
@@ -16,22 +16,21 @@ export interface UseProfileUpdateReturn {
16
16
 
17
17
  export const useProfileUpdate = (): UseProfileUpdateReturn => {
18
18
  const { user } = useAuth();
19
- const [isUpdating, setIsUpdating] = useState(false);
20
- const [error, setError] = useState<string | null>(null);
19
+ const [isUpdating] = useState(false);
20
+ const [error] = useState<string | null>(null);
21
21
 
22
22
  const updateProfile = useCallback(
23
- async (params: UpdateProfileParams) => {
23
+ (_params: UpdateProfileParams) => {
24
24
  if (!user) {
25
- throw new Error("No user logged in");
25
+ return Promise.reject(new Error("No user logged in"));
26
26
  }
27
27
 
28
28
  if (user.isAnonymous) {
29
- throw new Error("Anonymous users cannot update profile");
29
+ return Promise.reject(new Error("Anonymous users cannot update profile"));
30
30
  }
31
31
 
32
32
  // Note: App should implement this via Firebase SDK
33
- // Example: auth().currentUser?.updateProfile({ displayName, photoURL })
34
- throw new Error("Profile update should be implemented by app");
33
+ return Promise.reject(new Error("Profile update should be implemented by app"));
35
34
  },
36
35
  [user],
37
36
  );
@@ -42,3 +41,4 @@ export const useProfileUpdate = (): UseProfileUpdateReturn => {
42
41
  error,
43
42
  };
44
43
  };
44
+
@@ -5,7 +5,6 @@
5
5
 
6
6
  import { useState, useCallback, useMemo } from "react";
7
7
  import { useLocalization } from "@umituz/react-native-localization";
8
- import { batchValidate } from "@umituz/react-native-validation";
9
8
  import {
10
9
  validateEmail,
11
10
  validatePasswordForRegister,
@@ -85,8 +84,8 @@ export function useRegisterForm(): UseRegisterFormResult {
85
84
  }
86
85
  return next;
87
86
  });
88
- if (localError) setLocalError(null);
89
- }, [localError]);
87
+ setLocalError(null);
88
+ }, []);
90
89
 
91
90
  const handleEmailChange = useCallback((text: string) => {
92
91
  setEmail(text);
@@ -97,8 +96,8 @@ export function useRegisterForm(): UseRegisterFormResult {
97
96
  }
98
97
  return next;
99
98
  });
100
- if (localError) setLocalError(null);
101
- }, [localError]);
99
+ setLocalError(null);
100
+ }, []);
102
101
 
103
102
  const handlePasswordChange = useCallback((text: string) => {
104
103
  setPassword(text);
@@ -112,8 +111,8 @@ export function useRegisterForm(): UseRegisterFormResult {
112
111
  }
113
112
  return next;
114
113
  });
115
- if (localError) setLocalError(null);
116
- }, [localError]);
114
+ setLocalError(null);
115
+ }, []);
117
116
 
118
117
  const handleConfirmPasswordChange = useCallback((text: string) => {
119
118
  setConfirmPassword(text);
@@ -124,29 +123,28 @@ export function useRegisterForm(): UseRegisterFormResult {
124
123
  }
125
124
  return next;
126
125
  });
127
- if (localError) setLocalError(null);
128
- }, [localError]);
126
+ setLocalError(null);
127
+ }, []);
129
128
 
130
129
  const handleSignUp = useCallback(async () => {
131
130
  setLocalError(null);
132
131
  setFieldErrors({});
133
132
 
134
- // Manual validation since batchValidate is not available
135
133
  const emailResult = validateEmail(email.trim());
136
- if (!emailResult.isValid) {
137
- setFieldErrors((prev) => ({ ...prev, email: emailResult.error }));
134
+ if (!emailResult.isValid && emailResult.error) {
135
+ setFieldErrors((prev) => ({ ...prev, email: t(emailResult.error as string) }));
138
136
  return;
139
137
  }
140
138
 
141
139
  const passwordResult = validatePasswordForRegister(password, DEFAULT_PASSWORD_CONFIG);
142
- if (!passwordResult.isValid) {
143
- setFieldErrors((prev) => ({ ...prev, password: passwordResult.error }));
140
+ if (!passwordResult.isValid && passwordResult.error) {
141
+ setFieldErrors((prev) => ({ ...prev, password: t(passwordResult.error as string) }));
144
142
  return;
145
143
  }
146
144
 
147
145
  const confirmResult = validatePasswordConfirmation(password, confirmPassword);
148
- if (!confirmResult.isValid) {
149
- setFieldErrors((prev) => ({ ...prev, confirmPassword: confirmResult.error }));
146
+ if (!confirmResult.isValid && confirmResult.error) {
147
+ setFieldErrors((prev) => ({ ...prev, confirmPassword: t(confirmResult.error as string) }));
150
148
  return;
151
149
  }
152
150
 
@@ -156,7 +154,7 @@ export function useRegisterForm(): UseRegisterFormResult {
156
154
  password,
157
155
  displayName.trim() || undefined,
158
156
  );
159
- } catch (err: any) {
157
+ } catch (err: unknown) {
160
158
  const localizationKey = getAuthErrorLocalizationKey(err);
161
159
  const errorMessage = t(localizationKey);
162
160
  setLocalError(errorMessage);
@@ -184,3 +182,4 @@ export function useRegisterForm(): UseRegisterFormResult {
184
182
  };
185
183
  }
186
184
 
185
+
@@ -9,7 +9,7 @@ import { useAuth } from "./useAuth";
9
9
  import { generateGuestName, type GuestNameConfig } from "../../domain/utils/guestNameGenerator";
10
10
 
11
11
  export interface UserProfileData {
12
- displayName: string;
12
+ displayName?: string;
13
13
  userId?: string;
14
14
  isAnonymous: boolean;
15
15
  avatarUrl?: string;
@@ -27,8 +27,8 @@ export const useUserProfile = (
27
27
  ): UserProfileData | undefined => {
28
28
  const { user } = useAuth();
29
29
 
30
- const guestName = params?.guestDisplayName || "Guest";
31
- const accountRoute = params?.accountRoute || "Account";
30
+ const guestName = params?.guestDisplayName;
31
+ const accountRoute = params?.accountRoute;
32
32
  const nameConfig = params?.guestNameConfig;
33
33
 
34
34
  return useMemo(() => {
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import React, { useEffect, useState } from "react";
7
- import { createStackNavigator } from "@react-navigation/stack";
7
+ import { createStackNavigator, StackScreenProps } from "@react-navigation/stack";
8
8
  import { useAppDesignTokens } from "@umituz/react-native-design-system";
9
9
  import { storageRepository } from "@umituz/react-native-storage";
10
10
  import { unwrap } from "@umituz/react-native-storage";
@@ -16,7 +16,7 @@ export type AuthStackParamList = {
16
16
  Register: undefined;
17
17
  };
18
18
 
19
- const AuthStack = createStackNavigator();
19
+ const AuthStack = createStackNavigator<AuthStackParamList>();
20
20
 
21
21
  const SHOW_REGISTER_KEY = "auth_show_register";
22
22
 
@@ -51,15 +51,18 @@ export const AuthNavigator: React.FC<AuthNavigatorProps> = ({
51
51
  >(undefined);
52
52
 
53
53
  useEffect(() => {
54
- storageRepository.getString(SHOW_REGISTER_KEY, "false").then((result) => {
54
+ const checkInitialRoute = async () => {
55
+ const result = await storageRepository.getString(SHOW_REGISTER_KEY, "false");
55
56
  const value = unwrap(result, "false");
56
57
  if (value === "true") {
57
58
  setInitialRouteName("Register");
58
- storageRepository.removeItem(SHOW_REGISTER_KEY);
59
+ void storageRepository.removeItem(SHOW_REGISTER_KEY);
59
60
  } else {
60
61
  setInitialRouteName("Login");
61
62
  }
62
- });
63
+ };
64
+
65
+ void checkInitialRoute();
63
66
  }, []);
64
67
 
65
68
  if (initialRouteName === undefined) {
@@ -76,7 +79,7 @@ export const AuthNavigator: React.FC<AuthNavigatorProps> = ({
76
79
  >
77
80
  <AuthStack.Screen name="Login" component={LoginScreen} />
78
81
  <AuthStack.Screen name="Register">
79
- {(props: any) => (
82
+ {(props: StackScreenProps<AuthStackParamList, "Register">) => (
80
83
  <RegisterScreen
81
84
  {...props}
82
85
  termsUrl={termsUrl}
@@ -90,3 +93,4 @@ export const AuthNavigator: React.FC<AuthNavigatorProps> = ({
90
93
  );
91
94
  };
92
95
 
96
+
@@ -7,6 +7,7 @@
7
7
  import React from "react";
8
8
  import { View, ScrollView, StyleSheet } from "react-native";
9
9
  import { useAppDesignTokens } from "@umituz/react-native-design-system";
10
+
10
11
  import { ProfileSection, type ProfileSectionConfig } from "../components/ProfileSection";
11
12
  import { AccountActions, type AccountActionsConfig } from "../components/AccountActions";
12
13
 
@@ -23,6 +24,12 @@ export interface AccountScreenProps {
23
24
  export const AccountScreen: React.FC<AccountScreenProps> = ({ config }) => {
24
25
  const tokens = useAppDesignTokens();
25
26
 
27
+ const handleLogout = () => {
28
+ if (config.accountActions?.onLogout) {
29
+ void config.accountActions.onLogout();
30
+ }
31
+ };
32
+
26
33
  return (
27
34
  <ScrollView
28
35
  style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}
@@ -30,7 +37,7 @@ export const AccountScreen: React.FC<AccountScreenProps> = ({ config }) => {
30
37
  >
31
38
  <ProfileSection
32
39
  profile={config.profile}
33
- onSignIn={config.profile.isAnonymous ? config.accountActions?.onLogout : undefined}
40
+ onSignIn={config.profile.isAnonymous ? handleLogout : undefined}
34
41
  />
35
42
 
36
43
  {!config.isAnonymous && config.accountActions && (
@@ -54,3 +61,4 @@ const styles = StyleSheet.create({
54
61
  height: 24,
55
62
  },
56
63
  });
64
+