Skip to content

Commit 5286ce9

Browse files
committed
refactor: payments dashboard consistency changes
1 parent 3d49b92 commit 5286ce9

4 files changed

Lines changed: 27 additions & 12 deletions

File tree

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/[productId]/edit/page-client.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import { PricingSection } from "../../pricing-section";
3434
import { ProductCardPreview } from "../../product-card-preview";
3535
import {
3636
createFreePrice,
37+
isFreePrices,
3738
type Price,
3839
type Product,
3940
} from "../../utils";
@@ -378,7 +379,7 @@ function EditProductForm({ productId, existingProduct }: { productId: string, ex
378379
hasError={!!errors.prices}
379380
errorMessage={errors.prices}
380381
variant="form"
381-
isFree={Object.keys(prices).length === 1 && Object.values(prices)[0].USD === '0.00'}
382+
isFree={isFreePrices(prices)}
382383
onMakeFree={() => {
383384
setPrices(createFreePrice());
384385
}}

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/[productId]/page-client.tsx

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ import {
5959
priceToEditingPrice,
6060
type EditingPrice,
6161
} from "../price-edit-dialog";
62-
import { DEFAULT_INTERVAL_UNITS, generateUniqueId, intervalLabel, shortIntervalLabel, type Price, type Product } from "../utils";
62+
import { createFreePrice, DEFAULT_INTERVAL_UNITS, generateUniqueId, intervalLabel, isFreePrices, shortIntervalLabel, type Price, type Product } from "../utils";
6363

6464
const CUSTOMER_TYPE_COLORS = {
6565
user: 'bg-blue-500/15 text-blue-600 dark:bg-blue-500/20 dark:text-blue-400 ring-blue-500/30',
@@ -878,11 +878,7 @@ function ProductPricesSection({ productId, prices, onPricesChange, inline = fals
878878
};
879879

880880
const priceEntries = typedEntries(prices);
881-
const isFree = priceEntries.length === 1
882-
&& (priceEntries[0][1].USD === '0' || priceEntries[0][1].USD === '0.00')
883-
&& !priceEntries[0][1].interval
884-
&& !priceEntries[0][1].freeTrial
885-
&& !priceEntries[0][1].serverOnly;
881+
const isFree = isFreePrices(prices);
886882
const hasNoPrices = priceEntries.length === 0;
887883

888884
const handleMakePaid = () => {
@@ -891,10 +887,7 @@ function ProductPricesSection({ productId, prices, onPricesChange, inline = fals
891887
};
892888

893889
const handleMakeFree = () => {
894-
const newPriceId = generateUniqueId('price');
895-
onPricesChange({
896-
[newPriceId]: { USD: '0', serverOnly: false },
897-
});
890+
onPricesChange(createFreePrice());
898891
};
899892

900893
const listContent = (

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/new/page-client.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import { PricingSection } from "../pricing-section";
4242
import { ProductCardPreview } from "../product-card-preview";
4343
import {
4444
createFreePrice,
45+
isFreePrices,
4546
type Price,
4647
type Product,
4748
} from "../utils";
@@ -786,7 +787,7 @@ ${Object.entries(prices).map(([id, price]) => {
786787
hasError={!!errors.prices}
787788
errorMessage={errors.prices}
788789
variant="form"
789-
isFree={Object.keys(prices).length === 1 && Object.values(prices)[0].USD === '0.00'}
790+
isFree={isFreePrices(prices)}
790791
onMakeFree={() => {
791792
setPrices(createFreePrice());
792793
}}

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/utils.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,26 @@ export function createFreePrice(): { [priceId: string]: Price } {
129129
return { [generateUniqueId('price')]: { USD: '0.00', serverOnly: false } };
130130
}
131131

132+
/**
133+
* Returns true if `prices` represents a "free" product: exactly one price entry
134+
* whose USD amount is `'0'` or `'0.00'` and which has no interval, free-trial, or
135+
* server-only flag set (any of those would change the semantics meaningfully).
136+
*
137+
* We accept both `'0'` and `'0.00'` for backward-compatibility with rows written
138+
* before we standardized on `createFreePrice()` (which emits `'0.00'`). All three
139+
* product pages (list, edit, create) call this so the "Free" indicator and the
140+
* "Make free" / "Make paid" toggles stay in sync.
141+
*/
142+
export function isFreePrices(prices: PricesObject): boolean {
143+
const entries = Object.values(prices);
144+
if (entries.length !== 1) return false;
145+
const [price] = entries;
146+
return (price.USD === '0' || price.USD === '0.00')
147+
&& !price.interval
148+
&& !price.freeTrial
149+
&& !price.serverOnly;
150+
}
151+
132152
// ============================================================================
133153
// ID Validation & Generation
134154
// ============================================================================

0 commit comments

Comments
 (0)