# Next.js Frontend Architecture - Visual Flowchart ## πŸ“Š Data Flow Architecture ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ USER'S BROWSER β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Client Component ('use client') β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ β”‚ β”‚ React Hooks: useState, useEffect, useContext β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ React Query: useQuery, useMutation β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ Local State: filters, viewMode, form data β”‚ β”‚ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ ↓ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ β”‚ β”‚ API Client (apiClient.get/post/patch/delete) β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β€’ Handles CSRF token β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β€’ Auto-refresh on 401 β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β€’ Credentials included β”‚ β”‚ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ ↓↑ β”‚ β”‚ HTTP Requests/Responses β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓↑ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Backend API (Node/Express)β”‚ β”‚ /projects /listings β”‚ β”‚ /projects/[slug] β”‚ β”‚ /projects/[id]/inquiries β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` --- ## πŸ—οΈ Page Rendering Flow ### Pattern 1: Public Browse Page (`/du-an`) ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Server Component (SSR) β”‚ β”‚ βœ“ Layouts applied β”‚ β”‚ βœ“ Metadata generated β”‚ β”‚ βœ“ i18n applied β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Client Component β”‚ β”‚ βœ“ useState: filters, viewMode β”‚ β”‚ βœ“ useProjectsSearch hook β”‚ β”‚ βœ“ Render grid/list/map view β”‚ β”‚ βœ“ Handle filter changes β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ UI Components β”‚ β”‚ β”œβ”€ ProjectFilterBar β”‚ β”‚ β”œβ”€ ProjectCard (grid) β”‚ β”‚ β”œβ”€ ProjectListItem (list) β”‚ β”‚ └─ ProjectMap (Mapbox) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` ### Pattern 2: Detail Page (`/du-an/[slug]`) ``` REQUEST: /du-an/my-project-slug β”‚ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Server Component (generateMetadata) β”‚ β€’ Fetch project by slug (ISR 5min) β”‚ β€’ Generate metadata (title, description, OG) β”‚ β€’ Return to page β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Server Component (Page) β”‚ β”‚ β€’ Fetch project again by slug β”‚ β”‚ β€’ notFound() if not exists β”‚ β”‚ β€’ Pass to client component β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Client Component (DuAnDetailClient) β”‚ β€’ Tabs: amenities, location, price β”‚ β€’ Live data fetch: POIs, scores β”‚ β€’ Contact form handling β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Dynamic Components β”‚ β”‚ β”œβ”€ PriceTrendChart β”‚ β”‚ β”œβ”€ NeighborhoodRadarChart β”‚ β”‚ └─ NeighborhoodPOIMap β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` ### Pattern 3: Admin CRUD Page (`/projects`) ``` REQUEST: /projects (DEVELOPER only) β”‚ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Client Component (full) β”‚ β”‚ β€’ Check auth: useAuthStore β”‚ β”‚ β€’ Define filters state β”‚ β”‚ β€’ Setup React Query β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ useQuery { β”‚ β”‚ queryKey: ['projects', params] β”‚ β”‚ queryFn: duAnApi.searchMine() β”‚ β”‚ } β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Render List + Actions: β”‚ β”‚ β”œβ”€ New project button β”‚ β”‚ β”œβ”€ Filter inputs β”‚ β”‚ β”œβ”€ Project rows with actions β”‚ β”‚ β”œβ”€ Edit link β†’ /projects/[id]/ β”‚ β”‚ └─ Delete β†’ useMutation β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ useMutation { β”‚ β”‚ mutationFn: duAnApi.delete β”‚ β”‚ onSuccess: invalidateQueries β”‚ β”‚ } β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` --- ## πŸ“š Component Hierarchy ``` (Root Layout) β”œβ”€β”€ Locale Selector [locale] β”‚ β”œβ”€β”€ Locale Provider β”‚ β”œβ”€β”€ Auth Provider β”‚ β”œβ”€β”€ Theme Provider β”‚ β”‚ β”‚ β”œβ”€β”€ Route Group: (public) β”‚ β”‚ β”œβ”€β”€ Header/Footer β”‚ β”‚ β”œβ”€β”€ Route: du-an/ β”‚ β”‚ β”‚ β”œβ”€β”€ Server: DuAnPage (Server) β”‚ β”‚ β”‚ β”‚ └── Client: DuAnPage (Client) ← Main browsing β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ ProjectFilterBar β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ ProjectCard[] (grid) β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ ProjectListItem[] (list) β”‚ β”‚ β”‚ β”‚ └── ProjectMap (Mapbox) [dynamic] β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ └── Route: [slug]/ β”‚ β”‚ β”‚ β”œβ”€β”€ Server: generateMetadata() β†’ fetch() β”‚ β”‚ β”‚ β”œβ”€β”€ Server: Page() β†’ fetch() β”‚ β”‚ β”‚ └── Client: DuAnDetailClient β”‚ β”‚ β”‚ β”œβ”€β”€ Tabs β”‚ β”‚ β”‚ β”œβ”€β”€ PriceTrendChart [dynamic] β”‚ β”‚ β”‚ β”œβ”€β”€ NeighborhoodRadarChart [dynamic] β”‚ β”‚ β”‚ └── NeighborhoodPOIMap [dynamic] β”‚ β”‚ β”‚ β”‚ β”‚ └── Other routes: listings/, search/, etc. β”‚ β”‚ β”‚ β”œβ”€β”€ Route Group: (auth) β”‚ β”‚ β”œβ”€β”€ Login β”‚ β”‚ └── Register β”‚ β”‚ β”‚ └── Route Group: (dashboard) β”‚ β”œβ”€β”€ ProtectedLayout (auth check) β”‚ β”œβ”€β”€ Sidebar/Navbar β”‚ β”‚ β”‚ β”œβ”€β”€ Route: dashboard/ (main dashboard) β”‚ β”‚ β”‚ β”œβ”€β”€ Route: projects/ ← Project Management β”‚ β”‚ β”œβ”€β”€ Page (CRUD list) β”‚ β”‚ β”œβ”€β”€ new/Page (create form) β”‚ β”‚ └── [id]/edit/Page (update form) β”‚ β”‚ β”‚ β”œβ”€β”€ Route: listings/ (list management) β”‚ β”œβ”€β”€ Route: leads/ (lead management) β”‚ β”œβ”€β”€ Route: analytics/ (analytics dashboard) β”‚ └── ...other dashboard routes β”‚ └── Route Group: (admin) └── Route: admin/ (admin panel) ``` --- ## πŸ”„ Data Fetching Timeline ``` User navigates to /du-an/my-project-slug β”‚ β”œβ”€ [1] generateMetadata() in Server Component β”‚ └─ fetchProjectBySlug(slug) ← fetch() + normalization β”‚ └─ Returns Metadata { title, description, og } β”‚ β”œβ”€ [2] Page() Server Component renders β”‚ └─ fetchProjectBySlug(slug) ← fetch() again (cached) β”‚ └─ Pass project to Client Component β”‚ β”œβ”€ [3] DuAnDetailClient renders β”‚ β”œβ”€ useEffect: Fetch neighborhood scores β”‚ β”‚ └─ analyticsApi.getNeighborhoodScore() β”‚ β”‚ β”‚ └─ useEffect: Fetch POIs β”‚ └─ analyticsApi.getNearbyPOIs() β”‚ └─ Browser receives complete HTML + JS └─ Hydration β†’ interactive ``` --- ## 🧠 API Layer Organization ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ API Client Layer β”‚ β”‚ β”‚ β”‚ apiClient = { β”‚ β”‚ get(endpoint, headers) β”‚ β”‚ post(endpoint, body, headers) β”‚ β”‚ patch(endpoint, body, headers) β”‚ β”‚ delete(endpoint, headers) β”‚ β”‚ } β”‚ β”‚ β”‚ β”‚ Features: β”‚ β”‚ β€’ CSRF token extraction from cookies β”‚ β”‚ β€’ Auto-refresh on 401 β”‚ β”‚ β€’ Credentials included β”‚ β”‚ β€’ Coalesced refresh (only once) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Domain APIs β”‚ β”‚ β”‚ β”‚ duAnApi = { β”‚ β”‚ search(params) β†’ /projects?... β”‚ β”‚ searchMine(params) β†’ /projects/mine/list β”‚ β”‚ getBySlug(slug) β†’ /projects/[slug] β”‚ β”‚ getStats(id) β†’ /projects/[id]/stats β”‚ β”‚ create(payload) β†’ POST /projects β”‚ β”‚ update(id, payload) β†’ PATCH /projects/[id] β”‚ β”‚ delete(id) β†’ DELETE /projects/[id] β”‚ β”‚ } β”‚ β”‚ β”‚ β”‚ listingsApi = { ... } β”‚ β”‚ khuCongNghiepApi = { ... } β”‚ β”‚ inquiriesApi = { ... } β”‚ β”‚ analyticsApi = { ... } β”‚ β”‚ ... (other domains) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ React Query Hooks β”‚ β”‚ β”‚ β”‚ useProjectsSearch(params) { β”‚ β”‚ return useQuery({ β”‚ β”‚ queryKey: ['projects', 'search', params], β”‚ β”‚ queryFn: () => duAnApi.search(params), β”‚ β”‚ }) β”‚ β”‚ } β”‚ β”‚ β”‚ β”‚ useProjectDetail(slug) { β”‚ β”‚ return useQuery({ β”‚ β”‚ queryKey: ['projects', 'detail', slug], β”‚ β”‚ queryFn: () => duAnApi.getBySlug(slug), β”‚ β”‚ enabled: !!slug, β”‚ β”‚ }) β”‚ β”‚ } β”‚ β”‚ β”‚ β”‚ ... (other hooks for each API) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Client Components β”‚ β”‚ β”‚ β”‚ export function MyComponent() { β”‚ β”‚ const { data, isLoading } = β”‚ β”‚ useProjectsSearch(filters); β”‚ β”‚ // Use data in UI β”‚ β”‚ } β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` --- ## πŸ“ Route Group Strategy ``` App Routes β”‚ β”œβ”€ (public) ← No auth required, public layout β”‚ β”œβ”€ /du-an (browse) β”‚ β”œβ”€ /listings β”‚ β”œβ”€ /khu-cong-nghiep β”‚ β”œβ”€ /search β”‚ β”œβ”€ /agents β”‚ └─ ...other public pages β”‚ β”œβ”€ (auth) ← Auth layout (login/register form) β”‚ β”œβ”€ /login β”‚ └─ /register β”‚ β”œβ”€ (dashboard) ← Protected, dashboard layout β”‚ β”œβ”€ /dashboard (main) β”‚ β”œβ”€ /projects (CRUD) β”‚ β”œβ”€ /listings (CRUD) β”‚ β”œβ”€ /leads β”‚ β”œβ”€ /analytics β”‚ └─ ...other user pages β”‚ β”œβ”€ (admin) ← Admin-only, admin layout β”‚ β”œβ”€ /admin β”‚ β”œβ”€ /admin/users β”‚ β”œβ”€ /admin/kyc β”‚ └─ ...other admin pages β”‚ └─ auth/callback/ ← Unprotected, OAuth callbacks β”œβ”€ /auth/callback/google └─ /auth/callback/zalo ``` --- ## 🎯 Type Flow ``` Backend Response ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Raw JSON β”‚ β”‚ { β”‚ β”‚ "id": "...", β”‚ β”‚ "developer": { β”‚ β”‚ "logo": "url" ← might be missing β”‚ }, β”‚ β”‚ "media": [...], ← might be [] β”‚ "amenities": [...] β”‚ β”‚ } β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ normalizeProjectDetail() β”‚ β”‚ β€’ Fill missing fields β”‚ β”‚ β€’ Rename keys (logoβ†’logoUrl) β”‚ β”‚ β€’ Validate arrays β”‚ β”‚ β€’ Ensure no null/undefined β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ ProjectDetail Interface β”‚ β”‚ { β”‚ β”‚ id: string β”‚ β”‚ developer: { β”‚ β”‚ id, name, logoUrl β”‚ β”‚ } β”‚ β”‚ media: ProjectMedia[] β”‚ β”‚ amenities: ProjectAmenity[]β”‚ β”‚ } β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ React Component β”‚ β”‚ Safe to use all fields β”‚ β”‚ TypeScript checks types β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` --- ## πŸ—ΊοΈ Mapbox Integration Flow ``` ProjectMap Component Mounts β”‚ β”œβ”€ [1] Effect 1: Initialize map β”‚ β”œβ”€ Set accessToken β”‚ β”œβ”€ Create Map instance β”‚ β”œβ”€ Add controls (Navigation, Attribution) β”‚ └─ Store in ref β”‚ β”œβ”€ [2] Effect 2: Change map style β”‚ └─ map.setStyle(newStyle) β”‚ └─ [3] Effect 3: Update markers (when projects change) β”œβ”€ Clear old markers β”œβ”€ For each project with lat/lng: β”‚ β”œβ”€ Create marker element β”‚ β”œβ”€ Create popup HTML β”‚ β”œβ”€ Add to map β”‚ └─ Extend bounds β”œβ”€ Fit bounds to all markers β”‚ └─ map.fitBounds(bounds, padding) └─ Single marker? Fly to it └─ map.flyTo(center, zoom) ``` --- ## 🎨 Styling Cascade ``` Tailwind Config β”œβ”€ Design Tokens (CSS Variables) β”‚ β”œβ”€ --primary: 210 100% 50% β”‚ β”œβ”€ --secondary: 220 13% 91% β”‚ β”œβ”€ --muted: 210 10% 92% β”‚ └─ ... (full palette) β”‚ β”œβ”€ Color System β”‚ β”œβ”€ primary: hsl(var(--primary)) β”‚ β”œβ”€ secondary: hsl(var(--secondary)) β”‚ β”œβ”€ background: { DEFAULT, elevated, surface } β”‚ β”œβ”€ foreground: { DEFAULT, muted, dim } β”‚ └─ ... (semantic colors) β”‚ β”œβ”€ Spacing System β”‚ β”œβ”€ cell: 0.5rem β”‚ β”œβ”€ row: 2.25rem β”‚ β”œβ”€ sidebar: 15rem β”‚ └─ ... (spacing utilities) β”‚ β”œβ”€ Typography β”‚ β”œβ”€ heading-sm: 0.875rem / 1.25rem β”‚ β”œβ”€ heading-md: 1.125rem / 1.5rem β”‚ └─ ... (font sizes) β”‚ └─ Components Use Classes └─ className="text-primary bg-muted px-4 py-2 rounded-lg" └─ Resolved at build time ```