From 76d44345810017000cda062c1bc2b824156ce8c2 Mon Sep 17 00:00:00 2001 From: "soroush.asadi" Date: Mon, 15 Jun 2026 15:08:48 +0330 Subject: [PATCH] =?UTF-8?q?fix(qr):=20guest=20menu=20500=20(SSR)=20+=20rem?= =?UTF-8?q?ove=20caf=C3=A9=20discovery=20from=20owner=20panel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. The /q/{code} guest menu returned HTTP 500 on every load. Root cause: menu-item-model-viewer.tsx did a top-level `import "@google/model-viewer"`, a browser-only lib that touches `self` at module evaluation. Next pulled it into the server module graph (page → qr-guest-menu → qr-menu-3d-sheet → model-viewer) and SSR crashed with "self is not defined". Now the library is imported lazily inside useEffect (client-only); a poster placeholder shows until the custom element registers. Verified /q/* now returns 200. 2. Removed the "discover" (browse other cafés) item from the café owner sidebar — café discovery belongs in Koja, not the owner panel. The owner still manages their OWN Koja listing from Settings. Co-Authored-By: Claude Opus 4.8 --- .../menu/menu-item-model-viewer.tsx | 46 ++++++++++++++++++- web/dashboard/src/lib/sidebar-nav.ts | 5 +- 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/web/dashboard/src/components/menu/menu-item-model-viewer.tsx b/web/dashboard/src/components/menu/menu-item-model-viewer.tsx index 4ce5660..3a1786e 100644 --- a/web/dashboard/src/components/menu/menu-item-model-viewer.tsx +++ b/web/dashboard/src/components/menu/menu-item-model-viewer.tsx @@ -1,6 +1,6 @@ "use client"; -import "@google/model-viewer"; +import { useEffect, useState } from "react"; import { resolveMediaUrl } from "@/lib/api/client"; type MenuItemModelViewerProps = { @@ -10,16 +10,60 @@ type MenuItemModelViewerProps = { className?: string; }; +// `@google/model-viewer` references browser globals (`self`) at module load, so a +// top-level `import` crashes server-side rendering (e.g. the /q guest menu). +// Load it lazily on the client only, once, the first time a viewer mounts. +let modelViewerLoad: Promise | null = null; +function ensureModelViewerLoaded(): Promise { + if (typeof window === "undefined") return Promise.resolve(); + modelViewerLoad ??= import("@google/model-viewer"); + return modelViewerLoad; +} + export function MenuItemModelViewer({ modelUrl, posterUrl, alt, className, }: MenuItemModelViewerProps) { + const [ready, setReady] = useState(false); + + useEffect(() => { + let active = true; + void ensureModelViewerLoaded().then(() => { + if (active) setReady(true); + }); + return () => { + active = false; + }; + }, []); + const src = resolveMediaUrl(modelUrl); const poster = posterUrl ? resolveMediaUrl(posterUrl) : undefined; if (!src) return null; + // Until the custom element is registered, show the poster (or an empty box) so + // layout is stable and nothing references the browser-only library on the server. + if (!ready) { + return ( +
+ ); + } + return ( // @ts-expect-error model-viewer is a custom element from @google/model-viewer