Files
meezi/web/koja/src/components/seo/cafe-json-ld.tsx
T
soroush.asadi e839db7331 fix(koja): default to fa (no browser locale guess); guard null discoverProfile
Koja auto-detected locale from the browser Accept-Language (en for many Persian users); set localeDetection:false so locale-less URLs default to fa. Also guarded cafe.discoverProfile across the cafe page, cafe card, and JSON-LD — a café without a discover profile crashed the page (500). The cafe page now resolves the café first and notFound()s an unknown slug before fetching menu/reviews.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 01:51:50 +03:30

87 lines
3.1 KiB
TypeScript

import type { CafePublicDto } from "@/lib/types";
interface Props {
cafe: CafePublicDto;
locale: string;
baseUrl: string;
}
export function CafeJsonLd({ cafe, locale, baseUrl }: Props) {
const name = locale === "en" && cafe.nameEn ? cafe.nameEn : cafe.name;
const openingHours: string[] = [];
if (cafe.workingHours) {
const days = cafe.workingHours;
const dayMap: [string, keyof typeof days][] = [
["Sa", "sat"], ["Su", "sun"], ["Mo", "mon"],
["Tu", "tue"], ["We", "wed"], ["Th", "thu"], ["Fr", "fri"],
];
for (const [abbr, key] of dayMap) {
const d = days[key];
if (d?.isOpen && d.open && d.close) {
openingHours.push(`${abbr} ${d.open}-${d.close}`);
}
}
}
const schema: Record<string, unknown> = {
"@context": "https://schema.org",
"@type": "CafeOrCoffeeShop",
name,
description: cafe.description ?? undefined,
url: `${baseUrl}/${locale}/cafe/${cafe.slug}`,
...(cafe.logoUrl ? { logo: cafe.logoUrl } : {}),
...(cafe.coverImageUrl ? { image: [cafe.coverImageUrl, ...(cafe.galleryUrls ?? [])] } : {}),
...(cafe.address ? { address: { "@type": "PostalAddress", streetAddress: cafe.address, addressLocality: cafe.city ?? undefined, addressCountry: "IR" } } : {}),
...(cafe.phone ? { telephone: cafe.phone } : {}),
...(openingHours.length ? { openingHours } : {}),
...(cafe.instagramHandle ? { sameAs: [`https://instagram.com/${cafe.instagramHandle}`] } : {}),
...(cafe.websiteUrl ? { url: cafe.websiteUrl } : {}),
...(cafe.reviewCount > 0 ? {
aggregateRating: {
"@type": "AggregateRating",
ratingValue: cafe.averageRating.toFixed(1),
reviewCount: cafe.reviewCount,
bestRating: "5",
worstRating: "1",
},
} : {}),
...(cafe.discoverProfile?.themes?.length ? {
servesCuisine: cafe.discoverProfile.themes,
} : {}),
priceRange: (() => {
const tier = cafe.discoverProfile?.priceTier;
if (tier === "budget") return "﷼";
if (tier === "moderate") return "﷼﷼";
if (tier === "upscale") return "﷼﷼﷼";
if (tier === "luxury") return "﷼﷼﷼﷼";
return undefined;
})(),
};
// BreadcrumbList
const breadcrumb = {
"@context": "https://schema.org",
"@type": "BreadcrumbList",
itemListElement: [
{ "@type": "ListItem", position: 1, name: locale === "fa" ? "خانه" : "Home", item: `${baseUrl}/${locale}` },
{ "@type": "ListItem", position: 2, name: locale === "fa" ? "جستجو" : "Search", item: `${baseUrl}/${locale}/search` },
...(cafe.city ? [{ "@type": "ListItem", position: 3, name: cafe.city, item: `${baseUrl}/${locale}/city/${cafe.city.toLowerCase()}` }] : []),
{ "@type": "ListItem", position: cafe.city ? 4 : 3, name, item: `${baseUrl}/${locale}/cafe/${cafe.slug}` },
],
};
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumb) }}
/>
</>
);
}