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.
|
||||
|
||||
## 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
|
||||
|
||||
- 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 { getNavCategories } from "@/lib/categories";
|
||||
import { getLogoSrc } from "@/lib/media";
|
||||
import { getBusinessInfo, metaTagsToOther } from "@/lib/meshkee";
|
||||
|
||||
const vazir = Vazirmatn({
|
||||
subsets: ["arabic", "latin"],
|
||||
@@ -14,14 +15,24 @@ const vazir = Vazirmatn({
|
||||
weight: ["300", "400", "500", "600", "700", "800"],
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "داروخانه دکتر ارجمند | صفحه اصلی",
|
||||
description:
|
||||
"خرید آنلاین دارو، مکمل، محصولات بهداشتی و آرایشی از داروخانه دکتر ارجمند در قم، سالاریه.",
|
||||
};
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
const businessInfo = await getBusinessInfo();
|
||||
const other = metaTagsToOther(businessInfo?.metaTags);
|
||||
|
||||
return {
|
||||
title: "داروخانه دکتر ارجمند | صفحه اصلی",
|
||||
description:
|
||||
"خرید آنلاین دارو، مکمل، محصولات بهداشتی و آرایشی از داروخانه دکتر ارجمند در قم، سالاریه.",
|
||||
...(other ? { other } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
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 (
|
||||
<html lang="fa" dir="rtl" className={vazir.variable}>
|
||||
@@ -37,7 +48,7 @@ export default async function RootLayout({ children }: { children: React.ReactNo
|
||||
<TopBar />
|
||||
<Header logoSrc={logoSrc} categories={categories} />
|
||||
<main className="pb-12">{children}</main>
|
||||
<Footer logoSrc={logoSrc} />
|
||||
<Footer logoSrc={logoSrc} trustBadges={businessInfo?.trustBadges} />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
@@ -3,8 +3,16 @@ import { Instagram, MapPin, Phone, Send } from "lucide-react";
|
||||
import { Container } from "@/components/Container";
|
||||
import { Placeholder } from "@/components/Placeholder";
|
||||
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 (
|
||||
<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">
|
||||
@@ -60,14 +68,17 @@ export function Footer({ logoSrc }: { logoSrc: string }) {
|
||||
<Phone className="size-4" />
|
||||
{site.phone}
|
||||
</a>
|
||||
<div className="mt-5 flex gap-3">
|
||||
<span className="overflow-hidden rounded-lg bg-white">
|
||||
<Placeholder src="/placeholders/enamad.svg" className="size-16 object-cover" />
|
||||
</span>
|
||||
<span className="overflow-hidden rounded-lg bg-white">
|
||||
<Placeholder src="/placeholders/samandehi.svg" className="size-16 object-cover" />
|
||||
</span>
|
||||
</div>
|
||||
{badges.length > 0 ? (
|
||||
<div className="mt-5 flex flex-wrap items-center gap-3">
|
||||
{badges.map((badge) => (
|
||||
<div
|
||||
key={badge.kind}
|
||||
className="trust-badge overflow-hidden rounded-lg bg-white p-1 [&_img]:block [&_img]:h-16 [&_img]:w-auto"
|
||||
dangerouslySetInnerHTML={{ __html: badge.embedHtml }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</Container>
|
||||
<div className="border-t border-white/10">
|
||||
|
||||
@@ -26,3 +26,32 @@ export async function getBusinessId() {
|
||||
const tenant = await getTenant();
|
||||
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