@stigmer/react 3.39.1-dev.20260928194002 → 3.40.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/billing/AutoRechargeCard.js +1 -1
- package/billing/AutoRechargeCard.js.map +1 -1
- package/billing/BillingSection.d.ts +18 -1
- package/billing/BillingSection.d.ts.map +1 -1
- package/billing/BillingSection.js +21 -11
- package/billing/BillingSection.js.map +1 -1
- package/billing/ChangePlanDialog.d.ts +46 -0
- package/billing/ChangePlanDialog.d.ts.map +1 -0
- package/billing/ChangePlanDialog.js +90 -0
- package/billing/ChangePlanDialog.js.map +1 -0
- package/billing/CreditPackGrid.d.ts +1 -1
- package/billing/CreditPackGrid.js +1 -1
- package/billing/LowBalanceBanner.js +1 -1
- package/billing/LowBalanceBanner.js.map +1 -1
- package/billing/PaymentMethodCard.d.ts +9 -1
- package/billing/PaymentMethodCard.d.ts.map +1 -1
- package/billing/PaymentMethodCard.js +2 -2
- package/billing/PaymentMethodCard.js.map +1 -1
- package/billing/PlanCard.d.ts +27 -0
- package/billing/PlanCard.d.ts.map +1 -0
- package/billing/PlanCard.js +68 -0
- package/billing/PlanCard.js.map +1 -0
- package/billing/PlanPicker.d.ts +34 -0
- package/billing/PlanPicker.d.ts.map +1 -0
- package/billing/PlanPicker.js +46 -0
- package/billing/PlanPicker.js.map +1 -0
- package/billing/PlanSection.d.ts +45 -0
- package/billing/PlanSection.d.ts.map +1 -0
- package/billing/PlanSection.js +136 -0
- package/billing/PlanSection.js.map +1 -0
- package/billing/UpgradeNotice.d.ts +47 -0
- package/billing/UpgradeNotice.d.ts.map +1 -0
- package/billing/UpgradeNotice.js +49 -0
- package/billing/UpgradeNotice.js.map +1 -0
- package/billing/credit-packs.d.ts.map +1 -1
- package/billing/credit-packs.js +4 -1
- package/billing/credit-packs.js.map +1 -1
- package/billing/index.d.ts +26 -1
- package/billing/index.d.ts.map +1 -1
- package/billing/index.js +12 -0
- package/billing/index.js.map +1 -1
- package/billing/plan-features.d.ts +60 -0
- package/billing/plan-features.d.ts.map +1 -0
- package/billing/plan-features.js +128 -0
- package/billing/plan-features.js.map +1 -0
- package/billing/plan-state.d.ts +43 -0
- package/billing/plan-state.d.ts.map +1 -0
- package/billing/plan-state.js +69 -0
- package/billing/plan-state.js.map +1 -0
- package/billing/redirect.d.ts +31 -0
- package/billing/redirect.d.ts.map +1 -0
- package/billing/redirect.js +30 -0
- package/billing/redirect.js.map +1 -0
- package/billing/useBillingAccount.d.ts +10 -1
- package/billing/useBillingAccount.d.ts.map +1 -1
- package/billing/useBillingAccount.js +2 -2
- package/billing/useBillingAccount.js.map +1 -1
- package/billing/useChangePlan.d.ts +51 -0
- package/billing/useChangePlan.d.ts.map +1 -0
- package/billing/useChangePlan.js +61 -0
- package/billing/useChangePlan.js.map +1 -0
- package/billing/useCreateBillingPortalSession.d.ts +6 -3
- package/billing/useCreateBillingPortalSession.d.ts.map +1 -1
- package/billing/useCreateBillingPortalSession.js +11 -4
- package/billing/useCreateBillingPortalSession.js.map +1 -1
- package/billing/useCreateCheckoutSession.d.ts +7 -5
- package/billing/useCreateCheckoutSession.d.ts.map +1 -1
- package/billing/useCreateCheckoutSession.js +5 -3
- package/billing/useCreateCheckoutSession.js.map +1 -1
- package/billing/useCreatePaymentMethodSetupSession.d.ts +28 -0
- package/billing/useCreatePaymentMethodSetupSession.d.ts.map +1 -0
- package/billing/useCreatePaymentMethodSetupSession.js +50 -0
- package/billing/useCreatePaymentMethodSetupSession.js.map +1 -0
- package/billing/useEntitlements.d.ts +46 -0
- package/billing/useEntitlements.d.ts.map +1 -0
- package/billing/useEntitlements.js +37 -0
- package/billing/useEntitlements.js.map +1 -0
- package/billing/usePeriodEstimate.d.ts +35 -0
- package/billing/usePeriodEstimate.d.ts.map +1 -0
- package/billing/usePeriodEstimate.js +29 -0
- package/billing/usePeriodEstimate.js.map +1 -0
- package/billing/usePlans.d.ts +38 -0
- package/billing/usePlans.d.ts.map +1 -0
- package/billing/usePlans.js +33 -0
- package/billing/usePlans.js.map +1 -0
- package/billing/useSubscription.d.ts +39 -0
- package/billing/useSubscription.d.ts.map +1 -0
- package/billing/useSubscription.js +40 -0
- package/billing/useSubscription.js.map +1 -0
- package/channel-app/ChannelAppDetailPanel.d.ts +5 -0
- package/channel-app/ChannelAppDetailPanel.d.ts.map +1 -1
- package/channel-app/ChannelAppDetailPanel.js +9 -3
- package/channel-app/ChannelAppDetailPanel.js.map +1 -1
- package/channel-app/ChannelAppListPanel.d.ts +13 -4
- package/channel-app/ChannelAppListPanel.d.ts.map +1 -1
- package/channel-app/ChannelAppListPanel.js +5 -5
- package/channel-app/ChannelAppListPanel.js.map +1 -1
- package/channel-app/copy.d.ts +15 -0
- package/channel-app/copy.d.ts.map +1 -0
- package/channel-app/copy.js +15 -0
- package/channel-app/copy.js.map +1 -0
- package/index.d.ts +4 -2
- package/index.d.ts.map +1 -1
- package/index.js +4 -1
- package/index.js.map +1 -1
- package/internal/features.d.ts +11 -0
- package/internal/features.d.ts.map +1 -0
- package/internal/features.js +42 -0
- package/internal/features.js.map +1 -0
- package/licenses/license-format.d.ts +5 -3
- package/licenses/license-format.d.ts.map +1 -1
- package/licenses/license-format.js +10 -19
- package/licenses/license-format.js.map +1 -1
- package/package.json +5 -5
- package/plan-catalog/PlanCatalogConsole.d.ts +22 -0
- package/plan-catalog/PlanCatalogConsole.d.ts.map +1 -0
- package/plan-catalog/PlanCatalogConsole.js +90 -0
- package/plan-catalog/PlanCatalogConsole.js.map +1 -0
- package/plan-catalog/PlanCreateForm.d.ts +17 -0
- package/plan-catalog/PlanCreateForm.d.ts.map +1 -0
- package/plan-catalog/PlanCreateForm.js +52 -0
- package/plan-catalog/PlanCreateForm.js.map +1 -0
- package/plan-catalog/PlansAccessNotice.d.ts +10 -0
- package/plan-catalog/PlansAccessNotice.d.ts.map +1 -0
- package/plan-catalog/PlansAccessNotice.js +13 -0
- package/plan-catalog/PlansAccessNotice.js.map +1 -0
- package/plan-catalog/index.d.ts +8 -0
- package/plan-catalog/index.d.ts.map +1 -0
- package/plan-catalog/index.js +10 -0
- package/plan-catalog/index.js.map +1 -0
- package/plan-catalog/plan-draft.d.ts +30 -0
- package/plan-catalog/plan-draft.d.ts.map +1 -0
- package/plan-catalog/plan-draft.js +91 -0
- package/plan-catalog/plan-draft.js.map +1 -0
- package/plan-catalog/usePlanMutations.d.ts +42 -0
- package/plan-catalog/usePlanMutations.d.ts.map +1 -0
- package/plan-catalog/usePlanMutations.js +57 -0
- package/plan-catalog/usePlanMutations.js.map +1 -0
- package/settings/ChannelAppsSection.d.ts +7 -0
- package/settings/ChannelAppsSection.d.ts.map +1 -1
- package/settings/ChannelAppsSection.js +15 -2
- package/settings/ChannelAppsSection.js.map +1 -1
- package/settings/TeamsSection.d.ts.map +1 -1
- package/settings/TeamsSection.js +19 -2
- package/settings/TeamsSection.js.map +1 -1
- package/settings/settings-nav.d.ts.map +1 -1
- package/settings/settings-nav.js +7 -1
- package/settings/settings-nav.js.map +1 -1
- package/settings/useSettingsNavGroups.d.ts.map +1 -1
- package/settings/useSettingsNavGroups.js +4 -1
- package/settings/useSettingsNavGroups.js.map +1 -1
- package/src/billing/AutoRechargeCard.tsx +2 -2
- package/src/billing/BillingSection.tsx +68 -11
- package/src/billing/ChangePlanDialog.tsx +221 -0
- package/src/billing/CreditPackGrid.tsx +1 -1
- package/src/billing/LowBalanceBanner.tsx +1 -1
- package/src/billing/PaymentMethodCard.tsx +28 -2
- package/src/billing/PlanCard.tsx +171 -0
- package/src/billing/PlanPicker.tsx +178 -0
- package/src/billing/PlanSection.tsx +234 -0
- package/src/billing/UpgradeNotice.tsx +95 -0
- package/src/billing/credit-packs.ts +4 -1
- package/src/billing/index.ts +26 -1
- package/src/billing/plan-features.ts +162 -0
- package/src/billing/plan-state.ts +92 -0
- package/src/billing/redirect.ts +45 -0
- package/src/billing/useBillingAccount.ts +12 -0
- package/src/billing/useChangePlan.ts +115 -0
- package/src/billing/useCreateBillingPortalSession.ts +17 -6
- package/src/billing/useCreateCheckoutSession.ts +12 -7
- package/src/billing/useCreatePaymentMethodSetupSession.ts +77 -0
- package/src/billing/useEntitlements.ts +80 -0
- package/src/billing/usePeriodEstimate.ts +59 -0
- package/src/billing/usePlans.ts +65 -0
- package/src/billing/useSubscription.ts +70 -0
- package/src/channel-app/ChannelAppDetailPanel.tsx +179 -158
- package/src/channel-app/ChannelAppListPanel.tsx +14 -4
- package/src/channel-app/copy.ts +16 -0
- package/src/index.ts +54 -0
- package/src/internal/features.ts +49 -0
- package/src/licenses/license-format.ts +10 -19
- package/src/plan-catalog/PlanCatalogConsole.tsx +197 -0
- package/src/plan-catalog/PlanCreateForm.tsx +167 -0
- package/src/plan-catalog/PlansAccessNotice.tsx +20 -0
- package/src/plan-catalog/index.ts +14 -0
- package/src/plan-catalog/plan-draft.ts +116 -0
- package/src/plan-catalog/usePlanMutations.ts +94 -0
- package/src/settings/ChannelAppsSection.tsx +19 -2
- package/src/settings/TeamsSection.tsx +20 -2
- package/src/settings/settings-nav.ts +7 -0
- package/src/settings/useSettingsNavGroups.ts +8 -1
- package/src/team/CreateTeamForm.tsx +12 -3
- package/src/team/TeamMembersPanel.tsx +6 -2
- package/team/CreateTeamForm.d.ts +6 -1
- package/team/CreateTeamForm.d.ts.map +1 -1
- package/team/CreateTeamForm.js +4 -2
- package/team/CreateTeamForm.js.map +1 -1
- package/team/TeamMembersPanel.d.ts.map +1 -1
- package/team/TeamMembersPanel.js +3 -1
- package/team/TeamMembersPanel.js.map +1 -1
|
@@ -4,12 +4,13 @@ import { useCallback, useState } from "react";
|
|
|
4
4
|
import type { CreateCreditCheckoutSessionResponse } from "@stigmer/protos/ai/stigmer/billing/v1/io_pb";
|
|
5
5
|
import { useStigmer } from "../hooks.js";
|
|
6
6
|
import { toError } from "../internal/toError.js";
|
|
7
|
+
import { leaveForStripe, type BillingRedirect } from "./redirect.js";
|
|
7
8
|
|
|
8
9
|
/** Parameters for {@link useCreateCheckoutSession}'s `createSession` callback. */
|
|
9
10
|
export interface CreateCheckoutSessionInput {
|
|
10
11
|
/** Organization purchasing credits. */
|
|
11
12
|
readonly orgId: string;
|
|
12
|
-
/** Credit pack to purchase
|
|
13
|
+
/** Credit pack to purchase: its id, e.g. "starter", "growth" or "team" (the pack named Scale). */
|
|
13
14
|
readonly packId: string;
|
|
14
15
|
/** URL to redirect to after successful payment. */
|
|
15
16
|
readonly successUrl: string;
|
|
@@ -22,9 +23,10 @@ export interface UseCreateCheckoutSessionReturn {
|
|
|
22
23
|
/**
|
|
23
24
|
* Create a Stripe Checkout Session and redirect the user.
|
|
24
25
|
*
|
|
25
|
-
* On success,
|
|
26
|
-
*
|
|
27
|
-
*
|
|
26
|
+
* On success, leaves for the Stripe-hosted checkout page: through the
|
|
27
|
+
* hook's `redirect.openUrl` when given, else by navigating this window.
|
|
28
|
+
* The promise resolves with the response before the redirect occurs,
|
|
29
|
+
* allowing callers to perform cleanup if needed.
|
|
28
30
|
*/
|
|
29
31
|
readonly createSession: (
|
|
30
32
|
input: CreateCheckoutSessionInput,
|
|
@@ -60,8 +62,11 @@ export interface UseCreateCheckoutSessionReturn {
|
|
|
60
62
|
* };
|
|
61
63
|
* ```
|
|
62
64
|
*/
|
|
63
|
-
export function useCreateCheckoutSession(
|
|
65
|
+
export function useCreateCheckoutSession(
|
|
66
|
+
redirect?: BillingRedirect,
|
|
67
|
+
): UseCreateCheckoutSessionReturn {
|
|
64
68
|
const stigmer = useStigmer();
|
|
69
|
+
const openUrl = redirect?.openUrl;
|
|
65
70
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
66
71
|
const [error, setError] = useState<Error | null>(null);
|
|
67
72
|
|
|
@@ -83,7 +88,7 @@ export function useCreateCheckoutSession(): UseCreateCheckoutSessionReturn {
|
|
|
83
88
|
});
|
|
84
89
|
|
|
85
90
|
if (response.checkoutUrl) {
|
|
86
|
-
|
|
91
|
+
await leaveForStripe(response.checkoutUrl, { openUrl });
|
|
87
92
|
}
|
|
88
93
|
|
|
89
94
|
return response;
|
|
@@ -94,7 +99,7 @@ export function useCreateCheckoutSession(): UseCreateCheckoutSessionReturn {
|
|
|
94
99
|
setIsSubmitting(false);
|
|
95
100
|
}
|
|
96
101
|
},
|
|
97
|
-
[stigmer],
|
|
102
|
+
[stigmer, openUrl],
|
|
98
103
|
);
|
|
99
104
|
|
|
100
105
|
return { createSession, isSubmitting, error, clearError };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useMemo, useState } from "react";
|
|
4
|
+
import { useStigmer } from "../hooks.js";
|
|
5
|
+
import { toError } from "../internal/toError.js";
|
|
6
|
+
import { billingReturnUrl, leaveForStripe, type BillingRedirect } from "./redirect.js";
|
|
7
|
+
|
|
8
|
+
/** Return value of {@link useCreatePaymentMethodSetupSession}. */
|
|
9
|
+
export interface UseCreatePaymentMethodSetupSessionReturn {
|
|
10
|
+
/**
|
|
11
|
+
* Leave for Stripe's hosted page to save a card, with no charge. Stripe
|
|
12
|
+
* returns to the billing page with `?setup=success` (and `&plan=<id>`
|
|
13
|
+
* when `planId` names the plan the person was subscribing to, so the
|
|
14
|
+
* page can reopen that choice), or without a query when they leave.
|
|
15
|
+
* The saved card becomes the organization's default for every invoice.
|
|
16
|
+
*/
|
|
17
|
+
readonly addPaymentMethod: (orgId: string, planId?: string) => Promise<void>;
|
|
18
|
+
/** `true` while the setup session is being created. */
|
|
19
|
+
readonly isSubmitting: boolean;
|
|
20
|
+
/** Error from the last failed attempt, or `null` when healthy. */
|
|
21
|
+
readonly error: Error | null;
|
|
22
|
+
/** Reset `error` to `null`. */
|
|
23
|
+
readonly clearError: () => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Behaviour hook that saves a payment method through a Stripe-hosted setup
|
|
28
|
+
* page: the card door every subscription needs before its first period.
|
|
29
|
+
*
|
|
30
|
+
* The caller needs `can_manage_billing` on the organization. Pass the
|
|
31
|
+
* host's {@link BillingRedirect} where the window cannot host Stripe's
|
|
32
|
+
* page itself.
|
|
33
|
+
*/
|
|
34
|
+
export function useCreatePaymentMethodSetupSession(
|
|
35
|
+
redirect?: BillingRedirect,
|
|
36
|
+
): UseCreatePaymentMethodSetupSessionReturn {
|
|
37
|
+
const stigmer = useStigmer();
|
|
38
|
+
const openUrl = redirect?.openUrl;
|
|
39
|
+
const returnUrl = redirect?.returnUrl;
|
|
40
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
41
|
+
const [error, setError] = useState<Error | null>(null);
|
|
42
|
+
|
|
43
|
+
const addPaymentMethod = useCallback(
|
|
44
|
+
async (orgId: string, planId?: string): Promise<void> => {
|
|
45
|
+
setIsSubmitting(true);
|
|
46
|
+
setError(null);
|
|
47
|
+
try {
|
|
48
|
+
const billingPage = billingReturnUrl({ returnUrl });
|
|
49
|
+
const success = new URLSearchParams({ setup: "success" });
|
|
50
|
+
if (planId !== undefined && planId !== "") {
|
|
51
|
+
success.set("plan", planId);
|
|
52
|
+
}
|
|
53
|
+
const response = await stigmer.billing.createPaymentMethodSetupSession({
|
|
54
|
+
orgId,
|
|
55
|
+
successUrl: `${billingPage}?${success.toString()}`,
|
|
56
|
+
cancelUrl: billingPage,
|
|
57
|
+
});
|
|
58
|
+
if (response.setupUrl) {
|
|
59
|
+
await leaveForStripe(response.setupUrl, { openUrl });
|
|
60
|
+
}
|
|
61
|
+
} catch (e) {
|
|
62
|
+
const err = toError(e);
|
|
63
|
+
setError(err);
|
|
64
|
+
throw err;
|
|
65
|
+
} finally {
|
|
66
|
+
setIsSubmitting(false);
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
[stigmer.billing, openUrl, returnUrl],
|
|
70
|
+
);
|
|
71
|
+
const clearError = useCallback(() => setError(null), []);
|
|
72
|
+
|
|
73
|
+
return useMemo(
|
|
74
|
+
() => ({ addPaymentMethod, isSubmitting, error, clearError }),
|
|
75
|
+
[addPaymentMethod, isSubmitting, error, clearError],
|
|
76
|
+
);
|
|
77
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
import { create } from "@bufbuild/protobuf";
|
|
5
|
+
import {
|
|
6
|
+
GetEntitlementsInputSchema,
|
|
7
|
+
type GetEntitlementsOutput,
|
|
8
|
+
} from "@stigmer/protos/ai/stigmer/billing/subscription/v1/io_pb";
|
|
9
|
+
import type { Feature } from "@stigmer/protos/ai/stigmer/platform/v1/entitlement_pb";
|
|
10
|
+
import { useStigmer } from "../hooks.js";
|
|
11
|
+
import { useFetch } from "../internal/useFetch.js";
|
|
12
|
+
|
|
13
|
+
/** Options for {@link useEntitlements}. */
|
|
14
|
+
export interface UseEntitlementsOptions {
|
|
15
|
+
/** Skip fetching while `false` (stable no-op). Default `true`. */
|
|
16
|
+
readonly enabled?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Return value of {@link useEntitlements}. */
|
|
20
|
+
export interface UseEntitlementsReturn {
|
|
21
|
+
/**
|
|
22
|
+
* What the organization may do right now and the plan that permits it
|
|
23
|
+
* (`planId` empty on Free), or `null` before the first successful fetch.
|
|
24
|
+
*/
|
|
25
|
+
readonly entitlements: GetEntitlementsOutput | null;
|
|
26
|
+
/**
|
|
27
|
+
* Whether the organization's plan includes `feature` right now; `null`
|
|
28
|
+
* until the entitlements have loaded, so a caller never refuses on a
|
|
29
|
+
* guess.
|
|
30
|
+
*/
|
|
31
|
+
readonly allows: (feature: Feature) => boolean | null;
|
|
32
|
+
/** `true` while the initial fetch is in flight. */
|
|
33
|
+
readonly isLoading: boolean;
|
|
34
|
+
/** Error from the last failed request, or `null` when healthy. */
|
|
35
|
+
readonly error: Error | null;
|
|
36
|
+
/** Discard cached data and re-fetch from the server. */
|
|
37
|
+
readonly refetch: () => void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Data hook that resolves what an organization's plan permits right now.
|
|
42
|
+
*
|
|
43
|
+
* The answer is derived by the server on every read: the live
|
|
44
|
+
* subscription's plan, else Free's. A platform-managed organization
|
|
45
|
+
* resolves through its integrator's plan. Every member may read it
|
|
46
|
+
* (`can_view_billing` is granted to viewers).
|
|
47
|
+
*
|
|
48
|
+
* Cloud-only. Self-hosted editions answer from their license, not a plan,
|
|
49
|
+
* so callers gate on the edition before reading (see `UpgradeNotice`).
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```tsx
|
|
53
|
+
* const { allows } = useEntitlements(orgId);
|
|
54
|
+
* if (allows(Feature.teams) === false) return <UpgradeNotice feature={Feature.teams} />;
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export function useEntitlements(
|
|
58
|
+
orgId: string | null,
|
|
59
|
+
options?: UseEntitlementsOptions,
|
|
60
|
+
): UseEntitlementsReturn {
|
|
61
|
+
const stigmer = useStigmer();
|
|
62
|
+
const enabled = (options?.enabled ?? true) && orgId !== null && orgId !== "";
|
|
63
|
+
const { data: entitlements, isLoading, error, refetch } = useFetch(
|
|
64
|
+
enabled && orgId ? () => stigmer.subscription.getEntitlements(create(GetEntitlementsInputSchema, { orgId })) : null,
|
|
65
|
+
[enabled, orgId, stigmer],
|
|
66
|
+
null as GetEntitlementsOutput | null,
|
|
67
|
+
{ refetchOnWindowFocus: true },
|
|
68
|
+
);
|
|
69
|
+
const features = entitlements?.entitlements?.features;
|
|
70
|
+
return useMemo(
|
|
71
|
+
() => ({
|
|
72
|
+
entitlements,
|
|
73
|
+
allows: (feature: Feature) => (features === undefined ? null : features.includes(feature)),
|
|
74
|
+
isLoading,
|
|
75
|
+
error,
|
|
76
|
+
refetch,
|
|
77
|
+
}),
|
|
78
|
+
[entitlements, features, isLoading, error, refetch],
|
|
79
|
+
);
|
|
80
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
import { create } from "@bufbuild/protobuf";
|
|
5
|
+
import {
|
|
6
|
+
GetPeriodEstimateInputSchema,
|
|
7
|
+
type PeriodEstimate,
|
|
8
|
+
} from "@stigmer/protos/ai/stigmer/billing/subscription/v1/io_pb";
|
|
9
|
+
import { useStigmer } from "../hooks.js";
|
|
10
|
+
import { useFetch } from "../internal/useFetch.js";
|
|
11
|
+
|
|
12
|
+
/** Options for {@link usePeriodEstimate}. */
|
|
13
|
+
export interface UsePeriodEstimateOptions {
|
|
14
|
+
/** Skip fetching while `false`: read only while a subscription is live. Default `true`. */
|
|
15
|
+
readonly enabled?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Return value of {@link usePeriodEstimate}. */
|
|
19
|
+
export interface UsePeriodEstimateReturn {
|
|
20
|
+
/** The current period's estimated invoice, or `null` before the first successful fetch. */
|
|
21
|
+
readonly estimate: PeriodEstimate | null;
|
|
22
|
+
/** `true` while the initial fetch is in flight. */
|
|
23
|
+
readonly isLoading: boolean;
|
|
24
|
+
/** Error from the last failed request, or `null` when healthy. */
|
|
25
|
+
readonly error: Error | null;
|
|
26
|
+
/** Discard cached data and re-fetch from the server. */
|
|
27
|
+
readonly refetch: () => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Data hook that estimates what an organization's current subscription
|
|
32
|
+
* period will be invoiced.
|
|
33
|
+
*
|
|
34
|
+
* The estimate is the invoice the period would close with if no further
|
|
35
|
+
* usage occurred: the plan's minimum for the whole period (or its share of
|
|
36
|
+
* the period's provider cost, whichever is greater), less the commission
|
|
37
|
+
* already collected on tokens, plus managed organizations beyond those
|
|
38
|
+
* included. The server rates it by the same rule the monthly close
|
|
39
|
+
* invoices by, so it agrees with the invoice to the cent.
|
|
40
|
+
*
|
|
41
|
+
* The server answers NOT_FOUND when nothing is live to invoice (Free, or a
|
|
42
|
+
* managed organization), so enable it only while a subscription is live.
|
|
43
|
+
*
|
|
44
|
+
* Cloud-only; the caller needs `can_view_billing` on the organization.
|
|
45
|
+
*/
|
|
46
|
+
export function usePeriodEstimate(
|
|
47
|
+
orgId: string | null,
|
|
48
|
+
options?: UsePeriodEstimateOptions,
|
|
49
|
+
): UsePeriodEstimateReturn {
|
|
50
|
+
const stigmer = useStigmer();
|
|
51
|
+
const enabled = (options?.enabled ?? true) && orgId !== null && orgId !== "";
|
|
52
|
+
const { data: estimate, isLoading, error, refetch } = useFetch(
|
|
53
|
+
enabled && orgId ? () => stigmer.subscription.getPeriodEstimate(create(GetPeriodEstimateInputSchema, { orgId })) : null,
|
|
54
|
+
[enabled, orgId, stigmer],
|
|
55
|
+
null as PeriodEstimate | null,
|
|
56
|
+
{ refetchOnWindowFocus: true },
|
|
57
|
+
);
|
|
58
|
+
return useMemo(() => ({ estimate, isLoading, error, refetch }), [estimate, isLoading, error, refetch]);
|
|
59
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
import { create } from "@bufbuild/protobuf";
|
|
5
|
+
import type { Plan } from "@stigmer/protos/ai/stigmer/billing/plan/v1/api_pb";
|
|
6
|
+
import { ListPlansInputSchema } from "@stigmer/protos/ai/stigmer/billing/plan/v1/io_pb";
|
|
7
|
+
import { useStigmer } from "../hooks.js";
|
|
8
|
+
import { useFetch } from "../internal/useFetch.js";
|
|
9
|
+
|
|
10
|
+
/** Options for {@link usePlans}. */
|
|
11
|
+
export interface UsePlansOptions {
|
|
12
|
+
/** Include retired plans beside the ones that can still be bought. Default `false`. */
|
|
13
|
+
readonly includeRetired?: boolean;
|
|
14
|
+
/** Skip fetching while `false` (stable no-op). Default `true`. */
|
|
15
|
+
readonly enabled?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Return value of {@link usePlans}. */
|
|
19
|
+
export interface UsePlansReturn {
|
|
20
|
+
/** The catalog's plans, or `null` before the first successful fetch. */
|
|
21
|
+
readonly plans: readonly Plan[] | null;
|
|
22
|
+
/** `true` while the initial fetch is in flight. */
|
|
23
|
+
readonly isLoading: boolean;
|
|
24
|
+
/** `true` while a background refetch is in flight and stale data is shown. */
|
|
25
|
+
readonly isRefetching: boolean;
|
|
26
|
+
/** Error from the last failed request, or `null` when healthy. */
|
|
27
|
+
readonly error: Error | null;
|
|
28
|
+
/** Discard cached data and re-fetch from the server. */
|
|
29
|
+
readonly refetch: () => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Data hook that lists the Stigmer Cloud plan catalog.
|
|
34
|
+
*
|
|
35
|
+
* Every signed-in caller may read it. By default it lists only the plans
|
|
36
|
+
* that can still be bought; `includeRetired` adds the retired rows an
|
|
37
|
+
* operator audits. Free is not a row: an organization with no live
|
|
38
|
+
* subscription is on Free.
|
|
39
|
+
*
|
|
40
|
+
* Cloud-only: other editions serve no plans.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```tsx
|
|
44
|
+
* const { plans } = usePlans();
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export function usePlans(options?: UsePlansOptions): UsePlansReturn {
|
|
48
|
+
const stigmer = useStigmer();
|
|
49
|
+
const enabled = options?.enabled ?? true;
|
|
50
|
+
const includeRetired = options?.includeRetired ?? false;
|
|
51
|
+
const { data: plans, isLoading, isRefetching, error, refetch } = useFetch(
|
|
52
|
+
enabled
|
|
53
|
+
? () =>
|
|
54
|
+
stigmer.plan
|
|
55
|
+
.list(create(ListPlansInputSchema, { includeRetired }))
|
|
56
|
+
.then((result): readonly Plan[] => result.entries)
|
|
57
|
+
: null,
|
|
58
|
+
[enabled, includeRetired, stigmer],
|
|
59
|
+
null as readonly Plan[] | null,
|
|
60
|
+
);
|
|
61
|
+
return useMemo(
|
|
62
|
+
() => ({ plans, isLoading, isRefetching, error, refetch }),
|
|
63
|
+
[plans, isLoading, isRefetching, error, refetch],
|
|
64
|
+
);
|
|
65
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
import { create } from "@bufbuild/protobuf";
|
|
5
|
+
import { isNotFound } from "@stigmer/sdk";
|
|
6
|
+
import type { Subscription } from "@stigmer/protos/ai/stigmer/billing/subscription/v1/api_pb";
|
|
7
|
+
import { GetSubscriptionForOrganizationInputSchema } from "@stigmer/protos/ai/stigmer/billing/subscription/v1/io_pb";
|
|
8
|
+
import { useStigmer } from "../hooks.js";
|
|
9
|
+
import { useFetch } from "../internal/useFetch.js";
|
|
10
|
+
|
|
11
|
+
/** Return value of {@link useSubscription}. */
|
|
12
|
+
export interface UseSubscriptionReturn {
|
|
13
|
+
/**
|
|
14
|
+
* The organization's subscription as stored; `null` when it never
|
|
15
|
+
* subscribed (it is on Free) or before the first successful fetch.
|
|
16
|
+
* A subscription that ended reads `canceled` with its period in the
|
|
17
|
+
* past; {@link useEntitlements} says what the organization may do now.
|
|
18
|
+
*/
|
|
19
|
+
readonly subscription: Subscription | null;
|
|
20
|
+
/** `true` while the initial fetch is in flight. */
|
|
21
|
+
readonly isLoading: boolean;
|
|
22
|
+
/** `true` while a background refetch is in flight and stale data is shown. */
|
|
23
|
+
readonly isRefetching: boolean;
|
|
24
|
+
/** Error from the last failed request, or `null` when healthy. Never NOT_FOUND. */
|
|
25
|
+
readonly error: Error | null;
|
|
26
|
+
/** Discard cached data and re-fetch from the server. */
|
|
27
|
+
readonly refetch: () => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Data hook that reads an organization's subscription.
|
|
32
|
+
*
|
|
33
|
+
* The server answers NOT_FOUND for an organization that never subscribed;
|
|
34
|
+
* this hook reads that as `null`, because never subscribed is a state (the
|
|
35
|
+
* Free plan), not a failure.
|
|
36
|
+
*
|
|
37
|
+
* Pass `null` as `orgId` to skip fetching. Refetches when the window
|
|
38
|
+
* regains focus, so a plan changed in another window, or a card saved in
|
|
39
|
+
* the system browser, shows on return.
|
|
40
|
+
*
|
|
41
|
+
* Cloud-only; the caller needs `can_view_billing` on the organization.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```tsx
|
|
45
|
+
* const { subscription } = useSubscription(orgId);
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export function useSubscription(orgId: string | null): UseSubscriptionReturn {
|
|
49
|
+
const stigmer = useStigmer();
|
|
50
|
+
const { data: subscription, isLoading, isRefetching, error, refetch } = useFetch(
|
|
51
|
+
orgId
|
|
52
|
+
? () =>
|
|
53
|
+
stigmer.subscription
|
|
54
|
+
.getForOrganization(create(GetSubscriptionForOrganizationInputSchema, { orgId }))
|
|
55
|
+
.catch((err: unknown) => {
|
|
56
|
+
if (isNotFound(err)) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
throw err;
|
|
60
|
+
})
|
|
61
|
+
: null,
|
|
62
|
+
[orgId, stigmer],
|
|
63
|
+
null as Subscription | null,
|
|
64
|
+
{ refetchOnWindowFocus: true },
|
|
65
|
+
);
|
|
66
|
+
return useMemo(
|
|
67
|
+
() => ({ subscription, isLoading, isRefetching, error, refetch }),
|
|
68
|
+
[subscription, isLoading, isRefetching, error, refetch],
|
|
69
|
+
);
|
|
70
|
+
}
|