fix admin user related issues
Build and Push Docker Images / Build and Push Docker Images (push) Failing after 9s
Build and Push Docker Images / Build and Push Docker Images (push) Failing after 9s
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,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,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,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">
|
||||||
|
|||||||
@@ -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,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">
|
||||||
|
|||||||
Reference in New Issue
Block a user