@cosmicdrift/kumiko-bundled-features 0.386.0 → 0.388.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 (643) hide show
  1. package/LICENSE +21 -57
  2. package/dist/agent-tools/tool-catalog.js +1 -1
  3. package/dist/agent-tools/tool-dispatch.d.ts +7 -7
  4. package/dist/agent-tools/tool-dispatch.js +9 -9
  5. package/dist/audit/handlers/audit-events.js +1 -1
  6. package/dist/audit/screens.js +1 -1
  7. package/dist/auth-email-password/feature.js +1 -1
  8. package/dist/auth-email-password/handlers/change-password.write.js +3 -3
  9. package/dist/auth-email-password/handlers/confirm-token-flow.js +5 -5
  10. package/dist/auth-email-password/handlers/invite-accept-with-login.write.js +1 -1
  11. package/dist/auth-email-password/handlers/invite-accept.write.js +18 -18
  12. package/dist/auth-email-password/handlers/invite-create.write.d.ts +2 -2
  13. package/dist/auth-email-password/handlers/invite-create.write.js +10 -10
  14. package/dist/auth-email-password/handlers/invite-info.query.js +1 -1
  15. package/dist/auth-email-password/handlers/invite-signup-complete.write.js +24 -26
  16. package/dist/auth-email-password/handlers/login.write.d.ts +1 -1
  17. package/dist/auth-email-password/handlers/login.write.js +1 -1
  18. package/dist/auth-email-password/handlers/self-registration-status.query.js +1 -1
  19. package/dist/auth-email-password/handlers/signup-confirm.write.js +34 -37
  20. package/dist/auth-email-password/handlers/signup-request.write.js +6 -7
  21. package/dist/auth-email-password/handlers/switch-tenant-mfa-gate.write.d.ts +1 -1
  22. package/dist/auth-email-password/handlers/switch-tenant-mfa-gate.write.js +1 -4
  23. package/dist/auth-email-password/index.js +5 -5
  24. package/dist/auth-email-password/lockout-store.js +1 -1
  25. package/dist/auth-email-password/seeding.d.ts +42 -45
  26. package/dist/auth-email-password/seeding.js +51 -53
  27. package/dist/auth-email-password/signup-token-store.js +8 -8
  28. package/dist/auth-email-password/testing.js +3 -3
  29. package/dist/auth-email-password/web/auth-client.d.ts +2 -2
  30. package/dist/auth-email-password/web/auth-client.js +36 -37
  31. package/dist/auth-email-password/web/auth-form-primitives.js +10 -10
  32. package/dist/auth-email-password/web/auth-gate.js +15 -16
  33. package/dist/auth-email-password/web/client-plugin.d.ts +7 -7
  34. package/dist/auth-email-password/web/client-plugin.js +4 -5
  35. package/dist/auth-email-password/web/confirm-account-unlock-screen.js +1 -1
  36. package/dist/auth-email-password/web/constants.js +3 -3
  37. package/dist/auth-email-password/web/default-topbar-actions.d.ts +8 -8
  38. package/dist/auth-email-password/web/default-topbar-actions.js +6 -6
  39. package/dist/auth-email-password/web/forgot-password-screen.d.ts +2 -2
  40. package/dist/auth-email-password/web/forgot-password-screen.js +9 -9
  41. package/dist/auth-email-password/web/index.js +5 -5
  42. package/dist/auth-email-password/web/invite-accept-screen.d.ts +1 -1
  43. package/dist/auth-email-password/web/invite-accept-screen.js +16 -16
  44. package/dist/auth-email-password/web/login-screen.d.ts +1 -1
  45. package/dist/auth-email-password/web/login-screen.js +2 -3
  46. package/dist/auth-email-password/web/reset-password-screen.d.ts +4 -4
  47. package/dist/auth-email-password/web/reset-password-screen.js +12 -13
  48. package/dist/auth-email-password/web/session.d.ts +10 -9
  49. package/dist/auth-email-password/web/session.js +28 -29
  50. package/dist/auth-email-password/web/signup-complete-screen.js +3 -3
  51. package/dist/auth-email-password/web/signup-screen.d.ts +1 -1
  52. package/dist/auth-email-password/web/signup-screen.js +13 -13
  53. package/dist/auth-email-password/web/tenant-switcher.d.ts +4 -4
  54. package/dist/auth-email-password/web/tenant-switcher.js +21 -22
  55. package/dist/auth-email-password/web/use-shell-user.js +12 -13
  56. package/dist/auth-email-password/web/user-menu.d.ts +4 -4
  57. package/dist/auth-email-password/web/user-menu.js +12 -13
  58. package/dist/auth-email-password/web/verify-email-screen.d.ts +2 -2
  59. package/dist/auth-email-password/web/verify-email-screen.js +9 -10
  60. package/dist/auth-foundation/boot-checks.js +4 -4
  61. package/dist/auth-foundation/resolve-anonymous-access.d.ts +1 -1
  62. package/dist/auth-foundation/resolve-anonymous-access.js +3 -3
  63. package/dist/auth-foundation/types.js +2 -2
  64. package/dist/auth-mfa/feature-exports.d.ts +2 -2
  65. package/dist/auth-mfa/feature.js +8 -0
  66. package/dist/auth-mfa/handlers/disable.write.js +3 -3
  67. package/dist/auth-mfa/handlers/enable-confirm-preauth.write.js +4 -4
  68. package/dist/auth-mfa/handlers/enable-confirm.write.js +2 -2
  69. package/dist/auth-mfa/handlers/enable-start-preauth.write.js +1 -1
  70. package/dist/auth-mfa/handlers/reencrypt.job.js +1 -1
  71. package/dist/auth-mfa/handlers/regenerate-recovery.write.js +5 -5
  72. package/dist/auth-mfa/handlers/verify.write.js +1 -1
  73. package/dist/auth-mfa/token-secrets.js +1 -1
  74. package/dist/auth-mfa/web/client-plugin.d.ts +1 -1
  75. package/dist/auth-mfa/web/client-plugin.js +1 -1
  76. package/dist/auth-mfa/web/i18n.js +8 -4
  77. package/dist/auth-mfa/web/index.js +4 -4
  78. package/dist/auth-mfa/web/mfa-client.js +17 -16
  79. package/dist/auth-mfa/web/mfa-code-or-recovery-field.d.ts +11 -0
  80. package/dist/auth-mfa/web/mfa-code-or-recovery-field.js +12 -0
  81. package/dist/auth-mfa/web/mfa-disable-dialog.js +14 -3
  82. package/dist/auth-mfa/web/mfa-regenerate-recovery-dialog.js +14 -3
  83. package/dist/auth-mfa/web/mfa-setup-preauth-screen.js +10 -5
  84. package/dist/auth-mfa/web/mfa-verify-screen.js +32 -11
  85. package/dist/billing-foundation/aggregate-id.d.ts +5 -5
  86. package/dist/billing-foundation/aggregate-id.js +10 -11
  87. package/dist/billing-foundation/checkout-core.d.ts +9 -14
  88. package/dist/billing-foundation/checkout-core.js +24 -39
  89. package/dist/billing-foundation/consumer-protection/confirmation-mail.js +8 -2
  90. package/dist/billing-foundation/consumer-protection/consent-gate.d.ts +1 -0
  91. package/dist/billing-foundation/consumer-protection/consent-gate.js +1 -0
  92. package/dist/billing-foundation/consumer-protection/issue-confirmation.js +9 -3
  93. package/dist/billing-foundation/consumer-protection/mail-notifications.js +3 -1
  94. package/dist/billing-foundation/consumer-protection/termination-receipt-limit.d.ts +4 -2
  95. package/dist/billing-foundation/consumer-protection/termination-receipt-limit.js +6 -5
  96. package/dist/billing-foundation/consumer-protection/termination-record.d.ts +5 -1
  97. package/dist/billing-foundation/consumer-protection/termination-record.js +27 -17
  98. package/dist/billing-foundation/consumer-protection/termination-request.js +12 -7
  99. package/dist/billing-foundation/consumer-protection/termination-revoke.js +12 -7
  100. package/dist/billing-foundation/consumer-protection/termination-texts.d.ts +1 -1
  101. package/dist/billing-foundation/consumer-protection/termination-texts.js +3 -0
  102. package/dist/billing-foundation/consumer-protection/withdrawal-period.d.ts +5 -2
  103. package/dist/billing-foundation/consumer-protection/withdrawal-period.js +8 -9
  104. package/dist/billing-foundation/entities.js +27 -27
  105. package/dist/billing-foundation/events.d.ts +1 -0
  106. package/dist/billing-foundation/events.js +19 -17
  107. package/dist/billing-foundation/feature.js +45 -44
  108. package/dist/billing-foundation/get-subscription-for-tenant.d.ts +3 -3
  109. package/dist/billing-foundation/get-subscription-for-tenant.js +3 -5
  110. package/dist/billing-foundation/handlers/billing-plans.query.js +35 -5
  111. package/dist/billing-foundation/handlers/create-checkout-session.write.js +19 -17
  112. package/dist/billing-foundation/handlers/create-portal-session.write.js +11 -12
  113. package/dist/billing-foundation/handlers/list-subscriptions.query.js +4 -4
  114. package/dist/billing-foundation/handlers/plan-switch-preview.query.d.ts +13 -0
  115. package/dist/billing-foundation/handlers/plan-switch-preview.query.js +48 -0
  116. package/dist/billing-foundation/handlers/process-event.write.d.ts +6 -0
  117. package/dist/billing-foundation/handlers/process-event.write.js +28 -22
  118. package/dist/billing-foundation/handlers/start-plan-checkout.write.js +32 -5
  119. package/dist/billing-foundation/handlers/switch-plan.write.d.ts +2 -2
  120. package/dist/billing-foundation/handlers/switch-plan.write.js +56 -64
  121. package/dist/billing-foundation/handlers/sync-subscription.write.js +12 -1
  122. package/dist/billing-foundation/i18n.js +22 -0
  123. package/dist/billing-foundation/index.d.ts +1 -1
  124. package/dist/billing-foundation/plan-catalog.d.ts +21 -5
  125. package/dist/billing-foundation/plan-catalog.js +25 -16
  126. package/dist/billing-foundation/plan-switch.d.ts +12 -0
  127. package/dist/billing-foundation/plan-switch.js +60 -0
  128. package/dist/billing-foundation/projection.d.ts +15 -11
  129. package/dist/billing-foundation/projection.js +38 -37
  130. package/dist/billing-foundation/subscription-tier-sync.js +3 -3
  131. package/dist/billing-foundation/tenant-destroy-hook.d.ts +2 -2
  132. package/dist/billing-foundation/tenant-destroy-hook.js +7 -7
  133. package/dist/billing-foundation/types.d.ts +97 -42
  134. package/dist/billing-foundation/types.js +29 -20
  135. package/dist/billing-foundation/validate-options.js +17 -0
  136. package/dist/billing-foundation/web/billing-plans-panel.js +82 -36
  137. package/dist/billing-foundation/web/checkout-consent-dialog.d.ts +17 -3
  138. package/dist/billing-foundation/web/checkout-consent-dialog.js +14 -10
  139. package/dist/billing-foundation/web/constants.d.ts +4 -0
  140. package/dist/billing-foundation/web/constants.js +4 -0
  141. package/dist/billing-foundation/web/plan-price.d.ts +15 -0
  142. package/dist/billing-foundation/web/plan-price.js +32 -0
  143. package/dist/billing-foundation/web/plan-switch-confirm-dialog.d.ts +15 -0
  144. package/dist/billing-foundation/web/plan-switch-confirm-dialog.js +66 -0
  145. package/dist/cap-counter/aggregate-id.js +8 -9
  146. package/dist/cap-counter/cap-reservation.js +5 -1
  147. package/dist/cap-counter/constants.js +11 -12
  148. package/dist/cap-counter/enforce-cap.d.ts +59 -62
  149. package/dist/cap-counter/enforce-cap.js +49 -49
  150. package/dist/cap-counter/entity.js +29 -29
  151. package/dist/cap-counter/feature.js +34 -35
  152. package/dist/cap-counter/handlers/increment-rolling.write.d.ts +2 -2
  153. package/dist/cap-counter/handlers/increment-rolling.write.js +31 -31
  154. package/dist/cap-counter/handlers/mark-soft-warned.write.js +4 -5
  155. package/dist/cap-counter/stock-cap-guard.js +8 -2
  156. package/dist/cap-counter/with-cap-enforcement.d.ts +3 -5
  157. package/dist/channel-email/feature.js +1 -1
  158. package/dist/channel-email/smtp-transport.d.ts +18 -17
  159. package/dist/channel-email/smtp-transport.js +24 -21
  160. package/dist/channel-in-app/handlers/unread-count.query.js +3 -4
  161. package/dist/compliance-profiles/feature.js +4 -4
  162. package/dist/compliance-profiles/handlers/for-tenant.query.js +3 -6
  163. package/dist/compliance-profiles/handlers/list-profiles.query.js +5 -5
  164. package/dist/compliance-profiles/handlers/needs-profile.query.js +4 -4
  165. package/dist/compliance-profiles/handlers/set-profile.write.js +13 -14
  166. package/dist/compliance-profiles/handlers/sub-processors.query.js +9 -11
  167. package/dist/compliance-profiles/resolve-for-tenant.js +7 -7
  168. package/dist/compliance-profiles/schema/profile-selection.js +18 -23
  169. package/dist/compliance-profiles/seeding.js +14 -15
  170. package/dist/compliance-profiles-ops/handlers/tenants-missing-profile.query.js +3 -3
  171. package/dist/compliance-profiles-ops/index.js +2 -2
  172. package/dist/config/handlers/cascade.query.d.ts +5 -0
  173. package/dist/config/handlers/cascade.query.js +4 -3
  174. package/dist/config/handlers/readiness.query.d.ts +4 -4
  175. package/dist/config/handlers/readiness.query.js +1 -1
  176. package/dist/config/handlers/reencrypt.job.js +4 -5
  177. package/dist/config/handlers/set.write.js +4 -4
  178. package/dist/config/handlers/values.query.d.ts +5 -0
  179. package/dist/config/handlers/values.query.js +4 -3
  180. package/dist/config/resolver.js +4 -4
  181. package/dist/config/table.js +10 -10
  182. package/dist/config/write-helpers.d.ts +1 -0
  183. package/dist/config/write-helpers.js +19 -10
  184. package/dist/crypto-shredding/handlers/forget-subject.write.d.ts +2 -2
  185. package/dist/crypto-shredding/handlers/forget-subject.write.js +25 -21
  186. package/dist/custom-fields/aggregate-id.d.ts +12 -13
  187. package/dist/custom-fields/aggregate-id.js +16 -17
  188. package/dist/custom-fields/entity.js +49 -47
  189. package/dist/custom-fields/events.js +7 -8
  190. package/dist/custom-fields/feature.d.ts +14 -14
  191. package/dist/custom-fields/feature.js +53 -54
  192. package/dist/custom-fields/handlers/clear-custom-field.write.d.ts +3 -2
  193. package/dist/custom-fields/handlers/clear-custom-field.write.js +9 -8
  194. package/dist/custom-fields/handlers/define-tenant-field.write.d.ts +1 -1
  195. package/dist/custom-fields/handlers/define-tenant-field.write.js +2 -2
  196. package/dist/custom-fields/handlers/delete-system-field.write.js +6 -7
  197. package/dist/custom-fields/handlers/delete-tenant-field.write.d.ts +1 -1
  198. package/dist/custom-fields/handlers/delete-tenant-field.write.js +3 -3
  199. package/dist/custom-fields/handlers/set-custom-field.write.d.ts +3 -3
  200. package/dist/custom-fields/handlers/set-custom-field.write.js +25 -28
  201. package/dist/custom-fields/handlers/update-tenant-field.write.d.ts +1 -1
  202. package/dist/custom-fields/handlers/update-tenant-field.write.js +1 -1
  203. package/dist/custom-fields/lib/field-access.js +2 -2
  204. package/dist/custom-fields/lib/parse-serialized-field.js +1 -1
  205. package/dist/custom-fields/lib/quota.js +1 -1
  206. package/dist/custom-fields/lib/value-schema.js +10 -12
  207. package/dist/custom-fields/run-retention.js +3 -3
  208. package/dist/custom-fields/schemas.js +14 -14
  209. package/dist/custom-fields/web/client-plugin.js +2 -5
  210. package/dist/custom-fields/web/constants.js +2 -2
  211. package/dist/custom-fields/web/custom-fields-form-section.d.ts +4 -4
  212. package/dist/custom-fields/web/custom-fields-form-section.js +22 -22
  213. package/dist/custom-fields/wire-for-entity.js +65 -67
  214. package/dist/custom-fields/wire-user-data-rights.js +3 -3
  215. package/dist/data-retention/_internal/parse-override.js +2 -3
  216. package/dist/data-retention/feature.js +11 -18
  217. package/dist/data-retention/handlers/policy-for.query.js +6 -6
  218. package/dist/data-retention/keep-for.d.ts +7 -7
  219. package/dist/data-retention/keep-for.js +15 -15
  220. package/dist/data-retention/override-schema.d.ts +3 -4
  221. package/dist/data-retention/override-schema.js +13 -15
  222. package/dist/data-retention/presets.d.ts +4 -5
  223. package/dist/data-retention/presets.js +18 -23
  224. package/dist/data-retention/resolve-tenant-preset.js +17 -16
  225. package/dist/data-retention/resolver.d.ts +12 -12
  226. package/dist/data-retention/resolver.js +24 -23
  227. package/dist/data-retention/run-retention-cleanup.d.ts +8 -3
  228. package/dist/data-retention/run-retention-cleanup.js +61 -51
  229. package/dist/data-retention/schema/tenant-retention-override.js +13 -11
  230. package/dist/delivery/channel-context.js +6 -3
  231. package/dist/delivery/db/queries/preferences.js +2 -2
  232. package/dist/delivery/delivery-service.js +22 -7
  233. package/dist/delivery/feature.js +2 -2
  234. package/dist/delivery/handlers/log.query.js +1 -1
  235. package/dist/delivery/handlers/preferences.query.d.ts +1 -1
  236. package/dist/delivery/handlers/preferences.query.js +20 -1
  237. package/dist/delivery/handlers/unsubscribe-page-chrome.query.js +1 -1
  238. package/dist/delivery/tables.js +9 -8
  239. package/dist/delivery/unsubscribe-link.d.ts +3 -1
  240. package/dist/delivery/unsubscribe-page-strings.d.ts +1 -0
  241. package/dist/delivery/unsubscribe-page-strings.js +1 -0
  242. package/dist/delivery/unsubscribe-page.d.ts +2 -0
  243. package/dist/delivery/unsubscribe-page.js +5 -0
  244. package/dist/delivery/unsubscribe.js +29 -11
  245. package/dist/document-ingest-foundation/entity.js +4 -4
  246. package/dist/document-ingest-foundation/events.js +2 -2
  247. package/dist/document-ingest-foundation/feature.js +2 -2
  248. package/dist/document-ingest-foundation/pages.js +1 -1
  249. package/dist/document-ingest-foundation/tenant-destroy-hook.js +4 -4
  250. package/dist/feature-toggles/compose-tier-resolver.js +1 -1
  251. package/dist/feature-toggles/feature.js +1 -1
  252. package/dist/feature-toggles/global-feature-state-table.js +1 -1
  253. package/dist/feature-toggles/handlers/registered.query.js +5 -5
  254. package/dist/feature-toggles/toggle-sync-signal.js +1 -1
  255. package/dist/feature-toggles/web/constants.js +1 -1
  256. package/dist/file-derivatives/feature.d.ts +1 -1
  257. package/dist/file-derivatives/feature.js +1 -1
  258. package/dist/file-derivatives/handlers/public-variant.query.js +1 -1
  259. package/dist/file-derivatives/presets.js +1 -1
  260. package/dist/file-foundation/feature.js +6 -6
  261. package/dist/file-foundation/index.d.ts +1 -1
  262. package/dist/file-foundation/index.js +1 -1
  263. package/dist/file-foundation/read-file-ref-content.d.ts +3 -0
  264. package/dist/file-foundation/read-file-ref-content.js +6 -1
  265. package/dist/file-provider-inmemory/feature.d.ts +1 -1
  266. package/dist/file-provider-inmemory/feature.js +16 -16
  267. package/dist/file-provider-s3/env-named-feature.d.ts +0 -2
  268. package/dist/file-provider-s3/feature.js +16 -16
  269. package/dist/file-provider-s3/lib/s3-provider.js +6 -6
  270. package/dist/files/feature.js +6 -6
  271. package/dist/files-tenant-data/handlers/sweep-orphaned-derivatives.job.js +4 -4
  272. package/dist/files-tenant-data/hooks.js +24 -26
  273. package/dist/files-tenant-data/index.js +1 -1
  274. package/dist/folders/feature.d.ts +1 -1
  275. package/dist/folders/web/folder-manager.js +2 -2
  276. package/dist/folders/web/tree.js +5 -5
  277. package/dist/form-draft/db/queries/cleanup.js +3 -3
  278. package/dist/form-draft/db/queries/owned-file-refs.js +1 -1
  279. package/dist/form-draft/decrypt-draft.js +5 -1
  280. package/dist/form-draft/entity.js +3 -3
  281. package/dist/form-draft/feature.js +4 -5
  282. package/dist/form-draft/handlers/discard.write.js +1 -1
  283. package/dist/form-draft/release-file-refs.js +6 -6
  284. package/dist/form-draft/schemas.js +3 -3
  285. package/dist/form-draft/web/constants.js +0 -2
  286. package/dist/host-routing/dispatch.js +11 -20
  287. package/dist/inbound-mail-foundation/aggregate-id.d.ts +13 -13
  288. package/dist/inbound-mail-foundation/aggregate-id.js +19 -19
  289. package/dist/inbound-mail-foundation/connect-routes.js +4 -3
  290. package/dist/inbound-mail-foundation/constants.d.ts +22 -24
  291. package/dist/inbound-mail-foundation/constants.js +30 -33
  292. package/dist/inbound-mail-foundation/entities.d.ts +12 -12
  293. package/dist/inbound-mail-foundation/entities.js +47 -48
  294. package/dist/inbound-mail-foundation/events.d.ts +4 -3
  295. package/dist/inbound-mail-foundation/events.js +50 -47
  296. package/dist/inbound-mail-foundation/feature.js +39 -39
  297. package/dist/inbound-mail-foundation/handlers/account-state.js +8 -8
  298. package/dist/inbound-mail-foundation/handlers/connect-account.write.js +25 -26
  299. package/dist/inbound-mail-foundation/handlers/disconnect-account.write.js +7 -7
  300. package/dist/inbound-mail-foundation/handlers/ingest-message.write.js +50 -51
  301. package/dist/inbound-mail-foundation/handlers/list-accounts.query.js +8 -8
  302. package/dist/inbound-mail-foundation/handlers/list-messages.query.js +10 -10
  303. package/dist/inbound-mail-foundation/handlers/scope-visibility.js +3 -4
  304. package/dist/inbound-mail-foundation/handlers/update-account.write.d.ts +2 -2
  305. package/dist/inbound-mail-foundation/handlers/update-account.write.js +22 -14
  306. package/dist/inbound-mail-foundation/i18n.js +0 -1
  307. package/dist/inbound-mail-foundation/oauth-state.d.ts +5 -5
  308. package/dist/inbound-mail-foundation/oauth-state.js +11 -10
  309. package/dist/inbound-mail-foundation/projection.d.ts +4 -4
  310. package/dist/inbound-mail-foundation/projection.js +31 -29
  311. package/dist/inbound-mail-foundation/provider-factory.d.ts +4 -4
  312. package/dist/inbound-mail-foundation/provider-factory.js +9 -9
  313. package/dist/inbound-mail-foundation/retention-sweep.d.ts +5 -3
  314. package/dist/inbound-mail-foundation/retention-sweep.js +38 -36
  315. package/dist/inbound-mail-foundation/screens.js +0 -3
  316. package/dist/inbound-mail-foundation/tenant-destroy-hook.js +14 -16
  317. package/dist/inbound-mail-foundation/types.d.ts +49 -49
  318. package/dist/inbound-mail-foundation/types.js +21 -21
  319. package/dist/inbound-mail-foundation/watch-supervisor.d.ts +10 -10
  320. package/dist/inbound-mail-foundation/watch-supervisor.js +85 -67
  321. package/dist/inbound-provider-imap/credential-document.js +13 -13
  322. package/dist/inbound-provider-imap/feature.d.ts +2 -2
  323. package/dist/inbound-provider-imap/feature.js +42 -40
  324. package/dist/inbound-provider-imap/imap-client.d.ts +12 -11
  325. package/dist/inbound-provider-imap/imap-client.js +20 -19
  326. package/dist/inbound-provider-inmemory/feature.d.ts +9 -9
  327. package/dist/inbound-provider-inmemory/feature.js +19 -19
  328. package/dist/jobs/db/queries/retention.js +2 -2
  329. package/dist/jobs/db/queries/stale-run-sweep.js +3 -3
  330. package/dist/jobs/events.js +1 -1
  331. package/dist/jobs/feature.d.ts +1 -0
  332. package/dist/jobs/feature.js +13 -10
  333. package/dist/jobs/handlers/detail.query.js +4 -4
  334. package/dist/jobs/handlers/list.query.js +1 -1
  335. package/dist/jobs/handlers/projection-rebuild.job.js +1 -1
  336. package/dist/jobs/handlers/reindex-entity.job.js +1 -1
  337. package/dist/jobs/handlers/retry.write.js +1 -1
  338. package/dist/jobs/handlers/tenant-failures.query.d.ts +1 -1
  339. package/dist/jobs/handlers/tenant-failures.query.js +45 -41
  340. package/dist/jobs/handlers/tenant-runs.query.d.ts +1 -1
  341. package/dist/jobs/handlers/tenant-runs.query.js +54 -50
  342. package/dist/jobs/job-instant.js +1 -1
  343. package/dist/jobs/job-run-logger.js +15 -15
  344. package/dist/jobs/job-run-table.js +3 -3
  345. package/dist/jobs/screens.js +1 -1
  346. package/dist/jobs/tenant-job-failure-table.js +1 -1
  347. package/dist/jobs/tenant-job-run-table.js +1 -1
  348. package/dist/ledger/entity.js +5 -5
  349. package/dist/ledger/feature.js +3 -3
  350. package/dist/ledger/handlers/confirm-schedule-period.write.js +3 -3
  351. package/dist/ledger/recurring.js +12 -12
  352. package/dist/ledger/schemas.js +1 -1
  353. package/dist/ledger/web/constants.js +2 -2
  354. package/dist/ledger/web/index.js +7 -7
  355. package/dist/legal-pages/feature.d.ts +4 -4
  356. package/dist/legal-pages/feature.js +25 -26
  357. package/dist/legal-pages/markdown.js +3 -3
  358. package/dist/legal-pages/web/client-plugin.js +23 -24
  359. package/dist/legal-pages/web/index.js +0 -1
  360. package/dist/mail-foundation/feature.d.ts +5 -5
  361. package/dist/mail-foundation/feature.js +27 -25
  362. package/dist/mail-transport-inmemory/feature.d.ts +4 -4
  363. package/dist/mail-transport-inmemory/feature.js +27 -28
  364. package/dist/mail-transport-smtp/feature.js +20 -20
  365. package/dist/managed-pages/branding.js +6 -6
  366. package/dist/managed-pages/feature.d.ts +26 -26
  367. package/dist/managed-pages/feature.js +34 -34
  368. package/dist/managed-pages/handlers/by-slug.query.js +5 -6
  369. package/dist/managed-pages/handlers/by-tenant-published.query.js +2 -2
  370. package/dist/managed-pages/handlers/set.write.js +5 -7
  371. package/dist/managed-pages/screens/page-screens.js +11 -11
  372. package/dist/managed-pages/seeding.js +8 -8
  373. package/dist/managed-pages/table.js +13 -12
  374. package/dist/managed-pages/web/client-plugin.d.ts +1 -1
  375. package/dist/managed-pages/web/client-plugin.js +5 -5
  376. package/dist/managed-pages/web/i18n.js +1 -1
  377. package/dist/notes-history/entity.js +4 -4
  378. package/dist/notes-history/feature.d.ts +2 -2
  379. package/dist/notes-history-user-data/hooks.js +1 -1
  380. package/dist/page-render/layout.js +9 -9
  381. package/dist/page-render/markdown.js +11 -11
  382. package/dist/page-render/security-headers.js +7 -6
  383. package/dist/personal-access-tokens/feature.d.ts +1 -1
  384. package/dist/personal-access-tokens/feature.js +9 -5
  385. package/dist/personal-access-tokens/roles.js +2 -2
  386. package/dist/readiness/handlers/status.query.js +3 -3
  387. package/dist/renderer-foundation/api.js +12 -13
  388. package/dist/renderer-foundation/constants.js +2 -2
  389. package/dist/renderer-foundation/feature.js +7 -7
  390. package/dist/renderer-pdf/render-table-pdf.d.ts +1 -1
  391. package/dist/renderer-pdf/render-table-pdf.js +22 -10
  392. package/dist/secrets/feature.js +9 -9
  393. package/dist/secrets/web/constants.js +2 -2
  394. package/dist/seo/feature.js +3 -3
  395. package/dist/sessions/constants.d.ts +1 -1
  396. package/dist/sessions/constants.js +1 -1
  397. package/dist/sessions/feature.d.ts +2 -2
  398. package/dist/sessions/feature.js +12 -8
  399. package/dist/sessions/handlers/revoke-all-for-user.write.js +2 -2
  400. package/dist/sessions/handlers/revoke-all-others.write.js +2 -2
  401. package/dist/sessions/handlers/revoke.write.js +1 -1
  402. package/dist/sessions/schema/user-session.js +4 -4
  403. package/dist/sessions/screens.js +2 -2
  404. package/dist/sessions/session-callbacks.js +2 -2
  405. package/dist/sessions/session-revoked-event.js +2 -2
  406. package/dist/shared/assert-no-where-rule-in-ownership-write.js +1 -1
  407. package/dist/shared/decrypt-stored-pii.d.ts +1 -0
  408. package/dist/shared/decrypt-stored-pii.js +7 -2
  409. package/dist/shared/encrypt-for-direct-write.js +1 -1
  410. package/dist/shared/index.d.ts +1 -1
  411. package/dist/shared/index.js +1 -1
  412. package/dist/shared/mail-template-body.d.ts +6 -2
  413. package/dist/shared/mail-template-body.js +7 -4
  414. package/dist/shared/map-with-concurrency.js +5 -4
  415. package/dist/shared/parent-visibility.js +2 -2
  416. package/dist/shared/password-hashing.js +1 -1
  417. package/dist/shared/signup-handover.js +1 -2
  418. package/dist/shared/single-use-token-store.js +1 -1
  419. package/dist/step-dispatcher/feature.js +1 -0
  420. package/dist/subscription-dodo/client.d.ts +17 -0
  421. package/dist/subscription-dodo/client.js +31 -0
  422. package/dist/subscription-dodo/constants.d.ts +12 -0
  423. package/dist/subscription-dodo/constants.js +19 -0
  424. package/dist/subscription-dodo/feature.d.ts +37 -0
  425. package/dist/subscription-dodo/feature.js +144 -0
  426. package/dist/subscription-dodo/index.d.ts +2 -0
  427. package/dist/subscription-dodo/index.js +4 -0
  428. package/dist/subscription-dodo/plugin-methods.d.ts +23 -0
  429. package/dist/subscription-dodo/plugin-methods.js +260 -0
  430. package/dist/subscription-dodo/runtime.d.ts +27 -0
  431. package/dist/subscription-dodo/runtime.js +89 -0
  432. package/dist/subscription-dodo/verify-webhook.d.ts +37 -0
  433. package/dist/subscription-dodo/verify-webhook.js +189 -0
  434. package/dist/subscription-mollie/constants.js +15 -17
  435. package/dist/subscription-mollie/feature.d.ts +17 -18
  436. package/dist/subscription-mollie/feature.js +24 -24
  437. package/dist/subscription-mollie/index.js +3 -3
  438. package/dist/subscription-mollie/plugin-methods.d.ts +2 -2
  439. package/dist/subscription-mollie/plugin-methods.js +15 -14
  440. package/dist/subscription-mollie/verify-webhook.d.ts +10 -10
  441. package/dist/subscription-mollie/verify-webhook.js +27 -27
  442. package/dist/subscription-stripe/constants.js +4 -6
  443. package/dist/subscription-stripe/feature.d.ts +14 -14
  444. package/dist/subscription-stripe/feature.js +36 -34
  445. package/dist/subscription-stripe/index.js +6 -6
  446. package/dist/subscription-stripe/plugin-methods.js +18 -18
  447. package/dist/subscription-stripe/runtime.d.ts +1 -1
  448. package/dist/subscription-stripe/runtime.js +8 -7
  449. package/dist/subscription-stripe/verify-webhook.d.ts +11 -11
  450. package/dist/subscription-stripe/verify-webhook.js +59 -61
  451. package/dist/tags/feature.d.ts +3 -3
  452. package/dist/tags/screens.js +1 -1
  453. package/dist/template-resolver/api.d.ts +3 -2
  454. package/dist/template-resolver/api.js +34 -12
  455. package/dist/template-resolver/compile-document-body.js +1 -0
  456. package/dist/template-resolver/feature.js +2 -2
  457. package/dist/template-resolver/handlers/bundle-import.js +3 -3
  458. package/dist/template-resolver/handlers/copy-locale.write.d.ts +5 -1
  459. package/dist/template-resolver/handlers/copy-locale.write.js +22 -7
  460. package/dist/template-resolver/handlers/diff-to-system.query.js +3 -2
  461. package/dist/template-resolver/handlers/document-base.js +3 -2
  462. package/dist/template-resolver/handlers/find-by-id.query.js +8 -7
  463. package/dist/template-resolver/handlers/list.query.js +6 -7
  464. package/dist/template-resolver/handlers/overview.query.d.ts +3 -0
  465. package/dist/template-resolver/handlers/overview.query.js +6 -3
  466. package/dist/template-resolver/handlers/shared.js +2 -2
  467. package/dist/template-resolver/handlers/template-write.js +3 -3
  468. package/dist/template-resolver/handlers/tenant-override-counts.query.js +1 -3
  469. package/dist/template-resolver/handlers/tenant-override-selection.d.ts +2 -1
  470. package/dist/template-resolver/handlers/tenant-overrides.query.js +28 -5
  471. package/dist/template-resolver/handlers/upsert-system.write.js +3 -4
  472. package/dist/template-resolver/handlers/upsert-tenant.write.js +4 -5
  473. package/dist/template-resolver/handlers/usage.query.d.ts +2 -1
  474. package/dist/template-resolver/handlers/usage.query.js +26 -16
  475. package/dist/template-resolver/handlers/validate.query.js +7 -2
  476. package/dist/template-resolver/seeding.js +30 -14
  477. package/dist/template-resolver/store-template-resource.d.ts +1 -0
  478. package/dist/template-resolver/store-template-resource.js +19 -4
  479. package/dist/template-resolver/table.js +10 -11
  480. package/dist/template-resolver/template-delivery-policy.d.ts +0 -5
  481. package/dist/template-resolver/template-delivery-policy.js +0 -6
  482. package/dist/template-resolver/template-meta.js +7 -4
  483. package/dist/template-resolver/template-preview.js +4 -3
  484. package/dist/template-resolver/web/client-plugin.js +2 -2
  485. package/dist/template-resolver/web/constants.d.ts +2 -2
  486. package/dist/template-resolver/web/constants.js +3 -3
  487. package/dist/template-resolver/web/index.d.ts +1 -0
  488. package/dist/template-resolver/web/index.js +1 -0
  489. package/dist/template-resolver/web/mail-layout-constants.d.ts +5 -0
  490. package/dist/template-resolver/web/mail-layout-constants.js +7 -0
  491. package/dist/tenant/feature.js +7 -7
  492. package/dist/tenant/handlers/member-directory.query.d.ts +1 -0
  493. package/dist/tenant/handlers/member-directory.query.js +11 -4
  494. package/dist/tenant/handlers/members.query.js +1 -1
  495. package/dist/tenant/handlers/team-list.query.js +1 -1
  496. package/dist/tenant/handlers/tenant-directory.query.d.ts +1 -0
  497. package/dist/tenant/handlers/tenant-directory.query.js +9 -5
  498. package/dist/tenant/invitation-table.js +37 -40
  499. package/dist/tenant/membership-table.js +3 -3
  500. package/dist/tenant/schema/tenant.js +7 -9
  501. package/dist/tenant/screens.js +1 -1
  502. package/dist/tenant/seeding.d.ts +6 -5
  503. package/dist/tenant/seeding.js +12 -12
  504. package/dist/tenant/testing.js +5 -6
  505. package/dist/tenant/web/constants.js +2 -2
  506. package/dist/tenant-handover/events.js +5 -7
  507. package/dist/tenant-handover/feature.js +1 -1
  508. package/dist/tenant-handover/grant.js +1 -1
  509. package/dist/tenant-handover/handlers/claim.write.js +1 -1
  510. package/dist/tenant-handover/move-entity-graph.js +8 -9
  511. package/dist/tenant-handover/signup-handover-provider.js +1 -2
  512. package/dist/tenant-handover/transfer-graph.js +4 -5
  513. package/dist/tenant-lifecycle/boot-checks.js +1 -1
  514. package/dist/tenant-lifecycle/constants.d.ts +1 -0
  515. package/dist/tenant-lifecycle/constants.js +4 -0
  516. package/dist/tenant-lifecycle/feature.js +1 -1
  517. package/dist/tenant-lifecycle/run-tenant-destroy.js +2 -2
  518. package/dist/tenant-settings/constants.js +1 -1
  519. package/dist/tenant-settings/tenant-defaults.d.ts +1 -1
  520. package/dist/tenant-settings/tenant-defaults.js +9 -2
  521. package/dist/tier-engine/aggregate-id.d.ts +11 -11
  522. package/dist/tier-engine/aggregate-id.js +15 -16
  523. package/dist/tier-engine/compose-app.d.ts +14 -14
  524. package/dist/tier-engine/compose-app.js +1 -1
  525. package/dist/tier-engine/entity.js +18 -19
  526. package/dist/tier-engine/feature.d.ts +19 -17
  527. package/dist/tier-engine/feature.js +68 -69
  528. package/dist/tier-engine/handlers/active-tier.query.js +6 -7
  529. package/dist/tier-engine/handlers/get-tenant-tier.query.js +4 -4
  530. package/dist/tier-engine/handlers/set-tenant-tier.write.d.ts +7 -8
  531. package/dist/tier-engine/handlers/set-tenant-tier.write.js +7 -4
  532. package/dist/tier-engine/tier-resolver.js +2 -2
  533. package/dist/tier-engine/trial.js +8 -8
  534. package/dist/tier-engine/web/constants.js +2 -2
  535. package/dist/tier-engine/web/index.js +4 -4
  536. package/dist/user/db/queries/stream-tenant-backfill.js +8 -8
  537. package/dist/user/handlers/create.write.js +4 -4
  538. package/dist/user/schema/user.d.ts +11 -11
  539. package/dist/user/schema/user.js +45 -44
  540. package/dist/user/seeding.d.ts +15 -15
  541. package/dist/user/seeding.js +28 -28
  542. package/dist/user/testing.js +3 -3
  543. package/dist/user-data-rights/audit-download.d.ts +8 -8
  544. package/dist/user-data-rights/audit-download.js +19 -20
  545. package/dist/user-data-rights/boot-checks.js +1 -1
  546. package/dist/user-data-rights/deletion-token.js +1 -1
  547. package/dist/user-data-rights/feature.d.ts +49 -50
  548. package/dist/user-data-rights/feature.js +54 -53
  549. package/dist/user-data-rights/handlers/cancel-deletion.write.js +9 -9
  550. package/dist/user-data-rights/handlers/confirm-deletion-by-token.write.js +8 -8
  551. package/dist/user-data-rights/handlers/download-by-job.query.js +29 -30
  552. package/dist/user-data-rights/handlers/download-by-token.query.js +40 -42
  553. package/dist/user-data-rights/handlers/export-status.query.js +8 -9
  554. package/dist/user-data-rights/handlers/lift-restriction.write.js +7 -8
  555. package/dist/user-data-rights/handlers/list-download-attempts.query.js +2 -2
  556. package/dist/user-data-rights/handlers/my-audit-log.query.js +4 -4
  557. package/dist/user-data-rights/handlers/request-deletion-by-email.write.d.ts +5 -5
  558. package/dist/user-data-rights/handlers/request-deletion-by-email.write.js +16 -15
  559. package/dist/user-data-rights/handlers/request-deletion.write.d.ts +2 -2
  560. package/dist/user-data-rights/handlers/request-deletion.write.js +7 -9
  561. package/dist/user-data-rights/handlers/request-export.write.js +42 -41
  562. package/dist/user-data-rights/handlers/restrict-account.write.js +22 -19
  563. package/dist/user-data-rights/handlers/run-forget-cleanup.write.js +8 -8
  564. package/dist/user-data-rights/index.js +8 -10
  565. package/dist/user-data-rights/lib/default-mailers.js +3 -3
  566. package/dist/user-data-rights/lib/update-user-lifecycle.js +14 -17
  567. package/dist/user-data-rights/run-export-jobs.d.ts +38 -42
  568. package/dist/user-data-rights/run-export-jobs.js +130 -237
  569. package/dist/user-data-rights/run-forget-cleanup.d.ts +16 -20
  570. package/dist/user-data-rights/run-forget-cleanup.js +65 -83
  571. package/dist/user-data-rights/run-user-export.d.ts +34 -33
  572. package/dist/user-data-rights/run-user-export.js +55 -57
  573. package/dist/user-data-rights/schema/download-attempt.js +11 -11
  574. package/dist/user-data-rights/schema/download-token.js +37 -41
  575. package/dist/user-data-rights/schema/export-job.d.ts +4 -4
  576. package/dist/user-data-rights/schema/export-job.js +65 -67
  577. package/dist/user-data-rights/token-helpers.d.ts +8 -9
  578. package/dist/user-data-rights/token-helpers.js +21 -25
  579. package/dist/user-data-rights/web/client-plugin.d.ts +5 -5
  580. package/dist/user-data-rights/web/client-plugin.js +1 -1
  581. package/dist/user-data-rights/web/confirm-deletion-screen.js +6 -5
  582. package/dist/user-data-rights/web/constants.js +1 -1
  583. package/dist/user-data-rights/web/index.js +5 -5
  584. package/dist/user-data-rights/web/privacy-center-screen.js +14 -13
  585. package/dist/user-data-rights/web/public-deletion-gate.d.ts +4 -4
  586. package/dist/user-data-rights/web/request-deletion-screen.js +6 -6
  587. package/dist/user-data-rights/zip-path.d.ts +15 -15
  588. package/dist/user-data-rights/zip-path.js +41 -40
  589. package/dist/user-data-rights-defaults/feature.js +15 -15
  590. package/dist/user-data-rights-defaults/hooks/delivery-attempt.userdata-hook.js +3 -5
  591. package/dist/user-data-rights-defaults/hooks/file-ref.userdata-hook.js +45 -51
  592. package/dist/user-data-rights-defaults/hooks/job-run.userdata-hook.js +2 -2
  593. package/dist/user-data-rights-defaults/hooks/notification-preference.userdata-hook.js +1 -1
  594. package/dist/user-data-rights-defaults/hooks/user-session.userdata-hook.js +3 -3
  595. package/dist/user-data-rights-defaults/hooks/user.userdata-hook.js +32 -31
  596. package/dist/user-profile/handlers/change-email.write.js +13 -13
  597. package/dist/user-profile/index.js +3 -3
  598. package/dist/user-profile/web/client-plugin.js +1 -1
  599. package/dist/user-profile/web/constants.js +1 -1
  600. package/dist/user-profile/web/profile-screen.js +1 -1
  601. package/dist/waitlist/handlers/export-purge.write.js +2 -1
  602. package/dist/waitlist/handlers/submit.write.js +5 -5
  603. package/dist/waitlist/import.d.ts +1 -0
  604. package/dist/waitlist/import.js +4 -0
  605. package/dist/waitlist/legacy-migrate.js +17 -6
  606. package/dist/waitlist/link-on-signup.js +18 -6
  607. package/dist/waitlist/options.d.ts +3 -0
  608. package/dist/workflow-runner/aggregate-id.d.ts +1 -1
  609. package/dist/workflow-runner/aggregate-id.js +1 -1
  610. package/dist/workflow-runner/event-subscriber.js +4 -5
  611. package/dist/workflow-runner/event-trigger.js +4 -17
  612. package/dist/workflow-runner/feature.js +2 -2
  613. package/dist/workflow-runner/handlers/resume-run.write.js +4 -4
  614. package/dist/workflow-runner/pending-projection.js +6 -7
  615. package/dist/workflow-runner/runner.js +6 -6
  616. package/dist/workflow-runner/tables.js +6 -6
  617. package/dist/workflow-runner/workflow-registry.js +1 -1
  618. package/package.json +20 -10
  619. package/src/agent-tools/changes.json +6 -0
  620. package/src/audit/changes.json +12 -0
  621. package/src/auth-mfa/changes.json +6 -0
  622. package/src/billing-foundation/changes.json +55 -0
  623. package/src/cap-counter/changes.json +18 -0
  624. package/src/channel-email/changes.json +6 -0
  625. package/src/config/changes.json +12 -0
  626. package/src/crypto-shredding/changes.json +6 -0
  627. package/src/delivery/changes.json +30 -0
  628. package/src/form-draft/changes.json +6 -0
  629. package/src/host-routing/changes.json +6 -0
  630. package/src/inbound-mail-foundation/changes.json +18 -0
  631. package/src/jobs/changes.json +13 -0
  632. package/src/mail-foundation/changes.json +6 -0
  633. package/src/renderer-pdf/changes.json +6 -0
  634. package/src/step-dispatcher/changes.json +6 -0
  635. package/src/subscription-dodo/changes.json +14 -0
  636. package/src/subscription-stripe/changes.json +12 -0
  637. package/src/template-resolver/changes.json +48 -0
  638. package/src/tenant/changes.json +6 -0
  639. package/src/tenant-lifecycle/changes.json +6 -0
  640. package/src/tier-engine/changes.json +6 -0
  641. package/src/waitlist/changes.json +18 -0
  642. package/dist/billing-foundation/consumer-protection/stored-user-email.d.ts +0 -1
  643. package/dist/billing-foundation/consumer-protection/stored-user-email.js +0 -8
@@ -7,21 +7,10 @@ import { BillingContactPlan } from "./billing-contact-plan.js";
7
7
  import { CancelContractDialog } from "./cancel-contract-dialog.js";
8
8
  import { CheckoutConsentDialog } from "./checkout-consent-dialog.js";
9
9
  import { BillingPlanActions, SubscriptionFoundationHandlers, SubscriptionFoundationQueries, SubscriptionStatuses, } from "./constants.js";
10
+ import { resolveConsentLocale } from "./consumer-protection/consent-locale.js";
10
11
  import { localizedParams } from "./localized-params.js";
11
- function intervalKey(price) {
12
- if (price === null || price.interval === null)
13
- return undefined;
14
- return price.intervalCount !== null && price.intervalCount > 1
15
- ? `billing-foundation.plans.everyInterval.${price.interval}`
16
- : `billing-foundation.plans.perInterval.${price.interval}`;
17
- }
18
- function renewalKey(price) {
19
- if (price === null || price.interval === null)
20
- return undefined;
21
- return price.intervalCount !== null && price.intervalCount > 1
22
- ? `billing-foundation.consent.renewsEvery.${price.interval}`
23
- : `billing-foundation.consent.renews.${price.interval}`;
24
- }
12
+ import { cheapestPrice, intervalKey, pricesDiffer, renewalKey, tierPrices } from "./plan-price.js";
13
+ import { PlanSwitchConfirmDialog } from "./plan-switch-confirm-dialog.js";
25
14
  /** Which action the plan "would" offer if it weren't blocked — mirrors
26
15
  * plan-catalog.ts's own decision tree, since `unavailable` collapses both
27
16
  * "would checkout" and "would switch" into one action and the disabled CTA
@@ -46,10 +35,10 @@ function manageOrReactivateAction(canManage, subscription, t, onManage, redirect
46
35
  disabled: redirecting,
47
36
  };
48
37
  }
49
- /** `!canPurchase` hides every CTA outright (spec: "keine CTAs, Hinweis
50
- * purchaseNotAllowed") — distinct from the `unavailable` action's disabled
51
- * CTA, which still shows a button (e.g. for a plan whose price failed to
52
- * load) so this check runs before the general unavailable-fallback. */
38
+ /** `!canPurchase` hides every CTA outright (see `purchaseNotAllowed`) — distinct
39
+ * from the `unavailable` action's disabled CTA, which still shows a button
40
+ * (for example, when a plan's price failed to load), so this check runs
41
+ * before the general unavailable fallback. */
53
42
  function planCta(plan, result, t, onAction, redirecting) {
54
43
  if (plan.action === BillingPlanActions.current)
55
44
  return undefined;
@@ -86,8 +75,20 @@ export function BillingPlansPanel(_props) {
86
75
  const [redirecting, setRedirecting] = useState(false);
87
76
  const [consentTier, setConsentTier] = useState(null);
88
77
  const [cancelOpen, setCancelOpen] = useState(false);
78
+ const [chosenProvider, setChosenProvider] = useState(null);
79
+ const [switchTier, setSwitchTier] = useState(null);
80
+ const [switchRequested, setSwitchRequested] = useState(false);
81
+ // Only present when the tenant can choose (several offered providers) or
82
+ // the single offered provider has a note; absent means the server decides.
83
+ const providers = query.data?.providers;
84
+ const activeProvider = providers?.find((p) => p.name === chosenProvider) ?? providers?.[0];
85
+ const providerArg = activeProvider ? { providerName: activeProvider.name } : {};
89
86
  async function orderWithConsent(tier, consent) {
90
- const result = await checkoutMutation.mutate({ tier, consent });
87
+ const result = await checkoutMutation.mutate({
88
+ tier,
89
+ ...(consent !== undefined && { consent }),
90
+ ...providerArg,
91
+ });
91
92
  if (result.isSuccess) {
92
93
  setRedirecting(true);
93
94
  window.location.assign(result.data.url);
@@ -95,13 +96,20 @@ export function BillingPlansPanel(_props) {
95
96
  return result;
96
97
  }
97
98
  async function handleAction(tier, kind) {
98
- if (kind === "checkout" && query.data?.consumerProtection !== undefined) {
99
+ if (kind === "checkout" &&
100
+ (query.data?.consumerProtection !== undefined || (providers?.length ?? 0) > 1)) {
101
+ setChosenProvider(null);
99
102
  setConsentTier(tier);
100
103
  return;
101
104
  }
105
+ if (kind === "switch" && query.data?.inAppPlanSwitch) {
106
+ setSwitchTier(tier);
107
+ return;
108
+ }
102
109
  setRedirecting(true);
103
- const mutation = kind === "checkout" ? checkoutMutation : switchMutation;
104
- const result = await mutation.mutate({ tier });
110
+ const result = kind === "checkout"
111
+ ? await checkoutMutation.mutate({ tier, ...providerArg })
112
+ : await switchMutation.mutate({ tier });
105
113
  if (result.isSuccess) {
106
114
  window.location.assign(result.data.url);
107
115
  }
@@ -128,13 +136,19 @@ export function BillingPlansPanel(_props) {
128
136
  if (!query.data)
129
137
  return null;
130
138
  const result = query.data;
139
+ // Cards show the first provider's price; the dialog then follows the chosen one.
140
+ const shownPlans = providers?.[0]?.plans ?? result.plans;
141
+ const dialogPlans = activeProvider?.plans ?? result.plans;
131
142
  // The consent dialog shows the checkout error itself.
132
143
  const mutationError = (consentTier === null ? checkoutMutation.error : null) ??
133
144
  switchMutation.error ??
134
145
  portalMutation.error;
135
146
  const canManage = result.subscription !== null && !result.subscription.terminal && result.canPurchase;
136
147
  function toCardProps(plan) {
137
- const key = intervalKey(plan.price);
148
+ const comparable = providers !== undefined && providers.length > 1 ? tierPrices(providers, plan.tier) : [];
149
+ const fromPrice = pricesDiffer(comparable) ? cheapestPrice(comparable) : undefined;
150
+ const shownPrice = fromPrice ?? plan.price;
151
+ const key = intervalKey(shownPrice);
138
152
  return {
139
153
  title: t(plan.labelKey),
140
154
  description: plan.action === BillingPlanActions.paymentPending
@@ -147,13 +161,14 @@ export function BillingPlansPanel(_props) {
147
161
  // provider lookup failure.
148
162
  price: !result.enabled && plan.isCurrent
149
163
  ? undefined
150
- : plan.price === null
164
+ : shownPrice === null
151
165
  ? null
152
166
  : {
153
- amount: formatMoney(plan.price.unitAmount, plan.price.currency, locale),
167
+ amount: formatMoney(shownPrice.unitAmount, shownPrice.currency, locale),
168
+ ...(fromPrice !== undefined && { prefix: t("billing-foundation.plans.priceFrom") }),
154
169
  ...(key !== undefined && {
155
170
  period: t(key, {
156
- count: new Intl.NumberFormat(locale).format(plan.price.intervalCount ?? 1),
171
+ count: new Intl.NumberFormat(locale).format(shownPrice.intervalCount ?? 1),
157
172
  }),
158
173
  }),
159
174
  },
@@ -168,26 +183,57 @@ export function BillingPlansPanel(_props) {
168
183
  // (a legacy/free/pilot tier outside `catalog.plans`) — when none of the
169
184
  // rendered plans is the current one, a synthetic current-tier card (no
170
185
  // price, no cta) keeps it visible regardless of enabled/disabled.
171
- const consentPlan = consentTier !== null ? result.plans.find((plan) => plan.tier === consentTier) : undefined;
186
+ const consentPlan = consentTier !== null ? dialogPlans.find((plan) => plan.tier === consentTier) : undefined;
187
+ const switchPlan = switchTier !== null ? shownPlans.find((plan) => plan.tier === switchTier) : undefined;
172
188
  const consumerProtection = result.consumerProtection;
173
- const currentTierInPlans = result.plans.some((plan) => plan.isCurrent);
174
- const visiblePlans = result.enabled
175
- ? result.plans
176
- : result.plans.filter((plan) => plan.isCurrent);
177
- return (_jsxs("div", { className: "flex flex-col gap-4", "data-testid": "billing-plans-panel", children: [!result.enabled && (_jsx(Banner, { variant: "info", testId: "billing-plans-panel-disabled", children: t("billing-foundation.plans.billingDisabled") })), !result.canPurchase && (_jsx(Banner, { variant: "warning", testId: "billing-plans-panel-readonly", children: t("billing-foundation.plans.purchaseNotAllowed") })), mutationError !== null && (_jsx(Banner, { variant: "error", testId: "billing-plans-panel-mutation-error", children: t(mutationError.i18nKey, mutationError.i18nParams) })), result.subscription?.status === SubscriptionStatuses.pastDue && (_jsx(Banner, { variant: "warning", testId: "billing-plans-panel-past-due", children: t("billing-foundation.plans.pastDue") })), result.subscription !== null &&
189
+ const noteLocale = resolveConsentLocale(locale);
190
+ const noteOf = (provider) => provider.note?.[noteLocale] ?? provider.note?.["en"];
191
+ const singleProviderNote = providers !== undefined && providers.length === 1 && providers[0] !== undefined
192
+ ? noteOf(providers[0])
193
+ : undefined;
194
+ function providerChoiceFor(tier) {
195
+ if (providers === undefined || providers.length < 2 || activeProvider === undefined) {
196
+ return undefined;
197
+ }
198
+ const prices = tierPrices(providers, tier);
199
+ const showPrices = pricesDiffer(prices);
200
+ return {
201
+ value: activeProvider.name,
202
+ onChange: setChosenProvider,
203
+ options: providers.map((provider) => {
204
+ const price = provider.plans.find((plan) => plan.tier === tier)?.price;
205
+ const note = noteOf(provider);
206
+ return {
207
+ value: provider.name,
208
+ title: provider.displayName,
209
+ ...(note !== undefined && { description: note }),
210
+ ...(showPrices &&
211
+ price && { aside: formatMoney(price.unitAmount, price.currency, locale) }),
212
+ };
213
+ }),
214
+ };
215
+ }
216
+ const currentTierInPlans = shownPlans.some((plan) => plan.isCurrent);
217
+ const visiblePlans = result.enabled ? shownPlans : shownPlans.filter((plan) => plan.isCurrent);
218
+ return (_jsxs("div", { className: "flex flex-col gap-4", "data-testid": "billing-plans-panel", children: [!result.enabled && (_jsx(Banner, { variant: "info", testId: "billing-plans-panel-disabled", children: t("billing-foundation.plans.billingDisabled") })), !result.canPurchase && (_jsx(Banner, { variant: "warning", testId: "billing-plans-panel-readonly", children: t("billing-foundation.plans.purchaseNotAllowed") })), switchRequested && (_jsx(Banner, { variant: "info", testId: "billing-plans-panel-switch-requested", children: t("billing-foundation.plans.planSwitchRequested") })), mutationError !== null && (_jsx(Banner, { variant: "error", testId: "billing-plans-panel-mutation-error", children: t(mutationError.i18nKey, mutationError.i18nParams) })), result.subscription?.status === SubscriptionStatuses.pastDue && (_jsx(Banner, { variant: "warning", testId: "billing-plans-panel-past-due", children: t("billing-foundation.plans.pastDue") })), result.subscription !== null &&
178
219
  !result.subscription.terminal &&
179
220
  result.subscription.cancelAt != null && (_jsxs(Banner, { variant: "info", testId: "billing-plans-panel-cancel-scheduled", children: [t("billing-foundation.plans.cancelScheduled", {
180
221
  date: toInstant(result.subscription.cancelAt).toLocaleString(locale, {
181
222
  dateStyle: "medium",
182
223
  }),
183
- }), " ", t("billing-foundation.plans.switchRequiresReactivation")] })), consumerProtection !== undefined && canManage && result.subscription?.cancelAt == null && (_jsx("div", { children: _jsx(Button, { variant: "secondary", onClick: () => setCancelOpen(true), testId: "billing-plans-cancel-contract", children: t("billing-foundation.cancel.open") }) })), _jsxs(PlanGrid, { testId: "billing-plans-grid", children: [!currentTierInPlans && (_jsx(PlanCard, { title: t(result.currentTier.labelKey), current: true, testId: "billing-plan-card-current", features: result.currentTier.benefits.map((benefit) => t(benefit.labelKey, localizedParams(benefit.params, locale))), secondaryAction: manageOrReactivateAction(canManage, result.subscription, t, handleManage, redirecting) })), visiblePlans.map((plan) => (_jsx(PlanCard, { testId: `billing-plan-card-${plan.tier}`, ...toCardProps(plan) }, plan.tier))), result.contact !== undefined && _jsx(BillingContactPlan, { contact: result.contact })] }), consumerProtection !== undefined &&
184
- consentPlan !== undefined &&
185
- consentPlan.price !== null && (_jsx(CheckoutConsentDialog, { open: true, onOpenChange: (open) => {
224
+ }), " ", t("billing-foundation.plans.switchRequiresReactivation")] })), consumerProtection !== undefined && canManage && result.subscription?.cancelAt == null && (_jsx("div", { children: _jsx(Button, { variant: "secondary", onClick: () => setCancelOpen(true), testId: "billing-plans-cancel-contract", children: t("billing-foundation.cancel.open") }) })), _jsxs(PlanGrid, { testId: "billing-plans-grid", children: [!currentTierInPlans && (_jsx(PlanCard, { title: t(result.currentTier.labelKey), current: true, testId: "billing-plan-card-current", features: result.currentTier.benefits.map((benefit) => t(benefit.labelKey, localizedParams(benefit.params, locale))), secondaryAction: manageOrReactivateAction(canManage, result.subscription, t, handleManage, redirecting) })), visiblePlans.map((plan) => (_jsx(PlanCard, { testId: `billing-plan-card-${plan.tier}`, ...toCardProps(plan) }, plan.tier))), result.contact !== undefined && _jsx(BillingContactPlan, { contact: result.contact })] }), consentPlan !== undefined && consentPlan.price !== null && (_jsx(CheckoutConsentDialog, { open: true, onOpenChange: (open) => {
186
225
  if (!open)
187
226
  setConsentTier(null);
188
227
  }, planName: t(consentPlan.labelKey), price: {
189
228
  amount: formatMoney(consentPlan.price.unitAmount, consentPlan.price.currency, locale),
190
229
  renewalKey: renewalKey(consentPlan.price),
191
230
  count: new Intl.NumberFormat(locale).format(consentPlan.price.intervalCount ?? 1),
192
- }, consumerProtection: consumerProtection, onOrder: (consent) => orderWithConsent(consentPlan.tier, consent), onConsentTextOutdated: () => void query.refetch() })), consumerProtection !== undefined && canManage && (_jsx(CancelContractDialog, { open: cancelOpen, onOpenChange: setCancelOpen, onTerminated: () => void query.refetch() }))] }));
231
+ }, consumerProtection: consumerProtection, providerChoice: providerChoiceFor(consentPlan.tier), note: singleProviderNote, onOrder: (consent) => orderWithConsent(consentPlan.tier, consent), onConsentTextOutdated: () => void query.refetch() })), switchPlan !== undefined && (_jsx(PlanSwitchConfirmDialog, { open: true, onOpenChange: (open) => {
232
+ if (!open)
233
+ setSwitchTier(null);
234
+ }, tier: switchPlan.tier, fromPlanName: t(result.currentTier.labelKey), toPlanName: t(switchPlan.labelKey), consumerProtection: consumerProtection, onApplied: () => {
235
+ setSwitchTier(null);
236
+ setSwitchRequested(true);
237
+ void query.refetch();
238
+ }, onConsentTextOutdated: () => void query.refetch() })), consumerProtection !== undefined && canManage && (_jsx(CancelContractDialog, { open: cancelOpen, onOpenChange: setCancelOpen, onTerminated: () => void query.refetch() }))] }));
193
239
  }
@@ -18,11 +18,25 @@ export type CheckoutConsentDialogProps = {
18
18
  readonly renewalKey?: string;
19
19
  readonly count?: string;
20
20
  };
21
- readonly consumerProtection: ConsumerProtectionView;
22
- readonly onOrder: (consent: CheckoutConsentPayload) => Promise<WriteResult<{
21
+ /** Without it the dialog shows no legal links and no consent checkboxes and orders without a consent. */
22
+ readonly consumerProtection?: ConsumerProtectionView;
23
+ /** Several providers to pick from; the parent follows `value` for price and order. */
24
+ readonly providerChoice?: {
25
+ readonly options: readonly {
26
+ readonly value: string;
27
+ readonly title: string;
28
+ readonly description?: string;
29
+ readonly aside?: string;
30
+ }[];
31
+ readonly value: string;
32
+ readonly onChange: (value: string) => void;
33
+ };
34
+ /** Provider note under the price line when there is no provider choice. */
35
+ readonly note?: string;
36
+ readonly onOrder: (consent: CheckoutConsentPayload | undefined) => Promise<WriteResult<{
23
37
  url: string;
24
38
  }>>;
25
39
  readonly onConsentTextOutdated: () => void;
26
40
  };
27
- export declare function CheckoutConsentDialog({ open, onOpenChange, planName, price, consumerProtection, onOrder, onConsentTextOutdated, }: CheckoutConsentDialogProps): ReactNode;
41
+ export declare function CheckoutConsentDialog({ open, onOpenChange, planName, price, consumerProtection, providerChoice, note, onOrder, onConsentTextOutdated, }: CheckoutConsentDialogProps): ReactNode;
28
42
  export {};
@@ -1,10 +1,11 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useLocale, usePrimitives, useTranslation } from "@cosmicdrift/kumiko-renderer";
3
+ import { ChoiceCards } from "@cosmicdrift/kumiko-renderer/web";
3
4
  import { useState } from "react";
4
5
  import { resolveConsentLocale } from "./consumer-protection/consent-locale.js";
5
6
  export const CONSENT_TEXT_OUTDATED_CODE = "consent_text_outdated";
6
7
  // kumiko-lint-ignore no-custom-primitives Uses usePrimitives().Modal internally, name is a billing domain action, not a primitive reimplementation
7
- export function CheckoutConsentDialog({ open, onOpenChange, planName, price, consumerProtection, onOrder, onConsentTextOutdated, }) {
8
+ export function CheckoutConsentDialog({ open, onOpenChange, planName, price, consumerProtection, providerChoice, note, onOrder, onConsentTextOutdated, }) {
8
9
  const t = useTranslation();
9
10
  const uiLocale = useLocale().locale();
10
11
  const { Modal, Text, Field, Input, Button, Link, Banner } = usePrimitives();
@@ -13,17 +14,20 @@ export function CheckoutConsentDialog({ open, onOpenChange, planName, price, con
13
14
  const [submitting, setSubmitting] = useState(false);
14
15
  const [errorText, setErrorText] = useState(null);
15
16
  const consentLocale = resolveConsentLocale(uiLocale);
16
- const texts = consumerProtection.consentTexts[consentLocale];
17
- const legalLinks = consumerProtection.legalLinks[consentLocale];
17
+ const texts = consumerProtection?.consentTexts[consentLocale];
18
+ const legalLinks = consumerProtection?.legalLinks[consentLocale];
19
+ const consentGiven = consumerProtection === undefined || (earlyPerformance && withdrawalLoss);
18
20
  async function order() {
19
21
  setSubmitting(true);
20
22
  setErrorText(null);
21
- const result = await onOrder({
22
- earlyPerformanceRequested: true,
23
- withdrawalLossAcknowledged: true,
24
- consentTextVersion: texts.consentTextVersion,
25
- locale: consentLocale,
26
- });
23
+ const result = await onOrder(texts === undefined
24
+ ? undefined
25
+ : {
26
+ earlyPerformanceRequested: true,
27
+ withdrawalLossAcknowledged: true,
28
+ consentTextVersion: texts.consentTextVersion,
29
+ locale: consentLocale,
30
+ });
27
31
  if (result.isSuccess)
28
32
  return;
29
33
  setSubmitting(false);
@@ -34,5 +38,5 @@ export function CheckoutConsentDialog({ open, onOpenChange, planName, price, con
34
38
  onConsentTextOutdated();
35
39
  }
36
40
  }
37
- return (_jsx(Modal, { open: open, onOpenChange: onOpenChange, title: t("billing-foundation.consent.title"), testId: "checkout-consent-dialog", children: _jsxs("div", { className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex flex-col gap-1", "data-testid": "checkout-consent-summary", children: [_jsxs(Text, { children: [planName, " \u2014 ", price.amount] }), price.renewalKey !== undefined && (_jsxs(_Fragment, { children: [_jsx(Text, { variant: "muted", children: t(price.renewalKey, price.count !== undefined ? { count: price.count } : undefined) }), _jsx(Text, { variant: "muted", children: t("billing-foundation.consent.cancelAnytime") })] }))] }), _jsxs("div", { className: "flex flex-wrap gap-4", children: [_jsx(Link, { href: legalLinks.terms, target: "_blank", children: t("billing-foundation.consent.link.terms") }), _jsx(Link, { href: legalLinks.withdrawal, target: "_blank", children: t("billing-foundation.consent.link.withdrawal") }), _jsx(Link, { href: legalLinks.privacy, target: "_blank", children: t("billing-foundation.consent.link.privacy") })] }), _jsx(Field, { id: "consent-early-performance", label: texts.earlyPerformance, layout: "inline", children: _jsx(Input, { kind: "boolean", id: "consent-early-performance", name: "consent-early-performance", value: earlyPerformance, onChange: setEarlyPerformance, disabled: submitting }) }), _jsx(Field, { id: "consent-withdrawal-loss", label: texts.withdrawalLoss, layout: "inline", children: _jsx(Input, { kind: "boolean", id: "consent-withdrawal-loss", name: "consent-withdrawal-loss", value: withdrawalLoss, onChange: setWithdrawalLoss, disabled: submitting }) }), errorText !== null && (_jsx(Banner, { variant: "error", testId: "checkout-consent-error", children: errorText })), _jsxs("div", { className: "flex justify-end gap-2", children: [_jsx(Button, { variant: "secondary", disabled: submitting, onClick: () => onOpenChange(false), testId: "checkout-consent-cancel", children: t("billing-foundation.consent.back") }), _jsx(Button, { disabled: !earlyPerformance || !withdrawalLoss || submitting, loading: submitting, onClick: order, testId: "checkout-consent-order", children: t("billing-foundation.consent.order") })] })] }) }));
41
+ return (_jsx(Modal, { open: open, onOpenChange: onOpenChange, title: t("billing-foundation.consent.title"), testId: "checkout-consent-dialog", children: _jsxs("div", { className: "flex flex-col gap-4", children: [providerChoice !== undefined && (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Text, { variant: "muted", children: t("billing-foundation.consent.chooseProvider") }), _jsx(ChoiceCards, { value: providerChoice.value, options: providerChoice.options, onChange: providerChoice.onChange, ariaLabel: t("billing-foundation.consent.chooseProvider"), testId: "checkout-provider-choice" })] })), _jsxs("div", { className: "flex flex-col gap-1", "data-testid": "checkout-consent-summary", children: [_jsxs(Text, { children: [planName, " \u2014 ", price.amount] }), price.renewalKey !== undefined && (_jsxs(_Fragment, { children: [_jsx(Text, { variant: "muted", children: t(price.renewalKey, price.count !== undefined ? { count: price.count } : undefined) }), _jsx(Text, { variant: "muted", children: t("billing-foundation.consent.cancelAnytime") })] })), note !== undefined && (_jsx(Text, { variant: "muted", testId: "checkout-consent-note", children: note }))] }), legalLinks !== undefined && (_jsxs("div", { className: "flex flex-wrap gap-4", children: [_jsx(Link, { href: legalLinks.terms, target: "_blank", children: t("billing-foundation.consent.link.terms") }), _jsx(Link, { href: legalLinks.withdrawal, target: "_blank", children: t("billing-foundation.consent.link.withdrawal") }), _jsx(Link, { href: legalLinks.privacy, target: "_blank", children: t("billing-foundation.consent.link.privacy") })] })), texts !== undefined && (_jsxs(_Fragment, { children: [_jsx(Field, { id: "consent-early-performance", label: texts.earlyPerformance, layout: "inline", children: _jsx(Input, { kind: "boolean", id: "consent-early-performance", name: "consent-early-performance", value: earlyPerformance, onChange: setEarlyPerformance, disabled: submitting }) }), _jsx(Field, { id: "consent-withdrawal-loss", label: texts.withdrawalLoss, layout: "inline", children: _jsx(Input, { kind: "boolean", id: "consent-withdrawal-loss", name: "consent-withdrawal-loss", value: withdrawalLoss, onChange: setWithdrawalLoss, disabled: submitting }) })] })), errorText !== null && (_jsx(Banner, { variant: "error", testId: "checkout-consent-error", children: errorText })), _jsxs("div", { className: "flex justify-end gap-2", children: [_jsx(Button, { variant: "secondary", disabled: submitting, onClick: () => onOpenChange(false), testId: "checkout-consent-cancel", children: t("billing-foundation.consent.back") }), _jsx(Button, { disabled: !consentGiven || submitting, loading: submitting, onClick: order, testId: "checkout-consent-order", children: t("billing-foundation.consent.order") })] })] }) }));
38
42
  }
@@ -78,6 +78,10 @@ export declare const SubscriptionFoundationQueries: {
78
78
  * current tier and per-plan action. Only registered when a `catalog` is
79
79
  * configured. */
80
80
  readonly billingPlans: "billing-foundation:query:billing-plans";
81
+ /** What switching to a tier would cost right now, for providers that switch
82
+ * plans in-app (preview + apply). Only registered when a `catalog` is
83
+ * configured. */
84
+ readonly planSwitchPreview: "billing-foundation:query:plan-switch-preview";
81
85
  /** System-only probe: does the caller tenant hold a non-terminal
82
86
  * subscription (consumerProtection only). */
83
87
  readonly terminableSubscription: "billing-foundation:query:terminable-subscription";
@@ -84,6 +84,10 @@ export const SubscriptionFoundationQueries = {
84
84
  * current tier and per-plan action. Only registered when a `catalog` is
85
85
  * configured. */
86
86
  billingPlans: "billing-foundation:query:billing-plans",
87
+ /** What switching to a tier would cost right now, for providers that switch
88
+ * plans in-app (preview + apply). Only registered when a `catalog` is
89
+ * configured. */
90
+ planSwitchPreview: "billing-foundation:query:plan-switch-preview",
87
91
  /** System-only probe: does the caller tenant hold a non-terminal
88
92
  * subscription (consumerProtection only). */
89
93
  terminableSubscription: "billing-foundation:query:terminable-subscription",
@@ -0,0 +1,15 @@
1
+ import type { BillingPlanView } from "../types.ts";
2
+ type Recurrence = {
3
+ readonly interval: string | null;
4
+ readonly intervalCount: number | null;
5
+ };
6
+ export declare function intervalKey(price: Recurrence | null): string | undefined;
7
+ export declare function renewalKey(price: Recurrence | null): string | undefined;
8
+ type PlanPrice = NonNullable<BillingPlanView["price"]>;
9
+ export declare function pricesDiffer(prices: readonly PlanPrice[]): boolean;
10
+ /** Price of `tier` at every provider that offers it, in provider order. */
11
+ export declare function tierPrices(providers: readonly {
12
+ readonly plans: readonly BillingPlanView[];
13
+ }[], tier: string): PlanPrice[];
14
+ export declare function cheapestPrice(prices: readonly PlanPrice[]): PlanPrice | undefined;
15
+ export {};
@@ -0,0 +1,32 @@
1
+ export function intervalKey(price) {
2
+ if (price === null || price.interval === null)
3
+ return undefined;
4
+ return price.intervalCount !== null && price.intervalCount > 1
5
+ ? `billing-foundation.plans.everyInterval.${price.interval}`
6
+ : `billing-foundation.plans.perInterval.${price.interval}`;
7
+ }
8
+ export function renewalKey(price) {
9
+ if (price === null || price.interval === null)
10
+ return undefined;
11
+ return price.intervalCount !== null && price.intervalCount > 1
12
+ ? `billing-foundation.consent.renewsEvery.${price.interval}`
13
+ : `billing-foundation.consent.renews.${price.interval}`;
14
+ }
15
+ export function pricesDiffer(prices) {
16
+ const [first, ...rest] = prices;
17
+ return (first !== undefined &&
18
+ rest.some((price) => price.unitAmount !== first.unitAmount ||
19
+ price.currency !== first.currency ||
20
+ price.interval !== first.interval ||
21
+ price.intervalCount !== first.intervalCount));
22
+ }
23
+ /** Price of `tier` at every provider that offers it, in provider order. */
24
+ export function tierPrices(providers, tier) {
25
+ return providers.flatMap((provider) => {
26
+ const price = provider.plans.find((plan) => plan.tier === tier)?.price;
27
+ return price ? [price] : [];
28
+ });
29
+ }
30
+ export function cheapestPrice(prices) {
31
+ return prices.reduce((lowest, price) => lowest === undefined || price.unitAmount < lowest.unitAmount ? price : lowest, undefined);
32
+ }
@@ -0,0 +1,15 @@
1
+ import { type ReactNode } from "react";
2
+ import type { BillingPlansResult } from "../types.ts";
3
+ type ConsumerProtectionView = NonNullable<BillingPlansResult["consumerProtection"]>;
4
+ export type PlanSwitchConfirmDialogProps = {
5
+ readonly open: boolean;
6
+ readonly onOpenChange: (open: boolean) => void;
7
+ readonly tier: string;
8
+ readonly fromPlanName: string;
9
+ readonly toPlanName: string;
10
+ readonly consumerProtection?: ConsumerProtectionView;
11
+ readonly onApplied: () => void;
12
+ readonly onConsentTextOutdated: () => void;
13
+ };
14
+ export declare function PlanSwitchConfirmDialog({ open, onOpenChange, tier, fromPlanName, toPlanName, consumerProtection, onApplied, onConsentTextOutdated, }: PlanSwitchConfirmDialogProps): ReactNode;
15
+ export {};
@@ -0,0 +1,66 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useLocale, useMutation, usePrimitives, useQuery, useTranslation, } from "@cosmicdrift/kumiko-renderer";
3
+ import { formatMoney } from "@cosmicdrift/kumiko-renderer/web";
4
+ import { useState } from "react";
5
+ import { CONSENT_TEXT_OUTDATED_CODE, } from "./checkout-consent-dialog.js";
6
+ import { SubscriptionFoundationHandlers, SubscriptionFoundationQueries } from "./constants.js";
7
+ import { resolveConsentLocale } from "./consumer-protection/consent-locale.js";
8
+ import { intervalKey } from "./plan-price.js";
9
+ // kumiko-lint-ignore no-custom-primitives Uses usePrimitives().Modal internally, name is a billing domain action, not a primitive reimplementation
10
+ export function PlanSwitchConfirmDialog({ open, onOpenChange, tier, fromPlanName, toPlanName, consumerProtection, onApplied, onConsentTextOutdated, }) {
11
+ const t = useTranslation();
12
+ const locale = useLocale().locale();
13
+ const { Modal, Text, Field, Input, Button, Link, Banner } = usePrimitives();
14
+ const previewQuery = useQuery(SubscriptionFoundationQueries.planSwitchPreview, { tier });
15
+ const switchMutation = useMutation(SubscriptionFoundationHandlers.switchPlan);
16
+ const [earlyPerformance, setEarlyPerformance] = useState(false);
17
+ const [withdrawalLoss, setWithdrawalLoss] = useState(false);
18
+ const [submitting, setSubmitting] = useState(false);
19
+ const [errorText, setErrorText] = useState(null);
20
+ const consentLocale = resolveConsentLocale(locale);
21
+ const texts = consumerProtection?.consentTexts[consentLocale];
22
+ const legalLinks = consumerProtection?.legalLinks[consentLocale];
23
+ const consentGiven = consumerProtection === undefined || (earlyPerformance && withdrawalLoss);
24
+ async function confirm() {
25
+ setSubmitting(true);
26
+ setErrorText(null);
27
+ const consent = texts === undefined
28
+ ? undefined
29
+ : {
30
+ earlyPerformanceRequested: true,
31
+ withdrawalLossAcknowledged: true,
32
+ consentTextVersion: texts.consentTextVersion,
33
+ locale: consentLocale,
34
+ };
35
+ const result = await switchMutation.mutate({ tier, ...(consent && { consent }) });
36
+ if (result.isSuccess) {
37
+ onApplied();
38
+ return;
39
+ }
40
+ setSubmitting(false);
41
+ setErrorText(t(result.error.i18nKey, result.error.i18nParams));
42
+ if (result.error.code === CONSENT_TEXT_OUTDATED_CODE) {
43
+ setEarlyPerformance(false);
44
+ setWithdrawalLoss(false);
45
+ onConsentTextOutdated();
46
+ }
47
+ }
48
+ const reply = previewQuery.data;
49
+ const money = (amount, currency) => formatMoney(amount, currency, locale);
50
+ const recurringKey = reply === null ? undefined : intervalKey(reply.target);
51
+ const recurringPrice = reply === null
52
+ ? ""
53
+ : `${money(reply.target.unitAmount, reply.target.currency)}${recurringKey === undefined
54
+ ? ""
55
+ : ` ${t(recurringKey, { count: new Intl.NumberFormat(locale).format(reply.target.intervalCount ?? 1) })}`}`;
56
+ return (_jsx(Modal, { open: open, onOpenChange: onOpenChange, title: t("billing-foundation.planSwitch.title"), testId: "plan-switch-confirm-dialog", children: _jsxs("div", { className: "flex flex-col gap-4", children: [_jsx(Text, { children: t("billing-foundation.planSwitch.summary", { from: fromPlanName, to: toPlanName }) }), previewQuery.error !== null && (_jsx(Banner, { variant: "error", testId: "plan-switch-preview-error", children: t(previewQuery.error.i18nKey, previewQuery.error.i18nParams) })), previewQuery.loading && reply === null && (_jsx(Banner, { variant: "loading", testId: "plan-switch-preview-loading", children: t("kumiko.widget.loading") })), reply !== null && (_jsxs("div", { className: "flex flex-col gap-1", "data-testid": "plan-switch-summary", children: [_jsxs(Text, { children: [t("billing-foundation.planSwitch.dueNow", {
57
+ amount: money(reply.preview.amountDueNow, reply.preview.currency),
58
+ }), reply.preview.tax !== null &&
59
+ ` (${t("billing-foundation.planSwitch.includesTax", {
60
+ tax: money(reply.preview.tax, reply.preview.currency),
61
+ })})`] }), _jsx(Text, { variant: "muted", children: t("billing-foundation.planSwitch.credit", {
62
+ amount: money(reply.preview.credit, reply.preview.currency),
63
+ }) }), _jsx(Text, { variant: "muted", children: t("billing-foundation.planSwitch.recurring", {
64
+ amount: recurringPrice,
65
+ }) }), _jsx(Text, { variant: "muted", children: t("billing-foundation.planSwitch.effective") }), _jsx(Text, { variant: "muted", children: t("billing-foundation.planSwitch.pendingNote") })] })), legalLinks !== undefined && (_jsxs("div", { className: "flex flex-wrap gap-4", children: [_jsx(Link, { href: legalLinks.terms, target: "_blank", children: t("billing-foundation.consent.link.terms") }), _jsx(Link, { href: legalLinks.withdrawal, target: "_blank", children: t("billing-foundation.consent.link.withdrawal") }), _jsx(Link, { href: legalLinks.privacy, target: "_blank", children: t("billing-foundation.consent.link.privacy") })] })), texts !== undefined && (_jsxs(_Fragment, { children: [_jsx(Field, { id: "plan-switch-early-performance", label: texts.earlyPerformance, layout: "inline", children: _jsx(Input, { kind: "boolean", id: "plan-switch-early-performance", name: "plan-switch-early-performance", value: earlyPerformance, onChange: setEarlyPerformance, disabled: submitting }) }), _jsx(Field, { id: "plan-switch-withdrawal-loss", label: texts.withdrawalLoss, layout: "inline", children: _jsx(Input, { kind: "boolean", id: "plan-switch-withdrawal-loss", name: "plan-switch-withdrawal-loss", value: withdrawalLoss, onChange: setWithdrawalLoss, disabled: submitting }) })] })), errorText !== null && (_jsx(Banner, { variant: "error", testId: "plan-switch-confirm-error", children: errorText })), _jsxs("div", { className: "flex justify-end gap-2", children: [_jsx(Button, { variant: "secondary", disabled: submitting, onClick: () => onOpenChange(false), testId: "plan-switch-confirm-cancel", children: t("billing-foundation.consent.back") }), _jsx(Button, { disabled: !consentGiven || reply === null || submitting, loading: submitting, onClick: confirm, testId: "plan-switch-confirm-order", children: t("billing-foundation.planSwitch.order") })] })] }) }));
66
+ }
@@ -1,14 +1,13 @@
1
1
  import { v5 as uuidv5 } from "uuid";
2
- // Fixed UUID-namespace für die cap-counter-aggregate-id-Ableitung.
3
- // Generiert einmalig (2026-05-02), in Stein gemeißelt: ein Wechsel würde
4
- // jeden existing aggregate-Stream re-keyen → kaputter event-replay,
5
- // kaputte counter-history, verlorener Audit-Trail. Drift-Pin in
6
- // __tests__/drift.test.ts pinnt den UUID-Wert.
2
+ // Fixed UUID namespace for deriving cap-counter aggregate IDs.
3
+ // Generated once (2026-05-02) and immutable: changing it would re-key every
4
+ // existing aggregate stream, breaking event replay and counter history and
5
+ // losing the audit trail. The drift test in __tests__/drift.test.ts pins the UUID.
7
6
  const CAP_COUNTER_NAMESPACE = "9c1bf2a3-6e4d-4f5b-8a9c-2d3e4f5a6b7c";
8
- // Separater Namespace für Rolling-Window-Counter (Sprint 4). Eigener
9
- // Namespace damit das aggregate-id NIE mit einem Calendar-Counter
10
- // kollidiert, selbst wenn jemand "1970-01-01..." als periodStart in
11
- // den Calendar-Pfad reinpasst. Drift-Pin in __tests__/drift.test.ts.
7
+ // Separate namespace for rolling-window counters. This prevents the aggregate ID
8
+ // from colliding with a calendar counter, even if "1970-01-01..." is passed as
9
+ // periodStart through the calendar path. The drift test in __tests__/drift.test.ts
10
+ // pins the UUID.
12
11
  const CAP_COUNTER_ROLLING_NAMESPACE = "8b2ad0c6-1f3e-4f7c-9b8a-3c4d5e6f7a8b";
13
12
  /**
14
13
  * One aggregate per (tenantId, capName, periodStart-as-iso). A new calendar
@@ -269,7 +269,11 @@ async function consumeClaimedRow(txDb, user, claimed, options) {
269
269
  const booked = await bookCalendarDelta(txDb, user, options, options.units);
270
270
  if (!booked.isSuccess)
271
271
  return booked;
272
- await txDb.updateMany(capReservationsTable, { consumedAmount: claimed.consumedAmount + options.units }, ownRow);
272
+ await txDb.updateMany(capReservationsTable, {
273
+ consumedAmount: claimed.consumedAmount + options.units,
274
+ // Keep the tombstone alive past its last consume, else a later consume would start a fresh one and double count.
275
+ expiresAt: Temporal.Now.instant().add({ minutes: CAP_SETTLED_TOMBSTONE_TTL_MINUTES }),
276
+ }, ownRow);
273
277
  return booked;
274
278
  }
275
279
  if (options.units > claimed.amount) {
@@ -1,19 +1,18 @@
1
1
  // Feature name
2
2
  export const CAP_COUNTER_FEATURE = "cap-counter";
3
3
  export const CAP_COUNTER_LIST_SCREEN_ID = "cap-list";
4
- // Aggregate types — calendar-period-Counter benutzt CRUD-Events der
5
- // projection-row. Rolling-Window-Counter benutzt einen eigenen
6
- // aggregate-type mit custom increment-events (no projection — der
7
- // Read summiert über die letzten N Tage Events). Sind getrennt damit
8
- // die r.entity-projection nicht auch noch rolling-counter-rows tracken
9
- // muss.
4
+ // Aggregate types: calendar-period counters use CRUD events for the
5
+ // projection row. Rolling-window counters use a separate aggregate type
6
+ // with custom increment events (no projection—the read sums events from
7
+ // the last N days). They are separate so `r.entity-projection` does not
8
+ // also have to track rolling-counter rows.
10
9
  export const CAP_COUNTER_ROLLING_AGGREGATE_TYPE = "cap-counter-rolling";
11
- // Custom event-type für Rolling-Window-Counter. Symmetrisches Paar:
12
- // _SHORT — passt zu `r.defineEvent(short, schema)` im Registrar
13
- // (Framework prefixt automatisch zu QN)
14
- // _QN — qualifizierte Form für `ctx.unsafeAppendEvent({type})`
15
- // + `events.type`-Spalte + `registry.getEvent(qn)`-Lookup
16
- // Beide MÜSSEN konsistent sein (drift-pin im feature-test).
10
+ // Custom event types for rolling-window counters. The pair is symmetric:
11
+ // _SHORT — matches `r.defineEvent(short, schema)` in the registrar
12
+ // (the framework automatically prefixes it to the QN)
13
+ // _QN — qualified form for `ctx.unsafeAppendEvent({type})`, the
14
+ // `events.type` column, and `registry.getEvent(qn)` lookups
15
+ // Both MUST stay consistent; the feature test pins against drift.
17
16
  export const ROLLING_INCREMENTED_EVENT_SHORT = "rolling-incremented";
18
17
  export const ROLLING_INCREMENTED_EVENT_QN = "cap-counter:event:rolling-incremented";
19
18
  // Gives back a reserved rolling amount; the window sum is incremented minus released.