import React, { useEffect } from 'react'; import { BrowserRouter, Route, Routes, useLocation } from 'react-router-dom'; import { AnimatePresence, motion } from 'framer-motion'; import { Preloader } from './components/Preloader'; import { Header } from './components/Header'; import { Footer } from './components/Footer'; import { FloatingContact } from './components/FloatingContact'; // چتبات موقتاً خاموش است — برای روشنکردن، این ایمپورت و خط رندر پایین را برگردانید // import { ChatWidget } from './components/ChatWidget'; import { Home } from './pages/Home'; import { About } from './pages/About'; import { ServicesPage } from './pages/ServicesPage'; import { Projects } from './pages/Projects'; import { ProjectDetail } from './pages/ProjectDetail'; import { Investment } from './pages/Investment'; import { Blog } from './pages/Blog'; import { ArticleDetail } from './pages/ArticleDetail'; import { FaqPage } from './pages/FaqPage'; import { ContactPage } from './pages/ContactPage'; import { SiteProvider, useSiteData, useSite } from './api'; import { applySeo, seoKeyForPath } from './seo'; import { AdminProvider, useAdmin } from './admin/AdminContext'; import { AdminBar } from './admin/AdminBar'; import { AdminLoginPage } from './admin/AdminLoginPage'; import { AdminArticlesPage } from './admin/AdminArticlesPage'; import { ArticleWritePage } from './admin/ArticleWritePage'; import { ProjectWritePage } from './admin/ProjectWritePage'; import { LangProvider, useLang } from './i18n/lang'; // مسیرهای عمومی یک بار تعریف میشوند و دو بار سرو میشوند: ریشه فارسی و /en انگلیسی const PUBLIC_ROUTES: { path: string; element: React.ReactElement }[] = [ { path: '/', element: }, { path: '/about', element: }, { path: '/services', element: }, { path: '/projects', element: }, { path: '/projects/:slug', element: }, { path: '/investment', element: }, { path: '/blog', element: }, { path: '/blog/:slug', element: }, { path: '/faq', element: }, { path: '/contact', element: }, ]; // فرزند مستقیم باید باشد — آرایههای Route بهجای کامپوننت واسط const FA_ROUTES = PUBLIC_ROUTES.map(({ path, element }) => ( )); const EN_ROUTES = PUBLIC_ROUTES.map(({ path, element }) => ( )); function ScrollToTop() { const { pathname } = useLocation(); useEffect(() => { window.scrollTo({ top: 0, behavior: 'auto' }); }, [pathname]); return null; } function AnimatedRoutes() { const location = useLocation(); return ( } /> } /> } /> } /> } /> } /> {FA_ROUTES} } /> {EN_ROUTES} ); } function AppChrome() { const { pathname } = useLocation(); const { isAuthenticated } = useAdmin(); const data = useSite(); const { lang } = useLang(); const isAdminPage = pathname.startsWith('/admin'); // عنوان و توضیح متای هر صفحه از دیتابیس (سئو) — جز صفحههای جزئیات که خودشان ست میکنند useEffect(() => { if (isAdminPage) return; const key = seoKeyForPath(pathname); if (!key) return; const meta = data.seo_meta?.find((s) => s.page_key === key); const brand = data.brand.name; const navLabel = data.nav.find((n) => n.href === `/${key}`)?.label; // alternate انگلیسی فقط وقتی اعلام میشود که متای انگلیسی واقعاً ثبت شده باشد const hasEn = !!(meta && ((meta.title_en ?? '').trim() || (meta.description_en ?? '').trim())); applySeo({ title: meta?.title || (key === 'home' ? brand : navLabel ? `${navLabel} | ${brand}` : brand), description: meta?.description ?? '', path: pathname, lang, hasEn, }); }, [pathname, data, isAdminPage, lang]); return ( <> {!isAdminPage && } {!isAdminPage && } {!isAdminPage && } {/* چتبات موقتاً خاموش است — برای روشنکردن، این خط را از حالت کامنت خارج کنید */} {/* {!isAdminPage && } */} {isAuthenticated && !isAdminPage && } > ); } function AppShell() { const { data, error, reload } = useSiteData(); const { t, dir, lang } = useLang(); // overflow-x-clip جای overflow-x-hidden: اسکرولافزار نمیسازد، پس position: sticky // (فهرست مطالب مقاله) به صفحه میچسبد و با محتوا پایین نمیرود؛ برش افقی هم حفظ میشود. return ( {error ? ( {t('errorTitle')} {t('errorText')} {t('retry')} ) : data ? ( <> > ) : ( // اسکلت اولیه تا رسیدن دادههای API — صفحه هرگز خالی رندر نمیشود. // (خزندههای رندرکننده — مثل گوگل — صفحهٔ خالی را Soft 404 میبینند) {t('brandFallback')} {t('loading')} )} ); } export function App() { return ( ); }
{t('errorText')}
{t('loading')}