This commit is contained in:
payacom
2026-08-05 15:12:39 +03:30
parent 89320d57b9
commit 491040f752
27 changed files with 1732 additions and 32 deletions

View File

@@ -1,5 +1,5 @@
export const UPSTREAM_API =
process.env.UPSTREAM_API_URL ?? "https://api.modstagram.ir";
process.env.UPSTREAM_API_URL ?? "https://api.modstagram.com";
type ProxyOptions = {
stripAuth?: boolean;

152
src/lib/buildShopSeo.ts Normal file
View File

@@ -0,0 +1,152 @@
import { SITE_URL } from "@/config/pageSeo";
import { buildStorageUrl } from "@/components/main/BaseUrl";
import { getSiteSeoMeta } from "@/lib/i18n/seo";
import { DEFAULT_LANGUAGE, type AppLanguage } from "@/lib/i18n/registry";
import { IShopProductListing } from "@/types/types";
type ShopInfo = {
_id: string;
name: string;
logo?: string | null;
description?: string | null;
address?: string | null;
province?: { name: string } | null;
city?: { name: string } | null;
has_physical_location?: boolean;
rating?: { average: number; count: number } | null;
};
function cheapestAndDearest(listing: IShopProductListing) {
const prices = (listing.variants || []).map((v) =>
v.discount_price != null ? v.discount_price : v.price
);
if (!prices.length) return { low: null, high: null };
return { low: Math.min(...prices), high: Math.max(...prices) };
}
function totalStock(listing: IShopProductListing): number {
return (listing.variants || []).reduce((sum, v) => sum + (v.stock || 0), 0);
}
/** Product + Offer/AggregateOffer JSON-LD for a shop product listing page. */
export function buildProductJsonLd(
listing: IShopProductListing,
shop: ShopInfo | null,
url: string,
lang: AppLanguage = DEFAULT_LANGUAGE
) {
const siteName = getSiteSeoMeta(lang).name;
const images = (listing.images || []).map((img) => buildStorageUrl(img)).filter(Boolean);
const { low, high } = cheapestAndDearest(listing);
const inStock = totalStock(listing) > 0;
const skus = Array.from(
new Set((listing.variants || []).map((v) => v.sku).filter(Boolean))
);
const offerBase = {
priceCurrency: "IRR",
availability: inStock
? "https://schema.org/InStock"
: "https://schema.org/OutOfStock",
url,
seller: shop
? { "@type": "Organization", name: shop.name }
: undefined,
};
const offers =
low != null && high != null
? low === high
? { "@type": "Offer", ...offerBase, price: low }
: {
"@type": "AggregateOffer",
...offerBase,
lowPrice: low,
highPrice: high,
offerCount: listing.variants?.length || 1,
}
: undefined;
return {
"@context": "https://schema.org",
"@type": "Product",
name: listing.title,
description: listing.description || undefined,
image: images.length ? images : undefined,
sku: skus.length === 1 ? skus[0] : undefined,
category: listing.category || undefined,
brand: shop ? { "@type": "Brand", name: shop.name } : undefined,
offers,
url,
breadcrumb: {
"@type": "BreadcrumbList",
itemListElement: [
{ "@type": "ListItem", position: 1, name: siteName, item: SITE_URL },
{ "@type": "ListItem", position: 2, name: "فروشگاه‌ها", item: `${SITE_URL}/shops/discover` },
...(shop
? [
{
"@type": "ListItem",
position: 3,
name: shop.name,
item: `${SITE_URL}/shops/profile/${shop._id}`,
},
]
: []),
{
"@type": "ListItem",
position: shop ? 4 : 3,
name: listing.title,
item: url,
},
],
},
};
}
/** LocalBusiness/Store JSON-LD for a shop's public profile page. */
export function buildShopJsonLd(
shop: ShopInfo,
url: string,
lang: AppLanguage = DEFAULT_LANGUAGE
) {
const siteName = getSiteSeoMeta(lang).name;
const address =
shop.province?.name || shop.city?.name || shop.address
? {
"@type": "PostalAddress",
addressRegion: shop.province?.name || undefined,
addressLocality: shop.city?.name || undefined,
streetAddress: shop.address || undefined,
addressCountry: "IR",
}
: undefined;
const aggregateRating =
shop.rating && shop.rating.count > 0
? {
"@type": "AggregateRating",
ratingValue: shop.rating.average,
reviewCount: shop.rating.count,
}
: undefined;
return {
"@context": "https://schema.org",
"@type": shop.has_physical_location ? "LocalBusiness" : "OnlineStore",
name: shop.name,
description: shop.description || undefined,
image: shop.logo ? buildStorageUrl(shop.logo) : undefined,
url,
address,
aggregateRating,
breadcrumb: {
"@type": "BreadcrumbList",
itemListElement: [
{ "@type": "ListItem", position: 1, name: siteName, item: SITE_URL },
{ "@type": "ListItem", position: 2, name: "فروشگاه‌ها", item: `${SITE_URL}/shops/discover` },
{ "@type": "ListItem", position: 3, name: shop.name, item: url },
],
},
};
}