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/next.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { C as Cauthy } from './cauthy-2jtP2LVQ.mjs';
2
- import { S as Session } from './system-BrrJsrFW.mjs';
1
+ import { C as Cauthy } from './cauthy-C-GoRXy9.mjs';
2
+ import { S as Session } from './system-DUGV040y.mjs';
3
3
  import 'jose';
4
4
  import './theme.mjs';
5
5
 
package/dist/next.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { C as Cauthy } from './cauthy-CKfvvv_i.js';
2
- import { S as Session } from './system-BvvoNs2y.js';
1
+ import { C as Cauthy } from './cauthy-BUozWD72.js';
2
+ import { S as Session } from './system-Bz4haPrI.js';
3
3
  import 'jose';
4
4
  import './theme.js';
5
5
 
package/dist/react.d.mts CHANGED
@@ -620,7 +620,11 @@ interface OrganizationsClient {
620
620
  members(organizationId: string): Promise<OrganizationPerson[] | null>;
621
621
  /** The roles a member may be given, or null when the person may not see them. */
622
622
  roles(organizationId: string): Promise<OrganizationRoleChoice[] | null>;
623
- /** Adds a person by their address; an address with no account gets one and its invitation. */
623
+ /**
624
+ * Adds a person by their address; an address with no account gets one and
625
+ * its invitation. It only adds: a person who already belongs keeps their
626
+ * role and the result is the error `member_exists` (change it with `setRole`).
627
+ */
624
628
  add(organizationId: string, form: {
625
629
  email: string;
626
630
  role: string;
@@ -1193,6 +1197,8 @@ interface AuthTexts {
1193
1197
  savePassword: string;
1194
1198
  passwordChanged: string;
1195
1199
  confirming: string;
1200
+ /** While a form waits on what the workspace offers, or on the account it shows. */
1201
+ loading: string;
1196
1202
  emailConfirmed: string;
1197
1203
  invitedTo: string;
1198
1204
  invitedToOrganization: string;
@@ -1279,7 +1285,7 @@ interface AuthTexts {
1279
1285
  /** By error code; one missing shows what the server said. */
1280
1286
  errors: Record<string, string>;
1281
1287
  }
1282
- type Slot = 'form' | 'field' | 'label' | 'input' | 'button' | 'providerButton' | 'providerIcon' | 'link' | 'error' | 'fieldError' | 'hint' | 'notice' | 'separator' | 'footer' | 'check' | 'account' | 'section' | 'heading' | 'secret' | 'codes' | 'sessions' | 'session';
1288
+ type Slot = 'form' | 'field' | 'label' | 'input' | 'button' | 'providerButton' | 'providerIcon' | 'link' | 'error' | 'fieldError' | 'hint' | 'notice' | 'pending' | 'separator' | 'footer' | 'check' | 'account' | 'section' | 'heading' | 'secret' | 'codes' | 'sessions' | 'session';
1283
1289
  interface AuthFormProps {
1284
1290
  /** The language of the built-in sentences. Default: `en`. */
1285
1291
  locale?: 'en' | 'es';
@@ -1287,8 +1293,14 @@ interface AuthFormProps {
1287
1293
  texts?: Partial<Omit<AuthTexts, 'errors'>> & {
1288
1294
  errors?: Record<string, string>;
1289
1295
  };
1290
- /** Called once the person is signed in. Default: go to `returnTo`. */
1291
- onSuccess?: () => void;
1296
+ /**
1297
+ * Called once the person is signed in, instead of going to `returnTo`. The
1298
+ * form stays busy (its button loading, every control disabled) until it
1299
+ * leaves the page. When the application keeps the form on screen while it
1300
+ * moves on (a router's navigation, loading the session), return a Promise:
1301
+ * the form waits for it, then is free again if it is still shown.
1302
+ */
1303
+ onSuccess?: () => void | Promise<unknown>;
1292
1304
  /** Where to go after signing in, and where a provider returns to. Default: `/`. */
1293
1305
  returnTo?: string;
1294
1306
  /**
@@ -1331,7 +1343,9 @@ interface AuthFormProps {
1331
1343
  * other action (a provider, a passkey, a sign-in link, cancel, send again);
1332
1344
  * `danger` removes, leaves, deletes, disconnects or turns something off.
1333
1345
  * `loading` is true on the one button whose action is under way; every
1334
- * button is `disabled` meanwhile, as before.
1346
+ * button is `disabled` meanwhile. Draw a spinner (or the like) on the button
1347
+ * while `loading` is true, and keep its label: after a sign-in it stays true
1348
+ * until the page is left, so the person sees the work go on.
1335
1349
  */
1336
1350
  interface AuthButtonProps {
1337
1351
  type: 'submit' | 'button';
@@ -1372,6 +1386,7 @@ interface AuthInputProps {
1372
1386
  value: string;
1373
1387
  autoComplete?: string;
1374
1388
  required?: boolean;
1389
+ disabled?: boolean;
1375
1390
  className?: string;
1376
1391
  'aria-invalid'?: boolean;
1377
1392
  'aria-describedby'?: string;
@@ -1529,8 +1544,11 @@ interface AcceptInviteProps extends AuthFormProps {
1529
1544
  }
1530
1545
  declare function AcceptInvite(props: AcceptInviteProps): react.JSX.Element;
1531
1546
  interface PendingTermsProps extends Omit<AuthFormProps, 'links' | 'returnTo' | 'createAccounts'> {
1532
- /** Called once the person accepted. Nothing else happens: the application decides what follows. */
1533
- onSuccess?: () => void;
1547
+ /**
1548
+ * Called once the person accepted. Nothing else happens: the application
1549
+ * decides what follows. A Promise keeps the button loading until it settles.
1550
+ */
1551
+ onSuccess?: () => void | Promise<unknown>;
1534
1552
  }
1535
1553
  /**
1536
1554
  * The legal documents the signed-in person still owes (a new version of the
@@ -1583,8 +1601,8 @@ type AccountEvent = {
1583
1601
  kind: 'sessions.ended';
1584
1602
  };
1585
1603
  interface AccountProps extends Omit<AuthFormProps, 'links' | 'returnTo' | 'onSuccess'> {
1586
- /** Called after "sign out everywhere". Default: go to `/`. */
1587
- onSignedOut?: () => void;
1604
+ /** Called after "sign out everywhere". Default: go to `/`. A Promise keeps the button loading until it settles. */
1605
+ onSignedOut?: () => void | Promise<unknown>;
1588
1606
  /** The moment of a session, as the application writes dates. Default: the browser's own way, in the reader's time zone. */
1589
1607
  formatDate?: (iso: string) => string;
1590
1608
  /**
package/dist/react.d.ts CHANGED
@@ -620,7 +620,11 @@ interface OrganizationsClient {
620
620
  members(organizationId: string): Promise<OrganizationPerson[] | null>;
621
621
  /** The roles a member may be given, or null when the person may not see them. */
622
622
  roles(organizationId: string): Promise<OrganizationRoleChoice[] | null>;
623
- /** Adds a person by their address; an address with no account gets one and its invitation. */
623
+ /**
624
+ * Adds a person by their address; an address with no account gets one and
625
+ * its invitation. It only adds: a person who already belongs keeps their
626
+ * role and the result is the error `member_exists` (change it with `setRole`).
627
+ */
624
628
  add(organizationId: string, form: {
625
629
  email: string;
626
630
  role: string;
@@ -1193,6 +1197,8 @@ interface AuthTexts {
1193
1197
  savePassword: string;
1194
1198
  passwordChanged: string;
1195
1199
  confirming: string;
1200
+ /** While a form waits on what the workspace offers, or on the account it shows. */
1201
+ loading: string;
1196
1202
  emailConfirmed: string;
1197
1203
  invitedTo: string;
1198
1204
  invitedToOrganization: string;
@@ -1279,7 +1285,7 @@ interface AuthTexts {
1279
1285
  /** By error code; one missing shows what the server said. */
1280
1286
  errors: Record<string, string>;
1281
1287
  }
1282
- type Slot = 'form' | 'field' | 'label' | 'input' | 'button' | 'providerButton' | 'providerIcon' | 'link' | 'error' | 'fieldError' | 'hint' | 'notice' | 'separator' | 'footer' | 'check' | 'account' | 'section' | 'heading' | 'secret' | 'codes' | 'sessions' | 'session';
1288
+ type Slot = 'form' | 'field' | 'label' | 'input' | 'button' | 'providerButton' | 'providerIcon' | 'link' | 'error' | 'fieldError' | 'hint' | 'notice' | 'pending' | 'separator' | 'footer' | 'check' | 'account' | 'section' | 'heading' | 'secret' | 'codes' | 'sessions' | 'session';
1283
1289
  interface AuthFormProps {
1284
1290
  /** The language of the built-in sentences. Default: `en`. */
1285
1291
  locale?: 'en' | 'es';
@@ -1287,8 +1293,14 @@ interface AuthFormProps {
1287
1293
  texts?: Partial<Omit<AuthTexts, 'errors'>> & {
1288
1294
  errors?: Record<string, string>;
1289
1295
  };
1290
- /** Called once the person is signed in. Default: go to `returnTo`. */
1291
- onSuccess?: () => void;
1296
+ /**
1297
+ * Called once the person is signed in, instead of going to `returnTo`. The
1298
+ * form stays busy (its button loading, every control disabled) until it
1299
+ * leaves the page. When the application keeps the form on screen while it
1300
+ * moves on (a router's navigation, loading the session), return a Promise:
1301
+ * the form waits for it, then is free again if it is still shown.
1302
+ */
1303
+ onSuccess?: () => void | Promise<unknown>;
1292
1304
  /** Where to go after signing in, and where a provider returns to. Default: `/`. */
1293
1305
  returnTo?: string;
1294
1306
  /**
@@ -1331,7 +1343,9 @@ interface AuthFormProps {
1331
1343
  * other action (a provider, a passkey, a sign-in link, cancel, send again);
1332
1344
  * `danger` removes, leaves, deletes, disconnects or turns something off.
1333
1345
  * `loading` is true on the one button whose action is under way; every
1334
- * button is `disabled` meanwhile, as before.
1346
+ * button is `disabled` meanwhile. Draw a spinner (or the like) on the button
1347
+ * while `loading` is true, and keep its label: after a sign-in it stays true
1348
+ * until the page is left, so the person sees the work go on.
1335
1349
  */
1336
1350
  interface AuthButtonProps {
1337
1351
  type: 'submit' | 'button';
@@ -1372,6 +1386,7 @@ interface AuthInputProps {
1372
1386
  value: string;
1373
1387
  autoComplete?: string;
1374
1388
  required?: boolean;
1389
+ disabled?: boolean;
1375
1390
  className?: string;
1376
1391
  'aria-invalid'?: boolean;
1377
1392
  'aria-describedby'?: string;
@@ -1529,8 +1544,11 @@ interface AcceptInviteProps extends AuthFormProps {
1529
1544
  }
1530
1545
  declare function AcceptInvite(props: AcceptInviteProps): react.JSX.Element;
1531
1546
  interface PendingTermsProps extends Omit<AuthFormProps, 'links' | 'returnTo' | 'createAccounts'> {
1532
- /** Called once the person accepted. Nothing else happens: the application decides what follows. */
1533
- onSuccess?: () => void;
1547
+ /**
1548
+ * Called once the person accepted. Nothing else happens: the application
1549
+ * decides what follows. A Promise keeps the button loading until it settles.
1550
+ */
1551
+ onSuccess?: () => void | Promise<unknown>;
1534
1552
  }
1535
1553
  /**
1536
1554
  * The legal documents the signed-in person still owes (a new version of the
@@ -1583,8 +1601,8 @@ type AccountEvent = {
1583
1601
  kind: 'sessions.ended';
1584
1602
  };
1585
1603
  interface AccountProps extends Omit<AuthFormProps, 'links' | 'returnTo' | 'onSuccess'> {
1586
- /** Called after "sign out everywhere". Default: go to `/`. */
1587
- onSignedOut?: () => void;
1604
+ /** Called after "sign out everywhere". Default: go to `/`. A Promise keeps the button loading until it settles. */
1605
+ onSignedOut?: () => void | Promise<unknown>;
1588
1606
  /** The moment of a session, as the application writes dates. Default: the browser's own way, in the reader's time zone. */
1589
1607
  formatDate?: (iso: string) => string;
1590
1608
  /**
package/dist/react.js CHANGED
@@ -823,6 +823,7 @@ var TEXTS = {
823
823
  savePassword: "Save the password",
824
824
  passwordChanged: "Your password was changed. You can sign in with it now.",
825
825
  confirming: "Confirming your address\u2026",
826
+ loading: "Loading\u2026",
826
827
  emailConfirmed: "Your address is confirmed. You can sign in now.",
827
828
  invitedTo: "You've been invited to {workspace}",
828
829
  invitedToOrganization: "You've been invited to {organization} on {workspace}",
@@ -910,6 +911,7 @@ var TEXTS = {
910
911
  last_manager: "Somebody else must manage the members first: give that role to another member.",
911
912
  last_owner: "An organization keeps at least one owner: make another member owner first.",
912
913
  organization_not_found: "That organization no longer exists.",
914
+ member_exists: "That person already belongs to the organization; their role was not changed. Change it in the list instead.",
913
915
  already_member: "That person already belongs to the organization. Change their role instead.",
914
916
  unknown_role: "Choose one of the roles offered.",
915
917
  user_not_active: "That account is disabled.",
@@ -1003,6 +1005,7 @@ var TEXTS = {
1003
1005
  savePassword: "Guardar la contrase\xF1a",
1004
1006
  passwordChanged: "Tu contrase\xF1a se cambi\xF3. Ya puedes entrar con ella.",
1005
1007
  confirming: "Confirmando tu correo\u2026",
1008
+ loading: "Cargando\u2026",
1006
1009
  emailConfirmed: "Tu correo qued\xF3 confirmado. Ya puedes entrar.",
1007
1010
  invitedTo: "Te invitaron a {workspace}",
1008
1011
  invitedToOrganization: "Te invitaron a {organization} en {workspace}",
@@ -1090,6 +1093,7 @@ var TEXTS = {
1090
1093
  last_manager: "Primero alguien m\xE1s debe administrar los miembros: dale ese rol a otro miembro.",
1091
1094
  last_owner: "Una organizaci\xF3n conserva al menos un propietario: primero haz propietario a otro miembro.",
1092
1095
  organization_not_found: "Esa organizaci\xF3n ya no existe.",
1096
+ member_exists: "Esa persona ya pertenece a la organizaci\xF3n; su rol no cambi\xF3. C\xE1mbialo en la lista.",
1093
1097
  already_member: "Esa persona ya pertenece a la organizaci\xF3n. Cambia su rol.",
1094
1098
  unknown_role: "Escoge uno de los roles ofrecidos.",
1095
1099
  user_not_active: "Esa cuenta est\xE1 desactivada.",
@@ -1134,7 +1138,14 @@ var TEXTS = {
1134
1138
  }
1135
1139
  }
1136
1140
  };
1137
- var PlainButton = ({ variant: _variant, loading: _loading, ...p }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { ...p });
1141
+ var PlainButton = ({ variant: _variant, loading, children, ...p }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { ...p, "aria-busy": loading || void 0, children: [
1142
+ loading && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "cauthy-spinner", "aria-hidden": "true" }),
1143
+ children
1144
+ ] });
1145
+ var UnstyledButton = ({ variant: _variant, loading, children, ...p }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { ...p, "aria-busy": loading || void 0, children: [
1146
+ children,
1147
+ loading && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", children: "\u2026" })
1148
+ ] });
1138
1149
  var PlainInput = (p) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { ...p });
1139
1150
  var looks = /* @__PURE__ */ new Map();
1140
1151
  var loaded = /* @__PURE__ */ new Set();
@@ -1242,12 +1253,15 @@ function sameOrigin(to) {
1242
1253
  return void 0;
1243
1254
  }
1244
1255
  }
1245
- function useForm(props) {
1256
+ function isPending(v) {
1257
+ return typeof v?.then === "function";
1258
+ }
1259
+ function useForm(props, opts = {}) {
1246
1260
  const t = { ...TEXTS[props.locale ?? "en"], ...props.texts, errors: { ...TEXTS[props.locale ?? "en"].errors, ...props.texts?.errors } };
1247
1261
  const cx = (slot) => props.classNames?.[slot] ?? `cauthy-${slot}`;
1248
1262
  const own = (slot) => props.classNames?.[slot];
1249
1263
  const C = props.components ?? {};
1250
- const Button = C.Button ?? PlainButton;
1264
+ const Button = C.Button ?? (props.unstyled ? UnstyledButton : PlainButton);
1251
1265
  const Input = C.Input ?? PlainInput;
1252
1266
  const [error, setOnlyError] = (0, import_react.useState)(null);
1253
1267
  const [problems, setProblems] = (0, import_react.useState)({ list: [], round: 0 });
@@ -1264,9 +1278,32 @@ function useForm(props) {
1264
1278
  const fieldError = (name) => problems.list.find((e) => e.field === name) ?? (error?.field === name ? error : null);
1265
1279
  const firstWrong = (name) => problems.list[0]?.field === name ? problems.round : 0;
1266
1280
  const [acting, setActing] = (0, import_react.useState)(null);
1267
- const busy = acting !== null;
1281
+ const busy = acting !== null || opts.waiting === true;
1268
1282
  const setBusy = (0, import_react.useCallback)((on) => setActing(on === false ? null : on === true ? SUBMIT : on), []);
1269
1283
  const loading = (key = SUBMIT) => acting === key;
1284
+ const shown = (0, import_react.useRef)(true);
1285
+ (0, import_react.useEffect)(() => {
1286
+ shown.current = true;
1287
+ const back = (e) => {
1288
+ if (e.persisted) setActing(null);
1289
+ };
1290
+ window.addEventListener("pageshow", back);
1291
+ return () => {
1292
+ shown.current = false;
1293
+ window.removeEventListener("pageshow", back);
1294
+ };
1295
+ }, []);
1296
+ const hold = (after) => {
1297
+ if (!isPending(after)) return;
1298
+ const free = () => {
1299
+ if (shown.current) setActing(null);
1300
+ };
1301
+ after.then(free, free);
1302
+ };
1303
+ const fail = (e) => {
1304
+ setActing(null);
1305
+ setError(e);
1306
+ };
1270
1307
  const [policy, setPolicy] = (0, import_react.useState)(void 0);
1271
1308
  const locale = props.locale ?? "en";
1272
1309
  const say = (e) => {
@@ -1275,15 +1312,18 @@ function useForm(props) {
1275
1312
  };
1276
1313
  const passwordRule = describePasswordPolicy(policy, locale);
1277
1314
  const done = (to) => {
1278
- if (props.onSuccess) props.onSuccess();
1315
+ setActing((a) => a ?? SUBMIT);
1316
+ if (props.onSuccess) hold(props.onSuccess());
1279
1317
  else window.location.assign(sitePath(to) ?? sameOrigin(props.returnTo) ?? "/");
1280
1318
  };
1281
- const root = useRoot(props);
1282
- return { t, cx, own, C, Button, Input, error, setError, check, fieldError, firstWrong, busy, setBusy, loading, say, done, root, policy, setPolicy, passwordRule };
1319
+ const root = useRoot(opts.part ? { ...props, unstyled: true } : props);
1320
+ const plain = props.unstyled === true;
1321
+ return { t, cx, own, C, Button, Input, error, setError, fail, check, fieldError, firstWrong, busy, setBusy, loading, hold, say, done, root, plain, policy, setPolicy, passwordRule };
1283
1322
  }
1284
1323
  var SUBMIT = "submit";
1285
1324
  function Wrap(p) {
1286
1325
  const { Form: OwnForm } = p.f.C;
1326
+ const busy = p.busy ?? (p.f.busy || void 0);
1287
1327
  if (OwnForm) {
1288
1328
  const own = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1289
1329
  OwnForm,
@@ -1291,7 +1331,7 @@ function Wrap(p) {
1291
1331
  as: p.as === "form" ? "form" : "div",
1292
1332
  onSubmit: p.onSubmit,
1293
1333
  noValidate: p.noValidate,
1294
- "aria-busy": p.busy,
1334
+ "aria-busy": busy,
1295
1335
  className: p.slot ? p.f.own(p.slot) : void 0,
1296
1336
  section: p.section,
1297
1337
  children: p.children
@@ -1300,7 +1340,16 @@ function Wrap(p) {
1300
1340
  return p.root?.["data-cauthy"] !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ...p.root, children: own }) : own;
1301
1341
  }
1302
1342
  const Tag2 = p.as;
1303
- return /* @__PURE__ */ (0, import_jsx_runtime.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 });
1343
+ const children = p.busy && p.children === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Pending, { f: p.f }) : p.children;
1344
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tag2, { className: p.slot ? p.f.cx(p.slot) : void 0, ...p.root, onSubmit: p.onSubmit, noValidate: p.noValidate, "aria-busy": busy, children });
1345
+ }
1346
+ function Pending({ f }) {
1347
+ if (f.plain) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "status", children: f.t.loading });
1348
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { role: "status", className: f.cx("pending"), children: [
1349
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "cauthy-spinner", "aria-hidden": "true" }),
1350
+ " ",
1351
+ f.t.loading
1352
+ ] });
1304
1353
  }
1305
1354
  function Note({ f, children, footer }) {
1306
1355
  const { Text } = f.C;
@@ -1341,10 +1390,10 @@ function Check(p) {
1341
1390
  const { f } = p;
1342
1391
  const { Checkbox } = f.C;
1343
1392
  if (Checkbox) {
1344
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Checkbox, { id, name: p.name, checked: p.checked, required: p.required, className: f.own("check"), onChange: p.onChange, children: p.children });
1393
+ return /* @__PURE__ */ (0, import_jsx_runtime.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 });
1345
1394
  }
1346
1395
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: f.cx("check"), children: [
1347
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "checkbox", name: p.name, checked: p.checked, required: p.required, onChange: (e) => p.onChange(e.target.checked) }),
1396
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "checkbox", name: p.name, checked: p.checked, required: p.required, disabled: f.busy, onChange: (e) => p.onChange(e.target.checked) }),
1348
1397
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: p.children })
1349
1398
  ] });
1350
1399
  }
@@ -1353,10 +1402,11 @@ function Pick(p) {
1353
1402
  const id = p.id ?? made;
1354
1403
  const { f } = p;
1355
1404
  const { Select } = f.C;
1405
+ const disabled = p.disabled || f.busy;
1356
1406
  if (Select) {
1357
- return /* @__PURE__ */ (0, import_jsx_runtime.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 });
1407
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Select, { id, name: p.name, value: p.value, options: p.options, disabled, "aria-label": p.label, className: f.own("input"), onChange: p.onChange });
1358
1408
  }
1359
- return /* @__PURE__ */ (0, import_jsx_runtime.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__ */ (0, import_jsx_runtime.jsx)("option", { value: o.value, children: o.label }, o.value)) });
1409
+ return /* @__PURE__ */ (0, import_jsx_runtime.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__ */ (0, import_jsx_runtime.jsx)("option", { value: o.value, children: o.label }, o.value)) });
1360
1410
  }
1361
1411
  function Labelled({ f, id, label, children }) {
1362
1412
  const { Field: OwnField } = f.C;
@@ -1410,6 +1460,7 @@ function Field(p) {
1410
1460
  value: p.value,
1411
1461
  autoComplete: p.autoComplete,
1412
1462
  required: p.required,
1463
+ disabled: f.busy,
1413
1464
  className: f.cx("input"),
1414
1465
  "aria-invalid": bad ? true : void 0,
1415
1466
  "aria-describedby": described || void 0,
@@ -1499,6 +1550,7 @@ function SecondStepForm(p) {
1499
1550
  if (ctl.signal.aborted) return;
1500
1551
  setPushNumber(0);
1501
1552
  if (!done.ok) return f.setError(done.error);
1553
+ f.setBusy("push");
1502
1554
  f.done();
1503
1555
  };
1504
1556
  const sendCode = async () => {
@@ -1514,8 +1566,7 @@ function SecondStepForm(p) {
1514
1566
  f.setBusy(true);
1515
1567
  f.setError(null);
1516
1568
  const r = await auth.signIn.mfa({ code });
1517
- f.setBusy(false);
1518
- if (!r.ok) return f.setError(r.error);
1569
+ if (!r.ok) return f.fail(r.error);
1519
1570
  f.done();
1520
1571
  };
1521
1572
  const general = f.error && !f.error.field ? f.error : null;
@@ -1543,7 +1594,11 @@ function SecondStepForm(p) {
1543
1594
  }
1544
1595
  function ProviderButtons(p) {
1545
1596
  const { f } = p;
1546
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: p.providers.map((provider) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(f.Button, { type: "button", variant: "secondary", className: f.cx("providerButton"), onClick: () => p.onPick(provider.id), children: [
1597
+ const pick = (id) => {
1598
+ f.setBusy(`provider:${id}`);
1599
+ p.onPick(id);
1600
+ };
1601
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: p.providers.map((provider) => /* @__PURE__ */ (0, import_jsx_runtime.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: [
1547
1602
  provider.iconUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: provider.iconUrl, alt: "", width: 18, height: 18, className: f.cx("providerIcon") }),
1548
1603
  f.t.continueWith,
1549
1604
  " ",
@@ -1556,8 +1611,8 @@ function signInOffers(methods) {
1556
1611
  }
1557
1612
  function SignIn(props) {
1558
1613
  const auth = useAuth();
1559
- const f = useForm(props);
1560
1614
  const [methods, setMethods] = (0, import_react.useState)(null);
1615
+ const f = useForm(props, { waiting: methods === null });
1561
1616
  const [email, setEmail] = (0, import_react.useState)("");
1562
1617
  const [password, setPassword] = (0, import_react.useState)("");
1563
1618
  const [code, setCode] = (0, import_react.useState)("");
@@ -1629,14 +1684,16 @@ function SignIn(props) {
1629
1684
  f.setBusy(true);
1630
1685
  f.setError(null);
1631
1686
  const r = step === "sent" ? await auth.signIn.emailCode({ code }) : await auth.signIn.email({ email, password, captchaToken: challenge.token });
1632
- f.setBusy(false);
1633
1687
  if (step === "password") challenge.renew();
1634
1688
  if (!r.ok && r.error.code === "sso_required") return toCompany();
1635
1689
  if (!r.ok) {
1636
1690
  setOffer((n) => n + 1);
1637
- return f.setError(r.error);
1691
+ return f.fail(r.error);
1692
+ }
1693
+ if (r.status === "mfa_required") {
1694
+ f.setBusy(false);
1695
+ return setStep("code");
1638
1696
  }
1639
- if (r.status === "mfa_required") return setStep("code");
1640
1697
  f.done("returnTo" in r && typeof r.returnTo === "string" ? r.returnTo : void 0);
1641
1698
  };
1642
1699
  const usePasskey = async () => {
@@ -1645,10 +1702,10 @@ function SignIn(props) {
1645
1702
  f.setError(null);
1646
1703
  const r = await auth.signIn.passkey({ returnTo: props.returnTo, locale: props.locale, email });
1647
1704
  if (r.ok && r.status === "redirected") return;
1705
+ if (r.ok) return f.done();
1648
1706
  f.setBusy(false);
1649
- if (!r.ok) setOffer((n) => n + 1);
1650
- if (!r.ok) return r.error.code === "cancelled" ? void 0 : f.setError(r.error);
1651
- f.done();
1707
+ setOffer((n) => n + 1);
1708
+ if (r.error.code !== "cancelled") f.setError(r.error);
1652
1709
  };
1653
1710
  const sendLink = async () => {
1654
1711
  autofill.current?.abort();
@@ -1765,10 +1822,12 @@ function SignUp(props) {
1765
1822
  ...legal ? { acceptLegal, consents } : {},
1766
1823
  captchaToken: challenge.token
1767
1824
  });
1768
- f.setBusy(false);
1769
1825
  challenge.renew();
1770
- if (!r.ok) return f.setError(r.error);
1771
- if (r.status === "verification_required") return setSent(true);
1826
+ if (!r.ok) return f.fail(r.error);
1827
+ if (r.status === "verification_required") {
1828
+ f.setBusy(false);
1829
+ return setSent(true);
1830
+ }
1772
1831
  f.done();
1773
1832
  };
1774
1833
  const general = f.error && !f.error.field ? f.error : null;
@@ -1931,13 +1990,16 @@ function AcceptInvite(props) {
1931
1990
  f.setBusy(true);
1932
1991
  f.setError(null);
1933
1992
  const r = await auth.acceptInvitation(token, password);
1934
- f.setBusy(false);
1935
1993
  if (!r.ok) {
1994
+ f.setBusy(false);
1936
1995
  if (r.error.code === "invalid_token") return setState("invalid");
1937
1996
  return f.setError(r.error);
1938
1997
  }
1939
1998
  props.onSaved?.({ kind: "invitation.accepted", email: invitation?.email ?? "", ...invitation?.organization ? { organization: invitation.organization } : {} });
1940
- if (r.status === "mfa_required") return setState("code");
1999
+ if (r.status === "mfa_required") {
2000
+ f.setBusy(false);
2001
+ return setState("code");
2002
+ }
1941
2003
  f.done();
1942
2004
  };
1943
2005
  const general = f.error && !f.error.field ? f.error : null;
@@ -1975,10 +2037,11 @@ function PendingTerms(props) {
1975
2037
  f.setBusy(true);
1976
2038
  f.setError(null);
1977
2039
  const r = await auth.account.acceptLegal({ kinds: owed.map((d) => d.kind) });
2040
+ if (!r.ok) return f.fail(r.error);
2041
+ const after = props.onSuccess?.();
2042
+ if (isPending(after)) await Promise.resolve(after).catch(() => void 0);
1978
2043
  f.setBusy(false);
1979
- if (!r.ok) return f.setError(r.error);
1980
2044
  setOwed([]);
1981
- props.onSuccess?.();
1982
2045
  };
1983
2046
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Wrap, { f, as: "div", slot: "form", root: f.root, children: [
1984
2047
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Heading, { f, children: f.t.termsChanged }),
@@ -1993,15 +2056,15 @@ function PendingTerms(props) {
1993
2056
  }
1994
2057
  var ACCOUNT_SECTIONS = ["profile", "email", "password", "linked", "secondStep", "phone", "passkeys", "agents", "sessions"];
1995
2058
  function useSection(props) {
1996
- const f = useForm({ ...props, unstyled: true });
2059
+ const f = useForm(props, { part: true });
1997
2060
  const [done, setDone] = (0, import_react.useState)(null);
1998
- const run = async (action, key = SUBMIT) => {
2061
+ const run = async (action, key = SUBMIT, leaves = false) => {
1999
2062
  f.setBusy(key);
2000
2063
  f.setError(null);
2001
2064
  setDone(null);
2002
2065
  const r = await action();
2003
- f.setBusy(false);
2004
- if (!r.ok) f.setError(r.error);
2066
+ if (!r.ok) f.fail(r.error);
2067
+ else if (!leaves) f.setBusy(false);
2005
2068
  return r;
2006
2069
  };
2007
2070
  const general = f.error && !f.error.field ? f.error : null;
@@ -2414,11 +2477,26 @@ function LinkedPart({ props }) {
2414
2477
  " ",
2415
2478
  /* @__PURE__ */ (0, import_jsx_runtime.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 })
2416
2479
  ] }, x.provider)) }),
2417
- offered.filter((p) => !linked.some((x) => x.provider === p.id)).map((p) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(f.Button, { type: "button", variant: "secondary", className: f.cx("providerButton"), onClick: () => auth.account.link(p.id, { returnTo: here }), children: [
2418
- f.t.linkWith,
2419
- " ",
2420
- p.name
2421
- ] }, p.id))
2480
+ offered.filter((p) => !linked.some((x) => x.provider === p.id)).map((p) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2481
+ f.Button,
2482
+ {
2483
+ type: "button",
2484
+ variant: "secondary",
2485
+ loading: f.loading(`link:${p.id}`),
2486
+ disabled: f.busy,
2487
+ className: f.cx("providerButton"),
2488
+ onClick: () => {
2489
+ f.setBusy(`link:${p.id}`);
2490
+ auth.account.link(p.id, { returnTo: here });
2491
+ },
2492
+ children: [
2493
+ f.t.linkWith,
2494
+ " ",
2495
+ p.name
2496
+ ]
2497
+ },
2498
+ p.id
2499
+ ))
2422
2500
  ] });
2423
2501
  }
2424
2502
  function PasswordPart({ props }) {
@@ -2576,7 +2654,7 @@ function PhonePart({ props, account, onChanged }) {
2576
2654
  !ways.phone && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { f, name: "phone", type: "tel", label: f.t.phone, value: number, autoComplete: "tel", required: true, onChange: setNumber }),
2577
2655
  !ways.phone && ways.phoneChannels.length > 1 && f.C.Select && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChannelPick, { f, value: channel, channels: ways.phoneChannels, onChange: setChannel }),
2578
2656
  !ways.phone && ways.phoneChannels.length > 1 && !f.C.Select && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: f.cx("field"), role: "radiogroup", children: ways.phoneChannels.map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: f.cx("label"), children: [
2579
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "radio", name: "channel", value: c, checked: channel === c, onChange: () => setChannel(c) }),
2657
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "radio", name: "channel", value: c, checked: channel === c, disabled: f.busy, onChange: () => setChannel(c) }),
2580
2658
  " ",
2581
2659
  c === "whatsapp" ? f.t.byWhatsapp : f.t.bySms
2582
2660
  ] }, c)) }),
@@ -2623,10 +2701,10 @@ function SessionsPart({ props, when }) {
2623
2701
  void auth.account.sessions().then(setSessions);
2624
2702
  }, []);
2625
2703
  const end = async () => {
2626
- const r = await run(() => auth.account.signOutEverywhere());
2704
+ const r = await run(() => auth.account.signOutEverywhere(), SUBMIT, true);
2627
2705
  if (!r.ok) return;
2628
2706
  props.onSaved?.({ kind: "sessions.ended" });
2629
- if (props.onSignedOut) props.onSignedOut();
2707
+ if (props.onSignedOut) f.hold(props.onSignedOut());
2630
2708
  else window.location.assign("/");
2631
2709
  };
2632
2710
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Wrap, { f, as: "section", slot: "section", section: "sessions", children: [