@oxyhq/services 5.12.11 → 5.13.1

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 (208) hide show
  1. package/lib/commonjs/core/OxyServices.js +86 -8
  2. package/lib/commonjs/core/OxyServices.js.map +1 -1
  3. package/lib/commonjs/i18n/index.js +37 -1
  4. package/lib/commonjs/i18n/index.js.map +1 -1
  5. package/lib/commonjs/i18n/locales/ar-SA.json +128 -0
  6. package/lib/commonjs/i18n/locales/ca-ES.json +128 -0
  7. package/lib/commonjs/i18n/locales/de-DE.json +128 -0
  8. package/lib/commonjs/i18n/locales/en-US.json +85 -12
  9. package/lib/commonjs/i18n/locales/es-ES.json +58 -6
  10. package/lib/commonjs/i18n/locales/fr-FR.json +128 -0
  11. package/lib/commonjs/i18n/locales/it-IT.json +128 -0
  12. package/lib/commonjs/i18n/locales/ja-JP.json +127 -0
  13. package/lib/commonjs/i18n/locales/ko-KR.json +128 -0
  14. package/lib/commonjs/i18n/locales/pt-PT.json +128 -0
  15. package/lib/commonjs/i18n/locales/zh-CN.json +128 -0
  16. package/lib/commonjs/ui/components/FontLoader.js +22 -42
  17. package/lib/commonjs/ui/components/FontLoader.js.map +1 -1
  18. package/lib/commonjs/ui/components/OxyProvider.js +5 -8
  19. package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
  20. package/lib/commonjs/ui/components/StepBasedScreen.js +64 -44
  21. package/lib/commonjs/ui/components/StepBasedScreen.js.map +1 -1
  22. package/lib/commonjs/ui/components/internal/GroupedPillButtons.js +14 -35
  23. package/lib/commonjs/ui/components/internal/GroupedPillButtons.js.map +1 -1
  24. package/lib/commonjs/ui/components/internal/PinInput.js +2 -2
  25. package/lib/commonjs/ui/components/internal/PinInput.js.map +1 -1
  26. package/lib/commonjs/ui/context/OxyContext.js +434 -321
  27. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  28. package/lib/commonjs/ui/screens/FileManagementScreen.js +56 -5
  29. package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
  30. package/lib/commonjs/ui/screens/SignInScreen.js +43 -39
  31. package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
  32. package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js +139 -125
  33. package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js.map +1 -1
  34. package/lib/commonjs/ui/screens/internal/SignInUsernameStep.js +2 -4
  35. package/lib/commonjs/ui/screens/internal/SignInUsernameStep.js.map +1 -1
  36. package/lib/commonjs/ui/screens/steps/RecoverRequestStep.js +45 -25
  37. package/lib/commonjs/ui/screens/steps/RecoverRequestStep.js.map +1 -1
  38. package/lib/commonjs/ui/screens/steps/RecoverResetPasswordStep.js +88 -53
  39. package/lib/commonjs/ui/screens/steps/RecoverResetPasswordStep.js.map +1 -1
  40. package/lib/commonjs/ui/screens/steps/RecoverSuccessStep.js +79 -58
  41. package/lib/commonjs/ui/screens/steps/RecoverSuccessStep.js.map +1 -1
  42. package/lib/commonjs/ui/screens/steps/RecoverVerifyStep.js +61 -52
  43. package/lib/commonjs/ui/screens/steps/RecoverVerifyStep.js.map +1 -1
  44. package/lib/commonjs/ui/screens/steps/SignInPasswordStep.js +220 -31
  45. package/lib/commonjs/ui/screens/steps/SignInPasswordStep.js.map +1 -1
  46. package/lib/commonjs/ui/screens/steps/SignInTotpStep.js +77 -50
  47. package/lib/commonjs/ui/screens/steps/SignInTotpStep.js.map +1 -1
  48. package/lib/commonjs/ui/screens/steps/SignInUsernameStep.js +527 -66
  49. package/lib/commonjs/ui/screens/steps/SignInUsernameStep.js.map +1 -1
  50. package/lib/commonjs/ui/screens/steps/SignUpIdentityStep.js +55 -30
  51. package/lib/commonjs/ui/screens/steps/SignUpIdentityStep.js.map +1 -1
  52. package/lib/commonjs/ui/screens/steps/SignUpSecurityStep.js +64 -46
  53. package/lib/commonjs/ui/screens/steps/SignUpSecurityStep.js.map +1 -1
  54. package/lib/commonjs/ui/screens/steps/SignUpSummaryStep.js +84 -146
  55. package/lib/commonjs/ui/screens/steps/SignUpSummaryStep.js.map +1 -1
  56. package/lib/commonjs/ui/screens/steps/SignUpWelcomeStep.js +113 -34
  57. package/lib/commonjs/ui/screens/steps/SignUpWelcomeStep.js.map +1 -1
  58. package/lib/commonjs/ui/stores/authStore.js +16 -20
  59. package/lib/commonjs/ui/stores/authStore.js.map +1 -1
  60. package/lib/commonjs/ui/styles/authStyles.js +2 -1
  61. package/lib/commonjs/ui/styles/authStyles.js.map +1 -1
  62. package/lib/commonjs/ui/styles/index.js +11 -0
  63. package/lib/commonjs/ui/styles/index.js.map +1 -1
  64. package/lib/commonjs/ui/styles/spacing.js +51 -0
  65. package/lib/commonjs/ui/styles/spacing.js.map +1 -0
  66. package/lib/commonjs/utils/validationUtils.js +1 -1
  67. package/lib/module/core/OxyServices.js +86 -8
  68. package/lib/module/core/OxyServices.js.map +1 -1
  69. package/lib/module/i18n/index.js +37 -1
  70. package/lib/module/i18n/index.js.map +1 -1
  71. package/lib/module/i18n/locales/ar-SA.json +128 -0
  72. package/lib/module/i18n/locales/ca-ES.json +128 -0
  73. package/lib/module/i18n/locales/de-DE.json +128 -0
  74. package/lib/module/i18n/locales/en-US.json +85 -12
  75. package/lib/module/i18n/locales/es-ES.json +58 -6
  76. package/lib/module/i18n/locales/fr-FR.json +128 -0
  77. package/lib/module/i18n/locales/it-IT.json +128 -0
  78. package/lib/module/i18n/locales/ja-JP.json +127 -0
  79. package/lib/module/i18n/locales/ko-KR.json +128 -0
  80. package/lib/module/i18n/locales/pt-PT.json +128 -0
  81. package/lib/module/i18n/locales/zh-CN.json +128 -0
  82. package/lib/module/ui/components/FontLoader.js +23 -43
  83. package/lib/module/ui/components/FontLoader.js.map +1 -1
  84. package/lib/module/ui/components/OxyProvider.js +6 -8
  85. package/lib/module/ui/components/OxyProvider.js.map +1 -1
  86. package/lib/module/ui/components/StepBasedScreen.js +65 -45
  87. package/lib/module/ui/components/StepBasedScreen.js.map +1 -1
  88. package/lib/module/ui/components/internal/GroupedPillButtons.js +14 -35
  89. package/lib/module/ui/components/internal/GroupedPillButtons.js.map +1 -1
  90. package/lib/module/ui/components/internal/PinInput.js +2 -2
  91. package/lib/module/ui/components/internal/PinInput.js.map +1 -1
  92. package/lib/module/ui/context/OxyContext.js +434 -321
  93. package/lib/module/ui/context/OxyContext.js.map +1 -1
  94. package/lib/module/ui/screens/FileManagementScreen.js +56 -5
  95. package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
  96. package/lib/module/ui/screens/SignInScreen.js +44 -40
  97. package/lib/module/ui/screens/SignInScreen.js.map +1 -1
  98. package/lib/module/ui/screens/WelcomeNewUserScreen.js +138 -126
  99. package/lib/module/ui/screens/WelcomeNewUserScreen.js.map +1 -1
  100. package/lib/module/ui/screens/internal/SignInUsernameStep.js +2 -4
  101. package/lib/module/ui/screens/internal/SignInUsernameStep.js.map +1 -1
  102. package/lib/module/ui/screens/steps/RecoverRequestStep.js +45 -25
  103. package/lib/module/ui/screens/steps/RecoverRequestStep.js.map +1 -1
  104. package/lib/module/ui/screens/steps/RecoverResetPasswordStep.js +89 -54
  105. package/lib/module/ui/screens/steps/RecoverResetPasswordStep.js.map +1 -1
  106. package/lib/module/ui/screens/steps/RecoverSuccessStep.js +80 -59
  107. package/lib/module/ui/screens/steps/RecoverSuccessStep.js.map +1 -1
  108. package/lib/module/ui/screens/steps/RecoverVerifyStep.js +62 -53
  109. package/lib/module/ui/screens/steps/RecoverVerifyStep.js.map +1 -1
  110. package/lib/module/ui/screens/steps/SignInPasswordStep.js +221 -32
  111. package/lib/module/ui/screens/steps/SignInPasswordStep.js.map +1 -1
  112. package/lib/module/ui/screens/steps/SignInTotpStep.js +78 -51
  113. package/lib/module/ui/screens/steps/SignInTotpStep.js.map +1 -1
  114. package/lib/module/ui/screens/steps/SignInUsernameStep.js +530 -68
  115. package/lib/module/ui/screens/steps/SignInUsernameStep.js.map +1 -1
  116. package/lib/module/ui/screens/steps/SignUpIdentityStep.js +55 -30
  117. package/lib/module/ui/screens/steps/SignUpIdentityStep.js.map +1 -1
  118. package/lib/module/ui/screens/steps/SignUpSecurityStep.js +65 -47
  119. package/lib/module/ui/screens/steps/SignUpSecurityStep.js.map +1 -1
  120. package/lib/module/ui/screens/steps/SignUpSummaryStep.js +84 -146
  121. package/lib/module/ui/screens/steps/SignUpSummaryStep.js.map +1 -1
  122. package/lib/module/ui/screens/steps/SignUpWelcomeStep.js +114 -35
  123. package/lib/module/ui/screens/steps/SignUpWelcomeStep.js.map +1 -1
  124. package/lib/module/ui/stores/authStore.js +16 -20
  125. package/lib/module/ui/stores/authStore.js.map +1 -1
  126. package/lib/module/ui/styles/authStyles.js +2 -1
  127. package/lib/module/ui/styles/authStyles.js.map +1 -1
  128. package/lib/module/ui/styles/index.js +1 -0
  129. package/lib/module/ui/styles/index.js.map +1 -1
  130. package/lib/module/ui/styles/spacing.js +48 -0
  131. package/lib/module/ui/styles/spacing.js.map +1 -0
  132. package/lib/module/utils/validationUtils.js +1 -1
  133. package/lib/typescript/core/OxyServices.d.ts +38 -2
  134. package/lib/typescript/core/OxyServices.d.ts.map +1 -1
  135. package/lib/typescript/i18n/index.d.ts.map +1 -1
  136. package/lib/typescript/ui/components/FontLoader.d.ts +3 -3
  137. package/lib/typescript/ui/components/FontLoader.d.ts.map +1 -1
  138. package/lib/typescript/ui/components/OxyProvider.d.ts +2 -2
  139. package/lib/typescript/ui/components/OxyProvider.d.ts.map +1 -1
  140. package/lib/typescript/ui/components/StepBasedScreen.d.ts.map +1 -1
  141. package/lib/typescript/ui/components/internal/GroupedPillButtons.d.ts.map +1 -1
  142. package/lib/typescript/ui/context/OxyContext.d.ts +1 -0
  143. package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
  144. package/lib/typescript/ui/screens/FileManagementScreen.d.ts +10 -0
  145. package/lib/typescript/ui/screens/FileManagementScreen.d.ts.map +1 -1
  146. package/lib/typescript/ui/screens/SignInScreen.d.ts.map +1 -1
  147. package/lib/typescript/ui/screens/WelcomeNewUserScreen.d.ts.map +1 -1
  148. package/lib/typescript/ui/screens/steps/RecoverRequestStep.d.ts.map +1 -1
  149. package/lib/typescript/ui/screens/steps/RecoverResetPasswordStep.d.ts.map +1 -1
  150. package/lib/typescript/ui/screens/steps/RecoverSuccessStep.d.ts.map +1 -1
  151. package/lib/typescript/ui/screens/steps/RecoverVerifyStep.d.ts.map +1 -1
  152. package/lib/typescript/ui/screens/steps/SignInPasswordStep.d.ts +2 -0
  153. package/lib/typescript/ui/screens/steps/SignInPasswordStep.d.ts.map +1 -1
  154. package/lib/typescript/ui/screens/steps/SignInTotpStep.d.ts.map +1 -1
  155. package/lib/typescript/ui/screens/steps/SignInUsernameStep.d.ts.map +1 -1
  156. package/lib/typescript/ui/screens/steps/SignUpIdentityStep.d.ts.map +1 -1
  157. package/lib/typescript/ui/screens/steps/SignUpSecurityStep.d.ts.map +1 -1
  158. package/lib/typescript/ui/screens/steps/SignUpSummaryStep.d.ts.map +1 -1
  159. package/lib/typescript/ui/screens/steps/SignUpWelcomeStep.d.ts.map +1 -1
  160. package/lib/typescript/ui/stores/authStore.d.ts +7 -3
  161. package/lib/typescript/ui/stores/authStore.d.ts.map +1 -1
  162. package/lib/typescript/ui/styles/authStyles.d.ts +1 -0
  163. package/lib/typescript/ui/styles/authStyles.d.ts.map +1 -1
  164. package/lib/typescript/ui/styles/index.d.ts +1 -0
  165. package/lib/typescript/ui/styles/index.d.ts.map +1 -1
  166. package/lib/typescript/ui/styles/spacing.d.ts +43 -0
  167. package/lib/typescript/ui/styles/spacing.d.ts.map +1 -0
  168. package/lib/typescript/utils/validationUtils.d.ts +1 -1
  169. package/package.json +1 -1
  170. package/src/core/OxyServices.ts +96 -10
  171. package/src/i18n/index.ts +36 -0
  172. package/src/i18n/locales/ar-SA.json +128 -0
  173. package/src/i18n/locales/ca-ES.json +128 -0
  174. package/src/i18n/locales/de-DE.json +128 -0
  175. package/src/i18n/locales/en-US.json +85 -12
  176. package/src/i18n/locales/es-ES.json +58 -6
  177. package/src/i18n/locales/fr-FR.json +128 -0
  178. package/src/i18n/locales/it-IT.json +128 -0
  179. package/src/i18n/locales/ja-JP.json +127 -0
  180. package/src/i18n/locales/ko-KR.json +128 -0
  181. package/src/i18n/locales/pt-PT.json +128 -0
  182. package/src/i18n/locales/zh-CN.json +128 -0
  183. package/src/ui/components/FontLoader.tsx +17 -37
  184. package/src/ui/components/OxyProvider.tsx +14 -13
  185. package/src/ui/components/StepBasedScreen.tsx +66 -43
  186. package/src/ui/components/internal/GroupedPillButtons.tsx +15 -31
  187. package/src/ui/components/internal/PinInput.tsx +2 -2
  188. package/src/ui/context/OxyContext.tsx +404 -285
  189. package/src/ui/screens/FileManagementScreen.tsx +81 -4
  190. package/src/ui/screens/SignInScreen.tsx +59 -36
  191. package/src/ui/screens/WelcomeNewUserScreen.tsx +102 -91
  192. package/src/ui/screens/internal/SignInUsernameStep.tsx +1 -1
  193. package/src/ui/screens/steps/RecoverRequestStep.tsx +34 -24
  194. package/src/ui/screens/steps/RecoverResetPasswordStep.tsx +65 -36
  195. package/src/ui/screens/steps/RecoverSuccessStep.tsx +71 -47
  196. package/src/ui/screens/steps/RecoverVerifyStep.tsx +60 -50
  197. package/src/ui/screens/steps/SignInPasswordStep.tsx +191 -29
  198. package/src/ui/screens/steps/SignInTotpStep.tsx +68 -34
  199. package/src/ui/screens/steps/SignInUsernameStep.tsx +586 -57
  200. package/src/ui/screens/steps/SignUpIdentityStep.tsx +49 -35
  201. package/src/ui/screens/steps/SignUpSecurityStep.tsx +56 -39
  202. package/src/ui/screens/steps/SignUpSummaryStep.tsx +99 -89
  203. package/src/ui/screens/steps/SignUpWelcomeStep.tsx +88 -20
  204. package/src/ui/stores/authStore.ts +15 -19
  205. package/src/ui/styles/authStyles.ts +2 -1
  206. package/src/ui/styles/index.ts +1 -0
  207. package/src/ui/styles/spacing.ts +46 -0
  208. package/src/utils/validationUtils.ts +1 -1
@@ -1,12 +1,25 @@
1
1
  import type React from 'react';
2
2
  import type { RouteName } from '../../navigation/routes';
3
- import { useRef, useEffect } from 'react';
4
- import { View, Text } from 'react-native';
3
+ import { useRef, useEffect, useMemo, useState, useCallback } from 'react';
4
+ import {
5
+ View,
6
+ Text,
7
+ Platform,
8
+ StyleSheet,
9
+ type ViewStyle,
10
+ type TextStyle,
11
+ ActivityIndicator,
12
+ } from 'react-native';
5
13
  import { Ionicons } from '@expo/vector-icons';
6
14
  import HighFive from '../../../assets/illustrations/HighFive';
7
15
  import GroupedPillButtons from '../../components/internal/GroupedPillButtons';
8
16
  import TextField from '../../components/internal/TextField';
9
17
  import { useI18n } from '../../hooks/useI18n';
18
+ import { STEP_GAP, STEP_INNER_GAP, stepStyles } from '../../styles/spacing';
19
+ import Avatar from '../../components/Avatar';
20
+ import { TouchableOpacity } from 'react-native';
21
+ import { useOxy } from '../../context/OxyContext';
22
+ import { toast } from '../../../lib/sonner';
10
23
 
11
24
  interface SignInUsernameStepProps {
12
25
  // Common props from StepBasedScreen
@@ -43,9 +56,22 @@ interface SignInUsernameStepProps {
43
56
  validateUsername: (username: string) => Promise<boolean>;
44
57
  }
45
58
 
59
+ interface QuickAccount {
60
+ sessionId: string;
61
+ username: string;
62
+ displayName: string;
63
+ avatar?: string;
64
+ }
65
+
66
+ const MAX_QUICK_ACCOUNTS = 3;
67
+
68
+ const getThemeMode = (theme: string | undefined): 'light' | 'dark' =>
69
+ theme === 'dark' ? 'dark' : 'light';
70
+
46
71
  const SignInUsernameStep: React.FC<SignInUsernameStepProps> = ({
47
72
  colors,
48
73
  styles,
74
+ theme,
49
75
  navigate,
50
76
  nextStep,
51
77
  username,
@@ -61,83 +87,248 @@ const SignInUsernameStep: React.FC<SignInUsernameStepProps> = ({
61
87
  }) => {
62
88
  const inputRef = useRef<any>(null);
63
89
  const { t } = useI18n();
90
+ const { sessions, activeSessionId, switchSession, oxyServices } = useOxy();
91
+ const baseStyles = stepStyles;
92
+ const webShadowReset = Platform.OS === 'web' ? ({ boxShadow: 'none' } as any) : null;
93
+ const themeMode = getThemeMode(theme);
94
+ const [quickAccounts, setQuickAccounts] = useState<QuickAccount[]>([]);
95
+ const [loadingAccounts, setLoadingAccounts] = useState(false);
96
+ const [switchingSessionId, setSwitchingSessionId] = useState<string | null>(null);
97
+ const [showAccounts, setShowAccounts] = useState(false);
98
+
99
+ const otherSessions = useMemo(
100
+ () => (sessions ?? []).filter((session) => session.sessionId !== activeSessionId),
101
+ [sessions, activeSessionId]
102
+ );
103
+
104
+ // Show accounts if we have any sessions (either in add account mode or just have saved sessions)
105
+ const hasSessions = (sessions ?? []).length > 0;
106
+
107
+ // Use other sessions if in add account mode, otherwise show all sessions
108
+ const sessionsToShow = isAddAccountMode ? otherSessions : (sessions ?? []);
109
+
110
+ // Debug logging
111
+ if (__DEV__) {
112
+ console.log('SignInUsernameStep - Debug:', {
113
+ isAddAccountMode,
114
+ hasSessions,
115
+ sessionsCount: sessions?.length ?? 0,
116
+ otherSessionsCount: otherSessions.length,
117
+ sessionsToShowCount: sessionsToShow.length,
118
+ activeSessionId,
119
+ quickAccountsCount: quickAccounts.length,
120
+ loadingAccounts,
121
+ });
122
+ }
64
123
 
65
- // Monitor username prop changes
66
124
  useEffect(() => {
67
- console.log('👀 SignInUsernameStep username prop changed:', username);
68
- }, [username]);
125
+ let cancelled = false;
126
+
127
+ const loadQuickAccounts = async () => {
128
+ // Show accounts if we have sessions and services available
129
+ // In add account mode, show other sessions; otherwise show all sessions
130
+ if (!hasSessions || !oxyServices) {
131
+ if (!cancelled) {
132
+ setQuickAccounts([]);
133
+ setLoadingAccounts(false);
134
+ }
135
+ return;
136
+ }
137
+
138
+ // If in add account mode and no other sessions, don't show anything
139
+ if (isAddAccountMode && sessionsToShow.length === 0) {
140
+ if (!cancelled) {
141
+ setQuickAccounts([]);
142
+ setLoadingAccounts(false);
143
+ }
144
+ return;
145
+ }
146
+
147
+ // If not in add account mode and no sessions, don't show anything
148
+ if (!isAddAccountMode && sessionsToShow.length === 0) {
149
+ if (!cancelled) {
150
+ setQuickAccounts([]);
151
+ setLoadingAccounts(false);
152
+ }
153
+ return;
154
+ }
155
+
156
+ setLoadingAccounts(true);
157
+ const targetSessions = sessionsToShow.slice(0, MAX_QUICK_ACCOUNTS);
158
+
159
+ try {
160
+ const results = await Promise.all(
161
+ targetSessions.map(async (session): Promise<QuickAccount | null> => {
162
+ try {
163
+ const profile = await oxyServices.getUserBySession(session.sessionId);
164
+ const displayName =
165
+ profile?.name?.full ||
166
+ profile?.name?.first ||
167
+ profile?.username ||
168
+ 'Account';
169
+
170
+ return {
171
+ sessionId: session.sessionId,
172
+ username: profile?.username ?? 'account',
173
+ displayName,
174
+ avatar: profile?.avatar,
175
+ };
176
+ } catch (error) {
177
+ if (__DEV__) {
178
+ console.error(
179
+ `Failed to load profile for session ${session.sessionId}`,
180
+ error
181
+ );
182
+ }
183
+ return null;
184
+ }
185
+ })
186
+ );
187
+
188
+ if (!cancelled) {
189
+ setQuickAccounts(results.filter((item): item is QuickAccount => Boolean(item)));
190
+ }
191
+ } finally {
192
+ if (!cancelled) {
193
+ setLoadingAccounts(false);
194
+ }
195
+ }
196
+ };
197
+
198
+ void loadQuickAccounts();
199
+
200
+ return () => {
201
+ cancelled = true;
202
+ };
203
+ }, [hasSessions, sessionsToShow, oxyServices, isAddAccountMode]);
204
+
205
+ const handleSwitchAccount = useCallback(
206
+ async (sessionId: string) => {
207
+ setSwitchingSessionId(sessionId);
208
+ try {
209
+ await switchSession(sessionId);
210
+ const switchedAccount =
211
+ quickAccounts.find((account) => account.sessionId === sessionId) ?? null;
212
+ const successMessage =
213
+ t('signin.status.accountSwitched', {
214
+ name: switchedAccount?.displayName ?? t('signin.actions.openAccountSwitcher'),
215
+ }) || 'Account switched';
216
+ toast.success(successMessage);
217
+ } catch (error) {
218
+ if (__DEV__) {
219
+ console.error('Failed to switch account:', error);
220
+ }
221
+ toast.error(
222
+ t('signin.actions.switchAccountFailed') || 'Unable to switch accounts. Please try again.'
223
+ );
224
+ } finally {
225
+ setSwitchingSessionId(null);
226
+ }
227
+ },
228
+ [quickAccounts, switchSession, t]
229
+ );
230
+
231
+ const otherAccountsCount = sessionsToShow.length;
232
+
233
+ // Get all accounts for avatar display (current user + quick accounts)
234
+ const allAccountsForAvatars = useMemo(() => {
235
+ const accounts: Array<{
236
+ sessionId: string;
237
+ displayName: string;
238
+ username?: string;
239
+ avatar?: string;
240
+ isCurrent?: boolean;
241
+ }> = [];
242
+
243
+ // Add current user if in add account mode
244
+ if (isAddAccountMode && user) {
245
+ accounts.push({
246
+ sessionId: activeSessionId || '',
247
+ displayName: user.name?.full || user.username || 'Account',
248
+ username: user.username,
249
+ avatar: user.avatar,
250
+ isCurrent: true,
251
+ });
252
+ }
253
+
254
+ // Add quick accounts
255
+ quickAccounts.forEach((account) => {
256
+ accounts.push({
257
+ sessionId: account.sessionId,
258
+ displayName: account.displayName,
259
+ username: account.username,
260
+ avatar: account.avatar,
261
+ isCurrent: account.sessionId === activeSessionId,
262
+ });
263
+ });
264
+
265
+ return accounts;
266
+ }, [isAddAccountMode, user, quickAccounts, activeSessionId]);
69
267
 
70
268
  const handleUsernameChange = (text: string) => {
71
- console.log('📝 Username input changed:', text);
72
- setUsername(text);
269
+ // Text is already filtered by formatValue prop, but ensure it's clean
270
+ const filteredText = text.replace(/[^a-zA-Z0-9]/g, '');
271
+ setUsername(filteredText);
73
272
  if (errorMessage) setErrorMessage('');
74
273
  };
75
274
 
76
275
  const handleContinue = async () => {
77
- console.log('🚀 Continue button pressed, username:', username);
78
-
79
276
  const trimmedUsername = username?.trim() || '';
80
277
 
81
278
  if (!trimmedUsername) {
82
- console.log(' Username is empty');
83
- setErrorMessage('Please enter your username.');
279
+ setErrorMessage(t('signin.username.required') || 'Please enter your username.');
84
280
  setTimeout(() => inputRef.current?.focus(), 0);
85
281
  return;
86
282
  }
87
283
 
88
- if (trimmedUsername.length < 2) {
89
- console.log(' Username too short');
90
- setErrorMessage('Username must be at least 3 characters.');
284
+ if (trimmedUsername.length < 3) {
285
+ setErrorMessage(t('signin.username.minLength') || 'Username must be at least 3 characters.');
91
286
  return;
92
287
  }
93
288
 
94
- console.log('🔍 Starting username validation...');
95
289
  try {
96
290
  // Validate the username before proceeding
97
291
  const isValid = await validateUsername(trimmedUsername);
98
- console.log('📊 Validation result:', isValid);
99
292
 
100
293
  if (isValid) {
101
- console.log('✅ Validation passed, proceeding to next step');
102
294
  nextStep();
103
- } else {
104
- console.log('❌ Validation failed, staying on current step');
105
295
  }
106
296
  } catch (error) {
107
- console.error('🚨 Error during validation:', error);
297
+ if (__DEV__) console.error('Error during username validation:', error);
108
298
  setErrorMessage('Unable to validate username. Please try again.');
109
299
  }
110
300
  };
111
301
 
112
302
  return (
113
303
  <>
114
- <HighFive width={100} height={100} />
115
- <View style={styles.modernHeader}>
116
- <Text style={[styles.modernTitle, { color: colors.text }]}>
117
- {isAddAccountMode ? t('signin.addAccountTitle') : t('signin.title')}
304
+ <View style={[baseStyles.container, baseStyles.sectionSpacing, { alignItems: 'flex-start', position: 'relative' }]}>
305
+ <HighFive width={100} height={100} />
306
+ <TouchableOpacity
307
+ style={[stylesheet.languageButton, { backgroundColor: colors.inputBackground }]}
308
+ onPress={() => navigate('LanguageSelector')}
309
+ activeOpacity={0.7}
310
+ >
311
+ <Ionicons name="globe-outline" size={20} color={colors.primary} />
312
+ </TouchableOpacity>
313
+ </View>
314
+ <View style={[baseStyles.container, baseStyles.sectionSpacing, baseStyles.header]}>
315
+ <Text style={[styles.modernTitle, baseStyles.title, { color: colors.text, marginBottom: 0, marginTop: 0 }]}>
316
+ {t('signin.title')}
118
317
  </Text>
119
- <Text style={[styles.modernSubtitle, { color: colors.secondaryText }]}>
120
- {isAddAccountMode ? t('signin.addAccountSubtitle') : t('signin.subtitle')}
318
+ <Text style={[styles.modernSubtitle, baseStyles.subtitle, { color: colors.secondaryText, marginBottom: 0, marginTop: 0 }]}>
319
+ {t('signin.subtitle')}
121
320
  </Text>
122
321
  </View>
123
322
 
124
- {isAddAccountMode && user && (
125
- <View style={[styles.modernInfoCard, { backgroundColor: colors.inputBackground }]}>
126
- <Ionicons name="information-circle" size={20} color={colors.primary} />
127
- <Text style={[styles.modernInfoText, { color: colors.text }]}>
128
- {t('signin.currentlySignedInAs', { username: user.username }) || 'Currently signed in as '}
129
- <Text style={{ fontWeight: 'bold' }}>{user.username}</Text>
130
- </Text>
131
- </View>
132
- )}
133
-
134
- <View style={styles.modernInputContainer}>
323
+ <View style={[baseStyles.container, baseStyles.sectionSpacing]}>
135
324
  <TextField
136
325
  ref={inputRef}
137
326
  label={t('common.labels.username')}
138
327
  leading={<Ionicons name="person-outline" size={24} color={colors.secondaryText} />}
139
328
  value={username}
140
329
  onChangeText={handleUsernameChange}
330
+ formatValue={(text) => text.replace(/[^a-zA-Z0-9]/g, '')}
331
+ maxLength={30}
141
332
  autoCapitalize="none"
142
333
  autoCorrect={false}
143
334
  testID="username-input"
@@ -145,33 +336,371 @@ const SignInUsernameStep: React.FC<SignInUsernameStepProps> = ({
145
336
  error={validationStatus === 'invalid' ? errorMessage : undefined}
146
337
  loading={validationStatus === 'validating'}
147
338
  success={validationStatus === 'valid'}
339
+ helperText={t('signin.username.helper') || '3-30 characters, letters and numbers only'}
148
340
  onSubmitEditing={() => handleContinue()}
149
341
  autoFocus
342
+ accessibilityLabel={t('common.labels.username')}
343
+ accessibilityHint={t('signin.username.helper') || 'Enter your username, 3-30 characters, letters and numbers only'}
344
+ style={{ marginBottom: 0 }}
150
345
  />
151
346
  </View>
152
347
 
153
- <GroupedPillButtons
154
- buttons={[
155
- {
156
- text: t('common.links.signUp'),
157
- onPress: () => navigate('SignUp'),
158
- icon: 'person-add',
159
- variant: 'transparent',
160
- },
161
- {
162
- text: t('common.actions.continue'),
163
- onPress: handleContinue,
164
- icon: 'arrow-forward',
165
- variant: 'primary',
166
- loading: isValidating,
167
- disabled: !username || username.trim().length < 2 || isValidating,
168
- testID: 'username-next-button',
169
- },
170
- ]}
171
- colors={colors}
172
- />
348
+ {/* "Or" divider */}
349
+ {((isAddAccountMode && user) || (hasSessions && sessionsToShow.length > 0)) && (
350
+ <View style={[baseStyles.container, baseStyles.sectionSpacing, stylesheet.dividerContainer]}>
351
+ <View style={[stylesheet.dividerLine, { backgroundColor: colors.border }]} />
352
+ <Text style={[stylesheet.dividerText, { color: colors.secondaryText }]}>
353
+ {t('signin.or') || 'or'}
354
+ </Text>
355
+ <View style={[stylesheet.dividerLine, { backgroundColor: colors.border }]} />
356
+ </View>
357
+ )}
358
+
359
+ {(isAddAccountMode && user) || (hasSessions && sessionsToShow.length > 0) ? (
360
+ <View style={[baseStyles.container, baseStyles.sectionSpacing]}>
361
+ <TouchableOpacity
362
+ style={[
363
+ stylesheet.toggleButton,
364
+ {
365
+ backgroundColor: colors.inputBackground,
366
+ },
367
+ ]}
368
+ onPress={() => setShowAccounts(!showAccounts)}
369
+ activeOpacity={0.7}
370
+ >
371
+ <View style={stylesheet.toggleButtonContent}>
372
+ <Ionicons
373
+ name={showAccounts ? 'chevron-down' : 'chevron-forward'}
374
+ size={18}
375
+ color={colors.primary}
376
+ />
377
+ <Text style={[stylesheet.toggleButtonText, { color: colors.text }]}>
378
+ {t('signin.alreadySignedInWith') || 'Already signed in with'}
379
+ </Text>
380
+ {allAccountsForAvatars.length > 0 && (
381
+ <View style={stylesheet.avatarsContainer}>
382
+ {allAccountsForAvatars.slice(0, 5).map((account, index) => (
383
+ <View
384
+ key={account.sessionId}
385
+ style={[
386
+ stylesheet.avatarWrapper,
387
+ account.isCurrent && stylesheet.currentAvatarWrapper,
388
+ index > 0 && { marginLeft: -12 },
389
+ { zIndex: Math.min(allAccountsForAvatars.length, 5) - index },
390
+ { borderColor: colors.inputBackground || colors.background || '#FFFFFF' },
391
+ ]}
392
+ >
393
+ <Avatar
394
+ name={account.displayName}
395
+ text={account.displayName.charAt(0).toUpperCase()}
396
+ size={28}
397
+ theme={themeMode}
398
+ backgroundColor={colors.primary}
399
+ uri={account.avatar && oxyServices ? oxyServices.getFileDownloadUrl(account.avatar, 'thumb') : undefined}
400
+ />
401
+ </View>
402
+ ))}
403
+ </View>
404
+ )}
405
+ {!showAccounts && (quickAccounts.length > 0 || otherAccountsCount > 0) && allAccountsForAvatars.length === 0 && (
406
+ <View style={[stylesheet.accountCountBadge, { backgroundColor: `${colors.primary}15` }]}>
407
+ <Text style={[stylesheet.accountCountText, { color: colors.primary }]}>
408
+ {otherAccountsCount + (isAddAccountMode && user ? 1 : 0)}
409
+ </Text>
410
+ </View>
411
+ )}
412
+ </View>
413
+ </TouchableOpacity>
414
+
415
+ {showAccounts && (
416
+ <View style={stylesheet.accountsList}>
417
+ {loadingAccounts && quickAccounts.length === 0 && !isAddAccountMode ? (
418
+ <View style={stylesheet.accountItem}>
419
+ <ActivityIndicator color={colors.primary} size="small" />
420
+ </View>
421
+ ) : (
422
+ <>
423
+ {/* Show current account when in add account mode */}
424
+ {isAddAccountMode && user && (
425
+ <View
426
+ style={[
427
+ stylesheet.accountItem,
428
+ {
429
+ backgroundColor: colors.inputBackground,
430
+ },
431
+ ]}
432
+ >
433
+ <View style={[stylesheet.accountItemAvatarWrapper, { borderColor: colors.inputBackground || colors.background || '#FFFFFF' }]}>
434
+ <Avatar
435
+ name={user.name?.full || user.username}
436
+ text={(user.name?.full || user.username || 'U')
437
+ .slice(0, 1)
438
+ .toUpperCase()}
439
+ size={36}
440
+ theme={themeMode}
441
+ backgroundColor={colors.primary}
442
+ uri={user.avatar && oxyServices ? oxyServices.getFileDownloadUrl(user.avatar, 'thumb') : undefined}
443
+ />
444
+ </View>
445
+ <View style={stylesheet.accountItemText}>
446
+ <Text
447
+ style={[stylesheet.accountItemName, { color: colors.text }]}
448
+ numberOfLines={1}
449
+ >
450
+ {user.name?.full || user.username}
451
+ </Text>
452
+ {user.username && (
453
+ <Text
454
+ style={[
455
+ stylesheet.accountItemUsername,
456
+ { color: colors.secondaryText },
457
+ ]}
458
+ numberOfLines={1}
459
+ >
460
+ @{user.username}
461
+ </Text>
462
+ )}
463
+ </View>
464
+ <View style={[stylesheet.currentAccountBadgeContainer, { backgroundColor: `${colors.primary}15` }]}>
465
+ <Text style={[stylesheet.currentAccountBadge, { color: colors.primary }]}>
466
+ {t('signin.currentAccount') || 'Current'}
467
+ </Text>
468
+ </View>
469
+ </View>
470
+ )}
471
+ {quickAccounts.map((account) => (
472
+ <TouchableOpacity
473
+ key={account.sessionId}
474
+ style={[
475
+ stylesheet.accountItem,
476
+ {
477
+ backgroundColor: colors.inputBackground,
478
+ },
479
+ switchingSessionId === account.sessionId && stylesheet.accountItemLoading,
480
+ ]}
481
+ onPress={() => handleSwitchAccount(account.sessionId)}
482
+ disabled={switchingSessionId === account.sessionId}
483
+ activeOpacity={0.7}
484
+ >
485
+ {switchingSessionId === account.sessionId ? (
486
+ <ActivityIndicator color={colors.primary} size="small" />
487
+ ) : (
488
+ <>
489
+ <View style={[stylesheet.accountItemAvatarWrapper, { borderColor: colors.inputBackground || colors.background || '#FFFFFF' }]}>
490
+ <Avatar
491
+ name={account.displayName}
492
+ text={account.displayName.charAt(0).toUpperCase()}
493
+ size={36}
494
+ theme={themeMode}
495
+ backgroundColor={colors.primary}
496
+ uri={account.avatar && oxyServices ? oxyServices.getFileDownloadUrl(account.avatar, 'thumb') : undefined}
497
+ />
498
+ </View>
499
+ <View style={stylesheet.accountItemText}>
500
+ <Text
501
+ style={[stylesheet.accountItemName, { color: colors.text }]}
502
+ numberOfLines={1}
503
+ >
504
+ {account.displayName}
505
+ </Text>
506
+ {account.username && (
507
+ <Text
508
+ style={[
509
+ stylesheet.accountItemUsername,
510
+ { color: colors.secondaryText },
511
+ ]}
512
+ numberOfLines={1}
513
+ >
514
+ @{account.username}
515
+ </Text>
516
+ )}
517
+ </View>
518
+ </>
519
+ )}
520
+ </TouchableOpacity>
521
+ ))}
522
+ {otherAccountsCount > MAX_QUICK_ACCOUNTS && (
523
+ <TouchableOpacity
524
+ style={[
525
+ stylesheet.accountItem,
526
+ stylesheet.viewAllItem,
527
+ {
528
+ backgroundColor: colors.inputBackground,
529
+ },
530
+ ]}
531
+ onPress={() => navigate('AccountSwitcher')}
532
+ activeOpacity={0.7}
533
+ >
534
+ <Ionicons name="chevron-forward" size={18} color={colors.primary} />
535
+ <Text style={[stylesheet.viewAllText, { color: colors.primary }]}>
536
+ {t('signin.viewAllAccounts', {
537
+ count: otherAccountsCount - MAX_QUICK_ACCOUNTS,
538
+ }) || `View ${otherAccountsCount - MAX_QUICK_ACCOUNTS} more`}
539
+ </Text>
540
+ </TouchableOpacity>
541
+ )}
542
+ </>
543
+ )}
544
+ </View>
545
+ )}
546
+ </View>
547
+ ) : null}
548
+
549
+ <View style={[baseStyles.container, baseStyles.sectionSpacing, baseStyles.buttonContainer]}>
550
+ <GroupedPillButtons
551
+ buttons={[
552
+ {
553
+ text: t('common.links.signUp'),
554
+ onPress: () => navigate('SignUp'),
555
+ icon: 'person-add',
556
+ variant: 'transparent',
557
+ },
558
+ {
559
+ text: t('common.actions.continue'),
560
+ onPress: handleContinue,
561
+ icon: 'arrow-forward',
562
+ variant: 'primary',
563
+ loading: isValidating,
564
+ disabled: !username || username.trim().length < 3 || isValidating,
565
+ testID: 'username-next-button',
566
+ },
567
+ ]}
568
+ colors={colors}
569
+ />
570
+ </View>
173
571
  </>
174
572
  );
175
573
  };
176
574
 
177
575
  export default SignInUsernameStep;
576
+
577
+ const stylesheet = StyleSheet.create({
578
+ toggleButton: {
579
+ borderRadius: 24,
580
+ borderWidth: 0,
581
+ },
582
+ toggleButtonContent: {
583
+ flexDirection: 'row',
584
+ alignItems: 'center',
585
+ paddingHorizontal: 12,
586
+ paddingVertical: 10,
587
+ minHeight: 48,
588
+ gap: 10,
589
+ },
590
+ toggleButtonText: {
591
+ fontSize: 14,
592
+ fontWeight: '500',
593
+ },
594
+ accountCountBadge: {
595
+ paddingHorizontal: 8,
596
+ paddingVertical: 3,
597
+ borderRadius: 16,
598
+ minWidth: 22,
599
+ alignItems: 'center',
600
+ },
601
+ accountCountText: {
602
+ fontSize: 11,
603
+ fontWeight: '600',
604
+ },
605
+ accountsList: {
606
+ gap: 6,
607
+ marginTop: 6,
608
+ },
609
+ accountItem: {
610
+ flexDirection: 'row',
611
+ alignItems: 'center',
612
+ paddingHorizontal: 12,
613
+ paddingVertical: 10,
614
+ borderRadius: 24,
615
+ borderWidth: 0,
616
+ gap: 12,
617
+ minHeight: 56,
618
+ },
619
+ accountItemLoading: {
620
+ justifyContent: 'center',
621
+ paddingHorizontal: 16,
622
+ },
623
+ accountItemText: {
624
+ flex: 1,
625
+ },
626
+ accountItemName: {
627
+ fontSize: 15,
628
+ fontWeight: '500',
629
+ marginBottom: 2,
630
+ },
631
+ accountItemUsername: {
632
+ fontSize: 12,
633
+ },
634
+ viewAllItem: {
635
+ justifyContent: 'center',
636
+ paddingVertical: 10,
637
+ },
638
+ viewAllText: {
639
+ fontSize: 14,
640
+ fontWeight: '500',
641
+ marginLeft: 4,
642
+ },
643
+ currentAccountBadgeContainer: {
644
+ paddingHorizontal: 10,
645
+ paddingVertical: 4,
646
+ borderRadius: 16,
647
+ },
648
+ currentAccountBadge: {
649
+ fontSize: 10,
650
+ fontWeight: '600',
651
+ letterSpacing: 0.3,
652
+ },
653
+ dividerContainer: {
654
+ flexDirection: 'row',
655
+ alignItems: 'center',
656
+ marginVertical: 8,
657
+ },
658
+ dividerLine: {
659
+ flex: 1,
660
+ height: 1,
661
+ },
662
+ dividerText: {
663
+ fontSize: 14,
664
+ fontWeight: '500',
665
+ paddingHorizontal: 16,
666
+ textTransform: 'lowercase',
667
+ },
668
+ avatarsContainer: {
669
+ flexDirection: 'row',
670
+ alignItems: 'center',
671
+ marginLeft: 'auto',
672
+ },
673
+ avatarWrapper: {
674
+ position: 'relative',
675
+ borderRadius: 20,
676
+ borderWidth: 3,
677
+ },
678
+ currentAvatarWrapper: {
679
+ borderWidth: 3,
680
+ },
681
+ currentBadge: {
682
+ position: 'absolute',
683
+ bottom: -2,
684
+ right: -2,
685
+ width: 14,
686
+ height: 14,
687
+ borderRadius: 7,
688
+ alignItems: 'center',
689
+ justifyContent: 'center',
690
+ borderWidth: 0,
691
+ },
692
+ accountItemAvatarWrapper: {
693
+ borderRadius: 20,
694
+ borderWidth: 3,
695
+ },
696
+ languageButton: {
697
+ position: 'absolute',
698
+ top: 0,
699
+ right: 0,
700
+ width: 40,
701
+ height: 40,
702
+ borderRadius: 20,
703
+ alignItems: 'center',
704
+ justifyContent: 'center',
705
+ },
706
+ });