@oxyhq/services 15.0.0 → 17.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (280) hide show
  1. package/lib/commonjs/index.js +25 -55
  2. package/lib/commonjs/index.js.map +1 -1
  3. package/lib/commonjs/ui/client.js +0 -14
  4. package/lib/commonjs/ui/client.js.map +1 -1
  5. package/lib/commonjs/ui/components/OxyAccountDialog.js +764 -0
  6. package/lib/commonjs/ui/components/OxyAccountDialog.js.map +1 -0
  7. package/lib/commonjs/ui/components/OxyProvider.js +11 -6
  8. package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
  9. package/lib/commonjs/ui/components/OxySignInButton.js +10 -21
  10. package/lib/commonjs/ui/components/OxySignInButton.js.map +1 -1
  11. package/lib/commonjs/ui/components/ProfileButton.js +25 -112
  12. package/lib/commonjs/ui/components/ProfileButton.js.map +1 -1
  13. package/lib/commonjs/ui/components/RequireOxyAuth.js +299 -0
  14. package/lib/commonjs/ui/components/RequireOxyAuth.js.map +1 -0
  15. package/lib/commonjs/ui/context/OxyContext.js +106 -2
  16. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  17. package/lib/commonjs/ui/hooks/mutations/mutationKeys.js +1 -1
  18. package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js +10 -10
  19. package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js.map +1 -1
  20. package/lib/commonjs/ui/hooks/queries/queryKeys.js +1 -1
  21. package/lib/commonjs/ui/hooks/queries/useAccountQueries.js +7 -8
  22. package/lib/commonjs/ui/hooks/queries/useAccountQueries.js.map +1 -1
  23. package/lib/commonjs/ui/hooks/useAuth.js +19 -30
  24. package/lib/commonjs/ui/hooks/useAuth.js.map +1 -1
  25. package/lib/commonjs/ui/hooks/useSwitchableAccounts.js +37 -267
  26. package/lib/commonjs/ui/hooks/useSwitchableAccounts.js.map +1 -1
  27. package/lib/commonjs/ui/index.js +5 -40
  28. package/lib/commonjs/ui/index.js.map +1 -1
  29. package/lib/commonjs/ui/navigation/accountDialogManager.js +91 -0
  30. package/lib/commonjs/ui/navigation/accountDialogManager.js.map +1 -0
  31. package/lib/commonjs/ui/navigation/routes.js +0 -2
  32. package/lib/commonjs/ui/navigation/routes.js.map +1 -1
  33. package/lib/commonjs/ui/screens/ConnectedAppsScreen.js +17 -18
  34. package/lib/commonjs/ui/screens/ConnectedAppsScreen.js.map +1 -1
  35. package/lib/commonjs/ui/screens/ManageAccountScreen.js +4 -3
  36. package/lib/commonjs/ui/screens/ManageAccountScreen.js.map +1 -1
  37. package/lib/commonjs/ui/session/createSessionClient.js +10 -2
  38. package/lib/commonjs/ui/session/createSessionClient.js.map +1 -1
  39. package/lib/module/index.js +20 -17
  40. package/lib/module/index.js.map +1 -1
  41. package/lib/module/ui/client.js +1 -2
  42. package/lib/module/ui/client.js.map +1 -1
  43. package/lib/module/ui/components/OxyAccountDialog.js +761 -0
  44. package/lib/module/ui/components/OxyAccountDialog.js.map +1 -0
  45. package/lib/module/ui/components/OxyProvider.js +11 -6
  46. package/lib/module/ui/components/OxyProvider.js.map +1 -1
  47. package/lib/module/ui/components/OxySignInButton.js +10 -21
  48. package/lib/module/ui/components/OxySignInButton.js.map +1 -1
  49. package/lib/module/ui/components/ProfileButton.js +26 -112
  50. package/lib/module/ui/components/ProfileButton.js.map +1 -1
  51. package/lib/module/ui/components/RequireOxyAuth.js +294 -0
  52. package/lib/module/ui/components/RequireOxyAuth.js.map +1 -0
  53. package/lib/module/ui/context/OxyContext.js +107 -3
  54. package/lib/module/ui/context/OxyContext.js.map +1 -1
  55. package/lib/module/ui/hooks/mutations/mutationKeys.js +1 -1
  56. package/lib/module/ui/hooks/mutations/useAccountMutations.js +8 -8
  57. package/lib/module/ui/hooks/mutations/useAccountMutations.js.map +1 -1
  58. package/lib/module/ui/hooks/queries/queryKeys.js +1 -1
  59. package/lib/module/ui/hooks/queries/useAccountQueries.js +5 -6
  60. package/lib/module/ui/hooks/queries/useAccountQueries.js.map +1 -1
  61. package/lib/module/ui/hooks/useAuth.js +19 -30
  62. package/lib/module/ui/hooks/useAuth.js.map +1 -1
  63. package/lib/module/ui/hooks/useSwitchableAccounts.js +38 -267
  64. package/lib/module/ui/hooks/useSwitchableAccounts.js.map +1 -1
  65. package/lib/module/ui/index.js +1 -5
  66. package/lib/module/ui/index.js.map +1 -1
  67. package/lib/module/ui/navigation/accountDialogManager.js +81 -0
  68. package/lib/module/ui/navigation/accountDialogManager.js.map +1 -0
  69. package/lib/module/ui/navigation/routes.js +0 -2
  70. package/lib/module/ui/navigation/routes.js.map +1 -1
  71. package/lib/module/ui/screens/ConnectedAppsScreen.js +19 -20
  72. package/lib/module/ui/screens/ConnectedAppsScreen.js.map +1 -1
  73. package/lib/module/ui/screens/ManageAccountScreen.js +4 -3
  74. package/lib/module/ui/screens/ManageAccountScreen.js.map +1 -1
  75. package/lib/module/ui/session/createSessionClient.js +10 -2
  76. package/lib/module/ui/session/createSessionClient.js.map +1 -1
  77. package/lib/typescript/commonjs/index.d.ts +6 -13
  78. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  79. package/lib/typescript/commonjs/ui/client.d.ts +0 -4
  80. package/lib/typescript/commonjs/ui/client.d.ts.map +1 -1
  81. package/lib/typescript/commonjs/ui/components/OxyAccountDialog.d.ts +35 -0
  82. package/lib/typescript/commonjs/ui/components/OxyAccountDialog.d.ts.map +1 -0
  83. package/lib/typescript/commonjs/ui/components/OxyProvider.d.ts +2 -2
  84. package/lib/typescript/commonjs/ui/components/OxyProvider.d.ts.map +1 -1
  85. package/lib/typescript/commonjs/ui/components/OxySignInButton.d.ts.map +1 -1
  86. package/lib/typescript/commonjs/ui/components/ProfileButton.d.ts +11 -23
  87. package/lib/typescript/commonjs/ui/components/ProfileButton.d.ts.map +1 -1
  88. package/lib/typescript/commonjs/ui/components/RequireOxyAuth.d.ts +55 -0
  89. package/lib/typescript/commonjs/ui/components/RequireOxyAuth.d.ts.map +1 -0
  90. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts +13 -1
  91. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
  92. package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts +4 -4
  93. package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  94. package/lib/typescript/commonjs/ui/hooks/queries/useAccountQueries.d.ts +6 -7
  95. package/lib/typescript/commonjs/ui/hooks/queries/useAccountQueries.d.ts.map +1 -1
  96. package/lib/typescript/commonjs/ui/hooks/useAuth.d.ts.map +1 -1
  97. package/lib/typescript/commonjs/ui/hooks/useSwitchableAccounts.d.ts +13 -152
  98. package/lib/typescript/commonjs/ui/hooks/useSwitchableAccounts.d.ts.map +1 -1
  99. package/lib/typescript/commonjs/ui/index.d.ts +2 -5
  100. package/lib/typescript/commonjs/ui/index.d.ts.map +1 -1
  101. package/lib/typescript/commonjs/ui/navigation/accountDialogManager.d.ts +49 -0
  102. package/lib/typescript/commonjs/ui/navigation/accountDialogManager.d.ts.map +1 -0
  103. package/lib/typescript/commonjs/ui/navigation/routes.d.ts +1 -1
  104. package/lib/typescript/commonjs/ui/navigation/routes.d.ts.map +1 -1
  105. package/lib/typescript/commonjs/ui/screens/ConnectedAppsScreen.d.ts.map +1 -1
  106. package/lib/typescript/commonjs/ui/screens/ManageAccountScreen.d.ts +1 -1
  107. package/lib/typescript/commonjs/ui/screens/ManageAccountScreen.d.ts.map +1 -1
  108. package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts +9 -2
  109. package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts.map +1 -1
  110. package/lib/typescript/commonjs/ui/types/navigation.d.ts +9 -0
  111. package/lib/typescript/commonjs/ui/types/navigation.d.ts.map +1 -1
  112. package/lib/typescript/commonjs/ui/utils/storageHelpers.d.ts +7 -7
  113. package/lib/typescript/module/index.d.ts +6 -13
  114. package/lib/typescript/module/index.d.ts.map +1 -1
  115. package/lib/typescript/module/ui/client.d.ts +0 -4
  116. package/lib/typescript/module/ui/client.d.ts.map +1 -1
  117. package/lib/typescript/module/ui/components/OxyAccountDialog.d.ts +35 -0
  118. package/lib/typescript/module/ui/components/OxyAccountDialog.d.ts.map +1 -0
  119. package/lib/typescript/module/ui/components/OxyProvider.d.ts +2 -2
  120. package/lib/typescript/module/ui/components/OxyProvider.d.ts.map +1 -1
  121. package/lib/typescript/module/ui/components/OxySignInButton.d.ts.map +1 -1
  122. package/lib/typescript/module/ui/components/ProfileButton.d.ts +11 -23
  123. package/lib/typescript/module/ui/components/ProfileButton.d.ts.map +1 -1
  124. package/lib/typescript/module/ui/components/RequireOxyAuth.d.ts +55 -0
  125. package/lib/typescript/module/ui/components/RequireOxyAuth.d.ts.map +1 -0
  126. package/lib/typescript/module/ui/context/OxyContext.d.ts +13 -1
  127. package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
  128. package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts +4 -4
  129. package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  130. package/lib/typescript/module/ui/hooks/queries/useAccountQueries.d.ts +6 -7
  131. package/lib/typescript/module/ui/hooks/queries/useAccountQueries.d.ts.map +1 -1
  132. package/lib/typescript/module/ui/hooks/useAuth.d.ts.map +1 -1
  133. package/lib/typescript/module/ui/hooks/useSwitchableAccounts.d.ts +13 -152
  134. package/lib/typescript/module/ui/hooks/useSwitchableAccounts.d.ts.map +1 -1
  135. package/lib/typescript/module/ui/index.d.ts +2 -5
  136. package/lib/typescript/module/ui/index.d.ts.map +1 -1
  137. package/lib/typescript/module/ui/navigation/accountDialogManager.d.ts +49 -0
  138. package/lib/typescript/module/ui/navigation/accountDialogManager.d.ts.map +1 -0
  139. package/lib/typescript/module/ui/navigation/routes.d.ts +1 -1
  140. package/lib/typescript/module/ui/navigation/routes.d.ts.map +1 -1
  141. package/lib/typescript/module/ui/screens/ConnectedAppsScreen.d.ts.map +1 -1
  142. package/lib/typescript/module/ui/screens/ManageAccountScreen.d.ts +1 -1
  143. package/lib/typescript/module/ui/screens/ManageAccountScreen.d.ts.map +1 -1
  144. package/lib/typescript/module/ui/session/createSessionClient.d.ts +9 -2
  145. package/lib/typescript/module/ui/session/createSessionClient.d.ts.map +1 -1
  146. package/lib/typescript/module/ui/types/navigation.d.ts +9 -0
  147. package/lib/typescript/module/ui/types/navigation.d.ts.map +1 -1
  148. package/lib/typescript/module/ui/utils/storageHelpers.d.ts +7 -7
  149. package/package.json +4 -4
  150. package/src/index.ts +23 -28
  151. package/src/ui/client.ts +0 -4
  152. package/src/ui/components/OxyAccountDialog.tsx +760 -0
  153. package/src/ui/components/OxyProvider.tsx +12 -6
  154. package/src/ui/components/OxySignInButton.tsx +10 -21
  155. package/src/ui/components/ProfileButton.tsx +23 -126
  156. package/src/ui/components/RequireOxyAuth.tsx +266 -0
  157. package/src/ui/context/OxyContext.tsx +146 -7
  158. package/src/ui/hooks/mutations/mutationKeys.ts +1 -1
  159. package/src/ui/hooks/mutations/useAccountMutations.ts +8 -8
  160. package/src/ui/hooks/queries/queryKeys.ts +1 -1
  161. package/src/ui/hooks/queries/useAccountQueries.ts +8 -9
  162. package/src/ui/hooks/useAuth.ts +19 -31
  163. package/src/ui/hooks/useSwitchableAccounts.ts +42 -376
  164. package/src/ui/index.ts +2 -5
  165. package/src/ui/navigation/accountDialogManager.ts +84 -0
  166. package/src/ui/navigation/routes.ts +1 -5
  167. package/src/ui/screens/ConnectedAppsScreen.tsx +23 -24
  168. package/src/ui/screens/ManageAccountScreen.tsx +3 -2
  169. package/src/ui/session/createSessionClient.ts +9 -1
  170. package/src/ui/types/navigation.ts +9 -0
  171. package/src/ui/utils/storageHelpers.ts +7 -7
  172. package/lib/commonjs/ui/components/AccountMenu.js +0 -598
  173. package/lib/commonjs/ui/components/AccountMenu.js.map +0 -1
  174. package/lib/commonjs/ui/components/AccountMenuButton.js +0 -135
  175. package/lib/commonjs/ui/components/AccountMenuButton.js.map +0 -1
  176. package/lib/commonjs/ui/components/AccountSwitcher.js +0 -780
  177. package/lib/commonjs/ui/components/AccountSwitcher.js.map +0 -1
  178. package/lib/commonjs/ui/components/AnotherDeviceQR.js +0 -116
  179. package/lib/commonjs/ui/components/AnotherDeviceQR.js.map +0 -1
  180. package/lib/commonjs/ui/components/ProfileMenu.js +0 -518
  181. package/lib/commonjs/ui/components/ProfileMenu.js.map +0 -1
  182. package/lib/commonjs/ui/components/SignInAccountChooser.js +0 -189
  183. package/lib/commonjs/ui/components/SignInAccountChooser.js.map +0 -1
  184. package/lib/commonjs/ui/components/SignInModal.js +0 -600
  185. package/lib/commonjs/ui/components/SignInModal.js.map +0 -1
  186. package/lib/commonjs/ui/hooks/useOxyAuthSession.js +0 -501
  187. package/lib/commonjs/ui/hooks/useOxyAuthSession.js.map +0 -1
  188. package/lib/commonjs/ui/hooks/usePasswordSignIn.js +0 -170
  189. package/lib/commonjs/ui/hooks/usePasswordSignIn.js.map +0 -1
  190. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +0 -51
  191. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +0 -1
  192. package/lib/commonjs/ui/screens/OxyAuthScreen.js +0 -312
  193. package/lib/commonjs/ui/screens/OxyAuthScreen.js.map +0 -1
  194. package/lib/commonjs/utils/deviceFlowSignIn.js +0 -109
  195. package/lib/commonjs/utils/deviceFlowSignIn.js.map +0 -1
  196. package/lib/module/ui/components/AccountMenu.js +0 -593
  197. package/lib/module/ui/components/AccountMenu.js.map +0 -1
  198. package/lib/module/ui/components/AccountMenuButton.js +0 -130
  199. package/lib/module/ui/components/AccountMenuButton.js.map +0 -1
  200. package/lib/module/ui/components/AccountSwitcher.js +0 -774
  201. package/lib/module/ui/components/AccountSwitcher.js.map +0 -1
  202. package/lib/module/ui/components/AnotherDeviceQR.js +0 -110
  203. package/lib/module/ui/components/AnotherDeviceQR.js.map +0 -1
  204. package/lib/module/ui/components/ProfileMenu.js +0 -514
  205. package/lib/module/ui/components/ProfileMenu.js.map +0 -1
  206. package/lib/module/ui/components/SignInAccountChooser.js +0 -183
  207. package/lib/module/ui/components/SignInAccountChooser.js.map +0 -1
  208. package/lib/module/ui/components/SignInModal.js +0 -591
  209. package/lib/module/ui/components/SignInModal.js.map +0 -1
  210. package/lib/module/ui/hooks/useOxyAuthSession.js +0 -495
  211. package/lib/module/ui/hooks/useOxyAuthSession.js.map +0 -1
  212. package/lib/module/ui/hooks/usePasswordSignIn.js +0 -165
  213. package/lib/module/ui/hooks/usePasswordSignIn.js.map +0 -1
  214. package/lib/module/ui/screens/AccountSwitcherScreen.js +0 -48
  215. package/lib/module/ui/screens/AccountSwitcherScreen.js.map +0 -1
  216. package/lib/module/ui/screens/OxyAuthScreen.js +0 -306
  217. package/lib/module/ui/screens/OxyAuthScreen.js.map +0 -1
  218. package/lib/module/utils/deviceFlowSignIn.js +0 -106
  219. package/lib/module/utils/deviceFlowSignIn.js.map +0 -1
  220. package/lib/typescript/commonjs/ui/components/AccountMenu.d.ts +0 -47
  221. package/lib/typescript/commonjs/ui/components/AccountMenu.d.ts.map +0 -1
  222. package/lib/typescript/commonjs/ui/components/AccountMenuButton.d.ts +0 -26
  223. package/lib/typescript/commonjs/ui/components/AccountMenuButton.d.ts.map +0 -1
  224. package/lib/typescript/commonjs/ui/components/AccountSwitcher.d.ts +0 -46
  225. package/lib/typescript/commonjs/ui/components/AccountSwitcher.d.ts.map +0 -1
  226. package/lib/typescript/commonjs/ui/components/AnotherDeviceQR.d.ts +0 -34
  227. package/lib/typescript/commonjs/ui/components/AnotherDeviceQR.d.ts.map +0 -1
  228. package/lib/typescript/commonjs/ui/components/ProfileMenu.d.ts +0 -49
  229. package/lib/typescript/commonjs/ui/components/ProfileMenu.d.ts.map +0 -1
  230. package/lib/typescript/commonjs/ui/components/SignInAccountChooser.d.ts +0 -29
  231. package/lib/typescript/commonjs/ui/components/SignInAccountChooser.d.ts.map +0 -1
  232. package/lib/typescript/commonjs/ui/components/SignInModal.d.ts +0 -28
  233. package/lib/typescript/commonjs/ui/components/SignInModal.d.ts.map +0 -1
  234. package/lib/typescript/commonjs/ui/hooks/useOxyAuthSession.d.ts +0 -105
  235. package/lib/typescript/commonjs/ui/hooks/useOxyAuthSession.d.ts.map +0 -1
  236. package/lib/typescript/commonjs/ui/hooks/usePasswordSignIn.d.ts +0 -31
  237. package/lib/typescript/commonjs/ui/hooks/usePasswordSignIn.d.ts.map +0 -1
  238. package/lib/typescript/commonjs/ui/screens/AccountSwitcherScreen.d.ts +0 -11
  239. package/lib/typescript/commonjs/ui/screens/AccountSwitcherScreen.d.ts.map +0 -1
  240. package/lib/typescript/commonjs/ui/screens/OxyAuthScreen.d.ts +0 -22
  241. package/lib/typescript/commonjs/ui/screens/OxyAuthScreen.d.ts.map +0 -1
  242. package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts +0 -97
  243. package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts.map +0 -1
  244. package/lib/typescript/module/ui/components/AccountMenu.d.ts +0 -47
  245. package/lib/typescript/module/ui/components/AccountMenu.d.ts.map +0 -1
  246. package/lib/typescript/module/ui/components/AccountMenuButton.d.ts +0 -26
  247. package/lib/typescript/module/ui/components/AccountMenuButton.d.ts.map +0 -1
  248. package/lib/typescript/module/ui/components/AccountSwitcher.d.ts +0 -46
  249. package/lib/typescript/module/ui/components/AccountSwitcher.d.ts.map +0 -1
  250. package/lib/typescript/module/ui/components/AnotherDeviceQR.d.ts +0 -34
  251. package/lib/typescript/module/ui/components/AnotherDeviceQR.d.ts.map +0 -1
  252. package/lib/typescript/module/ui/components/ProfileMenu.d.ts +0 -49
  253. package/lib/typescript/module/ui/components/ProfileMenu.d.ts.map +0 -1
  254. package/lib/typescript/module/ui/components/SignInAccountChooser.d.ts +0 -29
  255. package/lib/typescript/module/ui/components/SignInAccountChooser.d.ts.map +0 -1
  256. package/lib/typescript/module/ui/components/SignInModal.d.ts +0 -28
  257. package/lib/typescript/module/ui/components/SignInModal.d.ts.map +0 -1
  258. package/lib/typescript/module/ui/hooks/useOxyAuthSession.d.ts +0 -105
  259. package/lib/typescript/module/ui/hooks/useOxyAuthSession.d.ts.map +0 -1
  260. package/lib/typescript/module/ui/hooks/usePasswordSignIn.d.ts +0 -31
  261. package/lib/typescript/module/ui/hooks/usePasswordSignIn.d.ts.map +0 -1
  262. package/lib/typescript/module/ui/screens/AccountSwitcherScreen.d.ts +0 -11
  263. package/lib/typescript/module/ui/screens/AccountSwitcherScreen.d.ts.map +0 -1
  264. package/lib/typescript/module/ui/screens/OxyAuthScreen.d.ts +0 -22
  265. package/lib/typescript/module/ui/screens/OxyAuthScreen.d.ts.map +0 -1
  266. package/lib/typescript/module/utils/deviceFlowSignIn.d.ts +0 -97
  267. package/lib/typescript/module/utils/deviceFlowSignIn.d.ts.map +0 -1
  268. package/src/ui/components/AccountMenu.tsx +0 -645
  269. package/src/ui/components/AccountMenuButton.tsx +0 -126
  270. package/src/ui/components/AccountSwitcher.tsx +0 -751
  271. package/src/ui/components/AnotherDeviceQR.tsx +0 -119
  272. package/src/ui/components/ProfileMenu.tsx +0 -564
  273. package/src/ui/components/SignInAccountChooser.tsx +0 -162
  274. package/src/ui/components/SignInModal.tsx +0 -530
  275. package/src/ui/hooks/useOxyAuthSession.ts +0 -635
  276. package/src/ui/hooks/usePasswordSignIn.ts +0 -207
  277. package/src/ui/screens/AccountSwitcherScreen.tsx +0 -43
  278. package/src/ui/screens/OxyAuthScreen.tsx +0 -324
  279. package/src/utils/__tests__/deviceFlowSignIn.test.ts +0 -231
  280. package/src/utils/deviceFlowSignIn.ts +0 -152
@@ -1,645 +0,0 @@
1
- import type React from 'react';
2
- import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
- import {
4
- View,
5
- Text,
6
- TouchableOpacity,
7
- StyleSheet,
8
- ScrollView,
9
- Modal,
10
- Pressable,
11
- Platform,
12
- ActivityIndicator,
13
- type ViewStyle,
14
- } from 'react-native';
15
- import { Ionicons } from '@expo/vector-icons';
16
- import { toast, useDialogControl, Dialog } from '@oxyhq/bloom';
17
- import { Divider } from '@oxyhq/bloom/divider';
18
- import { useTheme } from '@oxyhq/bloom/theme';
19
- import Avatar from './Avatar';
20
- import { useOxy } from '../context/OxyContext';
21
- import { useI18n } from '../hooks/useI18n';
22
- import { isDev, logger as loggerUtil } from '@oxyhq/core';
23
- import { useSwitchableAccounts, type SwitchableAccount } from '../hooks/useSwitchableAccounts';
24
-
25
- /**
26
- * Web-only anchor for the popover panel. Each field anchors the panel against
27
- * one viewport edge, so the popover can be placed against ANY corner: a
28
- * top-right avatar chip opens downward/right-aligned (`{ top, right }`), while a
29
- * bottom-left account button opens upward/left-aligned (`{ bottom, left }`).
30
- *
31
- * Callers MUST supply at most one vertical edge (`top` XOR `bottom`) and at most
32
- * one horizontal edge (`left` XOR `right`). The panel has a fixed width and
33
- * `maxHeight`, so a single vertical + single horizontal edge fully positions it.
34
- * Supplying both opposite edges (e.g. `top` AND `bottom`) would stretch the
35
- * panel on RN-Web and is unsupported.
36
- */
37
- export interface AccountMenuAnchor {
38
- /** Distance from the viewport top, when anchoring the panel's TOP edge. */
39
- top?: number;
40
- /** Distance from the viewport bottom, when anchoring the panel's BOTTOM edge (opens upward). */
41
- bottom?: number;
42
- /** Distance from the viewport left, when anchoring the panel's LEFT edge. */
43
- left?: number;
44
- /** Distance from the viewport right, when anchoring the panel's RIGHT edge. */
45
- right?: number;
46
- }
47
-
48
- export interface AccountMenuProps {
49
- open: boolean;
50
- onClose: () => void;
51
- onNavigateManage: () => void;
52
- onAddAccount: () => void;
53
- /** Optional anchor (web only). Native ignores this and uses bottom-sheet style. */
54
- anchor?: AccountMenuAnchor | null;
55
- /** Called before the active identity changes so apps can clear tenant-scoped state. */
56
- onBeforeSessionChange?: () => void | Promise<void>;
57
- }
58
-
59
- const isWeb = Platform.OS === 'web';
60
-
61
- /** Fixed popover width on web. Callers that compute an anchor (e.g. inbox's
62
- * MailboxDrawer) key their gutter math off this — keep them consistent. */
63
- const PANEL_WIDTH = 360;
64
-
65
- /**
66
- * Unified, canonical account switcher for the Oxy ecosystem. Gmail-style: the
67
- * accounts list sits at the top (current account first, with a checkmark), then
68
- * "Add another account", "Manage account", and the sign-out actions. Lists EVERY
69
- * switchable account (device sign-ins AND linked graph accounts) and routes every
70
- * switch through the single `switchToAccount(accountId)` path.
71
- *
72
- * Reads everything it needs from `useOxy()` / `useSwitchableAccounts()` — never
73
- * receives a session via props. Renders as a popover anchored to the trigger on
74
- * web, and as a full-width bottom-sheet style modal on native.
75
- */
76
- const AccountMenu: React.FC<AccountMenuProps> = ({
77
- open,
78
- onClose,
79
- onNavigateManage,
80
- onAddAccount,
81
- anchor,
82
- onBeforeSessionChange,
83
- }) => {
84
- const {
85
- switchToAccount,
86
- logout,
87
- logoutAll,
88
- removeSession,
89
- } = useOxy();
90
- const { t } = useI18n();
91
- const bloomTheme = useTheme();
92
- const colors = bloomTheme.colors;
93
-
94
- // Source EVERY switchable account — device sign-ins AND linked graph
95
- // accounts (owned orgs + shared-with-you) — from the single
96
- // `useSwitchableAccounts` hook, each hydrated with real
97
- // name/email/avatar/color. It also synthesises a live-user row when the
98
- // device set has not synced yet, so the signed-in user is always represented
99
- // (no "Not signed in" false negative).
100
- const { accounts } = useSwitchableAccounts();
101
-
102
- const [switchingAccountId, setSwitchingAccountId] = useState<string | null>(null);
103
- const [removingSessionId, setRemovingSessionId] = useState<string | null>(null);
104
- const [signingOut, setSigningOut] = useState(false);
105
- const [signingOutAll, setSigningOutAll] = useState(false);
106
-
107
- const signOutDialog = useDialogControl();
108
- const signOutAllDialog = useDialogControl();
109
-
110
- const containerRef = useRef<View | null>(null);
111
-
112
- // Current account first, then the remaining device sign-ins, then the linked
113
- // graph accounts — matching the Gmail-style chooser the inbox design ports.
114
- const rows = useMemo<SwitchableAccount[]>(() => {
115
- const device = accounts.filter((account) => account.onDevice);
116
- const graph = accounts.filter((account) => !account.onDevice);
117
- const current = device.filter((account) => account.isCurrent);
118
- const others = device.filter((account) => !account.isCurrent);
119
- return [...current, ...others, ...graph];
120
- }, [accounts]);
121
-
122
- // "Sign out of all accounts" only applies to real device sessions.
123
- const deviceCount = useMemo(
124
- () => accounts.filter((account) => account.onDevice).length,
125
- [accounts],
126
- );
127
-
128
- // Account ids currently listed — used to indent a graph-only row whose parent
129
- // account is also shown.
130
- const listedAccountIds = useMemo(
131
- () => new Set(rows.map((row) => row.accountId)),
132
- [rows],
133
- );
134
-
135
- const isSwitching = switchingAccountId !== null;
136
-
137
- // The ONE uniform switch path: every row (device sign-in or linked graph
138
- // account) routes through `useOxy().switchToAccount(accountId)`. It reuses
139
- // the device session when the account is already signed in here, and mints a
140
- // real session only on the first switch into a graph account — a single
141
- // source of truth for every switch.
142
- const handleSwitch = useCallback(async (account: SwitchableAccount) => {
143
- if (account.isCurrent || switchingAccountId) {
144
- return;
145
- }
146
- setSwitchingAccountId(account.accountId);
147
- try {
148
- await onBeforeSessionChange?.();
149
- await switchToAccount(account.accountId);
150
- toast.success(t('accountSwitcher.toasts.switchSuccess') || 'Switched account');
151
- onClose();
152
- } catch (error) {
153
- if (!isDev()) {
154
- loggerUtil.warn('Switch account failed', { component: 'AccountMenu' }, error as unknown);
155
- }
156
- toast.error(t('accountSwitcher.toasts.switchFailed') || 'Failed to switch account');
157
- } finally {
158
- setSwitchingAccountId(null);
159
- }
160
- }, [switchingAccountId, switchToAccount, t, onClose, onBeforeSessionChange]);
161
-
162
- // Sign out a SPECIFIC inactive account from its per-row icon. `removeSession`
163
- // is the SDK's canonical per-session sign-out: it targets the given session
164
- // id (cookie-cleared logout via `authuser` slot on web, bearer logout
165
- // otherwise) and removes ONLY that account without switching/clearing the
166
- // active session. The menu stays open so the user can keep managing accounts.
167
- const handleRemove = useCallback(async (sessionId: string) => {
168
- if (removingSessionId) {
169
- return;
170
- }
171
- setRemovingSessionId(sessionId);
172
- try {
173
- await removeSession(sessionId);
174
- toast.success(t('common.actions.signedOut') || 'Signed out');
175
- } catch (error) {
176
- loggerUtil.warn('Remove account failed', { component: 'AccountMenu' }, error as unknown);
177
- toast.error(t('common.errors.signOutFailed') || 'Failed to sign out');
178
- } finally {
179
- setRemovingSessionId(null);
180
- }
181
- }, [removingSessionId, removeSession, t]);
182
-
183
- const performSignOut = useCallback(async () => {
184
- if (signingOut) {
185
- return;
186
- }
187
- setSigningOut(true);
188
- try {
189
- await onBeforeSessionChange?.();
190
- await logout();
191
- toast.success(t('common.actions.signedOut') || 'Signed out');
192
- onClose();
193
- } catch (error) {
194
- loggerUtil.warn('Sign out failed', { component: 'AccountMenu' }, error as unknown);
195
- toast.error(t('common.errors.signOutFailed') || 'Failed to sign out');
196
- } finally {
197
- setSigningOut(false);
198
- }
199
- }, [signingOut, logout, t, onClose, onBeforeSessionChange]);
200
-
201
- const performSignOutAll = useCallback(async () => {
202
- if (signingOutAll) {
203
- return;
204
- }
205
- setSigningOutAll(true);
206
- try {
207
- await onBeforeSessionChange?.();
208
- await logoutAll();
209
- toast.success(t('accountSwitcher.toasts.signOutAllSuccess') || 'Signed out of all accounts');
210
- onClose();
211
- } catch (error) {
212
- loggerUtil.warn('Sign out all failed', { component: 'AccountMenu' }, error as unknown);
213
- toast.error(t('common.errors.signOutAllFailed') || 'Failed to sign out of all accounts');
214
- } finally {
215
- setSigningOutAll(false);
216
- }
217
- }, [signingOutAll, logoutAll, t, onClose, onBeforeSessionChange]);
218
-
219
- // Escape-to-close + focus management (web only).
220
- useEffect(() => {
221
- if (!open || !isWeb || typeof document === 'undefined') {
222
- return undefined;
223
- }
224
- const onKey = (event: KeyboardEvent) => {
225
- if (event.key === 'Escape') {
226
- event.stopPropagation();
227
- onClose();
228
- }
229
- };
230
- document.addEventListener('keydown', onKey, true);
231
- return () => document.removeEventListener('keydown', onKey, true);
232
- }, [open, onClose]);
233
-
234
- if (!open) {
235
- return null;
236
- }
237
-
238
- const overlayStyles: ViewStyle = isWeb
239
- ? styles.webOverlay
240
- : styles.nativeOverlay;
241
-
242
- // Apply ONLY the edges the anchor supplies (so the panel never sets
243
- // conflicting opposite edges). When no anchor is provided, fall back to the
244
- // historical top-right placement used by `AccountMenuButton`.
245
- const anchorStyle: ViewStyle = anchor
246
- ? {
247
- ...(anchor.top !== undefined ? { top: anchor.top } : null),
248
- ...(anchor.bottom !== undefined ? { bottom: anchor.bottom } : null),
249
- ...(anchor.left !== undefined ? { left: anchor.left } : null),
250
- ...(anchor.right !== undefined ? { right: anchor.right } : null),
251
- }
252
- : { top: 64, right: 16 };
253
-
254
- const panelStyles: ViewStyle[] = isWeb
255
- ? [
256
- styles.panelBase,
257
- styles.panelWeb,
258
- anchorStyle,
259
- { backgroundColor: colors.background, borderColor: colors.border },
260
- ]
261
- : [
262
- styles.panelBase,
263
- styles.panelNative,
264
- { backgroundColor: colors.background },
265
- ];
266
-
267
- const actionDisabled = isSwitching || signingOut || signingOutAll;
268
-
269
- const content = (
270
- <Pressable
271
- ref={containerRef}
272
- // Swallow taps inside the panel so they never reach the overlay's
273
- // outside-tap-to-close handler. (On web the panel is a direct,
274
- // absolutely-positioned child of the overlay so the anchor resolves
275
- // against the viewport.)
276
- onPress={() => undefined}
277
- style={panelStyles}
278
- accessibilityRole="menu"
279
- accessibilityLabel={t('accountMenu.label') || 'Account menu'}
280
- >
281
- <ScrollView
282
- style={styles.scroll}
283
- contentContainerStyle={styles.scrollContent}
284
- showsVerticalScrollIndicator={false}
285
- >
286
- {/* 1) Accounts list — current first (checkmark), then the other
287
- device sign-ins, then linked graph accounts (role badge,
288
- indented under their parent). Every row switches the same way. */}
289
- {rows.map((row) => {
290
- const sessionId = row.sessionId;
291
- const isBusy = switchingAccountId === row.accountId;
292
- const isRemoving = sessionId ? removingSessionId === sessionId : false;
293
- const isChild = Boolean(
294
- row.parentAccountId && listedAccountIds.has(row.parentAccountId),
295
- );
296
- const role = row.callerMembership?.role;
297
- return (
298
- <TouchableOpacity
299
- key={`account-${row.accountId}`}
300
- accessibilityRole="menuitem"
301
- accessibilityLabel={row.displayName}
302
- accessibilityState={{ selected: row.isCurrent }}
303
- onPress={() => handleSwitch(row)}
304
- disabled={row.isCurrent || isBusy || isSwitching}
305
- activeOpacity={0.6}
306
- style={[
307
- styles.accountRow,
308
- isChild && styles.childRow,
309
- row.isCurrent && { backgroundColor: colors.primarySubtle },
310
- isSwitching && !row.isCurrent && styles.rowDisabled,
311
- ]}
312
- >
313
- <Avatar
314
- uri={row.avatarUrl}
315
- name={row.displayName}
316
- size={row.isCurrent ? 40 : isChild ? 28 : 32}
317
- />
318
- <View style={styles.accountInfo}>
319
- <View style={styles.nameRow}>
320
- <Text
321
- style={[
322
- styles.accountName,
323
- { color: colors.text },
324
- row.isCurrent && styles.accountNameActive,
325
- ]}
326
- numberOfLines={1}
327
- >
328
- {row.displayName}
329
- </Text>
330
- {role ? (
331
- <View style={[styles.roleBadge, { backgroundColor: colors.card }]}>
332
- <Text style={[styles.roleBadgeText, { color: colors.textSecondary }]}>
333
- {t(`accounts.roles.${role}.label`) || role}
334
- </Text>
335
- </View>
336
- ) : null}
337
- </View>
338
- {row.email ? (
339
- <Text
340
- style={[styles.accountEmail, { color: colors.textSecondary }]}
341
- numberOfLines={1}
342
- >
343
- {row.email}
344
- </Text>
345
- ) : null}
346
- </View>
347
- {isBusy ? (
348
- <ActivityIndicator color={colors.primary} size="small" />
349
- ) : row.isCurrent ? (
350
- <Ionicons name="checkmark" size={20} color={colors.primary} />
351
- ) : isRemoving ? (
352
- <ActivityIndicator color={colors.textSecondary} size="small" />
353
- ) : sessionId ? (
354
- <TouchableOpacity
355
- accessibilityRole="button"
356
- accessibilityLabel={
357
- t('accountMenu.signOutAccount', { name: row.displayName })
358
- || `Sign out ${row.displayName}`
359
- }
360
- onPress={() => handleRemove(sessionId)}
361
- disabled={isSwitching || removingSessionId !== null}
362
- activeOpacity={0.6}
363
- hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
364
- style={styles.rowSignOutButton}
365
- >
366
- <Ionicons
367
- name="log-out-outline"
368
- size={18}
369
- color={colors.textSecondary}
370
- />
371
- </TouchableOpacity>
372
- ) : null}
373
- </TouchableOpacity>
374
- );
375
- })}
376
-
377
- {/* 2) Switching indicator. */}
378
- {isSwitching ? (
379
- <View style={styles.switchingRow}>
380
- <ActivityIndicator color={colors.textSecondary} size="small" />
381
- <Text style={[styles.switchingText, { color: colors.textSecondary }]}>
382
- {t('accountMenu.switching') || 'Switching account…'}
383
- </Text>
384
- </View>
385
- ) : null}
386
-
387
- <Divider color={colors.border} spacing={4} />
388
-
389
- {/* 3) Add another account. */}
390
- <TouchableOpacity
391
- accessibilityRole="menuitem"
392
- accessibilityLabel={t('accountMenu.addAnother') || 'Add another account'}
393
- onPress={() => {
394
- onClose();
395
- onAddAccount();
396
- }}
397
- disabled={actionDisabled}
398
- activeOpacity={0.6}
399
- style={[styles.actionRow, actionDisabled && styles.rowDisabled]}
400
- >
401
- <Ionicons name="person-add-outline" size={20} color={colors.icon} />
402
- <Text style={[styles.actionText, { color: colors.text }]}>
403
- {t('accountMenu.addAnother') || 'Add another account'}
404
- </Text>
405
- </TouchableOpacity>
406
-
407
- <Divider color={colors.border} spacing={4} />
408
-
409
- {/* 4) Manage account / Settings. */}
410
- <TouchableOpacity
411
- accessibilityRole="menuitem"
412
- accessibilityLabel={t('accountMenu.manage') || 'Manage your Oxy Account'}
413
- onPress={() => {
414
- onClose();
415
- onNavigateManage();
416
- }}
417
- disabled={actionDisabled}
418
- activeOpacity={0.6}
419
- style={[styles.actionRow, actionDisabled && styles.rowDisabled]}
420
- >
421
- <Ionicons name="settings-outline" size={18} color={colors.icon} />
422
- <Text style={[styles.actionText, { color: colors.text }]}>
423
- {t('accountMenu.manage') || 'Manage your Oxy Account'}
424
- </Text>
425
- </TouchableOpacity>
426
-
427
- {/* 5) Sign out (current). */}
428
- <TouchableOpacity
429
- accessibilityRole="menuitem"
430
- accessibilityLabel={t('common.actions.signOut') || 'Sign out'}
431
- onPress={() => signOutDialog.open()}
432
- disabled={actionDisabled}
433
- activeOpacity={0.6}
434
- style={[styles.actionRow, actionDisabled && styles.rowDisabled]}
435
- >
436
- {signingOut ? (
437
- <ActivityIndicator color={colors.error} size="small" />
438
- ) : (
439
- <Ionicons name="log-out-outline" size={18} color={colors.error} />
440
- )}
441
- <Text style={[styles.actionText, { color: colors.error }]}>
442
- {t('common.actions.signOut') || 'Sign out'}
443
- </Text>
444
- </TouchableOpacity>
445
-
446
- {/* 6) Sign out of all accounts (only when >1 device sign-in). */}
447
- {deviceCount > 1 ? (
448
- <TouchableOpacity
449
- accessibilityRole="menuitem"
450
- accessibilityLabel={t('accountMenu.signOutAll') || 'Sign out of all accounts'}
451
- onPress={() => signOutAllDialog.open()}
452
- disabled={actionDisabled}
453
- activeOpacity={0.6}
454
- style={[styles.actionRow, actionDisabled && styles.rowDisabled]}
455
- >
456
- {signingOutAll ? (
457
- <ActivityIndicator color={colors.error} size="small" />
458
- ) : (
459
- <Ionicons name="log-out-outline" size={18} color={colors.error} />
460
- )}
461
- <Text style={[styles.actionText, { color: colors.error }]}>
462
- {t('accountMenu.signOutAll') || 'Sign out of all accounts'}
463
- </Text>
464
- </TouchableOpacity>
465
- ) : null}
466
- </ScrollView>
467
- </Pressable>
468
- );
469
-
470
- return (
471
- <Modal
472
- visible={open}
473
- transparent
474
- animationType={isWeb ? 'fade' : 'slide'}
475
- onRequestClose={onClose}
476
- >
477
- <Pressable
478
- accessibilityRole="button"
479
- accessibilityLabel={t('common.actions.close') || 'Close'}
480
- onPress={onClose}
481
- style={overlayStyles}
482
- >
483
- {/*
484
- * Web: the panel is `position: absolute` and rendered as a DIRECT
485
- * child of the full-viewport overlay, so its anchor edges resolve
486
- * against the viewport. The panel swallows its own taps.
487
- *
488
- * Native: the overlay is `justify-content: flex-end`, so the
489
- * (statically positioned) panel docks to the bottom as a sheet.
490
- */}
491
- {content}
492
- </Pressable>
493
-
494
- <Dialog
495
- control={signOutDialog}
496
- title={t('common.actions.signOut') || 'Sign out'}
497
- description={t('common.confirms.signOut') || 'Are you sure you want to sign out?'}
498
- actions={[
499
- {
500
- label: t('common.actions.signOut') || 'Sign out',
501
- color: 'destructive',
502
- onPress: performSignOut,
503
- },
504
- { label: t('common.cancel') || 'Cancel', color: 'cancel' },
505
- ]}
506
- />
507
- <Dialog
508
- control={signOutAllDialog}
509
- title={t('accountMenu.signOutAll') || 'Sign out of all accounts'}
510
- description={t('common.confirms.signOutAll') || 'Are you sure you want to sign out of all accounts?'}
511
- actions={[
512
- {
513
- label: t('accountMenu.signOutAll') || 'Sign out of all accounts',
514
- color: 'destructive',
515
- onPress: performSignOutAll,
516
- },
517
- { label: t('common.cancel') || 'Cancel', color: 'cancel' },
518
- ]}
519
- />
520
- </Modal>
521
- );
522
- };
523
-
524
- const styles = StyleSheet.create({
525
- webOverlay: {
526
- flex: 1,
527
- backgroundColor: 'transparent',
528
- // Explicit positioning context: the absolutely-positioned panel is a
529
- // direct child and resolves its anchor edges (top/bottom/left/right)
530
- // against this full-viewport overlay — matching the trigger-rect math
531
- // callers compute from `window.innerWidth` / `window.innerHeight`.
532
- position: 'relative',
533
- },
534
- nativeOverlay: {
535
- flex: 1,
536
- backgroundColor: 'rgba(0,0,0,0.32)',
537
- justifyContent: 'flex-end',
538
- },
539
- panelBase: {
540
- borderRadius: 12,
541
- shadowColor: '#000',
542
- shadowOpacity: 0.18,
543
- shadowRadius: 24,
544
- shadowOffset: { width: 0, height: 8 },
545
- elevation: 12,
546
- overflow: 'hidden',
547
- },
548
- panelWeb: {
549
- position: 'absolute',
550
- width: PANEL_WIDTH,
551
- maxHeight: '85%',
552
- borderWidth: 1,
553
- },
554
- panelNative: {
555
- marginHorizontal: 0,
556
- borderTopLeftRadius: 20,
557
- borderTopRightRadius: 20,
558
- borderBottomLeftRadius: 0,
559
- borderBottomRightRadius: 0,
560
- paddingBottom: 12,
561
- maxHeight: '85%',
562
- },
563
- scroll: {
564
- flexGrow: 0,
565
- },
566
- scrollContent: {
567
- paddingVertical: 4,
568
- },
569
- accountRow: {
570
- flexDirection: 'row',
571
- alignItems: 'center',
572
- paddingHorizontal: 14,
573
- paddingVertical: 10,
574
- gap: 10,
575
- },
576
- childRow: {
577
- paddingLeft: 34,
578
- },
579
- rowDisabled: {
580
- opacity: 0.4,
581
- },
582
- accountInfo: {
583
- flex: 1,
584
- minWidth: 0,
585
- },
586
- nameRow: {
587
- flexDirection: 'row',
588
- alignItems: 'center',
589
- gap: 6,
590
- },
591
- accountName: {
592
- fontSize: 13,
593
- fontWeight: '500',
594
- flexShrink: 1,
595
- },
596
- accountNameActive: {
597
- fontWeight: '600',
598
- },
599
- roleBadge: {
600
- paddingHorizontal: 6,
601
- paddingVertical: 1,
602
- borderRadius: 8,
603
- },
604
- roleBadgeText: {
605
- fontSize: 10,
606
- fontWeight: '600',
607
- textTransform: 'capitalize',
608
- },
609
- accountEmail: {
610
- fontSize: 11,
611
- marginTop: 1,
612
- },
613
- rowSignOutButton: {
614
- width: 28,
615
- height: 28,
616
- alignItems: 'center',
617
- justifyContent: 'center',
618
- borderRadius: 14,
619
- opacity: 0.6,
620
- },
621
- switchingRow: {
622
- flexDirection: 'row',
623
- alignItems: 'center',
624
- justifyContent: 'center',
625
- paddingVertical: 8,
626
- gap: 8,
627
- },
628
- switchingText: {
629
- fontSize: 12,
630
- fontWeight: '500',
631
- },
632
- actionRow: {
633
- flexDirection: 'row',
634
- alignItems: 'center',
635
- paddingHorizontal: 14,
636
- paddingVertical: 12,
637
- gap: 10,
638
- },
639
- actionText: {
640
- fontSize: 13,
641
- fontWeight: '500',
642
- },
643
- });
644
-
645
- export default AccountMenu;