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