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,395 @@
1
+ import {
2
+ FC,
3
+ useRef,
4
+ useState,
5
+ useEffect,
6
+ useMemo,
7
+ useCallback,
8
+ } from "react";
9
+ import { Alert, TextInput, TextStyle, ViewStyle } from "react-native";
10
+ import type { AppStackScreenProps } from "@/navigators/AppNavigator";
11
+ import {
12
+ Button,
13
+ PressableIcon,
14
+ Screen,
15
+ Text,
16
+ TextField,
17
+ TextFieldAccessoryProps,
18
+ } from "@/components";
19
+ import { useHeader } from "@/utils/useHeader";
20
+ import type { ThemedStyle } from "@/theme/types";
21
+ import { useAppTheme } from "@/theme/context";
22
+ import { useAuth } from "@/context/AuthContext";
23
+
24
+ interface RegisterScreenProps extends AppStackScreenProps<"Register"> {}
25
+
26
+ // Validation functions
27
+ const usernameValidator = (username: string): string | undefined => {
28
+ if (!username.length) return "Please enter a username";
29
+ if (username.length < 3) return "Username must be at least 3 characters";
30
+ return undefined;
31
+ };
32
+
33
+ const passwordConfirmValidator = (password: string): string | undefined => {
34
+ if (!password.length) return "Please confirm password";
35
+ return undefined;
36
+ };
37
+
38
+ export const RegisterScreen: FC<RegisterScreenProps> = ({ navigation }) => {
39
+ const authPasswordInput = useRef<TextInput>(null);
40
+ const authPasswordConfirmInput = useRef<TextInput>(null);
41
+ const usernameInput = useRef<TextInput>(null);
42
+
43
+ // Local state
44
+ const [username, setUsername] = useState("");
45
+ const [authPasswordConfirm, setAuthPasswordConfirm] = useState("");
46
+ const [isAuthPasswordHidden, setIsAuthPasswordHidden] = useState(true);
47
+ const [isPasswordConfirmHidden, setIsPasswordConfirmHidden] = useState(true);
48
+
49
+ // Individual field errors
50
+ const [emailError, setEmailError] = useState<string | undefined>();
51
+ const [passwordError, setPasswordError] = useState<string | undefined>();
52
+ const [passwordConfirmError, setPasswordConfirmError] = useState<string | undefined>();
53
+ const [usernameError, setUsernameError] = useState<string | undefined>();
54
+
55
+ // Use auth context
56
+ const {
57
+ authEmail,
58
+ authPassword,
59
+ setAuthEmail,
60
+ setAuthPassword,
61
+ isLoading,
62
+ error,
63
+ signUp,
64
+ resetAuthState,
65
+ isAuthenticated,
66
+ validateEmail,
67
+ validatePassword
68
+ } = useAuth();
69
+
70
+ const {
71
+ themed,
72
+ theme: { colors },
73
+ } = useAppTheme();
74
+
75
+ // Navigate to authenticated area if signup successful
76
+ useEffect(() => {
77
+ if (isAuthenticated) {
78
+ console.log('works')
79
+ navigation.navigate("Demo");
80
+ }
81
+ }, [isAuthenticated, navigation]);
82
+
83
+ // Password confirmation validation
84
+ useEffect(() => {
85
+ if (authPassword.length > 1 && authPasswordConfirm.length > 1) {
86
+ if (authPassword !== authPasswordConfirm) {
87
+ setPasswordConfirmError("Passwords do not match");
88
+ } else {
89
+ setPasswordConfirmError(undefined);
90
+ }
91
+ }
92
+ }, [authPassword, authPasswordConfirm]);
93
+
94
+ const goBack = () => {
95
+ navigation.goBack();
96
+ resetAuthState();
97
+ setUsername("");
98
+ setAuthPasswordConfirm("");
99
+ };
100
+
101
+ useHeader({
102
+
103
+ leftIcon: "back",
104
+ onLeftPress: goBack,
105
+ });
106
+
107
+ const toLogin = () => {
108
+ navigation.goBack();
109
+ resetAuthState();
110
+ setUsername("");
111
+ setAuthPasswordConfirm("");
112
+ };
113
+
114
+ const handleRegister = async () => {
115
+ try {
116
+ // Reset error states
117
+ setEmailError(undefined);
118
+ setPasswordError(undefined);
119
+ setPasswordConfirmError(undefined);
120
+ setUsernameError(undefined);
121
+
122
+ let hasErrors = false;
123
+
124
+ // Validate all fields
125
+ const emailValidationError = validateEmail(authEmail);
126
+ if (emailValidationError) {
127
+ setEmailError(emailValidationError);
128
+ hasErrors = true;
129
+ }
130
+
131
+ const passwordValidationError = validatePassword(authPassword);
132
+ if (passwordValidationError) {
133
+ setPasswordError(passwordValidationError);
134
+ hasErrors = true;
135
+ }
136
+
137
+ // Validate username
138
+ const usernameValidationError = usernameValidator(username);
139
+ if (usernameValidationError) {
140
+ setUsernameError(usernameValidationError);
141
+ hasErrors = true;
142
+ }
143
+
144
+ // Validate password confirmation
145
+ const passwordConfirmValidationError = passwordConfirmValidator(authPasswordConfirm);
146
+ if (passwordConfirmValidationError) {
147
+ setPasswordConfirmError(passwordConfirmValidationError);
148
+ hasErrors = true;
149
+ }
150
+
151
+ // Check password match
152
+ if (authPassword !== authPasswordConfirm) {
153
+ setPasswordConfirmError("Passwords do not match");
154
+ hasErrors = true;
155
+ }
156
+
157
+ if (hasErrors) {
158
+ Alert.alert(
159
+ "Validation Error",
160
+ "Please check your input and try again",
161
+ [{ text: "OK", style: "cancel" }]
162
+ );
163
+ return;
164
+ }
165
+
166
+ console.log("All validations passed, proceeding with registration");
167
+
168
+ // Attempt registration
169
+ const result = await signUp(username);
170
+
171
+ if (result.success) {
172
+ console.log("Registration successful");
173
+
174
+ // Reset form fields on success
175
+ resetAuthState();
176
+ setUsername("");
177
+ setAuthPasswordConfirm("");
178
+
179
+ Alert.alert("Success", "Registration completed successfully!", [
180
+ { text: "OK" },
181
+ ]);
182
+ } else {
183
+ // Registration failed - keep form fields populated for retry
184
+ const errorMessage = result.error || "Registration failed. Please try again.";
185
+ console.error("Registration failed:", errorMessage);
186
+
187
+ Alert.alert("Registration Failed", errorMessage, [
188
+ { text: "OK" },
189
+ ]);
190
+ }
191
+ } catch (error) {
192
+ const errorMessage =
193
+ error instanceof Error
194
+ ? error.message
195
+ : "An unexpected error occurred during registration";
196
+
197
+ console.error("Registration error:", errorMessage);
198
+
199
+ Alert.alert("Registration Error", errorMessage, [
200
+ { text: "OK" },
201
+ ]);
202
+ }
203
+ };
204
+
205
+ // Create password toggle function
206
+ const createPasswordToggle = useCallback(
207
+ (isHidden: boolean, setIsHidden: (hidden: boolean) => void) =>
208
+ function PasswordToggle(props: TextFieldAccessoryProps) {
209
+ return (
210
+ <PressableIcon
211
+ icon={isHidden ? "view" : "hidden"}
212
+ color={colors.palette.neutral800}
213
+ containerStyle={props.style}
214
+ size={20}
215
+ onPress={() => setIsHidden(!isHidden)}
216
+ />
217
+ );
218
+ },
219
+ [colors.palette.neutral800]
220
+ );
221
+
222
+ // Memoized accessories for both password fields
223
+ const PasswordRightAccessory = useMemo(
224
+ () => createPasswordToggle(isAuthPasswordHidden, setIsAuthPasswordHidden),
225
+ [isAuthPasswordHidden, createPasswordToggle]
226
+ );
227
+
228
+ const PasswordConfirmRightAccessory = useMemo(
229
+ () => createPasswordToggle(isPasswordConfirmHidden, setIsPasswordConfirmHidden),
230
+ [isPasswordConfirmHidden, createPasswordToggle]
231
+ );
232
+
233
+ return (
234
+ <Screen
235
+ style={$root}
236
+ preset="auto"
237
+ safeAreaEdges={[ "bottom"]}
238
+ contentContainerStyle={themed($screenContentContainer)}
239
+ >
240
+ <Text
241
+ testID="register-heading"
242
+ preset="heading"
243
+ tx="signUpScreen:signUp"
244
+ style={themed($signUp)}
245
+ />
246
+ <Text
247
+ preset="subheading"
248
+ tx="signUpScreen:enterDetails"
249
+ style={themed($enterDetails)}
250
+ />
251
+ <Text
252
+ tx="signUpScreen:ToLogIn"
253
+ style={[themed($signUpText), { marginTop: 12 }]}
254
+ />
255
+ <Text
256
+ text="Login"
257
+ style={[themed($signUpLink), themed($signUpText)]}
258
+ onPress={toLogin}
259
+ />
260
+
261
+ {/* Show server connection errors */}
262
+ {error && (
263
+ <Text
264
+ text={error}
265
+ size="sm"
266
+ weight="light"
267
+ style={[themed($hint), { color: colors.error }]}
268
+ />
269
+ )}
270
+
271
+ <TextField
272
+ value={authEmail}
273
+ onChangeText={setAuthEmail}
274
+ containerStyle={themed($textField)}
275
+ autoCapitalize="none"
276
+ autoComplete="email"
277
+ autoCorrect={false}
278
+ keyboardType="email-address"
279
+ labelTx="loginScreen:emailFieldLabel"
280
+ placeholderTx="loginScreen:emailFieldPlaceholder"
281
+ helper={emailError}
282
+ status={emailError ? "error" : undefined}
283
+ onSubmitEditing={() => usernameInput.current?.focus()}
284
+ editable={!isLoading}
285
+ />
286
+
287
+ <TextField
288
+ ref={usernameInput}
289
+ value={username}
290
+ onChangeText={setUsername}
291
+ containerStyle={themed($textField)}
292
+ autoCapitalize="none"
293
+ autoComplete="username"
294
+ autoCorrect={false}
295
+ label="Username"
296
+ placeholder="Enter your username"
297
+ helper={usernameError}
298
+ status={usernameError ? "error" : undefined}
299
+ onSubmitEditing={() => authPasswordInput.current?.focus()}
300
+ editable={!isLoading}
301
+ />
302
+
303
+ <TextField
304
+ ref={authPasswordInput}
305
+ value={authPassword}
306
+ onChangeText={setAuthPassword}
307
+ containerStyle={themed($textField)}
308
+ autoCapitalize="none"
309
+ autoComplete="password"
310
+ autoCorrect={false}
311
+ secureTextEntry={isAuthPasswordHidden}
312
+ labelTx="loginScreen:passwordFieldLabel"
313
+ placeholderTx="loginScreen:passwordFieldPlaceholder"
314
+ helper={passwordError}
315
+ status={passwordError ? "error" : undefined}
316
+ onSubmitEditing={() => authPasswordConfirmInput.current?.focus()}
317
+ RightAccessory={PasswordRightAccessory}
318
+ editable={!isLoading}
319
+ />
320
+
321
+ <TextField
322
+ ref={authPasswordConfirmInput}
323
+ value={authPasswordConfirm}
324
+ onChangeText={setAuthPasswordConfirm}
325
+ containerStyle={themed($textField)}
326
+ autoCapitalize="none"
327
+ autoComplete="password"
328
+ autoCorrect={false}
329
+ secureTextEntry={isPasswordConfirmHidden}
330
+ labelTx="loginScreen:passwordFieldLabelConf"
331
+ placeholderTx="loginScreen:passwordFieldPlaceholder"
332
+ helper={passwordConfirmError}
333
+ status={passwordConfirmError ? "error" : undefined}
334
+ onSubmitEditing={handleRegister}
335
+ RightAccessory={PasswordConfirmRightAccessory}
336
+ editable={!isLoading}
337
+ />
338
+
339
+ <Button
340
+ testID="register-button"
341
+ text={isLoading ? "Creating Account..." : "Let's Go"}
342
+ style={themed($tapButton)}
343
+ preset="reversed"
344
+ onPress={handleRegister}
345
+ disabled={isLoading}
346
+ />
347
+
348
+ {isLoading && (
349
+ <Text
350
+ text="Creating your account..."
351
+ style={[themed($hint), { textAlign: "center" }]}
352
+ />
353
+ )}
354
+ </Screen>
355
+ );
356
+ };
357
+
358
+ const $root: ViewStyle = {
359
+ flex: 1,
360
+ };
361
+
362
+ const $screenContentContainer: ThemedStyle<ViewStyle> = ({ spacing }) => ({
363
+ paddingVertical: spacing.md,
364
+ paddingHorizontal: spacing.lg,
365
+ });
366
+
367
+ const $enterDetails: ThemedStyle<TextStyle> = ({ spacing }) => ({
368
+ marginBottom: spacing.sm,
369
+ });
370
+
371
+ const $signUp: ThemedStyle<TextStyle> = ({ spacing }) => ({
372
+ marginBottom: spacing.sm,
373
+ });
374
+
375
+ const $hint: ThemedStyle<TextStyle> = ({ colors, spacing }) => ({
376
+ color: colors.tint,
377
+ marginBottom: spacing.md,
378
+ });
379
+
380
+ const $signUpText: ThemedStyle<TextStyle> = ({ colors, spacing }) => ({
381
+ textAlign: "center",
382
+ });
383
+
384
+ const $signUpLink: ThemedStyle<TextStyle> = ({ colors, spacing }) => ({
385
+ color: colors.tint,
386
+ marginBottom: spacing.md,
387
+ });
388
+
389
+ const $textField: ThemedStyle<ViewStyle> = ({ spacing }) => ({
390
+ marginBottom: spacing.lg,
391
+ });
392
+
393
+ const $tapButton: ThemedStyle<ViewStyle> = ({ spacing }) => ({
394
+ marginTop: spacing.xs,
395
+ });
@@ -0,0 +1,114 @@
1
+ import { FC } from "react"
2
+ import { Image, ImageStyle, TextStyle, View, ViewStyle } from "react-native"
3
+
4
+ import { Button } from "@/components/Button" // @demo remove-current-line
5
+ import { Screen } from "@/components/Screen"
6
+ import { Text } from "@/components/Text"
7
+ import { useAuth } from "@/context/AuthContext" // @demo remove-current-line
8
+ import { isRTL } from "@/i18n"
9
+ import type { AppStackScreenProps } from "@/navigators/AppNavigator" // @demo remove-current-line
10
+ import type { ThemedStyle } from "@/theme/types"
11
+ import { useAppTheme } from "@/theme/context"
12
+ import { $styles } from "@/theme/styles"
13
+ import { useHeader } from "@/utils/useHeader" // @demo remove-current-line
14
+ import { useSafeAreaInsetsStyle } from "@/utils/useSafeAreaInsetsStyle"
15
+
16
+ const welcomeLogo = require("@assets/images/logo.png")
17
+ const welcomeFace = require("@assets/images/welcome-face.png")
18
+
19
+ interface WelcomeScreenProps extends AppStackScreenProps<"Welcome"> {} // @demo remove-current-line
20
+
21
+ // @demo replace-next-line export const WelcomeScreen: FC = function WelcomeScreen(
22
+ export const WelcomeScreen: FC<WelcomeScreenProps> = function WelcomeScreen(
23
+ _props, // @demo remove-current-line
24
+ ) {
25
+ const { themed, theme } = useAppTheme()
26
+ // @demo remove-block-start
27
+ const { navigation } = _props
28
+ const { logout } = useAuth()
29
+
30
+ function goNext() {
31
+ navigation.navigate("ChooseAuth")
32
+ }
33
+
34
+ useHeader(
35
+ {
36
+
37
+ },
38
+ [logout],
39
+ )
40
+ // @demo remove-block-end
41
+
42
+ const $bottomContainerInsets = useSafeAreaInsetsStyle(["bottom"])
43
+
44
+ return (
45
+ <Screen preset="fixed" contentContainerStyle={$styles.flex1}>
46
+ <View style={themed($topContainer)}>
47
+ <Image style={themed($welcomeLogo)} source={welcomeLogo} resizeMode="contain" />
48
+ <Text
49
+ testID="welcome-heading"
50
+ style={themed($welcomeHeading)}
51
+ tx="welcomeScreen:readyForLaunch"
52
+ preset="heading"
53
+ />
54
+ <Text tx="welcomeScreen:exciting" preset="subheading" />
55
+ <Image
56
+ style={$welcomeFace}
57
+ source={welcomeFace}
58
+ resizeMode="contain"
59
+ tintColor={theme.colors.palette.neutral900}
60
+ />
61
+ </View>
62
+
63
+ <View style={themed([$bottomContainer, $bottomContainerInsets])}>
64
+ <Text tx="welcomeScreen:postscript" size="md" />
65
+ {/* @demo remove-block-start */}
66
+ <Button
67
+ testID="next-screen-button"
68
+ preset="reversed"
69
+ tx="welcomeScreen:letsGo"
70
+ onPress={goNext}
71
+ />
72
+ {/* @demo remove-block-end */}
73
+ </View>
74
+ </Screen>
75
+ )
76
+ }
77
+
78
+ const $topContainer: ThemedStyle<ViewStyle> = ({ spacing }) => ({
79
+ flexShrink: 1,
80
+ flexGrow: 1,
81
+ flexBasis: "57%",
82
+ justifyContent: "center",
83
+ paddingHorizontal: spacing.lg,
84
+ })
85
+
86
+ const $bottomContainer: ThemedStyle<ViewStyle> = ({ colors, spacing }) => ({
87
+ flexShrink: 1,
88
+ flexGrow: 0,
89
+ flexBasis: "43%",
90
+ backgroundColor: colors.palette.neutral100,
91
+ borderTopLeftRadius: 16,
92
+ borderTopRightRadius: 16,
93
+ paddingHorizontal: spacing.lg,
94
+ justifyContent: "space-around",
95
+ })
96
+
97
+ const $welcomeLogo: ThemedStyle<ImageStyle> = ({ spacing }) => ({
98
+ height: 88,
99
+ width: "100%",
100
+ marginBottom: spacing.xxl,
101
+ })
102
+
103
+ const $welcomeFace: ImageStyle = {
104
+ height: 169,
105
+ width: 269,
106
+ position: "absolute",
107
+ bottom: -47,
108
+ right: -80,
109
+ transform: [{ scaleX: isRTL ? -1 : 1 }],
110
+ }
111
+
112
+ const $welcomeHeading: ThemedStyle<TextStyle> = ({ spacing }) => ({
113
+ marginBottom: spacing.md,
114
+ })
@@ -0,0 +1,73 @@
1
+ import { ApiErrorResponse } from "apisauce"
2
+
3
+ import { getGeneralApiProblem } from "./apiProblem"
4
+
5
+ test("handles connection errors", () => {
6
+ expect(getGeneralApiProblem({ problem: "CONNECTION_ERROR" } as ApiErrorResponse<null>)).toEqual({
7
+ kind: "cannot-connect",
8
+ temporary: true,
9
+ })
10
+ })
11
+
12
+ test("handles network errors", () => {
13
+ expect(getGeneralApiProblem({ problem: "NETWORK_ERROR" } as ApiErrorResponse<null>)).toEqual({
14
+ kind: "cannot-connect",
15
+ temporary: true,
16
+ })
17
+ })
18
+
19
+ test("handles timeouts", () => {
20
+ expect(getGeneralApiProblem({ problem: "TIMEOUT_ERROR" } as ApiErrorResponse<null>)).toEqual({
21
+ kind: "timeout",
22
+ temporary: true,
23
+ })
24
+ })
25
+
26
+ test("handles server errors", () => {
27
+ expect(getGeneralApiProblem({ problem: "SERVER_ERROR" } as ApiErrorResponse<null>)).toEqual({
28
+ kind: "server",
29
+ })
30
+ })
31
+
32
+ test("handles unknown errors", () => {
33
+ expect(getGeneralApiProblem({ problem: "UNKNOWN_ERROR" } as ApiErrorResponse<null>)).toEqual({
34
+ kind: "unknown",
35
+ temporary: true,
36
+ })
37
+ })
38
+
39
+ test("handles unauthorized errors", () => {
40
+ expect(
41
+ getGeneralApiProblem({ problem: "CLIENT_ERROR", status: 401 } as ApiErrorResponse<null>),
42
+ ).toEqual({
43
+ kind: "unauthorized",
44
+ })
45
+ })
46
+
47
+ test("handles forbidden errors", () => {
48
+ expect(
49
+ getGeneralApiProblem({ problem: "CLIENT_ERROR", status: 403 } as ApiErrorResponse<null>),
50
+ ).toEqual({
51
+ kind: "forbidden",
52
+ })
53
+ })
54
+
55
+ test("handles not-found errors", () => {
56
+ expect(
57
+ getGeneralApiProblem({ problem: "CLIENT_ERROR", status: 404 } as ApiErrorResponse<null>),
58
+ ).toEqual({
59
+ kind: "not-found",
60
+ })
61
+ })
62
+
63
+ test("handles other client errors", () => {
64
+ expect(
65
+ getGeneralApiProblem({ problem: "CLIENT_ERROR", status: 418 } as ApiErrorResponse<null>),
66
+ ).toEqual({
67
+ kind: "rejected",
68
+ })
69
+ })
70
+
71
+ test("handles cancellation errors", () => {
72
+ expect(getGeneralApiProblem({ problem: "CANCEL_ERROR" } as ApiErrorResponse<null>)).toBeNull()
73
+ })
@@ -0,0 +1,74 @@
1
+ import { ApiResponse } from "apisauce"
2
+
3
+ export type GeneralApiProblem =
4
+ /**
5
+ * Times up.
6
+ */
7
+ | { kind: "timeout"; temporary: true }
8
+ /**
9
+ * Cannot connect to the server for some reason.
10
+ */
11
+ | { kind: "cannot-connect"; temporary: true }
12
+ /**
13
+ * The server experienced a problem. Any 5xx error.
14
+ */
15
+ | { kind: "server" }
16
+ /**
17
+ * We're not allowed because we haven't identified ourself. This is 401.
18
+ */
19
+ | { kind: "unauthorized" }
20
+ /**
21
+ * We don't have access to perform that request. This is 403.
22
+ */
23
+ | { kind: "forbidden" }
24
+ /**
25
+ * Unable to find that resource. This is a 404.
26
+ */
27
+ | { kind: "not-found" }
28
+ /**
29
+ * All other 4xx series errors.
30
+ */
31
+ | { kind: "rejected" }
32
+ /**
33
+ * Something truly unexpected happened. Most likely can try again. This is a catch all.
34
+ */
35
+ | { kind: "unknown"; temporary: true }
36
+ /**
37
+ * The data we received is not in the expected format.
38
+ */
39
+ | { kind: "bad-data" }
40
+
41
+ /**
42
+ * Attempts to get a common cause of problems from an api response.
43
+ *
44
+ * @param response The api response.
45
+ */
46
+ export function getGeneralApiProblem(response: ApiResponse<any>): GeneralApiProblem | null {
47
+ switch (response.problem) {
48
+ case "CONNECTION_ERROR":
49
+ return { kind: "cannot-connect", temporary: true }
50
+ case "NETWORK_ERROR":
51
+ return { kind: "cannot-connect", temporary: true }
52
+ case "TIMEOUT_ERROR":
53
+ return { kind: "timeout", temporary: true }
54
+ case "SERVER_ERROR":
55
+ return { kind: "server" }
56
+ case "UNKNOWN_ERROR":
57
+ return { kind: "unknown", temporary: true }
58
+ case "CLIENT_ERROR":
59
+ switch (response.status) {
60
+ case 401:
61
+ return { kind: "unauthorized" }
62
+ case 403:
63
+ return { kind: "forbidden" }
64
+ case 404:
65
+ return { kind: "not-found" }
66
+ default:
67
+ return { kind: "rejected" }
68
+ }
69
+ case "CANCEL_ERROR":
70
+ return null
71
+ }
72
+
73
+ return null
74
+ }