Skip to content
Merged
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
8 changes: 8 additions & 0 deletions packages/components/src/components/modal/modal.scss
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,14 @@
border-bottom: 2px solid var(--general-section-5) !important;
}

&__icon {
margin-right: 1rem;

&:hover {
cursor: pointer;
}
}

&__title {
padding: 1.6rem 2.4rem;
display: flex;
Expand Down
11 changes: 11 additions & 0 deletions packages/components/src/components/modal/modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type TModalElement = {
close_icon_color?: string;
elements_to_ignore?: HTMLElement[];
has_close_icon?: boolean;
has_return_icon?: boolean;
header?: React.ReactNode;
header_background_color?: string;
height?: string;
Expand All @@ -29,6 +30,7 @@ type TModalElement = {
is_vertical_centered?: boolean;
is_vertical_top?: boolean;
onMount?: () => void;
onReturn?: () => void;
onUnmount?: () => void;
portalId?: string;
renderTitle?: () => React.ReactNode;
Expand All @@ -46,6 +48,7 @@ const ModalElement = ({
close_icon_color,
elements_to_ignore,
has_close_icon = true,
has_return_icon = false,
header,
header_background_color,
height,
Expand All @@ -58,6 +61,7 @@ const ModalElement = ({
is_vertical_centered,
is_vertical_top,
onMount,
onReturn,
onUnmount,
portalId,
renderTitle,
Expand Down Expand Up @@ -183,6 +187,9 @@ const ModalElement = ({
[`dc-modal-header__title--${className}`]: className,
})}
>
{has_return_icon && (
<Icon icon='IcArrowLeftBold' onClick={onReturn} className='dc-modal-header__icon' />
)}
{title}
</Text>
)}
Expand Down Expand Up @@ -222,6 +229,7 @@ const Modal = ({
elements_to_ignore,
exit_classname,
has_close_icon = true,
has_return_icon = false,
header,
header_background_color,
height,
Expand All @@ -236,6 +244,7 @@ const Modal = ({
onEntered,
onExited,
onMount,
onReturn,
onUnmount,
portalId,
renderTitle,
Expand Down Expand Up @@ -265,6 +274,7 @@ const Modal = ({
className={className}
close_icon_color={close_icon_color}
should_header_stick_body={should_header_stick_body}
has_return_icon={has_return_icon}
header={header}
header_background_color={header_background_color}
id={id}
Expand All @@ -280,6 +290,7 @@ const Modal = ({
has_close_icon={has_close_icon}
height={height}
onMount={onMount}
onReturn={onReturn}
onUnmount={onUnmount}
portalId={portalId}
renderTitle={renderTitle}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React from 'react';
import { screen, render } from '@testing-library/react';
import { isDesktop } from '@deriv/shared';
import { useModalManagerContext } from 'Components/modal-manager/modal-manager-context';
import { useStores } from 'Stores';
import BuySellModalTitle from '../buy-sell-modal-title';
import userEvent from '@testing-library/user-event';
Expand All @@ -22,74 +21,59 @@ const mock_store: DeepPartial<ReturnType<typeof useStores>> = {
},
};

const mock_modal_manager: DeepPartial<ReturnType<typeof useModalManagerContext>> = {
showModal: jest.fn(),
};

jest.mock('@deriv/shared', () => ({
...jest.requireActual('@deriv/shared'),
isDesktop: jest.fn(() => false),
}));

jest.mock('Components/modal-manager/modal-manager-context', () => ({
...jest.requireActual('Components/modal-manager/modal-manager-context'),
useModalManagerContext: jest.fn(() => mock_modal_manager),
}));

jest.mock('Stores', () => ({
...jest.requireActual('Stores'),
useStores: jest.fn(() => mock_store),
}));

const mock_props = {
is_buy: false,
onReturn: jest.fn(),
};

describe('<BuySellModalTitle />', () => {
it('should display Sell USD if table type is sell', () => {
render(<BuySellModalTitle />);
render(<BuySellModalTitle {...mock_props} />);

expect(screen.getByText('Sell USD')).toBeInTheDocument();
});

it('should display Buy USD if table type is buy', () => {
mock_store.buy_sell_store.table_type = 'buy';

render(<BuySellModalTitle is_buy={true} />);
render(<BuySellModalTitle {...mock_props} is_buy={true} />);

expect(screen.getByText('Buy USD')).toBeInTheDocument();
});

it('should display Add payment method text if should_show_add_payment_method_form is true and isDesktop is false', () => {
mock_store.my_profile_store.should_show_add_payment_method_form = true;

render(<BuySellModalTitle />);
render(<BuySellModalTitle {...mock_props} />);

expect(screen.getByText('Add payment method')).toBeInTheDocument();
});

it('should display Add payment method text with arrow icon if should_show_add_payment_method_form is true and isDesktop is true', () => {
(isDesktop as jest.Mock).mockReturnValue(true);

render(<BuySellModalTitle />);
render(<BuySellModalTitle {...mock_props} />);

expect(screen.getByTestId('dt_buy_sell_modal_back_icon')).toBeInTheDocument();
expect(screen.getByText('Add payment method')).toBeInTheDocument();
});

it('should call setShouldShowAddPaymentMethodForm when clicking the icon, if is_form_modified is false', () => {
render(<BuySellModalTitle />);

const back_icon = screen.getByTestId('dt_buy_sell_modal_back_icon');
userEvent.click(back_icon);

expect(mock_store.my_profile_store.setShouldShowAddPaymentMethodForm).toHaveBeenCalledWith(false);
});

it('should call showModal when clicking the icon, if is_form_modified is true', () => {
mock_store.general_store.is_form_modified = true;

render(<BuySellModalTitle />);
it('should call onReturn if user presses return icon', () => {
render(<BuySellModalTitle {...mock_props} />);

const back_icon = screen.getByTestId('dt_buy_sell_modal_back_icon');
userEvent.click(back_icon);

expect(mock_modal_manager.showModal).toHaveBeenCalledWith({ key: 'CancelAddPaymentMethodModal', props: {} });
expect(mock_props.onReturn).toBeCalled();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@ import { observer } from '@deriv/stores';
import { isDesktop } from '@deriv/shared';
import { useStores } from 'Stores';
import { Localize } from 'Components/i18next';
import { useModalManagerContext } from 'Components/modal-manager/modal-manager-context';

type TBuySellModalTitleProps = {
is_buy?: boolean;
onReturn: () => void;
};

const BuySellModalTitle = ({ is_buy = false }: TBuySellModalTitleProps) => {
const { general_store, buy_sell_store, my_profile_store } = useStores();
const BuySellModalTitle = ({ is_buy = false, onReturn }: TBuySellModalTitleProps) => {
const { buy_sell_store, my_profile_store } = useStores();
const { selected_ad_state } = buy_sell_store;
const { showModal } = useModalManagerContext();

const { account_currency } = selected_ad_state;

Expand All @@ -25,16 +24,7 @@ const BuySellModalTitle = ({ is_buy = false }: TBuySellModalTitleProps) => {
<Icon
icon='IcArrowLeftBold'
data_testid='dt_buy_sell_modal_back_icon'
onClick={() => {
if (general_store.is_form_modified) {
showModal({
key: 'CancelAddPaymentMethodModal',
props: {},
});
} else {
my_profile_store.setShouldShowAddPaymentMethodForm(false);
}
}}
onClick={onReturn}
className='buy-sell-modal-title__icon'
/>
<Localize i18n_default_text='Add payment method' />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useHistory, useLocation } from 'react-router-dom';
import { DesktopWrapper, MobileFullPageModal, MobileWrapper, Modal, ThemedScrollbars } from '@deriv/components';
import { routes } from '@deriv/shared';
import { observer } from '@deriv/stores';
import { localize } from 'Components/i18next';
import { useModalManagerContext } from 'Components/modal-manager/modal-manager-context';
import AddPaymentMethodForm from 'Pages/my-profile/payment-methods/add-payment-method/add-payment-method-form.jsx';
import BuySellForm from 'Pages/buy-sell/buy-sell-form.jsx';
Expand All @@ -18,6 +19,7 @@ const BuySellModal = () => {
const { hideModal, is_modal_open, showModal } = useModalManagerContext();
const { buy_sell_store, general_store, my_profile_store, order_store } = useStores();
const { is_buy_advert, selected_ad_state } = buy_sell_store;
const { account_currency } = selected_ad_state;
const { balance } = general_store;
const { should_show_add_payment_method_form } = my_profile_store;

Expand Down Expand Up @@ -64,6 +66,27 @@ const BuySellModal = () => {
buy_sell_store.setShowAdvertiserPage(false);
};

const getModalTitle = () => {
if (should_show_add_payment_method_form) {
return localize('Add payment method');
}
if (is_buy_advert) {
return localize('Buy {{ currency }}', { currency: account_currency });
}
return localize('Sell {{ currency }}', { currency: account_currency });
};

const onReturn = () => {
if (general_store.is_form_modified) {
showModal({
key: 'CancelAddPaymentMethodModal',
props: {},
});
} else {
my_profile_store.setShouldShowAddPaymentMethodForm(false);
}
};

React.useEffect(() => {
const balance_check =
parseFloat(balance) === 0 || parseFloat(balance) < buy_sell_store.advert?.min_order_amount_limit;
Expand Down Expand Up @@ -110,7 +133,7 @@ const BuySellModal = () => {
is_flex
is_modal_open={is_modal_open}
page_header_className='buy-sell-modal__header'
renderPageHeaderElement={<BuySellModalTitle is_buy={is_buy_advert} />}
renderPageHeaderElement={<BuySellModalTitle is_buy={is_buy_advert} onReturn={onReturn} />}
pageHeaderReturnFn={onCancel}
>
<BuySellModalError
Expand Down Expand Up @@ -144,10 +167,12 @@ const BuySellModal = () => {
className={classNames('buy-sell-modal', {
'buy-sell-modal__form': should_show_add_payment_method_form,
})}
has_return_icon={should_show_add_payment_method_form}
height={is_buy_advert ? 'auto' : '649px'}
is_open={is_modal_open}
onReturn={onReturn}
portalId='modal_root'
title={<BuySellModalTitle is_buy={is_buy_advert} />}
title={getModalTitle()}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I opted to pass a string instead of the component because this is what caused the console error issues. Because we had passed it as a component, the title prop in Modal component is wrapped inside Text which is strange as we had previously returned it as a Text Component.

We could create another prop for specifically passing a component, but the styles are already there, and we don't need to repeat it again and style it ourselves from the child to the parent.

There's no issue with the MobileFullPageModal, so I had left it as it is

toggleModal={onCancel}
width='456px'
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ const AddPaymentMethodForm = ({ should_show_separated_footer = false }) => {
onChange={handleChange}
name={payment_method_field[0]}
required={!!payment_method_field[1].required}
value={field.value || ''}
/>
)}
</Field>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import classNames from 'classnames';
import React from 'react';
import { observer } from 'mobx-react-lite';
import { Field, Form } from 'formik';
import { Button, DesktopWrapper, Input, Loading, Text } from '@deriv/components';
import { useP2PAdvertiserPaymentMethods } from '@deriv/hooks';
import { isDesktop, isMobile } from '@deriv/shared';
import { Localize, localize } from 'Components/i18next';
import { useStores } from 'Stores';
Expand All @@ -13,6 +13,8 @@ import './edit-payment-method-form.scss';

const EditPaymentMethodForm = () => {
const { showModal } = useModalManagerContext();
const { mutation, update } = useP2PAdvertiserPaymentMethods();
const { error: mutation_error, status: mutation_status } = mutation;
const { general_store, my_profile_store } = useStores();
const { payment_method_to_edit } = my_profile_store;

Expand All @@ -21,6 +23,10 @@ const EditPaymentMethodForm = () => {
fields_initial_values[key] = payment_method_to_edit.fields[key].value;
});

const updatePaymentMethod = values => {
update(payment_method_to_edit.id, values);
};

React.useEffect(() => {
return () => {
my_profile_store.setSelectedPaymentMethod('');
Expand All @@ -29,6 +35,15 @@ const EditPaymentMethodForm = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

React.useEffect(() => {
if (mutation_status === 'success') {
my_profile_store.setShouldShowEditPaymentMethodForm(false);
} else if (mutation_status === 'error') {
my_profile_store.setAddPaymentMethodErrorMessage(mutation_error.message);
showModal({ key: 'AddPaymentMethodErrorModal' });
}
}, [mutation_error, mutation_status]);

if (!payment_method_to_edit) {
return <Loading is_fullscreen={false} />;
}
Expand All @@ -38,7 +53,7 @@ const EditPaymentMethodForm = () => {
<ModalForm
enableReinitialize
initialValues={fields_initial_values}
onSubmit={my_profile_store.updatePaymentMethod}
onSubmit={updatePaymentMethod}
validate={my_profile_store.validatePaymentMethodFields}
>
{({ dirty, handleChange, isSubmitting, errors }) => {
Expand Down Expand Up @@ -106,6 +121,7 @@ const EditPaymentMethodForm = () => {
onChange={handleChange}
name={payment_method_key}
required={!!current_field.required}
value={field.value || ''}
/>
);
}}
Expand Down Expand Up @@ -156,4 +172,4 @@ const EditPaymentMethodForm = () => {
);
};

export default observer(EditPaymentMethodForm);
export default EditPaymentMethodForm;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrapping this component with observer caused the console logging error issue, which is caused by updating the payment method, which is then updating any observables inside this component whilst it is unmounting.

We don't need to have observer wrapping this component since it doesn't need to listen to any changes except if there are errors whilst updating the payment method

Loading