@stigmer/react 3.39.0 → 3.40.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.
Files changed (180) hide show
  1. package/billing/AutoRechargeCard.js +1 -1
  2. package/billing/AutoRechargeCard.js.map +1 -1
  3. package/billing/BillingSection.d.ts +18 -1
  4. package/billing/BillingSection.d.ts.map +1 -1
  5. package/billing/BillingSection.js +21 -11
  6. package/billing/BillingSection.js.map +1 -1
  7. package/billing/ChangePlanDialog.d.ts +46 -0
  8. package/billing/ChangePlanDialog.d.ts.map +1 -0
  9. package/billing/ChangePlanDialog.js +90 -0
  10. package/billing/ChangePlanDialog.js.map +1 -0
  11. package/billing/CreditPackGrid.d.ts +1 -1
  12. package/billing/CreditPackGrid.js +1 -1
  13. package/billing/LowBalanceBanner.js +1 -1
  14. package/billing/LowBalanceBanner.js.map +1 -1
  15. package/billing/PaymentMethodCard.d.ts +9 -1
  16. package/billing/PaymentMethodCard.d.ts.map +1 -1
  17. package/billing/PaymentMethodCard.js +2 -2
  18. package/billing/PaymentMethodCard.js.map +1 -1
  19. package/billing/PlanCard.d.ts +27 -0
  20. package/billing/PlanCard.d.ts.map +1 -0
  21. package/billing/PlanCard.js +68 -0
  22. package/billing/PlanCard.js.map +1 -0
  23. package/billing/PlanPicker.d.ts +34 -0
  24. package/billing/PlanPicker.d.ts.map +1 -0
  25. package/billing/PlanPicker.js +46 -0
  26. package/billing/PlanPicker.js.map +1 -0
  27. package/billing/PlanSection.d.ts +45 -0
  28. package/billing/PlanSection.d.ts.map +1 -0
  29. package/billing/PlanSection.js +136 -0
  30. package/billing/PlanSection.js.map +1 -0
  31. package/billing/UpgradeNotice.d.ts +47 -0
  32. package/billing/UpgradeNotice.d.ts.map +1 -0
  33. package/billing/UpgradeNotice.js +49 -0
  34. package/billing/UpgradeNotice.js.map +1 -0
  35. package/billing/credit-packs.d.ts.map +1 -1
  36. package/billing/credit-packs.js +4 -1
  37. package/billing/credit-packs.js.map +1 -1
  38. package/billing/index.d.ts +26 -1
  39. package/billing/index.d.ts.map +1 -1
  40. package/billing/index.js +12 -0
  41. package/billing/index.js.map +1 -1
  42. package/billing/plan-features.d.ts +60 -0
  43. package/billing/plan-features.d.ts.map +1 -0
  44. package/billing/plan-features.js +128 -0
  45. package/billing/plan-features.js.map +1 -0
  46. package/billing/plan-state.d.ts +43 -0
  47. package/billing/plan-state.d.ts.map +1 -0
  48. package/billing/plan-state.js +69 -0
  49. package/billing/plan-state.js.map +1 -0
  50. package/billing/redirect.d.ts +31 -0
  51. package/billing/redirect.d.ts.map +1 -0
  52. package/billing/redirect.js +30 -0
  53. package/billing/redirect.js.map +1 -0
  54. package/billing/useBillingAccount.d.ts +10 -1
  55. package/billing/useBillingAccount.d.ts.map +1 -1
  56. package/billing/useBillingAccount.js +2 -2
  57. package/billing/useBillingAccount.js.map +1 -1
  58. package/billing/useChangePlan.d.ts +51 -0
  59. package/billing/useChangePlan.d.ts.map +1 -0
  60. package/billing/useChangePlan.js +61 -0
  61. package/billing/useChangePlan.js.map +1 -0
  62. package/billing/useCreateBillingPortalSession.d.ts +6 -3
  63. package/billing/useCreateBillingPortalSession.d.ts.map +1 -1
  64. package/billing/useCreateBillingPortalSession.js +11 -4
  65. package/billing/useCreateBillingPortalSession.js.map +1 -1
  66. package/billing/useCreateCheckoutSession.d.ts +7 -5
  67. package/billing/useCreateCheckoutSession.d.ts.map +1 -1
  68. package/billing/useCreateCheckoutSession.js +5 -3
  69. package/billing/useCreateCheckoutSession.js.map +1 -1
  70. package/billing/useCreatePaymentMethodSetupSession.d.ts +28 -0
  71. package/billing/useCreatePaymentMethodSetupSession.d.ts.map +1 -0
  72. package/billing/useCreatePaymentMethodSetupSession.js +50 -0
  73. package/billing/useCreatePaymentMethodSetupSession.js.map +1 -0
  74. package/billing/useEntitlements.d.ts +46 -0
  75. package/billing/useEntitlements.d.ts.map +1 -0
  76. package/billing/useEntitlements.js +37 -0
  77. package/billing/useEntitlements.js.map +1 -0
  78. package/billing/usePeriodEstimate.d.ts +35 -0
  79. package/billing/usePeriodEstimate.d.ts.map +1 -0
  80. package/billing/usePeriodEstimate.js +29 -0
  81. package/billing/usePeriodEstimate.js.map +1 -0
  82. package/billing/usePlans.d.ts +38 -0
  83. package/billing/usePlans.d.ts.map +1 -0
  84. package/billing/usePlans.js +33 -0
  85. package/billing/usePlans.js.map +1 -0
  86. package/billing/useSubscription.d.ts +39 -0
  87. package/billing/useSubscription.d.ts.map +1 -0
  88. package/billing/useSubscription.js +40 -0
  89. package/billing/useSubscription.js.map +1 -0
  90. package/index.d.ts +4 -2
  91. package/index.d.ts.map +1 -1
  92. package/index.js +4 -1
  93. package/index.js.map +1 -1
  94. package/internal/features.d.ts +11 -0
  95. package/internal/features.d.ts.map +1 -0
  96. package/internal/features.js +42 -0
  97. package/internal/features.js.map +1 -0
  98. package/licenses/license-format.d.ts +5 -3
  99. package/licenses/license-format.d.ts.map +1 -1
  100. package/licenses/license-format.js +10 -19
  101. package/licenses/license-format.js.map +1 -1
  102. package/package.json +5 -5
  103. package/plan-catalog/PlanCatalogConsole.d.ts +22 -0
  104. package/plan-catalog/PlanCatalogConsole.d.ts.map +1 -0
  105. package/plan-catalog/PlanCatalogConsole.js +90 -0
  106. package/plan-catalog/PlanCatalogConsole.js.map +1 -0
  107. package/plan-catalog/PlanCreateForm.d.ts +17 -0
  108. package/plan-catalog/PlanCreateForm.d.ts.map +1 -0
  109. package/plan-catalog/PlanCreateForm.js +52 -0
  110. package/plan-catalog/PlanCreateForm.js.map +1 -0
  111. package/plan-catalog/PlansAccessNotice.d.ts +10 -0
  112. package/plan-catalog/PlansAccessNotice.d.ts.map +1 -0
  113. package/plan-catalog/PlansAccessNotice.js +13 -0
  114. package/plan-catalog/PlansAccessNotice.js.map +1 -0
  115. package/plan-catalog/index.d.ts +8 -0
  116. package/plan-catalog/index.d.ts.map +1 -0
  117. package/plan-catalog/index.js +10 -0
  118. package/plan-catalog/index.js.map +1 -0
  119. package/plan-catalog/plan-draft.d.ts +30 -0
  120. package/plan-catalog/plan-draft.d.ts.map +1 -0
  121. package/plan-catalog/plan-draft.js +91 -0
  122. package/plan-catalog/plan-draft.js.map +1 -0
  123. package/plan-catalog/usePlanMutations.d.ts +42 -0
  124. package/plan-catalog/usePlanMutations.d.ts.map +1 -0
  125. package/plan-catalog/usePlanMutations.js +57 -0
  126. package/plan-catalog/usePlanMutations.js.map +1 -0
  127. package/settings/TeamsSection.d.ts.map +1 -1
  128. package/settings/TeamsSection.js +19 -2
  129. package/settings/TeamsSection.js.map +1 -1
  130. package/settings/settings-nav.d.ts.map +1 -1
  131. package/settings/settings-nav.js +7 -1
  132. package/settings/settings-nav.js.map +1 -1
  133. package/settings/useSettingsNavGroups.d.ts.map +1 -1
  134. package/settings/useSettingsNavGroups.js +4 -1
  135. package/settings/useSettingsNavGroups.js.map +1 -1
  136. package/src/billing/AutoRechargeCard.tsx +2 -2
  137. package/src/billing/BillingSection.tsx +68 -11
  138. package/src/billing/ChangePlanDialog.tsx +221 -0
  139. package/src/billing/CreditPackGrid.tsx +1 -1
  140. package/src/billing/LowBalanceBanner.tsx +1 -1
  141. package/src/billing/PaymentMethodCard.tsx +28 -2
  142. package/src/billing/PlanCard.tsx +171 -0
  143. package/src/billing/PlanPicker.tsx +178 -0
  144. package/src/billing/PlanSection.tsx +234 -0
  145. package/src/billing/UpgradeNotice.tsx +95 -0
  146. package/src/billing/credit-packs.ts +4 -1
  147. package/src/billing/index.ts +26 -1
  148. package/src/billing/plan-features.ts +162 -0
  149. package/src/billing/plan-state.ts +92 -0
  150. package/src/billing/redirect.ts +45 -0
  151. package/src/billing/useBillingAccount.ts +12 -0
  152. package/src/billing/useChangePlan.ts +115 -0
  153. package/src/billing/useCreateBillingPortalSession.ts +17 -6
  154. package/src/billing/useCreateCheckoutSession.ts +12 -7
  155. package/src/billing/useCreatePaymentMethodSetupSession.ts +77 -0
  156. package/src/billing/useEntitlements.ts +80 -0
  157. package/src/billing/usePeriodEstimate.ts +59 -0
  158. package/src/billing/usePlans.ts +65 -0
  159. package/src/billing/useSubscription.ts +70 -0
  160. package/src/index.ts +54 -0
  161. package/src/internal/features.ts +49 -0
  162. package/src/licenses/license-format.ts +10 -19
  163. package/src/plan-catalog/PlanCatalogConsole.tsx +197 -0
  164. package/src/plan-catalog/PlanCreateForm.tsx +167 -0
  165. package/src/plan-catalog/PlansAccessNotice.tsx +20 -0
  166. package/src/plan-catalog/index.ts +14 -0
  167. package/src/plan-catalog/plan-draft.ts +116 -0
  168. package/src/plan-catalog/usePlanMutations.ts +94 -0
  169. package/src/settings/TeamsSection.tsx +20 -2
  170. package/src/settings/settings-nav.ts +7 -0
  171. package/src/settings/useSettingsNavGroups.ts +8 -1
  172. package/src/team/CreateTeamForm.tsx +12 -3
  173. package/src/team/TeamMembersPanel.tsx +6 -2
  174. package/team/CreateTeamForm.d.ts +6 -1
  175. package/team/CreateTeamForm.d.ts.map +1 -1
  176. package/team/CreateTeamForm.js +4 -2
  177. package/team/CreateTeamForm.js.map +1 -1
  178. package/team/TeamMembersPanel.d.ts.map +1 -1
  179. package/team/TeamMembersPanel.js +3 -1
  180. package/team/TeamMembersPanel.js.map +1 -1
@@ -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
+ }
package/src/index.ts CHANGED
@@ -1160,6 +1160,25 @@ export {
1160
1160
  useCreateCheckoutSession,
1161
1161
  useCreateBillingPortalSession,
1162
1162
  useSetAutoRechargeConfig,
1163
+ usePlans,
1164
+ useSubscription,
1165
+ useEntitlements,
1166
+ usePeriodEstimate,
1167
+ useChangePlan,
1168
+ useCancelSubscription,
1169
+ useCreatePaymentMethodSetupSession,
1170
+ planStanding,
1171
+ planMove,
1172
+ PlanSection,
1173
+ PlanCard,
1174
+ PlanPicker,
1175
+ ENTERPRISE_CONTACT_URL,
1176
+ ChangePlanDialog,
1177
+ PAYMENT_METHOD_REQUIRED,
1178
+ UpgradeNotice,
1179
+ planUpgradeFeature,
1180
+ PLAN_UPGRADE_REQUIRED,
1181
+ DEFAULT_BILLING_HREF,
1163
1182
  BillingSection,
1164
1183
  CreditBalanceCard,
1165
1184
  PaymentMethodCard,
@@ -1178,7 +1197,26 @@ export {
1178
1197
  formatLedgerDate,
1179
1198
  } from "./billing/index.js";
1180
1199
  export type {
1200
+ UseBillingAccountOptions,
1181
1201
  UseBillingAccountReturn,
1202
+ UsePlansOptions,
1203
+ UsePlansReturn,
1204
+ UseSubscriptionReturn,
1205
+ UseEntitlementsOptions,
1206
+ UseEntitlementsReturn,
1207
+ UsePeriodEstimateOptions,
1208
+ UsePeriodEstimateReturn,
1209
+ UseChangePlanReturn,
1210
+ UseCancelSubscriptionReturn,
1211
+ UseCreatePaymentMethodSetupSessionReturn,
1212
+ BillingRedirect,
1213
+ PlanStanding,
1214
+ PlanMove,
1215
+ PlanSectionProps,
1216
+ PlanCardProps,
1217
+ PlanPickerProps,
1218
+ ChangePlanDialogProps,
1219
+ UpgradeNoticeProps,
1182
1220
  UseCreditLedgerReturn,
1183
1221
  UseCreditLedgerOptions,
1184
1222
  UseBillingUsageReportReturn,
@@ -1285,6 +1323,22 @@ export type {
1285
1323
  LicensesConsoleProps,
1286
1324
  } from "./licenses/index.js";
1287
1325
 
1326
+ // Plan catalog — platform-operator management of the Stigmer Cloud plans
1327
+ // organizations subscribe to (gated on can_manage_plans)
1328
+ export {
1329
+ useCreatePlan,
1330
+ useRetirePlan,
1331
+ PlanCatalogConsole,
1332
+ PlanCreateForm,
1333
+ PlansAccessNotice,
1334
+ } from "./plan-catalog/index.js";
1335
+ export type {
1336
+ UseCreatePlanReturn,
1337
+ UseRetirePlanReturn,
1338
+ PlanCatalogConsoleProps,
1339
+ PlanCreateFormProps,
1340
+ } from "./plan-catalog/index.js";
1341
+
1288
1342
  // Settings — navigation structure + section components shared across app shells
1289
1343
  export {
1290
1344
  SETTINGS_NAV_GROUPS,
@@ -0,0 +1,49 @@
1
+ // The one vocabulary for entitlement features: a label and a line per
2
+ // feature, shared by every surface that names one (the licenses console,
3
+ // the plan comparison, the upgrade notice), so a feature reads the same
4
+ // wherever it appears.
5
+ //
6
+ // Keyed by every feature that grants something, so a value added to the
7
+ // contract fails to compile here until it has words. `platform_client` is
8
+ // not one: it gates nothing (platform/v1/entitlement.proto), so no surface
9
+ // names it.
10
+
11
+ import { Feature } from "@stigmer/protos/ai/stigmer/platform/v1/entitlement_pb";
12
+
13
+ /** A feature that grants something: every value but the zero value and `platform_client`. */
14
+ export type NamedFeature = Exclude<Feature, Feature.feature_unspecified | Feature.platform_client>;
15
+
16
+ /** Label and one-line explanation per named feature. */
17
+ export const FEATURE_COPY: Readonly<
18
+ Record<NamedFeature, { readonly label: string; readonly description: string }>
19
+ > = {
20
+ [Feature.sso_enforcement]: {
21
+ label: "SSO enforcement",
22
+ description: "Require members to sign in through a registered identity provider.",
23
+ },
24
+ [Feature.byo_provider_keys]: {
25
+ label: "Bring your own provider keys",
26
+ description: "Use the customer's own LLM provider keys instead of the metered proxy.",
27
+ },
28
+ [Feature.channels]: {
29
+ label: "Channels",
30
+ description: "Deliver agents over messaging channels through a channel runtime.",
31
+ },
32
+ [Feature.sharing]: {
33
+ label: "Sharing",
34
+ description: "Share agents with individuals and guests through hosted links.",
35
+ },
36
+ [Feature.teams]: {
37
+ label: "Teams",
38
+ description: "Group members into teams and grant access to a team at once.",
39
+ },
40
+ [Feature.managed_organizations]: {
41
+ label: "Managed organizations",
42
+ description: "Run organizations for your own customers under your identity provider.",
43
+ },
44
+ };
45
+
46
+ /** Whether a stored feature value is one the vocabulary names. */
47
+ export function isNamedFeature(feature: number): feature is NamedFeature {
48
+ return feature in FEATURE_COPY;
49
+ }
@@ -20,6 +20,7 @@ import {
20
20
  type Entitlements,
21
21
  } from "@stigmer/protos/ai/stigmer/platform/v1/entitlement_pb";
22
22
  import { LicenseTerm } from "@stigmer/protos/ai/stigmer/platform/v1/license_pb";
23
+ import { FEATURE_COPY } from "../internal/features.js";
23
24
 
24
25
  /** A term an operator can issue; the unspecified zero value is not one. */
25
26
  export type IssuableTerm = LicenseTerm.trial | LicenseTerm.paid;
@@ -59,29 +60,19 @@ export const TERM_LABELS: Readonly<Record<IssuableTerm, string>> = {
59
60
  };
60
61
 
61
62
  /**
62
- * Label and one-line explanation per grantable feature. Keyed by every
63
- * grantable `Feature` value, so a feature added to the contract fails to
64
- * compile here until it has words an operator can read.
63
+ * Label and one-line explanation per grantable feature: the shared
64
+ * vocabulary (../internal/features.ts), narrowed to what a license may
65
+ * carry. Keyed by every grantable `Feature` value, so a feature added to
66
+ * the contract fails to compile here until it has words an operator can
67
+ * read.
65
68
  */
66
69
  export const FEATURE_LABELS: Readonly<
67
70
  Record<GrantableFeature, { readonly label: string; readonly description: string }>
68
71
  > = {
69
- [Feature.sso_enforcement]: {
70
- label: "SSO enforcement",
71
- description: "Require members to sign in through a registered identity provider.",
72
- },
73
- [Feature.byo_provider_keys]: {
74
- label: "Bring your own provider keys",
75
- description: "Use the customer's own LLM provider keys instead of the metered proxy.",
76
- },
77
- [Feature.channels]: {
78
- label: "Channels",
79
- description: "Deliver agents over messaging channels through a channel runtime.",
80
- },
81
- [Feature.sharing]: {
82
- label: "Sharing",
83
- description: "Share agents with individuals and guests through hosted links.",
84
- },
72
+ [Feature.sso_enforcement]: FEATURE_COPY[Feature.sso_enforcement],
73
+ [Feature.byo_provider_keys]: FEATURE_COPY[Feature.byo_provider_keys],
74
+ [Feature.channels]: FEATURE_COPY[Feature.channels],
75
+ [Feature.sharing]: FEATURE_COPY[Feature.sharing],
85
76
  };
86
77
 
87
78
  /** Every grantable feature, in contract order (derived from the label map). */
@@ -0,0 +1,197 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import { cn } from "@stigmer/theme";
5
+ import { getUserMessage, isUnimplemented } from "@stigmer/sdk";
6
+ import type { Plan } from "@stigmer/protos/ai/stigmer/billing/plan/v1/api_pb";
7
+ import { PlanInstrument } from "@stigmer/protos/ai/stigmer/billing/plan/v1/spec_pb";
8
+ import { PlanLifecycle } from "@stigmer/protos/ai/stigmer/billing/plan/v1/status_pb";
9
+ import { formatCreditBalance } from "../billing/format.js";
10
+ import { formatManagedOrganizations, formatUsageShare, offeredFeatures } from "../billing/plan-features.js";
11
+ import { usePlans } from "../billing/usePlans.js";
12
+ import { Button } from "../button/index.js";
13
+ import { ApiResourceKind, useResourceAvailable } from "../deployment-mode.js";
14
+ import { useCheckPermission } from "../iam-policy/useCheckPermission.js";
15
+ import { CloudFeatureNotice } from "../internal/CloudFeatureNotice.js";
16
+ import { UNSTYLED_LIST } from "../internal/element-resets.js";
17
+ import { FEATURE_COPY, isNamedFeature } from "../internal/features.js";
18
+ import { ConfirmDialog } from "../resource-detail/ConfirmDialog.js";
19
+ import { useConfirmAction } from "../resource-detail/useConfirmAction.js";
20
+ import { StatusBadge } from "../resource-workbench/components/StatusBadge.js";
21
+ import { PlanCreateForm } from "./PlanCreateForm.js";
22
+ import { PlansAccessNotice } from "./PlansAccessNotice.js";
23
+ import { useRetirePlan } from "./usePlanMutations.js";
24
+
25
+ /** The platform object the catalog's permission is held on. */
26
+ const PLATFORM = { kind: "platform", id: "stigmer" } as const;
27
+
28
+ /** Props for {@link PlanCatalogConsole}. */
29
+ export interface PlanCatalogConsoleProps {
30
+ /** Additional CSS class names. */
31
+ readonly className?: string;
32
+ }
33
+
34
+ /**
35
+ * The platform-operator console for the Stigmer Cloud plan catalog: every
36
+ * plan, retired ones included, with its terms and what it offers; retiring
37
+ * a plan; and creating one.
38
+ *
39
+ * Every signed-in caller may read the catalog, so the page gates on
40
+ * `can_manage_plans` on `platform:stigmer` itself, and anyone without it
41
+ * sees the designed access notice. Organizations choose their plan under
42
+ * Billing, not here.
43
+ *
44
+ * @example
45
+ * ```tsx
46
+ * <PlanCatalogConsole />
47
+ * ```
48
+ */
49
+ export function PlanCatalogConsole({ className }: PlanCatalogConsoleProps) {
50
+ const available = useResourceAvailable(ApiResourceKind.plan);
51
+ const operator = useCheckPermission(available ? PLATFORM : null, "can_manage_plans", { fail: "closed" });
52
+ const catalog = usePlans({ includeRetired: true, enabled: available && operator.allowed });
53
+ const retirer = useRetirePlan();
54
+ const { confirmState, confirm, handleConfirm, handleCancel } = useConfirmAction();
55
+ const [creating, setCreating] = useState(false);
56
+
57
+ const header = <ConsoleHeader onCreate={operator.allowed && !creating ? () => setCreating(true) : undefined} />;
58
+
59
+ if (!available || (catalog.error && isUnimplemented(catalog.error))) {
60
+ return (
61
+ <div className={cn("stg:space-y-3", className)}>
62
+ {header}
63
+ <CloudFeatureNotice>
64
+ Plans are sold on Stigmer Cloud. The server this console is connected to serves no plan catalog.
65
+ </CloudFeatureNotice>
66
+ </div>
67
+ );
68
+ }
69
+ if (operator.isLoading || (operator.allowed && catalog.plans === null && catalog.error === null)) {
70
+ return (
71
+ <div className={cn("stg:space-y-2", className)} aria-busy="true">
72
+ {Array.from({ length: 3 }, (_, i) => (
73
+ <div key={i} className="stg:h-12 stg:animate-pulse stg:rounded-lg stg:bg-muted-subtle" />
74
+ ))}
75
+ </div>
76
+ );
77
+ }
78
+ if (!operator.allowed) {
79
+ return (
80
+ <div className={cn("stg:space-y-3", className)}>
81
+ {header}
82
+ <PlansAccessNotice />
83
+ </div>
84
+ );
85
+ }
86
+ if (catalog.error) {
87
+ return (
88
+ <div className={cn("stg:space-y-3", className)}>
89
+ {header}
90
+ <p className="stg:text-sm stg:text-destructive" role="alert">
91
+ {getUserMessage(catalog.error)}
92
+ </p>
93
+ <Button variant="outline" onClick={catalog.refetch}>
94
+ Try again
95
+ </Button>
96
+ </div>
97
+ );
98
+ }
99
+
100
+ const retire = async (plan: Plan) => {
101
+ const ok = await confirm({
102
+ title: `Retire ${plan.metadata?.name ?? "this plan"}?`,
103
+ description:
104
+ "It can no longer be bought. Every organization already on it keeps it, invoiced by its terms. Retiring is final.",
105
+ confirmLabel: "Retire",
106
+ variant: "destructive",
107
+ });
108
+ if (ok) {
109
+ retirer.retirePlan(plan.metadata?.id ?? "").then(catalog.refetch, () => undefined);
110
+ }
111
+ };
112
+
113
+ return (
114
+ <div className={cn("stg:space-y-4", className)}>
115
+ {header}
116
+ {creating ? (
117
+ <div className="stg:rounded-lg stg:border stg:border-border stg:bg-card stg:p-4">
118
+ <PlanCreateForm
119
+ onCreated={() => {
120
+ setCreating(false);
121
+ catalog.refetch();
122
+ }}
123
+ onCancel={() => setCreating(false)}
124
+ />
125
+ </div>
126
+ ) : null}
127
+ {retirer.error && (
128
+ <p className="stg:text-xs stg:text-destructive" role="alert">
129
+ {getUserMessage(retirer.error)}
130
+ </p>
131
+ )}
132
+ <ul className={cn(UNSTYLED_LIST, "stg:space-y-2")} aria-label="Plan catalog">
133
+ {(catalog.plans ?? []).map((plan) => (
134
+ <PlanRow key={plan.metadata?.id} plan={plan} onRetire={() => void retire(plan)} busy={retirer.isSubmitting} />
135
+ ))}
136
+ </ul>
137
+ <ConfirmDialog state={confirmState} onConfirm={handleConfirm} onCancel={handleCancel} />
138
+ </div>
139
+ );
140
+ }
141
+
142
+ function ConsoleHeader({ onCreate }: { readonly onCreate?: () => void }) {
143
+ return (
144
+ <div className="stg:flex stg:items-start stg:justify-between stg:gap-3">
145
+ <div>
146
+ <h2 className="stg:text-sm stg:font-semibold stg:text-foreground">Plans</h2>
147
+ <p className="stg:text-xs stg:text-muted-foreground">
148
+ The plans organizations subscribe to. A plan&apos;s terms are final; change them with a new plan and retire the
149
+ old one.
150
+ </p>
151
+ </div>
152
+ {onCreate && (
153
+ <Button size="xs" onClick={onCreate}>
154
+ New plan
155
+ </Button>
156
+ )}
157
+ </div>
158
+ );
159
+ }
160
+
161
+ function PlanRow({ plan, onRetire, busy }: { readonly plan: Plan; readonly onRetire: () => void; readonly busy: boolean }) {
162
+ const active = plan.status?.lifecycle === PlanLifecycle.active;
163
+ const license = plan.spec?.instrument === PlanInstrument.license;
164
+ const terms = plan.spec?.terms;
165
+ const listed = (plan.spec?.entitlements?.features ?? []).filter(isNamedFeature).map((f) => FEATURE_COPY[f].label);
166
+ const offered = new Set(offeredFeatures(plan.spec?.entitlements).map((f) => f.label));
167
+ const managed = formatManagedOrganizations(plan.spec?.entitlements, terms);
168
+ return (
169
+ <li className="stg:rounded-lg stg:border stg:border-border stg:bg-card stg:p-3">
170
+ <div className="stg:flex stg:items-center stg:justify-between stg:gap-3">
171
+ <div className="stg:min-w-0">
172
+ <p className="stg:truncate stg:text-sm stg:font-medium stg:text-foreground">
173
+ {plan.metadata?.name}{" "}
174
+ <span className="stg:font-normal stg:text-muted-foreground">({plan.metadata?.slug})</span>
175
+ </p>
176
+ <p className="stg:text-xs stg:text-muted-foreground">
177
+ {license
178
+ ? "Issued as a license"
179
+ : `${formatCreditBalance(terms?.monthlyMinimumMicros ?? BigInt(0))}/month ${formatUsageShare(terms)}`}
180
+ </p>
181
+ </div>
182
+ <div className="stg:flex stg:shrink-0 stg:items-center stg:gap-2">
183
+ <StatusBadge phase={active ? "ready" : "disabled"} label={active ? "On sale" : "Retired"} />
184
+ {active && (
185
+ <Button variant="outline" size="xs" onClick={onRetire} disabled={busy}>
186
+ Retire
187
+ </Button>
188
+ )}
189
+ </div>
190
+ </div>
191
+ <p className="stg:mt-1 stg:text-xs stg:text-muted-foreground">
192
+ {listed.map((label) => (offered.has(label) ? label : `${label} (not offered yet)`)).join(" · ")}
193
+ {managed === "" ? "" : ` · ${managed}`}
194
+ </p>
195
+ </li>
196
+ );
197
+ }