diff --git a/app/(prowler)/providers/page.tsx b/app/(prowler)/providers/page.tsx index 7b2d11118e..79ccbf5d29 100644 --- a/app/(prowler)/providers/page.tsx +++ b/app/(prowler)/providers/page.tsx @@ -3,12 +3,12 @@ import { Suspense } from "react"; import { getProvider } from "@/actions"; import { FilterControls } from "@/components/filters"; +import { AddProviderModal } from "@/components/providers"; import { - AddProviderModal, ColumnsProvider, DataTableProvider, SkeletonTableProvider, -} from "@/components/providers"; +} from "@/components/providers/table"; import { Header } from "@/components/ui"; import { SearchParamsProps } from "@/types"; diff --git a/components/providers/DeleteProvider.tsx b/components/providers/DeleteProvider.tsx deleted file mode 100644 index c5090ef274..0000000000 --- a/components/providers/DeleteProvider.tsx +++ /dev/null @@ -1,41 +0,0 @@ -"use client"; - -import { useRef } from "react"; - -import { deleteProvider } from "@/actions"; - -import { CustomButtonClientAction } from "../ui/custom"; -import { useToast } from "../ui/toast"; - -export const DeleteProvider = ({ id }: { id: string }) => { - const ref = useRef(null); - const { toast } = useToast(); - - async function clientAction(formData: FormData) { - // reset the form - ref.current?.reset(); - // client-side validation - const data = await deleteProvider(formData); - if (data?.errors && data.errors.length > 0) { - const error = data.errors[0]; - const errorMessage = `${error.detail}`; - // show error - toast({ - variant: "destructive", - title: "Oops! Something went wrong", - description: errorMessage, - }); - } else { - toast({ - title: "Success!", - description: "The provider was removed successfully.", - }); - } - } - return ( -
- - - - ); -}; diff --git a/components/providers/forms/DeleteForm.tsx b/components/providers/forms/DeleteForm.tsx new file mode 100644 index 0000000000..b739299fb8 --- /dev/null +++ b/components/providers/forms/DeleteForm.tsx @@ -0,0 +1,85 @@ +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import { Button, CircularProgress } from "@nextui-org/react"; +import React, { Dispatch, SetStateAction } from "react"; +import { useForm } from "react-hook-form"; +import * as z from "zod"; + +import { deleteProvider } from "@/actions"; +import { useToast } from "@/components/ui"; +import { Form } from "@/components/ui/form"; + +const formSchema = z.object({ + providerId: z.string(), +}); + +export const DeleteForm = ({ + providerId, + setIsOpen, +}: { + providerId: string; + setIsOpen: Dispatch>; +}) => { + const form = useForm>({ + resolver: zodResolver(formSchema), + }); + const { toast } = useToast(); + const isLoading = form.formState.isSubmitting; + + async function onSubmitClient(formData: FormData) { + // client-side validation + const data = await deleteProvider(formData); + + if (data?.errors && data.errors.length > 0) { + const error = data.errors[0]; + const errorMessage = `${error.detail}`; + // show error + toast({ + variant: "destructive", + title: "Oops! Something went wrong", + description: errorMessage, + }); + } else { + toast({ + title: "Success!", + description: "The provider was removed successfully.", + }); + } + } + + return ( +
+ + +
+ + +
+
+ + ); +}; diff --git a/components/providers/forms/index.ts b/components/providers/forms/index.ts new file mode 100644 index 0000000000..005f96941f --- /dev/null +++ b/components/providers/forms/index.ts @@ -0,0 +1 @@ +export * from "./DeleteForm"; diff --git a/components/providers/index.ts b/components/providers/index.ts index a0129d3520..b92fe7eb83 100644 --- a/components/providers/index.ts +++ b/components/providers/index.ts @@ -4,12 +4,7 @@ export * from "./ButtonAddProvider"; export * from "./CheckConnectionProvider"; export * from "./CustomRadioProvider"; export * from "./DateWithTime"; -export * from "./DeleteProvider"; +export * from "./forms/DeleteForm"; export * from "./ProviderInfo"; export * from "./ScanStatus"; export * from "./SnippetIdProvider"; -export * from "./table/ColumnsProvider"; -export * from "./table/DataTableColumnHeader"; -export * from "./table/DataTablePagination"; -export * from "./table/DataTableProvider"; -export * from "./table/SkeletonTableProvider"; diff --git a/components/providers/table/ColumnsProvider.tsx b/components/providers/table/columns-provider.tsx similarity index 50% rename from components/providers/table/ColumnsProvider.tsx rename to components/providers/table/columns-provider.tsx index 33c79a25bd..2b7ecbb5a5 100644 --- a/components/providers/table/ColumnsProvider.tsx +++ b/components/providers/table/columns-provider.tsx @@ -1,41 +1,21 @@ "use client"; -import { - Button, - Dropdown, - DropdownItem, - DropdownMenu, - DropdownSection, - DropdownTrigger, -} from "@nextui-org/react"; import { ColumnDef } from "@tanstack/react-table"; -import clsx from "clsx"; import { add } from "date-fns"; -import { VerticalDotsIcon } from "@/components/icons"; import { StatusBadge } from "@/components/ui"; import { ProviderProps } from "@/types"; -import { CheckConnectionProvider } from "../CheckConnectionProvider"; import { DateWithTime } from "../DateWithTime"; -import { DeleteProvider } from "../DeleteProvider"; import { ProviderInfo } from "../ProviderInfo"; -import { DataTableColumnHeader } from "./DataTableColumnHeader"; +import { SnippetIdProvider } from "../SnippetIdProvider"; +import { DataTableColumnHeader } from "./data-table-column-header"; +import { DataTableRowActions } from "./data-table-row-actions"; + const getProviderData = (row: { original: ProviderProps }) => { return row.original; }; -import { - AddNoteBulkIcon, - DeleteDocumentBulkIcon, - EditDocumentBulkIcon, -} from "@nextui-org/shared-icons"; - -import { SnippetIdProvider } from "../SnippetIdProvider"; - -const iconClasses = - "text-2xl text-default-500 pointer-events-none flex-shrink-0"; - export const ColumnsProvider: ColumnDef[] = [ { header: "n", @@ -133,68 +113,9 @@ export const ColumnsProvider: ColumnDef[] = [ }, }, { - accessorKey: "actions", - header: () =>
Actions
, id: "actions", cell: ({ row }) => { - const { id } = getProviderData(row); - return ( -
- - - - - - - } - > - - - - } - > - Edit provider - - - - - } - > - - - - - -
- ); + return ; }, }, ]; diff --git a/components/providers/table/DataTableColumnHeader.tsx b/components/providers/table/data-table-column-header.tsx similarity index 100% rename from components/providers/table/DataTableColumnHeader.tsx rename to components/providers/table/data-table-column-header.tsx diff --git a/components/providers/table/FilterColumnTable.tsx b/components/providers/table/data-table-filter-custom.tsx similarity index 92% rename from components/providers/table/FilterColumnTable.tsx rename to components/providers/table/data-table-filter-custom.tsx index 09b210dd44..6774257d3e 100644 --- a/components/providers/table/FilterColumnTable.tsx +++ b/components/providers/table/data-table-filter-custom.tsx @@ -1,12 +1,14 @@ +"use client"; + import { Button } from "@nextui-org/react"; import { useRouter, useSearchParams } from "next/navigation"; import { useCallback, useMemo } from "react"; -interface FilterColumnTableProps { +interface DataTableFilterCustomProps { filters: { key: string; values: string[] }[]; } -export function FilterColumnTable({ filters }: FilterColumnTableProps) { +export function DataTableFilterCustom({ filters }: DataTableFilterCustomProps) { const router = useRouter(); const searchParams = useSearchParams(); diff --git a/components/providers/table/DataTablePagination.tsx b/components/providers/table/data-table-pagination.tsx similarity index 100% rename from components/providers/table/DataTablePagination.tsx rename to components/providers/table/data-table-pagination.tsx diff --git a/components/providers/table/DataTableProvider.tsx b/components/providers/table/data-table-provider.tsx similarity index 92% rename from components/providers/table/DataTableProvider.tsx rename to components/providers/table/data-table-provider.tsx index b89fa2a6c6..e300496913 100644 --- a/components/providers/table/DataTableProvider.tsx +++ b/components/providers/table/data-table-provider.tsx @@ -23,8 +23,8 @@ import { } from "@/components/ui"; import { MetaDataProps } from "@/types"; -import { DataTablePagination } from "./DataTablePagination"; -import { FilterColumnTable } from "./FilterColumnTable"; +import { DataTableFilterCustom } from "./data-table-filter-custom"; +import { DataTablePagination } from "./data-table-pagination"; interface DataTableProviderProps { columns: ColumnDef[]; @@ -56,7 +56,7 @@ export function DataTableProvider({ }); // This will be used to have "custom" filters in the table and will be - // passed to the FilterColumnTable component. This needs to be dynamic + // passed to the DataTableFilterCustom component. This needs to be dynamic // based on the columns that are available in the table. const filters = [ @@ -67,7 +67,7 @@ export function DataTableProvider({ return ( <> - +
diff --git a/components/providers/table/data-table-row-actions.tsx b/components/providers/table/data-table-row-actions.tsx new file mode 100644 index 0000000000..e70e0b2496 --- /dev/null +++ b/components/providers/table/data-table-row-actions.tsx @@ -0,0 +1,112 @@ +"use client"; + +import { + Button, + Dropdown, + DropdownItem, + DropdownMenu, + DropdownSection, + DropdownTrigger, +} from "@nextui-org/react"; +import { + AddNoteBulkIcon, + DeleteDocumentBulkIcon, + EditDocumentBulkIcon, +} from "@nextui-org/shared-icons"; +import { Row } from "@tanstack/react-table"; +import clsx from "clsx"; +import { useState } from "react"; + +import { VerticalDotsIcon } from "@/components/icons"; +import { CustomAlertModal } from "@/components/ui/custom"; + +import { CheckConnectionProvider } from "../CheckConnectionProvider"; +import { DeleteForm } from "../forms/DeleteForm"; + +interface DataTableRowActionsProps { + row: Row; +} +const iconClasses = + "text-2xl text-default-500 pointer-events-none flex-shrink-0"; + +export function DataTableRowActions({ + row, +}: DataTableRowActionsProps) { + // const [isEditOpen, setIsEditOpen] = useState(false); + const [isDeleteOpen, setIsDeleteOpen] = useState(false); + const providerId = (row.original as { id: string }).id; + return ( + <> + {/* + + */} + + + + +
+ + + + + + + } + > + + + } + > + Edit Provider + + + + + } + onClick={() => setIsDeleteOpen(true)} + > + Delete Provider + + + + +
+ + ); +} diff --git a/components/providers/table/index.ts b/components/providers/table/index.ts new file mode 100644 index 0000000000..673896eada --- /dev/null +++ b/components/providers/table/index.ts @@ -0,0 +1,7 @@ +export { ColumnsProvider } from "./columns-provider"; +export { DataTableColumnHeader } from "./data-table-column-header"; +export { DataTableFilterCustom } from "./data-table-filter-custom"; +export { DataTablePagination } from "./data-table-pagination"; +export { DataTableProvider } from "./data-table-provider"; +export { DataTableRowActions } from "./data-table-row-actions"; +export { SkeletonTableProvider } from "./skeleton-table-provider"; diff --git a/components/providers/table/SkeletonTableProvider.tsx b/components/providers/table/skeleton-table-provider.tsx similarity index 100% rename from components/providers/table/SkeletonTableProvider.tsx rename to components/providers/table/skeleton-table-provider.tsx diff --git a/components/ui/alert-dialog/AlertDialog.tsx b/components/ui/alert-dialog/AlertDialog.tsx new file mode 100644 index 0000000000..474aee7665 --- /dev/null +++ b/components/ui/alert-dialog/AlertDialog.tsx @@ -0,0 +1,131 @@ +"use client"; + +import { cn } from "@nextui-org/react"; +import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"; +import * as React from "react"; + +const AlertDialog = AlertDialogPrimitive.Root; + +const AlertDialogTrigger = AlertDialogPrimitive.Trigger; + +const AlertDialogPortal = AlertDialogPrimitive.Portal; + +const AlertDialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName; + +const AlertDialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + +)); +AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName; + +const AlertDialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +AlertDialogHeader.displayName = "AlertDialogHeader"; + +const AlertDialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +AlertDialogFooter.displayName = "AlertDialogFooter"; + +const AlertDialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName; + +const AlertDialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogDescription.displayName = + AlertDialogPrimitive.Description.displayName; + +const AlertDialogAction = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName; + +const AlertDialogCancel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName; + +export { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogOverlay, + AlertDialogPortal, + AlertDialogTitle, + AlertDialogTrigger, +}; diff --git a/components/ui/custom/CustomAlertModal.tsx b/components/ui/custom/CustomAlertModal.tsx new file mode 100644 index 0000000000..f3c3c97e35 --- /dev/null +++ b/components/ui/custom/CustomAlertModal.tsx @@ -0,0 +1,39 @@ +import { Modal, ModalBody, ModalContent, ModalHeader } from "@nextui-org/react"; +import React, { ReactNode } from "react"; + +interface CustomAlertModalProps { + isOpen: boolean; + onOpenChange: (isOpen: boolean) => void; + title: string; + description: string; + children: ReactNode; +} + +export const CustomAlertModal: React.FC = ({ + isOpen, + onOpenChange, + title, + description, + children, +}) => { + return ( + + + {(_onClose) => ( + <> + {title} + +

{description}

+ {children} +
+ + )} +
+
+ ); +}; diff --git a/components/ui/custom/index.ts b/components/ui/custom/index.ts index 1599c7850f..0520404750 100644 --- a/components/ui/custom/index.ts +++ b/components/ui/custom/index.ts @@ -1,3 +1,4 @@ +export * from "./CustomAlertModal"; export * from "./CustomBox"; export * from "./CustomButtonClientAction"; export * from "./CustomInput"; diff --git a/components/ui/index.ts b/components/ui/index.ts index 70705ba015..17f6f0e874 100644 --- a/components/ui/index.ts +++ b/components/ui/index.ts @@ -1,5 +1,6 @@ export * from "./action-card/ActionCard"; export * from "./alert/Alert"; +export * from "./alert-dialog/AlertDialog"; export * from "./chart/Chart"; export * from "./dialog/Dialog"; export * from "./dropdown/Dropdown"; diff --git a/components/users/table/DataTableUser.tsx b/components/users/table/DataTableUser.tsx index 7b5c6a5049..b13093c2ab 100644 --- a/components/users/table/DataTableUser.tsx +++ b/components/users/table/DataTableUser.tsx @@ -11,7 +11,6 @@ import { } from "@tanstack/react-table"; import { useState } from "react"; -import { DataTablePagination } from "@/components/providers"; import { Table, TableBody, @@ -22,6 +21,8 @@ import { } from "@/components/ui"; import { MetaDataProps } from "@/types"; +import { DataTablePagination } from "./DataTablePagination"; + interface DataTableUserProps { columns: ColumnDef[]; data: TData[]; diff --git a/package-lock.json b/package-lock.json index 6ee023ac84..11d2ea0433 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@nextui-org/react": "^2.4.6", "@nextui-org/system": "2.2.1", "@nextui-org/theme": "2.2.5", + "@radix-ui/react-alert-dialog": "^1.1.1", "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-icons": "^1.3.0", @@ -2714,6 +2715,34 @@ "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" }, + "node_modules/@radix-ui/react-alert-dialog": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.1.tgz", + "integrity": "sha512-wmCoJwj7byuVuiLKqDLlX7ClSUU0vd9sdCeM+2Ls+uf13+cpSJoMgwysHq1SGVVkJj5Xn0XWi1NoRCdkMpr6Mw==", + "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-dialog": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "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-arrow": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz", @@ -2959,6 +2988,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.0.tgz", "integrity": "sha512-jQxj/0LKgp+j9BiTXz3O3sgs26RNet2iLWmsPyRz2SIcR4q/4SbazXfnYwbAr+vLYKSfc7qxzyGQA1HLlYiuNw==", + "license": "MIT", "peerDependencies": { "react": "^16.x || ^17.x || ^18.x" } @@ -3268,6 +3298,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", + "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.0" }, @@ -6148,6 +6179,7 @@ "version": "0.7.0", "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.0.tgz", "integrity": "sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==", + "license": "Apache-2.0", "dependencies": { "clsx": "2.0.0" }, @@ -6271,6 +6303,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", "engines": { "node": ">=6" } @@ -9543,6 +9576,7 @@ "version": "0.417.0", "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.417.0.tgz", "integrity": "sha512-F/MDUHDter8YMZ7JKQpW/5/+v38tdaoShKX3e+opYsqfCnaHwn+5zz3+lBrMDFMNtSsvxtNpchLIaMpEfsi/4w==", + "license": "ISC", "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } @@ -11707,6 +11741,7 @@ "version": "2.5.2", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.2.tgz", "integrity": "sha512-kjEBm+pvD+6eAwzJL2Bi+02/9LFLal1Gs61+QB7HvTfQQ0aXwC5LGT8PEt1gS0CWKktKe6ysPTAy3cBC5MeiIg==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/dcastil" @@ -11776,6 +11811,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", + "license": "MIT", "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders" } diff --git a/package.json b/package.json index b5df6e8b4e..9a84242353 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "@nextui-org/react": "^2.4.6", "@nextui-org/system": "2.2.1", "@nextui-org/theme": "2.2.5", + "@radix-ui/react-alert-dialog": "^1.1.1", "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-icons": "^1.3.0",