@oxyhq/services 10.1.0 → 10.2.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 (232) hide show
  1. package/README.md +2 -2
  2. package/lib/commonjs/index.js +10 -0
  3. package/lib/commonjs/index.js.map +1 -1
  4. package/lib/commonjs/ui/components/AccountMenu.js +297 -226
  5. package/lib/commonjs/ui/components/AccountMenu.js.map +1 -1
  6. package/lib/commonjs/ui/components/AccountMenuButton.js.map +1 -1
  7. package/lib/commonjs/ui/components/OxySignInButton.js +1 -1
  8. package/lib/commonjs/ui/components/SignInModal.js +23 -14
  9. package/lib/commonjs/ui/components/SignInModal.js.map +1 -1
  10. package/lib/commonjs/ui/components/accountMenuRows.js +18 -30
  11. package/lib/commonjs/ui/components/accountMenuRows.js.map +1 -1
  12. package/lib/commonjs/ui/context/OxyContext.js +33 -65
  13. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  14. package/lib/commonjs/ui/context/hooks/useAuthOperations.js +7 -13
  15. package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +1 -1
  16. package/lib/commonjs/ui/hooks/useAuth.js +9 -39
  17. package/lib/commonjs/ui/hooks/useAuth.js.map +1 -1
  18. package/lib/commonjs/ui/hooks/useDeviceAccounts.js +285 -0
  19. package/lib/commonjs/ui/hooks/useDeviceAccounts.js.map +1 -0
  20. package/lib/commonjs/ui/hooks/useSessionManagement.js +5 -6
  21. package/lib/commonjs/ui/hooks/useSessionManagement.js.map +1 -1
  22. package/lib/commonjs/ui/hooks/useSessionSocket.js +4 -5
  23. package/lib/commonjs/ui/hooks/useSessionSocket.js.map +1 -1
  24. package/lib/commonjs/ui/hooks/useWebSSO.js +1 -1
  25. package/lib/commonjs/ui/navigation/routes.js +7 -7
  26. package/lib/commonjs/ui/navigation/routes.js.map +1 -1
  27. package/lib/commonjs/ui/screens/OxyAuthScreen.js +21 -12
  28. package/lib/commonjs/ui/screens/OxyAuthScreen.js.map +1 -1
  29. package/lib/commonjs/ui/screens/ProfileScreen.js +18 -20
  30. package/lib/commonjs/ui/screens/ProfileScreen.js.map +1 -1
  31. package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js +4 -4
  32. package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js.map +1 -1
  33. package/lib/commonjs/ui/screens/{karma/KarmaAboutScreen.js → trust/TrustAboutScreen.js} +11 -11
  34. package/lib/commonjs/ui/screens/trust/TrustAboutScreen.js.map +1 -0
  35. package/lib/commonjs/ui/screens/{karma/KarmaCenterScreen.js → trust/TrustCenterScreen.js} +91 -41
  36. package/lib/commonjs/ui/screens/trust/TrustCenterScreen.js.map +1 -0
  37. package/lib/commonjs/ui/screens/{karma/KarmaFAQScreen.js → trust/TrustFAQScreen.js} +11 -11
  38. package/lib/commonjs/ui/screens/{karma/KarmaFAQScreen.js.map → trust/TrustFAQScreen.js.map} +1 -1
  39. package/lib/commonjs/ui/screens/{karma/KarmaLeaderboardScreen.js → trust/TrustLeaderboardScreen.js} +63 -42
  40. package/lib/commonjs/ui/screens/trust/TrustLeaderboardScreen.js.map +1 -0
  41. package/lib/commonjs/ui/screens/{karma/KarmaRewardsScreen.js → trust/TrustRewardsScreen.js} +54 -54
  42. package/lib/commonjs/ui/screens/trust/TrustRewardsScreen.js.map +1 -0
  43. package/lib/commonjs/ui/screens/{karma/KarmaRulesScreen.js → trust/TrustRulesScreen.js} +45 -16
  44. package/lib/commonjs/ui/screens/trust/TrustRulesScreen.js.map +1 -0
  45. package/lib/commonjs/ui/screens/trust/trustTier.js +23 -0
  46. package/lib/commonjs/ui/screens/trust/trustTier.js.map +1 -0
  47. package/lib/commonjs/utils/deviceFlowSignIn.js +12 -10
  48. package/lib/commonjs/utils/deviceFlowSignIn.js.map +1 -1
  49. package/lib/module/index.js +3 -0
  50. package/lib/module/index.js.map +1 -1
  51. package/lib/module/ui/components/AccountMenu.js +297 -226
  52. package/lib/module/ui/components/AccountMenu.js.map +1 -1
  53. package/lib/module/ui/components/AccountMenuButton.js.map +1 -1
  54. package/lib/module/ui/components/OxySignInButton.js +1 -1
  55. package/lib/module/ui/components/SignInModal.js +23 -14
  56. package/lib/module/ui/components/SignInModal.js.map +1 -1
  57. package/lib/module/ui/components/accountMenuRows.js +18 -30
  58. package/lib/module/ui/components/accountMenuRows.js.map +1 -1
  59. package/lib/module/ui/context/OxyContext.js +33 -65
  60. package/lib/module/ui/context/OxyContext.js.map +1 -1
  61. package/lib/module/ui/context/hooks/useAuthOperations.js +7 -13
  62. package/lib/module/ui/context/hooks/useAuthOperations.js.map +1 -1
  63. package/lib/module/ui/hooks/useAuth.js +9 -39
  64. package/lib/module/ui/hooks/useAuth.js.map +1 -1
  65. package/lib/module/ui/hooks/useDeviceAccounts.js +281 -0
  66. package/lib/module/ui/hooks/useDeviceAccounts.js.map +1 -0
  67. package/lib/module/ui/hooks/useSessionManagement.js +5 -6
  68. package/lib/module/ui/hooks/useSessionManagement.js.map +1 -1
  69. package/lib/module/ui/hooks/useSessionSocket.js +4 -5
  70. package/lib/module/ui/hooks/useSessionSocket.js.map +1 -1
  71. package/lib/module/ui/hooks/useWebSSO.js +1 -1
  72. package/lib/module/ui/navigation/routes.js +7 -7
  73. package/lib/module/ui/navigation/routes.js.map +1 -1
  74. package/lib/module/ui/screens/OxyAuthScreen.js +21 -12
  75. package/lib/module/ui/screens/OxyAuthScreen.js.map +1 -1
  76. package/lib/module/ui/screens/ProfileScreen.js +18 -20
  77. package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
  78. package/lib/module/ui/screens/WelcomeNewUserScreen.js +4 -4
  79. package/lib/module/ui/screens/WelcomeNewUserScreen.js.map +1 -1
  80. package/lib/module/ui/screens/{karma/KarmaAboutScreen.js → trust/TrustAboutScreen.js} +11 -11
  81. package/lib/module/ui/screens/trust/TrustAboutScreen.js.map +1 -0
  82. package/lib/module/ui/screens/{karma/KarmaCenterScreen.js → trust/TrustCenterScreen.js} +92 -42
  83. package/lib/module/ui/screens/trust/TrustCenterScreen.js.map +1 -0
  84. package/lib/module/ui/screens/{karma/KarmaFAQScreen.js → trust/TrustFAQScreen.js} +11 -11
  85. package/lib/module/ui/screens/{karma/KarmaFAQScreen.js.map → trust/TrustFAQScreen.js.map} +1 -1
  86. package/lib/module/ui/screens/{karma/KarmaLeaderboardScreen.js → trust/TrustLeaderboardScreen.js} +63 -42
  87. package/lib/module/ui/screens/trust/TrustLeaderboardScreen.js.map +1 -0
  88. package/lib/module/ui/screens/{karma/KarmaRewardsScreen.js → trust/TrustRewardsScreen.js} +54 -54
  89. package/lib/module/ui/screens/trust/TrustRewardsScreen.js.map +1 -0
  90. package/lib/module/ui/screens/{karma/KarmaRulesScreen.js → trust/TrustRulesScreen.js} +45 -16
  91. package/lib/module/ui/screens/trust/TrustRulesScreen.js.map +1 -0
  92. package/lib/module/ui/screens/trust/trustTier.js +19 -0
  93. package/lib/module/ui/screens/trust/trustTier.js.map +1 -0
  94. package/lib/module/utils/deviceFlowSignIn.js +13 -10
  95. package/lib/module/utils/deviceFlowSignIn.js.map +1 -1
  96. package/lib/typescript/commonjs/index.d.ts +3 -1
  97. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  98. package/lib/typescript/commonjs/ui/components/AccountMenu.d.ts +30 -10
  99. package/lib/typescript/commonjs/ui/components/AccountMenu.d.ts.map +1 -1
  100. package/lib/typescript/commonjs/ui/components/SignInModal.d.ts +1 -1
  101. package/lib/typescript/commonjs/ui/components/SignInModal.d.ts.map +1 -1
  102. package/lib/typescript/commonjs/ui/components/accountMenuRows.d.ts +19 -12
  103. package/lib/typescript/commonjs/ui/components/accountMenuRows.d.ts.map +1 -1
  104. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts +3 -3
  105. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
  106. package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
  107. package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts +1 -7
  108. package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  109. package/lib/typescript/commonjs/ui/hooks/useAuth.d.ts +3 -7
  110. package/lib/typescript/commonjs/ui/hooks/useAuth.d.ts.map +1 -1
  111. package/lib/typescript/commonjs/ui/hooks/useDeviceAccounts.d.ts +133 -0
  112. package/lib/typescript/commonjs/ui/hooks/useDeviceAccounts.d.ts.map +1 -0
  113. package/lib/typescript/commonjs/ui/hooks/useSessionManagement.d.ts.map +1 -1
  114. package/lib/typescript/commonjs/ui/hooks/useSessionSocket.d.ts.map +1 -1
  115. package/lib/typescript/commonjs/ui/hooks/useWebSSO.d.ts +1 -1
  116. package/lib/typescript/commonjs/ui/navigation/routes.d.ts +1 -1
  117. package/lib/typescript/commonjs/ui/screens/OxyAuthScreen.d.ts.map +1 -1
  118. package/lib/typescript/commonjs/ui/screens/ProfileScreen.d.ts.map +1 -1
  119. package/lib/typescript/commonjs/ui/screens/trust/TrustAboutScreen.d.ts +5 -0
  120. package/lib/typescript/commonjs/ui/screens/{karma/KarmaAboutScreen.d.ts.map → trust/TrustAboutScreen.d.ts.map} +1 -1
  121. package/lib/typescript/commonjs/ui/screens/trust/TrustCenterScreen.d.ts +5 -0
  122. package/lib/typescript/commonjs/ui/screens/trust/TrustCenterScreen.d.ts.map +1 -0
  123. package/lib/typescript/{module/ui/screens/karma/KarmaFAQScreen.d.ts → commonjs/ui/screens/trust/TrustFAQScreen.d.ts} +1 -1
  124. package/lib/typescript/commonjs/ui/screens/trust/TrustFAQScreen.d.ts.map +1 -0
  125. package/lib/typescript/commonjs/ui/screens/trust/TrustLeaderboardScreen.d.ts +5 -0
  126. package/lib/typescript/commonjs/ui/screens/trust/TrustLeaderboardScreen.d.ts.map +1 -0
  127. package/lib/typescript/commonjs/ui/screens/trust/TrustRewardsScreen.d.ts +5 -0
  128. package/lib/typescript/commonjs/ui/screens/{karma/KarmaRewardsScreen.d.ts.map → trust/TrustRewardsScreen.d.ts.map} +1 -1
  129. package/lib/typescript/commonjs/ui/screens/trust/TrustRulesScreen.d.ts +5 -0
  130. package/lib/typescript/commonjs/ui/screens/trust/TrustRulesScreen.d.ts.map +1 -0
  131. package/lib/typescript/commonjs/ui/screens/trust/trustTier.d.ts +9 -0
  132. package/lib/typescript/commonjs/ui/screens/trust/trustTier.d.ts.map +1 -0
  133. package/lib/typescript/commonjs/ui/types/navigation.d.ts +1 -1
  134. package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts +11 -9
  135. package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts.map +1 -1
  136. package/lib/typescript/module/index.d.ts +3 -1
  137. package/lib/typescript/module/index.d.ts.map +1 -1
  138. package/lib/typescript/module/ui/components/AccountMenu.d.ts +30 -10
  139. package/lib/typescript/module/ui/components/AccountMenu.d.ts.map +1 -1
  140. package/lib/typescript/module/ui/components/SignInModal.d.ts +1 -1
  141. package/lib/typescript/module/ui/components/SignInModal.d.ts.map +1 -1
  142. package/lib/typescript/module/ui/components/accountMenuRows.d.ts +19 -12
  143. package/lib/typescript/module/ui/components/accountMenuRows.d.ts.map +1 -1
  144. package/lib/typescript/module/ui/context/OxyContext.d.ts +3 -3
  145. package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
  146. package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
  147. package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts +1 -7
  148. package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  149. package/lib/typescript/module/ui/hooks/useAuth.d.ts +3 -7
  150. package/lib/typescript/module/ui/hooks/useAuth.d.ts.map +1 -1
  151. package/lib/typescript/module/ui/hooks/useDeviceAccounts.d.ts +133 -0
  152. package/lib/typescript/module/ui/hooks/useDeviceAccounts.d.ts.map +1 -0
  153. package/lib/typescript/module/ui/hooks/useSessionManagement.d.ts.map +1 -1
  154. package/lib/typescript/module/ui/hooks/useSessionSocket.d.ts.map +1 -1
  155. package/lib/typescript/module/ui/hooks/useWebSSO.d.ts +1 -1
  156. package/lib/typescript/module/ui/navigation/routes.d.ts +1 -1
  157. package/lib/typescript/module/ui/screens/OxyAuthScreen.d.ts.map +1 -1
  158. package/lib/typescript/module/ui/screens/ProfileScreen.d.ts.map +1 -1
  159. package/lib/typescript/module/ui/screens/trust/TrustAboutScreen.d.ts +5 -0
  160. package/lib/typescript/module/ui/screens/{karma/KarmaAboutScreen.d.ts.map → trust/TrustAboutScreen.d.ts.map} +1 -1
  161. package/lib/typescript/module/ui/screens/trust/TrustCenterScreen.d.ts +5 -0
  162. package/lib/typescript/module/ui/screens/trust/TrustCenterScreen.d.ts.map +1 -0
  163. package/lib/typescript/{commonjs/ui/screens/karma/KarmaFAQScreen.d.ts → module/ui/screens/trust/TrustFAQScreen.d.ts} +1 -1
  164. package/lib/typescript/module/ui/screens/trust/TrustFAQScreen.d.ts.map +1 -0
  165. package/lib/typescript/module/ui/screens/trust/TrustLeaderboardScreen.d.ts +5 -0
  166. package/lib/typescript/module/ui/screens/trust/TrustLeaderboardScreen.d.ts.map +1 -0
  167. package/lib/typescript/module/ui/screens/trust/TrustRewardsScreen.d.ts +5 -0
  168. package/lib/typescript/module/ui/screens/{karma/KarmaRewardsScreen.d.ts.map → trust/TrustRewardsScreen.d.ts.map} +1 -1
  169. package/lib/typescript/module/ui/screens/trust/TrustRulesScreen.d.ts +5 -0
  170. package/lib/typescript/module/ui/screens/trust/TrustRulesScreen.d.ts.map +1 -0
  171. package/lib/typescript/module/ui/screens/trust/trustTier.d.ts +9 -0
  172. package/lib/typescript/module/ui/screens/trust/trustTier.d.ts.map +1 -0
  173. package/lib/typescript/module/ui/types/navigation.d.ts +1 -1
  174. package/lib/typescript/module/utils/deviceFlowSignIn.d.ts +11 -9
  175. package/lib/typescript/module/utils/deviceFlowSignIn.d.ts.map +1 -1
  176. package/package.json +1 -1
  177. package/src/index.ts +10 -1
  178. package/src/ui/components/AccountMenu.tsx +311 -253
  179. package/src/ui/components/AccountMenuButton.tsx +2 -2
  180. package/src/ui/components/OxySignInButton.tsx +1 -1
  181. package/src/ui/components/SignInModal.tsx +26 -16
  182. package/src/ui/components/accountMenuRows.ts +28 -40
  183. package/src/ui/context/OxyContext.tsx +43 -61
  184. package/src/ui/context/hooks/useAuthOperations.ts +7 -13
  185. package/src/ui/hooks/useAuth.ts +11 -48
  186. package/src/ui/hooks/useDeviceAccounts.ts +348 -0
  187. package/src/ui/hooks/useSessionManagement.ts +5 -14
  188. package/src/ui/hooks/useSessionSocket.ts +4 -5
  189. package/src/ui/hooks/useWebSSO.ts +1 -1
  190. package/src/ui/navigation/routes.ts +13 -13
  191. package/src/ui/screens/OxyAuthScreen.tsx +21 -12
  192. package/src/ui/screens/ProfileScreen.tsx +15 -17
  193. package/src/ui/screens/WelcomeNewUserScreen.tsx +2 -2
  194. package/src/ui/screens/{karma/KarmaAboutScreen.tsx → trust/TrustAboutScreen.tsx} +15 -15
  195. package/src/ui/screens/{karma/KarmaCenterScreen.tsx → trust/TrustCenterScreen.tsx} +87 -41
  196. package/src/ui/screens/{karma/KarmaFAQScreen.tsx → trust/TrustFAQScreen.tsx} +10 -10
  197. package/src/ui/screens/trust/TrustLeaderboardScreen.tsx +101 -0
  198. package/src/ui/screens/{karma/KarmaRewardsScreen.tsx → trust/TrustRewardsScreen.tsx} +54 -54
  199. package/src/ui/screens/{karma/KarmaRulesScreen.tsx → trust/TrustRulesScreen.tsx} +27 -13
  200. package/src/ui/screens/trust/trustTier.ts +20 -0
  201. package/src/ui/types/navigation.ts +1 -2
  202. package/src/utils/__tests__/deviceFlowSignIn.test.ts +2 -3
  203. package/src/utils/deviceFlowSignIn.ts +18 -12
  204. package/lib/commonjs/ui/screens/karma/KarmaAboutScreen.js.map +0 -1
  205. package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js.map +0 -1
  206. package/lib/commonjs/ui/screens/karma/KarmaLeaderboardScreen.js.map +0 -1
  207. package/lib/commonjs/ui/screens/karma/KarmaRewardsScreen.js.map +0 -1
  208. package/lib/commonjs/ui/screens/karma/KarmaRulesScreen.js.map +0 -1
  209. package/lib/module/ui/screens/karma/KarmaAboutScreen.js.map +0 -1
  210. package/lib/module/ui/screens/karma/KarmaCenterScreen.js.map +0 -1
  211. package/lib/module/ui/screens/karma/KarmaLeaderboardScreen.js.map +0 -1
  212. package/lib/module/ui/screens/karma/KarmaRewardsScreen.js.map +0 -1
  213. package/lib/module/ui/screens/karma/KarmaRulesScreen.js.map +0 -1
  214. package/lib/typescript/commonjs/ui/screens/karma/KarmaAboutScreen.d.ts +0 -5
  215. package/lib/typescript/commonjs/ui/screens/karma/KarmaCenterScreen.d.ts +0 -5
  216. package/lib/typescript/commonjs/ui/screens/karma/KarmaCenterScreen.d.ts.map +0 -1
  217. package/lib/typescript/commonjs/ui/screens/karma/KarmaFAQScreen.d.ts.map +0 -1
  218. package/lib/typescript/commonjs/ui/screens/karma/KarmaLeaderboardScreen.d.ts +0 -5
  219. package/lib/typescript/commonjs/ui/screens/karma/KarmaLeaderboardScreen.d.ts.map +0 -1
  220. package/lib/typescript/commonjs/ui/screens/karma/KarmaRewardsScreen.d.ts +0 -5
  221. package/lib/typescript/commonjs/ui/screens/karma/KarmaRulesScreen.d.ts +0 -5
  222. package/lib/typescript/commonjs/ui/screens/karma/KarmaRulesScreen.d.ts.map +0 -1
  223. package/lib/typescript/module/ui/screens/karma/KarmaAboutScreen.d.ts +0 -5
  224. package/lib/typescript/module/ui/screens/karma/KarmaCenterScreen.d.ts +0 -5
  225. package/lib/typescript/module/ui/screens/karma/KarmaCenterScreen.d.ts.map +0 -1
  226. package/lib/typescript/module/ui/screens/karma/KarmaFAQScreen.d.ts.map +0 -1
  227. package/lib/typescript/module/ui/screens/karma/KarmaLeaderboardScreen.d.ts +0 -5
  228. package/lib/typescript/module/ui/screens/karma/KarmaLeaderboardScreen.d.ts.map +0 -1
  229. package/lib/typescript/module/ui/screens/karma/KarmaRewardsScreen.d.ts +0 -5
  230. package/lib/typescript/module/ui/screens/karma/KarmaRulesScreen.d.ts +0 -5
  231. package/lib/typescript/module/ui/screens/karma/KarmaRulesScreen.d.ts.map +0 -1
  232. package/src/ui/screens/karma/KarmaLeaderboardScreen.tsx +0 -88
@@ -0,0 +1,348 @@
1
+ import { useMemo } from 'react';
2
+ import { useQuery } from '@tanstack/react-query';
3
+ import {
4
+ getAccountDisplayName,
5
+ getAccountFallbackHandle,
6
+ } from '@oxyhq/core';
7
+ import type {
8
+ RefreshAllAccountUser,
9
+ User,
10
+ ClientSession,
11
+ } from '@oxyhq/core';
12
+ import { useOxy } from '../context/OxyContext';
13
+ import { useI18n } from './useI18n';
14
+ import { queryKeys } from './queries/queryKeys';
15
+
16
+ /**
17
+ * The per-account user shape carried by a {@link DeviceAccount}. Either:
18
+ * - the minimal projection returned by `POST /auth/refresh-all`
19
+ * ({@link RefreshAllAccountUser}, the shared apex path), or
20
+ * - the SDK's canonical {@link User} document (the active row on the local
21
+ * fallback path, which already has the full user loaded in `useOxy()`).
22
+ *
23
+ * Both satisfy `getAccountDisplayName`'s `DisplayNameUserShape`, so display
24
+ * resolution is uniform across paths.
25
+ */
26
+ export type DeviceAccountUser = RefreshAllAccountUser | User;
27
+
28
+ /**
29
+ * One signed-in account on this device, fully hydrated for the account
30
+ * chooser. Unlike the old `AccountMenu` behaviour (which only carried the
31
+ * ACTIVE session's user), EVERY entry here carries real per-account
32
+ * `displayName` / `email` / `avatarUrl` / `color`.
33
+ */
34
+ export interface DeviceAccount {
35
+ /** Session id used to switch to this account on native. */
36
+ sessionId: string;
37
+ /**
38
+ * Device-local refresh-cookie slot index (0..N-1), present only on the
39
+ * shared apex path. Web silent-switch (`refreshTokenViaCookie({ authuser }`)
40
+ * keys off this; absent on the local fallback and on native.
41
+ */
42
+ authuser?: number;
43
+ /** Whether this account is the currently-active session. */
44
+ isCurrent: boolean;
45
+ /** Friendly display name (never blank — falls back to a handle/sentinel). */
46
+ displayName: string;
47
+ /**
48
+ * Real account email, or `null` when the account genuinely has none.
49
+ * NEVER a synthesized `username@oxy.so` — a missing email stays `null` and
50
+ * the UI falls back to the `@handle` secondary line.
51
+ */
52
+ email: string | null;
53
+ /** Resolved avatar thumbnail URL, or `undefined` when the account has no avatar. */
54
+ avatarUrl?: string;
55
+ /** Account's preferred Bloom color preset, or `null` when unset. */
56
+ color: string | null;
57
+ /** The underlying per-account user payload (shared projection or full User). */
58
+ user: DeviceAccountUser;
59
+ }
60
+
61
+ export interface UseDeviceAccountsResult {
62
+ /** Every account signed in on this device, current one(s) flagged `isCurrent`. */
63
+ accounts: DeviceAccount[];
64
+ /** True until the first detection settles. */
65
+ isLoading: boolean;
66
+ /** The currently-active session id (mirrors `useOxy().activeSessionId`). */
67
+ currentSessionId: string | null;
68
+ /**
69
+ * `true` when the list came from the shared apex path
70
+ * (`refreshAllSessions()` returned >0 accounts — the cross-domain SSO cookie
71
+ * set on `*.oxy.so`). `false` when it came from the local `useOxy()`
72
+ * fallback (native, or cross-domain web where the apex cookie is absent).
73
+ */
74
+ fromSharedApex: boolean;
75
+ }
76
+
77
+ /**
78
+ * Resolve which entries are the current account, robustly.
79
+ *
80
+ * Primary signal: `entry.sessionId === activeSessionId`. On the web shared-apex
81
+ * path, `refreshAllSessions()` returns SERVER-side session ids that may not
82
+ * equal the locally-stored `activeSessionId` (a different storage namespace /
83
+ * server perspective), so a pure `sessionId` match can flag NO row as current —
84
+ * the bug that surfaced as "Not signed in" even while authenticated.
85
+ *
86
+ * Fallbacks, applied only when the `sessionId` match found nothing AND the user
87
+ * is authenticated:
88
+ * 1. Match the live `user.id` against each entry's per-account user id.
89
+ * 2. If still nothing and there is exactly one account, mark that one current.
90
+ *
91
+ * At most ONE entry is ever marked current.
92
+ *
93
+ * Pure & side-effect free so it is unit-testable without rendering React.
94
+ */
95
+ export function markCurrentAccount(
96
+ accounts: DeviceAccount[],
97
+ activeSessionId: string | null | undefined,
98
+ liveUserId: string | null | undefined,
99
+ isAuthenticated: boolean,
100
+ ): DeviceAccount[] {
101
+ const bySession = accounts.map((account): DeviceAccount => ({
102
+ ...account,
103
+ isCurrent: Boolean(activeSessionId) && account.sessionId === activeSessionId,
104
+ }));
105
+
106
+ if (bySession.some((account) => account.isCurrent) || !isAuthenticated) {
107
+ return bySession;
108
+ }
109
+
110
+ // No row matched by session id: fall back to the live user's id, marking at
111
+ // most the FIRST matching entry current (never more than one).
112
+ if (liveUserId) {
113
+ let matched = false;
114
+ const byUser = bySession.map((account): DeviceAccount => {
115
+ if (!matched && account.user.id === liveUserId) {
116
+ matched = true;
117
+ return { ...account, isCurrent: true };
118
+ }
119
+ return account;
120
+ });
121
+ if (matched) {
122
+ return byUser;
123
+ }
124
+ }
125
+
126
+ // Authenticated, nothing matched, but there is exactly one account → it must
127
+ // be the current one (single-account shared-apex / cross-domain case).
128
+ if (bySession.length === 1) {
129
+ return [{ ...bySession[0], isCurrent: true }];
130
+ }
131
+
132
+ return bySession;
133
+ }
134
+
135
+ /**
136
+ * Resolve every account signed in on this device for the unified account
137
+ * switcher, with real per-account name / email / avatar / color.
138
+ *
139
+ * ## Data sources (web vs native)
140
+ *
141
+ * - **Web on a `*.oxy.so` host** → `oxyServices.refreshAllSessions()` returns
142
+ * the full apex device-account list (identical to what `auth.oxy.so` sees,
143
+ * because the `Domain=oxy.so` refresh cookies reach `api.oxy.so`). Used
144
+ * directly — this is the `fromSharedApex: true` path.
145
+ * - **Cross-domain web (non-`oxy.so` apex)** OR **native (no browser cookies)**
146
+ * → `refreshAllSessions()` yields `{ accounts: [] }` (the apex cookies never
147
+ * reach the request: cross-domain by `Domain`, native by having no cookie
148
+ * jar at all; a 401 is also normalised to `{ accounts: [] }` inside the
149
+ * core mixin). In that case we FALL BACK to the SDK's local multi-account
150
+ * list from `useOxy()` (`sessions` + `activeSessionId` + the active `user`).
151
+ * This is the `fromSharedApex: false` path.
152
+ *
153
+ * The fallback decision is purely data-driven: **if `refreshAllSessions()`
154
+ * returns >0 accounts, use the shared path; otherwise fall back to local
155
+ * `useOxy()` sessions.** No host sniffing is needed — the cookie scoping does
156
+ * the discrimination for us, and the same code path works on native (where the
157
+ * fetch returns `{ accounts: [] }`).
158
+ *
159
+ * ## Why React Query (not a `useRef`/`useState` start-once like the auth app)
160
+ *
161
+ * The auth app's `use-device-accounts.ts` hand-rolls a `startedRef` because it
162
+ * has no React Query. The SDK does. `refreshAllSessions()` ROTATES single-use
163
+ * refresh cookies on every call, so it must run AT MOST ONCE per page load:
164
+ * we model that with `staleTime: Infinity` + `gcTime: Infinity` +
165
+ * `refetchOnWindowFocus/Reconnect/Mount: false` + `retry: false`. React Query
166
+ * dedupes concurrent mounts and caches the single result for the page's
167
+ * lifetime — the exact "run once" guarantee the auth app documents, but
168
+ * without the manual ref/state machinery.
169
+ *
170
+ * ## Validation note (intentionally NO zod re-parse)
171
+ *
172
+ * The auth app's hook calls `fetch` directly, so IT must `safeParse` the wire
173
+ * response with `@oxyhq/contracts`. This hook calls
174
+ * `oxyServices.refreshAllSessions()`, whose core mixin ALREADY validates and
175
+ * normalises the wire response (skips entries missing required fields,
176
+ * normalises `authuser` null→0, builds the `RefreshAllAccountUser` shape) — the
177
+ * mixin is the single source of truth. Re-validating the SDK's own already-typed
178
+ * output here would be redundant double-validation, so we do not re-parse.
179
+ *
180
+ * ## Error handling
181
+ *
182
+ * `refreshAllSessions()` already maps 401/404/abort to `{ accounts: [] }`
183
+ * internally. Any OTHER failure (network, 5xx) propagates as a thrown error and
184
+ * is surfaced by React Query (`query.isError`) — never swallowed. Callers that
185
+ * don't care can ignore it; the hook still returns the local fallback list so
186
+ * the chooser stays usable.
187
+ */
188
+ export function useDeviceAccounts(): UseDeviceAccountsResult {
189
+ const {
190
+ oxyServices,
191
+ sessions,
192
+ activeSessionId,
193
+ user,
194
+ isAuthenticated,
195
+ } = useOxy();
196
+ const { locale } = useI18n();
197
+
198
+ // Stable, per-API-origin query key. `refreshAllSessions` resolves against
199
+ // the session base url derived from `getBaseURL()`, so keying on it scopes
200
+ // the cached result to the API the provider is pointed at.
201
+ const baseURL = oxyServices.getBaseURL();
202
+
203
+ const query = useQuery({
204
+ queryKey: [...queryKeys.accounts.all, 'deviceAccounts', baseURL] as const,
205
+ queryFn: () => oxyServices.refreshAllSessions(),
206
+ // Only attempt the shared apex path while signed in. When logged out
207
+ // there is nothing to enumerate and the fallback (also empty) is used.
208
+ enabled: isAuthenticated,
209
+ // Single-use cookie rotation → run at most once per page load.
210
+ staleTime: Number.POSITIVE_INFINITY,
211
+ gcTime: Number.POSITIVE_INFINITY,
212
+ refetchOnWindowFocus: false,
213
+ refetchOnReconnect: false,
214
+ refetchOnMount: false,
215
+ retry: false,
216
+ });
217
+
218
+ const sharedAccounts = query.data?.accounts ?? [];
219
+ const fromSharedApex = sharedAccounts.length > 0;
220
+
221
+ const accounts = useMemo<DeviceAccount[]>(() => {
222
+ const resolveAvatarUrl = (avatar: string | null | undefined): string | undefined =>
223
+ avatar ? oxyServices.getFileDownloadUrl(avatar, 'thumb') : undefined;
224
+
225
+ // Build a single current-account row from the live `useOxy().user`. Used
226
+ // as a last-resort fallback when neither the shared-apex probe nor the
227
+ // local session store yielded a row but the user IS authenticated (e.g.
228
+ // a cross-domain host where the apex probe returned empty before the
229
+ // local session store hydrated). The signed-in user must ALWAYS be
230
+ // represented. Email is the REAL email or the `@handle` line — never a
231
+ // synthesized `username@oxy.so`.
232
+ const liveUserRow = (): DeviceAccount[] => {
233
+ if (!isAuthenticated || !user || !activeSessionId) {
234
+ return [];
235
+ }
236
+ const displayName = getAccountDisplayName(user, locale);
237
+ const handle = getAccountFallbackHandle(user);
238
+ const secondaryHandle = handle ? `@${handle}` : null;
239
+ return [{
240
+ sessionId: activeSessionId,
241
+ authuser: undefined,
242
+ isCurrent: true,
243
+ displayName,
244
+ email: user.email ?? secondaryHandle,
245
+ avatarUrl: resolveAvatarUrl(user.avatar),
246
+ color: user.color ?? null,
247
+ user,
248
+ }];
249
+ };
250
+
251
+ let built: DeviceAccount[];
252
+
253
+ if (fromSharedApex) {
254
+ // Shared apex path: every entry carries a real per-account user.
255
+ built = sharedAccounts.map((entry): DeviceAccount => {
256
+ // `entry.user` is non-null on the refresh-all path; the core
257
+ // mixin skips entries without a valid user. The fallback below
258
+ // keeps rendering defensive if a server response is incomplete.
259
+ const accountUser: DeviceAccountUser = entry.user ?? {
260
+ id: '',
261
+ username: '',
262
+ };
263
+ const displayName = getAccountDisplayName(accountUser, locale);
264
+ const handle = getAccountFallbackHandle(accountUser);
265
+ const email = entry.user?.email ?? null;
266
+ const secondaryHandle = handle ? `@${handle}` : null;
267
+ return {
268
+ sessionId: entry.sessionId,
269
+ authuser: entry.authuser,
270
+ // Provisional; finalised by `markCurrentAccount` below so the
271
+ // shared-apex path is robust to server/local session-id skew.
272
+ isCurrent: false,
273
+ displayName,
274
+ // Real email, or null (NEVER synthesized). The UI uses the
275
+ // `@handle` line only when email is genuinely absent.
276
+ email: email ?? secondaryHandle,
277
+ avatarUrl: resolveAvatarUrl(entry.user?.avatar),
278
+ color: entry.user?.color ?? null,
279
+ user: accountUser,
280
+ };
281
+ });
282
+ } else {
283
+ // Local fallback path: build from the SDK's multi-session store. The
284
+ // active session row gets the full loaded `user`; inactive fallback
285
+ // rows carry only what the `ClientSession` exposes (no synthesized
286
+ // identity — they show the active user's data only when active).
287
+ built = (sessions ?? []).map((session: ClientSession): DeviceAccount => {
288
+ const isCurrent = session.sessionId === activeSessionId;
289
+ const accountUser: DeviceAccountUser = isCurrent && user
290
+ ? user
291
+ : { id: session.userId ?? '', username: '' };
292
+ const displayName = getAccountDisplayName(accountUser, locale);
293
+ const handle = getAccountFallbackHandle(accountUser);
294
+ const email = isCurrent && user?.email ? user.email : null;
295
+ const secondaryHandle = handle ? `@${handle}` : null;
296
+ const avatar = isCurrent && user ? user.avatar : undefined;
297
+ const color = isCurrent && user?.color ? user.color : null;
298
+ return {
299
+ sessionId: session.sessionId,
300
+ authuser: session.authuser,
301
+ isCurrent,
302
+ displayName,
303
+ email: email ?? secondaryHandle,
304
+ avatarUrl: resolveAvatarUrl(avatar),
305
+ color,
306
+ user: accountUser,
307
+ };
308
+ });
309
+ }
310
+
311
+ // Robust current-account detection: tolerate server/local session-id
312
+ // skew on the shared-apex path by falling back to the live user's id and
313
+ // the single-account heuristic.
314
+ const flagged = markCurrentAccount(
315
+ built,
316
+ activeSessionId,
317
+ user?.id ?? null,
318
+ isAuthenticated,
319
+ );
320
+
321
+ // The signed-in user must ALWAYS be represented. If detection produced
322
+ // an empty list yet the user is authenticated, synthesize a single
323
+ // current row from the live `useOxy().user`.
324
+ if (flagged.length === 0) {
325
+ return liveUserRow();
326
+ }
327
+ return flagged;
328
+ }, [
329
+ fromSharedApex,
330
+ sharedAccounts,
331
+ sessions,
332
+ activeSessionId,
333
+ user,
334
+ isAuthenticated,
335
+ locale,
336
+ oxyServices,
337
+ ]);
338
+
339
+ return {
340
+ accounts,
341
+ // `isLoading` only reflects the shared probe while it's the relevant
342
+ // source. Once we know we're on the fallback (probe settled with 0
343
+ // accounts) the local list is synchronously available.
344
+ isLoading: isAuthenticated && query.isLoading,
345
+ currentSessionId: activeSessionId ?? null,
346
+ fromSharedApex,
347
+ };
348
+ }
@@ -199,7 +199,6 @@ export const useSessionManagement = ({
199
199
 
200
200
  const activateSession = useCallback(
201
201
  async (sessionId: string, user: User): Promise<void> => {
202
- await oxyServices.getTokenBySession(sessionId);
203
202
  setTokenReady?.(true);
204
203
  setActiveSessionId(sessionId);
205
204
  loginSuccess(user);
@@ -207,14 +206,7 @@ export const useSessionManagement = ({
207
206
  await applyLanguagePreference(user);
208
207
  onAuthStateChange?.(user);
209
208
  },
210
- [
211
- applyLanguagePreference,
212
- loginSuccess,
213
- onAuthStateChange,
214
- oxyServices,
215
- saveActiveSessionId,
216
- setTokenReady,
217
- ],
209
+ [applyLanguagePreference, loginSuccess, onAuthStateChange, saveActiveSessionId, setTokenReady],
218
210
  );
219
211
 
220
212
  const removalTimerIdsRef = useRef<Set<ReturnType<typeof setTimeout>>>(new Set());
@@ -271,10 +263,10 @@ export const useSessionManagement = ({
271
263
  // `refreshAllSessions` it carries its `authuser` slot index. We
272
264
  // proactively plant that slot's access token via the httpOnly
273
265
  // refresh cookie BEFORE validating, so the bearer-protected
274
- // validate/getCurrentUser calls don't need a token that was never
275
- // in memory (cold reload / different browser tab). The native path
276
- // has no per-session cookies and continues to rely on
277
- // `validateSession` + `getTokenBySession` directly.
266
+ // validate/getCurrentUser calls have the correct in-memory token
267
+ // after a cold reload or account switch in another tab. The native
268
+ // path arrives here only after a bearer has been planted by
269
+ // `claimSessionByToken` or secure shared-session restore.
278
270
  if (isWebBrowser()) {
279
271
  const targetSession = sessionsRef.current.find((s) => s.sessionId === sessionId);
280
272
  const targetAuthuser = targetSession?.authuser;
@@ -449,4 +441,3 @@ export const useSessionManagement = ({
449
441
  };
450
442
  };
451
443
 
452
-
@@ -149,8 +149,7 @@ export function useSessionSocket({
149
149
  // appear in the switch. Anything unknown falls through to `default`,
150
150
  // which is intentionally a no-op for session lifecycle — it only logs
151
151
  // in dev. This guards against future server-side event additions
152
- // (e.g. `session_created`) accidentally triggering sign-out via a
153
- // "legacy fallback" branch.
152
+ // accidentally triggering sign-out.
154
153
  switch (data.type) {
155
154
  case 'session_removed': {
156
155
  if (data.sessionId && onSessionRemovedRef.current) {
@@ -199,9 +198,9 @@ export function useSessionSocket({
199
198
  case 'session_created':
200
199
  case 'session_update': {
201
200
  // Lifecycle event for the current user. Just resync the sessions
202
- // list — never sign out. Historically this branch had a legacy
203
- // fallback that compared `data.sessionId === currentActiveSessionId`
204
- // and signed the user out if true, which was catastrophic: a
201
+ // list — never sign out. A prior implementation compared
202
+ // `data.sessionId === currentActiveSessionId` and signed the user
203
+ // out if true, which was catastrophic: a
205
204
  // `session_created` event fired immediately after a successful
206
205
  // sign-in carries the user's NEW (now-active) session id, which
207
206
  // matched and triggered an instant remote sign-out toast on every
@@ -8,7 +8,7 @@
8
8
  * without relying on third-party cookies.
9
9
  *
10
10
  * For browsers without FedCM support, users will need to click a sign-in button
11
- * which triggers a popup-based authentication flow.
11
+ * which triggers redirect authentication.
12
12
  *
13
13
  * This is called automatically by OxyContext on web platforms.
14
14
  *
@@ -28,12 +28,12 @@ export type RouteName =
28
28
  | 'SavesCollections'
29
29
  | 'EditProfileField' // Dedicated screen for editing a single profile field
30
30
  | 'LearnMoreUsernames' // Informational screen about usernames
31
- | 'KarmaCenter'
32
- | 'KarmaLeaderboard'
33
- | 'KarmaRewards'
34
- | 'KarmaRules'
35
- | 'AboutKarma'
36
- | 'KarmaFAQ'
31
+ | 'TrustCenter'
32
+ | 'TrustLeaderboard'
33
+ | 'TrustRewards'
34
+ | 'TrustRules'
35
+ | 'AboutTrust'
36
+ | 'TrustFAQ'
37
37
  | 'FollowersList' // List of user's followers
38
38
  | 'FollowingList' // List of users being followed
39
39
  | 'CreateManagedAccount' // Create a new managed sub-account
@@ -67,13 +67,13 @@ const screenLoaders: Record<RouteName, () => ComponentType<BaseScreenProps>> = {
67
67
  EditProfileField: () => require('../screens/EditProfileFieldScreen').default,
68
68
  // Informational screens
69
69
  LearnMoreUsernames: () => require('../screens/LearnMoreUsernamesScreen').default,
70
- // Karma screens
71
- KarmaCenter: () => require('../screens/karma/KarmaCenterScreen').default,
72
- KarmaLeaderboard: () => require('../screens/karma/KarmaLeaderboardScreen').default,
73
- KarmaRewards: () => require('../screens/karma/KarmaRewardsScreen').default,
74
- KarmaRules: () => require('../screens/karma/KarmaRulesScreen').default,
75
- AboutKarma: () => require('../screens/karma/KarmaAboutScreen').default,
76
- KarmaFAQ: () => require('../screens/karma/KarmaFAQScreen').default,
70
+ // Oxy Trust screens
71
+ TrustCenter: () => require('../screens/trust/TrustCenterScreen').default,
72
+ TrustLeaderboard: () => require('../screens/trust/TrustLeaderboardScreen').default,
73
+ TrustRewards: () => require('../screens/trust/TrustRewardsScreen').default,
74
+ TrustRules: () => require('../screens/trust/TrustRulesScreen').default,
75
+ AboutTrust: () => require('../screens/trust/TrustAboutScreen').default,
76
+ TrustFAQ: () => require('../screens/trust/TrustFAQScreen').default,
77
77
  // User list screens (followers/following)
78
78
  FollowersList: () => require('../screens/FollowersListScreen').default,
79
79
  FollowingList: () => require('../screens/FollowingListScreen').default,
@@ -142,13 +142,12 @@ const OxyAuthScreen: React.FC<BaseScreenProps> = ({
142
142
  // `claimSessionByToken` — the device-flow equivalent of OAuth's
143
143
  // code-for-token exchange (RFC 8628 §3.4).
144
144
  //
145
- // Without that exchange the SDK has no bearer token, so the subsequent
146
- // `switchSession` -> `getTokenBySession` call (`GET /session/token/:id`) 401s
147
- // against the C1-hardened API the session is authorized server-side but the
148
- // app never becomes authenticated and the sheet sits "Waiting for
149
- // authorization..." forever. Once `claimSessionByToken` plants the tokens in
150
- // the HttpService, the rest of the session wiring flows through the normal
151
- // `switchSession` path. This mirrors `SignInModal`'s web flow exactly.
145
+ // Without that exchange the SDK has no bearer token the session is
146
+ // authorized server-side but the app never becomes authenticated and the
147
+ // sheet sits "Waiting for authorization..." forever. Once
148
+ // `claimSessionByToken` plants the tokens in the HttpService, the rest of the
149
+ // session wiring flows through the normal `switchSession` path. This mirrors
150
+ // `SignInModal`'s web flow exactly.
152
151
  const handleAuthSuccess = useCallback(async (sessionId: string, sessionToken: string) => {
153
152
  if (isProcessingRef.current) return;
154
153
  isProcessingRef.current = true;
@@ -221,8 +220,11 @@ const OxyAuthScreen: React.FC<BaseScreenProps> = ({
221
220
 
222
221
  socket.on('connect_error', (err) => {
223
222
  debug.log('Socket connection error, falling back to polling:', (err instanceof Error ? err.message : null));
224
- // Fall back to polling if socket fails
223
+ // Realtime transport errored reflect the honest connection state. The
224
+ // poll is already running (started unconditionally in
225
+ // `generateAuthSession`), so `startPolling` here is a no-op backstop.
225
226
  socket.disconnect();
227
+ setConnectionType('polling');
226
228
  startPolling(sessionToken);
227
229
  });
228
230
 
@@ -231,9 +233,13 @@ const OxyAuthScreen: React.FC<BaseScreenProps> = ({
231
233
  });
232
234
  }, [oxyServices, handleAuthSuccess]);
233
235
 
234
- // Start polling for authorization (fallback)
236
+ // Start polling for authorization.
237
+ //
238
+ // Idempotent: if a poll interval is already running this is a no-op, so the
239
+ // `connect_error` path (which also calls this) cannot stack a second interval
240
+ // on top of the always-on poll started in `generateAuthSession`.
235
241
  const startPolling = useCallback((sessionToken: string) => {
236
- setConnectionType('polling');
242
+ if (pollingIntervalRef.current) return;
237
243
 
238
244
  pollingIntervalRef.current = setInterval(async () => {
239
245
  if (isProcessingRef.current) return;
@@ -312,14 +318,17 @@ const OxyAuthScreen: React.FC<BaseScreenProps> = ({
312
318
  setAuthSession({ sessionToken, expiresAt });
313
319
  setIsWaiting(true);
314
320
 
315
- // Try socket first, will fall back to polling if needed
321
+ // Socket is the fast path; the poll is a transport-independent backstop
322
+ // that guarantees completion even if the socket connects but silently
323
+ // never delivers auth_update (RN transport / idle-timeout).
316
324
  connectSocket(sessionToken);
325
+ startPolling(sessionToken);
317
326
  } catch (err: unknown) {
318
327
  setError((err instanceof Error ? err.message : null) || 'Failed to create auth session');
319
328
  } finally {
320
329
  setIsLoading(false);
321
330
  }
322
- }, [oxyServices, connectSocket, clientId]);
331
+ }, [oxyServices, connectSocket, startPolling, clientId]);
323
332
 
324
333
  // Generate a random session token
325
334
  const generateSessionToken = (): string => {
@@ -32,7 +32,7 @@ const ProfileScreen: React.FC<ProfileScreenProps> = ({ userId, username, theme,
32
32
  // Use useOxy() hook for OxyContext values
33
33
  const { oxyServices, user: currentUser } = useOxy();
34
34
  const [profile, setProfile] = useState<User | null>(null);
35
- const [karmaTotal, setKarmaTotal] = useState<number | null>(null);
35
+ const [reputationTotal, setReputationTotal] = useState<number | null>(null);
36
36
  const [postsCount, setPostsCount] = useState<number | null>(null);
37
37
  const [commentsCount, setCommentsCount] = useState<number | null>(null);
38
38
  const [isLoading, setIsLoading] = useState(true);
@@ -77,7 +77,7 @@ const ProfileScreen: React.FC<ProfileScreenProps> = ({ userId, username, theme,
77
77
  setIsLoading(true);
78
78
  setError(null);
79
79
 
80
- // Load user profile, karma total, and stats
80
+ // Load user profile, reputation total, and stats
81
81
  Promise.all([
82
82
  oxyServices.getUserById(userId).catch((err: unknown) => {
83
83
  // If this is the current user and the API call fails, use current user data as fallback
@@ -88,18 +88,16 @@ const ProfileScreen: React.FC<ProfileScreenProps> = ({ userId, username, theme,
88
88
  }
89
89
  throw err;
90
90
  }),
91
- oxyServices.getUserKarmaTotal ?
92
- oxyServices.getUserKarmaTotal(userId).catch(() => {
93
- return { total: undefined };
94
- }) :
95
- Promise.resolve({ total: undefined }),
91
+ oxyServices.getReputationBalance(userId)
92
+ .then((balance): { total: number | undefined } => ({ total: balance.total }))
93
+ .catch((): { total: number | undefined } => ({ total: undefined })),
96
94
  oxyServices.getUserStats ?
97
95
  oxyServices.getUserStats(userId).catch(() => {
98
96
  return { postCount: 0, commentCount: 0 };
99
97
  }) :
100
98
  Promise.resolve({ postCount: 0, commentCount: 0 })
101
99
  ])
102
- .then(([profileRes, karmaRes, statsRes]) => {
100
+ .then(([profileRes, reputationRes, statsRes]) => {
103
101
  if (!profileRes) {
104
102
  setError('Profile data is not available');
105
103
  setIsLoading(false);
@@ -107,7 +105,7 @@ const ProfileScreen: React.FC<ProfileScreenProps> = ({ userId, username, theme,
107
105
  }
108
106
 
109
107
  setProfile(profileRes);
110
- setKarmaTotal(typeof karmaRes.total === 'number' ? karmaRes.total : null);
108
+ setReputationTotal(typeof reputationRes.total === 'number' ? reputationRes.total : null);
111
109
 
112
110
  // Extract links from profile data
113
111
  if (profileRes.linksMetadata && Array.isArray(profileRes.linksMetadata)) {
@@ -326,24 +324,24 @@ const ProfileScreen: React.FC<ProfileScreenProps> = ({ userId, username, theme,
326
324
  {/* All Stats in one row */}
327
325
  <View style={styles.statsRow}>
328
326
  <View style={styles.statItem}>
329
- <Text style={styles.karmaAmount} className="text-primary">{karmaTotal !== null && karmaTotal !== undefined ? karmaTotal : '--'}</Text>
330
- <Text style={styles.karmaLabel} className="text-muted-foreground">{t('profile.karma') || 'Karma'}</Text>
327
+ <Text style={styles.statAmount} className="text-primary">{reputationTotal !== null && reputationTotal !== undefined ? reputationTotal : '--'}</Text>
328
+ <Text style={styles.statLabel} className="text-muted-foreground">{t('profile.reputation') || 'Reputation'}</Text>
331
329
  </View>
332
330
  <View style={styles.statItem}>
333
331
  {isLoadingCounts ? (
334
332
  <ActivityIndicator size="small" color={bloomTheme.colors.text} />
335
333
  ) : (
336
- <Text style={styles.karmaAmount} className="text-foreground">{followerCount !== null ? followerCount : '--'}</Text>
334
+ <Text style={styles.statAmount} className="text-foreground">{followerCount !== null ? followerCount : '--'}</Text>
337
335
  )}
338
- <Text style={styles.karmaLabel} className="text-muted-foreground">{t('profile.followers') || 'Followers'}</Text>
336
+ <Text style={styles.statLabel} className="text-muted-foreground">{t('profile.followers') || 'Followers'}</Text>
339
337
  </View>
340
338
  <View style={styles.statItem}>
341
339
  {isLoadingCounts ? (
342
340
  <ActivityIndicator size="small" color={bloomTheme.colors.text} />
343
341
  ) : (
344
- <Text style={styles.karmaAmount} className="text-foreground">{followingCount !== null ? followingCount : '--'}</Text>
342
+ <Text style={styles.statAmount} className="text-foreground">{followingCount !== null ? followingCount : '--'}</Text>
345
343
  )}
346
- <Text style={styles.karmaLabel} className="text-muted-foreground">{t('profile.following') || 'Following'}</Text>
344
+ <Text style={styles.statLabel} className="text-muted-foreground">{t('profile.following') || 'Following'}</Text>
347
345
  </View>
348
346
  </View>
349
347
  </View>
@@ -401,8 +399,8 @@ const createStyles = () => StyleSheet.create({
401
399
  },
402
400
  statsRow: { width: '100%', flex: 1, flexDirection: 'row', alignItems: 'center', marginTop: 6, marginBottom: 2, justifyContent: 'space-between' },
403
401
  statItem: { flex: 1, alignItems: 'center', minWidth: 50, marginBottom: 12 },
404
- karmaLabel: { fontSize: 14, marginBottom: 2, textAlign: 'center' },
405
- karmaAmount: { fontSize: 24, fontWeight: 'bold', textAlign: 'center', letterSpacing: 0.2 },
402
+ statLabel: { fontSize: 14, marginBottom: 2, textAlign: 'center' },
403
+ statAmount: { fontSize: 24, fontWeight: 'bold', textAlign: 'center', letterSpacing: 0.2 },
406
404
  // Error handling styles
407
405
  errorHeader: {
408
406
  flexDirection: 'row',
@@ -115,9 +115,9 @@ const WelcomeNewUserScreen: React.FC<BaseScreenProps & { newUser?: any }> = ({
115
115
  t('welcomeNew.principles.bullets.3') || 'No selling your data',
116
116
  ]
117
117
  },
118
- { key: 'karma', title: t('welcomeNew.karma.title') || 'Karma = Trust & Growth', body: t('welcomeNew.karma.body') || 'Oxy Karma is a points system that reacts to what you do. Helpful, respectful, constructive actions earn it. Harmful or low‑effort stuff chips it away. More karma can unlock benefits; low karma can limit features. It keeps things fair and rewards real contribution.' },
118
+ { key: 'trust', title: t('welcomeNew.trust.title') || 'Oxy Trust = Trust & Growth', body: t('welcomeNew.trust.body') || 'Oxy Trust is a reputation system that reacts to what you do. Helpful, respectful, constructive actions earn reputation. Harmful or low‑effort stuff chips it away. More reputation raises your trust tier and can unlock benefits; low reputation can limit features. It keeps things fair and rewards real contribution.' },
119
119
  { key: 'avatar', title: t('welcomeNew.avatar.title') || 'Make It Yours', body: t('welcomeNew.avatar.body') || 'Add an avatar so people recognize you. It will show anywhere you show up here. Skip if you want — you can add it later.', showAvatar: true },
120
- { key: 'ready', title: t('welcomeNew.ready.title') || "You're Ready", body: t('welcomeNew.ready.body') || 'Explore. Contribute. Earn karma. Stay in control.' }
120
+ { key: 'ready', title: t('welcomeNew.ready.title') || "You're Ready", body: t('welcomeNew.ready.body') || 'Explore. Contribute. Earn reputation. Stay in control.' }
121
121
  ];
122
122
  const totalSteps = steps.length;
123
123
  const avatarStepIndex = steps.findIndex(s => s.showAvatar);