@elvix.is/sdk 0.5.5 → 0.5.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/react.js CHANGED
@@ -204,6 +204,7 @@ import { useState as useState2 } from "react";
204
204
  var DEFAULT_COPY = {
205
205
  subtitle: "Pick how you want to continue.",
206
206
  googleButton: "Continue with Google",
207
+ passkeyButton: "Continue with passkey",
207
208
  emailPlaceholder: "you@example.com",
208
209
  sendCodeButton: "Send code",
209
210
  sendingLabel: "Sending\u2026",
@@ -272,6 +273,179 @@ function isSameOrigin(baseUrl) {
272
273
  }
273
274
  }
274
275
 
276
+ // src/react/passkey.ts
277
+ function b64urlToBuf(b64url) {
278
+ const b64 = b64url.replace(/-/g, "+").replace(/_/g, "/");
279
+ const pad = b64.length % 4 === 0 ? "" : "=".repeat(4 - b64.length % 4);
280
+ const bin = atob(b64 + pad);
281
+ const bytes = new Uint8Array(bin.length);
282
+ for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
283
+ return bytes.buffer;
284
+ }
285
+ function bufToB64url(buf) {
286
+ const bytes = new Uint8Array(buf);
287
+ let bin = "";
288
+ for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);
289
+ return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
290
+ }
291
+ async function runPasskeySignIn(baseUrl, clientId) {
292
+ if (!clientId) return { ok: false, error: "missing_client_id", message: "ElvixProvider needs a clientId." };
293
+ if (typeof window === "undefined" || !window.PublicKeyCredential || !navigator.credentials?.get) {
294
+ return { ok: false, error: "passkey_unsupported", message: "This browser can't use passkeys." };
295
+ }
296
+ const credentials = isSameOrigin(baseUrl) ? "include" : "omit";
297
+ let options;
298
+ try {
299
+ const res = await fetch(`${baseUrl}/api/auth/passkey/sign-in/start`, {
300
+ method: "POST",
301
+ headers: { "content-type": "application/json" },
302
+ credentials,
303
+ body: JSON.stringify({ intent: "app", clientId })
304
+ });
305
+ const body = await res.json();
306
+ if (!res.ok || !body.success || !body.data?.options) {
307
+ return { ok: false, error: body.errorMessage ?? "passkey_start_failed" };
308
+ }
309
+ options = body.data.options;
310
+ } catch (e) {
311
+ return { ok: false, error: "network", message: e instanceof Error ? e.message : void 0 };
312
+ }
313
+ let assertion;
314
+ try {
315
+ const publicKey = {
316
+ challenge: b64urlToBuf(options.challenge),
317
+ timeout: options.timeout,
318
+ rpId: options.rpId,
319
+ userVerification: options.userVerification,
320
+ allowCredentials: options.allowCredentials?.map((c) => ({
321
+ id: b64urlToBuf(c.id),
322
+ type: c.type,
323
+ transports: c.transports
324
+ }))
325
+ };
326
+ const cred = await navigator.credentials.get({ publicKey });
327
+ if (!cred) return { ok: false, error: "passkey_cancelled" };
328
+ const resp = cred.response;
329
+ assertion = {
330
+ id: cred.id,
331
+ rawId: bufToB64url(cred.rawId),
332
+ type: "public-key",
333
+ clientExtensionResults: cred.getClientExtensionResults(),
334
+ authenticatorAttachment: cred.authenticatorAttachment ?? void 0,
335
+ response: {
336
+ clientDataJSON: bufToB64url(resp.clientDataJSON),
337
+ authenticatorData: bufToB64url(resp.authenticatorData),
338
+ signature: bufToB64url(resp.signature),
339
+ userHandle: resp.userHandle ? bufToB64url(resp.userHandle) : void 0
340
+ }
341
+ };
342
+ } catch (e) {
343
+ const name = e?.name;
344
+ if (name === "NotAllowedError" || name === "AbortError") {
345
+ return { ok: false, error: "passkey_cancelled" };
346
+ }
347
+ return { ok: false, error: "passkey_failed", message: e instanceof Error ? e.message : void 0 };
348
+ }
349
+ try {
350
+ const res = await fetch(`${baseUrl}/api/auth/passkey/sign-in/finish`, {
351
+ method: "POST",
352
+ headers: { "content-type": "application/json" },
353
+ credentials,
354
+ body: JSON.stringify({ intent: "app", clientId, ...assertion })
355
+ });
356
+ const body = await res.json();
357
+ if (!res.ok || !body.success) {
358
+ return { ok: false, error: body.errorMessage ?? "passkey_verify_failed" };
359
+ }
360
+ if (body.data?.token) setElvixToken(body.data.token);
361
+ return { ok: true, redirect: body.data?.redirect, token: body.data?.token };
362
+ } catch (e) {
363
+ return { ok: false, error: "network", message: e instanceof Error ? e.message : void 0 };
364
+ }
365
+ }
366
+ async function runPasskeyRegister(baseUrl, surface) {
367
+ if (typeof window === "undefined" || !window.PublicKeyCredential || !navigator.credentials?.create) {
368
+ return { ok: false, error: "passkey_unsupported", message: "This browser can't use passkeys." };
369
+ }
370
+ const init = authInit();
371
+ const reqInit = {
372
+ headers: { "content-type": "application/json", ...init.headers },
373
+ credentials: init.credentials
374
+ };
375
+ let options;
376
+ try {
377
+ const res = await fetch(`${baseUrl}/api/auth/passkey/register/start`, {
378
+ method: "POST",
379
+ ...reqInit,
380
+ body: JSON.stringify({ surface })
381
+ });
382
+ const body = await res.json();
383
+ if (!res.ok || !body.success || !body.data?.options) {
384
+ return { ok: false, error: body.errorMessage ?? "passkey_register_failed" };
385
+ }
386
+ options = body.data.options;
387
+ } catch (e) {
388
+ return { ok: false, error: "network", message: e instanceof Error ? e.message : void 0 };
389
+ }
390
+ let attestation;
391
+ try {
392
+ const publicKey = {
393
+ challenge: b64urlToBuf(options.challenge),
394
+ rp: options.rp,
395
+ user: {
396
+ id: b64urlToBuf(options.user.id),
397
+ name: options.user.name,
398
+ displayName: options.user.displayName
399
+ },
400
+ pubKeyCredParams: options.pubKeyCredParams,
401
+ timeout: options.timeout,
402
+ attestation: options.attestation,
403
+ authenticatorSelection: options.authenticatorSelection,
404
+ excludeCredentials: options.excludeCredentials?.map((c) => ({
405
+ id: b64urlToBuf(c.id),
406
+ type: c.type,
407
+ transports: c.transports
408
+ })),
409
+ extensions: options.extensions
410
+ };
411
+ const cred = await navigator.credentials.create({ publicKey });
412
+ if (!cred) return { ok: false, error: "passkey_cancelled" };
413
+ const resp = cred.response;
414
+ attestation = {
415
+ id: cred.id,
416
+ rawId: bufToB64url(cred.rawId),
417
+ type: "public-key",
418
+ clientExtensionResults: cred.getClientExtensionResults(),
419
+ authenticatorAttachment: cred.authenticatorAttachment ?? void 0,
420
+ response: {
421
+ clientDataJSON: bufToB64url(resp.clientDataJSON),
422
+ attestationObject: bufToB64url(resp.attestationObject),
423
+ transports: resp.getTransports?.() ?? void 0
424
+ }
425
+ };
426
+ } catch (e) {
427
+ const name = e?.name;
428
+ if (name === "NotAllowedError" || name === "AbortError") {
429
+ return { ok: false, error: "passkey_cancelled" };
430
+ }
431
+ return { ok: false, error: "passkey_register_failed", message: e instanceof Error ? e.message : void 0 };
432
+ }
433
+ try {
434
+ const res = await fetch(`${baseUrl}/api/auth/passkey/register/finish`, {
435
+ method: "POST",
436
+ ...reqInit,
437
+ body: JSON.stringify({ surface, response: attestation })
438
+ });
439
+ const body = await res.json();
440
+ if (!res.ok || !body.success) {
441
+ return { ok: false, error: body.errorMessage ?? "passkey_register_failed" };
442
+ }
443
+ return { ok: true };
444
+ } catch (e) {
445
+ return { ok: false, error: "network", message: e instanceof Error ? e.message : void 0 };
446
+ }
447
+ }
448
+
275
449
  // src/react/elvix-sign-in.tsx
276
450
  import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
277
451
  function ElvixSignIn({
@@ -310,6 +484,29 @@ function ElvixSignIn({
310
484
  `${ctx.baseUrl}/api/auth/google/start?intent=app&clientId=${encodeURIComponent(ctx.clientId)}`
311
485
  );
312
486
  }
487
+ async function startPasskey() {
488
+ setBusy(true);
489
+ setError(null);
490
+ try {
491
+ const result = await runPasskeySignIn(ctx.baseUrl, ctx.clientId);
492
+ if (!result.ok) {
493
+ if (result.error === "passkey_cancelled") {
494
+ onResult?.({ ok: false, error: result.error });
495
+ return;
496
+ }
497
+ return fail(result.error, result.message);
498
+ }
499
+ setStep("done");
500
+ onResult?.({
501
+ ok: true,
502
+ method: "passkey",
503
+ redirect: result.redirect ?? redirectAfterSignIn,
504
+ token: result.token
505
+ });
506
+ } finally {
507
+ setBusy(false);
508
+ }
509
+ }
313
510
  async function startOtp(e) {
314
511
  e.preventDefault();
315
512
  if (!email.trim()) return fail("invalid_input", copy.errorEnterEmail);
@@ -388,6 +585,17 @@ function ElvixSignIn({
388
585
  children: copy.googleButton
389
586
  }
390
587
  ),
588
+ app?.methodPasskey && /* @__PURE__ */ jsx3(
589
+ "button",
590
+ {
591
+ type: "button",
592
+ onClick: startPasskey,
593
+ disabled: busy,
594
+ className: "elvix-btn elvix-btn-passkey",
595
+ "data-elvix-method": "passkey",
596
+ children: copy.passkeyButton
597
+ }
598
+ ),
391
599
  app?.methodEmailOtp && /* @__PURE__ */ jsxs2("form", { onSubmit: startOtp, "data-elvix-method": "email_otp", className: "elvix-otp-form", children: [
392
600
  /* @__PURE__ */ jsx3(
393
601
  "input",
@@ -428,492 +636,1693 @@ function ElvixSignIn({
428
636
  }
429
637
 
430
638
  // src/react/elvix-sign-in-form.tsx
431
- import { useMemo as useMemo2, useState as useState3 } from "react";
639
+ import { ArrowLeft, Check, Fingerprint, Loader2, X as X2 } from "lucide-react";
640
+ import { useCallback as useCallback3, useEffect as useEffect4, useMemo as useMemo2, useRef as useRef3, useState as useState5 } from "react";
432
641
 
433
- // src/react/elvix-shield.tsx
642
+ // src/react/elvix-logo.tsx
434
643
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
435
- function ElvixShield({
436
- size,
437
- fill,
438
- accent
439
- }) {
440
- return /* @__PURE__ */ jsxs3("svg", { width: size, height: size, viewBox: "2 2 20 20", "aria-hidden": true, style: { display: "block" }, children: [
441
- /* @__PURE__ */ jsx4(
442
- "path",
644
+ function ElvixLogo({ size = 22, withText = false, className }) {
645
+ const wordmarkSize = Math.round(size * 0.84);
646
+ return /* @__PURE__ */ jsxs3("div", { className: `inline-flex items-center gap-[7px] ${className ?? ""}`, "aria-label": "elvix", children: [
647
+ /* @__PURE__ */ jsxs3(
648
+ "svg",
443
649
  {
444
- fill,
445
- fillRule: "evenodd",
446
- d: "M 6 2.5 C 4.34 2.5 3 3.84 3 5.5 L 3 12.5 C 3 17.5 7 20.7 12 22 C 17 20.7 21 17.5 21 12.5 L 21 5.5 C 21 3.84 19.66 2.5 18 2.5 L 6 2.5 Z M 12 8.4 C 9.79 8.4 8 10.19 8 12.4 C 8 14.61 9.79 16.4 12 16.4 C 13.21 16.4 14.3 15.86 15.04 15 L 13.6 13.77 C 13.21 14.23 12.64 14.5 12 14.5 C 11.04 14.5 10.21 13.86 9.91 13 L 15.95 13 C 15.98 12.8 16 12.6 16 12.4 C 16 10.19 14.21 8.4 12 8.4 Z M 9.91 11.8 L 14.09 11.8 C 13.79 10.94 12.96 10.3 12 10.3 C 11.04 10.3 10.21 10.94 9.91 11.8 Z"
650
+ width: size,
651
+ height: size,
652
+ viewBox: "0 0 24 24",
653
+ "aria-hidden": "true",
654
+ style: { display: "block", overflow: "visible" },
655
+ children: [
656
+ /* @__PURE__ */ jsx4(
657
+ "path",
658
+ {
659
+ fill: "currentColor",
660
+ fillRule: "evenodd",
661
+ d: "\n M 6 2.5\n C 4.34 2.5 3 3.84 3 5.5\n L 3 12.5\n C 3 17.5 7 20.7 12 22\n C 17 20.7 21 17.5 21 12.5\n L 21 5.5\n C 21 3.84 19.66 2.5 18 2.5\n L 6 2.5\n Z\n\n M 12 8.4\n C 9.79 8.4 8 10.19 8 12.4\n C 8 14.61 9.79 16.4 12 16.4\n C 13.21 16.4 14.3 15.86 15.04 15\n L 13.6 13.77\n C 13.21 14.23 12.64 14.5 12 14.5\n C 11.04 14.5 10.21 13.86 9.91 13\n L 15.95 13\n C 15.98 12.8 16 12.6 16 12.4\n C 16 10.19 14.21 8.4 12 8.4\n Z\n\n M 9.91 11.8\n L 14.09 11.8\n C 13.79 10.94 12.96 10.3 12 10.3\n C 11.04 10.3 10.21 10.94 9.91 11.8\n Z\n "
662
+ }
663
+ ),
664
+ /* @__PURE__ */ jsx4("circle", { cx: "19.5", cy: "4.5", r: "2.4", fill: "#8e7dff" })
665
+ ]
447
666
  }
448
667
  ),
449
- /* @__PURE__ */ jsx4("circle", { cx: "19.5", cy: "4.5", r: "2.4", fill: accent })
668
+ withText && /* @__PURE__ */ jsx4(
669
+ "span",
670
+ {
671
+ style: { fontSize: wordmarkSize, lineHeight: 1, letterSpacing: "-0.025em" },
672
+ className: "font-semibold lowercase",
673
+ children: "elvix"
674
+ }
675
+ )
450
676
  ] });
451
677
  }
452
678
 
453
- // src/react/elvix-secured-badge.tsx
454
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
455
- var ELVIX_URL = "https://elvix.is";
456
- var SIZE = {
457
- sm: { height: 28, padX: 10, font: 11.5, icon: 14, gap: 6 },
458
- md: { height: 32, padX: 12, font: 12.5, icon: 16, gap: 7 },
459
- lg: { height: 36, padX: 14, font: 13, icon: 18, gap: 8 }
460
- };
461
- var TONE = {
462
- white: {
463
- light: { bg: "#ffffff", border: "#e4e4e7", lead: "#71717a", brand: "#0a0a0b", shield: "#0a0a0b" },
464
- dark: { bg: "#ffffff", border: "transparent", lead: "#71717a", brand: "#0a0a0b", shield: "#0a0a0b" }
465
- },
466
- dark: {
467
- light: { bg: "#0a0a0b", border: "rgba(0,0,0,0.1)", lead: "#d4d4d8", brand: "#ffffff", shield: "#ffffff" },
468
- dark: { bg: "#0a0a0b", border: "rgba(255,255,255,0.1)", lead: "#d4d4d8", brand: "#ffffff", shield: "#ffffff" }
469
- },
470
- outline: {
471
- light: { bg: "transparent", border: "rgba(0,0,0,0.15)", lead: "#71717a", brand: "#0a0a0b", shield: "#0a0a0b" },
472
- dark: { bg: "transparent", border: "rgba(142,125,255,0.4)", lead: "#d4d4d8", brand: "#ffffff", shield: "#ffffff" }
679
+ // src/react/elvix-recover-gate.tsx
680
+ import { AnimatePresence, motion } from "framer-motion";
681
+ import { CheckCircle2, EyeOff, LogOut, Undo2, X } from "lucide-react";
682
+ import { useState as useState3 } from "react";
683
+ import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
684
+ function ElvixRecoverGate({
685
+ baseUrl,
686
+ appName,
687
+ state,
688
+ sinceAt,
689
+ onRestore,
690
+ onCancel,
691
+ onFail
692
+ }) {
693
+ const [pane, setPane] = useState3("decide");
694
+ const [busy, setBusy] = useState3("none");
695
+ const [error, setError] = useState3(null);
696
+ const ninetyDaysMs = 90 * 24 * 60 * 60 * 1e3;
697
+ const daysLeft = (
698
+ // LEGACY: spine-lint-disable-next-line spine/enum-over-string
699
+ state === "soft_deleted_by_user" ? Math.max(
700
+ 0,
701
+ Math.ceil(
702
+ (new Date(sinceAt).getTime() + ninetyDaysMs - Date.now()) / (24 * 60 * 60 * 1e3)
703
+ )
704
+ ) : null
705
+ );
706
+ async function decide(decision) {
707
+ if (busy !== "none") return;
708
+ setBusy(decision);
709
+ setError(null);
710
+ try {
711
+ const init = authInit();
712
+ const res = await fetch(`${baseUrl}/api/auth/recover-membership`, {
713
+ method: "POST",
714
+ headers: { "Content-Type": "application/json", ...init.headers },
715
+ credentials: init.credentials,
716
+ body: JSON.stringify({ decision })
717
+ });
718
+ const raw = await res.json().catch(() => ({}));
719
+ const errorKey = raw.errorKey ?? raw.errorMessage;
720
+ if (!res.ok || !raw.success) {
721
+ const msg = errorKey === "grace_expired" ? "The 90-day window to restore has closed." : errorKey === "unauthenticated" ? "Your session ended. Sign in again." : "Something went wrong. Try again.";
722
+ setError(msg);
723
+ onFail?.(msg);
724
+ return;
725
+ }
726
+ const redirect = raw.data?.redirect ?? "/";
727
+ if (decision === "restore") {
728
+ onRestore?.({ redirect });
729
+ if (!onRestore) setPane("done_restored");
730
+ } else {
731
+ onCancel?.({ redirect });
732
+ if (!onCancel) setPane("done_cancelled");
733
+ }
734
+ } catch {
735
+ const msg = "Network hiccup. Try again.";
736
+ setError(msg);
737
+ onFail?.(msg);
738
+ } finally {
739
+ setBusy("none");
740
+ }
473
741
  }
474
- };
475
- function ElvixSecuredBadge({
476
- variant = "white",
477
- size = "md",
478
- theme = "dark",
479
- accentColor = "#8e7dff",
480
- href = ELVIX_URL,
481
- className = "",
482
- width,
483
- height,
484
- minWidth,
485
- maxWidth,
486
- minHeight,
487
- maxHeight
742
+ return /* @__PURE__ */ jsx5("div", { className: "relative overflow-hidden", children: /* @__PURE__ */ jsxs4(AnimatePresence, { mode: "wait", children: [
743
+ pane === "decide" && /* @__PURE__ */ jsx5(
744
+ motion.div,
745
+ {
746
+ initial: { opacity: 0, y: 6 },
747
+ animate: { opacity: 1, y: 0 },
748
+ exit: { opacity: 0, y: -4 },
749
+ transition: { duration: 0.24, ease: [0.22, 0.61, 0.36, 1] },
750
+ children: /* @__PURE__ */ jsx5(
751
+ DecidePane,
752
+ {
753
+ appName,
754
+ state,
755
+ daysLeft,
756
+ busy,
757
+ error,
758
+ onRestore: () => void decide("restore"),
759
+ onCancel: () => void decide("cancel")
760
+ }
761
+ )
762
+ },
763
+ "decide"
764
+ ),
765
+ pane === "done_restored" && /* @__PURE__ */ jsx5(
766
+ motion.div,
767
+ {
768
+ initial: { opacity: 0, y: 6 },
769
+ animate: { opacity: 1, y: 0 },
770
+ exit: { opacity: 0, y: -4 },
771
+ transition: { duration: 0.24, ease: [0.22, 0.61, 0.36, 1] },
772
+ children: /* @__PURE__ */ jsx5(
773
+ DonePane,
774
+ {
775
+ icon: "check",
776
+ title: `You're back on ${appName}.`,
777
+ body: "Your membership is active again. Continue to the app whenever you're ready."
778
+ }
779
+ )
780
+ },
781
+ "done_restored"
782
+ ),
783
+ pane === "done_cancelled" && /* @__PURE__ */ jsx5(
784
+ motion.div,
785
+ {
786
+ initial: { opacity: 0, y: 6 },
787
+ animate: { opacity: 1, y: 0 },
788
+ exit: { opacity: 0, y: -4 },
789
+ transition: { duration: 0.24, ease: [0.22, 0.61, 0.36, 1] },
790
+ children: /* @__PURE__ */ jsx5(
791
+ DonePane,
792
+ {
793
+ icon: "logout",
794
+ title: "You're signed out.",
795
+ body: `You stayed off ${appName}. Come back any time.`
796
+ }
797
+ )
798
+ },
799
+ "done_cancelled"
800
+ )
801
+ ] }) });
802
+ }
803
+ function DecidePane({
804
+ appName,
805
+ state,
806
+ daysLeft,
807
+ busy,
808
+ error,
809
+ onRestore,
810
+ onCancel
488
811
  }) {
489
- const s = SIZE[size];
490
- const t = TONE[variant][theme];
491
- const style = {
492
- display: "inline-flex",
493
- alignItems: "center",
494
- gap: s.gap,
495
- height: s.height,
496
- paddingLeft: s.padX,
497
- paddingRight: s.padX,
498
- fontSize: s.font,
499
- fontWeight: 500,
500
- borderRadius: 9999,
501
- background: t.bg,
502
- border: `1px solid ${t.border}`,
503
- color: t.brand,
504
- textDecoration: "none",
505
- userSelect: "none",
506
- lineHeight: 1,
507
- // Dimensional overrides win over the size preset above.
508
- ...sizeStyle({ width, height, minWidth, maxWidth, minHeight, maxHeight })
509
- };
510
- return /* @__PURE__ */ jsxs4(
511
- "a",
512
- {
513
- href,
514
- target: "_blank",
515
- rel: "noopener noreferrer",
516
- className,
517
- style,
518
- "data-elvix-secured-badge": "",
519
- children: [
520
- /* @__PURE__ */ jsx5(ElvixShield, { size: s.icon, fill: t.shield, accent: accentColor }),
521
- /* @__PURE__ */ jsxs4("span", { style: { display: "inline-flex", alignItems: "baseline", gap: 4 }, children: [
522
- /* @__PURE__ */ jsx5("span", { style: { color: t.lead }, children: "Secured by" }),
523
- /* @__PURE__ */ jsx5("span", { style: { color: t.brand, fontWeight: 600 }, children: "elvix" })
812
+ const isDeleted = state === "soft_deleted_by_user";
813
+ return /* @__PURE__ */ jsxs4("div", { className: "space-y-4", children: [
814
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-start gap-3", children: [
815
+ /* @__PURE__ */ jsx5(
816
+ "span",
817
+ {
818
+ className: "size-10 rounded-full inline-flex items-center justify-center shrink-0 " + (isDeleted ? "bg-red-500/15" : ""),
819
+ style: isDeleted ? void 0 : { background: "var(--elvix-primary-12)" },
820
+ children: isDeleted ? /* @__PURE__ */ jsx5(Undo2, { className: "size-5 text-red-500", strokeWidth: 2.2 }) : /* @__PURE__ */ jsx5(
821
+ EyeOff,
822
+ {
823
+ className: "size-5",
824
+ strokeWidth: 2.2,
825
+ style: { color: "var(--elvix-primary-strong)" }
826
+ }
827
+ )
828
+ }
829
+ ),
830
+ /* @__PURE__ */ jsxs4("div", { className: "min-w-0", children: [
831
+ /* @__PURE__ */ jsxs4("div", { className: "text-[15px] font-semibold tracking-tight text-fg-1 leading-tight", children: [
832
+ "Welcome back to ",
833
+ appName
834
+ ] }),
835
+ /* @__PURE__ */ jsx5("p", { className: "text-[12.5px] text-fg-3 leading-[1.55] mt-1", children: isDeleted ? `You left ${appName} earlier. ${daysLeft != null ? `You have ${daysLeft} ${daysLeft === 1 ? "day" : "days"} left to come back.` : "You're still inside the restore window."} Restore your spot now or sign back out.` : `Your membership on ${appName} is on pause right now. Restore to pick up where you left off, or sign back out and stay away.` })
836
+ ] })
837
+ ] }),
838
+ error ? /* @__PURE__ */ jsx5("p", { className: "text-[12px] text-red-500 leading-tight", children: error }) : null,
839
+ /* @__PURE__ */ jsx5(
840
+ "button",
841
+ {
842
+ type: "button",
843
+ onClick: onRestore,
844
+ disabled: busy !== "none",
845
+ autoFocus: true,
846
+ className: "w-full h-10 rounded-[10px] text-[14px] font-semibold tracking-tight cursor-pointer transition ring-1 ring-black/10 inline-flex items-center justify-center gap-1.5 disabled:opacity-60 disabled:cursor-not-allowed",
847
+ style: {
848
+ background: "var(--elvix-primary-strong)",
849
+ color: "var(--elvix-on-primary)",
850
+ backgroundImage: "linear-gradient(to bottom, rgba(255,255,255,0.12), rgba(255,255,255,0) 40%)",
851
+ boxShadow: "0 1px 0 rgba(255,255,255,0.06) inset, 0 2px 3px -1px rgba(0,0,0,0.18), 0 0 0 1px rgba(25,28,33,0.08)"
852
+ },
853
+ children: busy === "restore" ? "Restoring\u2026" : /* @__PURE__ */ jsxs4(Fragment2, { children: [
854
+ /* @__PURE__ */ jsx5(Undo2, { className: "size-3.5" }),
855
+ isDeleted ? `Restore my spot on ${appName}` : `Bring me back on ${appName}`
524
856
  ] })
525
- ]
857
+ }
858
+ ),
859
+ /* @__PURE__ */ jsx5(
860
+ "button",
861
+ {
862
+ type: "button",
863
+ onClick: onCancel,
864
+ disabled: busy !== "none",
865
+ className: "w-full h-10 rounded-[10px] text-[13px] font-medium text-fg-2 hover:text-fg-1 hover:bg-surface-hover cursor-pointer transition inline-flex items-center justify-center gap-1.5 disabled:opacity-60 disabled:cursor-not-allowed",
866
+ children: busy === "cancel" ? "Signing you out\u2026" : /* @__PURE__ */ jsxs4(Fragment2, { children: [
867
+ /* @__PURE__ */ jsx5(X, { className: "size-3.5" }),
868
+ "Not now, sign me out"
869
+ ] })
870
+ }
871
+ )
872
+ ] });
873
+ }
874
+ function DonePane({
875
+ icon,
876
+ title,
877
+ body
878
+ }) {
879
+ return /* @__PURE__ */ jsxs4("div", { className: "flex flex-col items-center text-center gap-4 py-2", children: [
880
+ /* @__PURE__ */ jsx5(
881
+ motion.span,
882
+ {
883
+ initial: { scale: 0.6, opacity: 0 },
884
+ animate: { scale: 1, opacity: 1 },
885
+ transition: { duration: 0.34, ease: [0.22, 0.61, 0.36, 1] },
886
+ className: "size-12 rounded-full inline-flex items-center justify-center",
887
+ style: { background: "var(--elvix-primary-12)" },
888
+ children: icon === "check" ? /* @__PURE__ */ jsx5(
889
+ CheckCircle2,
890
+ {
891
+ className: "size-7",
892
+ strokeWidth: 2.2,
893
+ style: { color: "var(--elvix-primary-strong)" }
894
+ }
895
+ ) : /* @__PURE__ */ jsx5(LogOut, { className: "size-7 text-fg-2", strokeWidth: 2.2 })
896
+ }
897
+ ),
898
+ /* @__PURE__ */ jsxs4("div", { className: "space-y-1 max-w-[320px]", children: [
899
+ /* @__PURE__ */ jsx5("div", { className: "text-[15px] font-semibold tracking-tight text-fg-1", children: title }),
900
+ /* @__PURE__ */ jsx5("div", { className: "text-[12.5px] text-fg-3 leading-[1.55]", children: body })
901
+ ] })
902
+ ] });
903
+ }
904
+
905
+ // src/react/google-one-tap.tsx
906
+ import { memo, useCallback, useEffect as useEffect2, useRef, useState as useState4 } from "react";
907
+
908
+ // src/react/spine-fetch.ts
909
+ function unwrapEnvelope(input) {
910
+ if (!input || typeof input !== "object") return input;
911
+ const e = input;
912
+ if (!("success" in e)) return input;
913
+ const dataObject = e.data && typeof e.data === "object" && !Array.isArray(e.data) ? e.data : {};
914
+ const arrayData = Array.isArray(e.data) ? { data: e.data } : {};
915
+ return {
916
+ ok: Boolean(e.success),
917
+ error: e.errorKey ?? e.errorMessage ?? void 0,
918
+ ...dataObject,
919
+ ...arrayData
920
+ };
921
+ }
922
+
923
+ // src/react/google-one-tap.tsx
924
+ import { jsxs as jsxs5 } from "react/jsx-runtime";
925
+ var GIS_SRC = "https://accounts.google.com/gsi/client";
926
+ var scriptPromise = null;
927
+ function loadGisScript() {
928
+ if (typeof window === "undefined") return Promise.reject(new Error("ssr"));
929
+ if (window.google?.accounts?.id) return Promise.resolve();
930
+ if (scriptPromise) return scriptPromise;
931
+ scriptPromise = new Promise((resolve, reject) => {
932
+ const existing = document.querySelector(`script[src="${GIS_SRC}"]`);
933
+ if (existing) {
934
+ existing.addEventListener("load", () => resolve(), { once: true });
935
+ existing.addEventListener("error", () => reject(new Error("gis_load_failed")), {
936
+ once: true
937
+ });
938
+ return;
526
939
  }
940
+ const s = document.createElement("script");
941
+ s.src = GIS_SRC;
942
+ s.async = true;
943
+ s.defer = true;
944
+ s.onload = () => resolve();
945
+ s.onerror = () => reject(new Error("gis_load_failed"));
946
+ document.head.appendChild(s);
947
+ });
948
+ return scriptPromise;
949
+ }
950
+ var GoogleOneTap = memo(function GoogleOneTap2({
951
+ baseUrl,
952
+ clientId,
953
+ intent,
954
+ appClientId,
955
+ config,
956
+ /** When true, also render the GIS button (used for popup ux_mode). */
957
+ renderButton = false,
958
+ /** Visible-on-page slot for the rendered GIS button. */
959
+ buttonContainerRef
960
+ }) {
961
+ const [err, setErr] = useState4(null);
962
+ const initialised = useRef(false);
963
+ const handleCredential = useCallback(
964
+ async (response) => {
965
+ try {
966
+ const res = await fetch(`${baseUrl}/api/auth/google/credential`, {
967
+ method: "POST",
968
+ headers: { "Content-Type": "application/json" },
969
+ credentials: isSameOrigin(baseUrl) ? "include" : "omit",
970
+ body: JSON.stringify({
971
+ credential: response.credential,
972
+ intent,
973
+ clientId: appClientId
974
+ })
975
+ });
976
+ const body = unwrapEnvelope(await res.json());
977
+ if (!res.ok || !body.ok) {
978
+ setErr(body.error ?? "Sign-in failed");
979
+ return;
980
+ }
981
+ if (body.token) setElvixToken(body.token);
982
+ if (body.redirect) {
983
+ window.location.assign(body.redirect);
984
+ }
985
+ } catch (e) {
986
+ setErr(e instanceof Error ? e.message : "Network error");
987
+ }
988
+ },
989
+ [baseUrl, intent, appClientId]
990
+ );
991
+ useEffect2(() => {
992
+ if (!clientId) return;
993
+ if (!config.oneTap && !renderButton) return;
994
+ let cancelled = false;
995
+ (async () => {
996
+ try {
997
+ await loadGisScript();
998
+ if (cancelled) return;
999
+ const g = window.google?.accounts?.id;
1000
+ if (!g) return;
1001
+ if (!initialised.current) {
1002
+ const initConfig = {
1003
+ client_id: clientId,
1004
+ callback: handleCredential,
1005
+ auto_select: config.autoSelect,
1006
+ use_fedcm_for_prompt: config.fedcm,
1007
+ ux_mode: "popup",
1008
+ itp_support: true,
1009
+ cancel_on_tap_outside: false,
1010
+ context: "signin"
1011
+ };
1012
+ if (config.hostedDomain) initConfig.hosted_domain = config.hostedDomain;
1013
+ g.initialize(initConfig);
1014
+ initialised.current = true;
1015
+ }
1016
+ if (renderButton && buttonContainerRef?.current) {
1017
+ buttonContainerRef.current.innerHTML = "";
1018
+ const measured = buttonContainerRef.current.clientWidth;
1019
+ const safeWidth = Math.min(400, Math.max(240, measured || 360));
1020
+ g.renderButton(buttonContainerRef.current, {
1021
+ theme: "outline",
1022
+ size: "large",
1023
+ shape: "rectangular",
1024
+ text: "continue_with",
1025
+ logo_alignment: "left",
1026
+ width: safeWidth
1027
+ });
1028
+ }
1029
+ if (config.oneTap) {
1030
+ g.prompt();
1031
+ }
1032
+ } catch (e) {
1033
+ setErr(e instanceof Error ? e.message : "GIS load failed");
1034
+ }
1035
+ })();
1036
+ return () => {
1037
+ cancelled = true;
1038
+ };
1039
+ }, [
1040
+ clientId,
1041
+ config.oneTap,
1042
+ config.autoSelect,
1043
+ config.popup,
1044
+ config.fedcm,
1045
+ config.hostedDomain,
1046
+ renderButton,
1047
+ handleCredential,
1048
+ buttonContainerRef
1049
+ ]);
1050
+ useEffect2(() => {
1051
+ return () => {
1052
+ const g = window.google?.accounts?.id;
1053
+ if (g) g.cancel();
1054
+ };
1055
+ }, []);
1056
+ if (err) {
1057
+ return /* @__PURE__ */ jsxs5("p", { className: "text-[11.5px] text-red-400 bg-red-500/10 border border-red-500/20 rounded-[8px] px-3 py-2", children: [
1058
+ "Google sign-in: ",
1059
+ err
1060
+ ] });
1061
+ }
1062
+ return null;
1063
+ });
1064
+
1065
+ // src/react/otp-input.tsx
1066
+ import {
1067
+ useCallback as useCallback2,
1068
+ useEffect as useEffect3,
1069
+ useRef as useRef2
1070
+ } from "react";
1071
+ import { jsx as jsx6 } from "react/jsx-runtime";
1072
+ var OTP_LENGTH = 6;
1073
+ function OtpInput({
1074
+ value,
1075
+ onChange,
1076
+ disabled,
1077
+ autoFocus
1078
+ }) {
1079
+ const refs = useRef2([]);
1080
+ const digits = Array.from({ length: OTP_LENGTH }, (_, i) => value[i] ?? "");
1081
+ useEffect3(() => {
1082
+ if (autoFocus) refs.current[0]?.focus();
1083
+ }, [autoFocus]);
1084
+ const focus = useCallback2((i) => refs.current[i]?.focus(), []);
1085
+ const onCharChange = useCallback2(
1086
+ (i, e) => {
1087
+ const stripped = e.target.value.replace(/\D/g, "");
1088
+ if (stripped.length > 1) {
1089
+ const fill = stripped.slice(0, OTP_LENGTH - i);
1090
+ const next2 = [...digits];
1091
+ for (let k = 0; k < fill.length; k += 1) {
1092
+ next2[i + k] = fill[k];
1093
+ }
1094
+ const merged = next2.join("");
1095
+ onChange(merged);
1096
+ focus(Math.min(i + fill.length, OTP_LENGTH - 1));
1097
+ return;
1098
+ }
1099
+ const ch = stripped.slice(-1);
1100
+ const next = [...digits];
1101
+ next[i] = ch;
1102
+ onChange(next.join(""));
1103
+ if (ch && i < OTP_LENGTH - 1) focus(i + 1);
1104
+ },
1105
+ [digits, onChange, focus]
1106
+ );
1107
+ const onKeyDown = useCallback2(
1108
+ (i, e) => {
1109
+ if (e.key === "Backspace" && !digits[i] && i > 0) {
1110
+ e.preventDefault();
1111
+ const next = [...digits];
1112
+ next[i - 1] = "";
1113
+ onChange(next.join(""));
1114
+ focus(i - 1);
1115
+ } else if (e.key === "ArrowLeft" && i > 0) {
1116
+ focus(i - 1);
1117
+ } else if (e.key === "ArrowRight" && i < OTP_LENGTH - 1) {
1118
+ focus(i + 1);
1119
+ }
1120
+ },
1121
+ [digits, onChange, focus]
1122
+ );
1123
+ const onPaste = useCallback2(
1124
+ (e) => {
1125
+ e.preventDefault();
1126
+ const pasted = e.clipboardData.getData("text").replace(/\D/g, "").slice(0, OTP_LENGTH);
1127
+ if (pasted) {
1128
+ onChange(pasted);
1129
+ focus(Math.min(pasted.length, OTP_LENGTH - 1));
1130
+ }
1131
+ },
1132
+ [onChange, focus]
527
1133
  );
1134
+ return (
1135
+ // 6-column grid keeps every box equal-width and never overflows
1136
+ // its container, so the input fits cleanly inside an ElvixCard
1137
+ // on the narrowest mobile viewports without a horizontal scroll.
1138
+ // `aspect-square` keeps each box visually balanced as width
1139
+ // shrinks. `max-w-12` caps the boxes at the original 48px on
1140
+ // wide surfaces so they don't bloat on desktop.
1141
+ /* @__PURE__ */ jsx6("div", { className: "grid grid-cols-6 gap-2 w-full", children: digits.map((d, i) => /* @__PURE__ */ jsx6(
1142
+ "input",
1143
+ {
1144
+ ref: (el) => {
1145
+ refs.current[i] = el;
1146
+ },
1147
+ type: "text",
1148
+ inputMode: "numeric",
1149
+ autoComplete: "one-time-code",
1150
+ maxLength: OTP_LENGTH,
1151
+ value: d,
1152
+ disabled,
1153
+ onChange: (e) => onCharChange(i, e),
1154
+ onKeyDown: (e) => onKeyDown(i, e),
1155
+ onPaste,
1156
+ onFocus: (e) => e.target.select(),
1157
+ className: "w-full aspect-square min-w-0 max-w-12 mx-auto text-center text-[20px] font-semibold tabular-nums rounded-[10px] bg-surface border border-border-base text-fg-1 focus:outline-none focus:border-[#8e7dff] focus:ring-2 focus:ring-[#8e7dff]/20 transition disabled:opacity-50"
1158
+ },
1159
+ i
1160
+ )) })
1161
+ );
1162
+ }
1163
+
1164
+ // src/react/username-rules.ts
1165
+ var USERNAME_MIN_LENGTH = 4;
1166
+ var USERNAME_MAX_LENGTH = 30;
1167
+ var RESERVED_USERNAMES = /* @__PURE__ */ new Set([
1168
+ // system + routing
1169
+ "admin",
1170
+ "administrator",
1171
+ "root",
1172
+ "system",
1173
+ "sys",
1174
+ "api",
1175
+ "auth",
1176
+ "oauth",
1177
+ "app",
1178
+ "apps",
1179
+ "account",
1180
+ "accounts",
1181
+ "billing",
1182
+ "checkout",
1183
+ "pay",
1184
+ "payment",
1185
+ "payments",
1186
+ "brand",
1187
+ "console",
1188
+ "dashboard",
1189
+ "dev",
1190
+ "developer",
1191
+ "developers",
1192
+ "docs",
1193
+ "documentation",
1194
+ "explore",
1195
+ "help",
1196
+ "home",
1197
+ "index",
1198
+ "legal",
1199
+ "privacy",
1200
+ "terms",
1201
+ "tos",
1202
+ "cookies",
1203
+ "cookie",
1204
+ "imprint",
1205
+ "login",
1206
+ "signin",
1207
+ "logout",
1208
+ "signout",
1209
+ "signup",
1210
+ "register",
1211
+ "me",
1212
+ "my",
1213
+ "profile",
1214
+ "profiles",
1215
+ "user",
1216
+ "users",
1217
+ "new",
1218
+ "edit",
1219
+ "create",
1220
+ "delete",
1221
+ "public",
1222
+ "private",
1223
+ "static",
1224
+ "assets",
1225
+ "settings",
1226
+ "setting",
1227
+ "preferences",
1228
+ "support",
1229
+ "contact",
1230
+ "www",
1231
+ "http",
1232
+ "https",
1233
+ "ftp",
1234
+ "mail",
1235
+ "next",
1236
+ "_next",
1237
+ "src",
1238
+ "test",
1239
+ "tests",
1240
+ // brand
1241
+ "elvix",
1242
+ "edvone",
1243
+ "axum",
1244
+ "aixum",
1245
+ "mithra",
1246
+ "delvix",
1247
+ "buildza",
1248
+ "danceclub",
1249
+ "pulse",
1250
+ "pulseai",
1251
+ // pseudo-paths / common reserved
1252
+ "null",
1253
+ "undefined",
1254
+ "true",
1255
+ "false"
1256
+ ]);
1257
+ function usernameReason(raw) {
1258
+ const v = raw.trim().toLowerCase();
1259
+ if (v.length < USERNAME_MIN_LENGTH) return "too_short";
1260
+ if (v.length > USERNAME_MAX_LENGTH) return "too_long";
1261
+ if (!/^[a-z0-9._]+$/.test(v)) return "bad_chars";
1262
+ if (!/^[a-z]/.test(v)) return "must_start_letter";
1263
+ if (!/[a-z0-9]$/.test(v)) return "must_end_alnum";
1264
+ if (/[._]{2}/.test(v)) return "no_double_special";
1265
+ if (RESERVED_USERNAMES.has(v)) return "reserved";
1266
+ return "ok";
1267
+ }
1268
+ function isValidUsername(raw) {
1269
+ return usernameReason(raw) === "ok";
528
1270
  }
529
1271
 
530
1272
  // src/react/elvix-sign-in-form.tsx
531
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
532
- function GoogleG({ size = 18 }) {
533
- return /* @__PURE__ */ jsxs5("svg", { width: size, height: size, viewBox: "0 0 18 18", "aria-hidden": true, style: { display: "block" }, children: [
534
- /* @__PURE__ */ jsx6(
535
- "path",
536
- {
537
- fill: "#4285F4",
538
- d: "M17.64 9.2c0-.64-.06-1.25-.16-1.84H9v3.48h4.84a4.14 4.14 0 0 1-1.8 2.72v2.26h2.92c1.7-1.57 2.68-3.88 2.68-6.62Z"
539
- }
540
- ),
541
- /* @__PURE__ */ jsx6(
542
- "path",
1273
+ import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1274
+ var ELVIX_SITE_URL = "https://elvix.is";
1275
+ var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1276
+ var ELVIX_LIGHT_VARS = {
1277
+ ["--canvas"]: "#fafafa",
1278
+ ["--surface"]: "#ffffff",
1279
+ ["--surface-2"]: "#f4f4f5",
1280
+ ["--surface-hover"]: "rgba(0, 0, 0, 0.04)",
1281
+ ["--surface-active"]: "rgba(0, 0, 0, 0.06)",
1282
+ ["--border"]: "rgba(0, 0, 0, 0.08)",
1283
+ ["--border-strong"]: "rgba(0, 0, 0, 0.14)",
1284
+ ["--fg-1"]: "#18181b",
1285
+ ["--fg-2"]: "#52525b",
1286
+ ["--fg-3"]: "#71717a",
1287
+ ["--placeholder"]: "#a1a1aa"
1288
+ };
1289
+ var ELVIX_DARK_VARS = {
1290
+ ["--canvas"]: "#0a0a0b",
1291
+ ["--surface"]: "#0d0d10",
1292
+ ["--surface-2"]: "#08080a",
1293
+ ["--surface-hover"]: "rgba(255, 255, 255, 0.04)",
1294
+ ["--surface-active"]: "rgba(255, 255, 255, 0.06)",
1295
+ ["--border"]: "rgba(255, 255, 255, 0.06)",
1296
+ ["--border-strong"]: "rgba(255, 255, 255, 0.12)",
1297
+ ["--fg-1"]: "#fafafa",
1298
+ ["--fg-2"]: "#a1a1aa",
1299
+ ["--fg-3"]: "#71717a",
1300
+ ["--placeholder"]: "#52525b"
1301
+ };
1302
+ function ElvixSignInForm(props) {
1303
+ const app = useElvixApp();
1304
+ const resolved = {
1305
+ ...props,
1306
+ mode: props.mode ?? "interactive",
1307
+ appName: props.appName ?? app?.appName ?? "your app",
1308
+ logoUrl: props.logoUrl ?? app?.logoUrl ?? null,
1309
+ logoUrlDark: props.logoUrlDark ?? app?.logoUrlDark ?? null,
1310
+ brandColor: props.brandColor ?? app?.brandColor ?? "#5d4dff",
1311
+ onBrandColor: props.onBrandColor ?? app?.onBrandColor ?? "#ffffff",
1312
+ methodGoogle: props.methodGoogle ?? app?.methodGoogle ?? false,
1313
+ methodEmailOtp: props.methodEmailOtp ?? app?.methodEmailOtp ?? true,
1314
+ methodPasskey: props.methodPasskey ?? app?.methodPasskey ?? false,
1315
+ methodUsername: props.methodUsername ?? app?.methodUsername ?? false,
1316
+ privacyPolicyUrl: props.privacyPolicyUrl ?? app?.privacyPolicyUrl ?? null,
1317
+ termsOfServiceUrl: props.termsOfServiceUrl ?? app?.termsOfServiceUrl ?? null,
1318
+ intent: props.intent ?? "app",
1319
+ clientId: props.clientId ?? app?.clientId ?? void 0,
1320
+ layout: props.layout ?? app?.layout ?? "centered",
1321
+ socialLayout: props.socialLayout ?? app?.socialLayout ?? "stacked",
1322
+ presentation: props.presentation ?? app?.presentation ?? "card",
1323
+ theme: props.theme ?? app?.theme ?? "light",
1324
+ showHeader: props.showHeader ?? app?.showHeader ?? true,
1325
+ transparentBg: props.transparentBg ?? app?.transparentBg ?? false,
1326
+ signInVerb: props.signInVerb ?? app?.signInVerb ?? "signin",
1327
+ googleConfig: props.googleConfig ?? app?.googleConfig ?? void 0,
1328
+ googleClientId: props.googleClientId ?? app?.googleClientId ?? void 0,
1329
+ websiteUrl: props.websiteUrl ?? app?.websiteUrl ?? null
1330
+ };
1331
+ const { framed = true, presentation = "card", theme = "light" } = resolved;
1332
+ const card = /* @__PURE__ */ jsx7(AuthCard, { ...resolved });
1333
+ let content = card;
1334
+ if (presentation === "drawer") {
1335
+ content = /* @__PURE__ */ jsx7(DrawerPresentation, { children: card });
1336
+ } else if (presentation === "modal") {
1337
+ content = /* @__PURE__ */ jsx7(ModalPresentation, { children: card });
1338
+ }
1339
+ const wrapped = (
1340
+ // LEGACY: spine-lint-disable-next-line spine/enum-over-string
1341
+ theme === "auto" ? content : /* @__PURE__ */ jsx7(
1342
+ "div",
543
1343
  {
544
- fill: "#34A853",
545
- d: "M9 18c2.43 0 4.47-.8 5.96-2.18l-2.92-2.26c-.8.54-1.84.86-3.04.86-2.34 0-4.32-1.58-5.03-3.7H.96v2.33A9 9 0 0 0 9 18Z"
1344
+ className: framed ? "bg-canvas" : void 0,
1345
+ style: theme === "dark" ? ELVIX_DARK_VARS : ELVIX_LIGHT_VARS,
1346
+ children: content
546
1347
  }
547
- ),
548
- /* @__PURE__ */ jsx6(
549
- "path",
1348
+ )
1349
+ );
1350
+ if (!framed) return wrapped;
1351
+ return /* @__PURE__ */ jsx7(FramedPreview, { children: wrapped });
1352
+ }
1353
+ function DrawerPresentation({ children }) {
1354
+ return /* @__PURE__ */ jsxs6("div", { className: "relative bg-surface-2 rounded-t-[20px] border-t border-x border-border-base shadow-[0_-12px_40px_-12px_rgba(0,0,0,0.18)] pt-2 pb-0 overflow-hidden", children: [
1355
+ /* @__PURE__ */ jsx7("div", { className: "mx-auto mt-1 mb-3 h-1 w-9 rounded-full bg-border-strong" }),
1356
+ /* @__PURE__ */ jsx7("div", { className: "px-3 pb-3", children })
1357
+ ] });
1358
+ }
1359
+ function ModalPresentation({ children }) {
1360
+ return /* @__PURE__ */ jsxs6("div", { className: "relative", children: [
1361
+ /* @__PURE__ */ jsx7(
1362
+ "div",
550
1363
  {
551
- fill: "#FBBC05",
552
- d: "M3.97 10.72A5.4 5.4 0 0 1 3.68 9c0-.6.1-1.18.29-1.72V4.95H.96A9 9 0 0 0 0 9c0 1.45.35 2.83.96 4.05l3.01-2.33Z"
1364
+ "aria-hidden": true,
1365
+ className: "absolute inset-0 -m-4 rounded-[18px] bg-black/30 backdrop-blur-sm pointer-events-none"
553
1366
  }
554
1367
  ),
555
- /* @__PURE__ */ jsx6(
556
- "path",
1368
+ /* @__PURE__ */ jsx7("div", { className: "relative", children })
1369
+ ] });
1370
+ }
1371
+ function AuthCard(props) {
1372
+ const { transparentBg = false } = props;
1373
+ const cardClass = transparentBg ? "overflow-hidden" : "rounded-[14px] bg-surface shadow-[0_2px_8px_rgba(0,0,0,0.04),0_20px_40px_-20px_rgba(0,0,0,0.12)] border border-border-base overflow-hidden";
1374
+ return /* @__PURE__ */ jsxs6("div", { className: cardClass, children: [
1375
+ /* @__PURE__ */ jsx7("div", { className: transparentBg ? "px-1 py-1" : "px-7 py-7", children: /* @__PURE__ */ jsx7(AuthBody, { ...props }) }),
1376
+ /* @__PURE__ */ jsx7(
1377
+ "div",
557
1378
  {
558
- fill: "#EA4335",
559
- d: "M9 3.58c1.32 0 2.5.45 3.44 1.35l2.58-2.59C13.46.89 11.43 0 9 0A9 9 0 0 0 .96 4.95l3.01 2.33C4.68 5.16 6.66 3.58 9 3.58Z"
1379
+ className: (transparentBg ? "mt-3 " : "border-t border-border-base bg-surface-hover px-7 py-3 ") + "flex items-center justify-center",
1380
+ children: /* @__PURE__ */ jsxs6(
1381
+ "a",
1382
+ {
1383
+ href: ELVIX_SITE_URL,
1384
+ target: "_blank",
1385
+ rel: "noopener noreferrer",
1386
+ className: "inline-flex items-center gap-1.5 pl-1.5 pr-2.5 py-1 rounded-full bg-surface ring-1 ring-border-base shadow-sm hover:bg-surface-hover hover:ring-border-strong transition",
1387
+ children: [
1388
+ /* @__PURE__ */ jsxs6("span", { className: "relative inline-flex items-center", children: [
1389
+ /* @__PURE__ */ jsx7(ElvixLogo, { size: 12, className: "text-fg-1" }),
1390
+ /* @__PURE__ */ jsx7(
1391
+ "span",
1392
+ {
1393
+ "aria-hidden": true,
1394
+ className: "absolute -right-px -top-px size-1 rounded-full bg-emerald-500 ring-1 ring-surface"
1395
+ }
1396
+ )
1397
+ ] }),
1398
+ /* @__PURE__ */ jsxs6("span", { className: "text-[11px] tracking-tight leading-none", children: [
1399
+ /* @__PURE__ */ jsx7("span", { className: "text-fg-3", children: "Secured by " }),
1400
+ /* @__PURE__ */ jsx7("span", { className: "font-semibold text-fg-1", children: "elvix" })
1401
+ ] })
1402
+ ]
1403
+ }
1404
+ )
560
1405
  }
561
1406
  )
562
1407
  ] });
563
1408
  }
564
- function ElvixSignInForm({
565
- onResult,
566
- redirectAfterSignIn,
567
- copy: copyProp,
568
- className = "",
569
- minWidth,
570
- maxWidth,
571
- minHeight,
572
- maxHeight,
573
- width,
574
- height
1409
+ function AuthBody({
1410
+ mode,
1411
+ appName,
1412
+ logoUrl,
1413
+ logoUrlDark,
1414
+ logoNode,
1415
+ brandColor,
1416
+ onBrandColor = "#ffffff",
1417
+ methodGoogle,
1418
+ methodEmailOtp,
1419
+ methodPasskey,
1420
+ methodUsername = false,
1421
+ privacyPolicyUrl,
1422
+ termsOfServiceUrl,
1423
+ intent = "app",
1424
+ clientId,
1425
+ websiteUrl,
1426
+ layout = "centered",
1427
+ socialLayout = "stacked",
1428
+ showHeader = true,
1429
+ theme = "light",
1430
+ signInVerb = "signin",
1431
+ belowHeading,
1432
+ belowMethods,
1433
+ googleConfig,
1434
+ googleClientId,
1435
+ onAuthenticated,
1436
+ onResult
575
1437
  }) {
576
- const ctx = useElvixContext();
577
- const app = useElvixApp();
578
- const copy = resolveCopy(app?.strings, copyProp);
579
- const [step, setStep] = useState3("identify");
580
- const [email, setEmail] = useState3("");
581
- const [code, setCode] = useState3("");
582
- const [challengeId, setChallengeId] = useState3(null);
583
- const [busy, setBusy] = useState3(false);
584
- const [error, setError] = useState3(null);
585
- const brand = app?.brandColor || "#6c5ce7";
586
- const onBrand = app?.onBrandColor || "#fff";
587
- const appName = app?.appName ?? "your app";
588
- const verb = app?.signInVerb === "login" ? "Log in" : "Sign in";
589
- const defaultTitle = app?.appName ? `${verb} to ${app.appName}` : verb;
590
- const title = copy.title ? fillCopy(copy.title, { app: app?.appName ?? "" }) : defaultTitle;
591
- const submitLabel = copy.submitButton ?? verb;
592
- const showGoogle = Boolean(app?.methodGoogle);
593
- const showEmail = Boolean(app?.methodEmailOtp);
594
- const showDivider = showGoogle && showEmail;
595
- const logoSrc = app?.iconUrl || app?.logoUrl || null;
596
- const privacyUrl = app?.privacyPolicyUrl || null;
597
- const termsUrl = app?.termsOfServiceUrl || null;
598
- const hasLegal = Boolean(privacyUrl || termsUrl);
599
- const cardStyle = useMemo2(
600
- () => ({
601
- boxSizing: "border-box",
602
- // Defaults first; the shared sizeStyle() overrides only the keys the host set,
603
- // so the form keeps its width "100%" / maxWidth 400 defaults when unsized.
604
- width: "100%",
605
- maxWidth: 400,
606
- ...sizeStyle({ width, height, minWidth, maxWidth, minHeight, maxHeight }),
607
- margin: "0 auto",
608
- display: "flex",
609
- flexDirection: "column",
610
- gap: 18,
611
- background: "#fff",
612
- color: "#18181b",
613
- border: "1px solid rgba(0,0,0,0.08)",
614
- borderRadius: 16,
615
- boxShadow: "0 1px 2px rgba(0,0,0,0.04), 0 12px 32px -12px rgba(0,0,0,0.18)",
616
- padding: 28,
617
- fontFamily: "ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif",
618
- textAlign: "center"
619
- }),
620
- [width, maxWidth, minWidth, height, minHeight, maxHeight]
1438
+ const { baseUrl } = useElvixContext();
1439
+ const isPreview = mode === "preview";
1440
+ const anyMethod = methodGoogle || methodEmailOtp || methodPasskey || methodUsername;
1441
+ const gisEnabled = !isPreview && methodGoogle && Boolean(googleConfig) && Boolean(
1442
+ googleConfig?.oneTap || googleConfig?.popup || googleConfig?.autoSelect || googleConfig?.fedcm
621
1443
  );
622
- function fail(error2, message) {
623
- setError(message ?? error2);
624
- onResult?.({ ok: false, error: error2, message });
625
- }
626
- async function startGoogle() {
627
- if (!ctx.clientId) return fail("missing_client_id", "ElvixProvider needs a clientId.");
628
- window.location.assign(
629
- `${ctx.baseUrl}/api/auth/google/start?intent=app&clientId=${encodeURIComponent(ctx.clientId)}`
630
- );
631
- }
632
- async function startOtp(e) {
633
- e.preventDefault();
634
- if (!email.trim()) return fail("invalid_input", copy.errorEnterEmail);
635
- setBusy(true);
1444
+ const gisButtonRef = useRef3(null);
1445
+ const useGisRenderedButton = gisEnabled && Boolean(googleClientId);
1446
+ const [step, setStep] = useState5(
1447
+ "identifier"
1448
+ );
1449
+ const [recoverState, setRecoverState] = useState5(null);
1450
+ const [identifier, setIdentifier] = useState5("");
1451
+ const [code, setCode] = useState5("");
1452
+ const [challengeId, setChallengeId] = useState5(null);
1453
+ const [sendingOtp, setSendingOtp] = useState5(false);
1454
+ const [verifyingOtp, setVerifyingOtp] = useState5(false);
1455
+ const [error, setError] = useState5(null);
1456
+ const [resendIn, setResendIn] = useState5(0);
1457
+ const [passkeyBusy, setPasskeyBusy] = useState5(false);
1458
+ const [usernameValue, setUsernameValue] = useState5("");
1459
+ const [usernameSuggestions, setUsernameSuggestions] = useState5([]);
1460
+ const [usernameCheck, setUsernameCheck] = useState5({ kind: "idle" });
1461
+ const [onboardingBusy, setOnboardingBusy] = useState5(null);
1462
+ const [finalRedirect, setFinalRedirect] = useState5("/");
1463
+ useEffect4(() => {
1464
+ if (resendIn <= 0) return;
1465
+ const t = setInterval(() => setResendIn((s) => Math.max(0, s - 1)), 1e3);
1466
+ return () => clearInterval(t);
1467
+ }, [resendIn]);
1468
+ const reportError = useCallback3(
1469
+ (code2, message) => {
1470
+ setError(message);
1471
+ onResult?.({
1472
+ ok: false,
1473
+ error: code2 ?? "unknown",
1474
+ message
1475
+ });
1476
+ },
1477
+ [onResult]
1478
+ );
1479
+ const applyLanding = useCallback3(
1480
+ (body) => {
1481
+ if (body.token) setElvixToken(body.token);
1482
+ if (body.next_step === "username") {
1483
+ setUsernameSuggestions(body.suggestions ?? []);
1484
+ setUsernameValue(body.suggestions?.[0] ?? "");
1485
+ setFinalRedirect(body.final ?? "/");
1486
+ setStep("username");
1487
+ return;
1488
+ }
1489
+ if (body.next_step === "passkey") {
1490
+ setFinalRedirect(body.final ?? "/");
1491
+ setStep("passkey");
1492
+ return;
1493
+ }
1494
+ if (body.next_step === "recover" && body.recover) {
1495
+ setRecoverState(body.recover);
1496
+ setFinalRedirect(body.final ?? "/");
1497
+ setStep("recover");
1498
+ return;
1499
+ }
1500
+ const redirect = body.redirect ?? defaultRedirect(intent);
1501
+ onResult?.({ ok: true, redirect });
1502
+ if (onAuthenticated) {
1503
+ onAuthenticated({ ok: true, redirect, token: body.token });
1504
+ return;
1505
+ }
1506
+ window.location.href = redirect;
1507
+ },
1508
+ [intent, onAuthenticated, onResult]
1509
+ );
1510
+ useEffect4(() => {
1511
+ if (isPreview) return;
1512
+ if (typeof window === "undefined") return;
1513
+ const params = new URLSearchParams(window.location.search);
1514
+ if (params.get("onboarding") !== "1") return;
1515
+ (async () => {
1516
+ try {
1517
+ const init = authInit();
1518
+ const res = await fetch(`${baseUrl}/api/onboarding/state`, {
1519
+ headers: init.headers,
1520
+ credentials: init.credentials
1521
+ });
1522
+ if (!res.ok) return;
1523
+ const body = unwrapEnvelope(await res.json());
1524
+ if (body.ok) applyLanding(body);
1525
+ } catch {
1526
+ } finally {
1527
+ const url = new URL(window.location.href);
1528
+ url.searchParams.delete("onboarding");
1529
+ url.searchParams.delete("next");
1530
+ window.history.replaceState({}, "", url.toString());
1531
+ }
1532
+ })();
1533
+ }, []);
1534
+ const autoSubmittedCodeRef = useRef3("");
1535
+ useEffect4(() => {
1536
+ if (code.length < 6) autoSubmittedCodeRef.current = "";
1537
+ }, [code]);
1538
+ const identifierValid = useMemo2(() => {
1539
+ const v = identifier.trim();
1540
+ if (!v) return false;
1541
+ if (v.includes("@")) return methodEmailOtp && EMAIL_RE.test(v);
1542
+ if (methodUsername) return isValidUsername(v);
1543
+ return false;
1544
+ }, [identifier, methodEmailOtp, methodUsername]);
1545
+ const identifierPlaceholder = methodEmailOtp && methodUsername ? "Email or username" : methodUsername ? "Username" : "Enter your email";
1546
+ const ctaBase = brandColor;
1547
+ const ctaFg = onBrandColor;
1548
+ const ctaStyle = {
1549
+ backgroundImage: "linear-gradient(to bottom, rgba(255,255,255,0.12), rgba(255,255,255,0) 40%)",
1550
+ backgroundColor: ctaBase,
1551
+ color: ctaFg,
1552
+ boxShadow: "0 1px 0 rgba(255,255,255,0.06) inset, 0 2px 3px -1px rgba(0,0,0,0.18), 0 0 0 1px rgba(25,28,33,0.08)"
1553
+ };
1554
+ const ctaLabelStyle = {
1555
+ filter: "drop-shadow(0 1px 2px rgba(0,0,0,0.18))"
1556
+ };
1557
+ const onSubmitIdentifier = useCallback3(
1558
+ async (e) => {
1559
+ e?.preventDefault();
1560
+ if (!identifierValid || isPreview || sendingOtp) return;
1561
+ setError(null);
1562
+ setSendingOtp(true);
1563
+ const v = identifier.trim();
1564
+ try {
1565
+ if (!v.includes("@")) {
1566
+ const res2 = await fetch(`${baseUrl}/api/auth/identifier/resolve`, {
1567
+ method: "POST",
1568
+ headers: { "Content-Type": "application/json" },
1569
+ credentials: isSameOrigin(baseUrl) ? "include" : "omit",
1570
+ body: JSON.stringify({
1571
+ username: v.toLowerCase(),
1572
+ intent,
1573
+ ...clientId ? { clientId } : {}
1574
+ })
1575
+ });
1576
+ const body2 = unwrapEnvelope(await res2.json().catch(() => ({})));
1577
+ if (!res2.ok || !body2.ok || !body2.challengeId) {
1578
+ if (body2.error === "too_recent" || body2.error === "too_many") {
1579
+ setResendIn(body2.retryAfterSeconds ?? 45);
1580
+ }
1581
+ reportError(body2.error, humanError(body2.error, body2.retryAfterSeconds));
1582
+ return;
1583
+ }
1584
+ setChallengeId(body2.challengeId);
1585
+ setStep("code");
1586
+ setResendIn(45);
1587
+ return;
1588
+ }
1589
+ const res = await fetch(`${baseUrl}/api/auth/otp/start`, {
1590
+ method: "POST",
1591
+ headers: { "Content-Type": "application/json" },
1592
+ credentials: isSameOrigin(baseUrl) ? "include" : "omit",
1593
+ body: JSON.stringify({ email: v, intent, clientId })
1594
+ });
1595
+ const body = unwrapEnvelope(await res.json().catch(() => ({})));
1596
+ if (!res.ok || !body.ok || !body.challengeId) {
1597
+ if (body.error === "too_recent" || body.error === "too_many") {
1598
+ setResendIn(body.retryAfterSeconds ?? 30);
1599
+ }
1600
+ reportError(body.error, humanError(body.error, body.retryAfterSeconds));
1601
+ return;
1602
+ }
1603
+ setChallengeId(body.challengeId);
1604
+ setStep("code");
1605
+ setResendIn(45);
1606
+ } catch {
1607
+ reportError("network_error", "Network hiccup. Try again.");
1608
+ } finally {
1609
+ setSendingOtp(false);
1610
+ }
1611
+ },
1612
+ [identifier, identifierValid, isPreview, sendingOtp, intent, clientId, reportError, baseUrl]
1613
+ );
1614
+ const onSubmitCode = useCallback3(
1615
+ async (e) => {
1616
+ e?.preventDefault();
1617
+ if (isPreview || verifyingOtp || code.length !== 6 || !challengeId) return;
1618
+ setError(null);
1619
+ setVerifyingOtp(true);
1620
+ try {
1621
+ const res = await fetch(`${baseUrl}/api/auth/otp/verify`, {
1622
+ method: "POST",
1623
+ headers: { "Content-Type": "application/json" },
1624
+ credentials: isSameOrigin(baseUrl) ? "include" : "omit",
1625
+ body: JSON.stringify({ challengeId, code })
1626
+ });
1627
+ const body = unwrapEnvelope(await res.json().catch(() => ({})));
1628
+ if (!res.ok || !body.ok) {
1629
+ reportError(body.error, humanError(body.error));
1630
+ return;
1631
+ }
1632
+ applyLanding(body);
1633
+ } catch {
1634
+ reportError("network_error", "Network hiccup. Try again.");
1635
+ } finally {
1636
+ setVerifyingOtp(false);
1637
+ }
1638
+ },
1639
+ [code, challengeId, isPreview, verifyingOtp, applyLanding, reportError, baseUrl]
1640
+ );
1641
+ useEffect4(() => {
1642
+ if (step !== "code") return;
1643
+ if (code.length !== 6) return;
1644
+ if (verifyingOtp || isPreview || !challengeId) return;
1645
+ if (autoSubmittedCodeRef.current === code) return;
1646
+ autoSubmittedCodeRef.current = code;
1647
+ void onSubmitCode();
1648
+ }, [code, step, verifyingOtp, isPreview, challengeId, onSubmitCode]);
1649
+ const onPasskey = useCallback3(async () => {
1650
+ if (isPreview || passkeyBusy) return;
636
1651
  setError(null);
1652
+ setPasskeyBusy(true);
637
1653
  try {
638
- const res = await fetch(`${ctx.baseUrl}/api/auth/otp/start`, {
639
- method: "POST",
640
- headers: { "content-type": "application/json" },
641
- credentials: isSameOrigin(ctx.baseUrl) ? "include" : "omit",
642
- body: JSON.stringify({
643
- email: email.trim(),
644
- intent: "app",
645
- clientId: ctx.clientId
646
- })
647
- });
648
- const body = await res.json();
649
- if (!res.ok || !body.success || !body.data?.challengeId) {
650
- return fail(body.errorMessage ?? "otp_start_failed");
1654
+ const result = await runPasskeySignIn(baseUrl, clientId);
1655
+ if (!result.ok) {
1656
+ if (result.error === "passkey_cancelled") {
1657
+ onResult?.({ ok: false, error: result.error });
1658
+ return;
1659
+ }
1660
+ reportError(result.error, result.message ?? humanError(result.error) ?? "Passkey verification failed.");
1661
+ return;
651
1662
  }
652
- setChallengeId(body.data.challengeId);
653
- setStep("code");
654
- } catch (e2) {
655
- fail("network", e2 instanceof Error ? e2.message : void 0);
1663
+ applyLanding({ next_step: "done", redirect: result.redirect, token: result.token });
656
1664
  } finally {
657
- setBusy(false);
1665
+ setPasskeyBusy(false);
658
1666
  }
659
- }
660
- async function verifyOtp(e) {
661
- e.preventDefault();
662
- if (!challengeId) return;
663
- if (code.trim().length !== 6) return fail("invalid_input", copy.errorEnterCode);
664
- setBusy(true);
1667
+ }, [isPreview, passkeyBusy, baseUrl, clientId, applyLanding, reportError, onResult]);
1668
+ const usernameAbortRef = useRef3(null);
1669
+ useEffect4(() => {
1670
+ if (step !== "username") return;
1671
+ const candidate = usernameValue.trim().toLowerCase();
1672
+ if (!candidate) {
1673
+ setUsernameCheck({ kind: "idle" });
1674
+ return;
1675
+ }
1676
+ setUsernameCheck({ kind: "checking" });
1677
+ const t = setTimeout(() => {
1678
+ usernameAbortRef.current?.abort();
1679
+ const ctrl = new AbortController();
1680
+ usernameAbortRef.current = ctrl;
1681
+ const init = authInit();
1682
+ fetch(`${baseUrl}/api/onboarding/username/check?u=${encodeURIComponent(candidate)}`, {
1683
+ signal: ctrl.signal,
1684
+ headers: init.headers,
1685
+ credentials: init.credentials
1686
+ }).then((r) => r.json()).then((raw) => unwrapEnvelope(raw)).then((b) => {
1687
+ if (b.ok === false) {
1688
+ setUsernameCheck({ kind: "rejected", reason: b.reason ?? "invalid" });
1689
+ return;
1690
+ }
1691
+ if (b.available) setUsernameCheck({ kind: "available" });
1692
+ else setUsernameCheck({ kind: "rejected", reason: b.reason ?? "taken" });
1693
+ }).catch((err) => {
1694
+ if (err.name !== "AbortError") setUsernameCheck({ kind: "idle" });
1695
+ });
1696
+ }, 220);
1697
+ return () => clearTimeout(t);
1698
+ }, [usernameValue, step, baseUrl]);
1699
+ const onSubmitUsername = useCallback3(
1700
+ async (auto) => {
1701
+ if (onboardingBusy || isPreview) return;
1702
+ setError(null);
1703
+ setOnboardingBusy(auto ? "skip" : "claim");
1704
+ try {
1705
+ const init = authInit();
1706
+ const res = await fetch(`${baseUrl}/api/onboarding/username`, {
1707
+ method: "POST",
1708
+ headers: { "Content-Type": "application/json", ...init.headers },
1709
+ credentials: init.credentials,
1710
+ body: JSON.stringify(auto ? {} : { username: usernameValue.trim().toLowerCase() })
1711
+ });
1712
+ const body = unwrapEnvelope(await res.json().catch(() => ({})));
1713
+ if (!res.ok || !body.ok) {
1714
+ reportError(body.error, humanError(body.error));
1715
+ return;
1716
+ }
1717
+ applyLanding(body);
1718
+ } catch {
1719
+ reportError("network_error", "Network hiccup. Try again.");
1720
+ } finally {
1721
+ setOnboardingBusy(null);
1722
+ }
1723
+ },
1724
+ [usernameValue, isPreview, onboardingBusy, applyLanding, reportError, baseUrl]
1725
+ );
1726
+ const onAddPasskey = useCallback3(async () => {
1727
+ if (onboardingBusy || isPreview) return;
665
1728
  setError(null);
1729
+ setOnboardingBusy("add");
666
1730
  try {
667
- const res = await fetch(`${ctx.baseUrl}/api/auth/otp/verify`, {
668
- method: "POST",
669
- headers: { "content-type": "application/json" },
670
- credentials: isSameOrigin(ctx.baseUrl) ? "include" : "omit",
671
- body: JSON.stringify({ challengeId, code: code.trim() })
672
- });
673
- const body = await res.json();
674
- if (!res.ok || !body.success) {
675
- return fail(body.errorMessage ?? "otp_verify_failed");
1731
+ const surface = intent === "app" ? "app" : intent;
1732
+ const result = await runPasskeyRegister(baseUrl, surface);
1733
+ if (!result.ok) {
1734
+ if (result.error === "passkey_cancelled") return;
1735
+ reportError(
1736
+ result.error,
1737
+ result.message ?? humanError(result.error) ?? "Passkey couldn't be added. Try again or skip for now."
1738
+ );
1739
+ return;
676
1740
  }
677
- if (body.data?.token) setElvixToken(body.data.token);
678
- setStep("done");
679
- onResult?.({
680
- ok: true,
681
- method: "email_otp",
682
- redirect: body.data?.redirect ?? redirectAfterSignIn,
683
- token: body.data?.token
684
- });
685
- } catch (e2) {
686
- fail("network", e2 instanceof Error ? e2.message : void 0);
1741
+ onResult?.({ ok: true, redirect: finalRedirect });
1742
+ if (onAuthenticated) {
1743
+ onAuthenticated({ ok: true, redirect: finalRedirect });
1744
+ return;
1745
+ }
1746
+ window.location.href = finalRedirect;
687
1747
  } finally {
688
- setBusy(false);
1748
+ setOnboardingBusy(null);
689
1749
  }
690
- }
691
- const labelStyle = {
692
- display: "block",
693
- textAlign: "left",
694
- fontSize: 12.5,
695
- fontWeight: 500,
696
- color: "#52525b",
697
- marginBottom: 6
698
- };
699
- const inputStyle = {
700
- boxSizing: "border-box",
701
- width: "100%",
702
- height: 44,
703
- padding: "0 14px",
704
- borderRadius: 10,
705
- border: "1px solid rgba(0,0,0,0.14)",
706
- background: "#fff",
707
- color: "#18181b",
708
- fontSize: 14,
709
- outline: "none"
710
- };
711
- const primaryBtnStyle = {
712
- boxSizing: "border-box",
713
- width: "100%",
714
- height: 44,
715
- marginTop: 10,
716
- border: "1px solid rgba(0,0,0,0.08)",
717
- borderRadius: 10,
718
- background: brand,
719
- color: onBrand,
720
- fontSize: 14,
721
- fontWeight: 600,
722
- cursor: busy ? "not-allowed" : "pointer",
723
- opacity: busy ? 0.65 : 1,
724
- backgroundImage: "linear-gradient(to bottom, rgba(255,255,255,0.12), rgba(255,255,255,0) 40%)",
725
- boxShadow: "0 1px 0 rgba(255,255,255,0.06) inset, 0 2px 3px -1px rgba(0,0,0,0.18), 0 0 0 1px rgba(25,28,33,0.06)"
726
- };
727
- const googleBtnStyle = {
728
- boxSizing: "border-box",
729
- width: "100%",
730
- height: 44,
731
- display: "inline-flex",
732
- alignItems: "center",
733
- justifyContent: "center",
734
- gap: 10,
735
- border: "1px solid rgba(0,0,0,0.14)",
736
- borderRadius: 10,
737
- background: "#fff",
738
- color: "#18181b",
739
- fontSize: 14,
740
- fontWeight: 500,
741
- cursor: busy ? "not-allowed" : "pointer",
742
- opacity: busy ? 0.65 : 1
743
- };
744
- const tileTint = hexToRgba(brand, 0.12);
745
- const root = `${className}`.trim() || void 0;
746
- return /* @__PURE__ */ jsxs5("div", { className: root, style: cardStyle, "data-elvix-pane": step, children: [
747
- /* @__PURE__ */ jsxs5("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", gap: 12 }, children: [
748
- /* @__PURE__ */ jsx6(
749
- "div",
750
- {
751
- style: {
752
- width: 52,
753
- height: 52,
754
- borderRadius: 14,
755
- display: "grid",
756
- placeItems: "center",
757
- overflow: "hidden",
758
- background: logoSrc ? "#fff" : tileTint,
759
- border: "1px solid rgba(0,0,0,0.08)"
760
- },
761
- children: logoSrc ? (
762
- // biome-ignore lint/a11y/useAltText: alt is set
763
- /* @__PURE__ */ jsx6(
764
- "img",
1750
+ }, [intent, isPreview, onboardingBusy, finalRedirect, onAuthenticated, onResult, reportError, baseUrl]);
1751
+ const onSkipPasskey = useCallback3(() => {
1752
+ if (onboardingBusy) return;
1753
+ setOnboardingBusy("skip");
1754
+ onResult?.({ ok: true, redirect: finalRedirect });
1755
+ if (onAuthenticated) {
1756
+ onAuthenticated({ ok: true, redirect: finalRedirect });
1757
+ return;
1758
+ }
1759
+ window.location.href = finalRedirect;
1760
+ }, [onboardingBusy, finalRedirect, onAuthenticated, onResult]);
1761
+ return /* @__PURE__ */ jsxs6(Fragment3, { children: [
1762
+ showHeader && /* @__PURE__ */ jsxs6(
1763
+ "div",
1764
+ {
1765
+ className: "gap-3 mb-6 " + (layout === "left" ? "flex items-center text-left" : layout === "banner" ? "-mx-7 -mt-7 px-7 py-6 flex flex-col items-center text-center border-b border-border-base" : "flex flex-col items-center text-center"),
1766
+ style: layout === "banner" ? { background: `linear-gradient(135deg, ${brandColor}24, ${brandColor}08)` } : void 0,
1767
+ children: [
1768
+ (() => {
1769
+ const letter = /* @__PURE__ */ jsx7(
1770
+ "div",
765
1771
  {
766
- src: logoSrc,
767
- alt: appName,
768
- style: { width: "100%", height: "100%", objectFit: "cover" }
1772
+ className: "size-12 rounded-[10px] border border-border-base grid place-items-center overflow-hidden transition" + (websiteUrl ? " hover:border-border-strong hover:shadow-sm" : ""),
1773
+ style: { background: `${brandColor}1a` },
1774
+ children: /* @__PURE__ */ jsx7("span", { className: "text-[18px] font-semibold", style: { color: brandColor }, children: appName?.[0]?.toUpperCase() ?? "?" })
769
1775
  }
770
- )
771
- ) : /* @__PURE__ */ jsx6("span", { style: { fontSize: 22, fontWeight: 700, color: brand }, children: appName.charAt(0).toUpperCase() || "?" })
772
- }
773
- ),
774
- /* @__PURE__ */ jsxs5("div", { children: [
775
- /* @__PURE__ */ jsx6("h2", { style: { margin: 0, fontSize: 19, fontWeight: 600, letterSpacing: "-0.01em" }, children: step === "code" ? "Check your inbox" : title }),
776
- step !== "done" && /* @__PURE__ */ jsx6("p", { style: { margin: "4px 0 0", fontSize: 13, color: "#71717a" }, children: step === "code" ? fillCopy(copy.codeSentSubtitle ?? "", { email }) : copy.subtitle })
777
- ] })
778
- ] }),
779
- step === "done" && /* @__PURE__ */ jsx6("p", { style: { margin: 0, fontSize: 14, color: "#18181b" }, children: copy.signedInText }),
780
- step === "identify" && /* @__PURE__ */ jsxs5("div", { style: { display: "flex", flexDirection: "column", gap: 14 }, children: [
781
- showGoogle && /* @__PURE__ */ jsxs5(
782
- "button",
1776
+ );
1777
+ const bareImg = (src) => /* @__PURE__ */ jsx7("img", { src, alt: appName, className: "h-10 w-auto max-w-[220px] object-contain" });
1778
+ let inner;
1779
+ if (logoNode) {
1780
+ inner = /* @__PURE__ */ jsx7("div", { className: "inline-flex items-center justify-center min-h-12 max-w-[220px]", children: logoNode });
1781
+ } else if (theme === "dark") {
1782
+ inner = logoUrlDark ? bareImg(logoUrlDark) : letter;
1783
+ } else if (theme === "auto" && logoUrlDark && logoUrl) {
1784
+ inner = /* @__PURE__ */ jsxs6("picture", { children: [
1785
+ /* @__PURE__ */ jsx7("source", { srcSet: logoUrlDark, media: "(prefers-color-scheme: dark)" }),
1786
+ /* @__PURE__ */ jsx7(
1787
+ "img",
1788
+ {
1789
+ src: logoUrl,
1790
+ alt: appName,
1791
+ className: "h-10 w-auto max-w-[220px] object-contain"
1792
+ }
1793
+ )
1794
+ ] });
1795
+ } else if (logoUrl) {
1796
+ inner = bareImg(logoUrl);
1797
+ } else {
1798
+ inner = letter;
1799
+ }
1800
+ return websiteUrl ? /* @__PURE__ */ jsx7(
1801
+ "a",
1802
+ {
1803
+ href: websiteUrl,
1804
+ target: "_blank",
1805
+ rel: "noopener noreferrer",
1806
+ "aria-label": `Visit ${appName} website`,
1807
+ className: "cursor-pointer",
1808
+ children: inner
1809
+ }
1810
+ ) : inner;
1811
+ })(),
1812
+ /* @__PURE__ */ jsxs6("div", { children: [
1813
+ /* @__PURE__ */ jsx7("div", { className: "text-[18px] font-semibold tracking-tight text-fg-1", children: step === "code" ? "Check your inbox" : step === "username" ? "Pick a username" : step === "passkey" ? `${signInVerb === "login" ? "Log in" : "Sign in"} faster next time` : step === "recover" ? `Welcome back to ${recoverState?.appName ?? appName}` : `${signInVerb === "login" ? "Log in" : "Sign in"} to ${appName || "your app"}` }),
1814
+ /* @__PURE__ */ jsx7("div", { className: "text-[12.5px] text-fg-3 mt-0.5", children: step === "code" ? `We sent a code to ${identifier}` : step === "username" ? `This is how people see you${appName ? ` on ${appName}` : ""}.` : step === "passkey" ? "Touch ID, Face ID, or your security key. No more email codes." : step === "recover" ? "Pick whether to come back or stay away." : "Pick how you want to continue." }),
1815
+ step === "identifier" && belowHeading
1816
+ ] })
1817
+ ]
1818
+ }
1819
+ ),
1820
+ step === "code" ? /* @__PURE__ */ jsxs6("form", { onSubmit: onSubmitCode, className: "space-y-3", children: [
1821
+ /* @__PURE__ */ jsx7(
1822
+ OtpInput,
783
1823
  {
784
- type: "button",
785
- onClick: startGoogle,
786
- disabled: busy,
787
- style: googleBtnStyle,
788
- "data-elvix-method": "google",
789
- children: [
790
- /* @__PURE__ */ jsx6(GoogleG, {}),
791
- /* @__PURE__ */ jsx6("span", { children: copy.googleButton })
792
- ]
1824
+ value: code,
1825
+ onChange: setCode,
1826
+ disabled: isPreview || verifyingOtp,
1827
+ autoFocus: true
793
1828
  }
794
1829
  ),
795
- showDivider && /* @__PURE__ */ jsxs5(
796
- "div",
1830
+ /* @__PURE__ */ jsx7(
1831
+ "button",
797
1832
  {
798
- "aria-hidden": true,
799
- style: { display: "flex", alignItems: "center", gap: 12, color: "#a1a1aa" },
800
- children: [
801
- /* @__PURE__ */ jsx6("span", { style: { flex: 1, height: 1, background: "rgba(0,0,0,0.08)" } }),
802
- /* @__PURE__ */ jsx6("span", { style: { fontSize: 11, fontWeight: 600, letterSpacing: "0.08em" }, children: "OR" }),
803
- /* @__PURE__ */ jsx6("span", { style: { flex: 1, height: 1, background: "rgba(0,0,0,0.08)" } })
804
- ]
1833
+ type: "submit",
1834
+ disabled: verifyingOtp || code.length !== 6,
1835
+ className: "cursor-pointer w-full inline-flex items-center justify-center h-9 px-4 rounded-[10px] font-semibold text-[13px] tracking-tight transition hover:brightness-[0.94] active:brightness-[0.88] disabled:opacity-60 disabled:cursor-not-allowed disabled:hover:brightness-100",
1836
+ style: ctaStyle,
1837
+ children: /* @__PURE__ */ jsxs6("span", { className: "inline-flex items-center gap-1.5", style: ctaLabelStyle, children: [
1838
+ verifyingOtp ? /* @__PURE__ */ jsx7(Loader2, { className: "size-4 animate-spin" }) : "Verify",
1839
+ !verifyingOtp && /* @__PURE__ */ jsx7("svg", { width: "11", height: "10", viewBox: "0 0 11 10", fill: "none", "aria-hidden": true, children: /* @__PURE__ */ jsx7(
1840
+ "path",
1841
+ {
1842
+ d: "M7.75 5L4.25 2.75V7.25L7.75 5Z",
1843
+ fill: "currentColor",
1844
+ stroke: "currentColor",
1845
+ opacity: "0.6",
1846
+ strokeWidth: "1.5",
1847
+ strokeLinecap: "round",
1848
+ strokeLinejoin: "round"
1849
+ }
1850
+ ) })
1851
+ ] })
805
1852
  }
806
1853
  ),
807
- showEmail && /* @__PURE__ */ jsxs5("form", { onSubmit: startOtp, "data-elvix-method": "email_otp", children: [
808
- /* @__PURE__ */ jsx6("label", { htmlFor: "elvix-email", style: labelStyle, children: "Email" }),
809
- /* @__PURE__ */ jsx6(
810
- "input",
1854
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-between pt-1", children: [
1855
+ /* @__PURE__ */ jsxs6(
1856
+ "button",
811
1857
  {
812
- id: "elvix-email",
813
- type: "email",
814
- value: email,
815
- onChange: (ev) => setEmail(ev.target.value),
816
- placeholder: copy.emailPlaceholder,
817
- required: true,
818
- disabled: busy,
819
- autoComplete: "email",
820
- style: inputStyle
1858
+ type: "button",
1859
+ onClick: () => {
1860
+ setStep("identifier");
1861
+ setCode("");
1862
+ setError(null);
1863
+ },
1864
+ className: "cursor-pointer inline-flex items-center gap-1 text-[12px] text-fg-2 hover:text-fg-1 hover:bg-surface-hover rounded-md px-2 -mx-2 py-1 transition",
1865
+ children: [
1866
+ /* @__PURE__ */ jsx7(ArrowLeft, { className: "size-3" }),
1867
+ " Use a different email"
1868
+ ]
821
1869
  }
822
1870
  ),
823
- /* @__PURE__ */ jsx6("button", { type: "submit", disabled: busy, style: primaryBtnStyle, children: busy ? copy.sendingLabel : copy.sendCodeButton })
824
- ] })
825
- ] }),
826
- step === "code" && /* @__PURE__ */ jsxs5("form", { onSubmit: verifyOtp, children: [
827
- /* @__PURE__ */ jsx6("label", { htmlFor: "elvix-code", style: labelStyle, children: "Verification code" }),
828
- /* @__PURE__ */ jsx6(
829
- "input",
1871
+ /* @__PURE__ */ jsx7(
1872
+ "button",
1873
+ {
1874
+ type: "button",
1875
+ disabled: resendIn > 0 || sendingOtp,
1876
+ onClick: () => onSubmitIdentifier(),
1877
+ className: "cursor-pointer text-[12px] text-fg-2 hover:text-fg-1 disabled:opacity-50 disabled:cursor-not-allowed disabled:text-fg-3 transition",
1878
+ children: resendIn > 0 ? `Resend in ${resendIn}s` : "Resend code"
1879
+ }
1880
+ )
1881
+ ] }),
1882
+ error && /* @__PURE__ */ jsx7("p", { className: "text-[11.5px] text-red-400 text-center", children: error })
1883
+ ] }) : step === "username" ? /* @__PURE__ */ jsxs6("div", { className: "space-y-4", children: [
1884
+ /* @__PURE__ */ jsxs6("div", { children: [
1885
+ /* @__PURE__ */ jsx7("label", { htmlFor: "onboarding-username", className: "block text-[12px] text-fg-3 mb-1.5", children: "Username" }),
1886
+ /* @__PURE__ */ jsxs6("div", { className: "relative", children: [
1887
+ /* @__PURE__ */ jsx7("span", { className: "absolute left-3 top-1/2 -translate-y-1/2 text-[14px] text-fg-3 pointer-events-none", children: "@" }),
1888
+ /* @__PURE__ */ jsx7(
1889
+ "input",
1890
+ {
1891
+ id: "onboarding-username",
1892
+ type: "text",
1893
+ autoFocus: true,
1894
+ autoComplete: "username",
1895
+ value: usernameValue,
1896
+ minLength: 4,
1897
+ maxLength: 30,
1898
+ onChange: (e) => setUsernameValue(e.target.value),
1899
+ placeholder: "yourname",
1900
+ disabled: onboardingBusy !== null,
1901
+ className: "w-full h-11 pl-7 pr-10 rounded-[10px] bg-surface border border-border-strong text-[14px] text-fg-1 placeholder:text-placeholder focus:outline-none focus:border-[#8e7dff] focus:ring-2 focus:ring-[#8e7dff]/20 transition disabled:opacity-60 disabled:cursor-not-allowed"
1902
+ }
1903
+ ),
1904
+ usernameCheck.kind === "checking" && /* @__PURE__ */ jsx7(Loader2, { className: "size-4 text-fg-3 absolute right-3 top-1/2 -translate-y-1/2 animate-spin pointer-events-none" }),
1905
+ usernameCheck.kind === "available" && /* @__PURE__ */ jsx7(Check, { className: "size-4 text-emerald-500 absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none" }),
1906
+ usernameCheck.kind === "rejected" && /* @__PURE__ */ jsx7(X2, { className: "size-4 text-red-500 absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none" })
1907
+ ] }),
1908
+ /* @__PURE__ */ jsxs6("p", { className: "text-[11px] mt-1.5 leading-relaxed min-h-[14px]", children: [
1909
+ usernameCheck.kind === "idle" && /* @__PURE__ */ jsx7("span", { className: "text-fg-3", children: "4\u201330 chars, a\u2013z 0\u20139 . _ \u2014 must start with a letter." }),
1910
+ usernameCheck.kind === "checking" && /* @__PURE__ */ jsx7("span", { className: "text-fg-3", children: "Checking\u2026" }),
1911
+ usernameCheck.kind === "available" && /* @__PURE__ */ jsx7("span", { className: "text-emerald-500", children: "Looks good. This one's yours." }),
1912
+ usernameCheck.kind === "rejected" && /* @__PURE__ */ jsx7("span", { className: "text-red-500", children: usernameReasonLabel(usernameCheck.reason) })
1913
+ ] })
1914
+ ] }),
1915
+ usernameSuggestions.length > 0 && /* @__PURE__ */ jsxs6("div", { className: "space-y-2", children: [
1916
+ /* @__PURE__ */ jsx7("div", { className: "text-[11px] uppercase tracking-[0.08em] text-fg-3", children: "Suggestions" }),
1917
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-1.5 overflow-x-auto whitespace-nowrap pb-0.5 [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden", children: [
1918
+ usernameSuggestions.slice(0, 3).map((s) => /* @__PURE__ */ jsxs6(
1919
+ "button",
1920
+ {
1921
+ type: "button",
1922
+ disabled: onboardingBusy !== null,
1923
+ onClick: () => setUsernameValue(s),
1924
+ className: "cursor-pointer shrink-0 inline-flex items-center h-7 px-2.5 rounded-full bg-surface-hover border border-border-base hover:border-border-strong hover:bg-surface-active transition text-[12px] text-fg-2 font-mono disabled:opacity-60 disabled:cursor-not-allowed",
1925
+ children: [
1926
+ "@",
1927
+ s
1928
+ ]
1929
+ },
1930
+ s
1931
+ )),
1932
+ /* @__PURE__ */ jsx7(
1933
+ "button",
1934
+ {
1935
+ type: "button",
1936
+ onClick: () => onSubmitUsername(true),
1937
+ disabled: onboardingBusy !== null,
1938
+ className: "cursor-pointer shrink-0 inline-flex items-center gap-1 h-7 px-2.5 rounded-full border border-dashed border-border-base hover:border-border-strong hover:bg-surface-hover transition text-[12px] text-fg-3 hover:text-fg-1 disabled:opacity-60 disabled:cursor-not-allowed",
1939
+ children: onboardingBusy === "skip" ? /* @__PURE__ */ jsx7(Loader2, { className: "size-3.5 animate-spin" }) : "Skip"
1940
+ }
1941
+ )
1942
+ ] })
1943
+ ] }),
1944
+ error && /* @__PURE__ */ jsx7("p", { className: "text-[11.5px] text-red-400", children: error }),
1945
+ /* @__PURE__ */ jsx7(
1946
+ "button",
830
1947
  {
831
- id: "elvix-code",
832
- type: "text",
833
- inputMode: "numeric",
834
- pattern: "[0-9]*",
835
- maxLength: 6,
836
- value: code,
837
- onChange: (ev) => setCode(ev.target.value.replace(/\D/g, "")),
838
- placeholder: copy.codePlaceholder,
839
- required: true,
840
- disabled: busy,
841
- autoComplete: "one-time-code",
842
- autoFocus: true,
843
- style: { ...inputStyle, letterSpacing: "0.3em", textAlign: "center", fontSize: 18 }
1948
+ type: "button",
1949
+ onClick: () => onSubmitUsername(false),
1950
+ disabled: onboardingBusy !== null || usernameCheck.kind !== "available",
1951
+ className: "cursor-pointer w-full inline-flex items-center justify-center h-9 px-4 rounded-[10px] font-semibold text-[13px] tracking-tight transition hover:brightness-[0.94] active:brightness-[0.88] disabled:opacity-60 disabled:cursor-not-allowed disabled:hover:brightness-100",
1952
+ style: ctaStyle,
1953
+ children: /* @__PURE__ */ jsx7("span", { className: "inline-flex items-center gap-1.5", style: ctaLabelStyle, children: onboardingBusy === "claim" ? /* @__PURE__ */ jsx7(Loader2, { className: "size-4 animate-spin" }) : `Claim @${usernameValue || "yourname"}` })
1954
+ }
1955
+ )
1956
+ ] }) : step === "passkey" ? /* @__PURE__ */ jsxs6("div", { className: "space-y-3", children: [
1957
+ /* @__PURE__ */ jsxs6("ul", { className: "text-[12.5px] text-fg-2 leading-relaxed space-y-1.5", children: [
1958
+ /* @__PURE__ */ jsxs6("li", { className: "flex items-start gap-2", children: [
1959
+ /* @__PURE__ */ jsx7("span", { className: "mt-1.5 size-1 rounded-full", style: { background: brandColor } }),
1960
+ /* @__PURE__ */ jsx7("span", { children: "No more password or email codes" })
1961
+ ] }),
1962
+ /* @__PURE__ */ jsxs6("li", { className: "flex items-start gap-2", children: [
1963
+ /* @__PURE__ */ jsx7("span", { className: "mt-1.5 size-1 rounded-full", style: { background: brandColor } }),
1964
+ /* @__PURE__ */ jsx7("span", { children: "Phish-proof \u2014 works only on this site" })
1965
+ ] }),
1966
+ /* @__PURE__ */ jsxs6("li", { className: "flex items-start gap-2", children: [
1967
+ /* @__PURE__ */ jsx7("span", { className: "mt-1.5 size-1 rounded-full", style: { background: brandColor } }),
1968
+ /* @__PURE__ */ jsx7("span", { children: "Syncs across your devices via iCloud / Google / 1Password" })
1969
+ ] })
1970
+ ] }),
1971
+ error && /* @__PURE__ */ jsx7("p", { className: "text-[11.5px] text-red-400", children: error }),
1972
+ /* @__PURE__ */ jsx7(
1973
+ "button",
1974
+ {
1975
+ type: "button",
1976
+ onClick: onAddPasskey,
1977
+ disabled: onboardingBusy !== null,
1978
+ className: "cursor-pointer w-full inline-flex items-center justify-center h-9 px-4 rounded-[10px] font-semibold text-[13px] tracking-tight transition hover:brightness-[0.94] active:brightness-[0.88] disabled:opacity-60 disabled:cursor-not-allowed disabled:hover:brightness-100",
1979
+ style: ctaStyle,
1980
+ children: /* @__PURE__ */ jsx7("span", { className: "inline-flex items-center gap-1.5", style: ctaLabelStyle, children: onboardingBusy === "add" ? /* @__PURE__ */ jsx7(Loader2, { className: "size-4 animate-spin" }) : /* @__PURE__ */ jsxs6(Fragment3, { children: [
1981
+ /* @__PURE__ */ jsx7(Fingerprint, { className: "size-4" }),
1982
+ " Add a passkey"
1983
+ ] }) })
844
1984
  }
845
1985
  ),
846
- /* @__PURE__ */ jsx6("button", { type: "submit", disabled: busy, style: primaryBtnStyle, children: busy ? copy.verifyingLabel : submitLabel }),
847
- /* @__PURE__ */ jsx6(
1986
+ /* @__PURE__ */ jsx7(
848
1987
  "button",
849
1988
  {
850
1989
  type: "button",
851
- onClick: () => {
852
- setStep("identify");
853
- setCode("");
854
- setError(null);
855
- },
856
- disabled: busy,
857
- style: {
858
- marginTop: 12,
859
- background: "none",
860
- border: "none",
861
- color: "#71717a",
862
- fontSize: 12.5,
863
- cursor: busy ? "not-allowed" : "pointer"
1990
+ onClick: onSkipPasskey,
1991
+ disabled: onboardingBusy !== null,
1992
+ className: "cursor-pointer w-full inline-flex items-center justify-center h-9 px-3 rounded-[8px] text-[13px] text-fg-2 hover:text-fg-1 hover:bg-surface-hover transition disabled:opacity-60 disabled:cursor-not-allowed",
1993
+ children: onboardingBusy === "skip" ? /* @__PURE__ */ jsx7(Loader2, { className: "size-4 animate-spin" }) : "Skip for now"
1994
+ }
1995
+ )
1996
+ ] }) : step === "recover" && recoverState ? (
1997
+ // Recovery gateway — user just signed back in to an app where
1998
+ // their membership is in a reversible off-state. Sign-in won't
1999
+ // complete until they pick Restore or Cancel. The SDK
2000
+ // component handles the API call; we just route the result
2001
+ // (final redirect on restore, sign-in URL on cancel).
2002
+ /* @__PURE__ */ jsx7(
2003
+ ElvixRecoverGate,
2004
+ {
2005
+ baseUrl,
2006
+ appName: recoverState.appName,
2007
+ state: recoverState.state,
2008
+ sinceAt: recoverState.sinceAt,
2009
+ onRestore: ({ redirect }) => {
2010
+ if (onAuthenticated) {
2011
+ onAuthenticated({ ok: true, redirect });
2012
+ } else {
2013
+ window.location.href = redirect;
2014
+ }
864
2015
  },
865
- children: "Use a different email"
2016
+ onCancel: ({ redirect }) => {
2017
+ window.location.href = redirect;
2018
+ }
866
2019
  }
867
2020
  )
868
- ] }),
869
- error && /* @__PURE__ */ jsx6("p", { role: "alert", style: { margin: 0, fontSize: 12.5, color: "#dc2626" }, children: error }),
870
- hasLegal && step !== "done" && /* @__PURE__ */ jsxs5("p", { style: { margin: 0, fontSize: 11.5, lineHeight: 1.5, color: "#a1a1aa" }, children: [
871
- "By continuing, you agree to ",
872
- appName,
873
- "'s",
874
- " ",
875
- termsUrl && /* @__PURE__ */ jsx6(
876
- "a",
2021
+ ) : !anyMethod ? /* @__PURE__ */ jsx7("div", { className: "rounded-[10px] border border-dashed border-border-base bg-surface-hover py-8 px-4 text-center", children: /* @__PURE__ */ jsx7("p", { className: "text-[12.5px] text-fg-3", children: "Enable at least one method to preview the sign-in." }) }) : /* @__PURE__ */ jsxs6("form", { onSubmit: onSubmitIdentifier, className: "space-y-2", children: [
2022
+ gisEnabled && googleClientId && /* @__PURE__ */ jsx7(
2023
+ GoogleOneTap,
877
2024
  {
878
- href: termsUrl,
879
- target: "_blank",
880
- rel: "noopener noreferrer",
881
- style: { color: "#71717a", textDecoration: "underline" },
882
- children: "Terms of Service"
2025
+ baseUrl,
2026
+ clientId: googleClientId,
2027
+ intent,
2028
+ appClientId: clientId,
2029
+ renderButton: useGisRenderedButton,
2030
+ buttonContainerRef: gisButtonRef,
2031
+ config: {
2032
+ oneTap: googleConfig?.oneTap ?? false,
2033
+ autoSelect: googleConfig?.autoSelect ?? false,
2034
+ popup: googleConfig?.popup ?? false,
2035
+ fedcm: googleConfig?.fedcm ?? false,
2036
+ hostedDomain: googleConfig?.hostedDomain ?? ""
2037
+ }
883
2038
  }
884
2039
  ),
885
- termsUrl && privacyUrl && " \xB7 ",
886
- privacyUrl && /* @__PURE__ */ jsx6(
887
- "a",
2040
+ (methodGoogle || methodPasskey) && /* @__PURE__ */ jsxs6(
2041
+ "div",
888
2042
  {
889
- href: privacyUrl,
890
- target: "_blank",
891
- rel: "noopener noreferrer",
892
- style: { color: "#71717a", textDecoration: "underline" },
893
- children: "Privacy Policy"
2043
+ className: socialLayout === "grid" && methodGoogle && methodPasskey ? "grid grid-cols-2 gap-2" : "space-y-2",
2044
+ children: [
2045
+ methodGoogle && (useGisRenderedButton ? (
2046
+ // GIS-rendered button respects ux_mode='popup' so the
2047
+ // OAuth flow runs in a small window instead of a full-
2048
+ // page redirect. Google styles this themselves; we
2049
+ // reserve the slot at our button height so layout stays
2050
+ // stable while GIS hydrates.
2051
+ /* @__PURE__ */ jsx7(
2052
+ "div",
2053
+ {
2054
+ ref: gisButtonRef,
2055
+ className: "w-full min-h-10",
2056
+ "aria-label": "Continue with Google"
2057
+ }
2058
+ )
2059
+ ) : /* @__PURE__ */ jsxs6(
2060
+ "a",
2061
+ {
2062
+ href: isPreview ? "#" : `${baseUrl}/api/auth/google/start?intent=${intent}${clientId ? `&clientId=${clientId}` : ""}`,
2063
+ onClick: isPreview ? (e) => e.preventDefault() : void 0,
2064
+ className: "cursor-pointer w-full inline-flex items-center justify-center gap-2 h-10 rounded-[10px] font-medium text-[13px] border border-border-base bg-surface text-fg-1 hover:bg-surface-hover transition",
2065
+ children: [
2066
+ /* @__PURE__ */ jsx7(GoogleGlyph, {}),
2067
+ socialLayout === "grid" && methodPasskey ? "Google" : "Continue with Google"
2068
+ ]
2069
+ }
2070
+ )),
2071
+ methodPasskey && /* @__PURE__ */ jsxs6(
2072
+ "button",
2073
+ {
2074
+ type: "button",
2075
+ disabled: passkeyBusy,
2076
+ onClick: isPreview ? void 0 : onPasskey,
2077
+ className: "cursor-pointer w-full inline-flex items-center justify-center gap-2 h-10 rounded-[10px] font-medium text-[13px] border border-border-base bg-surface text-fg-1 hover:bg-surface-hover transition disabled:cursor-not-allowed disabled:opacity-60",
2078
+ children: [
2079
+ passkeyBusy ? /* @__PURE__ */ jsx7(Loader2, { className: "size-4 animate-spin" }) : /* @__PURE__ */ jsx7(Fingerprint, { className: "size-4" }),
2080
+ socialLayout === "grid" && methodGoogle ? "Passkey" : "Continue with passkey"
2081
+ ]
2082
+ }
2083
+ )
2084
+ ]
894
2085
  }
895
- )
2086
+ ),
2087
+ (methodEmailOtp || methodUsername) && /* @__PURE__ */ jsxs6(Fragment3, { children: [
2088
+ (methodGoogle || methodPasskey) && /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-3 my-3", children: [
2089
+ /* @__PURE__ */ jsx7("span", { className: "h-px flex-1 bg-border-base" }),
2090
+ /* @__PURE__ */ jsx7("span", { className: "text-[11px] uppercase tracking-[0.08em] text-fg-3", children: "or" }),
2091
+ /* @__PURE__ */ jsx7("span", { className: "h-px flex-1 bg-border-base" })
2092
+ ] }),
2093
+ /* @__PURE__ */ jsx7(
2094
+ "input",
2095
+ {
2096
+ type: "text",
2097
+ disabled: sendingOtp,
2098
+ value: identifier,
2099
+ onChange: (e) => setIdentifier(e.target.value),
2100
+ placeholder: identifierPlaceholder,
2101
+ "aria-label": identifierPlaceholder,
2102
+ autoComplete: methodUsername ? "username" : "email",
2103
+ inputMode: methodUsername ? "text" : "email",
2104
+ className: "w-full h-10 px-3 rounded-[10px] bg-surface border border-border-strong text-[13px] text-fg-1 placeholder:text-placeholder focus:outline-none focus:border-[#8e7dff] focus:ring-2 focus:ring-[#8e7dff]/20 transition disabled:cursor-not-allowed"
2105
+ }
2106
+ ),
2107
+ /* @__PURE__ */ jsx7(
2108
+ "button",
2109
+ {
2110
+ type: "submit",
2111
+ disabled: !identifierValid || sendingOtp,
2112
+ className: "cursor-pointer w-full inline-flex items-center justify-center h-9 px-4 mt-3 rounded-[10px] font-semibold text-[13px] tracking-tight transition hover:brightness-[0.94] active:brightness-[0.88] disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:brightness-100",
2113
+ style: ctaStyle,
2114
+ children: /* @__PURE__ */ jsxs6("span", { className: "inline-flex items-center gap-1.5", style: ctaLabelStyle, children: [
2115
+ sendingOtp ? /* @__PURE__ */ jsx7(Loader2, { className: "size-4 animate-spin" }) : "Continue",
2116
+ !sendingOtp && /* @__PURE__ */ jsx7("svg", { width: "11", height: "10", viewBox: "0 0 11 10", fill: "none", "aria-hidden": true, children: /* @__PURE__ */ jsx7(
2117
+ "path",
2118
+ {
2119
+ d: "M7.75 5L4.25 2.75V7.25L7.75 5Z",
2120
+ fill: "currentColor",
2121
+ stroke: "currentColor",
2122
+ opacity: "0.6",
2123
+ strokeWidth: "1.5",
2124
+ strokeLinecap: "round",
2125
+ strokeLinejoin: "round"
2126
+ }
2127
+ ) })
2128
+ ] })
2129
+ }
2130
+ ),
2131
+ error && /* @__PURE__ */ jsx7("p", { className: "text-[11.5px] text-red-400 text-center mt-1", children: error })
2132
+ ] })
896
2133
  ] }),
897
- /* @__PURE__ */ jsx6("div", { style: { display: "flex", justifyContent: "center", paddingTop: 2 }, children: /* @__PURE__ */ jsx6(ElvixSecuredBadge, { variant: "outline", theme: "light", size: "sm", accentColor: brand }) })
2134
+ step === "identifier" && belowMethods,
2135
+ /* @__PURE__ */ jsxs6("div", { className: "text-center mt-5 leading-[1.45]", children: [
2136
+ /* @__PURE__ */ jsxs6("div", { className: "text-[11.5px] text-placeholder", children: [
2137
+ "By continuing, you agree to ",
2138
+ appName || "the app",
2139
+ "'s"
2140
+ ] }),
2141
+ /* @__PURE__ */ jsxs6("div", { className: "text-[11.5px] mt-1 flex items-center justify-center gap-1.5", children: [
2142
+ /* @__PURE__ */ jsx7(LegalLink, { href: termsOfServiceUrl, children: "Terms of Service" }),
2143
+ /* @__PURE__ */ jsx7("span", { className: "text-placeholder", children: "\xB7" }),
2144
+ /* @__PURE__ */ jsx7(LegalLink, { href: privacyPolicyUrl, children: "Privacy Policy" })
2145
+ ] })
2146
+ ] })
898
2147
  ] });
899
2148
  }
900
- function hexToRgba(hex, alpha) {
901
- const m = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(hex.trim());
902
- if (!m) return hex;
903
- let h = m[1];
904
- if (h.length === 3)
905
- h = h.split("").map((c) => c + c).join("");
906
- const n = Number.parseInt(h, 16);
907
- const r = n >> 16 & 255;
908
- const g = n >> 8 & 255;
909
- const b = n & 255;
910
- return `rgba(${r}, ${g}, ${b}, ${alpha})`;
2149
+ function FramedPreview({ children }) {
2150
+ const hDash = "repeating-linear-gradient(to right, rgba(0,0,0,0.22) 0 4px, transparent 4px 8px)";
2151
+ const vDash = "repeating-linear-gradient(to bottom, rgba(0,0,0,0.22) 0 4px, transparent 4px 8px)";
2152
+ const OVERSHOOT = 20;
2153
+ return /* @__PURE__ */ jsxs6("div", { className: "relative bg-[#f5f5f6] dark:bg-surface-hover px-4 pt-4 pb-2", children: [
2154
+ /* @__PURE__ */ jsx7(
2155
+ "div",
2156
+ {
2157
+ "aria-hidden": true,
2158
+ className: "absolute top-0 h-px pointer-events-none",
2159
+ style: { left: -OVERSHOOT, right: -OVERSHOOT, backgroundImage: hDash }
2160
+ }
2161
+ ),
2162
+ /* @__PURE__ */ jsx7(
2163
+ "div",
2164
+ {
2165
+ "aria-hidden": true,
2166
+ className: "absolute bottom-0 h-px pointer-events-none",
2167
+ style: { left: -OVERSHOOT, right: -OVERSHOOT, backgroundImage: hDash }
2168
+ }
2169
+ ),
2170
+ /* @__PURE__ */ jsx7(
2171
+ "div",
2172
+ {
2173
+ "aria-hidden": true,
2174
+ className: "absolute left-0 w-px pointer-events-none",
2175
+ style: { top: -OVERSHOOT, bottom: -OVERSHOOT, backgroundImage: vDash }
2176
+ }
2177
+ ),
2178
+ /* @__PURE__ */ jsx7(
2179
+ "div",
2180
+ {
2181
+ "aria-hidden": true,
2182
+ className: "absolute right-0 w-px pointer-events-none",
2183
+ style: { top: -OVERSHOOT, bottom: -OVERSHOOT, backgroundImage: vDash }
2184
+ }
2185
+ ),
2186
+ /* @__PURE__ */ jsx7("div", { className: "relative", children }),
2187
+ /* @__PURE__ */ jsx7(
2188
+ "div",
2189
+ {
2190
+ className: "relative mt-3 py-2.5 text-center text-[12px] font-medium text-fg-3",
2191
+ style: {
2192
+ backgroundImage: "repeating-linear-gradient(45deg, transparent 0 6px, rgba(0,0,0,0.06) 6px 7px)"
2193
+ },
2194
+ children: "This is a preview"
2195
+ }
2196
+ )
2197
+ ] });
2198
+ }
2199
+ function LegalLink({ href, children }) {
2200
+ const base = "cursor-pointer font-semibold text-fg-2 underline underline-offset-2 decoration-fg-3/60 hover:text-fg-1 hover:decoration-fg-1 transition";
2201
+ if (!href) return /* @__PURE__ */ jsx7("span", { className: base, children });
2202
+ return /* @__PURE__ */ jsx7("a", { href, target: "_blank", rel: "noopener noreferrer", className: base, children });
2203
+ }
2204
+ function GoogleGlyph() {
2205
+ return /* @__PURE__ */ jsxs6("svg", { viewBox: "0 0 18 18", className: "size-4", "aria-hidden": true, children: [
2206
+ /* @__PURE__ */ jsx7(
2207
+ "path",
2208
+ {
2209
+ fill: "#4285F4",
2210
+ d: "M16.51 8.18c0-.58-.05-1.13-.15-1.66H9v3.14h4.21a3.6 3.6 0 0 1-1.56 2.36v1.96h2.52c1.47-1.36 2.34-3.36 2.34-5.8z"
2211
+ }
2212
+ ),
2213
+ /* @__PURE__ */ jsx7(
2214
+ "path",
2215
+ {
2216
+ fill: "#34A853",
2217
+ d: "M9 17c2.1 0 3.87-.7 5.17-1.9l-2.52-1.96c-.7.47-1.6.74-2.65.74-2.04 0-3.77-1.38-4.38-3.23H2.02v2.03A8 8 0 0 0 9 17z"
2218
+ }
2219
+ ),
2220
+ /* @__PURE__ */ jsx7(
2221
+ "path",
2222
+ {
2223
+ fill: "#FBBC05",
2224
+ d: "M4.62 10.65A4.8 4.8 0 0 1 4.36 9c0-.57.1-1.12.26-1.65V5.32H2.02A8 8 0 0 0 1 9c0 1.29.31 2.5.86 3.58l2.51-1.93z"
2225
+ }
2226
+ ),
2227
+ /* @__PURE__ */ jsx7(
2228
+ "path",
2229
+ {
2230
+ fill: "#EA4335",
2231
+ d: "M9 4.77c1.14 0 2.17.4 2.98 1.17l2.23-2.23A7.84 7.84 0 0 0 9 1 8 8 0 0 0 1.86 5.32l2.51 1.93C5.23 5.17 6.96 4.77 9 4.77z"
2232
+ }
2233
+ )
2234
+ ] });
2235
+ }
2236
+ function humanError(code, retryAfterSeconds) {
2237
+ switch (code) {
2238
+ case "too_recent":
2239
+ return retryAfterSeconds ? `Wait ${retryAfterSeconds}s before requesting another code.` : "Wait a moment before requesting another code.";
2240
+ case "too_many":
2241
+ return retryAfterSeconds ? `Too many codes sent to this email. Try again in ${formatRetry(retryAfterSeconds)}.` : "Too many codes sent to this email. Try again later.";
2242
+ case "invalid_code":
2243
+ return "That code didn't work. Try again.";
2244
+ case "expired":
2245
+ return "That code expired. Send a new one.";
2246
+ case "send_failed":
2247
+ return "Couldn't send the email. Check the address.";
2248
+ case "user_paused":
2249
+ return "Your access has been paused. Contact support.";
2250
+ case "user_banned":
2251
+ return "Access denied.";
2252
+ case "username_not_found":
2253
+ return "No account with that username here.";
2254
+ case "method_disabled":
2255
+ return "That sign-in method isn't enabled for this app.";
2256
+ default:
2257
+ return "Something went wrong. Try again.";
2258
+ }
2259
+ }
2260
+ function formatRetry(seconds) {
2261
+ if (seconds < 60) return `${seconds}s`;
2262
+ const m = Math.ceil(seconds / 60);
2263
+ return m === 1 ? "1 minute" : `${m} minutes`;
2264
+ }
2265
+ function defaultRedirect(intent) {
2266
+ switch (intent) {
2267
+ case "console":
2268
+ return "/console";
2269
+ case "account":
2270
+ return "/account";
2271
+ default:
2272
+ return "/";
2273
+ }
2274
+ }
2275
+ function usernameReasonLabel(reason) {
2276
+ switch (reason) {
2277
+ case "blank":
2278
+ return "Pick a username.";
2279
+ case "too_short":
2280
+ return "Min 4 characters.";
2281
+ case "too_long":
2282
+ return "Max 30 characters.";
2283
+ case "only_numbers":
2284
+ return "Cannot be all numbers.";
2285
+ case "bad_start":
2286
+ return "Must start with a letter.";
2287
+ case "bad_chars":
2288
+ return "Only a\u2013z, 0\u20139, dot, underscore.";
2289
+ case "consecutive_special":
2290
+ return "No two dots or underscores in a row.";
2291
+ case "trailing_special":
2292
+ return "Cannot end with a dot or underscore.";
2293
+ case "taken":
2294
+ return "That one's taken.";
2295
+ default:
2296
+ return "That username isn't valid.";
2297
+ }
911
2298
  }
912
2299
 
913
2300
  // src/react/elvix-sign-in-button.tsx
914
- import { useState as useState4 } from "react";
915
- import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
916
- var ELVIX_URL2 = "https://elvix.is";
2301
+ import { useState as useState6 } from "react";
2302
+
2303
+ // src/react/elvix-shield.tsx
2304
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
2305
+ function ElvixShield({
2306
+ size,
2307
+ fill,
2308
+ accent
2309
+ }) {
2310
+ return /* @__PURE__ */ jsxs7("svg", { width: size, height: size, viewBox: "2 2 20 20", "aria-hidden": true, style: { display: "block" }, children: [
2311
+ /* @__PURE__ */ jsx8(
2312
+ "path",
2313
+ {
2314
+ fill,
2315
+ fillRule: "evenodd",
2316
+ d: "M 6 2.5 C 4.34 2.5 3 3.84 3 5.5 L 3 12.5 C 3 17.5 7 20.7 12 22 C 17 20.7 21 17.5 21 12.5 L 21 5.5 C 21 3.84 19.66 2.5 18 2.5 L 6 2.5 Z M 12 8.4 C 9.79 8.4 8 10.19 8 12.4 C 8 14.61 9.79 16.4 12 16.4 C 13.21 16.4 14.3 15.86 15.04 15 L 13.6 13.77 C 13.21 14.23 12.64 14.5 12 14.5 C 11.04 14.5 10.21 13.86 9.91 13 L 15.95 13 C 15.98 12.8 16 12.6 16 12.4 C 16 10.19 14.21 8.4 12 8.4 Z M 9.91 11.8 L 14.09 11.8 C 13.79 10.94 12.96 10.3 12 10.3 C 11.04 10.3 10.21 10.94 9.91 11.8 Z"
2317
+ }
2318
+ ),
2319
+ /* @__PURE__ */ jsx8("circle", { cx: "19.5", cy: "4.5", r: "2.4", fill: accent })
2320
+ ] });
2321
+ }
2322
+
2323
+ // src/react/elvix-sign-in-button.tsx
2324
+ import { Fragment as Fragment4, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
2325
+ var ELVIX_URL = "https://elvix.is";
917
2326
  var PRESET_LABEL = {
918
2327
  "sign-in-with-elvix": "Sign in with elvix",
919
2328
  "continue-with-elvix": "Continue with elvix",
@@ -952,7 +2361,7 @@ function shieldColor(variant, theme) {
952
2361
  }
953
2362
  function ElvixSignInButton({
954
2363
  clientId,
955
- baseUrl = ELVIX_URL2,
2364
+ baseUrl = ELVIX_URL,
956
2365
  returnUrl,
957
2366
  type = "standard",
958
2367
  variant = "filled",
@@ -974,7 +2383,7 @@ function ElvixSignInButton({
974
2383
  maxHeight
975
2384
  }) {
976
2385
  const sized = sizeStyle({ width, height, minWidth, maxWidth, minHeight, maxHeight });
977
- const [embedOpen, setEmbedOpen] = useState4(false);
2386
+ const [embedOpen, setEmbedOpen] = useState6(false);
978
2387
  const isIcon = type === "icon";
979
2388
  const resolvedLabel = label ?? PRESET_LABEL[preset];
980
2389
  const tone = variantTone(variant, theme);
@@ -999,16 +2408,16 @@ function ElvixSignInButton({
999
2408
  // Dimensional overrides win over the size preset above.
1000
2409
  ...sized
1001
2410
  };
1002
- const content = /* @__PURE__ */ jsxs6(Fragment2, { children: [
1003
- /* @__PURE__ */ jsx7(ElvixShield, { size: ICON_SIZE[size], fill: shieldColor(variant, theme), accent: "#8e7dff" }),
1004
- isIcon ? null : /* @__PURE__ */ jsx7("span", { children: resolvedLabel })
2411
+ const content = /* @__PURE__ */ jsxs8(Fragment4, { children: [
2412
+ /* @__PURE__ */ jsx9(ElvixShield, { size: ICON_SIZE[size], fill: shieldColor(variant, theme), accent: "#8e7dff" }),
2413
+ isIcon ? null : /* @__PURE__ */ jsx9("span", { children: resolvedLabel })
1005
2414
  ] });
1006
2415
  if (mode === "callback") {
1007
- return /* @__PURE__ */ jsx7("button", { type: "button", onClick, className, style, "aria-label": isIcon ? resolvedLabel : void 0, children: content });
2416
+ return /* @__PURE__ */ jsx9("button", { type: "button", onClick, className, style, "aria-label": isIcon ? resolvedLabel : void 0, children: content });
1008
2417
  }
1009
2418
  if (mode === "embed") {
1010
- return /* @__PURE__ */ jsxs6("div", { "data-elvix-signin-button-embed": "", style: sized, children: [
1011
- !embedOpen && /* @__PURE__ */ jsx7(
2419
+ return /* @__PURE__ */ jsxs8("div", { "data-elvix-signin-button-embed": "", style: sized, children: [
2420
+ !embedOpen && /* @__PURE__ */ jsx9(
1012
2421
  "button",
1013
2422
  {
1014
2423
  type: "button",
@@ -1019,7 +2428,7 @@ function ElvixSignInButton({
1019
2428
  children: content
1020
2429
  }
1021
2430
  ),
1022
- embedOpen && /* @__PURE__ */ jsx7(
2431
+ embedOpen && /* @__PURE__ */ jsx9(
1023
2432
  ElvixSignIn,
1024
2433
  {
1025
2434
  onResult: (r) => {
@@ -1036,18 +2445,95 @@ function ElvixSignInButton({
1036
2445
  const sep = base.includes("?") ? "&" : "?";
1037
2446
  return `${base}${sep}return=${encodeURIComponent(returnUrl)}`;
1038
2447
  })();
1039
- return /* @__PURE__ */ jsx7("a", { href: destination, className, style, "aria-label": isIcon ? resolvedLabel : void 0, children: content });
2448
+ return /* @__PURE__ */ jsx9("a", { href: destination, className, style, "aria-label": isIcon ? resolvedLabel : void 0, children: content });
2449
+ }
2450
+
2451
+ // src/react/elvix-secured-badge.tsx
2452
+ import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
2453
+ var ELVIX_URL2 = "https://elvix.is";
2454
+ var SIZE = {
2455
+ sm: { height: 28, padX: 10, font: 11.5, icon: 14, gap: 6 },
2456
+ md: { height: 32, padX: 12, font: 12.5, icon: 16, gap: 7 },
2457
+ lg: { height: 36, padX: 14, font: 13, icon: 18, gap: 8 }
2458
+ };
2459
+ var TONE = {
2460
+ white: {
2461
+ light: { bg: "#ffffff", border: "#e4e4e7", lead: "#71717a", brand: "#0a0a0b", shield: "#0a0a0b" },
2462
+ dark: { bg: "#ffffff", border: "transparent", lead: "#71717a", brand: "#0a0a0b", shield: "#0a0a0b" }
2463
+ },
2464
+ dark: {
2465
+ light: { bg: "#0a0a0b", border: "rgba(0,0,0,0.1)", lead: "#d4d4d8", brand: "#ffffff", shield: "#ffffff" },
2466
+ dark: { bg: "#0a0a0b", border: "rgba(255,255,255,0.1)", lead: "#d4d4d8", brand: "#ffffff", shield: "#ffffff" }
2467
+ },
2468
+ outline: {
2469
+ light: { bg: "transparent", border: "rgba(0,0,0,0.15)", lead: "#71717a", brand: "#0a0a0b", shield: "#0a0a0b" },
2470
+ dark: { bg: "transparent", border: "rgba(142,125,255,0.4)", lead: "#d4d4d8", brand: "#ffffff", shield: "#ffffff" }
2471
+ }
2472
+ };
2473
+ function ElvixSecuredBadge({
2474
+ variant = "white",
2475
+ size = "md",
2476
+ theme = "dark",
2477
+ accentColor = "#8e7dff",
2478
+ href = ELVIX_URL2,
2479
+ className = "",
2480
+ width,
2481
+ height,
2482
+ minWidth,
2483
+ maxWidth,
2484
+ minHeight,
2485
+ maxHeight
2486
+ }) {
2487
+ const s = SIZE[size];
2488
+ const t = TONE[variant][theme];
2489
+ const style = {
2490
+ display: "inline-flex",
2491
+ alignItems: "center",
2492
+ gap: s.gap,
2493
+ height: s.height,
2494
+ paddingLeft: s.padX,
2495
+ paddingRight: s.padX,
2496
+ fontSize: s.font,
2497
+ fontWeight: 500,
2498
+ borderRadius: 9999,
2499
+ background: t.bg,
2500
+ border: `1px solid ${t.border}`,
2501
+ color: t.brand,
2502
+ textDecoration: "none",
2503
+ userSelect: "none",
2504
+ lineHeight: 1,
2505
+ // Dimensional overrides win over the size preset above.
2506
+ ...sizeStyle({ width, height, minWidth, maxWidth, minHeight, maxHeight })
2507
+ };
2508
+ return /* @__PURE__ */ jsxs9(
2509
+ "a",
2510
+ {
2511
+ href,
2512
+ target: "_blank",
2513
+ rel: "noopener noreferrer",
2514
+ className,
2515
+ style,
2516
+ "data-elvix-secured-badge": "",
2517
+ children: [
2518
+ /* @__PURE__ */ jsx10(ElvixShield, { size: s.icon, fill: t.shield, accent: accentColor }),
2519
+ /* @__PURE__ */ jsxs9("span", { style: { display: "inline-flex", alignItems: "baseline", gap: 4 }, children: [
2520
+ /* @__PURE__ */ jsx10("span", { style: { color: t.lead }, children: "Secured by" }),
2521
+ /* @__PURE__ */ jsx10("span", { style: { color: t.brand, fontWeight: 600 }, children: "elvix" })
2522
+ ] })
2523
+ ]
2524
+ }
2525
+ );
1040
2526
  }
1041
2527
 
1042
2528
  // src/react/hooks.ts
1043
- import { useCallback, useEffect as useEffect2, useState as useState5 } from "react";
2529
+ import { useCallback as useCallback4, useEffect as useEffect5, useState as useState7 } from "react";
1044
2530
  var POLL_MS = 7e3;
1045
2531
  function useUserList(kind, opts) {
1046
2532
  const { applicationId, baseUrl = "", pollMs = POLL_MS } = opts;
1047
- const [slugs, setSlugs] = useState5([]);
1048
- const [loading, setLoading] = useState5(true);
1049
- const [error, setError] = useState5(null);
1050
- const refresh = useCallback(async () => {
2533
+ const [slugs, setSlugs] = useState7([]);
2534
+ const [loading, setLoading] = useState7(true);
2535
+ const [error, setError] = useState7(null);
2536
+ const refresh = useCallback4(async () => {
1051
2537
  setError(null);
1052
2538
  try {
1053
2539
  const res = await fetch(
@@ -1066,7 +2552,7 @@ function useUserList(kind, opts) {
1066
2552
  setLoading(false);
1067
2553
  }
1068
2554
  }, [applicationId, baseUrl, kind]);
1069
- useEffect2(() => {
2555
+ useEffect5(() => {
1070
2556
  refresh();
1071
2557
  const id = setInterval(refresh, pollMs);
1072
2558
  return () => clearInterval(id);
@@ -1078,13 +2564,13 @@ var useUserScopes = (opts) => useUserList("scopes", opts);
1078
2564
  var useUserMemberships = (opts) => useUserList("memberships", opts);
1079
2565
 
1080
2566
  // src/react/lifecycle-watcher.tsx
1081
- import { useEffect as useEffect3 } from "react";
2567
+ import { useEffect as useEffect6 } from "react";
1082
2568
  function ElvixLifecycleWatcher({
1083
2569
  baseUrl = "",
1084
2570
  pollMs = 7e3,
1085
2571
  onSignedOut
1086
2572
  }) {
1087
- useEffect3(() => {
2573
+ useEffect6(() => {
1088
2574
  let cancelled = false;
1089
2575
  let fired = false;
1090
2576
  const poll = async () => {
@@ -1113,7 +2599,7 @@ function ElvixLifecycleWatcher({
1113
2599
  }
1114
2600
 
1115
2601
  // src/react/elvix-username.tsx
1116
- import { useState as useState6 } from "react";
2602
+ import { useState as useState8 } from "react";
1117
2603
 
1118
2604
  // src/react/lib.ts
1119
2605
  async function appPost(opts, path, body) {
@@ -1171,7 +2657,7 @@ async function appDelete(opts, path) {
1171
2657
  }
1172
2658
 
1173
2659
  // src/react/elvix-username.tsx
1174
- import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
2660
+ import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
1175
2661
  function ElvixUsername({
1176
2662
  onResult,
1177
2663
  width,
@@ -1183,10 +2669,10 @@ function ElvixUsername({
1183
2669
  }) {
1184
2670
  const sizeProps = { width, height, minWidth, maxWidth, minHeight, maxHeight };
1185
2671
  const ctx = useElvixContext();
1186
- const [value, setValue] = useState6("");
1187
- const [busy, setBusy] = useState6(false);
1188
- const [error, setError] = useState6(null);
1189
- const [done, setDone] = useState6(null);
2672
+ const [value, setValue] = useState8("");
2673
+ const [busy, setBusy] = useState8(false);
2674
+ const [error, setError] = useState8(null);
2675
+ const [done, setDone] = useState8(null);
1190
2676
  async function submit(e) {
1191
2677
  e.preventDefault();
1192
2678
  if (!ctx.app) return;
@@ -1206,17 +2692,17 @@ function ElvixUsername({
1206
2692
  onResult?.(result);
1207
2693
  }
1208
2694
  if (done) {
1209
- return /* @__PURE__ */ jsx8(ElvixCard, { title: "Username saved", ...sizeProps, children: /* @__PURE__ */ jsxs7("p", { children: [
2695
+ return /* @__PURE__ */ jsx11(ElvixCard, { title: "Username saved", ...sizeProps, children: /* @__PURE__ */ jsxs10("p", { children: [
1210
2696
  "You are now ",
1211
- /* @__PURE__ */ jsxs7("strong", { children: [
2697
+ /* @__PURE__ */ jsxs10("strong", { children: [
1212
2698
  "@",
1213
2699
  done
1214
2700
  ] }),
1215
2701
  "."
1216
2702
  ] }) });
1217
2703
  }
1218
- return /* @__PURE__ */ jsx8(ElvixCard, { title: "Choose a username", ...sizeProps, children: /* @__PURE__ */ jsxs7("form", { onSubmit: submit, className: "elvix-form", children: [
1219
- /* @__PURE__ */ jsx8(
2704
+ return /* @__PURE__ */ jsx11(ElvixCard, { title: "Choose a username", ...sizeProps, children: /* @__PURE__ */ jsxs10("form", { onSubmit: submit, className: "elvix-form", children: [
2705
+ /* @__PURE__ */ jsx11(
1220
2706
  "input",
1221
2707
  {
1222
2708
  type: "text",
@@ -1229,14 +2715,14 @@ function ElvixUsername({
1229
2715
  className: "elvix-input"
1230
2716
  }
1231
2717
  ),
1232
- /* @__PURE__ */ jsx8("button", { type: "submit", disabled: busy || value.length < 4, className: "elvix-btn elvix-btn-primary", children: busy ? "Saving\u2026" : "Claim" }),
1233
- error && /* @__PURE__ */ jsx8("p", { role: "alert", className: "elvix-error", children: error })
2718
+ /* @__PURE__ */ jsx11("button", { type: "submit", disabled: busy || value.length < 4, className: "elvix-btn elvix-btn-primary", children: busy ? "Saving\u2026" : "Claim" }),
2719
+ error && /* @__PURE__ */ jsx11("p", { role: "alert", className: "elvix-error", children: error })
1234
2720
  ] }) });
1235
2721
  }
1236
2722
 
1237
2723
  // src/react/elvix-avatar.tsx
1238
- import { useState as useState7 } from "react";
1239
- import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
2724
+ import { useState as useState9 } from "react";
2725
+ import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
1240
2726
  function ElvixAvatar({
1241
2727
  onResult,
1242
2728
  width,
@@ -1248,9 +2734,9 @@ function ElvixAvatar({
1248
2734
  }) {
1249
2735
  const sizeProps = { width, height, minWidth, maxWidth, minHeight, maxHeight };
1250
2736
  const ctx = useElvixContext();
1251
- const [busy, setBusy] = useState7(false);
1252
- const [error, setError] = useState7(null);
1253
- const [preview, setPreview] = useState7(null);
2737
+ const [busy, setBusy] = useState9(false);
2738
+ const [error, setError] = useState9(null);
2739
+ const [preview, setPreview] = useState9(null);
1254
2740
  async function onFile(e) {
1255
2741
  const file = e.target.files?.[0];
1256
2742
  if (!file || !ctx.app) return;
@@ -1273,8 +2759,8 @@ function ElvixAvatar({
1273
2759
  if (!result.ok) setError(result.error);
1274
2760
  onResult?.(result);
1275
2761
  }
1276
- return /* @__PURE__ */ jsxs8(ElvixCard, { title: "Avatar", ...sizeProps, children: [
1277
- preview && /* @__PURE__ */ jsx9(
2762
+ return /* @__PURE__ */ jsxs11(ElvixCard, { title: "Avatar", ...sizeProps, children: [
2763
+ preview && /* @__PURE__ */ jsx12(
1278
2764
  "img",
1279
2765
  {
1280
2766
  src: preview,
@@ -1282,15 +2768,15 @@ function ElvixAvatar({
1282
2768
  style: { width: 96, height: 96, borderRadius: "50%", objectFit: "cover", marginBottom: 12 }
1283
2769
  }
1284
2770
  ),
1285
- /* @__PURE__ */ jsx9("input", { type: "file", accept: "image/png,image/jpeg,image/webp", onChange: onFile, disabled: busy }),
1286
- busy && /* @__PURE__ */ jsx9("p", { children: "Uploading\u2026" }),
1287
- error && /* @__PURE__ */ jsx9("p", { role: "alert", className: "elvix-error", children: error })
2771
+ /* @__PURE__ */ jsx12("input", { type: "file", accept: "image/png,image/jpeg,image/webp", onChange: onFile, disabled: busy }),
2772
+ busy && /* @__PURE__ */ jsx12("p", { children: "Uploading\u2026" }),
2773
+ error && /* @__PURE__ */ jsx12("p", { role: "alert", className: "elvix-error", children: error })
1288
2774
  ] });
1289
2775
  }
1290
2776
 
1291
2777
  // src/react/elvix-banner.tsx
1292
- import { useState as useState8 } from "react";
1293
- import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
2778
+ import { useState as useState10 } from "react";
2779
+ import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
1294
2780
  function ElvixBanner({
1295
2781
  onResult,
1296
2782
  width,
@@ -1302,9 +2788,9 @@ function ElvixBanner({
1302
2788
  }) {
1303
2789
  const sizeProps = { width, height, minWidth, maxWidth, minHeight, maxHeight };
1304
2790
  const ctx = useElvixContext();
1305
- const [busy, setBusy] = useState8(false);
1306
- const [error, setError] = useState8(null);
1307
- const [preview, setPreview] = useState8(null);
2791
+ const [busy, setBusy] = useState10(false);
2792
+ const [error, setError] = useState10(null);
2793
+ const [preview, setPreview] = useState10(null);
1308
2794
  async function onFile(e) {
1309
2795
  const file = e.target.files?.[0];
1310
2796
  if (!file || !ctx.app) return;
@@ -1327,8 +2813,8 @@ function ElvixBanner({
1327
2813
  if (!result.ok) setError(result.error);
1328
2814
  onResult?.(result);
1329
2815
  }
1330
- return /* @__PURE__ */ jsxs9(ElvixCard, { title: "Banner", ...sizeProps, children: [
1331
- preview && /* @__PURE__ */ jsx10(
2816
+ return /* @__PURE__ */ jsxs12(ElvixCard, { title: "Banner", ...sizeProps, children: [
2817
+ preview && /* @__PURE__ */ jsx13(
1332
2818
  "img",
1333
2819
  {
1334
2820
  src: preview,
@@ -1336,15 +2822,15 @@ function ElvixBanner({
1336
2822
  style: { width: "100%", aspectRatio: "16/9", objectFit: "cover", borderRadius: 10, marginBottom: 12 }
1337
2823
  }
1338
2824
  ),
1339
- /* @__PURE__ */ jsx10("input", { type: "file", accept: "image/png,image/jpeg,image/webp", onChange: onFile, disabled: busy }),
1340
- busy && /* @__PURE__ */ jsx10("p", { children: "Uploading\u2026" }),
1341
- error && /* @__PURE__ */ jsx10("p", { role: "alert", className: "elvix-error", children: error })
2825
+ /* @__PURE__ */ jsx13("input", { type: "file", accept: "image/png,image/jpeg,image/webp", onChange: onFile, disabled: busy }),
2826
+ busy && /* @__PURE__ */ jsx13("p", { children: "Uploading\u2026" }),
2827
+ error && /* @__PURE__ */ jsx13("p", { role: "alert", className: "elvix-error", children: error })
1342
2828
  ] });
1343
2829
  }
1344
2830
 
1345
2831
  // src/react/elvix-identity-form.tsx
1346
- import { useState as useState9 } from "react";
1347
- import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
2832
+ import { useState as useState11 } from "react";
2833
+ import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
1348
2834
  function ElvixIdentityForm({
1349
2835
  initialName = "",
1350
2836
  initialBio = "",
@@ -1358,11 +2844,11 @@ function ElvixIdentityForm({
1358
2844
  }) {
1359
2845
  const sizeProps = { width, height, minWidth, maxWidth, minHeight, maxHeight };
1360
2846
  const ctx = useElvixContext();
1361
- const [name, setName] = useState9(initialName);
1362
- const [bio, setBio] = useState9(initialBio);
1363
- const [busy, setBusy] = useState9(false);
1364
- const [error, setError] = useState9(null);
1365
- const [saved, setSaved] = useState9(false);
2847
+ const [name, setName] = useState11(initialName);
2848
+ const [bio, setBio] = useState11(initialBio);
2849
+ const [busy, setBusy] = useState11(false);
2850
+ const [error, setError] = useState11(null);
2851
+ const [saved, setSaved] = useState11(false);
1366
2852
  async function submit(e) {
1367
2853
  e.preventDefault();
1368
2854
  if (!ctx.app) return;
@@ -1378,24 +2864,24 @@ function ElvixIdentityForm({
1378
2864
  else setSaved(true);
1379
2865
  onResult?.(result);
1380
2866
  }
1381
- return /* @__PURE__ */ jsx11(ElvixCard, { title: "Identity", ...sizeProps, children: /* @__PURE__ */ jsxs10("form", { onSubmit: submit, className: "elvix-form", children: [
1382
- /* @__PURE__ */ jsxs10("label", { children: [
2867
+ return /* @__PURE__ */ jsx14(ElvixCard, { title: "Identity", ...sizeProps, children: /* @__PURE__ */ jsxs13("form", { onSubmit: submit, className: "elvix-form", children: [
2868
+ /* @__PURE__ */ jsxs13("label", { children: [
1383
2869
  "Name",
1384
- /* @__PURE__ */ jsx11("input", { value: name, onChange: (e) => setName(e.target.value), maxLength: 80, disabled: busy, className: "elvix-input" })
2870
+ /* @__PURE__ */ jsx14("input", { value: name, onChange: (e) => setName(e.target.value), maxLength: 80, disabled: busy, className: "elvix-input" })
1385
2871
  ] }),
1386
- /* @__PURE__ */ jsxs10("label", { children: [
2872
+ /* @__PURE__ */ jsxs13("label", { children: [
1387
2873
  "Bio",
1388
- /* @__PURE__ */ jsx11("textarea", { value: bio, onChange: (e) => setBio(e.target.value), maxLength: 500, rows: 3, disabled: busy, className: "elvix-input" })
2874
+ /* @__PURE__ */ jsx14("textarea", { value: bio, onChange: (e) => setBio(e.target.value), maxLength: 500, rows: 3, disabled: busy, className: "elvix-input" })
1389
2875
  ] }),
1390
- /* @__PURE__ */ jsx11("button", { type: "submit", disabled: busy, className: "elvix-btn elvix-btn-primary", children: busy ? "Saving\u2026" : "Save" }),
1391
- saved && /* @__PURE__ */ jsx11("p", { className: "elvix-muted", children: "Saved." }),
1392
- error && /* @__PURE__ */ jsx11("p", { role: "alert", className: "elvix-error", children: error })
2876
+ /* @__PURE__ */ jsx14("button", { type: "submit", disabled: busy, className: "elvix-btn elvix-btn-primary", children: busy ? "Saving\u2026" : "Save" }),
2877
+ saved && /* @__PURE__ */ jsx14("p", { className: "elvix-muted", children: "Saved." }),
2878
+ error && /* @__PURE__ */ jsx14("p", { role: "alert", className: "elvix-error", children: error })
1393
2879
  ] }) });
1394
2880
  }
1395
2881
 
1396
2882
  // src/react/elvix-region.tsx
1397
- import { useState as useState10 } from "react";
1398
- import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
2883
+ import { useState as useState12 } from "react";
2884
+ import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
1399
2885
  function ElvixRegion({
1400
2886
  initialCountry = "",
1401
2887
  initialTimezone = "",
@@ -1409,11 +2895,11 @@ function ElvixRegion({
1409
2895
  }) {
1410
2896
  const sizeProps = { width, height, minWidth, maxWidth, minHeight, maxHeight };
1411
2897
  const ctx = useElvixContext();
1412
- const [country, setCountry] = useState10(initialCountry);
1413
- const [timezone, setTimezone] = useState10(initialTimezone);
1414
- const [busy, setBusy] = useState10(false);
1415
- const [error, setError] = useState10(null);
1416
- const [saved, setSaved] = useState10(false);
2898
+ const [country, setCountry] = useState12(initialCountry);
2899
+ const [timezone, setTimezone] = useState12(initialTimezone);
2900
+ const [busy, setBusy] = useState12(false);
2901
+ const [error, setError] = useState12(null);
2902
+ const [saved, setSaved] = useState12(false);
1417
2903
  async function submit(e) {
1418
2904
  e.preventDefault();
1419
2905
  if (!ctx.app) return;
@@ -1429,24 +2915,24 @@ function ElvixRegion({
1429
2915
  else setSaved(true);
1430
2916
  onResult?.(result);
1431
2917
  }
1432
- return /* @__PURE__ */ jsx12(ElvixCard, { title: "Region", ...sizeProps, children: /* @__PURE__ */ jsxs11("form", { onSubmit: submit, className: "elvix-form", children: [
1433
- /* @__PURE__ */ jsxs11("label", { children: [
2918
+ return /* @__PURE__ */ jsx15(ElvixCard, { title: "Region", ...sizeProps, children: /* @__PURE__ */ jsxs14("form", { onSubmit: submit, className: "elvix-form", children: [
2919
+ /* @__PURE__ */ jsxs14("label", { children: [
1434
2920
  "Country (ISO-2)",
1435
- /* @__PURE__ */ jsx12("input", { value: country, onChange: (e) => setCountry(e.target.value.toUpperCase()), maxLength: 2, pattern: "[A-Z]{2}", disabled: busy, className: "elvix-input" })
2921
+ /* @__PURE__ */ jsx15("input", { value: country, onChange: (e) => setCountry(e.target.value.toUpperCase()), maxLength: 2, pattern: "[A-Z]{2}", disabled: busy, className: "elvix-input" })
1436
2922
  ] }),
1437
- /* @__PURE__ */ jsxs11("label", { children: [
2923
+ /* @__PURE__ */ jsxs14("label", { children: [
1438
2924
  "Timezone",
1439
- /* @__PURE__ */ jsx12("input", { value: timezone, onChange: (e) => setTimezone(e.target.value), placeholder: "Europe/Berlin", disabled: busy, className: "elvix-input" })
2925
+ /* @__PURE__ */ jsx15("input", { value: timezone, onChange: (e) => setTimezone(e.target.value), placeholder: "Europe/Berlin", disabled: busy, className: "elvix-input" })
1440
2926
  ] }),
1441
- /* @__PURE__ */ jsx12("button", { type: "submit", disabled: busy, className: "elvix-btn elvix-btn-primary", children: busy ? "Saving\u2026" : "Save" }),
1442
- saved && /* @__PURE__ */ jsx12("p", { className: "elvix-muted", children: "Saved." }),
1443
- error && /* @__PURE__ */ jsx12("p", { role: "alert", className: "elvix-error", children: error })
2927
+ /* @__PURE__ */ jsx15("button", { type: "submit", disabled: busy, className: "elvix-btn elvix-btn-primary", children: busy ? "Saving\u2026" : "Save" }),
2928
+ saved && /* @__PURE__ */ jsx15("p", { className: "elvix-muted", children: "Saved." }),
2929
+ error && /* @__PURE__ */ jsx15("p", { role: "alert", className: "elvix-error", children: error })
1444
2930
  ] }) });
1445
2931
  }
1446
2932
 
1447
2933
  // src/react/elvix-languages.tsx
1448
- import { useState as useState11 } from "react";
1449
- import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
2934
+ import { useState as useState13 } from "react";
2935
+ import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
1450
2936
  function ElvixLanguages({
1451
2937
  initial = [],
1452
2938
  onResult,
@@ -1459,10 +2945,10 @@ function ElvixLanguages({
1459
2945
  }) {
1460
2946
  const sizeProps = { width, height, minWidth, maxWidth, minHeight, maxHeight };
1461
2947
  const ctx = useElvixContext();
1462
- const [raw, setRaw] = useState11(initial.join(", "));
1463
- const [busy, setBusy] = useState11(false);
1464
- const [error, setError] = useState11(null);
1465
- const [saved, setSaved] = useState11(false);
2948
+ const [raw, setRaw] = useState13(initial.join(", "));
2949
+ const [busy, setBusy] = useState13(false);
2950
+ const [error, setError] = useState13(null);
2951
+ const [saved, setSaved] = useState13(false);
1466
2952
  async function submit(e) {
1467
2953
  e.preventDefault();
1468
2954
  if (!ctx.app) return;
@@ -1479,20 +2965,20 @@ function ElvixLanguages({
1479
2965
  else setSaved(true);
1480
2966
  onResult?.(result);
1481
2967
  }
1482
- return /* @__PURE__ */ jsx13(ElvixCard, { title: "Languages", ...sizeProps, children: /* @__PURE__ */ jsxs12("form", { onSubmit: submit, className: "elvix-form", children: [
1483
- /* @__PURE__ */ jsxs12("label", { children: [
2968
+ return /* @__PURE__ */ jsx16(ElvixCard, { title: "Languages", ...sizeProps, children: /* @__PURE__ */ jsxs15("form", { onSubmit: submit, className: "elvix-form", children: [
2969
+ /* @__PURE__ */ jsxs15("label", { children: [
1484
2970
  "Preferred languages (comma-separated BCP-47 tags)",
1485
- /* @__PURE__ */ jsx13("input", { value: raw, onChange: (e) => setRaw(e.target.value), placeholder: "en-GB, de-DE", disabled: busy, className: "elvix-input" })
2971
+ /* @__PURE__ */ jsx16("input", { value: raw, onChange: (e) => setRaw(e.target.value), placeholder: "en-GB, de-DE", disabled: busy, className: "elvix-input" })
1486
2972
  ] }),
1487
- /* @__PURE__ */ jsx13("button", { type: "submit", disabled: busy, className: "elvix-btn elvix-btn-primary", children: busy ? "Saving\u2026" : "Save" }),
1488
- saved && /* @__PURE__ */ jsx13("p", { className: "elvix-muted", children: "Saved." }),
1489
- error && /* @__PURE__ */ jsx13("p", { role: "alert", className: "elvix-error", children: error })
2973
+ /* @__PURE__ */ jsx16("button", { type: "submit", disabled: busy, className: "elvix-btn elvix-btn-primary", children: busy ? "Saving\u2026" : "Save" }),
2974
+ saved && /* @__PURE__ */ jsx16("p", { className: "elvix-muted", children: "Saved." }),
2975
+ error && /* @__PURE__ */ jsx16("p", { role: "alert", className: "elvix-error", children: error })
1490
2976
  ] }) });
1491
2977
  }
1492
2978
 
1493
2979
  // src/react/elvix-sessions.tsx
1494
- import { useEffect as useEffect4, useState as useState12 } from "react";
1495
- import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
2980
+ import { useEffect as useEffect7, useState as useState14 } from "react";
2981
+ import { jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
1496
2982
  function ElvixSessions({
1497
2983
  onResult,
1498
2984
  width,
@@ -1504,10 +2990,10 @@ function ElvixSessions({
1504
2990
  }) {
1505
2991
  const sizeProps = { width, height, minWidth, maxWidth, minHeight, maxHeight };
1506
2992
  const ctx = useElvixContext();
1507
- const [rows, setRows] = useState12(null);
1508
- const [error, setError] = useState12(null);
1509
- const [busy, setBusy] = useState12(false);
1510
- useEffect4(() => {
2993
+ const [rows, setRows] = useState14(null);
2994
+ const [error, setError] = useState14(null);
2995
+ const [busy, setBusy] = useState14(false);
2996
+ useEffect7(() => {
1511
2997
  if (!ctx.app) return;
1512
2998
  fetch(`${ctx.baseUrl}/api/account/apps/${ctx.app.applicationId}/sessions`, {
1513
2999
  ...authInit()
@@ -1527,29 +3013,29 @@ function ElvixSessions({
1527
3013
  if (result.ok) setRows((prev) => prev?.filter((s) => s.id !== id) ?? null);
1528
3014
  onResult?.(result);
1529
3015
  }
1530
- return /* @__PURE__ */ jsxs13(ElvixCard, { title: "Active sessions", ...sizeProps, children: [
1531
- error && /* @__PURE__ */ jsx14("p", { role: "alert", className: "elvix-error", children: error }),
1532
- !rows && !error && /* @__PURE__ */ jsx14("p", { children: "Loading\u2026" }),
1533
- rows && /* @__PURE__ */ jsx14("ul", { style: { listStyle: "none", padding: 0, margin: 0 }, children: rows.map((s) => /* @__PURE__ */ jsx14("li", { style: { padding: "10px 0", borderBottom: "1px solid rgba(0,0,0,0.06)" }, children: /* @__PURE__ */ jsxs13("div", { style: { display: "flex", justifyContent: "space-between", gap: 12 }, children: [
1534
- /* @__PURE__ */ jsxs13("div", { children: [
1535
- /* @__PURE__ */ jsxs13("div", { style: { fontSize: 13, fontWeight: 500 }, children: [
3016
+ return /* @__PURE__ */ jsxs16(ElvixCard, { title: "Active sessions", ...sizeProps, children: [
3017
+ error && /* @__PURE__ */ jsx17("p", { role: "alert", className: "elvix-error", children: error }),
3018
+ !rows && !error && /* @__PURE__ */ jsx17("p", { children: "Loading\u2026" }),
3019
+ rows && /* @__PURE__ */ jsx17("ul", { style: { listStyle: "none", padding: 0, margin: 0 }, children: rows.map((s) => /* @__PURE__ */ jsx17("li", { style: { padding: "10px 0", borderBottom: "1px solid rgba(0,0,0,0.06)" }, children: /* @__PURE__ */ jsxs16("div", { style: { display: "flex", justifyContent: "space-between", gap: 12 }, children: [
3020
+ /* @__PURE__ */ jsxs16("div", { children: [
3021
+ /* @__PURE__ */ jsxs16("div", { style: { fontSize: 13, fontWeight: 500 }, children: [
1536
3022
  s.device,
1537
- s.current && /* @__PURE__ */ jsx14("span", { style: { marginLeft: 8, color: "var(--elvix-primary-strong)", fontSize: 11 }, children: "\xB7 this device" })
3023
+ s.current && /* @__PURE__ */ jsx17("span", { style: { marginLeft: 8, color: "var(--elvix-primary-strong)", fontSize: 11 }, children: "\xB7 this device" })
1538
3024
  ] }),
1539
- /* @__PURE__ */ jsxs13("div", { style: { fontSize: 11, color: "rgba(0,0,0,0.55)" }, children: [
3025
+ /* @__PURE__ */ jsxs16("div", { style: { fontSize: 11, color: "rgba(0,0,0,0.55)" }, children: [
1540
3026
  s.country ?? "\u2014",
1541
3027
  " \xB7 since ",
1542
3028
  new Date(s.createdAt).toLocaleDateString()
1543
3029
  ] })
1544
3030
  ] }),
1545
- !s.current && /* @__PURE__ */ jsx14("button", { type: "button", disabled: busy, onClick: () => revoke(s.id), className: "elvix-btn elvix-btn-ghost", children: "Revoke" })
3031
+ !s.current && /* @__PURE__ */ jsx17("button", { type: "button", disabled: busy, onClick: () => revoke(s.id), className: "elvix-btn elvix-btn-ghost", children: "Revoke" })
1546
3032
  ] }) }, s.id)) })
1547
3033
  ] });
1548
3034
  }
1549
3035
 
1550
3036
  // src/react/elvix-export.tsx
1551
- import { useState as useState13 } from "react";
1552
- import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
3037
+ import { useState as useState15 } from "react";
3038
+ import { jsx as jsx18, jsxs as jsxs17 } from "react/jsx-runtime";
1553
3039
  function ElvixExport({
1554
3040
  onResult,
1555
3041
  width,
@@ -1561,9 +3047,9 @@ function ElvixExport({
1561
3047
  }) {
1562
3048
  const sizeProps = { width, height, minWidth, maxWidth, minHeight, maxHeight };
1563
3049
  const ctx = useElvixContext();
1564
- const [busy, setBusy] = useState13(false);
1565
- const [done, setDone] = useState13(false);
1566
- const [error, setError] = useState13(null);
3050
+ const [busy, setBusy] = useState15(false);
3051
+ const [done, setDone] = useState15(false);
3052
+ const [error, setError] = useState15(null);
1567
3053
  async function start() {
1568
3054
  if (!ctx.app) return;
1569
3055
  setBusy(true);
@@ -1578,16 +3064,16 @@ function ElvixExport({
1578
3064
  else setDone(true);
1579
3065
  onResult?.(result);
1580
3066
  }
1581
- return /* @__PURE__ */ jsxs14(ElvixCard, { title: "Export my data", ...sizeProps, children: [
1582
- /* @__PURE__ */ jsx15("p", { style: { fontSize: 13, color: "rgba(0,0,0,0.6)" }, children: "Request a zip of every record we hold for you in this app. Delivery by email; single-use download link valid for 24h." }),
1583
- done ? /* @__PURE__ */ jsx15("p", { className: "elvix-muted", children: "Request queued. Check your email." }) : /* @__PURE__ */ jsx15("button", { type: "button", onClick: start, disabled: busy, className: "elvix-btn elvix-btn-primary", children: busy ? "Queuing\u2026" : "Request export" }),
1584
- error && /* @__PURE__ */ jsx15("p", { role: "alert", className: "elvix-error", children: error })
3067
+ return /* @__PURE__ */ jsxs17(ElvixCard, { title: "Export my data", ...sizeProps, children: [
3068
+ /* @__PURE__ */ jsx18("p", { style: { fontSize: 13, color: "rgba(0,0,0,0.6)" }, children: "Request a zip of every record we hold for you in this app. Delivery by email; single-use download link valid for 24h." }),
3069
+ done ? /* @__PURE__ */ jsx18("p", { className: "elvix-muted", children: "Request queued. Check your email." }) : /* @__PURE__ */ jsx18("button", { type: "button", onClick: start, disabled: busy, className: "elvix-btn elvix-btn-primary", children: busy ? "Queuing\u2026" : "Request export" }),
3070
+ error && /* @__PURE__ */ jsx18("p", { role: "alert", className: "elvix-error", children: error })
1585
3071
  ] });
1586
3072
  }
1587
3073
 
1588
3074
  // src/react/elvix-deactivate.tsx
1589
- import { useState as useState14 } from "react";
1590
- import { Fragment as Fragment3, jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
3075
+ import { useState as useState16 } from "react";
3076
+ import { Fragment as Fragment5, jsx as jsx19, jsxs as jsxs18 } from "react/jsx-runtime";
1591
3077
  function ElvixDeactivate({
1592
3078
  onResult,
1593
3079
  width,
@@ -1599,11 +3085,11 @@ function ElvixDeactivate({
1599
3085
  }) {
1600
3086
  const sizeProps = { width, height, minWidth, maxWidth, minHeight, maxHeight };
1601
3087
  const ctx = useElvixContext();
1602
- const [pane, setPane] = useState14("warn");
1603
- const [challengeId, setChallengeId] = useState14(null);
1604
- const [code, setCode] = useState14("");
1605
- const [busy, setBusy] = useState14(false);
1606
- const [error, setError] = useState14(null);
3088
+ const [pane, setPane] = useState16("warn");
3089
+ const [challengeId, setChallengeId] = useState16(null);
3090
+ const [code, setCode] = useState16("");
3091
+ const [busy, setBusy] = useState16(false);
3092
+ const [error, setError] = useState16(null);
1607
3093
  async function startChallenge() {
1608
3094
  if (!ctx.app) return;
1609
3095
  setBusy(true);
@@ -1641,16 +3127,16 @@ function ElvixDeactivate({
1641
3127
  onResult?.(result);
1642
3128
  }
1643
3129
  if (pane === "done") {
1644
- return /* @__PURE__ */ jsx16(ElvixCard, { title: "Deactivated", ...sizeProps, children: /* @__PURE__ */ jsx16("p", { children: "Your access has been paused. Sign in again to restore it." }) });
3130
+ return /* @__PURE__ */ jsx19(ElvixCard, { title: "Deactivated", ...sizeProps, children: /* @__PURE__ */ jsx19("p", { children: "Your access has been paused. Sign in again to restore it." }) });
1645
3131
  }
1646
- return /* @__PURE__ */ jsxs15(ElvixCard, { title: "Deactivate account", ...sizeProps, children: [
1647
- pane === "warn" && /* @__PURE__ */ jsxs15(Fragment3, { children: [
1648
- /* @__PURE__ */ jsx16("p", { style: { fontSize: 13, color: "rgba(0,0,0,0.6)" }, children: "Pause your membership. You can restore it any time by signing in again. No data is deleted." }),
1649
- /* @__PURE__ */ jsx16("button", { type: "button", onClick: startChallenge, disabled: busy, className: "elvix-btn elvix-btn-danger", children: busy ? "Sending\u2026" : "Send code" })
3132
+ return /* @__PURE__ */ jsxs18(ElvixCard, { title: "Deactivate account", ...sizeProps, children: [
3133
+ pane === "warn" && /* @__PURE__ */ jsxs18(Fragment5, { children: [
3134
+ /* @__PURE__ */ jsx19("p", { style: { fontSize: 13, color: "rgba(0,0,0,0.6)" }, children: "Pause your membership. You can restore it any time by signing in again. No data is deleted." }),
3135
+ /* @__PURE__ */ jsx19("button", { type: "button", onClick: startChallenge, disabled: busy, className: "elvix-btn elvix-btn-danger", children: busy ? "Sending\u2026" : "Send code" })
1650
3136
  ] }),
1651
- pane === "otp" && /* @__PURE__ */ jsxs15("form", { onSubmit: confirm, className: "elvix-form", children: [
1652
- /* @__PURE__ */ jsx16("p", { className: "elvix-muted", children: "We sent a 6-digit code to your email." }),
1653
- /* @__PURE__ */ jsx16(
3137
+ pane === "otp" && /* @__PURE__ */ jsxs18("form", { onSubmit: confirm, className: "elvix-form", children: [
3138
+ /* @__PURE__ */ jsx19("p", { className: "elvix-muted", children: "We sent a 6-digit code to your email." }),
3139
+ /* @__PURE__ */ jsx19(
1654
3140
  "input",
1655
3141
  {
1656
3142
  type: "text",
@@ -1664,15 +3150,15 @@ function ElvixDeactivate({
1664
3150
  className: "elvix-input"
1665
3151
  }
1666
3152
  ),
1667
- /* @__PURE__ */ jsx16("button", { type: "submit", disabled: busy || code.length !== 6, className: "elvix-btn elvix-btn-danger", children: busy ? "Deactivating\u2026" : "Confirm" })
3153
+ /* @__PURE__ */ jsx19("button", { type: "submit", disabled: busy || code.length !== 6, className: "elvix-btn elvix-btn-danger", children: busy ? "Deactivating\u2026" : "Confirm" })
1668
3154
  ] }),
1669
- error && /* @__PURE__ */ jsx16("p", { role: "alert", className: "elvix-error", children: error })
3155
+ error && /* @__PURE__ */ jsx19("p", { role: "alert", className: "elvix-error", children: error })
1670
3156
  ] });
1671
3157
  }
1672
3158
 
1673
3159
  // src/react/elvix-leave.tsx
1674
- import { useState as useState15 } from "react";
1675
- import { Fragment as Fragment4, jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
3160
+ import { useState as useState17 } from "react";
3161
+ import { Fragment as Fragment6, jsx as jsx20, jsxs as jsxs19 } from "react/jsx-runtime";
1676
3162
  function ElvixLeave({
1677
3163
  onResult,
1678
3164
  width,
@@ -1684,11 +3170,11 @@ function ElvixLeave({
1684
3170
  }) {
1685
3171
  const sizeProps = { width, height, minWidth, maxWidth, minHeight, maxHeight };
1686
3172
  const ctx = useElvixContext();
1687
- const [pane, setPane] = useState15("warn");
1688
- const [challengeId, setChallengeId] = useState15(null);
1689
- const [code, setCode] = useState15("");
1690
- const [busy, setBusy] = useState15(false);
1691
- const [error, setError] = useState15(null);
3173
+ const [pane, setPane] = useState17("warn");
3174
+ const [challengeId, setChallengeId] = useState17(null);
3175
+ const [code, setCode] = useState17("");
3176
+ const [busy, setBusy] = useState17(false);
3177
+ const [error, setError] = useState17(null);
1692
3178
  async function startChallenge() {
1693
3179
  if (!ctx.app) return;
1694
3180
  setBusy(true);
@@ -1726,16 +3212,16 @@ function ElvixLeave({
1726
3212
  onResult?.(result);
1727
3213
  }
1728
3214
  if (pane === "done") {
1729
- return /* @__PURE__ */ jsx17(ElvixCard, { title: "You've left", ...sizeProps, children: /* @__PURE__ */ jsx17("p", { children: "You've left this app. Your data is archived; sign in again to rejoin." }) });
3215
+ return /* @__PURE__ */ jsx20(ElvixCard, { title: "You've left", ...sizeProps, children: /* @__PURE__ */ jsx20("p", { children: "You've left this app. Your data is archived; sign in again to rejoin." }) });
1730
3216
  }
1731
- return /* @__PURE__ */ jsxs16(ElvixCard, { title: "Leave this app", ...sizeProps, children: [
1732
- pane === "warn" && /* @__PURE__ */ jsxs16(Fragment4, { children: [
1733
- /* @__PURE__ */ jsx17("p", { style: { fontSize: 13, color: "rgba(0,0,0,0.6)" }, children: "Remove yourself from this app. Audit trail is preserved; you can sign back in any time to rejoin." }),
1734
- /* @__PURE__ */ jsx17("button", { type: "button", onClick: startChallenge, disabled: busy, className: "elvix-btn elvix-btn-danger", children: busy ? "Sending\u2026" : "Send code" })
3217
+ return /* @__PURE__ */ jsxs19(ElvixCard, { title: "Leave this app", ...sizeProps, children: [
3218
+ pane === "warn" && /* @__PURE__ */ jsxs19(Fragment6, { children: [
3219
+ /* @__PURE__ */ jsx20("p", { style: { fontSize: 13, color: "rgba(0,0,0,0.6)" }, children: "Remove yourself from this app. Audit trail is preserved; you can sign back in any time to rejoin." }),
3220
+ /* @__PURE__ */ jsx20("button", { type: "button", onClick: startChallenge, disabled: busy, className: "elvix-btn elvix-btn-danger", children: busy ? "Sending\u2026" : "Send code" })
1735
3221
  ] }),
1736
- pane === "otp" && /* @__PURE__ */ jsxs16("form", { onSubmit: confirm, className: "elvix-form", children: [
1737
- /* @__PURE__ */ jsx17("p", { className: "elvix-muted", children: "We sent a 6-digit code to your email." }),
1738
- /* @__PURE__ */ jsx17(
3222
+ pane === "otp" && /* @__PURE__ */ jsxs19("form", { onSubmit: confirm, className: "elvix-form", children: [
3223
+ /* @__PURE__ */ jsx20("p", { className: "elvix-muted", children: "We sent a 6-digit code to your email." }),
3224
+ /* @__PURE__ */ jsx20(
1739
3225
  "input",
1740
3226
  {
1741
3227
  type: "text",
@@ -1749,15 +3235,15 @@ function ElvixLeave({
1749
3235
  className: "elvix-input"
1750
3236
  }
1751
3237
  ),
1752
- /* @__PURE__ */ jsx17("button", { type: "submit", disabled: busy || code.length !== 6, className: "elvix-btn elvix-btn-danger", children: busy ? "Leaving\u2026" : "Confirm leave" })
3238
+ /* @__PURE__ */ jsx20("button", { type: "submit", disabled: busy || code.length !== 6, className: "elvix-btn elvix-btn-danger", children: busy ? "Leaving\u2026" : "Confirm leave" })
1753
3239
  ] }),
1754
- error && /* @__PURE__ */ jsx17("p", { role: "alert", className: "elvix-error", children: error })
3240
+ error && /* @__PURE__ */ jsx20("p", { role: "alert", className: "elvix-error", children: error })
1755
3241
  ] });
1756
3242
  }
1757
3243
 
1758
3244
  // src/react/elvix-address-book.tsx
1759
- import { useEffect as useEffect5, useState as useState16 } from "react";
1760
- import { jsx as jsx18, jsxs as jsxs17 } from "react/jsx-runtime";
3245
+ import { useEffect as useEffect8, useState as useState18 } from "react";
3246
+ import { jsx as jsx21, jsxs as jsxs20 } from "react/jsx-runtime";
1761
3247
  function ElvixAddressBook({
1762
3248
  onResult,
1763
3249
  width,
@@ -1769,11 +3255,11 @@ function ElvixAddressBook({
1769
3255
  }) {
1770
3256
  const sizeProps = { width, height, minWidth, maxWidth, minHeight, maxHeight };
1771
3257
  const ctx = useElvixContext();
1772
- const [rows, setRows] = useState16(null);
1773
- const [error, setError] = useState16(null);
1774
- const [busy, setBusy] = useState16(false);
1775
- const [adding, setAdding] = useState16(false);
1776
- const [form, setForm] = useState16({
3258
+ const [rows, setRows] = useState18(null);
3259
+ const [error, setError] = useState18(null);
3260
+ const [busy, setBusy] = useState18(false);
3261
+ const [adding, setAdding] = useState18(false);
3262
+ const [form, setForm] = useState18({
1777
3263
  label: "Home",
1778
3264
  line1: "",
1779
3265
  postalCode: "",
@@ -1789,7 +3275,7 @@ function ElvixAddressBook({
1789
3275
  else setError("load_failed");
1790
3276
  }).catch(() => setError("network"));
1791
3277
  }
1792
- useEffect5(() => {
3278
+ useEffect8(() => {
1793
3279
  reload();
1794
3280
  }, [ctx.app, ctx.baseUrl]);
1795
3281
  async function add(e) {
@@ -1822,14 +3308,14 @@ function ElvixAddressBook({
1822
3308
  if (result.ok) setRows((prev) => prev?.filter((a) => a.id !== id) ?? null);
1823
3309
  onResult?.(result);
1824
3310
  }
1825
- return /* @__PURE__ */ jsxs17(ElvixCard, { title: "Addresses", ...sizeProps, children: [
1826
- error && /* @__PURE__ */ jsx18("p", { role: "alert", className: "elvix-error", children: error }),
1827
- !rows && !error && /* @__PURE__ */ jsx18("p", { children: "Loading\u2026" }),
1828
- rows && rows.length === 0 && /* @__PURE__ */ jsx18("p", { className: "elvix-muted", children: "No addresses yet." }),
1829
- rows?.map((a) => /* @__PURE__ */ jsxs17("div", { style: { padding: "8px 0", borderBottom: "1px solid rgba(0,0,0,0.06)", display: "flex", justifyContent: "space-between", gap: 12 }, children: [
1830
- /* @__PURE__ */ jsxs17("div", { style: { fontSize: 13 }, children: [
1831
- /* @__PURE__ */ jsx18("div", { style: { fontWeight: 500 }, children: a.label }),
1832
- /* @__PURE__ */ jsxs17("div", { style: { color: "rgba(0,0,0,0.55)" }, children: [
3311
+ return /* @__PURE__ */ jsxs20(ElvixCard, { title: "Addresses", ...sizeProps, children: [
3312
+ error && /* @__PURE__ */ jsx21("p", { role: "alert", className: "elvix-error", children: error }),
3313
+ !rows && !error && /* @__PURE__ */ jsx21("p", { children: "Loading\u2026" }),
3314
+ rows && rows.length === 0 && /* @__PURE__ */ jsx21("p", { className: "elvix-muted", children: "No addresses yet." }),
3315
+ rows?.map((a) => /* @__PURE__ */ jsxs20("div", { style: { padding: "8px 0", borderBottom: "1px solid rgba(0,0,0,0.06)", display: "flex", justifyContent: "space-between", gap: 12 }, children: [
3316
+ /* @__PURE__ */ jsxs20("div", { style: { fontSize: 13 }, children: [
3317
+ /* @__PURE__ */ jsx21("div", { style: { fontWeight: 500 }, children: a.label }),
3318
+ /* @__PURE__ */ jsxs20("div", { style: { color: "rgba(0,0,0,0.55)" }, children: [
1833
3319
  a.line1,
1834
3320
  a.line2 ? `, ${a.line2}` : "",
1835
3321
  ", ",
@@ -1840,23 +3326,23 @@ function ElvixAddressBook({
1840
3326
  a.country
1841
3327
  ] })
1842
3328
  ] }),
1843
- /* @__PURE__ */ jsx18("button", { type: "button", disabled: busy, onClick: () => remove(a.id), className: "elvix-btn elvix-btn-ghost", children: "Remove" })
3329
+ /* @__PURE__ */ jsx21("button", { type: "button", disabled: busy, onClick: () => remove(a.id), className: "elvix-btn elvix-btn-ghost", children: "Remove" })
1844
3330
  ] }, a.id)),
1845
- !adding && /* @__PURE__ */ jsx18("button", { type: "button", onClick: () => setAdding(true), className: "elvix-btn elvix-btn-primary", style: { marginTop: 12 }, children: "Add address" }),
1846
- adding && /* @__PURE__ */ jsxs17("form", { onSubmit: add, className: "elvix-form", style: { marginTop: 12 }, children: [
1847
- /* @__PURE__ */ jsx18("input", { value: form.label, onChange: (e) => setForm({ ...form, label: e.target.value }), placeholder: "Label", className: "elvix-input" }),
1848
- /* @__PURE__ */ jsx18("input", { value: form.line1, onChange: (e) => setForm({ ...form, line1: e.target.value }), placeholder: "Street", required: true, className: "elvix-input" }),
1849
- /* @__PURE__ */ jsx18("input", { value: form.postalCode, onChange: (e) => setForm({ ...form, postalCode: e.target.value }), placeholder: "Postal code", required: true, className: "elvix-input" }),
1850
- /* @__PURE__ */ jsx18("input", { value: form.city, onChange: (e) => setForm({ ...form, city: e.target.value }), placeholder: "City", required: true, className: "elvix-input" }),
1851
- /* @__PURE__ */ jsx18("input", { value: form.country, onChange: (e) => setForm({ ...form, country: e.target.value.toUpperCase() }), placeholder: "Country (ISO-2)", maxLength: 2, required: true, className: "elvix-input" }),
1852
- /* @__PURE__ */ jsx18("button", { type: "submit", disabled: busy, className: "elvix-btn elvix-btn-primary", children: busy ? "Saving\u2026" : "Save" })
3331
+ !adding && /* @__PURE__ */ jsx21("button", { type: "button", onClick: () => setAdding(true), className: "elvix-btn elvix-btn-primary", style: { marginTop: 12 }, children: "Add address" }),
3332
+ adding && /* @__PURE__ */ jsxs20("form", { onSubmit: add, className: "elvix-form", style: { marginTop: 12 }, children: [
3333
+ /* @__PURE__ */ jsx21("input", { value: form.label, onChange: (e) => setForm({ ...form, label: e.target.value }), placeholder: "Label", className: "elvix-input" }),
3334
+ /* @__PURE__ */ jsx21("input", { value: form.line1, onChange: (e) => setForm({ ...form, line1: e.target.value }), placeholder: "Street", required: true, className: "elvix-input" }),
3335
+ /* @__PURE__ */ jsx21("input", { value: form.postalCode, onChange: (e) => setForm({ ...form, postalCode: e.target.value }), placeholder: "Postal code", required: true, className: "elvix-input" }),
3336
+ /* @__PURE__ */ jsx21("input", { value: form.city, onChange: (e) => setForm({ ...form, city: e.target.value }), placeholder: "City", required: true, className: "elvix-input" }),
3337
+ /* @__PURE__ */ jsx21("input", { value: form.country, onChange: (e) => setForm({ ...form, country: e.target.value.toUpperCase() }), placeholder: "Country (ISO-2)", maxLength: 2, required: true, className: "elvix-input" }),
3338
+ /* @__PURE__ */ jsx21("button", { type: "submit", disabled: busy, className: "elvix-btn elvix-btn-primary", children: busy ? "Saving\u2026" : "Save" })
1853
3339
  ] })
1854
3340
  ] });
1855
3341
  }
1856
3342
 
1857
3343
  // src/react/elvix-legal-entities.tsx
1858
- import { useEffect as useEffect6, useState as useState17 } from "react";
1859
- import { jsx as jsx19, jsxs as jsxs18 } from "react/jsx-runtime";
3344
+ import { useEffect as useEffect9, useState as useState19 } from "react";
3345
+ import { jsx as jsx22, jsxs as jsxs21 } from "react/jsx-runtime";
1860
3346
  function ElvixLegalEntities({
1861
3347
  onResult,
1862
3348
  width,
@@ -1868,11 +3354,11 @@ function ElvixLegalEntities({
1868
3354
  }) {
1869
3355
  const sizeProps = { width, height, minWidth, maxWidth, minHeight, maxHeight };
1870
3356
  const ctx = useElvixContext();
1871
- const [rows, setRows] = useState17(null);
1872
- const [error, setError] = useState17(null);
1873
- const [busy, setBusy] = useState17(false);
1874
- const [adding, setAdding] = useState17(false);
1875
- const [form, setForm] = useState17({
3357
+ const [rows, setRows] = useState19(null);
3358
+ const [error, setError] = useState19(null);
3359
+ const [busy, setBusy] = useState19(false);
3360
+ const [adding, setAdding] = useState19(false);
3361
+ const [form, setForm] = useState19({
1876
3362
  legalName: "",
1877
3363
  taxId: "",
1878
3364
  country: ""
@@ -1886,7 +3372,7 @@ function ElvixLegalEntities({
1886
3372
  else setError("load_failed");
1887
3373
  }).catch(() => setError("network"));
1888
3374
  }
1889
- useEffect6(() => {
3375
+ useEffect9(() => {
1890
3376
  reload();
1891
3377
  }, [ctx.app, ctx.baseUrl]);
1892
3378
  async function add(e) {
@@ -1919,27 +3405,27 @@ function ElvixLegalEntities({
1919
3405
  if (result.ok) setRows((prev) => prev?.filter((a) => a.id !== id) ?? null);
1920
3406
  onResult?.(result);
1921
3407
  }
1922
- return /* @__PURE__ */ jsxs18(ElvixCard, { title: "Legal entities", ...sizeProps, children: [
1923
- error && /* @__PURE__ */ jsx19("p", { role: "alert", className: "elvix-error", children: error }),
1924
- !rows && !error && /* @__PURE__ */ jsx19("p", { children: "Loading\u2026" }),
1925
- rows && rows.length === 0 && /* @__PURE__ */ jsx19("p", { className: "elvix-muted", children: "No legal entities yet." }),
1926
- rows?.map((e) => /* @__PURE__ */ jsxs18("div", { style: { padding: "8px 0", borderBottom: "1px solid rgba(0,0,0,0.06)", display: "flex", justifyContent: "space-between", gap: 12 }, children: [
1927
- /* @__PURE__ */ jsxs18("div", { style: { fontSize: 13 }, children: [
1928
- /* @__PURE__ */ jsx19("div", { style: { fontWeight: 500 }, children: e.legalName }),
1929
- /* @__PURE__ */ jsxs18("div", { style: { color: "rgba(0,0,0,0.55)" }, children: [
3408
+ return /* @__PURE__ */ jsxs21(ElvixCard, { title: "Legal entities", ...sizeProps, children: [
3409
+ error && /* @__PURE__ */ jsx22("p", { role: "alert", className: "elvix-error", children: error }),
3410
+ !rows && !error && /* @__PURE__ */ jsx22("p", { children: "Loading\u2026" }),
3411
+ rows && rows.length === 0 && /* @__PURE__ */ jsx22("p", { className: "elvix-muted", children: "No legal entities yet." }),
3412
+ rows?.map((e) => /* @__PURE__ */ jsxs21("div", { style: { padding: "8px 0", borderBottom: "1px solid rgba(0,0,0,0.06)", display: "flex", justifyContent: "space-between", gap: 12 }, children: [
3413
+ /* @__PURE__ */ jsxs21("div", { style: { fontSize: 13 }, children: [
3414
+ /* @__PURE__ */ jsx22("div", { style: { fontWeight: 500 }, children: e.legalName }),
3415
+ /* @__PURE__ */ jsxs21("div", { style: { color: "rgba(0,0,0,0.55)" }, children: [
1930
3416
  e.taxId,
1931
3417
  " \xB7 ",
1932
3418
  e.country
1933
3419
  ] })
1934
3420
  ] }),
1935
- /* @__PURE__ */ jsx19("button", { type: "button", disabled: busy, onClick: () => remove(e.id), className: "elvix-btn elvix-btn-ghost", children: "Remove" })
3421
+ /* @__PURE__ */ jsx22("button", { type: "button", disabled: busy, onClick: () => remove(e.id), className: "elvix-btn elvix-btn-ghost", children: "Remove" })
1936
3422
  ] }, e.id)),
1937
- !adding && /* @__PURE__ */ jsx19("button", { type: "button", onClick: () => setAdding(true), className: "elvix-btn elvix-btn-primary", style: { marginTop: 12 }, children: "Add entity" }),
1938
- adding && /* @__PURE__ */ jsxs18("form", { onSubmit: add, className: "elvix-form", style: { marginTop: 12 }, children: [
1939
- /* @__PURE__ */ jsx19("input", { value: form.legalName, onChange: (e) => setForm({ ...form, legalName: e.target.value }), placeholder: "Legal name", required: true, className: "elvix-input" }),
1940
- /* @__PURE__ */ jsx19("input", { value: form.taxId, onChange: (e) => setForm({ ...form, taxId: e.target.value }), placeholder: "Tax / VAT ID", required: true, className: "elvix-input" }),
1941
- /* @__PURE__ */ jsx19("input", { value: form.country, onChange: (e) => setForm({ ...form, country: e.target.value.toUpperCase() }), placeholder: "Country (ISO-2)", maxLength: 2, required: true, className: "elvix-input" }),
1942
- /* @__PURE__ */ jsx19("button", { type: "submit", disabled: busy, className: "elvix-btn elvix-btn-primary", children: busy ? "Saving\u2026" : "Save" })
3423
+ !adding && /* @__PURE__ */ jsx22("button", { type: "button", onClick: () => setAdding(true), className: "elvix-btn elvix-btn-primary", style: { marginTop: 12 }, children: "Add entity" }),
3424
+ adding && /* @__PURE__ */ jsxs21("form", { onSubmit: add, className: "elvix-form", style: { marginTop: 12 }, children: [
3425
+ /* @__PURE__ */ jsx22("input", { value: form.legalName, onChange: (e) => setForm({ ...form, legalName: e.target.value }), placeholder: "Legal name", required: true, className: "elvix-input" }),
3426
+ /* @__PURE__ */ jsx22("input", { value: form.taxId, onChange: (e) => setForm({ ...form, taxId: e.target.value }), placeholder: "Tax / VAT ID", required: true, className: "elvix-input" }),
3427
+ /* @__PURE__ */ jsx22("input", { value: form.country, onChange: (e) => setForm({ ...form, country: e.target.value.toUpperCase() }), placeholder: "Country (ISO-2)", maxLength: 2, required: true, className: "elvix-input" }),
3428
+ /* @__PURE__ */ jsx22("button", { type: "submit", disabled: busy, className: "elvix-btn elvix-btn-primary", children: busy ? "Saving\u2026" : "Save" })
1943
3429
  ] })
1944
3430
  ] });
1945
3431
  }