@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.
Files changed (200) 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/channel-app/ChannelAppDetailPanel.d.ts +5 -0
  91. package/channel-app/ChannelAppDetailPanel.d.ts.map +1 -1
  92. package/channel-app/ChannelAppDetailPanel.js +9 -3
  93. package/channel-app/ChannelAppDetailPanel.js.map +1 -1
  94. package/channel-app/ChannelAppListPanel.d.ts +13 -4
  95. package/channel-app/ChannelAppListPanel.d.ts.map +1 -1
  96. package/channel-app/ChannelAppListPanel.js +5 -5
  97. package/channel-app/ChannelAppListPanel.js.map +1 -1
  98. package/channel-app/copy.d.ts +15 -0
  99. package/channel-app/copy.d.ts.map +1 -0
  100. package/channel-app/copy.js +15 -0
  101. package/channel-app/copy.js.map +1 -0
  102. package/index.d.ts +4 -2
  103. package/index.d.ts.map +1 -1
  104. package/index.js +4 -1
  105. package/index.js.map +1 -1
  106. package/internal/features.d.ts +11 -0
  107. package/internal/features.d.ts.map +1 -0
  108. package/internal/features.js +42 -0
  109. package/internal/features.js.map +1 -0
  110. package/licenses/license-format.d.ts +5 -3
  111. package/licenses/license-format.d.ts.map +1 -1
  112. package/licenses/license-format.js +10 -19
  113. package/licenses/license-format.js.map +1 -1
  114. package/package.json +5 -5
  115. package/plan-catalog/PlanCatalogConsole.d.ts +22 -0
  116. package/plan-catalog/PlanCatalogConsole.d.ts.map +1 -0
  117. package/plan-catalog/PlanCatalogConsole.js +90 -0
  118. package/plan-catalog/PlanCatalogConsole.js.map +1 -0
  119. package/plan-catalog/PlanCreateForm.d.ts +17 -0
  120. package/plan-catalog/PlanCreateForm.d.ts.map +1 -0
  121. package/plan-catalog/PlanCreateForm.js +52 -0
  122. package/plan-catalog/PlanCreateForm.js.map +1 -0
  123. package/plan-catalog/PlansAccessNotice.d.ts +10 -0
  124. package/plan-catalog/PlansAccessNotice.d.ts.map +1 -0
  125. package/plan-catalog/PlansAccessNotice.js +13 -0
  126. package/plan-catalog/PlansAccessNotice.js.map +1 -0
  127. package/plan-catalog/index.d.ts +8 -0
  128. package/plan-catalog/index.d.ts.map +1 -0
  129. package/plan-catalog/index.js +10 -0
  130. package/plan-catalog/index.js.map +1 -0
  131. package/plan-catalog/plan-draft.d.ts +30 -0
  132. package/plan-catalog/plan-draft.d.ts.map +1 -0
  133. package/plan-catalog/plan-draft.js +91 -0
  134. package/plan-catalog/plan-draft.js.map +1 -0
  135. package/plan-catalog/usePlanMutations.d.ts +42 -0
  136. package/plan-catalog/usePlanMutations.d.ts.map +1 -0
  137. package/plan-catalog/usePlanMutations.js +57 -0
  138. package/plan-catalog/usePlanMutations.js.map +1 -0
  139. package/settings/ChannelAppsSection.d.ts +7 -0
  140. package/settings/ChannelAppsSection.d.ts.map +1 -1
  141. package/settings/ChannelAppsSection.js +15 -2
  142. package/settings/ChannelAppsSection.js.map +1 -1
  143. package/settings/TeamsSection.d.ts.map +1 -1
  144. package/settings/TeamsSection.js +19 -2
  145. package/settings/TeamsSection.js.map +1 -1
  146. package/settings/settings-nav.d.ts.map +1 -1
  147. package/settings/settings-nav.js +7 -1
  148. package/settings/settings-nav.js.map +1 -1
  149. package/settings/useSettingsNavGroups.d.ts.map +1 -1
  150. package/settings/useSettingsNavGroups.js +4 -1
  151. package/settings/useSettingsNavGroups.js.map +1 -1
  152. package/src/billing/AutoRechargeCard.tsx +2 -2
  153. package/src/billing/BillingSection.tsx +68 -11
  154. package/src/billing/ChangePlanDialog.tsx +221 -0
  155. package/src/billing/CreditPackGrid.tsx +1 -1
  156. package/src/billing/LowBalanceBanner.tsx +1 -1
  157. package/src/billing/PaymentMethodCard.tsx +28 -2
  158. package/src/billing/PlanCard.tsx +171 -0
  159. package/src/billing/PlanPicker.tsx +178 -0
  160. package/src/billing/PlanSection.tsx +234 -0
  161. package/src/billing/UpgradeNotice.tsx +95 -0
  162. package/src/billing/credit-packs.ts +4 -1
  163. package/src/billing/index.ts +26 -1
  164. package/src/billing/plan-features.ts +162 -0
  165. package/src/billing/plan-state.ts +92 -0
  166. package/src/billing/redirect.ts +45 -0
  167. package/src/billing/useBillingAccount.ts +12 -0
  168. package/src/billing/useChangePlan.ts +115 -0
  169. package/src/billing/useCreateBillingPortalSession.ts +17 -6
  170. package/src/billing/useCreateCheckoutSession.ts +12 -7
  171. package/src/billing/useCreatePaymentMethodSetupSession.ts +77 -0
  172. package/src/billing/useEntitlements.ts +80 -0
  173. package/src/billing/usePeriodEstimate.ts +59 -0
  174. package/src/billing/usePlans.ts +65 -0
  175. package/src/billing/useSubscription.ts +70 -0
  176. package/src/channel-app/ChannelAppDetailPanel.tsx +179 -158
  177. package/src/channel-app/ChannelAppListPanel.tsx +14 -4
  178. package/src/channel-app/copy.ts +16 -0
  179. package/src/index.ts +54 -0
  180. package/src/internal/features.ts +49 -0
  181. package/src/licenses/license-format.ts +10 -19
  182. package/src/plan-catalog/PlanCatalogConsole.tsx +197 -0
  183. package/src/plan-catalog/PlanCreateForm.tsx +167 -0
  184. package/src/plan-catalog/PlansAccessNotice.tsx +20 -0
  185. package/src/plan-catalog/index.ts +14 -0
  186. package/src/plan-catalog/plan-draft.ts +116 -0
  187. package/src/plan-catalog/usePlanMutations.ts +94 -0
  188. package/src/settings/ChannelAppsSection.tsx +19 -2
  189. package/src/settings/TeamsSection.tsx +20 -2
  190. package/src/settings/settings-nav.ts +7 -0
  191. package/src/settings/useSettingsNavGroups.ts +8 -1
  192. package/src/team/CreateTeamForm.tsx +12 -3
  193. package/src/team/TeamMembersPanel.tsx +6 -2
  194. package/team/CreateTeamForm.d.ts +6 -1
  195. package/team/CreateTeamForm.d.ts.map +1 -1
  196. package/team/CreateTeamForm.js +4 -2
  197. package/team/CreateTeamForm.js.map +1 -1
  198. package/team/TeamMembersPanel.d.ts.map +1 -1
  199. package/team/TeamMembersPanel.js +3 -1
  200. package/team/TeamMembersPanel.js.map +1 -1
@@ -18,6 +18,7 @@ import {
18
18
  whatsappChannelAppWebhookUrl,
19
19
  } from "./whatsappAppSetup.js";
20
20
  import { CopyBlock, CopyRow, FormField } from "./internal.js";
21
+ import { PermissionGate } from "../iam-policy/PermissionGate.js";
21
22
  import { SpinnerIcon } from "../internal/SpinnerIcon.js";
22
23
 
23
24
  // ---------------------------------------------------------------------------
@@ -66,6 +67,11 @@ export interface ChannelAppDetailPanelProps {
66
67
  * untouched preserves the stored values (rotate one secret without
67
68
  * re-entering the others). Deletion is refused server-side while any
68
69
  * agent channel still installs through this app.
70
+ *
71
+ * The credentials form is offered to a caller who may edit the app
72
+ * (`can_edit`) and the deletion section to one who may delete it
73
+ * (`can_delete`): its creator and the organization's admins. Someone
74
+ * granted viewer on an app sees its setup values with no actions.
69
75
  */
70
76
  export function ChannelAppDetailPanel({
71
77
  channelApp,
@@ -127,50 +133,55 @@ export function ChannelAppDetailPanel({
127
133
  ) : null}
128
134
 
129
135
  {/* Deletion */}
130
- <section className="stg:border-t stg:border-border stg:pt-3" aria-label="Danger zone">
131
- {deleteError && (
132
- <p className="stg:text-destructive stg:mb-2 stg:text-[0.65rem]" role="alert">
133
- {getUserMessage(deleteError)}
134
- </p>
135
- )}
136
- {confirmingDelete ? (
137
- <div className="stg:flex stg:items-center stg:gap-2">
138
- <p className="stg:text-xs stg:text-foreground">
139
- Delete this channel app? Channels installing through it must be
140
- disconnected first.
136
+ <PermissionGate
137
+ resource={{ kind: "channel_app", id: appId }}
138
+ relation="can_delete"
139
+ >
140
+ <section className="stg:border-t stg:border-border stg:pt-3" aria-label="Danger zone">
141
+ {deleteError && (
142
+ <p className="stg:text-destructive stg:mb-2 stg:text-[0.65rem]" role="alert">
143
+ {getUserMessage(deleteError)}
141
144
  </p>
145
+ )}
146
+ {confirmingDelete ? (
147
+ <div className="stg:flex stg:items-center stg:gap-2">
148
+ <p className="stg:text-xs stg:text-foreground">
149
+ Delete this channel app? Channels installing through it must be
150
+ disconnected first.
151
+ </p>
152
+ <button
153
+ type="button"
154
+ onClick={handleDelete}
155
+ disabled={isDeleting}
156
+ className={cn(
157
+ "stg:inline-flex stg:items-center stg:gap-1.5 stg:rounded-md stg:px-2.5 stg:py-1 stg:text-xs stg:font-medium",
158
+ "stg:bg-destructive stg:text-destructive-foreground stg:hover:opacity-90",
159
+ "stg:disabled:pointer-events-none stg:disabled:opacity-40",
160
+ )}
161
+ >
162
+ {isDeleting && <SpinnerIcon size={12} />}
163
+ Delete
164
+ </button>
165
+ <button
166
+ type="button"
167
+ onClick={() => setConfirmingDelete(false)}
168
+ disabled={isDeleting}
169
+ className="stg:text-muted-foreground stg:hover:text-foreground stg:text-xs stg:transition-colors"
170
+ >
171
+ Keep
172
+ </button>
173
+ </div>
174
+ ) : (
142
175
  <button
143
176
  type="button"
144
- onClick={handleDelete}
145
- disabled={isDeleting}
146
- className={cn(
147
- "stg:inline-flex stg:items-center stg:gap-1.5 stg:rounded-md stg:px-2.5 stg:py-1 stg:text-xs stg:font-medium",
148
- "stg:bg-destructive stg:text-destructive-foreground stg:hover:opacity-90",
149
- "stg:disabled:pointer-events-none stg:disabled:opacity-40",
150
- )}
151
- >
152
- {isDeleting && <SpinnerIcon size={12} />}
153
- Delete
154
- </button>
155
- <button
156
- type="button"
157
- onClick={() => setConfirmingDelete(false)}
158
- disabled={isDeleting}
159
- className="stg:text-muted-foreground stg:hover:text-foreground stg:text-xs stg:transition-colors"
177
+ onClick={() => setConfirmingDelete(true)}
178
+ className="stg:text-destructive stg:text-xs stg:font-medium stg:hover:opacity-80"
160
179
  >
161
- Keep
180
+ Delete channel app
162
181
  </button>
163
- </div>
164
- ) : (
165
- <button
166
- type="button"
167
- onClick={() => setConfirmingDelete(true)}
168
- className="stg:text-destructive stg:text-xs stg:font-medium stg:hover:opacity-80"
169
- >
170
- Delete channel app
171
- </button>
172
- )}
173
- </section>
182
+ )}
183
+ </section>
184
+ </PermissionGate>
174
185
  </div>
175
186
  );
176
187
  }
@@ -270,63 +281,68 @@ function SlackAppDetail({
270
281
  </section>
271
282
 
272
283
  {/* Credential rotation */}
273
- <form onSubmit={handleSave} className="stg:space-y-3" aria-label="Credentials">
274
- <p className="stg:text-xs stg:font-medium stg:text-foreground">Credentials</p>
275
- <p className="stg:-mt-2 stg:text-[0.65rem] stg:text-muted-foreground">
276
- Secrets show as <code className="stg:font-mono">***REDACTED***</code>;
277
- leave a field untouched to keep its stored value, or paste a new
278
- one to rotate it.
279
- </p>
280
-
281
- <FormField
282
- id={`${baseId}-client-id`}
283
- label="Client ID"
284
- value={clientId}
285
- onChange={setClientId}
286
- placeholder="1234567890.0987654321"
287
- disabled={isUpdating}
288
- required
289
- />
290
- <FormField
291
- id={`${baseId}-client-secret`}
292
- label="Client secret"
293
- value={clientSecret}
294
- onChange={setClientSecret}
295
- placeholder="Client secret"
296
- type="password"
297
- disabled={isUpdating}
298
- required
299
- />
300
- <FormField
301
- id={`${baseId}-signing-secret`}
302
- label="Signing secret"
303
- value={signingSecret}
304
- onChange={setSigningSecret}
305
- placeholder="Signing secret"
306
- type="password"
307
- disabled={isUpdating}
308
- required
309
- />
310
-
311
- {updateError && (
312
- <p className="stg:text-destructive stg:text-[0.65rem]" role="alert">
313
- {getUserMessage(updateError)}
284
+ <PermissionGate
285
+ resource={{ kind: "channel_app", id: appId }}
286
+ relation="can_edit"
287
+ >
288
+ <form onSubmit={handleSave} className="stg:space-y-3" aria-label="Credentials">
289
+ <p className="stg:text-xs stg:font-medium stg:text-foreground">Credentials</p>
290
+ <p className="stg:-mt-2 stg:text-[0.65rem] stg:text-muted-foreground">
291
+ Secrets show as <code className="stg:font-mono">***REDACTED***</code>;
292
+ leave a field untouched to keep its stored value, or paste a new
293
+ one to rotate it.
314
294
  </p>
315
- )}
316
295
 
317
- <button
318
- type="submit"
319
- disabled={!canSave}
320
- className={cn(
321
- "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",
322
- "stg:bg-primary stg:text-primary-foreground stg:hover:bg-primary-hover",
323
- "stg:disabled:pointer-events-none stg:disabled:opacity-40",
296
+ <FormField
297
+ id={`${baseId}-client-id`}
298
+ label="Client ID"
299
+ value={clientId}
300
+ onChange={setClientId}
301
+ placeholder="1234567890.0987654321"
302
+ disabled={isUpdating}
303
+ required
304
+ />
305
+ <FormField
306
+ id={`${baseId}-client-secret`}
307
+ label="Client secret"
308
+ value={clientSecret}
309
+ onChange={setClientSecret}
310
+ placeholder="Client secret"
311
+ type="password"
312
+ disabled={isUpdating}
313
+ required
314
+ />
315
+ <FormField
316
+ id={`${baseId}-signing-secret`}
317
+ label="Signing secret"
318
+ value={signingSecret}
319
+ onChange={setSigningSecret}
320
+ placeholder="Signing secret"
321
+ type="password"
322
+ disabled={isUpdating}
323
+ required
324
+ />
325
+
326
+ {updateError && (
327
+ <p className="stg:text-destructive stg:text-[0.65rem]" role="alert">
328
+ {getUserMessage(updateError)}
329
+ </p>
324
330
  )}
325
- >
326
- {isUpdating && <SpinnerIcon size={12} />}
327
- Save credentials
328
- </button>
329
- </form>
331
+
332
+ <button
333
+ type="submit"
334
+ disabled={!canSave}
335
+ className={cn(
336
+ "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",
337
+ "stg:bg-primary stg:text-primary-foreground stg:hover:bg-primary-hover",
338
+ "stg:disabled:pointer-events-none stg:disabled:opacity-40",
339
+ )}
340
+ >
341
+ {isUpdating && <SpinnerIcon size={12} />}
342
+ Save credentials
343
+ </button>
344
+ </form>
345
+ </PermissionGate>
330
346
  </>
331
347
  );
332
348
  }
@@ -440,74 +456,79 @@ function WhatsAppAppDetail({
440
456
  </section>
441
457
 
442
458
  {/* Credential rotation */}
443
- <form onSubmit={handleSave} className="stg:space-y-3" aria-label="Credentials">
444
- <p className="stg:text-xs stg:font-medium stg:text-foreground">Credentials</p>
445
- <p className="stg:-mt-2 stg:text-[0.65rem] stg:text-muted-foreground">
446
- Secrets show as <code className="stg:font-mono">***REDACTED***</code>;
447
- leave a field untouched to keep its stored value, or paste a new
448
- one to rotate it.
449
- </p>
450
-
451
- <FormField
452
- id={`${baseId}-app-id`}
453
- label="App ID"
454
- value={metaAppId}
455
- onChange={setMetaAppId}
456
- placeholder="1234567890123456"
457
- disabled={isUpdating}
458
- required
459
- />
460
- <FormField
461
- id={`${baseId}-app-secret`}
462
- label="App secret"
463
- value={appSecret}
464
- onChange={setAppSecret}
465
- placeholder="App secret"
466
- type="password"
467
- disabled={isUpdating}
468
- required
469
- />
470
- <FormField
471
- id={`${baseId}-access-token`}
472
- label="Access token"
473
- value={accessToken}
474
- onChange={setAccessToken}
475
- placeholder="Long-lived system-user access token"
476
- type="password"
477
- disabled={isUpdating}
478
- required
479
- />
480
- <FormField
481
- id={`${baseId}-verify-token`}
482
- label="Verify token"
483
- value={verifyToken}
484
- onChange={setVerifyToken}
485
- placeholder="Verify token"
486
- type="password"
487
- hint="Rotating it? Update Meta's webhook configuration to the same value."
488
- disabled={isUpdating}
489
- required
490
- />
491
-
492
- {updateError && (
493
- <p className="stg:text-destructive stg:text-[0.65rem]" role="alert">
494
- {getUserMessage(updateError)}
459
+ <PermissionGate
460
+ resource={{ kind: "channel_app", id: appId }}
461
+ relation="can_edit"
462
+ >
463
+ <form onSubmit={handleSave} className="stg:space-y-3" aria-label="Credentials">
464
+ <p className="stg:text-xs stg:font-medium stg:text-foreground">Credentials</p>
465
+ <p className="stg:-mt-2 stg:text-[0.65rem] stg:text-muted-foreground">
466
+ Secrets show as <code className="stg:font-mono">***REDACTED***</code>;
467
+ leave a field untouched to keep its stored value, or paste a new
468
+ one to rotate it.
495
469
  </p>
496
- )}
497
470
 
498
- <button
499
- type="submit"
500
- disabled={!canSave}
501
- className={cn(
502
- "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",
503
- "stg:bg-primary stg:text-primary-foreground stg:hover:bg-primary-hover",
504
- "stg:disabled:pointer-events-none stg:disabled:opacity-40",
471
+ <FormField
472
+ id={`${baseId}-app-id`}
473
+ label="App ID"
474
+ value={metaAppId}
475
+ onChange={setMetaAppId}
476
+ placeholder="1234567890123456"
477
+ disabled={isUpdating}
478
+ required
479
+ />
480
+ <FormField
481
+ id={`${baseId}-app-secret`}
482
+ label="App secret"
483
+ value={appSecret}
484
+ onChange={setAppSecret}
485
+ placeholder="App secret"
486
+ type="password"
487
+ disabled={isUpdating}
488
+ required
489
+ />
490
+ <FormField
491
+ id={`${baseId}-access-token`}
492
+ label="Access token"
493
+ value={accessToken}
494
+ onChange={setAccessToken}
495
+ placeholder="Long-lived system-user access token"
496
+ type="password"
497
+ disabled={isUpdating}
498
+ required
499
+ />
500
+ <FormField
501
+ id={`${baseId}-verify-token`}
502
+ label="Verify token"
503
+ value={verifyToken}
504
+ onChange={setVerifyToken}
505
+ placeholder="Verify token"
506
+ type="password"
507
+ hint="Rotating it? Update Meta's webhook configuration to the same value."
508
+ disabled={isUpdating}
509
+ required
510
+ />
511
+
512
+ {updateError && (
513
+ <p className="stg:text-destructive stg:text-[0.65rem]" role="alert">
514
+ {getUserMessage(updateError)}
515
+ </p>
505
516
  )}
506
- >
507
- {isUpdating && <SpinnerIcon size={12} />}
508
- Save credentials
509
- </button>
510
- </form>
517
+
518
+ <button
519
+ type="submit"
520
+ disabled={!canSave}
521
+ className={cn(
522
+ "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",
523
+ "stg:bg-primary stg:text-primary-foreground stg:hover:bg-primary-hover",
524
+ "stg:disabled:pointer-events-none stg:disabled:opacity-40",
525
+ )}
526
+ >
527
+ {isUpdating && <SpinnerIcon size={12} />}
528
+ Save credentials
529
+ </button>
530
+ </form>
531
+ </PermissionGate>
511
532
  </>
512
533
  );
513
534
  }
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
 
3
+ import type { ReactNode } from "react";
3
4
  import { cn } from "@stigmer/theme";
4
5
  import { getUserMessage } from "@stigmer/sdk";
5
6
  import { timestampDate } from "@bufbuild/protobuf/wkt";
@@ -22,14 +23,22 @@ export interface ChannelAppListPanelProps {
22
23
  readonly onEdit?: (app: ChannelApp) => void;
23
24
  /** Re-expose refetch so parents can trigger a list refresh. */
24
25
  readonly onRefetchRef?: (refetch: () => void) => void;
26
+ /**
27
+ * Shown when the list is empty. Defaults to "No channel apps registered
28
+ * yet." The list holds only the apps the caller may view (their creator,
29
+ * anyone granted viewer on one, and the organization's admins), so a host
30
+ * that knows the caller may not manage channel apps should say who does
31
+ * instead.
32
+ */
33
+ readonly emptyState?: ReactNode;
25
34
  /** Additional CSS class names for the root container. */
26
35
  readonly className?: string;
27
36
  }
28
37
 
29
38
  /**
30
- * Displays the {@link ChannelApp} resources owned by an organization —
31
- * the customer's own provider apps (Slack, Meta/WhatsApp) that agent
32
- * channels can install through.
39
+ * Displays the {@link ChannelApp} resources of an organization that the
40
+ * caller may view — the customer's own provider apps (Slack,
41
+ * Meta/WhatsApp) that agent channels can install through.
33
42
  *
34
43
  * Each row shows the app name, provider, the provider's non-secret app
35
44
  * identifier, and creation date. Secret fields never appear (they
@@ -49,6 +58,7 @@ export function ChannelAppListPanel({
49
58
  org,
50
59
  onEdit,
51
60
  onRefetchRef,
61
+ emptyState = "No channel apps registered yet.",
52
62
  className,
53
63
  }: ChannelAppListPanelProps) {
54
64
  const { channelApps, isLoading, error, refetch } = useChannelAppList(org);
@@ -87,7 +97,7 @@ export function ChannelAppListPanel({
87
97
  className,
88
98
  )}
89
99
  >
90
- No channel apps registered yet.
100
+ {emptyState}
91
101
  </p>
92
102
  );
93
103
  }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Channel-app copy shared by more than one surface, so the settings section
3
+ * and anything else that lists an organization's channel apps say the same
4
+ * thing.
5
+ *
6
+ * A channel app carries its organization's Slack or Meta app secrets, so the
7
+ * model shows it to its creator, anyone granted viewer on it and the
8
+ * organization's admins, and plain members and viewers may not see it at all
9
+ * (stigmer/stigmer#1384). An empty list is therefore not evidence that none
10
+ * is registered, so a caller who may not create channel apps is told who
11
+ * manages them instead of being told that nothing exists.
12
+ */
13
+
14
+ /** Shown in place of the empty state to a caller who may not create channel apps. */
15
+ export const CHANNEL_APPS_MANAGED_BY_ADMINS =
16
+ "Channel apps are managed by your organization's admins.";
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). */