diff --git a/.bolt/extra-store.log b/.bolt/extra-store.log new file mode 100644 index 0000000..12f19ad --- /dev/null +++ b/.bolt/extra-store.log @@ -0,0 +1,9 @@ + +> @workspace/extra-store@0.0.0 dev /home/ubuntu/executor/workspace/artifacts/extra-store +> vite --config vite.config.ts --host 0.0.0.0 + + + VITE v7.3.1 ready in 737 ms + + ➜ Local: http://localhost:3001/ + ➜ Network: http://10.128.0.201:3001/ diff --git a/.bolt/extra-store.pid b/.bolt/extra-store.pid new file mode 100644 index 0000000..b33a0a9 --- /dev/null +++ b/.bolt/extra-store.pid @@ -0,0 +1 @@ +47713 diff --git a/artifacts/extra-store/src/App.tsx b/artifacts/extra-store/src/App.tsx index cd4e71f..cb83ca4 100644 --- a/artifacts/extra-store/src/App.tsx +++ b/artifacts/extra-store/src/App.tsx @@ -1357,9 +1357,12 @@ function Header() { }); }; - const extraCats = - allCats?.filter((c) => !c.source || c.source === "extra") ?? []; - const sheinTree = tree?.filter((n) => n.source === "shein") ?? []; + const extraCats = Array.isArray(allCats) + ? allCats.filter((c) => !c.source || c.source === "extra") + : []; + const sheinTree = Array.isArray(tree) + ? tree.filter((n) => n.source === "shein") + : []; return (
@@ -1762,6 +1765,14 @@ function Footer() { ))} +
  • + + {lang === "en" ? "Admin Panel" : "لوحة المسؤول"} + +
  • @@ -5088,6 +5099,7 @@ function Router() { if (isAdmin) return ( + ); diff --git a/artifacts/extra-store/src/lib/admin-preview-api.ts b/artifacts/extra-store/src/lib/admin-preview-api.ts index 7dfb671..1da9bd6 100644 --- a/artifacts/extra-store/src/lib/admin-preview-api.ts +++ b/artifacts/extra-store/src/lib/admin-preview-api.ts @@ -80,6 +80,23 @@ function getBaseApiPath() { } } +function buildCategoryTree(categories: any[]) { + const sorted = [...categories].sort( + (a, b) => + Number(a?.sort_order || 0) - Number(b?.sort_order || 0) || + Number(a?.id || 0) - Number(b?.id || 0), + ); + + return sorted + .filter((category) => !category?.parent_id) + .map((category) => ({ + ...category, + children: sorted.filter( + (child) => Number(child?.parent_id || 0) === Number(category.id), + ), + })); +} + function seedStoreSettings() { return { store_name_ar: "اكسترا السعودية", @@ -1207,6 +1224,8 @@ function handlePreviewApi(url: URL, init?: RequestInit) { return json({ ok: true }); } + if (path === "/categories/tree" && method === "GET") + return json(buildCategoryTree(db.categories)); if (path === "/categories" && method === "GET") return json(db.categories); if (path === "/categories" && method === "POST") { const id = db.nextIds.categories++; diff --git a/artifacts/extra-store/src/pages/Admin.tsx b/artifacts/extra-store/src/pages/Admin.tsx index e371bff..5a6ac70 100644 --- a/artifacts/extra-store/src/pages/Admin.tsx +++ b/artifacts/extra-store/src/pages/Admin.tsx @@ -1,4 +1,5 @@ import { useState, useEffect, useRef, useCallback } from "react"; +import { useLocation } from "wouter"; import { API } from "../lib/api"; import { isJsonResponse, loginPreviewAdmin } from "../lib/mock-auth"; import { installPreviewAdminApi } from "../lib/admin-preview-api"; @@ -337,19 +338,50 @@ const TABS = [ { id: "settings", name: "الإعدادات", icon: Settings }, ]; +type AdminTabId = (typeof TABS)[number]["id"]; +const ADMIN_TABS = new Set(TABS.map((tab) => tab.id)); + +function getAdminTabFromPath(pathname: string): AdminTabId { + const parts = pathname.split("/").filter(Boolean); + const candidate = (parts[1] || "dashboard") as AdminTabId; + return ADMIN_TABS.has(candidate) ? candidate : "dashboard"; +} + // ─── Login ────────────────────────────────────────────── export default function AdminPage() { - const [token, setToken] = useState(localStorage.getItem("admin_token")); - const [username, setUsername] = useState(""); - const [password, setPassword] = useState(""); + const [token, setToken] = useState(() => + typeof window === "undefined" ? null : localStorage.getItem("admin_token"), + ); + const [username, setUsername] = useState("admin"); + const [password, setPassword] = useState("admin123"); const [showPass, setShowPass] = useState(false); const [loading, setLoading] = useState(false); - const [remember, setRemember] = useState(false); + const [remember, setRemember] = useState(() => + typeof window !== "undefined" && localStorage.getItem("admin_remember") === "1", + ); useEffect(() => { installPreviewAdminApi(); + if (typeof window === "undefined") return; + if (!localStorage.getItem("admin_token") && localStorage.getItem("admin_remember") === "1") { + try { + const session = loginPreviewAdmin({ username: "admin", password: "admin123" }); + localStorage.setItem("admin_token", session.token); + setToken(session.token); + } catch (_) {} + } }, []); + const handleQuickPreviewLogin = () => { + unlockAudio(); + const session = loginPreviewAdmin({ username: "admin", password: "admin123" }); + localStorage.setItem("admin_token", session.token); + if (remember) localStorage.setItem("admin_remember", "1"); + else localStorage.removeItem("admin_remember"); + setToken(session.token); + adminToast("تم فتح لوحة التحكم الكاملة بوضع المعاينة", "ok"); + }; + const handleLogin = async (e: React.FormEvent) => { e.preventDefault(); unlockAudio(); // unlock browser audio on this user gesture @@ -379,6 +411,7 @@ export default function AdminPage() { localStorage.setItem("admin_token", data.token); if (remember) localStorage.setItem("admin_remember", "1"); + else localStorage.removeItem("admin_remember"); setToken(data.token); } catch (error) { alert(error instanceof Error ? error.message : "بيانات الدخول غير صحيحة"); @@ -466,10 +499,20 @@ export default function AdminPage() { {loading && } دخول + -

    - admin / admin123 -

    +
    +

    admin / admin123

    + + العودة إلى المتجر + +
    ); @@ -561,7 +604,10 @@ function notifToneClasses(tone: string) { } function AdminDashboard({ onLogout }: { onLogout: () => void }) { - const [activeTab, setActiveTab] = useState("dashboard"); + const [location, setLocation] = useLocation(); + const [activeTab, setActiveTab] = useState(() => + typeof window === "undefined" ? "dashboard" : getAdminTabFromPath(window.location.pathname), + ); const [sidebarOpen, setSidebarOpen] = useState(false); const [checkoutNotifs, setCheckoutNotifs] = useState([]); const [showNotifPanel, setShowNotifPanel] = useState(false); @@ -571,6 +617,19 @@ function AdminDashboard({ onLogout }: { onLogout: () => void }) { const lastEventId = useRef(0); const eventsInitialized = useRef(false); + useEffect(() => { + const nextTab = getAdminTabFromPath(location); + setActiveTab((current) => (current === nextTab ? current : nextTab)); + }, [location]); + + const goToTab = useCallback( + (tabId: AdminTabId) => { + setActiveTab(tabId); + setLocation(tabId === "dashboard" ? "/admin" : `/admin/${tabId}`); + }, + [setLocation], + ); + const pollOrders = useCallback(async () => { try { const res = await fetch(`${API}/orders?limit=1`); @@ -790,7 +849,7 @@ function AdminDashboard({ onLogout }: { onLogout: () => void }) {