feat: Responsive admin UI, SEO pages, and click analytics
## Responsive Admin UI - Layout.tsx: Mobile sidebar drawer with hamburger menu - Dashboard.tsx: 2-col grid on mobile, responsive stats cards - OrchestratorDashboard.tsx: Responsive table with hidden columns - PagesTab.tsx: Responsive filters and table ## SEO Pages - New /admin/seo section with state landing pages - SEO page generation and management - State page content with dispensary/product counts ## Click Analytics - Product click tracking infrastructure - Click analytics dashboard ## Other Changes - Consumer features scaffolding (alerts, deals, favorites) - Health panel component - Workers dashboard improvements - Legacy DutchieAZ pages removed 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
53
CLAUDE.md
53
CLAUDE.md
@@ -340,6 +340,59 @@ The custom connection module at `src/dutchie-az/db/connection` is **DEPRECATED**
|
||||
|
||||
---
|
||||
|
||||
## PERFORMANCE REQUIREMENTS
|
||||
|
||||
**Database Queries:**
|
||||
- NEVER write N+1 queries - always batch fetch related data before iterating
|
||||
- NEVER run queries inside loops - batch them before the loop
|
||||
- Avoid multiple queries when one JOIN or subquery works
|
||||
- Dashboard/index pages should use MAX 5-10 queries total, not 50+
|
||||
- Mentally trace query count - if a page would run 20+ queries, refactor
|
||||
- Cache expensive aggregations (in-memory or Redis, 5-min TTL) instead of recalculating every request
|
||||
- Use query logging during development to verify query count
|
||||
|
||||
**Before submitting route/controller code, verify:**
|
||||
1. No queries inside `forEach`/`map`/`for` loops
|
||||
2. All related data fetched in batches before iteration
|
||||
3. Aggregations done in SQL (`COUNT`, `SUM`, `AVG`, `GROUP BY`), not in JS
|
||||
4. **Would this cause a 503 under load? If unsure, simplify.**
|
||||
|
||||
**Examples of BAD patterns:**
|
||||
```typescript
|
||||
// BAD: N+1 query - runs a query for each store
|
||||
const stores = await getStores();
|
||||
for (const store of stores) {
|
||||
store.products = await getProductsByStoreId(store.id); // N queries!
|
||||
}
|
||||
|
||||
// BAD: Query inside map
|
||||
const results = await Promise.all(
|
||||
storeIds.map(id => pool.query('SELECT * FROM products WHERE store_id = $1', [id]))
|
||||
);
|
||||
```
|
||||
|
||||
**Examples of GOOD patterns:**
|
||||
```typescript
|
||||
// GOOD: Batch fetch all products, then group in JS
|
||||
const stores = await getStores();
|
||||
const storeIds = stores.map(s => s.id);
|
||||
const allProducts = await pool.query(
|
||||
'SELECT * FROM products WHERE store_id = ANY($1)', [storeIds]
|
||||
);
|
||||
const productsByStore = groupBy(allProducts.rows, 'store_id');
|
||||
stores.forEach(s => s.products = productsByStore[s.id] || []);
|
||||
|
||||
// GOOD: Single query with JOIN
|
||||
const result = await pool.query(`
|
||||
SELECT s.*, COUNT(p.id) as product_count
|
||||
FROM stores s
|
||||
LEFT JOIN products p ON p.store_id = s.id
|
||||
GROUP BY s.id
|
||||
`);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## FORBIDDEN ACTIONS
|
||||
|
||||
1. **Deleting any data** (products, snapshots, images, logs, traces)
|
||||
|
||||
Reference in New Issue
Block a user