# 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 ```http GET /agents/:agentId/profile ``` ### DTO Phản Hồi ```typescript { 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) ```typescript export async function generateMetadata({ params }): Promise { // 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 cho dữ liệu có cấu trúc // 4. Truyền agent sang client component return <> } ``` ### Client Component (agent-detail-client.tsx) ```typescript '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 <> } ``` --- ## 🔗 Các API Call Được Sử Dụng ### Endpoint Công Khai Hiện Có (Không Cần Xác Thực) ```http # 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 ```typescript 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(`/agents/${id}/profile`), }; ``` ### agents-server.ts ```typescript 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 ```typescript 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 ```json { "@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 ```json { "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` |