UNPKG

@rsc-labs/medusa-rbac

Version:
1,218 lines 106 kB
import { jsx, Fragment, jsxs } from "react/jsx-runtime"; import { defineWidgetConfig, defineRouteConfig } from "@medusajs/admin-sdk"; import { Container, Alert, Heading, Text, Button, Select, Drawer, Label, Table, Badge, Input, toast, FocusModal, ProgressTabs, DropdownMenu, IconButton, clx, Prompt, Switch, Toaster } from "@medusajs/ui"; import React, { useState, useEffect, useMemo, createElement } from "react"; import { Grid2, CircularProgress } from "@mui/material"; import { Users, AcademicCap, LockClosedSolid, EllipsisHorizontal, Trash, Pencil } from "@medusajs/icons"; import { Link, useNavigate, useParams } from "react-router-dom"; import { useForm } from "react-hook-form"; var PermissionType = /* @__PURE__ */ ((PermissionType2) => { PermissionType2["PREDEFINED"] = "predefined"; PermissionType2["CUSTOM"] = "custom"; return PermissionType2; })(PermissionType || {}); var PermissionMatcherType = /* @__PURE__ */ ((PermissionMatcherType2) => { PermissionMatcherType2["API"] = "api"; return PermissionMatcherType2; })(PermissionMatcherType || {}); var PermissionActionType = /* @__PURE__ */ ((PermissionActionType2) => { PermissionActionType2["READ"] = "read"; PermissionActionType2["WRITE"] = "write"; PermissionActionType2["DELETE"] = "delete"; return PermissionActionType2; })(PermissionActionType || {}); var AdminRbacPolicyType = /* @__PURE__ */ ((AdminRbacPolicyType2) => { AdminRbacPolicyType2["DENY"] = "deny"; AdminRbacPolicyType2["ALLOW"] = "allow"; return AdminRbacPolicyType2; })(AdminRbacPolicyType || {}); function getActionMessage(actionType) { switch (actionType) { case PermissionActionType.READ: return `read, so you cannot view the content`; case PermissionActionType.WRITE: return `write, so you are not able to create the content`; case PermissionActionType.DELETE: return `delete, so you are not able to delete the content`; default: return "unknown action"; } } const ActionsList = ({ actionTypes }) => { return /* @__PURE__ */ jsx("ul", { children: actionTypes.map((actionType) => { return /* @__PURE__ */ jsx("li", { children: ` - ${getActionMessage(actionType)}` }); }) }); }; const TestMyAuthorization = ({ urlToTest }) => { const [isLoading, setLoading] = useState(true); const [authorizationResult, setAuthorizationResult] = useState(void 0); useEffect(() => { if (!isLoading) { return; } fetch(`/admin/rbac/check`, { method: "POST", credentials: "include", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ urlToTest }) }).then((res) => res.json()).then((responseJson) => { if (JSON.stringify(responseJson) === "{}") { setAuthorizationResult(void 0); } else { setAuthorizationResult(responseJson); } setLoading(false); }).catch((error) => { console.error(error); }); }, [isLoading]); return /* @__PURE__ */ jsx(Fragment, { children: !isLoading && authorizationResult && authorizationResult.denied.length > 0 && /* @__PURE__ */ jsx(Container, { children: /* @__PURE__ */ jsxs(Alert, { variant: "error", children: [ `You are unauthorized to:`, /* @__PURE__ */ jsx(ActionsList, { actionTypes: authorizationResult.denied }) ] }) }) }); }; const CustomersUnAuthorized = () => { return /* @__PURE__ */ jsx(TestMyAuthorization, { urlToTest: "/admin/customers" }); }; defineWidgetConfig({ zone: "customer.list.before" }); const OrdersUnauthorized = () => { return /* @__PURE__ */ jsx(TestMyAuthorization, { urlToTest: "/admin/orders" }); }; defineWidgetConfig({ zone: "order.list.before" }); const PriceListsUnauthorized = () => { return /* @__PURE__ */ jsx(TestMyAuthorization, { urlToTest: "/admin/price-lists" }); }; defineWidgetConfig({ zone: "price_list.list.before" }); const ProductsUnauthorized = () => { return /* @__PURE__ */ jsx(TestMyAuthorization, { urlToTest: "/admin/products" }); }; defineWidgetConfig({ zone: "product.list.before" }); const DashboardMembersCard = () => { const [members, setMembers] = useState([]); const [isLoading, setLoading] = useState(true); useEffect(() => { if (!isLoading) { return; } fetch(`/admin/rbac/members`, { credentials: "include" }).then((res) => res.json()).then((result) => { setMembers(result); setLoading(false); }).catch((error) => { console.error(error); }); }, [isLoading]); return /* @__PURE__ */ jsx(Container, { children: /* @__PURE__ */ jsxs(Grid2, { container: true, direction: "column", spacing: 3, children: [ /* @__PURE__ */ jsxs(Grid2, { container: true, alignItems: "center", children: [ /* @__PURE__ */ jsxs(Grid2, { children: [ isLoading && /* @__PURE__ */ jsx(CircularProgress, { size: 10 }), !isLoading && /* @__PURE__ */ jsxs(Heading, { children: [ members.length, " ", members.length === 1 ? "member" : "members" ] }) ] }), /* @__PURE__ */ jsx(Grid2, { children: !isLoading && /* @__PURE__ */ jsx(Users, {}) }) ] }), /* @__PURE__ */ jsxs(Grid2, { children: [ isLoading && /* @__PURE__ */ jsx(CircularProgress, { size: 10 }), !isLoading && /* @__PURE__ */ jsxs(Text, { children: [ members.filter((member) => member.role !== void 0).length, " assigned" ] }) ] }), /* @__PURE__ */ jsxs(Grid2, { children: [ isLoading && /* @__PURE__ */ jsx(CircularProgress, { size: 10 }), !isLoading && /* @__PURE__ */ jsxs(Text, { children: [ members.filter((member) => member.role === void 0).length, " unassigned" ] }) ] }), /* @__PURE__ */ jsx(Grid2, { children: /* @__PURE__ */ jsx( Link, { to: `/rbac/members`, style: { display: "contents" }, children: /* @__PURE__ */ jsx(Button, { children: "Configure" }) } ) }) ] }) }); }; const DashboardRolesCard = () => { const [roles, setRoles] = useState([]); const [isLoading, setLoading] = useState(true); useEffect(() => { if (!isLoading) { return; } fetch(`/admin/rbac/roles`, { credentials: "include" }).then((res) => res.json()).then((roles2) => { setRoles(roles2); setLoading(false); }).catch((error) => { console.error(error); }); }, [isLoading]); return /* @__PURE__ */ jsx(Container, { children: /* @__PURE__ */ jsxs(Grid2, { container: true, direction: "column", spacing: 3, children: [ /* @__PURE__ */ jsxs(Grid2, { container: true, alignItems: "center", children: [ /* @__PURE__ */ jsxs(Grid2, { children: [ isLoading && /* @__PURE__ */ jsx(CircularProgress, { size: 10 }), !isLoading && /* @__PURE__ */ jsxs(Heading, { children: [ roles.length, " ", roles.length === 1 ? "role" : "roles" ] }) ] }), /* @__PURE__ */ jsx(Grid2, { children: !isLoading && /* @__PURE__ */ jsx(AcademicCap, {}) }) ] }), /* @__PURE__ */ jsxs(Grid2, { children: [ isLoading && /* @__PURE__ */ jsx(CircularProgress, { size: 10 }), !isLoading && /* @__PURE__ */ jsxs(Text, { children: [ roles.filter((role) => role.users !== void 0 && role.users.length > 0).length, " used" ] }) ] }), /* @__PURE__ */ jsxs(Grid2, { children: [ isLoading && /* @__PURE__ */ jsx(CircularProgress, { size: 10 }), !isLoading && /* @__PURE__ */ jsxs(Text, { children: [ roles.filter((role) => role.users === void 0 || role.users.length === 0).length, " not used" ] }) ] }), /* @__PURE__ */ jsx(Grid2, { children: /* @__PURE__ */ jsx( Link, { to: `/rbac/roles`, style: { display: "contents" }, children: /* @__PURE__ */ jsx(Button, { children: "Configure" }) } ) }) ] }) }); }; const AssignedRolesList = ({ sortedRoles }) => { return /* @__PURE__ */ jsxs(Grid2, { container: true, direction: "column", rowSpacing: 2, children: [ sortedRoles.map((sortedRole) => { return /* @__PURE__ */ jsxs(Grid2, { container: true, columnSpacing: 1, alignItems: "center", justifyContent: "space-between", children: [ /* @__PURE__ */ jsx(Grid2, { children: /* @__PURE__ */ jsx(Text, { children: sortedRole.name }) }), /* @__PURE__ */ jsxs(Grid2, { container: true, columnSpacing: 1, alignItems: "center", children: [ /* @__PURE__ */ jsx(Grid2, { children: /* @__PURE__ */ jsx(Text, { children: sortedRole.users.length }) }), /* @__PURE__ */ jsx(Grid2, { children: /* @__PURE__ */ jsx(Users, {}) }) ] }) ] }, sortedRole.id); }), sortedRoles.length === 0 && /* @__PURE__ */ jsx(Grid2, { children: /* @__PURE__ */ jsx(Text, { children: `-` }) }) ] }); }; const DashboardAssignedRolesCard = () => { const [roles, setRoles] = useState([]); const [isLoading, setLoading] = useState(true); useEffect(() => { if (!isLoading) { return; } fetch(`/admin/rbac/roles`, { credentials: "include" }).then((res) => res.json()).then((roles2) => { const sorted = roles2.sort((a, b) => b.users.length - a.users.length); setRoles(sorted); setLoading(false); }).catch((error) => { console.error(error); }); }, [isLoading]); return /* @__PURE__ */ jsx(Container, { children: /* @__PURE__ */ jsxs(Grid2, { container: true, direction: "column", spacing: 3, children: [ /* @__PURE__ */ jsxs(Grid2, { children: [ isLoading && /* @__PURE__ */ jsx(CircularProgress, { size: 10 }), !isLoading && /* @__PURE__ */ jsx(Heading, { children: `Most used roles` }) ] }), /* @__PURE__ */ jsxs(Grid2, { children: [ isLoading && /* @__PURE__ */ jsx(CircularProgress, { size: 10 }), !isLoading && /* @__PURE__ */ jsx(AssignedRolesList, { sortedRoles: roles }) ] }) ] }) }); }; const DashboardPermissionsCard = () => { const [permissions, setPermissions] = useState([]); const [isLoading, setLoading] = useState(true); useEffect(() => { if (!isLoading) { return; } fetch(`/admin/rbac/permissions`, { credentials: "include" }).then((res) => res.json()).then((result) => { setPermissions(result); setLoading(false); }).catch((error) => { console.error(error); }); }, [isLoading]); return /* @__PURE__ */ jsx(Container, { children: /* @__PURE__ */ jsxs(Grid2, { container: true, direction: "column", spacing: 3, children: [ /* @__PURE__ */ jsxs(Grid2, { container: true, alignItems: "center", children: [ /* @__PURE__ */ jsxs(Grid2, { children: [ isLoading && /* @__PURE__ */ jsx(CircularProgress, { size: 10 }), !isLoading && /* @__PURE__ */ jsxs(Heading, { children: [ permissions.length, " ", permissions.length === 1 ? "permission" : "permissions" ] }) ] }), /* @__PURE__ */ jsx(Grid2, { children: !isLoading && /* @__PURE__ */ jsx(LockClosedSolid, {}) }) ] }), /* @__PURE__ */ jsxs(Grid2, { children: [ isLoading && /* @__PURE__ */ jsx(CircularProgress, { size: 10 }), !isLoading && /* @__PURE__ */ jsxs(Text, { children: [ permissions.filter((perm) => perm.type == PermissionType.PREDEFINED).length, " predefined" ] }) ] }), /* @__PURE__ */ jsxs(Grid2, { children: [ isLoading && /* @__PURE__ */ jsx(CircularProgress, { size: 10 }), !isLoading && /* @__PURE__ */ jsxs(Text, { children: [ permissions.filter((perm) => perm.type == PermissionType.CUSTOM).length, " custom" ] }) ] }), /* @__PURE__ */ jsx(Grid2, { children: /* @__PURE__ */ jsx( Link, { to: `/rbac/permissions`, style: { display: "contents" }, children: /* @__PURE__ */ jsx(Button, { children: "Configure" }) } ) }) ] }) }); }; const Dashboard = () => { return /* @__PURE__ */ jsxs(Grid2, { container: true, rowSpacing: 10, columnSpacing: 10, style: { marginTop: 15 }, children: [ /* @__PURE__ */ jsx(Grid2, { size: 4, children: /* @__PURE__ */ jsx(DashboardPermissionsCard, {}) }), /* @__PURE__ */ jsx(Grid2, { size: 4, children: /* @__PURE__ */ jsx(DashboardRolesCard, {}) }), /* @__PURE__ */ jsx(Grid2, { size: 4, children: /* @__PURE__ */ jsx(DashboardMembersCard, {}) }), /* @__PURE__ */ jsx(Grid2, { size: 4, children: /* @__PURE__ */ jsx(DashboardAssignedRolesCard, {}) }) ] }); }; const RbacAuthorizationCheck = ({ children }) => { const [isLoading, setLoading] = useState(true); const [authorizationResult, setAuthorizationResult] = useState(void 0); useEffect(() => { if (!isLoading) { return; } fetch(`/admin/rbac/check`, { method: "POST", credentials: "include", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ urlToTest: `/admin/rbac` }) }).then((res) => res.json()).then((responseJson) => { setAuthorizationResult(responseJson); setLoading(false); }).catch((error) => { console.error(error); }); }, [isLoading]); if (isLoading) { return /* @__PURE__ */ jsx(CircularProgress, { size: 12 }); } if (authorizationResult.denied.length > 0) { return /* @__PURE__ */ jsx(Grid2, { container: true, justifyContent: "center", children: /* @__PURE__ */ jsx(Alert, { variant: "error", children: "You are unauthorized to manage RBAC" }) }); } return /* @__PURE__ */ jsx("div", { children }); }; const RbacLicenceCheck = ({ children }) => { const [licenceStatus, setLicenceStatus] = useState(void 0); const [isLoading, setLoading] = useState(true); useEffect(() => { if (!isLoading) { return; } fetch(`/admin/licence`, { credentials: "include" }).then((res) => res.json()).then((result) => { setLicenceStatus(result.licence); setLoading(false); }).catch((error) => { console.error(error); }); }, [isLoading]); if (isLoading) { return /* @__PURE__ */ jsx(CircularProgress, { size: 12 }); } if (licenceStatus == void 0) { return /* @__PURE__ */ jsx(Grid2, { container: true, justifyContent: "center", children: /* @__PURE__ */ jsx(Alert, { variant: "error", children: "Cannot get licence status" }) }); } switch (licenceStatus) { case "EXPIRED": return /* @__PURE__ */ jsx(Grid2, { container: true, justifyContent: "center", children: /* @__PURE__ */ jsx(Alert, { variant: "error", children: "Licence is expired" }) }); case "INVALID": return /* @__PURE__ */ jsx(Grid2, { container: true, justifyContent: "center", children: /* @__PURE__ */ jsx(Alert, { variant: "error", children: "Licence is invalid" }) }); } if (licenceStatus !== "VALID") { return /* @__PURE__ */ jsx(Grid2, { container: true, justifyContent: "center", children: /* @__PURE__ */ jsx(Alert, { variant: "error", children: "Licence is in unknown state. Please contact us." }) }); } return /* @__PURE__ */ jsx("div", { children }); }; const MainPage = () => { return /* @__PURE__ */ jsxs(Grid2, { container: true, direction: "column", rowSpacing: 3, children: [ /* @__PURE__ */ jsx(Grid2, { children: /* @__PURE__ */ jsx(Heading, { level: "h1", children: "RBAC system" }) }), /* @__PURE__ */ jsx(Grid2, { children: /* @__PURE__ */ jsx(Dashboard, {}) }) ] }); }; const RbacPage = () => { return /* @__PURE__ */ jsx(RbacLicenceCheck, { children: /* @__PURE__ */ jsx(RbacAuthorizationCheck, { children: /* @__PURE__ */ jsx(MainPage, {}) }) }); }; const config$3 = defineRouteConfig({ label: "RBAC", icon: Users }); const SelectRole = ({ currentRole, roles, setChosenRole }) => { const [value, setValue] = useState(currentRole ? currentRole.id : void 0); const handleChange = (roleId) => { setValue(roleId); setChosenRole(roles.find((role) => role.id == roleId)); }; return /* @__PURE__ */ jsx("div", { className: "w-[256px]", children: /* @__PURE__ */ jsxs(Select, { onValueChange: handleChange, value, children: [ /* @__PURE__ */ jsx(Select.Trigger, { children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "Select a role" }) }), /* @__PURE__ */ jsx(Select.Content, { children: roles && roles.map((item) => /* @__PURE__ */ jsx(Select.Item, { value: item.id, children: `${item.name}` }, item.id)) }) ] }) }); }; const AvailableRolesList = ({ currentRole, setChosenRole }) => { const [isLoading, setLoading] = useState(true); const [roles, setRoles] = useState([]); useEffect(() => { if (!isLoading) { return; } fetch(`/admin/rbac/roles`, { credentials: "include" }).then((res) => res.json()).then((roles2) => { setRoles(roles2); setLoading(false); }).catch((error) => { console.error(error); }); }, [isLoading]); return /* @__PURE__ */ jsxs(Fragment, { children: [ isLoading && /* @__PURE__ */ jsx(CircularProgress, {}), !isLoading && /* @__PURE__ */ jsx(SelectRole, { currentRole, roles, setChosenRole }) ] }); }; const DrawerEditUser = ({ currentRole, setRole }) => { const [chosenRole, setChosenRole] = useState(currentRole); const [drawerIsOpen, setDrawerIsOpen] = useState(void 0); return /* @__PURE__ */ jsxs(Drawer, { open: drawerIsOpen, onOpenChange: setDrawerIsOpen, children: [ /* @__PURE__ */ jsx(Drawer.Trigger, { asChild: true, children: /* @__PURE__ */ jsx(Button, { children: `Assign` }) }), /* @__PURE__ */ jsxs(Drawer.Content, { children: [ /* @__PURE__ */ jsx(Drawer.Header, { children: /* @__PURE__ */ jsx(Drawer.Title, { children: "Select role" }) }), /* @__PURE__ */ jsx(Drawer.Body, { className: "p-4", children: /* @__PURE__ */ jsxs(Grid2, { container: true, direction: "column", columnSpacing: 10, rowSpacing: 3, children: [ /* @__PURE__ */ jsx(Grid2, { children: /* @__PURE__ */ jsx(Label, { children: "Choose role" }) }), /* @__PURE__ */ jsx(Grid2, { children: /* @__PURE__ */ jsx(AvailableRolesList, { currentRole, setChosenRole }) }) ] }) }), /* @__PURE__ */ jsxs(Drawer.Footer, { children: [ /* @__PURE__ */ jsx(Drawer.Close, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "secondary", children: "Cancel" }) }), /* @__PURE__ */ jsx(Button, { onClick: () => { setRole(chosenRole); setDrawerIsOpen(false); }, children: "Save" }) ] }) ] }) ] }); }; const RoleBadge = ({ role }) => { if (role) { return /* @__PURE__ */ jsx(Badge, { size: "small", color: "green", children: role.name }); } return /* @__PURE__ */ jsx(Badge, { size: "small", children: "Unassigned" }); }; function MembersTable() { const [members, setMembers] = useState([]); const [isLoading, setLoading] = useState(true); const assignRole = (roleId, userId) => { fetch(`/admin/rbac/members/assignments`, { method: "POST", credentials: "include", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ userId, roleId }) }).then((res) => res.json()).then(({ message }) => { setLoading(true); if (message) { throw message; } }).catch((e) => { setLoading(true); console.error(e); }); }; useEffect(() => { if (!isLoading) { return; } fetch(`/admin/rbac/members`, { credentials: "include" }).then((res) => res.json()).then((result) => { setMembers(result); setLoading(false); }).catch((error) => { console.error(error); }); }, [isLoading]); const [currentPage, setCurrentPage] = useState(0); const pageSize = 3; const pageCount = Math.ceil(members.length / pageSize); const canNextPage = useMemo( () => currentPage < pageCount - 1, [currentPage, pageCount] ); const canPreviousPage = useMemo(() => currentPage - 1 >= 0, [currentPage]); const nextPage = () => { if (canNextPage) { setCurrentPage(currentPage + 1); } }; const previousPage = () => { if (canPreviousPage) { setCurrentPage(currentPage - 1); } }; const currentMembers = useMemo(() => { if (isLoading) { return []; } const offset = currentPage * pageSize; const limit = Math.min(offset + pageSize, members.length); return members.slice(offset, limit); }, [currentPage, pageSize, members, isLoading]); return /* @__PURE__ */ jsxs("div", { className: "flex gap-1 flex-col", children: [ /* @__PURE__ */ jsxs(Table, { children: [ /* @__PURE__ */ jsx(Table.Header, { children: /* @__PURE__ */ jsxs(Table.Row, { children: [ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Name" }), /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Email" }), /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Role" }), /* @__PURE__ */ jsx(Table.HeaderCell, {}) ] }) }), /* @__PURE__ */ jsx(Table.Body, { children: currentMembers.map((member) => { const name = member.user.first_name !== null && member.user.last_name !== null ? `${member.user.first_name} ${member.user.last_name}` : "-"; return /* @__PURE__ */ jsxs( Table.Row, { className: "[&_td:last-child]:w-[1%] [&_td:last-child]:whitespace-nowrap", children: [ /* @__PURE__ */ jsx(Table.Cell, { children: name }), /* @__PURE__ */ jsx(Table.Cell, { children: member.user.email }), /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(RoleBadge, { role: member.role }) }), /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(DrawerEditUser, { currentRole: member.role, setRole: (role) => assignRole(role.id, member.user.id) }) }) ] }, member.user.id ); }) }) ] }), /* @__PURE__ */ jsx( Table.Pagination, { count: members.length, pageSize, pageIndex: currentPage, pageCount, canPreviousPage, canNextPage, previousPage, nextPage } ) ] }); } const MembersTable$1 = React.memo(MembersTable); const MembersPage = () => { return /* @__PURE__ */ jsx(RbacLicenceCheck, { children: /* @__PURE__ */ jsx(RbacAuthorizationCheck, { children: /* @__PURE__ */ jsxs(Grid2, { container: true, direction: "column", rowSpacing: 3, children: [ /* @__PURE__ */ jsx(Grid2, { children: /* @__PURE__ */ jsx(Heading, { level: "h1", children: "Members" }) }), /* @__PURE__ */ jsx(Grid2, { children: /* @__PURE__ */ jsx(Container, { children: /* @__PURE__ */ jsx(MembersTable$1, {}) }) }) ] }) }) }); }; const config$2 = defineRouteConfig({ label: "Members" }); const InputCreateCategory = ({ category, setCategory }) => { const [error, setError] = useState(void 0); return /* @__PURE__ */ jsxs(Fragment, { children: [ /* @__PURE__ */ jsx( Input, { value: category, onChange: (e) => setCategory(e.target.value), "aria-invalid": error !== void 0 } ), error !== void 0 && /* @__PURE__ */ jsx(Alert, { variant: "error", children: error }) ] }); }; const DrawerCreateCategory = ({ reload }) => { const [categoryName, setCategoryName] = useState(void 0); const [drawerIsOpen, setDrawerIsOpen] = useState(void 0); const onSubmit = () => { fetch(`/admin/rbac/categories`, { method: "POST", credentials: "include", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: categoryName }) }).then(async (response) => { if (response.ok) { reload(); toast.info("New category has been created", { description: "You can now select it from the list." }); } else { const error = await response.json(); toast.error("Error", { description: `New category cannot be created. ${error.message}` }); } }).catch((e) => { toast.error("Error", { description: `New category cannot be created. ${e.toString()}` }); console.error(e); }); }; return /* @__PURE__ */ jsxs(Drawer, { open: drawerIsOpen, onOpenChange: setDrawerIsOpen, children: [ /* @__PURE__ */ jsx(Drawer.Trigger, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "secondary", children: `Create` }) }), /* @__PURE__ */ jsxs(Drawer.Content, { children: [ /* @__PURE__ */ jsx(Drawer.Header, { children: /* @__PURE__ */ jsx(Drawer.Title, { children: "New category" }) }), /* @__PURE__ */ jsx(Drawer.Body, { children: /* @__PURE__ */ jsxs(Grid2, { container: true, direction: "column", columnSpacing: 10, rowSpacing: 3, children: [ /* @__PURE__ */ jsx(Grid2, { children: /* @__PURE__ */ jsx(Label, { children: "Name" }) }), /* @__PURE__ */ jsx(Grid2, { children: /* @__PURE__ */ jsx(InputCreateCategory, { setCategory: setCategoryName }) }) ] }) }), /* @__PURE__ */ jsxs(Drawer.Footer, { children: [ /* @__PURE__ */ jsx(Drawer.Close, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "secondary", children: "Cancel" }) }), /* @__PURE__ */ jsx(Button, { onClick: () => { onSubmit(); reload(); setDrawerIsOpen(false); }, children: "Save" }) ] }) ] }) ] }); }; const SelectCategory = ({ currentCategory, setChosenCategory }) => { const [value, setValue] = useState(void 0); const [categories, setCategories] = useState([]); const handleChange = (categoryId) => { if (categoryId !== "None") { setValue(categories.find((cat) => cat.id == categoryId)); setChosenCategory(categories.find((cat) => cat.id == categoryId)); } else { setValue(void 0); setChosenCategory("None"); } }; const [isLoading, setLoading] = useState(true); useEffect(() => { if (!isLoading) { return; } fetch(`/admin/rbac/categories`, { credentials: "include" }).then((res) => res.json()).then((result) => { setCategories(result); setLoading(false); }).catch((error) => { console.error(error); }); }, [isLoading]); return /* @__PURE__ */ jsxs(Grid2, { container: true, direction: "column", rowSpacing: 2, children: [ /* @__PURE__ */ jsx(Grid2, { children: /* @__PURE__ */ jsxs(Select, { onValueChange: handleChange, value: value ? value.id : "None", children: [ /* @__PURE__ */ jsx(Select.Trigger, { children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "Select a category" }) }), isLoading && /* @__PURE__ */ jsx(CircularProgress, {}), !isLoading && /* @__PURE__ */ jsxs(Select.Content, { children: [ /* @__PURE__ */ jsx(Select.Item, { value: "None", children: `None` }, "None"), /* @__PURE__ */ jsx(Select.Separator, {}), categories && categories.map((item) => /* @__PURE__ */ jsx(Select.Item, { value: item.id, children: `${item.name}` }, item.id)) ] }) ] }) }), /* @__PURE__ */ jsx(Grid2, { children: /* @__PURE__ */ jsx(Label, { size: "small", children: "You can create new category" }) }), /* @__PURE__ */ jsx(Grid2, { children: /* @__PURE__ */ jsx(DrawerCreateCategory, { reload: () => setLoading(true) }) }) ] }); }; const validateName$2 = (value) => { if (value && value.length > 0) { return true; } return false; }; const CreatePermissionGeneralStep = ({ register, errors, setCategory, currentCategory }) => { return /* @__PURE__ */ jsx(Grid2, { container: true, justifyContent: "center", children: /* @__PURE__ */ jsx(Grid2, { size: 4, children: /* @__PURE__ */ jsxs(Grid2, { container: true, direction: "column", spacing: 1, marginTop: 2, children: [ /* @__PURE__ */ jsx(Grid2, { children: /* @__PURE__ */ jsx(Heading, { level: "h1", children: "Create permission" }) }), /* @__PURE__ */ jsx(Grid2, { children: /* @__PURE__ */ jsx(Text, { children: "Set a name which will describe permission" }) }), /* @__PURE__ */ jsx(Grid2, { container: true, children: /* @__PURE__ */ jsx(Grid2, { children: /* @__PURE__ */ jsxs(Grid2, { container: true, direction: "column", spacing: 1, marginTop: 2, children: [ /* @__PURE__ */ jsx(Grid2, { marginTop: 4, children: /* @__PURE__ */ jsx(Label, { size: "small", children: "Name" }) }), /* @__PURE__ */ jsxs(Grid2, { children: [ /* @__PURE__ */ jsx( Input, { placeholder: "Reading products", ...register("name", { validateName: validateName$2 }), "aria-invalid": errors["name"] !== void 0 } ), errors["name"] !== void 0 && /* @__PURE__ */ jsx(Alert, { variant: "error", children: errors["name"].message }) ] }) ] }) }) }), /* @__PURE__ */ jsx(Grid2, { container: true, children: /* @__PURE__ */ jsx(Grid2, { children: /* @__PURE__ */ jsxs(Grid2, { container: true, direction: "column", spacing: 1, children: [ /* @__PURE__ */ jsx(Grid2, { marginTop: 4, children: /* @__PURE__ */ jsx(Label, { size: "small", children: "Category (optional)" }) }), /* @__PURE__ */ jsx(Grid2, { children: /* @__PURE__ */ jsx(SelectCategory, { currentCategory, setChosenCategory: setCategory }) }) ] }) }) }) ] }) }) }); }; const SelectMatcherType = ({ currentMatcherType, matcherTypes, setChosenMatcherType }) => { const [value, setValue] = useState(currentMatcherType); const handleChange = (matcherType) => { setValue(matcherType); setChosenMatcherType(matcherType); }; return /* @__PURE__ */ jsx("div", { className: "w-[256px]", children: /* @__PURE__ */ jsxs(Select, { onValueChange: handleChange, value, children: [ /* @__PURE__ */ jsx(Select.Trigger, { children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "Select a matcher type" }) }), /* @__PURE__ */ jsx(Select.Content, { children: matcherTypes && matcherTypes.map((item) => /* @__PURE__ */ jsx(Select.Item, { value: item, children: `${item}` }, item)) }) ] }) }); }; const SelectActionType = ({ currentActionType, actionTypes, setChosenActionType }) => { const [value, setValue] = useState(currentActionType); const handleChange = (actionType) => { setValue(actionType); setChosenActionType(actionType); }; return /* @__PURE__ */ jsx("div", { className: "w-[256px]", children: /* @__PURE__ */ jsxs(Select, { onValueChange: handleChange, value, children: [ /* @__PURE__ */ jsx(Select.Trigger, { children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "Select an action type" }) }), /* @__PURE__ */ jsx(Select.Content, { children: actionTypes && actionTypes.map((item) => /* @__PURE__ */ jsx(Select.Item, { value: item, children: `${item}` }, item)) }) ] }) }); }; const validateName$1 = (value) => { if (value && value.length > 0) { return true; } return false; }; const InputMatcher = ({ register, errors }) => { return /* @__PURE__ */ jsx(Grid2, { container: true, direction: "column", spacing: 1, children: /* @__PURE__ */ jsx(Grid2, { container: true, children: /* @__PURE__ */ jsx(Grid2, { children: /* @__PURE__ */ jsxs(Grid2, { container: true, direction: "column", spacing: 1, children: [ /* @__PURE__ */ jsx(Grid2, { children: /* @__PURE__ */ jsx(Label, { size: "small", children: "Matcher" }) }), /* @__PURE__ */ jsxs(Grid2, { children: [ /* @__PURE__ */ jsx( Input, { placeholder: "/admin/products", ...register("matcher", { validateName: validateName$1 }), "aria-invalid": errors["matcher"] !== void 0 } ), errors["matcher"] !== void 0 && /* @__PURE__ */ jsx(Alert, { variant: "error", children: errors["matcher"].message }) ] }) ] }) }) }) }); }; const CreatePermissionConfigurationStep = ({ register, errors, currentMatcherType, currentActionType, setMatcherType, setActionType }) => { return /* @__PURE__ */ jsx(Grid2, { container: true, justifyContent: "center", children: /* @__PURE__ */ jsx(Grid2, { size: 4, children: /* @__PURE__ */ jsxs(Grid2, { container: true, direction: "column", spacing: 1, marginTop: 2, children: [ /* @__PURE__ */ jsx(Grid2, { children: /* @__PURE__ */ jsx(Heading, { level: "h1", children: "Configure permission" }) }), /* @__PURE__ */ jsx(Grid2, { children: /* @__PURE__ */ jsx(Label, { size: "small", children: "Choose matcher type" }) }), /* @__PURE__ */ jsx(Grid2, { children: /* @__PURE__ */ jsx(SelectMatcherType, { currentMatcherType, matcherTypes: [PermissionMatcherType.API], setChosenMatcherType: setMatcherType }) }), errors["matcherType"] !== void 0 && /* @__PURE__ */ jsx(Alert, { variant: "error", children: errors["matcherType"].message }), /* @__PURE__ */ jsx(Grid2, { children: /* @__PURE__ */ jsx(Label, { size: "small", children: "Choose action type" }) }), /* @__PURE__ */ jsx(Grid2, { children: /* @__PURE__ */ jsx(SelectActionType, { currentActionType, actionTypes: Object.values(PermissionActionType), setChosenActionType: setActionType }) }), errors["actionType"] !== void 0 && /* @__PURE__ */ jsx(Alert, { variant: "error", children: errors["actionType"].message }), /* @__PURE__ */ jsx(Grid2, { children: /* @__PURE__ */ jsx(InputMatcher, { register, errors }) }) ] }) }) }); }; const tabOrder$1 = [ "general", "configuration" /* CONFIGURATION */ ]; const initialTabState$1 = { [ "general" /* GENERAL */ ]: "in-progress", [ "configuration" /* CONFIGURATION */ ]: "not-started" }; const PrimaryButton$1 = ({ tab, next, isLoading, handleSubmit }) => { if (tab === "general") { return /* @__PURE__ */ jsx(Button, { onClick: () => next(tab), children: "Continue" }); } return /* @__PURE__ */ jsx( Button, { type: "submit", isLoading, onClick: handleSubmit, children: "Create" }, "submit-button" ); }; const CreatePermissionModal = ({ reloadTable }) => { const { register, handleSubmit, formState: { errors }, getValues, setError, clearErrors, reset } = useForm({ defaultValues: { name: "", matcher: "" } }); const [activeTab, setActiveTab] = useState( "general" /* GENERAL */ ); const [tabState, setTabState] = useState(initialTabState$1); const [isOpen, setIsOpen] = useState(false); const [matcherType, setMatcherType] = useState(void 0); const [actionType, setActionType] = useState(void 0); const [category, setCategory] = useState(void 0); const partialFormValidation = (tab) => { let result = true; switch (tab) { case "general": if (getValues("name") && getValues.length > 0) { clearErrors("name"); } else { setError("name", { type: "custom", message: "Please fill the name" }); result = false; } return result; case "configuration": if (getValues("matcher") && getValues.length > 0) { clearErrors("matcher"); } else { setError("matcher", { type: "custom", message: "Please fill the matcher" }); result = false; } if (matcherType) { clearErrors("matcherType"); } else { setError("matcherType", { type: "custom", message: "Please fill the matcher type" }); result = false; } if (actionType) { clearErrors("actionType"); } else { setError("actionType", { type: "custom", message: "Please fill the action type" }); result = false; } return result; default: return false; } }; useEffect(() => { if (!isOpen) { reset(); setActiveTab( "general" /* GENERAL */ ); setMatcherType(void 0); setActionType(void 0); setCategory(void 0); setTabState(initialTabState$1); } }, [isOpen]); const isTabDirty = (tab) => { return partialFormValidation(tab); }; const onSubmit = (data) => { fetch(`/admin/rbac/permissions`, { method: "POST", credentials: "include", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: data.name, type: PermissionType.CUSTOM, matcherType, matcher: data.matcher, actionType, category }) }).then(async (response) => { if (response.ok) { toast.info("Permission", { description: "New permission has been created" }); reloadTable(); setIsOpen(false); } else { const error = await response.json(); toast.error("Permission", { description: `New permission cannot be created. ${error.message}` }); } }).catch((e) => { toast.error("Permission", { description: `New permission cannot be created. ${e.toString()}` }); console.error(e); }); }; function handleChangeTab(newTab) { if (activeTab === newTab) { return; } if (tabOrder$1.indexOf(newTab) < tabOrder$1.indexOf(activeTab)) { const isCurrentTabDirty = isTabDirty(activeTab); setTabState((prev) => ({ ...prev, [activeTab]: isCurrentTabDirty ? prev[activeTab] : "not-started", [newTab]: "in-progress" })); setActiveTab(newTab); return; } const tabs = tabOrder$1.slice(0, tabOrder$1.indexOf(newTab)); for (const tab of tabs) { if (tab === "general") { if (!partialFormValidation(tab)) { setTabState((prev) => ({ ...prev, [tab]: "in-progress" })); setActiveTab(tab); return; } setTabState((prev) => ({ ...prev, [tab]: "completed" })); } else if (tab === "configuration") { if (!partialFormValidation(tab)) { setTabState((prev) => ({ ...prev, [tab]: "in-progress" })); setActiveTab(tab); return; } setTabState((prev) => ({ ...prev, [tab]: "completed" })); } } setTabState((prev) => ({ ...prev, [activeTab]: "completed", [newTab]: "in-progress" })); setActiveTab(newTab); } const handleNextTab = (tab) => { if (tabOrder$1.indexOf(tab) + 1 >= tabOrder$1.length) { return; } const nextTab = tabOrder$1[tabOrder$1.indexOf(tab) + 1]; handleChangeTab(nextTab); }; return /* @__PURE__ */ jsxs(FocusModal, { open: isOpen, onOpenChange: setIsOpen, children: [ /* @__PURE__ */ jsx(FocusModal.Trigger, { children: /* @__PURE__ */ jsx(Button, { variant: "secondary", children: "Create" }) }), /* @__PURE__ */ jsx("form", { children: /* @__PURE__ */ jsx(FocusModal.Content, { children: /* @__PURE__ */ jsxs( ProgressTabs, { value: activeTab, onValueChange: (tab) => handleChangeTab(tab), className: "flex h-full flex-col overflow-hidden", children: [ /* @__PURE__ */ jsx(FocusModal.Header, { children: /* @__PURE__ */ jsx("div", { className: "flex w-full items-center justify-between gap-x-4", children: /* @__PURE__ */ jsx("div", { className: "-my-2 w-full max-w-[600px] border-l", children: /* @__PURE__ */ jsxs(ProgressTabs.List, { children: [ /* @__PURE__ */ jsx( ProgressTabs.Trigger, { value: "general", status: tabState.general, children: "General" } ), /* @__PURE__ */ jsx( ProgressTabs.Trigger, { value: "configuration", status: tabState.configuration, children: "Configuration" } ) ] }) }) }) }), /* @__PURE__ */ jsxs(FocusModal.Body, { className: "size-full overflow-hidden", children: [ /* @__PURE__ */ jsx( ProgressTabs.Content, { className: "size-full overflow-y-auto", value: "general", children: /* @__PURE__ */ jsx(CreatePermissionGeneralStep, { register, errors, setCategory, currentCategory: category }) } ), /* @__PURE__ */ jsx( ProgressTabs.Content, { className: "size-full overflow-y-auto", value: "configuration", children: /* @__PURE__ */ jsx( CreatePermissionConfigurationStep, { currentMatcherType: matcherType, currentActionType: actionType, register, errors, setActionType, setMatcherType } ) } ) ] }), /* @__PURE__ */ jsx(FocusModal.Footer, { children: /* @__PURE__ */ jsx(Grid2, { container: true, justifyContent: "flex-end", children: /* @__PURE__ */ jsx(Grid2, { children: /* @__PURE__ */ jsxs(Grid2, { container: true, columnSpacing: 2, rowSpacing: 5, children: [ /* @__PURE__ */ jsx(Grid2, { children: /* @__PURE__ */ jsx(FocusModal.Close, { children: /* @__PURE__ */ jsx(Button, { variant: "secondary", children: "Cancel" }) }) }), /* @__PURE__ */ jsx(Grid2, { children: /* @__PURE__ */ jsx( PrimaryButton$1, { tab: activeTab, next: handleNextTab, isLoading: false, handleSubmit: () => { if (partialFormValidation(activeTab)) { return handleSubmit(onSubmit)(); } } } ) }) ] }) }) }) }) ] } ) }) }) ] }); }; const ActionMenu = ({ groups }) => { return /* @__PURE__ */ jsxs(DropdownMenu, { children: [ /* @__PURE__ */ jsx(DropdownMenu.Trigger, { asChild: true, children: /* @__PURE__ */ jsx(IconButton, { size: "small", variant: "transparent", children: /* @__PURE__ */ jsx(EllipsisHorizontal, {}) }) }), /* @__PURE__ */ jsx(DropdownMenu.Content, { children: groups.map((group, index) => { if (!group.actions.length) { return null; } const isLast = index === groups.length - 1; return /* @__PURE__ */ jsxs(DropdownMenu.Group, { children: [ group.actions.map((action, index2) => { if (action.onClick) { return /* @__PURE__ */ jsxs( DropdownMenu.Item, { disabled: action.disabled, onClick: (e) => { e.stopPropagation(); action.onClick(); }, className: clx( "[&_svg]:text-ui-fg-subtle flex items-center gap-x-2", { "[&_svg]:text-ui-fg-disabled": action.disabled } ), children: [ action.icon, /* @__PURE__ */ jsx("span", { children: action.label }) ] }, index2 ); } return /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx( DropdownMenu.Item, { className: clx( "[&_svg]:text-ui-fg-subtle flex items-center gap-x-2", { "[&_svg]:text-ui-fg-disabled": action.disabled } ), asChild: true, disabled: action.disabled, children: /* @__PURE__ */ jsxs(Link, { to: action.to, onClick: (e) => e.stopPropagation(), children: [ action.icon, /* @__PURE__ */ jsx("span", { children: action.label }) ] }) } ) }, index2); }), !isLast && /* @__PURE__ */ jsx(DropdownMenu.Separator, {}) ] }, index); }) }) ] }); }; const Header = ({ title, subtitle, actions = [] }) => { return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [ /* @__PURE__ */ jsxs("div", { children: [ /* @__PURE__ */ jsx(Heading, { level: "h2", children: title }), subtitle && /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", size: "small", children: subtitle }) ] }), actions.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center gap-x-2", children: actions.map((action, index) => /* @__PURE__ */ jsxs(Fragment, { children: [ action.type === "button" && /* @__PURE__ */ createElement( Button, { ...action.props, size: action.props.size || "small", key: index }, /* @__PURE__ */ jsxs(Fragment, { children: [ action.props.children, action.link && /* @__PURE__ */ jsx(Link, { ...action.link }) ] }) ), action.type === "action-menu" && /* @__PURE__ */ jsx(ActionMenu, { ...action.props }), action.type === "custom" && action.children ] })) }) ] }); }; const DeletePermission = ({ permissionId, reloadTable }) => { const handleAction = () => { fetch(`/admin/rbac/permissions`, { method: "DELETE", credentials: "include", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ id: permissionId }) }).then((res) => res.json()).then(({ message }) => { reloadTable(); if (message) { throw message; } }).catch((e) => { reloadTable(); console.error(e); }); }; return /* @__PURE__ */ jsxs(Prompt, { children: [ /* @__PURE__ */ jsx(Prompt.Trigger, { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx(IconButton, { children: /* @__PURE__ */ jsx(Trash, {}) }) }), /* @__PURE__ */ jsxs(Prompt.Content, { children: [ /* @__PURE__ */ jsxs(Prompt.Header, { children: [ /* @__PURE__ */ jsx(Prompt.Title, { children: "Delete role" }), /* @__PURE__ */ jsx(Prompt.Description, { children: "Are you sure? This cannot be undone." }) ] }), /* @__PURE__ */ jsxs(Prompt.Footer, { children: [ /* @__PURE__ */ jsx(Prompt.Cancel, { onClick: (e) => e.stopPropagation(), children: "Cancel" }), /* @__PURE__ */ jsx(Prompt.Action, { onClick: (e) => { e.stopPropagation(); handleAction(); }, children: "Delete" }) ] }) ] }) ] }); }; const PermissionsTable$1 = ({ permissions, permissionType, reloadTable }) => { const [currentPage, setCurrentPage] = useState(0); const pageSize = 6; const pageCount = Math.ceil(permissions.length / pageSize); const canNextPage = useMemo( () => currentPage < pageCount - 1, [currentPage, pageCount] ); const canPreviousPage = useMemo(() => currentPage - 1 >= 0, [currentPage]); const nextPage = () => { if (canNextPage) { setCurrentPage(currentPage + 1); } }; const previousPage = () => { if (canPreviousPage) { setCurrentPage(currentPage - 1); } }; const currentPermissions = useMemo(() => { const offset = currentPage * pageSize; const limit = Math.min(offset + pageSize, permissions.length); return permissions.slice(offset, limit); }, [currentPage, pageSize, permissions]); const navigate = useNavigate(); const handleRowClick = (id) => { navigate(`/rbac/permissions/${id}`); }; return /* @__PURE__ */ jsxs("div", { className: "flex gap-1 flex-col", children: [ /* @__PURE__ */ jsxs(Table, { children: [ /* @__PURE__ */ jsx(Table.Header, { children: /* @__PURE__ */ jsxs(Table.Row, { children: [ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Target" }), /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Action" }), /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Type" }), /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Name" }), permissionType === PermissionType.CUSTOM && /* @__PURE__ */ jsx(Table.HeaderCell, {}) ] }) }), /* @__PURE__ */ jsx(Table.Body, { children: currentPermissions.map((permission) => { return /* @__PURE__ */ jsxs( Table.Row, { onClick: permissionType === PermissionType.CUSTOM ? () => handleRowClick(permission.id) : void 0, style: permissionType === PermissionType.CUSTOM ? { cursor: "pointer" } : {}, className: "[&_td:last-child]:w-[1%] [&_td:last-child]:whitespace-nowrap", children: [ /* @__PURE__ */ jsx(Table.Cell, { style: { alignContent: "center" }, children: `${permission.matcher}` }), /* @__PURE__ */ jsx(Table.Cell, { style: { alignContent: "center" }, children: `${permission.actionType.toUpperCase()}` }), /* @__PURE__ */ jsx(Table.Cell, { style: { alignContent: "center" }, children: permission.matcherType }), /* @__PURE__ */ jsx(Table.Cell, { style: { alignContent: "center" }, children: `${permission.name}` }), permissionType === PermissionType.CUSTOM && /* @__PURE__ */ jsx(Table.Cell, { style: { alignContent: "center" }, children: /* @__PURE__ */ jsx(Grid2, { container: true, spacing: 2, children: /* @__PURE__ */ jsx(Grid2, { children: /* @__PURE__ */ jsx( DeletePermission, { permissionId: permission.id, reloadTable } ) }) }) }) ] }, permission.id ); }) }) ] }), /* @__PURE__ */ jsx( Table.Pagination, { count: permissions.length, pageSize, pageIndex: currentPage, pageCount, canPreviousPage, canNextPage, previousPage, nextPage } ) ] }); }; const PermissionTable = React.memo(PermissionsTable$1); const DeleteCategory = ({ category, reload