cauthy-auth 0.34.0 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/react.mjs CHANGED
@@ -774,6 +774,7 @@ var TEXTS = {
774
774
  savePassword: "Save the password",
775
775
  passwordChanged: "Your password was changed. You can sign in with it now.",
776
776
  confirming: "Confirming your address\u2026",
777
+ loading: "Loading\u2026",
777
778
  emailConfirmed: "Your address is confirmed. You can sign in now.",
778
779
  invitedTo: "You've been invited to {workspace}",
779
780
  invitedToOrganization: "You've been invited to {organization} on {workspace}",
@@ -955,6 +956,7 @@ var TEXTS = {
955
956
  savePassword: "Guardar la contrase\xF1a",
956
957
  passwordChanged: "Tu contrase\xF1a se cambi\xF3. Ya puedes entrar con ella.",
957
958
  confirming: "Confirmando tu correo\u2026",
959
+ loading: "Cargando\u2026",
958
960
  emailConfirmed: "Tu correo qued\xF3 confirmado. Ya puedes entrar.",
959
961
  invitedTo: "Te invitaron a {workspace}",
960
962
  invitedToOrganization: "Te invitaron a {organization} en {workspace}",
@@ -1087,7 +1089,14 @@ var TEXTS = {
1087
1089
  }
1088
1090
  }
1089
1091
  };
1090
- var PlainButton = ({ variant: _variant, loading: _loading, ...p }) => /* @__PURE__ */ jsx("button", { ...p });
1092
+ var PlainButton = ({ variant: _variant, loading, children, ...p }) => /* @__PURE__ */ jsxs("button", { ...p, "aria-busy": loading || void 0, children: [
1093
+ loading && /* @__PURE__ */ jsx("span", { className: "cauthy-spinner", "aria-hidden": "true" }),
1094
+ children
1095
+ ] });
1096
+ var UnstyledButton = ({ variant: _variant, loading, children, ...p }) => /* @__PURE__ */ jsxs("button", { ...p, "aria-busy": loading || void 0, children: [
1097
+ children,
1098
+ loading && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u2026" })
1099
+ ] });
1091
1100
  var PlainInput = (p) => /* @__PURE__ */ jsx("input", { ...p });
1092
1101
  var looks = /* @__PURE__ */ new Map();
1093
1102
  var loaded = /* @__PURE__ */ new Set();
@@ -1195,12 +1204,15 @@ function sameOrigin(to) {
1195
1204
  return void 0;
1196
1205
  }
1197
1206
  }
1198
- function useForm(props) {
1207
+ function isPending(v) {
1208
+ return typeof v?.then === "function";
1209
+ }
1210
+ function useForm(props, opts = {}) {
1199
1211
  const t = { ...TEXTS[props.locale ?? "en"], ...props.texts, errors: { ...TEXTS[props.locale ?? "en"].errors, ...props.texts?.errors } };
1200
1212
  const cx = (slot) => props.classNames?.[slot] ?? `cauthy-${slot}`;
1201
1213
  const own = (slot) => props.classNames?.[slot];
1202
1214
  const C = props.components ?? {};
1203
- const Button = C.Button ?? PlainButton;
1215
+ const Button = C.Button ?? (props.unstyled ? UnstyledButton : PlainButton);
1204
1216
  const Input = C.Input ?? PlainInput;
1205
1217
  const [error, setOnlyError] = useState(null);
1206
1218
  const [problems, setProblems] = useState({ list: [], round: 0 });
@@ -1217,9 +1229,32 @@ function useForm(props) {
1217
1229
  const fieldError = (name) => problems.list.find((e) => e.field === name) ?? (error?.field === name ? error : null);
1218
1230
  const firstWrong = (name) => problems.list[0]?.field === name ? problems.round : 0;
1219
1231
  const [acting, setActing] = useState(null);
1220
- const busy = acting !== null;
1232
+ const busy = acting !== null || opts.waiting === true;
1221
1233
  const setBusy = useCallback((on) => setActing(on === false ? null : on === true ? SUBMIT : on), []);
1222
1234
  const loading = (key = SUBMIT) => acting === key;
1235
+ const shown = useRef(true);
1236
+ useEffect(() => {
1237
+ shown.current = true;
1238
+ const back = (e) => {
1239
+ if (e.persisted) setActing(null);
1240
+ };
1241
+ window.addEventListener("pageshow", back);
1242
+ return () => {
1243
+ shown.current = false;
1244
+ window.removeEventListener("pageshow", back);
1245
+ };
1246
+ }, []);
1247
+ const hold = (after) => {
1248
+ if (!isPending(after)) return;
1249
+ const free = () => {
1250
+ if (shown.current) setActing(null);
1251
+ };
1252
+ after.then(free, free);
1253
+ };
1254
+ const fail = (e) => {
1255
+ setActing(null);
1256
+ setError(e);
1257
+ };
1223
1258
  const [policy, setPolicy] = useState(void 0);
1224
1259
  const locale = props.locale ?? "en";
1225
1260
  const say = (e) => {
@@ -1228,15 +1263,18 @@ function useForm(props) {
1228
1263
  };
1229
1264
  const passwordRule = describePasswordPolicy(policy, locale);
1230
1265
  const done = (to) => {
1231
- if (props.onSuccess) props.onSuccess();
1266
+ setActing((a) => a ?? SUBMIT);
1267
+ if (props.onSuccess) hold(props.onSuccess());
1232
1268
  else window.location.assign(sitePath(to) ?? sameOrigin(props.returnTo) ?? "/");
1233
1269
  };
1234
- const root = useRoot(props);
1235
- return { t, cx, own, C, Button, Input, error, setError, check, fieldError, firstWrong, busy, setBusy, loading, say, done, root, policy, setPolicy, passwordRule };
1270
+ const root = useRoot(opts.part ? { ...props, unstyled: true } : props);
1271
+ const plain = props.unstyled === true;
1272
+ return { t, cx, own, C, Button, Input, error, setError, fail, check, fieldError, firstWrong, busy, setBusy, loading, hold, say, done, root, plain, policy, setPolicy, passwordRule };
1236
1273
  }
1237
1274
  var SUBMIT = "submit";
1238
1275
  function Wrap(p) {
1239
1276
  const { Form: OwnForm } = p.f.C;
1277
+ const busy = p.busy ?? (p.f.busy || void 0);
1240
1278
  if (OwnForm) {
1241
1279
  const own = /* @__PURE__ */ jsx(
1242
1280
  OwnForm,
@@ -1244,7 +1282,7 @@ function Wrap(p) {
1244
1282
  as: p.as === "form" ? "form" : "div",
1245
1283
  onSubmit: p.onSubmit,
1246
1284
  noValidate: p.noValidate,
1247
- "aria-busy": p.busy,
1285
+ "aria-busy": busy,
1248
1286
  className: p.slot ? p.f.own(p.slot) : void 0,
1249
1287
  section: p.section,
1250
1288
  children: p.children
@@ -1253,7 +1291,16 @@ function Wrap(p) {
1253
1291
  return p.root?.["data-cauthy"] !== void 0 ? /* @__PURE__ */ jsx("div", { ...p.root, children: own }) : own;
1254
1292
  }
1255
1293
  const Tag2 = p.as;
1256
- return /* @__PURE__ */ jsx(Tag2, { className: p.slot ? p.f.cx(p.slot) : void 0, ...p.root, onSubmit: p.onSubmit, noValidate: p.noValidate, "aria-busy": p.busy, children: p.children });
1294
+ const children = p.busy && p.children === void 0 ? /* @__PURE__ */ jsx(Pending, { f: p.f }) : p.children;
1295
+ return /* @__PURE__ */ jsx(Tag2, { className: p.slot ? p.f.cx(p.slot) : void 0, ...p.root, onSubmit: p.onSubmit, noValidate: p.noValidate, "aria-busy": busy, children });
1296
+ }
1297
+ function Pending({ f }) {
1298
+ if (f.plain) return /* @__PURE__ */ jsx("p", { role: "status", children: f.t.loading });
1299
+ return /* @__PURE__ */ jsxs("p", { role: "status", className: f.cx("pending"), children: [
1300
+ /* @__PURE__ */ jsx("span", { className: "cauthy-spinner", "aria-hidden": "true" }),
1301
+ " ",
1302
+ f.t.loading
1303
+ ] });
1257
1304
  }
1258
1305
  function Note({ f, children, footer }) {
1259
1306
  const { Text } = f.C;
@@ -1294,10 +1341,10 @@ function Check(p) {
1294
1341
  const { f } = p;
1295
1342
  const { Checkbox } = f.C;
1296
1343
  if (Checkbox) {
1297
- return /* @__PURE__ */ jsx(Checkbox, { id, name: p.name, checked: p.checked, required: p.required, className: f.own("check"), onChange: p.onChange, children: p.children });
1344
+ return /* @__PURE__ */ jsx(Checkbox, { id, name: p.name, checked: p.checked, required: p.required, disabled: f.busy, className: f.own("check"), onChange: p.onChange, children: p.children });
1298
1345
  }
1299
1346
  return /* @__PURE__ */ jsxs("label", { className: f.cx("check"), children: [
1300
- /* @__PURE__ */ jsx("input", { type: "checkbox", name: p.name, checked: p.checked, required: p.required, onChange: (e) => p.onChange(e.target.checked) }),
1347
+ /* @__PURE__ */ jsx("input", { type: "checkbox", name: p.name, checked: p.checked, required: p.required, disabled: f.busy, onChange: (e) => p.onChange(e.target.checked) }),
1301
1348
  /* @__PURE__ */ jsx("span", { children: p.children })
1302
1349
  ] });
1303
1350
  }
@@ -1306,10 +1353,11 @@ function Pick(p) {
1306
1353
  const id = p.id ?? made;
1307
1354
  const { f } = p;
1308
1355
  const { Select } = f.C;
1356
+ const disabled = p.disabled || f.busy;
1309
1357
  if (Select) {
1310
- return /* @__PURE__ */ jsx(Select, { id, name: p.name, value: p.value, options: p.options, disabled: p.disabled, "aria-label": p.label, className: f.own("input"), onChange: p.onChange });
1358
+ return /* @__PURE__ */ jsx(Select, { id, name: p.name, value: p.value, options: p.options, disabled, "aria-label": p.label, className: f.own("input"), onChange: p.onChange });
1311
1359
  }
1312
- return /* @__PURE__ */ jsx("select", { id, name: p.name, "aria-label": p.label, className: f.cx("input"), value: p.value, disabled: p.disabled, onChange: (e) => p.onChange(e.target.value), children: p.options.map((o) => /* @__PURE__ */ jsx("option", { value: o.value, children: o.label }, o.value)) });
1360
+ return /* @__PURE__ */ jsx("select", { id, name: p.name, "aria-label": p.label, className: f.cx("input"), value: p.value, disabled, onChange: (e) => p.onChange(e.target.value), children: p.options.map((o) => /* @__PURE__ */ jsx("option", { value: o.value, children: o.label }, o.value)) });
1313
1361
  }
1314
1362
  function Labelled({ f, id, label, children }) {
1315
1363
  const { Field: OwnField } = f.C;
@@ -1363,6 +1411,7 @@ function Field(p) {
1363
1411
  value: p.value,
1364
1412
  autoComplete: p.autoComplete,
1365
1413
  required: p.required,
1414
+ disabled: f.busy,
1366
1415
  className: f.cx("input"),
1367
1416
  "aria-invalid": bad ? true : void 0,
1368
1417
  "aria-describedby": described || void 0,
@@ -1452,6 +1501,7 @@ function SecondStepForm(p) {
1452
1501
  if (ctl.signal.aborted) return;
1453
1502
  setPushNumber(0);
1454
1503
  if (!done.ok) return f.setError(done.error);
1504
+ f.setBusy("push");
1455
1505
  f.done();
1456
1506
  };
1457
1507
  const sendCode = async () => {
@@ -1467,8 +1517,7 @@ function SecondStepForm(p) {
1467
1517
  f.setBusy(true);
1468
1518
  f.setError(null);
1469
1519
  const r = await auth.signIn.mfa({ code });
1470
- f.setBusy(false);
1471
- if (!r.ok) return f.setError(r.error);
1520
+ if (!r.ok) return f.fail(r.error);
1472
1521
  f.done();
1473
1522
  };
1474
1523
  const general = f.error && !f.error.field ? f.error : null;
@@ -1496,7 +1545,11 @@ function SecondStepForm(p) {
1496
1545
  }
1497
1546
  function ProviderButtons(p) {
1498
1547
  const { f } = p;
1499
- return /* @__PURE__ */ jsx(Fragment2, { children: p.providers.map((provider) => /* @__PURE__ */ jsxs(f.Button, { type: "button", variant: "secondary", className: f.cx("providerButton"), onClick: () => p.onPick(provider.id), children: [
1548
+ const pick = (id) => {
1549
+ f.setBusy(`provider:${id}`);
1550
+ p.onPick(id);
1551
+ };
1552
+ return /* @__PURE__ */ jsx(Fragment2, { children: p.providers.map((provider) => /* @__PURE__ */ jsxs(f.Button, { type: "button", variant: "secondary", loading: f.loading(`provider:${provider.id}`), disabled: f.busy, className: f.cx("providerButton"), onClick: () => pick(provider.id), children: [
1500
1553
  provider.iconUrl && /* @__PURE__ */ jsx("img", { src: provider.iconUrl, alt: "", width: 18, height: 18, className: f.cx("providerIcon") }),
1501
1554
  f.t.continueWith,
1502
1555
  " ",
@@ -1509,8 +1562,8 @@ function signInOffers(methods) {
1509
1562
  }
1510
1563
  function SignIn(props) {
1511
1564
  const auth = useAuth();
1512
- const f = useForm(props);
1513
1565
  const [methods, setMethods] = useState(null);
1566
+ const f = useForm(props, { waiting: methods === null });
1514
1567
  const [email, setEmail] = useState("");
1515
1568
  const [password, setPassword] = useState("");
1516
1569
  const [code, setCode] = useState("");
@@ -1582,14 +1635,16 @@ function SignIn(props) {
1582
1635
  f.setBusy(true);
1583
1636
  f.setError(null);
1584
1637
  const r = step === "sent" ? await auth.signIn.emailCode({ code }) : await auth.signIn.email({ email, password, captchaToken: challenge.token });
1585
- f.setBusy(false);
1586
1638
  if (step === "password") challenge.renew();
1587
1639
  if (!r.ok && r.error.code === "sso_required") return toCompany();
1588
1640
  if (!r.ok) {
1589
1641
  setOffer((n) => n + 1);
1590
- return f.setError(r.error);
1642
+ return f.fail(r.error);
1643
+ }
1644
+ if (r.status === "mfa_required") {
1645
+ f.setBusy(false);
1646
+ return setStep("code");
1591
1647
  }
1592
- if (r.status === "mfa_required") return setStep("code");
1593
1648
  f.done("returnTo" in r && typeof r.returnTo === "string" ? r.returnTo : void 0);
1594
1649
  };
1595
1650
  const usePasskey = async () => {
@@ -1598,10 +1653,10 @@ function SignIn(props) {
1598
1653
  f.setError(null);
1599
1654
  const r = await auth.signIn.passkey({ returnTo: props.returnTo, locale: props.locale, email });
1600
1655
  if (r.ok && r.status === "redirected") return;
1656
+ if (r.ok) return f.done();
1601
1657
  f.setBusy(false);
1602
- if (!r.ok) setOffer((n) => n + 1);
1603
- if (!r.ok) return r.error.code === "cancelled" ? void 0 : f.setError(r.error);
1604
- f.done();
1658
+ setOffer((n) => n + 1);
1659
+ if (r.error.code !== "cancelled") f.setError(r.error);
1605
1660
  };
1606
1661
  const sendLink = async () => {
1607
1662
  autofill.current?.abort();
@@ -1718,10 +1773,12 @@ function SignUp(props) {
1718
1773
  ...legal ? { acceptLegal, consents } : {},
1719
1774
  captchaToken: challenge.token
1720
1775
  });
1721
- f.setBusy(false);
1722
1776
  challenge.renew();
1723
- if (!r.ok) return f.setError(r.error);
1724
- if (r.status === "verification_required") return setSent(true);
1777
+ if (!r.ok) return f.fail(r.error);
1778
+ if (r.status === "verification_required") {
1779
+ f.setBusy(false);
1780
+ return setSent(true);
1781
+ }
1725
1782
  f.done();
1726
1783
  };
1727
1784
  const general = f.error && !f.error.field ? f.error : null;
@@ -1884,13 +1941,16 @@ function AcceptInvite(props) {
1884
1941
  f.setBusy(true);
1885
1942
  f.setError(null);
1886
1943
  const r = await auth.acceptInvitation(token, password);
1887
- f.setBusy(false);
1888
1944
  if (!r.ok) {
1945
+ f.setBusy(false);
1889
1946
  if (r.error.code === "invalid_token") return setState("invalid");
1890
1947
  return f.setError(r.error);
1891
1948
  }
1892
1949
  props.onSaved?.({ kind: "invitation.accepted", email: invitation?.email ?? "", ...invitation?.organization ? { organization: invitation.organization } : {} });
1893
- if (r.status === "mfa_required") return setState("code");
1950
+ if (r.status === "mfa_required") {
1951
+ f.setBusy(false);
1952
+ return setState("code");
1953
+ }
1894
1954
  f.done();
1895
1955
  };
1896
1956
  const general = f.error && !f.error.field ? f.error : null;
@@ -1928,10 +1988,11 @@ function PendingTerms(props) {
1928
1988
  f.setBusy(true);
1929
1989
  f.setError(null);
1930
1990
  const r = await auth.account.acceptLegal({ kinds: owed.map((d) => d.kind) });
1991
+ if (!r.ok) return f.fail(r.error);
1992
+ const after = props.onSuccess?.();
1993
+ if (isPending(after)) await Promise.resolve(after).catch(() => void 0);
1931
1994
  f.setBusy(false);
1932
- if (!r.ok) return f.setError(r.error);
1933
1995
  setOwed([]);
1934
- props.onSuccess?.();
1935
1996
  };
1936
1997
  return /* @__PURE__ */ jsxs(Wrap, { f, as: "div", slot: "form", root: f.root, children: [
1937
1998
  /* @__PURE__ */ jsx(Heading, { f, children: f.t.termsChanged }),
@@ -1946,15 +2007,15 @@ function PendingTerms(props) {
1946
2007
  }
1947
2008
  var ACCOUNT_SECTIONS = ["profile", "email", "password", "linked", "secondStep", "phone", "passkeys", "agents", "sessions"];
1948
2009
  function useSection(props) {
1949
- const f = useForm({ ...props, unstyled: true });
2010
+ const f = useForm(props, { part: true });
1950
2011
  const [done, setDone] = useState(null);
1951
- const run = async (action, key = SUBMIT) => {
2012
+ const run = async (action, key = SUBMIT, leaves = false) => {
1952
2013
  f.setBusy(key);
1953
2014
  f.setError(null);
1954
2015
  setDone(null);
1955
2016
  const r = await action();
1956
- f.setBusy(false);
1957
- if (!r.ok) f.setError(r.error);
2017
+ if (!r.ok) f.fail(r.error);
2018
+ else if (!leaves) f.setBusy(false);
1958
2019
  return r;
1959
2020
  };
1960
2021
  const general = f.error && !f.error.field ? f.error : null;
@@ -2367,11 +2428,26 @@ function LinkedPart({ props }) {
2367
2428
  " ",
2368
2429
  /* @__PURE__ */ jsx(f.Button, { type: "button", variant: "danger", loading: f.loading(`unlink:${x.provider}`), disabled: f.busy, className: f.cx("providerButton"), onClick: () => void unlink(x.provider), children: f.t.unlink })
2369
2430
  ] }, x.provider)) }),
2370
- offered.filter((p) => !linked.some((x) => x.provider === p.id)).map((p) => /* @__PURE__ */ jsxs(f.Button, { type: "button", variant: "secondary", className: f.cx("providerButton"), onClick: () => auth.account.link(p.id, { returnTo: here }), children: [
2371
- f.t.linkWith,
2372
- " ",
2373
- p.name
2374
- ] }, p.id))
2431
+ offered.filter((p) => !linked.some((x) => x.provider === p.id)).map((p) => /* @__PURE__ */ jsxs(
2432
+ f.Button,
2433
+ {
2434
+ type: "button",
2435
+ variant: "secondary",
2436
+ loading: f.loading(`link:${p.id}`),
2437
+ disabled: f.busy,
2438
+ className: f.cx("providerButton"),
2439
+ onClick: () => {
2440
+ f.setBusy(`link:${p.id}`);
2441
+ auth.account.link(p.id, { returnTo: here });
2442
+ },
2443
+ children: [
2444
+ f.t.linkWith,
2445
+ " ",
2446
+ p.name
2447
+ ]
2448
+ },
2449
+ p.id
2450
+ ))
2375
2451
  ] });
2376
2452
  }
2377
2453
  function PasswordPart({ props }) {
@@ -2529,7 +2605,7 @@ function PhonePart({ props, account, onChanged }) {
2529
2605
  !ways.phone && /* @__PURE__ */ jsx(Field, { f, name: "phone", type: "tel", label: f.t.phone, value: number, autoComplete: "tel", required: true, onChange: setNumber }),
2530
2606
  !ways.phone && ways.phoneChannels.length > 1 && f.C.Select && /* @__PURE__ */ jsx(ChannelPick, { f, value: channel, channels: ways.phoneChannels, onChange: setChannel }),
2531
2607
  !ways.phone && ways.phoneChannels.length > 1 && !f.C.Select && /* @__PURE__ */ jsx("div", { className: f.cx("field"), role: "radiogroup", children: ways.phoneChannels.map((c) => /* @__PURE__ */ jsxs("label", { className: f.cx("label"), children: [
2532
- /* @__PURE__ */ jsx("input", { type: "radio", name: "channel", value: c, checked: channel === c, onChange: () => setChannel(c) }),
2608
+ /* @__PURE__ */ jsx("input", { type: "radio", name: "channel", value: c, checked: channel === c, disabled: f.busy, onChange: () => setChannel(c) }),
2533
2609
  " ",
2534
2610
  c === "whatsapp" ? f.t.byWhatsapp : f.t.bySms
2535
2611
  ] }, c)) }),
@@ -2576,10 +2652,10 @@ function SessionsPart({ props, when }) {
2576
2652
  void auth.account.sessions().then(setSessions);
2577
2653
  }, []);
2578
2654
  const end = async () => {
2579
- const r = await run(() => auth.account.signOutEverywhere());
2655
+ const r = await run(() => auth.account.signOutEverywhere(), SUBMIT, true);
2580
2656
  if (!r.ok) return;
2581
2657
  props.onSaved?.({ kind: "sessions.ended" });
2582
- if (props.onSignedOut) props.onSignedOut();
2658
+ if (props.onSignedOut) f.hold(props.onSignedOut());
2583
2659
  else window.location.assign("/");
2584
2660
  };
2585
2661
  return /* @__PURE__ */ jsxs(Wrap, { f, as: "section", slot: "section", section: "sessions", children: [