From 593d1594bd5bd03e1194ba37d776b31b58a8c8bc Mon Sep 17 00:00:00 2001 From: Ho Ngoc Hai Date: Sun, 19 Apr 2026 16:01:13 +0700 Subject: [PATCH] refactor(web): replace emoji icons with lucide-react across the app MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 . 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 . - 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) --- .../(dashboard)/dashboard/kyc/page.tsx | 5 ++- .../[locale]/(dashboard)/inquiries/page.tsx | 3 +- .../[locale]/(public)/chuyen-nhuong/page.tsx | 34 +++++++++-------- .../chuyen-nhuong-detail-client.tsx | 8 +++- .../chuyen-nhuong/transfer-listing-card.tsx | 8 +++- .../chuyen-nhuong/transfer-wizard-client.tsx | 5 ++- .../listings/listing-detail-client.tsx | 4 +- .../neighborhood/neighborhood-poi-map.tsx | 38 +++++++++++++++---- apps/web/components/neighborhood/types.ts | 24 ++++++++---- apps/web/lib/chuyen-nhuong-api.ts | 23 +++++++---- apps/web/lib/listing-personas.ts | 31 ++++++++++----- apps/web/lib/validations/transfer.ts | 27 +++++++++---- apps/web/tsconfig.tsbuildinfo | 2 +- 13 files changed, 149 insertions(+), 63 deletions(-) diff --git a/apps/web/app/[locale]/(dashboard)/dashboard/kyc/page.tsx b/apps/web/app/[locale]/(dashboard)/dashboard/kyc/page.tsx index 46c1be7..914768d 100644 --- a/apps/web/app/[locale]/(dashboard)/dashboard/kyc/page.tsx +++ b/apps/web/app/[locale]/(dashboard)/dashboard/kyc/page.tsx @@ -1,5 +1,6 @@ 'use client'; +import { Check } from 'lucide-react'; import { useState, useCallback, useEffect } from 'react'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; @@ -530,8 +531,8 @@ export default function KycPage() { {kycStatus === 'VERIFIED' && ( -
- ✓ +
+

Danh tính đã được xác minh

diff --git a/apps/web/app/[locale]/(dashboard)/inquiries/page.tsx b/apps/web/app/[locale]/(dashboard)/inquiries/page.tsx index 3964a3d..ea839f0 100644 --- a/apps/web/app/[locale]/(dashboard)/inquiries/page.tsx +++ b/apps/web/app/[locale]/(dashboard)/inquiries/page.tsx @@ -1,5 +1,6 @@ 'use client'; +import { Inbox } from 'lucide-react'; import * as React from 'react'; import { InquiryDetailDialog } from '@/components/inquiries/inquiry-detail-dialog'; import { InquiryRow, InquiryStatusBadge } from '@/components/inquiries/inquiry-row'; @@ -103,7 +104,7 @@ export default function InquiriesPage() {

) : filteredData.length === 0 ? (
-

📭

+
{/* Results */} diff --git a/apps/web/components/chuyen-nhuong/chuyen-nhuong-detail-client.tsx b/apps/web/components/chuyen-nhuong/chuyen-nhuong-detail-client.tsx index c7203bb..224cd42 100644 --- a/apps/web/components/chuyen-nhuong/chuyen-nhuong-detail-client.tsx +++ b/apps/web/components/chuyen-nhuong/chuyen-nhuong-detail-client.tsx @@ -43,8 +43,12 @@ export function ChuyenNhuongDetailClient({ listing }: ChuyenNhuongDetailClientPr {STATUS_LABELS[listing.status]} - - {CATEGORY_ICONS[listing.category]} {CATEGORY_LABELS[listing.category]} + + {(() => { + const Icon = CATEGORY_ICONS[listing.category]; + return {listing.isNegotiable && ( diff --git a/apps/web/components/chuyen-nhuong/transfer-listing-card.tsx b/apps/web/components/chuyen-nhuong/transfer-listing-card.tsx index 82bab1e..40c75bf 100644 --- a/apps/web/components/chuyen-nhuong/transfer-listing-card.tsx +++ b/apps/web/components/chuyen-nhuong/transfer-listing-card.tsx @@ -44,8 +44,12 @@ export function TransferListingCard({ listing }: TransferListingCardProps) { {/* Category */}
- - {CATEGORY_ICONS[listing.category]} {CATEGORY_LABELS[listing.category]} + + {(() => { + const Icon = CATEGORY_ICONS[listing.category]; + return
diff --git a/apps/web/components/chuyen-nhuong/transfer-wizard-client.tsx b/apps/web/components/chuyen-nhuong/transfer-wizard-client.tsx index 8a5b1e2..01b2d8c 100644 --- a/apps/web/components/chuyen-nhuong/transfer-wizard-client.tsx +++ b/apps/web/components/chuyen-nhuong/transfer-wizard-client.tsx @@ -52,7 +52,10 @@ function StepCategory() { category === value && 'border-primary bg-primary/5 ring-1 ring-primary', )} > - {CATEGORY_ICONS[value]} + {(() => { + const Icon = CATEGORY_ICONS[value]; + return