@oxyhq/services 21.0.1 → 22.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/lib/commonjs/index.js +12 -0
  2. package/lib/commonjs/index.js.map +1 -1
  3. package/lib/commonjs/ui/boot/runProviderColdBoot.js +12 -2
  4. package/lib/commonjs/ui/boot/runProviderColdBoot.js.map +1 -1
  5. package/lib/commonjs/ui/components/OxyAccountDialog.js +27 -546
  6. package/lib/commonjs/ui/components/OxyAccountDialog.js.map +1 -1
  7. package/lib/commonjs/ui/components/OxyAuthChooser.js +881 -0
  8. package/lib/commonjs/ui/components/OxyAuthChooser.js.map +1 -0
  9. package/lib/commonjs/ui/components/OxyProvider.js +2 -0
  10. package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
  11. package/lib/commonjs/ui/components/oxyAuthChooserStyles.js +158 -0
  12. package/lib/commonjs/ui/components/oxyAuthChooserStyles.js.map +1 -0
  13. package/lib/commonjs/ui/context/OxyContext.js +50 -72
  14. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  15. package/lib/commonjs/ui/context/passkeyFlow.js +9 -8
  16. package/lib/commonjs/ui/context/passkeyFlow.js.map +1 -1
  17. package/lib/commonjs/ui/context/useOxyAccountGraph.js +6 -2
  18. package/lib/commonjs/ui/context/useOxyAccountGraph.js.map +1 -1
  19. package/lib/commonjs/ui/hooks/useSwitchableAccounts.js +2 -1
  20. package/lib/commonjs/ui/hooks/useSwitchableAccounts.js.map +1 -1
  21. package/lib/commonjs/ui/utils/commonsStoreLinks.js +42 -0
  22. package/lib/commonjs/ui/utils/commonsStoreLinks.js.map +1 -0
  23. package/lib/commonjs/ui/utils/crossOriginRestore.js +26 -0
  24. package/lib/commonjs/ui/utils/crossOriginRestore.js.map +1 -1
  25. package/lib/module/index.js +5 -0
  26. package/lib/module/index.js.map +1 -1
  27. package/lib/module/ui/boot/runProviderColdBoot.js +13 -3
  28. package/lib/module/ui/boot/runProviderColdBoot.js.map +1 -1
  29. package/lib/module/ui/components/OxyAccountDialog.js +29 -550
  30. package/lib/module/ui/components/OxyAccountDialog.js.map +1 -1
  31. package/lib/module/ui/components/OxyAuthChooser.js +878 -0
  32. package/lib/module/ui/components/OxyAuthChooser.js.map +1 -0
  33. package/lib/module/ui/components/OxyProvider.js +2 -0
  34. package/lib/module/ui/components/OxyProvider.js.map +1 -1
  35. package/lib/module/ui/components/oxyAuthChooserStyles.js +155 -0
  36. package/lib/module/ui/components/oxyAuthChooserStyles.js.map +1 -0
  37. package/lib/module/ui/context/OxyContext.js +50 -72
  38. package/lib/module/ui/context/OxyContext.js.map +1 -1
  39. package/lib/module/ui/context/passkeyFlow.js +9 -8
  40. package/lib/module/ui/context/passkeyFlow.js.map +1 -1
  41. package/lib/module/ui/context/useOxyAccountGraph.js +6 -2
  42. package/lib/module/ui/context/useOxyAccountGraph.js.map +1 -1
  43. package/lib/module/ui/hooks/useSwitchableAccounts.js +2 -1
  44. package/lib/module/ui/hooks/useSwitchableAccounts.js.map +1 -1
  45. package/lib/module/ui/utils/commonsStoreLinks.js +37 -0
  46. package/lib/module/ui/utils/commonsStoreLinks.js.map +1 -0
  47. package/lib/module/ui/utils/crossOriginRestore.js +26 -1
  48. package/lib/module/ui/utils/crossOriginRestore.js.map +1 -1
  49. package/lib/typescript/commonjs/index.d.ts +3 -1
  50. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  51. package/lib/typescript/commonjs/ui/boot/runProviderColdBoot.d.ts +6 -0
  52. package/lib/typescript/commonjs/ui/boot/runProviderColdBoot.d.ts.map +1 -1
  53. package/lib/typescript/commonjs/ui/components/OxyAccountDialog.d.ts +10 -20
  54. package/lib/typescript/commonjs/ui/components/OxyAccountDialog.d.ts.map +1 -1
  55. package/lib/typescript/commonjs/ui/components/OxyAuthChooser.d.ts +60 -0
  56. package/lib/typescript/commonjs/ui/components/OxyAuthChooser.d.ts.map +1 -0
  57. package/lib/typescript/commonjs/ui/components/OxyProvider.d.ts.map +1 -1
  58. package/lib/typescript/commonjs/ui/components/oxyAuthChooserStyles.d.ts +148 -0
  59. package/lib/typescript/commonjs/ui/components/oxyAuthChooserStyles.d.ts.map +1 -0
  60. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts +1 -1
  61. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
  62. package/lib/typescript/commonjs/ui/context/oxyContextTypes.d.ts +22 -27
  63. package/lib/typescript/commonjs/ui/context/oxyContextTypes.d.ts.map +1 -1
  64. package/lib/typescript/commonjs/ui/context/passkeyFlow.d.ts +15 -4
  65. package/lib/typescript/commonjs/ui/context/passkeyFlow.d.ts.map +1 -1
  66. package/lib/typescript/commonjs/ui/context/useOxyAccountGraph.d.ts +1 -0
  67. package/lib/typescript/commonjs/ui/context/useOxyAccountGraph.d.ts.map +1 -1
  68. package/lib/typescript/commonjs/ui/hooks/useSwitchableAccounts.d.ts.map +1 -1
  69. package/lib/typescript/commonjs/ui/types/navigation.d.ts +8 -0
  70. package/lib/typescript/commonjs/ui/types/navigation.d.ts.map +1 -1
  71. package/lib/typescript/commonjs/ui/utils/commonsStoreLinks.d.ts +24 -0
  72. package/lib/typescript/commonjs/ui/utils/commonsStoreLinks.d.ts.map +1 -0
  73. package/lib/typescript/commonjs/ui/utils/crossOriginRestore.d.ts +19 -0
  74. package/lib/typescript/commonjs/ui/utils/crossOriginRestore.d.ts.map +1 -1
  75. package/lib/typescript/module/index.d.ts +3 -1
  76. package/lib/typescript/module/index.d.ts.map +1 -1
  77. package/lib/typescript/module/ui/boot/runProviderColdBoot.d.ts +6 -0
  78. package/lib/typescript/module/ui/boot/runProviderColdBoot.d.ts.map +1 -1
  79. package/lib/typescript/module/ui/components/OxyAccountDialog.d.ts +10 -20
  80. package/lib/typescript/module/ui/components/OxyAccountDialog.d.ts.map +1 -1
  81. package/lib/typescript/module/ui/components/OxyAuthChooser.d.ts +60 -0
  82. package/lib/typescript/module/ui/components/OxyAuthChooser.d.ts.map +1 -0
  83. package/lib/typescript/module/ui/components/OxyProvider.d.ts.map +1 -1
  84. package/lib/typescript/module/ui/components/oxyAuthChooserStyles.d.ts +148 -0
  85. package/lib/typescript/module/ui/components/oxyAuthChooserStyles.d.ts.map +1 -0
  86. package/lib/typescript/module/ui/context/OxyContext.d.ts +1 -1
  87. package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
  88. package/lib/typescript/module/ui/context/oxyContextTypes.d.ts +22 -27
  89. package/lib/typescript/module/ui/context/oxyContextTypes.d.ts.map +1 -1
  90. package/lib/typescript/module/ui/context/passkeyFlow.d.ts +15 -4
  91. package/lib/typescript/module/ui/context/passkeyFlow.d.ts.map +1 -1
  92. package/lib/typescript/module/ui/context/useOxyAccountGraph.d.ts +1 -0
  93. package/lib/typescript/module/ui/context/useOxyAccountGraph.d.ts.map +1 -1
  94. package/lib/typescript/module/ui/hooks/useSwitchableAccounts.d.ts.map +1 -1
  95. package/lib/typescript/module/ui/types/navigation.d.ts +8 -0
  96. package/lib/typescript/module/ui/types/navigation.d.ts.map +1 -1
  97. package/lib/typescript/module/ui/utils/commonsStoreLinks.d.ts +24 -0
  98. package/lib/typescript/module/ui/utils/commonsStoreLinks.d.ts.map +1 -0
  99. package/lib/typescript/module/ui/utils/crossOriginRestore.d.ts +19 -0
  100. package/lib/typescript/module/ui/utils/crossOriginRestore.d.ts.map +1 -1
  101. package/package.json +5 -5
  102. package/src/index.ts +8 -1
  103. package/src/ui/boot/runProviderColdBoot.ts +24 -1
  104. package/src/ui/components/OxyAccountDialog.tsx +33 -571
  105. package/src/ui/components/OxyAuthChooser.tsx +883 -0
  106. package/src/ui/components/OxyProvider.tsx +2 -0
  107. package/src/ui/components/oxyAuthChooserStyles.ts +152 -0
  108. package/src/ui/context/OxyContext.tsx +56 -82
  109. package/src/ui/context/__tests__/passkeyFlow.test.ts +22 -15
  110. package/src/ui/context/oxyContextTypes.ts +27 -26
  111. package/src/ui/context/passkeyFlow.ts +17 -9
  112. package/src/ui/context/useOxyAccountGraph.ts +4 -2
  113. package/src/ui/hooks/useSwitchableAccounts.ts +1 -0
  114. package/src/ui/types/navigation.ts +8 -0
  115. package/src/ui/utils/__tests__/crossOriginRestore.test.ts +143 -0
  116. package/src/ui/utils/commonsStoreLinks.ts +38 -0
  117. package/src/ui/utils/crossOriginRestore.ts +34 -0
@@ -7,41 +7,25 @@ exports.default = void 0;
7
7
  var _react = require("react");
8
8
  var _reactNative = require("react-native");
9
9
  var _vectorIcons = require("@expo/vector-icons");
10
- var _reactNativeQrcodeSvg = _interopRequireDefault(require("react-native-qrcode-svg"));
11
- var _avatar = require("@oxyhq/bloom/avatar");
12
- var _button = require("@oxyhq/bloom/button");
13
10
  var _dialog = require("@oxyhq/bloom/dialog");
14
11
  var _typography = require("@oxyhq/bloom/typography");
15
12
  var _theme = require("@oxyhq/bloom/theme");
16
- var _core = require("@oxyhq/core");
17
- var _reactQuery = require("@tanstack/react-query");
18
13
  var _OxyContext = require("../context/OxyContext.js");
19
14
  var _useI18n = require("../hooks/useI18n.js");
20
- var _isWebBrowser = require("../utils/isWebBrowser.js");
15
+ var _OxyAuthChooser = _interopRequireDefault(require("./OxyAuthChooser.js"));
21
16
  var _LogoIcon = require("./logo/LogoIcon.js");
22
17
  var _jsxRuntime = require("react/jsx-runtime");
23
18
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
24
19
  /**
25
20
  * OxyAccountDialog — the ONE unified account dialog for `@oxyhq/services`.
26
21
  *
27
- * A thin React Native binding over the headless `AccountDialogController` in
28
- * `@oxyhq/core` (bound via `useSyncExternalStore`), presented as a Bloom `<Dialog>`.
29
- * One surface for account switching and sign-in data and state machine live in core.
30
- *
31
- * Views (from `snapshot.view`):
32
- * - `accounts` the unified `SwitchableAccount[]` list (device sign-ins graph
33
- * accounts). Tapping a row switches through `controller.switchTo(accountId)`
34
- * (the uniform switch), the active row is flagged, and a "+ Add account" row
35
- * opens the sign-in entry. This is what `ProfileButton` opens.
36
- * - `add` / `signin` — the sign-in entry: primary "Sign in with Oxy" (device
37
- * flow), "Scan a QR", and a secondary "Use a password" hand-off that opens
38
- * auth.oxy.so (password + 2FA are NOT in the SDK).
39
- * - `qr` — the cross-device QR handoff + its waiting state.
40
- *
41
- * Per-account color re-theming uses Bloom's `APP_COLOR_PRESETS` + `BloomColorScope`
42
- * (same visual language auth.oxy.so uses). Base theming is `useTheme()` + a
43
- * `StyleSheet`, so the dialog renders correctly in EVERY consumer — including apps
44
- * that do not use NativeWind (e.g. the accounts app).
22
+ * Bloom `<Dialog>` chrome (header, back button, placement, scroll body) around
23
+ * the headless chooser logic, which lives in `OxyAuthChooser` — the account
24
+ * switcher, sign-in, sign-up, and QR views are ALL there, extracted so the
25
+ * same chooser can be mounted bare by a future host with no Dialog chrome
26
+ * (e.g. an auth.oxy.so hub page driving the cross-origin passkey popup, b2).
27
+ * This file owns ONLY the chrome: the title/subtitle per view, the back
28
+ * button, and the Bloom `<Dialog>` surface itself.
45
29
  *
46
30
  * The surface is Bloom's `<Dialog>` (`@oxyhq/bloom/dialog`) with a responsive
47
31
  * `placement` — a bottom sheet on narrow viewports, a centered card on wide ones.
@@ -57,32 +41,11 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
57
41
  * Backdrop / swipe-to-dismiss is disabled (`dismissOnBackdrop={false}`) on purpose:
58
42
  * Bloom's controlled `bottom` placement does not fire `onClose` on a gesture or
59
43
  * backdrop dismissal, so allowing it would desync `isAccountDialogOpen` from the
60
- * sheet and block reopening. The header close button (and a successful switch)
61
- * drive `closeAccountDialog`, which flips `open` and runs the exit animation.
44
+ * sheet and block reopening. The header close button (and `OxyAuthChooser`'s
45
+ * `onComplete`) drive `closeAccountDialog`, which flips `open` and runs the exit
46
+ * animation.
62
47
  */
63
48
 
64
- /** Diameter of a row avatar. */const ROW_AVATAR_SIZE = 40;
65
- /** High-contrast QR colors — intentionally fixed (NOT themed) for scan reliability. */
66
- const QR_PLATE_BG = '#FFFFFF';
67
- const QR_FOREGROUND = '#000000';
68
- const QR_SIZE = 196;
69
-
70
- /**
71
- * Resolve an account's stored color (a named Bloom preset, e.g. `'purple'`) to
72
- * a concrete brand hex for the row accent. Falls back to the theme primary when
73
- * the account has no color or the value is not a recognized preset, so the accent
74
- * renders in EVERY consumer regardless of NativeWind availability.
75
- */
76
- function resolveAccentHex(color, fallback) {
77
- const preset = toPreset(color);
78
- return preset ? _theme.APP_COLOR_PRESETS[preset].hex : fallback;
79
- }
80
-
81
- /** Narrow a stored color string to a known `AppColorName`, or `undefined`. */
82
- function toPreset(color) {
83
- if (!color) return undefined;
84
- return _theme.APP_COLOR_NAMES.includes(color) ? color : undefined;
85
- }
86
49
  /**
87
50
  * The unified account dialog. Mounted once by `OxyProvider`; opened imperatively
88
51
  * via `useOxy().openAccountDialog(view?)` or imperative `openAccountDialog('signin')`.
@@ -91,93 +54,26 @@ const OxyAccountDialog = () => {
91
54
  const {
92
55
  accountDialogController: controller,
93
56
  isAccountDialogOpen,
94
- closeAccountDialog,
95
- showBottomSheet,
96
- logoutAll,
97
- refreshAccounts,
98
- signInWithPasskey
57
+ closeAccountDialog
99
58
  } = (0, _OxyContext.useOxy)();
100
59
  const theme = (0, _theme.useTheme)();
101
60
  const {
102
61
  t
103
62
  } = (0, _useI18n.useI18n)();
104
- const queryClient = (0, _reactQuery.useQueryClient)();
105
-
106
- // Passkey sign-in is offered ONLY on a first-party Oxy web origin (a credential
107
- // minted for `oxy.so` can only be asserted there or on a loopback dev host).
108
- // Off the web / on a non-Oxy origin the button is hidden entirely (a hub popup
109
- // for arbitrary web origins is a later phase). This is environment-static.
110
- const passkeyAvailable = (0, _react.useMemo)(() => (0, _isWebBrowser.isWebBrowser)() && (0, _core.isOxyRpOrigin)(), []);
111
- const [passkeyPending, setPasskeyPending] = (0, _react.useState)(false);
112
- const [passkeyError, setPasskeyError] = (0, _react.useState)(null);
113
- const handleSignInWithPasskey = (0, _react.useCallback)(async () => {
114
- if (passkeyPending) return;
115
- setPasskeyPending(true);
116
- setPasskeyError(null);
117
- try {
118
- await signInWithPasskey();
119
- closeAccountDialog();
120
- } catch (error) {
121
- // A cancelled/failed ceremony keeps the dialog open so the user can retry
122
- // or fall back to another method — surface a concise message, never swallow.
123
- setPasskeyError(error instanceof Error && error.message ? error.message : 'Passkey sign-in failed.');
124
- } finally {
125
- setPasskeyPending(false);
126
- }
127
- }, [passkeyPending, signInWithPasskey, closeAccountDialog]);
128
63
 
129
- // Bind the headless controller. `getSnapshot` returns a stable reference
130
- // between changes, so it is `useSyncExternalStore`-safe. Guard the no-provider
131
- // loading state (`controller` is `null`) with an inert store.
64
+ // A lightweight, header-only binding to the same controller `OxyAuthChooser`
65
+ // binds independently — cheap, and the established pattern here (
66
+ // `useSwitchableAccounts` also binds to this controller on its own).
132
67
  const subscribe = (0, _react.useCallback)(listener => controller ? controller.subscribe(listener) : () => undefined, [controller]);
133
68
  const getSnapshot = (0, _react.useCallback)(() => controller ? controller.getSnapshot() : EMPTY_SNAPSHOT, [controller]);
134
69
  const snapshot = (0, _react.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
135
- const [switching, setSwitching] = (0, _react.useState)(false);
136
- const handleSwitch = (0, _react.useCallback)(async accountId => {
137
- if (!controller || switching) return;
138
- if (accountId === snapshot.activeAccountId) {
139
- closeAccountDialog();
140
- return;
141
- }
142
- setSwitching(true);
143
- try {
144
- await controller.switchTo(accountId);
145
- // A switch succeeds unless the controller recorded an error. On success
146
- // reload the app's account graph and drop cached account-scoped data so
147
- // the new active identity re-fetches — the same side effects the
148
- // context's own `switchToAccount` performs.
149
- if (!controller.getSnapshot().error) {
150
- void refreshAccounts();
151
- queryClient.invalidateQueries();
152
- closeAccountDialog();
153
- }
154
- } finally {
155
- setSwitching(false);
156
- }
157
- }, [controller, switching, snapshot.activeAccountId, closeAccountDialog, refreshAccounts, queryClient]);
158
- const handleManage = (0, _react.useCallback)(() => {
159
- closeAccountDialog();
160
- showBottomSheet?.('ManageAccount');
161
- }, [closeAccountDialog, showBottomSheet]);
162
- const handleSignOutAll = (0, _react.useCallback)(() => {
163
- closeAccountDialog();
164
- void logoutAll();
165
- }, [closeAccountDialog, logoutAll]);
166
- const handlers = (0, _react.useMemo)(() => ({
167
- onSwitch: accountId => {
168
- void handleSwitch(accountId);
169
- },
170
- onAdd: () => controller?.add(),
171
- onManage: handleManage,
172
- onClose: closeAccountDialog
173
- }), [handleSwitch, controller, handleManage, closeAccountDialog]);
174
70
  if (!controller) {
175
71
  return null;
176
72
  }
177
73
  const {
178
74
  view
179
75
  } = snapshot;
180
- const showBack = view === 'qr' || view === 'add' && snapshot.accounts.length > 0;
76
+ const showBack = view === 'qr' || view === 'signup' || view === 'add' && snapshot.accounts.length > 0;
181
77
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_dialog.Dialog, {
182
78
  open: isAccountDialogOpen,
183
79
  onClose: closeAccountDialog,
@@ -199,27 +95,8 @@ const OxyAccountDialog = () => {
199
95
  style: styles.body,
200
96
  contentContainerStyle: styles.bodyContent,
201
97
  showsVerticalScrollIndicator: false,
202
- children: view === 'accounts' ? /*#__PURE__*/(0, _jsxRuntime.jsx)(AccountsView, {
203
- snapshot: snapshot,
204
- theme: theme,
205
- t: t,
206
- handlers: handlers
207
- }) : view === 'qr' ? /*#__PURE__*/(0, _jsxRuntime.jsx)(QrView, {
208
- snapshot: snapshot,
209
- theme: theme,
210
- t: t,
211
- onRetry: () => void controller.showQr()
212
- }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(SignInView, {
213
- snapshot: snapshot,
214
- theme: theme,
215
- t: t,
216
- handlers: handlers,
217
- onSignInWithOxy: () => void controller.signInWithOxy(),
218
- onScanQr: () => void controller.showQr(),
219
- onUsePassword: () => void controller.openPasswordAtOxyAuth(),
220
- onSignInWithPasskey: passkeyAvailable ? () => void handleSignInWithPasskey() : undefined,
221
- passkeyPending: passkeyPending,
222
- passkeyError: passkeyError
98
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_OxyAuthChooser.default, {
99
+ onComplete: closeAccountDialog
223
100
  })
224
101
  })]
225
102
  });
@@ -291,291 +168,6 @@ const DialogHeader = ({
291
168
  });
292
169
  };
293
170
 
294
- // ---------------------------------------------------------------------------
295
- // Accounts view
296
- // ---------------------------------------------------------------------------
297
-
298
- const AccountsView = ({
299
- snapshot,
300
- theme,
301
- t,
302
- handlers
303
- }) => {
304
- if (snapshot.loading && snapshot.accounts.length === 0) {
305
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
306
- style: styles.centeredBlock,
307
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.MaterialCommunityIcons, {
308
- name: "loading",
309
- size: 24,
310
- color: theme.colors.textSecondary
311
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_typography.Text, {
312
- style: [styles.mutedText, {
313
- color: theme.colors.textSecondary
314
- }],
315
- children: t('accountSwitcher.loading') || 'Loading accounts…'
316
- })]
317
- });
318
- }
319
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
320
- style: styles.rows,
321
- children: [snapshot.accounts.map(account => /*#__PURE__*/(0, _jsxRuntime.jsx)(AccountRow, {
322
- account: account,
323
- theme: theme,
324
- switching: snapshot.switchingAccountId === account.accountId,
325
- disabled: snapshot.switchingAccountId !== null,
326
- onPress: () => handlers.onSwitch(account.accountId)
327
- }, account.accountId)), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Pressable, {
328
- style: [styles.addRow, {
329
- borderColor: theme.colors.border
330
- }],
331
- onPress: handlers.onAdd,
332
- accessibilityRole: "button",
333
- accessibilityLabel: t('signin.addAccountTitle') || 'Add another account',
334
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
335
- style: [styles.addBadge, {
336
- borderColor: theme.colors.border
337
- }],
338
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.MaterialCommunityIcons, {
339
- name: "plus",
340
- size: 20,
341
- color: theme.colors.textSecondary
342
- })
343
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_typography.Text, {
344
- style: [styles.rowName, {
345
- color: theme.colors.textSecondary
346
- }],
347
- children: t('signin.addAccountTitle') || 'Add another account'
348
- })]
349
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
350
- style: styles.footerLinks,
351
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
352
- onPress: handlers.onManage,
353
- accessibilityRole: "button",
354
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_typography.Text, {
355
- style: [styles.linkText, {
356
- color: theme.colors.primary
357
- }],
358
- children: t('accountMenu.manage') || 'Manage accounts'
359
- })
360
- })
361
- })]
362
- });
363
- };
364
- const AccountRow = ({
365
- account,
366
- theme,
367
- switching,
368
- disabled,
369
- onPress
370
- }) => {
371
- const accent = resolveAccentHex(account.color, theme.colors.primary);
372
- const rowStyle = [styles.accountRow, {
373
- borderColor: account.isCurrent ? accent : theme.colors.border,
374
- backgroundColor: theme.colors.card
375
- }, disabled && !switching ? styles.rowDisabled : null];
376
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_theme.BloomColorScope, {
377
- colorPreset: toPreset(account.color),
378
- asChild: true,
379
- children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Pressable, {
380
- style: rowStyle,
381
- onPress: onPress,
382
- disabled: disabled,
383
- accessibilityRole: "button",
384
- accessibilityState: {
385
- selected: account.isCurrent,
386
- disabled
387
- },
388
- accessibilityLabel: account.displayName,
389
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
390
- style: [styles.avatarRing, {
391
- borderColor: account.isCurrent ? accent : 'transparent'
392
- }],
393
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_avatar.Avatar, {
394
- source: account.avatarUrl ?? undefined,
395
- variant: "thumb",
396
- name: account.displayName,
397
- size: ROW_AVATAR_SIZE
398
- })
399
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
400
- style: styles.rowMeta,
401
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_typography.Text, {
402
- style: [styles.rowName, {
403
- color: theme.colors.text
404
- }],
405
- numberOfLines: 1,
406
- children: account.displayName
407
- }), account.email ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_typography.Text, {
408
- style: [styles.rowHandle, {
409
- color: theme.colors.textSecondary
410
- }],
411
- numberOfLines: 1,
412
- children: account.email
413
- }) : null]
414
- }), switching ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.MaterialCommunityIcons, {
415
- name: "loading",
416
- size: 20,
417
- color: accent
418
- }) : account.isCurrent ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.MaterialCommunityIcons, {
419
- name: "check-circle",
420
- size: 20,
421
- color: accent
422
- }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.MaterialCommunityIcons, {
423
- name: "chevron-right",
424
- size: 20,
425
- color: theme.colors.textSecondary
426
- })]
427
- })
428
- });
429
- };
430
-
431
- // ---------------------------------------------------------------------------
432
- // Sign-in view (add / signin)
433
- // ---------------------------------------------------------------------------
434
-
435
- const SignInView = ({
436
- snapshot,
437
- theme,
438
- t,
439
- handlers,
440
- onSignInWithOxy,
441
- onScanQr,
442
- onUsePassword,
443
- onSignInWithPasskey,
444
- passkeyPending,
445
- passkeyError
446
- }) => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
447
- style: styles.signInBlock,
448
- children: [snapshot.accounts.length > 0 ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
449
- style: styles.rows,
450
- children: [snapshot.accounts.map(account => /*#__PURE__*/(0, _jsxRuntime.jsx)(AccountRow, {
451
- account: account,
452
- theme: theme,
453
- switching: snapshot.switchingAccountId === account.accountId,
454
- disabled: snapshot.switchingAccountId !== null,
455
- onPress: () => handlers.onSwitch(account.accountId)
456
- }, account.accountId)), /*#__PURE__*/(0, _jsxRuntime.jsx)(Dividerish, {
457
- theme: theme,
458
- label: t('signin.or') || 'or'
459
- })]
460
- }) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(_button.Button, {
461
- variant: "primary",
462
- onPress: onSignInWithOxy,
463
- style: styles.primaryButton,
464
- children: "Sign in with Oxy"
465
- }), onSignInWithPasskey ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_button.Button, {
466
- variant: "secondary",
467
- onPress: onSignInWithPasskey,
468
- disabled: passkeyPending,
469
- style: styles.secondaryButton,
470
- testID: "passkey-signin-button",
471
- children: passkeyPending ? t('accountSwitcher.passkeySigningIn') || 'Signing in…' : t('accountSwitcher.signInWithPasskey') || 'Sign in with a passkey'
472
- }) : null, passkeyError ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_typography.Text, {
473
- style: [styles.errorText, {
474
- color: theme.colors.error
475
- }],
476
- children: passkeyError
477
- }) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(_button.Button, {
478
- variant: "secondary",
479
- onPress: onScanQr,
480
- style: styles.secondaryButton,
481
- children: t('accountSwitcher.scanQr') || 'Scan a QR from another device'
482
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
483
- onPress: onUsePassword,
484
- accessibilityRole: "button",
485
- style: styles.passwordLink,
486
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_typography.Text, {
487
- style: [styles.linkText, {
488
- color: theme.colors.textSecondary
489
- }],
490
- children: "Use a password instead"
491
- })
492
- })]
493
- });
494
- const Dividerish = ({
495
- theme,
496
- label
497
- }) => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
498
- style: styles.dividerRow,
499
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
500
- style: [styles.dividerLine, {
501
- backgroundColor: theme.colors.border
502
- }]
503
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_typography.Text, {
504
- style: [styles.dividerText, {
505
- color: theme.colors.textSecondary
506
- }],
507
- children: label
508
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
509
- style: [styles.dividerLine, {
510
- backgroundColor: theme.colors.border
511
- }]
512
- })]
513
- });
514
-
515
- // ---------------------------------------------------------------------------
516
- // QR view
517
- // ---------------------------------------------------------------------------
518
-
519
- const QrView = ({
520
- snapshot,
521
- theme,
522
- t,
523
- onRetry
524
- }) => {
525
- const {
526
- signIn
527
- } = snapshot;
528
- if (signIn.phase === 'error') {
529
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
530
- style: styles.centeredBlock,
531
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_typography.Text, {
532
- style: [styles.errorText, {
533
- color: theme.colors.error
534
- }],
535
- children: signIn.error || 'Sign-in failed. Please try again.'
536
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_button.Button, {
537
- variant: "secondary",
538
- onPress: onRetry,
539
- style: styles.secondaryButton,
540
- children: t('common.actions.tryAgain') || 'Try again'
541
- })]
542
- });
543
- }
544
- if (signIn.phase === 'starting' || signIn.phase === 'authorized' || !signIn.qrPayload) {
545
- const label = signIn.phase === 'authorized' ? t('signin.status.signingIn') || 'Signing in…' : t('accountSwitcher.loading') || 'Preparing sign-in…';
546
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
547
- style: styles.centeredBlock,
548
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.MaterialCommunityIcons, {
549
- name: "loading",
550
- size: 26,
551
- color: theme.colors.primary
552
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_typography.Text, {
553
- style: [styles.mutedText, {
554
- color: theme.colors.textSecondary
555
- }],
556
- children: label
557
- })]
558
- });
559
- }
560
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
561
- style: styles.centeredBlock,
562
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
563
- style: styles.qrPlate,
564
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeQrcodeSvg.default, {
565
- value: signIn.qrPayload,
566
- size: QR_SIZE,
567
- backgroundColor: QR_PLATE_BG,
568
- color: QR_FOREGROUND
569
- })
570
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_typography.Text, {
571
- style: [styles.mutedText, {
572
- color: theme.colors.textSecondary
573
- }],
574
- children: t('accountSwitcher.scanWithOxy') || 'Scan with any Oxy app and approve.'
575
- })]
576
- });
577
- };
578
-
579
171
  // ---------------------------------------------------------------------------
580
172
  // Copy + helpers
581
173
  // ---------------------------------------------------------------------------
@@ -589,8 +181,13 @@ function headerCopy(view, accountCount, t) {
589
181
  };
590
182
  case 'qr':
591
183
  return {
592
- title: t('accountSwitcher.scanTitle') || 'Scan with Oxy',
593
- subtitle: t('accountSwitcher.scanSubtitle') || 'Approve from your phone.'
184
+ title: t('accountSwitcher.scanTitle') || 'Sign in with Oxy',
185
+ subtitle: t('accountSwitcher.scanSubtitle') || 'Scan with Commons on your phone.'
186
+ };
187
+ case 'signup':
188
+ return {
189
+ title: t('signup.title') || 'Create your account',
190
+ subtitle: t('signup.subtitle') || 'One identity for the whole ecosystem.'
594
191
  };
595
192
  default:
596
193
  return accountCount > 0 ? {
@@ -602,12 +199,6 @@ function headerCopy(view, accountCount, t) {
602
199
  };
603
200
  }
604
201
  }
605
-
606
- /** Current document URL on web; `undefined` where `location` is absent. */
607
- function currentHref() {
608
- const location = globalThis.location;
609
- return typeof location?.href === 'string' ? location.href : undefined;
610
- }
611
202
  const EMPTY_SNAPSHOT = {
612
203
  view: 'accounts',
613
204
  accounts: [],
@@ -621,7 +212,8 @@ const EMPTY_SNAPSHOT = {
621
212
  qrPayload: null,
622
213
  expiresAt: null,
623
214
  error: null
624
- }
215
+ },
216
+ commonsAvailability: 'unknown'
625
217
  };
626
218
  const styles = _reactNative.StyleSheet.create({
627
219
  header: {
@@ -661,117 +253,6 @@ const styles = _reactNative.StyleSheet.create({
661
253
  bodyContent: {
662
254
  paddingTop: 4,
663
255
  paddingBottom: 4
664
- },
665
- rows: {
666
- width: '100%',
667
- gap: 8
668
- },
669
- accountRow: {
670
- flexDirection: 'row',
671
- alignItems: 'center',
672
- gap: 12,
673
- borderWidth: _reactNative.StyleSheet.hairlineWidth,
674
- borderRadius: 16,
675
- paddingVertical: 10,
676
- paddingHorizontal: 12
677
- },
678
- rowDisabled: {
679
- opacity: 0.6
680
- },
681
- avatarRing: {
682
- borderRadius: 9999,
683
- borderWidth: 2,
684
- padding: 1
685
- },
686
- rowMeta: {
687
- flex: 1,
688
- minWidth: 0
689
- },
690
- rowName: {
691
- fontSize: 15,
692
- fontWeight: '600'
693
- },
694
- rowHandle: {
695
- fontSize: 12.5,
696
- marginTop: 1
697
- },
698
- addRow: {
699
- flexDirection: 'row',
700
- alignItems: 'center',
701
- gap: 12,
702
- borderWidth: _reactNative.StyleSheet.hairlineWidth,
703
- borderStyle: 'dashed',
704
- borderRadius: 16,
705
- paddingVertical: 10,
706
- paddingHorizontal: 12,
707
- marginTop: 8
708
- },
709
- addBadge: {
710
- width: ROW_AVATAR_SIZE,
711
- height: ROW_AVATAR_SIZE,
712
- borderRadius: 9999,
713
- borderWidth: _reactNative.StyleSheet.hairlineWidth,
714
- alignItems: 'center',
715
- justifyContent: 'center'
716
- },
717
- footerLinks: {
718
- alignItems: 'center',
719
- marginTop: 16
720
- },
721
- linkText: {
722
- fontSize: 14,
723
- fontWeight: '600'
724
- },
725
- signInBlock: {
726
- width: '100%'
727
- },
728
- primaryButton: {
729
- width: '100%',
730
- borderRadius: 14,
731
- marginTop: 8
732
- },
733
- secondaryButton: {
734
- width: '100%',
735
- borderRadius: 14,
736
- marginTop: 10
737
- },
738
- passwordLink: {
739
- alignSelf: 'center',
740
- paddingVertical: 10,
741
- marginTop: 12
742
- },
743
- dividerRow: {
744
- flexDirection: 'row',
745
- alignItems: 'center',
746
- width: '100%',
747
- marginVertical: 14
748
- },
749
- dividerLine: {
750
- flex: 1,
751
- height: _reactNative.StyleSheet.hairlineWidth
752
- },
753
- dividerText: {
754
- marginHorizontal: 12,
755
- fontSize: 13
756
- },
757
- centeredBlock: {
758
- alignItems: 'center',
759
- justifyContent: 'center',
760
- gap: 12,
761
- paddingVertical: 24
762
- },
763
- mutedText: {
764
- fontSize: 14,
765
- textAlign: 'center'
766
- },
767
- errorText: {
768
- fontSize: 14,
769
- textAlign: 'center'
770
- },
771
- qrPlate: {
772
- padding: 16,
773
- borderRadius: 16,
774
- backgroundColor: QR_PLATE_BG
775
256
  }
776
257
  });
777
258
  var _default = exports.default = OxyAccountDialog;