╔══════════════════════════════════════════════════════════════════════════════╗ ║ GOODGO FRONTEND ARCHITECTURE & DATA FLOW DIAGRAM ║ ╚══════════════════════════════════════════════════════════════════════════════╝ ┌─────────────────────────────────────────────────────────────────────────────┐ │ ROUTING & LAYOUT HIERARCHY │ └─────────────────────────────────────────────────────────────────────────────┘ app/ └── layout.tsx (global) └── [locale]/ ├── layout.tsx (i18n wrapper) │ ├── (public)/ ─────────────────── PUBLIC ROUTES │ ├── listings/ │ │ ├── page.tsx ────────────────────────────────┐ │ │ └── [id]/page.tsx (Server RSC) ────────────┐ │ │ │ └── (Client) ─┐ │ │ │ │ ├── ──┐├─┼─┤ Neighborhood │ │ ├── │ │ │ │ │ └── ├─┘ │ │ ├── search/ │ ├── khu-cong-nghiep/ │ └── du-an/ │ ├── (auth)/ ──────────────────── AUTH ROUTES │ ├── login/ │ └── register/ │ ├── (dashboard)/ ──────────────── PROTECTED ROUTES │ ├── dashboard/ │ ├── analytics/ │ ├── valuation/ │ ├── industrial-parks/ │ └── reports/ │ └── (admin)/ ──────────────────── ADMIN ROUTES └── admin/ ├── users/ ├── kyc/ └── moderation/ ┌─────────────────────────────────────────────────────────────────────────────┐ │ LISTING DETAIL PAGE FLOW │ └─────────────────────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────┐ │ /[locale]/(public)/listings/[id]/page.tsx (Server RSC) │ │ ────────────────────────────────────────────────────────── │ │ • generateMetadata() → SEO, OG, schema.json │ │ • await fetchListingById(id) → Backend API │ │ • Returns: │ └────┬───────────────────────────────────────────────────────┬┘ │ SSR data injection (props) │ ▼ ▼ ┌───────────────────────────────────────────────────────────────┐ │ (Client Component, 39 KB) │ │ ───────────────────────────────────────────────────────── │ │ Layout Sections (top to bottom): │ │ │ │ 1. IMAGE GALLERY ─────────── │ │ │ │ 2. KPI STRIP ─────────────── Price | m² | DOM | Views │ │ │ │ 3. CORE DETAILS ──────────── Address, Bedrooms, etc. │ │ │ │ 4. PRICE HISTORY ─────────── │ │ (Recharts AreaChart) │ │ │ │ 5. NEIGHBORHOOD SECTION ──── │ │ ├─ │ │ │ (6 categories, 0-10 scores) │ │ │ Dynamically loaded, SSR: false │ │ │ │ │ └─ │ │ (Mapbox GL with 6 POI types) │ │ Dynamically loaded, SSR: false │ │ • Schools, Hospitals, Transit, Shopping, etc. │ │ • Category filters │ │ • Distance display │ │ │ │ 6. AI ADVICE CARDS ────────── │ │ (Personas) │ │ │ │ 7. SIMILAR LISTINGS ───────── Recommendations │ │ │ │ 8. AGENT CARD ────────────── Agent quality score │ │ │ │ 9. CTA SECTION ────────────── Inquiry | Compare | Estimate │ └─────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────────────────────┐ │ API & STATE MANAGEMENT DATA FLOW │ └─────────────────────────────────────────────────────────────────────────────┘ ┌─────────────────────┐ │ Backend API │ │ (api/v1/...) │ └────────┬────────────┘ │ HTTPS + CSRF Token │ ▼ ┌──────────────────────────────────────────┐ │ lib/api-client.ts │ │ ───────────────────────────────────── │ │ • Base URL: NEXT_PUBLIC_API_URL │ │ • CSRF handling (from cookies) │ │ • 401 auto-refresh + retry │ │ • Type-safe generics: │ └────┬──────────────────────────────────┬──┘ │ │ ├─ apiClient.get() │ HTTP Methods ├─ apiClient.post() │ ├─ apiClient.patch() │ └─ apiClient.delete() │ │ ▼ ┌──────────────────────────────────────────────┐ │ Domain-Specific API Clients │ │ ───────────────────────────────────────── │ │ • listings-api.ts → ListingDetail │ │ • analytics-api.ts → MarketReport │ │ • valuation-api.ts → AVM estimates │ │ • agents-api.ts → AgentQualityScore │ │ • khu-cong-nghiep-api.ts │ │ • du-an-api.ts │ │ • inquiries-api.ts │ └────┬─────────────────────────────────────────┘ │ Returns typed data (interfaces) │ ▼ ┌────────────────────────────────────────────────┐ │ React Query (lib/hooks/) │ │ ───────────────────────────────────────── │ │ useQuery() with query keys: │ │ │ │ • useMarketReport(city, period) │ │ • usePriceTrend(district, city, type) │ │ • useListing(id) │ │ • useIndustrialParks() │ │ • useValuation() │ │ │ │ Features: │ │ ✓ Automatic caching + deduplication │ │ ✓ Background refetch intervals │ │ ✓ Error boundaries │ │ ✓ Loading states │ └────┬──────────────────────────────────────────┬┘ │ Provides data │ │ & loading/error states │ │ │ ├──────────────────┬───────────────────────┤ │ │ │ ▼ ▼ ▼ Components Zustand Stores React Context (render data) (client state) (global features) • Listings • auth-store • QueryProvider • Charts • comparison-store • ThemeProvider • Maps • preferences-store • AuthProvider • Neighborhood • NotificationsProvider ┌─────────────────────────────────────────────────────────────────────────────┐ │ NEIGHBORHOOD COMPONENTS DETAIL │ └─────────────────────────────────────────────────────────────────────────────┘ API Response (from backend) │ ├─ NeighborhoodScoreData │ ├─ overallScore: number (0-100) │ ├─ categories: NeighborhoodCategory[] │ │ └─ [ │ │ { category: "education", label: "Giáo dục", score: 7.5 }, │ │ { category: "healthcare", label: "Y tế", score: 8.2 }, │ │ { category: "transport", label: "Giao thông", score: 6.8 }, │ │ { category: "shopping", label: "Mua sắm", score: 7.9 }, │ │ { category: "dining", label: "Ẩm thực", score: 8.1 }, │ │ { category: "environment", label: "Môi trường", score: 7.3 }, │ │ ] │ ├─ pois: POIItem[] │ │ └─ [ │ │ { id: "1", name: "School X", category: "school", lat: 10.123, lng: 105.456, distance: 500 }, │ │ { id: "2", name: "Hospital Y", category: "hospital", lat: 10.124, lng: 105.457, distance: 800 }, │ │ ... │ │ ] │ └─ center: { lat: 10.123, lng: 105.456 } │ └─ Rendered as: │ ├─────────────────────────────────────────────┐ │ │ │ ───────────────────────────────────────── │ │ Recharts RadarChart Component │ │ │ │ Input: categories (0-10 scores) │ │ Output: │ │ • Radar polygon visualization │ │ • Badge strips below (Tốt/TB/Yếu) │ │ • Dark/light theme CSS variables │ └─────────────────────────────────────────────┘ │ ├─────────────────────────────────────────────┐ │ │ │ ───────────────────────────────────────── │ │ Mapbox GL Component (Client) │ │ │ │ Features: │ │ • Displays 6 POI category types │ │ • SVG icon markers (school, hospital, etc.)│ │ • Category filter toggles │ │ • Distance display on hover │ │ • Responsive map container │ │ • Theme-aware styling │ │ • Click handlers for POI selection │ └─────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────────────────────┐ │ CHART COMPONENTS STACK │ └─────────────────────────────────────────────────────────────────────────────┘ Recharts Library │ ├─ PriceAreaChart (price-area-chart.tsx) │ ├─ Input: PriceAreaChartPoint[] (period, avgPriceM2) │ ├─ Renders: AreaChart with: │ │ • Gradient fill (green/red based on trend) │ │ • Responsive container │ │ • XAxis: period labels │ │ • YAxis: formatted Vietnamese currency (tr/k) │ │ • Tooltip: formatted prices │ └─ Used in: Listing detail page │ ├─ DistrictHeatmap (district-heatmap.tsx - 9 KB) │ ├─ Input: HeatmapDataPoint[] (district, avgPrice, totalListings) │ ├─ Possibly uses Mapbox layers for geo-visualization │ └─ Used in: Analytics page │ ├─ PriceTrendChart (price-trend-chart.tsx) │ ├─ Multiple period comparison │ └─ Used in: Analytics, comparative analysis │ ├─ DistrictBarChart (district-bar-chart.tsx) │ ├─ Bar chart for district-level metrics │ └─ Used in: Market comparisons │ ├─ AgentPerformance (agent-performance.tsx - 6 KB) │ ├─ Agent metrics visualization │ └─ Used in: Agent detail pages │ └─ NeighborhoodRadarChart (neighborhood-radar-chart.tsx) ├─ Radar polygon for 6 categories (0-10 scores) └─ Used in: Listing detail, neighborhood info ┌─────────────────────────────────────────────────────────────────────────────┐ │ COMPONENT IMPORT PATTERNS │ └─────────────────────────────────────────────────────────────────────────────┘ STATIC IMPORT (always loaded): ────────────────────────────── import { Badge } from '@/components/ui/badge'; Used for: Base UI components, utilities, small components Bundle impact: Included in main bundle DYNAMIC IMPORT (lazy-loaded): ────────────────────────────── const NeighborhoodRadarChart = dynamic( () => import('@/components/neighborhood') .then(m => m.NeighborhoodRadarChart), { ssr: false, // Don't server-render (client-only due to Recharts) loading: () => // Loading UI } ); Used for: Heavy components (maps, charts, 3D) Bundle impact: Code-split into separate chunk, loaded on-demand BARREL EXPORT (for organization): ───────────────────────────────── // components/neighborhood/index.ts export { NeighborhoodRadarChart } from './neighborhood-radar-chart'; export { NeighborhoodPOIMap } from './neighborhood-poi-map'; // Usage: import { NeighborhoodRadarChart, NeighborhoodPOIMap } from '@/components/neighborhood'; Benefits: Cleaner imports, single export point ┌─────────────────────────────────────────────────────────────────────────────┐ │ MAPBOX GL INITIALIZATION FLOW │ └─────────────────────────────────────────────────────────────────────────────┘ 'use client' ← Must be client component (Mapbox GL requires DOM) │ ├─ import mapboxgl from 'mapbox-gl' ├─ import 'mapbox-gl/dist/mapbox-gl.css' ← CSS styles │ ├─ const mapStyle = useMapboxStyle() ← Get current theme │ └─ Returns: MAPBOX_STYLE_LIGHT or MAPBOX_STYLE_DARK │ ├─ new mapboxgl.Map({ │ container: mapContainerRef.current, │ style: mapStyle, │ center: [lng, lat], │ zoom: 14 │ }) │ ├─ On theme change: │ └─ map.setStyle(newStyle) ← Update map style reactively │ └─ Cleanup: └─ map?.remove() ← On unmount ┌─────────────────────────────────────────────────────────────────────────────┐ │ INTERNATIONALIZATION (i18n) FLOW │ └─────────────────────────────────────────────────────────────────────────────┘ Request: /vi/listings/123 │ ├─ [locale] route parameter = "vi" ├─ Loaded from: messages/vi.json │ └─ Component: const t = useTranslations(); {t('listings.detail.title')} │ └─ Output: Vietnamese text from translation file Request: /en/listings/123 │ ├─ [locale] route parameter = "en" ├─ Loaded from: messages/en.json │ └─ Output: English text from translation file ┌─────────────────────────────────────────────────────────────────────────────┐ │ SEO & METADATA GENERATION FLOW │ └─────────────────────────────────────────────────────────────────────────────┘ Server Component: generateMetadata({ params }) { │ ├─ await fetchListingById(params.id) ← Get listing data ├─ Construct title, description ├─ Get first image for OG │ └─ Return Metadata object: { title: "Property Title - Price", description: "Type | Area | Bedrooms | Address | Price", openGraph: { url: "https://goodgo.vn/vi/listings/123", type: "article", images: [{ url: "...", width: 1200, height: 630 }], }, alternates: { canonical: "...", languages: { vi: "...", en: "..." } } } Output: tags in ═══════════════════════════════════════════════════════════════════════════════ END OF ARCHITECTURE ═══════════════════════════════════════════════════════════════════════════════