102 lines
7.0 KiB
JavaScript
102 lines
7.0 KiB
JavaScript
import { renderToString } from 'react-dom/server';
|
||
import { StaticRouter } from 'react-router-dom/server.js';
|
||
import App from './App.jsx';
|
||
import { ApiDataProvider } from './context/ApiData.jsx';
|
||
import { parseBlocks } from './lib/serviceContent.js';
|
||
import { serviceMetadata, doctorMetadata, priceMetadata, pageMetadata, jsonForHTML } from './lib/seo.js';
|
||
import { DIRECTION_MEDIA, directionOf } from './lib/media.js';
|
||
export { parseBlocks };
|
||
|
||
export function siteFromCommon(common) {
|
||
const branches = common.branches || [];
|
||
const settings = common.settings || {};
|
||
const first = branches[0];
|
||
const coords = branches.find((b) => b.lat && b.lon);
|
||
return {
|
||
phone: first?.phones?.[0] || '+7 928 962-00-94', hours: first?.work_hours?.[0] || 'Пн–Вс: 08:00–20:00',
|
||
addresses: branches.map((b) => b.address.replace(/^г\.\s*/, '').replace(/(\d)\(/g, '$1 (')),
|
||
branches, coords: coords ? { lat: coords.lat, lon: coords.lon } : null,
|
||
maxUrl: settings.max_url || '', email: settings.email || '', legal: settings.legal || null,
|
||
};
|
||
}
|
||
|
||
export function renderPage(url, data, siteURL) {
|
||
const pathname = url.split('?')[0];
|
||
const initial = { ...data, prerendered: true, siteURL };
|
||
const servicePath = pathname.startsWith('/services/') ? pathname.slice(10).replace(/\/$/, '') : '';
|
||
const service = initial[`service:${servicePath}`]?.data;
|
||
if (service) {
|
||
service.content_sections = parseBlocks(service.blocks);
|
||
const specialty = DIRECTION_MEDIA[directionOf(service.path)]?.specialty;
|
||
initial[`doctors:${specialty}`] = { data: (initial.doctors?.data || []).filter((d) => d.specialties?.some((s) => s.slug === specialty)), meta: {} };
|
||
}
|
||
const parts = pathname.split('/').filter(Boolean);
|
||
const doctor = parts.length === 4 && parts[0] === 'company' ? initial[`doctor:${parts[3]}`]?.data : null;
|
||
const legal = parts[0] === 'legaldocs' ? initial[`page:${parts[1] || 'legaldocs'}`]?.data : null;
|
||
const checkup = parts.length === 2 && parts[0] === 'checkup' ? initial[`checkup:${parts[1]}`]?.data : null;
|
||
const specialty = parts.length === 3 && parts[0] === 'company' ? initial.specialties?.data.find((s) => s.slug === parts[2]) : null;
|
||
const titles = { '/services/': 'Услуги', '/company/staff/': 'Врачи', '/price/': 'Цены на медицинские услуги',
|
||
'/contacts/': 'Контакты', '/checkup/': 'Чек-апы', '/sales/': 'Акции и специальные предложения',
|
||
'/company/': 'О клинике', '/news/': 'Новости', '/articles/': 'Статьи', '/reviews/': 'Отзывы',
|
||
'/company/partners/': 'Партнёры', '/company/nadzor/': 'Надзорные органы', '/company/vacancy/': 'Вакансии',
|
||
'/info/faq/': 'Вопросы и ответы', '/company/licenses/': 'Лицензии и награды', '/patients/': 'Пациенту' };
|
||
// раздел «Компании» → запись pages с заголовком и SEO
|
||
const sectionSlugs = { '/company/': 'about', '/news/': 'news', '/articles/': 'articles', '/reviews/': 'reviews',
|
||
'/company/partners/': 'partners', '/company/nadzor/': 'nadzor', '/company/vacancy/': 'vacancy', '/info/faq/': 'faq', '/company/licenses/': 'licenses', '/patients/': 'patients' };
|
||
const section = sectionSlugs[pathname] ? initial[`page:${sectionSlugs[pathname]}`]?.data : null;
|
||
const post = parts.length === 2 && ['news', 'articles'].includes(parts[0]) ? initial[`${parts[0]}:${parts[1]}`]?.data : null;
|
||
let metadata = pageMetadata(pathname, specialty?.name || (pathname.startsWith('/price/') ? 'Цены на медицинские услуги' : titles[pathname]) || 'Клиника семейного здоровья', siteURL);
|
||
if (service) metadata = serviceMetadata(service, siteURL);
|
||
if (doctor) metadata = doctorMetadata(doctor, siteURL);
|
||
if (legal) metadata = pageMetadata(pathname, legal.title, siteURL, legal.seo);
|
||
if (checkup) metadata = pageMetadata(pathname, checkup.name, siteURL, checkup.seo, checkup.lead);
|
||
if (section) metadata = pageMetadata(pathname, section.title, siteURL, section.seo);
|
||
if (post) metadata = pageMetadata(pathname, post.title, siteURL, post.seo, post.lead);
|
||
if (parts[0] === 'price') metadata = priceMetadata(parts[1], initial.prices?.data, siteURL);
|
||
const html = renderToString(<ApiDataProvider value={initial}>
|
||
<StaticRouter location={url}><App /></StaticRouter>
|
||
</ApiDataProvider>);
|
||
const name = doctor?.full_name || legal?.title || checkup?.name || post?.title || section?.title || specialty?.name || titles[pathname] || metadata.title;
|
||
const crumbs = [{ name: 'Главная', item: `${siteURL}/` }];
|
||
if (doctor) crumbs.push({ name: 'Врачи', item: `${siteURL}/company/staff/` });
|
||
if (post) crumbs.push({ name: titles[`/${parts[0]}/`], item: `${siteURL}/${parts[0]}/` });
|
||
if (pathname !== '/') crumbs.push({ name, item: metadata.canonical });
|
||
const clinics = (initial.site?.branches || []).map((b) => ({ '@type': 'MedicalClinic',
|
||
'@id': `${siteURL}/contacts/#${b.slug}`, name: `DANA Medical — ${b.name}`, url: `${siteURL}/contacts/`,
|
||
address: { '@type': 'PostalAddress', streetAddress: b.address, addressCountry: 'RU' }, telephone: b.phones?.[0],
|
||
}));
|
||
const schema = service ? null : { '@context': 'https://schema.org', '@graph': [
|
||
{ '@type': doctor || checkup ? 'MedicalWebPage' : post ? (parts[0] === 'news' ? 'WebPage' : 'MedicalWebPage') : 'WebPage', '@id': metadata.canonical,
|
||
name, url: metadata.canonical, description: metadata.description, inLanguage: 'ru-RU',
|
||
breadcrumb: { '@id': `${metadata.canonical}#breadcrumbs` },
|
||
...(doctor ? { mainEntity: { '@id': `${metadata.canonical}#physician` } } : {}),
|
||
},
|
||
{ '@type': 'BreadcrumbList', '@id': `${metadata.canonical}#breadcrumbs`, itemListElement: crumbs.map((c, i) => ({ '@type': 'ListItem', position: i + 1, ...c })) },
|
||
...clinics,
|
||
...(doctor ? [{ '@type': 'Physician', '@id': `${metadata.canonical}#physician`, name: doctor.full_name,
|
||
url: metadata.canonical, description: doctor.position || undefined,
|
||
image: doctor.photo ? new URL(doctor.photo, siteURL).href : undefined }] : []),
|
||
] };
|
||
return { html, initial: jsonForHTML(initial), metadata, schema };
|
||
}
|
||
|
||
export function renderService(service, parent, common, siteURL) {
|
||
service.content_sections = parseBlocks(service.blocks);
|
||
const specialty = DIRECTION_MEDIA[directionOf(service.path)]?.specialty;
|
||
const initial = {
|
||
prerendered: true, siteURL, site: common.site,
|
||
[`service:${service.path}`]: { data: service, meta: {} },
|
||
specialties: { data: common.specialties, meta: {} },
|
||
prices: { data: common.prices || [], meta: {} },
|
||
[`doctors:${specialty}`]: { data: common.doctors.filter((d) =>
|
||
d.specialties?.some((s) => s.slug === specialty)), meta: {} },
|
||
};
|
||
if (parent) initial[`service:${parent.path}`] = { data: parent, meta: {} };
|
||
const html = renderToString(
|
||
<ApiDataProvider value={initial}>
|
||
<StaticRouter location={`/services/${service.path}/`}><App /></StaticRouter>
|
||
</ApiDataProvider>,
|
||
);
|
||
return { html, initial: jsonForHTML(initial), metadata: serviceMetadata(service, siteURL) };
|
||
}
|