mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
@@ -491,6 +491,50 @@ export default function CompanyProfileForm({
|
||||
(identity?.poa.verified ?? false) ||
|
||||
(identity ? !identity.faydaRequired && poaTyped : false);
|
||||
|
||||
/**
|
||||
* Drop an optional representative the company no longer wants.
|
||||
*
|
||||
* Verifying a PoA is one click on a step that calls itself optional, and it
|
||||
* is not reversible from the form: the verification owns the fields (so
|
||||
* blanking them is refused), and its mere existence makes the DARS paper due
|
||||
* — which then blocks the submit AND clamps the resume back to this step. The
|
||||
* settings page has the same escape hatch, but `/settings` is off-limits
|
||||
* until onboarding finishes, so without this the customer is stuck.
|
||||
*
|
||||
* Not offered to a freight forwarder: the API refuses (they must have one).
|
||||
*/
|
||||
const [poaRemovePending, setPoaRemovePending] = useState(false);
|
||||
const removePoa = async () => {
|
||||
setSaveError(null);
|
||||
setPoaRemovePending(true);
|
||||
try {
|
||||
await verifaydaService.removePoa();
|
||||
for (const key of [
|
||||
"poaName",
|
||||
"poaEmail",
|
||||
"poaPhone",
|
||||
"poaAddress",
|
||||
"poaLocation",
|
||||
] as const) {
|
||||
setValue(key, "", { shouldDirty: false });
|
||||
}
|
||||
// The paper is deleted server-side with the identity; a copy still
|
||||
// sitting in the picker would be re-uploaded on the documents step.
|
||||
setDocumentFiles({ ...documentFiles, [POA_DELEGATION_FILE_KEY]: null });
|
||||
onIdentityChange?.();
|
||||
} catch (err) {
|
||||
setSaveError(
|
||||
(err as { response?: { data?: { message?: string } } })?.response?.data
|
||||
?.message ??
|
||||
(err instanceof Error
|
||||
? err.message
|
||||
: "Could not remove the Power of Attorney"),
|
||||
);
|
||||
} finally {
|
||||
setPoaRemovePending(false);
|
||||
}
|
||||
};
|
||||
|
||||
// While linked, mirror the source values into the (disabled) target fields so
|
||||
// the copy stays current even if the user goes back and edits the source.
|
||||
useEffect(() => {
|
||||
@@ -699,10 +743,13 @@ export default function CompanyProfileForm({
|
||||
};
|
||||
// The GM's own verification never falls back to the signed-in account — that
|
||||
// account is the person onboarding, not necessarily the manager — so a GM
|
||||
// verified with no email claim has nowhere else for one to come from.
|
||||
// verified with no email claim has nowhere else for one to come from. The
|
||||
// name claim is optional too, and `REQUIRED_COMPANY_INFO` demands it, so it
|
||||
// gets the same treatment rather than dead-ending the submit.
|
||||
// "Same as owner" is exempt: the API copies the owner's (account-backed)
|
||||
// contact details across, so there is no gap and no input.
|
||||
const gmGaps = {
|
||||
name: !gmSameAsOwner && gmVerified && !identity?.gm.name?.trim(),
|
||||
email: !gmSameAsOwner && gmVerified && !identity?.gm.email?.trim(),
|
||||
phone: !gmSameAsOwner && gmVerified && !identity?.gm.phone?.trim(),
|
||||
};
|
||||
@@ -711,7 +758,9 @@ export default function CompanyProfileForm({
|
||||
if (step === "personnel") {
|
||||
// The manager's email is offered but not demanded: the API dropped it from
|
||||
// `REQUIRED_COMPANY_INFO` once the notify resolver stopped depending on it.
|
||||
// The phone is still required there, so it is still required here.
|
||||
// The name and phone are still required there, so they are still required
|
||||
// here.
|
||||
if (gmGaps.name) requiredKeys.push("generalManagerName");
|
||||
if (gmGaps.phone) requiredKeys.push("generalManagerPhone");
|
||||
} else if (step === "poa" && delegationRequired) {
|
||||
// Only once a PoA is required or provided: an untouched optional PoA is
|
||||
@@ -976,6 +1025,8 @@ export default function CompanyProfileForm({
|
||||
identity={identity}
|
||||
requirePoa={requirePoa}
|
||||
gaps={poaGaps}
|
||||
onRemovePoa={removePoa}
|
||||
removePending={poaRemovePending}
|
||||
delegationRequired={delegationRequired}
|
||||
poaDocumentSetting={poaDocumentSetting}
|
||||
documentFiles={documentFiles}
|
||||
|
||||
@@ -23,7 +23,7 @@ export interface PersonnelStepProps {
|
||||
* supply, and are therefore typed here. Computed by CompanyProfileForm, which
|
||||
* requires exactly these in the schema.
|
||||
*/
|
||||
gaps: { email: boolean; phone: boolean };
|
||||
gaps: { name: boolean; email: boolean; phone: boolean };
|
||||
}
|
||||
|
||||
export default function PersonnelStep({
|
||||
@@ -81,12 +81,20 @@ export default function PersonnelStep({
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Fayda's email and phone claims are optional, and the manager's own
|
||||
verification has no account to fall back on the way the owner's does
|
||||
— the person onboarding is not necessarily the manager. Whatever the
|
||||
verification left empty is typed here, and required: without it the
|
||||
submit fails on "Add your general manager email" with no field
|
||||
anywhere to satisfy it. */}
|
||||
{/* Fayda's name, email and phone claims are all optional, and the
|
||||
manager's own verification has no account to fall back on the way the
|
||||
owner's does — the person onboarding is not necessarily the manager.
|
||||
Whatever the verification left empty is typed here, and required:
|
||||
without it the submit fails on "Add your general manager name" with no
|
||||
field anywhere to satisfy it. */}
|
||||
{gaps.name && (
|
||||
<TextInput
|
||||
label="Name"
|
||||
placeholder="Abebe Bikila"
|
||||
error={errors.generalManagerName?.message}
|
||||
{...register("generalManagerName")}
|
||||
/>
|
||||
)}
|
||||
{(gaps.email || gaps.phone) && (
|
||||
<SimpleGrid cols={gaps.email && gaps.phone ? 2 : 1} spacing="md">
|
||||
{gaps.email && (
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Divider, SimpleGrid, Text, TextInput } from "@mantine/core";
|
||||
import { Button, Divider, Group, SimpleGrid, Text, TextInput } from "@mantine/core";
|
||||
import { Trash2 } from "lucide-react";
|
||||
import type { UseFormReturn } from "react-hook-form";
|
||||
|
||||
import { SmartFileInput } from "@edr/ui-common";
|
||||
@@ -21,6 +22,9 @@ export interface PoaStepProps {
|
||||
* the customer is actually asked to fill.
|
||||
*/
|
||||
gaps: { name: boolean; email: boolean; phone: boolean; address: boolean };
|
||||
/** Drop a verified representative the company decided against. */
|
||||
onRemovePoa: () => void;
|
||||
removePending: boolean;
|
||||
/** The DARS delegation paper is owed (a PoA exists, or the company forwards). */
|
||||
delegationRequired: boolean;
|
||||
/** Single-field upload setting carrying just the delegation letter. */
|
||||
@@ -36,6 +40,8 @@ export default function PoaStep({
|
||||
identity,
|
||||
requirePoa,
|
||||
gaps,
|
||||
onRemovePoa,
|
||||
removePending,
|
||||
delegationRequired,
|
||||
poaDocumentSetting,
|
||||
documentFiles,
|
||||
@@ -81,6 +87,26 @@ export default function PoaStep({
|
||||
required={requirePoa}
|
||||
/>
|
||||
)}
|
||||
{/* A verification cannot be undone by clearing the form — it owns those
|
||||
fields — and its mere existence makes the delegation paper due, which
|
||||
then blocks the submit. So an optional representative needs a way
|
||||
back out, here rather than only in settings (unreachable until
|
||||
onboarding finishes). */}
|
||||
{identity?.poa.verified && !requirePoa && (
|
||||
<Group justify="flex-end">
|
||||
<Button
|
||||
type="button"
|
||||
variant="subtle"
|
||||
color="red"
|
||||
size="xs"
|
||||
loading={removePending}
|
||||
leftSection={<Trash2 size={14} />}
|
||||
onClick={onRemovePoa}
|
||||
>
|
||||
Remove this representative
|
||||
</Button>
|
||||
</Group>
|
||||
)}
|
||||
{/* Whatever the Fayda claim did carry is shown on the panel above and
|
||||
is never typed here — the verification owns it. */}
|
||||
{gaps.name && (
|
||||
|
||||
@@ -148,15 +148,17 @@ export default function TabGeneralManager({ profile, mode = "edit", onContinue }
|
||||
// posting empty strings over a proven identity.
|
||||
const typedFieldsInUse = !gmSameAsOwner && !gm?.verified && !faydaRequired;
|
||||
|
||||
// Except for what the verification never supplied. Fayda's email and phone
|
||||
// claims are optional, and a manager verified without them has no account to
|
||||
// fall back on the way the owner does — so those stay typed, here as well as
|
||||
// in onboarding, or a wrong address could never be corrected.
|
||||
// Except for what the verification never supplied. Fayda's name, email and
|
||||
// phone claims are all optional, and a manager verified without them has no
|
||||
// account to fall back on the way the owner does — so those stay typed, here
|
||||
// as well as in onboarding, or a wrong value could never be corrected.
|
||||
const gmGaps = {
|
||||
name: !gmSameAsOwner && Boolean(gm?.verified) && !gm?.name?.trim(),
|
||||
email: !gmSameAsOwner && Boolean(gm?.verified) && !gm?.email?.trim(),
|
||||
phone: !gmSameAsOwner && Boolean(gm?.verified) && !gm?.phone?.trim(),
|
||||
};
|
||||
const savable = typedFieldsInUse || gmGaps.email || gmGaps.phone;
|
||||
const savable =
|
||||
typedFieldsInUse || gmGaps.name || gmGaps.email || gmGaps.phone;
|
||||
|
||||
return (
|
||||
<Card padding="lg">
|
||||
@@ -203,6 +205,14 @@ export default function TabGeneralManager({ profile, mode = "edit", onContinue }
|
||||
|
||||
{/* Whatever the verification did not supply is typed instead — the
|
||||
API keeps exactly those keys writable. */}
|
||||
{gmGaps.name && (
|
||||
<TextInput
|
||||
label="Full Name"
|
||||
placeholder="Abebe Bikila"
|
||||
error={errors.generalManagerName?.message}
|
||||
{...register("generalManagerName")}
|
||||
/>
|
||||
)}
|
||||
{(gmGaps.email || gmGaps.phone) && (
|
||||
<Grid>
|
||||
{gmGaps.email && (
|
||||
|
||||
Reference in New Issue
Block a user