This commit is contained in:
@@ -1,111 +1,20 @@
|
||||
'use client';
|
||||
|
||||
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'
|
||||
import CartChecker from '@/components/CartChecker'
|
||||
import ActiveChecker from '@/components/ActiveChecker'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
export const revalidate = 0
|
||||
|
||||
type LayoutParams = { name: string }
|
||||
|
||||
export async function generateMetadata({
|
||||
params
|
||||
}: {
|
||||
params: Promise<LayoutParams>
|
||||
}): Promise<Metadata> {
|
||||
const { name } = await params
|
||||
try {
|
||||
const restaurant = await getRestaurant(name)
|
||||
|
||||
const title = restaurant.seoTitle || restaurant.name
|
||||
const logo = restaurant.logo
|
||||
|
||||
const defaultIcon = "/icons/web-app-manifest-192x192.png"
|
||||
|
||||
// استفاده از API route برای تبدیل لوگو به مربع
|
||||
let iconUrl = defaultIcon
|
||||
let icon192Url = defaultIcon
|
||||
let icon512Url = "/icons/web-app-manifest-512x512.png"
|
||||
|
||||
if (logo && logo.trim() !== "") {
|
||||
iconUrl = `/${name}/api/logo?url=${encodeURIComponent(logo)}&size=192`
|
||||
icon192Url = `/${name}/api/logo?url=${encodeURIComponent(logo)}&size=192`
|
||||
icon512Url = `/${name}/api/logo?url=${encodeURIComponent(logo)}&size=512`
|
||||
}
|
||||
|
||||
return {
|
||||
title,
|
||||
description: restaurant.seoDescription || restaurant.description || `منوی ${restaurant.name}`,
|
||||
manifest: `/${name}/manifest.webmanifest`,
|
||||
icons: {
|
||||
icon: [
|
||||
{ url: iconUrl },
|
||||
{ url: icon192Url, sizes: "192x192", type: "image/png" },
|
||||
{ url: icon512Url, 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({
|
||||
params
|
||||
}: {
|
||||
params: Promise<LayoutParams>
|
||||
}): Promise<Viewport> {
|
||||
try {
|
||||
const { name } = await params
|
||||
const restaurant = await getRestaurant(name)
|
||||
|
||||
return {
|
||||
themeColor: restaurant.menuColor || '#F4F5F9'
|
||||
}
|
||||
} catch {
|
||||
return {
|
||||
themeColor: '#F4F5F9'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default async function Layout({
|
||||
export default function Layout({
|
||||
children,
|
||||
params
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
params: Promise<LayoutParams>
|
||||
}): Promise<React.ReactNode> {
|
||||
try {
|
||||
const { name } = await params
|
||||
await getRestaurant(name)
|
||||
return <>
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<PreferenceWrapper>{children}</PreferenceWrapper>
|
||||
<CartChecker />
|
||||
<ActiveChecker />
|
||||
</>
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message === 'RESTAURANT_NOT_FOUND') {
|
||||
notFound()
|
||||
}
|
||||
throw error
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
'use client';
|
||||
|
||||
import usePreference from '@/hooks/helpers/usePreference';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useGetAbout } from '@/app/[name]/(Main)/about/hooks/useAboutData';
|
||||
import SplashScreen from '@/components/overlays/SplashScreen';
|
||||
import { hexToOklch, calculateBrightness } from '@/lib/helpers/colorUtils';
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
// تابع برای اعمال رنگ primary
|
||||
function applyPrimaryColor(colorHex: string) {
|
||||
const root = document.documentElement;
|
||||
const oklchColor = hexToOklch(colorHex);
|
||||
@@ -28,42 +26,17 @@ function PreferenceWrapper({ children }: Props) {
|
||||
const { state: nightMode } = usePreference('night-mode', false);
|
||||
const { data: aboutData } = useGetAbout();
|
||||
|
||||
// بررسی اینکه آیا باید اسپلش نمایش داده بشه
|
||||
const [showSplash, setShowSplash] = useState(() => {
|
||||
if (typeof window === 'undefined') return true;
|
||||
const navigationEntries = performance.getEntriesByType('navigation') as PerformanceNavigationTiming[];
|
||||
const isReload = navigationEntries.length > 0 && navigationEntries[0].type === 'reload';
|
||||
return !sessionStorage.getItem('app-loaded') || isReload;
|
||||
});
|
||||
|
||||
const [cachedLogo, setCachedLogo] = useState<string | null>(null);
|
||||
const [cachedName, setCachedName] = useState<string | null>(null);
|
||||
|
||||
// بارگذاری رنگ و اطلاعات از localStorage در اولین رندر
|
||||
useEffect(() => {
|
||||
// بارگذاری رنگ
|
||||
const savedColor = localStorage.getItem('theme-primary-color');
|
||||
if (savedColor) {
|
||||
applyPrimaryColor(savedColor);
|
||||
}
|
||||
|
||||
// بارگذاری لوگو و نام
|
||||
const savedLogo = localStorage.getItem('restaurant-logo');
|
||||
const savedName = localStorage.getItem('restaurant-name');
|
||||
if (savedLogo) setCachedLogo(savedLogo);
|
||||
if (savedName) setCachedName(savedName);
|
||||
|
||||
// ثبت اینکه app لود شده
|
||||
sessionStorage.setItem('app-loaded', 'true');
|
||||
}, []);
|
||||
|
||||
// اعمال رنگ primary از about (با ذخیره در localStorage)
|
||||
useEffect(() => {
|
||||
if (aboutData?.data?.menuColor) {
|
||||
// ذخیره رنگ و اطلاعات در localStorage
|
||||
localStorage.setItem('theme-primary-color', aboutData.data.menuColor);
|
||||
|
||||
// ذخیره لوگو و نام برای اسپلش
|
||||
if (aboutData.data.logo) {
|
||||
localStorage.setItem('restaurant-logo', aboutData.data.logo);
|
||||
}
|
||||
@@ -76,39 +49,12 @@ function PreferenceWrapper({ children }: Props) {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [aboutData?.data?.menuColor]);
|
||||
|
||||
// اعمال تم تاریک/روشن
|
||||
useEffect(() => {
|
||||
const theme = nightMode;
|
||||
document.documentElement.setAttribute("data-theme", theme ? "dark" : "light");
|
||||
}, [nightMode]);
|
||||
|
||||
// بستن اسپلش بعد از مدت زمان مشخص
|
||||
useEffect(() => {
|
||||
if (showSplash) {
|
||||
const timer = setTimeout(() => {
|
||||
setShowSplash(false);
|
||||
}, 2500);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [showSplash]);
|
||||
|
||||
// استفاده از دادههای cache شده برای اسپلش
|
||||
const splashLogo = aboutData?.data?.logo || cachedLogo || undefined;
|
||||
const splashName = aboutData?.data?.name || cachedName || undefined;
|
||||
|
||||
return (
|
||||
<>
|
||||
{showSplash && (
|
||||
<SplashScreen
|
||||
logo={splashLogo}
|
||||
restaurantName={splashName}
|
||||
duration={2500}
|
||||
/>
|
||||
)}
|
||||
{!showSplash && children}
|
||||
</>
|
||||
)
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
export default PreferenceWrapper
|
||||
export default PreferenceWrapper
|
||||
|
||||
+41
-39
@@ -2,47 +2,49 @@ import { NextResponse } from "next/server";
|
||||
import type { NextRequest } from "next/server";
|
||||
|
||||
// Map host → tenant path
|
||||
const HOST_MAP: Record<string, string> = {
|
||||
"theboote.tahavol-mr.ir": "/boote",
|
||||
"thesun.tahavol-mr.ir": "/suncafe",
|
||||
"passataplus.ir": "/passata",
|
||||
// دامنههای جدید اینجا اضافه میشوند
|
||||
};
|
||||
// const HOST_MAP: Record<string, string> = {
|
||||
// "theboote.tahavol-mr.ir": "/boote",
|
||||
// "thesun.tahavol-mr.ir": "/suncafe",
|
||||
// "passataplus.ir": "/passata",
|
||||
// // دامنههای جدید اینجا اضافه میشوند
|
||||
// };
|
||||
|
||||
export function middleware(req: NextRequest) {
|
||||
const host = req.headers.get("host") || "";
|
||||
const tenantPath = HOST_MAP[host.toLowerCase()] || "";
|
||||
export function middleware(_req: NextRequest) {
|
||||
return NextResponse.next();
|
||||
|
||||
if (!tenantPath) {
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
const pathname = req.nextUrl.pathname;
|
||||
|
||||
// مسیرهای api و استاتیک را rewrite نکن
|
||||
if (
|
||||
pathname.startsWith("/api") ||
|
||||
pathname.startsWith("/_next") ||
|
||||
pathname.includes(".")
|
||||
) {
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
// اگر کاربر ریشه دامنه را باز کرده، به مسیر tenant ریدایرکت کن (آدرسبار /passata و غیره نشون بده)
|
||||
if (pathname === "/") {
|
||||
const url = req.nextUrl.clone();
|
||||
url.pathname = tenantPath;
|
||||
return NextResponse.redirect(url);
|
||||
}
|
||||
|
||||
// اگر مسیر از قبل با tenant یکی است (مثلاً /passata یا /passata/menu)، rewrite نکن
|
||||
if (pathname === tenantPath || pathname.startsWith(tenantPath + "/")) {
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
const url = req.nextUrl.clone();
|
||||
url.pathname = `${tenantPath}${pathname}`;
|
||||
return NextResponse.rewrite(url);
|
||||
// const host = req.headers.get("host") || "";
|
||||
// const tenantPath = HOST_MAP[host.toLowerCase()] || "";
|
||||
//
|
||||
// if (!tenantPath) {
|
||||
// return NextResponse.next();
|
||||
// }
|
||||
//
|
||||
// const pathname = req.nextUrl.pathname;
|
||||
//
|
||||
// // مسیرهای api و استاتیک را rewrite نکن
|
||||
// if (
|
||||
// pathname.startsWith("/api") ||
|
||||
// pathname.startsWith("/_next") ||
|
||||
// pathname.includes(".")
|
||||
// ) {
|
||||
// return NextResponse.next();
|
||||
// }
|
||||
//
|
||||
// // اگر کاربر ریشه دامنه را باز کرده، به مسیر tenant ریدایرکت کن (آدرسبار /passata و غیره نشون بده)
|
||||
// if (pathname === "/") {
|
||||
// const url = req.nextUrl.clone();
|
||||
// url.pathname = tenantPath;
|
||||
// return NextResponse.redirect(url);
|
||||
// }
|
||||
//
|
||||
// // اگر مسیر از قبل با tenant یکی است (مثلاً /passata یا /passata/menu)، rewrite نکن
|
||||
// if (pathname === tenantPath || pathname.startsWith(tenantPath + "/")) {
|
||||
// return NextResponse.next();
|
||||
// }
|
||||
//
|
||||
// const url = req.nextUrl.clone();
|
||||
// url.pathname = `${tenantPath}${pathname}`;
|
||||
// return NextResponse.rewrite(url);
|
||||
}
|
||||
|
||||
export const config = {
|
||||
|
||||
Reference in New Issue
Block a user