rn-swiftauth-sdk 1.0.3 → 1.0.4

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/README.md CHANGED
@@ -231,6 +231,120 @@ const {
231
231
  buttonText: { color: '#000000', fontWeight: 'bold' }
232
232
  }}
233
233
  />
234
+
235
+
236
+
237
+ ---
238
+
239
+ ## 🔐 Password Management
240
+
241
+ The **SwiftAuth SDK** includes built-in support for **password recovery flows** using **Firebase’s email-based password reset mechanism**.
242
+
243
+ ---
244
+
245
+ ### ✉️ Sending a Reset Email
246
+
247
+ The `sendPasswordReset(email)` function sends a password reset link to the user’s email address.
248
+
249
+ #### Method Signature
250
+
251
+ ```ts
252
+ sendPasswordReset: (email: string) => Promise<void>
253
+ ```
254
+
255
+ ---
256
+
257
+ ### 📌 Usage Example (React Native)
258
+
259
+ ```ts
260
+ import React, { useState } from 'react';
261
+ import { View, TextInput, Button, Alert, Text } from 'react-native';
262
+ import { useAuth } from 'rn-swiftauth-sdk';
263
+
264
+ export const ForgotPasswordScreen = () => {
265
+ const [email, setEmail] = useState('');
266
+ const { sendPasswordReset, isLoading, error } = useAuth();
267
+
268
+ const handleReset = async () => {
269
+ if (!email) {
270
+ Alert.alert("Error", "Please enter your email address.");
271
+ return;
272
+ }
273
+
274
+ try {
275
+ await sendPasswordReset(email);
276
+ Alert.alert("Success", "Password reset link sent! Check your email.");
277
+ } catch (err) {
278
+ // Errors are automatically handled by the global `error` state,
279
+ // but can also be caught locally for custom logic.
280
+ console.error("Reset failed:", err);
281
+ }
282
+ };
283
+
284
+ return (
285
+ <View>
286
+ <TextInput
287
+ placeholder="Enter your email"
288
+ value={email}
289
+ onChangeText={setEmail}
290
+ autoCapitalize="none"
291
+ keyboardType="email-address"
292
+ />
293
+
294
+ <Button
295
+ title={isLoading ? "Sending..." : "Reset Password"}
296
+ onPress={handleReset}
297
+ disabled={isLoading}
298
+ />
299
+
300
+ {error && <Text style={{ color: 'red' }}>{error.message}</Text>}
301
+ </View>
302
+ );
303
+ };
304
+ ```
305
+
306
+ ---
307
+
308
+ ### ⚠️ Error Handling
309
+
310
+ The error handling system includes **specific exceptions** for password reset flows.
311
+
312
+ | Exception Class | Error Code | User Message |
313
+ | ----------------------- | --------------------- | ------------------------------------------------------------------- |
314
+ | `UserNotFoundException` | `auth/user-not-found` | "No account found with this email. Please check the email address." |
315
+ | `InvalidEmailException` | `auth/invalid-email` | "The email address is badly formatted." |
316
+
317
+ Errors are exposed through the global `error` state returned by `useAuth()` and can also be handled locally using `try/catch`.
318
+
319
+ ---
320
+
321
+ ### 🎨 Integration with Custom UI
322
+
323
+ You can integrate password recovery into **custom login forms** by toggling a “Forgot Password” mode using the headless hook.
324
+
325
+ ```ts
326
+ const { sendPasswordReset } = useAuth();
327
+
328
+ const onForgotPasswordPress = async (email: string) => {
329
+ await sendPasswordReset(email);
330
+ // Show success feedback to the user
331
+ };
332
+ ```
333
+
334
+ This allows you to fully control your UI while leveraging SwiftAuth’s secure password reset flow.
335
+
336
+ ---
337
+
338
+ If you want, I can also:
339
+
340
+ * Add **UX flow diagrams**
341
+ * Add **Firebase setup prerequisites**
342
+ * Document **rate limits & edge cases**
343
+ * Align tone with the rest of your SDK README
344
+
345
+ Just tell me 👍
346
+
347
+
234
348
  ```
235
349
 
236
350
  ---
@@ -499,7 +613,7 @@ We welcome contributions! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
499
613
 
500
614
  ## License
501
615
 
502
- MIT License - see [LICENSE](LICENSE) file for details.
616
+ MIT License - see [LICENSE](LICENSE) file for detailss.
503
617
 
504
618
  ---
505
619
 
@@ -40,16 +40,35 @@ const useAuth_1 = require("../hooks/useAuth");
40
40
  const PasswordInput_1 = require("./PasswordInput");
41
41
  const validation_1 = require("../utils/validation");
42
42
  const LoginForm = ({ styles: userStyles }) => {
43
- const { signInWithEmail, signInWithGoogle, signInWithApple, isLoading, error, config } = (0, useAuth_1.useAuth)();
43
+ const { signInWithEmail, signInWithGoogle, signInWithApple, sendPasswordReset, isLoading, error, config, clearError } = (0, useAuth_1.useAuth)();
44
44
  const [email, setEmail] = (0, react_1.useState)('');
45
45
  const [password, setPassword] = (0, react_1.useState)('');
46
+ const [isResetMode, setIsResetMode] = (0, react_1.useState)(false); // ✅ 2. Mode Toggle State
46
47
  const [validationErrors, setValidationErrors] = (0, react_1.useState)({});
47
- //Check if form is filled to enable button
48
- const isFormFilled = email.length > 0 && password.length > 0;
48
+ // 3. Dynamic check: If resetting, we only need Email. If logging in, we need both.
49
+ const isFormFilled = isResetMode
50
+ ? email.length > 0
51
+ : (email.length > 0 && password.length > 0);
52
+ // ✅ 4. New Handler for Password Reset
53
+ const handleResetPassword = async () => {
54
+ setValidationErrors({});
55
+ const emailErr = (0, validation_1.validateEmail)(email);
56
+ if (emailErr) {
57
+ setValidationErrors({ email: emailErr });
58
+ return;
59
+ }
60
+ try {
61
+ await sendPasswordReset(email);
62
+ react_native_1.Alert.alert("Check your email", "If an account exists with this email, a password reset link has been sent.");
63
+ setIsResetMode(false); // Go back to login screen
64
+ }
65
+ catch (e) {
66
+ console.log('Reset failed:', e);
67
+ }
68
+ };
49
69
  const handleLogin = async () => {
50
- // 1. Reset previous errors
51
70
  setValidationErrors({});
52
- // 2. Validate Inputs
71
+ // Validate Inputs
53
72
  const emailErr = (0, validation_1.validateEmail)(email);
54
73
  const passErr = (0, validation_1.validatePasswordLogin)(password);
55
74
  if (emailErr || passErr) {
@@ -57,16 +76,12 @@ const LoginForm = ({ styles: userStyles }) => {
57
76
  email: emailErr || undefined,
58
77
  password: passErr || undefined
59
78
  });
60
- return; //Stop if invalid
79
+ return;
61
80
  }
62
- // 3. Attempt Login
63
81
  try {
64
- //UPDATED: Clean Object Syntax
65
82
  await signInWithEmail({ email, password });
66
83
  }
67
84
  catch (e) {
68
- // Auth errors handled by global state
69
- // DX: Log it for the developer (Optional but helpful for debugging)
70
85
  console.log('Login failed:', e);
71
86
  }
72
87
  };
@@ -86,8 +101,15 @@ const LoginForm = ({ styles: userStyles }) => {
86
101
  console.error('Apple Sign-In Error:', e);
87
102
  }
88
103
  };
104
+ const toggleResetMode = () => {
105
+ setIsResetMode(!isResetMode);
106
+ clearError();
107
+ setValidationErrors({});
108
+ setPassword('');
109
+ };
89
110
  return (react_1.default.createElement(react_native_1.View, { style: [defaultStyles.container, userStyles?.container] },
90
111
  error && (react_1.default.createElement(react_native_1.Text, { style: [defaultStyles.errorText, userStyles?.errorText] }, error.message)),
112
+ isResetMode && (react_1.default.createElement(react_native_1.Text, { style: defaultStyles.modeTitle }, "Reset Password")),
91
113
  react_1.default.createElement(react_native_1.TextInput, { style: [
92
114
  defaultStyles.input,
93
115
  userStyles?.input,
@@ -98,34 +120,30 @@ const LoginForm = ({ styles: userStyles }) => {
98
120
  setValidationErrors({ ...validationErrors, email: undefined });
99
121
  }, autoCapitalize: "none", keyboardType: "email-address", placeholderTextColor: "#999", editable: !isLoading }),
100
122
  validationErrors.email && (react_1.default.createElement(react_native_1.Text, { style: defaultStyles.validationText }, validationErrors.email)),
101
- react_1.default.createElement(PasswordInput_1.PasswordInput, { styles: userStyles, placeholder: "Password", value: password, onChangeText: (text) => {
102
- setPassword(text);
103
- if (validationErrors.password)
104
- setValidationErrors({ ...validationErrors, password: undefined });
105
- }, editable: !isLoading }),
106
- validationErrors.password && (react_1.default.createElement(react_native_1.Text, { style: defaultStyles.validationText }, validationErrors.password)),
123
+ !isResetMode && (react_1.default.createElement(react_1.default.Fragment, null,
124
+ react_1.default.createElement(PasswordInput_1.PasswordInput, { styles: userStyles, placeholder: "Password", value: password, onChangeText: (text) => {
125
+ setPassword(text);
126
+ if (validationErrors.password)
127
+ setValidationErrors({ ...validationErrors, password: undefined });
128
+ }, editable: !isLoading }),
129
+ validationErrors.password && (react_1.default.createElement(react_native_1.Text, { style: defaultStyles.validationText }, validationErrors.password)),
130
+ react_1.default.createElement(react_native_1.TouchableOpacity, { style: defaultStyles.forgotPasswordContainer, onPress: toggleResetMode, disabled: isLoading },
131
+ react_1.default.createElement(react_native_1.Text, { style: [defaultStyles.forgotPasswordText, userStyles?.linkText] }, "Forgot Password?")))),
107
132
  react_1.default.createElement(react_native_1.TouchableOpacity, { style: [
108
133
  defaultStyles.button,
109
- // Disable style if loading OR form is incomplete
110
134
  (isLoading || !isFormFilled) && defaultStyles.buttonDisabled,
111
135
  userStyles?.button
112
- ], onPress: handleLogin,
113
- // Disable interaction if loading OR form is incomplete
114
- disabled: isLoading || !isFormFilled }, isLoading ? (react_1.default.createElement(react_native_1.ActivityIndicator, { color: userStyles?.loadingIndicatorColor || "#fff" })) : (react_1.default.createElement(react_native_1.Text, { style: [defaultStyles.buttonText, userStyles?.buttonText] }, "Sign In"))),
115
- (config.enableGoogle || config.enableApple) && !isLoading && (react_1.default.createElement(react_1.default.Fragment, null,
136
+ ], onPress: isResetMode ? handleResetPassword : handleLogin, disabled: isLoading || !isFormFilled }, isLoading ? (react_1.default.createElement(react_native_1.ActivityIndicator, { color: userStyles?.loadingIndicatorColor || "#fff" })) : (react_1.default.createElement(react_native_1.Text, { style: [defaultStyles.buttonText, userStyles?.buttonText] }, isResetMode ? "Send Reset Link" : "Sign In"))),
137
+ isResetMode && (react_1.default.createElement(react_native_1.TouchableOpacity, { style: defaultStyles.cancelButton, onPress: toggleResetMode, disabled: isLoading },
138
+ react_1.default.createElement(react_native_1.Text, { style: defaultStyles.cancelButtonText }, "Back to Sign In"))),
139
+ !isResetMode && (config.enableGoogle || config.enableApple) && !isLoading && (react_1.default.createElement(react_1.default.Fragment, null,
116
140
  react_1.default.createElement(react_native_1.View, { style: defaultStyles.dividerContainer },
117
141
  react_1.default.createElement(react_native_1.View, { style: defaultStyles.divider }),
118
142
  react_1.default.createElement(react_native_1.Text, { style: defaultStyles.dividerText }, "OR"),
119
143
  react_1.default.createElement(react_native_1.View, { style: defaultStyles.divider })),
120
- config.enableGoogle && (react_1.default.createElement(react_native_1.TouchableOpacity, { style: [
121
- defaultStyles.oauthButton,
122
- defaultStyles.googleButton,
123
- ], onPress: handleGoogleSignIn },
144
+ config.enableGoogle && (react_1.default.createElement(react_native_1.TouchableOpacity, { style: [defaultStyles.oauthButton, defaultStyles.googleButton], onPress: handleGoogleSignIn },
124
145
  react_1.default.createElement(react_native_1.Text, { style: defaultStyles.googleButtonText }, "Continue with Google"))),
125
- config.enableApple && react_native_1.Platform.OS === 'ios' && (react_1.default.createElement(react_native_1.TouchableOpacity, { style: [
126
- defaultStyles.oauthButton,
127
- defaultStyles.appleButton,
128
- ], onPress: handleAppleSignIn },
146
+ config.enableApple && react_native_1.Platform.OS === 'ios' && (react_1.default.createElement(react_native_1.TouchableOpacity, { style: [defaultStyles.oauthButton, defaultStyles.appleButton], onPress: handleAppleSignIn },
129
147
  react_1.default.createElement(react_native_1.Text, { style: defaultStyles.appleButtonText }, "Continue with Apple")))))));
130
148
  };
131
149
  exports.LoginForm = LoginForm;
@@ -185,4 +203,31 @@ const defaultStyles = react_native_1.StyleSheet.create({
185
203
  fontSize: 16,
186
204
  fontWeight: '600',
187
205
  },
206
+ forgotPasswordContainer: {
207
+ alignSelf: 'flex-end',
208
+ marginBottom: 12,
209
+ padding: 4,
210
+ },
211
+ forgotPasswordText: {
212
+ color: '#007AFF',
213
+ fontSize: 14,
214
+ fontWeight: '500',
215
+ },
216
+ modeTitle: {
217
+ fontSize: 18,
218
+ fontWeight: 'bold',
219
+ marginBottom: 16,
220
+ color: '#333',
221
+ textAlign: 'center',
222
+ },
223
+ cancelButton: {
224
+ marginTop: 16,
225
+ alignItems: 'center',
226
+ padding: 8,
227
+ },
228
+ cancelButtonText: {
229
+ color: '#666',
230
+ fontSize: 14,
231
+ fontWeight: '500',
232
+ },
188
233
  });
@@ -41,6 +41,7 @@ const react_1 = __importStar(require("react"));
41
41
  const react_native_1 = require("react-native");
42
42
  const app_1 = require("firebase/app");
43
43
  const FirebaseAuth = __importStar(require("firebase/auth"));
44
+ const auth_1 = require("firebase/auth");
44
45
  const async_storage_1 = __importDefault(require("@react-native-async-storage/async-storage"));
45
46
  const google_signin_1 = require("@react-native-google-signin/google-signin");
46
47
  const AppleAuthentication = __importStar(require("expo-apple-authentication"));
@@ -169,6 +170,23 @@ const AuthProvider = ({ config, children }) => {
169
170
  throw mappedException;
170
171
  }
171
172
  };
173
+ const sendPasswordReset = async (email) => {
174
+ if (!firebaseAuthInstance)
175
+ return;
176
+ try {
177
+ setError(null);
178
+ setStatus(types_1.AuthStatus.LOADING);
179
+ await (0, auth_1.sendPasswordResetEmail)(firebaseAuthInstance, email);
180
+ setStatus(types_1.AuthStatus.UNAUTHENTICATED);
181
+ console.log(`Password reset email sent to ${email}`);
182
+ }
183
+ catch (err) {
184
+ const mappedException = (0, errors_1.mapFirebaseError)(err);
185
+ setError(mappedException);
186
+ setStatus(types_1.AuthStatus.UNAUTHENTICATED);
187
+ throw mappedException;
188
+ }
189
+ };
172
190
  const signInWithGoogle = async () => {
173
191
  if (!firebaseAuthInstance)
174
192
  throw new Error('Firebase not initialized');
@@ -286,6 +304,7 @@ const AuthProvider = ({ config, children }) => {
286
304
  config,
287
305
  signInWithEmail,
288
306
  signUpWithEmail,
307
+ sendPasswordReset,
289
308
  signInWithGoogle,
290
309
  signInWithApple,
291
310
  signOut,
@@ -30,6 +30,7 @@ export interface AuthContextType {
30
30
  config: AuthConfig;
31
31
  signInWithEmail: (options: EmailSignInOptions) => Promise<void>;
32
32
  signUpWithEmail: (options: EmailSignUpOptions) => Promise<void>;
33
+ sendPasswordReset: (email: string) => Promise<void>;
33
34
  signInWithGoogle: () => Promise<void>;
34
35
  signInWithApple: () => Promise<void>;
35
36
  signOut: () => Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rn-swiftauth-sdk",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "files": [
@@ -6,7 +6,8 @@ import {
6
6
  TouchableOpacity,
7
7
  StyleSheet,
8
8
  ActivityIndicator,
9
- Platform
9
+ Platform,
10
+ Alert
10
11
  } from 'react-native';
11
12
  import { useAuth } from '../hooks/useAuth';
12
13
  import { AuthScreenStyles } from '../types';
@@ -22,24 +23,49 @@ export const LoginForm = ({ styles: userStyles }: LoginFormProps) => {
22
23
  signInWithEmail,
23
24
  signInWithGoogle,
24
25
  signInWithApple,
26
+ sendPasswordReset,
25
27
  isLoading,
26
28
  error,
27
- config
29
+ config,
30
+ clearError
28
31
  } = useAuth();
29
32
 
30
33
  const [email, setEmail] = useState('');
31
34
  const [password, setPassword] = useState('');
35
+ const [isResetMode, setIsResetMode] = useState(false); // ✅ 2. Mode Toggle State
32
36
 
33
37
  const [validationErrors, setValidationErrors] = useState<{ email?: string; password?: string }>({});
34
38
 
35
- //Check if form is filled to enable button
36
- const isFormFilled = email.length > 0 && password.length > 0;
39
+ // 3. Dynamic check: If resetting, we only need Email. If logging in, we need both.
40
+ const isFormFilled = isResetMode
41
+ ? email.length > 0
42
+ : (email.length > 0 && password.length > 0);
37
43
 
38
- const handleLogin = async () => {
39
- // 1. Reset previous errors
44
+ // 4. New Handler for Password Reset
45
+ const handleResetPassword = async () => {
40
46
  setValidationErrors({});
47
+ const emailErr = validateEmail(email);
48
+ if (emailErr) {
49
+ setValidationErrors({ email: emailErr });
50
+ return;
51
+ }
52
+
53
+ try {
54
+ await sendPasswordReset(email);
55
+ Alert.alert(
56
+ "Check your email",
57
+ "If an account exists with this email, a password reset link has been sent."
58
+ );
59
+ setIsResetMode(false); // Go back to login screen
60
+ } catch (e) {
61
+ console.log('Reset failed:', e);
62
+ }
63
+ };
41
64
 
42
- // 2. Validate Inputs
65
+ const handleLogin = async () => {
66
+ setValidationErrors({});
67
+
68
+ // Validate Inputs
43
69
  const emailErr = validateEmail(email);
44
70
  const passErr = validatePasswordLogin(password);
45
71
 
@@ -48,16 +74,12 @@ export const LoginForm = ({ styles: userStyles }: LoginFormProps) => {
48
74
  email: emailErr || undefined,
49
75
  password: passErr || undefined
50
76
  });
51
- return; //Stop if invalid
77
+ return;
52
78
  }
53
79
 
54
- // 3. Attempt Login
55
80
  try {
56
- //UPDATED: Clean Object Syntax
57
81
  await signInWithEmail({ email, password });
58
82
  } catch (e) {
59
- // Auth errors handled by global state
60
- // DX: Log it for the developer (Optional but helpful for debugging)
61
83
  console.log('Login failed:', e);
62
84
  }
63
85
  };
@@ -78,16 +100,30 @@ export const LoginForm = ({ styles: userStyles }: LoginFormProps) => {
78
100
  }
79
101
  };
80
102
 
103
+
104
+ const toggleResetMode = () => {
105
+ setIsResetMode(!isResetMode);
106
+ clearError();
107
+ setValidationErrors({});
108
+
109
+ setPassword('');
110
+ };
111
+
81
112
  return (
82
113
  <View style={[defaultStyles.container, userStyles?.container]}>
83
- {/* Global API Error */}
114
+
84
115
  {error && (
85
116
  <Text style={[defaultStyles.errorText, userStyles?.errorText]}>
86
117
  {error.message}
87
118
  </Text>
88
119
  )}
89
120
 
90
- {/* Email Input */}
121
+
122
+ {isResetMode && (
123
+ <Text style={defaultStyles.modeTitle}>Reset Password</Text>
124
+ )}
125
+
126
+
91
127
  <TextInput
92
128
  style={[
93
129
  defaultStyles.input,
@@ -109,44 +145,69 @@ export const LoginForm = ({ styles: userStyles }: LoginFormProps) => {
109
145
  <Text style={defaultStyles.validationText}>{validationErrors.email}</Text>
110
146
  )}
111
147
 
112
- {/* Password Input */}
113
- <PasswordInput
114
- styles={userStyles}
115
- placeholder="Password"
116
- value={password}
117
- onChangeText={(text) => {
118
- setPassword(text);
119
- if (validationErrors.password) setValidationErrors({...validationErrors, password: undefined});
120
- }}
121
- editable={!isLoading}
122
- />
123
- {validationErrors.password && (
124
- <Text style={defaultStyles.validationText}>{validationErrors.password}</Text>
148
+
149
+ {!isResetMode && (
150
+ <>
151
+ <PasswordInput
152
+ styles={userStyles}
153
+ placeholder="Password"
154
+ value={password}
155
+ onChangeText={(text) => {
156
+ setPassword(text);
157
+ if (validationErrors.password) setValidationErrors({...validationErrors, password: undefined});
158
+ }}
159
+ editable={!isLoading}
160
+ />
161
+ {validationErrors.password && (
162
+ <Text style={defaultStyles.validationText}>{validationErrors.password}</Text>
163
+ )}
164
+
165
+
166
+ <TouchableOpacity
167
+ style={defaultStyles.forgotPasswordContainer}
168
+ onPress={toggleResetMode}
169
+ disabled={isLoading}
170
+ >
171
+ <Text style={[defaultStyles.forgotPasswordText, userStyles?.linkText]}>
172
+ Forgot Password?
173
+ </Text>
174
+ </TouchableOpacity>
175
+ </>
125
176
  )}
126
177
 
127
- {/* Sign In Button */}
178
+
128
179
  <TouchableOpacity
129
180
  style={[
130
181
  defaultStyles.button,
131
- // Disable style if loading OR form is incomplete
132
182
  (isLoading || !isFormFilled) && defaultStyles.buttonDisabled,
133
183
  userStyles?.button
134
184
  ]}
135
- onPress={handleLogin}
136
- // Disable interaction if loading OR form is incomplete
185
+
186
+ onPress={isResetMode ? handleResetPassword : handleLogin}
137
187
  disabled={isLoading || !isFormFilled}
138
188
  >
139
189
  {isLoading ? (
140
190
  <ActivityIndicator color={userStyles?.loadingIndicatorColor || "#fff"} />
141
191
  ) : (
142
192
  <Text style={[defaultStyles.buttonText, userStyles?.buttonText]}>
143
- Sign In
193
+ {isResetMode ? "Send Reset Link" : "Sign In"}
144
194
  </Text>
145
195
  )}
146
196
  </TouchableOpacity>
147
197
 
148
- {/* OAuth Section */}
149
- {(config.enableGoogle || config.enableApple) && !isLoading && (
198
+
199
+ {isResetMode && (
200
+ <TouchableOpacity
201
+ style={defaultStyles.cancelButton}
202
+ onPress={toggleResetMode}
203
+ disabled={isLoading}
204
+ >
205
+ <Text style={defaultStyles.cancelButtonText}>Back to Sign In</Text>
206
+ </TouchableOpacity>
207
+ )}
208
+
209
+
210
+ {!isResetMode && (config.enableGoogle || config.enableApple) && !isLoading && (
150
211
  <>
151
212
  <View style={defaultStyles.dividerContainer}>
152
213
  <View style={defaultStyles.divider} />
@@ -157,30 +218,20 @@ export const LoginForm = ({ styles: userStyles }: LoginFormProps) => {
157
218
  {/* Google Button */}
158
219
  {config.enableGoogle && (
159
220
  <TouchableOpacity
160
- style={[
161
- defaultStyles.oauthButton,
162
- defaultStyles.googleButton,
163
- ]}
221
+ style={[defaultStyles.oauthButton, defaultStyles.googleButton]}
164
222
  onPress={handleGoogleSignIn}
165
223
  >
166
- <Text style={defaultStyles.googleButtonText}>
167
- Continue with Google
168
- </Text>
224
+ <Text style={defaultStyles.googleButtonText}>Continue with Google</Text>
169
225
  </TouchableOpacity>
170
226
  )}
171
227
 
172
228
  {/* Apple Button (iOS Only) */}
173
229
  {config.enableApple && Platform.OS === 'ios' && (
174
230
  <TouchableOpacity
175
- style={[
176
- defaultStyles.oauthButton,
177
- defaultStyles.appleButton,
178
- ]}
231
+ style={[defaultStyles.oauthButton, defaultStyles.appleButton]}
179
232
  onPress={handleAppleSignIn}
180
233
  >
181
- <Text style={defaultStyles.appleButtonText}>
182
- Continue with Apple
183
- </Text>
234
+ <Text style={defaultStyles.appleButtonText}>Continue with Apple</Text>
184
235
  </TouchableOpacity>
185
236
  )}
186
237
  </>
@@ -252,4 +303,33 @@ const defaultStyles = StyleSheet.create({
252
303
  fontSize: 16,
253
304
  fontWeight: '600',
254
305
  },
306
+
307
+
308
+ forgotPasswordContainer: {
309
+ alignSelf: 'flex-end',
310
+ marginBottom: 12,
311
+ padding: 4,
312
+ },
313
+ forgotPasswordText: {
314
+ color: '#007AFF',
315
+ fontSize: 14,
316
+ fontWeight: '500',
317
+ },
318
+ modeTitle: {
319
+ fontSize: 18,
320
+ fontWeight: 'bold',
321
+ marginBottom: 16,
322
+ color: '#333',
323
+ textAlign: 'center',
324
+ },
325
+ cancelButton: {
326
+ marginTop: 16,
327
+ alignItems: 'center',
328
+ padding: 8,
329
+ },
330
+ cancelButtonText: {
331
+ color: '#666',
332
+ fontSize: 14,
333
+ fontWeight: '500',
334
+ },
255
335
  });
@@ -2,6 +2,8 @@ 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
4
  import * as FirebaseAuth from 'firebase/auth';
5
+
6
+ import { sendPasswordResetEmail } from 'firebase/auth';
5
7
  import ReactNativeAsyncStorage from '@react-native-async-storage/async-storage';
6
8
  import { GoogleSignin } from '@react-native-google-signin/google-signin';
7
9
  import * as AppleAuthentication from 'expo-apple-authentication';
@@ -152,6 +154,26 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children })
152
154
  }
153
155
  };
154
156
 
157
+
158
+ const sendPasswordReset = async (email: string) => {
159
+ if (!firebaseAuthInstance) return;
160
+ try {
161
+ setError(null);
162
+ setStatus(AuthStatus.LOADING);
163
+
164
+ await sendPasswordResetEmail(firebaseAuthInstance, email);
165
+
166
+
167
+ setStatus(AuthStatus.UNAUTHENTICATED);
168
+ console.log(`Password reset email sent to ${email}`);
169
+ } catch (err: any) {
170
+ const mappedException = mapFirebaseError(err);
171
+ setError(mappedException);
172
+ setStatus(AuthStatus.UNAUTHENTICATED);
173
+ throw mappedException;
174
+ }
175
+ };
176
+
155
177
  const signInWithGoogle = async () => {
156
178
  if (!firebaseAuthInstance) throw new Error('Firebase not initialized');
157
179
  if (!config.enableGoogle || !config.googleWebClientId) {
@@ -286,6 +308,7 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ config, children })
286
308
  config,
287
309
  signInWithEmail,
288
310
  signUpWithEmail,
311
+ sendPasswordReset,
289
312
  signInWithGoogle,
290
313
  signInWithApple,
291
314
  signOut,
@@ -38,7 +38,7 @@ export interface AuthContextType {
38
38
  // Function Signatures
39
39
  signInWithEmail: (options: EmailSignInOptions) => Promise<void>;
40
40
  signUpWithEmail: (options: EmailSignUpOptions) => Promise<void>;
41
-
41
+ sendPasswordReset: (email: string) => Promise<void>;
42
42
  signInWithGoogle: () => Promise<void>;
43
43
  signInWithApple: () => Promise<void>;
44
44
  signOut: () => Promise<void>;