rn-swiftauth-sdk 1.0.1 → 1.0.2

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.
@@ -22,17 +22,19 @@ export const LoginForm = ({ styles: userStyles }: LoginFormProps) => {
22
22
  signInWithEmail,
23
23
  signInWithGoogle,
24
24
  signInWithApple,
25
- isLoading, // ✅ Using the simplified boolean we added to context
25
+ isLoading,
26
26
  error,
27
- config // ✅ We need this to check if social buttons are enabled
27
+ config
28
28
  } = useAuth();
29
29
 
30
30
  const [email, setEmail] = useState('');
31
31
  const [password, setPassword] = useState('');
32
32
 
33
- // ✅ Validation Error State
34
33
  const [validationErrors, setValidationErrors] = useState<{ email?: string; password?: string }>({});
35
34
 
35
+ //Check if form is filled to enable button
36
+ const isFormFilled = email.length > 0 && password.length > 0;
37
+
36
38
  const handleLogin = async () => {
37
39
  // 1. Reset previous errors
38
40
  setValidationErrors({});
@@ -46,14 +48,17 @@ export const LoginForm = ({ styles: userStyles }: LoginFormProps) => {
46
48
  email: emailErr || undefined,
47
49
  password: passErr || undefined
48
50
  });
49
- return; // 🛑 Stop if invalid
51
+ return; //Stop if invalid
50
52
  }
51
53
 
52
54
  // 3. Attempt Login
53
55
  try {
54
- await signInWithEmail(email, password);
56
+ //UPDATED: Clean Object Syntax
57
+ await signInWithEmail({ email, password });
55
58
  } catch (e) {
56
59
  // Auth errors handled by global state
60
+ // DX: Log it for the developer (Optional but helpful for debugging)
61
+ console.log('Login failed:', e);
57
62
  }
58
63
  };
59
64
 
@@ -87,7 +92,7 @@ export const LoginForm = ({ styles: userStyles }: LoginFormProps) => {
87
92
  style={[
88
93
  defaultStyles.input,
89
94
  userStyles?.input,
90
- validationErrors.email ? { borderColor: 'red' } : {} // Highlight on error
95
+ validationErrors.email ? { borderColor: 'red' } : {}
91
96
  ]}
92
97
  placeholder="Email"
93
98
  value={email}
@@ -100,7 +105,6 @@ export const LoginForm = ({ styles: userStyles }: LoginFormProps) => {
100
105
  placeholderTextColor="#999"
101
106
  editable={!isLoading}
102
107
  />
103
- {/* Validation Message */}
104
108
  {validationErrors.email && (
105
109
  <Text style={defaultStyles.validationText}>{validationErrors.email}</Text>
106
110
  )}
@@ -124,11 +128,13 @@ export const LoginForm = ({ styles: userStyles }: LoginFormProps) => {
124
128
  <TouchableOpacity
125
129
  style={[
126
130
  defaultStyles.button,
127
- isLoading && defaultStyles.buttonDisabled,
131
+ // Disable style if loading OR form is incomplete
132
+ (isLoading || !isFormFilled) && defaultStyles.buttonDisabled,
128
133
  userStyles?.button
129
134
  ]}
130
135
  onPress={handleLogin}
131
- disabled={isLoading}
136
+ // Disable interaction if loading OR form is incomplete
137
+ disabled={isLoading || !isFormFilled}
132
138
  >
133
139
  {isLoading ? (
134
140
  <ActivityIndicator color={userStyles?.loadingIndicatorColor || "#fff"} />
@@ -139,7 +145,7 @@ export const LoginForm = ({ styles: userStyles }: LoginFormProps) => {
139
145
  )}
140
146
  </TouchableOpacity>
141
147
 
142
- {/* OAuth Section - Conditionally Rendered based on Config */}
148
+ {/* OAuth Section */}
143
149
  {(config.enableGoogle || config.enableApple) && !isLoading && (
144
150
  <>
145
151
  <View style={defaultStyles.dividerContainer}>
@@ -190,7 +196,7 @@ const defaultStyles = StyleSheet.create({
190
196
  backgroundColor: '#f5f5f5',
191
197
  padding: 15,
192
198
  borderRadius: 8,
193
- marginBottom: 8, // Reduced slightly to make room for validation text
199
+ marginBottom: 8,
194
200
  borderWidth: 1,
195
201
  borderColor: '#e0e0e0',
196
202
  fontSize: 16,
@@ -203,7 +209,10 @@ const defaultStyles = StyleSheet.create({
203
209
  alignItems: 'center',
204
210
  marginTop: 8,
205
211
  },
206
- buttonDisabled: { backgroundColor: '#a0cfff' },
212
+ buttonDisabled: {
213
+ backgroundColor: '#a0cfff',
214
+ opacity: 0.7
215
+ },
207
216
  buttonText: { color: '#fff', fontWeight: '600', fontSize: 16 },
208
217
 
209
218
  errorText: { color: 'red', marginBottom: 12, fontSize: 14, textAlign: 'center' },
@@ -6,7 +6,7 @@ import {
6
6
  TouchableOpacity,
7
7
  StyleSheet,
8
8
  ActivityIndicator,
9
- Platform
9
+ Platform,
10
10
  } from 'react-native';
11
11
 
12
12
  import { useAuth } from '../hooks/useAuth';
@@ -24,19 +24,20 @@ export const SignUpForm = ({ styles: userStyles, showHints = true }: SignUpFormP
24
24
  signUpWithEmail,
25
25
  signInWithGoogle,
26
26
  signInWithApple,
27
- isLoading, // ✅ Use boolean loading state
27
+ isLoading,
28
28
  error,
29
- config // ✅ Use config for conditional rendering
29
+ config
30
30
  } = useAuth();
31
31
 
32
32
  const [email, setEmail] = useState('');
33
33
  const [password, setPassword] = useState('');
34
34
  const [confirmPassword, setConfirmPassword] = useState('');
35
-
36
- // ✅ Proper Validation State
37
35
  const [validationErrors, setValidationErrors] = useState<{ email?: string; password?: string; confirm?: string }>({});
38
36
 
39
- // Password Requirements Logic for Visual Hints
37
+ // 1. Smart Button Logic: Check if all fields have content
38
+ const isFormFilled = email.length > 0 && password.length > 0 && confirmPassword.length > 0;
39
+
40
+ // Password Requirements Logic
40
41
  const requirements = [
41
42
  { label: "At least 6 characters", met: password.length >= 6 },
42
43
  { label: "Contains a number", met: /\d/.test(password) },
@@ -44,19 +45,16 @@ export const SignUpForm = ({ styles: userStyles, showHints = true }: SignUpFormP
44
45
  ];
45
46
 
46
47
  const handleSignUp = async () => {
47
- // 1. Reset Errors
48
48
  setValidationErrors({});
49
49
 
50
50
  // 2. Validate Inputs
51
51
  const emailErr = validateEmail(email);
52
52
  const passErr = validatePasswordSignup(password);
53
-
54
53
  let confirmErr;
55
54
  if (password !== confirmPassword) {
56
55
  confirmErr = "Passwords do not match.";
57
56
  }
58
57
 
59
- // 3. Check if any errors exist
60
58
  if (emailErr || passErr || confirmErr) {
61
59
  setValidationErrors({
62
60
  email: emailErr || undefined,
@@ -66,11 +64,11 @@ export const SignUpForm = ({ styles: userStyles, showHints = true }: SignUpFormP
66
64
  return;
67
65
  }
68
66
 
69
- // 4. Attempt Sign Up
70
67
  try {
71
- await signUpWithEmail(email, password);
68
+ // UPDATED: New Object Syntax
69
+ await signUpWithEmail({ email, password });
72
70
  } catch (e) {
73
- // Global error handled by useAuth
71
+ console.error('Sign Up Failed:', e);
74
72
  }
75
73
  };
76
74
 
@@ -92,7 +90,6 @@ export const SignUpForm = ({ styles: userStyles, showHints = true }: SignUpFormP
92
90
 
93
91
  return (
94
92
  <View style={[defaultStyles.container, userStyles?.container]}>
95
- {/* Global API Error */}
96
93
  {error && (
97
94
  <Text style={[defaultStyles.globalError, userStyles?.errorText]}>
98
95
  {error.message}
@@ -177,11 +174,13 @@ export const SignUpForm = ({ styles: userStyles, showHints = true }: SignUpFormP
177
174
  <TouchableOpacity
178
175
  style={[
179
176
  defaultStyles.button,
180
- isLoading && defaultStyles.buttonDisabled,
177
+ // Disable style if loading OR form incomplete
178
+ (isLoading || !isFormFilled) && defaultStyles.buttonDisabled,
181
179
  userStyles?.button
182
180
  ]}
183
181
  onPress={handleSignUp}
184
- disabled={isLoading}
182
+ // Disable interaction if loading OR form incomplete
183
+ disabled={isLoading || !isFormFilled}
185
184
  >
186
185
  {isLoading ? (
187
186
  <ActivityIndicator color={userStyles?.loadingIndicatorColor || "#fff"} />
@@ -192,7 +191,7 @@ export const SignUpForm = ({ styles: userStyles, showHints = true }: SignUpFormP
192
191
  )}
193
192
  </TouchableOpacity>
194
193
 
195
- {/* OAuth Section - Conditional Rendering */}
194
+ {/* OAuth Section */}
196
195
  {(config.enableGoogle || config.enableApple) && !isLoading && (
197
196
  <>
198
197
  <View style={defaultStyles.dividerContainer}>
@@ -201,7 +200,6 @@ export const SignUpForm = ({ styles: userStyles, showHints = true }: SignUpFormP
201
200
  <View style={defaultStyles.divider} />
202
201
  </View>
203
202
 
204
- {/* Google */}
205
203
  {config.enableGoogle && (
206
204
  <TouchableOpacity
207
205
  style={[defaultStyles.oauthButton, defaultStyles.googleButton]}
@@ -213,7 +211,6 @@ export const SignUpForm = ({ styles: userStyles, showHints = true }: SignUpFormP
213
211
  </TouchableOpacity>
214
212
  )}
215
213
 
216
- {/* Apple */}
217
214
  {config.enableApple && Platform.OS === 'ios' && (
218
215
  <TouchableOpacity
219
216
  style={[defaultStyles.oauthButton, defaultStyles.appleButton]}
@@ -232,17 +229,15 @@ export const SignUpForm = ({ styles: userStyles, showHints = true }: SignUpFormP
232
229
 
233
230
  const defaultStyles = StyleSheet.create({
234
231
  container: { width: '100%', marginVertical: 10 },
235
-
236
232
  input: {
237
233
  backgroundColor: '#f5f5f5',
238
234
  padding: 15,
239
235
  borderRadius: 8,
240
- marginBottom: 8, // Reduced for validation text space
236
+ marginBottom: 8,
241
237
  borderWidth: 1,
242
238
  borderColor: '#e0e0e0',
243
239
  fontSize: 16,
244
240
  },
245
-
246
241
  button: {
247
242
  backgroundColor: '#34C759',
248
243
  padding: 15,
@@ -250,15 +245,13 @@ const defaultStyles = StyleSheet.create({
250
245
  alignItems: 'center',
251
246
  marginTop: 8,
252
247
  },
253
-
254
- buttonDisabled: { backgroundColor: '#9ce4ae' },
255
-
248
+ buttonDisabled: {
249
+ backgroundColor: '#9ce4ae',
250
+ opacity: 0.7
251
+ },
256
252
  buttonText: { color: '#fff', fontWeight: '600', fontSize: 16 },
257
-
258
253
  globalError: { color: 'red', marginBottom: 12, fontSize: 14, textAlign: 'center' },
259
254
  validationText: { color: 'red', fontSize: 12, marginBottom: 10, marginLeft: 4, marginTop: -4 },
260
-
261
- // OAuth Styles
262
255
  dividerContainer: {
263
256
  flexDirection: 'row',
264
257
  alignItems: 'center',
@@ -266,7 +259,6 @@ const defaultStyles = StyleSheet.create({
266
259
  },
267
260
  divider: { flex: 1, height: 1, backgroundColor: '#e0e0e0' },
268
261
  dividerText: { marginHorizontal: 16, color: '#666', fontSize: 14 },
269
-
270
262
  oauthButton: {
271
263
  padding: 15,
272
264
  borderRadius: 8,
@@ -281,11 +273,8 @@ const defaultStyles = StyleSheet.create({
281
273
  borderColor: '#e0e0e0',
282
274
  },
283
275
  googleButtonText: { color: '#000', fontSize: 16, fontWeight: '500' },
284
-
285
276
  appleButton: { backgroundColor: '#000' },
286
277
  appleButtonText: { color: '#fff', fontSize: 16, fontWeight: '600' },
287
-
288
- // Password Hint Styles
289
278
  hintContainer: { marginBottom: 15, paddingLeft: 5 },
290
279
  hintRow: { flexDirection: 'row', alignItems: 'center', marginBottom: 4 },
291
280
  hintText: { color: '#666', fontSize: 12 },
@@ -1,39 +1,24 @@
1
1
  import React, { useEffect, useState, ReactNode, useMemo } from 'react';
2
2
  import { Platform } from 'react-native';
3
3
  import { initializeApp, getApps, getApp, FirebaseApp } from 'firebase/app';
4
-
5
- // Firebase Auth
6
- import {
7
- getAuth,
8
- initializeAuth,
9
- onAuthStateChanged,
10
- inMemoryPersistence,
11
- User as FirebaseUser,
12
- Auth as FirebaseAuth,
13
- createUserWithEmailAndPassword,
14
- signInWithEmailAndPassword,
15
- GoogleAuthProvider,
16
- OAuthProvider,
17
- signInWithCredential,
18
- } from 'firebase/auth';
19
-
20
- // The Hack: Import for getReactNativePersistence
21
- import * as firebaseAuth from 'firebase/auth';
22
- // @ts-ignore
23
- const getReactNativePersistence = (firebaseAuth as any).getReactNativePersistence;
24
-
4
+ import * as FirebaseAuth from 'firebase/auth';
25
5
  import ReactNativeAsyncStorage from '@react-native-async-storage/async-storage';
26
-
27
- // PROPER GOOGLE SIGN-IN
28
6
  import { GoogleSignin } from '@react-native-google-signin/google-signin';
29
-
30
- // Apple Sign-In (Expo)
31
7
  import * as AppleAuthentication from 'expo-apple-authentication';
32
8
  import * as Crypto from 'expo-crypto';
33
-
34
9
  import { mapFirebaseError } from '../errors';
35
10
  import { AuthContext } from './AuthContext';
36
- import { AuthConfig, AuthStatus, User, AuthError, AuthErrorCode } from '../types';
11
+ import {
12
+ AuthConfig,
13
+ AuthStatus,
14
+ User,
15
+ AuthError,
16
+ AuthErrorCode,
17
+ ProviderErrorCodes,
18
+ EmailSignInOptions,
19
+ EmailSignUpOptions
20
+ } from '../types';
21
+ const getReactNativePersistence = (FirebaseAuth as any).getReactNativePersistence;
37
22
 
38
23
  interface AuthProviderProps {
39
24
  config: AuthConfig;
@@ -44,17 +29,15 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children })
44
29
  const [user, setUser] = useState<User | null>(null);
45
30
  const [status, setStatus] = useState<AuthStatus>(AuthStatus.LOADING);
46
31
  const [error, setError] = useState<AuthError | null>(null);
47
- const [firebaseAuthInstance, setFirebaseAuthInstance] = useState<FirebaseAuth | null>(null);
48
-
49
- // ✅ NEW: Explicit loading state for initial app load vs action loading
32
+ const [firebaseAuthInstance, setFirebaseAuthInstance] = useState<FirebaseAuth.Auth | null>(null);
50
33
  const [isDataLoading, setIsDataLoading] = useState(true);
51
34
 
35
+ // --- INITIALIZATION ---
52
36
  useEffect(() => {
53
37
  let app: FirebaseApp;
54
- let auth: FirebaseAuth;
38
+ let auth: FirebaseAuth.Auth;
55
39
 
56
40
  if (!getApps().length) {
57
- // 1. Initialize App
58
41
  app = initializeApp({
59
42
  apiKey: config.apiKey,
60
43
  authDomain: config.authDomain,
@@ -64,42 +47,37 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children })
64
47
  appId: config.appId,
65
48
  });
66
49
 
67
- // 2. Select Persistence Strategy
68
- const selectedPersistence = config.persistence === 'memory'
69
- ? inMemoryPersistence
50
+ const selectedPersistence = config.persistence === 'memory'
51
+ ? FirebaseAuth.inMemoryPersistence
70
52
  : getReactNativePersistence(ReactNativeAsyncStorage);
71
53
 
72
- // 3. Initialize Auth
73
- auth = initializeAuth(app, {
54
+ auth = FirebaseAuth.initializeAuth(app, {
74
55
  persistence: selectedPersistence
75
56
  });
76
-
77
57
  } else {
78
58
  app = getApp();
79
- auth = getAuth(app);
59
+ auth = FirebaseAuth.getAuth(app);
80
60
  }
81
61
 
82
62
  setFirebaseAuthInstance(auth);
83
63
 
84
- // 4. Configure Google Sign-In if enabled
85
64
  if (config.enableGoogle && config.googleWebClientId) {
86
65
  try {
87
66
  GoogleSignin.configure({
88
67
  webClientId: config.googleWebClientId,
89
68
  offlineAccess: true,
90
- iosClientId: config.googleIOSClientId, // Optional
69
+ iosClientId: config.googleIOSClientId,
91
70
  });
92
- console.log('Google Sign-In configured successfully');
71
+ console.log('Google Sign-In configured successfully');
93
72
  } catch (err) {
94
- console.error('Google Sign-In configuration failed:', err);
73
+ console.error('Google Sign-In configuration failed:', err);
95
74
  }
96
75
  }
97
76
 
98
- const unsubscribe = onAuthStateChanged(auth, async (fbUser: FirebaseUser | null) => {
77
+ const unsubscribe = FirebaseAuth.onAuthStateChanged(auth, async (fbUser) => {
99
78
  try {
100
79
  if (fbUser) {
101
80
  try {
102
- // Force token refresh to ensure validity on load
103
81
  const token = await fbUser.getIdToken(true);
104
82
  setUser({
105
83
  uid: fbUser.uid,
@@ -112,7 +90,8 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children })
112
90
  setStatus(AuthStatus.AUTHENTICATED);
113
91
  } catch (tokenError: any) {
114
92
  console.error('Token retrieval error:', tokenError);
115
- if (tokenError.code === 'auth/user-token-expired' || tokenError.code === 'auth/null-user') {
93
+ if (tokenError.code === ProviderErrorCodes.USER_TOKEN_EXPIRED ||
94
+ tokenError.code === ProviderErrorCodes.NULL_USER) {
116
95
  setStatus(AuthStatus.TOKEN_EXPIRED);
117
96
  } else {
118
97
  setStatus(AuthStatus.UNAUTHENTICATED);
@@ -127,61 +106,48 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children })
127
106
  console.error("Auth State Error:", err);
128
107
  setStatus(AuthStatus.UNAUTHENTICATED);
129
108
  } finally {
130
- // ✅ Stop initial loading spinner once Firebase has checked storage
131
109
  setIsDataLoading(false);
132
110
  }
133
- }, (err) => {
134
- console.error("Auth State Error:", err);
135
- setStatus(AuthStatus.UNAUTHENTICATED);
136
- setIsDataLoading(false);
137
111
  });
138
112
 
139
113
  return () => unsubscribe();
140
114
  }, [config]);
141
115
 
142
- // Email/Password Sign In
143
- const signInWithEmail = async (email: string, pass: string) => {
116
+ // --- ACTIONS ---
117
+
118
+ const signInWithEmail = async ({ email, password }: EmailSignInOptions) => {
144
119
  if (!firebaseAuthInstance) return;
145
120
  try {
146
121
  setError(null);
147
122
  setStatus(AuthStatus.LOADING);
148
- await signInWithEmailAndPassword(firebaseAuthInstance, email, pass);
149
- } catch (err) {
150
- const mappedError = mapFirebaseError(err);
151
- setError(mappedError);
123
+ await FirebaseAuth.signInWithEmailAndPassword(firebaseAuthInstance, email, password);
124
+ } catch (err: any) {
125
+ const mapped = mapFirebaseError(err);
126
+ setError({ ...mapped, originalError: err });
152
127
  setStatus(AuthStatus.UNAUTHENTICATED);
153
- throw mappedError;
128
+ throw err;
154
129
  }
155
130
  };
156
131
 
157
- // Email/Password Sign Up
158
- const signUpWithEmail = async (email: string, pass: string) => {
132
+ const signUpWithEmail = async ({ email, password }: EmailSignUpOptions) => {
159
133
  if (!firebaseAuthInstance) return;
160
134
  try {
161
135
  setError(null);
162
136
  setStatus(AuthStatus.LOADING);
163
- await createUserWithEmailAndPassword(firebaseAuthInstance, email, pass);
164
- } catch (err) {
165
- const mappedError = mapFirebaseError(err);
166
- setError(mappedError);
137
+ await FirebaseAuth.createUserWithEmailAndPassword(firebaseAuthInstance, email, password);
138
+ } catch (err: any) {
139
+ const mapped = mapFirebaseError(err);
140
+ setError({ ...mapped, originalError: err });
167
141
  setStatus(AuthStatus.UNAUTHENTICATED);
168
- throw mappedError;
142
+ throw err;
169
143
  }
170
144
  };
171
145
 
172
- // PROPER GOOGLE SIGN-IN using @react-native-google-signin/google-signin
173
146
  const signInWithGoogle = async () => {
174
- if (!firebaseAuthInstance) {
175
- throw new Error('Firebase not initialized');
176
- }
177
-
147
+ if (!firebaseAuthInstance) throw new Error('Firebase not initialized');
178
148
  if (!config.enableGoogle || !config.googleWebClientId) {
179
- const configError: AuthError = {
180
- code: AuthErrorCode.CONFIG_ERROR,
181
- message: 'Google Sign-In is not enabled or configured. Please add googleWebClientId to your AuthConfig.',
182
- };
183
- setError(configError);
184
- throw configError;
149
+ setError({ code: AuthErrorCode.CONFIG_ERROR, message: 'Google Auth not configured. Missing googleWebClientId.' });
150
+ return;
185
151
  }
186
152
 
187
153
  try {
@@ -192,35 +158,31 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children })
192
158
  const userInfo = await GoogleSignin.signIn();
193
159
  const idToken = userInfo.data?.idToken;
194
160
 
195
- if (!idToken) {
196
- throw new Error('No ID token received from Google Sign-In');
197
- }
198
-
199
- const credential = GoogleAuthProvider.credential(idToken);
200
- await signInWithCredential(firebaseAuthInstance, credential);
161
+ if (!idToken) throw new Error('No ID token received from Google Sign-In');
201
162
 
202
- console.log('✅ Google Sign-In successful');
163
+ const credential = FirebaseAuth.GoogleAuthProvider.credential(idToken);
164
+ await FirebaseAuth.signInWithCredential(firebaseAuthInstance, credential);
165
+ console.log('Google Sign-In successful');
203
166
 
204
167
  } catch (err: any) {
205
- console.error('Google Sign-In Error:', err);
168
+ console.error('Google Sign-In Error:', err);
206
169
  let mappedError: AuthError;
207
170
 
208
- if (err.code === 'SIGN_IN_CANCELLED') {
171
+ if (err.code === ProviderErrorCodes.GOOGLE_CANCELLED) {
209
172
  mappedError = {
210
173
  code: AuthErrorCode.GOOGLE_SIGN_IN_CANCELLED,
211
174
  message: 'Google Sign-In was cancelled',
212
175
  originalError: err
213
176
  };
214
- // Reset status if cancelled, don't leave it loading
215
- setStatus(AuthStatus.UNAUTHENTICATED);
216
- return;
217
- } else if (err.code === 'IN_PROGRESS') {
177
+ setStatus(AuthStatus.UNAUTHENTICATED);
178
+ return;
179
+ } else if (err.code === ProviderErrorCodes.GOOGLE_IN_PROGRESS) {
218
180
  mappedError = {
219
181
  code: AuthErrorCode.GOOGLE_SIGN_IN_IN_PROGRESS,
220
182
  message: 'Google Sign-In is already in progress',
221
183
  originalError: err
222
184
  };
223
- } else if (err.code === 'PLAY_SERVICES_NOT_AVAILABLE') {
185
+ } else if (err.code === ProviderErrorCodes.GOOGLE_PLAY_UNAVAILABLE) {
224
186
  mappedError = {
225
187
  code: AuthErrorCode.GOOGLE_PLAY_SERVICES_NOT_AVAILABLE,
226
188
  message: 'Google Play Services are not available. Please update Google Play Services.',
@@ -230,18 +192,15 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children })
230
192
  mappedError = mapFirebaseError(err);
231
193
  }
232
194
 
233
- setError(mappedError);
195
+ setError({ ...mappedError, originalError: err });
234
196
  setStatus(AuthStatus.UNAUTHENTICATED);
235
197
  throw mappedError;
236
198
  }
237
199
  };
238
200
 
239
- // Apple Sign-In using expo-apple-authentication
240
201
  const signInWithApple = async () => {
241
- if (!firebaseAuthInstance) {
242
- throw new Error('Firebase not initialized');
243
- }
244
-
202
+ if (!firebaseAuthInstance) throw new Error('Firebase not initialized');
203
+
245
204
  if (Platform.OS !== 'ios') {
246
205
  const platformError: AuthError = {
247
206
  code: AuthErrorCode.APPLE_SIGN_IN_NOT_SUPPORTED,
@@ -264,13 +223,10 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children })
264
223
  try {
265
224
  setError(null);
266
225
  setStatus(AuthStatus.LOADING);
267
-
226
+
268
227
  const nonce = Math.random().toString(36).substring(2, 10);
269
- const hashedNonce = await Crypto.digestStringAsync(
270
- Crypto.CryptoDigestAlgorithm.SHA256,
271
- nonce
272
- );
273
-
228
+ const hashedNonce = await Crypto.digestStringAsync(Crypto.CryptoDigestAlgorithm.SHA256, nonce);
229
+
274
230
  const appleCredential = await AppleAuthentication.signInAsync({
275
231
  requestedScopes: [
276
232
  AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
@@ -281,23 +237,21 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children })
281
237
 
282
238
  const { identityToken } = appleCredential;
283
239
 
284
- if (!identityToken) {
285
- throw new Error('No identity token received from Apple');
286
- }
240
+ if (!identityToken) throw new Error('No identity token received from Apple');
287
241
 
288
- const provider = new OAuthProvider('apple.com');
242
+ const provider = new FirebaseAuth.OAuthProvider('apple.com');
289
243
  const credential = provider.credential({
290
244
  idToken: identityToken,
291
245
  rawNonce: nonce,
292
246
  });
293
247
 
294
- await signInWithCredential(firebaseAuthInstance, credential);
295
- console.log('Apple Sign-In successful');
248
+ await FirebaseAuth.signInWithCredential(firebaseAuthInstance, credential);
249
+ console.log('Apple Sign-In successful');
296
250
 
297
251
  } catch (err: any) {
298
- console.error('Apple Sign-In Error:', err);
252
+ console.error('Apple Sign-In Error:', err);
299
253
 
300
- if (err.code === 'ERR_REQUEST_CANCELED') {
254
+ if (err.code === ProviderErrorCodes.APPLE_CANCELLED) {
301
255
  const cancelError: AuthError = {
302
256
  code: AuthErrorCode.APPLE_SIGN_IN_CANCELLED,
303
257
  message: 'Apple Sign-In was cancelled',
@@ -309,13 +263,12 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children })
309
263
  }
310
264
 
311
265
  const mappedError = mapFirebaseError(err);
312
- setError(mappedError);
266
+ setError({ ...mappedError, originalError: err });
313
267
  setStatus(AuthStatus.UNAUTHENTICATED);
314
268
  throw mappedError;
315
269
  }
316
270
  };
317
271
 
318
- // Sign Out
319
272
  const signOut = async () => {
320
273
  try {
321
274
  if (firebaseAuthInstance) {
@@ -328,9 +281,9 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children })
328
281
  console.log('Google sign-out skipped or failed:', googleSignOutError);
329
282
  }
330
283
  }
331
- console.log('Sign out successful');
284
+ console.log('Sign out successful');
332
285
  } catch (err) {
333
- console.error('Sign out error:', err);
286
+ console.error('Sign out error:', err);
334
287
  setUser(null);
335
288
  setStatus(AuthStatus.UNAUTHENTICATED);
336
289
  }
@@ -341,10 +294,8 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children })
341
294
  const value = useMemo(() => ({
342
295
  user,
343
296
  status,
344
- // ✅ NEW: Combine internal loading with AuthStatus
345
297
  isLoading: isDataLoading || status === AuthStatus.LOADING,
346
298
  error,
347
- // ✅ NEW: Expose config for UI to read
348
299
  config,
349
300
  signInWithEmail,
350
301
  signUpWithEmail,