add primary color and splash screen
This commit is contained in:
@@ -109,7 +109,7 @@ function ToggleButton({
|
||||
whileTap={{ scale: 0.9 }}
|
||||
className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2"
|
||||
>
|
||||
<Refresh stroke="#333333" size={24} />
|
||||
<Refresh className="stroke-primary" size={24} />
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
@@ -330,7 +330,7 @@ function SideMenu({ menuState, toggleMenuState, nightModeState, togglenightModeS
|
||||
{tInstallPwaModal('AlreadyInstalled')}
|
||||
</p>
|
||||
) : isIOSDevice ? (
|
||||
<div className="mt-5 rounded-3xl bg-black/5 px-4 py-5 backdrop-blur-sm dark:bg-white/5">
|
||||
<div className="mt-5 rounded-3xl bg-primary/5 px-4 py-5 backdrop-blur-sm dark:bg-white/5">
|
||||
<p className="text-xs font-medium leading-relaxed text-center text-foreground/80 dark:text-neutral-200">
|
||||
{tInstallPwaModal('IOSDescription')}
|
||||
</p>
|
||||
|
||||
@@ -16,13 +16,13 @@ export default function SideMenuItem({ href, title, icon, className, children, .
|
||||
className={`
|
||||
inline-flex gap-3 ps-6 xl:ps-9 h-full w-full items-center overflow-hidden text-nowrap
|
||||
text-xs font-light text-disabled-text border-transparent
|
||||
data-[active]:border-s-6 data-[active]:border-black data-[active]:text-black
|
||||
dark:data-[active]:border-foreground dark:data-[active]:text-foreground
|
||||
data-active:border-s-6 data-active:border-primary data-active:text-primary
|
||||
dark:data-active:border-foreground dark:data-active:text-foreground
|
||||
${className}
|
||||
`}
|
||||
>
|
||||
{children}
|
||||
<span className="flex-shrink-0">{icon}</span>
|
||||
<span className="shrink-0">{icon}</span>
|
||||
<span className='mt-0.5'>{title}</span>
|
||||
</Link>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import Image from 'next/image';
|
||||
|
||||
interface SplashScreenProps {
|
||||
logo?: string;
|
||||
restaurantName?: string;
|
||||
duration?: number;
|
||||
}
|
||||
|
||||
export default function SplashScreen({
|
||||
logo,
|
||||
restaurantName,
|
||||
duration = 2000
|
||||
}: SplashScreenProps) {
|
||||
const [isVisible, setIsVisible] = useState(true);
|
||||
const [imageLoaded, setImageLoaded] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
// setIsVisible(false);
|
||||
}, duration);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [duration]);
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{isVisible && (
|
||||
<motion.div
|
||||
initial={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="fixed inset-0 z-[9999] flex flex-col items-center justify-center"
|
||||
>
|
||||
{/* دایرههای تزئینی پسزمینه */}
|
||||
<div className="absolute inset-0 overflow-hidden">
|
||||
<motion.div
|
||||
animate={{
|
||||
scale: [1, 1.2, 1],
|
||||
opacity: [0.1, 0.2, 0.1]
|
||||
}}
|
||||
transition={{
|
||||
duration: 3,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut"
|
||||
}}
|
||||
className="absolute -top-20 -right-20 w-80 h-80 rounded-full bg-primary/20 blur-3xl"
|
||||
/>
|
||||
<motion.div
|
||||
animate={{
|
||||
scale: [1.2, 1, 1.2],
|
||||
opacity: [0.1, 0.2, 0.1]
|
||||
}}
|
||||
transition={{
|
||||
duration: 3,
|
||||
delay: 0.5,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut"
|
||||
}}
|
||||
className="absolute -bottom-20 -left-20 w-80 h-80 rounded-full bg-primary/20 blur-3xl"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
initial={{ scale: 0.8, opacity: 0 }}
|
||||
animate={{ scale: 1, opacity: 1 }}
|
||||
transition={{
|
||||
duration: 0.5,
|
||||
ease: "easeOut"
|
||||
}}
|
||||
className="flex flex-col items-center gap-8 px-8 w-full max-w-md relative z-10"
|
||||
>
|
||||
{logo && (
|
||||
<motion.div
|
||||
initial={{ y: -20, opacity: 0 }}
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
transition={{ delay: 0.2, duration: 0.5 }}
|
||||
className="relative"
|
||||
>
|
||||
<div>
|
||||
<Image
|
||||
src={logo}
|
||||
alt={restaurantName || 'Restaurant Logo'}
|
||||
width={200}
|
||||
height={100}
|
||||
priority
|
||||
className="object-contain"
|
||||
onLoad={() => setImageLoaded(true)}
|
||||
style={{ opacity: imageLoaded ? 1 : 0, transition: 'opacity 0.3s' }}
|
||||
/>
|
||||
{!imageLoaded && (
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<div className="w-10 h-10 border-4 border-primary border-t-transparent rounded-full animate-spin" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* حلقه درخشان دور لوگو */}
|
||||
<motion.div
|
||||
animate={{
|
||||
scale: [1, 1.05, 1],
|
||||
opacity: [0.5, 0.8, 0.5]
|
||||
}}
|
||||
transition={{
|
||||
duration: 2,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut"
|
||||
}}
|
||||
// className="absolute -inset-2 rounded-3xl bg-gradient-to-r from-primary/20 via-primary/40 to-primary/20 -z-10 blur-xl"
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
{/*
|
||||
{restaurantName && (
|
||||
<motion.h1
|
||||
initial={{ y: 20, opacity: 0 }}
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
transition={{ delay: 0.3, duration: 0.5 }}
|
||||
className="text-3xl font-bold text-foreground text-center"
|
||||
>
|
||||
{restaurantName}
|
||||
</motion.h1>
|
||||
)} */}
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 0.5, duration: 0.5 }}
|
||||
className="flex gap-3 mt-2"
|
||||
>
|
||||
<motion.div
|
||||
animate={{
|
||||
scale: [1, 1.3, 1],
|
||||
opacity: [0.4, 1, 0.4]
|
||||
}}
|
||||
transition={{
|
||||
duration: 1.5,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut"
|
||||
}}
|
||||
// className="w-3 h-3 rounded-full bg-primary shadow-lg shadow-primary/50"
|
||||
/>
|
||||
<motion.div
|
||||
animate={{
|
||||
scale: [1, 1.3, 1],
|
||||
opacity: [0.4, 1, 0.4]
|
||||
}}
|
||||
transition={{
|
||||
duration: 1.5,
|
||||
delay: 0.2,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut"
|
||||
}}
|
||||
// className="w-3 h-3 rounded-full bg-primary shadow-lg shadow-primary/50"
|
||||
/>
|
||||
<motion.div
|
||||
animate={{
|
||||
scale: [1, 1.3, 1],
|
||||
opacity: [0.4, 1, 0.4]
|
||||
}}
|
||||
transition={{
|
||||
duration: 1.5,
|
||||
delay: 0.4,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut"
|
||||
}}
|
||||
// className="w-3 h-3 rounded-full bg-primary shadow-lg shadow-primary/50"
|
||||
/>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
);
|
||||
}
|
||||
@@ -42,10 +42,10 @@ export const TabContainerClassNames: TabContainerClassName = {
|
||||
headerActive: '',
|
||||
icon: 'text-xs transition-all duration-150',
|
||||
iconDeactive: 'stroke-disabled-text',
|
||||
iconActive: 'stroke-black dark:stroke-foreground',
|
||||
iconActive: 'stroke-primary dark:stroke-foreground',
|
||||
title: 'text-xs transition-all duration-150',
|
||||
titleDeactive: 'text-disabled-text',
|
||||
titleActive: 'text-black dark:text-foreground'
|
||||
titleActive: 'text-primary dark:text-foreground'
|
||||
}
|
||||
|
||||
function TabContainer({ className = TabContainerClassNames, changeType = TabContainerRenderType.RENDERER, defaultIndex = 0, itemRenderer, children, ...restProps }: Props) {
|
||||
|
||||
@@ -44,7 +44,7 @@ function TopBar({ profileDropState, toggleProfileDropState, toggleMenuState }: P
|
||||
S
|
||||
</div> */}
|
||||
{/* <div
|
||||
className={`z-50 top-0 left-0 w-full h-full px-5 py-15 md:px-15 xl:p-0 fixed xl:relative bg-[#00000044] xl:bg-transparent backdrop-blur-sm xl:backdrop-blur-none justify-center xl:block ${searchModalState ? 'flex' : 'hidden'}`}>
|
||||
className={`z-50 top-0 left-0 w-full h-full px-5 py-15 md:px-15 xl:p-0 fixed xl:relative bg-primary/25 xl:bg-transparent backdrop-blur-sm xl:backdrop-blur-none justify-center xl:block ${searchModalState ? 'flex' : 'hidden'}`}>
|
||||
<div
|
||||
className={`w-full h-full absolute`}
|
||||
onClick={toggleSearchModalState}>
|
||||
|
||||
@@ -38,7 +38,7 @@ function DialogOverlay({
|
||||
<DialogPrimitive.Overlay
|
||||
data-slot="dialog-overlay"
|
||||
className={cn(
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-primary/50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -21,7 +21,7 @@ function RateBar({ percentage, content, className = '' }: Props) {
|
||||
>
|
||||
</motion.span>
|
||||
</span>
|
||||
<Star1 className='fill-black dark:fill-foreground me-1 mb-1' size={12} />
|
||||
<Star1 className='fill-primary dark:fill-foreground me-1 mb-1' size={12} />
|
||||
<span className='text-xs font-medium'>{content}</span>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -13,11 +13,11 @@ type Props = {
|
||||
}
|
||||
|
||||
const defaultContent: Array<{ value: string, icon: React.ReactElement }> = [
|
||||
{ value: '5', icon: <Star className='fill-black dark:fill-foreground me-1 mb-1' size={24} /> },
|
||||
{ value: '4', icon: <Star className='fill-black dark:fill-foreground me-1 mb-1' size={24} /> },
|
||||
{ value: '3', icon: <Star className='fill-black dark:fill-foreground me-1 mb-1' size={24} /> },
|
||||
{ value: '2', icon: <Star className='fill-black dark:fill-foreground me-1 mb-1' size={24} /> },
|
||||
{ value: '1', icon: <Star className='fill-black dark:fill-foreground me-1 mb-1' size={24} /> },
|
||||
{ value: '5', icon: <Star className='fill-primary dark:fill-foreground me-1 mb-1' size={24} /> },
|
||||
{ value: '4', icon: <Star className='fill-primary dark:fill-foreground me-1 mb-1' size={24} /> },
|
||||
{ value: '3', icon: <Star className='fill-primary dark:fill-foreground me-1 mb-1' size={24} /> },
|
||||
{ value: '2', icon: <Star className='fill-primary dark:fill-foreground me-1 mb-1' size={24} /> },
|
||||
{ value: '1', icon: <Star className='fill-primary dark:fill-foreground me-1 mb-1' size={24} /> },
|
||||
]
|
||||
|
||||
function RateSelectionBar({ name, content = defaultContent, className = '', onValueChange, defaultValue = [5] }: Props) {
|
||||
@@ -36,7 +36,7 @@ function RateSelectionBar({ name, content = defaultContent, className = '', onVa
|
||||
{content?.map((value, index) => {
|
||||
return (
|
||||
<span key={index} className='flex items-center gap-1'>
|
||||
<Star1 className='fill-black dark:fill-neutral-200 me-0.5 mb-1' size={12} />
|
||||
<Star1 className='fill-primary dark:fill-neutral-200 me-0.5 mb-1' size={12} />
|
||||
<span className='text-xs font-medium'>{value.value}</span>
|
||||
</span>
|
||||
)
|
||||
|
||||
@@ -1,23 +1,128 @@
|
||||
'use client';
|
||||
|
||||
import usePreference from '@/hooks/helpers/usePreference';
|
||||
import React, { useEffect } from 'react'
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useGetAbout } from '@/app/[name]/(Main)/about/hooks/useAboutData';
|
||||
import SplashScreen from '@/components/overlays/SplashScreen';
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
// تابع برای اعمال رنگ primary
|
||||
function applyPrimaryColor(colorHex: string) {
|
||||
const root = document.documentElement;
|
||||
const hex = colorHex.replace('#', '');
|
||||
const r = parseInt(hex.substring(0, 2), 16) / 255;
|
||||
const g = parseInt(hex.substring(2, 4), 16) / 255;
|
||||
const b = parseInt(hex.substring(4, 6), 16) / 255;
|
||||
|
||||
const lightness = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
||||
const max = Math.max(r, g, b);
|
||||
const min = Math.min(r, g, b);
|
||||
const chroma = (max - min) * 0.15;
|
||||
|
||||
let hue = 0;
|
||||
if (chroma !== 0) {
|
||||
if (max === r) {
|
||||
hue = ((g - b) / (max - min) + (g < b ? 6 : 0)) * 60;
|
||||
} else if (max === g) {
|
||||
hue = ((b - r) / (max - min) + 2) * 60;
|
||||
} else {
|
||||
hue = ((r - g) / (max - min) + 4) * 60;
|
||||
}
|
||||
}
|
||||
|
||||
const oklchColor = `oklch(${lightness.toFixed(3)} ${chroma.toFixed(3)} ${hue.toFixed(1)})`;
|
||||
root.style.setProperty('--primary', oklchColor);
|
||||
|
||||
const brightness = (r * 299 + g * 587 + b * 114) / 1000;
|
||||
const foregroundOklch = brightness > 0.5 ? 'oklch(0 0 0)' : 'oklch(1 0 0)';
|
||||
root.style.setProperty('--primary-foreground', foregroundOklch);
|
||||
}
|
||||
|
||||
function PreferenceWrapper({ children }: Props) {
|
||||
const { state: nightMode } = usePreference('night-mode', false);
|
||||
const { data: aboutData, isSuccess } = 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 theme = nightMode //|| window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
// بارگذاری رنگ
|
||||
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);
|
||||
}
|
||||
if (aboutData.data.name) {
|
||||
localStorage.setItem('restaurant-name', aboutData.data.name);
|
||||
}
|
||||
|
||||
applyPrimaryColor(aboutData.data.menuColor);
|
||||
}
|
||||
}, [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 (
|
||||
<>
|
||||
{children}
|
||||
{showSplash && (
|
||||
<SplashScreen
|
||||
logo={splashLogo}
|
||||
restaurantName={splashName}
|
||||
duration={2500}
|
||||
/>
|
||||
)}
|
||||
{!showSplash && children}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user