cauthy-auth 0.32.0 → 0.33.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
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  // src/react.tsx
4
- import { createContext, useCallback, useContext, useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState2 } from "react";
4
+ import { createContext, useCallback as useCallback2, useContext, useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState2 } from "react";
5
5
 
6
6
  // src/passkey.ts
7
7
  function canUsePasskeys() {
@@ -721,7 +721,7 @@ function definePolicy(given) {
721
721
  }
722
722
 
723
723
  // src/components.tsx
724
- import { Fragment, useEffect, useId, useRef, useState } from "react";
724
+ import { Fragment, useCallback, useEffect, useId, useRef, useState } from "react";
725
725
  import { Fragment as Fragment2, jsx, jsxs } from "react/jsx-runtime";
726
726
  var TEXTS = {
727
727
  en: {
@@ -1085,7 +1085,7 @@ var TEXTS = {
1085
1085
  }
1086
1086
  }
1087
1087
  };
1088
- var PlainButton = (p) => /* @__PURE__ */ jsx("button", { ...p });
1088
+ var PlainButton = ({ variant: _variant, loading: _loading, ...p }) => /* @__PURE__ */ jsx("button", { ...p });
1089
1089
  var PlainInput = (p) => /* @__PURE__ */ jsx("input", { ...p });
1090
1090
  var looks = /* @__PURE__ */ new Map();
1091
1091
  var loaded = /* @__PURE__ */ new Set();
@@ -1200,8 +1200,24 @@ function useForm(props) {
1200
1200
  const C = props.components ?? {};
1201
1201
  const Button = C.Button ?? PlainButton;
1202
1202
  const Input = C.Input ?? PlainInput;
1203
- const [error, setError] = useState(null);
1204
- const [busy, setBusy] = useState(false);
1203
+ const [error, setOnlyError] = useState(null);
1204
+ const [problems, setProblems] = useState({ list: [], round: 0 });
1205
+ const setError = useCallback((e) => {
1206
+ setOnlyError(e);
1207
+ setProblems((p) => p.list.length === 0 ? p : { list: [], round: p.round });
1208
+ }, []);
1209
+ const check = (...found) => {
1210
+ const list = found.filter((e) => Boolean(e));
1211
+ setOnlyError(null);
1212
+ setProblems((p) => ({ list, round: list.length > 0 ? p.round + 1 : p.round }));
1213
+ return list.length === 0;
1214
+ };
1215
+ const fieldError = (name) => problems.list.find((e) => e.field === name) ?? (error?.field === name ? error : null);
1216
+ const firstWrong = (name) => problems.list[0]?.field === name ? problems.round : 0;
1217
+ const [acting, setActing] = useState(null);
1218
+ const busy = acting !== null;
1219
+ const setBusy = useCallback((on) => setActing(on === false ? null : on === true ? SUBMIT : on), []);
1220
+ const loading = (key = SUBMIT) => acting === key;
1205
1221
  const [policy, setPolicy] = useState(void 0);
1206
1222
  const locale = props.locale ?? "en";
1207
1223
  const say = (e) => {
@@ -1214,7 +1230,28 @@ function useForm(props) {
1214
1230
  else window.location.assign(sitePath(to) ?? sameOrigin(props.returnTo) ?? "/");
1215
1231
  };
1216
1232
  const root = useRoot(props);
1217
- return { t, cx, own, C, Button, Input, error, setError, busy, setBusy, say, done, root, policy, setPolicy, passwordRule };
1233
+ return { t, cx, own, C, Button, Input, error, setError, check, fieldError, firstWrong, busy, setBusy, loading, say, done, root, policy, setPolicy, passwordRule };
1234
+ }
1235
+ var SUBMIT = "submit";
1236
+ function Wrap(p) {
1237
+ const { Form: OwnForm } = p.f.C;
1238
+ if (OwnForm) {
1239
+ const own = /* @__PURE__ */ jsx(
1240
+ OwnForm,
1241
+ {
1242
+ as: p.as === "form" ? "form" : "div",
1243
+ onSubmit: p.onSubmit,
1244
+ noValidate: p.noValidate,
1245
+ "aria-busy": p.busy,
1246
+ className: p.slot ? p.f.own(p.slot) : void 0,
1247
+ section: p.section,
1248
+ children: p.children
1249
+ }
1250
+ );
1251
+ return p.root?.["data-cauthy"] !== void 0 ? /* @__PURE__ */ jsx("div", { ...p.root, children: own }) : own;
1252
+ }
1253
+ 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 });
1218
1255
  }
1219
1256
  function Note({ f, children, footer }) {
1220
1257
  const { Text } = f.C;
@@ -1295,6 +1332,11 @@ function isPasswordRule(e) {
1295
1332
  if (e.code === "weak_password" || PASSWORD_RULES.includes(e.code)) return true;
1296
1333
  return (e.code === "too_short" || e.code === "too_long") && (e.field === "password" || e.field === "newPassword");
1297
1334
  }
1335
+ function newPasswordProblem(value, policy, field = "password") {
1336
+ const bad = checkNewPassword(value, policy);
1337
+ if (!bad) return null;
1338
+ return { code: bad.code, message: bad.message, field, ...bad.code === "required" ? {} : { reasons: passwordProblems(value, policy) } };
1339
+ }
1298
1340
  function usePasswordPolicy(f, methods) {
1299
1341
  const { setPolicy } = f;
1300
1342
  useEffect(() => {
@@ -1304,7 +1346,11 @@ function usePasswordPolicy(f, methods) {
1304
1346
  function Field(p) {
1305
1347
  const id = useId();
1306
1348
  const { f } = p;
1307
- const bad = f.error?.field === p.name ? f.error : null;
1349
+ const bad = f.fieldError(p.name);
1350
+ const focus = f.firstWrong(p.name);
1351
+ useEffect(() => {
1352
+ if (focus > 0 && typeof document !== "undefined") document.getElementById(id)?.focus();
1353
+ }, [focus, id]);
1308
1354
  const described = [bad ? `${id}-error` : "", p.hint ? `${id}-hint` : ""].filter(Boolean).join(" ");
1309
1355
  const input = /* @__PURE__ */ jsx(
1310
1356
  f.Input,
@@ -1380,7 +1426,7 @@ function SecondStepForm(p) {
1380
1426
  });
1381
1427
  }, []);
1382
1428
  const sendEmail = async () => {
1383
- f.setBusy(true);
1429
+ f.setBusy("email");
1384
1430
  f.setError(null);
1385
1431
  const r = await auth.signIn.mfaSendEmail();
1386
1432
  f.setBusy(false);
@@ -1391,7 +1437,7 @@ function SecondStepForm(p) {
1391
1437
  const pushWait = useRef(null);
1392
1438
  useEffect(() => () => pushWait.current?.abort(), []);
1393
1439
  const sendPush = async () => {
1394
- f.setBusy(true);
1440
+ f.setBusy("push");
1395
1441
  f.setError(null);
1396
1442
  const r = await auth.signIn.mfaPush();
1397
1443
  f.setBusy(false);
@@ -1407,7 +1453,7 @@ function SecondStepForm(p) {
1407
1453
  f.done();
1408
1454
  };
1409
1455
  const sendCode = async () => {
1410
- f.setBusy(true);
1456
+ f.setBusy("phone");
1411
1457
  f.setError(null);
1412
1458
  const r = await auth.signIn.mfaSendCode();
1413
1459
  f.setBusy(false);
@@ -1425,10 +1471,10 @@ function SecondStepForm(p) {
1425
1471
  };
1426
1472
  const general = f.error && !f.error.field ? f.error : null;
1427
1473
  const to = phoneWay?.phone?.number ?? "";
1428
- return /* @__PURE__ */ jsxs("form", { className: f.cx("form"), ...f.root, onSubmit: submit, noValidate: true, children: [
1474
+ return /* @__PURE__ */ jsxs(Wrap, { f, as: "form", slot: "form", root: f.root, onSubmit: submit, noValidate: true, children: [
1429
1475
  /* @__PURE__ */ jsx(Note, { f, children: phoneSent ? f.t.codeSent.replace("{to}", phoneSent) : phoneWay?.email ? f.t.codeHelpEmail.replace("{to}", phoneWay.email.to) : phoneWay?.phone && !phoneWay.totp ? f.t.codeHelpPhone : f.t.codeHelp }),
1430
1476
  general && /* @__PURE__ */ jsx(Say, { f, tone: "danger", children: f.say(general) }),
1431
- phoneWay?.email && /* @__PURE__ */ jsx(f.Button, { type: "button", disabled: f.busy, className: f.cx("providerButton"), onClick: () => void sendEmail(), children: f.t.sendEmailAgain }),
1477
+ phoneWay?.email && /* @__PURE__ */ jsx(f.Button, { type: "button", variant: "secondary", loading: f.loading("email"), disabled: f.busy, className: f.cx("providerButton"), onClick: () => void sendEmail(), children: f.t.sendEmailAgain }),
1432
1478
  pushNumber > 0 ? f.C.Text ? (
1433
1479
  // The application's text: the number to pick drawn large, by its own rules.
1434
1480
  /* @__PURE__ */ jsxs("div", { role: "status", "aria-live": "polite", children: [
@@ -1440,15 +1486,15 @@ function SecondStepForm(p) {
1440
1486
  /* @__PURE__ */ jsx("p", { children: f.t.pushPick.replace("{n}", String(pushNumber)) }),
1441
1487
  /* @__PURE__ */ jsx("p", { style: { fontSize: 48, fontWeight: 700, margin: "4px 0", textAlign: "center" }, children: pushNumber }),
1442
1488
  /* @__PURE__ */ jsx("p", { children: f.t.pushWaiting })
1443
- ] }) : phoneWay?.push && /* @__PURE__ */ jsx(f.Button, { type: "button", disabled: f.busy, className: f.cx("button"), onClick: () => void sendPush(), children: f.t.pushSend }),
1444
- phoneWay?.phone && /* @__PURE__ */ jsx(f.Button, { type: "button", disabled: f.busy, className: f.cx("providerButton"), onClick: () => void sendCode(), children: (phoneWay.phone.channel === "whatsapp" ? f.t.sendCodeWhatsapp : f.t.sendCodeSms).replace("{to}", to) }),
1489
+ ] }) : phoneWay?.push && /* @__PURE__ */ jsx(f.Button, { type: "button", variant: "secondary", loading: f.loading("push"), disabled: f.busy, className: f.cx("button"), onClick: () => void sendPush(), children: f.t.pushSend }),
1490
+ phoneWay?.phone && /* @__PURE__ */ jsx(f.Button, { type: "button", variant: "secondary", loading: f.loading("phone"), disabled: f.busy, className: f.cx("providerButton"), onClick: () => void sendCode(), children: (phoneWay.phone.channel === "whatsapp" ? f.t.sendCodeWhatsapp : f.t.sendCodeSms).replace("{to}", to) }),
1445
1491
  /* @__PURE__ */ jsx(Field, { f, name: "code", type: "text", label: f.t.code, value: code, autoComplete: "one-time-code", required: true, onChange: setCode }),
1446
- /* @__PURE__ */ jsx(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.verify })
1492
+ /* @__PURE__ */ jsx(f.Button, { type: "submit", variant: "primary", loading: f.loading(), disabled: f.busy, className: f.cx("button"), children: f.t.verify })
1447
1493
  ] });
1448
1494
  }
1449
1495
  function ProviderButtons(p) {
1450
1496
  const { f } = p;
1451
- return /* @__PURE__ */ jsx(Fragment2, { children: p.providers.map((provider) => /* @__PURE__ */ jsxs(f.Button, { type: "button", className: f.cx("providerButton"), onClick: () => p.onPick(provider.id), children: [
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: [
1452
1498
  provider.iconUrl && /* @__PURE__ */ jsx("img", { src: provider.iconUrl, alt: "", width: 18, height: 18, className: f.cx("providerIcon") }),
1453
1499
  f.t.continueWith,
1454
1500
  " ",
@@ -1488,7 +1534,7 @@ function SignIn(props) {
1488
1534
  }, [email, methods]);
1489
1535
  const toCompany = () => {
1490
1536
  autofill.current?.abort();
1491
- f.setBusy(true);
1537
+ f.setBusy("sso");
1492
1538
  auth.signIn.sso({ email }, { returnTo: props.returnTo });
1493
1539
  };
1494
1540
  useEffect(() => {
@@ -1530,6 +1576,7 @@ function SignIn(props) {
1530
1576
  if (methods?.magicLink) return sendLink();
1531
1577
  return;
1532
1578
  }
1579
+ if (step === "password" && !f.check(checkEmail(email), checkPassword(password))) return;
1533
1580
  f.setBusy(true);
1534
1581
  f.setError(null);
1535
1582
  const r = step === "sent" ? await auth.signIn.emailCode({ code }) : await auth.signIn.email({ email, password, captchaToken: challenge.token });
@@ -1545,7 +1592,7 @@ function SignIn(props) {
1545
1592
  };
1546
1593
  const usePasskey = async () => {
1547
1594
  autofill.current?.abort();
1548
- f.setBusy(true);
1595
+ f.setBusy("passkey");
1549
1596
  f.setError(null);
1550
1597
  const r = await auth.signIn.passkey({ returnTo: props.returnTo, locale: props.locale, email });
1551
1598
  if (r.ok && r.status === "redirected") return;
@@ -1556,7 +1603,7 @@ function SignIn(props) {
1556
1603
  };
1557
1604
  const sendLink = async () => {
1558
1605
  autofill.current?.abort();
1559
- f.setBusy(true);
1606
+ f.setBusy("link");
1560
1607
  f.setError(null);
1561
1608
  const r = await auth.signIn.magicLink({ email, captchaToken: challenge.token, create: props.createAccounts, returnTo: props.returnTo });
1562
1609
  f.setBusy(false);
@@ -1567,24 +1614,24 @@ function SignIn(props) {
1567
1614
  };
1568
1615
  const general = f.error && !f.error.field ? f.error : null;
1569
1616
  const asksEmail = offers.email;
1570
- if (step === "opening") return /* @__PURE__ */ jsx("div", { className: f.cx("form"), ...f.root, "aria-busy": "true" });
1617
+ if (step === "opening") return /* @__PURE__ */ jsx(Wrap, { f, as: "div", slot: "form", root: f.root, busy: true });
1571
1618
  if (step === "sent") {
1572
- return /* @__PURE__ */ jsxs("form", { className: f.cx("form"), ...f.root, onSubmit: submit, noValidate: true, children: [
1619
+ return /* @__PURE__ */ jsxs(Wrap, { f, as: "form", slot: "form", root: f.root, onSubmit: submit, noValidate: true, children: [
1573
1620
  /* @__PURE__ */ jsx(Note, { f, children: f.t.signInLinkSent }),
1574
1621
  general && /* @__PURE__ */ jsx(Say, { f, tone: "danger", children: f.say(general) }),
1575
1622
  /* @__PURE__ */ jsx(Note, { f, children: f.t.codeFromMail }),
1576
1623
  /* @__PURE__ */ jsx(Field, { f, name: "code", type: "text", label: f.t.code, value: code, autoComplete: "one-time-code", required: true, onChange: setCode }),
1577
- /* @__PURE__ */ jsx(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.verify })
1624
+ /* @__PURE__ */ jsx(f.Button, { type: "submit", variant: "primary", loading: f.loading(), disabled: f.busy, className: f.cx("button"), children: f.t.verify })
1578
1625
  ] });
1579
1626
  }
1580
1627
  if (step === "code") return /* @__PURE__ */ jsx(SecondStepForm, { f, auth });
1581
- return /* @__PURE__ */ jsxs("form", { className: f.cx("form"), ...f.root, onSubmit: submit, noValidate: true, children: [
1628
+ return /* @__PURE__ */ jsxs(Wrap, { f, as: "form", slot: "form", root: f.root, onSubmit: submit, noValidate: true, children: [
1582
1629
  general && /* @__PURE__ */ jsx(Say, { f, tone: "danger", children: f.say(general) }),
1583
1630
  /* @__PURE__ */ jsx(ProviderButtons, { f, providers: methods?.providers ?? [], onPick: (id) => auth.signIn.social(id, { returnTo: props.returnTo }) }),
1584
- offers.passkey && /* @__PURE__ */ jsx(f.Button, { type: "button", disabled: f.busy, className: f.cx("providerButton"), onClick: () => void usePasskey(), children: f.t.usePasskey }),
1631
+ offers.passkey && /* @__PURE__ */ jsx(f.Button, { type: "button", variant: "secondary", loading: f.loading("passkey"), disabled: f.busy, className: f.cx("providerButton"), onClick: () => void usePasskey(), children: f.t.usePasskey }),
1585
1632
  asksEmail && ((methods?.providers.length ?? 0) > 0 || offers.passkey) && /* @__PURE__ */ jsx(Or, { f, children: f.t.orWithEmail }),
1586
1633
  asksEmail && /* @__PURE__ */ jsx(Field, { f, name: "email", type: "email", label: f.t.email, value: email, autoComplete: methods?.passkeyHere ? "username webauthn" : "username", required: true, onChange: setEmail }),
1587
- company && /* @__PURE__ */ jsxs(f.Button, { type: "button", disabled: f.busy, className: f.cx(company.enforced ? "button" : "providerButton"), onClick: toCompany, children: [
1634
+ company && /* @__PURE__ */ jsxs(f.Button, { type: "button", variant: company.enforced ? "primary" : "secondary", loading: f.loading("sso"), disabled: f.busy, className: f.cx(company.enforced ? "button" : "providerButton"), onClick: toCompany, children: [
1588
1635
  f.t.continueWith,
1589
1636
  " ",
1590
1637
  company.name
@@ -1606,10 +1653,10 @@ function SignIn(props) {
1606
1653
  }
1607
1654
  ),
1608
1655
  challenge.node,
1609
- /* @__PURE__ */ jsx(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.signIn })
1656
+ /* @__PURE__ */ jsx(f.Button, { type: "submit", variant: "primary", loading: f.loading(), disabled: f.busy, className: f.cx("button"), children: f.t.signIn })
1610
1657
  ] }),
1611
1658
  !company?.enforced && !passwordOn && methods?.magicLink && challenge.node,
1612
- methods?.magicLink && !company?.enforced && /* @__PURE__ */ jsx(f.Button, { type: "button", disabled: f.busy, className: f.cx("providerButton"), onClick: () => void sendLink(), children: f.t.emailMeALink }),
1659
+ methods?.magicLink && !company?.enforced && /* @__PURE__ */ jsx(f.Button, { type: "button", variant: "secondary", loading: f.loading("link"), disabled: f.busy, className: f.cx("providerButton"), onClick: () => void sendLink(), children: f.t.emailMeALink }),
1613
1660
  methods?.signUp !== false && props.links?.signUp && /* @__PURE__ */ jsxs(Note, { f, footer: true, children: [
1614
1661
  f.t.noAccount,
1615
1662
  " ",
@@ -1649,11 +1696,19 @@ function SignUp(props) {
1649
1696
  const [acceptLegal, setAcceptLegal] = useState(false);
1650
1697
  const [consents, setConsents] = useState({});
1651
1698
  const legal = methods?.legal;
1699
+ const guest = auth.session?.user.isAnonymous === true;
1652
1700
  const submit = async (e) => {
1653
1701
  e.preventDefault();
1702
+ const asksCompany = methods?.organization?.ask === true;
1703
+ if (!f.check(
1704
+ asksCompany && checkOrganization(organization, methods?.organization?.required === true),
1705
+ checkName(name),
1706
+ checkEmail(email),
1707
+ newPasswordProblem(password, f.policy)
1708
+ )) return;
1654
1709
  f.setBusy(true);
1655
1710
  f.setError(null);
1656
- const r = auth.session?.user.isAnonymous === true ? await auth.account.upgrade({ name, email, password }) : await auth.signUp.email({
1711
+ const r = guest ? await auth.account.upgrade({ name, email, password }) : await auth.signUp.email({
1657
1712
  name,
1658
1713
  email,
1659
1714
  password,
@@ -1669,30 +1724,31 @@ function SignUp(props) {
1669
1724
  };
1670
1725
  const general = f.error && !f.error.field ? f.error : null;
1671
1726
  const passwordOn = methods?.passwordSignIn !== false;
1672
- const providers = auth.session?.user.isAnonymous === true || methods?.organization?.required ? [] : methods?.providers ?? [];
1727
+ const providers = guest || methods?.organization?.required ? [] : methods?.providers ?? [];
1673
1728
  if (sent) {
1674
- return /* @__PURE__ */ jsxs("div", { className: f.cx("form"), ...f.root, children: [
1729
+ return /* @__PURE__ */ jsxs(Wrap, { f, as: "div", slot: "form", root: f.root, children: [
1675
1730
  /* @__PURE__ */ jsx(Note, { f, children: f.t.checkEmail }),
1676
1731
  props.links?.signIn && /* @__PURE__ */ jsx(A, { f, href: props.links.signIn, children: f.t.backToSignIn })
1677
1732
  ] });
1678
1733
  }
1734
+ if (methods === null) return /* @__PURE__ */ jsx(Wrap, { f, as: "div", slot: "form", root: f.root, busy: true });
1679
1735
  const footer = props.links?.signIn && /* @__PURE__ */ jsxs(Note, { f, footer: true, children: [
1680
1736
  f.t.haveAccount,
1681
1737
  " ",
1682
1738
  /* @__PURE__ */ jsx(A, { f, href: props.links.signIn, children: f.t.signIn })
1683
1739
  ] });
1684
1740
  if (!passwordOn) {
1685
- return /* @__PURE__ */ jsxs("div", { className: f.cx("form"), ...f.root, children: [
1741
+ return /* @__PURE__ */ jsxs(Wrap, { f, as: "div", slot: "form", root: f.root, children: [
1686
1742
  general && /* @__PURE__ */ jsx(Say, { f, tone: "danger", children: f.say(general) }),
1687
1743
  /* @__PURE__ */ jsx(ProviderButtons, { f, providers, onPick: (id) => auth.signIn.social(id, { returnTo: props.returnTo }) }),
1688
1744
  footer
1689
1745
  ] });
1690
1746
  }
1691
- return /* @__PURE__ */ jsxs("form", { className: f.cx("form"), ...f.root, onSubmit: submit, noValidate: true, children: [
1747
+ return /* @__PURE__ */ jsxs(Wrap, { f, as: "form", slot: "form", root: f.root, onSubmit: submit, noValidate: true, children: [
1692
1748
  general && /* @__PURE__ */ jsx(Say, { f, tone: "danger", children: f.say(general) }),
1693
1749
  /* @__PURE__ */ jsx(ProviderButtons, { f, providers, onPick: (id) => auth.signIn.social(id, { returnTo: props.returnTo }) }),
1694
1750
  providers.length > 0 && /* @__PURE__ */ jsx(Or, { f, children: f.t.orWithEmail }),
1695
- methods?.organization?.ask && /* @__PURE__ */ jsx(Field, { f, name: "organization", type: "text", label: f.t.company, value: organization, autoComplete: "organization", required: methods.organization.required, onChange: setOrganization }),
1751
+ methods.organization?.ask && /* @__PURE__ */ jsx(Field, { f, name: "organization", type: "text", label: f.t.company, value: organization, autoComplete: "organization", required: methods.organization.required, onChange: setOrganization }),
1696
1752
  /* @__PURE__ */ jsx(Field, { f, name: "name", type: "text", label: f.t.name, value: name, autoComplete: "name", onChange: setName }),
1697
1753
  /* @__PURE__ */ jsx(Field, { f, name: "email", type: "email", label: f.t.email, value: email, autoComplete: "username", required: true, onChange: setEmail }),
1698
1754
  /* @__PURE__ */ jsx(Field, { f, name: "password", type: "password", label: f.t.password, value: password, autoComplete: "new-password", required: true, onChange: setPassword, hint: f.passwordRule }),
@@ -1703,7 +1759,7 @@ function SignUp(props) {
1703
1759
  ] }),
1704
1760
  legal?.consentPurposes.map((p) => /* @__PURE__ */ jsx(Check, { f, name: `consent_${p.key}`, checked: consents[p.key] === true, required: p.required, onChange: (on) => setConsents({ ...consents, [p.key]: on }), children: p.label }, p.key)),
1705
1761
  challenge.node,
1706
- /* @__PURE__ */ jsx(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.signUp }),
1762
+ /* @__PURE__ */ jsx(f.Button, { type: "submit", variant: "primary", loading: f.loading(), disabled: f.busy, className: f.cx("button"), children: f.t.signUp }),
1707
1763
  footer
1708
1764
  ] });
1709
1765
  }
@@ -1725,16 +1781,16 @@ function ForgotPassword(props) {
1725
1781
  };
1726
1782
  const general = f.error && !f.error.field ? f.error : null;
1727
1783
  if (sent) {
1728
- return /* @__PURE__ */ jsxs("div", { className: f.cx("form"), ...f.root, children: [
1784
+ return /* @__PURE__ */ jsxs(Wrap, { f, as: "div", slot: "form", root: f.root, children: [
1729
1785
  /* @__PURE__ */ jsx(Note, { f, children: f.t.linkSent }),
1730
1786
  props.links?.signIn && /* @__PURE__ */ jsx(A, { f, href: props.links.signIn, children: f.t.backToSignIn })
1731
1787
  ] });
1732
1788
  }
1733
- return /* @__PURE__ */ jsxs("form", { className: f.cx("form"), ...f.root, onSubmit: submit, noValidate: true, children: [
1789
+ return /* @__PURE__ */ jsxs(Wrap, { f, as: "form", slot: "form", root: f.root, onSubmit: submit, noValidate: true, children: [
1734
1790
  general && /* @__PURE__ */ jsx(Say, { f, tone: "danger", children: f.say(general) }),
1735
1791
  /* @__PURE__ */ jsx(Field, { f, name: "email", type: "email", label: f.t.email, value: email, autoComplete: "username", required: true, onChange: setEmail }),
1736
1792
  challenge.node,
1737
- /* @__PURE__ */ jsx(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.sendLink }),
1793
+ /* @__PURE__ */ jsx(f.Button, { type: "submit", variant: "primary", loading: f.loading(), disabled: f.busy, className: f.cx("button"), children: f.t.sendLink }),
1738
1794
  props.links?.signIn && /* @__PURE__ */ jsx(Note, { f, footer: true, children: /* @__PURE__ */ jsx(A, { f, href: props.links.signIn, children: f.t.backToSignIn }) })
1739
1795
  ] });
1740
1796
  }
@@ -1761,21 +1817,21 @@ function ResetPassword(props) {
1761
1817
  const general = f.error && !f.error.field ? f.error : null;
1762
1818
  const back = props.links?.signIn && /* @__PURE__ */ jsx(A, { f, href: props.links.signIn, children: f.t.backToSignIn });
1763
1819
  if (changed) {
1764
- return /* @__PURE__ */ jsxs("div", { className: f.cx("form"), ...f.root, children: [
1820
+ return /* @__PURE__ */ jsxs(Wrap, { f, as: "div", slot: "form", root: f.root, children: [
1765
1821
  /* @__PURE__ */ jsx(Note, { f, children: f.t.passwordChanged }),
1766
1822
  back
1767
1823
  ] });
1768
1824
  }
1769
1825
  if (token === "") {
1770
- return /* @__PURE__ */ jsxs("div", { className: f.cx("form"), ...f.root, children: [
1826
+ return /* @__PURE__ */ jsxs(Wrap, { f, as: "div", slot: "form", root: f.root, children: [
1771
1827
  general && /* @__PURE__ */ jsx(Say, { f, tone: "danger", children: f.say(general) }),
1772
1828
  props.links?.forgotPassword && /* @__PURE__ */ jsx(A, { f, href: props.links.forgotPassword, children: f.t.forgotPassword })
1773
1829
  ] });
1774
1830
  }
1775
- return /* @__PURE__ */ jsxs("form", { className: f.cx("form"), ...f.root, onSubmit: submit, noValidate: true, children: [
1831
+ return /* @__PURE__ */ jsxs(Wrap, { f, as: "form", slot: "form", root: f.root, onSubmit: submit, noValidate: true, children: [
1776
1832
  general && /* @__PURE__ */ jsx(Say, { f, tone: "danger", children: f.say(general) }),
1777
1833
  /* @__PURE__ */ jsx(Field, { f, name: "password", type: "password", label: f.t.newPassword, value: password, autoComplete: "new-password", required: true, onChange: setPassword, hint: f.passwordRule }),
1778
- /* @__PURE__ */ jsx(f.Button, { type: "submit", disabled: f.busy || token === null, className: f.cx("button"), children: f.t.savePassword })
1834
+ /* @__PURE__ */ jsx(f.Button, { type: "submit", variant: "primary", loading: f.loading(), disabled: f.busy || token === null, className: f.cx("button"), children: f.t.savePassword })
1779
1835
  ] });
1780
1836
  }
1781
1837
  function VerifyEmail(props) {
@@ -1794,7 +1850,7 @@ function VerifyEmail(props) {
1794
1850
  setState("failed");
1795
1851
  });
1796
1852
  });
1797
- return /* @__PURE__ */ jsxs("div", { className: f.cx("form"), ...f.root, "aria-busy": state === "confirming", children: [
1853
+ return /* @__PURE__ */ jsxs(Wrap, { f, as: "div", slot: "form", root: f.root, busy: state === "confirming", children: [
1798
1854
  state === "confirming" && /* @__PURE__ */ jsx(Note, { f, children: f.t.confirming }),
1799
1855
  state === "confirmed" && /* @__PURE__ */ jsx(Note, { f, children: f.t.emailConfirmed }),
1800
1856
  state === "failed" && f.error && /* @__PURE__ */ jsx(Say, { f, tone: "danger", children: f.say(f.error) }),
@@ -1838,10 +1894,10 @@ function AcceptInvite(props) {
1838
1894
  const general = f.error && !f.error.field ? f.error : null;
1839
1895
  const back = props.links?.signIn && /* @__PURE__ */ jsx(A, { f, href: props.links.signIn, children: f.t.backToSignIn });
1840
1896
  if (state === "opening") {
1841
- return /* @__PURE__ */ jsx("div", { className: f.cx("form"), ...f.root, "aria-busy": "true", children: /* @__PURE__ */ jsx(Note, { f, children: f.t.openingInvitation }) });
1897
+ return /* @__PURE__ */ jsx(Wrap, { f, as: "div", slot: "form", root: f.root, busy: true, children: /* @__PURE__ */ jsx(Note, { f, children: f.t.openingInvitation }) });
1842
1898
  }
1843
1899
  if (state === "invalid") {
1844
- return /* @__PURE__ */ jsxs("div", { className: f.cx("form"), ...f.root, children: [
1900
+ return /* @__PURE__ */ jsxs(Wrap, { f, as: "div", slot: "form", root: f.root, children: [
1845
1901
  /* @__PURE__ */ jsx(Say, { f, tone: "danger", children: general ? f.say(general) : f.t.invitationInvalid }),
1846
1902
  back
1847
1903
  ] });
@@ -1849,13 +1905,13 @@ function AcceptInvite(props) {
1849
1905
  if (state === "code") return /* @__PURE__ */ jsx(SecondStepForm, { f, auth });
1850
1906
  const workspace = invitation?.workspace ?? "";
1851
1907
  const heading = invitation?.organization ? f.t.invitedToOrganization.replace("{organization}", invitation.organization).replace("{workspace}", workspace) : f.t.invitedTo.replace("{workspace}", workspace);
1852
- return /* @__PURE__ */ jsxs("form", { className: f.cx("form"), ...f.root, onSubmit: submit, noValidate: true, children: [
1908
+ return /* @__PURE__ */ jsxs(Wrap, { f, as: "form", slot: "form", root: f.root, onSubmit: submit, noValidate: true, children: [
1853
1909
  /* @__PURE__ */ jsx(Heading, { f, children: heading }),
1854
1910
  /* @__PURE__ */ jsx(Note, { f, children: f.t.invitedAs.replace("{email}", invitation?.email ?? "") }),
1855
1911
  general && /* @__PURE__ */ jsx(Say, { f, tone: "danger", children: f.say(general) }),
1856
1912
  /* @__PURE__ */ jsx("input", { type: "email", name: "username", autoComplete: "username", value: invitation?.email ?? "", readOnly: true, hidden: true }),
1857
1913
  /* @__PURE__ */ jsx(Field, { f, name: "password", type: "password", label: f.t.choosePassword, value: password, autoComplete: "new-password", required: true, onChange: setPassword, hint: f.passwordRule }),
1858
- /* @__PURE__ */ jsx(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.acceptInvite })
1914
+ /* @__PURE__ */ jsx(f.Button, { type: "submit", variant: "primary", loading: f.loading(), disabled: f.busy, className: f.cx("button"), children: f.t.acceptInvite })
1859
1915
  ] });
1860
1916
  }
1861
1917
  function PendingTerms(props) {
@@ -1875,7 +1931,7 @@ function PendingTerms(props) {
1875
1931
  setOwed([]);
1876
1932
  props.onSuccess?.();
1877
1933
  };
1878
- return /* @__PURE__ */ jsxs("div", { className: f.cx("form"), ...f.root, children: [
1934
+ return /* @__PURE__ */ jsxs(Wrap, { f, as: "div", slot: "form", root: f.root, children: [
1879
1935
  /* @__PURE__ */ jsx(Heading, { f, children: f.t.termsChanged }),
1880
1936
  /* @__PURE__ */ jsxs(Note, { f, children: [
1881
1937
  f.t.termsChangedHelp,
@@ -1883,15 +1939,15 @@ function PendingTerms(props) {
1883
1939
  /* @__PURE__ */ jsx(Documents, { f, documents: owed })
1884
1940
  ] }),
1885
1941
  f.error && /* @__PURE__ */ jsx(Say, { f, tone: "danger", children: f.say(f.error) }),
1886
- /* @__PURE__ */ jsx(f.Button, { type: "button", disabled: f.busy, className: f.cx("button"), onClick: () => void accept(), children: f.t.acceptTerms })
1942
+ /* @__PURE__ */ jsx(f.Button, { type: "button", variant: "primary", loading: f.loading(), disabled: f.busy, className: f.cx("button"), onClick: () => void accept(), children: f.t.acceptTerms })
1887
1943
  ] });
1888
1944
  }
1889
1945
  var ACCOUNT_SECTIONS = ["profile", "email", "password", "linked", "secondStep", "phone", "passkeys", "agents", "sessions"];
1890
1946
  function useSection(props) {
1891
1947
  const f = useForm({ ...props, unstyled: true });
1892
1948
  const [done, setDone] = useState(null);
1893
- const run = async (action) => {
1894
- f.setBusy(true);
1949
+ const run = async (action, key = SUBMIT) => {
1950
+ f.setBusy(key);
1895
1951
  f.setError(null);
1896
1952
  setDone(null);
1897
1953
  const r = await action();
@@ -1905,13 +1961,12 @@ function useSection(props) {
1905
1961
  function Account(props) {
1906
1962
  const auth = useAuth();
1907
1963
  const { accountUrl } = useSession();
1908
- const { root } = useForm(props);
1909
- const cx = (slot) => props.classNames?.[slot] ?? `cauthy-${slot}`;
1964
+ const f = useForm(props);
1910
1965
  const [account, setAccount] = useState(null);
1911
1966
  useEffect(() => {
1912
1967
  void auth.account.get().then(setAccount);
1913
1968
  }, []);
1914
- if (account === null) return /* @__PURE__ */ jsx("div", { className: cx("account"), ...root, "aria-busy": "true" });
1969
+ if (account === null) return /* @__PURE__ */ jsx(Wrap, { f, as: "div", slot: "account", root: f.root, busy: true });
1915
1970
  const when = props.formatDate ?? ((iso) => new Date(iso).toLocaleString());
1916
1971
  const mfa = (mfaEnabled) => setAccount({ ...account, mfaEnabled });
1917
1972
  const part = (section) => {
@@ -1936,7 +1991,7 @@ function Account(props) {
1936
1991
  return /* @__PURE__ */ jsx(SessionsPart, { props, when });
1937
1992
  }
1938
1993
  };
1939
- return /* @__PURE__ */ jsx("div", { className: cx("account"), ...root, children: (props.sections ?? ACCOUNT_SECTIONS).map((section) => /* @__PURE__ */ jsx(Fragment, { children: part(section) }, section)) });
1994
+ return /* @__PURE__ */ jsx(Wrap, { f, as: "div", slot: "account", root: f.root, children: (props.sections ?? ACCOUNT_SECTIONS).map((section) => /* @__PURE__ */ jsx(Fragment, { children: part(section) }, section)) });
1940
1995
  }
1941
1996
  function AccountProfile(props) {
1942
1997
  return /* @__PURE__ */ jsx(Account, { ...props, sections: ["profile"] });
@@ -2009,15 +2064,20 @@ function OrganizationMembers(props) {
2009
2064
  useEffect(() => {
2010
2065
  void load();
2011
2066
  }, [id]);
2012
- if (gone !== null) return /* @__PURE__ */ jsx("div", { className: f.cx("account"), ...root, children: /* @__PURE__ */ jsx(Say, { f, tone: "success", children: gone }) });
2013
- if (seat === void 0) return /* @__PURE__ */ jsx("div", { className: f.cx("account"), ...root, "aria-busy": "true" });
2014
- if (seat === null) return /* @__PURE__ */ jsx("div", { className: f.cx("account"), ...root, children: /* @__PURE__ */ jsx(Note, { f, children: f.t.organizationMissing }) });
2067
+ if (gone !== null) return /* @__PURE__ */ jsx(Wrap, { f, as: "div", slot: "account", root, children: /* @__PURE__ */ jsx(Say, { f, tone: "success", children: gone }) });
2068
+ if (seat === void 0) return /* @__PURE__ */ jsx(Wrap, { f, as: "div", slot: "account", root, busy: true });
2069
+ if (seat === null) return /* @__PURE__ */ jsx(Wrap, { f, as: "div", slot: "account", root, children: /* @__PURE__ */ jsx(Note, { f, children: f.t.organizationMissing }) });
2015
2070
  const manage = seat.can.manageMembers;
2016
2071
  const giveable = rolesToGive(seat, roles, props.roles);
2017
- const label = (r) => roles.find((c) => c.role === r)?.name || r;
2072
+ const roleName = (c) => props.roleLabel?.(c) || c.name || c.role;
2073
+ const label = (r) => {
2074
+ const c = roles.find((x) => x.role === r);
2075
+ return c ? roleName(c) : r;
2076
+ };
2018
2077
  const add = async (e) => {
2019
2078
  e.preventDefault();
2020
- const r = await run(() => auth.organizations.add(id, { email, role, locale: props.locale }));
2079
+ if (!f.check(checkEmail(email))) return;
2080
+ const r = await run(() => auth.organizations.add(id, { email, role, locale: props.locale }), "add");
2021
2081
  if (!r.ok) return;
2022
2082
  setEmail("");
2023
2083
  setDone(f.t.memberAdded.replace("{email}", r.member.email));
@@ -2025,28 +2085,28 @@ function OrganizationMembers(props) {
2025
2085
  await load();
2026
2086
  };
2027
2087
  const change = async (m, next) => {
2028
- const r = await run(() => auth.organizations.setRole(id, m.userId, next));
2088
+ const r = await run(() => auth.organizations.setRole(id, m.userId, next), `role:${m.userId}`);
2029
2089
  if (!r.ok) return;
2030
2090
  saved({ kind: "member.role_changed", organizationId: id, userId: m.userId, role: next, previousRole: m.role });
2031
2091
  await load();
2032
2092
  };
2033
2093
  const remove = async (m) => {
2034
2094
  setAsking(null);
2035
- const r = await run(() => auth.organizations.remove(id, m.userId));
2095
+ const r = await run(() => auth.organizations.remove(id, m.userId), `remove:${m.userId}`);
2036
2096
  if (!r.ok) return;
2037
2097
  saved({ kind: m.you ? "member.left" : "member.removed", organizationId: id, userId: m.userId });
2038
2098
  if (m.you) setSeat(null);
2039
2099
  else await load();
2040
2100
  };
2041
2101
  const inviteAgain = async (m) => {
2042
- const r = await run(() => auth.organizations.inviteAgain(id, m.userId, { locale: props.locale }));
2102
+ const r = await run(() => auth.organizations.inviteAgain(id, m.userId, { locale: props.locale }), `invite:${m.userId}`);
2043
2103
  if (!r.ok) return;
2044
2104
  setDone(f.t.memberInvitationSent);
2045
2105
  saved({ kind: "member.invited_again", organizationId: id, userId: m.userId });
2046
2106
  };
2047
2107
  const rename = async (e) => {
2048
2108
  e.preventDefault();
2049
- const r = await run(() => auth.organizations.rename(id, name));
2109
+ const r = await run(() => auth.organizations.rename(id, name), "rename");
2050
2110
  if (!r.ok) return;
2051
2111
  setSeat(r.organization);
2052
2112
  setName(r.organization.name);
@@ -2055,7 +2115,7 @@ function OrganizationMembers(props) {
2055
2115
  };
2056
2116
  const close = async (how) => {
2057
2117
  setAsking(null);
2058
- const r = await run(() => how === "archive" ? auth.organizations.archive(id) : auth.organizations.delete(id));
2118
+ const r = await run(() => how === "archive" ? auth.organizations.archive(id) : auth.organizations.delete(id), how);
2059
2119
  if (!r.ok) return;
2060
2120
  setGone((how === "archive" ? f.t.organizationArchived : f.t.organizationDeleted).replace("{org}", seat.name));
2061
2121
  saved({ kind: how === "archive" ? "organization.archived" : "organization.deleted", organizationId: id });
@@ -2070,8 +2130,8 @@ function OrganizationMembers(props) {
2070
2130
  const confirming = (text2, label2, yes) => /* @__PURE__ */ jsxs("span", { role: "group", "aria-label": label2, children: [
2071
2131
  /* @__PURE__ */ jsx("span", { children: text2 }),
2072
2132
  " ",
2073
- /* @__PURE__ */ jsx(f.Button, { type: "button", disabled: f.busy, className: f.cx("providerButton"), onClick: yes, children: f.t.yes }),
2074
- /* @__PURE__ */ jsx(f.Button, { type: "button", disabled: f.busy, className: f.cx("providerButton"), onClick: () => setAsking(null), children: f.t.cancel })
2133
+ /* @__PURE__ */ jsx(f.Button, { type: "button", variant: "danger", disabled: f.busy, className: f.cx("providerButton"), onClick: yes, children: f.t.yes }),
2134
+ /* @__PURE__ */ jsx(f.Button, { type: "button", variant: "secondary", disabled: f.busy, className: f.cx("providerButton"), onClick: () => setAsking(null), children: f.t.cancel })
2075
2135
  ] });
2076
2136
  const leaveQuestion = f.t.memberLeaveConfirm.replace("{org}", seat.name);
2077
2137
  const archiveQuestion = f.t.organizationArchiveConfirm.replace("{org}", seat.name);
@@ -2079,8 +2139,8 @@ function OrganizationMembers(props) {
2079
2139
  const archiving = seat.can.archive && !hidden("archive");
2080
2140
  const deleting = seat.can.delete && !hidden("delete");
2081
2141
  const renaming = seat.can.update && !hidden("rename");
2082
- return /* @__PURE__ */ jsxs("div", { className: f.cx("account"), ...root, children: [
2083
- /* @__PURE__ */ jsxs("section", { className: f.cx("section"), children: [
2142
+ return /* @__PURE__ */ jsxs(Wrap, { f, as: "div", slot: "account", root, children: [
2143
+ /* @__PURE__ */ jsxs(Wrap, { f, as: "section", slot: "section", section: "members", children: [
2084
2144
  /* @__PURE__ */ jsxs(Heading, { f, children: [
2085
2145
  f.t.members,
2086
2146
  " \xB7 ",
@@ -2093,6 +2153,8 @@ function OrganizationMembers(props) {
2093
2153
  f.Button,
2094
2154
  {
2095
2155
  type: "button",
2156
+ variant: "danger",
2157
+ loading: f.loading(`remove:${auth.session.user.id}`),
2096
2158
  disabled: f.busy,
2097
2159
  className: f.cx("providerButton"),
2098
2160
  onClick: () => ask(auth.session.user.id, f.t.memberLeave, leaveQuestion, () => void remove({ userId: auth.session.user.id, you: true })),
@@ -2127,34 +2189,34 @@ function OrganizationMembers(props) {
2127
2189
  label: `${f.t.memberRole}: ${who}`,
2128
2190
  value: m.role,
2129
2191
  disabled: f.busy,
2130
- options: choices.map((c) => ({ value: c.role, label: c.name || c.role })),
2192
+ options: choices.map((c) => ({ value: c.role, label: roleName(c) })),
2131
2193
  onChange: (next) => void change(m, next)
2132
2194
  }
2133
2195
  ) : f.C.Badge ? /* @__PURE__ */ jsx(f.C.Badge, { tone: "neutral", children: label(m.role) }) : /* @__PURE__ */ jsx("span", { children: label(m.role) }),
2134
2196
  " ",
2135
2197
  asking === m.userId ? confirming(question, action, () => void remove(m)) : /* @__PURE__ */ jsxs(Fragment2, { children: [
2136
- may.inviteAgain && /* @__PURE__ */ jsx(f.Button, { type: "button", disabled: f.busy, className: f.cx("providerButton"), onClick: () => void inviteAgain(m), children: f.t.memberInviteAgain }),
2137
- removable && /* @__PURE__ */ jsx(f.Button, { type: "button", disabled: f.busy, className: f.cx("providerButton"), onClick: () => ask(m.userId, action, question, () => void remove(m)), children: action })
2198
+ may.inviteAgain && /* @__PURE__ */ jsx(f.Button, { type: "button", variant: "secondary", loading: f.loading(`invite:${m.userId}`), disabled: f.busy, className: f.cx("providerButton"), onClick: () => void inviteAgain(m), children: f.t.memberInviteAgain }),
2199
+ removable && /* @__PURE__ */ jsx(f.Button, { type: "button", variant: "danger", loading: f.loading(`remove:${m.userId}`), disabled: f.busy, className: f.cx("providerButton"), onClick: () => ask(m.userId, action, question, () => void remove(m)), children: action })
2138
2200
  ] })
2139
2201
  ] }, m.userId);
2140
2202
  }) }),
2141
2203
  done && /* @__PURE__ */ jsx(Say, { f, tone: "success", children: done })
2142
2204
  ] }),
2143
- manage && giveable.length > 0 && /* @__PURE__ */ jsxs("form", { className: f.cx("section"), onSubmit: add, noValidate: true, children: [
2205
+ manage && giveable.length > 0 && /* @__PURE__ */ jsxs(Wrap, { f, as: "form", slot: "section", section: "add-member", onSubmit: add, noValidate: true, children: [
2144
2206
  /* @__PURE__ */ jsx(Heading, { f, children: f.t.memberAdd }),
2145
2207
  /* @__PURE__ */ jsx(Field, { f, name: "email", type: "email", label: f.t.email, value: email, autoComplete: "off", required: true, onChange: setEmail }),
2146
- /* @__PURE__ */ jsx(AddRole, { f, value: role, options: giveable.map((c) => ({ value: c.role, label: c.name || c.role })), onChange: setRole }),
2147
- /* @__PURE__ */ jsx(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.memberAdd })
2208
+ /* @__PURE__ */ jsx(AddRole, { f, value: role, options: giveable.map((c) => ({ value: c.role, label: roleName(c) })), onChange: setRole }),
2209
+ /* @__PURE__ */ jsx(f.Button, { type: "submit", variant: "primary", loading: f.loading("add"), disabled: f.busy, className: f.cx("button"), children: f.t.memberAdd })
2148
2210
  ] }),
2149
- (renaming || archiving || deleting) && /* @__PURE__ */ jsxs("section", { className: f.cx("section"), children: [
2211
+ (renaming || archiving || deleting) && /* @__PURE__ */ jsxs(Wrap, { f, as: "section", slot: "section", section: "organization", children: [
2150
2212
  /* @__PURE__ */ jsx(Heading, { f, children: f.t.organization }),
2151
- renaming && /* @__PURE__ */ jsxs("form", { onSubmit: rename, noValidate: true, children: [
2213
+ renaming && /* @__PURE__ */ jsxs(Wrap, { f, as: "form", section: "rename", onSubmit: rename, noValidate: true, children: [
2152
2214
  /* @__PURE__ */ jsx(Field, { f, name: "name", type: "text", label: f.t.name, value: name, autoComplete: "organization", required: true, onChange: setName }),
2153
- /* @__PURE__ */ jsx(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.organizationRename })
2215
+ /* @__PURE__ */ jsx(f.Button, { type: "submit", variant: "primary", loading: f.loading("rename"), disabled: f.busy, className: f.cx("button"), children: f.t.organizationRename })
2154
2216
  ] }),
2155
2217
  asking === "archive" ? confirming(archiveQuestion, f.t.organizationArchive, () => void close("archive")) : asking === "delete" ? confirming(deleteQuestion, f.t.organizationDelete, () => void close("delete")) : /* @__PURE__ */ jsxs(Fragment2, { children: [
2156
- archiving && /* @__PURE__ */ jsx(f.Button, { type: "button", disabled: f.busy, className: f.cx("providerButton"), onClick: () => ask("archive", f.t.organizationArchive, archiveQuestion, () => void close("archive")), children: f.t.organizationArchive }),
2157
- deleting && /* @__PURE__ */ jsx(f.Button, { type: "button", disabled: f.busy, className: f.cx("providerButton"), onClick: () => ask("delete", f.t.organizationDelete, deleteQuestion, () => void close("delete")), children: f.t.organizationDelete })
2218
+ archiving && /* @__PURE__ */ jsx(f.Button, { type: "button", variant: "danger", loading: f.loading("archive"), disabled: f.busy, className: f.cx("providerButton"), onClick: () => ask("archive", f.t.organizationArchive, archiveQuestion, () => void close("archive")), children: f.t.organizationArchive }),
2219
+ deleting && /* @__PURE__ */ jsx(f.Button, { type: "button", variant: "danger", loading: f.loading("delete"), disabled: f.busy, className: f.cx("providerButton"), onClick: () => ask("delete", f.t.organizationDelete, deleteQuestion, () => void close("delete")), children: f.t.organizationDelete })
2158
2220
  ] })
2159
2221
  ] })
2160
2222
  ] });
@@ -2179,14 +2241,15 @@ function PasskeysPart({ props, account, accountUrl, when }) {
2179
2241
  }, []);
2180
2242
  if (here === null) return null;
2181
2243
  if (!here) {
2182
- return /* @__PURE__ */ jsxs("section", { className: f.cx("section"), children: [
2244
+ return /* @__PURE__ */ jsxs(Wrap, { f, as: "section", slot: "section", section: "passkeys", children: [
2183
2245
  /* @__PURE__ */ jsx(Heading, { f, children: f.t.passkeys }),
2184
2246
  /* @__PURE__ */ jsx(A, { f, href: accountUrl, children: f.t.managePasskeys })
2185
2247
  ] });
2186
2248
  }
2187
2249
  const add = async (e, kind = "device") => {
2188
2250
  e?.preventDefault();
2189
- const r = await run(() => auth.account.addPasskey({ name, password, kind }));
2251
+ if (account.hasPassword && !f.check(checkPassword(password))) return;
2252
+ const r = await run(() => auth.account.addPasskey({ name, password, kind }), kind === "security_key" ? "security_key" : SUBMIT);
2190
2253
  if (!r.ok) {
2191
2254
  if (r.error.code === "cancelled") f.setError(null);
2192
2255
  return;
@@ -2198,12 +2261,12 @@ function PasskeysPart({ props, account, accountUrl, when }) {
2198
2261
  props.onSaved?.({ kind: "passkey.added", id: r.passkey.id });
2199
2262
  };
2200
2263
  const remove = async (id) => {
2201
- const r = await run(() => auth.account.removePasskey(id));
2264
+ const r = await run(() => auth.account.removePasskey(id), `remove:${id}`);
2202
2265
  if (!r.ok) return;
2203
2266
  setList(list.filter((p) => p.id !== id));
2204
2267
  props.onSaved?.({ kind: "passkey.removed", id });
2205
2268
  };
2206
- return /* @__PURE__ */ jsxs("form", { className: f.cx("section"), onSubmit: add, noValidate: true, children: [
2269
+ return /* @__PURE__ */ jsxs(Wrap, { f, as: "form", slot: "section", section: "passkeys", onSubmit: add, noValidate: true, children: [
2207
2270
  /* @__PURE__ */ jsx(Heading, { f, children: f.t.passkeys }),
2208
2271
  /* @__PURE__ */ jsx(Note, { f, children: f.t.passkeysHelp }),
2209
2272
  general && /* @__PURE__ */ jsx(Say, { f, tone: "danger", children: f.say(general) }),
@@ -2213,12 +2276,12 @@ function PasskeysPart({ props, account, accountUrl, when }) {
2213
2276
  " \xB7 ",
2214
2277
  when(p.createdAt),
2215
2278
  " ",
2216
- /* @__PURE__ */ jsx(f.Button, { type: "button", disabled: f.busy, className: f.cx("providerButton"), onClick: () => void remove(p.id), children: f.t.removePasskey })
2279
+ /* @__PURE__ */ jsx(f.Button, { type: "button", variant: "danger", loading: f.loading(`remove:${p.id}`), disabled: f.busy, className: f.cx("providerButton"), onClick: () => void remove(p.id), children: f.t.removePasskey })
2217
2280
  ] }, p.id)) }),
2218
2281
  /* @__PURE__ */ jsx(Field, { f, name: "name", type: "text", label: f.t.passkeyName, value: name, autoComplete: "off", onChange: setName }),
2219
2282
  account.hasPassword && /* @__PURE__ */ jsx(Field, { f, name: "password", type: "password", label: f.t.currentPassword, value: password, autoComplete: "current-password", required: true, onChange: setPassword }),
2220
- /* @__PURE__ */ jsx(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.addPasskeyDevice }),
2221
- /* @__PURE__ */ jsx(f.Button, { type: "button", disabled: f.busy, className: f.cx("providerButton"), onClick: () => void add(null, "security_key"), children: f.t.addSecurityKey }),
2283
+ /* @__PURE__ */ jsx(f.Button, { type: "submit", variant: "primary", loading: f.loading(), disabled: f.busy, className: f.cx("button"), children: f.t.addPasskeyDevice }),
2284
+ /* @__PURE__ */ jsx(f.Button, { type: "button", variant: "secondary", loading: f.loading("security_key"), disabled: f.busy, className: f.cx("providerButton"), onClick: () => void add(null, "security_key"), children: f.t.addSecurityKey }),
2222
2285
  done && /* @__PURE__ */ jsx(Say, { f, tone: "success", children: done })
2223
2286
  ] });
2224
2287
  }
@@ -2234,12 +2297,12 @@ function ProfilePart({ props, account, onSaved }) {
2234
2297
  setDone(f.t.saved);
2235
2298
  props.onSaved?.({ kind: "profile.saved", name: r.account.name });
2236
2299
  };
2237
- return /* @__PURE__ */ jsxs("form", { className: f.cx("section"), onSubmit: submit, noValidate: true, children: [
2300
+ return /* @__PURE__ */ jsxs(Wrap, { f, as: "form", slot: "section", section: "profile", onSubmit: submit, noValidate: true, children: [
2238
2301
  /* @__PURE__ */ jsx(Heading, { f, children: f.t.profile }),
2239
2302
  /* @__PURE__ */ jsx(Note, { f, children: account.email }),
2240
2303
  general && /* @__PURE__ */ jsx(Say, { f, tone: "danger", children: f.say(general) }),
2241
2304
  /* @__PURE__ */ jsx(Field, { f, name: "name", type: "text", label: f.t.name, value: name, autoComplete: "name", onChange: setName }),
2242
- /* @__PURE__ */ jsx(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.save }),
2305
+ /* @__PURE__ */ jsx(f.Button, { type: "submit", variant: "primary", loading: f.loading(), disabled: f.busy, className: f.cx("button"), children: f.t.save }),
2243
2306
  done && /* @__PURE__ */ jsx(Say, { f, tone: "success", children: done })
2244
2307
  ] });
2245
2308
  }
@@ -2250,19 +2313,20 @@ function EmailPart({ props, account }) {
2250
2313
  const [password, setPassword] = useState("");
2251
2314
  const submit = async (e) => {
2252
2315
  e.preventDefault();
2316
+ if (!f.check(checkEmail(email), account.hasPassword && checkPassword(password))) return;
2253
2317
  const r = await run(() => auth.account.changeEmail({ email, password }));
2254
2318
  if (!r.ok) return;
2255
2319
  setPassword("");
2256
2320
  setDone(f.t.emailSent.replace("{email}", r.email));
2257
2321
  props.onSaved?.({ kind: "email.change_requested", email: r.email });
2258
2322
  };
2259
- return /* @__PURE__ */ jsxs("form", { className: f.cx("section"), onSubmit: submit, noValidate: true, children: [
2323
+ return /* @__PURE__ */ jsxs(Wrap, { f, as: "form", slot: "section", section: "email", onSubmit: submit, noValidate: true, children: [
2260
2324
  /* @__PURE__ */ jsx(Heading, { f, children: f.t.emailAddress }),
2261
2325
  /* @__PURE__ */ jsx(Note, { f, children: account.email }),
2262
2326
  general && /* @__PURE__ */ jsx(Say, { f, tone: "danger", children: f.say(general) }),
2263
2327
  /* @__PURE__ */ jsx(Field, { f, name: "email", type: "email", label: f.t.newEmail, value: email, autoComplete: "email", required: true, onChange: setEmail }),
2264
2328
  account.hasPassword && /* @__PURE__ */ jsx(Field, { f, name: "password", type: "password", label: f.t.currentPassword, value: password, autoComplete: "current-password", required: true, onChange: setPassword }),
2265
- /* @__PURE__ */ jsx(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.changeEmail }),
2329
+ /* @__PURE__ */ jsx(f.Button, { type: "submit", variant: "primary", loading: f.loading(), disabled: f.busy, className: f.cx("button"), children: f.t.changeEmail }),
2266
2330
  done && /* @__PURE__ */ jsx(Say, { f, tone: "success", children: done })
2267
2331
  ] });
2268
2332
  }
@@ -2285,13 +2349,13 @@ function LinkedPart({ props }) {
2285
2349
  if (linked === null) return null;
2286
2350
  const name = (id) => offered.find((p) => p.id === id)?.name ?? id;
2287
2351
  const unlink = async (provider) => {
2288
- const r = await run(() => auth.account.unlink(provider));
2352
+ const r = await run(() => auth.account.unlink(provider), `unlink:${provider}`);
2289
2353
  if (!r.ok) return;
2290
2354
  setLinked(linked.filter((x) => x.provider !== provider));
2291
2355
  props.onSaved?.({ kind: "identity.unlinked", provider });
2292
2356
  };
2293
2357
  const here = typeof window === "undefined" ? void 0 : window.location.pathname;
2294
- return /* @__PURE__ */ jsxs("section", { className: f.cx("section"), children: [
2358
+ return /* @__PURE__ */ jsxs(Wrap, { f, as: "section", slot: "section", section: "linked", children: [
2295
2359
  /* @__PURE__ */ jsx(Heading, { f, children: f.t.linkedAccounts }),
2296
2360
  came && /* @__PURE__ */ jsx(Say, { f, tone: "success", children: f.t.linked }),
2297
2361
  general && /* @__PURE__ */ jsx(Say, { f, tone: "danger", children: f.say(general) }),
@@ -2299,9 +2363,9 @@ function LinkedPart({ props }) {
2299
2363
  name(x.provider),
2300
2364
  x.email ? ` \xB7 ${x.email}` : "",
2301
2365
  " ",
2302
- /* @__PURE__ */ jsx(f.Button, { type: "button", disabled: f.busy, className: f.cx("providerButton"), onClick: () => void unlink(x.provider), children: f.t.unlink })
2366
+ /* @__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 })
2303
2367
  ] }, x.provider)) }),
2304
- offered.filter((p) => !linked.some((x) => x.provider === p.id)).map((p) => /* @__PURE__ */ jsxs(f.Button, { type: "button", className: f.cx("providerButton"), onClick: () => auth.account.link(p.id, { returnTo: here }), children: [
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: [
2305
2369
  f.t.linkWith,
2306
2370
  " ",
2307
2371
  p.name
@@ -2316,6 +2380,7 @@ function PasswordPart({ props }) {
2316
2380
  usePasswordPolicy(f, useMethods(auth));
2317
2381
  const submit = async (e) => {
2318
2382
  e.preventDefault();
2383
+ if (!f.check(checkPassword(password), newPasswordProblem(newPassword, f.policy, "newPassword"))) return;
2319
2384
  const r = await run(() => auth.account.changePassword({ password, newPassword }));
2320
2385
  if (!r.ok) return;
2321
2386
  setPassword("");
@@ -2323,12 +2388,12 @@ function PasswordPart({ props }) {
2323
2388
  setDone(f.t.passwordChangedHere);
2324
2389
  props.onSaved?.({ kind: "password.changed" });
2325
2390
  };
2326
- return /* @__PURE__ */ jsxs("form", { className: f.cx("section"), onSubmit: submit, noValidate: true, children: [
2391
+ return /* @__PURE__ */ jsxs(Wrap, { f, as: "form", slot: "section", section: "password", onSubmit: submit, noValidate: true, children: [
2327
2392
  /* @__PURE__ */ jsx(Heading, { f, children: f.t.changePassword }),
2328
2393
  general && /* @__PURE__ */ jsx(Say, { f, tone: "danger", children: f.say(general) }),
2329
2394
  /* @__PURE__ */ jsx(Field, { f, name: "password", type: "password", label: f.t.currentPassword, value: password, autoComplete: "current-password", required: true, onChange: setPassword }),
2330
2395
  /* @__PURE__ */ jsx(Field, { f, name: "newPassword", type: "password", label: f.t.newPassword, value: newPassword, autoComplete: "new-password", required: true, onChange: setNewPassword, hint: f.passwordRule }),
2331
- /* @__PURE__ */ jsx(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.changePassword }),
2396
+ /* @__PURE__ */ jsx(f.Button, { type: "submit", variant: "primary", loading: f.loading(), disabled: f.busy, className: f.cx("button"), children: f.t.changePassword }),
2332
2397
  done && /* @__PURE__ */ jsx(Say, { f, tone: "success", children: done })
2333
2398
  ] });
2334
2399
  }
@@ -2365,7 +2430,7 @@ function SecondStepPart({ props, account, onChanged }) {
2365
2430
  props.onSaved?.({ kind: "mfa.disabled" });
2366
2431
  };
2367
2432
  const renew = async () => {
2368
- const r = await run(() => auth.account.mfa.recoveryCodes({ password }));
2433
+ const r = await run(() => auth.account.mfa.recoveryCodes({ password }), "codes");
2369
2434
  if (!r.ok) return;
2370
2435
  setCodes(r.recoveryCodes);
2371
2436
  props.onSaved?.({ kind: "recovery_codes.regenerated" });
@@ -2376,24 +2441,24 @@ function SecondStepPart({ props, account, onChanged }) {
2376
2441
  /* @__PURE__ */ jsx("ul", { className: f.cx("codes"), children: codes.map((c) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("code", { children: c }) }, c)) })
2377
2442
  ] });
2378
2443
  if (setup) {
2379
- return /* @__PURE__ */ jsxs("form", { className: f.cx("section"), onSubmit: confirm, noValidate: true, children: [
2444
+ return /* @__PURE__ */ jsxs(Wrap, { f, as: "form", slot: "section", section: "secondStep", onSubmit: confirm, noValidate: true, children: [
2380
2445
  /* @__PURE__ */ jsx(Heading, { f, children: f.t.secondStep }),
2381
2446
  /* @__PURE__ */ jsx(Note, { f, children: f.t.addToApp }),
2382
2447
  /* @__PURE__ */ jsx("code", { className: f.cx("secret"), children: setup.secret }),
2383
2448
  /* @__PURE__ */ jsx(A, { f, href: setup.otpauthUri, children: f.t.openInApp }),
2384
2449
  general && /* @__PURE__ */ jsx(Say, { f, tone: "danger", children: f.say(general) }),
2385
2450
  /* @__PURE__ */ jsx(Field, { f, name: "code", type: "text", label: f.t.code, value: code, autoComplete: "one-time-code", required: true, onChange: setCode }),
2386
- /* @__PURE__ */ jsx(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.verify })
2451
+ /* @__PURE__ */ jsx(f.Button, { type: "submit", variant: "primary", loading: f.loading(), disabled: f.busy, className: f.cx("button"), children: f.t.verify })
2387
2452
  ] });
2388
2453
  }
2389
- return /* @__PURE__ */ jsxs("form", { className: f.cx("section"), onSubmit: account.mfaEnabled ? turnOff : start, noValidate: true, children: [
2454
+ return /* @__PURE__ */ jsxs(Wrap, { f, as: "form", slot: "section", section: "secondStep", onSubmit: account.mfaEnabled ? turnOff : start, noValidate: true, children: [
2390
2455
  /* @__PURE__ */ jsx(Heading, { f, children: f.t.secondStep }),
2391
2456
  /* @__PURE__ */ jsx(Note, { f, children: account.mfaEnabled ? f.t.secondStepOn : f.t.secondStepOff }),
2392
2457
  shown,
2393
2458
  general && /* @__PURE__ */ jsx(Say, { f, tone: "danger", children: f.say(general) }),
2394
2459
  passwordField,
2395
- /* @__PURE__ */ jsx(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: account.mfaEnabled ? f.t.turnOff : f.t.turnOn }),
2396
- account.mfaEnabled && /* @__PURE__ */ jsx(f.Button, { type: "button", disabled: f.busy, className: f.cx("providerButton"), onClick: () => void renew(), children: f.t.newRecoveryCodes })
2460
+ /* @__PURE__ */ jsx(f.Button, { type: "submit", variant: account.mfaEnabled ? "danger" : "primary", loading: f.loading(), disabled: f.busy, className: f.cx("button"), children: account.mfaEnabled ? f.t.turnOff : f.t.turnOn }),
2461
+ account.mfaEnabled && /* @__PURE__ */ jsx(f.Button, { type: "button", variant: "secondary", loading: f.loading("codes"), disabled: f.busy, className: f.cx("providerButton"), onClick: () => void renew(), children: f.t.newRecoveryCodes })
2397
2462
  ] });
2398
2463
  }
2399
2464
  function PhonePart({ props, account, onChanged }) {
@@ -2443,15 +2508,15 @@ function PhonePart({ props, account, onChanged }) {
2443
2508
  };
2444
2509
  const passwordField = account.hasPassword && /* @__PURE__ */ jsx(Field, { f, name: "password", type: "password", label: f.t.currentPassword, value: password, autoComplete: "current-password", required: true, onChange: setPassword });
2445
2510
  if (sentTo) {
2446
- return /* @__PURE__ */ jsxs("form", { className: f.cx("section"), onSubmit: confirm, noValidate: true, children: [
2511
+ return /* @__PURE__ */ jsxs(Wrap, { f, as: "form", slot: "section", section: "phone", onSubmit: confirm, noValidate: true, children: [
2447
2512
  /* @__PURE__ */ jsx(Heading, { f, children: f.t.phone }),
2448
2513
  /* @__PURE__ */ jsx(Note, { f, children: f.t.codeSent.replace("{to}", sentTo) }),
2449
2514
  general && /* @__PURE__ */ jsx(Say, { f, tone: "danger", children: f.say(general) }),
2450
2515
  /* @__PURE__ */ jsx(Field, { f, name: "code", type: "text", label: f.t.code, value: code, autoComplete: "one-time-code", required: true, onChange: setCode }),
2451
- /* @__PURE__ */ jsx(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.verify })
2516
+ /* @__PURE__ */ jsx(f.Button, { type: "submit", variant: "primary", loading: f.loading(), disabled: f.busy, className: f.cx("button"), children: f.t.verify })
2452
2517
  ] });
2453
2518
  }
2454
- return /* @__PURE__ */ jsxs("form", { className: f.cx("section"), onSubmit: ways.phone ? remove : add, noValidate: true, children: [
2519
+ return /* @__PURE__ */ jsxs(Wrap, { f, as: "form", slot: "section", section: "phone", onSubmit: ways.phone ? remove : add, noValidate: true, children: [
2455
2520
  /* @__PURE__ */ jsx(Heading, { f, children: f.t.phone }),
2456
2521
  /* @__PURE__ */ jsx(Note, { f, children: ways.phone ? f.t.phoneOn.replace("{to}", ways.phone.number) : `${f.t.phoneOff} ${f.t.phoneHelp}` }),
2457
2522
  codes && /* @__PURE__ */ jsxs(Fragment2, { children: [
@@ -2467,7 +2532,7 @@ function PhonePart({ props, account, onChanged }) {
2467
2532
  c === "whatsapp" ? f.t.byWhatsapp : f.t.bySms
2468
2533
  ] }, c)) }),
2469
2534
  passwordField,
2470
- /* @__PURE__ */ jsx(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: ways.phone ? f.t.removePhone : f.t.addPhone })
2535
+ /* @__PURE__ */ jsx(f.Button, { type: "submit", variant: ways.phone ? "danger" : "primary", loading: f.loading(), disabled: f.busy, className: f.cx("button"), children: ways.phone ? f.t.removePhone : f.t.addPhone })
2471
2536
  ] });
2472
2537
  }
2473
2538
  function AgentsPart({ props, when }) {
@@ -2479,12 +2544,12 @@ function AgentsPart({ props, when }) {
2479
2544
  }, []);
2480
2545
  if (list.length === 0) return null;
2481
2546
  const disconnect = async (id) => {
2482
- const r = await run(() => auth.account.disconnectAgent(id));
2547
+ const r = await run(() => auth.account.disconnectAgent(id), `disconnect:${id}`);
2483
2548
  if (!r.ok) return;
2484
2549
  setList(list.filter((a) => a.id !== id));
2485
2550
  props.onSaved?.({ kind: "agent.disconnected", id });
2486
2551
  };
2487
- return /* @__PURE__ */ jsxs("section", { className: f.cx("section"), children: [
2552
+ return /* @__PURE__ */ jsxs(Wrap, { f, as: "section", slot: "section", section: "agents", children: [
2488
2553
  /* @__PURE__ */ jsx(Heading, { f, children: f.t.agents }),
2489
2554
  /* @__PURE__ */ jsx(Note, { f, children: f.t.agentsHelp }),
2490
2555
  general && /* @__PURE__ */ jsx(Say, { f, tone: "danger", children: f.say(general) }),
@@ -2497,7 +2562,7 @@ function AgentsPart({ props, when }) {
2497
2562
  " \xB7 ",
2498
2563
  when(a.lastUsedAt),
2499
2564
  " ",
2500
- /* @__PURE__ */ jsx(f.Button, { type: "button", disabled: f.busy, className: f.cx("providerButton"), onClick: () => void disconnect(a.id), children: f.t.disconnect })
2565
+ /* @__PURE__ */ jsx(f.Button, { type: "button", variant: "danger", loading: f.loading(`disconnect:${a.id}`), disabled: f.busy, className: f.cx("providerButton"), onClick: () => void disconnect(a.id), children: f.t.disconnect })
2501
2566
  ] }, a.id)) })
2502
2567
  ] });
2503
2568
  }
@@ -2515,7 +2580,7 @@ function SessionsPart({ props, when }) {
2515
2580
  if (props.onSignedOut) props.onSignedOut();
2516
2581
  else window.location.assign("/");
2517
2582
  };
2518
- return /* @__PURE__ */ jsxs("section", { className: f.cx("section"), children: [
2583
+ return /* @__PURE__ */ jsxs(Wrap, { f, as: "section", slot: "section", section: "sessions", children: [
2519
2584
  /* @__PURE__ */ jsx(Heading, { f, children: f.t.sessions }),
2520
2585
  /* @__PURE__ */ jsx("ul", { className: f.cx("sessions"), children: sessions.map((s, i) => /* @__PURE__ */ jsxs("li", { className: f.cx("session"), children: [
2521
2586
  s.device || f.t.unknownDevice,
@@ -2525,7 +2590,7 @@ function SessionsPart({ props, when }) {
2525
2590
  when(s.createdAt)
2526
2591
  ] }, i)) }),
2527
2592
  general && /* @__PURE__ */ jsx(Say, { f, tone: "danger", children: f.say(general) }),
2528
- /* @__PURE__ */ jsx(f.Button, { type: "button", disabled: f.busy, className: f.cx("button"), onClick: () => void end(), children: f.t.signOutEverywhere })
2593
+ /* @__PURE__ */ jsx(f.Button, { type: "button", variant: "danger", loading: f.loading(), disabled: f.busy, className: f.cx("button"), onClick: () => void end(), children: f.t.signOutEverywhere })
2529
2594
  ] });
2530
2595
  }
2531
2596
  function ChannelPick({ f, value, channels, onChange }) {
@@ -2546,15 +2611,22 @@ function ChannelPick({ f, value, channels, onChange }) {
2546
2611
  // src/react.tsx
2547
2612
  import { jsx as jsx2 } from "react/jsx-runtime";
2548
2613
  var Ctx = createContext(null);
2614
+ var LocaleCtx = createContext(() => void 0);
2549
2615
  var AHEAD_MS = 6e4;
2550
- function CauthyProvider({ children, basePath = "/api/auth", initialSession }) {
2616
+ function CauthyProvider({ children, basePath = "/api/auth", initialSession, locale }) {
2551
2617
  const base = withoutTrailingSlashes(basePath);
2618
+ const said = useRef2(locale);
2619
+ said.current = locale;
2620
+ const readLocale = useCallback2(() => {
2621
+ const l = said.current;
2622
+ return typeof l === "function" ? l() : l;
2623
+ }, []);
2552
2624
  const [session, setSession] = useState2(initialSession ?? null);
2553
2625
  const [status, setStatus] = useState2(
2554
2626
  initialSession === void 0 ? "loading" : initialSession === null ? "unauthenticated" : "authenticated"
2555
2627
  );
2556
2628
  const asking = useRef2(null);
2557
- const refresh = useCallback(() => {
2629
+ const refresh = useCallback2(() => {
2558
2630
  asking.current ??= (async () => {
2559
2631
  try {
2560
2632
  const res = await fetch(`${base}/session`, { credentials: "same-origin", headers: { accept: "application/json" } });
@@ -2612,7 +2684,7 @@ function CauthyProvider({ children, basePath = "/api/auth", initialSession }) {
2612
2684
  }),
2613
2685
  [session, status, refresh, base]
2614
2686
  );
2615
- return /* @__PURE__ */ jsx2(Ctx.Provider, { value, children });
2687
+ return /* @__PURE__ */ jsx2(Ctx.Provider, { value, children: /* @__PURE__ */ jsx2(LocaleCtx.Provider, { value: readLocale, children }) });
2616
2688
  }
2617
2689
  function useSession() {
2618
2690
  const ctx = useContext(Ctx);
@@ -2621,8 +2693,9 @@ function useSession() {
2621
2693
  }
2622
2694
  function useAuth() {
2623
2695
  const { session, status, refresh, basePath } = useSession();
2696
+ const locale = useContext(LocaleCtx);
2624
2697
  return useMemo(() => {
2625
- const client = createAuthClient({ basePath });
2698
+ const client = createAuthClient({ basePath, locale });
2626
2699
  const then = async (running) => {
2627
2700
  const r = await running;
2628
2701
  await refresh();
@@ -2659,7 +2732,7 @@ function useAuth() {
2659
2732
  status,
2660
2733
  refresh
2661
2734
  };
2662
- }, [basePath, refresh, session, status]);
2735
+ }, [basePath, locale, refresh, session, status]);
2663
2736
  }
2664
2737
  export {
2665
2738
  AcceptInvite,