diff --git a/packages/account/src/Components/account-limits/account-limits.jsx b/packages/account/src/Components/account-limits/account-limits.jsx index 9f5b1f4bb2ed..e5917db83b93 100644 --- a/packages/account/src/Components/account-limits/account-limits.jsx +++ b/packages/account/src/Components/account-limits/account-limits.jsx @@ -156,7 +156,12 @@ const AccountLimits = ({ - {formatMoney(currency, account_balance, true)} + {/* null or 0 are expected form BE when max balance limit is not set */} + {account_balance ? ( + formatMoney(currency, account_balance, true) + ) : ( + + )} diff --git a/packages/account/src/Components/api-token/__tests__/api-token.spec.js b/packages/account/src/Components/api-token/__tests__/api-token.spec.js index 28ef8828a07a..546d0321334f 100644 --- a/packages/account/src/Components/api-token/__tests__/api-token.spec.js +++ b/packages/account/src/Components/api-token/__tests__/api-token.spec.js @@ -306,7 +306,6 @@ describe('', () => { 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(); @@ -314,20 +313,10 @@ describe('', () => { 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(); }); diff --git a/packages/account/src/Components/api-token/api-token-clipboard.jsx b/packages/account/src/Components/api-token/api-token-clipboard.jsx index f88871ce12ab..94788c0781b4 100644 --- a/packages/account/src/Components/api-token/api-token-clipboard.jsx +++ b/packages/account/src/Components/api-token/api-token-clipboard.jsx @@ -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 }) => ( @@ -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(); @@ -45,15 +45,6 @@ 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 @@ -61,11 +52,11 @@ const ApiTokenClipboard = ({ scopes, text_copy, info_message, success_message, p 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(() => { diff --git a/packages/cashier/src/pages/on-ramp/on-ramp-provider-popup/__tests__/on-ramp-provider-popup.spec.tsx b/packages/cashier/src/pages/on-ramp/on-ramp-provider-popup/__tests__/on-ramp-provider-popup.spec.tsx index bea6c0561955..b0dfc0ba9186 100644 --- a/packages/cashier/src/pages/on-ramp/on-ramp-provider-popup/__tests__/on-ramp-provider-popup.spec.tsx +++ b/packages/cashier/src/pages/on-ramp/on-ramp-provider-popup/__tests__/on-ramp-provider-popup.spec.tsx @@ -18,7 +18,6 @@ describe('', () => { 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', @@ -29,7 +28,6 @@ describe('', () => { should_show_widget: false, widget_error: '', widget_html: 'Widget HTML', - onClickCopyDepositAddress: jest.fn(), onClickDisclaimerContinue: jest.fn(), onClickGoToDepositPage: jest.fn(), setIsOnRampModalOpen: jest.fn(), @@ -112,15 +110,6 @@ describe('', () => { expect(screen.getByRole('button', { name: 'Continue' })).toBeInTheDocument(); }); - it('should trigger onClick callback when the user clicks on copy icon', () => { - render(); - - 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(); @@ -128,12 +117,6 @@ describe('', () => { expect(fireEvent.focus(deposit_address_input)).toBeTruthy(); }); - it('should show "Copied!" message', () => { - render(); - - expect(screen.getByText('Copied!')).toBeInTheDocument(); - }); - it('should trigger onClick calbacks when the user clicks on "Cancel" and "Continue" buttons', () => { const { rerender } = render(); diff --git a/packages/cashier/src/pages/on-ramp/on-ramp-provider-popup/on-ramp-provider-popup.tsx b/packages/cashier/src/pages/on-ramp/on-ramp-provider-popup/on-ramp-provider-popup.tsx index 9965b588f4d7..3d27c6e7953d 100644 --- a/packages/cashier/src/pages/on-ramp/on-ramp-provider-popup/on-ramp-provider-popup.tsx +++ b/packages/cashier/src/pages/on-ramp/on-ramp-provider-popup/on-ramp-provider-popup.tsx @@ -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'; @@ -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; @@ -30,13 +27,10 @@ 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, @@ -44,6 +38,20 @@ const OnRampProviderPopup = ({ widget_html, }: TOnRampProviderPopupProps) => { const el_onramp_widget_container_ref = React.useRef(null); + const [is_copied, copyToClipboard, setIsCopied] = useCopyToClipboard(); + let timeout_clipboard: ReturnType; + + 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. @@ -111,17 +119,11 @@ const OnRampProviderPopup = ({
- + e.preventDefault()} @@ -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, diff --git a/packages/cashier/src/stores/__tests__/on-ramp-store.spec.js b/packages/cashier/src/stores/__tests__/on-ramp-store.spec.js index 6aaeaf2132e0..2130813bd62b 100644 --- a/packages/cashier/src/stores/__tests__/on-ramp-store.spec.js +++ b/packages/cashier/src/stores/__tests__/on-ramp-store.spec.js @@ -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(); @@ -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(); @@ -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); diff --git a/packages/cashier/src/stores/on-ramp-store.js b/packages/cashier/src/stores/on-ramp-store.js index f9d6d8375060..d08446fb9884 100644 --- a/packages/cashier/src/stores/on-ramp-store.js +++ b/packages/cashier/src/stores/on-ramp-store.js @@ -8,7 +8,6 @@ export default class OnRampStore extends BaseStore { api_error = null; deposit_address = null; is_deposit_address_loading = true; - is_deposit_address_popover_open = false; is_onramp_modal_open = false; is_requesting_widget_html = false; onramp_providers = []; @@ -17,8 +16,6 @@ export default class OnRampStore extends BaseStore { widget_error = null; widget_html = null; - deposit_address_ref = null; - constructor({ WS, root_store }) { super({ root_store }); @@ -26,7 +23,6 @@ export default class OnRampStore extends BaseStore { api_error: observable, deposit_address: observable, is_deposit_address_loading: observable, - is_deposit_address_popover_open: observable, is_onramp_modal_open: observable, is_requesting_widget_html: observable, onramp_providers: observable.shallow, @@ -40,7 +36,6 @@ export default class OnRampStore extends BaseStore { should_show_dialog: computed, onMountOnramp: action.bound, onUnmountOnramp: action.bound, - onClickCopyDepositAddress: action.bound, onClickDisclaimerContinue: action.bound, onClickGoToDepositPage: action.bound, pollApiForDepositAddress: action.bound, @@ -48,9 +43,7 @@ export default class OnRampStore extends BaseStore { setApiError: action.bound, setCopyIconRef: action.bound, setDepositAddress: action.bound, - setDepositAddressRef: action.bound, setIsDepositAddressLoading: action.bound, - setIsDepositAddressPopoverOpen: action.bound, setIsOnRampModalOpen: action.bound, setIsRequestingWidgetHtml: action.bound, setSelectedProvider: action.bound, @@ -182,20 +175,6 @@ export default class OnRampStore extends BaseStore { } } - 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); - }); - } - onClickDisclaimerContinue() { this.setShouldShowWidget(true); } @@ -248,7 +227,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); @@ -268,18 +246,10 @@ export default class OnRampStore extends BaseStore { this.deposit_address = deposit_address; } - setDepositAddressRef(ref) { - this.deposit_address_ref = ref; - } - setIsDepositAddressLoading(is_loading) { this.is_deposit_address_loading = is_loading; } - setIsDepositAddressPopoverOpen(is_open) { - this.is_deposit_address_popover_open = is_open; - } - setIsOnRampModalOpen(is_open) { this.is_onramp_modal_open = is_open; } diff --git a/packages/cashier/src/utils/utility.js b/packages/cashier/src/utils/utility.js index 4ce3d73bd19c..df9c28765299 100644 --- a/packages/cashier/src/utils/utility.js +++ b/packages/cashier/src/utils/utility.js @@ -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 = [ @@ -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 }; diff --git a/packages/cfd/src/Components/cfd-account-card.tsx b/packages/cfd/src/Components/cfd-account-card.tsx index 477e4a303c66..dbd392075556 100644 --- a/packages/cfd/src/Components/cfd-account-card.tsx +++ b/packages/cfd/src/Components/cfd-account-card.tsx @@ -175,17 +175,18 @@ const CFDAccountCardComponent = ({ is_eu, isEligibleForMoreDemoMt5Svg, isEligibleForMoreRealMt5, - platform, - title, - type, - specs, - onSelectAccount, onClickFund, onPasswordManager, + onSelectAccount, + platform, + setIsAcuityModalOpen, + setMT5TradeAccount, + specs, + title, toggleAccountsDialog, toggleMT5TradeModal, toggleShouldShowRealAccountsList, - setMT5TradeAccount, + type, }: TCFDAccountCard) => { const existing_data = existing_accounts_data?.length ? existing_accounts_data?.[0] : existing_accounts_data; const all_svg_acc: DetailsOfEachMT5Loginid[] = []; @@ -329,6 +330,22 @@ const CFDAccountCardComponent = ({ )}
+ {platform === CFD_PLATFORMS.MT5 && type.type === 'financial' && !isMobile() && is_logged_in && ( + + )} {existing_data &&
}
@@ -676,10 +693,11 @@ const CFDAccountCardComponent = ({ ); }; -const CFDAccountCard = connect(({ modules: { cfd }, client }: RootStore) => ({ +const CFDAccountCard = connect(({ modules: { cfd }, client, ui }: RootStore) => ({ dxtrade_tokens: cfd.dxtrade_tokens, isEligibleForMoreDemoMt5Svg: client.isEligibleForMoreDemoMt5Svg, isEligibleForMoreRealMt5: client.isEligibleForMoreRealMt5, + setIsAcuityModalOpen: ui.setIsAcuityModalOpen, setMT5TradeAccount: cfd.setMT5TradeAccount, }))(CFDAccountCardComponent); diff --git a/packages/cfd/src/Components/cfd-mt5-demo-account-display.tsx b/packages/cfd/src/Components/cfd-mt5-demo-account-display.tsx index 8f9bc6c06437..061c7cb8f0ea 100644 --- a/packages/cfd/src/Components/cfd-mt5-demo-account-display.tsx +++ b/packages/cfd/src/Components/cfd-mt5-demo-account-display.tsx @@ -107,7 +107,7 @@ const CFDMT5DemoAccountDisplay = ({ }, [is_logged_in, is_eu, is_eu_country, residence, platform]); return ( -
+ {is_loading ? ( ) : ( @@ -176,7 +176,7 @@ const CFDMT5DemoAccountDisplay = ({ )}
)} -
+ ); }; diff --git a/packages/cfd/src/Components/cfd-real-account-display.tsx b/packages/cfd/src/Components/cfd-real-account-display.tsx index 527fa67192f3..958595b13ad9 100644 --- a/packages/cfd/src/Components/cfd-real-account-display.tsx +++ b/packages/cfd/src/Components/cfd-real-account-display.tsx @@ -61,6 +61,7 @@ type TCFDRealAccountDisplayProps = { account_status?: object; openDerivRealAccountNeededModal: () => void; should_enable_add_button?: boolean; + setIsAcuityModalOpen: (value: boolean) => void; }; const CFDRealAccountDisplay = ({ @@ -88,6 +89,7 @@ const CFDRealAccountDisplay = ({ residence, openDerivRealAccountNeededModal, should_enable_add_button, + setIsAcuityModalOpen, }: TCFDRealAccountDisplayProps) => { const is_eu_user = (is_logged_in && is_eu) || (!is_logged_in && is_eu_country); @@ -231,6 +233,7 @@ const CFDRealAccountDisplay = ({ toggleShouldShowRealAccountsList={toggleShouldShowRealAccountsList} toggleAccountsDialog={toggleAccountsDialog} toggleMT5TradeModal={toggleMT5TradeModal} + setIsAcuityModalOpen={setIsAcuityModalOpen} /> ); diff --git a/packages/cfd/src/Components/props.types.ts b/packages/cfd/src/Components/props.types.ts index d2f0d6d443cb..ec638a4951f2 100644 --- a/packages/cfd/src/Components/props.types.ts +++ b/packages/cfd/src/Components/props.types.ts @@ -108,6 +108,7 @@ export type TCFDAccountCard = { toggleMT5TradeModal: (arg?: boolean) => void; toggleShouldShowRealAccountsList?: (arg?: boolean) => void; setMT5TradeAccount: (arg: any) => void; + setIsAcuityModalOpen: (value: boolean) => void; }; export type TTradingPlatformAccounts = { diff --git a/packages/cfd/src/Containers/__tests__/cfd-dashboard.spec.tsx b/packages/cfd/src/Containers/__tests__/cfd-dashboard.spec.tsx index 377092a28232..88e110edcb48 100644 --- a/packages/cfd/src/Containers/__tests__/cfd-dashboard.spec.tsx +++ b/packages/cfd/src/Containers/__tests__/cfd-dashboard.spec.tsx @@ -142,6 +142,7 @@ describe('', () => { onUnmount: jest.fn(), openAccountNeededModal: jest.fn(), openDerivRealAccountNeededModal: jest.fn(), + refreshNotifications: jest.fn(), openPasswordModal: jest.fn(), openTopUpModal: jest.fn(), platform: CFD_PLATFORMS.MT5, @@ -179,6 +180,7 @@ describe('', () => { ], setAccountType: jest.fn(), setCFDPasswordResetModal: jest.fn(), + setIsAcuityModalOpen: jest.fn(), setCurrentAccount: jest.fn(), standpoint: { financial_company: 'svg', diff --git a/packages/cfd/src/Containers/cfd-dashboard.tsx b/packages/cfd/src/Containers/cfd-dashboard.tsx index 29bded5b40d6..e3e4287d3161 100644 --- a/packages/cfd/src/Containers/cfd-dashboard.tsx +++ b/packages/cfd/src/Containers/cfd-dashboard.tsx @@ -173,6 +173,8 @@ export type TCFDDashboardProps = RouteComponentProps & { getRealSyntheticAccountsExistingData: DetailsOfEachMT5Loginid[] | undefined ) => void; openDerivRealAccountNeededModal: () => void; + setIsAcuityModalOpen: (value: boolean) => void; + refreshNotifications: () => void; }; const CFDDashboard = (props: TCFDDashboardProps) => { @@ -200,6 +202,7 @@ const CFDDashboard = (props: TCFDDashboardProps) => { React.useEffect(() => { updateActiveIndex(getIndexToSet()); openResetPassword(); + props.refreshNotifications(); props.onMount(); return () => { props.onUnmount(); @@ -393,6 +396,7 @@ const CFDDashboard = (props: TCFDDashboardProps) => { getRealSyntheticAccountsExistingData, getRealFinancialAccountsExistingData, openDerivRealAccountNeededModal, + setIsAcuityModalOpen, } = props; const should_show_missing_real_account = @@ -524,6 +528,7 @@ const CFDDashboard = (props: TCFDDashboardProps) => { residence={residence} openDerivRealAccountNeededModal={openDerivRealAccountNeededModal} should_enable_add_button={should_enable_add_button} + setIsAcuityModalOpen={setIsAcuityModalOpen} />
@@ -668,7 +673,7 @@ const CFDDashboard = (props: TCFDDashboardProps) => { }; export default withRouter( - connect(({ client, modules, ui }: RootStore) => ({ + connect(({ client, modules, notifications, ui }: RootStore) => ({ beginRealSignupForMt5: modules.cfd.beginRealSignupForMt5, checkShouldOpenAccount: modules.cfd.checkShouldOpenAccount, country: client.account_settings.residence, @@ -716,6 +721,7 @@ export default withRouter( NotificationMessages: ui.notification_messages_ui, onMount: modules.cfd.onMount, onUnmount: modules.cfd.onUnmount, + refreshNotifications: notifications.refreshNotifications, toggleAccountsDialog: ui.toggleAccountsDialog, toggleShouldShowRealAccountsList: ui.toggleShouldShowRealAccountsList, upgradeable_landing_companies: client.upgradeable_landing_companies, @@ -728,5 +734,6 @@ export default withRouter( dxtrade_verification_code: client.verification_code.trading_platform_dxtrade_password_reset, mt5_status_server: client.website_status.mt5_status, openDerivRealAccountNeededModal: ui.openDerivRealAccountNeededModal, + setIsAcuityModalOpen: ui.setIsAcuityModalOpen, }))(CFDDashboard) ); diff --git a/packages/cfd/src/sass/cfd-dashboard.scss b/packages/cfd/src/sass/cfd-dashboard.scss index a384c2973a68..81fa7740f26f 100644 --- a/packages/cfd/src/sass/cfd-dashboard.scss +++ b/packages/cfd/src/sass/cfd-dashboard.scss @@ -120,6 +120,41 @@ } } + &__social-banner { + width: 100%; + background-color: var(--general-section-1); + opacity: 95%; + top: 0; + position: absolute; + z-index: 1; + + &--close-icon { + position: absolute; + top: 1.6rem; + right: 1.6rem; + } + + &--wrapper { + display: flex; + flex-direction: row; + gap: 2.4rem; + align-items: center; + justify-content: center; + padding: 0.8rem 0; + margin-inline: 2.4rem; + + &__image { + width: 16rem; + height: 12rem; + } + + &__button { + float: right; + padding: 0.8rem; + } + } + } + &__download-container { border-top: 2px solid var(--general-section-1); padding-top: 2rem; @@ -440,7 +475,7 @@ grid-gap: 2.4rem; justify-content: center; padding-top: 2.4em; - height: 100%; + height: fit-content; @include mobile { display: flex; @@ -451,12 +486,6 @@ } } -.cfd-demo-accounts-display .cfd-account-card__wrapper { - @include desktop { - height: auto; - } -} - .cfd-jurisdiction-card--synthetic, .cfd-jurisdiction-card--financial { border: solid 1px var(--border-normal); @@ -698,11 +727,10 @@ display: grid; grid-template-rows: 1fr; justify-content: center; - height: 100%; + height: fit-content; @include mobile { grid-template-rows: 1fr; - height: fit-content; &:not(:last-child) { margin-bottom: 2.4rem; @@ -1057,6 +1085,22 @@ } } } + + &__acuity-banner { + background: var(--general-section-1); + height: 3.4rem; + border-radius: 0.8rem; + margin: 1.6rem; + + &--wrapper { + display: flex; + flex-direction: row; + align-items: center; + gap: 1.6rem; + padding: 0.8rem 2.65rem; + } + } + &__divider { width: calc(100% - 3.2rem); margin-left: 1.6rem; diff --git a/packages/components/src/components/clipboard/clipboard.jsx b/packages/components/src/components/clipboard/clipboard.jsx index 4db50ba5d1bc..b164beedc433 100644 --- a/packages/components/src/components/clipboard/clipboard.jsx +++ b/packages/components/src/components/clipboard/clipboard.jsx @@ -4,6 +4,7 @@ import PropTypes from 'prop-types'; import { useIsMounted } from '@deriv/shared'; import Popover from '../popover'; import Icon from '../icon'; +import { useCopyToClipboard } from '../../hooks'; const Clipboard = ({ text_copy, @@ -15,24 +16,19 @@ const Clipboard = ({ popover_props = {}, popoverAlignment = 'bottom', }) => { - const [is_copied, setIsCopied] = React.useState(false); + const [is_copied, copyToClipboard, setIsCopied] = useCopyToClipboard(); const isMounted = useIsMounted(); let timeout_clipboard = null; - const copyToClipboard = async text => { - await navigator.clipboard.writeText(text); - }; - const onClick = event => { - copyToClipboard(text_copy).then(() => { - setIsCopied(true); - timeout_clipboard = setTimeout(() => { - if (isMounted()) { - setIsCopied(false); - } - }, 2000); - event.stopPropagation(); - }); + copyToClipboard(text_copy); + + timeout_clipboard = setTimeout(() => { + if (isMounted()) { + setIsCopied(false); + } + }, 2000); + event.stopPropagation(); }; React.useEffect(() => { @@ -40,31 +36,29 @@ const Clipboard = ({ }, [timeout_clipboard]); return ( - <> - - {is_copied && ( - - )} - {!is_copied && ( - - )} - - + + {is_copied && ( + + )} + {!is_copied && ( + + )} + ); }; Clipboard.propTypes = { diff --git a/packages/components/src/components/icon/common/ic-close-dark.svg b/packages/components/src/components/icon/common/ic-close-dark.svg new file mode 100644 index 000000000000..a16cf2171fba --- /dev/null +++ b/packages/components/src/components/icon/common/ic-close-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/src/components/icon/icons.js b/packages/components/src/components/icon/icons.js index de1b602f4513..b5771da99a30 100644 --- a/packages/components/src/components/icon/icons.js +++ b/packages/components/src/components/icon/icons.js @@ -273,6 +273,7 @@ import './common/ic-clock-outline.svg'; import './common/ic-clock.svg'; import './common/ic-close-circle-red.svg'; import './common/ic-close-circle.svg'; +import './common/ic-close-dark.svg'; import './common/ic-close-light.svg'; import './common/ic-cloud-upload.svg'; import './common/ic-confirm-details.svg'; @@ -537,6 +538,7 @@ import './flag/ic-flag-uk.svg'; import './flag/ic-flag-vi.svg'; import './flag/ic-flag-zh-cn.svg'; import './flag/ic-flag-zh-tw.svg'; +import './mt5/ic-mt5-acuity.svg'; import './mt5/ic-mt5-cfd-platform.svg'; import './mt5/ic-mt5-cfds.svg'; import './mt5/ic-mt5-derived.svg'; diff --git a/packages/components/src/components/icon/mt5/ic-mt5-acuity.svg b/packages/components/src/components/icon/mt5/ic-mt5-acuity.svg new file mode 100644 index 000000000000..d0ee8977d8a0 --- /dev/null +++ b/packages/components/src/components/icon/mt5/ic-mt5-acuity.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/src/hooks/index.ts b/packages/components/src/hooks/index.ts index 6c7a7a434aa5..3c844f468bbc 100644 --- a/packages/components/src/hooks/index.ts +++ b/packages/components/src/hooks/index.ts @@ -9,3 +9,4 @@ export * from './use-deep-effect'; export * from './use-state-callback'; export * from './use-constructor'; export * from './use-safe-state'; +export * from './use-copy-to-clipboard'; diff --git a/packages/components/src/hooks/use-copy-to-clipboard.ts b/packages/components/src/hooks/use-copy-to-clipboard.ts new file mode 100644 index 000000000000..d058d6616af0 --- /dev/null +++ b/packages/components/src/hooks/use-copy-to-clipboard.ts @@ -0,0 +1,26 @@ +import React from 'react'; + +type IsCopied = boolean; +type CopyFn = (text: string) => Promise; +type IsCopyFn = (flag: boolean) => void; + +export const useCopyToClipboard = (): [IsCopied, CopyFn, IsCopyFn] => { + const [is_copied, setIsCopied] = React.useState(false); + + const copyToClipboard = async (text: string) => { + if (!navigator?.clipboard) { + return false; + } + + try { + await navigator.clipboard.writeText(text); + setIsCopied(true); + return true; + } catch (error) { + setIsCopied(false); + return false; + } + }; + + return [is_copied, copyToClipboard, setIsCopied]; +}; diff --git a/packages/components/stories/icon/icons.js b/packages/components/stories/icon/icons.js index 909eeb8ed34e..197b03b8388e 100644 --- a/packages/components/stories/icon/icons.js +++ b/packages/components/stories/icon/icons.js @@ -281,6 +281,7 @@ export const icons = 'IcClock', 'IcCloseCircleRed', 'IcCloseCircle', + 'IcCloseDark', 'IcCloseLight', 'IcCloudUpload', 'IcConfirmDetails', @@ -555,6 +556,7 @@ export const icons = 'IcFlagZhTw', ], 'mt5': [ + 'IcMt5Acuity', 'IcMt5CfdPlatform', 'IcMt5Cfds', 'IcMt5Derived', diff --git a/packages/core/src/App/Components/Elements/NotificationMessage/notification-banner.jsx b/packages/core/src/App/Components/Elements/NotificationMessage/notification-banner.jsx index a3a97ebbddc0..99249727d8ac 100644 --- a/packages/core/src/App/Components/Elements/NotificationMessage/notification-banner.jsx +++ b/packages/core/src/App/Components/Elements/NotificationMessage/notification-banner.jsx @@ -4,7 +4,17 @@ import classNames from 'classnames'; import { isMobile } from '@deriv/shared'; import { Button, Icon, Text } from '@deriv/components'; -const NotificationBanner = ({ className, header, message, primary_btn, secondary_btn, img_src, img_alt, onClose }) => ( +const NotificationBanner = ({ + className, + header, + message, + primary_btn, + secondary_btn, + img_src, + img_alt, + onClose, + icon, +}) => (
{img_alt} - +
); @@ -52,6 +62,7 @@ NotificationBanner.propTypes = { text: PropTypes.string, onClick: PropTypes.func, }), + icon: PropTypes.string, }; export default NotificationBanner; diff --git a/packages/core/src/App/Components/Elements/NotificationMessage/notification.jsx b/packages/core/src/App/Components/Elements/NotificationMessage/notification.jsx index ad71aebc7520..8d126d74281e 100644 --- a/packages/core/src/App/Components/Elements/NotificationMessage/notification.jsx +++ b/packages/core/src/App/Components/Elements/NotificationMessage/notification.jsx @@ -42,6 +42,7 @@ const Notification = ({ data, removeNotificationMessage }) => { img_src={data.img_src} img_alt={data.img_alt} onClose={destroy} + icon={data.icon} /> ); case 'trustpilot': @@ -176,6 +177,7 @@ Notification.propTypes = { img_src: PropTypes.string, is_auto_close: PropTypes.bool, key: PropTypes.string, + icon: PropTypes.string, message: PropTypes.oneOfType([PropTypes.node, PropTypes.string]), message_popup: PropTypes.string, primary_btn: PropTypes.object, diff --git a/packages/core/src/App/Containers/AcuityDownloadModal/acuity-download-modal.tsx b/packages/core/src/App/Containers/AcuityDownloadModal/acuity-download-modal.tsx new file mode 100644 index 000000000000..8386f9613d7f --- /dev/null +++ b/packages/core/src/App/Containers/AcuityDownloadModal/acuity-download-modal.tsx @@ -0,0 +1,71 @@ +import React from 'react'; +import RootStore from 'Stores/index'; +import { Button, Modal, Text, Icon } from '@deriv/components'; +import { Localize, localize } from '@deriv/translations'; +import { getUrlBase } from '@deriv/shared'; +import { connect } from 'Stores/connect'; +import 'Sass/app/modules/acuity-download.scss'; + +type TAcuityDownloadModal = { + is_acuity_modal_open: boolean; + is_eu: boolean; + setIsAcuityModalOpen: (value: boolean) => void; +}; + +const AcuityDownloadModal = ({ is_acuity_modal_open, is_eu, setIsAcuityModalOpen }: TAcuityDownloadModal) => { + const closeModal = () => setIsAcuityModalOpen(false); + + const openDownloadLink = () => { + window.open( + is_eu ? 'https://deriv.link/3hgxv2m' : 'https://deriv.link/3WhYxq1', + '_blank', + 'noopener,noreferrer' + ); + closeModal(); + }; + + return ( + +
+
+ +
+ + + +
+ + , ]} + /> + +
+
+ + + + +
+
+
+
+
+ ); +}; + +export default connect(({ client, ui }: RootStore) => ({ + is_acuity_modal_open: ui.is_acuity_modal_open, + is_eu: client.is_eu, + setIsAcuityModalOpen: ui.setIsAcuityModalOpen, +}))(AcuityDownloadModal); diff --git a/packages/core/src/App/Containers/AcuityDownloadModal/index.js b/packages/core/src/App/Containers/AcuityDownloadModal/index.js new file mode 100644 index 000000000000..0f46c6f0f364 --- /dev/null +++ b/packages/core/src/App/Containers/AcuityDownloadModal/index.js @@ -0,0 +1,3 @@ +import ActuityDownloadModal from './acuity-download-modal'; + +export default ActuityDownloadModal; diff --git a/packages/core/src/App/Containers/Modals/app-modals.jsx b/packages/core/src/App/Containers/Modals/app-modals.jsx index 621164058631..393e9f162208 100644 --- a/packages/core/src/App/Containers/Modals/app-modals.jsx +++ b/packages/core/src/App/Containers/Modals/app-modals.jsx @@ -9,6 +9,9 @@ import { moduleLoader } from '@deriv/shared'; const AccountSignupModal = React.lazy(() => moduleLoader(() => import(/* webpackChunkName: "account-signup-modal" */ '../AccountSignupModal')) ); +const AcuityDownloadModal = React.lazy(() => + import(/* webpackChunkName: "acuity-download-modal" */ '../AcuityDownloadModal') +); const CloseMxMltAccountModal = React.lazy(() => moduleLoader(() => import(/* webpackChunkName: "close-mx-mlt-account-modal" */ '../CloseMxMltAccountModal')) ); @@ -46,6 +49,7 @@ const WarningScamMessageModal = React.lazy(() => const AppModals = ({ is_account_needed_modal_on, + is_acuity_modal_open, is_welcome_modal_visible, is_reality_check_visible, is_set_residence_modal_visible, @@ -85,6 +89,11 @@ const AppModals = ({ } break; } + + if (is_acuity_modal_open) { + ComponentToLoad = ; + } + if (is_close_mx_mlt_account_modal_visible) { ComponentToLoad = ; } @@ -123,6 +132,7 @@ const AppModals = ({ export default connect(({ client, ui }) => ({ is_welcome_modal_visible: ui.is_welcome_modal_visible, is_account_needed_modal_on: ui.is_account_needed_modal_on, + is_acuity_modal_open: ui.is_acuity_modal_open, is_close_mx_mlt_account_modal_visible: ui.is_close_mx_mlt_account_modal_visible, is_close_uk_account_modal_visible: ui.is_close_uk_account_modal_visible, is_set_residence_modal_visible: ui.is_set_residence_modal_visible, diff --git a/packages/core/src/Stores/notification-store.js b/packages/core/src/Stores/notification-store.js index c93f1de486cb..fa1a42f49991 100644 --- a/packages/core/src/Stores/notification-store.js +++ b/packages/core/src/Stores/notification-store.js @@ -258,9 +258,13 @@ export default class NotificationStore extends BaseStore { const has_trustpilot = LocalStore.getObject('notification_messages')[loginid]?.includes( this.client_notifications.trustpilot.key ); + const has_acuity_mt5_download = LocalStore.getObject('notification_messages')[loginid]?.includes( + this.client_notifications.acuity_mt5_download.key + ); let has_missing_required_field; if (is_logged_in) { + if (isEmptyObject(account_status)) return; const { authentication: { document, identity, needs_verification }, status, @@ -304,6 +308,13 @@ export default class NotificationStore extends BaseStore { ) { this.addNotificationMessage(this.client_notifications.close_mx_mlt_account); } + + // Acuity notification is available for both Demo and Real desktop clients + this.addNotificationMessage(this.client_notifications.acuity); + if (!has_acuity_mt5_download && getPathname() === platform_name.DMT5) { + this.addNotificationMessage(this.client_notifications.acuity_mt5_download); + } + const client = accounts[loginid]; if (client && !client.is_virtual) { if (isEmptyObject(account_status)) return; @@ -603,6 +614,52 @@ export default class NotificationStore extends BaseStore { const platform_name_go = getPlatformSettings('go').name; const notifications = { + acuity: { + key: 'acuity', + header: localize('New trading tools for MT5'), + message: localize('Power up your Financial trades with intuitive tools from Acuity.'), + secondary_btn: { + text: localize('Learn More'), + onClick: () => { + ui.setIsAcuityModalOpen(true); + this.removeNotificationByKey({ key: this.client_notifications.acuity.key }); + this.removeNotificationMessage({ + key: this.client_notifications.acuity.key, + should_show_again: false, + }); + }, + }, + platform: [platform_name.DTrader], + is_disposable: true, + img_src: getUrlBase('/public/images/common/acuity_banner.png'), + img_alt: 'Acuity', + className: 'acuity', + type: 'news', + }, + acuity_mt5_download: { + key: 'acuity_mt5_download', + header: localize('Power up your trades with Acuity'), + message: localize( + 'Download intuitive trading tools to keep track of market events. The Acuity suite is only available for Windows, and is most recommended for financial assets.' + ), + secondary_btn: { + text: localize('Learn More'), + onClick: () => { + ui.setIsAcuityModalOpen(true); + this.removeNotificationByKey({ key: this.client_notifications.acuity_mt5_download.key }); + this.removeNotificationMessage({ + key: this.client_notifications.acuity_mt5_download.key, + should_show_again: false, + }); + }, + }, + platform: [platform_name.DMT5], + img_src: getUrlBase('/public/images/common/acuity_software.png'), + img_alt: 'Acuity Download', + className: 'acuity-mt5', + icon: 'IcCloseDark', + type: 'news', + }, ask_financial_risk_approval: { key: 'ask_financial_risk_approval', header: localize('Complete your Appropriateness Test'), diff --git a/packages/core/src/Stores/ui-store.js b/packages/core/src/Stores/ui-store.js index d4052c16dae2..d3d242383c7d 100644 --- a/packages/core/src/Stores/ui-store.js +++ b/packages/core/src/Stores/ui-store.js @@ -104,6 +104,9 @@ export default class UIStore extends BaseStore { // MT5 create real STP from demo, show only real accounts from switcher should_show_real_accounts_list = false; + // MT5 acuity download + is_acuity_modal_open = false; + // Real account signup real_account_signup = { active_modal_index: -1, @@ -226,6 +229,7 @@ export default class UIStore extends BaseStore { real_account_signup_target: observable, deposit_real_account_signup_target: observable, has_real_account_signup_ended: observable, + is_acuity_modal_open: observable, is_welcome_modal_visible: observable, is_close_mx_mlt_account_modal_visible: observable, is_close_uk_account_modal_visible: observable, @@ -320,6 +324,7 @@ export default class UIStore extends BaseStore { setCurrentFocus: action.bound, addToast: action.bound, removeToast: action.bound, + setIsAcuityModalOpen: action.bound, setIsNativepickerVisible: action.bound, setReportsTabIndex: action.bound, toggleWelcomeModal: action.bound, @@ -762,6 +767,10 @@ export default class UIStore extends BaseStore { this.should_show_real_accounts_list = value; } + setIsAcuityModalOpen(value) { + this.is_acuity_modal_open = value; + } + toggleShouldShowMultipliersOnboarding(value) { this.should_show_multipliers_onboarding = value; } diff --git a/packages/core/src/_common/utility.js b/packages/core/src/_common/utility.js index 93cb341b62eb..2ecf35655ae8 100644 --- a/packages/core/src/_common/utility.js +++ b/packages/core/src/_common/utility.js @@ -44,14 +44,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 = [ @@ -109,5 +101,4 @@ module.exports = { isOptionsBlocked, isSyntheticsUnavailable, isMultipliersOnly, - copyToClipboard, }; diff --git a/packages/core/src/public/images/common/static_images/acuity_banner.png b/packages/core/src/public/images/common/static_images/acuity_banner.png new file mode 100644 index 000000000000..16149d5509b8 Binary files /dev/null and b/packages/core/src/public/images/common/static_images/acuity_banner.png differ diff --git a/packages/core/src/public/images/common/static_images/acuity_modal.png b/packages/core/src/public/images/common/static_images/acuity_modal.png new file mode 100644 index 000000000000..b17a5b9bbe66 Binary files /dev/null and b/packages/core/src/public/images/common/static_images/acuity_modal.png differ diff --git a/packages/core/src/public/images/common/static_images/acuity_software.png b/packages/core/src/public/images/common/static_images/acuity_software.png new file mode 100644 index 000000000000..415bdfc9b7cb Binary files /dev/null and b/packages/core/src/public/images/common/static_images/acuity_software.png differ diff --git a/packages/core/src/sass/app/_common/components/notification-banner.scss b/packages/core/src/sass/app/_common/components/notification-banner.scss index bb73c0342991..585f58b379d6 100644 --- a/packages/core/src/sass/app/_common/components/notification-banner.scss +++ b/packages/core/src/sass/app/_common/components/notification-banner.scss @@ -94,6 +94,46 @@ top: 1.6rem; cursor: pointer; } + &__acuity { + .notification-banner { + &__bg { + width: 12.8rem; + clip-path: polygon(0 0, 100% 0, 100% 100%, 63% 100%); + } + &__btn-wrapper { + margin-top: 1.6rem; + } + &__img { + width: 12.7rem; + height: auto; + right: 0.3rem; + bottom: 1.4rem; + } + &--left { + width: unset; + } + } + } + + &__acuity-mt5 { + min-width: 55rem; + .notification-banner { + &--left { + min-width: 42.2rem; + } + &__bg { + background-color: var(--icon-grey-background); + width: 12.8rem; + clip-path: polygon(0 0, 100% 0, 100% 100%, 60% 100%); + } + &__img { + width: 15rem; + height: 11.5rem; + right: -1rem; + bottom: 3rem; + } + } + } @include mobile { height: 100%; diff --git a/packages/core/src/sass/app/modules/acuity-download.scss b/packages/core/src/sass/app/modules/acuity-download.scss new file mode 100644 index 000000000000..f6bfa6707a1d --- /dev/null +++ b/packages/core/src/sass/app/modules/acuity-download.scss @@ -0,0 +1,30 @@ +.acuity-download-modal { + &__body { + padding: 0rem 2.4rem 2.4rem; + + &--image { + padding-bottom: 1.6rem; + text-align: center; + } + + &--description { + padding: 0.8rem 0rem 1.6rem; + } + + &--info { + display: flex; + flex-direction: row; + padding: 0.8rem; + gap: 0.8rem; + background-color: var(--transparent-info); + + p { + width: 35.2rem; + } + } + &--button { + text-align: center; + padding-top: 1.6rem; + } + } +} diff --git a/packages/p2p/src/components/buy-sell/currency-selector/currency-selector.scss b/packages/p2p/src/components/buy-sell/currency-selector/currency-selector.scss index 8522ad4996e2..1679543fa23e 100644 --- a/packages/p2p/src/components/buy-sell/currency-selector/currency-selector.scss +++ b/packages/p2p/src/components/buy-sell/currency-selector/currency-selector.scss @@ -1,4 +1,4 @@ -.buy-sell > .currency-selector { +.buy-sell .currency-selector { @include desktop { position: absolute; margin-top: 0.4rem; diff --git a/packages/p2p/src/stores/buy-sell-store.js b/packages/p2p/src/stores/buy-sell-store.js index 1e3318e1a3a3..fc937df5783b 100644 --- a/packages/p2p/src/stores/buy-sell-store.js +++ b/packages/p2p/src/stores/buy-sell-store.js @@ -79,6 +79,7 @@ export default class BuySellStore extends BaseStore { selected_payment_method_value: observable, selected_payment_method_text: observable, selected_value: observable, + should_show_currency_selector_modal: observable, should_show_popup: observable, should_show_verification: observable, should_use_client_limits: observable, diff --git a/packages/translations/src/translations/id.json b/packages/translations/src/translations/id.json index c66c2e801648..8627e325f73d 100644 --- a/packages/translations/src/translations/id.json +++ b/packages/translations/src/translations/id.json @@ -646,7 +646,7 @@ "864610268": "Pertama, masukkan {{label}} Anda dan tanggal berakhir.", "864957760": "Nomer Positif Matematik", "865424952": "High-to-Low", - "865642450": "2. Mengakses dari browser yang berbeda", + "865642450": "2. Login dari browser yang berbeda", "866496238": "Pastikan data Sim Anda dapat dibaca dengan jelas, tidak buram atau menyilaukan", "868826608": "Dikecualikan dari {{brand_website_name}} hingga", "869823595": "Fungsi", @@ -991,7 +991,7 @@ "1310483610": "Hasil untuk \"{{ search_term }}\"", "1311680770": "hasil", "1311799109": "Kami tidak menyediakan token Binance Smart Chain untuk mendeposit, mohon gunakan Ethereum ({{token}}).", - "1313167179": "Silakan login", + "1313167179": "Silakan log in", "1313302450": "Bot akan berhenti melakukan trading jika total kerugian Anda melebihi jumlah ini.", "1316216284": "Anda dapat menggunakan kata sandi ini untuk semua akun {{platform}}.", "1319217849": "Periksa ponsel Anda", @@ -1082,7 +1082,7 @@ "1415006332": "dapatkan sub-daftar dari awal", "1415974522": "Jika Anda memilih \"Differs\", Anda akan memperoleh hasil jika digit terakhir pada tik terakhir berbeda dengan analisa Anda.", "1417558007": "Maks. total kerugian dalam 7 hari", - "1417914636": "Login ID", + "1417914636": "ID Login", "1418115525": "Blok ini mengulangi instruksi selama kondisi yang ditetapkan adalah benar.", "1421749665": "Simple Moving Average (SMA)", "1422060302": "Blok ini menggantikan item tertentu pada daftar dengan item lain yang diberikan. Blok ini juga dapat memasukkan item baru pada daftar posisi tertentu.", @@ -1210,7 +1210,7 @@ "1587046102": "Dokumen dari negara tersebut saat ini tidak dapat diterima — silakan coba jenis dokumen lain", "1589640950": "Penjualan kembali kontrak ini tidak tersedia.", "1589702653": "Bukti alamat", - "1593010588": "Login now", + "1593010588": "Login sekarang", "1594147169": "Silahkan kembali pada jam", "1594322503": "Penjualan tersedia", "1596378630": "Anda telah menambahkan akun riil Gaming.<0/>Deposit sekarang dan mulai bertrading.", @@ -1380,7 +1380,7 @@ "1806355993": "Tanpa komisi", "1806503050": "Mohon ketahui bahwa beberapa metode pembayaran tidak tersedia di negara Anda.", "1808058682": "Blok berhasil dimuat", - "1808393236": "Masuk", + "1808393236": "Login", "1808867555": "Blok ini menggunakan variabel “i” untuk mengontrol pengulangan. Pada setiap pengulangan, nilai “i” ditentukan oleh item pada daftar yang diberikan.", "1810217569": "Silakan refresh halaman ini untuk melanjutkan.", "1811109068": "Yurisdiksi", @@ -1620,7 +1620,7 @@ "2085602195": "- Nilai Masuk: nilai tik pertama kontrak", "2086742952": "Anda telah menambahkan akun riil Opsi.<0/>Deposit sekarang dan mulai bertrading.", "2086792088": "Kedua barrier harus dalam bentuk relatif atau mutlak", - "2088735355": "Batas sesi dan pengaksesan Anda", + "2088735355": "Batas sesi dan login Anda", "2089087110": "Indeks basket", "2089299875": "Total aset pada akun riil Deriv dan DMT5 Anda.", "2089581483": "Berakhir pada", @@ -1634,7 +1634,7 @@ "2100713124": "akun", "2101972779": "Sama seperti contoh di atas, menggunakan daftar tik.", "2102572780": "Panjang kode digit harus 6 karakter.", - "2104115663": "Akses terakhir", + "2104115663": "Login terakhir", "2104397115": "Kunjungi bagian pengaturan akun dan lengkapi data pribadi Anda untuk mengaktifkan deposit dan penarikan.", "2107381257": "Pemeliharaan sistem kasir terjadwal", "2109312805": "Spread adalah perbedaan antara harga beli dan harga jual. Spread variabel berarti bahwa spread terus berubah, tergantung pada kondisi pasar. Spread tetap akan tetap konstan tetapi tunduk pada perubahan, pada kebijaksanaan mutlak broker.", @@ -2034,7 +2034,7 @@ "-680528873": "Akun Anda akan didaftarkan pada {{legal_entity_name}} dan akan tunduk pada yurisdiksi dan hukum dari Samoa.", "-1125193491": "Tambahkan akun", "-2068229627": "Saya bukan PEP, dan saya belum menjadi PEP dalam tempo 12 bulan terakhir.", - "-186841084": "Change your login email", + "-186841084": "Ubah email login Anda", "-907403572": "To change your email address, you'll first need to unlink your email address from your {{identifier_title}} account.", "-1850792730": "Batalkan tautan dari {{identifier_title}}", "-2139303636": "Anda mungkin telah mengikuti tautan yang rusak, atau halaman tersebut telah pindah ke alamat baru.", @@ -2315,7 +2315,7 @@ "-749186458": "Pengalihan akun dinonaktifkan saat bot Anda berjalan. Hentikan bot Anda sebelum beralih akun.", "-662836330": "Apakah Anda ingin tetap mengaktifkan kontrak Anda atau menutupnya? Jika Anda ingin tetap mengaktifkan, Anda dapat menutupnya kemudian pada halaman <0>Laporan.", "-597939268": "Tetap aktifkan kontrak", - "-1322453991": "Anda perlu login untuk mengoperasikan bot.", + "-1322453991": "Anda perlu log in untuk mengoperasikan bot.", "-1483938124": "Strategi ini tidak sesuai digunakan pada DBot.", "-236548954": "Error Pembaruan Kontrak", "-1428017300": "YANG", @@ -3301,7 +3301,7 @@ "-490766438": "Anda terputus, coba kembali dalam {{ delay }} detik", "-1389975609": "tidak diketahui", "-1900515692": "Durasi harus bilangan bulat positif", - "-245297595": "Silahkan masuk", + "-245297595": "Silahkan login", "-1445046468": "Candle yang diberikan tidak berlaku", "-1891622945": "{{hourPast}}jam yang lalu", "-1723202824": "Mohon berikan izin untuk melihat dan mengelola folder Google drive yang dibuat menggunakan Bot Binary",