Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[P2PS] farrah/P2PS-1953/feat: ad terms #13636

Merged
merged 24 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
12512ba
feat: add unavailable button
farrah-deriv Feb 16, 2024
14e39e0
Merge remote-tracking branch 'upstream/master' into ad-terms
farrah-deriv Feb 16, 2024
2a2f2cb
fix: translation
farrah-deriv Feb 19, 2024
c1d478b
Merge remote-tracking branch 'upstream/master' into ad-terms
farrah-deriv Feb 19, 2024
aca4d2e
feat: ad terms flow
farrah-deriv Mar 6, 2024
3ea3adf
Merge remote-tracking branch 'upstream/master' into ad-terms
farrah-deriv Mar 7, 2024
a7d6b15
fix: review comments
farrah-deriv Mar 11, 2024
b5856f3
Merge remote-tracking branch 'upstream/master' into ad-terms
farrah-deriv Mar 11, 2024
2236253
fix: moved eligibility function to utils
farrah-deriv Mar 12, 2024
c5816a2
fix: refactor code
farrah-deriv Mar 12, 2024
cd8ec86
Merge remote-tracking branch 'upstream/master' into ad-terms
farrah-deriv Apr 13, 2024
179afd3
feat: add eligible countries
farrah-deriv Apr 14, 2024
af6d199
fix: test
farrah-deriv Apr 15, 2024
d53f2e1
Merge remote-tracking branch 'upstream/master' into ad-terms
farrah-deriv Apr 22, 2024
b54eca1
fix: remove unnecessary change
farrah-deriv Apr 25, 2024
950f42a
Merge remote-tracking branch 'upstream/master' into ad-terms
farrah-deriv May 1, 2024
2791f8a
fix: subtasks
farrah-deriv May 6, 2024
4447bb9
fix: tests
farrah-deriv May 7, 2024
9c7c2b0
fix: message type
farrah-deriv May 7, 2024
28b8fac
fix: subtasks
farrah-deriv May 8, 2024
06d138f
fix: subtasks
farrah-deriv May 13, 2024
70b815c
fix: subtasks
farrah-deriv May 14, 2024
adaae77
fix: sorting of country list
farrah-deriv May 14, 2024
d3c827a
fix: subtask
farrah-deriv May 16, 2024
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: 6 additions & 0 deletions packages/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ import type {
P2PAdvertUpdateResponse,
P2PChatCreateRequest,
P2PChatCreateResponse,
P2PCountryListRequest,
P2PCountryListResponse,
P2POrderCancelRequest,
P2POrderCancelResponse,
P2POrderConfirmRequest,
Expand Down Expand Up @@ -2490,6 +2492,10 @@ type TSocketEndpoints = {
request: P2PChatCreateRequest;
response: P2PChatCreateResponse;
};
p2p_country_list: {
request: P2PCountryListRequest;
response: P2PCountryListResponse;
};
p2p_order_cancel: {
request: P2POrderCancelRequest;
response: P2POrderCancelResponse;
Expand Down
3 changes: 2 additions & 1 deletion packages/components/src/components/dropdown/dropdown.scss
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,8 @@
min-width: 15rem;
width: 100%;

&:not(.cfd-personal-details-modal__form *):not(.trade-container__multiplier-dropdown):not(.dc-dropdown--left):not(.contract-type-info__dropdown) {
&:not(.cfd-personal-details-modal__form *):not(.trade-container__multiplier-dropdown):not(
.dc-dropdown--left):not(.contract-type-info__dropdown) {
margin-top: unset;
}

Expand Down
78 changes: 78 additions & 0 deletions packages/hooks/src/__tests__/useP2PCountryList.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import React from 'react';
import { APIProvider, useQuery } from '@deriv/api';
import { renderHook } from '@testing-library/react-hooks';
import useP2PCountryList from '../useP2PCountryList';

jest.mock('@deriv/api', () => ({
...jest.requireActual('@deriv/api'),
useQuery: jest.fn(),
}));

const wrapper = ({ children }: { children: JSX.Element }) => <APIProvider>{children}</APIProvider>;
const mockUseQuery = useQuery as jest.MockedFunction<typeof useQuery<'p2p_country_list'>>;

describe('useP2PCountryList', () => {
it('should return undefined when there is no response', () => {
// @ts-expect-error need to come up with a way to mock the return type of useQuery
mockUseQuery.mockReturnValue({ data: {} });
const { result } = renderHook(() => useP2PCountryList(), { wrapper });
expect(result.current.p2p_country_list).toBeUndefined();
});

it('should return country list with the correct details', () => {
const mockQueryData = {
p2p_country_list: {
ai: {
country_name: 'Anguilla',
cross_border_ads_enabled: 1,
fixed_rate_adverts: 'enabled',
float_rate_adverts: 'disabled',
float_rate_offset_limit: 10,
local_currency: 'XCD',
payment_methods: {
alipay: {
display_name: 'Alipay',
fields: {
account: {
display_name: 'Alipay ID',
required: 1,
type: 'text',
},
instructions: {
display_name: 'Instructions',
required: 0,
type: 'memo',
},
},
type: 'ewallet',
},
},
},
},
};
// @ts-expect-error need to come up with a way to mock the return type of useQuery
mockUseQuery.mockReturnValue({
data: mockQueryData,
});

const { result } = renderHook(() => useP2PCountryList(), { wrapper });
const { p2p_country_list } = result.current;
if (p2p_country_list) {
farrah-deriv marked this conversation as resolved.
Show resolved Hide resolved
expect(p2p_country_list).toEqual(mockQueryData.p2p_country_list);
}
});
it('should call the useQuery with parameters if passed', () => {
renderHook(() => useP2PCountryList({ country: 'id' }), { wrapper });
expect(mockUseQuery).toHaveBeenCalledWith('p2p_country_list', {
payload: { country: 'id' },
options: { refetchOnWindowFocus: false },
});
});
it('should call the useQuery with default parameters if not passed', () => {
renderHook(() => useP2PCountryList(), { wrapper });
expect(mockUseQuery).toHaveBeenCalledWith('p2p_country_list', {
payload: undefined,
options: { refetchOnWindowFocus: false },
});
});
});
1 change: 1 addition & 0 deletions packages/hooks/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export { default as useP2PAdvertInfo } from './useP2PAdvertInfo';
export { default as useP2PAdvertList } from './useP2PAdvertList';
export { default as useP2PAdvertiserPaymentMethods } from './useP2PAdvertiserPaymentMethods';
export { default as useP2PCompletedOrdersNotification } from './useP2PCompletedOrdersNotification';
export { default as useP2PCountryList } from './useP2PCountryList';
export { default as useP2PExchangeRate } from './useP2PExchangeRate';
export { default as useP2PNotificationCount } from './useP2PNotificationCount';
export { default as useP2POrderList } from './useP2POrderList';
Expand Down
22 changes: 22 additions & 0 deletions packages/hooks/src/useP2PCountryList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { useQuery } from '@deriv/api';

/**
* A custom hook that returns an object containing the list of countries available for P2P trading.
*
* For returning details of a specific country, the country code can be passed in the payload.
* @example: useCountryList({ country: 'id' })
*
*/
const useP2PCountryList = (payload?: NonNullable<Parameters<typeof useQuery<'p2p_country_list'>>[1]>['payload']) => {
const { data, ...rest } = useQuery('p2p_country_list', {
payload,
options: { refetchOnWindowFocus: false },
});

return {
p2p_country_list: data?.p2p_country_list,
...rest,
};
};

export default useP2PCountryList;
30 changes: 30 additions & 0 deletions packages/p2p/src/components/block-selector/block-selector.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
.block-selector {
margin: 2rem 0;

&__label {
display: flex;
align-items: center;
column-gap: 1rem;
}

&__options {
display: flex;
column-gap: 1rem;

@include mobile {
justify-content: space-between;
}
}

&__option {
background-color: transparent;
border: 1px solid var(--border-normal);
border-radius: 0.4rem;
padding: 0.7rem 1.6rem;
width: 12.4rem;

&--selected {
background-color: var(--general-main-3);
}
}
}
89 changes: 89 additions & 0 deletions packages/p2p/src/components/block-selector/block-selector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import React from 'react';
import classNames from 'classnames';
import { Icon, Text } from '@deriv/components';
import { localize, Localize } from 'Components/i18next';
import { useModalManagerContext } from 'Components/modal-manager/modal-manager-context';

type TBlockSelectorOptionProps = {
is_selected?: boolean;
name: string;
value: number;
};
type TBlockSelectorProps = {
label: string;
onSelect: (value: number) => void;
options: TBlockSelectorOptionProps[];
tooltip_info: React.ReactNode;
value: number;
};

const BlockSelector = ({ label, onSelect, options, tooltip_info, value }: TBlockSelectorProps) => {
const [selectors, setSelectors] = React.useState(options);
const [selected_value, setSelectedValue] = React.useState(value);
const { showModal } = useModalManagerContext();
const onClick = e => {
const selected_value = e.target.getAttribute('data-value');
onSelect?.(0);
setSelectedValue(0);

const updated_selectors = selectors.map(selector => {
const is_selected = !selector.is_selected && selector.value == selected_value;

if (is_selected) {
onSelect?.(selector.value);
setSelectedValue(selector.value);
}

return { ...selector, is_selected };
});

setSelectors(updated_selectors);
};

React.useEffect(() => {
onSelect(value);
}, []);

return (
<div className='block-selector'>
<div className='block-selector__label'>
<Text color='less-prominent' size='xs' line_height='xl'>
<Localize i18n_default_text={label} />
</Text>
<Icon
color='disabled'
icon='IcInfoOutline'
onClick={() => {
showModal({
key: 'ErrorModal',
props: {
has_close_icon: false,
error_message: tooltip_info,
error_modal_title: localize(label),
},
});
}}
/>
</div>
<div className='block-selector__options'>
{selectors.map(option => (
<Text
as='div'
align='center'
color={option.value === selected_value ? 'colored-background' : 'less-prominent'}
data-value={option.value}
key={option.name}
onClick={onClick}
className={classNames('block-selector__option', {
'block-selector__option--selected': option.value === selected_value,
})}
>
{option.name}
</Text>
))}
</div>
</div>
);
};

export default BlockSelector;
4 changes: 4 additions & 0 deletions packages/p2p/src/components/block-selector/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import BlockSelector from './block-selector';
import './block-selector.scss';

export default BlockSelector;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.dc-modal__container_ad-cancel-modal {
.dc-modal-body {
padding: 0.8rem 2.4rem;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const AdCancelModal = ({ confirm_label, message, onConfirm, title }: TAdCancelMo
const { hideModal, is_modal_open } = useModalManagerContext();

return (
<Modal has_close_icon={false} is_open={is_modal_open} small title={title}>
<Modal className='ad-cancel-modal' has_close_icon={false} is_open={is_modal_open} small title={title}>
<Modal.Body>
<Text as='p' size='xs' color='prominent'>
{message}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import AdCancelModal from './ad-cancel-modal';
import './ad-cancel-modal.scss';

export default AdCancelModal;
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ describe('<AdCreateEditErrorModal />', () => {
it('should display the ok button when there is no api error', () => {
render(<AdCreateEditErrorModal />);

const ok_button = screen.getByRole('button', { name: 'Ok' });
const ok_button = screen.getByRole('button', { name: 'OK' });
expect(ok_button).toBeInTheDocument();
});
it('should display the update ad button and "You already have an ad with this range" when there is api error', () => {
Expand All @@ -67,7 +67,7 @@ describe('<AdCreateEditErrorModal />', () => {
it('should close the modal on clicking update ad/ok button', () => {
render(<AdCreateEditErrorModal />);

const ok_button = screen.getByRole('button', { name: 'Ok' });
const ok_button = screen.getByRole('button', { name: 'OK' });
expect(ok_button).toBeInTheDocument();
userEvent.click(ok_button);
expect(mock_modal_manager.hideModal).toHaveBeenCalledTimes(1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ import { generateErrorDialogBody, generateErrorDialogTitle } from 'Utils/adverts

type TAdCreateEditErrorModalProps = {
ad_type?: string;
onUpdateAd?: () => void;
};

const AdCreateEditErrorModal = ({ ad_type = ads.CREATE }: TAdCreateEditErrorModalProps) => {
const AdCreateEditErrorModal = ({ ad_type = ads.CREATE, onUpdateAd }: TAdCreateEditErrorModalProps) => {
const { my_ads_store } = useStores();
const { hideModal, is_modal_open } = useModalManagerContext();
const { api_error_message, edit_ad_form_error, error_code } = my_ads_store;
Expand All @@ -37,8 +38,15 @@ const AdCreateEditErrorModal = ({ ad_type = ads.CREATE }: TAdCreateEditErrorModa
<Modal.Footer>
<Button
has_effect
text={is_api_error ? localize('Update ad') : localize('Ok')}
onClick={is_api_error ? hideModal : () => hideModal({ should_hide_all_modals: true })}
text={is_api_error ? localize('Update ad') : localize('OK')}
onClick={() => {
if (is_api_error) {
onUpdateAd?.();
hideModal();
} else {
hideModal({ should_hide_all_modals: true });
}
}}
primary
large
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ const AdCreatedModal = ({ adverts_archive_period }: TAdCreatedModalProps) => {
/>
</Modal.Body>
<Modal.Footer>
<Button has_effect text={localize('Ok')} onClick={onClickOkCreatedAd} primary large />
<Button has_effect text={localize('OK')} onClick={onClickOkCreatedAd} primary large />
</Modal.Footer>
</Modal>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ const mock_store: DeepPartial<ReturnType<typeof useStores>> = {
my_ads_store: {
payment_method_ids: [],
payment_method_names: [],
setMinCompletionRate: jest.fn(),
setMinJoinDays: jest.fn(),
setShowEditAdForm: jest.fn(),
},
};
Expand Down Expand Up @@ -45,7 +47,7 @@ describe('<CopyAdvertModal />', () => {
});

it('should render CopyAdvertModal', () => {
render(<CopyAdvertModal advert={adverts[0]} />, { wrapper });
render(<CopyAdvertModal advert={adverts[0]} country_list={{}} />, { wrapper });

expect(screen.getByText('Create a similar ad')).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Cancel' }));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@ import { Localize } from 'Components/i18next';
import { useModalManagerContext } from 'Components/modal-manager/modal-manager-context';
import CopyAdvertForm from 'Pages/my-ads/copy-advert-form';
import { useStores } from 'Stores';
import { TAdvert } from 'Types';
import { TAdvert, TCountryListProps } from 'Types';

const CopyAdvertModal = ({ advert }: TAdvert) => {
type TCopyAdvertModalProps = {
advert: TAdvert;
country_list: TCountryListProps;
};

const CopyAdvertModal = ({ advert, country_list }: TCopyAdvertModalProps) => {
const { is_modal_open } = useModalManagerContext();
const { my_ads_store } = useStores();

Expand All @@ -18,7 +23,11 @@ const CopyAdvertModal = ({ advert }: TAdvert) => {
title={<Localize i18n_default_text='Create a similar ad' />}
>
<Modal.Body className='copy-advert-modal'>
<CopyAdvertForm advert={advert} onCancel={() => my_ads_store.setShowEditAdForm(false)} />
<CopyAdvertForm
advert={advert}
country_list={country_list}
onCancel={() => my_ads_store.setShowEditAdForm(false)}
/>
</Modal.Body>
</Modal>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ describe('<ErrorModal/>', () => {
render(<ErrorModal error_message='error message' />, {
wrapper,
});
const ok_button = screen.getByText('Ok');
const ok_button = screen.getByText('OK');
expect(ok_button).toBeInTheDocument();
ok_button.click();
expect(mock_modal_manager_context.hideModal).toHaveBeenCalled();
Expand Down
Loading
Loading