cauthy-auth 0.36.1 → 0.38.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
@@ -796,11 +796,19 @@ var TEXTS = {
796
796
  secondStepOff: "Off: signing in asks only for your password.",
797
797
  turnOn: "Turn on",
798
798
  turnOff: "Turn off",
799
- addToApp: "Add this key to your authenticator app, then enter the code it shows.",
799
+ addToApp: "Scan this code with your authenticator app, or add the key below by hand, then enter the code it shows.",
800
800
  openInApp: "Open in an authenticator app",
801
+ qrAlt: "QR code to add this account to an authenticator app",
801
802
  recoveryCodes: "Recovery codes",
802
803
  newRecoveryCodes: "New recovery codes",
803
804
  keepCodes: "Keep these codes somewhere safe. Each works once, and they are not shown again.",
805
+ copyCodes: "Copy all",
806
+ downloadCodes: "Download .txt",
807
+ codesCopied: "The codes were copied.",
808
+ codesNotCopied: "The codes could not be copied. Select them and copy them by hand.",
809
+ change: "Change",
810
+ passwordSet: "Your account has a password.",
811
+ addAnotherPasskey: "Add a passkey",
804
812
  passkeys: "Passkeys",
805
813
  managePasskeys: "Add or remove a passkey",
806
814
  passkeysHelp: "Sign in with your fingerprint, your face or your device's lock, without a password.",
@@ -980,11 +988,19 @@ var TEXTS = {
980
988
  secondStepOff: "Inactivo: al entrar solo se pide tu contrase\xF1a.",
981
989
  turnOn: "Activar",
982
990
  turnOff: "Desactivar",
983
- addToApp: "Agrega esta clave a tu aplicaci\xF3n de autenticaci\xF3n y escribe el c\xF3digo que muestra.",
991
+ addToApp: "Escanea este c\xF3digo con tu aplicaci\xF3n de autenticaci\xF3n, o agrega a mano la clave de abajo, y escribe el c\xF3digo que muestra.",
984
992
  openInApp: "Abrir en una aplicaci\xF3n de autenticaci\xF3n",
993
+ qrAlt: "C\xF3digo QR para agregar esta cuenta a una aplicaci\xF3n de autenticaci\xF3n",
985
994
  recoveryCodes: "C\xF3digos de recuperaci\xF3n",
986
995
  newRecoveryCodes: "Nuevos c\xF3digos de recuperaci\xF3n",
987
996
  keepCodes: "Guarda estos c\xF3digos en un lugar seguro. Cada uno sirve una vez y no se vuelven a mostrar.",
997
+ copyCodes: "Copiar todos",
998
+ downloadCodes: "Descargar .txt",
999
+ codesCopied: "Los c\xF3digos quedaron copiados.",
1000
+ codesNotCopied: "No se pudieron copiar los c\xF3digos. Selecci\xF3nalos y c\xF3pialos a mano.",
1001
+ change: "Cambiar",
1002
+ passwordSet: "Tu cuenta tiene contrase\xF1a.",
1003
+ addAnotherPasskey: "Agregar una llave de acceso",
988
1004
  passkeys: "Llaves de acceso",
989
1005
  managePasskeys: "Agregar o quitar una llave de acceso",
990
1006
  passkeysHelp: "Entra con tu huella, tu rostro o el bloqueo de tu dispositivo, sin contrase\xF1a.",
@@ -2160,6 +2176,8 @@ function OrganizationMembers(props) {
2160
2176
  const [role, setRole] = useState("");
2161
2177
  const [name, setName] = useState("");
2162
2178
  const [asking, setAsking] = useState(null);
2179
+ const [adding, setAdding] = useState(false);
2180
+ const [renamingOpen, setRenamingOpen] = useState(false);
2163
2181
  const [gone, setGone] = useState(null);
2164
2182
  const id = props.organizationId;
2165
2183
  const when = props.formatDate ?? ((iso) => new Date(iso).toLocaleDateString());
@@ -2194,6 +2212,7 @@ function OrganizationMembers(props) {
2194
2212
  const r = await run(() => auth.organizations.add(id, { email, role, locale: props.locale }), "add");
2195
2213
  if (!r.ok) return;
2196
2214
  setEmail("");
2215
+ setAdding(false);
2197
2216
  setDone(f.t.memberAdded.replace("{email}", r.member.email));
2198
2217
  saved({ kind: "member.added", organizationId: id, userId: r.member.userId, email: r.member.email, role: r.member.role || role });
2199
2218
  await load();
@@ -2224,6 +2243,7 @@ function OrganizationMembers(props) {
2224
2243
  if (!r.ok) return;
2225
2244
  setSeat(r.organization);
2226
2245
  setName(r.organization.name);
2246
+ setRenamingOpen(false);
2227
2247
  setDone(f.t.organizationRenamed.replace("{name}", r.organization.name));
2228
2248
  saved({ kind: "organization.renamed", organizationId: id, name: r.organization.name });
2229
2249
  };
@@ -2253,6 +2273,24 @@ function OrganizationMembers(props) {
2253
2273
  const archiving = seat.can.archive && !hidden("archive");
2254
2274
  const deleting = seat.can.delete && !hidden("delete");
2255
2275
  const renaming = seat.can.update && !hidden("rename");
2276
+ const alone = members !== null && members.every((m) => m.you);
2277
+ const addOpen = adding || alone;
2278
+ const open = (form) => {
2279
+ setDone(null);
2280
+ f.setError(null);
2281
+ if (form === "add") setAdding(true);
2282
+ else setRenamingOpen(true);
2283
+ };
2284
+ const cancelAdd = () => {
2285
+ setEmail("");
2286
+ f.setError(null);
2287
+ setAdding(false);
2288
+ };
2289
+ const cancelRename = () => {
2290
+ setName(seat.name);
2291
+ f.setError(null);
2292
+ setRenamingOpen(false);
2293
+ };
2256
2294
  return /* @__PURE__ */ jsxs(Wrap, { f, as: "div", slot: "account", root, children: [
2257
2295
  /* @__PURE__ */ jsxs(Wrap, { f, as: "section", slot: "section", section: "members", children: [
2258
2296
  /* @__PURE__ */ jsxs(Heading, { f, children: [
@@ -2316,18 +2354,23 @@ function OrganizationMembers(props) {
2316
2354
  }) }),
2317
2355
  done && /* @__PURE__ */ jsx(Say, { f, tone: "success", children: done })
2318
2356
  ] }),
2319
- manage && giveable.length > 0 && /* @__PURE__ */ jsxs(Wrap, { f, as: "form", slot: "section", section: "add-member", onSubmit: add, noValidate: true, children: [
2357
+ manage && giveable.length > 0 && (addOpen ? /* @__PURE__ */ jsxs(Wrap, { f, as: "form", slot: "section", section: "add-member", onSubmit: add, noValidate: true, children: [
2320
2358
  /* @__PURE__ */ jsx(Heading, { f, children: f.t.memberAdd }),
2321
2359
  /* @__PURE__ */ jsx(Field, { f, name: "email", type: "email", label: f.t.email, value: email, autoComplete: "off", required: true, onChange: setEmail }),
2322
2360
  /* @__PURE__ */ jsx(AddRole, { f, value: role, options: giveable.map((c) => ({ value: c.role, label: roleName(c) })), onChange: setRole }),
2323
- /* @__PURE__ */ jsx(f.Button, { type: "submit", variant: "primary", loading: f.loading("add"), disabled: f.busy, className: f.cx("button"), children: f.t.memberAdd })
2324
- ] }),
2361
+ /* @__PURE__ */ jsx(f.Button, { type: "submit", variant: "primary", loading: f.loading("add"), disabled: f.busy, className: f.cx("button"), children: f.t.memberAdd }),
2362
+ !alone && /* @__PURE__ */ jsx(Cancel, { f, onClick: cancelAdd })
2363
+ ] }) : /* @__PURE__ */ jsx(Wrap, { f, as: "div", slot: "section", section: "add-member", children: /* @__PURE__ */ jsx(f.Button, { type: "button", variant: "secondary", disabled: f.busy, className: f.cx("providerButton"), onClick: () => open("add"), children: f.t.memberAdd }) })),
2325
2364
  (renaming || archiving || deleting) && /* @__PURE__ */ jsxs(Wrap, { f, as: "section", slot: "section", section: "organization", children: [
2326
2365
  /* @__PURE__ */ jsx(Heading, { f, children: f.t.organization }),
2327
- renaming && /* @__PURE__ */ jsxs(Wrap, { f, as: "form", section: "rename", onSubmit: rename, noValidate: true, children: [
2366
+ renaming && (renamingOpen ? /* @__PURE__ */ jsxs(Wrap, { f, as: "form", section: "rename", onSubmit: rename, noValidate: true, children: [
2328
2367
  /* @__PURE__ */ jsx(Field, { f, name: "name", type: "text", label: f.t.name, value: name, autoComplete: "organization", required: true, onChange: setName }),
2329
- /* @__PURE__ */ jsx(f.Button, { type: "submit", variant: "primary", loading: f.loading("rename"), disabled: f.busy, className: f.cx("button"), children: f.t.organizationRename })
2330
- ] }),
2368
+ /* @__PURE__ */ jsx(f.Button, { type: "submit", variant: "primary", loading: f.loading("rename"), disabled: f.busy, className: f.cx("button"), children: f.t.organizationRename }),
2369
+ /* @__PURE__ */ jsx(Cancel, { f, onClick: cancelRename })
2370
+ ] }) : /* @__PURE__ */ jsxs(Fragment2, { children: [
2371
+ /* @__PURE__ */ jsx(Note, { f, children: seat.name }),
2372
+ /* @__PURE__ */ jsx(f.Button, { type: "button", variant: "secondary", disabled: f.busy, className: f.cx("providerButton"), onClick: () => open("rename"), children: f.t.organizationRename })
2373
+ ] })),
2331
2374
  asking === "archive" ? confirming(archiveQuestion, f.t.organizationArchive, () => void close("archive")) : asking === "delete" ? confirming(deleteQuestion, f.t.organizationDelete, () => void close("delete")) : /* @__PURE__ */ jsxs(Fragment2, { children: [
2332
2375
  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 }),
2333
2376
  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 })
@@ -2346,6 +2389,7 @@ function PasskeysPart({ props, account, accountUrl, when }) {
2346
2389
  const [list, setList] = useState([]);
2347
2390
  const [name, setName] = useState("");
2348
2391
  const [password, setPassword] = useState("");
2392
+ const [adding, setAdding] = useState(false);
2349
2393
  useEffect(() => {
2350
2394
  void auth.methods().then((m) => {
2351
2395
  const can2 = m.passkeyHere && canUsePasskeys();
@@ -2371,6 +2415,7 @@ function PasskeysPart({ props, account, accountUrl, when }) {
2371
2415
  setList([...list, r.passkey]);
2372
2416
  setName("");
2373
2417
  setPassword("");
2418
+ setAdding(false);
2374
2419
  setDone(f.t.passkeyAdded);
2375
2420
  props.onSaved?.({ kind: "passkey.added", id: r.passkey.id });
2376
2421
  };
@@ -2380,43 +2425,123 @@ function PasskeysPart({ props, account, accountUrl, when }) {
2380
2425
  setList(list.filter((p) => p.id !== id));
2381
2426
  props.onSaved?.({ kind: "passkey.removed", id });
2382
2427
  };
2428
+ const cancel = () => {
2429
+ setName("");
2430
+ setPassword("");
2431
+ f.setError(null);
2432
+ setAdding(false);
2433
+ };
2434
+ const listed = list.length === 0 ? /* @__PURE__ */ jsx(Note, { f, children: f.t.noPasskeys }) : /* @__PURE__ */ jsx("ul", { className: f.cx("sessions"), children: list.map((p) => /* @__PURE__ */ jsxs("li", { className: f.cx("session"), children: [
2435
+ p.name || f.t.passkeyUnnamed,
2436
+ p.kind !== "unknown" && /* @__PURE__ */ jsx(Tag, { f, plain: ` \xB7 ${f.t.passkeyKinds[p.kind]}`, children: f.t.passkeyKinds[p.kind] }),
2437
+ " \xB7 ",
2438
+ when(p.createdAt),
2439
+ " ",
2440
+ /* @__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 })
2441
+ ] }, p.id)) });
2442
+ if (list.length > 0 && !adding) {
2443
+ return /* @__PURE__ */ jsxs(Wrap, { f, as: "div", slot: "section", section: "passkeys", children: [
2444
+ /* @__PURE__ */ jsx(Heading, { f, children: f.t.passkeys }),
2445
+ /* @__PURE__ */ jsx(Note, { f, children: f.t.passkeysHelp }),
2446
+ general && /* @__PURE__ */ jsx(Say, { f, tone: "danger", children: f.say(general) }),
2447
+ listed,
2448
+ /* @__PURE__ */ jsx(f.Button, { type: "button", variant: "secondary", disabled: f.busy, className: f.cx("providerButton"), onClick: () => {
2449
+ setDone(null);
2450
+ f.setError(null);
2451
+ setAdding(true);
2452
+ }, children: f.t.addAnotherPasskey }),
2453
+ done && /* @__PURE__ */ jsx(Say, { f, tone: "success", children: done })
2454
+ ] });
2455
+ }
2383
2456
  return /* @__PURE__ */ jsxs(Wrap, { f, as: "form", slot: "section", section: "passkeys", onSubmit: add, noValidate: true, children: [
2384
2457
  /* @__PURE__ */ jsx(Heading, { f, children: f.t.passkeys }),
2385
2458
  /* @__PURE__ */ jsx(Note, { f, children: f.t.passkeysHelp }),
2386
2459
  general && /* @__PURE__ */ jsx(Say, { f, tone: "danger", children: f.say(general) }),
2387
- list.length === 0 ? /* @__PURE__ */ jsx(Note, { f, children: f.t.noPasskeys }) : /* @__PURE__ */ jsx("ul", { className: f.cx("sessions"), children: list.map((p) => /* @__PURE__ */ jsxs("li", { className: f.cx("session"), children: [
2388
- p.name || f.t.passkeyUnnamed,
2389
- p.kind !== "unknown" && /* @__PURE__ */ jsx(Tag, { f, plain: ` \xB7 ${f.t.passkeyKinds[p.kind]}`, children: f.t.passkeyKinds[p.kind] }),
2390
- " \xB7 ",
2391
- when(p.createdAt),
2392
- " ",
2393
- /* @__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 })
2394
- ] }, p.id)) }),
2460
+ listed,
2395
2461
  /* @__PURE__ */ jsx(Field, { f, name: "name", type: "text", label: f.t.passkeyName, value: name, autoComplete: "off", onChange: setName }),
2396
2462
  account.hasPassword && /* @__PURE__ */ jsx(Field, { f, name: "password", type: "password", label: f.t.currentPassword, value: password, autoComplete: "current-password", required: true, onChange: setPassword }),
2397
2463
  /* @__PURE__ */ jsx(f.Button, { type: "submit", variant: "primary", loading: f.loading(), disabled: f.busy, className: f.cx("button"), children: f.t.addPasskeyDevice }),
2398
2464
  /* @__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 }),
2465
+ list.length > 0 && /* @__PURE__ */ jsx(Cancel, { f, onClick: cancel }),
2399
2466
  done && /* @__PURE__ */ jsx(Say, { f, tone: "success", children: done })
2400
2467
  ] });
2401
2468
  }
2469
+ function Cancel({ f, onClick }) {
2470
+ return /* @__PURE__ */ jsx(f.Button, { type: "button", variant: "secondary", disabled: f.busy, className: f.cx("providerButton"), onClick, children: f.t.cancel });
2471
+ }
2472
+ function RecoveryCodes({ f, codes }) {
2473
+ const [copied, setCopied] = useState(null);
2474
+ useEffect(() => setCopied(null), [codes]);
2475
+ const copy = async () => {
2476
+ try {
2477
+ await navigator.clipboard.writeText(codes.join("\n"));
2478
+ setCopied(true);
2479
+ } catch {
2480
+ setCopied(false);
2481
+ }
2482
+ };
2483
+ const download = () => {
2484
+ const file = new Blob([`${f.t.recoveryCodes}
2485
+
2486
+ ${codes.join("\n")}
2487
+ `], { type: "text/plain;charset=utf-8" });
2488
+ const url = URL.createObjectURL(file);
2489
+ const a = document.createElement("a");
2490
+ a.href = url;
2491
+ a.download = "recovery-codes.txt";
2492
+ document.body.appendChild(a);
2493
+ a.click();
2494
+ a.remove();
2495
+ setTimeout(() => URL.revokeObjectURL(url), 0);
2496
+ };
2497
+ return /* @__PURE__ */ jsxs(Fragment2, { children: [
2498
+ /* @__PURE__ */ jsx(Note, { f, children: f.t.keepCodes }),
2499
+ /* @__PURE__ */ jsx("ul", { className: f.cx("codes"), children: codes.map((c) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("code", { children: c }) }, c)) }),
2500
+ /* @__PURE__ */ jsx(f.Button, { type: "button", variant: "secondary", className: f.cx("providerButton"), onClick: () => void copy(), children: f.t.copyCodes }),
2501
+ /* @__PURE__ */ jsx(f.Button, { type: "button", variant: "secondary", className: f.cx("providerButton"), onClick: download, children: f.t.downloadCodes }),
2502
+ copied === true && /* @__PURE__ */ jsx(Say, { f, tone: "success", children: f.t.codesCopied }),
2503
+ copied === false && /* @__PURE__ */ jsx(Say, { f, tone: "danger", children: f.t.codesNotCopied })
2504
+ ] });
2505
+ }
2402
2506
  function ProfilePart({ props, account, onSaved }) {
2403
2507
  const auth = useAuth();
2404
2508
  const { f, done, setDone, run, general } = useSection(props);
2405
2509
  const [name, setName] = useState(account.name);
2510
+ const [editing, setEditing] = useState(account.name.trim() === "");
2406
2511
  const submit = async (e) => {
2407
2512
  e.preventDefault();
2408
2513
  const r = await run(() => auth.account.update({ name }));
2409
2514
  if (!r.ok) return;
2410
2515
  onSaved(r.account);
2516
+ setName(r.account.name);
2517
+ setEditing(r.account.name.trim() === "");
2411
2518
  setDone(f.t.saved);
2412
2519
  props.onSaved?.({ kind: "profile.saved", name: r.account.name });
2413
2520
  };
2521
+ const cancel = () => {
2522
+ setName(account.name);
2523
+ f.setError(null);
2524
+ setEditing(false);
2525
+ };
2526
+ if (!editing) {
2527
+ return /* @__PURE__ */ jsxs(Wrap, { f, as: "div", slot: "section", section: "profile", children: [
2528
+ /* @__PURE__ */ jsx(Heading, { f, children: f.t.profile }),
2529
+ /* @__PURE__ */ jsx(Note, { f, children: account.name }),
2530
+ /* @__PURE__ */ jsx(Note, { f, children: account.email }),
2531
+ /* @__PURE__ */ jsx(f.Button, { type: "button", variant: "secondary", disabled: f.busy, className: f.cx("providerButton"), onClick: () => {
2532
+ setDone(null);
2533
+ setEditing(true);
2534
+ }, children: f.t.change }),
2535
+ done && /* @__PURE__ */ jsx(Say, { f, tone: "success", children: done })
2536
+ ] });
2537
+ }
2414
2538
  return /* @__PURE__ */ jsxs(Wrap, { f, as: "form", slot: "section", section: "profile", onSubmit: submit, noValidate: true, children: [
2415
2539
  /* @__PURE__ */ jsx(Heading, { f, children: f.t.profile }),
2416
2540
  /* @__PURE__ */ jsx(Note, { f, children: account.email }),
2417
2541
  general && /* @__PURE__ */ jsx(Say, { f, tone: "danger", children: f.say(general) }),
2418
2542
  /* @__PURE__ */ jsx(Field, { f, name: "name", type: "text", label: f.t.name, value: name, autoComplete: "name", onChange: setName }),
2419
2543
  /* @__PURE__ */ jsx(f.Button, { type: "submit", variant: "primary", loading: f.loading(), disabled: f.busy, className: f.cx("button"), children: f.t.save }),
2544
+ account.name.trim() !== "" && /* @__PURE__ */ jsx(Cancel, { f, onClick: cancel }),
2420
2545
  done && /* @__PURE__ */ jsx(Say, { f, tone: "success", children: done })
2421
2546
  ] });
2422
2547
  }
@@ -2425,15 +2550,35 @@ function EmailPart({ props, account }) {
2425
2550
  const { f, done, setDone, run, general } = useSection(props);
2426
2551
  const [email, setEmail] = useState("");
2427
2552
  const [password, setPassword] = useState("");
2553
+ const [editing, setEditing] = useState(false);
2428
2554
  const submit = async (e) => {
2429
2555
  e.preventDefault();
2430
2556
  if (!f.check(checkEmail(email), account.hasPassword && checkPassword(password))) return;
2431
2557
  const r = await run(() => auth.account.changeEmail({ email, password }));
2432
2558
  if (!r.ok) return;
2559
+ setEmail("");
2433
2560
  setPassword("");
2561
+ setEditing(false);
2434
2562
  setDone(f.t.emailSent.replace("{email}", r.email));
2435
2563
  props.onSaved?.({ kind: "email.change_requested", email: r.email });
2436
2564
  };
2565
+ const cancel = () => {
2566
+ setEmail("");
2567
+ setPassword("");
2568
+ f.setError(null);
2569
+ setEditing(false);
2570
+ };
2571
+ if (!editing) {
2572
+ return /* @__PURE__ */ jsxs(Wrap, { f, as: "div", slot: "section", section: "email", children: [
2573
+ /* @__PURE__ */ jsx(Heading, { f, children: f.t.emailAddress }),
2574
+ /* @__PURE__ */ jsx(Note, { f, children: account.email }),
2575
+ /* @__PURE__ */ jsx(f.Button, { type: "button", variant: "secondary", disabled: f.busy, className: f.cx("providerButton"), onClick: () => {
2576
+ setDone(null);
2577
+ setEditing(true);
2578
+ }, children: f.t.changeEmail }),
2579
+ done && /* @__PURE__ */ jsx(Say, { f, tone: "success", children: done })
2580
+ ] });
2581
+ }
2437
2582
  return /* @__PURE__ */ jsxs(Wrap, { f, as: "form", slot: "section", section: "email", onSubmit: submit, noValidate: true, children: [
2438
2583
  /* @__PURE__ */ jsx(Heading, { f, children: f.t.emailAddress }),
2439
2584
  /* @__PURE__ */ jsx(Note, { f, children: account.email }),
@@ -2441,7 +2586,7 @@ function EmailPart({ props, account }) {
2441
2586
  /* @__PURE__ */ jsx(Field, { f, name: "email", type: "email", label: f.t.newEmail, value: email, autoComplete: "email", required: true, onChange: setEmail }),
2442
2587
  account.hasPassword && /* @__PURE__ */ jsx(Field, { f, name: "password", type: "password", label: f.t.currentPassword, value: password, autoComplete: "current-password", required: true, onChange: setPassword }),
2443
2588
  /* @__PURE__ */ jsx(f.Button, { type: "submit", variant: "primary", loading: f.loading(), disabled: f.busy, className: f.cx("button"), children: f.t.changeEmail }),
2444
- done && /* @__PURE__ */ jsx(Say, { f, tone: "success", children: done })
2589
+ /* @__PURE__ */ jsx(Cancel, { f, onClick: cancel })
2445
2590
  ] });
2446
2591
  }
2447
2592
  function LinkedPart({ props }) {
@@ -2506,6 +2651,7 @@ function PasswordPart({ props }) {
2506
2651
  const { f, done, setDone, run, general } = useSection(props);
2507
2652
  const [password, setPassword] = useState("");
2508
2653
  const [newPassword, setNewPassword] = useState("");
2654
+ const [editing, setEditing] = useState(false);
2509
2655
  usePasswordPolicy(f, useMethods(auth));
2510
2656
  const submit = async (e) => {
2511
2657
  e.preventDefault();
@@ -2514,16 +2660,34 @@ function PasswordPart({ props }) {
2514
2660
  if (!r.ok) return;
2515
2661
  setPassword("");
2516
2662
  setNewPassword("");
2663
+ setEditing(false);
2517
2664
  setDone(f.t.passwordChangedHere);
2518
2665
  props.onSaved?.({ kind: "password.changed" });
2519
2666
  };
2667
+ const cancel = () => {
2668
+ setPassword("");
2669
+ setNewPassword("");
2670
+ f.setError(null);
2671
+ setEditing(false);
2672
+ };
2673
+ if (!editing) {
2674
+ return /* @__PURE__ */ jsxs(Wrap, { f, as: "div", slot: "section", section: "password", children: [
2675
+ /* @__PURE__ */ jsx(Heading, { f, children: f.t.password }),
2676
+ /* @__PURE__ */ jsx(Note, { f, children: f.t.passwordSet }),
2677
+ /* @__PURE__ */ jsx(f.Button, { type: "button", variant: "secondary", disabled: f.busy, className: f.cx("providerButton"), onClick: () => {
2678
+ setDone(null);
2679
+ setEditing(true);
2680
+ }, children: f.t.changePassword }),
2681
+ done && /* @__PURE__ */ jsx(Say, { f, tone: "success", children: done })
2682
+ ] });
2683
+ }
2520
2684
  return /* @__PURE__ */ jsxs(Wrap, { f, as: "form", slot: "section", section: "password", onSubmit: submit, noValidate: true, children: [
2521
- /* @__PURE__ */ jsx(Heading, { f, children: f.t.changePassword }),
2685
+ /* @__PURE__ */ jsx(Heading, { f, children: f.t.password }),
2522
2686
  general && /* @__PURE__ */ jsx(Say, { f, tone: "danger", children: f.say(general) }),
2523
2687
  /* @__PURE__ */ jsx(Field, { f, name: "password", type: "password", label: f.t.currentPassword, value: password, autoComplete: "current-password", required: true, onChange: setPassword }),
2524
2688
  /* @__PURE__ */ jsx(Field, { f, name: "newPassword", type: "password", label: f.t.newPassword, value: newPassword, autoComplete: "new-password", required: true, onChange: setNewPassword, hint: f.passwordRule }),
2525
2689
  /* @__PURE__ */ jsx(f.Button, { type: "submit", variant: "primary", loading: f.loading(), disabled: f.busy, className: f.cx("button"), children: f.t.changePassword }),
2526
- done && /* @__PURE__ */ jsx(Say, { f, tone: "success", children: done })
2690
+ /* @__PURE__ */ jsx(Cancel, { f, onClick: cancel })
2527
2691
  ] });
2528
2692
  }
2529
2693
  function SecondStepPart({ props, account, onChanged }) {
@@ -2533,10 +2697,11 @@ function SecondStepPart({ props, account, onChanged }) {
2533
2697
  const [code, setCode] = useState("");
2534
2698
  const [setup, setSetup] = useState(null);
2535
2699
  const [codes, setCodes] = useState(null);
2700
+ const [asking, setAsking] = useState(null);
2536
2701
  const start = async (e) => {
2537
2702
  e.preventDefault();
2538
2703
  const r = await run(() => auth.account.mfa.setup({ password }));
2539
- if (r.ok) setSetup({ secret: r.secret, otpauthUri: r.otpauthUri });
2704
+ if (r.ok) setSetup({ secret: r.secret, otpauthUri: r.otpauthUri, qr: r.qr ?? "" });
2540
2705
  };
2541
2706
  const confirm = async (e) => {
2542
2707
  e.preventDefault();
@@ -2549,30 +2714,41 @@ function SecondStepPart({ props, account, onChanged }) {
2549
2714
  onChanged(true);
2550
2715
  props.onSaved?.({ kind: "mfa.enabled" });
2551
2716
  };
2552
- const turnOff = async (e) => {
2553
- e.preventDefault();
2554
- const r = await run(() => auth.account.mfa.disable({ password }));
2717
+ const turnOff = async (key) => {
2718
+ const r = await run(() => auth.account.mfa.disable({ password }), key);
2555
2719
  if (!r.ok) return;
2556
2720
  setPassword("");
2721
+ setAsking(null);
2557
2722
  setCodes(null);
2558
2723
  onChanged(false);
2559
2724
  props.onSaved?.({ kind: "mfa.disabled" });
2560
2725
  };
2561
- const renew = async () => {
2562
- const r = await run(() => auth.account.mfa.recoveryCodes({ password }), "codes");
2726
+ const renew = async (key) => {
2727
+ const r = await run(() => auth.account.mfa.recoveryCodes({ password }), key);
2563
2728
  if (!r.ok) return;
2729
+ setPassword("");
2730
+ setAsking(null);
2564
2731
  setCodes(r.recoveryCodes);
2565
2732
  props.onSaved?.({ kind: "recovery_codes.regenerated" });
2566
2733
  };
2734
+ const press = (what) => {
2735
+ f.setError(null);
2736
+ if (!account.hasPassword) return void (what === "off" ? turnOff(what) : renew(what));
2737
+ setPassword("");
2738
+ setCodes(null);
2739
+ setAsking(what);
2740
+ };
2741
+ const cancel = () => {
2742
+ setPassword("");
2743
+ f.setError(null);
2744
+ setAsking(null);
2745
+ };
2567
2746
  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 });
2568
- const shown = codes && /* @__PURE__ */ jsxs(Fragment2, { children: [
2569
- /* @__PURE__ */ jsx(Note, { f, children: f.t.keepCodes }),
2570
- /* @__PURE__ */ jsx("ul", { className: f.cx("codes"), children: codes.map((c) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("code", { children: c }) }, c)) })
2571
- ] });
2572
2747
  if (setup) {
2573
2748
  return /* @__PURE__ */ jsxs(Wrap, { f, as: "form", slot: "section", section: "secondStep", onSubmit: confirm, noValidate: true, children: [
2574
2749
  /* @__PURE__ */ jsx(Heading, { f, children: f.t.secondStep }),
2575
2750
  /* @__PURE__ */ jsx(Note, { f, children: f.t.addToApp }),
2751
+ setup.qr && /* @__PURE__ */ jsx("img", { className: f.cx("qr"), src: setup.qr, width: 220, height: 220, alt: f.t.qrAlt }),
2576
2752
  /* @__PURE__ */ jsx("code", { className: f.cx("secret"), children: setup.secret }),
2577
2753
  /* @__PURE__ */ jsx(A, { f, href: setup.otpauthUri, children: f.t.openInApp }),
2578
2754
  general && /* @__PURE__ */ jsx(Say, { f, tone: "danger", children: f.say(general) }),
@@ -2580,14 +2756,36 @@ function SecondStepPart({ props, account, onChanged }) {
2580
2756
  /* @__PURE__ */ jsx(f.Button, { type: "submit", variant: "primary", loading: f.loading(), disabled: f.busy, className: f.cx("button"), children: f.t.verify })
2581
2757
  ] });
2582
2758
  }
2583
- return /* @__PURE__ */ jsxs(Wrap, { f, as: "form", slot: "section", section: "secondStep", onSubmit: account.mfaEnabled ? turnOff : start, noValidate: true, children: [
2759
+ if (!account.mfaEnabled) {
2760
+ return /* @__PURE__ */ jsxs(Wrap, { f, as: "form", slot: "section", section: "secondStep", onSubmit: start, noValidate: true, children: [
2761
+ /* @__PURE__ */ jsx(Heading, { f, children: f.t.secondStep }),
2762
+ /* @__PURE__ */ jsx(Note, { f, children: f.t.secondStepOff }),
2763
+ general && /* @__PURE__ */ jsx(Say, { f, tone: "danger", children: f.say(general) }),
2764
+ passwordField,
2765
+ /* @__PURE__ */ jsx(f.Button, { type: "submit", variant: "primary", loading: f.loading(), disabled: f.busy, className: f.cx("button"), children: f.t.turnOn })
2766
+ ] });
2767
+ }
2768
+ if (asking) {
2769
+ const submit = (e) => {
2770
+ e.preventDefault();
2771
+ void (asking === "off" ? turnOff(SUBMIT) : renew(SUBMIT));
2772
+ };
2773
+ return /* @__PURE__ */ jsxs(Wrap, { f, as: "form", slot: "section", section: "secondStep", onSubmit: submit, noValidate: true, children: [
2774
+ /* @__PURE__ */ jsx(Heading, { f, children: f.t.secondStep }),
2775
+ /* @__PURE__ */ jsx(Note, { f, children: f.t.secondStepOn }),
2776
+ general && /* @__PURE__ */ jsx(Say, { f, tone: "danger", children: f.say(general) }),
2777
+ passwordField,
2778
+ /* @__PURE__ */ jsx(f.Button, { type: "submit", variant: asking === "off" ? "danger" : "primary", loading: f.loading(), disabled: f.busy, className: f.cx("button"), children: asking === "off" ? f.t.turnOff : f.t.newRecoveryCodes }),
2779
+ /* @__PURE__ */ jsx(Cancel, { f, onClick: cancel })
2780
+ ] });
2781
+ }
2782
+ return /* @__PURE__ */ jsxs(Wrap, { f, as: "div", slot: "section", section: "secondStep", children: [
2584
2783
  /* @__PURE__ */ jsx(Heading, { f, children: f.t.secondStep }),
2585
- /* @__PURE__ */ jsx(Note, { f, children: account.mfaEnabled ? f.t.secondStepOn : f.t.secondStepOff }),
2586
- shown,
2784
+ /* @__PURE__ */ jsx(Note, { f, children: f.t.secondStepOn }),
2785
+ codes && /* @__PURE__ */ jsx(RecoveryCodes, { f, codes }),
2587
2786
  general && /* @__PURE__ */ jsx(Say, { f, tone: "danger", children: f.say(general) }),
2588
- passwordField,
2589
- /* @__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 }),
2590
- 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 })
2787
+ /* @__PURE__ */ jsx(f.Button, { type: "button", variant: "danger", loading: f.loading("off"), disabled: f.busy, className: f.cx("button"), onClick: () => press("off"), children: f.t.turnOff }),
2788
+ /* @__PURE__ */ jsx(f.Button, { type: "button", variant: "secondary", loading: f.loading("codes"), disabled: f.busy, className: f.cx("providerButton"), onClick: () => press("codes"), children: f.t.newRecoveryCodes })
2591
2789
  ] });
2592
2790
  }
2593
2791
  function PhonePart({ props, account, onChanged }) {
@@ -2600,6 +2798,7 @@ function PhonePart({ props, account, onChanged }) {
2600
2798
  const [code, setCode] = useState("");
2601
2799
  const [sentTo, setSentTo] = useState("");
2602
2800
  const [codes, setCodes] = useState(null);
2801
+ const [removing, setRemoving] = useState(false);
2603
2802
  const load = () => auth.account.mfa.factors().then((w) => {
2604
2803
  setWays(w);
2605
2804
  if (w && w.phoneChannels.length > 0 && !w.phoneChannels.includes(channel)) setChannel(w.phoneChannels[0]);
@@ -2620,21 +2819,34 @@ function PhonePart({ props, account, onChanged }) {
2620
2819
  setSentTo("");
2621
2820
  setCode("");
2622
2821
  setPassword("");
2822
+ setNumber("");
2623
2823
  if (r.recoveryCodes.length > 0) setCodes(r.recoveryCodes);
2624
2824
  onChanged(true);
2625
2825
  props.onSaved?.({ kind: "phone.added", channel });
2626
2826
  await load();
2627
2827
  };
2628
- const remove = async (e) => {
2629
- e.preventDefault();
2630
- const r = await run(() => auth.account.mfa.removePhone({ password }));
2828
+ const remove = async (key) => {
2829
+ const r = await run(() => auth.account.mfa.removePhone({ password }), key);
2631
2830
  if (!r.ok) return;
2632
2831
  setPassword("");
2832
+ setRemoving(false);
2833
+ setCodes(null);
2633
2834
  await load();
2634
2835
  const left = await auth.account.mfa.factors();
2635
2836
  onChanged(Boolean(left?.totp));
2636
2837
  props.onSaved?.({ kind: "phone.removed" });
2637
2838
  };
2839
+ const press = () => {
2840
+ f.setError(null);
2841
+ if (!account.hasPassword) return void remove("remove");
2842
+ setPassword("");
2843
+ setRemoving(true);
2844
+ };
2845
+ const cancel = () => {
2846
+ setPassword("");
2847
+ f.setError(null);
2848
+ setRemoving(false);
2849
+ };
2638
2850
  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 });
2639
2851
  if (sentTo) {
2640
2852
  return /* @__PURE__ */ jsxs(Wrap, { f, as: "form", slot: "section", section: "phone", onSubmit: confirm, noValidate: true, children: [
@@ -2645,23 +2857,46 @@ function PhonePart({ props, account, onChanged }) {
2645
2857
  /* @__PURE__ */ jsx(f.Button, { type: "submit", variant: "primary", loading: f.loading(), disabled: f.busy, className: f.cx("button"), children: f.t.verify })
2646
2858
  ] });
2647
2859
  }
2648
- return /* @__PURE__ */ jsxs(Wrap, { f, as: "form", slot: "section", section: "phone", onSubmit: ways.phone ? remove : add, noValidate: true, children: [
2860
+ if (ways.phone) {
2861
+ const by = ways.phone.channel === "whatsapp" ? f.t.byWhatsapp : ways.phone.channel === "sms" ? f.t.bySms : "";
2862
+ const on = /* @__PURE__ */ jsxs(Note, { f, children: [
2863
+ f.t.phoneOn.replace("{to}", ways.phone.number),
2864
+ by && /* @__PURE__ */ jsx(Tag, { f, plain: ` \xB7 ${by}`, children: by })
2865
+ ] });
2866
+ if (removing) {
2867
+ return /* @__PURE__ */ jsxs(Wrap, { f, as: "form", slot: "section", section: "phone", onSubmit: (e) => {
2868
+ e.preventDefault();
2869
+ void remove(SUBMIT);
2870
+ }, noValidate: true, children: [
2871
+ /* @__PURE__ */ jsx(Heading, { f, children: f.t.phone }),
2872
+ on,
2873
+ general && /* @__PURE__ */ jsx(Say, { f, tone: "danger", children: f.say(general) }),
2874
+ passwordField,
2875
+ /* @__PURE__ */ jsx(f.Button, { type: "submit", variant: "danger", loading: f.loading(), disabled: f.busy, className: f.cx("button"), children: f.t.removePhone }),
2876
+ /* @__PURE__ */ jsx(Cancel, { f, onClick: cancel })
2877
+ ] });
2878
+ }
2879
+ return /* @__PURE__ */ jsxs(Wrap, { f, as: "div", slot: "section", section: "phone", children: [
2880
+ /* @__PURE__ */ jsx(Heading, { f, children: f.t.phone }),
2881
+ on,
2882
+ codes && /* @__PURE__ */ jsx(RecoveryCodes, { f, codes }),
2883
+ general && /* @__PURE__ */ jsx(Say, { f, tone: "danger", children: f.say(general) }),
2884
+ /* @__PURE__ */ jsx(f.Button, { type: "button", variant: "danger", loading: f.loading("remove"), disabled: f.busy, className: f.cx("button"), onClick: press, children: f.t.removePhone })
2885
+ ] });
2886
+ }
2887
+ return /* @__PURE__ */ jsxs(Wrap, { f, as: "form", slot: "section", section: "phone", onSubmit: add, noValidate: true, children: [
2649
2888
  /* @__PURE__ */ jsx(Heading, { f, children: f.t.phone }),
2650
- /* @__PURE__ */ jsx(Note, { f, children: ways.phone ? f.t.phoneOn.replace("{to}", ways.phone.number) : `${f.t.phoneOff} ${f.t.phoneHelp}` }),
2651
- codes && /* @__PURE__ */ jsxs(Fragment2, { children: [
2652
- /* @__PURE__ */ jsx(Note, { f, children: f.t.keepCodes }),
2653
- /* @__PURE__ */ jsx("ul", { className: f.cx("codes"), children: codes.map((c) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("code", { children: c }) }, c)) })
2654
- ] }),
2889
+ /* @__PURE__ */ jsx(Note, { f, children: `${f.t.phoneOff} ${f.t.phoneHelp}` }),
2655
2890
  general && /* @__PURE__ */ jsx(Say, { f, tone: "danger", children: f.say(general) }),
2656
- !ways.phone && /* @__PURE__ */ jsx(Field, { f, name: "phone", type: "tel", label: f.t.phone, value: number, autoComplete: "tel", required: true, onChange: setNumber }),
2657
- !ways.phone && ways.phoneChannels.length > 1 && f.C.Select && /* @__PURE__ */ jsx(ChannelPick, { f, value: channel, channels: ways.phoneChannels, onChange: setChannel }),
2658
- !ways.phone && ways.phoneChannels.length > 1 && !f.C.Select && /* @__PURE__ */ jsx("div", { className: f.cx("field"), role: "radiogroup", children: ways.phoneChannels.map((c) => /* @__PURE__ */ jsxs("label", { className: f.cx("label"), children: [
2891
+ /* @__PURE__ */ jsx(Field, { f, name: "phone", type: "tel", label: f.t.phone, value: number, autoComplete: "tel", required: true, onChange: setNumber }),
2892
+ ways.phoneChannels.length > 1 && f.C.Select && /* @__PURE__ */ jsx(ChannelPick, { f, value: channel, channels: ways.phoneChannels, onChange: setChannel }),
2893
+ ways.phoneChannels.length > 1 && !f.C.Select && /* @__PURE__ */ jsx("div", { className: f.cx("field"), role: "radiogroup", children: ways.phoneChannels.map((c) => /* @__PURE__ */ jsxs("label", { className: f.cx("label"), children: [
2659
2894
  /* @__PURE__ */ jsx("input", { type: "radio", name: "channel", value: c, checked: channel === c, disabled: f.busy, onChange: () => setChannel(c) }),
2660
2895
  " ",
2661
2896
  c === "whatsapp" ? f.t.byWhatsapp : f.t.bySms
2662
2897
  ] }, c)) }),
2663
2898
  passwordField,
2664
- /* @__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 })
2899
+ /* @__PURE__ */ jsx(f.Button, { type: "submit", variant: "primary", loading: f.loading(), disabled: f.busy, className: f.cx("button"), children: f.t.addPhone })
2665
2900
  ] });
2666
2901
  }
2667
2902
  function AgentsPart({ props, when }) {