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

743 lines
24 KiB
Markdown

# 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`
```typescript
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`
```typescript
// 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`
```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
```http
GET /agents/:agentId/profile
```
**Cấu Trúc Phản Hồi (DTO Hồ Sơ Công Khai):**
```typescript
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:**
```http
GET /listings?agentId=:agentId&status=ACTIVE # Bt đng sn đang hot đng ca môi gii
GET /reviews/stats?targetType=AGENT&targetId=:agentId # Thng kê đánh giá môi gii
GET /reviews?targetType=AGENT&targetId=:agentId&limit=10 # Đánh giá gn đây ca môi gii
```
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`
```typescript
// 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
```typescript
<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
```typescript
<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ủ đề:
```css
--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ụ
```typescript
<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`
```typescript
// 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`
```typescript
// 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
```typescript
// 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`
```typescript
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`
```typescript
// 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`
```json
{
"@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`
```typescript
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`
```typescript
// 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á
```typescript
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.totalReviews``avgReviewRating`
- **Cơ hội:** Tạo component `ReviewCard``RatingStars` 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
```typescript
// 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`
```typescript
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):**
```typescript
// [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:**
```typescript
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)