feat(form): add loading state and replace custom switch component
This commit introduces a loading state for form submissions and replaces the custom switch component with a third-party library for improved user experience and maintainability. - Adds `react-switch` as a dependency to replace the custom-built switch component, providing a more robust and feature-rich solution. - Implements a loading indicator on form submission buttons and in the confirmation modal by utilizing the `useIsMutating` hook from TanStack Query. This provides clear visual feedback to the user during asynchronous operations. - Creates a reusable `FormFooter` component to encapsulate the submit and cancel buttons, centralizing the form submission logic and loading state. - Refactors the company create/edit forms to use the new `FormFooter` and `react-switch` components. - Moves the `BreadcrumbItem` type to a shared types file for better code organization.
This commit is contained in:
@@ -1,15 +1,14 @@
|
||||
"use client";
|
||||
import { GlobeIcon, MoneyIcon } from "@/assets/icons";
|
||||
import InputGroup from "@/components/FormElements/InputGroup";
|
||||
import { TextAreaGroup } from "@/components/FormElements/InputGroup/text-area";
|
||||
import { Select } from "@/components/FormElements/select";
|
||||
import { ShowcaseSection } from "@/components/Layouts/showcase-section";
|
||||
import FormFooter from "@/components/ui/FormFooter";
|
||||
import { PHONE_REGEX } from "@/constants/regex";
|
||||
import { FormErrorMessages } from "@/constants/Texts";
|
||||
import { ICreateCompanyCredentials } from "@/lib/api/types/Company";
|
||||
import useCreateCompanyPresenter from "./useCreateCompanyPresenter";
|
||||
import { ShowcaseSection } from "@/components/Layouts/showcase-section";
|
||||
import { title } from "process";
|
||||
import InputGroup from "@/components/FormElements/InputGroup";
|
||||
import { FormErrorMessages } from "@/constants/Texts";
|
||||
import { Select } from "@/components/FormElements/select";
|
||||
import { CurrencyIcon, GlobeIcon, MoneyIcon } from "@/assets/icons";
|
||||
import MultiSelect from "@/components/FormElements/MultiSelect";
|
||||
import { TextAreaGroup } from "@/components/FormElements/InputGroup/text-area";
|
||||
import { PHONE_REGEX } from "@/constants/regex";
|
||||
|
||||
interface IProps {
|
||||
editMode?: boolean;
|
||||
@@ -18,21 +17,12 @@ interface IProps {
|
||||
}
|
||||
|
||||
const CreateCompany = ({ defaultValues, editMode, id }: IProps) => {
|
||||
const {
|
||||
errors,
|
||||
handleCancel,
|
||||
handleSubmit,
|
||||
isCreating,
|
||||
isMutating,
|
||||
isUpdating,
|
||||
onSubmit,
|
||||
router,
|
||||
register,
|
||||
} = useCreateCompanyPresenter({
|
||||
editMode,
|
||||
defaultValues,
|
||||
id,
|
||||
});
|
||||
const { errors, handleSubmit, onSubmit, register } =
|
||||
useCreateCompanyPresenter({
|
||||
editMode,
|
||||
defaultValues,
|
||||
id,
|
||||
});
|
||||
return (
|
||||
<form
|
||||
className="grid grid-cols-1 items-start gap-9 rounded-xl bg-gray-1 p-10 dark:bg-gray-7 sm:grid-cols-2"
|
||||
@@ -507,12 +497,17 @@ const CreateCompany = ({ defaultValues, editMode, id }: IProps) => {
|
||||
</ShowcaseSection>
|
||||
</div>
|
||||
|
||||
<div className="col-span-2 flex gap-6">
|
||||
<FormFooter />
|
||||
{/* <div className="col-span-2 flex gap-6">
|
||||
<button
|
||||
type="submit"
|
||||
className="mt-6 flex w-fit justify-center rounded-lg bg-primary p-[13px] font-medium text-white hover:bg-opacity-90"
|
||||
className="mt-6 flex w-fit items-center justify-center rounded-lg bg-primary p-[13px] font-medium text-white hover:bg-opacity-90"
|
||||
>
|
||||
Submit
|
||||
{isMutating ? (
|
||||
<div className="h-5 w-5 animate-spin rounded-full border-b-2 border-gray-1"></div>
|
||||
) : (
|
||||
"Submit"
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -521,7 +516,7 @@ const CreateCompany = ({ defaultValues, editMode, id }: IProps) => {
|
||||
>
|
||||
Opt out
|
||||
</button>
|
||||
</div>
|
||||
</div> */}
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
"use client";
|
||||
import InputGroup from "@/components/FormElements/InputGroup";
|
||||
import { TextAreaGroup } from "@/components/FormElements/InputGroup/text-area";
|
||||
import { ShowcaseSection } from "@/components/Layouts/showcase-section";
|
||||
import FormFooter from "@/components/ui/FormFooter";
|
||||
import { FormErrorMessages } from "@/constants/Texts";
|
||||
import { TCreateCompanyCategoryCredentials } from "@/lib/api";
|
||||
import useCreateCompanyCategoryPresenter from "./useCreateCompanyCategoryPresenter";
|
||||
|
||||
interface IProps {
|
||||
editMode?: boolean;
|
||||
defaultValues?: TCreateCompanyCategoryCredentials;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
const CreateCompanyCategoryForm = ({ defaultValues, editMode, id }: IProps) => {
|
||||
const { errors, handleSubmit, onSubmit, register } =
|
||||
useCreateCompanyCategoryPresenter({ defaultValues, editMode, id });
|
||||
return (
|
||||
<form className="grid grid-cols-1 gap-9" onSubmit={handleSubmit(onSubmit)}>
|
||||
<ShowcaseSection
|
||||
title="Create Company Category"
|
||||
className="grid grid-cols-2 gap-5"
|
||||
>
|
||||
<div className="flex flex-col gap-2">
|
||||
<InputGroup
|
||||
label="Name"
|
||||
{...register("name", {
|
||||
required: {
|
||||
value: true,
|
||||
message: FormErrorMessages.required,
|
||||
},
|
||||
minLength: {
|
||||
value: 2,
|
||||
message: FormErrorMessages.minLength(2),
|
||||
},
|
||||
maxLength: {
|
||||
value: 100,
|
||||
message: FormErrorMessages.maxLength(100),
|
||||
},
|
||||
})}
|
||||
type="text"
|
||||
name="name"
|
||||
placeholder="Enter Category Name"
|
||||
/>
|
||||
{errors.name && (
|
||||
<p className="mt-1 text-sm text-red-500">{errors.name.message}</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<TextAreaGroup
|
||||
label="Description"
|
||||
{...register("description", {
|
||||
minLength: {
|
||||
value: 10,
|
||||
message: FormErrorMessages.minLength(10),
|
||||
},
|
||||
maxLength: {
|
||||
value: 1000,
|
||||
message: FormErrorMessages.maxLength(1000),
|
||||
},
|
||||
})}
|
||||
name="description"
|
||||
placeholder="Enter Description"
|
||||
/>
|
||||
{errors.description && (
|
||||
<p className="mt-1 text-sm text-red-500">
|
||||
{errors.description.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<FormFooter />
|
||||
</ShowcaseSection>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateCompanyCategoryForm;
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
"use client";
|
||||
import {
|
||||
useCreateCompanyCategory,
|
||||
useUpdateCompanyCategories,
|
||||
} from "@/hooks/queries";
|
||||
import { TCreateCompanyCategoryCredentials } from "@/lib/api";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { SubmitHandler, useForm } from "react-hook-form";
|
||||
|
||||
interface IProps {
|
||||
editMode?: boolean;
|
||||
defaultValues?: TCreateCompanyCategoryCredentials;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
const useCreateCompanyCategoryPresenter = ({
|
||||
defaultValues,
|
||||
editMode,
|
||||
id,
|
||||
}: IProps) => {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
} = useForm<TCreateCompanyCategoryCredentials>({
|
||||
mode: "onChange",
|
||||
defaultValues,
|
||||
});
|
||||
const router = useRouter();
|
||||
const { mutate } = useCreateCompanyCategory();
|
||||
const { mutate: editCategory } = useUpdateCompanyCategories(id as string);
|
||||
const onSubmit: SubmitHandler<TCreateCompanyCategoryCredentials> = (data) => {
|
||||
if (editMode) {
|
||||
editCategory({ id: id as string, credentials: data });
|
||||
} else {
|
||||
mutate(data);
|
||||
}
|
||||
};
|
||||
return {
|
||||
register,
|
||||
onSubmit,
|
||||
handleSubmit,
|
||||
errors,
|
||||
router,
|
||||
};
|
||||
};
|
||||
|
||||
export default useCreateCompanyCategoryPresenter;
|
||||
Reference in New Issue
Block a user