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:
AmirReza Jamali
2025-09-20 16:13:04 +03:30
parent 2c43e945c2
commit e6493f5d83
27 changed files with 782 additions and 84 deletions
@@ -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;
@@ -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;