feat: 🎸 frontend locks
This commit is contained in:
@@ -15,10 +15,10 @@ import {
|
||||
RecaseError,
|
||||
type RolloverConfig,
|
||||
RolloverExpiryDurationType,
|
||||
TiersType,
|
||||
UsageModel,
|
||||
} from "@autumn/shared";
|
||||
import { createFeaturesFromItems } from "@server/internal/products/product-items/createFeaturesFromItems";
|
||||
|
||||
import { StatusCodes } from "http-status-codes";
|
||||
import {
|
||||
isBooleanFeatureItem,
|
||||
@@ -173,6 +173,18 @@ const validateProductItem = ({
|
||||
statusCode: StatusCodes.BAD_REQUEST,
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
item.tiers_type === TiersType.VolumeBased &&
|
||||
item.tiers.length > 1 &&
|
||||
item.usage_model !== UsageModel.Prepaid
|
||||
) {
|
||||
throw new RecaseError({
|
||||
message: `Volume-based pricing is only supported for prepaid items`,
|
||||
code: ErrCode.InvalidInputs,
|
||||
statusCode: StatusCodes.BAD_REQUEST,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -368,6 +380,7 @@ export const validateProductItems = ({
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 6. Can't have both weekly and monthly price items in the same product
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Infinite, type PriceTier } from "@autumn/shared";
|
||||
import { Infinite, type PriceTier, TiersType, UsageModel } from "@autumn/shared";
|
||||
import { PlusIcon, TrashSimpleIcon } from "@phosphor-icons/react";
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { IconButton } from "@/components/v2/buttons/IconButton";
|
||||
import { Input } from "@/components/v2/inputs/Input";
|
||||
import {
|
||||
@@ -87,6 +87,17 @@ export function PriceTiers() {
|
||||
);
|
||||
const [isEditing, setIsEditing] = useState<Record<string, boolean>>({});
|
||||
|
||||
// Auto-select prepaid when volume-based is active with multiple tiers
|
||||
useEffect(() => {
|
||||
if (
|
||||
item?.tiers_type === TiersType.VolumeBased &&
|
||||
(item?.tiers?.length ?? 0) > 1 &&
|
||||
item?.usage_model !== UsageModel.Prepaid
|
||||
) {
|
||||
setItem({ ...item, usage_model: UsageModel.Prepaid });
|
||||
}
|
||||
}, [item?.tiers_type, item?.tiers?.length]);
|
||||
|
||||
if (!item) return null;
|
||||
|
||||
const tiers = item.tiers || [];
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
itemToBillingInterval,
|
||||
nullish,
|
||||
ProductItemInterval,
|
||||
TiersType,
|
||||
UsageModel,
|
||||
} from "@autumn/shared";
|
||||
import { FormLabel } from "@/components/v2/form/FormLabel";
|
||||
@@ -16,6 +17,8 @@ export function PricedFeatureSettings() {
|
||||
if (!item) return null;
|
||||
|
||||
const isOneOff = itemToBillingInterval({ item }) === BillingInterval.OneOff;
|
||||
const isVolumeBased =
|
||||
item.tiers_type === TiersType.VolumeBased && (item.tiers?.length ?? 0) > 1;
|
||||
|
||||
const handleUsageModelChange = (value: string) => {
|
||||
const usageModel = value as UsageModel;
|
||||
@@ -42,7 +45,11 @@ export function PricedFeatureSettings() {
|
||||
label="Usage-based"
|
||||
description={"Bill for how much the customer uses"}
|
||||
disabledReason={
|
||||
isOneOff ? "Usage based prices must have an interval." : undefined
|
||||
isVolumeBased
|
||||
? "Volume-based pricing requires prepaid billing."
|
||||
: isOneOff
|
||||
? "Usage based prices must have an interval."
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
<AreaRadioGroupItem
|
||||
|
||||
Reference in New Issue
Block a user