fix admin user related issues
Build and Push Docker Images / Build and Push Docker Images (push) Failing after 9s

This commit is contained in:
2026-08-03 21:44:57 +05:00
parent 5244caa37e
commit d2fd218ee4
12 changed files with 41 additions and 28 deletions
+2 -2
View File
@@ -11,7 +11,7 @@ import {
TableHeader, TableHeader,
TableRow, TableRow,
} from "@/components/ui/table"; } from "@/components/ui/table";
import { userAtom } from "@/lib/auth-store"; import { isAdminUser, userAtom } from "@/lib/auth-store";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { getDevices } from "@/queries/devices"; import { getDevices } from "@/queries/devices";
import BlockDeviceDialog from "../block-device-dialog"; import BlockDeviceDialog from "../block-device-dialog";
@@ -22,7 +22,7 @@ import Pagination from "../pagination";
export function AdminDevicesTable() { export function AdminDevicesTable() {
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const user = useAtomValue(userAtom); const user = useAtomValue(userAtom);
const isAdmin = user?.is_admin; const isAdmin = isAdminUser(user);
const page = Number.parseInt(searchParams.get("page") as string) || 1; const page = Number.parseInt(searchParams.get("page") as string) || 1;
const limit = 10; const limit = 10;
+2 -2
View File
@@ -10,7 +10,7 @@ import {
TableHeader, TableHeader,
TableRow, TableRow,
} from "@/components/ui/table"; } from "@/components/ui/table";
import { userAtom } from "@/lib/auth-store"; import { isAdminUser, userAtom } from "@/lib/auth-store";
import { getDevices } from "@/queries/devices"; import { getDevices } from "@/queries/devices";
import DevicesTableSkeleton from "./device-table-skeleton"; import DevicesTableSkeleton from "./device-table-skeleton";
import ClickableRow from "./clickable-row"; import ClickableRow from "./clickable-row";
@@ -27,7 +27,7 @@ export function DevicesTable({
}) { }) {
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const user = useAtomValue(userAtom); const user = useAtomValue(userAtom);
const isAdmin = user?.is_admin; const isAdmin = isAdminUser(user);
const page = Number.parseInt(searchParams.get("page") as string) || 1; const page = Number.parseInt(searchParams.get("page") as string) || 1;
const limit = 10; const limit = 10;
+2 -2
View File
@@ -29,7 +29,7 @@ import {
SidebarMenuItem, SidebarMenuItem,
SidebarRail, SidebarRail,
} from "@/components/ui/sidebar"; } from "@/components/ui/sidebar";
import { userAtom } from "@/lib/auth-store"; import { isAdminUser, userAtom } from "@/lib/auth-store";
type Permission = { type Permission = {
id: number; id: number;
@@ -141,7 +141,7 @@ export function AppSidebar({
]; ];
let CATEGORIES: Categories; let CATEGORIES: Categories;
if (user?.is_admin) { if (isAdminUser(user)) {
CATEGORIES = categories; CATEGORIES = categories;
} else { } else {
// Filter out ADMIN CONTROL category for non-admin users // Filter out ADMIN CONTROL category for non-admin users
+10
View File
@@ -42,6 +42,16 @@ export function getStoredUser(): AuthUser | null {
return store.get(userAtom); return store.get(userAtom);
} }
/**
* Single source of truth for "is this user an administrator?" on the client.
* Mirrors the backend `user_is_admin` helper: any of the app-specific
* `is_admin` flag, Django `is_staff`, or `is_superuser` grants admin access.
* Use this for every admin route/UI gate instead of checking `is_admin` alone.
*/
export function isAdminUser(user: AuthUser | null | undefined): boolean {
return Boolean(user && (user.is_admin || user.is_staff || user.is_superuser));
}
/** Persist token + user on successful login. */ /** Persist token + user on successful login. */
export function setAuth(token: string, user: AuthUser): void { export function setAuth(token: string, user: AuthUser): void {
store.set(tokenAtom, token); store.set(tokenAtom, token);
+1
View File
@@ -64,6 +64,7 @@ export interface AuthUser {
last_name: string; last_name: string;
email: string; email: string;
is_admin: boolean; is_admin: boolean;
is_staff: boolean;
is_superuser: boolean; is_superuser: boolean;
last_login: string; last_login: string;
date_joined: string; date_joined: string;
+2 -2
View File
@@ -4,7 +4,7 @@ import { Navigate, useParams } from "react-router-dom";
import ClientErrorMessage from "@/components/client-error-message"; import ClientErrorMessage from "@/components/client-error-message";
import FullPageLoader from "@/components/full-page-loader"; import FullPageLoader from "@/components/full-page-loader";
import UserAgreementForm from "@/components/user/user-agreement-form"; import UserAgreementForm from "@/components/user/user-agreement-form";
import { userAtom } from "@/lib/auth-store"; import { isAdminUser, userAtom } from "@/lib/auth-store";
import { getProfileById } from "@/queries/users"; import { getProfileById } from "@/queries/users";
export default function UserAgreement() { export default function UserAgreement() {
@@ -17,7 +17,7 @@ export default function UserAgreement() {
enabled: !!userId, enabled: !!userId,
}); });
if (!authUser?.is_admin) return <Navigate to="/devices" replace />; if (!isAdminUser(authUser)) return <Navigate to="/devices" replace />;
if (isLoading) return <FullPageLoader />; if (isLoading) return <FullPageLoader />;
if (error) return <ClientErrorMessage message={error.message} />; if (error) return <ClientErrorMessage message={error.message} />;
if (!user) return null; if (!user) return null;
+12 -10
View File
@@ -80,7 +80,7 @@ export default function UserDetails() {
</div> </div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 items-start justify-start"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4 items-start justify-start">
<div id="database-information"> <div id="database-information">
<h4 className="p-2 rounded font-semibold">Database Information</h4> <h4 className="p-2 rounded font-semibold">Customer Input Information</h4>
<div className="shadow-md p-2 bg-sarLinkOrange/10 border border-dashed border-sarLinkOrange rounded-lg space-y-1 my-2 grid grid-cols-1 md:grid-cols-2 gap-2"> <div className="shadow-md p-2 bg-sarLinkOrange/10 border border-dashed border-sarLinkOrange rounded-lg space-y-1 my-2 grid grid-cols-1 md:grid-cols-2 gap-2">
<InputReadOnly <InputReadOnly
showCheck showCheck
@@ -191,15 +191,17 @@ export default function UserDetails() {
label="Phone Number" label="Phone Number"
value={nationalData?.primary_contact ?? ""} value={nationalData?.primary_contact ?? ""}
/> />
<div className="flex flex-col col-span-2 items-center justify-center"> {nationalData?.image_url && (
<img <div className="flex flex-col col-span-2 items-center justify-center">
src={nationalData?.image_url || "https://i.pravatar.cc/300"} <img
height={100} src={nationalData.image_url}
width={100} height={100}
className="object-fit aspect-square rounded-full" width={100}
alt="id photo" className="object-fit aspect-square rounded-full"
/> alt="id photo"
</div> />
</div>
)}
</div> </div>
</div> </div>
} }
+2 -2
View File
@@ -2,11 +2,11 @@ import { useAtomValue } from "jotai";
import { Navigate } from "react-router-dom"; import { Navigate } from "react-router-dom";
import { AdminDevicesTable } from "@/components/admin/admin-devices-table"; import { AdminDevicesTable } from "@/components/admin/admin-devices-table";
import DynamicFilter from "@/components/generic-filter"; import DynamicFilter from "@/components/generic-filter";
import { userAtom } from "@/lib/auth-store"; import { isAdminUser, userAtom } from "@/lib/auth-store";
export default function UserDevices() { export default function UserDevices() {
const user = useAtomValue(userAtom); const user = useAtomValue(userAtom);
if (!user?.is_admin) return <Navigate to="/devices" replace />; if (!isAdminUser(user)) return <Navigate to="/devices" replace />;
return ( return (
<div> <div>
<div className="flex justify-between items-center border rounded-md border-dashed font-bold title-bg py-4 px-2 mb-4"> <div className="flex justify-between items-center border rounded-md border-dashed font-bold title-bg py-4 px-2 mb-4">
+2 -2
View File
@@ -2,11 +2,11 @@ import { useAtomValue } from "jotai";
import { Navigate } from "react-router-dom"; import { Navigate } from "react-router-dom";
import { UsersPaymentsTable } from "@/components/admin/user-payments-table"; import { UsersPaymentsTable } from "@/components/admin/user-payments-table";
import DynamicFilter from "@/components/generic-filter"; import DynamicFilter from "@/components/generic-filter";
import { userAtom } from "@/lib/auth-store"; import { isAdminUser, userAtom } from "@/lib/auth-store";
export default function UserPayments() { export default function UserPayments() {
const user = useAtomValue(userAtom); const user = useAtomValue(userAtom);
if (!user?.is_admin) return <Navigate to="/payments" replace />; if (!isAdminUser(user)) return <Navigate to="/payments" replace />;
return ( return (
<div> <div>
+2 -2
View File
@@ -2,11 +2,11 @@ import { useAtomValue } from "jotai";
import { Navigate } from "react-router-dom"; import { Navigate } from "react-router-dom";
import { AdminTopupsTable } from "@/components/admin/admin-topup-table"; import { AdminTopupsTable } from "@/components/admin/admin-topup-table";
import DynamicFilter from "@/components/generic-filter"; import DynamicFilter from "@/components/generic-filter";
import { userAtom } from "@/lib/auth-store"; import { isAdminUser, userAtom } from "@/lib/auth-store";
export default function UserTopups() { export default function UserTopups() {
const user = useAtomValue(userAtom); const user = useAtomValue(userAtom);
if (!user?.is_admin) return <Navigate to="/top-ups" replace />; if (!isAdminUser(user)) return <Navigate to="/top-ups" replace />;
return ( return (
<div> <div>
<div className="flex justify-between items-center border rounded-md border-dashed font-bold title-bg py-4 px-2 mb-4"> <div className="flex justify-between items-center border rounded-md border-dashed font-bold title-bg py-4 px-2 mb-4">
+2 -2
View File
@@ -4,7 +4,7 @@ import { Navigate, useParams } from "react-router-dom";
import ClientErrorMessage from "@/components/client-error-message"; import ClientErrorMessage from "@/components/client-error-message";
import FullPageLoader from "@/components/full-page-loader"; import FullPageLoader from "@/components/full-page-loader";
import UserUpdateForm from "@/components/user/user-update-form"; import UserUpdateForm from "@/components/user/user-update-form";
import { userAtom } from "@/lib/auth-store"; import { isAdminUser, userAtom } from "@/lib/auth-store";
import { getProfileById } from "@/queries/users"; import { getProfileById } from "@/queries/users";
export default function UserUpdate() { export default function UserUpdate() {
@@ -17,7 +17,7 @@ export default function UserUpdate() {
enabled: !!userId, enabled: !!userId,
}); });
if (!authUser?.is_admin) return <Navigate to="/devices" replace />; if (!isAdminUser(authUser)) return <Navigate to="/devices" replace />;
if (isLoading) return <FullPageLoader />; if (isLoading) return <FullPageLoader />;
if (error) return <ClientErrorMessage message={error.message} />; if (error) return <ClientErrorMessage message={error.message} />;
if (!user) return null; if (!user) return null;
+2 -2
View File
@@ -2,11 +2,11 @@ import { useAtomValue } from "jotai";
import { Navigate } from "react-router-dom"; import { Navigate } from "react-router-dom";
import DynamicFilter from "@/components/generic-filter"; import DynamicFilter from "@/components/generic-filter";
import { UsersTable } from "@/components/user-table"; import { UsersTable } from "@/components/user-table";
import { userAtom } from "@/lib/auth-store"; import { isAdminUser, userAtom } from "@/lib/auth-store";
export default function Users() { export default function Users() {
const user = useAtomValue(userAtom); const user = useAtomValue(userAtom);
if (!user?.is_admin) return <Navigate to="/devices" replace />; if (!isAdminUser(user)) return <Navigate to="/devices" replace />;
return ( return (
<div> <div>
<div className="flex justify-between items-center border rounded-md border-dashed font-bold title-bg py-4 px-2 mb-4"> <div className="flex justify-between items-center border rounded-md border-dashed font-bold title-bg py-4 px-2 mb-4">