فرانتاند: ویرایش زنده درجا — دوقلوی دیجیتال سایت برای ادمین
لایه ویرایش بصری روی سایت واقعی (بدون داشبورد جدا): - ورود ادمین از مسیر پنهان /admin/login با توکن JWT (localStorage) و AdminContext شامل isEditMode و وضعیت ذخیره (همگام/در حال ذخیره/خطا) - AdminBar شناور پایین صفحه (فقط برای کاربر واردشده): کلید حالت ویرایش، وضعیت ذخیره، خروج — حالت ویرایش در sessionStorage میماند - InlineEdit: در حالت عادی دقیقاً همان HTML قبلی (بدون تغییر چیدمان)؛ در حالت ویرایش کادرخطچین + contentEditable با ذخیره خودکار در blur (Enter ذخیره، Esc لغو) — وایرایششده روی همه متنهای سایت: هیرو، تیتر بخشها، هیرو صفحات، داستان، فرم تماس، خبرنامه، فوتر، منو، برچسبهای وضعیت پروژه و... - کشوی عمومی EntityDrawer با پیکربندی متمرکز برای ۱۴ مجموعه (خدمات، پروژهها، مقالات، نظرات، سوالات، تیم، تایملاین، آمار، چرا آرامش، مدلهای سرمایهگذاری، مراحل کار، دفاتر، گالری، منو): ویرایش چندفیلدی، افزودن، حذف و جابهجایی ترتیب (▲▼) با ذخیره خودکار هر فیلد - دکمههای شناور ویرایش/حذف/ترتیب فقط در حالت ویرایش روی آیتمها - کشوهای یگانه: برند و لوگو، تنظیمات هیرو، فرم تماس، خبرنامه، فوتر، قبل/بعد و نقلقول داستان — آپلود تصویر به MinIO از همانجا - بهروزرسانی خوشبینانه SiteContext (updateSite) پس از هر PATCH؛ مسیرها در حالت ویرایش کلیک نمیشوند؛ breadcrumb محافظ beforeunload
This commit is contained in:
+25
-6
@@ -16,6 +16,9 @@ import { ArticleDetail } from './pages/ArticleDetail';
|
||||
import { FaqPage } from './pages/FaqPage';
|
||||
import { ContactPage } from './pages/ContactPage';
|
||||
import { SiteProvider, useSiteData } from './api';
|
||||
import { AdminProvider, useAdmin } from './admin/AdminContext';
|
||||
import { AdminBar } from './admin/AdminBar';
|
||||
import { AdminLoginPage } from './admin/AdminLoginPage';
|
||||
|
||||
function ScrollToTop() {
|
||||
const { pathname } = useLocation();
|
||||
@@ -35,8 +38,9 @@ function AnimatedRoutes() {
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.28, ease: [0.23, 1, 0.32, 1] }}>
|
||||
|
||||
|
||||
<Routes location={location}>
|
||||
<Route path="/admin/login" element={<AdminLoginPage />} />
|
||||
<Route path="/" element={<Home />} />
|
||||
<Route path="/about" element={<About />} />
|
||||
<Route path="/services" element={<ServicesPage />} />
|
||||
@@ -54,6 +58,22 @@ function AnimatedRoutes() {
|
||||
|
||||
}
|
||||
|
||||
function AppChrome() {
|
||||
const { pathname } = useLocation();
|
||||
const { isAuthenticated } = useAdmin();
|
||||
const isLoginPage = pathname === '/admin/login';
|
||||
|
||||
return (
|
||||
<>
|
||||
{!isLoginPage && <Header />}
|
||||
<AnimatedRoutes />
|
||||
{!isLoginPage && <Footer />}
|
||||
{!isLoginPage && <FloatingContact />}
|
||||
{isAuthenticated && <AdminBar />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function AppShell() {
|
||||
const { data, error, reload } = useSiteData();
|
||||
|
||||
@@ -81,10 +101,7 @@ function AppShell() {
|
||||
) : data ? (
|
||||
<BrowserRouter>
|
||||
<ScrollToTop />
|
||||
<Header />
|
||||
<AnimatedRoutes />
|
||||
<Footer />
|
||||
<FloatingContact />
|
||||
<AppChrome />
|
||||
</BrowserRouter>
|
||||
) : null}
|
||||
</div>
|
||||
@@ -99,7 +116,9 @@ export function App() {
|
||||
|
||||
return (
|
||||
<SiteProvider>
|
||||
<AppShell />
|
||||
<AdminProvider>
|
||||
<AppShell />
|
||||
</AdminProvider>
|
||||
</SiteProvider>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user