@oxyhq/services 13.3.0 → 14.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.
- package/lib/commonjs/index.js +11 -17
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/ui/components/AccountMenu.js +103 -65
- package/lib/commonjs/ui/components/AccountMenu.js.map +1 -1
- package/lib/commonjs/ui/components/AccountSwitcher.js +84 -126
- package/lib/commonjs/ui/components/AccountSwitcher.js.map +1 -1
- package/lib/commonjs/ui/components/ProfileMenu.js +140 -153
- package/lib/commonjs/ui/components/ProfileMenu.js.map +1 -1
- package/lib/commonjs/ui/components/SignInModal.js +4 -4
- package/lib/commonjs/ui/components/SignInModal.js.map +1 -1
- package/lib/commonjs/ui/context/OxyContext.js +506 -466
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js +59 -57
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/commonjs/ui/context/inSessionTokenRefresh.js +23 -42
- package/lib/commonjs/ui/context/inSessionTokenRefresh.js.map +1 -1
- package/lib/commonjs/ui/context/silentSessionRestore.js +12 -21
- package/lib/commonjs/ui/context/silentSessionRestore.js.map +1 -1
- package/lib/commonjs/ui/hooks/useAuth.js +4 -3
- package/lib/commonjs/ui/hooks/useAuth.js.map +1 -1
- package/lib/commonjs/ui/hooks/useOxyAuthSession.js +12 -7
- package/lib/commonjs/ui/hooks/useOxyAuthSession.js.map +1 -1
- package/lib/commonjs/ui/hooks/useSessionManagement.js +17 -65
- package/lib/commonjs/ui/hooks/useSessionManagement.js.map +1 -1
- package/lib/commonjs/ui/hooks/useSwitchableAccounts.js +288 -0
- package/lib/commonjs/ui/hooks/useSwitchableAccounts.js.map +1 -0
- package/lib/commonjs/ui/hooks/useWebSSO.js +12 -191
- package/lib/commonjs/ui/hooks/useWebSSO.js.map +1 -1
- package/lib/commonjs/ui/index.js.map +1 -1
- package/lib/commonjs/ui/screens/OxyAuthScreen.js +2 -2
- package/lib/commonjs/ui/screens/OxyAuthScreen.js.map +1 -1
- package/lib/commonjs/ui/session/createSessionClient.js +28 -0
- package/lib/commonjs/ui/session/createSessionClient.js.map +1 -0
- package/lib/commonjs/ui/session/index.js +52 -0
- package/lib/commonjs/ui/session/index.js.map +1 -0
- package/lib/commonjs/ui/session/projectSessionState.js +86 -0
- package/lib/commonjs/ui/session/projectSessionState.js.map +1 -0
- package/lib/commonjs/ui/session/sessionClientHost.js +30 -0
- package/lib/commonjs/ui/session/sessionClientHost.js.map +1 -0
- package/lib/commonjs/ui/session/tokenTransport.js +77 -0
- package/lib/commonjs/ui/session/tokenTransport.js.map +1 -0
- package/lib/commonjs/ui/utils/activeAuthuser.js +35 -79
- package/lib/commonjs/ui/utils/activeAuthuser.js.map +1 -1
- package/lib/commonjs/ui/utils/sessionHelpers.js +1 -51
- package/lib/commonjs/ui/utils/sessionHelpers.js.map +1 -1
- package/lib/commonjs/utils/deviceFlowSignIn.js +70 -15
- package/lib/commonjs/utils/deviceFlowSignIn.js.map +1 -1
- package/lib/module/index.js +5 -5
- package/lib/module/index.js.map +1 -1
- package/lib/module/ui/components/AccountMenu.js +103 -65
- package/lib/module/ui/components/AccountMenu.js.map +1 -1
- package/lib/module/ui/components/AccountSwitcher.js +85 -127
- package/lib/module/ui/components/AccountSwitcher.js.map +1 -1
- package/lib/module/ui/components/ProfileMenu.js +143 -156
- package/lib/module/ui/components/ProfileMenu.js.map +1 -1
- package/lib/module/ui/components/SignInModal.js +4 -4
- package/lib/module/ui/components/SignInModal.js.map +1 -1
- package/lib/module/ui/context/OxyContext.js +510 -470
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/context/hooks/useAuthOperations.js +61 -60
- package/lib/module/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/module/ui/context/inSessionTokenRefresh.js +24 -43
- package/lib/module/ui/context/inSessionTokenRefresh.js.map +1 -1
- package/lib/module/ui/context/silentSessionRestore.js +12 -20
- package/lib/module/ui/context/silentSessionRestore.js.map +1 -1
- package/lib/module/ui/hooks/useAuth.js +4 -3
- package/lib/module/ui/hooks/useAuth.js.map +1 -1
- package/lib/module/ui/hooks/useOxyAuthSession.js +12 -7
- package/lib/module/ui/hooks/useOxyAuthSession.js.map +1 -1
- package/lib/module/ui/hooks/useSessionManagement.js +18 -66
- package/lib/module/ui/hooks/useSessionManagement.js.map +1 -1
- package/lib/module/ui/hooks/useSwitchableAccounts.js +283 -0
- package/lib/module/ui/hooks/useSwitchableAccounts.js.map +1 -0
- package/lib/module/ui/hooks/useWebSSO.js +13 -192
- package/lib/module/ui/hooks/useWebSSO.js.map +1 -1
- package/lib/module/ui/index.js +0 -1
- package/lib/module/ui/index.js.map +1 -1
- package/lib/module/ui/screens/OxyAuthScreen.js +2 -2
- package/lib/module/ui/screens/OxyAuthScreen.js.map +1 -1
- package/lib/module/ui/session/createSessionClient.js +25 -0
- package/lib/module/ui/session/createSessionClient.js.map +1 -0
- package/lib/module/ui/session/index.js +17 -0
- package/lib/module/ui/session/index.js.map +1 -0
- package/lib/module/ui/session/projectSessionState.js +79 -0
- package/lib/module/ui/session/projectSessionState.js.map +1 -0
- package/lib/module/ui/session/sessionClientHost.js +26 -0
- package/lib/module/ui/session/sessionClientHost.js.map +1 -0
- package/lib/module/ui/session/tokenTransport.js +74 -0
- package/lib/module/ui/session/tokenTransport.js.map +1 -0
- package/lib/module/ui/utils/activeAuthuser.js +34 -76
- package/lib/module/ui/utils/activeAuthuser.js.map +1 -1
- package/lib/module/ui/utils/sessionHelpers.js +0 -49
- package/lib/module/ui/utils/sessionHelpers.js.map +1 -1
- package/lib/module/utils/deviceFlowSignIn.js +71 -16
- package/lib/module/utils/deviceFlowSignIn.js.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +2 -3
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/AccountMenu.d.ts +4 -2
- package/lib/typescript/commonjs/ui/components/AccountMenu.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/AccountSwitcher.d.ts +7 -8
- package/lib/typescript/commonjs/ui/components/AccountSwitcher.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/ProfileMenu.d.ts +12 -9
- package/lib/typescript/commonjs/ui/components/ProfileMenu.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts +16 -13
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts +27 -3
- package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/inSessionTokenRefresh.d.ts +22 -17
- package/lib/typescript/commonjs/ui/context/inSessionTokenRefresh.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/silentSessionRestore.d.ts +12 -18
- package/lib/typescript/commonjs/ui/context/silentSessionRestore.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts +1 -0
- package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useAuth.d.ts +1 -1
- package/lib/typescript/commonjs/ui/hooks/useAuth.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useOxyAuthSession.d.ts +2 -2
- package/lib/typescript/commonjs/ui/hooks/useOxyAuthSession.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useSessionManagement.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useSwitchableAccounts.d.ts +162 -0
- package/lib/typescript/commonjs/ui/hooks/useSwitchableAccounts.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/hooks/useWebSSO.d.ts +13 -52
- package/lib/typescript/commonjs/ui/hooks/useWebSSO.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts +15 -0
- package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/session/index.d.ts +15 -0
- package/lib/typescript/commonjs/ui/session/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/session/projectSessionState.d.ts +45 -0
- package/lib/typescript/commonjs/ui/session/projectSessionState.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/session/sessionClientHost.d.ts +14 -0
- package/lib/typescript/commonjs/ui/session/sessionClientHost.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/session/tokenTransport.d.ts +24 -0
- package/lib/typescript/commonjs/ui/session/tokenTransport.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/utils/activeAuthuser.d.ts +21 -29
- package/lib/typescript/commonjs/ui/utils/activeAuthuser.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/utils/sessionHelpers.d.ts +0 -19
- package/lib/typescript/commonjs/ui/utils/sessionHelpers.d.ts.map +1 -1
- package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts +51 -19
- package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +2 -3
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/AccountMenu.d.ts +4 -2
- package/lib/typescript/module/ui/components/AccountMenu.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/AccountSwitcher.d.ts +7 -8
- package/lib/typescript/module/ui/components/AccountSwitcher.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/ProfileMenu.d.ts +12 -9
- package/lib/typescript/module/ui/components/ProfileMenu.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/OxyContext.d.ts +16 -13
- package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts +27 -3
- package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/inSessionTokenRefresh.d.ts +22 -17
- package/lib/typescript/module/ui/context/inSessionTokenRefresh.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/silentSessionRestore.d.ts +12 -18
- package/lib/typescript/module/ui/context/silentSessionRestore.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts +1 -0
- package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useAuth.d.ts +1 -1
- package/lib/typescript/module/ui/hooks/useAuth.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useOxyAuthSession.d.ts +2 -2
- package/lib/typescript/module/ui/hooks/useOxyAuthSession.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useSessionManagement.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useSwitchableAccounts.d.ts +162 -0
- package/lib/typescript/module/ui/hooks/useSwitchableAccounts.d.ts.map +1 -0
- package/lib/typescript/module/ui/hooks/useWebSSO.d.ts +13 -52
- package/lib/typescript/module/ui/hooks/useWebSSO.d.ts.map +1 -1
- package/lib/typescript/module/ui/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/session/createSessionClient.d.ts +15 -0
- package/lib/typescript/module/ui/session/createSessionClient.d.ts.map +1 -0
- package/lib/typescript/module/ui/session/index.d.ts +15 -0
- package/lib/typescript/module/ui/session/index.d.ts.map +1 -0
- package/lib/typescript/module/ui/session/projectSessionState.d.ts +45 -0
- package/lib/typescript/module/ui/session/projectSessionState.d.ts.map +1 -0
- package/lib/typescript/module/ui/session/sessionClientHost.d.ts +14 -0
- package/lib/typescript/module/ui/session/sessionClientHost.d.ts.map +1 -0
- package/lib/typescript/module/ui/session/tokenTransport.d.ts +24 -0
- package/lib/typescript/module/ui/session/tokenTransport.d.ts.map +1 -0
- package/lib/typescript/module/ui/utils/activeAuthuser.d.ts +21 -29
- package/lib/typescript/module/ui/utils/activeAuthuser.d.ts.map +1 -1
- package/lib/typescript/module/ui/utils/sessionHelpers.d.ts +0 -19
- package/lib/typescript/module/ui/utils/sessionHelpers.d.ts.map +1 -1
- package/lib/typescript/module/utils/deviceFlowSignIn.d.ts +51 -19
- package/lib/typescript/module/utils/deviceFlowSignIn.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/index.ts +9 -9
- package/src/ui/components/AccountMenu.tsx +117 -70
- package/src/ui/components/AccountSwitcher.tsx +88 -134
- package/src/ui/components/ProfileMenu.tsx +158 -162
- package/src/ui/components/SignInModal.tsx +5 -5
- package/src/ui/context/OxyContext.tsx +560 -475
- package/src/ui/context/hooks/useAuthOperations.ts +72 -63
- package/src/ui/context/inSessionTokenRefresh.ts +23 -42
- package/src/ui/context/silentSessionRestore.ts +12 -26
- package/src/ui/hooks/useAuth.ts +4 -3
- package/src/ui/hooks/useOxyAuthSession.ts +13 -8
- package/src/ui/hooks/useSessionManagement.ts +16 -87
- package/src/ui/hooks/useSwitchableAccounts.ts +397 -0
- package/src/ui/hooks/useWebSSO.ts +13 -227
- package/src/ui/index.ts +0 -1
- package/src/ui/screens/OxyAuthScreen.tsx +2 -2
- package/src/ui/session/__tests__/createSessionClient.test.ts +41 -0
- package/src/ui/session/__tests__/projectSessionState.test.ts +114 -0
- package/src/ui/session/__tests__/sessionClientHost.test.ts +43 -0
- package/src/ui/session/__tests__/tokenTransport.test.ts +142 -0
- package/src/ui/session/createSessionClient.ts +21 -0
- package/src/ui/session/index.ts +19 -0
- package/src/ui/session/projectSessionState.ts +85 -0
- package/src/ui/session/sessionClientHost.ts +27 -0
- package/src/ui/session/tokenTransport.ts +76 -0
- package/src/ui/utils/activeAuthuser.ts +34 -76
- package/src/ui/utils/sessionHelpers.ts +0 -66
- package/src/utils/__tests__/deviceFlowSignIn.test.ts +243 -34
- package/src/utils/deviceFlowSignIn.ts +98 -21
- package/lib/commonjs/ui/components/accountMenuRows.js +0 -33
- package/lib/commonjs/ui/components/accountMenuRows.js.map +0 -1
- package/lib/commonjs/ui/hooks/useDeviceAccounts.js +0 -280
- package/lib/commonjs/ui/hooks/useDeviceAccounts.js.map +0 -1
- package/lib/commonjs/ui/hooks/useSessionSocket.js +0 -203
- package/lib/commonjs/ui/hooks/useSessionSocket.js.map +0 -1
- package/lib/commonjs/utils/silentGuardKey.js +0 -54
- package/lib/commonjs/utils/silentGuardKey.js.map +0 -1
- package/lib/module/ui/components/accountMenuRows.js +0 -29
- package/lib/module/ui/components/accountMenuRows.js.map +0 -1
- package/lib/module/ui/hooks/useDeviceAccounts.js +0 -276
- package/lib/module/ui/hooks/useDeviceAccounts.js.map +0 -1
- package/lib/module/ui/hooks/useSessionSocket.js +0 -199
- package/lib/module/ui/hooks/useSessionSocket.js.map +0 -1
- package/lib/module/utils/silentGuardKey.js +0 -49
- package/lib/module/utils/silentGuardKey.js.map +0 -1
- package/lib/typescript/commonjs/ui/components/accountMenuRows.d.ts +0 -34
- package/lib/typescript/commonjs/ui/components/accountMenuRows.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/hooks/useDeviceAccounts.d.ts +0 -133
- package/lib/typescript/commonjs/ui/hooks/useDeviceAccounts.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/hooks/useSessionSocket.d.ts +0 -14
- package/lib/typescript/commonjs/ui/hooks/useSessionSocket.d.ts.map +0 -1
- package/lib/typescript/commonjs/utils/silentGuardKey.d.ts +0 -31
- package/lib/typescript/commonjs/utils/silentGuardKey.d.ts.map +0 -1
- package/lib/typescript/module/ui/components/accountMenuRows.d.ts +0 -34
- package/lib/typescript/module/ui/components/accountMenuRows.d.ts.map +0 -1
- package/lib/typescript/module/ui/hooks/useDeviceAccounts.d.ts +0 -133
- package/lib/typescript/module/ui/hooks/useDeviceAccounts.d.ts.map +0 -1
- package/lib/typescript/module/ui/hooks/useSessionSocket.d.ts +0 -14
- package/lib/typescript/module/ui/hooks/useSessionSocket.d.ts.map +0 -1
- package/lib/typescript/module/utils/silentGuardKey.d.ts +0 -31
- package/lib/typescript/module/utils/silentGuardKey.d.ts.map +0 -1
- package/src/ui/components/accountMenuRows.ts +0 -48
- package/src/ui/hooks/useDeviceAccounts.ts +0 -344
- package/src/ui/hooks/useSessionSocket.ts +0 -233
- package/src/ui/utils/__tests__/activeAuthuser.test.ts +0 -75
- package/src/utils/__tests__/silentGuardKey.test.ts +0 -82
- package/src/utils/silentGuardKey.ts +0 -46
|
@@ -17,13 +17,11 @@ import { Ionicons } from '@expo/vector-icons';
|
|
|
17
17
|
import { toast, useDialogControl, Dialog } from '@oxyhq/bloom';
|
|
18
18
|
import { Divider } from '@oxyhq/bloom/divider';
|
|
19
19
|
import { useTheme } from '@oxyhq/bloom/theme';
|
|
20
|
-
import {
|
|
21
|
-
import type { AccountNode } from '@oxyhq/core';
|
|
20
|
+
import { isDev, logger as loggerUtil } from '@oxyhq/core';
|
|
22
21
|
import Avatar from './Avatar';
|
|
23
22
|
import { useOxy } from '../context/OxyContext';
|
|
24
23
|
import { useI18n } from '../hooks/useI18n';
|
|
25
|
-
import {
|
|
26
|
-
import { useDeviceAccounts } from '../hooks/useDeviceAccounts';
|
|
24
|
+
import { useSwitchableAccounts, type SwitchableAccount } from '../hooks/useSwitchableAccounts';
|
|
27
25
|
import type { AccountMenuAnchor } from './AccountMenu';
|
|
28
26
|
|
|
29
27
|
const isWeb = Platform.OS === 'web';
|
|
@@ -50,18 +48,18 @@ export interface AccountSwitcherActions {
|
|
|
50
48
|
}
|
|
51
49
|
|
|
52
50
|
interface TreeEntry {
|
|
53
|
-
root:
|
|
54
|
-
children:
|
|
51
|
+
root: SwitchableAccount;
|
|
52
|
+
children: SwitchableAccount[];
|
|
55
53
|
}
|
|
56
54
|
|
|
57
55
|
/**
|
|
58
|
-
* Group flat account
|
|
59
|
-
* whose parent is not in the set is treated as a root; any deeper
|
|
56
|
+
* Group flat account rows into a 2-level tree (org → direct children). A row
|
|
57
|
+
* whose parent is not in the set is treated as a root; any deeper row not
|
|
60
58
|
* captured under a root is promoted to its own root row so nothing is hidden.
|
|
61
59
|
*/
|
|
62
|
-
function toTree(nodes:
|
|
60
|
+
function toTree(nodes: SwitchableAccount[]): TreeEntry[] {
|
|
63
61
|
const byId = new Map(nodes.map((node) => [node.accountId, node]));
|
|
64
|
-
const childrenOf = new Map<string,
|
|
62
|
+
const childrenOf = new Map<string, SwitchableAccount[]>();
|
|
65
63
|
for (const node of nodes) {
|
|
66
64
|
if (node.parentAccountId && byId.has(node.parentAccountId)) {
|
|
67
65
|
const arr = childrenOf.get(node.parentAccountId) ?? [];
|
|
@@ -91,14 +89,14 @@ function toTree(nodes: AccountNode[]): TreeEntry[] {
|
|
|
91
89
|
/**
|
|
92
90
|
* The presentational, chrome-agnostic body of the unified account switcher.
|
|
93
91
|
*
|
|
94
|
-
* Two levels, relationship-aware
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
* - Section
|
|
92
|
+
* Two levels, relationship-aware, both sourced from the single
|
|
93
|
+
* {@link useSwitchableAccounts} hook and switched through the SINGLE
|
|
94
|
+
* `switchToAccount(accountId)` path:
|
|
95
|
+
* - Section A — accounts signed in on THIS device (`onDevice` rows). Tap to
|
|
96
|
+
* switch; per-row sign-out; "Add another account".
|
|
97
|
+
* - Section B — the account GRAPH not yet signed in here: "Your accounts"
|
|
98
98
|
* (self / owned) and "Shared with you" (member). 2-level tree (org →
|
|
99
|
-
* children), role badge, search, one-tap to
|
|
100
|
-
*
|
|
101
|
-
* Reads everything from `useOxy()` / `useDeviceAccounts()`.
|
|
99
|
+
* children), role badge, search, one-tap to switch.
|
|
102
100
|
*/
|
|
103
101
|
export const AccountSwitcherView: React.FC<AccountSwitcherActions> = ({
|
|
104
102
|
onClose,
|
|
@@ -109,22 +107,16 @@ export const AccountSwitcherView: React.FC<AccountSwitcherActions> = ({
|
|
|
109
107
|
onBeforeSessionChange,
|
|
110
108
|
}) => {
|
|
111
109
|
const {
|
|
112
|
-
activeSessionId,
|
|
113
|
-
switchSession,
|
|
114
110
|
switchToAccount,
|
|
115
111
|
removeSession,
|
|
116
112
|
logout,
|
|
117
113
|
logoutAll,
|
|
118
|
-
accounts,
|
|
119
|
-
user,
|
|
120
|
-
oxyServices,
|
|
121
114
|
} = useOxy();
|
|
122
|
-
const { t
|
|
115
|
+
const { t } = useI18n();
|
|
123
116
|
const { colors } = useTheme();
|
|
124
117
|
|
|
125
|
-
const { accounts
|
|
118
|
+
const { accounts } = useSwitchableAccounts();
|
|
126
119
|
|
|
127
|
-
const [busySessionId, setBusySessionId] = useState<string | null>(null);
|
|
128
120
|
const [switchingAccountId, setSwitchingAccountId] = useState<string | null>(null);
|
|
129
121
|
const [removingSessionId, setRemovingSessionId] = useState<string | null>(null);
|
|
130
122
|
const [signingOut, setSigningOut] = useState(false);
|
|
@@ -134,65 +126,58 @@ export const AccountSwitcherView: React.FC<AccountSwitcherActions> = ({
|
|
|
134
126
|
const signOutDialog = useDialogControl();
|
|
135
127
|
const signOutAllDialog = useDialogControl();
|
|
136
128
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const
|
|
140
|
-
const
|
|
129
|
+
// Section A — accounts signed in on THIS device, current one first.
|
|
130
|
+
const deviceRows = useMemo<SwitchableAccount[]>(() => {
|
|
131
|
+
const onDevice = accounts.filter((account) => account.onDevice);
|
|
132
|
+
const current = onDevice.filter((account) => account.isCurrent);
|
|
133
|
+
const others = onDevice.filter((account) => !account.isCurrent);
|
|
141
134
|
return [...current, ...others];
|
|
142
|
-
}, [
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
// (Section A) and a graph row (Section B). We dedupe by hiding such accounts
|
|
151
|
-
// from the graph — they show in Section A, where the active one is flagged
|
|
152
|
-
// current.
|
|
153
|
-
const deviceUserIds = useMemo<Set<string>>(
|
|
154
|
-
() => new Set(deviceAccounts.map((account) => account.user.id).filter((id): id is string => Boolean(id))),
|
|
155
|
-
[deviceAccounts],
|
|
135
|
+
}, [accounts]);
|
|
136
|
+
|
|
137
|
+
// Section B — linked graph accounts NOT already signed in on this device.
|
|
138
|
+
// (An account switched into becomes a device session, so it moves to Section
|
|
139
|
+
// A automatically — the unified hook already deduped it there.)
|
|
140
|
+
const graphAccounts = useMemo<SwitchableAccount[]>(
|
|
141
|
+
() => accounts.filter((account) => !account.onDevice),
|
|
142
|
+
[accounts],
|
|
156
143
|
);
|
|
157
144
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
// (deduped into Section A). This is the set Section B renders and searches.
|
|
161
|
-
const graphAccounts = useMemo<AccountNode[]>(
|
|
162
|
-
() => accounts.filter((node) => !deviceUserIds.has(node.accountId)),
|
|
163
|
-
[accounts, deviceUserIds],
|
|
164
|
-
);
|
|
145
|
+
const isSwitching = switchingAccountId !== null;
|
|
146
|
+
const actionDisabled = isSwitching || signingOut || signingOutAll;
|
|
165
147
|
|
|
166
|
-
const filtered = useMemo<
|
|
148
|
+
const filtered = useMemo<SwitchableAccount[]>(() => {
|
|
167
149
|
const q = query.trim().toLowerCase();
|
|
168
150
|
if (!q) return graphAccounts;
|
|
169
|
-
return graphAccounts.filter((
|
|
170
|
-
const name =
|
|
171
|
-
const username = (
|
|
151
|
+
return graphAccounts.filter((account) => {
|
|
152
|
+
const name = account.displayName.toLowerCase();
|
|
153
|
+
const username = (account.user?.username ?? '').toLowerCase();
|
|
172
154
|
return name.includes(q) || username.includes(q);
|
|
173
155
|
});
|
|
174
|
-
}, [graphAccounts, query
|
|
156
|
+
}, [graphAccounts, query]);
|
|
175
157
|
|
|
176
158
|
const yourAccounts = useMemo(
|
|
177
|
-
() => toTree(filtered.filter((
|
|
159
|
+
() => toTree(filtered.filter((account) => account.relationship !== 'member')),
|
|
178
160
|
[filtered],
|
|
179
161
|
);
|
|
180
162
|
const sharedAccounts = useMemo(
|
|
181
|
-
() => toTree(filtered.filter((
|
|
163
|
+
() => toTree(filtered.filter((account) => account.relationship === 'member')),
|
|
182
164
|
[filtered],
|
|
183
165
|
);
|
|
184
166
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
167
|
+
// The ONE uniform switch path. Device rows AND graph rows both switch by
|
|
168
|
+
// account id: `switchToAccount` reuses an existing device session (no mint)
|
|
169
|
+
// and mints a real session only on the FIRST entry into a graph account.
|
|
170
|
+
const handleSwitch = useCallback(async (account: SwitchableAccount) => {
|
|
171
|
+
if (switchingAccountId) return;
|
|
172
|
+
// Tapping the already-active account just closes — it IS the current one.
|
|
173
|
+
if (account.isCurrent) {
|
|
189
174
|
onClose();
|
|
190
175
|
return;
|
|
191
176
|
}
|
|
192
|
-
|
|
177
|
+
setSwitchingAccountId(account.accountId);
|
|
193
178
|
try {
|
|
194
179
|
await onBeforeSessionChange?.();
|
|
195
|
-
await
|
|
180
|
+
await switchToAccount(account.accountId);
|
|
196
181
|
toast.success(t('accountSwitcher.toasts.switchSuccess') || 'Switched account');
|
|
197
182
|
onClose();
|
|
198
183
|
} catch (error) {
|
|
@@ -201,12 +186,12 @@ export const AccountSwitcherView: React.FC<AccountSwitcherActions> = ({
|
|
|
201
186
|
}
|
|
202
187
|
toast.error(t('accountSwitcher.toasts.switchFailed') || 'Failed to switch account');
|
|
203
188
|
} finally {
|
|
204
|
-
|
|
189
|
+
setSwitchingAccountId(null);
|
|
205
190
|
}
|
|
206
|
-
}, [
|
|
191
|
+
}, [switchingAccountId, switchToAccount, t, onClose, onBeforeSessionChange]);
|
|
207
192
|
|
|
208
193
|
const handleRemoveDevice = useCallback(async (sessionId: string) => {
|
|
209
|
-
if (
|
|
194
|
+
if (removingSessionId) return;
|
|
210
195
|
setRemovingSessionId(sessionId);
|
|
211
196
|
try {
|
|
212
197
|
await removeSession(sessionId);
|
|
@@ -217,7 +202,7 @@ export const AccountSwitcherView: React.FC<AccountSwitcherActions> = ({
|
|
|
217
202
|
} finally {
|
|
218
203
|
setRemovingSessionId(null);
|
|
219
204
|
}
|
|
220
|
-
}, [
|
|
205
|
+
}, [removingSessionId, removeSession, t]);
|
|
221
206
|
|
|
222
207
|
const performSignOut = useCallback(async () => {
|
|
223
208
|
if (signingOut) return;
|
|
@@ -251,57 +236,26 @@ export const AccountSwitcherView: React.FC<AccountSwitcherActions> = ({
|
|
|
251
236
|
}
|
|
252
237
|
}, [signingOutAll, logoutAll, t, onClose, onBeforeSessionChange]);
|
|
253
238
|
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
try {
|
|
264
|
-
await onBeforeSessionChange?.();
|
|
265
|
-
// Switching INTO a graph account is a REAL session switch — the whole
|
|
266
|
-
// app becomes that account (no delegation header).
|
|
267
|
-
await switchToAccount(node.accountId);
|
|
268
|
-
toast.success(t('accountSwitcher.toasts.switchSuccess') || 'Switched account');
|
|
269
|
-
onClose();
|
|
270
|
-
} catch (error) {
|
|
271
|
-
if (!isDev()) {
|
|
272
|
-
loggerUtil.warn('Switch account failed', { component: 'AccountSwitcher' }, error as unknown);
|
|
273
|
-
}
|
|
274
|
-
toast.error(t('accountSwitcher.toasts.switchFailed') || 'Failed to switch account');
|
|
275
|
-
} finally {
|
|
276
|
-
setSwitchingAccountId(null);
|
|
277
|
-
}
|
|
278
|
-
}, [switchingAccountId, user?.id, switchToAccount, t, onClose, onBeforeSessionChange]);
|
|
279
|
-
|
|
280
|
-
const renderAccountNode = useCallback((node: AccountNode, isChild: boolean) => {
|
|
281
|
-
const displayName = getAccountDisplayName(node.account, locale);
|
|
282
|
-
const username = node.account?.username ? `@${node.account.username}` : null;
|
|
283
|
-
// Current account = the active session's user. After a real-session
|
|
284
|
-
// switch `user` IS this account (such accounts are normally deduped out of
|
|
285
|
-
// the graph, but the check stays correct/defensive if one lingers).
|
|
286
|
-
const active = node.accountId === user?.id;
|
|
287
|
-
const isNodeSwitching = switchingAccountId === node.accountId;
|
|
288
|
-
const role = node.callerMembership?.role;
|
|
289
|
-
const avatarUri = node.account?.avatar
|
|
290
|
-
? oxyServices.getFileDownloadUrl(node.account.avatar, 'thumb')
|
|
291
|
-
: undefined;
|
|
292
|
-
const permissions = node.callerMembership?.permissions ?? [];
|
|
293
|
-
const canManage = node.relationship !== 'member'
|
|
239
|
+
const renderAccountNode = useCallback((account: SwitchableAccount, isChild: boolean) => {
|
|
240
|
+
const displayName = account.displayName;
|
|
241
|
+
const secondary = account.email;
|
|
242
|
+
const active = account.isCurrent;
|
|
243
|
+
const isNodeSwitching = switchingAccountId === account.accountId;
|
|
244
|
+
const role = account.callerMembership?.role;
|
|
245
|
+
const avatarUri = account.avatarUrl;
|
|
246
|
+
const permissions = account.callerMembership?.permissions ?? [];
|
|
247
|
+
const canManage = account.relationship !== 'member'
|
|
294
248
|
|| permissions.includes('account:update')
|
|
295
249
|
|| permissions.includes('members:read');
|
|
296
|
-
const showSettings =
|
|
250
|
+
const showSettings = account.relationship !== 'self' && !!onOpenAccountSettings && canManage;
|
|
297
251
|
|
|
298
252
|
return (
|
|
299
253
|
<TouchableOpacity
|
|
300
|
-
key={`node-${
|
|
254
|
+
key={`node-${account.accountId}`}
|
|
301
255
|
accessibilityRole="menuitem"
|
|
302
256
|
accessibilityLabel={displayName}
|
|
303
257
|
accessibilityState={{ selected: active }}
|
|
304
|
-
onPress={() =>
|
|
258
|
+
onPress={() => handleSwitch(account)}
|
|
305
259
|
disabled={isSwitching}
|
|
306
260
|
activeOpacity={0.6}
|
|
307
261
|
style={[
|
|
@@ -325,9 +279,9 @@ export const AccountSwitcherView: React.FC<AccountSwitcherActions> = ({
|
|
|
325
279
|
</View>
|
|
326
280
|
) : null}
|
|
327
281
|
</View>
|
|
328
|
-
{
|
|
282
|
+
{secondary ? (
|
|
329
283
|
<Text style={[styles.accountEmail, { color: colors.textSecondary }]} numberOfLines={1}>
|
|
330
|
-
{
|
|
284
|
+
{secondary}
|
|
331
285
|
</Text>
|
|
332
286
|
) : null}
|
|
333
287
|
</View>
|
|
@@ -335,7 +289,7 @@ export const AccountSwitcherView: React.FC<AccountSwitcherActions> = ({
|
|
|
335
289
|
<TouchableOpacity
|
|
336
290
|
accessibilityRole="button"
|
|
337
291
|
accessibilityLabel={t('accounts.settings.title') || 'Account settings'}
|
|
338
|
-
onPress={() => onOpenAccountSettings?.(
|
|
292
|
+
onPress={() => onOpenAccountSettings?.(account.accountId)}
|
|
339
293
|
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
|
|
340
294
|
style={styles.settingsButton}
|
|
341
295
|
>
|
|
@@ -349,7 +303,7 @@ export const AccountSwitcherView: React.FC<AccountSwitcherActions> = ({
|
|
|
349
303
|
) : null}
|
|
350
304
|
</TouchableOpacity>
|
|
351
305
|
);
|
|
352
|
-
}, [
|
|
306
|
+
}, [switchingAccountId, isSwitching, colors, handleSwitch, onOpenAccountSettings, t]);
|
|
353
307
|
|
|
354
308
|
const renderTree = useCallback((entries: TreeEntry[]) => (
|
|
355
309
|
entries.map((entry) => (
|
|
@@ -371,38 +325,39 @@ export const AccountSwitcherView: React.FC<AccountSwitcherActions> = ({
|
|
|
371
325
|
<Text style={[styles.sectionLabel, { color: colors.textSecondary }]}>
|
|
372
326
|
{t('accountSwitcher.sections.thisDevice') || 'On this device'}
|
|
373
327
|
</Text>
|
|
374
|
-
{deviceRows.map((
|
|
375
|
-
const
|
|
376
|
-
const
|
|
328
|
+
{deviceRows.map((account) => {
|
|
329
|
+
const sessionId = account.sessionId;
|
|
330
|
+
const isBusy = switchingAccountId === account.accountId;
|
|
331
|
+
const isRemoving = sessionId ? removingSessionId === sessionId : false;
|
|
377
332
|
// The active device session IS the current account — there is no
|
|
378
|
-
// separate acting-as concept; switching makes
|
|
379
|
-
const isCurrentAccount =
|
|
333
|
+
// separate acting-as concept; switching makes it the whole app.
|
|
334
|
+
const isCurrentAccount = account.isCurrent;
|
|
380
335
|
return (
|
|
381
336
|
<TouchableOpacity
|
|
382
|
-
key={`device-${
|
|
337
|
+
key={`device-${account.accountId}`}
|
|
383
338
|
accessibilityRole="menuitem"
|
|
384
|
-
accessibilityLabel={
|
|
339
|
+
accessibilityLabel={account.displayName}
|
|
385
340
|
accessibilityState={{ selected: isCurrentAccount }}
|
|
386
|
-
onPress={() =>
|
|
341
|
+
onPress={() => handleSwitch(account)}
|
|
387
342
|
disabled={isCurrentAccount || isBusy || isSwitching}
|
|
388
343
|
activeOpacity={0.6}
|
|
389
344
|
style={[
|
|
390
345
|
styles.accountRow,
|
|
391
346
|
isCurrentAccount && { backgroundColor: colors.primarySubtle },
|
|
392
|
-
isSwitching && !
|
|
347
|
+
isSwitching && !isCurrentAccount && styles.rowDisabled,
|
|
393
348
|
]}
|
|
394
349
|
>
|
|
395
|
-
<Avatar uri={
|
|
350
|
+
<Avatar uri={account.avatarUrl} name={account.displayName} size={isCurrentAccount ? 40 : 32} />
|
|
396
351
|
<View style={styles.accountInfo}>
|
|
397
352
|
<Text
|
|
398
|
-
style={[styles.accountName, { color: colors.text },
|
|
353
|
+
style={[styles.accountName, { color: colors.text }, isCurrentAccount && styles.accountNameActive]}
|
|
399
354
|
numberOfLines={1}
|
|
400
355
|
>
|
|
401
|
-
{
|
|
356
|
+
{account.displayName}
|
|
402
357
|
</Text>
|
|
403
|
-
{
|
|
358
|
+
{account.email ? (
|
|
404
359
|
<Text style={[styles.accountEmail, { color: colors.textSecondary }]} numberOfLines={1}>
|
|
405
|
-
{
|
|
360
|
+
{account.email}
|
|
406
361
|
</Text>
|
|
407
362
|
) : null}
|
|
408
363
|
</View>
|
|
@@ -412,21 +367,21 @@ export const AccountSwitcherView: React.FC<AccountSwitcherActions> = ({
|
|
|
412
367
|
<Ionicons name="checkmark" size={20} color={colors.primary} />
|
|
413
368
|
) : isRemoving ? (
|
|
414
369
|
<ActivityIndicator color={colors.textSecondary} size="small" />
|
|
415
|
-
) : (
|
|
370
|
+
) : sessionId ? (
|
|
416
371
|
<TouchableOpacity
|
|
417
372
|
accessibilityRole="button"
|
|
418
373
|
accessibilityLabel={
|
|
419
|
-
t('accountMenu.signOutAccount', { name:
|
|
420
|
-
|| `Sign out ${
|
|
374
|
+
t('accountMenu.signOutAccount', { name: account.displayName })
|
|
375
|
+
|| `Sign out ${account.displayName}`
|
|
421
376
|
}
|
|
422
|
-
onPress={() => handleRemoveDevice(
|
|
377
|
+
onPress={() => handleRemoveDevice(sessionId)}
|
|
423
378
|
disabled={isSwitching || removingSessionId !== null}
|
|
424
379
|
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
|
|
425
380
|
style={styles.settingsButton}
|
|
426
381
|
>
|
|
427
382
|
<Ionicons name="log-out-outline" size={18} color={colors.textSecondary} />
|
|
428
383
|
</TouchableOpacity>
|
|
429
|
-
)}
|
|
384
|
+
) : null}
|
|
430
385
|
</TouchableOpacity>
|
|
431
386
|
);
|
|
432
387
|
})}
|
|
@@ -594,7 +549,6 @@ export interface AccountSwitcherProps extends AccountSwitcherActions {
|
|
|
594
549
|
/**
|
|
595
550
|
* Unified account switcher presented as a popover (web) / bottom-sheet style
|
|
596
551
|
* modal (native). The canonical entry point opened by {@link AccountMenuButton}.
|
|
597
|
-
* Supersedes `AccountMenu` (which remains exported as the device-only switcher).
|
|
598
552
|
*/
|
|
599
553
|
const AccountSwitcher: React.FC<AccountSwitcherProps> = ({ open, anchor, ...actions }) => {
|
|
600
554
|
const { t } = useI18n();
|