@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,167 @@
1
+ "use client";
2
+
3
+ import { useState, type FormEvent } from "react";
4
+ import { cn } from "@stigmer/theme";
5
+ import { getUserMessage } from "@stigmer/sdk";
6
+ import type { Plan } from "@stigmer/protos/ai/stigmer/billing/plan/v1/api_pb";
7
+ import { Feature } from "@stigmer/protos/ai/stigmer/platform/v1/entitlement_pb";
8
+ import { Button } from "../button/index.js";
9
+ import { UNSTYLED_FIELDSET } from "../internal/element-resets.js";
10
+ import { FEATURE_COPY, type NamedFeature } from "../internal/features.js";
11
+ import { Field, INPUT_CLASSES } from "../internal/form-primitives.js";
12
+ import { EMPTY_PLAN_DRAFT, planInputFromDraft, type PlanDraft } from "./plan-draft.js";
13
+ import { useCreatePlan } from "./usePlanMutations.js";
14
+
15
+ /** The features a subscription plan can list, in contract order. */
16
+ const PLAN_FEATURES: readonly NamedFeature[] = [
17
+ Feature.channels,
18
+ Feature.sharing,
19
+ Feature.teams,
20
+ Feature.managed_organizations,
21
+ Feature.byo_provider_keys,
22
+ Feature.sso_enforcement,
23
+ ];
24
+
25
+ /** Props for {@link PlanCreateForm}. */
26
+ export interface PlanCreateFormProps {
27
+ /** Fired with the created plan. */
28
+ readonly onCreated: (plan: Plan) => void;
29
+ /** Fired when the operator leaves without creating. */
30
+ readonly onCancel: () => void;
31
+ }
32
+
33
+ /**
34
+ * Adds a subscription plan to the catalog: its name and handle, its
35
+ * monthly minimum and usage share, its features, and the managed
36
+ * organizations it includes. The form states before submitting that a
37
+ * plan's terms are final: a change of terms is a new plan, and the old
38
+ * one is retired.
39
+ */
40
+ export function PlanCreateForm({ onCreated, onCancel }: PlanCreateFormProps) {
41
+ const creator = useCreatePlan();
42
+ const [draft, setDraft] = useState<PlanDraft>(EMPTY_PLAN_DRAFT);
43
+ const [confirming, setConfirming] = useState(false);
44
+ const result = planInputFromDraft(draft);
45
+ const set = <K extends keyof PlanDraft>(key: K, value: PlanDraft[K]) => {
46
+ setConfirming(false);
47
+ setDraft((current) => ({ ...current, [key]: value }));
48
+ };
49
+ const toggle = (feature: Feature, on: boolean) =>
50
+ set("features", on ? [...draft.features, feature] : draft.features.filter((f) => f !== feature));
51
+
52
+ const submit = (e: FormEvent) => {
53
+ e.preventDefault();
54
+ if (!result.ok) return;
55
+ if (!confirming) {
56
+ setConfirming(true);
57
+ return;
58
+ }
59
+ creator.createPlan(result.input).then(onCreated, () => undefined);
60
+ };
61
+
62
+ const fieldError = (field: keyof PlanDraft) =>
63
+ !result.ok && result.field === field && draft[field] !== "" ? (
64
+ <span className="stg:block stg:text-xs stg:text-destructive">{result.message}</span>
65
+ ) : null;
66
+
67
+ return (
68
+ <form onSubmit={submit} className="stg:space-y-4" aria-label="New plan">
69
+ <div className="stg:grid stg:gap-3 stg:sm:grid-cols-2">
70
+ <Field label="Name" required>
71
+ <input className={INPUT_CLASSES} value={draft.name} onChange={(e) => set("name", e.target.value)} />
72
+ </Field>
73
+ <Field label="Handle (slug)" required>
74
+ <input
75
+ className={INPUT_CLASSES}
76
+ value={draft.slug}
77
+ placeholder="team-2027"
78
+ onChange={(e) => set("slug", e.target.value)}
79
+ />
80
+ {fieldError("slug")}
81
+ </Field>
82
+ <Field label="Monthly minimum (USD)" required>
83
+ <input
84
+ className={INPUT_CLASSES}
85
+ inputMode="decimal"
86
+ value={draft.monthlyMinimumUsd}
87
+ onChange={(e) => set("monthlyMinimumUsd", e.target.value)}
88
+ />
89
+ {fieldError("monthlyMinimumUsd")}
90
+ </Field>
91
+ <Field label="Usage share (% of provider cost)" required>
92
+ <input
93
+ className={INPUT_CLASSES}
94
+ inputMode="decimal"
95
+ value={draft.usageSharePercent}
96
+ onChange={(e) => set("usageSharePercent", e.target.value)}
97
+ />
98
+ {fieldError("usageSharePercent")}
99
+ </Field>
100
+ </div>
101
+
102
+ <fieldset className={cn(UNSTYLED_FIELDSET, "stg:space-y-2")}>
103
+ <legend className="stg:mb-1 stg:text-xs stg:font-medium stg:text-muted-foreground">Features</legend>
104
+ {PLAN_FEATURES.map((feature) => (
105
+ <label key={feature} className="stg:flex stg:items-start stg:gap-2 stg:text-sm">
106
+ <input
107
+ type="checkbox"
108
+ className="stg:mt-0.5"
109
+ checked={draft.features.includes(feature)}
110
+ onChange={(e) => toggle(feature, e.target.checked)}
111
+ />
112
+ <span>
113
+ <span className="stg:block stg:text-foreground">{FEATURE_COPY[feature].label}</span>
114
+ <span className="stg:block stg:text-xs stg:text-muted-foreground">
115
+ {FEATURE_COPY[feature].description}
116
+ </span>
117
+ </span>
118
+ </label>
119
+ ))}
120
+ </fieldset>
121
+
122
+ {draft.features.includes(Feature.managed_organizations) && (
123
+ <div className="stg:grid stg:gap-3 stg:sm:grid-cols-2">
124
+ <Field label="Managed organizations included (empty: unlimited)">
125
+ <input
126
+ className={INPUT_CLASSES}
127
+ inputMode="numeric"
128
+ value={draft.includedManagedOrganizations}
129
+ onChange={(e) => set("includedManagedOrganizations", e.target.value)}
130
+ />
131
+ {fieldError("includedManagedOrganizations")}
132
+ </Field>
133
+ <Field label="Each one beyond (USD/month)">
134
+ <input
135
+ className={INPUT_CLASSES}
136
+ inputMode="decimal"
137
+ value={draft.perExtraOrganizationUsd}
138
+ onChange={(e) => set("perExtraOrganizationUsd", e.target.value)}
139
+ />
140
+ {fieldError("perExtraOrganizationUsd")}
141
+ </Field>
142
+ </div>
143
+ )}
144
+
145
+ {confirming && (
146
+ <p className="stg:rounded-md stg:bg-muted-subtle stg:px-3 stg:py-2 stg:text-xs stg:text-foreground" role="status">
147
+ A plan&apos;s terms are final once it exists: every subscriber on it is invoiced by them. A change of terms is
148
+ a new plan, and this one is retired. Create it?
149
+ </p>
150
+ )}
151
+ {creator.error && (
152
+ <p className="stg:text-xs stg:text-destructive" role="alert">
153
+ {getUserMessage(creator.error)}
154
+ </p>
155
+ )}
156
+
157
+ <div className="stg:flex stg:justify-end stg:gap-2">
158
+ <Button variant="ghost" size="sm" type="button" onClick={onCancel} disabled={creator.isSubmitting}>
159
+ Cancel
160
+ </Button>
161
+ <Button size="sm" type="submit" disabled={!result.ok || creator.isSubmitting}>
162
+ {confirming ? "Create plan" : "Review"}
163
+ </Button>
164
+ </div>
165
+ </form>
166
+ );
167
+ }
@@ -0,0 +1,20 @@
1
+ "use client";
2
+
3
+ import { EmptyState } from "../empty-state/index.js";
4
+
5
+ /**
6
+ * The access-denied state for the plan catalog's operator surface.
7
+ * Server-side authorization (`can_manage_plans` on `platform:stigmer`) is
8
+ * the real gate; this is the designed face of it for anyone who reaches
9
+ * the route without the permission.
10
+ */
11
+ export function PlansAccessNotice({ className }: { readonly className?: string }) {
12
+ return (
13
+ <EmptyState
14
+ variant="permission"
15
+ className={className}
16
+ title="Platform operator access required"
17
+ description="The plan catalog is managed by Stigmer's platform operators: a plan's terms are what every subscriber on it is invoiced. Organization roles do not grant access; organizations choose their plan under Billing."
18
+ />
19
+ );
20
+ }
@@ -0,0 +1,14 @@
1
+ // Plan catalog: the platform-operator console for the Stigmer Cloud plans
2
+ // organizations subscribe to (gated on can_manage_plans). Choosing a plan
3
+ // is the customer-facing billing surface's (../billing).
4
+
5
+ // Behavior hooks
6
+ export { useCreatePlan, useRetirePlan } from "./usePlanMutations.js";
7
+ export type { UseCreatePlanReturn, UseRetirePlanReturn } from "./usePlanMutations.js";
8
+
9
+ // Styled components
10
+ export { PlanCatalogConsole } from "./PlanCatalogConsole.js";
11
+ export type { PlanCatalogConsoleProps } from "./PlanCatalogConsole.js";
12
+ export { PlanCreateForm } from "./PlanCreateForm.js";
13
+ export type { PlanCreateFormProps } from "./PlanCreateForm.js";
14
+ export { PlansAccessNotice } from "./PlansAccessNotice.js";
@@ -0,0 +1,116 @@
1
+ // The plan create form's draft and its translation to the SDK's
2
+ // PlanInput: dollars typed by an operator become exact micros, a percent
3
+ // becomes basis points, and every field the contract cannot take is
4
+ // refused here with the field that is wrong, before a request is sent.
5
+
6
+ import type { PlanInput } from "@stigmer/sdk";
7
+ import { PlanInstrument } from "@stigmer/protos/ai/stigmer/billing/plan/v1/spec_pb";
8
+ import { Feature } from "@stigmer/protos/ai/stigmer/platform/v1/entitlement_pb";
9
+
10
+ // BigInt(...), not literals: the docs site compiles this source for a
11
+ // target below ES2020, which has no BigInt literal syntax.
12
+ const MICROS_PER_CENT = BigInt(10_000);
13
+ const CENTS_PER_USD = BigInt(100);
14
+
15
+ /** What an operator types to create a subscription plan. */
16
+ export interface PlanDraft {
17
+ readonly name: string;
18
+ readonly slug: string;
19
+ readonly monthlyMinimumUsd: string;
20
+ readonly usageSharePercent: string;
21
+ readonly features: readonly Feature[];
22
+ /** Empty: the plan includes every managed organization. */
23
+ readonly includedManagedOrganizations: string;
24
+ readonly perExtraOrganizationUsd: string;
25
+ }
26
+
27
+ export const EMPTY_PLAN_DRAFT: PlanDraft = {
28
+ name: "",
29
+ slug: "",
30
+ monthlyMinimumUsd: "",
31
+ usageSharePercent: "10",
32
+ features: [Feature.channels, Feature.sharing],
33
+ includedManagedOrganizations: "",
34
+ perExtraOrganizationUsd: "",
35
+ };
36
+
37
+ /** The draft as a PlanInput, or the first field that is wrong and why. */
38
+ export type PlanDraftResult =
39
+ | { readonly ok: true; readonly input: PlanInput }
40
+ | { readonly ok: false; readonly field: keyof PlanDraft; readonly message: string };
41
+
42
+ const SLUG = /^[a-z0-9]+(-[a-z0-9]+)*$/;
43
+
44
+ /** A dollar amount with at most two decimals as exact micros, or `null`. */
45
+ export function parseUsd(text: string): bigint | null {
46
+ const match = /^(\d+)(?:\.(\d{1,2}))?$/.exec(text.trim());
47
+ if (match === null) {
48
+ return null;
49
+ }
50
+ const cents = BigInt(match[1] ?? "0") * CENTS_PER_USD + BigInt((match[2] ?? "").padEnd(2, "0"));
51
+ return cents * MICROS_PER_CENT;
52
+ }
53
+
54
+ /** A percent with at most two decimals as basis points, or `null`. */
55
+ export function parseBasisPoints(text: string): number | null {
56
+ const match = /^(\d{1,3})(?:\.(\d{1,2}))?$/.exec(text.trim());
57
+ if (match === null) {
58
+ return null;
59
+ }
60
+ const points = Number(match[1]) * 100 + Number((match[2] ?? "").padEnd(2, "0"));
61
+ return points <= 10_000 ? points : null;
62
+ }
63
+
64
+ /** Translate a draft into the create request, refusing what the contract cannot take. */
65
+ export function planInputFromDraft(draft: PlanDraft): PlanDraftResult {
66
+ const name = draft.name.trim();
67
+ if (name === "") {
68
+ return { ok: false, field: "name", message: "A plan needs a name." };
69
+ }
70
+ const slug = draft.slug.trim();
71
+ if (!SLUG.test(slug)) {
72
+ return { ok: false, field: "slug", message: "Lowercase letters, digits and single hyphens, e.g. team-2027." };
73
+ }
74
+ const minimum = parseUsd(draft.monthlyMinimumUsd);
75
+ if (minimum === null) {
76
+ return { ok: false, field: "monthlyMinimumUsd", message: "A dollar amount, e.g. 99 or 99.50." };
77
+ }
78
+ const share = parseBasisPoints(draft.usageSharePercent);
79
+ if (share === null) {
80
+ return { ok: false, field: "usageSharePercent", message: "A percent from 0 to 100, e.g. 10." };
81
+ }
82
+ const managed = draft.features.includes(Feature.managed_organizations);
83
+ let includedManagedOrganizations: number | undefined;
84
+ let perExtraOrganizationMicros: bigint | undefined;
85
+ if (managed && draft.includedManagedOrganizations.trim() !== "") {
86
+ const included = Number(draft.includedManagedOrganizations.trim());
87
+ if (!Number.isInteger(included) || included < 0) {
88
+ return { ok: false, field: "includedManagedOrganizations", message: "A whole number, or empty for unlimited." };
89
+ }
90
+ includedManagedOrganizations = included;
91
+ const fee = parseUsd(draft.perExtraOrganizationUsd === "" ? "0" : draft.perExtraOrganizationUsd);
92
+ if (fee === null) {
93
+ return { ok: false, field: "perExtraOrganizationUsd", message: "A dollar amount per organization, e.g. 25." };
94
+ }
95
+ perExtraOrganizationMicros = fee;
96
+ }
97
+ return {
98
+ ok: true,
99
+ input: {
100
+ name,
101
+ slug,
102
+ org: "",
103
+ instrument: PlanInstrument.subscription,
104
+ entitlements: {
105
+ features: [...new Set(draft.features)].sort((a, b) => a - b),
106
+ ...(includedManagedOrganizations === undefined ? {} : { limits: { includedManagedOrganizations } }),
107
+ },
108
+ terms: {
109
+ monthlyMinimumMicros: minimum,
110
+ usageShareBasisPoints: share,
111
+ ...(perExtraOrganizationMicros === undefined ? {} : { perExtraOrganizationMicros }),
112
+ },
113
+ },
114
+ };
115
+ }
116
+
@@ -0,0 +1,94 @@
1
+ "use client";
2
+
3
+ import { useCallback, useMemo, useState } from "react";
4
+ import type { PlanInput } from "@stigmer/sdk";
5
+ import type { Plan } from "@stigmer/protos/ai/stigmer/billing/plan/v1/api_pb";
6
+ import { useStigmer } from "../hooks.js";
7
+ import { toError } from "../internal/toError.js";
8
+
9
+ /** Return value of {@link useCreatePlan}. */
10
+ export interface UseCreatePlanReturn {
11
+ /**
12
+ * Add a plan to the catalog. A plan's terms are final once it exists:
13
+ * there is no update, so changed terms are a new plan and the old one
14
+ * is retired.
15
+ */
16
+ readonly createPlan: (input: PlanInput) => Promise<Plan>;
17
+ /** `true` while a create is in flight. */
18
+ readonly isSubmitting: boolean;
19
+ /** Error from the last failed attempt, or `null` when healthy. */
20
+ readonly error: Error | null;
21
+ /** Reset `error` to `null`. */
22
+ readonly clearError: () => void;
23
+ }
24
+
25
+ /**
26
+ * Behaviour hook that adds a plan to the Stigmer Cloud catalog.
27
+ * Platform-operator surface: `can_manage_plans` on `platform:stigmer`.
28
+ */
29
+ export function useCreatePlan(): UseCreatePlanReturn {
30
+ const stigmer = useStigmer();
31
+ const [isSubmitting, setIsSubmitting] = useState(false);
32
+ const [error, setError] = useState<Error | null>(null);
33
+ const createPlan = useCallback(
34
+ async (input: PlanInput): Promise<Plan> => {
35
+ setIsSubmitting(true);
36
+ setError(null);
37
+ try {
38
+ return await stigmer.plan.create(input);
39
+ } catch (e) {
40
+ const err = toError(e);
41
+ setError(err);
42
+ throw err;
43
+ } finally {
44
+ setIsSubmitting(false);
45
+ }
46
+ },
47
+ [stigmer.plan],
48
+ );
49
+ const clearError = useCallback(() => setError(null), []);
50
+ return useMemo(() => ({ createPlan, isSubmitting, error, clearError }), [createPlan, isSubmitting, error, clearError]);
51
+ }
52
+
53
+ /** Return value of {@link useRetirePlan}. */
54
+ export interface UseRetirePlanReturn {
55
+ /**
56
+ * Retire a plan: it can no longer be bought, and every subscription
57
+ * already on it keeps it. Retiring is final.
58
+ */
59
+ readonly retirePlan: (planId: string) => Promise<Plan>;
60
+ /** `true` while a retire is in flight. */
61
+ readonly isSubmitting: boolean;
62
+ /** Error from the last failed attempt, or `null` when healthy. */
63
+ readonly error: Error | null;
64
+ /** Reset `error` to `null`. */
65
+ readonly clearError: () => void;
66
+ }
67
+
68
+ /**
69
+ * Behaviour hook that retires a catalog plan.
70
+ * Platform-operator surface: `can_manage_plans` on `platform:stigmer`.
71
+ */
72
+ export function useRetirePlan(): UseRetirePlanReturn {
73
+ const stigmer = useStigmer();
74
+ const [isSubmitting, setIsSubmitting] = useState(false);
75
+ const [error, setError] = useState<Error | null>(null);
76
+ const retirePlan = useCallback(
77
+ async (planId: string): Promise<Plan> => {
78
+ setIsSubmitting(true);
79
+ setError(null);
80
+ try {
81
+ return await stigmer.plan.retire(planId);
82
+ } catch (e) {
83
+ const err = toError(e);
84
+ setError(err);
85
+ throw err;
86
+ } finally {
87
+ setIsSubmitting(false);
88
+ }
89
+ },
90
+ [stigmer.plan],
91
+ );
92
+ const clearError = useCallback(() => setError(null), []);
93
+ return useMemo(() => ({ retirePlan, isSubmitting, error, clearError }), [retirePlan, isSubmitting, error, clearError]);
94
+ }
@@ -12,10 +12,22 @@
12
12
  *
13
13
  * The section owns the list's data and passes it down, so a create, an edit
14
14
  * or a delete refreshes the list here without a child reaching up.
15
+ *
16
+ * On Stigmer Cloud, teams are a plan feature: where the organization's plan
17
+ * lacks them, the section names the cheapest plan that includes them and
18
+ * links to the plans in place of "New team", and its existing teams stay
19
+ * readable and usable. Only Cloud reads
20
+ * the plan; on Enterprise a license never lists teams and every
21
+ * organization has them, so the read would refuse Enterprise its own teams.
15
22
  */
16
23
  import { useCallback, useId, useState } from "react";
17
24
  import type { Team } from "@stigmer/protos/ai/stigmer/iam/team/v1/api_pb";
18
- import { useResourceAvailable, ApiResourceKind } from "../deployment-mode.js";
25
+ import { Feature } from "@stigmer/protos/ai/stigmer/platform/v1/entitlement_pb";
26
+ import { UpgradeNotice } from "../billing/UpgradeNotice.js";
27
+ import { lowestPlanWith } from "../billing/plan-features.js";
28
+ import { useEntitlements } from "../billing/useEntitlements.js";
29
+ import { usePlans } from "../billing/usePlans.js";
30
+ import { useDeploymentMode, useResourceAvailable, ApiResourceKind } from "../deployment-mode.js";
19
31
  import { CloudFeatureNotice } from "../internal/CloudFeatureNotice.js";
20
32
  import { PermissionGate } from "../iam-policy/PermissionGate.js";
21
33
  import { useOrg } from "../organization/OrgProvider.js";
@@ -37,6 +49,11 @@ export function TeamsSection() {
37
49
  const orgSlug = activeOrg?.metadata?.slug ?? "";
38
50
  const orgId = activeOrg?.metadata?.id ?? "";
39
51
 
52
+ const onCloud = useDeploymentMode() === "cloud";
53
+ const plan = useEntitlements(orgId || null, { enabled: onCloud && teamsServed });
54
+ const planLacksTeams = plan.allows(Feature.teams) === false;
55
+ const catalog = usePlans({ enabled: planLacksTeams });
56
+ const unlockingPlanName = lowestPlanWith(catalog.plans ?? [], Feature.teams)?.metadata?.name;
40
57
  const list = useTeamList(teamsServed && orgSlug ? orgSlug : null);
41
58
  const { refetch } = list;
42
59
  const [flow, setFlow] = useState<FlowState>({ phase: "idle" });
@@ -69,7 +86,7 @@ export function TeamsSection() {
69
86
  Teams
70
87
  </h2>
71
88
 
72
- {teamsServed && orgId && flow.phase === "idle" && (
89
+ {teamsServed && orgId && flow.phase === "idle" && !planLacksTeams && (
73
90
  <PermissionGate resource={{ kind: "organization", id: orgId }} relation="can_create_team">
74
91
  <button
75
92
  type="button"
@@ -88,6 +105,7 @@ export function TeamsSection() {
88
105
  their leaving the organization, takes that access away.
89
106
  </p>
90
107
 
108
+ {teamsServed && orgId && planLacksTeams && <UpgradeNotice feature={Feature.teams} unlockingPlanName={unlockingPlanName} className="stg:mb-3" />}
91
109
  {!teamsServed ? (
92
110
  <CloudFeatureNotice>
93
111
  Teams are available in Stigmer Enterprise and Cloud. This edition
@@ -8,6 +8,7 @@ import {
8
8
  Building2,
9
9
  CreditCard,
10
10
  FileBadge,
11
+ Layers,
11
12
  KeyRound,
12
13
  Link,
13
14
  MessageSquare,
@@ -159,5 +160,11 @@ export const PLATFORM_SETTINGS_NAV_GROUP: SettingsNavGroup = {
159
160
  icon: FileBadge,
160
161
  requiredPermission: "can_issue_license",
161
162
  },
163
+ {
164
+ href: "/settings/plans",
165
+ label: "Plans",
166
+ icon: Layers,
167
+ requiredPermission: "can_manage_plans",
168
+ },
162
169
  ],
163
170
  };
@@ -62,11 +62,17 @@ export function useSettingsNavGroups(): readonly SettingsNavGroup[] {
62
62
  "can_issue_license",
63
63
  { fail: "closed" },
64
64
  );
65
+ const plans = useCheckPermission(
66
+ PLATFORM_RESOURCE,
67
+ "can_manage_plans",
68
+ { fail: "closed" },
69
+ );
65
70
 
66
71
  const pricingAllowed = pricing.allowed;
67
72
  const cursorAccountsAllowed = cursorAccounts.allowed;
68
73
  const providerStandingAllowed = providerStanding.allowed;
69
74
  const licensesAllowed = licenses.allowed;
75
+ const plansAllowed = plans.allowed;
70
76
 
71
77
  return useMemo(() => {
72
78
  const verdicts: Record<string, boolean> = {
@@ -74,6 +80,7 @@ export function useSettingsNavGroups(): readonly SettingsNavGroup[] {
74
80
  can_manage_cursor_accounts: cursorAccountsAllowed,
75
81
  can_view_provider_standing: providerStandingAllowed,
76
82
  can_issue_license: licensesAllowed,
83
+ can_manage_plans: plansAllowed,
77
84
  };
78
85
 
79
86
  const visibleItems = PLATFORM_SETTINGS_NAV_GROUP.items.filter(
@@ -91,5 +98,5 @@ export function useSettingsNavGroups(): readonly SettingsNavGroup[] {
91
98
  ...SETTINGS_NAV_GROUPS,
92
99
  { ...PLATFORM_SETTINGS_NAV_GROUP, items: visibleItems },
93
100
  ];
94
- }, [pricingAllowed, cursorAccountsAllowed, providerStandingAllowed, licensesAllowed]);
101
+ }, [pricingAllowed, cursorAccountsAllowed, providerStandingAllowed, licensesAllowed, plansAllowed]);
95
102
  }
@@ -11,6 +11,7 @@ import { cn } from "@stigmer/theme";
11
11
  import { getUserMessage } from "@stigmer/sdk";
12
12
  import { Field, INPUT_CLASSES } from "../internal/form-primitives.js";
13
13
  import { SpinnerIcon } from "../internal/SpinnerIcon.js";
14
+ import { planUpgradeFeature, UpgradeNotice } from "../billing/UpgradeNotice.js";
14
15
  import { useCreateTeam } from "./useTeamMutations.js";
15
16
 
16
17
  /** The longest description the contract accepts (`TeamSpec.description`). */
@@ -23,6 +24,11 @@ export interface CreateTeamFormProps {
23
24
  /** Fired with the created team. */
24
25
  readonly onCreated?: (team: Team) => void;
25
26
  readonly onCancel?: () => void;
27
+ /**
28
+ * Where the plans are, for the notice shown when Stigmer Cloud refuses a
29
+ * team on the organization's plan. Defaults to `/settings/billing`.
30
+ */
31
+ readonly billingHref?: string;
26
32
  readonly className?: string;
27
33
  }
28
34
 
@@ -31,12 +37,13 @@ export interface CreateTeamFormProps {
31
37
  *
32
38
  * All visual properties flow through `--stgm-*` design tokens.
33
39
  */
34
- export function CreateTeamForm({ org, onCreated, onCancel, className }: CreateTeamFormProps) {
40
+ export function CreateTeamForm({ org, onCreated, onCancel, billingHref, className }: CreateTeamFormProps) {
35
41
  const { create, isCreating, error, clearError } = useCreateTeam();
36
42
  const [name, setName] = useState("");
37
43
  const [description, setDescription] = useState("");
38
44
 
39
45
  const canSubmit = name.trim() !== "" && !isCreating;
46
+ const upgradeFeature = planUpgradeFeature(error);
40
47
 
41
48
  const handleSubmit = useCallback(
42
49
  async (e: FormEvent) => {
@@ -83,11 +90,13 @@ export function CreateTeamForm({ org, onCreated, onCancel, className }: CreateTe
83
90
  />
84
91
  </Field>
85
92
 
86
- {error && (
93
+ {error && upgradeFeature !== null ? (
94
+ <UpgradeNotice feature={upgradeFeature} error={error} billingHref={billingHref} />
95
+ ) : error ? (
87
96
  <p className="stg:text-destructive stg:text-[0.65rem]" role="alert">
88
97
  {getUserMessage(error)}
89
98
  </p>
90
- )}
99
+ ) : null}
91
100
 
92
101
  <div className="stg:flex stg:items-center stg:gap-2">
93
102
  <button
@@ -18,6 +18,8 @@ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/
18
18
  import { IamRole } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
19
19
  import { cn } from "@stigmer/theme";
20
20
  import { getUserMessage, granteeFromView, type Grantee } from "@stigmer/sdk";
21
+ import { Feature } from "@stigmer/protos/ai/stigmer/platform/v1/entitlement_pb";
22
+ import { planUpgradeFeature, UpgradeNotice } from "../billing/UpgradeNotice.js";
21
23
  import { UNSTYLED_LIST } from "../internal/element-resets.js";
22
24
  import { SpinnerIcon } from "../internal/SpinnerIcon.js";
23
25
  import { AccessRow, accessEntryKey } from "../iam-policy/AccessRow.js";
@@ -152,11 +154,13 @@ export function TeamMembersPanel({ teamId, orgId, className }: TeamMembersPanelP
152
154
  Add member
153
155
  </button>
154
156
  </div>
155
- {grantError && (
157
+ {grantError && planUpgradeFeature(grantError) === Feature.teams ? (
158
+ <UpgradeNotice feature={Feature.teams} error={grantError} className="stg:mt-1" />
159
+ ) : grantError ? (
156
160
  <p className="stg:mt-1 stg:text-destructive stg:text-[0.65rem]" role="alert">
157
161
  {getUserMessage(grantError)}
158
162
  </p>
159
- )}
163
+ ) : null}
160
164
  </PermissionGate>
161
165
  </div>
162
166
  );
@@ -8,6 +8,11 @@ export interface CreateTeamFormProps {
8
8
  /** Fired with the created team. */
9
9
  readonly onCreated?: (team: Team) => void;
10
10
  readonly onCancel?: () => void;
11
+ /**
12
+ * Where the plans are, for the notice shown when Stigmer Cloud refuses a
13
+ * team on the organization's plan. Defaults to `/settings/billing`.
14
+ */
15
+ readonly billingHref?: string;
11
16
  readonly className?: string;
12
17
  }
13
18
  /**
@@ -15,5 +20,5 @@ export interface CreateTeamFormProps {
15
20
  *
16
21
  * All visual properties flow through `--stgm-*` design tokens.
17
22
  */
18
- export declare function CreateTeamForm({ org, onCreated, onCancel, className }: CreateTeamFormProps): import("react/jsx-runtime").JSX.Element;
23
+ export declare function CreateTeamForm({ org, onCreated, onCancel, billingHref, className }: CreateTeamFormProps): import("react/jsx-runtime").JSX.Element;
19
24
  //# sourceMappingURL=CreateTeamForm.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"CreateTeamForm.d.ts","sourceRoot":"","sources":["../src/team/CreateTeamForm.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,+CAA+C,CAAC;AAO1E,6EAA6E;AAC7E,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAE/C,wCAAwC;AACxC,MAAM,WAAW,mBAAmB;IAClC,gDAAgD;IAChD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,mCAAmC;IACnC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IAC1C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IAC/B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,EAAE,GAAG,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,mBAAmB,2CAwF1F"}
1
+ {"version":3,"file":"CreateTeamForm.d.ts","sourceRoot":"","sources":["../src/team/CreateTeamForm.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,+CAA+C,CAAC;AAQ1E,6EAA6E;AAC7E,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAE/C,wCAAwC;AACxC,MAAM,WAAW,mBAAmB;IAClC,gDAAgD;IAChD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,mCAAmC;IACnC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IAC1C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IAC/B;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,EAAE,GAAG,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,EAAE,SAAS,EAAE,EAAE,mBAAmB,2CA2FvG"}
@@ -10,6 +10,7 @@ import { cn } from "@stigmer/theme";
10
10
  import { getUserMessage } from "@stigmer/sdk";
11
11
  import { Field, INPUT_CLASSES } from "../internal/form-primitives.js";
12
12
  import { SpinnerIcon } from "../internal/SpinnerIcon.js";
13
+ import { planUpgradeFeature, UpgradeNotice } from "../billing/UpgradeNotice.js";
13
14
  import { useCreateTeam } from "./useTeamMutations.js";
14
15
  /** The longest description the contract accepts (`TeamSpec.description`). */
15
16
  export const TEAM_DESCRIPTION_MAX_LENGTH = 500;
@@ -18,11 +19,12 @@ export const TEAM_DESCRIPTION_MAX_LENGTH = 500;
18
19
  *
19
20
  * All visual properties flow through `--stgm-*` design tokens.
20
21
  */
21
- export function CreateTeamForm({ org, onCreated, onCancel, className }) {
22
+ export function CreateTeamForm({ org, onCreated, onCancel, billingHref, className }) {
22
23
  const { create, isCreating, error, clearError } = useCreateTeam();
23
24
  const [name, setName] = useState("");
24
25
  const [description, setDescription] = useState("");
25
26
  const canSubmit = name.trim() !== "" && !isCreating;
27
+ const upgradeFeature = planUpgradeFeature(error);
26
28
  const handleSubmit = useCallback(async (e) => {
27
29
  e.preventDefault();
28
30
  if (!canSubmit)
@@ -40,6 +42,6 @@ export function CreateTeamForm({ org, onCreated, onCancel, className }) {
40
42
  // error state is managed by useCreateTeam
41
43
  }
42
44
  }, [canSubmit, clearError, create, name, org, description, onCreated]);
43
- return (_jsxs("form", { onSubmit: handleSubmit, className: cn("stg:space-y-3", className), children: [_jsx("h3", { className: "stg:text-foreground stg:text-sm stg:font-semibold", children: "New team" }), _jsx(Field, { label: "Name", required: true, children: _jsx("input", { type: "text", value: name, onChange: (e) => setName(e.target.value), placeholder: "Site Reliability", disabled: isCreating, autoFocus: true, className: INPUT_CLASSES }) }), _jsx(Field, { label: "Description", children: _jsx("textarea", { value: description, onChange: (e) => setDescription(e.target.value), placeholder: "Who is on this team and what it is for", maxLength: TEAM_DESCRIPTION_MAX_LENGTH, rows: 2, disabled: isCreating, className: cn(INPUT_CLASSES, "stg:resize-none") }) }), error && (_jsx("p", { className: "stg:text-destructive stg:text-[0.65rem]", role: "alert", children: getUserMessage(error) })), _jsxs("div", { className: "stg:flex stg:items-center stg:gap-2", children: [_jsxs("button", { type: "submit", disabled: !canSubmit, className: cn("stg:inline-flex stg:items-center stg:gap-1.5 stg:rounded-md stg:px-3 stg:py-1.5 stg:text-xs stg:font-medium", "stg:bg-primary stg:text-primary-foreground stg:hover:bg-primary-hover", "stg:disabled:pointer-events-none stg:disabled:opacity-40"), children: [isCreating && _jsx(SpinnerIcon, { size: 12 }), "Create team"] }), onCancel && (_jsx("button", { type: "button", onClick: onCancel, disabled: isCreating, className: cn("stg:rounded-md stg:px-3 stg:py-1.5 stg:text-xs", "stg:text-muted-foreground stg:hover:text-foreground stg:hover:bg-accent-hover", "stg:disabled:pointer-events-none stg:disabled:opacity-50"), children: "Cancel" }))] })] }));
45
+ return (_jsxs("form", { onSubmit: handleSubmit, className: cn("stg:space-y-3", className), children: [_jsx("h3", { className: "stg:text-foreground stg:text-sm stg:font-semibold", children: "New team" }), _jsx(Field, { label: "Name", required: true, children: _jsx("input", { type: "text", value: name, onChange: (e) => setName(e.target.value), placeholder: "Site Reliability", disabled: isCreating, autoFocus: true, className: INPUT_CLASSES }) }), _jsx(Field, { label: "Description", children: _jsx("textarea", { value: description, onChange: (e) => setDescription(e.target.value), placeholder: "Who is on this team and what it is for", maxLength: TEAM_DESCRIPTION_MAX_LENGTH, rows: 2, disabled: isCreating, className: cn(INPUT_CLASSES, "stg:resize-none") }) }), error && upgradeFeature !== null ? (_jsx(UpgradeNotice, { feature: upgradeFeature, error: error, billingHref: billingHref })) : error ? (_jsx("p", { className: "stg:text-destructive stg:text-[0.65rem]", role: "alert", children: getUserMessage(error) })) : null, _jsxs("div", { className: "stg:flex stg:items-center stg:gap-2", children: [_jsxs("button", { type: "submit", disabled: !canSubmit, className: cn("stg:inline-flex stg:items-center stg:gap-1.5 stg:rounded-md stg:px-3 stg:py-1.5 stg:text-xs stg:font-medium", "stg:bg-primary stg:text-primary-foreground stg:hover:bg-primary-hover", "stg:disabled:pointer-events-none stg:disabled:opacity-40"), children: [isCreating && _jsx(SpinnerIcon, { size: 12 }), "Create team"] }), onCancel && (_jsx("button", { type: "button", onClick: onCancel, disabled: isCreating, className: cn("stg:rounded-md stg:px-3 stg:py-1.5 stg:text-xs", "stg:text-muted-foreground stg:hover:text-foreground stg:hover:bg-accent-hover", "stg:disabled:pointer-events-none stg:disabled:opacity-50"), children: "Cancel" }))] })] }));
44
46
  }
45
47
  //# sourceMappingURL=CreateTeamForm.js.map