This commit is contained in:
@@ -54,7 +54,7 @@ const CreateCompany = ({ defaultValues, editMode, id }: IProps) => {
|
||||
{editMode ? "Edit company" : "New company"}
|
||||
</h1>
|
||||
<p className="text-sm text-dark-5 dark:text-dark-6">
|
||||
Profile, business details, address, tags, and supporting documents.
|
||||
Profile, business details, address, tags, documents, and links.
|
||||
</p>
|
||||
</header>
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import type { ILabelValue } from "@/types/shared";
|
||||
import type { UseFormSetValue, UseFormWatch } from "react-hook-form";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import CompanyDocuments from "./documents/CompanyDocuments";
|
||||
import CompanyLinks from "./links/CompanyLinks";
|
||||
|
||||
const companyInformationSectionId = uuidv4();
|
||||
const businessInformationSectionId = uuidv4();
|
||||
@@ -18,6 +19,7 @@ const settingsSectionId = uuidv4();
|
||||
const addressSectionId = uuidv4();
|
||||
const tagsSectionId = uuidv4();
|
||||
const documentsSectionId = uuidv4();
|
||||
const linksSectionId = uuidv4();
|
||||
|
||||
interface CompanyFormSectionsOptions {
|
||||
editMode?: boolean;
|
||||
@@ -169,9 +171,8 @@ const getTimezoneOffsetMinutes = (timezone: string, date = new Date()) => {
|
||||
timeZoneName: "shortOffset",
|
||||
});
|
||||
const offset =
|
||||
formatter
|
||||
.formatToParts(date)
|
||||
.find((part) => part.type === "timeZoneName")?.value ?? "";
|
||||
formatter.formatToParts(date).find((part) => part.type === "timeZoneName")
|
||||
?.value ?? "";
|
||||
const label = offset.replace(/^GMT/, "") || "+0";
|
||||
const match = label.match(/^([+-])(\d{1,2})(?::(\d{2}))?$/);
|
||||
|
||||
@@ -667,4 +668,19 @@ export const createCompanyFormSections = ({
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: linksSectionId,
|
||||
title: "Links",
|
||||
rootClassName: "lg:col-span-2",
|
||||
className: "md:grid-cols-1",
|
||||
fields: [
|
||||
{
|
||||
kind: "custom",
|
||||
name: "links",
|
||||
render: ({ control, errors, register }) => (
|
||||
<CompanyLinks control={control} errors={errors} register={register} />
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
"use client";
|
||||
|
||||
import InputGroup from "@/components/FormElements/InputGroup";
|
||||
import type { ICreateCompanyCredentials } from "@/lib/api";
|
||||
import type { Control, FieldErrors, UseFormRegister } from "react-hook-form";
|
||||
import { get, useFieldArray } from "react-hook-form";
|
||||
|
||||
const MAX_COMPANY_LINKS = 20;
|
||||
|
||||
interface CompanyLinksProps {
|
||||
control: Control<ICreateCompanyCredentials>;
|
||||
register: UseFormRegister<ICreateCompanyCredentials>;
|
||||
errors: FieldErrors<ICreateCompanyCredentials>;
|
||||
}
|
||||
|
||||
const validateHttpsUrl = (value?: string) => {
|
||||
const trimmedValue = value?.trim() ?? "";
|
||||
if (!trimmedValue) return "URL is required";
|
||||
|
||||
try {
|
||||
const url = new URL(trimmedValue);
|
||||
return url.protocol === "https:" || "Enter a valid HTTPS URL";
|
||||
} catch {
|
||||
return "Enter a valid HTTPS URL";
|
||||
}
|
||||
};
|
||||
|
||||
const CompanyLinks = ({ control, register, errors }: CompanyLinksProps) => {
|
||||
const { fields, append, remove } = useFieldArray({
|
||||
control,
|
||||
name: "links",
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{fields.length === 0 ? (
|
||||
<p className="text-sm text-dark-5 dark:text-dark-6">
|
||||
No external links added yet.
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
{fields.map((field, index) => {
|
||||
const urlName = `links.${index}.url` as const;
|
||||
const titleName = `links.${index}.title` as const;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={field.id}
|
||||
className="grid gap-4 rounded-xl border border-stroke/70 bg-gray-1/50 p-4 dark:border-dark-3 dark:bg-dark-2/50 sm:grid-cols-2"
|
||||
>
|
||||
<InputGroup<ICreateCompanyCredentials>
|
||||
{...register(titleName, {
|
||||
setValueAs: (value: string) => value.trim(),
|
||||
maxLength: {
|
||||
value: 200,
|
||||
message: "Title must be 200 characters or fewer",
|
||||
},
|
||||
})}
|
||||
name={titleName}
|
||||
label="Title (optional)"
|
||||
type="text"
|
||||
placeholder="LinkedIn"
|
||||
errors={errors}
|
||||
/>
|
||||
<InputGroup<ICreateCompanyCredentials>
|
||||
{...register(urlName, {
|
||||
validate: validateHttpsUrl,
|
||||
setValueAs: (value: string) => value.trim(),
|
||||
})}
|
||||
name={urlName}
|
||||
label="URL"
|
||||
type="url"
|
||||
placeholder="https://example.com/profile"
|
||||
required
|
||||
errors={errors}
|
||||
/>
|
||||
<div className="flex items-center justify-between sm:col-span-2">
|
||||
<span className="text-xs text-error">
|
||||
{get(errors, `links.${index}.root`)?.message ?? ""}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => remove(index)}
|
||||
className="rounded-lg border border-error/30 px-3 py-1.5 text-sm font-medium text-error transition hover:bg-error/10"
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<p className="text-xs text-dark-5 dark:text-dark-6">
|
||||
{fields.length}/{MAX_COMPANY_LINKS} links
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
disabled={fields.length >= MAX_COMPANY_LINKS}
|
||||
onClick={() => append({ url: "", title: "" })}
|
||||
className="rounded-xl bg-primary px-4 py-2 text-sm font-semibold text-white transition hover:bg-primary/90 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
+ Add link
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CompanyLinks;
|
||||
@@ -30,6 +30,7 @@ const useCreateCompanyPresenter = ({ defaultValues, editMode, id }: IProps) => {
|
||||
mode: "onChange",
|
||||
defaultValues: {
|
||||
...defaultValues,
|
||||
links: defaultValues?.links ?? [],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -72,6 +73,10 @@ const useCreateCompanyPresenter = ({ defaultValues, editMode, id }: IProps) => {
|
||||
employee_count: optionalNumber(data.employee_count),
|
||||
annual_revenue: optionalNumber(data.annual_revenue),
|
||||
document_file_ids: documentFileIds ?? [],
|
||||
links: (data.links ?? []).map((link) => ({
|
||||
url: link.url.trim(),
|
||||
...(link.title?.trim() ? { title: link.title.trim() } : {}),
|
||||
})),
|
||||
tags: {
|
||||
keywords: data?.tags?.keywords?.length ? data?.tags?.keywords : [],
|
||||
categories: data?.tags?.categories?.length ? data?.tags?.categories : [],
|
||||
|
||||
Reference in New Issue
Block a user