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,6 +1,6 @@
|
||||
# Agent Public Profile Page — Code Examples & Implementation Templates
|
||||
# Trang Hồ Sơ Công Khai Môi Giới — Ví Dụ Mã Nguồn & Mẫu Triển Khai
|
||||
|
||||
## 1️⃣ BACKEND: API Endpoint Creation
|
||||
## 1️⃣ BACKEND: Tạo Endpoint API
|
||||
|
||||
### File: `apps/api/src/modules/agents/application/queries/get-agent-profile/get-agent-profile.query.ts`
|
||||
|
||||
@@ -93,7 +93,7 @@ export class AgentPublicProfileDto {
|
||||
}
|
||||
```
|
||||
|
||||
### File: Update `apps/api/src/modules/agents/presentation/controllers/agents.controller.ts`
|
||||
### File: Cập nhật `apps/api/src/modules/agents/presentation/controllers/agents.controller.ts`
|
||||
|
||||
```typescript
|
||||
import { Controller, Get, Param, Post, UseGuards } from '@nestjs/common';
|
||||
@@ -111,7 +111,7 @@ export class AgentsController {
|
||||
private readonly queryBus: QueryBus,
|
||||
) {}
|
||||
|
||||
// ── Public endpoint ────────────────────────────────────────
|
||||
// ── Endpoint công khai ────────────────────────────────────────
|
||||
@ApiOperation({ summary: 'Get public agent profile' })
|
||||
@ApiParam({ name: 'agentId', description: 'Agent ID' })
|
||||
@ApiResponse({ status: 200, description: 'Agent profile', type: AgentPublicProfileDto })
|
||||
@@ -121,12 +121,12 @@ export class AgentsController {
|
||||
return this.queryBus.execute(new GetAgentProfileQuery(agentId));
|
||||
}
|
||||
|
||||
// ── Existing endpoints (unchanged) ─────────────────────────
|
||||
// ... rest of controller
|
||||
// ── Các endpoint hiện có (không thay đổi) ─────────────────────────
|
||||
// ... phần còn lại của controller
|
||||
}
|
||||
```
|
||||
|
||||
### File: Update `apps/api/src/modules/agents/domain/repositories/agent.repository.ts`
|
||||
### File: Cập nhật `apps/api/src/modules/agents/domain/repositories/agent.repository.ts`
|
||||
|
||||
```typescript
|
||||
export interface IAgentRepository {
|
||||
@@ -135,12 +135,12 @@ export interface IAgentRepository {
|
||||
updateQualityScore(agentId: string, score: number): Promise<void>;
|
||||
getDashboard(agentId: string): Promise<AgentDashboardData>;
|
||||
|
||||
// NEW METHOD:
|
||||
// PHƯƠNG THỨC MỚI:
|
||||
getPublicProfile(agentId: string): Promise<AgentPublicProfileDto>;
|
||||
}
|
||||
```
|
||||
|
||||
### File: Update `apps/api/src/modules/agents/infrastructure/repositories/prisma-agent.repository.ts`
|
||||
### File: Cập nhật `apps/api/src/modules/agents/infrastructure/repositories/prisma-agent.repository.ts`
|
||||
|
||||
```typescript
|
||||
async getPublicProfile(agentId: string) {
|
||||
@@ -160,7 +160,7 @@ async getPublicProfile(agentId: string) {
|
||||
|
||||
if (!agent) return null;
|
||||
|
||||
// Get stats in parallel
|
||||
// Lấy thống kê song song
|
||||
const [totalListings, activeListings, reviewStats] = await Promise.all([
|
||||
this.prisma.listing.count({
|
||||
where: { agentId },
|
||||
@@ -236,7 +236,7 @@ const API_BASE_URL = process.env['NEXT_PUBLIC_API_URL'] || 'http://localhost:300
|
||||
export async function fetchAgentById(id: string) {
|
||||
try {
|
||||
const res = await fetch(`${API_BASE_URL}/agents/${id}/profile`, {
|
||||
next: { revalidate: 3600 }, // ISR: revalidate every 1 hour
|
||||
next: { revalidate: 3600 }, // ISR: làm mới mỗi 1 giờ
|
||||
});
|
||||
if (!res.ok) return null;
|
||||
return res.json();
|
||||
@@ -248,7 +248,7 @@ export async function fetchAgentById(id: string) {
|
||||
|
||||
---
|
||||
|
||||
## 3️⃣ FRONTEND: Server Component (Page)
|
||||
## 3️⃣ FRONTEND: Server Component (Trang)
|
||||
|
||||
### File: `apps/web/app/[locale]/(public)/agents/[id]/page.tsx`
|
||||
|
||||
@@ -361,7 +361,7 @@ export default async function AgentProfilePage({ params }: PageProps) {
|
||||
|
||||
---
|
||||
|
||||
## 4️⃣ FRONTEND: Client Components
|
||||
## 4️⃣ FRONTEND: Các Client Component
|
||||
|
||||
### File: `apps/web/components/agents/agent-detail-client.tsx`
|
||||
|
||||
@@ -411,7 +411,7 @@ export function AgentDetailClient({ agent }: AgentDetailClientProps) {
|
||||
<Card>
|
||||
<CardContent className="p-6 md:p-8">
|
||||
<div className="flex flex-col gap-6 md:flex-row">
|
||||
{/* Avatar */}
|
||||
{/* Ảnh đại diện */}
|
||||
<div className="flex-shrink-0">
|
||||
{agent.avatarUrl ? (
|
||||
<Image
|
||||
@@ -428,11 +428,11 @@ export function AgentDetailClient({ agent }: AgentDetailClientProps) {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Info */}
|
||||
{/* Thông tin */}
|
||||
<div className="flex-1">
|
||||
<h1 className="text-3xl font-bold">{agent.fullName}</h1>
|
||||
|
||||
{/* Badges */}
|
||||
{/* Huy hiệu */}
|
||||
<div className="mt-3 flex flex-wrap gap-2">
|
||||
{agent.isVerified && (
|
||||
<Badge variant="default">✓ Verified</Badge>
|
||||
@@ -442,7 +442,7 @@ export function AgentDetailClient({ agent }: AgentDetailClientProps) {
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{/* Details */}
|
||||
{/* Chi tiết */}
|
||||
<dl className="mt-4 grid grid-cols-2 gap-4 md:grid-cols-3">
|
||||
{agent.licenseNumber && (
|
||||
<>
|
||||
@@ -468,12 +468,12 @@ export function AgentDetailClient({ agent }: AgentDetailClientProps) {
|
||||
</div>
|
||||
</dl>
|
||||
|
||||
{/* Bio */}
|
||||
{/* Tiểu sử */}
|
||||
{agent.bio && (
|
||||
<p className="mt-4 text-sm text-muted-foreground">{agent.bio}</p>
|
||||
)}
|
||||
|
||||
{/* Service Areas */}
|
||||
{/* Khu vực phục vụ */}
|
||||
{agent.serviceAreas.length > 0 && (
|
||||
<div className="mt-4">
|
||||
<p className="text-xs font-medium text-muted-foreground">Serves:</p>
|
||||
@@ -608,15 +608,15 @@ export function AgentReviewsSection({ agentId }: AgentReviewsSectionProps) {
|
||||
<section className="py-16 md:py-24 bg-muted/50">
|
||||
<div className="mx-auto max-w-7xl px-4">
|
||||
<div className="mb-8">
|
||||
<h2 className="text-2xl font-bold">Customer Reviews</h2>
|
||||
<h2 className="text-2xl font-bold">Đánh Giá Của Khách Hàng</h2>
|
||||
{stats && (
|
||||
<div className="mt-4 flex items-center gap-4">
|
||||
<div>
|
||||
<div className="text-3xl font-bold">{stats.averageRating.toFixed(1)}</div>
|
||||
<div className="text-sm text-muted-foreground">out of 5.0</div>
|
||||
<div className="text-sm text-muted-foreground">trên 5,0</div>
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Based on {stats.totalReviews} reviews
|
||||
Dựa trên {stats.totalReviews} đánh giá
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -651,7 +651,7 @@ export function AgentReviewsSection({ agentId }: AgentReviewsSectionProps) {
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center text-muted-foreground">
|
||||
No reviews yet
|
||||
Chưa có đánh giá
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -662,45 +662,45 @@ export function AgentReviewsSection({ agentId }: AgentReviewsSectionProps) {
|
||||
|
||||
---
|
||||
|
||||
## 5️⃣ STYLING REFERENCE
|
||||
## 5️⃣ THAM KHẢO GIAO DIỆN
|
||||
|
||||
All components use:
|
||||
- **Tailwind CSS** classes directly (no CSS modules)
|
||||
- **Responsive breakpoints**: `md:`, `lg:`
|
||||
- **Dark mode**: Uses CSS variables in `globals.css`
|
||||
- **Component pattern**: Card → CardContent
|
||||
Tất cả các component đều sử dụng:
|
||||
- Các class **Tailwind CSS** trực tiếp (không dùng CSS module)
|
||||
- **Breakpoint responsive**: `md:`, `lg:`
|
||||
- **Chế độ tối**: Dùng biến CSS trong `globals.css`
|
||||
- **Mẫu component**: Card → CardContent
|
||||
|
||||
### Common spacing patterns:
|
||||
### Các mẫu khoảng cách thông dụng:
|
||||
```typescript
|
||||
// Sections
|
||||
// Các phần
|
||||
<section className="py-16 md:py-24">
|
||||
<div className="mx-auto max-w-7xl px-4">
|
||||
{/* content */}
|
||||
{/* nội dung */}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
// Cards
|
||||
// Card
|
||||
<Card>
|
||||
<CardContent className="p-4 md:p-6">
|
||||
{/* content */}
|
||||
{/* nội dung */}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
// Grid
|
||||
// Lưới
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{/* items */}
|
||||
{/* các phần tử */}
|
||||
</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing Checklist
|
||||
## 🧪 Danh Sách Kiểm Thử
|
||||
|
||||
```bash
|
||||
# Backend API Test
|
||||
# Kiểm thử API Backend
|
||||
curl http://localhost:3001/api/v1/agents/[valid-agent-id]/profile
|
||||
|
||||
# Expected Response
|
||||
# Phản hồi kỳ vọng
|
||||
{
|
||||
"id": "...",
|
||||
"fullName": "...",
|
||||
@@ -714,10 +714,9 @@ curl http://localhost:3001/api/v1/agents/[valid-agent-id]/profile
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Frontend Test
|
||||
// Kiểm thử Frontend
|
||||
import { agentsApi } from '@/lib/agents-api';
|
||||
|
||||
const agent = await agentsApi.getById('agent-id-here');
|
||||
console.log(agent); // Should match structure above
|
||||
console.log(agent); // Phải khớp với cấu trúc trên
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user