add primary color and splash screen
This commit is contained in:
+1
-1
File diff suppressed because one or more lines are too long
@@ -92,7 +92,7 @@ function FoodPage({ }: Props) {
|
|||||||
{categoryName || '-'}
|
{categoryName || '-'}
|
||||||
</div>
|
</div>
|
||||||
<button onClick={() => router.back()} className='p-2 rounded-full bg-container/40'>
|
<button onClick={() => router.back()} className='p-2 rounded-full bg-container/40'>
|
||||||
<ArrowLeft size={18} className='stroke-black' />
|
<ArrowLeft size={18} className='stroke-primary dark:stroke-foreground' />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -103,7 +103,7 @@ function FoodPage({ }: Props) {
|
|||||||
{foodName}
|
{foodName}
|
||||||
</h5>
|
</h5>
|
||||||
<button className='p-2 bg-[#EAECF0] dark:bg-neutral-600 rounded-lg'>
|
<button className='p-2 bg-[#EAECF0] dark:bg-neutral-600 rounded-lg'>
|
||||||
<Heart variant='Bold' size={24} className='fill-black dark:fill-foreground' />
|
<Heart variant='Bold' size={24} className='fill-primary dark:fill-foreground' />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ function RatingOrderIndex({ }: Props) {
|
|||||||
title: 'text-sm2! font-normal mt-1',
|
title: 'text-sm2! font-normal mt-1',
|
||||||
header: 'rounded-lg h-7! w-full',
|
header: 'rounded-lg h-7! w-full',
|
||||||
headerActive: 'bg-white',
|
headerActive: 'bg-white',
|
||||||
titleActive: 'text-black'
|
titleActive: 'text-primary'
|
||||||
}}>
|
}}>
|
||||||
<TabHeader title={t("Tabs.Weeknesses.Title")} viewRenderer={badPoitns()}></TabHeader>
|
<TabHeader title={t("Tabs.Weeknesses.Title")} viewRenderer={badPoitns()}></TabHeader>
|
||||||
<TabHeader title={t("Tabs.Strengths.Title")} viewRenderer={goodPoitns()}></TabHeader>
|
<TabHeader title={t("Tabs.Strengths.Title")} viewRenderer={goodPoitns()}></TabHeader>
|
||||||
|
|||||||
@@ -60,10 +60,7 @@ function TransactionsIndex() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className='max-w-36 place-self-end col-span-1 h-full text-end py-4 px-5.5 flex flex-col justify-between rounded-e-normal'
|
className='max-w-36 place-self-end col-span-1 h-full text-end py-4 px-5.5 flex flex-col justify-between rounded-e-normal bg-linear-to-br from-primary to-menu-header'
|
||||||
style={{
|
|
||||||
background: 'linear-gradient(165.51deg, #000000 -8.44%, #C3C7DD 100%)',
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<p className='text-sm2 text-white leading-tight'>
|
<p className='text-sm2 text-white leading-tight'>
|
||||||
<span className='font-normal'>Customer</span><br />
|
<span className='font-normal'>Customer</span><br />
|
||||||
|
|||||||
@@ -50,10 +50,7 @@ function TransactionsIndex() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className='col-span-1 h-full text-end py-4 px-5.5 flex flex-col justify-between rounded-e-normal'
|
className='col-span-1 h-full text-end py-4 px-5.5 flex flex-col justify-between rounded-e-normal bg-linear-to-br from-primary to-menu-header'
|
||||||
style={{
|
|
||||||
background: 'linear-gradient(165.51deg, #000000 -8.44%, #C3C7DD 100%)',
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<p className='text-sm2 text-white leading-tight'>
|
<p className='text-sm2 text-white leading-tight'>
|
||||||
<span className='font-medium'>Your</span><br />
|
<span className='font-medium'>Your</span><br />
|
||||||
|
|||||||
@@ -42,6 +42,48 @@ export default async function RootLayout({
|
|||||||
dir='rtl'
|
dir='rtl'
|
||||||
className="h-svh overflow-hidden"
|
className="h-svh overflow-hidden"
|
||||||
data-theme="light" >
|
data-theme="light" >
|
||||||
|
<head>
|
||||||
|
<script
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: `
|
||||||
|
(function() {
|
||||||
|
try {
|
||||||
|
const savedColor = localStorage.getItem('theme-primary-color');
|
||||||
|
if (savedColor) {
|
||||||
|
const hex = savedColor.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) + ')';
|
||||||
|
document.documentElement.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)';
|
||||||
|
document.documentElement.style.setProperty('--primary-foreground', foregroundOklch);
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
})();
|
||||||
|
`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</head>
|
||||||
<body
|
<body
|
||||||
|
|
||||||
className={`antialiased bg-background h-svh overflow-hidden`}
|
className={`antialiased bg-background h-svh overflow-hidden`}
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ function ToggleButton({
|
|||||||
whileTap={{ scale: 0.9 }}
|
whileTap={{ scale: 0.9 }}
|
||||||
className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2"
|
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>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -330,7 +330,7 @@ function SideMenu({ menuState, toggleMenuState, nightModeState, togglenightModeS
|
|||||||
{tInstallPwaModal('AlreadyInstalled')}
|
{tInstallPwaModal('AlreadyInstalled')}
|
||||||
</p>
|
</p>
|
||||||
) : isIOSDevice ? (
|
) : 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">
|
<p className="text-xs font-medium leading-relaxed text-center text-foreground/80 dark:text-neutral-200">
|
||||||
{tInstallPwaModal('IOSDescription')}
|
{tInstallPwaModal('IOSDescription')}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -16,13 +16,13 @@ export default function SideMenuItem({ href, title, icon, className, children, .
|
|||||||
className={`
|
className={`
|
||||||
inline-flex gap-3 ps-6 xl:ps-9 h-full w-full items-center overflow-hidden text-nowrap
|
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
|
text-xs font-light text-disabled-text border-transparent
|
||||||
data-[active]:border-s-6 data-[active]:border-black data-[active]:text-black
|
data-active:border-s-6 data-active:border-primary data-active:text-primary
|
||||||
dark:data-[active]:border-foreground dark:data-[active]:text-foreground
|
dark:data-active:border-foreground dark:data-active:text-foreground
|
||||||
${className}
|
${className}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
<span className="flex-shrink-0">{icon}</span>
|
<span className="shrink-0">{icon}</span>
|
||||||
<span className='mt-0.5'>{title}</span>
|
<span className='mt-0.5'>{title}</span>
|
||||||
</Link>
|
</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: '',
|
headerActive: '',
|
||||||
icon: 'text-xs transition-all duration-150',
|
icon: 'text-xs transition-all duration-150',
|
||||||
iconDeactive: 'stroke-disabled-text',
|
iconDeactive: 'stroke-disabled-text',
|
||||||
iconActive: 'stroke-black dark:stroke-foreground',
|
iconActive: 'stroke-primary dark:stroke-foreground',
|
||||||
title: 'text-xs transition-all duration-150',
|
title: 'text-xs transition-all duration-150',
|
||||||
titleDeactive: 'text-disabled-text',
|
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) {
|
function TabContainer({ className = TabContainerClassNames, changeType = TabContainerRenderType.RENDERER, defaultIndex = 0, itemRenderer, children, ...restProps }: Props) {
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ function TopBar({ profileDropState, toggleProfileDropState, toggleMenuState }: P
|
|||||||
S
|
S
|
||||||
</div> */}
|
</div> */}
|
||||||
{/* <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
|
<div
|
||||||
className={`w-full h-full absolute`}
|
className={`w-full h-full absolute`}
|
||||||
onClick={toggleSearchModalState}>
|
onClick={toggleSearchModalState}>
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ function DialogOverlay({
|
|||||||
<DialogPrimitive.Overlay
|
<DialogPrimitive.Overlay
|
||||||
data-slot="dialog-overlay"
|
data-slot="dialog-overlay"
|
||||||
className={cn(
|
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
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ function RateBar({ percentage, content, className = '' }: Props) {
|
|||||||
>
|
>
|
||||||
</motion.span>
|
</motion.span>
|
||||||
</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>
|
<span className='text-xs font-medium'>{content}</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ type Props = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const defaultContent: Array<{ value: string, icon: React.ReactElement }> = [
|
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: '5', icon: <Star className='fill-primary 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: '4', icon: <Star className='fill-primary 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: '3', icon: <Star className='fill-primary 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: '2', icon: <Star className='fill-primary 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: '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) {
|
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) => {
|
{content?.map((value, index) => {
|
||||||
return (
|
return (
|
||||||
<span key={index} className='flex items-center gap-1'>
|
<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 className='text-xs font-medium'>{value.value}</span>
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,23 +1,128 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import usePreference from '@/hooks/helpers/usePreference';
|
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 = {
|
type Props = {
|
||||||
children: React.ReactNode
|
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) {
|
function PreferenceWrapper({ children }: Props) {
|
||||||
const { state: nightMode } = usePreference('night-mode', false);
|
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(() => {
|
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");
|
document.documentElement.setAttribute("data-theme", theme ? "dark" : "light");
|
||||||
}, [nightMode]);
|
}, [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 (
|
return (
|
||||||
<>
|
<>
|
||||||
{children}
|
{showSplash && (
|
||||||
|
<SplashScreen
|
||||||
|
logo={splashLogo}
|
||||||
|
restaurantName={splashName}
|
||||||
|
duration={2500}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!showSplash && children}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
interface UseThemeColorProps {
|
||||||
|
primaryColor?: string | null;
|
||||||
|
enabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* تبدیل hex به RGB normalized (0-1)
|
||||||
|
*/
|
||||||
|
function hexToRgbNormalized(
|
||||||
|
hex: string
|
||||||
|
): { r: number; g: number; b: number } | null {
|
||||||
|
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||||
|
return result
|
||||||
|
? {
|
||||||
|
r: parseInt(result[1], 16) / 255,
|
||||||
|
g: parseInt(result[2], 16) / 255,
|
||||||
|
b: parseInt(result[3], 16) / 255,
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* تبدیل RGB به OKLCH (تقریبی)
|
||||||
|
*/
|
||||||
|
function rgbToOklch(r: number, g: number, b: number): string {
|
||||||
|
// محاسبه Lightness تقریبی
|
||||||
|
const lightness = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
||||||
|
|
||||||
|
// محاسبه Chroma و Hue (ساده شده)
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return `oklch(${lightness.toFixed(3)} ${chroma.toFixed(3)} ${hue.toFixed(
|
||||||
|
1
|
||||||
|
)})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* هوک برای تغییر دینامیک رنگ primary بر اساس رنگ دریافتی از about
|
||||||
|
*/
|
||||||
|
export function useThemeColor({
|
||||||
|
primaryColor,
|
||||||
|
enabled = true,
|
||||||
|
}: UseThemeColorProps) {
|
||||||
|
useEffect(() => {
|
||||||
|
if (!enabled || !primaryColor) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const root = document.documentElement;
|
||||||
|
|
||||||
|
// اگر رنگ hex هست، آن را به oklch تبدیل میکنیم
|
||||||
|
if (primaryColor.startsWith("#")) {
|
||||||
|
const rgb = hexToRgbNormalized(primaryColor);
|
||||||
|
if (rgb) {
|
||||||
|
const oklchColor = rgbToOklch(rgb.r, rgb.g, rgb.b);
|
||||||
|
|
||||||
|
// اعمال رنگ primary
|
||||||
|
root.style.setProperty("--primary", oklchColor);
|
||||||
|
|
||||||
|
// محاسبه رنگ foreground متناسب
|
||||||
|
const brightness = (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000;
|
||||||
|
const foregroundOklch =
|
||||||
|
brightness > 0.5 ? "oklch(0 0 0)" : "oklch(1 0 0)";
|
||||||
|
root.style.setProperty("--primary-foreground", foregroundOklch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error setting theme color:", error);
|
||||||
|
}
|
||||||
|
}, [primaryColor, enabled]);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user