Implements a public-facing agent profile page with: - Backend: new GET /agents/:agentId/profile public API endpoint with agent info, active listings, quality score, and review stats - Frontend: server-rendered profile page with generateMetadata for SEO, JSON-LD structured data (RealEstateAgent schema), breadcrumbs - Agent profile displays bio, service areas, quality score gauge, active listing cards, reviews with star ratings, and contact CTA - Mobile responsive layout with sticky contact sidebar on desktop - Vietnamese UI text throughout, consistent with existing patterns Co-Authored-By: Paperclip <noreply@paperclip.ing>
86 lines
2.1 KiB
TypeScript
86 lines
2.1 KiB
TypeScript
import { apiClient } from './api-client';
|
|
|
|
// ─── Interfaces ──────────────────────────────────────────
|
|
|
|
export interface AgentListingItem {
|
|
id: string;
|
|
transactionType: string;
|
|
priceVND: string;
|
|
status: string;
|
|
property: {
|
|
id: string;
|
|
title: string;
|
|
propertyType: string;
|
|
address: string;
|
|
district: string;
|
|
city: string;
|
|
areaM2: number;
|
|
bedrooms: number | null;
|
|
bathrooms: number | null;
|
|
imageUrl: string | null;
|
|
};
|
|
}
|
|
|
|
export interface AgentPublicProfile {
|
|
id: string;
|
|
fullName: string;
|
|
avatarUrl: string | null;
|
|
phone: string;
|
|
email: string | null;
|
|
agency: string | null;
|
|
licenseNumber: string | null;
|
|
bio: string | null;
|
|
qualityScore: number;
|
|
totalDeals: number;
|
|
isVerified: boolean;
|
|
serviceAreas: string[];
|
|
memberSince: string;
|
|
activeListings: AgentListingItem[];
|
|
avgReviewRating: number;
|
|
totalReviews: number;
|
|
}
|
|
|
|
export interface AgentReviewItem {
|
|
id: string;
|
|
userId: string;
|
|
userName: string | null;
|
|
targetType: string;
|
|
targetId: string;
|
|
rating: number;
|
|
comment: string | null;
|
|
createdAt: string;
|
|
}
|
|
|
|
export interface AgentReviewStats {
|
|
targetType: string;
|
|
targetId: string;
|
|
averageRating: number;
|
|
totalReviews: number;
|
|
distribution: Record<number, number>;
|
|
}
|
|
|
|
export interface PaginatedReviews {
|
|
data: AgentReviewItem[];
|
|
total: number;
|
|
page: number;
|
|
limit: number;
|
|
totalPages: number;
|
|
}
|
|
|
|
// ─── API Functions ───────────────────────────────────────
|
|
|
|
export const agentsApi = {
|
|
getPublicProfile: (agentId: string) =>
|
|
apiClient.get<AgentPublicProfile>(`/agents/${agentId}/profile`),
|
|
|
|
getReviews: (agentId: string, page = 1, limit = 10) =>
|
|
apiClient.get<PaginatedReviews>(
|
|
`/reviews?targetType=AGENT&targetId=${agentId}&page=${page}&limit=${limit}`,
|
|
),
|
|
|
|
getReviewStats: (agentId: string) =>
|
|
apiClient.get<AgentReviewStats>(
|
|
`/reviews/stats?targetType=AGENT&targetId=${agentId}`,
|
|
),
|
|
};
|