feat(agents): add public agent profile page at /agents/[id]
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>
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import type { AgentPublicProfile } from '@/lib/agents-api';
|
||||
import type { ListingDetail } from '@/lib/listings-api';
|
||||
import { PROPERTY_TYPES, TRANSACTION_TYPES } from '@/lib/validations/listings';
|
||||
|
||||
@@ -170,6 +171,46 @@ export function generateWebsiteJsonLd(siteUrl: string) {
|
||||
};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Agent profile JSON-LD (RealEstateAgent)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export function generateAgentJsonLd(agent: AgentPublicProfile, siteUrl: string) {
|
||||
const jsonLd: Record<string, unknown> = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'RealEstateAgent',
|
||||
name: agent.fullName,
|
||||
url: `${siteUrl}/agents/${agent.id}`,
|
||||
description: agent.bio ?? `Môi giới bất động sản tại GoodGo`,
|
||||
...(agent.avatarUrl && { image: agent.avatarUrl }),
|
||||
telephone: agent.phone,
|
||||
...(agent.email && { email: agent.email }),
|
||||
...(agent.agency && {
|
||||
worksFor: {
|
||||
'@type': 'RealEstateAgent',
|
||||
name: agent.agency,
|
||||
},
|
||||
}),
|
||||
...(agent.serviceAreas.length > 0 && {
|
||||
areaServed: agent.serviceAreas.map((area) => ({
|
||||
'@type': 'AdministrativeArea',
|
||||
name: area,
|
||||
})),
|
||||
}),
|
||||
...(agent.totalReviews > 0 && {
|
||||
aggregateRating: {
|
||||
'@type': 'AggregateRating',
|
||||
ratingValue: agent.avgReviewRating,
|
||||
reviewCount: agent.totalReviews,
|
||||
bestRating: 5,
|
||||
worstRating: 1,
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
return jsonLd;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// React component that renders <script type="application/ld+json">
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user