chore: update project documentation, audit reports, and initialize IDE configuration files
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
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
This commit is contained in:
@@ -1,63 +1,63 @@
|
||||
# GoodGo Agent Public Profile Page — Comprehensive Exploration Report
|
||||
# Trang Hồ Sơ Công Khai Môi Giới GoodGo — Báo Cáo Khám Phá Toàn Diện
|
||||
|
||||
**Date:** April 11, 2026
|
||||
**Scope:** Full stack exploration for implementing `/agents/[id]` public profile page
|
||||
**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. WEB APP STRUCTURE & ROUTING
|
||||
## 1. CẤU TRÚC ỨNG DỤNG WEB & ĐỊNH TUYẾN
|
||||
|
||||
### File Structure
|
||||
### Cấu Trúc Tệp
|
||||
```
|
||||
apps/web/
|
||||
├── app/ # Next.js 15 App Router (Server Components)
|
||||
│ ├── [locale]/ # Internationalization (i18n) at root level
|
||||
│ │ ├── (admin)/ # Admin routes (protected)
|
||||
│ │ ├── (auth)/ # Auth routes (sign-in, etc.)
|
||||
│ │ ├── (dashboard)/ # Authenticated user dashboard
|
||||
│ │ └── (public)/ # Public-facing routes
|
||||
│ │ ├── listings/[id]/ # Existing listing detail page pattern
|
||||
│ ├── [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 # Landing page
|
||||
│ ├── layout.tsx # Root layout
|
||||
│ │ └── page.tsx # Trang đích
|
||||
│ ├── layout.tsx # Layout gốc
|
||||
│ ├── robots.ts # SEO: robots.txt
|
||||
│ └── sitemap.ts # SEO: sitemap.xml
|
||||
├── components/
|
||||
│ ├── ui/ # UI library (button, card, badge, input, etc.)
|
||||
│ ├── listings/ # Listing-specific components
|
||||
│ ├── search/ # Search & property card components
|
||||
│ ├── seo/ # JSON-LD, structured data
|
||||
│ └── providers/ # Context providers
|
||||
│ ├── 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 with CSRF protection
|
||||
│ ├── listings-api.ts # API client for listings
|
||||
│ ├── profile-api.ts # Auth/agent profile API client
|
||||
│ ├── listings-server.ts # Server-side data fetching
|
||||
│ ├── currency.ts # Currency formatting utilities
|
||||
│ └── validations/ # Zod schemas (listings, auth, etc.)
|
||||
└── public/ # Static assets
|
||||
│ ├── 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
|
||||
```
|
||||
|
||||
### Routing Patterns
|
||||
### Các Mẫu Định Tuyến
|
||||
|
||||
**Public Routes (under `(public)`):**
|
||||
- `/` → Home/landing page
|
||||
- `/listings/[id]` → Listing detail (EXISTING PATTERN)
|
||||
- `/search` → Search results page
|
||||
- `/compare` → Property comparison page
|
||||
- `/pricing` → Pricing page
|
||||
**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á
|
||||
|
||||
**Key Insight:** The `(public)` route group is for unauthenticated users. **Agent profiles should follow the same pattern: `/agents/[id]`** under the `(public)` group.
|
||||
**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. EXISTING AGENT-RELATED CODE
|
||||
## 2. MÃ LIÊN QUAN ĐẾN MÔI GIỚI HIỆN CÓ
|
||||
|
||||
### Agent Profile Type (Frontend)
|
||||
**File:** `apps/web/lib/profile-api.ts`
|
||||
### Kiểu Hồ Sơ Môi Giới (Frontend)
|
||||
**Tệp:** `apps/web/lib/profile-api.ts`
|
||||
|
||||
```typescript
|
||||
export interface AgentProfile {
|
||||
@@ -78,15 +78,15 @@ export interface AgentProfile {
|
||||
}
|
||||
```
|
||||
|
||||
### Existing Agent API Endpoints (Backend)
|
||||
**File:** `apps/api/src/modules/agents/presentation/controllers/agents.controller.ts`
|
||||
### 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 # Agent dashboard (authenticated)
|
||||
POST /agents/:agentId/recalculate-score # Recalculate quality score (admin)
|
||||
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)
|
||||
|
||||
// Returns:
|
||||
// Trả về:
|
||||
interface AgentDashboardData {
|
||||
agentId: string;
|
||||
qualityScore: number;
|
||||
@@ -105,8 +105,8 @@ interface AgentDashboardData {
|
||||
}
|
||||
```
|
||||
|
||||
### Prisma Schema — Agent Model
|
||||
**File:** `prisma/schema.prisma`
|
||||
### Schema Prisma — Model Agent
|
||||
**Tệp:** `prisma/schema.prisma`
|
||||
|
||||
```prisma
|
||||
model Agent {
|
||||
@@ -132,31 +132,31 @@ model Agent {
|
||||
}
|
||||
```
|
||||
|
||||
**Related models:**
|
||||
- `User` — Agent's user account (fullName, avatarUrl, phone, email, role, kycStatus)
|
||||
- `Listing` — Properties agent represents (has `agentId` foreign key)
|
||||
- `Lead` — Leads tracked by agent
|
||||
**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. AGENT API ENDPOINTS NEEDED FOR PUBLIC PROFILE
|
||||
## 3. CÁC ENDPOINT API MÔI GIỚI CẦN THIẾT CHO HỒ SƠ CÔNG KHAI
|
||||
|
||||
Based on the existing architecture, we need to create a **public endpoint** to fetch agent profile data:
|
||||
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:
|
||||
|
||||
### Proposed Endpoint
|
||||
### Endpoint Đề Xuất
|
||||
|
||||
```http
|
||||
GET /agents/:agentId/profile
|
||||
```
|
||||
|
||||
**Response Structure (Public Profile DTO):**
|
||||
**Cấu Trúc Phản Hồi (DTO Hồ Sơ Công Khai):**
|
||||
```typescript
|
||||
interface AgentPublicProfile {
|
||||
id: string;
|
||||
fullName: string;
|
||||
avatarUrl: string | null;
|
||||
|
||||
// Agent-specific fields
|
||||
// Các trường dành riêng cho môi giới
|
||||
licenseNumber: string | null;
|
||||
agency: string | null;
|
||||
qualityScore: number;
|
||||
@@ -164,39 +164,39 @@ interface AgentPublicProfile {
|
||||
serviceAreas: string[];
|
||||
isVerified: boolean;
|
||||
|
||||
// Stats
|
||||
// Thống kê
|
||||
totalListings: number;
|
||||
activeListings: number;
|
||||
avgReviewRating: number;
|
||||
totalReviews: number;
|
||||
|
||||
// Contact (optional, may require user preferences)
|
||||
// Liên hệ (tùy chọn, có thể yêu cầu tùy chọn người dùng)
|
||||
phone?: string;
|
||||
|
||||
// Timestamps
|
||||
// Dấu thời gian
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
```
|
||||
|
||||
**Related Endpoints Needed:**
|
||||
**Các Endpoint Liên Quan Cần Thiết:**
|
||||
```http
|
||||
GET /listings?agentId=:agentId&status=ACTIVE # Agent's active listings
|
||||
GET /reviews/stats?targetType=AGENT&targetId=:agentId # Agent reviews stats
|
||||
GET /reviews?targetType=AGENT&targetId=:agentId&limit=10 # Recent agent reviews
|
||||
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
|
||||
```
|
||||
|
||||
These endpoints already exist and are public (no authentication required).
|
||||
Các endpoint này đã tồn tại và công khai (không yêu cầu xác thực).
|
||||
|
||||
---
|
||||
|
||||
## 4. SHARED UI COMPONENTS & DESIGN PATTERNS
|
||||
## 4. CÁC COMPONENT UI DÙNG CHUNG & MẪU THIẾT KẾ
|
||||
|
||||
### Tailwind/Design System
|
||||
**File:** `apps/web/tailwind.config.ts`
|
||||
### Tailwind/Hệ Thống Thiết Kế
|
||||
**Tệp:** `apps/web/tailwind.config.ts`
|
||||
|
||||
```typescript
|
||||
// CSS Variables used (dark mode support)
|
||||
// Các biến CSS được sử dụng (hỗ trợ chế độ tối)
|
||||
colors: {
|
||||
primary, primary-foreground
|
||||
secondary, secondary-foreground
|
||||
@@ -206,7 +206,7 @@ colors: {
|
||||
card, card-foreground
|
||||
}
|
||||
|
||||
// Radius variable: var(--radius)
|
||||
// Biến Radius: var(--radius)
|
||||
borderRadius: {
|
||||
lg: 'var(--radius)',
|
||||
md: 'calc(var(--radius) - 2px)',
|
||||
@@ -214,20 +214,20 @@ borderRadius: {
|
||||
}
|
||||
```
|
||||
|
||||
### Available UI Components
|
||||
**File:** `apps/web/components/ui/`
|
||||
### Các Component UI Có Sẵn
|
||||
**Tệp:** `apps/web/components/ui/`
|
||||
|
||||
- `button.tsx` — Styled button with variants
|
||||
- `card.tsx` — Card (CardContent, etc.)
|
||||
- `badge.tsx` — Badge with variants
|
||||
- `input.tsx`, `label.tsx` — Form controls
|
||||
- `dialog.tsx` — Modal dialog
|
||||
- `tabs.tsx` — Tab navigation
|
||||
- `table.tsx` — Data table
|
||||
- `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
|
||||
|
||||
### Key Component Patterns
|
||||
### Các Mẫu Component Chính
|
||||
|
||||
#### Badge Component
|
||||
#### Component Badge
|
||||
```typescript
|
||||
<Badge variant="default">Đã xác minh</Badge>
|
||||
<Badge variant="secondary">Info badge</Badge>
|
||||
@@ -235,70 +235,70 @@ borderRadius: {
|
||||
<Badge variant="destructive">Danger</Badge>
|
||||
```
|
||||
|
||||
#### Card Pattern
|
||||
#### Mẫu Card
|
||||
```typescript
|
||||
<Card>
|
||||
<CardContent className="p-4">
|
||||
{/* Content */}
|
||||
{/* Nội dung */}
|
||||
</CardContent>
|
||||
</Card>
|
||||
```
|
||||
|
||||
#### Property Card (Listing Display) - REUSE THIS
|
||||
**File:** `apps/web/components/search/property-card.tsx`
|
||||
#### 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`
|
||||
|
||||
Shows a property with:
|
||||
- Image gallery
|
||||
- Price (formatted)
|
||||
- Title & address
|
||||
- Type, area, bedrooms badges
|
||||
- Transaction type badge
|
||||
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
|
||||
|
||||
**Reusable for:** Agent's listings display
|
||||
**Có thể tái sử dụng cho:** Hiển thị bất động sản của môi giới
|
||||
|
||||
---
|
||||
|
||||
## 5. STYLING & DESIGN PATTERNS
|
||||
## 5. KIỂU DÁNG & MẪU THIẾT KẾ
|
||||
|
||||
### Global CSS
|
||||
**File:** `apps/web/app/globals.css`
|
||||
### CSS Toàn Cục
|
||||
**Tệp:** `apps/web/app/globals.css`
|
||||
|
||||
Uses CSS variables for theming:
|
||||
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(...)
|
||||
/* Dark mode support via [data-theme="dark"] */
|
||||
/* Hỗ trợ chế độ tối qua [data-theme="dark"] */
|
||||
```
|
||||
|
||||
### Typography
|
||||
- Font: Inter (configured in tailwind.config.ts via CSS variable `--font-inter`)
|
||||
- Heading levels: h1, h2, h3, h4
|
||||
- Use classes: `text-lg font-bold`, `text-sm text-muted-foreground`
|
||||
### 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`
|
||||
|
||||
### Spacing
|
||||
- Tailwind standard: `p-4`, `mt-8`, `gap-3`, etc.
|
||||
- Card padding: `p-4`
|
||||
- Section padding: `py-16`, `py-24` for hero sections
|
||||
### 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
|
||||
|
||||
### Example Layout Pattern
|
||||
### Mẫu Layout Ví Dụ
|
||||
```typescript
|
||||
<section className="py-16 md:py-24">
|
||||
<div className="mx-auto max-w-7xl px-4">
|
||||
{/* Content */}
|
||||
{/* Nội dung */}
|
||||
</div>
|
||||
</section>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. STATE MANAGEMENT & DATA FETCHING
|
||||
## 6. QUẢN LÝ TRẠNG THÁI & LẤY DỮ LIỆU
|
||||
|
||||
### API Client Pattern
|
||||
**File:** `apps/web/lib/api-client.ts`
|
||||
### Mẫu API Client
|
||||
**Tệp:** `apps/web/lib/api-client.ts`
|
||||
|
||||
```typescript
|
||||
// Usage:
|
||||
// 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 }),
|
||||
@@ -306,18 +306,18 @@ const apiClient = {
|
||||
delete: <T>(endpoint: string, headers?: HeadersInit) => request<T>(endpoint, { method: 'DELETE', headers }),
|
||||
};
|
||||
|
||||
// CSRF protection included automatically
|
||||
// Bảo vệ CSRF được bao gồm tự động
|
||||
```
|
||||
|
||||
### Server-Side Data Fetching Pattern
|
||||
**File:** `apps/web/lib/listings-server.ts`
|
||||
### Mẫu Lấy Dữ Liệu Phía Server
|
||||
**Tệp:** `apps/web/lib/listings-server.ts`
|
||||
|
||||
```typescript
|
||||
// Example: fetch on server at build time or request time
|
||||
// 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: revalidate every 1 hour
|
||||
next: { revalidate: 3600 } // ISR: xác thực lại mỗi 1 giờ
|
||||
});
|
||||
if (!res.ok) return null;
|
||||
return res.json();
|
||||
@@ -327,9 +327,9 @@ export async function fetchListingById(id: string) {
|
||||
}
|
||||
```
|
||||
|
||||
### Client-Side Data Fetching Pattern
|
||||
### Mẫu Lấy Dữ Liệu Phía Client
|
||||
```typescript
|
||||
// In React component (using 'use client')
|
||||
// Trong React component (sử dụng 'use client')
|
||||
const [data, setData] = useState(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
@@ -341,17 +341,17 @@ React.useEffect(() => {
|
||||
}, []);
|
||||
```
|
||||
|
||||
### No Global State (Zustand)
|
||||
- **Currently no Zustand stores** for agent data in codebase
|
||||
- **Pattern:** Fetch data in page component → pass to child components
|
||||
- Dashboard uses local useState for profile fetch
|
||||
### 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. SEO PATTERNS & STRUCTURED DATA
|
||||
## 7. MẪU SEO & DỮ LIỆU CÓ CẤU TRÚC
|
||||
|
||||
### Metadata Generation Pattern
|
||||
**File:** `apps/web/app/[locale]/(public)/listings/[id]/page.tsx`
|
||||
### 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> {
|
||||
@@ -383,11 +383,11 @@ export async function generateMetadata({ params }: PageProps): Promise<Metadata>
|
||||
}
|
||||
```
|
||||
|
||||
### JSON-LD Structured Data
|
||||
**File:** `apps/web/components/seo/json-ld.tsx`
|
||||
### Dữ Liệu Có Cấu Trúc JSON-LD
|
||||
**Tệp:** `apps/web/components/seo/json-ld.tsx`
|
||||
|
||||
```typescript
|
||||
// Example: RealEstateListing schema
|
||||
// Ví dụ: Schema RealEstateListing
|
||||
export function generateListingJsonLd(listing, siteUrl) {
|
||||
return {
|
||||
'@context': 'https://schema.org',
|
||||
@@ -395,17 +395,17 @@ export function generateListingJsonLd(listing, siteUrl) {
|
||||
name: property.title,
|
||||
url: `${siteUrl}/listings/${listing.id}`,
|
||||
offers: { '@type': 'Offer', price: priceNum, priceCurrency: 'VND' },
|
||||
// ... more properties
|
||||
// ... thêm thuộc tính
|
||||
};
|
||||
}
|
||||
|
||||
// Usage in page:
|
||||
// Cách dùng trong trang:
|
||||
<JsonLd data={listingJsonLd} />
|
||||
<JsonLd data={breadcrumbJsonLd} />
|
||||
```
|
||||
|
||||
### For Agent Profile (Schema.org)
|
||||
**Appropriate schema:** `LocalBusiness` or `ProfessionalService`
|
||||
### Cho Hồ Sơ Môi Giới (Schema.org)
|
||||
**Schema phù hợp:** `LocalBusiness` hoặc `ProfessionalService`
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -429,10 +429,10 @@ export function generateListingJsonLd(listing, siteUrl) {
|
||||
|
||||
---
|
||||
|
||||
## 8. EXISTING LISTING CARD COMPONENTS
|
||||
## 8. CÁC COMPONENT THẺ BẤT ĐỘNG SẢN HIỆN CÓ
|
||||
|
||||
### Property Card Component
|
||||
**File:** `apps/web/components/search/property-card.tsx`
|
||||
### Component Thẻ Bất Động Sản
|
||||
**Tệp:** `apps/web/components/search/property-card.tsx`
|
||||
|
||||
```typescript
|
||||
interface PropertyCardProps {
|
||||
@@ -440,42 +440,42 @@ interface PropertyCardProps {
|
||||
compact?: boolean;
|
||||
}
|
||||
|
||||
// Displays:
|
||||
// - Image gallery (with count badge)
|
||||
// - Price (formatted)
|
||||
// - Title & address
|
||||
// - Badges: Transaction type, property type, area, bedrooms, bathrooms, direction
|
||||
// - Compare button
|
||||
// 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
|
||||
```
|
||||
|
||||
### Used in:
|
||||
- Home page featured listings
|
||||
- Search results
|
||||
- Comparison page
|
||||
### Đượ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
|
||||
|
||||
**For Agent Profile:** Can reuse this component to display agent's listings!
|
||||
**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. REVIEW & RATING COMPONENTS
|
||||
## 9. CÁC COMPONENT ĐÁNH GIÁ & XẾP HẠNG
|
||||
|
||||
### Review API Endpoints
|
||||
**File:** `apps/api/src/modules/reviews/presentation/controllers/reviews.controller.ts`
|
||||
### Các Endpoint API Đánh Giá
|
||||
**Tệp:** `apps/api/src/modules/reviews/presentation/controllers/reviews.controller.ts`
|
||||
|
||||
```typescript
|
||||
// Endpoints:
|
||||
GET /reviews # List reviews by target (pagination)
|
||||
GET /reviews/stats # Get aggregate rating stats
|
||||
GET /reviews/me # Get authenticated user's reviews
|
||||
POST /reviews # Create review (authenticated)
|
||||
DELETE /reviews/:id # Delete own review
|
||||
GET /reviews # Danh sách đánh giá theo đối tượng (phân trang)
|
||||
GET /reviews/stats # Lấy thống kê 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
|
||||
|
||||
// Query params:
|
||||
// Tham số truy vấn:
|
||||
GET /reviews?targetType=AGENT&targetId=:id&page=1&limit=20
|
||||
GET /reviews/stats?targetType=AGENT&targetId=:id
|
||||
```
|
||||
|
||||
### Review DTO
|
||||
### DTO Đánh Giá
|
||||
```typescript
|
||||
interface ReviewItemData {
|
||||
id: string;
|
||||
@@ -485,7 +485,7 @@ interface ReviewItemData {
|
||||
rating: number; // 1-5
|
||||
comment: string | null;
|
||||
createdAt: string;
|
||||
// User info:
|
||||
// Thông tin người dùng:
|
||||
user: {
|
||||
id: string;
|
||||
fullName: string;
|
||||
@@ -508,24 +508,24 @@ interface ReviewStatsData {
|
||||
}
|
||||
```
|
||||
|
||||
### No Review Display Component Yet
|
||||
- **Dashboard profile** shows `agentProfile.totalReviews` and `avgReviewRating`
|
||||
- **Opportunity:** Create a reusable `ReviewCard` and `RatingStars` component for agent profile
|
||||
### Chưa Có Component Hiển Thị Đánh Giá
|
||||
- **Hồ sơ bảng điều khiển** hiển thị `agentProfile.totalReviews` và `avgReviewRating`
|
||||
- **Cơ hội:** Tạo component `ReviewCard` và `RatingStars` có thể tái sử dụng cho hồ sơ môi giới
|
||||
|
||||
---
|
||||
|
||||
## 10. TYPE DEFINITIONS & INTERFACES
|
||||
## 10. ĐỊNH NGHĨA KIỂU & GIAO DIỆN
|
||||
|
||||
### Core Types Used Frontend
|
||||
### Các Kiểu Cốt Lõi Sử Dụng Ở Frontend
|
||||
|
||||
```typescript
|
||||
// From listings-api.ts
|
||||
// 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';
|
||||
|
||||
// From profile-api.ts
|
||||
// Từ profile-api.ts
|
||||
export interface AgentProfile {
|
||||
id: string;
|
||||
email: string | null;
|
||||
@@ -543,7 +543,7 @@ export interface AgentProfile {
|
||||
isVerified: boolean;
|
||||
}
|
||||
|
||||
// From listings-api.ts
|
||||
// Từ listings-api.ts
|
||||
export interface ListingDetail {
|
||||
id: string;
|
||||
status: ListingStatus;
|
||||
@@ -560,13 +560,13 @@ export interface ListingDetail {
|
||||
district: string;
|
||||
city: string;
|
||||
media: PropertyMedia[];
|
||||
// ... 15+ other properties
|
||||
// ... hơn 15 thuộc tính khác
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Validation Schemas (Zod)
|
||||
**File:** `apps/web/lib/validations/listings.ts`
|
||||
### Các Schema Xác Thực (Zod)
|
||||
**Tệp:** `apps/web/lib/validations/listings.ts`
|
||||
|
||||
```typescript
|
||||
export const TRANSACTION_TYPES = [
|
||||
@@ -577,7 +577,7 @@ export const TRANSACTION_TYPES = [
|
||||
export const PROPERTY_TYPES = [
|
||||
{ value: 'APARTMENT', label: 'Căn hộ' },
|
||||
{ value: 'HOUSE', label: 'Nhà riêng' },
|
||||
// ... more
|
||||
// ... thêm
|
||||
] as const;
|
||||
|
||||
export const LISTING_STATUSES = {
|
||||
@@ -589,61 +589,61 @@ export const LISTING_STATUSES = {
|
||||
|
||||
---
|
||||
|
||||
## 11. IMPLEMENTATION CHECKLIST FOR AGENT PROFILE PAGE
|
||||
## 11. DANH SÁCH KIỂM TRA TRIỂN KHAI TRANG HỒ SƠ MÔI GIỚI
|
||||
|
||||
### Backend (NestJS API)
|
||||
|
||||
**New DTO & Interfaces:**
|
||||
**DTO & Giao Diện Mới:**
|
||||
```
|
||||
✓ CreateGetAgentPublicProfileQuery
|
||||
✓ GetAgentPublicProfileHandler
|
||||
✓ AgentPublicProfileDto (response)
|
||||
✓ Update agent.repository.ts with method: getPublicProfile(agentId: string)
|
||||
✓ Update prisma-agent.repository.ts to fetch via Prisma
|
||||
✓ 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
|
||||
```
|
||||
|
||||
**New Endpoint:**
|
||||
**Endpoint Mới:**
|
||||
```
|
||||
✓ GET /agents/:agentId/profile (public, no auth)
|
||||
✓ Returns: AgentPublicProfileDto
|
||||
✓ Validates agentId exists
|
||||
✓ Returns 404 if not found
|
||||
✓ 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
|
||||
```
|
||||
|
||||
**Leverage Existing:**
|
||||
- Review queries: Already public
|
||||
- Listings queries: Already public
|
||||
- User profile: Linking to agent.userId
|
||||
**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)
|
||||
|
||||
**New Files:**
|
||||
**Các Tệp Mới:**
|
||||
```
|
||||
✓ apps/web/app/[locale]/(public)/agents/ (directory)
|
||||
✓ apps/web/app/[locale]/(public)/agents/[id]/ (directory)
|
||||
✓ 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 (optional)
|
||||
✓ 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 (server-side fetch for ISR)
|
||||
✓ apps/web/components/agents/ (new directory)
|
||||
✓ 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
|
||||
```
|
||||
|
||||
**Page Structure (follows listing pattern):**
|
||||
**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> {
|
||||
// Fetch agent
|
||||
// Return title, description, OG image, canonical URL
|
||||
// Lấy thông tin môi giới
|
||||
// Trả về title, description, OG image, canonical URL
|
||||
}
|
||||
|
||||
export default async function AgentProfilePage({ params }) {
|
||||
// Fetch agent profile (server-side, ISR)
|
||||
// Lấy hồ sơ môi giới (phía server, ISR)
|
||||
// Render JsonLd breadcrumb
|
||||
// Render JsonLd LocalBusiness or ProfessionalService
|
||||
// Pass to client component
|
||||
// Render JsonLd LocalBusiness hoặc ProfessionalService
|
||||
// Truyền xuống client component
|
||||
return <>
|
||||
<JsonLd data={agentJsonLd} />
|
||||
<AgentDetailClient agent={agent} />
|
||||
@@ -652,11 +652,11 @@ export default async function AgentProfilePage({ params }) {
|
||||
```
|
||||
|
||||
**Client Component:**
|
||||
- Display agent info (name, avatar, bio, license, agency)
|
||||
- Show quality score & badges
|
||||
- Render reviews section
|
||||
- Render listings section
|
||||
- Contact/inquiry button (optional)
|
||||
- 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
|
||||
@@ -667,77 +667,76 @@ export const agentsApi = {
|
||||
|
||||
---
|
||||
|
||||
## 12. KEY FILES TO REFERENCE/ADAPT
|
||||
## 12. CÁC TỆP CHÍNH CẦN THAM KHẢO/ĐIỀU CHỈNH
|
||||
|
||||
### Backend
|
||||
|
||||
| File | Purpose |
|
||||
| Tệp | Mục Đích |
|
||||
|------|---------|
|
||||
| `apps/api/src/modules/agents/presentation/controllers/agents.controller.ts` | Add new public endpoint |
|
||||
| `apps/api/src/modules/agents/domain/repositories/agent.repository.ts` | Add interface for public profile method |
|
||||
| `apps/api/src/modules/agents/infrastructure/repositories/prisma-agent.repository.ts` | Implement public profile fetch |
|
||||
| `apps/api/src/modules/agents/application/queries/` | Create new query handler for public profile |
|
||||
| `prisma/schema.prisma` | Reference for Agent model |
|
||||
| `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 — Reference Examples
|
||||
### Frontend — Ví Dụ Tham Khảo
|
||||
|
||||
| File | Purpose | Reuse Pattern |
|
||||
| Tệp | Mục Đích | Mẫu Tái Sử Dụng |
|
||||
|------|---------|---|
|
||||
| `apps/web/app/[locale]/(public)/listings/[id]/page.tsx` | Listing detail page | Use as template for agent page |
|
||||
| `apps/web/components/search/property-card.tsx` | Property card | Reuse for agent's listings |
|
||||
| `apps/web/lib/listings-api.ts` | Listings API client | Create similar agents-api.ts |
|
||||
| `apps/web/lib/listings-server.ts` | Server-side fetch | Create similar agents-server.ts |
|
||||
| `apps/web/components/seo/json-ld.tsx` | Structured data | Adapt for LocalBusiness schema |
|
||||
| `apps/web/lib/currency.ts` | Price formatting | Reuse for listing prices |
|
||||
| `apps/web/tailwind.config.ts` | Design system | Reference for styling |
|
||||
| `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. SUMMARY OF KEY FINDINGS
|
||||
## 13. TÓM TẮT CÁC PHÁT HIỆN CHÍNH
|
||||
|
||||
### What Exists (Reusable)
|
||||
✅ Agent model in Prisma with all needed fields
|
||||
✅ API endpoints for listings, reviews (public)
|
||||
✅ UI components: Card, Badge, Button, etc.
|
||||
✅ Tailwind design system with dark mode
|
||||
✅ SEO pattern with metadata generation & JSON-LD
|
||||
✅ Image gallery component for listings
|
||||
✅ PropertyCard component for listings display
|
||||
✅ API client with CSRF protection
|
||||
✅ Server-side data fetching with ISR pattern
|
||||
### 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
|
||||
|
||||
### What Needs Building (Agent Profile)
|
||||
🔨 `/agents/[id]` page (server component with metadata)
|
||||
🔨 `AgentDetailClient` component (client-side rendering)
|
||||
🔨 Public endpoint: `GET /agents/:agentId/profile`
|
||||
🔨 Agent listings section (reuse PropertyCard)
|
||||
🔨 Agent reviews section (fetch & display reviews)
|
||||
🔨 Rating stars/aggregate display component
|
||||
🔨 agents-api.ts (fetch agent profile)
|
||||
🔨 agents-server.ts (server-side fetch for ISR)
|
||||
🔨 JSON-LD LocalBusiness schema for agent
|
||||
### 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
|
||||
|
||||
### Architecture Decisions
|
||||
- **Routing:** Place at `apps/web/app/[locale]/(public)/agents/[id]/page.tsx`
|
||||
- **Pattern:** Follow listing detail page pattern exactly
|
||||
- **Metadata:** Use generateMetadata() server function
|
||||
- **Components:** Split into Server Component (page) + Client Component (interactive)
|
||||
- **SEO:** Include breadcrumb + LocalBusiness JSON-LD
|
||||
- **Styling:** Use existing Tailwind tokens + components
|
||||
- **Data Fetching:** Server-side fetch with ISR revalidation (3600s)
|
||||
### 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. NEXT STEPS
|
||||
|
||||
1. **Design API DTO** for public agent profile
|
||||
2. **Create backend query handler** for fetching public agent profile
|
||||
3. **Create frontend API client** (agents-api.ts)
|
||||
4. **Build page structure** following listing detail pattern
|
||||
5. **Create agent detail client component** with sections
|
||||
6. **Add reviews display section** with star ratings
|
||||
7. **Add listings display section** reusing PropertyCard
|
||||
8. **Generate JSON-LD** structured data for SEO
|
||||
9. **Test ISR & metadata** generation
|
||||
10. **Add international routes** (e.g., /en/agents/[id] via locale)
|
||||
## 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)
|
||||
|
||||
Reference in New Issue
Block a user