@poly-x/react 0.1.0-alpha.6 → 0.1.0-alpha.8

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/index.d.mts CHANGED
@@ -203,6 +203,7 @@ interface SignInLabels {
203
203
  passwordPlaceholder: string;
204
204
  submit: string;
205
205
  submitting: string;
206
+ forgotPassword: string;
206
207
  chooseWorkspace: string;
207
208
  chooseWorkspaceSubheading: string;
208
209
  securedBy: string;
@@ -235,6 +236,11 @@ interface SignInProps {
235
236
  panel?: ReactNode;
236
237
  /** BFF endpoint backed by `createAuthHandlers().authenticate`. Default `/api/polyx/authenticate`. */
237
238
  action?: string;
239
+ /**
240
+ * URL of your reset flow (where `<ForgotPassword/>` lives). When set, a
241
+ * "Forgot password?" link appears under the password field. Omit to hide it.
242
+ */
243
+ forgotPasswordUrl?: string;
238
244
  /** Where to send the browser after success. Overrides the server's default redirect. */
239
245
  afterSignInPath?: string;
240
246
  /** Pre-scope the login to a single organization (skips org discovery). */
@@ -261,6 +267,101 @@ interface SignInProps {
261
267
  */
262
268
  declare function SignIn(props: SignInProps): ReactNode;
263
269
  //#endregion
270
+ //#region src/components/forgot-password.d.ts
271
+ /** Every user-facing string, overridable for i18n / white-labelling. */
272
+ interface ForgotPasswordLabels {
273
+ heading: string;
274
+ subheading: string;
275
+ emailLabel: string;
276
+ emailPlaceholder: string;
277
+ submit: string;
278
+ submitting: string;
279
+ /** Shown after submit — always, regardless of whether the account exists. */
280
+ sentHeading: string;
281
+ /** Confirmation body. `{email}` is replaced with the entered address, emphasized. */
282
+ sentSubheading: string;
283
+ differentEmail: string;
284
+ backToSignIn: string;
285
+ securedBy: string;
286
+ genericError: string;
287
+ }
288
+ interface ForgotPasswordProps {
289
+ /** BFF endpoint backed by `createAuthHandlers().forgotPassword`. Default `/api/polyx/forgot-password`. */
290
+ action?: string;
291
+ /** Where the "back to sign in" link points. Default `/sign-in`. */
292
+ signInPath?: string;
293
+ /** Rendered above the heading — your wordmark or logo. */
294
+ logo?: ReactNode;
295
+ /** Show the "Secured by PolyX" attribution under the form. Default true. */
296
+ showSecuredBy?: boolean;
297
+ /** Appended to the root element's class list, for escape-hatch styling. */
298
+ className?: string;
299
+ labels?: Partial<ForgotPasswordLabels>;
300
+ }
301
+ /**
302
+ * The self-service "forgot password" form (poly-x v5 / F040). Renders an email field
303
+ * inside the consuming app and POSTs to the SDK's BFF (`@poly-x/next` `forgotPassword`),
304
+ * which asks poly-auth to email a single-use reset link SERVER-SIDE. The response is
305
+ * always the same — no token, no account-existence signal — so after submit the form
306
+ * shows an identical "check your email" state whether or not the account exists.
307
+ *
308
+ * Reuses the `<SignIn/>` styling: `import "@poly-x/react/styles.css"` once, then theme
309
+ * via the `--polyx-*` custom properties. Target `.polyx-forgot` for form-specific tweaks.
310
+ */
311
+ declare function ForgotPassword(props: ForgotPasswordProps): ReactNode;
312
+ //#endregion
313
+ //#region src/components/reset-password.d.ts
314
+ /** Every user-facing string, overridable for i18n / white-labelling. */
315
+ interface ResetPasswordLabels {
316
+ heading: string;
317
+ subheading: string;
318
+ newPasswordLabel: string;
319
+ confirmPasswordLabel: string;
320
+ passwordPlaceholder: string;
321
+ submit: string;
322
+ submitting: string;
323
+ successHeading: string;
324
+ successSubheading: string;
325
+ invalidHeading: string;
326
+ invalidSubheading: string;
327
+ requestNewLink: string;
328
+ backToSignIn: string;
329
+ securedBy: string;
330
+ passwordMismatch: string;
331
+ weakPassword: string;
332
+ genericError: string;
333
+ }
334
+ interface ResetPasswordProps {
335
+ /** BFF endpoint backed by `createAuthHandlers().resetPassword`. Default `/api/polyx/reset-password`. */
336
+ action?: string;
337
+ /** The opaque reset token. Defaults to `?token=` from the URL (client-side). */
338
+ token?: string;
339
+ /** Where to send the browser after a successful reset. Default `/sign-in`. */
340
+ afterResetPath?: string;
341
+ /** Where "request a new link" / "back to sign in" point. Default `/forgot-password` and `/sign-in`. */
342
+ forgotPasswordPath?: string;
343
+ signInPath?: string;
344
+ /** Rendered above the heading — your wordmark or logo. */
345
+ logo?: ReactNode;
346
+ /** Show the "Secured by PolyX" attribution under the form. Default true. */
347
+ showSecuredBy?: boolean;
348
+ /** Client-side minimum password length (the backend enforces the real floor). Default 8. */
349
+ minLength?: number;
350
+ /** Appended to the root element's class list, for escape-hatch styling. */
351
+ className?: string;
352
+ labels?: Partial<ResetPasswordLabels>;
353
+ }
354
+ /**
355
+ * The reset-completion form (poly-x v5 / F040). Reads the opaque token from the URL,
356
+ * collects a new password, and POSTs to the SDK's BFF (`@poly-x/next` `resetPassword`),
357
+ * which redeems the token single-use server-side. The token is never decoded in the
358
+ * browser — identity, validity, and expiry are all enforced by the backend.
359
+ *
360
+ * Reuses the `<SignIn/>` styling: `import "@poly-x/react/styles.css"` once, then theme
361
+ * via the `--polyx-*` custom properties. Target `.polyx-reset` for form-specific tweaks.
362
+ */
363
+ declare function ResetPassword(props: ResetPasswordProps): ReactNode;
364
+ //#endregion
264
365
  //#region src/components/auth-callback.d.ts
265
366
  interface AuthCallbackProps {
266
367
  /** Rendered while the exchange completes (e.g. a spinner). */
@@ -279,4 +380,4 @@ declare function AuthCallback({
279
380
  declare const PACKAGE_NAME = "@poly-x/react";
280
381
  declare const version = "0.0.0";
281
382
  //#endregion
282
- export { AuthCallback, type AuthCallbackMessage, type AuthCallbackProps, type AuthChannel, BroadcastAuthChannel, BroadcastChannelSessionChannel, type BrowserBridge, type CallbackParams, type LoginController, type LoginControllerDeps, PACKAGE_NAME, PolyXContext, type PolyXContextValue, PolyXProvider, type PolyXProviderProps, PopupCancelledError, type PopupHandle, Protected, type ProtectedProps, SessionStoragePkceStore, SignIn, type SignInLabels, type SignInOptions, type SignInProps, type SignInVariant, type UseAuth, WebLocksLock, WindowBrowserBridge, createLoginController, useAuth, useAuthCallback, useSession, useUser, version };
383
+ export { AuthCallback, type AuthCallbackMessage, type AuthCallbackProps, type AuthChannel, BroadcastAuthChannel, BroadcastChannelSessionChannel, type BrowserBridge, type CallbackParams, ForgotPassword, type ForgotPasswordLabels, type ForgotPasswordProps, type LoginController, type LoginControllerDeps, PACKAGE_NAME, PolyXContext, type PolyXContextValue, PolyXProvider, type PolyXProviderProps, PopupCancelledError, type PopupHandle, Protected, type ProtectedProps, ResetPassword, type ResetPasswordLabels, type ResetPasswordProps, SessionStoragePkceStore, SignIn, type SignInLabels, type SignInOptions, type SignInProps, type SignInVariant, type UseAuth, WebLocksLock, WindowBrowserBridge, createLoginController, useAuth, useAuthCallback, useSession, useUser, version };
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { AuthClient, FetchTransport, InMemoryLock, InMemoryPkceStore, InMemorySessionChannel, InMemorySessionStore, createSessionEngine, generatePkce, randomState, resolveConfig } from "@poly-x/core";
3
- import { createContext, useCallback, useContext, useEffect, useMemo, useState, useSyncExternalStore } from "react";
4
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { Fragment, createContext, useCallback, useContext, useEffect, useMemo, useState, useSyncExternalStore } from "react";
4
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
5
5
  //#region src/auth-channel.ts
6
6
  var BroadcastAuthChannel = class {
7
7
  supported;
@@ -351,7 +351,7 @@ function Protected({ children, fallback = null, loading = null }) {
351
351
  }
352
352
  //#endregion
353
353
  //#region src/components/sign-in.tsx
354
- const DEFAULT_LABELS = {
354
+ const DEFAULT_LABELS$2 = {
355
355
  heading: "Sign in",
356
356
  subheading: "Enter your credentials to continue.",
357
357
  tagline: "",
@@ -361,6 +361,7 @@ const DEFAULT_LABELS = {
361
361
  passwordPlaceholder: "••••••••",
362
362
  submit: "Sign in",
363
363
  submitting: "Signing in…",
364
+ forgotPassword: "Forgot password?",
364
365
  chooseWorkspace: "Choose a workspace",
365
366
  chooseWorkspaceSubheading: "You have access to more than one workspace.",
366
367
  securedBy: "Secured by PolyX",
@@ -392,7 +393,7 @@ function SignIn(props) {
392
393
  const action = props.action ?? "/api/polyx/authenticate";
393
394
  const variant = props.variant ?? "card";
394
395
  const labels = {
395
- ...DEFAULT_LABELS,
396
+ ...DEFAULT_LABELS$2,
396
397
  ...props.labels
397
398
  };
398
399
  const [email, setEmail] = useState("");
@@ -631,6 +632,14 @@ function SignIn(props) {
631
632
  onChange: (event) => setPassword(event.target.value)
632
633
  })]
633
634
  }),
635
+ props.forgotPasswordUrl ? /* @__PURE__ */ jsx("div", {
636
+ className: "polyx-signin__forgot",
637
+ children: /* @__PURE__ */ jsx("a", {
638
+ className: "polyx-signin__link",
639
+ href: props.forgotPasswordUrl,
640
+ children: labels.forgotPassword
641
+ })
642
+ }) : null,
634
643
  errorNode,
635
644
  /* @__PURE__ */ jsx("button", {
636
645
  type: "submit",
@@ -648,7 +657,7 @@ function SignIn(props) {
648
657
  "data-polyx-signin": step,
649
658
  children: [/* @__PURE__ */ jsx("aside", {
650
659
  className: "polyx-signin__panel",
651
- children: props.panel ?? /* @__PURE__ */ jsxs(Fragment, { children: [props.logo ? /* @__PURE__ */ jsx("div", {
660
+ children: props.panel ?? /* @__PURE__ */ jsxs(Fragment$1, { children: [props.logo ? /* @__PURE__ */ jsx("div", {
652
661
  className: "polyx-signin__panel-logo",
653
662
  children: props.logo
654
663
  }) : null, labels.tagline ? /* @__PURE__ */ jsx("p", {
@@ -667,6 +676,486 @@ function SignIn(props) {
667
676
  });
668
677
  }
669
678
  //#endregion
679
+ //#region src/components/_recovery-status.tsx
680
+ const ICONS = {
681
+ mail: /* @__PURE__ */ jsxs("svg", {
682
+ viewBox: "0 0 24 24",
683
+ fill: "none",
684
+ stroke: "currentColor",
685
+ strokeWidth: "1.75",
686
+ "aria-hidden": "true",
687
+ focusable: "false",
688
+ children: [/* @__PURE__ */ jsx("rect", {
689
+ x: "3",
690
+ y: "5",
691
+ width: "18",
692
+ height: "14",
693
+ rx: "2.5"
694
+ }), /* @__PURE__ */ jsx("path", {
695
+ d: "m4 7 8 6 8-6",
696
+ strokeLinecap: "round",
697
+ strokeLinejoin: "round"
698
+ })]
699
+ }),
700
+ check: /* @__PURE__ */ jsx("svg", {
701
+ viewBox: "0 0 24 24",
702
+ fill: "none",
703
+ stroke: "currentColor",
704
+ strokeWidth: "2",
705
+ "aria-hidden": "true",
706
+ focusable: "false",
707
+ children: /* @__PURE__ */ jsx("path", {
708
+ d: "m5 12.5 4.5 4.5L19 7",
709
+ strokeLinecap: "round",
710
+ strokeLinejoin: "round"
711
+ })
712
+ }),
713
+ warning: /* @__PURE__ */ jsxs("svg", {
714
+ viewBox: "0 0 24 24",
715
+ fill: "none",
716
+ stroke: "currentColor",
717
+ strokeWidth: "1.75",
718
+ "aria-hidden": "true",
719
+ focusable: "false",
720
+ children: [
721
+ /* @__PURE__ */ jsx("circle", {
722
+ cx: "12",
723
+ cy: "12",
724
+ r: "9"
725
+ }),
726
+ /* @__PURE__ */ jsx("path", {
727
+ d: "M12 7.5v5",
728
+ strokeLinecap: "round"
729
+ }),
730
+ /* @__PURE__ */ jsx("circle", {
731
+ cx: "12",
732
+ cy: "16",
733
+ r: "0.6",
734
+ fill: "currentColor",
735
+ stroke: "none"
736
+ })
737
+ ]
738
+ })
739
+ };
740
+ /**
741
+ * A centered, icon-topped confirmation card — the shape every recovery terminal
742
+ * state shares (link sent, password updated, link expired). Reuses the `<SignIn/>`
743
+ * class anatomy so it themes from the same `--polyx-*` custom properties.
744
+ */
745
+ function RecoveryStatus(props) {
746
+ return /* @__PURE__ */ jsxs("div", {
747
+ className: "polyx-signin__card",
748
+ children: [
749
+ /* @__PURE__ */ jsxs("div", {
750
+ className: "polyx-signin__header polyx-signin__header--center",
751
+ children: [
752
+ /* @__PURE__ */ jsx("span", {
753
+ className: `polyx-signin__status-icon polyx-signin__status-icon--${props.tone ?? "brand"}`,
754
+ children: ICONS[props.icon]
755
+ }),
756
+ /* @__PURE__ */ jsx("h1", {
757
+ className: "polyx-signin__heading",
758
+ children: props.heading
759
+ }),
760
+ /* @__PURE__ */ jsx("p", {
761
+ className: "polyx-signin__subheading",
762
+ children: props.children
763
+ })
764
+ ]
765
+ }),
766
+ props.actions ? /* @__PURE__ */ jsx("div", {
767
+ className: "polyx-signin__actions",
768
+ children: props.actions
769
+ }) : null,
770
+ props.footer
771
+ ]
772
+ });
773
+ }
774
+ //#endregion
775
+ //#region src/components/forgot-password.tsx
776
+ const DEFAULT_LABELS$1 = {
777
+ heading: "Reset your password",
778
+ subheading: "Enter your email and we'll send you a reset link.",
779
+ emailLabel: "Email",
780
+ emailPlaceholder: "you@company.com",
781
+ submit: "Send reset link",
782
+ submitting: "Sending…",
783
+ sentHeading: "Check your email",
784
+ sentSubheading: "If an account exists for {email}, a password reset link is on its way. It expires in 30 minutes.",
785
+ differentEmail: "Use a different email",
786
+ backToSignIn: "Back to sign in",
787
+ securedBy: "Secured by PolyX",
788
+ genericError: "Something went wrong. Please try again."
789
+ };
790
+ /** Splits a label on `{email}` and drops the entered address in, emphasized. */
791
+ function withEmail(template, email) {
792
+ return template.split("{email}").map((chunk, index, all) => /* @__PURE__ */ jsxs(Fragment, { children: [chunk, index < all.length - 1 ? /* @__PURE__ */ jsx("span", {
793
+ className: "polyx-signin__email",
794
+ children: email || "that address"
795
+ }) : null] }, index));
796
+ }
797
+ /**
798
+ * The self-service "forgot password" form (poly-x v5 / F040). Renders an email field
799
+ * inside the consuming app and POSTs to the SDK's BFF (`@poly-x/next` `forgotPassword`),
800
+ * which asks poly-auth to email a single-use reset link SERVER-SIDE. The response is
801
+ * always the same — no token, no account-existence signal — so after submit the form
802
+ * shows an identical "check your email" state whether or not the account exists.
803
+ *
804
+ * Reuses the `<SignIn/>` styling: `import "@poly-x/react/styles.css"` once, then theme
805
+ * via the `--polyx-*` custom properties. Target `.polyx-forgot` for form-specific tweaks.
806
+ */
807
+ function ForgotPassword(props) {
808
+ const action = props.action ?? "/api/polyx/forgot-password";
809
+ const signInPath = props.signInPath ?? "/sign-in";
810
+ const labels = {
811
+ ...DEFAULT_LABELS$1,
812
+ ...props.labels
813
+ };
814
+ const [email, setEmail] = useState("");
815
+ const [submitting, setSubmitting] = useState(false);
816
+ const [sent, setSent] = useState(false);
817
+ const [error, setError] = useState(null);
818
+ async function onSubmit(event) {
819
+ event.preventDefault();
820
+ setSubmitting(true);
821
+ setError(null);
822
+ try {
823
+ if ((await (await fetch(action, {
824
+ method: "POST",
825
+ headers: { "content-type": "application/json" },
826
+ body: JSON.stringify({ email })
827
+ })).json().catch(() => ({}))).status === "accepted") {
828
+ setSent(true);
829
+ return;
830
+ }
831
+ setError(labels.genericError);
832
+ } catch {
833
+ setError(labels.genericError);
834
+ } finally {
835
+ setSubmitting(false);
836
+ }
837
+ }
838
+ const rootClass = [
839
+ "polyx-signin",
840
+ "polyx-forgot",
841
+ props.className
842
+ ].filter(Boolean).join(" ");
843
+ const footer = props.showSecuredBy === false ? null : /* @__PURE__ */ jsxs("p", {
844
+ className: "polyx-signin__footer",
845
+ children: [/* @__PURE__ */ jsx("svg", {
846
+ className: "polyx-signin__shield",
847
+ viewBox: "0 0 16 16",
848
+ fill: "currentColor",
849
+ "aria-hidden": "true",
850
+ focusable: "false",
851
+ children: /* @__PURE__ */ jsx("path", { d: "M8 0.5 2 3v4.2c0 3.5 2.5 6.7 6 7.8 3.5-1.1 6-4.3 6-7.8V3L8 .5Zm2.9 5.6-3.4 3.4a.7.7 0 0 1-1 0L5.1 8.1a.7.7 0 1 1 1-1l.9.9 2.9-2.9a.7.7 0 0 1 1 1Z" })
852
+ }), labels.securedBy]
853
+ });
854
+ const header = (heading, subheading) => /* @__PURE__ */ jsxs("div", {
855
+ className: "polyx-signin__header",
856
+ children: [
857
+ props.logo ? /* @__PURE__ */ jsx("div", {
858
+ className: "polyx-signin__logo",
859
+ children: props.logo
860
+ }) : null,
861
+ /* @__PURE__ */ jsx("h1", {
862
+ className: "polyx-signin__heading",
863
+ children: heading
864
+ }),
865
+ /* @__PURE__ */ jsx("p", {
866
+ className: "polyx-signin__subheading",
867
+ children: subheading
868
+ })
869
+ ]
870
+ });
871
+ if (sent) return /* @__PURE__ */ jsx("div", {
872
+ className: rootClass,
873
+ "data-polyx-forgot": "sent",
874
+ children: /* @__PURE__ */ jsx(RecoveryStatus, {
875
+ icon: "mail",
876
+ heading: labels.sentHeading,
877
+ footer,
878
+ actions: /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("button", {
879
+ type: "button",
880
+ className: "polyx-signin__link",
881
+ onClick: () => setSent(false),
882
+ children: labels.differentEmail
883
+ }), /* @__PURE__ */ jsx("a", {
884
+ className: "polyx-signin__link",
885
+ href: signInPath,
886
+ children: labels.backToSignIn
887
+ })] }),
888
+ children: withEmail(labels.sentSubheading, email)
889
+ })
890
+ });
891
+ return /* @__PURE__ */ jsx("div", {
892
+ className: rootClass,
893
+ "data-polyx-forgot": "request",
894
+ children: /* @__PURE__ */ jsxs("div", {
895
+ className: "polyx-signin__card",
896
+ children: [
897
+ header(labels.heading, labels.subheading),
898
+ /* @__PURE__ */ jsxs("form", {
899
+ className: "polyx-signin__form",
900
+ onSubmit,
901
+ children: [
902
+ /* @__PURE__ */ jsxs("label", {
903
+ className: "polyx-signin__field",
904
+ children: [/* @__PURE__ */ jsx("span", {
905
+ className: "polyx-signin__label",
906
+ children: labels.emailLabel
907
+ }), /* @__PURE__ */ jsx("input", {
908
+ className: "polyx-signin__input",
909
+ type: "email",
910
+ name: "email",
911
+ autoComplete: "username",
912
+ placeholder: labels.emailPlaceholder,
913
+ required: true,
914
+ value: email,
915
+ disabled: submitting,
916
+ onChange: (event) => setEmail(event.target.value)
917
+ })]
918
+ }),
919
+ error ? /* @__PURE__ */ jsx("p", {
920
+ role: "alert",
921
+ className: "polyx-signin__error",
922
+ children: error
923
+ }) : null,
924
+ /* @__PURE__ */ jsx("button", {
925
+ type: "submit",
926
+ className: "polyx-signin__submit",
927
+ disabled: submitting,
928
+ children: submitting ? labels.submitting : labels.submit
929
+ })
930
+ ]
931
+ }),
932
+ footer
933
+ ]
934
+ })
935
+ });
936
+ }
937
+ //#endregion
938
+ //#region src/components/reset-password.tsx
939
+ const DEFAULT_LABELS = {
940
+ heading: "Choose a new password",
941
+ subheading: "Enter and confirm your new password.",
942
+ newPasswordLabel: "New password",
943
+ confirmPasswordLabel: "Confirm new password",
944
+ passwordPlaceholder: "••••••••",
945
+ submit: "Reset password",
946
+ submitting: "Resetting…",
947
+ successHeading: "Password updated",
948
+ successSubheading: "You can now sign in with your new password.",
949
+ invalidHeading: "This link is invalid or expired",
950
+ invalidSubheading: "Password reset links can be used once and expire quickly. Request a new one.",
951
+ requestNewLink: "Request a new link",
952
+ backToSignIn: "Back to sign in",
953
+ securedBy: "Secured by PolyX",
954
+ passwordMismatch: "Those passwords don't match.",
955
+ weakPassword: "Password must be at least 8 characters.",
956
+ genericError: "Something went wrong. Please try again."
957
+ };
958
+ /** Matches poly-auth's v5 reset floor (F040). */
959
+ const DEFAULT_MIN_LENGTH = 8;
960
+ /**
961
+ * The reset-completion form (poly-x v5 / F040). Reads the opaque token from the URL,
962
+ * collects a new password, and POSTs to the SDK's BFF (`@poly-x/next` `resetPassword`),
963
+ * which redeems the token single-use server-side. The token is never decoded in the
964
+ * browser — identity, validity, and expiry are all enforced by the backend.
965
+ *
966
+ * Reuses the `<SignIn/>` styling: `import "@poly-x/react/styles.css"` once, then theme
967
+ * via the `--polyx-*` custom properties. Target `.polyx-reset` for form-specific tweaks.
968
+ */
969
+ function ResetPassword(props) {
970
+ const action = props.action ?? "/api/polyx/reset-password";
971
+ const minLength = props.minLength ?? DEFAULT_MIN_LENGTH;
972
+ const signInPath = props.signInPath ?? "/sign-in";
973
+ const forgotPasswordPath = props.forgotPasswordPath ?? "/forgot-password";
974
+ const labels = {
975
+ ...DEFAULT_LABELS,
976
+ ...props.labels
977
+ };
978
+ const [token] = useState(() => props.token ?? (typeof window !== "undefined" ? new URLSearchParams(window.location.search).get("token") ?? "" : ""));
979
+ const [newPassword, setNewPassword] = useState("");
980
+ const [confirmPassword, setConfirmPassword] = useState("");
981
+ const [submitting, setSubmitting] = useState(false);
982
+ const [error, setError] = useState(null);
983
+ const [step, setStep] = useState(token ? "form" : "invalid");
984
+ async function onSubmit(event) {
985
+ event.preventDefault();
986
+ if (newPassword !== confirmPassword) {
987
+ setError(labels.passwordMismatch);
988
+ return;
989
+ }
990
+ setSubmitting(true);
991
+ setError(null);
992
+ try {
993
+ switch ((await (await fetch(action, {
994
+ method: "POST",
995
+ headers: { "content-type": "application/json" },
996
+ body: JSON.stringify({
997
+ token,
998
+ newPassword,
999
+ confirmPassword
1000
+ })
1001
+ })).json().catch(() => ({}))).status) {
1002
+ case "success":
1003
+ setStep("success");
1004
+ if (props.afterResetPath) window.location.assign(props.afterResetPath);
1005
+ return;
1006
+ case "weak_password":
1007
+ setError(labels.weakPassword);
1008
+ return;
1009
+ case "password_mismatch":
1010
+ setError(labels.passwordMismatch);
1011
+ return;
1012
+ case "invalid_or_expired":
1013
+ setStep("invalid");
1014
+ return;
1015
+ default:
1016
+ setError(labels.genericError);
1017
+ return;
1018
+ }
1019
+ } catch {
1020
+ setError(labels.genericError);
1021
+ } finally {
1022
+ setSubmitting(false);
1023
+ }
1024
+ }
1025
+ const rootClass = [
1026
+ "polyx-signin",
1027
+ "polyx-reset",
1028
+ props.className
1029
+ ].filter(Boolean).join(" ");
1030
+ const footer = props.showSecuredBy === false ? null : /* @__PURE__ */ jsxs("p", {
1031
+ className: "polyx-signin__footer",
1032
+ children: [/* @__PURE__ */ jsx("svg", {
1033
+ className: "polyx-signin__shield",
1034
+ viewBox: "0 0 16 16",
1035
+ fill: "currentColor",
1036
+ "aria-hidden": "true",
1037
+ focusable: "false",
1038
+ children: /* @__PURE__ */ jsx("path", { d: "M8 0.5 2 3v4.2c0 3.5 2.5 6.7 6 7.8 3.5-1.1 6-4.3 6-7.8V3L8 .5Zm2.9 5.6-3.4 3.4a.7.7 0 0 1-1 0L5.1 8.1a.7.7 0 1 1 1-1l.9.9 2.9-2.9a.7.7 0 0 1 1 1Z" })
1039
+ }), labels.securedBy]
1040
+ });
1041
+ const header = (heading, subheading) => /* @__PURE__ */ jsxs("div", {
1042
+ className: "polyx-signin__header",
1043
+ children: [
1044
+ props.logo ? /* @__PURE__ */ jsx("div", {
1045
+ className: "polyx-signin__logo",
1046
+ children: props.logo
1047
+ }) : null,
1048
+ /* @__PURE__ */ jsx("h1", {
1049
+ className: "polyx-signin__heading",
1050
+ children: heading
1051
+ }),
1052
+ /* @__PURE__ */ jsx("p", {
1053
+ className: "polyx-signin__subheading",
1054
+ children: subheading
1055
+ })
1056
+ ]
1057
+ });
1058
+ if (step === "success") return /* @__PURE__ */ jsx("div", {
1059
+ className: rootClass,
1060
+ "data-polyx-reset": "success",
1061
+ children: /* @__PURE__ */ jsx(RecoveryStatus, {
1062
+ icon: "check",
1063
+ heading: labels.successHeading,
1064
+ footer,
1065
+ actions: /* @__PURE__ */ jsx("a", {
1066
+ className: "polyx-signin__link",
1067
+ href: signInPath,
1068
+ children: labels.backToSignIn
1069
+ }),
1070
+ children: labels.successSubheading
1071
+ })
1072
+ });
1073
+ if (step === "invalid") return /* @__PURE__ */ jsx("div", {
1074
+ className: rootClass,
1075
+ "data-polyx-reset": "invalid",
1076
+ children: /* @__PURE__ */ jsx(RecoveryStatus, {
1077
+ icon: "warning",
1078
+ tone: "danger",
1079
+ heading: labels.invalidHeading,
1080
+ footer,
1081
+ actions: /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("a", {
1082
+ className: "polyx-signin__link",
1083
+ href: forgotPasswordPath,
1084
+ children: labels.requestNewLink
1085
+ }), /* @__PURE__ */ jsx("a", {
1086
+ className: "polyx-signin__link",
1087
+ href: signInPath,
1088
+ children: labels.backToSignIn
1089
+ })] }),
1090
+ children: labels.invalidSubheading
1091
+ })
1092
+ });
1093
+ return /* @__PURE__ */ jsx("div", {
1094
+ className: rootClass,
1095
+ "data-polyx-reset": step,
1096
+ children: /* @__PURE__ */ jsxs("div", {
1097
+ className: "polyx-signin__card",
1098
+ children: [
1099
+ header(labels.heading, labels.subheading),
1100
+ /* @__PURE__ */ jsxs("form", {
1101
+ className: "polyx-signin__form",
1102
+ onSubmit,
1103
+ children: [
1104
+ /* @__PURE__ */ jsxs("label", {
1105
+ className: "polyx-signin__field",
1106
+ children: [/* @__PURE__ */ jsx("span", {
1107
+ className: "polyx-signin__label",
1108
+ children: labels.newPasswordLabel
1109
+ }), /* @__PURE__ */ jsx("input", {
1110
+ className: "polyx-signin__input",
1111
+ type: "password",
1112
+ name: "newPassword",
1113
+ autoComplete: "new-password",
1114
+ placeholder: labels.passwordPlaceholder,
1115
+ required: true,
1116
+ minLength,
1117
+ value: newPassword,
1118
+ disabled: submitting,
1119
+ onChange: (event) => setNewPassword(event.target.value)
1120
+ })]
1121
+ }),
1122
+ /* @__PURE__ */ jsxs("label", {
1123
+ className: "polyx-signin__field",
1124
+ children: [/* @__PURE__ */ jsx("span", {
1125
+ className: "polyx-signin__label",
1126
+ children: labels.confirmPasswordLabel
1127
+ }), /* @__PURE__ */ jsx("input", {
1128
+ className: "polyx-signin__input",
1129
+ type: "password",
1130
+ name: "confirmPassword",
1131
+ autoComplete: "new-password",
1132
+ placeholder: labels.passwordPlaceholder,
1133
+ required: true,
1134
+ minLength,
1135
+ value: confirmPassword,
1136
+ disabled: submitting,
1137
+ onChange: (event) => setConfirmPassword(event.target.value)
1138
+ })]
1139
+ }),
1140
+ error ? /* @__PURE__ */ jsx("p", {
1141
+ role: "alert",
1142
+ className: "polyx-signin__error",
1143
+ children: error
1144
+ }) : null,
1145
+ /* @__PURE__ */ jsx("button", {
1146
+ type: "submit",
1147
+ className: "polyx-signin__submit",
1148
+ disabled: submitting,
1149
+ children: submitting ? labels.submitting : labels.submit
1150
+ })
1151
+ ]
1152
+ }),
1153
+ footer
1154
+ ]
1155
+ })
1156
+ });
1157
+ }
1158
+ //#endregion
670
1159
  //#region src/components/auth-callback.tsx
671
1160
  /** Drop on your `/auth/callback` route: completes the sign-in exchange on mount. */
672
1161
  function AuthCallback({ children = null }) {
@@ -685,4 +1174,4 @@ function AuthCallback({ children = null }) {
685
1174
  const PACKAGE_NAME = "@poly-x/react";
686
1175
  const version = "0.0.0";
687
1176
  //#endregion
688
- export { AuthCallback, BroadcastAuthChannel, BroadcastChannelSessionChannel, PACKAGE_NAME, PolyXContext, PolyXProvider, PopupCancelledError, Protected, SessionStoragePkceStore, SignIn, WebLocksLock, WindowBrowserBridge, createLoginController, useAuth, useAuthCallback, useSession, useUser, version };
1177
+ export { AuthCallback, BroadcastAuthChannel, BroadcastChannelSessionChannel, ForgotPassword, PACKAGE_NAME, PolyXContext, PolyXProvider, PopupCancelledError, Protected, ResetPassword, SessionStoragePkceStore, SignIn, WebLocksLock, WindowBrowserBridge, createLoginController, useAuth, useAuthCallback, useSession, useUser, version };