@@ -18,6 +19,7 @@ export function AddonList({ addons, selected, onToggle }: AddonListProps) {
{addons.map((a) => {
const on = selected.includes(a.id);
+ const price = addonPriceForDevices(a, devices);
return (
);
diff --git a/src/lib/format.test.ts b/src/lib/format.test.ts
index c4aad22..1c02b5f 100644
--- a/src/lib/format.test.ts
+++ b/src/lib/format.test.ts
@@ -14,8 +14,8 @@ import type { AddonData, PricingPlans } from '../types';
const pricing: PricingPlans = {
device_price: 100,
addons: [
- { id: 'a', name: 'A', price: 50, free_threshold: 0 },
- { id: 'b', name: 'B', price: 30, free_threshold: 0 },
+ { id: 'a', name: 'A', price: 50, free_threshold: -1 },
+ { id: 'b', name: 'B', price: 30, free_threshold: -1 },
],
};
@@ -30,10 +30,20 @@ describe('round2', () => {
describe('sumAddonPrices', () => {
const addons: AddonData[] = pricing.addons;
it('sums selected addon prices', () => {
- expect(sumAddonPrices(addons, ['a', 'b'])).toBe(80);
- expect(sumAddonPrices(addons, ['a'])).toBe(50);
- expect(sumAddonPrices(addons, ['unknown'])).toBe(0);
- expect(sumAddonPrices(addons, [])).toBe(0);
+ expect(sumAddonPrices(addons, ['a', 'b'], 6)).toBe(80);
+ expect(sumAddonPrices(addons, ['a'], 6)).toBe(50);
+ expect(sumAddonPrices(addons, ['unknown'], 6)).toBe(0);
+ expect(sumAddonPrices(addons, [], 6)).toBe(0);
+ });
+
+ it('makes addons free at their device threshold but keeps -1 paid', () => {
+ const thresholdAddons: AddonData[] = [
+ { id: 'threshold', name: 'Threshold', price: 50, free_threshold: 6 },
+ { id: 'paid', name: 'Paid', price: 30, free_threshold: -1 },
+ ];
+
+ expect(sumAddonPrices(thresholdAddons, ['threshold', 'paid'], 5)).toBe(80);
+ expect(sumAddonPrices(thresholdAddons, ['threshold', 'paid'], 6)).toBe(30);
});
});
diff --git a/src/lib/format.ts b/src/lib/format.ts
index 39d3c12..ad576b0 100644
--- a/src/lib/format.ts
+++ b/src/lib/format.ts
@@ -55,7 +55,7 @@ export interface PricingBreakdown {
/**
* Mirrors the backend `services/subscriptions.py`:
- * monthly = devices * device_price + sum(selected addon prices)
+ * monthly = devices * device_price + sum(selected applicable addon prices)
* total = monthly * duration_days / 30
*/
export function calculatePricing({
@@ -65,7 +65,7 @@ export function calculatePricing({
duration,
}: PricingInput): PricingBreakdown {
const deviceCost = pricing.device_price * devices;
- const addonCost = sumAddonPrices(pricing.addons, addonIds);
+ const addonCost = sumAddonPrices(pricing.addons, addonIds, devices);
const monthly = deviceCost + addonCost;
const periodTotal = round2((monthly * duration.days) / 30);
return {
@@ -76,9 +76,16 @@ export function calculatePricing({
};
}
-export function sumAddonPrices(addons: AddonData[], selected: string[]): number {
- const priceById = new Map(addons.map((a) => [a.id, a.price] as const));
- return selected.reduce
((sum, id) => sum + (priceById.get(id) ?? 0), 0);
+export function addonPriceForDevices(addon: AddonData, devices: number): number {
+ return addon.free_threshold >= 0 && devices >= addon.free_threshold ? 0 : addon.price;
+}
+
+export function sumAddonPrices(addons: AddonData[], selected: string[], devices: number): number {
+ const addonById = new Map(addons.map((addon) => [addon.id, addon] as const));
+ return selected.reduce((sum, id) => {
+ const addon = addonById.get(id);
+ return sum + (addon ? addonPriceForDevices(addon, devices) : 0);
+ }, 0);
}
/* ── Date ────────────────────────────────────────────────── */
diff --git a/src/pages/PlanPage.test.tsx b/src/pages/PlanPage.test.tsx
index 3141057..f102d03 100644
--- a/src/pages/PlanPage.test.tsx
+++ b/src/pages/PlanPage.test.tsx
@@ -43,8 +43,8 @@ function renderPlan(initialEntries: string[] = ['/plan']) {
const PLANS = {
device_price: 100,
addons: [
- { id: 'dedicated-ip', name: 'Dedicated IP', price: 50, free_threshold: 0 },
- { id: 'multi-hop', name: 'Multi-Hop', price: 30, free_threshold: 0 },
+ { id: 'dedicated-ip', name: 'Dedicated IP', price: 50, free_threshold: -1 },
+ { id: 'multi-hop', name: 'Multi-Hop', price: 30, free_threshold: -1 },
],
};
diff --git a/src/pages/PlanPage.tsx b/src/pages/PlanPage.tsx
index 23d5a42..f1d9ada 100644
--- a/src/pages/PlanPage.tsx
+++ b/src/pages/PlanPage.tsx
@@ -166,6 +166,7 @@ export function PlanPage() {