diff --git a/actions/providers.ts b/actions/providers.ts index 380ed72284..d4b0dc4d29 100644 --- a/actions/providers.ts +++ b/actions/providers.ts @@ -6,7 +6,7 @@ import { redirect } from "next/navigation"; import { auth } from "@/auth.config"; import { parseStringify } from "@/lib"; -export const getProvider = async ({ +export const getProviders = async ({ page = 1, query = "", sort = "", @@ -47,6 +47,68 @@ export const getProvider = async ({ } }; +export const getProvider = async (formData: FormData) => { + const session = await auth(); + const tenantId = session?.user.tenantId; + const providerId = formData.get("id"); + + const keyServer = process.env.API_BASE_URL; + const url = new URL(`${keyServer}/providers/${providerId}`); + + try { + const providers = await fetch(url.toString(), { + headers: { + "X-Tenant-ID": `${tenantId}`, + }, + }); + const data = await providers.json(); + const parsedData = parseStringify(data); + return parsedData; + } catch (error) { + return { + error: getErrorMessage(error), + }; + } +}; + +export const updateProvider = async (formData: FormData) => { + const session = await auth(); + const keyServer = process.env.API_BASE_URL; + + const tenantId = session?.user.tenantId; + const providerId = formData.get("providerId"); + const providerAlias = formData.get("alias"); + + const url = new URL(`${keyServer}/providers/${providerId}`); + + try { + const response = await fetch(url.toString(), { + method: "PATCH", + headers: { + "X-Tenant-ID": `${tenantId}`, + "Content-Type": "application/vnd.api+json", + }, + body: JSON.stringify({ + data: { + type: "Provider", + id: providerId, + attributes: { + alias: providerAlias, + }, + }, + }), + }); + const data = await response.json(); + revalidatePath("/providers"); + return parseStringify(data); + } catch (error) { + console.error(error); + return { + error: getErrorMessage(error), + }; + } +}; + export const addProvider = async (formData: FormData) => { const keyServer = process.env.API_BASE_URL; @@ -81,8 +143,8 @@ export const addProvider = async (formData: FormData) => { error: getErrorMessage(error), }; } - revalidatePath("/providers"); }; + export const checkConnectionProvider = async (formData: FormData) => { const keyServer = process.env.API_BASE_URL; @@ -107,6 +169,7 @@ export const checkConnectionProvider = async (formData: FormData) => { }; } }; + export const deleteProvider = async (formData: FormData) => { const keyServer = process.env.API_BASE_URL; diff --git a/app/(prowler)/providers/page.tsx b/app/(prowler)/providers/page.tsx index 79ccbf5d29..6a79a8a872 100644 --- a/app/(prowler)/providers/page.tsx +++ b/app/(prowler)/providers/page.tsx @@ -1,7 +1,7 @@ import { Spacer } from "@nextui-org/react"; import { Suspense } from "react"; -import { getProvider } from "@/actions"; +import { getProviders } from "@/actions"; import { FilterControls } from "@/components/filters"; import { AddProviderModal } from "@/components/providers"; import { @@ -54,7 +54,7 @@ const SSRDataTable = async ({ // Extract query from filters const query = (filters["filter[search]"] as string) || ""; - const providersData = await getProvider({ query, page, sort, filters }); + const providersData = await getProviders({ query, page, sort, filters }); return ( { placeholder="Enter your email" /> - + {type === "sign-in" && (
diff --git a/components/providers/forms/DeleteForm.tsx b/components/providers/forms/DeleteForm.tsx index b739299fb8..2a9d79902c 100644 --- a/components/providers/forms/DeleteForm.tsx +++ b/components/providers/forms/DeleteForm.tsx @@ -59,7 +59,7 @@ export const DeleteForm = ({ disabled={isLoading} className="w-full hidden sm:block" type="button" - onClick={() => setIsOpen(false)} + onPress={() => setIsOpen(false)} > Cancel diff --git a/components/providers/forms/EditForm.tsx b/components/providers/forms/EditForm.tsx new file mode 100644 index 0000000000..8479aeeabb --- /dev/null +++ b/components/providers/forms/EditForm.tsx @@ -0,0 +1,116 @@ +"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 { updateProvider } from "@/actions"; +import { useToast } from "@/components/ui"; +import { CustomInput } from "@/components/ui/custom"; +import { Form } from "@/components/ui/form"; +import { editProviderFormSchema } from "@/types"; + +export const EditForm = ({ + providerId, + providerAlias, + setIsOpen, +}: { + providerId: string; + providerAlias?: string; + setIsOpen: Dispatch>; +}) => { + const formSchema = editProviderFormSchema(providerAlias ?? ""); + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + providerId: providerId, + alias: providerAlias, + }, + }); + + const { toast } = useToast(); + const isLoading = form.formState.isSubmitting; + + const onSubmitClient = async (values: z.infer) => { + const formData = new FormData(); + + Object.entries(values).forEach( + ([key, value]) => value !== undefined && formData.append(key, value), + ); + + const data = await updateProvider(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 updated successfully.", + }); + setIsOpen(false); // Close the modal on success + } + }; + + return ( +
+ +
+ Current alias: {providerAlias} +
+
+ +
+ + +
+ + + +
+
+ + ); +}; diff --git a/components/providers/forms/index.ts b/components/providers/forms/index.ts index 005f96941f..af1cdc1b37 100644 --- a/components/providers/forms/index.ts +++ b/components/providers/forms/index.ts @@ -1 +1,2 @@ export * from "./DeleteForm"; +export * from "./EditForm"; diff --git a/components/providers/table/data-table-row-actions.tsx b/components/providers/table/data-table-row-actions.tsx index e70e0b2496..5b8067e0e8 100644 --- a/components/providers/table/data-table-row-actions.tsx +++ b/components/providers/table/data-table-row-actions.tsx @@ -21,6 +21,7 @@ import { VerticalDotsIcon } from "@/components/icons"; import { CustomAlertModal } from "@/components/ui/custom"; import { CheckConnectionProvider } from "../CheckConnectionProvider"; +import { EditForm } from "../forms"; import { DeleteForm } from "../forms/DeleteForm"; interface DataTableRowActionsProps { @@ -32,18 +33,24 @@ const iconClasses = export function DataTableRowActions({ row, }: DataTableRowActionsProps) { - // const [isEditOpen, setIsEditOpen] = useState(false); + const [isEditOpen, setIsEditOpen] = useState(false); const [isDeleteOpen, setIsDeleteOpen] = useState(false); const providerId = (row.original as { id: string }).id; + const providerAlias = (row.original as any).attributes?.alias; return ( <> - {/* - - */} + + ({ shortcut="⌘⇧E" textValue="Edit Provider" startContent={} + onClick={() => setIsEditOpen(true)} > Edit Provider diff --git a/components/ui/custom/CustomAlertModal.tsx b/components/ui/custom/CustomAlertModal.tsx index f3c3c97e35..bf3435b7bb 100644 --- a/components/ui/custom/CustomAlertModal.tsx +++ b/components/ui/custom/CustomAlertModal.tsx @@ -27,8 +27,8 @@ export const CustomAlertModal: React.FC = ({ {(_onClose) => ( <> {title} - -

{description}

+ + {description} {children} diff --git a/components/ui/custom/CustomInput.tsx b/components/ui/custom/CustomInput.tsx index 8a876eec62..81a91316d0 100644 --- a/components/ui/custom/CustomInput.tsx +++ b/components/ui/custom/CustomInput.tsx @@ -3,48 +3,39 @@ import { Icon } from "@iconify/react"; import { Input } from "@nextui-org/react"; import React, { useState } from "react"; -import { Control, FieldPath } from "react-hook-form"; -import { z } from "zod"; +import { Control, FieldPath, FieldValues } from "react-hook-form"; import { FormControl, FormField, FormMessage } from "@/components/ui/form"; -import { authFormSchema } from "@/types"; -const formSchema = authFormSchema("sign-up"); - -type CustomInputProps = { - control: Control>; +interface CustomInputProps { + control: Control; + name: FieldPath; + label?: string; + labelPlacement?: "inside" | "outside"; + variant?: "flat" | "bordered" | "underlined" | "faded"; + type?: string; + placeholder?: string; + password?: boolean; isRequired?: boolean; -} & ( - | { - password?: false; - name: FieldPath>; - label: string; - type: "text" | "email"; - placeholder: string; - } - | { - password: true; - name?: never; - label?: never; - type?: never; - placeholder?: never; - } -); + isInvalid?: boolean; +} -export const CustomInput = ({ +export const CustomInput = ({ control, name, - type, - label, + type = "text", + label = name, + labelPlacement = "inside", placeholder, + variant = "bordered", password = false, isRequired = true, -}: CustomInputProps) => { + isInvalid, +}: CustomInputProps) => { const [isVisible, setIsVisible] = useState(false); const toggleVisibility = () => setIsVisible(!isVisible); - const inputName = password ? "password" : name!; const inputLabel = password ? "Password" : label; const inputType = password ? (isVisible ? "text" : "password") : type; const inputPlaceholder = password ? "Enter your password" : placeholder; @@ -62,16 +53,18 @@ export const CustomInput = ({ return ( >} + name={name} render={({ field }) => ( <> diff --git a/types/auth.ts b/types/authFormSchema.ts similarity index 100% rename from types/auth.ts rename to types/authFormSchema.ts diff --git a/types/formSchemas.ts b/types/formSchemas.ts new file mode 100644 index 0000000000..ee174f95d0 --- /dev/null +++ b/types/formSchemas.ts @@ -0,0 +1,15 @@ +import { z } from "zod"; + +export const editProviderFormSchema = (currentAlias: string) => + z.object({ + alias: z + .string() + .refine((val) => val === "" || val.length >= 3, { + message: "The alias must be empty or have at least 3 characters.", + }) + .refine((val) => val !== currentAlias, { + message: "The new alias must be different from the current one.", + }) + .optional(), + providerId: z.string(), + }); diff --git a/types/index.ts b/types/index.ts index 89e43941c9..955dd0d6f8 100644 --- a/types/index.ts +++ b/types/index.ts @@ -1,2 +1,3 @@ -export * from "./auth"; +export * from "./authFormSchema"; export * from "./components"; +export * from "./formSchemas";