+
+
نمای کلی
+ بهروزرسانی خودکار هر دقیقه
+
+
+ {/* Money + today */}
+
امروز و مالی
+
+
+
+
+ 0 ? "red" : "default"}
+ />
+
+
+
+
+ {/* Chart */}
+
+
+
+
+ {/* Orders status */}
+
سفارشها
+
+
+
+
+
+
+
+
+
+ {/* Sellers + Users + Products */}
+
فروشگاهها و کاربران
+
+
+ 0 ? "red" : "default"}
+ />
+
+
+
+
+
+
+ );
+}
diff --git a/app/routes/admin.tsx b/app/routes/admin.tsx
new file mode 100644
index 0000000..65c63cb
--- /dev/null
+++ b/app/routes/admin.tsx
@@ -0,0 +1,144 @@
+import { Link, Outlet, useLocation } from "@remix-run/react";
+import {
+ LayoutDashboard,
+ ClipboardList,
+ Store,
+ Users,
+ Package,
+ Landmark,
+ ArrowRight,
+ ShieldCheck,
+ Loader2,
+} from "lucide-react";
+import type { LucideIcon } from "lucide-react";
+import { useRequireAdmin } from "~/hooks/useRequireAdmin";
+
+interface NavItem {
+ label: string;
+ to: string;
+ icon: LucideIcon;
+ exact?: boolean;
+ soon?: boolean; // planned but not built yet
+}
+
+const NAV: NavItem[] = [
+ { label: "نمای کلی", to: "/admin", icon: LayoutDashboard, exact: true },
+ { label: "سفارشها", to: "/admin/orders", icon: ClipboardList, soon: true },
+ { label: "فروشگاهها", to: "/admin/sellers", icon: Store, soon: true },
+ { label: "کاربران", to: "/admin/users", icon: Users, soon: true },
+ { label: "محصولات", to: "/admin/products", icon: Package, soon: true },
+ { label: "دفتر مالی", to: "/admin/ledger", icon: Landmark, soon: true },
+];
+
+export default function AdminLayout() {
+ const location = useLocation();
+ const { isLoading, isAdmin } = useRequireAdmin();
+
+ const isActive = (item: NavItem) =>
+ item.exact
+ ? location.pathname === item.to
+ : location.pathname.startsWith(item.to);
+
+ if (isLoading || !isAdmin) {
+ return (
+
+ {/* Desktop sidebar */}
+
+
+ {/* Mobile top bar + horizontal nav */}
+
+
+
+ {NAV.map((n) => {
+ const active = isActive(n);
+ const cls = `whitespace-nowrap px-3 py-1.5 rounded-full text-[13px] font-semibold ${
+ active ? "bg-BLACK text-white" : "bg-WHITE2 text-BLACK2"
+ } ${n.soon ? "opacity-45" : ""}`;
+ return n.soon ? (
+
+ {n.label}
+
+ ) : (
+
+ {n.label}
+
+ );
+ })}
+
+
+
+
+
+
+
+ );
+}
diff --git a/app/utils/token-manager.server.ts b/app/utils/token-manager.server.ts
index 73983e7..d15e4ba 100644
--- a/app/utils/token-manager.server.ts
+++ b/app/utils/token-manager.server.ts
@@ -30,6 +30,7 @@ const protectedRoutes = [
"/profile/setting",
"/cart",
"/store",
+ "/admin",
];
export async function validateTokens(request: Request) {
const session = await sessionStorage.getSession(
diff --git a/src/api/types/models/index.ts b/src/api/types/models/index.ts
index 59f4454..aa772d0 100644
--- a/src/api/types/models/index.ts
+++ b/src/api/types/models/index.ts
@@ -6959,7 +6959,13 @@ export interface UserProfile {
*/
readonly isStaff?: boolean;
/**
- *
+ *
+ * @type {boolean}
+ * @memberof UserProfile
+ */
+ readonly isSuperuser?: boolean;
+ /**
+ *
* @type {boolean}
* @memberof UserProfile
*/