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