Files
tm_panel/src/components/forms/companies/CreateCompany.tsx
T
AmirReza Jamali 3801073fd7 feat: Add loading states and improve form handling
This commit introduces several enhancements to form components and user experience across the application.

- **Sign-in Form:**
  - Implemented a loading state using `useIsMutating` from TanStack Query.
  - The username and password fields are now disabled during the login process to prevent concurrent submissions.
  - The "Sign in" button displays a spinner while the mutation is pending, providing clear visual feedback.

- **Assign to Company Modal:**
  - Refactored the modal to use `react-hook-form` for more robust state management and validation.
  - The form submission button is now disabled while the assignment request is in progress.

- **Tag Input Component:**
  - Added a new `shouldAddWithSpace` prop to allow creating tags by pressing the space bar, increasing the component's flexibility.

- **Header:**
  - Corrected the image path for the mobile header logo to display the correct icon.
2025-09-24 10:45:45 +03:30

537 lines
17 KiB
TypeScript

"use client";
import { GlobeIcon, MoneyIcon } from "@/assets/icons";
import InputGroup from "@/components/FormElements/InputGroup";
import TagInput from "@/components/FormElements/InputGroup/tag-input";
import { TextAreaGroup } from "@/components/FormElements/InputGroup/text-area";
import MultiSelect from "@/components/FormElements/MultiSelect";
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";
interface IProps {
editMode?: boolean;
defaultValues?: ICreateCompanyCredentials;
id?: string;
}
const CreateCompany = ({ defaultValues, editMode, id }: IProps) => {
const {
errors,
handleSubmit,
onSubmit,
register,
watch,
setValue,
companyCategories,
} = 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"
onSubmit={handleSubmit(onSubmit)}
>
<div className="flex flex-col gap-9">
<ShowcaseSection
title="Company Information"
className="flex flex-col gap-5"
>
<InputGroup
{...register("name", {
required: { message: FormErrorMessages.required, value: true },
minLength: {
value: 2,
message: FormErrorMessages.minLength(2),
},
maxLength: {
value: 200,
message: FormErrorMessages.maxLength(200),
},
})}
label="Name"
name="name"
required
type="text"
placeholder="Enter Company Name"
/>
{errors.name && (
<p className="mt-1 text-sm text-red-500">{errors.name.message}</p>
)}
<InputGroup
{...register("email", {
required: { message: FormErrorMessages.required, value: true },
pattern: {
value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
message: FormErrorMessages.invalidEmail,
},
})}
label="Email"
name="email"
required
type="email"
placeholder="Enter Company Email"
/>
{errors.email && (
<p className="mt-1 text-sm text-red-500">{errors.email.message}</p>
)}
<InputGroup
{...register("phone", {
required: { message: FormErrorMessages.required, value: true },
minLength: {
value: 10,
message: FormErrorMessages.minLength(10),
},
maxLength: {
value: 20,
message: FormErrorMessages.maxLength(20),
},
pattern: {
value: PHONE_REGEX,
message: FormErrorMessages.invalidPattern,
},
})}
label="Phone"
name="phone"
required
type="tel"
placeholder="Enter Company Phone"
/>
{errors.phone && (
<p className="mt-1 text-sm text-red-500">{errors.phone.message}</p>
)}
<Select
{...register("type", {
required: { message: FormErrorMessages.required, value: true },
})}
name="type"
label="Company type"
required
items={[
{ value: "private", label: "Private" },
{ value: "public", label: "Public" },
{ value: "government", label: "Government" },
{ value: "ngo", label: "NGO" },
{ value: "startup", label: "Startup" },
]}
defaultValue="Private"
prefixIcon={<GlobeIcon />}
/>
{errors.type && (
<p className="mt-1 text-sm text-red-500">{errors.type.message}</p>
)}
<InputGroup
{...register("registration_number", {
required: { message: FormErrorMessages.required, value: true },
minLength: { value: 5, message: FormErrorMessages.minLength(5) },
maxLength: {
value: 50,
message: FormErrorMessages.maxLength(50),
},
})}
label="Registration Number"
name="registration_number"
type="text"
required
placeholder="Enter Registration Number"
/>
{errors.registration_number && (
<p className="mt-1 text-sm text-red-500">
{errors.registration_number.message}
</p>
)}
<InputGroup
{...register("tax_id", {
required: { message: FormErrorMessages.required, value: true },
minLength: { value: 5, message: FormErrorMessages.minLength(5) },
maxLength: {
value: 50,
message: FormErrorMessages.maxLength(50),
},
})}
label="Tax id"
name="tax_id"
type="text"
required
placeholder="Enter Tax id"
/>
{errors.tax_id && (
<p className="mt-1 text-sm text-red-500">{errors.tax_id.message}</p>
)}
<InputGroup
{...register("industry", {
required: { message: FormErrorMessages.required, value: true },
minLength: { value: 2, message: FormErrorMessages.minLength(2) },
maxLength: {
value: 100,
message: FormErrorMessages.maxLength(100),
},
})}
label="Industry"
name="industry"
type="text"
required
placeholder="Enter Industry"
/>
{errors.industry && (
<p className="mt-1 text-sm text-red-500">
{errors.industry.message}
</p>
)}
<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>
)}
<InputGroup
{...register("website", {
required: { message: FormErrorMessages.required, value: true },
minLength: { value: 2, message: FormErrorMessages.minLength(2) },
maxLength: {
value: 100,
message: FormErrorMessages.maxLength(100),
},
})}
label="Website"
required
name="website"
type="url"
placeholder="Enter Company Website"
/>
{errors.website && (
<p className="mt-1 text-sm text-red-500">
{errors.website.message}
</p>
)}
</ShowcaseSection>
<ShowcaseSection title="Settings" className="grid grid-cols-1 gap-5">
<Select
{...register("language")}
name="language"
label="Language"
items={[
{ value: "en", label: "EN" },
{ value: "ar", label: "AR" },
{ value: "fr", label: "FR" },
{ value: "es", label: "ES" },
{ value: "de", label: "DE" },
{ value: "zh", label: "ZH" },
{ value: "jEna", label: "jEna" },
{ value: "ko", label: "KO" },
]}
defaultValue="en"
prefixIcon={<GlobeIcon />}
/>
{errors.language && (
<p className="mt-1 text-sm text-red-500">
{errors.language.message}
</p>
)}
<Select
{...register("currency")}
name="currency"
label="Currency"
items={[
{ label: "USD", value: "USD" },
{ label: "EUR", value: "EUR" },
{ label: "GBP", value: "GBP" },
{ label: "JPY", value: "JPY" },
{ label: "CAD", value: "CAD" },
{ label: "AUD", value: "AUD" },
{ label: "CHF", value: "CHF" },
{ label: "CNY", value: "CNY" },
{ label: "INR", value: "INR" },
{ label: "BRL", value: "BRL" },
]}
defaultValue="en"
prefixIcon={<MoneyIcon />}
/>
{errors.currency && (
<p className="mt-1 text-sm text-red-500">
{errors.currency.message}
</p>
)}
<InputGroup
{...register("timezone", {
minLength: { value: 3, message: FormErrorMessages.minLength(3) },
maxLength: {
value: 50,
message: FormErrorMessages.maxLength(50),
},
})}
label="Timezone"
name="timezone"
type="text"
placeholder="Enter Timezone"
/>
{errors.timezone && (
<p className="mt-1 text-sm text-red-500">
{errors.timezone.message}
</p>
)}
</ShowcaseSection>
</div>
<div className="flex flex-col gap-9">
<ShowcaseSection
title="Business Information"
className="grid grid-cols-1 gap-5"
>
<InputGroup
{...register("employee_count", {
min: { value: 1, message: FormErrorMessages.min(1) },
max: { value: 100000, message: FormErrorMessages.max(100000) },
})}
label="Employee Count"
name="employee_count"
type="number"
placeholder="Enter Employee Count"
/>
{errors.employee_count && (
<p className="mt-1 text-sm text-red-500">
{errors.employee_count.message}
</p>
)}
<InputGroup
{...register("annual_revenue", {
min: { value: 0, message: FormErrorMessages.min(0) },
max: {
value: 999999999999,
message: FormErrorMessages.max(999999999999),
},
})}
label="Annual Revenue"
name="annual_revenue"
type="number"
placeholder="Enter Annual Revenue"
/>
{errors.annual_revenue && (
<p className="mt-1 text-sm text-red-500">
{errors.annual_revenue.message}
</p>
)}
<InputGroup
{...register("founded_year", {
min: { value: 1800, message: FormErrorMessages.min(1800) },
max: {
value: 2100,
message: FormErrorMessages.max(2100),
},
})}
label="Founded Year"
name="founded_year"
type="number"
placeholder="Enter Founded Year"
/>
{errors.founded_year && (
<p className="mt-1 text-sm text-red-500">
{errors.founded_year.message}
</p>
)}
</ShowcaseSection>
<ShowcaseSection title="Address" className="grid grid-cols-1 gap-5">
<InputGroup
{...register("address.street", {
required: { message: FormErrorMessages.required, value: true },
minLength: {
value: 5,
message: FormErrorMessages.minLength(5),
},
maxLength: {
value: 200,
message: FormErrorMessages.maxLength(200),
},
})}
label="Street"
name="address.street"
required
type="text"
placeholder="Enter Street"
/>
{errors.address?.street && (
<p className="mt-1 text-sm text-red-500">
{errors.address.street.message}
</p>
)}
<InputGroup
{...register("address.city", {
required: { message: FormErrorMessages.required, value: true },
minLength: {
value: 2,
message: FormErrorMessages.minLength(2),
},
maxLength: {
value: 100,
message: FormErrorMessages.maxLength(100),
},
})}
label="City"
required
name="address.city"
type="text"
placeholder="Enter City"
/>
{errors.address?.city && (
<p className="mt-1 text-sm text-red-500">
{errors.address.city.message}
</p>
)}
<InputGroup
{...register("address.state", {
required: { message: FormErrorMessages.required, value: true },
minLength: {
value: 2,
message: FormErrorMessages.minLength(2),
},
maxLength: {
value: 100,
message: FormErrorMessages.maxLength(100),
},
})}
label="State"
name="address.state"
type="text"
required
placeholder="Enter State"
/>
{errors.address?.state && (
<p className="mt-1 text-sm text-red-500">
{errors.address.state.message}
</p>
)}
<InputGroup
{...register("address.postal_code", {
required: { message: FormErrorMessages.required, value: true },
minLength: {
value: 3,
message: FormErrorMessages.minLength(3),
},
maxLength: {
value: 20,
message: FormErrorMessages.maxLength(20),
},
})}
label="Postal code"
name="address.postal_code"
type="text"
required
placeholder="Enter Postal code"
/>
{errors.address?.postal_code && (
<p className="mt-1 text-sm text-red-500">
{errors.address.postal_code.message}
</p>
)}
<InputGroup
{...register("address.country", {
required: { message: FormErrorMessages.required, value: true },
minLength: {
value: 2,
message: FormErrorMessages.minLength(2),
},
maxLength: {
value: 100,
message: FormErrorMessages.maxLength(100),
},
})}
label="Country"
name="address.country"
type="text"
required
placeholder="Enter Country"
/>
{errors.address?.country && (
<p className="mt-1 text-sm text-red-500">
{errors.address.country.message}
</p>
)}
</ShowcaseSection>
<ShowcaseSection title="Tags" className="grid grid-cols-1 gap-5">
<TagInput
{...register("tags.keywords")}
name="tags.keywords"
label="Keywords"
placeholder="Enter Tag Keywords"
register={register}
setValue={setValue}
watch={watch}
/>
<MultiSelect
{...register("tags.categories")}
value={watch("tags.categories")}
name="tags.categories"
label="Categories"
items={
companyCategories
? companyCategories?.data?.categories?.map((c) => {
return {
text: c.name,
value: c.id,
};
})
: []
}
placeholder="Please select categories"
/>
<TagInput
{...register("tags.cpv_codes")}
label="CPV codes"
name="tags.cpv_codes"
shouldAddWithSpace
watch={watch}
setValue={setValue}
placeholder="Enter Tag CPV codes"
/>
<TagInput
{...register("tags.certifications")}
label="Certifications"
name="tags.certifications"
watch={watch}
setValue={setValue}
placeholder="Enter Tag Certifications"
/>
<TagInput
{...register("tags.specializations")}
label="Specializations"
name="tags.specializations"
watch={watch}
setValue={setValue}
placeholder="Enter Tag Specializations"
/>
{errors.tags?.specializations && (
<p className="mt-1 text-sm text-red-500">
{errors.tags.specializations.message}
</p>
)}
</ShowcaseSection>
</div>
<FormFooter />
</form>
);
};
export default CreateCompany;