mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-27 00:52:50 +00:00
feat: add verifcation on the document step
This commit is contained in:
@@ -70,6 +70,8 @@ interface RoleLicenseStepProps {
|
||||
/** Newly-selected files per profile id (not yet uploaded). */
|
||||
value: Record<string, File[]>;
|
||||
onChange: (value: Record<string, File[]>) => void;
|
||||
/** "Business license is required" style error, keyed by profile id. */
|
||||
errors?: Record<string, string>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,6 +84,7 @@ export default function RoleLicenseStep({
|
||||
profiles,
|
||||
value,
|
||||
onChange,
|
||||
errors,
|
||||
}: RoleLicenseStepProps) {
|
||||
const setFiles = (profileId: string, files: File[]) => {
|
||||
onChange({ ...value, [profileId]: files });
|
||||
@@ -123,6 +126,11 @@ export default function RoleLicenseStep({
|
||||
file={buildLicenseSetting(profile.id, label)}
|
||||
value={{ [LICENSE_FILE_KEY]: selected }}
|
||||
uploadedKeys={hasExisting ? [LICENSE_FILE_KEY] : undefined}
|
||||
errors={
|
||||
errors?.[profile.id]
|
||||
? { [LICENSE_FILE_KEY]: errors[profile.id] }
|
||||
: undefined
|
||||
}
|
||||
onChange={(v) => {
|
||||
const next = v[LICENSE_FILE_KEY];
|
||||
const files = Array.isArray(next) ? next : next ? [next] : [];
|
||||
|
||||
@@ -21,6 +21,7 @@ import type { ProfileResponse, UpdateProfilePayload } from "@/types/profile";
|
||||
import type { CompanyRegistrationData } from "@edr/types";
|
||||
import { ControlledPhoneField, toEthiopianE164 } from "@/components/PhoneField";
|
||||
import { SmartFileInput } from "@edr/ui-common";
|
||||
import { getMinFiles } from "@/types/fileUploadSettings";
|
||||
import { api } from "@/services/api";
|
||||
import RoleLicenseStep, {
|
||||
type RoleLicenseProfile,
|
||||
@@ -358,6 +359,72 @@ export default function CompanyProfileForm({
|
||||
|
||||
const hasDocuments = Boolean(uploadSetting?.fields?.length);
|
||||
|
||||
// Hard verification for the documents step: required company-level
|
||||
// documents and a business license per operational profile must both be
|
||||
// present before the user can continue.
|
||||
const [documentFieldErrors, setDocumentFieldErrors] = useState<
|
||||
Record<string, string>
|
||||
>({});
|
||||
const [licenseFieldErrors, setLicenseFieldErrors] = useState<
|
||||
Record<string, string>
|
||||
>({});
|
||||
|
||||
const validateRequiredDocuments = (): Record<string, string> => {
|
||||
const errs: Record<string, string> = {};
|
||||
for (const field of uploadSetting?.fields ?? []) {
|
||||
const min = getMinFiles(field);
|
||||
if (min <= 0) continue;
|
||||
if ((uploadedDocumentKeys ?? []).includes(field.fileKey)) continue;
|
||||
const v = documentFiles[field.fileKey];
|
||||
const count = Array.isArray(v) ? v.length : v ? 1 : 0;
|
||||
if (count < min) {
|
||||
errs[field.fileKey] = `${field.fileLabel} is required`;
|
||||
}
|
||||
}
|
||||
return errs;
|
||||
};
|
||||
|
||||
// Every role needs at least one license file (existing or newly selected).
|
||||
const validateLicenses = (): Record<string, string> => {
|
||||
const errs: Record<string, string> = {};
|
||||
for (const p of roleProfiles ?? []) {
|
||||
const hasNew = (licenseFiles?.[p.id]?.length ?? 0) > 0;
|
||||
const hasExisting = p.existingFiles.length > 0;
|
||||
if (!hasNew && !hasExisting) {
|
||||
errs[p.id] = "Business license is required";
|
||||
}
|
||||
}
|
||||
return errs;
|
||||
};
|
||||
|
||||
const handleDocumentFilesChange = (
|
||||
next: Record<string, File | File[] | null>,
|
||||
) => {
|
||||
setDocumentFiles(next);
|
||||
setDocumentFieldErrors((prev) => {
|
||||
if (Object.keys(prev).length === 0) return prev;
|
||||
const updated = { ...prev };
|
||||
for (const key of Object.keys(updated)) {
|
||||
const v = next[key];
|
||||
const hasValue = Array.isArray(v) ? v.length > 0 : v != null;
|
||||
if (hasValue) delete updated[key];
|
||||
}
|
||||
return updated;
|
||||
});
|
||||
};
|
||||
|
||||
const handleLicenseFilesChange = (next: Record<string, File[]>) => {
|
||||
onLicenseChange?.(next);
|
||||
setLicenseFieldErrors((prev) => {
|
||||
if (Object.keys(prev).length === 0) return prev;
|
||||
const updated = { ...prev };
|
||||
for (const id of Object.keys(updated)) {
|
||||
if ((next[id]?.length ?? 0) > 0) delete updated[id];
|
||||
}
|
||||
return updated;
|
||||
});
|
||||
};
|
||||
|
||||
// The registration/license details come straight from the eTrade lookup and
|
||||
// are not user-editable — shown as a read-only confirmation once a TIN lookup
|
||||
// (or rehydration) has filled them in. The address fields below are separate:
|
||||
@@ -402,18 +469,21 @@ export default function CompanyProfileForm({
|
||||
}
|
||||
};
|
||||
|
||||
// Every role needs at least one license file (existing or newly selected).
|
||||
const licenseComplete = (roleProfiles ?? []).every(
|
||||
(p) =>
|
||||
(licenseFiles?.[p.id]?.length ?? 0) > 0 || p.existingFiles.length > 0,
|
||||
);
|
||||
|
||||
const nextStep = async () => {
|
||||
userNavigatedRef.current = true;
|
||||
// The documents step auto-uploads whatever the user selected as they
|
||||
// continue (partial uploads are allowed — required-doc completeness is
|
||||
// re-checked on resume). A failed upload holds them on the step.
|
||||
// The documents step hard-blocks on required company documents and a
|
||||
// business license per operational profile before it auto-uploads and
|
||||
// submits — no partial-completion path forward.
|
||||
if (step === "documents") {
|
||||
const docErrors = validateRequiredDocuments();
|
||||
const licenseErrors = validateLicenses();
|
||||
if (Object.keys(docErrors).length > 0 || Object.keys(licenseErrors).length > 0) {
|
||||
setDocumentFieldErrors(docErrors);
|
||||
setLicenseFieldErrors(licenseErrors);
|
||||
setSaveError("Please upload all required documents before continuing.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (onUploadDocuments) {
|
||||
setSaving(true);
|
||||
try {
|
||||
@@ -427,12 +497,6 @@ export default function CompanyProfileForm({
|
||||
}
|
||||
}
|
||||
|
||||
if (!licenseComplete) {
|
||||
setSaveError(
|
||||
"Please upload a business license for each of your operational profiles.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
setSaveError(null);
|
||||
handleSubmit((data) => onSubmit(buildPayload(data, user)))();
|
||||
return;
|
||||
@@ -749,15 +813,17 @@ export default function CompanyProfileForm({
|
||||
file={uploadSetting}
|
||||
value={documentFiles}
|
||||
uploadedKeys={uploadedDocumentKeys}
|
||||
errors={documentFieldErrors}
|
||||
containerClassName="lg:grid grid-cols-2 items-stretch"
|
||||
onChange={setDocumentFiles}
|
||||
onChange={handleDocumentFilesChange}
|
||||
/>
|
||||
)}
|
||||
|
||||
<RoleLicenseStep
|
||||
profiles={roleProfiles ?? []}
|
||||
value={licenseFiles ?? {}}
|
||||
onChange={onLicenseChange ?? (() => { })}
|
||||
onChange={handleLicenseFilesChange}
|
||||
errors={licenseFieldErrors}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user