Skip to content

feat: add volume flat amount ui#868

Open
charlietlamb wants to merge 2 commits intodevfrom
volume-flat-amount-ui
Open

feat: add volume flat amount ui#868
charlietlamb wants to merge 2 commits intodevfrom
volume-flat-amount-ui

Conversation

@charlietlamb
Copy link
Contributor

@charlietlamb charlietlamb commented Mar 3, 2026

Screenshot 2026-03-03 at 12 44 24

Greptile Summary

This PR adds a volume flat amount UI for volume-based, multi-tier feature pricing. Users can now toggle between a per-unit (amount) view and a flat amount (flat_amount) view via a new segmented control that appears alongside the existing tier-behavior selector. The feature is backed by a new cleanTiersForMode utility that zeroes out unused fields before commit, and is well-covered by a new test file.

Key changes:

  • Improvements — Extracts tier-behavior Select into a new PriceSectionTitle component and adds a Per Unit / Flat Amount IconCheckbox toggle that is only shown for volume-based, multi-tier items.
  • Improvements — Extends PriceTiers to accept a volumePricingMode prop, switching the editable field between amount and flat_amount and hiding the BillingUnits label in flat mode.
  • Improvements — Adds a cleanTiersForMode utility in tierUtils.ts and a handleBeforeCommit hook in EditPlanFeatureSheet to ensure stale field values are zeroed/nulled before the item is committed.
  • Improvements — Adds an optional onBeforeCommit callback to SheetFooterActions that is invoked synchronously before handleUpdateProductItem, preserving the existing commit flow.
  • Improvements — Introduces comprehensive unit tests for cleanTiersForMode and updateTier (flat_amount path) in tierUtils.test.ts.

Two critical issues in EditPlanFeatureSheet should be addressed before merging:

  1. Stale flat_amount when reducing tiers to one in flat mode — If a user edits a multi-tier volume-based item in flat mode and then removes tiers down to one, the handleBeforeCommit guard condition skips cleanup. The single-tier item is submitted with leftover flat_amount values that contradict the per-unit UI state shown to the user.

  2. volumePricingMode state doesn't reset when switching items — The useState lazy initializer runs only once per mount. If users edit item A (with flat pricing) then switch to item B (without flat pricing) without closing the sheet, the toggle state remains frozen at item A's value, causing incorrect UI state for item B.

Confidence Score: 2/5

  • Not safe to merge — two critical state management bugs in EditPlanFeatureSheet will silently produce incorrect backend data in specific user workflows.
  • The core utility logic (cleanTiersForMode, updateTier) is correct and well-tested. UI components are cleanly structured. However, EditPlanFeatureSheet has two critical bugs: (1) handleBeforeCommit skips cleanup when users reduce a multi-tier volume item to single-tier in flat mode, leaving stale flat_amount in the submitted data; (2) volumePricingMode state won't reset when switching between items without remounting, causing incorrect toggle state. Both bugs lead to silent data corruption and should be fixed before shipping.
  • vite/src/views/products/plan/components/edit-plan-feature/EditPlanFeatureSheet.tsx — requires fixes to handleBeforeCommit guard condition and volumePricingMode state initialization.

Sequence Diagram

sequenceDiagram
    participant User
    participant PriceSectionTitle
    participant EditPlanFeatureSheet
    participant PriceTiers
    participant SheetFooterActions
    participant tierUtils

    User->>PriceSectionTitle: Select "Volume-based" tier behavior
    PriceSectionTitle->>EditPlanFeatureSheet: onTierBehaviorChange("VolumeBased")
    EditPlanFeatureSheet->>EditPlanFeatureSheet: setItem({ tier_behavior: VolumeBased })
    Note over EditPlanFeatureSheet: showVolumePricingToggle = true<br/>(isVolumeBased && isMultiTier)

    User->>PriceSectionTitle: Click "Flat Amount" toggle
    PriceSectionTitle->>EditPlanFeatureSheet: onVolumePricingModeChange("flat")
    EditPlanFeatureSheet->>EditPlanFeatureSheet: setVolumePricingMode("flat")
    EditPlanFeatureSheet->>PriceTiers: volumePricingMode="flat"
    PriceTiers->>User: Shows flat_amount field (hides BillingUnits)

    User->>PriceTiers: Enter flat amount value
    PriceTiers->>tierUtils: updateTier({ field: "flat_amount", value })
    tierUtils->>EditPlanFeatureSheet: setItem(updatedItem)

    User->>SheetFooterActions: Click "Update Plan Feature"
    SheetFooterActions->>EditPlanFeatureSheet: onBeforeCommit()
    EditPlanFeatureSheet->>tierUtils: cleanTiersForMode({ item, mode: "flat" })
    tierUtils-->>EditPlanFeatureSheet: cleaned item (amount=0 preserved)
    EditPlanFeatureSheet->>EditPlanFeatureSheet: setItem(cleaned)
    SheetFooterActions->>SheetFooterActions: handleUpdateProductItem()
    Note over SheetFooterActions: commitItemDraft() or closeSheet()

    User->>PriceSectionTitle: Select "Graduated" tier behavior
    PriceSectionTitle->>EditPlanFeatureSheet: onTierBehaviorChange("Graduated")
    EditPlanFeatureSheet->>EditPlanFeatureSheet: setVolumePricingMode("per_unit")
    EditPlanFeatureSheet->>EditPlanFeatureSheet: clear flat_amount on all tiers
    EditPlanFeatureSheet->>EditPlanFeatureSheet: setItem(newItem)
Loading

Last reviewed commit: c62fc87

Context used:

  • Rule from dashboard - CLAUDE.md (source)

@vercel
Copy link

vercel bot commented Mar 3, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
autumn-vite Ready Ready Preview, Comment Mar 3, 2026 11:23pm

Request Review

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

7 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +74 to +78
const handleBeforeCommit = () => {
if (!showVolumePricingToggle) return;
const cleaned = cleanTiersForMode({ item, mode: volumePricingMode });
setItem(cleaned);
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

flat_amount not cleaned when tiers are reduced to one in flat mode

handleBeforeCommit guards on showVolumePricingToggle (which requires both isVolumeBased && isMultiTier). If a user is in flat mode with 2+ tiers and then removes tiers down to one, showVolumePricingToggle becomes false and the cleanup is skipped. The submitted single-tier item then retains a stale flat_amount even though the UI was presenting it as a per-unit (amount-based) field.

Example failure:

  1. User edits a volume-based item with 2 tiers in flat mode → sets flat_amount on both tiers
  2. User deletes one tier → now 1 tier, showVolumePricingToggle = false
  3. User clicks "Update Plan Feature"
  4. handleBeforeCommit returns early without cleanup
  5. Backend receives single-tier item with flat_amount still populated (incorrect)

Fix: Also clean when the item is volume-based and in flat mode, regardless of tier count:

const handleBeforeCommit = () => {
    if (!isVolumeBased) return;
    if (!showVolumePricingToggle) {
        // Single-tier volume-based: always force per_unit cleanup
        const cleaned = cleanTiersForMode({ item, mode: "per_unit" });
        setItem(cleaned);
        return;
    }
    const cleaned = cleanTiersForMode({ item, mode: volumePricingMode });
    setItem(cleaned);
};
Prompt To Fix With AI
This is a comment left during a code review.
Path: vite/src/views/products/plan/components/edit-plan-feature/EditPlanFeatureSheet.tsx
Line: 74-78

Comment:
**`flat_amount` not cleaned when tiers are reduced to one in flat mode**

`handleBeforeCommit` guards on `showVolumePricingToggle` (which requires both `isVolumeBased && isMultiTier`). If a user is in flat mode with 2+ tiers and then removes tiers down to one, `showVolumePricingToggle` becomes `false` and the cleanup is skipped. The submitted single-tier item then retains a stale `flat_amount` even though the UI was presenting it as a per-unit (amount-based) field.

**Example failure:**
1. User edits a volume-based item with 2 tiers in flat mode → sets `flat_amount` on both tiers
2. User deletes one tier → now 1 tier, `showVolumePricingToggle = false`
3. User clicks "Update Plan Feature"
4. `handleBeforeCommit` returns early without cleanup
5. Backend receives single-tier item with `flat_amount` still populated (incorrect)

**Fix:** Also clean when the item is volume-based and in flat mode, regardless of tier count:

```tsx
const handleBeforeCommit = () => {
    if (!isVolumeBased) return;
    if (!showVolumePricingToggle) {
        // Single-tier volume-based: always force per_unit cleanup
        const cleaned = cleanTiersForMode({ item, mode: "per_unit" });
        setItem(cleaned);
        return;
    }
    const cleaned = cleanTiersForMode({ item, mode: volumePricingMode });
    setItem(cleaned);
};
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +43 to +50
const [volumePricingMode, setVolumePricingMode] = useState<VolumePricingMode>(
() => {
const hasFlatAmount = item?.tiers?.some(
(t) => t.flat_amount != null && t.flat_amount > 0,
);
return hasFlatAmount ? "flat" : "per_unit";
},
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

volumePricingMode not re-initialized when the active item changes

useState lazy initializers run only once per component mount. In the editor context, EditPlanFeatureSheet is not unmounted when users switch between items — only the context item value changes via setCurrentItem. This means if a user edits item A (with flat pricing → volumePricingMode = "flat"), then switches to item B (no flat pricing) without closing the sheet, the toggle will wrongly default to flat mode.

Example failure:

  1. User edits item A which has flat_amount values → volumePricingMode initialized to "flat"
  2. User closes item A and opens item B (no flat_amount)
  3. The sheet is not remounted, so the lazy initializer doesn't run
  4. volumePricingMode remains "flat" even though item B has no flat pricing
  5. UI toggle displays incorrect state for item B

Fix: Add a useEffect to reset the mode whenever the item changes:

useEffect(() => {
    const hasFlatAmount = item?.tiers?.some(
        (t) => t.flat_amount != null && t.flat_amount > 0,
    );
    setVolumePricingMode(hasFlatAmount ? "flat" : "per_unit");
}, [item?.feature_id]); // reset whenever the edited item changes
Prompt To Fix With AI
This is a comment left during a code review.
Path: vite/src/views/products/plan/components/edit-plan-feature/EditPlanFeatureSheet.tsx
Line: 43-50

Comment:
**`volumePricingMode` not re-initialized when the active item changes**

`useState` lazy initializers run only once per component mount. In the editor context, `EditPlanFeatureSheet` is not unmounted when users switch between items — only the context `item` value changes via `setCurrentItem`. This means if a user edits item A (with flat pricing → `volumePricingMode = "flat"`), then switches to item B (no flat pricing) without closing the sheet, the toggle will wrongly default to flat mode.

**Example failure:**
1. User edits item A which has `flat_amount` values → `volumePricingMode` initialized to `"flat"`
2. User closes item A and opens item B (no `flat_amount`)
3. The sheet is not remounted, so the lazy initializer doesn't run
4. `volumePricingMode` remains `"flat"` even though item B has no flat pricing
5. UI toggle displays incorrect state for item B

**Fix:** Add a `useEffect` to reset the mode whenever the item changes:

```tsx
useEffect(() => {
    const hasFlatAmount = item?.tiers?.some(
        (t) => t.flat_amount != null && t.flat_amount > 0,
    );
    setVolumePricingMode(hasFlatAmount ? "flat" : "per_unit");
}, [item?.feature_id]); // reset whenever the edited item changes
```

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 7 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant