@outseta/react 0.2.5 → 0.3.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@outseta/react",
3
- "version": "0.2.5",
3
+ "version": "0.3.1",
4
4
  "description": "React hooks and components for the Outseta API",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -19,7 +19,7 @@
19
19
  },
20
20
  "dependencies": {
21
21
  "@tanstack/react-query": "^5.0.0",
22
- "@outseta/api-client": "0.2.5"
22
+ "@outseta/api-client": "0.3.1"
23
23
  },
24
24
  "peerDependencies": {
25
25
  "react": "^18.0.0 || ^19.0.0"
@@ -0,0 +1,35 @@
1
+ import type { HTMLAttributes, ReactNode } from "react";
2
+ import { useOutseta } from "./OutsetaProvider.js";
3
+
4
+ export interface AuthCtaProps extends HTMLAttributes<HTMLDivElement> {
5
+ /** Message shown before the login/signup actions. */
6
+ message?: ReactNode;
7
+ /** Label for the signup button. */
8
+ signupLabel?: ReactNode;
9
+ /** Label for the login button. */
10
+ loginLabel?: ReactNode;
11
+ }
12
+
13
+ /**
14
+ * Generic login/signup call-to-action for gated content.
15
+ */
16
+ export function AuthCta({
17
+ message = "Sign up or log in to continue.",
18
+ signupLabel = "Sign up",
19
+ loginLabel = "Log in",
20
+ ...props
21
+ }: AuthCtaProps) {
22
+ const { openSignup, openLogin } = useOutseta();
23
+
24
+ return (
25
+ <div {...props}>
26
+ <p>{message}</p>
27
+ <button type="button" onClick={() => openSignup()}>
28
+ {signupLabel}
29
+ </button>{" "}
30
+ <button type="button" onClick={() => openLogin()}>
31
+ {loginLabel}
32
+ </button>
33
+ </div>
34
+ );
35
+ }
@@ -1,42 +1,56 @@
1
1
  import type { ReactNode } from "react";
2
2
  import { useOutseta } from "./OutsetaProvider.js";
3
+ import { AuthCta } from "./AuthCta.js";
4
+ import { UpgradeCta } from "./UpgradeCta.js";
3
5
 
4
6
  export interface ProtectedRouteProps {
5
- /** Plan UIDs that grant access to this route */
7
+ /** Plan UIDs that grant access to this route. */
6
8
  plans: string[];
7
- /** Content to render when access is granted */
9
+ /** Content to render when access is granted. */
8
10
  children: ReactNode;
9
- /** Rendered while checking auth status (defaults to null) */
11
+ /** Rendered while checking auth status. Defaults to null. */
10
12
  loading?: ReactNode;
11
- /** Rendered when user is logged in but lacks the right plan */
13
+ /** Rendered when user is logged in but lacks the right plan. */
12
14
  accessDenied?: ReactNode;
13
- /** Rendered when user is not logged in */
15
+ /** Rendered when user is not logged in. */
14
16
  unauthenticated?: ReactNode;
17
+ /** Message for the default unauthenticated CTA. */
18
+ unauthenticatedMessage?: ReactNode;
19
+ /** Message for the default upgrade/manage subscription CTA. */
20
+ accessDeniedMessage?: ReactNode;
15
21
  }
16
22
 
17
23
  /**
18
24
  * Protects content behind plan-based access control.
19
25
  *
20
- * Checks the current user's subscription plan UID against the
21
- * provided list of allowed plan UIDs.
26
+ * Checks the current user's subscription plan UID against the provided list of
27
+ * allowed plan UIDs. If access is denied, generic login/signup or upgrade CTAs
28
+ * are rendered by default and can be replaced with the `unauthenticated` and
29
+ * `accessDenied` props.
22
30
  */
23
31
  export function ProtectedRoute({
24
32
  plans,
25
33
  children,
26
34
  loading = null,
27
- accessDenied = null,
28
- unauthenticated = null,
35
+ accessDenied,
36
+ unauthenticated,
37
+ unauthenticatedMessage,
38
+ accessDeniedMessage,
29
39
  }: ProtectedRouteProps) {
30
40
  const { user, isLoading } = useOutseta();
31
41
 
32
42
  if (isLoading) return <>{loading}</>;
33
43
 
34
- if (!user) return <>{unauthenticated}</>;
44
+ if (!user) {
45
+ return <>{unauthenticated ?? <AuthCta message={unauthenticatedMessage} />}</>;
46
+ }
35
47
 
36
48
  const userPlanUid = user.Account?.CurrentSubscription?.Plan?.Uid;
37
49
  const hasAccess = userPlanUid ? plans.includes(userPlanUid) : false;
38
50
 
39
- if (!hasAccess) return <>{accessDenied}</>;
51
+ if (!hasAccess) {
52
+ return <>{accessDenied ?? <UpgradeCta message={accessDeniedMessage} />}</>;
53
+ }
40
54
 
41
55
  return <>{children}</>;
42
56
  }
@@ -0,0 +1,33 @@
1
+ import type { HTMLAttributes, ReactNode } from "react";
2
+ import { useOutseta } from "./OutsetaProvider.js";
3
+ import type { ProfileWidgetOptions } from "./types.js";
4
+
5
+ export interface UpgradeCtaProps extends HTMLAttributes<HTMLDivElement> {
6
+ /** Message shown before the upgrade/manage subscription action. */
7
+ message?: ReactNode;
8
+ /** Label for the profile/upgrade button. */
9
+ buttonLabel?: ReactNode;
10
+ /** Options passed to the Outseta profile widget. */
11
+ profileOptions?: ProfileWidgetOptions;
12
+ }
13
+
14
+ /**
15
+ * Generic upgrade/manage-subscription call-to-action for gated content.
16
+ */
17
+ export function UpgradeCta({
18
+ message = "Your current plan does not include access to this content.",
19
+ buttonLabel = "Manage subscription",
20
+ profileOptions = { tab: "planChange" },
21
+ ...props
22
+ }: UpgradeCtaProps) {
23
+ const { openProfile } = useOutseta();
24
+
25
+ return (
26
+ <div {...props}>
27
+ <p>{message}</p>
28
+ <button type="button" onClick={() => openProfile(profileOptions)}>
29
+ {buttonLabel}
30
+ </button>
31
+ </div>
32
+ );
33
+ }
@@ -12,6 +12,10 @@ export { ProfileEmbed } from "./ProfileEmbed.js";
12
12
  export type { ProfileEmbedProps } from "./ProfileEmbed.js";
13
13
  export { ProtectedRoute } from "./ProtectedRoute.js";
14
14
  export type { ProtectedRouteProps } from "./ProtectedRoute.js";
15
+ export { AuthCta } from "./AuthCta.js";
16
+ export type { AuthCtaProps } from "./AuthCta.js";
17
+ export { UpgradeCta } from "./UpgradeCta.js";
18
+ export type { UpgradeCtaProps } from "./UpgradeCta.js";
15
19
  export { PurchaseAddonButton } from "./PurchaseAddonButton.js";
16
20
  export type { PurchaseAddonButtonProps } from "./PurchaseAddonButton.js";
17
21
  export { LogoutButton } from "./LogoutButton.js";
@@ -2633,11 +2633,163 @@ export const EmailListSubscriberStatus = {
2633
2633
  Confirmed: 4,
2634
2634
  } as const;
2635
2635
 
2636
+ export interface TokenPayload {
2637
+ /** @nullable */
2638
+ access_token?: string | null;
2639
+ /** @nullable */
2640
+ authentication_callback_url?: string | null;
2641
+ expires_in?: number;
2642
+ /** @nullable */
2643
+ id_token?: string | null;
2644
+ /** @nullable */
2645
+ token_type?: string | null;
2646
+ }
2647
+
2648
+ /**
2649
+ * Returned when a login cannot complete with a password alone because the
2650
+ user has two-factor authentication enabled. Carries the opaque challenge
2651
+ token that must be echoed back to POST /api/v1/tokens/two-factor
2652
+ (or the recovery / resend / switch-mechanism endpoints) along with enough
2653
+ metadata for a client to render the verification step without a second
2654
+ round-trip. The property names are snake_case to match the wire format of
2655
+ the other token endpoints (see TokenPayload).
2656
+ */
2657
+ export interface TwoFactorChallengePayload {
2658
+ /**
2659
+ * Present and true on the 202 response from POST /api/v1/tokens
2660
+ when the user has at least one verified 2FA method. Absent on the
2661
+ resend / switch-mechanism responses, which only ever follow an
2662
+ already-issued login challenge.
2663
+ * @nullable
2664
+ */
2665
+ two_factor_required?: boolean | null;
2666
+ /**
2667
+ * Present and true instead of two_factor_required
2668
+ when the tenant forces 2FA but the user has not yet enrolled any
2669
+ method. In that case only challenge_token and
2670
+ expires_in are populated and the client must route the
2671
+ user through the mid-login enrollment endpoints
2672
+ (/api/v1/tokens/two-factor/enroll/...) before a token can be issued.
2673
+ * @nullable
2674
+ */
2675
+ two_factor_enrollment_required?: boolean | null;
2676
+ /**
2677
+ * Short-lived signed JWT (audience outseta:2fa-challenge) that
2678
+ identifies this challenge. Echo it back verbatim to complete the login.
2679
+ * @nullable
2680
+ */
2681
+ challenge_token?: string | null;
2682
+ /**
2683
+ * The mechanism this challenge was issued against: Email or
2684
+ Totp (authenticator app). For Email a code has already
2685
+ been sent to the user; for Totp the user reads the current
2686
+ code from their authenticator app and nothing is sent.
2687
+ * @nullable
2688
+ */
2689
+ mechanism?: string | null;
2690
+ /**
2691
+ * A masked view of where an emailed code was sent (e.g. b***@outseta.com),
2692
+ suitable for display. Empty when mechanism is Totp.
2693
+ * @nullable
2694
+ */
2695
+ masked_destination?: string | null;
2696
+ /** Seconds until the challenge expires (600). After this the
2697
+ challenge_token can no longer be verified and the login must restart. */
2698
+ expires_in?: number;
2699
+ /**
2700
+ * Every verified mechanism enrolled for this user (excluding recovery
2701
+ codes), e.g. ["Totp", "Email"]. A client can offer a
2702
+ "use a different method" option for any value other than the current
2703
+ mechanism via POST /api/v1/tokens/two-factor/switch-mechanism.
2704
+ * @nullable
2705
+ */
2706
+ available_mechanisms?: string[] | null;
2707
+ /**
2708
+ * true when the user has a batch of recovery codes on file, in
2709
+ which case POST /api/v1/tokens/two-factor/recovery can be used
2710
+ as a fallback if they cannot produce a primary code.
2711
+ * @nullable
2712
+ */
2713
+ recovery_codes_available?: boolean | null;
2714
+ }
2715
+
2636
2716
  export interface PasswordPayload {
2637
2717
  /** @nullable */
2638
2718
  Password?: string | null;
2639
2719
  }
2640
2720
 
2721
+ /**
2722
+ * Returned from POST /api/v1/tokens/two-factor/enroll/totp/begin when a
2723
+ user forced into 2FA chooses to enroll an authenticator app. Carries the
2724
+ shared secret in the three forms a client may need to present it, plus the
2725
+ challenge token the user's first generated code is confirmed against. The
2726
+ property names are snake_case to match the wire format of the other token
2727
+ endpoints (see TokenPayload).
2728
+ */
2729
+ export interface TwoFactorTotpEnrollmentPayload {
2730
+ /**
2731
+ * Short-lived signed JWT identifying the enrollment-test challenge.
2732
+ Echo it back to POST /api/v1/tokens/two-factor/enroll/totp/confirm
2733
+ along with the first code from the authenticator app.
2734
+ * @nullable
2735
+ */
2736
+ challenge_token?: string | null;
2737
+ /**
2738
+ * The Base32-encoded shared secret. Offer this for manual entry by users
2739
+ who cannot scan the QR code.
2740
+ * @nullable
2741
+ */
2742
+ secret?: string | null;
2743
+ /**
2744
+ * The full otpauth://totp/... URI encoding the secret, issuer and
2745
+ parameters. Most authenticator apps add an account directly from this.
2746
+ * @nullable
2747
+ */
2748
+ otpauth_uri?: string | null;
2749
+ /**
2750
+ * A QR code rendering of otpauth_uri as a PNG, Base64
2751
+ encoded — render it as an <img> for the user to scan.
2752
+ * @nullable
2753
+ */
2754
+ qr_code_png_base64?: string | null;
2755
+ /** Seconds until the enrollment-test challenge expires (600). */
2756
+ expires_in?: number;
2757
+ }
2758
+
2759
+ /**
2760
+ * Returned from the mid-login enrollment confirm endpoints
2761
+ (POST /api/v1/tokens/two-factor/enroll/{email|totp}/confirm) once the
2762
+ user has proven control of the new mechanism. Completes a forced-enrollment
2763
+ login: it both finalizes the JWT access token and surfaces the freshly
2764
+ generated recovery codes. The property names are snake_case to match the
2765
+ wire format of the other token endpoints (see TokenPayload).
2766
+ */
2767
+ export interface TwoFactorEnrollmentConfirmationPayload {
2768
+ /** Always true on a success response; the enrollment is now active. */
2769
+ confirmed?: boolean;
2770
+ /**
2771
+ * The user's recovery codes, generated as part of first-time enrollment.
2772
+ These are shown once and never returned again — prompt the user
2773
+ to store them somewhere safe. Each code is single-use at
2774
+ POST /api/v1/tokens/two-factor/recovery.
2775
+ * @nullable
2776
+ */
2777
+ recovery_codes?: string[] | null;
2778
+ /**
2779
+ * The JWT access token. Use it as Authorization: bearer {access_token}
2780
+ for subsequent API calls — login is complete.
2781
+ * @nullable
2782
+ */
2783
+ access_token?: string | null;
2784
+ /**
2785
+ * Always Bearer.
2786
+ * @nullable
2787
+ */
2788
+ token_type?: string | null;
2789
+ /** Seconds until the access token expires. */
2790
+ expires_in?: number;
2791
+ }
2792
+
2641
2793
  /**
2642
2794
  * @nullable
2643
2795
  */
@@ -5658,6 +5810,69 @@ export interface PersonSegmentRemovedWebhookPayload {
5658
5810
  ActivityEventData?: PersonSegmentRemovedActivityData;
5659
5811
  }
5660
5812
 
5813
+ export type AuthGetTokenParams = {
5814
+ /**
5815
+ * @nullable
5816
+ */
5817
+ data: unknown | null;
5818
+ };
5819
+
5820
+ export type AuthVerifyTwoFactorTokenParams = {
5821
+ /**
5822
+ * @nullable
5823
+ */
5824
+ data: unknown | null;
5825
+ };
5826
+
5827
+ export type AuthResendTwoFactorParams = {
5828
+ /**
5829
+ * @nullable
5830
+ */
5831
+ data: unknown | null;
5832
+ };
5833
+
5834
+ export type AuthSwitchTwoFactorMechanismParams = {
5835
+ /**
5836
+ * @nullable
5837
+ */
5838
+ data: unknown | null;
5839
+ };
5840
+
5841
+ export type AuthVerifyTwoFactorRecoveryParams = {
5842
+ /**
5843
+ * @nullable
5844
+ */
5845
+ data: unknown | null;
5846
+ };
5847
+
5848
+ export type TokenTwoFactorEnrollmentBeginEmailParams = {
5849
+ /**
5850
+ * @nullable
5851
+ */
5852
+ data: unknown | null;
5853
+ };
5854
+
5855
+ export type TokenTwoFactorEnrollmentBeginTotpParams = {
5856
+ /**
5857
+ * @nullable
5858
+ */
5859
+ data: unknown | null;
5860
+ };
5861
+
5862
+ export type TokenTwoFactorEnrollmentConfirmEmailParams = {
5863
+ /**
5864
+ * @nullable
5865
+ */
5866
+ data: unknown | null;
5867
+ };
5868
+
5869
+ export type TokenTwoFactorEnrollmentConfirmTotpParams = {
5870
+ /**
5871
+ * @nullable
5872
+ */
5873
+ data: unknown | null;
5874
+ };
5875
+
5661
5876
  export type CaseGetAllCasesParams = {
5662
5877
  /**
5663
5878
  * Search string