@oxyhq/services 16.0.0 → 17.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/index.js +21 -51
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/ui/client.js +0 -14
- package/lib/commonjs/ui/client.js.map +1 -1
- package/lib/commonjs/ui/components/OxyAccountDialog.js +764 -0
- package/lib/commonjs/ui/components/OxyAccountDialog.js.map +1 -0
- package/lib/commonjs/ui/components/OxyProvider.js +11 -6
- package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
- package/lib/commonjs/ui/components/OxySignInButton.js +10 -21
- package/lib/commonjs/ui/components/OxySignInButton.js.map +1 -1
- package/lib/commonjs/ui/components/ProfileButton.js +25 -112
- package/lib/commonjs/ui/components/ProfileButton.js.map +1 -1
- package/lib/commonjs/ui/components/RequireOxyAuth.js +299 -0
- package/lib/commonjs/ui/components/RequireOxyAuth.js.map +1 -0
- package/lib/commonjs/ui/context/OxyContext.js +106 -2
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js +1 -1
- package/lib/commonjs/ui/hooks/useAuth.js +19 -30
- package/lib/commonjs/ui/hooks/useAuth.js.map +1 -1
- package/lib/commonjs/ui/hooks/useSwitchableAccounts.js +37 -267
- package/lib/commonjs/ui/hooks/useSwitchableAccounts.js.map +1 -1
- package/lib/commonjs/ui/index.js +5 -40
- package/lib/commonjs/ui/index.js.map +1 -1
- package/lib/commonjs/ui/navigation/accountDialogManager.js +91 -0
- package/lib/commonjs/ui/navigation/accountDialogManager.js.map +1 -0
- package/lib/commonjs/ui/navigation/routes.js +0 -2
- package/lib/commonjs/ui/navigation/routes.js.map +1 -1
- package/lib/commonjs/ui/screens/ManageAccountScreen.js +4 -3
- package/lib/commonjs/ui/screens/ManageAccountScreen.js.map +1 -1
- package/lib/commonjs/ui/session/createSessionClient.js +10 -2
- package/lib/commonjs/ui/session/createSessionClient.js.map +1 -1
- package/lib/module/index.js +18 -15
- package/lib/module/index.js.map +1 -1
- package/lib/module/ui/client.js +1 -2
- package/lib/module/ui/client.js.map +1 -1
- package/lib/module/ui/components/OxyAccountDialog.js +761 -0
- package/lib/module/ui/components/OxyAccountDialog.js.map +1 -0
- package/lib/module/ui/components/OxyProvider.js +11 -6
- package/lib/module/ui/components/OxyProvider.js.map +1 -1
- package/lib/module/ui/components/OxySignInButton.js +10 -21
- package/lib/module/ui/components/OxySignInButton.js.map +1 -1
- package/lib/module/ui/components/ProfileButton.js +26 -112
- package/lib/module/ui/components/ProfileButton.js.map +1 -1
- package/lib/module/ui/components/RequireOxyAuth.js +294 -0
- package/lib/module/ui/components/RequireOxyAuth.js.map +1 -0
- package/lib/module/ui/context/OxyContext.js +107 -3
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/hooks/mutations/useAccountMutations.js +1 -1
- package/lib/module/ui/hooks/useAuth.js +19 -30
- package/lib/module/ui/hooks/useAuth.js.map +1 -1
- package/lib/module/ui/hooks/useSwitchableAccounts.js +38 -267
- package/lib/module/ui/hooks/useSwitchableAccounts.js.map +1 -1
- package/lib/module/ui/index.js +1 -5
- package/lib/module/ui/index.js.map +1 -1
- package/lib/module/ui/navigation/accountDialogManager.js +81 -0
- package/lib/module/ui/navigation/accountDialogManager.js.map +1 -0
- package/lib/module/ui/navigation/routes.js +0 -2
- package/lib/module/ui/navigation/routes.js.map +1 -1
- package/lib/module/ui/screens/ManageAccountScreen.js +4 -3
- package/lib/module/ui/screens/ManageAccountScreen.js.map +1 -1
- package/lib/module/ui/session/createSessionClient.js +10 -2
- package/lib/module/ui/session/createSessionClient.js.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +4 -11
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/client.d.ts +0 -4
- package/lib/typescript/commonjs/ui/client.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/OxyAccountDialog.d.ts +35 -0
- package/lib/typescript/commonjs/ui/components/OxyAccountDialog.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/OxyProvider.d.ts +2 -2
- package/lib/typescript/commonjs/ui/components/OxyProvider.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/OxySignInButton.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/ProfileButton.d.ts +11 -23
- package/lib/typescript/commonjs/ui/components/ProfileButton.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/RequireOxyAuth.d.ts +55 -0
- package/lib/typescript/commonjs/ui/components/RequireOxyAuth.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts +13 -1
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useAuth.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useSwitchableAccounts.d.ts +13 -152
- package/lib/typescript/commonjs/ui/hooks/useSwitchableAccounts.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/index.d.ts +2 -5
- package/lib/typescript/commonjs/ui/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/navigation/accountDialogManager.d.ts +49 -0
- package/lib/typescript/commonjs/ui/navigation/accountDialogManager.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/navigation/routes.d.ts +1 -1
- package/lib/typescript/commonjs/ui/navigation/routes.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/ManageAccountScreen.d.ts +1 -1
- package/lib/typescript/commonjs/ui/screens/ManageAccountScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts +9 -2
- package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/types/navigation.d.ts +9 -0
- package/lib/typescript/commonjs/ui/types/navigation.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +4 -11
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/client.d.ts +0 -4
- package/lib/typescript/module/ui/client.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/OxyAccountDialog.d.ts +35 -0
- package/lib/typescript/module/ui/components/OxyAccountDialog.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/OxyProvider.d.ts +2 -2
- package/lib/typescript/module/ui/components/OxyProvider.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/OxySignInButton.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/ProfileButton.d.ts +11 -23
- package/lib/typescript/module/ui/components/ProfileButton.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/RequireOxyAuth.d.ts +55 -0
- package/lib/typescript/module/ui/components/RequireOxyAuth.d.ts.map +1 -0
- package/lib/typescript/module/ui/context/OxyContext.d.ts +13 -1
- package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useAuth.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useSwitchableAccounts.d.ts +13 -152
- package/lib/typescript/module/ui/hooks/useSwitchableAccounts.d.ts.map +1 -1
- package/lib/typescript/module/ui/index.d.ts +2 -5
- package/lib/typescript/module/ui/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/navigation/accountDialogManager.d.ts +49 -0
- package/lib/typescript/module/ui/navigation/accountDialogManager.d.ts.map +1 -0
- package/lib/typescript/module/ui/navigation/routes.d.ts +1 -1
- package/lib/typescript/module/ui/navigation/routes.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/ManageAccountScreen.d.ts +1 -1
- package/lib/typescript/module/ui/screens/ManageAccountScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/session/createSessionClient.d.ts +9 -2
- package/lib/typescript/module/ui/session/createSessionClient.d.ts.map +1 -1
- package/lib/typescript/module/ui/types/navigation.d.ts +9 -0
- package/lib/typescript/module/ui/types/navigation.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +21 -26
- package/src/ui/client.ts +0 -4
- package/src/ui/components/OxyAccountDialog.tsx +760 -0
- package/src/ui/components/OxyProvider.tsx +12 -6
- package/src/ui/components/OxySignInButton.tsx +10 -21
- package/src/ui/components/ProfileButton.tsx +23 -126
- package/src/ui/components/RequireOxyAuth.tsx +266 -0
- package/src/ui/context/OxyContext.tsx +146 -7
- package/src/ui/hooks/mutations/useAccountMutations.ts +1 -1
- package/src/ui/hooks/useAuth.ts +19 -31
- package/src/ui/hooks/useSwitchableAccounts.ts +42 -376
- package/src/ui/index.ts +2 -5
- package/src/ui/navigation/accountDialogManager.ts +84 -0
- package/src/ui/navigation/routes.ts +0 -4
- package/src/ui/screens/ManageAccountScreen.tsx +3 -2
- package/src/ui/session/createSessionClient.ts +9 -1
- package/src/ui/types/navigation.ts +9 -0
- package/lib/commonjs/ui/components/AccountMenu.js +0 -598
- package/lib/commonjs/ui/components/AccountMenu.js.map +0 -1
- package/lib/commonjs/ui/components/AccountMenuButton.js +0 -135
- package/lib/commonjs/ui/components/AccountMenuButton.js.map +0 -1
- package/lib/commonjs/ui/components/AccountSwitcher.js +0 -780
- package/lib/commonjs/ui/components/AccountSwitcher.js.map +0 -1
- package/lib/commonjs/ui/components/AnotherDeviceQR.js +0 -116
- package/lib/commonjs/ui/components/AnotherDeviceQR.js.map +0 -1
- package/lib/commonjs/ui/components/ProfileMenu.js +0 -518
- package/lib/commonjs/ui/components/ProfileMenu.js.map +0 -1
- package/lib/commonjs/ui/components/SignInAccountChooser.js +0 -189
- package/lib/commonjs/ui/components/SignInAccountChooser.js.map +0 -1
- package/lib/commonjs/ui/components/SignInModal.js +0 -600
- package/lib/commonjs/ui/components/SignInModal.js.map +0 -1
- package/lib/commonjs/ui/hooks/useOxyAuthSession.js +0 -501
- package/lib/commonjs/ui/hooks/useOxyAuthSession.js.map +0 -1
- package/lib/commonjs/ui/hooks/usePasswordSignIn.js +0 -170
- package/lib/commonjs/ui/hooks/usePasswordSignIn.js.map +0 -1
- package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +0 -51
- package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +0 -1
- package/lib/commonjs/ui/screens/OxyAuthScreen.js +0 -312
- package/lib/commonjs/ui/screens/OxyAuthScreen.js.map +0 -1
- package/lib/commonjs/utils/deviceFlowSignIn.js +0 -109
- package/lib/commonjs/utils/deviceFlowSignIn.js.map +0 -1
- package/lib/module/ui/components/AccountMenu.js +0 -593
- package/lib/module/ui/components/AccountMenu.js.map +0 -1
- package/lib/module/ui/components/AccountMenuButton.js +0 -130
- package/lib/module/ui/components/AccountMenuButton.js.map +0 -1
- package/lib/module/ui/components/AccountSwitcher.js +0 -774
- package/lib/module/ui/components/AccountSwitcher.js.map +0 -1
- package/lib/module/ui/components/AnotherDeviceQR.js +0 -110
- package/lib/module/ui/components/AnotherDeviceQR.js.map +0 -1
- package/lib/module/ui/components/ProfileMenu.js +0 -514
- package/lib/module/ui/components/ProfileMenu.js.map +0 -1
- package/lib/module/ui/components/SignInAccountChooser.js +0 -183
- package/lib/module/ui/components/SignInAccountChooser.js.map +0 -1
- package/lib/module/ui/components/SignInModal.js +0 -591
- package/lib/module/ui/components/SignInModal.js.map +0 -1
- package/lib/module/ui/hooks/useOxyAuthSession.js +0 -495
- package/lib/module/ui/hooks/useOxyAuthSession.js.map +0 -1
- package/lib/module/ui/hooks/usePasswordSignIn.js +0 -165
- package/lib/module/ui/hooks/usePasswordSignIn.js.map +0 -1
- package/lib/module/ui/screens/AccountSwitcherScreen.js +0 -48
- package/lib/module/ui/screens/AccountSwitcherScreen.js.map +0 -1
- package/lib/module/ui/screens/OxyAuthScreen.js +0 -306
- package/lib/module/ui/screens/OxyAuthScreen.js.map +0 -1
- package/lib/module/utils/deviceFlowSignIn.js +0 -106
- package/lib/module/utils/deviceFlowSignIn.js.map +0 -1
- package/lib/typescript/commonjs/ui/components/AccountMenu.d.ts +0 -47
- package/lib/typescript/commonjs/ui/components/AccountMenu.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/components/AccountMenuButton.d.ts +0 -26
- package/lib/typescript/commonjs/ui/components/AccountMenuButton.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/components/AccountSwitcher.d.ts +0 -46
- package/lib/typescript/commonjs/ui/components/AccountSwitcher.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/components/AnotherDeviceQR.d.ts +0 -34
- package/lib/typescript/commonjs/ui/components/AnotherDeviceQR.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/components/ProfileMenu.d.ts +0 -49
- package/lib/typescript/commonjs/ui/components/ProfileMenu.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/components/SignInAccountChooser.d.ts +0 -29
- package/lib/typescript/commonjs/ui/components/SignInAccountChooser.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/components/SignInModal.d.ts +0 -28
- package/lib/typescript/commonjs/ui/components/SignInModal.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/hooks/useOxyAuthSession.d.ts +0 -105
- package/lib/typescript/commonjs/ui/hooks/useOxyAuthSession.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/hooks/usePasswordSignIn.d.ts +0 -31
- package/lib/typescript/commonjs/ui/hooks/usePasswordSignIn.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/screens/AccountSwitcherScreen.d.ts +0 -11
- package/lib/typescript/commonjs/ui/screens/AccountSwitcherScreen.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/screens/OxyAuthScreen.d.ts +0 -22
- package/lib/typescript/commonjs/ui/screens/OxyAuthScreen.d.ts.map +0 -1
- package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts +0 -97
- package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts.map +0 -1
- package/lib/typescript/module/ui/components/AccountMenu.d.ts +0 -47
- package/lib/typescript/module/ui/components/AccountMenu.d.ts.map +0 -1
- package/lib/typescript/module/ui/components/AccountMenuButton.d.ts +0 -26
- package/lib/typescript/module/ui/components/AccountMenuButton.d.ts.map +0 -1
- package/lib/typescript/module/ui/components/AccountSwitcher.d.ts +0 -46
- package/lib/typescript/module/ui/components/AccountSwitcher.d.ts.map +0 -1
- package/lib/typescript/module/ui/components/AnotherDeviceQR.d.ts +0 -34
- package/lib/typescript/module/ui/components/AnotherDeviceQR.d.ts.map +0 -1
- package/lib/typescript/module/ui/components/ProfileMenu.d.ts +0 -49
- package/lib/typescript/module/ui/components/ProfileMenu.d.ts.map +0 -1
- package/lib/typescript/module/ui/components/SignInAccountChooser.d.ts +0 -29
- package/lib/typescript/module/ui/components/SignInAccountChooser.d.ts.map +0 -1
- package/lib/typescript/module/ui/components/SignInModal.d.ts +0 -28
- package/lib/typescript/module/ui/components/SignInModal.d.ts.map +0 -1
- package/lib/typescript/module/ui/hooks/useOxyAuthSession.d.ts +0 -105
- package/lib/typescript/module/ui/hooks/useOxyAuthSession.d.ts.map +0 -1
- package/lib/typescript/module/ui/hooks/usePasswordSignIn.d.ts +0 -31
- package/lib/typescript/module/ui/hooks/usePasswordSignIn.d.ts.map +0 -1
- package/lib/typescript/module/ui/screens/AccountSwitcherScreen.d.ts +0 -11
- package/lib/typescript/module/ui/screens/AccountSwitcherScreen.d.ts.map +0 -1
- package/lib/typescript/module/ui/screens/OxyAuthScreen.d.ts +0 -22
- package/lib/typescript/module/ui/screens/OxyAuthScreen.d.ts.map +0 -1
- package/lib/typescript/module/utils/deviceFlowSignIn.d.ts +0 -97
- package/lib/typescript/module/utils/deviceFlowSignIn.d.ts.map +0 -1
- package/src/ui/components/AccountMenu.tsx +0 -645
- package/src/ui/components/AccountMenuButton.tsx +0 -126
- package/src/ui/components/AccountSwitcher.tsx +0 -751
- package/src/ui/components/AnotherDeviceQR.tsx +0 -119
- package/src/ui/components/ProfileMenu.tsx +0 -564
- package/src/ui/components/SignInAccountChooser.tsx +0 -162
- package/src/ui/components/SignInModal.tsx +0 -530
- package/src/ui/hooks/useOxyAuthSession.ts +0 -635
- package/src/ui/hooks/usePasswordSignIn.ts +0 -207
- package/src/ui/screens/AccountSwitcherScreen.tsx +0 -43
- package/src/ui/screens/OxyAuthScreen.tsx +0 -324
- package/src/utils/__tests__/deviceFlowSignIn.test.ts +0 -231
- package/src/utils/deviceFlowSignIn.ts +0 -152
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AnotherDeviceQR — the collapsed "Sign in on another device" disclosure shared
|
|
3
|
-
* by both sign-in containers (`SignInModal` on web, `OxyAuthScreen` on native).
|
|
4
|
-
*
|
|
5
|
-
* The QR code is always DEMOTED below the platform-primary "Continue with Oxy"
|
|
6
|
-
* action: you cannot scan your own screen, so the QR is only useful for handing
|
|
7
|
-
* sign-in to a SECOND device (scan with the Oxy Accounts app there). It lives
|
|
8
|
-
* inside a tap-to-expand disclosure so it never competes with the primary CTA.
|
|
9
|
-
*
|
|
10
|
-
* The QR plate stays white intentionally — high contrast is required for
|
|
11
|
-
* reliable scanning regardless of the app theme. That single plate (and its
|
|
12
|
-
* fixed dimensions) is the only StyleSheet/hardcoded-color in this component;
|
|
13
|
-
* everything else is composed from Bloom typography + centralized token classes.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import type React from 'react';
|
|
17
|
-
import { useState, useCallback } from 'react';
|
|
18
|
-
import { View, TouchableOpacity, StyleSheet } from 'react-native';
|
|
19
|
-
import QRCode from 'react-native-qrcode-svg';
|
|
20
|
-
import { useTheme } from '@oxyhq/bloom/theme';
|
|
21
|
-
import { Text } from '@oxyhq/bloom/typography';
|
|
22
|
-
import * as Icons from '@oxyhq/bloom/icons';
|
|
23
|
-
|
|
24
|
-
export interface AnotherDeviceQRProps {
|
|
25
|
-
/** The legacy QR payload (`oxyauth://<token>`). Empty string renders nothing. */
|
|
26
|
-
qrData: string;
|
|
27
|
-
/**
|
|
28
|
-
* The secure "Sign in with Oxy" handoff payload (`oxycommons://approve?...`).
|
|
29
|
-
* Preferred over `qrData` when present — it carries the PUBLIC authorize code
|
|
30
|
-
* (never the secret session token), so the Oxy identity app on another device
|
|
31
|
-
* can scan it to approve. Falls back to `qrData` when the handoff backend has
|
|
32
|
-
* not returned a payload.
|
|
33
|
-
*/
|
|
34
|
-
qrPayload?: string | null;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/** Size, in px, of the rendered QR symbol. */
|
|
38
|
-
const QR_SIZE = 200;
|
|
39
|
-
|
|
40
|
-
/** High-contrast QR colors — intentionally fixed (NOT themed) for scan reliability. */
|
|
41
|
-
const QR_PLATE_BG = '#FFFFFF';
|
|
42
|
-
const QR_FOREGROUND = '#000000';
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Collapsed disclosure that reveals a high-contrast QR for signing in on a
|
|
46
|
-
* second device with the Oxy Accounts app.
|
|
47
|
-
*/
|
|
48
|
-
const AnotherDeviceQR: React.FC<AnotherDeviceQRProps> = ({ qrData, qrPayload }) => {
|
|
49
|
-
const bloomTheme = useTheme();
|
|
50
|
-
const [expanded, setExpanded] = useState(false);
|
|
51
|
-
|
|
52
|
-
const toggle = useCallback(() => {
|
|
53
|
-
setExpanded((prev) => !prev);
|
|
54
|
-
}, []);
|
|
55
|
-
|
|
56
|
-
// Prefer the secure handoff payload (public authorize code) over the legacy
|
|
57
|
-
// token QR; fall back to `qrData` until the handoff backend is available.
|
|
58
|
-
const value = qrPayload || qrData;
|
|
59
|
-
|
|
60
|
-
if (!value) {
|
|
61
|
-
return null;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return (
|
|
65
|
-
<View className="w-full items-center">
|
|
66
|
-
<TouchableOpacity
|
|
67
|
-
className="flex-row items-center justify-center gap-space-8 py-space-12 px-space-8"
|
|
68
|
-
onPress={toggle}
|
|
69
|
-
accessibilityRole="button"
|
|
70
|
-
accessibilityState={{ expanded }}
|
|
71
|
-
accessibilityHint="Reveals a QR code to sign in using the Oxy Accounts app on another device"
|
|
72
|
-
>
|
|
73
|
-
<Icons.QrCode_Stroke2_Corner0_Rounded
|
|
74
|
-
size="sm"
|
|
75
|
-
style={{ color: bloomTheme.colors.textSecondary }}
|
|
76
|
-
/>
|
|
77
|
-
<Text className="font-sansSemibold text-body text-text-secondary">
|
|
78
|
-
Sign in on another device
|
|
79
|
-
</Text>
|
|
80
|
-
{expanded ? (
|
|
81
|
-
<Icons.ChevronTop_Stroke2_Corner0_Rounded
|
|
82
|
-
size="sm"
|
|
83
|
-
style={{ color: bloomTheme.colors.textSecondary }}
|
|
84
|
-
/>
|
|
85
|
-
) : (
|
|
86
|
-
<Icons.ChevronRight_Stroke2_Corner0_Rounded
|
|
87
|
-
size="sm"
|
|
88
|
-
style={{ color: bloomTheme.colors.textSecondary }}
|
|
89
|
-
/>
|
|
90
|
-
)}
|
|
91
|
-
</TouchableOpacity>
|
|
92
|
-
|
|
93
|
-
{expanded && (
|
|
94
|
-
<View className="items-center mt-space-8 gap-space-12">
|
|
95
|
-
{/* The QR plate is intentionally white for scan reliability. */}
|
|
96
|
-
<View style={styles.qrPlate}>
|
|
97
|
-
<QRCode value={value} size={QR_SIZE} backgroundColor={QR_PLATE_BG} color={QR_FOREGROUND} />
|
|
98
|
-
</View>
|
|
99
|
-
<Text className="font-sans text-caption text-text-tertiary text-center">
|
|
100
|
-
Scan with the Oxy app
|
|
101
|
-
</Text>
|
|
102
|
-
</View>
|
|
103
|
-
)}
|
|
104
|
-
</View>
|
|
105
|
-
);
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
// Measured/positioned layout only (no theming): the high-contrast QR plate keeps
|
|
109
|
-
// a fixed white background + padding so the symbol scans reliably regardless of
|
|
110
|
-
// the app theme. This is the single intentional non-token surface in the file.
|
|
111
|
-
const styles = StyleSheet.create({
|
|
112
|
-
qrPlate: {
|
|
113
|
-
padding: 16,
|
|
114
|
-
borderRadius: 16,
|
|
115
|
-
backgroundColor: QR_PLATE_BG,
|
|
116
|
-
},
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
export default AnotherDeviceQR;
|
|
@@ -1,564 +0,0 @@
|
|
|
1
|
-
import type React from 'react';
|
|
2
|
-
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
3
|
-
import {
|
|
4
|
-
View,
|
|
5
|
-
Pressable,
|
|
6
|
-
Modal,
|
|
7
|
-
ScrollView,
|
|
8
|
-
ActivityIndicator,
|
|
9
|
-
Platform,
|
|
10
|
-
StyleSheet,
|
|
11
|
-
type ViewStyle,
|
|
12
|
-
} from 'react-native';
|
|
13
|
-
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
|
14
|
-
import { Avatar } from '@oxyhq/bloom/avatar';
|
|
15
|
-
import { Text } from '@oxyhq/bloom/typography';
|
|
16
|
-
import { useTheme } from '@oxyhq/bloom/theme';
|
|
17
|
-
import { toast, useDialogControl, Dialog } from '@oxyhq/bloom';
|
|
18
|
-
import { Divider } from '@oxyhq/bloom/divider';
|
|
19
|
-
import { isDev, logger as loggerUtil } from '@oxyhq/core';
|
|
20
|
-
import { useOxy } from '../context/OxyContext';
|
|
21
|
-
import { useI18n } from '../hooks/useI18n';
|
|
22
|
-
import { useSwitchableAccounts, type SwitchableAccount } from '../hooks/useSwitchableAccounts';
|
|
23
|
-
|
|
24
|
-
const isWeb = Platform.OS === 'web';
|
|
25
|
-
|
|
26
|
-
/** Fixed popover width on web. `ProfileButton` mirrors this when anchoring. */
|
|
27
|
-
const MENU_WIDTH = 300;
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Web-only anchor. `ProfileButton` measures its trigger and anchors the panel to
|
|
31
|
-
* one corner so the menu opens either UPWARD (footer trigger) or DOWNWARD (a
|
|
32
|
-
* trigger at the top of a sidebar). Exactly ONE vertical edge is pinned:
|
|
33
|
-
* - `bottom` set → the panel's BOTTOM edge is anchored, so it opens UPWARD.
|
|
34
|
-
* - `top` set → the panel's TOP edge is anchored, so it opens DOWNWARD.
|
|
35
|
-
* Native ignores the anchor and docks the panel to the bottom as a sheet.
|
|
36
|
-
*/
|
|
37
|
-
export interface ProfileMenuAnchor {
|
|
38
|
-
/** Distance from the viewport left, for the panel's LEFT edge. */
|
|
39
|
-
left: number;
|
|
40
|
-
/** Distance from the viewport bottom, for the panel's BOTTOM edge (opens upward). */
|
|
41
|
-
bottom?: number;
|
|
42
|
-
/** Distance from the viewport top, for the panel's TOP edge (opens downward). */
|
|
43
|
-
top?: number;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export interface ProfileMenuProps {
|
|
47
|
-
open: boolean;
|
|
48
|
-
onClose: () => void;
|
|
49
|
-
/** Web-only trigger anchor. Native ignores this (bottom-sheet style). */
|
|
50
|
-
anchor?: ProfileMenuAnchor | null;
|
|
51
|
-
/** Navigate to the "Manage account" surface (settings). */
|
|
52
|
-
onNavigateManage: () => void;
|
|
53
|
-
/** Start the add-account / sign-in flow for an additional account. */
|
|
54
|
-
onAddAccount: () => void;
|
|
55
|
-
/** Optional: navigate to the signed-in user's own profile. */
|
|
56
|
-
onNavigateProfile?: () => void;
|
|
57
|
-
/** Called before the active identity changes so apps can clear scoped state. */
|
|
58
|
-
onBeforeSessionChange?: () => void | Promise<void>;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Clean account switcher, written with react-native `StyleSheet` + Bloom
|
|
63
|
-
* theme colors (the package convention — no NativeWind, which is only an
|
|
64
|
-
* optional peer and absent in the primary `accounts` consumer). Lists EVERY
|
|
65
|
-
* switchable account from {@link useSwitchableAccounts}
|
|
66
|
-
* — device sign-ins AND linked graph accounts (owned orgs + shared-with-you) —
|
|
67
|
-
* and routes EVERY switch through the context's single
|
|
68
|
-
* `switchToAccount(accountId)` dispatcher (there is no separate device-only
|
|
69
|
-
* path). Each on-device inactive row carries a per-account sign-out icon;
|
|
70
|
-
* graph-only rows carry a role badge and indent under their parent. Below the
|
|
71
|
-
* list: Add account, Manage account, optional View profile, and Sign out of all.
|
|
72
|
-
*
|
|
73
|
-
* Renders as an upward-opening popover anchored to the trigger on web, and a
|
|
74
|
-
* bottom-sheet style modal on native.
|
|
75
|
-
*/
|
|
76
|
-
const ProfileMenu: React.FC<ProfileMenuProps> = ({
|
|
77
|
-
open,
|
|
78
|
-
onClose,
|
|
79
|
-
anchor,
|
|
80
|
-
onNavigateManage,
|
|
81
|
-
onAddAccount,
|
|
82
|
-
onNavigateProfile,
|
|
83
|
-
onBeforeSessionChange,
|
|
84
|
-
}) => {
|
|
85
|
-
const {
|
|
86
|
-
switchToAccount,
|
|
87
|
-
logoutAll,
|
|
88
|
-
removeSession,
|
|
89
|
-
} = useOxy();
|
|
90
|
-
const { t } = useI18n();
|
|
91
|
-
const { colors } = useTheme();
|
|
92
|
-
|
|
93
|
-
const { accounts } = useSwitchableAccounts();
|
|
94
|
-
|
|
95
|
-
const [busyAccountId, setBusyAccountId] = useState<string | null>(null);
|
|
96
|
-
const [removingSessionId, setRemovingSessionId] = useState<string | null>(null);
|
|
97
|
-
const [signingOutAll, setSigningOutAll] = useState(false);
|
|
98
|
-
|
|
99
|
-
const signOutAllDialog = useDialogControl();
|
|
100
|
-
|
|
101
|
-
const isSwitching = busyAccountId !== null;
|
|
102
|
-
const actionDisabled = isSwitching || removingSessionId !== null || signingOutAll;
|
|
103
|
-
|
|
104
|
-
// Account ids currently in the list — used to decide whether a graph-only
|
|
105
|
-
// row is a CHILD (its parent is also shown) so it can be indented.
|
|
106
|
-
const listedAccountIds = useMemo(
|
|
107
|
-
() => new Set(accounts.map((account) => account.accountId)),
|
|
108
|
-
[accounts],
|
|
109
|
-
);
|
|
110
|
-
|
|
111
|
-
// The ONE uniform switch path: every row (device or linked) routes through
|
|
112
|
-
// `switchToAccount(accountId)`. It reuses the device session when the account
|
|
113
|
-
// is already signed in here, and mints one only on first entry.
|
|
114
|
-
const handleSwitch = useCallback(async (account: SwitchableAccount) => {
|
|
115
|
-
if (account.isCurrent || busyAccountId) {
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
setBusyAccountId(account.accountId);
|
|
119
|
-
try {
|
|
120
|
-
await onBeforeSessionChange?.();
|
|
121
|
-
await switchToAccount(account.accountId);
|
|
122
|
-
toast.success(t('accountSwitcher.toasts.switchSuccess') || 'Switched account');
|
|
123
|
-
onClose();
|
|
124
|
-
} catch (error) {
|
|
125
|
-
if (!isDev()) {
|
|
126
|
-
loggerUtil.warn('Switch account failed', { component: 'ProfileMenu' }, error as unknown);
|
|
127
|
-
}
|
|
128
|
-
toast.error(t('accountSwitcher.toasts.switchFailed') || 'Failed to switch account');
|
|
129
|
-
} finally {
|
|
130
|
-
setBusyAccountId(null);
|
|
131
|
-
}
|
|
132
|
-
}, [busyAccountId, switchToAccount, t, onClose, onBeforeSessionChange]);
|
|
133
|
-
|
|
134
|
-
// Sign out a specific inactive account without switching/clearing the active
|
|
135
|
-
// one. The menu stays open so the user can keep managing accounts.
|
|
136
|
-
const handleRemove = useCallback(async (sessionId: string) => {
|
|
137
|
-
if (removingSessionId) {
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
setRemovingSessionId(sessionId);
|
|
141
|
-
try {
|
|
142
|
-
await removeSession(sessionId);
|
|
143
|
-
toast.success(t('common.actions.signedOut') || 'Signed out');
|
|
144
|
-
} catch (error) {
|
|
145
|
-
loggerUtil.warn('Remove account failed', { component: 'ProfileMenu' }, error as unknown);
|
|
146
|
-
toast.error(t('common.errors.signOutFailed') || 'Failed to sign out');
|
|
147
|
-
} finally {
|
|
148
|
-
setRemovingSessionId(null);
|
|
149
|
-
}
|
|
150
|
-
}, [removingSessionId, removeSession, t]);
|
|
151
|
-
|
|
152
|
-
const performSignOutAll = useCallback(async () => {
|
|
153
|
-
if (signingOutAll) {
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
setSigningOutAll(true);
|
|
157
|
-
try {
|
|
158
|
-
await onBeforeSessionChange?.();
|
|
159
|
-
await logoutAll();
|
|
160
|
-
toast.success(t('accountSwitcher.toasts.signOutAllSuccess') || 'Signed out of all accounts');
|
|
161
|
-
onClose();
|
|
162
|
-
} catch (error) {
|
|
163
|
-
loggerUtil.warn('Sign out all failed', { component: 'ProfileMenu' }, error as unknown);
|
|
164
|
-
toast.error(t('common.errors.signOutAllFailed') || 'Failed to sign out of all accounts');
|
|
165
|
-
} finally {
|
|
166
|
-
setSigningOutAll(false);
|
|
167
|
-
}
|
|
168
|
-
}, [signingOutAll, logoutAll, t, onClose, onBeforeSessionChange]);
|
|
169
|
-
|
|
170
|
-
// Escape-to-close (web only).
|
|
171
|
-
useEffect(() => {
|
|
172
|
-
if (!open || !isWeb || typeof document === 'undefined') {
|
|
173
|
-
return undefined;
|
|
174
|
-
}
|
|
175
|
-
const onKey = (event: KeyboardEvent) => {
|
|
176
|
-
if (event.key === 'Escape') {
|
|
177
|
-
event.stopPropagation();
|
|
178
|
-
onClose();
|
|
179
|
-
}
|
|
180
|
-
};
|
|
181
|
-
document.addEventListener('keydown', onKey, true);
|
|
182
|
-
return () => document.removeEventListener('keydown', onKey, true);
|
|
183
|
-
}, [open, onClose]);
|
|
184
|
-
|
|
185
|
-
if (!open) {
|
|
186
|
-
return null;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Web: anchor the panel to the measured trigger. When the anchor pins `top`
|
|
190
|
-
// the panel opens DOWNWARD from the trigger; otherwise it pins `bottom` and
|
|
191
|
-
// opens UPWARD (the default footer behavior). With no anchor captured, fall
|
|
192
|
-
// back to a bottom-left placement. Native ignores this (the panel docks to
|
|
193
|
-
// the bottom via the overlay's flex-end).
|
|
194
|
-
const panelAnchorStyle: ViewStyle | undefined = isWeb
|
|
195
|
-
? {
|
|
196
|
-
position: 'absolute',
|
|
197
|
-
width: MENU_WIDTH,
|
|
198
|
-
left: anchor?.left ?? 8,
|
|
199
|
-
...(typeof anchor?.top === 'number'
|
|
200
|
-
? { top: anchor.top }
|
|
201
|
-
: { bottom: anchor?.bottom ?? 8 }),
|
|
202
|
-
}
|
|
203
|
-
: undefined;
|
|
204
|
-
|
|
205
|
-
const content = (
|
|
206
|
-
<Pressable
|
|
207
|
-
// Swallow taps inside the panel so they never reach the overlay's
|
|
208
|
-
// outside-tap-to-close handler.
|
|
209
|
-
onPress={() => undefined}
|
|
210
|
-
style={[
|
|
211
|
-
isWeb ? styles.panelWeb : styles.panelNative,
|
|
212
|
-
{ backgroundColor: colors.background },
|
|
213
|
-
isWeb ? { borderColor: colors.border } : null,
|
|
214
|
-
panelAnchorStyle,
|
|
215
|
-
styles.panelBounds,
|
|
216
|
-
styles.shadow,
|
|
217
|
-
]}
|
|
218
|
-
accessibilityRole="menu"
|
|
219
|
-
accessibilityLabel={t('accountMenu.label') || 'Account menu'}
|
|
220
|
-
>
|
|
221
|
-
<ScrollView
|
|
222
|
-
style={styles.scroll}
|
|
223
|
-
contentContainerStyle={styles.scrollContent}
|
|
224
|
-
showsVerticalScrollIndicator={false}
|
|
225
|
-
>
|
|
226
|
-
{/* 1) Every switchable account — device sign-ins AND linked graph
|
|
227
|
-
accounts. Active first (checkmark); on-device rows carry a
|
|
228
|
-
per-account sign-out icon; graph-only rows show a role badge
|
|
229
|
-
and indent under their parent. Every row switches through the
|
|
230
|
-
SAME `switchToAccount(accountId)` path. */}
|
|
231
|
-
{accounts.map((account) => {
|
|
232
|
-
const isActive = account.isCurrent;
|
|
233
|
-
const isBusy = busyAccountId === account.accountId;
|
|
234
|
-
const sessionId = account.sessionId;
|
|
235
|
-
const isRemoving = sessionId ? removingSessionId === sessionId : false;
|
|
236
|
-
const isChild = Boolean(
|
|
237
|
-
account.parentAccountId && listedAccountIds.has(account.parentAccountId),
|
|
238
|
-
);
|
|
239
|
-
const role = account.callerMembership?.role;
|
|
240
|
-
return (
|
|
241
|
-
<Pressable
|
|
242
|
-
key={`account-${account.accountId}`}
|
|
243
|
-
accessibilityRole="menuitem"
|
|
244
|
-
accessibilityLabel={account.displayName}
|
|
245
|
-
accessibilityState={{ selected: isActive }}
|
|
246
|
-
onPress={() => handleSwitch(account)}
|
|
247
|
-
disabled={isActive || isBusy || isSwitching}
|
|
248
|
-
style={[
|
|
249
|
-
styles.accountRow,
|
|
250
|
-
isChild && styles.childRow,
|
|
251
|
-
isActive && { backgroundColor: colors.backgroundSecondary },
|
|
252
|
-
isSwitching && !isActive && styles.rowDimmed,
|
|
253
|
-
]}
|
|
254
|
-
>
|
|
255
|
-
<Avatar
|
|
256
|
-
source={account.user.avatar ?? undefined}
|
|
257
|
-
uri={account.avatarUrl}
|
|
258
|
-
variant="thumb"
|
|
259
|
-
name={account.displayName}
|
|
260
|
-
size={isActive ? 40 : isChild ? 28 : 32}
|
|
261
|
-
/>
|
|
262
|
-
<View style={styles.accountInfo}>
|
|
263
|
-
<View style={styles.nameRow}>
|
|
264
|
-
<Text
|
|
265
|
-
style={[
|
|
266
|
-
styles.accountName,
|
|
267
|
-
{ color: colors.text },
|
|
268
|
-
isActive && styles.accountNameActive,
|
|
269
|
-
]}
|
|
270
|
-
numberOfLines={1}
|
|
271
|
-
>
|
|
272
|
-
{account.displayName}
|
|
273
|
-
</Text>
|
|
274
|
-
{role ? (
|
|
275
|
-
<View style={[styles.roleBadge, { backgroundColor: colors.card }]}>
|
|
276
|
-
<Text style={[styles.roleBadgeText, { color: colors.textSecondary }]}>
|
|
277
|
-
{t(`accounts.roles.${role}.label`) || role}
|
|
278
|
-
</Text>
|
|
279
|
-
</View>
|
|
280
|
-
) : null}
|
|
281
|
-
</View>
|
|
282
|
-
{account.email ? (
|
|
283
|
-
<Text
|
|
284
|
-
style={[styles.accountEmail, { color: colors.textSecondary }]}
|
|
285
|
-
numberOfLines={1}
|
|
286
|
-
>
|
|
287
|
-
{account.email}
|
|
288
|
-
</Text>
|
|
289
|
-
) : null}
|
|
290
|
-
</View>
|
|
291
|
-
{isBusy ? (
|
|
292
|
-
<ActivityIndicator color={colors.primary} size="small" />
|
|
293
|
-
) : isActive ? (
|
|
294
|
-
<MaterialCommunityIcons name="check" size={20} color={colors.primary} />
|
|
295
|
-
) : isRemoving ? (
|
|
296
|
-
<ActivityIndicator color={colors.textSecondary} size="small" />
|
|
297
|
-
) : sessionId ? (
|
|
298
|
-
<Pressable
|
|
299
|
-
accessibilityRole="button"
|
|
300
|
-
accessibilityLabel={
|
|
301
|
-
t('accountMenu.signOutAccount', { name: account.displayName })
|
|
302
|
-
|| `Sign out ${account.displayName}`
|
|
303
|
-
}
|
|
304
|
-
onPress={() => handleRemove(sessionId)}
|
|
305
|
-
disabled={actionDisabled}
|
|
306
|
-
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
|
|
307
|
-
style={styles.rowSignOutButton}
|
|
308
|
-
>
|
|
309
|
-
<MaterialCommunityIcons
|
|
310
|
-
name="logout"
|
|
311
|
-
size={18}
|
|
312
|
-
color={colors.textSecondary}
|
|
313
|
-
/>
|
|
314
|
-
</Pressable>
|
|
315
|
-
) : null}
|
|
316
|
-
</Pressable>
|
|
317
|
-
);
|
|
318
|
-
})}
|
|
319
|
-
|
|
320
|
-
{/* 2) Switching indicator. */}
|
|
321
|
-
{isSwitching ? (
|
|
322
|
-
<View style={styles.switchingRow}>
|
|
323
|
-
<ActivityIndicator color={colors.textSecondary} size="small" />
|
|
324
|
-
<Text style={[styles.switchingText, { color: colors.textSecondary }]}>
|
|
325
|
-
{t('accountMenu.switching') || 'Switching account…'}
|
|
326
|
-
</Text>
|
|
327
|
-
</View>
|
|
328
|
-
) : null}
|
|
329
|
-
|
|
330
|
-
<Divider color={colors.border} spacing={4} />
|
|
331
|
-
|
|
332
|
-
{/* 3) Add another account. */}
|
|
333
|
-
<ActionRow
|
|
334
|
-
icon="account-plus-outline"
|
|
335
|
-
iconColor={colors.icon}
|
|
336
|
-
textColor={colors.text}
|
|
337
|
-
label={t('accountMenu.addAnother') || 'Add another account'}
|
|
338
|
-
disabled={actionDisabled}
|
|
339
|
-
onPress={() => {
|
|
340
|
-
onClose();
|
|
341
|
-
onAddAccount();
|
|
342
|
-
}}
|
|
343
|
-
/>
|
|
344
|
-
|
|
345
|
-
{/* 4) Manage account. */}
|
|
346
|
-
<ActionRow
|
|
347
|
-
icon="cog-outline"
|
|
348
|
-
iconColor={colors.icon}
|
|
349
|
-
textColor={colors.text}
|
|
350
|
-
label={t('accountMenu.manage') || 'Manage your Oxy Account'}
|
|
351
|
-
disabled={actionDisabled}
|
|
352
|
-
onPress={() => {
|
|
353
|
-
onClose();
|
|
354
|
-
onNavigateManage();
|
|
355
|
-
}}
|
|
356
|
-
/>
|
|
357
|
-
|
|
358
|
-
{/* 5) View profile (optional). */}
|
|
359
|
-
{onNavigateProfile ? (
|
|
360
|
-
<ActionRow
|
|
361
|
-
icon="account-outline"
|
|
362
|
-
iconColor={colors.icon}
|
|
363
|
-
textColor={colors.text}
|
|
364
|
-
label={t('accountMenu.viewProfile') || 'View profile'}
|
|
365
|
-
disabled={actionDisabled}
|
|
366
|
-
onPress={() => {
|
|
367
|
-
onClose();
|
|
368
|
-
onNavigateProfile();
|
|
369
|
-
}}
|
|
370
|
-
/>
|
|
371
|
-
) : null}
|
|
372
|
-
|
|
373
|
-
{/* 6) Sign out of all accounts. */}
|
|
374
|
-
<Divider color={colors.border} spacing={4} />
|
|
375
|
-
<Pressable
|
|
376
|
-
accessibilityRole="menuitem"
|
|
377
|
-
accessibilityLabel={t('accountMenu.signOutAll') || 'Sign out of all accounts'}
|
|
378
|
-
onPress={() => signOutAllDialog.open()}
|
|
379
|
-
disabled={actionDisabled}
|
|
380
|
-
style={[styles.actionRow, actionDisabled && styles.rowDimmed]}
|
|
381
|
-
>
|
|
382
|
-
{signingOutAll ? (
|
|
383
|
-
<ActivityIndicator color={colors.error} size="small" />
|
|
384
|
-
) : (
|
|
385
|
-
<MaterialCommunityIcons name="logout" size={18} color={colors.error} />
|
|
386
|
-
)}
|
|
387
|
-
<Text style={[styles.actionText, { color: colors.error }]}>
|
|
388
|
-
{t('accountMenu.signOutAll') || 'Sign out of all accounts'}
|
|
389
|
-
</Text>
|
|
390
|
-
</Pressable>
|
|
391
|
-
</ScrollView>
|
|
392
|
-
</Pressable>
|
|
393
|
-
);
|
|
394
|
-
|
|
395
|
-
return (
|
|
396
|
-
<Modal
|
|
397
|
-
visible={open}
|
|
398
|
-
transparent
|
|
399
|
-
animationType={isWeb ? 'fade' : 'slide'}
|
|
400
|
-
onRequestClose={onClose}
|
|
401
|
-
>
|
|
402
|
-
<Pressable
|
|
403
|
-
accessibilityRole="button"
|
|
404
|
-
accessibilityLabel={t('common.actions.close') || 'Close'}
|
|
405
|
-
onPress={onClose}
|
|
406
|
-
style={isWeb ? styles.webOverlay : styles.nativeOverlay}
|
|
407
|
-
>
|
|
408
|
-
{content}
|
|
409
|
-
</Pressable>
|
|
410
|
-
|
|
411
|
-
<Dialog
|
|
412
|
-
control={signOutAllDialog}
|
|
413
|
-
title={t('accountMenu.signOutAll') || 'Sign out of all accounts'}
|
|
414
|
-
description={t('common.confirms.signOutAll') || 'Are you sure you want to sign out of all accounts?'}
|
|
415
|
-
actions={[
|
|
416
|
-
{
|
|
417
|
-
label: t('accountMenu.signOutAll') || 'Sign out of all accounts',
|
|
418
|
-
color: 'destructive',
|
|
419
|
-
onPress: performSignOutAll,
|
|
420
|
-
},
|
|
421
|
-
{ label: t('common.cancel') || 'Cancel', color: 'cancel' },
|
|
422
|
-
]}
|
|
423
|
-
/>
|
|
424
|
-
</Modal>
|
|
425
|
-
);
|
|
426
|
-
};
|
|
427
|
-
|
|
428
|
-
/** Bottom-section action row (Add account / Manage / View profile). */
|
|
429
|
-
const ActionRow: React.FC<{
|
|
430
|
-
icon: React.ComponentProps<typeof MaterialCommunityIcons>['name'];
|
|
431
|
-
iconColor: string;
|
|
432
|
-
textColor: string;
|
|
433
|
-
label: string;
|
|
434
|
-
disabled: boolean;
|
|
435
|
-
onPress: () => void;
|
|
436
|
-
}> = ({ icon, iconColor, textColor, label, disabled, onPress }) => (
|
|
437
|
-
<Pressable
|
|
438
|
-
accessibilityRole="menuitem"
|
|
439
|
-
accessibilityLabel={label}
|
|
440
|
-
onPress={onPress}
|
|
441
|
-
disabled={disabled}
|
|
442
|
-
style={[styles.actionRow, disabled && styles.rowDimmed]}
|
|
443
|
-
>
|
|
444
|
-
<MaterialCommunityIcons name={icon} size={20} color={iconColor} />
|
|
445
|
-
<Text style={[styles.actionText, { color: textColor }]}>{label}</Text>
|
|
446
|
-
</Pressable>
|
|
447
|
-
);
|
|
448
|
-
|
|
449
|
-
const styles = StyleSheet.create({
|
|
450
|
-
// Overlay: web is a positioning context for the anchored panel; native dims
|
|
451
|
-
// the backdrop and docks the sheet to the bottom.
|
|
452
|
-
webOverlay: {
|
|
453
|
-
flex: 1,
|
|
454
|
-
position: 'relative',
|
|
455
|
-
},
|
|
456
|
-
nativeOverlay: {
|
|
457
|
-
flex: 1,
|
|
458
|
-
justifyContent: 'flex-end',
|
|
459
|
-
backgroundColor: 'rgba(0,0,0,0.32)',
|
|
460
|
-
},
|
|
461
|
-
// Panel shell — web popover / native bottom sheet.
|
|
462
|
-
panelWeb: {
|
|
463
|
-
overflow: 'hidden',
|
|
464
|
-
borderRadius: 16,
|
|
465
|
-
borderWidth: 1,
|
|
466
|
-
},
|
|
467
|
-
panelNative: {
|
|
468
|
-
overflow: 'hidden',
|
|
469
|
-
borderTopLeftRadius: 24,
|
|
470
|
-
borderTopRightRadius: 24,
|
|
471
|
-
paddingBottom: 12,
|
|
472
|
-
},
|
|
473
|
-
// Shared height cap applied to both panel variants.
|
|
474
|
-
panelBounds: {
|
|
475
|
-
maxHeight: '85%',
|
|
476
|
-
},
|
|
477
|
-
// The panel's drop shadow (dynamic elevation).
|
|
478
|
-
shadow: {
|
|
479
|
-
shadowColor: '#000',
|
|
480
|
-
shadowOpacity: 0.18,
|
|
481
|
-
shadowRadius: 24,
|
|
482
|
-
shadowOffset: { width: 0, height: 8 },
|
|
483
|
-
elevation: 12,
|
|
484
|
-
},
|
|
485
|
-
scroll: {
|
|
486
|
-
flexGrow: 0,
|
|
487
|
-
},
|
|
488
|
-
scrollContent: {
|
|
489
|
-
paddingVertical: 4,
|
|
490
|
-
},
|
|
491
|
-
accountRow: {
|
|
492
|
-
flexDirection: 'row',
|
|
493
|
-
alignItems: 'center',
|
|
494
|
-
gap: 12,
|
|
495
|
-
paddingHorizontal: 16,
|
|
496
|
-
paddingVertical: 10,
|
|
497
|
-
},
|
|
498
|
-
childRow: {
|
|
499
|
-
paddingLeft: 40,
|
|
500
|
-
},
|
|
501
|
-
rowDimmed: {
|
|
502
|
-
opacity: 0.4,
|
|
503
|
-
},
|
|
504
|
-
accountInfo: {
|
|
505
|
-
flex: 1,
|
|
506
|
-
minWidth: 0,
|
|
507
|
-
},
|
|
508
|
-
nameRow: {
|
|
509
|
-
flexDirection: 'row',
|
|
510
|
-
alignItems: 'center',
|
|
511
|
-
gap: 6,
|
|
512
|
-
},
|
|
513
|
-
accountName: {
|
|
514
|
-
fontWeight: '500',
|
|
515
|
-
flexShrink: 1,
|
|
516
|
-
},
|
|
517
|
-
accountNameActive: {
|
|
518
|
-
fontWeight: '600',
|
|
519
|
-
},
|
|
520
|
-
accountEmail: {
|
|
521
|
-
fontSize: 12,
|
|
522
|
-
},
|
|
523
|
-
roleBadge: {
|
|
524
|
-
paddingHorizontal: 6,
|
|
525
|
-
paddingVertical: 1,
|
|
526
|
-
borderRadius: 8,
|
|
527
|
-
},
|
|
528
|
-
roleBadgeText: {
|
|
529
|
-
fontSize: 10,
|
|
530
|
-
fontWeight: '600',
|
|
531
|
-
textTransform: 'capitalize',
|
|
532
|
-
},
|
|
533
|
-
rowSignOutButton: {
|
|
534
|
-
width: 28,
|
|
535
|
-
height: 28,
|
|
536
|
-
alignItems: 'center',
|
|
537
|
-
justifyContent: 'center',
|
|
538
|
-
borderRadius: 9999,
|
|
539
|
-
opacity: 0.6,
|
|
540
|
-
},
|
|
541
|
-
switchingRow: {
|
|
542
|
-
flexDirection: 'row',
|
|
543
|
-
alignItems: 'center',
|
|
544
|
-
justifyContent: 'center',
|
|
545
|
-
gap: 8,
|
|
546
|
-
paddingVertical: 8,
|
|
547
|
-
},
|
|
548
|
-
switchingText: {
|
|
549
|
-
fontSize: 12,
|
|
550
|
-
fontWeight: '500',
|
|
551
|
-
},
|
|
552
|
-
actionRow: {
|
|
553
|
-
flexDirection: 'row',
|
|
554
|
-
alignItems: 'center',
|
|
555
|
-
gap: 12,
|
|
556
|
-
paddingHorizontal: 16,
|
|
557
|
-
paddingVertical: 12,
|
|
558
|
-
},
|
|
559
|
-
actionText: {
|
|
560
|
-
fontWeight: '500',
|
|
561
|
-
},
|
|
562
|
-
});
|
|
563
|
-
|
|
564
|
-
export default ProfileMenu;
|