Inject CMS meta tags as full HTML from business-info.
metaTags is now { html }; parse into head <meta> elements instead of name/content metadata.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
f60c98184b
commit
7ea3fe76c9
+15
-6
@@ -6,7 +6,12 @@ import { TopBar } from "@/components/home/TopBar";
|
|||||||
import { Header } from "@/components/home/Header";
|
import { Header } from "@/components/home/Header";
|
||||||
import { Footer } from "@/components/home/Footer";
|
import { Footer } from "@/components/home/Footer";
|
||||||
import { Providers } from "@/context/Providers";
|
import { Providers } from "@/context/Providers";
|
||||||
import { getBusinessInfo, getCategories, getCategoryTree } from "@/lib/meshkee";
|
import {
|
||||||
|
getBusinessInfo,
|
||||||
|
getCategories,
|
||||||
|
getCategoryTree,
|
||||||
|
parseMetaTagHtml,
|
||||||
|
} from "@/lib/meshkee";
|
||||||
|
|
||||||
// Body text — headings use the bold weight instead, see globals.css.
|
// Body text — headings use the bold weight instead, see globals.css.
|
||||||
const iranyekan = localFont({
|
const iranyekan = localFont({
|
||||||
@@ -29,16 +34,11 @@ const syne = Syne({
|
|||||||
|
|
||||||
export async function generateMetadata(): Promise<Metadata> {
|
export async function generateMetadata(): Promise<Metadata> {
|
||||||
const businessInfo = await getBusinessInfo();
|
const businessInfo = await getBusinessInfo();
|
||||||
const other: Record<string, string> = {};
|
|
||||||
for (const tag of businessInfo?.metaTags ?? []) {
|
|
||||||
if (tag.name && tag.content) other[tag.name] = tag.content;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: "صنیع هوم | فروشگاه لوازم خانگی",
|
title: "صنیع هوم | فروشگاه لوازم خانگی",
|
||||||
description: "فروشگاه آنلاین لوازم خانگی — یخچال، لباسشویی، تلویزیون و بیشتر",
|
description: "فروشگاه آنلاین لوازم خانگی — یخچال، لباسشویی، تلویزیون و بیشتر",
|
||||||
icons: businessInfo?.faviconUrl ? [{ url: businessInfo.faviconUrl }] : undefined,
|
icons: businessInfo?.faviconUrl ? [{ url: businessInfo.faviconUrl }] : undefined,
|
||||||
...(Object.keys(other).length > 0 ? { other } : {}),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,12 +49,21 @@ export default async function RootLayout({ children }: LayoutProps<"/">) {
|
|||||||
getBusinessInfo(),
|
getBusinessInfo(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const cmsMetaTags = (businessInfo?.metaTags ?? [])
|
||||||
|
.map((tag, index) => {
|
||||||
|
const attrs = parseMetaTagHtml(tag.html ?? "");
|
||||||
|
if (!attrs) return null;
|
||||||
|
return <meta key={`cms-meta-${index}`} {...attrs} />;
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html
|
<html
|
||||||
lang="fa"
|
lang="fa"
|
||||||
dir="rtl"
|
dir="rtl"
|
||||||
className={`${iranyekan.variable} ${iranyekanBold.variable} ${syne.variable} h-full antialiased`}
|
className={`${iranyekan.variable} ${iranyekanBold.variable} ${syne.variable} h-full antialiased`}
|
||||||
>
|
>
|
||||||
|
{cmsMetaTags.length > 0 ? <head>{cmsMetaTags}</head> : null}
|
||||||
<body className="min-h-full flex flex-col">
|
<body className="min-h-full flex flex-col">
|
||||||
<Providers>
|
<Providers>
|
||||||
<TopBar />
|
<TopBar />
|
||||||
|
|||||||
+17
-2
@@ -44,11 +44,26 @@ async function apiPost<T>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Full sanitized `<meta …>` HTML from business-info. */
|
||||||
export type BusinessMetaTag = {
|
export type BusinessMetaTag = {
|
||||||
name: string;
|
html: string;
|
||||||
content: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Parse a single `<meta …>` tag into React/Next attribute props. */
|
||||||
|
export function parseMetaTagHtml(html: string): Record<string, string> | null {
|
||||||
|
const match = html.trim().match(/^<meta\b([^>]*)\/?\s*>$/i);
|
||||||
|
if (!match) return null;
|
||||||
|
const attrs: Record<string, string> = {};
|
||||||
|
const attrRe = /([:\w.-]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
|
||||||
|
let m: RegExpExecArray | null;
|
||||||
|
while ((m = attrRe.exec(match[1]))) {
|
||||||
|
const key = m[1].toLowerCase();
|
||||||
|
if (key === '/') continue;
|
||||||
|
attrs[key] = m[2] ?? m[3] ?? m[4] ?? '';
|
||||||
|
}
|
||||||
|
return Object.keys(attrs).length > 0 ? attrs : null;
|
||||||
|
}
|
||||||
|
|
||||||
export type BusinessTrustBadge = {
|
export type BusinessTrustBadge = {
|
||||||
kind: "enamad" | "samandehi";
|
kind: "enamad" | "samandehi";
|
||||||
embedHtml: string;
|
embedHtml: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user