Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions components/Card/NewCardDetails/CardActionsRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,9 @@ interface CardActionsRowProps {
onFreezeToggle: () => void;
onManagePress: () => void;
/**
* Whether funds can move onto the card: not frozen, and KYC not paused or
* offboarded. Derived by the parent (`canAddFundsToCard`) rather than here, so
* this row and the freeze state it renders come from one reading of the card.
* Whether funds can move onto the card: it is not frozen. Derived by the
* parent (`canAddFundsToCard`) rather than here, so this row and the freeze
* state it renders come from one reading of the card.
*/
canAddFunds: boolean;
}
Expand Down
4 changes: 0 additions & 4 deletions components/Card/NewCardDetails/CardDetailsPane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ import {
useCardSpendRegistration,
} from '@/hooks/useCardSpendRegistration';
import { useCardStatus } from '@/hooks/useCardStatus';
import { useCustomer } from '@/hooks/useCustomer';
import useEuroSpendEnablement from '@/hooks/useEuroSpendEnablement';
import useRainRealTimeFunding from '@/hooks/useRainRealTimeFunding';
import { useRewardsUserData } from '@/hooks/useRewards';
Expand All @@ -61,7 +60,6 @@ import {
canAddFundsToCard,
canToggleCardFreeze,
canWithdrawFromCard,
isCustomerFundsRestricted,
} from '@/lib/utils/cardHelpers';
import { useCardHeroStore } from '@/store/useCardHeroStore';
import { useCardPaneStore } from '@/store/useCardPaneStore';
Expand Down Expand Up @@ -109,7 +107,6 @@ const CardDetailsPane = () => {

const { data: cardDetails, refetch } = useCardDetails();
const { data: cardStatus } = useCardStatus();
const { data: customer } = useCustomer();
const { data: rewardsData } = useRewardsUserData();
const { provider } = useCardProvider();
const [isFreezing, setIsFreezing] = useState(false);
Expand Down Expand Up @@ -242,7 +239,6 @@ const CardDetailsPane = () => {
// Add funds and Withdraw part ways on a frozen card — see the two helpers.
const fundsAccess = {
isCardFrozen,
isCustomerRestricted: isCustomerFundsRestricted(customer?.status),
provider,
};

Expand Down
9 changes: 1 addition & 8 deletions components/Card/NewCardDetails/SpendMode/SpendModeModals.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,8 @@ import SpendModeSheet from '@/components/Card/NewCardDetails/SpendMode/SpendMode
import WirexCardFundModal from '@/components/Card/WirexCardFundModal';
import { useCardDetails } from '@/hooks/useCardDetails';
import { useCardProvider } from '@/hooks/useCardProvider';
import { useCustomer } from '@/hooks/useCustomer';
import { CardStatus } from '@/lib/types';
import {
canAddFundsToCard,
canDepositToCard,
isCustomerFundsRestricted,
} from '@/lib/utils/cardHelpers';
import { canAddFundsToCard, canDepositToCard } from '@/lib/utils/cardHelpers';
import { useCardPaneStore } from '@/store/useCardPaneStore';
import { useSpendModeHelpStore } from '@/store/useSpendModeHelpStore';
import { useUserStore } from '@/store/useUserStore';
Expand All @@ -36,12 +31,10 @@ const SpendModeModals = ({
const [isHelpOpen, setIsHelpOpen] = useState(false);
const [isFundOpen, setIsFundOpen] = useState(false);
const { data: cardDetails } = useCardDetails();
const { data: customer } = useCustomer();
const { provider } = useCardProvider();
const canAddFunds =
canAddFundsToCard({
isCardFrozen: cardDetails?.status === CardStatus.FROZEN,
isCustomerRestricted: isCustomerFundsRestricted(customer?.status),
provider,
}) && !canDepositToCard(provider);

Expand Down
25 changes: 8 additions & 17 deletions components/Card/NewCardDetails/__tests__/SpendModeModals.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@ import { BackHandler, Platform } from 'react-native';

import SpendingModeCard from '@/components/Card/NewCardDetails/SpendingModeCard';
import SpendModeModals from '@/components/Card/NewCardDetails/SpendMode/SpendModeModals';
import { CardProvider, CardStatus, KycStatus } from '@/lib/types';
import { CardProvider, CardStatus } from '@/lib/types';
import { useCardPaneStore } from '@/store/useCardPaneStore';
import { useSpendModeHelpStore } from '@/store/useSpendModeHelpStore';

// eslint-disable-next-line @typescript-eslint/no-require-imports
const { act, create } = require('react-test-renderer');

let mockCardStatus = CardStatus.ACTIVE;
let mockCustomerStatus = KycStatus.APPROVED;
let mockProvider = CardProvider.WIREX;

jest.mock('@/hooks/useCardDetails', () => ({
Expand All @@ -20,9 +19,6 @@ jest.mock('@/hooks/useCardDetails', () => ({
jest.mock('@/hooks/useCardProvider', () => ({
useCardProvider: () => ({ provider: mockProvider }),
}));
jest.mock('@/hooks/useCustomer', () => ({
useCustomer: () => ({ data: { status: mockCustomerStatus } }),
}));
jest.mock('@/store/useUserStore', () => ({
useUserStore: (selector: any) => selector({ users: [{ selected: true, userId: 'user-a' }] }),
}));
Expand Down Expand Up @@ -63,7 +59,6 @@ const open = () => act(() => useCardPaneStore.getState().openSpendMode());
beforeEach(() => {
(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true;
mockCardStatus = CardStatus.ACTIVE;
mockCustomerStatus = KycStatus.APPROVED;
mockProvider = CardProvider.WIREX;
useCardPaneStore.setState({ isOpen: false, isSpendModeOpen: false, originRect: null });
useSpendModeHelpStore.setState({ shownByUserId: {} });
Expand Down Expand Up @@ -153,17 +148,13 @@ test('closes the selector before funding and returns to Home afterwards', () =>
expect(sheet().isOpen).toBe(false);
});

test.each(['frozen card', 'restricted customer', 'Rain card'])(
'keeps Add funds unavailable for a %s',
restriction => {
if (restriction === 'frozen card') mockCardStatus = CardStatus.FROZEN;
if (restriction === 'restricted customer') mockCustomerStatus = KycStatus.PAUSED;
if (restriction === 'Rain card') mockProvider = CardProvider.RAIN;
render();
open();
expect(sheet().onAddFunds).toBeUndefined();
},
);
test.each(['frozen card', 'Rain card'])('keeps Add funds unavailable for a %s', restriction => {
if (restriction === 'frozen card') mockCardStatus = CardStatus.FROZEN;
if (restriction === 'Rain card') mockProvider = CardProvider.RAIN;
render();
open();
expect(sheet().onAddFunds).toBeUndefined();
});

test('discards an unavailable selector without opening the card pane or marking help seen', () => {
render(false);
Expand Down
11 changes: 2 additions & 9 deletions hooks/useActivateCard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,19 +75,12 @@ export function useActivateCard() {
toggleStep,
canToggleStep,
activatingCard,
cardsEndorsement,
pushCardReady,
} = useCardSteps(_kycStatus as KycStatus | undefined, cardStatusResponse);

// Derived: under review state — the screen shows "your card is on its way"
// instead of a steps list the user cannot move. Previously read from the
// bridge.xyz endorsement alone, which is a concept neither live issuer has, so
// a Wirex/Sumsub or Rain/Didit applicant mid-decision fell through to the
// steps list and was asked to verify all over again.
const isUnderReview = isCardIssuanceUnderReview({
cardStatus: cardStatusResponse,
cardsEndorsement,
});
// instead of a steps list the user cannot move.
const isUnderReview = isCardIssuanceUnderReview({ cardStatus: cardStatusResponse });

// Track page view on mount
useEffect(() => {
Expand Down
52 changes: 23 additions & 29 deletions hooks/useCardSteps/__tests__/buildCardSteps.test.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
import { EndorsementStatus } from '@/components/BankTransfer/enums';
import { buildCardSteps } from '@/hooks/useCardSteps/stepHelpers';
import {
BridgeCustomerEndorsement,
CardProvider,
KycStatus,
RainApplicationStatus,
} from '@/lib/types';
import { CardProvider, KycStatus, RainApplicationStatus } from '@/lib/types';

const noop = () => {};

type Options = Parameters<typeof buildCardSteps>[7];
type Options = Parameters<typeof buildCardSteps>[5];

const build = ({
cardActivated = false,
Expand All @@ -21,8 +15,6 @@ const build = ({
options?: Options;
} = {}) =>
buildCardSteps(
undefined, // cardsEndorsement
undefined, // customerRejectionReasons
cardActivated,
activationBlocked,
noop, // handleProceedToKyc
Expand Down Expand Up @@ -281,33 +273,35 @@ describe('buildCardSteps - a blocked activation', () => {
});
});

describe('buildCardSteps - a retired Bridge endorsement', () => {
// An old bridge.xyz customer keeps their "cards" endorsement on Bridge's side.
const approvedBridgeEndorsement = {
name: 'cards',
status: EndorsementStatus.APPROVED,
} as unknown as BridgeCustomerEndorsement;

describe('buildCardSteps - an applicant with no card customer yet', () => {
// `/cards/status` reports no kycStatus until a card customer exists. The
// retired bridge.xyz "cards" endorsement used to stand in for it here; nothing
// does now, so the applicant is simply asked to start verification.
const kycStepFor = (kycStatus?: KycStatus) =>
buildCardSteps(approvedBridgeEndorsement, undefined, false, undefined, noop, noop, noop, {
buildCardSteps(false, undefined, noop, noop, noop, {
cardIssuer: CardProvider.WIREX,
kycStatus,
depositRequired: false,
}).find(s => s.key === 'kyc');

it('does not complete KYC for a Wirex applicant who has not verified', () => {
// The backend reports a card customer, so its kycStatus is the answer —
// the Bridge approval is for a card that no longer exists.
const kyc = kycStepFor(KycStatus.NOT_STARTED);
it.each([undefined, KycStatus.NOT_STARTED])(
'offers to start verification (kycStatus %s)',
kycStatus => {
const kyc = kycStepFor(kycStatus);

expect(kyc?.completed).toBe(false);
expect(kyc?.buttonText).toBe('Continue verification');
expect(kyc?.onPress).toBeDefined();
});
expect(kyc?.completed).toBe(false);
expect(kyc?.buttonText).toBe('Continue verification');
expect(kyc?.onPress).toBeDefined();
},
);

it('still honours the endorsement for a Bridge-only user with no card customer', () => {
const kyc = kycStepFor(undefined);
it('does not offer to activate a card', () => {
const activate = buildCardSteps(false, undefined, noop, noop, noop, {
cardIssuer: CardProvider.WIREX,
depositRequired: false,
}).find(s => s.key === 'activate');

expect(kyc?.completed).toBe(true);
expect(activate?.buttonText).toBeUndefined();
expect(activate?.onPress).toBeUndefined();
});
});
Loading
Loading