# Agent Public Profile Page โ€” Quick Reference Guide ## ๐ŸŽฏ Implementation Overview ### URL Pattern ``` /agents/[id] # Desktop /agents/[id]?locale=vi # With locale (i18n) /en/agents/[id] # Explicit locale ``` ### Page Location ``` apps/web/app/[locale]/(public)/agents/[id]/page.tsx ``` --- ## ๐Ÿ“ฆ Backend Setup (API Changes Required) ### New Public Endpoint ```http GET /agents/:agentId/profile ``` ### Response DTO ```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; } ``` ### Query Handler Files to Create ``` 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 ``` --- ## ๐ŸŽจ Frontend Architecture ### Directory Structure to Create ``` apps/web/ โ”œโ”€โ”€ app/[locale]/(public)/agents/ โ”‚ โ””โ”€โ”€ [id]/ โ”‚ โ”œโ”€โ”€ page.tsx โ† Server component (metadata, ISR) โ”‚ โ””โ”€โ”€ layout.tsx โ† Optional shared layout โ”‚ โ”œโ”€โ”€ components/agents/ โ”‚ โ”œโ”€โ”€ agent-detail-client.tsx โ† Main interactive component โ”‚ โ”œโ”€โ”€ agent-header.tsx โ† Profile info section โ”‚ โ”œโ”€โ”€ agent-listings-section.tsx โ† Grid of listings โ”‚ โ””โ”€โ”€ agent-reviews-section.tsx โ† Reviews with stars โ”‚ โ””โ”€โ”€ lib/ โ”œโ”€โ”€ agents-api.ts โ† API client โ””โ”€โ”€ agents-server.ts โ† Server-side ISR fetch ``` --- ## ๐Ÿ”„ Data Flow ``` 1. Browser requests: /agents/[id] โ†“ 2. Next.js generates metadata (SEO) - Title: "Agent Name โ€” Real Estate Agent at GoodGo" - Description: Bio + stats - Image: Avatar - Canonical URL โ†“ 3. Server-side fetch agent profile data - GET /api/v1/agents/:id/profile (ISR: revalidate 3600s) โ†“ 4. Render Server Component with metadata + JSON-LD โ†“ 5. Pass data to Client Component for interactivity - Fetch reviews in parallel - Fetch listings in parallel โ†“ 6. Client renders: - Agent header (avatar, name, stats, badges) - Reviews section (star ratings, comment cards) - Listings section (reuse PropertyCard component) ``` --- ## ๐ŸŽญ Component Composition ### Server Component (page.tsx) ```typescript export async function generateMetadata({ params }): Promise { // 1. Fetch agent using agents-server.ts // 2. Build SEO metadata // 3. Return title, description, OG, canonical } export default async function AgentProfilePage({ params }) { // 1. Fetch agent profile (with ISR) // 2. Generate JSON-LD (LocalBusiness schema) // 3. Render for structured data // 4. Pass agent to 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 agent's active listings // Fetch reviews & stats }, [agent.id]); return <> } ``` --- ## ๐Ÿ”— API Calls Used ### Existing Public Endpoints (No Auth Required) ```http # Get agent profile (NEW - to be created) GET /api/v1/agents/:agentId/profile # Get agent's listings (EXISTING) GET /api/v1/listings?agentId=:agentId&status=ACTIVE # Get agent reviews (EXISTING) GET /api/v1/reviews?targetType=AGENT&targetId=:agentId&limit=20 # Get agent review stats (EXISTING) GET /api/v1/reviews/stats?targetType=AGENT&targetId=:agentId ``` --- ## ๐ŸŽจ UI Sections & Reusable Components ### 1. Agent Header Section ``` โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ [Avatar] Name โ”‚ โ”‚ License: ABC123 โ”‚ โ”‚ Agency: XYZ Agency โ”‚ โ”‚ โœ“ Verified โ”‚ โ”‚ โ”‚ โ”‚ โญ 4.5 (120 reviews) โ”‚ โ”‚ ๐Ÿ“ Serves: Quan 1, Quan 7, ... โ”‚ โ”‚ ๐Ÿ  45 active listings โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ Components: Card, Badge, Image, Text Styling: Tailwind (p-6, flex, gap-4) ``` ### 2. Reviews Section ``` โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ Customer Reviews (120 total) โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ [Review Card] โ”‚ โ”‚ โญโญโญโญโญ John Doe โ”‚ 2 days ago โ”‚ โ”‚ "Great agent, very professional" โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ [Review Card] โ”‚ โ”‚ โญโญโญโญ Jane Smith โ”‚ 1 week ago โ”‚ โ”‚ "Good communication" โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ Components: Card, Badge (rating), Avatar Reuse: PropertyCard padding/spacing pattern ``` ### 3. Listings Section ``` โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ Active Listings (45 total) โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ [PropertyCard] [PropertyCard] โ”‚ โ”‚ [PropertyCard] [PropertyCard] โ”‚ โ”‚ [PropertyCard] [PropertyCard] โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ Components: PropertyCard (reuse from search/property-card.tsx) Grid: grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 ``` --- ## ๐ŸŽฏ Copy-Paste Templates ### 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 every 1 hour }); if (!res.ok) return null; return res.json(); } catch { return null; } } ``` --- ## ๐Ÿ” SEO & Structured Data ### Metadata Hints ```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: Use avatar or placeholder ``` ### JSON-LD Schema ```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" } ``` --- ## ๐Ÿš€ Implementation Phases ### Phase 1: Backend (1-2 hours) - [ ] Create GetAgentProfileQuery - [ ] Create GetAgentProfileHandler - [ ] Create AgentPublicProfileDto - [ ] Add endpoint to AgentsController - [ ] Update AgentRepository interface - [ ] Implement in PrismaAgentRepository - [ ] Test with Postman/curl ### Phase 2: Frontend Setup (1 hour) - [ ] Create agents-api.ts - [ ] Create agents-server.ts - [ ] Create agents folder structure - [ ] Create [id]/page.tsx (stub) - [ ] Import types from agents-api.ts ### Phase 3: UI Components (2-3 hours) - [ ] Create AgentHeader component - [ ] Create AgentReviewsSection component - [ ] Create AgentListingsSection component - [ ] Create RatingStars/ReviewCard components - [ ] Wire up data fetching ### Phase 4: SEO & Polish (1 hour) - [ ] Add generateMetadata() - [ ] Generate JSON-LD schemas - [ ] Test OG preview - [ ] Mobile responsive check - [ ] Dark mode testing ### Phase 5: Testing (1 hour) - [ ] Manual e2e test - [ ] Check 404 handling - [ ] Verify ISR revalidation - [ ] Test pagination (listings/reviews) - [ ] SEO audit (Lighthouse) --- ## ๐Ÿ“Š Example Response Structure ```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" } ``` --- ## ๐ŸŽฏ Key Files Reference | Phase | Files to Create/Modify | |-------|------------------------| | 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` |