diff --git a/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/run-strategy.tsx b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/run-strategy.tsx deleted file mode 100644 index bef7c154a4d6..000000000000 --- a/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/run-strategy.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react'; -import TradeAnimation from 'Components/trade-animation'; - -const RunStrategy = () => ( -
- -
-); - -export default RunStrategy; diff --git a/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/toolbar.scss b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/toolbar.scss index 52359c2b4d4d..dea20935fe24 100644 --- a/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/toolbar.scss +++ b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/toolbar.scss @@ -91,8 +91,7 @@ } } &__animation { - margin-right: 0.5rem; - max-width: 35rem; + width: 35rem; } &__dialog { &-text--second { diff --git a/packages/bot-web-ui/src/components/dashboard/bot-stop-notifiaction.spec.tsx b/packages/bot-web-ui/src/components/dashboard/bot-stop-notifiaction.spec.tsx new file mode 100644 index 000000000000..38be3d4fbf7d --- /dev/null +++ b/packages/bot-web-ui/src/components/dashboard/bot-stop-notifiaction.spec.tsx @@ -0,0 +1,101 @@ +import React from 'react'; +import { mockStore, StoreProvider } from '@deriv/stores'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { act, fireEvent, render, screen } from '@testing-library/react'; +// eslint-disable-next-line import/no-extraneous-dependencies +import userEvent from '@testing-library/user-event'; +import RootStore from 'Stores/index'; +import { DBotStoreProvider, mockDBotStore } from 'Stores/useDBotStore'; +import BotStopNotification from './bot-stop-notification'; + +jest.mock('@deriv/bot-skeleton/src/scratch/blockly', () => jest.fn()); +jest.mock('@deriv/bot-skeleton/src/scratch/dbot', () => ({ + saveRecentWorkspace: jest.fn(), + unHighlightAllBlocks: jest.fn(), +})); +jest.mock('@deriv/bot-skeleton/src/scratch/hooks/block_svg', () => jest.fn()); + +const mock_ws = { + authorized: { + subscribeProposalOpenContract: jest.fn(), + send: jest.fn(), + }, + storage: { + send: jest.fn(), + }, + contractUpdate: jest.fn(), + subscribeTicksHistory: jest.fn(), + forgetStream: jest.fn(), + activeSymbols: jest.fn(), + send: jest.fn(), +}; +describe('BotStopNotification', () => { + let wrapper: ({ children }: { children: JSX.Element }) => JSX.Element, mock_DBot_store: RootStore | undefined; + + beforeEach(() => { + jest.resetModules(); + const mock_store = mockStore({}); + mock_DBot_store = mockDBotStore(mock_store, mock_ws); + + wrapper = ({ children }: { children: JSX.Element }) => ( + + + {children} + + + ); + }); + + it('should clear the notification timer and hide message after timer expires', () => { + act(() => { + mock_DBot_store?.run_panel.setShowBotStopMessage(false); + }); + jest.useFakeTimers(); + + render(, { + wrapper, + }); + + // Advance timers to trigger notificationTimer + jest.advanceTimersByTime(6000); + + // Expect that setShowBotStopMessage(false) was called + expect(screen.queryByText('You’ve just stopped the bot.')).not.toBeInTheDocument(); + }); + + it('should render the toast component', () => { + const { container } = render(, { + wrapper, + }); + expect(container).toBeInTheDocument(); + }); + + it('should render to remove the toast component when clicking on close icon', () => { + act(() => { + mock_DBot_store?.run_panel.setShowBotStopMessage(false); + }); + + render(, { + wrapper, + }); + userEvent.click(screen.getByTestId('notification-close')); + expect(screen.queryByText('You’ve just stopped the bot.')).not.toBeInTheDocument(); + }); + + it('should render toast', () => { + act(() => { + mock_DBot_store?.run_panel.setShowBotStopMessage(true); + }); + + render(, { + wrapper, + }); + fireEvent.mouseOver(screen.getByTestId('bot-stop-notification')); + jest.advanceTimersByTime(6000); + expect(screen.queryByText('You’ve just stopped the bot.')).not.toBeInTheDocument(); + + fireEvent.mouseLeave(screen.getByTestId('bot-stop-notification')); + jest.advanceTimersByTime(4000); + expect(screen.queryByText('You’ve just stopped the bot.')).not.toBeInTheDocument(); + }); +}); diff --git a/packages/bot-web-ui/src/components/dashboard/bot-stop-notification.tsx b/packages/bot-web-ui/src/components/dashboard/bot-stop-notification.tsx new file mode 100644 index 000000000000..fb9821a26748 --- /dev/null +++ b/packages/bot-web-ui/src/components/dashboard/bot-stop-notification.tsx @@ -0,0 +1,61 @@ +import React, { useRef } from 'react'; +import { observer } from 'mobx-react'; +import { Icon, Toast } from '@deriv/components'; +import { Localize } from '@deriv/translations'; +import { useDBotStore } from 'Stores/useDBotStore'; + +const BotStopNotification = observer(() => { + const { run_panel } = useDBotStore(); + const { setShowBotStopMessage } = run_panel; + const notification_timer = useRef(6000); + + const notificationTimer = setTimeout(() => { + if (notification_timer.current) { + setShowBotStopMessage(false); + } + }, notification_timer.current); + + const resetTimer = () => { + setTimeout(() => { + setShowBotStopMessage(false); + }, 4000); + }; + + return ( +
{ + clearTimeout(notificationTimer); + }} + onMouseLeave={e => { + resetTimer(); + }} + data-testid='bot-stop-notification' + > + +
+ , + ]} + /> +
+ setShowBotStopMessage(false)} + /> +
+
+ ); +}); + +export default BotStopNotification; diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/run-strategy.tsx b/packages/bot-web-ui/src/components/dashboard/dashboard-component/run-strategy.tsx index c985dc6662ea..bef7c154a4d6 100644 --- a/packages/bot-web-ui/src/components/dashboard/dashboard-component/run-strategy.tsx +++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/run-strategy.tsx @@ -3,7 +3,7 @@ import TradeAnimation from 'Components/trade-animation'; const RunStrategy = () => (
- +
); diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard.scss b/packages/bot-web-ui/src/components/dashboard/dashboard.scss index 0662098752f4..674808040125 100644 --- a/packages/bot-web-ui/src/components/dashboard/dashboard.scss +++ b/packages/bot-web-ui/src/components/dashboard/dashboard.scss @@ -675,10 +675,18 @@ position: fixed; z-index: 1; left: 0; + right: 0; bottom: 7rem; - .dc-toast__message { - background: var(--text-general); - color: var(--general-main-1); + .dc-toast { + width: 100%; + &__message { + background: var(--text-prominent); + color: var(--general-main-1); + } + } + + @include mobile { + bottom: 10rem; } } diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard.tsx b/packages/bot-web-ui/src/components/dashboard/dashboard.tsx index c88dbf6dd0f1..d29258b432c7 100644 --- a/packages/bot-web-ui/src/components/dashboard/dashboard.tsx +++ b/packages/bot-web-ui/src/components/dashboard/dashboard.tsx @@ -10,7 +10,6 @@ import { DBOT_TABS, TAB_IDS } from 'Constants/bot-contents'; import { useDBotStore } from 'Stores/useDBotStore'; import RunPanel from '../run-panel'; import RunStrategy from './dashboard-component/run-strategy'; -import BotNotification from './bot-notification'; import DashboardComponent from './dashboard-component'; import { DBOT_ONBOARDING, @@ -21,6 +20,7 @@ import { tour_type, } from './joyride-config'; import ReactJoyrideWrapper from './react-joyride-wrapper'; +import StrategyNotification from './strategy-notification'; import TourSlider from './tour-slider'; import TourTriggrerDialog from './tour-trigger-dialog'; import Tutorial from './tutorial-tab'; @@ -264,7 +264,7 @@ const Dashboard = observer(() => { > {dialog_options.message} - + ); }); diff --git a/packages/bot-web-ui/src/components/dashboard/bot-notification.tsx b/packages/bot-web-ui/src/components/dashboard/strategy-notification.tsx similarity index 92% rename from packages/bot-web-ui/src/components/dashboard/bot-notification.tsx rename to packages/bot-web-ui/src/components/dashboard/strategy-notification.tsx index 6069b63a1815..8190d8882b18 100644 --- a/packages/bot-web-ui/src/components/dashboard/bot-notification.tsx +++ b/packages/bot-web-ui/src/components/dashboard/strategy-notification.tsx @@ -5,7 +5,7 @@ import { observer } from '@deriv/stores'; import { localize } from '@deriv/translations'; import { useDBotStore } from 'Stores/useDBotStore'; -const BotNotification = observer(() => { +const StrategyNotification = observer(() => { const { dashboard } = useDBotStore(); const { show_toast, toast_message, setOpenSettings } = dashboard; @@ -33,4 +33,4 @@ const BotNotification = observer(() => { return null; }); -export default BotNotification; +export default StrategyNotification; diff --git a/packages/bot-web-ui/src/components/run-panel/run-panel.scss b/packages/bot-web-ui/src/components/run-panel/run-panel.scss index d654730e0395..ea10f7406930 100644 --- a/packages/bot-web-ui/src/components/run-panel/run-panel.scss +++ b/packages/bot-web-ui/src/components/run-panel/run-panel.scss @@ -166,7 +166,6 @@ height: 6rem; display: flex; width: inherit; - padding-right: 8px; } &__stop-button, &__run-button { diff --git a/packages/bot-web-ui/src/components/trade-animation/trade-animation.jsx b/packages/bot-web-ui/src/components/trade-animation/trade-animation.jsx index 71de7214db6d..3db07001fece 100644 --- a/packages/bot-web-ui/src/components/trade-animation/trade-animation.jsx +++ b/packages/bot-web-ui/src/components/trade-animation/trade-animation.jsx @@ -1,11 +1,11 @@ import React from 'react'; import classNames from 'classnames'; import PropTypes from 'prop-types'; -import { Button, Icon, Modal, Text } from '@deriv/components'; -import { isMobile } from '@deriv/shared'; +import { Button, Icon } from '@deriv/components'; import { observer, useStore } from '@deriv/stores'; import { Localize, localize } from '@deriv/translations'; import ContractResultOverlay from 'Components/contract-result-overlay'; +import BotStopNotification from 'Components/dashboard/bot-stop-notification'; import { contract_stages } from 'Constants/contract-stage'; import { useDBotStore } from 'Stores/useDBotStore'; @@ -16,44 +16,6 @@ const CircularWrapper = ({ className }) => ( ); -const AnimationInfo = ({ toggleAnimationInfoModal }) => { - return ( -
- -
- ); -}; - -const AnimationInfoModal = ({ is_mobile, is_animation_info_modal_open, toggleAnimationInfoModal }) => { - return ( - - -
- - {localize( - 'Stopping the bot will prevent further trades. Any ongoing trades will be completed by our system.' - )} - - - {localize( - 'Please be aware that some completed transactions may not be displayed in the transaction table if the bot is stopped while placing trades.' - )} - - - {localize('You may refer to the statement page for details of all completed transactions.')} - -
-
-
- ); -}; - const ContractStageText = ({ contract_stage }) => { switch (contract_stage) { case contract_stages.NOT_RUNNING: @@ -72,8 +34,8 @@ const ContractStageText = ({ contract_stage }) => { } }; -const TradeAnimation = observer(({ className, info_direction }) => { - const { run_panel, toolbar, summary_card } = useDBotStore(); +const TradeAnimation = observer(({ className }) => { + const { run_panel, summary_card } = useDBotStore(); const { client } = useStore(); const { is_contract_completed, profit } = summary_card; const { @@ -84,13 +46,13 @@ const TradeAnimation = observer(({ className, info_direction }) => { onStopButtonClick, performSelfExclusionCheck, should_show_overlay, + show_bot_stop_message, } = run_panel; - const { is_animation_info_modal_open, toggleAnimationInfoModal } = toolbar; const { account_status } = client; const cashier_validation = account_status?.cashier_validation; - const [is_button_disabled, updateIsButtonDisabled] = React.useState(false); const is_unavailable_for_payment_agent = cashier_validation?.includes('WithdrawServiceUnavailableForPA'); + // perform self-exclusion checks which will be stored under the self-exclusion-store React.useEffect(() => { performSelfExclusionCheck(); @@ -127,13 +89,12 @@ const TradeAnimation = observer(({ className, info_direction }) => { return (
- {info_direction === 'left' && }
- {info_direction === 'right' && } - ); }); TradeAnimation.propTypes = { className: PropTypes.string, - info_direction: PropTypes.string, }; export default TradeAnimation; diff --git a/packages/bot-web-ui/src/components/trade-animation/trade-animation.scss b/packages/bot-web-ui/src/components/trade-animation/trade-animation.scss index 473352517115..5e4d246d3372 100644 --- a/packages/bot-web-ui/src/components/trade-animation/trade-animation.scss +++ b/packages/bot-web-ui/src/components/trade-animation/trade-animation.scss @@ -63,11 +63,6 @@ border-top-left-radius: 0; border-bottom-right-radius: $BORDER_RADIUS; } - &__info { - font-size: 10px; - margin: 8px; - cursor: pointer; - } // TODO: [fix-dc-bundle] Fix import issue with Deriv Component stylesheets (app should take precedence, and not repeat) &__button { width: fit-content; @@ -187,30 +182,47 @@ } } } - &__modal { - height: 28.4rem !important; - width: 44rem !important; - font-size: 1.6rem; - padding: 2.4rem; +} + +.dc-modal__container_animation__modal { + @include mobile { + width: 31.2rem !important; + } +} + +.bot-stop-notification { + position: fixed; + z-index: 9; + right: 38rem; + top: 12rem; - &--mobile { - font-size: 1.6rem !important; + .dc-toast { + width: 100%; + &__message { + background: var(--text-prominent); + color: var(--general-main-1); + padding: 1rem 1.6rem; } - &-body { - height: 438px; + &__message-content { + display: flex; - &--mobile { - padding: 1.6rem 0 !important; - } - &--content { - margin-top: 2rem; + @include mobile { + align-items: center; } } } -} -.dc-modal__container_animation__modal { @include mobile { - width: 31.2rem !important; + top: unset; + left: 0; + right: 0; + bottom: 10.5rem; + } + + .notification-close { + cursor: pointer; + filter: invert(1); + margin-left: 1rem; + margin-top: 0.1rem; } } diff --git a/packages/bot-web-ui/src/stores/run-panel-store.js b/packages/bot-web-ui/src/stores/run-panel-store.js index ceefa006727a..1923f7132edd 100644 --- a/packages/bot-web-ui/src/stores/run-panel-store.js +++ b/packages/bot-web-ui/src/stores/run-panel-store.js @@ -21,6 +21,7 @@ export default class RunPanelStore { is_sell_requested: observable, run_id: observable, error_type: observable, + show_bot_stop_message: observable, statistics: computed, is_stop_button_visible: computed, is_stop_button_disabled: computed, @@ -52,6 +53,7 @@ export default class RunPanelStore { setContractStage: action.bound, setHasOpenContract: action.bound, setIsRunning: action.bound, + setShowBotStopMessage: action.bound, onMount: action.bound, onUnmount: action.bound, handleInvalidToken: action.bound, @@ -82,6 +84,7 @@ export default class RunPanelStore { is_drawer_open = true; is_dialog_open = false; is_sell_requested = false; + show_bot_stop_message = false; run_id = ''; @@ -141,6 +144,10 @@ export default class RunPanelStore { ); } + setShowBotStopMessage(value) { + this.show_bot_stop_message = value; + } + async performSelfExclusionCheck() { const { self_exclusion } = this.root_store; await self_exclusion.checkRestriction(); @@ -207,15 +214,20 @@ export default class RunPanelStore { this.setContractStage(contract_stages.STARTING); this.dbot.runBot(); }); + this.setShowBotStopMessage(false); } onStopButtonClick() { const { is_multiplier } = this.root_store.summary_card; + const { summary_card } = this.root_store; if (is_multiplier) { this.showStopMultiplierContractDialog(); } else { this.stopBot(); + this.dbot.terminateBot(); + summary_card.clear(); + this.setShowBotStopMessage(true); } } @@ -553,7 +565,7 @@ export default class RunPanelStore { onBotTradeAgain(is_trade_again) { if (!is_trade_again) { - this.onStopButtonClick(); + this.stopBot(); } } diff --git a/packages/bot-web-ui/src/stores/toolbar-store.ts b/packages/bot-web-ui/src/stores/toolbar-store.ts index e614e5286b37..dc4a8fa8ea4a 100644 --- a/packages/bot-web-ui/src/stores/toolbar-store.ts +++ b/packages/bot-web-ui/src/stores/toolbar-store.ts @@ -8,7 +8,6 @@ interface IToolbarStore { file_name: { [key: string]: string }; has_undo_stack: boolean; has_redo_stack: boolean; - toggleAnimationInfoModal: () => void; onResetClick: () => void; closeResetDialog: () => void; onResetOkButtonClick: () => void; @@ -29,7 +28,6 @@ export default class ToolbarStore implements IToolbarStore { file_name: observable, has_undo_stack: observable, has_redo_stack: observable, - toggleAnimationInfoModal: action.bound, onResetClick: action.bound, closeResetDialog: action.bound, onResetOkButtonClick: action.bound, @@ -47,10 +45,6 @@ export default class ToolbarStore implements IToolbarStore { has_undo_stack = false; has_redo_stack = false; - toggleAnimationInfoModal = (): void => { - this.is_animation_info_modal_open = !this.is_animation_info_modal_open; - }; - onResetClick = (): void => { this.is_dialog_open = true; }; diff --git a/packages/components/src/components/icon/common/ic-bot-stop.svg b/packages/components/src/components/icon/common/ic-bot-stop.svg new file mode 100644 index 000000000000..b976aecb0ac1 --- /dev/null +++ b/packages/components/src/components/icon/common/ic-bot-stop.svg @@ -0,0 +1 @@ + \ No newline at end of file