@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.cjs CHANGED
@@ -352,7 +352,7 @@ function Protected({ children, fallback = null, loading = null }) {
352
352
  }
353
353
  //#endregion
354
354
  //#region src/components/sign-in.tsx
355
- const DEFAULT_LABELS = {
355
+ const DEFAULT_LABELS$2 = {
356
356
  heading: "Sign in",
357
357
  subheading: "Enter your credentials to continue.",
358
358
  tagline: "",
@@ -362,6 +362,7 @@ const DEFAULT_LABELS = {
362
362
  passwordPlaceholder: "••••••••",
363
363
  submit: "Sign in",
364
364
  submitting: "Signing in…",
365
+ forgotPassword: "Forgot password?",
365
366
  chooseWorkspace: "Choose a workspace",
366
367
  chooseWorkspaceSubheading: "You have access to more than one workspace.",
367
368
  securedBy: "Secured by PolyX",
@@ -393,7 +394,7 @@ function SignIn(props) {
393
394
  const action = props.action ?? "/api/polyx/authenticate";
394
395
  const variant = props.variant ?? "card";
395
396
  const labels = {
396
- ...DEFAULT_LABELS,
397
+ ...DEFAULT_LABELS$2,
397
398
  ...props.labels
398
399
  };
399
400
  const [email, setEmail] = (0, react.useState)("");
@@ -632,6 +633,14 @@ function SignIn(props) {
632
633
  onChange: (event) => setPassword(event.target.value)
633
634
  })]
634
635
  }),
636
+ props.forgotPasswordUrl ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
637
+ className: "polyx-signin__forgot",
638
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
639
+ className: "polyx-signin__link",
640
+ href: props.forgotPasswordUrl,
641
+ children: labels.forgotPassword
642
+ })
643
+ }) : null,
635
644
  errorNode,
636
645
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
637
646
  type: "submit",
@@ -668,6 +677,486 @@ function SignIn(props) {
668
677
  });
669
678
  }
670
679
  //#endregion
680
+ //#region src/components/_recovery-status.tsx
681
+ const ICONS = {
682
+ mail: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
683
+ viewBox: "0 0 24 24",
684
+ fill: "none",
685
+ stroke: "currentColor",
686
+ strokeWidth: "1.75",
687
+ "aria-hidden": "true",
688
+ focusable: "false",
689
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
690
+ x: "3",
691
+ y: "5",
692
+ width: "18",
693
+ height: "14",
694
+ rx: "2.5"
695
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
696
+ d: "m4 7 8 6 8-6",
697
+ strokeLinecap: "round",
698
+ strokeLinejoin: "round"
699
+ })]
700
+ }),
701
+ check: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
702
+ viewBox: "0 0 24 24",
703
+ fill: "none",
704
+ stroke: "currentColor",
705
+ strokeWidth: "2",
706
+ "aria-hidden": "true",
707
+ focusable: "false",
708
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
709
+ d: "m5 12.5 4.5 4.5L19 7",
710
+ strokeLinecap: "round",
711
+ strokeLinejoin: "round"
712
+ })
713
+ }),
714
+ warning: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
715
+ viewBox: "0 0 24 24",
716
+ fill: "none",
717
+ stroke: "currentColor",
718
+ strokeWidth: "1.75",
719
+ "aria-hidden": "true",
720
+ focusable: "false",
721
+ children: [
722
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
723
+ cx: "12",
724
+ cy: "12",
725
+ r: "9"
726
+ }),
727
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
728
+ d: "M12 7.5v5",
729
+ strokeLinecap: "round"
730
+ }),
731
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
732
+ cx: "12",
733
+ cy: "16",
734
+ r: "0.6",
735
+ fill: "currentColor",
736
+ stroke: "none"
737
+ })
738
+ ]
739
+ })
740
+ };
741
+ /**
742
+ * A centered, icon-topped confirmation card — the shape every recovery terminal
743
+ * state shares (link sent, password updated, link expired). Reuses the `<SignIn/>`
744
+ * class anatomy so it themes from the same `--polyx-*` custom properties.
745
+ */
746
+ function RecoveryStatus(props) {
747
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
748
+ className: "polyx-signin__card",
749
+ children: [
750
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
751
+ className: "polyx-signin__header polyx-signin__header--center",
752
+ children: [
753
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
754
+ className: `polyx-signin__status-icon polyx-signin__status-icon--${props.tone ?? "brand"}`,
755
+ children: ICONS[props.icon]
756
+ }),
757
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", {
758
+ className: "polyx-signin__heading",
759
+ children: props.heading
760
+ }),
761
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
762
+ className: "polyx-signin__subheading",
763
+ children: props.children
764
+ })
765
+ ]
766
+ }),
767
+ props.actions ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
768
+ className: "polyx-signin__actions",
769
+ children: props.actions
770
+ }) : null,
771
+ props.footer
772
+ ]
773
+ });
774
+ }
775
+ //#endregion
776
+ //#region src/components/forgot-password.tsx
777
+ const DEFAULT_LABELS$1 = {
778
+ heading: "Reset your password",
779
+ subheading: "Enter your email and we'll send you a reset link.",
780
+ emailLabel: "Email",
781
+ emailPlaceholder: "you@company.com",
782
+ submit: "Send reset link",
783
+ submitting: "Sending…",
784
+ sentHeading: "Check your email",
785
+ sentSubheading: "If an account exists for {email}, a password reset link is on its way. It expires in 30 minutes.",
786
+ differentEmail: "Use a different email",
787
+ backToSignIn: "Back to sign in",
788
+ securedBy: "Secured by PolyX",
789
+ genericError: "Something went wrong. Please try again."
790
+ };
791
+ /** Splits a label on `{email}` and drops the entered address in, emphasized. */
792
+ function withEmail(template, email) {
793
+ return template.split("{email}").map((chunk, index, all) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [chunk, index < all.length - 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
794
+ className: "polyx-signin__email",
795
+ children: email || "that address"
796
+ }) : null] }, index));
797
+ }
798
+ /**
799
+ * The self-service "forgot password" form (poly-x v5 / F040). Renders an email field
800
+ * inside the consuming app and POSTs to the SDK's BFF (`@poly-x/next` `forgotPassword`),
801
+ * which asks poly-auth to email a single-use reset link SERVER-SIDE. The response is
802
+ * always the same — no token, no account-existence signal — so after submit the form
803
+ * shows an identical "check your email" state whether or not the account exists.
804
+ *
805
+ * Reuses the `<SignIn/>` styling: `import "@poly-x/react/styles.css"` once, then theme
806
+ * via the `--polyx-*` custom properties. Target `.polyx-forgot` for form-specific tweaks.
807
+ */
808
+ function ForgotPassword(props) {
809
+ const action = props.action ?? "/api/polyx/forgot-password";
810
+ const signInPath = props.signInPath ?? "/sign-in";
811
+ const labels = {
812
+ ...DEFAULT_LABELS$1,
813
+ ...props.labels
814
+ };
815
+ const [email, setEmail] = (0, react.useState)("");
816
+ const [submitting, setSubmitting] = (0, react.useState)(false);
817
+ const [sent, setSent] = (0, react.useState)(false);
818
+ const [error, setError] = (0, react.useState)(null);
819
+ async function onSubmit(event) {
820
+ event.preventDefault();
821
+ setSubmitting(true);
822
+ setError(null);
823
+ try {
824
+ if ((await (await fetch(action, {
825
+ method: "POST",
826
+ headers: { "content-type": "application/json" },
827
+ body: JSON.stringify({ email })
828
+ })).json().catch(() => ({}))).status === "accepted") {
829
+ setSent(true);
830
+ return;
831
+ }
832
+ setError(labels.genericError);
833
+ } catch {
834
+ setError(labels.genericError);
835
+ } finally {
836
+ setSubmitting(false);
837
+ }
838
+ }
839
+ const rootClass = [
840
+ "polyx-signin",
841
+ "polyx-forgot",
842
+ props.className
843
+ ].filter(Boolean).join(" ");
844
+ const footer = props.showSecuredBy === false ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
845
+ className: "polyx-signin__footer",
846
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
847
+ className: "polyx-signin__shield",
848
+ viewBox: "0 0 16 16",
849
+ fill: "currentColor",
850
+ "aria-hidden": "true",
851
+ focusable: "false",
852
+ children: /* @__PURE__ */ (0, react_jsx_runtime.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" })
853
+ }), labels.securedBy]
854
+ });
855
+ const header = (heading, subheading) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
856
+ className: "polyx-signin__header",
857
+ children: [
858
+ props.logo ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
859
+ className: "polyx-signin__logo",
860
+ children: props.logo
861
+ }) : null,
862
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", {
863
+ className: "polyx-signin__heading",
864
+ children: heading
865
+ }),
866
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
867
+ className: "polyx-signin__subheading",
868
+ children: subheading
869
+ })
870
+ ]
871
+ });
872
+ if (sent) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
873
+ className: rootClass,
874
+ "data-polyx-forgot": "sent",
875
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RecoveryStatus, {
876
+ icon: "mail",
877
+ heading: labels.sentHeading,
878
+ footer,
879
+ actions: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
880
+ type: "button",
881
+ className: "polyx-signin__link",
882
+ onClick: () => setSent(false),
883
+ children: labels.differentEmail
884
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
885
+ className: "polyx-signin__link",
886
+ href: signInPath,
887
+ children: labels.backToSignIn
888
+ })] }),
889
+ children: withEmail(labels.sentSubheading, email)
890
+ })
891
+ });
892
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
893
+ className: rootClass,
894
+ "data-polyx-forgot": "request",
895
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
896
+ className: "polyx-signin__card",
897
+ children: [
898
+ header(labels.heading, labels.subheading),
899
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
900
+ className: "polyx-signin__form",
901
+ onSubmit,
902
+ children: [
903
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
904
+ className: "polyx-signin__field",
905
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
906
+ className: "polyx-signin__label",
907
+ children: labels.emailLabel
908
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
909
+ className: "polyx-signin__input",
910
+ type: "email",
911
+ name: "email",
912
+ autoComplete: "username",
913
+ placeholder: labels.emailPlaceholder,
914
+ required: true,
915
+ value: email,
916
+ disabled: submitting,
917
+ onChange: (event) => setEmail(event.target.value)
918
+ })]
919
+ }),
920
+ error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
921
+ role: "alert",
922
+ className: "polyx-signin__error",
923
+ children: error
924
+ }) : null,
925
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
926
+ type: "submit",
927
+ className: "polyx-signin__submit",
928
+ disabled: submitting,
929
+ children: submitting ? labels.submitting : labels.submit
930
+ })
931
+ ]
932
+ }),
933
+ footer
934
+ ]
935
+ })
936
+ });
937
+ }
938
+ //#endregion
939
+ //#region src/components/reset-password.tsx
940
+ const DEFAULT_LABELS = {
941
+ heading: "Choose a new password",
942
+ subheading: "Enter and confirm your new password.",
943
+ newPasswordLabel: "New password",
944
+ confirmPasswordLabel: "Confirm new password",
945
+ passwordPlaceholder: "••••••••",
946
+ submit: "Reset password",
947
+ submitting: "Resetting…",
948
+ successHeading: "Password updated",
949
+ successSubheading: "You can now sign in with your new password.",
950
+ invalidHeading: "This link is invalid or expired",
951
+ invalidSubheading: "Password reset links can be used once and expire quickly. Request a new one.",
952
+ requestNewLink: "Request a new link",
953
+ backToSignIn: "Back to sign in",
954
+ securedBy: "Secured by PolyX",
955
+ passwordMismatch: "Those passwords don't match.",
956
+ weakPassword: "Password must be at least 8 characters.",
957
+ genericError: "Something went wrong. Please try again."
958
+ };
959
+ /** Matches poly-auth's v5 reset floor (F040). */
960
+ const DEFAULT_MIN_LENGTH = 8;
961
+ /**
962
+ * The reset-completion form (poly-x v5 / F040). Reads the opaque token from the URL,
963
+ * collects a new password, and POSTs to the SDK's BFF (`@poly-x/next` `resetPassword`),
964
+ * which redeems the token single-use server-side. The token is never decoded in the
965
+ * browser — identity, validity, and expiry are all enforced by the backend.
966
+ *
967
+ * Reuses the `<SignIn/>` styling: `import "@poly-x/react/styles.css"` once, then theme
968
+ * via the `--polyx-*` custom properties. Target `.polyx-reset` for form-specific tweaks.
969
+ */
970
+ function ResetPassword(props) {
971
+ const action = props.action ?? "/api/polyx/reset-password";
972
+ const minLength = props.minLength ?? DEFAULT_MIN_LENGTH;
973
+ const signInPath = props.signInPath ?? "/sign-in";
974
+ const forgotPasswordPath = props.forgotPasswordPath ?? "/forgot-password";
975
+ const labels = {
976
+ ...DEFAULT_LABELS,
977
+ ...props.labels
978
+ };
979
+ const [token] = (0, react.useState)(() => props.token ?? (typeof window !== "undefined" ? new URLSearchParams(window.location.search).get("token") ?? "" : ""));
980
+ const [newPassword, setNewPassword] = (0, react.useState)("");
981
+ const [confirmPassword, setConfirmPassword] = (0, react.useState)("");
982
+ const [submitting, setSubmitting] = (0, react.useState)(false);
983
+ const [error, setError] = (0, react.useState)(null);
984
+ const [step, setStep] = (0, react.useState)(token ? "form" : "invalid");
985
+ async function onSubmit(event) {
986
+ event.preventDefault();
987
+ if (newPassword !== confirmPassword) {
988
+ setError(labels.passwordMismatch);
989
+ return;
990
+ }
991
+ setSubmitting(true);
992
+ setError(null);
993
+ try {
994
+ switch ((await (await fetch(action, {
995
+ method: "POST",
996
+ headers: { "content-type": "application/json" },
997
+ body: JSON.stringify({
998
+ token,
999
+ newPassword,
1000
+ confirmPassword
1001
+ })
1002
+ })).json().catch(() => ({}))).status) {
1003
+ case "success":
1004
+ setStep("success");
1005
+ if (props.afterResetPath) window.location.assign(props.afterResetPath);
1006
+ return;
1007
+ case "weak_password":
1008
+ setError(labels.weakPassword);
1009
+ return;
1010
+ case "password_mismatch":
1011
+ setError(labels.passwordMismatch);
1012
+ return;
1013
+ case "invalid_or_expired":
1014
+ setStep("invalid");
1015
+ return;
1016
+ default:
1017
+ setError(labels.genericError);
1018
+ return;
1019
+ }
1020
+ } catch {
1021
+ setError(labels.genericError);
1022
+ } finally {
1023
+ setSubmitting(false);
1024
+ }
1025
+ }
1026
+ const rootClass = [
1027
+ "polyx-signin",
1028
+ "polyx-reset",
1029
+ props.className
1030
+ ].filter(Boolean).join(" ");
1031
+ const footer = props.showSecuredBy === false ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1032
+ className: "polyx-signin__footer",
1033
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
1034
+ className: "polyx-signin__shield",
1035
+ viewBox: "0 0 16 16",
1036
+ fill: "currentColor",
1037
+ "aria-hidden": "true",
1038
+ focusable: "false",
1039
+ children: /* @__PURE__ */ (0, react_jsx_runtime.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" })
1040
+ }), labels.securedBy]
1041
+ });
1042
+ const header = (heading, subheading) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1043
+ className: "polyx-signin__header",
1044
+ children: [
1045
+ props.logo ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1046
+ className: "polyx-signin__logo",
1047
+ children: props.logo
1048
+ }) : null,
1049
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", {
1050
+ className: "polyx-signin__heading",
1051
+ children: heading
1052
+ }),
1053
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1054
+ className: "polyx-signin__subheading",
1055
+ children: subheading
1056
+ })
1057
+ ]
1058
+ });
1059
+ if (step === "success") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1060
+ className: rootClass,
1061
+ "data-polyx-reset": "success",
1062
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RecoveryStatus, {
1063
+ icon: "check",
1064
+ heading: labels.successHeading,
1065
+ footer,
1066
+ actions: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
1067
+ className: "polyx-signin__link",
1068
+ href: signInPath,
1069
+ children: labels.backToSignIn
1070
+ }),
1071
+ children: labels.successSubheading
1072
+ })
1073
+ });
1074
+ if (step === "invalid") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1075
+ className: rootClass,
1076
+ "data-polyx-reset": "invalid",
1077
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RecoveryStatus, {
1078
+ icon: "warning",
1079
+ tone: "danger",
1080
+ heading: labels.invalidHeading,
1081
+ footer,
1082
+ actions: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
1083
+ className: "polyx-signin__link",
1084
+ href: forgotPasswordPath,
1085
+ children: labels.requestNewLink
1086
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
1087
+ className: "polyx-signin__link",
1088
+ href: signInPath,
1089
+ children: labels.backToSignIn
1090
+ })] }),
1091
+ children: labels.invalidSubheading
1092
+ })
1093
+ });
1094
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1095
+ className: rootClass,
1096
+ "data-polyx-reset": step,
1097
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1098
+ className: "polyx-signin__card",
1099
+ children: [
1100
+ header(labels.heading, labels.subheading),
1101
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
1102
+ className: "polyx-signin__form",
1103
+ onSubmit,
1104
+ children: [
1105
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1106
+ className: "polyx-signin__field",
1107
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1108
+ className: "polyx-signin__label",
1109
+ children: labels.newPasswordLabel
1110
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1111
+ className: "polyx-signin__input",
1112
+ type: "password",
1113
+ name: "newPassword",
1114
+ autoComplete: "new-password",
1115
+ placeholder: labels.passwordPlaceholder,
1116
+ required: true,
1117
+ minLength,
1118
+ value: newPassword,
1119
+ disabled: submitting,
1120
+ onChange: (event) => setNewPassword(event.target.value)
1121
+ })]
1122
+ }),
1123
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1124
+ className: "polyx-signin__field",
1125
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1126
+ className: "polyx-signin__label",
1127
+ children: labels.confirmPasswordLabel
1128
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1129
+ className: "polyx-signin__input",
1130
+ type: "password",
1131
+ name: "confirmPassword",
1132
+ autoComplete: "new-password",
1133
+ placeholder: labels.passwordPlaceholder,
1134
+ required: true,
1135
+ minLength,
1136
+ value: confirmPassword,
1137
+ disabled: submitting,
1138
+ onChange: (event) => setConfirmPassword(event.target.value)
1139
+ })]
1140
+ }),
1141
+ error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1142
+ role: "alert",
1143
+ className: "polyx-signin__error",
1144
+ children: error
1145
+ }) : null,
1146
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1147
+ type: "submit",
1148
+ className: "polyx-signin__submit",
1149
+ disabled: submitting,
1150
+ children: submitting ? labels.submitting : labels.submit
1151
+ })
1152
+ ]
1153
+ }),
1154
+ footer
1155
+ ]
1156
+ })
1157
+ });
1158
+ }
1159
+ //#endregion
671
1160
  //#region src/components/auth-callback.tsx
672
1161
  /** Drop on your `/auth/callback` route: completes the sign-in exchange on mount. */
673
1162
  function AuthCallback({ children = null }) {
@@ -689,11 +1178,13 @@ const version = "0.0.0";
689
1178
  exports.AuthCallback = AuthCallback;
690
1179
  exports.BroadcastAuthChannel = BroadcastAuthChannel;
691
1180
  exports.BroadcastChannelSessionChannel = BroadcastChannelSessionChannel;
1181
+ exports.ForgotPassword = ForgotPassword;
692
1182
  exports.PACKAGE_NAME = PACKAGE_NAME;
693
1183
  exports.PolyXContext = PolyXContext;
694
1184
  exports.PolyXProvider = PolyXProvider;
695
1185
  exports.PopupCancelledError = PopupCancelledError;
696
1186
  exports.Protected = Protected;
1187
+ exports.ResetPassword = ResetPassword;
697
1188
  exports.SessionStoragePkceStore = SessionStoragePkceStore;
698
1189
  exports.SignIn = SignIn;
699
1190
  exports.WebLocksLock = WebLocksLock;
package/dist/index.d.cts 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 };