diff --git a/server/src/internal/products/handlers/handleUpdateProduct/updateProductDetails.ts b/server/src/internal/products/handlers/handleUpdateProduct/updateProductDetails.ts index dd6be3462..5fb534469 100644 --- a/server/src/internal/products/handlers/handleUpdateProduct/updateProductDetails.ts +++ b/server/src/internal/products/handlers/handleUpdateProduct/updateProductDetails.ts @@ -33,7 +33,7 @@ const productDetailsSame = (prod1: Product, prod2: UpdateProduct) => { return false; } - if (notNullish(prod2.group) && prod1.group !== prod2.group) { + if (prod2.group !== undefined && prod1.group !== prod2.group) { return false; } @@ -223,6 +223,7 @@ export const handleUpdateProductDetails = async ({ } // 2. Update product + console.log("New group:", newProduct.group); await ProductService.updateByInternalId({ db, @@ -230,7 +231,10 @@ export const handleUpdateProductDetails = async ({ update: { id: newProduct.id, name: newProduct.name, - group: newProduct.group, + group: + newProduct.group === "" || newProduct.group === null + ? "" + : newProduct.group, is_add_on: newProduct.is_add_on, is_default: newProduct.is_default, archived: newProduct.archived, diff --git a/shared/api/products/productOpModels.ts b/shared/api/products/productOpModels.ts index 2e3c4d91e..bab88191d 100644 --- a/shared/api/products/productOpModels.ts +++ b/shared/api/products/productOpModels.ts @@ -35,7 +35,7 @@ export const UpdateProductV2ParamsSchema = z.object({ is_add_on: z.boolean().optional(), is_default: z.boolean().optional(), version: z.number().optional(), - group: z.string().nullish(), + group: z.string().nonempty().nullable(), archived: z.boolean().optional(), items: z.array(CreateProductItemParamsSchema).optional(), diff --git a/shared/models/productV2Models/productV2Models.ts b/shared/models/productV2Models/productV2Models.ts index b9bdfda2e..488f4ef0b 100644 --- a/shared/models/productV2Models/productV2Models.ts +++ b/shared/models/productV2Models/productV2Models.ts @@ -11,7 +11,7 @@ export const ProductV2Schema = z.object({ is_add_on: z.boolean(), is_default: z.boolean(), version: z.number().default(1), - group: z.string(), + group: z.string().nullable(), env: z.nativeEnum(AppEnv), free_trial: FreeTrialSchema.nullish(), diff --git a/shared/utils/productV2Utils/mapToProductV2.ts b/shared/utils/productV2Utils/mapToProductV2.ts index 90ebfa45e..84f1c09e8 100644 --- a/shared/utils/productV2Utils/mapToProductV2.ts +++ b/shared/utils/productV2Utils/mapToProductV2.ts @@ -85,7 +85,7 @@ export const mapToProductV2 = ({ is_add_on: product.is_add_on, is_default: product.is_default, version: product.version, - group: product.group, + group: product.group || null, free_trial: product.free_trial, created_at: product.created_at, env: product.env, diff --git a/shared/utils/productV3Utils/mapToProductV3.ts b/shared/utils/productV3Utils/mapToProductV3.ts index e72724b5d..f372ba88d 100644 --- a/shared/utils/productV3Utils/mapToProductV3.ts +++ b/shared/utils/productV3Utils/mapToProductV3.ts @@ -1,13 +1,7 @@ -import type { z } from "zod/v4"; import type { ProductV2 } from "../../models/productV2Models/productV2Models.js"; -import type { PlanResponseSchema } from "../../models/productV3Models/productV3Response.js"; import { productV2ToBasePrice } from "./productItemUtils/productV3ItemUtils.js"; -export function mapToProductV3({ - product, -}: { - product: ProductV2; -}): z.infer { +export function mapToProductV3({ product }: { product: ProductV2 }) { const productV3 = { id: product.id, name: product.name, 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 800d77d87..8237144e0 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 @@ -1,4 +1,5 @@ import { AreaCheckbox } from "@/components/v2/checkboxes/AreaCheckbox"; +import { Input } from "@/components/v2/inputs/Input"; import { SheetSection } from "@/components/v2/sheets/InlineSheet"; import { useProductStore } from "@/hooks/stores/useProductStore"; @@ -10,6 +11,8 @@ export const AdditionalOptions = ({ const product = useProductStore((s) => s.product); const setProduct = useProductStore((s) => s.setProduct); + const hasGroup = product.group !== null; + return (
@@ -33,6 +36,26 @@ export const AdditionalOptions = ({ setProduct({ ...product, is_add_on: checked }) } /> +
+ + setProduct({ ...product, group: checked ? "" : null }) + } + > + {hasGroup && ( + + setProduct({ ...product, group: e.target.value }) + } + /> + )} + +
); diff --git a/vite/src/views/products/plan/components/plan-card/PlanFeatureList.tsx b/vite/src/views/products/plan/components/plan-card/PlanFeatureList.tsx index 1464b27f2..588fdc743 100644 --- a/vite/src/views/products/plan/components/plan-card/PlanFeatureList.tsx +++ b/vite/src/views/products/plan/components/plan-card/PlanFeatureList.tsx @@ -31,6 +31,8 @@ export const PlanFeatureList = ({ const filteredItems = productV2ToFeatureItems({ items: product.items }); + console.log("Filtered items:", filteredItems); + // Group items by entity_feature_id const groupedItems = filteredItems.reduce( (acc, item) => { @@ -77,7 +79,12 @@ export const PlanFeatureList = ({ ); } - const groups = Object.entries(groupedItems); + const groups = Object.entries(groupedItems).sort(([keyA], [keyB]) => { + // "no_entity" should always come first + if (keyA === "no_entity") return -1; + if (keyB === "no_entity") return 1; + return 0; + }); const hasEntityFeatureIds = groups.some(([key]) => key !== "no_entity"); return (