update
This commit is contained in:
@@ -1,42 +1,41 @@
|
||||
'use client';
|
||||
|
||||
// context زبان سایت: نگهداری زبان فعلی (fa/en)، ذخیره در localStorage، و تنظیم جهت صفحه (rtl/ltr).
|
||||
|
||||
import { createContext, useContext, useEffect, useState } from 'react';
|
||||
import { content } from '@/data/content';
|
||||
import {createContext, useContext, useEffect, useState} from 'react';
|
||||
import {content} from '@/data/content';
|
||||
|
||||
const LanguageContext = createContext(null);
|
||||
|
||||
export function LanguageProvider({ children }) {
|
||||
const [locale, setLocale] = useState('fa');
|
||||
const [mounted, setMounted] = useState(false);
|
||||
export function LanguageProvider({children}) {
|
||||
const [locale, setLocale] = useState('fa');
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const saved = window.localStorage.getItem('rm-locale');
|
||||
if (saved === 'fa' || saved === 'en') setLocale(saved);
|
||||
setMounted(true);
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
const saved = window.localStorage.getItem('rm-locale');
|
||||
if (saved === 'fa' || saved === 'en') setLocale(saved);
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const root = document.documentElement;
|
||||
root.lang = locale;
|
||||
root.dir = content[locale].dir;
|
||||
window.localStorage.setItem('rm-locale', locale);
|
||||
}, [locale]);
|
||||
useEffect(() => {
|
||||
const root = document.documentElement;
|
||||
root.lang = locale;
|
||||
root.dir = content[locale].dir;
|
||||
window.localStorage.setItem('rm-locale', locale);
|
||||
}, [locale]);
|
||||
|
||||
const toggleLocale = () => setLocale((l) => (l === 'fa' ? 'en' : 'fa'));
|
||||
const toggleLocale = () => setLocale((l) => (l === 'fa' ? 'en' : 'fa'));
|
||||
|
||||
return (
|
||||
<LanguageContext.Provider
|
||||
value={{ locale, setLocale, toggleLocale, t: content[locale], dir: content[locale].dir, mounted }}
|
||||
>
|
||||
{children}
|
||||
</LanguageContext.Provider>
|
||||
);
|
||||
return (
|
||||
<LanguageContext.Provider
|
||||
value={{locale, setLocale, toggleLocale, t: content[locale], dir: content[locale].dir, mounted}}
|
||||
>
|
||||
{children}
|
||||
</LanguageContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useLanguage() {
|
||||
const ctx = useContext(LanguageContext);
|
||||
if (!ctx) throw new Error('useLanguage must be used within LanguageProvider');
|
||||
return ctx;
|
||||
const ctx = useContext(LanguageContext);
|
||||
if (!ctx) throw new Error('useLanguage must be used within LanguageProvider');
|
||||
return ctx;
|
||||
}
|
||||
|
||||
@@ -1,47 +1,44 @@
|
||||
'use client';
|
||||
|
||||
import { createContext, useContext, useEffect, useState } from 'react';
|
||||
import {createContext, useContext, useEffect, useState} from 'react';
|
||||
|
||||
const ThemeContext = createContext(null);
|
||||
|
||||
export function ThemeProvider({ children }) {
|
||||
// پیشفرض سایت حالا «روشن» (light) است. اگر کاربر قبلاً تم را عوض کرده باشد
|
||||
// (ذخیرهشده در localStorage) همان ترجیح رو حفظ میکنیم؛ در غیر این صورت فقط
|
||||
// اگر سیستم کاربر صراحتاً روی حالت تیره تنظیم شده باشد، تیره را انتخاب میکنیم.
|
||||
const [theme, setTheme] = useState('light');
|
||||
const [mounted, setMounted] = useState(false);
|
||||
export function ThemeProvider({children}) {
|
||||
|
||||
// On mount: read saved preference, else fall back to system preference
|
||||
// (but default stays light unless the OS explicitly prefers dark).
|
||||
useEffect(() => {
|
||||
const saved = window.localStorage.getItem('rm-theme');
|
||||
if (saved === 'light' || saved === 'dark') {
|
||||
setTheme(saved);
|
||||
} else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
setTheme('dark');
|
||||
}
|
||||
setMounted(true);
|
||||
}, []);
|
||||
const [theme, setTheme] = useState('light');
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
// Reflect theme on <html> class + persist.
|
||||
useEffect(() => {
|
||||
const root = document.documentElement;
|
||||
root.classList.remove('light', 'dark');
|
||||
root.classList.add(theme);
|
||||
window.localStorage.setItem('rm-theme', theme);
|
||||
}, [theme]);
|
||||
|
||||
const toggleTheme = () => setTheme((t) => (t === 'dark' ? 'light' : 'dark'));
|
||||
useEffect(() => {
|
||||
const saved = window.localStorage.getItem('rm-theme');
|
||||
if (saved === 'light' || saved === 'dark') {
|
||||
setTheme(saved);
|
||||
} else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
setTheme('dark');
|
||||
}
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={{ theme, setTheme, toggleTheme, mounted }}>
|
||||
{children}
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const root = document.documentElement;
|
||||
root.classList.remove('light', 'dark');
|
||||
root.classList.add(theme);
|
||||
window.localStorage.setItem('rm-theme', theme);
|
||||
}, [theme]);
|
||||
|
||||
const toggleTheme = () => setTheme((t) => (t === 'dark' ? 'light' : 'dark'));
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={{theme, setTheme, toggleTheme, mounted}}>
|
||||
{children}
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useTheme() {
|
||||
const ctx = useContext(ThemeContext);
|
||||
if (!ctx) throw new Error('useTheme must be used within ThemeProvider');
|
||||
return ctx;
|
||||
const ctx = useContext(ThemeContext);
|
||||
if (!ctx) throw new Error('useTheme must be used within ThemeProvider');
|
||||
return ctx;
|
||||
}
|
||||
|
||||
+15
-18
@@ -1,29 +1,26 @@
|
||||
import { Vazirmatn } from 'next/font/google';
|
||||
import {Vazirmatn} from 'next/font/google';
|
||||
import './globals.css';
|
||||
import Providers from './providers';
|
||||
|
||||
const vazirmatn = Vazirmatn({
|
||||
subsets: ['arabic'],
|
||||
variable: '--font-vazirmatn',
|
||||
display: 'swap',
|
||||
subsets: ['arabic'],
|
||||
variable: '--font-vazirmatn',
|
||||
display: 'swap',
|
||||
});
|
||||
|
||||
export const metadata = {
|
||||
title: 'روبات مارکت | دستگاههای وندینگ هوشمند',
|
||||
description:
|
||||
'روبات مارکت؛ دستگاههای فروش خودکار هوشمند با مدیریت آنلاین، تحویل نرم و طراحی مدرن.',
|
||||
title: 'روبات مارکت | دستگاههای وندینگ هوشمند',
|
||||
description:
|
||||
'روبات مارکت؛ دستگاههای فروش خودکار هوشمند با مدیریت آنلاین، تحویل نرم و طراحی مدرن.',
|
||||
};
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
// پوسته پیشفرض سمت سرور: فارسی + حالت روشن (Light).
|
||||
// ThemeProvider/LanguageProvider بعد از mount شدن، اگر کاربر قبلاً تم/زبان
|
||||
// دیگری را انتخاب کرده بود (ذخیرهشده در localStorage)، آن را روی <html> اعمال
|
||||
// میکنند؛ suppressHydrationWarning جلوی هشدار بیضرر عدمتطابق را میگیرد.
|
||||
return (
|
||||
<html lang="fa" dir="rtl" className={`${vazirmatn.variable} light`} suppressHydrationWarning>
|
||||
<body className="bg-page text-primary antialiased transition-colors duration-300">
|
||||
export default function RootLayout({children}) {
|
||||
|
||||
return (
|
||||
<html lang="fa" dir="rtl" className={`${vazirmatn.variable} light`} suppressHydrationWarning>
|
||||
<body className="bg-page text-primary antialiased transition-colors duration-300">
|
||||
<Providers>{children}</Providers>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
+49
-49
@@ -2,60 +2,60 @@
|
||||
|
||||
// پل ارتباطی بین Redux، حالت تیره/روشن، و زبان فا/en با تنظیمات ConfigProvider آنتدیزاین.
|
||||
|
||||
import { Provider as ReduxProvider } from 'react-redux';
|
||||
import { ConfigProvider, theme as antdThemeApi } from 'antd';
|
||||
import {Provider as ReduxProvider} from 'react-redux';
|
||||
import {ConfigProvider, theme as antdThemeApi} from 'antd';
|
||||
import faIR from 'antd/locale/fa_IR';
|
||||
import enUS from 'antd/locale/en_US';
|
||||
import { store } from '@/store/store';
|
||||
import { ThemeProvider, useTheme } from './context/ThemeContext';
|
||||
import { LanguageProvider, useLanguage } from './context/LanguageContext';
|
||||
import {store} from '@/store/store';
|
||||
import {ThemeProvider, useTheme} from './context/ThemeContext';
|
||||
import {LanguageProvider, useLanguage} from './context/LanguageContext';
|
||||
|
||||
function AntdBridge({ children }) {
|
||||
const { theme } = useTheme();
|
||||
const { locale, dir } = useLanguage();
|
||||
function AntdBridge({children}) {
|
||||
const {theme} = useTheme();
|
||||
const {locale, dir} = useLanguage();
|
||||
|
||||
const antdTheme = {
|
||||
algorithm: theme === 'dark' ? antdThemeApi.darkAlgorithm : antdThemeApi.defaultAlgorithm,
|
||||
token: {
|
||||
colorPrimary: '#00a693',
|
||||
colorInfo: '#00a693',
|
||||
borderRadius: 10,
|
||||
fontFamily: 'var(--font-vazirmatn), Tahoma, sans-serif',
|
||||
colorBgContainer: theme === 'dark' ? '#0c2b24' : '#ffffff',
|
||||
colorBgElevated: theme === 'dark' ? '#0f332b' : '#ffffff',
|
||||
colorBorder: theme === 'dark' ? '#0b3d38' : '#c0ebe3',
|
||||
colorText: theme === 'dark' ? '#e6f7f4' : '#04231f',
|
||||
colorTextSecondary: theme === 'dark' ? 'rgba(230,247,244,0.7)' : 'rgba(4,35,31,0.65)',
|
||||
},
|
||||
components: {
|
||||
Button: { controlHeight: 44, fontWeight: 600 },
|
||||
Badge: { colorError: '#ff6b57' },
|
||||
Modal: {
|
||||
contentBg: theme === 'dark' ? '#0c2b24' : '#ffffff',
|
||||
headerBg: theme === 'dark' ? '#0c2b24' : '#ffffff',
|
||||
titleColor: theme === 'dark' ? '#e6f7f4' : '#04231f',
|
||||
},
|
||||
Drawer: {
|
||||
colorBgElevated: theme === 'dark' ? '#0c2b24' : '#ffffff',
|
||||
},
|
||||
},
|
||||
};
|
||||
const antdTheme = {
|
||||
algorithm: theme === 'dark' ? antdThemeApi.darkAlgorithm : antdThemeApi.defaultAlgorithm,
|
||||
token: {
|
||||
colorPrimary: '#00a693',
|
||||
colorInfo: '#00a693',
|
||||
borderRadius: 10,
|
||||
fontFamily: 'var(--font-vazirmatn), Tahoma, sans-serif',
|
||||
colorBgContainer: theme === 'dark' ? '#0c2b24' : '#ffffff',
|
||||
colorBgElevated: theme === 'dark' ? '#0f332b' : '#ffffff',
|
||||
colorBorder: theme === 'dark' ? '#0b3d38' : '#c0ebe3',
|
||||
colorText: theme === 'dark' ? '#e6f7f4' : '#04231f',
|
||||
colorTextSecondary: theme === 'dark' ? 'rgba(230,247,244,0.7)' : 'rgba(4,35,31,0.65)',
|
||||
},
|
||||
components: {
|
||||
Button: {controlHeight: 44, fontWeight: 600},
|
||||
Badge: {colorError: '#ff6b57'},
|
||||
Modal: {
|
||||
contentBg: theme === 'dark' ? '#0c2b24' : '#ffffff',
|
||||
headerBg: theme === 'dark' ? '#0c2b24' : '#ffffff',
|
||||
titleColor: theme === 'dark' ? '#e6f7f4' : '#04231f',
|
||||
},
|
||||
Drawer: {
|
||||
colorBgElevated: theme === 'dark' ? '#0c2b24' : '#ffffff',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<ConfigProvider direction={dir} locale={locale === 'fa' ? faIR : enUS} theme={antdTheme}>
|
||||
{children}
|
||||
</ConfigProvider>
|
||||
);
|
||||
return (
|
||||
<ConfigProvider direction={dir} locale={locale === 'fa' ? faIR : enUS} theme={antdTheme}>
|
||||
{children}
|
||||
</ConfigProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Providers({ children }) {
|
||||
return (
|
||||
<ReduxProvider store={store}>
|
||||
<ThemeProvider>
|
||||
<LanguageProvider>
|
||||
<AntdBridge>{children}</AntdBridge>
|
||||
</LanguageProvider>
|
||||
</ThemeProvider>
|
||||
</ReduxProvider>
|
||||
);
|
||||
export default function Providers({children}) {
|
||||
return (
|
||||
<ReduxProvider store={store}>
|
||||
<ThemeProvider>
|
||||
<LanguageProvider>
|
||||
<AntdBridge>{children}</AntdBridge>
|
||||
</LanguageProvider>
|
||||
</ThemeProvider>
|
||||
</ReduxProvider>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user