@umituz/react-native-auth 3.4.7 → 3.4.9

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-auth",
3
- "version": "3.4.7",
3
+ "version": "3.4.9",
4
4
  "description": "Authentication service for React Native apps - Secure, type-safe, and production-ready. Provider-agnostic design with dependency injection, configurable validation, and comprehensive error handling.",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -15,10 +15,10 @@ export const configureMigration = (config: MigrationConfig): void => {
15
15
  migrationConfig = config;
16
16
  };
17
17
 
18
- export const migrateUserData = async (
18
+ export const migrateUserData = (
19
19
  anonymousId: string,
20
20
  authId: string
21
- ): Promise<void> => {
21
+ ): void => {
22
22
  if (__DEV__) {
23
23
  console.log(`[Migration] Starting migration from ${anonymousId} to ${authId}`);
24
24
  }
package/src/index.ts CHANGED
@@ -87,6 +87,13 @@ export {
87
87
  resetAuthService,
88
88
  } from './infrastructure/services/AuthService';
89
89
 
90
+ export {
91
+ createStorageProvider,
92
+ StorageProviderAdapter,
93
+ } from './infrastructure/adapters/StorageProviderAdapter';
94
+
95
+ export type { IStorageProvider } from './infrastructure/services/AuthPackage';
96
+
90
97
  export {
91
98
  ensureUserDocument,
92
99
  markUserDeleted,
@@ -187,13 +187,13 @@ export async function ensureUserDocument(
187
187
  const collectionName = userDocumentConfig.collectionName || "users";
188
188
  const userRef = doc(db, collectionName, user.uid);
189
189
  const userDoc = await getDoc(userRef);
190
- const baseData = buildBaseData(user, allExtras as UserDocumentExtras);
190
+ const baseData = buildBaseData(user, allExtras);
191
191
 
192
192
  if (!userDoc.exists()) {
193
- const createData = buildCreateData(baseData, allExtras as UserDocumentExtras);
193
+ const createData = buildCreateData(baseData, allExtras);
194
194
  await setDoc(userRef, createData);
195
195
  } else {
196
- const updateData = buildUpdateData(baseData, allExtras as UserDocumentExtras);
196
+ const updateData = buildUpdateData(baseData, allExtras);
197
197
  await setDoc(userRef, updateData, { merge: true });
198
198
  }
199
199
 
@@ -122,42 +122,44 @@ export async function initializeAuth(
122
122
  // 4. Initialize Auth Listener (for state management)
123
123
  initializeAuthListener({
124
124
  autoAnonymousSignIn,
125
- onAuthStateChange: async (user) => {
126
- if (!user) {
127
- // User signed out
128
- previousUserId = null;
129
- wasAnonymous = false;
130
- onAuthStateChange?.(null);
131
- return;
132
- }
133
-
134
- const currentUserId = user.uid;
135
- const isCurrentlyAnonymous = user.isAnonymous ?? false;
136
-
137
- // Detect anonymous-to-authenticated conversion
138
- if (
139
- previousUserId &&
140
- previousUserId !== currentUserId &&
141
- wasAnonymous &&
142
- !isCurrentlyAnonymous &&
143
- onUserConverted
144
- ) {
145
- try {
146
- await onUserConverted(previousUserId, currentUserId);
147
- } catch {
148
- // Migration failed but don't block user flow
125
+ onAuthStateChange: (user) => {
126
+ void (async () => {
127
+ if (!user) {
128
+ // User signed out
129
+ previousUserId = null;
130
+ wasAnonymous = false;
131
+ await onAuthStateChange?.(null);
132
+ return;
149
133
  }
150
- }
151
134
 
152
- // Create/update user document in Firestore
153
- await ensureUserDocument(user);
135
+ const currentUserId = user.uid;
136
+ const isCurrentlyAnonymous = user.isAnonymous ?? false;
137
+
138
+ // Detect anonymous-to-authenticated conversion
139
+ if (
140
+ previousUserId &&
141
+ previousUserId !== currentUserId &&
142
+ wasAnonymous &&
143
+ !isCurrentlyAnonymous &&
144
+ onUserConverted
145
+ ) {
146
+ try {
147
+ await onUserConverted(previousUserId, currentUserId);
148
+ } catch {
149
+ // Migration failed but don't block user flow
150
+ }
151
+ }
152
+
153
+ // Create/update user document in Firestore
154
+ await ensureUserDocument(user);
154
155
 
155
- // Update tracking state
156
- previousUserId = currentUserId;
157
- wasAnonymous = isCurrentlyAnonymous;
156
+ // Update tracking state
157
+ previousUserId = currentUserId;
158
+ wasAnonymous = isCurrentlyAnonymous;
158
159
 
159
- // Call app's custom callback
160
- onAuthStateChange?.(user);
160
+ // Call app's custom callback
161
+ await onAuthStateChange?.(user);
162
+ })();
161
163
  },
162
164
  });
163
165
 
@@ -11,6 +11,7 @@ import { useAppDesignTokens, AtomicIcon, AtomicText } from "@umituz/react-native
11
11
  export interface AccountActionsConfig {
12
12
  logoutText: string;
13
13
  deleteAccountText: string;
14
+ changePasswordText?: string;
14
15
  logoutConfirmTitle: string;
15
16
  logoutConfirmMessage: string;
16
17
  deleteConfirmTitle: string;
@@ -19,6 +20,8 @@ export interface AccountActionsConfig {
19
20
  deleteErrorMessage?: string;
20
21
  onLogout: () => Promise<void>;
21
22
  onDeleteAccount: () => Promise<void>;
23
+ onChangePassword?: () => void;
24
+ showChangePassword?: boolean;
22
25
  }
23
26
 
24
27
  export interface AccountActionsProps {
@@ -30,6 +33,7 @@ export const AccountActions: React.FC<AccountActionsProps> = ({ config }) => {
30
33
  const {
31
34
  logoutText,
32
35
  deleteAccountText,
36
+ changePasswordText,
33
37
  logoutConfirmTitle,
34
38
  logoutConfirmMessage,
35
39
  deleteConfirmTitle,
@@ -38,6 +42,8 @@ export const AccountActions: React.FC<AccountActionsProps> = ({ config }) => {
38
42
  deleteErrorMessage = "Failed to delete account. Please try again.",
39
43
  onLogout,
40
44
  onDeleteAccount,
45
+ onChangePassword,
46
+ showChangePassword = false,
41
47
  } = config;
42
48
 
43
49
  const handleLogout = () => {
@@ -80,6 +86,21 @@ export const AccountActions: React.FC<AccountActionsProps> = ({ config }) => {
80
86
 
81
87
  return (
82
88
  <View style={styles.container}>
89
+ {/* Change Password */}
90
+ {showChangePassword && onChangePassword && changePasswordText && (
91
+ <TouchableOpacity
92
+ style={[styles.actionButton, { borderColor: tokens.colors.border }]}
93
+ onPress={onChangePassword}
94
+ activeOpacity={0.7}
95
+ >
96
+ <AtomicIcon name="key-outline" size="md" customColor={tokens.colors.textPrimary} />
97
+ <AtomicText style={[styles.actionText, { color: tokens.colors.textPrimary }]}>
98
+ {changePasswordText}
99
+ </AtomicText>
100
+ <AtomicIcon name="chevron-forward" size="sm" color="secondary" />
101
+ </TouchableOpacity>
102
+ )}
103
+
83
104
  {/* Logout */}
84
105
  <TouchableOpacity
85
106
  style={[styles.actionButton, { borderColor: tokens.colors.border }]}
@@ -3,36 +3,45 @@
3
3
  * Main container for auth screens with gradient and scroll
4
4
  */
5
5
 
6
- import React from "react";
6
+ import React, { useMemo } from "react";
7
7
  import {
8
8
  View,
9
9
  StyleSheet,
10
10
  ScrollView,
11
11
  KeyboardAvoidingView,
12
- Platform,
13
12
  } from "react-native";
14
13
  import { useSafeAreaInsets } from "react-native-safe-area-context";
14
+ import { useResponsive } from "@umituz/react-native-design-system";
15
15
  import { AuthGradientBackground } from "./AuthGradientBackground";
16
16
 
17
+ /** Layout constants for auth screens */
18
+ const AUTH_LAYOUT = {
19
+ VERTICAL_PADDING: 40,
20
+ HORIZONTAL_PADDING: 20,
21
+ MAX_CONTENT_WIDTH: 440,
22
+ } as const;
23
+
17
24
  interface AuthContainerProps {
18
25
  children: React.ReactNode;
19
26
  }
20
27
 
21
28
  export const AuthContainer: React.FC<AuthContainerProps> = ({ children }) => {
22
29
  const insets = useSafeAreaInsets();
30
+ const { spacingMultiplier } = useResponsive();
31
+
32
+ const dynamicStyles = useMemo(() => ({
33
+ paddingTop: insets.top + (AUTH_LAYOUT.VERTICAL_PADDING * spacingMultiplier),
34
+ paddingBottom: insets.bottom + (AUTH_LAYOUT.VERTICAL_PADDING * spacingMultiplier),
35
+ }), [insets.top, insets.bottom, spacingMultiplier]);
23
36
 
24
37
  return (
25
38
  <KeyboardAvoidingView
26
39
  style={styles.container}
27
- behavior={Platform.OS === "ios" ? "padding" : "height"}
28
- keyboardVerticalOffset={Platform.OS === "ios" ? 0 : 20}
40
+ behavior="padding"
29
41
  >
30
42
  <AuthGradientBackground />
31
43
  <ScrollView
32
- contentContainerStyle={[
33
- styles.scrollContent,
34
- { paddingTop: insets.top + 40, paddingBottom: insets.bottom + 40 },
35
- ]}
44
+ contentContainerStyle={[styles.scrollContent, dynamicStyles]}
36
45
  keyboardShouldPersistTaps="handled"
37
46
  showsVerticalScrollIndicator={false}
38
47
  >
@@ -48,12 +57,12 @@ const styles = StyleSheet.create({
48
57
  },
49
58
  scrollContent: {
50
59
  flexGrow: 1,
51
- paddingHorizontal: 20,
60
+ paddingHorizontal: AUTH_LAYOUT.HORIZONTAL_PADDING,
52
61
  },
53
62
  content: {
54
63
  flex: 1,
55
64
  justifyContent: "center",
56
- maxWidth: 440,
65
+ maxWidth: AUTH_LAYOUT.MAX_CONTENT_WIDTH,
57
66
  alignSelf: "center",
58
67
  width: "100%",
59
68
  },
@@ -4,9 +4,8 @@ import {
4
4
  TouchableOpacity,
5
5
  StyleSheet,
6
6
  Platform,
7
- ActivityIndicator,
8
7
  } from "react-native";
9
- import { useAppDesignTokens, AtomicText, AtomicIcon } from "@umituz/react-native-design-system";
8
+ import { useAppDesignTokens, AtomicText, AtomicIcon, AtomicSpinner } from "@umituz/react-native-design-system";
10
9
  import { useLocalization } from "@umituz/react-native-localization";
11
10
  import type { SocialAuthProvider } from "../../domain/value-objects/AuthConfig";
12
11
 
@@ -67,7 +66,7 @@ export const SocialLoginButtons: React.FC<SocialLoginButtonsProps> = ({
67
66
  activeOpacity={0.7}
68
67
  >
69
68
  {googleLoading ? (
70
- <ActivityIndicator size="small" color={tokens.colors.textPrimary} />
69
+ <AtomicSpinner size="sm" color={tokens.colors.textPrimary} />
71
70
  ) : (
72
71
  <>
73
72
  <AtomicIcon name="logo-google" size="sm" />
@@ -91,7 +90,7 @@ export const SocialLoginButtons: React.FC<SocialLoginButtonsProps> = ({
91
90
  activeOpacity={0.7}
92
91
  >
93
92
  {appleLoading ? (
94
- <ActivityIndicator size="small" color={tokens.colors.textPrimary} />
93
+ <AtomicSpinner size="sm" color={tokens.colors.textPrimary} />
95
94
  ) : (
96
95
  <>
97
96
  <AtomicIcon name="logo-apple" size="sm" color="onSurface" />
@@ -13,8 +13,6 @@ import {
13
13
  type SocialAuthResult,
14
14
  } from "@umituz/react-native-firebase";
15
15
 
16
- declare const __DEV__: boolean;
17
-
18
16
  export interface UseAppleAuthResult {
19
17
  signInWithApple: () => Promise<SocialAuthResult>;
20
18
  appleLoading: boolean;
@@ -38,18 +36,7 @@ export function useAppleAuth(): UseAppleAuthResult {
38
36
  return { success: false, error: "Apple Sign-In is not available" };
39
37
  }
40
38
 
41
- if (__DEV__) {
42
- // eslint-disable-next-line no-console
43
- console.log("[useAppleAuth] Apple sign-in requested");
44
- }
45
-
46
39
  const result = await signInWithApple();
47
-
48
- if (__DEV__) {
49
- // eslint-disable-next-line no-console
50
- console.log("[useAppleAuth] Apple sign-in result:", result);
51
- }
52
-
53
40
  return result;
54
41
  }, [appleAvailable, signInWithApple]);
55
42
 
@@ -14,8 +14,6 @@ import {
14
14
  type SocialAuthResult,
15
15
  } from "@umituz/react-native-firebase";
16
16
 
17
- declare const __DEV__: boolean;
18
-
19
17
  // Type declarations for expo-auth-session
20
18
  interface GoogleAuthRequestConfig {
21
19
  iosClientId: string;
@@ -59,11 +57,7 @@ try {
59
57
  WebBrowser.maybeCompleteAuthSession();
60
58
  }
61
59
  } catch {
62
- // expo-auth-session not available
63
- if (__DEV__) {
64
- // eslint-disable-next-line no-console
65
- console.log("[useGoogleAuth] expo-auth-session not available");
66
- }
60
+ // expo-auth-session not available - silent fallback
67
61
  }
68
62
 
69
63
  export interface GoogleAuthConfig {
@@ -118,11 +112,8 @@ export function useGoogleAuth(config?: GoogleAuthConfig): UseGoogleAuthResult {
118
112
  if (idToken) {
119
113
  setIsLoading(true);
120
114
  signInWithGoogleToken(idToken)
121
- .catch((error: unknown) => {
122
- if (__DEV__) {
123
- // eslint-disable-next-line no-console
124
- console.error("[useGoogleAuth] Firebase sign-in error:", error);
125
- }
115
+ .catch(() => {
116
+ // Silent error handling
126
117
  })
127
118
  .finally(() => {
128
119
  setIsLoading(false);
@@ -152,12 +143,6 @@ export function useGoogleAuth(config?: GoogleAuthConfig): UseGoogleAuthResult {
152
143
  const firebaseResult = await signInWithGoogleToken(
153
144
  result.authentication.idToken,
154
145
  );
155
-
156
- if (__DEV__) {
157
- // eslint-disable-next-line no-console
158
- console.log("[useGoogleAuth] Sign-in successful:", firebaseResult);
159
- }
160
-
161
146
  return firebaseResult;
162
147
  }
163
148
 
@@ -22,8 +22,6 @@ import {
22
22
  type SocialAuthResult,
23
23
  } from "@umituz/react-native-firebase";
24
24
 
25
- declare const __DEV__: boolean;
26
-
27
25
  export interface UseSocialLoginConfig extends SocialAuthConfig {}
28
26
 
29
27
  export interface UseSocialLoginResult {
@@ -64,11 +62,6 @@ export function useSocialLogin(config?: UseSocialLoginConfig): UseSocialLoginRes
64
62
  return Promise.resolve({ success: false, error: "Google Sign-In is not configured" });
65
63
  }
66
64
 
67
- if (__DEV__) {
68
- // eslint-disable-next-line no-console
69
- console.log("[useSocialLogin] Use useGoogleAuth hook for Google OAuth flow");
70
- }
71
-
72
65
  return Promise.resolve({
73
66
  success: false,
74
67
  error: "Use useGoogleAuth hook for Google OAuth flow",
@@ -87,18 +80,7 @@ export function useSocialLogin(config?: UseSocialLoginConfig): UseSocialLoginRes
87
80
  return { success: false, error: "Apple Sign-In is not available" };
88
81
  }
89
82
 
90
- if (__DEV__) {
91
- // eslint-disable-next-line no-console
92
- console.log("[useSocialLogin] Apple sign-in requested");
93
- }
94
-
95
83
  const result = await firebaseSignInWithApple();
96
-
97
- if (__DEV__) {
98
- // eslint-disable-next-line no-console
99
- console.log("[useSocialLogin] Apple sign-in result:", result);
100
- }
101
-
102
84
  return result;
103
85
  }, [appleAvailable, firebaseSignInWithApple]);
104
86
 
@@ -5,8 +5,8 @@
5
5
  */
6
6
 
7
7
  import React from "react";
8
- import { View, StyleSheet } from "react-native";
9
- import { useAppDesignTokens, ScreenLayout } from "@umituz/react-native-design-system";
8
+ import { View, TouchableOpacity, StyleSheet } from "react-native";
9
+ import { useAppDesignTokens, ScreenLayout, AtomicIcon, AtomicText } from "@umituz/react-native-design-system";
10
10
 
11
11
  import { ProfileSection, type ProfileSectionConfig } from "../components/ProfileSection";
12
12
  import { AccountActions, type AccountActionsConfig } from "../components/AccountActions";
@@ -15,6 +15,8 @@ export interface AccountScreenConfig {
15
15
  profile: ProfileSectionConfig;
16
16
  accountActions?: AccountActionsConfig;
17
17
  isAnonymous: boolean;
18
+ editProfileText?: string;
19
+ onEditProfile?: () => void;
18
20
  }
19
21
 
20
22
  export interface AccountScreenProps {
@@ -43,6 +45,24 @@ export const AccountScreen: React.FC<AccountScreenProps> = ({ config }) => {
43
45
  signInText="Sign In"
44
46
  />
45
47
 
48
+ {/* Edit Profile Option */}
49
+ {!config.isAnonymous && config.onEditProfile && config.editProfileText && (
50
+ <>
51
+ <View style={styles.divider} />
52
+ <TouchableOpacity
53
+ style={[styles.actionButton, { borderColor: tokens.colors.border }]}
54
+ onPress={config.onEditProfile}
55
+ activeOpacity={0.7}
56
+ >
57
+ <AtomicIcon name="person-outline" size="md" customColor={tokens.colors.textPrimary} />
58
+ <AtomicText style={[styles.actionText, { color: tokens.colors.textPrimary }]}>
59
+ {config.editProfileText}
60
+ </AtomicText>
61
+ <AtomicIcon name="chevron-forward" size="sm" color="secondary" />
62
+ </TouchableOpacity>
63
+ </>
64
+ )}
65
+
46
66
  {!config.isAnonymous && config.accountActions && (
47
67
  <>
48
68
  <View style={styles.divider} />
@@ -60,5 +80,19 @@ const styles = StyleSheet.create({
60
80
  divider: {
61
81
  height: 24,
62
82
  },
83
+ actionButton: {
84
+ flexDirection: "row",
85
+ alignItems: "center",
86
+ paddingVertical: 16,
87
+ paddingHorizontal: 16,
88
+ borderRadius: 12,
89
+ borderWidth: 1,
90
+ gap: 12,
91
+ },
92
+ actionText: {
93
+ flex: 1,
94
+ fontSize: 16,
95
+ fontWeight: "500",
96
+ },
63
97
  });
64
98
 
@@ -4,8 +4,8 @@
4
4
  */
5
5
 
6
6
  import React from "react";
7
- import { View, ScrollView, StyleSheet, ActivityIndicator } from "react-native";
8
- import { useAppDesignTokens, AtomicText } from "@umituz/react-native-design-system";
7
+ import { View, ScrollView, StyleSheet } from "react-native";
8
+ import { useAppDesignTokens, AtomicText, AtomicSpinner } from "@umituz/react-native-design-system";
9
9
  import { EditProfileAvatar } from "../components/EditProfileAvatar";
10
10
  import { EditProfileForm } from "../components/EditProfileForm";
11
11
  import { EditProfileActions } from "../components/EditProfileActions";
@@ -46,7 +46,7 @@ export const EditProfileScreen: React.FC<EditProfileScreenProps> = ({ config })
46
46
  if (config.isLoading) {
47
47
  return (
48
48
  <View style={[styles.loading, { backgroundColor: tokens.colors.backgroundPrimary }]}>
49
- <ActivityIndicator size="large" color={tokens.colors.primary} />
49
+ <AtomicSpinner size="lg" color="primary" fullContainer />
50
50
  </View>
51
51
  );
52
52
  }