- Move 8 stray .md (+5 .txt) from ~/Desktop into docs/explorations/from-desktop/ - Reorganize 27 .md/.txt at workspace root: - audit reports -> docs/audits/ - exploration reports -> docs/explorations/ - design system -> docs/design-system/ - Keep only README/CHANGELOG/CONTRIBUTING/CLAUDE at repo root - Refresh docs/README.md as canonical index with links to all groups - Note: pre-existing docs/audits/AUDIT_INDEX.md and AUDIT_SUMMARY.md were overwritten by the newer root-level versions during the move Co-Authored-By: Paperclip <noreply@paperclip.ing>
358 lines
19 KiB
Plaintext
358 lines
19 KiB
Plaintext
╔════════════════════════════════════════════════════════════════════════════╗
|
|
║ GOODGO PLATFORM AI - NEXT.JS FRONTEND QUICK REFERENCE ║
|
|
╚════════════════════════════════════════════════════════════════════════════╝
|
|
|
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
1. PROJECT STRUCTURE
|
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
|
|
/apps/web/
|
|
├─ app/[locale]/ ← Next.js 15 App Router (public, auth, dashboard, admin)
|
|
├─ components/ ← React components (listings, neighborhood, map, charts, design-system)
|
|
├─ lib/ ← API clients, hooks, stores, utilities
|
|
├─ i18n/ & messages/ ← Internationalization (vi/en)
|
|
└─ public/ ← Static assets
|
|
|
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
2. CORE TECHNOLOGIES
|
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
|
|
Framework: Next.js 15.5.14 (App Router)
|
|
Runtime: React 18.3.0
|
|
Styling: Tailwind CSS 3.4.0 + CVA 0.7.1
|
|
Forms: react-hook-form 7.72.1 + Zod 4.3.6
|
|
State: Zustand 5.0.12 (client) + React Query 5.96.2 (server)
|
|
Mapping: Mapbox GL 3.21.0
|
|
Charts: Recharts 3.8.1
|
|
Icons: lucide-react 1.7.0
|
|
i18n: next-intl 4.9.0
|
|
Theme: next-themes 0.4.6
|
|
Real-time: socket.io-client 4.8.3
|
|
PDF Export: html2canvas 1.4.1 + jspdf 4.2.1
|
|
|
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
3. NEIGHBORHOOD FEATURES
|
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
|
|
COMPONENTS:
|
|
✓ NeighborhoodPOIMap (11 KB, Mapbox GL + 6 POI categories)
|
|
✓ NeighborhoodRadarChart (2 KB, Recharts radar, 0-10 scores, 6 categories)
|
|
✓ NeighborhoodScore (2 KB, compact score display)
|
|
|
|
TYPES & CONFIG:
|
|
✓ NeighborhoodCategory (label, score 0-10, icon)
|
|
✓ POIItem (id, name, category, lat/lng, distance)
|
|
✓ POICategory (school, hospital, transit, shopping, restaurant, park)
|
|
✓ NeighborhoodScoreData (overall score, categories, POIs, center)
|
|
|
|
INTEGRATION:
|
|
✓ Dynamically loaded in listing-detail-client.tsx
|
|
✓ Part of enrichment data pipeline (API response)
|
|
✓ Maps imported with SSR disabled for performance
|
|
|
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
4. MAPBOX GL USAGE
|
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
|
|
COMPONENTS:
|
|
• listing-map.tsx Multiple listings + click handlers
|
|
• location-picker.tsx Interactive location selection
|
|
• neighborhood-poi-map.tsx POI visualization with filters
|
|
|
|
STYLING:
|
|
• Light: mapbox://styles/mapbox/streets-v12
|
|
• Dark: mapbox://styles/mapbox/dark-v11
|
|
• Theme-aware via useMapboxStyle() hook
|
|
|
|
PATTERN:
|
|
'use client' ← Must be client component
|
|
import 'mapbox-gl/dist/mapbox-gl.css'
|
|
const style = useMapboxStyle() ← Get theme-aware style
|
|
new mapboxgl.Map({ style }) ← Create map instance
|
|
|
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
5. CHART COMPONENTS (RECHARTS)
|
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
|
|
✓ price-area-chart.tsx Trend visualization (up/down coloring)
|
|
✓ price-trend-chart.tsx Historical trends
|
|
✓ district-bar-chart.tsx District comparisons
|
|
✓ district-heatmap.tsx Heat visualization (9 KB)
|
|
✓ agent-performance.tsx Agent metrics (6 KB)
|
|
✓ neighborhood-radar-chart.tsx Radar chart (0-10 scores)
|
|
|
|
PATTERN:
|
|
ResponsiveContainer ← Responsive sizing
|
|
CSS variables for theming ← --color-signal-up, --color-signal-down
|
|
Custom Tooltip styling ← Themed backgrounds
|
|
Vietnamese number formatting ← tr/k notation
|
|
|
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
6. API CLIENT PATTERN
|
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
|
|
BASE CLIENT (lib/api-client.ts):
|
|
apiClient.get<T>(endpoint, headers?)
|
|
apiClient.post<T>(endpoint, body?, headers?)
|
|
apiClient.patch<T>(endpoint, body?, headers?)
|
|
apiClient.delete<T>(endpoint, headers?)
|
|
|
|
FEATURES:
|
|
✓ CSRF token handling (from cookies)
|
|
✓ Automatic 401 refresh-and-retry (except auth endpoints)
|
|
✓ Concurrent refresh coalescing
|
|
✓ Type-safe with generics
|
|
✓ Base URL from NEXT_PUBLIC_API_URL env var
|
|
|
|
DOMAIN-SPECIFIC CLIENTS (lib/*-api.ts):
|
|
listings-api.ts | Listing CRUD, search
|
|
analytics-api.ts | Market reports, heatmaps, trends
|
|
neighborhood-api.ts | Neighborhood scoring (if exists)
|
|
valuation-api.ts | AVM estimates
|
|
agents-api.ts | Agent profiles
|
|
khu-cong-nghiep-api | Industrial parks
|
|
du-an-api.ts | Projects
|
|
inquiries-api.ts | Inquiries
|
|
leads-api.ts | Leads
|
|
admin-api.ts | Admin operations
|
|
|
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
7. REACT QUERY HOOKS (lib/hooks/)
|
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
|
|
PATTERN:
|
|
const analyticsKeys = {
|
|
all: ['analytics'] as const,
|
|
marketReport: (city, period) => ['analytics', 'market-report', city, period] as const,
|
|
};
|
|
|
|
export function useMarketReport(city: string, period: string) {
|
|
return useQuery({
|
|
queryKey: analyticsKeys.marketReport(city, period),
|
|
queryFn: () => analyticsApi.getMarketReport(city, period),
|
|
});
|
|
}
|
|
|
|
AVAILABLE HOOKS:
|
|
use-analytics.ts useMarketReport, useHeatmap, usePriceTrend, etc.
|
|
use-listings.ts useListings, useListing
|
|
use-khu-cong-nghiep.ts useIndustrialParks
|
|
use-du-an.ts useProjects
|
|
use-valuation.ts useValuation
|
|
use-inquiries.ts useInquiries
|
|
use-leads.ts useLeads
|
|
use-reports.ts useReports
|
|
use-saved-searches.ts useSavedSearches
|
|
|
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
8. LISTING DETAIL PAGE FLOW
|
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
|
|
ROUTE: /[locale]/(public)/listings/[id]/page.tsx
|
|
|
|
1. SERVER COMPONENT (RSC)
|
|
├─ generateMetadata() ← SEO: OG, canonical, schema
|
|
└─ fetchListingById(id) ← Data fetching
|
|
|
|
2. CLIENT COMPONENT (listing-detail-client.tsx - 39 KB)
|
|
├─ ImageGallery ← Media slideshow
|
|
├─ KPI Strip ← Trader-style metrics
|
|
├─ Core Details ← Address, bedrooms, etc.
|
|
├─ PriceHistoryChart ← Recharts trend
|
|
├─ Neighborhood Section
|
|
│ ├─ NeighborhoodRadarChart ← (dynamic import, SSR: false)
|
|
│ └─ NeighborhoodPOIMap ← (dynamic import, SSR: false)
|
|
├─ AiAdviceCards ← Personas
|
|
├─ SimilarListings ← Recommendations
|
|
├─ AgentCard ← Quality score
|
|
└─ CTA Section ← Inquiry, comparison, estimate
|
|
|
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
9. STATE MANAGEMENT LAYERS
|
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
|
|
LAYER 1: REACT QUERY (Server State)
|
|
Purpose: Fetch & cache API data
|
|
Provider: QueryProvider (components/providers/query-provider.tsx)
|
|
Error Handle: QueryErrorResetBoundary + custom error boundary
|
|
Usage: useQuery, useMutation hooks
|
|
|
|
LAYER 2: ZUSTAND (Client State)
|
|
Purpose: UI state, preferences
|
|
Examples: auth-store, comparison-store, preferences-store
|
|
Persistence: localStorage integration
|
|
Usage: Direct store access or hooks
|
|
|
|
LAYER 3: CONTEXT (Global Features)
|
|
Theme: ThemeProvider (next-themes)
|
|
Notifications: NotificationsProvider
|
|
Auth: AuthProvider
|
|
Query: QueryProvider
|
|
|
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
10. DESIGN SYSTEM COMPONENTS
|
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
|
|
FINANCIAL METRICS:
|
|
✓ kpi-card.tsx Key performance indicator
|
|
✓ stat-card.tsx Statistics display
|
|
✓ ticker-strip.tsx Horizontal ticker/marquee
|
|
✓ price-delta.tsx Price change with signal
|
|
✓ signal.tsx Up/down indicator
|
|
✓ numeric.tsx Formatted number display
|
|
|
|
DATA DISPLAY:
|
|
✓ data-table.tsx @tanstack/react-table wrapper
|
|
✓ empty-state.tsx Empty data fallback
|
|
✓ skeleton.tsx Loading skeleton
|
|
✓ status-chip.tsx Status badge
|
|
|
|
LAYOUT:
|
|
✓ dashboard-layout.tsx Dashboard structure
|
|
✓ density-provider.tsx Compact/dense mode toggle
|
|
✓ divider.tsx Visual separator
|
|
|
|
UI PRIMITIVES (from shadcn/ui via design-system/):
|
|
✓ badge.tsx, button.tsx, card.tsx, dialog.tsx, etc.
|
|
|
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
11. INTERNATIONALIZATION (i18n)
|
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
|
|
SETUP:
|
|
Library: next-intl 4.9.0
|
|
Locales: vi (Vietnamese), en (English)
|
|
Route param: [locale] prefix
|
|
Translations: /messages/ directory
|
|
|
|
USAGE:
|
|
const t = useTranslations();
|
|
<span>{t('key.path')}</span>
|
|
|
|
ROUTES:
|
|
/vi/listings/[id] ← Vietnamese
|
|
/en/listings/[id] ← English
|
|
|
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
12. PERFORMANCE OPTIMIZATIONS
|
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
|
|
CODE SPLITTING:
|
|
dynamic(() => import(...), { ssr: false }) ← Heavy components (maps, charts)
|
|
|
|
LAZY LOADING:
|
|
loading: () => <Fallback /> ← Show UI while loading
|
|
|
|
IMAGE OPTIMIZATION:
|
|
next/image ← Built-in optimization
|
|
|
|
CACHING:
|
|
React Query query keys ← Automatic deduplication & refetch intervals
|
|
|
|
CSS-IN-JS:
|
|
Tailwind + CVA ← Static + composable variants
|
|
|
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
13. KEY FILE PATHS REFERENCE
|
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
|
|
NEIGHBORHOOD:
|
|
lib/components/neighborhood/ Components
|
|
lib/components/neighborhood/types.ts Type definitions
|
|
lib/components/neighborhood/neighborhood-poi-map.tsx Mapbox POI layer
|
|
lib/components/neighborhood/neighborhood-radar-chart.tsx Radar scores
|
|
|
|
MAPPING:
|
|
lib/mapbox-style.ts Theme-aware styles
|
|
lib/components/map/listing-map.tsx Listings on map
|
|
lib/components/map/location-picker.tsx Location selection
|
|
|
|
CHARTS:
|
|
lib/components/charts/ Chart components
|
|
lib/components/charts/price-area-chart.tsx Area trend chart
|
|
|
|
API & STATE:
|
|
lib/api-client.ts Base HTTP client
|
|
lib/listings-api.ts Listings endpoints
|
|
lib/analytics-api.ts Analytics endpoints
|
|
lib/hooks/use-analytics.ts Analytics React Query hooks
|
|
|
|
LISTING DETAIL:
|
|
app/[locale]/(public)/listings/[id]/page.tsx Server component
|
|
lib/components/listings/listing-detail-client.tsx Client component (39 KB)
|
|
|
|
PROVIDERS:
|
|
lib/components/providers/query-provider.tsx React Query setup
|
|
lib/components/providers/theme-provider.tsx Theme management
|
|
|
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
14. CODING PATTERNS
|
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
|
|
COMPONENT FILE HEADER:
|
|
'use client' ← Client component marker
|
|
import ... ← External imports
|
|
import ... ← Internal imports
|
|
|
|
interface ComponentProps { ... } ← Props interface
|
|
|
|
export function Component({ ... }: ComponentProps) {
|
|
// implementation
|
|
}
|
|
|
|
API CLIENT USAGE:
|
|
const data = await apiClient.get<Type>('/endpoint');
|
|
const result = await apiClient.post<Type>('/endpoint', body);
|
|
|
|
REACT QUERY HOOK:
|
|
export function useData(param: string) {
|
|
return useQuery({
|
|
queryKey: ['key', param],
|
|
queryFn: () => api.getData(param),
|
|
enabled: !!param, ← Conditional queries
|
|
});
|
|
}
|
|
|
|
COMPONENT IMPORTS:
|
|
import dynamic from 'next/dynamic';
|
|
const Map = dynamic(() => import('...').then(m => m.Component), {
|
|
ssr: false,
|
|
loading: () => <LoadingState />
|
|
});
|
|
|
|
ZUSTAND STORE:
|
|
import { create } from 'zustand';
|
|
const useStore = create((set) => ({
|
|
value: null,
|
|
setValue: (v) => set({ value: v }),
|
|
}));
|
|
|
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
15. DEBUGGING & DEVELOPMENT TIPS
|
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
|
|
ENV VARS:
|
|
NEXT_PUBLIC_API_URL ← Backend API base URL
|
|
NEXT_PUBLIC_SITE_URL ← Frontend base URL
|
|
MAPBOX_ACCESS_TOKEN ← Mapbox API key
|
|
|
|
SCRIPTS:
|
|
npm run dev ← Start dev server (port 3000)
|
|
npm run build ← Production build
|
|
npm run lint ← ESLint
|
|
npm test ← Vitest
|
|
npm run typecheck ← TypeScript check
|
|
|
|
HOT TIPS:
|
|
• Dynamic imports reduce bundle size for heavy components
|
|
• React Query keys are the foundation of caching strategy
|
|
• Use CSS variables for theming (see design-system/)
|
|
• Barrel exports (index.ts) for cleaner imports
|
|
• Zustand for simple state, React Query for server state
|
|
• Always check enabled condition in useQuery() for dependent queries
|
|
|
|
╔════════════════════════════════════════════════════════════════════════════╗
|
|
║ Report Generated: April 2026 ║
|
|
║ Base Path: /Users/velikho/Desktop/WORKING/goodgo-platform-ai/apps/web ║
|
|
╚════════════════════════════════════════════════════════════════════════════╝
|