From 97b78e2413d46560b105e50d858989880b2d95cb Mon Sep 17 00:00:00 2001 From: Ayush Rodrigues Date: Fri, 16 Jan 2026 17:25:01 +0000 Subject: [PATCH 01/34] clean ups and initial splitting --- .../pricingAgent/pricingAgentRouter.ts | 38 ++- vite/src/app/layout.tsx | 44 ++-- .../general/table/table-content.tsx | 4 +- vite/src/components/v2/badges/SectionTag.tsx | 19 ++ .../components/v2/checkboxes/AreaCheckbox.tsx | 69 +++--- vite/src/components/v2/sheets/Sheet.tsx | 32 ++- vite/src/components/v2/tooltips/Tooltip.tsx | 4 +- vite/src/contexts/PortalContainerContext.tsx | 5 + .../CustomerProductsTable.tsx | 234 +++++------------- .../customerProductsTableFilters.ts | 53 +++- .../CustomerPurchasesTable.tsx | 130 ++++++++++ .../customers2/customer/CustomerView2.tsx | 2 + .../hooks/useCustomerProductsData.ts | 97 ++++++++ .../plan/components/EditPlanHeader.tsx | 6 +- .../edit-plan-details/AdditionalOptions.tsx | 31 ++- .../product-list/ProductListTable.tsx | 212 +++++++++------- .../ProductCountsTooltip.tsx | 1 + 17 files changed, 641 insertions(+), 340 deletions(-) create mode 100644 vite/src/components/v2/badges/SectionTag.tsx create mode 100644 vite/src/contexts/PortalContainerContext.tsx create mode 100644 vite/src/views/customers2/components/table/customer-purchases/CustomerPurchasesTable.tsx create mode 100644 vite/src/views/customers2/hooks/useCustomerProductsData.ts diff --git a/server/src/internal/pricingAgent/pricingAgentRouter.ts b/server/src/internal/pricingAgent/pricingAgentRouter.ts index ff8e341d8..410fe1063 100644 --- a/server/src/internal/pricingAgent/pricingAgentRouter.ts +++ b/server/src/internal/pricingAgent/pricingAgentRouter.ts @@ -53,7 +53,7 @@ const FeatureSchema = z .describe( "Unique ID for the feature (lowercase, underscores, no spaces)", ), - name: z.string().nullish().describe("Display name for the feature"), + name: z.string().describe("Display name for the feature"), type: ApiFeatureType.describe( "Type: single_use for consumables, continuous_use for allocated resources, boolean for on/off", ), @@ -96,6 +96,14 @@ const FeatureSchema = z }, ); +const PriceTierSchema = z.object({ + to: z + .number() + .or(z.literal("inf")) + .describe("The upper limit of this tier (use 'inf' for unlimited)"), + amount: z.number().describe("The price per unit for this tier"), +}); + const ProductItemSchema = z.object({ feature_id: z .string() @@ -117,6 +125,12 @@ const ProductItemSchema = z.object({ .describe( "Price amount. When feature_id is null, this is a standalone flat fee. When feature_id is set with usage_model, this is the per-unit price.", ), + tiers: z + .array(PriceTierSchema) + .nullish() + .describe( + "Tiered pricing structure. Use instead of price for volume-based pricing. Each tier defines upper limit (to) and price per unit (amount).", + ), usage_model: UsageModel.nullish().describe( "prepaid or pay_per_use. Required when pricing per unit of usage.", ), @@ -232,9 +246,10 @@ export type PricingConfig = z.infer; // ============ SYSTEM PROMPT ============ const SYSTEM_PROMPT = `You are a helpful pricing configuration assistant for Autumn, a billing and entitlements platform. -Your job is to help users design their pricing model through natural conversation. You should: +Your job is to help users set up their pricing model through natural conversation. You should: 1. Ask clarifying questions to understand their needs 2. Generate and update the pricing configuration as you learn more +3. Read through these instructions carefully for every single message, and follow them exactly. **IMPORTANT**: Call the build_pricing tool EVERY time the user provides any information at all about their pricing, features, or products. This updates the live preview they see. Even partial information should trigger a tool call with your best interpretation. @@ -245,6 +260,8 @@ Your job is to help users design their pricing model through natural conversatio - **boolean**: On/off features (advanced analytics, priority support, SSO) - **credit_system**: A unified credit pool that maps to multiple single_use features + + ## Item Types Products contain an array of items. There are THREE distinct item patterns: @@ -264,7 +281,11 @@ Products contain an array of items. There are THREE distinct item patterns: \`{ feature_id: "credits", price: 10, usage_model: "prepaid", billing_units: 10000 }\` → Customer pays $10 once to receive 10,000 credits -5. **Per-Unit Pricing Structure**: +5. **Tiered Pricing**: + \`{ feature_id: "api_calls", included_usage: 1000, tiers: [{ to: 5000, amount: 0.02 }, { to: "inf", amount: 0.01 }], usage_model: "pay_per_use", interval: "month" }\` + → Customer gets 1,000 API calls free, then pays $0.02/call up to 5,000, then $0.01/call after that. + +6. **Per-Unit Pricing Structure**: For any "per-X" pricing (like "$Y per seat", "$Y per project", "$Y per website"), ALWAYS use this pattern: - Base subscription fee: \`{ feature_id: null, price: 10, interval: "month" }\` - Unit allocation: \`{ feature_id: "seats", included_usage: 1, price: 10, usage_model: "pay_per_use", billing_units: 1 }\` @@ -273,10 +294,13 @@ This creates: $Y/month base price that includes 1 unit, then $Y per additional u + ## Guidelines when building the config - Refer to the Item Types section above to see examples of how to build the config. +- **Features vs Items**: Features define WHAT can be tracked (e.g., "credits"). Items define HOW that feature is granted in a product (recurring, one-time, free, paid). NEVER create duplicate features for the same underlying resource. For example, "monthly tokens" and "one-time tokens" should be the SAME feature ("tokens"), referenced by different items and intervals. + - If you identify more than 3 features from user input, build the 3 most important (prioritizing metered features) and ask the user to confirm if they want to add more. Inform them clearly that you kept it simple to start with, but they can add more later. - Product and Feature IDs should be lowercase with underscores (e.g., "pro_plan", "chat_messages") @@ -288,15 +312,17 @@ This creates: $Y/month base price that includes 1 unit, then $Y per additional u - For annual variants of plans, create another separate plan but with the annual price interval. Name it - Annual + + ## Guidelines when responding to the user - Do NOT tell the user what pricing you have built or describe the pricing in any way, as it is a waste to read (they can see it on the right). - If the user asks about changing currency, let them know they can do so in the Autumn dashboard, under Developer > Stripe. -- If the user has a price for a feature, clarify whether it should be a usage-based (pay_per_use) or prepaid (prepaid) pricing +- If the user has a price for a feature, ask them whether it should be a usage-based (pay_per_use) or prepaid (prepaid) pricing -- If you don't know something, DO NOT make up information or assume anything. They can reach us on discord here: https://discord.gg/atmn (we're very responsive) +- If the user asks about pricing or functionality that you are not sure whether is possible, DO NOT make up information or assume it can be done. They can reach us on discord here: https://discord.gg/atmn (we're very responsive) - Keep responses very concise and friendly.`; @@ -319,7 +345,7 @@ pricingAgentRouter.post("/chat", async (c) => { const anthropicClient = createAnthropic({ apiKey: process.env.ANTHROPIC_API_KEY, }); - const baseModel = anthropicClient("claude-sonnet-4-20250514"); + const baseModel = anthropicClient("claude-opus-4-5"); const posthog = getPostHogClient(); const distinctId = ctx.userId || ctx.org?.id || "anonymous"; diff --git a/vite/src/app/layout.tsx b/vite/src/app/layout.tsx index d8f39bbf1..830eb389b 100644 --- a/vite/src/app/layout.tsx +++ b/vite/src/app/layout.tsx @@ -2,10 +2,11 @@ import { AppEnv } from "@autumn/shared"; import { ArrowRightIcon } from "@phosphor-icons/react"; import { AutumnProvider } from "autumn-js/react"; import { NuqsAdapter } from "nuqs/adapters/react-router/v7"; -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { Outlet, useNavigate } from "react-router"; import { CustomToaster } from "@/components/general/CustomToaster"; import { IconButton } from "@/components/v2/buttons/IconButton"; +import { PortalContainerContext } from "@/contexts/PortalContainerContext"; import { useAutumnFlags } from "@/hooks/common/useAutumnFlags"; import { useGlobalErrorHandler } from "@/hooks/common/useGlobalErrorHandler"; import { useOrg } from "@/hooks/common/useOrg"; @@ -28,6 +29,7 @@ export function MainLayout() { const { data, isPending } = useSession(); const { org, isLoading: orgLoading } = useOrg(); const { handleApiError } = useGlobalErrorHandler(); + const containerRef = useRef(null); const navigate = useNavigate(); @@ -94,20 +96,26 @@ export function MainLayout() { includeCredentials={true} > -
- - - - - {/* */} - -
+ +
+ + + + + {/* */} + +
+
); } -const MainContent = () => { +const MainContent = ({ + containerRef, +}: { + containerRef: React.RefObject; +}) => { const env = useEnv(); const { org } = useOrg(); const [showDeployDialog, setShowDeployDialog] = useState(false); @@ -127,7 +135,10 @@ const MainContent = () => { "font-normal", )} > -
+
{env === AppEnv.Sandbox && (

You're in sandbox

@@ -157,17 +168,6 @@ const MainContent = () => {
- {/*
-
-

- Autumn is coming to mobile soon -

-

- We're currently designed for larger screens. Come back on - your desktop? -

-
-
*/}
diff --git a/vite/src/components/general/table/table-content.tsx b/vite/src/components/general/table/table-content.tsx index 80fc12417..8e1e57c0d 100644 --- a/vite/src/components/general/table/table-content.tsx +++ b/vite/src/components/general/table/table-content.tsx @@ -19,9 +19,9 @@ export function TableContent({ return (
diff --git a/vite/src/components/v2/badges/SectionTag.tsx b/vite/src/components/v2/badges/SectionTag.tsx new file mode 100644 index 000000000..0e75d8872 --- /dev/null +++ b/vite/src/components/v2/badges/SectionTag.tsx @@ -0,0 +1,19 @@ +import { cn } from "@/lib/utils"; + +interface SectionTagProps { + children: React.ReactNode; + className?: string; +} + +export const SectionTag = ({ children, className }: SectionTagProps) => { + return ( +
+ {children} +
+ ); +}; diff --git a/vite/src/components/v2/checkboxes/AreaCheckbox.tsx b/vite/src/components/v2/checkboxes/AreaCheckbox.tsx index 8fdc8b9c6..0e74e288b 100644 --- a/vite/src/components/v2/checkboxes/AreaCheckbox.tsx +++ b/vite/src/components/v2/checkboxes/AreaCheckbox.tsx @@ -2,6 +2,11 @@ "use client"; import React from "react"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/components/v2/tooltips/Tooltip"; import { cn } from "@/lib/utils"; import { Checkbox } from "./Checkbox"; @@ -12,18 +17,18 @@ interface AreaCheckboxProps { title: string; tooltip?: string; disabled?: boolean; + disabledReason?: string; hide?: boolean; description?: string; children?: React.ReactNode; } function AreaCheckbox({ - className, checked = false, onCheckedChange, title, - tooltip, disabled = false, + disabledReason, hide = false, description, children, @@ -31,29 +36,31 @@ function AreaCheckbox({ const id = React.useId(); if (hide) return null; - return ( -
+ + const isDisabled = disabled || !!disabledReason; + + const content = ( +
{ - if (!disabled && onCheckedChange) { + if (!isDisabled && onCheckedChange) { onCheckedChange(checked as boolean); } }} - disabled={disabled} + disabled={isDisabled} size="sm" className="mt-[3px]" /> -
+
); + + if (disabledReason) { + return ( + + +
{content}
+
+ + {disabledReason} + +
+ ); + } + + return content; } export { AreaCheckbox }; - -// {tooltip && ( -// -// -// -// -// -// -//

{tooltip}

-//
-//
-//
-// )} diff --git a/vite/src/components/v2/sheets/Sheet.tsx b/vite/src/components/v2/sheets/Sheet.tsx index a931fb4cf..c92088c0d 100644 --- a/vite/src/components/v2/sheets/Sheet.tsx +++ b/vite/src/components/v2/sheets/Sheet.tsx @@ -1,10 +1,13 @@ "use client"; +import { AppEnv } from "@autumn/shared"; import * as SheetPrimitive from "@radix-ui/react-dialog"; import { XIcon } from "lucide-react"; import type * as React from "react"; +import { usePortalContainer } from "@/contexts/PortalContainerContext"; import { cn } from "@/lib/utils"; +import { useEnv } from "@/utils/envUtils"; function Sheet({ ...props }: React.ComponentProps) { return ; @@ -25,7 +28,14 @@ function SheetClose({ function SheetPortal({ ...props }: React.ComponentProps) { - return ; + const containerRef = usePortalContainer(); + return ( + + ); } function SheetOverlay({ @@ -36,7 +46,7 @@ function SheetOverlay({ | null>(null); + +export const usePortalContainer = () => useContext(PortalContainerContext); diff --git a/vite/src/views/customers2/components/table/customer-products/CustomerProductsTable.tsx b/vite/src/views/customers2/components/table/customer-products/CustomerProductsTable.tsx index 324bc0446..50558683e 100644 --- a/vite/src/views/customers2/components/table/customer-products/CustomerProductsTable.tsx +++ b/vite/src/views/customers2/components/table/customer-products/CustomerProductsTable.tsx @@ -1,44 +1,36 @@ import { AppEnv, type Entity, type FullCusProduct } from "@autumn/shared"; -import { - ArrowSquareOutIcon, - PackageIcon, - Subtract, - User, -} from "@phosphor-icons/react"; +import { ArrowSquareOutIcon, PackageIcon } from "@phosphor-icons/react"; import type { Row } from "@tanstack/react-table"; -import { parseAsBoolean, useQueryState } from "nuqs"; import { useMemo, useState } from "react"; -import { useLocation, useNavigate } from "react-router"; -import { AdminHover } from "@/components/general/AdminHover"; import { Table } from "@/components/general/table"; import { Button } from "@/components/v2/buttons/Button"; import { IconButton } from "@/components/v2/buttons/IconButton"; import { useSheetStore } from "@/hooks/stores/useSheetStore"; import { useEntity } from "@/hooks/stores/useSubscriptionStore"; import { useEnv } from "@/utils/envUtils"; -import { getCusProductHoverTexts } from "@/views/admin/adminUtils"; -import { useCusQuery } from "@/views/customers/customer/hooks/useCusQuery"; import { useFullCusSearchQuery } from "@/views/customers/hooks/useFullCusSearchQuery"; import { useSavedViewsQuery } from "@/views/customers/hooks/useSavedViewsQuery"; +import { useCustomerProductsData } from "@/views/customers2/hooks/useCustomerProductsData"; import { useCustomerTable } from "@/views/customers2/hooks/useCustomerTable"; import { AttachProductSheetTrigger } from "./AttachProductSheetTrigger"; import { CancelProductDialog } from "./CancelProductDialog"; -import { CustomerProductPrice } from "./CustomerProductPrice"; import { CustomerProductsColumns } from "./CustomerProductsColumns"; -import { filterCustomerProductsByEntity } from "./customerProductsTableFilters"; import { ShowExpiredActionButton } from "./ShowExpiredActionButton"; import { TransferProductDialog } from "./TransferProductDialog"; export function CustomerProductsTable() { const env = useEnv(); - const { customer, isLoading } = useCusQuery(); - - const { entityId } = useEntity(); - const [showExpired, setShowExpired] = useQueryState( - "customerProductsShowExpired", - parseAsBoolean.withDefault(false), - ); + const { + customer, + isLoading, + showExpired, + setShowExpired, + subscriptions, + hasEntities, + purchases, + } = useCustomerProductsData(); + const { setEntityId } = useEntity(); const [cancelOpen, setCancelOpen] = useState(false); const [transferOpen, setTransferOpen] = useState(false); const [selectedProduct, setSelectedProduct] = useState( @@ -46,95 +38,44 @@ export function CustomerProductsTable() { ); const selectedItemId = useSheetStore((s) => s.itemId); - const { setEntityId } = useEntity(); - useSavedViewsQuery(); useFullCusSearchQuery(); - const { regularProducts, entityProducts } = useMemo( - () => - filterCustomerProductsByEntity({ - customer, - showExpired: showExpired ?? false, - }), - [customer, showExpired], - ); + // Build columns dynamically - include Scope column only when there are entity products + const columns = useMemo(() => { + const baseColumns = [ + CustomerProductsColumns[0], // Name + ]; - const displayedProducts = useMemo(() => { - // Always just show regular products, never combine with entity products - return regularProducts; - }, [regularProducts]); - - const displayedEntityProducts = useMemo(() => { - if (entityId) { - const selectedEntity = customer.entities.find( - (e: Entity) => e.id === entityId || e.internal_id === entityId, - ); - if (selectedEntity) { - return entityProducts.filter( - (product) => - product.internal_entity_id === selectedEntity.internal_id || - product.entity_id === selectedEntity.id, - ); - } - } - return entityProducts; - }, [entityProducts, entityId, customer.entities]); - - const attachedProductsTableColumns = useMemo( - () => CustomerProductsColumns, - [], - ); - - const navigate = useNavigate(); - const location = useLocation(); - - const entityProductsTableColumns = useMemo( - () => [ - { - header: "Plan", - accessorKey: "plan", - cell: ({ row }: { row: Row }) => { - const quantity = row.original.quantity; - const showQuantity = quantity && quantity > 1; - - return ( -
- - {row.original.product.name} - - {showQuantity && ( -
- {quantity} -
- )} -
- ); - }, - }, - { - header: "Entity", - accessorKey: "entity", + // Add Scope column if there are entity products + if (subscriptions.hasEntityProducts) { + baseColumns.push({ + header: "Scope", + accessorKey: "scope", cell: ({ row }: { row: Row }) => { const product = row.original; + + // If no entity, it's customer-level + if (!product.internal_entity_id && !product.entity_id) { + return Customer; + } + + // Find the entity const entity = customer.entities.find( (e: Entity) => e.internal_id === product.internal_entity_id || e.id === product.entity_id, ); - const handleEntityClick = (e: React.MouseEvent) => { - e.stopPropagation(); - if (!entity) return; - setEntityId(entity.id || entity.internal_id); - }; - if (!entity) return ; return ( ); }, - }, + }); + } - { - header: "Price", - accessorKey: "price", - cell: ({ row }: { row: Row }) => { - return ; - }, - }, + // Add remaining columns + baseColumns.push( + CustomerProductsColumns[1], // Price CustomerProductsColumns[2], // Status CustomerProductsColumns[3], // Created At CustomerProductsColumns[4], // Actions - ], - [customer.entities, location.pathname, location.search, navigate], - ); + ); + + return baseColumns; + }, [subscriptions.hasEntityProducts, customer.entities, setEntityId]); const setSheet = useSheetStore((s) => s.setSheet); @@ -178,42 +117,28 @@ export function CustomerProductsTable() { }); }; - const hasEntities = customer.entities.length > 0; + const tableMeta = { + onCancelClick: handleCancelClick, + onTransferClick: handleTransferClick, + hasEntities, + }; - const enableSorting = false; const table = useCustomerTable({ - data: displayedProducts, - columns: attachedProductsTableColumns, + data: subscriptions.all, + columns, options: { globalFilterFn: "includesString", enableGlobalFilter: true, - meta: { - onCancelClick: handleCancelClick, - onTransferClick: handleTransferClick, - hasEntities, - }, + meta: tableMeta, }, }); - const entityTable = useCustomerTable({ - data: displayedEntityProducts, // Changed from entityProducts to displayedEntityProducts - columns: entityProductsTableColumns, - options: { - globalFilterFn: "includesString", - enableGlobalFilter: true, - meta: { - onCancelClick: handleCancelClick, - onTransferClick: handleTransferClick, - hasEntities, - }, - }, - }); - - const hasEntityProducts = entityProducts.length > 0; // Removed && !entityId + // Check if there are any purchases (for empty state messaging) + const hasPurchases = purchases.all.length > 0; const emptyStateChildren = - entityProducts.length > 0 ? ( - "No customer-level plans found" + subscriptions.hasEntityProducts || hasPurchases ? ( + "No subscriptions found" ) : ( <> Enable a plan to start a subscription @@ -256,13 +181,13 @@ export function CustomerProductsTable() { @@ -279,57 +204,12 @@ export function CustomerProductsTable() { - {hasEntityProducts && ( -
- - Customer Plans -
- )} - {/* {hasProducts ? ( */} - {/* ) : ( - !isLoading && ( - 0 - ? "No customer-level plans." - : "Enable a plan to start a subscription" - } - /> - ) - )} */}
- {hasEntityProducts && ( -
-
- - Entity Plans -
- - - - - - - - -
- )}
); } diff --git a/vite/src/views/customers2/components/table/customer-products/customerProductsTableFilters.ts b/vite/src/views/customers2/components/table/customer-products/customerProductsTableFilters.ts index 9cb447bc0..b7b43e7f6 100644 --- a/vite/src/views/customers2/components/table/customer-products/customerProductsTableFilters.ts +++ b/vite/src/views/customers2/components/table/customer-products/customerProductsTableFilters.ts @@ -1,6 +1,21 @@ -import type { FullCusProduct, FullCustomer } from "@autumn/shared"; +import { + cusProductToProduct, + type FullCusProduct, + type FullCustomer, + isOneOffProductV2, + mapToProductV2, +} from "@autumn/shared"; import { filterByExpiredStatus } from "@/views/customers2/utils/tableFilterUtils"; +/** + * Determines if a customer product is a one-off purchase using existing utilities + */ +export function isOneOffCusProduct(cusProduct: FullCusProduct): boolean { + const fullProduct = cusProductToProduct({ cusProduct }); + const productV2 = mapToProductV2({ product: fullProduct }); + return isOneOffProductV2({ items: productV2.items }); +} + export function filterCustomerProducts({ customer, showExpired, @@ -38,3 +53,39 @@ export function filterCustomerProductsByEntity({ return { regularProducts, entityProducts }; } + +/** + * Filters and splits customer products by subscription type AND entity level + */ +export function filterCustomerProductsByType({ + customer, + showExpired, +}: { + customer: FullCustomer; + showExpired: boolean; +}): { + subscriptions: { + customerLevel: FullCusProduct[]; + entityLevel: FullCusProduct[]; + }; + purchases: { + customerLevel: FullCusProduct[]; + entityLevel: FullCusProduct[]; + }; +} { + const { regularProducts, entityProducts } = filterCustomerProductsByEntity({ + customer, + showExpired, + }); + + return { + subscriptions: { + customerLevel: regularProducts.filter((p) => !isOneOffCusProduct(p)), + entityLevel: entityProducts.filter((p) => !isOneOffCusProduct(p)), + }, + purchases: { + customerLevel: regularProducts.filter((p) => isOneOffCusProduct(p)), + entityLevel: entityProducts.filter((p) => isOneOffCusProduct(p)), + }, + }; +} diff --git a/vite/src/views/customers2/components/table/customer-purchases/CustomerPurchasesTable.tsx b/vite/src/views/customers2/components/table/customer-purchases/CustomerPurchasesTable.tsx new file mode 100644 index 000000000..4c6673d3f --- /dev/null +++ b/vite/src/views/customers2/components/table/customer-purchases/CustomerPurchasesTable.tsx @@ -0,0 +1,130 @@ +import type { Entity, FullCusProduct } from "@autumn/shared"; +import { ShoppingBagIcon } from "@phosphor-icons/react"; +import type { Row } from "@tanstack/react-table"; +import { useMemo } from "react"; +import { Table } from "@/components/general/table"; +import { Button } from "@/components/v2/buttons/Button"; +import { useSheetStore } from "@/hooks/stores/useSheetStore"; +import { useEntity } from "@/hooks/stores/useSubscriptionStore"; +import { useCustomerProductsData } from "@/views/customers2/hooks/useCustomerProductsData"; +import { useCustomerTable } from "@/views/customers2/hooks/useCustomerTable"; +import { CustomerProductsColumns } from "../customer-products/CustomerProductsColumns"; + +export function CustomerPurchasesTable() { + const { customer, isLoading, purchases, hasEntities } = + useCustomerProductsData(); + const { setEntityId } = useEntity(); + const selectedItemId = useSheetStore((s) => s.itemId); + const setSheet = useSheetStore((s) => s.setSheet); + + // Build columns dynamically - include Scope column only when there are entity products + const columns = useMemo(() => { + const baseColumns = [ + CustomerProductsColumns[0], // Name + ]; + + // Add Scope column if there are entity products + if (purchases.hasEntityProducts) { + baseColumns.push({ + header: "Scope", + accessorKey: "scope", + cell: ({ row }: { row: Row }) => { + const product = row.original; + + // If no entity, it's customer-level + if (!product.internal_entity_id && !product.entity_id) { + return Customer; + } + + // Find the entity + const entity = customer.entities.find( + (e: Entity) => + e.internal_id === product.internal_entity_id || + e.id === product.entity_id, + ); + + if (!entity) return ; + + return ( + + ); + }, + }); + } + + // Add remaining columns (no Actions column for purchases) + baseColumns.push( + CustomerProductsColumns[1], // Price + CustomerProductsColumns[2], // Status + CustomerProductsColumns[3], // Created At + ); + + return baseColumns; + }, [purchases.hasEntityProducts, customer.entities, setEntityId]); + + const table = useCustomerTable({ + data: purchases.all, + columns, + options: { + globalFilterFn: "includesString", + enableGlobalFilter: true, + meta: { hasEntities }, + }, + }); + + const handleRowClick = (cusProduct: FullCusProduct) => { + setSheet({ + type: "subscription-detail", + itemId: cusProduct.id, + }); + }; + + const hasPurchases = purchases.all.length > 0; + + if (!hasPurchases) return null; + + return ( +
+ + + + + + Purchases + + + + + + + + +
+ ); +} diff --git a/vite/src/views/customers2/customer/CustomerView2.tsx b/vite/src/views/customers2/customer/CustomerView2.tsx index 08fc81cf9..ad389908b 100644 --- a/vite/src/views/customers2/customer/CustomerView2.tsx +++ b/vite/src/views/customers2/customer/CustomerView2.tsx @@ -14,6 +14,7 @@ import { useCusReferralQuery } from "../../customers/customer/hooks/useCusReferr import { CustomerFeatureUsageTable } from "../components/table/customer-feature-usage/CustomerFeatureUsageTable"; import { CustomerInvoicesTable } from "../components/table/customer-invoices/CustomerInvoicesTable"; import { CustomerProductsTable } from "../components/table/customer-products/CustomerProductsTable"; +import { CustomerPurchasesTable } from "../components/table/customer-purchases/CustomerPurchasesTable"; import { CustomerUsageAnalyticsTable } from "../components/table/customer-usage-analytics/CustomerUsageAnalyticsTable"; import { CustomerBreadcrumbs } from "./CustomerBreadcrumbs2"; import { CustomerContext } from "./CustomerContext"; @@ -93,6 +94,7 @@ export default function CustomerView2() { {/* */}
+ {/* */} {/* */} diff --git a/vite/src/views/customers2/hooks/useCustomerProductsData.ts b/vite/src/views/customers2/hooks/useCustomerProductsData.ts new file mode 100644 index 000000000..57248fd62 --- /dev/null +++ b/vite/src/views/customers2/hooks/useCustomerProductsData.ts @@ -0,0 +1,97 @@ +import type { Entity, FullCusProduct } from "@autumn/shared"; +import { parseAsBoolean, useQueryState } from "nuqs"; +import { useMemo } from "react"; +import { useEntity } from "@/hooks/stores/useSubscriptionStore"; +import { useCusQuery } from "@/views/customers/customer/hooks/useCusQuery"; +import { filterCustomerProductsByType } from "../components/table/customer-products/customerProductsTableFilters"; + +function filterBySelectedEntity({ + products, + entityId, + entities, +}: { + products: FullCusProduct[]; + entityId: string | null; + entities: Entity[]; +}): FullCusProduct[] { + if (!entityId) return products; + + const selectedEntity = entities.find( + (e: Entity) => e.id === entityId || e.internal_id === entityId, + ); + + if (!selectedEntity) return products; + + return products.filter( + (product) => + product.internal_entity_id === selectedEntity.internal_id || + product.entity_id === selectedEntity.id, + ); +} + +export function useCustomerProductsData() { + const { customer, isLoading } = useCusQuery(); + const { entityId } = useEntity(); + const [showExpired, setShowExpired] = useQueryState( + "customerProductsShowExpired", + parseAsBoolean.withDefault(false), + ); + + const { subscriptions, purchases } = useMemo( + () => + filterCustomerProductsByType({ + customer, + showExpired: showExpired ?? false, + }), + [customer, showExpired], + ); + + // Filter entity-level products by selected entity (if any) + const filteredSubscriptionsEntityLevel = useMemo( + () => + filterBySelectedEntity({ + products: subscriptions.entityLevel, + entityId, + entities: customer.entities, + }), + [subscriptions.entityLevel, entityId, customer.entities], + ); + + const filteredPurchasesEntityLevel = useMemo( + () => + filterBySelectedEntity({ + products: purchases.entityLevel, + entityId, + entities: customer.entities, + }), + [purchases.entityLevel, entityId, customer.entities], + ); + + // Combine customer-level (always shown) + filtered entity-level products + const allSubscriptions = useMemo( + () => [...subscriptions.customerLevel, ...filteredSubscriptionsEntityLevel], + [subscriptions.customerLevel, filteredSubscriptionsEntityLevel], + ); + + const allPurchases = useMemo( + () => [...purchases.customerLevel, ...filteredPurchasesEntityLevel], + [purchases.customerLevel, filteredPurchasesEntityLevel], + ); + + return { + customer, + isLoading, + showExpired, + setShowExpired, + entityId, + hasEntities: customer.entities.length > 0, + subscriptions: { + all: allSubscriptions, + hasEntityProducts: subscriptions.entityLevel.length > 0, + }, + purchases: { + all: allPurchases, + hasEntityProducts: purchases.entityLevel.length > 0, + }, + }; +} diff --git a/vite/src/views/products/plan/components/EditPlanHeader.tsx b/vite/src/views/products/plan/components/EditPlanHeader.tsx index 8be8aca61..4d7a52010 100644 --- a/vite/src/views/products/plan/components/EditPlanHeader.tsx +++ b/vite/src/views/products/plan/components/EditPlanHeader.tsx @@ -151,11 +151,11 @@ export const EditPlanHeader = () => { )} - }> {counts?.active || 0} diff --git a/vite/src/views/products/plan/components/edit-plan-details/AdditionalOptions.tsx b/vite/src/views/products/plan/components/edit-plan-details/AdditionalOptions.tsx index 810e67b57..c82a0c99d 100644 --- a/vite/src/views/products/plan/components/edit-plan-details/AdditionalOptions.tsx +++ b/vite/src/views/products/plan/components/edit-plan-details/AdditionalOptions.tsx @@ -18,13 +18,17 @@ export const AdditionalOptions = ({ return (
- {((product.planType === "free" && !product.is_add_on) || + {(product.planType === "free" || product.free_trial?.card_required === false) && ( setProduct({ ...product, is_default: checked }) } @@ -32,7 +36,7 @@ export const AdditionalOptions = ({ )} @@ -45,6 +49,25 @@ export const AdditionalOptions = ({ > {notNullish(product.free_trial) && } + item.interval) + ? "You must add at least one recurring price before marking this plan as an add-on." + : undefined + } + onCheckedChange={(checked) => + setProduct({ + ...product, + is_add_on: checked, + is_default: checked ? false : product.is_default, + }) + } + />
); diff --git a/vite/src/views/products/products/components/product-list/ProductListTable.tsx b/vite/src/views/products/products/components/product-list/ProductListTable.tsx index 010ded2fa..5db73db62 100644 --- a/vite/src/views/products/products/components/product-list/ProductListTable.tsx +++ b/vite/src/views/products/products/components/product-list/ProductListTable.tsx @@ -1,13 +1,13 @@ -import type { ProductV2 } from "@autumn/shared"; +import { isOneOffProductV2, type ProductV2 } from "@autumn/shared"; import { CubeIcon } from "@phosphor-icons/react"; import { useMemo } from "react"; import { Table } from "@/components/general/table"; +import { SectionTag } from "@/components/v2/badges/SectionTag"; import { EmptyState } from "@/components/v2/empty-states/EmptyState"; import { useProductsQuery } from "@/hooks/queries/useProductsQuery"; import { pushPage } from "@/utils/genUtils"; import { useProductsQueryState } from "@/views/products/hooks/useProductsQueryState"; import { useProductTable } from "@/views/products/hooks/useProductTable"; -import { AddOnPlanCreateButton } from "./AddOnPlanCreateButton"; import { createProductListColumns } from "./ProductListColumns"; import { ProductListCreateButton } from "./ProductListCreateButton"; import { ProductListMenuButton } from "./ProductListMenuButton"; @@ -20,65 +20,72 @@ export function ProductListTable() { const { products, counts, isCountsLoading } = useProductsQuery(); const { queryStates } = useProductsQueryState(); - // Filter products based on archived state and add counts - const { basePlans, addOnPlans } = useMemo(() => { - const filtered = products?.filter((product) => - queryStates.showArchivedProducts ? product.archived : !product.archived, - ); + const { recurringBasePlans, recurringAddOnPlans, oneTimePlans } = + useMemo(() => { + const filtered = products?.filter((product) => + queryStates.showArchivedProducts ? product.archived : !product.archived, + ); - // Deduplicate by ID, keeping the latest version - const deduplicated = filtered?.reduce((acc, product) => { - const existingIndex = acc.findIndex((p) => p.id === product.id); + // Deduplicate by ID, keeping the latest version + const deduplicated = filtered?.reduce((acc, product) => { + const existingIndex = acc.findIndex((p) => p.id === product.id); - if (existingIndex === -1) { - acc.push(product); - } else { - const existing = acc[existingIndex]; - if (queryStates.showArchivedProducts) { - // If showing archived, always keep the newest version - if (product.version > existing.version) { - acc[existingIndex] = product; - } + if (existingIndex === -1) { + acc.push(product); } else { - // If not showing archived, prefer non-archived versions - if (product.archived && !existing.archived) { - // Keep existing non-archived version - } else if (!product.archived && existing.archived) { - // Replace archived with non-archived - acc[existingIndex] = product; - } else if (product.version > existing.version) { - // Both have same archived status, keep newer version - acc[existingIndex] = product; + const existing = acc[existingIndex]; + if (queryStates.showArchivedProducts) { + // If showing archived, always keep the newest version + if (product.version > existing.version) { + acc[existingIndex] = product; + } + } else { + // If not showing archived, prefer non-archived versions + if (product.archived && !existing.archived) { + // Keep existing non-archived version + } else if (!product.archived && existing.archived) { + // Replace archived with non-archived + acc[existingIndex] = product; + } else if (product.version > existing.version) { + // Both have same archived status, keep newer version + acc[existingIndex] = product; + } } } - } - return acc; - }, [] as ProductV2[]); + return acc; + }, [] as ProductV2[]); - // Add counts to products - const productsWithCounts = deduplicated?.map((product) => ({ - ...product, - active_count: counts[product.id]?.active || 0, - })) as ProductWithCounts[]; + // Add counts to products + const productsWithCounts = deduplicated?.map((product) => ({ + ...product, + active_count: counts[product.id]?.active || 0, + })) as ProductWithCounts[]; - // Split into base plans and add-on plans - const basePlans = productsWithCounts?.filter( - (product) => !product.is_add_on, - ); - const addOnPlans = productsWithCounts?.filter( - (product) => product.is_add_on, - ); + // Split by one-off vs recurring first + const oneTimePlans = + productsWithCounts?.filter((p) => + isOneOffProductV2({ items: p.items }), + ) || []; + const recurringPlans = + productsWithCounts?.filter( + (p) => !isOneOffProductV2({ items: p.items }), + ) || []; - return { basePlans, addOnPlans }; - }, [products, counts, queryStates.showArchivedProducts]); + // Then split recurring by add-on status + const recurringBasePlans = recurringPlans.filter((p) => !p.is_add_on); + const recurringAddOnPlans = recurringPlans.filter((p) => p.is_add_on); + + return { recurringBasePlans, recurringAddOnPlans, oneTimePlans }; + }, [products, counts, queryStates.showArchivedProducts]); // Check if any product has a group const hasAnyGroup = useMemo( () => - basePlans?.some((product) => Boolean(product.group?.trim())) || - addOnPlans?.some((product) => Boolean(product.group?.trim())), - [basePlans, addOnPlans], + recurringBasePlans?.some((product) => Boolean(product.group?.trim())) || + recurringAddOnPlans?.some((product) => Boolean(product.group?.trim())) || + oneTimePlans?.some((product) => Boolean(product.group?.trim())), + [recurringBasePlans, recurringAddOnPlans, oneTimePlans], ); const columns = useMemo( @@ -86,8 +93,8 @@ export function ProductListTable() { [hasAnyGroup], ); - const baseTable = useProductTable({ - data: basePlans || [], + const recurringBaseTable = useProductTable({ + data: recurringBasePlans || [], columns, options: { globalFilterFn: "includesString", @@ -96,8 +103,18 @@ export function ProductListTable() { }, }); - const addOnTable = useProductTable({ - data: addOnPlans || [], + const recurringAddOnTable = useProductTable({ + data: recurringAddOnPlans || [], + columns, + options: { + globalFilterFn: "includesString", + enableGlobalFilter: true, + enableSorting: true, + }, + }); + + const oneTimeTable = useProductTable({ + data: oneTimePlans || [], columns, options: { globalFilterFn: "includesString", @@ -111,22 +128,24 @@ export function ProductListTable() { const enableSorting = true; - const hasBaseRows = baseTable.getRowModel().rows.length > 0; - const hasAddOns = addOnPlans && addOnPlans.length > 0; + const hasRecurringBasePlans = recurringBasePlans.length > 0; + const hasRecurringAddOns = recurringAddOnPlans.length > 0; + const hasOneTimePlans = oneTimePlans.length > 0; // For archived view, always show table structure even if empty // For non-archived view, show EmptyState when no plans exist - const showTableStructure = queryStates.showArchivedProducts || hasBaseRows; + const showTableStructure = + queryStates.showArchivedProducts || hasRecurringBasePlans; return (
- {/* Base Plans Table */} {showTableStructure ? ( <> + {/* Recurring Plans Section */}
- Base Plans + Recurring Plans
@@ -151,47 +170,64 @@ export function ProductListTable() {
-
+ + {hasRecurringAddOns && Main Plans} + + + + + + + + {/* Recurring Add-ons (only shown if add-ons exist) */} + {hasRecurringAddOns && ( + + Add-ons - -
-
+ + )}
-
- - - - - Add-on Plans - - - - - -
+ + {/* One-Time Plans Section (only shown if one-time plans exist) */} + {hasOneTimePlans && ( +
+ + + + + One-Time Plans + + -
- -
+
+
+ )} ) : ( } /> diff --git a/vite/src/views/products/products/product-row-toolbar/ProductCountsTooltip.tsx b/vite/src/views/products/products/product-row-toolbar/ProductCountsTooltip.tsx index d7f4f83c0..3034228cf 100644 --- a/vite/src/views/products/products/product-row-toolbar/ProductCountsTooltip.tsx +++ b/vite/src/views/products/products/product-row-toolbar/ProductCountsTooltip.tsx @@ -19,6 +19,7 @@ export const ProductCountsTooltip = ({ product }: { product: ProductV2 }) => { const versionCounts = getVersionCounts(products); const handleClick = (e: React.MouseEvent) => { + e.preventDefault(); e.stopPropagation(); if (activeCount === 0) return; From 94b7be295d25cf329a78256326430b6156663314 Mon Sep 17 00:00:00 2001 From: Charlie Lamb Date: Fri, 16 Jan 2026 17:30:26 +0000 Subject: [PATCH 02/34] feat: add vite build github action --- .github/workflows/vite-build.yml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 .github/workflows/vite-build.yml diff --git a/.github/workflows/vite-build.yml b/.github/workflows/vite-build.yml new file mode 100644 index 000000000..feb577ae7 --- /dev/null +++ b/.github/workflows/vite-build.yml @@ -0,0 +1,29 @@ +name: Vite Build Check + +on: + push: + branches: + - main + - staging + - dev + pull_request: + +jobs: + vite-build: + name: Vite Build Check + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: 1.3.2 + + - name: Install dependencies + run: bun install + + - name: Build Vite + run: cd vite && bun run build From 9e2af9d1b6255879e423be6d486ea59cb8549bec Mon Sep 17 00:00:00 2001 From: Charlie Lamb Date: Fri, 16 Jan 2026 17:44:20 +0000 Subject: [PATCH 03/34] chore: fix build error --- vite/src/components/v2/LineItemsPreview.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vite/src/components/v2/LineItemsPreview.tsx b/vite/src/components/v2/LineItemsPreview.tsx index b71aa371f..d7ea76813 100644 --- a/vite/src/components/v2/LineItemsPreview.tsx +++ b/vite/src/components/v2/LineItemsPreview.tsx @@ -6,7 +6,7 @@ import { AccordionItem, AccordionTrigger, } from "@/components/ui/accordion"; -import { Badge } from "@/components/v2/badges/badge"; +import { Badge } from "@/components/v2/badges/Badge"; import { LoadingShimmerText } from "@/components/v2/LoadingShimmerText"; import { SheetSection } from "@/components/v2/sheets/SharedSheetComponents"; import { cn } from "@/lib/utils"; From 2e45ef613d5cd0a6154335e1651d521e3cec5de5 Mon Sep 17 00:00:00 2001 From: Charlie Lamb Date: Fri, 16 Jan 2026 17:49:39 +0000 Subject: [PATCH 04/34] chore: improve vite build action --- .github/workflows/vite-build.yml | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/.github/workflows/vite-build.yml b/.github/workflows/vite-build.yml index feb577ae7..8e440689b 100644 --- a/.github/workflows/vite-build.yml +++ b/.github/workflows/vite-build.yml @@ -9,8 +9,29 @@ on: pull_request: jobs: - vite-build: - name: Vite Build Check + typecheck: + name: Type Check + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: 1.3.2 + + - name: Install dependencies + run: bun install + + - name: Run TypeScript type check + run: cd vite && bunx tsc --noEmit + env: + NODE_OPTIONS: "--max-old-space-size=8192" + + build: + name: Build runs-on: ubuntu-latest steps: @@ -26,4 +47,4 @@ jobs: run: bun install - name: Build Vite - run: cd vite && bun run build + run: cd vite && bunx vite build From c4cef8f760e333cedea1fe698029ad64164fa627 Mon Sep 17 00:00:00 2001 From: Charlie Lamb Date: Fri, 16 Jan 2026 17:54:48 +0000 Subject: [PATCH 05/34] chore: fix trial colouring --- .../components/TrialEditorRow.tsx | 14 +++++++++----- .../general/form/fields/number-field.tsx | 4 +++- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/vite/src/components/forms/update-subscription-v2/components/TrialEditorRow.tsx b/vite/src/components/forms/update-subscription-v2/components/TrialEditorRow.tsx index e3e168a0a..2c8f96df9 100644 --- a/vite/src/components/forms/update-subscription-v2/components/TrialEditorRow.tsx +++ b/vite/src/components/forms/update-subscription-v2/components/TrialEditorRow.tsx @@ -156,9 +156,7 @@ export function TrialEditorRow({ Free Trial
@@ -210,6 +208,11 @@ export function TrialEditorRow({ setIsAddingNewTrial(true); }; + const isNewTrial = !isCurrentlyTrialing; + const editModeRingClass = isNewTrial + ? "ring-1 ring-inset ring-green-500/50" + : "ring-1 ring-inset ring-amber-500/50"; + return (
Free Trial
@@ -239,6 +242,7 @@ export function TrialEditorRow({ placeholder="7" min={1} className="w-16" + inputClassName="placeholder:opacity-50" hideFieldInfo /> )} diff --git a/vite/src/components/general/form/fields/number-field.tsx b/vite/src/components/general/form/fields/number-field.tsx index 1caf112c3..d735d5ba5 100644 --- a/vite/src/components/general/form/fields/number-field.tsx +++ b/vite/src/components/general/form/fields/number-field.tsx @@ -10,6 +10,7 @@ export function NumberField({ min, max, className, + inputClassName, hideFieldInfo, disabled, }: { @@ -18,6 +19,7 @@ export function NumberField({ min?: number; max?: number; className?: string; + inputClassName?: string; hideFieldInfo?: boolean; disabled?: boolean; }) { @@ -51,7 +53,7 @@ export function NumberField({ placeholder={placeholder} value={field.state.value ?? ""} onChange={handleChange} - className="text-sm" + className={cn("text-sm", inputClassName)} disabled={disabled} /> {!hideFieldInfo && } From 31145aed99fbe92e64b0b71881ade9039a9ac8e3 Mon Sep 17 00:00:00 2001 From: Charlie Lamb Date: Fri, 16 Jan 2026 17:58:51 +0000 Subject: [PATCH 06/34] chore: vite memory optimizations --- .github/workflows/vite-build.yml | 2 ++ vite/vite.config.ts | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/vite-build.yml b/.github/workflows/vite-build.yml index 8e440689b..e7daf3430 100644 --- a/.github/workflows/vite-build.yml +++ b/.github/workflows/vite-build.yml @@ -48,3 +48,5 @@ jobs: - name: Build Vite run: cd vite && bunx vite build + env: + NODE_OPTIONS: "--max-old-space-size=8192" diff --git a/vite/vite.config.ts b/vite/vite.config.ts index 4d857b400..5465900fc 100644 --- a/vite/vite.config.ts +++ b/vite/vite.config.ts @@ -89,6 +89,7 @@ export default defineConfig({ }, build: { - sourcemap: true, + // Disable sourcemaps in CI to reduce memory usage during build + sourcemap: !process.env.CI, }, }); From 104ed0febe52356be237f796db7474e4d623294f Mon Sep 17 00:00:00 2001 From: Charlie Lamb Date: Fri, 16 Jan 2026 18:02:05 +0000 Subject: [PATCH 07/34] chore: clean up node options test --- .github/workflows/vite-build.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/vite-build.yml b/.github/workflows/vite-build.yml index e7daf3430..9aaf84b6e 100644 --- a/.github/workflows/vite-build.yml +++ b/.github/workflows/vite-build.yml @@ -27,8 +27,6 @@ jobs: - name: Run TypeScript type check run: cd vite && bunx tsc --noEmit - env: - NODE_OPTIONS: "--max-old-space-size=8192" build: name: Build @@ -48,5 +46,3 @@ jobs: - name: Build Vite run: cd vite && bunx vite build - env: - NODE_OPTIONS: "--max-old-space-size=8192" From bd2cce93f0aabe3621cab347080a51736b3f7d0b Mon Sep 17 00:00:00 2001 From: Ayush Rodrigues Date: Fri, 16 Jan 2026 18:18:55 +0000 Subject: [PATCH 08/34] cleanup --- .../common/formatUtils/formatInterval.ts | 12 ++--- shared/utils/productDisplayUtils.ts | 25 ++++++----- .../CustomerPurchasesTable.tsx | 45 +++++++++++++++++-- 3 files changed, 63 insertions(+), 19 deletions(-) diff --git a/shared/utils/common/formatUtils/formatInterval.ts b/shared/utils/common/formatUtils/formatInterval.ts index 615936a0a..3572d99d3 100644 --- a/shared/utils/common/formatUtils/formatInterval.ts +++ b/shared/utils/common/formatUtils/formatInterval.ts @@ -15,11 +15,13 @@ export const formatInterval = ({ }): string => { if (!interval) return ""; - // Handle one_off and lifetime (no interval string) - if ( - interval === BillingInterval.OneOff || - interval === EntInterval.Lifetime - ) { + // Handle one_off (show "one time") + if (interval === BillingInterval.OneOff) { + return "one times"; + } + + // Handle lifetime (no interval string) + if (interval === EntInterval.Lifetime) { return ""; } diff --git a/shared/utils/productDisplayUtils.ts b/shared/utils/productDisplayUtils.ts index 3905426bc..6ecfbbaec 100644 --- a/shared/utils/productDisplayUtils.ts +++ b/shared/utils/productDisplayUtils.ts @@ -73,14 +73,17 @@ export const getFeatureItemDisplay = ({ ? "0 " : `${numberWithCommas(item.included_usage)} `; - const intervalStr = formatInterval({ - interval: item.interval ?? undefined, - intervalCount: item.interval_count ?? undefined, - }); + // If interval is null for a feature item, it's a one-time/lifetime feature + const intervalStr = item.interval + ? formatInterval({ + interval: item.interval, + intervalCount: item.interval_count ?? undefined, + }) + : "one-off"; return { primary_text: `${includedUsageTxt}${featureName}`, - secondary_text: fullDisplay && intervalStr ? intervalStr : undefined, + secondary_text: fullDisplay ? intervalStr : undefined, }; }; @@ -158,13 +161,15 @@ export const getFeaturePriceItemDisplay = ({ priceStr2 = `${billingFeatureName}`; } - // let intervalStr = isMainPrice && item.interval ? ` per ${item.interval}` : ""; + // If interval is null for a priced feature, it's a one-time purchase const intervalStr = isMainPrice || fullDisplay - ? formatInterval({ - interval: item.interval ?? undefined, - intervalCount: item.interval_count ?? undefined, - }) + ? item.interval + ? formatInterval({ + interval: item.interval, + intervalCount: item.interval_count ?? undefined, + }) + : "one-off" : ""; if (includedUsageStr) { diff --git a/vite/src/views/customers2/components/table/customer-purchases/CustomerPurchasesTable.tsx b/vite/src/views/customers2/components/table/customer-purchases/CustomerPurchasesTable.tsx index 4c6673d3f..7ff6107fa 100644 --- a/vite/src/views/customers2/components/table/customer-purchases/CustomerPurchasesTable.tsx +++ b/vite/src/views/customers2/components/table/customer-purchases/CustomerPurchasesTable.tsx @@ -1,14 +1,16 @@ import type { Entity, FullCusProduct } from "@autumn/shared"; import { ShoppingBagIcon } from "@phosphor-icons/react"; import type { Row } from "@tanstack/react-table"; -import { useMemo } from "react"; +import { useMemo, useState } from "react"; import { Table } from "@/components/general/table"; import { Button } from "@/components/v2/buttons/Button"; import { useSheetStore } from "@/hooks/stores/useSheetStore"; import { useEntity } from "@/hooks/stores/useSubscriptionStore"; import { useCustomerProductsData } from "@/views/customers2/hooks/useCustomerProductsData"; import { useCustomerTable } from "@/views/customers2/hooks/useCustomerTable"; +import { CancelProductDialog } from "../customer-products/CancelProductDialog"; import { CustomerProductsColumns } from "../customer-products/CustomerProductsColumns"; +import { TransferProductDialog } from "../customer-products/TransferProductDialog"; export function CustomerPurchasesTable() { const { customer, isLoading, purchases, hasEntities } = @@ -16,6 +18,11 @@ export function CustomerPurchasesTable() { const { setEntityId } = useEntity(); const selectedItemId = useSheetStore((s) => s.itemId); const setSheet = useSheetStore((s) => s.setSheet); + const [cancelOpen, setCancelOpen] = useState(false); + const [transferOpen, setTransferOpen] = useState(false); + const [selectedProduct, setSelectedProduct] = useState( + null, + ); // Build columns dynamically - include Scope column only when there are entity products const columns = useMemo(() => { @@ -63,23 +70,40 @@ export function CustomerPurchasesTable() { }); } - // Add remaining columns (no Actions column for purchases) + // Add remaining columns baseColumns.push( CustomerProductsColumns[1], // Price CustomerProductsColumns[2], // Status CustomerProductsColumns[3], // Created At + CustomerProductsColumns[4], // Actions (empty for purchases, but keeps layout consistent) ); return baseColumns; }, [purchases.hasEntityProducts, customer.entities, setEntityId]); + const handleCancelClick = (product: FullCusProduct) => { + setSelectedProduct(product); + setCancelOpen(true); + }; + + const handleTransferClick = (product: FullCusProduct) => { + setSelectedProduct(product); + setTransferOpen(true); + }; + + const tableMeta = { + onCancelClick: handleCancelClick, + onTransferClick: handleTransferClick, + hasEntities, + }; + const table = useCustomerTable({ data: purchases.all, columns, options: { globalFilterFn: "includesString", enableGlobalFilter: true, - meta: { hasEntities }, + meta: tableMeta, }, }); @@ -96,6 +120,20 @@ export function CustomerPurchasesTable() { return (
+ {selectedProduct && ( + <> + + + + )} - From f7ba0ac8bf5c733bfd034fc7c653cd805ed14f74 Mon Sep 17 00:00:00 2001 From: Charlie Lamb Date: Fri, 16 Jan 2026 18:29:41 +0000 Subject: [PATCH 09/34] chore: fix trial dropdown state --- .../generateTrialChanges.ts | 7 ++++++ .../components/EditPlanSection.tsx | 5 +--- .../components/SectionTitle.tsx | 2 +- .../components/SubscriptionItemRow.tsx | 2 +- .../components/TrialEditorRow.tsx | 14 ++++------- .../UpdateSubscriptionFormProvider.tsx | 3 +++ .../hooks/useHasSubscriptionChanges.ts | 2 ++ .../hooks/useTrialState.ts | 24 ++++++++++++++----- .../hooks/useUpdateSubscriptionForm.ts | 1 + .../hooks/useUpdateSubscriptionRequestBody.ts | 2 ++ .../updateSubscriptionFormSchema.ts | 1 + .../utils/getFreeTrial.ts | 3 +++ .../plan/components/SelectFeatureSheet.tsx | 12 +++++++--- 13 files changed, 54 insertions(+), 24 deletions(-) diff --git a/shared/utils/productV2Utils/compareProductUtils/generateTrialChanges.ts b/shared/utils/productV2Utils/compareProductUtils/generateTrialChanges.ts index 8303c0e3c..4cf28e629 100644 --- a/shared/utils/productV2Utils/compareProductUtils/generateTrialChanges.ts +++ b/shared/utils/productV2Utils/compareProductUtils/generateTrialChanges.ts @@ -27,11 +27,13 @@ export function generateTrialChanges({ removeTrial, trialLength, trialDuration, + trialEnabled = true, }: { customerProduct: FullCusProduct; removeTrial: boolean; trialLength: number | null; trialDuration: FreeTrialDuration; + trialEnabled?: boolean; }): ItemEdit[] { const isCurrentlyTrialing = isCustomerProductTrialing(customerProduct); const remainingDays = getRemainingTrialDays({ @@ -53,6 +55,11 @@ export function generateTrialChanges({ return changes; } + // If trial is not enabled (collapsed), don't generate changes for new trials + if (!trialEnabled && !isCurrentlyTrialing) { + return changes; + } + if (!removeTrial && trialLength !== null && trialLength > 0) { const newTrialDays = getTrialLengthInDays({ trialLength, trialDuration }); diff --git a/vite/src/components/forms/update-subscription-v2/components/EditPlanSection.tsx b/vite/src/components/forms/update-subscription-v2/components/EditPlanSection.tsx index a584a8bfe..a842e8895 100644 --- a/vite/src/components/forms/update-subscription-v2/components/EditPlanSection.tsx +++ b/vite/src/components/forms/update-subscription-v2/components/EditPlanSection.tsx @@ -203,10 +203,7 @@ export function EditPlanSection() {
} variant="skeleton" size="sm" - className="text-t4 hover:text-t2 hover:bg-muted" + className="text-green-600 dark:text-green-500 hover:text-green-700 dark:hover:text-green-400 hover:bg-black/5 dark:hover:bg-white/10" onClick={() => setIsEditingQuantity(false)} /> diff --git a/vite/src/components/forms/update-subscription-v2/components/TrialEditorRow.tsx b/vite/src/components/forms/update-subscription-v2/components/TrialEditorRow.tsx index 2c8f96df9..4465daf50 100644 --- a/vite/src/components/forms/update-subscription-v2/components/TrialEditorRow.tsx +++ b/vite/src/components/forms/update-subscription-v2/components/TrialEditorRow.tsx @@ -193,19 +193,15 @@ export function TrialEditorRow({ } const handleClearTrial = () => { - if (!hasTrialValue && !isCurrentlyTrialing) { - onCollapse(); - setIsEditing(false); - setIsAddingNewTrial(true); - return; - } + // Just collapse - preserve the value so it can be restored + onCollapse(); + setIsEditing(false); + setIsAddingNewTrial(true); - form.setFieldValue("trialLength", null); + // Only mark for removal if they're currently trialing (ending an active trial) if (isCurrentlyTrialing) { onEndTrial(); } - setIsEditing(false); - setIsAddingNewTrial(true); }; const isNewTrial = !isCurrentlyTrialing; diff --git a/vite/src/components/forms/update-subscription-v2/context/UpdateSubscriptionFormProvider.tsx b/vite/src/components/forms/update-subscription-v2/context/UpdateSubscriptionFormProvider.tsx index 5a3da2653..d2c048187 100644 --- a/vite/src/components/forms/update-subscription-v2/context/UpdateSubscriptionFormProvider.tsx +++ b/vite/src/components/forms/update-subscription-v2/context/UpdateSubscriptionFormProvider.tsx @@ -172,6 +172,7 @@ export function UpdateSubscriptionFormProvider({ removeTrial: formValues.removeTrial, trialLength: formValues.trialLength, trialDuration: formValues.trialDuration, + trialEnabled: formValues.trialEnabled, }); const freeTrialValue = freeTrial === null ? undefined : (freeTrial ?? base.free_trial); @@ -187,6 +188,7 @@ export function UpdateSubscriptionFormProvider({ formValues.removeTrial, formValues.trialLength, formValues.trialDuration, + formValues.trialEnabled, ]); const hasBillingChanges = useHasBillingChanges({ @@ -202,6 +204,7 @@ export function UpdateSubscriptionFormProvider({ removeTrial: formValues.removeTrial, trialLength: formValues.trialLength, trialDuration: formValues.trialDuration, + trialEnabled: formValues.trialEnabled, }); const previewQuery = useUpdateSubscriptionPreview({ diff --git a/vite/src/components/forms/update-subscription-v2/hooks/useHasSubscriptionChanges.ts b/vite/src/components/forms/update-subscription-v2/hooks/useHasSubscriptionChanges.ts index 7dd5a3a6d..86ee45031 100644 --- a/vite/src/components/forms/update-subscription-v2/hooks/useHasSubscriptionChanges.ts +++ b/vite/src/components/forms/update-subscription-v2/hooks/useHasSubscriptionChanges.ts @@ -34,6 +34,7 @@ export function useHasSubscriptionChanges({ removeTrial: formValues.removeTrial, trialLength: formValues.trialLength, trialDuration: formValues.trialDuration, + trialEnabled: formValues.trialEnabled, }); if (trialChanges.length > 0) return true; @@ -74,6 +75,7 @@ export function useHasSubscriptionChanges({ formValues.removeTrial, formValues.trialLength, formValues.trialDuration, + formValues.trialEnabled, formValues.version, formValues.items, formValues.prepaidOptions, diff --git a/vite/src/components/forms/update-subscription-v2/hooks/useTrialState.ts b/vite/src/components/forms/update-subscription-v2/hooks/useTrialState.ts index 710a3bf67..47e89b926 100644 --- a/vite/src/components/forms/update-subscription-v2/hooks/useTrialState.ts +++ b/vite/src/components/forms/update-subscription-v2/hooks/useTrialState.ts @@ -7,7 +7,7 @@ import { isCustomerProductTrialing, } from "@autumn/shared"; import { useStore } from "@tanstack/react-form"; -import { useCallback, useState } from "react"; +import { useCallback } from "react"; import type { UseUpdateSubscriptionForm } from "./useUpdateSubscriptionForm"; interface UseTrialStateParams { @@ -54,7 +54,10 @@ export function useTrialState({ ? formatRemainingTrialTime({ trialEndsAt: customerProduct.trial_ends_at }) : null; - const [isTrialExpanded, setIsTrialExpanded] = useState(isCurrentlyTrialing); + const trialEnabled = useStore( + form.store, + (state) => state.values.trialEnabled, + ); const removeTrial = useStore(form.store, (state) => state.values.removeTrial); @@ -80,22 +83,31 @@ export function useTrialState({ const handleToggleTrial = useCallback(() => { if (removeTrial) { form.setFieldValue("removeTrial", false); - setIsTrialExpanded(true); + form.setFieldValue("trialEnabled", true); } else { - setIsTrialExpanded((prev) => !prev); + form.setFieldValue("trialEnabled", !trialEnabled); } - }, [removeTrial, form]); + }, [removeTrial, trialEnabled, form]); const handleEndTrial = useCallback(() => { form.setFieldValue("removeTrial", true); + form.setFieldValue("trialEnabled", false); }, [form]); const handleRevertTrial = useCallback(() => { form.setFieldValue("removeTrial", false); + form.setFieldValue("trialEnabled", true); form.setFieldValue("trialLength", remainingTrialDays); form.setFieldValue("trialDuration", FreeTrialDuration.Day); }, [form, remainingTrialDays]); + const setIsTrialExpanded = useCallback( + (expanded: boolean) => { + form.setFieldValue("trialEnabled", expanded); + }, + [form], + ); + return { isCurrentlyTrialing, remainingTrialDays, @@ -105,7 +117,7 @@ export function useTrialState({ removeTrial, hasTrialValue, isTrialModified, - isTrialExpanded, + isTrialExpanded: trialEnabled, handleToggleTrial, handleEndTrial, handleRevertTrial, diff --git a/vite/src/components/forms/update-subscription-v2/hooks/useUpdateSubscriptionForm.ts b/vite/src/components/forms/update-subscription-v2/hooks/useUpdateSubscriptionForm.ts index 642fb1631..4bfbb2b43 100644 --- a/vite/src/components/forms/update-subscription-v2/hooks/useUpdateSubscriptionForm.ts +++ b/vite/src/components/forms/update-subscription-v2/hooks/useUpdateSubscriptionForm.ts @@ -49,6 +49,7 @@ export function useUpdateSubscriptionForm({ trialLength: remainingTrialDays, trialDuration: FreeTrialDuration.Day, removeTrial: false, + trialEnabled: isTrialing, version: currentVersion, items: null, } as UpdateSubscriptionForm, diff --git a/vite/src/components/forms/update-subscription-v2/hooks/useUpdateSubscriptionRequestBody.ts b/vite/src/components/forms/update-subscription-v2/hooks/useUpdateSubscriptionRequestBody.ts index dc63c233c..ced9f2560 100644 --- a/vite/src/components/forms/update-subscription-v2/hooks/useUpdateSubscriptionRequestBody.ts +++ b/vite/src/components/forms/update-subscription-v2/hooks/useUpdateSubscriptionRequestBody.ts @@ -25,6 +25,7 @@ export function useUpdateSubscriptionRequestBody({ trialLength, trialDuration, removeTrial, + trialEnabled, version, items, } = formValues; @@ -89,6 +90,7 @@ export function useUpdateSubscriptionRequestBody({ removeTrial, trialLength, trialDuration, + trialEnabled, }); if (freeTrial !== undefined) { requestBody.free_trial = freeTrial; diff --git a/vite/src/components/forms/update-subscription-v2/updateSubscriptionFormSchema.ts b/vite/src/components/forms/update-subscription-v2/updateSubscriptionFormSchema.ts index c25cf2dcf..e5a5f6786 100644 --- a/vite/src/components/forms/update-subscription-v2/updateSubscriptionFormSchema.ts +++ b/vite/src/components/forms/update-subscription-v2/updateSubscriptionFormSchema.ts @@ -7,6 +7,7 @@ export const UpdateSubscriptionFormSchema = z.object({ trialLength: z.number().positive().nullable(), trialDuration: z.enum(FreeTrialDuration), removeTrial: z.boolean(), + trialEnabled: z.boolean(), version: z.number().positive(), diff --git a/vite/src/components/forms/update-subscription-v2/utils/getFreeTrial.ts b/vite/src/components/forms/update-subscription-v2/utils/getFreeTrial.ts index a4a757afc..d13043380 100644 --- a/vite/src/components/forms/update-subscription-v2/utils/getFreeTrial.ts +++ b/vite/src/components/forms/update-subscription-v2/utils/getFreeTrial.ts @@ -7,12 +7,15 @@ export function getFreeTrial({ removeTrial, trialLength, trialDuration, + trialEnabled, }: { removeTrial: boolean; trialLength: number | null; trialDuration: FreeTrialDuration; + trialEnabled: boolean; }): CreateFreeTrial | null | undefined { if (removeTrial) return null; + if (!trialEnabled) return undefined; if (trialLength !== null && trialLength > 0) { return { length: trialLength, diff --git a/vite/src/views/products/plan/components/SelectFeatureSheet.tsx b/vite/src/views/products/plan/components/SelectFeatureSheet.tsx index 168761f5c..a3121f9aa 100644 --- a/vite/src/views/products/plan/components/SelectFeatureSheet.tsx +++ b/vite/src/views/products/plan/components/SelectFeatureSheet.tsx @@ -33,7 +33,7 @@ export function SelectFeatureSheet({ const [searchValue, setSearchValue] = useState(""); const { features } = useFeaturesQuery(); - const { product, setProduct } = useProduct(); + const { product, setProduct, initialProduct } = useProduct(); const { setSheet } = useSheet(); useEffect(() => { @@ -60,8 +60,14 @@ export function SelectFeatureSheet({ const selectedFeature = features.find((f) => f.id === featureId); if (!selectedFeature) return; - // Create a new item with the selected feature - const newItem = getDefaultItem({ feature: selectedFeature }); + // Check if this feature was previously configured in initialProduct + const previousItem = initialProduct?.items?.find( + (i) => i.feature_id === featureId, + ); + + // Use the previous configuration if available, otherwise create a new default item + const newItem = + previousItem ?? getDefaultItem({ feature: selectedFeature }); // Add the new item to the product const newItems = [...product.items, newItem]; From 18d053df1030d20edd860a2d09f2b8024530336f Mon Sep 17 00:00:00 2001 From: Charlie Lamb Date: Fri, 16 Jan 2026 18:31:16 +0000 Subject: [PATCH 10/34] chore: add animation when opening custom plan editor --- .../v2/inline-custom-plan-editor/InlinePlanEditor.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/vite/src/components/v2/inline-custom-plan-editor/InlinePlanEditor.tsx b/vite/src/components/v2/inline-custom-plan-editor/InlinePlanEditor.tsx index 19678f636..a6104c398 100644 --- a/vite/src/components/v2/inline-custom-plan-editor/InlinePlanEditor.tsx +++ b/vite/src/components/v2/inline-custom-plan-editor/InlinePlanEditor.tsx @@ -52,7 +52,12 @@ function InlinePlanEditorContent({ const hasPlanChanges = useHasPlanChanges(); return ( -
+
-
+ ); } From 58f44eec5e155a18f15f4ac81894c6cbb89d3003 Mon Sep 17 00:00:00 2001 From: Charlie Lamb Date: Fri, 16 Jan 2026 18:39:14 +0000 Subject: [PATCH 11/34] chore: interval text for one time & github action quick fix --- .github/workflows/vite-build.yml | 5 --- .../components/EditPlanSection.tsx | 35 ++++++++++++------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/.github/workflows/vite-build.yml b/.github/workflows/vite-build.yml index 9aaf84b6e..fc194d98d 100644 --- a/.github/workflows/vite-build.yml +++ b/.github/workflows/vite-build.yml @@ -1,11 +1,6 @@ name: Vite Build Check on: - push: - branches: - - main - - staging - - dev pull_request: jobs: diff --git a/vite/src/components/forms/update-subscription-v2/components/EditPlanSection.tsx b/vite/src/components/forms/update-subscription-v2/components/EditPlanSection.tsx index a842e8895..f724640aa 100644 --- a/vite/src/components/forms/update-subscription-v2/components/EditPlanSection.tsx +++ b/vite/src/components/forms/update-subscription-v2/components/EditPlanSection.tsx @@ -82,19 +82,30 @@ export function EditPlanSection() { }, }); - const oldIntervalText = originalInterval - ? formatInterval({ - interval: originalInterval, - intervalCount: originalIntervalCount, - }) - : null; + const getIntervalText = ( + interval: typeof originalInterval, + intervalCount: number, + hasPriceItem: boolean, + ) => { + if (interval) { + return formatInterval({ interval, intervalCount }); + } + // Only show "one-time" if there's a price item with no interval + // Otherwise it's variable/usage-based + return hasPriceItem ? "one-time" : null; + }; - const newIntervalText = currentInterval - ? formatInterval({ - interval: currentInterval, - intervalCount: currentIntervalCount, - }) - : (oldIntervalText ?? "per month"); + const oldIntervalText = getIntervalText( + originalInterval, + originalIntervalCount, + !!originalPriceItem, + ); + + const newIntervalText = getIntervalText( + currentInterval, + currentIntervalCount, + !!currentPriceItem, + ); return { oldPrice: formatPrice(originalPrice), From 20e52f09d7aa3640a4e4deffcec34c979a272d51 Mon Sep 17 00:00:00 2001 From: Charlie Lamb Date: Fri, 16 Jan 2026 18:45:21 +0000 Subject: [PATCH 12/34] chore: fix animation and spacing in update subscription sheet --- .../components/EditPlanSection.tsx | 4 ++-- .../InlinePlanEditor.tsx | 3 ++- .../sheets/SubscriptionUpdateSheet2.tsx | 19 +++++++++++-------- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/vite/src/components/forms/update-subscription-v2/components/EditPlanSection.tsx b/vite/src/components/forms/update-subscription-v2/components/EditPlanSection.tsx index f724640aa..3f7fc87c1 100644 --- a/vite/src/components/forms/update-subscription-v2/components/EditPlanSection.tsx +++ b/vite/src/components/forms/update-subscription-v2/components/EditPlanSection.tsx @@ -155,7 +155,7 @@ export function EditPlanSection() { )}
-
+
{product?.items?.map((item: ProductItem, index: number) => { if (!item.feature_id) return null; @@ -215,7 +215,7 @@ export function EditPlanSection() { className={cn( "grid transition-[grid-template-rows] duration-200 ease-out", trialState.isTrialExpanded || trialState.removeTrial - ? "grid-rows-[1fr]" + ? "grid-rows-[1fr] mb-2" : "grid-rows-[0fr]", )} > diff --git a/vite/src/components/v2/inline-custom-plan-editor/InlinePlanEditor.tsx b/vite/src/components/v2/inline-custom-plan-editor/InlinePlanEditor.tsx index a6104c398..b639fc453 100644 --- a/vite/src/components/v2/inline-custom-plan-editor/InlinePlanEditor.tsx +++ b/vite/src/components/v2/inline-custom-plan-editor/InlinePlanEditor.tsx @@ -55,7 +55,8 @@ function InlinePlanEditorContent({
diff --git a/vite/src/views/customers2/components/sheets/SubscriptionUpdateSheet2.tsx b/vite/src/views/customers2/components/sheets/SubscriptionUpdateSheet2.tsx index 9f7e7bc62..25c78e3da 100644 --- a/vite/src/views/customers2/components/sheets/SubscriptionUpdateSheet2.tsx +++ b/vite/src/views/customers2/components/sheets/SubscriptionUpdateSheet2.tsx @@ -1,5 +1,6 @@ import type { FullCusProduct, ProductItem, ProductV2 } from "@autumn/shared"; import { useQuery } from "@tanstack/react-query"; +import { AnimatePresence } from "motion/react"; import { useMemo } from "react"; import { @@ -75,14 +76,16 @@ function SheetContent() { - {showPlanEditor && productWithFormItems && ( - - )} + + {showPlanEditor && productWithFormItems && ( + + )} +
); } From 8d9d08071fa13112eef48b9e73ceec1b87136e5c Mon Sep 17 00:00:00 2001 From: Charlie Lamb Date: Fri, 16 Jan 2026 18:50:05 +0000 Subject: [PATCH 13/34] chore: fix icon empty sheet click issue --- .../views/products/plan/components/plan-card/PlanFeatureRow.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vite/src/views/products/plan/components/plan-card/PlanFeatureRow.tsx b/vite/src/views/products/plan/components/plan-card/PlanFeatureRow.tsx index 1b2859c6c..3efcb45b4 100644 --- a/vite/src/views/products/plan/components/plan-card/PlanFeatureRow.tsx +++ b/vite/src/views/products/plan/components/plan-card/PlanFeatureRow.tsx @@ -84,7 +84,7 @@ export const PlanFeatureRow = ({ }, [itemId, currentItemId]); const handleRowClicked = () => { - if (isDisabled) return; + if (readOnly || isDisabled) return; const currentItemId = getItemId({ item, itemIndex: index }); setItem(item); From 2542d470e695e616d7e35e958861228b8187f1ed Mon Sep 17 00:00:00 2001 From: John Yeo Date: Fri, 16 Jan 2026 18:54:56 +0000 Subject: [PATCH 14/34] fix: preview response not returning debug details --- .../handleSchedulePhaseCompleted.ts | 4 +- .../handleCurrentCustomerProductErrors.ts | 26 +++++ .../errors/handleUpdateSubscriptionErrors.ts | 4 + .../handlePreviewUpdateSubscription.ts | 6 +- .../handlers/handleGetProductDeleteInfo.ts | 105 ++++++++---------- .../products/internalProductRouter.ts | 46 ++++---- .../custom-plan/update-free-to-paid.test.ts | 97 ++++++++++++++++ .../classifyCustomerProduct.ts | 21 ++-- 8 files changed, 206 insertions(+), 103 deletions(-) create mode 100644 server/src/internal/billing/v2/updateSubscription/errors/handleCurrentCustomerProductErrors.ts diff --git a/server/src/external/stripe/webhookHandlers/legacy/handleSubUpdated/handleSchedulePhaseCompleted.ts b/server/src/external/stripe/webhookHandlers/legacy/handleSubUpdated/handleSchedulePhaseCompleted.ts index c7b1c657f..75adba428 100644 --- a/server/src/external/stripe/webhookHandlers/legacy/handleSubUpdated/handleSchedulePhaseCompleted.ts +++ b/server/src/external/stripe/webhookHandlers/legacy/handleSubUpdated/handleSchedulePhaseCompleted.ts @@ -3,7 +3,7 @@ import { CusProductStatus, type FullCustomer, formatMs, - isCustomerProductExpired, + hasCustomerProductEnded, } from "@autumn/shared"; import { createStripeCli } from "@/external/connect/createStripeCli.js"; import { stripeCustomerToNowMs } from "@/external/stripe/customers/index"; @@ -66,7 +66,7 @@ export const handleSchedulePhaseCompleted = async ({ ); for (const cusProduct of customerProducts) { - const shouldExpire = isCustomerProductExpired(cusProduct, { nowMs }); + const shouldExpire = hasCustomerProductEnded(cusProduct, { nowMs }); if (shouldExpire) { logger.info( diff --git a/server/src/internal/billing/v2/updateSubscription/errors/handleCurrentCustomerProductErrors.ts b/server/src/internal/billing/v2/updateSubscription/errors/handleCurrentCustomerProductErrors.ts new file mode 100644 index 000000000..6f47ebfd0 --- /dev/null +++ b/server/src/internal/billing/v2/updateSubscription/errors/handleCurrentCustomerProductErrors.ts @@ -0,0 +1,26 @@ +import { + isCustomerProductExpired, + isCustomerProductScheduled, + RecaseError, +} from "@autumn/shared"; +import type { UpdateSubscriptionBillingContext } from "@/internal/billing/v2/billingContext"; + +export const handleCurrentCustomerProductErrors = ({ + billingContext, +}: { + billingContext: UpdateSubscriptionBillingContext; +}) => { + const { customerProduct } = billingContext; + + if (isCustomerProductScheduled(customerProduct)) { + throw new RecaseError({ + message: `Cannot update subscription for '${customerProduct.product.name}' because it is scheduled and not yet active`, + }); + } + + if (isCustomerProductExpired(customerProduct)) { + throw new RecaseError({ + message: `Cannot update subscription for '${customerProduct.product.name}' because it has expired`, + }); + } +}; diff --git a/server/src/internal/billing/v2/updateSubscription/errors/handleUpdateSubscriptionErrors.ts b/server/src/internal/billing/v2/updateSubscription/errors/handleUpdateSubscriptionErrors.ts index d24a70d1f..575a6eb08 100644 --- a/server/src/internal/billing/v2/updateSubscription/errors/handleUpdateSubscriptionErrors.ts +++ b/server/src/internal/billing/v2/updateSubscription/errors/handleUpdateSubscriptionErrors.ts @@ -7,6 +7,7 @@ import { cusProductToProcessorType } from "@shared/utils/cusProductUtils/convert import type { AutumnContext } from "@/honoUtils/HonoEnv"; import type { UpdateSubscriptionBillingContext } from "@/internal/billing/v2/billingContext"; import type { AutumnBillingPlan } from "@/internal/billing/v2/types/autumnBillingPlan"; +import { handleCurrentCustomerProductErrors } from "./handleCurrentCustomerProductErrors"; import { handleCustomPlanErrors } from "./handleCustomPlanErrors"; import { handleFeatureQuantityErrors } from "./handleFeatureQuantityErrors"; import { @@ -35,6 +36,9 @@ export const handleUpdateSubscriptionErrors = async ({ }); } + // 1. Current customer product errors + handleCurrentCustomerProductErrors({ billingContext }); + // 2. Product type transition errors handleProductTypeTransitionErrors({ billingContext, autumnBillingPlan }); diff --git a/server/src/internal/billing/v2/updateSubscription/handlePreviewUpdateSubscription.ts b/server/src/internal/billing/v2/updateSubscription/handlePreviewUpdateSubscription.ts index 8b6d5caf6..5c618f4ed 100644 --- a/server/src/internal/billing/v2/updateSubscription/handlePreviewUpdateSubscription.ts +++ b/server/src/internal/billing/v2/updateSubscription/handlePreviewUpdateSubscription.ts @@ -67,10 +67,6 @@ export const handlePreviewUpdateSubscription = createRoute({ }, }); - return c.json({ - ...previewResponse, - autumn: autumnBillingPlan, - stripe: stripeBillingPlan, - }); + return c.json(previewResponse); }, }); diff --git a/server/src/internal/products/handlers/handleGetProductDeleteInfo.ts b/server/src/internal/products/handlers/handleGetProductDeleteInfo.ts index 06b24f9b9..432918051 100644 --- a/server/src/internal/products/handlers/handleGetProductDeleteInfo.ts +++ b/server/src/internal/products/handlers/handleGetProductDeleteInfo.ts @@ -1,68 +1,51 @@ -import { ProductNotFoundError } from "@autumn/shared"; +import { AffectedResource, ProductNotFoundError } from "@autumn/shared"; +import { createRoute } from "@/honoMiddlewares/routeHandler.js"; import { CusProdReadService } from "@/internal/customers/cusProducts/CusProdReadService.js"; -import { handleRequestError } from "@/utils/errorUtils.js"; -import type { ExtendedRequest } from "@/utils/models/Request.js"; -import { routeHandler } from "@/utils/routerUtils.js"; import { ProductService } from "../ProductService.js"; -export const handleGetProductDeleteInfo = async (req: any, res: any) => - routeHandler({ - req, - res, - action: "Get product deletion info", - handler: async (req: ExtendedRequest, res: any) => { - try { - // 1. Get number of versions - const { db } = req; - const productId = Array.isArray(req.params.productId) - ? req.params.productId[0] - : req.params.productId; +export const handleGetProductDeleteInfo = createRoute({ + resource: AffectedResource.Product, + handler: async (c) => { + // 1. Get number of versions + const ctx = c.get("ctx"); + const { db, org, env } = ctx; + const { productId } = c.req.param(); - const product = await ProductService.get({ - db, - id: productId, - orgId: req.orgId, - env: req.env, - }); + const product = await ProductService.get({ + db, + id: productId, + orgId: org.id, + env, + }); - if (!product) { - throw new ProductNotFoundError({ productId }); - } + if (!product) { + throw new ProductNotFoundError({ productId }); + } - const [allVersions, latestVersion, deletionText] = await Promise.all([ - CusProdReadService.existsForProduct({ - db, - productId, - }), - CusProdReadService.existsForProduct({ - db, - internalProductId: product.internal_id, - }), - ProductService.getDeletionText({ - db, - productId, - orgId: req.orgId, - env: req.env, - }), - ]); + const [allVersions, latestVersion, deletionText] = await Promise.all([ + CusProdReadService.existsForProduct({ + db, + productId, + }), + CusProdReadService.existsForProduct({ + db, + internalProductId: product.internal_id, + }), + ProductService.getDeletionText({ + db, + productId, + orgId: org.id, + env, + }), + ]); - res.status(200).send({ - numVersion: product.version, - hasCusProducts: allVersions, - hasCusProductsLatest: latestVersion, - customerName: - deletionText[0]?.name || - deletionText[0]?.email || - deletionText[0]?.id, - totalCount: deletionText[0]?.totalCount, - }); - } catch (error) { - handleRequestError({ - error, - req, - res, - action: "Get product info", - }); - } - }, - }); + return c.json({ + numVersion: product.version, + hasCusProducts: allVersions, + hasCusProductsLatest: latestVersion, + customerName: + deletionText[0]?.name || deletionText[0]?.email || deletionText[0]?.id, + totalCount: deletionText[0]?.totalCount, + }); + }, +}); diff --git a/server/src/internal/products/internalProductRouter.ts b/server/src/internal/products/internalProductRouter.ts index 45861ff2d..06795440f 100644 --- a/server/src/internal/products/internalProductRouter.ts +++ b/server/src/internal/products/internalProductRouter.ts @@ -1,37 +1,11 @@ import { Router } from "express"; -import { handleFrontendReqError } from "@/utils/errorUtils.js"; import { EntitlementService } from "./entitlements/EntitlementService.js"; import { handleGetProductDeleteInfo } from "./handlers/handleGetProductDeleteInfo.js"; export const expressProductRouter: Router = Router({ mergeParams: true }); -expressProductRouter.get("/:productId/info", handleGetProductDeleteInfo); - -expressProductRouter.get( - "/has_entity_feature_id", - async (req: any, res: any) => { - try { - const { db, orgId, env } = req; - - const hasEntityFeatureId = await EntitlementService.hasEntityFeatureId({ - db, - orgId, - env, - }); - - res.status(200).send({ hasEntityFeatureId }); - } catch (error) { - handleFrontendReqError({ - error, - req, - res, - action: "Check has entity feature id", - }); - } - }, -); - import { Hono } from "hono"; +import { createRoute } from "@/honoMiddlewares/routeHandler.js"; import type { HonoEnv } from "@/honoUtils/HonoEnv.js"; import { handleGetProducts } from "@/internal/products/internalHandlers/handleGetProducts.js"; import { handleCopyEnvironment } from "./handlers/handleCopyEnvironment/handleCopyEnvironment.js"; @@ -55,3 +29,21 @@ internalProductRouter.get("/migrations", ...handleGetMigrations); internalProductRouter.get("/:productId/count", ...handleGetProductCount); internalProductRouter.get("/:productId/data", ...handleGetProductInternal); internalProductRouter.post("/copy_to_production", ...handleCopyEnvironment); +internalProductRouter.get("/:productId/info", ...handleGetProductDeleteInfo); +internalProductRouter.get( + "/has_entity_feature_id", + ...createRoute({ + handler: async (c) => { + const ctx = c.get("ctx"); + const { db, org, env } = ctx; + + const hasEntityFeatureId = await EntitlementService.hasEntityFeatureId({ + db, + orgId: org.id, + env, + }); + + return c.json({ hasEntityFeatureId }); + }, + }), +); diff --git a/server/tests/integration/billing/update-subscription/custom-plan/update-free-to-paid.test.ts b/server/tests/integration/billing/update-subscription/custom-plan/update-free-to-paid.test.ts index 0b1460e9e..07a244c4c 100644 --- a/server/tests/integration/billing/update-subscription/custom-plan/update-free-to-paid.test.ts +++ b/server/tests/integration/billing/update-subscription/custom-plan/update-free-to-paid.test.ts @@ -398,3 +398,100 @@ test.concurrent(`${chalk.yellowBright("free-to-paid: update free users to alloca env: ctx.env, }); }); + +// 7. Converting free messages to unlimited with monthly price +test.concurrent(`${chalk.yellowBright("free-to-paid: convert to unlimited with monthly price")}`, async () => { + const messagesItem = items.monthlyMessages({ includedUsage: 100 }); + const free = products.base({ items: [messagesItem] }); + + const { customerId, autumnV1, ctx } = await initScenario({ + customerId: "f2p-unlimited-price", + setup: [ + s.customer({ testClock: true, paymentMethod: "success" }), + s.products({ list: [free] }), + ], + actions: [s.attach({ productId: "base" })], + }); + + // Track some usage before update + const messagesUsage = 50; + await autumnV1.track( + { + customer_id: customerId, + feature_id: TestFeature.Messages, + value: messagesUsage, + }, + { timeout: 2000 }, + ); + + // Step 2: Add monthly price item (converting to paid) + const priceItem = items.monthlyPrice(); + + const updateParams1 = { + customer_id: customerId, + product_id: free.id, + items: [messagesItem, priceItem], + }; + + const preview1 = await autumnV1.subscriptions.previewUpdate(updateParams1); + + // Should charge $20 for monthly base price + expect(preview1.total).toEqual(20); + + await autumnV1.subscriptions.update(updateParams1); + + // Verify first update + const customer1 = await autumnV1.customers.get(customerId); + + expectCustomerFeatureCorrect({ + customer: customer1, + featureId: TestFeature.Messages, + includedUsage: messagesItem.included_usage, + balance: messagesItem.included_usage - messagesUsage, + usage: messagesUsage, + }); + + expectCustomerInvoiceCorrect({ + customer: customer1, + count: 1, + latestTotal: 20, + }); + + // Step 3: Convert messages to unlimited + const unlimitedMessagesItem = items.unlimitedMessages(); + + const updateParams2 = { + customer_id: customerId, + product_id: free.id, + items: [unlimitedMessagesItem, priceItem], + }; + + const preview2 = await autumnV1.subscriptions.previewUpdate(updateParams2); + + // No additional charge for unlimited upgrade + expect(preview2.total).toEqual(0); + + await autumnV1.subscriptions.update(updateParams2); + + // Verify final state + const customer2 = await autumnV1.customers.get(customerId); + + // Messages should now be unlimited + expect(customer2.features[TestFeature.Messages].unlimited).toBe(true); + + // Still only 1 invoice (no additional charge for unlimited) + expectCustomerInvoiceCorrect({ + customer: customer2, + count: 2, + latestTotal: 0, // unlimited + }); + + await expectSubToBeCorrect({ + db: ctx.db, + customerId, + org: ctx.org, + env: ctx.env, + }); +}); + +// 1. Update free -> paid -> add feature diff --git a/shared/utils/cusProductUtils/classifyCustomerProduct/classifyCustomerProduct.ts b/shared/utils/cusProductUtils/classifyCustomerProduct/classifyCustomerProduct.ts index b3dc4cdd9..5e21ac1b4 100644 --- a/shared/utils/cusProductUtils/classifyCustomerProduct/classifyCustomerProduct.ts +++ b/shared/utils/cusProductUtils/classifyCustomerProduct/classifyCustomerProduct.ts @@ -75,17 +75,22 @@ export const isCustomerProductCanceling = (cp?: FullCusProduct) => { return notNullish(cp.canceled_at); }; -export const isCustomerProductExpired = ( - cp: FullCusProduct, - params: { nowMs?: number }, -) => { - const nowMs = params.nowMs ?? Date.now(); +export const isCustomerProductExpired = (cp?: FullCusProduct) => { + if (!cp) return false; + return cp.status === CusProductStatus.Expired; +}; - return ( +export const hasCustomerProductEnded = ( + cp: FullCusProduct, + params?: { nowMs?: number }, +) => { + const nowMs = params?.nowMs ?? Date.now(); + + const hasEnded = isCustomerProductCanceling(cp) && notNullish(cp.ended_at) && - nowMs >= cp.ended_at - ); + nowMs >= cp.ended_at; + return hasEnded; }; export const isCustomerProductTrialing = ( From fe6951976a90c2be359e104622f829efe7e90dfe Mon Sep 17 00:00:00 2001 From: Charlie Lamb Date: Fri, 16 Jan 2026 19:08:53 +0000 Subject: [PATCH 15/34] chore: cubic suggestion --- .../InlinePlanEditor.tsx | 13 +++++++++---- .../sheets/SubscriptionUpdateSheet2.tsx | 19 ++++++++----------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/vite/src/components/v2/inline-custom-plan-editor/InlinePlanEditor.tsx b/vite/src/components/v2/inline-custom-plan-editor/InlinePlanEditor.tsx index b639fc453..fe29d9819 100644 --- a/vite/src/components/v2/inline-custom-plan-editor/InlinePlanEditor.tsx +++ b/vite/src/components/v2/inline-custom-plan-editor/InlinePlanEditor.tsx @@ -15,15 +15,16 @@ import { useHasPlanChanges, useProduct, useSheet } from "./PlanEditorContext"; interface InlinePlanEditorProps { product: FrontendProduct; - productName?: string; onSave: (items: ProductItem[]) => void; onCancel: () => void; + isOpen: boolean; } export function InlinePlanEditor({ product, onSave, onCancel, + isOpen, }: InlinePlanEditorProps) { const mainContent = document.querySelector("[data-main-content]"); @@ -33,9 +34,13 @@ export function InlinePlanEditor({ } return createPortal( - - - , + + {isOpen && ( + + + + )} + , mainContent, ); } diff --git a/vite/src/views/customers2/components/sheets/SubscriptionUpdateSheet2.tsx b/vite/src/views/customers2/components/sheets/SubscriptionUpdateSheet2.tsx index 25c78e3da..df2f6d52d 100644 --- a/vite/src/views/customers2/components/sheets/SubscriptionUpdateSheet2.tsx +++ b/vite/src/views/customers2/components/sheets/SubscriptionUpdateSheet2.tsx @@ -1,6 +1,5 @@ import type { FullCusProduct, ProductItem, ProductV2 } from "@autumn/shared"; import { useQuery } from "@tanstack/react-query"; -import { AnimatePresence } from "motion/react"; import { useMemo } from "react"; import { @@ -76,16 +75,14 @@ function SheetContent() { - - {showPlanEditor && productWithFormItems && ( - - )} - + {productWithFormItems && ( + + )}
); } From 2ab6cd2ad9deb6196ebfe648e2ac6d47aef1043c Mon Sep 17 00:00:00 2001 From: Charlie Lamb Date: Fri, 16 Jan 2026 19:21:02 +0000 Subject: [PATCH 16/34] chore: add layout animations --- .../components/EditPlanSection.tsx | 184 ++++++++++-------- 1 file changed, 106 insertions(+), 78 deletions(-) diff --git a/vite/src/components/forms/update-subscription-v2/components/EditPlanSection.tsx b/vite/src/components/forms/update-subscription-v2/components/EditPlanSection.tsx index 3f7fc87c1..41d5ec2b7 100644 --- a/vite/src/components/forms/update-subscription-v2/components/EditPlanSection.tsx +++ b/vite/src/components/forms/update-subscription-v2/components/EditPlanSection.tsx @@ -8,11 +8,11 @@ import { UsageModel, } from "@autumn/shared"; import { PencilSimpleIcon } from "@phosphor-icons/react"; +import { LayoutGroup, motion, type Transition } from "motion/react"; import { useMemo } from "react"; import { Button } from "@/components/v2/buttons/Button"; import { SheetSection } from "@/components/v2/sheets/SharedSheetComponents"; import { useOrg } from "@/hooks/common/useOrg"; -import { cn } from "@/lib/utils"; import { useUpdateSubscriptionFormContext } from "../context/UpdateSubscriptionFormProvider"; import { PriceDisplay } from "./PriceDisplay"; import { SectionTitle } from "./SectionTitle"; @@ -20,6 +20,11 @@ import { SubscriptionItemRow } from "./SubscriptionItemRow"; import { TrialEditorRow } from "./TrialEditorRow"; import { VersionChangeRow } from "./VersionChangeRow"; +const LAYOUT_TRANSITION: Transition = { + duration: 0.35, + ease: [0.32, 0.72, 0, 1], +}; + export function EditPlanSection() { const { formContext, @@ -155,90 +160,113 @@ export function EditPlanSection() { )}
-
- {product?.items?.map((item: ProductItem, index: number) => { - if (!item.feature_id) return null; + +
+ {product?.items?.map((item: ProductItem, index: number) => { + if (!item.feature_id) return null; - const featureId = item.feature_id; - const featureForOptions = features?.find( - (f) => f.id === featureId, - ); - const prepaidOption = featureToOptions({ - feature: featureForOptions, - options: customerProduct?.options, - }); + const featureId = item.feature_id; + const featureForOptions = features?.find( + (f) => f.id === featureId, + ); + const prepaidOption = featureToOptions({ + feature: featureForOptions, + options: customerProduct?.options, + }); - const isPrepaid = item.usage_model === UsageModel.Prepaid; - const currentPrepaidQuantity = isPrepaid - ? prepaidOptions[featureId] - : prepaidOption?.quantity; - const initialPrepaidQuantity = isPrepaid - ? initialPrepaidOptions[featureId] - : undefined; + const isPrepaid = item.usage_model === UsageModel.Prepaid; + const currentPrepaidQuantity = isPrepaid + ? prepaidOptions[featureId] + : prepaidOption?.quantity; + const initialPrepaidQuantity = isPrepaid + ? initialPrepaidOptions[featureId] + : undefined; - const originalItem = originalItemsMap.get(featureId); - const isCreated = - !originalItem && originalItems && originalItems.length > 0; - const edits = buildEditsForItem({ - updatedItem: item, - originalItem, - updatedPrepaidQuantity: currentPrepaidQuantity, - originalPrepaidQuantity: initialPrepaidQuantity, - }); + const originalItem = originalItemsMap.get(featureId); + const isCreated = + !originalItem && originalItems && originalItems.length > 0; + const edits = buildEditsForItem({ + updatedItem: item, + originalItem, + updatedPrepaidQuantity: currentPrepaidQuantity, + originalPrepaidQuantity: initialPrepaidQuantity, + }); - return ( - - ); - })} - {deletedItems.map((item: ProductItem, index: number) => ( - - ))} - {showVersionChange && ( - - )} -
+ + + ); + })} + {deletedItems.map((item: ProductItem, index: number) => ( + + + + ))} + {showVersionChange && ( + + + )} - > -
- trialState.setIsTrialExpanded(false)} - onRevert={trialState.handleRevertTrial} - /> -
+ {(trialState.isTrialExpanded || trialState.removeTrial) && ( + + trialState.setIsTrialExpanded(false)} + onRevert={trialState.handleRevertTrial} + /> + + )} + + +
-
+
- ) : null} - + ) : ( + + )} ); } From f512212807dd7e873e39f1dcf43388d72f154c0b Mon Sep 17 00:00:00 2001 From: Charlie Lamb Date: Fri, 16 Jan 2026 19:23:24 +0000 Subject: [PATCH 17/34] chore: fix tick colour in update quantity input --- .../update-subscription-v2/components/SubscriptionItemRow.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vite/src/components/forms/update-subscription-v2/components/SubscriptionItemRow.tsx b/vite/src/components/forms/update-subscription-v2/components/SubscriptionItemRow.tsx index edd5a0af4..b5c1f3103 100644 --- a/vite/src/components/forms/update-subscription-v2/components/SubscriptionItemRow.tsx +++ b/vite/src/components/forms/update-subscription-v2/components/SubscriptionItemRow.tsx @@ -255,7 +255,7 @@ export function SubscriptionItemRow({ icon={} variant="skeleton" size="sm" - className="text-green-600 dark:text-green-500 hover:text-green-700 dark:hover:text-green-400 hover:bg-black/5 dark:hover:bg-white/10" + className="text-green-600 dark:text-green-500 hover:text-green-700! dark:hover:text-green-400! hover:bg-black/5 dark:hover:bg-white/10" onClick={() => setIsEditingQuantity(false)} /> From 6a3da85e1fe33b2290e95a49120f341f764282ee Mon Sep 17 00:00:00 2001 From: Charlie Lamb Date: Fri, 16 Jan 2026 19:39:03 +0000 Subject: [PATCH 18/34] chore: add animations --- .../components/EditPlanSection.tsx | 8 +- .../components/SubscriptionItemRow.tsx | 96 ++++++----- .../components/TrialEditorRow.tsx | 152 ++++++++++++------ .../constants/animationConstants.ts | 16 ++ 4 files changed, 178 insertions(+), 94 deletions(-) create mode 100644 vite/src/components/forms/update-subscription-v2/constants/animationConstants.ts diff --git a/vite/src/components/forms/update-subscription-v2/components/EditPlanSection.tsx b/vite/src/components/forms/update-subscription-v2/components/EditPlanSection.tsx index 41d5ec2b7..aec3f42d3 100644 --- a/vite/src/components/forms/update-subscription-v2/components/EditPlanSection.tsx +++ b/vite/src/components/forms/update-subscription-v2/components/EditPlanSection.tsx @@ -8,11 +8,12 @@ import { UsageModel, } from "@autumn/shared"; import { PencilSimpleIcon } from "@phosphor-icons/react"; -import { LayoutGroup, motion, type Transition } from "motion/react"; +import { LayoutGroup, motion } from "motion/react"; import { useMemo } from "react"; import { Button } from "@/components/v2/buttons/Button"; import { SheetSection } from "@/components/v2/sheets/SharedSheetComponents"; import { useOrg } from "@/hooks/common/useOrg"; +import { LAYOUT_TRANSITION } from "../constants/animationConstants"; import { useUpdateSubscriptionFormContext } from "../context/UpdateSubscriptionFormProvider"; import { PriceDisplay } from "./PriceDisplay"; import { SectionTitle } from "./SectionTitle"; @@ -20,11 +21,6 @@ import { SubscriptionItemRow } from "./SubscriptionItemRow"; import { TrialEditorRow } from "./TrialEditorRow"; import { VersionChangeRow } from "./VersionChangeRow"; -const LAYOUT_TRANSITION: Transition = { - duration: 0.35, - ease: [0.32, 0.72, 0, 1], -}; - export function EditPlanSection() { const { formContext, diff --git a/vite/src/components/forms/update-subscription-v2/components/SubscriptionItemRow.tsx b/vite/src/components/forms/update-subscription-v2/components/SubscriptionItemRow.tsx index b5c1f3103..86f65f366 100644 --- a/vite/src/components/forms/update-subscription-v2/components/SubscriptionItemRow.tsx +++ b/vite/src/components/forms/update-subscription-v2/components/SubscriptionItemRow.tsx @@ -9,6 +9,7 @@ import { CheckIcon, PencilSimpleIcon, } from "@phosphor-icons/react"; +import { AnimatePresence, motion } from "motion/react"; import { useState } from "react"; import { IconButton } from "@/components/v2/buttons/IconButton"; import { @@ -21,6 +22,7 @@ import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery"; import { cn } from "@/lib/utils"; import { PlanFeatureIcon } from "@/views/products/plan/components/plan-card/PlanFeatureIcon"; import { CustomDotIcon } from "@/views/products/plan/components/plan-card/PlanFeatureRow"; +import { FAST_TRANSITION } from "../constants/animationConstants"; import type { UseUpdateSubscriptionForm } from "../hooks/useUpdateSubscriptionForm"; import { getEditIcon } from "../utils/getEditIcon"; import { getItemRingClass } from "../utils/ringClassUtils"; @@ -243,42 +245,64 @@ export function SubscriptionItemRow({ (showPrepaidOutside || hasEditableEdit) && form && featureId && ( -
- {isEditingQuantity ? ( - <> - - {(field) => ( - - )} - - } - variant="skeleton" - size="sm" - className="text-green-600 dark:text-green-500 hover:text-green-700! dark:hover:text-green-400! hover:bg-black/5 dark:hover:bg-white/10" - onClick={() => setIsEditingQuantity(false)} - /> - - ) : ( - <> - - x{prepaidQuantity ?? 0} - - - - } - variant="skeleton" - size="sm" - className="text-t4 hover:text-t2 hover:bg-muted" - onClick={() => setIsEditingQuantity(true)} - /> - - Update prepaid quantity - - - )} -
+ + + {isEditingQuantity ? ( + + + {(field) => ( + + )} + + } + variant="skeleton" + size="sm" + className="text-green-600 dark:text-green-500 hover:text-green-700! dark:hover:text-green-400! hover:bg-black/5 dark:hover:bg-white/10" + onClick={() => setIsEditingQuantity(false)} + /> + + ) : ( + + + x{prepaidQuantity ?? 0} + + + + } + variant="skeleton" + size="sm" + className="text-t4 hover:text-t2 hover:bg-muted" + onClick={() => setIsEditingQuantity(true)} + /> + + Update prepaid quantity + + + )} + + )}
diff --git a/vite/src/components/forms/update-subscription-v2/components/TrialEditorRow.tsx b/vite/src/components/forms/update-subscription-v2/components/TrialEditorRow.tsx index 4465daf50..72e85a21f 100644 --- a/vite/src/components/forms/update-subscription-v2/components/TrialEditorRow.tsx +++ b/vite/src/components/forms/update-subscription-v2/components/TrialEditorRow.tsx @@ -2,10 +2,12 @@ import { type FreeTrialDuration, getTrialLengthInDays } from "@autumn/shared"; import { ArrowCounterClockwiseIcon, CalendarBlankIcon, + CheckIcon, PencilSimpleIcon, TrashIcon, } from "@phosphor-icons/react"; import { useStore } from "@tanstack/react-form"; +import { AnimatePresence, motion } from "motion/react"; import { useRef, useState } from "react"; import { IconButton } from "@/components/v2/buttons/IconButton"; import { @@ -14,6 +16,7 @@ import { TooltipTrigger, } from "@/components/v2/tooltips/Tooltip"; import { cn } from "@/lib/utils"; +import { FAST_TRANSITION } from "../constants/animationConstants"; import { formatTrialDuration, TRIAL_DURATION_OPTIONS, @@ -139,11 +142,10 @@ export function TrialEditorRow({ ); } - if ( - !isEditing && - !isAddingNewTrial && - (hasTrialValue || isCurrentlyTrialing) - ) { + const showDisplayMode = + !isEditing && !isAddingNewTrial && (hasTrialValue || isCurrentlyTrialing); + + if (showDisplayMode) { return (
) : null}
-
- } - variant="skeleton" - size="sm" - className="text-t4 hover:text-t2 hover:bg-muted" - onClick={() => { - setIsEditing(true); - setIsAddingNewTrial(false); - }} - /> -
+ + + + } + variant="skeleton" + size="sm" + className="text-t4 hover:text-t2 hover:bg-muted" + onClick={() => { + setIsEditing(true); + setIsAddingNewTrial(false); + }} + /> + + +
); } @@ -230,43 +248,73 @@ export function TrialEditorRow({ Free Trial
-
- - {(field) => ( - + + + + {(field) => ( + + )} + + + {(field) => ( + + )} + + } + variant="skeleton" + size="sm" + className="text-green-600 dark:text-green-500 hover:text-green-700! dark:hover:text-green-400! hover:bg-black/5 dark:hover:bg-white/10" + onClick={() => { + if (hasTrialValue) { + setIsEditing(false); + setIsAddingNewTrial(false); + } else { + handleClearTrial(); + } + }} /> - )} - - - {(field) => ( - } + variant="skeleton" + size="sm" + className="text-t4 hover:text-red-400!" + onClick={handleClearTrial} /> - )} - - } - variant="skeleton" - size="sm" - className="text-t4 hover:text-red-400" - onClick={handleClearTrial} - /> -
+ + +
); } diff --git a/vite/src/components/forms/update-subscription-v2/constants/animationConstants.ts b/vite/src/components/forms/update-subscription-v2/constants/animationConstants.ts new file mode 100644 index 000000000..308309694 --- /dev/null +++ b/vite/src/components/forms/update-subscription-v2/constants/animationConstants.ts @@ -0,0 +1,16 @@ +import type { Transition } from "motion/react"; + +export const FAST_TRANSITION: Transition = { + duration: 0.1, + ease: [0.32, 0.72, 0, 1], +}; + +export const TRANSITION: Transition = { + duration: 0.2, + ease: [0.32, 0.72, 0, 1], +}; + +export const LAYOUT_TRANSITION: Transition = { + duration: 0.35, + ease: [0.32, 0.72, 0, 1], +}; From 25319baa0bd1d882df0912599754960642d7b0d4 Mon Sep 17 00:00:00 2001 From: Ayush Rodrigues Date: Fri, 16 Jan 2026 19:52:49 +0000 Subject: [PATCH 19/34] style customer page --- .../components/CustomerPlansSection.tsx | 11 +++++++++++ .../customer-products/CustomerProductsTable.tsx | 4 +++- .../customer-purchases/CustomerPurchasesTable.tsx | 15 ++++----------- .../views/customers2/customer/CustomerView2.tsx | 6 ++---- .../components/product-list/ProductListTable.tsx | 2 +- 5 files changed, 21 insertions(+), 17 deletions(-) create mode 100644 vite/src/views/customers2/components/CustomerPlansSection.tsx diff --git a/vite/src/views/customers2/components/CustomerPlansSection.tsx b/vite/src/views/customers2/components/CustomerPlansSection.tsx new file mode 100644 index 000000000..92eacdacd --- /dev/null +++ b/vite/src/views/customers2/components/CustomerPlansSection.tsx @@ -0,0 +1,11 @@ +import { CustomerProductsTable } from "./table/customer-products/CustomerProductsTable"; +import { CustomerPurchasesTable } from "./table/customer-purchases/CustomerPurchasesTable"; + +export function CustomerPlansSection() { + return ( +
+ + +
+ ); +} diff --git a/vite/src/views/customers2/components/table/customer-products/CustomerProductsTable.tsx b/vite/src/views/customers2/components/table/customer-products/CustomerProductsTable.tsx index 50558683e..a7f8ecc2e 100644 --- a/vite/src/views/customers2/components/table/customer-products/CustomerProductsTable.tsx +++ b/vite/src/views/customers2/components/table/customer-products/CustomerProductsTable.tsx @@ -3,6 +3,7 @@ import { ArrowSquareOutIcon, PackageIcon } from "@phosphor-icons/react"; import type { Row } from "@tanstack/react-table"; import { useMemo, useState } from "react"; import { Table } from "@/components/general/table"; +import { SectionTag } from "@/components/v2/badges/SectionTag"; import { Button } from "@/components/v2/buttons/Button"; import { IconButton } from "@/components/v2/buttons/IconButton"; import { useSheetStore } from "@/hooks/stores/useSheetStore"; @@ -194,7 +195,7 @@ export function CustomerProductsTable() { - Subscriptions + Plans + Subscriptions diff --git a/vite/src/views/customers2/components/table/customer-purchases/CustomerPurchasesTable.tsx b/vite/src/views/customers2/components/table/customer-purchases/CustomerPurchasesTable.tsx index 7ff6107fa..2df0a8d2c 100644 --- a/vite/src/views/customers2/components/table/customer-purchases/CustomerPurchasesTable.tsx +++ b/vite/src/views/customers2/components/table/customer-purchases/CustomerPurchasesTable.tsx @@ -1,5 +1,5 @@ import type { Entity, FullCusProduct } from "@autumn/shared"; -import { ShoppingBagIcon } from "@phosphor-icons/react"; +import { SectionTag } from "@/components/v2/badges/SectionTag"; import type { Row } from "@tanstack/react-table"; import { useMemo, useState } from "react"; import { Table } from "@/components/general/table"; @@ -147,16 +147,9 @@ export function CustomerPurchasesTable() { }} > - - - - Purchases - - + + + Purchases diff --git a/vite/src/views/customers2/customer/CustomerView2.tsx b/vite/src/views/customers2/customer/CustomerView2.tsx index ee9c7425d..ef86c0f3f 100644 --- a/vite/src/views/customers2/customer/CustomerView2.tsx +++ b/vite/src/views/customers2/customer/CustomerView2.tsx @@ -12,10 +12,9 @@ import ErrorScreen from "@/views/general/ErrorScreen"; import LoadingScreen from "@/views/general/LoadingScreen"; import { useCusQuery } from "../../customers/customer/hooks/useCusQuery"; import { useCusReferralQuery } from "../../customers/customer/hooks/useCusReferralQuery"; +import { CustomerPlansSection } from "../components/CustomerPlansSection"; import { CustomerFeatureUsageTable } from "../components/table/customer-feature-usage/CustomerFeatureUsageTable"; import { CustomerInvoicesTable } from "../components/table/customer-invoices/CustomerInvoicesTable"; -import { CustomerProductsTable } from "../components/table/customer-products/CustomerProductsTable"; -import { CustomerPurchasesTable } from "../components/table/customer-purchases/CustomerPurchasesTable"; import { CustomerUsageAnalyticsTable } from "../components/table/customer-usage-analytics/CustomerUsageAnalyticsTable"; import { CustomerBreadcrumbs } from "./CustomerBreadcrumbs2"; import { CustomerContext } from "./CustomerContext"; @@ -101,8 +100,7 @@ export default function CustomerView2() { {/* */} {/* */}
- - + {/* */} {/* */} diff --git a/vite/src/views/products/products/components/product-list/ProductListTable.tsx b/vite/src/views/products/products/components/product-list/ProductListTable.tsx index 5db73db62..f4ca1e91b 100644 --- a/vite/src/views/products/products/components/product-list/ProductListTable.tsx +++ b/vite/src/views/products/products/components/product-list/ProductListTable.tsx @@ -217,7 +217,7 @@ export function ProductListTable() { - One-Time Plans + One-off Plans From c4d3a9bff92ddf331fac9a08c5a37826540e9875 Mon Sep 17 00:00:00 2001 From: Ayush Rodrigues Date: Fri, 16 Jan 2026 22:48:17 +0000 Subject: [PATCH 20/34] split complete --- .../components/CustomerPlansSection.tsx | 8 +- .../CustomerProductsTable.tsx | 151 ++++++++++------- .../CustomerPurchasesTable.tsx | 160 ------------------ 3 files changed, 95 insertions(+), 224 deletions(-) delete mode 100644 vite/src/views/customers2/components/table/customer-purchases/CustomerPurchasesTable.tsx diff --git a/vite/src/views/customers2/components/CustomerPlansSection.tsx b/vite/src/views/customers2/components/CustomerPlansSection.tsx index 92eacdacd..0974dcb1d 100644 --- a/vite/src/views/customers2/components/CustomerPlansSection.tsx +++ b/vite/src/views/customers2/components/CustomerPlansSection.tsx @@ -1,11 +1,5 @@ import { CustomerProductsTable } from "./table/customer-products/CustomerProductsTable"; -import { CustomerPurchasesTable } from "./table/customer-purchases/CustomerPurchasesTable"; export function CustomerPlansSection() { - return ( -
- - -
- ); + return ; } diff --git a/vite/src/views/customers2/components/table/customer-products/CustomerProductsTable.tsx b/vite/src/views/customers2/components/table/customer-products/CustomerProductsTable.tsx index a7f8ecc2e..17faf7a29 100644 --- a/vite/src/views/customers2/components/table/customer-products/CustomerProductsTable.tsx +++ b/vite/src/views/customers2/components/table/customer-products/CustomerProductsTable.tsx @@ -19,6 +19,47 @@ import { CustomerProductsColumns } from "./CustomerProductsColumns"; import { ShowExpiredActionButton } from "./ShowExpiredActionButton"; import { TransferProductDialog } from "./TransferProductDialog"; +// Shared scope column factory +function createScopeColumn( + entities: Entity[], + setEntityId: (id: string) => void, +) { + return { + header: "Scope", + accessorKey: "scope", + cell: ({ row }: { row: Row }) => { + const product = row.original; + + if (!product.internal_entity_id && !product.entity_id) { + return Customer; + } + + const entity = entities.find( + (e: Entity) => + e.internal_id === product.internal_entity_id || + e.id === product.entity_id, + ); + + if (!entity) return ; + + return ( + + ); + }, + }; +} + export function CustomerProductsTable() { const env = useEnv(); const { @@ -38,68 +79,32 @@ export function CustomerProductsTable() { null, ); const selectedItemId = useSheetStore((s) => s.itemId); + const setSheet = useSheetStore((s) => s.setSheet); useSavedViewsQuery(); useFullCusSearchQuery(); - // Build columns dynamically - include Scope column only when there are entity products + const hasPurchases = purchases.all.length > 0; + const hasEntityProducts = + subscriptions.hasEntityProducts || purchases.hasEntityProducts; + + // Build columns - same for both tables to keep them consistent const columns = useMemo(() => { - const baseColumns = [ - CustomerProductsColumns[0], // Name - ]; + const baseColumns = [CustomerProductsColumns[0]]; - // Add Scope column if there are entity products - if (subscriptions.hasEntityProducts) { - baseColumns.push({ - header: "Scope", - accessorKey: "scope", - cell: ({ row }: { row: Row }) => { - const product = row.original; - - // If no entity, it's customer-level - if (!product.internal_entity_id && !product.entity_id) { - return Customer; - } - - // Find the entity - const entity = customer.entities.find( - (e: Entity) => - e.internal_id === product.internal_entity_id || - e.id === product.entity_id, - ); - - if (!entity) return ; - - return ( - - ); - }, - }); + if (hasEntityProducts) { + baseColumns.push(createScopeColumn(customer.entities, setEntityId)); } - // Add remaining columns baseColumns.push( - CustomerProductsColumns[1], // Price - CustomerProductsColumns[2], // Status - CustomerProductsColumns[3], // Created At - CustomerProductsColumns[4], // Actions + CustomerProductsColumns[1], + CustomerProductsColumns[2], + CustomerProductsColumns[3], + CustomerProductsColumns[4], ); return baseColumns; - }, [subscriptions.hasEntityProducts, customer.entities, setEntityId]); - - const setSheet = useSheetStore((s) => s.setSheet); + }, [hasEntityProducts, customer.entities, setEntityId]); const handleCancelClick = (product: FullCusProduct) => { setSelectedProduct(product); @@ -124,7 +129,7 @@ export function CustomerProductsTable() { hasEntities, }; - const table = useCustomerTable({ + const subscriptionTable = useCustomerTable({ data: subscriptions.all, columns, options: { @@ -134,11 +139,18 @@ export function CustomerProductsTable() { }, }); - // Check if there are any purchases (for empty state messaging) - const hasPurchases = purchases.all.length > 0; + const purchaseTable = useCustomerTable({ + data: purchases.all, + columns, + options: { + globalFilterFn: "includesString", + enableGlobalFilter: true, + meta: tableMeta, + }, + }); const emptyStateChildren = - subscriptions.hasEntityProducts || hasPurchases ? ( + hasEntityProducts || hasPurchases ? ( "No subscriptions found" ) : ( <> @@ -164,7 +176,7 @@ export function CustomerProductsTable() { ); return ( -
+
{selectedProduct && ( <> )} + + {/* Subscriptions Table */} - Subscriptions + {hasPurchases && Subscriptions} + + {/* Purchases Table - only rendered if there are purchases */} + {hasPurchases && ( + + + Purchases + + + + + + )}
); } diff --git a/vite/src/views/customers2/components/table/customer-purchases/CustomerPurchasesTable.tsx b/vite/src/views/customers2/components/table/customer-purchases/CustomerPurchasesTable.tsx deleted file mode 100644 index 2df0a8d2c..000000000 --- a/vite/src/views/customers2/components/table/customer-purchases/CustomerPurchasesTable.tsx +++ /dev/null @@ -1,160 +0,0 @@ -import type { Entity, FullCusProduct } from "@autumn/shared"; -import { SectionTag } from "@/components/v2/badges/SectionTag"; -import type { Row } from "@tanstack/react-table"; -import { useMemo, useState } from "react"; -import { Table } from "@/components/general/table"; -import { Button } from "@/components/v2/buttons/Button"; -import { useSheetStore } from "@/hooks/stores/useSheetStore"; -import { useEntity } from "@/hooks/stores/useSubscriptionStore"; -import { useCustomerProductsData } from "@/views/customers2/hooks/useCustomerProductsData"; -import { useCustomerTable } from "@/views/customers2/hooks/useCustomerTable"; -import { CancelProductDialog } from "../customer-products/CancelProductDialog"; -import { CustomerProductsColumns } from "../customer-products/CustomerProductsColumns"; -import { TransferProductDialog } from "../customer-products/TransferProductDialog"; - -export function CustomerPurchasesTable() { - const { customer, isLoading, purchases, hasEntities } = - useCustomerProductsData(); - const { setEntityId } = useEntity(); - const selectedItemId = useSheetStore((s) => s.itemId); - const setSheet = useSheetStore((s) => s.setSheet); - const [cancelOpen, setCancelOpen] = useState(false); - const [transferOpen, setTransferOpen] = useState(false); - const [selectedProduct, setSelectedProduct] = useState( - null, - ); - - // Build columns dynamically - include Scope column only when there are entity products - const columns = useMemo(() => { - const baseColumns = [ - CustomerProductsColumns[0], // Name - ]; - - // Add Scope column if there are entity products - if (purchases.hasEntityProducts) { - baseColumns.push({ - header: "Scope", - accessorKey: "scope", - cell: ({ row }: { row: Row }) => { - const product = row.original; - - // If no entity, it's customer-level - if (!product.internal_entity_id && !product.entity_id) { - return Customer; - } - - // Find the entity - const entity = customer.entities.find( - (e: Entity) => - e.internal_id === product.internal_entity_id || - e.id === product.entity_id, - ); - - if (!entity) return ; - - return ( - - ); - }, - }); - } - - // Add remaining columns - baseColumns.push( - CustomerProductsColumns[1], // Price - CustomerProductsColumns[2], // Status - CustomerProductsColumns[3], // Created At - CustomerProductsColumns[4], // Actions (empty for purchases, but keeps layout consistent) - ); - - return baseColumns; - }, [purchases.hasEntityProducts, customer.entities, setEntityId]); - - const handleCancelClick = (product: FullCusProduct) => { - setSelectedProduct(product); - setCancelOpen(true); - }; - - const handleTransferClick = (product: FullCusProduct) => { - setSelectedProduct(product); - setTransferOpen(true); - }; - - const tableMeta = { - onCancelClick: handleCancelClick, - onTransferClick: handleTransferClick, - hasEntities, - }; - - const table = useCustomerTable({ - data: purchases.all, - columns, - options: { - globalFilterFn: "includesString", - enableGlobalFilter: true, - meta: tableMeta, - }, - }); - - const handleRowClick = (cusProduct: FullCusProduct) => { - setSheet({ - type: "subscription-detail", - itemId: cusProduct.id, - }); - }; - - const hasPurchases = purchases.all.length > 0; - - if (!hasPurchases) return null; - - return ( -
- {selectedProduct && ( - <> - - - - )} - - - - - Purchases - - - - - -
- ); -} From ad4030b0a361ee5ebf4b9a1c31c3d6fdf73bf974 Mon Sep 17 00:00:00 2001 From: Ayush Rodrigues Date: Fri, 16 Jan 2026 22:53:36 +0000 Subject: [PATCH 21/34] lil fix --- .../components/product-list/ProductListTable.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/vite/src/views/products/products/components/product-list/ProductListTable.tsx b/vite/src/views/products/products/components/product-list/ProductListTable.tsx index f4ca1e91b..af459479b 100644 --- a/vite/src/views/products/products/components/product-list/ProductListTable.tsx +++ b/vite/src/views/products/products/components/product-list/ProductListTable.tsx @@ -135,7 +135,10 @@ export function ProductListTable() { // For archived view, always show table structure even if empty // For non-archived view, show EmptyState when no plans exist const showTableStructure = - queryStates.showArchivedProducts || hasRecurringBasePlans; + queryStates.showArchivedProducts || + hasRecurringBasePlans || + hasRecurringAddOns || + hasOneTimePlans; return (
@@ -150,7 +153,9 @@ export function ProductListTable() { enableSorting, isLoading: isCountsLoading, getRowHref, - emptyStateText: "You haven't archived any plans yet.", + emptyStateText: queryStates.showArchivedProducts + ? "You haven't archived any plans yet" + : "You haven't created any recurring plans yet", rowClassName: "h-10", }} > From 27aa5789dc90ee36d5af1af469a34a947f89d72d Mon Sep 17 00:00:00 2001 From: Ayush Rodrigues Date: Fri, 16 Jan 2026 22:55:15 +0000 Subject: [PATCH 22/34] copy change --- .../products/components/product-list/ProductListTable.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vite/src/views/products/products/components/product-list/ProductListTable.tsx b/vite/src/views/products/products/components/product-list/ProductListTable.tsx index af459479b..61468a8f7 100644 --- a/vite/src/views/products/products/components/product-list/ProductListTable.tsx +++ b/vite/src/views/products/products/components/product-list/ProductListTable.tsx @@ -155,7 +155,7 @@ export function ProductListTable() { getRowHref, emptyStateText: queryStates.showArchivedProducts ? "You haven't archived any plans yet" - : "You haven't created any recurring plans yet", + : "You haven't created any main plans yet", rowClassName: "h-10", }} > From fcd96ca2af6e6596c1e9fd7d4688875c07f13874 Mon Sep 17 00:00:00 2001 From: Ayush Rodrigues Date: Fri, 16 Jan 2026 22:56:02 +0000 Subject: [PATCH 23/34] typo --- shared/utils/common/formatUtils/formatInterval.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/utils/common/formatUtils/formatInterval.ts b/shared/utils/common/formatUtils/formatInterval.ts index 3572d99d3..61108bc5d 100644 --- a/shared/utils/common/formatUtils/formatInterval.ts +++ b/shared/utils/common/formatUtils/formatInterval.ts @@ -17,7 +17,7 @@ export const formatInterval = ({ // Handle one_off (show "one time") if (interval === BillingInterval.OneOff) { - return "one times"; + return "one-off"; } // Handle lifetime (no interval string) From ad52c40279f5489e52f6da7d329a6b27dd39d82e Mon Sep 17 00:00:00 2001 From: Ayush Rodrigues Date: Fri, 16 Jan 2026 22:59:50 +0000 Subject: [PATCH 24/34] remove unused prop --- vite/src/components/v2/checkboxes/AreaCheckbox.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/vite/src/components/v2/checkboxes/AreaCheckbox.tsx b/vite/src/components/v2/checkboxes/AreaCheckbox.tsx index 0e74e288b..39b904aa6 100644 --- a/vite/src/components/v2/checkboxes/AreaCheckbox.tsx +++ b/vite/src/components/v2/checkboxes/AreaCheckbox.tsx @@ -15,7 +15,6 @@ interface AreaCheckboxProps { checked?: boolean; onCheckedChange?: (checked: boolean) => void; title: string; - tooltip?: string; disabled?: boolean; disabledReason?: string; hide?: boolean; From 464575f2235ea9d1d157d7e8b2568736c377a161 Mon Sep 17 00:00:00 2001 From: Ayush Rodrigues Date: Sun, 18 Jan 2026 14:43:13 +0000 Subject: [PATCH 25/34] made it clear template prompts are templates --- vite/src/views/onboarding4/AIChatView.tsx | 4 +- .../components/TemplatePrompts.tsx | 47 ++++++++++++------- 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/vite/src/views/onboarding4/AIChatView.tsx b/vite/src/views/onboarding4/AIChatView.tsx index 4dea32972..cac16459a 100644 --- a/vite/src/views/onboarding4/AIChatView.tsx +++ b/vite/src/views/onboarding4/AIChatView.tsx @@ -355,14 +355,14 @@ export function AIChatView({ onBack }: AIChatViewProps) { initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.4, ease: "easeOut", delay: 0.1 }} - className="flex flex-col items-center mt-6" + className="flex flex-col items-center mt-3 w-full max-w-2xl" > diff --git a/vite/src/views/onboarding4/components/TemplatePrompts.tsx b/vite/src/views/onboarding4/components/TemplatePrompts.tsx index a3d550a38..163ea31b7 100644 --- a/vite/src/views/onboarding4/components/TemplatePrompts.tsx +++ b/vite/src/views/onboarding4/components/TemplatePrompts.tsx @@ -7,23 +7,36 @@ interface TemplatePromptsProps { export function TemplatePrompts({ onSelectTemplate }: TemplatePromptsProps) { return ( -
- {PRICING_TEMPLATE_PROMPTS.map((template) => ( - - ))} +
+ {/* Header with decorative lines */} + +
+
+ Copy a template +
+
+ + {/* Template cards */} +
+ {PRICING_TEMPLATE_PROMPTS.map((template) => ( + + ))} +
); } From de180fa43758d54c55b6bd159caae3a0bdbf7bea Mon Sep 17 00:00:00 2001 From: Ayush Rodrigues Date: Sun, 18 Jan 2026 23:37:27 +0000 Subject: [PATCH 26/34] lots of fixes --- .cursor/debug.log | 203 +++++++++++++ .../internal/customers/CusSearchService.ts | 21 +- shared/utils/productDisplayUtils.ts | 268 +++++++++++------- .../compareProductUtils/compareItemUtils.ts | 16 +- .../components/general/table/table-header.tsx | 4 +- .../InlinePlanEditor.tsx | 17 +- vite/src/components/v2/selects/Select.tsx | 4 +- .../v2/sheet-overlay/SheetOverlay.tsx | 105 +++++++ .../v2/sheets/SharedSheetComponents.tsx | 23 +- vite/src/components/v2/sheets/Sheet.tsx | 23 +- vite/src/hooks/stores/useSheetStore.ts | 6 +- .../filter-dropdown/CustomersFilterButton.tsx | 2 +- .../filter-dropdown/FilterStatusSubMenu.tsx | 16 +- .../filter-dropdown/ProductsSubMenu.tsx | 228 ++++++++------- .../components/filter-dropdown/SavedViews.tsx | 38 ++- .../filter-dropdown/SavedViewsDropdown.tsx | 5 +- .../analytics/hooks/useAnalyticsData.tsx | 4 +- .../hooks/useCustomersQueryStates.tsx | 12 +- .../sheets/SubscriptionDetailSheet.tsx | 1 + .../customer-list/CustomerListColumns.tsx | 3 + .../CustomerListFilterButton.tsx | 10 +- .../customer-list/CustomerListSearchBar.tsx | 6 +- .../table/customer-list/CustomerListTable.tsx | 10 +- .../CustomerProductsColumns.tsx | 1 + .../CustomerProductsStatus.tsx | 30 +- vite/src/views/main-sidebar/MainSidebar.tsx | 2 +- .../src/views/onboarding4/OnboardingGuide.tsx | 4 +- .../components/UpdateFeatureSheet.tsx | 8 + .../components/UpdateCreditSystemSheet.tsx | 8 + .../products/plan/components/PlanEditor.tsx | 95 +------ .../plan/components/SaveChangesBar.tsx | 6 +- .../EditPlanFeatureSheet.tsx | 56 +++- .../products/plan/utils/getDefaultItem.ts | 17 +- .../product-list/ProductListColumns.tsx | 2 +- .../product-list/ProductListTable.tsx | 12 +- 35 files changed, 828 insertions(+), 438 deletions(-) create mode 100644 .cursor/debug.log create mode 100644 vite/src/components/v2/sheet-overlay/SheetOverlay.tsx diff --git a/.cursor/debug.log b/.cursor/debug.log new file mode 100644 index 000000000..348613480 --- /dev/null +++ b/.cursor/debug.log @@ -0,0 +1,203 @@ +{"location":"useProductSync.ts:useEffect","message":"sync effect triggered","data":{"isNewProduct":true,"isVersionChanged":true,"isProductUpdated":true,"productId":"free_plan","productVersion":1,"willUpdate":true},"timestamp":1768767206353,"sessionId":"debug-session","hypothesisId":"H4"} +{"location":"useProductSync.ts:setBaseProduct","message":"updating baseProduct","data":{"frontendProductItemsCount":1,"frontendProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38IUPp9xbsN9aTLxJWylhmhpSMd"}]},"timestamp":1768767206353,"sessionId":"debug-session","hypothesisId":"H4"} +{"location":"useProductSync.ts:useEffect","message":"sync effect triggered","data":{"isNewProduct":false,"isVersionChanged":false,"isProductUpdated":false,"productId":"free_plan","productVersion":1,"lastProductId":"free_plan","lastProductVersion":1,"willUpdate":false},"timestamp":1768767206354,"sessionId":"debug-session","hypothesisId":"H4"} +{"location":"useProductSync.ts:useEffect","message":"sync effect triggered","data":{"isNewProduct":false,"isVersionChanged":false,"isProductUpdated":false,"productId":"free_plan","productVersion":1,"lastProductId":"free_plan","lastProductVersion":1,"willUpdate":false},"timestamp":1768767206365,"sessionId":"debug-session","hypothesisId":"H4"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38IUPp9xbsN9aTLxJWylhmhpSMd"}],"baseProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38IUPp9xbsN9aTLxJWylhmhpSMd"}]},"timestamp":1768767206361,"sessionId":"debug-session","hypothesisId":"H1-H3"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38IUPp9xbsN9aTLxJWylhmhpSMd"}],"baseProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38IUPp9xbsN9aTLxJWylhmhpSMd"}]},"timestamp":1768767206361,"sessionId":"debug-session","hypothesisId":"H1-H3"} +{"location":"useProductSync.ts:useEffect","message":"sync effect triggered","data":{"isNewProduct":false,"isVersionChanged":false,"isProductUpdated":true,"productId":"free_plan","productVersion":1,"lastProductId":"free_plan","lastProductVersion":1,"willUpdate":true},"timestamp":1768767207732,"sessionId":"debug-session","hypothesisId":"H4"} +{"location":"useProductSync.ts:setBaseProduct","message":"updating baseProduct","data":{"frontendProductItemsCount":1,"frontendProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38IUPp9xbsN9aTLxJWylhmhpSMd"}]},"timestamp":1768767207732,"sessionId":"debug-session","hypothesisId":"H4"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38IUPp9xbsN9aTLxJWylhmhpSMd"}],"baseProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38IUPp9xbsN9aTLxJWylhmhpSMd"}]},"timestamp":1768767207732,"sessionId":"debug-session","hypothesisId":"H1-H3"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38IUPp9xbsN9aTLxJWylhmhpSMd"}],"baseProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38IUPp9xbsN9aTLxJWylhmhpSMd"}]},"timestamp":1768767207733,"sessionId":"debug-session","hypothesisId":"H1-H3"} +{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"analytics","item1Interval":null,"item1IncludedUsage":null,"items2Count":1,"items2FeatureIds":["messages"]},"timestamp":1768767208701,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38IUPp9xbsN9aTLxJWylhmhpSMd"},{"feature_id":"analytics","interval":null,"included_usage":null,"reset_usage_when_enabled":true}],"baseProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38IUPp9xbsN9aTLxJWylhmhpSMd"}]},"timestamp":1768767208701,"sessionId":"debug-session","hypothesisId":"H1-H3"} +{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"analytics","item1Interval":null,"item1IncludedUsage":null,"items2Count":1,"items2FeatureIds":["messages"]},"timestamp":1768767208701,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"analytics","item1Interval":null,"item1IncludedUsage":null,"items2Count":1,"items2FeatureIds":["messages"]},"timestamp":1768767208701,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38IUPp9xbsN9aTLxJWylhmhpSMd"},{"feature_id":"analytics","interval":null,"included_usage":null,"reset_usage_when_enabled":true}],"baseProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38IUPp9xbsN9aTLxJWylhmhpSMd"}]},"timestamp":1768767208701,"sessionId":"debug-session","hypothesisId":"H1-H3"} +{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"analytics","item1Interval":null,"item1IncludedUsage":null,"items2Count":1,"items2FeatureIds":["messages"]},"timestamp":1768767208702,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"useProductSync.ts:useEffect","message":"sync effect triggered","data":{"isNewProduct":false,"isVersionChanged":false,"isProductUpdated":false,"productId":"free_plan","productVersion":1,"lastProductId":"free_plan","lastProductVersion":1,"willUpdate":false},"timestamp":1768767208711,"sessionId":"debug-session","hypothesisId":"H4"} +{"location":"SaveChangesBar.tsx:handleSaveClicked:start","message":"save clicked - before save","data":{"productId":"free_plan","itemsCount":2,"items":[{"feature_id":"messages","interval":null,"included_usage":10},{"feature_id":"analytics","interval":null,"included_usage":null}]},"timestamp":1768767210156,"sessionId":"debug-session","hypothesisId":"H2"} +{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"analytics","item1Interval":null,"item1IncludedUsage":null,"items2Count":1,"items2FeatureIds":["messages"]},"timestamp":1768767217153,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"useProductSync.ts:useEffect","message":"sync effect triggered","data":{"isNewProduct":false,"isVersionChanged":true,"isProductUpdated":true,"productId":"free_plan","productVersion":2,"lastProductId":"free_plan","lastProductVersion":1,"willUpdate":true},"timestamp":1768767220542,"sessionId":"debug-session","hypothesisId":"H4"} +{"location":"useProductSync.ts:setBaseProduct","message":"updating baseProduct","data":{"frontendProductItemsCount":2,"frontendProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"},{"feature_id":"analytics","entitlement_id":"ent_38RfHfo63jeH40j8nB7K1BAHLeT"}]},"timestamp":1768767220542,"sessionId":"debug-session","hypothesisId":"H4"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"},{"feature_id":"analytics","entitlement_id":"ent_38RfHfo63jeH40j8nB7K1BAHLeT"}],"baseProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"},{"feature_id":"analytics","entitlement_id":"ent_38RfHfo63jeH40j8nB7K1BAHLeT"}]},"timestamp":1768767220548,"sessionId":"debug-session","hypothesisId":"H1-H3"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"},{"feature_id":"analytics","entitlement_id":"ent_38RfHfo63jeH40j8nB7K1BAHLeT"}],"baseProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"},{"feature_id":"analytics","entitlement_id":"ent_38RfHfo63jeH40j8nB7K1BAHLeT"}]},"timestamp":1768767220548,"sessionId":"debug-session","hypothesisId":"H1-H3"} +{"location":"useProductSync.ts:useEffect","message":"sync effect triggered","data":{"isNewProduct":false,"isVersionChanged":false,"isProductUpdated":false,"productId":"free_plan","productVersion":2,"lastProductId":"free_plan","lastProductVersion":2,"willUpdate":false},"timestamp":1768767220550,"sessionId":"debug-session","hypothesisId":"H4"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}],"baseProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"},{"feature_id":"analytics","entitlement_id":"ent_38RfHfo63jeH40j8nB7K1BAHLeT"}]},"timestamp":1768767226001,"sessionId":"debug-session","hypothesisId":"H1-H3"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}],"baseProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"},{"feature_id":"analytics","entitlement_id":"ent_38RfHfo63jeH40j8nB7K1BAHLeT"}]},"timestamp":1768767226002,"sessionId":"debug-session","hypothesisId":"H1-H3"} +{"location":"useProductSync.ts:useEffect","message":"sync effect triggered","data":{"isNewProduct":false,"isVersionChanged":false,"isProductUpdated":false,"productId":"free_plan","productVersion":2,"lastProductId":"free_plan","lastProductVersion":2,"willUpdate":false},"timestamp":1768767226003,"sessionId":"debug-session","hypothesisId":"H4"} +{"location":"SaveChangesBar.tsx:handleSaveClicked:start","message":"save clicked - before save","data":{"productId":"free_plan","itemsCount":1,"items":[{"feature_id":"messages","interval":null,"included_usage":10}]},"timestamp":1768767226796,"sessionId":"debug-session","hypothesisId":"H2"} +{"location":"SaveChangesBar.tsx:onSuccess","message":"save onSuccess - about to refetch","data":{},"timestamp":1768767232230,"sessionId":"debug-session","hypothesisId":"H4"} +{"location":"useProductSync.ts:useEffect","message":"sync effect triggered","data":{"isNewProduct":false,"isVersionChanged":false,"isProductUpdated":true,"productId":"free_plan","productVersion":2,"lastProductId":"free_plan","lastProductVersion":2,"willUpdate":true},"timestamp":1768767233610,"sessionId":"debug-session","hypothesisId":"H4"} +{"location":"useProductSync.ts:setBaseProduct","message":"updating baseProduct","data":{"frontendProductItemsCount":1,"frontendProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767233610,"sessionId":"debug-session","hypothesisId":"H4"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}],"baseProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767233611,"sessionId":"debug-session","hypothesisId":"H1-H3"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}],"baseProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767233611,"sessionId":"debug-session","hypothesisId":"H1-H3"} +{"location":"SaveChangesBar.tsx:onSuccess:afterRefetch","message":"refetch completed","data":{},"timestamp":1768767235270,"sessionId":"debug-session","hypothesisId":"H4"} +{"location":"SaveChangesBar.tsx:handleSaveClicked:end","message":"save completed","data":{"result":true},"timestamp":1768767235270,"sessionId":"debug-session","hypothesisId":"H2"} +{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"analytics","item1Interval":null,"item1IncludedUsage":null,"items2Count":1,"items2FeatureIds":["messages"]},"timestamp":1768767236608,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"},{"feature_id":"analytics","interval":null,"included_usage":null,"reset_usage_when_enabled":true}],"baseProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767236608,"sessionId":"debug-session","hypothesisId":"H1-H3"} +{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"analytics","item1Interval":null,"item1IncludedUsage":null,"items2Count":1,"items2FeatureIds":["messages"]},"timestamp":1768767236608,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"analytics","item1Interval":null,"item1IncludedUsage":null,"items2Count":1,"items2FeatureIds":["messages"]},"timestamp":1768767236608,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"},{"feature_id":"analytics","interval":null,"included_usage":null,"reset_usage_when_enabled":true}],"baseProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767236608,"sessionId":"debug-session","hypothesisId":"H1-H3"} +{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"analytics","item1Interval":null,"item1IncludedUsage":null,"items2Count":1,"items2FeatureIds":["messages"]},"timestamp":1768767236609,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"useProductSync.ts:useEffect","message":"sync effect triggered","data":{"isNewProduct":false,"isVersionChanged":false,"isProductUpdated":false,"productId":"free_plan","productVersion":2,"lastProductId":"free_plan","lastProductVersion":2,"willUpdate":false},"timestamp":1768767236614,"sessionId":"debug-session","hypothesisId":"H4"} +{"location":"SaveChangesBar.tsx:handleSaveClicked:start","message":"save clicked - before save","data":{"productId":"free_plan","itemsCount":2,"items":[{"feature_id":"messages","interval":null,"included_usage":10},{"feature_id":"analytics","interval":null,"included_usage":null}]},"timestamp":1768767237868,"sessionId":"debug-session","hypothesisId":"H2"} +{"location":"SaveChangesBar.tsx:onSuccess","message":"save onSuccess - about to refetch","data":{},"timestamp":1768767242801,"sessionId":"debug-session","hypothesisId":"H4"} +{"location":"useProductSync.ts:useEffect","message":"sync effect triggered","data":{"isNewProduct":false,"isVersionChanged":false,"isProductUpdated":true,"productId":"free_plan","productVersion":2,"lastProductId":"free_plan","lastProductVersion":2,"willUpdate":true},"timestamp":1768767244180,"sessionId":"debug-session","hypothesisId":"H4"} +{"location":"useProductSync.ts:setBaseProduct","message":"updating baseProduct","data":{"frontendProductItemsCount":2,"frontendProductItems":[{"feature_id":"analytics","entitlement_id":"ent_38RfKZrFZYfWslXTJOSbeoBjzkV"},{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767244181,"sessionId":"debug-session","hypothesisId":"H4"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"},{"feature_id":"analytics","interval":null,"included_usage":null,"reset_usage_when_enabled":true}],"baseProductItems":[{"feature_id":"analytics","entitlement_id":"ent_38RfKZrFZYfWslXTJOSbeoBjzkV"},{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767244181,"sessionId":"debug-session","hypothesisId":"H1-H3"} +{"location":"compareItemUtils.ts:featureItemsAreSame","message":"feature items comparison failed","data":{"featureId":"analytics","failedChecks":[{"check":"reset_usage_when_enabled","message":"Reset usage when enabled different: true !== false"}],"item1ResetUsage":true,"item2ResetUsage":false,"item1Interval":null,"item1IncludedUsage":null},"timestamp":1768767244181,"sessionId":"debug-session","hypothesisId":"H3"} +{"location":"compareItemUtils.ts:featureItemsAreSame","message":"feature items comparison failed","data":{"featureId":"analytics","failedChecks":[{"check":"reset_usage_when_enabled","message":"Reset usage when enabled different: true !== false"}],"item1ResetUsage":true,"item2ResetUsage":false,"item1Interval":null,"item1IncludedUsage":null},"timestamp":1768767244181,"sessionId":"debug-session","hypothesisId":"H3"} +{"location":"compareProductUtils.ts:productsAreSame:itemsDifferent","message":"items not same after comparison","data":{"item1":"{\"feature_id\":\"analytics\",\"feature_type\":\"static\",\"included_usage\":null,\"interval\":null,\"price\":null,\"tiers\":null,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":true,\"config\":{\"on_increase\":\"prorate_immediately\",\"on_decrease\":\"prorate_immediately\"}}","item2":"{\"feature_id\":\"analytics\",\"entity_feature_id\":null,\"entitlement_id\":\"ent_38RfKZrFZYfWslXTJOSbeoBjzkV\",\"feature_type\":\"static\",\"reset_usage_when_enabled\":false,\"config\":{\"on_increase\":\"prorate_immediately\",\"on_decrease\":\"prorate_immediately\"}}","featureId":"analytics"},"timestamp":1768767244181,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"compareProductUtils.ts:productsAreSame:itemsDifferent","message":"items not same after comparison","data":{"item1":"{\"feature_id\":\"analytics\",\"feature_type\":\"static\",\"included_usage\":null,\"interval\":null,\"price\":null,\"tiers\":null,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":true,\"config\":{\"on_increase\":\"prorate_immediately\",\"on_decrease\":\"prorate_immediately\"}}","item2":"{\"feature_id\":\"analytics\",\"entity_feature_id\":null,\"entitlement_id\":\"ent_38RfKZrFZYfWslXTJOSbeoBjzkV\",\"feature_type\":\"static\",\"reset_usage_when_enabled\":false,\"config\":{\"on_increase\":\"prorate_immediately\",\"on_decrease\":\"prorate_immediately\"}}","featureId":"analytics"},"timestamp":1768767244182,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"compareItemUtils.ts:featureItemsAreSame","message":"feature items comparison failed","data":{"featureId":"analytics","failedChecks":[{"check":"reset_usage_when_enabled","message":"Reset usage when enabled different: true !== false"}],"item1ResetUsage":true,"item2ResetUsage":false,"item1Interval":null,"item1IncludedUsage":null},"timestamp":1768767244182,"sessionId":"debug-session","hypothesisId":"H3"} +{"location":"compareProductUtils.ts:productsAreSame:itemsDifferent","message":"items not same after comparison","data":{"item1":"{\"feature_id\":\"analytics\",\"feature_type\":\"static\",\"included_usage\":null,\"interval\":null,\"price\":null,\"tiers\":null,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":true,\"config\":{\"on_increase\":\"prorate_immediately\",\"on_decrease\":\"prorate_immediately\"}}","item2":"{\"feature_id\":\"analytics\",\"entity_feature_id\":null,\"entitlement_id\":\"ent_38RfKZrFZYfWslXTJOSbeoBjzkV\",\"feature_type\":\"static\",\"reset_usage_when_enabled\":false,\"config\":{\"on_increase\":\"prorate_immediately\",\"on_decrease\":\"prorate_immediately\"}}","featureId":"analytics"},"timestamp":1768767244182,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"},{"feature_id":"analytics","interval":null,"included_usage":null,"reset_usage_when_enabled":true}],"baseProductItems":[{"feature_id":"analytics","entitlement_id":"ent_38RfKZrFZYfWslXTJOSbeoBjzkV"},{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767244182,"sessionId":"debug-session","hypothesisId":"H1-H3"} +{"location":"compareItemUtils.ts:featureItemsAreSame","message":"feature items comparison failed","data":{"featureId":"analytics","failedChecks":[{"check":"reset_usage_when_enabled","message":"Reset usage when enabled different: true !== false"}],"item1ResetUsage":true,"item2ResetUsage":false,"item1Interval":null,"item1IncludedUsage":null},"timestamp":1768767244182,"sessionId":"debug-session","hypothesisId":"H3"} +{"location":"compareProductUtils.ts:productsAreSame:itemsDifferent","message":"items not same after comparison","data":{"item1":"{\"feature_id\":\"analytics\",\"feature_type\":\"static\",\"included_usage\":null,\"interval\":null,\"price\":null,\"tiers\":null,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":true,\"config\":{\"on_increase\":\"prorate_immediately\",\"on_decrease\":\"prorate_immediately\"}}","item2":"{\"feature_id\":\"analytics\",\"entity_feature_id\":null,\"entitlement_id\":\"ent_38RfKZrFZYfWslXTJOSbeoBjzkV\",\"feature_type\":\"static\",\"reset_usage_when_enabled\":false,\"config\":{\"on_increase\":\"prorate_immediately\",\"on_decrease\":\"prorate_immediately\"}}","featureId":"analytics"},"timestamp":1768767244182,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"SaveChangesBar.tsx:onSuccess:afterRefetch","message":"refetch completed","data":{},"timestamp":1768767245839,"sessionId":"debug-session","hypothesisId":"H4"} +{"location":"SaveChangesBar.tsx:handleSaveClicked:end","message":"save completed","data":{"result":true},"timestamp":1768767245839,"sessionId":"debug-session","hypothesisId":"H2"} +{"location":"compareItemUtils.ts:featureItemsAreSame","message":"feature items comparison failed","data":{"featureId":"analytics","failedChecks":[{"check":"reset_usage_when_enabled","message":"Reset usage when enabled different: true !== false"}],"item1ResetUsage":true,"item2ResetUsage":false,"item1Interval":null,"item1IncludedUsage":null},"timestamp":1768767394297,"sessionId":"debug-session","hypothesisId":"H3"} +{"location":"compareItemUtils.ts:featureItemsAreSame","message":"feature items comparison failed","data":{"featureId":"analytics","failedChecks":[{"check":"reset_usage_when_enabled","message":"Reset usage when enabled different: true !== false"}],"item1ResetUsage":true,"item2ResetUsage":false,"item1Interval":null,"item1IncludedUsage":null},"timestamp":1768767394298,"sessionId":"debug-session","hypothesisId":"H3"} +{"location":"compareProductUtils.ts:productsAreSame:itemsDifferent","message":"items not same after comparison","data":{"item1":"{\"feature_id\":\"analytics\",\"feature_type\":\"static\",\"included_usage\":null,\"interval\":null,\"price\":null,\"tiers\":null,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":true,\"config\":{\"on_increase\":\"prorate_immediately\",\"on_decrease\":\"prorate_immediately\"}}","item2":"{\"feature_id\":\"analytics\",\"entity_feature_id\":null,\"entitlement_id\":\"ent_38RfKZrFZYfWslXTJOSbeoBjzkV\",\"feature_type\":\"static\",\"reset_usage_when_enabled\":false,\"config\":{\"on_increase\":\"prorate_immediately\",\"on_decrease\":\"prorate_immediately\"}}","featureId":"analytics"},"timestamp":1768767394297,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"compareProductUtils.ts:productsAreSame:itemsDifferent","message":"items not same after comparison","data":{"item1":"{\"feature_id\":\"analytics\",\"feature_type\":\"static\",\"included_usage\":null,\"interval\":null,\"price\":null,\"tiers\":null,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":true,\"config\":{\"on_increase\":\"prorate_immediately\",\"on_decrease\":\"prorate_immediately\"}}","item2":"{\"feature_id\":\"analytics\",\"entity_feature_id\":null,\"entitlement_id\":\"ent_38RfKZrFZYfWslXTJOSbeoBjzkV\",\"feature_type\":\"static\",\"reset_usage_when_enabled\":false,\"config\":{\"on_increase\":\"prorate_immediately\",\"on_decrease\":\"prorate_immediately\"}}","featureId":"analytics"},"timestamp":1768767394298,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"},{"feature_id":"analytics","interval":null,"included_usage":null,"reset_usage_when_enabled":true}],"baseProductItems":[{"feature_id":"analytics","entitlement_id":"ent_38RfKZrFZYfWslXTJOSbeoBjzkV"},{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767394297,"sessionId":"debug-session","hypothesisId":"H1-H3"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"},{"feature_id":"analytics","interval":null,"included_usage":null,"reset_usage_when_enabled":true}],"baseProductItems":[{"feature_id":"analytics","entitlement_id":"ent_38RfKZrFZYfWslXTJOSbeoBjzkV"},{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767394298,"sessionId":"debug-session","hypothesisId":"H1-H3"} +{"location":"compareItemUtils.ts:featureItemsAreSame","message":"feature items comparison failed","data":{"featureId":"analytics","failedChecks":[{"check":"reset_usage_when_enabled","message":"Reset usage when enabled different: true !== false"}],"item1ResetUsage":true,"item2ResetUsage":false,"item1Interval":null,"item1IncludedUsage":null},"timestamp":1768767394298,"sessionId":"debug-session","hypothesisId":"H3"} +{"location":"compareProductUtils.ts:productsAreSame:itemsDifferent","message":"items not same after comparison","data":{"item1":"{\"feature_id\":\"analytics\",\"feature_type\":\"static\",\"included_usage\":null,\"interval\":null,\"price\":null,\"tiers\":null,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":true,\"config\":{\"on_increase\":\"prorate_immediately\",\"on_decrease\":\"prorate_immediately\"}}","item2":"{\"feature_id\":\"analytics\",\"entity_feature_id\":null,\"entitlement_id\":\"ent_38RfKZrFZYfWslXTJOSbeoBjzkV\",\"feature_type\":\"static\",\"reset_usage_when_enabled\":false,\"config\":{\"on_increase\":\"prorate_immediately\",\"on_decrease\":\"prorate_immediately\"}}","featureId":"analytics"},"timestamp":1768767394298,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"compareItemUtils.ts:featureItemsAreSame","message":"feature items comparison failed","data":{"featureId":"analytics","failedChecks":[{"check":"reset_usage_when_enabled","message":"Reset usage when enabled different: true !== false"}],"item1ResetUsage":true,"item2ResetUsage":false,"item1Interval":null,"item1IncludedUsage":null},"timestamp":1768767394297,"sessionId":"debug-session","hypothesisId":"H3"} +{"location":"compareProductUtils.ts:productsAreSame:itemsDifferent","message":"items not same after comparison","data":{"item1":"{\"feature_id\":\"analytics\",\"feature_type\":\"static\",\"included_usage\":null,\"interval\":null,\"price\":null,\"tiers\":null,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":true,\"config\":{\"on_increase\":\"prorate_immediately\",\"on_decrease\":\"prorate_immediately\"}}","item2":"{\"feature_id\":\"analytics\",\"entity_feature_id\":null,\"entitlement_id\":\"ent_38RfKZrFZYfWslXTJOSbeoBjzkV\",\"feature_type\":\"static\",\"reset_usage_when_enabled\":false,\"config\":{\"on_increase\":\"prorate_immediately\",\"on_decrease\":\"prorate_immediately\"}}","featureId":"analytics"},"timestamp":1768767394297,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"useProductSync.ts:useEffect","message":"sync effect triggered","data":{"isNewProduct":true,"isVersionChanged":true,"isProductUpdated":true,"productId":"free_plan","productVersion":2,"willUpdate":true},"timestamp":1768767394313,"sessionId":"debug-session","hypothesisId":"H4"} +{"location":"useProductSync.ts:setBaseProduct","message":"updating baseProduct","data":{"frontendProductItemsCount":2,"frontendProductItems":[{"feature_id":"analytics","entitlement_id":"ent_38RfKZrFZYfWslXTJOSbeoBjzkV"},{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767394314,"sessionId":"debug-session","hypothesisId":"H4"} +{"location":"useProductSync.ts:useEffect","message":"sync effect triggered","data":{"isNewProduct":false,"isVersionChanged":false,"isProductUpdated":false,"productId":"free_plan","productVersion":2,"lastProductId":"free_plan","lastProductVersion":2,"willUpdate":false},"timestamp":1768767394318,"sessionId":"debug-session","hypothesisId":"H4"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"analytics","entitlement_id":"ent_38RfKZrFZYfWslXTJOSbeoBjzkV"},{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}],"baseProductItems":[{"feature_id":"analytics","entitlement_id":"ent_38RfKZrFZYfWslXTJOSbeoBjzkV"},{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767394332,"sessionId":"debug-session","hypothesisId":"H1-H3"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"analytics","entitlement_id":"ent_38RfKZrFZYfWslXTJOSbeoBjzkV"},{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}],"baseProductItems":[{"feature_id":"analytics","entitlement_id":"ent_38RfKZrFZYfWslXTJOSbeoBjzkV"},{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767394332,"sessionId":"debug-session","hypothesisId":"H1-H3"} +{"location":"useProductSync.ts:useEffect","message":"sync effect triggered","data":{"isNewProduct":false,"isVersionChanged":false,"isProductUpdated":false,"productId":"free_plan","productVersion":2,"lastProductId":"free_plan","lastProductVersion":2,"willUpdate":false},"timestamp":1768767394337,"sessionId":"debug-session","hypothesisId":"H4"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}],"baseProductItems":[{"feature_id":"analytics","entitlement_id":"ent_38RfKZrFZYfWslXTJOSbeoBjzkV"},{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767501092,"sessionId":"debug-session","hypothesisId":"H1-H3"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}],"baseProductItems":[{"feature_id":"analytics","entitlement_id":"ent_38RfKZrFZYfWslXTJOSbeoBjzkV"},{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767501092,"sessionId":"debug-session","hypothesisId":"H1-H3"} +{"location":"useProductSync.ts:useEffect","message":"sync effect triggered","data":{"isNewProduct":false,"isVersionChanged":false,"isProductUpdated":false,"productId":"free_plan","productVersion":2,"lastProductId":"free_plan","lastProductVersion":2,"willUpdate":false},"timestamp":1768767501096,"sessionId":"debug-session","hypothesisId":"H4"} +{"location":"SaveChangesBar.tsx:handleSaveClicked:start","message":"save clicked - before save","data":{"productId":"free_plan","itemsCount":1,"items":[{"feature_id":"messages","interval":null,"included_usage":10}]},"timestamp":1768767501888,"sessionId":"debug-session","hypothesisId":"H2"} +{"location":"SaveChangesBar.tsx:onSuccess","message":"save onSuccess - about to refetch","data":{},"timestamp":1768767508153,"sessionId":"debug-session","hypothesisId":"H4"} +{"location":"useProductSync.ts:useEffect","message":"sync effect triggered","data":{"isNewProduct":false,"isVersionChanged":false,"isProductUpdated":true,"productId":"free_plan","productVersion":2,"lastProductId":"free_plan","lastProductVersion":2,"willUpdate":true},"timestamp":1768767509550,"sessionId":"debug-session","hypothesisId":"H4"} +{"location":"useProductSync.ts:setBaseProduct","message":"updating baseProduct","data":{"frontendProductItemsCount":1,"frontendProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767509550,"sessionId":"debug-session","hypothesisId":"H4"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}],"baseProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767509550,"sessionId":"debug-session","hypothesisId":"H1-H3"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}],"baseProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767509551,"sessionId":"debug-session","hypothesisId":"H1-H3"} +{"location":"SaveChangesBar.tsx:onSuccess:afterRefetch","message":"refetch completed","data":{},"timestamp":1768767511210,"sessionId":"debug-session","hypothesisId":"H4"} +{"location":"SaveChangesBar.tsx:handleSaveClicked:end","message":"save completed","data":{"result":true},"timestamp":1768767511210,"sessionId":"debug-session","hypothesisId":"H2"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"},{"feature_id":"analytics","interval":null,"included_usage":null}],"baseProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767525751,"sessionId":"debug-session","hypothesisId":"H1-H3"} +{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"analytics","item1Interval":null,"item1IncludedUsage":null,"items2Count":1,"items2FeatureIds":["messages"]},"timestamp":1768767525751,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"analytics","item1Interval":null,"item1IncludedUsage":null,"items2Count":1,"items2FeatureIds":["messages"]},"timestamp":1768767525751,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"analytics","item1Interval":null,"item1IncludedUsage":null,"items2Count":1,"items2FeatureIds":["messages"]},"timestamp":1768767525751,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"analytics","item1Interval":null,"item1IncludedUsage":null,"items2Count":1,"items2FeatureIds":["messages"]},"timestamp":1768767525751,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"},{"feature_id":"analytics","interval":null,"included_usage":null}],"baseProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767525751,"sessionId":"debug-session","hypothesisId":"H1-H3"} +{"location":"useProductSync.ts:useEffect","message":"sync effect triggered","data":{"isNewProduct":false,"isVersionChanged":false,"isProductUpdated":false,"productId":"free_plan","productVersion":2,"lastProductId":"free_plan","lastProductVersion":2,"willUpdate":false},"timestamp":1768767525757,"sessionId":"debug-session","hypothesisId":"H4"} +{"location":"SaveChangesBar.tsx:handleSaveClicked:start","message":"save clicked - before save","data":{"productId":"free_plan","itemsCount":2,"items":[{"feature_id":"messages","interval":null,"included_usage":10},{"feature_id":"analytics","interval":null,"included_usage":null}]},"timestamp":1768767526804,"sessionId":"debug-session","hypothesisId":"H2"} +{"location":"SaveChangesBar.tsx:onSuccess","message":"save onSuccess - about to refetch","data":{},"timestamp":1768767531740,"sessionId":"debug-session","hypothesisId":"H4"} +{"location":"useProductSync.ts:useEffect","message":"sync effect triggered","data":{"isNewProduct":false,"isVersionChanged":false,"isProductUpdated":true,"productId":"free_plan","productVersion":2,"lastProductId":"free_plan","lastProductVersion":2,"willUpdate":true},"timestamp":1768767533130,"sessionId":"debug-session","hypothesisId":"H4"} +{"location":"useProductSync.ts:setBaseProduct","message":"updating baseProduct","data":{"frontendProductItemsCount":2,"frontendProductItems":[{"feature_id":"analytics","entitlement_id":"ent_38RfuuS2oOJiQBLwQrlKxo8TOM4"},{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767533131,"sessionId":"debug-session","hypothesisId":"H4"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"},{"feature_id":"analytics","interval":null,"included_usage":null}],"baseProductItems":[{"feature_id":"analytics","entitlement_id":"ent_38RfuuS2oOJiQBLwQrlKxo8TOM4"},{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767533131,"sessionId":"debug-session","hypothesisId":"H1-H3"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"},{"feature_id":"analytics","interval":null,"included_usage":null}],"baseProductItems":[{"feature_id":"analytics","entitlement_id":"ent_38RfuuS2oOJiQBLwQrlKxo8TOM4"},{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767533131,"sessionId":"debug-session","hypothesisId":"H1-H3"} +{"location":"SaveChangesBar.tsx:onSuccess:afterRefetch","message":"refetch completed","data":{},"timestamp":1768767534789,"sessionId":"debug-session","hypothesisId":"H4"} +{"location":"SaveChangesBar.tsx:handleSaveClicked:end","message":"save completed","data":{"result":true},"timestamp":1768767534789,"sessionId":"debug-session","hypothesisId":"H2"} +{"location":"useProductSync.ts:useEffect","message":"sync effect triggered","data":{"isNewProduct":false,"isVersionChanged":false,"isProductUpdated":false,"productId":"free_plan","productVersion":2,"lastProductId":"free_plan","lastProductVersion":2,"willUpdate":false},"timestamp":1768767554592,"sessionId":"debug-session","hypothesisId":"H4"} +{"location":"useProductSync.ts:useEffect","message":"sync effect triggered","data":{"isNewProduct":true,"isVersionChanged":true,"isProductUpdated":true,"productId":"free_plan","productVersion":2,"willUpdate":true},"timestamp":1768767559098,"sessionId":"debug-session","hypothesisId":"H4"} +{"location":"useProductSync.ts:setBaseProduct","message":"updating baseProduct","data":{"frontendProductItemsCount":2,"frontendProductItems":[{"feature_id":"analytics","entitlement_id":"ent_38RfuuS2oOJiQBLwQrlKxo8TOM4"},{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767559098,"sessionId":"debug-session","hypothesisId":"H4"} +{"location":"useProductSync.ts:useEffect","message":"sync effect triggered","data":{"isNewProduct":false,"isVersionChanged":false,"isProductUpdated":false,"productId":"free_plan","productVersion":2,"lastProductId":"free_plan","lastProductVersion":2,"willUpdate":false},"timestamp":1768767559099,"sessionId":"debug-session","hypothesisId":"H4"} +{"location":"useProductSync.ts:useEffect","message":"sync effect triggered","data":{"isNewProduct":false,"isVersionChanged":false,"isProductUpdated":false,"productId":"free_plan","productVersion":2,"lastProductId":"free_plan","lastProductVersion":2,"willUpdate":false},"timestamp":1768767559112,"sessionId":"debug-session","hypothesisId":"H4"} +{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":null,"item1IncludedUsage":null,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767564109,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":null,"item1IncludedUsage":null,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767564109,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":null,"item1IncludedUsage":null,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767564109,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":null,"item1IncludedUsage":null,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767564109,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":"month","item1IncludedUsage":null,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767568548,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":"month","item1IncludedUsage":null,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767568548,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":"month","item1IncludedUsage":null,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767568548,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":"month","item1IncludedUsage":null,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767568548,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":"month","item1IncludedUsage":2,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767569476,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":"month","item1IncludedUsage":2,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767569476,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":"month","item1IncludedUsage":2,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767569476,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":"month","item1IncludedUsage":2,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767569476,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":"month","item1IncludedUsage":25,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767569717,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":"month","item1IncludedUsage":25,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767569717,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":"month","item1IncludedUsage":25,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767569717,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":"month","item1IncludedUsage":25,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767569717,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"SaveChangesBar.tsx:handleSaveClicked:start","message":"save clicked - before save","data":{"productId":"free_plan","itemsCount":3,"items":[{"feature_id":"analytics"},{"feature_id":"messages","interval":null,"included_usage":10},{"feature_id":"seats","interval":"month","included_usage":25}]},"timestamp":1768767573201,"sessionId":"debug-session","hypothesisId":"H2"} +{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":"month","item1IncludedUsage":25,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767573208,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":"month","item1IncludedUsage":25,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767573209,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":"month","item1IncludedUsage":25,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767573209,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":"month","item1IncludedUsage":25,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767573209,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"SaveChangesBar.tsx:handleSaveClicked:end","message":"save completed","data":{"result":false},"timestamp":1768767574954,"sessionId":"debug-session","hypothesisId":"H2"} +{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":"month","item1IncludedUsage":25,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767575485,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":"month","item1IncludedUsage":25,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767575485,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":"month","item1IncludedUsage":25,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767575485,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":"month","item1IncludedUsage":25,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767575485,"sessionId":"debug-session","hypothesisId":"H2-H3"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768767861061,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768767861062,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768492990,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768492990,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768494722,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768494722,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768494856,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768494857,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768503769,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768503770,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":null,"included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":null}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768509714,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":null,"included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":null}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768509714,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":[{"to":"inf","amount":0}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768510972,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":[{"to":"inf","amount":0}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768510973,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":[{"to":"inf","amount":0}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768512258,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":[{"to":"inf","amount":0}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768512259,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":[{"to":"inf","amount":4}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768513093,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":[{"to":"inf","amount":4}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768513093,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":[{"to":"inf","amount":4}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768513323,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":[{"to":"inf","amount":4}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768513323,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"compareItemUtils.ts:featurePriceItemsAreSame","message":"feature price items comparison failed","data":{"featureId":"seats","failedEntChecks":[{"check":"included_usage","message":"Included usage different: null != 0"}],"failedPriceChecks":[],"item1":"{\"feature_id\":\"seats\",\"feature_type\":\"continuous_use\",\"included_usage\":null,\"interval\":\"month\",\"price\":4,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":false,\"usage_model\":\"pay_per_use\",\"config\":{\"on_increase\":\"prorate_immediately\",\"on_decrease\":\"prorate_immediately\"}}","item2":"{\"feature_id\":\"seats\",\"feature_type\":\"continuous_use\",\"included_usage\":0,\"interval\":\"month\",\"interval_count\":1,\"price\":4,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":false,\"usage_model\":\"pay_per_use\",\"created_at\":1768768517976,\"entitlement_id\":\"ent_38Rhv08gZdC1AOrMvs20Gt4exbK\",\"price_id\":\"pr_38RhuzPJAjXGGc5JaF7JBQnKclZ\",\"price_config\":{\"type\":\"usage\",\"interval\":\"month\",\"bill_when\":\"end_of_period\",\"feature_id\":\"seats\",\"usage_tiers\":[{\"to\":\"inf\",\"amount\":4}],\"billing_units\":1,\"interval_count\":1,\"should_prorate\":true,\"stripe_price_id\":\"price_1Sr2gtG2wlWesuVT9hSb0qPO\",\"stripe_product_id\":\"prod_Tog21hujMdhsAJ\",\"internal_feature_id\":\"fe_38IUUvu4BoVK96JwbDZfJHwcRyD\",\"stripe_placeholder_price_id\":\"price_1Sr2guG2wlWesuVTfbCs2bkT\"},\"config\":{\"on_decrease\":\"prorate_immediately\",\"on_increase\":\"prorate_immediately\"},\"usage_limit\":null}"},"timestamp":1768768522738,"sessionId":"debug-session","hypothesisId":"H3"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":[{"to":"inf","amount":4}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768522738,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"compareItemUtils.ts:featurePriceItemsAreSame","message":"feature price items comparison failed","data":{"featureId":"seats","failedEntChecks":[{"check":"included_usage","message":"Included usage different: null != 0"}],"failedPriceChecks":[],"item1":"{\"feature_id\":\"seats\",\"feature_type\":\"continuous_use\",\"included_usage\":null,\"interval\":\"month\",\"price\":4,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":false,\"usage_model\":\"pay_per_use\",\"config\":{\"on_increase\":\"prorate_immediately\",\"on_decrease\":\"prorate_immediately\"}}","item2":"{\"feature_id\":\"seats\",\"feature_type\":\"continuous_use\",\"included_usage\":0,\"interval\":\"month\",\"interval_count\":1,\"price\":4,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":false,\"usage_model\":\"pay_per_use\",\"created_at\":1768768517976,\"entitlement_id\":\"ent_38Rhv08gZdC1AOrMvs20Gt4exbK\",\"price_id\":\"pr_38RhuzPJAjXGGc5JaF7JBQnKclZ\",\"price_config\":{\"type\":\"usage\",\"interval\":\"month\",\"bill_when\":\"end_of_period\",\"feature_id\":\"seats\",\"usage_tiers\":[{\"to\":\"inf\",\"amount\":4}],\"billing_units\":1,\"interval_count\":1,\"should_prorate\":true,\"stripe_price_id\":\"price_1Sr2gtG2wlWesuVT9hSb0qPO\",\"stripe_product_id\":\"prod_Tog21hujMdhsAJ\",\"internal_feature_id\":\"fe_38IUUvu4BoVK96JwbDZfJHwcRyD\",\"stripe_placeholder_price_id\":\"price_1Sr2guG2wlWesuVTfbCs2bkT\"},\"config\":{\"on_decrease\":\"prorate_immediately\",\"on_increase\":\"prorate_immediately\"},\"usage_limit\":null}"},"timestamp":1768768522739,"sessionId":"debug-session","hypothesisId":"H3"} +{"location":"compareItemUtils.ts:featurePriceItemsAreSame","message":"feature price items comparison failed","data":{"featureId":"seats","failedEntChecks":[{"check":"included_usage","message":"Included usage different: null != 0"}],"failedPriceChecks":[],"item1":"{\"feature_id\":\"seats\",\"feature_type\":\"continuous_use\",\"included_usage\":null,\"interval\":\"month\",\"price\":4,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":false,\"usage_model\":\"pay_per_use\",\"config\":{\"on_increase\":\"prorate_immediately\",\"on_decrease\":\"prorate_immediately\"}}","item2":"{\"feature_id\":\"seats\",\"feature_type\":\"continuous_use\",\"included_usage\":0,\"interval\":\"month\",\"interval_count\":1,\"price\":4,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":false,\"usage_model\":\"pay_per_use\",\"created_at\":1768768517976,\"entitlement_id\":\"ent_38Rhv08gZdC1AOrMvs20Gt4exbK\",\"price_id\":\"pr_38RhuzPJAjXGGc5JaF7JBQnKclZ\",\"price_config\":{\"type\":\"usage\",\"interval\":\"month\",\"bill_when\":\"end_of_period\",\"feature_id\":\"seats\",\"usage_tiers\":[{\"to\":\"inf\",\"amount\":4}],\"billing_units\":1,\"interval_count\":1,\"should_prorate\":true,\"stripe_price_id\":\"price_1Sr2gtG2wlWesuVT9hSb0qPO\",\"stripe_product_id\":\"prod_Tog21hujMdhsAJ\",\"internal_feature_id\":\"fe_38IUUvu4BoVK96JwbDZfJHwcRyD\",\"stripe_placeholder_price_id\":\"price_1Sr2guG2wlWesuVTfbCs2bkT\"},\"config\":{\"on_decrease\":\"prorate_immediately\",\"on_increase\":\"prorate_immediately\"},\"usage_limit\":null}"},"timestamp":1768768522738,"sessionId":"debug-session","hypothesisId":"H3"} +{"location":"compareItemUtils.ts:featurePriceItemsAreSame","message":"feature price items comparison failed","data":{"featureId":"seats","failedEntChecks":[{"check":"included_usage","message":"Included usage different: null != 0"}],"failedPriceChecks":[],"item1":"{\"feature_id\":\"seats\",\"feature_type\":\"continuous_use\",\"included_usage\":null,\"interval\":\"month\",\"price\":4,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":false,\"usage_model\":\"pay_per_use\",\"config\":{\"on_increase\":\"prorate_immediately\",\"on_decrease\":\"prorate_immediately\"}}","item2":"{\"feature_id\":\"seats\",\"feature_type\":\"continuous_use\",\"included_usage\":0,\"interval\":\"month\",\"interval_count\":1,\"price\":4,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":false,\"usage_model\":\"pay_per_use\",\"created_at\":1768768517976,\"entitlement_id\":\"ent_38Rhv08gZdC1AOrMvs20Gt4exbK\",\"price_id\":\"pr_38RhuzPJAjXGGc5JaF7JBQnKclZ\",\"price_config\":{\"type\":\"usage\",\"interval\":\"month\",\"bill_when\":\"end_of_period\",\"feature_id\":\"seats\",\"usage_tiers\":[{\"to\":\"inf\",\"amount\":4}],\"billing_units\":1,\"interval_count\":1,\"should_prorate\":true,\"stripe_price_id\":\"price_1Sr2gtG2wlWesuVT9hSb0qPO\",\"stripe_product_id\":\"prod_Tog21hujMdhsAJ\",\"internal_feature_id\":\"fe_38IUUvu4BoVK96JwbDZfJHwcRyD\",\"stripe_placeholder_price_id\":\"price_1Sr2guG2wlWesuVTfbCs2bkT\"},\"config\":{\"on_decrease\":\"prorate_immediately\",\"on_increase\":\"prorate_immediately\"},\"usage_limit\":null}"},"timestamp":1768768522739,"sessionId":"debug-session","hypothesisId":"H3"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":[{"to":"inf","amount":4}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768522739,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768616773,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768616773,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768617686,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768617687,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768619603,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768619604,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768627805,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768627805,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":null,"included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":null}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768634551,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":null,"included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":null}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768634551,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":[{"to":"inf","amount":0}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768635559,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":[{"to":"inf","amount":0}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768635559,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":[{"to":"inf","amount":4}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768636851,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":[{"to":"inf","amount":4}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768636851,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":[{"to":"inf","amount":4}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768637168,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":[{"to":"inf","amount":4}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768637169,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":[{"to":"inf","amount":4}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768647252,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":[{"to":"inf","amount":4}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768647253,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768650150,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768650151,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"runs","feature_type":"single_use","interval":"month","included_usage":null,"reset_usage_when_enabled":true,"price":null,"tiers":null}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768653414,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"runs","feature_type":"single_use","interval":"month","included_usage":null,"reset_usage_when_enabled":true,"price":null,"tiers":null}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768653414,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"runs","feature_type":"single_use","interval":"month","included_usage":null,"reset_usage_when_enabled":true,"price":null,"tiers":[{"to":"inf","amount":0}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768654738,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"runs","feature_type":"single_use","interval":"month","included_usage":null,"reset_usage_when_enabled":true,"price":null,"tiers":[{"to":"inf","amount":0}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768654738,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"runs","feature_type":"single_use","interval":"month","included_usage":null,"reset_usage_when_enabled":true,"price":null,"tiers":[{"to":"inf","amount":5}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768656050,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"runs","feature_type":"single_use","interval":"month","included_usage":null,"reset_usage_when_enabled":true,"price":null,"tiers":[{"to":"inf","amount":5}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768656050,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"runs","feature_type":"single_use","interval":"month","included_usage":null,"reset_usage_when_enabled":true,"price":null,"tiers":[{"to":"inf","amount":5}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768656372,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"runs","feature_type":"single_use","interval":"month","included_usage":null,"reset_usage_when_enabled":true,"price":null,"tiers":[{"to":"inf","amount":5}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768656373,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"runs","feature_type":"single_use","interval":"month","included_usage":null,"reset_usage_when_enabled":true,"price":null,"tiers":[{"to":"inf","amount":5}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768657946,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"runs","feature_type":"single_use","interval":"month","included_usage":null,"reset_usage_when_enabled":true,"price":null,"tiers":[{"to":"inf","amount":5}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768657947,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"runs","feature_type":"single_use","interval":"month","included_usage":null,"reset_usage_when_enabled":true,"price":null,"tiers":[{"to":"inf","amount":5}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"runs","feature_type":"single_use","interval":"month","included_usage":0,"reset_usage_when_enabled":true,"price":null,"tiers":[{"amount":5,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768668632,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"runs","feature_type":"single_use","interval":"month","included_usage":null,"reset_usage_when_enabled":true,"price":null,"tiers":[{"to":"inf","amount":5}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"runs","feature_type":"single_use","interval":"month","included_usage":0,"reset_usage_when_enabled":true,"price":null,"tiers":[{"amount":5,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768668633,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"runs","feature_type":"single_use","interval":"month","included_usage":0,"reset_usage_when_enabled":true,"price":null,"tiers":[{"amount":5,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768670093,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"runs","feature_type":"single_use","interval":"month","included_usage":0,"reset_usage_when_enabled":true,"price":null,"tiers":[{"amount":5,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768670094,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":null,"included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":null}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"runs","feature_type":"single_use","interval":"month","included_usage":0,"reset_usage_when_enabled":true,"price":null,"tiers":[{"amount":5,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768675489,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":null,"included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":null}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"runs","feature_type":"single_use","interval":"month","included_usage":0,"reset_usage_when_enabled":true,"price":null,"tiers":[{"amount":5,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768675489,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":null,"included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":null}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768677586,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":null,"included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":null}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768677586,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":null,"included_usage":5,"reset_usage_when_enabled":false,"price":null,"tiers":null}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768678144,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":null,"included_usage":5,"reset_usage_when_enabled":false,"price":null,"tiers":null}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768678144,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"compareItemUtils.ts:featureItemsAreSame","message":"feature items comparison failed","data":{"featureId":"seats","failedChecks":[{"check":"included_usage","message":"Included usage different: 5 != null"}],"item1":"{\"feature_id\":\"seats\",\"feature_type\":\"continuous_use\",\"included_usage\":5,\"interval\":null,\"price\":null,\"tiers\":null,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":false}","item2":"{\"feature_id\":\"seats\",\"feature_type\":\"continuous_use\",\"included_usage\":null,\"interval\":null,\"price\":null,\"tiers\":null,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":false}"},"timestamp":1768768678144,"sessionId":"debug-session","hypothesisId":"H2"} +{"location":"compareItemUtils.ts:featureItemsAreSame","message":"feature items comparison failed","data":{"featureId":"seats","failedChecks":[{"check":"included_usage","message":"Included usage different: 5 != null"}],"item1":"{\"feature_id\":\"seats\",\"feature_type\":\"continuous_use\",\"included_usage\":5,\"interval\":null,\"price\":null,\"tiers\":null,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":false}","item2":"{\"feature_id\":\"seats\",\"feature_type\":\"continuous_use\",\"included_usage\":null,\"interval\":null,\"price\":null,\"tiers\":null,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":false}"},"timestamp":1768768678145,"sessionId":"debug-session","hypothesisId":"H2"} +{"location":"compareItemUtils.ts:featureItemsAreSame","message":"feature items comparison failed","data":{"featureId":"seats","failedChecks":[{"check":"included_usage","message":"Included usage different: 5 != null"}],"item1":"{\"feature_id\":\"seats\",\"feature_type\":\"continuous_use\",\"included_usage\":5,\"interval\":null,\"price\":null,\"tiers\":null,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":false}","item2":"{\"feature_id\":\"seats\",\"feature_type\":\"continuous_use\",\"included_usage\":null,\"interval\":null,\"price\":null,\"tiers\":null,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":false}"},"timestamp":1768768678145,"sessionId":"debug-session","hypothesisId":"H2"} +{"location":"compareItemUtils.ts:featureItemsAreSame","message":"feature items comparison failed","data":{"featureId":"seats","failedChecks":[{"check":"included_usage","message":"Included usage different: 5 != null"}],"item1":"{\"feature_id\":\"seats\",\"feature_type\":\"continuous_use\",\"included_usage\":5,\"interval\":null,\"price\":null,\"tiers\":null,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":false}","item2":"{\"feature_id\":\"seats\",\"feature_type\":\"continuous_use\",\"included_usage\":null,\"interval\":null,\"price\":null,\"tiers\":null,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":false}"},"timestamp":1768768678145,"sessionId":"debug-session","hypothesisId":"H2"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":null,"included_usage":5,"reset_usage_when_enabled":false,"price":null,"tiers":null}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":null,"included_usage":5,"reset_usage_when_enabled":false}]},"timestamp":1768768686901,"sessionId":"debug-session","hypothesisId":"H1"} +{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":null,"included_usage":5,"reset_usage_when_enabled":false,"price":null,"tiers":null}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":null,"included_usage":5,"reset_usage_when_enabled":false}]},"timestamp":1768768686901,"sessionId":"debug-session","hypothesisId":"H1"} diff --git a/server/src/internal/customers/CusSearchService.ts b/server/src/internal/customers/CusSearchService.ts index ae97f6228..972531e57 100644 --- a/server/src/internal/customers/CusSearchService.ts +++ b/server/src/internal/customers/CusSearchService.ts @@ -10,12 +10,14 @@ import { and, desc, eq, - gt, ilike, isNotNull, + gt, + ilike, + isNotNull, isNull, lt, notExists, or, - sql + sql, } from "drizzle-orm"; import { alias } from "drizzle-orm/pg-core"; import type { DrizzleCli } from "@/db/initDrizzle.js"; @@ -155,6 +157,16 @@ export class CusSearchService { ? or( ...statuses.map((status) => { switch (status) { + case "active": + return and( + eq(customerProducts.status, CusProductStatus.Active), + isNull(customerProducts.canceled_at), + ); + case "past_due": + return and( + eq(customerProducts.status, CusProductStatus.PastDue), + isNull(customerProducts.canceled_at), + ); case "canceled": return and( isNotNull(customerProducts.canceled_at), @@ -164,6 +176,7 @@ export class CusSearchService { return and( gt(customerProducts.trial_ends_at, Date.now()), isNotNull(customerProducts.free_trial_id), + isNull(customerProducts.canceled_at), activeProdFilter, ); case CusProductStatus.Expired: @@ -513,7 +526,8 @@ export class CusSearchService { }); } - if (filters?.version && filters?.version.length > 0) { + // Call searchByProduct if we have version filters OR status filters + if ((filters?.version && filters?.version.length > 0) || (filters?.status && filters?.status.length > 0)) { return await CusSearchService.searchByProduct({ db, orgId, @@ -642,7 +656,6 @@ export class CusSearchService { return { data: finalResults, count: totalCount }; } } - // // Legacy support for product_id field (if still used) // let productIds: string[] = []; // if (filters.product_id) { diff --git a/shared/utils/productDisplayUtils.ts b/shared/utils/productDisplayUtils.ts index 6ecfbbaec..637bb8b59 100644 --- a/shared/utils/productDisplayUtils.ts +++ b/shared/utils/productDisplayUtils.ts @@ -1,4 +1,7 @@ -import { FeatureType } from "../models/featureModels/featureEnums.js"; +import { + FeatureType, + FeatureUsageType, +} from "../models/featureModels/featureEnums.js"; import type { Feature } from "../models/featureModels/featureModels.js"; import { Infinite } from "../models/productModels/productEnums.js"; import type { ProductItem } from "../models/productV2Models/productItemModels/productItemModels.js"; @@ -12,40 +15,84 @@ import { } from "./productV2Utils/productItemUtils/getItemType.js"; import { notNullish, nullish } from "./utils.js"; +// ============================================================================ +// Types +// ============================================================================ + +interface DisplayResult { + primary_text: string; + secondary_text?: string; +} + +interface FormatTiersParams { + item: ProductItem; + currency?: string | null; + amountFormatOptions?: Intl.NumberFormatOptions; +} + +// ============================================================================ +// Helpers +// ============================================================================ + +const getIntervalDisplay = (item: ProductItem): string | undefined => { + if (!item.interval) return undefined; + + return formatInterval({ + interval: item.interval, + intervalCount: item.interval_count ?? undefined, + }); +}; + +const getIncludedUsageText = (item: ProductItem, feature: Feature): string => { + const featureName = getFeatureName({ + feature, + units: item.included_usage, + }); + + if (item.included_usage === Infinite) { + return `Unlimited ${featureName}`; + } + + if (nullish(item.included_usage) || item.included_usage === 0) { + return `0 ${featureName}`; + } + + return `${numberWithCommas(item.included_usage)} ${featureName}`; +}; + +const isSingleUseFeature = (feature: Feature): boolean => { + return feature.config?.usage_type === FeatureUsageType.Single; +}; + +// ============================================================================ +// Tier Formatting +// ============================================================================ + export const formatTiers = ({ item, currency, amountFormatOptions, -}: { - item: ProductItem; - currency?: string | null; - amountFormatOptions?: Intl.NumberFormatOptions; -}) => { +}: FormatTiersParams): string | undefined => { const tiers = item.tiers; - if (tiers) { - if (tiers.length === 1) { - return formatAmount({ - currency, - amount: tiers[0].amount, - amountFormatOptions, - }); - } + if (!tiers) return undefined; - const firstPrice = tiers[0].amount; - const lastPrice = tiers[tiers.length - 1].amount; + const format = (amount: number) => + formatAmount({ currency, amount, amountFormatOptions }); - return `${formatAmount({ - currency, - amount: firstPrice, - amountFormatOptions, - })} - ${formatAmount({ - currency, - amount: lastPrice, - amountFormatOptions, - })}`; + if (tiers.length === 1) { + return format(tiers[0].amount); } + + const firstPrice = tiers[0].amount; + const lastPrice = tiers[tiers.length - 1].amount; + + return `${format(firstPrice)} - ${format(lastPrice)}`; }; +// ============================================================================ +// Feature Item Display (no pricing, just entitlement) +// ============================================================================ + export const getFeatureItemDisplay = ({ item, feature, @@ -54,57 +101,30 @@ export const getFeatureItemDisplay = ({ item: ProductItem; feature?: Feature; fullDisplay?: boolean; -}) => { - if (!feature) throw new Error(`Feature ${item.feature_id} not found`); +}): DisplayResult => { + if (!feature) { + // Return fallback display when feature is not found (e.g., during feature ID rename) + return { primary_text: item.feature_id || "Loading..." }; + } + // Boolean features just show the name if (feature.type === FeatureType.Boolean) { return { primary_text: feature.name }; } - const featureName = getFeatureName({ - feature, - units: item.included_usage, - }); + const primaryText = getIncludedUsageText(item, feature); - const includedUsageTxt = - item.included_usage === Infinite - ? "Unlimited " - : nullish(item.included_usage) || item.included_usage === 0 - ? "0 " - : `${numberWithCommas(item.included_usage)} `; - - // If interval is null for a feature item, it's a one-time/lifetime feature - const intervalStr = item.interval - ? formatInterval({ - interval: item.interval, - intervalCount: item.interval_count ?? undefined, - }) - : "one-off"; - - return { - primary_text: `${includedUsageTxt}${featureName}`, - secondary_text: fullDisplay ? intervalStr : undefined, - }; -}; - -export const getPriceItemDisplay = ({ - item, - currency, -}: { - item: ProductItem; - currency?: string | null; -}) => { - const primaryText = formatAmount({ - currency, - amount: item.price as number, - }); - - const intervalStr = formatInterval({ - interval: item.interval ?? undefined, - intervalCount: item.interval_count ?? undefined, - }); - - const secondaryText = intervalStr || undefined; + // Determine secondary text (interval display) + let secondaryText: string | undefined; + if (fullDisplay) { + const intervalDisplay = getIntervalDisplay(item); + if (intervalDisplay) { + secondaryText = intervalDisplay; + } else if (isSingleUseFeature(feature)) { + // Only show "one-off" for single-use features, not continuous use + secondaryText = "one-off"; + } + } return { primary_text: primaryText, @@ -112,12 +132,39 @@ export const getPriceItemDisplay = ({ }; }; +// ============================================================================ +// Price Item Display (flat price, no feature) +// ============================================================================ + +export const getPriceItemDisplay = ({ + item, + currency, +}: { + item: ProductItem; + currency?: string | null; +}): DisplayResult => { + const primaryText = formatAmount({ + currency, + amount: item.price as number, + }); + + const secondaryText = getIntervalDisplay(item); + + return { + primary_text: primaryText, + secondary_text: secondaryText, + }; +}; + +// ============================================================================ +// Feature + Price Item Display (usage-based pricing) +// ============================================================================ + export const getFeaturePriceItemDisplay = ({ feature, item, currency, isMainPrice = false, - // minifyIncluded = false, amountFormatOptions, fullDisplay = false, }: { @@ -125,73 +172,77 @@ export const getFeaturePriceItemDisplay = ({ item: ProductItem; currency?: string | null; isMainPrice?: boolean; - // minifyIncluded?: boolean; amountFormatOptions?: Intl.NumberFormatOptions; fullDisplay?: boolean; -}) => { +}): DisplayResult => { if (!feature) { throw new Error(`Feature ${item.feature_id} not found`); } - // 1. Get included usage + // Build included usage string (e.g., "100 credits") + const includedUsage = item.included_usage as number | null; + const hasIncludedUsage = notNullish(includedUsage) && includedUsage > 0; + const includedFeatureName = getFeatureName({ feature, units: item.included_usage, }); + const includedUsageStr = hasIncludedUsage + ? `${numberWithCommas(includedUsage)} ${includedFeatureName}` + : ""; - const includedUsage = item.included_usage as number | null; - let includedUsageStr = ""; - if (notNullish(includedUsage) && includedUsage > 0) { - includedUsageStr = `${numberWithCommas(includedUsage)} ${includedFeatureName}`; - } - + // Build price string (e.g., "$0.01") const priceStr = formatTiers({ item, currency, amountFormatOptions }) ?? ""; - // For "per X" display, use singular when billing_units is 1 or not specified + // Build billing unit string (e.g., "credit" or "100 credits") const billingUnits = item.billing_units ?? 1; const billingFeatureName = getFeatureName({ feature, units: billingUnits, }); + const perUnitStr = + billingUnits > 1 + ? `${numberWithCommas(billingUnits)} ${billingFeatureName}` + : billingFeatureName; - let priceStr2 = ""; - if (billingUnits > 1) { - priceStr2 = `${numberWithCommas(billingUnits)} ${billingFeatureName}`; - } else { - priceStr2 = `${billingFeatureName}`; + // Build interval string + const showInterval = isMainPrice || fullDisplay; + let intervalStr = ""; + if (showInterval) { + const intervalDisplay = getIntervalDisplay(item); + if (intervalDisplay) { + intervalStr = intervalDisplay; + } else if (isSingleUseFeature(feature)) { + intervalStr = "one-off"; + } } - // If interval is null for a priced feature, it's a one-time purchase - const intervalStr = - isMainPrice || fullDisplay - ? item.interval - ? formatInterval({ - interval: item.interval, - intervalCount: item.interval_count ?? undefined, - }) - : "one-off" - : ""; - - if (includedUsageStr) { + // Format output based on what we have + if (hasIncludedUsage) { return { primary_text: includedUsageStr, - secondary_text: `then ${priceStr} per ${priceStr2} ${intervalStr}`, + secondary_text: + `then ${priceStr} per ${perUnitStr} ${intervalStr}`.trim(), }; } - if (isMainPrice || fullDisplay) { + if (showInterval) { return { primary_text: priceStr, - secondary_text: `per ${priceStr2} ${intervalStr}`, + secondary_text: `per ${perUnitStr} ${intervalStr}`.trim(), }; } return { - primary_text: `${priceStr} per ${priceStr2} ${intervalStr}`, + primary_text: `${priceStr} per ${perUnitStr}`.trim(), secondary_text: undefined, }; }; +// ============================================================================ +// Main Entry Point +// ============================================================================ + export const getProductItemDisplay = ({ item, features, @@ -204,26 +255,25 @@ export const getProductItemDisplay = ({ currency?: string | null; fullDisplay?: boolean; amountFormatOptions?: Intl.NumberFormatOptions; -}) => { +}): DisplayResult => { + const findFeature = () => features.find((f) => f.id === item.feature_id); + if (isFeatureItem(item)) { return getFeatureItemDisplay({ item, - feature: features.find((f) => f.id === item.feature_id), + feature: findFeature(), fullDisplay, }); } if (isPriceItem(item)) { - return getPriceItemDisplay({ - item, - currency, - }); + return getPriceItemDisplay({ item, currency }); } if (isFeaturePriceItem(item)) { return getFeaturePriceItemDisplay({ item, - feature: features.find((f) => f.id === item.feature_id), + feature: findFeature(), currency, fullDisplay, amountFormatOptions, diff --git a/shared/utils/productV2Utils/compareProductUtils/compareItemUtils.ts b/shared/utils/productV2Utils/compareProductUtils/compareItemUtils.ts index f2401e365..ed0fb3277 100644 --- a/shared/utils/productV2Utils/compareProductUtils/compareItemUtils.ts +++ b/shared/utils/productV2Utils/compareProductUtils/compareItemUtils.ts @@ -103,6 +103,12 @@ const tiersAreSame = ( ); }; +// Helper to normalize included_usage for comparison (null and 0 are equivalent) +const normalizeIncludedUsage = (value: number | "inf" | null | undefined) => { + if (value === null || value === undefined) return 0; + return value; +}; + export const featureItemsAreSame = ({ item1, item2, @@ -118,7 +124,10 @@ export const featureItemsAreSame = ({ message: `Feature ID different: ${item1.feature_id} != ${item2.feature_id}`, }, included_usage: { - condition: item1.included_usage == item2.included_usage, + // Normalize null/undefined to 0 for comparison since they're semantically equivalent + condition: + normalizeIncludedUsage(item1.included_usage) == + normalizeIncludedUsage(item2.included_usage), message: `Included usage different: ${item1.included_usage} != ${item2.included_usage}`, }, interval: { @@ -232,7 +241,10 @@ export const featurePriceItemsAreSame = ({ // console.log("Item 2 config:", item2.config); const entsSame = { included_usage: { - condition: item1.included_usage == item2.included_usage, + // Normalize null/undefined to 0 for comparison since they're semantically equivalent + condition: + normalizeIncludedUsage(item1.included_usage) == + normalizeIncludedUsage(item2.included_usage), message: `Included usage different: ${item1.included_usage} != ${item2.included_usage}`, }, usage_limit: { diff --git a/vite/src/components/general/table/table-header.tsx b/vite/src/components/general/table/table-header.tsx index 306869073..74400b5f5 100644 --- a/vite/src/components/general/table/table-header.tsx +++ b/vite/src/components/general/table/table-header.tsx @@ -14,7 +14,7 @@ function SortIcon({ sortDirection }: { sortDirection: string | false }) { return (
+ + )} ); diff --git a/vite/src/views/customers/components/filter-dropdown/SavedViews.tsx b/vite/src/views/customers/components/filter-dropdown/SavedViews.tsx index db5744939..21766b5b1 100644 --- a/vite/src/views/customers/components/filter-dropdown/SavedViews.tsx +++ b/vite/src/views/customers/components/filter-dropdown/SavedViews.tsx @@ -1,3 +1,7 @@ +import { TrashIcon } from "@phosphor-icons/react"; +import { useState } from "react"; +import { toast } from "sonner"; +import { Button } from "@/components/ui/button"; import { DropdownMenuGroup, DropdownMenuItem, @@ -9,13 +13,8 @@ import { PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; -import { Button } from "@/components/ui/button"; - -import { toast } from "sonner"; -import { getBackendErr } from "@/utils/genUtils"; import { useAxiosInstance } from "@/services/useAxiosInstance"; -import { Delete } from "lucide-react"; -import { useState } from "react"; +import { getBackendErr } from "@/utils/genUtils"; import { useCustomersQueryStates } from "../../hooks/useCustomersQueryStates"; interface SavedView { @@ -34,7 +33,7 @@ export const SavedViews = ({ mutateViews: any; setDropdownOpen: (open: boolean) => void; }) => { - const { setQueryStates } = useCustomersQueryStates(); + const { setFilters } = useCustomersQueryStates(); const axiosInstance = useAxiosInstance(); const [deletingViewId, setDeletingViewId] = useState(null); @@ -44,24 +43,17 @@ export const SavedViews = ({ const decodedParams = atob(view.filters); const params = new URLSearchParams(decodedParams); - // Apply all parameters using setQueryStates (this will reset pagination automatically) + // Apply all parameters using setFilters (this will reset pagination automatically) const statusParam = params.get("status") || ""; const versionParam = params.get("version") || ""; const noneParam = params.get("none"); - const queryParams = { - page: 1, + setFilters({ q: params.get("q") || "", status: statusParam ? statusParam.split(",").filter(Boolean) : [], version: versionParam ? versionParam.split(",").filter(Boolean) : [], none: noneParam === "true", - lastItemId: "", - }; - - setQueryStates(queryParams); - - // Explicitly trigger a data refetch to ensure the view is applied immediately - // await mutate(); + }); toast.success(`Applied filters from ${view.name} view`); } catch (error) { @@ -95,7 +87,7 @@ export const SavedViews = ({ {views.map((view: SavedView) => (
{ await applyView(view); setDropdownOpen(false); @@ -110,18 +102,22 @@ export const SavedViews = ({ e.preventDefault()} onCloseAutoFocus={(e) => e.preventDefault()} > diff --git a/vite/src/views/customers/components/filter-dropdown/SavedViewsDropdown.tsx b/vite/src/views/customers/components/filter-dropdown/SavedViewsDropdown.tsx index 482baabac..9e3595826 100644 --- a/vite/src/views/customers/components/filter-dropdown/SavedViewsDropdown.tsx +++ b/vite/src/views/customers/components/filter-dropdown/SavedViewsDropdown.tsx @@ -1,3 +1,5 @@ +import { BookmarkIcon, Trash2 } from "lucide-react"; +import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { DropdownMenu, @@ -10,8 +12,6 @@ import { } from "@/components/ui/dropdown-menu"; import { useAxiosInstance } from "@/services/useAxiosInstance"; import { getBackendErr } from "@/utils/genUtils"; -import { BookmarkIcon, Trash2 } from "lucide-react"; -import { toast } from "sonner"; import { useCustomersQueryStates } from "../../hooks/useCustomersQueryStates"; import { useSavedViewsQuery } from "../../hooks/useSavedViewsQuery"; @@ -119,6 +119,7 @@ export const SavedViewsDropdown = () => { > {view.name}
- {!hasRows && hasSearchQuery ? ( + {!hasRows && hasActiveFiltersOrSearch ? ( } diff --git a/vite/src/views/customers2/components/table/customer-products/CustomerProductsColumns.tsx b/vite/src/views/customers2/components/table/customer-products/CustomerProductsColumns.tsx index 983a5962d..6074e950a 100644 --- a/vite/src/views/customers2/components/table/customer-products/CustomerProductsColumns.tsx +++ b/vite/src/views/customers2/components/table/customer-products/CustomerProductsColumns.tsx @@ -50,6 +50,7 @@ export const CustomerProductsColumns = [ status={row.original.status} starts_at={row.original.starts_at ?? undefined} canceled={row.original.canceled} + canceled_at={row.original.canceled_at ?? undefined} trialing={isCustomerProductTrialing(row.original) || false} trial_ends_at={row.original.trial_ends_at ?? undefined} /> diff --git a/vite/src/views/customers2/components/table/customer-products/CustomerProductsStatus.tsx b/vite/src/views/customers2/components/table/customer-products/CustomerProductsStatus.tsx index 2a9dbfe02..c159dd9e0 100644 --- a/vite/src/views/customers2/components/table/customer-products/CustomerProductsStatus.tsx +++ b/vite/src/views/customers2/components/table/customer-products/CustomerProductsStatus.tsx @@ -14,15 +14,29 @@ const StatusItem = ({ children, text, trial_ends_at, + canceled_at, tooltip, className, }: { children: React.ReactNode; text: string; trial_ends_at?: number; + canceled_at?: number; tooltip?: boolean; className?: string; }) => { + const getSubtext = () => { + if (trial_ends_at) { + return `${formatDistanceToNow(trial_ends_at)} left`; + } + if (canceled_at) { + return `${formatDistanceToNow(canceled_at)} ago`; + } + return null; + }; + + const subtext = getSubtext(); + return (
{tooltip ? ( @@ -31,10 +45,8 @@ const StatusItem = ({ {children} {text} - {trial_ends_at && ( - - ({formatDistanceToNow(trial_ends_at)} left) - + {subtext && ( + ({subtext}) )} @@ -45,12 +57,10 @@ const StatusItem = ({ {children} {text}
- {trial_ends_at && ( + {subtext && ( <> - - {formatDistanceToNow(trial_ends_at)} left - + {subtext} )} @@ -63,6 +73,7 @@ export const CustomerProductsStatus = ({ tooltip, status, canceled, + canceled_at, trialing, trial_ends_at, starts_at, @@ -70,6 +81,7 @@ export const CustomerProductsStatus = ({ status?: CusProductStatus; tooltip?: boolean; canceled?: boolean; + canceled_at?: number; trialing?: boolean; trial_ends_at?: number; starts_at?: number; @@ -105,7 +117,7 @@ export const CustomerProductsStatus = ({ // If product is canceled, show that status if (canceled) { return ( - + { icon: , }, { - title: "Features", + title: "Plan Features", value: "features", icon: , }, diff --git a/vite/src/views/onboarding4/OnboardingGuide.tsx b/vite/src/views/onboarding4/OnboardingGuide.tsx index c7377c462..5ab58311f 100644 --- a/vite/src/views/onboarding4/OnboardingGuide.tsx +++ b/vite/src/views/onboarding4/OnboardingGuide.tsx @@ -105,9 +105,9 @@ function StepCard({ animate={{ flex: isActive ? 4 : 1 }} transition={{ duration: 0.3, ease: "easeInOut" }} className={cn( - "relative rounded-xl border bg-card cursor-pointer h-29 overflow-hidden", + "relative border dark:border-none rounded-xl bg-card cursor-pointer h-29 overflow-hidden", isActive - ? "border-primary/30" + ? "" : "hover:border-primary/20 hover:bg-interactive-secondary-hover", isComplete && !isActive && "opacity-50", )} diff --git a/vite/src/views/products/features/components/UpdateFeatureSheet.tsx b/vite/src/views/products/features/components/UpdateFeatureSheet.tsx index 0baa1f65c..fc95bad06 100644 --- a/vite/src/views/products/features/components/UpdateFeatureSheet.tsx +++ b/vite/src/views/products/features/components/UpdateFeatureSheet.tsx @@ -22,12 +22,14 @@ interface UpdateFeatureSheetProps { open: boolean; setOpen: (open: boolean) => void; selectedFeature: Feature | null; + onSuccess?: (oldId: string, newId: string) => void; } function UpdateFeatureSheet({ open, setOpen, selectedFeature, + onSuccess, }: UpdateFeatureSheetProps) { const [loading, setLoading] = useState(false); @@ -63,6 +65,12 @@ function UpdateFeatureSheet({ await refetch(); toast.success("Feature updated successfully"); + + // Call onSuccess with old and new IDs, if it's updated from the plan editor to update the plan items. + if (onSuccess) { + onSuccess(selectedFeature.id, feature.id); + } + setOpen(false); } catch (error: unknown) { console.log(error); diff --git a/vite/src/views/products/features/credit-systems/components/UpdateCreditSystemSheet.tsx b/vite/src/views/products/features/credit-systems/components/UpdateCreditSystemSheet.tsx index 847f208db..5fbc54192 100644 --- a/vite/src/views/products/features/credit-systems/components/UpdateCreditSystemSheet.tsx +++ b/vite/src/views/products/features/credit-systems/components/UpdateCreditSystemSheet.tsx @@ -21,12 +21,14 @@ interface UpdateCreditSystemSheetProps { open: boolean; setOpen: (open: boolean) => void; selectedCreditSystem: Feature | null; + onSuccess?: (oldId: string, newId: string) => void; } function UpdateCreditSystemSheet({ open, setOpen, selectedCreditSystem, + onSuccess, }: UpdateCreditSystemSheetProps) { const [loading, setLoading] = useState(false); const [creditSystem, setCreditSystem] = useState({ @@ -92,6 +94,12 @@ function UpdateCreditSystemSheet({ await refetch(); toast.success("Credit system updated successfully"); + + // Call onSuccess with old and new IDs + if (onSuccess) { + onSuccess(selectedCreditSystem.id, creditSystem.id || selectedCreditSystem.id); + } + setOpen(false); } catch (error: unknown) { console.log(error); diff --git a/vite/src/views/products/plan/components/PlanEditor.tsx b/vite/src/views/products/plan/components/PlanEditor.tsx index 1601fe5ba..2fd6ddce4 100644 --- a/vite/src/views/products/plan/components/PlanEditor.tsx +++ b/vite/src/views/products/plan/components/PlanEditor.tsx @@ -1,10 +1,6 @@ -import { AnimatePresence, motion } from "motion/react"; -import { createPortal } from "react-dom"; -import { - useCurrentItem, - useHasItemChanges, - useIsCusPlanEditor, -} from "@/hooks/stores/useProductStore"; +import { motion } from "motion/react"; +import { SheetOverlay } from "@/components/v2/sheet-overlay/SheetOverlay"; +import { useIsCusPlanEditor } from "@/hooks/stores/useProductStore"; import { useSheetStore } from "@/hooks/stores/useSheetStore"; import { CustomerPlanEditorBar } from "@/views/customers2/customer-plan/CustomerPlanEditorBar"; import { CustomerPlanInfoBox } from "@/views/customers2/customer-plan/CustomerPlanInfoBox"; @@ -14,66 +10,8 @@ import { EditPlanHeader } from "./EditPlanHeader"; import PlanCard from "./plan-card/PlanCard"; import { SaveChangesBar } from "./SaveChangesBar"; -function shouldCloseSheetOnMouseDown({ - e, - item, - sheetType, - hasItemChanges, -}: { - e: React.MouseEvent; - item: ReturnType; - sheetType: string | null; - hasItemChanges: boolean; -}): boolean { - // Don't close if item has unsaved changes - if (hasItemChanges) { - return false; - } - - // Don't close if item is invalid - // if (item && !checkItemIsValid(item, false)) { - // return false; - // } - - // Get the active element before blur happens - const activeElement = document.activeElement; - - if ( - activeElement && - activeElement !== document.body && - activeElement instanceof HTMLElement - ) { - // Only apply blur behavior to inputs, textareas, and selects - const isInputElement = - activeElement.tagName === "INPUT" || - activeElement.tagName === "TEXTAREA" || - activeElement.tagName === "SELECT"; - - if (!isInputElement) { - // Not an input, proceed with normal close behavior - return !!sheetType; - } - - // Check if the active element is within the sheet (not in the main content area) - const clickTarget = e.target as HTMLElement; - const isActiveInSheet = !clickTarget.contains(activeElement); - - if (isActiveInSheet) { - activeElement.blur(); - e.preventDefault(); // Prevent default to stop the click from propagating - return false; - } - } - - // If the click is outside the sheet and no input is focused, close the sheet - return !!sheetType; -} - export const PlanEditor = () => { - const closeSheet = useSheetStore((s) => s.closeSheet); const sheetType = useSheetStore((s) => s.type); - const item = useCurrentItem(); - const hasItemChanges = useHasItemChanges(); return (
@@ -100,32 +38,7 @@ export const PlanEditor = () => { )}
- {createPortal( - - {sheetType && ( - { - if ( - shouldCloseSheetOnMouseDown({ - e, - item, - sheetType, - hasItemChanges, - }) - ) { - closeSheet(); - } - }} - /> - )} - , - document.body, - )} + diff --git a/vite/src/views/products/plan/components/SaveChangesBar.tsx b/vite/src/views/products/plan/components/SaveChangesBar.tsx index 2ee2c04b7..b74aaf63e 100644 --- a/vite/src/views/products/plan/components/SaveChangesBar.tsx +++ b/vite/src/views/products/plan/components/SaveChangesBar.tsx @@ -128,9 +128,9 @@ export const SaveChangesBar = ({

You have unsaved changes

- + { + if (oldId !== newId && product.items) { + // Wait for features to be refetched to avoid race condition + await refetch(); + // Update the feature_id in the product item + const updatedItems = product.items.map((i) => + i.feature_id === oldId ? { ...i, feature_id: newId } : i, + ); + setProduct({ ...product, items: updatedItems }); + + // Also update initialItem so it doesn't show as having changes + if (item?.feature_id === oldId) { + setInitialItem({ ...item, feature_id: newId }); + } + } + }; const emptyPriceItem = item?.usage_model && @@ -56,6 +81,16 @@ export function EditPlanFeatureSheet({ {feature?.name}

} + action={ + } + onClick={() => setEditFeatureOpen(true)} + > + Edit Feature + + } /> )} @@ -98,6 +133,23 @@ export function EditPlanFeatureSheet({ {/* Footer stays at bottom */} {showFooter && } + + {/* Edit Feature Sheet */} + {feature?.type === FeatureType.CreditSystem ? ( + + ) : ( + + )}
); } diff --git a/vite/src/views/products/plan/utils/getDefaultItem.ts b/vite/src/views/products/plan/utils/getDefaultItem.ts index 6c881a88e..1b9b46706 100644 --- a/vite/src/views/products/plan/utils/getDefaultItem.ts +++ b/vite/src/views/products/plan/utils/getDefaultItem.ts @@ -16,22 +16,27 @@ export const getDefaultItem = ({ feature, }); + // For static (boolean) features, reset_usage_when_enabled is not applicable + // since they don't track usage. Setting it causes mismatch with backend + // which doesn't store this field for boolean features. + const isStaticFeature = itemFeatureType === ProductItemFeatureType.Static; + const isContinuousUse = + itemFeatureType === ProductItemFeatureType.ContinuousUse; + // Create a new item with the selected feature const newItem = { feature_id: feature.id, feature_type: itemFeatureType, included_usage: null, interval: - itemFeatureType === ProductItemFeatureType.ContinuousUse || - itemFeatureType === ProductItemFeatureType.Static - ? null - : ProductItemInterval.Month, + isContinuousUse || isStaticFeature ? null : ProductItemInterval.Month, price: null, tiers: null, billing_units: 1, entity_feature_id: null, - reset_usage_when_enabled: - itemFeatureType !== ProductItemFeatureType.ContinuousUse, + // Only set reset_usage_when_enabled for usage-tracked features + // Boolean/static features don't track usage, so this field is not applicable + reset_usage_when_enabled: isStaticFeature ? undefined : !isContinuousUse, }; return newItem; diff --git a/vite/src/views/products/products/components/product-list/ProductListColumns.tsx b/vite/src/views/products/products/components/product-list/ProductListColumns.tsx index beb1731a0..c44f84721 100644 --- a/vite/src/views/products/products/components/product-list/ProductListColumns.tsx +++ b/vite/src/views/products/products/components/product-list/ProductListColumns.tsx @@ -17,7 +17,7 @@ export const createProductListColumns = ({ size: 300, header: "Name", accessorKey: "name", - enableSorting: false, + enableSorting: true, cell: ({ row }: { row: Row }) => { return (
diff --git a/vite/src/views/products/products/components/product-list/ProductListTable.tsx b/vite/src/views/products/products/components/product-list/ProductListTable.tsx index 61468a8f7..ede408760 100644 --- a/vite/src/views/products/products/components/product-list/ProductListTable.tsx +++ b/vite/src/views/products/products/components/product-list/ProductListTable.tsx @@ -1,6 +1,7 @@ import { isOneOffProductV2, type ProductV2 } from "@autumn/shared"; import { CubeIcon } from "@phosphor-icons/react"; -import { useMemo } from "react"; +import type { SortingState } from "@tanstack/react-table"; +import { useMemo, useState } from "react"; import { Table } from "@/components/general/table"; import { SectionTag } from "@/components/v2/badges/SectionTag"; import { EmptyState } from "@/components/v2/empty-states/EmptyState"; @@ -20,6 +21,9 @@ export function ProductListTable() { const { products, counts, isCountsLoading } = useProductsQuery(); const { queryStates } = useProductsQueryState(); + // Shared sorting state for all tables + const [sorting, setSorting] = useState([]); + const { recurringBasePlans, recurringAddOnPlans, oneTimePlans } = useMemo(() => { const filtered = products?.filter((product) => @@ -100,6 +104,8 @@ export function ProductListTable() { globalFilterFn: "includesString", enableGlobalFilter: true, enableSorting: true, + state: { sorting }, + onSortingChange: setSorting, }, }); @@ -110,6 +116,8 @@ export function ProductListTable() { globalFilterFn: "includesString", enableGlobalFilter: true, enableSorting: true, + state: { sorting }, + onSortingChange: setSorting, }, }); @@ -120,6 +128,8 @@ export function ProductListTable() { globalFilterFn: "includesString", enableGlobalFilter: true, enableSorting: true, + state: { sorting }, + onSortingChange: setSorting, }, }); From fed83da23ab06f6a76a781ad4878c37dcb3e14e3 Mon Sep 17 00:00:00 2001 From: Ayush Rodrigues Date: Sun, 18 Jan 2026 23:48:45 +0000 Subject: [PATCH 27/34] rm uneeded file --- .cursor/debug.log | 203 ---------------------------------------------- 1 file changed, 203 deletions(-) delete mode 100644 .cursor/debug.log diff --git a/.cursor/debug.log b/.cursor/debug.log deleted file mode 100644 index 348613480..000000000 --- a/.cursor/debug.log +++ /dev/null @@ -1,203 +0,0 @@ -{"location":"useProductSync.ts:useEffect","message":"sync effect triggered","data":{"isNewProduct":true,"isVersionChanged":true,"isProductUpdated":true,"productId":"free_plan","productVersion":1,"willUpdate":true},"timestamp":1768767206353,"sessionId":"debug-session","hypothesisId":"H4"} -{"location":"useProductSync.ts:setBaseProduct","message":"updating baseProduct","data":{"frontendProductItemsCount":1,"frontendProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38IUPp9xbsN9aTLxJWylhmhpSMd"}]},"timestamp":1768767206353,"sessionId":"debug-session","hypothesisId":"H4"} -{"location":"useProductSync.ts:useEffect","message":"sync effect triggered","data":{"isNewProduct":false,"isVersionChanged":false,"isProductUpdated":false,"productId":"free_plan","productVersion":1,"lastProductId":"free_plan","lastProductVersion":1,"willUpdate":false},"timestamp":1768767206354,"sessionId":"debug-session","hypothesisId":"H4"} -{"location":"useProductSync.ts:useEffect","message":"sync effect triggered","data":{"isNewProduct":false,"isVersionChanged":false,"isProductUpdated":false,"productId":"free_plan","productVersion":1,"lastProductId":"free_plan","lastProductVersion":1,"willUpdate":false},"timestamp":1768767206365,"sessionId":"debug-session","hypothesisId":"H4"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38IUPp9xbsN9aTLxJWylhmhpSMd"}],"baseProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38IUPp9xbsN9aTLxJWylhmhpSMd"}]},"timestamp":1768767206361,"sessionId":"debug-session","hypothesisId":"H1-H3"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38IUPp9xbsN9aTLxJWylhmhpSMd"}],"baseProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38IUPp9xbsN9aTLxJWylhmhpSMd"}]},"timestamp":1768767206361,"sessionId":"debug-session","hypothesisId":"H1-H3"} -{"location":"useProductSync.ts:useEffect","message":"sync effect triggered","data":{"isNewProduct":false,"isVersionChanged":false,"isProductUpdated":true,"productId":"free_plan","productVersion":1,"lastProductId":"free_plan","lastProductVersion":1,"willUpdate":true},"timestamp":1768767207732,"sessionId":"debug-session","hypothesisId":"H4"} -{"location":"useProductSync.ts:setBaseProduct","message":"updating baseProduct","data":{"frontendProductItemsCount":1,"frontendProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38IUPp9xbsN9aTLxJWylhmhpSMd"}]},"timestamp":1768767207732,"sessionId":"debug-session","hypothesisId":"H4"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38IUPp9xbsN9aTLxJWylhmhpSMd"}],"baseProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38IUPp9xbsN9aTLxJWylhmhpSMd"}]},"timestamp":1768767207732,"sessionId":"debug-session","hypothesisId":"H1-H3"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38IUPp9xbsN9aTLxJWylhmhpSMd"}],"baseProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38IUPp9xbsN9aTLxJWylhmhpSMd"}]},"timestamp":1768767207733,"sessionId":"debug-session","hypothesisId":"H1-H3"} -{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"analytics","item1Interval":null,"item1IncludedUsage":null,"items2Count":1,"items2FeatureIds":["messages"]},"timestamp":1768767208701,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38IUPp9xbsN9aTLxJWylhmhpSMd"},{"feature_id":"analytics","interval":null,"included_usage":null,"reset_usage_when_enabled":true}],"baseProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38IUPp9xbsN9aTLxJWylhmhpSMd"}]},"timestamp":1768767208701,"sessionId":"debug-session","hypothesisId":"H1-H3"} -{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"analytics","item1Interval":null,"item1IncludedUsage":null,"items2Count":1,"items2FeatureIds":["messages"]},"timestamp":1768767208701,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"analytics","item1Interval":null,"item1IncludedUsage":null,"items2Count":1,"items2FeatureIds":["messages"]},"timestamp":1768767208701,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38IUPp9xbsN9aTLxJWylhmhpSMd"},{"feature_id":"analytics","interval":null,"included_usage":null,"reset_usage_when_enabled":true}],"baseProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38IUPp9xbsN9aTLxJWylhmhpSMd"}]},"timestamp":1768767208701,"sessionId":"debug-session","hypothesisId":"H1-H3"} -{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"analytics","item1Interval":null,"item1IncludedUsage":null,"items2Count":1,"items2FeatureIds":["messages"]},"timestamp":1768767208702,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"useProductSync.ts:useEffect","message":"sync effect triggered","data":{"isNewProduct":false,"isVersionChanged":false,"isProductUpdated":false,"productId":"free_plan","productVersion":1,"lastProductId":"free_plan","lastProductVersion":1,"willUpdate":false},"timestamp":1768767208711,"sessionId":"debug-session","hypothesisId":"H4"} -{"location":"SaveChangesBar.tsx:handleSaveClicked:start","message":"save clicked - before save","data":{"productId":"free_plan","itemsCount":2,"items":[{"feature_id":"messages","interval":null,"included_usage":10},{"feature_id":"analytics","interval":null,"included_usage":null}]},"timestamp":1768767210156,"sessionId":"debug-session","hypothesisId":"H2"} -{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"analytics","item1Interval":null,"item1IncludedUsage":null,"items2Count":1,"items2FeatureIds":["messages"]},"timestamp":1768767217153,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"useProductSync.ts:useEffect","message":"sync effect triggered","data":{"isNewProduct":false,"isVersionChanged":true,"isProductUpdated":true,"productId":"free_plan","productVersion":2,"lastProductId":"free_plan","lastProductVersion":1,"willUpdate":true},"timestamp":1768767220542,"sessionId":"debug-session","hypothesisId":"H4"} -{"location":"useProductSync.ts:setBaseProduct","message":"updating baseProduct","data":{"frontendProductItemsCount":2,"frontendProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"},{"feature_id":"analytics","entitlement_id":"ent_38RfHfo63jeH40j8nB7K1BAHLeT"}]},"timestamp":1768767220542,"sessionId":"debug-session","hypothesisId":"H4"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"},{"feature_id":"analytics","entitlement_id":"ent_38RfHfo63jeH40j8nB7K1BAHLeT"}],"baseProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"},{"feature_id":"analytics","entitlement_id":"ent_38RfHfo63jeH40j8nB7K1BAHLeT"}]},"timestamp":1768767220548,"sessionId":"debug-session","hypothesisId":"H1-H3"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"},{"feature_id":"analytics","entitlement_id":"ent_38RfHfo63jeH40j8nB7K1BAHLeT"}],"baseProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"},{"feature_id":"analytics","entitlement_id":"ent_38RfHfo63jeH40j8nB7K1BAHLeT"}]},"timestamp":1768767220548,"sessionId":"debug-session","hypothesisId":"H1-H3"} -{"location":"useProductSync.ts:useEffect","message":"sync effect triggered","data":{"isNewProduct":false,"isVersionChanged":false,"isProductUpdated":false,"productId":"free_plan","productVersion":2,"lastProductId":"free_plan","lastProductVersion":2,"willUpdate":false},"timestamp":1768767220550,"sessionId":"debug-session","hypothesisId":"H4"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}],"baseProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"},{"feature_id":"analytics","entitlement_id":"ent_38RfHfo63jeH40j8nB7K1BAHLeT"}]},"timestamp":1768767226001,"sessionId":"debug-session","hypothesisId":"H1-H3"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}],"baseProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"},{"feature_id":"analytics","entitlement_id":"ent_38RfHfo63jeH40j8nB7K1BAHLeT"}]},"timestamp":1768767226002,"sessionId":"debug-session","hypothesisId":"H1-H3"} -{"location":"useProductSync.ts:useEffect","message":"sync effect triggered","data":{"isNewProduct":false,"isVersionChanged":false,"isProductUpdated":false,"productId":"free_plan","productVersion":2,"lastProductId":"free_plan","lastProductVersion":2,"willUpdate":false},"timestamp":1768767226003,"sessionId":"debug-session","hypothesisId":"H4"} -{"location":"SaveChangesBar.tsx:handleSaveClicked:start","message":"save clicked - before save","data":{"productId":"free_plan","itemsCount":1,"items":[{"feature_id":"messages","interval":null,"included_usage":10}]},"timestamp":1768767226796,"sessionId":"debug-session","hypothesisId":"H2"} -{"location":"SaveChangesBar.tsx:onSuccess","message":"save onSuccess - about to refetch","data":{},"timestamp":1768767232230,"sessionId":"debug-session","hypothesisId":"H4"} -{"location":"useProductSync.ts:useEffect","message":"sync effect triggered","data":{"isNewProduct":false,"isVersionChanged":false,"isProductUpdated":true,"productId":"free_plan","productVersion":2,"lastProductId":"free_plan","lastProductVersion":2,"willUpdate":true},"timestamp":1768767233610,"sessionId":"debug-session","hypothesisId":"H4"} -{"location":"useProductSync.ts:setBaseProduct","message":"updating baseProduct","data":{"frontendProductItemsCount":1,"frontendProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767233610,"sessionId":"debug-session","hypothesisId":"H4"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}],"baseProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767233611,"sessionId":"debug-session","hypothesisId":"H1-H3"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}],"baseProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767233611,"sessionId":"debug-session","hypothesisId":"H1-H3"} -{"location":"SaveChangesBar.tsx:onSuccess:afterRefetch","message":"refetch completed","data":{},"timestamp":1768767235270,"sessionId":"debug-session","hypothesisId":"H4"} -{"location":"SaveChangesBar.tsx:handleSaveClicked:end","message":"save completed","data":{"result":true},"timestamp":1768767235270,"sessionId":"debug-session","hypothesisId":"H2"} -{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"analytics","item1Interval":null,"item1IncludedUsage":null,"items2Count":1,"items2FeatureIds":["messages"]},"timestamp":1768767236608,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"},{"feature_id":"analytics","interval":null,"included_usage":null,"reset_usage_when_enabled":true}],"baseProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767236608,"sessionId":"debug-session","hypothesisId":"H1-H3"} -{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"analytics","item1Interval":null,"item1IncludedUsage":null,"items2Count":1,"items2FeatureIds":["messages"]},"timestamp":1768767236608,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"analytics","item1Interval":null,"item1IncludedUsage":null,"items2Count":1,"items2FeatureIds":["messages"]},"timestamp":1768767236608,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"},{"feature_id":"analytics","interval":null,"included_usage":null,"reset_usage_when_enabled":true}],"baseProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767236608,"sessionId":"debug-session","hypothesisId":"H1-H3"} -{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"analytics","item1Interval":null,"item1IncludedUsage":null,"items2Count":1,"items2FeatureIds":["messages"]},"timestamp":1768767236609,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"useProductSync.ts:useEffect","message":"sync effect triggered","data":{"isNewProduct":false,"isVersionChanged":false,"isProductUpdated":false,"productId":"free_plan","productVersion":2,"lastProductId":"free_plan","lastProductVersion":2,"willUpdate":false},"timestamp":1768767236614,"sessionId":"debug-session","hypothesisId":"H4"} -{"location":"SaveChangesBar.tsx:handleSaveClicked:start","message":"save clicked - before save","data":{"productId":"free_plan","itemsCount":2,"items":[{"feature_id":"messages","interval":null,"included_usage":10},{"feature_id":"analytics","interval":null,"included_usage":null}]},"timestamp":1768767237868,"sessionId":"debug-session","hypothesisId":"H2"} -{"location":"SaveChangesBar.tsx:onSuccess","message":"save onSuccess - about to refetch","data":{},"timestamp":1768767242801,"sessionId":"debug-session","hypothesisId":"H4"} -{"location":"useProductSync.ts:useEffect","message":"sync effect triggered","data":{"isNewProduct":false,"isVersionChanged":false,"isProductUpdated":true,"productId":"free_plan","productVersion":2,"lastProductId":"free_plan","lastProductVersion":2,"willUpdate":true},"timestamp":1768767244180,"sessionId":"debug-session","hypothesisId":"H4"} -{"location":"useProductSync.ts:setBaseProduct","message":"updating baseProduct","data":{"frontendProductItemsCount":2,"frontendProductItems":[{"feature_id":"analytics","entitlement_id":"ent_38RfKZrFZYfWslXTJOSbeoBjzkV"},{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767244181,"sessionId":"debug-session","hypothesisId":"H4"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"},{"feature_id":"analytics","interval":null,"included_usage":null,"reset_usage_when_enabled":true}],"baseProductItems":[{"feature_id":"analytics","entitlement_id":"ent_38RfKZrFZYfWslXTJOSbeoBjzkV"},{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767244181,"sessionId":"debug-session","hypothesisId":"H1-H3"} -{"location":"compareItemUtils.ts:featureItemsAreSame","message":"feature items comparison failed","data":{"featureId":"analytics","failedChecks":[{"check":"reset_usage_when_enabled","message":"Reset usage when enabled different: true !== false"}],"item1ResetUsage":true,"item2ResetUsage":false,"item1Interval":null,"item1IncludedUsage":null},"timestamp":1768767244181,"sessionId":"debug-session","hypothesisId":"H3"} -{"location":"compareItemUtils.ts:featureItemsAreSame","message":"feature items comparison failed","data":{"featureId":"analytics","failedChecks":[{"check":"reset_usage_when_enabled","message":"Reset usage when enabled different: true !== false"}],"item1ResetUsage":true,"item2ResetUsage":false,"item1Interval":null,"item1IncludedUsage":null},"timestamp":1768767244181,"sessionId":"debug-session","hypothesisId":"H3"} -{"location":"compareProductUtils.ts:productsAreSame:itemsDifferent","message":"items not same after comparison","data":{"item1":"{\"feature_id\":\"analytics\",\"feature_type\":\"static\",\"included_usage\":null,\"interval\":null,\"price\":null,\"tiers\":null,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":true,\"config\":{\"on_increase\":\"prorate_immediately\",\"on_decrease\":\"prorate_immediately\"}}","item2":"{\"feature_id\":\"analytics\",\"entity_feature_id\":null,\"entitlement_id\":\"ent_38RfKZrFZYfWslXTJOSbeoBjzkV\",\"feature_type\":\"static\",\"reset_usage_when_enabled\":false,\"config\":{\"on_increase\":\"prorate_immediately\",\"on_decrease\":\"prorate_immediately\"}}","featureId":"analytics"},"timestamp":1768767244181,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"compareProductUtils.ts:productsAreSame:itemsDifferent","message":"items not same after comparison","data":{"item1":"{\"feature_id\":\"analytics\",\"feature_type\":\"static\",\"included_usage\":null,\"interval\":null,\"price\":null,\"tiers\":null,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":true,\"config\":{\"on_increase\":\"prorate_immediately\",\"on_decrease\":\"prorate_immediately\"}}","item2":"{\"feature_id\":\"analytics\",\"entity_feature_id\":null,\"entitlement_id\":\"ent_38RfKZrFZYfWslXTJOSbeoBjzkV\",\"feature_type\":\"static\",\"reset_usage_when_enabled\":false,\"config\":{\"on_increase\":\"prorate_immediately\",\"on_decrease\":\"prorate_immediately\"}}","featureId":"analytics"},"timestamp":1768767244182,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"compareItemUtils.ts:featureItemsAreSame","message":"feature items comparison failed","data":{"featureId":"analytics","failedChecks":[{"check":"reset_usage_when_enabled","message":"Reset usage when enabled different: true !== false"}],"item1ResetUsage":true,"item2ResetUsage":false,"item1Interval":null,"item1IncludedUsage":null},"timestamp":1768767244182,"sessionId":"debug-session","hypothesisId":"H3"} -{"location":"compareProductUtils.ts:productsAreSame:itemsDifferent","message":"items not same after comparison","data":{"item1":"{\"feature_id\":\"analytics\",\"feature_type\":\"static\",\"included_usage\":null,\"interval\":null,\"price\":null,\"tiers\":null,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":true,\"config\":{\"on_increase\":\"prorate_immediately\",\"on_decrease\":\"prorate_immediately\"}}","item2":"{\"feature_id\":\"analytics\",\"entity_feature_id\":null,\"entitlement_id\":\"ent_38RfKZrFZYfWslXTJOSbeoBjzkV\",\"feature_type\":\"static\",\"reset_usage_when_enabled\":false,\"config\":{\"on_increase\":\"prorate_immediately\",\"on_decrease\":\"prorate_immediately\"}}","featureId":"analytics"},"timestamp":1768767244182,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"},{"feature_id":"analytics","interval":null,"included_usage":null,"reset_usage_when_enabled":true}],"baseProductItems":[{"feature_id":"analytics","entitlement_id":"ent_38RfKZrFZYfWslXTJOSbeoBjzkV"},{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767244182,"sessionId":"debug-session","hypothesisId":"H1-H3"} -{"location":"compareItemUtils.ts:featureItemsAreSame","message":"feature items comparison failed","data":{"featureId":"analytics","failedChecks":[{"check":"reset_usage_when_enabled","message":"Reset usage when enabled different: true !== false"}],"item1ResetUsage":true,"item2ResetUsage":false,"item1Interval":null,"item1IncludedUsage":null},"timestamp":1768767244182,"sessionId":"debug-session","hypothesisId":"H3"} -{"location":"compareProductUtils.ts:productsAreSame:itemsDifferent","message":"items not same after comparison","data":{"item1":"{\"feature_id\":\"analytics\",\"feature_type\":\"static\",\"included_usage\":null,\"interval\":null,\"price\":null,\"tiers\":null,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":true,\"config\":{\"on_increase\":\"prorate_immediately\",\"on_decrease\":\"prorate_immediately\"}}","item2":"{\"feature_id\":\"analytics\",\"entity_feature_id\":null,\"entitlement_id\":\"ent_38RfKZrFZYfWslXTJOSbeoBjzkV\",\"feature_type\":\"static\",\"reset_usage_when_enabled\":false,\"config\":{\"on_increase\":\"prorate_immediately\",\"on_decrease\":\"prorate_immediately\"}}","featureId":"analytics"},"timestamp":1768767244182,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"SaveChangesBar.tsx:onSuccess:afterRefetch","message":"refetch completed","data":{},"timestamp":1768767245839,"sessionId":"debug-session","hypothesisId":"H4"} -{"location":"SaveChangesBar.tsx:handleSaveClicked:end","message":"save completed","data":{"result":true},"timestamp":1768767245839,"sessionId":"debug-session","hypothesisId":"H2"} -{"location":"compareItemUtils.ts:featureItemsAreSame","message":"feature items comparison failed","data":{"featureId":"analytics","failedChecks":[{"check":"reset_usage_when_enabled","message":"Reset usage when enabled different: true !== false"}],"item1ResetUsage":true,"item2ResetUsage":false,"item1Interval":null,"item1IncludedUsage":null},"timestamp":1768767394297,"sessionId":"debug-session","hypothesisId":"H3"} -{"location":"compareItemUtils.ts:featureItemsAreSame","message":"feature items comparison failed","data":{"featureId":"analytics","failedChecks":[{"check":"reset_usage_when_enabled","message":"Reset usage when enabled different: true !== false"}],"item1ResetUsage":true,"item2ResetUsage":false,"item1Interval":null,"item1IncludedUsage":null},"timestamp":1768767394298,"sessionId":"debug-session","hypothesisId":"H3"} -{"location":"compareProductUtils.ts:productsAreSame:itemsDifferent","message":"items not same after comparison","data":{"item1":"{\"feature_id\":\"analytics\",\"feature_type\":\"static\",\"included_usage\":null,\"interval\":null,\"price\":null,\"tiers\":null,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":true,\"config\":{\"on_increase\":\"prorate_immediately\",\"on_decrease\":\"prorate_immediately\"}}","item2":"{\"feature_id\":\"analytics\",\"entity_feature_id\":null,\"entitlement_id\":\"ent_38RfKZrFZYfWslXTJOSbeoBjzkV\",\"feature_type\":\"static\",\"reset_usage_when_enabled\":false,\"config\":{\"on_increase\":\"prorate_immediately\",\"on_decrease\":\"prorate_immediately\"}}","featureId":"analytics"},"timestamp":1768767394297,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"compareProductUtils.ts:productsAreSame:itemsDifferent","message":"items not same after comparison","data":{"item1":"{\"feature_id\":\"analytics\",\"feature_type\":\"static\",\"included_usage\":null,\"interval\":null,\"price\":null,\"tiers\":null,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":true,\"config\":{\"on_increase\":\"prorate_immediately\",\"on_decrease\":\"prorate_immediately\"}}","item2":"{\"feature_id\":\"analytics\",\"entity_feature_id\":null,\"entitlement_id\":\"ent_38RfKZrFZYfWslXTJOSbeoBjzkV\",\"feature_type\":\"static\",\"reset_usage_when_enabled\":false,\"config\":{\"on_increase\":\"prorate_immediately\",\"on_decrease\":\"prorate_immediately\"}}","featureId":"analytics"},"timestamp":1768767394298,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"},{"feature_id":"analytics","interval":null,"included_usage":null,"reset_usage_when_enabled":true}],"baseProductItems":[{"feature_id":"analytics","entitlement_id":"ent_38RfKZrFZYfWslXTJOSbeoBjzkV"},{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767394297,"sessionId":"debug-session","hypothesisId":"H1-H3"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"},{"feature_id":"analytics","interval":null,"included_usage":null,"reset_usage_when_enabled":true}],"baseProductItems":[{"feature_id":"analytics","entitlement_id":"ent_38RfKZrFZYfWslXTJOSbeoBjzkV"},{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767394298,"sessionId":"debug-session","hypothesisId":"H1-H3"} -{"location":"compareItemUtils.ts:featureItemsAreSame","message":"feature items comparison failed","data":{"featureId":"analytics","failedChecks":[{"check":"reset_usage_when_enabled","message":"Reset usage when enabled different: true !== false"}],"item1ResetUsage":true,"item2ResetUsage":false,"item1Interval":null,"item1IncludedUsage":null},"timestamp":1768767394298,"sessionId":"debug-session","hypothesisId":"H3"} -{"location":"compareProductUtils.ts:productsAreSame:itemsDifferent","message":"items not same after comparison","data":{"item1":"{\"feature_id\":\"analytics\",\"feature_type\":\"static\",\"included_usage\":null,\"interval\":null,\"price\":null,\"tiers\":null,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":true,\"config\":{\"on_increase\":\"prorate_immediately\",\"on_decrease\":\"prorate_immediately\"}}","item2":"{\"feature_id\":\"analytics\",\"entity_feature_id\":null,\"entitlement_id\":\"ent_38RfKZrFZYfWslXTJOSbeoBjzkV\",\"feature_type\":\"static\",\"reset_usage_when_enabled\":false,\"config\":{\"on_increase\":\"prorate_immediately\",\"on_decrease\":\"prorate_immediately\"}}","featureId":"analytics"},"timestamp":1768767394298,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"compareItemUtils.ts:featureItemsAreSame","message":"feature items comparison failed","data":{"featureId":"analytics","failedChecks":[{"check":"reset_usage_when_enabled","message":"Reset usage when enabled different: true !== false"}],"item1ResetUsage":true,"item2ResetUsage":false,"item1Interval":null,"item1IncludedUsage":null},"timestamp":1768767394297,"sessionId":"debug-session","hypothesisId":"H3"} -{"location":"compareProductUtils.ts:productsAreSame:itemsDifferent","message":"items not same after comparison","data":{"item1":"{\"feature_id\":\"analytics\",\"feature_type\":\"static\",\"included_usage\":null,\"interval\":null,\"price\":null,\"tiers\":null,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":true,\"config\":{\"on_increase\":\"prorate_immediately\",\"on_decrease\":\"prorate_immediately\"}}","item2":"{\"feature_id\":\"analytics\",\"entity_feature_id\":null,\"entitlement_id\":\"ent_38RfKZrFZYfWslXTJOSbeoBjzkV\",\"feature_type\":\"static\",\"reset_usage_when_enabled\":false,\"config\":{\"on_increase\":\"prorate_immediately\",\"on_decrease\":\"prorate_immediately\"}}","featureId":"analytics"},"timestamp":1768767394297,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"useProductSync.ts:useEffect","message":"sync effect triggered","data":{"isNewProduct":true,"isVersionChanged":true,"isProductUpdated":true,"productId":"free_plan","productVersion":2,"willUpdate":true},"timestamp":1768767394313,"sessionId":"debug-session","hypothesisId":"H4"} -{"location":"useProductSync.ts:setBaseProduct","message":"updating baseProduct","data":{"frontendProductItemsCount":2,"frontendProductItems":[{"feature_id":"analytics","entitlement_id":"ent_38RfKZrFZYfWslXTJOSbeoBjzkV"},{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767394314,"sessionId":"debug-session","hypothesisId":"H4"} -{"location":"useProductSync.ts:useEffect","message":"sync effect triggered","data":{"isNewProduct":false,"isVersionChanged":false,"isProductUpdated":false,"productId":"free_plan","productVersion":2,"lastProductId":"free_plan","lastProductVersion":2,"willUpdate":false},"timestamp":1768767394318,"sessionId":"debug-session","hypothesisId":"H4"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"analytics","entitlement_id":"ent_38RfKZrFZYfWslXTJOSbeoBjzkV"},{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}],"baseProductItems":[{"feature_id":"analytics","entitlement_id":"ent_38RfKZrFZYfWslXTJOSbeoBjzkV"},{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767394332,"sessionId":"debug-session","hypothesisId":"H1-H3"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"analytics","entitlement_id":"ent_38RfKZrFZYfWslXTJOSbeoBjzkV"},{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}],"baseProductItems":[{"feature_id":"analytics","entitlement_id":"ent_38RfKZrFZYfWslXTJOSbeoBjzkV"},{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767394332,"sessionId":"debug-session","hypothesisId":"H1-H3"} -{"location":"useProductSync.ts:useEffect","message":"sync effect triggered","data":{"isNewProduct":false,"isVersionChanged":false,"isProductUpdated":false,"productId":"free_plan","productVersion":2,"lastProductId":"free_plan","lastProductVersion":2,"willUpdate":false},"timestamp":1768767394337,"sessionId":"debug-session","hypothesisId":"H4"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}],"baseProductItems":[{"feature_id":"analytics","entitlement_id":"ent_38RfKZrFZYfWslXTJOSbeoBjzkV"},{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767501092,"sessionId":"debug-session","hypothesisId":"H1-H3"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}],"baseProductItems":[{"feature_id":"analytics","entitlement_id":"ent_38RfKZrFZYfWslXTJOSbeoBjzkV"},{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767501092,"sessionId":"debug-session","hypothesisId":"H1-H3"} -{"location":"useProductSync.ts:useEffect","message":"sync effect triggered","data":{"isNewProduct":false,"isVersionChanged":false,"isProductUpdated":false,"productId":"free_plan","productVersion":2,"lastProductId":"free_plan","lastProductVersion":2,"willUpdate":false},"timestamp":1768767501096,"sessionId":"debug-session","hypothesisId":"H4"} -{"location":"SaveChangesBar.tsx:handleSaveClicked:start","message":"save clicked - before save","data":{"productId":"free_plan","itemsCount":1,"items":[{"feature_id":"messages","interval":null,"included_usage":10}]},"timestamp":1768767501888,"sessionId":"debug-session","hypothesisId":"H2"} -{"location":"SaveChangesBar.tsx:onSuccess","message":"save onSuccess - about to refetch","data":{},"timestamp":1768767508153,"sessionId":"debug-session","hypothesisId":"H4"} -{"location":"useProductSync.ts:useEffect","message":"sync effect triggered","data":{"isNewProduct":false,"isVersionChanged":false,"isProductUpdated":true,"productId":"free_plan","productVersion":2,"lastProductId":"free_plan","lastProductVersion":2,"willUpdate":true},"timestamp":1768767509550,"sessionId":"debug-session","hypothesisId":"H4"} -{"location":"useProductSync.ts:setBaseProduct","message":"updating baseProduct","data":{"frontendProductItemsCount":1,"frontendProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767509550,"sessionId":"debug-session","hypothesisId":"H4"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}],"baseProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767509550,"sessionId":"debug-session","hypothesisId":"H1-H3"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}],"baseProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767509551,"sessionId":"debug-session","hypothesisId":"H1-H3"} -{"location":"SaveChangesBar.tsx:onSuccess:afterRefetch","message":"refetch completed","data":{},"timestamp":1768767511210,"sessionId":"debug-session","hypothesisId":"H4"} -{"location":"SaveChangesBar.tsx:handleSaveClicked:end","message":"save completed","data":{"result":true},"timestamp":1768767511210,"sessionId":"debug-session","hypothesisId":"H2"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"},{"feature_id":"analytics","interval":null,"included_usage":null}],"baseProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767525751,"sessionId":"debug-session","hypothesisId":"H1-H3"} -{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"analytics","item1Interval":null,"item1IncludedUsage":null,"items2Count":1,"items2FeatureIds":["messages"]},"timestamp":1768767525751,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"analytics","item1Interval":null,"item1IncludedUsage":null,"items2Count":1,"items2FeatureIds":["messages"]},"timestamp":1768767525751,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"analytics","item1Interval":null,"item1IncludedUsage":null,"items2Count":1,"items2FeatureIds":["messages"]},"timestamp":1768767525751,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"analytics","item1Interval":null,"item1IncludedUsage":null,"items2Count":1,"items2FeatureIds":["messages"]},"timestamp":1768767525751,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"},{"feature_id":"analytics","interval":null,"included_usage":null}],"baseProductItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767525751,"sessionId":"debug-session","hypothesisId":"H1-H3"} -{"location":"useProductSync.ts:useEffect","message":"sync effect triggered","data":{"isNewProduct":false,"isVersionChanged":false,"isProductUpdated":false,"productId":"free_plan","productVersion":2,"lastProductId":"free_plan","lastProductVersion":2,"willUpdate":false},"timestamp":1768767525757,"sessionId":"debug-session","hypothesisId":"H4"} -{"location":"SaveChangesBar.tsx:handleSaveClicked:start","message":"save clicked - before save","data":{"productId":"free_plan","itemsCount":2,"items":[{"feature_id":"messages","interval":null,"included_usage":10},{"feature_id":"analytics","interval":null,"included_usage":null}]},"timestamp":1768767526804,"sessionId":"debug-session","hypothesisId":"H2"} -{"location":"SaveChangesBar.tsx:onSuccess","message":"save onSuccess - about to refetch","data":{},"timestamp":1768767531740,"sessionId":"debug-session","hypothesisId":"H4"} -{"location":"useProductSync.ts:useEffect","message":"sync effect triggered","data":{"isNewProduct":false,"isVersionChanged":false,"isProductUpdated":true,"productId":"free_plan","productVersion":2,"lastProductId":"free_plan","lastProductVersion":2,"willUpdate":true},"timestamp":1768767533130,"sessionId":"debug-session","hypothesisId":"H4"} -{"location":"useProductSync.ts:setBaseProduct","message":"updating baseProduct","data":{"frontendProductItemsCount":2,"frontendProductItems":[{"feature_id":"analytics","entitlement_id":"ent_38RfuuS2oOJiQBLwQrlKxo8TOM4"},{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767533131,"sessionId":"debug-session","hypothesisId":"H4"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"},{"feature_id":"analytics","interval":null,"included_usage":null}],"baseProductItems":[{"feature_id":"analytics","entitlement_id":"ent_38RfuuS2oOJiQBLwQrlKxo8TOM4"},{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767533131,"sessionId":"debug-session","hypothesisId":"H1-H3"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"},{"feature_id":"analytics","interval":null,"included_usage":null}],"baseProductItems":[{"feature_id":"analytics","entitlement_id":"ent_38RfuuS2oOJiQBLwQrlKxo8TOM4"},{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767533131,"sessionId":"debug-session","hypothesisId":"H1-H3"} -{"location":"SaveChangesBar.tsx:onSuccess:afterRefetch","message":"refetch completed","data":{},"timestamp":1768767534789,"sessionId":"debug-session","hypothesisId":"H4"} -{"location":"SaveChangesBar.tsx:handleSaveClicked:end","message":"save completed","data":{"result":true},"timestamp":1768767534789,"sessionId":"debug-session","hypothesisId":"H2"} -{"location":"useProductSync.ts:useEffect","message":"sync effect triggered","data":{"isNewProduct":false,"isVersionChanged":false,"isProductUpdated":false,"productId":"free_plan","productVersion":2,"lastProductId":"free_plan","lastProductVersion":2,"willUpdate":false},"timestamp":1768767554592,"sessionId":"debug-session","hypothesisId":"H4"} -{"location":"useProductSync.ts:useEffect","message":"sync effect triggered","data":{"isNewProduct":true,"isVersionChanged":true,"isProductUpdated":true,"productId":"free_plan","productVersion":2,"willUpdate":true},"timestamp":1768767559098,"sessionId":"debug-session","hypothesisId":"H4"} -{"location":"useProductSync.ts:setBaseProduct","message":"updating baseProduct","data":{"frontendProductItemsCount":2,"frontendProductItems":[{"feature_id":"analytics","entitlement_id":"ent_38RfuuS2oOJiQBLwQrlKxo8TOM4"},{"feature_id":"messages","interval":null,"included_usage":10,"reset_usage_when_enabled":true,"entitlement_id":"ent_38RfHdX9I9BqCWnytV2ylYvGgnu"}]},"timestamp":1768767559098,"sessionId":"debug-session","hypothesisId":"H4"} -{"location":"useProductSync.ts:useEffect","message":"sync effect triggered","data":{"isNewProduct":false,"isVersionChanged":false,"isProductUpdated":false,"productId":"free_plan","productVersion":2,"lastProductId":"free_plan","lastProductVersion":2,"willUpdate":false},"timestamp":1768767559099,"sessionId":"debug-session","hypothesisId":"H4"} -{"location":"useProductSync.ts:useEffect","message":"sync effect triggered","data":{"isNewProduct":false,"isVersionChanged":false,"isProductUpdated":false,"productId":"free_plan","productVersion":2,"lastProductId":"free_plan","lastProductVersion":2,"willUpdate":false},"timestamp":1768767559112,"sessionId":"debug-session","hypothesisId":"H4"} -{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":null,"item1IncludedUsage":null,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767564109,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":null,"item1IncludedUsage":null,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767564109,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":null,"item1IncludedUsage":null,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767564109,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":null,"item1IncludedUsage":null,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767564109,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":"month","item1IncludedUsage":null,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767568548,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":"month","item1IncludedUsage":null,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767568548,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":"month","item1IncludedUsage":null,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767568548,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":"month","item1IncludedUsage":null,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767568548,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":"month","item1IncludedUsage":2,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767569476,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":"month","item1IncludedUsage":2,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767569476,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":"month","item1IncludedUsage":2,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767569476,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":"month","item1IncludedUsage":2,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767569476,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":"month","item1IncludedUsage":25,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767569717,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":"month","item1IncludedUsage":25,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767569717,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":"month","item1IncludedUsage":25,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767569717,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":"month","item1IncludedUsage":25,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767569717,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"SaveChangesBar.tsx:handleSaveClicked:start","message":"save clicked - before save","data":{"productId":"free_plan","itemsCount":3,"items":[{"feature_id":"analytics"},{"feature_id":"messages","interval":null,"included_usage":10},{"feature_id":"seats","interval":"month","included_usage":25}]},"timestamp":1768767573201,"sessionId":"debug-session","hypothesisId":"H2"} -{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":"month","item1IncludedUsage":25,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767573208,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":"month","item1IncludedUsage":25,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767573209,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":"month","item1IncludedUsage":25,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767573209,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":"month","item1IncludedUsage":25,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767573209,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"SaveChangesBar.tsx:handleSaveClicked:end","message":"save completed","data":{"result":false},"timestamp":1768767574954,"sessionId":"debug-session","hypothesisId":"H2"} -{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":"month","item1IncludedUsage":25,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767575485,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":"month","item1IncludedUsage":25,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767575485,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":"month","item1IncludedUsage":25,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767575485,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"compareProductUtils.ts:productsAreSame:noSimilarItem","message":"no similar item found for item1","data":{"item1FeatureId":"seats","item1Interval":"month","item1IncludedUsage":25,"items2Count":2,"items2FeatureIds":["analytics","messages"]},"timestamp":1768767575485,"sessionId":"debug-session","hypothesisId":"H2-H3"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768767861061,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768767861062,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768492990,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768492990,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768494722,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768494722,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768494856,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768494857,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768503769,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768503770,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":null,"included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":null}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768509714,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":null,"included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":null}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768509714,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":[{"to":"inf","amount":0}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768510972,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":[{"to":"inf","amount":0}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768510973,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":[{"to":"inf","amount":0}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768512258,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":[{"to":"inf","amount":0}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768512259,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":[{"to":"inf","amount":4}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768513093,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":[{"to":"inf","amount":4}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768513093,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":[{"to":"inf","amount":4}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768513323,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":[{"to":"inf","amount":4}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768513323,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"compareItemUtils.ts:featurePriceItemsAreSame","message":"feature price items comparison failed","data":{"featureId":"seats","failedEntChecks":[{"check":"included_usage","message":"Included usage different: null != 0"}],"failedPriceChecks":[],"item1":"{\"feature_id\":\"seats\",\"feature_type\":\"continuous_use\",\"included_usage\":null,\"interval\":\"month\",\"price\":4,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":false,\"usage_model\":\"pay_per_use\",\"config\":{\"on_increase\":\"prorate_immediately\",\"on_decrease\":\"prorate_immediately\"}}","item2":"{\"feature_id\":\"seats\",\"feature_type\":\"continuous_use\",\"included_usage\":0,\"interval\":\"month\",\"interval_count\":1,\"price\":4,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":false,\"usage_model\":\"pay_per_use\",\"created_at\":1768768517976,\"entitlement_id\":\"ent_38Rhv08gZdC1AOrMvs20Gt4exbK\",\"price_id\":\"pr_38RhuzPJAjXGGc5JaF7JBQnKclZ\",\"price_config\":{\"type\":\"usage\",\"interval\":\"month\",\"bill_when\":\"end_of_period\",\"feature_id\":\"seats\",\"usage_tiers\":[{\"to\":\"inf\",\"amount\":4}],\"billing_units\":1,\"interval_count\":1,\"should_prorate\":true,\"stripe_price_id\":\"price_1Sr2gtG2wlWesuVT9hSb0qPO\",\"stripe_product_id\":\"prod_Tog21hujMdhsAJ\",\"internal_feature_id\":\"fe_38IUUvu4BoVK96JwbDZfJHwcRyD\",\"stripe_placeholder_price_id\":\"price_1Sr2guG2wlWesuVTfbCs2bkT\"},\"config\":{\"on_decrease\":\"prorate_immediately\",\"on_increase\":\"prorate_immediately\"},\"usage_limit\":null}"},"timestamp":1768768522738,"sessionId":"debug-session","hypothesisId":"H3"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":[{"to":"inf","amount":4}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768522738,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"compareItemUtils.ts:featurePriceItemsAreSame","message":"feature price items comparison failed","data":{"featureId":"seats","failedEntChecks":[{"check":"included_usage","message":"Included usage different: null != 0"}],"failedPriceChecks":[],"item1":"{\"feature_id\":\"seats\",\"feature_type\":\"continuous_use\",\"included_usage\":null,\"interval\":\"month\",\"price\":4,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":false,\"usage_model\":\"pay_per_use\",\"config\":{\"on_increase\":\"prorate_immediately\",\"on_decrease\":\"prorate_immediately\"}}","item2":"{\"feature_id\":\"seats\",\"feature_type\":\"continuous_use\",\"included_usage\":0,\"interval\":\"month\",\"interval_count\":1,\"price\":4,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":false,\"usage_model\":\"pay_per_use\",\"created_at\":1768768517976,\"entitlement_id\":\"ent_38Rhv08gZdC1AOrMvs20Gt4exbK\",\"price_id\":\"pr_38RhuzPJAjXGGc5JaF7JBQnKclZ\",\"price_config\":{\"type\":\"usage\",\"interval\":\"month\",\"bill_when\":\"end_of_period\",\"feature_id\":\"seats\",\"usage_tiers\":[{\"to\":\"inf\",\"amount\":4}],\"billing_units\":1,\"interval_count\":1,\"should_prorate\":true,\"stripe_price_id\":\"price_1Sr2gtG2wlWesuVT9hSb0qPO\",\"stripe_product_id\":\"prod_Tog21hujMdhsAJ\",\"internal_feature_id\":\"fe_38IUUvu4BoVK96JwbDZfJHwcRyD\",\"stripe_placeholder_price_id\":\"price_1Sr2guG2wlWesuVTfbCs2bkT\"},\"config\":{\"on_decrease\":\"prorate_immediately\",\"on_increase\":\"prorate_immediately\"},\"usage_limit\":null}"},"timestamp":1768768522739,"sessionId":"debug-session","hypothesisId":"H3"} -{"location":"compareItemUtils.ts:featurePriceItemsAreSame","message":"feature price items comparison failed","data":{"featureId":"seats","failedEntChecks":[{"check":"included_usage","message":"Included usage different: null != 0"}],"failedPriceChecks":[],"item1":"{\"feature_id\":\"seats\",\"feature_type\":\"continuous_use\",\"included_usage\":null,\"interval\":\"month\",\"price\":4,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":false,\"usage_model\":\"pay_per_use\",\"config\":{\"on_increase\":\"prorate_immediately\",\"on_decrease\":\"prorate_immediately\"}}","item2":"{\"feature_id\":\"seats\",\"feature_type\":\"continuous_use\",\"included_usage\":0,\"interval\":\"month\",\"interval_count\":1,\"price\":4,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":false,\"usage_model\":\"pay_per_use\",\"created_at\":1768768517976,\"entitlement_id\":\"ent_38Rhv08gZdC1AOrMvs20Gt4exbK\",\"price_id\":\"pr_38RhuzPJAjXGGc5JaF7JBQnKclZ\",\"price_config\":{\"type\":\"usage\",\"interval\":\"month\",\"bill_when\":\"end_of_period\",\"feature_id\":\"seats\",\"usage_tiers\":[{\"to\":\"inf\",\"amount\":4}],\"billing_units\":1,\"interval_count\":1,\"should_prorate\":true,\"stripe_price_id\":\"price_1Sr2gtG2wlWesuVT9hSb0qPO\",\"stripe_product_id\":\"prod_Tog21hujMdhsAJ\",\"internal_feature_id\":\"fe_38IUUvu4BoVK96JwbDZfJHwcRyD\",\"stripe_placeholder_price_id\":\"price_1Sr2guG2wlWesuVTfbCs2bkT\"},\"config\":{\"on_decrease\":\"prorate_immediately\",\"on_increase\":\"prorate_immediately\"},\"usage_limit\":null}"},"timestamp":1768768522738,"sessionId":"debug-session","hypothesisId":"H3"} -{"location":"compareItemUtils.ts:featurePriceItemsAreSame","message":"feature price items comparison failed","data":{"featureId":"seats","failedEntChecks":[{"check":"included_usage","message":"Included usage different: null != 0"}],"failedPriceChecks":[],"item1":"{\"feature_id\":\"seats\",\"feature_type\":\"continuous_use\",\"included_usage\":null,\"interval\":\"month\",\"price\":4,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":false,\"usage_model\":\"pay_per_use\",\"config\":{\"on_increase\":\"prorate_immediately\",\"on_decrease\":\"prorate_immediately\"}}","item2":"{\"feature_id\":\"seats\",\"feature_type\":\"continuous_use\",\"included_usage\":0,\"interval\":\"month\",\"interval_count\":1,\"price\":4,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":false,\"usage_model\":\"pay_per_use\",\"created_at\":1768768517976,\"entitlement_id\":\"ent_38Rhv08gZdC1AOrMvs20Gt4exbK\",\"price_id\":\"pr_38RhuzPJAjXGGc5JaF7JBQnKclZ\",\"price_config\":{\"type\":\"usage\",\"interval\":\"month\",\"bill_when\":\"end_of_period\",\"feature_id\":\"seats\",\"usage_tiers\":[{\"to\":\"inf\",\"amount\":4}],\"billing_units\":1,\"interval_count\":1,\"should_prorate\":true,\"stripe_price_id\":\"price_1Sr2gtG2wlWesuVT9hSb0qPO\",\"stripe_product_id\":\"prod_Tog21hujMdhsAJ\",\"internal_feature_id\":\"fe_38IUUvu4BoVK96JwbDZfJHwcRyD\",\"stripe_placeholder_price_id\":\"price_1Sr2guG2wlWesuVTfbCs2bkT\"},\"config\":{\"on_decrease\":\"prorate_immediately\",\"on_increase\":\"prorate_immediately\"},\"usage_limit\":null}"},"timestamp":1768768522739,"sessionId":"debug-session","hypothesisId":"H3"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":[{"to":"inf","amount":4}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768522739,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768616773,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768616773,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768617686,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768617687,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768619603,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768619604,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768627805,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768627805,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":null,"included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":null}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768634551,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":null,"included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":null}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768634551,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":[{"to":"inf","amount":0}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768635559,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":[{"to":"inf","amount":0}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768635559,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":[{"to":"inf","amount":4}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768636851,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":[{"to":"inf","amount":4}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768636851,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":[{"to":"inf","amount":4}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768637168,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":[{"to":"inf","amount":4}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768637169,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":[{"to":"inf","amount":4}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768647252,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":[{"to":"inf","amount":4}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768647253,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768650150,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768650151,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"runs","feature_type":"single_use","interval":"month","included_usage":null,"reset_usage_when_enabled":true,"price":null,"tiers":null}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768653414,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"runs","feature_type":"single_use","interval":"month","included_usage":null,"reset_usage_when_enabled":true,"price":null,"tiers":null}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768653414,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"runs","feature_type":"single_use","interval":"month","included_usage":null,"reset_usage_when_enabled":true,"price":null,"tiers":[{"to":"inf","amount":0}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768654738,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"runs","feature_type":"single_use","interval":"month","included_usage":null,"reset_usage_when_enabled":true,"price":null,"tiers":[{"to":"inf","amount":0}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768654738,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"runs","feature_type":"single_use","interval":"month","included_usage":null,"reset_usage_when_enabled":true,"price":null,"tiers":[{"to":"inf","amount":5}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768656050,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"runs","feature_type":"single_use","interval":"month","included_usage":null,"reset_usage_when_enabled":true,"price":null,"tiers":[{"to":"inf","amount":5}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768656050,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"runs","feature_type":"single_use","interval":"month","included_usage":null,"reset_usage_when_enabled":true,"price":null,"tiers":[{"to":"inf","amount":5}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768656372,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"runs","feature_type":"single_use","interval":"month","included_usage":null,"reset_usage_when_enabled":true,"price":null,"tiers":[{"to":"inf","amount":5}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":"month","included_usage":0,"reset_usage_when_enabled":false,"price":null,"tiers":[{"amount":4,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768656373,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"runs","feature_type":"single_use","interval":"month","included_usage":null,"reset_usage_when_enabled":true,"price":null,"tiers":[{"to":"inf","amount":5}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768657946,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"runs","feature_type":"single_use","interval":"month","included_usage":null,"reset_usage_when_enabled":true,"price":null,"tiers":[{"to":"inf","amount":5}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768657947,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"runs","feature_type":"single_use","interval":"month","included_usage":null,"reset_usage_when_enabled":true,"price":null,"tiers":[{"to":"inf","amount":5}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"runs","feature_type":"single_use","interval":"month","included_usage":0,"reset_usage_when_enabled":true,"price":null,"tiers":[{"amount":5,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768668632,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"runs","feature_type":"single_use","interval":"month","included_usage":null,"reset_usage_when_enabled":true,"price":null,"tiers":[{"to":"inf","amount":5}],"usage_model":"pay_per_use"}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"runs","feature_type":"single_use","interval":"month","included_usage":0,"reset_usage_when_enabled":true,"price":null,"tiers":[{"amount":5,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768668633,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"runs","feature_type":"single_use","interval":"month","included_usage":0,"reset_usage_when_enabled":true,"price":null,"tiers":[{"amount":5,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768670093,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":1,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"runs","feature_type":"single_use","interval":"month","included_usage":0,"reset_usage_when_enabled":true,"price":null,"tiers":[{"amount":5,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768670094,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":null,"included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":null}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"runs","feature_type":"single_use","interval":"month","included_usage":0,"reset_usage_when_enabled":true,"price":null,"tiers":[{"amount":5,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768675489,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":null,"included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":null}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"runs","feature_type":"single_use","interval":"month","included_usage":0,"reset_usage_when_enabled":true,"price":null,"tiers":[{"amount":5,"to":"inf"}],"usage_model":"pay_per_use"}]},"timestamp":1768768675489,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":null,"included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":null}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768677586,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":null,"included_usage":null,"reset_usage_when_enabled":false,"price":null,"tiers":null}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768677586,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":null,"included_usage":5,"reset_usage_when_enabled":false,"price":null,"tiers":null}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768678144,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":true,"itemsSame":false,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":1,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":null,"included_usage":5,"reset_usage_when_enabled":false,"price":null,"tiers":null}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true}]},"timestamp":1768768678144,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"compareItemUtils.ts:featureItemsAreSame","message":"feature items comparison failed","data":{"featureId":"seats","failedChecks":[{"check":"included_usage","message":"Included usage different: 5 != null"}],"item1":"{\"feature_id\":\"seats\",\"feature_type\":\"continuous_use\",\"included_usage\":5,\"interval\":null,\"price\":null,\"tiers\":null,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":false}","item2":"{\"feature_id\":\"seats\",\"feature_type\":\"continuous_use\",\"included_usage\":null,\"interval\":null,\"price\":null,\"tiers\":null,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":false}"},"timestamp":1768768678144,"sessionId":"debug-session","hypothesisId":"H2"} -{"location":"compareItemUtils.ts:featureItemsAreSame","message":"feature items comparison failed","data":{"featureId":"seats","failedChecks":[{"check":"included_usage","message":"Included usage different: 5 != null"}],"item1":"{\"feature_id\":\"seats\",\"feature_type\":\"continuous_use\",\"included_usage\":5,\"interval\":null,\"price\":null,\"tiers\":null,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":false}","item2":"{\"feature_id\":\"seats\",\"feature_type\":\"continuous_use\",\"included_usage\":null,\"interval\":null,\"price\":null,\"tiers\":null,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":false}"},"timestamp":1768768678145,"sessionId":"debug-session","hypothesisId":"H2"} -{"location":"compareItemUtils.ts:featureItemsAreSame","message":"feature items comparison failed","data":{"featureId":"seats","failedChecks":[{"check":"included_usage","message":"Included usage different: 5 != null"}],"item1":"{\"feature_id\":\"seats\",\"feature_type\":\"continuous_use\",\"included_usage\":5,\"interval\":null,\"price\":null,\"tiers\":null,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":false}","item2":"{\"feature_id\":\"seats\",\"feature_type\":\"continuous_use\",\"included_usage\":null,\"interval\":null,\"price\":null,\"tiers\":null,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":false}"},"timestamp":1768768678145,"sessionId":"debug-session","hypothesisId":"H2"} -{"location":"compareItemUtils.ts:featureItemsAreSame","message":"feature items comparison failed","data":{"featureId":"seats","failedChecks":[{"check":"included_usage","message":"Included usage different: 5 != null"}],"item1":"{\"feature_id\":\"seats\",\"feature_type\":\"continuous_use\",\"included_usage\":5,\"interval\":null,\"price\":null,\"tiers\":null,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":false}","item2":"{\"feature_id\":\"seats\",\"feature_type\":\"continuous_use\",\"included_usage\":null,\"interval\":null,\"price\":null,\"tiers\":null,\"billing_units\":1,\"entity_feature_id\":null,\"reset_usage_when_enabled\":false}"},"timestamp":1768768678145,"sessionId":"debug-session","hypothesisId":"H2"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":null,"included_usage":5,"reset_usage_when_enabled":false,"price":null,"tiers":null}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":null,"included_usage":5,"reset_usage_when_enabled":false}]},"timestamp":1768768686901,"sessionId":"debug-session","hypothesisId":"H1"} -{"location":"useProductStore.ts:useHasChanges","message":"hasChanges computed","data":{"hasChanges":false,"itemsSame":true,"detailsSame":true,"freeTrialsSame":true,"productItemsCount":2,"baseProductItemsCount":2,"productItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":null,"included_usage":5,"reset_usage_when_enabled":false,"price":null,"tiers":null}],"baseProductItems":[{"feature_id":"messages","feature_type":"single_use","interval":null,"included_usage":10,"reset_usage_when_enabled":true},{"feature_id":"seats","feature_type":"continuous_use","interval":null,"included_usage":5,"reset_usage_when_enabled":false}]},"timestamp":1768768686901,"sessionId":"debug-session","hypothesisId":"H1"} From 182492f7520324e39161df8901c428089d57ea73 Mon Sep 17 00:00:00 2001 From: John Yeo Date: Mon, 19 Jan 2026 11:17:14 +0000 Subject: [PATCH 28/34] fix: get or create cached full customer overwrites previous cache on set --- server/experiments/test.ts | 46 +----- .../setFullCustomerCache.lua | 55 +++++++ server/src/_luaScriptsV2/luaScriptsV2.ts | 9 ++ server/src/external/redis/initRedis.ts | 14 ++ .../deduction/deductionToTrackResponse.ts | 2 +- .../balances/utils/handleThresholdReached.ts | 5 +- .../getOrCreateCachedFullCustomer.ts | 2 + .../setCachedFullCustomer.ts | 54 +++---- server/tests/_temp/temp.test.ts | 136 ++++++++++++------ .../tests/balances/check/basic/check4.test.ts | 2 +- .../check/send-event/send-event1.test.ts | 2 + .../track/allocated/track-allocated5.test.ts | 2 + .../track/breakdown/track-breakdown6.test.ts | 3 + .../track/legacy/track-legacy2.test.ts | 6 +- .../track/legacy/track-legacy3.test.ts | 17 ++- 15 files changed, 220 insertions(+), 135 deletions(-) create mode 100644 server/src/_luaScriptsV2/deleteFullCustomerCache/setFullCustomerCache.lua diff --git a/server/experiments/test.ts b/server/experiments/test.ts index 0be900cc7..6e378d77e 100644 --- a/server/experiments/test.ts +++ b/server/experiments/test.ts @@ -9,51 +9,7 @@ export const test = async () => { env: AppEnv.Sandbox, }); - const stripeCustomer = await stripeCli.customers.create({ - email: "test@test.com", - }); - - const newSubSchedule = await stripeCli.subscriptionSchedules.create({ - customer: stripeCustomer.id, - phases: [ - { - items: [ - { - quantity: 1, - price: "price_1SZ88Z5NEqfWRGSe7BOQBy3T", - }, - ], - proration_behavior: "always_invoice", - }, - ], - // billing_behavior: "", - start_date: "now", - billing_mode: { type: "flexible" }, - end_behavior: "release" - } as Stripe.SubscriptionScheduleCreateParams) - - const subSchedule = await stripeCli.subscriptionSchedules.retrieve(newSubSchedule.id); - - console.log('Sub schedule status:', subSchedule.status); - - // Get current phase - const updatedSubSchedule = await stripeCli.subscriptionSchedules.update(newSubSchedule.id, { - phases: [ - { - start_date: subSchedule.current_phase?.start_date, - items: [ - { - price: "price_1SZ88Z5NEqfWRGSe7BOQBy3T", - quantity: 4, - }, - ], - proration_behavior: "always_invoice" - }, - ], - }); - - - console.log('Updated sub schedule status:', updatedSubSchedule.status); + // 1. Create stripe empty price? }; diff --git a/server/src/_luaScriptsV2/deleteFullCustomerCache/setFullCustomerCache.lua b/server/src/_luaScriptsV2/deleteFullCustomerCache/setFullCustomerCache.lua new file mode 100644 index 000000000..b84d8b2aa --- /dev/null +++ b/server/src/_luaScriptsV2/deleteFullCustomerCache/setFullCustomerCache.lua @@ -0,0 +1,55 @@ +--[[ + Set FullCustomer in Redis cache. + + Atomically: + 1. Checks if stale-write guard exists and is newer than fetchTime (skip if so) + 2. Checks if cache already exists (skip if so, unless overwrite is true) + 3. Sets the cache using JSON.SET + 4. Sets TTL on the cache key + + KEYS: + [1] guardKey - stale-write guard key to check + [2] cacheKey - cache key to set + + ARGV: + [1] fetchTimeMs - timestamp when data was fetched from Postgres + [2] cacheTtl - TTL in seconds for the cache key + [3] serializedData - JSON string of the FullCustomer data + [4] overwrite - "1" to overwrite existing cache, "0" to skip if exists + + Returns: + "STALE_WRITE" = guard exists with newer timestamp, write blocked + "CACHE_EXISTS" = cache already exists, write skipped (only when overwrite is false) + "OK" = cache set successfully +]] + +local guardKey = KEYS[1] +local cacheKey = KEYS[2] +local fetchTimeMs = tonumber(ARGV[1]) +local cacheTtl = tonumber(ARGV[2]) +local serializedData = ARGV[3] +local overwrite = ARGV[4] == "true" + +-- Check if guard exists (deletion happened recently) +-- Skip check if either value is nil/null/falsey +local guardTime = redis.call("GET", guardKey) +if guardTime and guardTime ~= cjson.null and fetchTimeMs then + local guardTimeNum = tonumber(guardTime) + if guardTimeNum and guardTimeNum > fetchTimeMs then + return "STALE_WRITE" + end +end + +-- Check if cache already exists (skip this check if overwrite is true) +if not overwrite then + local existing = redis.call("JSON.TYPE", cacheKey) + if existing then + return "CACHE_EXISTS" + end +end + +-- Set the cache using JSON.SET +redis.call("JSON.SET", cacheKey, "$", serializedData) +redis.call("EXPIRE", cacheKey, cacheTtl) + +return "OK" diff --git a/server/src/_luaScriptsV2/luaScriptsV2.ts b/server/src/_luaScriptsV2/luaScriptsV2.ts index a510eb27a..b04919b7c 100644 --- a/server/src/_luaScriptsV2/luaScriptsV2.ts +++ b/server/src/_luaScriptsV2/luaScriptsV2.ts @@ -76,6 +76,15 @@ export const DELETE_FULL_CUSTOMER_CACHE_SCRIPT = readFileSync( "utf-8", ); +/** + * Lua script for setting a FullCustomer cache in Redis. + * Checks stale-write guard, checks if cache exists, and sets cache atomically. + */ +export const SET_FULL_CUSTOMER_CACHE_SCRIPT = readFileSync( + join(DELETE_CACHE_DIR, "setFullCustomerCache.lua"), + "utf-8", +); + /** * Lua script for batch deleting multiple FullCustomer caches from Redis. * For each customer: checks test guard, sets stale-write guard, deletes cache. diff --git a/server/src/external/redis/initRedis.ts b/server/src/external/redis/initRedis.ts index ba14b798a..0b4b0ac22 100644 --- a/server/src/external/redis/initRedis.ts +++ b/server/src/external/redis/initRedis.ts @@ -17,6 +17,7 @@ import { BATCH_DELETE_FULL_CUSTOMER_CACHE_SCRIPT, DEDUCT_FROM_CUSTOMER_ENTITLEMENTS_SCRIPT, DELETE_FULL_CUSTOMER_CACHE_SCRIPT, + SET_FULL_CUSTOMER_CACHE_SCRIPT, } from "../../_luaScriptsV2/luaScriptsV2.js"; // if (!process.env.CACHE_URL) { @@ -173,6 +174,11 @@ const configureRedisInstance = (redisInstance: Redis): Redis => { lua: BATCH_DELETE_FULL_CUSTOMER_CACHE_SCRIPT, }); + redisInstance.defineCommand("setFullCustomerCache", { + numberOfKeys: 2, + lua: SET_FULL_CUSTOMER_CACHE_SCRIPT, + }); + redisInstance.on("error", (error) => { console.error(`[Redis] Connection error:`, error.message); }); @@ -338,6 +344,14 @@ declare module "ioredis" { guardTtl: string, customersJson: string, ): Promise; + setFullCustomerCache( + guardKey: string, + cacheKey: string, + fetchTimeMs: string, + cacheTtl: string, + serializedData: string, + overwrite: string, + ): Promise<"STALE_WRITE" | "CACHE_EXISTS" | "OK">; } } diff --git a/server/src/internal/balances/utils/deduction/deductionToTrackResponse.ts b/server/src/internal/balances/utils/deduction/deductionToTrackResponse.ts index 0a94ad877..9505c9400 100644 --- a/server/src/internal/balances/utils/deduction/deductionToTrackResponse.ts +++ b/server/src/internal/balances/utils/deduction/deductionToTrackResponse.ts @@ -1,7 +1,7 @@ import type { ApiBalance, Feature, FullCustomer } from "@autumn/shared"; import { - fullCustomerToCustomerEntitlements, findCustomerEntitlementById, + fullCustomerToCustomerEntitlements, getRelevantFeatures, } from "@autumn/shared"; import { Decimal } from "decimal.js"; diff --git a/server/src/internal/balances/utils/handleThresholdReached.ts b/server/src/internal/balances/utils/handleThresholdReached.ts index aaf85102d..4726232e8 100644 --- a/server/src/internal/balances/utils/handleThresholdReached.ts +++ b/server/src/internal/balances/utils/handleThresholdReached.ts @@ -47,7 +47,8 @@ export const handleAllowanceUsed = async ({ oldFullCus: FullCustomer; newFullCus: FullCustomer; }) => { - for (const cusProduct of newFullCus.customer_products) { + const clonedNewFullCus = structuredClone(newFullCus); + for (const cusProduct of clonedNewFullCus.customer_products) { for (const cusEnt of cusProduct.customer_entitlements) { cusEnt.usage_allowed = false; } @@ -61,7 +62,7 @@ export const handleAllowanceUsed = async ({ const { apiCustomer: newApiCustomer, legacyData: newLegacyData } = await getApiCustomerBase({ ctx, - fullCus: newFullCus, + fullCus: clonedNewFullCus, }); const prevCusFeature = prevApiCustomer.balances[feature.id]; diff --git a/server/src/internal/customers/cusUtils/fullCustomerCacheUtils/getOrCreateCachedFullCustomer.ts b/server/src/internal/customers/cusUtils/fullCustomerCacheUtils/getOrCreateCachedFullCustomer.ts index 6b92c51c3..5a8a71abe 100644 --- a/server/src/internal/customers/cusUtils/fullCustomerCacheUtils/getOrCreateCachedFullCustomer.ts +++ b/server/src/internal/customers/cusUtils/fullCustomerCacheUtils/getOrCreateCachedFullCustomer.ts @@ -177,12 +177,14 @@ export const getOrCreateCachedFullCustomer = async ({ // 6. Set cache (await to ensure it's ready before Redis deduction) if (!skipCache && setCache) { + // Note (to fix): causes race condition when cache isn't set and concurrent track requests each set the cache. await setCachedFullCustomer({ ctx, fullCustomer, customerId: fullCustomer.id || fullCustomer.internal_id, fetchTimeMs, source, + overwrite: true, }).catch((err) => logger.error(`Failed to set cache: ${err}`)); } diff --git a/server/src/internal/customers/cusUtils/fullCustomerCacheUtils/setCachedFullCustomer.ts b/server/src/internal/customers/cusUtils/fullCustomerCacheUtils/setCachedFullCustomer.ts index 746835443..d54cfc483 100644 --- a/server/src/internal/customers/cusUtils/fullCustomerCacheUtils/setCachedFullCustomer.ts +++ b/server/src/internal/customers/cusUtils/fullCustomerCacheUtils/setCachedFullCustomer.ts @@ -2,6 +2,7 @@ import type { FullCustomer } from "@autumn/shared"; import { redis } from "@/external/redis/initRedis.js"; import type { AutumnContext } from "@/honoUtils/HonoEnv.js"; import { tryRedisWrite } from "@/utils/cacheUtils/cacheUtils.js"; +import { addToExtraLogs } from "@/utils/logging/addToExtraLogs.js"; import { buildFullCustomerCacheGuardKey, buildFullCustomerCacheKey, @@ -20,12 +21,14 @@ export const setCachedFullCustomer = async ({ customerId, fetchTimeMs, source, + overwrite = false, }: { ctx: AutumnContext; fullCustomer: FullCustomer; customerId: string; fetchTimeMs: number; source?: string; + overwrite?: boolean; }): Promise => { const { org, env, logger } = ctx; @@ -41,24 +44,14 @@ export const setCachedFullCustomer = async ({ }); const result = await tryRedisWrite(async () => { - // Check if guard exists (deletion happened recently) - const guardTime = await redis.get(guardKey); - if (guardTime && Number(guardTime) > fetchTimeMs) { - return "STALE_WRITE" as const; - } - - // Check if cache already exists (JSON.TYPE returns null if key doesn't exist) - const existing = await redis.call("JSON.TYPE", cacheKey); - if (existing) { - return "CACHE_EXISTS" as const; - } - - // Set the cache using JSON.SET (stores as native JSON for JSONPath operations) - const serialized = JSON.stringify(fullCustomer); - await redis.call("JSON.SET", cacheKey, "$", serialized); - await redis.expire(cacheKey, FULL_CUSTOMER_CACHE_TTL_SECONDS); - - return "OK" as const; + return await redis.setFullCustomerCache( + guardKey, + cacheKey, + String(fetchTimeMs), + String(FULL_CUSTOMER_CACHE_TTL_SECONDS), + JSON.stringify(fullCustomer), + String(overwrite), + ); }); if (result === null) { @@ -66,19 +59,18 @@ export const setCachedFullCustomer = async ({ return "FAILED"; } - if (result === "STALE_WRITE") { - logger.info( - `[setCachedFullCustomer] Stale write blocked for ${customerId}, source: ${source}`, - ); - } else if (result === "CACHE_EXISTS") { - logger.debug( - `[setCachedFullCustomer] Cache already exists for ${customerId}, source: ${source}`, - ); - } else { - logger.info( - `[setCachedFullCustomer] Set cache for ${customerId}, source: ${source}`, - ); - } + logger.info( + `[setCachedFullCustomer] ${customerId}: ${result}, source: ${source}`, + ); + addToExtraLogs({ + ctx, + extras: { + setCache: { + result, + fullCustomer, + }, + }, + }); return result; }; diff --git a/server/tests/_temp/temp.test.ts b/server/tests/_temp/temp.test.ts index 1864dd16f..66e0c317f 100644 --- a/server/tests/_temp/temp.test.ts +++ b/server/tests/_temp/temp.test.ts @@ -1,10 +1,20 @@ import { beforeAll, describe } from "bun:test"; -import { ApiVersion } from "@autumn/shared"; +import { + ApiVersion, + BillingInterval, + type FullProduct, + isConsumablePrice, + isFixedPrice, +} from "@autumn/shared"; import { TestFeature } from "@tests/setup/v2Features.js"; import ctx from "@tests/utils/testInitUtils/createTestContext.js"; import chalk from "chalk"; +import type { Stripe } from "stripe"; import { AutumnInt } from "@/external/autumn/autumnCli.js"; +import { ProductService } from "@/internal/products/ProductService"; +import { constructPriceItem } from "@/internal/products/product-items/productItemUtils"; import { + constructArrearItem, constructFeatureItem, constructPrepaidItem, } from "@/utils/scriptUtils/constructItem.js"; @@ -12,9 +22,6 @@ import { constructProduct, constructRawProduct, } from "@/utils/scriptUtils/createTestProducts.js"; -import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js"; -import { initCustomerV3 } from "../../src/utils/scriptUtils/testUtils/initCustomerV3"; -import { expectSubToBeCorrect } from "../merged/mergeUtils/expectSubCorrect"; const prepaidUsersItem = constructPrepaidItem({ featureId: TestFeature.Users, @@ -32,68 +39,107 @@ const free = constructProduct({ ], }); -const oneOffCredits = constructRawProduct({ - id: "one_off_credits", +const growthYearly = constructRawProduct({ + id: "growth-yearly", items: [ - constructPrepaidItem({ - featureId: TestFeature.Credits, + constructArrearItem({ + featureId: TestFeature.Words, includedUsage: 0, - billingUnits: 1, - price: 0.01, - isOneOff: true, + price: 1, + billingUnits: 100, + }), + constructPriceItem({ + price: 2000, + interval: BillingInterval.Year, }), ], - // trial: true, }); const testCase = "temp"; +const buildSubscriptionItems = ({ + fullProduct, +}: { + fullProduct: FullProduct; +}): Stripe.SubscriptionScheduleCreateParams.Phase.Item[] => { + return fullProduct.prices.map((p) => { + if (isConsumablePrice(p)) { + return { + price: p.config.stripe_empty_price_id ?? undefined, + quantity: 0, + }; + } + return { + price: p.config.stripe_price_id ?? undefined, + quantity: 1, + }; + }); +}; + describe(`${chalk.yellowBright("temp: add on")}`, () => { const customerId = testCase; const autumnV1: AutumnInt = new AutumnInt({ version: ApiVersion.V1_2 }); beforeAll(async () => { - await initCustomerV3({ - ctx, - customerId, - withTestClock: true, - attachPm: "success", + // await initCustomerV3({ + // ctx, + // customerId, + // withTestClock: true, + // attachPm: "success", + // }); + + // await initProductsV0({ + // ctx, + // products: [free, growthYearly], + // prefix: testCase, + // }); + + const { stripeCli } = ctx; + + const growthYearly = await ProductService.getFull({ + db: ctx.db, + orgId: ctx.org.id, + env: ctx.env, + idOrInternalId: "growth-yearly_temp", }); - await initProductsV0({ - ctx, - products: [free], - prefix: testCase, - }); + // const basePrice = growthYearly.prices.find(isFixedPrice) - await autumnV1.attach({ - customer_id: customerId, - product_id: free.id, - options: [ + // const emptyPrice = await stripeCli.prices.create({ + // product: growthYearly?.processor?.id, + // unit_amount: 0, + // currency: "usd", + // recurring: { + // ...(billingIntervalToStripe({ + // interval: BillingInterval.Year, + // intervalCount: 1, + // }) as any), + // }, + // }); + + // console.log(emptyPrice); + + const newSubscription = await stripeCli.subscriptions.create({ + customer: "cus_ToYUVA6XSJrMa8", + items: [ { - feature_id: TestFeature.Users, - quantity: 10, + price: "price_1SqvPM5NEqgjQ4gyNktukeYr", + quantity: 1, }, ], + billing_mode: { type: "flexible" }, + billing_cycle_anchor: Math.floor(new Date("2026-12-26").getTime() / 1000), }); - const dashboardItem = constructFeatureItem({ - featureId: TestFeature.Dashboard, - isBoolean: true, - }); - - await autumnV1.attach({ - customer_id: customerId, - product_id: free.id, - is_custom: true, - items: [prepaidUsersItem, dashboardItem], - }); - - await expectSubToBeCorrect({ - db: ctx.db, - customerId, - org: ctx.org, - env: ctx.env, + await stripeCli.subscriptions.update(newSubscription.id, { + items: [ + { + id: newSubscription.items.data[0].id, + deleted: true, + }, + ...buildSubscriptionItems({ fullProduct: growthYearly }) + ], + proration_behavior: "none", }); }); }); diff --git a/server/tests/balances/check/basic/check4.test.ts b/server/tests/balances/check/basic/check4.test.ts index ad0f903f9..1c8a3a2c2 100644 --- a/server/tests/balances/check/basic/check4.test.ts +++ b/server/tests/balances/check/basic/check4.test.ts @@ -80,7 +80,7 @@ describe(`${chalk.yellowBright("check4: test /check on unlimited feature")}`, () granted_balance: 0, max_purchase: null, overage_allowed: false, - plan_id: "check4_free", + plan_id: freeProd.id, purchased_balance: 0, reset: null, usage: 0, diff --git a/server/tests/balances/check/send-event/send-event1.test.ts b/server/tests/balances/check/send-event/send-event1.test.ts index ff42b8804..07232f5ce 100644 --- a/server/tests/balances/check/send-event/send-event1.test.ts +++ b/server/tests/balances/check/send-event/send-event1.test.ts @@ -50,6 +50,8 @@ describe(`${chalk.yellowBright("send-event1: Testing send event")}`, () => { customer_id: customerId, product_id: pro.id, }); + + await autumn.customers.get(customerId); // set cache }); test("should check with track for allocated feature", async () => { diff --git a/server/tests/balances/track/allocated/track-allocated5.test.ts b/server/tests/balances/track/allocated/track-allocated5.test.ts index d0ff5590d..a1bfa5a02 100644 --- a/server/tests/balances/track/allocated/track-allocated5.test.ts +++ b/server/tests/balances/track/allocated/track-allocated5.test.ts @@ -52,6 +52,8 @@ describe(`${chalk.yellowBright(`${testCase}: Tracking allocated feature with con product_id: free.id, }); + await autumn.customers.get(customerId); // set cache + const promises = []; let totalUsage = 0; let numberOfTracks = 0; diff --git a/server/tests/balances/track/breakdown/track-breakdown6.test.ts b/server/tests/balances/track/breakdown/track-breakdown6.test.ts index 08be49f72..73dab2adf 100644 --- a/server/tests/balances/track/breakdown/track-breakdown6.test.ts +++ b/server/tests/balances/track/breakdown/track-breakdown6.test.ts @@ -115,6 +115,8 @@ describe(`${chalk.yellowBright("track-breakdown6: exhaust into overage, then add value: deductValue, }); + console.log("Track result:", trackRes); + // 500 from prepaid, 100 from overage expect(trackRes.balance).toMatchObject({ granted_balance: 500, @@ -136,6 +138,7 @@ describe(`${chalk.yellowBright("track-breakdown6: exhaust into overage, then add await timeout(2000); }); + return; test("attach lifetime prepaid product", async () => { await autumnV2.attach({ diff --git a/server/tests/balances/track/legacy/track-legacy2.test.ts b/server/tests/balances/track/legacy/track-legacy2.test.ts index d5693f89f..71296d54f 100644 --- a/server/tests/balances/track/legacy/track-legacy2.test.ts +++ b/server/tests/balances/track/legacy/track-legacy2.test.ts @@ -1,7 +1,8 @@ import { beforeAll, describe, expect, test } from "bun:test"; -import type { LimitedItem } from "@autumn/shared"; +import { ApiVersion, type LimitedItem } from "@autumn/shared"; import ctx from "@tests/utils/testInitUtils/createTestContext.js"; import chalk from "chalk"; +import { AutumnInt } from "@/external/autumn/autumnCli.js"; import { constructArrearItem } from "../../../../src/utils/scriptUtils/constructItem.js"; import { constructProduct } from "../../../../src/utils/scriptUtils/createTestProducts.js"; import { initCustomerV3 } from "../../../../src/utils/scriptUtils/testUtils/initCustomerV3.js"; @@ -26,6 +27,7 @@ const proWithOverage = constructProduct({ const testCase = "track-legacy2"; describe(`${chalk.yellowBright("track-legacy2: Testing /entitled & /events, for pro with overage")}`, () => { const customerId = testCase; + const autumn: AutumnInt = new AutumnInt({ version: ApiVersion.V1_2 }); beforeAll(async () => { await initProductsV0({ @@ -50,6 +52,8 @@ describe(`${chalk.yellowBright("track-legacy2: Testing /entitled & /events, for customerId: customerId, productId: proWithOverage.id, }); + + await autumn.customers.get(customerId); // set cache }); test("should have correct entitlements (pro with overage)", async () => { diff --git a/server/tests/balances/track/legacy/track-legacy3.test.ts b/server/tests/balances/track/legacy/track-legacy3.test.ts index f6c398e2e..7b47bdb76 100644 --- a/server/tests/balances/track/legacy/track-legacy3.test.ts +++ b/server/tests/balances/track/legacy/track-legacy3.test.ts @@ -1,7 +1,9 @@ import { beforeAll, describe, test } from "bun:test"; -import { ProductItemInterval } from "@autumn/shared"; +import { ApiVersion, ProductItemInterval } from "@autumn/shared"; +import { timeout } from "@tests/utils/genUtils.js"; import ctx from "@tests/utils/testInitUtils/createTestContext.js"; import chalk from "chalk"; +import { AutumnInt } from "@/external/autumn/autumnCli.js"; import { constructFeatureItem, constructPrepaidItem, @@ -60,6 +62,7 @@ describe(`${chalk.yellowBright( let curAllowance = 0; const oneTimeBillingUnits = 100; // From oneTimeAddOnMetered1 prepaid item const oneTimeQuantity = 2 * oneTimeBillingUnits; + const autumn: AutumnInt = new AutumnInt({ version: ApiVersion.V1_2 }); beforeAll(async () => { await initProductsV0({ @@ -78,19 +81,13 @@ describe(`${chalk.yellowBright( }); }); - // test("should have correct entitlements (free)", async function () { - // await checkEntitledOnProduct({ - // customerId: customerId, - // product: free, - // finish: true, - // }); - // }); - test("should attach pro", async () => { await AutumnCli.attach({ customerId: customerId, productId: pro.id, }); + + await autumn.customers.get(customerId); // set cache }); test("should have correct entitlements (pro)", async () => { @@ -122,6 +119,8 @@ describe(`${chalk.yellowBright( }, ], }); + + await autumn.customers.get(customerId); // set cache }); test("should have correct entitlements (one time top up)", async () => { From 4ec3b70776ebae04830127a781d0d390268c6e28 Mon Sep 17 00:00:00 2001 From: John Yeo Date: Mon, 19 Jan 2026 15:50:28 +0000 Subject: [PATCH 29/34] fix: autumn provider backend url --- vite/src/app/layout.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vite/src/app/layout.tsx b/vite/src/app/layout.tsx index c33ae783b..8ba54b124 100644 --- a/vite/src/app/layout.tsx +++ b/vite/src/app/layout.tsx @@ -92,8 +92,8 @@ export function MainLayout() { return ( From 16b083ff393899f8ed90599b98b2e70bc150d4bf Mon Sep 17 00:00:00 2001 From: John Yeo Date: Mon, 19 Jan 2026 19:39:43 +0000 Subject: [PATCH 30/34] feat: allow no customer ID in list events --- server/src/utils/logging/initLogger.ts | 6 +----- shared/api/events/list/eventsListParams.ts | 3 ++- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/server/src/utils/logging/initLogger.ts b/server/src/utils/logging/initLogger.ts index fc78c9094..f3194ad77 100644 --- a/server/src/utils/logging/initLogger.ts +++ b/server/src/utils/logging/initLogger.ts @@ -134,11 +134,7 @@ export const initLogger = () => { stream: pino.transport({ target: "@axiomhq/pino", options: { - dataset: - process.env.NODE_ENV === "test" || - process.env.NODE_ENV === "development" - ? "server-dev" - : "express", + dataset: "express", token: process.env.AXIOM_TOKEN, }, }), diff --git a/shared/api/events/list/eventsListParams.ts b/shared/api/events/list/eventsListParams.ts index 656946f35..e749a7853 100644 --- a/shared/api/events/list/eventsListParams.ts +++ b/shared/api/events/list/eventsListParams.ts @@ -4,11 +4,12 @@ import { createPaginationParamsSchema } from "../../common/pagePaginationSchemas export const ApiEventsListParamsSchema = createPaginationParamsSchema({ defaultLimit: 100, }).extend({ - customer_id: z.string().describe("Filter events by customer ID"), + customer_id: z.string().optional().describe("Filter events by customer ID"), feature_id: z .string() .min(1) .or(z.array(z.string().min(1))) + .optional() .describe("Filter by specific feature ID(s)"), custom_range: z From 4e023cfd1e130d73acfa1270f5439e4ca0668203 Mon Sep 17 00:00:00 2001 From: amianthus <49116958+SirTenzin@users.noreply.github.com> Date: Tue, 20 Jan 2026 14:40:22 +0000 Subject: [PATCH 31/34] Rename 'Plan Features' to 'Features' in sidebar --- vite/src/views/main-sidebar/MainSidebar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vite/src/views/main-sidebar/MainSidebar.tsx b/vite/src/views/main-sidebar/MainSidebar.tsx index 246df6e2c..90e406282 100644 --- a/vite/src/views/main-sidebar/MainSidebar.tsx +++ b/vite/src/views/main-sidebar/MainSidebar.tsx @@ -152,7 +152,7 @@ export const MainSidebar = () => { icon: , }, { - title: "Plan Features", + title: "Features", value: "features", icon: , }, From 34b3ef4106c3b536ea3920e670da809ec59dd1b5 Mon Sep 17 00:00:00 2001 From: amianthus <49116958+SirTenzin@users.noreply.github.com> Date: Tue, 20 Jan 2026 15:15:09 +0000 Subject: [PATCH 32/34] =?UTF-8?q?fix:=20=F0=9F=90=9B=20hotfix=20plan/has?= =?UTF-8?q?=5Fcustomers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/internal/products/productRouter.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/internal/products/productRouter.ts b/server/src/internal/products/productRouter.ts index 46df6f435..22593307a 100644 --- a/server/src/internal/products/productRouter.ts +++ b/server/src/internal/products/productRouter.ts @@ -9,11 +9,10 @@ import { handleGetPlan } from "./handlers/handleGetPlan.js"; import { handleGetPlanDeleteInfo } from "./handlers/handleGetPlanDeleteInfo.js"; import { handleListPlans } from "./handlers/handleListPlans.js"; import { handleMigrateProductV2 } from "./handlers/handleMigrateProductV2.js"; -import { handlePlanHasCustomers } from "./handlers/handlePlanHasCustomers.js"; + import { handleUpdatePlan } from "./handlers/handleUpdateProduct/handleUpdatePlan.js"; export const expressProductRouter = express.Router(); -expressProductRouter.get("/:product_id/has_customers", handlePlanHasCustomers); export const honoProductBetaRouter = new Hono(); honoProductBetaRouter.get("", ...handleListPlans); @@ -37,6 +36,7 @@ honoProductRouter.delete("/:product_id", ...handleDeleteProductHono); honoProductRouter.post("/:product_id/copy", ...handleCopyProductV2); // Info before deleting plan +honoProductRouter.get("/:product_id/has_customers", ...handlePlanHasCustomersV2); honoProductRouter.post( "/:product_id/has_customers", ...handlePlanHasCustomersV2, From 673971a63b2b929f30397849f820591f7d366312 Mon Sep 17 00:00:00 2001 From: John Yeo Date: Tue, 20 Jan 2026 18:39:23 +0000 Subject: [PATCH 33/34] fix: billing portal --- server/src/internal/customers/cusRouter.ts | 3 +- .../handleGetBillingPortal.ts | 114 +++++++++--------- 2 files changed, 57 insertions(+), 60 deletions(-) diff --git a/server/src/internal/customers/cusRouter.ts b/server/src/internal/customers/cusRouter.ts index 3876bb7b1..995760e19 100644 --- a/server/src/internal/customers/cusRouter.ts +++ b/server/src/internal/customers/cusRouter.ts @@ -15,7 +15,7 @@ import { handleUpdateBalancesV2 } from "./handlers/handleUpdateBalancesV2.js"; import { handleUpdateCustomerV2 } from "./handlers/handleUpdateCustomerV2.js"; export const expressCusRouter = express.Router(); -expressCusRouter.get("/:customer_id/billing_portal", handleGetBillingPortal); +// expressCusRouter.get("/:customer_id/billing_portal", handleGetBillingPortal); export const cusRouter = new Hono(); @@ -35,6 +35,7 @@ cusRouter.post("/:customer_id/transfer", ...handleTransferProductV2); // Billing portal cusRouter.post("/:customer_id/billing_portal", ...handleCreateBillingPortal); +cusRouter.get("/:customer_id/billing_portal", ...handleGetBillingPortal); // Legacy... cusRouter.post("/:customer_id/balances", ...handleUpdateBalancesV2); diff --git a/server/src/internal/customers/handlers/handleBillingPortal/handleGetBillingPortal.ts b/server/src/internal/customers/handlers/handleBillingPortal/handleGetBillingPortal.ts index a08b16429..b8bd5faa0 100644 --- a/server/src/internal/customers/handlers/handleBillingPortal/handleGetBillingPortal.ts +++ b/server/src/internal/customers/handlers/handleBillingPortal/handleGetBillingPortal.ts @@ -1,67 +1,63 @@ -import { ErrCode, RecaseError } from "@autumn/shared"; +import { + ErrCode, + GetBillingPortalQuerySchema, + RecaseError, +} from "@autumn/shared"; import { StatusCodes } from "http-status-codes"; +import z from "zod/v4"; +import { createStripeCusIfNotExists } from "@/external/stripe/stripeCusUtils"; +import { createRoute } from "@/honoMiddlewares/routeHandler"; import { createStripeCli } from "../../../../external/connect/createStripeCli"; -import { createStripeCusIfNotExists } from "../../../../external/stripe/stripeCusUtils"; -import { routeHandler } from "../../../../utils/routerUtils"; -import { OrgService } from "../../../orgs/OrgService"; import { toSuccessUrl } from "../../../orgs/orgUtils/convertOrgUtils"; import { CusService } from "../../CusService"; -export const handleGetBillingPortal = (req: any, res: any) => - routeHandler({ - req, - res, - action: "get billing portal", - handler: async (req, res) => { - const returnUrl = req.query.return_url; - const customerId = req.params.customer_id; - const [org, customer] = await Promise.all([ - OrgService.getFromReq(req), - CusService.get({ - db: req.db, - idOrInternalId: customerId, - orgId: req.orgId, - env: req.env, - }), - ]); +export const handleGetBillingPortal = createRoute({ + query: GetBillingPortalQuerySchema, + params: z.object({ + customer_id: z.string(), + }), + // body: GetBillingPortalBodySchema, + handler: async (c) => { + const returnUrl = c.req.valid("query").return_url; + const customerId = c.req.param().customer_id; + const ctx = c.get("ctx"); + const [customer] = await Promise.all([ + CusService.get({ + db: ctx.db, + idOrInternalId: customerId, + orgId: ctx.org.id, + env: ctx.env, + }), + ]); - if (!customer) { - throw new RecaseError({ - message: `Customer ${customerId} not found`, - code: ErrCode.CustomerNotFound, - statusCode: StatusCodes.NOT_FOUND, - }); - } - - const stripeCli = createStripeCli({ org, env: req.env }); - - let stripeCusId: string = customer.processor?.id; - if (!customer.processor?.id) { - const newCus = await createStripeCusIfNotExists({ - db: req.db, - org, - env: req.env, - customer, - logger: req.logger, - }); - - if (!newCus) { - throw new RecaseError({ - message: `Failed to create Stripe customer`, - }); - } - - stripeCusId = newCus.id; - } - - const portal = await stripeCli.billingPortal.sessions.create({ - customer: stripeCusId, - return_url: returnUrl || toSuccessUrl({ org, env: req.env }), + if (!customer) { + throw new RecaseError({ + message: `Customer ${customerId} not found`, + code: ErrCode.CustomerNotFound, + statusCode: StatusCodes.NOT_FOUND, }); + } - res.status(200).json({ - customer_id: customer.id || null, - url: portal.url, - }); - }, - }); + const stripeCli = createStripeCli({ org: ctx.org, env: ctx.env }); + + const stripeCustomer = await createStripeCusIfNotExists({ + db: ctx.db, + org: ctx.org, + env: ctx.env, + customer, + logger: ctx.logger, + }); + + const stripeCusId = stripeCustomer.id; + + const portal = await stripeCli.billingPortal.sessions.create({ + customer: stripeCusId, + return_url: returnUrl || toSuccessUrl({ org: ctx.org, env: ctx.env }), + }); + + return c.json({ + customer_id: customer.id || null, + url: portal.url, + }); + }, +}); From c163b7deebfc2f91a5dabd5f6cb96f46e998fd47 Mon Sep 17 00:00:00 2001 From: John Yeo Date: Tue, 20 Jan 2026 21:22:04 +0000 Subject: [PATCH 34/34] fix: new group by max --- server/src/internal/events/EventsAggregationService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/internal/events/EventsAggregationService.ts b/server/src/internal/events/EventsAggregationService.ts index 1127b9cf0..1513e0e16 100644 --- a/server/src/internal/events/EventsAggregationService.ts +++ b/server/src/internal/events/EventsAggregationService.ts @@ -305,9 +305,9 @@ export class EventsAggregationService { }>; const distinctCount = Number(distinctJson.data[0]?.distinct_count ?? 0); - if (distinctCount > 30) { + if (distinctCount > 100) { throw new RecaseError({ - message: `Too many distinct group values (${distinctCount}). Maximum allowed is 30. Please choose a property with fewer unique values.`, + message: `Too many distinct group values (${distinctCount}). Maximum allowed is 100. Please choose a property with fewer unique values.`, code: ErrCode.InvalidInputs, statusCode: StatusCodes.BAD_REQUEST, });