ignite-parse-auth-kit 1.0.0

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.
Files changed (241) hide show
  1. package/CONTRIBUTING.md +0 -0
  2. package/LICENSE +21 -0
  3. package/README.md +492 -0
  4. package/app/app.tsx +116 -0
  5. package/app/components/AlertTongle.tsx +105 -0
  6. package/app/components/AutoImage.tsx +89 -0
  7. package/app/components/Button.tsx +248 -0
  8. package/app/components/Card.tsx +314 -0
  9. package/app/components/EmptyState.tsx +248 -0
  10. package/app/components/Header.tsx +332 -0
  11. package/app/components/Icon.tsx +140 -0
  12. package/app/components/ListItem.tsx +243 -0
  13. package/app/components/ListView.tsx +42 -0
  14. package/app/components/Screen.tsx +305 -0
  15. package/app/components/Text.test.tsx +23 -0
  16. package/app/components/Text.tsx +116 -0
  17. package/app/components/TextField.tsx +292 -0
  18. package/app/components/Toggle/Checkbox.tsx +123 -0
  19. package/app/components/Toggle/Radio.tsx +106 -0
  20. package/app/components/Toggle/Switch.tsx +264 -0
  21. package/app/components/Toggle/Toggle.tsx +285 -0
  22. package/app/components/index copy.ts +15 -0
  23. package/app/components/index.ts +18 -0
  24. package/app/config/config.base.ts +26 -0
  25. package/app/config/config.dev.ts +10 -0
  26. package/app/config/config.prod.ts +10 -0
  27. package/app/config/index.ts +28 -0
  28. package/app/context/AuthContext.tsx +14 -0
  29. package/app/context/EpisodeContext.tsx +136 -0
  30. package/app/context/auth/AuthProvider.tsx +340 -0
  31. package/app/context/auth/hooks.ts +29 -0
  32. package/app/context/auth/index.ts +38 -0
  33. package/app/context/auth/reducer.ts +68 -0
  34. package/app/context/auth/services.ts +394 -0
  35. package/app/context/auth/types.ts +99 -0
  36. package/app/context/auth/validation.ts +45 -0
  37. package/app/devtools/ReactotronClient.ts +9 -0
  38. package/app/devtools/ReactotronClient.web.ts +12 -0
  39. package/app/devtools/ReactotronConfig.ts +139 -0
  40. package/app/i18n/ar.ts +126 -0
  41. package/app/i18n/demo-ar.ts +464 -0
  42. package/app/i18n/demo-en.ts +462 -0
  43. package/app/i18n/demo-es.ts +469 -0
  44. package/app/i18n/demo-fr.ts +471 -0
  45. package/app/i18n/demo-hi.ts +468 -0
  46. package/app/i18n/demo-ja.ts +464 -0
  47. package/app/i18n/demo-ko.ts +457 -0
  48. package/app/i18n/en.ts +146 -0
  49. package/app/i18n/es.ts +132 -0
  50. package/app/i18n/fr.ts +132 -0
  51. package/app/i18n/hi.ts +131 -0
  52. package/app/i18n/index.ts +86 -0
  53. package/app/i18n/ja.ts +130 -0
  54. package/app/i18n/ko.ts +129 -0
  55. package/app/i18n/translate.ts +33 -0
  56. package/app/lib/Parse/index.ts +2 -0
  57. package/app/lib/Parse/parse.ts +62 -0
  58. package/app/navigators/AppNavigator.tsx +145 -0
  59. package/app/navigators/DemoNavigator.tsx +137 -0
  60. package/app/navigators/navigationUtilities.ts +208 -0
  61. package/app/screens/ChooseAuthScreen.tsx +224 -0
  62. package/app/screens/DemoCommunityScreen.tsx +141 -0
  63. package/app/screens/DemoDebugScreen.tsx +192 -0
  64. package/app/screens/DemoPodcastListScreen.tsx +387 -0
  65. package/app/screens/DemoShowroomScreen/DemoDivider.tsx +66 -0
  66. package/app/screens/DemoShowroomScreen/DemoShowroomScreen.tsx +313 -0
  67. package/app/screens/DemoShowroomScreen/DemoUseCase.tsx +52 -0
  68. package/app/screens/DemoShowroomScreen/DrawerIconButton.tsx +120 -0
  69. package/app/screens/DemoShowroomScreen/SectionListWithKeyboardAwareScrollView.tsx +59 -0
  70. package/app/screens/DemoShowroomScreen/demos/DemoAutoImage.tsx +230 -0
  71. package/app/screens/DemoShowroomScreen/demos/DemoButton.tsx +234 -0
  72. package/app/screens/DemoShowroomScreen/demos/DemoCard.tsx +181 -0
  73. package/app/screens/DemoShowroomScreen/demos/DemoEmptyState.tsx +78 -0
  74. package/app/screens/DemoShowroomScreen/demos/DemoHeader.tsx +151 -0
  75. package/app/screens/DemoShowroomScreen/demos/DemoIcon.tsx +115 -0
  76. package/app/screens/DemoShowroomScreen/demos/DemoListItem.tsx +218 -0
  77. package/app/screens/DemoShowroomScreen/demos/DemoText.tsx +144 -0
  78. package/app/screens/DemoShowroomScreen/demos/DemoTextField.tsx +233 -0
  79. package/app/screens/DemoShowroomScreen/demos/DemoToggle.tsx +354 -0
  80. package/app/screens/DemoShowroomScreen/demos/index.ts +12 -0
  81. package/app/screens/ErrorScreen/ErrorBoundary.tsx +76 -0
  82. package/app/screens/ErrorScreen/ErrorDetails.tsx +98 -0
  83. package/app/screens/ForgetPasswordScreen.tsx +180 -0
  84. package/app/screens/LoginScreen.tsx +260 -0
  85. package/app/screens/RegisterScreen.tsx +395 -0
  86. package/app/screens/WelcomeScreen.tsx +114 -0
  87. package/app/services/api/apiProblem.test.ts +73 -0
  88. package/app/services/api/apiProblem.ts +74 -0
  89. package/app/services/api/index.ts +91 -0
  90. package/app/services/api/types.ts +50 -0
  91. package/app/theme/colors.ts +85 -0
  92. package/app/theme/colorsDark.ts +50 -0
  93. package/app/theme/context.tsx +145 -0
  94. package/app/theme/context.utils.ts +25 -0
  95. package/app/theme/spacing.ts +14 -0
  96. package/app/theme/spacingDark.ts +14 -0
  97. package/app/theme/styles.ts +24 -0
  98. package/app/theme/theme.ts +23 -0
  99. package/app/theme/timing.ts +6 -0
  100. package/app/theme/types.ts +64 -0
  101. package/app/theme/typography.ts +71 -0
  102. package/app/utils/crashReporting.ts +62 -0
  103. package/app/utils/delay.ts +6 -0
  104. package/app/utils/formatDate.ts +49 -0
  105. package/app/utils/gestureHandler.native.ts +3 -0
  106. package/app/utils/gestureHandler.ts +6 -0
  107. package/app/utils/hasValidStringProp.ts +11 -0
  108. package/app/utils/openLinkInBrowser.ts +8 -0
  109. package/app/utils/storage/index.ts +82 -0
  110. package/app/utils/storage/storage.test.ts +61 -0
  111. package/app/utils/useHeader.tsx +37 -0
  112. package/app/utils/useIsMounted.ts +18 -0
  113. package/app/utils/useSafeAreaInsetsStyle.ts +46 -0
  114. package/app.config.ts +39 -0
  115. package/app.json +67 -0
  116. package/assets/icons/back.png +0 -0
  117. package/assets/icons/back@2x.png +0 -0
  118. package/assets/icons/back@3x.png +0 -0
  119. package/assets/icons/bell.png +0 -0
  120. package/assets/icons/bell@2x.png +0 -0
  121. package/assets/icons/bell@3x.png +0 -0
  122. package/assets/icons/caretLeft.png +0 -0
  123. package/assets/icons/caretLeft@2x.png +0 -0
  124. package/assets/icons/caretLeft@3x.png +0 -0
  125. package/assets/icons/caretRight.png +0 -0
  126. package/assets/icons/caretRight@2x.png +0 -0
  127. package/assets/icons/caretRight@3x.png +0 -0
  128. package/assets/icons/check.png +0 -0
  129. package/assets/icons/check@2x.png +0 -0
  130. package/assets/icons/check@3x.png +0 -0
  131. package/assets/icons/demo/clap.png +0 -0
  132. package/assets/icons/demo/clap@2x.png +0 -0
  133. package/assets/icons/demo/clap@3x.png +0 -0
  134. package/assets/icons/demo/community.png +0 -0
  135. package/assets/icons/demo/community@2x.png +0 -0
  136. package/assets/icons/demo/community@3x.png +0 -0
  137. package/assets/icons/demo/components.png +0 -0
  138. package/assets/icons/demo/components@2x.png +0 -0
  139. package/assets/icons/demo/components@3x.png +0 -0
  140. package/assets/icons/demo/debug.png +0 -0
  141. package/assets/icons/demo/debug@2x.png +0 -0
  142. package/assets/icons/demo/debug@3x.png +0 -0
  143. package/assets/icons/demo/github.png +0 -0
  144. package/assets/icons/demo/github@2x.png +0 -0
  145. package/assets/icons/demo/github@3x.png +0 -0
  146. package/assets/icons/demo/heart.png +0 -0
  147. package/assets/icons/demo/heart@2x.png +0 -0
  148. package/assets/icons/demo/heart@3x.png +0 -0
  149. package/assets/icons/demo/pin.png +0 -0
  150. package/assets/icons/demo/pin@2x.png +0 -0
  151. package/assets/icons/demo/pin@3x.png +0 -0
  152. package/assets/icons/demo/podcast.png +0 -0
  153. package/assets/icons/demo/podcast@2x.png +0 -0
  154. package/assets/icons/demo/podcast@3x.png +0 -0
  155. package/assets/icons/demo/slack.png +0 -0
  156. package/assets/icons/demo/slack@2x.png +0 -0
  157. package/assets/icons/demo/slack@3x.png +0 -0
  158. package/assets/icons/google.png +0 -0
  159. package/assets/icons/hidden.png +0 -0
  160. package/assets/icons/hidden@2x.png +0 -0
  161. package/assets/icons/hidden@3x.png +0 -0
  162. package/assets/icons/ladybug.png +0 -0
  163. package/assets/icons/ladybug@2x.png +0 -0
  164. package/assets/icons/ladybug@3x.png +0 -0
  165. package/assets/icons/lock.png +0 -0
  166. package/assets/icons/lock@2x.png +0 -0
  167. package/assets/icons/lock@3x.png +0 -0
  168. package/assets/icons/menu.png +0 -0
  169. package/assets/icons/menu@2x.png +0 -0
  170. package/assets/icons/menu@3x.png +0 -0
  171. package/assets/icons/more.png +0 -0
  172. package/assets/icons/more@2x.png +0 -0
  173. package/assets/icons/more@3x.png +0 -0
  174. package/assets/icons/settings.png +0 -0
  175. package/assets/icons/settings@2x.png +0 -0
  176. package/assets/icons/settings@3x.png +0 -0
  177. package/assets/icons/view.png +0 -0
  178. package/assets/icons/view@2x.png +0 -0
  179. package/assets/icons/view@3x.png +0 -0
  180. package/assets/icons/x.png +0 -0
  181. package/assets/icons/x@2x.png +0 -0
  182. package/assets/icons/x@3x.png +0 -0
  183. package/assets/images/app-icon-all.png +0 -0
  184. package/assets/images/app-icon-android-adaptive-background.png +0 -0
  185. package/assets/images/app-icon-android-adaptive-foreground.png +0 -0
  186. package/assets/images/app-icon-android-legacy.png +0 -0
  187. package/assets/images/app-icon-ios.png +0 -0
  188. package/assets/images/app-icon-web-favicon.png +0 -0
  189. package/assets/images/demo/cr-logo.png +0 -0
  190. package/assets/images/demo/cr-logo@2x.png +0 -0
  191. package/assets/images/demo/cr-logo@3x.png +0 -0
  192. package/assets/images/demo/rnl-logo.png +0 -0
  193. package/assets/images/demo/rnl-logo@2x.png +0 -0
  194. package/assets/images/demo/rnl-logo@3x.png +0 -0
  195. package/assets/images/demo/rnn-logo.png +0 -0
  196. package/assets/images/demo/rnn-logo@2x.png +0 -0
  197. package/assets/images/demo/rnn-logo@3x.png +0 -0
  198. package/assets/images/demo/rnr-image-1.png +0 -0
  199. package/assets/images/demo/rnr-image-1@2x.png +0 -0
  200. package/assets/images/demo/rnr-image-1@3x.png +0 -0
  201. package/assets/images/demo/rnr-image-2.png +0 -0
  202. package/assets/images/demo/rnr-image-2@2x.png +0 -0
  203. package/assets/images/demo/rnr-image-2@3x.png +0 -0
  204. package/assets/images/demo/rnr-image-3.png +0 -0
  205. package/assets/images/demo/rnr-image-3@2x.png +0 -0
  206. package/assets/images/demo/rnr-image-3@3x.png +0 -0
  207. package/assets/images/demo/rnr-logo.png +0 -0
  208. package/assets/images/demo/rnr-logo@2x.png +0 -0
  209. package/assets/images/demo/rnr-logo@3x.png +0 -0
  210. package/assets/images/logo.png +0 -0
  211. package/assets/images/logo@2x.png +0 -0
  212. package/assets/images/logo@3x.png +0 -0
  213. package/assets/images/sad-face.png +0 -0
  214. package/assets/images/sad-face@2x.png +0 -0
  215. package/assets/images/sad-face@3x.png +0 -0
  216. package/assets/images/welcome-face.png +0 -0
  217. package/assets/images/welcome-face@2x.png +0 -0
  218. package/assets/images/welcome-face@3x.png +0 -0
  219. package/babel.config.js +7 -0
  220. package/bin/cli.js +196 -0
  221. package/ignite/templates/app-icon/android-adaptive-background.png +0 -0
  222. package/ignite/templates/app-icon/android-adaptive-foreground.png +0 -0
  223. package/ignite/templates/app-icon/android-legacy.png +0 -0
  224. package/ignite/templates/app-icon/ios-universal.png +0 -0
  225. package/ignite/templates/component/NAME.tsx.ejs +39 -0
  226. package/ignite/templates/navigator/NAMENavigator.tsx.ejs +18 -0
  227. package/ignite/templates/screen/NAMEScreen.tsx.ejs +29 -0
  228. package/ignite/templates/splash-screen/logo.png +0 -0
  229. package/index.tsx +9 -0
  230. package/jest.config.js +5 -0
  231. package/metro.config.js +31 -0
  232. package/package.json +166 -0
  233. package/plugins/withSplashScreen.ts +69 -0
  234. package/src/app/_layout.tsx +58 -0
  235. package/src/app/index.tsx +5 -0
  236. package/test/i18n.test.ts +75 -0
  237. package/test/mockFile.ts +6 -0
  238. package/test/setup.ts +58 -0
  239. package/test/test-tsconfig.json +8 -0
  240. package/tsconfig.json +52 -0
  241. package/types/lib.es5.d.ts +25 -0
@@ -0,0 +1,394 @@
1
+ import Parse from "@/lib/Parse";
2
+ import type { GoogleAuthResponse, PersistedUserData } from "./types";
3
+ import { validateEmail, validatePassword, validateUsername } from "./validation";
4
+
5
+ /**
6
+ * Authentication services
7
+ * Contains all Parse-related authentication operations
8
+ */
9
+
10
+ /**
11
+ * Lightweight probe to confirm the Parse backend is reachable
12
+ */
13
+ export const checkParseServerConnection = async (): Promise<boolean> => {
14
+ try {
15
+ const TestObject = Parse.Object.extend("TestConnection");
16
+ const query = new Parse.Query(TestObject);
17
+ query.limit(1);
18
+ await query.find();
19
+ return true;
20
+ } catch (error) {
21
+ console.error("Parse server connection check failed:", error);
22
+ return false;
23
+ }
24
+ };
25
+
26
+ /**
27
+ * Logs in a user with email and password
28
+ */
29
+ export const loginUser = async (
30
+ email: string,
31
+ password: string
32
+ ): Promise<{ success: boolean; error?: string; user?: Parse.User }> => {
33
+ try {
34
+ // 1) Validate inputs
35
+ const emailError = validateEmail(email);
36
+ const passwordError = validatePassword(password);
37
+
38
+ if (emailError || passwordError) {
39
+ const errorMessage = emailError || passwordError;
40
+ return { success: false, error: errorMessage };
41
+ }
42
+
43
+ console.log("Checking Parse server connection...");
44
+ const isServerRunning = await checkParseServerConnection();
45
+
46
+ if (!isServerRunning) {
47
+ const errorMessage =
48
+ "Parse server is not running. Please check your server connection.";
49
+ console.error("Server check failed:", errorMessage);
50
+ return { success: false, error: errorMessage };
51
+ }
52
+
53
+ // 2) Attempt Parse login
54
+ console.log("Parse server is running. Proceeding with login...");
55
+
56
+ const user = await Parse.User.logIn(email, password);
57
+ return { success: true, user };
58
+ } catch (error) {
59
+ let errorMessage = "Failed to log in";
60
+
61
+ if (error instanceof Error) {
62
+ if (error.message.includes("Invalid username/password")) {
63
+ errorMessage = "Invalid email or password. Please try again.";
64
+ } else if (
65
+ error.message.includes("XMLHttpRequest") ||
66
+ error.message.includes("Network Error") ||
67
+ error.message.includes("Failed to fetch") ||
68
+ error.message.includes("ECONNREFUSED")
69
+ ) {
70
+ errorMessage =
71
+ "Cannot connect to server. Please check if the Parse server is running.";
72
+ } else {
73
+ errorMessage = error.message;
74
+ }
75
+ }
76
+
77
+ console.error("Login error:", errorMessage);
78
+ return { success: false, error: errorMessage };
79
+ }
80
+ };
81
+
82
+ /**
83
+ * Signs up a new user
84
+ */
85
+ export const signUpUser = async (
86
+ username: string,
87
+ email: string,
88
+ password: string
89
+ ): Promise<{ success: boolean; error?: string; user?: Parse.User }> => {
90
+ try {
91
+ // 1) Validate inputs
92
+ const emailError = validateEmail(email);
93
+ const passwordError = validatePassword(password);
94
+ const usernameError = validateUsername(username);
95
+
96
+ if (emailError || passwordError || usernameError) {
97
+ const errorMessage = emailError || passwordError || usernameError;
98
+ return { success: false, error: errorMessage };
99
+ }
100
+
101
+ console.log("Checking Parse server connection...");
102
+ const isServerRunning = await checkParseServerConnection();
103
+
104
+ if (!isServerRunning) {
105
+ const errorMessage =
106
+ "Parse server is not running. Please check your server connection.";
107
+ console.error("Server check failed:", errorMessage);
108
+ return { success: false, error: errorMessage };
109
+ }
110
+
111
+ // 2) Create and sign up a new Parse user
112
+ console.log("Parse server is running. Proceeding with signup...");
113
+
114
+ const user = new Parse.User();
115
+ user.set("username", username.trim());
116
+ user.set("email", email);
117
+ user.set("password", password);
118
+
119
+ const newUser = await user.signUp();
120
+
121
+ console.log("Signup successful!");
122
+ return { success: true, user: newUser };
123
+ } catch (error) {
124
+ let errorMessage = "Failed to sign up";
125
+
126
+ if (error instanceof Error) {
127
+ if (error.message.includes("username") && error.message.includes("taken")) {
128
+ errorMessage = "Username is already taken. Please choose another one.";
129
+ } else if (error.message.includes("email") && error.message.includes("taken")) {
130
+ errorMessage = "Email is already registered. Please use another email or try logging in.";
131
+ } else if (error.message.includes("Account already exists")) {
132
+ errorMessage = "An account with this email already exists. Please try logging in.";
133
+ } else if (
134
+ error.message.includes("XMLHttpRequest") ||
135
+ error.message.includes("Network Error") ||
136
+ error.message.includes("Failed to fetch") ||
137
+ error.message.includes("ECONNREFUSED")
138
+ ) {
139
+ errorMessage =
140
+ "Cannot connect to server. Please check if the Parse server is running.";
141
+ } else {
142
+ errorMessage = error.message;
143
+ }
144
+ }
145
+
146
+ console.error("Signup error:", errorMessage);
147
+ return { success: false, error: errorMessage };
148
+ }
149
+ };
150
+
151
+ /**
152
+ * Signs in a user with Google OAuth
153
+ */
154
+ export const signInWithGoogle = async (
155
+ response: GoogleAuthResponse
156
+ ): Promise<{ success: boolean; error?: string; user?: Parse.User }> => {
157
+ try {
158
+ // 1) Validate OAuth response
159
+ if (response?.type !== "success") {
160
+ console.log("Google authentication was not successful");
161
+ return {
162
+ success: false,
163
+ error: "Google authentication was cancelled or failed",
164
+ };
165
+ }
166
+
167
+ if (
168
+ !response.authentication?.idToken ||
169
+ !response.authentication?.accessToken
170
+ ) {
171
+ console.error(
172
+ "Missing required authentication data in response",
173
+ response.authentication
174
+ );
175
+ return {
176
+ success: false,
177
+ error:
178
+ "Incomplete authentication data. Please try signing in again.",
179
+ };
180
+ }
181
+
182
+ // 2) Ensure backend is reachable
183
+ console.log("Checking Parse server connection...");
184
+ const isServerRunning = await checkParseServerConnection();
185
+
186
+ if (!isServerRunning) {
187
+ const errorMessage =
188
+ "Parse server is not running. Please check your server connection.";
189
+ console.error("Server check failed:", errorMessage);
190
+ return { success: false, error: errorMessage };
191
+ }
192
+
193
+ // 3) Fetch Google profile and authenticate with Parse using authData
194
+ console.log("Parse server is running. Proceeding with Google sign-in...");
195
+
196
+ const { idToken, accessToken } = response.authentication;
197
+
198
+ // Fetch Google user profile
199
+ const profileResponse = await fetch(
200
+ "https://www.googleapis.com/oauth2/v3/userinfo",
201
+ {
202
+ headers: { Authorization: `Bearer ${accessToken}` },
203
+ }
204
+ );
205
+
206
+ if (!profileResponse.ok) {
207
+ throw new Error(
208
+ `Failed to fetch user profile: ${profileResponse.status}`
209
+ );
210
+ }
211
+
212
+ const profile = await profileResponse.json();
213
+
214
+ const { sub: googleUserId, email, name, picture } = profile;
215
+
216
+ if (!googleUserId) {
217
+ throw new Error("Google ID (sub) not found in user profile");
218
+ }
219
+
220
+ if (!email || !name) {
221
+ throw new Error(
222
+ "Required user information missing from Google profile"
223
+ );
224
+ }
225
+
226
+ // Prepare Parse authentication data
227
+ const authData = {
228
+ id: googleUserId,
229
+ id_token: idToken,
230
+ access_token: accessToken,
231
+ };
232
+
233
+ // Authenticate with Parse
234
+ const user = await Parse.User.logInWith("google", { authData });
235
+
236
+ // Update user profile
237
+ user.set("username", name.trim());
238
+ user.set("email", email);
239
+
240
+ console.log("Google login successful:", {
241
+ userId: googleUserId,
242
+ email,
243
+ username: name,
244
+ hasPicture: Boolean(picture),
245
+ });
246
+
247
+ return { success: true, user };
248
+ } catch (error) {
249
+ let errorMessage = "Failed to sign in with Google";
250
+
251
+ if (error instanceof Error) {
252
+ if (
253
+ error.message.includes("XMLHttpRequest") ||
254
+ error.message.includes("Network Error") ||
255
+ error.message.includes("Failed to fetch") ||
256
+ error.message.includes("ECONNREFUSED")
257
+ ) {
258
+ errorMessage =
259
+ "Cannot connect to server. Please check if the Parse server is running.";
260
+ } else {
261
+ errorMessage = error.message;
262
+ }
263
+ }
264
+
265
+ console.error("Google sign-in error:", errorMessage);
266
+ return { success: false, error: errorMessage };
267
+ }
268
+ };
269
+
270
+ /**
271
+ * Requests a password reset for the given email
272
+ */
273
+ export const requestPasswordReset = async (
274
+ email: string
275
+ ): Promise<{ success: boolean; error?: string; message?: string }> => {
276
+ try {
277
+ // 1) Validate email
278
+ const emailError = validateEmail(email);
279
+ if (emailError) {
280
+ return { success: false, error: emailError };
281
+ }
282
+
283
+ // 2) Check server connection
284
+ console.log("Checking Parse server connection...");
285
+ const isServerRunning = await checkParseServerConnection();
286
+
287
+ if (!isServerRunning) {
288
+ const errorMessage =
289
+ "Parse server is not running. Please check your server connection.";
290
+ console.error("Server check failed:", errorMessage);
291
+ return { success: false, error: errorMessage };
292
+ }
293
+
294
+ // 3) Request password reset
295
+ console.log("Parse server is running. Requesting password reset...");
296
+ await Parse.User.requestPasswordReset(email);
297
+
298
+ const successMessage = `Password reset email sent to ${email}. Please check your inbox and follow the instructions to reset your password.`;
299
+
300
+ console.log("Password reset request successful for:", email);
301
+ return { success: true, message: successMessage };
302
+ } catch (error) {
303
+ let errorMessage = "Failed to request password reset";
304
+
305
+ if (error instanceof Error) {
306
+ if (error.message.includes("no user found with email")) {
307
+ errorMessage = "No account found with this email address.";
308
+ } else if (error.message.includes("email adapter")) {
309
+ errorMessage = "Email service is not configured. Please contact support.";
310
+ } else if (
311
+ error.message.includes("XMLHttpRequest") ||
312
+ error.message.includes("Network Error") ||
313
+ error.message.includes("Failed to fetch") ||
314
+ error.message.includes("ECONNREFUSED")
315
+ ) {
316
+ errorMessage =
317
+ "Cannot connect to server. Please check if the Parse server is running.";
318
+ } else {
319
+ errorMessage = error.message;
320
+ }
321
+ }
322
+
323
+ console.error("Password reset error:", errorMessage);
324
+ return { success: false, error: errorMessage };
325
+ }
326
+ };
327
+
328
+ /**
329
+ * Logs out the current user
330
+ */
331
+ export const logoutUser = async (): Promise<{
332
+ success: boolean;
333
+ error?: string;
334
+ }> => {
335
+ try {
336
+ // Attempt Parse logout (may fail silently on RN; that's OK)
337
+ try {
338
+ await Parse.User.logOut();
339
+ } catch (logoutError) {
340
+ console.warn(
341
+ "Parse.User.logOut() failed (common in React Native):",
342
+ logoutError
343
+ );
344
+ }
345
+
346
+ return { success: true };
347
+ } catch (error) {
348
+ const errorMessage =
349
+ error instanceof Error ? error.message : "Failed to log out";
350
+ console.error("Logout error:", errorMessage);
351
+ return { success: false, error: errorMessage };
352
+ }
353
+ };
354
+
355
+ /**
356
+ * Gets the current user from Parse
357
+ */
358
+ export const getCurrentUser = async (): Promise<Parse.User | null> => {
359
+ try {
360
+ return await Parse.User.currentAsync();
361
+ } catch (error) {
362
+ console.error("Error getting current user:", error);
363
+ return null;
364
+ }
365
+ };
366
+
367
+ /**
368
+ * Fetches all users from Parse
369
+ */
370
+ export const fetchAllUsers = async (): Promise<Parse.User[]> => {
371
+ try {
372
+ const query = new Parse.Query(Parse.User);
373
+ const results = await query.find();
374
+ return results;
375
+ } catch (error) {
376
+ console.error("Error fetching users:", error);
377
+ return [];
378
+ }
379
+ };
380
+
381
+ /**
382
+ * Restores a user session from a persisted token
383
+ */
384
+ export const restoreUserSession = async (
385
+ token: string
386
+ ): Promise<Parse.User | null> => {
387
+ try {
388
+ const user = await Parse.User.become(token);
389
+ return user;
390
+ } catch (error) {
391
+ console.error("Failed to restore user session:", error);
392
+ return null;
393
+ }
394
+ };
@@ -0,0 +1,99 @@
1
+ import Parse from "@/lib/Parse";
2
+ import type { TokenResponse } from "expo-auth-session";
3
+
4
+ /**
5
+ * Type definitions for authentication system
6
+ */
7
+
8
+ // Narrow Parse user shape we rely on across the UI
9
+ export interface IAppUser extends Parse.User {
10
+ getEmail(): string;
11
+ getUsername(): string;
12
+ getSessionToken(): string;
13
+ }
14
+
15
+ export interface GoogleAuthResponse {
16
+ type: "success" | "dismiss" | "cancel" | "opened" | "locked" | "error";
17
+ errorCode?: string | null;
18
+ error?: any;
19
+ params?: Record<string, string>;
20
+ authentication?:
21
+ | (TokenResponse & {
22
+ idToken?: string;
23
+ accessToken?: string;
24
+ })
25
+ | null;
26
+ url?: string;
27
+ }
28
+
29
+ // In-memory auth state kept in the provider
30
+ export interface AuthState {
31
+ authToken?: string;
32
+ authEmail: string;
33
+ authPassword: string;
34
+ isLoading: boolean;
35
+ error: string;
36
+ currentUser?: Parse.User;
37
+ username?: string;
38
+ resetPasswordMessage?: string;
39
+ }
40
+
41
+ // Reducer actions
42
+ export type AuthAction =
43
+ | { type: "SET_AUTH_EMAIL"; payload: string }
44
+ | { type: "SET_AUTH_PASSWORD"; payload: string }
45
+ | { type: "SET_ERROR"; payload: string }
46
+ | { type: "SET_AUTH_TOKEN"; payload: { token?: string } }
47
+ | { type: "SET_LOADING"; payload: boolean }
48
+ | { type: "SET_CURRENT_USER"; payload: Parse.User | undefined }
49
+ | { type: "SET_RESET_PASSWORD_MESSAGE"; payload: string }
50
+ | { type: "RESET_AUTH_STATE" }
51
+ | { type: "CLEAR_FORM" }
52
+ | { type: "CLEAR_RESET_MESSAGE" };
53
+
54
+ // Public API exposed via the Auth context
55
+ export type AuthContextType = {
56
+ // State
57
+ isAuthenticated: boolean;
58
+ authToken?: string;
59
+ authEmail: string;
60
+ authPassword: string;
61
+ isLoading: boolean;
62
+ error: string;
63
+ currentUser?: Parse.User;
64
+ username?: string;
65
+ resetPasswordMessage?: string;
66
+
67
+ // Actions
68
+ setAuthEmail: (email: string) => void;
69
+ setAuthPassword: (password: string) => void;
70
+ setError: (error: string) => void;
71
+ setAuthToken: (token?: string) => void;
72
+ resetAuthState: () => void;
73
+ clearForm: () => void;
74
+ clearResetMessage: () => void;
75
+ login: () => Promise<{ success: boolean; error?: string }>;
76
+ signUp: (username: string) => Promise<{ success: boolean; error?: string }>;
77
+ googleSignIn: (
78
+ response: GoogleAuthResponse
79
+ ) => Promise<{ success: boolean; error?: string; user?: Parse.User }>;
80
+ requestPasswordReset: (email: string) => Promise<{ success: boolean; error?: string; message?: string }>;
81
+ logout: () => Promise<{ success: boolean; error?: string }>;
82
+ checkCurrentUser: () => Promise<boolean>;
83
+ checkServerStatus: () => Promise<{ isRunning: boolean; message: string }>;
84
+
85
+ // Validation helpers
86
+ validateEmail: (email: string) => string | undefined;
87
+ validatePassword: (password: string) => string | undefined;
88
+ validateUsername: (username: string) => string | undefined;
89
+ };
90
+
91
+ export interface AuthProviderProps {}
92
+
93
+ // Persisted user data structure
94
+ export interface PersistedUserData {
95
+ objectId: string;
96
+ sessionToken: string;
97
+ username: string;
98
+ email: string;
99
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Validation utilities for authentication
3
+ */
4
+
5
+ /**
6
+ * Validates an email address
7
+ * @param email - The email address to validate
8
+ * @returns Error message if invalid, undefined if valid
9
+ */
10
+ export const validateEmail = (email: string): string | undefined => {
11
+ if (!email.length) return "Email can't be blank";
12
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
13
+ return "Must be a valid email address";
14
+ }
15
+ return undefined;
16
+ };
17
+
18
+ /**
19
+ * Validates a password
20
+ * @param password - The password to validate
21
+ * @returns Error message if invalid, undefined if valid
22
+ */
23
+ export const validatePassword = (password: string): string | undefined => {
24
+ if (!password.length) return "Password can't be blank";
25
+ if (password.length < 6) {
26
+ return "Password must be at least 6 characters";
27
+ }
28
+ return undefined;
29
+ };
30
+
31
+ /**
32
+ * Validates a username
33
+ * @param username - The username to validate
34
+ * @returns Error message if invalid, undefined if valid
35
+ */
36
+ export const validateUsername = (username: string): string | undefined => {
37
+ if (!username.trim().length) return "Username can't be blank";
38
+ if (username.trim().length < 3) {
39
+ return "Username must be at least 3 characters";
40
+ }
41
+ if (!/^[a-zA-Z0-9_]+$/.test(username.trim())) {
42
+ return "Username can only contain letters, numbers, and underscores";
43
+ }
44
+ return undefined;
45
+ };
@@ -0,0 +1,9 @@
1
+ /**
2
+ * This file is loaded in React Native and exports the RN version
3
+ * of Reactotron's client.
4
+ *
5
+ * Web is loaded from ReactotronClient.web.ts.
6
+ */
7
+ import Reactotron from "reactotron-react-native"
8
+
9
+ export { Reactotron }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * This file is loaded in web and exports the React.js version
3
+ * of Reactotron's client.
4
+ *
5
+ * React Native is loaded from ReactotronClient.ts.
6
+ *
7
+ * If your project does not need web support, you can delete this file and
8
+ * remove reactotron-react-js from your package.json dependencies.
9
+ */
10
+ import Reactotron from "reactotron-react-js"
11
+
12
+ export { Reactotron }
@@ -0,0 +1,139 @@
1
+ /**
2
+ * This file does the setup for integration with Reactotron, which is a
3
+ * free desktop app for inspecting and debugging your React Native app.
4
+ * @see https://github.com/infinitered/reactotron
5
+ */
6
+ import { Platform, NativeModules } from "react-native"
7
+ import { ArgType } from "reactotron-core-client"
8
+ import { ReactotronReactNative } from "reactotron-react-native"
9
+ import mmkvPlugin from "reactotron-react-native-mmkv"
10
+
11
+ import { goBack, resetRoot, navigate } from "@/navigators/navigationUtilities"
12
+ import { storage } from "@/utils/storage"
13
+
14
+ import { Reactotron } from "./ReactotronClient"
15
+
16
+ const reactotron = Reactotron.configure({
17
+ name: require("../../package.json").name,
18
+ onConnect: () => {
19
+ /** since this file gets hot reloaded, let's clear the past logs every time we connect */
20
+ Reactotron.clear()
21
+ },
22
+ })
23
+
24
+ reactotron.use(mmkvPlugin<ReactotronReactNative>({ storage }))
25
+
26
+ if (Platform.OS !== "web") {
27
+ reactotron.useReactNative({
28
+ networking: {
29
+ ignoreUrls: /symbolicate/,
30
+ },
31
+ })
32
+ }
33
+
34
+ /**
35
+ * Reactotron allows you to define custom commands that you can run
36
+ * from Reactotron itself, and they will run in your app.
37
+ *
38
+ * Define them in the section below with `onCustomCommand`. Use your
39
+ * creativity -- this is great for development to quickly and easily
40
+ * get your app into the state you want.
41
+ *
42
+ * NOTE: If you edit this file while running the app, you will need to do a full refresh
43
+ * or else your custom commands won't be registered correctly.
44
+ */
45
+ reactotron.onCustomCommand({
46
+ title: "Show Dev Menu",
47
+ description: "Opens the React Native dev menu",
48
+ command: "showDevMenu",
49
+ handler: () => {
50
+ Reactotron.log("Showing React Native dev menu")
51
+ NativeModules.DevMenu.show()
52
+ },
53
+ })
54
+
55
+ reactotron.onCustomCommand({
56
+ title: "Reset Navigation State",
57
+ description: "Resets the navigation state",
58
+ command: "resetNavigation",
59
+ handler: () => {
60
+ Reactotron.log("resetting navigation state")
61
+ resetRoot({ index: 0, routes: [] })
62
+ },
63
+ })
64
+
65
+ reactotron.onCustomCommand<[{ name: "route"; type: ArgType.String }]>({
66
+ command: "navigateTo",
67
+ handler: (args) => {
68
+ const { route } = args ?? {}
69
+ if (route) {
70
+ Reactotron.log(`Navigating to: ${route}`)
71
+ // @ts-ignore
72
+ navigate(route as any) // this should be tied to the navigator, but since this is for debugging, we can navigate to illegal routes
73
+ } else {
74
+ Reactotron.log("Could not navigate. No route provided.")
75
+ }
76
+ },
77
+ title: "Navigate To Screen",
78
+ description: "Navigates to a screen by name.",
79
+ args: [{ name: "route", type: ArgType.String }],
80
+ })
81
+
82
+ reactotron.onCustomCommand({
83
+ title: "Go Back",
84
+ description: "Goes back",
85
+ command: "goBack",
86
+ handler: () => {
87
+ Reactotron.log("Going back")
88
+ goBack()
89
+ },
90
+ })
91
+
92
+ /**
93
+ * We're going to add `console.tron` to the Reactotron object.
94
+ * Now, anywhere in our app in development, we can use Reactotron like so:
95
+ *
96
+ * ```
97
+ * if (__DEV__) {
98
+ * console.tron.display({
99
+ * name: 'JOKE',
100
+ * preview: 'What's the best thing about Switzerland?',
101
+ * value: 'I don't know, but the flag is a big plus!',
102
+ * important: true
103
+ * })
104
+ * }
105
+ * ```
106
+ *
107
+ * Use this power responsibly! :)
108
+ */
109
+ console.tron = reactotron
110
+
111
+ /**
112
+ * We tell typescript about our dark magic
113
+ *
114
+ * You can also import Reactotron yourself from ./reactotronClient
115
+ * and use it directly, like Reactotron.log('hello world')
116
+ */
117
+ declare global {
118
+ interface Console {
119
+ /**
120
+ * Reactotron client for logging, displaying, measuring performance, and more.
121
+ * @see https://github.com/infinitered/reactotron
122
+ * @example
123
+ * if (__DEV__) {
124
+ * console.tron.display({
125
+ * name: 'JOKE',
126
+ * preview: 'What's the best thing about Switzerland?',
127
+ * value: 'I don't know, but the flag is a big plus!',
128
+ * important: true
129
+ * })
130
+ * }
131
+ */
132
+ tron: typeof reactotron
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Now that we've setup all our Reactotron configuration, let's connect!
138
+ */
139
+ reactotron.connect()