2026-01-26 10:14:10 +03:30
|
|
|
|
import { useEffect, useMemo, useCallback } from "react";
|
|
|
|
|
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
|
|
|
|
import { RouterProvider } from "@tanstack/react-router";
|
|
|
|
|
|
import { ToastContainer } from "react-toastify";
|
|
|
|
|
|
|
|
|
|
|
|
import {
|
|
|
|
|
|
useUserProfileStore,
|
|
|
|
|
|
useUserStore,
|
|
|
|
|
|
} from "./context/zustand-store/userStore";
|
|
|
|
|
|
import { makeRouter } from "./routes/routes";
|
|
|
|
|
|
import { useDarkMode } from "./hooks/useDarkMode";
|
|
|
|
|
|
import { ItemWithSubItems } from "./types/userPermissions";
|
|
|
|
|
|
import { useFetchProfile } from "./hooks/useFetchProfile";
|
2026-02-01 15:49:32 +03:30
|
|
|
|
import { getInspectionMenuItems } from "./config/menuItems";
|
2026-01-26 10:14:10 +03:30
|
|
|
|
|
|
|
|
|
|
const versionNumber = "/src/version.txt";
|
|
|
|
|
|
import "./index.css";
|
|
|
|
|
|
import "react-toastify/dist/ReactToastify.css";
|
|
|
|
|
|
import { checkIsMobile } from "./utils/checkIsMobile";
|
|
|
|
|
|
|
|
|
|
|
|
const queryClient = new QueryClient();
|
|
|
|
|
|
|
|
|
|
|
|
function AppContent() {
|
|
|
|
|
|
const auth = useUserStore((s) => s.auth);
|
|
|
|
|
|
const { profile } = useUserProfileStore();
|
|
|
|
|
|
const { getProfile } = useFetchProfile();
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (auth && !profile) {
|
|
|
|
|
|
getProfile();
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [auth, profile, getProfile]);
|
|
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default function App() {
|
|
|
|
|
|
const auth = useUserStore((s) => s.auth);
|
|
|
|
|
|
const { profile } = useUserProfileStore();
|
|
|
|
|
|
const [isDark] = useDarkMode();
|
|
|
|
|
|
|
|
|
|
|
|
const menuItems: ItemWithSubItems[] = useMemo(() => {
|
|
|
|
|
|
const userPermissions = profile?.permissions || [];
|
|
|
|
|
|
|
|
|
|
|
|
const permissionsArray = Array.isArray(userPermissions)
|
|
|
|
|
|
? userPermissions.filter((p): p is string => typeof p === "string")
|
|
|
|
|
|
: [];
|
|
|
|
|
|
return getInspectionMenuItems(permissionsArray);
|
|
|
|
|
|
}, [profile?.permissions]);
|
|
|
|
|
|
|
|
|
|
|
|
const router = useMemo(() => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const newRouter = makeRouter(auth ?? null);
|
|
|
|
|
|
if (!newRouter) {
|
|
|
|
|
|
console.error("Router creation returned null");
|
|
|
|
|
|
return makeRouter(null);
|
|
|
|
|
|
}
|
|
|
|
|
|
return newRouter;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("Router creation error:", error);
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
return makeRouter(null);
|
|
|
|
|
|
} catch (fallbackError) {
|
|
|
|
|
|
console.error("Fallback router creation failed:", fallbackError);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [auth, menuItems]);
|
|
|
|
|
|
|
|
|
|
|
|
const hardRefresh = useCallback(() => {
|
|
|
|
|
|
const url = new URL(window.location.href);
|
|
|
|
|
|
url.searchParams.set("refresh", Date.now().toString());
|
|
|
|
|
|
window.location.href = url.toString();
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
|
|
const runWhenIdle = useCallback((fn: () => void) => {
|
|
|
|
|
|
const ric = (window as any).requestIdleCallback;
|
|
|
|
|
|
if (typeof ric === "function") {
|
|
|
|
|
|
ric(fn);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
setTimeout(fn, 300);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
let aborted = false;
|
|
|
|
|
|
const controller = new AbortController();
|
|
|
|
|
|
const checkVersion = () => {
|
|
|
|
|
|
if (document.visibilityState !== "visible") return;
|
|
|
|
|
|
fetch(versionNumber + `?_=${Date.now()}`, {
|
|
|
|
|
|
signal: controller.signal,
|
|
|
|
|
|
cache: "no-store",
|
|
|
|
|
|
})
|
|
|
|
|
|
.then((res) => res.text())
|
|
|
|
|
|
.then(async (txt) => {
|
|
|
|
|
|
if (aborted) return;
|
|
|
|
|
|
const latest = txt.trim();
|
|
|
|
|
|
const stored = localStorage.getItem("AppVersion");
|
|
|
|
|
|
if (latest && latest !== stored) {
|
|
|
|
|
|
localStorage.setItem("AppVersion", latest);
|
|
|
|
|
|
const clearAndReload = async () => {
|
|
|
|
|
|
if ("caches" in window) {
|
|
|
|
|
|
const names = await caches.keys();
|
|
|
|
|
|
for (const n of names) {
|
|
|
|
|
|
await caches.delete(n).catch(() => undefined);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
setTimeout(hardRefresh, 200);
|
|
|
|
|
|
};
|
|
|
|
|
|
runWhenIdle(clearAndReload);
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
.catch(() => {});
|
|
|
|
|
|
};
|
|
|
|
|
|
runWhenIdle(checkVersion);
|
|
|
|
|
|
return () => {
|
|
|
|
|
|
aborted = true;
|
|
|
|
|
|
controller.abort();
|
|
|
|
|
|
};
|
|
|
|
|
|
}, [hardRefresh, runWhenIdle]);
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
const url = new URL(window.location.href);
|
|
|
|
|
|
if (url.searchParams.has("refresh")) {
|
|
|
|
|
|
url.searchParams.delete("refresh");
|
|
|
|
|
|
window.history.replaceState(
|
|
|
|
|
|
{},
|
|
|
|
|
|
document.title,
|
2026-02-01 15:49:32 +03:30
|
|
|
|
url.pathname + url.search,
|
2026-01-26 10:14:10 +03:30
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
|
|
if (!router) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="flex items-center justify-center min-h-screen">
|
|
|
|
|
|
<div className="text-center">
|
|
|
|
|
|
<h1 className="text-2xl font-bold mb-4">در حال بارگذاری...</h1>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div
|
|
|
|
|
|
className={
|
|
|
|
|
|
isDark ? "dark:bg-dark-900 dark-scrollbar" : "bg-white light-scrollbar"
|
|
|
|
|
|
}
|
|
|
|
|
|
>
|
|
|
|
|
|
<QueryClientProvider client={queryClient}>
|
|
|
|
|
|
<AppContent />
|
|
|
|
|
|
<RouterProvider router={router} />
|
|
|
|
|
|
</QueryClientProvider>
|
|
|
|
|
|
<ToastContainer position="bottom-right" rtl={true} theme="light" />
|
|
|
|
|
|
{checkIsMobile() && <div className="h-20"></div>}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|