wip
This commit is contained in:
@@ -66,13 +66,11 @@ export const baseMiddleware = async (c: Context<HonoEnv>, next: Next) => {
|
||||
body = await c.req.json();
|
||||
}
|
||||
|
||||
logger.info(`${method} ${path}`, {
|
||||
logger.info(`[HONO] ${method} ${path}`, {
|
||||
context: {
|
||||
body,
|
||||
},
|
||||
});
|
||||
|
||||
logger.info(`URL: ${c.req.url}`);
|
||||
|
||||
await next();
|
||||
};
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { Feature } from "@autumn/shared";
|
||||
import { validateFeature } from "../internalFeatureRouter.js";
|
||||
import { generateId } from "@/utils/genUtils.js";
|
||||
import type { Feature } from "@autumn/shared";
|
||||
import { OrgService } from "@/internal/orgs/OrgService.js";
|
||||
import { FeatureService } from "../FeatureService.js";
|
||||
import { JobName } from "@/queue/JobName.js";
|
||||
import { addTaskToQueue } from "@/queue/queueUtils.js";
|
||||
import { handleFrontendReqError } from "@/utils/errorUtils.js";
|
||||
import { generateId } from "@/utils/genUtils.js";
|
||||
import { FeatureService } from "../FeatureService.js";
|
||||
import { validateFeature } from "../internalFeatureRouter.js";
|
||||
|
||||
export const handleCreateFeature = async (req: any, res: any) => {
|
||||
try {
|
||||
console.log("Trying to create feature");
|
||||
const data = req.body;
|
||||
let { db, orgId, env, logtail: logger } = req;
|
||||
let parsedFeature = validateFeature(data);
|
||||
const { db, orgId, env, logtail: logger } = req;
|
||||
const parsedFeature = validateFeature(data);
|
||||
|
||||
const feature: Feature = {
|
||||
archived: false,
|
||||
@@ -23,8 +23,8 @@ export const handleCreateFeature = async (req: any, res: any) => {
|
||||
...parsedFeature,
|
||||
};
|
||||
|
||||
let org = await OrgService.getFromReq(req);
|
||||
let insertedData = await FeatureService.insert({
|
||||
const org = await OrgService.getFromReq(req);
|
||||
const insertedData = await FeatureService.insert({
|
||||
db,
|
||||
data: feature,
|
||||
logger,
|
||||
@@ -38,7 +38,7 @@ export const handleCreateFeature = async (req: any, res: any) => {
|
||||
},
|
||||
});
|
||||
|
||||
let insertedFeature =
|
||||
const insertedFeature =
|
||||
insertedData && insertedData.length > 0 ? insertedData[0] : null;
|
||||
res.status(200).json(insertedFeature);
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import express, { Router } from "express";
|
||||
|
||||
import { FeatureService } from "./FeatureService.js";
|
||||
import { ErrCode, FeatureType, products, entitlements } from "@autumn/shared";
|
||||
import { validateCreditSystem, validateFeatureId } from "./featureUtils.js";
|
||||
import { generateId } from "@/utils/genUtils.js";
|
||||
import { handleUpdateFeature } from "@/internal/features/handlers/handleUpdateFeature.js";
|
||||
import { CreateFeatureSchema, ErrCode, FeatureType } from "@autumn/shared";
|
||||
import express, { type Router } from "express";
|
||||
import { handleDeleteFeature } from "@/internal/features/handlers/handleDeleteFeature.js";
|
||||
import { handleUpdateFeature } from "@/internal/features/handlers/handleUpdateFeature.js";
|
||||
import RecaseError, { formatZodError } from "@/utils/errorUtils.js";
|
||||
import { validateMeteredConfig } from "./featureUtils.js";
|
||||
import { CreateFeatureSchema } from "@autumn/shared";
|
||||
import { sql, eq, and } from "drizzle-orm";
|
||||
import { generateId } from "@/utils/genUtils.js";
|
||||
import { FeatureService } from "./FeatureService.js";
|
||||
import {
|
||||
validateCreditSystem,
|
||||
validateFeatureId,
|
||||
validateMeteredConfig,
|
||||
} from "./featureUtils.js";
|
||||
import { handleCreateFeature } from "./handlers/handleCreateFeature.js";
|
||||
import { handleGetFeatureDeletionInfo } from "./handlers/handleGetFeatureDeletionInfo.js";
|
||||
|
||||
@@ -17,20 +17,20 @@ export const internalFeatureRouter: Router = express.Router();
|
||||
|
||||
internalFeatureRouter.get("", async (req: any, res: any) => {
|
||||
try {
|
||||
let { showArchived } = req.query;
|
||||
const { showArchived } = req.query;
|
||||
|
||||
if (showArchived !== undefined) {
|
||||
// If showArchived is specified, use FeatureService.list with the parameter
|
||||
let features = await FeatureService.list({
|
||||
const features = await FeatureService.list({
|
||||
db: req.db,
|
||||
orgId: req.orgId,
|
||||
env: req.env,
|
||||
archived: showArchived === "true" ? true : false,
|
||||
archived: showArchived === "true",
|
||||
});
|
||||
res.status(200).json({ features });
|
||||
} else {
|
||||
// If no showArchived parameter, use the original getFromReq method
|
||||
let features = await FeatureService.getFromReq(req);
|
||||
const features = await FeatureService.getFromReq(req);
|
||||
res.status(200).json({ features });
|
||||
}
|
||||
} catch (error: any) {
|
||||
@@ -40,14 +40,14 @@ internalFeatureRouter.get("", async (req: any, res: any) => {
|
||||
});
|
||||
|
||||
export const validateFeature = (data: any) => {
|
||||
let featureType = data.type;
|
||||
const featureType = data.type;
|
||||
|
||||
validateFeatureId(data.id);
|
||||
|
||||
let config = data.config;
|
||||
if (featureType == FeatureType.Metered) {
|
||||
if (featureType === FeatureType.Metered) {
|
||||
config = validateMeteredConfig(config);
|
||||
} else if (featureType == FeatureType.CreditSystem) {
|
||||
} else if (featureType === FeatureType.CreditSystem) {
|
||||
config = validateCreditSystem(config);
|
||||
}
|
||||
|
||||
|
||||
@@ -355,8 +355,6 @@ export class ProductService {
|
||||
internalId: string;
|
||||
update: any;
|
||||
}) {
|
||||
console.log("internalId", internalId);
|
||||
console.log("update", update);
|
||||
await db
|
||||
.update(products)
|
||||
.set(update)
|
||||
|
||||
@@ -77,6 +77,7 @@ export const handleUpdateProductV2 = createRoute({
|
||||
const newProductV2: ProductV2 = {
|
||||
...curProductV2,
|
||||
...body,
|
||||
group: body.group || curProductV2.group || "",
|
||||
items: body.items || [],
|
||||
free_trial: newFreeTrial || curProductV2.free_trial || undefined,
|
||||
};
|
||||
@@ -149,7 +150,7 @@ export const handleUpdateProductV2 = createRoute({
|
||||
// New full product
|
||||
const newFullProduct = await ProductService.getFull({
|
||||
db,
|
||||
idOrInternalId: fullProduct.id,
|
||||
idOrInternalId: body.id || fullProduct.id,
|
||||
orgId: org.id,
|
||||
env,
|
||||
});
|
||||
@@ -191,17 +192,17 @@ export const handleUpdateProductV2 = createRoute({
|
||||
jobName: JobName.RewardMigration,
|
||||
payload: {
|
||||
oldPrices: fullProduct.prices,
|
||||
productId: fullProduct.id,
|
||||
productId: body.id || fullProduct.id,
|
||||
orgId: org.id,
|
||||
env,
|
||||
},
|
||||
});
|
||||
|
||||
return c.json(
|
||||
getProductResponse({
|
||||
product: newFullProduct,
|
||||
features,
|
||||
}),
|
||||
);
|
||||
const productResponse = await getProductResponse({
|
||||
product: newFullProduct,
|
||||
features,
|
||||
});
|
||||
|
||||
return c.json(productResponse);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -205,9 +205,7 @@ export const handleUpdateProductDetails = async ({
|
||||
}
|
||||
}
|
||||
|
||||
if (productDetailsSame(curProduct, newProduct)) {
|
||||
return;
|
||||
}
|
||||
if (productDetailsSame(curProduct, newProduct)) return;
|
||||
|
||||
if (notNullish(newProduct.id) && newProduct.id !== curProduct.id) {
|
||||
if (customersOnAllVersions.length > 0) {
|
||||
@@ -225,6 +223,7 @@ export const handleUpdateProductDetails = async ({
|
||||
}
|
||||
|
||||
// 2. Update product
|
||||
|
||||
await ProductService.updateByInternalId({
|
||||
db,
|
||||
internalId: curProduct.internal_id,
|
||||
|
||||
@@ -126,6 +126,14 @@ productRouter.get("/:productId/data2", async (req: any, res) => {
|
||||
const { version } = req.query;
|
||||
const { db, orgId, env } = req;
|
||||
|
||||
console.log("[/data2] Request params:", {
|
||||
productId,
|
||||
version,
|
||||
orgId,
|
||||
env,
|
||||
featuresLength: req.features?.length,
|
||||
});
|
||||
|
||||
const [product, latestProduct] = await Promise.all([
|
||||
ProductService.getFull({
|
||||
db,
|
||||
@@ -146,9 +154,16 @@ productRouter.get("/:productId/data2", async (req: any, res) => {
|
||||
throw new ProductNotFoundError({ productId, version });
|
||||
}
|
||||
|
||||
console.log(
|
||||
"[/data2] Product found:",
|
||||
product.id,
|
||||
"Features available:",
|
||||
req.features?.length || 0,
|
||||
);
|
||||
|
||||
const productV2 = mapToProductV2({
|
||||
product: product,
|
||||
features: req.features,
|
||||
features: req.features || [],
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
@@ -158,8 +173,13 @@ productRouter.get("/:productId/data2", async (req: any, res) => {
|
||||
},
|
||||
numVersions: latestProduct.version,
|
||||
});
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
console.error("Failed to get product", error);
|
||||
console.error("Error details:", {
|
||||
message: error?.message,
|
||||
stack: error?.stack,
|
||||
name: error?.name,
|
||||
});
|
||||
res.status(500).send(error);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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().optional(),
|
||||
group: z.string().nullish(),
|
||||
archived: z.boolean().optional(),
|
||||
|
||||
items: z.array(CreateProductItemParamsSchema).optional(),
|
||||
|
||||
@@ -45,7 +45,7 @@ export const UpdateProductSchema = z.object({
|
||||
name: z.string().min(1, "Product name cannot be empty").optional(),
|
||||
is_add_on: z.boolean().optional(),
|
||||
is_default: z.boolean().optional(),
|
||||
group: z.string().optional(),
|
||||
group: z.string().nullish(),
|
||||
archived: z.boolean().optional(),
|
||||
});
|
||||
|
||||
|
||||
@@ -32,9 +32,11 @@ export const useFeatureCreationActions = ({
|
||||
setProduct,
|
||||
setBaseProduct,
|
||||
}: FeatureCreationActionsProps) => {
|
||||
const { refetch: refetchFeatures } = useFeaturesQuery();
|
||||
const { features, refetch: refetchFeatures } = useFeaturesQuery();
|
||||
|
||||
// Create feature and add to product
|
||||
const handleProceed = useCallback(async (): Promise<boolean> => {
|
||||
// 1. If feature already exists, update it, if not create it
|
||||
const createdFeature = await createFeature(
|
||||
feature as CreateFeature,
|
||||
axiosInstance,
|
||||
@@ -45,22 +47,9 @@ export const useFeatureCreationActions = ({
|
||||
|
||||
await refetchFeatures(); // Refresh features list
|
||||
|
||||
// Debug: Log the feature data used to create product item
|
||||
console.log("FeatureCreationActions - created feature data:", {
|
||||
id: createdFeature.id,
|
||||
type: createdFeature.type,
|
||||
usage_type: createdFeature.config?.usage_type,
|
||||
fullConfig: createdFeature.config,
|
||||
});
|
||||
|
||||
// Create ProductItem and add to product immediately for live editing
|
||||
const newItem = createProductItem(createdFeature);
|
||||
|
||||
console.log("FeatureCreationActions - newItem created:", {
|
||||
feature_id: newItem.feature_id,
|
||||
feature_type: newItem.feature_type,
|
||||
});
|
||||
|
||||
setFeature(createdFeature);
|
||||
|
||||
// Add feature item to product (preserving any existing base price item)
|
||||
|
||||
@@ -23,42 +23,34 @@ export const usePlanDetailsActions = ({
|
||||
productCreatedRef,
|
||||
setBaseProduct,
|
||||
}: PlanDetailsActionsProps) => {
|
||||
const { products } = useProductsQuery();
|
||||
const { products, refetch: refetchProducts } = useProductsQuery();
|
||||
|
||||
// Create product and update base state
|
||||
const handleProceed = useCallback(async (): Promise<boolean> => {
|
||||
// console.log("Product:", product);
|
||||
// Check if base product exists in products query
|
||||
|
||||
let newProduct: ProductV2;
|
||||
if (products.find((p) => p.id === baseProduct.id)) {
|
||||
// Update product
|
||||
await updateProduct({
|
||||
newProduct = await updateProduct({
|
||||
axiosInstance,
|
||||
productId: baseProduct.id,
|
||||
product: product as ProductV2,
|
||||
onSuccess: async () => {
|
||||
// await handleRefetch();
|
||||
},
|
||||
onSuccess: async () => {},
|
||||
});
|
||||
|
||||
console.log(
|
||||
`Updating product, productId: ${baseProduct.id}, product:`,
|
||||
product,
|
||||
);
|
||||
return true;
|
||||
} else {
|
||||
const createdProduct = await createProduct(
|
||||
newProduct = await createProduct(
|
||||
product,
|
||||
axiosInstance,
|
||||
productCreatedRef,
|
||||
);
|
||||
|
||||
console.log(`Creating product, product:`, createdProduct);
|
||||
|
||||
if (!createdProduct) return false;
|
||||
|
||||
setBaseProduct(createdProduct);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!newProduct) return false;
|
||||
|
||||
setBaseProduct(newProduct);
|
||||
await refetchProducts();
|
||||
|
||||
return true;
|
||||
}, [
|
||||
product,
|
||||
baseProduct,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery";
|
||||
import { useProductsQuery } from "@/hooks/queries/useProductsQuery";
|
||||
import { useAxiosInstance } from "@/services/useAxiosInstance";
|
||||
@@ -12,6 +12,7 @@ export const useOnboardingData = () => {
|
||||
isLoading: productsLoading,
|
||||
refetch: refetchProducts,
|
||||
} = useProductsQuery();
|
||||
|
||||
const {
|
||||
features,
|
||||
isLoading: featuresLoading,
|
||||
@@ -21,8 +22,10 @@ export const useOnboardingData = () => {
|
||||
const {
|
||||
baseProduct,
|
||||
setBaseProduct,
|
||||
|
||||
feature,
|
||||
setFeature,
|
||||
|
||||
productCreatedRef,
|
||||
featureCreatedRef,
|
||||
isButtonLoading,
|
||||
@@ -65,6 +68,7 @@ export const useOnboardingData = () => {
|
||||
latestId: firstProduct.id,
|
||||
};
|
||||
// loadProductData(firstProduct.id);
|
||||
|
||||
setBaseProduct(firstProduct);
|
||||
}
|
||||
|
||||
@@ -143,15 +147,11 @@ export const useOnboardingData = () => {
|
||||
// ]);
|
||||
|
||||
// Refetch product data
|
||||
const handleRefetch = useCallback(async () => {
|
||||
// if (product?.id) {
|
||||
// await loadProductData(product.id);
|
||||
// }
|
||||
}, [product?.id]);
|
||||
const handleRefetch = () => {};
|
||||
|
||||
useEffect(() => {
|
||||
console.log("[useOnboardingData] Feature: ", feature);
|
||||
}, [feature]);
|
||||
// useEffect(() => {
|
||||
// console.log("[useOnboardingData] Feature: ", feature);
|
||||
// }, [feature]);
|
||||
|
||||
const isQueryLoading = useMemo(() => {
|
||||
return productsLoading || featuresLoading;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
AppEnv,
|
||||
BillingInterval,
|
||||
type Feature,
|
||||
type ProductItem,
|
||||
type ProductV2,
|
||||
} from "@autumn/shared";
|
||||
@@ -29,6 +30,8 @@ export const useOnboardingState = () => {
|
||||
internal_id: "",
|
||||
});
|
||||
|
||||
const [baseFeature, setBaseFeature] = useState<Feature>(getDefaultFeature());
|
||||
|
||||
// Feature creation state
|
||||
const [feature, setFeature] = useState(() => getDefaultFeature());
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
AppEnv,
|
||||
type CreateFeature,
|
||||
CreateFeatureSchema,
|
||||
CreateProductSchema,
|
||||
type Feature,
|
||||
FeatureType,
|
||||
FeatureUsageType,
|
||||
@@ -159,39 +158,39 @@ export const handleBackNavigation = async (
|
||||
setSelectedProductId: (id: string) => void,
|
||||
axiosInstance?: AxiosInstance,
|
||||
) => {
|
||||
// When going back to step 1 from step 2, load the created product
|
||||
const currentStepNum = getStepNumber(step);
|
||||
const willGoToStep1 = currentStepNum === 2;
|
||||
|
||||
if (willGoToStep1 && productCreatedRef.current.created && productCreatedRef.current.latestId) {
|
||||
// Load the created product data
|
||||
if (axiosInstance) {
|
||||
try {
|
||||
const response = await axiosInstance.get(
|
||||
`/products/${productCreatedRef.current.latestId}/data2`,
|
||||
);
|
||||
setBaseProduct(response.data.product);
|
||||
setSelectedProductId(productCreatedRef.current.latestId);
|
||||
return;
|
||||
} catch (error) {
|
||||
console.error("Failed to load product on back navigation:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const isInConflictState = checkForStateConflicts(
|
||||
step,
|
||||
productCreatedRef,
|
||||
baseProduct,
|
||||
);
|
||||
|
||||
if (isInConflictState) {
|
||||
resetCreationTracking(productCreatedRef, featureCreatedRef);
|
||||
|
||||
const initialProduct = createInitialProductState(baseProduct.env);
|
||||
setBaseProduct(initialProduct);
|
||||
setSelectedProductId("");
|
||||
}
|
||||
// // When going back to step 1 from step 2, load the created product
|
||||
// const currentStepNum = getStepNumber(step);
|
||||
// const willGoToStep1 = currentStepNum === 2;
|
||||
// if (
|
||||
// willGoToStep1 &&
|
||||
// productCreatedRef.current.created &&
|
||||
// productCreatedRef.current.latestId
|
||||
// ) {
|
||||
// // Load the created product data
|
||||
// if (axiosInstance) {
|
||||
// try {
|
||||
// const response = await axiosInstance.get(
|
||||
// `/products/${productCreatedRef.current.latestId}/data2`,
|
||||
// );
|
||||
// setBaseProduct(response.data.product);
|
||||
// setSelectedProductId(productCreatedRef.current.latestId);
|
||||
// return;
|
||||
// } catch (error) {
|
||||
// console.error("Failed to load product on back navigation:", error);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// const isInConflictState = checkForStateConflicts(
|
||||
// step,
|
||||
// productCreatedRef,
|
||||
// baseProduct,
|
||||
// );
|
||||
// if (isInConflictState) {
|
||||
// resetCreationTracking(productCreatedRef, featureCreatedRef);
|
||||
// const initialProduct = createInitialProductState(baseProduct.env);
|
||||
// setBaseProduct(initialProduct);
|
||||
// setSelectedProductId("");
|
||||
// }
|
||||
};
|
||||
|
||||
// Handle plan selection logic
|
||||
@@ -262,50 +261,55 @@ export const createProduct = async (
|
||||
}>,
|
||||
) => {
|
||||
try {
|
||||
const result = CreateProductSchema.safeParse({
|
||||
name: product?.name,
|
||||
id: product?.id,
|
||||
items: product?.items || [],
|
||||
});
|
||||
// const result = CreateProductSchema.safeParse({
|
||||
// name: product?.name,
|
||||
// id: product?.id,
|
||||
// items: product?.items || [],
|
||||
// });
|
||||
|
||||
if (result.error) {
|
||||
console.error("Product validation error:", result.error);
|
||||
toast.error("Invalid product data");
|
||||
return null;
|
||||
}
|
||||
// if (result.error) {
|
||||
// console.error("Product validation error:", result.error);
|
||||
// toast.error("Invalid product data");
|
||||
// return null;
|
||||
// }
|
||||
|
||||
let createdProduct: Awaited<
|
||||
ReturnType<typeof ProductService.createProduct>
|
||||
>;
|
||||
|
||||
if (!productCreatedRef.current.created) {
|
||||
// First time creating the product
|
||||
createdProduct = await ProductService.createProduct(
|
||||
axiosInstance,
|
||||
result.data,
|
||||
);
|
||||
productCreatedRef.current = {
|
||||
created: true,
|
||||
latestId: createdProduct.id,
|
||||
};
|
||||
toast.success(`Product "${product?.name}" created successfully!`);
|
||||
} else {
|
||||
// Product already exists, update it (supports ID changes)
|
||||
// Note: Backend creates a new product if ID changed, archives old one
|
||||
await ProductService.updateProduct(
|
||||
axiosInstance,
|
||||
productCreatedRef.current.latestId as string,
|
||||
result.data,
|
||||
);
|
||||
// Fetch the full product after update (same as PlanEditorView does)
|
||||
const response = await axiosInstance.get(
|
||||
`/products/${result.data.id}/data2`,
|
||||
);
|
||||
createdProduct = response.data.product;
|
||||
createdProduct = await ProductService.createProduct(axiosInstance, product);
|
||||
productCreatedRef.current = {
|
||||
created: true,
|
||||
latestId: createdProduct.id,
|
||||
};
|
||||
toast.success(`Product "${product?.name}" created successfully!`);
|
||||
|
||||
productCreatedRef.current.latestId = result.data.id;
|
||||
toast.success(`Product "${product?.name}" updated successfully!`);
|
||||
}
|
||||
// if (!productCreatedRef.current.created) {
|
||||
// // First time creating the product
|
||||
// createdProduct = await ProductService.createProduct(
|
||||
// axiosInstance,
|
||||
// product,
|
||||
// );
|
||||
// productCreatedRef.current = {
|
||||
// created: true,
|
||||
// latestId: createdProduct.id,
|
||||
// };
|
||||
// toast.success(`Product "${product?.name}" created successfully!`);
|
||||
// } else {
|
||||
// // Product already exists, update it (supports ID changes)
|
||||
// // Note: Backend creates a new product if ID changed, archives old one
|
||||
// await ProductService.updateProduct(
|
||||
// axiosInstance,
|
||||
// productCreatedRef.current.latestId as string,
|
||||
// product,
|
||||
// );
|
||||
// // Fetch the full product after update (same as PlanEditorView does)
|
||||
// const response = await axiosInstance.get(`/products/${product.id}/data2`);
|
||||
// createdProduct = response.data.product;
|
||||
|
||||
// productCreatedRef.current.latestId = product.id;
|
||||
// toast.success(`Product "${product?.name}" updated successfully!`);
|
||||
// }
|
||||
|
||||
return {
|
||||
...createdProduct,
|
||||
@@ -393,15 +397,9 @@ export const createProductItem = (createdFeature: CreateFeature) => {
|
||||
// Map feature type to product item feature type
|
||||
let featureType: ProductItemFeatureType;
|
||||
|
||||
if (
|
||||
createdFeature.type === FeatureType.Boolean ||
|
||||
createdFeature.type === "boolean"
|
||||
) {
|
||||
if (createdFeature.type === FeatureType.Boolean) {
|
||||
featureType = ProductItemFeatureType.Static;
|
||||
} else if (
|
||||
createdFeature.type === FeatureType.CreditSystem ||
|
||||
createdFeature.type === "credit_system"
|
||||
) {
|
||||
} else if (createdFeature.type === FeatureType.CreditSystem) {
|
||||
featureType = ProductItemFeatureType.SingleUse;
|
||||
} else if (
|
||||
createdFeature.type === FeatureType.Metered ||
|
||||
@@ -428,10 +426,7 @@ export const createProductItem = (createdFeature: CreateFeature) => {
|
||||
console.log("createProductItem - mapped to feature type:", featureType);
|
||||
|
||||
// Boolean features have a simplified structure with no pricing/billing properties
|
||||
if (
|
||||
createdFeature.type === FeatureType.Boolean ||
|
||||
createdFeature.type === "boolean"
|
||||
) {
|
||||
if (createdFeature.type === FeatureType.Boolean) {
|
||||
return {
|
||||
feature_id: createdFeature.id,
|
||||
feature_type: featureType,
|
||||
|
||||
@@ -53,6 +53,7 @@ export const BasePriceSection = () => {
|
||||
intervalCount?: number;
|
||||
}) => {
|
||||
const newItems = [...product.items];
|
||||
|
||||
// Find base price item by isBasePrice flag, not by price match
|
||||
const basePriceIndex = newItems.findIndex((item: ProductItem) =>
|
||||
isPriceItem(item),
|
||||
|
||||
@@ -18,30 +18,31 @@ export function NewFeatureDetails({
|
||||
targetKey: "id",
|
||||
});
|
||||
|
||||
if (feature)
|
||||
return (
|
||||
<SheetSection title="Feature Details">
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<Input
|
||||
placeholder="eg. Messages"
|
||||
value={feature.name}
|
||||
onChange={(e) => setSource(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
if (!feature) return null;
|
||||
|
||||
<div>
|
||||
<FormLabel>ID</FormLabel>
|
||||
<Input
|
||||
placeholder="eg. messages"
|
||||
value={feature.id}
|
||||
onChange={(e) => setTarget(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
return (
|
||||
<SheetSection title="Feature Details">
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<Input
|
||||
placeholder="eg. Messages"
|
||||
value={feature.name}
|
||||
onChange={(e) => setSource(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<FormLabel>ID</FormLabel>
|
||||
<Input
|
||||
placeholder="eg. messages"
|
||||
value={feature.id}
|
||||
onChange={(e) => setTarget(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</SheetSection>
|
||||
);
|
||||
</div>
|
||||
</SheetSection>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
type FrontendProductItem,
|
||||
type ProductV2,
|
||||
UpdateProductSchema,
|
||||
UpdateProductV2ParamsSchema,
|
||||
} from "@autumn/shared";
|
||||
import type { AxiosError, AxiosInstance } from "axios";
|
||||
import { toast } from "sonner";
|
||||
@@ -29,20 +29,24 @@ export const updateProduct = async ({
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
const updateData = {
|
||||
...UpdateProductSchema.parse(product),
|
||||
const updateData = UpdateProductV2ParamsSchema.parse({
|
||||
...product,
|
||||
items: product.items,
|
||||
free_trial: product.free_trial,
|
||||
};
|
||||
});
|
||||
|
||||
await ProductService.updateProduct(axiosInstance, productId, updateData);
|
||||
const updatedProduct = await ProductService.updateProduct(
|
||||
axiosInstance,
|
||||
productId,
|
||||
updateData,
|
||||
);
|
||||
|
||||
toast.success("Product updated successfully");
|
||||
|
||||
await onSuccess();
|
||||
return true;
|
||||
return updatedProduct;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
console.error((error as ZodError).message);
|
||||
toast.error(
|
||||
getBackendErr(error as AxiosError | ZodError, "Failed to update product"),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user