feat(org-admin): Remove organization admin console

This commit is contained in:
x032205
2025-08-07 18:39:57 -04:00
parent fc5d42baf0
commit eedc5f533e
13 changed files with 54 additions and 338 deletions
@@ -1,39 +0,0 @@
import { useState } from "react";
import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
import { OrgAdminProjects } from "./components/OrgAdminProjects";
enum TabSections {
Projects = "projects"
}
export const AdminPage = () => {
const { t } = useTranslation();
const [activeTab, setActiveTab] = useState<TabSections>(TabSections.Projects);
return (
<>
<Helmet>
<title>{t("common.head-title", { title: t("settings.org.title") })}</title>
</Helmet>
<div className="flex w-full justify-center bg-bunker-800 text-white">
<div className="w-full max-w-7xl">
<PageHeader
title="Organization Admin Console"
description="View and manage resources across your organization."
/>
<Tabs value={activeTab} onValueChange={(el) => setActiveTab(el as TabSections)}>
<TabList>
<Tab value={TabSections.Projects}>Projects</Tab>
</TabList>
<TabPanel value={TabSections.Projects}>
<OrgAdminProjects />
</TabPanel>
</Tabs>
</div>
</div>
</>
);
};
@@ -1,169 +0,0 @@
import { useState } from "react";
import { faEllipsis, faMagnifyingGlass, faSignIn } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNavigate } from "@tanstack/react-router";
import { format } from "date-fns";
import { motion } from "framer-motion";
import { createNotification } from "@app/components/notifications";
import {
Button,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
EmptyState,
Input,
Pagination,
Spinner,
Table,
TableContainer,
TableSkeleton,
TBody,
Td,
Th,
THead,
Tr
} from "@app/components/v2";
import {
OrgPermissionAdminConsoleAction,
OrgPermissionSubjects
} from "@app/context/OrgPermissionContext/types";
import { getProjectHomePage } from "@app/helpers/project";
import { withPermission } from "@app/hoc";
import { useDebounce } from "@app/hooks";
import { useOrgAdminAccessProject, useOrgAdminGetProjects } from "@app/hooks/api";
import { ProjectType } from "@app/hooks/api/workspace/types";
export const OrgAdminProjects = withPermission(
() => {
const [page, setPage] = useState(1);
const [search, setSearch] = useState("");
const [debouncedSearch] = useDebounce(search);
const [perPage, setPerPage] = useState(25);
const navigate = useNavigate();
const orgAdminAccessProject = useOrgAdminAccessProject();
const { data, isPending: isProjectsLoading } = useOrgAdminGetProjects({
offset: (page - 1) * perPage,
limit: perPage,
search: debouncedSearch || undefined
});
const projects = data?.projects || [];
const projectCount = data?.count || 0;
const isEmpty = !isProjectsLoading && projects.length === 0;
const handleAccessProject = async (type: ProjectType, projectId: string) => {
try {
await orgAdminAccessProject.mutateAsync({
projectId
});
await navigate({
to: getProjectHomePage(type),
params: {
projectId
}
});
} catch {
createNotification({
text: "Failed to access project",
type: "error"
});
}
};
return (
<motion.div
key="panel-projects"
transition={{ duration: 0.15 }}
initial={{ opacity: 0, translateX: 30 }}
animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: 30 }}
>
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4 flex justify-between">
<p className="text-xl font-semibold text-mineshaft-100">Projects</p>
</div>
<div>
<Input
value={search}
onChange={(e) => setSearch(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
placeholder="Search by project name"
/>
<TableContainer className="mt-4">
<Table>
<THead>
<Tr>
<Th>Name</Th>
<Th>Slug</Th>
<Th>Created At</Th>
<Th className="w-5" />
</Tr>
</THead>
<TBody>
{isProjectsLoading && <TableSkeleton columns={4} innerKey="projects" />}
{!isProjectsLoading &&
projects?.map(({ name, slug, createdAt, id, type }) => (
<Tr key={`project-${id}`} className="group w-full">
<Td>{name}</Td>
<Td>{slug}</Td>
<Td>{format(new Date(createdAt), "yyyy-MM-dd, hh:mm aaa")}</Td>
<Td>
<div>
<DropdownMenu>
<DropdownMenuTrigger asChild className="rounded-lg">
<Button
variant="link"
className="text-bunker-300 hover:text-primary-400 data-[state=open]:text-primary-400"
>
<FontAwesomeIcon size="sm" icon={faEllipsis} />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="p-1">
<DropdownMenuItem
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
handleAccessProject(type, id);
}}
icon={<FontAwesomeIcon icon={faSignIn} />}
disabled={
orgAdminAccessProject.variables?.projectId === id &&
orgAdminAccessProject.isPending
}
>
Access{" "}
{orgAdminAccessProject.variables?.projectId === id &&
orgAdminAccessProject.isPending && <Spinner size="xs" />}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</Td>
</Tr>
))}
</TBody>
</Table>
{!isProjectsLoading && (
<Pagination
count={projectCount}
page={page}
perPage={perPage}
onChangePage={(newPage) => setPage(newPage)}
onChangePerPage={(newPerPage) => setPerPage(newPerPage)}
/>
)}
{isEmpty && <EmptyState title="No projects found" />}
</TableContainer>
</div>
</div>
</motion.div>
);
},
{
action: OrgPermissionAdminConsoleAction.AccessAllProjects,
subject: OrgPermissionSubjects.AdminConsole
}
);
@@ -1 +0,0 @@
export { OrgAdminProjects } from "./OrgAdminProjects";
@@ -1,16 +0,0 @@
import { createFileRoute } from "@tanstack/react-router";
import { AdminPage } from "./AdminPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/organization/admin"
)({
component: AdminPage,
context: () => ({
breadcrumbs: [
{
label: "Admin Console"
}
]
})
});
@@ -35,6 +35,7 @@ import {
Tooltip
} from "@app/components/v2";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
import { OrgPermissionAdminConsoleAction } from "@app/context/OrgPermissionContext/types";
import { getProjectHomePage, getProjectLottieIcon, getProjectTitle } from "@app/helpers/project";
import {
getUserTablePreference,
@@ -42,7 +43,11 @@ import {
setUserTablePreference
} from "@app/helpers/userTablePreferences";
import { useDebounce, usePagination, usePopUp, useResetPageHelper } from "@app/hooks";
import { useRequestProjectAccess, useSearchProjects } from "@app/hooks/api";
import {
useOrgAdminAccessProject,
useRequestProjectAccess,
useSearchProjects
} from "@app/hooks/api";
import { ProjectType, Workspace } from "@app/hooks/api/workspace/types";
type Props = {
@@ -120,6 +125,8 @@ export const AllProjectView = ({
initPerPage: getUserTablePreference("allProjectsTable", PreferenceKey.PerPage, 50)
});
const orgAdminAccessProject = useOrgAdminAccessProject();
const handlePerPageChange = (newPerPage: number) => {
setPerPage(newPerPage);
setUserTablePreference("allProjectsTable", PreferenceKey.PerPage, newPerPage);
@@ -137,6 +144,25 @@ export const AllProjectView = ({
type: projectTypeFilter
});
const handleAccessProject = async (type: ProjectType, projectId: string) => {
try {
await orgAdminAccessProject.mutateAsync({
projectId
});
await navigate({
to: getProjectHomePage(type),
params: {
projectId
}
});
} catch {
createNotification({
text: "Failed to access project",
type: "error"
});
}
};
useResetPageHelper({
setPage,
offset,
@@ -325,7 +351,7 @@ export const AllProjectView = ({
<span>Joined</span>
</Badge>
) : (
<div className="opacity-0 transition-all group-hover:opacity-100">
<div className="flex gap-2 opacity-0 transition-all group-hover:opacity-100">
<Button
size="xs"
variant="outline_bg"
@@ -333,6 +359,32 @@ export const AllProjectView = ({
>
Request Access
</Button>
<OrgPermissionCan
I={OrgPermissionAdminConsoleAction.AccessAllProjects}
an={OrgPermissionSubjects.AdminConsole}
>
{(isAllowed) =>
isAllowed && (
<Button
size="xs"
variant="star"
colorSchema="primary"
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
handleAccessProject(workspace.type, workspace.id);
}}
disabled={
orgAdminAccessProject.variables?.projectId === workspace.id &&
orgAdminAccessProject.isPending
}
>
Admin Access
</Button>
)
}
</OrgPermissionCan>
</div>
)}
</div>