Skip to content

Commit

Permalink
Merge pull request #333 from ameerul-deriv/FEQ-2627-my-counterparties…
Browse files Browse the repository at this point in the history
…-tab-not-showing-correct-message

Ameerul / FEQ-2627 My Counterparties tab is not showing the correct message for temp banned user
  • Loading branch information
farrah-deriv authored Sep 19, 2024
2 parents b1d53ad + 0f2a2ef commit a63c186
Show file tree
Hide file tree
Showing 12 changed files with 338 additions and 55 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { Dispatch, SetStateAction, useEffect } from 'react';
import { api } from '@/hooks';
import { useEffect } from 'react';
import { useHistory } from 'react-router-dom';
import { useShallow } from 'zustand/react/shallow';
import { BUY_SELL_URL, ERROR_CODES } from '@/constants';
import { api, useModalManager } from '@/hooks';
import { useErrorStore } from '@/stores';
import { getCurrentRoute } from '@/utils';
import { Localize, useTranslations } from '@deriv-com/translations';
import { Button, Modal, Text, useDevice } from '@deriv-com/ui';
import { ErrorModal } from '../ErrorModal';
import './BlockUnblockUserModal.scss';

type TBlockUnblockUserModalProps = {
Expand All @@ -11,7 +17,6 @@ type TBlockUnblockUserModalProps = {
isModalOpen: boolean;
onClickBlocked?: () => void;
onRequestClose: () => void;
setErrorMessage?: Dispatch<SetStateAction<string | undefined>>;
};

const BlockUnblockUserModal = ({
Expand All @@ -21,10 +26,9 @@ const BlockUnblockUserModal = ({
isModalOpen,
onClickBlocked,
onRequestClose,
setErrorMessage,
}: TBlockUnblockUserModalProps) => {
const { isMobile } = useDevice();
const { localize } = useTranslations();
const { isMobile } = useDevice();
const {
mutate: blockAdvertiser,
mutation: { error, isSuccess },
Expand All @@ -33,17 +37,28 @@ const BlockUnblockUserModal = ({
mutate: unblockAdvertiser,
mutation: { error: unblockError, isSuccess: unblockIsSuccess },
} = api.counterparty.useUnblock();
const { hideModal, isModalOpenFor, showModal } = useModalManager();
const { errorMessages, setErrorMessages } = useErrorStore(
useShallow(state => ({ errorMessages: state.errorMessages, setErrorMessages: state.setErrorMessages }))
);
const isAdvertiser = getCurrentRoute() === 'advertiser';
const history = useHistory();

useEffect(() => {
if (isSuccess || unblockIsSuccess) {
onClickBlocked?.();
onRequestClose();
} else if (error || unblockError) {
setErrorMessage?.(error?.message || unblockError?.message);
onRequestClose();
setErrorMessages(error || unblockError);

if (error?.code === ERROR_CODES.PERMISSION_DENIED && isAdvertiser) {
showModal('ErrorModal');
} else {
onRequestClose();
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isSuccess, onClickBlocked, unblockIsSuccess, unblockError, error, setErrorMessage]);
}, [isSuccess, onClickBlocked, unblockIsSuccess, unblockError, error, setErrorMessages]);

const textSize = isMobile ? 'md' : 'sm';
const getModalTitle = () =>
Expand All @@ -70,6 +85,24 @@ const BlockUnblockUserModal = ({
}
};

const permissionDeniedError = errorMessages.find(error => error.code === ERROR_CODES.PERMISSION_DENIED);

if (permissionDeniedError && isModalOpenFor('ErrorModal')) {
return (
<ErrorModal
buttonText={localize('Got it')}
hideCloseIcon
isModalOpen
message={permissionDeniedError.message}
onRequestClose={() => {
hideModal();
history.push(BUY_SELL_URL);
}}
title={localize('Unable to block advertiser')}
/>
);
}

return (
<Modal
ariaHideApp={false}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,42 @@
import { BUY_SELL_URL } from '@/constants';
import { getCurrentRoute } from '@/utils';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import BlockUnblockUserModal from '../BlockUnblockUserModal';

const mockOnRequestClose = jest.fn();
const mockUseBlockMutate = jest.fn();
const mockUseUnblockMutate = jest.fn();
const mockBlockMutation = {
error: {},
isSuccess: false,
};

const mockModalManager = {
hideModal: jest.fn(),
isModalOpenFor: jest.fn().mockReturnValue(false),
showModal: jest.fn(),
};

const mockStore = {
errorMessages: [],
setErrorMessages: jest.fn(),
};

const mockPush = jest.fn();

jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useHistory: () => ({ push: mockPush }),
}));

jest.mock('@/hooks', () => ({
...jest.requireActual('@/hooks'),
api: {
counterparty: {
useBlock: jest.fn(() => ({
mutate: mockUseBlockMutate,
mutation: {
error: {},
isSuccess: false,
},
mutation: mockBlockMutation,
})),
useUnblock: jest.fn(() => ({
mutate: mockUseUnblockMutate,
Expand All @@ -28,11 +49,25 @@ jest.mock('@/hooks', () => ({
},
}));

jest.mock('@/hooks/custom-hooks', () => ({
useModalManager: jest.fn(() => mockModalManager),
}));

jest.mock('@/utils', () => ({
getCurrentRoute: jest.fn().mockReturnValue('my-profile'),
}));

jest.mock('@deriv-com/ui', () => ({
...jest.requireActual('@deriv-com/ui'),
useDevice: jest.fn(() => ({ isMobile: false })),
}));

jest.mock('@/stores', () => ({
useErrorStore: jest.fn(selector => (selector ? selector(mockStore) : mockStore)),
}));

const mockGetCurrentRoute = getCurrentRoute as jest.Mock;

describe('BlockUnblockUserModal', () => {
it('should render the modal with correct title and behaviour for blocking user', async () => {
render(
Expand Down Expand Up @@ -100,4 +135,67 @@ describe('BlockUnblockUserModal', () => {

expect(mockOnRequestClose).toBeCalled();
});

it('should call onClickBlocked and onRequestClose if isSuccess or mutation returns success', async () => {
mockBlockMutation.isSuccess = true;
const mockOnClickBlocked = jest.fn();
render(
<BlockUnblockUserModal
advertiserName='Hu Tao'
id='2'
isBlocked={true}
isModalOpen={true}
onClickBlocked={mockOnClickBlocked}
onRequestClose={mockOnRequestClose}
/>
);

expect(mockOnRequestClose).toHaveBeenCalled();
expect(mockOnClickBlocked).toHaveBeenCalled();
});

it('should show error modal when permission is denied and current route is advertiser', async () => {
mockGetCurrentRoute.mockReturnValue('advertiser');
mockModalManager.isModalOpenFor.mockImplementation((modalName: string) => modalName === 'ErrorModal');
const error = {
code: 'PermissionDenied',
message: 'You are not allowed to block this user',
};
// @ts-expect-error - mock values
mockStore.errorMessages = [error];
mockBlockMutation.error = error;
mockBlockMutation.isSuccess = false;
render(
<BlockUnblockUserModal
advertiserName='Hu Tao'
id='2'
isBlocked={true}
isModalOpen={true}
onRequestClose={mockOnRequestClose}
/>
);

expect(screen.queryByText('Unable to block advertiser')).toBeVisible();
expect(screen.queryByText('You are not allowed to block this user')).toBeVisible();
});

it('should call hideModal and history.push when user clicks on Got it button', async () => {
render(
<BlockUnblockUserModal
advertiserName='Hu Tao'
id='2'
isBlocked={true}
isModalOpen={true}
onRequestClose={mockOnRequestClose}
/>
);

const gotItBtn = screen.getByRole('button', {
name: 'Got it',
});
await userEvent.click(gotItBtn);

expect(mockModalManager.hideModal).toHaveBeenCalled();
expect(mockPush).toHaveBeenCalledWith(BUY_SELL_URL);
});
});
1 change: 1 addition & 0 deletions src/constants/api-error-codes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ export const ERROR_CODES = {
ORDER_CREATE_FAIL_RATE_SLIPPAGE: 'OrderCreateFailRateSlippage',
ORDER_EMAIL_VERIFICATION_REQUIRED: 'OrderEmailVerificationRequired',
PERMISSION_DENIED: 'PermissionDenied',
TEMPORARY_BAR: 'TemporaryBar',
} as const;
14 changes: 6 additions & 8 deletions src/hooks/custom-hooks/useIsAdvertiserBarred.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useState } from 'react';
import { useMemo } from 'react';
import useInvalidateQuery from '../api/useInvalidateQuery';
import { api } from '..';

Expand All @@ -8,16 +8,14 @@ import { api } from '..';
*/
const useIsAdvertiserBarred = (): boolean => {
const { data = {} } = api.advertiser.useGetInfo();
const [isAdvertiserBarred, setIsAdvertiserBarred] = useState(false);
const invalidate = useInvalidateQuery();

useEffect(() => {
if (isAdvertiserBarred !== !!data.blocked_until) {
invalidate('p2p_advertiser_adverts');
setIsAdvertiserBarred(!!data.blocked_until);
}
const isAdvertiserBarred = useMemo(() => {
invalidate('p2p_advertiser_adverts');
return !!data.blocked_until;

// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isAdvertiserBarred, data.blocked_until]);
}, [data.blocked_until]);

return isAdvertiserBarred;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,16 @@ const AdvertiserBlockOverlay = ({
</Button>
</div>
{children}
<BlockUnblockUserModal
advertiserName={advertiserName}
id={id ?? ''}
isBlocked
isModalOpen={!!isModalOpenFor('BlockUnblockUserModal')}
onClickBlocked={() => setShowOverlay(false)}
onRequestClose={hideModal}
/>
{isModalOpenFor('BlockUnblockUserModal') && (
<BlockUnblockUserModal
advertiserName={advertiserName}
id={id ?? ''}
isBlocked
isModalOpen={!!isModalOpenFor('BlockUnblockUserModal')}
onClickBlocked={() => setShowOverlay(false)}
onRequestClose={hideModal}
/>
)}
</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
import { useEffect } from 'react';
import { useShallow } from 'zustand/react/shallow';
import { Table } from '@/components';
import { ERROR_CODES } from '@/constants';
import { api } from '@/hooks';
import { useIsAdvertiserBarred } from '@/hooks/custom-hooks';
import { useErrorStore } from '@/stores';
import { DerivLightIcBlockedAdvertisersBarredIcon } from '@deriv/quill-icons';
import { Localize } from '@deriv-com/translations';
import { Loader, Text, useDevice } from '@deriv-com/ui';
Expand All @@ -18,21 +22,14 @@ type TMyProfileCounterpartiesTableRowRendererProps = {
id?: string;
is_blocked: boolean;
name?: string;
setErrorMessage: Dispatch<SetStateAction<string | undefined>>;
};

const MyProfileCounterpartiesTableRowRenderer = ({
id,
is_blocked: isBlocked,
name,
setErrorMessage,
}: TMyProfileCounterpartiesTableRowRendererProps) => (
<MyProfileCounterpartiesTableRow
id={id ?? ''}
isBlocked={isBlocked}
nickname={name ?? ''}
setErrorMessage={setErrorMessage}
/>
<MyProfileCounterpartiesTableRow id={id ?? ''} isBlocked={isBlocked} nickname={name ?? ''} />
);

const MyProfileCounterpartiesTable = ({
Expand All @@ -50,8 +47,14 @@ const MyProfileCounterpartiesTable = ({
is_blocked: dropdownValue === 'blocked' ? 1 : 0,
trade_partners: 1,
});
const [errorMessage, setErrorMessage] = useState<string | undefined>('');
const { isDesktop } = useDevice();
const { errorMessages, reset } = useErrorStore(
useShallow(state => ({ errorMessages: state.errorMessages, reset: state.reset }))
);
const isAdvertiserBarred = useIsAdvertiserBarred();
const errorMessage = errorMessages.find(
error => error.code === ERROR_CODES.TEMPORARY_BAR || error.code === ERROR_CODES.PERMISSION_DENIED
)?.message;

useEffect(() => {
if (data.length > 0) {
Expand All @@ -62,6 +65,13 @@ const MyProfileCounterpartiesTable = ({
}
}, [data, errorMessage, setShowHeader]);

useEffect(() => {
if (!isAdvertiserBarred && errorMessages.some(error => error.code === ERROR_CODES.TEMPORARY_BAR)) {
setShowHeader(true);
reset();
}
}, [errorMessage, errorMessages, isAdvertiserBarred, reset, setShowHeader]);

if (isLoading) {
return <Loader className='my-profile-counterparties-table__loader' isFullScreen={false} />;
}
Expand Down Expand Up @@ -93,7 +103,6 @@ const MyProfileCounterpartiesTable = ({
rowRender={(rowData: unknown) => (
<MyProfileCounterpartiesTableRowRenderer
{...(rowData as TMyProfileCounterpartiesTableRowRendererProps)}
setErrorMessage={setErrorMessage}
/>
)}
tableClassname='my-profile-counterparties-table'
Expand Down
Loading

0 comments on commit a63c186

Please sign in to comment.