--- name: crazeanalytix-standards description: Coding patterns and standards for the CrazeAnalytix project — an Amazon seller analytics dashboard built with React 19, TypeScript, Vite, and Tailwind CSS. Use this skill when creating new components, API routes, data processors, or modifying existing code in the CrazeAnalytix codebase to ensure consistency with established conventions. --- # CrazeAnalytix Development Standards ## Architecture Overview Single-page React 19 app with centralized state in `App.tsx`, Vercel serverless API routes, and client-side data processing. Data flows: Dropbox → `/api/*` routes → `dataProcessor.ts` → App state → View components. ## Component Patterns ### Structure ```tsx interface MyComponentProps { data: SalesRecord[]; filters: FilterState; onFilterChange: (key: keyof FilterState, value: string[]) => void; } const MyComponent: React.FC = ({ data, filters, onFilterChange }) => { const computed = useMemo(() => aggregateData(data), [data]); const handleChange = useCallback((value: string) => { onFilterChange('customer', [value]); }, [onFilterChange]); return
...
; }; export default MyComponent; ``` Rules: - Define `Props` interface above component - Use `React.FC` typing - Wrap expensive computations in `useMemo` - Wrap event handlers in `useCallback` - Use `React.memo()` for row/item renderers in lists and tables - Lazy-load view components: `const DataGrid = lazy(() => import('./components/DataGrid'))` ### State Management All global state lives in `App.tsx` via `useState` hooks. No Redux. Props drilling to children. ```tsx const [rawData, setRawData] = useState([]); const [adsData, setAdsData] = useState([]); const [stockMap, setStockMap] = useState>(new Map()); const [filters, setFilters] = useState({ customer: [], year: [], ... }); ``` ## Naming Conventions | Context | Convention | Example | |---------|-----------|---------| | Components | PascalCase `.tsx` | `Dashboard.tsx`, `FilterBar.tsx` | | Services | camelCase `.ts` | `dataProcessor.ts`, `geminiService.ts` | | API routes | kebab-case `.ts` | `fetch-ads.ts`, `ask-gemini.ts` | | Process functions | `process` | `processCSV`, `processAdsExcel` | | Event handlers | `handle` | `handleDataFetch`, `handleFilterChange` | | Calculators | `calculate` | `calculateVelocityMap`, `calculateLineMovers` | | State setters | `set` | `setRawData`, `setFilters` | | Map variables | `Map` | `stockMap`, `vendorStockMap`, `buyBoxLostMap` | | Constants | `UPPER_SNAKE_CASE` | `MONTH_ORDER`, `PAN_EU_COUNTRIES` | | Props callbacks | `on` | `onFilterChange`, `onAdsUpload` | ## API Route Pattern All routes in `/api/` follow this structure: ```typescript import type { VercelRequest, VercelResponse } from '@vercel/node'; const RESOURCE_DROPBOX_URL = "https://www.dropbox.com/...&dl=1"; export default async function handler(req: VercelRequest, res: VercelResponse) { res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); if (req.method === 'OPTIONS') return res.status(200).end(); try { console.log('[route-name] Fetching from Dropbox...'); const response = await fetch(RESOURCE_DROPBOX_URL, { cache: 'no-store', headers: { 'Pragma': 'no-cache', 'Cache-Control': 'no-cache' } }); if (!response.ok) throw new Error(`Dropbox responded with ${response.status}`); const buffer = await response.arrayBuffer(); res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); res.status(200).send(Buffer.from(buffer)); } catch (error: any) { console.error('[route-name] Error:', error); res.status(500).json({ error: error.message }); } } ``` ## Data Processing All processing lives in `services/dataProcessor.ts`. See [references/data-processing.md](references/data-processing.md) for detailed patterns. Key rules: - Use `getColumnValue(row, aliases[])` for flexible column mapping (supports multiple header names) - Parse currencies with `parseCurrency()` — handles both EU (`1.234,56`) and US (`1,234.56`) formats - Normalize months with `normalizeMonth()` — handles English, Spanish, numeric, and Excel serial dates - Filter with `isAllowedCustomer()` before returning processed data - `PAN_EU_COUNTRIES = ['Amazon DE', 'Amazon IT', 'Amazon FR', 'Amazon ES']` is the default when no customer is selected ## Styling Standards Dark theme with Tailwind CSS (loaded via CDN). No custom CSS files. **Color system:** - Backgrounds: `bg-slate-950` (page), `bg-slate-900` (cards), `bg-slate-800` (inputs/hover) - Text: `text-slate-100` (primary), `text-slate-300` (secondary), `text-slate-400`/`text-slate-500` (muted) - Borders: `border-slate-700` - Accent colors: indigo (`#6366f1`), pink (`#ec4899`), emerald (`#10b981`), amber (`#f59e0b`) **Responsive (mobile-first):** ```tsx className="text-xs md:text-sm px-3 py-2 md:px-6 md:py-3" className="flex flex-col gap-3 md:flex-row" className="grid grid-cols-2 md:grid-cols-4 gap-4" ``` **Interactive states:** ```tsx className="hover:bg-white/[0.02] transition-colors" className="hover:scale-105 transition-all" ``` **Badge pattern:** ```tsx className="inline-flex items-center gap-1.5 px-2 py-1 rounded bg-gradient-to-br from-amber-500 to-orange-500 text-white border border-amber-400/50 shadow-lg" ``` ## Types All interfaces in `types.ts`. See [references/type-definitions.md](references/type-definitions.md) for full details. Core types: `SalesRecord`, `AdsRecord`, `TrafficRecord`, `ForecastRecord`, `FilterState`, `AggregatedData`, `PivotRow`, `CombinedKPIs`, `GrowthMetric`. ## Excel/CSV Processing - **CSV**: PapaParse with `{ header: true, skipEmptyLines: true }` - **Excel**: XLSX library with `XLSX.read(buffer, { type: 'array' })`, then `sheet_to_json(worksheet, { header: 1 })` - Always filter: `.filter(r => r.year > 2023 && isAllowedCustomer(r.customer))` - Support multiple column header aliases for robust parsing ## Caching IndexedDB via `services/storage.ts` with schema versioning. Cache data on load, serve cached immediately, refresh in background.