docs: consolidate exploration & audit reports under docs/ (TEC-3094)
- 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>
This commit is contained in:
442
docs/explorations/NEXTJS_VISUAL_FLOWCHART.md
Normal file
442
docs/explorations/NEXTJS_VISUAL_FLOWCHART.md
Normal file
@@ -0,0 +1,442 @@
|
||||
# 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<T>(endpoint, headers) │
|
||||
│ post<T>(endpoint, body, headers) │
|
||||
│ patch<T>(endpoint, body, headers) │
|
||||
│ delete<T>(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
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user