feat(web): public menu language pill selector; fix missing cover_url column

Public menu page now shows a TR|EN|... pill selector (styled after the
Menulio design reference) that switches category/item text to the AI
translations added via the mobile app, falling back to Turkish for
anything not yet translated.

Also adds a migration for restaurants.cover_url, which was referenced by
existing code but was missing from the live database — the public menu
page was silently forcing cover photos to null on every restaurant.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
AyrisAI
2026-08-29 01:55:26 +03:00
co-authored by Claude Sonnet 5
parent 6ec4631e82
commit df250c40e8
3 changed files with 102 additions and 18 deletions
+12 -5
View File
@@ -17,6 +17,7 @@ interface MenuItemRow {
price: number;
image_url: string | null;
is_active: boolean;
translations: Record<string, { name: string; description: string | null }> | null;
}
interface MenuCategoryRow {
@@ -25,6 +26,7 @@ interface MenuCategoryRow {
description: string | null;
is_active: boolean;
menu_items: MenuItemRow[];
translations: Record<string, { name: string }> | null;
}
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
@@ -71,14 +73,14 @@ export async function generateMetadata({ params }: PageProps): Promise<Metadata>
async function fetchRestaurantBySlug(slug: string) {
let { data: bySlug, error } = await supabase
.from("restaurants")
.select("id, name, slug, logo_url, cover_url, phone, address")
.select("id, name, slug, logo_url, cover_url, phone, address, enabled_languages")
.eq("slug", slug)
.maybeSingle();
if (error && error.code === "42703") {
const { data: fallback } = await supabase
.from("restaurants")
.select("id, name, slug, logo_url, phone, address")
.select("id, name, slug, logo_url, phone, address, enabled_languages")
.eq("slug", slug)
.maybeSingle();
if (fallback) {
@@ -90,14 +92,14 @@ async function fetchRestaurantBySlug(slug: string) {
let { data: domainRow, error: domErr } = await supabase
.from("domains")
.select("restaurant_id, restaurants(id, name, slug, logo_url, cover_url, phone, address)")
.select("restaurant_id, restaurants(id, name, slug, logo_url, cover_url, phone, address, enabled_languages)")
.eq("hostname", slug)
.maybeSingle();
if (domErr && domErr.code === "42703") {
const { data: fallbackDom } = await supabase
.from("domains")
.select("restaurant_id, restaurants(id, name, slug, logo_url, phone, address)")
.select("restaurant_id, restaurants(id, name, slug, logo_url, phone, address, enabled_languages)")
.eq("hostname", slug)
.maybeSingle();
if (fallbackDom?.restaurants) {
@@ -150,7 +152,9 @@ export default async function PublicMenuPage({ params, searchParams }: PageProps
if (menu) {
const { data: rawCategories } = await supabase
.from("menu_categories")
.select("id, name, description, is_active, menu_items(id, name, description, price, image_url, is_active)")
.select(
"id, name, description, is_active, translations, menu_items(id, name, description, price, image_url, is_active, translations)",
)
.eq("menu_id", menu.id)
.eq("is_active", true)
.order("sort_order", { ascending: true })
@@ -161,6 +165,7 @@ export default async function PublicMenuPage({ params, searchParams }: PageProps
name: cat.name,
description: cat.description,
is_active: cat.is_active,
translations: cat.translations ?? {},
menu_items: (cat.menu_items ?? [])
.filter((item) => item.is_active)
.map((item): MenuItem => ({
@@ -170,6 +175,7 @@ export default async function PublicMenuPage({ params, searchParams }: PageProps
price: item.price,
image_url: item.image_url,
is_active: item.is_active,
translations: item.translations ?? {},
})),
}));
} else if (slug === "demo" || slug === "gusto-brasserie") {
@@ -202,6 +208,7 @@ export default async function PublicMenuPage({ params, searchParams }: PageProps
categories={categories}
initialThemeKey={themeKey}
customThemeConfig={customConfig}
enabledLanguages={restaurant.enabled_languages ?? ["tr"]}
/>
);
}