@oxyhq/services 5.13.1 → 5.13.2

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 (204) hide show
  1. package/README.md +71 -0
  2. package/lib/commonjs/core/HttpClient.js +238 -0
  3. package/lib/commonjs/core/HttpClient.js.map +1 -0
  4. package/lib/commonjs/core/OxyServices.js +530 -332
  5. package/lib/commonjs/core/OxyServices.js.map +1 -1
  6. package/lib/commonjs/core/RequestManager.js +199 -0
  7. package/lib/commonjs/core/RequestManager.js.map +1 -0
  8. package/lib/commonjs/core/index.js +38 -1
  9. package/lib/commonjs/core/index.js.map +1 -1
  10. package/lib/commonjs/index.js +36 -0
  11. package/lib/commonjs/index.js.map +1 -1
  12. package/lib/commonjs/ui/components/Avatar.js +94 -27
  13. package/lib/commonjs/ui/components/Avatar.js.map +1 -1
  14. package/lib/commonjs/ui/components/FollowButton.js +1 -0
  15. package/lib/commonjs/ui/components/FollowButton.js.map +1 -1
  16. package/lib/commonjs/ui/components/internal/TextField.js +13 -8
  17. package/lib/commonjs/ui/components/internal/TextField.js.map +1 -1
  18. package/lib/commonjs/ui/context/OxyContext.js +183 -224
  19. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  20. package/lib/commonjs/ui/hooks/useSessionSocket.js +80 -22
  21. package/lib/commonjs/ui/hooks/useSessionSocket.js.map +1 -1
  22. package/lib/commonjs/ui/index.js +4 -1
  23. package/lib/commonjs/ui/index.js.map +1 -1
  24. package/lib/commonjs/ui/screens/AccountSettingsScreen.js +32 -2
  25. package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
  26. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +101 -59
  27. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +1 -1
  28. package/lib/commonjs/ui/screens/FileManagementScreen.js +3 -2
  29. package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
  30. package/lib/commonjs/ui/screens/LanguageSelectorScreen.js +75 -117
  31. package/lib/commonjs/ui/screens/LanguageSelectorScreen.js.map +1 -1
  32. package/lib/commonjs/ui/screens/SignInScreen.js +0 -11
  33. package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
  34. package/lib/commonjs/ui/screens/SignUpScreen.js +14 -16
  35. package/lib/commonjs/ui/screens/SignUpScreen.js.map +1 -1
  36. package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js +50 -18
  37. package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js.map +1 -1
  38. package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js +10 -10
  39. package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js.map +1 -1
  40. package/lib/commonjs/ui/screens/steps/SignInPasswordStep.js +16 -26
  41. package/lib/commonjs/ui/screens/steps/SignInPasswordStep.js.map +1 -1
  42. package/lib/commonjs/ui/screens/steps/SignInUsernameStep.js +104 -212
  43. package/lib/commonjs/ui/screens/steps/SignInUsernameStep.js.map +1 -1
  44. package/lib/commonjs/ui/stores/accountStore.js +237 -0
  45. package/lib/commonjs/ui/stores/accountStore.js.map +1 -0
  46. package/lib/commonjs/ui/stores/authStore.js +2 -1
  47. package/lib/commonjs/ui/stores/authStore.js.map +1 -1
  48. package/lib/commonjs/ui/styles/authStyles.js +14 -7
  49. package/lib/commonjs/ui/styles/authStyles.js.map +1 -1
  50. package/lib/commonjs/utils/asyncUtils.js +9 -22
  51. package/lib/commonjs/utils/asyncUtils.js.map +1 -1
  52. package/lib/commonjs/utils/cache.js +259 -0
  53. package/lib/commonjs/utils/cache.js.map +1 -0
  54. package/lib/commonjs/utils/index.js +99 -0
  55. package/lib/commonjs/utils/index.js.map +1 -1
  56. package/lib/commonjs/utils/languageUtils.js +159 -0
  57. package/lib/commonjs/utils/languageUtils.js.map +1 -0
  58. package/lib/commonjs/utils/requestUtils.js +217 -0
  59. package/lib/commonjs/utils/requestUtils.js.map +1 -0
  60. package/lib/commonjs/utils/sessionUtils.js +191 -0
  61. package/lib/commonjs/utils/sessionUtils.js.map +1 -0
  62. package/lib/module/core/HttpClient.js +232 -0
  63. package/lib/module/core/HttpClient.js.map +1 -0
  64. package/lib/module/core/OxyServices.js +528 -326
  65. package/lib/module/core/OxyServices.js.map +1 -1
  66. package/lib/module/core/RequestManager.js +194 -0
  67. package/lib/module/core/RequestManager.js.map +1 -0
  68. package/lib/module/core/index.js +2 -0
  69. package/lib/module/core/index.js.map +1 -1
  70. package/lib/module/index.js +2 -0
  71. package/lib/module/index.js.map +1 -1
  72. package/lib/module/ui/components/Avatar.js +94 -27
  73. package/lib/module/ui/components/Avatar.js.map +1 -1
  74. package/lib/module/ui/components/FollowButton.js +1 -0
  75. package/lib/module/ui/components/FollowButton.js.map +1 -1
  76. package/lib/module/ui/components/internal/TextField.js +13 -8
  77. package/lib/module/ui/components/internal/TextField.js.map +1 -1
  78. package/lib/module/ui/context/OxyContext.js +182 -223
  79. package/lib/module/ui/context/OxyContext.js.map +1 -1
  80. package/lib/module/ui/hooks/useSessionSocket.js +80 -22
  81. package/lib/module/ui/hooks/useSessionSocket.js.map +1 -1
  82. package/lib/module/ui/index.js +4 -2
  83. package/lib/module/ui/index.js.map +1 -1
  84. package/lib/module/ui/screens/AccountSettingsScreen.js +33 -2
  85. package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
  86. package/lib/module/ui/screens/AccountSwitcherScreen.js +102 -60
  87. package/lib/module/ui/screens/AccountSwitcherScreen.js.map +1 -1
  88. package/lib/module/ui/screens/FileManagementScreen.js +3 -2
  89. package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
  90. package/lib/module/ui/screens/LanguageSelectorScreen.js +73 -117
  91. package/lib/module/ui/screens/LanguageSelectorScreen.js.map +1 -1
  92. package/lib/module/ui/screens/SignInScreen.js +0 -11
  93. package/lib/module/ui/screens/SignInScreen.js.map +1 -1
  94. package/lib/module/ui/screens/SignUpScreen.js +14 -16
  95. package/lib/module/ui/screens/SignUpScreen.js.map +1 -1
  96. package/lib/module/ui/screens/WelcomeNewUserScreen.js +50 -18
  97. package/lib/module/ui/screens/WelcomeNewUserScreen.js.map +1 -1
  98. package/lib/module/ui/screens/internal/SignInPasswordStep.js +10 -10
  99. package/lib/module/ui/screens/internal/SignInPasswordStep.js.map +1 -1
  100. package/lib/module/ui/screens/steps/SignInPasswordStep.js +16 -26
  101. package/lib/module/ui/screens/steps/SignInPasswordStep.js.map +1 -1
  102. package/lib/module/ui/screens/steps/SignInUsernameStep.js +105 -214
  103. package/lib/module/ui/screens/steps/SignInUsernameStep.js.map +1 -1
  104. package/lib/module/ui/stores/accountStore.js +229 -0
  105. package/lib/module/ui/stores/accountStore.js.map +1 -0
  106. package/lib/module/ui/stores/authStore.js +2 -1
  107. package/lib/module/ui/stores/authStore.js.map +1 -1
  108. package/lib/module/ui/styles/authStyles.js +14 -7
  109. package/lib/module/ui/styles/authStyles.js.map +1 -1
  110. package/lib/module/utils/asyncUtils.js +10 -22
  111. package/lib/module/utils/asyncUtils.js.map +1 -1
  112. package/lib/module/utils/cache.js +250 -0
  113. package/lib/module/utils/cache.js.map +1 -0
  114. package/lib/module/utils/index.js +7 -0
  115. package/lib/module/utils/index.js.map +1 -1
  116. package/lib/module/utils/languageUtils.js +151 -0
  117. package/lib/module/utils/languageUtils.js.map +1 -0
  118. package/lib/module/utils/requestUtils.js +210 -0
  119. package/lib/module/utils/requestUtils.js.map +1 -0
  120. package/lib/module/utils/sessionUtils.js +180 -0
  121. package/lib/module/utils/sessionUtils.js.map +1 -0
  122. package/lib/typescript/core/HttpClient.d.ts +64 -0
  123. package/lib/typescript/core/HttpClient.d.ts.map +1 -0
  124. package/lib/typescript/core/OxyServices.d.ts +82 -71
  125. package/lib/typescript/core/OxyServices.d.ts.map +1 -1
  126. package/lib/typescript/core/RequestManager.d.ts +67 -0
  127. package/lib/typescript/core/RequestManager.d.ts.map +1 -0
  128. package/lib/typescript/core/index.d.ts +2 -0
  129. package/lib/typescript/core/index.d.ts.map +1 -1
  130. package/lib/typescript/index.d.ts +2 -0
  131. package/lib/typescript/index.d.ts.map +1 -1
  132. package/lib/typescript/models/interfaces.d.ts +15 -0
  133. package/lib/typescript/models/interfaces.d.ts.map +1 -1
  134. package/lib/typescript/models/session.d.ts +1 -0
  135. package/lib/typescript/models/session.d.ts.map +1 -1
  136. package/lib/typescript/ui/components/Avatar.d.ts +6 -7
  137. package/lib/typescript/ui/components/Avatar.d.ts.map +1 -1
  138. package/lib/typescript/ui/components/FollowButton.d.ts.map +1 -1
  139. package/lib/typescript/ui/components/internal/TextField.d.ts.map +1 -1
  140. package/lib/typescript/ui/context/OxyContext.d.ts +4 -0
  141. package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
  142. package/lib/typescript/ui/hooks/useSessionSocket.d.ts.map +1 -1
  143. package/lib/typescript/ui/index.d.ts +2 -2
  144. package/lib/typescript/ui/index.d.ts.map +1 -1
  145. package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
  146. package/lib/typescript/ui/screens/AccountSwitcherScreen.d.ts.map +1 -1
  147. package/lib/typescript/ui/screens/LanguageSelectorScreen.d.ts +3 -3
  148. package/lib/typescript/ui/screens/LanguageSelectorScreen.d.ts.map +1 -1
  149. package/lib/typescript/ui/screens/SignInScreen.d.ts.map +1 -1
  150. package/lib/typescript/ui/screens/SignUpScreen.d.ts.map +1 -1
  151. package/lib/typescript/ui/screens/WelcomeNewUserScreen.d.ts.map +1 -1
  152. package/lib/typescript/ui/screens/internal/SignInPasswordStep.d.ts.map +1 -1
  153. package/lib/typescript/ui/screens/steps/SignInPasswordStep.d.ts.map +1 -1
  154. package/lib/typescript/ui/screens/steps/SignInUsernameStep.d.ts.map +1 -1
  155. package/lib/typescript/ui/stores/accountStore.d.ts +34 -0
  156. package/lib/typescript/ui/stores/accountStore.d.ts.map +1 -0
  157. package/lib/typescript/ui/stores/authStore.d.ts.map +1 -1
  158. package/lib/typescript/ui/styles/authStyles.d.ts +18 -2
  159. package/lib/typescript/ui/styles/authStyles.d.ts.map +1 -1
  160. package/lib/typescript/utils/asyncUtils.d.ts +2 -0
  161. package/lib/typescript/utils/asyncUtils.d.ts.map +1 -1
  162. package/lib/typescript/utils/cache.d.ts +128 -0
  163. package/lib/typescript/utils/cache.d.ts.map +1 -0
  164. package/lib/typescript/utils/index.d.ts +4 -0
  165. package/lib/typescript/utils/index.d.ts.map +1 -1
  166. package/lib/typescript/utils/languageUtils.d.ts +38 -0
  167. package/lib/typescript/utils/languageUtils.d.ts.map +1 -0
  168. package/lib/typescript/utils/requestUtils.d.ts +122 -0
  169. package/lib/typescript/utils/requestUtils.d.ts.map +1 -0
  170. package/lib/typescript/utils/sessionUtils.d.ts +55 -0
  171. package/lib/typescript/utils/sessionUtils.d.ts.map +1 -0
  172. package/package.json +1 -1
  173. package/src/core/HttpClient.ts +277 -0
  174. package/src/core/OxyServices.ts +458 -351
  175. package/src/core/RequestManager.ts +240 -0
  176. package/src/core/index.ts +10 -0
  177. package/src/index.ts +10 -0
  178. package/src/models/interfaces.ts +19 -0
  179. package/src/models/session.ts +1 -1
  180. package/src/ui/components/Avatar.tsx +151 -35
  181. package/src/ui/components/FollowButton.tsx +1 -0
  182. package/src/ui/components/internal/TextField.tsx +7 -6
  183. package/src/ui/context/OxyContext.tsx +213 -217
  184. package/src/ui/hooks/useSessionSocket.ts +72 -18
  185. package/src/ui/index.ts +4 -1
  186. package/src/ui/screens/AccountSettingsScreen.tsx +34 -2
  187. package/src/ui/screens/AccountSwitcherScreen.tsx +102 -68
  188. package/src/ui/screens/FileManagementScreen.tsx +1 -1
  189. package/src/ui/screens/LanguageSelectorScreen.tsx +86 -143
  190. package/src/ui/screens/SignInScreen.tsx +0 -7
  191. package/src/ui/screens/SignUpScreen.tsx +14 -15
  192. package/src/ui/screens/WelcomeNewUserScreen.tsx +52 -15
  193. package/src/ui/screens/internal/SignInPasswordStep.tsx +4 -6
  194. package/src/ui/screens/steps/SignInPasswordStep.tsx +4 -8
  195. package/src/ui/screens/steps/SignInUsernameStep.tsx +110 -256
  196. package/src/ui/stores/accountStore.ts +285 -0
  197. package/src/ui/stores/authStore.ts +2 -1
  198. package/src/ui/styles/authStyles.ts +14 -7
  199. package/src/utils/asyncUtils.ts +10 -24
  200. package/src/utils/cache.ts +264 -0
  201. package/src/utils/index.ts +19 -0
  202. package/src/utils/languageUtils.ts +174 -0
  203. package/src/utils/requestUtils.ts +234 -0
  204. package/src/utils/sessionUtils.ts +206 -0
@@ -4,22 +4,21 @@ import { useRef, useEffect, useMemo, useState, useCallback } from 'react';
4
4
  import {
5
5
  View,
6
6
  Text,
7
- Platform,
8
7
  StyleSheet,
9
- type ViewStyle,
10
- type TextStyle,
11
8
  ActivityIndicator,
9
+ TouchableOpacity,
12
10
  } from 'react-native';
13
11
  import { Ionicons } from '@expo/vector-icons';
14
12
  import HighFive from '../../../assets/illustrations/HighFive';
15
13
  import GroupedPillButtons from '../../components/internal/GroupedPillButtons';
16
14
  import TextField from '../../components/internal/TextField';
17
15
  import { useI18n } from '../../hooks/useI18n';
18
- import { STEP_GAP, STEP_INNER_GAP, stepStyles } from '../../styles/spacing';
16
+ import { stepStyles } from '../../styles/spacing';
19
17
  import Avatar from '../../components/Avatar';
20
- import { TouchableOpacity } from 'react-native';
21
18
  import { useOxy } from '../../context/OxyContext';
22
19
  import { toast } from '../../../lib/sonner';
20
+ import { useAccountStore, useAccounts, useAccountLoading, useAccountLoadingSession, type QuickAccount } from '../../stores/accountStore';
21
+ import { fontFamilies } from '../../styles/fonts';
23
22
 
24
23
  interface SignInUsernameStepProps {
25
24
  // Common props from StepBasedScreen
@@ -56,13 +55,6 @@ interface SignInUsernameStepProps {
56
55
  validateUsername: (username: string) => Promise<boolean>;
57
56
  }
58
57
 
59
- interface QuickAccount {
60
- sessionId: string;
61
- username: string;
62
- displayName: string;
63
- avatar?: string;
64
- }
65
-
66
58
  const MAX_QUICK_ACCOUNTS = 3;
67
59
 
68
60
  const getThemeMode = (theme: string | undefined): 'light' | 'dark' =>
@@ -89,190 +81,103 @@ const SignInUsernameStep: React.FC<SignInUsernameStepProps> = ({
89
81
  const { t } = useI18n();
90
82
  const { sessions, activeSessionId, switchSession, oxyServices } = useOxy();
91
83
  const baseStyles = stepStyles;
92
- const webShadowReset = Platform.OS === 'web' ? ({ boxShadow: 'none' } as any) : null;
93
84
  const themeMode = getThemeMode(theme);
94
- const [quickAccounts, setQuickAccounts] = useState<QuickAccount[]>([]);
95
- const [loadingAccounts, setLoadingAccounts] = useState(false);
96
85
  const [switchingSessionId, setSwitchingSessionId] = useState<string | null>(null);
97
86
  const [showAccounts, setShowAccounts] = useState(false);
87
+ const previousSessionIdsRef = useRef<string>('');
98
88
 
99
- const otherSessions = useMemo(
100
- () => (sessions ?? []).filter((session) => session.sessionId !== activeSessionId),
101
- [sessions, activeSessionId]
102
- );
89
+ // Zustand store - use stable selectors
90
+ const quickAccounts = useAccounts();
91
+ const loadingAccounts = useAccountLoading();
92
+ const isLoading = useAccountStore(state => state.loading);
103
93
 
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
- }
94
+ // Store actions are stable - get them once
95
+ const loadAccountsRef = useRef(useAccountStore.getState().loadAccounts);
96
+ const setAccountsRef = useRef(useAccountStore.getState().setAccounts);
97
+ const moveAccountToTopRef = useRef(useAccountStore.getState().moveAccountToTop);
123
98
 
99
+ // Update refs if store changes (shouldn't happen, but safe)
124
100
  useEffect(() => {
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
- }
101
+ loadAccountsRef.current = useAccountStore.getState().loadAccounts;
102
+ setAccountsRef.current = useAccountStore.getState().setAccounts;
103
+ moveAccountToTopRef.current = useAccountStore.getState().moveAccountToTop;
104
+ }, []);
105
+
106
+ const sessionsToLoad = useMemo(() => {
107
+ const allSessions = sessions || [];
108
+ return allSessions.slice(0, MAX_QUICK_ACCOUNTS);
109
+ }, [sessions]);
110
+
111
+ const sessionsToLoadIds = useMemo(
112
+ () => sessionsToLoad.map(s => s.sessionId).sort().join(','),
113
+ [sessionsToLoad]
114
+ );
137
115
 
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
- }
116
+ useEffect(() => {
117
+ if (previousSessionIdsRef.current === sessionsToLoadIds || isLoading) return;
118
+ if (!sessionsToLoad.length || !oxyServices) {
119
+ setAccountsRef.current([]);
120
+ previousSessionIdsRef.current = sessionsToLoadIds;
121
+ return;
122
+ }
146
123
 
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
- }
124
+ previousSessionIdsRef.current = sessionsToLoadIds;
155
125
 
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
- };
126
+ const uniqueSessionIds = Array.from(new Set(sessionsToLoad.map(s => s.sessionId)));
127
+ if (uniqueSessionIds.length === 0) {
128
+ setAccountsRef.current([]);
129
+ return;
130
+ }
197
131
 
198
- void loadQuickAccounts();
132
+ const currentAccounts = useAccountStore.getState().accounts;
133
+ const accountsArray = Object.values(currentAccounts);
199
134
 
200
- return () => {
201
- cancelled = true;
202
- };
203
- }, [hasSessions, sessionsToShow, oxyServices, isAddAccountMode]);
135
+ void loadAccountsRef.current(uniqueSessionIds, oxyServices, accountsArray);
136
+ }, [sessionsToLoadIds, oxyServices, isLoading]);
204
137
 
205
138
  const handleSwitchAccount = useCallback(
206
139
  async (sessionId: string) => {
140
+ if (switchingSessionId || sessionId === activeSessionId) return;
141
+
207
142
  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);
143
+ moveAccountToTopRef.current(sessionId);
144
+
145
+ switchSession(sessionId).catch((error) => {
146
+ if (__DEV__) console.error('Failed to switch account:', error);
147
+ const state = useAccountStore.getState();
148
+ const account = state.accounts[sessionId];
149
+ if (account) {
150
+ const filtered = Object.values(state.accounts).filter(a => a.sessionId !== sessionId);
151
+ setAccountsRef.current([...filtered, account]);
220
152
  }
221
- toast.error(
222
- t('signin.actions.switchAccountFailed') || 'Unable to switch accounts. Please try again.'
223
- );
224
- } finally {
153
+ toast.error(t('signin.actions.switchAccountFailed') || 'Unable to switch accounts. Please try again.');
154
+ }).finally(() => {
225
155
  setSwitchingSessionId(null);
226
- }
156
+ });
227
157
  },
228
- [quickAccounts, switchSession, t]
158
+ [switchSession, switchingSessionId, activeSessionId, t]
229
159
  );
230
160
 
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
161
 
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
- });
162
+ const accountsForDisplay = useMemo(() => {
163
+ const sessionMap = new Map(sessions?.map(s => [s.sessionId, s]) || []);
164
+ return quickAccounts.map(account => {
165
+ const session = sessionMap.get(account.sessionId);
166
+ const isCurrent = session?.isCurrent === true || account.sessionId === activeSessionId;
167
+ return {
168
+ ...account,
169
+ isCurrent,
170
+ };
263
171
  });
172
+ }, [quickAccounts, sessions, activeSessionId]);
264
173
 
265
- return accounts;
266
- }, [isAddAccountMode, user, quickAccounts, activeSessionId]);
267
-
268
- const handleUsernameChange = (text: string) => {
269
- // Text is already filtered by formatValue prop, but ensure it's clean
174
+ const handleUsernameChange = useCallback((text: string) => {
270
175
  const filteredText = text.replace(/[^a-zA-Z0-9]/g, '');
271
176
  setUsername(filteredText);
272
177
  if (errorMessage) setErrorMessage('');
273
- };
178
+ }, [setUsername, setErrorMessage, errorMessage]);
274
179
 
275
- const handleContinue = async () => {
180
+ const handleContinue = useCallback(async () => {
276
181
  const trimmedUsername = username?.trim() || '';
277
182
 
278
183
  if (!trimmedUsername) {
@@ -287,9 +192,7 @@ const SignInUsernameStep: React.FC<SignInUsernameStepProps> = ({
287
192
  }
288
193
 
289
194
  try {
290
- // Validate the username before proceeding
291
195
  const isValid = await validateUsername(trimmedUsername);
292
-
293
196
  if (isValid) {
294
197
  nextStep();
295
198
  }
@@ -297,7 +200,7 @@ const SignInUsernameStep: React.FC<SignInUsernameStepProps> = ({
297
200
  if (__DEV__) console.error('Error during username validation:', error);
298
201
  setErrorMessage('Unable to validate username. Please try again.');
299
202
  }
300
- };
203
+ }, [username, validateUsername, nextStep, setErrorMessage, t]);
301
204
 
302
205
  return (
303
206
  <>
@@ -345,8 +248,7 @@ const SignInUsernameStep: React.FC<SignInUsernameStepProps> = ({
345
248
  />
346
249
  </View>
347
250
 
348
- {/* "Or" divider */}
349
- {((isAddAccountMode && user) || (hasSessions && sessionsToShow.length > 0)) && (
251
+ {accountsForDisplay.length > 0 && (
350
252
  <View style={[baseStyles.container, baseStyles.sectionSpacing, stylesheet.dividerContainer]}>
351
253
  <View style={[stylesheet.dividerLine, { backgroundColor: colors.border }]} />
352
254
  <Text style={[stylesheet.dividerText, { color: colors.secondaryText }]}>
@@ -356,7 +258,7 @@ const SignInUsernameStep: React.FC<SignInUsernameStepProps> = ({
356
258
  </View>
357
259
  )}
358
260
 
359
- {(isAddAccountMode && user) || (hasSessions && sessionsToShow.length > 0) ? (
261
+ {accountsForDisplay.length > 0 ? (
360
262
  <View style={[baseStyles.container, baseStyles.sectionSpacing]}>
361
263
  <TouchableOpacity
362
264
  style={[
@@ -377,35 +279,34 @@ const SignInUsernameStep: React.FC<SignInUsernameStepProps> = ({
377
279
  <Text style={[stylesheet.toggleButtonText, { color: colors.text }]}>
378
280
  {t('signin.alreadySignedInWith') || 'Already signed in with'}
379
281
  </Text>
380
- {allAccountsForAvatars.length > 0 && (
282
+ {accountsForDisplay.length > 0 && (
381
283
  <View style={stylesheet.avatarsContainer}>
382
- {allAccountsForAvatars.slice(0, 5).map((account, index) => (
284
+ {accountsForDisplay.slice(0, 5).map((account, index) => (
383
285
  <View
384
- key={account.sessionId}
286
+ key={`avatar-${account.sessionId}`}
385
287
  style={[
386
288
  stylesheet.avatarWrapper,
387
289
  account.isCurrent && stylesheet.currentAvatarWrapper,
388
290
  index > 0 && { marginLeft: -12 },
389
- { zIndex: Math.min(allAccountsForAvatars.length, 5) - index },
291
+ { zIndex: Math.min(accountsForDisplay.length, 5) - index },
390
292
  { borderColor: colors.inputBackground || colors.background || '#FFFFFF' },
391
293
  ]}
392
294
  >
393
295
  <Avatar
394
296
  name={account.displayName}
395
- text={account.displayName.charAt(0).toUpperCase()}
396
297
  size={28}
397
298
  theme={themeMode}
398
299
  backgroundColor={colors.primary}
399
- uri={account.avatar && oxyServices ? oxyServices.getFileDownloadUrl(account.avatar, 'thumb') : undefined}
300
+ uri={account.avatarUrl}
400
301
  />
401
302
  </View>
402
303
  ))}
403
304
  </View>
404
305
  )}
405
- {!showAccounts && (quickAccounts.length > 0 || otherAccountsCount > 0) && allAccountsForAvatars.length === 0 && (
306
+ {!showAccounts && accountsForDisplay.length === 0 && quickAccounts.length > 0 && (
406
307
  <View style={[stylesheet.accountCountBadge, { backgroundColor: `${colors.primary}15` }]}>
407
308
  <Text style={[stylesheet.accountCountText, { color: colors.primary }]}>
408
- {otherAccountsCount + (isAddAccountMode && user ? 1 : 0)}
309
+ {quickAccounts.length}
409
310
  </Text>
410
311
  </View>
411
312
  )}
@@ -414,63 +315,15 @@ const SignInUsernameStep: React.FC<SignInUsernameStepProps> = ({
414
315
 
415
316
  {showAccounts && (
416
317
  <View style={stylesheet.accountsList}>
417
- {loadingAccounts && quickAccounts.length === 0 && !isAddAccountMode ? (
318
+ {loadingAccounts && accountsForDisplay.length === 0 ? (
418
319
  <View style={stylesheet.accountItem}>
419
320
  <ActivityIndicator color={colors.primary} size="small" />
420
321
  </View>
421
322
  ) : (
422
323
  <>
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) => (
324
+ {accountsForDisplay.map((account) => (
472
325
  <TouchableOpacity
473
- key={account.sessionId}
326
+ key={`account-${account.sessionId}`}
474
327
  style={[
475
328
  stylesheet.accountItem,
476
329
  {
@@ -479,7 +332,7 @@ const SignInUsernameStep: React.FC<SignInUsernameStepProps> = ({
479
332
  switchingSessionId === account.sessionId && stylesheet.accountItemLoading,
480
333
  ]}
481
334
  onPress={() => handleSwitchAccount(account.sessionId)}
482
- disabled={switchingSessionId === account.sessionId}
335
+ disabled={switchingSessionId === account.sessionId || account.isCurrent}
483
336
  activeOpacity={0.7}
484
337
  >
485
338
  {switchingSessionId === account.sessionId ? (
@@ -489,11 +342,10 @@ const SignInUsernameStep: React.FC<SignInUsernameStepProps> = ({
489
342
  <View style={[stylesheet.accountItemAvatarWrapper, { borderColor: colors.inputBackground || colors.background || '#FFFFFF' }]}>
490
343
  <Avatar
491
344
  name={account.displayName}
492
- text={account.displayName.charAt(0).toUpperCase()}
493
345
  size={36}
494
346
  theme={themeMode}
495
347
  backgroundColor={colors.primary}
496
- uri={account.avatar && oxyServices ? oxyServices.getFileDownloadUrl(account.avatar, 'thumb') : undefined}
348
+ uri={account.avatarUrl}
497
349
  />
498
350
  </View>
499
351
  <View style={stylesheet.accountItemText}>
@@ -515,11 +367,18 @@ const SignInUsernameStep: React.FC<SignInUsernameStepProps> = ({
515
367
  </Text>
516
368
  )}
517
369
  </View>
370
+ {account.isCurrent ? (
371
+ <View style={[stylesheet.currentAccountBadgeContainer, { backgroundColor: `${colors.primary}20` }]}>
372
+ <Text style={[stylesheet.currentAccountBadge, { color: colors.primary }]}>
373
+ {t('signin.currentAccount') || 'Current'}
374
+ </Text>
375
+ </View>
376
+ ) : null}
518
377
  </>
519
378
  )}
520
379
  </TouchableOpacity>
521
380
  ))}
522
- {otherAccountsCount > MAX_QUICK_ACCOUNTS && (
381
+ {sessions && sessions.length > MAX_QUICK_ACCOUNTS && (
523
382
  <TouchableOpacity
524
383
  style={[
525
384
  stylesheet.accountItem,
@@ -534,8 +393,8 @@ const SignInUsernameStep: React.FC<SignInUsernameStepProps> = ({
534
393
  <Ionicons name="chevron-forward" size={18} color={colors.primary} />
535
394
  <Text style={[stylesheet.viewAllText, { color: colors.primary }]}>
536
395
  {t('signin.viewAllAccounts', {
537
- count: otherAccountsCount - MAX_QUICK_ACCOUNTS,
538
- }) || `View ${otherAccountsCount - MAX_QUICK_ACCOUNTS} more`}
396
+ count: sessions.length - MAX_QUICK_ACCOUNTS,
397
+ }) || `View ${sessions.length - MAX_QUICK_ACCOUNTS} more`}
539
398
  </Text>
540
399
  </TouchableOpacity>
541
400
  )}
@@ -615,6 +474,7 @@ const stylesheet = StyleSheet.create({
615
474
  borderWidth: 0,
616
475
  gap: 12,
617
476
  minHeight: 56,
477
+ justifyContent: 'space-between',
618
478
  },
619
479
  accountItemLoading: {
620
480
  justifyContent: 'center',
@@ -641,14 +501,19 @@ const stylesheet = StyleSheet.create({
641
501
  marginLeft: 4,
642
502
  },
643
503
  currentAccountBadgeContainer: {
644
- paddingHorizontal: 10,
645
- paddingVertical: 4,
646
- borderRadius: 16,
504
+ paddingHorizontal: 12,
505
+ paddingVertical: 5,
506
+ borderRadius: 12,
507
+ marginLeft: 'auto',
508
+ minWidth: 60,
509
+ alignItems: 'center',
510
+ justifyContent: 'center',
647
511
  },
648
512
  currentAccountBadge: {
649
- fontSize: 10,
650
- fontWeight: '600',
651
- letterSpacing: 0.3,
513
+ fontSize: 11,
514
+ fontFamily: fontFamilies.phuduExtraBold,
515
+ letterSpacing: 0.5,
516
+ textTransform: 'uppercase',
652
517
  },
653
518
  dividerContainer: {
654
519
  flexDirection: 'row',
@@ -678,17 +543,6 @@ const stylesheet = StyleSheet.create({
678
543
  currentAvatarWrapper: {
679
544
  borderWidth: 3,
680
545
  },
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
546
  accountItemAvatarWrapper: {
693
547
  borderRadius: 20,
694
548
  borderWidth: 3,