refactor(forms): memoize dynamic form sections with stable ids
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
Add uuid-based section keys and memoized DynamicSection to reduce re-renders, fix dashboard GSAP scoping, update logo sizing, and temporarily hide notice-type and closing-soon widgets. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -46,6 +46,7 @@
|
|||||||
"react-toastify": "^11.0.5",
|
"react-toastify": "^11.0.5",
|
||||||
"react-tooltip": "^5.29.1",
|
"react-tooltip": "^5.29.1",
|
||||||
"tailwind-merge": "^2.6.0",
|
"tailwind-merge": "^2.6.0",
|
||||||
|
"uuid": "^14.0.1",
|
||||||
"zod": "^4.1.5"
|
"zod": "^4.1.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
Generated
+9
@@ -104,6 +104,9 @@ importers:
|
|||||||
tailwind-merge:
|
tailwind-merge:
|
||||||
specifier: ^2.6.0
|
specifier: ^2.6.0
|
||||||
version: 2.6.1
|
version: 2.6.1
|
||||||
|
uuid:
|
||||||
|
specifier: ^14.0.1
|
||||||
|
version: 14.0.1
|
||||||
zod:
|
zod:
|
||||||
specifier: ^4.1.5
|
specifier: ^4.1.5
|
||||||
version: 4.3.6
|
version: 4.3.6
|
||||||
@@ -2977,6 +2980,10 @@ packages:
|
|||||||
util-deprecate@1.0.2:
|
util-deprecate@1.0.2:
|
||||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||||
|
|
||||||
|
uuid@14.0.1:
|
||||||
|
resolution: {integrity: sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
uuid@8.3.2:
|
uuid@8.3.2:
|
||||||
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
|
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -6218,6 +6225,8 @@ snapshots:
|
|||||||
|
|
||||||
util-deprecate@1.0.2: {}
|
util-deprecate@1.0.2: {}
|
||||||
|
|
||||||
|
uuid@14.0.1: {}
|
||||||
|
|
||||||
uuid@8.3.2: {}
|
uuid@8.3.2: {}
|
||||||
|
|
||||||
verror@1.10.0:
|
verror@1.10.0:
|
||||||
|
|||||||
@@ -28,34 +28,41 @@ export function ClosingSoonCard({ tenders, isLoading }: Props) {
|
|||||||
const rootRef = useRef<HTMLDivElement>(null);
|
const rootRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
|
const root = rootRef.current;
|
||||||
|
if (!root) return;
|
||||||
|
|
||||||
const reduced =
|
const reduced =
|
||||||
typeof window !== "undefined" &&
|
typeof window !== "undefined" &&
|
||||||
window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
||||||
const ctx = gsap.context(() => {
|
const ctx = gsap.context(() => {
|
||||||
if (reduced) return;
|
if (reduced) return;
|
||||||
gsap.from(rootRef.current, {
|
gsap.from(root, {
|
||||||
opacity: 0,
|
opacity: 0,
|
||||||
y: 24,
|
y: 24,
|
||||||
duration: 0.6,
|
duration: 0.6,
|
||||||
ease: "power3.out",
|
ease: "power3.out",
|
||||||
delay: 0.3,
|
delay: 0.3,
|
||||||
});
|
});
|
||||||
gsap.from("[data-closing-row]", {
|
|
||||||
x: 24,
|
const rows = root.querySelectorAll("[data-closing-row]");
|
||||||
opacity: 0,
|
if (rows.length > 0) {
|
||||||
duration: 0.45,
|
gsap.from(rows, {
|
||||||
stagger: 0.07,
|
x: 24,
|
||||||
delay: 0.55,
|
opacity: 0,
|
||||||
ease: "power2.out",
|
duration: 0.45,
|
||||||
});
|
stagger: 0.07,
|
||||||
}, rootRef);
|
delay: 0.55,
|
||||||
|
ease: "power2.out",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, root);
|
||||||
return () => ctx.revert();
|
return () => ctx.revert();
|
||||||
}, [tenders.length, isLoading]);
|
}, [tenders.length, isLoading]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={rootRef}
|
ref={rootRef}
|
||||||
className="flex h-full flex-col overflow-hidden rounded-2xl bg-white shadow-1 ring-1 ring-stroke dark:bg-gray-dark dark:ring-stroke-dark dark:shadow-card"
|
className="flex h-full flex-col overflow-hidden rounded-2xl bg-white shadow-1 ring-1 ring-stroke dark:bg-gray-dark dark:shadow-card dark:ring-stroke-dark"
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between border-b border-stroke px-5 py-4 dark:border-stroke-dark">
|
<div className="flex items-center justify-between border-b border-stroke px-5 py-4 dark:border-stroke-dark">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
@@ -66,7 +73,9 @@ export function ClosingSoonCard({ tenders, isLoading }: Props) {
|
|||||||
<h2 className="text-base font-bold text-dark dark:text-white">
|
<h2 className="text-base font-bold text-dark dark:text-white">
|
||||||
Closing Soon
|
Closing Soon
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-xs text-dark-6">Tenders with looming deadlines</p>
|
<p className="text-xs text-dark-6">
|
||||||
|
Tenders with looming deadlines
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Link
|
<Link
|
||||||
@@ -80,7 +89,10 @@ export function ClosingSoonCard({ tenders, isLoading }: Props) {
|
|||||||
<ul className="flex-1 divide-y divide-stroke dark:divide-stroke-dark">
|
<ul className="flex-1 divide-y divide-stroke dark:divide-stroke-dark">
|
||||||
{isLoading
|
{isLoading
|
||||||
? Array.from({ length: 5 }).map((_, i) => (
|
? Array.from({ length: 5 }).map((_, i) => (
|
||||||
<li key={i} className="flex animate-pulse items-center gap-3 px-5 py-3.5">
|
<li
|
||||||
|
key={i}
|
||||||
|
className="flex animate-pulse items-center gap-3 px-5 py-3.5"
|
||||||
|
>
|
||||||
<div className="size-10 rounded-lg bg-gray-2 dark:bg-dark-3" />
|
<div className="size-10 rounded-lg bg-gray-2 dark:bg-dark-3" />
|
||||||
<div className="flex-1 space-y-2">
|
<div className="flex-1 space-y-2">
|
||||||
<div className="h-3 w-3/4 rounded bg-gray-2 dark:bg-dark-3" />
|
<div className="h-3 w-3/4 rounded bg-gray-2 dark:bg-dark-3" />
|
||||||
|
|||||||
@@ -53,15 +53,15 @@ export function TenderDashboard() {
|
|||||||
<CountryDistribution countries={countries} isLoading={isPending} />
|
<CountryDistribution countries={countries} isLoading={isPending} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-span-12 xl:col-span-5">
|
{/* <div className="col-span-12 xl:col-span-5">
|
||||||
<NoticeTypeBreakdown
|
<NoticeTypeBreakdown
|
||||||
noticeTypes={noticeTypes}
|
noticeTypes={noticeTypes}
|
||||||
isLoading={isPending}
|
isLoading={isPending}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div> */}
|
||||||
<div className="col-span-12 xl:col-span-7">
|
{/* <div className="col-span-12 xl:col-span-7">
|
||||||
<ClosingSoonCard tenders={closingSoonList} isLoading={isPending} />
|
<ClosingSoonCard tenders={closingSoonList} isLoading={isPending} />
|
||||||
</div>
|
</div> */}
|
||||||
|
|
||||||
<div className="col-span-12">
|
<div className="col-span-12">
|
||||||
<RecentTenders tenders={recent} isLoading={recentIsLoading} />
|
<RecentTenders tenders={recent} isLoading={recentIsLoading} />
|
||||||
|
|||||||
@@ -45,34 +45,41 @@ export function RecentTenders({ tenders, isLoading }: Props) {
|
|||||||
const rootRef = useRef<HTMLDivElement>(null);
|
const rootRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
|
const root = rootRef.current;
|
||||||
|
if (!root) return;
|
||||||
|
|
||||||
const reduced =
|
const reduced =
|
||||||
typeof window !== "undefined" &&
|
typeof window !== "undefined" &&
|
||||||
window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
||||||
const ctx = gsap.context(() => {
|
const ctx = gsap.context(() => {
|
||||||
if (reduced) return;
|
if (reduced) return;
|
||||||
gsap.from(rootRef.current, {
|
gsap.from(root, {
|
||||||
opacity: 0,
|
opacity: 0,
|
||||||
y: 24,
|
y: 24,
|
||||||
duration: 0.6,
|
duration: 0.6,
|
||||||
ease: "power3.out",
|
ease: "power3.out",
|
||||||
delay: 0.35,
|
delay: 0.35,
|
||||||
});
|
});
|
||||||
gsap.from("[data-recent-row]", {
|
|
||||||
x: -24,
|
const rows = root.querySelectorAll("[data-recent-row]");
|
||||||
opacity: 0,
|
if (rows.length > 0) {
|
||||||
duration: 0.5,
|
gsap.from(rows, {
|
||||||
stagger: 0.06,
|
x: -24,
|
||||||
delay: 0.55,
|
opacity: 0,
|
||||||
ease: "power2.out",
|
duration: 0.5,
|
||||||
});
|
stagger: 0.06,
|
||||||
}, rootRef);
|
delay: 0.55,
|
||||||
|
ease: "power2.out",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, root);
|
||||||
return () => ctx.revert();
|
return () => ctx.revert();
|
||||||
}, [tenders.length, isLoading]);
|
}, [tenders.length, isLoading]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={rootRef}
|
ref={rootRef}
|
||||||
className="flex h-full flex-col overflow-hidden rounded-2xl bg-white shadow-1 ring-1 ring-stroke dark:bg-gray-dark dark:ring-stroke-dark dark:shadow-card"
|
className="flex h-full flex-col overflow-hidden rounded-2xl bg-white shadow-1 ring-1 ring-stroke dark:bg-gray-dark dark:shadow-card dark:ring-stroke-dark"
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between border-b border-stroke px-5 py-4 dark:border-stroke-dark">
|
<div className="flex items-center justify-between border-b border-stroke px-5 py-4 dark:border-stroke-dark">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import type {
|
|||||||
HTMLInputTypeAttribute,
|
HTMLInputTypeAttribute,
|
||||||
ReactNode,
|
ReactNode,
|
||||||
} from "react";
|
} from "react";
|
||||||
|
import { memo } from "react";
|
||||||
|
|
||||||
export interface DynamicFieldContext<TValues extends FieldValues> {
|
export interface DynamicFieldContext<TValues extends FieldValues> {
|
||||||
control: Control<TValues>;
|
control: Control<TValues>;
|
||||||
@@ -61,6 +62,7 @@ export type DynamicFormField<TValues extends FieldValues> =
|
|||||||
| DynamicCustomField<TValues>;
|
| DynamicCustomField<TValues>;
|
||||||
|
|
||||||
export interface DynamicFormSection<TValues extends FieldValues> {
|
export interface DynamicFormSection<TValues extends FieldValues> {
|
||||||
|
id: string;
|
||||||
title?: ReactNode;
|
title?: ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
rootClassName?: string;
|
rootClassName?: string;
|
||||||
@@ -90,6 +92,70 @@ const getErrorMessage = <TValues extends FieldValues>(
|
|||||||
return typeof message === "string" ? message : undefined;
|
return typeof message === "string" ? message : undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface DynamicSectionProps<TValues extends FieldValues> {
|
||||||
|
section: DynamicFormSection<TValues>;
|
||||||
|
register: UseFormRegister<TValues>;
|
||||||
|
control: Control<TValues>;
|
||||||
|
errors: FieldErrors<TValues>;
|
||||||
|
footer?: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DynamicSectionComponent = <TValues extends FieldValues>({
|
||||||
|
section,
|
||||||
|
register,
|
||||||
|
control,
|
||||||
|
errors,
|
||||||
|
footer,
|
||||||
|
}: DynamicSectionProps<TValues>) => (
|
||||||
|
<ShowcaseSection
|
||||||
|
title={section.title}
|
||||||
|
rootClassName={section.rootClassName}
|
||||||
|
className={cn("grid grid-cols-1 gap-3 md:grid-cols-2", section.className)}
|
||||||
|
>
|
||||||
|
{section.fields.map((field) => {
|
||||||
|
if (field.hidden) return null;
|
||||||
|
|
||||||
|
if (field.kind === "custom") {
|
||||||
|
return (
|
||||||
|
<div key={field.name} className={field.className}>
|
||||||
|
{field.render({
|
||||||
|
control,
|
||||||
|
errors,
|
||||||
|
register,
|
||||||
|
error: getErrorMessage(errors, field.name),
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InputGroup<TValues>
|
||||||
|
key={field.name}
|
||||||
|
{...register(field.name, field.rules)}
|
||||||
|
name={field.name}
|
||||||
|
label={field.label}
|
||||||
|
type={field.type}
|
||||||
|
placeholder={field.placeholder}
|
||||||
|
required={field.required}
|
||||||
|
disabled={field.disabled}
|
||||||
|
autoComplete={field.autoComplete}
|
||||||
|
dataCy={field.dataCy}
|
||||||
|
icon={field.icon}
|
||||||
|
iconPosition={field.iconPosition}
|
||||||
|
className={field.className}
|
||||||
|
errors={errors}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
{footer ? <div className="md:col-span-2">{footer}</div> : null}
|
||||||
|
</ShowcaseSection>
|
||||||
|
);
|
||||||
|
|
||||||
|
const DynamicSection = memo(
|
||||||
|
DynamicSectionComponent,
|
||||||
|
) as typeof DynamicSectionComponent;
|
||||||
|
|
||||||
const DynamicFormBuilder = <TValues extends FieldValues>({
|
const DynamicFormBuilder = <TValues extends FieldValues>({
|
||||||
sections,
|
sections,
|
||||||
register,
|
register,
|
||||||
@@ -113,57 +179,19 @@ const DynamicFormBuilder = <TValues extends FieldValues>({
|
|||||||
{header}
|
{header}
|
||||||
<div className={sectionsClassName}>
|
<div className={sectionsClassName}>
|
||||||
{sections.map((section, sectionIndex) => (
|
{sections.map((section, sectionIndex) => (
|
||||||
<ShowcaseSection
|
<DynamicSection
|
||||||
key={sectionIndex}
|
key={section.id}
|
||||||
title={section.title}
|
section={section}
|
||||||
rootClassName={section.rootClassName}
|
register={register}
|
||||||
className={cn(
|
control={control}
|
||||||
"grid grid-cols-1 gap-3 md:grid-cols-2",
|
errors={errors}
|
||||||
section.className,
|
footer={
|
||||||
)}
|
footerPlacement === "last-section" &&
|
||||||
>
|
sectionIndex === sections.length - 1
|
||||||
{section.fields.map((field) => {
|
? footer
|
||||||
if (field.hidden) return null;
|
: undefined
|
||||||
|
}
|
||||||
if (field.kind === "custom") {
|
/>
|
||||||
return (
|
|
||||||
<div key={field.name} className={field.className}>
|
|
||||||
{field.render({
|
|
||||||
control,
|
|
||||||
errors,
|
|
||||||
register,
|
|
||||||
error: getErrorMessage(errors, field.name),
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<InputGroup<TValues>
|
|
||||||
key={field.name}
|
|
||||||
{...register(field.name, field.rules)}
|
|
||||||
name={field.name}
|
|
||||||
label={field.label}
|
|
||||||
type={field.type}
|
|
||||||
placeholder={field.placeholder}
|
|
||||||
required={field.required}
|
|
||||||
disabled={field.disabled}
|
|
||||||
autoComplete={field.autoComplete}
|
|
||||||
dataCy={field.dataCy}
|
|
||||||
icon={field.icon}
|
|
||||||
iconPosition={field.iconPosition}
|
|
||||||
className={field.className}
|
|
||||||
errors={errors}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
|
|
||||||
{footer &&
|
|
||||||
footerPlacement === "last-section" &&
|
|
||||||
sectionIndex === sections.length - 1 ? (
|
|
||||||
<div className="md:col-span-2">{footer}</div>
|
|
||||||
) : null}
|
|
||||||
</ShowcaseSection>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
{footerPlacement === "form" ? footer : null}
|
{footerPlacement === "form" ? footer : null}
|
||||||
|
|||||||
@@ -8,8 +8,11 @@ import FileImageUploadCard from "@/components/ui/FileImageUploadCard";
|
|||||||
import { FormErrorMessages } from "@/constants/Texts";
|
import { FormErrorMessages } from "@/constants/Texts";
|
||||||
import type { Dispatch, SetStateAction } from "react";
|
import type { Dispatch, SetStateAction } from "react";
|
||||||
import { Controller, type UseFormRegisterReturn } from "react-hook-form";
|
import { Controller, type UseFormRegisterReturn } from "react-hook-form";
|
||||||
|
import { v4 as uuidv4 } from "uuid";
|
||||||
import type { CreateAdminFormValues } from "./createAdminForm.types";
|
import type { CreateAdminFormValues } from "./createAdminForm.types";
|
||||||
|
|
||||||
|
const adminSectionId = uuidv4();
|
||||||
|
|
||||||
interface CreateAdminFormSectionsOptions {
|
interface CreateAdminFormSectionsOptions {
|
||||||
editMode?: boolean;
|
editMode?: boolean;
|
||||||
passwordFieldInputType: "password" | "text";
|
passwordFieldInputType: "password" | "text";
|
||||||
@@ -36,6 +39,7 @@ export const createAdminFormSections = ({
|
|||||||
clearSelectedProfileImage,
|
clearSelectedProfileImage,
|
||||||
}: CreateAdminFormSectionsOptions): DynamicFormSection<CreateAdminFormValues>[] => [
|
}: CreateAdminFormSectionsOptions): DynamicFormSection<CreateAdminFormValues>[] => [
|
||||||
{
|
{
|
||||||
|
id: adminSectionId,
|
||||||
title: "Input Fields",
|
title: "Input Fields",
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,11 +2,15 @@ import { TextAreaGroup } from "@/components/FormElements/InputGroup/text-area";
|
|||||||
import type { DynamicFormSection } from "@/components/forms/DynamicFormBuilder";
|
import type { DynamicFormSection } from "@/components/forms/DynamicFormBuilder";
|
||||||
import { FormErrorMessages } from "@/constants/Texts";
|
import { FormErrorMessages } from "@/constants/Texts";
|
||||||
import type { TCreateCompanyCategoryCredentials } from "@/lib/api";
|
import type { TCreateCompanyCategoryCredentials } from "@/lib/api";
|
||||||
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
|
||||||
|
const companyCategorySectionId = uuidv4();
|
||||||
|
|
||||||
export const createCompanyCategoryFormSections = (
|
export const createCompanyCategoryFormSections = (
|
||||||
editMode?: boolean,
|
editMode?: boolean,
|
||||||
): DynamicFormSection<TCreateCompanyCategoryCredentials>[] => [
|
): DynamicFormSection<TCreateCompanyCategoryCredentials>[] => [
|
||||||
{
|
{
|
||||||
|
id: companyCategorySectionId,
|
||||||
title: editMode ? "Edit Company Category" : "Create Company Category",
|
title: editMode ? "Edit Company Category" : "Create Company Category",
|
||||||
className: "gap-5",
|
className: "gap-5",
|
||||||
fields: [
|
fields: [
|
||||||
|
|||||||
@@ -9,8 +9,16 @@ import { FormErrorMessages } from "@/constants/Texts";
|
|||||||
import type { ICreateCompanyCredentials } from "@/lib/api";
|
import type { ICreateCompanyCredentials } from "@/lib/api";
|
||||||
import type { ILabelValue } from "@/types/shared";
|
import type { ILabelValue } from "@/types/shared";
|
||||||
import type { UseFormSetValue, UseFormWatch } from "react-hook-form";
|
import type { UseFormSetValue, UseFormWatch } from "react-hook-form";
|
||||||
|
import { v4 as uuidv4 } from "uuid";
|
||||||
import CompanyDocuments from "./documents/CompanyDocuments";
|
import CompanyDocuments from "./documents/CompanyDocuments";
|
||||||
|
|
||||||
|
const companyInformationSectionId = uuidv4();
|
||||||
|
const businessInformationSectionId = uuidv4();
|
||||||
|
const settingsSectionId = uuidv4();
|
||||||
|
const addressSectionId = uuidv4();
|
||||||
|
const tagsSectionId = uuidv4();
|
||||||
|
const documentsSectionId = uuidv4();
|
||||||
|
|
||||||
interface CompanyFormSectionsOptions {
|
interface CompanyFormSectionsOptions {
|
||||||
editMode?: boolean;
|
editMode?: boolean;
|
||||||
id?: string;
|
id?: string;
|
||||||
@@ -79,6 +87,7 @@ export const createCompanyFormSections = ({
|
|||||||
setValue,
|
setValue,
|
||||||
}: CompanyFormSectionsOptions): DynamicFormSection<ICreateCompanyCredentials>[] => [
|
}: CompanyFormSectionsOptions): DynamicFormSection<ICreateCompanyCredentials>[] => [
|
||||||
{
|
{
|
||||||
|
id: companyInformationSectionId,
|
||||||
title: "Company information",
|
title: "Company information",
|
||||||
rootClassName: "lg:col-span-2",
|
rootClassName: "lg:col-span-2",
|
||||||
className: "gap-x-5 gap-y-5 sm:grid-cols-2 md:grid-cols-2",
|
className: "gap-x-5 gap-y-5 sm:grid-cols-2 md:grid-cols-2",
|
||||||
@@ -235,6 +244,7 @@ export const createCompanyFormSections = ({
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: businessInformationSectionId,
|
||||||
title: "Business information",
|
title: "Business information",
|
||||||
className: "gap-x-5 gap-y-5 sm:grid-cols-2 md:grid-cols-2",
|
className: "gap-x-5 gap-y-5 sm:grid-cols-2 md:grid-cols-2",
|
||||||
fields: [
|
fields: [
|
||||||
@@ -276,6 +286,7 @@ export const createCompanyFormSections = ({
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: settingsSectionId,
|
||||||
title: "Settings",
|
title: "Settings",
|
||||||
className: "gap-x-5 gap-y-5 sm:grid-cols-2 md:grid-cols-2",
|
className: "gap-x-5 gap-y-5 sm:grid-cols-2 md:grid-cols-2",
|
||||||
fields: [
|
fields: [
|
||||||
@@ -323,6 +334,7 @@ export const createCompanyFormSections = ({
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: addressSectionId,
|
||||||
title: "Address",
|
title: "Address",
|
||||||
className: "gap-x-5 gap-y-5 sm:grid-cols-2 md:grid-cols-2",
|
className: "gap-x-5 gap-y-5 sm:grid-cols-2 md:grid-cols-2",
|
||||||
fields: [
|
fields: [
|
||||||
@@ -395,6 +407,7 @@ export const createCompanyFormSections = ({
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: tagsSectionId,
|
||||||
title: "Tags",
|
title: "Tags",
|
||||||
className: "gap-x-5 gap-y-5 sm:grid-cols-2 md:grid-cols-2",
|
className: "gap-x-5 gap-y-5 sm:grid-cols-2 md:grid-cols-2",
|
||||||
fields: [
|
fields: [
|
||||||
@@ -479,6 +492,7 @@ export const createCompanyFormSections = ({
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: documentsSectionId,
|
||||||
title: "Documents",
|
title: "Documents",
|
||||||
rootClassName: "lg:col-span-2",
|
rootClassName: "lg:col-span-2",
|
||||||
className: "md:grid-cols-1",
|
className: "md:grid-cols-1",
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ import type { ILabelValue } from "@/types/shared";
|
|||||||
import { getEnumAsArray } from "@/utils/shared";
|
import { getEnumAsArray } from "@/utils/shared";
|
||||||
import type { Dispatch, SetStateAction } from "react";
|
import type { Dispatch, SetStateAction } from "react";
|
||||||
import { Controller, type UseFormWatch } from "react-hook-form";
|
import { Controller, type UseFormWatch } from "react-hook-form";
|
||||||
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
|
||||||
|
const customerSectionId = uuidv4();
|
||||||
|
|
||||||
interface CustomerFormSectionsOptions {
|
interface CustomerFormSectionsOptions {
|
||||||
editMode?: boolean;
|
editMode?: boolean;
|
||||||
@@ -31,6 +34,7 @@ export const createCustomerFormSections = ({
|
|||||||
setShowPassword,
|
setShowPassword,
|
||||||
}: CustomerFormSectionsOptions): DynamicFormSection<CreateCustomerCredentials>[] => [
|
}: CustomerFormSectionsOptions): DynamicFormSection<CreateCustomerCredentials>[] => [
|
||||||
{
|
{
|
||||||
|
id: customerSectionId,
|
||||||
title: "Create Customer",
|
title: "Create Customer",
|
||||||
className: "gap-5",
|
className: "gap-5",
|
||||||
fields: [
|
fields: [
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ import { FormErrorMessages } from "@/constants/Texts";
|
|||||||
import type { TCreateNotificationCredentials } from "@/lib/api/types/NotificationHistory";
|
import type { TCreateNotificationCredentials } from "@/lib/api/types/NotificationHistory";
|
||||||
import type { ILabelValue } from "@/types/shared";
|
import type { ILabelValue } from "@/types/shared";
|
||||||
import type { ChangeEvent } from "react";
|
import type { ChangeEvent } from "react";
|
||||||
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
|
||||||
|
const notificationSectionId = uuidv4();
|
||||||
|
|
||||||
interface NotificationFormSectionsOptions {
|
interface NotificationFormSectionsOptions {
|
||||||
recipient: ILabelValue[] | null;
|
recipient: ILabelValue[] | null;
|
||||||
@@ -37,6 +40,7 @@ export const createNotificationFormSections = ({
|
|||||||
channels,
|
channels,
|
||||||
}: NotificationFormSectionsOptions): DynamicFormSection<TCreateNotificationCredentials>[] => [
|
}: NotificationFormSectionsOptions): DynamicFormSection<TCreateNotificationCredentials>[] => [
|
||||||
{
|
{
|
||||||
|
id: notificationSectionId,
|
||||||
title: "Create Notification",
|
title: "Create Notification",
|
||||||
className: "gap-5",
|
className: "gap-5",
|
||||||
fields: [
|
fields: [
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ export function Logo() {
|
|||||||
return (
|
return (
|
||||||
<Image
|
<Image
|
||||||
src="/images/main-logo.svg"
|
src="/images/main-logo.svg"
|
||||||
width={174}
|
width={1217}
|
||||||
height={32}
|
height={192}
|
||||||
|
className="h-auto w-[174px]"
|
||||||
alt="Opp lens logo"
|
alt="Opp lens logo"
|
||||||
role="presentation"
|
role="presentation"
|
||||||
quality={100}
|
quality={100}
|
||||||
|
|||||||
Reference in New Issue
Block a user