diff --git a/app/(prowler)/users/page.tsx b/app/(prowler)/users/page.tsx
index 7c1549b2cf..cff19b91e0 100644
--- a/app/(prowler)/users/page.tsx
+++ b/app/(prowler)/users/page.tsx
@@ -3,8 +3,10 @@ import { redirect } from "next/navigation";
import { Suspense } from "react";
import { getUsers } from "@/actions/users";
+import { auth } from "@/auth.config";
import { Header } from "@/components/ui";
import {
+ AddUserModal,
ColumnsUser,
DataTableUser,
SkeletonTableUser,
@@ -12,11 +14,19 @@ import {
import { searchParamsProps } from "@/types";
export default async function Users({ searchParams }: searchParamsProps) {
+ const session = await auth();
+ if (session?.user?.role !== "admin") {
+ redirect("/");
+ }
+
return (
<>
+
}>
diff --git a/components/ui/dropdown/Dropdown.tsx b/components/ui/dropdown/Dropdown.tsx
new file mode 100644
index 0000000000..b88da41b59
--- /dev/null
+++ b/components/ui/dropdown/Dropdown.tsx
@@ -0,0 +1,200 @@
+"use client";
+
+import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
+import { Check, ChevronRight, Circle } from "lucide-react";
+import * as React from "react";
+
+import { cn } from "@/lib/utils";
+
+const DropdownMenu = DropdownMenuPrimitive.Root;
+
+const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
+
+const DropdownMenuGroup = DropdownMenuPrimitive.Group;
+
+const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
+
+const DropdownMenuSub = DropdownMenuPrimitive.Sub;
+
+const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
+
+const DropdownMenuSubTrigger = React.forwardRef<
+ React.ElementRef
,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean;
+ }
+>(({ className, inset, children, ...props }, ref) => (
+
+ {children}
+
+
+));
+DropdownMenuSubTrigger.displayName =
+ DropdownMenuPrimitive.SubTrigger.displayName;
+
+const DropdownMenuSubContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+DropdownMenuSubContent.displayName =
+ DropdownMenuPrimitive.SubContent.displayName;
+
+const DropdownMenuContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, sideOffset = 4, ...props }, ref) => (
+
+
+
+));
+DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
+
+const DropdownMenuItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean;
+ }
+>(({ className, inset, ...props }, ref) => (
+
+));
+DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
+
+const DropdownMenuCheckboxItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, checked, ...props }, ref) => (
+
+
+
+
+
+
+ {children}
+
+));
+DropdownMenuCheckboxItem.displayName =
+ DropdownMenuPrimitive.CheckboxItem.displayName;
+
+const DropdownMenuRadioItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+
+
+
+
+ {children}
+
+));
+DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
+
+const DropdownMenuLabel = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean;
+ }
+>(({ className, inset, ...props }, ref) => (
+
+));
+DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
+
+const DropdownMenuSeparator = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
+
+const DropdownMenuShortcut = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => {
+ return (
+
+ );
+};
+DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
+
+export {
+ DropdownMenu,
+ DropdownMenuCheckboxItem,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuPortal,
+ DropdownMenuRadioGroup,
+ DropdownMenuRadioItem,
+ DropdownMenuSeparator,
+ DropdownMenuShortcut,
+ DropdownMenuSub,
+ DropdownMenuSubContent,
+ DropdownMenuSubTrigger,
+ DropdownMenuTrigger,
+};
diff --git a/components/ui/index.ts b/components/ui/index.ts
index f026779b96..70705ba015 100644
--- a/components/ui/index.ts
+++ b/components/ui/index.ts
@@ -2,7 +2,9 @@ export * from "./action-card/ActionCard";
export * from "./alert/Alert";
export * from "./chart/Chart";
export * from "./dialog/Dialog";
+export * from "./dropdown/Dropdown";
export * from "./header/Header";
+export * from "./label/Label";
export * from "./select/Select";
export * from "./sidebar";
export * from "./table/SeverityBadge";
diff --git a/components/ui/label/Label.tsx b/components/ui/label/Label.tsx
new file mode 100644
index 0000000000..9bdf1a5044
--- /dev/null
+++ b/components/ui/label/Label.tsx
@@ -0,0 +1,26 @@
+"use client";
+
+import * as LabelPrimitive from "@radix-ui/react-label";
+import { cva, type VariantProps } from "class-variance-authority";
+import * as React from "react";
+
+import { cn } from "@/lib/utils";
+
+const labelVariants = cva(
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
+);
+
+const Label = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef &
+ VariantProps
+>(({ className, ...props }, ref) => (
+
+));
+Label.displayName = LabelPrimitive.Root.displayName;
+
+export { Label };
diff --git a/components/ui/sidebar/SidebarWrap.tsx b/components/ui/sidebar/SidebarWrap.tsx
index 201cecdecf..e584ca1043 100644
--- a/components/ui/sidebar/SidebarWrap.tsx
+++ b/components/ui/sidebar/SidebarWrap.tsx
@@ -18,7 +18,7 @@ import {
} from "../../icons/prowler/ProwlerIcons";
import { ThemeSwitch } from "../../ThemeSwitch";
import Sidebar from "./Sidebar";
-import { sectionItemsWithTeams } from "./SidebarItems";
+import { sectionItems, sectionItemsWithTeams } from "./SidebarItems";
import { UserAvatar } from "./UserAvatar";
export const SidebarWrap = () => {
@@ -81,7 +81,11 @@ export const SidebarWrap = () => {
diff --git a/components/users/AddUserModal.tsx b/components/users/AddUserModal.tsx
new file mode 100644
index 0000000000..49d4b70dd9
--- /dev/null
+++ b/components/users/AddUserModal.tsx
@@ -0,0 +1,62 @@
+"use client";
+
+import { Button, Input } from "@nextui-org/react";
+import { useRef, useState } from "react";
+
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui";
+
+import { ButtonAddUser } from "./ButtonAddUser";
+import { CustomSelectUser } from "./CustomSelectUser";
+
+export const AddUserModal = () => {
+ const [open, setOpen] = useState(false);
+ const ref = useRef(null);
+
+ return (
+
+ );
+};
diff --git a/components/users/ButtonAddUser.tsx b/components/users/ButtonAddUser.tsx
new file mode 100644
index 0000000000..e840dc0157
--- /dev/null
+++ b/components/users/ButtonAddUser.tsx
@@ -0,0 +1,14 @@
+"use client";
+
+import { Button } from "@nextui-org/react";
+import React from "react";
+import { useFormStatus } from "react-dom";
+
+export const ButtonAddUser = () => {
+ const { pending } = useFormStatus();
+ return (
+
+ );
+};
diff --git a/components/users/ButtonEditUser.tsx b/components/users/ButtonEditUser.tsx
new file mode 100644
index 0000000000..a0322b7a2d
--- /dev/null
+++ b/components/users/ButtonEditUser.tsx
@@ -0,0 +1,14 @@
+"use client";
+
+import { Button } from "@nextui-org/react";
+import React from "react";
+import { useFormStatus } from "react-dom";
+
+export const ButtonEditUser = () => {
+ const { pending } = useFormStatus();
+ return (
+
+ );
+};
diff --git a/components/users/CustomSelectUser.tsx b/components/users/CustomSelectUser.tsx
new file mode 100644
index 0000000000..10acfc973c
--- /dev/null
+++ b/components/users/CustomSelectUser.tsx
@@ -0,0 +1,39 @@
+import {
+ Label,
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui";
+import { UserProps } from "@/types";
+
+interface CustomSelectUserProps {
+ userData?: UserProps;
+}
+
+export const CustomSelectUser: React.FC = ({
+ userData,
+}) => {
+ return (
+ <>
+
+
+ >
+ );
+};
diff --git a/components/users/EditUserModal.tsx b/components/users/EditUserModal.tsx
new file mode 100644
index 0000000000..0d64c7f940
--- /dev/null
+++ b/components/users/EditUserModal.tsx
@@ -0,0 +1,82 @@
+"use client";
+
+import { Input } from "@nextui-org/react";
+import { useRef } from "react";
+
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui";
+import { UserProps } from "@/types";
+
+import { ButtonEditUser } from "./ButtonEditUser";
+import { CustomSelectUser } from "./CustomSelectUser";
+
+interface EditUserModalProps {
+ isOpen: boolean;
+ setIsOpen: React.Dispatch>;
+ userData: UserProps;
+}
+
+export const EditUserModal: React.FC = ({
+ isOpen,
+ setIsOpen,
+ userData,
+}) => {
+ const ref = useRef(null);
+
+ return (
+
+ );
+};
diff --git a/components/users/index.ts b/components/users/index.ts
index a458fa1122..36f6d8f557 100644
--- a/components/users/index.ts
+++ b/components/users/index.ts
@@ -1,5 +1,11 @@
+export * from "./AddUserModal";
+export * from "./ButtonAddUser";
+export * from "./ButtonEditUser";
+export * from "./CustomSelectUser";
+export * from "./EditUserModal";
export * from "./table/ColumnsUser";
export * from "./table/DataTableColumnHeader";
export * from "./table/DataTablePagination";
export * from "./table/DataTableUser";
export * from "./table/SkeletonTableUser";
+export * from "./table/UserActions";
diff --git a/components/users/table/ColumnsUser.tsx b/components/users/table/ColumnsUser.tsx
index 4dbf8001f0..70a05c189a 100644
--- a/components/users/table/ColumnsUser.tsx
+++ b/components/users/table/ColumnsUser.tsx
@@ -1,17 +1,10 @@
"use client";
-import {
- Button,
- Dropdown,
- DropdownItem,
- DropdownMenu,
- DropdownTrigger,
-} from "@nextui-org/react";
import { ColumnDef } from "@tanstack/react-table";
-import { VerticalDotsIcon } from "@/components/icons";
import { DateWithTime } from "@/components/providers";
import { StatusBadge } from "@/components/ui";
+import { UserActions } from "@/components/users";
import { UserProps } from "@/types";
const getUserData = (row: { original: UserProps }) => {
@@ -63,21 +56,9 @@ export const ColumnsUser: ColumnDef[] = [
accessorKey: "actions",
header: () => Actions
,
id: "actions",
- cell: () => {
- return (
-
-
-
-
-
-
- Edit
-
-
-
- );
+ cell: ({ row }) => {
+ const userData = getUserData(row);
+ return ;
},
},
];
diff --git a/components/users/table/UserActions.tsx b/components/users/table/UserActions.tsx
new file mode 100644
index 0000000000..9ff00eda1d
--- /dev/null
+++ b/components/users/table/UserActions.tsx
@@ -0,0 +1,54 @@
+"use client";
+
+import { useState } from "react";
+
+import { VerticalDotsIcon } from "@/components/icons";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "@/components/ui";
+import { EditUserModal } from "@/components/users";
+import { UserProps } from "@/types";
+
+interface UserActionsProps {
+ userData: UserProps;
+}
+
+export const UserActions: React.FC = ({ userData }) => {
+ const [isEditOpen, setIsEditOpen] = useState(false);
+
+ return (
+ <>
+
+
+
+
+
+
+ setIsEditOpen(true)}
+ >
+ Edit
+
+
+ Delete
+
+
+
+
+ {isEditOpen && (
+
+ )}
+ >
+ );
+};
diff --git a/package-lock.json b/package-lock.json
index 689b2cbfb6..320beae819 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,6 +13,7 @@
"@nextui-org/system": "2.2.1",
"@nextui-org/theme": "2.2.5",
"@radix-ui/react-dialog": "^1.1.1",
+ "@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-select": "^2.1.1",
@@ -2886,6 +2887,35 @@
}
}
},
+ "node_modules/@radix-ui/react-dropdown-menu": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.1.tgz",
+ "integrity": "sha512-y8E+x9fBq9qvteD2Zwa4397pUVhYsh9iq44b5RD5qu1GMJWBCBuVg1hMyItbc6+zH00TxGRqd9Iot4wzf3OoBQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.0",
+ "@radix-ui/react-compose-refs": "1.1.0",
+ "@radix-ui/react-context": "1.1.0",
+ "@radix-ui/react-id": "1.1.0",
+ "@radix-ui/react-menu": "2.1.1",
+ "@radix-ui/react-primitive": "2.0.0",
+ "@radix-ui/react-use-controllable-state": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-focus-guards": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.0.tgz",
@@ -2953,6 +2983,7 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.0.tgz",
"integrity": "sha512-peLblDlFw/ngk3UWq0VnYaOLy6agTZZ+MUO/WhVfm14vJGML+xH4FAl2XQGLqdefjNb7ApRg6Yn7U42ZhmYXdw==",
+ "license": "MIT",
"dependencies": {
"@radix-ui/react-primitive": "2.0.0"
},
@@ -2971,6 +3002,71 @@
}
}
},
+ "node_modules/@radix-ui/react-menu": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.1.tgz",
+ "integrity": "sha512-oa3mXRRVjHi6DZu/ghuzdylyjaMXLymx83irM7hTxutQbD+7IhPKdMdRHD26Rm+kHRrWcrUkkRPv5pd47a2xFQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.0",
+ "@radix-ui/react-collection": "1.1.0",
+ "@radix-ui/react-compose-refs": "1.1.0",
+ "@radix-ui/react-context": "1.1.0",
+ "@radix-ui/react-direction": "1.1.0",
+ "@radix-ui/react-dismissable-layer": "1.1.0",
+ "@radix-ui/react-focus-guards": "1.1.0",
+ "@radix-ui/react-focus-scope": "1.1.0",
+ "@radix-ui/react-id": "1.1.0",
+ "@radix-ui/react-popper": "1.2.0",
+ "@radix-ui/react-portal": "1.1.1",
+ "@radix-ui/react-presence": "1.1.0",
+ "@radix-ui/react-primitive": "2.0.0",
+ "@radix-ui/react-roving-focus": "1.1.0",
+ "@radix-ui/react-slot": "1.1.0",
+ "@radix-ui/react-use-callback-ref": "1.1.0",
+ "aria-hidden": "^1.1.1",
+ "react-remove-scroll": "2.5.7"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-menu/node_modules/react-remove-scroll": {
+ "version": "2.5.7",
+ "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.7.tgz",
+ "integrity": "sha512-FnrTWO4L7/Bhhf3CYBNArEG/yROV0tKmTv7/3h9QCFvH6sndeFf1wPqOcbFVu5VAulS5dV1wGT3GZZ/1GawqiA==",
+ "license": "MIT",
+ "dependencies": {
+ "react-remove-scroll-bar": "^2.3.4",
+ "react-style-singleton": "^2.2.1",
+ "tslib": "^2.1.0",
+ "use-callback-ref": "^1.3.0",
+ "use-sidecar": "^1.1.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-popper": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.0.tgz",
@@ -3070,6 +3166,37 @@
}
}
},
+ "node_modules/@radix-ui/react-roving-focus": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.0.tgz",
+ "integrity": "sha512-EA6AMGeq9AEeQDeSH0aZgG198qkfHSbvWTf1HvoDmOB5bBG/qTxjYMWUKMnYiV6J/iP/J8MEFSuB2zRU2n7ODA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.0",
+ "@radix-ui/react-collection": "1.1.0",
+ "@radix-ui/react-compose-refs": "1.1.0",
+ "@radix-ui/react-context": "1.1.0",
+ "@radix-ui/react-direction": "1.1.0",
+ "@radix-ui/react-id": "1.1.0",
+ "@radix-ui/react-primitive": "2.0.0",
+ "@radix-ui/react-use-callback-ref": "1.1.0",
+ "@radix-ui/react-use-controllable-state": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-select": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.1.tgz",
diff --git a/package.json b/package.json
index 8646874078..07d2fb4d13 100644
--- a/package.json
+++ b/package.json
@@ -5,6 +5,7 @@
"@nextui-org/system": "2.2.1",
"@nextui-org/theme": "2.2.5",
"@radix-ui/react-dialog": "^1.1.1",
+ "@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-select": "^2.1.1",