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:
Pablo Lara
2024-09-24 13:12:11 +02:00
committed by GitHub
12 changed files with 242 additions and 45 deletions
+65 -2
View File
@@ -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;
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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">
+1 -1
View File
@@ -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>
+116
View File
@@ -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
View File
@@ -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>
+2 -2
View File
@@ -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>
</>
+23 -30
View File
@@ -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}
/>
+15
View File
@@ -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
View File
@@ -1,2 +1,3 @@
export * from "./auth";
export * from "./authFormSchema";
export * from "./components";
export * from "./formSchemas";