Load site meta tags and trust badges from Meshkee business info.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
cd88e0ce46
commit
b4d6b30243
@@ -61,6 +61,13 @@ Homepage images: `GET /tenants/{domain}/website/static-images?pageKey=home`. Cat
|
|||||||
|
|
||||||
`url` → img, `linkUrl` → wrap in a link, empty/fail → placeholder. Use `slot.aspectRatio`. If a slot’s size/ratio changes in CSS, update `STATIC_IMAGE_SLOTS` in `lib/static-images.ts` in the same change.
|
`url` → img, `linkUrl` → wrap in a link, empty/fail → placeholder. Use `slot.aspectRatio`. If a slot’s size/ratio changes in CSS, update `STATIC_IMAGE_SLOTS` in `lib/static-images.ts` in the same change.
|
||||||
|
|
||||||
|
## Tags & badges
|
||||||
|
|
||||||
|
`GET /tenants/{domain}/website/business-info`
|
||||||
|
|
||||||
|
- `metaTags`: `[{ name, content }]` emitted once from root `generateMetadata` as `other`. Empty → omit.
|
||||||
|
- `trustBadges`: `[{ kind: "enamad" | "samandehi", embedHtml }]` rendered in the footer with `dangerouslySetInnerHTML`. Empty → omit. Do not hardcode eNamad or Samandehi placeholders.
|
||||||
|
|
||||||
## Local fallbacks
|
## Local fallbacks
|
||||||
|
|
||||||
- Slider: `src/assets/images/slides`, else `/placeholders/slider-*.svg`
|
- Slider: `src/assets/images/slides`, else `/placeholders/slider-*.svg`
|
||||||
|
|||||||
+18
-7
@@ -6,6 +6,7 @@ import { Header } from "@/components/layout/Header";
|
|||||||
import { Footer } from "@/components/layout/Footer";
|
import { Footer } from "@/components/layout/Footer";
|
||||||
import { getNavCategories } from "@/lib/categories";
|
import { getNavCategories } from "@/lib/categories";
|
||||||
import { getLogoSrc } from "@/lib/media";
|
import { getLogoSrc } from "@/lib/media";
|
||||||
|
import { getBusinessInfo, metaTagsToOther } from "@/lib/meshkee";
|
||||||
|
|
||||||
const vazir = Vazirmatn({
|
const vazir = Vazirmatn({
|
||||||
subsets: ["arabic", "latin"],
|
subsets: ["arabic", "latin"],
|
||||||
@@ -14,14 +15,24 @@ const vazir = Vazirmatn({
|
|||||||
weight: ["300", "400", "500", "600", "700", "800"],
|
weight: ["300", "400", "500", "600", "700", "800"],
|
||||||
});
|
});
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export async function generateMetadata(): Promise<Metadata> {
|
||||||
title: "داروخانه دکتر ارجمند | صفحه اصلی",
|
const businessInfo = await getBusinessInfo();
|
||||||
description:
|
const other = metaTagsToOther(businessInfo?.metaTags);
|
||||||
"خرید آنلاین دارو، مکمل، محصولات بهداشتی و آرایشی از داروخانه دکتر ارجمند در قم، سالاریه.",
|
|
||||||
};
|
return {
|
||||||
|
title: "داروخانه دکتر ارجمند | صفحه اصلی",
|
||||||
|
description:
|
||||||
|
"خرید آنلاین دارو، مکمل، محصولات بهداشتی و آرایشی از داروخانه دکتر ارجمند در قم، سالاریه.",
|
||||||
|
...(other ? { other } : {}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
||||||
const [logoSrc, categories] = await Promise.all([Promise.resolve(getLogoSrc()), getNavCategories()]);
|
const [logoSrc, categories, businessInfo] = await Promise.all([
|
||||||
|
Promise.resolve(getLogoSrc()),
|
||||||
|
getNavCategories(),
|
||||||
|
getBusinessInfo(),
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang="fa" dir="rtl" className={vazir.variable}>
|
<html lang="fa" dir="rtl" className={vazir.variable}>
|
||||||
@@ -37,7 +48,7 @@ export default async function RootLayout({ children }: { children: React.ReactNo
|
|||||||
<TopBar />
|
<TopBar />
|
||||||
<Header logoSrc={logoSrc} categories={categories} />
|
<Header logoSrc={logoSrc} categories={categories} />
|
||||||
<main className="pb-12">{children}</main>
|
<main className="pb-12">{children}</main>
|
||||||
<Footer logoSrc={logoSrc} />
|
<Footer logoSrc={logoSrc} trustBadges={businessInfo?.trustBadges} />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,8 +3,16 @@ import { Instagram, MapPin, Phone, Send } from "lucide-react";
|
|||||||
import { Container } from "@/components/Container";
|
import { Container } from "@/components/Container";
|
||||||
import { Placeholder } from "@/components/Placeholder";
|
import { Placeholder } from "@/components/Placeholder";
|
||||||
import { footerLinks, site } from "@/lib/data";
|
import { footerLinks, site } from "@/lib/data";
|
||||||
|
import type { BusinessTrustBadge } from "@/lib/meshkee";
|
||||||
|
|
||||||
export function Footer({ logoSrc }: { logoSrc: string }) {
|
export function Footer({
|
||||||
|
logoSrc,
|
||||||
|
trustBadges = [],
|
||||||
|
}: {
|
||||||
|
logoSrc: string;
|
||||||
|
trustBadges?: BusinessTrustBadge[];
|
||||||
|
}) {
|
||||||
|
const badges = trustBadges.filter((badge) => badge.embedHtml?.trim());
|
||||||
return (
|
return (
|
||||||
<footer id="contact" className="mt-6 bg-navy-deep text-white">
|
<footer id="contact" className="mt-6 bg-navy-deep text-white">
|
||||||
<Container className="grid gap-10 py-12 sm:grid-cols-2 lg:grid-cols-4">
|
<Container className="grid gap-10 py-12 sm:grid-cols-2 lg:grid-cols-4">
|
||||||
@@ -60,14 +68,17 @@ export function Footer({ logoSrc }: { logoSrc: string }) {
|
|||||||
<Phone className="size-4" />
|
<Phone className="size-4" />
|
||||||
{site.phone}
|
{site.phone}
|
||||||
</a>
|
</a>
|
||||||
<div className="mt-5 flex gap-3">
|
{badges.length > 0 ? (
|
||||||
<span className="overflow-hidden rounded-lg bg-white">
|
<div className="mt-5 flex flex-wrap items-center gap-3">
|
||||||
<Placeholder src="/placeholders/enamad.svg" className="size-16 object-cover" />
|
{badges.map((badge) => (
|
||||||
</span>
|
<div
|
||||||
<span className="overflow-hidden rounded-lg bg-white">
|
key={badge.kind}
|
||||||
<Placeholder src="/placeholders/samandehi.svg" className="size-16 object-cover" />
|
className="trust-badge overflow-hidden rounded-lg bg-white p-1 [&_img]:block [&_img]:h-16 [&_img]:w-auto"
|
||||||
</span>
|
dangerouslySetInnerHTML={{ __html: badge.embedHtml }}
|
||||||
</div>
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</Container>
|
</Container>
|
||||||
<div className="border-t border-white/10">
|
<div className="border-t border-white/10">
|
||||||
|
|||||||
@@ -26,3 +26,32 @@ export async function getBusinessId() {
|
|||||||
const tenant = await getTenant();
|
const tenant = await getTenant();
|
||||||
return tenant?.id != null ? String(tenant.id) : "";
|
return tenant?.id != null ? String(tenant.id) : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type BusinessMetaTag = {
|
||||||
|
name: string;
|
||||||
|
content: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type BusinessTrustBadge = {
|
||||||
|
kind: "enamad" | "samandehi";
|
||||||
|
embedHtml: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type BusinessInfo = {
|
||||||
|
metaTags?: BusinessMetaTag[];
|
||||||
|
trustBadges?: BusinessTrustBadge[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function getBusinessInfo() {
|
||||||
|
return meshkeeGet<BusinessInfo | null>(tenantPath("/website/business-info"), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function metaTagsToOther(tags: BusinessMetaTag[] | undefined) {
|
||||||
|
const other: Record<string, string> = {};
|
||||||
|
for (const tag of tags ?? []) {
|
||||||
|
const name = tag.name?.trim();
|
||||||
|
const content = tag.content?.trim();
|
||||||
|
if (name && content) other[name] = content;
|
||||||
|
}
|
||||||
|
return Object.keys(other).length > 0 ? other : undefined;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="90" height="90" viewBox="0 0 90 90">
|
|
||||||
<rect width="90" height="90" fill="#F3F7FF"/>
|
|
||||||
<rect x="34.1" y="36.9" width="21.9" height="16.2" rx="6.0" fill="none" stroke="#2261FE" stroke-width="2.0" opacity="0.35"/>
|
|
||||||
<circle cx="45.0" cy="46.0" r="2.9" fill="none" stroke="#2261FE" stroke-width="2.0" opacity="0.35"/>
|
|
||||||
<circle cx="38.9" cy="41.4" r="1.0" fill="#2261FE" opacity="0.28"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 439 B |
@@ -1,6 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="90" height="90" viewBox="0 0 90 90">
|
|
||||||
<rect width="90" height="90" fill="#F3F7FF"/>
|
|
||||||
<rect x="34.1" y="36.9" width="21.9" height="16.2" rx="6.0" fill="none" stroke="#2261FE" stroke-width="2.0" opacity="0.35"/>
|
|
||||||
<circle cx="45.0" cy="46.0" r="2.9" fill="none" stroke="#2261FE" stroke-width="2.0" opacity="0.35"/>
|
|
||||||
<circle cx="38.9" cy="41.4" r="1.0" fill="#2261FE" opacity="0.28"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 439 B |
Reference in New Issue
Block a user