fix(companies): honest status errors and per-role suspension visibility

A suspended customer trying to create a contract was told their account
was "awaiting approval" - the guards collapsed every non-active status
into the pending message. Contract and booking creation now go through a
shared assertCompanyActiveFor that names the real company status, and
the per-role guard names the role's actual state (suspended,
blacklisted, rejected - with the reviewer note) so a customer suspended
for one operation knows the others still work.

Portal: the operation dropdown gets a Suspended badge, the block modal a
suspended branch quoting the staff message, and the wizard a
suspended/blacklisted gate instead of falling through to a submit error.

EDRFREIGHT-234
This commit is contained in:
Nathnael
2026-07-21 09:09:34 +00:00
parent c8f932f5d5
commit 4afffca3d2
5 changed files with 131 additions and 25 deletions

View File

@@ -124,6 +124,30 @@ export default function NewContractPage({
);
}
// A suspended/blacklisted account must be told its real status — falling
// through to the wizard here only to fail at submit read as "pending" before.
if (
!auth.isPending &&
(auth.companyStatus === "suspended" || auth.companyStatus === "blacklisted")
) {
return (
<GateNotice
title={
auth.companyStatus === "suspended"
? "Account Suspended"
: "Account Blacklisted"
}
body={
auth.companyStatus === "suspended"
? "Your company account has been suspended by EDR staff, so new contracts are disabled. Please contact EDR support for details."
: "Your company account has been blacklisted, so new contracts are disabled. Please contact EDR support."
}
actionLabel="Back to Contracts"
onAction={() => navigate("/contracts")}
/>
);
}
// Profile changes pending review lock out new contract creation too.
if (!auth.isPending && auth.isUnderReview) {
return (
@@ -349,13 +373,19 @@ export default function NewContractPage({
// badges. Intercity rides any customer profile, so always "approved".
const operationStatus = useMemo(
() =>
(op: OperationType): "approved" | "pending" | "rejected" | "missing" => {
(
op: OperationType,
): "approved" | "pending" | "rejected" | "suspended" | "missing" => {
if (op === "intercity") return "approved";
const target = operationToProfileType(op, profileTypes);
const status = profileStatusByType.get(target);
if (!status) return "missing";
if (status === "active") return "approved";
if (status === "rejected") return "rejected";
// Suspension is per-role: the customer keeps working under their other
// roles, so this operation must say "suspended", not "pending".
if (status === "suspended" || status === "blacklisted")
return "suspended";
return "pending";
},
[profileStatusByType, profileTypes],
@@ -1171,6 +1201,8 @@ export default function NewContractPage({
? profileByType.get(pendingApprovalProfile)
: undefined;
const isRejected = target?.status === "rejected";
const isSuspended =
target?.status === "suspended" || target?.status === "blacklisted";
const label = pendingApprovalProfile
? (PROFILE_TYPE_LABELS[pendingApprovalProfile] ??
pendingApprovalProfile)
@@ -1179,12 +1211,42 @@ export default function NewContractPage({
<Modal
opened={pendingApprovalProfile !== null}
onClose={() => setPendingApprovalProfile(null)}
title={isRejected ? "Profile not approved" : "Awaiting approval"}
title={
isRejected
? "Profile not approved"
: isSuspended
? "Role suspended"
: "Awaiting approval"
}
centered
radius="lg"
>
<Stack gap="md">
{isRejected ? (
{isSuspended ? (
<>
<Text size="sm" c="dimmed">
Your {label} role has been suspended by EDR staff, so you
can&apos;t start a contract under it. Your other roles are
unaffected. Contact EDR support to resolve this.
</Text>
{target?.reviewNote && (
<Alert color="orange" variant="light" radius="md">
<Text size="sm">
<strong>Message from EDR staff:</strong>{" "}
{target.reviewNote}
</Text>
</Alert>
)}
<Group justify="flex-end" gap="sm">
<Button
color="edr-green"
onClick={() => setPendingApprovalProfile(null)}
>
OK
</Button>
</Group>
</>
) : isRejected ? (
<>
<Text size="sm" c="dimmed">
Your {label} profile was not approved. Fix the issue below

View File

@@ -45,7 +45,7 @@ export function Step1ContractType({
/** Approval state of the profile each operation maps to (for the badges). */
operationStatus?: (
op: OperationType,
) => "approved" | "pending" | "rejected" | "missing";
) => "approved" | "pending" | "rejected" | "suspended" | "missing";
}) {
const contractType = form.watch("contractType");
@@ -229,6 +229,11 @@ export function Step1ContractType({
Rejected
</Badge>
)}
{status === "suspended" && (
<Badge size="xs" color="orange" variant="light" radius="sm">
Suspended
</Badge>
)}
{status === "missing" && (
<Badge size="xs" color="gray" variant="light" radius="sm">
Add license