feat(config): add-ons pricing source of truth#19
Conversation
Define the add-on catalog the onboarding 'ADD-ONS / packs' step was missing (was a '0 packs added' placeholder). Per-module add-ons (Web/CCTV/Social) plus the cross-module 'platform' intelligence layer, priced like modules (monthly + ~17%-off annual). Marketing (landing-client) must mirror this — no add-on prices invented elsewhere.
📝 WalkthroughWalkthroughA new Add-ons pricing data module
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
src/config/addons.tsxESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/config/addons.tsx (1)
22-38: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winMake the catalog immutable at the module boundary.
Line 38 exports a mutable array of mutable objects, and Line 186 returns those same references. Any consumer can accidentally rewrite a price or requirement and change the shared pricing source for the rest of the session.
Proposed change
export interface AddOnConfig { - id: string; - scope: AddOnScope; - title: string; - description: string; - icon: string; - price: { - monthly: number; - annual: number; + readonly id: string; + readonly scope: AddOnScope; + readonly title: string; + readonly description: string; + readonly icon: string; + readonly price: { + readonly monthly: number; + readonly annual: number; }; /** Eligibility / prerequisite note shown under the price. */ - requirement: string; + readonly requirement: string; /** What the add-on unlocks. */ - unlocks: string[]; + readonly unlocks: readonly string[]; } -export const ADD_ONS: AddOnConfig[] = [ +export const ADD_ONS = [ // ... -]; +] as const satisfies readonly AddOnConfig[]; /** Add-ons scoped to a given module, or the cross-module "platform" layer. */ -export const getAddOns = (scope: AddOnScope): AddOnConfig[] => +export const getAddOns = (scope: AddOnScope): readonly AddOnConfig[] => ADD_ONS.filter((addon) => addon.scope === scope);Also applies to: 38-38, 186-187
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/config/addons.tsx` around lines 22 - 38, Make the add-on catalog immutable at the module boundary by preventing consumers from mutating ADD_ONS or the objects returned by the helper at the export points in addons.tsx. Update ADD_ONS and the function that returns catalog entries (the one currently returning those same references) so callers receive readonly/frozen data or defensive copies, and ensure the AddOnConfig shape and any related return types reflect that immutability for fields like price, requirement, and unlocks.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@src/config/addons.tsx`:
- Around line 22-38: Make the add-on catalog immutable at the module boundary by
preventing consumers from mutating ADD_ONS or the objects returned by the helper
at the export points in addons.tsx. Update ADD_ONS and the function that returns
catalog entries (the one currently returning those same references) so callers
receive readonly/frozen data or defensive copies, and ensure the AddOnConfig
shape and any related return types reflect that immutability for fields like
price, requirement, and unlocks.
What & why
The onboarding ADD-ONS step is a
"0 packs added"placeholder (src/app/choose-modules/page.tsx) — there was no add-on definition anywhere. This addssrc/config/addons.tsxas the source of truth for add-ons, the same wayplans.tsxis for modules. Marketing (landing-client) should mirror this rather than inventing prices.Model
scope: 'web' | 'cctv' | 'social').scope: 'platform') — Cross-Channel Validation, Agent-to-Agent (A2A/MCP), Advanced Insight, Unified Journey, Ask CROW Pro.monthlyper-month rate,annual= per-month when billed yearly (~17% off, consistent withpricing.ts).iconis a Lucide name string — pure data config, no rendering deps. HelpersgetAddOns(scope)andgetAddOnPrice(addon, billing).Catalog (monthly / annual)
Notes
choose-modulesto render these, and mirror into landing-client's pricing add-ons section.Summary by CodeRabbit