diff --git a/src/app/[name]/(Main)/layout.tsx b/src/app/[name]/(Main)/layout.tsx index 41cfd61..dbeed5f 100644 --- a/src/app/[name]/(Main)/layout.tsx +++ b/src/app/[name]/(Main)/layout.tsx @@ -1,10 +1,6 @@ import ClientMenuRouteWrapper from "@/components/wrapper/ClientMenuRouteWrapper"; import ClientSideWrapper from "@/components/wrapper/ClientSideWrapper"; -export const metadata = { - title: 'Menu', -} - export default function MenuLayout({ children, }: Readonly<{ diff --git a/src/app/[name]/layout.tsx b/src/app/[name]/layout.tsx index 9053b06..9af68f6 100644 --- a/src/app/[name]/layout.tsx +++ b/src/app/[name]/layout.tsx @@ -1,33 +1,95 @@ import PreferenceWrapper from '@/components/wrapper/PreferenceWrapper' import React from 'react' import type { Metadata, Viewport } from 'next' +import { getRestaurant } from './lib/getRestaurant' +import { notFound } from 'next/navigation' + +export const dynamic = 'force-dynamic' +export const revalidate = 0 type LayoutParams = { name: string } -export async function generateMetadata ({ +export async function generateMetadata({ params }: { params: Promise }): Promise { const { name } = await params - return { - title: `${name} Dashboard`, - description: `PWA dashboard for ${name}`, - manifest: `/manifests/${name}/manifest.json` + try { + const restaurant = await getRestaurant(name) + + const favicon = restaurant.logo || "/icons/web-app-manifest-192x192.png" + const title = restaurant.seoTitle || restaurant.name + + const defaultIcon = "/icons/web-app-manifest-192x192.png" + const iconUrl = favicon || defaultIcon + + return { + title, + description: restaurant.seoDescription || restaurant.description || `منوی ${restaurant.name}`, + manifest: `/${name}/manifest.webmanifest`, + icons: { + icon: [ + { url: iconUrl }, + { url: iconUrl, sizes: "192x192", type: "image/png" }, + { url: iconUrl, sizes: "512x512", type: "image/png" }, + ], + shortcut: iconUrl, + apple: iconUrl, + }, + } + } catch { + return { + title: `${name} - Dashboard`, + description: `PWA dashboard for ${name}`, + manifest: `/${name}/manifest.webmanifest`, + icons: { + icon: [ + { url: "/icons/web-app-manifest-192x192.png" }, + { url: "/icons/web-app-manifest-192x192.png", sizes: "192x192", type: "image/png" }, + { url: "/icons/web-app-manifest-512x512.png", sizes: "512x512", type: "image/png" }, + ], + shortcut: "/icons/web-app-manifest-192x192.png", + apple: "/icons/web-app-manifest-192x192.png", + }, + } } } -export async function generateViewport (): Promise { - return { - themeColor: '#F4F5F9' +export async function generateViewport({ + params +}: { + params: Promise +}): Promise { + try { + const { name } = await params + const restaurant = await getRestaurant(name) + + return { + themeColor: restaurant.menuColor || '#F4F5F9' + } + } catch { + return { + themeColor: '#F4F5F9' + } } } -export default function Layout ({ - children +export default async function Layout({ + children, + params }: { children: React.ReactNode params: Promise -}): React.ReactNode { - return {children} +}): Promise { + try { + const { name } = await params + await getRestaurant(name) + return {children} + } catch (error) { + if (error instanceof Error && error.message === 'RESTAURANT_NOT_FOUND') { + notFound() + } + throw error + } } diff --git a/src/app/[name]/lib/getRestaurant.ts b/src/app/[name]/lib/getRestaurant.ts new file mode 100644 index 0000000..0ddf458 --- /dev/null +++ b/src/app/[name]/lib/getRestaurant.ts @@ -0,0 +1,37 @@ +import { API_BASE_URL } from "@/config/const"; +import { AboutResponse, Restaurant } from "./(Main)/about/types/Types"; + +export async function getRestaurant(slug: string): Promise { + try { + const response = await fetch( + `${API_BASE_URL}/public/restaurants/${slug}`, + { + cache: "no-store", + headers: { + "Content-Type": "application/json", + }, + } + ); + + if (!response.ok) { + if (response.status === 404) { + throw new Error("RESTAURANT_NOT_FOUND"); + } + throw new Error(`Failed to fetch restaurant: ${response.status}`); + } + + const data: AboutResponse = await response.json(); + + if (!data.success || !data.data) { + throw new Error("RESTAURANT_NOT_FOUND"); + } + + return data.data; + } catch (error) { + if (error instanceof Error && error.message === "RESTAURANT_NOT_FOUND") { + throw error; + } + throw new Error(`Failed to fetch restaurant data: ${error}`); + } +} + diff --git a/src/app/[name]/manifest.webmanifest/route.ts b/src/app/[name]/manifest.webmanifest/route.ts new file mode 100644 index 0000000..dc63fbb --- /dev/null +++ b/src/app/[name]/manifest.webmanifest/route.ts @@ -0,0 +1,84 @@ +import { NextResponse } from "next/server"; +import { getRestaurant } from "../lib/getRestaurant"; + +export const dynamic = "force-dynamic"; +export const revalidate = 0; + +export async function GET( + request: Request, + { params }: { params: Promise<{ name: string }> } +) { + try { + const { name } = await params; + const restaurant = await getRestaurant(name); + + const themeColor = restaurant.menuColor || "#F4F5F9"; + const logo = restaurant.logo; + + const icons = []; + if (logo && logo.trim() !== "") { + icons.push( + { + src: logo, + sizes: "192x192", + type: "image/png", + purpose: "any", + }, + { + src: logo, + sizes: "512x512", + type: "image/png", + purpose: "any", + } + ); + } else { + icons.push( + { + src: "/icons/web-app-manifest-192x192.png", + sizes: "192x192", + type: "image/png", + purpose: "any", + }, + { + src: "/icons/web-app-manifest-512x512.png", + sizes: "512x512", + type: "image/png", + purpose: "any", + } + ); + } + + const manifest = { + name: restaurant.name, + short_name: restaurant.name, + description: restaurant.description || `منوی ${restaurant.name}`, + start_url: `/${name}`, + scope: `/${name}`, + id: `/${name}`, + display: "standalone", + display_override: ["fullscreen", "minimal-ui", "browser"], + background_color: "#F4F5F9", + theme_color: themeColor, + orientation: "portrait", + dir: "rtl", + lang: "fa-IR", + icons, + }; + + return NextResponse.json(manifest, { + headers: { + "Content-Type": "application/manifest+json", + "Cache-Control": + "no-store, no-cache, must-revalidate, proxy-revalidate", + Pragma: "no-cache", + Expires: "0", + }, + }); + } catch (error) { + if (error instanceof Error && error.message === "RESTAURANT_NOT_FOUND") { + return new NextResponse("Restaurant not found", { status: 404 }); + } + + return new NextResponse("Internal Server Error", { status: 500 }); + } +} diff --git a/src/app/favicon.ico b/src/app/favicon.ico.backup similarity index 100% rename from src/app/favicon.ico rename to src/app/favicon.ico.backup