{"_id":"@bluecircuit/offer-calculator","name":"@bluecircuit/offer-calculator","dist-tags":{"latest":"2.0.0"},"versions":{"2.0.0":{"name":"@bluecircuit/offer-calculator","version":"2.0.0","description":"CMS-driven React component for device offer calculation with multiplicative cascading discounts","author":{"name":"Alex shatalin","email":"riversidedevelopers99@gmail.com"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/your-org/offer-calculator.git"},"keywords":["react","calculator","offer","pricing","typescript","nextjs","cms"],"main":"./dist/index.js","module":"./dist/index.mjs","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"},"./styles":{"import":"./dist/calculator.css","require":"./dist/calculator.css"},"./types":{"types":"./dist/types.d.ts","import":"./dist/types.mjs","require":"./dist/types.js"}},"scripts":{"dev":"tsup --watch","build":"tsup","test":"vitest run","test:watch":"vitest","test:coverage":"vitest run --coverage","lint":"eslint src --ext .ts,.tsx","typecheck":"tsc --noEmit","prepublishOnly":"npm run build && npm run test","prepare":"npm run build"},"peerDependencies":{"react":"^18.0.0 || ^19.0.0","react-dom":"^18.0.0 || ^19.0.0"},"peerDependenciesMeta":{"next":{"optional":true}},"devDependencies":{"@testing-library/jest-dom":"^6.1.5","@testing-library/react":"^14.1.2","@types/node":"^20.10.6","@types/react":"^18.2.46","@types/react-dom":"^18.2.18","@typescript-eslint/eslint-plugin":"^6.17.0","@typescript-eslint/parser":"^6.17.0","@vitest/coverage-v8":"^1.1.0","eslint":"^8.56.0","eslint-plugin-react":"^7.33.2","eslint-plugin-react-hooks":"^4.6.0","jsdom":"^23.0.1","react":"^18.2.0","react-dom":"^18.2.0","tsup":"^8.0.1","typescript":"^5.3.3","vitest":"^4.0.17"},"engines":{"node":">=18.0.0"},"_id":"@bluecircuit/offer-calculator@2.0.0","gitHead":"dfe84f2aee7f480fecf4501eb3b27c482ec46bf3","bugs":{"url":"https://github.com/your-org/offer-calculator/issues"},"homepage":"https://github.com/your-org/offer-calculator#readme","_nodeVersion":"22.19.0","_npmVersion":"10.9.3","dist":{"integrity":"sha512-VMGIaOYAdXckFBuEaonz9cTval5MPBJwmxEtdjULtgYgJuCiCgeDf8zt85hh8hzqsZ/NG4gMFUqxaforEvpE0A==","shasum":"757b17f51c77577230dba93af620f60ee9569e90","tarball":"https://registry.npmjs.org/@bluecircuit/offer-calculator/-/offer-calculator-2.0.0.tgz","fileCount":18,"unpackedSize":348396,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDCxE1xdw8xZPzoL1z8ujidlbjwoC/JSkv4izgl9gPWPAIhAIj2sY7VvcJoG3HWNwatYuO9f91IVFbgSTTxf2uQsD3O"}]},"_npmUser":{"name":"ashatalin","email":"ashatalin1981@gmail.com"},"directories":{},"maintainers":[{"name":"ashatalin","email":"ashatalin1981@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/offer-calculator_2.0.0_1768962215525_0.9072202369551885"},"_hasShrinkwrap":false}},"time":{"created":"2026-01-21T02:23:35.465Z","2.0.0":"2026-01-21T02:23:35.720Z","modified":"2026-01-21T02:23:35.888Z"},"maintainers":[{"name":"ashatalin","email":"ashatalin1981@gmail.com"}],"description":"CMS-driven React component for device offer calculation with multiplicative cascading discounts","homepage":"https://github.com/your-org/offer-calculator#readme","keywords":["react","calculator","offer","pricing","typescript","nextjs","cms"],"repository":{"type":"git","url":"git+https://github.com/your-org/offer-calculator.git"},"author":{"name":"Alex shatalin","email":"riversidedevelopers99@gmail.com"},"bugs":{"url":"https://github.com/your-org/offer-calculator/issues"},"license":"MIT","readme":"# React Offer Calculator Component\n\nA reusable, CMS-driven React component for calculating device offers with multiplicative cascading discounts.\n\n## Features\n\n- **CMS-Driven**: All configuration (prices, defects, conditions) loaded from CMS\n- **Multiplicative Cascading**: Order-independent discount calculation\n- **Minimum Price Enforcement**: Configurable floor price (default: $1.00)\n- **TypeScript**: Fully typed for type safety\n- **Accessible**: ARIA support, keyboard navigation, screen reader friendly\n- **Server-Side Rendering**: Next.js App Router compatible\n- **Tested**: 50+ unit tests covering all edge cases\n\n## Quick Start\n\n### Installation\n\n```bash\n# Install dependencies (if using standalone)\nnpm install react react-dom next\n\n# Or copy the src/ directory into your Next.js project\n```\n\n### Basic Usage\n\n```tsx\nimport { OfferCalculator } from '@/components/calculator/OfferCalculator';\nimport { fetchCalculatorConfig } from '@/lib/cms/fetchCalculatorConfig';\n\nexport default async function CalculatorPage() {\n  // Fetch configuration from CMS\n  const config = await fetchCalculatorConfig('7885');\n\n  return <OfferCalculator config={config} />;\n}\n```\n\n## Project Structure\n\n```\nsrc/\n├── lib/\n│   ├── calculator/\n│   │   ├── types.ts              # TypeScript interfaces\n│   │   ├── pricing.ts            # Core calculation logic\n│   │   ├── validation.ts         # Config validation\n│   │   └── hooks.ts              # React hooks\n│   └── cms/\n│       └── fetchCalculatorConfig.ts  # CMS data fetcher\n├── components/\n│   └── calculator/\n│       ├── OfferCalculator.tsx   # Main component\n│       ├── ConditionTabs.tsx     # Condition selector\n│       ├── DefectCheckbox.tsx    # Defect checkbox\n│       ├── PriceDisplay.tsx      # Price display\n│       └── calculator.module.css # Styles\n└── app/\n    └── calculator/\n        ├── page.tsx              # Server component\n        └── loading.tsx           # Loading state\n```\n\n## CMS Data Contract\n\n### API Endpoint\n\n```typescript\nGET /api/calculator/config?deviceId=7885\n\nResponse: CalculatorConfig\n```\n\n### Data Structure\n\n```typescript\ninterface CalculatorConfig {\n  basePrice: number;              // e.g., 230.00\n  currency: string;               // e.g., \"USD\"\n  defects: DefectAdjustment[];    // List of selectable defects\n  conditions: ConditionTier[];    // Condition tiers\n  device: DeviceInfo;             // Device metadata\n  minimumOffer: number;           // Minimum floor (e.g., 1.00)\n}\n\ninterface DefectAdjustment {\n  id: string;                     // e.g., \"check1\"\n  label: string;                  // e.g., \"Motherboard issues\"\n  percent: number;                // 0-100 (e.g., 60 = 60% discount)\n  description?: string;           // Tooltip text\n  category?: string;              // Grouping (e.g., \"hardware\")\n}\n\ninterface ConditionTier {\n  id: string;                     // e.g., \"flawless\"\n  label: string;                  // e.g., \"Flawless\"\n  percent: number;                // e.g., 100 (CMS value)\n  multiplier: number;             // Computed: percent / 100\n  description: string;            // Condition description\n  isDefault?: boolean;            // Mark default tier\n}\n```\n\n### Example CMS Response\n\n```json\n{\n  \"basePrice\": 230.00,\n  \"currency\": \"USD\",\n  \"defects\": [\n    {\n      \"id\": \"check1\",\n      \"label\": \"Motherboard issues\",\n      \"percent\": 60,\n      \"description\": \"Select if device has defective MB...\",\n      \"category\": \"critical\"\n    }\n  ],\n  \"conditions\": [\n    {\n      \"id\": \"flawless\",\n      \"label\": \"Flawless\",\n      \"percent\": 100,\n      \"multiplier\": 1.0,\n      \"description\": \"Like New, no visible signs of previous usage.\",\n      \"isDefault\": true\n    }\n  ],\n  \"device\": {\n    \"id\": \"7885\",\n    \"model\": \"Dell G15 5510\",\n    \"type\": \"laptop\"\n  },\n  \"minimumOffer\": 1.00\n}\n```\n\n## Calculation Algorithm\n\n### How It Works\n\n1. **Apply Condition Multiplier**\n   ```\n   price = basePrice * conditionMultiplier\n   ```\n\n2. **Sort Defects by Severity** (DESC)\n   ```\n   sorted = [60%, 45%, 15%]\n   ```\n\n3. **Apply Each Defect Multiplicatively**\n   ```\n   price = price * (1 - percent/100)\n   ```\n\n4. **Round to 2 Decimals**\n   ```\n   price = Math.round(price * 100) / 100\n   ```\n\n5. **Enforce Minimum**\n   ```\n   finalPrice = Math.max(minimumOffer, price)\n   ```\n\n### Example Calculation\n\n```\nBase Price: $230.00\nCondition: Flawless (1.0x)\nDefects: Motherboard (60%), Screen (45%)\n\nStep 1: Apply condition\n  $230.00 * 1.0 = $230.00\n\nStep 2: Sort defects\n  [60%, 45%]\n\nStep 3: Apply defects\n  $230.00 * (1 - 0.60) = $230.00 * 0.40 = $92.00\n  $92.00 * (1 - 0.45) = $92.00 * 0.55 = $50.60\n\nStep 4: Round\n  $50.60\n\nStep 5: Check minimum\n  max($1.00, $50.60) = $50.60\n\nFinal Price: $50.60\n```\n\n## Component Props\n\n### `<OfferCalculator>`\n\n```typescript\ninterface OfferCalculatorProps {\n  config: CalculatorConfig;       // Required: CMS configuration\n  showBreakdown?: boolean;        // Show detailed breakdown\n  className?: string;             // Additional CSS classes\n  formFieldPrefix?: string;       // Prefix for hidden form inputs\n}\n```\n\n### Example Usage\n\n```tsx\n<OfferCalculator\n  config={config}\n  showBreakdown={true}\n  className=\"my-custom-class\"\n  formFieldPrefix=\"offer\"\n/>\n```\n\n## Customization\n\n### Styling\n\n#### Option 1: Override CSS Module Classes\n\n```tsx\nimport styles from './custom-calculator.module.css';\n\n<OfferCalculator config={config} className={styles.customCalculator} />\n```\n\n#### Option 2: Global CSS\n\n```css\n/* Override specific elements */\n.condition-tabs__tab {\n  background: #your-color;\n  border-radius: 12px;\n}\n\n.price-display__main {\n  background: linear-gradient(135deg, #your-gradient);\n}\n```\n\n#### Option 3: Inline Styles\n\n```tsx\n<div style={{ maxWidth: '600px', margin: '0 auto' }}>\n  <OfferCalculator config={config} />\n</div>\n```\n\n### Using with Different CMS\n\nUpdate `src/lib/cms/fetchCalculatorConfig.ts`:\n\n```typescript\nexport async function fetchCalculatorConfig(deviceId: string) {\n  // Replace with your CMS API endpoint\n  const response = await fetch(`https://your-cms.com/api/devices/${deviceId}`);\n  const rawData = await response.json();\n\n  // Map your CMS data to CalculatorConfig\n  const config = normalizeConfig({\n    basePrice: rawData.price,\n    currency: rawData.currency || 'USD',\n    defects: rawData.defects_list,\n    conditions: rawData.condition_tiers,\n    device: rawData.device_info,\n    minimumOffer: rawData.min_offer || 1.0,\n  });\n\n  return config;\n}\n```\n\n## Environment Variables\n\n```bash\n# .env.local\n\n# API endpoint for calculator configuration\nCALCULATOR_API_URL=https://your-cms.com/api/calculator/config\n\n# Use mock data (for development)\nUSE_MOCK_CALCULATOR_DATA=true\n```\n\n## Testing\n\n### Run Unit Tests\n\n```bash\n# Using Vitest\nnpm test\n\n# Watch mode\nnpm test -- --watch\n\n# Coverage\nnpm test -- --coverage\n```\n\n### Test Coverage\n\n- ✅ 36 pricing algorithm tests\n- ✅ Minimum price enforcement\n- ✅ Order independence\n- ✅ CMS validation\n- ✅ Decimal precision\n- ✅ Real-world scenarios\n- ✅ Condition multipliers\n\n## Accessibility\n\nThe calculator is built with accessibility in mind:\n\n- **ARIA Roles**: Proper `tablist`, `tab`, `tabpanel` roles\n- **Keyboard Navigation**: Arrow keys, Tab, Space, Enter\n- **Screen Reader Support**: Descriptive labels, live regions\n- **Focus Management**: Logical tab order\n- **Color Contrast**: WCAG AA compliant\n\n### Keyboard Shortcuts\n\n| Key | Action |\n|-----|--------|\n| `Tab` | Navigate between elements |\n| `Arrow Keys` | Navigate condition tabs |\n| `Space` | Toggle checkbox/radio |\n| `Enter` | Activate button |\n\n## Hidden Form Inputs\n\nThe calculator automatically creates hidden inputs for form submission:\n\n```html\n<input type=\"hidden\" name=\"device_price\" value=\"50.60\" />\n<input type=\"hidden\" name=\"gadget_cosmetic_condition\" value=\"flawless\" />\n<input type=\"hidden\" name=\"selected_defects\" value='[\"check1\",\"check2\"]' />\n<input type=\"hidden\" name=\"device_id\" value=\"7885\" />\n```\n\nAccess these in your backend:\n\n```typescript\n// Next.js API route\nexport async function POST(request: Request) {\n  const formData = await request.formData();\n\n  const price = formData.get('device_price');\n  const condition = formData.get('gadget_cosmetic_condition');\n  const defects = JSON.parse(formData.get('selected_defects'));\n  const deviceId = formData.get('device_id');\n\n  // Process offer...\n}\n```\n\n## Caching & Revalidation\n\n### Next.js ISR (Incremental Static Regeneration)\n\n```typescript\n// Automatic caching (1 hour)\nconst config = await fetchCalculatorConfig(deviceId);\n```\n\n### On-Demand Revalidation\n\n```typescript\nimport { revalidateCalculatorConfig } from '@/lib/cms/fetchCalculatorConfig';\n\n// In webhook or API route\nawait revalidateCalculatorConfig('7885');\n```\n\n### Manual Cache Control\n\n```typescript\nconst config = await fetch('/api/calculator/config?deviceId=7885', {\n  next: {\n    revalidate: 3600,  // Cache for 1 hour\n    tags: ['calculator-config-7885'],\n  },\n});\n```\n\n## Troubleshooting\n\n### Calculator shows $1.00 for everything\n\n**Issue**: Base price is invalid or missing\n\n**Solution**: Check CMS response contains valid `basePrice` number\n\n```typescript\nconsole.log('Config:', config);\n// Check: config.basePrice > 0\n```\n\n### Defects not appearing\n\n**Issue**: Defects array is empty or invalid\n\n**Solution**: Validate `config.defects` has valid items\n\n```typescript\nconsole.log('Defects:', config.defects);\n// Check: Array with id, label, percent\n```\n\n### Price doesn't update when selecting defects\n\n**Issue**: React state not updating\n\n**Solution**: Check browser console for errors, ensure `useOfferCalculator` hook is working\n\n### Styles not loading\n\n**Issue**: CSS module not imported\n\n**Solution**: Ensure `calculator.module.css` is imported in component\n\n## Performance\n\n- **Initial Load**: ~50kb gzipped (component + deps)\n- **Calculation Time**: <5ms for any number of defects\n- **Re-renders**: Optimized with `useMemo` and `useCallback`\n- **Bundle Size**: Tree-shakeable, no unnecessary dependencies\n\n## Browser Support\n\n- Chrome/Edge: ✅ Latest 2 versions\n- Firefox: ✅ Latest 2 versions\n- Safari: ✅ Latest 2 versions\n- Mobile Safari: ✅ iOS 14+\n- Chrome Mobile: ✅ Latest\n\n## License\n\nMIT License - See existing calculator license.\n\n## Support\n\nFor issues or questions:\n1. Check this README\n2. Review test cases in `tests/calculator/pricing.test.ts`\n3. Inspect browser console for errors\n4. Check CMS response format\n\n## Migration from Vanilla JS\n\nIf migrating from the original vanilla JS calculator:\n\n1. All calculation logic is preserved (identical results)\n2. Same multiplicative cascading algorithm\n3. Same minimum price enforcement\n4. Same defect percentages\n5. Enhanced with TypeScript type safety\n6. React component architecture for reusability\n\n## Version History\n\n- **v2.0.0**: React component conversion\n  - TypeScript rewrite\n  - CMS integration\n  - Server-side rendering support\n  - Comprehensive test suite\n\n- **v1.0.0**: Original vanilla JS implementation\n  - Multiplicative cascading\n  - Minimum price enforcement\n  - 36 unit tests\n","readmeFilename":"README.md","_rev":"1-c6b7b40392b058c5e355eac25b8ff58e"}