@elvix.is/sdk 0.5.6 → 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.d.ts +190 -33
- package/dist/react.js +1976 -676
- package/dist/styles.css +2 -0
- package/package.json +10 -4
package/dist/react.js
CHANGED
|
@@ -363,6 +363,88 @@ async function runPasskeySignIn(baseUrl, clientId) {
|
|
|
363
363
|
return { ok: false, error: "network", message: e instanceof Error ? e.message : void 0 };
|
|
364
364
|
}
|
|
365
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
|
+
}
|
|
366
448
|
|
|
367
449
|
// src/react/elvix-sign-in.tsx
|
|
368
450
|
import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
@@ -554,552 +636,1693 @@ function ElvixSignIn({
|
|
|
554
636
|
}
|
|
555
637
|
|
|
556
638
|
// src/react/elvix-sign-in-form.tsx
|
|
557
|
-
import {
|
|
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";
|
|
558
641
|
|
|
559
|
-
// src/react/elvix-
|
|
642
|
+
// src/react/elvix-logo.tsx
|
|
560
643
|
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
561
|
-
function
|
|
562
|
-
size
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
return /* @__PURE__ */ jsxs3("svg", { width: size, height: size, viewBox: "2 2 20 20", "aria-hidden": true, style: { display: "block" }, children: [
|
|
567
|
-
/* @__PURE__ */ jsx4(
|
|
568
|
-
"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",
|
|
569
649
|
{
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
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
|
+
]
|
|
573
666
|
}
|
|
574
667
|
),
|
|
575
|
-
/* @__PURE__ */ jsx4(
|
|
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
|
+
)
|
|
576
676
|
] });
|
|
577
677
|
}
|
|
578
678
|
|
|
579
|
-
// src/react/elvix-
|
|
580
|
-
import {
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
dark: {
|
|
593
|
-
light: { bg: "#0a0a0b", border: "rgba(0,0,0,0.1)", lead: "#d4d4d8", brand: "#ffffff", shield: "#ffffff" },
|
|
594
|
-
dark: { bg: "#0a0a0b", border: "rgba(255,255,255,0.1)", lead: "#d4d4d8", brand: "#ffffff", shield: "#ffffff" }
|
|
595
|
-
},
|
|
596
|
-
outline: {
|
|
597
|
-
light: { bg: "transparent", border: "rgba(0,0,0,0.15)", lead: "#71717a", brand: "#0a0a0b", shield: "#0a0a0b" },
|
|
598
|
-
dark: { bg: "transparent", border: "rgba(142,125,255,0.4)", lead: "#d4d4d8", brand: "#ffffff", shield: "#ffffff" }
|
|
599
|
-
}
|
|
600
|
-
};
|
|
601
|
-
function ElvixSecuredBadge({
|
|
602
|
-
variant = "white",
|
|
603
|
-
size = "md",
|
|
604
|
-
theme = "dark",
|
|
605
|
-
accentColor = "#8e7dff",
|
|
606
|
-
href = ELVIX_URL,
|
|
607
|
-
className = "",
|
|
608
|
-
width,
|
|
609
|
-
height,
|
|
610
|
-
minWidth,
|
|
611
|
-
maxWidth,
|
|
612
|
-
minHeight,
|
|
613
|
-
maxHeight
|
|
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
|
|
614
692
|
}) {
|
|
615
|
-
const
|
|
616
|
-
const
|
|
617
|
-
const
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
background: t.bg,
|
|
628
|
-
border: `1px solid ${t.border}`,
|
|
629
|
-
color: t.brand,
|
|
630
|
-
textDecoration: "none",
|
|
631
|
-
userSelect: "none",
|
|
632
|
-
lineHeight: 1,
|
|
633
|
-
// Dimensional overrides win over the size preset above.
|
|
634
|
-
...sizeStyle({ width, height, minWidth, maxWidth, minHeight, maxHeight })
|
|
635
|
-
};
|
|
636
|
-
return /* @__PURE__ */ jsxs4(
|
|
637
|
-
"a",
|
|
638
|
-
{
|
|
639
|
-
href,
|
|
640
|
-
target: "_blank",
|
|
641
|
-
rel: "noopener noreferrer",
|
|
642
|
-
className,
|
|
643
|
-
style,
|
|
644
|
-
"data-elvix-secured-badge": "",
|
|
645
|
-
children: [
|
|
646
|
-
/* @__PURE__ */ jsx5(ElvixShield, { size: s.icon, fill: t.shield, accent: accentColor }),
|
|
647
|
-
/* @__PURE__ */ jsxs4("span", { style: { display: "inline-flex", alignItems: "baseline", gap: 4 }, children: [
|
|
648
|
-
/* @__PURE__ */ jsx5("span", { style: { color: t.lead }, children: "Secured by" }),
|
|
649
|
-
/* @__PURE__ */ jsx5("span", { style: { color: t.brand, fontWeight: 600 }, children: "elvix" })
|
|
650
|
-
] })
|
|
651
|
-
]
|
|
652
|
-
}
|
|
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
|
|
653
705
|
);
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
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");
|
|
677
740
|
}
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
/* @__PURE__ */ jsx6(
|
|
683
|
-
"path",
|
|
741
|
+
}
|
|
742
|
+
return /* @__PURE__ */ jsx5("div", { className: "relative overflow-hidden", children: /* @__PURE__ */ jsxs4(AnimatePresence, { mode: "wait", children: [
|
|
743
|
+
pane === "decide" && /* @__PURE__ */ jsx5(
|
|
744
|
+
motion.div,
|
|
684
745
|
{
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
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"
|
|
688
764
|
),
|
|
689
|
-
/* @__PURE__ */
|
|
690
|
-
|
|
765
|
+
pane === "done_restored" && /* @__PURE__ */ jsx5(
|
|
766
|
+
motion.div,
|
|
691
767
|
{
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
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"
|
|
695
782
|
),
|
|
696
|
-
/* @__PURE__ */
|
|
697
|
-
|
|
783
|
+
pane === "done_cancelled" && /* @__PURE__ */ jsx5(
|
|
784
|
+
motion.div,
|
|
698
785
|
{
|
|
699
|
-
|
|
700
|
-
|
|
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
|
|
811
|
+
}) {
|
|
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}`
|
|
856
|
+
] })
|
|
701
857
|
}
|
|
702
858
|
),
|
|
703
|
-
/* @__PURE__ */
|
|
704
|
-
"
|
|
859
|
+
/* @__PURE__ */ jsx5(
|
|
860
|
+
"button",
|
|
705
861
|
{
|
|
706
|
-
|
|
707
|
-
|
|
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
|
+
] })
|
|
708
870
|
}
|
|
709
871
|
)
|
|
710
872
|
] });
|
|
711
873
|
}
|
|
712
|
-
function
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
className = "",
|
|
717
|
-
minWidth,
|
|
718
|
-
maxWidth,
|
|
719
|
-
minHeight,
|
|
720
|
-
maxHeight,
|
|
721
|
-
width,
|
|
722
|
-
height
|
|
874
|
+
function DonePane({
|
|
875
|
+
icon,
|
|
876
|
+
title,
|
|
877
|
+
body
|
|
723
878
|
}) {
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
const showPasskey = Boolean(app?.methodPasskey);
|
|
742
|
-
const showEmail = Boolean(app?.methodEmailOtp);
|
|
743
|
-
const showDivider = (showGoogle || showPasskey) && showEmail;
|
|
744
|
-
const logoSrc = app?.iconUrl || app?.logoUrl || null;
|
|
745
|
-
const privacyUrl = app?.privacyPolicyUrl || null;
|
|
746
|
-
const termsUrl = app?.termsOfServiceUrl || null;
|
|
747
|
-
const hasLegal = Boolean(privacyUrl || termsUrl);
|
|
748
|
-
const cardStyle = useMemo2(
|
|
749
|
-
() => ({
|
|
750
|
-
boxSizing: "border-box",
|
|
751
|
-
// Defaults first; the shared sizeStyle() overrides only the keys the host set,
|
|
752
|
-
// so the form keeps its width "100%" / maxWidth 400 defaults when unsized.
|
|
753
|
-
width: "100%",
|
|
754
|
-
maxWidth: 400,
|
|
755
|
-
...sizeStyle({ width, height, minWidth, maxWidth, minHeight, maxHeight }),
|
|
756
|
-
margin: "0 auto",
|
|
757
|
-
display: "flex",
|
|
758
|
-
flexDirection: "column",
|
|
759
|
-
gap: 18,
|
|
760
|
-
background: "#fff",
|
|
761
|
-
color: "#18181b",
|
|
762
|
-
border: "1px solid rgba(0,0,0,0.08)",
|
|
763
|
-
borderRadius: 16,
|
|
764
|
-
boxShadow: "0 1px 2px rgba(0,0,0,0.04), 0 12px 32px -12px rgba(0,0,0,0.18)",
|
|
765
|
-
padding: 28,
|
|
766
|
-
fontFamily: "ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif",
|
|
767
|
-
textAlign: "center"
|
|
768
|
-
}),
|
|
769
|
-
[width, maxWidth, minWidth, height, minHeight, maxHeight]
|
|
770
|
-
);
|
|
771
|
-
function fail(error2, message) {
|
|
772
|
-
setError(message ?? error2);
|
|
773
|
-
onResult?.({ ok: false, error: error2, message });
|
|
774
|
-
}
|
|
775
|
-
async function startGoogle() {
|
|
776
|
-
if (!ctx.clientId) return fail("missing_client_id", "ElvixProvider needs a clientId.");
|
|
777
|
-
window.location.assign(
|
|
778
|
-
`${ctx.baseUrl}/api/auth/google/start?intent=app&clientId=${encodeURIComponent(ctx.clientId)}`
|
|
779
|
-
);
|
|
780
|
-
}
|
|
781
|
-
async function startPasskey() {
|
|
782
|
-
setBusy(true);
|
|
783
|
-
setError(null);
|
|
784
|
-
try {
|
|
785
|
-
const result = await runPasskeySignIn(ctx.baseUrl, ctx.clientId);
|
|
786
|
-
if (!result.ok) {
|
|
787
|
-
if (result.error === "passkey_cancelled") {
|
|
788
|
-
onResult?.({ ok: false, error: result.error });
|
|
789
|
-
return;
|
|
790
|
-
}
|
|
791
|
-
return fail(result.error, result.message);
|
|
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 })
|
|
792
896
|
}
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
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
|
|
799
937
|
});
|
|
800
|
-
|
|
801
|
-
setBusy(false);
|
|
938
|
+
return;
|
|
802
939
|
}
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
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]
|
|
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";
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
// src/react/elvix-sign-in-form.tsx
|
|
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",
|
|
1343
|
+
{
|
|
1344
|
+
className: framed ? "bg-canvas" : void 0,
|
|
1345
|
+
style: theme === "dark" ? ELVIX_DARK_VARS : ELVIX_LIGHT_VARS,
|
|
1346
|
+
children: content
|
|
1347
|
+
}
|
|
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",
|
|
1363
|
+
{
|
|
1364
|
+
"aria-hidden": true,
|
|
1365
|
+
className: "absolute inset-0 -m-4 rounded-[18px] bg-black/30 backdrop-blur-sm pointer-events-none"
|
|
1366
|
+
}
|
|
1367
|
+
),
|
|
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",
|
|
1378
|
+
{
|
|
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
|
+
)
|
|
1405
|
+
}
|
|
1406
|
+
)
|
|
1407
|
+
] });
|
|
1408
|
+
}
|
|
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
|
|
1437
|
+
}) {
|
|
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
|
|
1443
|
+
);
|
|
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
|
|
819
1475
|
});
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
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;
|
|
823
1488
|
}
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
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;
|
|
1651
|
+
setError(null);
|
|
1652
|
+
setPasskeyBusy(true);
|
|
1653
|
+
try {
|
|
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;
|
|
1662
|
+
}
|
|
1663
|
+
applyLanding({ next_step: "done", redirect: result.redirect, token: result.token });
|
|
828
1664
|
} finally {
|
|
829
|
-
|
|
1665
|
+
setPasskeyBusy(false);
|
|
830
1666
|
}
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
if (
|
|
835
|
-
|
|
836
|
-
|
|
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;
|
|
837
1728
|
setError(null);
|
|
1729
|
+
setOnboardingBusy("add");
|
|
838
1730
|
try {
|
|
839
|
-
const
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
return
|
|
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;
|
|
848
1740
|
}
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
token: body.data?.token
|
|
856
|
-
});
|
|
857
|
-
} catch (e2) {
|
|
858
|
-
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;
|
|
859
1747
|
} finally {
|
|
860
|
-
|
|
1748
|
+
setOnboardingBusy(null);
|
|
861
1749
|
}
|
|
862
|
-
}
|
|
863
|
-
const
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
const primaryBtnStyle = {
|
|
884
|
-
boxSizing: "border-box",
|
|
885
|
-
width: "100%",
|
|
886
|
-
height: 44,
|
|
887
|
-
marginTop: 10,
|
|
888
|
-
border: "1px solid rgba(0,0,0,0.08)",
|
|
889
|
-
borderRadius: 10,
|
|
890
|
-
background: brand,
|
|
891
|
-
color: onBrand,
|
|
892
|
-
fontSize: 14,
|
|
893
|
-
fontWeight: 600,
|
|
894
|
-
cursor: busy ? "not-allowed" : "pointer",
|
|
895
|
-
opacity: busy ? 0.65 : 1,
|
|
896
|
-
backgroundImage: "linear-gradient(to bottom, rgba(255,255,255,0.12), rgba(255,255,255,0) 40%)",
|
|
897
|
-
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)"
|
|
898
|
-
};
|
|
899
|
-
const googleBtnStyle = {
|
|
900
|
-
boxSizing: "border-box",
|
|
901
|
-
width: "100%",
|
|
902
|
-
height: 44,
|
|
903
|
-
display: "inline-flex",
|
|
904
|
-
alignItems: "center",
|
|
905
|
-
justifyContent: "center",
|
|
906
|
-
gap: 10,
|
|
907
|
-
border: "1px solid rgba(0,0,0,0.14)",
|
|
908
|
-
borderRadius: 10,
|
|
909
|
-
background: "#fff",
|
|
910
|
-
color: "#18181b",
|
|
911
|
-
fontSize: 14,
|
|
912
|
-
fontWeight: 500,
|
|
913
|
-
cursor: busy ? "not-allowed" : "pointer",
|
|
914
|
-
opacity: busy ? 0.65 : 1
|
|
915
|
-
};
|
|
916
|
-
const tileTint = hexToRgba(brand, 0.12);
|
|
917
|
-
const root = `${className}`.trim() || void 0;
|
|
918
|
-
return /* @__PURE__ */ jsxs5("div", { className: root, style: cardStyle, "data-elvix-pane": step, children: [
|
|
919
|
-
/* @__PURE__ */ jsxs5("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", gap: 12 }, children: [
|
|
920
|
-
/* @__PURE__ */ jsx6(
|
|
921
|
-
"div",
|
|
922
|
-
{
|
|
923
|
-
style: {
|
|
924
|
-
width: 52,
|
|
925
|
-
height: 52,
|
|
926
|
-
borderRadius: 14,
|
|
927
|
-
display: "grid",
|
|
928
|
-
placeItems: "center",
|
|
929
|
-
overflow: "hidden",
|
|
930
|
-
background: logoSrc ? "#fff" : tileTint,
|
|
931
|
-
border: "1px solid rgba(0,0,0,0.08)"
|
|
932
|
-
},
|
|
933
|
-
children: logoSrc ? (
|
|
934
|
-
// biome-ignore lint/a11y/useAltText: alt is set
|
|
935
|
-
/* @__PURE__ */ jsx6(
|
|
936
|
-
"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",
|
|
937
1771
|
{
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
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() ?? "?" })
|
|
941
1775
|
}
|
|
942
|
-
)
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
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,
|
|
955
1823
|
{
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
disabled:
|
|
959
|
-
|
|
960
|
-
"data-elvix-method": "google",
|
|
961
|
-
children: [
|
|
962
|
-
/* @__PURE__ */ jsx6(GoogleG, {}),
|
|
963
|
-
/* @__PURE__ */ jsx6("span", { children: copy.googleButton })
|
|
964
|
-
]
|
|
1824
|
+
value: code,
|
|
1825
|
+
onChange: setCode,
|
|
1826
|
+
disabled: isPreview || verifyingOtp,
|
|
1827
|
+
autoFocus: true
|
|
965
1828
|
}
|
|
966
1829
|
),
|
|
967
|
-
|
|
1830
|
+
/* @__PURE__ */ jsx7(
|
|
968
1831
|
"button",
|
|
969
1832
|
{
|
|
970
|
-
type: "
|
|
971
|
-
|
|
972
|
-
disabled:
|
|
973
|
-
style:
|
|
974
|
-
"
|
|
975
|
-
|
|
976
|
-
/* @__PURE__ */
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
/* @__PURE__ */ jsx6("span", { style: { flex: 1, height: 1, background: "rgba(0,0,0,0.08)" } })
|
|
990
|
-
]
|
|
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
|
+
] })
|
|
991
1852
|
}
|
|
992
1853
|
),
|
|
993
|
-
|
|
994
|
-
/* @__PURE__ */
|
|
995
|
-
|
|
996
|
-
"input",
|
|
1854
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-between pt-1", children: [
|
|
1855
|
+
/* @__PURE__ */ jsxs6(
|
|
1856
|
+
"button",
|
|
997
1857
|
{
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
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
|
+
]
|
|
1007
1869
|
}
|
|
1008
1870
|
),
|
|
1009
|
-
/* @__PURE__ */
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
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",
|
|
1016
1947
|
{
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
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
|
+
] }) })
|
|
1030
1984
|
}
|
|
1031
1985
|
),
|
|
1032
|
-
/* @__PURE__ */
|
|
1033
|
-
/* @__PURE__ */ jsx6(
|
|
1986
|
+
/* @__PURE__ */ jsx7(
|
|
1034
1987
|
"button",
|
|
1035
1988
|
{
|
|
1036
1989
|
type: "button",
|
|
1037
|
-
onClick:
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
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
|
+
}
|
|
1050
2015
|
},
|
|
1051
|
-
|
|
2016
|
+
onCancel: ({ redirect }) => {
|
|
2017
|
+
window.location.href = redirect;
|
|
2018
|
+
}
|
|
1052
2019
|
}
|
|
1053
2020
|
)
|
|
1054
|
-
] }),
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
"By continuing, you agree to ",
|
|
1058
|
-
appName,
|
|
1059
|
-
"'s",
|
|
1060
|
-
" ",
|
|
1061
|
-
termsUrl && /* @__PURE__ */ jsx6(
|
|
1062
|
-
"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,
|
|
1063
2024
|
{
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
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
|
+
}
|
|
1069
2038
|
}
|
|
1070
2039
|
),
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
"a",
|
|
2040
|
+
(methodGoogle || methodPasskey) && /* @__PURE__ */ jsxs6(
|
|
2041
|
+
"div",
|
|
1074
2042
|
{
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
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
|
+
]
|
|
1080
2085
|
}
|
|
1081
|
-
)
|
|
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
|
+
] })
|
|
1082
2133
|
] }),
|
|
1083
|
-
|
|
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
|
+
] })
|
|
1084
2147
|
] });
|
|
1085
2148
|
}
|
|
1086
|
-
function
|
|
1087
|
-
const
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
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
|
+
}
|
|
1097
2298
|
}
|
|
1098
2299
|
|
|
1099
2300
|
// src/react/elvix-sign-in-button.tsx
|
|
1100
|
-
import { useState as
|
|
1101
|
-
|
|
1102
|
-
|
|
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";
|
|
1103
2326
|
var PRESET_LABEL = {
|
|
1104
2327
|
"sign-in-with-elvix": "Sign in with elvix",
|
|
1105
2328
|
"continue-with-elvix": "Continue with elvix",
|
|
@@ -1138,7 +2361,7 @@ function shieldColor(variant, theme) {
|
|
|
1138
2361
|
}
|
|
1139
2362
|
function ElvixSignInButton({
|
|
1140
2363
|
clientId,
|
|
1141
|
-
baseUrl =
|
|
2364
|
+
baseUrl = ELVIX_URL,
|
|
1142
2365
|
returnUrl,
|
|
1143
2366
|
type = "standard",
|
|
1144
2367
|
variant = "filled",
|
|
@@ -1160,7 +2383,7 @@ function ElvixSignInButton({
|
|
|
1160
2383
|
maxHeight
|
|
1161
2384
|
}) {
|
|
1162
2385
|
const sized = sizeStyle({ width, height, minWidth, maxWidth, minHeight, maxHeight });
|
|
1163
|
-
const [embedOpen, setEmbedOpen] =
|
|
2386
|
+
const [embedOpen, setEmbedOpen] = useState6(false);
|
|
1164
2387
|
const isIcon = type === "icon";
|
|
1165
2388
|
const resolvedLabel = label ?? PRESET_LABEL[preset];
|
|
1166
2389
|
const tone = variantTone(variant, theme);
|
|
@@ -1185,16 +2408,16 @@ function ElvixSignInButton({
|
|
|
1185
2408
|
// Dimensional overrides win over the size preset above.
|
|
1186
2409
|
...sized
|
|
1187
2410
|
};
|
|
1188
|
-
const content = /* @__PURE__ */
|
|
1189
|
-
/* @__PURE__ */
|
|
1190
|
-
isIcon ? null : /* @__PURE__ */
|
|
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 })
|
|
1191
2414
|
] });
|
|
1192
2415
|
if (mode === "callback") {
|
|
1193
|
-
return /* @__PURE__ */
|
|
2416
|
+
return /* @__PURE__ */ jsx9("button", { type: "button", onClick, className, style, "aria-label": isIcon ? resolvedLabel : void 0, children: content });
|
|
1194
2417
|
}
|
|
1195
2418
|
if (mode === "embed") {
|
|
1196
|
-
return /* @__PURE__ */
|
|
1197
|
-
!embedOpen && /* @__PURE__ */
|
|
2419
|
+
return /* @__PURE__ */ jsxs8("div", { "data-elvix-signin-button-embed": "", style: sized, children: [
|
|
2420
|
+
!embedOpen && /* @__PURE__ */ jsx9(
|
|
1198
2421
|
"button",
|
|
1199
2422
|
{
|
|
1200
2423
|
type: "button",
|
|
@@ -1205,7 +2428,7 @@ function ElvixSignInButton({
|
|
|
1205
2428
|
children: content
|
|
1206
2429
|
}
|
|
1207
2430
|
),
|
|
1208
|
-
embedOpen && /* @__PURE__ */
|
|
2431
|
+
embedOpen && /* @__PURE__ */ jsx9(
|
|
1209
2432
|
ElvixSignIn,
|
|
1210
2433
|
{
|
|
1211
2434
|
onResult: (r) => {
|
|
@@ -1222,18 +2445,95 @@ function ElvixSignInButton({
|
|
|
1222
2445
|
const sep = base.includes("?") ? "&" : "?";
|
|
1223
2446
|
return `${base}${sep}return=${encodeURIComponent(returnUrl)}`;
|
|
1224
2447
|
})();
|
|
1225
|
-
return /* @__PURE__ */
|
|
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
|
+
);
|
|
1226
2526
|
}
|
|
1227
2527
|
|
|
1228
2528
|
// src/react/hooks.ts
|
|
1229
|
-
import { useCallback, useEffect as
|
|
2529
|
+
import { useCallback as useCallback4, useEffect as useEffect5, useState as useState7 } from "react";
|
|
1230
2530
|
var POLL_MS = 7e3;
|
|
1231
2531
|
function useUserList(kind, opts) {
|
|
1232
2532
|
const { applicationId, baseUrl = "", pollMs = POLL_MS } = opts;
|
|
1233
|
-
const [slugs, setSlugs] =
|
|
1234
|
-
const [loading, setLoading] =
|
|
1235
|
-
const [error, setError] =
|
|
1236
|
-
const refresh =
|
|
2533
|
+
const [slugs, setSlugs] = useState7([]);
|
|
2534
|
+
const [loading, setLoading] = useState7(true);
|
|
2535
|
+
const [error, setError] = useState7(null);
|
|
2536
|
+
const refresh = useCallback4(async () => {
|
|
1237
2537
|
setError(null);
|
|
1238
2538
|
try {
|
|
1239
2539
|
const res = await fetch(
|
|
@@ -1252,7 +2552,7 @@ function useUserList(kind, opts) {
|
|
|
1252
2552
|
setLoading(false);
|
|
1253
2553
|
}
|
|
1254
2554
|
}, [applicationId, baseUrl, kind]);
|
|
1255
|
-
|
|
2555
|
+
useEffect5(() => {
|
|
1256
2556
|
refresh();
|
|
1257
2557
|
const id = setInterval(refresh, pollMs);
|
|
1258
2558
|
return () => clearInterval(id);
|
|
@@ -1264,13 +2564,13 @@ var useUserScopes = (opts) => useUserList("scopes", opts);
|
|
|
1264
2564
|
var useUserMemberships = (opts) => useUserList("memberships", opts);
|
|
1265
2565
|
|
|
1266
2566
|
// src/react/lifecycle-watcher.tsx
|
|
1267
|
-
import { useEffect as
|
|
2567
|
+
import { useEffect as useEffect6 } from "react";
|
|
1268
2568
|
function ElvixLifecycleWatcher({
|
|
1269
2569
|
baseUrl = "",
|
|
1270
2570
|
pollMs = 7e3,
|
|
1271
2571
|
onSignedOut
|
|
1272
2572
|
}) {
|
|
1273
|
-
|
|
2573
|
+
useEffect6(() => {
|
|
1274
2574
|
let cancelled = false;
|
|
1275
2575
|
let fired = false;
|
|
1276
2576
|
const poll = async () => {
|
|
@@ -1299,7 +2599,7 @@ function ElvixLifecycleWatcher({
|
|
|
1299
2599
|
}
|
|
1300
2600
|
|
|
1301
2601
|
// src/react/elvix-username.tsx
|
|
1302
|
-
import { useState as
|
|
2602
|
+
import { useState as useState8 } from "react";
|
|
1303
2603
|
|
|
1304
2604
|
// src/react/lib.ts
|
|
1305
2605
|
async function appPost(opts, path, body) {
|
|
@@ -1357,7 +2657,7 @@ async function appDelete(opts, path) {
|
|
|
1357
2657
|
}
|
|
1358
2658
|
|
|
1359
2659
|
// src/react/elvix-username.tsx
|
|
1360
|
-
import { jsx as
|
|
2660
|
+
import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1361
2661
|
function ElvixUsername({
|
|
1362
2662
|
onResult,
|
|
1363
2663
|
width,
|
|
@@ -1369,10 +2669,10 @@ function ElvixUsername({
|
|
|
1369
2669
|
}) {
|
|
1370
2670
|
const sizeProps = { width, height, minWidth, maxWidth, minHeight, maxHeight };
|
|
1371
2671
|
const ctx = useElvixContext();
|
|
1372
|
-
const [value, setValue] =
|
|
1373
|
-
const [busy, setBusy] =
|
|
1374
|
-
const [error, setError] =
|
|
1375
|
-
const [done, setDone] =
|
|
2672
|
+
const [value, setValue] = useState8("");
|
|
2673
|
+
const [busy, setBusy] = useState8(false);
|
|
2674
|
+
const [error, setError] = useState8(null);
|
|
2675
|
+
const [done, setDone] = useState8(null);
|
|
1376
2676
|
async function submit(e) {
|
|
1377
2677
|
e.preventDefault();
|
|
1378
2678
|
if (!ctx.app) return;
|
|
@@ -1392,17 +2692,17 @@ function ElvixUsername({
|
|
|
1392
2692
|
onResult?.(result);
|
|
1393
2693
|
}
|
|
1394
2694
|
if (done) {
|
|
1395
|
-
return /* @__PURE__ */
|
|
2695
|
+
return /* @__PURE__ */ jsx11(ElvixCard, { title: "Username saved", ...sizeProps, children: /* @__PURE__ */ jsxs10("p", { children: [
|
|
1396
2696
|
"You are now ",
|
|
1397
|
-
/* @__PURE__ */
|
|
2697
|
+
/* @__PURE__ */ jsxs10("strong", { children: [
|
|
1398
2698
|
"@",
|
|
1399
2699
|
done
|
|
1400
2700
|
] }),
|
|
1401
2701
|
"."
|
|
1402
2702
|
] }) });
|
|
1403
2703
|
}
|
|
1404
|
-
return /* @__PURE__ */
|
|
1405
|
-
/* @__PURE__ */
|
|
2704
|
+
return /* @__PURE__ */ jsx11(ElvixCard, { title: "Choose a username", ...sizeProps, children: /* @__PURE__ */ jsxs10("form", { onSubmit: submit, className: "elvix-form", children: [
|
|
2705
|
+
/* @__PURE__ */ jsx11(
|
|
1406
2706
|
"input",
|
|
1407
2707
|
{
|
|
1408
2708
|
type: "text",
|
|
@@ -1415,14 +2715,14 @@ function ElvixUsername({
|
|
|
1415
2715
|
className: "elvix-input"
|
|
1416
2716
|
}
|
|
1417
2717
|
),
|
|
1418
|
-
/* @__PURE__ */
|
|
1419
|
-
error && /* @__PURE__ */
|
|
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 })
|
|
1420
2720
|
] }) });
|
|
1421
2721
|
}
|
|
1422
2722
|
|
|
1423
2723
|
// src/react/elvix-avatar.tsx
|
|
1424
|
-
import { useState as
|
|
1425
|
-
import { jsx as
|
|
2724
|
+
import { useState as useState9 } from "react";
|
|
2725
|
+
import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1426
2726
|
function ElvixAvatar({
|
|
1427
2727
|
onResult,
|
|
1428
2728
|
width,
|
|
@@ -1434,9 +2734,9 @@ function ElvixAvatar({
|
|
|
1434
2734
|
}) {
|
|
1435
2735
|
const sizeProps = { width, height, minWidth, maxWidth, minHeight, maxHeight };
|
|
1436
2736
|
const ctx = useElvixContext();
|
|
1437
|
-
const [busy, setBusy] =
|
|
1438
|
-
const [error, setError] =
|
|
1439
|
-
const [preview, setPreview] =
|
|
2737
|
+
const [busy, setBusy] = useState9(false);
|
|
2738
|
+
const [error, setError] = useState9(null);
|
|
2739
|
+
const [preview, setPreview] = useState9(null);
|
|
1440
2740
|
async function onFile(e) {
|
|
1441
2741
|
const file = e.target.files?.[0];
|
|
1442
2742
|
if (!file || !ctx.app) return;
|
|
@@ -1459,8 +2759,8 @@ function ElvixAvatar({
|
|
|
1459
2759
|
if (!result.ok) setError(result.error);
|
|
1460
2760
|
onResult?.(result);
|
|
1461
2761
|
}
|
|
1462
|
-
return /* @__PURE__ */
|
|
1463
|
-
preview && /* @__PURE__ */
|
|
2762
|
+
return /* @__PURE__ */ jsxs11(ElvixCard, { title: "Avatar", ...sizeProps, children: [
|
|
2763
|
+
preview && /* @__PURE__ */ jsx12(
|
|
1464
2764
|
"img",
|
|
1465
2765
|
{
|
|
1466
2766
|
src: preview,
|
|
@@ -1468,15 +2768,15 @@ function ElvixAvatar({
|
|
|
1468
2768
|
style: { width: 96, height: 96, borderRadius: "50%", objectFit: "cover", marginBottom: 12 }
|
|
1469
2769
|
}
|
|
1470
2770
|
),
|
|
1471
|
-
/* @__PURE__ */
|
|
1472
|
-
busy && /* @__PURE__ */
|
|
1473
|
-
error && /* @__PURE__ */
|
|
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 })
|
|
1474
2774
|
] });
|
|
1475
2775
|
}
|
|
1476
2776
|
|
|
1477
2777
|
// src/react/elvix-banner.tsx
|
|
1478
|
-
import { useState as
|
|
1479
|
-
import { jsx as
|
|
2778
|
+
import { useState as useState10 } from "react";
|
|
2779
|
+
import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1480
2780
|
function ElvixBanner({
|
|
1481
2781
|
onResult,
|
|
1482
2782
|
width,
|
|
@@ -1488,9 +2788,9 @@ function ElvixBanner({
|
|
|
1488
2788
|
}) {
|
|
1489
2789
|
const sizeProps = { width, height, minWidth, maxWidth, minHeight, maxHeight };
|
|
1490
2790
|
const ctx = useElvixContext();
|
|
1491
|
-
const [busy, setBusy] =
|
|
1492
|
-
const [error, setError] =
|
|
1493
|
-
const [preview, setPreview] =
|
|
2791
|
+
const [busy, setBusy] = useState10(false);
|
|
2792
|
+
const [error, setError] = useState10(null);
|
|
2793
|
+
const [preview, setPreview] = useState10(null);
|
|
1494
2794
|
async function onFile(e) {
|
|
1495
2795
|
const file = e.target.files?.[0];
|
|
1496
2796
|
if (!file || !ctx.app) return;
|
|
@@ -1513,8 +2813,8 @@ function ElvixBanner({
|
|
|
1513
2813
|
if (!result.ok) setError(result.error);
|
|
1514
2814
|
onResult?.(result);
|
|
1515
2815
|
}
|
|
1516
|
-
return /* @__PURE__ */
|
|
1517
|
-
preview && /* @__PURE__ */
|
|
2816
|
+
return /* @__PURE__ */ jsxs12(ElvixCard, { title: "Banner", ...sizeProps, children: [
|
|
2817
|
+
preview && /* @__PURE__ */ jsx13(
|
|
1518
2818
|
"img",
|
|
1519
2819
|
{
|
|
1520
2820
|
src: preview,
|
|
@@ -1522,15 +2822,15 @@ function ElvixBanner({
|
|
|
1522
2822
|
style: { width: "100%", aspectRatio: "16/9", objectFit: "cover", borderRadius: 10, marginBottom: 12 }
|
|
1523
2823
|
}
|
|
1524
2824
|
),
|
|
1525
|
-
/* @__PURE__ */
|
|
1526
|
-
busy && /* @__PURE__ */
|
|
1527
|
-
error && /* @__PURE__ */
|
|
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 })
|
|
1528
2828
|
] });
|
|
1529
2829
|
}
|
|
1530
2830
|
|
|
1531
2831
|
// src/react/elvix-identity-form.tsx
|
|
1532
|
-
import { useState as
|
|
1533
|
-
import { jsx as
|
|
2832
|
+
import { useState as useState11 } from "react";
|
|
2833
|
+
import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1534
2834
|
function ElvixIdentityForm({
|
|
1535
2835
|
initialName = "",
|
|
1536
2836
|
initialBio = "",
|
|
@@ -1544,11 +2844,11 @@ function ElvixIdentityForm({
|
|
|
1544
2844
|
}) {
|
|
1545
2845
|
const sizeProps = { width, height, minWidth, maxWidth, minHeight, maxHeight };
|
|
1546
2846
|
const ctx = useElvixContext();
|
|
1547
|
-
const [name, setName] =
|
|
1548
|
-
const [bio, setBio] =
|
|
1549
|
-
const [busy, setBusy] =
|
|
1550
|
-
const [error, setError] =
|
|
1551
|
-
const [saved, setSaved] =
|
|
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);
|
|
1552
2852
|
async function submit(e) {
|
|
1553
2853
|
e.preventDefault();
|
|
1554
2854
|
if (!ctx.app) return;
|
|
@@ -1564,24 +2864,24 @@ function ElvixIdentityForm({
|
|
|
1564
2864
|
else setSaved(true);
|
|
1565
2865
|
onResult?.(result);
|
|
1566
2866
|
}
|
|
1567
|
-
return /* @__PURE__ */
|
|
1568
|
-
/* @__PURE__ */
|
|
2867
|
+
return /* @__PURE__ */ jsx14(ElvixCard, { title: "Identity", ...sizeProps, children: /* @__PURE__ */ jsxs13("form", { onSubmit: submit, className: "elvix-form", children: [
|
|
2868
|
+
/* @__PURE__ */ jsxs13("label", { children: [
|
|
1569
2869
|
"Name",
|
|
1570
|
-
/* @__PURE__ */
|
|
2870
|
+
/* @__PURE__ */ jsx14("input", { value: name, onChange: (e) => setName(e.target.value), maxLength: 80, disabled: busy, className: "elvix-input" })
|
|
1571
2871
|
] }),
|
|
1572
|
-
/* @__PURE__ */
|
|
2872
|
+
/* @__PURE__ */ jsxs13("label", { children: [
|
|
1573
2873
|
"Bio",
|
|
1574
|
-
/* @__PURE__ */
|
|
2874
|
+
/* @__PURE__ */ jsx14("textarea", { value: bio, onChange: (e) => setBio(e.target.value), maxLength: 500, rows: 3, disabled: busy, className: "elvix-input" })
|
|
1575
2875
|
] }),
|
|
1576
|
-
/* @__PURE__ */
|
|
1577
|
-
saved && /* @__PURE__ */
|
|
1578
|
-
error && /* @__PURE__ */
|
|
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 })
|
|
1579
2879
|
] }) });
|
|
1580
2880
|
}
|
|
1581
2881
|
|
|
1582
2882
|
// src/react/elvix-region.tsx
|
|
1583
|
-
import { useState as
|
|
1584
|
-
import { jsx as
|
|
2883
|
+
import { useState as useState12 } from "react";
|
|
2884
|
+
import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1585
2885
|
function ElvixRegion({
|
|
1586
2886
|
initialCountry = "",
|
|
1587
2887
|
initialTimezone = "",
|
|
@@ -1595,11 +2895,11 @@ function ElvixRegion({
|
|
|
1595
2895
|
}) {
|
|
1596
2896
|
const sizeProps = { width, height, minWidth, maxWidth, minHeight, maxHeight };
|
|
1597
2897
|
const ctx = useElvixContext();
|
|
1598
|
-
const [country, setCountry] =
|
|
1599
|
-
const [timezone, setTimezone] =
|
|
1600
|
-
const [busy, setBusy] =
|
|
1601
|
-
const [error, setError] =
|
|
1602
|
-
const [saved, setSaved] =
|
|
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);
|
|
1603
2903
|
async function submit(e) {
|
|
1604
2904
|
e.preventDefault();
|
|
1605
2905
|
if (!ctx.app) return;
|
|
@@ -1615,24 +2915,24 @@ function ElvixRegion({
|
|
|
1615
2915
|
else setSaved(true);
|
|
1616
2916
|
onResult?.(result);
|
|
1617
2917
|
}
|
|
1618
|
-
return /* @__PURE__ */
|
|
1619
|
-
/* @__PURE__ */
|
|
2918
|
+
return /* @__PURE__ */ jsx15(ElvixCard, { title: "Region", ...sizeProps, children: /* @__PURE__ */ jsxs14("form", { onSubmit: submit, className: "elvix-form", children: [
|
|
2919
|
+
/* @__PURE__ */ jsxs14("label", { children: [
|
|
1620
2920
|
"Country (ISO-2)",
|
|
1621
|
-
/* @__PURE__ */
|
|
2921
|
+
/* @__PURE__ */ jsx15("input", { value: country, onChange: (e) => setCountry(e.target.value.toUpperCase()), maxLength: 2, pattern: "[A-Z]{2}", disabled: busy, className: "elvix-input" })
|
|
1622
2922
|
] }),
|
|
1623
|
-
/* @__PURE__ */
|
|
2923
|
+
/* @__PURE__ */ jsxs14("label", { children: [
|
|
1624
2924
|
"Timezone",
|
|
1625
|
-
/* @__PURE__ */
|
|
2925
|
+
/* @__PURE__ */ jsx15("input", { value: timezone, onChange: (e) => setTimezone(e.target.value), placeholder: "Europe/Berlin", disabled: busy, className: "elvix-input" })
|
|
1626
2926
|
] }),
|
|
1627
|
-
/* @__PURE__ */
|
|
1628
|
-
saved && /* @__PURE__ */
|
|
1629
|
-
error && /* @__PURE__ */
|
|
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 })
|
|
1630
2930
|
] }) });
|
|
1631
2931
|
}
|
|
1632
2932
|
|
|
1633
2933
|
// src/react/elvix-languages.tsx
|
|
1634
|
-
import { useState as
|
|
1635
|
-
import { jsx as
|
|
2934
|
+
import { useState as useState13 } from "react";
|
|
2935
|
+
import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1636
2936
|
function ElvixLanguages({
|
|
1637
2937
|
initial = [],
|
|
1638
2938
|
onResult,
|
|
@@ -1645,10 +2945,10 @@ function ElvixLanguages({
|
|
|
1645
2945
|
}) {
|
|
1646
2946
|
const sizeProps = { width, height, minWidth, maxWidth, minHeight, maxHeight };
|
|
1647
2947
|
const ctx = useElvixContext();
|
|
1648
|
-
const [raw, setRaw] =
|
|
1649
|
-
const [busy, setBusy] =
|
|
1650
|
-
const [error, setError] =
|
|
1651
|
-
const [saved, setSaved] =
|
|
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);
|
|
1652
2952
|
async function submit(e) {
|
|
1653
2953
|
e.preventDefault();
|
|
1654
2954
|
if (!ctx.app) return;
|
|
@@ -1665,20 +2965,20 @@ function ElvixLanguages({
|
|
|
1665
2965
|
else setSaved(true);
|
|
1666
2966
|
onResult?.(result);
|
|
1667
2967
|
}
|
|
1668
|
-
return /* @__PURE__ */
|
|
1669
|
-
/* @__PURE__ */
|
|
2968
|
+
return /* @__PURE__ */ jsx16(ElvixCard, { title: "Languages", ...sizeProps, children: /* @__PURE__ */ jsxs15("form", { onSubmit: submit, className: "elvix-form", children: [
|
|
2969
|
+
/* @__PURE__ */ jsxs15("label", { children: [
|
|
1670
2970
|
"Preferred languages (comma-separated BCP-47 tags)",
|
|
1671
|
-
/* @__PURE__ */
|
|
2971
|
+
/* @__PURE__ */ jsx16("input", { value: raw, onChange: (e) => setRaw(e.target.value), placeholder: "en-GB, de-DE", disabled: busy, className: "elvix-input" })
|
|
1672
2972
|
] }),
|
|
1673
|
-
/* @__PURE__ */
|
|
1674
|
-
saved && /* @__PURE__ */
|
|
1675
|
-
error && /* @__PURE__ */
|
|
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 })
|
|
1676
2976
|
] }) });
|
|
1677
2977
|
}
|
|
1678
2978
|
|
|
1679
2979
|
// src/react/elvix-sessions.tsx
|
|
1680
|
-
import { useEffect as
|
|
1681
|
-
import { jsx as
|
|
2980
|
+
import { useEffect as useEffect7, useState as useState14 } from "react";
|
|
2981
|
+
import { jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
1682
2982
|
function ElvixSessions({
|
|
1683
2983
|
onResult,
|
|
1684
2984
|
width,
|
|
@@ -1690,10 +2990,10 @@ function ElvixSessions({
|
|
|
1690
2990
|
}) {
|
|
1691
2991
|
const sizeProps = { width, height, minWidth, maxWidth, minHeight, maxHeight };
|
|
1692
2992
|
const ctx = useElvixContext();
|
|
1693
|
-
const [rows, setRows] =
|
|
1694
|
-
const [error, setError] =
|
|
1695
|
-
const [busy, setBusy] =
|
|
1696
|
-
|
|
2993
|
+
const [rows, setRows] = useState14(null);
|
|
2994
|
+
const [error, setError] = useState14(null);
|
|
2995
|
+
const [busy, setBusy] = useState14(false);
|
|
2996
|
+
useEffect7(() => {
|
|
1697
2997
|
if (!ctx.app) return;
|
|
1698
2998
|
fetch(`${ctx.baseUrl}/api/account/apps/${ctx.app.applicationId}/sessions`, {
|
|
1699
2999
|
...authInit()
|
|
@@ -1713,29 +3013,29 @@ function ElvixSessions({
|
|
|
1713
3013
|
if (result.ok) setRows((prev) => prev?.filter((s) => s.id !== id) ?? null);
|
|
1714
3014
|
onResult?.(result);
|
|
1715
3015
|
}
|
|
1716
|
-
return /* @__PURE__ */
|
|
1717
|
-
error && /* @__PURE__ */
|
|
1718
|
-
!rows && !error && /* @__PURE__ */
|
|
1719
|
-
rows && /* @__PURE__ */
|
|
1720
|
-
/* @__PURE__ */
|
|
1721
|
-
/* @__PURE__ */
|
|
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: [
|
|
1722
3022
|
s.device,
|
|
1723
|
-
s.current && /* @__PURE__ */
|
|
3023
|
+
s.current && /* @__PURE__ */ jsx17("span", { style: { marginLeft: 8, color: "var(--elvix-primary-strong)", fontSize: 11 }, children: "\xB7 this device" })
|
|
1724
3024
|
] }),
|
|
1725
|
-
/* @__PURE__ */
|
|
3025
|
+
/* @__PURE__ */ jsxs16("div", { style: { fontSize: 11, color: "rgba(0,0,0,0.55)" }, children: [
|
|
1726
3026
|
s.country ?? "\u2014",
|
|
1727
3027
|
" \xB7 since ",
|
|
1728
3028
|
new Date(s.createdAt).toLocaleDateString()
|
|
1729
3029
|
] })
|
|
1730
3030
|
] }),
|
|
1731
|
-
!s.current && /* @__PURE__ */
|
|
3031
|
+
!s.current && /* @__PURE__ */ jsx17("button", { type: "button", disabled: busy, onClick: () => revoke(s.id), className: "elvix-btn elvix-btn-ghost", children: "Revoke" })
|
|
1732
3032
|
] }) }, s.id)) })
|
|
1733
3033
|
] });
|
|
1734
3034
|
}
|
|
1735
3035
|
|
|
1736
3036
|
// src/react/elvix-export.tsx
|
|
1737
|
-
import { useState as
|
|
1738
|
-
import { jsx as
|
|
3037
|
+
import { useState as useState15 } from "react";
|
|
3038
|
+
import { jsx as jsx18, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
1739
3039
|
function ElvixExport({
|
|
1740
3040
|
onResult,
|
|
1741
3041
|
width,
|
|
@@ -1747,9 +3047,9 @@ function ElvixExport({
|
|
|
1747
3047
|
}) {
|
|
1748
3048
|
const sizeProps = { width, height, minWidth, maxWidth, minHeight, maxHeight };
|
|
1749
3049
|
const ctx = useElvixContext();
|
|
1750
|
-
const [busy, setBusy] =
|
|
1751
|
-
const [done, setDone] =
|
|
1752
|
-
const [error, setError] =
|
|
3050
|
+
const [busy, setBusy] = useState15(false);
|
|
3051
|
+
const [done, setDone] = useState15(false);
|
|
3052
|
+
const [error, setError] = useState15(null);
|
|
1753
3053
|
async function start() {
|
|
1754
3054
|
if (!ctx.app) return;
|
|
1755
3055
|
setBusy(true);
|
|
@@ -1764,16 +3064,16 @@ function ElvixExport({
|
|
|
1764
3064
|
else setDone(true);
|
|
1765
3065
|
onResult?.(result);
|
|
1766
3066
|
}
|
|
1767
|
-
return /* @__PURE__ */
|
|
1768
|
-
/* @__PURE__ */
|
|
1769
|
-
done ? /* @__PURE__ */
|
|
1770
|
-
error && /* @__PURE__ */
|
|
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 })
|
|
1771
3071
|
] });
|
|
1772
3072
|
}
|
|
1773
3073
|
|
|
1774
3074
|
// src/react/elvix-deactivate.tsx
|
|
1775
|
-
import { useState as
|
|
1776
|
-
import { Fragment as
|
|
3075
|
+
import { useState as useState16 } from "react";
|
|
3076
|
+
import { Fragment as Fragment5, jsx as jsx19, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
1777
3077
|
function ElvixDeactivate({
|
|
1778
3078
|
onResult,
|
|
1779
3079
|
width,
|
|
@@ -1785,11 +3085,11 @@ function ElvixDeactivate({
|
|
|
1785
3085
|
}) {
|
|
1786
3086
|
const sizeProps = { width, height, minWidth, maxWidth, minHeight, maxHeight };
|
|
1787
3087
|
const ctx = useElvixContext();
|
|
1788
|
-
const [pane, setPane] =
|
|
1789
|
-
const [challengeId, setChallengeId] =
|
|
1790
|
-
const [code, setCode] =
|
|
1791
|
-
const [busy, setBusy] =
|
|
1792
|
-
const [error, setError] =
|
|
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);
|
|
1793
3093
|
async function startChallenge() {
|
|
1794
3094
|
if (!ctx.app) return;
|
|
1795
3095
|
setBusy(true);
|
|
@@ -1827,16 +3127,16 @@ function ElvixDeactivate({
|
|
|
1827
3127
|
onResult?.(result);
|
|
1828
3128
|
}
|
|
1829
3129
|
if (pane === "done") {
|
|
1830
|
-
return /* @__PURE__ */
|
|
3130
|
+
return /* @__PURE__ */ jsx19(ElvixCard, { title: "Deactivated", ...sizeProps, children: /* @__PURE__ */ jsx19("p", { children: "Your access has been paused. Sign in again to restore it." }) });
|
|
1831
3131
|
}
|
|
1832
|
-
return /* @__PURE__ */
|
|
1833
|
-
pane === "warn" && /* @__PURE__ */
|
|
1834
|
-
/* @__PURE__ */
|
|
1835
|
-
/* @__PURE__ */
|
|
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" })
|
|
1836
3136
|
] }),
|
|
1837
|
-
pane === "otp" && /* @__PURE__ */
|
|
1838
|
-
/* @__PURE__ */
|
|
1839
|
-
/* @__PURE__ */
|
|
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(
|
|
1840
3140
|
"input",
|
|
1841
3141
|
{
|
|
1842
3142
|
type: "text",
|
|
@@ -1850,15 +3150,15 @@ function ElvixDeactivate({
|
|
|
1850
3150
|
className: "elvix-input"
|
|
1851
3151
|
}
|
|
1852
3152
|
),
|
|
1853
|
-
/* @__PURE__ */
|
|
3153
|
+
/* @__PURE__ */ jsx19("button", { type: "submit", disabled: busy || code.length !== 6, className: "elvix-btn elvix-btn-danger", children: busy ? "Deactivating\u2026" : "Confirm" })
|
|
1854
3154
|
] }),
|
|
1855
|
-
error && /* @__PURE__ */
|
|
3155
|
+
error && /* @__PURE__ */ jsx19("p", { role: "alert", className: "elvix-error", children: error })
|
|
1856
3156
|
] });
|
|
1857
3157
|
}
|
|
1858
3158
|
|
|
1859
3159
|
// src/react/elvix-leave.tsx
|
|
1860
|
-
import { useState as
|
|
1861
|
-
import { Fragment as
|
|
3160
|
+
import { useState as useState17 } from "react";
|
|
3161
|
+
import { Fragment as Fragment6, jsx as jsx20, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
1862
3162
|
function ElvixLeave({
|
|
1863
3163
|
onResult,
|
|
1864
3164
|
width,
|
|
@@ -1870,11 +3170,11 @@ function ElvixLeave({
|
|
|
1870
3170
|
}) {
|
|
1871
3171
|
const sizeProps = { width, height, minWidth, maxWidth, minHeight, maxHeight };
|
|
1872
3172
|
const ctx = useElvixContext();
|
|
1873
|
-
const [pane, setPane] =
|
|
1874
|
-
const [challengeId, setChallengeId] =
|
|
1875
|
-
const [code, setCode] =
|
|
1876
|
-
const [busy, setBusy] =
|
|
1877
|
-
const [error, setError] =
|
|
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);
|
|
1878
3178
|
async function startChallenge() {
|
|
1879
3179
|
if (!ctx.app) return;
|
|
1880
3180
|
setBusy(true);
|
|
@@ -1912,16 +3212,16 @@ function ElvixLeave({
|
|
|
1912
3212
|
onResult?.(result);
|
|
1913
3213
|
}
|
|
1914
3214
|
if (pane === "done") {
|
|
1915
|
-
return /* @__PURE__ */
|
|
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." }) });
|
|
1916
3216
|
}
|
|
1917
|
-
return /* @__PURE__ */
|
|
1918
|
-
pane === "warn" && /* @__PURE__ */
|
|
1919
|
-
/* @__PURE__ */
|
|
1920
|
-
/* @__PURE__ */
|
|
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" })
|
|
1921
3221
|
] }),
|
|
1922
|
-
pane === "otp" && /* @__PURE__ */
|
|
1923
|
-
/* @__PURE__ */
|
|
1924
|
-
/* @__PURE__ */
|
|
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(
|
|
1925
3225
|
"input",
|
|
1926
3226
|
{
|
|
1927
3227
|
type: "text",
|
|
@@ -1935,15 +3235,15 @@ function ElvixLeave({
|
|
|
1935
3235
|
className: "elvix-input"
|
|
1936
3236
|
}
|
|
1937
3237
|
),
|
|
1938
|
-
/* @__PURE__ */
|
|
3238
|
+
/* @__PURE__ */ jsx20("button", { type: "submit", disabled: busy || code.length !== 6, className: "elvix-btn elvix-btn-danger", children: busy ? "Leaving\u2026" : "Confirm leave" })
|
|
1939
3239
|
] }),
|
|
1940
|
-
error && /* @__PURE__ */
|
|
3240
|
+
error && /* @__PURE__ */ jsx20("p", { role: "alert", className: "elvix-error", children: error })
|
|
1941
3241
|
] });
|
|
1942
3242
|
}
|
|
1943
3243
|
|
|
1944
3244
|
// src/react/elvix-address-book.tsx
|
|
1945
|
-
import { useEffect as
|
|
1946
|
-
import { jsx as
|
|
3245
|
+
import { useEffect as useEffect8, useState as useState18 } from "react";
|
|
3246
|
+
import { jsx as jsx21, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
1947
3247
|
function ElvixAddressBook({
|
|
1948
3248
|
onResult,
|
|
1949
3249
|
width,
|
|
@@ -1955,11 +3255,11 @@ function ElvixAddressBook({
|
|
|
1955
3255
|
}) {
|
|
1956
3256
|
const sizeProps = { width, height, minWidth, maxWidth, minHeight, maxHeight };
|
|
1957
3257
|
const ctx = useElvixContext();
|
|
1958
|
-
const [rows, setRows] =
|
|
1959
|
-
const [error, setError] =
|
|
1960
|
-
const [busy, setBusy] =
|
|
1961
|
-
const [adding, setAdding] =
|
|
1962
|
-
const [form, setForm] =
|
|
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({
|
|
1963
3263
|
label: "Home",
|
|
1964
3264
|
line1: "",
|
|
1965
3265
|
postalCode: "",
|
|
@@ -1975,7 +3275,7 @@ function ElvixAddressBook({
|
|
|
1975
3275
|
else setError("load_failed");
|
|
1976
3276
|
}).catch(() => setError("network"));
|
|
1977
3277
|
}
|
|
1978
|
-
|
|
3278
|
+
useEffect8(() => {
|
|
1979
3279
|
reload();
|
|
1980
3280
|
}, [ctx.app, ctx.baseUrl]);
|
|
1981
3281
|
async function add(e) {
|
|
@@ -2008,14 +3308,14 @@ function ElvixAddressBook({
|
|
|
2008
3308
|
if (result.ok) setRows((prev) => prev?.filter((a) => a.id !== id) ?? null);
|
|
2009
3309
|
onResult?.(result);
|
|
2010
3310
|
}
|
|
2011
|
-
return /* @__PURE__ */
|
|
2012
|
-
error && /* @__PURE__ */
|
|
2013
|
-
!rows && !error && /* @__PURE__ */
|
|
2014
|
-
rows && rows.length === 0 && /* @__PURE__ */
|
|
2015
|
-
rows?.map((a) => /* @__PURE__ */
|
|
2016
|
-
/* @__PURE__ */
|
|
2017
|
-
/* @__PURE__ */
|
|
2018
|
-
/* @__PURE__ */
|
|
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: [
|
|
2019
3319
|
a.line1,
|
|
2020
3320
|
a.line2 ? `, ${a.line2}` : "",
|
|
2021
3321
|
", ",
|
|
@@ -2026,23 +3326,23 @@ function ElvixAddressBook({
|
|
|
2026
3326
|
a.country
|
|
2027
3327
|
] })
|
|
2028
3328
|
] }),
|
|
2029
|
-
/* @__PURE__ */
|
|
3329
|
+
/* @__PURE__ */ jsx21("button", { type: "button", disabled: busy, onClick: () => remove(a.id), className: "elvix-btn elvix-btn-ghost", children: "Remove" })
|
|
2030
3330
|
] }, a.id)),
|
|
2031
|
-
!adding && /* @__PURE__ */
|
|
2032
|
-
adding && /* @__PURE__ */
|
|
2033
|
-
/* @__PURE__ */
|
|
2034
|
-
/* @__PURE__ */
|
|
2035
|
-
/* @__PURE__ */
|
|
2036
|
-
/* @__PURE__ */
|
|
2037
|
-
/* @__PURE__ */
|
|
2038
|
-
/* @__PURE__ */
|
|
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" })
|
|
2039
3339
|
] })
|
|
2040
3340
|
] });
|
|
2041
3341
|
}
|
|
2042
3342
|
|
|
2043
3343
|
// src/react/elvix-legal-entities.tsx
|
|
2044
|
-
import { useEffect as
|
|
2045
|
-
import { jsx as
|
|
3344
|
+
import { useEffect as useEffect9, useState as useState19 } from "react";
|
|
3345
|
+
import { jsx as jsx22, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
2046
3346
|
function ElvixLegalEntities({
|
|
2047
3347
|
onResult,
|
|
2048
3348
|
width,
|
|
@@ -2054,11 +3354,11 @@ function ElvixLegalEntities({
|
|
|
2054
3354
|
}) {
|
|
2055
3355
|
const sizeProps = { width, height, minWidth, maxWidth, minHeight, maxHeight };
|
|
2056
3356
|
const ctx = useElvixContext();
|
|
2057
|
-
const [rows, setRows] =
|
|
2058
|
-
const [error, setError] =
|
|
2059
|
-
const [busy, setBusy] =
|
|
2060
|
-
const [adding, setAdding] =
|
|
2061
|
-
const [form, setForm] =
|
|
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({
|
|
2062
3362
|
legalName: "",
|
|
2063
3363
|
taxId: "",
|
|
2064
3364
|
country: ""
|
|
@@ -2072,7 +3372,7 @@ function ElvixLegalEntities({
|
|
|
2072
3372
|
else setError("load_failed");
|
|
2073
3373
|
}).catch(() => setError("network"));
|
|
2074
3374
|
}
|
|
2075
|
-
|
|
3375
|
+
useEffect9(() => {
|
|
2076
3376
|
reload();
|
|
2077
3377
|
}, [ctx.app, ctx.baseUrl]);
|
|
2078
3378
|
async function add(e) {
|
|
@@ -2105,27 +3405,27 @@ function ElvixLegalEntities({
|
|
|
2105
3405
|
if (result.ok) setRows((prev) => prev?.filter((a) => a.id !== id) ?? null);
|
|
2106
3406
|
onResult?.(result);
|
|
2107
3407
|
}
|
|
2108
|
-
return /* @__PURE__ */
|
|
2109
|
-
error && /* @__PURE__ */
|
|
2110
|
-
!rows && !error && /* @__PURE__ */
|
|
2111
|
-
rows && rows.length === 0 && /* @__PURE__ */
|
|
2112
|
-
rows?.map((e) => /* @__PURE__ */
|
|
2113
|
-
/* @__PURE__ */
|
|
2114
|
-
/* @__PURE__ */
|
|
2115
|
-
/* @__PURE__ */
|
|
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: [
|
|
2116
3416
|
e.taxId,
|
|
2117
3417
|
" \xB7 ",
|
|
2118
3418
|
e.country
|
|
2119
3419
|
] })
|
|
2120
3420
|
] }),
|
|
2121
|
-
/* @__PURE__ */
|
|
3421
|
+
/* @__PURE__ */ jsx22("button", { type: "button", disabled: busy, onClick: () => remove(e.id), className: "elvix-btn elvix-btn-ghost", children: "Remove" })
|
|
2122
3422
|
] }, e.id)),
|
|
2123
|
-
!adding && /* @__PURE__ */
|
|
2124
|
-
adding && /* @__PURE__ */
|
|
2125
|
-
/* @__PURE__ */
|
|
2126
|
-
/* @__PURE__ */
|
|
2127
|
-
/* @__PURE__ */
|
|
2128
|
-
/* @__PURE__ */
|
|
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" })
|
|
2129
3429
|
] })
|
|
2130
3430
|
] });
|
|
2131
3431
|
}
|