This commit is contained in:
@@ -1,111 +1,20 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
import PreferenceWrapper from '@/components/wrapper/PreferenceWrapper'
|
import PreferenceWrapper from '@/components/wrapper/PreferenceWrapper'
|
||||||
import React from 'react'
|
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 CartChecker from '@/components/CartChecker'
|
||||||
import ActiveChecker from '@/components/ActiveChecker'
|
import ActiveChecker from '@/components/ActiveChecker'
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic'
|
export default function Layout({
|
||||||
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({
|
|
||||||
children,
|
children,
|
||||||
params
|
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
params: Promise<LayoutParams>
|
}) {
|
||||||
}): Promise<React.ReactNode> {
|
return (
|
||||||
try {
|
<>
|
||||||
const { name } = await params
|
|
||||||
await getRestaurant(name)
|
|
||||||
return <>
|
|
||||||
<PreferenceWrapper>{children}</PreferenceWrapper>
|
<PreferenceWrapper>{children}</PreferenceWrapper>
|
||||||
<CartChecker />
|
<CartChecker />
|
||||||
<ActiveChecker />
|
<ActiveChecker />
|
||||||
</>
|
</>
|
||||||
} catch (error) {
|
)
|
||||||
if (error instanceof Error && error.message === 'RESTAURANT_NOT_FOUND') {
|
|
||||||
notFound()
|
|
||||||
}
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import usePreference from '@/hooks/helpers/usePreference';
|
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 { useGetAbout } from '@/app/[name]/(Main)/about/hooks/useAboutData';
|
||||||
import SplashScreen from '@/components/overlays/SplashScreen';
|
|
||||||
import { hexToOklch, calculateBrightness } from '@/lib/helpers/colorUtils';
|
import { hexToOklch, calculateBrightness } from '@/lib/helpers/colorUtils';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
// تابع برای اعمال رنگ primary
|
|
||||||
function applyPrimaryColor(colorHex: string) {
|
function applyPrimaryColor(colorHex: string) {
|
||||||
const root = document.documentElement;
|
const root = document.documentElement;
|
||||||
const oklchColor = hexToOklch(colorHex);
|
const oklchColor = hexToOklch(colorHex);
|
||||||
@@ -28,42 +26,17 @@ function PreferenceWrapper({ children }: Props) {
|
|||||||
const { state: nightMode } = usePreference('night-mode', false);
|
const { state: nightMode } = usePreference('night-mode', false);
|
||||||
const { data: aboutData } = useGetAbout();
|
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(() => {
|
useEffect(() => {
|
||||||
// بارگذاری رنگ
|
|
||||||
const savedColor = localStorage.getItem('theme-primary-color');
|
const savedColor = localStorage.getItem('theme-primary-color');
|
||||||
if (savedColor) {
|
if (savedColor) {
|
||||||
applyPrimaryColor(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(() => {
|
useEffect(() => {
|
||||||
if (aboutData?.data?.menuColor) {
|
if (aboutData?.data?.menuColor) {
|
||||||
// ذخیره رنگ و اطلاعات در localStorage
|
|
||||||
localStorage.setItem('theme-primary-color', aboutData.data.menuColor);
|
localStorage.setItem('theme-primary-color', aboutData.data.menuColor);
|
||||||
|
|
||||||
// ذخیره لوگو و نام برای اسپلش
|
|
||||||
if (aboutData.data.logo) {
|
if (aboutData.data.logo) {
|
||||||
localStorage.setItem('restaurant-logo', 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
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [aboutData?.data?.menuColor]);
|
}, [aboutData?.data?.menuColor]);
|
||||||
|
|
||||||
// اعمال تم تاریک/روشن
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const theme = nightMode;
|
const theme = nightMode;
|
||||||
document.documentElement.setAttribute("data-theme", theme ? "dark" : "light");
|
document.documentElement.setAttribute("data-theme", theme ? "dark" : "light");
|
||||||
}, [nightMode]);
|
}, [nightMode]);
|
||||||
|
|
||||||
// بستن اسپلش بعد از مدت زمان مشخص
|
return <>{children}</>;
|
||||||
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}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default PreferenceWrapper
|
export default PreferenceWrapper
|
||||||
|
|||||||
+41
-39
@@ -2,47 +2,49 @@ import { NextResponse } from "next/server";
|
|||||||
import type { NextRequest } from "next/server";
|
import type { NextRequest } from "next/server";
|
||||||
|
|
||||||
// Map host → tenant path
|
// Map host → tenant path
|
||||||
const HOST_MAP: Record<string, string> = {
|
// const HOST_MAP: Record<string, string> = {
|
||||||
"theboote.tahavol-mr.ir": "/boote",
|
// "theboote.tahavol-mr.ir": "/boote",
|
||||||
"thesun.tahavol-mr.ir": "/suncafe",
|
// "thesun.tahavol-mr.ir": "/suncafe",
|
||||||
"passataplus.ir": "/passata",
|
// "passataplus.ir": "/passata",
|
||||||
// دامنههای جدید اینجا اضافه میشوند
|
// // دامنههای جدید اینجا اضافه میشوند
|
||||||
};
|
// };
|
||||||
|
|
||||||
export function middleware(req: NextRequest) {
|
export function middleware(_req: NextRequest) {
|
||||||
const host = req.headers.get("host") || "";
|
return NextResponse.next();
|
||||||
const tenantPath = HOST_MAP[host.toLowerCase()] || "";
|
|
||||||
|
|
||||||
if (!tenantPath) {
|
// const host = req.headers.get("host") || "";
|
||||||
return NextResponse.next();
|
// const tenantPath = HOST_MAP[host.toLowerCase()] || "";
|
||||||
}
|
//
|
||||||
|
// if (!tenantPath) {
|
||||||
const pathname = req.nextUrl.pathname;
|
// return NextResponse.next();
|
||||||
|
// }
|
||||||
// مسیرهای api و استاتیک را rewrite نکن
|
//
|
||||||
if (
|
// const pathname = req.nextUrl.pathname;
|
||||||
pathname.startsWith("/api") ||
|
//
|
||||||
pathname.startsWith("/_next") ||
|
// // مسیرهای api و استاتیک را rewrite نکن
|
||||||
pathname.includes(".")
|
// if (
|
||||||
) {
|
// pathname.startsWith("/api") ||
|
||||||
return NextResponse.next();
|
// pathname.startsWith("/_next") ||
|
||||||
}
|
// pathname.includes(".")
|
||||||
|
// ) {
|
||||||
// اگر کاربر ریشه دامنه را باز کرده، به مسیر tenant ریدایرکت کن (آدرسبار /passata و غیره نشون بده)
|
// return NextResponse.next();
|
||||||
if (pathname === "/") {
|
// }
|
||||||
const url = req.nextUrl.clone();
|
//
|
||||||
url.pathname = tenantPath;
|
// // اگر کاربر ریشه دامنه را باز کرده، به مسیر tenant ریدایرکت کن (آدرسبار /passata و غیره نشون بده)
|
||||||
return NextResponse.redirect(url);
|
// if (pathname === "/") {
|
||||||
}
|
// const url = req.nextUrl.clone();
|
||||||
|
// url.pathname = tenantPath;
|
||||||
// اگر مسیر از قبل با tenant یکی است (مثلاً /passata یا /passata/menu)، rewrite نکن
|
// return NextResponse.redirect(url);
|
||||||
if (pathname === tenantPath || pathname.startsWith(tenantPath + "/")) {
|
// }
|
||||||
return NextResponse.next();
|
//
|
||||||
}
|
// // اگر مسیر از قبل با tenant یکی است (مثلاً /passata یا /passata/menu)، rewrite نکن
|
||||||
|
// if (pathname === tenantPath || pathname.startsWith(tenantPath + "/")) {
|
||||||
const url = req.nextUrl.clone();
|
// return NextResponse.next();
|
||||||
url.pathname = `${tenantPath}${pathname}`;
|
// }
|
||||||
return NextResponse.rewrite(url);
|
//
|
||||||
|
// const url = req.nextUrl.clone();
|
||||||
|
// url.pathname = `${tenantPath}${pathname}`;
|
||||||
|
// return NextResponse.rewrite(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
|
|||||||
Reference in New Issue
Block a user