Files
goodgo-platform/docs/audits/AGENT_PROFILE_EXPLORATION.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

24 KiB

Trang Hồ Sơ Công Khai Môi Giới GoodGo — Báo Cáo Khám Phá Toàn Diện

Ngày: 11 tháng 4 năm 2026
Phạm vi: Khám phá toàn stack để triển khai trang hồ sơ công khai /agents/[id]
Codebase: GoodGo Platform (Next.js 15 + NestJS 10 + PostgreSQL + Prisma)


1. CẤU TRÚC ỨNG DỤNG WEB & ĐỊNH TUYẾN

Cấu Trúc Tệp

apps/web/
├── app/                          # Next.js 15 App Router (Server Components)
│   ├── [locale]/                 # Quốc tế hóa (i18n) ở cấp gốc
│   │   ├── (admin)/              # Các tuyến admin (được bảo vệ)
│   │   ├── (auth)/               # Các tuyến xác thực (đăng nhập, v.v.)
│   │   ├── (dashboard)/          # Bảng điều khiển người dùng đã xác thực
│   │   └── (public)/             # Các tuyến công khai
│   │       ├── listings/[id]/    # Mẫu trang chi tiết bất động sản hiện có
│   │       ├── search/
│   │       ├── compare/
│   │       ├── pricing/
│   │       └── page.tsx          # Trang đích
│   ├── layout.tsx                # Layout gốc
│   ├── robots.ts                 # SEO: robots.txt
│   └── sitemap.ts                # SEO: sitemap.xml
├── components/
│   ├── ui/                       # Thư viện UI (button, card, badge, input, v.v.)
│   ├── listings/                 # Các component dành riêng cho bất động sản
│   ├── search/                   # Các component tìm kiếm & thẻ bất động sản
│   ├── seo/                      # JSON-LD, dữ liệu có cấu trúc
│   └── providers/                # Các context provider
├── lib/
│   ├── api-client.ts             # Fetch wrapper với bảo vệ CSRF
│   ├── listings-api.ts           # API client cho bất động sản
│   ├── profile-api.ts            # API client hồ sơ xác thực/môi giới
│   ├── listings-server.ts        # Lấy dữ liệu phía server
│   ├── currency.ts               # Tiện ích định dạng tiền tệ
│   └── validations/              # Zod schemas (bất động sản, xác thực, v.v.)
└── public/                       # Tài nguyên tĩnh

Các Mẫu Định Tuyến

Các Tuyến Công Khai (dưới (public)):

  • / → Trang chủ/trang đích
  • /listings/[id] → Chi tiết bất động sản (MẪU HIỆN CÓ)
  • /search → Trang kết quả tìm kiếm
  • /compare → Trang so sánh bất động sản
  • /pricing → Trang bảng giá

Nhận Xét Chính: Nhóm tuyến (public) dành cho người dùng chưa xác thực. Hồ sơ môi giới nên theo cùng mẫu: /agents/[id] trong nhóm (public).


2. MÃ LIÊN QUAN ĐẾN MÔI GIỚI HIỆN CÓ

Kiểu Hồ Sơ Môi Giới (Frontend)

Tệp: apps/web/lib/profile-api.ts

export interface AgentProfile {
  id: string;
  email: string | null;
  phone: string;
  fullName: string;
  avatarUrl: string | null;
  role: string;
  kycStatus: string;
  isActive: boolean;
  createdAt: string;
  licenseNumber: string | null;
  agency: string | null;
  qualityScore: number | null;
  serviceAreas: string[];
  isVerified: boolean;
}

Các Endpoint API Môi Giới Hiện Có (Backend)

Tệp: apps/api/src/modules/agents/presentation/controllers/agents.controller.ts

// Endpoints:
GET    /agents/me/dashboard          # Bảng điều khiển môi giới (đã xác thực)
POST   /agents/:agentId/recalculate-score  # Tính lại điểm chất lượng (admin)

// Trả về:
interface AgentDashboardData {
  agentId: string;
  qualityScore: number;
  totalDeals: number;
  responseTimeAvg: number | null;
  isVerified: boolean;
  totalLeads: number;
  leadsByStatus: Record<string, number>;
  conversionRate: number;
  totalInquiries: number;
  unreadInquiries: number;
  totalListings: number;
  activeListings: number;
  avgReviewRating: number;
  totalReviews: number;
}

Schema Prisma — Model Agent

Tệp: prisma/schema.prisma

model Agent {
  id              String   @id @default(cuid())
  userId          String   @unique
  user            User     @relation(fields: [userId], references: [id])
  licenseNumber   String?
  agency          String?
  qualityScore    Float    @default(0)
  totalDeals      Int      @default(0)
  responseTimeAvg Int?
  bio             String?
  serviceAreas    Json     // JSON array: ["quan-1", "quan-7", "thu-duc"]
  isVerified      Boolean  @default(false)
  createdAt       DateTime @default(now())
  updatedAt       DateTime @updatedAt

  listings Listing[]
  leads    Lead[]

  @@index([qualityScore])
  @@index([isVerified])
}

Các model liên quan:

  • User — Tài khoản người dùng của môi giới (fullName, avatarUrl, phone, email, role, kycStatus)
  • Listing — Các bất động sản môi giới đại diện (có khóa ngoại agentId)
  • Lead — Các đầu mối được môi giới theo dõi

3. CÁC ENDPOINT API MÔI GIỚI CẦN THIẾT CHO HỒ SƠ CÔNG KHAI

Dựa trên kiến trúc hiện có, chúng ta cần tạo một endpoint công khai để lấy dữ liệu hồ sơ môi giới:

Endpoint Đề Xuất

GET /agents/:agentId/profile

Cấu Trúc Phản Hồi (DTO Hồ Sơ Công Khai):

interface AgentPublicProfile {
  id: string;
  fullName: string;
  avatarUrl: string | null;
  
  // Các trường dành riêng cho môi giới
  licenseNumber: string | null;
  agency: string | null;
  qualityScore: number;
  bio: string | null;
  serviceAreas: string[];
  isVerified: boolean;
  
  // Thống kê
  totalListings: number;
  activeListings: number;
  avgReviewRating: number;
  totalReviews: number;
  
  // Liên hệ (tùy chọn, có thể yêu cầu tùy chọn người dùng)
  phone?: string;
  
  // Dấu thời gian
  createdAt: string;
  updatedAt: string;
}

Các Endpoint Liên Quan Cần Thiết:

GET /listings?agentId=:agentId&status=ACTIVE     # Bất động sản đang hoạt động của môi giới
GET /reviews/stats?targetType=AGENT&targetId=:agentId  # Thống kê đánh giá môi giới
GET /reviews?targetType=AGENT&targetId=:agentId&limit=10  # Đánh giá gần đây của môi giới

Các endpoint này đã tồn tại và công khai (không yêu cầu xác thực).


4. CÁC COMPONENT UI DÙNG CHUNG & MẪU THIẾT KẾ

Tailwind/Hệ Thống Thiết Kế

Tệp: apps/web/tailwind.config.ts

// Các biến CSS được sử dụng (hỗ trợ chế độ tối)
colors: {
  primary, primary-foreground
  secondary, secondary-foreground
  destructive, destructive-foreground
  muted, muted-foreground
  accent, accent-foreground
  card, card-foreground
}

// Biến Radius: var(--radius)
borderRadius: {
  lg: 'var(--radius)',
  md: 'calc(var(--radius) - 2px)',
  sm: 'calc(var(--radius) - 4px)',
}

Các Component UI Có Sẵn

Tệp: apps/web/components/ui/

  • button.tsx — Nút có kiểu dáng với các biến thể
  • card.tsx — Thẻ (CardContent, v.v.)
  • badge.tsx — Huy hiệu với các biến thể
  • input.tsx, label.tsx — Các điều khiển biểu mẫu
  • dialog.tsx — Hộp thoại modal
  • tabs.tsx — Điều hướng tab
  • table.tsx — Bảng dữ liệu

Các Mẫu Component Chính

Component Badge

<Badge variant="default">Đã xác minh</Badge>
<Badge variant="secondary">Info badge</Badge>
<Badge variant="outline">Outline badge</Badge>
<Badge variant="destructive">Danger</Badge>

Mẫu Card

<Card>
  <CardContent className="p-4">
    {/* Nội dung */}
  </CardContent>
</Card>

Thẻ Bất Động Sản (Hiển Thị Bất Động Sản) - TÁI SỬ DỤNG CÁI NÀY

Tệp: apps/web/components/search/property-card.tsx

Hiển thị bất động sản với:

  • Thư viện ảnh
  • Giá (đã định dạng)
  • Tiêu đề & địa chỉ
  • Huy hiệu loại, diện tích, số phòng ngủ
  • Huy hiệu loại giao dịch

Có thể tái sử dụng cho: Hiển thị bất động sản của môi giới


5. KIỂU DÁNG & MẪU THIẾT KẾ

CSS Toàn Cục

Tệp: apps/web/app/globals.css

Sử dụng biến CSS để tạo chủ đề:

--primary: hsl(var(--primary-h), var(--primary-s), var(--primary-l))
--background: hsl(...)
--card: hsl(...)
/* Hỗ trợ chế độ tối qua [data-theme="dark"] */

Kiểu Chữ

  • Font: Inter (được cấu hình trong tailwind.config.ts qua biến CSS --font-inter)
  • Các cấp tiêu đề: h1, h2, h3, h4
  • Sử dụng các lớp: text-lg font-bold, text-sm text-muted-foreground

Khoảng Cách

  • Tailwind tiêu chuẩn: p-4, mt-8, gap-3, v.v.
  • Padding card: p-4
  • Padding phần: py-16, py-24 cho các phần hero

Mẫu Layout Ví Dụ

<section className="py-16 md:py-24">
  <div className="mx-auto max-w-7xl px-4">
    {/* Nội dung */}
  </div>
</section>

6. QUẢN LÝ TRẠNG THÁI & LẤY DỮ LIỆU

Mẫu API Client

Tệp: apps/web/lib/api-client.ts

// Cách sử dụng:
const apiClient = {
  get: <T>(endpoint: string, headers?: HeadersInit) => request<T>(endpoint, { method: 'GET', headers }),
  post: <T>(endpoint: string, body?: unknown, headers?: HeadersInit) => request<T>(endpoint, { method: 'POST', body, headers }),
  patch: <T>(endpoint: string, body?: unknown, headers?: HeadersInit) => request<T>(endpoint, { method: 'PATCH', body, headers }),
  delete: <T>(endpoint: string, headers?: HeadersInit) => request<T>(endpoint, { method: 'DELETE', headers }),
};

// Bảo vệ CSRF được bao gồm tự động

Mẫu Lấy Dữ Liệu Phía Server

Tệp: apps/web/lib/listings-server.ts

// Ví dụ: lấy dữ liệu trên server tại thời điểm build hoặc thời điểm request
export async function fetchListingById(id: string) {
  try {
    const res = await fetch(`${API_BASE_URL}/listings/${id}`, {
      next: { revalidate: 3600 } // ISR: xác thực lại mỗi 1 giờ
    });
    if (!res.ok) return null;
    return res.json();
  } catch {
    return null;
  }
}

Mẫu Lấy Dữ Liệu Phía Client

// Trong React component (sử dụng 'use client')
const [data, setData] = useState(null);

React.useEffect(() => {
  apiClient
    .get('/endpoint')
    .then((res) => setData(res))
    .catch(() => setError(true))
    .finally(() => setLoading(false));
}, []);

Không Có Trạng Thái Toàn Cục (Zustand)

  • Hiện tại không có Zustand store cho dữ liệu môi giới trong codebase
  • Mẫu: Lấy dữ liệu trong page component → truyền xuống các component con
  • Dashboard sử dụng useState cục bộ để lấy hồ sơ

7. MẪU SEO & DỮ LIỆU CÓ CẤU TRÚC

Mẫu Tạo Metadata

Tệp: apps/web/app/[locale]/(public)/listings/[id]/page.tsx

export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
  const listing = await fetchListingById(params.id);
  if (!listing) {
    return { title: 'Không tìm thấy' };
  }

  return {
    title: `${property.title} - ${formatPrice(listing.priceVND)} VND`,
    description: '...',
    alternates: {
      canonical: `${siteUrl}/${params.locale}/listings/${params.id}`,
      languages: { vi: '...', en: '...' }
    },
    openGraph: {
      type: 'article',
      locale: params.locale === 'vi' ? 'vi_VN' : 'en_US',
      url: canonicalUrl,
      title,
      images: [{ url: firstImage.url, width: 1200, height: 630 }],
    },
    twitter: {
      card: 'summary_large_image',
      title,
      images: [firstImage.url],
    },
  };
}

Dữ Liệu Có Cấu Trúc JSON-LD

Tệp: apps/web/components/seo/json-ld.tsx

// Ví dụ: Schema RealEstateListing
export function generateListingJsonLd(listing, siteUrl) {
  return {
    '@context': 'https://schema.org',
    '@type': 'RealEstateListing',
    name: property.title,
    url: `${siteUrl}/listings/${listing.id}`,
    offers: { '@type': 'Offer', price: priceNum, priceCurrency: 'VND' },
    // ... thêm thuộc tính
  };
}

// Cách dùng trong trang:
<JsonLd data={listingJsonLd} />
<JsonLd data={breadcrumbJsonLd} />

Cho Hồ Sơ Môi Giới (Schema.org)

Schema phù hợp: LocalBusiness hoặc ProfessionalService

{
  "@context": "https://schema.org",
  "@type": "LocalBusiness",
  "name": "Agent Full Name",
  "description": "Bio",
  "url": "https://goodgo.vn/en/agents/[id]",
  "image": "avatarUrl",
  "address": {
    "@type": "PostalAddress",
    "addressLocality": "Ho Chi Minh"
  },
  "aggregateRating": {
    "@type": "AggregateRating",
    "ratingValue": avgReviewRating,
    "reviewCount": totalReviews
  }
}

8. CÁC COMPONENT THẺ BẤT ĐỘNG SẢN HIỆN CÓ

Component Thẻ Bất Động Sản

Tệp: apps/web/components/search/property-card.tsx

interface PropertyCardProps {
  listing: ListingDetail;
  compact?: boolean;
}

// Hiển thị:
// - Thư viện ảnh (với huy hiệu đếm)
// - Giá (đã định dạng)
// - Tiêu đề & địa chỉ
// - Huy hiệu: Loại giao dịch, loại bất động sản, diện tích, phòng ngủ, phòng tắm, hướng
// - Nút so sánh

Được Sử Dụng Trong:

  • Bất động sản nổi bật trên trang chủ
  • Kết quả tìm kiếm
  • Trang so sánh

Cho Hồ Sơ Môi Giới: Có thể tái sử dụng component này để hiển thị bất động sản của môi giới!


9. CÁC COMPONENT ĐÁNH GIÁ & XẾP HẠNG

Các Endpoint API Đánh Giá

Tệp: apps/api/src/modules/reviews/presentation/controllers/reviews.controller.ts

// Endpoints:
GET    /reviews                     # Danh sách đánh giá theo đối tượng (phân trang)
GET    /reviews/stats               # Lấy thống  xếp hạng tổng hợp
GET    /reviews/me                  # Lấy đánh giá của người dùng đã xác thực
POST   /reviews                     # Tạo đánh giá (đã xác thực)
DELETE /reviews/:id                 # Xóa đánh giá của bản thân

// Tham số truy vấn:
GET /reviews?targetType=AGENT&targetId=:id&page=1&limit=20
GET /reviews/stats?targetType=AGENT&targetId=:id

DTO Đánh Giá

interface ReviewItemData {
  id: string;
  userId: string;
  targetType: string;
  targetId: string;
  rating: number;        // 1-5
  comment: string | null;
  createdAt: string;
  // Thông tin người dùng:
  user: {
    id: string;
    fullName: string;
    avatarUrl: string | null;
  };
}

interface ReviewStatsData {
  targetType: string;
  targetId: string;
  totalReviews: number;
  averageRating: number;
  ratingDistribution: {
    "1": number;
    "2": number;
    "3": number;
    "4": number;
    "5": number;
  };
}

Chưa Có Component Hiển Thị Đánh Giá

  • Hồ sơ bảng điều khiển hiển thị agentProfile.totalReviewsavgReviewRating
  • Cơ hội: Tạo component ReviewCardRatingStars có thể tái sử dụng cho hồ sơ môi giới

10. ĐỊNH NGHĨA KIỂU & GIAO DIỆN

Các Kiểu Cốt Lõi Sử Dụng Ở Frontend

// Từ listings-api.ts
export type TransactionType = 'SALE' | 'RENT';
export type PropertyType = 'APARTMENT' | 'HOUSE' | 'VILLA' | 'LAND' | 'OFFICE' | 'SHOPHOUSE';
export type ListingStatus = 'DRAFT' | 'PENDING_REVIEW' | 'ACTIVE' | 'RESERVED' | 'SOLD' | 'RENTED' | 'EXPIRED' | 'REJECTED';
export type Direction = 'NORTH' | 'SOUTH' | 'EAST' | 'WEST' | 'NORTHEAST' | 'NORTHWEST' | 'SOUTHEAST' | 'SOUTHWEST';

// Từ profile-api.ts
export interface AgentProfile {
  id: string;
  email: string | null;
  phone: string;
  fullName: string;
  avatarUrl: string | null;
  role: string;
  kycStatus: string;
  isActive: boolean;
  createdAt: string;
  licenseNumber: string | null;
  agency: string | null;
  qualityScore: number | null;
  serviceAreas: string[];
  isVerified: boolean;
}

// Từ listings-api.ts
export interface ListingDetail {
  id: string;
  status: ListingStatus;
  transactionType: TransactionType;
  priceVND: string;
  publishedAt: string | null;
  property: {
    id: string;
    propertyType: PropertyType;
    title: string;
    areaM2: number;
    address: string;
    ward: string;
    district: string;
    city: string;
    media: PropertyMedia[];
    // ... hơn 15 thuộc tính khác
  };
}

Các Schema Xác Thực (Zod)

Tệp: apps/web/lib/validations/listings.ts

export const TRANSACTION_TYPES = [
  { value: 'SALE', label: 'Bán' },
  { value: 'RENT', label: 'Cho thuê' },
] as const;

export const PROPERTY_TYPES = [
  { value: 'APARTMENT', label: 'Căn hộ' },
  { value: 'HOUSE', label: 'Nhà riêng' },
  // ... thêm
] as const;

export const LISTING_STATUSES = {
  DRAFT: { label: 'Nháp', variant: 'secondary' },
  ACTIVE: { label: 'Đang bán', variant: 'success' },
  // ...
};

11. DANH SÁCH KIỂM TRA TRIỂN KHAI TRANG HỒ SƠ MÔI GIỚI

Backend (NestJS API)

DTO & Giao Diện Mới:

✓ CreateGetAgentPublicProfileQuery
✓ GetAgentPublicProfileHandler
✓ AgentPublicProfileDto (phản hồi)
✓ Cập nhật agent.repository.ts với phương thức: getPublicProfile(agentId: string)
✓ Cập nhật prisma-agent.repository.ts để lấy dữ liệu qua Prisma

Endpoint Mới:

✓ GET /agents/:agentId/profile (công khai, không xác thực)
✓ Trả về: AgentPublicProfileDto
✓ Xác thực agentId tồn tại
✓ Trả về 404 nếu không tìm thấy

Tận Dụng Hiện Có:

  • Các truy vấn đánh giá: Đã công khai
  • Các truy vấn bất động sản: Đã công khai
  • Hồ sơ người dùng: Liên kết đến agent.userId

Frontend (Next.js)

Các Tệp Mới:

✓ apps/web/app/[locale]/(public)/agents/ (thư mục)
✓ apps/web/app/[locale]/(public)/agents/[id]/ (thư mục)
✓ apps/web/app/[locale]/(public)/agents/[id]/page.tsx (server component)
✓ apps/web/app/[locale]/(public)/agents/[id]/layout.tsx (tùy chọn)
✓ apps/web/lib/agents-api.ts (API client)
✓ apps/web/lib/agents-server.ts (lấy dữ liệu phía server cho ISR)
✓ apps/web/components/agents/ (thư mục mới)
✓ apps/web/components/agents/agent-detail-client.tsx (client component)
✓ apps/web/components/agents/agent-listings-section.tsx
✓ apps/web/components/agents/agent-reviews-section.tsx

Cấu Trúc Trang (theo mẫu bất động sản):

// [id]/page.tsx (Server Component)
export async function generateMetadata({ params }): Promise<Metadata> {
  // Lấy thông tin môi giới
  // Trả về title, description, OG image, canonical URL
}

export default async function AgentProfilePage({ params }) {
  // Lấy hồ sơ môi giới (phía server, ISR)
  // Render JsonLd breadcrumb
  // Render JsonLd LocalBusiness hoặc ProfessionalService
  // Truyền xuống client component
  return <>
    <JsonLd data={agentJsonLd} />
    <AgentDetailClient agent={agent} />
  </>
}

Client Component:

  • Hiển thị thông tin môi giới (tên, avatar, bio, giấy phép, công ty)
  • Hiển thị điểm chất lượng & huy hiệu
  • Render phần đánh giá
  • Render phần bất động sản
  • Nút liên hệ/yêu cầu tư vấn (tùy chọn)

API Client:

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

12. CÁC TỆP CHÍNH CẦN THAM KHẢO/ĐIỀU CHỈNH

Backend

Tệp Mục Đích
apps/api/src/modules/agents/presentation/controllers/agents.controller.ts Thêm endpoint công khai mới
apps/api/src/modules/agents/domain/repositories/agent.repository.ts Thêm giao diện cho phương thức hồ sơ công khai
apps/api/src/modules/agents/infrastructure/repositories/prisma-agent.repository.ts Triển khai lấy hồ sơ công khai
apps/api/src/modules/agents/application/queries/ Tạo query handler mới cho hồ sơ công khai
prisma/schema.prisma Tham chiếu cho model Agent

Frontend — Ví Dụ Tham Khảo

Tệp Mục Đích Mẫu Tái Sử Dụng
apps/web/app/[locale]/(public)/listings/[id]/page.tsx Trang chi tiết bất động sản Dùng làm template cho trang môi giới
apps/web/components/search/property-card.tsx Thẻ bất động sản Tái sử dụng cho bất động sản của môi giới
apps/web/lib/listings-api.ts API client bất động sản Tạo agents-api.ts tương tự
apps/web/lib/listings-server.ts Lấy dữ liệu phía server Tạo agents-server.ts tương tự
apps/web/components/seo/json-ld.tsx Dữ liệu có cấu trúc Điều chỉnh cho schema LocalBusiness
apps/web/lib/currency.ts Định dạng giá Tái sử dụng cho giá bất động sản
apps/web/tailwind.config.ts Hệ thống thiết kế Tham khảo cho kiểu dáng

13. TÓM TẮT CÁC PHÁT HIỆN CHÍNH

Những Gì Đã Có (Có Thể Tái Sử Dụng)

Model Agent trong Prisma với tất cả các trường cần thiết
Các endpoint API cho bất động sản, đánh giá (công khai)
Các component UI: Card, Badge, Button, v.v.
Hệ thống thiết kế Tailwind với chế độ tối
Mẫu SEO với tạo metadata & JSON-LD
Component thư viện ảnh cho bất động sản
Component PropertyCard để hiển thị bất động sản
API client với bảo vệ CSRF
Lấy dữ liệu phía server với mẫu ISR

Những Gì Cần Xây Dựng (Hồ Sơ Môi Giới)

🔨 Trang /agents/[id] (server component với metadata)
🔨 Component AgentDetailClient (render phía client)
🔨 Endpoint công khai: GET /agents/:agentId/profile
🔨 Phần bất động sản của môi giới (tái sử dụng PropertyCard)
🔨 Phần đánh giá của môi giới (lấy & hiển thị đánh giá)
🔨 Component hiển thị sao xếp hạng/tổng hợp
🔨 agents-api.ts (lấy hồ sơ môi giới)
🔨 agents-server.ts (lấy dữ liệu phía server cho ISR)
🔨 Schema JSON-LD LocalBusiness cho môi giới

Các Quyết Định Kiến Trúc

  • Định tuyến: Đặt tại apps/web/app/[locale]/(public)/agents/[id]/page.tsx
  • Mẫu: Theo mẫu trang chi tiết bất động sản chính xác
  • Metadata: Sử dụng hàm server generateMetadata()
  • Component: Chia thành Server Component (trang) + Client Component (tương tác)
  • SEO: Bao gồm breadcrumb + JSON-LD LocalBusiness
  • Kiểu dáng: Sử dụng các token Tailwind hiện có + component
  • Lấy dữ liệu: Lấy dữ liệu phía server với ISR revalidation (3600 giây)

14. CÁC BƯỚC TIẾP THEO

  1. Thiết kế API DTO cho hồ sơ môi giới công khai
  2. Tạo backend query handler để lấy hồ sơ môi giới công khai
  3. Tạo frontend API client (agents-api.ts)
  4. Xây dựng cấu trúc trang theo mẫu trang chi tiết bất động sản
  5. Tạo agent detail client component với các phần
  6. Thêm phần hiển thị đánh giá với xếp hạng sao
  7. Thêm phần hiển thị bất động sản tái sử dụng PropertyCard
  8. Tạo JSON-LD dữ liệu có cấu trúc cho SEO
  9. Kiểm tra ISR & metadata generation
  10. Thêm các tuyến quốc tế (ví dụ: /en/agents/[id] qua locale)