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

bahar/extract_copy_to_clipboard_to_hook #6753

Merged
Show file tree
Hide file tree
Changes from 4 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
Original file line number Diff line number Diff line change
Expand Up @@ -306,28 +306,17 @@ describe('<ApiToken/>', () => {

fireEvent.click(copy_btns_1[0]);
expect(screen.queryByText(warning_msg)).not.toBeInTheDocument();
expect(await screen.findByTestId('dt_token_copied_icon')).toBeInTheDocument();

act(() => jest.advanceTimersByTime(2100));
expect(screen.queryByTestId('dt_token_copied_icon')).not.toBeInTheDocument();

fireEvent.click(copy_btns_1[1]);
expect(await screen.findByText(warning_msg)).toBeInTheDocument();

expect(document.execCommand).toHaveBeenCalledTimes(1);

const ok_btn = screen.getByRole('button', { name: /ok/i });
expect(ok_btn).toBeInTheDocument();

fireEvent.click(ok_btn);
expect(await screen.findByTestId('dt_token_copied_icon')).toBeInTheDocument();
const copy_btns_2 = await screen.findAllByTestId('dt_copy_token_icon');
expect(copy_btns_2.length).toBe(1);

act(() => jest.advanceTimersByTime(2100));
expect(screen.queryByTestId('dt_token_copied_icon')).not.toBeInTheDocument();

expect(document.execCommand).toHaveBeenCalledTimes(2);

jest.clearAllMocks();
});
Expand Down
15 changes: 3 additions & 12 deletions packages/account/src/Components/api-token/api-token-clipboard.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useIsMounted } from '@deriv/shared';
import { Button, Icon, Modal, Text, Popover } from '@deriv/components';
import { Button, Icon, Modal, Text, Popover, useCopyToClipboard } from '@deriv/components';
import { localize } from '@deriv/translations';

const WarningNoteBullet = ({ message }) => (
Expand Down Expand Up @@ -29,7 +29,7 @@ const WarningDialogMessage = () => (
);

const ApiTokenClipboard = ({ scopes, text_copy, info_message, success_message, popover_alignment = 'bottom' }) => {
const [is_copied, setIsCopied] = React.useState(false);
const [is_copied, copyToClipboard, setIsCopied] = useCopyToClipboard();
const [is_modal_open, setIsModalOpen] = React.useState(false);
const [is_popover_open, setIsPopoverOpen] = React.useState(false);
const isMounted = useIsMounted();
Expand All @@ -45,27 +45,18 @@ const ApiTokenClipboard = ({ scopes, text_copy, info_message, success_message, p
if (!is_copied) setIsPopoverOpen(false);
};

const copyToClipboard = text => {
const textField = document.createElement('textarea');
textField.innerText = text;
document.body.appendChild(textField);
textField.select();
document.execCommand('copy');
textField.remove();
};

/* two timeouts help to prevent popup window blinking.
without early hiding the popup we will see shortly the description message like during hovering.
this bug appears when popup is handled outside like here
*/
const onClick = () => {
setIsModalOpen(false);
copyToClipboard(text_copy);
setIsCopied(true);
setIsPopoverOpen(true);
timeout_clipboard = setTimeout(() => {
if (isMounted()) {
setIsPopoverOpen(false);
setIsCopied(false);
}
}, 1900);
timeout_clipboard_2 = setTimeout(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ describe('<OnRampProviderPopup />', () => {
api_error: '',
deposit_address: 'tb1qhux20f7h42ya9nqdntl6r9p7p264a2ct8t3n6p',
is_deposit_address_loading: false,
is_deposit_address_popover_open: false,
is_requesting_widget_html: false,
selected_provider: {
name: 'Changelly',
Expand All @@ -29,7 +28,6 @@ describe('<OnRampProviderPopup />', () => {
should_show_widget: false,
widget_error: '',
widget_html: 'Widget HTML',
onClickCopyDepositAddress: jest.fn(),
onClickDisclaimerContinue: jest.fn(),
onClickGoToDepositPage: jest.fn(),
setIsOnRampModalOpen: jest.fn(),
Expand Down Expand Up @@ -112,28 +110,13 @@ describe('<OnRampProviderPopup />', () => {
expect(screen.getByRole('button', { name: 'Continue' })).toBeInTheDocument();
});

it('should trigger onClick callback when the user clicks on copy icon', () => {
render(<OnRampProviderPopup {...props} />);

const copy_icon = screen.getByTestId('dti_deposit_address_icon');
fireEvent.click(copy_icon);

expect(props.onClickCopyDepositAddress).toHaveBeenCalledTimes(1);
});

it('should trigger onFocus method when the user clicks on deposit address field', () => {
render(<OnRampProviderPopup {...props} />);

const deposit_address_input = screen.getByRole('textbox');
expect(fireEvent.focus(deposit_address_input)).toBeTruthy();
});

it('should show "Copied!" message', () => {
render(<OnRampProviderPopup {...props} is_deposit_address_popover_open />);

expect(screen.getByText('Copied!')).toBeInTheDocument();
});

it('should trigger onClick calbacks when the user clicks on "Cancel" and "Continue" buttons', () => {
const { rerender } = render(<OnRampProviderPopup {...props} />);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import classNames from 'classnames';
import React from 'react';
import { Button, HintBox, Icon, Loading, Popover, Text } from '@deriv/components';
import { Button, HintBox, Icon, Loading, Popover, Text, useCopyToClipboard } from '@deriv/components';
import { getKebabCase, website_name, isMobile } from '@deriv/shared';
import { localize, Localize } from '@deriv/translations';
import { connect } from 'Stores/connect';
Expand All @@ -11,13 +11,10 @@ type TOnRampProviderPopupProps = {
deposit_address: string;
is_dark_mode_on: TUiStore['is_dark_mode_on'];
is_deposit_address_loading: boolean;
is_deposit_address_popover_open: boolean;
is_requesting_widget_html: boolean;
onClickCopyDepositAddress: () => void;
onClickDisclaimerContinue: () => void;
onClickGoToDepositPage: () => void;
selected_provider: TProviderDetails;
setDepositAddressRef: (ref: HTMLDivElement | null) => void;
setIsOnRampModalOpen: (boolean: boolean) => void;
should_show_dialog: boolean;
should_show_widget: boolean;
Expand All @@ -30,20 +27,31 @@ const OnRampProviderPopup = ({
deposit_address,
is_dark_mode_on,
is_deposit_address_loading,
is_deposit_address_popover_open,
is_requesting_widget_html,
onClickCopyDepositAddress,
onClickDisclaimerContinue,
onClickGoToDepositPage,
selected_provider,
setDepositAddressRef,
setIsOnRampModalOpen,
should_show_dialog,
should_show_widget,
widget_error,
widget_html,
}: TOnRampProviderPopupProps) => {
const el_onramp_widget_container_ref = React.useRef(null);
const [is_copied, copyToClipboard, setIsCopied] = useCopyToClipboard();
let timeout_clipboard: ReturnType<typeof setTimeout>;

const onClickCopyDepositAddress = () => {
copyToClipboard(deposit_address);

timeout_clipboard = setTimeout(() => {
setIsCopied(false);
}, 500);
};

React.useEffect(() => {
return () => clearTimeout(timeout_clipboard);
}, []);

// JS executed after "on-ramp__widget-container" has been added to the DOM.
// Used for providers that require JS to be executed for inclusion of their widget.
Expand Down Expand Up @@ -111,17 +119,11 @@ const OnRampProviderPopup = ({
<Localize i18n_default_text="Please copy the crypto address you see below. You'll need it to deposit your cryptocurrency." />
</Text>
<div className='on-ramp__popup-deposit-address'>
<Popover
zIndex={9998}
alignment='right'
message={localize('Copied!')}
is_open={is_deposit_address_popover_open}
>
<Popover zIndex={9998} alignment='right' message={localize('Copied!')} is_open={is_copied}>
<input
className={classNames('on-ramp__popup-deposit-address-text', {
'on-ramp__popup-deposit-address-text--dark': is_dark_mode_on,
})}
ref={setDepositAddressRef}
defaultValue={deposit_address}
disabled
onFocus={e => e.preventDefault()}
Expand Down Expand Up @@ -197,13 +199,10 @@ export default connect(({ modules, ui }: TRootStore) => ({
deposit_address: modules.cashier.onramp.deposit_address,
is_dark_mode_on: ui.is_dark_mode_on,
is_deposit_address_loading: modules.cashier.onramp.is_deposit_address_loading,
is_deposit_address_popover_open: modules.cashier.onramp.is_deposit_address_popover_open,
is_requesting_widget_html: modules.cashier.onramp.is_requesting_widget_html,
onClickCopyDepositAddress: modules.cashier.onramp.onClickCopyDepositAddress,
onClickDisclaimerContinue: modules.cashier.onramp.onClickDisclaimerContinue,
onClickGoToDepositPage: modules.cashier.onramp.onClickGoToDepositPage,
selected_provider: modules.cashier.onramp.selected_provider,
setDepositAddressRef: modules.cashier.onramp.setDepositAddressRef,
setIsOnRampModalOpen: modules.cashier.onramp.setIsOnRampModalOpen,
should_show_dialog: modules.cashier.onramp.should_show_dialog,
should_show_widget: modules.cashier.onramp.should_show_widget,
Expand Down
42 changes: 0 additions & 42 deletions packages/cashier/src/stores/__tests__/on-ramp-store.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,35 +164,6 @@ describe('OnRampStore', () => {
expect(spyDisposeGetWidgetHtmlReaction).toBeCalledTimes(1);
});

it('should show and hide deposit address popover when deposit address is copied', async () => {
jest.useFakeTimers();
jest.spyOn(document, 'createRange').mockImplementation(() => ({
selectNodeContents: jest.fn(),
}));
jest.spyOn(window, 'window', 'get').mockImplementation(() => ({
getSelection: () => ({
addRange: jest.fn(),
removeAllRanges: jest.fn(),
}),
}));
Object.assign(navigator, {
clipboard: {
writeText: jest.fn(() => Promise.resolve()),
},
});
const spySetIsDepositAddressPopoverOpen = jest.spyOn(onramp_store, 'setIsDepositAddressPopoverOpen');
onramp_store.onClickCopyDepositAddress();

expect(await spySetIsDepositAddressPopoverOpen).toHaveBeenCalledWith(true);
jest.runAllTimers();
expect(setTimeout).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 500);
expect(await spySetIsDepositAddressPopoverOpen).toHaveBeenCalledWith(false);

jest.restoreAllMocks();
jest.useRealTimers();
});

it('should show widget when onClickDisclaimerContinue method was called', () => {
onramp_store.onClickDisclaimerContinue();

Expand Down Expand Up @@ -277,7 +248,6 @@ describe('OnRampStore', () => {

expect(onramp_store.api_error).toBeNull();
expect(onramp_store.deposit_address).toBeNull();
expect(onramp_store.deposit_address_ref).toBeNull();
expect(onramp_store.is_deposit_address_loading).toBeTruthy();
expect(onramp_store.selected_provider).toBeNull();
expect(onramp_store.should_show_widget).toBeFalsy();
Expand All @@ -303,24 +273,12 @@ describe('OnRampStore', () => {
expect(onramp_store.deposit_address).toBe('deposit address');
});

it('should set deposit address ref', () => {
onramp_store.setDepositAddressRef('deposit address ref');

expect(onramp_store.deposit_address_ref).toBe('deposit address ref');
});

it('should change value of the variable is_deposit_address_loading', () => {
onramp_store.setIsDepositAddressLoading(true);

expect(onramp_store.is_deposit_address_loading).toBeTruthy();
});

it('should change value of the variable is_deposit_address_popover_open', () => {
onramp_store.setIsDepositAddressPopoverOpen(true);

expect(onramp_store.is_deposit_address_popover_open).toBeTruthy();
});

it('should change value of the variable is_onramp_modal_open', () => {
onramp_store.setIsOnRampModalOpen(true);

Expand Down
29 changes: 0 additions & 29 deletions packages/cashier/src/stores/on-ramp-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ export default class OnRampStore extends BaseStore {
@observable api_error = null;
@observable deposit_address = null;
@observable is_deposit_address_loading = true;
@observable is_deposit_address_popover_open = false;
@observable is_onramp_modal_open = false;
@observable is_requesting_widget_html = false;
@observable.shallow onramp_providers = [];
Expand All @@ -17,8 +16,6 @@ export default class OnRampStore extends BaseStore {
@observable widget_error = null;
@observable widget_html = null;

deposit_address_ref = null;

constructor({ WS, root_store }) {
super({ root_store });
this.WS = WS;
Expand Down Expand Up @@ -149,21 +146,6 @@ export default class OnRampStore extends BaseStore {
}
}

@action.bound
onClickCopyDepositAddress() {
const range = document.createRange();
range.selectNodeContents(this.deposit_address_ref);

const selections = window.getSelection();
selections.removeAllRanges();
selections.addRange(range);

navigator.clipboard.writeText(this.deposit_address).then(() => {
this.setIsDepositAddressPopoverOpen(true);
setTimeout(() => this.setIsDepositAddressPopoverOpen(false), 500);
});
}

@action.bound
onClickDisclaimerContinue() {
this.setShouldShowWidget(true);
Expand Down Expand Up @@ -220,7 +202,6 @@ export default class OnRampStore extends BaseStore {
resetPopup() {
this.setApiError(null);
this.setDepositAddress(null);
this.setDepositAddressRef(null);
this.setIsDepositAddressLoading(true);
this.setSelectedProvider(null);
this.setShouldShowWidget(false);
Expand All @@ -243,21 +224,11 @@ export default class OnRampStore extends BaseStore {
this.deposit_address = deposit_address;
}

@action.bound
setDepositAddressRef(ref) {
this.deposit_address_ref = ref;
}

@action.bound
setIsDepositAddressLoading(is_loading) {
this.is_deposit_address_loading = is_loading;
}

@action.bound
setIsDepositAddressPopoverOpen(is_open) {
this.is_deposit_address_popover_open = is_open;
}

@action.bound
setIsOnRampModalOpen(is_open) {
this.is_onramp_modal_open = is_open;
Expand Down
10 changes: 1 addition & 9 deletions packages/cashier/src/utils/utility.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,6 @@ class PromiseClass {
}
}

const copyToClipboard = text => {
const textField = document.createElement('textarea');
textField.innerText = text;
document.body.appendChild(textField);
textField.select();
document.execCommand('copy');
textField.remove();
};
// TODO: [duplicate_code] - Move this to shared package
// eu countries to support
const eu_countries = [
Expand Down Expand Up @@ -104,4 +96,4 @@ const getAccountText = account => {
return account_text;
};

export { copyToClipboard, createElement, getAccountText, getStaticHash, isEuCountry, PromiseClass, template };
export { createElement, getAccountText, getStaticHash, isEuCountry, PromiseClass, template };
Loading