cauthy-auth 0.12.0 → 0.13.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
@@ -203,13 +203,13 @@ function createAuthClient(options = {}) {
203
203
  const said = typeof options.locale === "function" ? options.locale() : options.locale;
204
204
  return (said ?? (typeof document === "undefined" ? "" : document.documentElement?.lang) ?? "").trim();
205
205
  };
206
- async function post(path, form, bad) {
206
+ async function post(path, form, bad, captchaToken) {
207
207
  if (bad) return { ok: false, error: { code: bad.code, message: bad.message, field: bad.field } };
208
208
  try {
209
209
  const res = await doFetch(`${base}${path}`, {
210
210
  method: "POST",
211
211
  credentials: "same-origin",
212
- headers: { "content-type": "application/json", accept: "application/json", ...locale() ? { "x-cauthy-locale": locale() } : {} },
212
+ headers: { "content-type": "application/json", accept: "application/json", ...locale() ? { "x-cauthy-locale": locale() } : {}, ...captchaToken ? { "x-cauthy-captcha": captchaToken } : {} },
213
213
  body: JSON.stringify(form)
214
214
  });
215
215
  const body = await res.json().catch(() => null);
@@ -261,9 +261,11 @@ function createAuthClient(options = {}) {
261
261
  }
262
262
  return {
263
263
  signIn: {
264
- email: (f) => post("/sign-in/email", { email: f.email, password: f.password }, firstProblem(checkEmail(f.email), checkPassword(f.password))),
264
+ email: (f) => post("/sign-in/email", { email: f.email, password: f.password }, firstProblem(checkEmail(f.email), checkPassword(f.password)), f.captchaToken),
265
265
  mfa: (f) => post("/sign-in/mfa", { code: f.code }, checkCode(f.code)),
266
- magicLink: (f) => post("/sign-in/magic-link", { email: f.email }, checkEmail(f.email)),
266
+ mfaMethods: () => post("/sign-in/mfa/methods", {}, null),
267
+ mfaSendCode: () => post("/sign-in/mfa/send", {}, null),
268
+ magicLink: (f) => post("/sign-in/magic-link", { email: f.email }, checkEmail(f.email), f.captchaToken),
267
269
  magicLinkVerify: (f) => post("/sign-in/magic-link/verify", { token: f.token }, null),
268
270
  emailCode: (f) => post("/sign-in/email-code", { code: f.code }, checkCode(f.code)),
269
271
  passkeyUrl,
@@ -344,9 +346,9 @@ function createAuthClient(options = {}) {
344
346
  }
345
347
  },
346
348
  signUp: {
347
- email: (f) => post("/sign-up/email", { email: f.email, password: f.password, name: f.name }, firstProblem(checkName(f.name), checkEmail(f.email), checkNewPassword(f.password)))
349
+ email: (f) => post("/sign-up/email", { email: f.email, password: f.password, name: f.name }, firstProblem(checkName(f.name), checkEmail(f.email), checkNewPassword(f.password)), f.captchaToken)
348
350
  },
349
- forgotPassword: (f) => post("/forgot-password", { email: f.email }, checkEmail(f.email)),
351
+ forgotPassword: (f) => post("/forgot-password", { email: f.email }, checkEmail(f.email), f.captchaToken),
350
352
  resetPassword: (f) => post("/reset-password", { token: f.token, password: f.password }, checkNewPassword(f.password)),
351
353
  verifyEmail: (f) => post("/verify-email", { token: f.token }, null),
352
354
  signOut: () => post("/sign-out", {}, null),
@@ -364,7 +366,14 @@ function createAuthClient(options = {}) {
364
366
  setup: (f) => post("/me/mfa/setup", { password: f.password }, null),
365
367
  verify: (f) => post("/me/mfa/verify", { code: f.code }, checkCode(f.code)),
366
368
  disable: (f) => post("/me/mfa/disable", { password: f.password }, null),
367
- recoveryCodes: (f) => post("/me/mfa/recovery-codes", { password: f.password }, null)
369
+ recoveryCodes: (f) => post("/me/mfa/recovery-codes", { password: f.password }, null),
370
+ async factors() {
371
+ const body = await read("/me/mfa/factors");
372
+ return body && typeof body.totp === "boolean" ? body : null;
373
+ },
374
+ addPhone: (f) => post("/me/mfa/phone", { phone: f.phone, channel: f.channel, password: f.password }, null),
375
+ verifyPhone: (f) => post("/me/mfa/phone/verify", { code: f.code }, checkCode(f.code)),
376
+ removePhone: (f) => post("/me/mfa/phone/remove", { password: f.password }, null)
368
377
  },
369
378
  changeEmail: (f) => post("/me/email", { email: f.email, password: f.password }, checkEmail(f.email)),
370
379
  async identities() {
@@ -482,6 +491,18 @@ var TEXTS = {
482
491
  linkSent: "If that address has an account, a link to choose a new password is on its way.",
483
492
  codeHelp: "Enter the code from your authenticator app, or a recovery code.",
484
493
  verify: "Verify",
494
+ codeHelpPhone: "We send a code to your phone. Ask for it, then type it here, or use a recovery code.",
495
+ sendCodeSms: "Send a code by SMS to {to}",
496
+ sendCodeWhatsapp: "Send a code by WhatsApp to {to}",
497
+ codeSent: "We sent a code to {to}. Type it here.",
498
+ phone: "Phone, with its country code",
499
+ phoneHelp: "Get your codes by SMS or WhatsApp instead of an app.",
500
+ phoneOn: "Codes go to {to}.",
501
+ phoneOff: "No phone yet.",
502
+ addPhone: "Add phone",
503
+ removePhone: "Remove phone",
504
+ bySms: "SMS",
505
+ byWhatsapp: "WhatsApp",
485
506
  checkEmail: "Almost there: open the link we sent to confirm your address, then sign in.",
486
507
  backToSignIn: "Back to sign in",
487
508
  emailMeALink: "Email me a sign-in link",
@@ -549,6 +570,13 @@ var TEXTS = {
549
570
  step_up_required: "Enter your current password.",
550
571
  weak_password: "Choose a stronger password.",
551
572
  unauthenticated: "Sign in first.",
573
+ phone_refused: "The code could not be sent to that number.",
574
+ too_many_codes: "Enough codes were sent. Use the last one, or sign in again.",
575
+ phone_unavailable: "Codes cannot be sent that way here.",
576
+ phone_already_set: "Remove the phone you have first.",
577
+ phone_codes_unavailable: "Codes cannot be sent right now. Try again in a moment.",
578
+ captcha_required: "Complete the check that you are not a bot, then try again.",
579
+ captcha_failed: "The check that you are not a bot did not pass. Try it again.",
552
580
  passkey_failed: "The passkey could not be used. Try again, or sign in another way.",
553
581
  passkey_exists: "This device already has a passkey for your account.",
554
582
  passkey_unavailable: "Passkeys cannot be used here."
@@ -571,6 +599,18 @@ var TEXTS = {
571
599
  linkSent: "Si ese correo tiene una cuenta, ya va en camino un enlace para elegir una contrase\xF1a nueva.",
572
600
  codeHelp: "Escribe el c\xF3digo de tu aplicaci\xF3n de autenticaci\xF3n, o un c\xF3digo de recuperaci\xF3n.",
573
601
  verify: "Verificar",
602
+ codeHelpPhone: "Te enviamos un c\xF3digo a tu tel\xE9fono. P\xEDdelo y escr\xEDbelo aqu\xED, o usa un c\xF3digo de recuperaci\xF3n.",
603
+ sendCodeSms: "Enviar un c\xF3digo por SMS a {to}",
604
+ sendCodeWhatsapp: "Enviar un c\xF3digo por WhatsApp a {to}",
605
+ codeSent: "Te enviamos un c\xF3digo a {to}. Escr\xEDbelo aqu\xED.",
606
+ phone: "Tel\xE9fono, con su c\xF3digo de pa\xEDs",
607
+ phoneHelp: "Recibe tus c\xF3digos por SMS o WhatsApp en vez de una app.",
608
+ phoneOn: "Los c\xF3digos van a {to}.",
609
+ phoneOff: "Todav\xEDa no tienes un tel\xE9fono.",
610
+ addPhone: "Agregar tel\xE9fono",
611
+ removePhone: "Quitar tel\xE9fono",
612
+ bySms: "SMS",
613
+ byWhatsapp: "WhatsApp",
574
614
  checkEmail: "Ya casi: abre el enlace que te enviamos para confirmar tu correo y luego entra.",
575
615
  backToSignIn: "Volver a entrar",
576
616
  emailMeALink: "Env\xEDame un enlace para entrar",
@@ -638,6 +678,13 @@ var TEXTS = {
638
678
  step_up_required: "Escribe tu contrase\xF1a actual.",
639
679
  weak_password: "Elige una contrase\xF1a m\xE1s fuerte.",
640
680
  unauthenticated: "Inicia sesi\xF3n primero.",
681
+ phone_refused: "No se pudo enviar el c\xF3digo a ese n\xFAmero.",
682
+ too_many_codes: "Ya se enviaron suficientes c\xF3digos. Usa el \xFAltimo o vuelve a entrar.",
683
+ phone_unavailable: "Aqu\xED no se pueden enviar c\xF3digos de esa forma.",
684
+ phone_already_set: "Primero quita el tel\xE9fono que tienes.",
685
+ phone_codes_unavailable: "Ahora no se pueden enviar c\xF3digos. Int\xE9ntalo en un momento.",
686
+ captcha_required: "Completa la verificaci\xF3n de que no eres un robot y vuelve a intentarlo.",
687
+ captcha_failed: "La verificaci\xF3n de que no eres un robot no pas\xF3. Int\xE9ntalo de nuevo.",
641
688
  passkey_failed: "No se pudo usar la llave de acceso. Intenta de nuevo o entra de otra forma.",
642
689
  passkey_exists: "Este dispositivo ya tiene una llave de acceso de tu cuenta.",
643
690
  passkey_unavailable: "Aqu\xED no se pueden usar llaves de acceso."
@@ -682,6 +729,63 @@ function useRoot(props) {
682
729
  if (props.unstyled) return {};
683
730
  return { "data-cauthy": "", style: vars };
684
731
  }
732
+ var turnstileScript = null;
733
+ function loadTurnstile() {
734
+ const ready = () => window.turnstile;
735
+ if (ready()) return Promise.resolve(ready());
736
+ turnstileScript ??= new Promise((resolve, reject) => {
737
+ const script = document.createElement("script");
738
+ script.src = "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit";
739
+ script.async = true;
740
+ script.onload = () => ready() ? resolve(ready()) : reject(new Error("turnstile did not load"));
741
+ script.onerror = () => {
742
+ turnstileScript = null;
743
+ reject(new Error("turnstile did not load"));
744
+ };
745
+ document.head.appendChild(script);
746
+ });
747
+ return turnstileScript;
748
+ }
749
+ function useChallenge(siteKey, locale) {
750
+ const box = useRef(null);
751
+ const widget = useRef(null);
752
+ const [token, setToken] = useState("");
753
+ useEffect(() => {
754
+ if (!siteKey || !box.current) return;
755
+ let gone = false;
756
+ void loadTurnstile().then((api) => {
757
+ if (gone || !box.current) return;
758
+ const id = api.render(box.current, {
759
+ sitekey: siteKey,
760
+ language: locale ?? "auto",
761
+ size: "flexible",
762
+ callback: (t) => setToken(t),
763
+ "expired-callback": () => setToken(""),
764
+ "error-callback": () => setToken("")
765
+ });
766
+ widget.current = { api, id };
767
+ }).catch(() => {
768
+ });
769
+ return () => {
770
+ gone = true;
771
+ if (widget.current) widget.current.api.remove(widget.current.id);
772
+ widget.current = null;
773
+ };
774
+ }, [siteKey, locale]);
775
+ const renew = () => {
776
+ setToken("");
777
+ if (widget.current) widget.current.api.reset(widget.current.id);
778
+ };
779
+ const node = siteKey ? /* @__PURE__ */ jsx("div", { ref: box, className: "cauthy-challenge" }) : null;
780
+ return { node, token: token || void 0, renew };
781
+ }
782
+ function useMethods(auth) {
783
+ const [methods, setMethods] = useState(null);
784
+ useEffect(() => {
785
+ void auth.methods().then(setMethods);
786
+ }, []);
787
+ return methods;
788
+ }
685
789
  function useForm(props) {
686
790
  const t = { ...TEXTS[props.locale ?? "en"], ...props.texts, errors: { ...TEXTS[props.locale ?? "en"].errors, ...props.texts?.errors } };
687
791
  const cx = (slot) => props.classNames?.[slot] ?? `cauthy-${slot}`;
@@ -754,6 +858,23 @@ function SignIn(props) {
754
858
  const [password, setPassword] = useState("");
755
859
  const [code, setCode] = useState("");
756
860
  const [step, setStep] = useState("password");
861
+ const challenge = useChallenge(methods?.captchaSiteKey, props.locale);
862
+ const [phoneWay, setPhoneWay] = useState(null);
863
+ const [phoneSent, setPhoneSent] = useState("");
864
+ useEffect(() => {
865
+ if (step !== "code") return;
866
+ void auth.signIn.mfaMethods().then((r) => {
867
+ if (r.ok) setPhoneWay({ totp: r.totp, phone: r.phone });
868
+ });
869
+ }, [step]);
870
+ const sendCode = async () => {
871
+ f.setBusy(true);
872
+ f.setError(null);
873
+ const r = await auth.signIn.mfaSendCode();
874
+ f.setBusy(false);
875
+ if (!r.ok) return f.setError(r.error);
876
+ setPhoneSent(r.sentTo);
877
+ };
757
878
  const autofill = useRef(null);
758
879
  const [offer, setOffer] = useState(0);
759
880
  useEffect(() => {
@@ -788,8 +909,9 @@ function SignIn(props) {
788
909
  autofill.current?.abort();
789
910
  f.setBusy(true);
790
911
  f.setError(null);
791
- const r = step === "code" ? await auth.signIn.mfa({ code }) : step === "sent" ? await auth.signIn.emailCode({ code }) : await auth.signIn.email({ email, password });
912
+ const r = step === "code" ? await auth.signIn.mfa({ code }) : step === "sent" ? await auth.signIn.emailCode({ code }) : await auth.signIn.email({ email, password, captchaToken: challenge.token });
792
913
  f.setBusy(false);
914
+ if (step === "password") challenge.renew();
793
915
  if (!r.ok) {
794
916
  setOffer((n) => n + 1);
795
917
  return f.setError(r.error);
@@ -812,8 +934,9 @@ function SignIn(props) {
812
934
  autofill.current?.abort();
813
935
  f.setBusy(true);
814
936
  f.setError(null);
815
- const r = await auth.signIn.magicLink({ email });
937
+ const r = await auth.signIn.magicLink({ email, captchaToken: challenge.token });
816
938
  f.setBusy(false);
939
+ challenge.renew();
817
940
  if (!r.ok) return f.setError(r.error);
818
941
  setStep("sent");
819
942
  };
@@ -829,9 +952,11 @@ function SignIn(props) {
829
952
  ] });
830
953
  }
831
954
  if (step === "code") {
955
+ const to = phoneWay?.phone?.number ?? "";
832
956
  return /* @__PURE__ */ jsxs("form", { className: f.cx("form"), ...f.root, onSubmit: submit, noValidate: true, children: [
833
- /* @__PURE__ */ jsx("p", { className: f.cx("notice"), children: f.t.codeHelp }),
957
+ /* @__PURE__ */ jsx("p", { className: f.cx("notice"), children: phoneSent ? f.t.codeSent.replace("{to}", phoneSent) : phoneWay?.phone && !phoneWay.totp ? f.t.codeHelpPhone : f.t.codeHelp }),
834
958
  general && /* @__PURE__ */ jsx("p", { role: "alert", className: f.cx("error"), children: f.say(general) }),
959
+ 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) }),
835
960
  /* @__PURE__ */ jsx(Field, { f, name: "code", type: "text", label: f.t.code, value: code, autoComplete: "one-time-code", required: true, onChange: setCode }),
836
961
  /* @__PURE__ */ jsx(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.verify })
837
962
  ] });
@@ -860,6 +985,7 @@ function SignIn(props) {
860
985
  aside: props.links?.forgotPassword ? /* @__PURE__ */ jsx("a", { className: f.cx("link"), href: props.links.forgotPassword, children: f.t.forgotPassword }) : void 0
861
986
  }
862
987
  ),
988
+ challenge.node,
863
989
  /* @__PURE__ */ jsx(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.signIn }),
864
990
  methods?.magicLink && /* @__PURE__ */ jsx(f.Button, { type: "button", disabled: f.busy, className: f.cx("providerButton"), onClick: () => void sendLink(), children: f.t.emailMeALink }),
865
991
  methods?.signUp !== false && props.links?.signUp && /* @__PURE__ */ jsxs("p", { className: f.cx("footer"), children: [
@@ -891,12 +1017,14 @@ function SignUp(props) {
891
1017
  const [email, setEmail] = useState("");
892
1018
  const [password, setPassword] = useState("");
893
1019
  const [sent, setSent] = useState(false);
1020
+ const challenge = useChallenge(useMethods(auth)?.captchaSiteKey, props.locale);
894
1021
  const submit = async (e) => {
895
1022
  e.preventDefault();
896
1023
  f.setBusy(true);
897
1024
  f.setError(null);
898
- const r = await auth.signUp.email({ name, email, password });
1025
+ const r = await auth.signUp.email({ name, email, password, captchaToken: challenge.token });
899
1026
  f.setBusy(false);
1027
+ challenge.renew();
900
1028
  if (!r.ok) return f.setError(r.error);
901
1029
  if (r.status === "verification_required") return setSent(true);
902
1030
  f.done();
@@ -913,6 +1041,7 @@ function SignUp(props) {
913
1041
  /* @__PURE__ */ jsx(Field, { f, name: "name", type: "text", label: f.t.name, value: name, autoComplete: "name", onChange: setName }),
914
1042
  /* @__PURE__ */ jsx(Field, { f, name: "email", type: "email", label: f.t.email, value: email, autoComplete: "username", required: true, onChange: setEmail }),
915
1043
  /* @__PURE__ */ jsx(Field, { f, name: "password", type: "password", label: f.t.password, value: password, autoComplete: "new-password", required: true, onChange: setPassword }),
1044
+ challenge.node,
916
1045
  /* @__PURE__ */ jsx(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.signUp }),
917
1046
  props.links?.signIn && /* @__PURE__ */ jsxs("p", { className: f.cx("footer"), children: [
918
1047
  f.t.haveAccount,
@@ -926,12 +1055,14 @@ function ForgotPassword(props) {
926
1055
  const f = useForm(props);
927
1056
  const [email, setEmail] = useState("");
928
1057
  const [sent, setSent] = useState(false);
1058
+ const challenge = useChallenge(useMethods(auth)?.captchaSiteKey, props.locale);
929
1059
  const submit = async (e) => {
930
1060
  e.preventDefault();
931
1061
  f.setBusy(true);
932
1062
  f.setError(null);
933
- const r = await auth.forgotPassword({ email });
1063
+ const r = await auth.forgotPassword({ email, captchaToken: challenge.token });
934
1064
  f.setBusy(false);
1065
+ challenge.renew();
935
1066
  if (!r.ok) return f.setError(r.error);
936
1067
  setSent(true);
937
1068
  };
@@ -945,6 +1076,7 @@ function ForgotPassword(props) {
945
1076
  return /* @__PURE__ */ jsxs("form", { className: f.cx("form"), ...f.root, onSubmit: submit, noValidate: true, children: [
946
1077
  general && /* @__PURE__ */ jsx("p", { role: "alert", className: f.cx("error"), children: f.say(general) }),
947
1078
  /* @__PURE__ */ jsx(Field, { f, name: "email", type: "email", label: f.t.email, value: email, autoComplete: "username", required: true, onChange: setEmail }),
1079
+ challenge.node,
948
1080
  /* @__PURE__ */ jsx(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.sendLink }),
949
1081
  props.links?.signIn && /* @__PURE__ */ jsx("p", { className: f.cx("footer"), children: /* @__PURE__ */ jsx("a", { className: f.cx("link"), href: props.links.signIn, children: f.t.backToSignIn }) })
950
1082
  ] });
@@ -1044,6 +1176,7 @@ function Account(props) {
1044
1176
  account.hasPassword && /* @__PURE__ */ jsx(PasswordPart, { props }),
1045
1177
  /* @__PURE__ */ jsx(LinkedPart, { props }),
1046
1178
  /* @__PURE__ */ jsx(SecondStepPart, { props, account, onChanged: (mfaEnabled) => setAccount({ ...account, mfaEnabled }) }),
1179
+ /* @__PURE__ */ jsx(PhonePart, { props, account, onChanged: (mfaEnabled) => setAccount({ ...account, mfaEnabled }) }),
1047
1180
  /* @__PURE__ */ jsx(PasskeysPart, { props, account, accountUrl: accountUrl(), when }),
1048
1181
  /* @__PURE__ */ jsxs("section", { className: cx("section"), children: [
1049
1182
  /* @__PURE__ */ jsx("h2", { className: cx("heading"), children: t.sessions }),
@@ -1273,6 +1406,77 @@ function SecondStepPart({ props, account, onChanged }) {
1273
1406
  account.mfaEnabled && /* @__PURE__ */ jsx(f.Button, { type: "button", disabled: f.busy, className: f.cx("providerButton"), onClick: () => void renew(), children: f.t.newRecoveryCodes })
1274
1407
  ] });
1275
1408
  }
1409
+ function PhonePart({ props, account, onChanged }) {
1410
+ const auth = useAuth();
1411
+ const { f, run, general } = useSection(props);
1412
+ const [ways, setWays] = useState(null);
1413
+ const [number, setNumber] = useState("");
1414
+ const [channel, setChannel] = useState("sms");
1415
+ const [password, setPassword] = useState("");
1416
+ const [code, setCode] = useState("");
1417
+ const [sentTo, setSentTo] = useState("");
1418
+ const [codes, setCodes] = useState(null);
1419
+ const load = () => auth.account.mfa.factors().then((w) => {
1420
+ setWays(w);
1421
+ if (w && w.phoneChannels.length > 0 && !w.phoneChannels.includes(channel)) setChannel(w.phoneChannels[0]);
1422
+ });
1423
+ useEffect(() => {
1424
+ void load();
1425
+ }, []);
1426
+ if (!ways || ways.phoneChannels.length === 0) return null;
1427
+ const add = async (e) => {
1428
+ e.preventDefault();
1429
+ const r = await run(() => auth.account.mfa.addPhone({ phone: number, channel, password }));
1430
+ if (r.ok) setSentTo(r.sentTo);
1431
+ };
1432
+ const confirm = async (e) => {
1433
+ e.preventDefault();
1434
+ const r = await run(() => auth.account.mfa.verifyPhone({ code }));
1435
+ if (!r.ok) return;
1436
+ setSentTo("");
1437
+ setCode("");
1438
+ setPassword("");
1439
+ if (r.recoveryCodes.length > 0) setCodes(r.recoveryCodes);
1440
+ onChanged(true);
1441
+ await load();
1442
+ };
1443
+ const remove = async (e) => {
1444
+ e.preventDefault();
1445
+ const r = await run(() => auth.account.mfa.removePhone({ password }));
1446
+ if (!r.ok) return;
1447
+ setPassword("");
1448
+ await load();
1449
+ const left = await auth.account.mfa.factors();
1450
+ onChanged(Boolean(left?.totp));
1451
+ };
1452
+ 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 });
1453
+ if (sentTo) {
1454
+ return /* @__PURE__ */ jsxs("form", { className: f.cx("section"), onSubmit: confirm, noValidate: true, children: [
1455
+ /* @__PURE__ */ jsx("h2", { className: f.cx("heading"), children: f.t.phone }),
1456
+ /* @__PURE__ */ jsx("p", { className: f.cx("notice"), children: f.t.codeSent.replace("{to}", sentTo) }),
1457
+ general && /* @__PURE__ */ jsx("p", { role: "alert", className: f.cx("error"), children: f.say(general) }),
1458
+ /* @__PURE__ */ jsx(Field, { f, name: "code", type: "text", label: f.t.code, value: code, autoComplete: "one-time-code", required: true, onChange: setCode }),
1459
+ /* @__PURE__ */ jsx(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.verify })
1460
+ ] });
1461
+ }
1462
+ return /* @__PURE__ */ jsxs("form", { className: f.cx("section"), onSubmit: ways.phone ? remove : add, noValidate: true, children: [
1463
+ /* @__PURE__ */ jsx("h2", { className: f.cx("heading"), children: f.t.phone }),
1464
+ /* @__PURE__ */ jsx("p", { className: f.cx("notice"), children: ways.phone ? f.t.phoneOn.replace("{to}", ways.phone.number) : `${f.t.phoneOff} ${f.t.phoneHelp}` }),
1465
+ codes && /* @__PURE__ */ jsxs(Fragment, { children: [
1466
+ /* @__PURE__ */ jsx("p", { className: f.cx("notice"), children: f.t.keepCodes }),
1467
+ /* @__PURE__ */ jsx("ul", { className: f.cx("codes"), children: codes.map((c) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("code", { children: c }) }, c)) })
1468
+ ] }),
1469
+ general && /* @__PURE__ */ jsx("p", { role: "alert", className: f.cx("error"), children: f.say(general) }),
1470
+ !ways.phone && /* @__PURE__ */ jsx(Field, { f, name: "phone", type: "tel", label: f.t.phone, value: number, autoComplete: "tel", required: true, onChange: setNumber }),
1471
+ !ways.phone && ways.phoneChannels.length > 1 && /* @__PURE__ */ jsx("div", { className: f.cx("field"), role: "radiogroup", children: ways.phoneChannels.map((c) => /* @__PURE__ */ jsxs("label", { className: f.cx("label"), children: [
1472
+ /* @__PURE__ */ jsx("input", { type: "radio", name: "channel", value: c, checked: channel === c, onChange: () => setChannel(c) }),
1473
+ " ",
1474
+ c === "whatsapp" ? f.t.byWhatsapp : f.t.bySms
1475
+ ] }, c)) }),
1476
+ passwordField,
1477
+ /* @__PURE__ */ jsx(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: ways.phone ? f.t.removePhone : f.t.addPhone })
1478
+ ] });
1479
+ }
1276
1480
  function SignOutEverywhere({ props }) {
1277
1481
  const auth = useAuth();
1278
1482
  const { f, run, general } = useSection(props);