@umituz/react-native-auth 2.7.3 → 2.7.6

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 (42) hide show
  1. package/package.json +40 -16
  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/AuthLegalLinks.tsx +7 -8
  17. package/src/presentation/components/EditProfileActions.tsx +53 -0
  18. package/src/presentation/components/EditProfileAvatar.tsx +33 -0
  19. package/src/presentation/components/EditProfileForm.tsx +55 -0
  20. package/src/presentation/components/LoginForm.tsx +1 -1
  21. package/src/presentation/components/PasswordMatchIndicator.tsx +6 -14
  22. package/src/presentation/components/PasswordStrengthIndicator.tsx +11 -17
  23. package/src/presentation/components/ProfileBenefitsList.tsx +47 -0
  24. package/src/presentation/components/ProfileSection.tsx +20 -85
  25. package/src/presentation/components/RegisterForm.tsx +6 -6
  26. package/src/presentation/components/SocialLoginButtons.tsx +11 -15
  27. package/src/presentation/hooks/mutations/useAuthMutations.ts +50 -0
  28. package/src/presentation/hooks/useAccountManagement.ts +1 -0
  29. package/src/presentation/hooks/useAuthActions.ts +19 -35
  30. package/src/presentation/hooks/useAuthState.ts +3 -1
  31. package/src/presentation/hooks/useLoginForm.ts +6 -8
  32. package/src/presentation/hooks/useProfileUpdate.ts +7 -7
  33. package/src/presentation/hooks/useRegisterForm.ts +16 -17
  34. package/src/presentation/hooks/useUserProfile.ts +3 -3
  35. package/src/presentation/navigation/AuthNavigator.tsx +10 -6
  36. package/src/presentation/screens/AccountScreen.tsx +9 -1
  37. package/src/presentation/screens/EditProfileScreen.tsx +40 -185
  38. package/src/presentation/screens/LoginScreen.tsx +4 -6
  39. package/src/presentation/screens/RegisterScreen.tsx +4 -6
  40. package/src/presentation/stores/authModalStore.ts +2 -1
  41. package/src/types/external.d.ts +31 -45
  42. package/src/infrastructure/services/AuthCoreService.ts +0 -138
@@ -1,21 +1,26 @@
1
1
  /**
2
2
  * Edit Profile Screen
3
- * Pure UI for profile editing
4
- * Business logic provided via props
3
+ * Pure UI for profile editing - Composition only
5
4
  */
6
5
 
7
6
  import React from "react";
8
- import {
9
- View,
10
- Text,
11
- TextInput,
12
- TouchableOpacity,
13
- ScrollView,
14
- StyleSheet,
15
- ActivityIndicator,
16
- } from "react-native";
17
- import { useAppDesignTokens } from "@umituz/react-native-design-system";
18
- import { Avatar } from "@umituz/react-native-design-system";
7
+ import { View, ScrollView, StyleSheet, ActivityIndicator } from "react-native";
8
+ import { useAppDesignTokens, AtomicText } from "@umituz/react-native-design-system";
9
+ import { EditProfileAvatar } from "../components/EditProfileAvatar";
10
+ import { EditProfileForm } from "../components/EditProfileForm";
11
+ import { EditProfileActions } from "../components/EditProfileActions";
12
+
13
+ export interface EditProfileLabels {
14
+ title: string;
15
+ displayNameLabel: string;
16
+ displayNamePlaceholder: string;
17
+ emailLabel: string;
18
+ emailPlaceholder: string;
19
+ photoLabel: string;
20
+ changePhotoButton: string;
21
+ saveButton: string;
22
+ cancelButton: string;
23
+ }
19
24
 
20
25
  export interface EditProfileConfig {
21
26
  displayName: string;
@@ -28,26 +33,14 @@ export interface EditProfileConfig {
28
33
  onChangePhoto?: () => void;
29
34
  onSave: () => void;
30
35
  onCancel?: () => void;
31
- labels: {
32
- title: string;
33
- displayNameLabel: string;
34
- displayNamePlaceholder: string;
35
- emailLabel: string;
36
- emailPlaceholder: string;
37
- photoLabel: string;
38
- changePhotoButton: string;
39
- saveButton: string;
40
- cancelButton: string;
41
- };
36
+ labels: EditProfileLabels;
42
37
  }
43
38
 
44
39
  export interface EditProfileScreenProps {
45
40
  config: EditProfileConfig;
46
41
  }
47
42
 
48
- export const EditProfileScreen: React.FC<EditProfileScreenProps> = ({
49
- config,
50
- }) => {
43
+ export const EditProfileScreen: React.FC<EditProfileScreenProps> = ({ config }) => {
51
44
  const tokens = useAppDesignTokens();
52
45
 
53
46
  if (config.isLoading) {
@@ -63,115 +56,29 @@ export const EditProfileScreen: React.FC<EditProfileScreenProps> = ({
63
56
  style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}
64
57
  contentContainerStyle={styles.content}
65
58
  >
66
- <Text style={[styles.title, { color: tokens.colors.text }]}>
59
+ <AtomicText type="headlineSmall" style={styles.title}>
67
60
  {config.labels.title}
68
- </Text>
69
-
70
- {/* Avatar */}
71
- <View style={styles.avatarContainer}>
72
- <Avatar
73
- uri={config.photoURL || undefined}
74
- name={config.displayName}
75
- size="xl"
76
- shape="circle"
77
- />
78
- </View>
79
-
80
- {/* Display Name */}
81
- <View style={styles.field}>
82
- <Text style={[styles.label, { color: tokens.colors.textSecondary }]}>
83
- {config.labels.displayNameLabel}
84
- </Text>
85
- <TextInput
86
- style={[
87
- styles.input,
88
- {
89
- backgroundColor: tokens.colors.surface,
90
- color: tokens.colors.text,
91
- borderColor: tokens.colors.border,
92
- },
93
- ]}
94
- value={config.displayName}
95
- onChangeText={config.onChangeDisplayName}
96
- placeholder={config.labels.displayNamePlaceholder}
97
- placeholderTextColor={tokens.colors.textTertiary}
98
- />
99
- </View>
100
-
101
- {/* Email */}
102
- <View style={styles.field}>
103
- <Text style={[styles.label, { color: tokens.colors.textSecondary }]}>
104
- {config.labels.emailLabel}
105
- </Text>
106
- <TextInput
107
- style={[
108
- styles.input,
109
- {
110
- backgroundColor: tokens.colors.surface,
111
- color: tokens.colors.text,
112
- borderColor: tokens.colors.border,
113
- },
114
- ]}
115
- value={config.email}
116
- onChangeText={config.onChangeEmail}
117
- placeholder={config.labels.emailPlaceholder}
118
- placeholderTextColor={tokens.colors.textTertiary}
119
- keyboardType="email-address"
120
- autoCapitalize="none"
121
- />
122
- </View>
61
+ </AtomicText>
123
62
 
124
- {/* Photo */}
125
- {config.onChangePhoto && (
126
- <View style={styles.field}>
127
- <Text style={[styles.label, { color: tokens.colors.textSecondary }]}>
128
- {config.labels.photoLabel}
129
- </Text>
130
- <TouchableOpacity
131
- style={[
132
- styles.photoButton,
133
- { backgroundColor: tokens.colors.surface, borderColor: tokens.colors.border },
134
- ]}
135
- onPress={config.onChangePhoto}
136
- >
137
- <Text style={[styles.photoButtonText, { color: tokens.colors.primary }]}>
138
- {config.labels.changePhotoButton}
139
- </Text>
140
- </TouchableOpacity>
141
- </View>
142
- )}
63
+ <EditProfileAvatar
64
+ photoURL={config.photoURL}
65
+ displayName={config.displayName}
66
+ />
143
67
 
144
- {/* Actions */}
145
- <View style={styles.actions}>
146
- <TouchableOpacity
147
- style={[styles.saveButton, { backgroundColor: tokens.colors.primary }]}
148
- onPress={config.onSave}
149
- disabled={config.isSaving}
150
- >
151
- {config.isSaving ? (
152
- <ActivityIndicator size="small" color={tokens.colors.onPrimary} />
153
- ) : (
154
- <Text style={[styles.saveButtonText, { color: tokens.colors.onPrimary }]}>
155
- {config.labels.saveButton}
156
- </Text>
157
- )}
158
- </TouchableOpacity>
68
+ <EditProfileForm
69
+ displayName={config.displayName}
70
+ email={config.email}
71
+ onChangeDisplayName={config.onChangeDisplayName}
72
+ onChangeEmail={config.onChangeEmail}
73
+ labels={config.labels}
74
+ />
159
75
 
160
- {config.onCancel && (
161
- <TouchableOpacity
162
- style={[
163
- styles.cancelButton,
164
- { backgroundColor: tokens.colors.surface, borderColor: tokens.colors.border },
165
- ]}
166
- onPress={config.onCancel}
167
- disabled={config.isSaving}
168
- >
169
- <Text style={[styles.cancelButtonText, { color: tokens.colors.text }]}>
170
- {config.labels.cancelButton}
171
- </Text>
172
- </TouchableOpacity>
173
- )}
174
- </View>
76
+ <EditProfileActions
77
+ isSaving={config.isSaving}
78
+ onSave={config.onSave}
79
+ onCancel={config.onCancel}
80
+ labels={config.labels}
81
+ />
175
82
  </ScrollView>
176
83
  );
177
84
  };
@@ -189,59 +96,7 @@ const styles = StyleSheet.create({
189
96
  alignItems: "center",
190
97
  },
191
98
  title: {
192
- fontSize: 24,
193
- fontWeight: "600",
194
99
  marginBottom: 24,
195
100
  },
196
- avatarContainer: {
197
- alignItems: "center",
198
- marginBottom: 24,
199
- },
200
- field: {
201
- marginBottom: 20,
202
- },
203
- label: {
204
- fontSize: 14,
205
- fontWeight: "500",
206
- marginBottom: 8,
207
- },
208
- input: {
209
- borderWidth: 1,
210
- borderRadius: 8,
211
- padding: 12,
212
- fontSize: 16,
213
- },
214
- photoButton: {
215
- borderWidth: 1,
216
- borderRadius: 8,
217
- padding: 12,
218
- alignItems: "center",
219
- },
220
- photoButtonText: {
221
- fontSize: 14,
222
- fontWeight: "500",
223
- },
224
- actions: {
225
- marginTop: 32,
226
- gap: 12,
227
- },
228
- saveButton: {
229
- padding: 16,
230
- borderRadius: 8,
231
- alignItems: "center",
232
- },
233
- saveButtonText: {
234
- fontSize: 16,
235
- fontWeight: "600",
236
- },
237
- cancelButton: {
238
- padding: 16,
239
- borderRadius: 8,
240
- alignItems: "center",
241
- borderWidth: 1,
242
- },
243
- cancelButtonText: {
244
- fontSize: 16,
245
- fontWeight: "500",
246
- },
247
101
  });
102
+
@@ -4,23 +4,20 @@
4
4
  */
5
5
 
6
6
  import React from "react";
7
- import { useNavigation } from "@react-navigation/native";
7
+ import { useNavigation, NavigationProp } from "@react-navigation/native";
8
8
  import { useLocalization } from "@umituz/react-native-localization";
9
9
  import type { AuthStackParamList } from "../navigation/AuthNavigator";
10
- import type { StackNavigationProp } from "@react-navigation/stack";
11
10
  import { AuthContainer } from "../components/AuthContainer";
12
11
  import { AuthHeader } from "../components/AuthHeader";
13
12
  import { AuthFormCard } from "../components/AuthFormCard";
14
13
  import { LoginForm } from "../components/LoginForm";
15
14
 
16
- type LoginScreenNavigationProp = any;
17
-
18
15
  export const LoginScreen: React.FC = () => {
19
16
  const { t } = useLocalization();
20
- const navigation = useNavigation();
17
+ const navigation = useNavigation<NavigationProp<AuthStackParamList>>();
21
18
 
22
19
  const handleNavigateToRegister = () => {
23
- navigation.navigate("Register" as never);
20
+ navigation.navigate("Register");
24
21
  };
25
22
 
26
23
  return (
@@ -32,3 +29,4 @@ export const LoginScreen: React.FC = () => {
32
29
  </AuthContainer>
33
30
  );
34
31
  };
32
+
@@ -4,17 +4,14 @@
4
4
  */
5
5
 
6
6
  import React from "react";
7
- import { useNavigation } from "@react-navigation/native";
7
+ import { useNavigation, NavigationProp } from "@react-navigation/native";
8
8
  import { useLocalization } from "@umituz/react-native-localization";
9
9
  import type { AuthStackParamList } from "../navigation/AuthNavigator";
10
- import type { StackNavigationProp } from "@react-navigation/stack";
11
10
  import { AuthContainer } from "../components/AuthContainer";
12
11
  import { AuthHeader } from "../components/AuthHeader";
13
12
  import { AuthFormCard } from "../components/AuthFormCard";
14
13
  import { RegisterForm } from "../components/RegisterForm";
15
14
 
16
- type RegisterScreenNavigationProp = any;
17
-
18
15
  export interface RegisterScreenProps {
19
16
  termsUrl?: string;
20
17
  privacyUrl?: string;
@@ -29,10 +26,10 @@ export const RegisterScreen: React.FC<RegisterScreenProps> = ({
29
26
  onPrivacyPress,
30
27
  }) => {
31
28
  const { t } = useLocalization();
32
- const navigation = useNavigation();
29
+ const navigation = useNavigation<NavigationProp<AuthStackParamList>>();
33
30
 
34
31
  const handleNavigateToLogin = () => {
35
- navigation.navigate("Login" as never);
32
+ navigation.navigate("Login");
36
33
  };
37
34
 
38
35
  return (
@@ -50,3 +47,4 @@ export const RegisterScreen: React.FC<RegisterScreenProps> = ({
50
47
  </AuthContainer>
51
48
  );
52
49
  };
50
+
@@ -64,11 +64,12 @@ export const useAuthModalStore = create<AuthModalStore>((set, get) => ({
64
64
  executePendingCallback: () => {
65
65
  const { pendingCallback } = get();
66
66
  if (pendingCallback) {
67
- pendingCallback();
67
+ void pendingCallback();
68
68
  set({ pendingCallback: null });
69
69
  }
70
70
  },
71
71
 
72
+
72
73
  clearPendingCallback: () => {
73
74
  set({ pendingCallback: null });
74
75
  },
@@ -1,46 +1,25 @@
1
1
  /**
2
- * Type declarations for external dependencies
3
- * These are optional dependencies that should be provided by the consuming application
2
+ * Type declarations for external dependencies that might not have types
3
+ * or where package resolution is failing in the current environment.
4
4
  */
5
5
 
6
- declare module '@umituz/react-native-design-system' {
7
- export function useAppDesignTokens(): any;
8
- export const defaultTheme: any;
9
- }
10
-
11
- declare module '@umituz/react-native-design-system' {
12
- export const AtomicInput: any;
13
- export const AtomicButton: any;
14
- export const AtomicText: any;
15
- export const AtomicView: any;
16
- }
6
+ /* eslint-disable @typescript-eslint/no-explicit-any */
7
+ /* eslint-disable @typescript-eslint/no-unused-vars */
17
8
 
18
- declare module '@umituz/react-native-localization' {
19
- export function useTranslation(): {
20
- t: (key: string, params?: Record<string, any>) => string;
21
- };
22
- export function useLocalization(): {
23
- t: (key: string, params?: Record<string, any>) => string;
24
- };
25
- }
9
+ import React from 'react';
26
10
 
27
- declare module '@umituz/react-native-firebase' {
28
- export function useFirebaseAuth(): any;
29
- }
30
-
31
- declare module '@umituz/react-native-validation' {
32
- export function useValidation(): any;
33
- export function batchValidate(): any;
34
- }
35
-
36
- declare module '@umituz/react-native-storage' {
37
- export const storageRepository: {
38
- getString: (key: string, defaultValue?: string) => Promise<{ value: string } | null>;
39
- setString: (key: string, value: string) => Promise<void>;
40
- removeItem: (key: string) => Promise<void>;
41
- };
42
- export function unwrap(result: any, defaultValue: any): any;
43
- }
11
+ declare module 'expo-device';
12
+ declare module 'expo-application';
13
+ declare module '@umituz/react-native-uuid';
14
+ declare module 'rn-emoji-keyboard';
15
+ declare module '@react-navigation/bottom-tabs';
16
+ declare module '@umituz/react-native-haptics';
17
+ declare module 'expo-clipboard';
18
+ declare module 'expo-sharing';
19
+ declare module 'expo-file-system/build/legacy';
20
+ declare module '@umituz/react-native-design-system-theme';
21
+ declare module '@sentry/react-native';
22
+ declare module '@sentry/types';
44
23
 
45
24
  declare module 'react-native-safe-area-context' {
46
25
  export function useSafeAreaInsets(): {
@@ -52,17 +31,24 @@ declare module 'react-native-safe-area-context' {
52
31
  }
53
32
 
54
33
  declare module 'expo-linear-gradient' {
55
- import { ComponentType } from 'react';
56
- export const LinearGradient: ComponentType<any>;
34
+ export const LinearGradient: React.ComponentType<any>;
57
35
  }
58
36
 
59
37
  declare module '@react-navigation/stack' {
60
- import { ComponentType } from 'react';
61
- export const createStackNavigator: any;
62
- export type StackNavigationProp<any> = any;
38
+ export function createStackNavigator<T = any>(): {
39
+ Navigator: React.ComponentType<any>;
40
+ Screen: React.ComponentType<any>;
41
+ };
42
+ export type StackNavigationProp<T = any> = any;
43
+ export type StackScreenProps<T = any, K = any> = any;
63
44
  }
64
45
 
65
46
  declare module '@react-navigation/native' {
66
- export function useNavigation(): any;
47
+ export function useNavigation<T = any>(): T;
67
48
  export function useFocusEffect(effect: () => void | (() => void)): void;
68
- }
49
+ export type NavigationProp<T = any> = {
50
+ navigate: (name: string, params?: any) => void;
51
+ goBack: () => void;
52
+ reset: (state: any) => void;
53
+ };
54
+ }
@@ -1,138 +0,0 @@
1
- /**
2
- * Auth Core Service
3
- * Handles core authentication operations
4
- */
5
-
6
- import type { IAuthService, SignUpParams, SignInParams } from "../../application/ports/IAuthService";
7
- import type { IAuthProvider } from "../../application/ports/IAuthProvider";
8
- import type { AuthUser } from "../../domain/entities/AuthUser";
9
- import type { AuthConfig } from "../../domain/value-objects/AuthConfig";
10
- import {
11
- AuthInitializationError,
12
- AuthValidationError,
13
- AuthWeakPasswordError,
14
- AuthInvalidEmailError,
15
- } from "../../domain/errors/AuthError";
16
- import {
17
- validateEmail,
18
- validatePasswordForLogin,
19
- validatePasswordForRegister,
20
- validateDisplayName,
21
- } from "../utils/AuthValidation";
22
-
23
- export class AuthCoreService implements Partial<IAuthService> {
24
- private provider: IAuthProvider | null = null;
25
- private config: AuthConfig;
26
-
27
- constructor(config: AuthConfig) {
28
- this.config = config;
29
- }
30
-
31
- async initialize(providerOrAuth: IAuthProvider | any): Promise<void> {
32
- if (!providerOrAuth) {
33
- throw new AuthInitializationError("Auth provider or Firebase Auth instance is required");
34
- }
35
-
36
- // Check if it's a Firebase Auth instance (has currentUser property)
37
- if ("currentUser" in providerOrAuth) {
38
- // Import FirebaseAuthProvider directly
39
- const { FirebaseAuthProvider } = require("../providers/FirebaseAuthProvider");
40
- const firebaseProvider = new FirebaseAuthProvider(providerOrAuth);
41
- await firebaseProvider.initialize();
42
- this.provider = firebaseProvider;
43
- } else {
44
- this.provider = providerOrAuth as IAuthProvider;
45
- await this.provider.initialize();
46
- }
47
- }
48
-
49
- isInitialized(): boolean {
50
- return this.provider !== null && this.provider.isInitialized();
51
- }
52
-
53
- private getProvider(): IAuthProvider {
54
- if (!this.provider || !this.provider.isInitialized()) {
55
- throw new AuthInitializationError("Auth service is not initialized");
56
- }
57
- return this.provider;
58
- }
59
-
60
- async signUp(params: SignUpParams): Promise<AuthUser> {
61
- const provider = this.getProvider();
62
-
63
- // Validate email
64
- const emailResult = validateEmail(params.email);
65
- if (!emailResult.isValid) {
66
- throw new AuthInvalidEmailError(emailResult.error);
67
- }
68
-
69
- // Validate display name if provided
70
- if (params.displayName) {
71
- const nameResult = validateDisplayName(params.displayName);
72
- if (!nameResult.isValid) {
73
- throw new AuthValidationError(nameResult.error || "Invalid name", "displayName");
74
- }
75
- }
76
-
77
- // Validate password strength for registration
78
- const passwordResult = validatePasswordForRegister(params.password, this.config.password);
79
- if (!passwordResult.isValid) {
80
- throw new AuthWeakPasswordError(passwordResult.error);
81
- }
82
-
83
- return provider.signUp({
84
- email: params.email,
85
- password: params.password,
86
- displayName: params.displayName,
87
- });
88
- }
89
-
90
- async signIn(params: SignInParams): Promise<AuthUser> {
91
- const provider = this.getProvider();
92
-
93
- // Validate email format
94
- const emailResult = validateEmail(params.email);
95
- if (!emailResult.isValid) {
96
- throw new AuthInvalidEmailError(emailResult.error);
97
- }
98
-
99
- // For login, only check if password is provided (no strength requirements)
100
- const passwordResult = validatePasswordForLogin(params.password);
101
- if (!passwordResult.isValid) {
102
- throw new AuthValidationError(passwordResult.error || "Password is required", "password");
103
- }
104
-
105
- return provider.signIn({
106
- email: params.email,
107
- password: params.password,
108
- });
109
- }
110
-
111
- async signOut(): Promise<void> {
112
- if (!this.provider) {
113
- return;
114
- }
115
-
116
- await this.provider.signOut();
117
- }
118
-
119
- getCurrentUser(): AuthUser | null {
120
- if (!this.provider) {
121
- return null;
122
- }
123
- return this.provider.getCurrentUser();
124
- }
125
-
126
- onAuthStateChange(callback: (user: AuthUser | null) => void): () => void {
127
- if (!this.provider) {
128
- callback(null);
129
- return () => {};
130
- }
131
-
132
- return this.provider.onAuthStateChange(callback);
133
- }
134
-
135
- getConfig(): AuthConfig {
136
- return this.config;
137
- }
138
- }