@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.
- package/LICENSE +201 -0
- package/README.md +1176 -0
- package/dist/client/helpers.d.ts +17 -0
- package/dist/client/helpers.d.ts.map +1 -0
- package/dist/client/helpers.js +43 -0
- package/dist/client/helpers.js.map +1 -0
- package/dist/client/index.d.ts +1041 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +1068 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/parsers.d.ts +45 -0
- package/dist/client/parsers.d.ts.map +1 -0
- package/dist/client/parsers.js +138 -0
- package/dist/client/parsers.js.map +1 -0
- package/dist/client/polyfill.d.ts +2 -0
- package/dist/client/polyfill.d.ts.map +1 -0
- package/dist/client/polyfill.js +3 -0
- package/dist/client/polyfill.js.map +1 -0
- package/dist/component/_generated/api.d.ts +36 -0
- package/dist/component/_generated/api.d.ts.map +1 -0
- package/dist/component/_generated/api.js +31 -0
- package/dist/component/_generated/api.js.map +1 -0
- package/dist/component/_generated/component.d.ts +542 -0
- package/dist/component/_generated/component.d.ts.map +1 -0
- package/dist/component/_generated/component.js +11 -0
- package/dist/component/_generated/component.js.map +1 -0
- package/dist/component/_generated/dataModel.d.ts +46 -0
- package/dist/component/_generated/dataModel.d.ts.map +1 -0
- package/dist/component/_generated/dataModel.js +11 -0
- package/dist/component/_generated/dataModel.js.map +1 -0
- package/dist/component/_generated/server.d.ts +121 -0
- package/dist/component/_generated/server.d.ts.map +1 -0
- package/dist/component/_generated/server.js +78 -0
- package/dist/component/_generated/server.js.map +1 -0
- package/dist/component/convex.config.d.ts +3 -0
- package/dist/component/convex.config.d.ts.map +1 -0
- package/dist/component/convex.config.js +3 -0
- package/dist/component/convex.config.js.map +1 -0
- package/dist/component/lib.d.ts +1005 -0
- package/dist/component/lib.d.ts.map +1 -0
- package/dist/component/lib.js +647 -0
- package/dist/component/lib.js.map +1 -0
- package/dist/component/schema.d.ts +191 -0
- package/dist/component/schema.d.ts.map +1 -0
- package/dist/component/schema.js +104 -0
- package/dist/component/schema.js.map +1 -0
- package/dist/component/util.d.ts +61 -0
- package/dist/component/util.d.ts.map +1 -0
- package/dist/component/util.js +142 -0
- package/dist/component/util.js.map +1 -0
- package/dist/core/catalog.d.ts +18 -0
- package/dist/core/catalog.d.ts.map +1 -0
- package/dist/core/catalog.js +82 -0
- package/dist/core/catalog.js.map +1 -0
- package/dist/core/index.d.ts +9 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +9 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/markdown.d.ts +12 -0
- package/dist/core/markdown.d.ts.map +1 -0
- package/dist/core/markdown.js +26 -0
- package/dist/core/markdown.js.map +1 -0
- package/dist/core/payments.d.ts +11 -0
- package/dist/core/payments.d.ts.map +1 -0
- package/dist/core/payments.js +27 -0
- package/dist/core/payments.js.map +1 -0
- package/dist/core/pendingCheckout.d.ts +15 -0
- package/dist/core/pendingCheckout.d.ts.map +1 -0
- package/dist/core/pendingCheckout.js +40 -0
- package/dist/core/pendingCheckout.js.map +1 -0
- package/dist/core/resolver.d.ts +11 -0
- package/dist/core/resolver.d.ts.map +1 -0
- package/dist/core/resolver.js +106 -0
- package/dist/core/resolver.js.map +1 -0
- package/dist/core/selectors.d.ts +12 -0
- package/dist/core/selectors.d.ts.map +1 -0
- package/dist/core/selectors.js +18 -0
- package/dist/core/selectors.js.map +1 -0
- package/dist/core/subscriptionUpdate.d.ts +20 -0
- package/dist/core/subscriptionUpdate.d.ts.map +1 -0
- package/dist/core/subscriptionUpdate.js +64 -0
- package/dist/core/subscriptionUpdate.js.map +1 -0
- package/dist/core/types.d.ts +170 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +15 -0
- package/dist/core/types.js.map +1 -0
- package/dist/design-system/colors/color-utils.d.ts +10 -0
- package/dist/design-system/colors/color-utils.d.ts.map +1 -0
- package/dist/design-system/colors/color-utils.js +91 -0
- package/dist/design-system/colors/color-utils.js.map +1 -0
- package/dist/design-system/colors/config.d.ts +33 -0
- package/dist/design-system/colors/config.d.ts.map +1 -0
- package/dist/design-system/colors/config.js +224 -0
- package/dist/design-system/colors/config.js.map +1 -0
- package/dist/design-system/colors/index.d.ts +3 -0
- package/dist/design-system/colors/index.d.ts.map +1 -0
- package/dist/design-system/colors/index.js +3 -0
- package/dist/design-system/colors/index.js.map +1 -0
- package/dist/design-system/rounded/config.d.ts +31 -0
- package/dist/design-system/rounded/config.d.ts.map +1 -0
- package/dist/design-system/rounded/config.js +76 -0
- package/dist/design-system/rounded/config.js.map +1 -0
- package/dist/design-system/rounded/index.d.ts +2 -0
- package/dist/design-system/rounded/index.d.ts.map +1 -0
- package/dist/design-system/rounded/index.js +2 -0
- package/dist/design-system/rounded/index.js.map +1 -0
- package/dist/design-system/typography/config.d.ts +55 -0
- package/dist/design-system/typography/config.d.ts.map +1 -0
- package/dist/design-system/typography/config.js +308 -0
- package/dist/design-system/typography/config.js.map +1 -0
- package/dist/design-system/typography/index.d.ts +3 -0
- package/dist/design-system/typography/index.d.ts.map +1 -0
- package/dist/design-system/typography/index.js +3 -0
- package/dist/design-system/typography/index.js.map +1 -0
- package/dist/design-system/typography/tokens.d.ts +23 -0
- package/dist/design-system/typography/tokens.d.ts.map +1 -0
- package/dist/design-system/typography/tokens.js +99 -0
- package/dist/design-system/typography/tokens.js.map +1 -0
- package/dist/react/hooks/useCheckoutSuccessParams.d.ts +2 -0
- package/dist/react/hooks/useCheckoutSuccessParams.d.ts.map +1 -0
- package/dist/react/hooks/useCheckoutSuccessParams.js +5 -0
- package/dist/react/hooks/useCheckoutSuccessParams.js.map +1 -0
- package/dist/react/index.d.ts +25 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +22 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/primitives/BillingGate.d.ts +8 -0
- package/dist/react/primitives/BillingGate.d.ts.map +1 -0
- package/dist/react/primitives/BillingGate.js +13 -0
- package/dist/react/primitives/BillingGate.js.map +1 -0
- package/dist/react/primitives/BillingToggle.d.ts +8 -0
- package/dist/react/primitives/BillingToggle.d.ts.map +1 -0
- package/dist/react/primitives/BillingToggle.js +12 -0
- package/dist/react/primitives/BillingToggle.js.map +1 -0
- package/dist/react/primitives/CheckoutButton.d.ts +11 -0
- package/dist/react/primitives/CheckoutButton.d.ts.map +1 -0
- package/dist/react/primitives/CheckoutButton.js +21 -0
- package/dist/react/primitives/CheckoutButton.js.map +1 -0
- package/dist/react/primitives/CheckoutSuccessSummary.d.ts +7 -0
- package/dist/react/primitives/CheckoutSuccessSummary.d.ts.map +1 -0
- package/dist/react/primitives/CheckoutSuccessSummary.js +11 -0
- package/dist/react/primitives/CheckoutSuccessSummary.js.map +1 -0
- package/dist/react/primitives/CustomerPortalButton.d.ts +8 -0
- package/dist/react/primitives/CustomerPortalButton.d.ts.map +1 -0
- package/dist/react/primitives/CustomerPortalButton.js +21 -0
- package/dist/react/primitives/CustomerPortalButton.js.map +1 -0
- package/dist/react/primitives/NumberInput.d.ts +11 -0
- package/dist/react/primitives/NumberInput.d.ts.map +1 -0
- package/dist/react/primitives/NumberInput.js +18 -0
- package/dist/react/primitives/NumberInput.js.map +1 -0
- package/dist/react/primitives/OneTimeCheckoutButton.d.ts +11 -0
- package/dist/react/primitives/OneTimeCheckoutButton.d.ts.map +1 -0
- package/dist/react/primitives/OneTimeCheckoutButton.js +4 -0
- package/dist/react/primitives/OneTimeCheckoutButton.js.map +1 -0
- package/dist/react/primitives/OneTimePaymentStatusBadge.d.ts +6 -0
- package/dist/react/primitives/OneTimePaymentStatusBadge.d.ts.map +1 -0
- package/dist/react/primitives/OneTimePaymentStatusBadge.js +11 -0
- package/dist/react/primitives/OneTimePaymentStatusBadge.js.map +1 -0
- package/dist/react/primitives/PaymentWarningBanner.d.ts +7 -0
- package/dist/react/primitives/PaymentWarningBanner.d.ts.map +1 -0
- package/dist/react/primitives/PaymentWarningBanner.js +18 -0
- package/dist/react/primitives/PaymentWarningBanner.js.map +1 -0
- package/dist/react/primitives/PricingCard.d.ts +37 -0
- package/dist/react/primitives/PricingCard.d.ts.map +1 -0
- package/dist/react/primitives/PricingCard.js +125 -0
- package/dist/react/primitives/PricingCard.js.map +1 -0
- package/dist/react/primitives/PricingSection.d.ts +39 -0
- package/dist/react/primitives/PricingSection.d.ts.map +1 -0
- package/dist/react/primitives/PricingSection.js +24 -0
- package/dist/react/primitives/PricingSection.js.map +1 -0
- package/dist/react/primitives/ScheduledChangeBanner.d.ts +8 -0
- package/dist/react/primitives/ScheduledChangeBanner.d.ts.map +1 -0
- package/dist/react/primitives/ScheduledChangeBanner.js +13 -0
- package/dist/react/primitives/ScheduledChangeBanner.js.map +1 -0
- package/dist/react/primitives/SegmentControl.d.ts +11 -0
- package/dist/react/primitives/SegmentControl.d.ts.map +1 -0
- package/dist/react/primitives/SegmentControl.js +8 -0
- package/dist/react/primitives/SegmentControl.js.map +1 -0
- package/dist/react/primitives/SegmentGroup.d.ts +14 -0
- package/dist/react/primitives/SegmentGroup.d.ts.map +1 -0
- package/dist/react/primitives/SegmentGroup.js +11 -0
- package/dist/react/primitives/SegmentGroup.js.map +1 -0
- package/dist/react/primitives/TrialLimitBanner.d.ts +7 -0
- package/dist/react/primitives/TrialLimitBanner.d.ts.map +1 -0
- package/dist/react/primitives/TrialLimitBanner.js +14 -0
- package/dist/react/primitives/TrialLimitBanner.js.map +1 -0
- package/dist/react/shared.d.ts +28 -0
- package/dist/react/shared.d.ts.map +1 -0
- package/dist/react/shared.js +109 -0
- package/dist/react/shared.js.map +1 -0
- package/dist/react/widgets/BillingPortal.d.ts +9 -0
- package/dist/react/widgets/BillingPortal.d.ts.map +1 -0
- package/dist/react/widgets/BillingPortal.js +30 -0
- package/dist/react/widgets/BillingPortal.js.map +1 -0
- package/dist/react/widgets/ProductItem.d.ts +8 -0
- package/dist/react/widgets/ProductItem.d.ts.map +1 -0
- package/dist/react/widgets/ProductItem.js +14 -0
- package/dist/react/widgets/ProductItem.js.map +1 -0
- package/dist/react/widgets/ProductRoot.d.ts +16 -0
- package/dist/react/widgets/ProductRoot.d.ts.map +1 -0
- package/dist/react/widgets/ProductRoot.js +171 -0
- package/dist/react/widgets/ProductRoot.js.map +1 -0
- package/dist/react/widgets/SubscriptionItem.d.ts +27 -0
- package/dist/react/widgets/SubscriptionItem.d.ts.map +1 -0
- package/dist/react/widgets/SubscriptionItem.js +32 -0
- package/dist/react/widgets/SubscriptionItem.js.map +1 -0
- package/dist/react/widgets/SubscriptionRoot.d.ts +16 -0
- package/dist/react/widgets/SubscriptionRoot.d.ts.map +1 -0
- package/dist/react/widgets/SubscriptionRoot.js +405 -0
- package/dist/react/widgets/SubscriptionRoot.js.map +1 -0
- package/dist/react/widgets/index.d.ts +19 -0
- package/dist/react/widgets/index.d.ts.map +1 -0
- package/dist/react/widgets/index.js +16 -0
- package/dist/react/widgets/index.js.map +1 -0
- package/dist/react/widgets/productGroupContext.d.ts +6 -0
- package/dist/react/widgets/productGroupContext.d.ts.map +1 -0
- package/dist/react/widgets/productGroupContext.js +3 -0
- package/dist/react/widgets/productGroupContext.js.map +1 -0
- package/dist/react/widgets/subscriptionContext.d.ts +6 -0
- package/dist/react/widgets/subscriptionContext.d.ts.map +1 -0
- package/dist/react/widgets/subscriptionContext.js +3 -0
- package/dist/react/widgets/subscriptionContext.js.map +1 -0
- package/dist/react/widgets/types.d.ts +171 -0
- package/dist/react/widgets/types.d.ts.map +1 -0
- package/dist/react/widgets/types.js +2 -0
- package/dist/react/widgets/types.js.map +1 -0
- package/dist/svelte/index.d.ts +22 -0
- package/dist/svelte/index.d.ts.map +1 -0
- package/dist/svelte/index.js +20 -0
- package/dist/svelte/index.js.map +1 -0
- package/dist/svelte/primitives/BillingGate.svelte +28 -0
- package/dist/svelte/primitives/BillingToggle.svelte +27 -0
- package/dist/svelte/primitives/CheckoutButton.svelte +60 -0
- package/dist/svelte/primitives/CheckoutSuccessSummary.svelte +34 -0
- package/dist/svelte/primitives/CustomerPortalButton.svelte +60 -0
- package/dist/svelte/primitives/NumberInput.svelte +71 -0
- package/dist/svelte/primitives/OneTimeCheckoutButton.svelte +37 -0
- package/dist/svelte/primitives/OneTimePaymentStatusBadge.svelte +20 -0
- package/dist/svelte/primitives/PaymentWarningBanner.svelte +30 -0
- package/dist/svelte/primitives/PricingCard.svelte +356 -0
- package/dist/svelte/primitives/PricingSection.svelte +121 -0
- package/dist/svelte/primitives/ScheduledChangeBanner.svelte +46 -0
- package/dist/svelte/primitives/SegmentControl.svelte +38 -0
- package/dist/svelte/primitives/SegmentGroup.svelte +52 -0
- package/dist/svelte/primitives/TrialLimitBanner.svelte +32 -0
- package/dist/svelte/primitives/shared.d.ts +13 -0
- package/dist/svelte/primitives/shared.d.ts.map +1 -0
- package/dist/svelte/primitives/shared.js +87 -0
- package/dist/svelte/primitives/shared.js.map +1 -0
- package/dist/svelte/widgets/BillingPortal.svelte +55 -0
- package/dist/svelte/widgets/Product.svelte +35 -0
- package/dist/svelte/widgets/ProductRoot.svelte +428 -0
- package/dist/svelte/widgets/Subscription.svelte +52 -0
- package/dist/svelte/widgets/SubscriptionRoot.svelte +690 -0
- package/dist/svelte/widgets/index.d.ts +19 -0
- package/dist/svelte/widgets/index.d.ts.map +1 -0
- package/dist/svelte/widgets/index.js +16 -0
- package/dist/svelte/widgets/index.js.map +1 -0
- package/dist/svelte/widgets/productGroupContext.d.ts +6 -0
- package/dist/svelte/widgets/productGroupContext.d.ts.map +1 -0
- package/dist/svelte/widgets/productGroupContext.js +2 -0
- package/dist/svelte/widgets/productGroupContext.js.map +1 -0
- package/dist/svelte/widgets/subscriptionContext.d.ts +6 -0
- package/dist/svelte/widgets/subscriptionContext.d.ts.map +1 -0
- package/dist/svelte/widgets/subscriptionContext.js +2 -0
- package/dist/svelte/widgets/subscriptionContext.js.map +1 -0
- package/dist/svelte/widgets/types.d.ts +171 -0
- package/dist/svelte/widgets/types.d.ts.map +1 -0
- package/dist/svelte/widgets/types.js +2 -0
- package/dist/svelte/widgets/types.js.map +1 -0
- package/package.json +182 -0
- package/src/client/helpers.test.ts +139 -0
- package/src/client/helpers.ts +51 -0
- package/src/client/index.test.ts +1554 -0
- package/src/client/index.ts +1504 -0
- package/src/client/parsers.test.ts +1017 -0
- package/src/client/parsers.ts +182 -0
- package/src/client/polyfill.ts +2 -0
- package/src/component/_generated/api.ts +52 -0
- package/src/component/_generated/component.ts +619 -0
- package/src/component/_generated/dataModel.ts +60 -0
- package/src/component/_generated/server.ts +156 -0
- package/src/component/convex.config.ts +3 -0
- package/src/component/lib.test.ts +1359 -0
- package/src/component/lib.ts +726 -0
- package/src/component/schema.ts +112 -0
- package/src/component/util.test.ts +281 -0
- package/src/component/util.ts +228 -0
- package/src/core/catalog.test.ts +212 -0
- package/src/core/catalog.ts +119 -0
- package/src/core/index.ts +8 -0
- package/src/core/markdown.test.ts +43 -0
- package/src/core/markdown.ts +26 -0
- package/src/core/payments.test.ts +69 -0
- package/src/core/payments.ts +33 -0
- package/src/core/pendingCheckout.test.ts +44 -0
- package/src/core/pendingCheckout.ts +40 -0
- package/src/core/resolver.test.ts +283 -0
- package/src/core/resolver.ts +160 -0
- package/src/core/selectors.test.ts +119 -0
- package/src/core/selectors.ts +35 -0
- package/src/core/subscriptionUpdate.test.ts +164 -0
- package/src/core/subscriptionUpdate.ts +102 -0
- package/src/core/types.ts +220 -0
- package/src/design-system/README.md +40 -0
- package/src/design-system/base.css +27 -0
- package/src/design-system/colors/color-utils.ts +110 -0
- package/src/design-system/colors/config.ts +282 -0
- package/src/design-system/colors/index.ts +2 -0
- package/src/design-system/colors/utilities.css +2328 -0
- package/src/design-system/components/badges.css +65 -0
- package/src/design-system/components/buttons.css +256 -0
- package/src/design-system/components/dialog.css +218 -0
- package/src/design-system/components/icon-buttons.css +115 -0
- package/src/design-system/components/inputs.css +94 -0
- package/src/design-system/components/links.css +53 -0
- package/src/design-system/components/prose.css +67 -0
- package/src/design-system/components/segment-control.css +303 -0
- package/src/design-system/index.css +21 -0
- package/src/design-system/rounded/config.ts +91 -0
- package/src/design-system/rounded/index.ts +1 -0
- package/src/design-system/rounded/utilities.css +37 -0
- package/src/design-system/typography/config.ts +340 -0
- package/src/design-system/typography/index.ts +2 -0
- package/src/design-system/typography/tokens.ts +148 -0
- package/src/design-system/typography/utilities.css +728 -0
- package/src/library.css +20 -0
- package/src/react/hooks/useCheckoutSuccessParams.ts +7 -0
- package/src/react/index.tsx +47 -0
- package/src/react/primitives/BillingGate.tsx +26 -0
- package/src/react/primitives/BillingToggle.tsx +29 -0
- package/src/react/primitives/CheckoutButton.tsx +47 -0
- package/src/react/primitives/CheckoutSuccessSummary.tsx +36 -0
- package/src/react/primitives/CustomerPortalButton.tsx +50 -0
- package/src/react/primitives/NumberInput.tsx +83 -0
- package/src/react/primitives/OneTimeCheckoutButton.tsx +27 -0
- package/src/react/primitives/OneTimePaymentStatusBadge.tsx +18 -0
- package/src/react/primitives/PaymentWarningBanner.tsx +33 -0
- package/src/react/primitives/PricingCard.tsx +421 -0
- package/src/react/primitives/PricingSection.tsx +129 -0
- package/src/react/primitives/ScheduledChangeBanner.tsx +52 -0
- package/src/react/primitives/SegmentControl.tsx +32 -0
- package/src/react/primitives/SegmentGroup.tsx +53 -0
- package/src/react/primitives/TrialLimitBanner.tsx +32 -0
- package/src/react/shared.ts +138 -0
- package/src/react/widgets/BillingPortal.tsx +56 -0
- package/src/react/widgets/ProductItem.tsx +26 -0
- package/src/react/widgets/ProductRoot.tsx +441 -0
- package/src/react/widgets/SubscriptionItem.tsx +71 -0
- package/src/react/widgets/SubscriptionRoot.tsx +759 -0
- package/src/react/widgets/index.ts +36 -0
- package/src/react/widgets/productGroupContext.ts +10 -0
- package/src/react/widgets/subscriptionContext.ts +10 -0
- package/src/react/widgets/types.ts +179 -0
- package/src/svelte/index.ts +43 -0
- package/src/svelte/primitives/BillingGate.svelte +28 -0
- package/src/svelte/primitives/BillingToggle.svelte +27 -0
- package/src/svelte/primitives/CheckoutButton.svelte +60 -0
- package/src/svelte/primitives/CheckoutSuccessSummary.svelte +34 -0
- package/src/svelte/primitives/CustomerPortalButton.svelte +60 -0
- package/src/svelte/primitives/NumberInput.svelte +71 -0
- package/src/svelte/primitives/OneTimeCheckoutButton.svelte +37 -0
- package/src/svelte/primitives/OneTimePaymentStatusBadge.svelte +20 -0
- package/src/svelte/primitives/PaymentWarningBanner.svelte +30 -0
- package/src/svelte/primitives/PricingCard.svelte +356 -0
- package/src/svelte/primitives/PricingSection.svelte +121 -0
- package/src/svelte/primitives/ScheduledChangeBanner.svelte +46 -0
- package/src/svelte/primitives/SegmentControl.svelte +38 -0
- package/src/svelte/primitives/SegmentGroup.svelte +52 -0
- package/src/svelte/primitives/TrialLimitBanner.svelte +32 -0
- package/src/svelte/primitives/shared.ts +113 -0
- package/src/svelte/svelte.d.ts +6 -0
- package/src/svelte/widgets/BillingPortal.svelte +55 -0
- package/src/svelte/widgets/Product.svelte +35 -0
- package/src/svelte/widgets/ProductRoot.svelte +428 -0
- package/src/svelte/widgets/Subscription.svelte +52 -0
- package/src/svelte/widgets/SubscriptionRoot.svelte +690 -0
- package/src/svelte/widgets/index.ts +36 -0
- package/src/svelte/widgets/productGroupContext.ts +7 -0
- package/src/svelte/widgets/subscriptionContext.ts +7 -0
- package/src/svelte/widgets/types.ts +179 -0
- package/src/tailwind.css +6 -0
- package/src/test.ts +18 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
buildUpdateSummary,
|
|
4
|
+
type UpdateSummaryInput,
|
|
5
|
+
} from "./subscriptionUpdate.js";
|
|
6
|
+
|
|
7
|
+
const base: UpdateSummaryInput = {
|
|
8
|
+
kind: "plan-switch",
|
|
9
|
+
updateBehavior: "proration-charge-immediately",
|
|
10
|
+
currentLabel: "Basic",
|
|
11
|
+
newLabel: "Premium",
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
describe("buildUpdateSummary", () => {
|
|
15
|
+
describe("plan-switch", () => {
|
|
16
|
+
it("returns correct title for plan-switch", () => {
|
|
17
|
+
const result = buildUpdateSummary(base);
|
|
18
|
+
expect(result.title).toBe("Switch plan?");
|
|
19
|
+
expect(result.confirmLabel).toBe("Confirm switch");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("describes proration-charge-immediately", () => {
|
|
23
|
+
const result = buildUpdateSummary(base);
|
|
24
|
+
expect(result.description).toContain("prorated and charged immediately");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("describes proration-charge", () => {
|
|
28
|
+
const result = buildUpdateSummary({
|
|
29
|
+
...base,
|
|
30
|
+
updateBehavior: "proration-charge",
|
|
31
|
+
});
|
|
32
|
+
expect(result.description).toContain(
|
|
33
|
+
"prorated and applied to your next invoice",
|
|
34
|
+
);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("describes proration-none", () => {
|
|
38
|
+
const result = buildUpdateSummary({
|
|
39
|
+
...base,
|
|
40
|
+
updateBehavior: "proration-none",
|
|
41
|
+
});
|
|
42
|
+
expect(result.description).toContain("next billing cycle");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("preserves labels", () => {
|
|
46
|
+
const result = buildUpdateSummary(base);
|
|
47
|
+
expect(result.currentLabel).toBe("Basic");
|
|
48
|
+
expect(result.newLabel).toBe("Premium");
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe("seat-update", () => {
|
|
53
|
+
it("returns correct title for seat-update", () => {
|
|
54
|
+
const result = buildUpdateSummary({ ...base, kind: "seat-update" });
|
|
55
|
+
expect(result.title).toBe("Update seats?");
|
|
56
|
+
expect(result.confirmLabel).toBe("Confirm update");
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe("dateNote with currentPeriodEnd", () => {
|
|
61
|
+
it("includes date note for proration-charge", () => {
|
|
62
|
+
const result = buildUpdateSummary({
|
|
63
|
+
...base,
|
|
64
|
+
updateBehavior: "proration-charge",
|
|
65
|
+
currentPeriodEnd: "2025-06-15T00:00:00Z",
|
|
66
|
+
});
|
|
67
|
+
expect(result.dateNote).toContain("next invoice");
|
|
68
|
+
expect(result.dateNote).toContain("2025");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("includes date note for proration-none", () => {
|
|
72
|
+
const result = buildUpdateSummary({
|
|
73
|
+
...base,
|
|
74
|
+
updateBehavior: "proration-none",
|
|
75
|
+
currentPeriodEnd: "2025-06-15T00:00:00Z",
|
|
76
|
+
});
|
|
77
|
+
expect(result.dateNote).toContain("next billing cycle");
|
|
78
|
+
expect(result.dateNote).toContain("2025");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("returns null dateNote for proration-charge-immediately", () => {
|
|
82
|
+
const result = buildUpdateSummary({
|
|
83
|
+
...base,
|
|
84
|
+
updateBehavior: "proration-charge-immediately",
|
|
85
|
+
currentPeriodEnd: "2025-06-15T00:00:00Z",
|
|
86
|
+
});
|
|
87
|
+
expect(result.dateNote).toBeNull();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("returns null dateNote when no currentPeriodEnd", () => {
|
|
91
|
+
const result = buildUpdateSummary({
|
|
92
|
+
...base,
|
|
93
|
+
updateBehavior: "proration-charge",
|
|
94
|
+
});
|
|
95
|
+
expect(result.dateNote).toBeNull();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("returns null dateNote for invalid date", () => {
|
|
99
|
+
const result = buildUpdateSummary({
|
|
100
|
+
...base,
|
|
101
|
+
updateBehavior: "proration-charge",
|
|
102
|
+
currentPeriodEnd: "not-a-date",
|
|
103
|
+
});
|
|
104
|
+
expect(result.dateNote).toBeNull();
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe("trial handling", () => {
|
|
109
|
+
it("returns trial description when isTrialing is true", () => {
|
|
110
|
+
const result = buildUpdateSummary({
|
|
111
|
+
...base,
|
|
112
|
+
isTrialing: true,
|
|
113
|
+
});
|
|
114
|
+
expect(result.description).toContain("trial");
|
|
115
|
+
expect(result.dateNote).toBeNull();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("includes trial end date when provided", () => {
|
|
119
|
+
const result = buildUpdateSummary({
|
|
120
|
+
...base,
|
|
121
|
+
isTrialing: true,
|
|
122
|
+
trialEnd: "2025-07-01T00:00:00Z",
|
|
123
|
+
});
|
|
124
|
+
expect(result.description).toContain("trial");
|
|
125
|
+
expect(result.description).toContain("2025");
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("uses generic trial message when trialEnd is null", () => {
|
|
129
|
+
const result = buildUpdateSummary({
|
|
130
|
+
...base,
|
|
131
|
+
isTrialing: true,
|
|
132
|
+
trialEnd: null,
|
|
133
|
+
});
|
|
134
|
+
expect(result.description).toContain("free trial will continue");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("handles invalid trialEnd date gracefully", () => {
|
|
138
|
+
const result = buildUpdateSummary({
|
|
139
|
+
...base,
|
|
140
|
+
isTrialing: true,
|
|
141
|
+
trialEnd: "invalid-date",
|
|
142
|
+
});
|
|
143
|
+
// Falls back to generic trial message
|
|
144
|
+
expect(result.description).toContain("free trial will continue");
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("returns correct confirm label for trial plan-switch", () => {
|
|
148
|
+
const result = buildUpdateSummary({
|
|
149
|
+
...base,
|
|
150
|
+
isTrialing: true,
|
|
151
|
+
});
|
|
152
|
+
expect(result.confirmLabel).toBe("Confirm switch");
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("returns correct confirm label for trial seat-update", () => {
|
|
156
|
+
const result = buildUpdateSummary({
|
|
157
|
+
...base,
|
|
158
|
+
kind: "seat-update",
|
|
159
|
+
isTrialing: true,
|
|
160
|
+
});
|
|
161
|
+
expect(result.confirmLabel).toBe("Confirm update");
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { UpdateBehavior } from "./types.js";
|
|
2
|
+
|
|
3
|
+
export type UpdateSummaryInput = {
|
|
4
|
+
kind: "plan-switch" | "seat-update";
|
|
5
|
+
updateBehavior: UpdateBehavior;
|
|
6
|
+
currentLabel: string;
|
|
7
|
+
newLabel: string;
|
|
8
|
+
currentPeriodEnd?: string | null;
|
|
9
|
+
isTrialing?: boolean;
|
|
10
|
+
trialEnd?: string | null;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type UpdateSummary = {
|
|
14
|
+
title: string;
|
|
15
|
+
description: string;
|
|
16
|
+
currentLabel: string;
|
|
17
|
+
newLabel: string;
|
|
18
|
+
dateNote: string | null;
|
|
19
|
+
confirmLabel: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const getBehaviorDescription = (updateBehavior: UpdateBehavior): string => {
|
|
23
|
+
switch (updateBehavior) {
|
|
24
|
+
case "proration-charge-immediately":
|
|
25
|
+
return "The price difference will be prorated and charged immediately.";
|
|
26
|
+
case "proration-charge":
|
|
27
|
+
return "The price difference will be prorated and applied to your next invoice.";
|
|
28
|
+
case "proration-none":
|
|
29
|
+
return "The new price will take effect at your next billing cycle.";
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const formatPeriodEnd = (
|
|
34
|
+
iso: string,
|
|
35
|
+
updateBehavior: UpdateBehavior,
|
|
36
|
+
): string | null => {
|
|
37
|
+
const date = new Date(iso);
|
|
38
|
+
if (isNaN(date.getTime())) return null;
|
|
39
|
+
const formatted = date.toLocaleDateString(undefined, {
|
|
40
|
+
month: "short",
|
|
41
|
+
day: "numeric",
|
|
42
|
+
year: "numeric",
|
|
43
|
+
});
|
|
44
|
+
if (updateBehavior === "proration-charge") {
|
|
45
|
+
return `Your next invoice is on ${formatted}.`;
|
|
46
|
+
}
|
|
47
|
+
if (updateBehavior === "proration-none") {
|
|
48
|
+
return `Your next billing cycle starts on ${formatted}.`;
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const formatTrialEnd = (iso: string): string | null => {
|
|
54
|
+
const date = new Date(iso);
|
|
55
|
+
if (isNaN(date.getTime())) return null;
|
|
56
|
+
const formatted = date.toLocaleDateString(undefined, {
|
|
57
|
+
month: "short",
|
|
58
|
+
day: "numeric",
|
|
59
|
+
year: "numeric",
|
|
60
|
+
});
|
|
61
|
+
return `Your trial continues until ${formatted}. The new price will apply once the trial ends.`;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const buildUpdateSummary = (
|
|
65
|
+
input: UpdateSummaryInput,
|
|
66
|
+
): UpdateSummary => {
|
|
67
|
+
const {
|
|
68
|
+
kind,
|
|
69
|
+
updateBehavior,
|
|
70
|
+
currentLabel,
|
|
71
|
+
newLabel,
|
|
72
|
+
currentPeriodEnd,
|
|
73
|
+
isTrialing,
|
|
74
|
+
trialEnd,
|
|
75
|
+
} = input;
|
|
76
|
+
|
|
77
|
+
if (isTrialing) {
|
|
78
|
+
const trialNote = trialEnd ? formatTrialEnd(trialEnd) : null;
|
|
79
|
+
return {
|
|
80
|
+
title: kind === "plan-switch" ? "Switch plan?" : "Update seats?",
|
|
81
|
+
description:
|
|
82
|
+
trialNote ??
|
|
83
|
+
"Your free trial will continue. The new price will take effect once the trial ends.",
|
|
84
|
+
currentLabel,
|
|
85
|
+
newLabel,
|
|
86
|
+
dateNote: null,
|
|
87
|
+
confirmLabel:
|
|
88
|
+
kind === "plan-switch" ? "Confirm switch" : "Confirm update",
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
title: kind === "plan-switch" ? "Switch plan?" : "Update seats?",
|
|
94
|
+
description: getBehaviorDescription(updateBehavior),
|
|
95
|
+
currentLabel,
|
|
96
|
+
newLabel,
|
|
97
|
+
dateNote: currentPeriodEnd
|
|
98
|
+
? formatPeriodEnd(currentPeriodEnd, updateBehavior)
|
|
99
|
+
: null,
|
|
100
|
+
confirmLabel: kind === "plan-switch" ? "Confirm switch" : "Confirm update",
|
|
101
|
+
};
|
|
102
|
+
};
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/** Category of a billing plan. Determines default UI behavior and available actions. */
|
|
2
|
+
export type PlanCategory = "free" | "trial" | "paid" | "enterprise" | "custom";
|
|
3
|
+
|
|
4
|
+
/** Billing model type. `"recurring"` for subscriptions, `"onetime"` for single purchases. */
|
|
5
|
+
export type BillingType = "recurring" | "onetime" | "custom";
|
|
6
|
+
|
|
7
|
+
/** Billing cycles supported by the Creem API. */
|
|
8
|
+
export type SupportedRecurringCycle =
|
|
9
|
+
| "every-month"
|
|
10
|
+
| "every-three-months"
|
|
11
|
+
| "every-six-months"
|
|
12
|
+
| "every-year";
|
|
13
|
+
|
|
14
|
+
/** Billing cycle including a `"custom"` fallback for unrecognized intervals. */
|
|
15
|
+
export type RecurringCycle = SupportedRecurringCycle | "custom";
|
|
16
|
+
|
|
17
|
+
/** Status of a one-time payment order. */
|
|
18
|
+
export type OneTimePaymentStatus =
|
|
19
|
+
| "pending"
|
|
20
|
+
| "paid"
|
|
21
|
+
| "refunded"
|
|
22
|
+
| "partially_refunded";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Actions the current billing entity is allowed to perform.
|
|
26
|
+
* Resolved by `resolveBillingSnapshot` based on subscription state.
|
|
27
|
+
* Use with `<BillingGate requiredActions="...">` for conditional UI rendering.
|
|
28
|
+
*/
|
|
29
|
+
export type AvailableAction =
|
|
30
|
+
| "checkout"
|
|
31
|
+
| "portal"
|
|
32
|
+
| "cancel"
|
|
33
|
+
| "reactivate"
|
|
34
|
+
| "switch_interval"
|
|
35
|
+
| "update_seats"
|
|
36
|
+
| "contact_sales";
|
|
37
|
+
|
|
38
|
+
/** A single plan definition in the billing catalog. */
|
|
39
|
+
export type PlanCatalogEntry = {
|
|
40
|
+
/** Unique plan identifier (e.g. `"basic"`, `"premium"`). */
|
|
41
|
+
planId: string;
|
|
42
|
+
/** Plan category — drives default UI behavior and action resolution. */
|
|
43
|
+
category: PlanCategory;
|
|
44
|
+
/** Billing model. Defaults to `"recurring"` if omitted. */
|
|
45
|
+
billingType?: BillingType;
|
|
46
|
+
/** Supported billing cycles for this plan (e.g. `["every-month", "every-year"]`). */
|
|
47
|
+
billingCycles?: RecurringCycle[];
|
|
48
|
+
/** Pricing model — `"seat"` enables per-seat controls in widgets. */
|
|
49
|
+
pricingModel?: "flat" | "seat";
|
|
50
|
+
/** Map of billing cycle → Creem product ID (e.g. `{ "every-month": "prod_xxx" }`). */
|
|
51
|
+
creemProductIds?: Record<string, string>;
|
|
52
|
+
/** "Contact sales" URL for enterprise plans. */
|
|
53
|
+
contactUrl?: string;
|
|
54
|
+
/** Highlight this plan as recommended in the UI. */
|
|
55
|
+
recommended?: boolean;
|
|
56
|
+
/** Arbitrary metadata for custom logic. */
|
|
57
|
+
metadata?: Record<string, unknown>;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/** Plan catalog entry enriched with resolved UI display fields from Creem product data. */
|
|
61
|
+
export type UIPlanEntry = PlanCatalogEntry & {
|
|
62
|
+
/** Display title (auto-resolved from Creem product name if omitted). */
|
|
63
|
+
title?: string;
|
|
64
|
+
/** Display description (auto-resolved from Creem product description, rendered as Markdown). */
|
|
65
|
+
description?: string;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/** Static plan catalog configuration. Defines available plans and their Creem product mappings. */
|
|
69
|
+
export type PlanCatalog = {
|
|
70
|
+
/** Catalog version string (included in `BillingSnapshot.catalogVersion`). */
|
|
71
|
+
version: string;
|
|
72
|
+
/** Ordered list of plan definitions. */
|
|
73
|
+
plans: PlanCatalogEntry[];
|
|
74
|
+
/** Plan ID to use when no subscription is active (e.g. `"free"`). */
|
|
75
|
+
defaultPlanId?: string;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/** Lightweight subscription state used by the billing resolver. */
|
|
79
|
+
export type SubscriptionSnapshot = {
|
|
80
|
+
/** Creem subscription ID. */
|
|
81
|
+
id?: string;
|
|
82
|
+
/** Creem product ID of the subscribed plan. */
|
|
83
|
+
productId?: string;
|
|
84
|
+
/** Subscription status (e.g. `"active"`, `"trialing"`, `"canceled"`, `"paused"`, `"scheduled_cancel"`). */
|
|
85
|
+
status?: string;
|
|
86
|
+
/** Billing interval (e.g. `"every-month"`, `"every-year"`). */
|
|
87
|
+
recurringInterval?: string | null;
|
|
88
|
+
/** Number of seats (for seat-based pricing). */
|
|
89
|
+
seats?: number | null;
|
|
90
|
+
/** Whether the subscription is set to cancel at the end of the current period. */
|
|
91
|
+
cancelAtPeriodEnd?: boolean;
|
|
92
|
+
/** ISO timestamp of the current period end. */
|
|
93
|
+
currentPeriodEnd?: string | null;
|
|
94
|
+
/** ISO timestamp when the trial expires. */
|
|
95
|
+
trialEnd?: string | null;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/** Snapshot of a one-time payment, parsed from checkout success query params. */
|
|
99
|
+
export type PaymentSnapshot = {
|
|
100
|
+
/** Payment status. */
|
|
101
|
+
status: OneTimePaymentStatus;
|
|
102
|
+
/** Creem checkout ID. */
|
|
103
|
+
checkoutId?: string;
|
|
104
|
+
/** Creem order ID. */
|
|
105
|
+
orderId?: string;
|
|
106
|
+
/** Creem customer ID. */
|
|
107
|
+
customerId?: string;
|
|
108
|
+
/** Creem product ID. */
|
|
109
|
+
productId?: string;
|
|
110
|
+
/** Your custom request ID (passed via checkout metadata). */
|
|
111
|
+
requestId?: string;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/** Query parameters from a Creem checkout success redirect URL. Parsed by `parseCheckoutSuccessParams`. */
|
|
115
|
+
export type CheckoutSuccessParams = {
|
|
116
|
+
/** Creem checkout session ID (`checkout_id` query param). */
|
|
117
|
+
checkoutId?: string;
|
|
118
|
+
/** Creem order ID (`order_id` query param). */
|
|
119
|
+
orderId?: string;
|
|
120
|
+
/** Creem customer ID (`customer_id` query param). */
|
|
121
|
+
customerId?: string;
|
|
122
|
+
/** Creem product ID (`product_id` query param). */
|
|
123
|
+
productId?: string;
|
|
124
|
+
/** Your custom request ID (`request_id` query param). */
|
|
125
|
+
requestId?: string;
|
|
126
|
+
/** Creem signature for verification (`signature` query param). */
|
|
127
|
+
signature?: string;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Resolved billing state for a billing entity.
|
|
132
|
+
* Central data structure consumed by widgets and `<BillingGate>`.
|
|
133
|
+
* Produced by `creem.getBillingSnapshot()` or `resolveBillingSnapshot()`.
|
|
134
|
+
*/
|
|
135
|
+
export type BillingSnapshot = {
|
|
136
|
+
/** ISO timestamp when this snapshot was resolved. */
|
|
137
|
+
resolvedAt: string;
|
|
138
|
+
/** Version of the plan catalog used for resolution (if a catalog was provided). */
|
|
139
|
+
catalogVersion?: string;
|
|
140
|
+
/** ID of the currently active plan (from the catalog), or `null` if none matched. */
|
|
141
|
+
activePlanId: string | null;
|
|
142
|
+
/** Category of the active plan (e.g. `"free"`, `"paid"`, `"trial"`, `"enterprise"`). */
|
|
143
|
+
activeCategory: PlanCategory;
|
|
144
|
+
/** Current billing model. */
|
|
145
|
+
billingType: BillingType;
|
|
146
|
+
/** Current billing interval (e.g. `"every-month"`). */
|
|
147
|
+
recurringCycle?: RecurringCycle;
|
|
148
|
+
/** All billing cycles available for the active plan. Used by `<BillingToggle>`. */
|
|
149
|
+
availableBillingCycles: RecurringCycle[];
|
|
150
|
+
/** Raw subscription status string (e.g. `"active"`, `"trialing"`, `"canceled"`). */
|
|
151
|
+
subscriptionState?: string;
|
|
152
|
+
/** Current seat count for seat-based subscriptions. */
|
|
153
|
+
seats?: number;
|
|
154
|
+
/** One-time payment state, or `null` if not applicable. */
|
|
155
|
+
payment: PaymentSnapshot | null;
|
|
156
|
+
/** Actions the billing entity is allowed to perform. */
|
|
157
|
+
availableActions: AvailableAction[];
|
|
158
|
+
/** Additional metadata (cancelAtPeriodEnd, currentPeriodEnd, trialEnd, userContext). */
|
|
159
|
+
metadata?: Record<string, unknown>;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Intent object passed to `onBeforeCheckout` and stored by `pendingCheckout`.
|
|
164
|
+
* Represents the product and optional seat count the user wants to purchase.
|
|
165
|
+
*/
|
|
166
|
+
export type CheckoutIntent = {
|
|
167
|
+
/** Creem product ID to purchase. */
|
|
168
|
+
productId: string;
|
|
169
|
+
/** Number of seats/units (for seat-based plans). */
|
|
170
|
+
units?: number;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* How the Creem API handles plan switches and seat changes.
|
|
175
|
+
* - `"proration-charge-immediately"` — prorate and charge the difference now
|
|
176
|
+
* - `"proration-charge"` — prorate, charge on next invoice
|
|
177
|
+
* - `"proration-none"` — no proration, change takes effect on next billing cycle
|
|
178
|
+
*/
|
|
179
|
+
export type UpdateBehavior =
|
|
180
|
+
| "proration-charge-immediately"
|
|
181
|
+
| "proration-charge"
|
|
182
|
+
| "proration-none";
|
|
183
|
+
|
|
184
|
+
/** Get a human-readable description for a plan switch based on the proration behavior. */
|
|
185
|
+
export const getSwitchPlanDescription = (
|
|
186
|
+
updateBehavior: UpdateBehavior,
|
|
187
|
+
planTitle?: string,
|
|
188
|
+
): string => {
|
|
189
|
+
const prefix = planTitle
|
|
190
|
+
? `You are about to switch to the ${planTitle} plan.`
|
|
191
|
+
: "You are about to switch your plan.";
|
|
192
|
+
|
|
193
|
+
switch (updateBehavior) {
|
|
194
|
+
case "proration-charge-immediately":
|
|
195
|
+
return `${prefix} The price difference will be prorated and charged immediately.`;
|
|
196
|
+
case "proration-charge":
|
|
197
|
+
return `${prefix} The price difference will be prorated and applied to your next invoice.`;
|
|
198
|
+
case "proration-none":
|
|
199
|
+
return `${prefix} The new price will take effect at your next billing cycle.`;
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
/** Arbitrary user context passed through to the billing resolver. */
|
|
204
|
+
export type BillingUserContext = Record<string, unknown>;
|
|
205
|
+
|
|
206
|
+
/** Input for `resolveBillingSnapshot()`. Provide subscription + catalog data to resolve the billing state. */
|
|
207
|
+
export type BillingResolverInput = {
|
|
208
|
+
/** Optional plan catalog for plan-aware resolution. */
|
|
209
|
+
catalog?: PlanCatalog;
|
|
210
|
+
/** The entity's current (primary) subscription, or `null` if none. */
|
|
211
|
+
currentSubscription?: SubscriptionSnapshot | null;
|
|
212
|
+
/** All subscriptions for the entity (including ended). */
|
|
213
|
+
allSubscriptions?: SubscriptionSnapshot[];
|
|
214
|
+
/** One-time payment state (from checkout success params). */
|
|
215
|
+
payment?: PaymentSnapshot | null;
|
|
216
|
+
/** Arbitrary user context passed through to `metadata.userContext`. */
|
|
217
|
+
userContext?: BillingUserContext;
|
|
218
|
+
/** Override for the current timestamp (ISO string). Defaults to `new Date().toISOString()`. */
|
|
219
|
+
now?: string;
|
|
220
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Design System
|
|
2
|
+
|
|
3
|
+
Foundation-first design system structure.
|
|
4
|
+
|
|
5
|
+
## Structure
|
|
6
|
+
|
|
7
|
+
- `typography/config.ts` - base numbers, steps, typefaces, role definitions,
|
|
8
|
+
semantic alias map.
|
|
9
|
+
- `typography/tokens.ts` - computed typography tokens (desktop/tablet/mobile)
|
|
10
|
+
and helpers for future export pipelines.
|
|
11
|
+
- `typography/utilities.css` - Tailwind-friendly utility classes (`.display-l`,
|
|
12
|
+
`.body-m`, etc.) and CSS variables.
|
|
13
|
+
- `colors/config.ts` - semantic palettes, semantic role defaults (light/dark),
|
|
14
|
+
and state layer defaults.
|
|
15
|
+
- `colors/index.ts` - typed exports for color foundation configuration.
|
|
16
|
+
- `colors/utilities.css` - semantic color variables plus utility classes for
|
|
17
|
+
base and palette-prefixed intent roles.
|
|
18
|
+
- `base.css` - global defaults (`surface.base` page background +
|
|
19
|
+
`foreground.default` + Body L text styles).
|
|
20
|
+
- `rounded/config.ts` - semantic border radius roles, component defaults, and
|
|
21
|
+
component naming helpers.
|
|
22
|
+
- `rounded/utilities.css` - semantic radius utility classes (`radius-s`,
|
|
23
|
+
`radius-m`, etc.).
|
|
24
|
+
- `index.css` - aggregate stylesheet entry for the design system.
|
|
25
|
+
|
|
26
|
+
## Notes
|
|
27
|
+
|
|
28
|
+
- Responsive recalculation is applied only to `Display` and `Heading` roles.
|
|
29
|
+
- Other roles keep desktop values across breakpoints.
|
|
30
|
+
- Typeface and default weight are tokenized per family:
|
|
31
|
+
`--et-typeface-brand/plain` and `--et-font-weight-brand/plain`.
|
|
32
|
+
- Color utility convention:
|
|
33
|
+
- Canonical: `bg-surface-base`, `text-foreground-default`,
|
|
34
|
+
`border-border-default`.
|
|
35
|
+
- Intent canonical: `bg-primary-surface-base`,
|
|
36
|
+
`text-success-foreground-default`.
|
|
37
|
+
- Shortcut utilities (property-inferred): `surface-base`, `foreground-default`,
|
|
38
|
+
`primary-surface-base`.
|
|
39
|
+
- Values are temporary defaults and are expected to be replaced by Figma-sourced
|
|
40
|
+
values later.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
@layer base {
|
|
2
|
+
html {
|
|
3
|
+
scroll-behavior: smooth;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
@media (prefers-reduced-motion: reduce) {
|
|
7
|
+
html {
|
|
8
|
+
scroll-behavior: auto;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
body {
|
|
13
|
+
background-color: var(--et-color-surface-subtle);
|
|
14
|
+
color: var(--et-color-foreground-default);
|
|
15
|
+
font-family: var(--et-typeface-plain);
|
|
16
|
+
font-weight: var(--et-font-weight-plain);
|
|
17
|
+
font-synthesis: none;
|
|
18
|
+
font-size: var(--et-type-body-l-size-desktop);
|
|
19
|
+
line-height: var(--et-type-body-l-line-height-desktop);
|
|
20
|
+
letter-spacing: var(--et-type-body-l-tracking-desktop);
|
|
21
|
+
|
|
22
|
+
@variant dark {
|
|
23
|
+
background-color: var(--et-color-surface-subtle-dark);
|
|
24
|
+
color: var(--et-color-foreground-default-dark);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
export function normalizeHex(input: string): string {
|
|
2
|
+
const raw = input.trim().replace(/^#/, "").toUpperCase();
|
|
3
|
+
if (/^[0-9A-F]{6}$/.test(raw)) return `#${raw}`;
|
|
4
|
+
if (/^[0-9A-F]{3}$/.test(raw))
|
|
5
|
+
return `#${raw
|
|
6
|
+
.split("")
|
|
7
|
+
.map((c) => c + c)
|
|
8
|
+
.join("")}`;
|
|
9
|
+
return "#000000";
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function srgbToLinear(c: number): number {
|
|
13
|
+
return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function linearToSrgb(c: number): number {
|
|
17
|
+
return c <= 0.0031308 ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function clamp01(n: number): number {
|
|
21
|
+
return Math.min(1, Math.max(0, n));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function clampChannel(n: number): number {
|
|
25
|
+
return Math.round(clamp01(n) * 255);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function toHex(n: number): string {
|
|
29
|
+
return n.toString(16).padStart(2, "0").toUpperCase();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function hexToOklch(hexInput: string): string {
|
|
33
|
+
const hex = normalizeHex(hexInput);
|
|
34
|
+
const r = parseInt(hex.slice(1, 3), 16) / 255;
|
|
35
|
+
const g = parseInt(hex.slice(3, 5), 16) / 255;
|
|
36
|
+
const b = parseInt(hex.slice(5, 7), 16) / 255;
|
|
37
|
+
|
|
38
|
+
const lr = srgbToLinear(r);
|
|
39
|
+
const lg = srgbToLinear(g);
|
|
40
|
+
const lb = srgbToLinear(b);
|
|
41
|
+
|
|
42
|
+
const l = 0.4122214708 * lr + 0.5363325363 * lg + 0.0514459929 * lb;
|
|
43
|
+
const m = 0.2119034982 * lr + 0.6806995451 * lg + 0.1073969566 * lb;
|
|
44
|
+
const s = 0.0883024619 * lr + 0.2817188376 * lg + 0.6299787005 * lb;
|
|
45
|
+
|
|
46
|
+
const l_ = Math.cbrt(l);
|
|
47
|
+
const m_ = Math.cbrt(m);
|
|
48
|
+
const s_ = Math.cbrt(s);
|
|
49
|
+
|
|
50
|
+
const L = 0.2104542553 * l_ + 0.793617785 * m_ - 0.0040720468 * s_;
|
|
51
|
+
const a = 1.9779984951 * l_ - 2.428592205 * m_ + 0.4505937099 * s_;
|
|
52
|
+
const b2 = 0.0259040371 * l_ + 0.7827717662 * m_ - 0.808675766 * s_;
|
|
53
|
+
|
|
54
|
+
const C = Math.sqrt(a * a + b2 * b2);
|
|
55
|
+
let h = (Math.atan2(b2, a) * 180) / Math.PI;
|
|
56
|
+
if (h < 0) h += 360;
|
|
57
|
+
|
|
58
|
+
return `oklch(${L.toFixed(4)} ${C.toFixed(4)} ${h.toFixed(2)})`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function parseOklch(
|
|
62
|
+
input: string,
|
|
63
|
+
): { l: number; c: number; h: number } | null {
|
|
64
|
+
const m = input
|
|
65
|
+
.trim()
|
|
66
|
+
.match(
|
|
67
|
+
/^oklch\(\s*([0-9]*\.?[0-9]+)\s+([0-9]*\.?[0-9]+)\s+([0-9]*\.?[0-9]+)(?:deg)?\s*\)$/i,
|
|
68
|
+
);
|
|
69
|
+
if (!m) return null;
|
|
70
|
+
const l = Number(m[1]);
|
|
71
|
+
const c = Number(m[2]);
|
|
72
|
+
const h = Number(m[3]);
|
|
73
|
+
if (!Number.isFinite(l) || !Number.isFinite(c) || !Number.isFinite(h))
|
|
74
|
+
return null;
|
|
75
|
+
return { l, c, h };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function normalizeOklch(input: string): string {
|
|
79
|
+
const parsed = parseOklch(input);
|
|
80
|
+
if (!parsed) return input;
|
|
81
|
+
const h = ((parsed.h % 360) + 360) % 360;
|
|
82
|
+
return `oklch(${parsed.l.toFixed(4)} ${parsed.c.toFixed(4)} ${h.toFixed(2)})`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function oklchToHex(input: string): string {
|
|
86
|
+
const parsed = parseOklch(input);
|
|
87
|
+
if (!parsed) return "#000000";
|
|
88
|
+
|
|
89
|
+
const hRad = (parsed.h * Math.PI) / 180;
|
|
90
|
+
const a = parsed.c * Math.cos(hRad);
|
|
91
|
+
const b = parsed.c * Math.sin(hRad);
|
|
92
|
+
|
|
93
|
+
const l_ = parsed.l + 0.3963377774 * a + 0.2158037573 * b;
|
|
94
|
+
const m_ = parsed.l - 0.1055613458 * a - 0.0638541728 * b;
|
|
95
|
+
const s_ = parsed.l - 0.0894841775 * a - 1.291485548 * b;
|
|
96
|
+
|
|
97
|
+
const l = l_ * l_ * l_;
|
|
98
|
+
const m = m_ * m_ * m_;
|
|
99
|
+
const s = s_ * s_ * s_;
|
|
100
|
+
|
|
101
|
+
const lr = 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s;
|
|
102
|
+
const lg = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s;
|
|
103
|
+
const lb = -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s;
|
|
104
|
+
|
|
105
|
+
const r = clampChannel(linearToSrgb(lr));
|
|
106
|
+
const g = clampChannel(linearToSrgb(lg));
|
|
107
|
+
const bHex = clampChannel(linearToSrgb(lb));
|
|
108
|
+
|
|
109
|
+
return `#${toHex(r)}${toHex(g)}${toHex(bHex)}`;
|
|
110
|
+
}
|