@oxyhq/services 22.0.0 → 22.2.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 (78) hide show
  1. package/lib/commonjs/index.js +12 -0
  2. package/lib/commonjs/index.js.map +1 -1
  3. package/lib/commonjs/ui/components/OxyAccountDialog.js +27 -529
  4. package/lib/commonjs/ui/components/OxyAccountDialog.js.map +1 -1
  5. package/lib/commonjs/ui/components/OxyAuthChooser.js +902 -0
  6. package/lib/commonjs/ui/components/OxyAuthChooser.js.map +1 -0
  7. package/lib/commonjs/ui/components/oxyAuthChooserStyles.js +158 -0
  8. package/lib/commonjs/ui/components/oxyAuthChooserStyles.js.map +1 -0
  9. package/lib/commonjs/ui/components/passkeyHubPopup.js +33 -0
  10. package/lib/commonjs/ui/components/passkeyHubPopup.js.map +1 -0
  11. package/lib/commonjs/ui/context/OxyContext.js +35 -4
  12. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  13. package/lib/commonjs/ui/context/useOxyAccountGraph.js +6 -2
  14. package/lib/commonjs/ui/context/useOxyAccountGraph.js.map +1 -1
  15. package/lib/commonjs/ui/hooks/useSwitchableAccounts.js +2 -1
  16. package/lib/commonjs/ui/hooks/useSwitchableAccounts.js.map +1 -1
  17. package/lib/commonjs/ui/utils/commonsStoreLinks.js +42 -0
  18. package/lib/commonjs/ui/utils/commonsStoreLinks.js.map +1 -0
  19. package/lib/module/index.js +5 -0
  20. package/lib/module/index.js.map +1 -1
  21. package/lib/module/ui/components/OxyAccountDialog.js +29 -533
  22. package/lib/module/ui/components/OxyAccountDialog.js.map +1 -1
  23. package/lib/module/ui/components/OxyAuthChooser.js +899 -0
  24. package/lib/module/ui/components/OxyAuthChooser.js.map +1 -0
  25. package/lib/module/ui/components/oxyAuthChooserStyles.js +155 -0
  26. package/lib/module/ui/components/oxyAuthChooserStyles.js.map +1 -0
  27. package/lib/module/ui/components/passkeyHubPopup.js +29 -0
  28. package/lib/module/ui/components/passkeyHubPopup.js.map +1 -0
  29. package/lib/module/ui/context/OxyContext.js +35 -4
  30. package/lib/module/ui/context/OxyContext.js.map +1 -1
  31. package/lib/module/ui/context/useOxyAccountGraph.js +6 -2
  32. package/lib/module/ui/context/useOxyAccountGraph.js.map +1 -1
  33. package/lib/module/ui/hooks/useSwitchableAccounts.js +2 -1
  34. package/lib/module/ui/hooks/useSwitchableAccounts.js.map +1 -1
  35. package/lib/module/ui/utils/commonsStoreLinks.js +37 -0
  36. package/lib/module/ui/utils/commonsStoreLinks.js.map +1 -0
  37. package/lib/typescript/commonjs/index.d.ts +2 -0
  38. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  39. package/lib/typescript/commonjs/ui/components/OxyAccountDialog.d.ts +10 -20
  40. package/lib/typescript/commonjs/ui/components/OxyAccountDialog.d.ts.map +1 -1
  41. package/lib/typescript/commonjs/ui/components/OxyAuthChooser.d.ts +60 -0
  42. package/lib/typescript/commonjs/ui/components/OxyAuthChooser.d.ts.map +1 -0
  43. package/lib/typescript/commonjs/ui/components/oxyAuthChooserStyles.d.ts +148 -0
  44. package/lib/typescript/commonjs/ui/components/oxyAuthChooserStyles.d.ts.map +1 -0
  45. package/lib/typescript/commonjs/ui/components/passkeyHubPopup.d.ts +23 -0
  46. package/lib/typescript/commonjs/ui/components/passkeyHubPopup.d.ts.map +1 -0
  47. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
  48. package/lib/typescript/commonjs/ui/context/useOxyAccountGraph.d.ts +1 -0
  49. package/lib/typescript/commonjs/ui/context/useOxyAccountGraph.d.ts.map +1 -1
  50. package/lib/typescript/commonjs/ui/hooks/useSwitchableAccounts.d.ts.map +1 -1
  51. package/lib/typescript/commonjs/ui/utils/commonsStoreLinks.d.ts +24 -0
  52. package/lib/typescript/commonjs/ui/utils/commonsStoreLinks.d.ts.map +1 -0
  53. package/lib/typescript/module/index.d.ts +2 -0
  54. package/lib/typescript/module/index.d.ts.map +1 -1
  55. package/lib/typescript/module/ui/components/OxyAccountDialog.d.ts +10 -20
  56. package/lib/typescript/module/ui/components/OxyAccountDialog.d.ts.map +1 -1
  57. package/lib/typescript/module/ui/components/OxyAuthChooser.d.ts +60 -0
  58. package/lib/typescript/module/ui/components/OxyAuthChooser.d.ts.map +1 -0
  59. package/lib/typescript/module/ui/components/oxyAuthChooserStyles.d.ts +148 -0
  60. package/lib/typescript/module/ui/components/oxyAuthChooserStyles.d.ts.map +1 -0
  61. package/lib/typescript/module/ui/components/passkeyHubPopup.d.ts +23 -0
  62. package/lib/typescript/module/ui/components/passkeyHubPopup.d.ts.map +1 -0
  63. package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
  64. package/lib/typescript/module/ui/context/useOxyAccountGraph.d.ts +1 -0
  65. package/lib/typescript/module/ui/context/useOxyAccountGraph.d.ts.map +1 -1
  66. package/lib/typescript/module/ui/hooks/useSwitchableAccounts.d.ts.map +1 -1
  67. package/lib/typescript/module/ui/utils/commonsStoreLinks.d.ts +24 -0
  68. package/lib/typescript/module/ui/utils/commonsStoreLinks.d.ts.map +1 -0
  69. package/package.json +11 -11
  70. package/src/index.ts +7 -0
  71. package/src/ui/components/OxyAccountDialog.tsx +33 -557
  72. package/src/ui/components/OxyAuthChooser.tsx +907 -0
  73. package/src/ui/components/oxyAuthChooserStyles.ts +152 -0
  74. package/src/ui/components/passkeyHubPopup.ts +28 -0
  75. package/src/ui/context/OxyContext.tsx +37 -3
  76. package/src/ui/context/useOxyAccountGraph.ts +4 -2
  77. package/src/ui/hooks/useSwitchableAccounts.ts +1 -0
  78. package/src/ui/utils/commonsStoreLinks.ts +38 -0
@@ -0,0 +1,902 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _react = require("react");
8
+ var _reactNative = require("react-native");
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
+ var _typography = require("@oxyhq/bloom/typography");
14
+ var _theme = require("@oxyhq/bloom/theme");
15
+ var _core = require("@oxyhq/core");
16
+ var _reactQuery = require("@tanstack/react-query");
17
+ var _OxyContext = require("../context/OxyContext.js");
18
+ var _useI18n = require("../hooks/useI18n.js");
19
+ var _isWebBrowser = require("../utils/isWebBrowser.js");
20
+ var _commonsStoreLinks = require("../utils/commonsStoreLinks.js");
21
+ var _oxyAuthChooserStyles = require("./oxyAuthChooserStyles.js");
22
+ var _jsxRuntime = require("react/jsx-runtime");
23
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
24
+ /**
25
+ * OxyAuthChooser — the account switcher + sign-in/sign-up surface, WITHOUT
26
+ * any dialog chrome.
27
+ *
28
+ * A thin React Native binding over the headless `AccountDialogController` in
29
+ * `@oxyhq/core` (bound via `useSyncExternalStore`) — the same data/state
30
+ * machine {@link OxyAccountDialog} renders, extracted so it can be mounted in
31
+ * TWO places: wrapped in Bloom's `<Dialog>` by `OxyAccountDialog` (the normal
32
+ * in-app surface), and mounted bare by a future auth.oxy.so hub page for the
33
+ * cross-origin passkey popup (b2) — same chooser, two hosts, two completion
34
+ * strategies via the `onComplete` prop. Neither host duplicates view logic.
35
+ *
36
+ * Views (from `snapshot.view`):
37
+ * - `accounts` — the unified `SwitchableAccount[]` list. Tapping a row
38
+ * switches through `controller.switchTo(accountId)`, the active row is
39
+ * flagged, and a "+ Add account" row opens the sign-in entry.
40
+ * - `add` / `signin` — the sign-in entry. On WEB this auto-starts "Sign in
41
+ * with Oxy" (native shared-keychain silently, else the QR handoff) the
42
+ * instant the view is entered — the QR is the PRIMARY web surface, not a
43
+ * button behind a click. On NATIVE it stays button-driven (unchanged):
44
+ * primary "Sign in with Oxy", secondary "Scan a QR from another device".
45
+ * Both platforms get a footer entry into `signup`.
46
+ * - `signup` — account creation. Commons is ALWAYS the priority path (owner
47
+ * mandate) and leads everywhere it appears: native shows only "Create your
48
+ * identity in Commons" (or "Get Commons" first, if not installed); web on
49
+ * a first-party Oxy origin leads with the SAME "Get Commons" CTA, with
50
+ * inline passkey creation (username + `registerWithPasskey`) offered
51
+ * UNDERNEATH as the de-emphasized "don't want to install anything"
52
+ * alternative — never co-equal, never a competing button. Anywhere else (a
53
+ * non-Oxy web origin, before the b2 hub-relay ships) shows an honest "not
54
+ * available here yet" message instead of a broken button.
55
+ * - `qr` — the cross-device Commons handoff, and the PRIMARY web sign-in
56
+ * surface. On a first-party Oxy origin this ALSO carries a "No Commons?"
57
+ * passkey link — identity is ONE thing (Commons key or browser passkey,
58
+ * same Oxy ID), so there is no separate "sign in with a passkey" button
59
+ * anywhere in this file, but passkey is explicitly the LIGHTWEIGHT
60
+ * ALTERNATIVE for someone who doesn't want to install Commons, never
61
+ * co-equal: same small-text-link subordination, same framing, wherever it
62
+ * appears. Native additionally branches on `commonsAvailability`: when
63
+ * Commons isn't installed, this leads with a "Get Commons" CTA instead of
64
+ * a same-device dead-end QR.
65
+ *
66
+ * Per-account color re-theming uses Bloom's `APP_COLOR_PRESETS` + `BloomColorScope`
67
+ * (same visual language auth.oxy.so uses). Base theming is `useTheme()` + a
68
+ * `StyleSheet`, so this renders correctly in EVERY consumer — including apps
69
+ * that do not use NativeWind (e.g. the accounts app).
70
+ */
71
+
72
+ /** Diameter of a row avatar. */const ROW_AVATAR_SIZE = 40;
73
+ /** High-contrast QR colors — intentionally fixed (NOT themed) for scan reliability. */
74
+ const QR_PLATE_BG = '#FFFFFF';
75
+ const QR_FOREGROUND = '#000000';
76
+ const QR_SIZE = 196;
77
+ /** Commons' own identity-creation deep link (mirrors the `approve`/`attest`/`card` intents). */
78
+ const COMMONS_CREATE_IDENTITY_URL = 'oxycommons://create-identity';
79
+ /**
80
+ * Resolve an account's stored color (a named Bloom preset, e.g. `'purple'`) to
81
+ * a concrete brand hex for the row accent. Falls back to the theme primary when
82
+ * the account has no color or the value is not a recognized preset, so the accent
83
+ * renders in EVERY consumer regardless of NativeWind availability.
84
+ */
85
+ function resolveAccentHex(color, fallback) {
86
+ const preset = toPreset(color);
87
+ return preset ? _theme.APP_COLOR_PRESETS[preset].hex : fallback;
88
+ }
89
+
90
+ /** Narrow a stored color string to a known `AppColorName`, or `undefined`. */
91
+ function toPreset(color) {
92
+ if (!color) return undefined;
93
+ return _theme.APP_COLOR_NAMES.includes(color) ? color : undefined;
94
+ }
95
+ /**
96
+ * The account switcher + sign-in/sign-up chooser. Mounted by `OxyAccountDialog`
97
+ * (wrapped in Bloom's `<Dialog>`) today; mountable bare by any future host that
98
+ * supplies its own `onComplete`.
99
+ */
100
+ const OxyAuthChooser = ({
101
+ onComplete
102
+ }) => {
103
+ const {
104
+ accountDialogController: controller,
105
+ showBottomSheet,
106
+ refreshAccounts,
107
+ signInWithPasskey,
108
+ registerWithPasskey,
109
+ oxyServices
110
+ } = (0, _OxyContext.useOxy)();
111
+ const theme = (0, _theme.useTheme)();
112
+ const {
113
+ t
114
+ } = (0, _useI18n.useI18n)();
115
+ const queryClient = (0, _reactQuery.useQueryClient)();
116
+
117
+ // A credential minted for `oxy.so` can only be ASSERTED there (or a loopback
118
+ // dev host) — a hard WebAuthn RP-ID boundary the browser enforces, not
119
+ // feature detection. On a first-party Oxy web origin the ceremony runs
120
+ // directly ('direct'). On any OTHER web origin (b2) it can't run locally, so
121
+ // the passkey action instead opens a popup at the auth.oxy.so passkey hub —
122
+ // where the SAME ceremony IS first-party — and relays the resulting session
123
+ // back via `AccountDialogController.startPasskeyHubSignIn` ('hub'). Native
124
+ // has neither: Commons owns identity there ('none'). `isOxyRpOrigin()` reads
125
+ // `location` once and is stable for the component's lifetime.
126
+ const passkeyMode = (0, _react.useMemo)(() => {
127
+ if (!(0, _isWebBrowser.isWebBrowser)()) return 'none';
128
+ return (0, _core.isOxyRpOrigin)() ? 'direct' : 'hub';
129
+ }, []);
130
+ const passkeyAvailable = passkeyMode !== 'none';
131
+ const [signInPasskeyPending, setSignInPasskeyPending] = (0, _react.useState)(false);
132
+ const [signInPasskeyError, setSignInPasskeyError] = (0, _react.useState)(null);
133
+ const handleSignInWithPasskey = (0, _react.useCallback)(async () => {
134
+ if (signInPasskeyPending) return;
135
+ setSignInPasskeyPending(true);
136
+ setSignInPasskeyError(null);
137
+ try {
138
+ await signInWithPasskey();
139
+ onComplete?.();
140
+ } catch (error) {
141
+ // A cancelled/failed ceremony keeps the view open so the user can retry
142
+ // or fall back to the QR — surface a concise message, never swallow.
143
+ setSignInPasskeyError(error instanceof Error && error.message ? error.message : 'Passkey sign-in failed.');
144
+ } finally {
145
+ setSignInPasskeyPending(false);
146
+ }
147
+ }, [signInPasskeyPending, signInWithPasskey, onComplete]);
148
+ const [createPasskeyPending, setCreatePasskeyPending] = (0, _react.useState)(false);
149
+ const [createPasskeyError, setCreatePasskeyError] = (0, _react.useState)(null);
150
+ const handleCreateWithPasskey = (0, _react.useCallback)(async username => {
151
+ if (createPasskeyPending) return;
152
+ setCreatePasskeyPending(true);
153
+ setCreatePasskeyError(null);
154
+ try {
155
+ await registerWithPasskey({
156
+ username
157
+ });
158
+ onComplete?.();
159
+ } catch (error) {
160
+ setCreatePasskeyError(error instanceof Error && error.message ? error.message : 'Account creation failed.');
161
+ } finally {
162
+ setCreatePasskeyPending(false);
163
+ }
164
+ }, [createPasskeyPending, registerWithPasskey, onComplete]);
165
+
166
+ // Bind the headless controller. `getSnapshot` returns a stable reference
167
+ // between changes, so it is `useSyncExternalStore`-safe. Guard the no-provider
168
+ // loading state (`controller` is `null`) with an inert store.
169
+ const subscribe = (0, _react.useCallback)(listener => controller ? controller.subscribe(listener) : () => undefined, [controller]);
170
+ const getSnapshot = (0, _react.useCallback)(() => controller ? controller.getSnapshot() : EMPTY_SNAPSHOT, [controller]);
171
+ const snapshot = (0, _react.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
172
+ const {
173
+ view
174
+ } = snapshot;
175
+
176
+ // Web: "Sign in with Oxy" auto-starts the instant the sign-in entry is
177
+ // reached — no button tap needed. `signInWithOxy()` tries the shared
178
+ // keychain first (silently, native-only) then falls to the QR handoff,
179
+ // which is what actually renders: the QR is the primary web surface, not a
180
+ // peer button among several. Gated on `phase === 'idle'` so navigating back
181
+ // to this view while a flow is already active (or re-entering it) never
182
+ // restarts/duplicates it. Native stays fully button-driven (unchanged) —
183
+ // this effect is a no-op there.
184
+ (0, _react.useEffect)(() => {
185
+ if (!controller || !(0, _isWebBrowser.isWebBrowser)()) return;
186
+ if (view !== 'signin' && view !== 'add' || snapshot.signIn.phase !== 'idle') return;
187
+ void controller.signInWithOxy();
188
+ }, [controller, view, snapshot.signIn.phase]);
189
+ const [switching, setSwitching] = (0, _react.useState)(false);
190
+ const handleSwitch = (0, _react.useCallback)(async accountId => {
191
+ if (!controller || switching) return;
192
+ if (accountId === snapshot.activeAccountId) {
193
+ onComplete?.();
194
+ return;
195
+ }
196
+ setSwitching(true);
197
+ try {
198
+ await controller.switchTo(accountId);
199
+ // A switch succeeds unless the controller recorded an error. On success
200
+ // reload the app's account graph and drop cached account-scoped data so
201
+ // the new active identity re-fetches — the same side effects the
202
+ // context's own `switchToAccount` performs.
203
+ if (!controller.getSnapshot().error) {
204
+ void refreshAccounts();
205
+ queryClient.invalidateQueries();
206
+ onComplete?.();
207
+ }
208
+ } finally {
209
+ setSwitching(false);
210
+ }
211
+ }, [controller, switching, snapshot.activeAccountId, onComplete, refreshAccounts, queryClient]);
212
+ const handleManage = (0, _react.useCallback)(() => {
213
+ onComplete?.();
214
+ showBottomSheet?.('ManageAccount');
215
+ }, [onComplete, showBottomSheet]);
216
+ const handlers = (0, _react.useMemo)(() => ({
217
+ onSwitch: accountId => {
218
+ void handleSwitch(accountId);
219
+ },
220
+ onAdd: () => controller?.add(),
221
+ onManage: handleManage
222
+ }), [handleSwitch, controller, handleManage]);
223
+ if (!controller) {
224
+ return null;
225
+ }
226
+ if (view === 'accounts') {
227
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(AccountsView, {
228
+ snapshot: snapshot,
229
+ theme: theme,
230
+ t: t,
231
+ handlers: handlers
232
+ });
233
+ }
234
+ if (view === 'qr') {
235
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(QrView, {
236
+ snapshot: snapshot,
237
+ theme: theme,
238
+ t: t,
239
+ onRetry: () => void controller.showQr(),
240
+ passkeyAvailable: passkeyAvailable,
241
+ onSignInWithPasskey: passkeyMode === 'direct' ? () => void handleSignInWithPasskey() : () => void controller.startPasskeyHubSignIn()
242
+ // The hub-popup flow's pending/error state is `snapshot.signIn`
243
+ // (already rendered elsewhere in this view) — only the DIRECT ceremony
244
+ // uses this component-local pair.
245
+ ,
246
+ passkeyPending: passkeyMode === 'direct' ? signInPasskeyPending : false,
247
+ passkeyError: passkeyMode === 'direct' ? signInPasskeyError : null,
248
+ onCreateAccount: () => controller.startSignup()
249
+ });
250
+ }
251
+ if (view === 'signup') {
252
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(SignUpView, {
253
+ snapshot: snapshot,
254
+ theme: theme,
255
+ t: t,
256
+ oxyServices: oxyServices,
257
+ passkeyMode: passkeyMode,
258
+ onCreateWithPasskey: handleCreateWithPasskey,
259
+ createPending: createPasskeyPending,
260
+ createError: createPasskeyError,
261
+ onOpenHub: () => void controller.startPasskeyHubSignIn(),
262
+ onBackToSignIn: () => controller.setView('signin')
263
+ });
264
+ }
265
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(SignInView, {
266
+ snapshot: snapshot,
267
+ theme: theme,
268
+ t: t,
269
+ handlers: handlers,
270
+ onSignInWithOxy: () => void controller.signInWithOxy(),
271
+ onScanQr: () => void controller.showQr(),
272
+ onCreateAccount: () => controller.startSignup()
273
+ });
274
+ };
275
+
276
+ // ---------------------------------------------------------------------------
277
+ // Accounts view
278
+ // ---------------------------------------------------------------------------
279
+
280
+ /**
281
+ * Inline error banner for `snapshot.error` (the account-list / switch-failure
282
+ * error — distinct from `snapshot.signIn.error`, which `QrView` already
283
+ * renders). `AccountsView` and `SignInView` previously swallowed it: a
284
+ * blocked/failed switch left the user with no feedback outside the QR view.
285
+ */
286
+ const ErrorBanner = ({
287
+ error,
288
+ theme
289
+ }) => {
290
+ if (!error) return null;
291
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
292
+ style: [_oxyAuthChooserStyles.authChooserStyles.errorBanner, {
293
+ backgroundColor: theme.colors.negativeSubtle
294
+ }],
295
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.MaterialCommunityIcons, {
296
+ name: "alert-circle-outline",
297
+ size: 18,
298
+ color: theme.colors.error
299
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_typography.Text, {
300
+ style: [_oxyAuthChooserStyles.authChooserStyles.errorBannerText, {
301
+ color: theme.colors.error
302
+ }],
303
+ children: error
304
+ })]
305
+ });
306
+ };
307
+ const AccountsView = ({
308
+ snapshot,
309
+ theme,
310
+ t,
311
+ handlers
312
+ }) => {
313
+ if (snapshot.loading && snapshot.accounts.length === 0) {
314
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
315
+ style: _oxyAuthChooserStyles.authChooserStyles.centeredBlock,
316
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.MaterialCommunityIcons, {
317
+ name: "loading",
318
+ size: 24,
319
+ color: theme.colors.textSecondary
320
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_typography.Text, {
321
+ style: [_oxyAuthChooserStyles.authChooserStyles.mutedText, {
322
+ color: theme.colors.textSecondary
323
+ }],
324
+ children: t('accountSwitcher.loading') || 'Loading accounts…'
325
+ })]
326
+ });
327
+ }
328
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
329
+ style: _oxyAuthChooserStyles.authChooserStyles.rows,
330
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(ErrorBanner, {
331
+ error: snapshot.error,
332
+ theme: theme
333
+ }), snapshot.accounts.map(account => /*#__PURE__*/(0, _jsxRuntime.jsx)(AccountRow, {
334
+ account: account,
335
+ theme: theme,
336
+ switching: snapshot.switchingAccountId === account.accountId,
337
+ disabled: snapshot.switchingAccountId !== null,
338
+ onPress: () => handlers.onSwitch(account.accountId)
339
+ }, account.accountId)), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Pressable, {
340
+ style: [_oxyAuthChooserStyles.authChooserStyles.addRow, {
341
+ borderColor: theme.colors.border
342
+ }],
343
+ onPress: handlers.onAdd,
344
+ accessibilityRole: "button",
345
+ accessibilityLabel: t('signin.addAccountTitle') || 'Add another account',
346
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
347
+ style: [_oxyAuthChooserStyles.authChooserStyles.addBadge, {
348
+ borderColor: theme.colors.border
349
+ }],
350
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.MaterialCommunityIcons, {
351
+ name: "plus",
352
+ size: 20,
353
+ color: theme.colors.textSecondary
354
+ })
355
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_typography.Text, {
356
+ style: [_oxyAuthChooserStyles.authChooserStyles.rowName, {
357
+ color: theme.colors.textSecondary
358
+ }],
359
+ children: t('signin.addAccountTitle') || 'Add another account'
360
+ })]
361
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
362
+ style: _oxyAuthChooserStyles.authChooserStyles.footerLinks,
363
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
364
+ onPress: handlers.onManage,
365
+ accessibilityRole: "button",
366
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_typography.Text, {
367
+ style: [_oxyAuthChooserStyles.authChooserStyles.linkText, {
368
+ color: theme.colors.primary
369
+ }],
370
+ children: t('accountMenu.manage') || 'Manage accounts'
371
+ })
372
+ })
373
+ })]
374
+ });
375
+ };
376
+ const AccountRow = ({
377
+ account,
378
+ theme,
379
+ switching,
380
+ disabled,
381
+ onPress
382
+ }) => {
383
+ const accent = resolveAccentHex(account.color, theme.colors.primary);
384
+ const rowStyle = [_oxyAuthChooserStyles.authChooserStyles.accountRow, {
385
+ borderColor: account.isCurrent ? accent : theme.colors.border,
386
+ backgroundColor: theme.colors.card
387
+ }, disabled && !switching ? _oxyAuthChooserStyles.authChooserStyles.rowDisabled : null];
388
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_theme.BloomColorScope, {
389
+ colorPreset: toPreset(account.color),
390
+ asChild: true,
391
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Pressable, {
392
+ style: rowStyle,
393
+ onPress: onPress,
394
+ disabled: disabled,
395
+ accessibilityRole: "button",
396
+ accessibilityState: {
397
+ selected: account.isCurrent,
398
+ disabled
399
+ },
400
+ accessibilityLabel: account.displayName,
401
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
402
+ style: [_oxyAuthChooserStyles.authChooserStyles.avatarRing, {
403
+ borderColor: account.isCurrent ? accent : 'transparent'
404
+ }],
405
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_avatar.Avatar, {
406
+ source: account.avatarUrl ?? undefined,
407
+ variant: "thumb",
408
+ name: account.displayName,
409
+ size: ROW_AVATAR_SIZE
410
+ })
411
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
412
+ style: _oxyAuthChooserStyles.authChooserStyles.rowMeta,
413
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_typography.Text, {
414
+ style: [_oxyAuthChooserStyles.authChooserStyles.rowName, {
415
+ color: theme.colors.text
416
+ }],
417
+ numberOfLines: 1,
418
+ children: account.displayName
419
+ }), account.email ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_typography.Text, {
420
+ style: [_oxyAuthChooserStyles.authChooserStyles.rowHandle, {
421
+ color: theme.colors.textSecondary
422
+ }],
423
+ numberOfLines: 1,
424
+ children: account.email
425
+ }) : null]
426
+ }), switching ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.MaterialCommunityIcons, {
427
+ name: "loading",
428
+ size: 20,
429
+ color: accent
430
+ }) : account.isCurrent ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.MaterialCommunityIcons, {
431
+ name: "check-circle",
432
+ size: 20,
433
+ color: accent
434
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.MaterialCommunityIcons, {
435
+ name: "chevron-right",
436
+ size: 20,
437
+ color: theme.colors.textSecondary
438
+ })]
439
+ })
440
+ });
441
+ };
442
+
443
+ // ---------------------------------------------------------------------------
444
+ // Sign-in view (add / signin) — NATIVE-facing today: on web the auto-start
445
+ // effect above moves the view to `qr` almost immediately, so this renders
446
+ // only for a brief instant there.
447
+ // ---------------------------------------------------------------------------
448
+
449
+ const SignInView = ({
450
+ snapshot,
451
+ theme,
452
+ t,
453
+ handlers,
454
+ onSignInWithOxy,
455
+ onScanQr,
456
+ onCreateAccount
457
+ }) => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
458
+ style: _oxyAuthChooserStyles.authChooserStyles.signInBlock,
459
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(ErrorBanner, {
460
+ error: snapshot.error,
461
+ theme: theme
462
+ }), snapshot.accounts.length > 0 ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
463
+ style: _oxyAuthChooserStyles.authChooserStyles.rows,
464
+ children: [snapshot.accounts.map(account => /*#__PURE__*/(0, _jsxRuntime.jsx)(AccountRow, {
465
+ account: account,
466
+ theme: theme,
467
+ switching: snapshot.switchingAccountId === account.accountId,
468
+ disabled: snapshot.switchingAccountId !== null,
469
+ onPress: () => handlers.onSwitch(account.accountId)
470
+ }, account.accountId)), /*#__PURE__*/(0, _jsxRuntime.jsx)(Dividerish, {
471
+ theme: theme,
472
+ label: t('signin.or') || 'or'
473
+ })]
474
+ }) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(_button.Button, {
475
+ variant: "primary",
476
+ onPress: onSignInWithOxy,
477
+ style: _oxyAuthChooserStyles.authChooserStyles.primaryButton,
478
+ children: t('accountSwitcher.signInWithOxy') || 'Sign in with Oxy'
479
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_button.Button, {
480
+ variant: "secondary",
481
+ onPress: onScanQr,
482
+ style: _oxyAuthChooserStyles.authChooserStyles.secondaryButton,
483
+ children: t('accountSwitcher.scanQr') || 'Scan a QR from another device'
484
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(SignUpFooterLink, {
485
+ theme: theme,
486
+ t: t,
487
+ onPress: onCreateAccount
488
+ })]
489
+ });
490
+ const Dividerish = ({
491
+ theme,
492
+ label
493
+ }) => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
494
+ style: _oxyAuthChooserStyles.authChooserStyles.dividerRow,
495
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
496
+ style: [_oxyAuthChooserStyles.authChooserStyles.dividerLine, {
497
+ backgroundColor: theme.colors.border
498
+ }]
499
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_typography.Text, {
500
+ style: [_oxyAuthChooserStyles.authChooserStyles.dividerText, {
501
+ color: theme.colors.textSecondary
502
+ }],
503
+ children: label
504
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
505
+ style: [_oxyAuthChooserStyles.authChooserStyles.dividerLine, {
506
+ backgroundColor: theme.colors.border
507
+ }]
508
+ })]
509
+ });
510
+
511
+ /** "New to Oxy? Create one" — shared by `SignInView` and `QrView`. */
512
+ const SignUpFooterLink = ({
513
+ theme,
514
+ t,
515
+ onPress
516
+ }) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
517
+ onPress: onPress,
518
+ accessibilityRole: "button",
519
+ style: _oxyAuthChooserStyles.authChooserStyles.footerLink,
520
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_typography.Text, {
521
+ style: [_oxyAuthChooserStyles.authChooserStyles.linkText, {
522
+ color: theme.colors.textSecondary
523
+ }],
524
+ children: t('signin.createAccountLink') || 'New to Oxy? Create one'
525
+ })
526
+ });
527
+
528
+ /** "Already have an account? Sign in" — used by `SignUpView`. */
529
+ const SignInFooterLink = ({
530
+ theme,
531
+ t,
532
+ onPress
533
+ }) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
534
+ onPress: onPress,
535
+ accessibilityRole: "button",
536
+ style: _oxyAuthChooserStyles.authChooserStyles.footerLink,
537
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_typography.Text, {
538
+ style: [_oxyAuthChooserStyles.authChooserStyles.linkText, {
539
+ color: theme.colors.textSecondary
540
+ }],
541
+ children: t('signup.backToSignInLink') || 'Already have an account? Sign in'
542
+ })
543
+ });
544
+
545
+ // ---------------------------------------------------------------------------
546
+ // QR view — the PRIMARY web sign-in surface, and the ONLY sign-in surface on
547
+ // native. Commons is ALWAYS the priority path (owner mandate): the QR here
548
+ // and the "Get Commons" CTA below are the leading content everywhere they
549
+ // appear. The passkey link is explicitly the lightweight, de-emphasized
550
+ // alternative for someone who doesn't want to install Commons — same visual
551
+ // subordination (a small text link, never a button) and the same "No
552
+ // Commons?" framing in its copy wherever it's offered.
553
+ // ---------------------------------------------------------------------------
554
+
555
+ const QrView = ({
556
+ snapshot,
557
+ theme,
558
+ t,
559
+ onRetry,
560
+ passkeyAvailable,
561
+ onSignInWithPasskey,
562
+ passkeyPending,
563
+ passkeyError,
564
+ onCreateAccount
565
+ }) => {
566
+ const {
567
+ signIn,
568
+ commonsAvailability
569
+ } = snapshot;
570
+ const [showQrAnyway, setShowQrAnyway] = (0, _react.useState)(false);
571
+ const passkeyLink = passkeyAvailable && signIn.phase !== 'authorized' ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
572
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
573
+ onPress: onSignInWithPasskey,
574
+ disabled: passkeyPending,
575
+ accessibilityRole: "button",
576
+ style: _oxyAuthChooserStyles.authChooserStyles.footerLink,
577
+ testID: "passkey-signin-link",
578
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_typography.Text, {
579
+ style: [_oxyAuthChooserStyles.authChooserStyles.linkText, {
580
+ color: theme.colors.textSecondary
581
+ }],
582
+ children: passkeyPending ? t('accountSwitcher.passkeySigningIn') || 'Signing in…' : t('accountSwitcher.useIdentityOnDevice') || 'No Commons? Use a passkey on this device instead'
583
+ })
584
+ }), passkeyError ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_typography.Text, {
585
+ style: [_oxyAuthChooserStyles.authChooserStyles.errorText, {
586
+ color: theme.colors.error
587
+ }],
588
+ children: passkeyError
589
+ }) : null]
590
+ }) : null;
591
+
592
+ // Native, Commons confirmed NOT installed: a same-device QR is a dead
593
+ // end (the user can't scan their own screen) — lead with "Get Commons"
594
+ // instead, and demote the QR to an explicit "I have it elsewhere" reveal.
595
+ if (commonsAvailability === 'unavailable' && !showQrAnyway) {
596
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
597
+ style: _oxyAuthChooserStyles.authChooserStyles.centeredBlock,
598
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_typography.Text, {
599
+ style: [_oxyAuthChooserStyles.authChooserStyles.mutedText, {
600
+ color: theme.colors.textSecondary
601
+ }],
602
+ children: t('accountSwitcher.commonsNotInstalled') || "Don't have Commons? Get the app to sign in with your Oxy ID."
603
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_button.Button, {
604
+ variant: "primary",
605
+ onPress: () => void _reactNative.Linking.openURL((0, _commonsStoreLinks.getCommonsAcquisitionUrl)(_reactNative.Platform.OS)),
606
+ style: _oxyAuthChooserStyles.authChooserStyles.primaryButton,
607
+ children: t('accountSwitcher.getCommons') || 'Get Commons'
608
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
609
+ onPress: () => setShowQrAnyway(true),
610
+ accessibilityRole: "button",
611
+ style: _oxyAuthChooserStyles.authChooserStyles.footerLink,
612
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_typography.Text, {
613
+ style: [_oxyAuthChooserStyles.authChooserStyles.linkText, {
614
+ color: theme.colors.textSecondary
615
+ }],
616
+ children: t('accountSwitcher.showQrAnyway') || 'I have Commons on another device'
617
+ })
618
+ }), passkeyLink, /*#__PURE__*/(0, _jsxRuntime.jsx)(SignUpFooterLink, {
619
+ theme: theme,
620
+ t: t,
621
+ onPress: onCreateAccount
622
+ })]
623
+ });
624
+ }
625
+ if (signIn.phase === 'error') {
626
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
627
+ style: _oxyAuthChooserStyles.authChooserStyles.centeredBlock,
628
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_typography.Text, {
629
+ style: [_oxyAuthChooserStyles.authChooserStyles.errorText, {
630
+ color: theme.colors.error
631
+ }],
632
+ children: signIn.error || 'Sign-in failed. Please try again.'
633
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_button.Button, {
634
+ variant: "secondary",
635
+ onPress: onRetry,
636
+ style: _oxyAuthChooserStyles.authChooserStyles.secondaryButton,
637
+ children: t('common.actions.tryAgain') || 'Try again'
638
+ }), passkeyLink]
639
+ });
640
+ }
641
+ if (signIn.phase === 'starting' || signIn.phase === 'authorized' || !signIn.qrPayload) {
642
+ const label = signIn.phase === 'authorized' ? t('signin.status.signingIn') || 'Signing in…' : t('accountSwitcher.loading') || 'Preparing sign-in…';
643
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
644
+ style: _oxyAuthChooserStyles.authChooserStyles.centeredBlock,
645
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.MaterialCommunityIcons, {
646
+ name: "loading",
647
+ size: 26,
648
+ color: theme.colors.primary
649
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_typography.Text, {
650
+ style: [_oxyAuthChooserStyles.authChooserStyles.mutedText, {
651
+ color: theme.colors.textSecondary
652
+ }],
653
+ children: label
654
+ })]
655
+ });
656
+ }
657
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
658
+ style: _oxyAuthChooserStyles.authChooserStyles.centeredBlock,
659
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_typography.Text, {
660
+ style: [_oxyAuthChooserStyles.authChooserStyles.qrHeadline, {
661
+ color: theme.colors.text
662
+ }],
663
+ children: t('accountSwitcher.qrHeadline') || 'Sign in with your Oxy identity'
664
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
665
+ style: _oxyAuthChooserStyles.authChooserStyles.qrPlate,
666
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeQrcodeSvg.default, {
667
+ value: signIn.qrPayload,
668
+ size: QR_SIZE,
669
+ backgroundColor: QR_PLATE_BG,
670
+ color: QR_FOREGROUND
671
+ })
672
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_typography.Text, {
673
+ style: [_oxyAuthChooserStyles.authChooserStyles.mutedText, {
674
+ color: theme.colors.textSecondary
675
+ }],
676
+ children: t('accountSwitcher.scanWithOxy') || 'Scan with Commons on your phone to continue.'
677
+ }), passkeyLink, /*#__PURE__*/(0, _jsxRuntime.jsx)(SignUpFooterLink, {
678
+ theme: theme,
679
+ t: t,
680
+ onPress: onCreateAccount
681
+ })]
682
+ });
683
+ };
684
+
685
+ // ---------------------------------------------------------------------------
686
+ // Sign-up view
687
+ // ---------------------------------------------------------------------------
688
+
689
+ /** Debounced username-availability check over the existing SDK method. */
690
+ function useUsernameAvailability(oxyServices) {
691
+ const [status, setStatus] = (0, _react.useState)('idle');
692
+ const timerRef = (0, _react.useRef)(null);
693
+ const requestSeqRef = (0, _react.useRef)(0);
694
+ const check = (0, _react.useCallback)(value => {
695
+ if (timerRef.current) clearTimeout(timerRef.current);
696
+ const seq = ++requestSeqRef.current;
697
+ if (!value || value.length < 3) {
698
+ setStatus('idle');
699
+ return;
700
+ }
701
+ setStatus('checking');
702
+ timerRef.current = setTimeout(() => {
703
+ void oxyServices.checkUsernameAvailability(value).then(result => {
704
+ if (seq !== requestSeqRef.current) return; // superseded by a newer check
705
+ setStatus(result.available ? 'available' : 'taken');
706
+ }).catch(() => {
707
+ if (seq !== requestSeqRef.current) return;
708
+ setStatus('idle'); // network/error — don't block on a stale verdict
709
+ });
710
+ }, 400);
711
+ }, [oxyServices]);
712
+ return {
713
+ status,
714
+ check
715
+ };
716
+ }
717
+ const UsernameStatusText = ({
718
+ status,
719
+ theme,
720
+ t
721
+ }) => {
722
+ if (status === 'idle') return null;
723
+ if (status === 'checking') {
724
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_typography.Text, {
725
+ style: [_oxyAuthChooserStyles.authChooserStyles.mutedText, {
726
+ color: theme.colors.textSecondary
727
+ }],
728
+ children: t('signup.usernameChecking') || 'Checking…'
729
+ });
730
+ }
731
+ if (status === 'available') {
732
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_typography.Text, {
733
+ style: [_oxyAuthChooserStyles.authChooserStyles.mutedText, {
734
+ color: theme.colors.success
735
+ }],
736
+ children: t('signup.usernameAvailable') || 'Available'
737
+ });
738
+ }
739
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_typography.Text, {
740
+ style: [_oxyAuthChooserStyles.authChooserStyles.mutedText, {
741
+ color: theme.colors.error
742
+ }],
743
+ children: t('signup.usernameTaken') || 'This username is taken'
744
+ });
745
+ };
746
+ const SignUpView = ({
747
+ snapshot,
748
+ theme,
749
+ t,
750
+ oxyServices,
751
+ passkeyMode,
752
+ onCreateWithPasskey,
753
+ createPending,
754
+ createError,
755
+ onOpenHub,
756
+ onBackToSignIn
757
+ }) => {
758
+ const [username, setUsername] = (0, _react.useState)('');
759
+ const {
760
+ status: usernameStatus,
761
+ check: checkUsername
762
+ } = useUsernameAvailability(oxyServices);
763
+ const canSubmit = username.trim().length >= 3 && usernameStatus === 'available' && !createPending;
764
+
765
+ // Native — Commons owns identity creation. Mirrors the QR view's own
766
+ // commonsAvailability branch: deep-link straight in when installed, else
767
+ // lead with the same "Get Commons" fallback.
768
+ if (!(0, _isWebBrowser.isWebBrowser)()) {
769
+ const commonsInstalled = snapshot.commonsAvailability === 'available';
770
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
771
+ style: _oxyAuthChooserStyles.authChooserStyles.centeredBlock,
772
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_button.Button, {
773
+ variant: "primary",
774
+ onPress: () => void _reactNative.Linking.openURL(commonsInstalled ? COMMONS_CREATE_IDENTITY_URL : (0, _commonsStoreLinks.getCommonsAcquisitionUrl)(_reactNative.Platform.OS)),
775
+ style: _oxyAuthChooserStyles.authChooserStyles.primaryButton,
776
+ children: commonsInstalled ? t('signup.createInCommons') || 'Create your identity in Commons' : t('accountSwitcher.getCommons') || 'Get Commons'
777
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(SignInFooterLink, {
778
+ theme: theme,
779
+ t: t,
780
+ onPress: onBackToSignIn
781
+ })]
782
+ });
783
+ }
784
+
785
+ // Web, first-party Oxy origin. Commons leads (owner mandate: it's ALWAYS
786
+ // the priority path, everywhere the two appear) with a "Get Commons" CTA;
787
+ // passkey creation is the de-emphasized alternative underneath for someone
788
+ // who doesn't want to install anything — secondary button, introduced by
789
+ // its own "No Commons?" framing, same subordination as the QR view's link.
790
+ if (passkeyMode === 'direct') {
791
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
792
+ style: _oxyAuthChooserStyles.authChooserStyles.signInBlock,
793
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_typography.Text, {
794
+ style: [_oxyAuthChooserStyles.authChooserStyles.qrHeadline, {
795
+ color: theme.colors.text
796
+ }],
797
+ children: t('signup.commonsHeadline') || 'Create your identity in Commons'
798
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_typography.Text, {
799
+ style: [_oxyAuthChooserStyles.authChooserStyles.mutedText, {
800
+ color: theme.colors.textSecondary
801
+ }],
802
+ children: t('signup.commonsExplainer') || 'Commons is your full self-custody Oxy identity — one app, works everywhere.'
803
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_button.Button, {
804
+ variant: "primary",
805
+ onPress: () => void _reactNative.Linking.openURL((0, _commonsStoreLinks.getCommonsAcquisitionUrl)(_reactNative.Platform.OS)),
806
+ style: _oxyAuthChooserStyles.authChooserStyles.primaryButton,
807
+ children: t('accountSwitcher.getCommons') || 'Get Commons'
808
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(Dividerish, {
809
+ theme: theme,
810
+ label: t('signin.or') || 'or'
811
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_typography.Text, {
812
+ style: [_oxyAuthChooserStyles.authChooserStyles.mutedText, {
813
+ color: theme.colors.textSecondary
814
+ }],
815
+ children: t('signup.passkeyAlternative') || "No Commons? Create a passkey on this device instead"
816
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TextInput, {
817
+ style: [_oxyAuthChooserStyles.authChooserStyles.usernameInput, {
818
+ borderColor: theme.colors.border,
819
+ color: theme.colors.text
820
+ }],
821
+ placeholder: t('signup.usernamePlaceholder') || 'Choose a username',
822
+ placeholderTextColor: theme.colors.textSecondary,
823
+ value: username,
824
+ autoCapitalize: "none",
825
+ autoCorrect: false,
826
+ autoComplete: "username",
827
+ onChangeText: value => {
828
+ setUsername(value);
829
+ checkUsername(value.trim());
830
+ },
831
+ testID: "signup-username-input"
832
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(UsernameStatusText, {
833
+ status: usernameStatus,
834
+ theme: theme,
835
+ t: t
836
+ }), createError ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_typography.Text, {
837
+ style: [_oxyAuthChooserStyles.authChooserStyles.errorText, {
838
+ color: theme.colors.error
839
+ }],
840
+ children: createError
841
+ }) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(_button.Button, {
842
+ variant: "secondary",
843
+ onPress: () => void onCreateWithPasskey(username.trim()),
844
+ disabled: !canSubmit,
845
+ style: _oxyAuthChooserStyles.authChooserStyles.secondaryButton,
846
+ testID: "signup-create-button",
847
+ children: createPending ? t('signup.creating') || 'Creating…' : t('signup.createAccount') || 'Create account'
848
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(SignInFooterLink, {
849
+ theme: theme,
850
+ t: t,
851
+ onPress: onBackToSignIn
852
+ })]
853
+ });
854
+ }
855
+
856
+ // Web, non-Oxy origin (b2): passkey creation can't run locally here — the
857
+ // same WebAuthn RP-ID boundary as sign-in — so this opens the SAME
858
+ // auth.oxy.so passkey hub popup `QrView`'s "No Commons?" link uses.
859
+ // Completing EITHER a sign-in OR a fresh sign-up there relays the resulting
860
+ // session back via the SAME poll/socket engine `showQr` drives.
861
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
862
+ style: _oxyAuthChooserStyles.authChooserStyles.centeredBlock,
863
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_typography.Text, {
864
+ style: [_oxyAuthChooserStyles.authChooserStyles.mutedText, {
865
+ color: theme.colors.textSecondary
866
+ }],
867
+ children: t('signup.hubExplainer') || 'Create your Oxy ID in a secure window, then come right back.'
868
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_button.Button, {
869
+ variant: "primary",
870
+ onPress: onOpenHub,
871
+ style: _oxyAuthChooserStyles.authChooserStyles.primaryButton,
872
+ children: t('signup.continueInWindow') || 'Continue in a new window'
873
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(SignInFooterLink, {
874
+ theme: theme,
875
+ t: t,
876
+ onPress: onBackToSignIn
877
+ })]
878
+ });
879
+ };
880
+
881
+ // ---------------------------------------------------------------------------
882
+ // Empty snapshot (no-provider loading state)
883
+ // ---------------------------------------------------------------------------
884
+
885
+ const EMPTY_SNAPSHOT = {
886
+ view: 'accounts',
887
+ accounts: [],
888
+ activeAccountId: null,
889
+ loading: false,
890
+ error: null,
891
+ switchingAccountId: null,
892
+ signIn: {
893
+ phase: 'idle',
894
+ authorizeCode: null,
895
+ qrPayload: null,
896
+ expiresAt: null,
897
+ error: null
898
+ },
899
+ commonsAvailability: 'unknown'
900
+ };
901
+ var _default = exports.default = OxyAuthChooser;
902
+ //# sourceMappingURL=OxyAuthChooser.js.map