@orangecheck/auth-client 2.33.4 → 2.35.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 (50) hide show
  1. package/dist/appearance.mjs +23 -0
  2. package/dist/appearance.mjs.map +1 -0
  3. package/dist/bond.mjs +20 -0
  4. package/dist/bond.mjs.map +1 -0
  5. package/dist/chunk-4E7GJ5IB.mjs +1184 -0
  6. package/dist/chunk-4E7GJ5IB.mjs.map +1 -0
  7. package/dist/chunk-7ST4Q5KR.mjs +346 -0
  8. package/dist/chunk-7ST4Q5KR.mjs.map +1 -0
  9. package/dist/chunk-7UYPMTAV.mjs +652 -0
  10. package/dist/chunk-7UYPMTAV.mjs.map +1 -0
  11. package/dist/chunk-BYAF2P2L.mjs +253 -0
  12. package/dist/chunk-BYAF2P2L.mjs.map +1 -0
  13. package/dist/chunk-CYR7J4UY.mjs +118 -0
  14. package/dist/chunk-CYR7J4UY.mjs.map +1 -0
  15. package/dist/chunk-H2TXOCXH.mjs +23 -0
  16. package/dist/chunk-H2TXOCXH.mjs.map +1 -0
  17. package/dist/chunk-HZ4FJMF3.mjs +490 -0
  18. package/dist/chunk-HZ4FJMF3.mjs.map +1 -0
  19. package/dist/chunk-TJJNTITX.mjs +187 -0
  20. package/dist/chunk-TJJNTITX.mjs.map +1 -0
  21. package/dist/chunk-UFRU3I3D.mjs +913 -0
  22. package/dist/chunk-UFRU3I3D.mjs.map +1 -0
  23. package/dist/chunk-VRLFZAGK.mjs +37 -0
  24. package/dist/chunk-VRLFZAGK.mjs.map +1 -0
  25. package/dist/chunk-XCZVSE7M.mjs +40 -0
  26. package/dist/chunk-XCZVSE7M.mjs.map +1 -0
  27. package/dist/components.mjs +19 -0
  28. package/dist/components.mjs.map +1 -0
  29. package/dist/index.js +77 -67
  30. package/dist/index.js.map +1 -1
  31. package/dist/index.mjs +112 -4001
  32. package/dist/index.mjs.map +1 -1
  33. package/dist/linked-identities.mjs +41 -0
  34. package/dist/linked-identities.mjs.map +1 -0
  35. package/dist/provider.mjs +17 -0
  36. package/dist/provider.mjs.map +1 -0
  37. package/dist/return-to.mjs +18 -0
  38. package/dist/return-to.mjs.map +1 -0
  39. package/dist/signin.mjs +23 -0
  40. package/dist/signin.mjs.map +1 -0
  41. package/dist/sudo.mjs +16 -0
  42. package/dist/sudo.mjs.map +1 -0
  43. package/dist/tab-session.mjs +29 -0
  44. package/dist/tab-session.mjs.map +1 -0
  45. package/dist/types.mjs +17 -0
  46. package/dist/types.mjs.map +1 -0
  47. package/dist/webauthn.mjs +15 -0
  48. package/dist/webauthn.mjs.map +1 -0
  49. package/package.json +2 -1
  50. package/dist/index.d.mts +0 -303
@@ -0,0 +1,1184 @@
1
+ import {
2
+ LinkPromptStep
3
+ } from "./chunk-UFRU3I3D.mjs";
4
+ import {
5
+ SIGNIN_ARROW,
6
+ SIGNIN_BUTTON_RADIUS,
7
+ SIGNIN_CASE,
8
+ SIGNIN_FONT,
9
+ SIGNIN_STRONG,
10
+ signinRadius,
11
+ signinRoleSize,
12
+ signinSize,
13
+ signinTracking
14
+ } from "./chunk-H2TXOCXH.mjs";
15
+ import {
16
+ stripFragment
17
+ } from "./chunk-CYR7J4UY.mjs";
18
+ import {
19
+ clearTabSession
20
+ } from "./chunk-TJJNTITX.mjs";
21
+
22
+ // src/signin.tsx
23
+ import * as React from "react";
24
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
25
+ function isSameOriginPath(candidate, origin) {
26
+ try {
27
+ return new URL(candidate, origin).origin === origin;
28
+ } catch {
29
+ return false;
30
+ }
31
+ }
32
+ function resolutionOrigin() {
33
+ return typeof window === "undefined" ? "https://ochk.io" : window.location.origin;
34
+ }
35
+ function safeReturnTo(input) {
36
+ const candidate = input ?? "/";
37
+ if (typeof candidate !== "string") return "/";
38
+ if (!candidate.startsWith("/")) return "/";
39
+ return isSameOriginPath(candidate, resolutionOrigin()) ? candidate : "/";
40
+ }
41
+ function familyReturnTarget(input) {
42
+ if (typeof input !== "string" || input.length === 0) return void 0;
43
+ if (input.startsWith("/") && isSameOriginPath(input, resolutionOrigin())) {
44
+ return input;
45
+ }
46
+ try {
47
+ const u = new URL(input);
48
+ if (u.protocol !== "https:") return void 0;
49
+ const host = u.hostname.toLowerCase();
50
+ if (host === "ochk.io" || host.endsWith(".ochk.io")) return u.toString();
51
+ } catch {
52
+ }
53
+ return void 0;
54
+ }
55
+ function hardNavigate(target) {
56
+ if (typeof window === "undefined") return;
57
+ window.location.assign(target);
58
+ }
59
+ var SIGNIN_ERRORS = {
60
+ bad_request: "That doesn't look like a Bitcoin address.",
61
+ invalid_challenge: "That signature doesn't match this address. Sign with the wallet that holds it.",
62
+ sig_invalid: "That signature doesn't match this address. Sign with the wallet that holds it.",
63
+ sig_unsupported_scheme: "We can't check this wallet's signature format yet. Try another wallet, or use email.",
64
+ expired: "That request timed out. Try again for a fresh one.",
65
+ not_yet_valid: "That request is not valid yet. Check your device clock, then try again.",
66
+ nonce_mismatch: "That request didn't match. Try again for a fresh one.",
67
+ audience_mismatch: "That request didn't match this site. Reload the page and try again.",
68
+ purpose_mismatch: "That request didn't match. Try again for a fresh one.",
69
+ malformed: "That signature couldn't be read. Copy it again, whole.",
70
+ cross_site_blocked: "Your browser blocked this request. Reload the page and try again.",
71
+ rate_limited: "Too many tries. Wait a minute, then try again.",
72
+ invalid_email: "That email address doesn't look right.",
73
+ mailer_unavailable: "We couldn't send the email right now. Try again in a minute.",
74
+ mailer_error: "We couldn't send the email right now. Try again in a minute.",
75
+ wrong_code: "That code isn't right. Check the newest email from us.",
76
+ replay: "That code was already used. Send a new one.",
77
+ invalid_token: "That code is no longer valid. Send a new one.",
78
+ email_mismatch: "That code was sent to a different address. Send a new one."
79
+ };
80
+ var EMAIL_ERRORS = {
81
+ expired: "That code expired. Send a new one."
82
+ };
83
+ var SUBMIT_ERRORS = {
84
+ bad_request: "That signature couldn't be read. Copy it again, whole."
85
+ };
86
+ function networkError(authOrigin) {
87
+ let host = authOrigin;
88
+ try {
89
+ host = new URL(authOrigin).host;
90
+ } catch {
91
+ }
92
+ return `We couldn't reach the orangecheck sign-in service at ${host}. Check your connection, then try again.`;
93
+ }
94
+ function humanSigninError(reason, path, step = "challenge") {
95
+ if (!/^[a-z0-9_]+$/.test(reason)) return reason;
96
+ if (path === "wallet" && step === "submit" && SUBMIT_ERRORS[reason]) return SUBMIT_ERRORS[reason];
97
+ if (path === "email" && EMAIL_ERRORS[reason]) return EMAIL_ERRORS[reason];
98
+ return SIGNIN_ERRORS[reason] ?? (path === "email" ? "That didn't work. Try again, or send a new code." : "That didn't work. Try again.");
99
+ }
100
+ function failureMessage(err, path, step, authOrigin) {
101
+ if (err instanceof TypeError || err instanceof SyntaxError) return networkError(authOrigin);
102
+ return humanSigninError(err instanceof Error ? err.message : "failed", path, step);
103
+ }
104
+ var BECH32_ADDRESS = /^(bc1|tb1|bcrt1)[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{8,87}$/i;
105
+ var BASE58_ADDRESS = /^[123mn][1-9A-HJ-NP-Za-km-z]{25,34}$/;
106
+ function looksLikeBitcoinAddress(input) {
107
+ const s = input.trim();
108
+ return BECH32_ADDRESS.test(s) || BASE58_ADDRESS.test(s);
109
+ }
110
+ function OcSignIn({
111
+ audience,
112
+ returnTo,
113
+ onSuccess,
114
+ resolveReturnTo,
115
+ linkPrompt: linkPromptProp = true,
116
+ add: addProp,
117
+ authOrigin = "https://ochk.io",
118
+ initialPath,
119
+ providersFirst = true,
120
+ paths,
121
+ className,
122
+ mode,
123
+ providerHref
124
+ }) {
125
+ const walletEnabled = paths?.wallet ?? true;
126
+ const emailEnabled = paths?.email ?? true;
127
+ const firstParty = mode === "first-party";
128
+ const credentials = firstParty ? "omit" : "include";
129
+ const linkPrompt = linkPromptProp && !firstParty;
130
+ const [resolvedReturn, setResolvedReturn] = React.useState(
131
+ () => familyReturnTarget(returnTo) ?? safeReturnTo(returnTo)
132
+ );
133
+ React.useEffect(() => {
134
+ const fromProp = familyReturnTarget(returnTo);
135
+ if (fromProp) {
136
+ setResolvedReturn(fromProp);
137
+ return;
138
+ }
139
+ if (typeof window === "undefined") return;
140
+ const q = new URLSearchParams(window.location.search);
141
+ const fromQuery = familyReturnTarget(q.get("return_to")) ?? familyReturnTarget(q.get("next"));
142
+ setResolvedReturn(fromQuery ?? "/");
143
+ }, [returnTo]);
144
+ const [addMode, setAddMode] = React.useState(Boolean(addProp) && !firstParty);
145
+ React.useEffect(() => {
146
+ if (typeof window === "undefined") return;
147
+ if (firstParty) {
148
+ setAddMode(false);
149
+ return;
150
+ }
151
+ if (addProp !== void 0) {
152
+ setAddMode(Boolean(addProp));
153
+ return;
154
+ }
155
+ setAddMode(new URLSearchParams(window.location.search).get("add") === "1");
156
+ }, [addProp, firstParty]);
157
+ const [path, setPath] = React.useState(
158
+ initialPath ?? (providersFirst ? "email" : "wallet")
159
+ );
160
+ const tabsId = React.useId();
161
+ const tabOrder = providersFirst ? ["email", "wallet"] : ["wallet", "email"];
162
+ const tabRefs = React.useRef({});
163
+ const onTabKey = (e) => {
164
+ const i = tabOrder.indexOf(path);
165
+ const last = tabOrder.length - 1;
166
+ const to = e.key === "ArrowRight" ? (i + 1) % tabOrder.length : e.key === "ArrowLeft" ? (i + last) % tabOrder.length : e.key === "Home" ? 0 : e.key === "End" ? last : -1;
167
+ if (to < 0) return;
168
+ e.preventDefault();
169
+ const next = tabOrder[to];
170
+ setPath(next);
171
+ tabRefs.current[next]?.focus();
172
+ };
173
+ const [signedIn, setSignedIn] = React.useState(null);
174
+ const [linkAlso, setLinkAlso] = React.useState(false);
175
+ const [oauthError, setOauthError] = React.useState(false);
176
+ React.useEffect(() => {
177
+ if (typeof window === "undefined") return;
178
+ if (new URLSearchParams(window.location.search).has("oauth_error")) {
179
+ setOauthError(true);
180
+ }
181
+ }, []);
182
+ const navigate = React.useCallback(
183
+ async (account) => {
184
+ if (resolveReturnTo) {
185
+ try {
186
+ const resolved = familyReturnTarget(await resolveReturnTo(account));
187
+ hardNavigate(resolved ?? resolvedReturn);
188
+ return;
189
+ } catch {
190
+ }
191
+ }
192
+ hardNavigate(resolvedReturn);
193
+ },
194
+ [resolveReturnTo, resolvedReturn]
195
+ );
196
+ const handleSuccess = React.useCallback(
197
+ async (account, token, via) => {
198
+ const proceed = () => {
199
+ clearTabSession();
200
+ if (onSuccess) onSuccess(account, token);
201
+ else void navigate(account);
202
+ };
203
+ if (!linkPrompt || !linkAlso) {
204
+ proceed();
205
+ return;
206
+ }
207
+ try {
208
+ const meRes = await fetch(`${authOrigin}/api/auth/me`, {
209
+ credentials: "include",
210
+ headers: { Accept: "application/json" }
211
+ });
212
+ const me = meRes.ok ? await meRes.json() : null;
213
+ const didOc = me?.account?.did_oc;
214
+ if (didOc) {
215
+ setSignedIn({
216
+ method: via === "email" ? "btc" : "email",
217
+ didOc,
218
+ proceed
219
+ });
220
+ return;
221
+ }
222
+ } catch {
223
+ }
224
+ proceed();
225
+ },
226
+ [onSuccess, linkPrompt, linkAlso, navigate, authOrigin]
227
+ );
228
+ if (!walletEnabled && !emailEnabled) {
229
+ return /* @__PURE__ */ jsxs(
230
+ "div",
231
+ {
232
+ className,
233
+ "data-oc-signin": "",
234
+ style: {
235
+ padding: "1rem",
236
+ border: "1px solid var(--border, #27272a)",
237
+ color: "var(--foreground, #fafafa)",
238
+ fontFamily: SIGNIN_FONT,
239
+ fontSize: signinSize(12)
240
+ },
241
+ children: [
242
+ "no signin paths enabled \u2014 set ",
243
+ /* @__PURE__ */ jsx("code", { children: "paths.wallet" }),
244
+ " or",
245
+ " ",
246
+ /* @__PURE__ */ jsx("code", { children: "paths.email" }),
247
+ " to ",
248
+ /* @__PURE__ */ jsx("code", { children: "true" })
249
+ ]
250
+ }
251
+ );
252
+ }
253
+ if (signedIn) {
254
+ return /* @__PURE__ */ jsx("div", { className, "data-oc-signin": "", children: /* @__PURE__ */ jsx(
255
+ LinkPromptStep,
256
+ {
257
+ method: signedIn.method,
258
+ didOc: signedIn.didOc,
259
+ authOrigin,
260
+ onResolved: signedIn.proceed
261
+ }
262
+ ) });
263
+ }
264
+ const showWallet = walletEnabled && (path === "wallet" || !emailEnabled);
265
+ const showEmail = emailEnabled && (path === "email" || !walletEnabled);
266
+ const bothEnabled = walletEnabled && emailEnabled;
267
+ const activeMethod = !emailEnabled ? "wallet" : !walletEnabled ? "email" : path;
268
+ return /* @__PURE__ */ jsxs("div", { className, "data-oc-signin": "", children: [
269
+ oauthError && /* @__PURE__ */ jsx(
270
+ "div",
271
+ {
272
+ "data-oc-signin-oauth-error": "",
273
+ style: {
274
+ marginBottom: 14,
275
+ padding: "0.6rem 0.75rem",
276
+ border: "1px solid var(--destructive, #ef4444)",
277
+ borderRadius: signinRadius(6),
278
+ color: "var(--destructive, #ef4444)",
279
+ fontFamily: SIGNIN_FONT,
280
+ fontSize: signinRoleSize("text", signinSize(11))
281
+ },
282
+ children: "That sign-in didn't finish. Try again, or use another way below."
283
+ }
284
+ ),
285
+ addMode && /* @__PURE__ */ jsxs(
286
+ "div",
287
+ {
288
+ "data-oc-signin-add-mode": "",
289
+ style: {
290
+ marginBottom: 14,
291
+ padding: "0.6rem 0.75rem",
292
+ border: "1px solid var(--primary, #f97316)",
293
+ borderLeftWidth: 3,
294
+ borderRadius: signinRadius(4),
295
+ background: "color-mix(in srgb, var(--primary, #f97316) 8%, transparent)",
296
+ color: "var(--foreground, #fafafa)",
297
+ fontFamily: SIGNIN_FONT,
298
+ fontSize: signinRoleSize("text", signinSize(11)),
299
+ lineHeight: 1.6
300
+ },
301
+ children: [
302
+ /* @__PURE__ */ jsx(
303
+ "strong",
304
+ {
305
+ style: {
306
+ color: "var(--primary, #f97316)",
307
+ letterSpacing: signinTracking("0.12em"),
308
+ textTransform: SIGNIN_CASE,
309
+ fontSize: signinSize(10),
310
+ display: "block",
311
+ marginBottom: 3
312
+ },
313
+ children: "\xA7 adding another account"
314
+ }
315
+ ),
316
+ "Your current OrangeCheck account stays signed in. The new account joins your browser's roster; you can switch between them anytime from the account menu."
317
+ ]
318
+ }
319
+ ),
320
+ providersFirst && /* @__PURE__ */ jsx(
321
+ ProviderSignIn,
322
+ {
323
+ authOrigin,
324
+ returnTo: resolvedReturn,
325
+ add: addMode,
326
+ credentials,
327
+ firstParty,
328
+ providerHref,
329
+ first: true
330
+ }
331
+ ),
332
+ bothEnabled && /* @__PURE__ */ jsx(
333
+ "div",
334
+ {
335
+ role: "tablist",
336
+ "aria-label": "Sign-in method",
337
+ "data-oc-signin-tabs": "",
338
+ style: {
339
+ display: "flex",
340
+ gap: 8,
341
+ marginBottom: 16,
342
+ borderBottom: "1px solid var(--border, #27272a)"
343
+ },
344
+ children: tabOrder.map((p) => /* @__PURE__ */ jsx(
345
+ SigninTab,
346
+ {
347
+ id: `${tabsId}-${p}`,
348
+ controls: `${tabsId}-panel`,
349
+ active: path === p,
350
+ onClick: () => setPath(p),
351
+ onKeyDown: onTabKey,
352
+ ref: (el) => {
353
+ tabRefs.current[p] = el;
354
+ },
355
+ children: p === "email" ? "Email" : "Bitcoin wallet"
356
+ },
357
+ p
358
+ ))
359
+ }
360
+ ),
361
+ /* @__PURE__ */ jsxs(
362
+ "div",
363
+ {
364
+ "data-oc-signin-panel": "",
365
+ ...bothEnabled ? {
366
+ id: `${tabsId}-panel`,
367
+ role: "tabpanel",
368
+ "aria-labelledby": `${tabsId}-${path}`
369
+ } : {},
370
+ children: [
371
+ showWallet && /* @__PURE__ */ jsx(
372
+ WalletFlow,
373
+ {
374
+ authOrigin,
375
+ audience,
376
+ add: addMode,
377
+ credentials,
378
+ onSuccess: (a, t) => void handleSuccess(a, t, "wallet")
379
+ }
380
+ ),
381
+ showEmail && /* @__PURE__ */ jsx(
382
+ EmailFlow,
383
+ {
384
+ authOrigin,
385
+ add: addMode,
386
+ credentials,
387
+ onSuccess: (a, t) => void handleSuccess(a, t, "email")
388
+ }
389
+ )
390
+ ]
391
+ }
392
+ ),
393
+ !providersFirst && /* @__PURE__ */ jsx(
394
+ ProviderSignIn,
395
+ {
396
+ authOrigin,
397
+ returnTo: resolvedReturn,
398
+ add: addMode,
399
+ credentials,
400
+ firstParty,
401
+ providerHref
402
+ }
403
+ ),
404
+ linkPrompt && /* @__PURE__ */ jsxs("label", { "data-oc-signin-linkalso": "", style: linkAlsoStyle, children: [
405
+ /* @__PURE__ */ jsx(
406
+ "input",
407
+ {
408
+ type: "checkbox",
409
+ checked: linkAlso,
410
+ onChange: (e) => setLinkAlso(e.target.checked),
411
+ style: { margin: 0, accentColor: "var(--primary, #f97316)" }
412
+ }
413
+ ),
414
+ /* @__PURE__ */ jsxs("span", { children: [
415
+ "After signing in, also link",
416
+ " ",
417
+ activeMethod === "email" ? "a Bitcoin wallet" : "an email",
418
+ " ",
419
+ /* @__PURE__ */ jsx("span", { children: "\u2014 optional, one more signature." })
420
+ ] })
421
+ ] })
422
+ ] });
423
+ }
424
+ var SigninTab = React.forwardRef(function SigninTab2({ id, controls, active, onClick, onKeyDown, children }, ref) {
425
+ return /* @__PURE__ */ jsx(
426
+ "button",
427
+ {
428
+ ref,
429
+ type: "button",
430
+ role: "tab",
431
+ id,
432
+ "aria-selected": active,
433
+ "aria-controls": controls,
434
+ tabIndex: active ? 0 : -1,
435
+ onClick,
436
+ onKeyDown,
437
+ "data-oc-signin-tab": active ? "active" : "inactive",
438
+ style: {
439
+ minHeight: 44,
440
+ padding: "0.6rem 0.875rem",
441
+ background: "transparent",
442
+ border: "none",
443
+ borderBottom: active ? "2px solid var(--primary, #f97316)" : "2px solid transparent",
444
+ color: active ? "var(--foreground, #fafafa)" : "var(--muted-foreground, #a1a1aa)",
445
+ fontFamily: SIGNIN_FONT,
446
+ fontSize: signinRoleSize("label", signinSize(11)),
447
+ letterSpacing: signinTracking("0.12em"),
448
+ textTransform: SIGNIN_CASE,
449
+ cursor: "pointer",
450
+ marginBottom: -1
451
+ },
452
+ children
453
+ }
454
+ );
455
+ });
456
+ function ProviderIcon({ id }) {
457
+ const common = {
458
+ width: 14,
459
+ height: 14,
460
+ viewBox: "0 0 24 24",
461
+ fill: "currentColor",
462
+ "aria-hidden": true,
463
+ style: { flex: "0 0 auto" }
464
+ };
465
+ if (id === "google") {
466
+ return /* @__PURE__ */ jsx("svg", { ...common, children: /* @__PURE__ */ jsx("path", { d: "M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z" }) });
467
+ }
468
+ if (id === "github") {
469
+ return /* @__PURE__ */ jsx("svg", { ...common, children: /* @__PURE__ */ jsx("path", { d: "M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" }) });
470
+ }
471
+ return null;
472
+ }
473
+ function ProviderSignIn({
474
+ authOrigin,
475
+ returnTo,
476
+ add,
477
+ credentials,
478
+ firstParty,
479
+ providerHref,
480
+ first = false
481
+ }) {
482
+ const [providers, setProviders] = React.useState(null);
483
+ const [origin, setOrigin] = React.useState("");
484
+ const hidden = firstParty && !providerHref;
485
+ React.useEffect(() => {
486
+ setOrigin(window.location.origin);
487
+ if (hidden) return;
488
+ let cancelled = false;
489
+ fetch(`${authOrigin}/api/auth/providers`, { credentials }).then((r) => r.ok ? r.json() : null).then((body) => {
490
+ if (!cancelled) setProviders(body?.providers ?? []);
491
+ }).catch(() => {
492
+ if (!cancelled) setProviders([]);
493
+ });
494
+ return () => {
495
+ cancelled = true;
496
+ };
497
+ }, [authOrigin, credentials, hidden]);
498
+ if (hidden) return null;
499
+ if (providers !== null && providers.length === 0) return null;
500
+ const providerReturnTo = stripFragment(
501
+ returnTo.startsWith("/") ? origin ? `${origin}${returnTo}` : returnTo : returnTo
502
+ );
503
+ const line = { flex: 1, height: 1, background: "var(--border, #27272a)" };
504
+ const BLOCK_GAP = 16;
505
+ const BUTTON_GAP = 8;
506
+ const divider = /* @__PURE__ */ jsxs(
507
+ "div",
508
+ {
509
+ style: {
510
+ display: "flex",
511
+ alignItems: "center",
512
+ gap: 10,
513
+ // Symmetric in both orderings: in `first` mode the divider
514
+ // owns the gap on both sides; at the bottom the container's
515
+ // marginTop owns the gap above, so the divider only pads below.
516
+ margin: first ? `${BLOCK_GAP}px 0` : `0 0 ${BLOCK_GAP}px`,
517
+ color: "var(--muted-foreground, #a1a1aa)",
518
+ fontFamily: SIGNIN_FONT,
519
+ fontSize: signinRoleSize("hint", signinSize(10)),
520
+ letterSpacing: signinTracking("0.16em"),
521
+ textTransform: SIGNIN_CASE
522
+ },
523
+ children: [
524
+ /* @__PURE__ */ jsx("span", { style: line }),
525
+ "or",
526
+ /* @__PURE__ */ jsx("span", { style: line })
527
+ ]
528
+ }
529
+ );
530
+ const buttonStyle = (i) => ({
531
+ display: "flex",
532
+ alignItems: "center",
533
+ justifyContent: "center",
534
+ gap: 10,
535
+ boxSizing: "border-box",
536
+ width: "100%",
537
+ minHeight: 44,
538
+ marginTop: i === 0 ? 0 : BUTTON_GAP,
539
+ padding: "0.6rem 0.875rem",
540
+ border: "1px solid var(--border, #27272a)",
541
+ borderRadius: SIGNIN_BUTTON_RADIUS,
542
+ background: "transparent",
543
+ color: "var(--muted-foreground, #a1a1aa)",
544
+ fontFamily: SIGNIN_FONT,
545
+ fontSize: signinSize(12),
546
+ textDecoration: "none"
547
+ });
548
+ if (providers === null) {
549
+ if (!first) return null;
550
+ return /* @__PURE__ */ jsxs("div", { "data-oc-signin-providers": "", "aria-hidden": "true", children: [
551
+ [0, 1].map((i) => /* @__PURE__ */ jsx("div", { style: { ...buttonStyle(i), visibility: "hidden" }, children: "\xA0" }, i)),
552
+ divider
553
+ ] });
554
+ }
555
+ const buttons = providers.map((p, i) => /* @__PURE__ */ jsxs(
556
+ "a",
557
+ {
558
+ href: providerHref ? providerHref(p.id) : `${authOrigin}/api/auth/${p.id}/start?return_to=${encodeURIComponent(
559
+ providerReturnTo
560
+ )}${add ? "&add=1" : ""}`,
561
+ "data-oc-signin-provider": p.id,
562
+ style: buttonStyle(i),
563
+ children: [
564
+ /* @__PURE__ */ jsx(ProviderIcon, { id: p.id }),
565
+ /* @__PURE__ */ jsx("span", { children: p.label })
566
+ ]
567
+ },
568
+ p.id
569
+ ));
570
+ return /* @__PURE__ */ jsx(
571
+ "div",
572
+ {
573
+ "data-oc-signin-providers": "",
574
+ style: first ? { marginBottom: 0 } : { marginTop: BLOCK_GAP },
575
+ children: first ? /* @__PURE__ */ jsxs(Fragment, { children: [
576
+ buttons,
577
+ divider
578
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
579
+ divider,
580
+ buttons
581
+ ] })
582
+ }
583
+ );
584
+ }
585
+ async function loadAdapter() {
586
+ return await import("@orangecheck/wallet-adapter");
587
+ }
588
+ async function detectBrowserWallets() {
589
+ return (await loadAdapter()).detectWallets().filter((w) => w.detected && w.id !== "manual").map((w) => ({ id: w.id, name: w.name }));
590
+ }
591
+ function WalletFlow({
592
+ authOrigin,
593
+ audience,
594
+ add,
595
+ credentials,
596
+ onSuccess
597
+ }) {
598
+ const uid = React.useId();
599
+ const ids = {
600
+ address: `${uid}-address`,
601
+ wallet: `${uid}-wallet`,
602
+ message: `${uid}-message`,
603
+ signature: `${uid}-signature`
604
+ };
605
+ const [address, setAddress] = React.useState("");
606
+ const [error, setError] = React.useState(null);
607
+ const [submitting, setSubmitting] = React.useState(false);
608
+ const [wallets, setWallets] = React.useState(null);
609
+ const [walletId, setWalletId] = React.useState(null);
610
+ const [manual, setManual] = React.useState(null);
611
+ const [pasted, setPasted] = React.useState("");
612
+ const [copied, setCopied] = React.useState("idle");
613
+ React.useEffect(() => {
614
+ let cancelled = false;
615
+ detectBrowserWallets().then((found) => {
616
+ if (cancelled) return;
617
+ setWallets(found);
618
+ setWalletId(found[0]?.id ?? null);
619
+ }).catch(() => {
620
+ if (!cancelled) setWallets([]);
621
+ });
622
+ return () => {
623
+ cancelled = true;
624
+ };
625
+ }, []);
626
+ async function fetchChallenge(addr) {
627
+ const res = await fetch(
628
+ `${authOrigin}/api/challenge?addr=${encodeURIComponent(addr)}&audience=${encodeURIComponent(audience)}&purpose=login`
629
+ );
630
+ const json = await res.json();
631
+ if (!res.ok || !json.message || !json.nonce) {
632
+ throw new Error(json.error ?? "challenge_failed");
633
+ }
634
+ return { message: json.message, nonce: json.nonce };
635
+ }
636
+ async function submitSignature(message, nonce, signature, scheme) {
637
+ const res = await fetch(`${authOrigin}/api/auth/signin`, {
638
+ method: "POST",
639
+ credentials,
640
+ headers: { "Content-Type": "application/json" },
641
+ body: JSON.stringify({
642
+ message,
643
+ signature,
644
+ ...scheme ? { scheme } : {},
645
+ expectedNonce: nonce,
646
+ expectedAudience: audience,
647
+ expectedPurpose: "login",
648
+ // Multi-account add-mode · the auth host preserves the current
649
+ // roster_id when set, instead of minting a fresh one.
650
+ ...add ? { add: true } : {}
651
+ })
652
+ });
653
+ const json = await res.json();
654
+ if (!res.ok || !("ok" in json) || !json.ok || !json.account) {
655
+ throw new Error(
656
+ "reason" in json && json.reason || "error" in json && json.error || "verify_failed"
657
+ );
658
+ }
659
+ onSuccess(json.account, json.token);
660
+ }
661
+ async function signIn(e) {
662
+ e.preventDefault();
663
+ const addr = address.trim();
664
+ if (!addr) {
665
+ setError("Paste your Bitcoin address.");
666
+ return;
667
+ }
668
+ if (!looksLikeBitcoinAddress(addr)) {
669
+ setError(humanSigninError("bad_request", "wallet"));
670
+ return;
671
+ }
672
+ setError(null);
673
+ setSubmitting(true);
674
+ let step = "challenge";
675
+ try {
676
+ let id = walletId;
677
+ if (wallets === null) {
678
+ const found = await detectBrowserWallets().catch(() => []);
679
+ setWallets(found);
680
+ id = found[0]?.id ?? null;
681
+ setWalletId(id);
682
+ }
683
+ const challenge = await fetchChallenge(addr);
684
+ if (!id) {
685
+ setManual(challenge);
686
+ setSubmitting(false);
687
+ return;
688
+ }
689
+ const signer = (await loadAdapter()).getSigner(id, { address: addr });
690
+ const signature = await signer(challenge.message);
691
+ step = "submit";
692
+ await submitSignature(challenge.message, challenge.nonce, signature, "bip322");
693
+ } catch (err) {
694
+ setError(failureMessage(err, "wallet", step, authOrigin));
695
+ setSubmitting(false);
696
+ }
697
+ }
698
+ async function signInManual(e) {
699
+ e.preventDefault();
700
+ if (!manual) return;
701
+ const sig = pasted.trim();
702
+ if (!sig) {
703
+ setError("Paste the signature your wallet gave you.");
704
+ return;
705
+ }
706
+ setError(null);
707
+ setSubmitting(true);
708
+ try {
709
+ await submitSignature(manual.message, manual.nonce, sig, void 0);
710
+ } catch (err) {
711
+ setError(failureMessage(err, "wallet", "submit", authOrigin));
712
+ setSubmitting(false);
713
+ }
714
+ }
715
+ async function copyMessage() {
716
+ if (!manual) return;
717
+ try {
718
+ await navigator.clipboard.writeText(manual.message);
719
+ setCopied("copied");
720
+ } catch {
721
+ setCopied("failed");
722
+ }
723
+ }
724
+ if (manual) {
725
+ return /* @__PURE__ */ jsxs("form", { onSubmit: signInManual, "data-oc-signin-wallet-manual": "", children: [
726
+ /* @__PURE__ */ jsxs(FlowHeader, { children: [
727
+ "In your wallet, use \u201Csign message\u201D (BIP-322) with ",
728
+ address.trim(),
729
+ ", sign the text below, and paste the signature back here. Nothing is spent."
730
+ ] }),
731
+ /* @__PURE__ */ jsx(Label, { htmlFor: ids.message, children: "Message to sign" }),
732
+ /* @__PURE__ */ jsx(
733
+ "textarea",
734
+ {
735
+ id: ids.message,
736
+ readOnly: true,
737
+ value: manual.message,
738
+ rows: 5,
739
+ onFocus: (e) => e.currentTarget.select(),
740
+ style: { ...inputStyle, resize: "vertical" }
741
+ }
742
+ ),
743
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => void copyMessage(), style: linkButtonStyle, children: copied === "copied" ? "Copied" : "Copy message" }),
744
+ copied === "failed" && /* @__PURE__ */ jsx(Hint, { children: "Couldn't copy here. Select the text above and copy it." }),
745
+ /* @__PURE__ */ jsxs("div", { style: { marginTop: 14 }, children: [
746
+ /* @__PURE__ */ jsx(Label, { htmlFor: ids.signature, children: "Signature" }),
747
+ /* @__PURE__ */ jsx(
748
+ "textarea",
749
+ {
750
+ id: ids.signature,
751
+ value: pasted,
752
+ onChange: (e) => {
753
+ setPasted(e.target.value);
754
+ if (error) setError(null);
755
+ },
756
+ rows: 3,
757
+ placeholder: "paste the signature",
758
+ spellCheck: false,
759
+ autoFocus: true,
760
+ "aria-invalid": error ? true : void 0,
761
+ style: { ...inputStyle, resize: "vertical" }
762
+ }
763
+ )
764
+ ] }),
765
+ error && /* @__PURE__ */ jsx(ErrorLine, { children: error }),
766
+ /* @__PURE__ */ jsx(
767
+ "button",
768
+ {
769
+ type: "submit",
770
+ disabled: submitting || !pasted.trim(),
771
+ style: submitStyle(submitting || !pasted.trim()),
772
+ children: submitting ? "Checking\u2026" : /* @__PURE__ */ jsx(Submit, { children: "Sign me in" })
773
+ }
774
+ ),
775
+ /* @__PURE__ */ jsx(
776
+ "button",
777
+ {
778
+ type: "button",
779
+ onClick: () => {
780
+ setManual(null);
781
+ setPasted("");
782
+ setError(null);
783
+ setCopied("idle");
784
+ },
785
+ style: linkButtonStyle,
786
+ children: "Use a different address"
787
+ }
788
+ )
789
+ ] });
790
+ }
791
+ const noBrowserWallet = wallets !== null && wallets.length === 0;
792
+ return /* @__PURE__ */ jsxs("form", { onSubmit: signIn, "data-oc-signin-wallet": "", children: [
793
+ /* @__PURE__ */ jsx(FlowHeader, { children: "Your wallet signs a short message to prove you hold the address. Nothing is spent and your key never leaves the wallet. Works with browser wallets, or any wallet that can sign a message, like Sparrow." }),
794
+ /* @__PURE__ */ jsx(Label, { htmlFor: ids.address, children: "Bitcoin address" }),
795
+ /* @__PURE__ */ jsx(
796
+ "input",
797
+ {
798
+ id: ids.address,
799
+ type: "text",
800
+ value: address,
801
+ onChange: (e) => {
802
+ setAddress(e.target.value);
803
+ if (error) setError(null);
804
+ },
805
+ placeholder: "bc1q\u2026 \xB7 3\u2026 \xB7 1\u2026",
806
+ autoComplete: "off",
807
+ autoCapitalize: "none",
808
+ spellCheck: false,
809
+ required: true,
810
+ "aria-invalid": error ? true : void 0,
811
+ style: inputStyle
812
+ }
813
+ ),
814
+ wallets && wallets.length > 1 && /* @__PURE__ */ jsxs("div", { style: { marginTop: 12 }, children: [
815
+ /* @__PURE__ */ jsx(Label, { htmlFor: ids.wallet, children: "Sign with" }),
816
+ /* @__PURE__ */ jsx(
817
+ "select",
818
+ {
819
+ id: ids.wallet,
820
+ value: walletId ?? "",
821
+ onChange: (e) => setWalletId(e.target.value),
822
+ style: inputStyle,
823
+ children: wallets.map((w) => /* @__PURE__ */ jsx("option", { value: w.id, children: w.name }, w.id))
824
+ }
825
+ )
826
+ ] }),
827
+ error && /* @__PURE__ */ jsx(ErrorLine, { children: error }),
828
+ /* @__PURE__ */ jsx(
829
+ "button",
830
+ {
831
+ type: "submit",
832
+ disabled: submitting || !address.trim(),
833
+ style: submitStyle(submitting || !address.trim()),
834
+ children: submitting ? "Waiting for wallet\u2026" : noBrowserWallet ? /* @__PURE__ */ jsx(Submit, { children: "Get the message to sign" }) : /* @__PURE__ */ jsx(Submit, { children: "Sign in with wallet" })
835
+ }
836
+ ),
837
+ noBrowserWallet && /* @__PURE__ */ jsx(Hint, { children: "No browser wallet found here. You can sign with any wallet and paste." })
838
+ ] });
839
+ }
840
+ function EmailFlow({ authOrigin, add, credentials, onSuccess }) {
841
+ const uid = React.useId();
842
+ const [stage, setStage] = React.useState("enter");
843
+ const [email, setEmail] = React.useState("");
844
+ const [emailError, setEmailError] = React.useState(null);
845
+ const [code, setCode] = React.useState("");
846
+ const [codeError, setCodeError] = React.useState(null);
847
+ const [token, setToken] = React.useState(null);
848
+ const [submitting, setSubmitting] = React.useState(false);
849
+ const [resent, setResent] = React.useState(false);
850
+ async function sendCode(address) {
851
+ try {
852
+ const res = await fetch(`${authOrigin}/api/auth/email-otp/start`, {
853
+ method: "POST",
854
+ credentials,
855
+ headers: { "Content-Type": "application/json" },
856
+ body: JSON.stringify({ email: address })
857
+ });
858
+ const json = await res.json();
859
+ if (!res.ok || !json.token) return humanSigninError(json.reason ?? "server_error", "email");
860
+ setToken(json.token);
861
+ return null;
862
+ } catch {
863
+ return networkError(authOrigin);
864
+ }
865
+ }
866
+ async function start(e) {
867
+ e.preventDefault();
868
+ const trimmed = email.trim().toLowerCase();
869
+ if (!trimmed || !/.+@.+\..+/.test(trimmed)) {
870
+ setEmailError(humanSigninError("invalid_email", "email"));
871
+ return;
872
+ }
873
+ setEmailError(null);
874
+ setSubmitting(true);
875
+ const err = await sendCode(trimmed);
876
+ setSubmitting(false);
877
+ if (err) {
878
+ setEmailError(err);
879
+ return;
880
+ }
881
+ setEmail(trimmed);
882
+ setStage("code");
883
+ }
884
+ async function resend() {
885
+ setCodeError(null);
886
+ setResent(false);
887
+ setSubmitting(true);
888
+ const err = await sendCode(email);
889
+ setSubmitting(false);
890
+ setCode("");
891
+ if (err) setCodeError(err);
892
+ else setResent(true);
893
+ }
894
+ async function verify(e) {
895
+ e.preventDefault();
896
+ if (!token) return;
897
+ if (code.length !== 6) {
898
+ setCodeError("The code is 6 digits.");
899
+ return;
900
+ }
901
+ setCodeError(null);
902
+ setSubmitting(true);
903
+ try {
904
+ const res = await fetch(`${authOrigin}/api/auth/email-otp/verify`, {
905
+ method: "POST",
906
+ credentials,
907
+ headers: { "Content-Type": "application/json" },
908
+ body: JSON.stringify({
909
+ email,
910
+ code,
911
+ token,
912
+ // Multi-account add-mode · see WalletFlow.
913
+ ...add ? { add: true } : {}
914
+ })
915
+ });
916
+ const json = await res.json();
917
+ if (!res.ok || !("ok" in json) || !json.ok || !json.account) {
918
+ throw new Error(
919
+ "reason" in json && json.reason || "error" in json && json.error || "server_error"
920
+ );
921
+ }
922
+ onSuccess(json.account, json.token);
923
+ } catch (err) {
924
+ setCodeError(failureMessage(err, "email", "submit", authOrigin));
925
+ } finally {
926
+ setSubmitting(false);
927
+ }
928
+ }
929
+ if (stage === "enter") {
930
+ return /* @__PURE__ */ jsxs("form", { onSubmit: start, "data-oc-signin-email": "", children: [
931
+ /* @__PURE__ */ jsx(FlowHeader, { children: "We email you a 6-digit code. No password, and no wallet needed to start. Add a Bitcoin address any time: a way in that only you hold." }),
932
+ /* @__PURE__ */ jsx(Label, { htmlFor: `${uid}-email`, children: "Email" }),
933
+ /* @__PURE__ */ jsx(
934
+ "input",
935
+ {
936
+ id: `${uid}-email`,
937
+ type: "email",
938
+ autoComplete: "email",
939
+ value: email,
940
+ onChange: (e) => {
941
+ setEmail(e.target.value);
942
+ if (emailError) setEmailError(null);
943
+ },
944
+ placeholder: "you@example.com",
945
+ required: true,
946
+ "aria-invalid": emailError ? true : void 0,
947
+ style: inputStyle
948
+ }
949
+ ),
950
+ emailError && /* @__PURE__ */ jsx(ErrorLine, { children: emailError }),
951
+ /* @__PURE__ */ jsx("button", { type: "submit", disabled: submitting, style: submitStyle(submitting), children: submitting ? "Sending\u2026" : /* @__PURE__ */ jsx(Submit, { children: "Send one-time code" }) })
952
+ ] });
953
+ }
954
+ return /* @__PURE__ */ jsxs("form", { onSubmit: verify, "data-oc-signin-email-verify": "", children: [
955
+ /* @__PURE__ */ jsxs(FlowHeader, { children: [
956
+ resent ? "Sent a new code to " : "Sent a 6-digit code to ",
957
+ /* @__PURE__ */ jsx("span", { style: { color: "var(--foreground, #fafafa)" }, children: email }),
958
+ ". It expires in 10 minutes."
959
+ ] }),
960
+ /* @__PURE__ */ jsx(Label, { htmlFor: `${uid}-code`, children: "One-time code" }),
961
+ /* @__PURE__ */ jsx(
962
+ "input",
963
+ {
964
+ id: `${uid}-code`,
965
+ type: "text",
966
+ inputMode: "numeric",
967
+ pattern: "\\d{6}",
968
+ maxLength: 6,
969
+ autoComplete: "one-time-code",
970
+ value: code,
971
+ onChange: (e) => {
972
+ setCode(e.target.value.replace(/\D/g, "").slice(0, 6));
973
+ if (codeError) setCodeError(null);
974
+ },
975
+ placeholder: "6 digits",
976
+ required: true,
977
+ autoFocus: true,
978
+ "aria-invalid": codeError ? true : void 0,
979
+ style: { ...inputStyle, letterSpacing: "0.4em", fontSize: 16 }
980
+ }
981
+ ),
982
+ codeError && /* @__PURE__ */ jsx(ErrorLine, { children: codeError }),
983
+ /* @__PURE__ */ jsx(
984
+ "button",
985
+ {
986
+ type: "submit",
987
+ disabled: submitting || code.length !== 6,
988
+ style: submitStyle(submitting || code.length !== 6),
989
+ children: submitting ? "Verifying\u2026" : /* @__PURE__ */ jsx(Submit, { children: "Verify \xB7 sign me in" })
990
+ }
991
+ ),
992
+ /* @__PURE__ */ jsxs("div", { children: [
993
+ /* @__PURE__ */ jsx(
994
+ "button",
995
+ {
996
+ type: "button",
997
+ onClick: () => void resend(),
998
+ disabled: submitting,
999
+ style: linkButtonStyle,
1000
+ children: "Send a new code"
1001
+ }
1002
+ ),
1003
+ /* @__PURE__ */ jsx(
1004
+ "button",
1005
+ {
1006
+ type: "button",
1007
+ onClick: () => {
1008
+ setStage("enter");
1009
+ setCode("");
1010
+ setToken(null);
1011
+ setResent(false);
1012
+ },
1013
+ style: linkButtonStyle,
1014
+ children: "Use a different email"
1015
+ }
1016
+ )
1017
+ ] })
1018
+ ] });
1019
+ }
1020
+ function Submit({ children }) {
1021
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1022
+ children,
1023
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", style: { display: SIGNIN_ARROW }, children: " \u2192" })
1024
+ ] });
1025
+ }
1026
+ function FlowHeader({
1027
+ label,
1028
+ children
1029
+ }) {
1030
+ return /* @__PURE__ */ jsxs("div", { style: { marginBottom: 16 }, children: [
1031
+ label && /* @__PURE__ */ jsx(
1032
+ "div",
1033
+ {
1034
+ style: {
1035
+ color: "var(--primary, #f97316)",
1036
+ fontFamily: SIGNIN_FONT,
1037
+ fontSize: signinSize(10),
1038
+ letterSpacing: signinTracking("0.18em"),
1039
+ textTransform: SIGNIN_CASE,
1040
+ marginBottom: 6
1041
+ },
1042
+ children: label
1043
+ }
1044
+ ),
1045
+ /* @__PURE__ */ jsx(
1046
+ "p",
1047
+ {
1048
+ style: {
1049
+ color: "var(--muted-foreground, #a1a1aa)",
1050
+ fontSize: signinRoleSize("text", "13px"),
1051
+ lineHeight: 1.55,
1052
+ margin: 0
1053
+ },
1054
+ children
1055
+ }
1056
+ )
1057
+ ] });
1058
+ }
1059
+ var linkButtonStyle = {
1060
+ display: "inline-flex",
1061
+ alignItems: "center",
1062
+ minHeight: 44,
1063
+ marginTop: 4,
1064
+ marginRight: 16,
1065
+ background: "transparent",
1066
+ border: "none",
1067
+ color: "var(--muted-foreground, #a1a1aa)",
1068
+ fontFamily: SIGNIN_FONT,
1069
+ fontSize: signinRoleSize("button", signinSize(11)),
1070
+ letterSpacing: signinTracking("0.12em"),
1071
+ textTransform: SIGNIN_CASE,
1072
+ cursor: "pointer",
1073
+ padding: 0
1074
+ };
1075
+ function Label({
1076
+ htmlFor,
1077
+ children
1078
+ }) {
1079
+ return /* @__PURE__ */ jsx(
1080
+ "label",
1081
+ {
1082
+ htmlFor,
1083
+ style: {
1084
+ display: "block",
1085
+ color: "var(--foreground, #fafafa)",
1086
+ fontFamily: SIGNIN_FONT,
1087
+ fontSize: signinRoleSize("label", signinSize(11)),
1088
+ letterSpacing: signinTracking("0.12em"),
1089
+ textTransform: SIGNIN_CASE,
1090
+ marginBottom: 6
1091
+ },
1092
+ children
1093
+ }
1094
+ );
1095
+ }
1096
+ var inputStyle = {
1097
+ width: "100%",
1098
+ padding: "0.55rem 0.75rem",
1099
+ background: "var(--background, #0a0a0a)",
1100
+ color: "var(--foreground, #fafafa)",
1101
+ border: "1px solid var(--input, #27272a)",
1102
+ borderRadius: signinRadius(6),
1103
+ fontFamily: SIGNIN_FONT,
1104
+ // 16px — never below it. A sub-16px font-size makes iOS Safari
1105
+ // auto-zoom the viewport on focus; the family rule is >=16px on
1106
+ // every form field. Desktop reads fine at 16 too.
1107
+ fontSize: 16
1108
+ };
1109
+ var linkAlsoStyle = {
1110
+ display: "flex",
1111
+ alignItems: "center",
1112
+ // The whole row is the checkbox's hit area; the 13px box alone was not.
1113
+ minHeight: 44,
1114
+ gap: 8,
1115
+ marginTop: 16,
1116
+ paddingTop: 14,
1117
+ borderTop: "1px solid var(--border, #27272a)",
1118
+ color: "var(--muted-foreground, #a1a1aa)",
1119
+ fontFamily: SIGNIN_FONT,
1120
+ fontSize: signinRoleSize("text", signinSize(12)),
1121
+ lineHeight: 1.5,
1122
+ cursor: "pointer"
1123
+ };
1124
+ function submitStyle(disabled) {
1125
+ return {
1126
+ marginTop: 12,
1127
+ padding: "0.6rem 1rem",
1128
+ background: "var(--primary, #f97316)",
1129
+ color: "var(--primary-foreground, #0a0a0a)",
1130
+ border: "none",
1131
+ borderRadius: SIGNIN_BUTTON_RADIUS,
1132
+ fontFamily: SIGNIN_FONT,
1133
+ fontSize: signinRoleSize("button", signinSize(11)),
1134
+ letterSpacing: signinTracking("0.14em"),
1135
+ textTransform: SIGNIN_CASE,
1136
+ fontWeight: SIGNIN_STRONG,
1137
+ cursor: disabled ? "not-allowed" : "pointer",
1138
+ opacity: disabled ? 0.5 : 1,
1139
+ width: "100%"
1140
+ };
1141
+ }
1142
+ function ErrorLine({ children }) {
1143
+ return /* @__PURE__ */ jsx(
1144
+ "p",
1145
+ {
1146
+ role: "alert",
1147
+ style: {
1148
+ marginTop: 6,
1149
+ marginBottom: 0,
1150
+ color: "var(--destructive, #ef4444)",
1151
+ fontFamily: SIGNIN_FONT,
1152
+ fontSize: signinRoleSize("hint", signinSize(11)),
1153
+ letterSpacing: "0.04em"
1154
+ },
1155
+ children
1156
+ }
1157
+ );
1158
+ }
1159
+ function Hint({ children }) {
1160
+ return /* @__PURE__ */ jsx(
1161
+ "p",
1162
+ {
1163
+ style: {
1164
+ marginTop: 12,
1165
+ marginBottom: 0,
1166
+ color: "var(--muted-foreground, #a1a1aa)",
1167
+ fontFamily: SIGNIN_FONT,
1168
+ fontSize: signinRoleSize("hint", "10.5px"),
1169
+ lineHeight: 1.55
1170
+ },
1171
+ children
1172
+ }
1173
+ );
1174
+ }
1175
+
1176
+ export {
1177
+ safeReturnTo,
1178
+ familyReturnTarget,
1179
+ networkError,
1180
+ humanSigninError,
1181
+ looksLikeBitcoinAddress,
1182
+ OcSignIn
1183
+ };
1184
+ //# sourceMappingURL=chunk-4E7GJ5IB.mjs.map