Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | 17x 144x 13x 13x 13x 17x 17x 142x 17x 44x 44x 44x 2x 44x 44x 44x | import { useGetCurrentMerchantId } from "@hooks/common";
import { customInstance } from "@services/api";
import { useQuery } from "react-query";
import { QKEY_MERCHANT_OWNER_REASSIGNMENT } from "@constants/queryKeys";
import { GENERAL_STALE_TIME } from "@features/Merchants/MerchantSidePanel/constants";
/**
* In-flight Primary Account Holder reassignment for the merchant Team tab.
*
* Reads `GET /merchants/{merchantId}/owner`, whose owner object carries a
* `pendingChangeRequest` (same shape the acquirer side already reads off the
* merchant-preview endpoint). The field is omitted entirely when no
* reassignment is in flight. The account-domain `/accounts/{id}/members`
* endpoint that feeds the team list does NOT carry this, so it lives here as a
* separate query.
*/
export type PahReassignment = {
state: "pending_reassignment" | "ready_for_approval";
inviteId: number;
newOwnerEmail: string;
newOwnerUserAccID: number;
inviteStatus: string;
personStatus: string;
};
/**
* Reassignment-related fields carried on a merchant `owner` object. Use this to
* read them with keyof-checked access instead of untyped `any`.
*
* PAH006 SS-4 lifecycle note: `pendingChangeRequest` is present only while the
* invite is unaccepted and is cleared at invite-accept (before the new PAH logs
* in to do identity). Do NOT *require* it to enable identity-only re-onboarding
* (`flag && pendingChangeRequest`) — that would disable the flow in exactly the
* post-accept identity-under-review window. See
* `resolveIsPahReassignmentIdentityOnly` for the safe use (suppress only).
*/
export interface MerchantOwnerReassignment {
isOwnerReassignmentIdentityOnly?: boolean;
pendingChangeRequest?: PahReassignment;
statusName?: string;
}
/**
* GB-21477: resolve whether the logged-in user should enter identity-only
* re-onboarding.
*
* The BE flag `isOwnerReassignmentIdentityOnly` is merchant-scoped — it is true
* whenever an owner identity task is open for the pending new PAH
* (`account_read_handler` derives it from the merchant's identity-task status,
* not the session). So every session viewing the merchant — including the
* seated, already-approved OLD PAH while the invite is unaccepted — receives it
* and was wrongly forced into identity-only onboarding.
*
* Identity-only must apply only to the INCOMING new PAH. While a reassignment is
* pending to a DIFFERENT user than the one logged in, suppress it. Once the new
* PAH accepts, `pendingChangeRequest` is cleared, so they correctly enter the
* flow — and the suppression cannot misfire (no target email to compare).
*/
export const resolveIsPahReassignmentIdentityOnly = (
owner: MerchantOwnerReassignment | undefined,
currentUserEmail: string | undefined,
): boolean => {
if (!owner?.isOwnerReassignmentIdentityOnly) return false;
const targetEmail = owner.pendingChangeRequest?.newOwnerEmail;
const isPendingToSomeoneElse = Boolean(
targetEmail && targetEmail !== currentUserEmail,
);
return !isPendingToSomeoneElse;
};
// owner.statusName values meaning the PAH has finished submitting identity
// (both ID proof and selfie uploaded). The BE advances pending →
// ready_for_verification on submission, then → approved once underwriting
// verifies. See usePAHUploader / useGetMerchants for the same status reads.
const OWNER_IDENTITY_SUBMITTED_STATUSES = ["ready_for_verification", "approved"];
/**
* PAH006 SS-4 follow-up: a reassigned identity-only PAH gets full portal access
* as soon as they finish submitting identity — they should not be held on a
* review screen waiting for underwriting approval. This reads the BE owner
* status that flips on that submission, so the wizard and the portal gate stay
* in sync off a single signal.
*/
export const isOwnerIdentitySubmitted = (
owner?: MerchantOwnerReassignment,
): boolean =>
OWNER_IDENTITY_SUBMITTED_STATUSES.includes(owner?.statusName ?? "");
export const usePahReassignmentStatus = (enabled = true) => {
const { merchantId } = useGetCurrentMerchantId();
const queryEnabled = enabled && Boolean(merchantId);
const { data, isLoading } = useQuery(
[QKEY_MERCHANT_OWNER_REASSIGNMENT, merchantId],
() =>
customInstance({
url: `/merchants/${merchantId}/owner`,
method: "GET",
}),
{
staleTime: GENERAL_STALE_TIME,
refetchOnWindowFocus: false,
refetchOnMount: false,
enabled: queryEnabled,
},
);
const reassignment: PahReassignment | undefined = data?.pendingChangeRequest;
const isPendingReassignment = Boolean(reassignment);
return { reassignment, isPendingReassignment, isLoading };
};
|