@oxyhq/services 5.11.10 → 5.11.11

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 (170) hide show
  1. package/lib/commonjs/ui/components/AnimationExample.js +1 -1
  2. package/lib/commonjs/ui/components/AnimationExample.js.map +1 -1
  3. package/lib/commonjs/ui/components/Header.js +2 -2
  4. package/lib/commonjs/ui/components/Header.js.map +1 -1
  5. package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
  6. package/lib/commonjs/ui/components/StepBasedScreen.README.md +337 -0
  7. package/lib/commonjs/ui/components/StepBasedScreen.js +361 -0
  8. package/lib/commonjs/ui/components/StepBasedScreen.js.map +1 -0
  9. package/lib/commonjs/ui/components/icon/OxyIcon.js +3 -3
  10. package/lib/commonjs/ui/components/icon/OxyIcon.js.map +1 -1
  11. package/lib/commonjs/ui/components/internal/PinInput.js +1 -1
  12. package/lib/commonjs/ui/components/internal/PinInput.js.map +1 -1
  13. package/lib/commonjs/ui/components/photogrid/JustifiedPhotoGrid.js.map +1 -1
  14. package/lib/commonjs/ui/context/OxyContext.js +7 -7
  15. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  16. package/lib/commonjs/ui/screens/ProfileScreen.js +55 -55
  17. package/lib/commonjs/ui/screens/ProfileScreen.js.map +1 -1
  18. package/lib/commonjs/ui/screens/RecoverAccountScreen.js +87 -219
  19. package/lib/commonjs/ui/screens/RecoverAccountScreen.js.map +1 -1
  20. package/lib/commonjs/ui/screens/SignInScreen.js +138 -235
  21. package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
  22. package/lib/commonjs/ui/screens/SignUpScreen.js +139 -742
  23. package/lib/commonjs/ui/screens/SignUpScreen.js.map +1 -1
  24. package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js +3 -3
  25. package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js.map +1 -1
  26. package/lib/commonjs/ui/screens/internal/SignInUsernameStep.js +2 -2
  27. package/lib/commonjs/ui/screens/internal/SignInUsernameStep.js.map +1 -1
  28. package/lib/commonjs/ui/screens/steps/RecoverRequestStep.js +110 -0
  29. package/lib/commonjs/ui/screens/steps/RecoverRequestStep.js.map +1 -0
  30. package/lib/commonjs/ui/screens/steps/RecoverSuccessStep.js +138 -0
  31. package/lib/commonjs/ui/screens/steps/RecoverSuccessStep.js.map +1 -0
  32. package/lib/commonjs/ui/screens/steps/RecoverVerifyStep.js +141 -0
  33. package/lib/commonjs/ui/screens/steps/RecoverVerifyStep.js.map +1 -0
  34. package/lib/commonjs/ui/screens/steps/SignInPasswordStep.js +165 -0
  35. package/lib/commonjs/ui/screens/steps/SignInPasswordStep.js.map +1 -0
  36. package/lib/commonjs/ui/screens/steps/SignInUsernameStep.js +150 -0
  37. package/lib/commonjs/ui/screens/steps/SignInUsernameStep.js.map +1 -0
  38. package/lib/commonjs/ui/screens/steps/SignUpIdentityStep.js +171 -0
  39. package/lib/commonjs/ui/screens/steps/SignUpIdentityStep.js.map +1 -0
  40. package/lib/commonjs/ui/screens/steps/SignUpSecurityStep.js +163 -0
  41. package/lib/commonjs/ui/screens/steps/SignUpSecurityStep.js.map +1 -0
  42. package/lib/commonjs/ui/screens/steps/SignUpSummaryStep.js +170 -0
  43. package/lib/commonjs/ui/screens/steps/SignUpSummaryStep.js.map +1 -0
  44. package/lib/commonjs/ui/screens/steps/SignUpWelcomeStep.js +72 -0
  45. package/lib/commonjs/ui/screens/steps/SignUpWelcomeStep.js.map +1 -0
  46. package/lib/module/ui/components/AnimationExample.js +1 -1
  47. package/lib/module/ui/components/AnimationExample.js.map +1 -1
  48. package/lib/module/ui/components/Header.js +2 -2
  49. package/lib/module/ui/components/Header.js.map +1 -1
  50. package/lib/module/ui/components/OxyProvider.js.map +1 -1
  51. package/lib/module/ui/components/Section.js.map +1 -1
  52. package/lib/module/ui/components/SectionTitle.js.map +1 -1
  53. package/lib/module/ui/components/StepBasedScreen.README.md +337 -0
  54. package/lib/module/ui/components/StepBasedScreen.js +356 -0
  55. package/lib/module/ui/components/StepBasedScreen.js.map +1 -0
  56. package/lib/module/ui/components/icon/FAIRWalletIcon.js.map +1 -1
  57. package/lib/module/ui/components/icon/OxyIcon.js +3 -3
  58. package/lib/module/ui/components/icon/OxyIcon.js.map +1 -1
  59. package/lib/module/ui/components/internal/PinInput.js +1 -1
  60. package/lib/module/ui/components/internal/PinInput.js.map +1 -1
  61. package/lib/module/ui/components/photogrid/JustifiedPhotoGrid.js.map +1 -1
  62. package/lib/module/ui/context/OxyContext.js +7 -7
  63. package/lib/module/ui/context/OxyContext.js.map +1 -1
  64. package/lib/module/ui/screens/ProfileScreen.js +55 -55
  65. package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
  66. package/lib/module/ui/screens/RecoverAccountScreen.js +91 -222
  67. package/lib/module/ui/screens/RecoverAccountScreen.js.map +1 -1
  68. package/lib/module/ui/screens/SignInScreen.js +140 -237
  69. package/lib/module/ui/screens/SignInScreen.js.map +1 -1
  70. package/lib/module/ui/screens/SignUpScreen.js +141 -743
  71. package/lib/module/ui/screens/SignUpScreen.js.map +1 -1
  72. package/lib/module/ui/screens/internal/SignInPasswordStep.js +3 -3
  73. package/lib/module/ui/screens/internal/SignInPasswordStep.js.map +1 -1
  74. package/lib/module/ui/screens/internal/SignInUsernameStep.js +2 -2
  75. package/lib/module/ui/screens/internal/SignInUsernameStep.js.map +1 -1
  76. package/lib/module/ui/screens/steps/RecoverRequestStep.js +105 -0
  77. package/lib/module/ui/screens/steps/RecoverRequestStep.js.map +1 -0
  78. package/lib/module/ui/screens/steps/RecoverSuccessStep.js +133 -0
  79. package/lib/module/ui/screens/steps/RecoverSuccessStep.js.map +1 -0
  80. package/lib/module/ui/screens/steps/RecoverVerifyStep.js +136 -0
  81. package/lib/module/ui/screens/steps/RecoverVerifyStep.js.map +1 -0
  82. package/lib/module/ui/screens/steps/SignInPasswordStep.js +160 -0
  83. package/lib/module/ui/screens/steps/SignInPasswordStep.js.map +1 -0
  84. package/lib/module/ui/screens/steps/SignInUsernameStep.js +145 -0
  85. package/lib/module/ui/screens/steps/SignInUsernameStep.js.map +1 -0
  86. package/lib/module/ui/screens/steps/SignUpIdentityStep.js +166 -0
  87. package/lib/module/ui/screens/steps/SignUpIdentityStep.js.map +1 -0
  88. package/lib/module/ui/screens/steps/SignUpSecurityStep.js +158 -0
  89. package/lib/module/ui/screens/steps/SignUpSecurityStep.js.map +1 -0
  90. package/lib/module/ui/screens/steps/SignUpSummaryStep.js +165 -0
  91. package/lib/module/ui/screens/steps/SignUpSummaryStep.js.map +1 -0
  92. package/lib/module/ui/screens/steps/SignUpWelcomeStep.js +67 -0
  93. package/lib/module/ui/screens/steps/SignUpWelcomeStep.js.map +1 -0
  94. package/lib/typescript/models/interfaces.d.ts +4 -3
  95. package/lib/typescript/models/interfaces.d.ts.map +1 -1
  96. package/lib/typescript/ui/components/AnimationExample.d.ts +1 -1
  97. package/lib/typescript/ui/components/AnimationExample.d.ts.map +1 -1
  98. package/lib/typescript/ui/components/OxyPayButton.d.ts +2 -2
  99. package/lib/typescript/ui/components/OxyPayButton.d.ts.map +1 -1
  100. package/lib/typescript/ui/components/Section.d.ts +2 -1
  101. package/lib/typescript/ui/components/Section.d.ts.map +1 -1
  102. package/lib/typescript/ui/components/SectionTitle.d.ts +2 -1
  103. package/lib/typescript/ui/components/SectionTitle.d.ts.map +1 -1
  104. package/lib/typescript/ui/components/StepBasedScreen.d.ts +24 -0
  105. package/lib/typescript/ui/components/StepBasedScreen.d.ts.map +1 -0
  106. package/lib/typescript/ui/components/icon/FAIRWalletIcon.d.ts +2 -1
  107. package/lib/typescript/ui/components/icon/FAIRWalletIcon.d.ts.map +1 -1
  108. package/lib/typescript/ui/components/icon/OxyIcon.d.ts +1 -1
  109. package/lib/typescript/ui/components/icon/OxyIcon.d.ts.map +1 -1
  110. package/lib/typescript/ui/components/internal/PinInput.d.ts +9 -1
  111. package/lib/typescript/ui/components/internal/PinInput.d.ts.map +1 -1
  112. package/lib/typescript/ui/context/OxyContext.d.ts +2 -1
  113. package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
  114. package/lib/typescript/ui/screens/PaymentGatewayScreen.d.ts +2 -2
  115. package/lib/typescript/ui/screens/PaymentGatewayScreen.d.ts.map +1 -1
  116. package/lib/typescript/ui/screens/ProfileScreen.d.ts.map +1 -1
  117. package/lib/typescript/ui/screens/RecoverAccountScreen.d.ts +2 -9
  118. package/lib/typescript/ui/screens/RecoverAccountScreen.d.ts.map +1 -1
  119. package/lib/typescript/ui/screens/SignInScreen.d.ts.map +1 -1
  120. package/lib/typescript/ui/screens/SignUpScreen.d.ts +1 -1
  121. package/lib/typescript/ui/screens/SignUpScreen.d.ts.map +1 -1
  122. package/lib/typescript/ui/screens/steps/RecoverRequestStep.d.ts +21 -0
  123. package/lib/typescript/ui/screens/steps/RecoverRequestStep.d.ts.map +1 -0
  124. package/lib/typescript/ui/screens/steps/RecoverSuccessStep.d.ts +18 -0
  125. package/lib/typescript/ui/screens/steps/RecoverSuccessStep.d.ts.map +1 -0
  126. package/lib/typescript/ui/screens/steps/RecoverVerifyStep.d.ts +24 -0
  127. package/lib/typescript/ui/screens/steps/RecoverVerifyStep.d.ts.map +1 -0
  128. package/lib/typescript/ui/screens/steps/SignInPasswordStep.d.ts +27 -0
  129. package/lib/typescript/ui/screens/steps/SignInPasswordStep.d.ts.map +1 -0
  130. package/lib/typescript/ui/screens/steps/SignInUsernameStep.d.ts +27 -0
  131. package/lib/typescript/ui/screens/steps/SignInUsernameStep.d.ts.map +1 -0
  132. package/lib/typescript/ui/screens/steps/SignUpIdentityStep.d.ts +25 -0
  133. package/lib/typescript/ui/screens/steps/SignUpIdentityStep.d.ts.map +1 -0
  134. package/lib/typescript/ui/screens/steps/SignUpSecurityStep.d.ts +26 -0
  135. package/lib/typescript/ui/screens/steps/SignUpSecurityStep.d.ts.map +1 -0
  136. package/lib/typescript/ui/screens/steps/SignUpSummaryStep.d.ts +16 -0
  137. package/lib/typescript/ui/screens/steps/SignUpSummaryStep.d.ts.map +1 -0
  138. package/lib/typescript/ui/screens/steps/SignUpWelcomeStep.d.ts +13 -0
  139. package/lib/typescript/ui/screens/steps/SignUpWelcomeStep.d.ts.map +1 -0
  140. package/package.json +2 -3
  141. package/src/models/interfaces.ts +5 -3
  142. package/src/ui/components/AnimationExample.tsx +9 -8
  143. package/src/ui/components/Header.tsx +2 -2
  144. package/src/ui/components/OxyPayButton.tsx +2 -2
  145. package/src/ui/components/OxyProvider.tsx +1 -1
  146. package/src/ui/components/Section.tsx +7 -7
  147. package/src/ui/components/SectionTitle.tsx +2 -2
  148. package/src/ui/components/StepBasedScreen.README.md +337 -0
  149. package/src/ui/components/StepBasedScreen.tsx +417 -0
  150. package/src/ui/components/icon/FAIRWalletIcon.tsx +2 -2
  151. package/src/ui/components/icon/OxyIcon.tsx +10 -11
  152. package/src/ui/components/internal/PinInput.tsx +13 -4
  153. package/src/ui/components/photogrid/JustifiedPhotoGrid.tsx +1 -1
  154. package/src/ui/context/OxyContext.tsx +12 -11
  155. package/src/ui/screens/PaymentGatewayScreen.tsx +2 -2
  156. package/src/ui/screens/ProfileScreen.tsx +54 -54
  157. package/src/ui/screens/RecoverAccountScreen.tsx +98 -211
  158. package/src/ui/screens/SignInScreen.tsx +148 -271
  159. package/src/ui/screens/SignUpScreen.tsx +146 -748
  160. package/src/ui/screens/internal/SignInPasswordStep.tsx +3 -3
  161. package/src/ui/screens/internal/SignInUsernameStep.tsx +2 -2
  162. package/src/ui/screens/steps/RecoverRequestStep.tsx +130 -0
  163. package/src/ui/screens/steps/RecoverSuccessStep.tsx +131 -0
  164. package/src/ui/screens/steps/RecoverVerifyStep.tsx +153 -0
  165. package/src/ui/screens/steps/SignInPasswordStep.tsx +172 -0
  166. package/src/ui/screens/steps/SignInUsernameStep.tsx +176 -0
  167. package/src/ui/screens/steps/SignUpIdentityStep.tsx +204 -0
  168. package/src/ui/screens/steps/SignUpSecurityStep.tsx +191 -0
  169. package/src/ui/screens/steps/SignUpSummaryStep.tsx +130 -0
  170. package/src/ui/screens/steps/SignUpWelcomeStep.tsx +65 -0
@@ -1,383 +1,69 @@
1
- import React, { useState, useRef, useEffect, useCallback, useMemo } from 'react';
2
- import {
3
- View,
4
- Text,
5
- TextInput,
6
- TouchableOpacity,
7
- StyleSheet,
8
- ActivityIndicator,
9
- Platform,
10
- KeyboardAvoidingView,
11
- ScrollView,
12
- Animated,
13
- StatusBar,
14
- Alert,
15
- } from 'react-native';
1
+ import type React from 'react';
2
+ import { useState, useRef, useEffect, useCallback, useMemo } from 'react';
16
3
  import type { BaseScreenProps } from '../navigation/types';
17
4
  import { useOxy } from '../context/OxyContext';
18
- import { useThemeColors, createCommonStyles, createAuthStyles } from '../styles';
19
- import { Ionicons } from '@expo/vector-icons';
20
- import Svg, { Path, Circle } from 'react-native-svg';
5
+ import { useThemeColors } from '../styles';
21
6
  import { toast } from '../../lib/sonner';
22
- import HighFive from '../../assets/illustrations/HighFive';
23
- import GroupedPillButtons from '../components/internal/GroupedPillButtons';
24
- import TextField from '../components/internal/TextField';
25
- import SignUpIdentityStep from './internal/SignUpIdentityStep';
26
- import SignUpSecurityStep from './internal/SignUpSecurityStep';
27
- import SignUpSummaryStep from './internal/SignUpSummaryStep';
28
- import SignUpWelcomeStep from './internal/SignUpWelcomeStep';
7
+ import StepBasedScreen, { type StepConfig } from '../components/StepBasedScreen';
8
+ import SignUpWelcomeStep from './steps/SignUpWelcomeStep';
9
+ import SignUpIdentityStep from './steps/SignUpIdentityStep';
10
+ import SignUpSecurityStep from './steps/SignUpSecurityStep';
11
+ import SignUpSummaryStep from './steps/SignUpSummaryStep';
29
12
 
30
13
  // Types for better type safety
31
- interface FormData {
32
- username: string;
33
- email: string;
34
- password: string;
35
- confirmPassword: string;
36
- }
37
-
38
14
  interface ValidationState {
39
15
  status: 'idle' | 'validating' | 'valid' | 'invalid';
40
16
  message: string;
41
17
  }
42
18
 
43
- interface PasswordVisibility {
44
- password: boolean;
45
- confirmPassword: boolean;
46
- }
47
-
48
19
  // Constants
49
20
  const USERNAME_MIN_LENGTH = 3;
50
21
  const PASSWORD_MIN_LENGTH = 8;
51
- const VALIDATION_DEBOUNCE_MS = 800;
52
- const CACHE_DURATION_MS = 5 * 60 * 1000; // 5 minutes
53
22
 
54
23
  // Email validation regex
55
24
  const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
56
25
 
57
- // Styles factory function
58
- const createStyles = (colors: any, theme: string) => StyleSheet.create({
59
- container: {
60
- flex: 1,
61
- },
62
- scrollContent: {
63
- flexGrow: 1,
64
- paddingHorizontal: 24,
65
- paddingTop: 4,
66
- paddingBottom: 20,
67
- },
68
- stepContainer: {
69
- flex: 1,
70
- justifyContent: 'flex-start',
71
- alignItems: 'flex-start',
72
- },
73
- modernHeader: {
74
- alignItems: 'flex-start',
75
- width: '100%',
76
- marginBottom: 24,
77
- },
78
- modernTitle: {
79
- fontFamily: Platform.OS === 'web' ? 'Phudu' : 'Phudu-Bold',
80
- fontWeight: Platform.OS === 'web' ? 'bold' : undefined,
81
- fontSize: 62,
82
- lineHeight: 48,
83
- marginBottom: 18,
84
- textAlign: 'left',
85
- letterSpacing: -1,
86
- },
87
- modernSubtitle: {
88
- fontSize: 18,
89
- lineHeight: 24,
90
- textAlign: 'left',
91
- opacity: 0.8,
92
- marginBottom: 24,
93
- },
94
- welcomeImageContainer: {
95
- alignItems: 'center',
96
- justifyContent: 'center',
97
- marginVertical: 20,
98
- },
99
- welcomeTitle: {
100
- fontFamily: Platform.OS === 'web' ? 'Phudu' : 'Phudu-Bold',
101
- fontWeight: Platform.OS === 'web' ? 'bold' : undefined,
102
- fontSize: 42,
103
- lineHeight: 48,
104
- marginBottom: 12,
105
- textAlign: 'left',
106
- letterSpacing: -1,
107
- },
108
- welcomeText: {
109
- fontSize: 18,
110
- lineHeight: 24,
111
- textAlign: 'left',
112
- opacity: 0.8,
113
- marginBottom: 24,
114
- },
115
- stepTitle: {
116
- fontFamily: Platform.OS === 'web' ? 'Phudu' : 'Phudu-Bold',
117
- fontWeight: Platform.OS === 'web' ? 'bold' : undefined,
118
- fontSize: 42,
119
- lineHeight: 48,
120
- marginBottom: 12,
121
- textAlign: 'left',
122
- letterSpacing: -1,
123
- },
124
- inputContainer: {
125
- width: '100%',
126
- marginBottom: 24,
127
- },
128
- premiumInputWrapper: {
129
- flexDirection: 'row',
130
- alignItems: 'center',
131
- height: 56,
132
- borderRadius: 16,
133
- paddingHorizontal: 20,
134
- borderWidth: 2,
135
- backgroundColor: colors.inputBackground,
136
- },
137
- inputIcon: {
138
- marginRight: 12,
139
- },
140
- inputContent: {
141
- flex: 1,
142
- },
143
- modernLabel: {
144
- fontSize: 12,
145
- fontWeight: '500',
146
- marginBottom: 2,
147
- },
148
- modernInput: {
149
- flex: 1,
150
- fontSize: 16,
151
- height: '100%',
152
- },
153
- validationIndicator: {
154
- marginLeft: 8,
155
- },
156
- validationCard: {
157
- flexDirection: 'row',
158
- alignItems: 'center',
159
- padding: 12,
160
- borderRadius: 12,
161
- marginTop: 8,
162
- gap: 8,
163
- },
164
- belowInputMessage: {
165
- flexDirection: 'row',
166
- alignItems: 'center',
167
- marginTop: 4,
168
- marginBottom: 0,
169
- gap: 6,
170
- },
171
- belowInputText: {
172
- fontSize: 13,
173
- fontWeight: '500',
174
- },
175
- validationIconContainer: {
176
- width: 32,
177
- height: 32,
178
- borderRadius: 16,
179
- justifyContent: 'center',
180
- alignItems: 'center',
181
- marginRight: 12,
182
- },
183
- validationTextContainer: {
184
- flex: 1,
185
- },
186
- validationTitle: {
187
- fontSize: 12,
188
- fontWeight: '600',
189
- marginBottom: 2,
190
- },
191
- validationSubtitle: {
192
- fontSize: 11,
193
- opacity: 0.8,
194
- },
195
- passwordToggle: {
196
- padding: 4,
197
- },
198
- passwordHint: {
199
- fontSize: 12,
200
- marginTop: 4,
201
- },
202
- button: {
203
- flexDirection: 'row',
204
- alignItems: 'center',
205
- justifyContent: 'center',
206
- paddingVertical: 18,
207
- paddingHorizontal: 32,
208
- borderRadius: 16,
209
- marginVertical: 8,
210
- gap: 8,
211
- width: '100%',
212
- ...Platform.select({
213
- web: {
214
- boxShadow: '0 4px 8px rgba(0,0,0,0.3)',
215
- },
216
- default: {
217
- shadowOffset: { width: 0, height: 4 },
218
- shadowOpacity: 0.3,
219
- shadowRadius: 8,
220
- elevation: 6,
221
- }
222
- }),
223
- },
224
- buttonText: {
225
- color: '#FFFFFF',
226
- fontSize: 16,
227
- fontWeight: '600',
228
- letterSpacing: 0.5,
229
- },
230
- footerTextContainer: {
231
- flexDirection: 'row',
232
- justifyContent: 'center',
233
- marginTop: 16,
234
- },
235
- footerText: {
236
- fontSize: 15,
237
- },
238
- linkText: {
239
- fontSize: 14,
240
- lineHeight: 20,
241
- fontWeight: '600',
242
- textDecorationLine: 'underline',
243
- },
244
- userInfoContainer: {
245
- padding: 20,
246
- marginVertical: 20,
247
- borderRadius: 24,
248
- alignItems: 'center',
249
- ...Platform.select({
250
- web: {
251
- boxShadow: '0 1px 4px rgba(0,0,0,0.04)',
252
- },
253
- default: {
254
- shadowColor: '#000',
255
- shadowOpacity: 0.04,
256
- shadowOffset: { width: 0, height: 1 },
257
- shadowRadius: 4,
258
- elevation: 1,
259
- }
260
- }),
261
- },
262
- userInfoText: {
263
- fontSize: 16,
264
- marginBottom: 8,
265
- textAlign: 'center',
266
- },
267
- actionButtonsContainer: {
268
- marginTop: 24,
269
- },
270
- navigationButtons: {
271
- flexDirection: 'row',
272
- justifyContent: 'center',
273
- marginTop: 16,
274
- marginBottom: 8,
275
- width: '100%',
276
- gap: 8,
277
- },
278
- navButton: {
279
- flexDirection: 'row',
280
- alignItems: 'center',
281
- paddingVertical: 6,
282
- paddingHorizontal: 12,
283
- gap: 6,
284
- minWidth: 70,
285
- borderWidth: 1,
286
- ...Platform.select({
287
- web: {
288
- boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
289
- },
290
- default: {
291
- shadowOffset: { width: 0, height: 2 },
292
- shadowOpacity: 0.1,
293
- shadowRadius: 4,
294
- elevation: 2,
295
- }
296
- }),
297
- },
298
- backButton: {
299
- backgroundColor: 'transparent',
300
- borderTopLeftRadius: 35,
301
- borderBottomLeftRadius: 35,
302
- borderTopRightRadius: 12,
303
- borderBottomRightRadius: 12,
304
- },
305
- nextButton: {
306
- backgroundColor: 'transparent',
307
- borderTopRightRadius: 35,
308
- borderBottomRightRadius: 35,
309
- borderTopLeftRadius: 12,
310
- borderBottomLeftRadius: 12,
311
- },
312
- navButtonText: {
313
- fontSize: 13,
314
- fontWeight: '500',
315
- },
316
- progressContainer: {
317
- flexDirection: 'row',
318
- justifyContent: 'center',
319
- marginBottom: 20,
320
- marginTop: 8,
321
- },
322
- progressDot: {
323
- height: 10,
324
- width: 10,
325
- borderRadius: 5,
326
- marginHorizontal: 6,
327
- borderWidth: 2,
328
- borderColor: '#fff',
329
- ...Platform.select({
330
- web: {
331
- boxShadow: '0 1px 2px rgba(0,0,0,0.08)',
332
- },
333
- default: {
334
- shadowColor: colors.primary,
335
- shadowOpacity: 0.08,
336
- shadowOffset: { width: 0, height: 1 },
337
- shadowRadius: 2,
338
- elevation: 1,
339
- }
340
- }),
341
- },
342
- summaryContainer: {
343
- padding: 0,
344
- marginBottom: 24,
345
- width: '100%',
346
- },
347
- summaryRow: {
348
- flexDirection: 'row',
349
- marginBottom: 10,
350
- },
351
- summaryLabel: {
352
- fontSize: 15,
353
- width: 90,
354
- },
355
- summaryValue: {
356
- fontSize: 15,
357
- fontWeight: '600',
358
- flex: 1,
359
- },
360
- });
26
+ // Main component
27
+ const SignUpScreen: React.FC<BaseScreenProps> = ({
28
+ navigate,
29
+ goBack,
30
+ onAuthenticated,
31
+ theme,
32
+ }) => {
33
+ const { signUp, oxyServices } = useOxy();
34
+ const colors = useThemeColors(theme);
35
+
36
+ // Form data state
37
+ const [username, setUsername] = useState('');
38
+ const [email, setEmail] = useState('');
39
+ const [password, setPassword] = useState('');
40
+ const [confirmPassword, setConfirmPassword] = useState('');
41
+ const [showPassword, setShowPassword] = useState(false);
42
+ const [showConfirmPassword, setShowConfirmPassword] = useState(false);
43
+ const [isLoading, setIsLoading] = useState(false);
361
44
 
362
- // Custom hooks for better separation of concerns
363
- const useFormValidation = (oxyServices: any) => {
45
+ // Validation state
364
46
  const [validationState, setValidationState] = useState<ValidationState>({
365
47
  status: 'idle',
366
- message: ''
48
+ message: '',
367
49
  });
368
50
 
369
- const validationCache = useRef<Map<string, { available: boolean; timestamp: number }>>(new Map());
51
+ // Error message state
52
+ const [errorMessage, setErrorMessage] = useState('');
370
53
 
371
- const validateUsername = useCallback(async (username: string): Promise<boolean> => {
372
- if (!username || username.length < USERNAME_MIN_LENGTH) {
373
- setValidationState({ status: 'invalid', message: '' });
54
+ // Username validation with caching
55
+ const usernameCache = useRef<Map<string, { available: boolean; timestamp: number }>>(new Map());
56
+
57
+ const validateUsername = useCallback(async (usernameToValidate: string): Promise<boolean> => {
58
+ if (!usernameToValidate || usernameToValidate.length < USERNAME_MIN_LENGTH) {
59
+ setValidationState({ status: 'invalid', message: 'Username must be at least 3 characters' });
374
60
  return false;
375
61
  }
376
62
 
377
63
  // Check cache first
378
- const cached = validationCache.current.get(username);
64
+ const cached = usernameCache.current.get(usernameToValidate);
379
65
  const now = Date.now();
380
- if (cached && (now - cached.timestamp) < CACHE_DURATION_MS) {
66
+ if (cached && (now - cached.timestamp) < 5 * 60 * 1000) {
381
67
  const isValid = cached.available;
382
68
  setValidationState({
383
69
  status: isValid ? 'valid' : 'invalid',
@@ -389,11 +75,11 @@ const useFormValidation = (oxyServices: any) => {
389
75
  setValidationState({ status: 'validating', message: '' });
390
76
 
391
77
  try {
392
- const result = await oxyServices.checkUsernameAvailability(username);
78
+ const result = await oxyServices.checkUsernameAvailability(usernameToValidate);
393
79
  const isValid = result.available;
394
80
 
395
81
  // Cache the result
396
- validationCache.current.set(username, {
82
+ usernameCache.current.set(usernameToValidate, {
397
83
  available: isValid,
398
84
  timestamp: now
399
85
  });
@@ -414,428 +100,140 @@ const useFormValidation = (oxyServices: any) => {
414
100
  }
415
101
  }, [oxyServices]);
416
102
 
417
- const validateEmail = useCallback((email: string): boolean => {
418
- return EMAIL_REGEX.test(email);
419
- }, []);
420
-
421
- const validatePassword = useCallback((password: string): boolean => {
422
- return password.length >= PASSWORD_MIN_LENGTH;
423
- }, []);
424
-
425
- const validatePasswordsMatch = useCallback((password: string, confirmPassword: string): boolean => {
426
- return password === confirmPassword;
103
+ // Email validation
104
+ const validateEmail = useCallback((emailToValidate: string): boolean => {
105
+ return EMAIL_REGEX.test(emailToValidate);
427
106
  }, []);
428
107
 
429
- // Cleanup cache on unmount
430
- useEffect(() => {
431
- return () => {
432
- validationCache.current.clear();
433
- };
434
- }, []);
435
-
436
- return {
437
- validationState,
438
- validateUsername,
439
- validateEmail,
440
- validatePassword,
441
- validatePasswordsMatch
442
- };
443
- };
444
-
445
- const useFormData = () => {
446
- const [formData, setFormData] = useState<FormData>({
447
- username: '',
448
- email: '',
449
- password: '',
450
- confirmPassword: ''
451
- });
452
-
453
- const [passwordVisibility, setPasswordVisibility] = useState<PasswordVisibility>({
454
- password: false,
455
- confirmPassword: false
456
- });
457
-
458
- const updateField = useCallback((field: keyof FormData, value: string) => {
459
- setFormData(prev => ({ ...prev, [field]: value }));
108
+ // Password validation
109
+ const validatePassword = useCallback((passwordToValidate: string): boolean => {
110
+ return passwordToValidate.length >= PASSWORD_MIN_LENGTH;
460
111
  }, []);
461
112
 
462
- const togglePasswordVisibility = useCallback((field: keyof PasswordVisibility) => {
463
- setPasswordVisibility(prev => ({ ...prev, [field]: !prev[field] }));
464
- }, []);
465
-
466
- const resetForm = useCallback(() => {
467
- setFormData({
468
- username: '',
469
- email: '',
470
- password: '',
471
- confirmPassword: ''
472
- });
473
- setPasswordVisibility({
474
- password: false,
475
- confirmPassword: false
476
- });
477
- }, []);
478
-
479
- return {
480
- formData,
481
- passwordVisibility,
482
- updateField,
483
- togglePasswordVisibility,
484
- resetForm
485
- };
486
- };
487
-
488
- // Reusable components
489
- const ValidationIndicator: React.FC<{ status: ValidationState['status']; colors: any; styles: any }> = React.memo(({ status, colors, styles }) => {
490
- if (status === 'validating') {
491
- return <ActivityIndicator size="small" color={colors.primary} style={styles.validationIndicator} />;
492
- }
493
- if (status === 'valid') {
494
- return <Ionicons name="checkmark-circle" size={22} color={colors.success} style={styles.validationIndicator} />;
495
- }
496
- if (status === 'invalid') {
497
- return <Ionicons name="close-circle" size={22} color={colors.error} style={styles.validationIndicator} />;
498
- }
499
- return null;
500
- });
501
-
502
- const ValidationMessage: React.FC<{ validationState: ValidationState; colors: any; styles: any }> = React.memo(({ validationState, colors, styles }) => {
503
- if (validationState.status === 'idle' || !validationState.message) return null;
504
-
505
- const isSuccess = validationState.status === 'valid';
506
- const backgroundColor = isSuccess ? colors.success + '10' : colors.error + '10';
507
- const borderColor = isSuccess ? colors.success + '30' : colors.error + '30';
508
- const iconColor = isSuccess ? colors.success : colors.error;
509
- const iconName = isSuccess ? 'checkmark-circle' : 'alert-circle';
510
- const title = isSuccess ? 'Username Available' : 'Username Taken';
511
- const subtitle = isSuccess ? 'Good choice! This username is available' : validationState.message;
512
-
513
- return (
514
- <View style={[styles.validationCard, { backgroundColor, borderColor }]}>
515
- <View style={[styles.validationIconContainer, { backgroundColor: iconColor + '20' }]}>
516
- <Ionicons name={iconName} size={16} color={iconColor} />
517
- </View>
518
- <View style={styles.validationTextContainer}>
519
- <Text style={[styles.validationTitle, { color: iconColor }]}>
520
- {title}
521
- </Text>
522
- <Text style={[styles.validationSubtitle, { color: colors.secondaryText }]}>
523
- {subtitle}
524
- </Text>
525
- </View>
526
- </View>
527
- );
528
- });
529
-
530
- const FormInput: React.FC<{
531
- icon: string;
532
- label: string;
533
- value: string;
534
- onChangeText: (text: string) => void;
535
- secureTextEntry?: boolean;
536
- keyboardType?: 'default' | 'email-address';
537
- autoCapitalize?: 'none' | 'sentences';
538
- autoCorrect?: boolean;
539
- testID?: string;
540
- colors: any;
541
- styles: any;
542
- borderColor?: string;
543
- rightComponent?: React.ReactNode;
544
- }> = React.memo(({
545
- icon,
546
- label,
547
- value,
548
- onChangeText,
549
- secureTextEntry = false,
550
- keyboardType = 'default',
551
- autoCapitalize = 'sentences',
552
- autoCorrect = true,
553
- testID,
554
- colors,
555
- styles,
556
- borderColor,
557
- rightComponent
558
- }) => (
559
- <View style={styles.inputContainer}>
560
- <View style={[
561
- styles.premiumInputWrapper,
562
- {
563
- borderColor: borderColor || colors.border,
564
- backgroundColor: colors.inputBackground,
565
- shadowColor: colors.primary,
566
- shadowOffset: { width: 0, height: 4 },
567
- shadowOpacity: 0.1,
568
- shadowRadius: 12,
569
- elevation: 3,
570
- }
571
- ]}>
572
- <Ionicons
573
- name={icon as any}
574
- size={22}
575
- color={colors.secondaryText}
576
- style={styles.inputIcon}
577
- />
578
- <View style={styles.inputContent}>
579
- <Text style={[styles.modernLabel, { color: colors.secondaryText }]}>
580
- {label}
581
- </Text>
582
- <TextInput
583
- style={[styles.modernInput, { color: colors.text }]}
584
- value={value}
585
- onChangeText={onChangeText}
586
- secureTextEntry={secureTextEntry}
587
- keyboardType={keyboardType}
588
- autoCapitalize={autoCapitalize}
589
- autoCorrect={autoCorrect}
590
- testID={testID}
591
- placeholderTextColor="transparent"
592
- />
593
- </View>
594
- {rightComponent}
595
- </View>
596
- </View>
597
- ));
598
-
599
- const ProgressIndicator: React.FC<{ currentStep: number; totalSteps: number; colors: any; styles: any }> = React.memo(({ currentStep, totalSteps, colors, styles }) => (
600
- <View style={styles.progressContainer}>
601
- {Array.from({ length: totalSteps }, (_, index) => (
602
- <View
603
- key={index}
604
- style={[
605
- styles.progressDot,
606
- currentStep === index ?
607
- { backgroundColor: colors.primary, width: 24 } :
608
- { backgroundColor: colors.border }
609
- ]}
610
- />
611
- ))}
612
- </View>
613
- ));
614
-
615
- // Main component
616
- const SignUpScreen: React.FC<BaseScreenProps> = ({
617
- navigate,
618
- goBack,
619
- onAuthenticated,
620
- theme,
621
- }) => {
622
- const { signUp, isLoading, user, isAuthenticated, oxyServices } = useOxy();
623
- const colors = useThemeColors(theme);
624
-
625
- // Form state
626
- const { formData, passwordVisibility, updateField, togglePasswordVisibility, resetForm } = useFormData();
627
- const { validationState, validateUsername, validateEmail, validatePassword, validatePasswordsMatch } = useFormValidation(oxyServices);
628
-
629
- // UI state
630
- const [currentStep, setCurrentStep] = useState(0);
631
- const [errorMessage, setErrorMessage] = useState('');
632
-
633
- // Animation refs
634
- const fadeAnim = useRef(new Animated.Value(1)).current;
635
- const slideAnim = useRef(new Animated.Value(0)).current;
636
-
637
- // Memoized styles
638
- const styles = useMemo(() => createAuthStyles(colors, theme), [colors, theme]);
639
-
640
- // Debounced username validation
641
- useEffect(() => {
642
- if (!formData.username || formData.username.length < USERNAME_MIN_LENGTH) {
113
+ // Handle form completion
114
+ const handleComplete = useCallback(async (stepData: any[]) => {
115
+ if (!username || !email || !password) {
116
+ toast.error('Please fill in all required fields');
643
117
  return;
644
118
  }
645
119
 
646
- const timeoutId = setTimeout(() => {
647
- validateUsername(formData.username);
648
- }, VALIDATION_DEBOUNCE_MS);
649
-
650
- return () => clearTimeout(timeoutId);
651
- }, [formData.username, validateUsername]);
652
-
653
- // Animation functions
654
- const animateTransition = useCallback((nextStep: number) => {
655
- Animated.timing(fadeAnim, {
656
- toValue: 0,
657
- duration: 250,
658
- useNativeDriver: Platform.OS !== 'web',
659
- }).start(() => {
660
- setCurrentStep(nextStep);
661
- slideAnim.setValue(-100);
662
-
663
- Animated.parallel([
664
- Animated.timing(fadeAnim, {
665
- toValue: 1,
666
- duration: 250,
667
- useNativeDriver: Platform.OS !== 'web',
668
- }),
669
- Animated.timing(slideAnim, {
670
- toValue: 0,
671
- duration: 300,
672
- useNativeDriver: Platform.OS !== 'web',
673
- })
674
- ]).start();
675
- });
676
- }, [fadeAnim, slideAnim]);
677
-
678
- const nextStep = useCallback(() => {
679
- if (currentStep < 3) {
680
- animateTransition(currentStep + 1);
681
- }
682
- }, [currentStep, animateTransition]);
683
-
684
- const prevStep = useCallback(() => {
685
- if (currentStep > 0) {
686
- animateTransition(currentStep - 1);
687
- }
688
- }, [currentStep, animateTransition]);
689
-
690
- // Form validation helpers
691
- const isIdentityStepValid = useCallback(() => {
692
- return formData.username &&
693
- formData.email &&
694
- validateEmail(formData.email) &&
695
- validationState.status === 'valid';
696
- }, [formData.username, formData.email, validateEmail, validationState.status]);
697
-
698
- const isSecurityStepValid = useCallback(() => {
699
- return formData.password &&
700
- validatePassword(formData.password) &&
701
- validatePasswordsMatch(formData.password, formData.confirmPassword);
702
- }, [formData.password, formData.confirmPassword, validatePassword, validatePasswordsMatch]);
703
-
704
- // Custom next handlers for validation
705
- const handleIdentityNext = useCallback(() => {
706
- if (!isIdentityStepValid()) {
707
- toast.error('Please enter a valid username and email.');
120
+ if (!validateEmail(email)) {
121
+ toast.error('Please enter a valid email address');
708
122
  return;
709
123
  }
710
- nextStep();
711
- }, [isIdentityStepValid, nextStep]);
712
124
 
713
- const handleSecurityNext = useCallback(() => {
714
- if (!isSecurityStepValid()) {
715
- toast.error('Please enter a valid password and confirm it.');
125
+ if (!validatePassword(password)) {
126
+ toast.error('Password must be at least 8 characters long');
716
127
  return;
717
128
  }
718
- nextStep();
719
- }, [isSecurityStepValid, nextStep]);
720
129
 
721
- // Sign up handler
722
- const handleSignUp = useCallback(async () => {
723
- if (!isIdentityStepValid() || !isSecurityStepValid()) {
724
- toast.error('Please fill in all fields correctly');
130
+ if (password !== confirmPassword) {
131
+ toast.error('Passwords do not match');
725
132
  return;
726
133
  }
727
134
 
728
135
  try {
729
- setErrorMessage('');
730
- const user = await signUp(formData.username, formData.email, formData.password);
136
+ setIsLoading(true);
137
+ const user = await signUp(username, email, password);
731
138
  toast.success('Account created successfully! Welcome to Oxy!');
732
139
 
733
- // Instead of finalizing immediately, route to post-signup welcome & avatar setup
140
+ // Navigate to welcome screen or handle authentication
734
141
  navigate('WelcomeNewUser', { newUser: user });
735
- resetForm(); // Clear the form for potential future use
736
142
  } catch (error: any) {
737
143
  toast.error(error.message || 'Sign up failed');
144
+ } finally {
145
+ setIsLoading(false);
738
146
  }
739
- }, [formData, isIdentityStepValid, isSecurityStepValid, signUp, onAuthenticated, resetForm]);
147
+ }, [username, email, password, confirmPassword, validateEmail, validatePassword, signUp, navigate]);
740
148
 
741
- // Memoized step components
742
- const updateFieldString = (field: string, value: string) => updateField(field as keyof FormData, value);
743
- const validatePasswordsMatchNoArgs = () => validatePasswordsMatch(formData.password, formData.confirmPassword);
744
- const togglePasswordVisibilityNoArgs = () => togglePasswordVisibility('password');
149
+ // Cleanup cache on unmount
150
+ useEffect(() => {
151
+ return () => {
152
+ usernameCache.current.clear();
153
+ };
154
+ }, []);
745
155
 
746
- const renderWelcomeStep = useMemo(() => (
747
- <SignUpWelcomeStep
748
- styles={styles}
749
- fadeAnim={fadeAnim}
750
- slideAnim={slideAnim}
751
- colors={colors}
752
- nextStep={nextStep}
753
- navigate={navigate}
754
- />
755
- ), [styles, fadeAnim, slideAnim, colors, nextStep, navigate]);
156
+ // Step configurations
157
+ const steps: StepConfig[] = useMemo(() => [
158
+ {
159
+ id: 'welcome',
160
+ component: SignUpWelcomeStep,
161
+ canProceed: () => true,
162
+ },
163
+ {
164
+ id: 'identity',
165
+ component: SignUpIdentityStep,
166
+ canProceed: () => !!(username.trim() && email.trim() && validateEmail(email) && validationState.status === 'valid'),
167
+ onEnter: () => {
168
+ // Auto-validate username when entering this step
169
+ if (username && validationState.status === 'idle') {
170
+ validateUsername(username);
171
+ }
172
+ },
173
+ },
174
+ {
175
+ id: 'security',
176
+ component: SignUpSecurityStep,
177
+ canProceed: () => !!(password && validatePassword(password) && password === confirmPassword),
178
+ },
179
+ {
180
+ id: 'summary',
181
+ component: SignUpSummaryStep,
182
+ canProceed: () => true,
183
+ },
184
+ ], [username, email, password, confirmPassword, validationState.status, validateEmail, validatePassword, validateUsername]);
185
+
186
+ // Step data for the reusable component
187
+ const stepData = useMemo(() => [
188
+ // Welcome step - no data needed
189
+ {},
190
+ // Identity step
191
+ {
192
+ username,
193
+ email,
194
+ setUsername,
195
+ setEmail,
196
+ validationState,
197
+ setValidationState,
198
+ setErrorMessage,
199
+ validateEmail,
200
+ validateUsername,
201
+ },
202
+ // Security step
203
+ {
204
+ password,
205
+ confirmPassword,
206
+ setPassword,
207
+ setConfirmPassword,
208
+ showPassword,
209
+ showConfirmPassword,
210
+ setShowPassword,
211
+ setShowConfirmPassword,
212
+ setErrorMessage,
213
+ validatePassword,
214
+ },
215
+ // Summary step
216
+ {
217
+ isLoading,
218
+ },
219
+ ], [
220
+ username, email, password, confirmPassword, showPassword, showConfirmPassword,
221
+ validationState, errorMessage, validateEmail, validatePassword, isLoading
222
+ ]);
756
223
 
757
- const renderIdentityStep = useMemo(() => (
758
- <SignUpIdentityStep
759
- styles={styles}
760
- fadeAnim={fadeAnim}
761
- slideAnim={slideAnim}
762
- colors={colors}
763
- formData={formData}
764
- validationState={validationState}
765
- updateField={updateFieldString}
766
- setErrorMessage={setErrorMessage}
767
- prevStep={prevStep}
768
- handleIdentityNext={handleIdentityNext}
769
- ValidationMessage={ValidationMessage}
770
- validateEmail={validateEmail}
224
+ return (
225
+ <StepBasedScreen
226
+ steps={steps}
227
+ stepData={stepData}
228
+ onComplete={handleComplete}
771
229
  navigate={navigate}
230
+ goBack={goBack}
231
+ onAuthenticated={onAuthenticated}
232
+ theme={theme}
233
+ showProgressIndicator={true}
234
+ enableAnimations={true}
235
+ oxyServices={oxyServices}
772
236
  />
773
- ), [styles, fadeAnim, slideAnim, colors, formData, validationState, updateFieldString, setErrorMessage, prevStep, handleIdentityNext, ValidationMessage, validateEmail, navigate]);
774
-
775
- const renderSecurityStep = useMemo(() => (
776
- <SignUpSecurityStep
777
- styles={styles}
778
- fadeAnim={fadeAnim}
779
- slideAnim={slideAnim}
780
- colors={colors}
781
- formData={formData}
782
- passwordVisibility={passwordVisibility}
783
- updateField={updateFieldString}
784
- validatePassword={validatePassword}
785
- validatePasswordsMatch={validatePasswordsMatchNoArgs}
786
- prevStep={prevStep}
787
- handleSecurityNext={handleSecurityNext}
788
- setErrorMessage={setErrorMessage}
789
- togglePasswordVisibility={togglePasswordVisibilityNoArgs}
790
- PASSWORD_MIN_LENGTH={PASSWORD_MIN_LENGTH}
791
- />
792
- ), [styles, fadeAnim, slideAnim, colors, formData, passwordVisibility, updateFieldString, validatePassword, validatePasswordsMatchNoArgs, prevStep, handleSecurityNext, setErrorMessage, togglePasswordVisibilityNoArgs, PASSWORD_MIN_LENGTH]);
793
-
794
- const renderSummaryStep = useMemo(() => (
795
- <SignUpSummaryStep
796
- styles={styles}
797
- fadeAnim={fadeAnim}
798
- slideAnim={slideAnim}
799
- colors={colors}
800
- formData={formData}
801
- isLoading={isLoading}
802
- handleSignUp={handleSignUp}
803
- prevStep={prevStep}
804
- />
805
- ), [styles, fadeAnim, slideAnim, colors, formData, isLoading, handleSignUp, prevStep]);
806
-
807
- const renderCurrentStep = useCallback(() => {
808
- switch (currentStep) {
809
- case 0:
810
- return renderWelcomeStep;
811
- case 1:
812
- return renderIdentityStep;
813
- case 2:
814
- return renderSecurityStep;
815
- case 3:
816
- return renderSummaryStep;
817
- default:
818
- return renderWelcomeStep;
819
- }
820
- }, [currentStep, renderWelcomeStep, renderIdentityStep, renderSecurityStep, renderSummaryStep]);
821
-
822
- return (
823
- <KeyboardAvoidingView
824
- style={[styles.container, { backgroundColor: colors.background }]}
825
- behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
826
- >
827
- <StatusBar
828
- barStyle={theme === 'dark' ? 'light-content' : 'dark-content'}
829
- backgroundColor={colors.background}
830
- />
831
- <ScrollView
832
- contentContainerStyle={styles.scrollContent}
833
- showsVerticalScrollIndicator={false}
834
- keyboardShouldPersistTaps="handled"
835
- >
836
- {renderCurrentStep()}
837
- </ScrollView>
838
- </KeyboardAvoidingView>
839
237
  );
840
238
  };
841
239