- Add @@unique([subscriptionId, metric, periodStart, periodEnd]) constraint
to UsageRecord model with corresponding migration
- Replace racy findFirst+update/create pattern with Prisma upsert using
INSERT ON CONFLICT DO UPDATE SET count = count + delta
- Fix CheckQuotaHandler to use period-scoped findUnique instead of
unscoped findFirst, preventing stale cross-period reads
- Update tests to reflect atomic upsert pattern
Closes GOO-4
Co-Authored-By: Paperclip <noreply@paperclip.ing>
User directive: avoid emojis for UI chrome; keep the icon language
consistent with the rest of the design system (shadcn + lucide-react).
Swaps
-----
- lib/listing-personas.ts — Persona emojis (👨👩👧🏡🚇🧑💻🌳📈🛡️🏥)
→ Lucide icons (Baby, Home, TrainFront, Laptop, Trees, TrendingUp,
Shield, HeartPulse). Persona type now carries `icon: LucideIcon`.
- components/neighborhood/types.ts — POI_CATEGORY_CONFIG emojis
(🏫🏥🚇🛒🍽️🌳) → Lucide (GraduationCap, Stethoscope, TrainFront,
ShoppingBag, UtensilsCrossed, Trees). Config type tightened to
`icon: LucideIcon`.
- components/neighborhood/neighborhood-poi-map.tsx — filter pills
now render <config.icon h-3.5 w-3.5>. Map markers were text-emoji
(el.textContent = config.icon); replaced with hard-coded inline
SVG strings per category (POI_MARKER_SVG) since lucide-static
isn't installed. Marker bumped 28px → 32px for larger hit target.
Popup now shows only the property name + category label (no
emoji prefix). closeButton: true + closeOnClick: true for
better dismissibility.
- listing-detail-client.tsx — PersonaFitCard now renders
<p.icon h-4 w-4 aria-hidden>.
- transfer / chuyen-nhuong files — category icons (🛋️🧊🖥️🍳🛍️🏠)
migrated to Lucide (Sofa, Refrigerator, Monitor, ChefHat, Store,
Home) with type `icon: LucideIcon`.
- Small replacements: inquiries page 📭 → Inbox; kyc page ✓ → Check.
POI popup click fix
-------------------
The inner SVG inside each POI marker was capturing pointer events
before Mapbox's marker-click handler saw them, so clicking a marker
did nothing. Explicit `innerSvg.style.pointerEvents = 'none'` lets
clicks reach the wrapping .poi-marker div that setPopup() is bound
to. Verified via DOM dispatch: click → popup opens with property
name + category + distance + × close.
Verification
------------
- Grep across the 4 scoped files for emoji code points → 0 hits.
- pnpm -w test: 624/624 green.
- Typecheck: no new errors in touched files.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add three new frontend page sections:
- Industrial parks (khu-cong-nghiep): listing, detail, filter bar
- Transfer listings (chuyen-nhuong): search, category tabs, detail
- AI reports dashboard: list, create, viewer with TOC
Includes components, API clients, hooks, server helpers, i18n keys,
navigation links in public and dashboard layouts, and lint fixes.
Co-Authored-By: Paperclip <noreply@paperclip.ing>