@umituz/react-native-auth 3.4.19 → 3.4.21

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.19",
3
+ "version": "3.4.21",
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
@@ -172,6 +172,9 @@ export type { UseGoogleAuthResult, GoogleAuthConfig as GoogleAuthHookConfig } fr
172
172
  export { useAppleAuth } from './presentation/hooks/useAppleAuth';
173
173
  export type { UseAppleAuthResult } from './presentation/hooks/useAppleAuth';
174
174
 
175
+ export { useAuthBottomSheet } from './presentation/hooks/useAuthBottomSheet';
176
+ export { useAuthBottomSheetWrapper } from './presentation/hooks/useAuthBottomSheetWrapper';
177
+
175
178
  export type { UserProfile, UpdateProfileParams } from './domain/entities/UserProfile';
176
179
 
177
180
  // Domain Utils - Anonymous Names
@@ -217,10 +220,8 @@ export type { PasswordMatchIndicatorProps } from './presentation/components/Pass
217
220
  export { AuthBottomSheet } from './presentation/components/AuthBottomSheet';
218
221
  export type { AuthBottomSheetProps } from './presentation/components/AuthBottomSheet';
219
222
  export { AuthBottomSheetWrapper } from './presentation/components/AuthBottomSheetWrapper';
220
- export type {
221
- AuthBottomSheetWrapperProps,
222
- SocialAuthConfiguration,
223
- } from './presentation/components/AuthBottomSheetWrapper';
223
+ export type { AuthBottomSheetWrapperProps } from './presentation/components/AuthBottomSheetWrapper';
224
+ export type { SocialAuthConfiguration } from './presentation/hooks/useAuthBottomSheetWrapper';
224
225
  export { SocialLoginButtons } from './presentation/components/SocialLoginButtons';
225
226
  export type { SocialLoginButtonsProps } from './presentation/components/SocialLoginButtons';
226
227
  export { ProfileSection } from './presentation/components/ProfileSection';
@@ -3,7 +3,7 @@
3
3
  * Bottom sheet modal for authentication (Login/Register)
4
4
  */
5
5
 
6
- import React, { useEffect, useCallback, useRef, useState } from "react";
6
+ import React from "react";
7
7
  import { View, TouchableOpacity, ScrollView } from "react-native";
8
8
  import {
9
9
  useAppDesignTokens,
@@ -11,11 +11,9 @@ import {
11
11
  AtomicIcon,
12
12
  AtomicKeyboardAvoidingView,
13
13
  BottomSheetModal,
14
- type BottomSheetModalRef,
15
14
  } from "@umituz/react-native-design-system";
16
15
  import { useLocalization } from "@umituz/react-native-localization";
17
- import { useAuthModalStore } from "../stores/authModalStore";
18
- import { useAuth } from "../hooks/useAuth";
16
+ import { useAuthBottomSheet } from "../hooks/useAuthBottomSheet";
19
17
  import { LoginForm } from "./LoginForm";
20
18
  import { RegisterForm } from "./RegisterForm";
21
19
  import { SocialLoginButtons } from "./SocialLoginButtons";
@@ -46,67 +44,19 @@ export const AuthBottomSheet: React.FC<AuthBottomSheetProps> = ({
46
44
  }) => {
47
45
  const tokens = useAppDesignTokens();
48
46
  const { t } = useLocalization();
49
- const modalRef = useRef<BottomSheetModalRef>(null);
50
47
 
51
- const [googleLoading, setGoogleLoading] = useState(false);
52
- const [appleLoading, setAppleLoading] = useState(false);
53
-
54
- const { isVisible, mode, hideAuthModal, setMode, executePendingCallback, clearPendingCallback } =
55
- useAuthModalStore();
56
- const { isAuthenticated, isAnonymous } = useAuth();
57
-
58
- useEffect(() => {
59
- if (isVisible) {
60
- modalRef.current?.present();
61
- } else {
62
- modalRef.current?.dismiss();
63
- }
64
- }, [isVisible]);
65
-
66
- useEffect(() => {
67
- if (isAuthenticated && !isAnonymous && isVisible) {
68
- hideAuthModal();
69
- executePendingCallback();
70
- }
71
- }, [isAuthenticated, isAnonymous, isVisible, hideAuthModal, executePendingCallback]);
72
-
73
- const handleDismiss = useCallback(() => {
74
- hideAuthModal();
75
- clearPendingCallback();
76
- }, [hideAuthModal, clearPendingCallback]);
77
-
78
- const handleClose = useCallback(() => {
79
- modalRef.current?.dismiss();
80
- handleDismiss();
81
- }, [handleDismiss]);
82
-
83
- const handleNavigateToRegister = useCallback(() => {
84
- setMode("register");
85
- }, [setMode]);
86
-
87
- const handleNavigateToLogin = useCallback(() => {
88
- setMode("login");
89
- }, [setMode]);
90
-
91
- const handleGoogleSignIn = useCallback(async () => {
92
- if (!onGoogleSignIn) return;
93
- setGoogleLoading(true);
94
- try {
95
- await onGoogleSignIn();
96
- } finally {
97
- setGoogleLoading(false);
98
- }
99
- }, [onGoogleSignIn]);
100
-
101
- const handleAppleSignIn = useCallback(async () => {
102
- if (!onAppleSignIn) return;
103
- setAppleLoading(true);
104
- try {
105
- await onAppleSignIn();
106
- } finally {
107
- setAppleLoading(false);
108
- }
109
- }, [onAppleSignIn]);
48
+ const {
49
+ modalRef,
50
+ googleLoading,
51
+ appleLoading,
52
+ mode,
53
+ handleDismiss,
54
+ handleClose,
55
+ handleNavigateToRegister,
56
+ handleNavigateToLogin,
57
+ handleGoogleSignIn,
58
+ handleAppleSignIn,
59
+ } = useAuthBottomSheet({ onGoogleSignIn, onAppleSignIn });
110
60
 
111
61
  return (
112
62
  <BottomSheetModal
@@ -118,11 +68,6 @@ export const AuthBottomSheet: React.FC<AuthBottomSheetProps> = ({
118
68
  <AtomicKeyboardAvoidingView
119
69
  style={{ flex: 1 }}
120
70
  >
121
- <ScrollView
122
- contentContainerStyle={styles.scrollContent}
123
- showsVerticalScrollIndicator={false}
124
- keyboardShouldPersistTaps="handled"
125
- >
126
71
  <TouchableOpacity
127
72
  style={styles.closeButton}
128
73
  onPress={handleClose}
@@ -133,6 +78,12 @@ export const AuthBottomSheet: React.FC<AuthBottomSheetProps> = ({
133
78
  <AtomicIcon name="close" size="md" color="secondary" />
134
79
  </TouchableOpacity>
135
80
 
81
+ <ScrollView
82
+ contentContainerStyle={styles.scrollContent}
83
+ showsVerticalScrollIndicator={false}
84
+ keyboardShouldPersistTaps="handled"
85
+ >
86
+
136
87
  <View style={styles.header}>
137
88
  <AtomicText type="headlineLarge" color="primary" style={styles.title}>
138
89
  {mode === "login" ? t("auth.signIn") : t("auth.createAccount")}
@@ -18,19 +18,9 @@
18
18
  * ```
19
19
  */
20
20
 
21
- import React, { useCallback, useMemo } from "react";
22
- import { Platform } from "react-native";
21
+ import React from "react";
23
22
  import { AuthBottomSheet } from "./AuthBottomSheet";
24
- import { useGoogleAuth, type GoogleAuthConfig } from "../hooks/useGoogleAuth";
25
- import { useAppleAuth } from "../hooks/useAppleAuth";
26
- import type { SocialAuthProvider } from "../../domain/value-objects/AuthConfig";
27
-
28
- declare const __DEV__: boolean;
29
-
30
- export interface SocialAuthConfiguration {
31
- google?: GoogleAuthConfig;
32
- apple?: { enabled: boolean };
33
- }
23
+ import { useAuthBottomSheetWrapper, type SocialAuthConfiguration } from "../hooks/useAuthBottomSheetWrapper";
34
24
 
35
25
  export interface AuthBottomSheetWrapperProps {
36
26
  /** Terms of Service URL */
@@ -55,51 +45,9 @@ export const AuthBottomSheetWrapper: React.FC<AuthBottomSheetWrapperProps> = ({
55
45
  onPrivacyPress,
56
46
  socialConfig,
57
47
  }) => {
58
- const { signInWithGoogle, googleConfigured } = useGoogleAuth(socialConfig?.google);
59
- const { signInWithApple, appleAvailable } = useAppleAuth();
60
-
61
- const providers = useMemo<SocialAuthProvider[]>(() => {
62
- const result: SocialAuthProvider[] = [];
63
-
64
- if (Platform.OS === "ios" && socialConfig?.apple?.enabled && appleAvailable) {
65
- result.push("apple");
66
- }
67
-
68
- if (googleConfigured) {
69
- result.push("google");
70
- }
71
-
72
- if (__DEV__) {
73
- // eslint-disable-next-line no-console
74
- console.log("[AuthBottomSheetWrapper] Enabled providers:", result);
75
- }
76
-
77
- return result;
78
- }, [socialConfig?.apple?.enabled, appleAvailable, googleConfigured]);
79
-
80
- const handleGoogleSignIn = useCallback(async () => {
81
- if (__DEV__) {
82
- // eslint-disable-next-line no-console
83
- console.log("[AuthBottomSheetWrapper] Google sign-in requested");
84
- }
85
- const result = await signInWithGoogle();
86
- if (__DEV__) {
87
- // eslint-disable-next-line no-console
88
- console.log("[AuthBottomSheetWrapper] Google result:", result);
89
- }
90
- }, [signInWithGoogle]);
91
-
92
- const handleAppleSignIn = useCallback(async () => {
93
- if (__DEV__) {
94
- // eslint-disable-next-line no-console
95
- console.log("[AuthBottomSheetWrapper] Apple sign-in requested");
96
- }
97
- const result = await signInWithApple();
98
- if (__DEV__) {
99
- // eslint-disable-next-line no-console
100
- console.log("[AuthBottomSheetWrapper] Apple result:", result);
101
- }
102
- }, [signInWithApple]);
48
+ const { providers, handleGoogleSignIn, handleAppleSignIn } = useAuthBottomSheetWrapper({
49
+ socialConfig,
50
+ });
103
51
 
104
52
  return (
105
53
  <AuthBottomSheet
@@ -0,0 +1,106 @@
1
+ import { useCallback, useEffect, useRef, useState } from "react";
2
+ import type { BottomSheetModalRef } from "@umituz/react-native-design-system";
3
+ import { useAuthModalStore } from "../stores/authModalStore";
4
+ import { useAuth } from "../hooks/useAuth";
5
+
6
+ interface UseAuthBottomSheetProps {
7
+ onGoogleSignIn?: () => Promise<void>;
8
+ onAppleSignIn?: () => Promise<void>;
9
+ }
10
+
11
+ export function useAuthBottomSheet({ onGoogleSignIn, onAppleSignIn }: UseAuthBottomSheetProps) {
12
+ const modalRef = useRef<BottomSheetModalRef>(null);
13
+ const [googleLoading, setGoogleLoading] = useState(false);
14
+ const [appleLoading, setAppleLoading] = useState(false);
15
+
16
+ const { isVisible, mode, hideAuthModal, setMode, executePendingCallback, clearPendingCallback } =
17
+ useAuthModalStore();
18
+ const { isAuthenticated, isAnonymous } = useAuth();
19
+
20
+ // Handle visibility sync with modalRef
21
+ useEffect(() => {
22
+ if (isVisible) {
23
+ modalRef.current?.present();
24
+ } else {
25
+ modalRef.current?.dismiss();
26
+ }
27
+ }, [isVisible]);
28
+
29
+ const handleDismiss = useCallback(() => {
30
+ hideAuthModal();
31
+ clearPendingCallback();
32
+ }, [hideAuthModal, clearPendingCallback]);
33
+
34
+ const handleClose = useCallback(() => {
35
+ modalRef.current?.dismiss();
36
+ handleDismiss();
37
+ }, [handleDismiss]);
38
+
39
+ const prevIsAuthenticatedRef = useRef(isAuthenticated);
40
+ const prevIsVisibleRef = useRef(isVisible);
41
+ const prevIsAnonymousRef = useRef(isAnonymous);
42
+
43
+ useEffect(() => {
44
+ // Determine if user just successfully authenticated (either A: were not authed at all, or B: were anonymous and now aren't)
45
+ const justAuthenticated = !prevIsAuthenticatedRef.current && isAuthenticated;
46
+ const justConvertedFromAnonymous = prevIsAnonymousRef.current && !isAnonymous && isAuthenticated;
47
+
48
+ if ((justAuthenticated || justConvertedFromAnonymous) && isVisible && !isAnonymous) {
49
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
50
+ // eslint-disable-next-line no-console
51
+ console.log("[AuthBottomSheet] Auto-closing due to successful authentication transition", {
52
+ justAuthenticated,
53
+ justConvertedFromAnonymous,
54
+ });
55
+ }
56
+ handleClose();
57
+ executePendingCallback();
58
+ }
59
+
60
+ // Update refs for next render
61
+ prevIsAuthenticatedRef.current = isAuthenticated;
62
+ prevIsVisibleRef.current = isVisible;
63
+ prevIsAnonymousRef.current = isAnonymous;
64
+ }, [isAuthenticated, isVisible, isAnonymous, executePendingCallback, handleClose]);
65
+
66
+ const handleNavigateToRegister = useCallback(() => {
67
+ setMode("register");
68
+ }, [setMode]);
69
+
70
+ const handleNavigateToLogin = useCallback(() => {
71
+ setMode("login");
72
+ }, [setMode]);
73
+
74
+ const handleGoogleSignIn = useCallback(async () => {
75
+ if (!onGoogleSignIn) return;
76
+ setGoogleLoading(true);
77
+ try {
78
+ await onGoogleSignIn();
79
+ } finally {
80
+ setGoogleLoading(false);
81
+ }
82
+ }, [onGoogleSignIn]);
83
+
84
+ const handleAppleSignIn = useCallback(async () => {
85
+ if (!onAppleSignIn) return;
86
+ setAppleLoading(true);
87
+ try {
88
+ await onAppleSignIn();
89
+ } finally {
90
+ setAppleLoading(false);
91
+ }
92
+ }, [onAppleSignIn]);
93
+
94
+ return {
95
+ modalRef,
96
+ googleLoading,
97
+ appleLoading,
98
+ mode,
99
+ handleDismiss,
100
+ handleClose,
101
+ handleNavigateToRegister,
102
+ handleNavigateToLogin,
103
+ handleGoogleSignIn,
104
+ handleAppleSignIn,
105
+ };
106
+ }
@@ -0,0 +1,70 @@
1
+ import { useCallback, useMemo } from "react";
2
+ import { Platform } from "react-native";
3
+ import { useGoogleAuth, type GoogleAuthConfig } from "./useGoogleAuth";
4
+ import { useAppleAuth } from "./useAppleAuth";
5
+ import type { SocialAuthProvider } from "../../domain/value-objects/AuthConfig";
6
+
7
+ declare const __DEV__: boolean;
8
+
9
+ export interface SocialAuthConfiguration {
10
+ google?: GoogleAuthConfig;
11
+ apple?: { enabled: boolean };
12
+ }
13
+
14
+ interface UseAuthBottomSheetWrapperProps {
15
+ socialConfig?: SocialAuthConfiguration;
16
+ }
17
+
18
+ export function useAuthBottomSheetWrapper({ socialConfig }: UseAuthBottomSheetWrapperProps) {
19
+ const { signInWithGoogle, googleConfigured } = useGoogleAuth(socialConfig?.google);
20
+ const { signInWithApple, appleAvailable } = useAppleAuth();
21
+
22
+ const providers = useMemo<SocialAuthProvider[]>(() => {
23
+ const result: SocialAuthProvider[] = [];
24
+
25
+ if (Platform.OS === "ios" && socialConfig?.apple?.enabled && appleAvailable) {
26
+ result.push("apple");
27
+ }
28
+
29
+ if (googleConfigured) {
30
+ result.push("google");
31
+ }
32
+
33
+ if (__DEV__) {
34
+ // eslint-disable-next-line no-console
35
+ console.log("[useAuthBottomSheetWrapper] Enabled providers:", result);
36
+ }
37
+
38
+ return result;
39
+ }, [socialConfig?.apple?.enabled, appleAvailable, googleConfigured]);
40
+
41
+ const handleGoogleSignIn = useCallback(async () => {
42
+ if (__DEV__) {
43
+ // eslint-disable-next-line no-console
44
+ console.log("[useAuthBottomSheetWrapper] Google sign-in requested");
45
+ }
46
+ const result = await signInWithGoogle();
47
+ if (__DEV__) {
48
+ // eslint-disable-next-line no-console
49
+ console.log("[useAuthBottomSheetWrapper] Google result:", result);
50
+ }
51
+ }, [signInWithGoogle]);
52
+
53
+ const handleAppleSignIn = useCallback(async () => {
54
+ if (__DEV__) {
55
+ // eslint-disable-next-line no-console
56
+ console.log("[useAuthBottomSheetWrapper] Apple sign-in requested");
57
+ }
58
+ const result = await signInWithApple();
59
+ if (__DEV__) {
60
+ // eslint-disable-next-line no-console
61
+ console.log("[useAuthBottomSheetWrapper] Apple result:", result);
62
+ }
63
+ }, [signInWithApple]);
64
+
65
+ return {
66
+ providers,
67
+ handleGoogleSignIn,
68
+ handleAppleSignIn,
69
+ };
70
+ }
@@ -14,51 +14,10 @@ import {
14
14
  type SocialAuthResult,
15
15
  } from "@umituz/react-native-firebase";
16
16
 
17
- // Type declarations for expo-auth-session
18
- interface GoogleAuthRequestConfig {
19
- iosClientId: string;
20
- webClientId: string;
21
- androidClientId: string;
22
- }
23
-
24
- interface AuthSessionAuthentication {
25
- idToken?: string;
26
- accessToken?: string;
27
- }
28
-
29
- interface AuthSessionResult {
30
- type: "success" | "cancel" | "dismiss" | "error" | "locked";
31
- authentication?: AuthSessionAuthentication;
32
- }
33
-
34
- interface AuthRequest {
35
- promptAsync: () => Promise<AuthSessionResult>;
36
- }
17
+ import * as Google from "expo-auth-session/providers/google";
18
+ import * as WebBrowser from "expo-web-browser";
37
19
 
38
- type UseAuthRequestReturn = [
39
- AuthRequest | null,
40
- AuthSessionResult | null,
41
- () => Promise<AuthSessionResult>,
42
- ];
43
-
44
- // Dynamic imports to handle optional dependencies
45
- type GoogleModule = { useAuthRequest: (config: GoogleAuthRequestConfig) => UseAuthRequestReturn };
46
- type WebBrowserModule = { maybeCompleteAuthSession: () => void };
47
-
48
- let Google: GoogleModule | null = null;
49
- let WebBrowser: WebBrowserModule | null = null;
50
-
51
- try {
52
- // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
53
- Google = require("expo-auth-session/providers/google") as GoogleModule;
54
- // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
55
- WebBrowser = require("expo-web-browser") as WebBrowserModule;
56
- if (WebBrowser) {
57
- WebBrowser.maybeCompleteAuthSession();
58
- }
59
- } catch {
60
- // expo-auth-session not available - silent fallback
61
- }
20
+ WebBrowser.maybeCompleteAuthSession();
62
21
 
63
22
  export interface GoogleAuthConfig {
64
23
  iosClientId?: string;
@@ -123,7 +82,7 @@ export function useGoogleAuth(config?: GoogleAuthConfig): UseGoogleAuthResult {
123
82
  }, [googleResponse, signInWithGoogleToken]);
124
83
 
125
84
  const signInWithGoogle = useCallback(async (): Promise<SocialAuthResult> => {
126
- if (!Google || !promptGoogleAsync) {
85
+ if (!promptGoogleAsync) {
127
86
  return { success: false, error: "expo-auth-session is not available" };
128
87
  }
129
88