@mmailaender/convex-creem 0.1.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 (383) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +1176 -0
  3. package/dist/client/helpers.d.ts +17 -0
  4. package/dist/client/helpers.d.ts.map +1 -0
  5. package/dist/client/helpers.js +43 -0
  6. package/dist/client/helpers.js.map +1 -0
  7. package/dist/client/index.d.ts +1041 -0
  8. package/dist/client/index.d.ts.map +1 -0
  9. package/dist/client/index.js +1068 -0
  10. package/dist/client/index.js.map +1 -0
  11. package/dist/client/parsers.d.ts +45 -0
  12. package/dist/client/parsers.d.ts.map +1 -0
  13. package/dist/client/parsers.js +138 -0
  14. package/dist/client/parsers.js.map +1 -0
  15. package/dist/client/polyfill.d.ts +2 -0
  16. package/dist/client/polyfill.d.ts.map +1 -0
  17. package/dist/client/polyfill.js +3 -0
  18. package/dist/client/polyfill.js.map +1 -0
  19. package/dist/component/_generated/api.d.ts +36 -0
  20. package/dist/component/_generated/api.d.ts.map +1 -0
  21. package/dist/component/_generated/api.js +31 -0
  22. package/dist/component/_generated/api.js.map +1 -0
  23. package/dist/component/_generated/component.d.ts +542 -0
  24. package/dist/component/_generated/component.d.ts.map +1 -0
  25. package/dist/component/_generated/component.js +11 -0
  26. package/dist/component/_generated/component.js.map +1 -0
  27. package/dist/component/_generated/dataModel.d.ts +46 -0
  28. package/dist/component/_generated/dataModel.d.ts.map +1 -0
  29. package/dist/component/_generated/dataModel.js +11 -0
  30. package/dist/component/_generated/dataModel.js.map +1 -0
  31. package/dist/component/_generated/server.d.ts +121 -0
  32. package/dist/component/_generated/server.d.ts.map +1 -0
  33. package/dist/component/_generated/server.js +78 -0
  34. package/dist/component/_generated/server.js.map +1 -0
  35. package/dist/component/convex.config.d.ts +3 -0
  36. package/dist/component/convex.config.d.ts.map +1 -0
  37. package/dist/component/convex.config.js +3 -0
  38. package/dist/component/convex.config.js.map +1 -0
  39. package/dist/component/lib.d.ts +1005 -0
  40. package/dist/component/lib.d.ts.map +1 -0
  41. package/dist/component/lib.js +647 -0
  42. package/dist/component/lib.js.map +1 -0
  43. package/dist/component/schema.d.ts +191 -0
  44. package/dist/component/schema.d.ts.map +1 -0
  45. package/dist/component/schema.js +104 -0
  46. package/dist/component/schema.js.map +1 -0
  47. package/dist/component/util.d.ts +61 -0
  48. package/dist/component/util.d.ts.map +1 -0
  49. package/dist/component/util.js +142 -0
  50. package/dist/component/util.js.map +1 -0
  51. package/dist/core/catalog.d.ts +18 -0
  52. package/dist/core/catalog.d.ts.map +1 -0
  53. package/dist/core/catalog.js +82 -0
  54. package/dist/core/catalog.js.map +1 -0
  55. package/dist/core/index.d.ts +9 -0
  56. package/dist/core/index.d.ts.map +1 -0
  57. package/dist/core/index.js +9 -0
  58. package/dist/core/index.js.map +1 -0
  59. package/dist/core/markdown.d.ts +12 -0
  60. package/dist/core/markdown.d.ts.map +1 -0
  61. package/dist/core/markdown.js +26 -0
  62. package/dist/core/markdown.js.map +1 -0
  63. package/dist/core/payments.d.ts +11 -0
  64. package/dist/core/payments.d.ts.map +1 -0
  65. package/dist/core/payments.js +27 -0
  66. package/dist/core/payments.js.map +1 -0
  67. package/dist/core/pendingCheckout.d.ts +15 -0
  68. package/dist/core/pendingCheckout.d.ts.map +1 -0
  69. package/dist/core/pendingCheckout.js +40 -0
  70. package/dist/core/pendingCheckout.js.map +1 -0
  71. package/dist/core/resolver.d.ts +11 -0
  72. package/dist/core/resolver.d.ts.map +1 -0
  73. package/dist/core/resolver.js +106 -0
  74. package/dist/core/resolver.js.map +1 -0
  75. package/dist/core/selectors.d.ts +12 -0
  76. package/dist/core/selectors.d.ts.map +1 -0
  77. package/dist/core/selectors.js +18 -0
  78. package/dist/core/selectors.js.map +1 -0
  79. package/dist/core/subscriptionUpdate.d.ts +20 -0
  80. package/dist/core/subscriptionUpdate.d.ts.map +1 -0
  81. package/dist/core/subscriptionUpdate.js +64 -0
  82. package/dist/core/subscriptionUpdate.js.map +1 -0
  83. package/dist/core/types.d.ts +170 -0
  84. package/dist/core/types.d.ts.map +1 -0
  85. package/dist/core/types.js +15 -0
  86. package/dist/core/types.js.map +1 -0
  87. package/dist/design-system/colors/color-utils.d.ts +10 -0
  88. package/dist/design-system/colors/color-utils.d.ts.map +1 -0
  89. package/dist/design-system/colors/color-utils.js +91 -0
  90. package/dist/design-system/colors/color-utils.js.map +1 -0
  91. package/dist/design-system/colors/config.d.ts +33 -0
  92. package/dist/design-system/colors/config.d.ts.map +1 -0
  93. package/dist/design-system/colors/config.js +224 -0
  94. package/dist/design-system/colors/config.js.map +1 -0
  95. package/dist/design-system/colors/index.d.ts +3 -0
  96. package/dist/design-system/colors/index.d.ts.map +1 -0
  97. package/dist/design-system/colors/index.js +3 -0
  98. package/dist/design-system/colors/index.js.map +1 -0
  99. package/dist/design-system/rounded/config.d.ts +31 -0
  100. package/dist/design-system/rounded/config.d.ts.map +1 -0
  101. package/dist/design-system/rounded/config.js +76 -0
  102. package/dist/design-system/rounded/config.js.map +1 -0
  103. package/dist/design-system/rounded/index.d.ts +2 -0
  104. package/dist/design-system/rounded/index.d.ts.map +1 -0
  105. package/dist/design-system/rounded/index.js +2 -0
  106. package/dist/design-system/rounded/index.js.map +1 -0
  107. package/dist/design-system/typography/config.d.ts +55 -0
  108. package/dist/design-system/typography/config.d.ts.map +1 -0
  109. package/dist/design-system/typography/config.js +308 -0
  110. package/dist/design-system/typography/config.js.map +1 -0
  111. package/dist/design-system/typography/index.d.ts +3 -0
  112. package/dist/design-system/typography/index.d.ts.map +1 -0
  113. package/dist/design-system/typography/index.js +3 -0
  114. package/dist/design-system/typography/index.js.map +1 -0
  115. package/dist/design-system/typography/tokens.d.ts +23 -0
  116. package/dist/design-system/typography/tokens.d.ts.map +1 -0
  117. package/dist/design-system/typography/tokens.js +99 -0
  118. package/dist/design-system/typography/tokens.js.map +1 -0
  119. package/dist/react/hooks/useCheckoutSuccessParams.d.ts +2 -0
  120. package/dist/react/hooks/useCheckoutSuccessParams.d.ts.map +1 -0
  121. package/dist/react/hooks/useCheckoutSuccessParams.js +5 -0
  122. package/dist/react/hooks/useCheckoutSuccessParams.js.map +1 -0
  123. package/dist/react/index.d.ts +25 -0
  124. package/dist/react/index.d.ts.map +1 -0
  125. package/dist/react/index.js +22 -0
  126. package/dist/react/index.js.map +1 -0
  127. package/dist/react/primitives/BillingGate.d.ts +8 -0
  128. package/dist/react/primitives/BillingGate.d.ts.map +1 -0
  129. package/dist/react/primitives/BillingGate.js +13 -0
  130. package/dist/react/primitives/BillingGate.js.map +1 -0
  131. package/dist/react/primitives/BillingToggle.d.ts +8 -0
  132. package/dist/react/primitives/BillingToggle.d.ts.map +1 -0
  133. package/dist/react/primitives/BillingToggle.js +12 -0
  134. package/dist/react/primitives/BillingToggle.js.map +1 -0
  135. package/dist/react/primitives/CheckoutButton.d.ts +11 -0
  136. package/dist/react/primitives/CheckoutButton.d.ts.map +1 -0
  137. package/dist/react/primitives/CheckoutButton.js +21 -0
  138. package/dist/react/primitives/CheckoutButton.js.map +1 -0
  139. package/dist/react/primitives/CheckoutSuccessSummary.d.ts +7 -0
  140. package/dist/react/primitives/CheckoutSuccessSummary.d.ts.map +1 -0
  141. package/dist/react/primitives/CheckoutSuccessSummary.js +11 -0
  142. package/dist/react/primitives/CheckoutSuccessSummary.js.map +1 -0
  143. package/dist/react/primitives/CustomerPortalButton.d.ts +8 -0
  144. package/dist/react/primitives/CustomerPortalButton.d.ts.map +1 -0
  145. package/dist/react/primitives/CustomerPortalButton.js +21 -0
  146. package/dist/react/primitives/CustomerPortalButton.js.map +1 -0
  147. package/dist/react/primitives/NumberInput.d.ts +11 -0
  148. package/dist/react/primitives/NumberInput.d.ts.map +1 -0
  149. package/dist/react/primitives/NumberInput.js +18 -0
  150. package/dist/react/primitives/NumberInput.js.map +1 -0
  151. package/dist/react/primitives/OneTimeCheckoutButton.d.ts +11 -0
  152. package/dist/react/primitives/OneTimeCheckoutButton.d.ts.map +1 -0
  153. package/dist/react/primitives/OneTimeCheckoutButton.js +4 -0
  154. package/dist/react/primitives/OneTimeCheckoutButton.js.map +1 -0
  155. package/dist/react/primitives/OneTimePaymentStatusBadge.d.ts +6 -0
  156. package/dist/react/primitives/OneTimePaymentStatusBadge.d.ts.map +1 -0
  157. package/dist/react/primitives/OneTimePaymentStatusBadge.js +11 -0
  158. package/dist/react/primitives/OneTimePaymentStatusBadge.js.map +1 -0
  159. package/dist/react/primitives/PaymentWarningBanner.d.ts +7 -0
  160. package/dist/react/primitives/PaymentWarningBanner.d.ts.map +1 -0
  161. package/dist/react/primitives/PaymentWarningBanner.js +18 -0
  162. package/dist/react/primitives/PaymentWarningBanner.js.map +1 -0
  163. package/dist/react/primitives/PricingCard.d.ts +37 -0
  164. package/dist/react/primitives/PricingCard.d.ts.map +1 -0
  165. package/dist/react/primitives/PricingCard.js +125 -0
  166. package/dist/react/primitives/PricingCard.js.map +1 -0
  167. package/dist/react/primitives/PricingSection.d.ts +39 -0
  168. package/dist/react/primitives/PricingSection.d.ts.map +1 -0
  169. package/dist/react/primitives/PricingSection.js +24 -0
  170. package/dist/react/primitives/PricingSection.js.map +1 -0
  171. package/dist/react/primitives/ScheduledChangeBanner.d.ts +8 -0
  172. package/dist/react/primitives/ScheduledChangeBanner.d.ts.map +1 -0
  173. package/dist/react/primitives/ScheduledChangeBanner.js +13 -0
  174. package/dist/react/primitives/ScheduledChangeBanner.js.map +1 -0
  175. package/dist/react/primitives/SegmentControl.d.ts +11 -0
  176. package/dist/react/primitives/SegmentControl.d.ts.map +1 -0
  177. package/dist/react/primitives/SegmentControl.js +8 -0
  178. package/dist/react/primitives/SegmentControl.js.map +1 -0
  179. package/dist/react/primitives/SegmentGroup.d.ts +14 -0
  180. package/dist/react/primitives/SegmentGroup.d.ts.map +1 -0
  181. package/dist/react/primitives/SegmentGroup.js +11 -0
  182. package/dist/react/primitives/SegmentGroup.js.map +1 -0
  183. package/dist/react/primitives/TrialLimitBanner.d.ts +7 -0
  184. package/dist/react/primitives/TrialLimitBanner.d.ts.map +1 -0
  185. package/dist/react/primitives/TrialLimitBanner.js +14 -0
  186. package/dist/react/primitives/TrialLimitBanner.js.map +1 -0
  187. package/dist/react/shared.d.ts +28 -0
  188. package/dist/react/shared.d.ts.map +1 -0
  189. package/dist/react/shared.js +109 -0
  190. package/dist/react/shared.js.map +1 -0
  191. package/dist/react/widgets/BillingPortal.d.ts +9 -0
  192. package/dist/react/widgets/BillingPortal.d.ts.map +1 -0
  193. package/dist/react/widgets/BillingPortal.js +30 -0
  194. package/dist/react/widgets/BillingPortal.js.map +1 -0
  195. package/dist/react/widgets/ProductItem.d.ts +8 -0
  196. package/dist/react/widgets/ProductItem.d.ts.map +1 -0
  197. package/dist/react/widgets/ProductItem.js +14 -0
  198. package/dist/react/widgets/ProductItem.js.map +1 -0
  199. package/dist/react/widgets/ProductRoot.d.ts +16 -0
  200. package/dist/react/widgets/ProductRoot.d.ts.map +1 -0
  201. package/dist/react/widgets/ProductRoot.js +171 -0
  202. package/dist/react/widgets/ProductRoot.js.map +1 -0
  203. package/dist/react/widgets/SubscriptionItem.d.ts +27 -0
  204. package/dist/react/widgets/SubscriptionItem.d.ts.map +1 -0
  205. package/dist/react/widgets/SubscriptionItem.js +32 -0
  206. package/dist/react/widgets/SubscriptionItem.js.map +1 -0
  207. package/dist/react/widgets/SubscriptionRoot.d.ts +16 -0
  208. package/dist/react/widgets/SubscriptionRoot.d.ts.map +1 -0
  209. package/dist/react/widgets/SubscriptionRoot.js +405 -0
  210. package/dist/react/widgets/SubscriptionRoot.js.map +1 -0
  211. package/dist/react/widgets/index.d.ts +19 -0
  212. package/dist/react/widgets/index.d.ts.map +1 -0
  213. package/dist/react/widgets/index.js +16 -0
  214. package/dist/react/widgets/index.js.map +1 -0
  215. package/dist/react/widgets/productGroupContext.d.ts +6 -0
  216. package/dist/react/widgets/productGroupContext.d.ts.map +1 -0
  217. package/dist/react/widgets/productGroupContext.js +3 -0
  218. package/dist/react/widgets/productGroupContext.js.map +1 -0
  219. package/dist/react/widgets/subscriptionContext.d.ts +6 -0
  220. package/dist/react/widgets/subscriptionContext.d.ts.map +1 -0
  221. package/dist/react/widgets/subscriptionContext.js +3 -0
  222. package/dist/react/widgets/subscriptionContext.js.map +1 -0
  223. package/dist/react/widgets/types.d.ts +171 -0
  224. package/dist/react/widgets/types.d.ts.map +1 -0
  225. package/dist/react/widgets/types.js +2 -0
  226. package/dist/react/widgets/types.js.map +1 -0
  227. package/dist/svelte/index.d.ts +22 -0
  228. package/dist/svelte/index.d.ts.map +1 -0
  229. package/dist/svelte/index.js +20 -0
  230. package/dist/svelte/index.js.map +1 -0
  231. package/dist/svelte/primitives/BillingGate.svelte +28 -0
  232. package/dist/svelte/primitives/BillingToggle.svelte +27 -0
  233. package/dist/svelte/primitives/CheckoutButton.svelte +60 -0
  234. package/dist/svelte/primitives/CheckoutSuccessSummary.svelte +34 -0
  235. package/dist/svelte/primitives/CustomerPortalButton.svelte +60 -0
  236. package/dist/svelte/primitives/NumberInput.svelte +71 -0
  237. package/dist/svelte/primitives/OneTimeCheckoutButton.svelte +37 -0
  238. package/dist/svelte/primitives/OneTimePaymentStatusBadge.svelte +20 -0
  239. package/dist/svelte/primitives/PaymentWarningBanner.svelte +30 -0
  240. package/dist/svelte/primitives/PricingCard.svelte +356 -0
  241. package/dist/svelte/primitives/PricingSection.svelte +121 -0
  242. package/dist/svelte/primitives/ScheduledChangeBanner.svelte +46 -0
  243. package/dist/svelte/primitives/SegmentControl.svelte +38 -0
  244. package/dist/svelte/primitives/SegmentGroup.svelte +52 -0
  245. package/dist/svelte/primitives/TrialLimitBanner.svelte +32 -0
  246. package/dist/svelte/primitives/shared.d.ts +13 -0
  247. package/dist/svelte/primitives/shared.d.ts.map +1 -0
  248. package/dist/svelte/primitives/shared.js +87 -0
  249. package/dist/svelte/primitives/shared.js.map +1 -0
  250. package/dist/svelte/widgets/BillingPortal.svelte +55 -0
  251. package/dist/svelte/widgets/Product.svelte +35 -0
  252. package/dist/svelte/widgets/ProductRoot.svelte +428 -0
  253. package/dist/svelte/widgets/Subscription.svelte +52 -0
  254. package/dist/svelte/widgets/SubscriptionRoot.svelte +690 -0
  255. package/dist/svelte/widgets/index.d.ts +19 -0
  256. package/dist/svelte/widgets/index.d.ts.map +1 -0
  257. package/dist/svelte/widgets/index.js +16 -0
  258. package/dist/svelte/widgets/index.js.map +1 -0
  259. package/dist/svelte/widgets/productGroupContext.d.ts +6 -0
  260. package/dist/svelte/widgets/productGroupContext.d.ts.map +1 -0
  261. package/dist/svelte/widgets/productGroupContext.js +2 -0
  262. package/dist/svelte/widgets/productGroupContext.js.map +1 -0
  263. package/dist/svelte/widgets/subscriptionContext.d.ts +6 -0
  264. package/dist/svelte/widgets/subscriptionContext.d.ts.map +1 -0
  265. package/dist/svelte/widgets/subscriptionContext.js +2 -0
  266. package/dist/svelte/widgets/subscriptionContext.js.map +1 -0
  267. package/dist/svelte/widgets/types.d.ts +171 -0
  268. package/dist/svelte/widgets/types.d.ts.map +1 -0
  269. package/dist/svelte/widgets/types.js +2 -0
  270. package/dist/svelte/widgets/types.js.map +1 -0
  271. package/package.json +182 -0
  272. package/src/client/helpers.test.ts +139 -0
  273. package/src/client/helpers.ts +51 -0
  274. package/src/client/index.test.ts +1554 -0
  275. package/src/client/index.ts +1504 -0
  276. package/src/client/parsers.test.ts +1017 -0
  277. package/src/client/parsers.ts +182 -0
  278. package/src/client/polyfill.ts +2 -0
  279. package/src/component/_generated/api.ts +52 -0
  280. package/src/component/_generated/component.ts +619 -0
  281. package/src/component/_generated/dataModel.ts +60 -0
  282. package/src/component/_generated/server.ts +156 -0
  283. package/src/component/convex.config.ts +3 -0
  284. package/src/component/lib.test.ts +1359 -0
  285. package/src/component/lib.ts +726 -0
  286. package/src/component/schema.ts +112 -0
  287. package/src/component/util.test.ts +281 -0
  288. package/src/component/util.ts +228 -0
  289. package/src/core/catalog.test.ts +212 -0
  290. package/src/core/catalog.ts +119 -0
  291. package/src/core/index.ts +8 -0
  292. package/src/core/markdown.test.ts +43 -0
  293. package/src/core/markdown.ts +26 -0
  294. package/src/core/payments.test.ts +69 -0
  295. package/src/core/payments.ts +33 -0
  296. package/src/core/pendingCheckout.test.ts +44 -0
  297. package/src/core/pendingCheckout.ts +40 -0
  298. package/src/core/resolver.test.ts +283 -0
  299. package/src/core/resolver.ts +160 -0
  300. package/src/core/selectors.test.ts +119 -0
  301. package/src/core/selectors.ts +35 -0
  302. package/src/core/subscriptionUpdate.test.ts +164 -0
  303. package/src/core/subscriptionUpdate.ts +102 -0
  304. package/src/core/types.ts +220 -0
  305. package/src/design-system/README.md +40 -0
  306. package/src/design-system/base.css +27 -0
  307. package/src/design-system/colors/color-utils.ts +110 -0
  308. package/src/design-system/colors/config.ts +282 -0
  309. package/src/design-system/colors/index.ts +2 -0
  310. package/src/design-system/colors/utilities.css +2328 -0
  311. package/src/design-system/components/badges.css +65 -0
  312. package/src/design-system/components/buttons.css +256 -0
  313. package/src/design-system/components/dialog.css +218 -0
  314. package/src/design-system/components/icon-buttons.css +115 -0
  315. package/src/design-system/components/inputs.css +94 -0
  316. package/src/design-system/components/links.css +53 -0
  317. package/src/design-system/components/prose.css +67 -0
  318. package/src/design-system/components/segment-control.css +303 -0
  319. package/src/design-system/index.css +21 -0
  320. package/src/design-system/rounded/config.ts +91 -0
  321. package/src/design-system/rounded/index.ts +1 -0
  322. package/src/design-system/rounded/utilities.css +37 -0
  323. package/src/design-system/typography/config.ts +340 -0
  324. package/src/design-system/typography/index.ts +2 -0
  325. package/src/design-system/typography/tokens.ts +148 -0
  326. package/src/design-system/typography/utilities.css +728 -0
  327. package/src/library.css +20 -0
  328. package/src/react/hooks/useCheckoutSuccessParams.ts +7 -0
  329. package/src/react/index.tsx +47 -0
  330. package/src/react/primitives/BillingGate.tsx +26 -0
  331. package/src/react/primitives/BillingToggle.tsx +29 -0
  332. package/src/react/primitives/CheckoutButton.tsx +47 -0
  333. package/src/react/primitives/CheckoutSuccessSummary.tsx +36 -0
  334. package/src/react/primitives/CustomerPortalButton.tsx +50 -0
  335. package/src/react/primitives/NumberInput.tsx +83 -0
  336. package/src/react/primitives/OneTimeCheckoutButton.tsx +27 -0
  337. package/src/react/primitives/OneTimePaymentStatusBadge.tsx +18 -0
  338. package/src/react/primitives/PaymentWarningBanner.tsx +33 -0
  339. package/src/react/primitives/PricingCard.tsx +421 -0
  340. package/src/react/primitives/PricingSection.tsx +129 -0
  341. package/src/react/primitives/ScheduledChangeBanner.tsx +52 -0
  342. package/src/react/primitives/SegmentControl.tsx +32 -0
  343. package/src/react/primitives/SegmentGroup.tsx +53 -0
  344. package/src/react/primitives/TrialLimitBanner.tsx +32 -0
  345. package/src/react/shared.ts +138 -0
  346. package/src/react/widgets/BillingPortal.tsx +56 -0
  347. package/src/react/widgets/ProductItem.tsx +26 -0
  348. package/src/react/widgets/ProductRoot.tsx +441 -0
  349. package/src/react/widgets/SubscriptionItem.tsx +71 -0
  350. package/src/react/widgets/SubscriptionRoot.tsx +759 -0
  351. package/src/react/widgets/index.ts +36 -0
  352. package/src/react/widgets/productGroupContext.ts +10 -0
  353. package/src/react/widgets/subscriptionContext.ts +10 -0
  354. package/src/react/widgets/types.ts +179 -0
  355. package/src/svelte/index.ts +43 -0
  356. package/src/svelte/primitives/BillingGate.svelte +28 -0
  357. package/src/svelte/primitives/BillingToggle.svelte +27 -0
  358. package/src/svelte/primitives/CheckoutButton.svelte +60 -0
  359. package/src/svelte/primitives/CheckoutSuccessSummary.svelte +34 -0
  360. package/src/svelte/primitives/CustomerPortalButton.svelte +60 -0
  361. package/src/svelte/primitives/NumberInput.svelte +71 -0
  362. package/src/svelte/primitives/OneTimeCheckoutButton.svelte +37 -0
  363. package/src/svelte/primitives/OneTimePaymentStatusBadge.svelte +20 -0
  364. package/src/svelte/primitives/PaymentWarningBanner.svelte +30 -0
  365. package/src/svelte/primitives/PricingCard.svelte +356 -0
  366. package/src/svelte/primitives/PricingSection.svelte +121 -0
  367. package/src/svelte/primitives/ScheduledChangeBanner.svelte +46 -0
  368. package/src/svelte/primitives/SegmentControl.svelte +38 -0
  369. package/src/svelte/primitives/SegmentGroup.svelte +52 -0
  370. package/src/svelte/primitives/TrialLimitBanner.svelte +32 -0
  371. package/src/svelte/primitives/shared.ts +113 -0
  372. package/src/svelte/svelte.d.ts +6 -0
  373. package/src/svelte/widgets/BillingPortal.svelte +55 -0
  374. package/src/svelte/widgets/Product.svelte +35 -0
  375. package/src/svelte/widgets/ProductRoot.svelte +428 -0
  376. package/src/svelte/widgets/Subscription.svelte +52 -0
  377. package/src/svelte/widgets/SubscriptionRoot.svelte +690 -0
  378. package/src/svelte/widgets/index.ts +36 -0
  379. package/src/svelte/widgets/productGroupContext.ts +7 -0
  380. package/src/svelte/widgets/subscriptionContext.ts +7 -0
  381. package/src/svelte/widgets/types.ts +179 -0
  382. package/src/tailwind.css +6 -0
  383. package/src/test.ts +18 -0
@@ -0,0 +1,759 @@
1
+ import {
2
+ useState,
3
+ useCallback,
4
+ useMemo,
5
+ useEffect,
6
+ useRef,
7
+ type PropsWithChildren,
8
+ } from "react";
9
+ import { useQuery, useConvex } from "convex/react";
10
+ import { Dialog } from "@ark-ui/react/dialog";
11
+ import { Portal } from "@ark-ui/react/portal";
12
+
13
+ import { PricingSection } from "../primitives/PricingSection.js";
14
+ import { PaymentWarningBanner } from "../primitives/PaymentWarningBanner.js";
15
+ import { ScheduledChangeBanner } from "../primitives/ScheduledChangeBanner.js";
16
+
17
+ import { SubscriptionContext } from "./subscriptionContext.js";
18
+ import { pendingCheckout } from "../../core/pendingCheckout.js";
19
+
20
+ import type {
21
+ UIPlanEntry,
22
+ RecurringCycle,
23
+ UpdateBehavior,
24
+ } from "../../core/types.js";
25
+ import { buildUpdateSummary } from "../../core/subscriptionUpdate.js";
26
+ import { formatPriceWithInterval, formatSeatPrice } from "../shared.js";
27
+ import type {
28
+ BillingPermissions,
29
+ CheckoutIntent,
30
+ ConnectedBillingApi,
31
+ ConnectedBillingModel,
32
+ SubscriptionPlanRegistration,
33
+ } from "./types.js";
34
+
35
+ export const SubscriptionRoot = ({
36
+ api,
37
+ permissions,
38
+ className = "",
39
+ successUrl,
40
+ units,
41
+ showSeatPicker = false,
42
+ twoColumnLayout = false,
43
+ updateBehavior = "proration-charge-immediately",
44
+ onBeforeCheckout,
45
+ children,
46
+ }: PropsWithChildren<{
47
+ api: ConnectedBillingApi;
48
+ permissions?: BillingPermissions;
49
+ class?: string;
50
+ className?: string;
51
+ successUrl?: string;
52
+ units?: number;
53
+ showSeatPicker?: boolean;
54
+ twoColumnLayout?: boolean;
55
+ updateBehavior?: UpdateBehavior;
56
+ onBeforeCheckout?: (intent: CheckoutIntent) => Promise<boolean> | boolean;
57
+ }>) => {
58
+ const canChange = permissions?.canChangeSubscription !== false;
59
+ const canCancel = permissions?.canCancelSubscription !== false;
60
+ const canResume = permissions?.canResumeSubscription !== false;
61
+
62
+ const client = useConvex();
63
+
64
+ const billingUiModelRef = api.uiModel;
65
+ const checkoutLinkRef = api.checkouts.create;
66
+ const updateRef = api.subscriptions?.update;
67
+ const cancelRef = api.subscriptions?.cancel;
68
+ const resumeRef = api.subscriptions?.resume;
69
+
70
+ const modelRaw = useQuery(billingUiModelRef, {});
71
+ const model = (modelRaw ?? null) as ConnectedBillingModel | null;
72
+
73
+ const [selectedCycle, setSelectedCycle] =
74
+ useState<RecurringCycle>("every-month");
75
+ const [isActionLoading, setIsActionLoading] = useState(false);
76
+ const [actionError, setActionError] = useState<string | null>(null);
77
+ const [updateDialogOpen, setUpdateDialogOpen] = useState(false);
78
+ const [pendingUpdate, setPendingUpdate] = useState<
79
+ | {
80
+ kind: "plan-switch";
81
+ plan: UIPlanEntry;
82
+ productId: string;
83
+ units?: number;
84
+ }
85
+ | { kind: "seat-update"; units: number }
86
+ | null
87
+ >(null);
88
+ const [cancelDialogOpen, setCancelDialogOpen] = useState(false);
89
+ const [registeredPlans, setRegisteredPlans] = useState<
90
+ SubscriptionPlanRegistration[]
91
+ >([]);
92
+
93
+ const contextValue = useMemo(
94
+ () => ({
95
+ registerPlan: (plan: SubscriptionPlanRegistration) => {
96
+ setRegisteredPlans((prev) => [
97
+ ...prev.filter((c) => c.planId !== plan.planId),
98
+ plan,
99
+ ]);
100
+ return () => {
101
+ setRegisteredPlans((prev) =>
102
+ prev.filter((c) => c.planId !== plan.planId),
103
+ );
104
+ };
105
+ },
106
+ }),
107
+ [],
108
+ );
109
+
110
+ const allProducts = useMemo(
111
+ () => model?.allProducts ?? [],
112
+ [model?.allProducts],
113
+ );
114
+
115
+ const plans = useMemo<UIPlanEntry[]>(() => {
116
+ return registeredPlans.map((plan) => {
117
+ const productIds = plan.productIds ?? {};
118
+ const firstProductId = Object.values(productIds)[0];
119
+ const firstProduct = firstProductId
120
+ ? allProducts.find((p) => p.id === firstProductId)
121
+ : undefined;
122
+
123
+ const cycleKeys = Object.keys(productIds).filter(
124
+ (k): k is RecurringCycle => k !== "custom",
125
+ );
126
+
127
+ const entry: UIPlanEntry = {
128
+ planId: plan.planId,
129
+ category:
130
+ plan.type === "free"
131
+ ? "free"
132
+ : plan.type === "enterprise"
133
+ ? "enterprise"
134
+ : "paid",
135
+ billingType:
136
+ plan.type === "free" || plan.type === "enterprise"
137
+ ? "custom"
138
+ : "recurring",
139
+ pricingModel: plan.type === "seat-based" ? "seat" : "flat",
140
+ title:
141
+ plan.title ??
142
+ firstProduct?.name ??
143
+ plan.planId.charAt(0).toUpperCase() + plan.planId.slice(1),
144
+ description: plan.description ?? firstProduct?.description ?? undefined,
145
+ contactUrl: plan.contactUrl,
146
+ recommended: plan.recommended,
147
+ creemProductIds:
148
+ Object.keys(productIds).length > 0
149
+ ? (productIds as Record<string, string>)
150
+ : undefined,
151
+ };
152
+ if (cycleKeys.length > 0) {
153
+ entry.billingCycles = cycleKeys;
154
+ }
155
+ return entry;
156
+ });
157
+ }, [registeredPlans, allProducts]);
158
+
159
+ // Collect all product IDs that belong to plans in THIS component instance
160
+ const ownProductIds = useMemo(() => {
161
+ const ids = new Set<string>();
162
+ for (const plan of plans) {
163
+ if (plan.creemProductIds) {
164
+ for (const pid of Object.values(plan.creemProductIds)) {
165
+ if (pid) ids.add(pid);
166
+ }
167
+ }
168
+ }
169
+ return ids;
170
+ }, [plans]);
171
+
172
+ // Find the subscription from activeSubscriptions that belongs to THIS component
173
+ const matchedSubscription = useMemo(() => {
174
+ const subs = model?.activeSubscriptions;
175
+ if (!subs || ownProductIds.size === 0) return null;
176
+ return subs.find((s) => ownProductIds.has(s.productId)) ?? null;
177
+ }, [model?.activeSubscriptions, ownProductIds]);
178
+
179
+ const ownsActiveSubscription = matchedSubscription != null;
180
+ const localSubscriptionProductId = matchedSubscription?.productId ?? null;
181
+ const localCancelAtPeriodEnd =
182
+ matchedSubscription?.cancelAtPeriodEnd ?? false;
183
+ const localCurrentPeriodEnd = matchedSubscription?.currentPeriodEnd ?? null;
184
+ const localSubscriptionState = matchedSubscription?.status ?? null;
185
+ const localSubscribedSeats = matchedSubscription?.seats ?? null;
186
+
187
+ const snapshot = model?.billingSnapshot ?? null;
188
+
189
+ const canCheckout =
190
+ !model?.user && onBeforeCheckout != null
191
+ ? true
192
+ : permissions?.canCheckout !== false;
193
+
194
+ const canUpdateSeats =
195
+ !model?.user && onBeforeCheckout != null
196
+ ? true
197
+ : permissions?.canUpdateSeats !== false;
198
+
199
+ const activePlanId = useMemo(() => {
200
+ if (!model) return null;
201
+ const subProductId = localSubscriptionProductId;
202
+ if (subProductId) {
203
+ const matchedPlan = registeredPlans.find((plan) => {
204
+ const values = Object.values(plan.productIds ?? {}).filter(
205
+ Boolean,
206
+ ) as string[];
207
+ return values.includes(subProductId);
208
+ });
209
+ return matchedPlan?.planId ?? null;
210
+ }
211
+ if (model.user) {
212
+ const freePlan = plans.find((p) => p.category === "free");
213
+ if (freePlan) return freePlan.planId;
214
+ }
215
+ return null;
216
+ }, [model, localSubscriptionProductId, registeredPlans, plans]);
217
+
218
+ // Pending checkout resume after auth
219
+ const pendingCheckoutHandled = useRef(false);
220
+ useEffect(() => {
221
+ if (!model?.user || pendingCheckoutHandled.current) return;
222
+ pendingCheckoutHandled.current = true;
223
+ const pending = pendingCheckout.load();
224
+ if (!pending) return;
225
+ if ((model.activeSubscriptions ?? []).length > 0) {
226
+ pendingCheckout.clear();
227
+ return;
228
+ }
229
+ startCheckout(pending.productId, pending.units);
230
+ // eslint-disable-next-line react-hooks/exhaustive-deps
231
+ }, [model?.user]);
232
+
233
+ const getFallbackSuccessUrl = (): string | undefined => {
234
+ if (typeof window === "undefined") return undefined;
235
+ return `${window.location.origin}${window.location.pathname}`;
236
+ };
237
+
238
+ const getPreferredTheme = (): "light" | "dark" => {
239
+ if (typeof window === "undefined") return "light";
240
+ return window.matchMedia("(prefers-color-scheme: dark)").matches
241
+ ? "dark"
242
+ : "light";
243
+ };
244
+
245
+ const startCheckout = useCallback(
246
+ async (productId: string, checkoutUnits?: number) => {
247
+ if (onBeforeCheckout) {
248
+ const proceed = await onBeforeCheckout({
249
+ productId,
250
+ units: checkoutUnits,
251
+ });
252
+ if (!proceed) return;
253
+ }
254
+ setIsActionLoading(true);
255
+ setActionError(null);
256
+ try {
257
+ const { url } = await client.action(checkoutLinkRef, {
258
+ productId,
259
+ ...(successUrl ? { successUrl } : {}),
260
+ fallbackSuccessUrl: getFallbackSuccessUrl(),
261
+ theme: getPreferredTheme(),
262
+ ...(checkoutUnits != null ? { units: checkoutUnits } : {}),
263
+ });
264
+ // Suppress Convex client's beforeunload dialog during checkout redirect.
265
+ // Convex registers via addEventListener, so onbeforeunload=null has no effect.
266
+ // A capture-phase listener fires before non-capture listeners on the same target
267
+ // in modern browsers, and stopImmediatePropagation() blocks all subsequent handlers.
268
+ window.addEventListener(
269
+ "beforeunload",
270
+ (e) => {
271
+ e.stopImmediatePropagation();
272
+ },
273
+ { capture: true, once: true },
274
+ );
275
+ window.location.href = url;
276
+ window.location.href = url;
277
+ } catch (error) {
278
+ setActionError(
279
+ error instanceof Error ? error.message : "Checkout failed",
280
+ );
281
+ } finally {
282
+ setIsActionLoading(false);
283
+ }
284
+ },
285
+ [client, checkoutLinkRef, successUrl, onBeforeCheckout],
286
+ );
287
+
288
+ const handlePricingCheckout = useCallback(
289
+ async (payload: {
290
+ plan: UIPlanEntry;
291
+ productId: string;
292
+ units?: number;
293
+ }) => {
294
+ await startCheckout(payload.productId, payload.units);
295
+ },
296
+ [startCheckout],
297
+ );
298
+
299
+ const requestSwitchPlan = useCallback(
300
+ (payload: { plan: UIPlanEntry; productId: string; units?: number }) => {
301
+ setPendingUpdate({ kind: "plan-switch", ...payload });
302
+ setUpdateDialogOpen(true);
303
+ },
304
+ [],
305
+ );
306
+
307
+ const confirmUpdate = useCallback(async () => {
308
+ if (!updateRef || !pendingUpdate) return;
309
+ const update = pendingUpdate;
310
+ const subId = matchedSubscription?.id;
311
+ setUpdateDialogOpen(false);
312
+ setPendingUpdate(null);
313
+ setActionError(null);
314
+ try {
315
+ if (update.kind === "plan-switch") {
316
+ await client.mutation(
317
+ updateRef,
318
+ {
319
+ productId: update.productId,
320
+ ...(subId ? { subscriptionId: subId } : {}),
321
+ updateBehavior,
322
+ },
323
+ {
324
+ optimisticUpdate: (store) => {
325
+ const current = store.getQuery(billingUiModelRef, {});
326
+ if (current) {
327
+ const m = current as ConnectedBillingModel;
328
+ store.setQuery(
329
+ billingUiModelRef,
330
+ {},
331
+ {
332
+ ...m,
333
+ activeSubscriptions: (m.activeSubscriptions ?? []).map(
334
+ (s) =>
335
+ ownProductIds.has(s.productId)
336
+ ? { ...s, productId: update.productId }
337
+ : s,
338
+ ),
339
+ },
340
+ );
341
+ }
342
+ },
343
+ },
344
+ );
345
+ } else {
346
+ await client.mutation(
347
+ updateRef,
348
+ {
349
+ units: update.units,
350
+ ...(subId ? { subscriptionId: subId } : {}),
351
+ updateBehavior,
352
+ },
353
+ {
354
+ optimisticUpdate: (store) => {
355
+ const current = store.getQuery(billingUiModelRef, {});
356
+ if (current) {
357
+ const m = current as ConnectedBillingModel;
358
+ store.setQuery(
359
+ billingUiModelRef,
360
+ {},
361
+ {
362
+ ...m,
363
+ activeSubscriptions: (m.activeSubscriptions ?? []).map(
364
+ (s) =>
365
+ s.id === subId ? { ...s, seats: update.units } : s,
366
+ ),
367
+ },
368
+ );
369
+ }
370
+ },
371
+ },
372
+ );
373
+ }
374
+ } catch (error) {
375
+ setActionError(
376
+ error instanceof Error
377
+ ? error.message
378
+ : update.kind === "plan-switch"
379
+ ? "Switch failed"
380
+ : "Seat update failed",
381
+ );
382
+ }
383
+ }, [
384
+ updateRef,
385
+ pendingUpdate,
386
+ matchedSubscription,
387
+ client,
388
+ billingUiModelRef,
389
+ ownProductIds,
390
+ updateBehavior,
391
+ ]);
392
+
393
+ const handleUpdateSeats = useCallback((payload: { units: number }) => {
394
+ setPendingUpdate({ kind: "seat-update", units: payload.units });
395
+ setUpdateDialogOpen(true);
396
+ }, []);
397
+
398
+ const updateSummary = useMemo(() => {
399
+ if (!pendingUpdate) return null;
400
+
401
+ if (pendingUpdate.kind === "plan-switch") {
402
+ const currentPlan = plans.find((p) => {
403
+ const pids = p.creemProductIds ? Object.values(p.creemProductIds) : [];
404
+ return (
405
+ localSubscriptionProductId != null &&
406
+ pids.includes(localSubscriptionProductId)
407
+ );
408
+ });
409
+ const currentTitle = currentPlan?.title ?? "Current plan";
410
+ const currentPrice = formatPriceWithInterval(
411
+ localSubscriptionProductId ?? undefined,
412
+ allProducts,
413
+ );
414
+ const newPrice = formatPriceWithInterval(
415
+ pendingUpdate.productId,
416
+ allProducts,
417
+ );
418
+
419
+ return buildUpdateSummary({
420
+ kind: "plan-switch",
421
+ updateBehavior,
422
+ currentLabel: currentPrice
423
+ ? `${currentTitle} \u00b7 ${currentPrice}`
424
+ : currentTitle,
425
+ newLabel: newPrice
426
+ ? `${pendingUpdate.plan.title ?? "New plan"} \u00b7 ${newPrice}`
427
+ : (pendingUpdate.plan.title ?? "New plan"),
428
+ currentPeriodEnd: matchedSubscription?.currentPeriodEnd,
429
+ isTrialing: matchedSubscription?.status === "trialing",
430
+ trialEnd: matchedSubscription?.trialEnd,
431
+ });
432
+ }
433
+
434
+ const currentSeats = localSubscribedSeats ?? 1;
435
+ const currentPrice = formatSeatPrice(
436
+ localSubscriptionProductId ?? undefined,
437
+ allProducts,
438
+ currentSeats,
439
+ );
440
+ const newPrice = formatSeatPrice(
441
+ localSubscriptionProductId ?? undefined,
442
+ allProducts,
443
+ pendingUpdate.units,
444
+ );
445
+
446
+ return buildUpdateSummary({
447
+ kind: "seat-update",
448
+ updateBehavior,
449
+ currentLabel:
450
+ currentPrice ?? `${currentSeats} seat${currentSeats !== 1 ? "s" : ""}`,
451
+ newLabel:
452
+ newPrice ??
453
+ `${pendingUpdate.units} seat${pendingUpdate.units !== 1 ? "s" : ""}`,
454
+ currentPeriodEnd: matchedSubscription?.currentPeriodEnd,
455
+ isTrialing: matchedSubscription?.status === "trialing",
456
+ trialEnd: matchedSubscription?.trialEnd,
457
+ });
458
+ }, [
459
+ pendingUpdate,
460
+ plans,
461
+ localSubscriptionProductId,
462
+ allProducts,
463
+ localSubscribedSeats,
464
+ updateBehavior,
465
+ matchedSubscription,
466
+ ]);
467
+
468
+ const confirmCancelSubscription = useCallback(async () => {
469
+ if (!cancelRef) return;
470
+ const subId = matchedSubscription?.id;
471
+ setCancelDialogOpen(false);
472
+ setActionError(null);
473
+ try {
474
+ await client.mutation(
475
+ cancelRef,
476
+ {
477
+ ...(subId ? { subscriptionId: subId } : {}),
478
+ },
479
+ {
480
+ optimisticUpdate: (store) => {
481
+ const current = store.getQuery(billingUiModelRef, {});
482
+ if (current) {
483
+ const m = current as ConnectedBillingModel;
484
+ store.setQuery(
485
+ billingUiModelRef,
486
+ {},
487
+ {
488
+ ...m,
489
+ activeSubscriptions: (m.activeSubscriptions ?? []).map((s) =>
490
+ ownProductIds.has(s.productId)
491
+ ? { ...s, cancelAtPeriodEnd: true }
492
+ : s,
493
+ ),
494
+ },
495
+ );
496
+ }
497
+ },
498
+ },
499
+ );
500
+ } catch (error) {
501
+ setActionError(error instanceof Error ? error.message : "Cancel failed");
502
+ }
503
+ }, [
504
+ cancelRef,
505
+ matchedSubscription,
506
+ client,
507
+ billingUiModelRef,
508
+ ownProductIds,
509
+ ]);
510
+
511
+ const resumeSubscription = useCallback(async () => {
512
+ if (!resumeRef) return;
513
+ const subId = matchedSubscription?.id;
514
+ setActionError(null);
515
+ try {
516
+ await client.mutation(
517
+ resumeRef,
518
+ {
519
+ ...(subId ? { subscriptionId: subId } : {}),
520
+ },
521
+ {
522
+ optimisticUpdate: (store) => {
523
+ const current = store.getQuery(billingUiModelRef, {});
524
+ if (current) {
525
+ const m = current as ConnectedBillingModel;
526
+ store.setQuery(
527
+ billingUiModelRef,
528
+ {},
529
+ {
530
+ ...m,
531
+ activeSubscriptions: (m.activeSubscriptions ?? []).map((s) =>
532
+ ownProductIds.has(s.productId)
533
+ ? { ...s, cancelAtPeriodEnd: false, status: "active" }
534
+ : s,
535
+ ),
536
+ },
537
+ );
538
+ }
539
+ },
540
+ },
541
+ );
542
+ } catch (error) {
543
+ setActionError(error instanceof Error ? error.message : "Resume failed");
544
+ }
545
+ }, [
546
+ resumeRef,
547
+ matchedSubscription,
548
+ client,
549
+ billingUiModelRef,
550
+ ownProductIds,
551
+ ]);
552
+
553
+ const openCancelDialog = useCallback(() => {
554
+ setCancelDialogOpen(true);
555
+ }, []);
556
+
557
+ return (
558
+ <SubscriptionContext.Provider value={contextValue}>
559
+ <div className="hidden" aria-hidden="true">
560
+ {children}
561
+ </div>
562
+
563
+ <section className={`space-y-4 ${className}`}>
564
+ {actionError && (
565
+ <div className="rounded-lg border border-red-300 bg-red-50 px-3 py-2 text-sm text-red-700">
566
+ {actionError}
567
+ </div>
568
+ )}
569
+
570
+ {!model ? (
571
+ <p className="text-sm text-zinc-500">Loading billing model…</p>
572
+ ) : (
573
+ <>
574
+ {ownsActiveSubscription && snapshot && (
575
+ <ScheduledChangeBanner
576
+ snapshot={{
577
+ ...snapshot,
578
+ metadata: {
579
+ ...snapshot.metadata,
580
+ cancelAtPeriodEnd: localCancelAtPeriodEnd,
581
+ currentPeriodEnd: localCurrentPeriodEnd,
582
+ },
583
+ }}
584
+ isLoading={isActionLoading}
585
+ onResume={
586
+ resumeRef && canResume ? resumeSubscription : undefined
587
+ }
588
+ />
589
+ )}
590
+ <PaymentWarningBanner snapshot={snapshot} />
591
+
592
+ <PricingSection
593
+ plans={plans}
594
+ snapshot={snapshot ? { ...snapshot, activePlanId } : null}
595
+ selectedCycle={selectedCycle}
596
+ products={allProducts}
597
+ subscriptionProductId={localSubscriptionProductId}
598
+ subscriptionStatus={localSubscriptionState}
599
+ subscriptionTrialEnd={matchedSubscription?.trialEnd ?? null}
600
+ units={units}
601
+ showSeatPicker={showSeatPicker}
602
+ twoColumnLayout={twoColumnLayout}
603
+ subscribedSeats={localSubscribedSeats}
604
+ isGroupSubscribed={ownsActiveSubscription}
605
+ onCycleChange={setSelectedCycle}
606
+ disableCheckout={!canCheckout}
607
+ disableSwitch={!canChange}
608
+ disableSeats={!canUpdateSeats}
609
+ onCheckout={canCheckout ? handlePricingCheckout : undefined}
610
+ onSwitchPlan={
611
+ updateRef && canChange ? requestSwitchPlan : undefined
612
+ }
613
+ onUpdateSeats={
614
+ updateRef && canUpdateSeats ? handleUpdateSeats : undefined
615
+ }
616
+ onCancelSubscription={
617
+ cancelRef &&
618
+ canCancel &&
619
+ ownsActiveSubscription &&
620
+ !localCancelAtPeriodEnd
621
+ ? openCancelDialog
622
+ : undefined
623
+ }
624
+ />
625
+
626
+ <div className="flex flex-wrap items-center gap-3">{children}</div>
627
+
628
+ {/* Cancel Dialog */}
629
+ <Dialog.Root
630
+ open={cancelDialogOpen}
631
+ onOpenChange={(details: { open: boolean }) =>
632
+ setCancelDialogOpen(details.open)
633
+ }
634
+ >
635
+ <Portal>
636
+ <Dialog.Backdrop className="dialog-backdrop" />
637
+ <Dialog.Positioner className="dialog-positioner">
638
+ <Dialog.Content className="dialog-content">
639
+ <Dialog.CloseTrigger
640
+ className="icon-button-ghost-sm absolute right-2 top-2"
641
+ aria-label="Close dialog"
642
+ >
643
+ <svg
644
+ aria-hidden="true"
645
+ viewBox="0 0 24 24"
646
+ fill="none"
647
+ className="h-4 w-4"
648
+ >
649
+ <path
650
+ d="M18 6L6 18M6 6L18 18"
651
+ stroke="currentColor"
652
+ strokeWidth="1.5"
653
+ strokeLinecap="round"
654
+ strokeLinejoin="round"
655
+ />
656
+ </svg>
657
+ </Dialog.CloseTrigger>
658
+ <Dialog.Title className="dialog-title">
659
+ Cancel subscription?
660
+ </Dialog.Title>
661
+ <Dialog.Description className="dialog-description">
662
+ Are you sure you want to cancel your subscription? You
663
+ will continue to have access until the end of your current
664
+ billing period.
665
+ </Dialog.Description>
666
+ <div className="dialog-actions">
667
+ <button
668
+ type="button"
669
+ className="dialog-action-danger"
670
+ onClick={confirmCancelSubscription}
671
+ >
672
+ Yes, cancel
673
+ </button>
674
+ <Dialog.CloseTrigger className="button-faded h-8 w-full">
675
+ Keep subscription
676
+ </Dialog.CloseTrigger>
677
+ </div>
678
+ </Dialog.Content>
679
+ </Dialog.Positioner>
680
+ </Portal>
681
+ </Dialog.Root>
682
+
683
+ {/* Update Confirmation Dialog */}
684
+ <Dialog.Root
685
+ open={updateDialogOpen}
686
+ onOpenChange={(details: { open: boolean }) => {
687
+ setUpdateDialogOpen(details.open);
688
+ if (!details.open) setPendingUpdate(null);
689
+ }}
690
+ >
691
+ <Portal>
692
+ <Dialog.Backdrop className="dialog-backdrop" />
693
+ <Dialog.Positioner className="dialog-positioner">
694
+ <Dialog.Content className="dialog-content">
695
+ <Dialog.CloseTrigger
696
+ className="icon-button-ghost-sm absolute right-2 top-2"
697
+ aria-label="Close dialog"
698
+ >
699
+ <svg
700
+ aria-hidden="true"
701
+ viewBox="0 0 24 24"
702
+ fill="none"
703
+ className="h-4 w-4"
704
+ >
705
+ <path
706
+ d="M18 6L6 18M6 6L18 18"
707
+ stroke="currentColor"
708
+ strokeWidth="1.5"
709
+ strokeLinecap="round"
710
+ strokeLinejoin="round"
711
+ />
712
+ </svg>
713
+ </Dialog.CloseTrigger>
714
+ <Dialog.Title className="dialog-title">
715
+ {updateSummary?.title}
716
+ </Dialog.Title>
717
+ {updateSummary && (
718
+ <>
719
+ <div className="my-3 flex flex-col gap-1 rounded-lg bg-surface-subtle px-3 py-2.5">
720
+ <span className="label-m text-foreground-muted">
721
+ {updateSummary.currentLabel}
722
+ </span>
723
+ <span className="body-s text-foreground-placeholder">
724
+ {"\u2192"}
725
+ </span>
726
+ <span className="label-m text-foreground-default">
727
+ {updateSummary.newLabel}
728
+ </span>
729
+ </div>
730
+ <Dialog.Description className="dialog-description">
731
+ {updateSummary.description}
732
+ {updateSummary.dateNote && (
733
+ <> {updateSummary.dateNote}</>
734
+ )}
735
+ </Dialog.Description>
736
+ </>
737
+ )}
738
+ <div className="dialog-actions">
739
+ <button
740
+ type="button"
741
+ className="button-filled h-8 w-full"
742
+ onClick={confirmUpdate}
743
+ >
744
+ {updateSummary?.confirmLabel ?? "Confirm"}
745
+ </button>
746
+ <Dialog.CloseTrigger className="button-faded h-8 w-full">
747
+ Cancel
748
+ </Dialog.CloseTrigger>
749
+ </div>
750
+ </Dialog.Content>
751
+ </Dialog.Positioner>
752
+ </Portal>
753
+ </Dialog.Root>
754
+ </>
755
+ )}
756
+ </section>
757
+ </SubscriptionContext.Provider>
758
+ );
759
+ };