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,180 @@
1
+ import React, { FC, useState, useEffect } from "react";
2
+ import { observer } from "mobx-react-lite";
3
+ import { ViewStyle, TextStyle, View, Image, ImageStyle } from "react-native";
4
+ import type { AppStackScreenProps } from "@/navigators/AppNavigator"
5
+ import { Button, Screen, Text, TextField } from "@/components";
6
+ import { useAuth } from "@/context/AuthContext";
7
+
8
+ const logo = require("@assets/images/logo.png");
9
+
10
+ interface ForgetPasswordScreenProps
11
+ extends AppStackScreenProps<"ForgetPassword"> {}
12
+
13
+ export const ForgetPasswordScreen: FC<ForgetPasswordScreenProps> = observer(
14
+ function ForgetPasswordScreen({ navigation }) {
15
+ const {
16
+ requestPasswordReset,
17
+ validateEmail,
18
+ isLoading,
19
+ error,
20
+ resetPasswordMessage,
21
+ clearResetMessage,
22
+ setError
23
+ } = useAuth();
24
+
25
+ const [email, setEmail] = useState("");
26
+ const [emailError, setEmailError] = useState<string | undefined>();
27
+
28
+ // Clear any existing messages when component mounts
29
+ useEffect(() => {
30
+ clearResetMessage();
31
+ setError("");
32
+ }, [clearResetMessage, setError]);
33
+
34
+ // Handle reset password submission
35
+ const resetPassword = async () => {
36
+ // Clear previous errors
37
+ setEmailError(undefined);
38
+
39
+ // Validate email
40
+ const emailValidationError = validateEmail(email);
41
+ setEmailError(emailValidationError);
42
+
43
+ if (emailValidationError) {
44
+ return;
45
+ }
46
+
47
+ // Request password reset
48
+ const result = await requestPasswordReset(email);
49
+
50
+ if (result.success) {
51
+ // Email is cleared after successful request
52
+ setEmail("");
53
+ }
54
+ };
55
+
56
+ const goBack = () => {
57
+ navigation.goBack();
58
+ };
59
+
60
+ return (
61
+ <Screen
62
+ style={$root}
63
+ preset="scroll"
64
+ safeAreaEdges={["top", "bottom"]}
65
+ >
66
+ <View style={$topContainer}>
67
+ <Image style={$logoImage} source={logo} resizeMode="contain" />
68
+ <Text
69
+ text="Forgot Password"
70
+ size="xl"
71
+ preset="heading"
72
+ style={$welcomeText}
73
+ />
74
+ <Text
75
+ text="Enter your email to receive a password reset link"
76
+ preset="default"
77
+ style={$termsText}
78
+ />
79
+ </View>
80
+
81
+ <View style={$formContainer}>
82
+ <TextField
83
+ value={email}
84
+ onChangeText={setEmail}
85
+ containerStyle={$textField}
86
+ autoCapitalize="none"
87
+ autoComplete="email"
88
+ autoCorrect={false}
89
+ keyboardType="email-address"
90
+ label="Email"
91
+ placeholder="Enter email"
92
+ helper={emailError}
93
+ status={emailError ? "error" : undefined}
94
+ onSubmitEditing={resetPassword}
95
+ editable={!isLoading}
96
+ />
97
+
98
+ {error && <Text style={$errorText} text={error} />}
99
+ {resetPasswordMessage && (
100
+ <Text style={$successText} text={resetPasswordMessage} />
101
+ )}
102
+
103
+ <Button
104
+ testID="reset-password-button"
105
+ text={isLoading ? "Sending..." : "Request New Password"}
106
+ style={$resetButton}
107
+ preset="reversed"
108
+ onPress={resetPassword}
109
+ disabled={isLoading}
110
+ />
111
+
112
+ <Button
113
+ testID="back-button"
114
+ text="Back to Login"
115
+ style={$backButton}
116
+ preset="default"
117
+ onPress={goBack}
118
+ disabled={isLoading}
119
+ />
120
+ </View>
121
+ </Screen>
122
+ );
123
+ }
124
+ );
125
+
126
+ const $root: ViewStyle = {
127
+ flex: 1,
128
+ paddingHorizontal: 24,
129
+ };
130
+
131
+ const $topContainer: ViewStyle = {
132
+ alignItems: "center",
133
+ marginTop: 50,
134
+ marginBottom: 20,
135
+ };
136
+
137
+ const $logoImage: ImageStyle = {
138
+ height: 100,
139
+ width: 200,
140
+ marginBottom: 10,
141
+ };
142
+
143
+ const $welcomeText: TextStyle = {
144
+ marginBottom: 10,
145
+ textAlign: "center",
146
+ };
147
+
148
+ const $termsText: TextStyle = {
149
+ marginBottom: 20,
150
+ textAlign: "center",
151
+ };
152
+
153
+ const $formContainer: ViewStyle = {
154
+ flex: 1,
155
+ };
156
+
157
+ const $textField: ViewStyle = {
158
+ marginBottom: 16,
159
+ };
160
+
161
+ const $errorText: TextStyle = {
162
+ color: "red",
163
+ marginBottom: 16,
164
+ textAlign: "center",
165
+ };
166
+
167
+ const $successText: TextStyle = {
168
+ color: "green",
169
+ marginBottom: 16,
170
+ textAlign: "center",
171
+ };
172
+
173
+ const $resetButton: ViewStyle = {
174
+ marginTop: 10,
175
+ marginBottom: 16,
176
+ };
177
+
178
+ const $backButton: ViewStyle = {
179
+ marginBottom: 16,
180
+ };
@@ -0,0 +1,260 @@
1
+ import { ComponentType, FC, useEffect, useMemo, useRef, useState } from "react";
2
+ // eslint-disable-next-line no-restricted-imports
3
+ import { TextInput, TextStyle, ViewStyle, Alert } from "react-native";
4
+
5
+ import { Button } from "@/components/Button";
6
+ import { PressableIcon } from "@/components/Icon";
7
+ import { Screen } from "@/components/Screen";
8
+ import { Text } from "@/components/Text";
9
+ import { type TextFieldAccessoryProps, TextField } from "@/components/TextField";
10
+ import type { AppStackScreenProps } from "@/navigators/AppNavigator";
11
+ import type { ThemedStyle } from "@/theme/types";
12
+ import { useAppTheme } from "@/theme/context";
13
+ import { useHeader } from "@/utils/useHeader";
14
+ import { useAuth } from "@/context/AuthContext";
15
+
16
+ interface LoginScreenProps extends AppStackScreenProps<"Login"> {}
17
+
18
+ export const LoginScreen: FC<LoginScreenProps> = ({ navigation }) => {
19
+ const authPasswordInput = useRef<TextInput>(null);
20
+ const [isAuthPasswordHidden, setIsAuthPasswordHidden] = useState(true);
21
+
22
+ // Use auth context instead of local state and separate hook
23
+ const {
24
+ authEmail,
25
+ authPassword,
26
+ setAuthEmail,
27
+ setAuthPassword,
28
+ validateEmail,
29
+ validatePassword,
30
+ isLoading,
31
+ error,
32
+ login,
33
+ resetAuthState,
34
+ isAuthenticated
35
+ } = useAuth();
36
+
37
+ const {
38
+ themed,
39
+ theme: { colors },
40
+ } = useAppTheme();
41
+
42
+ // Navigate to authenticated area if login successful
43
+ useEffect(() => {
44
+ if (isAuthenticated) {
45
+ navigation.navigate("Welcome"); // or wherever you want to redirect after login
46
+ }
47
+ }, [isAuthenticated, navigation]);
48
+
49
+ // useEffect(() => {
50
+ // // Optional: Pre-fill form fields for development
51
+ // // Remove these in production
52
+ // setAuthEmail("test@example.com");
53
+ // setAuthPassword("password123");
54
+ // }, [setAuthEmail, setAuthPassword]);
55
+
56
+ const register = () => {
57
+ navigation.navigate("Register");
58
+ resetAuthState(); // Clear form fields using context method
59
+ };
60
+
61
+ useHeader({
62
+ leftIcon: "back",
63
+ onLeftPress: () => {
64
+ navigation.navigate("ChooseAuth");
65
+ },
66
+ });
67
+
68
+
69
+ const forgetPassword = () => {
70
+ navigation.navigate("ForgetPassword");
71
+ };
72
+
73
+ const handleLogin = async () => {
74
+ // Validate inputs before attempting login
75
+ const emailError = validateEmail(authEmail);
76
+ const passwordError = validatePassword(authPassword);
77
+
78
+ if (emailError || passwordError) {
79
+ return; // Validation errors will be shown in the UI
80
+ }
81
+
82
+ try {
83
+ const result = await login();
84
+
85
+ if (result.success) {
86
+ // Navigation handled by useEffect above
87
+ console.log("Login successful!");
88
+ } else {
89
+ // Error is already set in the context, will be displayed in UI
90
+ Alert.alert(
91
+ "Login Failed",
92
+ result.error || "An unexpected error occurred during login."
93
+ );
94
+ }
95
+ } catch (error) {
96
+ console.error("Login error:", error);
97
+ Alert.alert(
98
+ "Error",
99
+ error instanceof Error ? error.message : "An unexpected error occurred. Please try again."
100
+ );
101
+ }
102
+ };
103
+
104
+ // Compute validation errors
105
+ const emailValidationError = authEmail ? validateEmail(authEmail) : undefined;
106
+ const passwordValidationError = authPassword ? validatePassword(authPassword) : undefined;
107
+ const hasValidationErrors = !!(emailValidationError || passwordValidationError);
108
+
109
+ const PasswordRightAccessory: ComponentType<TextFieldAccessoryProps> = useMemo(
110
+ () =>
111
+ function PasswordRightAccessory(props: TextFieldAccessoryProps) {
112
+ return (
113
+ <PressableIcon
114
+ icon={isAuthPasswordHidden ? "view" : "hidden"}
115
+ color={colors.palette.neutral800}
116
+ containerStyle={props.style}
117
+ size={20}
118
+ onPress={() => setIsAuthPasswordHidden(!isAuthPasswordHidden)}
119
+ />
120
+ );
121
+ },
122
+ [isAuthPasswordHidden, colors.palette.neutral800]
123
+ );
124
+
125
+ return (
126
+ <Screen
127
+ preset="auto"
128
+ contentContainerStyle={themed($screenContentContainer)}
129
+ safeAreaEdges={["bottom"]}
130
+ >
131
+ <Text
132
+ testID="login-heading"
133
+ tx="loginScreen:logIn"
134
+ preset="heading"
135
+ style={themed($logIn)}
136
+ />
137
+ <Text
138
+ tx="loginScreen:enterDetails"
139
+ preset="subheading"
140
+ style={themed($enterDetails)}
141
+ />
142
+
143
+ <Text tx="loginScreen:toRegister" style={themed($loginText)} />
144
+ <Text
145
+ text="Register"
146
+ style={[themed($loginText), themed($loginLink)]}
147
+ onPress={register}
148
+ />
149
+
150
+ {/* Show server connection errors */}
151
+ {error && (
152
+ <Text
153
+ text={error}
154
+ size="sm"
155
+ weight="light"
156
+ style={[themed($hint), { color: colors.error }]}
157
+ />
158
+ )}
159
+
160
+ <TextField
161
+ value={authEmail}
162
+ onChangeText={setAuthEmail}
163
+ containerStyle={themed($textField)}
164
+ autoCapitalize="none"
165
+ autoComplete="email"
166
+ autoCorrect={false}
167
+ keyboardType="email-address"
168
+ labelTx="loginScreen:emailFieldLabel"
169
+ placeholderTx="loginScreen:emailFieldPlaceholder"
170
+ helper={emailValidationError}
171
+ status={emailValidationError ? "error" : undefined}
172
+ onSubmitEditing={() => authPasswordInput.current?.focus()}
173
+ editable={!isLoading}
174
+ />
175
+
176
+ <TextField
177
+ ref={authPasswordInput}
178
+ value={authPassword}
179
+ onChangeText={setAuthPassword}
180
+ containerStyle={themed($textField)}
181
+ autoCapitalize="none"
182
+ autoComplete="password"
183
+ autoCorrect={false}
184
+ secureTextEntry={isAuthPasswordHidden}
185
+ labelTx="loginScreen:passwordFieldLabel"
186
+ placeholderTx="loginScreen:passwordFieldPlaceholder"
187
+ helper={passwordValidationError}
188
+ status={passwordValidationError ? "error" : undefined}
189
+ onSubmitEditing={handleLogin}
190
+ RightAccessory={PasswordRightAccessory}
191
+ editable={!isLoading}
192
+ />
193
+
194
+ <Text
195
+ text="Forgot Password?"
196
+ style={$forgotPasswordText}
197
+ onPress={forgetPassword}
198
+ />
199
+
200
+ <Button
201
+ testID="login-button"
202
+ tx={isLoading ? "common:loading" : "loginScreen:tapToLogIn"}
203
+ text={isLoading ? "Signing in..." : undefined}
204
+ style={themed($tapButton)}
205
+ preset="reversed"
206
+ onPress={handleLogin}
207
+ disabled={isLoading || hasValidationErrors}
208
+ />
209
+
210
+ {/* Optional: Add loading indicator */}
211
+ {isLoading && (
212
+ <Text
213
+ text="Connecting to server..."
214
+ style={[themed($hint), { textAlign: "center" }]}
215
+ />
216
+ )}
217
+ </Screen>
218
+ );
219
+ };
220
+
221
+ const $screenContentContainer: ThemedStyle<ViewStyle> = ({ spacing }) => ({
222
+ paddingVertical: spacing.xxl,
223
+ paddingHorizontal: spacing.lg,
224
+ });
225
+
226
+ const $logIn: ThemedStyle<TextStyle> = ({ spacing }) => ({
227
+ marginBottom: spacing.sm,
228
+ });
229
+
230
+ const $enterDetails: ThemedStyle<TextStyle> = ({ spacing }) => ({
231
+ marginBottom: spacing.lg,
232
+ });
233
+
234
+ const $hint: ThemedStyle<TextStyle> = ({ colors, spacing }) => ({
235
+ color: colors.tint,
236
+ marginBottom: spacing.md,
237
+ });
238
+
239
+ const $textField: ThemedStyle<ViewStyle> = ({ spacing }) => ({
240
+ marginBottom: spacing.lg,
241
+ });
242
+
243
+ const $tapButton: ThemedStyle<ViewStyle> = ({ spacing }) => ({
244
+ marginTop: spacing.xs,
245
+ });
246
+
247
+ const $loginText: ThemedStyle<TextStyle> = ({ colors, spacing }) => ({
248
+ textAlign: "center",
249
+ });
250
+
251
+ const $loginLink: ThemedStyle<TextStyle> = ({ colors, spacing }) => ({
252
+ color: colors.tint,
253
+ marginBottom: spacing.md,
254
+ });
255
+
256
+ const $forgotPasswordText: TextStyle = {
257
+ textAlign: "center",
258
+ marginBottom: 24,
259
+ color: "#4870FF",
260
+ };