207 lines
6.4 KiB
JavaScript
207 lines
6.4 KiB
JavaScript
import { cache } from "react";
|
|
import DoctorPage from "@/components/doctor";
|
|
import Layout from "@/components/layout/StLayout";
|
|
import { notFound } from "next/navigation";
|
|
import { getStateInfo } from "@/lib/getStateInfo";
|
|
import { getRequestOrigin } from "@/lib/getCanonicalUrl";
|
|
import { safeJsonLd } from "@/lib/sanitize";
|
|
import { imageUrl } from "@/helper";
|
|
|
|
const API_URL = process.env.NEXT_PUBLIC_API_URL;
|
|
const FALLBACK_IMG = "https://nobat724.com/assets/images/logo.png";
|
|
|
|
const getDoctor = cache(async (slug) => {
|
|
if (!slug || slug === "undefined") return null;
|
|
const res = await fetch(`${API_URL}/api/v1/doctor/${slug}`, {
|
|
next: { revalidate: 3600, tags: [`doctor-${slug}`] },
|
|
});
|
|
if (res.status === 404 || res.status === 400) return null;
|
|
if (!res.ok) throw new Error(`Failed to fetch doctor: ${res.status}`);
|
|
const json = await res.json();
|
|
return json?.data?.data ?? null;
|
|
});
|
|
|
|
const getDoctorAddresses = cache(async (doctorId) => {
|
|
if (!doctorId) return [];
|
|
try {
|
|
const res = await fetch(`${API_URL}/api/v1/clinic-pro/doctor-addresses/${doctorId}`, {
|
|
next: { revalidate: 3600, tags: [`doctor-addresses-${doctorId}`] },
|
|
});
|
|
if (!res.ok) return [];
|
|
const json = await res.json();
|
|
return json?.data ?? [];
|
|
} catch {
|
|
return [];
|
|
}
|
|
});
|
|
|
|
export async function generateMetadata({ params }) {
|
|
const { slug } = await params;
|
|
const { matchedCity } = await getStateInfo();
|
|
const siteName = matchedCity?.site_name || "نوبت 724";
|
|
|
|
const doctor = await getDoctor(slug);
|
|
if (!doctor) notFound();
|
|
|
|
try {
|
|
|
|
const specialtyNames = doctor.specialties?.map((s) => s.name).join(" و ") || "";
|
|
const title = `دکتر ${doctor.name}${specialtyNames ? " | " + specialtyNames : ""} | ${siteName}`;
|
|
const detailText = (doctor.detail || "")
|
|
.replace(/<[^>]*>/g, "")
|
|
.replace(/\s+/g, " ")
|
|
.trim();
|
|
const description = (
|
|
detailText ||
|
|
`رزرو نوبت آنلاین دکتر ${doctor.name}${specialtyNames ? " متخصص " + specialtyNames : ""}${doctor.address ? " | " + doctor.address : ""}`.trim()
|
|
).slice(0, 160);
|
|
const image = imageUrl(doctor.img?.[0]?.url) || FALLBACK_IMG;
|
|
|
|
return {
|
|
title,
|
|
description,
|
|
openGraph: {
|
|
title,
|
|
description,
|
|
type: "profile",
|
|
images: [image],
|
|
},
|
|
twitter: {
|
|
card: "summary",
|
|
title,
|
|
description,
|
|
images: [image],
|
|
},
|
|
};
|
|
} catch {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
async function Doctor({ params }) {
|
|
const { slug } = await params;
|
|
const origin = await getRequestOrigin();
|
|
let comments = null;
|
|
let rateAggregate = { point: 0, satisfaction: 0, averages: [] };
|
|
|
|
const doctor = await getDoctor(slug);
|
|
if (!doctor) notFound();
|
|
|
|
if (doctor) {
|
|
try {
|
|
const [resComments, resRate] = await Promise.all([
|
|
fetch(`${API_URL}/api/v1/comments/${doctor.uuid}`, { cache: "no-store" }),
|
|
fetch(`${API_URL}/api/v1/rate/${doctor.uuid}`, { cache: "no-store" }),
|
|
]);
|
|
const [jsonComments, jsonRate] = await Promise.all([
|
|
resComments.ok ? resComments.json() : null,
|
|
resRate.ok ? resRate.json() : null,
|
|
]);
|
|
comments = jsonComments?.data?.data;
|
|
rateAggregate = jsonRate?.data?.data ?? rateAggregate;
|
|
} catch (error) {}
|
|
}
|
|
|
|
const addresses = doctor ? await getDoctorAddresses(doctor.id) : [];
|
|
|
|
const specialtyNames = doctor?.specialties?.map((s) => s.name).join(" و ") || "";
|
|
const jsonLd = doctor
|
|
? {
|
|
"@context": "https://schema.org",
|
|
"@type": "Physician",
|
|
"@id": `${origin}/doctor/${doctor.uuid}#physician`,
|
|
name: `دکتر ${doctor.name}`,
|
|
url: `${origin}/doctor/${doctor.uuid}`,
|
|
...(specialtyNames && { medicalSpecialty: specialtyNames }),
|
|
...(doctor.img?.[0]?.url && {
|
|
image: {
|
|
"@type": "ImageObject",
|
|
url: imageUrl(doctor.img[0].url),
|
|
},
|
|
}),
|
|
...(Number(doctor.point) > 0 && {
|
|
aggregateRating: {
|
|
"@type": "AggregateRating",
|
|
ratingValue: doctor.point,
|
|
bestRating: "5",
|
|
ratingCount: comments?.length || 1,
|
|
},
|
|
}),
|
|
...(comments?.length > 0 && {
|
|
review: comments.slice(0, 5).map((c) => ({
|
|
"@type": "Review",
|
|
author: { "@type": "Person", name: c.author?.real_name || "بیمار" },
|
|
reviewBody: c.comment,
|
|
datePublished: new Date(c.created * 1000).toISOString(),
|
|
})),
|
|
}),
|
|
...(doctor.social_media && {
|
|
sameAs: Object.values(doctor.social_media).filter(Boolean),
|
|
}),
|
|
...(addresses.length > 0 && {
|
|
workLocation: addresses.map((addr) => ({
|
|
"@type": "MedicalClinic",
|
|
name: addr.clinic_name || addr.name || `مطب دکتر ${doctor.name}`,
|
|
address: {
|
|
"@type": "PostalAddress",
|
|
streetAddress: addr.address,
|
|
},
|
|
...(addr.telephone && { telephone: addr.telephone }),
|
|
...(addr.map?.latitude && addr.map?.longitude && {
|
|
geo: {
|
|
"@type": "GeoCoordinates",
|
|
latitude: addr.map.latitude,
|
|
longitude: addr.map.longitude,
|
|
},
|
|
}),
|
|
})),
|
|
}),
|
|
}
|
|
: null;
|
|
|
|
const specialtyLabel = doctor?.specialties?.[0]?.name || "پزشکان";
|
|
const breadcrumbJsonLd = doctor
|
|
? {
|
|
"@context": "https://schema.org",
|
|
"@type": "BreadcrumbList",
|
|
itemListElement: [
|
|
{ "@type": "ListItem", position: 1, name: "خانه", item: origin },
|
|
{
|
|
"@type": "ListItem",
|
|
position: 2,
|
|
name: specialtyLabel,
|
|
item: `${origin}/doctors?specialty=${encodeURIComponent(specialtyLabel)}`,
|
|
},
|
|
{ "@type": "ListItem", position: 3, name: `دکتر ${doctor.name}` },
|
|
],
|
|
}
|
|
: null;
|
|
|
|
return (
|
|
<>
|
|
{jsonLd && (
|
|
<script
|
|
type="application/ld+json"
|
|
dangerouslySetInnerHTML={{ __html: safeJsonLd(jsonLd) }}
|
|
/>
|
|
)}
|
|
{breadcrumbJsonLd && (
|
|
<script
|
|
type="application/ld+json"
|
|
dangerouslySetInnerHTML={{ __html: safeJsonLd(breadcrumbJsonLd) }}
|
|
/>
|
|
)}
|
|
<Layout>
|
|
<DoctorPage
|
|
doctor={doctor}
|
|
comments={comments}
|
|
rateAggregate={rateAggregate}
|
|
slug={slug}
|
|
/>
|
|
</Layout>
|
|
</>
|
|
);
|
|
}
|
|
|
|
export default Doctor;
|