Files
goodgo-platform/docs/audits/AGENT_PROFILE_QUICK_REFERENCE.md
Ho Ngoc Hai 11f2bf26e6
Some checks failed
CI / Lint → Typecheck → Test → Build (22) (push) Failing after 29s
CI / E2E Tests (push) Has been skipped
CodeQL Analysis / CodeQL (javascript-typescript) (push) Failing after 2m42s
Deploy / Build Web Image (push) Failing after 27s
Deploy / Build AI Services Image (push) Failing after 29s
E2E Tests / Playwright E2E (push) Failing after 43s
Deploy / Build API Image (push) Failing after 1m31s
Security Scanning / Dependency Audit (pnpm) (push) Failing after 6s
Security Scanning / Trivy Scan — API Image (push) Failing after 5m35s
Security Scanning / Trivy Scan — AI Services Image (push) Failing after 3m45s
Deploy / Deploy to Staging (push) Has been skipped
Deploy / Smoke Test Staging (push) Has been skipped
Deploy / Deploy to Production (push) Has been skipped
Deploy / Smoke Test Production (push) Has been skipped
Deploy / Rollback Staging (push) Has been skipped
Deploy / Rollback Production (push) Has been skipped
Security Scanning / Trivy Scan — Web Image (push) Failing after 13m51s
Security Scanning / Trivy Filesystem Scan (push) Failing after 14m46s
Security Scanning / Security Gate (push) Has been cancelled
chore: update project documentation, audit reports, and initialize IDE configuration files
2026-04-19 03:12:54 +07:00

11 KiB

Trang Hồ Sơ Công Khai Môi Giới — Hướng Dẫn Tham Khảo Nhanh

🎯 Tổng Quan Triển Khai

Cấu Trúc URL

/agents/[id]                    # Desktop
/agents/[id]?locale=vi          # Với locale (i18n)
/en/agents/[id]                 # Locale tường minh

Vị Trí Trang

apps/web/app/[locale]/(public)/agents/[id]/page.tsx

📦 Cấu Hình Backend (Cần Thay Đổi API)

Endpoint Công Khai Mới

GET /agents/:agentId/profile

DTO Phản Hồi

{
  id: string;
  fullName: string;
  avatarUrl: string | null;
  licenseNumber: string | null;
  agency: string | null;
  qualityScore: number;
  bio: string | null;
  serviceAreas: string[];
  isVerified: boolean;
  totalListings: number;
  activeListings: number;
  avgReviewRating: number;
  totalReviews: number;
  phone?: string;
  createdAt: string;
  updatedAt: string;
}

Các File Query Handler Cần Tạo

apps/api/src/modules/agents/application/queries/get-agent-profile/
├── get-agent-profile.query.ts
└── get-agent-profile.handler.ts

apps/api/src/modules/agents/presentation/dto/
└── agent-public-profile.dto.ts

🎨 Kiến Trúc Frontend

Cấu Trúc Thư Mục Cần Tạo

apps/web/
├── app/[locale]/(public)/agents/
│   └── [id]/
│       ├── page.tsx              ← Server component (metadata, ISR)
│       └── layout.tsx            ← Layout dùng chung tùy chọn
│
├── components/agents/
│   ├── agent-detail-client.tsx   ← Component tương tác chính
│   ├── agent-header.tsx          ← Phần thông tin hồ sơ
│   ├── agent-listings-section.tsx ← Lưới danh sách bất động sản
│   └── agent-reviews-section.tsx  ← Đánh giá với sao
│
└── lib/
    ├── agents-api.ts            ← API client
    └── agents-server.ts         ← Fetch phía server (ISR)

🔄 Luồng Dữ Liệu

1. Trình duyệt yêu cầu: /agents/[id]
                ↓
2. Next.js tạo metadata (SEO)
   - Title: "Agent Name — Real Estate Agent at GoodGo"
   - Description: Bio + thống kê
   - Image: Avatar
   - Canonical URL
                ↓
3. Fetch dữ liệu hồ sơ môi giới phía server
   - GET /api/v1/agents/:id/profile (ISR: revalidate 3600s)
                ↓
4. Render Server Component với metadata + JSON-LD
                ↓
5. Truyền dữ liệu sang Client Component để tương tác
   - Fetch đánh giá song song
   - Fetch danh sách bất động sản song song
                ↓
6. Client render:
   - Header môi giới (avatar, tên, thống kê, huy hiệu)
   - Phần đánh giá (xếp hạng sao, thẻ bình luận)
   - Phần danh sách bất động sản (tái sử dụng component PropertyCard)

🎭 Kết Hợp Component

Server Component (page.tsx)

export async function generateMetadata({ params }): Promise<Metadata> {
  // 1. Fetch môi giới dùng agents-server.ts
  // 2. Xây dựng metadata SEO
  // 3. Trả về title, description, OG, canonical
}

export default async function AgentProfilePage({ params }) {
  // 1. Fetch hồ sơ môi giới (với ISR)
  // 2. Tạo JSON-LD (schema LocalBusiness)
  // 3. Render <JsonLd> cho dữ liệu có cấu trúc
  // 4. Truyền agent sang client component
  return <>
    <JsonLd data={agentJsonLd} />
    <AgentDetailClient agent={agent} />
  </>
}

Client Component (agent-detail-client.tsx)

'use client';

export function AgentDetailClient({ agent }: Props) {
  const [listings, setListings] = useState([]);
  const [reviews, setReviews] = useState([]);
  const [reviewStats, setReviewStats] = useState(null);

  useEffect(() => {
    // Fetch danh sách bất động sản đang hoạt động của môi giới
    // Fetch đánh giá & thống kê
  }, [agent.id]);

  return <>
    <AgentHeader agent={agent} />
    <AgentReviewsSection reviews={reviews} stats={reviewStats} />
    <AgentListingsSection listings={listings} />
  </>
}

🔗 Các API Call Được Sử Dụng

Endpoint Công Khai Hiện Có (Không Cần Xác Thực)

# Lấy hồ sơ môi giới (MỚI - cần tạo)
GET /api/v1/agents/:agentId/profile

# Lấy danh sách bất động sản của môi giới (ĐÃ CÓ)
GET /api/v1/listings?agentId=:agentId&status=ACTIVE

# Lấy đánh giá môi giới (ĐÃ CÓ)
GET /api/v1/reviews?targetType=AGENT&targetId=:agentId&limit=20

# Lấy thống kê đánh giá môi giới (ĐÃ CÓ)
GET /api/v1/reviews/stats?targetType=AGENT&targetId=:agentId

🎨 Các Phần UI & Component Tái Sử Dụng

1. Phần Header Môi Giới

┌─────────────────────────────────┐
│ [Avatar] Tên                    │
│          Giấy phép: ABC123      │
│          Công ty: XYZ Agency    │
│          ✓ Đã Xác Minh          │
│                                 │
│ ⭐ 4.5 (120 đánh giá)           │
│ 📍 Phục vụ: Quận 1, Quận 7, ...│
│ 🏠 45 tin đăng đang hoạt động  │
└─────────────────────────────────┘

Components: Card, Badge, Image, Text
Styling: Tailwind (p-6, flex, gap-4)

2. Phần Đánh Giá

┌─────────────────────────────────┐
│ Đánh Giá Khách Hàng (120 tổng) │
├─────────────────────────────────┤
│ [Thẻ Đánh Giá]                  │
│ ⭐⭐⭐⭐⭐ John Doe  │ 2 ngày trước│
│ "Môi giới tuyệt vời, rất chuyên nghiệp" │
├─────────────────────────────────┤
│ [Thẻ Đánh Giá]                  │
│ ⭐⭐⭐⭐ Jane Smith │ 1 tuần trước│
│ "Giao tiếp tốt"                  │
└─────────────────────────────────┘

Components: Card, Badge (xếp hạng), Avatar
Tái sử dụng: Kiểu padding/spacing của PropertyCard

3. Phần Danh Sách Bất Động Sản

┌─────────────────────────────────┐
│ Tin Đăng Đang Hoạt Động (45)   │
├─────────────────────────────────┤
│ [PropertyCard]  [PropertyCard]   │
│ [PropertyCard]  [PropertyCard]   │
│ [PropertyCard]  [PropertyCard]   │
└─────────────────────────────────┘

Components: PropertyCard (tái sử dụng từ search/property-card.tsx)
Grid: grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4

🎯 Mẫu Copy-Paste

agents-api.ts

import { apiClient } from './api-client';

export interface AgentPublicProfile {
  id: string;
  fullName: string;
  avatarUrl: string | null;
  licenseNumber: string | null;
  agency: string | null;
  qualityScore: number;
  bio: string | null;
  serviceAreas: string[];
  isVerified: boolean;
  totalListings: number;
  activeListings: number;
  avgReviewRating: number;
  totalReviews: number;
  createdAt: string;
  updatedAt: string;
}

export const agentsApi = {
  getById: (id: string) => 
    apiClient.get<AgentPublicProfile>(`/agents/${id}/profile`),
};

agents-server.ts

const API_BASE_URL = process.env['NEXT_PUBLIC_API_URL'] || 'http://localhost:3001/api/v1';

export async function fetchAgentById(id: string) {
  try {
    const res = await fetch(`${API_BASE_URL}/agents/${id}/profile`, {
      next: { revalidate: 3600 } // ISR: revalidate mỗi 1 giờ
    });
    if (!res.ok) return null;
    return res.json();
  } catch {
    return null;
  }
}

🔍 SEO & Dữ Liệu Có Cấu Trúc

Gợi Ý Metadata

title: `${agent.fullName} — Real Estate Agent at GoodGo`
description: `${agent.bio}. Quality Score: ${agent.qualityScore}. ${agent.activeListings} active listings. ⭐ ${agent.avgReviewRating} (${agent.totalReviews} reviews)`

// OG Image: Dùng avatar hoặc ảnh thay thế

Schema JSON-LD

{
  "@context": "https://schema.org",
  "@type": "LocalBusiness",
  "name": "Agent Full Name",
  "description": "Bio",
  "url": "https://goodgo.vn/en/agents/[id]",
  "image": "avatarUrl",
  "aggregateRating": {
    "@type": "AggregateRating",
    "ratingValue": 4.5,
    "reviewCount": 120
  },
  "areaServed": ["Quan 1", "Quan 7", "Thu Duc"],
  "knowsAbout": "Real Estate"
}

🚀 Các Giai Đoạn Triển Khai

Giai Đoạn 1: Backend (1-2 giờ)

  • Tạo GetAgentProfileQuery
  • Tạo GetAgentProfileHandler
  • Tạo AgentPublicProfileDto
  • Thêm endpoint vào AgentsController
  • Cập nhật interface AgentRepository
  • Triển khai trong PrismaAgentRepository
  • Kiểm tra với Postman/curl

Giai Đoạn 2: Cài Đặt Frontend (1 giờ)

  • Tạo agents-api.ts
  • Tạo agents-server.ts
  • Tạo cấu trúc thư mục agents
  • Tạo [id]/page.tsx (stub)
  • Import kiểu từ agents-api.ts

Giai Đoạn 3: Các Component UI (2-3 giờ)

  • Tạo component AgentHeader
  • Tạo component AgentReviewsSection
  • Tạo component AgentListingsSection
  • Tạo các component RatingStars/ReviewCard
  • Kết nối việc fetch dữ liệu

Giai Đoạn 4: SEO & Hoàn Thiện (1 giờ)

  • Thêm generateMetadata()
  • Tạo schema JSON-LD
  • Kiểm tra xem trước OG
  • Kiểm tra responsive trên di động
  • Kiểm tra chế độ tối

Giai Đoạn 5: Kiểm Thử (1 giờ)

  • Kiểm thử e2e thủ công
  • Kiểm tra xử lý lỗi 404
  • Xác minh việc revalidate ISR
  • Kiểm tra phân trang (danh sách/đánh giá)
  • Kiểm tra SEO (Lighthouse)

📊 Ví Dụ Cấu Trúc Phản Hồi

{
  "id": "clu1x2y3z4a5b6c7d8e9f0",
  "fullName": "Nguyễn Văn A",
  "avatarUrl": "https://cdn.goodgo.vn/avatars/agent-123.jpg",
  "licenseNumber": "DA123456",
  "agency": "GoodGo Agency",
  "qualityScore": 4.8,
  "bio": "Specialized in high-end real estate in District 1 and 7",
  "serviceAreas": ["quan-1", "quan-7", "thu-duc"],
  "isVerified": true,
  "totalListings": 45,
  "activeListings": 32,
  "avgReviewRating": 4.7,
  "totalReviews": 120,
  "createdAt": "2024-01-15T10:30:00Z",
  "updatedAt": "2024-04-10T15:45:00Z"
}

🎯 Tham Khảo Các File Chính

Giai Đoạn File Cần Tạo/Chỉnh Sửa
Backend agents/application/queries/get-agent-profile/*
Backend agents/presentation/controllers/agents.controller.ts
Backend agents/presentation/dto/agent-public-profile.dto.ts
Frontend lib/agents-api.ts
Frontend lib/agents-server.ts
Frontend app/[locale]/(public)/agents/[id]/page.tsx
Frontend components/agents/agent-detail-client.tsx
Frontend components/agents/agent-header.tsx
Frontend components/agents/agent-reviews-section.tsx
Frontend components/agents/agent-listings-section.tsx