@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.
- package/package.json +33 -9
- package/src/application/ports/IAuthRepository.ts +11 -0
- package/src/infrastructure/adapters/StorageProviderAdapter.ts +26 -10
- package/src/infrastructure/adapters/UIProviderAdapter.ts +20 -9
- package/src/infrastructure/providers/FirebaseAuthProvider.ts +3 -2
- package/src/infrastructure/repositories/AuthRepository.ts +91 -0
- package/src/infrastructure/services/AuthPackage.ts +14 -6
- package/src/infrastructure/services/AuthService.ts +67 -119
- package/src/infrastructure/services/GuestModeService.ts +1 -6
- package/src/infrastructure/utils/AuthValidation.ts +15 -14
- package/src/infrastructure/utils/auth-tracker.util.ts +28 -0
- package/src/presentation/components/AccountActions.tsx +38 -50
- package/src/presentation/components/AuthBottomSheet.tsx +4 -4
- package/src/presentation/components/AuthDivider.tsx +0 -1
- package/src/presentation/components/AuthGradientBackground.tsx +1 -1
- package/src/presentation/components/AuthHeader.tsx +1 -1
- package/src/presentation/components/AuthLegalLinks.tsx +7 -8
- package/src/presentation/components/AuthLink.tsx +1 -1
- package/src/presentation/components/EditProfileActions.tsx +53 -0
- package/src/presentation/components/EditProfileAvatar.tsx +33 -0
- package/src/presentation/components/EditProfileForm.tsx +55 -0
- package/src/presentation/components/LoginForm.tsx +1 -1
- package/src/presentation/components/PasswordMatchIndicator.tsx +6 -14
- package/src/presentation/components/PasswordStrengthIndicator.tsx +11 -17
- package/src/presentation/components/ProfileBenefitsList.tsx +47 -0
- package/src/presentation/components/ProfileSection.tsx +20 -85
- package/src/presentation/components/RegisterForm.tsx +6 -6
- package/src/presentation/components/SocialLoginButtons.tsx +11 -15
- package/src/presentation/hooks/mutations/useAuthMutations.ts +50 -0
- package/src/presentation/hooks/useAccountManagement.ts +2 -0
- package/src/presentation/hooks/useAuthActions.ts +19 -35
- package/src/presentation/hooks/useAuthState.ts +4 -1
- package/src/presentation/hooks/useLoginForm.ts +6 -8
- package/src/presentation/hooks/useProfileUpdate.ts +7 -7
- package/src/presentation/hooks/useRegisterForm.ts +16 -17
- package/src/presentation/hooks/useUserProfile.ts +3 -3
- package/src/presentation/navigation/AuthNavigator.tsx +10 -6
- package/src/presentation/screens/AccountScreen.tsx +9 -1
- package/src/presentation/screens/EditProfileScreen.tsx +40 -185
- package/src/presentation/screens/LoginScreen.tsx +4 -6
- package/src/presentation/screens/RegisterScreen.tsx +4 -6
- package/src/presentation/stores/authModalStore.ts +2 -1
- package/src/types/external.d.ts +31 -45
- package/src/infrastructure/services/AuthCoreService.ts +0 -138
|
@@ -6,162 +6,133 @@
|
|
|
6
6
|
import type { Auth } from "firebase/auth";
|
|
7
7
|
import type { IAuthService, SignUpParams, SignInParams } from "../../application/ports/IAuthService";
|
|
8
8
|
import type { IAuthProvider } from "../../application/ports/IAuthProvider";
|
|
9
|
+
import { FirebaseAuthProvider } from "../providers/FirebaseAuthProvider";
|
|
9
10
|
import type { AuthUser } from "../../domain/entities/AuthUser";
|
|
10
11
|
import type { AuthConfig } from "../../domain/value-objects/AuthConfig";
|
|
11
12
|
import { DEFAULT_AUTH_CONFIG } from "../../domain/value-objects/AuthConfig";
|
|
12
|
-
import {
|
|
13
|
-
import { GuestModeService
|
|
13
|
+
import { AuthRepository } from "../repositories/AuthRepository";
|
|
14
|
+
import { GuestModeService } from "./GuestModeService";
|
|
14
15
|
import { authEventService } from "./AuthEventService";
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
trackPackageError,
|
|
18
|
-
addPackageBreadcrumb,
|
|
19
|
-
} from "@umituz/react-native-sentry";
|
|
16
|
+
import { authTracker } from "../utils/auth-tracker.util";
|
|
17
|
+
import type { IStorageProvider } from "./AuthPackage";
|
|
20
18
|
|
|
21
19
|
export class AuthService implements IAuthService {
|
|
22
|
-
private
|
|
20
|
+
private repository!: AuthRepository;
|
|
23
21
|
private guestModeService: GuestModeService;
|
|
24
22
|
private storageProvider?: IStorageProvider;
|
|
25
23
|
private initialized: boolean = false;
|
|
24
|
+
private config: AuthConfig;
|
|
26
25
|
|
|
27
26
|
constructor(config: Partial<AuthConfig> = {}, storageProvider?: IStorageProvider) {
|
|
28
|
-
|
|
27
|
+
this.config = {
|
|
29
28
|
...DEFAULT_AUTH_CONFIG,
|
|
30
29
|
...config,
|
|
31
|
-
password: {
|
|
32
|
-
...DEFAULT_AUTH_CONFIG.password,
|
|
33
|
-
...config.password,
|
|
34
|
-
},
|
|
30
|
+
password: { ...DEFAULT_AUTH_CONFIG.password, ...config.password },
|
|
35
31
|
};
|
|
32
|
+
// Initialize with a dummy provider effectively, or null?
|
|
33
|
+
// AuthRepository needs a provider. We can't init it without one.
|
|
34
|
+
// We'll initialize it properly in initialize()
|
|
35
|
+
// For now we can cast null or strict init check.
|
|
36
|
+
// Better: Allow repository to be nullable or initialize with a dummy/proxy.
|
|
37
|
+
// To satisfy strict TS, let's delay repository creation or create a NotInitializedProvider.
|
|
38
|
+
// But since initialize() sets it up, we can use a ! or optional.
|
|
36
39
|
|
|
37
|
-
this.coreService = new AuthCoreService(authConfig);
|
|
38
40
|
this.guestModeService = new GuestModeService();
|
|
39
41
|
this.storageProvider = storageProvider;
|
|
42
|
+
|
|
43
|
+
// We can't instantiate AuthRepository yet if we don't have provider.
|
|
44
|
+
// So we'll have to keep it optional or allow late init.
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private get repositoryInstance(): AuthRepository {
|
|
48
|
+
if (!this.initialized) throw new Error("AuthService not initialized");
|
|
49
|
+
return this.repository;
|
|
40
50
|
}
|
|
41
51
|
|
|
42
52
|
async initialize(providerOrAuth: IAuthProvider | Auth): Promise<void> {
|
|
43
|
-
if (this.initialized)
|
|
44
|
-
|
|
53
|
+
if (this.initialized) return;
|
|
54
|
+
|
|
55
|
+
let provider: IAuthProvider;
|
|
56
|
+
|
|
57
|
+
// Check if it's a Firebase Auth instance (has currentUser property)
|
|
58
|
+
if ("currentUser" in providerOrAuth) {
|
|
59
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
60
|
+
const firebaseProvider = new FirebaseAuthProvider(providerOrAuth as any);
|
|
61
|
+
await firebaseProvider.initialize();
|
|
62
|
+
provider = firebaseProvider as unknown as IAuthProvider;
|
|
63
|
+
} else {
|
|
64
|
+
provider = providerOrAuth;
|
|
65
|
+
await provider.initialize();
|
|
45
66
|
}
|
|
46
67
|
|
|
47
|
-
|
|
48
|
-
await this.coreService.initialize(providerOrAuth);
|
|
68
|
+
this.repository = new AuthRepository(provider, this.config);
|
|
49
69
|
|
|
50
|
-
// Initialize guest mode if storage provider is available
|
|
51
70
|
if (this.storageProvider) {
|
|
52
71
|
await this.guestModeService.load(this.storageProvider);
|
|
53
72
|
}
|
|
54
|
-
|
|
55
73
|
this.initialized = true;
|
|
56
74
|
}
|
|
57
75
|
|
|
58
76
|
isInitialized(): boolean {
|
|
59
|
-
return this.initialized
|
|
77
|
+
return this.initialized;
|
|
60
78
|
}
|
|
61
79
|
|
|
62
80
|
async signUp(params: SignUpParams): Promise<AuthUser> {
|
|
63
|
-
|
|
64
|
-
email: params.email,
|
|
65
|
-
});
|
|
66
|
-
|
|
81
|
+
authTracker.logOperationStarted("Sign up", { email: params.email });
|
|
67
82
|
try {
|
|
68
|
-
const user = await this.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (this.guestModeService.getIsGuestMode() && this.storageProvider) {
|
|
72
|
-
await this.guestModeService.clear(this.storageProvider);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
addPackageBreadcrumb("auth", "Sign up successful", {
|
|
76
|
-
userId: user.uid,
|
|
77
|
-
});
|
|
78
|
-
|
|
83
|
+
const user = await this.repositoryInstance.signUp(params);
|
|
84
|
+
await this.clearGuestModeIfNeeded();
|
|
85
|
+
authTracker.logOperationSuccess("Sign up", { userId: user.uid });
|
|
79
86
|
authEventService.emitUserAuthenticated(user.uid);
|
|
80
87
|
return user;
|
|
81
88
|
} catch (error) {
|
|
82
|
-
|
|
83
|
-
error instanceof Error ? error : new Error("Sign up failed"),
|
|
84
|
-
{
|
|
85
|
-
packageName: "auth",
|
|
86
|
-
operation: "sign-up",
|
|
87
|
-
email: params.email,
|
|
88
|
-
}
|
|
89
|
-
);
|
|
89
|
+
authTracker.logOperationError("sign-up", error, { email: params.email });
|
|
90
90
|
throw error;
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
async signIn(params: SignInParams): Promise<AuthUser> {
|
|
95
|
-
|
|
96
|
-
email: params.email,
|
|
97
|
-
});
|
|
98
|
-
|
|
95
|
+
authTracker.logOperationStarted("Sign in", { email: params.email });
|
|
99
96
|
try {
|
|
100
|
-
const user = await this.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (this.guestModeService.getIsGuestMode() && this.storageProvider) {
|
|
104
|
-
await this.guestModeService.clear(this.storageProvider);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
addPackageBreadcrumb("auth", "Sign in successful", {
|
|
108
|
-
userId: user.uid,
|
|
109
|
-
});
|
|
110
|
-
|
|
97
|
+
const user = await this.repositoryInstance.signIn(params);
|
|
98
|
+
await this.clearGuestModeIfNeeded();
|
|
99
|
+
authTracker.logOperationSuccess("Sign in", { userId: user.uid });
|
|
111
100
|
authEventService.emitUserAuthenticated(user.uid);
|
|
112
101
|
return user;
|
|
113
102
|
} catch (error) {
|
|
114
|
-
|
|
115
|
-
error instanceof Error ? error : new Error("Sign in failed"),
|
|
116
|
-
{
|
|
117
|
-
packageName: "auth",
|
|
118
|
-
operation: "sign-in",
|
|
119
|
-
email: params.email,
|
|
120
|
-
}
|
|
121
|
-
);
|
|
103
|
+
authTracker.logOperationError("sign-in", error, { email: params.email });
|
|
122
104
|
throw error;
|
|
123
105
|
}
|
|
124
106
|
}
|
|
125
107
|
|
|
126
108
|
async signOut(): Promise<void> {
|
|
127
|
-
|
|
128
|
-
|
|
109
|
+
authTracker.logOperationStarted("Sign out");
|
|
129
110
|
try {
|
|
130
|
-
await this.
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if (this.guestModeService.getIsGuestMode() && this.storageProvider) {
|
|
134
|
-
await this.guestModeService.clear(this.storageProvider);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
addPackageBreadcrumb("auth", "Sign out successful");
|
|
111
|
+
await this.repositoryInstance.signOut();
|
|
112
|
+
await this.clearGuestModeIfNeeded();
|
|
113
|
+
authTracker.logOperationSuccess("Sign out");
|
|
138
114
|
} catch (error) {
|
|
139
|
-
|
|
140
|
-
error instanceof Error ? error : new Error("Sign out failed"),
|
|
141
|
-
{
|
|
142
|
-
packageName: "auth",
|
|
143
|
-
operation: "sign-out",
|
|
144
|
-
}
|
|
145
|
-
);
|
|
115
|
+
authTracker.logOperationError("sign-out", error);
|
|
146
116
|
throw error;
|
|
147
117
|
}
|
|
148
118
|
}
|
|
149
119
|
|
|
120
|
+
private async clearGuestModeIfNeeded(): Promise<void> {
|
|
121
|
+
if (this.guestModeService.getIsGuestMode() && this.storageProvider) {
|
|
122
|
+
await this.guestModeService.clear(this.storageProvider);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
150
126
|
async setGuestMode(): Promise<void> {
|
|
151
127
|
if (!this.storageProvider) {
|
|
152
128
|
throw new Error("Storage provider is required for guest mode");
|
|
153
129
|
}
|
|
154
|
-
|
|
155
|
-
// No provider needed for guest mode enablement
|
|
156
|
-
|
|
157
130
|
await this.guestModeService.enable(this.storageProvider);
|
|
158
131
|
}
|
|
159
132
|
|
|
160
133
|
getCurrentUser(): AuthUser | null {
|
|
161
|
-
if (this.
|
|
162
|
-
|
|
163
|
-
}
|
|
164
|
-
return this.coreService.getCurrentUser();
|
|
134
|
+
if (!this.initialized) return null;
|
|
135
|
+
return this.guestModeService.getIsGuestMode() ? null : this.repositoryInstance.getCurrentUser();
|
|
165
136
|
}
|
|
166
137
|
|
|
167
138
|
getIsGuestMode(): boolean {
|
|
@@ -170,55 +141,32 @@ export class AuthService implements IAuthService {
|
|
|
170
141
|
|
|
171
142
|
onAuthStateChange(callback: (user: AuthUser | null) => void): () => void {
|
|
172
143
|
const wrappedCallback = this.guestModeService.wrapAuthStateCallback(callback);
|
|
173
|
-
return this.
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
getConfig(): AuthConfig {
|
|
177
|
-
return this.coreService.getConfig();
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
getCoreService(): AuthCoreService {
|
|
181
|
-
return this.coreService;
|
|
144
|
+
return this.repositoryInstance.onAuthStateChange(wrappedCallback);
|
|
182
145
|
}
|
|
183
146
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}
|
|
147
|
+
getConfig(): AuthConfig { return this.config; }
|
|
148
|
+
getGuestModeService(): GuestModeService { return this.guestModeService; }
|
|
149
|
+
getRepository(): AuthRepository { return this.repositoryInstance; }
|
|
187
150
|
}
|
|
188
151
|
|
|
189
|
-
// Singleton instance
|
|
190
152
|
let authServiceInstance: AuthService | null = null;
|
|
191
153
|
|
|
192
|
-
/**
|
|
193
|
-
* Initialize auth service with provider or Firebase Auth instance
|
|
194
|
-
*/
|
|
195
154
|
export async function initializeAuthService(
|
|
196
155
|
providerOrAuth: IAuthProvider | Auth,
|
|
197
156
|
config?: Partial<AuthConfig>,
|
|
198
157
|
storageProvider?: IStorageProvider
|
|
199
158
|
): Promise<AuthService> {
|
|
200
159
|
if (!authServiceInstance) {
|
|
201
|
-
// Initialize package if not already done
|
|
202
|
-
const packageConfig = getAuthPackage()?.getConfig();
|
|
203
160
|
authServiceInstance = new AuthService(config, storageProvider);
|
|
204
161
|
}
|
|
205
162
|
await authServiceInstance.initialize(providerOrAuth);
|
|
206
163
|
return authServiceInstance;
|
|
207
164
|
}
|
|
208
165
|
|
|
209
|
-
/**
|
|
210
|
-
* Get auth service instance
|
|
211
|
-
*/
|
|
212
166
|
export function getAuthService(): AuthService | null {
|
|
213
|
-
|
|
214
|
-
return null;
|
|
215
|
-
}
|
|
216
|
-
return authServiceInstance;
|
|
167
|
+
return (authServiceInstance && authServiceInstance.isInitialized()) ? authServiceInstance : null;
|
|
217
168
|
}
|
|
218
169
|
|
|
219
|
-
/**
|
|
220
|
-
* Reset auth service (useful for testing)
|
|
221
|
-
*/
|
|
222
170
|
export function resetAuthService(): void {
|
|
223
171
|
authServiceInstance = null;
|
|
224
172
|
}
|
|
@@ -6,12 +6,7 @@
|
|
|
6
6
|
import type { IAuthProvider } from "../../application/ports/IAuthProvider";
|
|
7
7
|
import type { AuthUser } from "../../domain/entities/AuthUser";
|
|
8
8
|
import { emitGuestModeEnabled } from "../utils/AuthEventEmitter";
|
|
9
|
-
|
|
10
|
-
export interface IStorageProvider {
|
|
11
|
-
get(key: string): Promise<string | null>;
|
|
12
|
-
set(key: string, value: string): Promise<void>;
|
|
13
|
-
remove(key: string): Promise<void>;
|
|
14
|
-
}
|
|
9
|
+
import type { IStorageProvider } from "./AuthPackage";
|
|
15
10
|
|
|
16
11
|
export class GuestModeService {
|
|
17
12
|
private isGuestMode: boolean = false;
|
|
@@ -8,7 +8,7 @@ import { getAuthPackage } from "../services/AuthPackage";
|
|
|
8
8
|
|
|
9
9
|
export interface ValidationResult {
|
|
10
10
|
isValid: boolean;
|
|
11
|
-
error?: string;
|
|
11
|
+
error?: string; // This should be a localization key
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export interface PasswordStrengthResult extends ValidationResult {
|
|
@@ -58,12 +58,12 @@ function getValidationConfig(): ValidationConfig {
|
|
|
58
58
|
*/
|
|
59
59
|
export function validateEmail(email: string): ValidationResult {
|
|
60
60
|
if (!email || email.trim() === "") {
|
|
61
|
-
return { isValid: false, error: "
|
|
61
|
+
return { isValid: false, error: "auth.validation.emailRequired" };
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
const config = getValidationConfig();
|
|
65
65
|
if (!config.emailRegex.test(email.trim())) {
|
|
66
|
-
return { isValid: false, error: "
|
|
66
|
+
return { isValid: false, error: "auth.validation.invalidEmail" };
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
return { isValid: true };
|
|
@@ -75,7 +75,7 @@ export function validateEmail(email: string): ValidationResult {
|
|
|
75
75
|
*/
|
|
76
76
|
export function validatePasswordForLogin(password: string): ValidationResult {
|
|
77
77
|
if (!password || password.length === 0) {
|
|
78
|
-
return { isValid: false, error: "
|
|
78
|
+
return { isValid: false, error: "auth.validation.passwordRequired" };
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
return { isValid: true };
|
|
@@ -102,7 +102,7 @@ export function validatePasswordForRegister(
|
|
|
102
102
|
if (!password || password.length === 0) {
|
|
103
103
|
return {
|
|
104
104
|
isValid: false,
|
|
105
|
-
error: "
|
|
105
|
+
error: "auth.validation.passwordRequired",
|
|
106
106
|
requirements,
|
|
107
107
|
};
|
|
108
108
|
}
|
|
@@ -110,7 +110,7 @@ export function validatePasswordForRegister(
|
|
|
110
110
|
if (!requirements.hasMinLength) {
|
|
111
111
|
return {
|
|
112
112
|
isValid: false,
|
|
113
|
-
error:
|
|
113
|
+
error: "auth.validation.passwordTooShort",
|
|
114
114
|
requirements,
|
|
115
115
|
};
|
|
116
116
|
}
|
|
@@ -118,7 +118,7 @@ export function validatePasswordForRegister(
|
|
|
118
118
|
if (config.requireUppercase && !validationConfig.uppercaseRegex.test(password)) {
|
|
119
119
|
return {
|
|
120
120
|
isValid: false,
|
|
121
|
-
error: "
|
|
121
|
+
error: "auth.validation.passwordRequireUppercase",
|
|
122
122
|
requirements,
|
|
123
123
|
};
|
|
124
124
|
}
|
|
@@ -126,7 +126,7 @@ export function validatePasswordForRegister(
|
|
|
126
126
|
if (config.requireLowercase && !validationConfig.lowercaseRegex.test(password)) {
|
|
127
127
|
return {
|
|
128
128
|
isValid: false,
|
|
129
|
-
error: "
|
|
129
|
+
error: "auth.validation.passwordRequireLowercase",
|
|
130
130
|
requirements,
|
|
131
131
|
};
|
|
132
132
|
}
|
|
@@ -134,7 +134,7 @@ export function validatePasswordForRegister(
|
|
|
134
134
|
if (config.requireNumber && !validationConfig.numberRegex.test(password)) {
|
|
135
135
|
return {
|
|
136
136
|
isValid: false,
|
|
137
|
-
error: "
|
|
137
|
+
error: "auth.validation.passwordRequireNumber",
|
|
138
138
|
requirements,
|
|
139
139
|
};
|
|
140
140
|
}
|
|
@@ -142,7 +142,7 @@ export function validatePasswordForRegister(
|
|
|
142
142
|
if (config.requireSpecialChar && !validationConfig.specialCharRegex.test(password)) {
|
|
143
143
|
return {
|
|
144
144
|
isValid: false,
|
|
145
|
-
error: "
|
|
145
|
+
error: "auth.validation.passwordRequireSpecialChar",
|
|
146
146
|
requirements,
|
|
147
147
|
};
|
|
148
148
|
}
|
|
@@ -158,11 +158,11 @@ export function validatePasswordConfirmation(
|
|
|
158
158
|
confirmPassword: string
|
|
159
159
|
): ValidationResult {
|
|
160
160
|
if (!confirmPassword) {
|
|
161
|
-
return { isValid: false, error: "
|
|
161
|
+
return { isValid: false, error: "auth.validation.confirmPasswordRequired" };
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
if (password !== confirmPassword) {
|
|
165
|
-
return { isValid: false, error: "
|
|
165
|
+
return { isValid: false, error: "auth.validation.passwordsDoNotMatch" };
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
return { isValid: true };
|
|
@@ -176,7 +176,7 @@ export function validateDisplayName(
|
|
|
176
176
|
minLength?: number
|
|
177
177
|
): ValidationResult {
|
|
178
178
|
if (!displayName || displayName.trim() === "") {
|
|
179
|
-
return { isValid: false, error: "
|
|
179
|
+
return { isValid: false, error: "auth.validation.nameRequired" };
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
const config = getValidationConfig();
|
|
@@ -185,10 +185,11 @@ export function validateDisplayName(
|
|
|
185
185
|
if (displayName.trim().length < actualMinLength) {
|
|
186
186
|
return {
|
|
187
187
|
isValid: false,
|
|
188
|
-
error:
|
|
188
|
+
error: "auth.validation.nameTooShort",
|
|
189
189
|
};
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
return { isValid: true };
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
+
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {
|
|
2
|
+
trackPackageError as sentryTrack,
|
|
3
|
+
addPackageBreadcrumb as sentryBreadcrumb
|
|
4
|
+
} from "@umituz/react-native-sentry";
|
|
5
|
+
|
|
6
|
+
export const authTracker = {
|
|
7
|
+
logOperationStarted: (operation: string, data?: Record<string, unknown>) => {
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-call
|
|
9
|
+
(sentryBreadcrumb as any)("auth", `${operation} started`, data);
|
|
10
|
+
},
|
|
11
|
+
|
|
12
|
+
logOperationSuccess: (operation: string, data?: Record<string, unknown>) => {
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-call
|
|
14
|
+
(sentryBreadcrumb as any)("auth", `${operation} successful`, data);
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
logOperationError: (operation: string, error: unknown, metadata?: Record<string, unknown>) => {
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-call
|
|
19
|
+
(sentryTrack as any)(
|
|
20
|
+
error instanceof Error ? error : new Error(`${operation} failed`),
|
|
21
|
+
{
|
|
22
|
+
packageName: "auth",
|
|
23
|
+
operation,
|
|
24
|
+
...metadata,
|
|
25
|
+
}
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
@@ -5,16 +5,16 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import React from "react";
|
|
8
|
-
import { View,
|
|
9
|
-
import { useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
8
|
+
import { View, TouchableOpacity, StyleSheet, Alert } from "react-native";
|
|
9
|
+
import { useAppDesignTokens, AtomicIcon, AtomicText } from "@umituz/react-native-design-system";
|
|
10
10
|
|
|
11
11
|
export interface AccountActionsConfig {
|
|
12
|
-
logoutText
|
|
13
|
-
deleteAccountText
|
|
14
|
-
logoutConfirmTitle
|
|
15
|
-
logoutConfirmMessage
|
|
16
|
-
deleteConfirmTitle
|
|
17
|
-
deleteConfirmMessage
|
|
12
|
+
logoutText: string;
|
|
13
|
+
deleteAccountText: string;
|
|
14
|
+
logoutConfirmTitle: string;
|
|
15
|
+
logoutConfirmMessage: string;
|
|
16
|
+
deleteConfirmTitle: string;
|
|
17
|
+
deleteConfirmMessage: string;
|
|
18
18
|
onLogout: () => Promise<void>;
|
|
19
19
|
onDeleteAccount: () => Promise<void>;
|
|
20
20
|
}
|
|
@@ -26,13 +26,12 @@ export interface AccountActionsProps {
|
|
|
26
26
|
export const AccountActions: React.FC<AccountActionsProps> = ({ config }) => {
|
|
27
27
|
const tokens = useAppDesignTokens();
|
|
28
28
|
const {
|
|
29
|
-
logoutText
|
|
30
|
-
deleteAccountText
|
|
31
|
-
logoutConfirmTitle
|
|
32
|
-
logoutConfirmMessage
|
|
33
|
-
deleteConfirmTitle
|
|
34
|
-
deleteConfirmMessage
|
|
35
|
-
"This will permanently delete your account and all data. This action cannot be undone.",
|
|
29
|
+
logoutText,
|
|
30
|
+
deleteAccountText,
|
|
31
|
+
logoutConfirmTitle,
|
|
32
|
+
logoutConfirmMessage,
|
|
33
|
+
deleteConfirmTitle,
|
|
34
|
+
deleteConfirmMessage,
|
|
36
35
|
onLogout,
|
|
37
36
|
onDeleteAccount,
|
|
38
37
|
} = config;
|
|
@@ -43,12 +42,14 @@ export const AccountActions: React.FC<AccountActionsProps> = ({ config }) => {
|
|
|
43
42
|
{
|
|
44
43
|
text: logoutText,
|
|
45
44
|
style: "destructive",
|
|
46
|
-
onPress:
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
45
|
+
onPress: () => {
|
|
46
|
+
void (async () => {
|
|
47
|
+
try {
|
|
48
|
+
await onLogout();
|
|
49
|
+
} catch (error) {
|
|
50
|
+
// Silent error handling
|
|
51
|
+
}
|
|
52
|
+
})();
|
|
52
53
|
},
|
|
53
54
|
},
|
|
54
55
|
]);
|
|
@@ -60,12 +61,14 @@ export const AccountActions: React.FC<AccountActionsProps> = ({ config }) => {
|
|
|
60
61
|
{
|
|
61
62
|
text: deleteAccountText,
|
|
62
63
|
style: "destructive",
|
|
63
|
-
onPress:
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
64
|
+
onPress: () => {
|
|
65
|
+
void (async () => {
|
|
66
|
+
try {
|
|
67
|
+
await onDeleteAccount();
|
|
68
|
+
} catch (error) {
|
|
69
|
+
// Silent error handling
|
|
70
|
+
}
|
|
71
|
+
})();
|
|
69
72
|
},
|
|
70
73
|
},
|
|
71
74
|
]);
|
|
@@ -79,15 +82,11 @@ export const AccountActions: React.FC<AccountActionsProps> = ({ config }) => {
|
|
|
79
82
|
onPress={handleLogout}
|
|
80
83
|
activeOpacity={0.7}
|
|
81
84
|
>
|
|
82
|
-
<
|
|
83
|
-
|
|
84
|
-
</Text>
|
|
85
|
-
<Text style={[styles.actionText, { color: tokens.colors.error }]}>
|
|
85
|
+
<AtomicIcon name="logout" size="md" color="error" />
|
|
86
|
+
<AtomicText style={[styles.actionText, { color: tokens.colors.error }]}>
|
|
86
87
|
{logoutText}
|
|
87
|
-
</
|
|
88
|
-
<
|
|
89
|
-
›
|
|
90
|
-
</Text>
|
|
88
|
+
</AtomicText>
|
|
89
|
+
<AtomicIcon name="chevron-right" size="sm" color="secondary" />
|
|
91
90
|
</TouchableOpacity>
|
|
92
91
|
|
|
93
92
|
{/* Delete Account */}
|
|
@@ -96,15 +95,11 @@ export const AccountActions: React.FC<AccountActionsProps> = ({ config }) => {
|
|
|
96
95
|
onPress={handleDeleteAccount}
|
|
97
96
|
activeOpacity={0.7}
|
|
98
97
|
>
|
|
99
|
-
<
|
|
100
|
-
|
|
101
|
-
</Text>
|
|
102
|
-
<Text style={[styles.actionText, { color: tokens.colors.error }]}>
|
|
98
|
+
<AtomicIcon name="trash-2" size="md" color="error" />
|
|
99
|
+
<AtomicText style={[styles.actionText, { color: tokens.colors.error }]}>
|
|
103
100
|
{deleteAccountText}
|
|
104
|
-
</
|
|
105
|
-
<
|
|
106
|
-
›
|
|
107
|
-
</Text>
|
|
101
|
+
</AtomicText>
|
|
102
|
+
<AtomicIcon name="chevron-right" size="sm" color="secondary" />
|
|
108
103
|
</TouchableOpacity>
|
|
109
104
|
</View>
|
|
110
105
|
);
|
|
@@ -123,16 +118,9 @@ const styles = StyleSheet.create({
|
|
|
123
118
|
borderWidth: 1,
|
|
124
119
|
gap: 12,
|
|
125
120
|
},
|
|
126
|
-
actionIcon: {
|
|
127
|
-
fontSize: 20,
|
|
128
|
-
},
|
|
129
121
|
actionText: {
|
|
130
122
|
flex: 1,
|
|
131
123
|
fontSize: 16,
|
|
132
124
|
fontWeight: "500",
|
|
133
125
|
},
|
|
134
|
-
chevron: {
|
|
135
|
-
fontSize: 24,
|
|
136
|
-
fontWeight: "400",
|
|
137
|
-
},
|
|
138
126
|
});
|
|
@@ -45,7 +45,7 @@ export const AuthBottomSheet: React.FC<AuthBottomSheetProps> = ({
|
|
|
45
45
|
}) => {
|
|
46
46
|
const tokens = useAppDesignTokens();
|
|
47
47
|
const { t } = useLocalization();
|
|
48
|
-
const modalRef = useRef<
|
|
48
|
+
const modalRef = useRef<BottomSheetModal>(null);
|
|
49
49
|
|
|
50
50
|
const [googleLoading, setGoogleLoading] = useState(false);
|
|
51
51
|
const [appleLoading, setAppleLoading] = useState(false);
|
|
@@ -138,7 +138,7 @@ export const AuthBottomSheet: React.FC<AuthBottomSheetProps> = ({
|
|
|
138
138
|
style={styles.closeButton}
|
|
139
139
|
onPress={handleClose}
|
|
140
140
|
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
|
|
141
|
-
accessibilityLabel={t("common.close")
|
|
141
|
+
accessibilityLabel={t("common.close")}
|
|
142
142
|
accessibilityRole="button"
|
|
143
143
|
>
|
|
144
144
|
<AtomicIcon name="close" size="md" color="secondary" />
|
|
@@ -169,8 +169,8 @@ export const AuthBottomSheet: React.FC<AuthBottomSheetProps> = ({
|
|
|
169
169
|
{socialProviders.length > 0 && (
|
|
170
170
|
<SocialLoginButtons
|
|
171
171
|
enabledProviders={socialProviders}
|
|
172
|
-
onGooglePress={handleGoogleSignIn}
|
|
173
|
-
onApplePress={handleAppleSignIn}
|
|
172
|
+
onGooglePress={() => { void handleGoogleSignIn(); }}
|
|
173
|
+
onApplePress={() => { void handleAppleSignIn(); }}
|
|
174
174
|
googleLoading={googleLoading}
|
|
175
175
|
appleLoading={appleLoading}
|
|
176
176
|
/>
|
|
@@ -13,7 +13,7 @@ export const AuthGradientBackground: React.FC = () => {
|
|
|
13
13
|
|
|
14
14
|
return (
|
|
15
15
|
<LinearGradient
|
|
16
|
-
colors={[tokens.colors.primary, tokens.colors.
|
|
16
|
+
colors={[tokens.colors.primary, tokens.colors.surfaceSecondary]}
|
|
17
17
|
start={{ x: 0, y: 0 }}
|
|
18
18
|
end={{ x: 1, y: 1 }}
|
|
19
19
|
style={StyleSheet.absoluteFill}
|
|
@@ -20,7 +20,7 @@ export const AuthHeader: React.FC<AuthHeaderProps> = ({ title, subtitle }) => {
|
|
|
20
20
|
return (
|
|
21
21
|
<View style={[styles.header, { marginBottom: tokens.spacing.xl, paddingHorizontal: tokens.spacing.md }]}>
|
|
22
22
|
<AtomicText
|
|
23
|
-
type="
|
|
23
|
+
type="headlineLarge"
|
|
24
24
|
style={{ color: tokens.colors.onPrimary, textAlign: "center" }}
|
|
25
25
|
responsive
|
|
26
26
|
>
|