@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,113 @@
1
+ import type {
2
+ AvailableAction,
3
+ BillingSnapshot,
4
+ UIPlanEntry,
5
+ RecurringCycle,
6
+ } from "../../core/types.js";
7
+ import type { ConnectedProduct } from "../widgets/types.js";
8
+
9
+ const CYCLE_KEY_ALIASES: Record<RecurringCycle, string[]> = {
10
+ "every-month": ["every-month", "monthly", "month"],
11
+ "every-three-months": ["every-three-months", "quarterly", "every-quarter"],
12
+ "every-six-months": ["every-six-months", "semiannual", "semi-annually"],
13
+ "every-year": ["every-year", "yearly", "annual"],
14
+ custom: ["custom"],
15
+ };
16
+
17
+ export const formatRecurringCycle = (cycle: RecurringCycle) => {
18
+ if (cycle === "every-month") return "Monthly";
19
+ if (cycle === "every-three-months") return "Quarterly";
20
+ if (cycle === "every-six-months") return "Semi-annual";
21
+ if (cycle === "every-year") return "Yearly";
22
+ return "Custom";
23
+ };
24
+
25
+ export const resolveProductIdForPlan = (
26
+ plan: UIPlanEntry,
27
+ selectedCycle: RecurringCycle | undefined,
28
+ ) => {
29
+ const productIds = plan.creemProductIds;
30
+ if (!productIds) {
31
+ return undefined;
32
+ }
33
+
34
+ const aliases = selectedCycle
35
+ ? CYCLE_KEY_ALIASES[selectedCycle]
36
+ : CYCLE_KEY_ALIASES.custom;
37
+ for (const alias of aliases) {
38
+ if (productIds[alias]) {
39
+ return productIds[alias];
40
+ }
41
+ const partial = Object.entries(productIds).find(([key]) =>
42
+ key.toLowerCase().includes(alias),
43
+ );
44
+ if (partial) {
45
+ return partial[1];
46
+ }
47
+ }
48
+
49
+ return Object.values(productIds)[0];
50
+ };
51
+
52
+ export const hasBillingActionLocal = (
53
+ snapshot: BillingSnapshot,
54
+ action: AvailableAction,
55
+ ) => snapshot.availableActions.includes(action);
56
+
57
+ export const formatPrice = (amount: number, currency: string): string => {
58
+ return new Intl.NumberFormat(undefined, {
59
+ style: "currency",
60
+ currency,
61
+ minimumFractionDigits: 0,
62
+ maximumFractionDigits: 2,
63
+ }).format(amount / 100);
64
+ };
65
+
66
+ export const resolveProductPrice = (
67
+ productId: string | undefined,
68
+ products: ConnectedProduct[],
69
+ ): { formatted: string; interval?: string } | null => {
70
+ if (!productId || !products.length) return null;
71
+ const product = products.find((p) => p.id === productId);
72
+ if (!product) return null;
73
+ if (product.price == null || !product.currency) return null;
74
+ const formatted = formatPrice(product.price, product.currency);
75
+ return { formatted, interval: product.billingPeriod ?? undefined };
76
+ };
77
+
78
+ const INTERVAL_LABELS: Record<string, string> = {
79
+ month: "/mo",
80
+ "every-month": "/mo",
81
+ "every-three-months": "/3mo",
82
+ "every-six-months": "/6mo",
83
+ year: "/yr",
84
+ "every-year": "/yr",
85
+ };
86
+
87
+ export const formatSeatPrice = (
88
+ productId: string | undefined,
89
+ products: ConnectedProduct[],
90
+ seats: number,
91
+ ): string | null => {
92
+ const resolved = resolveProductPrice(productId, products);
93
+ if (!resolved) return null;
94
+ const suffix = resolved.interval
95
+ ? (INTERVAL_LABELS[resolved.interval] ?? "")
96
+ : "";
97
+ if (seats <= 1) {
98
+ return `${resolved.formatted}${suffix}`;
99
+ }
100
+ return `${resolved.formatted}${suffix} × ${seats} seats`;
101
+ };
102
+
103
+ export const formatPriceWithInterval = (
104
+ productId: string | undefined,
105
+ products: ConnectedProduct[],
106
+ ): string | null => {
107
+ const resolved = resolveProductPrice(productId, products);
108
+ if (!resolved) return null;
109
+ const suffix = resolved.interval
110
+ ? (INTERVAL_LABELS[resolved.interval] ?? "")
111
+ : "";
112
+ return `${resolved.formatted}${suffix}`;
113
+ };
@@ -0,0 +1,6 @@
1
+ declare module "*.svelte" {
2
+ import type { Component } from "svelte";
3
+
4
+ const SvelteComponent: Component<any>;
5
+ export default SvelteComponent;
6
+ }
@@ -0,0 +1,55 @@
1
+ <script lang="ts">
2
+ import { useConvexClient, useQuery } from "convex-svelte";
3
+ import CustomerPortalButton from "../primitives/CustomerPortalButton.svelte";
4
+ import type { BillingPermissions, ConnectedBillingApi, ConnectedBillingModel } from "./types.js";
5
+ import type { Snippet } from "svelte";
6
+
7
+ interface Props {
8
+ api: ConnectedBillingApi;
9
+ permissions?: BillingPermissions;
10
+ class?: string;
11
+ children?: Snippet;
12
+ }
13
+
14
+ let { api, permissions = undefined, class: className = "", children }: Props = $props();
15
+
16
+ const canAccess = $derived(permissions?.canAccessPortal !== false);
17
+
18
+ const client = useConvexClient();
19
+
20
+ // svelte-ignore state_referenced_locally
21
+ const billingUiModelRef = api.uiModel;
22
+ // svelte-ignore state_referenced_locally
23
+ const portalUrlRef = api.customers?.portalUrl;
24
+
25
+ const billingModelQuery = useQuery(billingUiModelRef, {});
26
+ const model = $derived(billingModelQuery.data as ConnectedBillingModel | undefined);
27
+ const hasCreemCustomer = $derived(model?.hasCreemCustomer ?? false);
28
+
29
+ let isLoading = $state(false);
30
+
31
+ const openPortal = async () => {
32
+ if (!portalUrlRef) return;
33
+ isLoading = true;
34
+ try {
35
+ const { url } = await client.action(portalUrlRef, {});
36
+ window.open(url, "_blank", "noopener,noreferrer");
37
+ } finally {
38
+ isLoading = false;
39
+ }
40
+ };
41
+ </script>
42
+
43
+ {#if portalUrlRef && hasCreemCustomer && canAccess}
44
+ <CustomerPortalButton
45
+ disabled={isLoading}
46
+ onOpenPortal={openPortal}
47
+ {className}
48
+ >
49
+ {#if children}
50
+ {@render children()}
51
+ {:else}
52
+ Manage billing
53
+ {/if}
54
+ </CustomerPortalButton>
55
+ {/if}
@@ -0,0 +1,35 @@
1
+ <script lang="ts">
2
+ import { getContext, untrack } from "svelte";
3
+ import {
4
+ PRODUCT_GROUP_CONTEXT_KEY,
5
+ type ProductGroupContextValue,
6
+ } from "./productGroupContext.js";
7
+ import type { ProductType } from "./types.js";
8
+
9
+ interface Props {
10
+ productId: string;
11
+ type: ProductType;
12
+ title?: string;
13
+ description?: string;
14
+ }
15
+
16
+ let {
17
+ productId,
18
+ type,
19
+ title = undefined,
20
+ description = undefined,
21
+ }: Props = $props();
22
+
23
+ // Must be used inside a <Product.Root>
24
+ const rootContext = getContext<ProductGroupContextValue | undefined>(
25
+ PRODUCT_GROUP_CONTEXT_KEY,
26
+ );
27
+
28
+ if (rootContext) {
29
+ $effect(() => {
30
+ const registration = { productId, type, title, description };
31
+ const unregister = untrack(() => rootContext.registerItem(registration));
32
+ return () => untrack(unregister);
33
+ });
34
+ }
35
+ </script>
@@ -0,0 +1,428 @@
1
+ <script lang="ts">
2
+ import { setContext, untrack } from "svelte";
3
+ import { useConvexClient, useQuery } from "convex-svelte";
4
+ import CheckoutButton from "../primitives/CheckoutButton.svelte";
5
+ import { formatPriceWithInterval } from "../primitives/shared.js";
6
+ import {
7
+ PRODUCT_GROUP_CONTEXT_KEY,
8
+ type ProductGroupContextValue,
9
+ } from "./productGroupContext.js";
10
+ import type {
11
+ BillingPermissions,
12
+ CheckoutIntent,
13
+ ConnectedBillingApi,
14
+ ConnectedBillingModel,
15
+ ProductItemRegistration,
16
+ Transition,
17
+ } from "./types.js";
18
+ import { SvelteSet } from "svelte/reactivity";
19
+ import { renderMarkdown } from "../../core/markdown.js";
20
+ import { pendingCheckout } from "../../core/pendingCheckout.js";
21
+
22
+ interface Props {
23
+ api: ConnectedBillingApi;
24
+ permissions?: BillingPermissions;
25
+ transition?: Transition[];
26
+ class?: string;
27
+ layout?: "default" | "single";
28
+ styleVariant?: "legacy" | "pricing";
29
+ showImages?: boolean;
30
+ pricingCtaVariant?: "filled" | "faded";
31
+ successUrl?: string;
32
+ onBeforeCheckout?: (intent: CheckoutIntent) => Promise<boolean> | boolean;
33
+ children?: import("svelte").Snippet;
34
+ }
35
+
36
+ let {
37
+ api,
38
+ permissions = undefined,
39
+ transition = [],
40
+ class: className = "",
41
+ layout = "default",
42
+ styleVariant = "legacy",
43
+ showImages = false,
44
+ pricingCtaVariant = "faded",
45
+ successUrl = undefined,
46
+ onBeforeCheckout = undefined,
47
+ children,
48
+ }: Props = $props();
49
+
50
+ const client = useConvexClient();
51
+
52
+ // svelte-ignore state_referenced_locally
53
+ const billingUiModelRef = api.uiModel;
54
+ // svelte-ignore state_referenced_locally
55
+ const checkoutLinkRef = api.checkouts.create;
56
+
57
+ const billingModelQuery = useQuery(billingUiModelRef, {});
58
+
59
+ let registeredItems = $state<ProductItemRegistration[]>([]);
60
+ let isLoading = $state(false);
61
+ let error = $state<string | null>(null);
62
+
63
+ const contextValue: ProductGroupContextValue = {
64
+ registerItem: (item) => {
65
+ registeredItems = [
66
+ ...registeredItems.filter(
67
+ (candidate) => candidate.productId !== item.productId,
68
+ ),
69
+ item,
70
+ ];
71
+ return () => {
72
+ registeredItems = registeredItems.filter(
73
+ (candidate) => candidate.productId !== item.productId,
74
+ );
75
+ };
76
+ },
77
+ };
78
+
79
+ setContext(PRODUCT_GROUP_CONTEXT_KEY, contextValue);
80
+
81
+ const model = $derived(
82
+ (billingModelQuery.data ?? null) as ConnectedBillingModel | null,
83
+ );
84
+ const canCheckout = $derived(
85
+ !model?.user && onBeforeCheckout != null
86
+ ? true
87
+ : permissions?.canCheckout !== false,
88
+ );
89
+ const allProducts = $derived(model?.allProducts ?? []);
90
+ const rawOwnedProductIds = $derived(model?.ownedProductIds ?? []);
91
+
92
+ $effect(() => {
93
+ if (!model?.user) return;
94
+ untrack(() => {
95
+ const pending = pendingCheckout.load();
96
+ if (!pending) return;
97
+ if ((model!.ownedProductIds ?? []).includes(pending.productId)) {
98
+ pendingCheckout.clear();
99
+ return;
100
+ }
101
+ startCheckout(pending.productId);
102
+ });
103
+ });
104
+
105
+ // Resolve effective ownership by applying transition rules.
106
+ // If the user purchased a "via_product" (upgrade delta), they effectively
107
+ // own the transition target ('to') and no longer just the source ('from').
108
+ const effectiveOwnedProductIds = $derived.by<string[]>(() => {
109
+ const effective = new SvelteSet(rawOwnedProductIds);
110
+ for (const rule of transition) {
111
+ if (rule.kind === "via_product" && effective.has(rule.viaProductId)) {
112
+ effective.add(rule.to);
113
+ effective.delete(rule.from);
114
+ }
115
+ }
116
+ return [...effective];
117
+ });
118
+
119
+ const activeOwnedProductId = $derived(
120
+ registeredItems.find((item) =>
121
+ effectiveOwnedProductIds.includes(item.productId),
122
+ )?.productId ?? null,
123
+ );
124
+
125
+ // Determine if a product is a lower tier than the target by traversing the
126
+ // transition graph (from → to edges). Returns true if there is a path from
127
+ // `productId` to `targetId`, meaning `productId` is a lower tier.
128
+ const isLowerTierThan = (productId: string, targetId: string): boolean => {
129
+ const visited = new SvelteSet<string>();
130
+ const queue = [productId];
131
+ while (queue.length > 0) {
132
+ const current = queue.shift()!;
133
+ if (visited.has(current)) continue;
134
+ visited.add(current);
135
+ for (const rule of transition) {
136
+ if (rule.from === current) {
137
+ if (rule.to === targetId) return true;
138
+ queue.push(rule.to);
139
+ }
140
+ }
141
+ }
142
+ return false;
143
+ };
144
+
145
+ const resolveTransitionTarget = (
146
+ fromProductId: string,
147
+ toProductId: string,
148
+ ) =>
149
+ transition.find(
150
+ (rule) => rule.from === fromProductId && rule.to === toProductId,
151
+ );
152
+
153
+ const resolveCheckoutProductId = (toProductId: string) => {
154
+ if (!activeOwnedProductId) {
155
+ return toProductId;
156
+ }
157
+ const rule = resolveTransitionTarget(activeOwnedProductId, toProductId);
158
+ if (!rule) {
159
+ return null;
160
+ }
161
+ if (rule.kind === "via_product") {
162
+ return rule.viaProductId;
163
+ }
164
+ return toProductId;
165
+ };
166
+
167
+ const getFallbackSuccessUrl = (): string | undefined => {
168
+ if (typeof window === "undefined") return undefined;
169
+ return `${window.location.origin}${window.location.pathname}`;
170
+ };
171
+
172
+ const getPreferredTheme = (): "light" | "dark" => {
173
+ if (typeof window === "undefined") return "light";
174
+ return window.matchMedia("(prefers-color-scheme: dark)").matches
175
+ ? "dark"
176
+ : "light";
177
+ };
178
+
179
+ const startCheckout = async (checkoutProductId: string) => {
180
+ if (onBeforeCheckout) {
181
+ const proceed = await onBeforeCheckout({ productId: checkoutProductId });
182
+ if (!proceed) return;
183
+ }
184
+ isLoading = true;
185
+ error = null;
186
+ try {
187
+ const { url } = await client.action(checkoutLinkRef, {
188
+ productId: checkoutProductId,
189
+ ...(successUrl ? { successUrl } : {}),
190
+ fallbackSuccessUrl: getFallbackSuccessUrl(),
191
+ theme: getPreferredTheme(),
192
+ });
193
+ // Suppress Convex client's beforeunload dialog during checkout redirect.
194
+ // Convex registers via addEventListener, so onbeforeunload=null has no effect.
195
+ // A capture-phase listener fires before non-capture listeners on the same target
196
+ // in modern browsers, and stopImmediatePropagation() blocks all subsequent handlers.
197
+ window.addEventListener(
198
+ "beforeunload",
199
+ (e) => {
200
+ e.stopImmediatePropagation();
201
+ },
202
+ { capture: true, once: true },
203
+ );
204
+ window.location.href = url;
205
+ } catch (checkoutError) {
206
+ error =
207
+ checkoutError instanceof Error
208
+ ? checkoutError.message
209
+ : "Checkout failed";
210
+ } finally {
211
+ isLoading = false;
212
+ }
213
+ };
214
+
215
+ const splitPriceLabel = (
216
+ value: string | null,
217
+ ): { main: string; suffix: string | null; tail: string } | null => {
218
+ if (!value) return null;
219
+ const match = value.match(/^(.*?)(\/[a-z0-9]+)(.*)$/i);
220
+ if (!match) return { main: value, suffix: null, tail: "" };
221
+ return {
222
+ main: match[1]?.trim() ?? value,
223
+ suffix: match[2] ?? null,
224
+ tail: match[3]?.trim() ?? "",
225
+ };
226
+ };
227
+ </script>
228
+
229
+ <div class="hidden" aria-hidden="true">
230
+ {@render children?.()}
231
+ </div>
232
+
233
+ <section
234
+ class={`${styleVariant === "pricing" ? "space-y-0" : "space-y-3"} ${className}`}
235
+ >
236
+ {#if error}
237
+ <p class="text-sm text-red-600">{error}</p>
238
+ {/if}
239
+
240
+ <div
241
+ class={styleVariant === "pricing"
242
+ ? layout === "single"
243
+ ? "flex justify-center"
244
+ : "grid grid-cols-1 gap-1 lg:grid-cols-2"
245
+ : `flex gap-3 ${layout === "single" ? "justify-center" : "flex-wrap items-center"}`}
246
+ >
247
+ {#each registeredItems as item (item.productId)}
248
+ {@const isOwned = effectiveOwnedProductIds.includes(item.productId)}
249
+ {@const isIncluded =
250
+ !isOwned &&
251
+ activeOwnedProductId != null &&
252
+ isLowerTierThan(item.productId, activeOwnedProductId)}
253
+ {@const checkoutProductId = resolveCheckoutProductId(item.productId)}
254
+ {@const matchedProduct = allProducts.find((p) => p.id === item.productId)}
255
+ {@const resolvedTitle =
256
+ item.title ?? matchedProduct?.name ?? item.productId}
257
+ {@const resolvedDescription =
258
+ item.description ?? matchedProduct?.description}
259
+ {@const resolvedImageUrl = matchedProduct?.imageUrl}
260
+ {@const resolvedPrice = formatPriceWithInterval(
261
+ item.productId,
262
+ allProducts,
263
+ )}
264
+ {@const splitPrice = splitPriceLabel(resolvedPrice)}
265
+ {@const descriptionLines = (resolvedDescription ?? "")
266
+ .split(/\r?\n/)
267
+ .map((line) => line.trim())
268
+ .filter(Boolean)
269
+ .map((line) => line.replace(/^(?:[-*]\s+|[✔✓]\s*)/, "").trim())
270
+ .filter(Boolean)}
271
+ <article
272
+ class={styleVariant === "pricing"
273
+ ? `w-full ${layout === "single" ? "max-w-[680px]" : "max-w-none"} rounded-2xl bg-surface-base ${isIncluded ? "opacity-60" : ""}`
274
+ : `max-w-sm rounded-xl border p-4 shadow-sm ${isIncluded ? "border-zinc-100 bg-zinc-50 opacity-60 dark:border-zinc-800 dark:bg-zinc-900" : "border-zinc-200 bg-zinc-50 dark:border-zinc-800 dark:bg-zinc-950"}`}
275
+ >
276
+ {#if styleVariant === "pricing"}
277
+ {#if showImages && resolvedImageUrl}
278
+ <div class="p-1">
279
+ <img
280
+ src={resolvedImageUrl}
281
+ alt={resolvedTitle}
282
+ class="aspect-[16/9] w-full rounded-xl object-cover"
283
+ loading="lazy"
284
+ />
285
+ </div>
286
+ {/if}
287
+
288
+ <div class="px-6 pb-6 pt-6">
289
+ <div class="mb-3 flex min-h-6 items-center justify-between gap-2">
290
+ <h3 class="title-s text-foreground-default">{resolvedTitle}</h3>
291
+ {#if isOwned}
292
+ <span class="badge-faded-sm">Owned</span>
293
+ {:else if isIncluded}
294
+ <span class="badge-faded-sm">Included</span>
295
+ {/if}
296
+ </div>
297
+
298
+ {#if splitPrice}
299
+ <div class="flex items-baseline gap-1">
300
+ <span class="heading-s text-foreground-default"
301
+ >{splitPrice.main}</span
302
+ >
303
+ {#if splitPrice.suffix}
304
+ <span class="title-s text-foreground-placeholder"
305
+ >{splitPrice.suffix}</span
306
+ >
307
+ {/if}
308
+ {#if splitPrice.tail}
309
+ <span class="title-s text-foreground-placeholder"
310
+ >{splitPrice.tail}</span
311
+ >
312
+ {/if}
313
+ </div>
314
+ {/if}
315
+
316
+ <div class="mb-4 mt-6 flex min-h-8 items-start">
317
+ {#if checkoutProductId && !isOwned && !isIncluded}
318
+ <CheckoutButton
319
+ productId={checkoutProductId}
320
+ disabled={isLoading || !canCheckout}
321
+ onCheckout={() => startCheckout(checkoutProductId)}
322
+ className={`${pricingCtaVariant === "filled" ? "button-filled" : "button-faded"} w-full`}
323
+ >
324
+ {activeOwnedProductId ? "Upgrade" : "Buy now"}
325
+ </CheckoutButton>
326
+ {:else if !isOwned && !isIncluded}
327
+ <CheckoutButton
328
+ productId={item.productId}
329
+ disabled={isLoading || !canCheckout}
330
+ onCheckout={() => startCheckout(item.productId)}
331
+ className={`${pricingCtaVariant === "filled" ? "button-filled" : "button-faded"} w-full`}
332
+ >
333
+ Buy now
334
+ </CheckoutButton>
335
+ {/if}
336
+ </div>
337
+
338
+ {#if descriptionLines.length > 0}
339
+ <div class="w-full pt-4">
340
+ <p class="label-m mb-4 font-semibold text-foreground-default">What's included:</p>
341
+ <ul class="space-y-2">
342
+ {#each descriptionLines as feature (feature)}
343
+ <li class="flex items-center gap-2">
344
+ <span
345
+ class="inline-flex h-5 w-5 shrink-0 items-center justify-center text-foreground-muted"
346
+ >
347
+ <svg
348
+ aria-hidden="true"
349
+ viewBox="0 0 24 24"
350
+ fill="none"
351
+ class="h-4 w-4"
352
+ >
353
+ <path
354
+ d="M20 6L9 17L4 12"
355
+ stroke="currentColor"
356
+ stroke-width="2.5"
357
+ stroke-linecap="round"
358
+ stroke-linejoin="round"
359
+ />
360
+ </svg>
361
+ </span>
362
+ <span class="body-m text-foreground-default"
363
+ >{feature}</span
364
+ >
365
+ </li>
366
+ {/each}
367
+ </ul>
368
+ </div>
369
+ {/if}
370
+ </div>
371
+ {:else}
372
+ <h3 class="text-base font-semibold text-zinc-900 dark:text-zinc-100">
373
+ {resolvedTitle}
374
+ </h3>
375
+
376
+ {#if resolvedPrice}
377
+ <p
378
+ class={`mt-2 text-2xl font-bold ${isIncluded ? "text-zinc-400 dark:text-zinc-500" : "text-zinc-900 dark:text-zinc-100"}`}
379
+ >
380
+ {resolvedPrice}
381
+ </p>
382
+ {/if}
383
+
384
+ <div class="mt-4">
385
+ {#if isOwned}
386
+ <span
387
+ class="inline-flex rounded-md bg-emerald-100 px-3 py-2 text-sm font-medium text-emerald-700"
388
+ >
389
+ Owned
390
+ </span>
391
+ {:else if isIncluded}
392
+ <span
393
+ class="inline-flex rounded-md bg-zinc-100 px-3 py-2 text-sm font-medium text-zinc-500 dark:bg-zinc-800 dark:text-zinc-400"
394
+ >
395
+ Included
396
+ </span>
397
+ {:else if checkoutProductId}
398
+ <CheckoutButton
399
+ productId={checkoutProductId}
400
+ disabled={isLoading || !canCheckout}
401
+ onCheckout={() => startCheckout(checkoutProductId)}
402
+ >
403
+ {activeOwnedProductId ? "Upgrade" : "Buy now"}
404
+ </CheckoutButton>
405
+ {:else}
406
+ <CheckoutButton
407
+ productId={item.productId}
408
+ disabled={isLoading || !canCheckout}
409
+ onCheckout={() => startCheckout(item.productId)}
410
+ >
411
+ Buy now
412
+ </CheckoutButton>
413
+ {/if}
414
+ </div>
415
+
416
+ {#if resolvedDescription}
417
+ <div
418
+ class="creem-prose mt-4 text-sm text-zinc-600 dark:text-zinc-300"
419
+ >
420
+ <!-- eslint-disable-next-line svelte/no-at-html-tags — merchant-authored markdown from Creem -->
421
+ {@html renderMarkdown(resolvedDescription)}
422
+ </div>
423
+ {/if}
424
+ {/if}
425
+ </article>
426
+ {/each}
427
+ </div>
428
+ </section>
@@ -0,0 +1,52 @@
1
+ <script lang="ts">
2
+ import { getContext, untrack } from "svelte";
3
+ import type { RecurringCycle } from "../../core/types.js";
4
+ import {
5
+ SUBSCRIPTION_CONTEXT_KEY,
6
+ type SubscriptionContextValue,
7
+ } from "./subscriptionContext.js";
8
+ type BaseProps = {
9
+ planId?: string;
10
+ title?: string;
11
+ description?: string;
12
+ recommended?: boolean;
13
+ };
14
+
15
+ type Props =
16
+ | (BaseProps & { type: "free"; productIds?: undefined; contactUrl?: string })
17
+ | (BaseProps & { type: "single"; productIds: Partial<Record<RecurringCycle, string>>; contactUrl?: string })
18
+ | (BaseProps & { type: "seat-based"; productIds: Partial<Record<RecurringCycle, string>>; contactUrl?: string })
19
+ | (BaseProps & { type: "enterprise"; productIds?: undefined; contactUrl: string });
20
+
21
+ let {
22
+ planId = undefined,
23
+ type,
24
+ title = undefined,
25
+ description = undefined,
26
+ contactUrl = undefined,
27
+ recommended = undefined,
28
+ productIds = undefined,
29
+ }: Props = $props();
30
+
31
+ // Must be used inside a <Subscription.Root>
32
+ const rootContext = getContext<SubscriptionContextValue | undefined>(
33
+ SUBSCRIPTION_CONTEXT_KEY,
34
+ );
35
+
36
+ if (rootContext) {
37
+ $effect(() => {
38
+ const resolvedPlanId = planId ?? Object.values(productIds ?? {})[0] ?? type;
39
+ const registration = {
40
+ planId: resolvedPlanId,
41
+ type,
42
+ title,
43
+ description,
44
+ contactUrl,
45
+ recommended,
46
+ productIds,
47
+ };
48
+ const unregister = untrack(() => rootContext.registerPlan(registration));
49
+ return () => untrack(unregister);
50
+ });
51
+ }
52
+ </script>