@oxyhq/services 15.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 +25 -55
- 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/mutationKeys.js +1 -1
- package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js +10 -10
- package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js.map +1 -1
- package/lib/commonjs/ui/hooks/queries/queryKeys.js +1 -1
- package/lib/commonjs/ui/hooks/queries/useAccountQueries.js +7 -8
- package/lib/commonjs/ui/hooks/queries/useAccountQueries.js.map +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/ConnectedAppsScreen.js +17 -18
- package/lib/commonjs/ui/screens/ConnectedAppsScreen.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 +20 -17
- 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/mutationKeys.js +1 -1
- package/lib/module/ui/hooks/mutations/useAccountMutations.js +8 -8
- package/lib/module/ui/hooks/mutations/useAccountMutations.js.map +1 -1
- package/lib/module/ui/hooks/queries/queryKeys.js +1 -1
- package/lib/module/ui/hooks/queries/useAccountQueries.js +5 -6
- package/lib/module/ui/hooks/queries/useAccountQueries.js.map +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/ConnectedAppsScreen.js +19 -20
- package/lib/module/ui/screens/ConnectedAppsScreen.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 +6 -13
- 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 +4 -4
- package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/queries/useAccountQueries.d.ts +6 -7
- package/lib/typescript/commonjs/ui/hooks/queries/useAccountQueries.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/ConnectedAppsScreen.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/commonjs/ui/utils/storageHelpers.d.ts +7 -7
- package/lib/typescript/module/index.d.ts +6 -13
- 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 +4 -4
- package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/queries/useAccountQueries.d.ts +6 -7
- package/lib/typescript/module/ui/hooks/queries/useAccountQueries.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/ConnectedAppsScreen.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/lib/typescript/module/ui/utils/storageHelpers.d.ts +7 -7
- package/package.json +4 -4
- package/src/index.ts +23 -28
- 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/mutationKeys.ts +1 -1
- package/src/ui/hooks/mutations/useAccountMutations.ts +8 -8
- package/src/ui/hooks/queries/queryKeys.ts +1 -1
- package/src/ui/hooks/queries/useAccountQueries.ts +8 -9
- 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 +1 -5
- package/src/ui/screens/ConnectedAppsScreen.tsx +23 -24
- 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/src/ui/utils/storageHelpers.ts +7 -7
- 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,162 +0,0 @@
|
|
|
1
|
-
import type React from 'react';
|
|
2
|
-
import { View, Text, TouchableOpacity, StyleSheet, ActivityIndicator } from 'react-native';
|
|
3
|
-
import { Ionicons } from '@expo/vector-icons';
|
|
4
|
-
import { useTheme } from '@oxyhq/bloom/theme';
|
|
5
|
-
import Avatar from './Avatar';
|
|
6
|
-
import { useI18n } from '../hooks/useI18n';
|
|
7
|
-
import type { SwitchableAccount } from '../hooks/useSwitchableAccounts';
|
|
8
|
-
|
|
9
|
-
export interface SignInAccountChooserProps {
|
|
10
|
-
/** Accounts available on this device / in the caller's graph, current first. */
|
|
11
|
-
accounts: SwitchableAccount[];
|
|
12
|
-
/** Selecting a row — the active account continues, others switch into. */
|
|
13
|
-
onSelectAccount: (account: SwitchableAccount) => void;
|
|
14
|
-
/** "Use another account" → reveal the sign-in options (password / QR / add). */
|
|
15
|
-
onUseAnother: () => void;
|
|
16
|
-
/** The account id currently being switched into (shows a per-row spinner). */
|
|
17
|
-
pendingAccountId?: string | null;
|
|
18
|
-
/** Disables every row while a selection is in flight. */
|
|
19
|
-
disabled?: boolean;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Google-style account chooser (React Native). Lists every account the user can
|
|
24
|
-
* continue as — device sign-ins + linked graph accounts, from
|
|
25
|
-
* {@link SwitchableAccount} — plus a "Use another account" affordance. Rendered
|
|
26
|
-
* as the FRONT screen of the sign-in surfaces (`SignInModal` web,
|
|
27
|
-
* `OxyAuthScreen` native) whenever accounts exist; selecting a row funnels into
|
|
28
|
-
* the SAME `switchToAccount` path the account switcher uses. When no accounts
|
|
29
|
-
* exist the surfaces skip this and show the sign-in options directly.
|
|
30
|
-
*
|
|
31
|
-
* Presentational + chrome-agnostic (no modal/sheet wrapper, no data fetching) so
|
|
32
|
-
* both containers reuse it and it is unit-testable in isolation.
|
|
33
|
-
*/
|
|
34
|
-
export const SignInAccountChooser: React.FC<SignInAccountChooserProps> = ({
|
|
35
|
-
accounts,
|
|
36
|
-
onSelectAccount,
|
|
37
|
-
onUseAnother,
|
|
38
|
-
pendingAccountId,
|
|
39
|
-
disabled,
|
|
40
|
-
}) => {
|
|
41
|
-
const { colors } = useTheme();
|
|
42
|
-
const { t } = useI18n();
|
|
43
|
-
|
|
44
|
-
return (
|
|
45
|
-
<View style={styles.container}>
|
|
46
|
-
{accounts.map((account) => {
|
|
47
|
-
const isPending = pendingAccountId === account.accountId;
|
|
48
|
-
const secondary = account.email;
|
|
49
|
-
return (
|
|
50
|
-
<TouchableOpacity
|
|
51
|
-
key={account.accountId}
|
|
52
|
-
accessibilityRole="button"
|
|
53
|
-
accessibilityLabel={t('signin.chooser.continueAs', { name: account.displayName })}
|
|
54
|
-
accessibilityState={{ selected: account.isCurrent, disabled: Boolean(disabled) }}
|
|
55
|
-
onPress={() => onSelectAccount(account)}
|
|
56
|
-
disabled={disabled}
|
|
57
|
-
activeOpacity={0.7}
|
|
58
|
-
style={[
|
|
59
|
-
styles.row,
|
|
60
|
-
{ borderColor: colors.border },
|
|
61
|
-
account.isCurrent && { backgroundColor: colors.primarySubtle, borderColor: colors.primarySubtle },
|
|
62
|
-
disabled && !isPending && styles.rowDisabled,
|
|
63
|
-
]}
|
|
64
|
-
>
|
|
65
|
-
<Avatar uri={account.avatarUrl} name={account.displayName} size={44} />
|
|
66
|
-
<View style={styles.info}>
|
|
67
|
-
<Text style={[styles.name, { color: colors.text }]} numberOfLines={1}>
|
|
68
|
-
{account.displayName}
|
|
69
|
-
</Text>
|
|
70
|
-
{secondary ? (
|
|
71
|
-
<Text style={[styles.secondary, { color: colors.textSecondary }]} numberOfLines={1}>
|
|
72
|
-
{secondary}
|
|
73
|
-
</Text>
|
|
74
|
-
) : null}
|
|
75
|
-
</View>
|
|
76
|
-
{isPending ? (
|
|
77
|
-
<ActivityIndicator size="small" color={colors.primary} />
|
|
78
|
-
) : account.isCurrent ? (
|
|
79
|
-
<View style={[styles.currentPill, { backgroundColor: colors.card }]}>
|
|
80
|
-
<Ionicons name="checkmark" size={14} color={colors.primary} />
|
|
81
|
-
<Text style={[styles.currentPillText, { color: colors.primary }]}>
|
|
82
|
-
{t('signin.chooser.signedIn')}
|
|
83
|
-
</Text>
|
|
84
|
-
</View>
|
|
85
|
-
) : (
|
|
86
|
-
<Ionicons name="chevron-forward" size={20} color={colors.textSecondary} />
|
|
87
|
-
)}
|
|
88
|
-
</TouchableOpacity>
|
|
89
|
-
);
|
|
90
|
-
})}
|
|
91
|
-
|
|
92
|
-
<TouchableOpacity
|
|
93
|
-
accessibilityRole="button"
|
|
94
|
-
accessibilityLabel={t('signin.chooser.useAnother')}
|
|
95
|
-
onPress={onUseAnother}
|
|
96
|
-
disabled={disabled}
|
|
97
|
-
activeOpacity={0.7}
|
|
98
|
-
style={[styles.row, { borderColor: colors.border }, disabled && styles.rowDisabled]}
|
|
99
|
-
>
|
|
100
|
-
<View style={[styles.addIcon, { backgroundColor: colors.backgroundSecondary }]}>
|
|
101
|
-
<Ionicons name="person-add-outline" size={20} color={colors.textSecondary} />
|
|
102
|
-
</View>
|
|
103
|
-
<View style={styles.info}>
|
|
104
|
-
<Text style={[styles.name, { color: colors.text }]}>{t('signin.chooser.useAnother')}</Text>
|
|
105
|
-
</View>
|
|
106
|
-
<Ionicons name="chevron-forward" size={20} color={colors.textSecondary} />
|
|
107
|
-
</TouchableOpacity>
|
|
108
|
-
</View>
|
|
109
|
-
);
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
const styles = StyleSheet.create({
|
|
113
|
-
container: {
|
|
114
|
-
width: '100%',
|
|
115
|
-
gap: 8,
|
|
116
|
-
},
|
|
117
|
-
row: {
|
|
118
|
-
flexDirection: 'row',
|
|
119
|
-
alignItems: 'center',
|
|
120
|
-
gap: 12,
|
|
121
|
-
borderWidth: 1,
|
|
122
|
-
borderRadius: 14,
|
|
123
|
-
paddingHorizontal: 14,
|
|
124
|
-
paddingVertical: 12,
|
|
125
|
-
},
|
|
126
|
-
rowDisabled: {
|
|
127
|
-
opacity: 0.5,
|
|
128
|
-
},
|
|
129
|
-
info: {
|
|
130
|
-
flex: 1,
|
|
131
|
-
minWidth: 0,
|
|
132
|
-
},
|
|
133
|
-
name: {
|
|
134
|
-
fontSize: 15,
|
|
135
|
-
fontWeight: '600',
|
|
136
|
-
},
|
|
137
|
-
secondary: {
|
|
138
|
-
fontSize: 13,
|
|
139
|
-
marginTop: 2,
|
|
140
|
-
},
|
|
141
|
-
currentPill: {
|
|
142
|
-
flexDirection: 'row',
|
|
143
|
-
alignItems: 'center',
|
|
144
|
-
gap: 3,
|
|
145
|
-
paddingHorizontal: 8,
|
|
146
|
-
paddingVertical: 3,
|
|
147
|
-
borderRadius: 999,
|
|
148
|
-
},
|
|
149
|
-
currentPillText: {
|
|
150
|
-
fontSize: 12,
|
|
151
|
-
fontWeight: '600',
|
|
152
|
-
},
|
|
153
|
-
addIcon: {
|
|
154
|
-
width: 44,
|
|
155
|
-
height: 44,
|
|
156
|
-
borderRadius: 22,
|
|
157
|
-
alignItems: 'center',
|
|
158
|
-
justifyContent: 'center',
|
|
159
|
-
},
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
export default SignInAccountChooser;
|
|
@@ -1,530 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SignInModal — web-first centered sign-in modal.
|
|
3
|
-
*
|
|
4
|
-
* Two phases, Google-style:
|
|
5
|
-
* 1. Account chooser (FRONT screen, shown when the device/user already has
|
|
6
|
-
* accounts): pick an account to continue as — one tap switches through the
|
|
7
|
-
* SAME `switchToAccount` path the account switcher uses — or "Use another
|
|
8
|
-
* account" to reveal the sign-in options.
|
|
9
|
-
* 2. Sign-in options: the first-party password flow (identifier → password →
|
|
10
|
-
* optional 2FA, `usePasswordSignIn`) as the PRIMARY action, with the
|
|
11
|
-
* cross-app device flow (same-device deep-link + "sign in on another device"
|
|
12
|
-
* QR) as a SECONDARY option below an "or" divider.
|
|
13
|
-
*
|
|
14
|
-
* When there are no accounts the modal opens straight on the sign-in options.
|
|
15
|
-
* The device-flow machinery lives in the shared `useOxyAuthSession` hook (the
|
|
16
|
-
* native `OxyAuthScreen` consumes it too — neither container re-implements the
|
|
17
|
-
* transport). Animates with a fade + scale; per-phase content cross-fades and
|
|
18
|
-
* respects reduced motion.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
import type React from 'react';
|
|
22
|
-
import { useState, useEffect, useCallback } from 'react';
|
|
23
|
-
import { View, Text, TextInput, TouchableOpacity, StyleSheet, Modal, Linking, AccessibilityInfo } from 'react-native';
|
|
24
|
-
import Animated, {
|
|
25
|
-
useSharedValue,
|
|
26
|
-
useAnimatedStyle,
|
|
27
|
-
withTiming,
|
|
28
|
-
FadeIn,
|
|
29
|
-
} from 'react-native-reanimated';
|
|
30
|
-
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
31
|
-
import { useTheme } from '@oxyhq/bloom/theme';
|
|
32
|
-
import { Button } from '@oxyhq/bloom/button';
|
|
33
|
-
import { Loading } from '@oxyhq/bloom/loading';
|
|
34
|
-
import { toast } from '@oxyhq/bloom';
|
|
35
|
-
import { Ionicons } from '@expo/vector-icons';
|
|
36
|
-
import { isDev, logger as loggerUtil } from '@oxyhq/core';
|
|
37
|
-
import { useOxy } from '../context/OxyContext';
|
|
38
|
-
import OxyLogo from './OxyLogo';
|
|
39
|
-
import AnotherDeviceQR from './AnotherDeviceQR';
|
|
40
|
-
import SignInAccountChooser from './SignInAccountChooser';
|
|
41
|
-
import { useSwitchableAccounts, type SwitchableAccount } from '../hooks/useSwitchableAccounts';
|
|
42
|
-
import { useI18n } from '../hooks/useI18n';
|
|
43
|
-
import { useOxyAuthSession, OXY_ACCOUNTS_WEB_URL } from '../hooks/useOxyAuthSession';
|
|
44
|
-
import { usePasswordSignIn } from '../hooks/usePasswordSignIn';
|
|
45
|
-
|
|
46
|
-
// Store for modal visibility with subscription support
|
|
47
|
-
let modalVisible = false;
|
|
48
|
-
let setModalVisibleCallback: ((visible: boolean) => void) | null = null;
|
|
49
|
-
const visibilityListeners = new Set<(visible: boolean) => void>();
|
|
50
|
-
|
|
51
|
-
export const showSignInModal = () => {
|
|
52
|
-
modalVisible = true;
|
|
53
|
-
setModalVisibleCallback?.(true);
|
|
54
|
-
for (const listener of visibilityListeners) listener(true);
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
export const hideSignInModal = () => {
|
|
58
|
-
modalVisible = false;
|
|
59
|
-
setModalVisibleCallback?.(false);
|
|
60
|
-
for (const listener of visibilityListeners) listener(false);
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
export const isSignInModalVisible = () => modalVisible;
|
|
64
|
-
|
|
65
|
-
/** Subscribe to modal visibility changes */
|
|
66
|
-
export const subscribeToSignInModal = (listener: (visible: boolean) => void): (() => void) => {
|
|
67
|
-
visibilityListeners.add(listener);
|
|
68
|
-
return () => visibilityListeners.delete(listener);
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
const SignInModal: React.FC = () => {
|
|
72
|
-
const [visible, setVisible] = useState(false);
|
|
73
|
-
const insets = useSafeAreaInsets();
|
|
74
|
-
const theme = useTheme();
|
|
75
|
-
|
|
76
|
-
// Register the imperative visibility callback.
|
|
77
|
-
useEffect(() => {
|
|
78
|
-
setModalVisibleCallback = setVisible;
|
|
79
|
-
return () => {
|
|
80
|
-
setModalVisibleCallback = null;
|
|
81
|
-
};
|
|
82
|
-
}, []);
|
|
83
|
-
|
|
84
|
-
if (!visible) return null;
|
|
85
|
-
|
|
86
|
-
// Mounting the content only while visible preserves the session-created-on-open
|
|
87
|
-
// / cleaned-up-on-close behaviour and resets the phase state on every reopen.
|
|
88
|
-
return <SignInModalContent theme={theme} insets={insets} />;
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
interface SignInModalContentProps {
|
|
92
|
-
theme: ReturnType<typeof useTheme>;
|
|
93
|
-
insets: ReturnType<typeof useSafeAreaInsets>;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const SignInModalContent: React.FC<SignInModalContentProps> = ({ theme, insets }) => {
|
|
97
|
-
const { oxyServices, handleWebSession, clientId, switchToAccount } = useOxy();
|
|
98
|
-
const { t } = useI18n();
|
|
99
|
-
const { accounts } = useSwitchableAccounts();
|
|
100
|
-
|
|
101
|
-
// Phase: the chooser is the front screen whenever accounts exist AND the user
|
|
102
|
-
// has not tapped "Use another account" this session.
|
|
103
|
-
const [useAnother, setUseAnother] = useState(false);
|
|
104
|
-
const [switchingId, setSwitchingId] = useState<string | null>(null);
|
|
105
|
-
const showChooser = !useAnother && accounts.length > 0;
|
|
106
|
-
|
|
107
|
-
const { qrData, qrPayload, isLoading, error, isWaiting, openSameDeviceApproval, retry } = useOxyAuthSession(
|
|
108
|
-
oxyServices,
|
|
109
|
-
clientId,
|
|
110
|
-
handleWebSession,
|
|
111
|
-
{ onSignedIn: hideSignInModal },
|
|
112
|
-
);
|
|
113
|
-
|
|
114
|
-
// First-party password sign-in — the PRIMARY option once past the chooser.
|
|
115
|
-
const pw = usePasswordSignIn({ onSignedIn: hideSignInModal });
|
|
116
|
-
|
|
117
|
-
// Entrance animation (respects reduced motion).
|
|
118
|
-
const opacity = useSharedValue(0);
|
|
119
|
-
const scale = useSharedValue(0.96);
|
|
120
|
-
// biome-ignore lint/correctness/useExhaustiveDependencies: opacity/scale are Reanimated SharedValues (stable refs), not deps; runs once on mount.
|
|
121
|
-
useEffect(() => {
|
|
122
|
-
let cancelled = false;
|
|
123
|
-
AccessibilityInfo.isReduceMotionEnabled().then((reduced) => {
|
|
124
|
-
if (cancelled) return;
|
|
125
|
-
const duration = reduced ? 0 : 250;
|
|
126
|
-
opacity.value = withTiming(1, { duration });
|
|
127
|
-
scale.value = withTiming(1, { duration });
|
|
128
|
-
});
|
|
129
|
-
return () => {
|
|
130
|
-
cancelled = true;
|
|
131
|
-
};
|
|
132
|
-
}, []);
|
|
133
|
-
|
|
134
|
-
const backdropStyle = useAnimatedStyle(() => ({ opacity: opacity.value }));
|
|
135
|
-
const contentStyle = useAnimatedStyle(() => ({
|
|
136
|
-
opacity: opacity.value,
|
|
137
|
-
transform: [{ scale: scale.value }],
|
|
138
|
-
}));
|
|
139
|
-
|
|
140
|
-
const handleClose = useCallback(() => hideSignInModal(), []);
|
|
141
|
-
const handleCreateAccount = useCallback(() => Linking.openURL(OXY_ACCOUNTS_WEB_URL), []);
|
|
142
|
-
|
|
143
|
-
const handleSelectAccount = useCallback(async (account: SwitchableAccount) => {
|
|
144
|
-
// The active account is already signed in — selecting it just continues.
|
|
145
|
-
if (account.isCurrent) {
|
|
146
|
-
hideSignInModal();
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
if (switchingId) return;
|
|
150
|
-
setSwitchingId(account.accountId);
|
|
151
|
-
try {
|
|
152
|
-
await switchToAccount(account.accountId);
|
|
153
|
-
hideSignInModal();
|
|
154
|
-
} catch (switchError) {
|
|
155
|
-
if (isDev()) {
|
|
156
|
-
loggerUtil.warn('SignInModal: switch account failed', { component: 'SignInModal' }, switchError as unknown);
|
|
157
|
-
}
|
|
158
|
-
toast.error(t('accountSwitcher.toasts.switchFailed') || 'Failed to switch account');
|
|
159
|
-
} finally {
|
|
160
|
-
setSwitchingId(null);
|
|
161
|
-
}
|
|
162
|
-
}, [switchingId, switchToAccount, t]);
|
|
163
|
-
|
|
164
|
-
const title = showChooser ? t('signin.chooser.title') : 'Sign in to Oxy';
|
|
165
|
-
const subtitle = showChooser
|
|
166
|
-
? t('signin.chooser.subtitle')
|
|
167
|
-
: 'Continue with your Oxy identity to sign in securely.';
|
|
168
|
-
|
|
169
|
-
return (
|
|
170
|
-
<Modal visible transparent animationType="none" statusBarTranslucent onRequestClose={handleClose}>
|
|
171
|
-
<Animated.View style={[styles.backdrop, { backgroundColor: theme.colors.overlay }, backdropStyle]}>
|
|
172
|
-
<TouchableOpacity style={StyleSheet.absoluteFill} onPress={handleClose} activeOpacity={1} />
|
|
173
|
-
|
|
174
|
-
<Animated.View
|
|
175
|
-
style={[
|
|
176
|
-
styles.card,
|
|
177
|
-
{ backgroundColor: theme.colors.card, paddingTop: insets.top + 28, paddingBottom: insets.bottom + 24 },
|
|
178
|
-
contentStyle,
|
|
179
|
-
]}
|
|
180
|
-
>
|
|
181
|
-
<TouchableOpacity
|
|
182
|
-
style={[styles.closeButton, { backgroundColor: theme.colors.backgroundSecondary }]}
|
|
183
|
-
onPress={handleClose}
|
|
184
|
-
accessibilityRole="button"
|
|
185
|
-
accessibilityLabel="Close sign in"
|
|
186
|
-
>
|
|
187
|
-
<Ionicons name="close" size={22} color={theme.colors.textSecondary} />
|
|
188
|
-
</TouchableOpacity>
|
|
189
|
-
|
|
190
|
-
{/* Branded header */}
|
|
191
|
-
<View style={styles.header}>
|
|
192
|
-
<OxyLogo variant="icon" size={52} />
|
|
193
|
-
<Text className="text-foreground" style={styles.title}>{title}</Text>
|
|
194
|
-
<Text className="text-muted-foreground" style={styles.subtitle}>{subtitle}</Text>
|
|
195
|
-
</View>
|
|
196
|
-
|
|
197
|
-
{showChooser ? (
|
|
198
|
-
<Animated.View key="chooser" entering={FadeIn.duration(180)} style={styles.phase}>
|
|
199
|
-
<SignInAccountChooser
|
|
200
|
-
accounts={accounts}
|
|
201
|
-
onSelectAccount={handleSelectAccount}
|
|
202
|
-
onUseAnother={() => setUseAnother(true)}
|
|
203
|
-
pendingAccountId={switchingId}
|
|
204
|
-
disabled={switchingId !== null}
|
|
205
|
-
/>
|
|
206
|
-
</Animated.View>
|
|
207
|
-
) : (
|
|
208
|
-
<Animated.View key="signIn" entering={FadeIn.duration(180)} style={styles.phase}>
|
|
209
|
-
{/* Back to the chooser when accounts exist. */}
|
|
210
|
-
{accounts.length > 0 && (
|
|
211
|
-
<TouchableOpacity
|
|
212
|
-
onPress={() => setUseAnother(false)}
|
|
213
|
-
accessibilityRole="button"
|
|
214
|
-
style={styles.backRow}
|
|
215
|
-
>
|
|
216
|
-
<Ionicons name="chevron-back" size={18} color={theme.colors.textSecondary} />
|
|
217
|
-
<Text style={[styles.backText, { color: theme.colors.textSecondary }]}>{t('signin.chooser.title')}</Text>
|
|
218
|
-
</TouchableOpacity>
|
|
219
|
-
)}
|
|
220
|
-
|
|
221
|
-
{/* PRIMARY — first-party password sign-in. Always usable; the
|
|
222
|
-
device-flow loading/error state below never gates it. */}
|
|
223
|
-
<View style={styles.form}>
|
|
224
|
-
{pw.step === 'identifier' && (
|
|
225
|
-
<TextInput
|
|
226
|
-
style={[styles.input, { borderColor: theme.colors.border, color: theme.colors.text, backgroundColor: theme.colors.backgroundSecondary }]}
|
|
227
|
-
value={pw.identifier}
|
|
228
|
-
onChangeText={pw.setIdentifier}
|
|
229
|
-
onSubmitEditing={pw.submitIdentifier}
|
|
230
|
-
placeholder="Username or email"
|
|
231
|
-
placeholderTextColor={theme.colors.textSecondary}
|
|
232
|
-
autoCapitalize="none"
|
|
233
|
-
autoCorrect={false}
|
|
234
|
-
keyboardType="email-address"
|
|
235
|
-
returnKeyType="next"
|
|
236
|
-
accessibilityLabel="Username or email"
|
|
237
|
-
/>
|
|
238
|
-
)}
|
|
239
|
-
|
|
240
|
-
{pw.step === 'password' && (
|
|
241
|
-
<>
|
|
242
|
-
<Text style={[styles.contextText, { color: theme.colors.textSecondary }]}>{pw.identifier}</Text>
|
|
243
|
-
<TextInput
|
|
244
|
-
style={[styles.input, { borderColor: theme.colors.border, color: theme.colors.text, backgroundColor: theme.colors.backgroundSecondary }]}
|
|
245
|
-
value={pw.password}
|
|
246
|
-
onChangeText={pw.setPassword}
|
|
247
|
-
onSubmitEditing={pw.submitPassword}
|
|
248
|
-
placeholder="Password"
|
|
249
|
-
placeholderTextColor={theme.colors.textSecondary}
|
|
250
|
-
secureTextEntry
|
|
251
|
-
autoCapitalize="none"
|
|
252
|
-
autoCorrect={false}
|
|
253
|
-
returnKeyType="go"
|
|
254
|
-
accessibilityLabel="Password"
|
|
255
|
-
/>
|
|
256
|
-
</>
|
|
257
|
-
)}
|
|
258
|
-
|
|
259
|
-
{pw.step === 'twoFactor' && (
|
|
260
|
-
<TextInput
|
|
261
|
-
style={[styles.input, { borderColor: theme.colors.border, color: theme.colors.text, backgroundColor: theme.colors.backgroundSecondary }]}
|
|
262
|
-
value={pw.code}
|
|
263
|
-
onChangeText={pw.setCode}
|
|
264
|
-
onSubmitEditing={pw.submitTwoFactor}
|
|
265
|
-
placeholder={pw.useBackupCode ? 'Backup code' : '6-digit code'}
|
|
266
|
-
placeholderTextColor={theme.colors.textSecondary}
|
|
267
|
-
autoCapitalize="none"
|
|
268
|
-
autoCorrect={false}
|
|
269
|
-
keyboardType={pw.useBackupCode ? 'default' : 'number-pad'}
|
|
270
|
-
returnKeyType="go"
|
|
271
|
-
accessibilityLabel={pw.useBackupCode ? 'Backup code' : 'Two-factor code'}
|
|
272
|
-
/>
|
|
273
|
-
)}
|
|
274
|
-
|
|
275
|
-
<Button
|
|
276
|
-
variant="primary"
|
|
277
|
-
onPress={
|
|
278
|
-
pw.step === 'identifier' ? pw.submitIdentifier
|
|
279
|
-
: pw.step === 'password' ? pw.submitPassword
|
|
280
|
-
: pw.submitTwoFactor
|
|
281
|
-
}
|
|
282
|
-
loading={pw.isSubmitting}
|
|
283
|
-
disabled={pw.isSubmitting}
|
|
284
|
-
style={styles.primaryButton}
|
|
285
|
-
>
|
|
286
|
-
{pw.step === 'identifier' ? 'Continue' : pw.step === 'password' ? 'Sign in' : 'Verify'}
|
|
287
|
-
</Button>
|
|
288
|
-
|
|
289
|
-
{pw.error && (
|
|
290
|
-
<Text className="text-destructive" style={styles.formError}>{pw.error}</Text>
|
|
291
|
-
)}
|
|
292
|
-
|
|
293
|
-
{pw.step === 'twoFactor' && (
|
|
294
|
-
<TouchableOpacity onPress={() => pw.setUseBackupCode(!pw.useBackupCode)} accessibilityRole="button" style={styles.linkButton}>
|
|
295
|
-
<Text style={[styles.linkText, { color: theme.colors.primary }]}>
|
|
296
|
-
{pw.useBackupCode ? 'Use authenticator code' : 'Use a backup code'}
|
|
297
|
-
</Text>
|
|
298
|
-
</TouchableOpacity>
|
|
299
|
-
)}
|
|
300
|
-
|
|
301
|
-
{pw.step !== 'identifier' && (
|
|
302
|
-
<TouchableOpacity onPress={pw.back} accessibilityRole="button" style={styles.linkButton}>
|
|
303
|
-
<Text style={[styles.linkText, { color: theme.colors.textSecondary }]}>Back</Text>
|
|
304
|
-
</TouchableOpacity>
|
|
305
|
-
)}
|
|
306
|
-
</View>
|
|
307
|
-
|
|
308
|
-
{/* "or" divider */}
|
|
309
|
-
<View style={styles.dividerRow}>
|
|
310
|
-
<View style={[styles.dividerLine, { backgroundColor: theme.colors.border }]} />
|
|
311
|
-
<Text style={[styles.dividerText, { color: theme.colors.textSecondary }]}>or</Text>
|
|
312
|
-
<View style={[styles.dividerLine, { backgroundColor: theme.colors.border }]} />
|
|
313
|
-
</View>
|
|
314
|
-
|
|
315
|
-
{/* SECONDARY — cross-app device flow. Its loading/error state gates
|
|
316
|
-
ONLY this section; the password form above is always usable. */}
|
|
317
|
-
{isLoading ? (
|
|
318
|
-
<View style={styles.deviceLoading}>
|
|
319
|
-
<Loading size="small" style={styles.statusSpinner} />
|
|
320
|
-
<Text style={[styles.statusText, { color: theme.colors.textSecondary }]}>Preparing sign in…</Text>
|
|
321
|
-
</View>
|
|
322
|
-
) : error ? (
|
|
323
|
-
<View style={styles.errorContainer}>
|
|
324
|
-
<Text className="text-destructive" style={styles.errorText}>{error}</Text>
|
|
325
|
-
<Button variant="secondary" onPress={retry} style={styles.secondaryButton}>Try Again</Button>
|
|
326
|
-
</View>
|
|
327
|
-
) : (
|
|
328
|
-
<>
|
|
329
|
-
{qrPayload && (
|
|
330
|
-
<Button
|
|
331
|
-
variant="secondary"
|
|
332
|
-
onPress={openSameDeviceApproval}
|
|
333
|
-
icon={<OxyLogo variant="icon" size={20} fillColor={theme.colors.text} style={styles.buttonIcon} />}
|
|
334
|
-
style={styles.secondaryButton}
|
|
335
|
-
>
|
|
336
|
-
Sign in with the Oxy app
|
|
337
|
-
</Button>
|
|
338
|
-
)}
|
|
339
|
-
|
|
340
|
-
{isWaiting && (
|
|
341
|
-
<View style={styles.statusContainer}>
|
|
342
|
-
<Loading size="small" style={styles.statusSpinner} />
|
|
343
|
-
<Text style={[styles.statusText, { color: theme.colors.textSecondary }]}>Waiting for authorization…</Text>
|
|
344
|
-
</View>
|
|
345
|
-
)}
|
|
346
|
-
|
|
347
|
-
<View style={styles.qrSection}>
|
|
348
|
-
<AnotherDeviceQR qrData={qrData} qrPayload={qrPayload} />
|
|
349
|
-
</View>
|
|
350
|
-
</>
|
|
351
|
-
)}
|
|
352
|
-
</Animated.View>
|
|
353
|
-
)}
|
|
354
|
-
|
|
355
|
-
{/* Footer — create an account */}
|
|
356
|
-
<View style={styles.footer}>
|
|
357
|
-
<Text style={styles.footerText} className="text-muted-foreground">
|
|
358
|
-
Don't have an Oxy account?{' '}
|
|
359
|
-
</Text>
|
|
360
|
-
<TouchableOpacity onPress={handleCreateAccount} accessibilityRole="link">
|
|
361
|
-
<Text style={styles.footerLink} className="text-primary">Create one</Text>
|
|
362
|
-
</TouchableOpacity>
|
|
363
|
-
</View>
|
|
364
|
-
</Animated.View>
|
|
365
|
-
</Animated.View>
|
|
366
|
-
</Modal>
|
|
367
|
-
);
|
|
368
|
-
};
|
|
369
|
-
|
|
370
|
-
const styles = StyleSheet.create({
|
|
371
|
-
backdrop: {
|
|
372
|
-
flex: 1,
|
|
373
|
-
justifyContent: 'center',
|
|
374
|
-
alignItems: 'center',
|
|
375
|
-
},
|
|
376
|
-
card: {
|
|
377
|
-
width: '100%',
|
|
378
|
-
maxWidth: 420,
|
|
379
|
-
alignItems: 'center',
|
|
380
|
-
paddingHorizontal: 24,
|
|
381
|
-
borderRadius: 28,
|
|
382
|
-
},
|
|
383
|
-
closeButton: {
|
|
384
|
-
position: 'absolute',
|
|
385
|
-
top: 16,
|
|
386
|
-
right: 16,
|
|
387
|
-
width: 36,
|
|
388
|
-
height: 36,
|
|
389
|
-
borderRadius: 18,
|
|
390
|
-
justifyContent: 'center',
|
|
391
|
-
alignItems: 'center',
|
|
392
|
-
zIndex: 10,
|
|
393
|
-
},
|
|
394
|
-
header: {
|
|
395
|
-
alignItems: 'center',
|
|
396
|
-
marginBottom: 24,
|
|
397
|
-
},
|
|
398
|
-
title: {
|
|
399
|
-
fontSize: 26,
|
|
400
|
-
fontWeight: '800',
|
|
401
|
-
letterSpacing: -0.5,
|
|
402
|
-
marginTop: 16,
|
|
403
|
-
textAlign: 'center',
|
|
404
|
-
},
|
|
405
|
-
subtitle: {
|
|
406
|
-
fontSize: 15,
|
|
407
|
-
lineHeight: 21,
|
|
408
|
-
marginTop: 8,
|
|
409
|
-
textAlign: 'center',
|
|
410
|
-
},
|
|
411
|
-
phase: {
|
|
412
|
-
width: '100%',
|
|
413
|
-
},
|
|
414
|
-
backRow: {
|
|
415
|
-
flexDirection: 'row',
|
|
416
|
-
alignItems: 'center',
|
|
417
|
-
gap: 4,
|
|
418
|
-
alignSelf: 'flex-start',
|
|
419
|
-
paddingVertical: 6,
|
|
420
|
-
marginBottom: 8,
|
|
421
|
-
},
|
|
422
|
-
backText: {
|
|
423
|
-
fontSize: 14,
|
|
424
|
-
fontWeight: '600',
|
|
425
|
-
},
|
|
426
|
-
primaryButton: {
|
|
427
|
-
width: '100%',
|
|
428
|
-
borderRadius: 12,
|
|
429
|
-
},
|
|
430
|
-
secondaryButton: {
|
|
431
|
-
width: '100%',
|
|
432
|
-
borderRadius: 12,
|
|
433
|
-
marginTop: 12,
|
|
434
|
-
},
|
|
435
|
-
buttonIcon: {
|
|
436
|
-
marginRight: 10,
|
|
437
|
-
},
|
|
438
|
-
form: {
|
|
439
|
-
width: '100%',
|
|
440
|
-
},
|
|
441
|
-
input: {
|
|
442
|
-
width: '100%',
|
|
443
|
-
borderWidth: 1,
|
|
444
|
-
borderRadius: 12,
|
|
445
|
-
paddingHorizontal: 16,
|
|
446
|
-
paddingVertical: 13,
|
|
447
|
-
fontSize: 15,
|
|
448
|
-
marginBottom: 12,
|
|
449
|
-
},
|
|
450
|
-
contextText: {
|
|
451
|
-
fontSize: 14,
|
|
452
|
-
textAlign: 'center',
|
|
453
|
-
marginBottom: 12,
|
|
454
|
-
},
|
|
455
|
-
formError: {
|
|
456
|
-
fontSize: 14,
|
|
457
|
-
textAlign: 'center',
|
|
458
|
-
marginTop: 12,
|
|
459
|
-
},
|
|
460
|
-
linkButton: {
|
|
461
|
-
alignSelf: 'center',
|
|
462
|
-
paddingVertical: 8,
|
|
463
|
-
marginTop: 8,
|
|
464
|
-
},
|
|
465
|
-
linkText: {
|
|
466
|
-
fontSize: 14,
|
|
467
|
-
fontWeight: '600',
|
|
468
|
-
},
|
|
469
|
-
dividerRow: {
|
|
470
|
-
flexDirection: 'row',
|
|
471
|
-
alignItems: 'center',
|
|
472
|
-
width: '100%',
|
|
473
|
-
marginVertical: 20,
|
|
474
|
-
},
|
|
475
|
-
dividerLine: {
|
|
476
|
-
flex: 1,
|
|
477
|
-
height: 1,
|
|
478
|
-
},
|
|
479
|
-
dividerText: {
|
|
480
|
-
marginHorizontal: 12,
|
|
481
|
-
fontSize: 13,
|
|
482
|
-
},
|
|
483
|
-
deviceLoading: {
|
|
484
|
-
flexDirection: 'row',
|
|
485
|
-
alignItems: 'center',
|
|
486
|
-
justifyContent: 'center',
|
|
487
|
-
paddingVertical: 12,
|
|
488
|
-
},
|
|
489
|
-
statusContainer: {
|
|
490
|
-
flexDirection: 'row',
|
|
491
|
-
alignItems: 'center',
|
|
492
|
-
marginTop: 20,
|
|
493
|
-
},
|
|
494
|
-
statusSpinner: {
|
|
495
|
-
flex: undefined,
|
|
496
|
-
},
|
|
497
|
-
statusText: {
|
|
498
|
-
marginLeft: 8,
|
|
499
|
-
fontSize: 14,
|
|
500
|
-
},
|
|
501
|
-
qrSection: {
|
|
502
|
-
width: '100%',
|
|
503
|
-
marginTop: 24,
|
|
504
|
-
},
|
|
505
|
-
errorContainer: {
|
|
506
|
-
alignItems: 'center',
|
|
507
|
-
paddingVertical: 20,
|
|
508
|
-
width: '100%',
|
|
509
|
-
},
|
|
510
|
-
errorText: {
|
|
511
|
-
fontSize: 14,
|
|
512
|
-
textAlign: 'center',
|
|
513
|
-
marginBottom: 16,
|
|
514
|
-
},
|
|
515
|
-
footer: {
|
|
516
|
-
flexDirection: 'row',
|
|
517
|
-
flexWrap: 'wrap',
|
|
518
|
-
justifyContent: 'center',
|
|
519
|
-
marginTop: 28,
|
|
520
|
-
},
|
|
521
|
-
footerText: {
|
|
522
|
-
fontSize: 14,
|
|
523
|
-
},
|
|
524
|
-
footerLink: {
|
|
525
|
-
fontSize: 14,
|
|
526
|
-
fontWeight: '600',
|
|
527
|
-
},
|
|
528
|
-
});
|
|
529
|
-
|
|
530
|
-
export default SignInModal;
|