@dloizides/auth-web 1.2.2 → 1.3.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/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.3.0 (2026-05-31)
4
+
5
+ Additive release for the auth-UX reuse roll-out (feature-reuse-roadmap #1).
6
+ Promotes the "Forgot password?" modal body the product apps each hand-rolled
7
+ into a single shared, **react-query-free** export so katalogos / erevna / kefi
8
+ stop duplicating it. No breaking changes; every 1.2.x export is unchanged.
9
+
10
+ ### Added
11
+
12
+ - **`<ForgotPasswordFields>`** — the embedded, themeable "request a reset link"
13
+ body, sized to live inside an app-owned modal shell (no screen/card wrapper,
14
+ no title — the host modal owns those). Adds the two modal affordances a
15
+ screen-shaped form lacks: a `Cancel` button beside submit and a `Close` button
16
+ on the success state, each rendered only when its handler is supplied. Clears
17
+ itself when `visible` flips to `false` so reopening starts fresh.
18
+ - **`useForgotPasswordSubmit`** — the headless, **react-query-free** forgot-
19
+ password logic: a plain `useState` + `client.forgotPassword(...)` promise
20
+ chain that runs on a provider-less login route (where the existing
21
+ `useBffForgotPassword` `useMutation` crashes with "No QueryClient set"). Email
22
+ validation gating via `canSubmit`, anti-enumeration success, `hasNetworkError`,
23
+ and `reset()`. Also exports the `isValidForgotPasswordEmail` helper.
24
+ - **`ForgotPasswordFieldsLabels`** + **`DEFAULT_FORGOT_PASSWORD_FIELDS_LABELS`** —
25
+ the forgot-password label bag plus the modal `cancel` / `close` strings.
26
+ - New `AuthTestIds` entries `forgotPasswordCancelButton` /
27
+ `forgotPasswordCloseButton`.
28
+
29
+ ### Notes
30
+
31
+ - The existing screen-shaped `<ForgotPasswordForm>` (react-query-based) is
32
+ unchanged. A future cleanup may re-base it on `useForgotPasswordSubmit` to drop
33
+ its react-query coupling; deferred to keep this release additive.
34
+
3
35
  ## 1.2.0 (2026-05-22)
4
36
 
5
37
  Additive release for Phase 3d of the unified-auth plan — the event-scoped PIN
package/dist/index.d.mts CHANGED
@@ -52,6 +52,17 @@ interface ForgotPasswordFormLabels {
52
52
  /** Shown when the entered value is not a valid email. */
53
53
  invalidEmail: string;
54
54
  }
55
+ /**
56
+ * Strings rendered by `<ForgotPasswordFields>` — the embedded modal body. It is
57
+ * the forgot-password form plus the two modal affordances (`cancel` alongside
58
+ * submit, `close` on the success state) that a screen-shaped form has no need of.
59
+ */
60
+ interface ForgotPasswordFieldsLabels extends ForgotPasswordFormLabels {
61
+ /** The "cancel" button shown alongside submit. */
62
+ cancel: string;
63
+ /** The "close" button shown on the success state. */
64
+ close: string;
65
+ }
55
66
  /** Strings rendered by `<OtpForm>` — the two-step email-OTP login form. */
56
67
  interface OtpFormLabels {
57
68
  /** Title shown on the request-a-code step. */
@@ -127,6 +138,7 @@ interface ResetPasswordFormLabels {
127
138
  }
128
139
  declare const DEFAULT_LOGIN_LABELS: LoginFormLabels;
129
140
  declare const DEFAULT_FORGOT_PASSWORD_LABELS: ForgotPasswordFormLabels;
141
+ declare const DEFAULT_FORGOT_PASSWORD_FIELDS_LABELS: ForgotPasswordFieldsLabels;
130
142
  declare const DEFAULT_OTP_LABELS: OtpFormLabels;
131
143
  declare const DEFAULT_PIN_LABELS: PinFormLabels;
132
144
  declare const DEFAULT_RESET_PASSWORD_LABELS: ResetPasswordFormLabels;
@@ -284,6 +296,56 @@ interface ForgotPasswordFormProps {
284
296
  /** Themeable "request a reset link" form built on `useBffForgotPassword`. */
285
297
  declare function ForgotPasswordForm({ client, theme: themeProp, labels: labelsProp, resetUrlTemplate, onSuccess, testIdPrefix, }: Readonly<ForgotPasswordFormProps>): ReactElement;
286
298
 
299
+ /**
300
+ * `<ForgotPasswordFields>` — the embedded, themeable "request a reset link"
301
+ * body, sized to live INSIDE an app-owned modal shell (not a full screen).
302
+ *
303
+ * The product apps each show "Forgot password?" as a modal launched from their
304
+ * login route, wrapping their own modal chrome (title bar + close affordance)
305
+ * around an identical email field + submit/cancel body. This is that body,
306
+ * promoted into the package so the three apps stop hand-rolling it.
307
+ *
308
+ * Built on the react-query-free `useForgotPasswordSubmit` hook, so it renders +
309
+ * submits on a provider-less login route (where `useMutation` would crash). The
310
+ * backend is anti-enumeration, so a successful request swaps to a generic
311
+ * confirmation that never reveals whether the address is registered.
312
+ *
313
+ * Differs from the screen-shaped `<ForgotPasswordForm>`: no outer screen/card
314
+ * wrapper and no title (the host modal owns those), plus two modal affordances —
315
+ * a `Cancel` button beside submit and a `Close` button on the success state —
316
+ * each rendered only when its handler is supplied.
317
+ */
318
+
319
+ interface ForgotPasswordFieldsProps {
320
+ /** The same-origin BFF client (build it with `createBffAuthClient`). */
321
+ client: BffAuthClient;
322
+ /** Explicit theme; overrides any `<AuthThemeProvider>`. */
323
+ theme?: AuthTheme;
324
+ /** Localised copy. Partial — unspecified keys fall back to English defaults. */
325
+ labels?: Partial<ForgotPasswordFieldsLabels>;
326
+ /**
327
+ * Full URL with a `{token}` placeholder, forwarded to the backend so it can
328
+ * build the reset-email link without hardcoding a frontend host.
329
+ */
330
+ resetUrlTemplate?: string;
331
+ /**
332
+ * The host modal's open state. When it flips to `false` the body clears
333
+ * itself, so reopening shows a fresh form regardless of which affordance
334
+ * closed it. Defaults to `true` for non-modal embedding.
335
+ */
336
+ visible?: boolean;
337
+ /** Called once the request succeeds (the generic-confirmation state). */
338
+ onSuccess?: () => void;
339
+ /** When supplied, renders a Cancel button beside submit. */
340
+ onCancel?: () => void;
341
+ /** When supplied, renders a Close button on the success state. */
342
+ onClose?: () => void;
343
+ /** Prefix applied to every `testID`. */
344
+ testIdPrefix?: string;
345
+ }
346
+ /** Embedded "request a reset link" body for an app-owned modal. */
347
+ declare function ForgotPasswordFields({ client, theme: themeProp, labels: labelsProp, resetUrlTemplate, visible, onSuccess, onCancel, onClose, testIdPrefix, }: Readonly<ForgotPasswordFieldsProps>): ReactElement;
348
+
287
349
  /**
288
350
  * `<ResetPasswordForm>` — the ready-made, themeable "choose a new password"
289
351
  * form.
@@ -438,6 +500,8 @@ declare const AuthTestIds: {
438
500
  readonly forgotPasswordForm: "auth-forgot-form";
439
501
  readonly forgotPasswordEmailInput: "auth-forgot-email";
440
502
  readonly forgotPasswordSubmitButton: "auth-forgot-submit";
503
+ readonly forgotPasswordCancelButton: "auth-forgot-cancel";
504
+ readonly forgotPasswordCloseButton: "auth-forgot-close";
441
505
  readonly forgotPasswordError: "auth-forgot-error";
442
506
  readonly forgotPasswordSuccess: "auth-forgot-success";
443
507
  readonly resetPasswordForm: "auth-reset-form";
@@ -590,6 +654,38 @@ interface UseResetPasswordFormResult {
590
654
  */
591
655
  declare function useResetPasswordForm({ client, token, onSuccess, }: UseResetPasswordFormArgs): UseResetPasswordFormResult;
592
656
 
657
+ /** `true` when `value` (after trimming) looks like an email address. */
658
+ declare function isValidForgotPasswordEmail(value: string): boolean;
659
+ interface UseForgotPasswordSubmitArgs {
660
+ /** The same-origin BFF client (build it with `createBffAuthClient`). */
661
+ client: BffAuthClient;
662
+ /**
663
+ * Full URL with a `{token}` placeholder, forwarded to the backend so it can
664
+ * build the reset-email link without hardcoding a frontend host.
665
+ */
666
+ resetUrlTemplate?: string;
667
+ /** Invoked once the request succeeds (the generic-confirmation state). */
668
+ onSuccess?: () => void;
669
+ }
670
+ interface UseForgotPasswordSubmitResult {
671
+ email: string;
672
+ setEmail: (value: string) => void;
673
+ /** `true` once the request has succeeded (show the generic confirmation). */
674
+ submitted: boolean;
675
+ /** `true` while the request is in flight. */
676
+ isSubmitting: boolean;
677
+ /** `true` when the last submit hit a network / 5xx failure. */
678
+ hasNetworkError: boolean;
679
+ /** `true` when the email is valid and no request is in flight. */
680
+ canSubmit: boolean;
681
+ /** Validate, then fire the request (no-op when `canSubmit` is false). */
682
+ submit: () => void;
683
+ /** Clear all state — call when the modal closes. */
684
+ reset: () => void;
685
+ }
686
+ /** Headless, react-query-free "request a reset link" logic. */
687
+ declare function useForgotPasswordSubmit({ client, resetUrlTemplate, onSuccess, }: UseForgotPasswordSubmitArgs): UseForgotPasswordSubmitResult;
688
+
593
689
  /**
594
690
  * The two discrete steps of the email-OTP login flow.
595
691
  *
@@ -814,4 +910,4 @@ declare function validatePasswordPolicy(password: string): PasswordPolicyError[]
814
910
  /** `true` when the password satisfies every policy rule. */
815
911
  declare function isPasswordValid(password: string): boolean;
816
912
 
817
- export { AuthTestIds, type AuthTheme, type AuthThemeColors, AuthThemeProvider, type AuthThemeProviderProps, type AuthThemeRadii, type AuthThemeSpacing, type AuthThemeTypography, BffAuthStatus, type CreateBffAuthClientOptions, DEFAULT_FORGOT_PASSWORD_LABELS, DEFAULT_LOGIN_LABELS, DEFAULT_OTP_LABELS, DEFAULT_PIN_LABELS, DEFAULT_RESET_PASSWORD_LABELS, ForgotPasswordForm, type ForgotPasswordFormLabels, type ForgotPasswordFormProps, LoginForm, type LoginFormLabels, type LoginFormProps, OtpForm, type OtpFormLabels, type OtpFormProps, OtpLoginStep, PASSWORD_MAX_LENGTH, PASSWORD_MIN_LENGTH, PasswordPolicyError, PinForm, type PinFormLabels, type PinFormProps, ResetPasswordError, ResetPasswordForm, type ResetPasswordFormLabels, type ResetPasswordFormProps, type RoleRoute, type RoleRouteTable, type UseBffAuthOptions, type UseBffAuthResult, type UseBffForgotPasswordOptions, type UseBffResetPasswordOptions, type UseOtpLoginOptions, type UseOtpLoginResult, type UsePinLoginOptions, type UsePinLoginResult, type UseResetPasswordFormArgs, type UseResetPasswordFormResult, collectUserRoles, createBffAuthClient, defaultAuthTheme, isPasswordValid, resolvePostLoginRoute, useAuthTheme, useBffAuth, useBffForgotPassword, useBffResetPassword, useOtpLogin, usePinLogin, useResetPasswordForm, validatePasswordPolicy, withTestIdPrefix };
913
+ export { AuthTestIds, type AuthTheme, type AuthThemeColors, AuthThemeProvider, type AuthThemeProviderProps, type AuthThemeRadii, type AuthThemeSpacing, type AuthThemeTypography, BffAuthStatus, type CreateBffAuthClientOptions, DEFAULT_FORGOT_PASSWORD_FIELDS_LABELS, DEFAULT_FORGOT_PASSWORD_LABELS, DEFAULT_LOGIN_LABELS, DEFAULT_OTP_LABELS, DEFAULT_PIN_LABELS, DEFAULT_RESET_PASSWORD_LABELS, ForgotPasswordFields, type ForgotPasswordFieldsLabels, type ForgotPasswordFieldsProps, ForgotPasswordForm, type ForgotPasswordFormLabels, type ForgotPasswordFormProps, LoginForm, type LoginFormLabels, type LoginFormProps, OtpForm, type OtpFormLabels, type OtpFormProps, OtpLoginStep, PASSWORD_MAX_LENGTH, PASSWORD_MIN_LENGTH, PasswordPolicyError, PinForm, type PinFormLabels, type PinFormProps, ResetPasswordError, ResetPasswordForm, type ResetPasswordFormLabels, type ResetPasswordFormProps, type RoleRoute, type RoleRouteTable, type UseBffAuthOptions, type UseBffAuthResult, type UseBffForgotPasswordOptions, type UseBffResetPasswordOptions, type UseForgotPasswordSubmitArgs, type UseForgotPasswordSubmitResult, type UseOtpLoginOptions, type UseOtpLoginResult, type UsePinLoginOptions, type UsePinLoginResult, type UseResetPasswordFormArgs, type UseResetPasswordFormResult, collectUserRoles, createBffAuthClient, defaultAuthTheme, isPasswordValid, isValidForgotPasswordEmail, resolvePostLoginRoute, useAuthTheme, useBffAuth, useBffForgotPassword, useBffResetPassword, useForgotPasswordSubmit, useOtpLogin, usePinLogin, useResetPasswordForm, validatePasswordPolicy, withTestIdPrefix };
package/dist/index.d.ts CHANGED
@@ -52,6 +52,17 @@ interface ForgotPasswordFormLabels {
52
52
  /** Shown when the entered value is not a valid email. */
53
53
  invalidEmail: string;
54
54
  }
55
+ /**
56
+ * Strings rendered by `<ForgotPasswordFields>` — the embedded modal body. It is
57
+ * the forgot-password form plus the two modal affordances (`cancel` alongside
58
+ * submit, `close` on the success state) that a screen-shaped form has no need of.
59
+ */
60
+ interface ForgotPasswordFieldsLabels extends ForgotPasswordFormLabels {
61
+ /** The "cancel" button shown alongside submit. */
62
+ cancel: string;
63
+ /** The "close" button shown on the success state. */
64
+ close: string;
65
+ }
55
66
  /** Strings rendered by `<OtpForm>` — the two-step email-OTP login form. */
56
67
  interface OtpFormLabels {
57
68
  /** Title shown on the request-a-code step. */
@@ -127,6 +138,7 @@ interface ResetPasswordFormLabels {
127
138
  }
128
139
  declare const DEFAULT_LOGIN_LABELS: LoginFormLabels;
129
140
  declare const DEFAULT_FORGOT_PASSWORD_LABELS: ForgotPasswordFormLabels;
141
+ declare const DEFAULT_FORGOT_PASSWORD_FIELDS_LABELS: ForgotPasswordFieldsLabels;
130
142
  declare const DEFAULT_OTP_LABELS: OtpFormLabels;
131
143
  declare const DEFAULT_PIN_LABELS: PinFormLabels;
132
144
  declare const DEFAULT_RESET_PASSWORD_LABELS: ResetPasswordFormLabels;
@@ -284,6 +296,56 @@ interface ForgotPasswordFormProps {
284
296
  /** Themeable "request a reset link" form built on `useBffForgotPassword`. */
285
297
  declare function ForgotPasswordForm({ client, theme: themeProp, labels: labelsProp, resetUrlTemplate, onSuccess, testIdPrefix, }: Readonly<ForgotPasswordFormProps>): ReactElement;
286
298
 
299
+ /**
300
+ * `<ForgotPasswordFields>` — the embedded, themeable "request a reset link"
301
+ * body, sized to live INSIDE an app-owned modal shell (not a full screen).
302
+ *
303
+ * The product apps each show "Forgot password?" as a modal launched from their
304
+ * login route, wrapping their own modal chrome (title bar + close affordance)
305
+ * around an identical email field + submit/cancel body. This is that body,
306
+ * promoted into the package so the three apps stop hand-rolling it.
307
+ *
308
+ * Built on the react-query-free `useForgotPasswordSubmit` hook, so it renders +
309
+ * submits on a provider-less login route (where `useMutation` would crash). The
310
+ * backend is anti-enumeration, so a successful request swaps to a generic
311
+ * confirmation that never reveals whether the address is registered.
312
+ *
313
+ * Differs from the screen-shaped `<ForgotPasswordForm>`: no outer screen/card
314
+ * wrapper and no title (the host modal owns those), plus two modal affordances —
315
+ * a `Cancel` button beside submit and a `Close` button on the success state —
316
+ * each rendered only when its handler is supplied.
317
+ */
318
+
319
+ interface ForgotPasswordFieldsProps {
320
+ /** The same-origin BFF client (build it with `createBffAuthClient`). */
321
+ client: BffAuthClient;
322
+ /** Explicit theme; overrides any `<AuthThemeProvider>`. */
323
+ theme?: AuthTheme;
324
+ /** Localised copy. Partial — unspecified keys fall back to English defaults. */
325
+ labels?: Partial<ForgotPasswordFieldsLabels>;
326
+ /**
327
+ * Full URL with a `{token}` placeholder, forwarded to the backend so it can
328
+ * build the reset-email link without hardcoding a frontend host.
329
+ */
330
+ resetUrlTemplate?: string;
331
+ /**
332
+ * The host modal's open state. When it flips to `false` the body clears
333
+ * itself, so reopening shows a fresh form regardless of which affordance
334
+ * closed it. Defaults to `true` for non-modal embedding.
335
+ */
336
+ visible?: boolean;
337
+ /** Called once the request succeeds (the generic-confirmation state). */
338
+ onSuccess?: () => void;
339
+ /** When supplied, renders a Cancel button beside submit. */
340
+ onCancel?: () => void;
341
+ /** When supplied, renders a Close button on the success state. */
342
+ onClose?: () => void;
343
+ /** Prefix applied to every `testID`. */
344
+ testIdPrefix?: string;
345
+ }
346
+ /** Embedded "request a reset link" body for an app-owned modal. */
347
+ declare function ForgotPasswordFields({ client, theme: themeProp, labels: labelsProp, resetUrlTemplate, visible, onSuccess, onCancel, onClose, testIdPrefix, }: Readonly<ForgotPasswordFieldsProps>): ReactElement;
348
+
287
349
  /**
288
350
  * `<ResetPasswordForm>` — the ready-made, themeable "choose a new password"
289
351
  * form.
@@ -438,6 +500,8 @@ declare const AuthTestIds: {
438
500
  readonly forgotPasswordForm: "auth-forgot-form";
439
501
  readonly forgotPasswordEmailInput: "auth-forgot-email";
440
502
  readonly forgotPasswordSubmitButton: "auth-forgot-submit";
503
+ readonly forgotPasswordCancelButton: "auth-forgot-cancel";
504
+ readonly forgotPasswordCloseButton: "auth-forgot-close";
441
505
  readonly forgotPasswordError: "auth-forgot-error";
442
506
  readonly forgotPasswordSuccess: "auth-forgot-success";
443
507
  readonly resetPasswordForm: "auth-reset-form";
@@ -590,6 +654,38 @@ interface UseResetPasswordFormResult {
590
654
  */
591
655
  declare function useResetPasswordForm({ client, token, onSuccess, }: UseResetPasswordFormArgs): UseResetPasswordFormResult;
592
656
 
657
+ /** `true` when `value` (after trimming) looks like an email address. */
658
+ declare function isValidForgotPasswordEmail(value: string): boolean;
659
+ interface UseForgotPasswordSubmitArgs {
660
+ /** The same-origin BFF client (build it with `createBffAuthClient`). */
661
+ client: BffAuthClient;
662
+ /**
663
+ * Full URL with a `{token}` placeholder, forwarded to the backend so it can
664
+ * build the reset-email link without hardcoding a frontend host.
665
+ */
666
+ resetUrlTemplate?: string;
667
+ /** Invoked once the request succeeds (the generic-confirmation state). */
668
+ onSuccess?: () => void;
669
+ }
670
+ interface UseForgotPasswordSubmitResult {
671
+ email: string;
672
+ setEmail: (value: string) => void;
673
+ /** `true` once the request has succeeded (show the generic confirmation). */
674
+ submitted: boolean;
675
+ /** `true` while the request is in flight. */
676
+ isSubmitting: boolean;
677
+ /** `true` when the last submit hit a network / 5xx failure. */
678
+ hasNetworkError: boolean;
679
+ /** `true` when the email is valid and no request is in flight. */
680
+ canSubmit: boolean;
681
+ /** Validate, then fire the request (no-op when `canSubmit` is false). */
682
+ submit: () => void;
683
+ /** Clear all state — call when the modal closes. */
684
+ reset: () => void;
685
+ }
686
+ /** Headless, react-query-free "request a reset link" logic. */
687
+ declare function useForgotPasswordSubmit({ client, resetUrlTemplate, onSuccess, }: UseForgotPasswordSubmitArgs): UseForgotPasswordSubmitResult;
688
+
593
689
  /**
594
690
  * The two discrete steps of the email-OTP login flow.
595
691
  *
@@ -814,4 +910,4 @@ declare function validatePasswordPolicy(password: string): PasswordPolicyError[]
814
910
  /** `true` when the password satisfies every policy rule. */
815
911
  declare function isPasswordValid(password: string): boolean;
816
912
 
817
- export { AuthTestIds, type AuthTheme, type AuthThemeColors, AuthThemeProvider, type AuthThemeProviderProps, type AuthThemeRadii, type AuthThemeSpacing, type AuthThemeTypography, BffAuthStatus, type CreateBffAuthClientOptions, DEFAULT_FORGOT_PASSWORD_LABELS, DEFAULT_LOGIN_LABELS, DEFAULT_OTP_LABELS, DEFAULT_PIN_LABELS, DEFAULT_RESET_PASSWORD_LABELS, ForgotPasswordForm, type ForgotPasswordFormLabels, type ForgotPasswordFormProps, LoginForm, type LoginFormLabels, type LoginFormProps, OtpForm, type OtpFormLabels, type OtpFormProps, OtpLoginStep, PASSWORD_MAX_LENGTH, PASSWORD_MIN_LENGTH, PasswordPolicyError, PinForm, type PinFormLabels, type PinFormProps, ResetPasswordError, ResetPasswordForm, type ResetPasswordFormLabels, type ResetPasswordFormProps, type RoleRoute, type RoleRouteTable, type UseBffAuthOptions, type UseBffAuthResult, type UseBffForgotPasswordOptions, type UseBffResetPasswordOptions, type UseOtpLoginOptions, type UseOtpLoginResult, type UsePinLoginOptions, type UsePinLoginResult, type UseResetPasswordFormArgs, type UseResetPasswordFormResult, collectUserRoles, createBffAuthClient, defaultAuthTheme, isPasswordValid, resolvePostLoginRoute, useAuthTheme, useBffAuth, useBffForgotPassword, useBffResetPassword, useOtpLogin, usePinLogin, useResetPasswordForm, validatePasswordPolicy, withTestIdPrefix };
913
+ export { AuthTestIds, type AuthTheme, type AuthThemeColors, AuthThemeProvider, type AuthThemeProviderProps, type AuthThemeRadii, type AuthThemeSpacing, type AuthThemeTypography, BffAuthStatus, type CreateBffAuthClientOptions, DEFAULT_FORGOT_PASSWORD_FIELDS_LABELS, DEFAULT_FORGOT_PASSWORD_LABELS, DEFAULT_LOGIN_LABELS, DEFAULT_OTP_LABELS, DEFAULT_PIN_LABELS, DEFAULT_RESET_PASSWORD_LABELS, ForgotPasswordFields, type ForgotPasswordFieldsLabels, type ForgotPasswordFieldsProps, ForgotPasswordForm, type ForgotPasswordFormLabels, type ForgotPasswordFormProps, LoginForm, type LoginFormLabels, type LoginFormProps, OtpForm, type OtpFormLabels, type OtpFormProps, OtpLoginStep, PASSWORD_MAX_LENGTH, PASSWORD_MIN_LENGTH, PasswordPolicyError, PinForm, type PinFormLabels, type PinFormProps, ResetPasswordError, ResetPasswordForm, type ResetPasswordFormLabels, type ResetPasswordFormProps, type RoleRoute, type RoleRouteTable, type UseBffAuthOptions, type UseBffAuthResult, type UseBffForgotPasswordOptions, type UseBffResetPasswordOptions, type UseForgotPasswordSubmitArgs, type UseForgotPasswordSubmitResult, type UseOtpLoginOptions, type UseOtpLoginResult, type UsePinLoginOptions, type UsePinLoginResult, type UseResetPasswordFormArgs, type UseResetPasswordFormResult, collectUserRoles, createBffAuthClient, defaultAuthTheme, isPasswordValid, isValidForgotPasswordEmail, resolvePostLoginRoute, useAuthTheme, useBffAuth, useBffForgotPassword, useBffResetPassword, useForgotPasswordSubmit, useOtpLogin, usePinLogin, useResetPasswordForm, validatePasswordPolicy, withTestIdPrefix };
package/dist/index.js CHANGED
@@ -34,6 +34,11 @@ var DEFAULT_FORGOT_PASSWORD_LABELS = {
34
34
  networkError: "Something went wrong. Please try again.",
35
35
  invalidEmail: "Enter a valid email address."
36
36
  };
37
+ var DEFAULT_FORGOT_PASSWORD_FIELDS_LABELS = {
38
+ ...DEFAULT_FORGOT_PASSWORD_LABELS,
39
+ cancel: "Cancel",
40
+ close: "Close"
41
+ };
37
42
  var DEFAULT_OTP_LABELS = {
38
43
  requestTitle: "Sign in with a code",
39
44
  requestDescription: "Enter your email and we will send you a one-time code.",
@@ -92,6 +97,8 @@ var AuthTestIds = {
92
97
  forgotPasswordForm: "auth-forgot-form",
93
98
  forgotPasswordEmailInput: "auth-forgot-email",
94
99
  forgotPasswordSubmitButton: "auth-forgot-submit",
100
+ forgotPasswordCancelButton: "auth-forgot-cancel",
101
+ forgotPasswordCloseButton: "auth-forgot-close",
95
102
  forgotPasswordError: "auth-forgot-error",
96
103
  forgotPasswordSuccess: "auth-forgot-success",
97
104
  resetPasswordForm: "auth-reset-form",
@@ -612,6 +619,182 @@ function ForgotPasswordForm({
612
619
  }
613
620
  ) });
614
621
  }
622
+ var EMAIL_REGEX2 = /^[^@\s]+@[^.\s]+\.[^\s]+$/;
623
+ function isValidForgotPasswordEmail(value) {
624
+ return EMAIL_REGEX2.test(value.trim());
625
+ }
626
+ function useForgotPasswordSubmit({
627
+ client,
628
+ resetUrlTemplate,
629
+ onSuccess
630
+ }) {
631
+ const [email, setEmail] = react.useState("");
632
+ const [submitted, setSubmitted] = react.useState(false);
633
+ const [isSubmitting, setIsSubmitting] = react.useState(false);
634
+ const [hasNetworkError, setHasNetworkError] = react.useState(false);
635
+ const canSubmit = isValidForgotPasswordEmail(email) && !isSubmitting;
636
+ const submit = react.useCallback(() => {
637
+ const target = email.trim();
638
+ if (!isValidForgotPasswordEmail(target) || isSubmitting) {
639
+ return;
640
+ }
641
+ setHasNetworkError(false);
642
+ setIsSubmitting(true);
643
+ const request = { email: target, resetUrlTemplate };
644
+ client.forgotPassword(request).then(() => {
645
+ setSubmitted(true);
646
+ onSuccess?.();
647
+ }).catch(() => setHasNetworkError(true)).finally(() => setIsSubmitting(false));
648
+ }, [client, email, isSubmitting, resetUrlTemplate, onSuccess]);
649
+ const reset = react.useCallback(() => {
650
+ setEmail("");
651
+ setSubmitted(false);
652
+ setIsSubmitting(false);
653
+ setHasNetworkError(false);
654
+ }, []);
655
+ return {
656
+ email,
657
+ setEmail,
658
+ submitted,
659
+ isSubmitting,
660
+ hasNetworkError,
661
+ canSubmit,
662
+ submit,
663
+ reset
664
+ };
665
+ }
666
+ var SECONDARY_BORDER_WIDTH = 1;
667
+ function useModalStyles(theme) {
668
+ return react.useMemo(
669
+ () => reactNative.StyleSheet.create({
670
+ body: { padding: theme.spacing.md },
671
+ actions: {
672
+ flexDirection: "row",
673
+ justifyContent: "flex-end",
674
+ alignItems: "center",
675
+ marginTop: theme.spacing.md,
676
+ gap: theme.spacing.sm
677
+ },
678
+ secondaryButton: {
679
+ borderRadius: theme.radii.input,
680
+ paddingVertical: theme.spacing.sm,
681
+ paddingHorizontal: theme.spacing.md,
682
+ borderWidth: SECONDARY_BORDER_WIDTH,
683
+ borderColor: theme.colors.border
684
+ },
685
+ secondaryButtonText: {
686
+ fontSize: theme.typography.body,
687
+ fontWeight: "600",
688
+ color: theme.colors.text
689
+ }
690
+ }),
691
+ [theme]
692
+ );
693
+ }
694
+ function ForgotPasswordFields({
695
+ client,
696
+ theme: themeProp,
697
+ labels: labelsProp,
698
+ resetUrlTemplate,
699
+ visible = true,
700
+ onSuccess,
701
+ onCancel,
702
+ onClose,
703
+ testIdPrefix
704
+ }) {
705
+ const theme = useAuthTheme(themeProp);
706
+ const styles = useAuthStyles(theme);
707
+ const modalStyles = useModalStyles(theme);
708
+ const labels = react.useMemo(
709
+ () => ({ ...DEFAULT_FORGOT_PASSWORD_FIELDS_LABELS, ...labelsProp }),
710
+ [labelsProp]
711
+ );
712
+ const form = useForgotPasswordSubmit({ client, resetUrlTemplate, onSuccess });
713
+ const { reset } = form;
714
+ react.useEffect(() => {
715
+ if (!visible) {
716
+ reset();
717
+ }
718
+ }, [visible, reset]);
719
+ const submitButtonStyle = form.canSubmit ? styles.primaryButton : [styles.primaryButton, styles.primaryButtonDisabled];
720
+ const handleCancel = () => {
721
+ form.reset();
722
+ onCancel?.();
723
+ };
724
+ const handleClose = () => {
725
+ form.reset();
726
+ onClose?.();
727
+ };
728
+ if (form.submitted) {
729
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: modalStyles.body, testID: withTestIdPrefix(AuthTestIds.forgotPasswordForm, testIdPrefix), children: [
730
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles.successText, testID: withTestIdPrefix(AuthTestIds.forgotPasswordSuccess, testIdPrefix), children: labels.successMessage }),
731
+ onClose !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: modalStyles.actions, children: /* @__PURE__ */ jsxRuntime.jsx(
732
+ reactNative.TouchableOpacity,
733
+ {
734
+ accessibilityHint: labels.close,
735
+ accessibilityLabel: labels.close,
736
+ accessibilityRole: "button",
737
+ style: styles.primaryButton,
738
+ testID: withTestIdPrefix(AuthTestIds.forgotPasswordCloseButton, testIdPrefix),
739
+ onPress: handleClose,
740
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles.primaryButtonText, children: labels.close })
741
+ }
742
+ ) }) : null
743
+ ] });
744
+ }
745
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: modalStyles.body, testID: withTestIdPrefix(AuthTestIds.forgotPasswordForm, testIdPrefix), children: [
746
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles.subtitle, children: labels.description }),
747
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles.fieldGroup, children: [
748
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles.label, children: labels.emailLabel }),
749
+ /* @__PURE__ */ jsxRuntime.jsx(
750
+ reactNative.TextInput,
751
+ {
752
+ accessibilityHint: labels.emailPlaceholder,
753
+ accessibilityLabel: labels.emailLabel,
754
+ autoCapitalize: "none",
755
+ autoCorrect: false,
756
+ editable: !form.isSubmitting,
757
+ keyboardType: "email-address",
758
+ placeholder: labels.emailPlaceholder,
759
+ placeholderTextColor: theme.colors.textSecondary,
760
+ style: styles.input,
761
+ testID: withTestIdPrefix(AuthTestIds.forgotPasswordEmailInput, testIdPrefix),
762
+ value: form.email,
763
+ onChangeText: form.setEmail
764
+ }
765
+ )
766
+ ] }),
767
+ form.hasNetworkError ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles.errorText, testID: withTestIdPrefix(AuthTestIds.forgotPasswordError, testIdPrefix), children: labels.networkError }) : null,
768
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: modalStyles.actions, children: [
769
+ onCancel !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(
770
+ reactNative.TouchableOpacity,
771
+ {
772
+ accessibilityHint: labels.cancel,
773
+ accessibilityLabel: labels.cancel,
774
+ accessibilityRole: "button",
775
+ disabled: form.isSubmitting,
776
+ style: modalStyles.secondaryButton,
777
+ testID: withTestIdPrefix(AuthTestIds.forgotPasswordCancelButton, testIdPrefix),
778
+ onPress: handleCancel,
779
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: modalStyles.secondaryButtonText, children: labels.cancel })
780
+ }
781
+ ) : null,
782
+ /* @__PURE__ */ jsxRuntime.jsx(
783
+ reactNative.TouchableOpacity,
784
+ {
785
+ accessibilityHint: labels.submit,
786
+ accessibilityLabel: form.isSubmitting ? labels.submitting : labels.submit,
787
+ accessibilityRole: "button",
788
+ disabled: !form.canSubmit,
789
+ style: submitButtonStyle,
790
+ testID: withTestIdPrefix(AuthTestIds.forgotPasswordSubmitButton, testIdPrefix),
791
+ onPress: form.submit,
792
+ children: form.isSubmitting ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { color: theme.colors.onPrimary, size: "small" }) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles.primaryButtonText, children: labels.submit })
793
+ }
794
+ )
795
+ ] })
796
+ ] });
797
+ }
615
798
 
616
799
  // src/hooks/ResetPasswordError.ts
617
800
  var ResetPasswordError = /* @__PURE__ */ ((ResetPasswordError2) => {
@@ -1113,9 +1296,9 @@ function useOtpLogin(options) {
1113
1296
  ]
1114
1297
  );
1115
1298
  }
1116
- var EMAIL_REGEX2 = /^[^@\s]+@[^.\s]+\.[^\s]+$/;
1299
+ var EMAIL_REGEX3 = /^[^@\s]+@[^.\s]+\.[^\s]+$/;
1117
1300
  function isValidEmail2(value) {
1118
- return EMAIL_REGEX2.test(value);
1301
+ return EMAIL_REGEX3.test(value);
1119
1302
  }
1120
1303
  function transportErrorFor(step, error, labels) {
1121
1304
  if (error === null) {
@@ -1396,11 +1579,13 @@ Object.defineProperty(exports, "createFetchHttpClient", {
1396
1579
  exports.AuthTestIds = AuthTestIds;
1397
1580
  exports.AuthThemeProvider = AuthThemeProvider;
1398
1581
  exports.BffAuthStatus = BffAuthStatus;
1582
+ exports.DEFAULT_FORGOT_PASSWORD_FIELDS_LABELS = DEFAULT_FORGOT_PASSWORD_FIELDS_LABELS;
1399
1583
  exports.DEFAULT_FORGOT_PASSWORD_LABELS = DEFAULT_FORGOT_PASSWORD_LABELS;
1400
1584
  exports.DEFAULT_LOGIN_LABELS = DEFAULT_LOGIN_LABELS;
1401
1585
  exports.DEFAULT_OTP_LABELS = DEFAULT_OTP_LABELS;
1402
1586
  exports.DEFAULT_PIN_LABELS = DEFAULT_PIN_LABELS;
1403
1587
  exports.DEFAULT_RESET_PASSWORD_LABELS = DEFAULT_RESET_PASSWORD_LABELS;
1588
+ exports.ForgotPasswordFields = ForgotPasswordFields;
1404
1589
  exports.ForgotPasswordForm = ForgotPasswordForm;
1405
1590
  exports.LoginForm = LoginForm;
1406
1591
  exports.OtpForm = OtpForm;
@@ -1415,11 +1600,13 @@ exports.collectUserRoles = collectUserRoles;
1415
1600
  exports.createBffAuthClient = createBffAuthClient;
1416
1601
  exports.defaultAuthTheme = defaultAuthTheme;
1417
1602
  exports.isPasswordValid = isPasswordValid;
1603
+ exports.isValidForgotPasswordEmail = isValidForgotPasswordEmail;
1418
1604
  exports.resolvePostLoginRoute = resolvePostLoginRoute;
1419
1605
  exports.useAuthTheme = useAuthTheme;
1420
1606
  exports.useBffAuth = useBffAuth;
1421
1607
  exports.useBffForgotPassword = useBffForgotPassword;
1422
1608
  exports.useBffResetPassword = useBffResetPassword;
1609
+ exports.useForgotPasswordSubmit = useForgotPasswordSubmit;
1423
1610
  exports.useOtpLogin = useOtpLogin;
1424
1611
  exports.usePinLogin = usePinLogin;
1425
1612
  exports.useResetPasswordForm = useResetPasswordForm;