diff --git a/docs/website-api/AI_PROMPT.md b/docs/website-api/AI_PROMPT.md index c0581d8..aced2b2 100644 --- a/docs/website-api/AI_PROMPT.md +++ b/docs/website-api/AI_PROMPT.md @@ -331,6 +331,24 @@ These are **required on every crawlable page** (home, listing, product/blog/port **Open Graph (recommended)** - Set `og:title`, `og:description`, and `og:image` on important pages (home + detail pages) from CMS media when available. +### Site-wide meta tags + trust badges (eNamad / Samandehi) + +Business admins configure this under **Website → Tags & Badges**. + +1. `GET /tenants/{domain}/website/business-info` +2. For each item in `metaTags` (array of `{ name, content }`), emit once in the root layout `
`: + +```html + +``` + +In Next.js App Router, map them into `generateMetadata()` → `other: { [name]: content }`. Verification metas (eNamad, Google, etc.) belong here as custom name/value rows. + +3. For each item in `trustBadges` (array of `{ kind, embedHtml }`), render `embedHtml` in the site footer (e.g. `dangerouslySetInnerHTML`). These are HTML widgets only (not meta). Scripts are stripped by the API — still treat the HTML as CMS-controlled content. + +- When arrays are empty, omit tags / footer badges. +- Do **not** hardcode eNamad/Samandehi HTML in the website repo when these fields are present. + ### Site-wide Schema.org JSON-LD (Organization / LocalBusiness) Business admins configure this under **Website → Settings → Structured data**. The API builds a ready `jsonLd` object for you. @@ -398,6 +416,7 @@ Website rules: 4. Public URL is included via CMS sitemap and/or `sitemap-config.json` static pages 5. Site-wide Organization/LocalBusiness JSON-LD from `business-info.schema.jsonLd` when enabled 6. Product/blog detail pages emit `product.schema.jsonLd` / `blog.schema.jsonLd` when present +7. Site-wide `metaTags` + footer `trustBadges` from business-info when present If OpenAPI and this brief conflict, **OpenAPI wins**. diff --git a/docs/website-api/openapi.json b/docs/website-api/openapi.json index 78b1746..78687eb 100644 --- a/docs/website-api/openapi.json +++ b/docs/website-api/openapi.json @@ -567,6 +567,36 @@ } }, "required": ["enabled", "type", "sameAs", "jsonLd"] + }, + "metaTags": { + "type": "array", + "description": "Enabled custom meta tags for the site (name/content). Configured under Website → Tags & Badges.", + "items": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "content": { "type": "string" } + }, + "required": ["name", "content"] + } + }, + "trustBadges": { + "type": "array", + "description": "Enabled eNamad / Samandehi footer widgets (sanitized HTML). Empty when none are shown.", + "items": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "enum": ["enamad", "samandehi"] + }, + "embedHtml": { + "type": "string", + "description": "Trusted HTML snippet (scripts stripped server-side)." + } + }, + "required": ["kind", "embedHtml"] + } } } } diff --git a/src/business-settings/business-settings.service.ts b/src/business-settings/business-settings.service.ts index ddeba6e..2eb8a72 100644 --- a/src/business-settings/business-settings.service.ts +++ b/src/business-settings/business-settings.service.ts @@ -18,6 +18,7 @@ import { toPrismaJson, } from './business-settings.util'; import { normalizeWebsiteSchemaSettings } from './website-schema.util'; +import { normalizeWebsiteTagsAndBadges } from './website-tags-badges.util'; import { UpdateBusinessSettingsDto } from './dto/update-business-settings.dto'; import { normalizeBusinessPrimaryColorId } from './business-primary-colors'; @@ -217,6 +218,31 @@ export class BusinessSettingsService { : current.website.schema.sameAs, }) : current.website.schema, + tagsAndBadges: + dto.website.tagsAndBadges !== undefined + ? normalizeWebsiteTagsAndBadges({ + ...current.website.tagsAndBadges, + ...dto.website.tagsAndBadges, + metaTags: + dto.website.tagsAndBadges.metaTags !== undefined + ? dto.website.tagsAndBadges.metaTags + : current.website.tagsAndBadges.metaTags, + enamad: + dto.website.tagsAndBadges.enamad !== undefined + ? { + ...current.website.tagsAndBadges.enamad, + ...dto.website.tagsAndBadges.enamad, + } + : current.website.tagsAndBadges.enamad, + samandehi: + dto.website.tagsAndBadges.samandehi !== undefined + ? { + ...current.website.tagsAndBadges.samandehi, + ...dto.website.tagsAndBadges.samandehi, + } + : current.website.tagsAndBadges.samandehi, + }) + : current.website.tagsAndBadges, }; } diff --git a/src/business-settings/business-settings.types.ts b/src/business-settings/business-settings.types.ts index 4ad8553..5a677ec 100644 --- a/src/business-settings/business-settings.types.ts +++ b/src/business-settings/business-settings.types.ts @@ -127,6 +127,37 @@ export type WebsiteSettings = { sitemapConfig?: WebsiteSitemapConfig | null; /** Site-wide Schema.org Organization / LocalBusiness settings (Phase A). */ schema: WebsiteSchemaSettings; + /** Custom tags + eNamad / Samandehi trust badges. */ + tagsAndBadges: WebsiteTagsAndBadgesSettings; +}; + +/** Custom head meta tag (name + content). Present = published. */ +export type WebsiteMetaTag = { + id: string; + name: string; + content: string; +}; + +/** Fixed trust integrations (eNamad / Samandehi) — footer HTML only. */ +export type WebsiteTrustBadgeSettings = { + /** Footer widget HTML; empty = not shown. */ + embedHtml: string; +}; + +export type WebsiteTagsAndBadgesSettings = { + metaTags: WebsiteMetaTag[]; + enamad: WebsiteTrustBadgeSettings; + samandehi: WebsiteTrustBadgeSettings; +}; + +export const DEFAULT_WEBSITE_TRUST_BADGE: WebsiteTrustBadgeSettings = { + embedHtml: '', +}; + +export const DEFAULT_WEBSITE_TAGS_AND_BADGES: WebsiteTagsAndBadgesSettings = { + metaTags: [], + enamad: { ...DEFAULT_WEBSITE_TRUST_BADGE }, + samandehi: { ...DEFAULT_WEBSITE_TRUST_BADGE }, }; /** Schema.org @type for the site-wide entity JSON-LD. */ @@ -381,6 +412,11 @@ export const DEFAULT_BUSINESS_SETTINGS: BusinessSettings = { website: { specialProductsSource: 'store_item', schema: { ...DEFAULT_WEBSITE_SCHEMA_SETTINGS }, + tagsAndBadges: { + metaTags: [], + enamad: { ...DEFAULT_WEBSITE_TRUST_BADGE }, + samandehi: { ...DEFAULT_WEBSITE_TRUST_BADGE }, + }, }, sms: { senderNumber: null, diff --git a/src/business-settings/business-settings.util.ts b/src/business-settings/business-settings.util.ts index e83c1dc..2927622 100644 --- a/src/business-settings/business-settings.util.ts +++ b/src/business-settings/business-settings.util.ts @@ -34,12 +34,14 @@ import { WebsiteSitemapConfig, ZarinpalGatewaySettings, DEFAULT_WEBSITE_SCHEMA_SETTINGS, + DEFAULT_WEBSITE_TAGS_AND_BADGES, } from './business-settings.types'; import { defaultOrderStepColor, normalizeOrderStepColor, } from './order-step-colors'; import { normalizeWebsiteSchemaSettings } from './website-schema.util'; +import { normalizeWebsiteTagsAndBadges } from './website-tags-badges.util'; function isRecord(value: unknown): value is Record