@oxyhq/services 16.0.0 → 18.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/README.md +77 -98
- package/lib/commonjs/index.js +40 -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 +742 -0
- package/lib/commonjs/ui/components/OxyAccountDialog.js.map +1 -0
- package/lib/commonjs/ui/components/OxyConsentScreen.js +461 -0
- package/lib/commonjs/ui/components/OxyConsentScreen.js.map +1 -0
- package/lib/commonjs/ui/components/OxyProvider.js +13 -6
- package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
- package/lib/commonjs/ui/components/OxySignInButton.js +180 -22
- 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/components/oauthNavigation.js +73 -0
- package/lib/commonjs/ui/components/oauthNavigation.js.map +1 -0
- package/lib/commonjs/ui/context/OxyContext.js +122 -4
- 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 +19 -16
- 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 +739 -0
- package/lib/module/ui/components/OxyAccountDialog.js.map +1 -0
- package/lib/module/ui/components/OxyConsentScreen.js +454 -0
- package/lib/module/ui/components/OxyConsentScreen.js.map +1 -0
- package/lib/module/ui/components/OxyProvider.js +13 -6
- package/lib/module/ui/components/OxyProvider.js.map +1 -1
- package/lib/module/ui/components/OxySignInButton.js +181 -22
- 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/components/oauthNavigation.js +70 -0
- package/lib/module/ui/components/oauthNavigation.js.map +1 -0
- package/lib/module/ui/context/OxyContext.js +123 -5
- 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 +8 -12
- 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 +51 -0
- package/lib/typescript/commonjs/ui/components/OxyAccountDialog.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/OxyConsentScreen.d.ts +45 -0
- package/lib/typescript/commonjs/ui/components/OxyConsentScreen.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 +56 -0
- 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/components/oauthNavigation.d.ts +36 -0
- package/lib/typescript/commonjs/ui/components/oauthNavigation.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts +27 -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/queries/useServicesQueries.d.ts +1 -1
- package/lib/typescript/commonjs/ui/hooks/queries/useServicesQueries.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 +27 -0
- package/lib/typescript/commonjs/ui/types/navigation.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +8 -12
- 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 +51 -0
- package/lib/typescript/module/ui/components/OxyAccountDialog.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/OxyConsentScreen.d.ts +45 -0
- package/lib/typescript/module/ui/components/OxyConsentScreen.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 +56 -0
- 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/components/oauthNavigation.d.ts +36 -0
- package/lib/typescript/module/ui/components/oauthNavigation.d.ts.map +1 -0
- package/lib/typescript/module/ui/context/OxyContext.d.ts +27 -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/queries/useServicesQueries.d.ts +1 -1
- package/lib/typescript/module/ui/hooks/queries/useServicesQueries.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 +27 -0
- package/lib/typescript/module/ui/types/navigation.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/index.ts +33 -27
- package/src/ui/client.ts +0 -4
- package/src/ui/components/OxyAccountDialog.tsx +735 -0
- package/src/ui/components/OxyConsentScreen.tsx +456 -0
- package/src/ui/components/OxyProvider.tsx +14 -6
- package/src/ui/components/OxySignInButton.tsx +227 -22
- package/src/ui/components/ProfileButton.tsx +23 -126
- package/src/ui/components/RequireOxyAuth.tsx +266 -0
- package/src/ui/components/__tests__/OxyConsentScreen.test.tsx +130 -0
- package/src/ui/components/oauthNavigation.ts +89 -0
- package/src/ui/context/OxyContext.tsx +176 -9
- package/src/ui/context/__tests__/OxyContext.coldBoot.test.tsx +165 -0
- 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 +27 -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
|
@@ -0,0 +1,735 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OxyAccountDialog — the ONE unified account dialog for `@oxyhq/services`.
|
|
3
|
+
*
|
|
4
|
+
* A thin React Native binding over the headless `AccountDialogController` in
|
|
5
|
+
* `@oxyhq/core` (bound via `useSyncExternalStore`), presented as a bottom-sheet
|
|
6
|
+
* modal. It REPLACES the five drifting device/account surfaces the SDK shipped
|
|
7
|
+
* before (`ProfileMenu`, `AccountMenu`, `AccountSwitcher`, `SignInAccountChooser`,
|
|
8
|
+
* and `SignInModal`) with a single implementation whose data + state machine live
|
|
9
|
+
* in core, so the RN and web (auth-sdk) bindings render the SAME list from the
|
|
10
|
+
* SAME logic and cannot diverge.
|
|
11
|
+
*
|
|
12
|
+
* Views (from `snapshot.view`):
|
|
13
|
+
* - `accounts` — the unified `SwitchableAccount[]` list (device sign-ins ∪ graph
|
|
14
|
+
* accounts). Tapping a row switches through `controller.switchTo(accountId)`
|
|
15
|
+
* (the uniform switch), the active row is flagged, and a "+ Add account" row
|
|
16
|
+
* opens the sign-in entry. This is what `ProfileButton` opens.
|
|
17
|
+
* - `add` / `signin` — the sign-in entry: primary "Sign in with Oxy" (device
|
|
18
|
+
* flow), "Scan a QR", and a secondary "Use a password" hand-off that opens
|
|
19
|
+
* auth.oxy.so (password + 2FA are NOT in the SDK).
|
|
20
|
+
* - `qr` — the cross-device QR handoff + its waiting state.
|
|
21
|
+
*
|
|
22
|
+
* Per-account color re-theming uses Bloom's `APP_COLOR_PRESETS` + `BloomColorScope`
|
|
23
|
+
* (same visual language auth.oxy.so uses). Base theming is `useTheme()` + a
|
|
24
|
+
* `StyleSheet`, so the dialog renders correctly in EVERY consumer — including apps
|
|
25
|
+
* that do not use NativeWind (e.g. the accounts app).
|
|
26
|
+
*
|
|
27
|
+
* The surface is Bloom's `<Dialog>` (`@oxyhq/bloom/dialog`) with a responsive
|
|
28
|
+
* `placement` — a bottom sheet on narrow viewports, a centered card on wide ones.
|
|
29
|
+
* It REPLACES the hand-rolled RN `<Modal>` + `<GestureHandlerRootView>` + manual
|
|
30
|
+
* backdrop/card wrapper this component used before. That RN `<Modal>` is invisible
|
|
31
|
+
* under React StrictMode on web: react-native-web's `ModalPortal` appends its host
|
|
32
|
+
* node during render but removes it in an effect cleanup and never re-attaches, so
|
|
33
|
+
* the dialog never paints in a dev Vite build. Bloom's `<Dialog>` renders through
|
|
34
|
+
* its own Portal and has no such lifecycle hazard.
|
|
35
|
+
*
|
|
36
|
+
* Open/close is CONTROLLED by `isAccountDialogOpen` (the `open` prop); the Dialog
|
|
37
|
+
* stays mounted whenever the controller exists so it can animate its own close.
|
|
38
|
+
* Backdrop / swipe-to-dismiss is disabled (`dismissOnBackdrop={false}`) on purpose:
|
|
39
|
+
* Bloom's controlled `bottom` placement does not fire `onClose` on a gesture or
|
|
40
|
+
* backdrop dismissal, so allowing it would desync `isAccountDialogOpen` from the
|
|
41
|
+
* sheet and block reopening. The header close button (and a successful switch)
|
|
42
|
+
* drive `closeAccountDialog`, which flips `open` and runs the exit animation.
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
import type React from 'react';
|
|
46
|
+
import { useCallback, useMemo, useState, useSyncExternalStore } from 'react';
|
|
47
|
+
import {
|
|
48
|
+
Platform,
|
|
49
|
+
Pressable,
|
|
50
|
+
ScrollView,
|
|
51
|
+
StyleSheet,
|
|
52
|
+
View,
|
|
53
|
+
type StyleProp,
|
|
54
|
+
type ViewStyle,
|
|
55
|
+
} from 'react-native';
|
|
56
|
+
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
|
57
|
+
import QRCode from 'react-native-qrcode-svg';
|
|
58
|
+
import { Avatar } from '@oxyhq/bloom/avatar';
|
|
59
|
+
import { Button } from '@oxyhq/bloom/button';
|
|
60
|
+
import { Dialog } from '@oxyhq/bloom/dialog';
|
|
61
|
+
import { Text } from '@oxyhq/bloom/typography';
|
|
62
|
+
import {
|
|
63
|
+
useTheme,
|
|
64
|
+
BloomColorScope,
|
|
65
|
+
APP_COLOR_NAMES,
|
|
66
|
+
APP_COLOR_PRESETS,
|
|
67
|
+
type AppColorName,
|
|
68
|
+
} from '@oxyhq/bloom/theme';
|
|
69
|
+
import type { SwitchableAccount, AccountDialogSnapshot } from '@oxyhq/core';
|
|
70
|
+
import { useQueryClient } from '@tanstack/react-query';
|
|
71
|
+
import { useOxy } from '../context/OxyContext';
|
|
72
|
+
import { useI18n } from '../hooks/useI18n';
|
|
73
|
+
import OxyLogo from './OxyLogo';
|
|
74
|
+
|
|
75
|
+
/** Diameter of a row avatar. */
|
|
76
|
+
const ROW_AVATAR_SIZE = 40;
|
|
77
|
+
/** High-contrast QR colors — intentionally fixed (NOT themed) for scan reliability. */
|
|
78
|
+
const QR_PLATE_BG = '#FFFFFF';
|
|
79
|
+
const QR_FOREGROUND = '#000000';
|
|
80
|
+
const QR_SIZE = 196;
|
|
81
|
+
|
|
82
|
+
const isWeb = Platform.OS === 'web';
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Resolve an account's stored color (a named Bloom preset, e.g. `'purple'`) to
|
|
86
|
+
* a concrete brand hex for the row accent. Falls back to the theme primary when
|
|
87
|
+
* the account has no color or the value is not a recognized preset, so the accent
|
|
88
|
+
* renders in EVERY consumer regardless of NativeWind availability.
|
|
89
|
+
*/
|
|
90
|
+
function resolveAccentHex(color: string | null, fallback: string): string {
|
|
91
|
+
const preset = toPreset(color);
|
|
92
|
+
return preset ? APP_COLOR_PRESETS[preset].hex : fallback;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Narrow a stored color string to a known `AppColorName`, or `undefined`. */
|
|
96
|
+
function toPreset(color: string | null): AppColorName | undefined {
|
|
97
|
+
if (!color) return undefined;
|
|
98
|
+
return (APP_COLOR_NAMES as readonly string[]).includes(color)
|
|
99
|
+
? (color as AppColorName)
|
|
100
|
+
: undefined;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
interface OxyAccountDialogHandlers {
|
|
104
|
+
onSwitch: (accountId: string) => void;
|
|
105
|
+
onAdd: () => void;
|
|
106
|
+
onManage: () => void;
|
|
107
|
+
onClose: () => void;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* The unified account dialog. Mounted once by `OxyProvider`; opened imperatively
|
|
112
|
+
* via `useOxy().openAccountDialog(view?)` (or the back-compat `showSignInModal`).
|
|
113
|
+
*/
|
|
114
|
+
const OxyAccountDialog: React.FC = () => {
|
|
115
|
+
const {
|
|
116
|
+
accountDialogController: controller,
|
|
117
|
+
isAccountDialogOpen,
|
|
118
|
+
closeAccountDialog,
|
|
119
|
+
showBottomSheet,
|
|
120
|
+
logoutAll,
|
|
121
|
+
refreshAccounts,
|
|
122
|
+
} = useOxy();
|
|
123
|
+
const theme = useTheme();
|
|
124
|
+
const { t } = useI18n();
|
|
125
|
+
const queryClient = useQueryClient();
|
|
126
|
+
|
|
127
|
+
// Bind the headless controller. `getSnapshot` returns a stable reference
|
|
128
|
+
// between changes, so it is `useSyncExternalStore`-safe. Guard the no-provider
|
|
129
|
+
// loading state (`controller` is `null`) with an inert store.
|
|
130
|
+
const subscribe = useCallback(
|
|
131
|
+
(listener: () => void) => (controller ? controller.subscribe(listener) : () => undefined),
|
|
132
|
+
[controller],
|
|
133
|
+
);
|
|
134
|
+
const getSnapshot = useCallback(
|
|
135
|
+
() => (controller ? controller.getSnapshot() : EMPTY_SNAPSHOT),
|
|
136
|
+
[controller],
|
|
137
|
+
);
|
|
138
|
+
const snapshot = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
139
|
+
|
|
140
|
+
const [switching, setSwitching] = useState(false);
|
|
141
|
+
|
|
142
|
+
const handleSwitch = useCallback(
|
|
143
|
+
async (accountId: string) => {
|
|
144
|
+
if (!controller || switching) return;
|
|
145
|
+
if (accountId === snapshot.activeAccountId) {
|
|
146
|
+
closeAccountDialog();
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
setSwitching(true);
|
|
150
|
+
try {
|
|
151
|
+
await controller.switchTo(accountId);
|
|
152
|
+
// A switch succeeds unless the controller recorded an error. On success
|
|
153
|
+
// reload the app's account graph and drop cached account-scoped data so
|
|
154
|
+
// the new active identity re-fetches — the same side effects the
|
|
155
|
+
// context's own `switchToAccount` performs.
|
|
156
|
+
if (!controller.getSnapshot().error) {
|
|
157
|
+
void refreshAccounts();
|
|
158
|
+
queryClient.invalidateQueries();
|
|
159
|
+
closeAccountDialog();
|
|
160
|
+
}
|
|
161
|
+
} finally {
|
|
162
|
+
setSwitching(false);
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
[controller, switching, snapshot.activeAccountId, closeAccountDialog, refreshAccounts, queryClient],
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
const handleManage = useCallback(() => {
|
|
169
|
+
closeAccountDialog();
|
|
170
|
+
showBottomSheet?.('ManageAccount');
|
|
171
|
+
}, [closeAccountDialog, showBottomSheet]);
|
|
172
|
+
|
|
173
|
+
const handleSignOutAll = useCallback(() => {
|
|
174
|
+
closeAccountDialog();
|
|
175
|
+
void logoutAll();
|
|
176
|
+
}, [closeAccountDialog, logoutAll]);
|
|
177
|
+
|
|
178
|
+
const handlers = useMemo<OxyAccountDialogHandlers>(
|
|
179
|
+
() => ({
|
|
180
|
+
onSwitch: (accountId) => {
|
|
181
|
+
void handleSwitch(accountId);
|
|
182
|
+
},
|
|
183
|
+
onAdd: () => controller?.add(),
|
|
184
|
+
onManage: handleManage,
|
|
185
|
+
onClose: closeAccountDialog,
|
|
186
|
+
}),
|
|
187
|
+
[handleSwitch, controller, handleManage, closeAccountDialog],
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
if (!controller) {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const { view } = snapshot;
|
|
195
|
+
const showBack = view === 'qr' || (view === 'add' && snapshot.accounts.length > 0);
|
|
196
|
+
|
|
197
|
+
return (
|
|
198
|
+
<Dialog
|
|
199
|
+
open={isAccountDialogOpen}
|
|
200
|
+
onClose={closeAccountDialog}
|
|
201
|
+
placement={{ base: 'bottom', md: 'center' }}
|
|
202
|
+
dismissOnBackdrop={false}
|
|
203
|
+
maxWidth={420}
|
|
204
|
+
label={headerCopy(view, snapshot.accounts.length, t).title}
|
|
205
|
+
>
|
|
206
|
+
<DialogHeader
|
|
207
|
+
snapshot={snapshot}
|
|
208
|
+
theme={theme}
|
|
209
|
+
t={t}
|
|
210
|
+
showBack={showBack}
|
|
211
|
+
onBack={() => controller.setView('accounts')}
|
|
212
|
+
onClose={closeAccountDialog}
|
|
213
|
+
/>
|
|
214
|
+
<ScrollView
|
|
215
|
+
style={styles.body}
|
|
216
|
+
contentContainerStyle={styles.bodyContent}
|
|
217
|
+
showsVerticalScrollIndicator={false}
|
|
218
|
+
>
|
|
219
|
+
{view === 'accounts' ? (
|
|
220
|
+
<AccountsView snapshot={snapshot} theme={theme} t={t} handlers={handlers} />
|
|
221
|
+
) : view === 'qr' ? (
|
|
222
|
+
<QrView snapshot={snapshot} theme={theme} t={t} onRetry={() => void controller.showQr()} />
|
|
223
|
+
) : (
|
|
224
|
+
<SignInView
|
|
225
|
+
snapshot={snapshot}
|
|
226
|
+
theme={theme}
|
|
227
|
+
t={t}
|
|
228
|
+
handlers={handlers}
|
|
229
|
+
onSignInWithOxy={() => void controller.signInWithOxy()}
|
|
230
|
+
onScanQr={() => void controller.showQr()}
|
|
231
|
+
onUsePassword={() =>
|
|
232
|
+
controller.openPasswordAtOxyAuth({
|
|
233
|
+
returnUrl: isWeb ? currentHref() : undefined,
|
|
234
|
+
})
|
|
235
|
+
}
|
|
236
|
+
/>
|
|
237
|
+
)}
|
|
238
|
+
</ScrollView>
|
|
239
|
+
</Dialog>
|
|
240
|
+
);
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
// ---------------------------------------------------------------------------
|
|
244
|
+
// Header
|
|
245
|
+
// ---------------------------------------------------------------------------
|
|
246
|
+
|
|
247
|
+
type Theme = ReturnType<typeof useTheme>;
|
|
248
|
+
type Translate = ReturnType<typeof useI18n>['t'];
|
|
249
|
+
|
|
250
|
+
interface HeaderProps {
|
|
251
|
+
snapshot: AccountDialogSnapshot;
|
|
252
|
+
theme: Theme;
|
|
253
|
+
t: Translate;
|
|
254
|
+
showBack: boolean;
|
|
255
|
+
onBack: () => void;
|
|
256
|
+
onClose: () => void;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const DialogHeader: React.FC<HeaderProps> = ({ snapshot, theme, t, showBack, onBack, onClose }) => {
|
|
260
|
+
const { title, subtitle } = headerCopy(snapshot.view, snapshot.accounts.length, t);
|
|
261
|
+
return (
|
|
262
|
+
<View style={styles.header}>
|
|
263
|
+
<View style={styles.headerBar}>
|
|
264
|
+
{showBack ? (
|
|
265
|
+
<Pressable
|
|
266
|
+
onPress={onBack}
|
|
267
|
+
style={[styles.iconButton, { backgroundColor: theme.colors.backgroundSecondary }]}
|
|
268
|
+
accessibilityRole="button"
|
|
269
|
+
accessibilityLabel={t('common.actions.back') || 'Back'}
|
|
270
|
+
>
|
|
271
|
+
<MaterialCommunityIcons name="chevron-left" size={22} color={theme.colors.textSecondary} />
|
|
272
|
+
</Pressable>
|
|
273
|
+
) : (
|
|
274
|
+
<View style={styles.iconButton} />
|
|
275
|
+
)}
|
|
276
|
+
<OxyLogo variant="icon" size={34} fillColor={theme.colors.primary} />
|
|
277
|
+
<Pressable
|
|
278
|
+
onPress={onClose}
|
|
279
|
+
style={[styles.iconButton, { backgroundColor: theme.colors.backgroundSecondary }]}
|
|
280
|
+
accessibilityRole="button"
|
|
281
|
+
accessibilityLabel={t('common.actions.close') || 'Close'}
|
|
282
|
+
>
|
|
283
|
+
<MaterialCommunityIcons name="close" size={20} color={theme.colors.textSecondary} />
|
|
284
|
+
</Pressable>
|
|
285
|
+
</View>
|
|
286
|
+
<Text style={[styles.title, { color: theme.colors.text }]} numberOfLines={1}>
|
|
287
|
+
{title}
|
|
288
|
+
</Text>
|
|
289
|
+
{subtitle ? (
|
|
290
|
+
<Text style={[styles.subtitle, { color: theme.colors.textSecondary }]} numberOfLines={2}>
|
|
291
|
+
{subtitle}
|
|
292
|
+
</Text>
|
|
293
|
+
) : null}
|
|
294
|
+
</View>
|
|
295
|
+
);
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
// ---------------------------------------------------------------------------
|
|
299
|
+
// Accounts view
|
|
300
|
+
// ---------------------------------------------------------------------------
|
|
301
|
+
|
|
302
|
+
interface AccountsViewProps {
|
|
303
|
+
snapshot: AccountDialogSnapshot;
|
|
304
|
+
theme: Theme;
|
|
305
|
+
t: Translate;
|
|
306
|
+
handlers: OxyAccountDialogHandlers;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const AccountsView: React.FC<AccountsViewProps> = ({ snapshot, theme, t, handlers }) => {
|
|
310
|
+
if (snapshot.loading && snapshot.accounts.length === 0) {
|
|
311
|
+
return (
|
|
312
|
+
<View style={styles.centeredBlock}>
|
|
313
|
+
<MaterialCommunityIcons name="loading" size={24} color={theme.colors.textSecondary} />
|
|
314
|
+
<Text style={[styles.mutedText, { color: theme.colors.textSecondary }]}>
|
|
315
|
+
{t('accountSwitcher.loading') || 'Loading accounts…'}
|
|
316
|
+
</Text>
|
|
317
|
+
</View>
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return (
|
|
322
|
+
<View style={styles.rows}>
|
|
323
|
+
{snapshot.accounts.map((account) => (
|
|
324
|
+
<AccountRow
|
|
325
|
+
key={account.accountId}
|
|
326
|
+
account={account}
|
|
327
|
+
theme={theme}
|
|
328
|
+
switching={snapshot.switchingAccountId === account.accountId}
|
|
329
|
+
disabled={snapshot.switchingAccountId !== null}
|
|
330
|
+
onPress={() => handlers.onSwitch(account.accountId)}
|
|
331
|
+
/>
|
|
332
|
+
))}
|
|
333
|
+
|
|
334
|
+
<Pressable
|
|
335
|
+
style={[styles.addRow, { borderColor: theme.colors.border }]}
|
|
336
|
+
onPress={handlers.onAdd}
|
|
337
|
+
accessibilityRole="button"
|
|
338
|
+
accessibilityLabel={t('signin.addAccountTitle') || 'Add another account'}
|
|
339
|
+
>
|
|
340
|
+
<View style={[styles.addBadge, { borderColor: theme.colors.border }]}>
|
|
341
|
+
<MaterialCommunityIcons name="plus" size={20} color={theme.colors.textSecondary} />
|
|
342
|
+
</View>
|
|
343
|
+
<Text style={[styles.rowName, { color: theme.colors.textSecondary }]}>
|
|
344
|
+
{t('signin.addAccountTitle') || 'Add another account'}
|
|
345
|
+
</Text>
|
|
346
|
+
</Pressable>
|
|
347
|
+
|
|
348
|
+
<View style={styles.footerLinks}>
|
|
349
|
+
<Pressable onPress={handlers.onManage} accessibilityRole="button">
|
|
350
|
+
<Text style={[styles.linkText, { color: theme.colors.primary }]}>
|
|
351
|
+
{t('accountMenu.manage') || 'Manage accounts'}
|
|
352
|
+
</Text>
|
|
353
|
+
</Pressable>
|
|
354
|
+
</View>
|
|
355
|
+
</View>
|
|
356
|
+
);
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
interface AccountRowProps {
|
|
360
|
+
account: SwitchableAccount;
|
|
361
|
+
theme: Theme;
|
|
362
|
+
switching: boolean;
|
|
363
|
+
disabled: boolean;
|
|
364
|
+
onPress: () => void;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const AccountRow: React.FC<AccountRowProps> = ({ account, theme, switching, disabled, onPress }) => {
|
|
368
|
+
const accent = resolveAccentHex(account.color, theme.colors.primary);
|
|
369
|
+
const rowStyle: StyleProp<ViewStyle> = [
|
|
370
|
+
styles.accountRow,
|
|
371
|
+
{
|
|
372
|
+
borderColor: account.isCurrent ? accent : theme.colors.border,
|
|
373
|
+
backgroundColor: theme.colors.card,
|
|
374
|
+
},
|
|
375
|
+
disabled && !switching ? styles.rowDisabled : null,
|
|
376
|
+
];
|
|
377
|
+
|
|
378
|
+
return (
|
|
379
|
+
<BloomColorScope colorPreset={toPreset(account.color)} asChild>
|
|
380
|
+
<Pressable
|
|
381
|
+
style={rowStyle}
|
|
382
|
+
onPress={onPress}
|
|
383
|
+
disabled={disabled}
|
|
384
|
+
accessibilityRole="button"
|
|
385
|
+
accessibilityState={{ selected: account.isCurrent, disabled }}
|
|
386
|
+
accessibilityLabel={account.displayName}
|
|
387
|
+
>
|
|
388
|
+
<View style={[styles.avatarRing, { borderColor: account.isCurrent ? accent : 'transparent' }]}>
|
|
389
|
+
<Avatar
|
|
390
|
+
source={account.user.avatar ?? undefined}
|
|
391
|
+
variant="thumb"
|
|
392
|
+
name={account.displayName}
|
|
393
|
+
size={ROW_AVATAR_SIZE}
|
|
394
|
+
/>
|
|
395
|
+
</View>
|
|
396
|
+
<View style={styles.rowMeta}>
|
|
397
|
+
<Text style={[styles.rowName, { color: theme.colors.text }]} numberOfLines={1}>
|
|
398
|
+
{account.displayName}
|
|
399
|
+
</Text>
|
|
400
|
+
{account.email ? (
|
|
401
|
+
<Text style={[styles.rowHandle, { color: theme.colors.textSecondary }]} numberOfLines={1}>
|
|
402
|
+
{account.email}
|
|
403
|
+
</Text>
|
|
404
|
+
) : null}
|
|
405
|
+
</View>
|
|
406
|
+
{switching ? (
|
|
407
|
+
<MaterialCommunityIcons name="loading" size={20} color={accent} />
|
|
408
|
+
) : account.isCurrent ? (
|
|
409
|
+
<MaterialCommunityIcons name="check-circle" size={20} color={accent} />
|
|
410
|
+
) : (
|
|
411
|
+
<MaterialCommunityIcons name="chevron-right" size={20} color={theme.colors.textSecondary} />
|
|
412
|
+
)}
|
|
413
|
+
</Pressable>
|
|
414
|
+
</BloomColorScope>
|
|
415
|
+
);
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
// ---------------------------------------------------------------------------
|
|
419
|
+
// Sign-in view (add / signin)
|
|
420
|
+
// ---------------------------------------------------------------------------
|
|
421
|
+
|
|
422
|
+
interface SignInViewProps {
|
|
423
|
+
snapshot: AccountDialogSnapshot;
|
|
424
|
+
theme: Theme;
|
|
425
|
+
t: Translate;
|
|
426
|
+
handlers: OxyAccountDialogHandlers;
|
|
427
|
+
onSignInWithOxy: () => void;
|
|
428
|
+
onScanQr: () => void;
|
|
429
|
+
onUsePassword: () => void;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const SignInView: React.FC<SignInViewProps> = ({
|
|
433
|
+
snapshot,
|
|
434
|
+
theme,
|
|
435
|
+
t,
|
|
436
|
+
handlers,
|
|
437
|
+
onSignInWithOxy,
|
|
438
|
+
onScanQr,
|
|
439
|
+
onUsePassword,
|
|
440
|
+
}) => (
|
|
441
|
+
<View style={styles.signInBlock}>
|
|
442
|
+
{snapshot.accounts.length > 0 ? (
|
|
443
|
+
<View style={styles.rows}>
|
|
444
|
+
{snapshot.accounts.map((account) => (
|
|
445
|
+
<AccountRow
|
|
446
|
+
key={account.accountId}
|
|
447
|
+
account={account}
|
|
448
|
+
theme={theme}
|
|
449
|
+
switching={snapshot.switchingAccountId === account.accountId}
|
|
450
|
+
disabled={snapshot.switchingAccountId !== null}
|
|
451
|
+
onPress={() => handlers.onSwitch(account.accountId)}
|
|
452
|
+
/>
|
|
453
|
+
))}
|
|
454
|
+
<Dividerish theme={theme} label={t('signin.or') || 'or'} />
|
|
455
|
+
</View>
|
|
456
|
+
) : null}
|
|
457
|
+
|
|
458
|
+
<Button variant="primary" onPress={onSignInWithOxy} style={styles.primaryButton}>
|
|
459
|
+
Sign in with Oxy
|
|
460
|
+
</Button>
|
|
461
|
+
|
|
462
|
+
<Button variant="secondary" onPress={onScanQr} style={styles.secondaryButton}>
|
|
463
|
+
{t('accountSwitcher.scanQr') || 'Scan a QR from another device'}
|
|
464
|
+
</Button>
|
|
465
|
+
|
|
466
|
+
<Pressable onPress={onUsePassword} accessibilityRole="button" style={styles.passwordLink}>
|
|
467
|
+
<Text style={[styles.linkText, { color: theme.colors.textSecondary }]}>
|
|
468
|
+
Use a password instead
|
|
469
|
+
</Text>
|
|
470
|
+
</Pressable>
|
|
471
|
+
</View>
|
|
472
|
+
);
|
|
473
|
+
|
|
474
|
+
const Dividerish: React.FC<{ theme: Theme; label: string }> = ({ theme, label }) => (
|
|
475
|
+
<View style={styles.dividerRow}>
|
|
476
|
+
<View style={[styles.dividerLine, { backgroundColor: theme.colors.border }]} />
|
|
477
|
+
<Text style={[styles.dividerText, { color: theme.colors.textSecondary }]}>{label}</Text>
|
|
478
|
+
<View style={[styles.dividerLine, { backgroundColor: theme.colors.border }]} />
|
|
479
|
+
</View>
|
|
480
|
+
);
|
|
481
|
+
|
|
482
|
+
// ---------------------------------------------------------------------------
|
|
483
|
+
// QR view
|
|
484
|
+
// ---------------------------------------------------------------------------
|
|
485
|
+
|
|
486
|
+
interface QrViewProps {
|
|
487
|
+
snapshot: AccountDialogSnapshot;
|
|
488
|
+
theme: Theme;
|
|
489
|
+
t: Translate;
|
|
490
|
+
onRetry: () => void;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
const QrView: React.FC<QrViewProps> = ({ snapshot, theme, t, onRetry }) => {
|
|
494
|
+
const { signIn } = snapshot;
|
|
495
|
+
|
|
496
|
+
if (signIn.phase === 'error') {
|
|
497
|
+
return (
|
|
498
|
+
<View style={styles.centeredBlock}>
|
|
499
|
+
<Text style={[styles.errorText, { color: theme.colors.error }]}>
|
|
500
|
+
{signIn.error || 'Sign-in failed. Please try again.'}
|
|
501
|
+
</Text>
|
|
502
|
+
<Button variant="secondary" onPress={onRetry} style={styles.secondaryButton}>
|
|
503
|
+
{t('common.actions.tryAgain') || 'Try again'}
|
|
504
|
+
</Button>
|
|
505
|
+
</View>
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
if (signIn.phase === 'starting' || signIn.phase === 'authorized' || !signIn.qrPayload) {
|
|
510
|
+
const label =
|
|
511
|
+
signIn.phase === 'authorized'
|
|
512
|
+
? t('signin.status.signingIn') || 'Signing in…'
|
|
513
|
+
: t('accountSwitcher.loading') || 'Preparing sign-in…';
|
|
514
|
+
return (
|
|
515
|
+
<View style={styles.centeredBlock}>
|
|
516
|
+
<MaterialCommunityIcons name="loading" size={26} color={theme.colors.primary} />
|
|
517
|
+
<Text style={[styles.mutedText, { color: theme.colors.textSecondary }]}>{label}</Text>
|
|
518
|
+
</View>
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
return (
|
|
523
|
+
<View style={styles.centeredBlock}>
|
|
524
|
+
<View style={styles.qrPlate}>
|
|
525
|
+
<QRCode value={signIn.qrPayload} size={QR_SIZE} backgroundColor={QR_PLATE_BG} color={QR_FOREGROUND} />
|
|
526
|
+
</View>
|
|
527
|
+
<Text style={[styles.mutedText, { color: theme.colors.textSecondary }]}>
|
|
528
|
+
{t('accountSwitcher.scanWithOxy') || 'Scan with any Oxy app and approve.'}
|
|
529
|
+
</Text>
|
|
530
|
+
</View>
|
|
531
|
+
);
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
// ---------------------------------------------------------------------------
|
|
535
|
+
// Copy + helpers
|
|
536
|
+
// ---------------------------------------------------------------------------
|
|
537
|
+
|
|
538
|
+
function headerCopy(
|
|
539
|
+
view: AccountDialogSnapshot['view'],
|
|
540
|
+
accountCount: number,
|
|
541
|
+
t: Translate,
|
|
542
|
+
): { title: string; subtitle: string | null } {
|
|
543
|
+
switch (view) {
|
|
544
|
+
case 'accounts':
|
|
545
|
+
return {
|
|
546
|
+
title: t('accountSwitcher.sections.yourAccounts') || 'Your accounts',
|
|
547
|
+
subtitle: t('signin.chooser.subtitle') || 'Choose which account to continue with.',
|
|
548
|
+
};
|
|
549
|
+
case 'qr':
|
|
550
|
+
return {
|
|
551
|
+
title: t('accountSwitcher.scanTitle') || 'Scan with Oxy',
|
|
552
|
+
subtitle: t('accountSwitcher.scanSubtitle') || 'Approve from your phone.',
|
|
553
|
+
};
|
|
554
|
+
default:
|
|
555
|
+
return accountCount > 0
|
|
556
|
+
? {
|
|
557
|
+
title: t('signin.addAccountTitle') || 'Add another account',
|
|
558
|
+
subtitle: t('signin.addAccountSubtitle') || 'Sign in with another account.',
|
|
559
|
+
}
|
|
560
|
+
: {
|
|
561
|
+
title: t('signin.title') || 'Sign in',
|
|
562
|
+
subtitle: t('signin.subtitle') || 'One identity for the whole ecosystem.',
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/** Current document URL on web; `undefined` where `location` is absent. */
|
|
568
|
+
function currentHref(): string | undefined {
|
|
569
|
+
const location = (globalThis as { location?: { href?: string } }).location;
|
|
570
|
+
return typeof location?.href === 'string' ? location.href : undefined;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const EMPTY_SNAPSHOT: AccountDialogSnapshot = {
|
|
574
|
+
view: 'accounts',
|
|
575
|
+
accounts: [],
|
|
576
|
+
activeAccountId: null,
|
|
577
|
+
loading: false,
|
|
578
|
+
error: null,
|
|
579
|
+
switchingAccountId: null,
|
|
580
|
+
signIn: { phase: 'idle', authorizeCode: null, qrPayload: null, expiresAt: null, error: null },
|
|
581
|
+
};
|
|
582
|
+
|
|
583
|
+
const styles = StyleSheet.create({
|
|
584
|
+
header: {
|
|
585
|
+
alignItems: 'center',
|
|
586
|
+
marginBottom: 12,
|
|
587
|
+
},
|
|
588
|
+
headerBar: {
|
|
589
|
+
width: '100%',
|
|
590
|
+
flexDirection: 'row',
|
|
591
|
+
alignItems: 'center',
|
|
592
|
+
justifyContent: 'space-between',
|
|
593
|
+
marginBottom: 8,
|
|
594
|
+
},
|
|
595
|
+
iconButton: {
|
|
596
|
+
width: 32,
|
|
597
|
+
height: 32,
|
|
598
|
+
borderRadius: 16,
|
|
599
|
+
alignItems: 'center',
|
|
600
|
+
justifyContent: 'center',
|
|
601
|
+
},
|
|
602
|
+
title: {
|
|
603
|
+
fontSize: 22,
|
|
604
|
+
fontWeight: '800',
|
|
605
|
+
letterSpacing: -0.4,
|
|
606
|
+
textAlign: 'center',
|
|
607
|
+
marginTop: 6,
|
|
608
|
+
},
|
|
609
|
+
subtitle: {
|
|
610
|
+
fontSize: 14,
|
|
611
|
+
lineHeight: 19,
|
|
612
|
+
textAlign: 'center',
|
|
613
|
+
marginTop: 4,
|
|
614
|
+
},
|
|
615
|
+
body: {
|
|
616
|
+
width: '100%',
|
|
617
|
+
},
|
|
618
|
+
bodyContent: {
|
|
619
|
+
paddingTop: 4,
|
|
620
|
+
paddingBottom: 4,
|
|
621
|
+
},
|
|
622
|
+
rows: {
|
|
623
|
+
width: '100%',
|
|
624
|
+
gap: 8,
|
|
625
|
+
},
|
|
626
|
+
accountRow: {
|
|
627
|
+
flexDirection: 'row',
|
|
628
|
+
alignItems: 'center',
|
|
629
|
+
gap: 12,
|
|
630
|
+
borderWidth: StyleSheet.hairlineWidth,
|
|
631
|
+
borderRadius: 16,
|
|
632
|
+
paddingVertical: 10,
|
|
633
|
+
paddingHorizontal: 12,
|
|
634
|
+
},
|
|
635
|
+
rowDisabled: {
|
|
636
|
+
opacity: 0.6,
|
|
637
|
+
},
|
|
638
|
+
avatarRing: {
|
|
639
|
+
borderRadius: 9999,
|
|
640
|
+
borderWidth: 2,
|
|
641
|
+
padding: 1,
|
|
642
|
+
},
|
|
643
|
+
rowMeta: {
|
|
644
|
+
flex: 1,
|
|
645
|
+
minWidth: 0,
|
|
646
|
+
},
|
|
647
|
+
rowName: {
|
|
648
|
+
fontSize: 15,
|
|
649
|
+
fontWeight: '600',
|
|
650
|
+
},
|
|
651
|
+
rowHandle: {
|
|
652
|
+
fontSize: 12.5,
|
|
653
|
+
marginTop: 1,
|
|
654
|
+
},
|
|
655
|
+
addRow: {
|
|
656
|
+
flexDirection: 'row',
|
|
657
|
+
alignItems: 'center',
|
|
658
|
+
gap: 12,
|
|
659
|
+
borderWidth: StyleSheet.hairlineWidth,
|
|
660
|
+
borderStyle: 'dashed',
|
|
661
|
+
borderRadius: 16,
|
|
662
|
+
paddingVertical: 10,
|
|
663
|
+
paddingHorizontal: 12,
|
|
664
|
+
marginTop: 8,
|
|
665
|
+
},
|
|
666
|
+
addBadge: {
|
|
667
|
+
width: ROW_AVATAR_SIZE,
|
|
668
|
+
height: ROW_AVATAR_SIZE,
|
|
669
|
+
borderRadius: 9999,
|
|
670
|
+
borderWidth: StyleSheet.hairlineWidth,
|
|
671
|
+
alignItems: 'center',
|
|
672
|
+
justifyContent: 'center',
|
|
673
|
+
},
|
|
674
|
+
footerLinks: {
|
|
675
|
+
alignItems: 'center',
|
|
676
|
+
marginTop: 16,
|
|
677
|
+
},
|
|
678
|
+
linkText: {
|
|
679
|
+
fontSize: 14,
|
|
680
|
+
fontWeight: '600',
|
|
681
|
+
},
|
|
682
|
+
signInBlock: {
|
|
683
|
+
width: '100%',
|
|
684
|
+
},
|
|
685
|
+
primaryButton: {
|
|
686
|
+
width: '100%',
|
|
687
|
+
borderRadius: 14,
|
|
688
|
+
marginTop: 8,
|
|
689
|
+
},
|
|
690
|
+
secondaryButton: {
|
|
691
|
+
width: '100%',
|
|
692
|
+
borderRadius: 14,
|
|
693
|
+
marginTop: 10,
|
|
694
|
+
},
|
|
695
|
+
passwordLink: {
|
|
696
|
+
alignSelf: 'center',
|
|
697
|
+
paddingVertical: 10,
|
|
698
|
+
marginTop: 12,
|
|
699
|
+
},
|
|
700
|
+
dividerRow: {
|
|
701
|
+
flexDirection: 'row',
|
|
702
|
+
alignItems: 'center',
|
|
703
|
+
width: '100%',
|
|
704
|
+
marginVertical: 14,
|
|
705
|
+
},
|
|
706
|
+
dividerLine: {
|
|
707
|
+
flex: 1,
|
|
708
|
+
height: StyleSheet.hairlineWidth,
|
|
709
|
+
},
|
|
710
|
+
dividerText: {
|
|
711
|
+
marginHorizontal: 12,
|
|
712
|
+
fontSize: 13,
|
|
713
|
+
},
|
|
714
|
+
centeredBlock: {
|
|
715
|
+
alignItems: 'center',
|
|
716
|
+
justifyContent: 'center',
|
|
717
|
+
gap: 12,
|
|
718
|
+
paddingVertical: 24,
|
|
719
|
+
},
|
|
720
|
+
mutedText: {
|
|
721
|
+
fontSize: 14,
|
|
722
|
+
textAlign: 'center',
|
|
723
|
+
},
|
|
724
|
+
errorText: {
|
|
725
|
+
fontSize: 14,
|
|
726
|
+
textAlign: 'center',
|
|
727
|
+
},
|
|
728
|
+
qrPlate: {
|
|
729
|
+
padding: 16,
|
|
730
|
+
borderRadius: 16,
|
|
731
|
+
backgroundColor: QR_PLATE_BG,
|
|
732
|
+
},
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
export default OxyAccountDialog;
|