mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-23 04:21:52 +00:00
Merge pull request #62 from prowler-cloud/PRWLR-4041-Providers-Page-Manage-Providers-Modal-Delete-Providers-Modal
Providers page manage providers modal
This commit is contained in:
+65
-2
@@ -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;
|
||||
|
||||
|
||||
@@ -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 (
|
||||
<DataTableProvider
|
||||
|
||||
@@ -126,7 +126,7 @@ export const AuthForm = ({ type }: { type: string }) => {
|
||||
placeholder="Enter your email"
|
||||
/>
|
||||
|
||||
<CustomInput control={form.control} password />
|
||||
<CustomInput control={form.control} name="password" password />
|
||||
|
||||
{type === "sign-in" && (
|
||||
<div className="flex items-center justify-between px-1 py-2">
|
||||
|
||||
@@ -59,7 +59,7 @@ export const DeleteForm = ({
|
||||
disabled={isLoading}
|
||||
className="w-full hidden sm:block"
|
||||
type="button"
|
||||
onClick={() => setIsOpen(false)}
|
||||
onPress={() => setIsOpen(false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
@@ -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<SetStateAction<boolean>>;
|
||||
}) => {
|
||||
const formSchema = editProviderFormSchema(providerAlias ?? "");
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
providerId: providerId,
|
||||
alias: providerAlias,
|
||||
},
|
||||
});
|
||||
|
||||
const { toast } = useToast();
|
||||
const isLoading = form.formState.isSubmitting;
|
||||
|
||||
const onSubmitClient = async (values: z.infer<typeof formSchema>) => {
|
||||
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 (
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmitClient)}
|
||||
className="flex flex-col space-y-4"
|
||||
>
|
||||
<div className="text-md">
|
||||
Current alias: <span className="font-bold">{providerAlias}</span>
|
||||
</div>
|
||||
<div>
|
||||
<CustomInput
|
||||
control={form.control}
|
||||
name="alias"
|
||||
type="text"
|
||||
label="Alias"
|
||||
labelPlacement="inside"
|
||||
placeholder={providerAlias}
|
||||
variant="bordered"
|
||||
isRequired={false}
|
||||
isInvalid={!!form.formState.errors.alias}
|
||||
/>
|
||||
</div>
|
||||
<input type="hidden" name="providerId" value={providerId} />
|
||||
|
||||
<div className="w-full flex justify-center sm:space-x-6">
|
||||
<Button
|
||||
size="lg"
|
||||
variant="bordered"
|
||||
disabled={isLoading}
|
||||
className="w-full hidden sm:block"
|
||||
type="button"
|
||||
onPress={() => setIsOpen(false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
size="lg"
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
className="w-full"
|
||||
>
|
||||
{isLoading ? (
|
||||
<CircularProgress aria-label="Loading..." size="md" />
|
||||
) : (
|
||||
<span>Save</span>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
@@ -1 +1,2 @@
|
||||
export * from "./DeleteForm";
|
||||
export * from "./EditForm";
|
||||
|
||||
@@ -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<ProviderProps> {
|
||||
@@ -32,18 +33,24 @@ const iconClasses =
|
||||
export function DataTableRowActions<ProviderProps>({
|
||||
row,
|
||||
}: DataTableRowActionsProps<ProviderProps>) {
|
||||
// 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 (
|
||||
<>
|
||||
{/* <ResponsiveDialog
|
||||
<CustomAlertModal
|
||||
isOpen={isEditOpen}
|
||||
setIsOpen={setIsEditOpen}
|
||||
title="Edit Person"
|
||||
onOpenChange={setIsEditOpen}
|
||||
title="Edit Provider"
|
||||
description={"Edit the provider details"}
|
||||
>
|
||||
<EditForm providerId={providerId} setIsOpen={setIsEditOpen} />
|
||||
</ResponsiveDialog> */}
|
||||
<EditForm
|
||||
providerId={providerId}
|
||||
providerAlias={providerAlias}
|
||||
setIsOpen={setIsEditOpen}
|
||||
/>
|
||||
</CustomAlertModal>
|
||||
<CustomAlertModal
|
||||
isOpen={isDeleteOpen}
|
||||
onOpenChange={setIsDeleteOpen}
|
||||
@@ -82,6 +89,7 @@ export function DataTableRowActions<ProviderProps>({
|
||||
shortcut="⌘⇧E"
|
||||
textValue="Edit Provider"
|
||||
startContent={<EditDocumentBulkIcon className={iconClasses} />}
|
||||
onClick={() => setIsEditOpen(true)}
|
||||
>
|
||||
Edit Provider
|
||||
</DropdownItem>
|
||||
|
||||
@@ -27,8 +27,8 @@ export const CustomAlertModal: React.FC<CustomAlertModalProps> = ({
|
||||
{(_onClose) => (
|
||||
<>
|
||||
<ModalHeader className="flex flex-col py-0">{title}</ModalHeader>
|
||||
<ModalBody className="space-y-2">
|
||||
<p>{description}</p>
|
||||
<ModalBody>
|
||||
{description}
|
||||
{children}
|
||||
</ModalBody>
|
||||
</>
|
||||
|
||||
@@ -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<z.infer<typeof formSchema>>;
|
||||
interface CustomInputProps<T extends FieldValues> {
|
||||
control: Control<T>;
|
||||
name: FieldPath<T>;
|
||||
label?: string;
|
||||
labelPlacement?: "inside" | "outside";
|
||||
variant?: "flat" | "bordered" | "underlined" | "faded";
|
||||
type?: string;
|
||||
placeholder?: string;
|
||||
password?: boolean;
|
||||
isRequired?: boolean;
|
||||
} & (
|
||||
| {
|
||||
password?: false;
|
||||
name: FieldPath<z.infer<typeof formSchema>>;
|
||||
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 = <T extends FieldValues>({
|
||||
control,
|
||||
name,
|
||||
type,
|
||||
label,
|
||||
type = "text",
|
||||
label = name,
|
||||
labelPlacement = "inside",
|
||||
placeholder,
|
||||
variant = "bordered",
|
||||
password = false,
|
||||
isRequired = true,
|
||||
}: CustomInputProps) => {
|
||||
isInvalid,
|
||||
}: CustomInputProps<T>) => {
|
||||
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 (
|
||||
<FormField
|
||||
control={control}
|
||||
name={inputName as FieldPath<z.infer<typeof formSchema>>}
|
||||
name={name}
|
||||
render={({ field }) => (
|
||||
<>
|
||||
<FormControl>
|
||||
<Input
|
||||
isRequired={inputIsRequired}
|
||||
label={inputLabel}
|
||||
labelPlacement={labelPlacement}
|
||||
placeholder={inputPlaceholder}
|
||||
type={inputType}
|
||||
variant="bordered"
|
||||
variant={variant}
|
||||
isInvalid={isInvalid}
|
||||
endContent={endContent}
|
||||
{...field}
|
||||
/>
|
||||
|
||||
@@ -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(),
|
||||
});
|
||||
+2
-1
@@ -1,2 +1,3 @@
|
||||
export * from "./auth";
|
||||
export * from "./authFormSchema";
|
||||
export * from "./components";
|
||||
export * from "./formSchemas";
|
||||
|
||||
Reference in New Issue
Block a user