@umituz/react-native-auth 4.2.0 → 4.2.3

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": "4.2.0",
3
+ "version": "4.2.3",
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",
package/src/index.ts CHANGED
@@ -61,6 +61,10 @@ export { useAppleAuth } from './presentation/hooks/useAppleAuth';
61
61
  export type { UseAppleAuthResult } from './presentation/hooks/useAppleAuth';
62
62
  export { useAuthBottomSheet } from './presentation/hooks/useAuthBottomSheet';
63
63
  export type { SocialAuthConfiguration } from './presentation/hooks/useAuthBottomSheet';
64
+ export { useAuthHandlers } from './presentation/hooks/useAuthHandlers';
65
+ export type { AuthHandlersAppInfo, AuthHandlersTranslations } from './presentation/hooks/useAuthHandlers';
66
+ export { usePasswordPromptNavigation } from './presentation/hooks/usePasswordPromptNavigation';
67
+ export type { UsePasswordPromptNavigationOptions, UsePasswordPromptNavigationReturn } from './presentation/hooks/usePasswordPromptNavigation';
64
68
 
65
69
  // =============================================================================
66
70
  // PRESENTATION LAYER - Components
@@ -76,6 +80,8 @@ export { AccountScreen } from './presentation/screens/AccountScreen';
76
80
  export type { AccountScreenProps, AccountScreenConfig } from './presentation/screens/AccountScreen';
77
81
  export { EditProfileScreen } from './presentation/screens/EditProfileScreen';
78
82
  export type { EditProfileScreenProps } from './presentation/screens/EditProfileScreen';
83
+ export { PasswordPromptScreen } from './presentation/screens/PasswordPromptScreen';
84
+ export type { PasswordPromptScreenProps } from './presentation/screens/PasswordPromptScreen';
79
85
  export { AuthNavigator } from './presentation/navigation/AuthNavigator';
80
86
  export type { AuthStackParamList } from './presentation/navigation/AuthNavigator';
81
87
  export { AuthBottomSheet } from './presentation/components/AuthBottomSheet';
@@ -7,6 +7,8 @@ import { useCallback, useState } from "react";
7
7
  import { useAuth } from "./useAuth";
8
8
  import { deleteCurrentUser } from "@umituz/react-native-firebase";
9
9
 
10
+ declare const __DEV__: boolean;
11
+
10
12
  export interface UseAccountManagementOptions {
11
13
  /**
12
14
  * Callback invoked when reauthentication is required (for Google/Apple)
@@ -40,6 +42,10 @@ export const useAccountManagement = (
40
42
  }, [signOut]);
41
43
 
42
44
  const deleteAccount = useCallback(async () => {
45
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
46
+ console.log("[useAccountManagement] deleteAccount called, user:", user);
47
+ }
48
+
43
49
  if (!user) {
44
50
  throw new Error("No user logged in");
45
51
  }
@@ -51,12 +57,24 @@ export const useAccountManagement = (
51
57
  setIsDeletingAccount(true);
52
58
 
53
59
  try {
60
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
61
+ console.log("[useAccountManagement] Calling deleteCurrentUser with options:", {
62
+ autoReauthenticate: true,
63
+ hasOnPasswordRequired: !!onPasswordRequired,
64
+ hasOnReauthRequired: !!onReauthRequired,
65
+ });
66
+ }
67
+
54
68
  const result = await deleteCurrentUser({
55
69
  autoReauthenticate: true,
56
70
  onPasswordRequired,
57
71
  onGoogleReauthRequired: onReauthRequired,
58
72
  });
59
73
 
74
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
75
+ console.log("[useAccountManagement] deleteCurrentUser result:", result);
76
+ }
77
+
60
78
  if (!result.success) {
61
79
  throw new Error(result.error?.message || "Failed to delete account");
62
80
  }
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Auth Handlers Hook
3
+ * Centralized authentication-related handlers
4
+ * Uses auth package for all auth operations - no duplication
5
+ */
6
+
7
+ import { useCallback } from "react";
8
+ import { Linking, Alert } from "react-native";
9
+ import { useAuth } from "./useAuth";
10
+ import { useAuthModalStore } from "../stores/authModalStore";
11
+ import { useAccountManagement } from "./useAccountManagement";
12
+ import { AlertService } from "@umituz/react-native-design-system";
13
+ import { usePasswordPromptNavigation } from "./usePasswordPromptNavigation";
14
+
15
+ declare const __DEV__: boolean;
16
+
17
+ export interface AuthHandlersAppInfo {
18
+ appStoreUrl?: string;
19
+ }
20
+
21
+ export interface AuthHandlersTranslations {
22
+ deleteAccountTitle?: string;
23
+ deleteAccountMessage?: string;
24
+ cancel?: string;
25
+ delete?: string;
26
+ common?: string;
27
+ appStoreUrlNotConfigured?: string;
28
+ unableToOpenAppStore?: string;
29
+ failedToOpenAppStore?: string;
30
+ unknown?: string;
31
+ deleteAccountError?: string;
32
+ }
33
+
34
+ /**
35
+ * Hook that provides authentication-related handlers
36
+ */
37
+ export const useAuthHandlers = (appInfo: AuthHandlersAppInfo, translations?: AuthHandlersTranslations) => {
38
+ const { signOut } = useAuth();
39
+ const { showAuthModal } = useAuthModalStore();
40
+
41
+ const { showPasswordPrompt } = usePasswordPromptNavigation({
42
+ title: translations?.deleteAccountTitle || "Confirm Account Deletion",
43
+ message: translations?.deleteAccountMessage || "Please enter your password to permanently delete your account. This action cannot be undone.",
44
+ cancelText: translations?.cancel || "Cancel",
45
+ confirmText: translations?.delete || "Delete",
46
+ });
47
+
48
+ const { deleteAccount: deleteAccountFromAuth } = useAccountManagement({
49
+ onPasswordRequired: showPasswordPrompt,
50
+ });
51
+
52
+ const handleRatePress = useCallback(async () => {
53
+ const url = appInfo.appStoreUrl;
54
+ if (!url) {
55
+ Alert.alert(translations?.common || "", translations?.appStoreUrlNotConfigured || "");
56
+ return;
57
+ }
58
+
59
+ try {
60
+ const canOpen = await Linking.canOpenURL(url);
61
+ if (!canOpen) {
62
+ Alert.alert(
63
+ translations?.common || "",
64
+ translations?.unableToOpenAppStore || ""
65
+ );
66
+ return;
67
+ }
68
+ await Linking.openURL(url);
69
+ } catch (error) {
70
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
71
+ console.error("[useAuthHandlers] Failed to open app store:", error);
72
+ }
73
+ Alert.alert(translations?.common || "", translations?.failedToOpenAppStore || "");
74
+ }
75
+ }, [appInfo.appStoreUrl, translations]);
76
+
77
+ const handleSignOut = useCallback(async () => {
78
+ try {
79
+ await signOut();
80
+ } catch (error) {
81
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
82
+ console.error("[useAuthHandlers] Sign out failed:", error);
83
+ }
84
+ AlertService.createErrorAlert(
85
+ translations?.common || "",
86
+ translations?.unknown || ""
87
+ );
88
+ }
89
+ }, [signOut, translations]);
90
+
91
+ const handleDeleteAccount = useCallback(async () => {
92
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
93
+ console.log("[useAuthHandlers] handleDeleteAccount called");
94
+ }
95
+ try {
96
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
97
+ console.log("[useAuthHandlers] Calling deleteAccountFromAuth...");
98
+ }
99
+ await deleteAccountFromAuth();
100
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
101
+ console.log("[useAuthHandlers] deleteAccountFromAuth completed successfully");
102
+ }
103
+ } catch (error) {
104
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
105
+ console.error("[useAuthHandlers] deleteAccountFromAuth failed:", error);
106
+ }
107
+ const errorMessage = error instanceof Error ? error.message : String(error);
108
+ AlertService.createErrorAlert(
109
+ translations?.common || "Error",
110
+ errorMessage || translations?.deleteAccountError || "Failed to delete account"
111
+ );
112
+ }
113
+ }, [deleteAccountFromAuth, translations]);
114
+
115
+ const handleSignIn = useCallback(() => {
116
+ showAuthModal(undefined, "login");
117
+ }, [showAuthModal]);
118
+
119
+ return {
120
+ handleRatePress,
121
+ handleSignOut,
122
+ handleDeleteAccount,
123
+ handleSignIn,
124
+ };
125
+ };
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Password Prompt Navigation Hook
3
+ * Navigation-based password prompt that maintains Promise interface
4
+ * Uses AppNavigation for global navigation access
5
+ */
6
+
7
+ import { useCallback, useRef, useEffect } from 'react';
8
+ import { AppNavigation } from '@umituz/react-native-design-system';
9
+
10
+ export interface UsePasswordPromptNavigationOptions {
11
+ title?: string;
12
+ message?: string;
13
+ confirmText?: string;
14
+ cancelText?: string;
15
+ }
16
+
17
+ export interface UsePasswordPromptNavigationReturn {
18
+ showPasswordPrompt: () => Promise<string | null>;
19
+ }
20
+
21
+ export const usePasswordPromptNavigation = (
22
+ options: UsePasswordPromptNavigationOptions
23
+ ): UsePasswordPromptNavigationReturn => {
24
+ const { title, message, confirmText, cancelText } = options;
25
+ const resolveRef = useRef<((value: string | null) => void) | null>(null);
26
+
27
+ useEffect(() => {
28
+ return () => {
29
+ if (resolveRef.current) {
30
+ resolveRef.current(null);
31
+ resolveRef.current = null;
32
+ }
33
+ };
34
+ }, []);
35
+
36
+ const showPasswordPrompt = useCallback((): Promise<string | null> => {
37
+ return new Promise<string | null>((resolve, reject) => {
38
+ if (resolveRef.current) {
39
+ resolveRef.current(null);
40
+ }
41
+
42
+ resolveRef.current = resolve;
43
+
44
+ const params = {
45
+ onComplete: (password: string | null) => {
46
+ if (resolveRef.current) {
47
+ resolveRef.current(password);
48
+ resolveRef.current = null;
49
+ }
50
+ },
51
+ title,
52
+ message,
53
+ confirmText,
54
+ cancelText,
55
+ };
56
+
57
+ try {
58
+ AppNavigation.navigate('PasswordPrompt', params);
59
+ } catch (error) {
60
+ if (resolveRef.current) {
61
+ resolveRef.current(null);
62
+ resolveRef.current = null;
63
+ }
64
+ reject(error);
65
+ }
66
+ });
67
+ }, [title, message, confirmText, cancelText]);
68
+
69
+ return {
70
+ showPasswordPrompt,
71
+ };
72
+ };
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Password Prompt Screen
3
+ * Navigation screen for password input during account deletion reauthentication
4
+ */
5
+
6
+ import React, { useState, useEffect } from 'react';
7
+ import { View, StyleSheet, KeyboardAvoidingView, Platform, TouchableOpacity } from 'react-native';
8
+ import {
9
+ AtomicInput,
10
+ AtomicButton,
11
+ AtomicText,
12
+ AtomicIcon,
13
+ SafeAreaView,
14
+ useAppDesignTokens
15
+ } from '@umituz/react-native-design-system';
16
+
17
+ export interface PasswordPromptScreenProps {
18
+ route: {
19
+ params: {
20
+ onComplete: (password: string | null) => void;
21
+ title?: string;
22
+ message?: string;
23
+ confirmText?: string;
24
+ cancelText?: string;
25
+ };
26
+ };
27
+ navigation: {
28
+ goBack: () => void;
29
+ };
30
+ }
31
+
32
+ export const PasswordPromptScreen: React.FC<PasswordPromptScreenProps> = ({
33
+ route,
34
+ navigation,
35
+ }) => {
36
+ const tokens = useAppDesignTokens();
37
+ const [password, setPassword] = useState('');
38
+ const [error, setError] = useState('');
39
+
40
+ const {
41
+ onComplete,
42
+ title = 'Password Required',
43
+ message = 'Enter your password to continue',
44
+ confirmText = 'Confirm',
45
+ cancelText = 'Cancel',
46
+ } = route.params;
47
+
48
+ useEffect(() => {
49
+ return () => {
50
+ onComplete(null);
51
+ };
52
+ }, [onComplete]);
53
+
54
+ const handleConfirm = () => {
55
+ if (!password.trim()) {
56
+ setError('Password is required');
57
+ return;
58
+ }
59
+ onComplete(password);
60
+ navigation.goBack();
61
+ };
62
+
63
+ const handleCancel = () => {
64
+ onComplete(null);
65
+ navigation.goBack();
66
+ };
67
+
68
+ return (
69
+ <SafeAreaView style={[styles.safeArea, { backgroundColor: tokens.colors.background }]} edges={['top', 'bottom']}>
70
+ <View style={[styles.headerBar, { borderBottomColor: tokens.colors.border }]}>
71
+ <TouchableOpacity onPress={handleCancel} style={styles.closeButton}>
72
+ <AtomicIcon name="close" size="lg" color="textSecondary" />
73
+ </TouchableOpacity>
74
+ <AtomicText type="headlineLarge" fontWeight="600" color="textPrimary">
75
+ {title}
76
+ </AtomicText>
77
+ <View style={styles.placeholder} />
78
+ </View>
79
+
80
+ <KeyboardAvoidingView
81
+ behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
82
+ style={styles.keyboardView}
83
+ >
84
+ <View style={[styles.container, { padding: tokens.spacing.xl }]}>
85
+ <View style={styles.messageContainer}>
86
+ <AtomicText type="bodyMedium" color="textSecondary" style={styles.message}>
87
+ {message}
88
+ </AtomicText>
89
+ </View>
90
+
91
+ <View style={styles.content}>
92
+ <AtomicInput
93
+ value={password}
94
+ onChangeText={(text: string) => {
95
+ setPassword(text);
96
+ setError('');
97
+ }}
98
+ placeholder="Password"
99
+ secureTextEntry
100
+ autoFocus
101
+ state={error ? 'error' : 'default'}
102
+ helperText={error}
103
+ style={{ marginBottom: tokens.spacing.md }}
104
+ />
105
+ </View>
106
+
107
+ <View style={[styles.buttons, { gap: tokens.spacing.sm }]}>
108
+ <AtomicButton
109
+ title={cancelText}
110
+ onPress={handleCancel}
111
+ variant="secondary"
112
+ style={styles.button}
113
+ />
114
+ <AtomicButton
115
+ title={confirmText}
116
+ onPress={handleConfirm}
117
+ variant="primary"
118
+ style={styles.button}
119
+ />
120
+ </View>
121
+ </View>
122
+ </KeyboardAvoidingView>
123
+ </SafeAreaView>
124
+ );
125
+ };
126
+
127
+ const styles = StyleSheet.create({
128
+ safeArea: {
129
+ flex: 1,
130
+ },
131
+ headerBar: {
132
+ flexDirection: 'row',
133
+ alignItems: 'center',
134
+ justifyContent: 'space-between',
135
+ paddingHorizontal: 16,
136
+ paddingVertical: 12,
137
+ borderBottomWidth: 1,
138
+ },
139
+ closeButton: {
140
+ padding: 4,
141
+ width: 40,
142
+ },
143
+ placeholder: {
144
+ width: 40,
145
+ },
146
+ keyboardView: {
147
+ flex: 1,
148
+ },
149
+ container: {
150
+ flex: 1,
151
+ justifyContent: 'center',
152
+ },
153
+ messageContainer: {
154
+ marginBottom: 24,
155
+ },
156
+ message: {
157
+ textAlign: 'center',
158
+ lineHeight: 22,
159
+ },
160
+ content: {
161
+ flex: 1,
162
+ justifyContent: 'center',
163
+ },
164
+ buttons: {
165
+ flexDirection: 'row',
166
+ },
167
+ button: {
168
+ flex: 1,
169
+ },
170
+ });