@oxyhq/services 12.1.1 → 13.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 (150) hide show
  1. package/lib/commonjs/index.js +73 -6
  2. package/lib/commonjs/index.js.map +1 -1
  3. package/lib/commonjs/ui/components/AccountMenuButton.js +26 -9
  4. package/lib/commonjs/ui/components/AccountMenuButton.js.map +1 -1
  5. package/lib/commonjs/ui/components/AccountSwitcher.js +792 -0
  6. package/lib/commonjs/ui/components/AccountSwitcher.js.map +1 -0
  7. package/lib/commonjs/ui/components/ActiveAccountBanner.js +144 -0
  8. package/lib/commonjs/ui/components/ActiveAccountBanner.js.map +1 -0
  9. package/lib/commonjs/ui/components/SignInModal.js +9 -2
  10. package/lib/commonjs/ui/components/SignInModal.js.map +1 -1
  11. package/lib/commonjs/ui/context/OxyContext.js +72 -27
  12. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  13. package/lib/commonjs/ui/context/hooks/useAuthOperations.js +8 -0
  14. package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +1 -1
  15. package/lib/commonjs/ui/index.js +42 -0
  16. package/lib/commonjs/ui/index.js.map +1 -1
  17. package/lib/commonjs/ui/navigation/routes.js +4 -1
  18. package/lib/commonjs/ui/navigation/routes.js.map +1 -1
  19. package/lib/commonjs/ui/screens/AccountMembersScreen.js +469 -0
  20. package/lib/commonjs/ui/screens/AccountMembersScreen.js.map +1 -0
  21. package/lib/commonjs/ui/screens/AccountSettingsScreen.js +264 -0
  22. package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -0
  23. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +51 -0
  24. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +1 -0
  25. package/lib/commonjs/ui/screens/{CreateManagedAccountScreen.js → CreateAccountScreen.js} +116 -25
  26. package/lib/commonjs/ui/screens/CreateAccountScreen.js.map +1 -0
  27. package/lib/commonjs/ui/screens/ManageAccountScreen.js +11 -11
  28. package/lib/commonjs/ui/screens/ManageAccountScreen.js.map +1 -1
  29. package/lib/commonjs/ui/screens/OxyAuthScreen.js +10 -2
  30. package/lib/commonjs/ui/screens/OxyAuthScreen.js.map +1 -1
  31. package/lib/commonjs/utils/crossApex.js +74 -0
  32. package/lib/commonjs/utils/crossApex.js.map +1 -0
  33. package/lib/module/index.js +21 -4
  34. package/lib/module/index.js.map +1 -1
  35. package/lib/module/ui/components/AccountMenuButton.js +26 -9
  36. package/lib/module/ui/components/AccountMenuButton.js.map +1 -1
  37. package/lib/module/ui/components/AccountSwitcher.js +786 -0
  38. package/lib/module/ui/components/AccountSwitcher.js.map +1 -0
  39. package/lib/module/ui/components/ActiveAccountBanner.js +141 -0
  40. package/lib/module/ui/components/ActiveAccountBanner.js.map +1 -0
  41. package/lib/module/ui/components/SignInModal.js +9 -2
  42. package/lib/module/ui/components/SignInModal.js.map +1 -1
  43. package/lib/module/ui/context/OxyContext.js +72 -27
  44. package/lib/module/ui/context/OxyContext.js.map +1 -1
  45. package/lib/module/ui/context/hooks/useAuthOperations.js +8 -0
  46. package/lib/module/ui/context/hooks/useAuthOperations.js.map +1 -1
  47. package/lib/module/ui/index.js +5 -0
  48. package/lib/module/ui/index.js.map +1 -1
  49. package/lib/module/ui/navigation/routes.js +4 -1
  50. package/lib/module/ui/navigation/routes.js.map +1 -1
  51. package/lib/module/ui/screens/AccountMembersScreen.js +464 -0
  52. package/lib/module/ui/screens/AccountMembersScreen.js.map +1 -0
  53. package/lib/module/ui/screens/AccountSettingsScreen.js +259 -0
  54. package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -0
  55. package/lib/module/ui/screens/AccountSwitcherScreen.js +48 -0
  56. package/lib/module/ui/screens/AccountSwitcherScreen.js.map +1 -0
  57. package/lib/module/ui/screens/{CreateManagedAccountScreen.js → CreateAccountScreen.js} +117 -26
  58. package/lib/module/ui/screens/CreateAccountScreen.js.map +1 -0
  59. package/lib/module/ui/screens/ManageAccountScreen.js +11 -11
  60. package/lib/module/ui/screens/ManageAccountScreen.js.map +1 -1
  61. package/lib/module/ui/screens/OxyAuthScreen.js +10 -2
  62. package/lib/module/ui/screens/OxyAuthScreen.js.map +1 -1
  63. package/lib/module/utils/crossApex.js +69 -0
  64. package/lib/module/utils/crossApex.js.map +1 -0
  65. package/lib/typescript/commonjs/index.d.ts +8 -1
  66. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  67. package/lib/typescript/commonjs/ui/components/AccountMenuButton.d.ts +13 -4
  68. package/lib/typescript/commonjs/ui/components/AccountMenuButton.d.ts.map +1 -1
  69. package/lib/typescript/commonjs/ui/components/AccountSwitcher.d.ts +47 -0
  70. package/lib/typescript/commonjs/ui/components/AccountSwitcher.d.ts.map +1 -0
  71. package/lib/typescript/commonjs/ui/components/{ActingAsBanner.d.ts → ActiveAccountBanner.d.ts} +1 -1
  72. package/lib/typescript/commonjs/ui/components/ActiveAccountBanner.d.ts.map +1 -0
  73. package/lib/typescript/commonjs/ui/components/SignInModal.d.ts.map +1 -1
  74. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts +33 -5
  75. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
  76. package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
  77. package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  78. package/lib/typescript/commonjs/ui/index.d.ts +5 -0
  79. package/lib/typescript/commonjs/ui/index.d.ts.map +1 -1
  80. package/lib/typescript/commonjs/ui/navigation/routes.d.ts +1 -1
  81. package/lib/typescript/commonjs/ui/navigation/routes.d.ts.map +1 -1
  82. package/lib/typescript/commonjs/ui/screens/AccountMembersScreen.d.ts +10 -0
  83. package/lib/typescript/commonjs/ui/screens/AccountMembersScreen.d.ts.map +1 -0
  84. package/lib/typescript/commonjs/ui/screens/AccountSettingsScreen.d.ts +12 -0
  85. package/lib/typescript/commonjs/ui/screens/AccountSettingsScreen.d.ts.map +1 -0
  86. package/lib/typescript/commonjs/ui/screens/AccountSwitcherScreen.d.ts +11 -0
  87. package/lib/typescript/commonjs/ui/screens/AccountSwitcherScreen.d.ts.map +1 -0
  88. package/lib/typescript/commonjs/ui/screens/CreateAccountScreen.d.ts +11 -0
  89. package/lib/typescript/commonjs/ui/screens/CreateAccountScreen.d.ts.map +1 -0
  90. package/lib/typescript/commonjs/ui/screens/OxyAuthScreen.d.ts.map +1 -1
  91. package/lib/typescript/commonjs/utils/crossApex.d.ts +55 -0
  92. package/lib/typescript/commonjs/utils/crossApex.d.ts.map +1 -0
  93. package/lib/typescript/module/index.d.ts +8 -1
  94. package/lib/typescript/module/index.d.ts.map +1 -1
  95. package/lib/typescript/module/ui/components/AccountMenuButton.d.ts +13 -4
  96. package/lib/typescript/module/ui/components/AccountMenuButton.d.ts.map +1 -1
  97. package/lib/typescript/module/ui/components/AccountSwitcher.d.ts +47 -0
  98. package/lib/typescript/module/ui/components/AccountSwitcher.d.ts.map +1 -0
  99. package/lib/typescript/module/ui/components/{ActingAsBanner.d.ts → ActiveAccountBanner.d.ts} +1 -1
  100. package/lib/typescript/module/ui/components/ActiveAccountBanner.d.ts.map +1 -0
  101. package/lib/typescript/module/ui/components/SignInModal.d.ts.map +1 -1
  102. package/lib/typescript/module/ui/context/OxyContext.d.ts +33 -5
  103. package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
  104. package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
  105. package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  106. package/lib/typescript/module/ui/index.d.ts +5 -0
  107. package/lib/typescript/module/ui/index.d.ts.map +1 -1
  108. package/lib/typescript/module/ui/navigation/routes.d.ts +1 -1
  109. package/lib/typescript/module/ui/navigation/routes.d.ts.map +1 -1
  110. package/lib/typescript/module/ui/screens/AccountMembersScreen.d.ts +10 -0
  111. package/lib/typescript/module/ui/screens/AccountMembersScreen.d.ts.map +1 -0
  112. package/lib/typescript/module/ui/screens/AccountSettingsScreen.d.ts +12 -0
  113. package/lib/typescript/module/ui/screens/AccountSettingsScreen.d.ts.map +1 -0
  114. package/lib/typescript/module/ui/screens/AccountSwitcherScreen.d.ts +11 -0
  115. package/lib/typescript/module/ui/screens/AccountSwitcherScreen.d.ts.map +1 -0
  116. package/lib/typescript/module/ui/screens/CreateAccountScreen.d.ts +11 -0
  117. package/lib/typescript/module/ui/screens/CreateAccountScreen.d.ts.map +1 -0
  118. package/lib/typescript/module/ui/screens/OxyAuthScreen.d.ts.map +1 -1
  119. package/lib/typescript/module/utils/crossApex.d.ts +55 -0
  120. package/lib/typescript/module/utils/crossApex.d.ts.map +1 -0
  121. package/package.json +3 -3
  122. package/src/index.ts +22 -4
  123. package/src/ui/components/AccountMenuButton.tsx +25 -10
  124. package/src/ui/components/AccountSwitcher.tsx +763 -0
  125. package/src/ui/components/ActiveAccountBanner.tsx +130 -0
  126. package/src/ui/components/SignInModal.tsx +18 -6
  127. package/src/ui/context/OxyContext.tsx +121 -35
  128. package/src/ui/context/hooks/useAuthOperations.ts +8 -0
  129. package/src/ui/index.ts +5 -0
  130. package/src/ui/navigation/routes.ts +8 -2
  131. package/src/ui/screens/AccountMembersScreen.tsx +481 -0
  132. package/src/ui/screens/AccountSettingsScreen.tsx +261 -0
  133. package/src/ui/screens/AccountSwitcherScreen.tsx +43 -0
  134. package/src/ui/screens/{CreateManagedAccountScreen.tsx → CreateAccountScreen.tsx} +140 -22
  135. package/src/ui/screens/ManageAccountScreen.tsx +19 -19
  136. package/src/ui/screens/OxyAuthScreen.tsx +20 -6
  137. package/src/utils/crossApex.ts +75 -0
  138. package/lib/commonjs/ui/components/ActingAsBanner.js +0 -140
  139. package/lib/commonjs/ui/components/ActingAsBanner.js.map +0 -1
  140. package/lib/commonjs/ui/screens/CreateManagedAccountScreen.js.map +0 -1
  141. package/lib/module/ui/components/ActingAsBanner.js +0 -137
  142. package/lib/module/ui/components/ActingAsBanner.js.map +0 -1
  143. package/lib/module/ui/screens/CreateManagedAccountScreen.js.map +0 -1
  144. package/lib/typescript/commonjs/ui/components/ActingAsBanner.d.ts.map +0 -1
  145. package/lib/typescript/commonjs/ui/screens/CreateManagedAccountScreen.d.ts +0 -5
  146. package/lib/typescript/commonjs/ui/screens/CreateManagedAccountScreen.d.ts.map +0 -1
  147. package/lib/typescript/module/ui/components/ActingAsBanner.d.ts.map +0 -1
  148. package/lib/typescript/module/ui/screens/CreateManagedAccountScreen.d.ts +0 -5
  149. package/lib/typescript/module/ui/screens/CreateManagedAccountScreen.d.ts.map +0 -1
  150. package/src/ui/components/ActingAsBanner.tsx +0 -129
@@ -0,0 +1,786 @@
1
+ "use strict";
2
+
3
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
4
+ import { View, Text, TextInput, TouchableOpacity, StyleSheet, ScrollView, Modal, Pressable, Platform, ActivityIndicator } from 'react-native';
5
+ import { Ionicons } from '@expo/vector-icons';
6
+ import { toast, useDialogControl, Dialog } from '@oxyhq/bloom';
7
+ import { Divider } from '@oxyhq/bloom/divider';
8
+ import { useTheme } from '@oxyhq/bloom/theme';
9
+ import { getAccountDisplayName, isDev, logger as loggerUtil } from '@oxyhq/core';
10
+ import Avatar from "./Avatar.js";
11
+ import { useOxy } from "../context/OxyContext.js";
12
+ import { useI18n } from "../hooks/useI18n.js";
13
+ import { buildAccountRows } from "./accountMenuRows.js";
14
+ import { useDeviceAccounts } from "../hooks/useDeviceAccounts.js";
15
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
16
+ const isWeb = Platform.OS === 'web';
17
+ const PANEL_WIDTH = 380;
18
+
19
+ /**
20
+ * The set of action callbacks the switcher body needs. Supplied by whichever
21
+ * chrome wraps it — the {@link AccountSwitcher} modal/popover (web header chip,
22
+ * native sheet) or the {@link AccountSwitcherScreen} bottom-sheet route.
23
+ */
24
+
25
+ /**
26
+ * Group flat account nodes into a 2-level tree (org → direct children). A node
27
+ * whose parent is not in the set is treated as a root; any deeper node not
28
+ * captured under a root is promoted to its own root row so nothing is hidden.
29
+ */
30
+ function toTree(nodes) {
31
+ const byId = new Map(nodes.map(node => [node.accountId, node]));
32
+ const childrenOf = new Map();
33
+ for (const node of nodes) {
34
+ if (node.parentAccountId && byId.has(node.parentAccountId)) {
35
+ const arr = childrenOf.get(node.parentAccountId) ?? [];
36
+ arr.push(node);
37
+ childrenOf.set(node.parentAccountId, arr);
38
+ }
39
+ }
40
+ const rendered = new Set();
41
+ const result = [];
42
+ for (const node of nodes) {
43
+ const isRoot = !node.parentAccountId || !byId.has(node.parentAccountId);
44
+ if (!isRoot) continue;
45
+ const children = childrenOf.get(node.accountId) ?? [];
46
+ result.push({
47
+ root: node,
48
+ children
49
+ });
50
+ rendered.add(node.accountId);
51
+ for (const child of children) rendered.add(child.accountId);
52
+ }
53
+ for (const node of nodes) {
54
+ if (!rendered.has(node.accountId)) {
55
+ result.push({
56
+ root: node,
57
+ children: []
58
+ });
59
+ rendered.add(node.accountId);
60
+ }
61
+ }
62
+ return result;
63
+ }
64
+
65
+ /**
66
+ * The presentational, chrome-agnostic body of the unified account switcher.
67
+ *
68
+ * Two levels, relationship-aware:
69
+ * - Section A — accounts signed in on THIS device (independent sign-ins). Tap
70
+ * to switch session; per-row sign-out; "Add another account".
71
+ * - Section B — the account GRAPH under the active sign-in: "Your accounts"
72
+ * (self / owned) and "Shared with you" (member). 2-level tree (org →
73
+ * children), role badge, search, one-tap to act-as.
74
+ *
75
+ * Reads everything from `useOxy()` / `useDeviceAccounts()`.
76
+ */
77
+ export const AccountSwitcherView = ({
78
+ onClose,
79
+ onAddAccount,
80
+ onNavigateManage,
81
+ onCreateAccount,
82
+ onOpenAccountSettings,
83
+ onBeforeSessionChange
84
+ }) => {
85
+ const {
86
+ activeSessionId,
87
+ switchSession,
88
+ removeSession,
89
+ logout,
90
+ logoutAll,
91
+ accounts,
92
+ actingAs,
93
+ setActingAs,
94
+ oxyServices
95
+ } = useOxy();
96
+ const {
97
+ t,
98
+ locale
99
+ } = useI18n();
100
+ const {
101
+ colors
102
+ } = useTheme();
103
+ const {
104
+ accounts: deviceAccounts
105
+ } = useDeviceAccounts();
106
+ const [busySessionId, setBusySessionId] = useState(null);
107
+ const [removingSessionId, setRemovingSessionId] = useState(null);
108
+ const [signingOut, setSigningOut] = useState(false);
109
+ const [signingOutAll, setSigningOutAll] = useState(false);
110
+ const [query, setQuery] = useState('');
111
+ const signOutDialog = useDialogControl();
112
+ const signOutAllDialog = useDialogControl();
113
+ const deviceRows = useMemo(() => {
114
+ const built = buildAccountRows({
115
+ accounts: deviceAccounts
116
+ });
117
+ const current = built.filter(row => row.isActive);
118
+ const others = built.filter(row => !row.isActive);
119
+ return [...current, ...others];
120
+ }, [deviceAccounts]);
121
+ const isSwitching = busySessionId !== null;
122
+ const actionDisabled = isSwitching || signingOut || signingOutAll;
123
+
124
+ // --- Account graph (Section B) ---
125
+ const filtered = useMemo(() => {
126
+ const q = query.trim().toLowerCase();
127
+ if (!q) return accounts;
128
+ return accounts.filter(node => {
129
+ const name = getAccountDisplayName(node.account, locale).toLowerCase();
130
+ const username = (node.account?.username ?? '').toLowerCase();
131
+ return name.includes(q) || username.includes(q);
132
+ });
133
+ }, [accounts, query, locale]);
134
+ const yourAccounts = useMemo(() => toTree(filtered.filter(node => node.relationship !== 'member')), [filtered]);
135
+ const sharedAccounts = useMemo(() => toTree(filtered.filter(node => node.relationship === 'member')), [filtered]);
136
+ const handleSwitchDevice = useCallback(async sessionId => {
137
+ if (busySessionId) return;
138
+ // Tapping the already-active sign-in returns to that sign-in's own
139
+ // personal account (clearing any account-graph switch). When already on
140
+ // the personal account this just closes.
141
+ if (sessionId === activeSessionId) {
142
+ if (actingAs !== null) setActingAs(null);
143
+ onClose();
144
+ return;
145
+ }
146
+ setBusySessionId(sessionId);
147
+ try {
148
+ await onBeforeSessionChange?.();
149
+ await switchSession(sessionId);
150
+ // The active sign-in changed; any account-graph switch belonged to the
151
+ // previous sign-in, so the new sign-in starts on its personal account.
152
+ if (actingAs !== null) setActingAs(null);
153
+ toast.success(t('accountSwitcher.toasts.switchSuccess') || 'Switched account');
154
+ onClose();
155
+ } catch (error) {
156
+ if (!isDev()) {
157
+ loggerUtil.warn('Switch account failed', {
158
+ component: 'AccountSwitcher'
159
+ }, error);
160
+ }
161
+ toast.error(t('accountSwitcher.toasts.switchFailed') || 'Failed to switch account');
162
+ } finally {
163
+ setBusySessionId(null);
164
+ }
165
+ }, [activeSessionId, busySessionId, actingAs, setActingAs, switchSession, t, onClose, onBeforeSessionChange]);
166
+ const handleRemoveDevice = useCallback(async sessionId => {
167
+ if (sessionId === activeSessionId || removingSessionId) return;
168
+ setRemovingSessionId(sessionId);
169
+ try {
170
+ await removeSession(sessionId);
171
+ toast.success(t('common.actions.signedOut') || 'Signed out');
172
+ } catch (error) {
173
+ loggerUtil.warn('Remove account failed', {
174
+ component: 'AccountSwitcher'
175
+ }, error);
176
+ toast.error(t('common.errors.signOutFailed') || 'Failed to sign out');
177
+ } finally {
178
+ setRemovingSessionId(null);
179
+ }
180
+ }, [activeSessionId, removingSessionId, removeSession, t]);
181
+ const performSignOut = useCallback(async () => {
182
+ if (signingOut) return;
183
+ setSigningOut(true);
184
+ try {
185
+ await onBeforeSessionChange?.();
186
+ await logout();
187
+ toast.success(t('common.actions.signedOut') || 'Signed out');
188
+ onClose();
189
+ } catch (error) {
190
+ loggerUtil.warn('Sign out failed', {
191
+ component: 'AccountSwitcher'
192
+ }, error);
193
+ toast.error(t('common.errors.signOutFailed') || 'Failed to sign out');
194
+ } finally {
195
+ setSigningOut(false);
196
+ }
197
+ }, [signingOut, logout, t, onClose, onBeforeSessionChange]);
198
+ const performSignOutAll = useCallback(async () => {
199
+ if (signingOutAll) return;
200
+ setSigningOutAll(true);
201
+ try {
202
+ await onBeforeSessionChange?.();
203
+ await logoutAll();
204
+ toast.success(t('accountSwitcher.toasts.signOutAllSuccess') || 'Signed out of all accounts');
205
+ onClose();
206
+ } catch (error) {
207
+ loggerUtil.warn('Sign out all failed', {
208
+ component: 'AccountSwitcher'
209
+ }, error);
210
+ toast.error(t('common.errors.signOutAllFailed') || 'Failed to sign out of all accounts');
211
+ } finally {
212
+ setSigningOutAll(false);
213
+ }
214
+ }, [signingOutAll, logoutAll, t, onClose, onBeforeSessionChange]);
215
+ const handleSelectAccount = useCallback(node => {
216
+ // The personal (self) account = no delegation (acting-as null).
217
+ setActingAs(node.relationship === 'self' ? null : node.accountId);
218
+ onClose();
219
+ }, [setActingAs, onClose]);
220
+ const renderAccountNode = useCallback((node, isChild) => {
221
+ const displayName = getAccountDisplayName(node.account, locale);
222
+ const username = node.account?.username ? `@${node.account.username}` : null;
223
+ const active = node.relationship === 'self' ? actingAs === null : actingAs === node.accountId;
224
+ const role = node.callerMembership?.role;
225
+ const avatarUri = node.account?.avatar ? oxyServices.getFileDownloadUrl(node.account.avatar, 'thumb') : undefined;
226
+ const permissions = node.callerMembership?.permissions ?? [];
227
+ const canManage = node.relationship !== 'member' || permissions.includes('account:update') || permissions.includes('members:read');
228
+ const showSettings = node.relationship !== 'self' && !!onOpenAccountSettings && canManage;
229
+ return /*#__PURE__*/_jsxs(TouchableOpacity, {
230
+ accessibilityRole: "menuitem",
231
+ accessibilityLabel: displayName,
232
+ accessibilityState: {
233
+ selected: active
234
+ },
235
+ onPress: () => handleSelectAccount(node),
236
+ activeOpacity: 0.6,
237
+ style: [styles.accountRow, isChild && styles.childRow, active && {
238
+ backgroundColor: colors.primarySubtle
239
+ }],
240
+ children: [/*#__PURE__*/_jsx(Avatar, {
241
+ uri: avatarUri,
242
+ name: displayName,
243
+ size: isChild ? 28 : 34
244
+ }), /*#__PURE__*/_jsxs(View, {
245
+ style: styles.accountInfo,
246
+ children: [/*#__PURE__*/_jsxs(View, {
247
+ style: styles.nameRow,
248
+ children: [/*#__PURE__*/_jsx(Text, {
249
+ style: [styles.accountName, {
250
+ color: colors.text
251
+ }],
252
+ numberOfLines: 1,
253
+ children: displayName
254
+ }), role ? /*#__PURE__*/_jsx(View, {
255
+ style: [styles.roleBadge, {
256
+ backgroundColor: colors.card
257
+ }],
258
+ children: /*#__PURE__*/_jsx(Text, {
259
+ style: [styles.roleBadgeText, {
260
+ color: colors.textSecondary
261
+ }],
262
+ children: t(`accounts.roles.${role}.label`) || role
263
+ })
264
+ }) : null]
265
+ }), username ? /*#__PURE__*/_jsx(Text, {
266
+ style: [styles.accountEmail, {
267
+ color: colors.textSecondary
268
+ }],
269
+ numberOfLines: 1,
270
+ children: username
271
+ }) : null]
272
+ }), showSettings ? /*#__PURE__*/_jsx(TouchableOpacity, {
273
+ accessibilityRole: "button",
274
+ accessibilityLabel: t('accounts.settings.title') || 'Account settings',
275
+ onPress: () => onOpenAccountSettings?.(node.accountId),
276
+ hitSlop: {
277
+ top: 8,
278
+ bottom: 8,
279
+ left: 8,
280
+ right: 8
281
+ },
282
+ style: styles.settingsButton,
283
+ children: /*#__PURE__*/_jsx(Ionicons, {
284
+ name: "settings-outline",
285
+ size: 16,
286
+ color: colors.textSecondary
287
+ })
288
+ }) : null, active ? /*#__PURE__*/_jsx(Ionicons, {
289
+ name: "checkmark",
290
+ size: 20,
291
+ color: colors.primary
292
+ }) : null]
293
+ }, `node-${node.accountId}`);
294
+ }, [actingAs, colors, handleSelectAccount, locale, onOpenAccountSettings, oxyServices, t]);
295
+ const renderTree = useCallback(entries => entries.map(entry => /*#__PURE__*/_jsxs(View, {
296
+ children: [renderAccountNode(entry.root, false), entry.children.map(child => renderAccountNode(child, true))]
297
+ }, `tree-${entry.root.accountId}`)), [renderAccountNode]);
298
+ return /*#__PURE__*/_jsxs(ScrollView, {
299
+ style: styles.scroll,
300
+ contentContainerStyle: styles.scrollContent,
301
+ showsVerticalScrollIndicator: false,
302
+ keyboardShouldPersistTaps: "handled",
303
+ children: [/*#__PURE__*/_jsx(Text, {
304
+ style: [styles.sectionLabel, {
305
+ color: colors.textSecondary
306
+ }],
307
+ children: t('accountSwitcher.sections.thisDevice') || 'On this device'
308
+ }), deviceRows.map(row => {
309
+ const isBusy = busySessionId === row.sessionId;
310
+ const isRemoving = removingSessionId === row.sessionId;
311
+ // The active sign-in is THE current account only when no
312
+ // account-graph switch is in effect; otherwise a switched-into
313
+ // account is current and this row reads as the signed-in (but not
314
+ // active) account — tappable to return to it.
315
+ const isCurrentAccount = row.isActive && actingAs === null;
316
+ return /*#__PURE__*/_jsxs(TouchableOpacity, {
317
+ accessibilityRole: "menuitem",
318
+ accessibilityLabel: row.displayName,
319
+ accessibilityState: {
320
+ selected: isCurrentAccount
321
+ },
322
+ onPress: () => handleSwitchDevice(row.sessionId),
323
+ disabled: isCurrentAccount || isBusy || isSwitching,
324
+ activeOpacity: 0.6,
325
+ style: [styles.accountRow, isCurrentAccount && {
326
+ backgroundColor: colors.primarySubtle
327
+ }, isSwitching && !row.isActive && styles.rowDisabled],
328
+ children: [/*#__PURE__*/_jsx(Avatar, {
329
+ uri: row.avatarUri,
330
+ name: row.displayName,
331
+ size: row.isActive ? 40 : 32
332
+ }), /*#__PURE__*/_jsxs(View, {
333
+ style: styles.accountInfo,
334
+ children: [/*#__PURE__*/_jsx(Text, {
335
+ style: [styles.accountName, {
336
+ color: colors.text
337
+ }, row.isActive && styles.accountNameActive],
338
+ numberOfLines: 1,
339
+ children: row.displayName
340
+ }), row.secondary ? /*#__PURE__*/_jsx(Text, {
341
+ style: [styles.accountEmail, {
342
+ color: colors.textSecondary
343
+ }],
344
+ numberOfLines: 1,
345
+ children: row.secondary
346
+ }) : null]
347
+ }), isBusy ? /*#__PURE__*/_jsx(ActivityIndicator, {
348
+ color: colors.primary,
349
+ size: "small"
350
+ }) : isCurrentAccount ? /*#__PURE__*/_jsx(Ionicons, {
351
+ name: "checkmark",
352
+ size: 20,
353
+ color: colors.primary
354
+ }) : row.isActive ?
355
+ // Active sign-in while switched into another account:
356
+ // no current-checkmark, no sign-out (can't sign out the
357
+ // active session here) — tap the row to return to it.
358
+ null : isRemoving ? /*#__PURE__*/_jsx(ActivityIndicator, {
359
+ color: colors.textSecondary,
360
+ size: "small"
361
+ }) : /*#__PURE__*/_jsx(TouchableOpacity, {
362
+ accessibilityRole: "button",
363
+ accessibilityLabel: t('accountMenu.signOutAccount', {
364
+ name: row.displayName
365
+ }) || `Sign out ${row.displayName}`,
366
+ onPress: () => handleRemoveDevice(row.sessionId),
367
+ disabled: isSwitching || removingSessionId !== null,
368
+ hitSlop: {
369
+ top: 8,
370
+ bottom: 8,
371
+ left: 8,
372
+ right: 8
373
+ },
374
+ style: styles.settingsButton,
375
+ children: /*#__PURE__*/_jsx(Ionicons, {
376
+ name: "log-out-outline",
377
+ size: 18,
378
+ color: colors.textSecondary
379
+ })
380
+ })]
381
+ }, `device-${row.sessionId}`);
382
+ }), isSwitching ? /*#__PURE__*/_jsxs(View, {
383
+ style: styles.switchingRow,
384
+ children: [/*#__PURE__*/_jsx(ActivityIndicator, {
385
+ color: colors.textSecondary,
386
+ size: "small"
387
+ }), /*#__PURE__*/_jsx(Text, {
388
+ style: [styles.switchingText, {
389
+ color: colors.textSecondary
390
+ }],
391
+ children: t('accountMenu.switching') || 'Switching account…'
392
+ })]
393
+ }) : null, /*#__PURE__*/_jsxs(TouchableOpacity, {
394
+ accessibilityRole: "menuitem",
395
+ accessibilityLabel: t('accountMenu.addAnother') || 'Add another account',
396
+ onPress: () => {
397
+ onClose();
398
+ onAddAccount();
399
+ },
400
+ disabled: actionDisabled,
401
+ activeOpacity: 0.6,
402
+ style: [styles.actionRow, actionDisabled && styles.rowDisabled],
403
+ children: [/*#__PURE__*/_jsx(Ionicons, {
404
+ name: "person-add-outline",
405
+ size: 20,
406
+ color: colors.icon
407
+ }), /*#__PURE__*/_jsx(Text, {
408
+ style: [styles.actionText, {
409
+ color: colors.text
410
+ }],
411
+ children: t('accountMenu.addAnother') || 'Add another account'
412
+ })]
413
+ }), accounts.length > 0 ? /*#__PURE__*/_jsxs(_Fragment, {
414
+ children: [/*#__PURE__*/_jsx(Divider, {
415
+ color: colors.border,
416
+ spacing: 4
417
+ }), accounts.length > 6 ? /*#__PURE__*/_jsxs(View, {
418
+ style: [styles.searchBox, {
419
+ backgroundColor: colors.card,
420
+ borderColor: colors.border
421
+ }],
422
+ children: [/*#__PURE__*/_jsx(Ionicons, {
423
+ name: "search",
424
+ size: 16,
425
+ color: colors.textSecondary
426
+ }), /*#__PURE__*/_jsx(TextInput, {
427
+ value: query,
428
+ onChangeText: setQuery,
429
+ placeholder: t('accountSwitcher.searchPlaceholder') || 'Search accounts',
430
+ placeholderTextColor: colors.textSecondary,
431
+ style: [styles.searchInput, {
432
+ color: colors.text
433
+ }],
434
+ autoCapitalize: "none",
435
+ autoCorrect: false
436
+ })]
437
+ }) : null, yourAccounts.length > 0 ? /*#__PURE__*/_jsxs(_Fragment, {
438
+ children: [/*#__PURE__*/_jsx(Text, {
439
+ style: [styles.sectionLabel, {
440
+ color: colors.textSecondary
441
+ }],
442
+ children: t('accountSwitcher.sections.yourAccounts') || 'Your accounts'
443
+ }), renderTree(yourAccounts)]
444
+ }) : null, sharedAccounts.length > 0 ? /*#__PURE__*/_jsxs(_Fragment, {
445
+ children: [/*#__PURE__*/_jsx(Text, {
446
+ style: [styles.sectionLabel, {
447
+ color: colors.textSecondary
448
+ }],
449
+ children: t('accountSwitcher.sections.sharedWithYou') || 'Shared with you'
450
+ }), renderTree(sharedAccounts)]
451
+ }) : null]
452
+ }) : null, /*#__PURE__*/_jsx(Divider, {
453
+ color: colors.border,
454
+ spacing: 4
455
+ }), /*#__PURE__*/_jsxs(TouchableOpacity, {
456
+ accessibilityRole: "menuitem",
457
+ accessibilityLabel: t('accounts.create.title') || 'Create account',
458
+ onPress: () => {
459
+ onClose();
460
+ onCreateAccount();
461
+ },
462
+ disabled: actionDisabled,
463
+ activeOpacity: 0.6,
464
+ style: [styles.actionRow, actionDisabled && styles.rowDisabled],
465
+ children: [/*#__PURE__*/_jsx(Ionicons, {
466
+ name: "add-circle-outline",
467
+ size: 20,
468
+ color: colors.icon
469
+ }), /*#__PURE__*/_jsx(Text, {
470
+ style: [styles.actionText, {
471
+ color: colors.text
472
+ }],
473
+ children: t('accounts.create.title') || 'Create account'
474
+ })]
475
+ }), /*#__PURE__*/_jsxs(TouchableOpacity, {
476
+ accessibilityRole: "menuitem",
477
+ accessibilityLabel: t('accountMenu.manage') || 'Manage your Oxy Account',
478
+ onPress: () => {
479
+ onClose();
480
+ onNavigateManage();
481
+ },
482
+ disabled: actionDisabled,
483
+ activeOpacity: 0.6,
484
+ style: [styles.actionRow, actionDisabled && styles.rowDisabled],
485
+ children: [/*#__PURE__*/_jsx(Ionicons, {
486
+ name: "settings-outline",
487
+ size: 18,
488
+ color: colors.icon
489
+ }), /*#__PURE__*/_jsx(Text, {
490
+ style: [styles.actionText, {
491
+ color: colors.text
492
+ }],
493
+ children: t('accountMenu.manage') || 'Manage your Oxy Account'
494
+ })]
495
+ }), /*#__PURE__*/_jsxs(TouchableOpacity, {
496
+ accessibilityRole: "menuitem",
497
+ accessibilityLabel: t('common.actions.signOut') || 'Sign out',
498
+ onPress: () => signOutDialog.open(),
499
+ disabled: actionDisabled,
500
+ activeOpacity: 0.6,
501
+ style: [styles.actionRow, actionDisabled && styles.rowDisabled],
502
+ children: [signingOut ? /*#__PURE__*/_jsx(ActivityIndicator, {
503
+ color: colors.error,
504
+ size: "small"
505
+ }) : /*#__PURE__*/_jsx(Ionicons, {
506
+ name: "log-out-outline",
507
+ size: 18,
508
+ color: colors.error
509
+ }), /*#__PURE__*/_jsx(Text, {
510
+ style: [styles.actionText, {
511
+ color: colors.error
512
+ }],
513
+ children: t('common.actions.signOut') || 'Sign out'
514
+ })]
515
+ }), deviceRows.length > 1 ? /*#__PURE__*/_jsxs(TouchableOpacity, {
516
+ accessibilityRole: "menuitem",
517
+ accessibilityLabel: t('accountMenu.signOutAll') || 'Sign out of all accounts',
518
+ onPress: () => signOutAllDialog.open(),
519
+ disabled: actionDisabled,
520
+ activeOpacity: 0.6,
521
+ style: [styles.actionRow, actionDisabled && styles.rowDisabled],
522
+ children: [signingOutAll ? /*#__PURE__*/_jsx(ActivityIndicator, {
523
+ color: colors.error,
524
+ size: "small"
525
+ }) : /*#__PURE__*/_jsx(Ionicons, {
526
+ name: "log-out-outline",
527
+ size: 18,
528
+ color: colors.error
529
+ }), /*#__PURE__*/_jsx(Text, {
530
+ style: [styles.actionText, {
531
+ color: colors.error
532
+ }],
533
+ children: t('accountMenu.signOutAll') || 'Sign out of all accounts'
534
+ })]
535
+ }) : null, /*#__PURE__*/_jsx(Dialog, {
536
+ control: signOutDialog,
537
+ title: t('common.actions.signOut') || 'Sign out',
538
+ description: t('common.confirms.signOut') || 'Are you sure you want to sign out?',
539
+ actions: [{
540
+ label: t('common.actions.signOut') || 'Sign out',
541
+ color: 'destructive',
542
+ onPress: performSignOut
543
+ }, {
544
+ label: t('common.cancel') || 'Cancel',
545
+ color: 'cancel'
546
+ }]
547
+ }), /*#__PURE__*/_jsx(Dialog, {
548
+ control: signOutAllDialog,
549
+ title: t('accountMenu.signOutAll') || 'Sign out of all accounts',
550
+ description: t('common.confirms.signOutAll') || 'Are you sure you want to sign out of all accounts?',
551
+ actions: [{
552
+ label: t('accountMenu.signOutAll') || 'Sign out of all accounts',
553
+ color: 'destructive',
554
+ onPress: performSignOutAll
555
+ }, {
556
+ label: t('common.cancel') || 'Cancel',
557
+ color: 'cancel'
558
+ }]
559
+ })]
560
+ });
561
+ };
562
+ /**
563
+ * Unified account switcher presented as a popover (web) / bottom-sheet style
564
+ * modal (native). The canonical entry point opened by {@link AccountMenuButton}.
565
+ * Supersedes `AccountMenu` (which remains exported as the device-only switcher).
566
+ */
567
+ const AccountSwitcher = ({
568
+ open,
569
+ anchor,
570
+ ...actions
571
+ }) => {
572
+ const {
573
+ t
574
+ } = useI18n();
575
+ const {
576
+ colors
577
+ } = useTheme();
578
+ const containerRef = useRef(null);
579
+ const {
580
+ onClose
581
+ } = actions;
582
+ useEffect(() => {
583
+ if (!open || !isWeb || typeof document === 'undefined') {
584
+ return undefined;
585
+ }
586
+ const onKey = event => {
587
+ if (event.key === 'Escape') {
588
+ event.stopPropagation();
589
+ onClose();
590
+ }
591
+ };
592
+ document.addEventListener('keydown', onKey, true);
593
+ return () => document.removeEventListener('keydown', onKey, true);
594
+ }, [open, onClose]);
595
+ if (!open) {
596
+ return null;
597
+ }
598
+ const anchorStyle = anchor ? {
599
+ ...(anchor.top !== undefined ? {
600
+ top: anchor.top
601
+ } : null),
602
+ ...(anchor.bottom !== undefined ? {
603
+ bottom: anchor.bottom
604
+ } : null),
605
+ ...(anchor.left !== undefined ? {
606
+ left: anchor.left
607
+ } : null),
608
+ ...(anchor.right !== undefined ? {
609
+ right: anchor.right
610
+ } : null)
611
+ } : {
612
+ top: 64,
613
+ right: 16
614
+ };
615
+ const panelStyles = isWeb ? [styles.panelBase, styles.panelWeb, anchorStyle, {
616
+ backgroundColor: colors.background,
617
+ borderColor: colors.border
618
+ }] : [styles.panelBase, styles.panelNative, {
619
+ backgroundColor: colors.background
620
+ }];
621
+ return /*#__PURE__*/_jsx(Modal, {
622
+ visible: open,
623
+ transparent: true,
624
+ animationType: isWeb ? 'fade' : 'slide',
625
+ onRequestClose: actions.onClose,
626
+ children: /*#__PURE__*/_jsx(Pressable, {
627
+ accessibilityRole: "button",
628
+ accessibilityLabel: t('common.actions.close') || 'Close',
629
+ onPress: actions.onClose,
630
+ style: isWeb ? styles.webOverlay : styles.nativeOverlay,
631
+ children: /*#__PURE__*/_jsx(Pressable, {
632
+ ref: containerRef,
633
+ onPress: () => undefined,
634
+ style: panelStyles,
635
+ accessibilityRole: "menu",
636
+ accessibilityLabel: t('accountSwitcher.label') || 'Account switcher',
637
+ children: /*#__PURE__*/_jsx(AccountSwitcherView, {
638
+ ...actions
639
+ })
640
+ })
641
+ })
642
+ });
643
+ };
644
+ const styles = StyleSheet.create({
645
+ webOverlay: {
646
+ flex: 1,
647
+ backgroundColor: 'transparent',
648
+ position: 'relative'
649
+ },
650
+ nativeOverlay: {
651
+ flex: 1,
652
+ backgroundColor: 'rgba(0,0,0,0.32)',
653
+ justifyContent: 'flex-end'
654
+ },
655
+ panelBase: {
656
+ borderRadius: 12,
657
+ shadowColor: '#000',
658
+ shadowOpacity: 0.18,
659
+ shadowRadius: 24,
660
+ shadowOffset: {
661
+ width: 0,
662
+ height: 8
663
+ },
664
+ elevation: 12,
665
+ overflow: 'hidden'
666
+ },
667
+ panelWeb: {
668
+ position: 'absolute',
669
+ width: PANEL_WIDTH,
670
+ maxHeight: '85%',
671
+ borderWidth: 1
672
+ },
673
+ panelNative: {
674
+ marginHorizontal: 0,
675
+ borderTopLeftRadius: 20,
676
+ borderTopRightRadius: 20,
677
+ paddingBottom: 12,
678
+ maxHeight: '85%'
679
+ },
680
+ scroll: {
681
+ flexGrow: 0
682
+ },
683
+ scrollContent: {
684
+ paddingVertical: 4
685
+ },
686
+ sectionLabel: {
687
+ fontSize: 11,
688
+ fontWeight: '600',
689
+ textTransform: 'uppercase',
690
+ letterSpacing: 0.5,
691
+ paddingHorizontal: 14,
692
+ paddingTop: 12,
693
+ paddingBottom: 4
694
+ },
695
+ accountRow: {
696
+ flexDirection: 'row',
697
+ alignItems: 'center',
698
+ paddingHorizontal: 14,
699
+ paddingVertical: 10,
700
+ gap: 10
701
+ },
702
+ childRow: {
703
+ paddingLeft: 34
704
+ },
705
+ rowDisabled: {
706
+ opacity: 0.4
707
+ },
708
+ accountInfo: {
709
+ flex: 1,
710
+ minWidth: 0
711
+ },
712
+ nameRow: {
713
+ flexDirection: 'row',
714
+ alignItems: 'center',
715
+ gap: 6
716
+ },
717
+ accountName: {
718
+ fontSize: 13,
719
+ fontWeight: '500',
720
+ flexShrink: 1
721
+ },
722
+ accountNameActive: {
723
+ fontWeight: '600'
724
+ },
725
+ accountEmail: {
726
+ fontSize: 11,
727
+ marginTop: 1
728
+ },
729
+ roleBadge: {
730
+ paddingHorizontal: 6,
731
+ paddingVertical: 1,
732
+ borderRadius: 8
733
+ },
734
+ roleBadgeText: {
735
+ fontSize: 10,
736
+ fontWeight: '600',
737
+ textTransform: 'capitalize'
738
+ },
739
+ settingsButton: {
740
+ width: 28,
741
+ height: 28,
742
+ alignItems: 'center',
743
+ justifyContent: 'center',
744
+ borderRadius: 14
745
+ },
746
+ switchingRow: {
747
+ flexDirection: 'row',
748
+ alignItems: 'center',
749
+ justifyContent: 'center',
750
+ paddingVertical: 8,
751
+ gap: 8
752
+ },
753
+ switchingText: {
754
+ fontSize: 12,
755
+ fontWeight: '500'
756
+ },
757
+ searchBox: {
758
+ flexDirection: 'row',
759
+ alignItems: 'center',
760
+ gap: 8,
761
+ marginHorizontal: 14,
762
+ marginVertical: 6,
763
+ paddingHorizontal: 10,
764
+ height: 36,
765
+ borderRadius: 10,
766
+ borderWidth: 1
767
+ },
768
+ searchInput: {
769
+ flex: 1,
770
+ fontSize: 13,
771
+ padding: 0
772
+ },
773
+ actionRow: {
774
+ flexDirection: 'row',
775
+ alignItems: 'center',
776
+ paddingHorizontal: 14,
777
+ paddingVertical: 12,
778
+ gap: 10
779
+ },
780
+ actionText: {
781
+ fontSize: 13,
782
+ fontWeight: '500'
783
+ }
784
+ });
785
+ export default AccountSwitcher;
786
+ //# sourceMappingURL=AccountSwitcher.js.map