cauthy-auth 0.33.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}",
@@ -861,6 +862,7 @@ var TEXTS = {
861
862
  last_manager: "Somebody else must manage the members first: give that role to another member.",
862
863
  last_owner: "An organization keeps at least one owner: make another member owner first.",
863
864
  organization_not_found: "That organization no longer exists.",
865
+ member_exists: "That person already belongs to the organization; their role was not changed. Change it in the list instead.",
864
866
  already_member: "That person already belongs to the organization. Change their role instead.",
865
867
  unknown_role: "Choose one of the roles offered.",
866
868
  user_not_active: "That account is disabled.",
@@ -954,6 +956,7 @@ var TEXTS = {
954
956
  savePassword: "Guardar la contrase\xF1a",
955
957
  passwordChanged: "Tu contrase\xF1a se cambi\xF3. Ya puedes entrar con ella.",
956
958
  confirming: "Confirmando tu correo\u2026",
959
+ loading: "Cargando\u2026",
957
960
  emailConfirmed: "Tu correo qued\xF3 confirmado. Ya puedes entrar.",
958
961
  invitedTo: "Te invitaron a {workspace}",
959
962
  invitedToOrganization: "Te invitaron a {organization} en {workspace}",
@@ -1041,6 +1044,7 @@ var TEXTS = {
1041
1044
  last_manager: "Primero alguien m\xE1s debe administrar los miembros: dale ese rol a otro miembro.",
1042
1045
  last_owner: "Una organizaci\xF3n conserva al menos un propietario: primero haz propietario a otro miembro.",
1043
1046
  organization_not_found: "Esa organizaci\xF3n ya no existe.",
1047
+ member_exists: "Esa persona ya pertenece a la organizaci\xF3n; su rol no cambi\xF3. C\xE1mbialo en la lista.",
1044
1048
  already_member: "Esa persona ya pertenece a la organizaci\xF3n. Cambia su rol.",
1045
1049
  unknown_role: "Escoge uno de los roles ofrecidos.",
1046
1050
  user_not_active: "Esa cuenta est\xE1 desactivada.",
@@ -1085,7 +1089,14 @@ var TEXTS = {
1085
1089
  }
1086
1090
  }
1087
1091
  };
1088
- 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
+ ] });
1089
1100
  var PlainInput = (p) => /* @__PURE__ */ jsx("input", { ...p });
1090
1101
  var looks = /* @__PURE__ */ new Map();
1091
1102
  var loaded = /* @__PURE__ */ new Set();
@@ -1193,12 +1204,15 @@ function sameOrigin(to) {
1193
1204
  return void 0;
1194
1205
  }
1195
1206
  }
1196
- function useForm(props) {
1207
+ function isPending(v) {
1208
+ return typeof v?.then === "function";
1209
+ }
1210
+ function useForm(props, opts = {}) {
1197
1211
  const t = { ...TEXTS[props.locale ?? "en"], ...props.texts, errors: { ...TEXTS[props.locale ?? "en"].errors, ...props.texts?.errors } };
1198
1212
  const cx = (slot) => props.classNames?.[slot] ?? `cauthy-${slot}`;
1199
1213
  const own = (slot) => props.classNames?.[slot];
1200
1214
  const C = props.components ?? {};
1201
- const Button = C.Button ?? PlainButton;
1215
+ const Button = C.Button ?? (props.unstyled ? UnstyledButton : PlainButton);
1202
1216
  const Input = C.Input ?? PlainInput;
1203
1217
  const [error, setOnlyError] = useState(null);
1204
1218
  const [problems, setProblems] = useState({ list: [], round: 0 });
@@ -1215,9 +1229,32 @@ function useForm(props) {
1215
1229
  const fieldError = (name) => problems.list.find((e) => e.field === name) ?? (error?.field === name ? error : null);
1216
1230
  const firstWrong = (name) => problems.list[0]?.field === name ? problems.round : 0;
1217
1231
  const [acting, setActing] = useState(null);
1218
- const busy = acting !== null;
1232
+ const busy = acting !== null || opts.waiting === true;
1219
1233
  const setBusy = useCallback((on) => setActing(on === false ? null : on === true ? SUBMIT : on), []);
1220
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
+ };
1221
1258
  const [policy, setPolicy] = useState(void 0);
1222
1259
  const locale = props.locale ?? "en";
1223
1260
  const say = (e) => {
@@ -1226,15 +1263,18 @@ function useForm(props) {
1226
1263
  };
1227
1264
  const passwordRule = describePasswordPolicy(policy, locale);
1228
1265
  const done = (to) => {
1229
- if (props.onSuccess) props.onSuccess();
1266
+ setActing((a) => a ?? SUBMIT);
1267
+ if (props.onSuccess) hold(props.onSuccess());
1230
1268
  else window.location.assign(sitePath(to) ?? sameOrigin(props.returnTo) ?? "/");
1231
1269
  };
1232
- const root = useRoot(props);
1233
- 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 };
1234
1273
  }
1235
1274
  var SUBMIT = "submit";
1236
1275
  function Wrap(p) {
1237
1276
  const { Form: OwnForm } = p.f.C;
1277
+ const busy = p.busy ?? (p.f.busy || void 0);
1238
1278
  if (OwnForm) {
1239
1279
  const own = /* @__PURE__ */ jsx(
1240
1280
  OwnForm,
@@ -1242,7 +1282,7 @@ function Wrap(p) {
1242
1282
  as: p.as === "form" ? "form" : "div",
1243
1283
  onSubmit: p.onSubmit,
1244
1284
  noValidate: p.noValidate,
1245
- "aria-busy": p.busy,
1285
+ "aria-busy": busy,
1246
1286
  className: p.slot ? p.f.own(p.slot) : void 0,
1247
1287
  section: p.section,
1248
1288
  children: p.children
@@ -1251,7 +1291,16 @@ function Wrap(p) {
1251
1291
  return p.root?.["data-cauthy"] !== void 0 ? /* @__PURE__ */ jsx("div", { ...p.root, children: own }) : own;
1252
1292
  }
1253
1293
  const Tag2 = p.as;
1254
- 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
+ ] });
1255
1304
  }
1256
1305
  function Note({ f, children, footer }) {
1257
1306
  const { Text } = f.C;
@@ -1292,10 +1341,10 @@ function Check(p) {
1292
1341
  const { f } = p;
1293
1342
  const { Checkbox } = f.C;
1294
1343
  if (Checkbox) {
1295
- 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 });
1296
1345
  }
1297
1346
  return /* @__PURE__ */ jsxs("label", { className: f.cx("check"), children: [
1298
- /* @__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) }),
1299
1348
  /* @__PURE__ */ jsx("span", { children: p.children })
1300
1349
  ] });
1301
1350
  }
@@ -1304,10 +1353,11 @@ function Pick(p) {
1304
1353
  const id = p.id ?? made;
1305
1354
  const { f } = p;
1306
1355
  const { Select } = f.C;
1356
+ const disabled = p.disabled || f.busy;
1307
1357
  if (Select) {
1308
- 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 });
1309
1359
  }
1310
- 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)) });
1311
1361
  }
1312
1362
  function Labelled({ f, id, label, children }) {
1313
1363
  const { Field: OwnField } = f.C;
@@ -1361,6 +1411,7 @@ function Field(p) {
1361
1411
  value: p.value,
1362
1412
  autoComplete: p.autoComplete,
1363
1413
  required: p.required,
1414
+ disabled: f.busy,
1364
1415
  className: f.cx("input"),
1365
1416
  "aria-invalid": bad ? true : void 0,
1366
1417
  "aria-describedby": described || void 0,
@@ -1450,6 +1501,7 @@ function SecondStepForm(p) {
1450
1501
  if (ctl.signal.aborted) return;
1451
1502
  setPushNumber(0);
1452
1503
  if (!done.ok) return f.setError(done.error);
1504
+ f.setBusy("push");
1453
1505
  f.done();
1454
1506
  };
1455
1507
  const sendCode = async () => {
@@ -1465,8 +1517,7 @@ function SecondStepForm(p) {
1465
1517
  f.setBusy(true);
1466
1518
  f.setError(null);
1467
1519
  const r = await auth.signIn.mfa({ code });
1468
- f.setBusy(false);
1469
- if (!r.ok) return f.setError(r.error);
1520
+ if (!r.ok) return f.fail(r.error);
1470
1521
  f.done();
1471
1522
  };
1472
1523
  const general = f.error && !f.error.field ? f.error : null;
@@ -1494,7 +1545,11 @@ function SecondStepForm(p) {
1494
1545
  }
1495
1546
  function ProviderButtons(p) {
1496
1547
  const { f } = p;
1497
- 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: [
1498
1553
  provider.iconUrl && /* @__PURE__ */ jsx("img", { src: provider.iconUrl, alt: "", width: 18, height: 18, className: f.cx("providerIcon") }),
1499
1554
  f.t.continueWith,
1500
1555
  " ",
@@ -1507,8 +1562,8 @@ function signInOffers(methods) {
1507
1562
  }
1508
1563
  function SignIn(props) {
1509
1564
  const auth = useAuth();
1510
- const f = useForm(props);
1511
1565
  const [methods, setMethods] = useState(null);
1566
+ const f = useForm(props, { waiting: methods === null });
1512
1567
  const [email, setEmail] = useState("");
1513
1568
  const [password, setPassword] = useState("");
1514
1569
  const [code, setCode] = useState("");
@@ -1580,14 +1635,16 @@ function SignIn(props) {
1580
1635
  f.setBusy(true);
1581
1636
  f.setError(null);
1582
1637
  const r = step === "sent" ? await auth.signIn.emailCode({ code }) : await auth.signIn.email({ email, password, captchaToken: challenge.token });
1583
- f.setBusy(false);
1584
1638
  if (step === "password") challenge.renew();
1585
1639
  if (!r.ok && r.error.code === "sso_required") return toCompany();
1586
1640
  if (!r.ok) {
1587
1641
  setOffer((n) => n + 1);
1588
- 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");
1589
1647
  }
1590
- if (r.status === "mfa_required") return setStep("code");
1591
1648
  f.done("returnTo" in r && typeof r.returnTo === "string" ? r.returnTo : void 0);
1592
1649
  };
1593
1650
  const usePasskey = async () => {
@@ -1596,10 +1653,10 @@ function SignIn(props) {
1596
1653
  f.setError(null);
1597
1654
  const r = await auth.signIn.passkey({ returnTo: props.returnTo, locale: props.locale, email });
1598
1655
  if (r.ok && r.status === "redirected") return;
1656
+ if (r.ok) return f.done();
1599
1657
  f.setBusy(false);
1600
- if (!r.ok) setOffer((n) => n + 1);
1601
- if (!r.ok) return r.error.code === "cancelled" ? void 0 : f.setError(r.error);
1602
- f.done();
1658
+ setOffer((n) => n + 1);
1659
+ if (r.error.code !== "cancelled") f.setError(r.error);
1603
1660
  };
1604
1661
  const sendLink = async () => {
1605
1662
  autofill.current?.abort();
@@ -1716,10 +1773,12 @@ function SignUp(props) {
1716
1773
  ...legal ? { acceptLegal, consents } : {},
1717
1774
  captchaToken: challenge.token
1718
1775
  });
1719
- f.setBusy(false);
1720
1776
  challenge.renew();
1721
- if (!r.ok) return f.setError(r.error);
1722
- 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
+ }
1723
1782
  f.done();
1724
1783
  };
1725
1784
  const general = f.error && !f.error.field ? f.error : null;
@@ -1882,13 +1941,16 @@ function AcceptInvite(props) {
1882
1941
  f.setBusy(true);
1883
1942
  f.setError(null);
1884
1943
  const r = await auth.acceptInvitation(token, password);
1885
- f.setBusy(false);
1886
1944
  if (!r.ok) {
1945
+ f.setBusy(false);
1887
1946
  if (r.error.code === "invalid_token") return setState("invalid");
1888
1947
  return f.setError(r.error);
1889
1948
  }
1890
1949
  props.onSaved?.({ kind: "invitation.accepted", email: invitation?.email ?? "", ...invitation?.organization ? { organization: invitation.organization } : {} });
1891
- if (r.status === "mfa_required") return setState("code");
1950
+ if (r.status === "mfa_required") {
1951
+ f.setBusy(false);
1952
+ return setState("code");
1953
+ }
1892
1954
  f.done();
1893
1955
  };
1894
1956
  const general = f.error && !f.error.field ? f.error : null;
@@ -1926,10 +1988,11 @@ function PendingTerms(props) {
1926
1988
  f.setBusy(true);
1927
1989
  f.setError(null);
1928
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);
1929
1994
  f.setBusy(false);
1930
- if (!r.ok) return f.setError(r.error);
1931
1995
  setOwed([]);
1932
- props.onSuccess?.();
1933
1996
  };
1934
1997
  return /* @__PURE__ */ jsxs(Wrap, { f, as: "div", slot: "form", root: f.root, children: [
1935
1998
  /* @__PURE__ */ jsx(Heading, { f, children: f.t.termsChanged }),
@@ -1944,15 +2007,15 @@ function PendingTerms(props) {
1944
2007
  }
1945
2008
  var ACCOUNT_SECTIONS = ["profile", "email", "password", "linked", "secondStep", "phone", "passkeys", "agents", "sessions"];
1946
2009
  function useSection(props) {
1947
- const f = useForm({ ...props, unstyled: true });
2010
+ const f = useForm(props, { part: true });
1948
2011
  const [done, setDone] = useState(null);
1949
- const run = async (action, key = SUBMIT) => {
2012
+ const run = async (action, key = SUBMIT, leaves = false) => {
1950
2013
  f.setBusy(key);
1951
2014
  f.setError(null);
1952
2015
  setDone(null);
1953
2016
  const r = await action();
1954
- f.setBusy(false);
1955
- if (!r.ok) f.setError(r.error);
2017
+ if (!r.ok) f.fail(r.error);
2018
+ else if (!leaves) f.setBusy(false);
1956
2019
  return r;
1957
2020
  };
1958
2021
  const general = f.error && !f.error.field ? f.error : null;
@@ -2365,11 +2428,26 @@ function LinkedPart({ props }) {
2365
2428
  " ",
2366
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 })
2367
2430
  ] }, x.provider)) }),
2368
- 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: [
2369
- f.t.linkWith,
2370
- " ",
2371
- p.name
2372
- ] }, 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
+ ))
2373
2451
  ] });
2374
2452
  }
2375
2453
  function PasswordPart({ props }) {
@@ -2527,7 +2605,7 @@ function PhonePart({ props, account, onChanged }) {
2527
2605
  !ways.phone && /* @__PURE__ */ jsx(Field, { f, name: "phone", type: "tel", label: f.t.phone, value: number, autoComplete: "tel", required: true, onChange: setNumber }),
2528
2606
  !ways.phone && ways.phoneChannels.length > 1 && f.C.Select && /* @__PURE__ */ jsx(ChannelPick, { f, value: channel, channels: ways.phoneChannels, onChange: setChannel }),
2529
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: [
2530
- /* @__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) }),
2531
2609
  " ",
2532
2610
  c === "whatsapp" ? f.t.byWhatsapp : f.t.bySms
2533
2611
  ] }, c)) }),
@@ -2574,10 +2652,10 @@ function SessionsPart({ props, when }) {
2574
2652
  void auth.account.sessions().then(setSessions);
2575
2653
  }, []);
2576
2654
  const end = async () => {
2577
- const r = await run(() => auth.account.signOutEverywhere());
2655
+ const r = await run(() => auth.account.signOutEverywhere(), SUBMIT, true);
2578
2656
  if (!r.ok) return;
2579
2657
  props.onSaved?.({ kind: "sessions.ended" });
2580
- if (props.onSignedOut) props.onSignedOut();
2658
+ if (props.onSignedOut) f.hold(props.onSignedOut());
2581
2659
  else window.location.assign("/");
2582
2660
  };
2583
2661
  return /* @__PURE__ */ jsxs(Wrap, { f, as: "section", slot: "section", section: "sessions", children: [