From 66cd5531fce973778681729148209a5235931fd2 Mon Sep 17 00:00:00 2001 From: John Yeo Date: Tue, 2 Sep 2025 23:44:24 -0700 Subject: [PATCH] moving cusproduct to tanstack --- server/src/check.ts | 2 +- .../stripeSubUtils/stripeSubItemUtils.ts | 6 +- .../handleCusProductDeleted.ts | 2 +- .../handleSchedulePhaseCompleted.ts | 14 +- .../stripe/webhookUtils/webhookUtils.ts | 2 +- .../src/internal/analytics/analyticsUtils.ts | 2 +- .../handlers/handleProductsUpdated.ts | 6 +- .../api/entitled/checkUtils/getCheckData.ts | 2 +- .../addProductFlow/handlePaidProduct.ts | 3 +- .../multiAttach/handleMultiAttachFlow.ts | 2 +- .../handleQuantityDowngrade.ts | 2 +- .../handleQuantityUpgrade.ts | 2 +- .../createUsageInvoiceItems.ts | 5 +- .../priceToUnusedPreviewItem.ts | 19 +- .../attachParamsUtils/getPricesAndEnts.ts | 5 +- .../attachParams/convertToParams.ts | 2 +- .../attach/attachUtils/attachUtils.ts | 2 +- .../attach/attachUtils/convertAttachParams.ts | 4 +- .../attach/attachUtils/getAttachBranch.ts | 5 +- .../attach/attachUtils/getAttachConfig.ts | 2 +- .../createContUseInvoiceItems.ts | 2 +- .../getContUseItems/priceToContUseItem.ts | 2 +- .../attach/attachUtils/handleAttachErrors.ts | 5 +- .../attach/checkout/getHasProrations.ts | 2 +- .../attach/checkout/previewToCheckoutRes.ts | 2 +- .../attachParamsToPreview.ts | 2 +- .../getMultiAttachPreview.ts | 3 +- .../getNewProductPreview.ts | 2 +- .../getUpdateEntsPreview.ts | 2 +- .../getUpgradeProductPreview.ts | 4 +- .../customers/attach/mergeUtils/mergeUtils.ts | 2 +- .../mergeUtils/paramsToScheduleItems.ts | 2 +- .../attach/mergeUtils/paramsToSubItems.ts | 5 +- .../customers/cancel/cancelImmediately.ts | 9 +- .../customers/cancel/handleCancelProduct.ts | 5 +- .../cusProducts/cusEnts/cusEntUtils.ts | 214 ++++++------- .../cusEnts/cusEntUtils/getExistingUsage.ts | 2 +- .../customers/cusProducts/cusProductUtils.ts | 19 +- .../cusProductUtils/convertCusProduct.ts | 194 ++++++------ .../cusProductUtils/getUpcomingSchedules.ts | 2 +- .../getCusFeaturesResponse.ts | 6 +- .../internal/customers/cusUtils/cusUtils.ts | 2 +- .../customers/cusUtils/getCustomerDetails.ts | 6 +- .../handlers/handleCusProductExpired.ts | 2 +- .../handleDecreaseAndTransfer.ts | 2 +- .../internal/customers/internalCusRouter.ts | 284 +++--------------- server/src/internal/features/featureUtils.ts | 5 +- .../internal/invoices/invoiceFormatUtils.ts | 7 +- .../previewItemUtils/getCurContUseItems.ts | 5 +- .../previewItemUtils/getItemsForCurProduct.ts | 2 +- .../products/internalProductRouter.ts | 36 +++ .../internal/products/pricecn/pricecnUtils.ts | 3 +- .../productResponseUtils/getAttachScenario.ts | 2 +- .../utils/checkUtils/checkCustomerCorrect.ts | 2 +- .../utils/importUtils/addUsagePricesToSub.ts | 2 +- server/src/utils/importUtils/updateUsages.ts | 2 +- server/tests/core/cancel/cancel2.test.ts | 8 +- server/tests/core/cancel/cancel3.test.ts | 11 - server/tests/merged/mergeUtils.test.ts | 2 +- .../merged/mergeUtils/expectSubCorrect.ts | 2 +- .../utils/expectUtils/expectContUseUtils.ts | 4 +- .../utils/expectUtils/expectInvoiceUtils.ts | 5 +- .../utils/expectUtils/expectScheduleUtils.ts | 2 +- .../tests/utils/expectUtils/expectSubUtils.ts | 5 +- shared/index.ts | 1 + .../cusEntUtils/sortCusEntsForDeduction.ts | 113 +++++++ .../cusProductUtils/classifyCusProduct.ts | 17 ++ .../cusProductUtils/convertCusProduct.ts | 123 ++++++++ .../cusProductUtils/cusProductConstants.ts | 12 + .../cusProductUtils/productIdToCusProduct.ts | 38 +++ shared/utils/index.ts | 9 + .../product-item/validateProductItem.ts | 6 - .../views/customers/customer/CustomerView.tsx | 37 +-- .../customers/customer/hooks/useCusQuery.tsx | 9 +- .../customer/product/CustomerProductView.tsx | 134 ++++++--- .../product/components/AttachButton.tsx | 15 +- .../product/components/AttachModal.tsx | 21 +- .../components/AttachPreviewDetails.tsx | 5 +- .../components/CustomerProductBadge.tsx | 4 +- .../components/CustomerProductBreadcrumbs.tsx | 23 +- .../attach-preview/AttachNewItems.tsx | 4 +- .../attach-preview/DueNextCycle.tsx | 7 +- .../components/attach-preview/DueToday.tsx | 4 +- .../attach-preview/UpdateQuantity.tsx | 4 +- .../customer/product/hooks/useAttachState.tsx | 29 +- .../product/hooks/useCusProductQuery.tsx | 61 ++++ .../features/components/CreateFeature.tsx | 2 +- .../views/products/product/ProductSidebar.tsx | 12 +- .../views/products/product/ProductView.tsx | 130 ++++---- .../components/ProductViewBreadcrumbs.tsx | 6 +- .../components/UpdateProductButton.tsx | 42 ++- .../product/hooks/getCachedProduct.tsx | 53 ++++ .../products/product/hooks/useProductData.tsx | 11 +- .../product/hooks/useProductQuery.tsx | 54 ++++ .../products/product/prices/CreatePrice.tsx | 76 ----- .../product-item/CreateProductItem.tsx | 10 +- .../product-item/CreateProductItem2.tsx | 6 +- .../product/product-item/EntitiesDropdown.tsx | 17 +- .../product-item/ProductItemConfig.tsx | 34 +-- .../product/product-item/ProductItemRow.tsx | 8 +- .../product/product-item/ProductItemTable.tsx | 6 +- .../product-item/UpdateProductItem.tsx | 6 +- .../components/ConfigWithFeature.tsx | 6 +- .../components/SelectItemFeature.tsx | 3 +- .../CreateItemDialogContent.tsx | 60 ++-- .../product-item-config/FeatureItemConfig.tsx | 9 +- .../product-item-config/PriceItemConfig.tsx | 4 - .../advanced-config/AdvancedItemConfig.tsx | 5 +- .../components/SelectFeature.tsx | 3 +- .../components/SelectResetCycle.tsx | 13 +- .../components/feature-price/BillingUnits.tsx | 3 +- .../components/feature-price/FeaturePrice.tsx | 3 - .../item-config-footer/AddFeatureButton.tsx | 48 --- .../AddToEntityDropdown.tsx | 7 - .../item-config-footer/ItemConfigFooter.tsx | 2 - .../product/versioning/CountAndMigrate.tsx | 3 +- 116 files changed, 1189 insertions(+), 1105 deletions(-) create mode 100644 shared/utils/cusEntUtils/sortCusEntsForDeduction.ts create mode 100644 shared/utils/cusProductUtils/classifyCusProduct.ts create mode 100644 shared/utils/cusProductUtils/convertCusProduct.ts create mode 100644 shared/utils/cusProductUtils/cusProductConstants.ts create mode 100644 shared/utils/cusProductUtils/productIdToCusProduct.ts create mode 100644 shared/utils/index.ts create mode 100644 vite/src/views/customers/customer/product/hooks/useCusProductQuery.tsx create mode 100644 vite/src/views/products/product/hooks/getCachedProduct.tsx create mode 100644 vite/src/views/products/product/hooks/useProductQuery.tsx delete mode 100644 vite/src/views/products/product/prices/CreatePrice.tsx delete mode 100644 vite/src/views/products/product/product-item/product-item-config/item-config-footer/AddFeatureButton.tsx diff --git a/server/src/check.ts b/server/src/check.ts index bc27af8ad..cf0ee00cd 100644 --- a/server/src/check.ts +++ b/server/src/check.ts @@ -16,7 +16,7 @@ import { } from "@autumn/shared"; import Stripe from "stripe"; import assert from "assert"; -import { cusProductToPrices } from "@/internal/customers/cusProducts/cusProductUtils/convertCusProduct.js"; +import { cusProductToPrices } from "@autumn/shared"; import { notNullish } from "@/utils/genUtils.js"; import { getAllStripeSchedules, diff --git a/server/src/external/stripe/stripeSubUtils/stripeSubItemUtils.ts b/server/src/external/stripe/stripeSubUtils/stripeSubItemUtils.ts index 2cc511f61..714fe393b 100644 --- a/server/src/external/stripe/stripeSubUtils/stripeSubItemUtils.ts +++ b/server/src/external/stripe/stripeSubUtils/stripeSubItemUtils.ts @@ -1,9 +1,5 @@ -import { isTrialing } from "@/internal/customers/cusProducts/cusProductUtils.js"; -import { cusProductToPrices } from "@/internal/customers/cusProducts/cusProductUtils/convertCusProduct.js"; -import { priceToInvoiceDescription } from "@/internal/invoices/invoiceFormatUtils.js"; -import { getProration } from "@/internal/invoices/previewItemUtils/getItemsForNewProduct.js"; +import { cusProductToPrices } from "@autumn/shared"; import { getBillingType } from "@/internal/products/prices/priceUtils.js"; -import { priceToInvoiceAmount } from "@/internal/products/prices/priceUtils/priceToInvoiceAmount.js"; import { isFixedPrice } from "@/internal/products/prices/priceUtils/usagePriceUtils/classifyUsagePrice.js"; import { notNullish } from "@/utils/genUtils.js"; import { diff --git a/server/src/external/stripe/webhookHandlers/handleSubDeleted/handleCusProductDeleted.ts b/server/src/external/stripe/webhookHandlers/handleSubDeleted/handleCusProductDeleted.ts index bc68fae2a..7fc652d80 100644 --- a/server/src/external/stripe/webhookHandlers/handleSubDeleted/handleCusProductDeleted.ts +++ b/server/src/external/stripe/webhookHandlers/handleSubDeleted/handleCusProductDeleted.ts @@ -6,7 +6,7 @@ import { activateDefaultProduct, cancelCusProductSubscriptions, } from "@/internal/customers/cusProducts/cusProductUtils.js"; -import { cusProductToPrices } from "@/internal/customers/cusProducts/cusProductUtils/convertCusProduct.js"; +import { cusProductToPrices } from "@autumn/shared"; import { getExistingCusProducts } from "@/internal/customers/cusProducts/cusProductUtils/getExistingCusProducts.js"; import { ExtendedRequest } from "@/utils/models/Request.js"; import { diff --git a/server/src/external/stripe/webhookHandlers/handleSubUpdated/handleSchedulePhaseCompleted.ts b/server/src/external/stripe/webhookHandlers/handleSubUpdated/handleSchedulePhaseCompleted.ts index dcd89a24a..5f02029a8 100644 --- a/server/src/external/stripe/webhookHandlers/handleSubUpdated/handleSchedulePhaseCompleted.ts +++ b/server/src/external/stripe/webhookHandlers/handleSubUpdated/handleSchedulePhaseCompleted.ts @@ -1,21 +1,13 @@ -import { DrizzleCli } from "@/db/initDrizzle.js"; -import { - AppEnv, - AttachScenario, - CusProductStatus, - Organization, -} from "@autumn/shared"; import Stripe from "stripe"; +import { AttachScenario, CusProductStatus } from "@autumn/shared"; import { createStripeCli } from "../../utils.js"; import { CusProductService } from "@/internal/customers/cusProducts/CusProductService.js"; -import { cusProductInPhase } from "@/internal/customers/attach/mergeUtils/phaseUtils/phaseUtils.js"; -import { formatUnixToDateTime, notNullish } from "@/utils/genUtils.js"; +import { notNullish } from "@/utils/genUtils.js"; import { getStripeNow } from "@/utils/scriptUtils/testClockUtils.js"; -import { getExistingCusProducts } from "@/internal/customers/cusProducts/cusProductUtils/getExistingCusProducts.js"; import { activateFutureProduct } from "@/internal/customers/cusProducts/cusProductUtils.js"; import { ExtendedRequest } from "@/utils/models/Request.js"; -import { cusProductToProduct } from "@/internal/customers/cusProducts/cusProductUtils/convertCusProduct.js"; +import { cusProductToProduct } from "@autumn/shared"; import { isFreeProduct, isOneOff } from "@/internal/products/productUtils.js"; import { addProductsUpdatedWebhookTask } from "@/internal/analytics/handlers/handleProductsUpdated.js"; diff --git a/server/src/external/stripe/webhookUtils/webhookUtils.ts b/server/src/external/stripe/webhookUtils/webhookUtils.ts index b0ff31854..3cb424784 100644 --- a/server/src/external/stripe/webhookUtils/webhookUtils.ts +++ b/server/src/external/stripe/webhookUtils/webhookUtils.ts @@ -6,7 +6,7 @@ import { cusProductToEnts, cusProductToPrices, cusProductToProduct, -} from "@/internal/customers/cusProducts/cusProductUtils/convertCusProduct.js"; +} from "@autumn/shared"; import { ExtendedRequest } from "@/utils/models/Request.js"; import { Entity, FullCusProduct, FullCustomer } from "@autumn/shared"; import Stripe from "stripe"; diff --git a/server/src/internal/analytics/analyticsUtils.ts b/server/src/internal/analytics/analyticsUtils.ts index 47d961cea..2b72dfe7e 100644 --- a/server/src/internal/analytics/analyticsUtils.ts +++ b/server/src/internal/analytics/analyticsUtils.ts @@ -11,7 +11,7 @@ import { FullCustomerEntitlement, EntInterval, } from "@autumn/shared"; -import { cusProductToProduct } from "@/internal/customers/cusProducts/cusProductUtils/convertCusProduct.js"; +import { cusProductToProduct } from "@autumn/shared"; import { ACTIVE_STATUSES } from "@/internal/customers/cusProducts/CusProductService.js"; import { isFreeProduct } from "../products/productUtils.js"; diff --git a/server/src/internal/analytics/handlers/handleProductsUpdated.ts b/server/src/internal/analytics/handlers/handleProductsUpdated.ts index cf6f6599b..ba10f4967 100644 --- a/server/src/internal/analytics/handlers/handleProductsUpdated.ts +++ b/server/src/internal/analytics/handlers/handleProductsUpdated.ts @@ -23,11 +23,7 @@ import { ActionService } from "@/internal/analytics/ActionService.js"; import { constructAction } from "@/internal/analytics/actionUtils.js"; import { parseReqForAction } from "@/internal/analytics/actionUtils.js"; import { RELEVANT_STATUSES } from "@/internal/customers/cusProducts/CusProductService.js"; -import { - cusProductToEnts, - cusProductToPrices, - cusProductToProduct, -} from "@/internal/customers/cusProducts/cusProductUtils/convertCusProduct.js"; +import { cusProductToPrices, cusProductToProduct } from "@autumn/shared"; interface ActionDetails { request_id: string; diff --git a/server/src/internal/api/entitled/checkUtils/getCheckData.ts b/server/src/internal/api/entitled/checkUtils/getCheckData.ts index add274e7c..e711e096b 100644 --- a/server/src/internal/api/entitled/checkUtils/getCheckData.ts +++ b/server/src/internal/api/entitled/checkUtils/getCheckData.ts @@ -1,5 +1,5 @@ import { cusEntMatchesEntity } from "@/internal/customers/cusProducts/cusEnts/cusEntUtils/findCusEntUtils.js"; -import { cusProductsToCusEnts } from "@/internal/customers/cusProducts/cusProductUtils/convertCusProduct.js"; +import { cusProductsToCusEnts } from "@autumn/shared"; import { getOrCreateCustomer } from "@/internal/customers/cusUtils/getOrCreateCustomer.js"; import { getCreditSystemsFromFeature } from "@/internal/features/creditSystemUtils.js"; import RecaseError from "@/utils/errorUtils.js"; diff --git a/server/src/internal/customers/attach/attachFunctions/addProductFlow/handlePaidProduct.ts b/server/src/internal/customers/attach/attachFunctions/addProductFlow/handlePaidProduct.ts index 7511f2304..e2692d8d8 100644 --- a/server/src/internal/customers/attach/attachFunctions/addProductFlow/handlePaidProduct.ts +++ b/server/src/internal/customers/attach/attachFunctions/addProductFlow/handlePaidProduct.ts @@ -33,8 +33,7 @@ import { getCustomerSub } from "../../attachUtils/convertAttachParams.js"; import { paramsToSubItems } from "../../mergeUtils/paramsToSubItems.js"; import { updateStripeSub2 } from "../upgradeFlow/updateStripeSub2.js"; import { subToNewSchedule } from "../../mergeUtils/subToNewSchedule.js"; -import { isTrialing } from "@/internal/customers/cusProducts/cusProductUtils.js"; -import { formatUnixToDate } from "@/utils/genUtils.js"; +import { isTrialing } from "@autumn/shared"; export const handlePaidProduct = async ({ req, diff --git a/server/src/internal/customers/attach/attachFunctions/multiAttach/handleMultiAttachFlow.ts b/server/src/internal/customers/attach/attachFunctions/multiAttach/handleMultiAttachFlow.ts index 68d6a5eb4..d86895ee9 100644 --- a/server/src/internal/customers/attach/attachFunctions/multiAttach/handleMultiAttachFlow.ts +++ b/server/src/internal/customers/attach/attachFunctions/multiAttach/handleMultiAttachFlow.ts @@ -29,7 +29,7 @@ import { import { paramsToSubItems } from "../../mergeUtils/paramsToSubItems.js"; import { getAddAndRemoveProducts } from "./getAddAndRemoveProducts.js"; import { handleUpgradeFlowSchedule } from "../upgradeFlow/handleUpgradeFlowSchedule.js"; -import { isTrialing } from "@/internal/customers/cusProducts/cusProductUtils.js"; +import { isTrialing } from "@autumn/shared"; export const handleMultiAttachFlow = async ({ req, diff --git a/server/src/internal/customers/attach/attachFunctions/updateQuantityFlow/handleQuantityDowngrade.ts b/server/src/internal/customers/attach/attachFunctions/updateQuantityFlow/handleQuantityDowngrade.ts index 34f85811d..ee4db0f67 100644 --- a/server/src/internal/customers/attach/attachFunctions/updateQuantityFlow/handleQuantityDowngrade.ts +++ b/server/src/internal/customers/attach/attachFunctions/updateQuantityFlow/handleQuantityDowngrade.ts @@ -22,7 +22,7 @@ import { getRelatedCusEnt } from "@/internal/customers/cusProducts/cusPrices/cus import { getInvoiceItems } from "@/internal/invoices/invoiceUtils.js"; import { InvoiceService } from "@/internal/invoices/InvoiceService.js"; import { constructStripeInvoiceItem } from "@/internal/invoices/invoiceItemUtils/invoiceItemUtils.js"; -import { cusProductToProduct } from "@/internal/customers/cusProducts/cusProductUtils/convertCusProduct.js"; +import { cusProductToProduct } from "@autumn/shared"; import { subToPeriodStartEnd } from "@/external/stripe/stripeSubUtils/convertSubUtils.js"; import { createAndFinalizeInvoice } from "@/internal/invoices/invoiceUtils/createAndFinalizeInvoice.js"; import { notNullish } from "@/utils/genUtils.js"; diff --git a/server/src/internal/customers/attach/attachFunctions/updateQuantityFlow/handleQuantityUpgrade.ts b/server/src/internal/customers/attach/attachFunctions/updateQuantityFlow/handleQuantityUpgrade.ts index 6f32f0f92..a22322788 100644 --- a/server/src/internal/customers/attach/attachFunctions/updateQuantityFlow/handleQuantityUpgrade.ts +++ b/server/src/internal/customers/attach/attachFunctions/updateQuantityFlow/handleQuantityUpgrade.ts @@ -17,7 +17,7 @@ import { } from "@/internal/products/prices/priceUtils/prorationConfigUtils.js"; import { priceToInvoiceAmount } from "@/internal/products/prices/priceUtils/priceToInvoiceAmount.js"; import { constructStripeInvoiceItem } from "@/internal/invoices/invoiceItemUtils/invoiceItemUtils.js"; -import { cusProductToProduct } from "@/internal/customers/cusProducts/cusProductUtils/convertCusProduct.js"; +import { cusProductToProduct } from "@autumn/shared"; import { createAndFinalizeInvoice } from "@/internal/invoices/invoiceUtils/createAndFinalizeInvoice.js"; import { getRelatedCusEnt } from "@/internal/customers/cusProducts/cusPrices/cusPriceUtils.js"; import { CusEntService } from "@/internal/customers/cusProducts/cusEnts/CusEntitlementService.js"; diff --git a/server/src/internal/customers/attach/attachFunctions/upgradeDiffIntFlow/createUsageInvoiceItems.ts b/server/src/internal/customers/attach/attachFunctions/upgradeDiffIntFlow/createUsageInvoiceItems.ts index 22b84d750..e5e8239ec 100644 --- a/server/src/internal/customers/attach/attachFunctions/upgradeDiffIntFlow/createUsageInvoiceItems.ts +++ b/server/src/internal/customers/attach/attachFunctions/upgradeDiffIntFlow/createUsageInvoiceItems.ts @@ -7,10 +7,7 @@ import { getCusPriceUsage, getRelatedCusEnt, } from "@/internal/customers/cusProducts/cusPrices/cusPriceUtils.js"; -import { - cusProductsToCusEnts, - cusProductsToCusPrices, -} from "@/internal/customers/cusProducts/cusProductUtils/convertCusProduct.js"; +import { cusProductsToCusEnts, cusProductsToCusPrices } from "@autumn/shared"; import { formatPrice, getBillingType, diff --git a/server/src/internal/customers/attach/attachPreviewUtils/priceToUnusedPreviewItem.ts b/server/src/internal/customers/attach/attachPreviewUtils/priceToUnusedPreviewItem.ts index 387a696f0..fb56adaec 100644 --- a/server/src/internal/customers/attach/attachPreviewUtils/priceToUnusedPreviewItem.ts +++ b/server/src/internal/customers/attach/attachPreviewUtils/priceToUnusedPreviewItem.ts @@ -11,27 +11,16 @@ import { } from "@autumn/shared"; import { logger } from "better-auth"; import Stripe from "stripe"; -import { isTrialing } from "../../cusProducts/cusProductUtils.js"; -import { - formatUnixToDate, - formatUnixToDateTime, - notNullish, -} from "@/utils/genUtils.js"; +import { isTrialing } from "@autumn/shared"; +import { formatUnixToDate, notNullish } from "@/utils/genUtils.js"; import { priceToUsageModel } from "@/internal/products/prices/priceUtils/convertPrice.js"; import { - formatPrice, getPriceEntitlement, getPriceOptions, } from "@/internal/products/prices/priceUtils.js"; -import { - cusProductToEnts, - cusProductToProduct, -} from "../../cusProducts/cusProductUtils/convertCusProduct.js"; +import { cusProductToEnts } from "@autumn/shared"; import { isFixedPrice } from "@/internal/products/prices/priceUtils/usagePriceUtils/classifyUsagePrice.js"; -import { - getAmountAfterStripeDiscounts, - getUnusedAmountAfterDiscount, -} from "@/internal/rewards/rewardUtils.js"; +import { getUnusedAmountAfterDiscount } from "@/internal/rewards/rewardUtils.js"; import { Decimal } from "decimal.js"; const getDiscountsApplied = ({ diff --git a/server/src/internal/customers/attach/attachUtils/attachParams/attachParamsUtils/getPricesAndEnts.ts b/server/src/internal/customers/attach/attachUtils/attachParams/attachParamsUtils/getPricesAndEnts.ts index c50ea9fbf..738af9161 100644 --- a/server/src/internal/customers/attach/attachUtils/attachParams/attachParamsUtils/getPricesAndEnts.ts +++ b/server/src/internal/customers/attach/attachUtils/attachParams/attachParamsUtils/getPricesAndEnts.ts @@ -1,7 +1,4 @@ -import { - cusProductToPrices, - cusProductToEnts, -} from "@/internal/customers/cusProducts/cusProductUtils/convertCusProduct.js"; +import { cusProductToPrices, cusProductToEnts } from "@autumn/shared"; import { getExistingCusProducts } from "@/internal/customers/cusProducts/cusProductUtils/getExistingCusProducts.js"; import { getEntsWithFeature } from "@/internal/products/entitlements/entitlementUtils.js"; import { diff --git a/server/src/internal/customers/attach/attachUtils/attachParams/convertToParams.ts b/server/src/internal/customers/attach/attachUtils/attachParams/convertToParams.ts index 3ec99a978..8ff2bf59d 100644 --- a/server/src/internal/customers/attach/attachUtils/attachParams/convertToParams.ts +++ b/server/src/internal/customers/attach/attachUtils/attachParams/convertToParams.ts @@ -6,7 +6,7 @@ import { cusProductToEnts, cusProductToPrices, cusProductToProduct, -} from "@/internal/customers/cusProducts/cusProductUtils/convertCusProduct.js"; +} from "@autumn/shared"; import { newCusToFullCus } from "@/internal/customers/cusUtils/cusUtils.js"; import { ExtendedRequest } from "@/utils/models/Request.js"; import { diff --git a/server/src/internal/customers/attach/attachUtils/attachUtils.ts b/server/src/internal/customers/attach/attachUtils/attachUtils.ts index a75ef3f09..a8c55326f 100644 --- a/server/src/internal/customers/attach/attachUtils/attachUtils.ts +++ b/server/src/internal/customers/attach/attachUtils/attachUtils.ts @@ -11,7 +11,7 @@ import { } from "./convertAttachParams.js"; import { FullCusProduct } from "@autumn/shared"; import { subItemInCusProduct } from "@/external/stripe/stripeSubUtils/stripeSubItemUtils.js"; -import { isTrialing } from "../../cusProducts/cusProductUtils.js"; +import { isTrialing } from "@autumn/shared"; import { ACTIVE_STATUSES } from "../../cusProducts/CusProductService.js"; export const getCycleWillReset = ({ diff --git a/server/src/internal/customers/attach/attachUtils/convertAttachParams.ts b/server/src/internal/customers/attach/attachUtils/convertAttachParams.ts index b6dd3e30a..c590d8a1a 100644 --- a/server/src/internal/customers/attach/attachUtils/convertAttachParams.ts +++ b/server/src/internal/customers/attach/attachUtils/convertAttachParams.ts @@ -1,9 +1,7 @@ import { AttachParams } from "@/internal/customers/cusProducts/AttachParams.js"; import { getExistingCusProducts } from "../../cusProducts/cusProductUtils/getExistingCusProducts.js"; -import { CusProductStatus } from "@autumn/shared"; +import { cusProductToProduct } from "@autumn/shared"; import Stripe from "stripe"; -import { cusProductToProduct } from "../../cusProducts/cusProductUtils/convertCusProduct.js"; -import { isTrialing } from "../../cusProducts/cusProductUtils.js"; export const attachParamsToCurCusProduct = ({ attachParams, diff --git a/server/src/internal/customers/attach/attachUtils/getAttachBranch.ts b/server/src/internal/customers/attach/attachUtils/getAttachBranch.ts index a65072887..afd9ba39f 100644 --- a/server/src/internal/customers/attach/attachUtils/getAttachBranch.ts +++ b/server/src/internal/customers/attach/attachUtils/getAttachBranch.ts @@ -12,10 +12,7 @@ import { isFreeProduct, isProductUpgrade, } from "@/internal/products/productUtils.js"; -import { - cusProductToPrices, - cusProductToProduct, -} from "../../cusProducts/cusProductUtils/convertCusProduct.js"; +import { cusProductToPrices, cusProductToProduct } from "@autumn/shared"; import { FeatureOptions, FullCusProduct } from "@autumn/shared"; import { productsAreSame } from "@/internal/products/productUtils/compareProductUtils.js"; import { hasPrepaidPrice } from "@/internal/products/prices/priceUtils/usagePriceUtils/classifyUsagePrice.js"; diff --git a/server/src/internal/customers/attach/attachUtils/getAttachConfig.ts b/server/src/internal/customers/attach/attachUtils/getAttachConfig.ts index 5b0f0c90a..f8f34fcc4 100644 --- a/server/src/internal/customers/attach/attachUtils/getAttachConfig.ts +++ b/server/src/internal/customers/attach/attachUtils/getAttachConfig.ts @@ -12,7 +12,7 @@ import { nullish, notNullish } from "@/utils/genUtils.js"; import { ProrationBehavior } from "@autumn/shared"; import { attachParamsToProduct } from "./convertAttachParams.js"; import { attachParamToCusProducts } from "./convertAttachParams.js"; -import { cusProductToPrices } from "../../cusProducts/cusProductUtils/convertCusProduct.js"; +import { cusProductToPrices } from "@autumn/shared"; import { willMergeSub } from "../mergeUtils/mergeUtils.js"; export const intervalsAreSame = ({ diff --git a/server/src/internal/customers/attach/attachUtils/getContUseItems/createContUseInvoiceItems.ts b/server/src/internal/customers/attach/attachUtils/getContUseItems/createContUseInvoiceItems.ts index afed93eb9..df2c24037 100644 --- a/server/src/internal/customers/attach/attachUtils/getContUseItems/createContUseInvoiceItems.ts +++ b/server/src/internal/customers/attach/attachUtils/getContUseItems/createContUseInvoiceItems.ts @@ -10,7 +10,7 @@ import { import Stripe from "stripe"; import { getContUseInvoiceItems } from "./getContUseInvoiceItems.js"; import { findPriceInStripeItems } from "@/external/stripe/stripeSubUtils/stripeSubItemUtils.js"; -import { cusProductToPrices } from "@/internal/customers/cusProducts/cusProductUtils/convertCusProduct.js"; +import { cusProductToPrices } from "@autumn/shared"; import { attachParamsToProduct } from "../convertAttachParams.js"; import { subToAutumnInterval } from "@/external/stripe/utils.js"; import { intervalsAreSame } from "../getAttachConfig.js"; diff --git a/server/src/internal/customers/attach/attachUtils/getContUseItems/priceToContUseItem.ts b/server/src/internal/customers/attach/attachUtils/getContUseItems/priceToContUseItem.ts index 4d3750e7f..5d626b92b 100644 --- a/server/src/internal/customers/attach/attachUtils/getContUseItems/priceToContUseItem.ts +++ b/server/src/internal/customers/attach/attachUtils/getContUseItems/priceToContUseItem.ts @@ -17,7 +17,7 @@ import { getContUseUpgradeItems } from "./getContUseUpgradeItems.js"; import { priceToInvoiceItem } from "@/internal/products/prices/priceUtils/priceToInvoiceItem.js"; import { Decimal } from "decimal.js"; import { notNullish } from "@/utils/genUtils.js"; -import { cusProductsToCusPrices } from "@/internal/customers/cusProducts/cusProductUtils/convertCusProduct.js"; +import { cusProductsToCusPrices } from "@autumn/shared"; import { findStripeItemForPrice } from "@/external/stripe/stripeSubUtils/stripeSubItemUtils.js"; import { subToPeriodStartEnd } from "@/external/stripe/stripeSubUtils/convertSubUtils.js"; diff --git a/server/src/internal/customers/attach/attachUtils/handleAttachErrors.ts b/server/src/internal/customers/attach/attachUtils/handleAttachErrors.ts index ba3173d1b..03d963be7 100644 --- a/server/src/internal/customers/attach/attachUtils/handleAttachErrors.ts +++ b/server/src/internal/customers/attach/attachUtils/handleAttachErrors.ts @@ -20,10 +20,7 @@ import { getBillingType } from "@/internal/products/prices/priceUtils.js"; import { BillingType } from "@autumn/shared"; import { notNullish, nullOrUndefined } from "@/utils/genUtils.js"; import { attachParamToCusProducts } from "./convertAttachParams.js"; -import { - cusProductsToCusEnts, - cusProductToPrices, -} from "../../cusProducts/cusProductUtils/convertCusProduct.js"; +import { cusProductsToCusEnts, cusProductToPrices } from "@autumn/shared"; import { findPriceForFeature } from "@/internal/products/prices/priceUtils/findPriceUtils.js"; import { getResetBalance } from "../../cusProducts/cusEnts/cusEntUtils.js"; import { Decimal } from "decimal.js"; diff --git a/server/src/internal/customers/attach/checkout/getHasProrations.ts b/server/src/internal/customers/attach/checkout/getHasProrations.ts index 2b99bd981..1f45a96c4 100644 --- a/server/src/internal/customers/attach/checkout/getHasProrations.ts +++ b/server/src/internal/customers/attach/checkout/getHasProrations.ts @@ -5,7 +5,7 @@ import { attachParamsToCurCusProduct, attachParamToCusProducts, } from "../attachUtils/convertAttachParams.js"; -import { cusProductToPrices } from "../../cusProducts/cusProductUtils/convertCusProduct.js"; +import { cusProductToPrices } from "@autumn/shared"; import { isFreeProduct } from "@/internal/products/productUtils.js"; export const getHasProrations = async ({ diff --git a/server/src/internal/customers/attach/checkout/previewToCheckoutRes.ts b/server/src/internal/customers/attach/checkout/previewToCheckoutRes.ts index fc3e05779..e1fa4a686 100644 --- a/server/src/internal/customers/attach/checkout/previewToCheckoutRes.ts +++ b/server/src/internal/customers/attach/checkout/previewToCheckoutRes.ts @@ -15,7 +15,7 @@ import { cusProductToEnts, cusProductToPrices, cusProductToProduct, -} from "../../cusProducts/cusProductUtils/convertCusProduct.js"; +} from "@autumn/shared"; import { ExtendedRequest } from "@/utils/models/Request.js"; import { getProductItemResponse, diff --git a/server/src/internal/customers/attach/handleAttachPreview/attachParamsToPreview.ts b/server/src/internal/customers/attach/handleAttachPreview/attachParamsToPreview.ts index 997a53ad0..79d4e5684 100644 --- a/server/src/internal/customers/attach/handleAttachPreview/attachParamsToPreview.ts +++ b/server/src/internal/customers/attach/handleAttachPreview/attachParamsToPreview.ts @@ -5,7 +5,7 @@ import { getAttachBranch } from "../attachUtils/getAttachBranch.js"; import { getAttachConfig } from "../attachUtils/getAttachConfig.js"; import { AttachFunction } from "@autumn/shared"; import { getAttachFunction } from "../attachUtils/getAttachFunction.js"; -import { cusProductToProduct } from "../../cusProducts/cusProductUtils/convertCusProduct.js"; +import { cusProductToProduct } from "@autumn/shared"; import { attachParamToCusProducts } from "../attachUtils/convertAttachParams.js"; import { getDowngradeProductPreview } from "./getDowngradeProductPreview.js"; import { getNewProductPreview } from "./getNewProductPreview.js"; diff --git a/server/src/internal/customers/attach/handleAttachPreview/getMultiAttachPreview.ts b/server/src/internal/customers/attach/handleAttachPreview/getMultiAttachPreview.ts index 4e8eda4d7..f06ed04e1 100644 --- a/server/src/internal/customers/attach/handleAttachPreview/getMultiAttachPreview.ts +++ b/server/src/internal/customers/attach/handleAttachPreview/getMultiAttachPreview.ts @@ -1,9 +1,10 @@ import { ExtendedRequest } from "@/utils/models/Request.js"; import { AttachParams } from "../../cusProducts/AttachParams.js"; import { AttachBody, AttachBranch, PreviewLineItem } from "@autumn/shared"; +import { cusProductsToPrices } from "@autumn/shared"; + import { getCustomerSub } from "../attachUtils/convertAttachParams.js"; -import { cusProductsToPrices } from "../../cusProducts/cusProductUtils/convertCusProduct.js"; import { priceToUnusedPreviewItem } from "../attachPreviewUtils/priceToUnusedPreviewItem.js"; import { handleMultiAttachErrors } from "../attachUtils/handleAttachErrors/handleMultiAttachErrors.js"; import { getAddAndRemoveProducts } from "../attachFunctions/multiAttach/getAddAndRemoveProducts.js"; diff --git a/server/src/internal/customers/attach/handleAttachPreview/getNewProductPreview.ts b/server/src/internal/customers/attach/handleAttachPreview/getNewProductPreview.ts index 0a675b665..d7e7f0dd1 100644 --- a/server/src/internal/customers/attach/handleAttachPreview/getNewProductPreview.ts +++ b/server/src/internal/customers/attach/handleAttachPreview/getNewProductPreview.ts @@ -24,7 +24,7 @@ import { getSmallestInterval, } from "@/internal/products/prices/priceUtils/priceIntervalUtils.js"; import { subToPeriodStartEnd } from "@/external/stripe/stripeSubUtils/convertSubUtils.js"; -import { isTrialing } from "../../cusProducts/cusProductUtils.js"; +import { isTrialing } from "@autumn/shared"; const getNextCycleItems = async ({ newProduct, diff --git a/server/src/internal/customers/attach/handleAttachPreview/getUpdateEntsPreview.ts b/server/src/internal/customers/attach/handleAttachPreview/getUpdateEntsPreview.ts index 48179baca..5af51ed3f 100644 --- a/server/src/internal/customers/attach/handleAttachPreview/getUpdateEntsPreview.ts +++ b/server/src/internal/customers/attach/handleAttachPreview/getUpdateEntsPreview.ts @@ -3,7 +3,7 @@ import { attachParamsToProduct, attachParamToCusProducts, } from "../attachUtils/convertAttachParams.js"; -import { cusProductToProduct } from "../../cusProducts/cusProductUtils/convertCusProduct.js"; +import { cusProductToProduct } from "@autumn/shared"; export const getUpdateEntsPreview = async ({ req, diff --git a/server/src/internal/customers/attach/handleAttachPreview/getUpgradeProductPreview.ts b/server/src/internal/customers/attach/handleAttachPreview/getUpgradeProductPreview.ts index 848ed02c6..650109238 100644 --- a/server/src/internal/customers/attach/handleAttachPreview/getUpgradeProductPreview.ts +++ b/server/src/internal/customers/attach/handleAttachPreview/getUpgradeProductPreview.ts @@ -36,8 +36,8 @@ import { getLatestPeriodEnd, subToPeriodStartEnd, } from "@/external/stripe/stripeSubUtils/convertSubUtils.js"; -import { isTrialing } from "../../cusProducts/cusProductUtils.js"; -import { cusProductToPrices } from "../../cusProducts/cusProductUtils/convertCusProduct.js"; +import { isTrialing } from "@autumn/shared"; +import { cusProductToPrices } from "@autumn/shared"; import { isPrepaidPrice } from "@/internal/products/prices/priceUtils/usagePriceUtils/classifyUsagePrice.js"; const getNextCycleAt = ({ diff --git a/server/src/internal/customers/attach/mergeUtils/mergeUtils.ts b/server/src/internal/customers/attach/mergeUtils/mergeUtils.ts index de1c31aa5..28cd8092b 100644 --- a/server/src/internal/customers/attach/mergeUtils/mergeUtils.ts +++ b/server/src/internal/customers/attach/mergeUtils/mergeUtils.ts @@ -10,7 +10,7 @@ import { import { AttachBranch, Entity, FullCusProduct, Price } from "@autumn/shared"; import Stripe from "stripe"; import { getExistingUsageFromCusProducts } from "../../cusProducts/cusEnts/cusEntUtils.js"; -import { cusProductToEnts } from "../../cusProducts/cusProductUtils/convertCusProduct.js"; +import { cusProductToEnts } from "@autumn/shared"; import { AttachParams } from "../../cusProducts/AttachParams.js"; import { attachParamsToCurCusProduct, diff --git a/server/src/internal/customers/attach/mergeUtils/paramsToScheduleItems.ts b/server/src/internal/customers/attach/mergeUtils/paramsToScheduleItems.ts index af599120c..bfbd9b5f5 100644 --- a/server/src/internal/customers/attach/mergeUtils/paramsToScheduleItems.ts +++ b/server/src/internal/customers/attach/mergeUtils/paramsToScheduleItems.ts @@ -6,7 +6,7 @@ import { AttachParams } from "../../cusProducts/AttachParams.js"; import { getStripeSubItems2 } from "@/external/stripe/stripeSubUtils/getStripeSubItems.js"; import { AttachConfig, FullCusProduct } from "@autumn/shared"; import { ExtendedRequest } from "@/utils/models/Request.js"; -import { cusProductToPrices } from "../../cusProducts/cusProductUtils/convertCusProduct.js"; +import { cusProductToPrices } from "@autumn/shared"; import { isArrearPrice } from "@/internal/products/prices/priceUtils/usagePriceUtils/classifyUsagePrice.js"; import { priceToScheduleItem, diff --git a/server/src/internal/customers/attach/mergeUtils/paramsToSubItems.ts b/server/src/internal/customers/attach/mergeUtils/paramsToSubItems.ts index bc5690635..1a735026e 100644 --- a/server/src/internal/customers/attach/mergeUtils/paramsToSubItems.ts +++ b/server/src/internal/customers/attach/mergeUtils/paramsToSubItems.ts @@ -4,10 +4,7 @@ import { getStripeSubItems2 } from "@/external/stripe/stripeSubUtils/getStripeSu import { AttachConfig, CusProductStatus, FullCusProduct } from "@autumn/shared"; import { getExistingCusProducts } from "../../cusProducts/cusProductUtils/getExistingCusProducts.js"; import { ExtendedRequest } from "@/utils/models/Request.js"; -import { - cusProductToPrices, - cusProductToProduct, -} from "../../cusProducts/cusProductUtils/convertCusProduct.js"; +import { cusProductToPrices, cusProductToProduct } from "@autumn/shared"; import { isArrearPrice } from "@/internal/products/prices/priceUtils/usagePriceUtils/classifyUsagePrice.js"; import { findStripeItemForPrice, diff --git a/server/src/internal/customers/cancel/cancelImmediately.ts b/server/src/internal/customers/cancel/cancelImmediately.ts index c3793b8bb..3ec076e44 100644 --- a/server/src/internal/customers/cancel/cancelImmediately.ts +++ b/server/src/internal/customers/cancel/cancelImmediately.ts @@ -6,16 +6,15 @@ import { FullCusProduct, FullCustomer, } from "@autumn/shared"; -import { - cusProductToProduct, - cusProductToSub, -} from "../cusProducts/cusProductUtils/convertCusProduct.js"; -import { getLatestPeriodEnd } from "@/external/stripe/stripeSubUtils/convertSubUtils.js"; + +import { cusProductToProduct } from "@autumn/shared"; + import { CusProductService } from "../cusProducts/CusProductService.js"; import { activateDefaultProduct } from "../cusProducts/cusProductUtils.js"; import { getExistingCusProducts } from "../cusProducts/cusProductUtils/getExistingCusProducts.js"; import { addProductsUpdatedWebhookTask } from "@/internal/analytics/handlers/handleProductsUpdated.js"; import { isOneOff } from "@/internal/products/productUtils.js"; +import { cusProductToSub } from "../cusProducts/cusProductUtils/convertCusProduct.js"; export const cancelImmediately = async ({ req, diff --git a/server/src/internal/customers/cancel/handleCancelProduct.ts b/server/src/internal/customers/cancel/handleCancelProduct.ts index ad086fe6a..2540da30e 100644 --- a/server/src/internal/customers/cancel/handleCancelProduct.ts +++ b/server/src/internal/customers/cancel/handleCancelProduct.ts @@ -11,10 +11,7 @@ import { EntitlementWithFeature, } from "@autumn/shared"; import { getExistingCusProducts } from "../cusProducts/cusProductUtils/getExistingCusProducts.js"; -import { - cusProductToPrices, - cusProductToProduct, -} from "../cusProducts/cusProductUtils/convertCusProduct.js"; +import { cusProductToPrices, cusProductToProduct } from "@autumn/shared"; import { createStripeCli } from "@/external/stripe/utils.js"; import RecaseError from "@/utils/errorUtils.js"; import { StatusCodes } from "http-status-codes"; diff --git a/server/src/internal/customers/cusProducts/cusEnts/cusEntUtils.ts b/server/src/internal/customers/cusProducts/cusEnts/cusEntUtils.ts index 5497cbacb..7fb8f9980 100644 --- a/server/src/internal/customers/cusProducts/cusEnts/cusEntUtils.ts +++ b/server/src/internal/customers/cusProducts/cusEnts/cusEntUtils.ts @@ -118,131 +118,131 @@ export const getCusEntBalance = ({ }; }; -export const sortCusEntsForDeduction = ( - cusEnts: (FullCustomerEntitlement & { - customer_product?: FullCusProduct; - })[], - reverseOrder: boolean = false -) => { - let intervalOrder: Record = { - [EntInterval.Minute]: 0, // 1 minute - [EntInterval.Hour]: 1, // 1 hour - [EntInterval.Day]: 2, // 1 day - [EntInterval.Week]: 3, // 1 week - [EntInterval.Month]: 4, // 1 month - [EntInterval.Quarter]: 5, // 3 months - [EntInterval.Year]: 6, // 1 year - [EntInterval.SemiAnnual]: 7, // 6 months - [EntInterval.Lifetime]: 8, // 1 time - }; +// export const sortCusEntsForDeduction = ( +// cusEnts: (FullCustomerEntitlement & { +// customer_product?: FullCusProduct; +// })[], +// reverseOrder: boolean = false +// ) => { +// let intervalOrder: Record = { +// [EntInterval.Minute]: 0, // 1 minute +// [EntInterval.Hour]: 1, // 1 hour +// [EntInterval.Day]: 2, // 1 day +// [EntInterval.Week]: 3, // 1 week +// [EntInterval.Month]: 4, // 1 month +// [EntInterval.Quarter]: 5, // 3 months +// [EntInterval.Year]: 6, // 1 year +// [EntInterval.SemiAnnual]: 7, // 6 months +// [EntInterval.Lifetime]: 8, // 1 time +// }; - // console.log( - // `Cus ents before (${reverseOrder ? "reversed" : "normal"})`, - // cusEnts.map( - // (ce) => `${ce.entitlement.feature_id} - ${ce.entitlement.interval}` - // ) - // ); - cusEnts.sort((a, b) => { - const aEnt = a.entitlement; - const bEnt = b.entitlement; +// // console.log( +// // `Cus ents before (${reverseOrder ? "reversed" : "normal"})`, +// // cusEnts.map( +// // (ce) => `${ce.entitlement.feature_id} - ${ce.entitlement.interval}` +// // ) +// // ); +// cusEnts.sort((a, b) => { +// const aEnt = a.entitlement; +// const bEnt = b.entitlement; - // 1. If boolean, go first - if (aEnt.feature.type == FeatureType.Boolean) { - return -1; - } +// // 1. If boolean, go first +// if (aEnt.feature.type == FeatureType.Boolean) { +// return -1; +// } - if (bEnt.feature.type == FeatureType.Boolean) { - return 1; - } +// if (bEnt.feature.type == FeatureType.Boolean) { +// return 1; +// } - // 1. If a is credit system and b is not, a should go last - if ( - aEnt.feature.type == FeatureType.CreditSystem && - bEnt.feature.type != FeatureType.CreditSystem - ) { - return 1; - } +// // 1. If a is credit system and b is not, a should go last +// if ( +// aEnt.feature.type == FeatureType.CreditSystem && +// bEnt.feature.type != FeatureType.CreditSystem +// ) { +// return 1; +// } - // 2. If a is not credit system and b is, a should go first - if ( - aEnt.feature.type != FeatureType.CreditSystem && - bEnt.feature.type == FeatureType.CreditSystem - ) { - return -1; - } +// // 2. If a is not credit system and b is, a should go first +// if ( +// aEnt.feature.type != FeatureType.CreditSystem && +// bEnt.feature.type == FeatureType.CreditSystem +// ) { +// return -1; +// } - // 2. Sort by unlimited (unlimited goes first) - if ( - aEnt.allowance_type == AllowanceType.Unlimited && - bEnt.allowance_type != AllowanceType.Unlimited - ) { - return -1; - } +// // 2. Sort by unlimited (unlimited goes first) +// if ( +// aEnt.allowance_type == AllowanceType.Unlimited && +// bEnt.allowance_type != AllowanceType.Unlimited +// ) { +// return -1; +// } - if ( - aEnt.allowance_type != AllowanceType.Unlimited && - bEnt.allowance_type == AllowanceType.Unlimited - ) { - return 1; - } +// if ( +// aEnt.allowance_type != AllowanceType.Unlimited && +// bEnt.allowance_type == AllowanceType.Unlimited +// ) { +// return 1; +// } - // If one has usage_allowed, it should go last - if (!a.usage_allowed && b.usage_allowed) { - return -1; - } +// // If one has usage_allowed, it should go last +// if (!a.usage_allowed && b.usage_allowed) { +// return -1; +// } - if (!b.usage_allowed && a.usage_allowed) { - return 1; - } +// if (!b.usage_allowed && a.usage_allowed) { +// return 1; +// } - // If one has a next_reset_at, it should go first - let nextResetFirst = reverseOrder ? 1 : -1; +// // If one has a next_reset_at, it should go first +// let nextResetFirst = reverseOrder ? 1 : -1; - if (a.next_reset_at && !b.next_reset_at) { - return nextResetFirst; - } +// if (a.next_reset_at && !b.next_reset_at) { +// return nextResetFirst; +// } - // If b has a next_reset_at, it should go first - if (!a.next_reset_at && b.next_reset_at) { - return -nextResetFirst; - } +// // If b has a next_reset_at, it should go first +// if (!a.next_reset_at && b.next_reset_at) { +// return -nextResetFirst; +// } - // 3. Sort by interval - let aVal = entIntervalToValue(aEnt.interval, aEnt.interval_count); - let bVal = entIntervalToValue(bEnt.interval, bEnt.interval_count); - if (aEnt.interval && bEnt.interval && !aVal.eq(bVal)) { - if (reverseOrder) { - return bVal.sub(aVal).toNumber(); - // return intervalOrder[bEnt.interval] - intervalOrder[aEnt.interval]; - } else { - return aVal.sub(bVal).toNumber(); - // return intervalOrder[aEnt.interval] - intervalOrder[bEnt.interval]; - } - } +// // 3. Sort by interval +// let aVal = entIntervalToValue(aEnt.interval, aEnt.interval_count); +// let bVal = entIntervalToValue(bEnt.interval, bEnt.interval_count); +// if (aEnt.interval && bEnt.interval && !aVal.eq(bVal)) { +// if (reverseOrder) { +// return bVal.sub(aVal).toNumber(); +// // return intervalOrder[bEnt.interval] - intervalOrder[aEnt.interval]; +// } else { +// return aVal.sub(bVal).toNumber(); +// // return intervalOrder[aEnt.interval] - intervalOrder[bEnt.interval]; +// } +// } - // Check if a is main product - let aIsAddOn = a.customer_product?.product?.is_add_on; - let bIsAddOn = b.customer_product?.product?.is_add_on; +// // Check if a is main product +// let aIsAddOn = a.customer_product?.product?.is_add_on; +// let bIsAddOn = b.customer_product?.product?.is_add_on; - if (aIsAddOn && !bIsAddOn) { - return 1; - } +// if (aIsAddOn && !bIsAddOn) { +// return 1; +// } - if (!aIsAddOn && bIsAddOn) { - return -1; - } +// if (!aIsAddOn && bIsAddOn) { +// return -1; +// } - // 4. Sort by created_at - return a.created_at - b.created_at; - }); +// // 4. Sort by created_at +// return a.created_at - b.created_at; +// }); - // console.log( - // `Cus ents after (${reverseOrder ? "reversed" : "normal"})`, - // cusEnts.map( - // (ce) => `${ce.entitlement.feature_id} - ${ce.entitlement.interval}` - // ) - // ); -}; +// // console.log( +// // `Cus ents after (${reverseOrder ? "reversed" : "normal"})`, +// // cusEnts.map( +// // (ce) => `${ce.entitlement.feature_id} - ${ce.entitlement.interval}` +// // ) +// // ); +// }; // Get related cusPrice export const getRelatedCusPrice = ( diff --git a/server/src/internal/customers/cusProducts/cusEnts/cusEntUtils/getExistingUsage.ts b/server/src/internal/customers/cusProducts/cusEnts/cusEntUtils/getExistingUsage.ts index 08de21d29..82363d236 100644 --- a/server/src/internal/customers/cusProducts/cusEnts/cusEntUtils/getExistingUsage.ts +++ b/server/src/internal/customers/cusProducts/cusEnts/cusEntUtils/getExistingUsage.ts @@ -15,11 +15,11 @@ import { getRelatedCusPrice, getResetBalance, getUnlimitedAndUsageAllowed, - sortCusEntsForDeduction, } from "../cusEntUtils.js"; import { getEntOptions } from "@/internal/products/prices/priceUtils.js"; import { notNullish, nullish } from "@/utils/genUtils.js"; import { performDeductionOnCusEnt } from "@/trigger/updateBalanceTask.js"; +import { sortCusEntsForDeduction } from "@autumn/shared"; export const getExistingCusEntAndUsage = async ({ curCusProduct, diff --git a/server/src/internal/customers/cusProducts/cusProductUtils.ts b/server/src/internal/customers/cusProducts/cusProductUtils.ts index a20aad387..c5986d93c 100644 --- a/server/src/internal/customers/cusProducts/cusProductUtils.ts +++ b/server/src/internal/customers/cusProducts/cusProductUtils.ts @@ -36,12 +36,9 @@ import { addProductsUpdatedWebhookTask } from "@/internal/analytics/handlers/han import { DrizzleCli } from "@/db/initDrizzle.js"; import { getExistingCusProducts } from "./cusProductUtils/getExistingCusProducts.js"; import { ExtendedRequest } from "@/utils/models/Request.js"; -import { cusProductToPrices } from "./cusProductUtils/convertCusProduct.js"; +import { cusProductToPrices } from "@autumn/shared"; import { isFreeProduct, isOneOff } from "@/internal/products/productUtils.js"; -import { - isDefaultTrial, - isDefaultTrialFullProduct, -} from "@/internal/products/productUtils/classifyProduct.js"; +import { isDefaultTrialFullProduct } from "@/internal/products/productUtils/classifyProduct.js"; import { initStripeCusAndProducts } from "../handlers/handleCreateCustomer.js"; import { handleAddProduct } from "../attach/attachFunctions/addProductFlow/handleAddProduct.js"; import { newCusToAttachParams } from "../attach/attachUtils/attachParams/convertToParams.js"; @@ -603,18 +600,6 @@ export const searchCusProducts = ({ }); }; -export const isTrialing = ({ - cusProduct, - now, -}: { - cusProduct: FullCusProduct; - now?: number; -}) => { - return ( - cusProduct.trial_ends_at && cusProduct.trial_ends_at > (now || Date.now()) - ); -}; - export const getMainCusProduct = async ({ db, internalCustomerId, diff --git a/server/src/internal/customers/cusProducts/cusProductUtils/convertCusProduct.ts b/server/src/internal/customers/cusProducts/cusProductUtils/convertCusProduct.ts index 2c9684bd5..5c7e72f33 100644 --- a/server/src/internal/customers/cusProducts/cusProductUtils/convertCusProduct.ts +++ b/server/src/internal/customers/cusProducts/cusProductUtils/convertCusProduct.ts @@ -1,6 +1,7 @@ import { BillingType, CusProductStatus, + cusProductToPrices, FullCusEntWithFullCusProduct, FullCusProduct, FullCustomerEntitlement, @@ -8,128 +9,125 @@ import { FullProduct, Product, } from "@autumn/shared"; -import { sortCusEntsForDeduction } from "../cusEnts/cusEntUtils.js"; +import { sortCusEntsForDeduction } from "@autumn/shared"; import { getStripeSchedules, getStripeSubs, } from "@/external/stripe/stripeSubUtils.js"; import Stripe from "stripe"; -import { DrizzleCli } from "@/db/initDrizzle.js"; import { getBillingType } from "@/internal/products/prices/priceUtils.js"; -import { ACTIVE_STATUSES } from "../CusProductService.js"; -export const cusProductsToPrices = ({ - cusProducts, -}: { - cusProducts: FullCusProduct[]; -}) => { - return cusProducts.flatMap((cp) => cusProductToPrices({ cusProduct: cp })); -}; +// export const cusProductsToPrices = ({ +// cusProducts, +// }: { +// cusProducts: FullCusProduct[]; +// }) => { +// return cusProducts.flatMap((cp) => cusProductToPrices({ cusProduct: cp })); +// }; -export const cusProductsToCusPrices = ({ - cusProducts, - inStatuses, - billingType, -}: { - cusProducts: FullCusProduct[]; - inStatuses?: CusProductStatus[]; - billingType?: BillingType; -}) => { - const cusPrices: FullCustomerPrice[] = []; +// export const cusProductsToCusPrices = ({ +// cusProducts, +// inStatuses, +// billingType, +// }: { +// cusProducts: FullCusProduct[]; +// inStatuses?: CusProductStatus[]; +// billingType?: BillingType; +// }) => { +// const cusPrices: FullCustomerPrice[] = []; - for (const cusProduct of cusProducts) { - if (inStatuses && !inStatuses.includes(cusProduct.status)) { - continue; - } +// for (const cusProduct of cusProducts) { +// if (inStatuses && !inStatuses.includes(cusProduct.status)) { +// continue; +// } - let prices = cusProduct.customer_prices; - if (billingType) { - prices = prices.filter( - (cp) => getBillingType(cp.price.config) === billingType - ); - } +// let prices = cusProduct.customer_prices; +// if (billingType) { +// prices = prices.filter( +// (cp) => getBillingType(cp.price.config) === billingType +// ); +// } - cusPrices.push(...prices); - } +// cusPrices.push(...prices); +// } - return cusPrices; -}; +// return cusPrices; +// }; -export const cusProductsToCusEnts = ({ - cusProducts, - inStatuses = [CusProductStatus.Active], - reverseOrder = false, - featureId, -}: { - cusProducts: FullCusProduct[]; - inStatuses?: CusProductStatus[]; - reverseOrder?: boolean; - featureId?: string; -}) => { - let cusEnts: FullCustomerEntitlement[] = []; +// export const cusProductsToCusEnts = ({ +// cusProducts, +// inStatuses = [CusProductStatus.Active], +// reverseOrder = false, +// featureId, +// }: { +// cusProducts: FullCusProduct[]; +// inStatuses?: CusProductStatus[]; +// reverseOrder?: boolean; +// featureId?: string; +// }) => { +// let cusEnts: FullCustomerEntitlement[] = []; - for (const cusProduct of cusProducts) { - if (!inStatuses.includes(cusProduct.status)) { - continue; - } +// for (const cusProduct of cusProducts) { +// if (!inStatuses.includes(cusProduct.status)) { +// continue; +// } - cusEnts.push( - ...cusProduct.customer_entitlements.map((cusEnt) => ({ - ...cusEnt, - customer_product: cusProduct, - })) - ); - } +// cusEnts.push( +// ...cusProduct.customer_entitlements.map((cusEnt) => ({ +// ...cusEnt, +// customer_product: cusProduct, +// })) +// ); +// } - if (featureId) { - cusEnts = cusEnts.filter( - (cusEnt) => cusEnt.entitlement.feature_id === featureId - ); - } +// if (featureId) { +// cusEnts = cusEnts.filter( +// (cusEnt) => cusEnt.entitlement.feature_id === featureId +// ); +// } - sortCusEntsForDeduction(cusEnts, reverseOrder); +// sortCusEntsForDeduction(cusEnts, reverseOrder); - return cusEnts as FullCusEntWithFullCusProduct[]; -}; +// return cusEnts as FullCusEntWithFullCusProduct[]; +// }; -export const cusProductToPrices = ({ - cusProduct, - billingType, -}: { - cusProduct: FullCusProduct; - billingType?: BillingType; -}) => { - let prices = cusProduct.customer_prices.map((cp) => cp.price); +// export const cusProductToPrices = ({ +// cusProduct, +// billingType, +// }: { +// cusProduct: FullCusProduct; +// billingType?: BillingType; +// }) => { +// let prices = cusProduct.customer_prices.map((cp) => cp.price); - if (billingType) { - prices = prices.filter((p) => getBillingType(p.config) === billingType); - } +// if (billingType) { +// prices = prices.filter((p) => getBillingType(p.config) === billingType); +// } - return prices; -}; +// return prices; +// }; -export const cusProductToEnts = ({ - cusProduct, -}: { - cusProduct: FullCusProduct; -}) => { - return cusProduct.customer_entitlements.map((ce) => ce.entitlement); -}; +// export const cusProductToEnts = ({ +// cusProduct, +// }: { +// cusProduct: FullCusProduct; +// }) => { +// return cusProduct.customer_entitlements.map((ce) => ce.entitlement); +// }; -export const cusProductToProduct = ({ - cusProduct, -}: { - cusProduct: FullCusProduct; -}) => { - return { - ...cusProduct.product, - prices: cusProductToPrices({ cusProduct }), - entitlements: cusProductToEnts({ cusProduct }), - free_trial: cusProduct.free_trial, - } as FullProduct; -}; +// export const cusProductToProduct = ({ +// cusProduct, +// }: { +// cusProduct: FullCusProduct; +// }) => { +// return { +// ...cusProduct.product, +// prices: cusProductToPrices({ cusProduct }), +// entitlements: cusProductToEnts({ cusProduct }), +// free_trial: cusProduct.free_trial, +// } as FullProduct; +// }; -// Subs, schedules export const cusProductsToSchedules = ({ cusProducts, stripeCli, diff --git a/server/src/internal/customers/cusProducts/cusProductUtils/getUpcomingSchedules.ts b/server/src/internal/customers/cusProducts/cusProductUtils/getUpcomingSchedules.ts index 818daccbd..b590aaafb 100644 --- a/server/src/internal/customers/cusProducts/cusProductUtils/getUpcomingSchedules.ts +++ b/server/src/internal/customers/cusProducts/cusProductUtils/getUpcomingSchedules.ts @@ -1,6 +1,6 @@ import { formatUnixToDateTime } from "@/utils/genUtils.js"; import { CusProductStatus, FullCusProduct } from "@autumn/shared"; -import { cusProductToPrices } from "./convertCusProduct.js"; +import { cusProductToPrices } from "@autumn/shared"; import { isFreeProduct, isOneOff } from "@/internal/products/productUtils.js"; export type ScheduledStartStatusPair = { diff --git a/server/src/internal/customers/cusUtils/cusFeatureResponseUtils/getCusFeaturesResponse.ts b/server/src/internal/customers/cusUtils/cusFeatureResponseUtils/getCusFeaturesResponse.ts index c39b6a363..0e19f5dfb 100644 --- a/server/src/internal/customers/cusUtils/cusFeatureResponseUtils/getCusFeaturesResponse.ts +++ b/server/src/internal/customers/cusUtils/cusFeatureResponseUtils/getCusFeaturesResponse.ts @@ -5,12 +5,8 @@ import { Entity, APIVersion, } from "@autumn/shared"; -import { - cusProductsToCusEnts, - cusProductsToCusPrices, -} from "../../cusProducts/cusProductUtils/convertCusProduct.js"; +import { cusProductsToCusEnts, cusProductsToCusPrices } from "@autumn/shared"; import { getCusBalances } from "./getCusBalances.js"; -import { ACTIVE_STATUSES } from "../../cusProducts/CusProductService.js"; export const getCusFeaturesResponse = async ({ cusProducts, diff --git a/server/src/internal/customers/cusUtils/cusUtils.ts b/server/src/internal/customers/cusUtils/cusUtils.ts index d5d7fdd52..84e13c48f 100644 --- a/server/src/internal/customers/cusUtils/cusUtils.ts +++ b/server/src/internal/customers/cusUtils/cusUtils.ts @@ -9,12 +9,12 @@ import { Invoice, InvoiceResponse, Organization, + sortCusEntsForDeduction, } from "@autumn/shared"; import { CusService } from "@/internal/customers/CusService.js"; import { InvoiceService } from "@/internal/invoices/InvoiceService.js"; -import { sortCusEntsForDeduction } from "@/internal/customers/cusProducts/cusEnts/cusEntUtils.js"; import { StatusCodes } from "http-status-codes"; import { notNullish, nullish } from "@/utils/genUtils.js"; import { DrizzleCli } from "@/db/initDrizzle.js"; diff --git a/server/src/internal/customers/cusUtils/getCustomerDetails.ts b/server/src/internal/customers/cusUtils/getCustomerDetails.ts index ea8411a18..cf74533ea 100644 --- a/server/src/internal/customers/cusUtils/getCustomerDetails.ts +++ b/server/src/internal/customers/cusUtils/getCustomerDetails.ts @@ -19,10 +19,8 @@ import { getCusInvoices } from "./cusUtils.js"; import { orgToVersion } from "@/utils/versionUtils.js"; import { DrizzleCli } from "@/db/initDrizzle.js"; -import { - cusProductsToCusEnts, - cusProductsToCusPrices, -} from "../cusProducts/cusProductUtils/convertCusProduct.js"; +import { cusProductsToCusEnts, cusProductsToCusPrices } from "@autumn/shared"; + import { invoicesToResponse } from "@/internal/invoices/invoiceUtils.js"; import { getCusBalances } from "./cusFeatureResponseUtils/getCusBalances.js"; import { featuresToObject } from "./cusFeatureResponseUtils/balancesToFeatureResponse.js"; diff --git a/server/src/internal/customers/handlers/handleCusProductExpired.ts b/server/src/internal/customers/handlers/handleCusProductExpired.ts index 278137144..684fe7a2a 100644 --- a/server/src/internal/customers/handlers/handleCusProductExpired.ts +++ b/server/src/internal/customers/handlers/handleCusProductExpired.ts @@ -23,7 +23,7 @@ import { } from "@autumn/shared"; import { StatusCodes } from "http-status-codes"; import { CusService } from "../CusService.js"; -import { cusProductToPrices } from "../cusProducts/cusProductUtils/convertCusProduct.js"; +import { cusProductToPrices } from "@autumn/shared"; export const expireCusProduct = async ({ req, diff --git a/server/src/internal/customers/handlers/handleTransferProduct/handleDecreaseAndTransfer.ts b/server/src/internal/customers/handlers/handleTransferProduct/handleDecreaseAndTransfer.ts index 41cc5d976..664d8b2cc 100644 --- a/server/src/internal/customers/handlers/handleTransferProduct/handleDecreaseAndTransfer.ts +++ b/server/src/internal/customers/handlers/handleTransferProduct/handleDecreaseAndTransfer.ts @@ -8,7 +8,7 @@ import { } from "@autumn/shared"; import { createFullCusProduct } from "../../add-product/createFullCusProduct.js"; import { attachToInsertParams } from "@/internal/products/productUtils.js"; -import { cusProductToProduct } from "../../cusProducts/cusProductUtils/convertCusProduct.js"; +import { cusProductToProduct } from "@autumn/shared"; import { ExtendedRequest } from "@/utils/models/Request.js"; import { createStripeCli } from "@/external/stripe/utils.js"; import RecaseError from "@/utils/errorUtils.js"; diff --git a/server/src/internal/customers/internalCusRouter.ts b/server/src/internal/customers/internalCusRouter.ts index 3400b5b97..f934ba87a 100644 --- a/server/src/internal/customers/internalCusRouter.ts +++ b/server/src/internal/customers/internalCusRouter.ts @@ -9,6 +9,7 @@ import { FullCusProduct, FullCustomerEntitlement, FullCustomerPrice, + productToCusProduct, } from "@autumn/shared"; import RecaseError, { handleFrontendReqError } from "@/utils/errorUtils.js"; @@ -22,11 +23,12 @@ import { mapToProductV2 } from "../products/productV2Utils.js"; import { RewardRedemptionService } from "../rewards/RewardRedemptionService.js"; import { CusReadService } from "./CusReadService.js"; import { StatusCodes } from "http-status-codes"; -import { cusProductToProduct } from "./cusProducts/cusProductUtils/convertCusProduct.js"; +import { cusProductToProduct } from "@autumn/shared"; import { createOrgResponse, isStripeConnected } from "../orgs/orgUtils.js"; import { routeHandler } from "@/utils/routerUtils.js"; import { CusSearchService } from "./CusSearchService.js"; import { CusBatchService } from "../api/batch/CusBatchService.js"; +import { ACTIVE_STATUSES } from "./cusProducts/CusProductService.js"; export const cusRouter: Router = Router(); @@ -127,203 +129,6 @@ cusRouter.get("/:customer_id/events", async (req: any, res: any) => { } }); -// cusRouter.get("/:customer_id/stripe", async (req: any, res: any) => { -// try { -// const { db, org, features, env } = req; -// const { customer_id } = req.params; -// let discount = null; - -// const customer = await CusService.get({ -// db, -// orgId: req.orgId, -// env, -// idOrInternalId: customer_id, -// }); - -// if (org.stripe_config && customer.processor?.id) { -// try { -// const stripeCli = createStripeCli({ org, env }); -// const stripeCus: any = await stripeCli.customers.retrieve( -// customer.processor.id -// ); - -// if (stripeCus.discount) { -// discount = stripeCus.discount; -// } -// } catch (error) { -// console.log("error", error); -// } -// } - -// } catch (error) { -// handleFrontendReqError({ req, error, res, action: "get customer data" }); -// } -// }); - -// cusRouter.get("/:customer_id/events", async (req: any, res: any) => { -// try { -// const { db, org, features, env } = req; -// const { customer_id } = req.params; -// const orgId = req.orgId; -// const limit = req.query.limit || 10; -// const period = req.query.period || "all"; - -// console.log("Fetching events for customer:", customer_id); - -// const events = await EventService.getByCustomerId({ -// db, -// internalCustomerId: customer_id, -// env, -// orgId: orgId, -// limit, -// }); - -// console.log("Events:", events); - -// res.status(200).json({ events }); -// } catch (error) { -// handleFrontendReqError({ req, error, res, action: "get customer events" }); -// } -// }); - -cusRouter.get("/:customer_id/data", async (req: any, res: any) => { - try { - const { db, org, features, env } = req; - const { customer_id } = req.params; - const orgId = req.orgId; - - const [coupons, products, customer] = await Promise.all([ - RewardService.list({ - db, - orgId: orgId, - env, - }), - - ProductService.listFull({ db, orgId, env, returnAll: true }), - CusService.getFull({ - db, - orgId, - env, - idOrInternalId: customer_id, - withEntities: true, - expand: [CusExpand.Invoices], - inStatuses: [ - CusProductStatus.Active, - CusProductStatus.PastDue, - CusProductStatus.Scheduled, - CusProductStatus.Expired, - ], - }), - ]); - - if (!customer) { - throw new RecaseError({ - message: "Customer not found", - code: ErrCode.CustomerNotFound, - statusCode: StatusCodes.NOT_FOUND, - }); - } - - let invoices = customer.invoices; - let entities = customer.entities; - const events = await EventService.getByCustomerId({ - db, - internalCustomerId: customer.internal_id, - env, - orgId: orgId, - limit: 10, - }); - - let fullCustomer = customer as any; - let cusProducts = fullCustomer.customer_products; - fullCustomer.products = fullCustomer.customer_products; - fullCustomer.entitlements = cusProducts.flatMap( - (product: FullCusProduct) => product.customer_entitlements - ); - fullCustomer.prices = cusProducts.flatMap( - (product: FullCusProduct) => product.customer_prices - ); - - for (const product of fullCustomer.products) { - product.entitlements = product.customer_entitlements.map( - (cusEnt: FullCustomerEntitlement) => { - return cusEnt.entitlement; - } - ); - product.prices = product.customer_prices.map( - (cusPrice: FullCustomerPrice) => { - return cusPrice.price; - } - ); - } - - let discount = null; - if (org.stripe_config && customer.processor?.id) { - try { - const stripeCli = createStripeCli({ org, env }); - const stripeCus: any = await stripeCli.customers.retrieve( - customer.processor.id - ); - - if (stripeCus.discount) { - discount = stripeCus.discount; - } - } catch (error) { - console.log("error", error); - } - } - - for (const invoice of invoices || []) { - invoice.product_ids = invoice.product_ids.sort(); - invoice.internal_product_ids = invoice.internal_product_ids.sort(); - } - - // fullCustomer.entitlements = fullCustomer.entitlements.sort( - // (a: any, b: any) => { - // const productA = fullCustomer.products.find( - // (p: any) => p.id === a.customer_product_id - // ); - // const productB = fullCustomer.products.find( - // (p: any) => p.id === b.customer_product_id - // ); - - // return ( - // new Date(b.created_at).getTime() - new Date(a.created_at).getTime() || - // b.id.localeCompare(a.id) - // ); - // } - // ); - - // for (const cusEnt of fullCustomer.entitlements) { - // // let entitlement = cusEnt.entitlement; - - // // Show used, limit, etc. - // let { balance, unused } = getCusEntMasterBalance({ - // cusEnt, - // entities, - // }); - - // cusEnt.balance = balance; - // cusEnt.unused = unused; - // } - - res.status(200).json({ - customer: fullCustomer, - products: getLatestProducts(products), - versionCounts: getProductVersionCounts(products), - invoices, - features, - coupons, - events, - discount, - org, - entities, - }); - } catch (error) { - handleFrontendReqError({ req, error, res, action: "get customer data" }); - } -}); - cusRouter.get("/:customer_id/referrals", async (req: any, res: any) => { try { const { env, db, org } = req; @@ -476,34 +281,15 @@ cusRouter.get( let cusProducts = customer.customer_products; let entity = customer.entity; - let cusProduct; - if (notNullish(customer_product_id)) { - cusProduct = cusProducts.find( - (p: any) => - p.id === customer_product_id && - (entity ? p.internal_entity_id === entity.internal_id : true) - ); - } else if (notNullish(version)) { - cusProduct = cusProducts.find( - (p: any) => - p.product.id === product_id && - (p.status === CusProductStatus.Active || - p.status === CusProductStatus.PastDue) && - p.product.version === parseInt(version) && - (entity ? p.internal_entity_id === entity.internal_id : true) - ); - } else { - cusProduct = cusProducts.find( - (p: any) => - p.product.id === product_id && - (p.status === CusProductStatus.Active || - p.status === CusProductStatus.PastDue) && - (entity - ? p.internal_entity_id === entity.internal_id - : nullish(p.internal_entity_id)) - ); - } + let cusProduct = productToCusProduct({ + cusProducts, + productId: product_id, + internalEntityId: entity?.internal_id, + version: version ? parseInt(version) : undefined, + cusProductId: customer_product_id, + inStatuses: ACTIVE_STATUSES, + }); let product = cusProduct ? cusProductToProduct({ cusProduct }) @@ -519,32 +305,32 @@ cusRouter.get( }); let productV2 = mapToProductV2({ product: product!, features }); - - let numVersions = await ProductService.getProductVersionCount({ - db, - orgId: org.id, - env, - productId: product_id, - }); + // let numVersions = await ProductService.getProductVersionCount({ + // db, + // orgId: org.id, + // env, + // productId: product_id, + // }); res.status(200).json({ - customer, - // preview, - product: cusProduct - ? { - ...productV2, - options: cusProduct.options, - isActive: cusProduct.status === CusProductStatus.Active, - isCustom: cusProduct.is_custom, - isCanceled: - cusProduct.canceled_at !== null || cusProduct.canceled, - cusProductId: cusProduct.id, - } - : productV2, - features, - numVersions, - entities: customer.entities, - org: createOrgResponse({ org, env }), + cusProduct, + product: productV2, + // customer, + // product: cusProduct + // ? { + // ...productV2, + // options: cusProduct.options, + // isActive: cusProduct.status === CusProductStatus.Active, + // isCustom: cusProduct.is_custom, + // isCanceled: + // cusProduct.canceled_at !== null || cusProduct.canceled, + // cusProductId: cusProduct.id, + // } + // : productV2, + // features, + // numVersions, + // entities: customer.entities, + // org: createOrgResponse({ org, env }), }); } catch (error) { handleFrontendReqError({ diff --git a/server/src/internal/features/featureUtils.ts b/server/src/internal/features/featureUtils.ts index f64a5975f..97481b1e4 100644 --- a/server/src/internal/features/featureUtils.ts +++ b/server/src/internal/features/featureUtils.ts @@ -18,10 +18,7 @@ import { generateFeatureDisplay } from "@/external/llm/llmUtils.js"; import { ProductService } from "../products/ProductService.js"; import { getCreditSystemsFromFeature } from "./creditSystemUtils.js"; import { DrizzleCli } from "@/db/initDrizzle.js"; -import { - cusProductsToCusPrices, - cusProductToPrices, -} from "../customers/cusProducts/cusProductUtils/convertCusProduct.js"; +import { cusProductsToCusPrices, cusProductToPrices } from "@autumn/shared"; import { priceToFeature } from "../products/prices/priceUtils/convertPrice.js"; import { ACTIVE_STATUSES } from "../customers/cusProducts/CusProductService.js"; diff --git a/server/src/internal/invoices/invoiceFormatUtils.ts b/server/src/internal/invoices/invoiceFormatUtils.ts index 9ad8cf241..05a4cf051 100644 --- a/server/src/internal/invoices/invoiceFormatUtils.ts +++ b/server/src/internal/invoices/invoiceFormatUtils.ts @@ -18,11 +18,8 @@ import { getBillingType, getPriceEntitlement, } from "../products/prices/priceUtils.js"; -import { cusProductToEnts } from "../customers/cusProducts/cusProductUtils/convertCusProduct.js"; -import { - getCusPriceUsage, - getRelatedCusEnt, -} from "../customers/cusProducts/cusPrices/cusPriceUtils.js"; +import { cusProductToEnts } from "@autumn/shared"; + import { getFeatureQuantity } from "../customers/cusProducts/cusProductUtils.js"; import { formatAmount } from "@/utils/formatUtils.js"; import { getIntervalString } from "../products/productUtils/productResponseUtils/getProductItemDisplay.js"; diff --git a/server/src/internal/invoices/previewItemUtils/getCurContUseItems.ts b/server/src/internal/invoices/previewItemUtils/getCurContUseItems.ts index a78f3a608..3d68d142e 100644 --- a/server/src/internal/invoices/previewItemUtils/getCurContUseItems.ts +++ b/server/src/internal/invoices/previewItemUtils/getCurContUseItems.ts @@ -19,10 +19,7 @@ import { priceToUsageModel, } from "@/internal/products/prices/priceUtils/convertPrice.js"; import { attachParamToCusProducts } from "@/internal/customers/attach/attachUtils/convertAttachParams.js"; -import { - cusProductToEnts, - cusProductToPrices, -} from "@/internal/customers/cusProducts/cusProductUtils/convertCusProduct.js"; +import { cusProductToEnts, cusProductToPrices } from "@autumn/shared"; import { getExistingUsageFromCusProducts } from "@/internal/customers/cusProducts/cusEnts/cusEntUtils.js"; import { getPriceEntitlement } from "@/internal/products/prices/priceUtils.js"; diff --git a/server/src/internal/invoices/previewItemUtils/getItemsForCurProduct.ts b/server/src/internal/invoices/previewItemUtils/getItemsForCurProduct.ts index fdf1a7732..10870544f 100644 --- a/server/src/internal/invoices/previewItemUtils/getItemsForCurProduct.ts +++ b/server/src/internal/invoices/previewItemUtils/getItemsForCurProduct.ts @@ -1,7 +1,7 @@ import Stripe from "stripe"; import { attachParamToCusProducts } from "@/internal/customers/attach/attachUtils/convertAttachParams.js"; -import { cusProductToPrices } from "@/internal/customers/cusProducts/cusProductUtils/convertCusProduct.js"; +import { cusProductToPrices } from "@autumn/shared"; import { AttachParams } from "@/internal/customers/cusProducts/AttachParams.js"; import { getBillingType } from "@/internal/products/prices/priceUtils.js"; diff --git a/server/src/internal/products/internalProductRouter.ts b/server/src/internal/products/internalProductRouter.ts index 499c267e8..233f2d4a0 100644 --- a/server/src/internal/products/internalProductRouter.ts +++ b/server/src/internal/products/internalProductRouter.ts @@ -119,6 +119,42 @@ productRouter.get("/rewards", async (req: any, res) => { } }); +productRouter.get("/:productId/data2", async (req: any, res) => { + try { + const { productId } = req.params; + const { version } = req.query; + const { db, orgId, env } = req; + + const product = await ProductService.getFull({ + db, + idOrInternalId: productId, + orgId, + env, + version: version ? parseInt(version) : undefined, + }); + + if (!product) { + throw new RecaseError({ + message: `Product ${productId} ${ + version ? `(v${version})` : "" + } not found`, + code: ErrCode.ProductNotFound, + statusCode: StatusCodes.NOT_FOUND, + }); + } + + let productV2 = mapToProductV2({ + product: product, + features: req.features, + }); + + res.status(200).json({ product: productV2 }); + } catch (error) { + console.error("Failed to get product", error); + res.status(500).send(error); + } +}); + productRouter.get("/data", async (req: any, res) => { try { let { db } = req; diff --git a/server/src/internal/products/pricecn/pricecnUtils.ts b/server/src/internal/products/pricecn/pricecnUtils.ts index 371db3623..ff7613fa0 100644 --- a/server/src/internal/products/pricecn/pricecnUtils.ts +++ b/server/src/internal/products/pricecn/pricecnUtils.ts @@ -16,11 +16,10 @@ import { AttachScenario, FullProduct, FullCustomer, - FreeTrialResponseSchema, } from "@autumn/shared"; import { isPriceItem } from "../product-items/productItemUtils/getItemType.js"; import { isFeaturePriceItem } from "../product-items/productItemUtils/getItemType.js"; -import { cusProductToProduct } from "@/internal/customers/cusProducts/cusProductUtils/convertCusProduct.js"; +import { cusProductToProduct } from "@autumn/shared"; import { isProductUpgrade } from "../productUtils.js"; import { getLargestInterval } from "../prices/priceUtils/priceIntervalUtils.js"; import { DrizzleCli } from "@/db/initDrizzle.js"; diff --git a/server/src/internal/products/productUtils/productResponseUtils/getAttachScenario.ts b/server/src/internal/products/productUtils/productResponseUtils/getAttachScenario.ts index 8f83cf9f0..f13e0e759 100644 --- a/server/src/internal/products/productUtils/productResponseUtils/getAttachScenario.ts +++ b/server/src/internal/products/productUtils/productResponseUtils/getAttachScenario.ts @@ -1,4 +1,4 @@ -import { cusProductToProduct } from "@/internal/customers/cusProducts/cusProductUtils/convertCusProduct.js"; +import { cusProductToProduct } from "@autumn/shared"; import { AttachScenario, FullCustomer, FullProduct } from "@autumn/shared"; import { isFreeProduct, diff --git a/server/src/utils/checkUtils/checkCustomerCorrect.ts b/server/src/utils/checkUtils/checkCustomerCorrect.ts index 5205eb2e8..de91abdc3 100644 --- a/server/src/utils/checkUtils/checkCustomerCorrect.ts +++ b/server/src/utils/checkUtils/checkCustomerCorrect.ts @@ -23,7 +23,7 @@ import { cusProductToPrices, cusProductToEnts, cusProductToProduct, -} from "@/internal/customers/cusProducts/cusProductUtils/convertCusProduct.js"; +} from "@autumn/shared"; import { getExistingCusProducts } from "@/internal/customers/cusProducts/cusProductUtils/getExistingCusProducts.js"; import { getPriceEntitlement, diff --git a/server/src/utils/importUtils/addUsagePricesToSub.ts b/server/src/utils/importUtils/addUsagePricesToSub.ts index 79329acee..d485b213b 100644 --- a/server/src/utils/importUtils/addUsagePricesToSub.ts +++ b/server/src/utils/importUtils/addUsagePricesToSub.ts @@ -9,7 +9,7 @@ import { } from "@autumn/shared"; import Stripe from "stripe"; import { ExtendedRequest } from "../models/Request.js"; -import { cusProductToPrices } from "@/internal/customers/cusProducts/cusProductUtils/convertCusProduct.js"; +import { cusProductToPrices } from "@autumn/shared"; import { getUsageBasedSub } from "@/external/stripe/stripeSubUtils.js"; import { priceToFeature } from "@/internal/products/prices/priceUtils/convertPrice.js"; import { logger } from "@/external/logtail/logtailUtils.js"; diff --git a/server/src/utils/importUtils/updateUsages.ts b/server/src/utils/importUtils/updateUsages.ts index 8c5d2a222..68e4a6981 100644 --- a/server/src/utils/importUtils/updateUsages.ts +++ b/server/src/utils/importUtils/updateUsages.ts @@ -1,7 +1,7 @@ import { DrizzleCli } from "@/db/initDrizzle.js"; import { CusEntService } from "@/internal/customers/cusProducts/cusEnts/CusEntitlementService.js"; import { RELEVANT_STATUSES } from "@/internal/customers/cusProducts/CusProductService.js"; -import { cusProductsToCusEnts } from "@/internal/customers/cusProducts/cusProductUtils/convertCusProduct.js"; +import { cusProductsToCusEnts } from "@autumn/shared"; import { FullCustomer } from "@autumn/shared"; export const updateUsages = async ({ diff --git a/server/tests/core/cancel/cancel2.test.ts b/server/tests/core/cancel/cancel2.test.ts index fb6453b64..5c72f4734 100644 --- a/server/tests/core/cancel/cancel2.test.ts +++ b/server/tests/core/cancel/cancel2.test.ts @@ -28,13 +28,7 @@ import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js" import { advanceTestClock } from "tests/utils/stripeUtils.js"; import { addWeeks } from "date-fns"; import { getExpectedInvoiceTotal } from "tests/utils/expectUtils/expectInvoiceUtils.js"; -import { timeout } from "@/utils/genUtils.js"; -import { CusService } from "@/internal/customers/CusService.js"; -import { cusProductToPrices } from "@/internal/customers/cusProducts/cusProductUtils/convertCusProduct.js"; -import { isPrepaidPrice } from "@shared/utils/productUtils/priceUtils.js"; -import { isContUsePrice } from "@/internal/products/prices/priceUtils/usagePriceUtils/classifyUsagePrice.js"; -import { calculateProrationAmount } from "@/internal/invoices/prorationUtils.js"; -import { Decimal } from "decimal.js"; + import { advanceToNextInvoice } from "tests/utils/testAttachUtils/testAttachUtils.js"; let premium = constructProduct({ diff --git a/server/tests/core/cancel/cancel3.test.ts b/server/tests/core/cancel/cancel3.test.ts index d1b3be60d..1337bff5d 100644 --- a/server/tests/core/cancel/cancel3.test.ts +++ b/server/tests/core/cancel/cancel3.test.ts @@ -26,17 +26,6 @@ import { } from "tests/utils/testProductUtils/testProductUtils.js"; import { expect } from "chai"; import { attachAndExpectCorrect } from "tests/utils/expectUtils/expectAttach.js"; -import { advanceTestClock } from "tests/utils/stripeUtils.js"; -import { addWeeks } from "date-fns"; -import { getExpectedInvoiceTotal } from "tests/utils/expectUtils/expectInvoiceUtils.js"; -import { timeout } from "@/utils/genUtils.js"; -import { CusService } from "@/internal/customers/CusService.js"; -import { cusProductToPrices } from "@/internal/customers/cusProducts/cusProductUtils/convertCusProduct.js"; -import { isPrepaidPrice } from "@shared/utils/productUtils/priceUtils.js"; -import { isContUsePrice } from "@/internal/products/prices/priceUtils/usagePriceUtils/classifyUsagePrice.js"; -import { calculateProrationAmount } from "@/internal/invoices/prorationUtils.js"; -import { Decimal } from "decimal.js"; -import { advanceToNextInvoice } from "tests/utils/testAttachUtils/testAttachUtils.js"; let premium = constructProduct({ id: "premium", diff --git a/server/tests/merged/mergeUtils.test.ts b/server/tests/merged/mergeUtils.test.ts index 4c643a0b1..dab02adb0 100644 --- a/server/tests/merged/mergeUtils.test.ts +++ b/server/tests/merged/mergeUtils.test.ts @@ -5,7 +5,7 @@ import { cusProductToEnts, cusProductToPrices, cusProductToProduct, -} from "@/internal/customers/cusProducts/cusProductUtils/convertCusProduct.js"; +} from "@autumn/shared"; import { CusService } from "@/internal/customers/CusService.js"; import { getPriceEntitlement, diff --git a/server/tests/merged/mergeUtils/expectSubCorrect.ts b/server/tests/merged/mergeUtils/expectSubCorrect.ts index 855ff200c..d2bcbb9fc 100644 --- a/server/tests/merged/mergeUtils/expectSubCorrect.ts +++ b/server/tests/merged/mergeUtils/expectSubCorrect.ts @@ -15,7 +15,7 @@ import { cusProductToPrices, cusProductToEnts, cusProductToProduct, -} from "@/internal/customers/cusProducts/cusProductUtils/convertCusProduct.js"; +} from "@autumn/shared"; import { formatPrice, getPriceEntitlement, diff --git a/server/tests/utils/expectUtils/expectContUseUtils.ts b/server/tests/utils/expectUtils/expectContUseUtils.ts index 93a4c438b..4fe5067a5 100644 --- a/server/tests/utils/expectUtils/expectContUseUtils.ts +++ b/server/tests/utils/expectUtils/expectContUseUtils.ts @@ -2,7 +2,7 @@ import Stripe from "stripe"; import { DrizzleCli } from "@/db/initDrizzle.js"; import { getStripeSubs } from "@/external/stripe/stripeSubUtils.js"; import { findStripeItemForPrice } from "@/external/stripe/stripeSubUtils/stripeSubItemUtils.js"; -import { cusProductToPrices } from "@/internal/customers/cusProducts/cusProductUtils/convertCusProduct.js"; +import { cusProductToPrices } from "@autumn/shared"; import { CusService } from "@/internal/customers/CusService.js"; import { findContUsePrice } from "@/internal/products/prices/priceUtils/findPriceUtils.js"; import { AppEnv, FullCustomer, Organization } from "@autumn/shared"; @@ -10,7 +10,7 @@ import { expect } from "chai"; import { TestFeature } from "tests/setup/v2Features.js"; import { calculateProrationAmount } from "@/internal/invoices/prorationUtils.js"; import { AutumnInt } from "@/external/autumn/autumnCli.js"; -import { formatUnixToDate, notNullish } from "@/utils/genUtils.js"; +import { notNullish } from "@/utils/genUtils.js"; import { subToPeriodStartEnd } from "@/external/stripe/stripeSubUtils/convertSubUtils.js"; export const expectSubQuantityCorrect = async ({ diff --git a/server/tests/utils/expectUtils/expectInvoiceUtils.ts b/server/tests/utils/expectUtils/expectInvoiceUtils.ts index cb8f29e55..07b028937 100644 --- a/server/tests/utils/expectUtils/expectInvoiceUtils.ts +++ b/server/tests/utils/expectUtils/expectInvoiceUtils.ts @@ -1,8 +1,5 @@ import { DrizzleCli } from "@/db/initDrizzle.js"; -import { - cusProductToPrices, - cusProductToEnts, -} from "@/internal/customers/cusProducts/cusProductUtils/convertCusProduct.js"; +import { cusProductToPrices, cusProductToEnts } from "@autumn/shared"; import { getPriceEntitlement } from "@/internal/products/prices/priceUtils.js"; import { priceToInvoiceAmount } from "@/internal/products/prices/priceUtils/priceToInvoiceAmount.js"; import { diff --git a/server/tests/utils/expectUtils/expectScheduleUtils.ts b/server/tests/utils/expectUtils/expectScheduleUtils.ts index e0958ce70..d16c543f6 100644 --- a/server/tests/utils/expectUtils/expectScheduleUtils.ts +++ b/server/tests/utils/expectUtils/expectScheduleUtils.ts @@ -2,7 +2,7 @@ import { DrizzleCli } from "@/db/initDrizzle.js"; import { AutumnInt } from "@/external/autumn/autumnCli.js"; import { getStripeSchedules } from "@/external/stripe/stripeSubUtils.js"; import { findStripePriceFromPrices } from "@/external/stripe/stripeSubUtils/stripeSubItemUtils.js"; -import { cusProductToPrices } from "@/internal/customers/cusProducts/cusProductUtils/convertCusProduct.js"; +import { cusProductToPrices } from "@autumn/shared"; import { CusService } from "@/internal/customers/CusService.js"; import { isV4Usage } from "@/internal/products/prices/priceUtils/usagePriceUtils/classifyUsagePrice.js"; import { diff --git a/server/tests/utils/expectUtils/expectSubUtils.ts b/server/tests/utils/expectUtils/expectSubUtils.ts index 9b2345b1c..9921b9025 100644 --- a/server/tests/utils/expectUtils/expectSubUtils.ts +++ b/server/tests/utils/expectUtils/expectSubUtils.ts @@ -4,10 +4,7 @@ import { findStripeItemForPrice, isLicenseItem, } from "@/external/stripe/stripeSubUtils/stripeSubItemUtils.js"; -import { - cusProductToPrices, - cusProductToSub, -} from "@/internal/customers/cusProducts/cusProductUtils/convertCusProduct.js"; +import { cusProductToPrices } from "@autumn/shared"; import { CusService } from "@/internal/customers/CusService.js"; import { getBillingType } from "@/internal/products/prices/priceUtils.js"; import { isV4Usage } from "@/internal/products/prices/priceUtils/usagePriceUtils/classifyUsagePrice.js"; diff --git a/shared/index.ts b/shared/index.ts index 9af8c047f..813d13b72 100644 --- a/shared/index.ts +++ b/shared/index.ts @@ -145,6 +145,7 @@ export * from "./utils/productUtils.js"; export * from "./utils/productDisplayUtils/sortProductItems.js"; export * from "./utils/intervalUtils.js"; export * from "./utils/productUtils/priceToInvoiceAmount.js"; +export * from "./utils/index.js"; // ENUMS export * from "./enums/SuccessCode.js"; diff --git a/shared/utils/cusEntUtils/sortCusEntsForDeduction.ts b/shared/utils/cusEntUtils/sortCusEntsForDeduction.ts new file mode 100644 index 000000000..43b24ad97 --- /dev/null +++ b/shared/utils/cusEntUtils/sortCusEntsForDeduction.ts @@ -0,0 +1,113 @@ +import { FullCustomerEntitlement } from "../../models/cusProductModels/cusEntModels/cusEntModels.js"; +import { FullCusProduct } from "../../models/cusProductModels/cusProductModels.js"; +import { FeatureType } from "../../models/featureModels/featureEnums.js"; +import { AllowanceType } from "../../models/productModels/entModels/entModels.js"; +import { entIntervalToValue } from "../intervalUtils.js"; + +export const sortCusEntsForDeduction = ( + cusEnts: (FullCustomerEntitlement & { + customer_product?: FullCusProduct; + })[], + reverseOrder: boolean = false +) => { + cusEnts.sort((a, b) => { + const aEnt = a.entitlement; + const bEnt = b.entitlement; + + // 1. If boolean, go first + if (aEnt.feature.type == FeatureType.Boolean) { + return -1; + } + + if (bEnt.feature.type == FeatureType.Boolean) { + return 1; + } + + // 1. If a is credit system and b is not, a should go last + if ( + aEnt.feature.type == FeatureType.CreditSystem && + bEnt.feature.type != FeatureType.CreditSystem + ) { + return 1; + } + + // 2. If a is not credit system and b is, a should go first + if ( + aEnt.feature.type != FeatureType.CreditSystem && + bEnt.feature.type == FeatureType.CreditSystem + ) { + return -1; + } + + // 2. Sort by unlimited (unlimited goes first) + if ( + aEnt.allowance_type == AllowanceType.Unlimited && + bEnt.allowance_type != AllowanceType.Unlimited + ) { + return -1; + } + + if ( + aEnt.allowance_type != AllowanceType.Unlimited && + bEnt.allowance_type == AllowanceType.Unlimited + ) { + return 1; + } + + // If one has usage_allowed, it should go last + if (!a.usage_allowed && b.usage_allowed) { + return -1; + } + + if (!b.usage_allowed && a.usage_allowed) { + return 1; + } + + // If one has a next_reset_at, it should go first + let nextResetFirst = reverseOrder ? 1 : -1; + + if (a.next_reset_at && !b.next_reset_at) { + return nextResetFirst; + } + + // If b has a next_reset_at, it should go first + if (!a.next_reset_at && b.next_reset_at) { + return -nextResetFirst; + } + + // 3. Sort by interval + let aVal = entIntervalToValue(aEnt.interval, aEnt.interval_count); + let bVal = entIntervalToValue(bEnt.interval, bEnt.interval_count); + if (aEnt.interval && bEnt.interval && !aVal.eq(bVal)) { + if (reverseOrder) { + return bVal.sub(aVal).toNumber(); + // return intervalOrder[bEnt.interval] - intervalOrder[aEnt.interval]; + } else { + return aVal.sub(bVal).toNumber(); + // return intervalOrder[aEnt.interval] - intervalOrder[bEnt.interval]; + } + } + + // Check if a is main product + let aIsAddOn = a.customer_product?.product?.is_add_on; + let bIsAddOn = b.customer_product?.product?.is_add_on; + + if (aIsAddOn && !bIsAddOn) { + return 1; + } + + if (!aIsAddOn && bIsAddOn) { + return -1; + } + + // 4. Sort by created_at + return a.created_at - b.created_at; + }); + + // console.log( + // `Cus ents after (${reverseOrder ? "reversed" : "normal"})`, + // cusEnts.map( + // (ce) => `${ce.entitlement.feature_id} - ${ce.entitlement.interval}` + // ) + // ); +}; diff --git a/shared/utils/cusProductUtils/classifyCusProduct.ts b/shared/utils/cusProductUtils/classifyCusProduct.ts new file mode 100644 index 000000000..c0e786de9 --- /dev/null +++ b/shared/utils/cusProductUtils/classifyCusProduct.ts @@ -0,0 +1,17 @@ +import { FullCusProduct } from "../../models/cusProductModels/cusProductModels.js"; + +export const isCanceled = ({ cusProduct }: { cusProduct: FullCusProduct }) => { + return cusProduct.canceled; +}; + +export const isTrialing = ({ + cusProduct, + now, +}: { + cusProduct: FullCusProduct; + now?: number; +}) => { + return ( + cusProduct.trial_ends_at && cusProduct.trial_ends_at > (now || Date.now()) + ); +}; diff --git a/shared/utils/cusProductUtils/convertCusProduct.ts b/shared/utils/cusProductUtils/convertCusProduct.ts new file mode 100644 index 000000000..74be2f309 --- /dev/null +++ b/shared/utils/cusProductUtils/convertCusProduct.ts @@ -0,0 +1,123 @@ +import { + BillingType, + CusProductStatus, + FullCusEntWithFullCusProduct, + FullCusProduct, + FullCustomerEntitlement, + FullCustomerPrice, + FullProduct, +} from "@autumn/shared"; + +import { getBillingType } from "../productUtils/priceUtils.js"; +import { sortCusEntsForDeduction } from "../cusEntUtils/sortCusEntsForDeduction.js"; + +export const cusProductsToPrices = ({ + cusProducts, +}: { + cusProducts: FullCusProduct[]; +}) => { + return cusProducts.flatMap((cp) => cusProductToPrices({ cusProduct: cp })); +}; + +export const cusProductsToCusPrices = ({ + cusProducts, + inStatuses, + billingType, +}: { + cusProducts: FullCusProduct[]; + inStatuses?: CusProductStatus[]; + billingType?: BillingType; +}) => { + const cusPrices: FullCustomerPrice[] = []; + + for (const cusProduct of cusProducts) { + if (inStatuses && !inStatuses.includes(cusProduct.status)) { + continue; + } + + let prices = cusProduct.customer_prices; + if (billingType) { + prices = prices.filter( + (cp) => getBillingType(cp.price.config) === billingType + ); + } + + cusPrices.push(...prices); + } + + return cusPrices; +}; + +export const cusProductsToCusEnts = ({ + cusProducts, + inStatuses = [CusProductStatus.Active], + reverseOrder = false, + featureId, +}: { + cusProducts: FullCusProduct[]; + inStatuses?: CusProductStatus[]; + reverseOrder?: boolean; + featureId?: string; +}) => { + let cusEnts: FullCustomerEntitlement[] = []; + + for (const cusProduct of cusProducts) { + if (!inStatuses.includes(cusProduct.status)) { + continue; + } + + cusEnts.push( + ...cusProduct.customer_entitlements.map((cusEnt) => ({ + ...cusEnt, + customer_product: cusProduct, + })) + ); + } + + if (featureId) { + cusEnts = cusEnts.filter( + (cusEnt) => cusEnt.entitlement.feature_id === featureId + ); + } + + sortCusEntsForDeduction(cusEnts, reverseOrder); + + return cusEnts as FullCusEntWithFullCusProduct[]; +}; + +export const cusProductToPrices = ({ + cusProduct, + billingType, +}: { + cusProduct: FullCusProduct; + billingType?: BillingType; +}) => { + let prices = cusProduct.customer_prices.map((cp) => cp.price); + + if (billingType) { + prices = prices.filter((p) => getBillingType(p.config) === billingType); + } + + return prices; +}; + +export const cusProductToEnts = ({ + cusProduct, +}: { + cusProduct: FullCusProduct; +}) => { + return cusProduct.customer_entitlements.map((ce) => ce.entitlement); +}; + +export const cusProductToProduct = ({ + cusProduct, +}: { + cusProduct: FullCusProduct; +}) => { + return { + ...cusProduct.product, + prices: cusProductToPrices({ cusProduct }), + entitlements: cusProductToEnts({ cusProduct }), + free_trial: cusProduct.free_trial, + } as FullProduct; +}; diff --git a/shared/utils/cusProductUtils/cusProductConstants.ts b/shared/utils/cusProductUtils/cusProductConstants.ts new file mode 100644 index 000000000..b3d8403f7 --- /dev/null +++ b/shared/utils/cusProductUtils/cusProductConstants.ts @@ -0,0 +1,12 @@ +import { CusProductStatus } from "../../models/cusProductModels/cusProductEnums.js"; + +export const ACTIVE_STATUSES = [ + CusProductStatus.Active, + CusProductStatus.PastDue, +]; + +export const RELEVANT_STATUSES = [ + CusProductStatus.Active, + CusProductStatus.PastDue, + CusProductStatus.Scheduled, +]; diff --git a/shared/utils/cusProductUtils/productIdToCusProduct.ts b/shared/utils/cusProductUtils/productIdToCusProduct.ts new file mode 100644 index 000000000..19af324ae --- /dev/null +++ b/shared/utils/cusProductUtils/productIdToCusProduct.ts @@ -0,0 +1,38 @@ +import { CusProductStatus, FullCusProduct } from "@autumn/shared"; +import { nullish } from "../utils.js"; + +export const productToCusProduct = ({ + productId, + cusProducts, + internalEntityId, + cusProductId, + version, + inStatuses, +}: { + productId: string; + cusProducts: FullCusProduct[]; + internalEntityId?: string; + cusProductId?: string; + version?: number; + inStatuses?: CusProductStatus[]; +}) => { + if (cusProductId) { + return cusProducts.find((cusProduct) => cusProduct.id === cusProductId); + } + + return cusProducts.find((cusProduct) => { + let prodIdMatch = cusProduct.product.id === productId; + + let entityMatch = internalEntityId + ? cusProduct.internal_entity_id === internalEntityId + : nullish(cusProduct.internal_entity_id); + + let versionMatch = version ? cusProduct.product.version === version : true; + + let statusMatch = inStatuses + ? inStatuses.includes(cusProduct.status) + : true; + + return prodIdMatch && entityMatch && versionMatch && statusMatch; + }); +}; diff --git a/shared/utils/index.ts b/shared/utils/index.ts new file mode 100644 index 000000000..db95691f7 --- /dev/null +++ b/shared/utils/index.ts @@ -0,0 +1,9 @@ +// Cus ent utils +export * from "./cusEntUtils/sortCusEntsForDeduction.js"; + +// Cus product utils +export * from "./cusProductUtils/classifyCusProduct.js"; +export * from "./cusProductUtils/convertCusProduct.js"; +export * from "./cusProductUtils/productIdToCusProduct.js"; +export * from "./cusProductUtils/cusProductConstants.js"; +export * from "./utils.js"; diff --git a/vite/src/utils/product/product-item/validateProductItem.ts b/vite/src/utils/product/product-item/validateProductItem.ts index 8b5dab55d..04a313bf8 100644 --- a/vite/src/utils/product/product-item/validateProductItem.ts +++ b/vite/src/utils/product/product-item/validateProductItem.ts @@ -3,23 +3,17 @@ import { Feature, FeatureUsageType, FrontendProductItem, - Infinite, - ProductItem, - ProductItemInterval, RolloverConfig, RolloverDuration, } from "@autumn/shared"; import { toast } from "sonner"; import { isFeatureItem, isFeaturePriceItem } from "../getItemType"; -import { isOneOffProduct } from "../priceUtils"; export const validateProductItem = ({ item, - // show, features, }: { item: FrontendProductItem; - // show: any; features: Feature[]; }) => { const feature = features.find((f) => f.id == item.feature_id); diff --git a/vite/src/views/customers/customer/CustomerView.tsx b/vite/src/views/customers/customer/CustomerView.tsx index 1869e8649..aacaa8f31 100644 --- a/vite/src/views/customers/customer/CustomerView.tsx +++ b/vite/src/views/customers/customer/CustomerView.tsx @@ -3,9 +3,9 @@ import LoadingScreen from "@/views/general/LoadingScreen"; import { CusProductStatus } from "@autumn/shared"; import { CustomerContext } from "./CustomerContext"; -import { notNullish } from "@/utils/genUtils"; +import { getRedirectUrl, notNullish, pushPage } from "@/utils/genUtils"; import { useEffect, useState } from "react"; -import { useParams, useSearchParams } from "react-router"; +import { Link, useParams, useSearchParams } from "react-router"; import { useCusQuery } from "./hooks/useCusQuery"; import { CustomerSidebar } from "./components/customer-sidebar/CustomerSidebar"; import { CustomerPageHeader } from "./components/customer-header/CustomerPageHeader"; @@ -15,6 +15,7 @@ import { CustomerEntitlementsList } from "./entitlements/CustomerEntitlementsLis import { useCusReferralQuery } from "./hooks/useCusReferralQuery"; import { InvoicesTable } from "./InvoicesTable"; import { CustomerEventsList } from "./CustomerEventsList"; +import ErrorScreen from "@/views/general/ErrorScreen"; export default function CustomerView() { // const { customer_id } = useParams(); @@ -56,19 +57,19 @@ export default function CustomerView() { if (cusLoading) return ; - // if (!data) { - // return ( - // - //
Customer not found
- // - // Return - // - //
- // ); - // } + if (!customer) { + return ( + +
Customer not found
+ + Return + +
+ ); + } // const { customer, products, invoices, coupons, discount, events, entities } = // data; @@ -106,9 +107,9 @@ export default function CustomerView() {
- - -
+
+ +
diff --git a/vite/src/views/customers/customer/hooks/useCusQuery.tsx b/vite/src/views/customers/customer/hooks/useCusQuery.tsx index 004293dc3..6a27892aa 100644 --- a/vite/src/views/customers/customer/hooks/useCusQuery.tsx +++ b/vite/src/views/customers/customer/hooks/useCusQuery.tsx @@ -1,6 +1,5 @@ import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery"; import { useProductsQuery } from "@/hooks/queries/useProductsQuery"; -import { useRewardsQuery } from "@/hooks/queries/useRewardsQuery"; import { useAxiosInstance } from "@/services/useAxiosInstance"; import { useQuery } from "@tanstack/react-query"; import { useParams } from "react-router"; @@ -15,8 +14,12 @@ export const useCusQuery = () => { const cachedCustomer = useMemo(getCachedCustomer, [getCachedCustomer]); const fetcher = async () => { - const { data } = await axiosInstance.get(`/customers/${customer_id}`); - return data; + try { + const { data } = await axiosInstance.get(`/customers/${customer_id}`); + return data; + } catch (error) { + return null; + } }; const { diff --git a/vite/src/views/customers/customer/product/CustomerProductView.tsx b/vite/src/views/customers/customer/product/CustomerProductView.tsx index ab405e7b9..1a5f893e6 100644 --- a/vite/src/views/customers/customer/product/CustomerProductView.tsx +++ b/vite/src/views/customers/customer/product/CustomerProductView.tsx @@ -3,7 +3,13 @@ import ProductSidebar from "@/views/products/product/ProductSidebar"; import LoadingScreen from "@/views/general/LoadingScreen"; import { useState, useEffect, useRef } from "react"; -import { Customer, Entity, Feature, ProductItem } from "@autumn/shared"; +import { + Customer, + Entity, + Feature, + ProductItem, + ProductV2, +} from "@autumn/shared"; import { useAxiosSWR } from "@/services/useAxiosSwr"; import { CustomToaster } from "@/components/general/CustomToaster"; import { ManageProduct } from "@/views/products/product/ManageProduct"; @@ -15,6 +21,11 @@ import { useEnv } from "@/utils/envUtils"; import { CustomerProductBreadcrumbs } from "./components/CustomerProductBreadcrumbs"; import { FrontendProduct, useAttachState } from "./hooks/useAttachState"; import { sortProductItems } from "@/utils/productUtils"; +import { useCusQuery } from "../hooks/useCusQuery"; +import { useOrg } from "@/hooks/common/useOrg"; +import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery"; +import { useCusProductQuery } from "./hooks/useCusProductQuery"; +import { notNullish } from "@/utils/genUtils"; interface OptionValue { feature_id: string; @@ -45,15 +56,46 @@ export default function CustomerProductView() { const [searchParams] = useSearchParams(); const entityIdParam = searchParams.get("entity_id"); - const env = useEnv(); + const { isLoading: orgLoading } = useOrg(); + const { isLoading: featuresLoading } = useFeaturesQuery(); + + const env = useEnv(); + const initialProductRef = useRef(null); + + const [product, setProduct] = useState(null); - const initialProductRef = useRef(null); - const [product, setProduct] = useState(null); - const [features, setFeatures] = useState([]); const [options, setOptions] = useState([]); const [entityId, setEntityId] = useState(entityIdParam); const [entityFeatureIds, setEntityFeatureIds] = useState([]); + const version = searchParams.get("version"); + const customer_product_id = searchParams.get("id"); + + // const { data, isLoading, error } = useAxiosSWR({ + // url: `/customers/${customer_id}/product/${product_id}${getProductUrlParams({ + // version, + // customer_product_id, + // entity_id: entityId, + // })}`, + // env, + // }); + + const { + product: originalProduct, + cusProduct, + isLoading, + error, + } = useCusProductQuery(); + + const { isLoading: cusLoading } = useCusQuery(); + + const attachState = useAttachState({ + product, + setProduct, + initialProductRef, + cusProduct, + }); + useEffect(() => { if (entityIdParam) { setEntityId(entityIdParam); @@ -62,30 +104,11 @@ export default function CustomerProductView() { } }, [entityIdParam]); - const version = searchParams.get("version"); - const customer_product_id = searchParams.get("id"); - const { data, isLoading, mutate, error } = useAxiosSWR({ - url: `/customers/${customer_id}/product/${product_id}${getProductUrlParams({ - version, - customer_product_id, - entity_id: entityId, - })}`, - env, - }); - - const [selectedEntitlementAllowance, setSelectedEntitlementAllowance] = - useState<"unlimited" | number>(0); - - const attachState = useAttachState({ - product, - setProduct, - initialProductRef, - }); - useEffect(() => { - if (!data?.product || !data?.customer) return; + if (!originalProduct) return; + + const product = originalProduct; - const product = data.product; setProduct(product); initialProductRef.current = structuredClone({ ...product, @@ -96,22 +119,18 @@ export default function CustomerProductView() { Array.from( new Set( product.items - .filter((item: ProductItem) => item.entity_feature_id != null) - .map((item: ProductItem) => item.entity_feature_id) + .filter((item: ProductItem) => notNullish(item.entity_feature_id)) + .map((item: ProductItem) => item.entity_feature_id!) ) ) ); - if (product.options) { - setOptions(product.options); + if (cusProduct?.options) { + setOptions(cusProduct.options); } else { setOptions([]); } - - if (data?.features) { - setFeatures(data.features); - } - }, [data]); + }, [originalProduct, cusProduct]); if (error) { return ( @@ -123,28 +142,33 @@ export default function CustomerProductView() { ); } - if (isLoading || !product) return ; + // console.log("Product:", product); + // console.log("Is loading:", isLoading); + // console.log("Is org loading:", orgLoading); + // console.log("Is cus loading:", cusLoading); + // console.log("Is features loading:", featuresLoading); + + if (isLoading || !product || cusLoading || orgLoading || featuresLoading) + return ; if (!customer_id || !product_id) { return
Customer or product not found
; } - const { customer } = data; - return ( ); }; + +export type NewSubscription = { + id: "new"; + code: "checkout_session" | "combine_subscription" | "renew"; +}; + +export type UpdateSubscription = { + id: "update"; + code: "upgrade" | "downgrade"; +}; + +export type Product = { + scenario: "new:checkout_session" | "new:combine_subscription"; +}; diff --git a/vite/src/views/customers/customer/product/components/AttachButton.tsx b/vite/src/views/customers/customer/product/components/AttachButton.tsx index 2a6d78b6d..9e1388e7f 100644 --- a/vite/src/views/customers/customer/product/components/AttachButton.tsx +++ b/vite/src/views/customers/customer/product/components/AttachButton.tsx @@ -8,20 +8,23 @@ import { useAxiosInstance } from "@/services/useAxiosInstance"; import { getBackendErr } from "@/utils/genUtils"; import { toast } from "sonner"; import { getAttachBody } from "./attachProductUtils"; +import { useCusQuery } from "../../hooks/useCusQuery"; +import { useProductQueryState } from "@/views/products/product/hooks/useProductQuery"; export const AttachButton = () => { const axios = useAxiosInstance(); const [open, setOpen] = useState(false); const [buttonLoading, setButtonLoading] = useState(false); - const { attachState, product, entityId, customer, version } = - useProductContext(); - const { preview, setPreview } = attachState; + const { customer } = useCusQuery(); - const { buttonText } = attachState; + const { queryStates } = useProductQueryState(); + const { attachState, product, entityId } = useProductContext(); + const { buttonText, setPreview } = attachState; const handleAttachClicked = async () => { setButtonLoading(true); + try { const res = await axios.post( "/v1/attach/preview", @@ -30,15 +33,17 @@ export const AttachButton = () => { attachState, product, entityId, - version: version || product.version, + version: queryStates.version || product.version, }) ); setPreview(res.data); setOpen(true); } catch (error) { + console.log("error", error); toast.error(getBackendErr(error, "Failed to attach product")); } + setButtonLoading(false); }; diff --git a/vite/src/views/customers/customer/product/components/AttachModal.tsx b/vite/src/views/customers/customer/product/components/AttachModal.tsx index 993288003..1e53c8e9a 100644 --- a/vite/src/views/customers/customer/product/components/AttachModal.tsx +++ b/vite/src/views/customers/customer/product/components/AttachModal.tsx @@ -1,3 +1,5 @@ +import { cn } from "@/lib/utils"; + import { Button } from "@/components/ui/button"; import { useState } from "react"; import { @@ -26,12 +28,11 @@ import { useAxiosInstance } from "@/services/useAxiosInstance"; import { useEnv } from "@/utils/envUtils"; import { getStripeInvoiceLink } from "@/utils/linkUtils"; import { AttachPreviewDetails } from "./AttachPreviewDetails"; -import { ToggleConfigButton } from "./ToggleConfigButton"; -import { cn } from "@/lib/utils"; -import { Separator } from "@/components/ui/separator"; + import { AttachInfo } from "./attach-preview/AttachInfo"; import { getAttachBody } from "./attachProductUtils"; import { InvoiceCustomerButton } from "./InvoiceCustomerButton"; +import { useCusQuery } from "../../hooks/useCusQuery"; export const AttachModal = ({ open, @@ -40,16 +41,16 @@ export const AttachModal = ({ open: boolean; setOpen: (open: boolean) => void; }) => { - const { product, customer, entities, entityId, attachState, version } = - useProductContext(); + const { customer, entities } = useCusQuery(); + const { product, entityId, attachState, version } = useProductContext(); const navigation = useNavigate(); const env = useEnv(); const axiosInstance = useAxiosInstance(); - const { preview, options, setOptions, flags } = attachState; + const { preview, options, flags } = attachState; const [checkoutLoading, setCheckoutLoading] = useState(false); - const [invoiceLoading, setInvoiceLoading] = useState(false); + const [invoiceLoading] = useState(false); const getName = () => { const cusName = @@ -191,9 +192,10 @@ export const AttachModal = ({ } }; - const [configOpen, setConfigOpen] = useState(false); + const [configOpen] = useState(false); const mainWidth = "w-lg"; + return ( @@ -210,10 +212,12 @@ export const AttachModal = ({

Details

+ Product {product?.name} + Customer {getName()} @@ -221,6 +225,7 @@ export const AttachModal = ({
{preview && !flags.isFree && } +
diff --git a/vite/src/views/customers/customer/product/components/AttachPreviewDetails.tsx b/vite/src/views/customers/customer/product/components/AttachPreviewDetails.tsx index 52217de43..9ddf2ed45 100644 --- a/vite/src/views/customers/customer/product/components/AttachPreviewDetails.tsx +++ b/vite/src/views/customers/customer/product/components/AttachPreviewDetails.tsx @@ -8,7 +8,7 @@ import { AttachBranch } from "@autumn/shared"; import { OptionsInput } from "./attach-preview/OptionsInput"; export const AttachPreviewDetails = () => { - const { org, attachState } = useProductContext(); + const { attachState } = useProductContext(); const { preview } = attachState; if (!preview) { @@ -20,9 +20,6 @@ export const AttachPreviewDetails = () => { return ( - {/* Options */} - - {/* */} {!isUpdatePrepaidQuantity && ( <> diff --git a/vite/src/views/customers/customer/product/components/CustomerProductBadge.tsx b/vite/src/views/customers/customer/product/components/CustomerProductBadge.tsx index 5bac54484..70560daa1 100644 --- a/vite/src/views/customers/customer/product/components/CustomerProductBadge.tsx +++ b/vite/src/views/customers/customer/product/components/CustomerProductBadge.tsx @@ -1,8 +1,10 @@ import { Badge } from "@/components/ui/badge"; import { useProductContext } from "@/views/products/product/ProductContext"; +import { useCusQuery } from "../../hooks/useCusQuery"; export const CustomerProductBadge = () => { - const { product, customer } = useProductContext(); + const { customer } = useCusQuery(); + const { product } = useProductContext(); if (!customer) return null; diff --git a/vite/src/views/customers/customer/product/components/CustomerProductBreadcrumbs.tsx b/vite/src/views/customers/customer/product/components/CustomerProductBreadcrumbs.tsx index be1930a78..ac5b339b1 100644 --- a/vite/src/views/customers/customer/product/components/CustomerProductBreadcrumbs.tsx +++ b/vite/src/views/customers/customer/product/components/CustomerProductBreadcrumbs.tsx @@ -6,13 +6,14 @@ import { BreadcrumbSeparator, } from "@/components/ui/breadcrumb"; import { useEnv } from "@/utils/envUtils"; -import { navigateTo } from "@/utils/genUtils"; +import { navigateTo, pushPage } from "@/utils/genUtils"; import { useProductContext } from "@/views/products/product/ProductContext"; import { useNavigate } from "react-router"; +import { useCusQuery } from "../../hooks/useCusQuery"; export const CustomerProductBreadcrumbs = () => { - const env = useEnv(); - const { customer, product, entityId } = useProductContext(); + const { customer } = useCusQuery(); + const { product, entityId } = useProductContext(); const navigation = useNavigate(); return ( @@ -21,7 +22,7 @@ export const CustomerProductBreadcrumbs = () => { navigateTo("/customers", navigation, env)} + onClick={() => navigateTo("/customers", navigation)} > Customers @@ -30,13 +31,13 @@ export const CustomerProductBreadcrumbs = () => { - navigateTo( - `/customers/${customer.id || customer.internal_id}${ - entityId ? `?entity_id=${entityId}` : "" - }`, - navigation, - env, - ) + pushPage({ + path: `/customers/${customer.id || customer.internal_id}`, + navigate: navigation, + queryParams: { + entity_id: entityId, + }, + }) } > {customer.name diff --git a/vite/src/views/customers/customer/product/components/attach-preview/AttachNewItems.tsx b/vite/src/views/customers/customer/product/components/attach-preview/AttachNewItems.tsx index 04675891c..b1c61495b 100644 --- a/vite/src/views/customers/customer/product/components/attach-preview/AttachNewItems.tsx +++ b/vite/src/views/customers/customer/product/components/attach-preview/AttachNewItems.tsx @@ -1,4 +1,5 @@ import { PriceItem } from "@/components/pricing/attach-pricing-dialog"; +import { useOrg } from "@/hooks/common/useOrg"; import { isFeaturePriceItem } from "@/utils/product/getItemType"; import { getFeatureString, @@ -7,7 +8,8 @@ import { import { useProductContext } from "@/views/products/product/ProductContext"; export const AttachNewItems = () => { - const { attachState, features, org } = useProductContext(); + const { org } = useOrg(); + const { attachState, features } = useProductContext(); const { preview } = attachState; if (preview?.new_items) { diff --git a/vite/src/views/customers/customer/product/components/attach-preview/DueNextCycle.tsx b/vite/src/views/customers/customer/product/components/attach-preview/DueNextCycle.tsx index 465856d0e..bcbe3d546 100644 --- a/vite/src/views/customers/customer/product/components/attach-preview/DueNextCycle.tsx +++ b/vite/src/views/customers/customer/product/components/attach-preview/DueNextCycle.tsx @@ -6,9 +6,14 @@ import { useProductContext } from "@/views/products/product/ProductContext"; import { getFeatureInvoiceDescription } from "@autumn/shared"; import { formatAmount } from "@/utils/formatUtils/formatTextUtils"; import { AdjustableOptions } from "./AdjustQuantity"; +import { useOrg } from "@/hooks/common/useOrg"; +import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery"; export const DueNextCycle = () => { - const { attachState, product, features, org } = useProductContext(); + const { org } = useOrg(); + const { features } = useFeaturesQuery(); + const { attachState, product } = useProductContext(); + const preview = attachState.preview; const currency = org.default_currency || "USD"; diff --git a/vite/src/views/customers/customer/product/components/attach-preview/DueToday.tsx b/vite/src/views/customers/customer/product/components/attach-preview/DueToday.tsx index bad0d8599..39677977f 100644 --- a/vite/src/views/customers/customer/product/components/attach-preview/DueToday.tsx +++ b/vite/src/views/customers/customer/product/components/attach-preview/DueToday.tsx @@ -13,9 +13,11 @@ import { import { Decimal } from "decimal.js"; import { Input } from "@/components/ui/input"; import { notNullish } from "@/utils/genUtils"; +import { useOrg } from "@/hooks/common/useOrg"; export const DueToday = () => { - const { attachState, product, org } = useProductContext(); + const { org } = useOrg(); + const { attachState, product } = useProductContext(); const { preview, options, setOptions } = attachState; const dueToday = preview.due_today; diff --git a/vite/src/views/customers/customer/product/components/attach-preview/UpdateQuantity.tsx b/vite/src/views/customers/customer/product/components/attach-preview/UpdateQuantity.tsx index b7e42ac1a..0c111bd15 100644 --- a/vite/src/views/customers/customer/product/components/attach-preview/UpdateQuantity.tsx +++ b/vite/src/views/customers/customer/product/components/attach-preview/UpdateQuantity.tsx @@ -1,12 +1,14 @@ import { PriceItem } from "@/components/pricing/attach-pricing-dialog"; import { Input } from "@/components/ui/input"; +import { useOrg } from "@/hooks/common/useOrg"; import { notNullish } from "@/utils/genUtils"; import { formatAmount } from "@/utils/product/productItemUtils"; import { useProductContext } from "@/views/products/product/ProductContext"; import { AttachBranch } from "@autumn/shared"; export const UpdateQuantity = () => { - const { attachState, product, org } = useProductContext(); + const { org } = useOrg(); + const { attachState, product } = useProductContext(); const { preview, options, setOptions } = attachState; if (preview.branch !== AttachBranch.UpdatePrepaidQuantity) { diff --git a/vite/src/views/customers/customer/product/hooks/useAttachState.tsx b/vite/src/views/customers/customer/product/hooks/useAttachState.tsx index b7c6dc5d5..eab9fb0c6 100644 --- a/vite/src/views/customers/customer/product/hooks/useAttachState.tsx +++ b/vite/src/views/customers/customer/product/hooks/useAttachState.tsx @@ -4,7 +4,10 @@ import { isOneOffProduct } from "@/utils/product/priceUtils"; import { sortProductItems } from "@/utils/productUtils"; import { AttachPreview, + CusProduct, FeatureOptions, + FullCusProduct, + isCanceled, ProductItem, ProductV2, UsageModel, @@ -32,22 +35,25 @@ const productHasPrepaid = (items: ProductItem[]) => { ); }; -const productIsAddOn = (product: FrontendProduct) => { +const productIsAddOn = (product: ProductV2) => { return product.is_add_on; }; -const productIsFree = (product: FrontendProduct) => { +const productIsFree = (product: ProductV2) => { return product.items.every((item) => isFeatureItem(item)); }; export const useAttachState = ({ product, + cusProduct, setProduct, initialProductRef, }: { - product: FrontendProduct | null; - setProduct: (product: FrontendProduct) => void; - initialProductRef: React.RefObject; + product: ProductV2 | null; + setProduct: (product: ProductV2) => void; + + cusProduct: FullCusProduct | undefined; + initialProductRef: React.RefObject; }) => { const [preview, setPreview] = useState(null); const [options, setOptions] = useState< @@ -58,12 +64,13 @@ export const useAttachState = ({ >([]); const [itemsChanged, setItemsChanged] = useState(false); - const [flags, setFlags] = useState({ hasPrepaid: product ? productHasPrepaid(product.items) : false, isAddOn: product ? productIsAddOn(product) : false, isFree: product ? productIsFree(product) : false, - isCanceled: product ? product.isCanceled : false, + + isCanceled: cusProduct ? isCanceled({ cusProduct }) : false, + isOneOff: product ? isOneOffProduct(product.items, product.is_add_on) : false, @@ -80,7 +87,7 @@ export const useAttachState = ({ hasPrepaid: product ? productHasPrepaid(product.items) : false, isAddOn: product ? productIsAddOn(product) : false, isFree: product ? productIsFree(product) : false, - isCanceled: product ? product.isCanceled : false, + isCanceled: cusProduct ? isCanceled({ cusProduct }) : false, isOneOff: product ? isOneOffProduct(product.items, product.is_add_on) : false, @@ -119,7 +126,7 @@ export const useAttachState = ({ const getButtonDisabled = () => { if (flags.isOneOff) return false; - if (product?.isActive && !itemsChanged && !flags.isCanceled) { + if (cusProduct && !itemsChanged && !flags.isCanceled) { if (flags.hasPrepaid) { return false; } @@ -147,7 +154,7 @@ export const useAttachState = ({ return AttachCase.OneOff; } - if (product?.isActive && !itemsChanged) { + if (cusProduct && !itemsChanged) { return AttachCase.Active; } @@ -157,7 +164,7 @@ export const useAttachState = ({ }; const getButtonText = () => { - if (product?.isActive && !itemsChanged) { + if (cusProduct && !itemsChanged) { if (flags.isOneOff) { return "Attach Product"; } diff --git a/vite/src/views/customers/customer/product/hooks/useCusProductQuery.tsx b/vite/src/views/customers/customer/product/hooks/useCusProductQuery.tsx new file mode 100644 index 000000000..54dc59bcf --- /dev/null +++ b/vite/src/views/customers/customer/product/hooks/useCusProductQuery.tsx @@ -0,0 +1,61 @@ +import { useAxiosInstance } from "@/services/useAxiosInstance"; +import { FullCusProduct, ProductV2 } from "@autumn/shared"; +import { useQuery } from "@tanstack/react-query"; +import { parseAsInteger, parseAsString, useQueryStates } from "nuqs"; +import { useParams } from "react-router"; + +export const useCusProductQuery = () => { + const axiosInstance = useAxiosInstance(); + const { customer_id, product_id } = useParams(); + + const [queryStates] = useQueryStates({ + version: parseAsInteger, + customer_product_id: parseAsString, + entity_id: parseAsString, + }); + + const fetcher = async () => { + const queryParams = { + version: queryStates.version, + customer_product_id: queryStates.customer_product_id, + entity_id: queryStates.entity_id, + }; + + try { + const { data } = await axiosInstance.get( + `/customers/${customer_id}/product/${product_id}`, + { params: queryParams } + ); + return data; + } catch (error) { + return null; + } + }; + + const { data, isLoading, error, refetch } = useQuery<{ + cusProduct: FullCusProduct; + product: ProductV2; + }>({ + queryKey: ["customer_product", customer_id, product_id], + queryFn: fetcher, + }); + + return { + cusProduct: data?.cusProduct, + product: data?.product, + isLoading, + error, + refetch, + }; + + // const { data, isLoading, error, refetch } = useQuery({ + // queryKey: ["customer_product", customer_id, product_id], + // queryFn: fetcher, + // }); + // return { + // cusProduct, + // isLoading, + // error, + // refetch, + // }; +}; diff --git a/vite/src/views/products/features/components/CreateFeature.tsx b/vite/src/views/products/features/components/CreateFeature.tsx index 38680b176..2f59444d5 100644 --- a/vite/src/views/products/features/components/CreateFeature.tsx +++ b/vite/src/views/products/features/components/CreateFeature.tsx @@ -77,10 +77,10 @@ export const CreateFeature = ({ } ); + await refetch(); if (onSuccess) { await onSuccess(createdFeature); } else { - await refetch(); setOpen(false); } } catch (error) { diff --git a/vite/src/views/products/product/ProductSidebar.tsx b/vite/src/views/products/product/ProductSidebar.tsx index bfef0556c..a4a714276 100644 --- a/vite/src/views/products/product/ProductSidebar.tsx +++ b/vite/src/views/products/product/ProductSidebar.tsx @@ -15,7 +15,7 @@ import { UpdateProductButton } from "./components/UpdateProductButton"; import { isOneOffProduct } from "@/utils/product/priceUtils"; export default function ProductSidebar() { - const { product, setProduct, customer } = useProductContext(); + const { product, setProduct, isCusProductView } = useProductContext(); const [freeTrialModalOpen, setFreeTrialModalOpen] = useState(false); const [entitiesOpen, setEntitiesOpen] = useState(false); const [accordionValues, setAccordionValues] = useState([ @@ -32,8 +32,6 @@ export default function ProductSidebar() { setProduct({ ...product, free_trial: null }); }; - const isCustomerProductView = notNullish(customer); - const handleAccordionToggle = (value: string) => { setAccordionValues((prev) => prev.includes(value) ? prev.filter((v) => v !== value) : [...prev, value] @@ -43,7 +41,7 @@ export default function ProductSidebar() { return (
- {isCustomerProductView && } + {isCusProductView && }
-
+ {/*
-
+
*/}
- {isCustomerProductView ? : } + {isCusProductView ? : }
diff --git a/vite/src/views/products/product/ProductView.tsx b/vite/src/views/products/product/ProductView.tsx index ccbbdf717..9cff144b7 100644 --- a/vite/src/views/products/product/ProductView.tsx +++ b/vite/src/views/products/product/ProductView.tsx @@ -19,6 +19,7 @@ import { getBackendErr } from "@/utils/genUtils"; import { useProductChangedAlert } from "./hooks/useProductChangedAlert"; import { useProductData } from "./hooks/useProductData"; import { UpdateProductButton } from "./components/UpdateProductButton"; +import { useProductQuery } from "./hooks/useProductQuery"; function ProductView({ env }: { env: AppEnv }) { const axiosInstance = useAxiosInstance(); @@ -26,115 +27,108 @@ function ProductView({ env }: { env: AppEnv }) { const [searchParams] = useSearchParams(); const version = searchParams.get("version"); + // const url = `/products/${product_id}/data?version=${version}`; + // const { data, isLoading, mutate } = useAxiosSWR({ url }); + + // const countUrl = `/products/${product_id}/count?version=${version}`; + // const { data: counts, mutate: mutateCount } = useAxiosSWR({ url: countUrl }); + const [showNewVersionDialog, setShowNewVersionDialog] = useState(false); - - const url = `/products/${product_id}/data?version=${version}`; - const { data, isLoading, mutate } = useAxiosSWR({ url }); - - const countUrl = `/products/${product_id}/count?version=${version}`; - const { data: counts, mutate: mutateCount } = useAxiosSWR({ url: countUrl }); - - //this is to make sure pricing for unlimited entitlements can't be applied - const [selectedEntitlementAllowance, setSelectedEntitlementAllowance] = - useState<"unlimited" | number>(0); + const { product: originalProduct, isLoading, error } = useProductQuery(); const { product, setProduct, hasChanges, - features, - setFeatures, entityFeatureIds, setEntityFeatureIds, actionState, isNewProduct, - } = useProductData({ - originalProduct: data?.product as any, - originalFeatures: data?.features as any, - }); + } = useProductData({ originalProduct }); const { modal } = useProductChangedAlert({ hasChanges }); - const [buttonLoading, setButtonLoading] = useState(false); if (isLoading) return ; - - if (!product) { + if (error) { return ( - Product {product_id} not found + {error ? error.message : `Product ${product_id} not found`} ); } - const updateProduct = async () => { - try { - await ProductService.updateProduct(axiosInstance, product.id, { - ...UpdateProductSchema.parse(product), - items: product.items, - free_trial: product.free_trial, - }); + if (!product) return <>; - if (isNewProduct) { - toast.success("Product created successfully"); - } else { - toast.success("Product updated successfully"); - } + // const updateProduct = async () => { + // try { + // await ProductService.updateProduct(axiosInstance, product.id, { + // ...UpdateProductSchema.parse(product), + // items: product.items, + // free_trial: product.free_trial, + // }); - await mutate(); - await mutateCount(); - } catch (error) { - toast.error(getBackendErr(error, "Failed to update product")); - } - }; + // if (isNewProduct) { + // toast.success("Product created successfully"); + // } else { + // toast.success("Product updated successfully"); + // } - const updateProductClicked = async () => { - if (!counts) { - toast.error("Something went wrong, please try again..."); - return; - } + // await refetch(); + // await mutateCount(); + // } catch (error) { + // toast.error(getBackendErr(error, "Failed to update product")); + // } + // }; - if (version && version < data?.numVersions) { - toast.error("You can only update the latest version of a product"); - return; - } + // const updateProductClicked = async () => { + // if (!counts) { + // toast.error("Something went wrong, please try again..."); + // return; + // } - if (counts?.all > 0) { - setShowNewVersionDialog(true); - return; - } + // if (version && version < data?.numVersions) { + // toast.error("You can only update the latest version of a product"); + // return; + // } - await updateProduct(); - }; + // if (counts?.all > 0) { + // setShowNewVersionDialog(true); + // return; + // } + + // await updateProduct(); + // }; return ( - + /> */}
diff --git a/vite/src/views/products/product/components/ProductViewBreadcrumbs.tsx b/vite/src/views/products/product/components/ProductViewBreadcrumbs.tsx index 0d020aad0..8a31e7727 100644 --- a/vite/src/views/products/product/components/ProductViewBreadcrumbs.tsx +++ b/vite/src/views/products/product/components/ProductViewBreadcrumbs.tsx @@ -11,9 +11,9 @@ import { useProductContext } from "../ProductContext"; import { useEnv } from "@/utils/envUtils"; export default function ProductViewBreadcrumbs() { - let { product } = useProductContext(); - let env = useEnv(); - let navigate = useNavigate(); + const { product } = useProductContext(); + const env = useEnv(); + const navigate = useNavigate(); return ( diff --git a/vite/src/views/products/product/components/UpdateProductButton.tsx b/vite/src/views/products/product/components/UpdateProductButton.tsx index f6e3753e1..09b7d8ead 100644 --- a/vite/src/views/products/product/components/UpdateProductButton.tsx +++ b/vite/src/views/products/product/components/UpdateProductButton.tsx @@ -2,19 +2,51 @@ import { Button } from "@/components/ui/button"; import { useState } from "react"; import { useProductContext } from "@/views/products/product/ProductContext"; import { Upload } from "lucide-react"; +import { useProductQuery } from "../hooks/useProductQuery"; +import { useAxiosInstance } from "@/services/useAxiosInstance"; +import { UpdateProductSchema } from "@autumn/shared"; +import { toast } from "sonner"; +import { getBackendErr } from "@/utils/genUtils"; export const UpdateProductButton = () => { - const [open, setOpen] = useState(false); - // const [loading, setLoading] = useState(false); + const [buttonLoading, setButtonLoading] = useState(false); + const axiosInstance = useAxiosInstance(); - const { handleCreateProduct, actionState, buttonLoading, setButtonLoading } = - useProductContext(); + const { actionState, product } = useProductContext(); + + const { refetch } = useProductQuery(); + + const updateProduct = async () => { + try { + await axiosInstance.post(`/v1/products/${product.id}`, { + ...UpdateProductSchema.parse(product), + items: product.items, + free_trial: product.free_trial, + }); + // await ProductService.updateProduct(axiosInstance, product.id, { + // ...UpdateProductSchema.parse(product), + // items: product.items, + // free_trial: product.free_trial, + // }); + + toast.success("Product updated successfully"); + // if (isNewProduct) { + // toast.success("Product created successfully"); + // } else { + // } + + await refetch(); + // await mutateCount(); + } catch (error) { + toast.error(getBackendErr(error, "Failed to update product")); + } + }; return ( - - - Create Price - - - - - -
- ); -}; diff --git a/vite/src/views/products/product/product-item/CreateProductItem.tsx b/vite/src/views/products/product/product-item/CreateProductItem.tsx index 5b64b36a6..76e0b1d79 100644 --- a/vite/src/views/products/product/product-item/CreateProductItem.tsx +++ b/vite/src/views/products/product/product-item/CreateProductItem.tsx @@ -26,6 +26,7 @@ import { emptyPriceItem } from "./create-product-item/defaultItemConfigs"; import { itemsHaveSameInterval } from "@/utils/product/productItemUtils"; import { toast } from "sonner"; import { getFeature } from "@/utils/product/entitlementUtils"; +import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery"; export const defaultProductItem: ProductItem = { feature_id: null, @@ -150,15 +151,12 @@ const useMergeFeaturePriceItem = ({ }; export function CreateProductItem() { + const { features } = useFeaturesQuery(); + const { product, setProduct } = useProductContext(); + const [open, setOpen] = useState(false); const [showCreateFeature, setShowCreateFeature] = useState(false); const [item, setItem] = useState(defaultProductItem); - const { features, product, setProduct, setFeatures, counts, mutate } = - useProductContext(); - - const [configState, setConfigState] = useState({ - showPrice: false, - }); const { shouldMergeFeatureItem, mergeFeatureItem } = useMergeFeatureItem({ item, diff --git a/vite/src/views/products/product/product-item/CreateProductItem2.tsx b/vite/src/views/products/product/product-item/CreateProductItem2.tsx index 59f77a036..67fb33b69 100644 --- a/vite/src/views/products/product/product-item/CreateProductItem2.tsx +++ b/vite/src/views/products/product/product-item/CreateProductItem2.tsx @@ -19,6 +19,7 @@ import { useSteps } from "./useSteps"; import { CreateItemStep } from "./utils/CreateItemStep"; import { cn } from "@/lib/utils"; import { defaultPriceItem } from "./create-product-item/defaultItemConfigs"; +import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery"; const defaultProductItem: ProductItem = { feature_id: null, @@ -47,14 +48,15 @@ export function CreateProductItem2({ const [open, setOpen] = useState(false); const [showCreateFeature, setShowCreateFeature] = useState(false); const [item, setItem] = useState(defaultProductItem); - const { features, product, setProduct } = useProductContext(); - const { setFirstItemCreated } = useModelPricingContext(); + const { product, setProduct } = useProductContext(); + const { features } = useFeaturesQuery(); const stepState = useSteps({ initialStep: CreateItemStep.SelectItemType }); const handleCreateProductItem = async (entityFeatureId?: string) => { const validatedItem = validateProductItem({ item: { + isPrice: false, ...item, entity_feature_id: entityFeatureId ? entityFeatureId diff --git a/vite/src/views/products/product/product-item/EntitiesDropdown.tsx b/vite/src/views/products/product/product-item/EntitiesDropdown.tsx index 729439583..7c96a50f5 100644 --- a/vite/src/views/products/product/product-item/EntitiesDropdown.tsx +++ b/vite/src/views/products/product/product-item/EntitiesDropdown.tsx @@ -1,8 +1,3 @@ -import { - DialogContent, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Dialog, DialogTrigger } from "@/components/ui/dialog"; import { @@ -11,12 +6,7 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; -import { - Feature, - features, - FeatureUsageType, - ProductItem, -} from "@autumn/shared"; +import { Feature, FeatureUsageType, ProductItem } from "@autumn/shared"; import { useState } from "react"; import { useProductContext } from "../ProductContext"; import { CheckIcon, PlusIcon } from "lucide-react"; @@ -24,6 +14,7 @@ import { CreateFeature } from "@/views/products/features/components/CreateFeatur import { toast } from "sonner"; import { cn } from "@/lib/utils"; import { CustomDialogContent } from "@/components/general/modal-components/DialogContentWrapper"; +import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery"; export const EntitiesDropdown = ({ open, @@ -50,8 +41,10 @@ export const EntitiesDropdown = ({ }; export const EntitiesDropdownContent = () => { - const { product, features, entityFeatureIds, setEntityFeatureIds } = + const { features } = useFeaturesQuery(); + const { product, entityFeatureIds, setEntityFeatureIds } = useProductContext(); + const [createFeatureOpen, setCreateFeatureOpen] = useState(false); const continuousUseFeatures = features.filter( diff --git a/vite/src/views/products/product/product-item/ProductItemConfig.tsx b/vite/src/views/products/product/product-item/ProductItemConfig.tsx index 3fdca1731..c2a7eb48a 100644 --- a/vite/src/views/products/product/product-item/ProductItemConfig.tsx +++ b/vite/src/views/products/product/product-item/ProductItemConfig.tsx @@ -6,43 +6,25 @@ import { ProductItemInterval, TierInfinite, } from "@autumn/shared"; -import { useEffect, useState } from "react"; -import { useProductContext } from "@/views/products/product/ProductContext"; -import { cn } from "@/lib/utils"; -import { useProductItemContext } from "./ProductItemContext"; import { getShowParams, shouldShowProrationConfig, } from "@/utils/product/productItemUtils"; + +import { cn } from "@/lib/utils"; +import { useEffect, useState } from "react"; +import { useProductItemContext } from "./ProductItemContext"; +import { useProductContext } from "@/views/products/product/ProductContext"; import { ConfigWithFeature } from "./components/ConfigWithFeature"; -import FixedPriceConfig from "./components/ConfigFixedPrice"; -import { - isFeatureItem, - isFeaturePriceItem, - isPriceItem, -} from "@/utils/product/getItemType"; import { PriceItemConfig } from "./product-item-config/PriceItemConfig"; +import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery"; export const ProductItemConfig = () => { // HOOKS - const { features } = useProductContext(); - const { item, setItem } = useProductItemContext(); - const [show, setShow] = useState(getShowParams(item)); - const handleAddPrice = () => { - setItem({ - ...item, - tiers: [ - { - to: TierInfinite, - amount: item.price ?? 0, - }, - ], - interval: ProductItemInterval.Month, - }); - setShow({ ...show, price: !show.price }); - }; + const { features } = useFeaturesQuery(); + const { item, setItem } = useProductItemContext(); useEffect(() => { const feature = features.find((f: Feature) => f.id == item.feature_id); diff --git a/vite/src/views/products/product/product-item/ProductItemRow.tsx b/vite/src/views/products/product/product-item/ProductItemRow.tsx index 180f87db5..e8faec492 100644 --- a/vite/src/views/products/product/product-item/ProductItemRow.tsx +++ b/vite/src/views/products/product/product-item/ProductItemRow.tsx @@ -66,15 +66,15 @@ export const ProductItemRow = ({ const getFreeFeatureString = (item: ProductItem) => { const feature = features.find((f: Feature) => f.id == item.feature_id); + if (feature?.type === FeatureType.Boolean) { + return `${feature.name}`; + } + const featureName = getName({ featureId: item.feature_id!, units: item.included_usage, }); - if (feature?.type === FeatureType.Boolean) { - return `${featureName}`; - } - if (item.included_usage == Infinite) { return `Unlimited ${featureName}`; } diff --git a/vite/src/views/products/product/product-item/ProductItemTable.tsx b/vite/src/views/products/product/product-item/ProductItemTable.tsx index 734ce8db6..c710a9432 100644 --- a/vite/src/views/products/product/product-item/ProductItemTable.tsx +++ b/vite/src/views/products/product/product-item/ProductItemTable.tsx @@ -20,10 +20,12 @@ import { EntitiesDropdownContent } from "./EntitiesDropdown"; import { CreateFreeTrial } from "../free-trial/CreateFreeTrial"; import { InfoTooltip } from "@/components/general/modal-components/InfoTooltip"; import { getFrontendItemConfig } from "./utils/getFrontendItemConfig"; +import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery"; export const ProductItemTable = () => { - const { product, features, org, entityFeatureIds, isOnboarding, autoSave } = - useProductContext(); + const { features } = useFeaturesQuery(); + + const { product, org, entityFeatureIds, isOnboarding } = useProductContext(); const [selectedItem, setSelectedItem] = useState(null); const [selectedIndex, setSelectedIndex] = useState(null); diff --git a/vite/src/views/products/product/product-item/UpdateProductItem.tsx b/vite/src/views/products/product/product-item/UpdateProductItem.tsx index 470ad9eb2..c4b1bdf70 100644 --- a/vite/src/views/products/product/product-item/UpdateProductItem.tsx +++ b/vite/src/views/products/product/product-item/UpdateProductItem.tsx @@ -11,8 +11,8 @@ import CopyButton from "@/components/general/CopyButton"; import { CustomDialogContent, CustomDialogBody, - CustomDialogFooter, } from "@/components/general/modal-components/DialogContentWrapper"; + import { ItemConfigFooter } from "./product-item-config/item-config-footer/ItemConfigFooter"; import { AdvancedConfigSidebar, @@ -20,6 +20,7 @@ import { ToggleAdvancedConfigButton, } from "./product-item-config/AdvancedConfigSidebar"; import { isPriceItem } from "@/utils/product/getItemType"; +import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery"; export default function UpdateProductItem({ selectedItem, @@ -34,7 +35,8 @@ export default function UpdateProductItem({ open: boolean; setOpen: (open: boolean) => void; }) { - const { product, setProduct, features } = useProductContext(); + const { features } = useFeaturesQuery(); + const { product, setProduct } = useProductContext(); const [showCreateFeature, setShowCreateFeature] = useState(false); const [advancedOpen, setAdvancedOpen] = useState(false); diff --git a/vite/src/views/products/product/product-item/components/ConfigWithFeature.tsx b/vite/src/views/products/product/product-item/components/ConfigWithFeature.tsx index 27b93f608..bd2a34efd 100644 --- a/vite/src/views/products/product/product-item/components/ConfigWithFeature.tsx +++ b/vite/src/views/products/product/product-item/components/ConfigWithFeature.tsx @@ -11,10 +11,12 @@ import { Button } from "@/components/ui/button"; import { isFeatureItem, isFeaturePriceItem } from "@/utils/product/getItemType"; import { itemsHaveSameInterval } from "@/utils/product/productItemUtils"; import { toast } from "sonner"; +import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery"; export const ConfigWithFeature = () => { - const { features, product, setProduct } = useProductContext(); - const { item, isUpdate, setOpen, warning } = useProductItemContext(); + const { product, setProduct } = useProductContext(); + const { item, setOpen, warning } = useProductItemContext(); + const { features } = useFeaturesQuery(); const isBooleanFeature = getFeature(item.feature_id, features)?.type === FeatureType.Boolean; diff --git a/vite/src/views/products/product/product-item/components/SelectItemFeature.tsx b/vite/src/views/products/product/product-item/components/SelectItemFeature.tsx index 32435e4b6..f2d6ad970 100644 --- a/vite/src/views/products/product/product-item/components/SelectItemFeature.tsx +++ b/vite/src/views/products/product/product-item/components/SelectItemFeature.tsx @@ -15,9 +15,10 @@ import { PlusIcon } from "lucide-react"; import { getItemType } from "@/utils/product/productItemUtils"; import { CreateItemStep } from "../utils/CreateItemStep"; import { useEffect, useState } from "react"; +import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery"; export const SelectItemFeature = () => { - const { features } = useProductContext(); + const { features } = useFeaturesQuery(); const { item, setItem, isUpdate, stepState } = useProductItemContext(); const [open, setOpen] = useState(false); const itemType = getItemType(item); diff --git a/vite/src/views/products/product/product-item/create-product-item/CreateItemDialogContent.tsx b/vite/src/views/products/product/product-item/create-product-item/CreateItemDialogContent.tsx index 503e96ea2..aece9d810 100644 --- a/vite/src/views/products/product/product-item/create-product-item/CreateItemDialogContent.tsx +++ b/vite/src/views/products/product/product-item/create-product-item/CreateItemDialogContent.tsx @@ -2,36 +2,26 @@ import { CustomDialogBody, CustomDialogContent, } from "@/components/general/modal-components/DialogContentWrapper"; -import { DialogHeader, DialogTitle } from "@/components/ui/dialog"; -import { useProductItemContext } from "../ProductItemContext"; -import { ProductItemConfig } from "../ProductItemConfig"; -import { - ProductItemType, - Infinite, - FeatureType, - BillingInterval, - CreateFeature as CreateFeatureType, -} from "@autumn/shared"; -import { ItemConfigFooter } from "../product-item-config/item-config-footer/ItemConfigFooter"; -import { useEffect, useState } from "react"; -import { useProductContext } from "../../ProductContext"; -import { CreateItemIntro } from "./CreateItemIntro"; -import { getItemType } from "@/utils/product/productItemUtils"; -import { CreateFeature } from "@/views/products/features/components/CreateFeature"; -import { CreateItemStep } from "../utils/CreateItemStep"; -import { useSteps } from "../useSteps"; -import { SelectFeatureStep } from "../product-item-config/components/SelectFeature"; -import { keyToTitle } from "@/utils/formatUtils/formatTextUtils"; -import { Button } from "@/components/ui/button"; -import { AdvancedItemConfig } from "../product-item-config/advanced-config/AdvancedItemConfig"; -import { ChevronRight, X } from "lucide-react"; -import { isFeatureItem } from "@/utils/product/getItemType"; + import { AdvancedConfigSidebar, MainDialogBodyWrapper, ToggleAdvancedConfigButton, } from "../product-item-config/AdvancedConfigSidebar"; +import { DialogHeader, DialogTitle } from "@/components/ui/dialog"; +import { useProductItemContext } from "../ProductItemContext"; +import { ProductItemConfig } from "../ProductItemConfig"; +import { CreateFeature as CreateFeatureType } from "@autumn/shared"; +import { ItemConfigFooter } from "../product-item-config/item-config-footer/ItemConfigFooter"; +import { useEffect, useState } from "react"; +import { useProductContext } from "../../ProductContext"; +import { getItemType } from "@/utils/product/productItemUtils"; +import { CreateFeature } from "@/views/products/features/components/CreateFeature"; +import { CreateItemStep } from "../utils/CreateItemStep"; +import { keyToTitle } from "@/utils/formatUtils/formatTextUtils"; +import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery"; + export const CreateItemDialogContent = ({ open, setOpen, @@ -39,18 +29,11 @@ export const CreateItemDialogContent = ({ open: boolean; setOpen: (open: boolean) => void; }) => { - const { features, setFeatures } = useProductContext(); + // const { features, setFeatures } = useProductContext(); + const { features } = useFeaturesQuery(); const { stepState, item, setItem } = useProductItemContext(); - const { - stepVal, - popStep, - pushStep, - resetSteps, - previousStep, - replaceStep, - stepCount, - } = stepState; + const { stepVal, popStep, resetSteps, replaceStep, stepCount } = stepState; useEffect(() => { if (open) { @@ -59,15 +42,8 @@ export const CreateItemDialogContent = ({ }, [open]); const handleFeatureCreated = async (feature: CreateFeatureType) => { - setFeatures([...features, feature]); + // setFeatures([...features, feature]); setItem({ ...item, feature_id: feature.id! }); - - // // replaceStep(CreateItemStep.CreateItem); - // if (previousStep === CreateItemStep.CreateItem) { - // replaceStep(CreateItemStep.CreateItem); - // } else { - // pushStep(CreateItemStep.CreateItem); - // } replaceStep(CreateItemStep.CreateItem); }; diff --git a/vite/src/views/products/product/product-item/product-item-config/FeatureItemConfig.tsx b/vite/src/views/products/product/product-item/product-item-config/FeatureItemConfig.tsx index e82964e7b..f52bb511a 100644 --- a/vite/src/views/products/product/product-item/product-item-config/FeatureItemConfig.tsx +++ b/vite/src/views/products/product/product-item/product-item-config/FeatureItemConfig.tsx @@ -6,19 +6,16 @@ import { SelectResetCycle } from "./components/SelectResetCycle"; import FeaturePrice from "./components/feature-price/FeaturePrice"; import { isFeatureItem, isFeaturePriceItem } from "@/utils/product/getItemType"; import React from "react"; -import { Button } from "@/components/ui/button"; -import { PlusIcon } from "lucide-react"; -import { PrepaidToggle } from "./components/feature-price/PrepaidToggle"; -import { AdvancedItemConfig } from "./advanced-config/AdvancedItemConfig"; + import { notNullish } from "@/utils/genUtils"; import { getFeature, getFeatureUsageType, } from "@/utils/product/entitlementUtils"; -import { useProductContext } from "../../ProductContext"; +import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery"; export const FeatureConfig = () => { - const { features } = useProductContext(); + const { features } = useFeaturesQuery(); const { item, setItem } = useProductItemContext(); if (!item.feature_id) return null; diff --git a/vite/src/views/products/product/product-item/product-item-config/PriceItemConfig.tsx b/vite/src/views/products/product/product-item/product-item-config/PriceItemConfig.tsx index 903b32310..e29120d11 100644 --- a/vite/src/views/products/product/product-item/product-item-config/PriceItemConfig.tsx +++ b/vite/src/views/products/product/product-item/product-item-config/PriceItemConfig.tsx @@ -14,10 +14,6 @@ import { nullish } from "@/utils/genUtils"; export const PriceItemConfig = () => { const { item, setItem, isUpdate } = useProductItemContext(); - // useEffect(() => { - // console.log("Item:", item); - // }, [item]); - return (
diff --git a/vite/src/views/products/product/product-item/product-item-config/advanced-config/AdvancedItemConfig.tsx b/vite/src/views/products/product/product-item/product-item-config/advanced-config/AdvancedItemConfig.tsx index 12ef3e03f..53095aaea 100644 --- a/vite/src/views/products/product/product-item/product-item-config/advanced-config/AdvancedItemConfig.tsx +++ b/vite/src/views/products/product/product-item/product-item-config/advanced-config/AdvancedItemConfig.tsx @@ -15,13 +15,12 @@ import { Input } from "@/components/ui/input"; import { RolloverConfigView } from "./RolloverConfig"; import { notNullish } from "@/utils/genUtils"; +import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery"; export const AdvancedItemConfig = () => { - const { features } = useProductContext(); + const { features } = useFeaturesQuery(); const { item, setItem } = useProductItemContext(); - const [isOpen, setIsOpen] = useState(item.usage_limit != null); - const showProrationConfig = shouldShowProrationConfig({ item, features }); const usageType = getFeatureUsageType({ item, features }); diff --git a/vite/src/views/products/product/product-item/product-item-config/components/SelectFeature.tsx b/vite/src/views/products/product/product-item/product-item-config/components/SelectFeature.tsx index 806e8c996..ed7c8d36b 100644 --- a/vite/src/views/products/product/product-item/product-item-config/components/SelectFeature.tsx +++ b/vite/src/views/products/product/product-item/product-item-config/components/SelectFeature.tsx @@ -11,6 +11,7 @@ import { FeatureTypeBadge } from "@/views/products/features/components/FeatureTy import { Button } from "@/components/ui/button"; import { ArrowLeft, Plus } from "lucide-react"; import { CreateItemStep } from "../../utils/CreateItemStep"; +import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery"; export const SelectFeatureStep = ({ popStep, @@ -19,7 +20,7 @@ export const SelectFeatureStep = ({ popStep: () => void; pushStep: (step: CreateItemStep) => void; }) => { - const { features } = useProductContext(); + const { features } = useFeaturesQuery(); const { item } = useProductItemContext(); return ( diff --git a/vite/src/views/products/product/product-item/product-item-config/components/SelectResetCycle.tsx b/vite/src/views/products/product/product-item/product-item-config/components/SelectResetCycle.tsx index 884a6f92c..9edbd9d25 100644 --- a/vite/src/views/products/product/product-item/product-item-config/components/SelectResetCycle.tsx +++ b/vite/src/views/products/product/product-item/product-item-config/components/SelectResetCycle.tsx @@ -19,18 +19,9 @@ import FieldLabel from "@/components/general/modal-components/FieldLabel"; import { cn } from "@/lib/utils"; import { InfoTooltip } from "@/components/general/modal-components/InfoTooltip"; import { getFeatureUsageType } from "@/utils/product/entitlementUtils"; -import { useProductContext } from "../../../ProductContext"; -import { Button } from "@/components/ui/button"; -import { ArrowUp01, CheckIcon, PlusIcon } from "lucide-react"; import { useState } from "react"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover"; -import { ToggleDisplayButton } from "@/components/general/ToggleDisplayButton"; -import { Input } from "@/components/ui/input"; import { CustomiseIntervalPopover } from "./CusomiseIntervalPopover"; +import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery"; const getIntervalText = ({ interval, @@ -57,7 +48,7 @@ const getIntervalText = ({ }; export const SelectResetCycle = () => { - const { features } = useProductContext(); + const { features } = useFeaturesQuery(); const { item, setItem } = useProductItemContext(); const handleChange = (value: EntInterval) => { diff --git a/vite/src/views/products/product/product-item/product-item-config/components/feature-price/BillingUnits.tsx b/vite/src/views/products/product/product-item/product-item-config/components/feature-price/BillingUnits.tsx index 8f62e60f9..a18fcc5ce 100644 --- a/vite/src/views/products/product/product-item/product-item-config/components/feature-price/BillingUnits.tsx +++ b/vite/src/views/products/product/product-item/product-item-config/components/feature-price/BillingUnits.tsx @@ -11,6 +11,7 @@ import { PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; +import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery"; export const BillingUnits = ({ className, @@ -19,7 +20,7 @@ export const BillingUnits = ({ className?: string; disabled: boolean; }) => { - const { features } = useProductContext(); + const { features } = useFeaturesQuery(); const { item, setItem } = useProductItemContext(); const [popoverOpen, setPopoverOpen] = useState(false); const triggerRef = useRef(null); diff --git a/vite/src/views/products/product/product-item/product-item-config/components/feature-price/FeaturePrice.tsx b/vite/src/views/products/product/product-item/product-item-config/components/feature-price/FeaturePrice.tsx index 52eeae82e..7f3519b07 100644 --- a/vite/src/views/products/product/product-item/product-item-config/components/feature-price/FeaturePrice.tsx +++ b/vite/src/views/products/product/product-item/product-item-config/components/feature-price/FeaturePrice.tsx @@ -16,11 +16,8 @@ import { Input } from "@/components/ui/input"; import { useOrg } from "@/hooks/common/useOrg"; export default function FeaturePrice() { - const { features } = useProductContext(); const { item, setItem } = useProductItemContext(); - const feature = features.find((f: Feature) => f.id == item.feature_id); - const setUsageTier = (index: number, key: string, value: string | number) => { const newUsageTiers = [...item.tiers]; newUsageTiers[index] = { ...newUsageTiers[index], [key]: value }; diff --git a/vite/src/views/products/product/product-item/product-item-config/item-config-footer/AddFeatureButton.tsx b/vite/src/views/products/product/product-item/product-item-config/item-config-footer/AddFeatureButton.tsx deleted file mode 100644 index fb2ffa824..000000000 --- a/vite/src/views/products/product/product-item/product-item-config/item-config-footer/AddFeatureButton.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { Button } from "@/components/ui/button"; -import { cn } from "@/lib/utils"; -import { useProductItemContext } from "../../ProductItemContext"; -import { isPriceItem } from "@/utils/product/getItemType"; -import { Infinite } from "@autumn/shared"; -import { PlusIcon } from "lucide-react"; - -export const AddFeatureButton = () => { - // Show add feature button if current item is fixedPrice - const { item, setItem, isUpdate, show, setShow } = useProductItemContext(); - - const showAddFeature = isPriceItem(item); - - if (!showAddFeature || isUpdate) return null; - - return ( - - ); -}; diff --git a/vite/src/views/products/product/product-item/product-item-config/item-config-footer/AddToEntityDropdown.tsx b/vite/src/views/products/product/product-item/product-item-config/item-config-footer/AddToEntityDropdown.tsx index f7f4caf02..d99bf1de7 100644 --- a/vite/src/views/products/product/product-item/product-item-config/item-config-footer/AddToEntityDropdown.tsx +++ b/vite/src/views/products/product/product-item/product-item-config/item-config-footer/AddToEntityDropdown.tsx @@ -1,10 +1,3 @@ -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; import { useProductContext } from "../../../ProductContext"; import { useProductItemContext } from "../../ProductItemContext"; import { Button } from "@/components/ui/button"; diff --git a/vite/src/views/products/product/product-item/product-item-config/item-config-footer/ItemConfigFooter.tsx b/vite/src/views/products/product/product-item/product-item-config/item-config-footer/ItemConfigFooter.tsx index 0b81f712b..90dd307f4 100644 --- a/vite/src/views/products/product/product-item/product-item-config/item-config-footer/ItemConfigFooter.tsx +++ b/vite/src/views/products/product/product-item/product-item-config/item-config-footer/ItemConfigFooter.tsx @@ -30,8 +30,6 @@ export const ItemConfigFooter = ({ const isEmpty = isEmptyItem(item); - const showIntro = product.items.length === 0; - return ( {handleBack ? ( diff --git a/vite/src/views/products/product/versioning/CountAndMigrate.tsx b/vite/src/views/products/product/versioning/CountAndMigrate.tsx index e554442ef..bac558850 100644 --- a/vite/src/views/products/product/versioning/CountAndMigrate.tsx +++ b/vite/src/views/products/product/versioning/CountAndMigrate.tsx @@ -27,8 +27,7 @@ export const CountAndMigrate = () => { mutateCount, } = useProductContext(); - const env = useEnv(); - const axiosInstance = useAxiosInstance({ env }); + const axiosInstance = useAxiosInstance(); const [loading, setLoading] = useState(false); const [confirmMigrateOpen, setConfirmMigrateOpen] = useState(false);