@oxyhq/services 10.4.0 → 10.5.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 (37) hide show
  1. package/lib/commonjs/ui/components/AnotherDeviceQR.js +121 -0
  2. package/lib/commonjs/ui/components/AnotherDeviceQR.js.map +1 -0
  3. package/lib/commonjs/ui/components/SignInModal.js +145 -356
  4. package/lib/commonjs/ui/components/SignInModal.js.map +1 -1
  5. package/lib/commonjs/ui/hooks/useOxyAuthSession.js +458 -0
  6. package/lib/commonjs/ui/hooks/useOxyAuthSession.js.map +1 -0
  7. package/lib/commonjs/ui/screens/OxyAuthScreen.js +62 -460
  8. package/lib/commonjs/ui/screens/OxyAuthScreen.js.map +1 -1
  9. package/lib/module/ui/components/AnotherDeviceQR.js +116 -0
  10. package/lib/module/ui/components/AnotherDeviceQR.js.map +1 -0
  11. package/lib/module/ui/components/SignInModal.js +149 -358
  12. package/lib/module/ui/components/SignInModal.js.map +1 -1
  13. package/lib/module/ui/hooks/useOxyAuthSession.js +452 -0
  14. package/lib/module/ui/hooks/useOxyAuthSession.js.map +1 -0
  15. package/lib/module/ui/screens/OxyAuthScreen.js +62 -460
  16. package/lib/module/ui/screens/OxyAuthScreen.js.map +1 -1
  17. package/lib/typescript/commonjs/ui/components/AnotherDeviceQR.d.ts +24 -0
  18. package/lib/typescript/commonjs/ui/components/AnotherDeviceQR.d.ts.map +1 -0
  19. package/lib/typescript/commonjs/ui/components/SignInModal.d.ts +10 -5
  20. package/lib/typescript/commonjs/ui/components/SignInModal.d.ts.map +1 -1
  21. package/lib/typescript/commonjs/ui/hooks/useOxyAuthSession.d.ts +83 -0
  22. package/lib/typescript/commonjs/ui/hooks/useOxyAuthSession.d.ts.map +1 -0
  23. package/lib/typescript/commonjs/ui/screens/OxyAuthScreen.d.ts +12 -8
  24. package/lib/typescript/commonjs/ui/screens/OxyAuthScreen.d.ts.map +1 -1
  25. package/lib/typescript/module/ui/components/AnotherDeviceQR.d.ts +24 -0
  26. package/lib/typescript/module/ui/components/AnotherDeviceQR.d.ts.map +1 -0
  27. package/lib/typescript/module/ui/components/SignInModal.d.ts +10 -5
  28. package/lib/typescript/module/ui/components/SignInModal.d.ts.map +1 -1
  29. package/lib/typescript/module/ui/hooks/useOxyAuthSession.d.ts +83 -0
  30. package/lib/typescript/module/ui/hooks/useOxyAuthSession.d.ts.map +1 -0
  31. package/lib/typescript/module/ui/screens/OxyAuthScreen.d.ts +12 -8
  32. package/lib/typescript/module/ui/screens/OxyAuthScreen.d.ts.map +1 -1
  33. package/package.json +1 -1
  34. package/src/ui/components/AnotherDeviceQR.tsx +111 -0
  35. package/src/ui/components/SignInModal.tsx +142 -381
  36. package/src/ui/hooks/useOxyAuthSession.ts +556 -0
  37. package/src/ui/screens/OxyAuthScreen.tsx +61 -506
@@ -8,39 +8,31 @@ var _react = require("react");
8
8
  var _reactNative = require("react-native");
9
9
  var _reactNativeReanimated = _interopRequireWildcard(require("react-native-reanimated"));
10
10
  var _reactNativeSafeAreaContext = require("react-native-safe-area-context");
11
- var _socket = _interopRequireDefault(require("socket.io-client"));
12
- var _reactNativeQrcodeSvg = _interopRequireDefault(require("react-native-qrcode-svg"));
13
11
  var _theme = require("@oxyhq/bloom/theme");
14
12
  var _button = require("@oxyhq/bloom/button");
15
13
  var _loading = require("@oxyhq/bloom/loading");
16
14
  var _OxyContext = require("../context/OxyContext.js");
17
15
  var _OxyLogo = _interopRequireDefault(require("./OxyLogo.js"));
18
- var _core = require("@oxyhq/core");
19
- var _deviceFlowSignIn = require("../../utils/deviceFlowSignIn.js");
16
+ var _AnotherDeviceQR = _interopRequireDefault(require("./AnotherDeviceQR.js"));
17
+ var _useOxyAuthSession = require("../hooks/useOxyAuthSession.js");
20
18
  var _jsxRuntime = require("react/jsx-runtime");
21
19
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
22
20
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
23
21
  /**
24
- * SignInModal - Full screen sign-in modal with QR code
22
+ * SignInModal - Web-first centered sign-in modal (Continue with Oxy)
25
23
  *
26
- * A semi-transparent full-screen modal that displays:
27
- * - QR code for scanning with Oxy Accounts app
28
- * - Button to open the Oxy Auth approval page
24
+ * A semi-transparent full-screen modal whose primary action is the one-tap
25
+ * "Continue with Oxy" approval flow. The QR code is demoted to a collapsed
26
+ * "Sign in on another device" disclosure (you can't scan your own screen).
29
27
  *
30
- * Animates with fade-in effect.
28
+ * ALL of the auth-session machinery (session-token creation, QR data, socket +
29
+ * polling, waiting/error/retry state, the open-auth handler, deep-link return,
30
+ * and cleanup) lives in the shared `useOxyAuthSession` hook, which the native
31
+ * `OxyAuthScreen` also consumes — neither container re-implements the transport.
32
+ *
33
+ * Animates with a fade + scale effect.
31
34
  */
32
35
 
33
- const debug = (0, _core.createDebugLogger)('SignInModal');
34
- const {
35
- width: SCREEN_WIDTH,
36
- height: SCREEN_HEIGHT
37
- } = _reactNative.Dimensions.get('window');
38
-
39
- // Auth session expiration (5 minutes)
40
- const AUTH_SESSION_EXPIRY_MS = 5 * 60 * 1000;
41
-
42
- // Polling interval (fallback if socket fails)
43
- const POLLING_INTERVAL_MS = 3000;
44
36
  // Store for modal visibility with subscription support
45
37
  let modalVisible = false;
46
38
  let setModalVisibleCallback = null;
@@ -68,10 +60,6 @@ const subscribeToSignInModal = listener => {
68
60
  exports.subscribeToSignInModal = subscribeToSignInModal;
69
61
  const SignInModal = () => {
70
62
  const [visible, setVisible] = (0, _react.useState)(false);
71
- const [authSession, setAuthSession] = (0, _react.useState)(null);
72
- const [isLoading, setIsLoading] = (0, _react.useState)(false);
73
- const [error, setError] = (0, _react.useState)(null);
74
- const [isWaiting, setIsWaiting] = (0, _react.useState)(false);
75
63
  const insets = (0, _reactNativeSafeAreaContext.useSafeAreaInsets)();
76
64
  const theme = (0, _theme.useTheme)();
77
65
  const {
@@ -79,42 +67,59 @@ const SignInModal = () => {
79
67
  switchSession,
80
68
  clientId
81
69
  } = (0, _OxyContext.useOxy)();
82
- const socketRef = (0, _react.useRef)(null);
83
- const pollingIntervalRef = (0, _react.useRef)(null);
84
- const isProcessingRef = (0, _react.useRef)(false);
85
-
86
- // Animation values
87
- const opacity = (0, _reactNativeReanimated.useSharedValue)(0);
88
- const scale = (0, _reactNativeReanimated.useSharedValue)(0.9);
89
70
 
90
- // Register callback
71
+ // Register the imperative visibility callback.
91
72
  (0, _react.useEffect)(() => {
92
73
  setModalVisibleCallback = setVisible;
93
74
  return () => {
94
75
  setModalVisibleCallback = null;
95
76
  };
96
77
  }, []);
78
+ if (!visible) return null;
79
+
80
+ // The hook owns the whole auth-session lifecycle. Mounting it only while the
81
+ // modal is visible matches the previous behavior (session created on open,
82
+ // cleaned up on close) — the inner component is unmounted when `visible`
83
+ // flips false, which runs the hook's unmount cleanup.
84
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(SignInModalContent, {
85
+ theme: theme,
86
+ insets: insets,
87
+ oxyServices: oxyServices,
88
+ switchSession: switchSession,
89
+ clientId: clientId
90
+ });
91
+ };
92
+ const SignInModalContent = ({
93
+ theme,
94
+ insets,
95
+ oxyServices,
96
+ switchSession,
97
+ clientId
98
+ }) => {
99
+ const {
100
+ qrData,
101
+ isLoading,
102
+ error,
103
+ isWaiting,
104
+ openAuthApproval,
105
+ retry
106
+ } = (0, _useOxyAuthSession.useOxyAuthSession)(oxyServices, clientId, switchSession, {
107
+ onSignedIn: hideSignInModal
108
+ });
109
+
110
+ // Entrance animation.
111
+ const opacity = (0, _reactNativeReanimated.useSharedValue)(0);
112
+ const scale = (0, _reactNativeReanimated.useSharedValue)(0.96);
97
113
 
98
- // Animate in/out
99
- // biome-ignore lint/correctness/useExhaustiveDependencies: opacity and scale are Reanimated SharedValues (stable refs) that should not be listed as dependencies
114
+ // biome-ignore lint/correctness/useExhaustiveDependencies: opacity/scale are Reanimated SharedValues (stable refs) and must not be listed as deps; this runs once on mount to play the entrance.
100
115
  (0, _react.useEffect)(() => {
101
- if (visible) {
102
- opacity.value = (0, _reactNativeReanimated.withTiming)(1, {
103
- duration: 250
104
- });
105
- scale.value = (0, _reactNativeReanimated.withTiming)(1, {
106
- duration: 250
107
- });
108
- generateAuthSession();
109
- } else {
110
- opacity.value = (0, _reactNativeReanimated.withTiming)(0, {
111
- duration: 200
112
- });
113
- scale.value = (0, _reactNativeReanimated.withTiming)(0.9, {
114
- duration: 200
115
- });
116
- }
117
- }, [visible]);
116
+ opacity.value = (0, _reactNativeReanimated.withTiming)(1, {
117
+ duration: 250
118
+ });
119
+ scale.value = (0, _reactNativeReanimated.withTiming)(1, {
120
+ duration: 250
121
+ });
122
+ }, []);
118
123
  const backdropStyle = (0, _reactNativeReanimated.useAnimatedStyle)(() => ({
119
124
  opacity: opacity.value
120
125
  }));
@@ -124,245 +129,14 @@ const SignInModal = () => {
124
129
  scale: scale.value
125
130
  }]
126
131
  }));
127
-
128
- // Handle successful authorization.
129
- //
130
- // The auth-session socket (or fallback poll) hands us the new
131
- // `sessionId`. Before any session-management code can touch it we
132
- // MUST first exchange the secret `sessionToken` (held only by this
133
- // client) for the first access token via `claimSessionByToken` —
134
- // this is the device-flow equivalent of OAuth's code-for-token
135
- // exchange (RFC 8628 §3.4).
136
- //
137
- // Without that exchange the SDK has no bearer token and the app never
138
- // becomes authenticated even though the session is authorized server-side.
139
- // Once `claimSessionByToken` plants the tokens in the HttpService, the rest
140
- // of the session wiring (state, persistence, language preference) flows
141
- // through the normal `switchSession` path.
142
- const handleAuthSuccess = (0, _react.useCallback)(async (sessionId, sessionToken) => {
143
- if (isProcessingRef.current) return;
144
- isProcessingRef.current = true;
145
- try {
146
- // Claim the first access token with the secret sessionToken, then
147
- // hydrate the session. Shared with the native `OxyAuthScreen` via
148
- // `completeDeviceFlowSignIn` so the two paths cannot drift.
149
- if (!switchSession) {
150
- throw new Error('Session management unavailable');
151
- }
152
- await (0, _deviceFlowSignIn.completeDeviceFlowSignIn)({
153
- oxyServices,
154
- sessionId,
155
- sessionToken,
156
- switchSession
157
- });
158
- hideSignInModal();
159
- } catch (err) {
160
- debug.error('Error completing auth:', err);
161
- setError('Authorization successful but failed to complete sign in. Please try again.');
162
- isProcessingRef.current = false;
163
- }
164
- }, [oxyServices, switchSession]);
165
-
166
- // Cleanup socket and polling
167
- const cleanup = (0, _react.useCallback)(() => {
168
- setIsWaiting(false);
169
- if (socketRef.current) {
170
- socketRef.current.disconnect();
171
- socketRef.current = null;
172
- }
173
- if (pollingIntervalRef.current) {
174
- clearInterval(pollingIntervalRef.current);
175
- pollingIntervalRef.current = null;
176
- }
177
- }, []);
178
-
179
- // Connect to socket for real-time updates
180
- const connectSocket = (0, _react.useCallback)(sessionToken => {
181
- const baseURL = oxyServices.getBaseURL();
182
- const socket = (0, _socket.default)(`${baseURL}/auth-session`, {
183
- transports: ['websocket', 'polling'],
184
- reconnection: true,
185
- reconnectionAttempts: 3,
186
- reconnectionDelay: 1000
187
- });
188
- socketRef.current = socket;
189
- socket.on('connect', () => {
190
- debug.log('Auth socket connected');
191
- socket.emit('join', sessionToken);
192
- });
193
- socket.on('auth_update', payload => {
194
- debug.log('Auth update received:', payload);
195
- if (payload.status === 'authorized' && payload.sessionId) {
196
- cleanup();
197
- // `sessionToken` was generated by this client and is in scope
198
- // via the `sessionToken` arg — pass it through so the claim
199
- // call can exchange it for the first access token.
200
- handleAuthSuccess(payload.sessionId, sessionToken);
201
- } else if (payload.status === 'cancelled') {
202
- cleanup();
203
- setError('Authorization was denied.');
204
- } else if (payload.status === 'expired') {
205
- cleanup();
206
- setError('Session expired. Please try again.');
207
- }
208
- });
209
- socket.on('connect_error', err => {
210
- debug.log('Socket connection error, falling back to polling:', err instanceof Error ? err.message : null);
211
- socket.disconnect();
212
- startPolling(sessionToken);
213
- });
214
- }, [oxyServices, handleAuthSuccess, cleanup]);
215
-
216
- // Start polling for authorization.
217
- //
218
- // Idempotent: if a poll interval is already running this is a no-op, so the
219
- // `connect_error` path (which also calls this) cannot stack a second
220
- // interval on top of the always-on poll started in `generateAuthSession`.
221
- const startPolling = (0, _react.useCallback)(sessionToken => {
222
- if (pollingIntervalRef.current) return;
223
- pollingIntervalRef.current = setInterval(async () => {
224
- if (isProcessingRef.current) return;
225
- try {
226
- const response = await oxyServices.makeRequest('GET', `/auth/session/status/${sessionToken}`, undefined, {
227
- cache: false
228
- });
229
- if (response.authorized && response.sessionId) {
230
- cleanup();
231
- // Pass the original sessionToken through; the claim
232
- // exchange needs it to mint the first access token.
233
- handleAuthSuccess(response.sessionId, sessionToken);
234
- } else if (response.status === 'cancelled') {
235
- cleanup();
236
- setError('Authorization was denied.');
237
- } else if (response.status === 'expired') {
238
- cleanup();
239
- setError('Session expired. Please try again.');
240
- }
241
- } catch (err) {
242
- debug.log('Auth polling error:', err);
243
- }
244
- }, POLLING_INTERVAL_MS);
245
- }, [oxyServices, handleAuthSuccess, cleanup]);
246
-
247
- // Generate a new auth session
248
- const generateAuthSession = (0, _react.useCallback)(async () => {
249
- setIsLoading(true);
250
- setError(null);
251
- isProcessingRef.current = false;
252
-
253
- // The cross-app device sign-in flow identifies the requesting app by its
254
- // real registered OAuth client id (ApplicationCredential publicKey).
255
- // Without it the API cannot resolve the consent identity, so we fail
256
- // fast with a clear configuration error rather than creating a session
257
- // the server would reject.
258
- if (!clientId) {
259
- setError('This app is not configured for sign-in (missing clientId).');
260
- setIsLoading(false);
261
- return;
262
- }
263
- try {
264
- const sessionToken = generateSessionToken();
265
- const expiresAt = Date.now() + AUTH_SESSION_EXPIRY_MS;
266
- await oxyServices.makeRequest('POST', '/auth/session/create', {
267
- sessionToken,
268
- expiresAt,
269
- clientId
270
- }, {
271
- cache: false
272
- });
273
- setAuthSession({
274
- sessionToken,
275
- expiresAt
276
- });
277
- setIsWaiting(true);
278
- // Socket is the fast path; the poll is a transport-independent
279
- // backstop that guarantees completion even if the socket connects
280
- // but silently never delivers auth_update (RN transport /
281
- // idle-timeout).
282
- connectSocket(sessionToken);
283
- startPolling(sessionToken);
284
- } catch (err) {
285
- setError((err instanceof Error ? err.message : null) || 'Failed to create auth session');
286
- } finally {
287
- setIsLoading(false);
288
- }
289
- }, [oxyServices, connectSocket, startPolling, clientId]);
290
-
291
- // Generate a cryptographically random session token.
292
- // 16 random bytes -> 32 hex chars (128 bits of entropy) — unguessable.
293
- // crypto.getRandomValues is guaranteed available because importing
294
- // @oxyhq/core installs a polyfill via expo-crypto on React Native.
295
- const generateSessionToken = () => {
296
- const bytes = new Uint8Array(16);
297
- crypto.getRandomValues(bytes);
298
- return Array.from(bytes, b => b.toString(16).padStart(2, '0')).join('');
299
- };
300
-
301
- // Build the QR code data
302
- const getQRData = () => {
303
- if (!authSession) return '';
304
- return `oxyauth://${authSession.sessionToken}`;
305
- };
306
-
307
- // Open Oxy Auth approval page for this device-flow session.
308
- const handleOpenAuthApproval = (0, _react.useCallback)(async () => {
309
- if (!authSession) return;
310
- const baseURL = oxyServices.getBaseURL();
311
- // Resolve auth web URL
312
- let authWebUrl = oxyServices.config?.authWebUrl;
313
- if (!authWebUrl) {
314
- try {
315
- const url = new URL(baseURL);
316
- if (url.port === '3001') {
317
- url.port = '3002';
318
- authWebUrl = url.origin;
319
- } else if (url.hostname.startsWith('api.')) {
320
- url.hostname = `auth.${url.hostname.slice(4)}`;
321
- authWebUrl = url.origin;
322
- }
323
- } catch {
324
- authWebUrl = 'https://auth.oxy.so';
325
- }
326
- }
327
- authWebUrl = authWebUrl || 'https://auth.oxy.so';
328
- const webUrl = new URL('/authorize', authWebUrl);
329
- webUrl.searchParams.set('token', authSession.sessionToken);
330
- if (_reactNative.Platform.OS === 'web') {
331
- // Open a separate approval window on web for the device-flow token.
332
- const width = 500;
333
- const height = 650;
334
- const screenWidth = window.screen?.width ?? width;
335
- const screenHeight = window.screen?.height ?? height;
336
- const left = (screenWidth - width) / 2;
337
- const top = (screenHeight - height) / 2;
338
- window.open(webUrl.toString(), 'oxy-auth-approval', `width=${width},height=${height},left=${left},top=${top}`);
339
- } else {
340
- // Open in browser on native
341
- _reactNative.Linking.openURL(webUrl.toString());
342
- }
343
- }, [authSession, oxyServices]);
344
-
345
- // Refresh session
346
- const handleRefresh = (0, _react.useCallback)(() => {
347
- cleanup();
348
- generateAuthSession();
349
- }, [generateAuthSession, cleanup]);
350
-
351
- // Handle close
352
132
  const handleClose = (0, _react.useCallback)(() => {
353
- cleanup();
354
133
  hideSignInModal();
355
- }, [cleanup]);
356
-
357
- // Clean up on unmount
358
- (0, _react.useEffect)(() => {
359
- return () => {
360
- cleanup();
361
- };
362
- }, [cleanup]);
363
- if (!visible) return null;
134
+ }, []);
135
+ const handleCreateAccount = (0, _react.useCallback)(() => {
136
+ _reactNative.Linking.openURL(_useOxyAuthSession.OXY_ACCOUNTS_WEB_URL);
137
+ }, []);
364
138
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Modal, {
365
- visible: visible,
139
+ visible: true,
366
140
  transparent: true,
367
141
  animationType: "none",
368
142
  statusBarTranslucent: true,
@@ -376,15 +150,22 @@ const SignInModal = () => {
376
150
  onPress: handleClose,
377
151
  activeOpacity: 1
378
152
  }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNativeReanimated.default.View, {
379
- style: [styles.content, contentStyle, {
380
- paddingTop: insets.top + 20,
381
- paddingBottom: insets.bottom + 20
382
- }],
153
+ style: [styles.card, {
154
+ backgroundColor: theme.colors.card,
155
+ paddingTop: insets.top + 24,
156
+ paddingBottom: insets.bottom + 24
157
+ }, contentStyle],
383
158
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
384
- style: styles.closeButton,
159
+ style: [styles.closeButton, {
160
+ backgroundColor: theme.colors.backgroundSecondary
161
+ }],
385
162
  onPress: handleClose,
163
+ accessibilityRole: "button",
164
+ accessibilityLabel: "Close sign in",
386
165
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
387
- style: styles.closeButtonText,
166
+ style: [styles.closeButtonText, {
167
+ color: theme.colors.textSecondary
168
+ }],
388
169
  children: "\xD7"
389
170
  })
390
171
  }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
@@ -395,11 +176,11 @@ const SignInModal = () => {
395
176
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
396
177
  className: "text-foreground",
397
178
  style: styles.title,
398
- children: "Sign in with Oxy"
179
+ children: "Sign in to Oxy"
399
180
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
400
181
  className: "text-muted-foreground",
401
182
  style: styles.subtitle,
402
- children: "Scan with Oxy Accounts app or use the button below"
183
+ children: "Continue with your Oxy identity to sign in securely"
403
184
  })]
404
185
  }), isLoading ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
405
186
  style: styles.loadingContainer,
@@ -417,54 +198,54 @@ const SignInModal = () => {
417
198
  style: styles.errorText,
418
199
  children: error
419
200
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_button.Button, {
420
- onPress: handleRefresh,
201
+ variant: "primary",
202
+ onPress: retry,
203
+ style: styles.primaryButton,
421
204
  children: "Try Again"
422
205
  })]
423
206
  }) : /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
424
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
425
- style: [styles.qrContainer, {
426
- backgroundColor: 'white'
427
- }],
428
- children: authSession ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeQrcodeSvg.default, {
429
- value: getQRData(),
430
- size: 200,
431
- backgroundColor: "white",
432
- color: "black"
433
- }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_loading.Loading, {
434
- size: "large"
435
- })
436
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
437
- style: styles.dividerContainer,
438
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
439
- style: [styles.divider, {
440
- backgroundColor: 'rgba(255,255,255,0.3)'
441
- }]
442
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
443
- style: [styles.dividerText, {
444
- color: 'rgba(255,255,255,0.7)'
445
- }],
446
- children: "or"
447
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
448
- style: [styles.divider, {
449
- backgroundColor: 'rgba(255,255,255,0.3)'
450
- }]
451
- })]
452
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_button.Button, {
453
- onPress: handleOpenAuthApproval,
207
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_button.Button, {
208
+ variant: "primary",
209
+ onPress: openAuthApproval,
454
210
  icon: /*#__PURE__*/(0, _jsxRuntime.jsx)(_OxyLogo.default, {
455
211
  variant: "icon",
456
212
  size: 20,
457
- fillColor: theme.colors.card
213
+ fillColor: theme.colors.primaryForeground,
214
+ style: styles.buttonIcon
458
215
  }),
459
- children: "Open Oxy Auth"
216
+ style: styles.primaryButton,
217
+ children: "Continue with Oxy"
460
218
  }), isWaiting && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
461
219
  style: styles.statusContainer,
462
220
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_loading.Loading, {
463
- size: "small"
221
+ size: "small",
222
+ style: styles.statusSpinner
464
223
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
465
- style: styles.statusText,
224
+ style: [styles.statusText, {
225
+ color: theme.colors.textSecondary
226
+ }],
466
227
  children: "Waiting for authorization..."
467
228
  })]
229
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
230
+ style: styles.qrSection,
231
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_AnotherDeviceQR.default, {
232
+ qrData: qrData
233
+ })
234
+ })]
235
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
236
+ style: styles.footer,
237
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
238
+ style: styles.footerText,
239
+ className: "text-muted-foreground",
240
+ children: ["Don't have an Oxy account?", ' ']
241
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
242
+ onPress: handleCreateAccount,
243
+ accessibilityRole: "link",
244
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
245
+ style: styles.footerLink,
246
+ className: "text-primary",
247
+ children: "Create one"
248
+ })
468
249
  })]
469
250
  })]
470
251
  })]
@@ -477,11 +258,12 @@ const styles = _reactNative.StyleSheet.create({
477
258
  justifyContent: 'center',
478
259
  alignItems: 'center'
479
260
  },
480
- content: {
261
+ card: {
481
262
  width: '100%',
482
263
  maxWidth: 400,
483
264
  alignItems: 'center',
484
- paddingHorizontal: 24
265
+ paddingHorizontal: 24,
266
+ borderRadius: 24
485
267
  },
486
268
  closeButton: {
487
269
  position: 'absolute',
@@ -490,58 +272,52 @@ const styles = _reactNative.StyleSheet.create({
490
272
  width: 40,
491
273
  height: 40,
492
274
  borderRadius: 20,
493
- backgroundColor: 'rgba(255, 255, 255, 0.1)',
494
275
  justifyContent: 'center',
495
276
  alignItems: 'center',
496
277
  zIndex: 10
497
278
  },
498
279
  closeButtonText: {
499
- color: 'white',
500
280
  fontSize: 28,
501
281
  fontWeight: '300',
502
282
  lineHeight: 32
503
283
  },
504
284
  header: {
505
285
  alignItems: 'center',
506
- marginBottom: 32
286
+ marginBottom: 28
507
287
  },
508
288
  title: {
509
- fontSize: 28,
289
+ fontSize: 26,
510
290
  fontWeight: 'bold',
511
- marginTop: 16
291
+ marginTop: 16,
292
+ textAlign: 'center'
512
293
  },
513
294
  subtitle: {
514
295
  fontSize: 15,
515
296
  marginTop: 8,
516
297
  textAlign: 'center'
517
298
  },
518
- qrContainer: {
519
- padding: 20,
520
- borderRadius: 16
521
- },
522
- dividerContainer: {
523
- flexDirection: 'row',
524
- alignItems: 'center',
525
- marginVertical: 24,
526
- width: '100%'
527
- },
528
- divider: {
529
- flex: 1,
530
- height: 1
299
+ primaryButton: {
300
+ width: '100%',
301
+ borderRadius: 12
531
302
  },
532
- dividerText: {
533
- marginHorizontal: 16,
534
- fontSize: 14
303
+ buttonIcon: {
304
+ marginRight: 10
535
305
  },
536
306
  statusContainer: {
537
307
  flexDirection: 'row',
538
308
  alignItems: 'center',
539
- marginTop: 24
309
+ marginTop: 20
310
+ },
311
+ statusSpinner: {
312
+ flex: undefined
540
313
  },
541
314
  statusText: {
542
315
  marginLeft: 8,
543
- fontSize: 14,
544
- color: 'rgba(255, 255, 255, 0.7)'
316
+ fontSize: 14
317
+ },
318
+ qrSection: {
319
+ width: '100%',
320
+ marginTop: 24
545
321
  },
546
322
  loadingContainer: {
547
323
  alignItems: 'center',
@@ -560,6 +336,19 @@ const styles = _reactNative.StyleSheet.create({
560
336
  fontSize: 14,
561
337
  textAlign: 'center',
562
338
  marginBottom: 16
339
+ },
340
+ footer: {
341
+ flexDirection: 'row',
342
+ flexWrap: 'wrap',
343
+ justifyContent: 'center',
344
+ marginTop: 28
345
+ },
346
+ footerText: {
347
+ fontSize: 14
348
+ },
349
+ footerLink: {
350
+ fontSize: 14,
351
+ fontWeight: '600'
563
352
  }
564
353
  });
565
354
  var _default = exports.default = SignInModal;