Skip to content

Commit 173a306

Browse files
Merge pull request #432 from ameerul-deriv/P2PS-4790-safety-alert-banner-for-p2p-scam
Ameerul / P2PS-4790 Safety Alert Banner for P2P Scam
2 parents 72d3789 + 6668a55 commit 173a306

File tree

6 files changed

+158
-1
lines changed

6 files changed

+158
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.safety-alert-modal {
2+
@include default-modal;
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { useEffect, useState } from 'react';
2+
import moment from 'moment';
3+
import { useLocalStorage } from 'usehooks-ts';
4+
import { api } from '@/hooks';
5+
import { DerivLightIcWarningIcon } from '@deriv/quill-icons';
6+
import { Localize } from '@deriv-com/translations';
7+
import { Button, Modal, Text } from '@deriv-com/ui';
8+
import './SafetyAlertModal.scss';
9+
10+
const SafetyAlertModal = () => {
11+
const { data } = api.account.useActiveAccount();
12+
const [isModalOpen, setIsModalOpen] = useState(false);
13+
const [safetyModalTimestamp, setSafetyModalTimestamp] = useLocalStorage(
14+
`p2p_${data?.loginid}_disclaimer_shown`,
15+
''
16+
);
17+
18+
const onClickOK = () => {
19+
const currentTimestamp = moment().valueOf();
20+
const timestamp = currentTimestamp.toString();
21+
setSafetyModalTimestamp(timestamp);
22+
setIsModalOpen(false);
23+
};
24+
25+
useEffect(() => {
26+
if (data?.loginid) {
27+
if (safetyModalTimestamp) {
28+
const now = moment();
29+
const savedTime = moment(parseInt(safetyModalTimestamp));
30+
31+
// Calculate the difference in days
32+
const daysPassed = now.diff(savedTime, 'days');
33+
34+
if (daysPassed >= 1) {
35+
localStorage.removeItem(`p2p_${data?.loginid}_disclaimer_shown`);
36+
}
37+
} else {
38+
setIsModalOpen(true);
39+
}
40+
}
41+
// eslint-disable-next-line react-hooks/exhaustive-deps
42+
}, [data?.loginid, safetyModalTimestamp]);
43+
44+
if (!isModalOpen) return null;
45+
46+
return (
47+
<Modal ariaHideApp={false} className='safety-alert-modal' isOpen={isModalOpen}>
48+
<Modal.Body className='px-10 pt-20 pb-5 flex flex-col gap-[1.5rem]'>
49+
<div className='flex flex-col items-center gap-[1.5rem]'>
50+
<DerivLightIcWarningIcon height='64px' width='64px' />
51+
<Text align='start' weight='bold'>
52+
Stay safe from phishing scams
53+
</Text>
54+
</div>
55+
<div className='flex flex-col gap-[1.5rem]'>
56+
<Text size='sm'>
57+
<Localize i18n_default_text='Deriv will NEVER ask for your login details.' />
58+
</Text>
59+
<div className='flex flex-col gap-2'>
60+
<Text size='sm'>
61+
<Localize i18n_default_text='Protect your account:' />
62+
</Text>
63+
<ul className='flex flex-col gap-2 list-disc pl-10'>
64+
<li>
65+
<Text size='sm'>
66+
<Localize i18n_default_text='Never share verification codes or their screenshots.' />
67+
</Text>
68+
</li>
69+
<li>
70+
<Text size='sm'>
71+
<Localize i18n_default_text='Always check the website URL.' />
72+
</Text>
73+
</li>
74+
<li>
75+
<Text size='sm'>
76+
<Localize i18n_default_text='Only communicate with Deriv through live chat.' />
77+
</Text>
78+
</li>
79+
</ul>
80+
</div>
81+
<Text size='sm'>
82+
<Localize i18n_default_text='If you spot anything suspicious, let us know via live chat.' />
83+
</Text>
84+
</div>
85+
</Modal.Body>
86+
<Modal.Footer hideBorder>
87+
<Button onClick={onClickOK} size='lg' textSize='sm'>
88+
<Localize i18n_default_text='OK' />
89+
</Button>
90+
</Modal.Footer>
91+
</Modal>
92+
);
93+
};
94+
95+
export default SafetyAlertModal;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import moment from 'moment';
2+
import { fireEvent, render, screen } from '@testing-library/react';
3+
import SafetyAlertModal from '../SafetyAlertModal';
4+
5+
jest.mock('@/hooks', () => ({
6+
api: {
7+
account: {
8+
useActiveAccount: jest.fn(() => ({
9+
data: {
10+
loginid: '123456',
11+
},
12+
})),
13+
},
14+
},
15+
}));
16+
17+
describe('SafetyAlertModal', () => {
18+
afterEach(() => {
19+
localStorage.clear();
20+
});
21+
22+
it('should render the modal if the timestamp is not set', () => {
23+
render(<SafetyAlertModal />);
24+
expect(screen.getByText('Stay safe from phishing scams')).toBeInTheDocument();
25+
expect(screen.getByText('Deriv will NEVER ask for your login details.')).toBeInTheDocument();
26+
expect(screen.getByText('Protect your account:')).toBeInTheDocument();
27+
expect(screen.getByText('Never share verification codes or their screenshots.')).toBeInTheDocument();
28+
expect(screen.getByText('Always check the website URL.')).toBeInTheDocument();
29+
expect(screen.getByText('Only communicate with Deriv through live chat.')).toBeInTheDocument();
30+
expect(screen.getByText('If you spot anything suspicious, let us know via live chat.')).toBeInTheDocument();
31+
expect(screen.getByRole('button', { name: 'OK' })).toBeInTheDocument();
32+
});
33+
34+
it('should not render the modal if the timestamp is set', () => {
35+
localStorage.setItem('p2p_123456_disclaimer_shown', moment().valueOf().toString());
36+
render(<SafetyAlertModal />);
37+
expect(screen.queryByText('Stay safe from phishing scams')).not.toBeInTheDocument();
38+
});
39+
40+
it('should hide the modal when clicking the OK button', () => {
41+
render(<SafetyAlertModal />);
42+
const okButton = screen.getByRole('button', { name: 'OK' });
43+
fireEvent.click(okButton);
44+
expect(screen.queryByText('Stay safe from phishing scams')).not.toBeInTheDocument();
45+
});
46+
47+
it('should remove the timestamp from localStorage once the timestamp is passed one day', () => {
48+
localStorage.setItem('p2p_123456_disclaimer_shown', moment().subtract(1, 'day').valueOf().toString());
49+
render(<SafetyAlertModal />);
50+
expect(screen.queryByText('Stay safe from phishing scams')).not.toBeInTheDocument();
51+
});
52+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as SafetyAlertModal } from './SafetyAlertModal';

src/components/Modals/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,6 @@ export * from './PreferredCountriesModal';
3333
export * from './RadioGroupFilterModal';
3434
export * from './RateFluctuationModal';
3535
export * from './RatingModal';
36+
export * from './SafetyAlertModal';
3637
export * from './ShareAdsModal';
3738
export * from './VideoPlayerModal';

src/routes/AppContent/index.tsx

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useEffect, useRef, useState } from 'react';
22
import { useHistory, useLocation } from 'react-router-dom';
33
import { BlockedScenarios } from '@/components/BlockedScenarios';
4+
import { SafetyAlertModal } from '@/components/Modals';
45
import { BUY_SELL_URL, ERROR_CODES } from '@/constants';
56
import { api, useIsP2PBlocked, useLiveChat, useOAuth } from '@/hooks';
67
import { GuideTooltip } from '@/pages/guide/components';
@@ -102,7 +103,10 @@ const AppContent = () => {
102103
}, []);
103104

104105
const getComponent = () => {
105-
if ((isP2PSettingsLoading || isLoadingActiveAccount || !isFetched || !activeAccountData) && !isEndpointRoute) {
106+
if (
107+
(isP2PSettingsLoading || isLoadingActiveAccount || !isFetched || !activeAccountData || isLoading) &&
108+
!isEndpointRoute
109+
) {
106110
return <Loader />;
107111
} else if ((isP2PBlocked && !isEndpointRoute) || isPermissionDenied || p2pSettingsError?.code) {
108112
return (
@@ -127,6 +131,7 @@ const AppContent = () => {
127131
))}
128132
</Tabs>
129133
{isDesktop && !isEndpointRoute && <GuideTooltip />}
134+
{!isEndpointRoute && <SafetyAlertModal />}
130135
<Router />
131136
</div>
132137
);

0 commit comments

Comments
 (0)