-
- {sub_title}
+
+ {!is_real && sub_title ? `${sub_title} ${localize('Demo')}` : sub_title}
{short_code_and_region && (
)}
-
- {name}
-
+ {!is_real && !sub_title && !is_deriv_platform ? `${name} ${localize('Demo')}` : name}
+
+
{app_desc}
{mt5_acc_auth_status && (
@@ -111,6 +119,7 @@ const TradingAppCard = ({
is_external={is_external}
new_tab={new_tab}
is_buttons_disabled={!!mt5_acc_auth_status}
+ is_account_being_created={!!is_account_being_created}
is_real={is_real}
/>
diff --git a/packages/appstore/src/components/currency-switcher-card/__tests__/index.spec.tsx b/packages/appstore/src/components/currency-switcher-card/__tests__/index.spec.tsx
new file mode 100644
index 000000000000..8ea983d0a707
--- /dev/null
+++ b/packages/appstore/src/components/currency-switcher-card/__tests__/index.spec.tsx
@@ -0,0 +1,85 @@
+import React from 'react';
+import CurrencySwitcherCard from '../index';
+import { render } from '@testing-library/react';
+import { StoreProvider, mockStore } from '@deriv/stores';
+
+jest.mock('../demo/demo-account-card', () => ({
+ __esModule: true,
+ default: () =>
DemoAccountCard
,
+}));
+
+jest.mock('../real/real-account-switcher', () => ({
+ __esModule: true,
+ default: () =>
RealAccountSwitcher
,
+}));
+
+describe('CurrencySwitcherCard', () => {
+ it('should render empty div if user has no real account', () => {
+ const mock = mockStore({
+ traders_hub: {
+ is_real: true,
+ },
+ client: {
+ has_any_real_account: false,
+ },
+ });
+ const wrapper = ({ children }: { children: JSX.Element }) => (
+
{children}
+ );
+
+ const { container } = render(
, { wrapper });
+ expect(container).toBeEmptyDOMElement();
+ });
+ it('should render demo account card if user is in demo', () => {
+ const mock = mockStore({
+ traders_hub: {
+ is_demo: true,
+ },
+ });
+ const wrapper = ({ children }: { children: JSX.Element }) => (
+
{children}
+ );
+
+ const { container } = render(
, { wrapper });
+ expect(container).toBeInTheDocument();
+ expect(container).toHaveTextContent('DemoAccountCard');
+ });
+
+ it('should render real account switcher if user is in real and not an eu user', () => {
+ const mock = mockStore({
+ traders_hub: {
+ is_real: true,
+ is_eu_user: false,
+ },
+ client: {
+ has_any_real_account: true,
+ },
+ });
+ const wrapper = ({ children }: { children: JSX.Element }) => (
+
{children}
+ );
+
+ const { container } = render(
, { wrapper });
+ expect(container).toBeInTheDocument();
+ expect(container).toHaveTextContent('RealAccountSwitcher');
+ });
+
+ it('should render real account switcher if user is in real and is an eu user', () => {
+ const mock = mockStore({
+ traders_hub: {
+ is_real: true,
+ is_eu_user: true,
+ },
+ client: {
+ has_maltainvest_account: true,
+ },
+ });
+ const wrapper = ({ children }: { children: JSX.Element }) => (
+
{children}
+ );
+
+ const { container } = render(
, { wrapper });
+ expect(container).toBeInTheDocument();
+ expect(container).toHaveTextContent('RealAccountSwitcher');
+ });
+});
diff --git a/packages/appstore/src/components/currency-switcher-card/demo/__tests__/demo-account-card.spec.tsx b/packages/appstore/src/components/currency-switcher-card/demo/__tests__/demo-account-card.spec.tsx
new file mode 100644
index 000000000000..ee0034f382a2
--- /dev/null
+++ b/packages/appstore/src/components/currency-switcher-card/demo/__tests__/demo-account-card.spec.tsx
@@ -0,0 +1,118 @@
+import React from 'react';
+import DemoAccountCard from '../demo-account-card';
+import { render, screen } from '@testing-library/react';
+import { StoreProvider, mockStore } from '@deriv/stores';
+
+describe('DemoAccountCard', () => {
+ it('should render correctly', () => {
+ const mock = mockStore({
+ modules: {
+ cfd: {
+ current_list: {
+ VRTC123123: {
+ landing_company_short: 'svg',
+ },
+ },
+ },
+ },
+ traders_hub: {
+ selected_account_type: 'demo',
+ },
+ client: {
+ accounts: {
+ CR1231123: {
+ balance: 10000,
+ currency: 'USD',
+ },
+ },
+ loginid: 'CR1231123',
+ },
+ });
+
+ const wrapper = ({ children }: { children: JSX.Element }) => (
+
{children}
+ );
+
+ const { container } = render(
, {
+ wrapper,
+ });
+ expect(container).toBeInTheDocument();
+ });
+
+ it('should render correctly with the correct balance and text', () => {
+ const mock = mockStore({
+ modules: {
+ cfd: {
+ current_list: {
+ VRTC123123: {
+ landing_company_short: 'svg',
+ },
+ },
+ },
+ },
+ traders_hub: {
+ selected_account_type: 'demo',
+ },
+ client: {
+ accounts: {
+ VRTC123123: {
+ balance: 10000,
+ currency: 'USD',
+ is_virtual: 1,
+ },
+ },
+ loginid: 'VRTC123123',
+ },
+ });
+
+ const wrapper = ({ children }: { children: JSX.Element }) => (
+
{children}
+ );
+
+ const { container } = render(
, {
+ wrapper,
+ });
+ expect(container).toBeInTheDocument();
+ expect(screen.getByText('demo')).toBeInTheDocument();
+ expect(screen.getByText('10,000.00')).toBeInTheDocument();
+ });
+
+ it('should render the reset balance button when the balance is not 10,000', () => {
+ const mock = mockStore({
+ modules: {
+ cfd: {
+ current_list: {
+ VRTC123123: {
+ landing_company_short: 'svg',
+ },
+ },
+ },
+ },
+ traders_hub: {
+ selected_account_type: 'demo',
+ },
+ client: {
+ accounts: {
+ VRTC123123: {
+ balance: 9000,
+ currency: 'USD',
+ is_virtual: 1,
+ },
+ },
+ loginid: 'VRTC123123',
+ },
+ });
+
+ const wrapper = ({ children }: { children: JSX.Element }) => (
+
{children}
+ );
+
+ const { container } = render(
, {
+ wrapper,
+ });
+ expect(container).toBeInTheDocument();
+ expect(screen.getByText('demo')).toBeInTheDocument();
+ expect(screen.getByText('9,000.00')).toBeInTheDocument();
+ expect(screen.getByText('Reset Balance')).toBeInTheDocument();
+ });
+});
diff --git a/packages/appstore/src/components/currency-switcher-card/demo/demo-account-card.tsx b/packages/appstore/src/components/currency-switcher-card/demo/demo-account-card.tsx
index 6c7984381579..f3b8469d66ce 100644
--- a/packages/appstore/src/components/currency-switcher-card/demo/demo-account-card.tsx
+++ b/packages/appstore/src/components/currency-switcher-card/demo/demo-account-card.tsx
@@ -1,41 +1,53 @@
import React from 'react';
-import { observer } from 'mobx-react-lite';
import { Button, Text } from '@deriv/components';
import CurrencySwitcherContainer from 'Components/containers/currency-switcher-container';
import BalanceText from 'Components/elements/text/balance-text';
-import { useStores } from 'Stores/index';
import './demo-account-card.scss';
import { localize } from '@deriv/translations';
+import { usePlatformAccounts } from '@deriv/hooks';
+import { useStore, observer } from '@deriv/stores';
-const DemoAccountCard = () => {
- const { client, traders_hub } = useStores();
- const { accounts, loginid, resetVirtualBalance } = client;
- const { platform_demo_balance, selected_account_type } = traders_hub;
+const DemoAccountCard = observer(() => {
+ const { client, traders_hub, common } = useStore();
+ const { accounts, loginid, resetVirtualBalance, default_currency } = client;
+ const { selected_account_type } = traders_hub;
+ const { demo: platform_demo_account } = usePlatformAccounts();
const canResetBalance = () => {
- return accounts[loginid]?.balance !== 10000;
+ return loginid && (accounts[loginid]?.balance || 0) !== 10000;
};
+ const { current_language } = common;
+
return (
- {selected_account_type}
+ {localize(selected_account_type)}
}
actions={
canResetBalance() && (
-
+
{localize('Reset Balance')}
)
}
>
-
+
);
-};
+});
-export default observer(DemoAccountCard);
+export default DemoAccountCard;
diff --git a/packages/appstore/src/components/currency-switcher-card/index.tsx b/packages/appstore/src/components/currency-switcher-card/index.tsx
index 1fe3548b5354..55255deef8f8 100644
--- a/packages/appstore/src/components/currency-switcher-card/index.tsx
+++ b/packages/appstore/src/components/currency-switcher-card/index.tsx
@@ -2,10 +2,10 @@ import React from 'react';
import { observer } from 'mobx-react-lite';
import DemoAccountCard from './demo/demo-account-card';
import RealAccountSwitcher from './real/real-account-switcher';
-import { useStores } from 'Stores/index';
+import { useStore } from '@deriv/stores';
-const CurrencySwitcherCard = () => {
- const { traders_hub, client } = useStores();
+const CurrencySwitcherCard = observer(() => {
+ const { traders_hub, client } = useStore();
const { has_any_real_account, has_maltainvest_account } = client;
const { is_real, is_demo, is_eu_user } = traders_hub;
@@ -19,6 +19,6 @@ const CurrencySwitcherCard = () => {
return
;
}
return null;
-};
+});
-export default observer(CurrencySwitcherCard);
+export default CurrencySwitcherCard;
diff --git a/packages/appstore/src/components/currency-switcher-card/real/__tests__/real-account-card.spec.tsx b/packages/appstore/src/components/currency-switcher-card/real/__tests__/real-account-card.spec.tsx
new file mode 100644
index 000000000000..6d003094b406
--- /dev/null
+++ b/packages/appstore/src/components/currency-switcher-card/real/__tests__/real-account-card.spec.tsx
@@ -0,0 +1,156 @@
+import React from 'react';
+import RealAccountCard from '../real-account-card';
+import { render, screen, fireEvent } from '@testing-library/react';
+import { StoreProvider, mockStore } from '@deriv/stores';
+import { createMemoryHistory } from 'history';
+import { Router } from 'react-router-dom';
+
+describe('RealAccountCard', () => {
+ it('should render the component', () => {
+ const mock = mockStore({
+ modules: {
+ cfd: {
+ current_list: {
+ CR1231123: {
+ landing_company_short: 'maltainvest',
+ },
+ },
+ },
+ },
+ });
+ const wrapper = ({ children }: { children: JSX.Element }) => (
+
{children}
+ );
+
+ const { container } = render(
, { wrapper });
+ expect(container).toBeInTheDocument();
+ });
+
+ it('should render the component with the correct balance and currency as USD', () => {
+ const mock = mockStore({
+ client: {
+ accounts: {
+ CR1231123: {
+ balance: 1000,
+ currency: 'USD',
+ },
+ },
+ loginid: 'CR1231123',
+ },
+ modules: {
+ cfd: {
+ current_list: {
+ CR1231123: {
+ landing_company_short: 'maltainvest',
+ },
+ },
+ },
+ },
+ });
+ const wrapper = ({ children }: { children: JSX.Element }) => (
+
{children}
+ );
+
+ const { container } = render(
, { wrapper });
+ expect(container).toBeInTheDocument();
+ expect(screen.getByText('1,000.00')).toBeInTheDocument();
+ expect(screen.getByText('USD')).toBeInTheDocument();
+ });
+
+ it('should render the component with the correct balance and currency as EUR', () => {
+ const mock = mockStore({
+ client: {
+ accounts: {
+ CR1231123: {
+ balance: 20120,
+ currency: 'EUR',
+ },
+ },
+ loginid: 'CR1231123',
+ },
+ modules: {
+ cfd: {
+ current_list: {
+ CR1231123: {
+ landing_company_short: 'maltainvest',
+ },
+ },
+ },
+ },
+ });
+ const wrapper = ({ children }: { children: JSX.Element }) => (
+
{children}
+ );
+
+ const { container } = render(
, { wrapper });
+ expect(container).toBeInTheDocument();
+ expect(screen.getByText('20,120.00')).toBeInTheDocument();
+ expect(screen.getByText('EUR')).toBeInTheDocument();
+ });
+
+ it('should redirect the user to cashier_deposit page after clicking deposit', () => {
+ const history = createMemoryHistory();
+ const mock = mockStore({
+ client: {
+ accounts: {
+ CR1231123: {
+ balance: 20120,
+ currency: 'EUR',
+ },
+ },
+ loginid: 'CR1231123',
+ },
+ modules: {
+ cfd: {
+ current_list: {
+ CR1231123: {
+ landing_company_short: 'maltainvest',
+ },
+ },
+ },
+ },
+ });
+ const wrapper = ({ children }: { children: JSX.Element }) => (
+
+ {children}
+
+ );
+
+ const { container } = render(
, { wrapper });
+ expect(container).toBeInTheDocument();
+ expect(screen.getByText('Deposit')).toBeInTheDocument();
+ fireEvent.click(screen.getByText('Deposit'));
+ expect(history.location.pathname).toBe('/cashier/deposit');
+ });
+
+ it('should open the currency selection modal', () => {
+ const mock = mockStore({
+ client: {
+ accounts: {
+ CR1231123: {
+ balance: 20120,
+ currency: 'EUR',
+ },
+ },
+ loginid: 'CR1231123',
+ },
+ modules: {
+ cfd: {
+ current_list: {
+ CR1231123: {
+ landing_company_short: 'svg',
+ },
+ },
+ },
+ },
+ });
+ const wrapper = ({ children }: { children: JSX.Element }) => (
+
{children}
+ );
+
+ const { container } = render(
, { wrapper });
+ expect(container).toBeInTheDocument();
+ fireEvent.click(screen.getByTestId('dt_currency-switcher__arrow'));
+ expect(mock.traders_hub.openModal).toHaveBeenCalled();
+ });
+});
diff --git a/packages/appstore/src/components/currency-switcher-card/real/__tests__/real-account-switcher.spec.tsx b/packages/appstore/src/components/currency-switcher-card/real/__tests__/real-account-switcher.spec.tsx
new file mode 100644
index 000000000000..8dacb5d26ba0
--- /dev/null
+++ b/packages/appstore/src/components/currency-switcher-card/real/__tests__/real-account-switcher.spec.tsx
@@ -0,0 +1,122 @@
+import React from 'react';
+import RealAccountSwitcher from '../real-account-switcher';
+import { render, screen } from '@testing-library/react';
+import { StoreProvider, mockStore } from '@deriv/stores';
+
+jest.mock('Components/containers/currency-switcher-container', () => ({
+ __esModule: true,
+ default: ({ children }: { children: JSX.Element }) =>
{children}
,
+}));
+
+jest.mock('../real-account-card', () => ({
+ __esModule: true,
+ default: () =>
RealAccountCard
,
+}));
+
+jest.mock('@deriv/account', () => ({
+ __esModule: true,
+ getStatusBadgeConfig: () => ({
+ text: 'Pending verification',
+ icon: 'pending',
+ }),
+}));
+
+jest.mock('Components/pre-loader/currency-switcher-loader', () => ({
+ __esModule: true,
+ default: () =>
Loader
,
+}));
+
+describe('RealAccountSwitcher', () => {
+ it('should render the component', () => {
+ const mock = mockStore({});
+ const wrapper = ({ children }: { children: JSX.Element }) => (
+
{children}
+ );
+
+ const { container } = render(
, { wrapper });
+ expect(container).toBeInTheDocument();
+ });
+
+ it('should render AccountNeedsVerification component with the correct pending status', () => {
+ const mock = mockStore({
+ traders_hub: {
+ multipliers_account_status: 'pending',
+ is_eu_user: true,
+ },
+ });
+ const wrapper = ({ children }: { children: JSX.Element }) => (
+
{children}
+ );
+
+ const { container } = render(
, { wrapper });
+ expect(container).toBeInTheDocument();
+ expect(screen.getByText('Pending verification')).toBeInTheDocument();
+ expect(screen.getByText('Pending verification')).toHaveClass(
+ 'switcher-status-badge__container switcher-status-badge__container--pending'
+ );
+ });
+
+ it('should render RealAccountCard if its an eu user and has a maltainvest account', () => {
+ const mock = mockStore({
+ client: {
+ has_maltainvest_account: true,
+ },
+ traders_hub: {
+ is_eu_user: true,
+ },
+ });
+ const wrapper = ({ children }: { children: JSX.Element }) => (
+
{children}
+ );
+
+ const { container } = render(
, { wrapper });
+ expect(container).toBeInTheDocument();
+ expect(screen.getByText('RealAccountCard')).toBeInTheDocument();
+ });
+
+ it('should render RealAccountCard if the user has no cr account and not an eu user', () => {
+ const mock = mockStore({});
+ const wrapper = ({ children }: { children: JSX.Element }) => (
+
{children}
+ );
+
+ const { container } = render(
, { wrapper });
+ expect(container).toBeInTheDocument();
+ expect(screen.getByText('RealAccountCard')).toBeInTheDocument();
+ });
+
+ it('should render Currency Switcher Loader if is_switching is true', () => {
+ const mock = mockStore({
+ client: {
+ is_switching: true,
+ },
+ });
+ const wrapper = ({ children }: { children: JSX.Element }) => (
+
{children}
+ );
+
+ const { container } = render(
, { wrapper });
+ expect(container).toBeInTheDocument();
+ expect(screen.getByText('Loader')).toBeInTheDocument();
+ });
+
+ it('should render empty div', () => {
+ const mock = mockStore({
+ client: {
+ is_switching: false,
+ has_maltainvest_account: false,
+ },
+ traders_hub: {
+ is_eu_user: false,
+ no_CR_account: true,
+ },
+ });
+ const wrapper = ({ children }: { children: JSX.Element }) => (
+
{children}
+ );
+
+ const { container } = render(
, { wrapper });
+ expect(container).toBeInTheDocument();
+ expect(screen.queryByText('RealAccountCard')).not.toBeInTheDocument();
+ });
+});
diff --git a/packages/appstore/src/components/currency-switcher-card/real/real-account-card.tsx b/packages/appstore/src/components/currency-switcher-card/real/real-account-card.tsx
index 1618f61b4c4c..b70da515b9e5 100644
--- a/packages/appstore/src/components/currency-switcher-card/real/real-account-card.tsx
+++ b/packages/appstore/src/components/currency-switcher-card/real/real-account-card.tsx
@@ -1,30 +1,33 @@
import React from 'react';
import { useHistory } from 'react-router';
-import { observer } from 'mobx-react-lite';
import { Button, Text } from '@deriv/components';
import { formatMoney, getCurrencyName, routes } from '@deriv/shared';
-import { localize } from '@deriv/translations';
+import { Localize } from '@deriv/translations';
import BalanceText from 'Components/elements/text/balance-text';
import CurrencySwitcherContainer from 'Components/containers/currency-switcher-container';
-import { TRootStore } from 'Types';
-import { useStores } from 'Stores/index';
+import { useStore, observer } from '@deriv/stores';
+import { IsIconCurrency } from 'Assets/svgs/currency';
const default_balance = { balance: 0, currency: 'USD' };
-const RealAccountCard = () => {
+const RealAccountCard = observer(() => {
const history = useHistory();
- const store = useStores();
- const { client, modules, traders_hub }: TRootStore = store;
+
+ const { client, common, modules, traders_hub } = useStore();
const { accounts, loginid } = client;
+ const { current_language } = common;
const { current_list } = modules.cfd;
const { openModal, is_eu_user } = traders_hub;
- const { balance, currency } = accounts[loginid] || default_balance;
+
+ const { balance, currency } = loginid ? accounts[loginid] : default_balance;
const has_mf_mt5_account = Object.keys(current_list)
.map(key => current_list[key])
.some(account => account.landing_company_short === 'maltainvest');
+ const get_currency = (IsIconCurrency(currency?.toUpperCase()) && currency) || 'USD';
+
return (
{
{getCurrencyName(currency)}
}
- icon={currency}
+ icon={get_currency}
onClick={() => {
if (!is_eu_user && !has_mf_mt5_account) {
openModal('currency_selection');
@@ -44,19 +47,19 @@ const RealAccountCard = () => {
{
e.stopPropagation();
- history.push(routes.cashier_deposit);
+ history.push(`${routes.cashier_deposit}#deposit`);
}}
secondary
className='currency-switcher__button'
>
- {localize('Deposit')}
+
}
has_interaction
>
-
+
);
-};
+});
-export default observer(RealAccountCard);
+export default RealAccountCard;
diff --git a/packages/appstore/src/components/currency-switcher-card/real/real-account-switcher.tsx b/packages/appstore/src/components/currency-switcher-card/real/real-account-switcher.tsx
index 343da9fdd321..28c7d9dd1985 100644
--- a/packages/appstore/src/components/currency-switcher-card/real/real-account-switcher.tsx
+++ b/packages/appstore/src/components/currency-switcher-card/real/real-account-switcher.tsx
@@ -1,23 +1,24 @@
import React from 'react';
-import { observer } from 'mobx-react-lite';
import { getStatusBadgeConfig } from '@deriv/account';
import { StatusBadge, Text } from '@deriv/components';
import CurrencySwitcherContainer from 'Components/containers/currency-switcher-container';
import CurrencySwitcherLoader from 'Components/pre-loader/currency-switcher-loader';
-import { useStores } from 'Stores/index';
+import { useStore, observer } from '@deriv/stores';
import RealAccountCard from './real-account-card';
import './real-account-switcher.scss';
+import { IsIconCurrency } from 'Assets/svgs/currency';
type AccountNeedsVerificationProps = {
multipliers_account_status: string;
};
+
const AccountNeedsVerification = observer(({ multipliers_account_status }: AccountNeedsVerificationProps) => {
- const { client, traders_hub } = useStores();
+ const { client, traders_hub } = useStore();
const { account_list, loginid } = client;
const { openModal, openFailedVerificationModal } = traders_hub;
- const title = account_list.find((acc: { loginid: string }) => loginid === acc.loginid).title;
- const icon = account_list.find((acc: { loginid: string }) => loginid === acc.loginid).icon;
+ const account = account_list?.find((acc: { loginid?: string }) => loginid === acc?.loginid);
+ const icon_title = account?.title;
const { text: badge_text, icon: badge_icon } = getStatusBadgeConfig(
multipliers_account_status,
@@ -30,10 +31,10 @@ const AccountNeedsVerification = observer(({ multipliers_account_status }: Accou
className='real-account-switcher__container'
title={
- {title}
+ {icon_title}
}
- icon={icon}
+ icon={IsIconCurrency(icon_title) ? icon_title : 'USD'}
onClick={() => {
return openModal('currency_selection');
}}
@@ -43,16 +44,10 @@ const AccountNeedsVerification = observer(({ multipliers_account_status }: Accou
);
});
-const RealAccountSwitcher = () => {
- const { client, traders_hub } = useStores();
+const RealAccountSwitcher = observer(() => {
+ const { client, traders_hub } = useStore();
const { is_logging_in, is_switching, has_maltainvest_account } = client;
- const {
- multipliers_account_status,
-
- is_eu_user,
- no_CR_account,
- no_MF_account,
- } = traders_hub;
+ const { multipliers_account_status, is_eu_user, no_CR_account, no_MF_account } = traders_hub;
const eu_account = is_eu_user && !no_MF_account;
const cr_account = !is_eu_user && !no_CR_account;
@@ -76,6 +71,6 @@ const RealAccountSwitcher = () => {
return
;
}
return null;
-};
+});
-export default observer(RealAccountSwitcher);
+export default RealAccountSwitcher;
diff --git a/packages/appstore/src/components/elements/text/__tests__/balance-text.spec.tsx b/packages/appstore/src/components/elements/text/__tests__/balance-text.spec.tsx
new file mode 100644
index 000000000000..cdc113b5b034
--- /dev/null
+++ b/packages/appstore/src/components/elements/text/__tests__/balance-text.spec.tsx
@@ -0,0 +1,123 @@
+import React from 'react';
+import BalanceText from '../balance-text';
+import { render, screen } from '@testing-library/react';
+import { StoreProvider, mockStore } from '@deriv/stores';
+
+describe('BalanceText', () => {
+ it('should render the component', () => {
+ const mock = mockStore({});
+
+ const wrapper = ({ children }: { children: JSX.Element }) => (
+
{children}
+ );
+
+ const { container } = render(
, {
+ wrapper,
+ });
+ expect(container).toBeInTheDocument();
+ });
+
+ it('should render the correct balance and currency', () => {
+ const mock = mockStore({});
+
+ const wrapper = ({ children }: { children: JSX.Element }) => (
+
{children}
+ );
+
+ const { container } = render(
, {
+ wrapper,
+ });
+ expect(container).toBeInTheDocument();
+ expect(screen.getByText('1,000.00')).toBeInTheDocument();
+ expect(screen.getByText('USD')).toBeInTheDocument();
+ });
+
+ it('should render the correct div class for dotted underline_style', () => {
+ const mock = mockStore({});
+
+ const wrapper = ({ children }: { children: JSX.Element }) => (
+
{children}
+ );
+
+ const { container } = render(
, {
+ wrapper,
+ });
+ expect(container).toBeInTheDocument();
+ expect(screen.getByTestId('dt_balance-text__container')).toHaveClass('balance-text--dotted');
+ });
+
+ it('should have classname ending with demo if user has selected_account_type demo and has an active real account ', () => {
+ const mock = mockStore({
+ traders_hub: {
+ selected_account_type: 'demo',
+ },
+ client: {
+ has_active_real_account: true,
+ },
+ });
+
+ const wrapper = ({ children }: { children: JSX.Element }) => (
+
{children}
+ );
+
+ const { container } = render(
, {
+ wrapper,
+ });
+ expect(container).toBeInTheDocument();
+ expect(screen.getByText('1,000.00')).toHaveClass('balance-text__text--demo');
+ });
+
+ it('should have classname ending with real if user has selected_account_type demo and has an active real account ', () => {
+ const mock = mockStore({
+ traders_hub: {
+ selected_account_type: 'real',
+ },
+ client: {
+ has_active_real_account: true,
+ },
+ });
+
+ const wrapper = ({ children }: { children: JSX.Element }) => (
+
{children}
+ );
+
+ const { container } = render(
, {
+ wrapper,
+ });
+ expect(container).toBeInTheDocument();
+ expect(screen.getByText('1,000.00')).toHaveClass('balance-text__text--real');
+ });
+
+ it('should not have classname if selected_account_type is empty ', () => {
+ const mock = mockStore({
+ traders_hub: {
+ selected_account_type: '',
+ },
+ });
+
+ const wrapper = ({ children }: { children: JSX.Element }) => (
+
{children}
+ );
+
+ const { container } = render(
, {
+ wrapper,
+ });
+ expect(container).toBeInTheDocument();
+ expect(screen.getByText('1,000.00')).not.toHaveClass('balance-text__text--real');
+ expect(screen.getByText('1,000.00')).not.toHaveClass('balance-text__text--demo');
+ });
+
+ it('should have classname as container if underline_style is none', () => {
+ const mock = mockStore({});
+
+ const wrapper = ({ children }: { children: JSX.Element }) => (
+
{children}
+ );
+
+ const { container } = render(
, {
+ wrapper,
+ });
+ expect(container).toBeInTheDocument();
+ expect(screen.getByTestId('dt_balance-text__container')).toHaveClass('balance-text__container');
+ });
+});
diff --git a/packages/appstore/src/components/elements/text/balance-text.scss b/packages/appstore/src/components/elements/text/balance-text.scss
index b6042caf9dae..f7027f809c59 100644
--- a/packages/appstore/src/components/elements/text/balance-text.scss
+++ b/packages/appstore/src/components/elements/text/balance-text.scss
@@ -5,27 +5,17 @@
&__text {
&--demo {
- color: var(--text-profit-success);
+ color: var(--status-info);
padding-right: 0.5rem;
}
&--real {
- color: var(--text-general);
- padding-right: 0.5rem;
- }
-
- &--amount {
- color: var(--text-profit-success);
- padding-right: 0.5rem;
- }
-
- &--no-amount {
- color: var(--text-general);
+ color: var(--status-success);
padding-right: 0.5rem;
}
}
&--dotted {
- border-bottom: 4px dotted var(--checkbox-disabled-grey);
+ border-bottom: 4px dotted var(--border-normal-3);
}
}
diff --git a/packages/appstore/src/components/elements/text/balance-text.tsx b/packages/appstore/src/components/elements/text/balance-text.tsx
index da5f53a2189f..1c08fe0b1884 100644
--- a/packages/appstore/src/components/elements/text/balance-text.tsx
+++ b/packages/appstore/src/components/elements/text/balance-text.tsx
@@ -2,7 +2,7 @@ import React from 'react';
import classNames from 'classnames';
import { Text } from '@deriv/components';
import { formatMoney } from '@deriv/shared';
-import { useStores } from 'Stores';
+import { useStore, observer } from '@deriv/stores';
import './balance-text.scss';
// Todo: this definitely needs to be somewhere else
@@ -15,17 +15,16 @@ type BalanceTextProps = {
underline_style?: 'dotted' | 'solid' | 'none';
};
-const BalanceText = ({ balance, currency, size = 'm', underline_style = 'none' }: BalanceTextProps) => {
- const { client, traders_hub } = useStores();
+const BalanceText = observer(({ balance, currency, size = 'm', underline_style = 'none' }: BalanceTextProps) => {
+ const { traders_hub } = useStore();
const { selected_account_type } = traders_hub;
- const { has_active_real_account } = client;
const getTextClassName = () => {
if (selected_account_type === 'demo') {
- return has_active_real_account ? 'balance-text__text--demo' : 'balance-text__text--amount';
+ return 'balance-text__text--demo';
}
if (selected_account_type === 'real') {
- return has_active_real_account ? 'balance-text__text--real' : 'balance-text__text--no-amount';
+ return 'balance-text__text--real';
}
return '';
};
@@ -33,6 +32,7 @@ const BalanceText = ({ balance, currency, size = 'm', underline_style = 'none' }
return (
{formatMoney(currency, balance, true)}
@@ -42,6 +42,6 @@ const BalanceText = ({ balance, currency, size = 'm', underline_style = 'none' }
);
-};
+});
export default BalanceText;
diff --git a/packages/appstore/src/components/empty-state/__tests__/empty-state.spec.tsx b/packages/appstore/src/components/empty-state/__tests__/empty-state.spec.tsx
new file mode 100644
index 000000000000..64e03e7ceefc
--- /dev/null
+++ b/packages/appstore/src/components/empty-state/__tests__/empty-state.spec.tsx
@@ -0,0 +1,41 @@
+import React from 'react';
+import EmptyState from '../empty-state';
+import { render, screen } from '@testing-library/react';
+
+describe('EmptyState', () => {
+ it('should render correctly', () => {
+ const { container } = render(
+
(
+ <>
+ Message
+ Some content
+ >
+ )}
+ renderTitle={() => Title
}
+ />
+ );
+ expect(container).toBeInTheDocument();
+ });
+
+ it('should render correctly with the correct text', () => {
+ const { container } = render(
+ (
+ <>
+ Message
+ Some content
+ >
+ )}
+ renderTitle={() => Title
}
+ />
+ );
+
+ expect(container).toBeInTheDocument();
+ expect(screen.getByText('Message')).toBeInTheDocument();
+ expect(screen.getByText('Some content')).toBeInTheDocument();
+ expect(screen.getByText('Title')).toBeInTheDocument();
+ });
+});
diff --git a/packages/appstore/src/components/empty-state/empty-state.tsx b/packages/appstore/src/components/empty-state/empty-state.tsx
index 95cae4072700..c8e653a48b84 100644
--- a/packages/appstore/src/components/empty-state/empty-state.tsx
+++ b/packages/appstore/src/components/empty-state/empty-state.tsx
@@ -7,7 +7,7 @@ type TProps = {
renderTitle: () => React.ReactNode;
};
-const EmptyState: React.FC = ({ icon_name, renderMessage, renderTitle }) => {
+const EmptyState = ({ icon_name, renderMessage, renderTitle }: TProps) => {
return (
diff --git a/packages/appstore/src/components/get-more-accounts/__tests__/get-more-accounts.spec.tsx b/packages/appstore/src/components/get-more-accounts/__tests__/get-more-accounts.spec.tsx
new file mode 100644
index 000000000000..db00b4a6059e
--- /dev/null
+++ b/packages/appstore/src/components/get-more-accounts/__tests__/get-more-accounts.spec.tsx
@@ -0,0 +1,23 @@
+import React from 'react';
+import GetMoreAccounts from '../get-more-accounts';
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+
+describe('GetMoreAccounts', () => {
+ const mock_props = {
+ description: 'description',
+ icon: 'icon',
+ title: 'title',
+ onClick: jest.fn(),
+ };
+ it('should render the component', () => {
+ const { container } = render(
);
+ expect(container).toBeInTheDocument();
+ });
+
+ it('should call the onClick prop when clicked', () => {
+ render(
);
+ userEvent.click(screen.getByText('title'));
+ expect(mock_props.onClick).toHaveBeenCalled();
+ });
+});
diff --git a/packages/appstore/src/components/main-title-bar/__tests__/account-type-dropdown.spec.tsx b/packages/appstore/src/components/main-title-bar/__tests__/account-type-dropdown.spec.tsx
new file mode 100644
index 000000000000..50823fa1f02f
--- /dev/null
+++ b/packages/appstore/src/components/main-title-bar/__tests__/account-type-dropdown.spec.tsx
@@ -0,0 +1,53 @@
+import React from 'react';
+import AccountTypeDropdown from '../account-type-dropdown';
+import { render, screen } from '@testing-library/react';
+import { StoreProvider, mockStore } from '@deriv/stores';
+import userEvent from '@testing-library/user-event';
+
+describe('AccountTypeDropdown', () => {
+ it('should render the component', () => {
+ const mock = mockStore({});
+ const wrapper = ({ children }: { children: JSX.Element }) => (
+
{children}
+ );
+
+ const { container } = render(
, { wrapper });
+ expect(container).toBeInTheDocument();
+ });
+
+ it('should show real account type', () => {
+ const mock = mockStore({});
+ const wrapper = ({ children }: { children: JSX.Element }) => (
+
{children}
+ );
+
+ render(
, { wrapper });
+ expect(screen.getByText('Real')).toBeInTheDocument();
+ });
+
+ it('should show demo account type', () => {
+ const mock = mockStore({
+ traders_hub: {
+ selected_account_type: 'demo',
+ },
+ });
+ const wrapper = ({ children }: { children: JSX.Element }) => (
+
{children}
+ );
+
+ render(
, { wrapper });
+ expect(screen.getByText('Demo')).toBeInTheDocument();
+ });
+
+ it('should change to demo account type', () => {
+ const mock = mockStore({});
+ const wrapper = ({ children }: { children: JSX.Element }) => (
+
{children}
+ );
+
+ render(
, { wrapper });
+ userEvent.click(screen.getByText('Real'));
+ userEvent.click(screen.getByText('Demo'));
+ expect(mock.traders_hub.selectAccountType).toBeCalledTimes(1);
+ });
+});
diff --git a/packages/appstore/src/components/main-title-bar/__tests__/regulators-switcher.spec.tsx b/packages/appstore/src/components/main-title-bar/__tests__/regulators-switcher.spec.tsx
new file mode 100644
index 000000000000..c42c2275ee9a
--- /dev/null
+++ b/packages/appstore/src/components/main-title-bar/__tests__/regulators-switcher.spec.tsx
@@ -0,0 +1,103 @@
+import React from 'react';
+import RegulatorSwitcher from '../regulators-switcher';
+import { render, screen, fireEvent } from '@testing-library/react';
+import { StoreProvider, mockStore } from '@deriv/stores';
+
+jest.mock('../../pre-loader/regulations-switcher-loader', () => ({
+ __esModule: true,
+ default: () =>
RegulationsSwitcherLoader
,
+}));
+
+describe('RegulatorSwitcher', () => {
+ it('should render correctly', () => {
+ const mock = mockStore({});
+
+ const wrapper = ({ children }: { children: JSX.Element }) => (
+
{children}
+ );
+
+ const { container } = render(
, {
+ wrapper,
+ });
+ expect(container).toBeInTheDocument();
+ });
+
+ it('should render the correct text', () => {
+ const mock = mockStore({});
+
+ const wrapper = ({ children }: { children: JSX.Element }) => (
+
{children}
+ );
+
+ const { container } = render(
, {
+ wrapper,
+ });
+ expect(container).toBeInTheDocument();
+ expect(screen.getByText('Regulation:')).toBeInTheDocument();
+ expect(screen.getByText('Non-EU')).toBeInTheDocument();
+ expect(screen.getByText('EU')).toBeInTheDocument();
+ });
+ it('should open toggleRegulatorsCompareModal if the user clicks on the icon', () => {
+ const mock = mockStore({});
+
+ const wrapper = ({ children }: { children: JSX.Element }) => (
+
{children}
+ );
+
+ const { container } = render(
, {
+ wrapper,
+ });
+ expect(container).toBeInTheDocument();
+ fireEvent.click(screen.getByTestId('dt_regulators-switcher-icon'));
+ expect(mock.traders_hub.toggleRegulatorsCompareModal).toHaveBeenCalled();
+ });
+
+ it('should switch the region to EU if the user clicks on EU', () => {
+ const mock = mockStore({});
+
+ const wrapper = ({ children }: { children: JSX.Element }) => (
+
{children}
+ );
+
+ const { container } = render(
, {
+ wrapper,
+ });
+ expect(container).toBeInTheDocument();
+ fireEvent.click(screen.getByText('EU'));
+ expect(mock.traders_hub.selectRegion).toHaveBeenCalledWith('EU');
+ });
+
+ it('should switch the region to Non-EU if the user clicks on Non-EU', () => {
+ const mock = mockStore({});
+
+ const wrapper = ({ children }: { children: JSX.Element }) => (
+
{children}
+ );
+
+ const { container } = render(
, {
+ wrapper,
+ });
+ expect(container).toBeInTheDocument();
+ fireEvent.click(screen.getByText('Non-EU'));
+ expect(mock.traders_hub.selectRegion).toHaveBeenCalledWith('Non-EU');
+ });
+
+ it('should show loader if is_switching is true', () => {
+ const mock = mockStore({
+ client: {
+ is_switching: true,
+ },
+ });
+
+ const wrapper = ({ children }: { children: JSX.Element }) => (
+
{children}
+ );
+
+ const { container } = render(
, {
+ wrapper,
+ });
+
+ expect(container).toBeInTheDocument();
+ expect(screen.getByText('RegulationsSwitcherLoader')).toBeInTheDocument();
+ });
+});
diff --git a/packages/appstore/src/components/main-title-bar/account-type-dropdown.scss b/packages/appstore/src/components/main-title-bar/account-type-dropdown.scss
index 0f7c8674fe78..e1c19419d3bf 100644
--- a/packages/appstore/src/components/main-title-bar/account-type-dropdown.scss
+++ b/packages/appstore/src/components/main-title-bar/account-type-dropdown.scss
@@ -11,8 +11,8 @@
border: none;
& > div.dc-dropdown {
&-container {
- min-width: 8.2rem;
- width: 8.2rem;
+ min-width: 7.1rem;
+ width: auto;
}
}
@@ -32,41 +32,41 @@
line-height: 20px;
letter-spacing: 0em;
text-align: left;
- padding-left: 0.6em;
+ padding-left: 0.4em;
+ }
+ & > .dc-dropdown__display-text {
+ padding-right: 2.3em;
}
}
&--real,
&--real:focus,
&--real:hover {
- border: 1px solid var(--status-info);
- width: 87%;
+ border: 1px solid var(--status-success);
+ min-width: 87%;
& > .dc-text {
- color: var(--status-info);
+ color: var(--status-success);
}
}
&--demo,
&--demo:focus,
&--demo:hover {
- border: 1px solid var(--status-success);
+ border: 1px solid var(--status-info);
width: 90%;
& > .dc-text {
- color: var(--status-success);
- padding-left: 0.8em;
+ color: var(--status-info);
+ padding-left: 0.65em;
}
}
&__icon {
- &--real,
- &--demo {
- transform: none !important;
- }
+ transform: none;
&--real {
- --fill-color1: var(--status-info) !important;
- right: 1.8rem;
+ --fill-color1: var(--status-success);
+ right: 0.7rem;
}
&--demo {
- --fill-color1: var(--status-success) !important;
- right: 1.5rem;
+ --fill-color1: var(--status-info);
+ right: 1.4rem;
}
}
}
diff --git a/packages/appstore/src/components/main-title-bar/account-type-dropdown.tsx b/packages/appstore/src/components/main-title-bar/account-type-dropdown.tsx
index 72d875adf60e..d9cae3aa7804 100644
--- a/packages/appstore/src/components/main-title-bar/account-type-dropdown.tsx
+++ b/packages/appstore/src/components/main-title-bar/account-type-dropdown.tsx
@@ -1,15 +1,15 @@
import React from 'react';
import classNames from 'classnames';
-import { observer } from 'mobx-react-lite';
import { Dropdown } from '@deriv/components';
-import { account_types } from 'Constants/platform-config';
-import { useStores } from 'Stores';
+import { getAccountTypes } from 'Constants/platform-config';
+import { useStore, observer } from '@deriv/stores';
import './account-type-dropdown.scss';
-const AccountTypeDropdown = () => {
- const { traders_hub, client } = useStores();
+const AccountTypeDropdown = observer(() => {
+ const { traders_hub, client, common } = useStore();
const { selected_account_type, selectAccountType } = traders_hub;
const { setPrevAccountType } = client;
+ const { current_language } = common;
return (
@@ -20,7 +20,8 @@ const AccountTypeDropdown = () => {
'account-type-dropdown',
`account-type-dropdown--${selected_account_type}`
)}
- list={account_types}
+ list={getAccountTypes()}
+ key={`account-type-dropdown__icon--key-${current_language}`}
onChange={async (e: React.ChangeEvent) => {
await selectAccountType(e.target.value);
await setPrevAccountType(e.target.value);
@@ -28,6 +29,6 @@ const AccountTypeDropdown = () => {
/>
);
-};
+});
-export default observer(AccountTypeDropdown);
+export default AccountTypeDropdown;
diff --git a/packages/appstore/src/components/main-title-bar/asset-summary.tsx b/packages/appstore/src/components/main-title-bar/asset-summary.tsx
index 354ca1c35039..ae8734f3cfc3 100644
--- a/packages/appstore/src/components/main-title-bar/asset-summary.tsx
+++ b/packages/appstore/src/components/main-title-bar/asset-summary.tsx
@@ -1,71 +1,26 @@
import React from 'react';
-import { observer } from 'mobx-react-lite';
import { Text, Popover } from '@deriv/components';
import { localize } from '@deriv/translations';
import { isMobile } from '@deriv/shared';
import BalanceText from 'Components/elements/text/balance-text';
-import { useStores } from 'Stores';
+import { observer, useStore } from '@deriv/stores';
import './asset-summary.scss';
import TotalAssetsLoader from 'Components/pre-loader/total-assets-loader';
+import { useTotalAccountBalance, useCFDAccounts, usePlatformAccounts } from '@deriv/hooks';
-const AssetSummary = () => {
- const { traders_hub, client, common } = useStores();
- const {
- selected_account_type,
- platform_real_balance,
- cfd_demo_balance,
- platform_demo_balance,
- cfd_real_balance,
- is_eu_user,
- no_CR_account,
- no_MF_account,
- } = traders_hub;
- const { is_logging_in, is_switching } = client;
- const { getExchangeRate } = common;
-
- const [exchanged_rate_cfd_real, setExchangedRateCfdReal] = React.useState(1);
- const [exchanged_rate_cfd_demo, setExchangedRateCfdDemo] = React.useState(1);
-
- React.useEffect(() => {
- const getCurrentExchangeRate = (
- currency: string,
- setExchangeRate: React.Dispatch
>,
- base_currency = platform_real_balance.currency
- ) => {
- if (currency) {
- getExchangeRate(currency, base_currency).then((res: number) => {
- setExchangeRate(res);
- });
- }
- };
-
- if (cfd_real_balance.currency !== platform_real_balance.currency) {
- getCurrentExchangeRate(cfd_real_balance.currency, setExchangedRateCfdReal);
- }
- if (cfd_demo_balance.currency !== platform_demo_balance.currency) {
- getCurrentExchangeRate(cfd_demo_balance.currency, setExchangedRateCfdDemo, platform_demo_balance.currency);
- }
- }, [
- cfd_demo_balance.currency,
- cfd_real_balance.currency,
- getExchangeRate,
- platform_demo_balance.currency,
- platform_real_balance.currency,
- ]);
-
- const getTotalBalance = () => {
- if (selected_account_type === 'real') {
- return {
- balance: platform_real_balance.balance + cfd_real_balance.balance * exchanged_rate_cfd_real,
- currency: platform_real_balance.currency,
- };
- }
-
- return {
- balance: platform_demo_balance.balance + cfd_demo_balance.balance * exchanged_rate_cfd_demo,
- currency: platform_demo_balance.currency,
- };
- };
+const AssetSummary = observer(() => {
+ const { traders_hub, client, common } = useStore();
+ const { selected_account_type, is_eu_user, no_CR_account, no_MF_account } = traders_hub;
+ const { is_logging_in, is_switching, default_currency } = client;
+ const { current_language } = common;
+ const { real: platform_real_accounts, demo: platform_demo_account } = usePlatformAccounts();
+ const { real: cfd_real_accounts, demo: cfd_demo_accounts } = useCFDAccounts();
+ const platform_real_balance = useTotalAccountBalance(platform_real_accounts);
+ const cfd_real_balance = useTotalAccountBalance(cfd_real_accounts);
+ const cfd_demo_balance = useTotalAccountBalance(cfd_demo_accounts);
+ const is_real = selected_account_type === 'real';
+ const real_total_balance = platform_real_balance.balance + cfd_real_balance.balance;
+ const demo_total_balance = (platform_demo_account?.balance || 0) + cfd_demo_balance.balance;
const has_active_related_deriv_account = !((no_CR_account && !is_eu_user) || (no_MF_account && is_eu_user)); // if selected region is non-eu, check active cr accounts, if selected region is eu- check active mf accounts
const eu_account = is_eu_user && !no_MF_account;
@@ -87,7 +42,7 @@ const AssetSummary = () => {
{has_active_related_deriv_account || selected_account_type === 'demo' ? (
{!isMobile() ? (
-
+
{localize('Total assets')}
) : null}
@@ -98,8 +53,12 @@ const AssetSummary = () => {
is_bubble_hover_enabled
>
@@ -107,6 +66,6 @@ const AssetSummary = () => {
) : null}
);
-};
+});
-export default observer(AssetSummary);
+export default AssetSummary;
diff --git a/packages/appstore/src/components/main-title-bar/index.tsx b/packages/appstore/src/components/main-title-bar/index.tsx
index 13450b67080e..ceeeb4e8304d 100644
--- a/packages/appstore/src/components/main-title-bar/index.tsx
+++ b/packages/appstore/src/components/main-title-bar/index.tsx
@@ -24,8 +24,8 @@ const MainTitleBar = () => {
-
- {localize("Trader's hub")}
+
+ {localize("Trader's Hub")}
@@ -34,8 +34,8 @@ const MainTitleBar = () => {
-
- {localize("Trader's hub")}
+
+ {localize("Trader's Hub")}
diff --git a/packages/appstore/src/components/main-title-bar/regulators-switcher.tsx b/packages/appstore/src/components/main-title-bar/regulators-switcher.tsx
index 2fbcca99361b..33133cb1fb9c 100644
--- a/packages/appstore/src/components/main-title-bar/regulators-switcher.tsx
+++ b/packages/appstore/src/components/main-title-bar/regulators-switcher.tsx
@@ -1,11 +1,10 @@
import React, { HTMLAttributes } from 'react';
import classNames from 'classnames';
-import { observer } from 'mobx-react-lite';
import { Icon, Text } from '@deriv/components';
import { localize } from '@deriv/translations';
import { region_availability } from 'Constants/platform-config';
import RegulationsSwitcherLoader from 'Components/pre-loader/regulations-switcher-loader';
-import { useStores } from 'Stores/index';
+import { useStore, observer } from '@deriv/stores';
import './regulators-switcher.scss';
type SwitcherItemProps = {
@@ -16,15 +15,15 @@ type SwitcherItemProps = {
const SwitcherItem = ({ children, is_selected, ...props }: SwitcherItemProps & HTMLAttributes
) => {
return (
-
+
{children}
);
};
-const RegulatorSwitcher = () => {
- const { traders_hub, client } = useStores();
+const RegulatorSwitcher = observer(() => {
+ const { traders_hub, client } = useStore();
const { toggleRegulatorsCompareModal } = traders_hub;
const { is_switching } = client;
@@ -32,7 +31,11 @@ const RegulatorSwitcher = () => {
{localize('Regulation:')}
-
toggleRegulatorsCompareModal()}>
+
toggleRegulatorsCompareModal()}
+ >
@@ -57,6 +60,6 @@ const RegulatorSwitcher = () => {
)}
);
-};
+});
-export default observer(RegulatorSwitcher);
+export default RegulatorSwitcher;
diff --git a/packages/appstore/src/components/modals/account-type-modal/account-type-modal.tsx b/packages/appstore/src/components/modals/account-type-modal/account-type-modal.tsx
index 57119007fb8b..bd10965f0c96 100644
--- a/packages/appstore/src/components/modals/account-type-modal/account-type-modal.tsx
+++ b/packages/appstore/src/components/modals/account-type-modal/account-type-modal.tsx
@@ -5,21 +5,11 @@ import { observer } from 'mobx-react-lite';
import classNames from 'classnames';
import { useStores } from 'Stores/index';
import TradigPlatformIconProps from 'Assets/svgs/trading-platform';
-import { TModalContent, TAccountType, TAccountCard, TTradingPlatformAvailableAccount } from './types';
+import { TModalContent, TAccountCard, TTradingPlatformAvailableAccount } from './types';
import { TIconTypes } from 'Types';
import { CFD_PLATFORMS } from '@deriv/shared';
-
-const derived_account: TAccountType = {
- title_and_type: localize('Derived'),
- icon: 'Derived',
- description: localize('Trade CFDs on MT5 with Derived indices that simulate real-world market movements.'),
-};
-
-const financial_account: TAccountType = {
- title_and_type: localize('Financial'),
- icon: 'Financial',
- description: localize('Trade CFDs on MT5 with forex, stock indices, commodities, and cryptocurrencies.'),
-};
+import { getDerivedAccount, getFinancialAccount, getSwapFreeAccount } from '../../../helpers/account-helper';
+import { useHasSwapFreeAccount } from '@deriv/hooks';
const AccountCard = ({ selectAccountTypeCard, account_type_card, title_and_type, description, icon }: TAccountCard) => {
const cardSelection = (cardType: string) => {
@@ -54,25 +44,35 @@ const ModalContent = ({
selectAccountTypeCard,
is_financial_available,
is_synthetic_available,
+ is_swapfree_available,
}: TModalContent) => {
return (
{is_synthetic_available && (
selectAccountTypeCard(`${derived_account.title_and_type}`)}
- description={derived_account.description}
- title_and_type={derived_account.title_and_type}
- icon={derived_account.icon}
+ selectAccountTypeCard={() => selectAccountTypeCard(`${getDerivedAccount().title_and_type}`)}
+ description={getDerivedAccount().description}
+ title_and_type={getDerivedAccount().title_and_type}
+ icon={getDerivedAccount().icon}
/>
)}
{is_financial_available && (
selectAccountTypeCard(`${financial_account.title_and_type}`)}
- description={financial_account.description}
- title_and_type={financial_account.title_and_type}
- icon={financial_account.icon}
+ selectAccountTypeCard={() => selectAccountTypeCard(`${getFinancialAccount().title_and_type}`)}
+ description={getFinancialAccount().description}
+ title_and_type={getFinancialAccount().title_and_type}
+ icon={getFinancialAccount().icon}
+ />
+ )}
+ {is_swapfree_available && (
+ selectAccountTypeCard(`${getSwapFreeAccount().title_and_type}`)}
+ description={getSwapFreeAccount().description}
+ title_and_type={getSwapFreeAccount().title_and_type}
+ icon={getSwapFreeAccount().icon}
/>
)}
@@ -92,6 +92,13 @@ const MT5AccountTypeModal = () => {
const { trading_platform_available_accounts } = client;
const { enableApp, disableApp } = ui;
const { setAppstorePlatform } = common;
+
+ React.useEffect(() => {
+ if (!is_account_type_modal_visible) {
+ selectAccountTypeCard('');
+ }
+ }, [is_account_type_modal_visible, selectAccountTypeCard]);
+
const is_financial_available = trading_platform_available_accounts.some(
(available_account: TTradingPlatformAvailableAccount) => available_account.market_type === 'financial'
);
@@ -99,11 +106,23 @@ const MT5AccountTypeModal = () => {
const is_synthetic_available = trading_platform_available_accounts.some(
(available_account: TTradingPlatformAvailableAccount) => available_account.market_type === 'gaming'
);
+ const is_swapfree_available = useHasSwapFreeAccount();
+
+ const set_account_type = () => {
+ const localizedAccountType = localize(account_type_card);
- const set_account_type = () =>
- account_type_card === 'Derived'
- ? setAccountType({ category: 'real', type: 'synthetic' })
- : setAccountType({ category: 'real', type: 'financial' });
+ switch (localizedAccountType) {
+ case localize('Derived'):
+ setAccountType({ category: 'real', type: 'synthetic' });
+ break;
+ case localize('Financial'):
+ setAccountType({ category: 'real', type: 'financial' });
+ break;
+ default:
+ setAccountType({ category: 'real', type: 'all' });
+ break;
+ }
+ };
return (
@@ -126,6 +145,7 @@ const MT5AccountTypeModal = () => {
selectAccountTypeCard={selectAccountTypeCard}
is_financial_available={is_financial_available}
is_synthetic_available={is_synthetic_available}
+ is_swapfree_available={is_swapfree_available}
/>
{
selectAccountTypeCard={selectAccountTypeCard}
is_financial_available={is_financial_available}
is_synthetic_available={is_synthetic_available}
+ is_swapfree_available={is_swapfree_available}
/>
>;
is_financial_available: boolean;
is_synthetic_available: boolean;
+ is_swapfree_available: boolean;
};
export type TAccountType = {
@@ -20,7 +21,7 @@ export type TAccountCard = {
};
export type TTradingPlatformAvailableAccount = {
- market_type: 'financial' | 'gaming';
+ market_type: 'financial' | 'gaming' | 'all';
name: string;
requirements: {
after_first_deposit: {
diff --git a/packages/appstore/src/components/modals/modal-manager.tsx b/packages/appstore/src/components/modals/modal-manager.tsx
index 321f390e8c5d..f630834a6127 100644
--- a/packages/appstore/src/components/modals/modal-manager.tsx
+++ b/packages/appstore/src/components/modals/modal-manager.tsx
@@ -11,6 +11,7 @@ import {
CFDPasswordManagerModal,
CompareAccountsModal,
} from '@deriv/cfd';
+import { TTradingPlatformAvailableAccount } from './account-type-modal/types';
import MT5AccountTypeModal from './account-type-modal';
import RegulatorsCompareModal from './regulators-compare-modal';
import { useStores } from 'Stores';
@@ -45,8 +46,10 @@ const ModalManager = () => {
toggleMT5TradeModal,
getRealSyntheticAccountsExistingData,
getRealFinancialAccountsExistingData,
+ getRealSwapfreeAccountsExistingData,
current_account,
dxtrade_companies,
+ derivez_companies,
mt5_companies,
topUpVirtual,
} = modules.cfd;
@@ -102,7 +105,7 @@ const ModalManager = () => {
enableCFDPasswordModal();
};
- const existing_accounts_data = (acc_type: 'synthetic' | 'financial') => {
+ const existing_accounts_data = (acc_type: TTradingPlatformAvailableAccount['market_type'] | 'synthetic') => {
const current_list_keys = Object.keys(current_list);
const should_be_enabled = (list_item: TCurrentList) =>
platform === 'dxtrade' ? list_item.enabled === 1 : true;
@@ -123,6 +126,7 @@ const ModalManager = () => {
getRealSyntheticAccountsExistingData(existing_accounts_data('synthetic'));
getRealFinancialAccountsExistingData(existing_accounts_data('financial'));
+ getRealSwapfreeAccountsExistingData(existing_accounts_data('all'));
return (
@@ -134,6 +138,7 @@ const ModalManager = () => {
', () => {
+ const is_blurry = {
+ icon: true,
+ item: false,
+ text: false,
+ get: false,
+ topup: false,
+ trade: false,
+ cfd_item: false,
+ cfd_text: false,
+ options_item: false,
+ options_text: false,
+ cfd_description: false,
+ options_description: false,
+ platformlauncher: false,
+ };
+
+ const is_onboarding_animated = {
+ text: false,
+ trade: false,
+ topup: false,
+ button: false,
+ get: false,
+ };
+
+ test('should render derivez in page if !CFDs_restricted_countries (non-eu countries)', () => {
+ const mock = mockStore({});
+
+ render(
+
+
+
+ );
+ expect(screen.queryByText('Deriv EZ')).toBeInTheDocument();
+ });
+
+ test('should not render derivez if CFDs_restricted_countries: true (eu countries)', () => {
+ const mock = mockStore({
+ traders_hub: {
+ CFDs_restricted_countries: true,
+ },
+ });
+
+ render(
+
+
+
+ );
+ expect(screen.queryByText('Deriv Ez')).not.toBeInTheDocument();
+ });
+});
diff --git a/packages/appstore/src/components/onboarding-new/static-cfd-account-manager.scss b/packages/appstore/src/components/onboarding-new/static-cfd-account-manager.scss
index 8a9e80f000d6..a250743f7385 100644
--- a/packages/appstore/src/components/onboarding-new/static-cfd-account-manager.scss
+++ b/packages/appstore/src/components/onboarding-new/static-cfd-account-manager.scss
@@ -55,6 +55,7 @@
margin-bottom: 0.5rem;
margin-left: 2rem;
cursor: not-allowed;
+ pointer-events: none;
&--blurry {
opacity: 0.2;
@@ -70,6 +71,7 @@
margin-bottom: 0.5rem;
margin-left: 2rem;
cursor: not-allowed;
+ pointer-events: none;
&--blurry {
opacity: 0.2;
@@ -82,8 +84,9 @@
}
&-get {
- cursor: not-allowed;
margin-left: 1rem;
+ user-select: none;
+ pointer-events: none;
&--blurry {
opacity: 0.2;
}
diff --git a/packages/appstore/src/components/onboarding-new/static-cfd-account-manager.tsx b/packages/appstore/src/components/onboarding-new/static-cfd-account-manager.tsx
index dab93cfaad51..773820c69506 100644
--- a/packages/appstore/src/components/onboarding-new/static-cfd-account-manager.tsx
+++ b/packages/appstore/src/components/onboarding-new/static-cfd-account-manager.tsx
@@ -1,6 +1,7 @@
import React from 'react';
import { Button, Text } from '@deriv/components';
import { formatMoney, CFD_PLATFORMS } from '@deriv/shared';
+import { useStore } from '@deriv/stores';
import { Localize, localize } from '@deriv/translations';
import TradigPlatformIconProps from 'Assets/svgs/trading-platform';
import { TPlatform } from 'Types';
@@ -66,6 +67,9 @@ const StaticCFDAccountManager = ({
is_eu_user,
}: TStaticCFDAccountManager) => {
const icon_size = 48;
+ const platform_color = platform === 'options' ? 'prominent' : 'general';
+ const { traders_hub } = useStore();
+ const { is_demo } = traders_hub;
return (
) : (
))}
+ {platform === CFD_PLATFORMS.DERIVEZ && (
+
+ )}
+
{platform === CFD_PLATFORMS.DXTRADE && (
{appname}
@@ -167,14 +182,14 @@ const StaticCFDAccountManager = ({
) : (
{description}
)}
- {has_account && platform !== CFD_PLATFORMS.DXTRADE ? (
+ {has_account ? (
-
+
) : (
diff --git a/packages/appstore/src/components/onboarding-new/static-dashboard.scss b/packages/appstore/src/components/onboarding-new/static-dashboard.scss
index 27dba57ca43e..3e09f296c030 100644
--- a/packages/appstore/src/components/onboarding-new/static-dashboard.scss
+++ b/packages/appstore/src/components/onboarding-new/static-dashboard.scss
@@ -11,6 +11,7 @@
}
&--deposit-button {
cursor: not-allowed;
+ pointer-events: none;
&-blurry {
opacity: 0.2;
}
@@ -135,7 +136,8 @@
}
&__body {
- display: flex;
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
&--header {
margin-top: 2rem;
@@ -160,6 +162,7 @@
}
@include mobile {
+ display: flex;
flex-direction: column;
}
diff --git a/packages/appstore/src/components/onboarding-new/static-dashboard.tsx b/packages/appstore/src/components/onboarding-new/static-dashboard.tsx
index 6d98895d07fe..d9fcd14b295a 100644
--- a/packages/appstore/src/components/onboarding-new/static-dashboard.tsx
+++ b/packages/appstore/src/components/onboarding-new/static-dashboard.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { useStores } from 'Stores';
+import { useStore } from '@deriv/stores';
import classNames from 'classnames';
import { observer } from 'mobx-react-lite';
import { Text, ButtonToggle, ThemedScrollbars, Button } from '@deriv/components';
@@ -63,8 +63,8 @@ const StaticDashboard = ({
is_onboarding_animated,
loginid,
}: TStaticDashboard) => {
- const { client, traders_hub } = useStores();
- const { content_flag } = traders_hub;
+ const { client, traders_hub } = useStore();
+ const { content_flag, CFDs_restricted_countries, financial_restricted_countries } = traders_hub;
const { is_eu_country, is_logged_in } = client;
const [index, setIndex] = React.useState
(0);
@@ -152,7 +152,7 @@ const StaticDashboard = ({
color={
is_blurry.options_text || is_blurry.options_description
? 'less-prominent'
- : 'prominent'
+ : 'general'
}
>
{eu_user ? (
@@ -175,7 +175,7 @@ const StaticDashboard = ({
/>
) : (
- {!is_eu_user && (
+ {!is_eu_user && !CFDs_restricted_countries && (
)}
{isMobile() && !has_account &&
}
- {is_eu_user && (
+ {is_eu_user && !financial_restricted_countries && (
)}
- {!is_eu_user && (
-
+
+ {isMobile() && !has_account && }
+ {!financial_restricted_countries && (
+
)}
- financial_amount={financial_amount}
- derived_amount={derived_amount}
- loginid={loginid}
- currency={currency}
- has_account={has_account}
- is_last_step={is_last_step}
- is_blurry={is_blurry}
- is_onboarding_animated={is_onboarding_animated}
- is_financial_last_step={is_financial_last_step}
- is_eu_user={is_eu_user}
- />
+
)}
{isDesktop() && has_account && !eu_user && (
)}
- {!is_eu_user && (
+
+ {!is_eu_user && !CFDs_restricted_countries && !financial_restricted_countries && (
@@ -515,29 +540,48 @@ const StaticDashboard = ({
: 'prominent'
}
>
- {localize('Other CFDs')}
+ {localize('Other CFD Platforms')}
)}
- {!is_eu_user && (
-
+ {!is_eu_user && !CFDs_restricted_countries && (
+
+
+
+
)}
)}
diff --git a/packages/appstore/src/components/onboarding-new/static-trading-app-card.scss b/packages/appstore/src/components/onboarding-new/static-trading-app-card.scss
index 43ae2ab10cfc..b8db484d6cd9 100644
--- a/packages/appstore/src/components/onboarding-new/static-trading-app-card.scss
+++ b/packages/appstore/src/components/onboarding-new/static-trading-app-card.scss
@@ -44,6 +44,7 @@
&--active,
&--blurry {
cursor: not-allowed;
+ pointer-events: none;
}
&--blurry {
opacity: 0.2;
diff --git a/packages/appstore/src/components/onboarding-new/static-trading-app-card.tsx b/packages/appstore/src/components/onboarding-new/static-trading-app-card.tsx
index a80fdab42866..9c02b4102d86 100644
--- a/packages/appstore/src/components/onboarding-new/static-trading-app-card.tsx
+++ b/packages/appstore/src/components/onboarding-new/static-trading-app-card.tsx
@@ -56,7 +56,7 @@ const StaticTradingAppCard = ({
>
{name}
-
+
{app_desc}
@@ -68,7 +68,7 @@ const StaticTradingAppCard = ({
'static-trading-app-card__button--hidden': !has_applauncher_account,
})}
>
- {localize('Trade')}
+ {localize('Open')}
diff --git a/packages/appstore/src/components/options-multipliers-listing/index.tsx b/packages/appstore/src/components/options-multipliers-listing/index.tsx
index 2012d60278fc..b7ab059bc47d 100644
--- a/packages/appstore/src/components/options-multipliers-listing/index.tsx
+++ b/packages/appstore/src/components/options-multipliers-listing/index.tsx
@@ -35,7 +35,7 @@ const OptionsAndMultipliersListing = () => {
);
} else if ((low_risk_cr_eu || is_eu) && !isMobile()) {
return (
-
+
);
@@ -50,8 +50,8 @@ const OptionsAndMultipliersListing = () => {
low_risk_cr_non_eu || high_risk_cr || cr_demo ? (
,
,
@@ -86,7 +86,7 @@ const OptionsAndMultipliersListing = () => {
openRealAccountSignup('maltainvest');
}
} else {
- openRealAccountSignup();
+ openRealAccountSignup('svg');
}
}}
/>
diff --git a/packages/appstore/src/components/routes/routes-wrapper.tsx b/packages/appstore/src/components/routes/routes-wrapper.tsx
index 4f2803ff312b..8092cd5b2913 100644
--- a/packages/appstore/src/components/routes/routes-wrapper.tsx
+++ b/packages/appstore/src/components/routes/routes-wrapper.tsx
@@ -1,6 +1,5 @@
import * as React from 'react';
import { BrowserRouter as Router } from 'react-router-dom';
-import { TReactChildren } from 'Types';
const RoutesWrapper: React.FC = ({ has_router, children }) => {
if (has_router) {
@@ -10,9 +9,8 @@ const RoutesWrapper: React.FC = ({ has_router, children })
return {children} ;
};
-type TRoutesWrapperProps = {
+type TRoutesWrapperProps = React.PropsWithChildren<{
has_router: boolean;
- children: TReactChildren;
-};
+}>;
export default RoutesWrapper;
diff --git a/packages/appstore/src/components/trade-button/trade-button.tsx b/packages/appstore/src/components/trade-button/trade-button.tsx
index 38820ec28467..b26623f33324 100644
--- a/packages/appstore/src/components/trade-button/trade-button.tsx
+++ b/packages/appstore/src/components/trade-button/trade-button.tsx
@@ -23,25 +23,25 @@ const TradeButton = ({
if (new_tab) {
return (
- {localize('Trade')}
+ {localize('Open')}
);
}
return (
- {localize('Trade')}
+ {localize('Open')}
);
}
return (
- {localize('Trade')}
+ {localize('Open')}
);
} else if (onAction) {
return (
onAction()} is_disabled={is_buttons_disabled}>
- {localize('Trade')}
+ {localize('Open')}
);
}
@@ -57,7 +57,7 @@ const TradeButton = ({
rel='noopener noreferrer'
>
- {localize('Trade')}
+ {localize('Open')}
);
diff --git a/packages/appstore/src/constants/platform-config.ts b/packages/appstore/src/constants/platform-config.ts
index 8c95c6b576d4..a7c628273162 100644
--- a/packages/appstore/src/constants/platform-config.ts
+++ b/packages/appstore/src/constants/platform-config.ts
@@ -5,9 +5,9 @@ import { TAccountCategory, TRegionAvailability } from 'Types';
export type AccountType = { text: 'Real' | 'Demo'; value: TAccountCategory };
export type RegionAvailability = 'Non-EU' | 'EU' | 'All';
-export const account_types: AccountType[] = [
- { text: 'Demo', value: 'demo' },
- { text: 'Real', value: 'real' },
+export const getAccountTypes = (): AccountType[] => [
+ { text: localize('Demo'), value: 'demo' },
+ { text: localize('Real'), value: 'real' },
];
export const region_availability: RegionAvailability[] = ['Non-EU', 'EU'];
diff --git a/packages/appstore/src/constants/tour-steps-config-new.tsx b/packages/appstore/src/constants/tour-steps-config-new.tsx
index 19e467379a1c..3af199dd9b2f 100644
--- a/packages/appstore/src/constants/tour-steps-config-new.tsx
+++ b/packages/appstore/src/constants/tour-steps-config-new.tsx
@@ -50,7 +50,7 @@ export const getTourStepConfig = (): Step[] => [
title: (
- {localize("Trader's hub tour")}
+ {localize("Trader's Hub tour")}
@@ -81,7 +81,7 @@ export const getTourStepConfigHighRisk = (): Step[] => [
title: (
- {localize("Trader's hub tour")}
+ {localize("Trader's Hub tour")}
diff --git a/packages/appstore/src/constants/trading-hub-content.tsx b/packages/appstore/src/constants/trading-hub-content.tsx
index 3c065b768be4..f830452a2da0 100644
--- a/packages/appstore/src/constants/trading-hub-content.tsx
+++ b/packages/appstore/src/constants/trading-hub-content.tsx
@@ -42,7 +42,7 @@ export const getTradingHubContents = (): TTradingHubContents => ({
}}
/>
),
- footer_header: localize("Welcome to Trader's hub"),
+ footer_header: localize("Welcome to Trader's Hub"),
footer_text: localize('This is your personal start page for Deriv'),
has_next_content: false,
},
@@ -222,7 +222,7 @@ export const getTradingHubContents = (): TTradingHubContents => ({
/>
),
footer_header: localize('Start trading'),
- footer_text: localize('Click ‘Trade’ to start trading with your account'),
+ footer_text: localize('Click ‘Open’ to start trading with your account'),
has_next_content: true,
next_content: localize('Start trading'),
},
diff --git a/packages/appstore/src/helpers/account-helper.ts b/packages/appstore/src/helpers/account-helper.ts
index 1f3c68f06231..4014b990c54f 100644
--- a/packages/appstore/src/helpers/account-helper.ts
+++ b/packages/appstore/src/helpers/account-helper.ts
@@ -1,4 +1,6 @@
import { isCryptocurrency } from '@deriv/shared';
+import { localize } from '@deriv/translations';
+import { TAccountType } from '../components/modals/account-type-modal/types';
type TAccountProps = {
a_currency: string;
@@ -35,3 +37,23 @@ export const getSortedAccountList = (account_list: TAccountProps, accounts: TAcc
return 1;
});
};
+
+export const getDerivedAccount = (): TAccountType => ({
+ title_and_type: localize('Derived'),
+ icon: 'Derived',
+ description: localize('Trade CFDs on MT5 with Derived indices that simulate real-world market movements.'),
+});
+
+export const getFinancialAccount = (): TAccountType => ({
+ title_and_type: localize('Financial'),
+ icon: 'Financial',
+ description: localize('Trade CFDs on MT5 with forex, stock indices, commodities, and cryptocurrencies.'),
+});
+
+export const getSwapFreeAccount = (): TAccountType => ({
+ title_and_type: localize('Swap-Free'),
+ icon: 'SwapFree',
+ description: localize(
+ 'Trade swap-free CFDs on MT5 with synthetics, forex, stocks, stock indices, cryptocurrencies and ETFs.'
+ ),
+});
diff --git a/packages/appstore/src/modules/onboarding/empty-onboarding.tsx b/packages/appstore/src/modules/onboarding/empty-onboarding.tsx
index 5e3974c5f8a1..3f6ab5bcabc4 100644
--- a/packages/appstore/src/modules/onboarding/empty-onboarding.tsx
+++ b/packages/appstore/src/modules/onboarding/empty-onboarding.tsx
@@ -5,7 +5,7 @@ const EmptyOnboarding = () => {
return (
);
diff --git a/packages/appstore/src/modules/onboarding/onboarding.scss b/packages/appstore/src/modules/onboarding/onboarding.scss
index 208d97f5b7e1..c186299f486d 100644
--- a/packages/appstore/src/modules/onboarding/onboarding.scss
+++ b/packages/appstore/src/modules/onboarding/onboarding.scss
@@ -1,15 +1,27 @@
.onboarding-wrapper {
display: flex;
flex-direction: column;
- background-color: var(--text-less-prominent);
+ gap: 3rem;
+ background-color: var(--general-hover);
height: inherit;
+
.onboarding-header {
+ margin-left: 2.5rem;
+ margin-right: 2.5rem;
display: flex;
- margin-left: 1rem;
justify-content: space-between;
+ align-items: center;
+
+ @include mobile {
+ margin-left: 1rem;
+ margin-right: 1rem;
+ }
+
+ &--deriv-logo {
+ margin-top: 1rem;
+ }
&__cross-icon {
- margin: 3.5rem 3rem 0 0;
cursor: pointer;
}
}
@@ -97,9 +109,12 @@
&__header {
width: 100%;
height: 7.2rem;
- background-color: $color-black;
display: flex;
justify-content: center;
align-items: center;
+
+ @include mobile {
+ height: auto;
+ }
}
}
diff --git a/packages/appstore/src/modules/onboarding/onboarding.tsx b/packages/appstore/src/modules/onboarding/onboarding.tsx
index fcd47c002fe0..46ec9a57cbb1 100644
--- a/packages/appstore/src/modules/onboarding/onboarding.tsx
+++ b/packages/appstore/src/modules/onboarding/onboarding.tsx
@@ -79,10 +79,12 @@ const Onboarding = ({ contents = getTradingHubContents() }: TOnboardingProps) =>
return (
-
+
+
+
diff --git a/packages/appstore/src/modules/traders-hub/index.tsx b/packages/appstore/src/modules/traders-hub/index.tsx
index 81a7953355ed..ec98e56b6194 100644
--- a/packages/appstore/src/modules/traders-hub/index.tsx
+++ b/packages/appstore/src/modules/traders-hub/index.tsx
@@ -65,7 +65,7 @@ const TradersHub = () => {
]}
/>
diff --git a/packages/appstore/src/modules/traders-hub/traders-hub.scss b/packages/appstore/src/modules/traders-hub/traders-hub.scss
index a69e05e9b777..ab9fd05592b9 100644
--- a/packages/appstore/src/modules/traders-hub/traders-hub.scss
+++ b/packages/appstore/src/modules/traders-hub/traders-hub.scss
@@ -1,9 +1,11 @@
.traders-hub {
- width: 100%;
- padding: 5rem 4rem;
+ max-width: 123.2rem;
+ margin: auto;
+ padding: 5rem 0;
@include mobile {
padding: 2rem;
+ width: 100%;
}
&__main-container {
diff --git a/packages/appstore/src/types/common.types.ts b/packages/appstore/src/types/common.types.ts
index 62319a20fa9a..d0ed899ca233 100644
--- a/packages/appstore/src/types/common.types.ts
+++ b/packages/appstore/src/types/common.types.ts
@@ -12,7 +12,7 @@ export type RequiredAndNotNull
= {
export type TRegionAvailability = 'Non-EU' | 'EU' | 'All';
export type TAccountCategory = 'real' | 'demo';
-export type TPlatform = 'dxtrade' | 'mt5' | 'trader' | 'dbot' | 'smarttrader' | 'bbot' | 'go';
+export type TPlatform = 'dxtrade' | 'mt5' | 'trader' | 'dbot' | 'smarttrader' | 'bbot' | 'go' | 'derivez';
export type TBrandData = {
name: string;
icon?: string;
@@ -66,7 +66,7 @@ export type TDetailsOfEachMT5Loginid = DetailsOfEachMT5Loginid & {
};
export type TTradingPlatformAvailableAccount = {
- market_type: 'financial' | 'gaming';
+ market_type: 'financial' | 'gaming' | 'all';
name: string;
requirements: {
after_first_deposit: {
@@ -119,7 +119,8 @@ export type TIconTypes =
| 'Options'
| 'SmartTrader'
| 'SmartTraderBlue'
- | 'CFDs';
+ | 'CFDs'
+ | 'DerivEz';
export interface AvailableAccount {
name: string;
diff --git a/packages/appstore/src/types/props.types.ts b/packages/appstore/src/types/props.types.ts
index 5d991bfa4be0..ed94bb03b11d 100644
--- a/packages/appstore/src/types/props.types.ts
+++ b/packages/appstore/src/types/props.types.ts
@@ -4,19 +4,3 @@ export type TConfigProps = {
has_router: boolean;
routes: ConfigStore['routes'];
};
-
-type ReactTypes = React.ComponentType | React.ElementType;
-
-type TLocalizeProps = {
- components?: ReactTypes[];
- i18n?: unknown;
- i18n_default_text: string;
- values?: {
- [k: string]: string;
- };
-};
-
-export type TStringTranslation = string | React.ReactElement;
-
-// ref: https://www.carlrippon.com/react-children-with-typescript/
-export type TReactChildren = React.ReactNode;
diff --git a/packages/appstore/webpack.config.js b/packages/appstore/webpack.config.js
index e14e81c2c94e..120afce6e79b 100644
--- a/packages/appstore/webpack.config.js
+++ b/packages/appstore/webpack.config.js
@@ -26,7 +26,7 @@ const svg_loaders = [
{ removeUselessStrokeAndFill: false },
{ removeUknownsAndDefaults: false },
],
- floatPrecision: 2,
+ floatPrecision: 3,
},
},
},
@@ -156,7 +156,7 @@ module.exports = function (env) {
]
: [],
},
- devtool: is_release ? undefined : 'eval-cheap-module-source-map',
+ devtool: is_release ? 'source-map' : 'eval-cheap-module-source-map',
externals: [
{
react: true,
diff --git a/packages/bot-skeleton/jest.config.js b/packages/bot-skeleton/jest.config.js
new file mode 100644
index 000000000000..26391d325ed4
--- /dev/null
+++ b/packages/bot-skeleton/jest.config.js
@@ -0,0 +1,19 @@
+const baseConfigForPackages = require('../../jest.config.base');
+
+module.exports = {
+ ...baseConfigForPackages,
+ clearMocks: true,
+ moduleNameMapper: {
+ '\\.s(c|a)ss$': '/../../__mocks__/styleMock.js',
+ '^.+\\.svg$': '/../../__mocks__/styleMock.js',
+ '^Constants/(.*)$': '/src/constants/$1',
+ '^Scratch/(.*)$': '/src/scratch/$1',
+ '^Services/(.*)$': '/src/services/$1',
+ '^Utils/(.*)$': '/src/utils/$1',
+ },
+ // remove later
+ testRegex: 'packages/bot-skeleton/src/services/api/__tests__/datadog-middleware.spec.ts',
+ globals: {
+ __webpack_public_path__: '/',
+ },
+};
diff --git a/packages/bot-skeleton/src/constants/config.js b/packages/bot-skeleton/src/constants/config.js
index e4d08d319ab8..380771c797ce 100644
--- a/packages/bot-skeleton/src/constants/config.js
+++ b/packages/bot-skeleton/src/constants/config.js
@@ -267,9 +267,6 @@ export const config = {
[localize('Signal'), '2'],
],
gd: {
- cid: '828416594271-b4bhia944ecegn3j327oeb4l8o803bts.apps.googleusercontent.com',
- aid: 'derivbot-248506',
- api: 'AIzaSyA52MX2l8p75-w7nvab7fU6Lk6KwLqnyEI',
scope: 'https://www.googleapis.com/auth/drive.file',
discovery_docs: 'https://www.googleapis.com/discovery/v1/apis/drive/v3/rest',
},
diff --git a/packages/bot-skeleton/src/constants/save-type.js b/packages/bot-skeleton/src/constants/save-type.ts
similarity index 100%
rename from packages/bot-skeleton/src/constants/save-type.js
rename to packages/bot-skeleton/src/constants/save-type.ts
diff --git a/packages/bot-skeleton/src/scratch/backward-compatibility.js b/packages/bot-skeleton/src/scratch/backward-compatibility.js
index e242752f312b..a8ad81490eb7 100644
--- a/packages/bot-skeleton/src/scratch/backward-compatibility.js
+++ b/packages/bot-skeleton/src/scratch/backward-compatibility.js
@@ -8,6 +8,7 @@ export default class BlockConversion {
this.blocks_pending_reconnect = {};
this.workspace = this.createWorkspace();
this.workspace_variables = {};
+ this.has_market_block = false;
}
getConversions() {
@@ -212,7 +213,10 @@ export default class BlockConversion {
lists_create_with: block_node =>
generateGrowingListBlock(block_node, 'lists_create_with', localize('list'), 'VALUE'),
macda: block_node => generateIndicatorBlock(block_node, 'macda_statement', 'macda'),
- market: block_node => tradeOptions(block_node),
+ market: block_node => {
+ this.has_market_block = true;
+ return tradeOptions(block_node);
+ },
rsi: block_node => generateIndicatorBlock(block_node, 'rsi_statement', 'rsi'),
rsia: block_node => generateIndicatorBlock(block_node, 'rsia_statement', 'rsia'),
sma: block_node => generateIndicatorBlock(block_node, 'sma_statement', 'sma'),
@@ -352,10 +356,14 @@ export default class BlockConversion {
// to "Run once at start". Legacy "market" blocks had no such thing as "Run once at start"
// not moving everything would kill Martingale strategies as they'd be reinitialised each run.
const trade_definition_block = this.workspace.getTradeDefinitionBlock();
-
+ const has_initialization_block = trade_definition_block.getBlocksInStatement('INITIALIZATION').length > 0;
if (trade_definition_block) {
trade_definition_block.getBlocksInStatement('SUBMARKET').forEach(block => {
- if (block.type !== 'trade_definition_tradeoptions') {
+ if (
+ block.type !== 'trade_definition_tradeoptions' &&
+ this.has_market_block &&
+ !has_initialization_block
+ ) {
const last_connection = trade_definition_block.getLastConnectionInStatement('INITIALIZATION');
block.unplug(true);
last_connection.connect(block.previousConnection);
diff --git a/packages/bot-skeleton/src/scratch/blocks/Binary/Trade Definition/multiplier_stop_loss.js b/packages/bot-skeleton/src/scratch/blocks/Binary/Trade Definition/multiplier_stop_loss.js
index c17e8570157d..75b5f989fefd 100644
--- a/packages/bot-skeleton/src/scratch/blocks/Binary/Trade Definition/multiplier_stop_loss.js
+++ b/packages/bot-skeleton/src/scratch/blocks/Binary/Trade Definition/multiplier_stop_loss.js
@@ -30,7 +30,7 @@ Blockly.Blocks.multiplier_stop_loss = {
previousStatement: null,
nextStatement: null,
tooltip: localize(
- 'Your contract is closed automatically when your loss is more than or equals to this amount.'
+ 'Your contract is closed automatically when your loss is more than or equals to this amount. This block can only be used with the multipliers trade type.'
),
category: Blockly.Categories.Trade_Definition,
};
@@ -39,7 +39,7 @@ Blockly.Blocks.multiplier_stop_loss = {
return {
display_name: localize('Stop loss'),
description: localize(
- 'Your contract is closed automatically when your loss is more than or equals to this amount.'
+ 'Your contract is closed automatically when your loss is more than or equals to this amount. This block can only be used with the multipliers trade type.'
),
};
},
diff --git a/packages/bot-skeleton/src/scratch/blocks/Binary/Trade Definition/multiplier_take_profit.js b/packages/bot-skeleton/src/scratch/blocks/Binary/Trade Definition/multiplier_take_profit.js
index 23890b01ff4d..ad0c3c26a75f 100644
--- a/packages/bot-skeleton/src/scratch/blocks/Binary/Trade Definition/multiplier_take_profit.js
+++ b/packages/bot-skeleton/src/scratch/blocks/Binary/Trade Definition/multiplier_take_profit.js
@@ -30,7 +30,7 @@ Blockly.Blocks.multiplier_take_profit = {
previousStatement: null,
nextStatement: null,
tooltip: localize(
- 'Your contract is closed automatically when your profit is more than or equals to this amount.'
+ 'Your contract is closed automatically when your profit is more than or equals to this amount. This block can only be used with the multipliers trade type.'
),
category: Blockly.Categories.Trade_Definition,
};
@@ -39,7 +39,7 @@ Blockly.Blocks.multiplier_take_profit = {
return {
display_name: localize('Take Profit'),
description: localize(
- 'Your contract is closed automatically when your profit is more than or equals to this amount.'
+ 'Your contract is closed automatically when your profit is more than or equals to this amount. This block can only be used with the multipliers trade type.'
),
};
},
diff --git a/packages/bot-skeleton/src/scratch/dbot-store.js b/packages/bot-skeleton/src/scratch/dbot-store.js
index fb7333342df2..25a34313a8c3 100644
--- a/packages/bot-skeleton/src/scratch/dbot-store.js
+++ b/packages/bot-skeleton/src/scratch/dbot-store.js
@@ -20,16 +20,16 @@ class DBotStore extends DBotStoreInterface {
this.is_mobile = store.is_mobile || false;
this.is_dark_mode_on = store.is_dark_mode_on || false;
this.client = store.client;
+ this.dashboard = store.dashboard;
this.flyout = store.flyout;
- this.populateConfig = store.populateConfig;
this.toolbar = store.toolbar;
this.toolbox = store.toolbox;
this.save_modal = store.save_modal;
+ this.load_modal = store.load_modal;
this.setContractUpdateConfig = store.setContractUpdateConfig;
this.toggleStrategyModal = store.toggleStrategyModal;
this.handleFileChange = store.handleFileChange;
- this.startLoading = store.startLoading;
- this.endLoading = store.endLoading;
+ this.setLoading = store.setLoading;
reaction(
() => this.client.loginid,
diff --git a/packages/bot-skeleton/src/scratch/dbot.js b/packages/bot-skeleton/src/scratch/dbot.js
index 64ed4c5baf0b..6da5fe47957b 100644
--- a/packages/bot-skeleton/src/scratch/dbot.js
+++ b/packages/bot-skeleton/src/scratch/dbot.js
@@ -23,6 +23,7 @@ class DBot {
*/
async initWorkspace(public_path, store, api_helpers_store, is_mobile) {
const recent_files = await getSavedWorkspaces();
+
return new Promise((resolve, reject) => {
__webpack_public_path__ = public_path; // eslint-disable-line no-global-assign
ApiHelpers.setInstance(api_helpers_store);
@@ -46,7 +47,6 @@ class DBot {
return;
}
this.workspace = Blockly.inject(el_scratch_div, {
- grid: { spacing: 40, length: 11, colour: '#f3f3f3' },
media: `${__webpack_public_path__}media/`,
trashcan: !is_mobile,
zoom: { wheel: true, startScale: workspaceScale },
@@ -54,10 +54,6 @@ class DBot {
});
this.workspace.cached_xml = { main: main_xml };
- this.workspace.save_workspace_interval = setInterval(async () => {
- // Periodically save the workspace.
- await saveWorkspaceToRecent(Blockly.Xml.workspaceToDom(this.workspace), save_types.UNSAVED);
- }, 10000);
this.workspace.addChangeListener(this.valueInputLimitationsListener.bind(this));
this.workspace.addChangeListener(event => updateDisabledBlocks(this.workspace, event));
@@ -73,12 +69,15 @@ class DBot {
// Push main.xml to workspace and reset the undo stack.
this.workspace.current_strategy_id = Blockly.utils.genUid();
Blockly.derivWorkspace.strategy_to_load = main_xml;
+ Blockly.mainWorkspace.strategy_to_load = main_xml;
let file_name = config.default_file_name;
if (recent_files && recent_files.length) {
const latest_file = recent_files[0];
Blockly.derivWorkspace.strategy_to_load = latest_file.xml;
+ Blockly.mainWorkspace.strategy_to_load = latest_file.xml;
file_name = latest_file.name;
Blockly.derivWorkspace.current_strategy_id = latest_file.id;
+ Blockly.mainWorkspace.current_strategy_id = latest_file.id;
}
const event_group = `dbot-load${Date.now()}`;
@@ -108,6 +107,10 @@ class DBot {
});
}
+ async saveRecentWorkspace() {
+ await saveWorkspaceToRecent(Blockly.Xml.workspaceToDom(this.workspace), save_types.UNSAVED);
+ }
+
/**
* Allows you to add a function that needs to be executed before running the bot. Each
* function needs to return true in order for the bot to run.
diff --git a/packages/bot-skeleton/src/scratch/hooks/block_svg.js b/packages/bot-skeleton/src/scratch/hooks/block_svg.js
index deb2be471a6e..c88b8f781f51 100644
--- a/packages/bot-skeleton/src/scratch/hooks/block_svg.js
+++ b/packages/bot-skeleton/src/scratch/hooks/block_svg.js
@@ -320,6 +320,13 @@ Blockly.BlockSvg.prototype.setCollapsed = function (collapsed) {
this.setErrorHighlighted(collapsed && this.hasErrorHighlightedDescendant());
};
+export const blocksCoordinate = () => {
+ const during_purchase = Blockly.derivWorkspace?.getCanvas().children[1];
+ const after_purchase = Blockly.derivWorkspace?.getCanvas().children[2];
+ during_purchase?.setAttribute('transform', 'translate(720,0)');
+ after_purchase?.setAttribute('transform', 'translate(720,248)');
+};
+
/**
* @deriv/bot: Add check for workspace.getCanvas() before appendChild() is called.
*/
diff --git a/packages/bot-skeleton/src/scratch/hooks/css.js b/packages/bot-skeleton/src/scratch/hooks/css.js
index 4117c7f9cca2..4d44f4e9498f 100644
--- a/packages/bot-skeleton/src/scratch/hooks/css.js
+++ b/packages/bot-skeleton/src/scratch/hooks/css.js
@@ -64,10 +64,10 @@ Blockly.Css.CONTENT = [
'}',
'.injectionDiv {',
- 'height: 100%;',
+ 'height: calc(100vh - 11rem);',
'position: relative;',
'overflow: hidden;' /* So blocks in drag surface disappear at edges */,
- 'touch-action: none',
+ 'touch-action: none;',
'}',
'.blocklyNonSelectable {',
@@ -635,7 +635,6 @@ Blockly.Css.CONTENT = [
'.blocklyMainBackground {',
'stroke-width: 0;',
- 'fill: var(--general-main-1) !important',
// 'stroke: #c6c6c6;', /* Equates to #ddd due to border being off-pixel. */
'}',
diff --git a/packages/bot-skeleton/src/scratch/hooks/data_category.js b/packages/bot-skeleton/src/scratch/hooks/data_category.js
index e19f51b90257..c35ee1a4b504 100644
--- a/packages/bot-skeleton/src/scratch/hooks/data_category.js
+++ b/packages/bot-skeleton/src/scratch/hooks/data_category.js
@@ -101,7 +101,7 @@ Blockly.DataCategory.addCreateVariable = function (xmlList, workspace) {
el_input_xml.setAttribute('placeholder', input_placeholder);
const callback = function (button) {
- const el_input_container = document.querySelector('.flyout__input');
+ const el_input_container = document.querySelector('.flyout__input > .dc-input__container');
const el_input = el_input_container.firstChild;
const buttonWorkspace = button.getTargetWorkspace();
diff --git a/packages/bot-skeleton/src/scratch/hooks/trashcan.js b/packages/bot-skeleton/src/scratch/hooks/trashcan.js
index d0f69befe708..7a4128848b96 100644
--- a/packages/bot-skeleton/src/scratch/hooks/trashcan.js
+++ b/packages/bot-skeleton/src/scratch/hooks/trashcan.js
@@ -1,22 +1,23 @@
-const trashcan_margin = 16;
-const core_footer_height = 36;
+export const initTrashCan = (margin = 16, height = 36) => {
+ /**
+ * Distance between trashcan and bottom edge of workspace.
+ * @type {number}
+ * @private
+ */
+ Blockly.Trashcan.prototype.MARGIN_BOTTOM_ = margin * 2 + height; // eslint-disable-line
-/**
- * Distance between trashcan and bottom edge of workspace.
- * @type {number}
- * @private
- */
-Blockly.Trashcan.prototype.MARGIN_BOTTOM_ = trashcan_margin * 2 + core_footer_height; // eslint-disable-line
+ /**
+ * Distance between trashcan and right edge of workspace.
+ * @type {number}
+ * @private
+ */
+ Blockly.Trashcan.prototype.MARGIN_SIDE_ = margin; // eslint-disable-line
-/**
- * Distance between trashcan and right edge of workspace.
- * @type {number}
- * @private
- */
-Blockly.Trashcan.prototype.MARGIN_SIDE_ = trashcan_margin; // eslint-disable-line
+ /**
+ * Inspect the contents of the trash.
+ * @deriv/bot: Noop for us, restore original functionality when trashcan can be inspected.
+ */
+ Blockly.Trashcan.prototype.click = function () {};
-/**
- * Inspect the contents of the trash.
- * @deriv/bot: Noop for us, restore original functionality when trashcan can be inspected.
- */
-Blockly.Trashcan.prototype.click = function () {};
+ window.dispatchEvent(new Event('resize')); // trigger UI update
+};
diff --git a/packages/bot-skeleton/src/scratch/hooks/workspace_svg.js b/packages/bot-skeleton/src/scratch/hooks/workspace_svg.js
index e8c40ce2660b..82a6e101cab7 100644
--- a/packages/bot-skeleton/src/scratch/hooks/workspace_svg.js
+++ b/packages/bot-skeleton/src/scratch/hooks/workspace_svg.js
@@ -146,7 +146,7 @@ Blockly.WorkspaceSvg.prototype.cleanUp = function (x = 0, y = 0, blocks_to_clean
const start = (column_index - 1) * blocks_per_column;
const fat_neighbour_block = root_blocks
.slice(start, start + blocks_per_column)
- .reduce((a, b) => (a.getHeightWidth().width > b.getHeightWidth().width ? a : b));
+ ?.reduce((a, b) => (a.getHeightWidth().width > b.getHeightWidth().width ? a : b));
let position_x = cursor_x + fat_neighbour_block.getHeightWidth().width + Blockly.BlockSvg.MIN_BLOCK_X;
if (!is_import) {
@@ -399,12 +399,12 @@ Blockly.WorkspaceSvg.prototype.dispose = function (should_show_loading = false)
};
if (should_show_loading) {
- const { startLoading, endLoading } = DBotStore.instance;
- startLoading();
+ const { setLoading } = DBotStore.instance;
+ setLoading(true);
setTimeout(() => {
disposeFn();
- endLoading();
+ setLoading(false);
}, 50);
} else {
disposeFn();
@@ -415,12 +415,12 @@ Blockly.WorkspaceSvg.prototype.dispose = function (should_show_loading = false)
* Dispose of all blocks in workspace, with an optimization to prevent resizes.
*/
Blockly.WorkspaceSvg.prototype.asyncClear = function () {
- const { startLoading, endLoading } = DBotStore.instance;
- startLoading();
+ const { setLoading } = DBotStore.instance;
+ setLoading(true);
return new Promise(resolve => {
this.clear();
- endLoading();
+ setLoading(false);
resolve();
});
};
diff --git a/packages/bot-skeleton/src/scratch/utils/index.js b/packages/bot-skeleton/src/scratch/utils/index.js
index 04f1f492dc96..8849c827fbc2 100644
--- a/packages/bot-skeleton/src/scratch/utils/index.js
+++ b/packages/bot-skeleton/src/scratch/utils/index.js
@@ -62,12 +62,12 @@ export const load = ({
workspace,
showIncompatibleStrategyDialog,
}) => {
- const { startLoading, endLoading } = DBotStore.instance;
- startLoading();
+ const { setLoading } = DBotStore.instance;
+ setLoading(true);
setTimeout(async () => {
const showInvalidStrategyError = () => {
- endLoading();
+ setLoading(false);
const error_message = localize('XML file contains unsupported elements. Please check or modify file.');
globalObserver.emit('ui.log.error', error_message);
};
@@ -153,7 +153,7 @@ export const load = ({
console.error(e); // eslint-disable-line
return showInvalidStrategyError();
} finally {
- endLoading();
+ setLoading(false);
}
return true;
@@ -176,7 +176,6 @@ export const loadBlocks = (xml, drop_event, event_group, workspace) => {
export const loadWorkspace = async (xml, event_group, workspace) => {
Blockly.Events.setGroup(event_group);
await workspace.asyncClear();
-
Blockly.Xml.domToWorkspace(xml, workspace);
};
diff --git a/packages/bot-skeleton/src/scratch/xml/main.xml b/packages/bot-skeleton/src/scratch/xml/main.xml
index ce66438103ac..28dc79b32c3b 100644
--- a/packages/bot-skeleton/src/scratch/xml/main.xml
+++ b/packages/bot-skeleton/src/scratch/xml/main.xml
@@ -1,4 +1,4 @@
-
+
@@ -53,7 +53,7 @@
-
+
@@ -62,7 +62,7 @@
-
+
diff --git a/packages/bot-skeleton/src/services/api/__tests__/datadog-middleware.spec.ts b/packages/bot-skeleton/src/services/api/__tests__/datadog-middleware.spec.ts
new file mode 100644
index 000000000000..1a05a301c2c4
--- /dev/null
+++ b/packages/bot-skeleton/src/services/api/__tests__/datadog-middleware.spec.ts
@@ -0,0 +1,146 @@
+import APIMiddleware, { REQUESTS } from '../api-middleware';
+import { datadogLogs } from '@datadog/browser-logs';
+
+jest.mock('@datadog/browser-logs', () => {
+ return {
+ ...jest.requireActual('@datadog/browser-logs'),
+ datadogLogs: {
+ init: jest.fn(),
+ logger: {
+ info: jest.fn(),
+ },
+ },
+ };
+});
+
+describe('APIMiddleware', () => {
+ let api_middleware: APIMiddleware;
+ const info = jest.fn();
+ const mockMeasure = jest.fn(() => ({ startTime: 0 }));
+ const clearMarks = jest.fn();
+ const clearMeasures = jest.fn();
+ const mockSendRequestsStatistic = jest.fn();
+
+ const measure_object = {
+ name: 'time',
+ startTime: 15288.20000004768,
+ duration: 133,
+ detail: null,
+ isBotRunning: false,
+ };
+
+ beforeEach(() => {
+ Object.defineProperty(window, 'performance', {
+ value: {
+ mark: jest.fn(),
+ measure: mockMeasure,
+ getEntriesByName: jest.fn().mockReturnValue([{ name: 'entry_name' }]),
+ logger: {
+ info: jest.fn().mockReturnValue([{ measure: 'measure_name' }, measure_object]),
+ },
+ mockSendRequestsStatistic,
+ clearMeasures,
+ clearMarks,
+ },
+ });
+
+ api_middleware = new APIMiddleware();
+ });
+
+ it('Should get measure for each request, invoke method log(), clear measures', () => {
+ const spyLog = jest.spyOn(api_middleware, 'log');
+
+ api_middleware.sendRequestsStatistic(false);
+
+ REQUESTS.forEach(request_name => {
+ expect(spyLog).toHaveBeenCalledWith([{ name: 'entry_name' }], false, request_name);
+ });
+ expect(clearMeasures).toBeCalledTimes(1);
+ });
+
+ it('Should log info if measures are there', () => {
+ const datadog_logs = {
+ name: 'time',
+ startTimeDate: 15288.20000004768,
+ duration: 133,
+ detail: null,
+ isBotRunning: false,
+ };
+
+ const spyDatalogsInfo = jest.spyOn(datadogLogs.logger, 'info');
+
+ api_middleware.log([datadog_logs], false);
+
+ expect(spyDatalogsInfo).toHaveBeenCalledWith(datadog_logs.name, { ...measure_object });
+ });
+
+ it('Should not log info if measures are absent', () => {
+ api_middleware.log([], false);
+ expect(info).toBeCalledTimes(0);
+ });
+
+ it('GetRequestType', () => {
+ const spyGetRequestType = jest.spyOn(api_middleware, 'getRequestType');
+ const request_type = { authorize: 1 };
+ const result = api_middleware.getRequestType(request_type);
+ REQUESTS.forEach(type => {
+ if (type in request_type) {
+ expect(spyGetRequestType).toHaveBeenCalledWith(request_type);
+ expect(result).toBeDefined();
+ }
+ });
+ });
+
+ it('Should invoke the method defineMeasure()', async () => {
+ const spydefineMeasure = jest.spyOn(api_middleware, 'defineMeasure');
+ const response_promise = new Promise((res, rej) => res({ authorize: 1 }));
+
+ await api_middleware.sendIsCalled({ response_promise, args: [{ authorize: 1 }] });
+
+ expect(spydefineMeasure).toHaveBeenCalledWith('authorize');
+ });
+
+ describe('Define measure', () => {
+ it('Should define measure of history API call', () => {
+ const spydefineMeasure = jest.spyOn(api_middleware, 'defineMeasure');
+ const result = api_middleware.defineMeasure('history');
+
+ expect(spydefineMeasure).toHaveBeenCalledWith('history');
+ expect(mockMeasure).toHaveBeenCalledWith('ticks_history', 'ticks_history_start', 'ticks_history_end');
+ expect(result).toBeDefined();
+ });
+
+ it('Should define measure of proposal API call', () => {
+ const spydefineMeasure = jest.spyOn(api_middleware, 'defineMeasure');
+
+ const result = api_middleware.defineMeasure('proposal');
+
+ expect(spydefineMeasure).toHaveBeenCalledWith('proposal');
+ expect(mockMeasure).toHaveBeenCalledWith('run-proposal', 'bot-start', 'first_proposal_end');
+ expect(clearMarks).toBeCalledTimes(1);
+ expect(result).toBeDefined();
+ });
+
+ it('Should define measure for API calls except of proposal and history', () => {
+ const spydefineMeasure = jest.spyOn(api_middleware, 'defineMeasure');
+
+ REQUESTS.forEach(request_name => {
+ if (request_name !== 'proposal' && request_name !== 'history') {
+ const result = api_middleware.defineMeasure(request_name);
+ expect(spydefineMeasure).toHaveBeenCalledWith(request_name);
+ expect(mockMeasure).toHaveBeenCalledWith(
+ `${request_name}`,
+ `${request_name}_start`,
+ `${request_name}_end`
+ );
+ expect(result).toBeDefined();
+ }
+ });
+ });
+ });
+
+ it('Should be added the method sendRequestsStatistic to window', () => {
+ expect(window).not.toBeUndefined();
+ expect(mockSendRequestsStatistic).not.toBeUndefined();
+ });
+});
diff --git a/packages/bot-skeleton/src/services/api/api-base.js b/packages/bot-skeleton/src/services/api/api-base.js
index fd7c6d96dabd..69756d0c21fa 100644
--- a/packages/bot-skeleton/src/services/api/api-base.js
+++ b/packages/bot-skeleton/src/services/api/api-base.js
@@ -80,6 +80,7 @@ class APIBase {
subscribe() {
doUntilDone(() => this.api.send({ balance: 1, subscribe: 1 }));
doUntilDone(() => this.api.send({ transaction: 1, subscribe: 1 }));
+ doUntilDone(() => this.api.send({ proposal_open_contract: 1, subscribe: 1 }));
}
getActiveSymbols = async () => {
diff --git a/packages/bot-skeleton/src/services/api/api-middleware.js b/packages/bot-skeleton/src/services/api/api-middleware.js
new file mode 100644
index 000000000000..98deb21eed81
--- /dev/null
+++ b/packages/bot-skeleton/src/services/api/api-middleware.js
@@ -0,0 +1,149 @@
+import { datadogLogs } from '@datadog/browser-logs';
+import { formatDate, formatTime } from '@deriv/shared';
+
+const DATADOG_CLIENT_TOKEN_LOGS = process.env.DATADOG_CLIENT_TOKEN_LOGS ?? '';
+const isProduction = process.env.CIRCLE_JOB === 'release_production';
+const isStaging = process.env.CIRCLE_JOB === 'release_staging';
+
+let dataDogSessionSampleRate = 0;
+let dataDogVersion = '';
+let dataDogEnv = '';
+
+if (isProduction) {
+ dataDogVersion = `deriv-app-${process.env.CIRCLE_TAG}`;
+ dataDogSessionSampleRate = +process.env.DATADOG_SESSION_SAMPLE_RATE ?? 10;
+ dataDogEnv = 'production';
+} else if (isStaging) {
+ dataDogVersion = `deriv-app-staging-v${formatDate(new Date(), 'YYYYMMDD')}-${formatTime(Date.now(), 'HH:mm')}`;
+ dataDogSessionSampleRate = 100;
+ dataDogEnv = 'staging';
+}
+
+datadogLogs.init({
+ clientToken: DATADOG_CLIENT_TOKEN_LOGS,
+ site: 'datadoghq.com',
+ forwardErrorsToLogs: false,
+ service: 'Dbot',
+ sessionSampleRate: dataDogSessionSampleRate,
+ version: dataDogVersion,
+ env: dataDogEnv,
+});
+
+export const REQUESTS = [
+ 'authorize',
+ 'balance',
+ 'active_symbols',
+ 'transaction',
+ 'ticks_history',
+ 'forget',
+ 'proposal_open_contract',
+ 'proposal',
+ 'buy',
+ 'exchange_rates',
+ 'trading_times',
+ 'time',
+ 'get_account_status',
+ 'get_settings',
+ 'payout_currencies',
+ 'website_status',
+ 'get_financial_assessment',
+ 'mt5_login_list',
+ 'get_self_exclusion',
+ 'landing_company',
+ 'get_limits',
+ 'paymentagent_list',
+ 'platform',
+ 'trading_platform_available_accounts',
+ 'trading_platform_accounts',
+ 'statement',
+ 'landing_company_details',
+ 'contracts_for',
+ 'residence_list',
+ 'account_security',
+ 'p2p_advertiser_info',
+ 'platform',
+ 'history',
+ 'amount',
+ 'run-proposal',
+];
+
+class APIMiddleware {
+ constructor(config) {
+ this.config = config;
+ this.debounced_calls = {};
+ this.addGlobalMethod();
+ }
+
+ getRequestType = request => {
+ let req_type;
+ REQUESTS.forEach(type => {
+ if (type in request && !req_type) req_type = type;
+ });
+
+ return req_type;
+ };
+
+ log = (measures = [], is_bot_running) => {
+ if (measures && measures.length) {
+ measures.forEach(measure => {
+ datadogLogs.logger.info(measure.name, {
+ name: measure.name,
+ startTime: measure.startTimeDate,
+ duration: measure.duration,
+ detail: measure.detail,
+ isBotRunning: is_bot_running,
+ });
+ });
+ }
+ };
+
+ defineMeasure = res_type => {
+ if (res_type) {
+ let measure;
+ if (res_type === 'proposal') {
+ performance.mark('first_proposal_end');
+ if (performance.getEntriesByName('bot-start', 'mark').length) {
+ measure = performance.measure('run-proposal', 'bot-start', 'first_proposal_end');
+ performance.clearMarks('bot-start');
+ }
+ }
+ if (res_type === 'history') {
+ performance.mark('ticks_history_end');
+ measure = performance.measure('ticks_history', 'ticks_history_start', 'ticks_history_end');
+ } else {
+ performance.mark(`${res_type}_end`);
+ measure = performance.measure(`${res_type}`, `${res_type}_start`, `${res_type}_end`);
+ }
+ return (measure.startTimeDate = new Date(Date.now() - measure.startTime));
+ }
+ return false;
+ };
+
+ sendIsCalled = ({ response_promise, args: [request] }) => {
+ const req_type = this.getRequestType(request);
+ if (req_type) performance.mark(`${req_type}_start`);
+ response_promise.then(res => {
+ const res_type = this.getRequestType(res);
+ if (res_type) {
+ this.defineMeasure(res_type);
+ }
+ });
+ return response_promise;
+ };
+
+ sendRequestsStatistic = is_bot_running => {
+ REQUESTS.forEach(req_type => {
+ const measure = performance.getEntriesByName(req_type);
+ if (measure && measure.length) {
+ this.log(measure, is_bot_running, req_type);
+ }
+ });
+ performance.clearMeasures();
+ };
+
+ addGlobalMethod() {
+ if (window) window.sendRequestsStatistic = this.sendRequestsStatistic;
+ }
+}
+
+export default APIMiddleware;
diff --git a/packages/bot-skeleton/src/services/api/appId.js b/packages/bot-skeleton/src/services/api/appId.js
index e56a9931fbea..d745eaedda9f 100644
--- a/packages/bot-skeleton/src/services/api/appId.js
+++ b/packages/bot-skeleton/src/services/api/appId.js
@@ -1,12 +1,14 @@
import DerivAPIBasic from '@deriv/deriv-api/dist/DerivAPIBasic';
import { getAppId, getSocketURL, website_name } from '@deriv/shared';
import { getLanguage } from '@deriv/translations';
+import APIMiddleware from './api-middleware';
export const generateDerivApiInstance = () => {
const socket_url = `wss://${getSocketURL()}/websockets/v3?app_id=${getAppId()}&l=${getLanguage()}&brand=${website_name.toLowerCase()}`;
const deriv_socket = new WebSocket(socket_url);
const deriv_api = new DerivAPIBasic({
connection: deriv_socket,
+ middleware: new APIMiddleware({}),
});
return deriv_api;
};
diff --git a/packages/bot-skeleton/src/services/tradeEngine/trade/OpenContract.js b/packages/bot-skeleton/src/services/tradeEngine/trade/OpenContract.js
index 2c65f1637ae6..91257fd64c5e 100644
--- a/packages/bot-skeleton/src/services/tradeEngine/trade/OpenContract.js
+++ b/packages/bot-skeleton/src/services/tradeEngine/trade/OpenContract.js
@@ -1,8 +1,6 @@
import { getRoundedNumber } from '@deriv/shared';
import { sell, openContractReceived } from './state/actions';
import { contractStatus, contract as broadcastContract } from '../utils/broadcast';
-import { doUntilDone } from '../utils/helpers';
-import DBotStore from '../../../scratch/dbot-store';
import { api_base } from '../../api/api-base';
export default Engine =>
@@ -51,29 +49,6 @@ export default Engine =>
});
}
- subscribeToOpenContract(contract_id = this.contractId) {
- this.contractId = contract_id;
- const request_object = {
- proposal_open_contract: 1,
- contract_id,
- subscribe: 1,
- };
-
- doUntilDone(() => api_base.api.send(request_object))
- .then(data => {
- const { populateConfig } = DBotStore.instance;
- populateConfig(data.proposal_open_contract);
- this.openContractId = data.proposal_open_contract.id;
- })
- .catch(error => {
- if (error.error.code !== 'AlreadySubscribed') {
- doUntilDone(() => api_base.api.send(request_object)).then(
- response => (this.openContractId = response.proposal_open_contract.id)
- );
- }
- });
- }
-
setContractFlags(contract) {
const { is_expired, is_valid_to_sell, is_sold, entry_tick } = contract;
diff --git a/packages/bot-skeleton/src/services/tradeEngine/trade/Proposal.js b/packages/bot-skeleton/src/services/tradeEngine/trade/Proposal.js
index 515d40b9421a..3d1b4cd1645f 100644
--- a/packages/bot-skeleton/src/services/tradeEngine/trade/Proposal.js
+++ b/packages/bot-skeleton/src/services/tradeEngine/trade/Proposal.js
@@ -55,12 +55,9 @@ export default Engine =>
}
renewProposalsOnPurchase() {
- this.unsubscribeProposals().then(() => this.requestProposals());
- }
-
- clearProposals() {
this.data.proposals = [];
this.store.dispatch(clearProposals());
+ this.requestProposals();
}
requestProposals() {
@@ -98,11 +95,7 @@ export default Engine =>
const subscription = api_base.api.onMessage().subscribe(response => {
if (response.data.msg_type === 'proposal') {
const { passthrough, proposal } = response.data;
- if (
- proposal &&
- this.data.proposals.findIndex(p => p.id === proposal.id) === -1 &&
- !this.data.forget_proposal_ids.includes(proposal.id)
- ) {
+ if (proposal && this.data.proposals.findIndex(p => p.id === proposal.id) === -1) {
// Add proposals based on the ID returned by the API.
this.data.proposals.push({ ...proposal, ...passthrough });
this.checkProposalReady();
@@ -112,31 +105,6 @@ export default Engine =>
api_base.pushSubscription(subscription);
}
- unsubscribeProposals() {
- const { proposals } = this.data;
- const removeForgetProposalById = forget_proposal_id =>
- (this.data.forget_proposal_ids = this.data.forget_proposal_ids.filter(id => id !== forget_proposal_id));
-
- this.clearProposals();
-
- return Promise.all(
- proposals.map(proposal => {
- if (!this.data.forget_proposal_ids.includes(proposal.id)) {
- this.data.forget_proposal_ids.push(proposal.id);
- }
-
- if (proposal.error) {
- removeForgetProposalById(proposal.id);
- return Promise.resolve();
- }
-
- return doUntilDone(() => api_base.api.forget(proposal.id)).then(() => {
- removeForgetProposalById(proposal.id);
- });
- })
- );
- }
-
checkProposalReady() {
// Proposals are considered ready when the proposals in our memory match the ones
// we've requested from the API, we determine this by checking the passthrough of the response.
diff --git a/packages/bot-skeleton/src/services/tradeEngine/trade/Purchase.js b/packages/bot-skeleton/src/services/tradeEngine/trade/Purchase.js
index cd3730f840eb..50b88bdba299 100644
--- a/packages/bot-skeleton/src/services/tradeEngine/trade/Purchase.js
+++ b/packages/bot-skeleton/src/services/tradeEngine/trade/Purchase.js
@@ -29,7 +29,7 @@ export default Engine =>
buy,
});
- this.subscribeToOpenContract(buy.contract_id);
+ this.contractId = buy.contract_id;
this.store.dispatch(purchaseSuccessful());
this.renewProposalsOnPurchase();
delayIndex = 0;
diff --git a/packages/bot-skeleton/src/services/tradeEngine/trade/index.js b/packages/bot-skeleton/src/services/tradeEngine/trade/index.js
index 0a619a075f0f..c62865207d9e 100644
--- a/packages/bot-skeleton/src/services/tradeEngine/trade/index.js
+++ b/packages/bot-skeleton/src/services/tradeEngine/trade/index.js
@@ -71,7 +71,6 @@ export default class TradeEngine extends Balance(Purchase(Sell(OpenContract(Prop
this.data = {
contract: {},
proposals: [],
- forget_proposal_ids: [],
};
this.store = createStore(rootReducer, applyMiddleware(thunk));
}
diff --git a/packages/bot-skeleton/src/utils/index.js b/packages/bot-skeleton/src/utils/index.js
index 795af467ae88..160b4435faf1 100644
--- a/packages/bot-skeleton/src/utils/index.js
+++ b/packages/bot-skeleton/src/utils/index.js
@@ -5,3 +5,5 @@ export { importExternal } from './html-helper';
export { onWorkspaceResize } from './workspace';
export { getSavedWorkspaces, saveWorkspaceToRecent, removeExistingWorkspace } from './local-storage';
export { timeSince } from './date-time-helper';
+export { setColors } from '../scratch/hooks/colours';
+export { blocksCoordinate } from '../scratch/hooks/block_svg';
diff --git a/packages/bot-skeleton/src/utils/local-storage.js b/packages/bot-skeleton/src/utils/local-storage.js
index f6ab6194614f..c3d82c4b1ac2 100644
--- a/packages/bot-skeleton/src/utils/local-storage.js
+++ b/packages/bot-skeleton/src/utils/local-storage.js
@@ -2,7 +2,6 @@ import LZString from 'lz-string';
import localForage from 'localforage';
import DBotStore from '../scratch/dbot-store';
import { save_types } from '../constants/save-type';
-
/**
* Save workspace to localStorage
* @param {String} save_type // constants/save_types.js (unsaved, local, googledrive)
@@ -11,7 +10,11 @@ import { save_types } from '../constants/save-type';
export const saveWorkspaceToRecent = async (xml, save_type = save_types.UNSAVED) => {
// Ensure strategies don't go through expensive conversion.
xml.setAttribute('is_dbot', true);
- const { save_modal } = DBotStore.instance;
+ const {
+ load_modal: { updateListStrategies },
+ save_modal,
+ } = DBotStore.instance;
+
const workspace_id = Blockly.derivWorkspace.current_strategy_id || Blockly.utils.genUid();
const workspaces = await getSavedWorkspaces();
const current_xml = Blockly.Xml.domToText(xml);
@@ -43,7 +46,7 @@ export const saveWorkspaceToRecent = async (xml, save_type = save_types.UNSAVED)
if (workspaces.length > 10) {
workspaces.pop();
}
-
+ updateListStrategies(workspaces);
localForage.setItem('saved_workspaces', LZString.compress(JSON.stringify(workspaces)));
};
diff --git a/packages/bot-skeleton/src/utils/workspace.js b/packages/bot-skeleton/src/utils/workspace.js
index 391d4ca4074a..60bc2e6e0808 100644
--- a/packages/bot-skeleton/src/utils/workspace.js
+++ b/packages/bot-skeleton/src/utils/workspace.js
@@ -19,7 +19,7 @@ export const onWorkspaceResize = () => {
const el_scratch_div = document.getElementById('scratch_div');
if (el_scratch_div) {
- el_scratch_div.style.width = '100vw';
+ el_scratch_div.style.width = 'calc(100vw - 3.2rem)';
el_scratch_div.style.height = 'var(--bot-content-height)';
Blockly.svgResize(workspace);
}
diff --git a/packages/bot-web-ui/.eslintrc.js b/packages/bot-web-ui/.eslintrc.js
index d148761aca9d..438b2b413cbc 100644
--- a/packages/bot-web-ui/.eslintrc.js
+++ b/packages/bot-web-ui/.eslintrc.js
@@ -17,4 +17,51 @@ module.exports = {
webpack: { config: webpackConfig({}) },
},
},
-};
\ No newline at end of file
+ plugins: ['simple-import-sort'],
+ rules: {
+ 'simple-import-sort/imports': 'warn',
+ 'simple-import-sort/exports': 'warn',
+ },
+ overrides: [
+ {
+ files: ['**/*.js', '**/*.ts', '**/*.tsx', '**/*.jsx'],
+ rules: {
+ 'simple-import-sort/imports': [
+ 'warn',
+ {
+ groups: [
+ [
+ 'public-path',
+ // `react` first, then packages starting with a character
+ '^react$',
+ '^[a-z]',
+ // Packages starting with `@`
+ '^@',
+ // Packages starting with `~`
+ '^~',
+ '^Components',
+ '^Constants',
+ '^Utils',
+ '^Types',
+ '^Stores',
+ // Imports starting with `../`
+ '^\\.\\.(?!/?$)',
+ '^\\.\\./?$',
+ // Imports starting with `./`
+ '^\\./(?=.*/)(?!/?$)',
+ '^\\.(?!/?$)',
+ '^\\./?$',
+ // Style imports
+ '^.+\\.s?css$',
+ // Side effect imports
+ '^\\u0000',
+ // Delete the empty line copied as the next line of the last import
+ '\\s*',
+ ],
+ ],
+ },
+ ],
+ },
+ },
+ ],
+};
diff --git a/packages/bot-web-ui/jest.config.js b/packages/bot-web-ui/jest.config.js
new file mode 100644
index 000000000000..779f8f2cbf1a
--- /dev/null
+++ b/packages/bot-web-ui/jest.config.js
@@ -0,0 +1,19 @@
+const baseConfigForPackages = require('../../jest.config.base');
+
+module.exports = {
+ ...baseConfigForPackages,
+ clearMocks: true,
+ moduleNameMapper: {
+ '\\.s(c|a)ss$': '/../../__mocks__/styleMock.js',
+ '^.+\\.svg$': '/../../__mocks__/styleMock.js',
+ '^App/(.*)$': '/src/app/$1',
+ '^Components/(.*)$': '/src/components/$1',
+ '^Constants/(.*)$': '/src/constants/$1',
+ '^Stores/(.*)$': '/src/stores/$1',
+ '^Utils/(.*)$': '/src/utils/$1',
+ },
+ collectCoverageFrom: ['**/*.{js,jsx,ts,tsx}', '!**/node_modules/**'],
+ globals: {
+ __webpack_public_path__: '/',
+ },
+};
diff --git a/packages/bot-web-ui/package-lock.json b/packages/bot-web-ui/package-lock.json
new file mode 100644
index 000000000000..dd2b96df9265
--- /dev/null
+++ b/packages/bot-web-ui/package-lock.json
@@ -0,0 +1,11563 @@
+{
+ "name": "@deriv/bot-web-ui",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "@deriv/bot-web-ui",
+ "version": "1.0.0",
+ "license": "MIT",
+ "dependencies": {
+ "@deriv/bot-skeleton": "^1.0.0",
+ "@deriv/components": "^1.0.0",
+ "@deriv/deriv-charts": "1.1.8",
+ "@deriv/shared": "^1.0.0",
+ "@deriv/stores": "^1.0.0",
+ "@deriv/translations": "^1.0.0",
+ "classnames": "^2.2.6",
+ "crc-32": "^1.2.0",
+ "formik": "^2.1.4",
+ "gh-pages": "^2.1.1",
+ "immutable": "^3.8.2",
+ "lodash.debounce": "^4.0.8",
+ "lz-string": "^1.4.4",
+ "mobx": "^6.6.1",
+ "mobx-react": "^7.5.1",
+ "pako": "^1.0.11",
+ "prop-types": "^15.7.2",
+ "react": "^17.0.2",
+ "react-content-loader": "^6.2.0",
+ "react-dom": "^17.0.2",
+ "react-joyride": "^2.5.3",
+ "react-transition-group": "4.4.2",
+ "yup": "^0.32.11"
+ },
+ "devDependencies": {
+ "@babel/eslint-parser": "^7.17.0",
+ "@babel/preset-react": "^7.16.7",
+ "@types/react": "^18.0.7",
+ "@types/react-dom": "^18.0.0",
+ "babel-loader": "^8.1.0",
+ "clean-webpack-plugin": "^3.0.0",
+ "concurrently": "^5.3.0",
+ "copy-webpack-plugin": "^9.0.1",
+ "css-hot-loader": "^1.4.4",
+ "css-loader": "^5.0.1",
+ "eslint-config-airbnb-base": "^14.2.1",
+ "eslint-config-binary": "^1.0.2",
+ "eslint-config-prettier": "^7.2.0",
+ "eslint-plugin-import": "^2.23.4",
+ "eslint-plugin-prettier": "^3.3.1",
+ "eslint-plugin-react": "^7.22.0",
+ "eslint-plugin-react-hooks": "^4.2.0",
+ "eslint-plugin-simple-import-sort": "^10.0.0",
+ "lint-staged": "^10.4.0",
+ "loader-utils": "^1.1.0",
+ "mini-css-extract-plugin": "^1.3.4",
+ "node-sass": "^7.0.1",
+ "raw-loader": "^4.0.0",
+ "sass-loader": "^12.6.0",
+ "sass-resources-loader": "^2.1.1",
+ "stylelint-webpack-plugin": "^2.1.1",
+ "svg-sprite-loader": "^5.2.1",
+ "typescript": "^4.6.3",
+ "webpack": "^5.46.0",
+ "webpack-cli": "^4.7.2"
+ },
+ "engines": {
+ "node": "^16.16.0"
+ }
+ },
+ "../api": {
+ "name": "@deriv/api",
+ "version": "1.0.0",
+ "dependencies": {
+ "@deriv/shared": "^1.0.0",
+ "@tanstack/react-query": "^4.28.0",
+ "@tanstack/react-query-devtools": "^4.28.0",
+ "react": "^17.0.2"
+ },
+ "devDependencies": {
+ "@deriv/api-types": "^1.0.94",
+ "@testing-library/react": "^12.0.0",
+ "@testing-library/react-hooks": "^7.0.2",
+ "@testing-library/user-event": "^13.5.0",
+ "typescript": "^4.6.3"
+ }
+ },
+ "../api/node_modules/@deriv/shared": {
+ "resolved": "../shared",
+ "link": true
+ },
+ "../bot-skeleton": {
+ "name": "@deriv/bot-skeleton",
+ "version": "1.0.0",
+ "license": "MIT",
+ "dependencies": {
+ "@deriv/deriv-api": "^1.0.11",
+ "@deriv/indicators": "^1.0.0",
+ "@deriv/js-interpreter": "^3.0.0",
+ "@deriv/shared": "^1.0.0",
+ "@deriv/translations": "^1.0.0",
+ "binary-utils": "^4.23.0",
+ "blockly": "3.20191014.4",
+ "file-saver": "^2.0.2",
+ "immutable": "^3.8.2",
+ "localforage": "^1.9.0",
+ "lz-string": "^1.4.4",
+ "mobx": "^6.6.1",
+ "mobx-react": "^7.5.1",
+ "redux": "^4.0.1",
+ "redux-thunk": "^2.2.0",
+ "scratch-blocks": "0.1.0-prerelease.20200917235131",
+ "shx": "^0.3.2"
+ },
+ "devDependencies": {
+ "@babel/eslint-parser": "^7.17.0",
+ "@babel/preset-react": "^7.16.7",
+ "@types/react": "^18.0.7",
+ "@types/react-dom": "^18.0.0",
+ "deep-diff": "^1.0.2",
+ "eslint-config-airbnb-base": "^14.2.1",
+ "eslint-config-binary": "^1.0.2",
+ "eslint-config-prettier": "^7.2.0",
+ "eslint-plugin-import": "^2.23.4",
+ "eslint-plugin-prettier": "^3.3.1",
+ "eslint-plugin-react": "^7.22.0",
+ "eslint-plugin-react-hooks": "^4.2.0",
+ "typescript": "^4.6.3"
+ }
+ },
+ "../bot-skeleton/node_modules/@deriv/indicators": {
+ "resolved": "../indicators",
+ "link": true
+ },
+ "../bot-skeleton/node_modules/@deriv/shared": {
+ "resolved": "../shared",
+ "link": true
+ },
+ "../bot-skeleton/node_modules/@deriv/translations": {
+ "resolved": "../translations",
+ "link": true
+ },
+ "../components": {
+ "name": "@deriv/components",
+ "version": "1.0.0",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@contentpass/zxcvbn": "^4.4.3",
+ "@deriv/shared": "^1.0.0",
+ "@deriv/translations": "^1.0.0",
+ "@enykeev/react-virtualized": "^9.22.4-mirror.1",
+ "classnames": "^2.2.6",
+ "framer-motion": "^6.5.1",
+ "gh-pages": "^2.1.1",
+ "glob": "^7.1.5",
+ "lodash.throttle": "^4.1.1",
+ "prop-types": "^15.7.2",
+ "react": "^17.0.2",
+ "react-content-loader": "^6.2.0",
+ "react-div-100vh": "^0.3.8",
+ "react-dom": "^17.0.2",
+ "react-dropzone": "11.0.1",
+ "react-router-dom": "^5.2.0",
+ "react-swipeable": "^6.2.1",
+ "react-tiny-popover": "^7.0.1",
+ "react-transition-group": "4.4.2"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.12.10",
+ "@babel/eslint-parser": "^7.17.0",
+ "@babel/preset-react": "^7.16.7",
+ "@storybook/addon-actions": "^6.5.10",
+ "@storybook/addon-essentials": "^6.5.10",
+ "@storybook/addon-info": "^5.3.21",
+ "@storybook/addon-interactions": "^6.5.10",
+ "@storybook/addon-knobs": "^6.4.0",
+ "@storybook/addon-links": "^6.5.10",
+ "@storybook/builder-webpack5": "^6.5.10",
+ "@storybook/manager-webpack5": "^6.5.10",
+ "@storybook/react": "^6.5.10",
+ "@storybook/testing-library": "^0.0.13",
+ "@testing-library/react": "^12.0.0",
+ "@types/lodash.throttle": "^4.1.7",
+ "@types/react": "^18.0.7",
+ "@types/react-dom": "^18.0.0",
+ "babel-loader": "^8.1.0",
+ "copy-webpack-plugin": "^9.0.1",
+ "copy-webpack-plugin-v6": "npm:copy-webpack-plugin@6",
+ "cross-env": "^5.2.0",
+ "eslint-config-airbnb-base": "^14.2.1",
+ "eslint-config-binary": "^1.0.2",
+ "eslint-config-prettier": "^7.2.0",
+ "eslint-plugin-import": "^2.23.4",
+ "eslint-plugin-prettier": "^3.3.1",
+ "eslint-plugin-react": "^7.22.0",
+ "eslint-plugin-react-hooks": "^4.2.0",
+ "lint-staged": "^10.4.0",
+ "node-sass": "^7.0.1",
+ "sass-loader": "^12.6.0",
+ "sass-resources-loader": "^2.1.1",
+ "style-loader": "^1.2.1",
+ "svg-sprite-loader": "^5.2.1",
+ "svgo-loader": "^3.0.0",
+ "typescript": "^4.6.3",
+ "webpack": "^5.46.0",
+ "webpack-bundle-analyzer": "^4.3.0",
+ "webpack-cli": "^4.7.2"
+ },
+ "engines": {
+ "node": "^16.16.0"
+ }
+ },
+ "../components/node_modules/@deriv/shared": {
+ "resolved": "../shared",
+ "link": true
+ },
+ "../components/node_modules/@deriv/translations": {
+ "resolved": "../translations",
+ "link": true
+ },
+ "../indicators": {
+ "name": "@deriv/indicators",
+ "version": "1.0.0",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@deriv/shared": "^1.0.0",
+ "react": "^17.0.2",
+ "react-dom": "^17.0.2"
+ },
+ "devDependencies": {
+ "@babel/eslint-parser": "^7.17.0",
+ "@babel/preset-env": "^7.12.11",
+ "@babel/preset-react": "^7.16.7",
+ "eslint-config-airbnb-base": "^14.2.1",
+ "eslint-config-binary": "^1.0.2",
+ "eslint-config-prettier": "^7.2.0",
+ "eslint-plugin-import": "^2.23.4",
+ "eslint-plugin-prettier": "^3.3.1",
+ "ts-jest": "^26.4.2"
+ },
+ "engines": {
+ "node": "^16.16.0"
+ }
+ },
+ "../indicators/node_modules/@deriv/shared": {
+ "resolved": "../shared",
+ "link": true
+ },
+ "../shared": {
+ "name": "@deriv/shared",
+ "version": "1.0.0",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@deriv/api-types": "^1.0.94",
+ "@deriv/translations": "^1.0.0",
+ "@types/js-cookie": "^3.0.1",
+ "@types/react-loadable": "^5.5.6",
+ "canvas-toBlob": "^1.0.0",
+ "extend": "^3.0.2",
+ "i18next": "^22.4.6",
+ "js-cookie": "^2.2.1",
+ "mobx": "^6.6.1",
+ "moment": "^2.29.2",
+ "object.fromentries": "^2.0.0",
+ "react": "^17.0.2",
+ "react-loadable": "^5.5.0"
+ },
+ "devDependencies": {
+ "@babel/eslint-parser": "^7.17.0",
+ "@babel/preset-react": "^7.16.7",
+ "@types/jsdom": "^20.0.0",
+ "@types/react": "^18.0.7",
+ "@types/react-dom": "^18.0.0",
+ "jsdom": "^16.2.1",
+ "moment": "^2.29.2",
+ "typescript": "^4.6.3"
+ },
+ "engines": {
+ "node": "^16.16.0"
+ }
+ },
+ "../shared/node_modules/@deriv/translations": {
+ "resolved": "../translations",
+ "link": true
+ },
+ "../stores": {
+ "name": "@deriv/stores",
+ "version": "1.0.0",
+ "dependencies": {
+ "@deriv/api": "1.0.0",
+ "lodash.merge": "^4.6.2",
+ "mobx": "^6.6.1",
+ "mobx-persist-store": "1.1.2",
+ "mobx-react-lite": "^3.4.0",
+ "react": "^17.0.2"
+ },
+ "devDependencies": {
+ "@deriv/api-types": "^1.0.94",
+ "@testing-library/react": "^12.0.0",
+ "@testing-library/react-hooks": "^7.0.2",
+ "@types/lodash.merge": "^4.6.7",
+ "moment": "^2.29.2",
+ "react-router": "^5.2.0",
+ "typescript": "^4.6.3"
+ }
+ },
+ "../stores/node_modules/@deriv/api": {
+ "resolved": "../api",
+ "link": true
+ },
+ "../translations": {
+ "name": "@deriv/translations",
+ "version": "1.0.0",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "commander": "^3.0.2",
+ "crc-32": "^1.2.0",
+ "glob": "^7.1.5",
+ "i18next": "^22.4.6",
+ "prop-types": "^15.7.2",
+ "react": "^17.0.2",
+ "react-i18next": "^11.11.0"
+ },
+ "devDependencies": {
+ "@babel/eslint-parser": "^7.17.0",
+ "@babel/preset-react": "^7.16.7",
+ "@xmldom/xmldom": "^0.8.4",
+ "cross-env": "^5.2.0"
+ },
+ "engines": {
+ "node": "^16.16.0"
+ }
+ },
+ "node_modules/@ampproject/remapping": {
+ "version": "2.2.0",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.1.0",
+ "@jridgewell/trace-mapping": "^0.3.9"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@ampproject/remapping/node_modules/@jridgewell/gen-mapping": {
+ "version": "0.1.1",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@jridgewell/set-array": "^1.0.0",
+ "@jridgewell/sourcemap-codec": "^1.4.10"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.18.6",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/highlight": "^7.18.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/code-frame/node_modules/@babel/helper-validator-identifier": {
+ "version": "7.18.6",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/code-frame/node_modules/@babel/highlight": {
+ "version": "7.18.6",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.18.6",
+ "chalk": "^2.0.0",
+ "js-tokens": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.19.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@ampproject/remapping": "^2.1.0",
+ "@babel/code-frame": "^7.18.6",
+ "@babel/generator": "^7.19.0",
+ "@babel/helper-compilation-targets": "^7.19.0",
+ "@babel/helper-module-transforms": "^7.19.0",
+ "@babel/helpers": "^7.19.0",
+ "@babel/parser": "^7.19.0",
+ "@babel/template": "^7.18.10",
+ "@babel/traverse": "^7.19.0",
+ "@babel/types": "^7.19.0",
+ "convert-source-map": "^1.7.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.1",
+ "semver": "^6.3.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/eslint-parser": {
+ "version": "7.21.8",
+ "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.21.8.tgz",
+ "integrity": "sha512-HLhI+2q+BP3sf78mFUZNCGc10KEmoUqtUT1OCdMZsN+qr4qFeLUod62/zAnF3jNQstwyasDkZnVXwfK2Bml7MQ==",
+ "dev": true,
+ "dependencies": {
+ "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1",
+ "eslint-visitor-keys": "^2.1.0",
+ "semver": "^6.3.0"
+ },
+ "engines": {
+ "node": "^10.13.0 || ^12.13.0 || >=14.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": ">=7.11.0",
+ "eslint": "^7.5.0 || ^8.0.0"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.19.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/types": "^7.19.0",
+ "@jridgewell/gen-mapping": "^0.3.2",
+ "jsesc": "^2.5.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/generator/node_modules/jsesc": {
+ "version": "2.5.2",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/helper-annotate-as-pure": {
+ "version": "7.18.6",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.18.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.19.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/compat-data": "^7.19.0",
+ "@babel/helper-validator-option": "^7.18.6",
+ "browserslist": "^4.20.2",
+ "semver": "^6.3.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets/node_modules/@babel/compat-data": {
+ "version": "7.19.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.18.6",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.18.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.19.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/helper-environment-visitor": "^7.18.9",
+ "@babel/helper-module-imports": "^7.18.6",
+ "@babel/helper-simple-access": "^7.18.6",
+ "@babel/helper-split-export-declaration": "^7.18.6",
+ "@babel/helper-validator-identifier": "^7.18.6",
+ "@babel/template": "^7.18.10",
+ "@babel/traverse": "^7.19.0",
+ "@babel/types": "^7.19.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms/node_modules/@babel/helper-environment-visitor": {
+ "version": "7.18.9",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms/node_modules/@babel/helper-simple-access": {
+ "version": "7.18.6",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/types": "^7.18.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms/node_modules/@babel/helper-split-export-declaration": {
+ "version": "7.18.6",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/types": "^7.18.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms/node_modules/@babel/helper-validator-identifier": {
+ "version": "7.18.6",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.19.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.18.6",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.19.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/template": "^7.18.10",
+ "@babel/traverse": "^7.19.0",
+ "@babel/types": "^7.19.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.19.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-jsx": {
+ "version": "7.18.6",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.18.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-display-name": {
+ "version": "7.18.6",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.18.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx": {
+ "version": "7.19.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.18.6",
+ "@babel/helper-module-imports": "^7.18.6",
+ "@babel/helper-plugin-utils": "^7.19.0",
+ "@babel/plugin-syntax-jsx": "^7.18.6",
+ "@babel/types": "^7.19.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-development": {
+ "version": "7.18.6",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/plugin-transform-react-jsx": "^7.18.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-pure-annotations": {
+ "version": "7.18.6",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.18.6",
+ "@babel/helper-plugin-utils": "^7.18.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/preset-react": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.18.6.tgz",
+ "integrity": "sha512-zXr6atUmyYdiWRVLOZahakYmOBHtWc2WGCkP8PYTgZi0iJXDY2CN180TdrIW4OGOAdLc7TifzDIvtx6izaRIzg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.18.6",
+ "@babel/helper-validator-option": "^7.18.6",
+ "@babel/plugin-transform-react-display-name": "^7.18.6",
+ "@babel/plugin-transform-react-jsx": "^7.18.6",
+ "@babel/plugin-transform-react-jsx-development": "^7.18.6",
+ "@babel/plugin-transform-react-pure-annotations": "^7.18.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/runtime": {
+ "version": "7.19.0",
+ "license": "MIT",
+ "dependencies": {
+ "regenerator-runtime": "^0.13.4"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.18.10",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.18.6",
+ "@babel/parser": "^7.18.10",
+ "@babel/types": "^7.18.10"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.19.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.18.6",
+ "@babel/generator": "^7.19.0",
+ "@babel/helper-environment-visitor": "^7.18.9",
+ "@babel/helper-function-name": "^7.19.0",
+ "@babel/helper-hoist-variables": "^7.18.6",
+ "@babel/helper-split-export-declaration": "^7.18.6",
+ "@babel/parser": "^7.19.0",
+ "@babel/types": "^7.19.0",
+ "debug": "^4.1.0",
+ "globals": "^11.1.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse/node_modules/@babel/helper-environment-visitor": {
+ "version": "7.18.9",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse/node_modules/@babel/helper-function-name": {
+ "version": "7.19.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/template": "^7.18.10",
+ "@babel/types": "^7.19.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse/node_modules/@babel/helper-hoist-variables": {
+ "version": "7.18.6",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/types": "^7.18.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse/node_modules/@babel/helper-split-export-declaration": {
+ "version": "7.18.6",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/types": "^7.18.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.19.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.18.10",
+ "@babel/helper-validator-identifier": "^7.18.6",
+ "to-fast-properties": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types/node_modules/@babel/helper-string-parser": {
+ "version": "7.18.10",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types/node_modules/@babel/helper-validator-identifier": {
+ "version": "7.18.6",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@csstools/selector-specificity": {
+ "version": "2.0.2",
+ "dev": true,
+ "license": "CC0-1.0",
+ "peer": true,
+ "engines": {
+ "node": "^12 || ^14 || >=16"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ },
+ "peerDependencies": {
+ "postcss": "^8.2",
+ "postcss-selector-parser": "^6.0.10"
+ }
+ },
+ "node_modules/@deriv/bot-skeleton": {
+ "resolved": "../bot-skeleton",
+ "link": true
+ },
+ "node_modules/@deriv/components": {
+ "resolved": "../components",
+ "link": true
+ },
+ "node_modules/@deriv/deriv-charts": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/@deriv/deriv-charts/-/deriv-charts-1.1.8.tgz",
+ "integrity": "sha512-IBAHcWK99sl8SefAb9zPxKV4AX3EauxELNOpxx5DmeHlq/peOnL5qf2HN7fRq/uOtZvn5Ol1QRnJAFsSFU2irA==",
+ "dependencies": {
+ "@welldone-software/why-did-you-render": "^3.3.8",
+ "classnames": "^2.3.1",
+ "event-emitter-es6": "^1.1.5",
+ "lodash.debounce": "^4.0.8",
+ "mobx": "^6.5.0",
+ "mobx-react-lite": "^3.4.0",
+ "moment": "^2.24.0",
+ "prop-types": "^15.7.2",
+ "react": "^16.13.1",
+ "react-dom": "^16.13.1",
+ "react-tabs": "^3.0.0",
+ "react-transition-group": "^4.4.1",
+ "robust-websocket": "^1.0.0",
+ "sinon-chai": "^3.7.0",
+ "url-search-params-polyfill": "^7.0.0"
+ }
+ },
+ "node_modules/@deriv/deriv-charts/node_modules/react": {
+ "version": "16.14.0",
+ "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz",
+ "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "object-assign": "^4.1.1",
+ "prop-types": "^15.6.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/@deriv/deriv-charts/node_modules/react-dom": {
+ "version": "16.14.0",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz",
+ "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "object-assign": "^4.1.1",
+ "prop-types": "^15.6.2",
+ "scheduler": "^0.19.1"
+ },
+ "peerDependencies": {
+ "react": "^16.14.0"
+ }
+ },
+ "node_modules/@deriv/deriv-charts/node_modules/scheduler": {
+ "version": "0.19.1",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz",
+ "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "object-assign": "^4.1.1"
+ }
+ },
+ "node_modules/@deriv/shared": {
+ "resolved": "../shared",
+ "link": true
+ },
+ "node_modules/@deriv/stores": {
+ "resolved": "../stores",
+ "link": true
+ },
+ "node_modules/@deriv/translations": {
+ "resolved": "../translations",
+ "link": true
+ },
+ "node_modules/@discoveryjs/json-ext": {
+ "version": "0.5.7",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "0.4.3",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.1.1",
+ "espree": "^7.3.0",
+ "globals": "^13.9.0",
+ "ignore": "^4.0.6",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^3.13.1",
+ "minimatch": "^3.0.4",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/globals": {
+ "version": "13.17.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/type-fest": {
+ "version": "0.20.2",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "peer": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@gar/promisify": {
+ "version": "1.1.3",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@gilbarbara/deep-equal": {
+ "version": "0.1.1",
+ "license": "MIT"
+ },
+ "node_modules/@humanwhocodes/config-array": {
+ "version": "0.5.0",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "dependencies": {
+ "@humanwhocodes/object-schema": "^1.2.0",
+ "debug": "^4.1.1",
+ "minimatch": "^3.0.4"
+ },
+ "engines": {
+ "node": ">=10.10.0"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array/node_modules/@humanwhocodes/object-schema": {
+ "version": "1.2.1",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "peer": true
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/set-array": "^1.0.1",
+ "@jridgewell/sourcemap-codec": "^1.4.10",
+ "@jridgewell/trace-mapping": "^0.3.9"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/set-array": {
+ "version": "1.1.2",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/source-map": {
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz",
+ "integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.0",
+ "@jridgewell/trace-mapping": "^0.3.9"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.4.14",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.18",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz",
+ "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/resolve-uri": "3.1.0",
+ "@jridgewell/sourcemap-codec": "1.4.14"
+ }
+ },
+ "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": {
+ "version": "5.1.1-v1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-scope": "5.1.1"
+ }
+ },
+ "node_modules/@npmcli/fs": {
+ "version": "1.1.1",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "@gar/promisify": "^1.0.1",
+ "semver": "^7.3.5"
+ }
+ },
+ "node_modules/@npmcli/fs/node_modules/semver": {
+ "version": "7.3.8",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@npmcli/move-file": {
+ "version": "1.1.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mkdirp": "^1.0.4",
+ "rimraf": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@npmcli/move-file/node_modules/rimraf": {
+ "version": "3.0.2",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@sinonjs/commons": {
+ "version": "1.8.3",
+ "license": "BSD-3-Clause",
+ "peer": true,
+ "dependencies": {
+ "type-detect": "4.0.8"
+ }
+ },
+ "node_modules/@sinonjs/fake-timers": {
+ "version": "9.1.2",
+ "license": "BSD-3-Clause",
+ "peer": true,
+ "dependencies": {
+ "@sinonjs/commons": "^1.7.0"
+ }
+ },
+ "node_modules/@sinonjs/samsam": {
+ "version": "6.1.1",
+ "license": "BSD-3-Clause",
+ "peer": true,
+ "dependencies": {
+ "@sinonjs/commons": "^1.6.0",
+ "lodash.get": "^4.4.2",
+ "type-detect": "^4.0.8"
+ }
+ },
+ "node_modules/@sinonjs/text-encoding": {
+ "version": "0.7.2",
+ "license": "(Unlicense OR Apache-2.0)",
+ "peer": true
+ },
+ "node_modules/@tootallnate/once": {
+ "version": "1.1.2",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/@types/eslint-scope": {
+ "version": "3.7.4",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/eslint": "*",
+ "@types/estree": "*"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz",
+ "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==",
+ "dev": true
+ },
+ "node_modules/@types/glob": {
+ "version": "7.2.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/minimatch": "*",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.11",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/json5": {
+ "version": "0.0.29",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/lodash": {
+ "version": "4.14.186",
+ "license": "MIT"
+ },
+ "node_modules/@types/minimatch": {
+ "version": "5.1.2",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/minimist": {
+ "version": "1.2.2",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/normalize-package-data": {
+ "version": "2.4.1",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/parse-json": {
+ "version": "4.0.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/prop-types": {
+ "version": "15.7.5",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "18.2.6",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.6.tgz",
+ "integrity": "sha512-wRZClXn//zxCFW+ye/D2qY65UsYP1Fpex2YXorHc8awoNamkMZSvBxwxdYVInsHOZZd2Ppq8isnSzJL5Mpf8OA==",
+ "dev": true,
+ "dependencies": {
+ "@types/prop-types": "*",
+ "@types/scheduler": "*",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "18.2.4",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.4.tgz",
+ "integrity": "sha512-G2mHoTMTL4yoydITgOGwWdWMVd8sNgyEP85xVmMKAPUBwQWm9wBPQUmvbeF4V3WBY1P7mmL4BkjQ0SqUpf1snw==",
+ "dev": true,
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
+ "node_modules/@types/scheduler": {
+ "version": "0.16.2",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/tapable": {
+ "version": "1.0.8",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/uglify-js": {
+ "version": "3.17.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "source-map": "^0.6.1"
+ }
+ },
+ "node_modules/@types/uglify-js/node_modules/source-map": {
+ "version": "0.6.1",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/@types/webpack": {
+ "version": "4.41.32",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "@types/tapable": "^1",
+ "@types/uglify-js": "*",
+ "@types/webpack-sources": "*",
+ "anymatch": "^3.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
+ "node_modules/@types/webpack/node_modules/source-map": {
+ "version": "0.6.1",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/@webassemblyjs/ast": {
+ "version": "1.11.6",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz",
+ "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==",
+ "dev": true,
+ "dependencies": {
+ "@webassemblyjs/helper-numbers": "1.11.6",
+ "@webassemblyjs/helper-wasm-bytecode": "1.11.6"
+ }
+ },
+ "node_modules/@webassemblyjs/floating-point-hex-parser": {
+ "version": "1.11.6",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz",
+ "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==",
+ "dev": true
+ },
+ "node_modules/@webassemblyjs/helper-api-error": {
+ "version": "1.11.6",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz",
+ "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==",
+ "dev": true
+ },
+ "node_modules/@webassemblyjs/helper-buffer": {
+ "version": "1.11.6",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz",
+ "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==",
+ "dev": true
+ },
+ "node_modules/@webassemblyjs/helper-numbers": {
+ "version": "1.11.6",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz",
+ "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==",
+ "dev": true,
+ "dependencies": {
+ "@webassemblyjs/floating-point-hex-parser": "1.11.6",
+ "@webassemblyjs/helper-api-error": "1.11.6",
+ "@xtuc/long": "4.2.2"
+ }
+ },
+ "node_modules/@webassemblyjs/helper-wasm-bytecode": {
+ "version": "1.11.6",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz",
+ "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==",
+ "dev": true
+ },
+ "node_modules/@webassemblyjs/helper-wasm-section": {
+ "version": "1.11.6",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz",
+ "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==",
+ "dev": true,
+ "dependencies": {
+ "@webassemblyjs/ast": "1.11.6",
+ "@webassemblyjs/helper-buffer": "1.11.6",
+ "@webassemblyjs/helper-wasm-bytecode": "1.11.6",
+ "@webassemblyjs/wasm-gen": "1.11.6"
+ }
+ },
+ "node_modules/@webassemblyjs/ieee754": {
+ "version": "1.11.6",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz",
+ "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==",
+ "dev": true,
+ "dependencies": {
+ "@xtuc/ieee754": "^1.2.0"
+ }
+ },
+ "node_modules/@webassemblyjs/leb128": {
+ "version": "1.11.6",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz",
+ "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==",
+ "dev": true,
+ "dependencies": {
+ "@xtuc/long": "4.2.2"
+ }
+ },
+ "node_modules/@webassemblyjs/utf8": {
+ "version": "1.11.6",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz",
+ "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==",
+ "dev": true
+ },
+ "node_modules/@webassemblyjs/wasm-edit": {
+ "version": "1.11.6",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz",
+ "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==",
+ "dev": true,
+ "dependencies": {
+ "@webassemblyjs/ast": "1.11.6",
+ "@webassemblyjs/helper-buffer": "1.11.6",
+ "@webassemblyjs/helper-wasm-bytecode": "1.11.6",
+ "@webassemblyjs/helper-wasm-section": "1.11.6",
+ "@webassemblyjs/wasm-gen": "1.11.6",
+ "@webassemblyjs/wasm-opt": "1.11.6",
+ "@webassemblyjs/wasm-parser": "1.11.6",
+ "@webassemblyjs/wast-printer": "1.11.6"
+ }
+ },
+ "node_modules/@webassemblyjs/wasm-gen": {
+ "version": "1.11.6",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz",
+ "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==",
+ "dev": true,
+ "dependencies": {
+ "@webassemblyjs/ast": "1.11.6",
+ "@webassemblyjs/helper-wasm-bytecode": "1.11.6",
+ "@webassemblyjs/ieee754": "1.11.6",
+ "@webassemblyjs/leb128": "1.11.6",
+ "@webassemblyjs/utf8": "1.11.6"
+ }
+ },
+ "node_modules/@webassemblyjs/wasm-opt": {
+ "version": "1.11.6",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz",
+ "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==",
+ "dev": true,
+ "dependencies": {
+ "@webassemblyjs/ast": "1.11.6",
+ "@webassemblyjs/helper-buffer": "1.11.6",
+ "@webassemblyjs/wasm-gen": "1.11.6",
+ "@webassemblyjs/wasm-parser": "1.11.6"
+ }
+ },
+ "node_modules/@webassemblyjs/wasm-parser": {
+ "version": "1.11.6",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz",
+ "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==",
+ "dev": true,
+ "dependencies": {
+ "@webassemblyjs/ast": "1.11.6",
+ "@webassemblyjs/helper-api-error": "1.11.6",
+ "@webassemblyjs/helper-wasm-bytecode": "1.11.6",
+ "@webassemblyjs/ieee754": "1.11.6",
+ "@webassemblyjs/leb128": "1.11.6",
+ "@webassemblyjs/utf8": "1.11.6"
+ }
+ },
+ "node_modules/@webassemblyjs/wast-printer": {
+ "version": "1.11.6",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz",
+ "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==",
+ "dev": true,
+ "dependencies": {
+ "@webassemblyjs/ast": "1.11.6",
+ "@xtuc/long": "4.2.2"
+ }
+ },
+ "node_modules/@webpack-cli/configtest": {
+ "version": "1.2.0",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "webpack": "4.x.x || 5.x.x",
+ "webpack-cli": "4.x.x"
+ }
+ },
+ "node_modules/@webpack-cli/info": {
+ "version": "1.5.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "envinfo": "^7.7.3"
+ },
+ "peerDependencies": {
+ "webpack-cli": "4.x.x"
+ }
+ },
+ "node_modules/@webpack-cli/serve": {
+ "version": "1.7.0",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "webpack-cli": "4.x.x"
+ },
+ "peerDependenciesMeta": {
+ "webpack-dev-server": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@welldone-software/why-did-you-render": {
+ "version": "3.6.0",
+ "license": "MIT",
+ "dependencies": {
+ "lodash": "^4"
+ },
+ "peerDependencies": {
+ "react": ">=16.12"
+ }
+ },
+ "node_modules/@xtuc/ieee754": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
+ "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
+ "dev": true
+ },
+ "node_modules/@xtuc/long": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
+ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
+ "dev": true
+ },
+ "node_modules/abbrev": {
+ "version": "1.1.1",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/acorn": {
+ "version": "7.4.1",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/agent-base": {
+ "version": "6.0.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/agentkeepalive": {
+ "version": "4.2.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.1.0",
+ "depd": "^1.1.2",
+ "humanize-ms": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 8.0.0"
+ }
+ },
+ "node_modules/agentkeepalive/node_modules/depd": {
+ "version": "1.1.2",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/aggregate-error": {
+ "version": "3.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "clean-stack": "^2.0.0",
+ "indent-string": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ajv-keywords": {
+ "version": "3.5.2",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "ajv": "^6.9.1"
+ }
+ },
+ "node_modules/ajv/node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/ajv/node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/ajv/node_modules/uri-js": {
+ "version": "4.4.1",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/ansi-colors": {
+ "version": "4.1.3",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/ansi-escapes": {
+ "version": "4.3.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "type-fest": "^0.21.3"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ansi-escapes/node_modules/type-fest": {
+ "version": "0.21.3",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "3.2.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.2",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/anymatch/node_modules/picomatch": {
+ "version": "2.3.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/aproba": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/are-we-there-yet": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "delegates": "^1.0.0",
+ "readable-stream": "^3.6.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/are-we-there-yet/node_modules/readable-stream": {
+ "version": "3.6.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/are-we-there-yet/node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/are-we-there-yet/node_modules/string_decoder": {
+ "version": "1.3.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
+ "node_modules/arr-diff": {
+ "version": "4.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/arr-flatten": {
+ "version": "1.1.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/arr-union": {
+ "version": "3.1.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/array-buffer-byte-length": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz",
+ "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "is-array-buffer": "^3.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array-includes": {
+ "version": "3.1.6",
+ "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz",
+ "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4",
+ "get-intrinsic": "^1.1.3",
+ "is-string": "^1.0.7"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array-union": {
+ "version": "2.1.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/array-uniq": {
+ "version": "1.0.3",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/array-unique": {
+ "version": "0.3.2",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/array.prototype.flat": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz",
+ "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4",
+ "es-shim-unscopables": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.flatmap": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz",
+ "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4",
+ "es-shim-unscopables": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.tosorted": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.1.tgz",
+ "integrity": "sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4",
+ "es-shim-unscopables": "^1.0.0",
+ "get-intrinsic": "^1.1.3"
+ }
+ },
+ "node_modules/arrify": {
+ "version": "1.0.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/asn1": {
+ "version": "0.2.6",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": "~2.1.0"
+ }
+ },
+ "node_modules/assert-plus": {
+ "version": "1.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/assertion-error": {
+ "version": "1.1.0",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/assign-symbols": {
+ "version": "1.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/astral-regex": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/async": {
+ "version": "2.6.4",
+ "license": "MIT",
+ "dependencies": {
+ "lodash": "^4.17.14"
+ }
+ },
+ "node_modules/async-foreach": {
+ "version": "0.1.3",
+ "dev": true,
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/atob": {
+ "version": "2.1.2",
+ "dev": true,
+ "license": "(MIT OR Apache-2.0)",
+ "bin": {
+ "atob": "bin/atob.js"
+ },
+ "engines": {
+ "node": ">= 4.5.0"
+ }
+ },
+ "node_modules/available-typed-arrays": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
+ "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/aws-sign2": {
+ "version": "0.7.0",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/aws4": {
+ "version": "1.11.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/babel-loader": {
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.3.0.tgz",
+ "integrity": "sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q==",
+ "dev": true,
+ "dependencies": {
+ "find-cache-dir": "^3.3.1",
+ "loader-utils": "^2.0.0",
+ "make-dir": "^3.1.0",
+ "schema-utils": "^2.6.5"
+ },
+ "engines": {
+ "node": ">= 8.9"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0",
+ "webpack": ">=2"
+ }
+ },
+ "node_modules/babel-loader/node_modules/loader-utils": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
+ "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
+ "dev": true,
+ "dependencies": {
+ "big.js": "^5.2.2",
+ "emojis-list": "^3.0.0",
+ "json5": "^2.1.2"
+ },
+ "engines": {
+ "node": ">=8.9.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "license": "MIT"
+ },
+ "node_modules/base": {
+ "version": "0.11.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cache-base": "^1.0.1",
+ "class-utils": "^0.3.5",
+ "component-emitter": "^1.2.1",
+ "define-property": "^1.0.0",
+ "isobject": "^3.0.1",
+ "mixin-deep": "^1.2.0",
+ "pascalcase": "^0.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/bcrypt-pbkdf": {
+ "version": "1.0.2",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "tweetnacl": "^0.14.3"
+ }
+ },
+ "node_modules/big.js": {
+ "version": "5.2.2",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/bluebird": {
+ "version": "3.7.2",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.21.3",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "caniuse-lite": "^1.0.30001370",
+ "electron-to-chromium": "^1.4.202",
+ "node-releases": "^2.0.6",
+ "update-browserslist-db": "^1.0.5"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/browserslist/node_modules/caniuse-lite": {
+ "version": "1.0.30001390",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/browserslist/node_modules/node-releases": {
+ "version": "2.0.6",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/buffer-from": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+ "dev": true
+ },
+ "node_modules/cacache": {
+ "version": "15.3.0",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "@npmcli/fs": "^1.0.0",
+ "@npmcli/move-file": "^1.0.1",
+ "chownr": "^2.0.0",
+ "fs-minipass": "^2.0.0",
+ "glob": "^7.1.4",
+ "infer-owner": "^1.0.4",
+ "lru-cache": "^6.0.0",
+ "minipass": "^3.1.1",
+ "minipass-collect": "^1.0.2",
+ "minipass-flush": "^1.0.5",
+ "minipass-pipeline": "^1.2.2",
+ "mkdirp": "^1.0.3",
+ "p-map": "^4.0.0",
+ "promise-inflight": "^1.0.1",
+ "rimraf": "^3.0.2",
+ "ssri": "^8.0.1",
+ "tar": "^6.0.2",
+ "unique-filename": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/cacache/node_modules/p-map": {
+ "version": "4.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "aggregate-error": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/cacache/node_modules/rimraf": {
+ "version": "3.0.2",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/cache-base": {
+ "version": "1.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "collection-visit": "^1.0.0",
+ "component-emitter": "^1.2.1",
+ "get-value": "^2.0.6",
+ "has-value": "^1.0.0",
+ "isobject": "^3.0.1",
+ "set-value": "^2.0.0",
+ "to-object-path": "^0.3.0",
+ "union-value": "^1.0.0",
+ "unset-value": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/call-bind": {
+ "version": "1.0.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.1",
+ "get-intrinsic": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/camelcase": {
+ "version": "5.3.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/camelcase-keys": {
+ "version": "6.2.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "camelcase": "^5.3.1",
+ "map-obj": "^4.0.0",
+ "quick-lru": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/caseless": {
+ "version": "0.12.0",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/chai": {
+ "version": "4.3.6",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "assertion-error": "^1.1.0",
+ "check-error": "^1.0.2",
+ "deep-eql": "^3.0.1",
+ "get-func-name": "^2.0.0",
+ "loupe": "^2.3.1",
+ "pathval": "^1.1.1",
+ "type-detect": "^4.0.5"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "2.4.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/chalk/node_modules/supports-color": {
+ "version": "5.5.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/check-error": {
+ "version": "1.0.2",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/chownr": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/chrome-trace-event": {
+ "version": "1.0.3",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0"
+ }
+ },
+ "node_modules/class-utils": {
+ "version": "0.3.6",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "arr-union": "^3.1.0",
+ "define-property": "^0.2.5",
+ "isobject": "^3.0.0",
+ "static-extend": "^0.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/class-utils/node_modules/define-property": {
+ "version": "0.2.5",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-descriptor": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/class-utils/node_modules/is-accessor-descriptor": {
+ "version": "0.1.6",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "kind-of": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/class-utils/node_modules/is-accessor-descriptor/node_modules/kind-of": {
+ "version": "3.2.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-buffer": "^1.1.5"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/class-utils/node_modules/is-data-descriptor": {
+ "version": "0.1.4",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "kind-of": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/class-utils/node_modules/is-data-descriptor/node_modules/kind-of": {
+ "version": "3.2.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-buffer": "^1.1.5"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/class-utils/node_modules/is-descriptor": {
+ "version": "0.1.6",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-accessor-descriptor": "^0.1.6",
+ "is-data-descriptor": "^0.1.4",
+ "kind-of": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/class-utils/node_modules/kind-of": {
+ "version": "5.1.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/classnames": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz",
+ "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw=="
+ },
+ "node_modules/clean-stack": {
+ "version": "2.2.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/clean-webpack-plugin": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/clean-webpack-plugin/-/clean-webpack-plugin-3.0.0.tgz",
+ "integrity": "sha512-MciirUH5r+cYLGCOL5JX/ZLzOZbVr1ot3Fw+KcvbhUb6PM+yycqd9ZhIlcigQ5gl+XhppNmw3bEFuaaMNyLj3A==",
+ "dev": true,
+ "dependencies": {
+ "@types/webpack": "^4.4.31",
+ "del": "^4.1.1"
+ },
+ "engines": {
+ "node": ">=8.9.0"
+ },
+ "peerDependencies": {
+ "webpack": "*"
+ }
+ },
+ "node_modules/cli-cursor": {
+ "version": "3.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "restore-cursor": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cli-truncate": {
+ "version": "2.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "slice-ansi": "^3.0.0",
+ "string-width": "^4.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/cli-truncate/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/cli-truncate/node_modules/color-convert": {
+ "version": "2.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/cli-truncate/node_modules/color-name": {
+ "version": "1.1.4",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cli-truncate/node_modules/slice-ansi": {
+ "version": "3.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "astral-regex": "^2.0.0",
+ "is-fullwidth-code-point": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui": {
+ "version": "5.0.0",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^3.1.0",
+ "strip-ansi": "^5.2.0",
+ "wrap-ansi": "^5.1.0"
+ }
+ },
+ "node_modules/cliui/node_modules/ansi-regex": {
+ "version": "4.1.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/cliui/node_modules/emoji-regex": {
+ "version": "7.0.3",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cliui/node_modules/is-fullwidth-code-point": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/cliui/node_modules/string-width": {
+ "version": "3.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^7.0.1",
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/cliui/node_modules/strip-ansi": {
+ "version": "5.2.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/cliui/node_modules/wrap-ansi": {
+ "version": "5.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^3.2.0",
+ "string-width": "^3.0.0",
+ "strip-ansi": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/clone": {
+ "version": "2.1.2",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/clone-deep": {
+ "version": "4.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-plain-object": "^2.0.4",
+ "kind-of": "^6.0.2",
+ "shallow-clone": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/clone-deep/node_modules/is-plain-object": {
+ "version": "2.0.4",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "isobject": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/clsx": {
+ "version": "1.2.1",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/collection-visit": {
+ "version": "1.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "map-visit": "^1.0.0",
+ "object-visit": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/collection-visit/node_modules/object-visit": {
+ "version": "1.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "isobject": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "1.9.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "1.1.3"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.3",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/color-support": {
+ "version": "1.1.3",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "color-support": "bin.js"
+ }
+ },
+ "node_modules/colord": {
+ "version": "2.9.3",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/colorette": {
+ "version": "2.0.19",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/commander": {
+ "version": "2.20.3",
+ "license": "MIT"
+ },
+ "node_modules/commondir": {
+ "version": "1.0.1",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/component-emitter": {
+ "version": "1.3.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "license": "MIT"
+ },
+ "node_modules/concurrently": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-5.3.0.tgz",
+ "integrity": "sha512-8MhqOB6PWlBfA2vJ8a0bSFKATOdWlHiQlk11IfmQBPaHVP8oP2gsh2MObE6UR3hqDHqvaIvLTyceNW6obVuFHQ==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "^2.4.2",
+ "date-fns": "^2.0.1",
+ "lodash": "^4.17.15",
+ "read-pkg": "^4.0.1",
+ "rxjs": "^6.5.2",
+ "spawn-command": "^0.0.2-1",
+ "supports-color": "^6.1.0",
+ "tree-kill": "^1.2.2",
+ "yargs": "^13.3.0"
+ },
+ "bin": {
+ "concurrently": "bin/concurrently.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/confusing-browser-globals": {
+ "version": "1.0.11",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/console-control-strings": {
+ "version": "1.1.0",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/convert-source-map": {
+ "version": "1.8.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "safe-buffer": "~5.1.1"
+ }
+ },
+ "node_modules/copy-descriptor": {
+ "version": "0.1.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/copy-webpack-plugin": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-9.1.0.tgz",
+ "integrity": "sha512-rxnR7PaGigJzhqETHGmAcxKnLZSR5u1Y3/bcIv/1FnqXedcL/E2ewK7ZCNrArJKCiSv8yVXhTqetJh8inDvfsA==",
+ "dev": true,
+ "dependencies": {
+ "fast-glob": "^3.2.7",
+ "glob-parent": "^6.0.1",
+ "globby": "^11.0.3",
+ "normalize-path": "^3.0.0",
+ "schema-utils": "^3.1.1",
+ "serialize-javascript": "^6.0.0"
+ },
+ "engines": {
+ "node": ">= 12.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "webpack": "^5.1.0"
+ }
+ },
+ "node_modules/copy-webpack-plugin/node_modules/schema-utils": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.2.tgz",
+ "integrity": "sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg==",
+ "dev": true,
+ "dependencies": {
+ "@types/json-schema": "^7.0.8",
+ "ajv": "^6.12.5",
+ "ajv-keywords": "^3.5.2"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/core-util-is": {
+ "version": "1.0.3",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cosmiconfig": {
+ "version": "7.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/parse-json": "^4.0.0",
+ "import-fresh": "^3.2.1",
+ "parse-json": "^5.0.0",
+ "path-type": "^4.0.0",
+ "yaml": "^1.10.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/cosmiconfig/node_modules/yaml": {
+ "version": "1.10.2",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/crc-32": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
+ "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
+ "bin": {
+ "crc32": "bin/crc32.njs"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/css-functions-list": {
+ "version": "3.1.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=12.22"
+ }
+ },
+ "node_modules/css-hot-loader": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/css-hot-loader/-/css-hot-loader-1.4.4.tgz",
+ "integrity": "sha512-J/qXHz+r7FOT92qMIJfxUk0LC9fecQNZVr0MswQ4FOpKLyOCBjofVMfc6R268bh/5ktkTShrweMr0wWqerC92g==",
+ "dev": true,
+ "dependencies": {
+ "loader-utils": "^1.1.0",
+ "lodash": "^4.17.5",
+ "normalize-url": "^1.9.1"
+ }
+ },
+ "node_modules/css-loader": {
+ "version": "5.2.7",
+ "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-5.2.7.tgz",
+ "integrity": "sha512-Q7mOvpBNBG7YrVGMxRxcBJZFL75o+cH2abNASdibkj/fffYD8qWbInZrD0S9ccI6vZclF3DsHE7njGlLtaHbhg==",
+ "dev": true,
+ "dependencies": {
+ "icss-utils": "^5.1.0",
+ "loader-utils": "^2.0.0",
+ "postcss": "^8.2.15",
+ "postcss-modules-extract-imports": "^3.0.0",
+ "postcss-modules-local-by-default": "^4.0.0",
+ "postcss-modules-scope": "^3.0.0",
+ "postcss-modules-values": "^4.0.0",
+ "postcss-value-parser": "^4.1.0",
+ "schema-utils": "^3.0.0",
+ "semver": "^7.3.5"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "webpack": "^4.27.0 || ^5.0.0"
+ }
+ },
+ "node_modules/css-loader/node_modules/loader-utils": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
+ "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
+ "dev": true,
+ "dependencies": {
+ "big.js": "^5.2.2",
+ "emojis-list": "^3.0.0",
+ "json5": "^2.1.2"
+ },
+ "engines": {
+ "node": ">=8.9.0"
+ }
+ },
+ "node_modules/css-loader/node_modules/schema-utils": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.2.tgz",
+ "integrity": "sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg==",
+ "dev": true,
+ "dependencies": {
+ "@types/json-schema": "^7.0.8",
+ "ajv": "^6.12.5",
+ "ajv-keywords": "^3.5.2"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/css-loader/node_modules/semver": {
+ "version": "7.5.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz",
+ "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==",
+ "dev": true,
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/cssesc": {
+ "version": "3.0.0",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "cssesc": "bin/cssesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.1.0",
+ "license": "MIT"
+ },
+ "node_modules/dashdash": {
+ "version": "1.14.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "assert-plus": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/date-fns": {
+ "version": "2.29.3",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.11"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/date-fns"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.3.4",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decamelize": {
+ "version": "1.2.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/decode-uri-component": {
+ "version": "0.2.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/dedent": {
+ "version": "0.7.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/deep-eql": {
+ "version": "3.0.1",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "type-detect": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=0.12"
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/deepmerge": {
+ "version": "2.2.1",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/define-properties": {
+ "version": "1.1.4",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-property-descriptors": "^1.0.0",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/define-property": {
+ "version": "1.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-descriptor": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/del": {
+ "version": "4.1.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/glob": "^7.1.1",
+ "globby": "^6.1.0",
+ "is-path-cwd": "^2.0.0",
+ "is-path-in-cwd": "^2.0.0",
+ "p-map": "^2.0.0",
+ "pify": "^4.0.1",
+ "rimraf": "^2.6.3"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/del/node_modules/array-union": {
+ "version": "1.0.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "array-uniq": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/del/node_modules/globby": {
+ "version": "6.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "array-union": "^1.0.1",
+ "glob": "^7.0.3",
+ "object-assign": "^4.0.1",
+ "pify": "^2.0.0",
+ "pinkie-promise": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/del/node_modules/globby/node_modules/pify": {
+ "version": "2.3.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/delegates": {
+ "version": "1.0.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/diff": {
+ "version": "5.1.0",
+ "license": "BSD-3-Clause",
+ "peer": true,
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
+ "node_modules/dir-glob": {
+ "version": "3.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-type": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/doctrine": {
+ "version": "3.0.0",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/dom-helpers": {
+ "version": "5.2.1",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.8.7",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/domelementtype": {
+ "version": "1.3.1",
+ "dev": true,
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/domready": {
+ "version": "1.0.8",
+ "dev": true
+ },
+ "node_modules/ecc-jsbn": {
+ "version": "0.1.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "jsbn": "~0.1.0",
+ "safer-buffer": "^2.1.0"
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.4.243",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/email-addresses": {
+ "version": "3.1.0",
+ "license": "MIT"
+ },
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/emojis-list": {
+ "version": "3.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/encoding": {
+ "version": "0.1.13",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "iconv-lite": "^0.6.2"
+ }
+ },
+ "node_modules/encoding/node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/end-of-stream": {
+ "version": "1.4.4",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "once": "^1.4.0"
+ }
+ },
+ "node_modules/enhanced-resolve": {
+ "version": "5.14.0",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.14.0.tgz",
+ "integrity": "sha512-+DCows0XNwLDcUhbFJPdlQEVnT2zXlCv7hPxemTz86/O+B/hCQ+mb7ydkPKiflpVraqLPCAfu7lDy+hBXueojw==",
+ "dev": true,
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/enquirer": {
+ "version": "2.3.6",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-colors": "^4.1.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/entities": {
+ "version": "1.1.2",
+ "dev": true,
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/env-paths": {
+ "version": "2.2.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/envinfo": {
+ "version": "7.8.1",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "envinfo": "dist/cli.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/err-code": {
+ "version": "2.0.3",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/error-ex": {
+ "version": "1.3.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "node_modules/es-abstract": {
+ "version": "1.21.2",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz",
+ "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==",
+ "dev": true,
+ "dependencies": {
+ "array-buffer-byte-length": "^1.0.0",
+ "available-typed-arrays": "^1.0.5",
+ "call-bind": "^1.0.2",
+ "es-set-tostringtag": "^2.0.1",
+ "es-to-primitive": "^1.2.1",
+ "function.prototype.name": "^1.1.5",
+ "get-intrinsic": "^1.2.0",
+ "get-symbol-description": "^1.0.0",
+ "globalthis": "^1.0.3",
+ "gopd": "^1.0.1",
+ "has": "^1.0.3",
+ "has-property-descriptors": "^1.0.0",
+ "has-proto": "^1.0.1",
+ "has-symbols": "^1.0.3",
+ "internal-slot": "^1.0.5",
+ "is-array-buffer": "^3.0.2",
+ "is-callable": "^1.2.7",
+ "is-negative-zero": "^2.0.2",
+ "is-regex": "^1.1.4",
+ "is-shared-array-buffer": "^1.0.2",
+ "is-string": "^1.0.7",
+ "is-typed-array": "^1.1.10",
+ "is-weakref": "^1.0.2",
+ "object-inspect": "^1.12.3",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.4",
+ "regexp.prototype.flags": "^1.4.3",
+ "safe-regex-test": "^1.0.0",
+ "string.prototype.trim": "^1.2.7",
+ "string.prototype.trimend": "^1.0.6",
+ "string.prototype.trimstart": "^1.0.6",
+ "typed-array-length": "^1.0.4",
+ "unbox-primitive": "^1.0.2",
+ "which-typed-array": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/es-module-lexer": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.2.1.tgz",
+ "integrity": "sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg==",
+ "dev": true
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz",
+ "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==",
+ "dev": true,
+ "dependencies": {
+ "get-intrinsic": "^1.1.3",
+ "has": "^1.0.3",
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-shim-unscopables": {
+ "version": "1.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has": "^1.0.3"
+ }
+ },
+ "node_modules/es-to-primitive": {
+ "version": "1.2.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-callable": "^1.1.4",
+ "is-date-object": "^1.0.1",
+ "is-symbol": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/es-to-primitive/node_modules/is-date-object": {
+ "version": "1.0.5",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.1.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "7.32.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/code-frame": "7.12.11",
+ "@eslint/eslintrc": "^0.4.3",
+ "@humanwhocodes/config-array": "^0.5.0",
+ "ajv": "^6.10.0",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.2",
+ "debug": "^4.0.1",
+ "doctrine": "^3.0.0",
+ "enquirer": "^2.3.5",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^5.1.1",
+ "eslint-utils": "^2.1.0",
+ "eslint-visitor-keys": "^2.0.0",
+ "espree": "^7.3.1",
+ "esquery": "^1.4.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^6.0.1",
+ "functional-red-black-tree": "^1.0.1",
+ "glob-parent": "^5.1.2",
+ "globals": "^13.6.0",
+ "ignore": "^4.0.6",
+ "import-fresh": "^3.0.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "js-yaml": "^3.13.1",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.4.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.0.4",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.1",
+ "progress": "^2.0.0",
+ "regexpp": "^3.1.0",
+ "semver": "^7.2.1",
+ "strip-ansi": "^6.0.0",
+ "strip-json-comments": "^3.1.0",
+ "table": "^6.0.9",
+ "text-table": "^0.2.0",
+ "v8-compile-cache": "^2.0.3"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-config-airbnb-base": {
+ "version": "14.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.1.tgz",
+ "integrity": "sha512-GOrQyDtVEc1Xy20U7vsB2yAoB4nBlfH5HZJeatRXHleO+OS5Ot+MWij4Dpltw4/DyIkqUfqz1epfhVR5XWWQPA==",
+ "dev": true,
+ "dependencies": {
+ "confusing-browser-globals": "^1.0.10",
+ "object.assign": "^4.1.2",
+ "object.entries": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 6"
+ },
+ "peerDependencies": {
+ "eslint": "^5.16.0 || ^6.8.0 || ^7.2.0",
+ "eslint-plugin-import": "^2.22.1"
+ }
+ },
+ "node_modules/eslint-config-binary": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/eslint-config-binary/-/eslint-config-binary-1.0.2.tgz",
+ "integrity": "sha512-4PCr0wR6/aE+v9TKrcl4p/Qhs8u7mayoZuQe+599D12MIOmfRFPyhlxczORG5dSBr6+loNGmMtPTJe3tJv3ktg==",
+ "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
+ "dev": true
+ },
+ "node_modules/eslint-config-prettier": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-7.2.0.tgz",
+ "integrity": "sha512-rV4Qu0C3nfJKPOAhFujFxB7RMP+URFyQqqOZW9DMRD7ZDTFyjaIlETU3xzHELt++4ugC0+Jm084HQYkkJe+Ivg==",
+ "dev": true,
+ "bin": {
+ "eslint-config-prettier": "bin/cli.js"
+ },
+ "peerDependencies": {
+ "eslint": ">=7.0.0"
+ }
+ },
+ "node_modules/eslint-import-resolver-node": {
+ "version": "0.3.7",
+ "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz",
+ "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==",
+ "dev": true,
+ "dependencies": {
+ "debug": "^3.2.7",
+ "is-core-module": "^2.11.0",
+ "resolve": "^1.22.1"
+ }
+ },
+ "node_modules/eslint-import-resolver-node/node_modules/debug": {
+ "version": "3.2.7",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/eslint-module-utils": {
+ "version": "2.7.4",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^3.2.7"
+ },
+ "engines": {
+ "node": ">=4"
+ },
+ "peerDependenciesMeta": {
+ "eslint": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-module-utils/node_modules/debug": {
+ "version": "3.2.7",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/eslint-plugin-import": {
+ "version": "2.27.5",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz",
+ "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==",
+ "dev": true,
+ "dependencies": {
+ "array-includes": "^3.1.6",
+ "array.prototype.flat": "^1.3.1",
+ "array.prototype.flatmap": "^1.3.1",
+ "debug": "^3.2.7",
+ "doctrine": "^2.1.0",
+ "eslint-import-resolver-node": "^0.3.7",
+ "eslint-module-utils": "^2.7.4",
+ "has": "^1.0.3",
+ "is-core-module": "^2.11.0",
+ "is-glob": "^4.0.3",
+ "minimatch": "^3.1.2",
+ "object.values": "^1.1.6",
+ "resolve": "^1.22.1",
+ "semver": "^6.3.0",
+ "tsconfig-paths": "^3.14.1"
+ },
+ "engines": {
+ "node": ">=4"
+ },
+ "peerDependencies": {
+ "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8"
+ }
+ },
+ "node_modules/eslint-plugin-import/node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/eslint-plugin-import/node_modules/doctrine": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
+ "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
+ "dev": true,
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/eslint-plugin-prettier": {
+ "version": "3.4.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz",
+ "integrity": "sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==",
+ "dev": true,
+ "dependencies": {
+ "prettier-linter-helpers": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ },
+ "peerDependencies": {
+ "eslint": ">=5.0.0",
+ "prettier": ">=1.13.0"
+ },
+ "peerDependenciesMeta": {
+ "eslint-config-prettier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-plugin-react": {
+ "version": "7.32.2",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.32.2.tgz",
+ "integrity": "sha512-t2fBMa+XzonrrNkyVirzKlvn5RXzzPwRHtMvLAtVZrt8oxgnTQaYbU6SXTOO1mwQgp1y5+toMSKInnzGr0Knqg==",
+ "dev": true,
+ "dependencies": {
+ "array-includes": "^3.1.6",
+ "array.prototype.flatmap": "^1.3.1",
+ "array.prototype.tosorted": "^1.1.1",
+ "doctrine": "^2.1.0",
+ "estraverse": "^5.3.0",
+ "jsx-ast-utils": "^2.4.1 || ^3.0.0",
+ "minimatch": "^3.1.2",
+ "object.entries": "^1.1.6",
+ "object.fromentries": "^2.0.6",
+ "object.hasown": "^1.1.2",
+ "object.values": "^1.1.6",
+ "prop-types": "^15.8.1",
+ "resolve": "^2.0.0-next.4",
+ "semver": "^6.3.0",
+ "string.prototype.matchall": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=4"
+ },
+ "peerDependencies": {
+ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8"
+ }
+ },
+ "node_modules/eslint-plugin-react-hooks": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz",
+ "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0"
+ }
+ },
+ "node_modules/eslint-plugin-react/node_modules/doctrine": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
+ "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
+ "dev": true,
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/eslint-plugin-react/node_modules/resolve": {
+ "version": "2.0.0-next.4",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz",
+ "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==",
+ "dev": true,
+ "dependencies": {
+ "is-core-module": "^2.9.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/eslint-plugin-simple-import-sort": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-10.0.0.tgz",
+ "integrity": "sha512-AeTvO9UCMSNzIHRkg8S6c3RPy5YEwKWSQPx3DYghLedo2ZQxowPFLGDN1AZ2evfg6r6mjBSZSLxLFsWSu3acsw==",
+ "dev": true,
+ "peerDependencies": {
+ "eslint": ">=5.0.0"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "5.1.1",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^4.1.1"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/eslint-scope/node_modules/estraverse": {
+ "version": "4.3.0",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/eslint-utils": {
+ "version": "2.1.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "eslint-visitor-keys": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mysticatea"
+ }
+ },
+ "node_modules/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "1.3.0",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "2.1.0",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/eslint/node_modules/@babel/code-frame": {
+ "version": "7.12.11",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/highlight": "^7.10.4"
+ }
+ },
+ "node_modules/eslint/node_modules/@babel/helper-validator-identifier": {
+ "version": "7.18.6",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/eslint/node_modules/@babel/highlight": {
+ "version": "7.18.6",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.18.6",
+ "chalk": "^2.0.0",
+ "js-tokens": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/eslint/node_modules/@babel/highlight/node_modules/ansi-styles": {
+ "version": "3.2.1",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "color-convert": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/eslint/node_modules/@babel/highlight/node_modules/chalk": {
+ "version": "2.4.2",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/eslint/node_modules/@babel/highlight/node_modules/color-convert": {
+ "version": "1.9.3",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "color-name": "1.1.3"
+ }
+ },
+ "node_modules/eslint/node_modules/@babel/highlight/node_modules/color-name": {
+ "version": "1.1.3",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/eslint/node_modules/@babel/highlight/node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/eslint/node_modules/@babel/highlight/node_modules/has-flag": {
+ "version": "3.0.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/eslint/node_modules/@babel/highlight/node_modules/supports-color": {
+ "version": "5.5.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/eslint/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/eslint/node_modules/chalk": {
+ "version": "4.1.2",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/eslint/node_modules/color-convert": {
+ "version": "2.0.1",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/eslint/node_modules/color-name": {
+ "version": "1.1.4",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/eslint/node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "dev": true,
+ "license": "ISC",
+ "peer": true,
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/eslint/node_modules/globals": {
+ "version": "13.17.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint/node_modules/has-flag": {
+ "version": "4.0.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/eslint/node_modules/semver": {
+ "version": "7.3.7",
+ "dev": true,
+ "license": "ISC",
+ "peer": true,
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/eslint/node_modules/supports-color": {
+ "version": "7.2.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/eslint/node_modules/type-fest": {
+ "version": "0.20.2",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "peer": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/espree": {
+ "version": "7.3.1",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "peer": true,
+ "dependencies": {
+ "acorn": "^7.4.0",
+ "acorn-jsx": "^5.3.1",
+ "eslint-visitor-keys": "^1.3.0"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/espree/node_modules/eslint-visitor-keys": {
+ "version": "1.3.0",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/esprima": {
+ "version": "4.0.1",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "peer": true,
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.4.0",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "peer": true,
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/event-emitter-es6": {
+ "version": "1.1.5",
+ "license": "MIT"
+ },
+ "node_modules/events": {
+ "version": "3.3.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.x"
+ }
+ },
+ "node_modules/execa": {
+ "version": "4.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cross-spawn": "^7.0.0",
+ "get-stream": "^5.0.0",
+ "human-signals": "^1.1.1",
+ "is-stream": "^2.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^4.0.0",
+ "onetime": "^5.1.0",
+ "signal-exit": "^3.0.2",
+ "strip-final-newline": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
+ "node_modules/exenv": {
+ "version": "1.2.2",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/expand-brackets": {
+ "version": "2.1.4",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^2.3.3",
+ "define-property": "^0.2.5",
+ "extend-shallow": "^2.0.1",
+ "posix-character-classes": "^0.1.0",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/expand-brackets/node_modules/debug": {
+ "version": "2.6.9",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/expand-brackets/node_modules/define-property": {
+ "version": "0.2.5",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-descriptor": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/expand-brackets/node_modules/is-accessor-descriptor": {
+ "version": "0.1.6",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "kind-of": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/expand-brackets/node_modules/is-accessor-descriptor/node_modules/kind-of": {
+ "version": "3.2.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-buffer": "^1.1.5"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/expand-brackets/node_modules/is-data-descriptor": {
+ "version": "0.1.4",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "kind-of": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/expand-brackets/node_modules/is-data-descriptor/node_modules/kind-of": {
+ "version": "3.2.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-buffer": "^1.1.5"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/expand-brackets/node_modules/is-descriptor": {
+ "version": "0.1.6",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-accessor-descriptor": "^0.1.6",
+ "is-data-descriptor": "^0.1.4",
+ "kind-of": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/expand-brackets/node_modules/kind-of": {
+ "version": "5.1.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/expand-brackets/node_modules/ms": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/extend": {
+ "version": "3.0.2",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/extend-shallow": {
+ "version": "2.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extendable": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/extglob": {
+ "version": "2.0.4",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "array-unique": "^0.3.2",
+ "define-property": "^1.0.0",
+ "expand-brackets": "^2.1.4",
+ "extend-shallow": "^2.0.1",
+ "fragment-cache": "^0.2.1",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/extsprintf": {
+ "version": "1.3.0",
+ "dev": true,
+ "engines": [
+ "node >=0.6.0"
+ ],
+ "license": "MIT"
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-diff": {
+ "version": "1.2.0",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/fast-glob": {
+ "version": "3.2.11",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/fast-glob/node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/fast-glob/node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/fast-glob/node_modules/fastq": {
+ "version": "1.13.0",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fast-glob/node_modules/reusify": {
+ "version": "1.0.4",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/fastest-levenshtein": {
+ "version": "1.0.16",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4.9.1"
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "6.0.1",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "flat-cache": "^3.0.4"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/file-entry-cache/node_modules/flat-cache": {
+ "version": "3.0.4",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "flatted": "^3.1.0",
+ "rimraf": "^3.0.2"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/file-entry-cache/node_modules/flatted": {
+ "version": "3.2.7",
+ "dev": true,
+ "license": "ISC",
+ "peer": true
+ },
+ "node_modules/file-entry-cache/node_modules/rimraf": {
+ "version": "3.0.2",
+ "dev": true,
+ "license": "ISC",
+ "peer": true,
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/filename-reserved-regex": {
+ "version": "1.0.0",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/filenamify": {
+ "version": "1.2.1",
+ "license": "MIT",
+ "dependencies": {
+ "filename-reserved-regex": "^1.0.0",
+ "strip-outer": "^1.0.0",
+ "trim-repeated": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/filenamify-url": {
+ "version": "1.0.0",
+ "license": "MIT",
+ "dependencies": {
+ "filenamify": "^1.0.0",
+ "humanize-url": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-cache-dir": {
+ "version": "3.3.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "commondir": "^1.0.1",
+ "make-dir": "^3.0.2",
+ "pkg-dir": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/avajs/find-cache-dir?sponsor=1"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "4.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/for-each": {
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
+ "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==",
+ "dev": true,
+ "dependencies": {
+ "is-callable": "^1.1.3"
+ }
+ },
+ "node_modules/for-in": {
+ "version": "1.0.2",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/forever-agent": {
+ "version": "0.6.1",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/form-data": {
+ "version": "2.3.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.6",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 0.12"
+ }
+ },
+ "node_modules/formik": {
+ "version": "2.2.9",
+ "resolved": "https://registry.npmjs.org/formik/-/formik-2.2.9.tgz",
+ "integrity": "sha512-LQLcISMmf1r5at4/gyJigGn0gOwFbeEAlji+N9InZF6LIMXnFNkO42sCI8Jt84YZggpD4cPWObAZaxpEFtSzNA==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://opencollective.com/formik"
+ }
+ ],
+ "dependencies": {
+ "deepmerge": "^2.1.1",
+ "hoist-non-react-statics": "^3.3.0",
+ "lodash": "^4.17.21",
+ "lodash-es": "^4.17.21",
+ "react-fast-compare": "^2.0.1",
+ "tiny-warning": "^1.0.2",
+ "tslib": "^1.10.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/fragment-cache": {
+ "version": "0.2.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "map-cache": "^0.2.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/fs-extra": {
+ "version": "8.1.0",
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=6 <7 || >=8"
+ }
+ },
+ "node_modules/fs-minipass": {
+ "version": "2.1.0",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "license": "ISC"
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.1",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/function.prototype.name": {
+ "version": "1.1.5",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.19.0",
+ "functions-have-names": "^1.2.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/functional-red-black-tree": {
+ "version": "1.0.1",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/functions-have-names": {
+ "version": "1.2.3",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gauge": {
+ "version": "3.0.2",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "aproba": "^1.0.3 || ^2.0.0",
+ "color-support": "^1.1.2",
+ "console-control-strings": "^1.0.0",
+ "has-unicode": "^2.0.1",
+ "object-assign": "^4.1.1",
+ "signal-exit": "^3.0.0",
+ "string-width": "^4.2.3",
+ "strip-ansi": "^6.0.1",
+ "wide-align": "^1.1.2"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/gaze": {
+ "version": "1.1.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "globule": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/get-func-name": {
+ "version": "2.0.0",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
+ "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==",
+ "dev": true,
+ "dependencies": {
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3",
+ "has-proto": "^1.0.1",
+ "has-symbols": "^1.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-stdin": {
+ "version": "4.0.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/get-stream": {
+ "version": "5.2.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pump": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/get-symbol-description": {
+ "version": "1.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-value": {
+ "version": "2.0.6",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/getpass": {
+ "version": "0.1.7",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "assert-plus": "^1.0.0"
+ }
+ },
+ "node_modules/gh-pages": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-2.2.0.tgz",
+ "integrity": "sha512-c+yPkNOPMFGNisYg9r4qvsMIjVYikJv7ImFOhPIVPt0+AcRUamZ7zkGRLHz7FKB0xrlZ+ddSOJsZv9XAFVXLmA==",
+ "dependencies": {
+ "async": "^2.6.1",
+ "commander": "^2.18.0",
+ "email-addresses": "^3.0.1",
+ "filenamify-url": "^1.0.0",
+ "fs-extra": "^8.1.0",
+ "globby": "^6.1.0"
+ },
+ "bin": {
+ "gh-pages": "bin/gh-pages.js",
+ "gh-pages-clean": "bin/gh-pages-clean.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/gh-pages/node_modules/array-union": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
+ "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==",
+ "dependencies": {
+ "array-uniq": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/gh-pages/node_modules/globby": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
+ "integrity": "sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==",
+ "dependencies": {
+ "array-union": "^1.0.1",
+ "glob": "^7.0.3",
+ "object-assign": "^4.0.1",
+ "pify": "^2.0.0",
+ "pinkie-promise": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/gh-pages/node_modules/pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/glob-to-regexp": {
+ "version": "0.4.1",
+ "dev": true,
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/global-modules": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "global-prefix": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/global-modules/node_modules/global-prefix": {
+ "version": "3.0.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "ini": "^1.3.5",
+ "kind-of": "^6.0.2",
+ "which": "^1.3.1"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/global-modules/node_modules/which": {
+ "version": "1.3.1",
+ "dev": true,
+ "license": "ISC",
+ "peer": true,
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "which": "bin/which"
+ }
+ },
+ "node_modules/globals": {
+ "version": "11.12.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/globalthis": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz",
+ "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==",
+ "dev": true,
+ "dependencies": {
+ "define-properties": "^1.1.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/globby": {
+ "version": "11.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "array-union": "^2.1.0",
+ "dir-glob": "^3.0.1",
+ "fast-glob": "^3.2.9",
+ "ignore": "^5.2.0",
+ "merge2": "^1.4.1",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/globby/node_modules/ignore": {
+ "version": "5.2.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/globjoin": {
+ "version": "0.1.4",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/globule": {
+ "version": "1.3.4",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "glob": "~7.1.1",
+ "lodash": "^4.17.21",
+ "minimatch": "~3.0.2"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/globule/node_modules/glob": {
+ "version": "7.1.7",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/globule/node_modules/minimatch": {
+ "version": "3.0.8",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
+ "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
+ "dev": true,
+ "dependencies": {
+ "get-intrinsic": "^1.1.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.10",
+ "license": "ISC"
+ },
+ "node_modules/har-schema": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/har-validator": {
+ "version": "5.1.5",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^6.12.3",
+ "har-schema": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/hard-rejection": {
+ "version": "2.1.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/has": {
+ "version": "1.0.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/has-ansi": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/has-ansi/node_modules/ansi-regex": {
+ "version": "2.1.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/has-bigints": {
+ "version": "1.0.2",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "3.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/has-property-descriptors": {
+ "version": "1.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "get-intrinsic": "^1.1.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
+ "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.0.3",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-unicode": {
+ "version": "2.0.1",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/has-value": {
+ "version": "1.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "get-value": "^2.0.6",
+ "has-values": "^1.0.0",
+ "isobject": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/has-values": {
+ "version": "1.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^3.0.0",
+ "kind-of": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/has-values/node_modules/is-number": {
+ "version": "3.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "kind-of": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/has-values/node_modules/is-number/node_modules/kind-of": {
+ "version": "3.2.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-buffer": "^1.1.5"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/has-values/node_modules/kind-of": {
+ "version": "4.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-buffer": "^1.1.5"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/he": {
+ "version": "1.2.0",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "he": "bin/he"
+ }
+ },
+ "node_modules/hoist-non-react-statics": {
+ "version": "3.3.2",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "react-is": "^16.7.0"
+ }
+ },
+ "node_modules/hosted-git-info": {
+ "version": "2.8.9",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/html-tags": {
+ "version": "3.2.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/http-cache-semantics": {
+ "version": "4.1.0",
+ "dev": true,
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/http-proxy-agent": {
+ "version": "4.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@tootallnate/once": "1",
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/http-signature": {
+ "version": "1.2.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "assert-plus": "^1.0.0",
+ "jsprim": "^1.2.2",
+ "sshpk": "^1.7.0"
+ },
+ "engines": {
+ "node": ">=0.8",
+ "npm": ">=1.3.7"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "5.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/human-signals": {
+ "version": "1.1.1",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8.12.0"
+ }
+ },
+ "node_modules/humanize-ms": {
+ "version": "1.2.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.0.0"
+ }
+ },
+ "node_modules/humanize-url": {
+ "version": "1.0.1",
+ "license": "MIT",
+ "dependencies": {
+ "normalize-url": "^1.0.0",
+ "strip-url-auth": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/icss-utils": {
+ "version": "5.1.0",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "^10 || ^12 || >= 14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "4.0.6",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/image-size": {
+ "version": "0.5.5",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "image-size": "bin/image-size.js"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/immutable": {
+ "version": "3.8.2",
+ "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz",
+ "integrity": "sha512-15gZoQ38eYjEjxkorfbcgBKBL6R7T459OuK+CpcWt7O3KF4uPCx2tD0uFETlUDIyo+1789crbMhTvQBSR5yBMg==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/import-lazy": {
+ "version": "4.0.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/import-local": {
+ "version": "3.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pkg-dir": "^4.2.0",
+ "resolve-cwd": "^3.0.0"
+ },
+ "bin": {
+ "import-local-fixture": "fixtures/cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/indent-string": {
+ "version": "4.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/infer-owner": {
+ "version": "1.0.4",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "license": "ISC",
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "license": "ISC"
+ },
+ "node_modules/ini": {
+ "version": "1.3.8",
+ "dev": true,
+ "license": "ISC",
+ "peer": true
+ },
+ "node_modules/internal-slot": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz",
+ "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==",
+ "dev": true,
+ "dependencies": {
+ "get-intrinsic": "^1.2.0",
+ "has": "^1.0.3",
+ "side-channel": "^1.0.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/interpret": {
+ "version": "2.2.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/ip": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/is-accessor-descriptor": {
+ "version": "1.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "kind-of": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-array-buffer": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz",
+ "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.2.0",
+ "is-typed-array": "^1.1.10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/is-bigint": {
+ "version": "1.0.4",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-bigints": "^1.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-boolean-object": {
+ "version": "1.1.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-buffer": {
+ "version": "1.1.6",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/is-callable": {
+ "version": "1.2.7",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.12.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz",
+ "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==",
+ "dev": true,
+ "dependencies": {
+ "has": "^1.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-data-descriptor": {
+ "version": "1.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "kind-of": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-descriptor": {
+ "version": "1.0.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-extendable": {
+ "version": "0.1.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-lambda": {
+ "version": "1.0.1",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/is-lite": {
+ "version": "0.9.2",
+ "license": "MIT"
+ },
+ "node_modules/is-negative-zero": {
+ "version": "2.0.2",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-number-object": {
+ "version": "1.0.7",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-obj": {
+ "version": "1.0.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-path-cwd": {
+ "version": "2.2.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/is-path-in-cwd": {
+ "version": "2.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-path-inside": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/is-path-inside": {
+ "version": "2.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-is-inside": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/is-path-inside/node_modules/path-is-inside": {
+ "version": "1.0.2",
+ "dev": true,
+ "license": "(WTFPL OR MIT)"
+ },
+ "node_modules/is-plain-obj": {
+ "version": "1.1.0",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-plain-object": {
+ "version": "5.0.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-regex": {
+ "version": "1.1.4",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-regexp": {
+ "version": "1.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-shared-array-buffer": {
+ "version": "1.0.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-stream": {
+ "version": "2.0.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-string": {
+ "version": "1.0.7",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-symbol": {
+ "version": "1.0.4",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-typed-array": {
+ "version": "1.1.10",
+ "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz",
+ "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==",
+ "dev": true,
+ "dependencies": {
+ "available-typed-arrays": "^1.0.5",
+ "call-bind": "^1.0.2",
+ "for-each": "^0.3.3",
+ "gopd": "^1.0.1",
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-typedarray": {
+ "version": "1.0.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/is-unicode-supported": {
+ "version": "0.1.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-weakref": {
+ "version": "1.0.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-windows": {
+ "version": "1.0.2",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/isarray": {
+ "version": "1.0.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/isobject": {
+ "version": "3.0.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/isstream": {
+ "version": "0.1.2",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/jest-worker": {
+ "version": "28.1.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "merge-stream": "^2.0.0",
+ "supports-color": "^8.0.0"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0"
+ }
+ },
+ "node_modules/jest-worker/node_modules/has-flag": {
+ "version": "4.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/jest-worker/node_modules/supports-color": {
+ "version": "8.1.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
+ "node_modules/js-base64": {
+ "version": "2.6.4",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "3.14.1",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/js-yaml/node_modules/argparse": {
+ "version": "1.0.10",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "node_modules/js-yaml/node_modules/sprintf-js": {
+ "version": "1.0.3",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "peer": true
+ },
+ "node_modules/jsbn": {
+ "version": "0.1.1",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-parse-better-errors": {
+ "version": "1.0.2",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-schema": {
+ "version": "0.4.0",
+ "dev": true,
+ "license": "(AFL-2.1 OR BSD-3-Clause)"
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/json-stringify-safe": {
+ "version": "5.0.1",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/json5": {
+ "version": "2.2.1",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/jsonfile": {
+ "version": "4.0.0",
+ "license": "MIT",
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/jsprim": {
+ "version": "1.4.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "assert-plus": "1.0.0",
+ "extsprintf": "1.3.0",
+ "json-schema": "0.4.0",
+ "verror": "1.10.0"
+ },
+ "engines": {
+ "node": ">=0.6.0"
+ }
+ },
+ "node_modules/jsx-ast-utils": {
+ "version": "3.3.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "array-includes": "^3.1.5",
+ "object.assign": "^4.1.3"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/just-extend": {
+ "version": "4.2.1",
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/kind-of": {
+ "version": "6.0.3",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/klona": {
+ "version": "2.0.5",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/known-css-properties": {
+ "version": "0.25.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/levn/node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/levn/node_modules/type-check": {
+ "version": "0.4.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/lint-staged": {
+ "version": "10.5.4",
+ "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.5.4.tgz",
+ "integrity": "sha512-EechC3DdFic/TdOPgj/RB3FicqE6932LTHCUm0Y2fsD9KGlLB+RwJl2q1IYBIvEsKzDOgn0D4gll+YxG5RsrKg==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "^4.1.0",
+ "cli-truncate": "^2.1.0",
+ "commander": "^6.2.0",
+ "cosmiconfig": "^7.0.0",
+ "debug": "^4.2.0",
+ "dedent": "^0.7.0",
+ "enquirer": "^2.3.6",
+ "execa": "^4.1.0",
+ "listr2": "^3.2.2",
+ "log-symbols": "^4.0.0",
+ "micromatch": "^4.0.2",
+ "normalize-path": "^3.0.0",
+ "please-upgrade-node": "^3.2.0",
+ "string-argv": "0.3.1",
+ "stringify-object": "^3.3.0"
+ },
+ "bin": {
+ "lint-staged": "bin/lint-staged.js"
+ },
+ "funding": {
+ "url": "https://opencollective.com/lint-staged"
+ }
+ },
+ "node_modules/lint-staged/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/lint-staged/node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/lint-staged/node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/lint-staged/node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/lint-staged/node_modules/commander": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz",
+ "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/lint-staged/node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/lint-staged/node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/listr2": {
+ "version": "3.14.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cli-truncate": "^2.1.0",
+ "colorette": "^2.0.16",
+ "log-update": "^4.0.0",
+ "p-map": "^4.0.0",
+ "rfdc": "^1.3.0",
+ "rxjs": "^7.5.1",
+ "through": "^2.3.8",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "enquirer": ">= 2.3.0 < 3"
+ },
+ "peerDependenciesMeta": {
+ "enquirer": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/listr2/node_modules/p-map": {
+ "version": "4.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "aggregate-error": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/listr2/node_modules/rxjs": {
+ "version": "7.5.7",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/listr2/node_modules/tslib": {
+ "version": "2.4.0",
+ "dev": true,
+ "license": "0BSD"
+ },
+ "node_modules/loader-runner": {
+ "version": "4.3.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.11.5"
+ }
+ },
+ "node_modules/loader-utils": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz",
+ "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==",
+ "dev": true,
+ "dependencies": {
+ "big.js": "^5.2.2",
+ "emojis-list": "^3.0.0",
+ "json5": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/loader-utils/node_modules/json5": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
+ "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
+ "dev": true,
+ "dependencies": {
+ "minimist": "^1.2.0"
+ },
+ "bin": {
+ "json5": "lib/cli.js"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "5.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "license": "MIT"
+ },
+ "node_modules/lodash-es": {
+ "version": "4.17.21",
+ "license": "MIT"
+ },
+ "node_modules/lodash.debounce": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
+ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="
+ },
+ "node_modules/lodash.get": {
+ "version": "4.4.2",
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/lodash.truncate": {
+ "version": "4.4.2",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/log-symbols": {
+ "version": "4.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^4.1.0",
+ "is-unicode-supported": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/log-symbols/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/log-symbols/node_modules/chalk": {
+ "version": "4.1.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/log-symbols/node_modules/color-convert": {
+ "version": "2.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/log-symbols/node_modules/color-name": {
+ "version": "1.1.4",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/log-symbols/node_modules/has-flag": {
+ "version": "4.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/log-symbols/node_modules/supports-color": {
+ "version": "7.2.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/log-update": {
+ "version": "4.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-escapes": "^4.3.0",
+ "cli-cursor": "^3.1.0",
+ "slice-ansi": "^4.0.0",
+ "wrap-ansi": "^6.2.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/log-update/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/log-update/node_modules/color-convert": {
+ "version": "2.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/log-update/node_modules/color-name": {
+ "version": "1.1.4",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/log-update/node_modules/wrap-ansi": {
+ "version": "6.2.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/loupe": {
+ "version": "2.3.4",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "get-func-name": "^2.0.0"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "6.0.0",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/lz-string": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
+ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
+ "bin": {
+ "lz-string": "bin/bin.js"
+ }
+ },
+ "node_modules/make-dir": {
+ "version": "3.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/make-fetch-happen": {
+ "version": "9.1.0",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "agentkeepalive": "^4.1.3",
+ "cacache": "^15.2.0",
+ "http-cache-semantics": "^4.1.0",
+ "http-proxy-agent": "^4.0.1",
+ "https-proxy-agent": "^5.0.0",
+ "is-lambda": "^1.0.1",
+ "lru-cache": "^6.0.0",
+ "minipass": "^3.1.3",
+ "minipass-collect": "^1.0.2",
+ "minipass-fetch": "^1.3.2",
+ "minipass-flush": "^1.0.5",
+ "minipass-pipeline": "^1.2.4",
+ "negotiator": "^0.6.2",
+ "promise-retry": "^2.0.1",
+ "socks-proxy-agent": "^6.0.0",
+ "ssri": "^8.0.0"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/map-cache": {
+ "version": "0.2.2",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/map-obj": {
+ "version": "4.3.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/map-visit": {
+ "version": "1.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "object-visit": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/map-visit/node_modules/object-visit": {
+ "version": "1.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "isobject": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/mathml-tag-names": {
+ "version": "2.1.3",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/meow": {
+ "version": "9.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/minimist": "^1.2.0",
+ "camelcase-keys": "^6.2.2",
+ "decamelize": "^1.2.0",
+ "decamelize-keys": "^1.1.0",
+ "hard-rejection": "^2.1.0",
+ "minimist-options": "4.1.0",
+ "normalize-package-data": "^3.0.0",
+ "read-pkg-up": "^7.0.1",
+ "redent": "^3.0.0",
+ "trim-newlines": "^3.0.0",
+ "type-fest": "^0.18.0",
+ "yargs-parser": "^20.2.3"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/meow/node_modules/decamelize-keys": {
+ "version": "1.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "decamelize": "^1.1.0",
+ "map-obj": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/meow/node_modules/hosted-git-info": {
+ "version": "4.1.0",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/meow/node_modules/map-obj": {
+ "version": "1.0.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/meow/node_modules/normalize-package-data": {
+ "version": "3.0.3",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "hosted-git-info": "^4.0.1",
+ "is-core-module": "^2.5.0",
+ "semver": "^7.3.4",
+ "validate-npm-package-license": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/meow/node_modules/read-pkg": {
+ "version": "5.2.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/normalize-package-data": "^2.4.0",
+ "normalize-package-data": "^2.5.0",
+ "parse-json": "^5.0.0",
+ "type-fest": "^0.6.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/meow/node_modules/read-pkg-up": {
+ "version": "7.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "find-up": "^4.1.0",
+ "read-pkg": "^5.2.0",
+ "type-fest": "^0.8.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/meow/node_modules/read-pkg-up/node_modules/type-fest": {
+ "version": "0.8.1",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/meow/node_modules/read-pkg/node_modules/hosted-git-info": {
+ "version": "2.8.9",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/meow/node_modules/read-pkg/node_modules/normalize-package-data": {
+ "version": "2.5.0",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "hosted-git-info": "^2.1.4",
+ "resolve": "^1.10.0",
+ "semver": "2 || 3 || 4 || 5",
+ "validate-npm-package-license": "^3.0.1"
+ }
+ },
+ "node_modules/meow/node_modules/read-pkg/node_modules/semver": {
+ "version": "5.7.1",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver"
+ }
+ },
+ "node_modules/meow/node_modules/read-pkg/node_modules/type-fest": {
+ "version": "0.6.0",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/meow/node_modules/semver": {
+ "version": "7.3.7",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/meow/node_modules/type-fest": {
+ "version": "0.18.1",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/meow/node_modules/yargs-parser": {
+ "version": "20.2.9",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/merge-options": {
+ "version": "1.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-plain-obj": "^1.1"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/merge-stream": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.5",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.2",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/micromatch/node_modules/picomatch": {
+ "version": "2.3.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mimic-fn": {
+ "version": "2.1.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/min-indent": {
+ "version": "1.0.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/mini-css-extract-plugin": {
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-1.6.2.tgz",
+ "integrity": "sha512-WhDvO3SjGm40oV5y26GjMJYjd2UMqrLAGKy5YS2/3QKJy2F7jgynuHTir/tgUUOiNQu5saXHdc8reo7YuhhT4Q==",
+ "dev": true,
+ "dependencies": {
+ "loader-utils": "^2.0.0",
+ "schema-utils": "^3.0.0",
+ "webpack-sources": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "webpack": "^4.4.0 || ^5.0.0"
+ }
+ },
+ "node_modules/mini-css-extract-plugin/node_modules/loader-utils": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
+ "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
+ "dev": true,
+ "dependencies": {
+ "big.js": "^5.2.2",
+ "emojis-list": "^3.0.0",
+ "json5": "^2.1.2"
+ },
+ "engines": {
+ "node": ">=8.9.0"
+ }
+ },
+ "node_modules/mini-css-extract-plugin/node_modules/schema-utils": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.2.tgz",
+ "integrity": "sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg==",
+ "dev": true,
+ "dependencies": {
+ "@types/json-schema": "^7.0.8",
+ "ajv": "^6.12.5",
+ "ajv-keywords": "^3.5.2"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.6",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/minimist-options": {
+ "version": "4.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "arrify": "^1.0.1",
+ "is-plain-obj": "^1.1.0",
+ "kind-of": "^6.0.3"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/minipass": {
+ "version": "3.3.4",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/minipass-collect": {
+ "version": "1.0.2",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/minipass-fetch": {
+ "version": "1.4.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "minipass": "^3.1.0",
+ "minipass-sized": "^1.0.3",
+ "minizlib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "optionalDependencies": {
+ "encoding": "^0.1.12"
+ }
+ },
+ "node_modules/minipass-flush": {
+ "version": "1.0.5",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/minipass-pipeline": {
+ "version": "1.2.4",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/minipass-sized": {
+ "version": "1.0.3",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/minizlib": {
+ "version": "2.1.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "minipass": "^3.0.0",
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/mitt": {
+ "version": "1.1.2",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/mixin-deep": {
+ "version": "1.3.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "for-in": "^1.0.2",
+ "is-extendable": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/mixin-deep/node_modules/is-extendable": {
+ "version": "1.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-plain-object": "^2.0.4"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/mixin-deep/node_modules/is-plain-object": {
+ "version": "2.0.4",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "isobject": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/mkdirp": {
+ "version": "1.0.4",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "mkdirp": "bin/cmd.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/mobx": {
+ "version": "6.9.0",
+ "resolved": "https://registry.npmjs.org/mobx/-/mobx-6.9.0.tgz",
+ "integrity": "sha512-HdKewQEREEJgsWnErClfbFoVebze6rGazxFLU/XUyrII8dORfVszN1V0BMRnQSzcgsNNtkX8DHj3nC6cdWE9YQ==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mobx"
+ }
+ },
+ "node_modules/mobx-react": {
+ "version": "7.6.0",
+ "resolved": "https://registry.npmjs.org/mobx-react/-/mobx-react-7.6.0.tgz",
+ "integrity": "sha512-+HQUNuh7AoQ9ZnU6c4rvbiVVl+wEkb9WqYsVDzGLng+Dqj1XntHu79PvEWKtSMoMj67vFp/ZPXcElosuJO8ckA==",
+ "dependencies": {
+ "mobx-react-lite": "^3.4.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mobx"
+ },
+ "peerDependencies": {
+ "mobx": "^6.1.0",
+ "react": "^16.8.0 || ^17 || ^18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ },
+ "react-native": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/mobx-react-lite": {
+ "version": "3.4.3",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mobx"
+ },
+ "peerDependencies": {
+ "mobx": "^6.1.0",
+ "react": "^16.8.0 || ^17 || ^18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ },
+ "react-native": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/moment": {
+ "version": "2.29.4",
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.2",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nan": {
+ "version": "2.16.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nanoclone": {
+ "version": "0.2.1",
+ "license": "MIT"
+ },
+ "node_modules/nanomatch": {
+ "version": "1.2.13",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "arr-diff": "^4.0.0",
+ "array-unique": "^0.3.2",
+ "define-property": "^2.0.2",
+ "extend-shallow": "^3.0.2",
+ "fragment-cache": "^0.2.1",
+ "is-windows": "^1.0.2",
+ "kind-of": "^6.0.2",
+ "object.pick": "^1.3.0",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/nanomatch/node_modules/define-property": {
+ "version": "2.0.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-descriptor": "^1.0.2",
+ "isobject": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/nanomatch/node_modules/extend-shallow": {
+ "version": "3.0.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "assign-symbols": "^1.0.0",
+ "is-extendable": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/nanomatch/node_modules/is-extendable": {
+ "version": "1.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-plain-object": "^2.0.4"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/nanomatch/node_modules/is-plain-object": {
+ "version": "2.0.4",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "isobject": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/negotiator": {
+ "version": "0.6.3",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/neo-async": {
+ "version": "2.6.2",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nise": {
+ "version": "5.1.1",
+ "license": "BSD-3-Clause",
+ "peer": true,
+ "dependencies": {
+ "@sinonjs/commons": "^1.8.3",
+ "@sinonjs/fake-timers": ">=5",
+ "@sinonjs/text-encoding": "^0.7.1",
+ "just-extend": "^4.0.2",
+ "path-to-regexp": "^1.7.0"
+ }
+ },
+ "node_modules/node-gyp": {
+ "version": "8.4.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "env-paths": "^2.2.0",
+ "glob": "^7.1.4",
+ "graceful-fs": "^4.2.6",
+ "make-fetch-happen": "^9.1.0",
+ "nopt": "^5.0.0",
+ "npmlog": "^6.0.0",
+ "rimraf": "^3.0.2",
+ "semver": "^7.3.5",
+ "tar": "^6.1.2",
+ "which": "^2.0.2"
+ },
+ "bin": {
+ "node-gyp": "bin/node-gyp.js"
+ },
+ "engines": {
+ "node": ">= 10.12.0"
+ }
+ },
+ "node_modules/node-gyp/node_modules/are-we-there-yet": {
+ "version": "3.0.1",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "delegates": "^1.0.0",
+ "readable-stream": "^3.6.0"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/node-gyp/node_modules/gauge": {
+ "version": "4.0.4",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "aproba": "^1.0.3 || ^2.0.0",
+ "color-support": "^1.1.3",
+ "console-control-strings": "^1.1.0",
+ "has-unicode": "^2.0.1",
+ "signal-exit": "^3.0.7",
+ "string-width": "^4.2.3",
+ "strip-ansi": "^6.0.1",
+ "wide-align": "^1.1.5"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/node-gyp/node_modules/npmlog": {
+ "version": "6.0.2",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "are-we-there-yet": "^3.0.0",
+ "console-control-strings": "^1.1.0",
+ "gauge": "^4.0.3",
+ "set-blocking": "^2.0.0"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/node-gyp/node_modules/readable-stream": {
+ "version": "3.6.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/node-gyp/node_modules/rimraf": {
+ "version": "3.0.2",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/node-gyp/node_modules/semver": {
+ "version": "7.3.8",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/node-sass": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-7.0.3.tgz",
+ "integrity": "sha512-8MIlsY/4dXUkJDYht9pIWBhMil3uHmE8b/AdJPjmFn1nBx9X9BASzfzmsCy0uCCb8eqI3SYYzVPDswWqSx7gjw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "dependencies": {
+ "async-foreach": "^0.1.3",
+ "chalk": "^4.1.2",
+ "cross-spawn": "^7.0.3",
+ "gaze": "^1.0.0",
+ "get-stdin": "^4.0.1",
+ "glob": "^7.0.3",
+ "lodash": "^4.17.15",
+ "meow": "^9.0.0",
+ "nan": "^2.13.2",
+ "node-gyp": "^8.4.1",
+ "npmlog": "^5.0.0",
+ "request": "^2.88.0",
+ "sass-graph": "^4.0.1",
+ "stdout-stream": "^1.4.0",
+ "true-case-path": "^1.0.2"
+ },
+ "bin": {
+ "node-sass": "bin/node-sass"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/node-sass/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/node-sass/node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/node-sass/node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/node-sass/node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/node-sass/node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/node-sass/node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/nopt": {
+ "version": "5.0.0",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "abbrev": "1"
+ },
+ "bin": {
+ "nopt": "bin/nopt.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/normalize-package-data": {
+ "version": "2.5.0",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "hosted-git-info": "^2.1.4",
+ "resolve": "^1.10.0",
+ "semver": "2 || 3 || 4 || 5",
+ "validate-npm-package-license": "^3.0.1"
+ }
+ },
+ "node_modules/normalize-package-data/node_modules/semver": {
+ "version": "5.7.1",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver"
+ }
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/normalize-url": {
+ "version": "1.9.1",
+ "license": "MIT",
+ "dependencies": {
+ "object-assign": "^4.0.1",
+ "prepend-http": "^1.0.0",
+ "query-string": "^4.1.0",
+ "sort-keys": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/npm-run-path": {
+ "version": "4.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/npmlog": {
+ "version": "5.0.1",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "are-we-there-yet": "^2.0.0",
+ "console-control-strings": "^1.1.0",
+ "gauge": "^3.0.0",
+ "set-blocking": "^2.0.0"
+ }
+ },
+ "node_modules/oauth-sign": {
+ "version": "0.9.0",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-copy": {
+ "version": "0.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "copy-descriptor": "^0.1.0",
+ "define-property": "^0.2.5",
+ "kind-of": "^3.0.3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-copy/node_modules/define-property": {
+ "version": "0.2.5",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-descriptor": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-copy/node_modules/is-accessor-descriptor": {
+ "version": "0.1.6",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "kind-of": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-copy/node_modules/is-data-descriptor": {
+ "version": "0.1.4",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "kind-of": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-copy/node_modules/is-descriptor": {
+ "version": "0.1.6",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-accessor-descriptor": "^0.1.6",
+ "is-data-descriptor": "^0.1.4",
+ "kind-of": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-copy/node_modules/is-descriptor/node_modules/kind-of": {
+ "version": "5.1.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-copy/node_modules/kind-of": {
+ "version": "3.2.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-buffer": "^1.1.5"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.12.3",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
+ "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object-keys": {
+ "version": "1.1.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/object.assign": {
+ "version": "4.1.4",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "has-symbols": "^1.0.3",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object.entries": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz",
+ "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/object.fromentries": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz",
+ "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object.hasown": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.2.tgz",
+ "integrity": "sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==",
+ "dev": true,
+ "dependencies": {
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object.pick": {
+ "version": "1.3.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "isobject": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object.values": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz",
+ "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/onetime": {
+ "version": "5.1.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mimic-fn": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.1",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.3"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/optionator/node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/optionator/node_modules/type-check": {
+ "version": "0.4.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "2.3.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "4.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/p-map": {
+ "version": "2.1.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/p-try": {
+ "version": "2.2.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/pako": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
+ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parse-json": {
+ "version": "5.2.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-even-better-errors": "^2.3.0",
+ "lines-and-columns": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parse-json/node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/pascalcase": {
+ "version": "0.1.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/path-to-regexp": {
+ "version": "1.8.0",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "isarray": "0.0.1"
+ }
+ },
+ "node_modules/path-to-regexp/node_modules/isarray": {
+ "version": "0.0.1",
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/path-type": {
+ "version": "4.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pathval": {
+ "version": "1.1.1",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/performance-now": {
+ "version": "2.1.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/picocolors": {
+ "version": "1.0.0",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/pify": {
+ "version": "4.0.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/pinkie": {
+ "version": "2.0.4",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/pinkie-promise": {
+ "version": "2.0.1",
+ "license": "MIT",
+ "dependencies": {
+ "pinkie": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/pkg-dir": {
+ "version": "4.2.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "find-up": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/please-upgrade-node": {
+ "version": "3.2.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "semver-compare": "^1.0.0"
+ }
+ },
+ "node_modules/popper.js": {
+ "version": "1.16.1",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/popperjs"
+ }
+ },
+ "node_modules/posix-character-classes": {
+ "version": "0.1.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.4.16",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.4",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.2"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-media-query-parser": {
+ "version": "0.2.3",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/postcss-modules-extract-imports": {
+ "version": "3.0.0",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "^10 || ^12 || >= 14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/postcss-modules-local-by-default": {
+ "version": "4.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "icss-utils": "^5.0.0",
+ "postcss-selector-parser": "^6.0.2",
+ "postcss-value-parser": "^4.1.0"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >= 14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/postcss-modules-scope": {
+ "version": "3.0.0",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "postcss-selector-parser": "^6.0.4"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >= 14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/postcss-modules-values": {
+ "version": "4.0.0",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "icss-utils": "^5.0.0"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >= 14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/postcss-prefix-selector": {
+ "version": "1.16.0",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "postcss": ">4 <9"
+ }
+ },
+ "node_modules/postcss-resolve-nested-selector": {
+ "version": "0.1.1",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/postcss-safe-parser": {
+ "version": "6.0.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=12.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ "peerDependencies": {
+ "postcss": "^8.3.3"
+ }
+ },
+ "node_modules/postcss-selector-parser": {
+ "version": "6.0.10",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/postcss/node_modules/nanoid": {
+ "version": "3.3.4",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/postcss/node_modules/source-map-js": {
+ "version": "1.0.2",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/posthtml": {
+ "version": "0.9.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "posthtml-parser": "^0.2.0",
+ "posthtml-render": "^1.0.5"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/posthtml-parser": {
+ "version": "0.2.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "htmlparser2": "^3.8.3",
+ "isobject": "^2.1.0"
+ }
+ },
+ "node_modules/posthtml-parser/node_modules/dom-serializer": {
+ "version": "0.2.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "domelementtype": "^2.0.1",
+ "entities": "^2.0.0"
+ }
+ },
+ "node_modules/posthtml-parser/node_modules/dom-serializer/node_modules/domelementtype": {
+ "version": "2.3.0",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fb55"
+ }
+ ],
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/posthtml-parser/node_modules/dom-serializer/node_modules/entities": {
+ "version": "2.2.0",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/posthtml-parser/node_modules/domhandler": {
+ "version": "2.4.2",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "domelementtype": "1"
+ }
+ },
+ "node_modules/posthtml-parser/node_modules/domutils": {
+ "version": "1.7.0",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "dom-serializer": "0",
+ "domelementtype": "1"
+ }
+ },
+ "node_modules/posthtml-parser/node_modules/htmlparser2": {
+ "version": "3.10.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "domelementtype": "^1.3.1",
+ "domhandler": "^2.3.0",
+ "domutils": "^1.5.1",
+ "entities": "^1.1.1",
+ "inherits": "^2.0.1",
+ "readable-stream": "^3.1.1"
+ }
+ },
+ "node_modules/posthtml-parser/node_modules/isobject": {
+ "version": "2.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "isarray": "1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/posthtml-parser/node_modules/readable-stream": {
+ "version": "3.6.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/posthtml-parser/node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/posthtml-parser/node_modules/string_decoder": {
+ "version": "1.3.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
+ "node_modules/posthtml-rename-id": {
+ "version": "1.0.12",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "escape-string-regexp": "1.0.5"
+ }
+ },
+ "node_modules/posthtml-render": {
+ "version": "1.4.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/posthtml-svg-mode": {
+ "version": "1.0.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "merge-options": "1.0.1",
+ "posthtml": "^0.9.2",
+ "posthtml-parser": "^0.2.1",
+ "posthtml-render": "^1.0.6"
+ }
+ },
+ "node_modules/prepend-http": {
+ "version": "1.0.4",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/prettier": {
+ "version": "2.7.1",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "bin": {
+ "prettier": "bin-prettier.js"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/prettier-linter-helpers": {
+ "version": "1.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-diff": "^1.1.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/process-nextick-args": {
+ "version": "2.0.1",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/progress": {
+ "version": "2.0.3",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/promise-inflight": {
+ "version": "1.0.1",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/promise-retry": {
+ "version": "2.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "err-code": "^2.0.2",
+ "retry": "^0.12.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/prop-types": {
+ "version": "15.8.1",
+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
+ "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
+ "dependencies": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.13.1"
+ }
+ },
+ "node_modules/property-expr": {
+ "version": "2.0.5",
+ "license": "MIT"
+ },
+ "node_modules/psl": {
+ "version": "1.9.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/pump": {
+ "version": "3.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.1.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.5.3",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/query-string": {
+ "version": "4.3.4",
+ "license": "MIT",
+ "dependencies": {
+ "object-assign": "^4.1.0",
+ "strict-uri-encode": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/quick-lru": {
+ "version": "4.0.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/randombytes": {
+ "version": "2.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "^5.1.0"
+ }
+ },
+ "node_modules/raw-loader": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-4.0.2.tgz",
+ "integrity": "sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA==",
+ "dev": true,
+ "dependencies": {
+ "loader-utils": "^2.0.0",
+ "schema-utils": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "webpack": "^4.0.0 || ^5.0.0"
+ }
+ },
+ "node_modules/raw-loader/node_modules/loader-utils": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
+ "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
+ "dev": true,
+ "dependencies": {
+ "big.js": "^5.2.2",
+ "emojis-list": "^3.0.0",
+ "json5": "^2.1.2"
+ },
+ "engines": {
+ "node": ">=8.9.0"
+ }
+ },
+ "node_modules/raw-loader/node_modules/schema-utils": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.2.tgz",
+ "integrity": "sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg==",
+ "dev": true,
+ "dependencies": {
+ "@types/json-schema": "^7.0.8",
+ "ajv": "^6.12.5",
+ "ajv-keywords": "^3.5.2"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/react": {
+ "version": "17.0.2",
+ "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
+ "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "object-assign": "^4.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-content-loader": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/react-content-loader/-/react-content-loader-6.2.1.tgz",
+ "integrity": "sha512-6ONbFX+Hi3SHuP66JB8CPvJn372pj+qwltJV0J8z/8MFrq98I1cbFdZuhDWeQXu3CFxiiDTXJn7DFxx2ZvrO7g==",
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "react": ">=16.0.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "17.0.2",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
+ "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "object-assign": "^4.1.1",
+ "scheduler": "^0.20.2"
+ },
+ "peerDependencies": {
+ "react": "17.0.2"
+ }
+ },
+ "node_modules/react-fast-compare": {
+ "version": "2.0.4",
+ "license": "MIT"
+ },
+ "node_modules/react-floater": {
+ "version": "0.7.6",
+ "license": "MIT",
+ "dependencies": {
+ "deepmerge": "^4.2.2",
+ "exenv": "^1.2.2",
+ "is-lite": "^0.8.2",
+ "popper.js": "^1.16.0",
+ "prop-types": "^15.8.1",
+ "react-proptype-conditional-require": "^1.0.4",
+ "tree-changes": "^0.9.1"
+ },
+ "peerDependencies": {
+ "react": "15 - 18",
+ "react-dom": "15 - 18"
+ }
+ },
+ "node_modules/react-floater/node_modules/deepmerge": {
+ "version": "4.3.1",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-floater/node_modules/is-lite": {
+ "version": "0.8.2",
+ "license": "MIT"
+ },
+ "node_modules/react-is": {
+ "version": "16.13.1",
+ "license": "MIT"
+ },
+ "node_modules/react-joyride": {
+ "version": "2.5.4",
+ "resolved": "https://registry.npmjs.org/react-joyride/-/react-joyride-2.5.4.tgz",
+ "integrity": "sha512-CLV1Ju79iRKIP/KqsEX05nSxMBzT41BhPAyNrTdBQWEYAkpyo6iiCPelk6No2W8iPgw373JKk2cK7AQ5Q3lFew==",
+ "dependencies": {
+ "deepmerge": "^4.3.1",
+ "exenv": "^1.2.2",
+ "is-lite": "^0.9.2",
+ "prop-types": "^15.8.1",
+ "react-floater": "^0.7.6",
+ "react-is": "^16.13.1",
+ "scroll": "^3.0.1",
+ "scrollparent": "^2.0.1",
+ "tree-changes": "^0.9.2"
+ },
+ "peerDependencies": {
+ "react": "15 - 18",
+ "react-dom": "15 - 18"
+ }
+ },
+ "node_modules/react-joyride/node_modules/deepmerge": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
+ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-proptype-conditional-require": {
+ "version": "1.0.4",
+ "license": "MIT"
+ },
+ "node_modules/react-tabs": {
+ "version": "3.2.3",
+ "license": "MIT",
+ "dependencies": {
+ "clsx": "^1.1.0",
+ "prop-types": "^15.5.0"
+ },
+ "peerDependencies": {
+ "react": "^16.3.0 || ^17.0.0-0"
+ }
+ },
+ "node_modules/react-transition-group": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz",
+ "integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==",
+ "dependencies": {
+ "@babel/runtime": "^7.5.5",
+ "dom-helpers": "^5.0.1",
+ "loose-envify": "^1.4.0",
+ "prop-types": "^15.6.2"
+ },
+ "peerDependencies": {
+ "react": ">=16.6.0",
+ "react-dom": ">=16.6.0"
+ }
+ },
+ "node_modules/read-pkg": {
+ "version": "4.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "normalize-package-data": "^2.3.2",
+ "parse-json": "^4.0.0",
+ "pify": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/read-pkg/node_modules/parse-json": {
+ "version": "4.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "error-ex": "^1.3.1",
+ "json-parse-better-errors": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/read-pkg/node_modules/pify": {
+ "version": "3.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/readable-stream": {
+ "version": "2.3.7",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "node_modules/rechoir": {
+ "version": "0.7.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "resolve": "^1.9.0"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/redent": {
+ "version": "3.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "indent-string": "^4.0.0",
+ "strip-indent": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/regenerator-runtime": {
+ "version": "0.13.9",
+ "license": "MIT"
+ },
+ "node_modules/regex-not": {
+ "version": "1.0.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "extend-shallow": "^3.0.2",
+ "safe-regex": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/regex-not/node_modules/extend-shallow": {
+ "version": "3.0.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "assign-symbols": "^1.0.0",
+ "is-extendable": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/regex-not/node_modules/is-extendable": {
+ "version": "1.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-plain-object": "^2.0.4"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/regex-not/node_modules/is-plain-object": {
+ "version": "2.0.4",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "isobject": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/regexp.prototype.flags": {
+ "version": "1.4.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3",
+ "functions-have-names": "^1.2.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/regexpp": {
+ "version": "3.2.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mysticatea"
+ }
+ },
+ "node_modules/repeat-element": {
+ "version": "1.1.4",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/repeat-string": {
+ "version": "1.6.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/request": {
+ "version": "2.88.2",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "aws-sign2": "~0.7.0",
+ "aws4": "^1.8.0",
+ "caseless": "~0.12.0",
+ "combined-stream": "~1.0.6",
+ "extend": "~3.0.2",
+ "forever-agent": "~0.6.1",
+ "form-data": "~2.3.2",
+ "har-validator": "~5.1.3",
+ "http-signature": "~1.2.0",
+ "is-typedarray": "~1.0.0",
+ "isstream": "~0.1.2",
+ "json-stringify-safe": "~5.0.1",
+ "mime-types": "~2.1.19",
+ "oauth-sign": "~0.9.0",
+ "performance-now": "^2.1.0",
+ "qs": "~6.5.2",
+ "safe-buffer": "^5.1.2",
+ "tough-cookie": "~2.5.0",
+ "tunnel-agent": "^0.6.0",
+ "uuid": "^3.3.2"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/require-main-filename": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/resolve": {
+ "version": "1.22.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-core-module": "^2.9.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-cwd": {
+ "version": "3.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "resolve-from": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/resolve-cwd/node_modules/resolve-from": {
+ "version": "5.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/resolve-url": {
+ "version": "0.2.1",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/restore-cursor": {
+ "version": "3.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "onetime": "^5.1.0",
+ "signal-exit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ret": {
+ "version": "0.1.15",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12"
+ }
+ },
+ "node_modules/retry": {
+ "version": "0.12.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/rfdc": {
+ "version": "1.3.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/rimraf": {
+ "version": "2.7.1",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ }
+ },
+ "node_modules/robust-websocket": {
+ "version": "1.0.0",
+ "license": "ISC"
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/rxjs": {
+ "version": "6.6.7",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^1.9.0"
+ },
+ "engines": {
+ "npm": ">=2.0.0"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/safe-regex": {
+ "version": "1.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ret": "~0.1.10"
+ }
+ },
+ "node_modules/safe-regex-test": {
+ "version": "1.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.1.3",
+ "is-regex": "^1.1.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/sass-graph": {
+ "version": "4.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "glob": "^7.0.0",
+ "lodash": "^4.17.11",
+ "scss-tokenizer": "^0.4.3",
+ "yargs": "^17.2.1"
+ },
+ "bin": {
+ "sassgraph": "bin/sassgraph"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/sass-graph/node_modules/cliui": {
+ "version": "8.0.1",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/sass-graph/node_modules/y18n": {
+ "version": "5.0.8",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/sass-graph/node_modules/yargs": {
+ "version": "17.6.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cliui": "^8.0.1",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.3",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^21.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/sass-graph/node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/sass-loader": {
+ "version": "12.6.0",
+ "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-12.6.0.tgz",
+ "integrity": "sha512-oLTaH0YCtX4cfnJZxKSLAyglED0naiYfNG1iXfU5w1LNZ+ukoA5DtyDIN5zmKVZwYNJP4KRc5Y3hkWga+7tYfA==",
+ "dev": true,
+ "dependencies": {
+ "klona": "^2.0.4",
+ "neo-async": "^2.6.2"
+ },
+ "engines": {
+ "node": ">= 12.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "fibers": ">= 3.1.0",
+ "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0",
+ "sass": "^1.3.0",
+ "sass-embedded": "*",
+ "webpack": "^5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "fibers": {
+ "optional": true
+ },
+ "node-sass": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/sass-resources-loader": {
+ "version": "2.2.5",
+ "resolved": "https://registry.npmjs.org/sass-resources-loader/-/sass-resources-loader-2.2.5.tgz",
+ "integrity": "sha512-po8rfETH9cOQACWxubT/1CCu77KjxwRtCDm6QAXZH99aUHBydwSoxdIjC40SGp/dcS/FkSNJl0j1VEojGZqlvQ==",
+ "dev": true,
+ "dependencies": {
+ "async": "^3.2.3",
+ "chalk": "^4.1.0",
+ "glob": "^7.1.6",
+ "loader-utils": "^2.0.0"
+ }
+ },
+ "node_modules/sass-resources-loader/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/sass-resources-loader/node_modules/async": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
+ "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==",
+ "dev": true
+ },
+ "node_modules/sass-resources-loader/node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/sass-resources-loader/node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/sass-resources-loader/node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/sass-resources-loader/node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/sass-resources-loader/node_modules/loader-utils": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
+ "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
+ "dev": true,
+ "dependencies": {
+ "big.js": "^5.2.2",
+ "emojis-list": "^3.0.0",
+ "json5": "^2.1.2"
+ },
+ "engines": {
+ "node": ">=8.9.0"
+ }
+ },
+ "node_modules/sass-resources-loader/node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.20.2",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "object-assign": "^4.1.1"
+ }
+ },
+ "node_modules/schema-utils": {
+ "version": "2.7.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/json-schema": "^7.0.5",
+ "ajv": "^6.12.4",
+ "ajv-keywords": "^3.5.2"
+ },
+ "engines": {
+ "node": ">= 8.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/scroll": {
+ "version": "3.0.1",
+ "license": "MIT"
+ },
+ "node_modules/scrollparent": {
+ "version": "2.0.1",
+ "license": "ISC"
+ },
+ "node_modules/scss-tokenizer": {
+ "version": "0.4.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "js-base64": "^2.4.9",
+ "source-map": "^0.7.3"
+ }
+ },
+ "node_modules/semver": {
+ "version": "6.3.0",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/semver-compare": {
+ "version": "1.0.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/serialize-javascript": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz",
+ "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==",
+ "dev": true,
+ "dependencies": {
+ "randombytes": "^2.1.0"
+ }
+ },
+ "node_modules/set-blocking": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/set-value": {
+ "version": "2.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "extend-shallow": "^2.0.1",
+ "is-extendable": "^0.1.1",
+ "is-plain-object": "^2.0.3",
+ "split-string": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/set-value/node_modules/is-plain-object": {
+ "version": "2.0.4",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "isobject": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/shallow-clone": {
+ "version": "3.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "kind-of": "^6.0.2"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/side-channel": {
+ "version": "1.0.4",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.0",
+ "get-intrinsic": "^1.0.2",
+ "object-inspect": "^1.9.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/signal-exit": {
+ "version": "3.0.7",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/sinon": {
+ "version": "14.0.1",
+ "license": "BSD-3-Clause",
+ "peer": true,
+ "dependencies": {
+ "@sinonjs/commons": "^1.8.3",
+ "@sinonjs/fake-timers": "^9.1.2",
+ "@sinonjs/samsam": "^6.1.1",
+ "diff": "^5.0.0",
+ "nise": "^5.1.1",
+ "supports-color": "^7.2.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/sinon"
+ }
+ },
+ "node_modules/sinon-chai": {
+ "version": "3.7.0",
+ "license": "(BSD-2-Clause OR WTFPL)",
+ "peerDependencies": {
+ "chai": "^4.0.0",
+ "sinon": ">=4.0.0"
+ }
+ },
+ "node_modules/sinon/node_modules/has-flag": {
+ "version": "4.0.0",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/sinon/node_modules/supports-color": {
+ "version": "7.2.0",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/slash": {
+ "version": "3.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/slice-ansi": {
+ "version": "4.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "astral-regex": "^2.0.0",
+ "is-fullwidth-code-point": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/slice-ansi?sponsor=1"
+ }
+ },
+ "node_modules/slice-ansi/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/slice-ansi/node_modules/color-convert": {
+ "version": "2.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/slice-ansi/node_modules/color-name": {
+ "version": "1.1.4",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/smart-buffer": {
+ "version": "4.2.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6.0.0",
+ "npm": ">= 3.0.0"
+ }
+ },
+ "node_modules/snapdragon": {
+ "version": "0.8.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "base": "^0.11.1",
+ "debug": "^2.2.0",
+ "define-property": "^0.2.5",
+ "extend-shallow": "^2.0.1",
+ "map-cache": "^0.2.2",
+ "source-map": "^0.5.6",
+ "source-map-resolve": "^0.5.0",
+ "use": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/snapdragon-node": {
+ "version": "2.1.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "define-property": "^1.0.0",
+ "isobject": "^3.0.0",
+ "snapdragon-util": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/snapdragon-util": {
+ "version": "3.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "kind-of": "^3.2.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/snapdragon-util/node_modules/kind-of": {
+ "version": "3.2.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-buffer": "^1.1.5"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/snapdragon/node_modules/debug": {
+ "version": "2.6.9",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/snapdragon/node_modules/define-property": {
+ "version": "0.2.5",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-descriptor": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/snapdragon/node_modules/is-accessor-descriptor": {
+ "version": "0.1.6",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "kind-of": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/snapdragon/node_modules/is-accessor-descriptor/node_modules/kind-of": {
+ "version": "3.2.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-buffer": "^1.1.5"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/snapdragon/node_modules/is-data-descriptor": {
+ "version": "0.1.4",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "kind-of": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/snapdragon/node_modules/is-data-descriptor/node_modules/kind-of": {
+ "version": "3.2.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-buffer": "^1.1.5"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/snapdragon/node_modules/is-descriptor": {
+ "version": "0.1.6",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-accessor-descriptor": "^0.1.6",
+ "is-data-descriptor": "^0.1.4",
+ "kind-of": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/snapdragon/node_modules/kind-of": {
+ "version": "5.1.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/snapdragon/node_modules/ms": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/snapdragon/node_modules/source-map": {
+ "version": "0.5.7",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/socks": {
+ "version": "2.7.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ip": "^2.0.0",
+ "smart-buffer": "^4.2.0"
+ },
+ "engines": {
+ "node": ">= 10.13.0",
+ "npm": ">= 3.0.0"
+ }
+ },
+ "node_modules/socks-proxy-agent": {
+ "version": "6.2.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^6.0.2",
+ "debug": "^4.3.3",
+ "socks": "^2.6.2"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/sort-keys": {
+ "version": "1.1.2",
+ "license": "MIT",
+ "dependencies": {
+ "is-plain-obj": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-list-map": {
+ "version": "2.0.1",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/source-map": {
+ "version": "0.7.4",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/source-map-resolve": {
+ "version": "0.5.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "atob": "^2.1.2",
+ "decode-uri-component": "^0.2.0",
+ "resolve-url": "^0.2.1",
+ "source-map-url": "^0.4.0",
+ "urix": "^0.1.0"
+ }
+ },
+ "node_modules/source-map-support": {
+ "version": "0.5.21",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
+ "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
+ "dev": true,
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
+ "node_modules/source-map-support/node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-url": {
+ "version": "0.4.1",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/spawn-command": {
+ "version": "0.0.2-1",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/spdx-correct": {
+ "version": "3.1.1",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "spdx-expression-parse": "^3.0.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "node_modules/spdx-exceptions": {
+ "version": "2.3.0",
+ "dev": true,
+ "license": "CC-BY-3.0"
+ },
+ "node_modules/spdx-expression-parse": {
+ "version": "3.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "spdx-exceptions": "^2.1.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "node_modules/spdx-license-ids": {
+ "version": "3.0.12",
+ "dev": true,
+ "license": "CC0-1.0"
+ },
+ "node_modules/split-string": {
+ "version": "3.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "extend-shallow": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/split-string/node_modules/extend-shallow": {
+ "version": "3.0.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "assign-symbols": "^1.0.0",
+ "is-extendable": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/split-string/node_modules/is-extendable": {
+ "version": "1.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-plain-object": "^2.0.4"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/split-string/node_modules/is-plain-object": {
+ "version": "2.0.4",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "isobject": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/sshpk": {
+ "version": "1.17.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "asn1": "~0.2.3",
+ "assert-plus": "^1.0.0",
+ "bcrypt-pbkdf": "^1.0.0",
+ "dashdash": "^1.12.0",
+ "ecc-jsbn": "~0.1.1",
+ "getpass": "^0.1.1",
+ "jsbn": "~0.1.0",
+ "safer-buffer": "^2.0.2",
+ "tweetnacl": "~0.14.0"
+ },
+ "bin": {
+ "sshpk-conv": "bin/sshpk-conv",
+ "sshpk-sign": "bin/sshpk-sign",
+ "sshpk-verify": "bin/sshpk-verify"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/ssri": {
+ "version": "8.0.1",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^3.1.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/static-extend": {
+ "version": "0.1.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "define-property": "^0.2.5",
+ "object-copy": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/static-extend/node_modules/define-property": {
+ "version": "0.2.5",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-descriptor": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/static-extend/node_modules/is-accessor-descriptor": {
+ "version": "0.1.6",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "kind-of": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/static-extend/node_modules/is-accessor-descriptor/node_modules/kind-of": {
+ "version": "3.2.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-buffer": "^1.1.5"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/static-extend/node_modules/is-data-descriptor": {
+ "version": "0.1.4",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "kind-of": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/static-extend/node_modules/is-data-descriptor/node_modules/kind-of": {
+ "version": "3.2.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-buffer": "^1.1.5"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/static-extend/node_modules/is-descriptor": {
+ "version": "0.1.6",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-accessor-descriptor": "^0.1.6",
+ "is-data-descriptor": "^0.1.4",
+ "kind-of": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/static-extend/node_modules/kind-of": {
+ "version": "5.1.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/stdout-stream": {
+ "version": "1.4.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "readable-stream": "^2.0.1"
+ }
+ },
+ "node_modules/strict-uri-encode": {
+ "version": "1.1.0",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/string_decoder": {
+ "version": "1.1.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "node_modules/string-argv": {
+ "version": "0.3.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6.19"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string.prototype.matchall": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz",
+ "integrity": "sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4",
+ "get-intrinsic": "^1.1.3",
+ "has-symbols": "^1.0.3",
+ "internal-slot": "^1.0.3",
+ "regexp.prototype.flags": "^1.4.3",
+ "side-channel": "^1.0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.trim": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz",
+ "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.trimend": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz",
+ "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.trimstart": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz",
+ "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/stringify-object": {
+ "version": "3.3.0",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "get-own-enumerable-property-symbols": "^3.0.0",
+ "is-obj": "^1.0.1",
+ "is-regexp": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/stringify-object/node_modules/get-own-enumerable-property-symbols": {
+ "version": "3.0.2",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-bom": {
+ "version": "3.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/strip-final-newline": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/strip-indent": {
+ "version": "3.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "min-indent": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/strip-outer": {
+ "version": "1.0.1",
+ "license": "MIT",
+ "dependencies": {
+ "escape-string-regexp": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/strip-url-auth": {
+ "version": "1.0.1",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/style-search": {
+ "version": "0.1.0",
+ "dev": true,
+ "license": "ISC",
+ "peer": true
+ },
+ "node_modules/stylelint": {
+ "version": "14.11.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@csstools/selector-specificity": "^2.0.2",
+ "balanced-match": "^2.0.0",
+ "colord": "^2.9.3",
+ "cosmiconfig": "^7.0.1",
+ "css-functions-list": "^3.1.0",
+ "debug": "^4.3.4",
+ "fast-glob": "^3.2.11",
+ "fastest-levenshtein": "^1.0.16",
+ "file-entry-cache": "^6.0.1",
+ "global-modules": "^2.0.0",
+ "globby": "^11.1.0",
+ "globjoin": "^0.1.4",
+ "html-tags": "^3.2.0",
+ "ignore": "^5.2.0",
+ "import-lazy": "^4.0.0",
+ "imurmurhash": "^0.1.4",
+ "is-plain-object": "^5.0.0",
+ "known-css-properties": "^0.25.0",
+ "mathml-tag-names": "^2.1.3",
+ "meow": "^9.0.0",
+ "micromatch": "^4.0.5",
+ "normalize-path": "^3.0.0",
+ "picocolors": "^1.0.0",
+ "postcss": "^8.4.16",
+ "postcss-media-query-parser": "^0.2.3",
+ "postcss-resolve-nested-selector": "^0.1.1",
+ "postcss-safe-parser": "^6.0.0",
+ "postcss-selector-parser": "^6.0.10",
+ "postcss-value-parser": "^4.2.0",
+ "resolve-from": "^5.0.0",
+ "string-width": "^4.2.3",
+ "strip-ansi": "^6.0.1",
+ "style-search": "^0.1.0",
+ "supports-hyperlinks": "^2.2.0",
+ "svg-tags": "^1.0.0",
+ "table": "^6.8.0",
+ "v8-compile-cache": "^2.3.0",
+ "write-file-atomic": "^4.0.2"
+ },
+ "bin": {
+ "stylelint": "bin/stylelint.js"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/stylelint"
+ }
+ },
+ "node_modules/stylelint-webpack-plugin": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/stylelint-webpack-plugin/-/stylelint-webpack-plugin-2.5.0.tgz",
+ "integrity": "sha512-UX+A4FzHM98VI+mnN4ZGDXnDOf3RC4+xk26nyveUQmIDx43dFYbZQHsZNVFl17La9+VQFpwOHh3c5Zv/kwAI3g==",
+ "dev": true,
+ "dependencies": {
+ "arrify": "^2.0.1",
+ "globby": "^11.1.0",
+ "jest-worker": "^28.1.3",
+ "micromatch": "^4.0.5",
+ "normalize-path": "^3.0.0",
+ "schema-utils": "^3.1.1"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "stylelint": "^13.0.0 || ^14.0.0 || ^15.0.0",
+ "webpack": "^4.0.0 || ^5.0.0"
+ }
+ },
+ "node_modules/stylelint-webpack-plugin/node_modules/arrify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz",
+ "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/stylelint-webpack-plugin/node_modules/schema-utils": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.2.tgz",
+ "integrity": "sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg==",
+ "dev": true,
+ "dependencies": {
+ "@types/json-schema": "^7.0.8",
+ "ajv": "^6.12.5",
+ "ajv-keywords": "^3.5.2"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/stylelint/node_modules/balanced-match": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/stylelint/node_modules/ignore": {
+ "version": "5.2.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/stylelint/node_modules/resolve-from": {
+ "version": "5.0.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "6.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/supports-hyperlinks": {
+ "version": "2.3.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "has-flag": "^4.0.0",
+ "supports-color": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/supports-hyperlinks/node_modules/has-flag": {
+ "version": "4.0.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/supports-hyperlinks/node_modules/supports-color": {
+ "version": "7.2.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/svg-baker": {
+ "version": "1.7.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bluebird": "^3.5.0",
+ "clone": "^2.1.1",
+ "he": "^1.1.1",
+ "image-size": "^0.5.1",
+ "loader-utils": "^1.1.0",
+ "merge-options": "1.0.1",
+ "micromatch": "3.1.0",
+ "postcss": "^5.2.17",
+ "postcss-prefix-selector": "^1.6.0",
+ "posthtml-rename-id": "^1.0",
+ "posthtml-svg-mode": "^1.0.3",
+ "query-string": "^4.3.2",
+ "traverse": "^0.6.6"
+ }
+ },
+ "node_modules/svg-baker-runtime": {
+ "version": "1.4.7",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "deepmerge": "1.3.2",
+ "mitt": "1.1.2",
+ "svg-baker": "^1.7.0"
+ }
+ },
+ "node_modules/svg-baker-runtime/node_modules/deepmerge": {
+ "version": "1.3.2",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/svg-baker/node_modules/ansi-regex": {
+ "version": "2.1.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/svg-baker/node_modules/ansi-styles": {
+ "version": "2.2.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/svg-baker/node_modules/braces": {
+ "version": "2.3.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "arr-flatten": "^1.1.0",
+ "array-unique": "^0.3.2",
+ "extend-shallow": "^2.0.1",
+ "fill-range": "^4.0.0",
+ "isobject": "^3.0.1",
+ "repeat-element": "^1.1.2",
+ "snapdragon": "^0.8.1",
+ "snapdragon-node": "^2.0.1",
+ "split-string": "^3.0.2",
+ "to-regex": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/svg-baker/node_modules/chalk": {
+ "version": "1.1.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^2.2.1",
+ "escape-string-regexp": "^1.0.2",
+ "has-ansi": "^2.0.0",
+ "strip-ansi": "^3.0.0",
+ "supports-color": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/svg-baker/node_modules/chalk/node_modules/supports-color": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/svg-baker/node_modules/fill-range": {
+ "version": "4.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "extend-shallow": "^2.0.1",
+ "is-number": "^3.0.0",
+ "repeat-string": "^1.6.1",
+ "to-regex-range": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/svg-baker/node_modules/has-flag": {
+ "version": "1.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/svg-baker/node_modules/is-number": {
+ "version": "3.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "kind-of": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/svg-baker/node_modules/is-number/node_modules/kind-of": {
+ "version": "3.2.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-buffer": "^1.1.5"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/svg-baker/node_modules/kind-of": {
+ "version": "5.1.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/svg-baker/node_modules/micromatch": {
+ "version": "3.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "arr-diff": "^4.0.0",
+ "array-unique": "^0.3.2",
+ "braces": "^2.2.2",
+ "define-property": "^1.0.0",
+ "extend-shallow": "^2.0.1",
+ "extglob": "^2.0.2",
+ "fragment-cache": "^0.2.1",
+ "kind-of": "^5.0.2",
+ "nanomatch": "^1.2.1",
+ "object.pick": "^1.3.0",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/svg-baker/node_modules/postcss": {
+ "version": "5.2.18",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^1.1.3",
+ "js-base64": "^2.1.9",
+ "source-map": "^0.5.6",
+ "supports-color": "^3.2.3"
+ },
+ "engines": {
+ "node": ">=0.12"
+ }
+ },
+ "node_modules/svg-baker/node_modules/source-map": {
+ "version": "0.5.7",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/svg-baker/node_modules/strip-ansi": {
+ "version": "3.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/svg-baker/node_modules/supports-color": {
+ "version": "3.2.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/svg-baker/node_modules/to-regex-range": {
+ "version": "2.1.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^3.0.0",
+ "repeat-string": "^1.6.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/svg-sprite-loader": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/svg-sprite-loader/-/svg-sprite-loader-5.2.1.tgz",
+ "integrity": "sha512-n2IZc87rpOeXh+PQFksFMGCfMV/BT01YG+Dlbyjoh2Cz8BSTL5Vi/7KDr86Pt/u1NRDCVb3vY74BF5rKCmqbNA==",
+ "dev": true,
+ "dependencies": {
+ "bluebird": "^3.5.0",
+ "deepmerge": "1.3.2",
+ "domready": "1.0.8",
+ "escape-string-regexp": "1.0.5",
+ "loader-utils": "^1.1.0",
+ "svg-baker": "^1.5.0",
+ "svg-baker-runtime": "^1.4.7",
+ "url-slug": "2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/svg-sprite-loader/node_modules/deepmerge": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-1.3.2.tgz",
+ "integrity": "sha512-qjMjTrk+RKv/sp4RPDpV5CnKhxjFI9p+GkLBOls5A8EEElldYWCWA9zceAkmfd0xIo2aU1nxiaLFoiya2sb6Cg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/svg-tags": {
+ "version": "1.0.0",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/table": {
+ "version": "6.8.0",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "peer": true,
+ "dependencies": {
+ "ajv": "^8.0.1",
+ "lodash.truncate": "^4.4.2",
+ "slice-ansi": "^4.0.0",
+ "string-width": "^4.2.3",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/table/node_modules/ajv": {
+ "version": "8.11.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/table/node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/table/node_modules/uri-js": {
+ "version": "4.4.1",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "peer": true,
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/tapable": {
+ "version": "2.2.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/tar": {
+ "version": "6.1.11",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "chownr": "^2.0.0",
+ "fs-minipass": "^2.0.0",
+ "minipass": "^3.0.0",
+ "minizlib": "^2.1.1",
+ "mkdirp": "^1.0.3",
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/terser": {
+ "version": "5.17.6",
+ "resolved": "https://registry.npmjs.org/terser/-/terser-5.17.6.tgz",
+ "integrity": "sha512-V8QHcs8YuyLkLHsJO5ucyff1ykrLVsR4dNnS//L5Y3NiSXpbK1J+WMVUs67eI0KTxs9JtHhgEQpXQVHlHI92DQ==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/source-map": "^0.3.2",
+ "acorn": "^8.5.0",
+ "commander": "^2.20.0",
+ "source-map-support": "~0.5.20"
+ },
+ "bin": {
+ "terser": "bin/terser"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/terser-webpack-plugin": {
+ "version": "5.3.9",
+ "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz",
+ "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.17",
+ "jest-worker": "^27.4.5",
+ "schema-utils": "^3.1.1",
+ "serialize-javascript": "^6.0.1",
+ "terser": "^5.16.8"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "webpack": "^5.1.0"
+ },
+ "peerDependenciesMeta": {
+ "@swc/core": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "uglify-js": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/terser-webpack-plugin/node_modules/has-flag": {
+ "version": "4.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/terser-webpack-plugin/node_modules/jest-worker": {
+ "version": "27.5.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "merge-stream": "^2.0.0",
+ "supports-color": "^8.0.0"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ }
+ },
+ "node_modules/terser-webpack-plugin/node_modules/schema-utils": {
+ "version": "3.1.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/json-schema": "^7.0.8",
+ "ajv": "^6.12.5",
+ "ajv-keywords": "^3.5.2"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/terser-webpack-plugin/node_modules/supports-color": {
+ "version": "8.1.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
+ "node_modules/terser/node_modules/acorn": {
+ "version": "8.8.2",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
+ "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==",
+ "dev": true,
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/text-table": {
+ "version": "0.2.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/through": {
+ "version": "2.3.8",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tiny-warning": {
+ "version": "1.0.3",
+ "license": "MIT"
+ },
+ "node_modules/to-fast-properties": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/to-object-path": {
+ "version": "0.3.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "kind-of": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/to-object-path/node_modules/kind-of": {
+ "version": "3.2.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-buffer": "^1.1.5"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/to-regex": {
+ "version": "3.0.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "define-property": "^2.0.2",
+ "extend-shallow": "^3.0.2",
+ "regex-not": "^1.0.2",
+ "safe-regex": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/to-regex/node_modules/define-property": {
+ "version": "2.0.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-descriptor": "^1.0.2",
+ "isobject": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/to-regex/node_modules/extend-shallow": {
+ "version": "3.0.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "assign-symbols": "^1.0.0",
+ "is-extendable": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/to-regex/node_modules/is-extendable": {
+ "version": "1.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-plain-object": "^2.0.4"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/to-regex/node_modules/is-plain-object": {
+ "version": "2.0.4",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "isobject": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/toposort": {
+ "version": "2.0.2",
+ "license": "MIT"
+ },
+ "node_modules/tough-cookie": {
+ "version": "2.5.0",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "psl": "^1.1.28",
+ "punycode": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/traverse": {
+ "version": "0.6.6",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tree-changes": {
+ "version": "0.9.3",
+ "license": "MIT",
+ "dependencies": {
+ "@gilbarbara/deep-equal": "^0.1.1",
+ "is-lite": "^0.8.2"
+ }
+ },
+ "node_modules/tree-changes/node_modules/is-lite": {
+ "version": "0.8.2",
+ "license": "MIT"
+ },
+ "node_modules/tree-kill": {
+ "version": "1.2.2",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "tree-kill": "cli.js"
+ }
+ },
+ "node_modules/trim-newlines": {
+ "version": "3.0.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/trim-repeated": {
+ "version": "1.0.0",
+ "license": "MIT",
+ "dependencies": {
+ "escape-string-regexp": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/true-case-path": {
+ "version": "1.0.3",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "glob": "^7.1.2"
+ }
+ },
+ "node_modules/tsconfig-paths": {
+ "version": "3.14.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/json5": "^0.0.29",
+ "json5": "^1.0.1",
+ "minimist": "^1.2.6",
+ "strip-bom": "^3.0.0"
+ }
+ },
+ "node_modules/tsconfig-paths/node_modules/json5": {
+ "version": "1.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "minimist": "^1.2.0"
+ },
+ "bin": {
+ "json5": "lib/cli.js"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "1.14.1",
+ "license": "0BSD"
+ },
+ "node_modules/tunnel-agent": {
+ "version": "0.6.0",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "safe-buffer": "^5.0.1"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/tweetnacl": {
+ "version": "0.14.5",
+ "dev": true,
+ "license": "Unlicense"
+ },
+ "node_modules/type-detect": {
+ "version": "4.0.8",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/typed-array-length": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz",
+ "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "for-each": "^0.3.3",
+ "is-typed-array": "^1.1.9"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "4.9.5",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
+ "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
+ "dev": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=4.2.0"
+ }
+ },
+ "node_modules/unbox-primitive": {
+ "version": "1.0.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "has-bigints": "^1.0.2",
+ "has-symbols": "^1.0.3",
+ "which-boxed-primitive": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/unidecode": {
+ "version": "0.1.8",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4.12"
+ }
+ },
+ "node_modules/union-value": {
+ "version": "1.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "arr-union": "^3.1.0",
+ "get-value": "^2.0.6",
+ "is-extendable": "^0.1.1",
+ "set-value": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/unique-filename": {
+ "version": "1.1.1",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "unique-slug": "^2.0.0"
+ }
+ },
+ "node_modules/unique-slug": {
+ "version": "2.0.2",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "imurmurhash": "^0.1.4"
+ }
+ },
+ "node_modules/universalify": {
+ "version": "0.1.2",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
+ "node_modules/unset-value": {
+ "version": "1.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-value": "^0.3.1",
+ "isobject": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/unset-value/node_modules/has-value": {
+ "version": "0.3.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "get-value": "^2.0.3",
+ "has-values": "^0.1.4",
+ "isobject": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/unset-value/node_modules/has-value/node_modules/isobject": {
+ "version": "2.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "isarray": "1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/unset-value/node_modules/has-values": {
+ "version": "0.1.4",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.0.7",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.1.1",
+ "picocolors": "^1.0.0"
+ },
+ "bin": {
+ "browserslist-lint": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/urix": {
+ "version": "0.1.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/url-search-params-polyfill": {
+ "version": "7.0.1",
+ "license": "MIT"
+ },
+ "node_modules/url-slug": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "unidecode": "0.1.8"
+ }
+ },
+ "node_modules/use": {
+ "version": "3.1.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/uuid": {
+ "version": "3.4.0",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "uuid": "bin/uuid"
+ }
+ },
+ "node_modules/v8-compile-cache": {
+ "version": "2.3.0",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/validate-npm-package-license": {
+ "version": "3.0.4",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "spdx-correct": "^3.0.0",
+ "spdx-expression-parse": "^3.0.0"
+ }
+ },
+ "node_modules/verror": {
+ "version": "1.10.0",
+ "dev": true,
+ "engines": [
+ "node >=0.6.0"
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "assert-plus": "^1.0.0",
+ "core-util-is": "1.0.2",
+ "extsprintf": "^1.2.0"
+ }
+ },
+ "node_modules/verror/node_modules/core-util-is": {
+ "version": "1.0.2",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/watchpack": {
+ "version": "2.4.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "glob-to-regexp": "^0.4.1",
+ "graceful-fs": "^4.1.2"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/webpack": {
+ "version": "5.83.1",
+ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.83.1.tgz",
+ "integrity": "sha512-TNsG9jDScbNuB+Lb/3+vYolPplCS3bbEaJf+Bj0Gw4DhP3ioAflBb1flcRt9zsWITyvOhM96wMQNRWlSX52DgA==",
+ "dev": true,
+ "dependencies": {
+ "@types/eslint-scope": "^3.7.3",
+ "@types/estree": "^1.0.0",
+ "@webassemblyjs/ast": "^1.11.5",
+ "@webassemblyjs/wasm-edit": "^1.11.5",
+ "@webassemblyjs/wasm-parser": "^1.11.5",
+ "acorn": "^8.7.1",
+ "acorn-import-assertions": "^1.7.6",
+ "browserslist": "^4.14.5",
+ "chrome-trace-event": "^1.0.2",
+ "enhanced-resolve": "^5.14.0",
+ "es-module-lexer": "^1.2.1",
+ "eslint-scope": "5.1.1",
+ "events": "^3.2.0",
+ "glob-to-regexp": "^0.4.1",
+ "graceful-fs": "^4.2.9",
+ "json-parse-even-better-errors": "^2.3.1",
+ "loader-runner": "^4.2.0",
+ "mime-types": "^2.1.27",
+ "neo-async": "^2.6.2",
+ "schema-utils": "^3.1.2",
+ "tapable": "^2.1.1",
+ "terser-webpack-plugin": "^5.3.7",
+ "watchpack": "^2.4.0",
+ "webpack-sources": "^3.2.3"
+ },
+ "bin": {
+ "webpack": "bin/webpack.js"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependenciesMeta": {
+ "webpack-cli": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/webpack-cli": {
+ "version": "4.10.0",
+ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz",
+ "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==",
+ "dev": true,
+ "dependencies": {
+ "@discoveryjs/json-ext": "^0.5.0",
+ "@webpack-cli/configtest": "^1.2.0",
+ "@webpack-cli/info": "^1.5.0",
+ "@webpack-cli/serve": "^1.7.0",
+ "colorette": "^2.0.14",
+ "commander": "^7.0.0",
+ "cross-spawn": "^7.0.3",
+ "fastest-levenshtein": "^1.0.12",
+ "import-local": "^3.0.2",
+ "interpret": "^2.2.0",
+ "rechoir": "^0.7.0",
+ "webpack-merge": "^5.7.3"
+ },
+ "bin": {
+ "webpack-cli": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "webpack": "4.x.x || 5.x.x"
+ },
+ "peerDependenciesMeta": {
+ "@webpack-cli/generators": {
+ "optional": true
+ },
+ "@webpack-cli/migrate": {
+ "optional": true
+ },
+ "webpack-bundle-analyzer": {
+ "optional": true
+ },
+ "webpack-dev-server": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/webpack-cli/node_modules/commander": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
+ "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/webpack-merge": {
+ "version": "5.8.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "clone-deep": "^4.0.1",
+ "wildcard": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/webpack-sources": {
+ "version": "1.4.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "source-list-map": "^2.0.0",
+ "source-map": "~0.6.1"
+ }
+ },
+ "node_modules/webpack-sources/node_modules/source-map": {
+ "version": "0.6.1",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/webpack/node_modules/acorn": {
+ "version": "8.8.2",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
+ "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==",
+ "dev": true,
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/webpack/node_modules/acorn-import-assertions": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz",
+ "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==",
+ "dev": true,
+ "peerDependencies": {
+ "acorn": "^8"
+ }
+ },
+ "node_modules/webpack/node_modules/schema-utils": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.2.tgz",
+ "integrity": "sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg==",
+ "dev": true,
+ "dependencies": {
+ "@types/json-schema": "^7.0.8",
+ "ajv": "^6.12.5",
+ "ajv-keywords": "^3.5.2"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/webpack/node_modules/webpack-sources": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz",
+ "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==",
+ "dev": true,
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/which-boxed-primitive": {
+ "version": "1.0.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-bigint": "^1.0.1",
+ "is-boolean-object": "^1.1.0",
+ "is-number-object": "^1.0.4",
+ "is-string": "^1.0.5",
+ "is-symbol": "^1.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-module": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/which-typed-array": {
+ "version": "1.1.9",
+ "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz",
+ "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==",
+ "dev": true,
+ "dependencies": {
+ "available-typed-arrays": "^1.0.5",
+ "call-bind": "^1.0.2",
+ "for-each": "^0.3.3",
+ "gopd": "^1.0.1",
+ "has-tostringtag": "^1.0.0",
+ "is-typed-array": "^1.1.10"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/wide-align": {
+ "version": "1.1.5",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^1.0.2 || 2 || 3 || 4"
+ }
+ },
+ "node_modules/wildcard": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.3",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/color-convert": {
+ "version": "2.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/color-name": {
+ "version": "1.1.4",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "license": "ISC"
+ },
+ "node_modules/write-file-atomic": {
+ "version": "4.0.2",
+ "dev": true,
+ "license": "ISC",
+ "peer": true,
+ "dependencies": {
+ "imurmurhash": "^0.1.4",
+ "signal-exit": "^3.0.7"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/y18n": {
+ "version": "4.0.3",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yallist": {
+ "version": "4.0.0",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yargs": {
+ "version": "13.3.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cliui": "^5.0.0",
+ "find-up": "^3.0.0",
+ "get-caller-file": "^2.0.1",
+ "require-directory": "^2.1.1",
+ "require-main-filename": "^2.0.0",
+ "set-blocking": "^2.0.0",
+ "string-width": "^3.0.0",
+ "which-module": "^2.0.0",
+ "y18n": "^4.0.0",
+ "yargs-parser": "^13.1.2"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "13.1.2",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "camelcase": "^5.0.0",
+ "decamelize": "^1.2.0"
+ }
+ },
+ "node_modules/yargs/node_modules/ansi-regex": {
+ "version": "4.1.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/yargs/node_modules/emoji-regex": {
+ "version": "7.0.3",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/yargs/node_modules/find-up": {
+ "version": "3.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/yargs/node_modules/is-fullwidth-code-point": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/yargs/node_modules/locate-path": {
+ "version": "3.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^3.0.0",
+ "path-exists": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/yargs/node_modules/p-locate": {
+ "version": "3.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/yargs/node_modules/path-exists": {
+ "version": "3.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/yargs/node_modules/string-width": {
+ "version": "3.1.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^7.0.1",
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/yargs/node_modules/strip-ansi": {
+ "version": "5.2.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/yup": {
+ "version": "0.32.11",
+ "resolved": "https://registry.npmjs.org/yup/-/yup-0.32.11.tgz",
+ "integrity": "sha512-Z2Fe1bn+eLstG8DRR6FTavGD+MeAwyfmouhHsIUgaADz8jvFKbO/fXc2trJKZg+5EBjh4gGm3iU/t3onKlXHIg==",
+ "dependencies": {
+ "@babel/runtime": "^7.15.4",
+ "@types/lodash": "^4.14.175",
+ "lodash": "^4.17.21",
+ "lodash-es": "^4.17.21",
+ "nanoclone": "^0.2.1",
+ "property-expr": "^2.0.4",
+ "toposort": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ }
+ }
+}
diff --git a/packages/bot-web-ui/package.json b/packages/bot-web-ui/package.json
index 4f8571e7241e..6456cc12b46b 100644
--- a/packages/bot-web-ui/package.json
+++ b/packages/bot-web-ui/package.json
@@ -15,15 +15,17 @@
]
},
"engines": {
- "node": "^16.16.0"
+ "node": "18.x"
},
"scripts": {
"start": "npm run test && npm run serve",
- "serve": "echo \"Serving...\" && webpack --progress --watch",
- "build": "f () { npm run build:skeleton && webpack --progress --env base=$1 ;}; f",
+ "serve": "echo \"Serving...\" && NODE_OPTIONS='-r ts-node/register' webpack --progress --watch",
+ "build": "f () { npm run build:skeleton && NODE_OPTIONS='-r ts-node/register' webpack --progress --env base=$1 ;}; f",
"build:skeleton": "lerna exec --scope @deriv/bot-skeleton -- npm run build",
"build:travis": "echo \"No build:travis specified\"",
- "test:eslint": "eslint \"./src/**/*.?(js|jsx|ts|tsx)\"",
+ "test:eslint": "eslint \"./src/**/*.?(js|jsx|ts|tsx|spec.ts|spec.js|spec.tsx|spec.tsx)\"",
+ "fix:eslint-all": "eslint --fix \"./src/**/*.?(js|jsx|ts|tsx|spec.ts|spec.js|spec.tsx|spec.tsx)\"",
+ "fix:eslint-one": "eslint --fix src/stores/data-collection-store.js",
"deploy": "echo \"No deploy specified\"",
"deploy:clean": "echo \"No deploy:clean specified\"",
"deploy:folder": "echo \"No deploy:folder specified\"",
@@ -50,6 +52,7 @@
"eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-react": "^7.22.0",
"eslint-plugin-react-hooks": "^4.2.0",
+ "eslint-plugin-simple-import-sort": "^10.0.0",
"lint-staged": "^10.4.0",
"loader-utils": "^1.1.0",
"mini-css-extract-plugin": "^1.3.4",
@@ -58,22 +61,25 @@
"sass-loader": "^12.6.0",
"sass-resources-loader": "^2.1.1",
"stylelint-webpack-plugin": "^2.1.1",
- "svg-sprite-loader": "^5.2.1",
+ "svg-sprite-loader": "^6.0.11",
"typescript": "^4.6.3",
- "webpack": "^5.46.0",
+ "webpack": "^5.81.0",
"webpack-cli": "^4.7.2"
},
"dependencies": {
+ "@datadog/browser-logs": "^4.36.0",
"@deriv/bot-skeleton": "^1.0.0",
"@deriv/components": "^1.0.0",
- "@deriv/deriv-charts": "1.1.8",
+ "@deriv/deriv-charts": "1.2.2",
"@deriv/shared": "^1.0.0",
+ "@deriv/stores": "^1.0.0",
"@deriv/translations": "^1.0.0",
"classnames": "^2.2.6",
"crc-32": "^1.2.0",
"formik": "^2.1.4",
"gh-pages": "^2.1.1",
"immutable": "^3.8.2",
+ "lodash.debounce": "^4.0.8",
"lz-string": "^1.4.4",
"mobx": "^6.6.1",
"mobx-react": "^7.5.1",
@@ -82,6 +88,8 @@
"react": "^17.0.2",
"react-content-loader": "^6.2.0",
"react-dom": "^17.0.2",
- "react-transition-group": "4.4.2"
+ "react-transition-group": "4.4.2",
+ "react-joyride": "^2.5.3",
+ "yup": "^0.32.11"
}
}
diff --git a/packages/bot-web-ui/src/app/app-content.jsx b/packages/bot-web-ui/src/app/app-content.jsx
new file mode 100644
index 000000000000..987e347c8d19
--- /dev/null
+++ b/packages/bot-web-ui/src/app/app-content.jsx
@@ -0,0 +1,101 @@
+import React from 'react';
+import { ApiHelpers, ServerTime, setColors } from '@deriv/bot-skeleton';
+import { Loading } from '@deriv/components';
+import { observer, useStore } from '@deriv/stores';
+import { Audio, BotNotificationMessages, Dashboard, NetworkToastPopup, RoutePromptDialog } from 'Components';
+import BotBuilder from 'Components/dashboard/bot-builder';
+import GTM from 'Utils/gtm';
+import { MobxContentProvider } from 'Stores/connect';
+import { useDBotStore } from 'Stores/useDBotStore';
+import BlocklyLoading from '../components/blockly-loading';
+import './app.scss';
+
+const AppContent = observer(() => {
+ const [is_loading, setIsLoading] = React.useState(true);
+ const RootStore = useStore();
+ const {
+ common,
+ client,
+ ui: { is_dark_mode_on },
+ } = RootStore;
+ const DBotStores = useDBotStore();
+ const { app } = DBotStores;
+ const { showDigitalOptionsMaltainvestError } = app;
+
+ // TODO: Remove this when connect is removed completely
+ const combinedStore = { ...DBotStores, core: { ...RootStore } };
+
+ //Do not remove this is for the bot-skeleton package to load blockly with the theme
+ React.useEffect(() => {
+ setColors(is_dark_mode_on);
+ }, [is_dark_mode_on]);
+
+ React.useEffect(() => {
+ /**
+ * Inject: External Script Hotjar - for DBot only
+ */
+ (function (h, o, t, j) {
+ /* eslint-disable */
+ h.hj =
+ h.hj ||
+ function () {
+ (h.hj.q = h.hj.q || []).push(arguments);
+ };
+ /* eslint-enable */
+ h._hjSettings = { hjid: 3050531, hjsv: 6 };
+ const a = o.getElementsByTagName('head')[0];
+ const r = o.createElement('script');
+ r.async = 1;
+ r.src = t + h._hjSettings.hjid + j + h._hjSettings.hjsv;
+ a.appendChild(r);
+ })(window, document, 'https://static.hotjar.com/c/hotjar-', '.js?sv=');
+ }, []);
+
+ React.useEffect(() => {
+ showDigitalOptionsMaltainvestError(client, common);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [client.is_options_blocked, client.account_settings.country_code]);
+
+ React.useEffect(() => {
+ GTM.init(combinedStore);
+ ServerTime.init(common);
+ app.setDBotEngineStores(combinedStore);
+ ApiHelpers.setInstance(app.api_helpers_store);
+ const { active_symbols } = ApiHelpers.instance;
+ setIsLoading(true);
+ active_symbols.retrieveActiveSymbols(true).then(() => {
+ setIsLoading(false);
+ });
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ React.useEffect(() => {
+ const onDisconnectFromNetwork = () => {
+ setIsLoading(false);
+ };
+ window.addEventListener('offline', onDisconnectFromNetwork);
+ return () => {
+ window.removeEventListener('offline', onDisconnectFromNetwork);
+ };
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ return is_loading ? (
+
+ ) : (
+ // TODO: remove MobxContentProvider when all connect method is removed
+
+
+
+
+ );
+});
+
+export default AppContent;
diff --git a/packages/bot-web-ui/src/app/app.js b/packages/bot-web-ui/src/app/app.js
index 3fd0f460fe76..833a994836d8 100644
--- a/packages/bot-web-ui/src/app/app.js
+++ b/packages/bot-web-ui/src/app/app.js
@@ -1,9 +1,9 @@
import React from 'react';
-import { makeLazyLoader, moduleLoader } from '@deriv/shared';
import { Loading } from '@deriv/components';
+import { makeLazyLoader, moduleLoader } from '@deriv/shared';
const Bot = makeLazyLoader(
- () => moduleLoader(() => import(/* webpackChunkName: "bot-web-ui-app", webpackPreload: true */ './app.jsx')),
+ () => moduleLoader(() => import(/* webpackChunkName: "bot-web-ui-app", webpackPreload: true */ './app.tsx')),
() =>
)();
diff --git a/packages/bot-web-ui/src/app/app.jsx b/packages/bot-web-ui/src/app/app.jsx
deleted file mode 100644
index 189409a83a2e..000000000000
--- a/packages/bot-web-ui/src/app/app.jsx
+++ /dev/null
@@ -1,124 +0,0 @@
-import '../public-path'; // Leave this here (at the top)! OK boss!
-import React from 'react'; // eslint-disable-line import/first
-import { Loading } from '@deriv/components';
-import { DBot, ServerTime, ApiHelpers } from '@deriv/bot-skeleton'; // eslint-disable-line import/first
-import {
- Audio,
- BlocklyLoading,
- BotFooterExtensions,
- BotNotificationMessages,
- MainContent,
- QuickStrategy,
- RunPanel,
- RoutePromptDialog,
- Toolbar,
- NetworkToastPopup,
-} from 'Components';
-import { LocalStore } from '@deriv/shared';
-import { MobxContentProvider } from 'Stores/connect';
-import RootStore from 'Stores';
-import GTM from 'Utils/gtm';
-import './app.scss';
-import Dashboard from 'Components/dashboard';
-import { setColors } from '../../../bot-skeleton/src/scratch/hooks/colours';
-
-const App = ({ passthrough }) => {
- const { root_store, WS } = passthrough;
- const [is_loading, setIsLoading] = React.useState(true);
- const dbot_dashboard_storage = LocalStore.get('show_dbot_dashboard');
- const show_dashboard = dbot_dashboard_storage !== undefined && dbot_dashboard_storage !== 'false';
- const root_store_instance = React.useRef(new RootStore(root_store, WS, DBot));
- const { app, common, core } = root_store_instance.current;
- const { onMount, onUnmount, showDigitalOptionsMaltainvestError } = app;
- const {
- ui: { is_dark_mode_on },
- } = root_store;
-
- React.useEffect(() => {
- setColors(is_dark_mode_on);
- }, []);
-
- React.useEffect(() => {
- /**
- * Inject: External Script Hotjar - for DBot only
- */
- (function (h, o, t, j) {
- /* eslint-disable */
- h.hj =
- h.hj ||
- function () {
- (h.hj.q = h.hj.q || []).push(arguments);
- };
- /* eslint-enable */
- h._hjSettings = { hjid: 3050531, hjsv: 6 };
- const a = o.getElementsByTagName('head')[0];
- const r = o.createElement('script');
- r.async = 1;
- r.src = t + h._hjSettings.hjid + j + h._hjSettings.hjsv;
- a.appendChild(r);
- })(window, document, 'https://static.hotjar.com/c/hotjar-', '.js?sv=');
- }, []);
-
- React.useEffect(() => {
- showDigitalOptionsMaltainvestError(core.client, common);
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [core.client.is_options_blocked, core.client.account_settings.country_code]);
-
- React.useEffect(() => {
- GTM.init(root_store_instance.current);
- ServerTime.init(common);
- app.setDBotEngineStores(root_store_instance.current);
- ApiHelpers.setInstance(app.api_helpers_store);
- const { active_symbols } = ApiHelpers.instance;
- setIsLoading(true);
- active_symbols.retrieveActiveSymbols(true).then(() => {
- setIsLoading(false);
- if (!show_dashboard) {
- onMount();
- }
- });
- return () => {
- if (!show_dashboard) {
- onUnmount();
- }
- };
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [onMount, onUnmount]);
-
- React.useEffect(() => {
- const onDisconnectFromNetwork = () => {
- setIsLoading(false);
- };
- window.addEventListener('offline', onDisconnectFromNetwork);
- return () => {
- window.removeEventListener('offline', onDisconnectFromNetwork);
- };
- }, []);
-
- return is_loading ? (
-
- ) : (
-
-
- {show_dashboard ? (
-
- ) : (
- <>
-
-
-
-
-
-
-
-
-
-
- >
- )}
-
-
- );
-};
-
-export default App;
diff --git a/packages/bot-web-ui/src/app/app.scss b/packages/bot-web-ui/src/app/app.scss
index 9ae85bf87eb8..69d3a1efdb22 100644
--- a/packages/bot-web-ui/src/app/app.scss
+++ b/packages/bot-web-ui/src/app/app.scss
@@ -5,4 +5,10 @@
--drawer-content-height-no-stat: calc(100vh - 233px);
--drawer-scroll-height: calc(100vh - 365px);
--drawer-content-height-mobile: calc(100% - 233px);
+ &-dashboard {
+ background: var(--general-section-1);
+ }
+
+ --tab-content-height: calc(100vh - 16.6rem);
+ --tab-content-height-mobile: calc(100vh - 12.6rem);
}
diff --git a/packages/bot-web-ui/src/app/app.tsx b/packages/bot-web-ui/src/app/app.tsx
new file mode 100644
index 000000000000..1d938f5c0caa
--- /dev/null
+++ b/packages/bot-web-ui/src/app/app.tsx
@@ -0,0 +1,49 @@
+import '../public-path'; // Leave this here (at the top)! OK boss!
+import React from 'react';
+import type { TRootStore, TWebSocket } from 'Types';
+import AppContent from './app-content';
+import DBotProviders from './dbot-providers';
+
+type TAppProps = {
+ passthrough: {
+ WS: TWebSocket;
+ root_store: TRootStore;
+ };
+};
+
+const App = ({ passthrough }: TAppProps) => {
+ const { root_store, WS } = passthrough;
+
+ React.useEffect(() => {
+ const initSurvicate = () => {
+ if (document.getElementById('dbot-survicate')) {
+ const survicate_box = document.getElementById('survicate-box') || undefined;
+ if (survicate_box) {
+ survicate_box.style.display = 'block';
+ }
+ return;
+ }
+ const script = document.createElement('script');
+ script.id = 'dbot-survicate';
+ script.async = true;
+ script.src = 'https://survey.survicate.com/workspaces/83b651f6b3eca1ab4551d95760fe5deb/web_surveys.js';
+ document.body.appendChild(script);
+ };
+
+ initSurvicate();
+ return () => {
+ const survicate_box = document.getElementById('survicate-box') || undefined;
+ if (survicate_box) {
+ survicate_box.style.display = 'none';
+ }
+ };
+ }, []);
+
+ return (
+
+
+
+ );
+};
+
+export default App;
diff --git a/packages/bot-web-ui/src/app/dbot-providers.tsx b/packages/bot-web-ui/src/app/dbot-providers.tsx
new file mode 100644
index 000000000000..501dbbf47bc7
--- /dev/null
+++ b/packages/bot-web-ui/src/app/dbot-providers.tsx
@@ -0,0 +1,14 @@
+import React from 'react';
+import { StoreProvider } from '@deriv/stores';
+import type { TRootStore, TWebSocket } from 'Types';
+import { DBotStoreProvider } from 'Stores/useDBotStore';
+
+const DBotProviders = ({ children, store, WS }: React.PropsWithChildren<{ store: TRootStore; WS: TWebSocket }>) => {
+ return (
+
+ {children}
+
+ );
+};
+
+export default DBotProviders;
diff --git a/packages/bot-web-ui/src/components/audio/audio.tsx b/packages/bot-web-ui/src/components/audio/audio.tsx
index 3282129581eb..37d6558f9639 100644
--- a/packages/bot-web-ui/src/components/audio/audio.tsx
+++ b/packages/bot-web-ui/src/components/audio/audio.tsx
@@ -1,6 +1,6 @@
import React from 'react';
-const Audio: React.FC = () => (
+const Audio = () => (
<>
diff --git a/packages/bot-web-ui/src/components/blockly-loading/blockly-loading.tsx b/packages/bot-web-ui/src/components/blockly-loading/blockly-loading.tsx
index d16488e6f993..c7338c44f42f 100644
--- a/packages/bot-web-ui/src/components/blockly-loading/blockly-loading.tsx
+++ b/packages/bot-web-ui/src/components/blockly-loading/blockly-loading.tsx
@@ -1,13 +1,13 @@
import React from 'react';
import { Loading } from '@deriv/components';
-import RootStore from 'Stores/index';
import { connect } from 'Stores/connect';
+import RootStore from 'Stores/index';
type TBlocklyLoadingProps = {
is_loading: boolean;
};
-const BlocklyLoading: React.FC = ({ is_loading }) => (
+const BlocklyLoading = ({ is_loading }: TBlocklyLoadingProps) => (
<>
{is_loading && (
diff --git a/packages/bot-web-ui/src/components/bot-footer-extensions/bot-footer-extensions.scss b/packages/bot-web-ui/src/components/bot-footer-extensions/bot-footer-extensions.scss
deleted file mode 100644
index 1209c3801dd5..000000000000
--- a/packages/bot-web-ui/src/components/bot-footer-extensions/bot-footer-extensions.scss
+++ /dev/null
@@ -1,18 +0,0 @@
-.bot-footer-extensions {
- display: inline;
-
- &__button {
- display: flex;
- align-items: center;
- font-size: var(--text-size-xs);
- padding: 0.8rem;
- cursor: pointer;
- height: 100%;
-
- &--active {
- border-bottom: 0.2rem solid var(--brand-red-coral);
- font-weight: 700;
- padding-bottom: 0.6rem;
- }
- }
-}
diff --git a/packages/bot-web-ui/src/components/bot-footer-extensions/bot-footer-extensions.tsx b/packages/bot-web-ui/src/components/bot-footer-extensions/bot-footer-extensions.tsx
deleted file mode 100644
index 6e1ba9324938..000000000000
--- a/packages/bot-web-ui/src/components/bot-footer-extensions/bot-footer-extensions.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-import React from 'react';
-import RootStore from 'Stores/index';
-import { connect } from 'Stores/connect';
-import SecurityAndPrivacy from './security-and-privacy';
-import FooterTabs from './footer-tabs';
-
-type TPopulateFooterExtensionsProps = {
- position: string;
- Component: React.FC;
- has_right_separator: boolean;
-};
-
-type TBotFooterExtentionsProps = {
- active_tab: string;
- populateFooterExtensions: (footer_extensions: TPopulateFooterExtensionsProps[]) => void;
- setActiveTab: (tab_title: string) => void;
-};
-
-const BotFooterExtensions = ({ active_tab, populateFooterExtensions, setActiveTab }: TBotFooterExtentionsProps) => {
- React.useEffect(() => populateFooter());
- React.useEffect(() => () => populateFooterExtensions([]), [populateFooterExtensions]);
-
- const populateFooter = () => {
- populateFooterExtensions([
- {
- position: 'left',
- Component: () =>
,
- has_right_separator: false,
- },
- {
- position: 'right',
- Component: SecurityAndPrivacy,
- has_right_separator: true,
- },
- ]);
- };
-
- return null;
-};
-
-export default connect(({ ui, main_content }: RootStore) => ({
- active_tab: main_content.active_tab,
- populateFooterExtensions: ui.populateFooterExtensions,
- setActiveTab: main_content.setActiveTab,
-}))(BotFooterExtensions);
diff --git a/packages/bot-web-ui/src/components/bot-footer-extensions/footer-tabs.tsx b/packages/bot-web-ui/src/components/bot-footer-extensions/footer-tabs.tsx
deleted file mode 100644
index 476278e4ffd5..000000000000
--- a/packages/bot-web-ui/src/components/bot-footer-extensions/footer-tabs.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import React from 'react';
-import classNames from 'classnames';
-import { Localize } from '@deriv/translations';
-import { tabs_title } from 'Constants/bot-contents';
-
-type TFooterTabsProps = {
- active_tab: string;
- setActiveTab: (tab_title: string) => void;
-};
-
-const FooterTabs = ({ active_tab, setActiveTab }: TFooterTabsProps) => (
-
- {Object.keys(tabs_title).map(key => {
- const tab_title = tabs_title[key];
- return (
- setActiveTab(tab_title)}
- >
- {tab_title === tabs_title.WORKSPACE && }
- {tab_title === tabs_title.CHART && }
-
- );
- })}
-
-);
-
-export default FooterTabs;
diff --git a/packages/bot-web-ui/src/components/bot-footer-extensions/index.ts b/packages/bot-web-ui/src/components/bot-footer-extensions/index.ts
deleted file mode 100644
index 1fcf957b9a6f..000000000000
--- a/packages/bot-web-ui/src/components/bot-footer-extensions/index.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-import BotFooterExtensions from './bot-footer-extensions';
-import './bot-footer-extensions.scss';
-
-export default BotFooterExtensions;
diff --git a/packages/bot-web-ui/src/components/bot-footer-extensions/security-and-privacy.tsx b/packages/bot-web-ui/src/components/bot-footer-extensions/security-and-privacy.tsx
deleted file mode 100644
index eb46b22548f0..000000000000
--- a/packages/bot-web-ui/src/components/bot-footer-extensions/security-and-privacy.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import React from 'react';
-import { Icon, Popover, StaticUrl } from '@deriv/components';
-import { localize } from '@deriv/translations';
-
-const SecurityAndPrivacy: React.FC = () => (
-
-
-
-
-
-);
-
-export default SecurityAndPrivacy;
diff --git a/packages/bot-web-ui/src/components/bot-notification-messages/bot-notification-messages.scss b/packages/bot-web-ui/src/components/bot-notification-messages/bot-notification-messages.scss
index 54d42cb74614..4bafadfe37bb 100644
--- a/packages/bot-web-ui/src/components/bot-notification-messages/bot-notification-messages.scss
+++ b/packages/bot-web-ui/src/components/bot-notification-messages/bot-notification-messages.scss
@@ -1,16 +1,21 @@
.notifications-container {
position: absolute;
- top: 65px;
+ top: 6.5rem;
left: 0;
- width: calc(100vw - 30px);
- height: calc(100% - 65px);
+ width: calc(100vw - 1.6rem);
+ height: calc(100% - 6.5rem);
.notification-messages {
transition: transform 0.3s ease, opacity 0.25s linear;
}
- &--is-panel-open {
+ &__dashboard {
.notification-messages {
- transform: translate3d(calc(-350px), 0, 0);
+ transform: translate3d(calc(-42.7rem), 0, 0);
+ }
+ }
+ &--panel-open {
+ .notification-messages {
+ transform: translate3d(calc(-36.5rem), 0, 0);
}
}
}
diff --git a/packages/bot-web-ui/src/components/bot-notification-messages/bot-notification-messages.tsx b/packages/bot-web-ui/src/components/bot-notification-messages/bot-notification-messages.tsx
index 219eb24d6a78..a61931436508 100644
--- a/packages/bot-web-ui/src/components/bot-notification-messages/bot-notification-messages.tsx
+++ b/packages/bot-web-ui/src/components/bot-notification-messages/bot-notification-messages.tsx
@@ -1,24 +1,37 @@
import React from 'react';
import classNames from 'classnames';
-import RootStore from 'Stores/index';
+import { DBOT_TABS } from 'Constants/bot-contents';
import { connect } from 'Stores/connect';
+import RootStore from 'Stores/index';
interface TBotNotificationMessagesProps {
is_drawer_open: boolean;
+ active_tab: number;
+ is_info_panel_visible: boolean;
Notifications: React.ComponentType;
}
-const BotNotificationMessages = ({ is_drawer_open, Notifications }: TBotNotificationMessagesProps) => (
+const { BOT_BUILDER, CHART } = DBOT_TABS;
+
+const BotNotificationMessages = ({
+ is_drawer_open,
+ Notifications,
+ is_info_panel_visible,
+ active_tab,
+}: TBotNotificationMessagesProps) => (
);
-export default connect(({ core, run_panel }: RootStore) => ({
+export default connect(({ core, run_panel, dashboard }: RootStore) => ({
is_drawer_open: run_panel.is_drawer_open,
Notifications: core.ui.notification_messages_ui,
+ active_tab: dashboard.active_tab,
+ is_info_panel_visible: dashboard.is_info_panel_visible,
}))(BotNotificationMessages);
diff --git a/packages/bot-web-ui/src/components/chart/chart.scss b/packages/bot-web-ui/src/components/chart/chart.scss
index 212a3b62c3c0..d8e82d0d8bea 100644
--- a/packages/bot-web-ui/src/components/chart/chart.scss
+++ b/packages/bot-web-ui/src/components/chart/chart.scss
@@ -26,4 +26,117 @@
position: absolute;
}
}
+
+ .ciq-chart {
+ .cq-top-ui-widgets,
+ & .info-box {
+ transition: transform 0.25s ease;
+
+ .cq-symbols-display {
+ z-index: 1;
+
+ &.ciq-disabled {
+ display: none;
+ }
+ @include mobile {
+ min-width: 17rem;
+ max-width: 26rem;
+ width: auto;
+
+ .cq-menu-btn {
+ padding: 0.2rem;
+ }
+ .cq-symbol-select-btn {
+ margin: 0.2rem;
+
+ .cq-symbol-dropdown {
+ transform: scale(1);
+ margin-left: auto;
+ }
+ .cq-symbol {
+ font-size: 1.2rem;
+ }
+ .cq-chart-price {
+ display: none;
+ }
+ }
+ }
+ }
+ }
+
+ .cq-top-ui-widgets {
+ align-items: center;
+ display: flex;
+ flex-wrap: wrap;
+ position: absolute;
+ top: 0;
+ width: 100%;
+ }
+
+ .sc-toolbar-widget {
+ transition: transform 0.25s ease;
+
+ @include mobile {
+ background: transparent;
+ border-width: 0;
+ bottom: 20.8vh;
+
+ .sc-chart-mode,
+ .sc-studies {
+ background: var(--general-section-1);
+ padding: 0.4rem 0.2rem;
+ width: 4rem;
+ height: 4rem;
+ display: flex;
+ border-radius: 50%;
+ justify-content: center;
+ align-items: center;
+ margin: 0.8rem;
+ opacity: 0.75;
+
+ &__menu {
+ &__timeperiod {
+ top: 0.8rem;
+ left: 0.8rem;
+ }
+ & > .ic-icon {
+ top: 0.6rem;
+ }
+ }
+ }
+ }
+ .app-contents .ciq-menu {
+ margin: 0;
+ }
+ }
+ }
+
+ .smartcharts-mobile {
+ .cq-context {
+ z-index: 99;
+ .sc-mcd__category:last-child {
+ margin-bottom: 3rem !important;
+ }
+ }
+
+ & .cq-chart-title .cq-menu-dropdown {
+ position: fixed;
+ height: 100% !important;
+ .sc-dialog {
+ height: 100% !important;
+ &__body {
+ height: inherit !important;
+ .sc-mcd {
+ height: inherit !important;
+ min-width: auto !important;
+ }
+ }
+ }
+ }
+ }
+}
+
+.cq-modal-dropdown.stxMenuActive {
+ left: 0;
+ top: 0;
}
diff --git a/packages/bot-web-ui/src/components/chart/chart.tsx b/packages/bot-web-ui/src/components/chart/chart.tsx
index 3274174c01b3..954002992b9a 100644
--- a/packages/bot-web-ui/src/components/chart/chart.tsx
+++ b/packages/bot-web-ui/src/components/chart/chart.tsx
@@ -1,16 +1,20 @@
import React from 'react';
+import classNames from 'classnames';
+import { ActiveSymbols, ForgetRequest } from '@deriv/api-types';
// TODO Remove this after smartcharts is replaced
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import { ChartTitle, SmartChart } from '@deriv/deriv-charts';
-import RootStore from 'Stores/index';
+import { isDesktop, isMobile } from '@deriv/shared';
import { connect } from 'Stores/connect';
+import RootStore from 'Stores/index';
import ToolbarWidgets from './toolbar-widgets';
interface TChartProps {
chart_type: string;
+ getMarketsOrder: (active_symbols: ActiveSymbols) => string[];
granularity: number;
- is_mobile: boolean;
+ is_drawer_open: boolean;
is_socket_opened: boolean;
onSymbolChange: (symbol: string) => void;
setChartStatus: (status: boolean) => void;
@@ -19,17 +23,17 @@ interface TChartProps {
symbol: object;
updateChartType: (chart_type: string) => void;
updateGranularity: (granularity: number) => void;
- wsForget: (request) => void;
+ wsForget: (request: ForgetRequest) => void;
wsForgetStream: (stream_id: string) => void;
- wsSendRequest: (req) => void;
- wsSubscribe: (req, callback) => void;
- getMarketsOrder: (active_symbols) => void;
+ wsSendRequest: (req: { [k: string]: unknown }) => void;
+ wsSubscribe: (req: { [k: string]: unknown }, callback: () => void) => void;
}
const Chart = ({
chart_type,
+ getMarketsOrder,
granularity,
- is_mobile,
+ is_drawer_open,
is_socket_opened,
onSymbolChange,
setChartStatus,
@@ -42,44 +46,53 @@ const Chart = ({
wsForgetStream,
wsSendRequest,
wsSubscribe,
- getMarketsOrder,
}: TChartProps) => {
- const barriers = [];
+ const barriers: [] = [];
return (
-
setChartStatus(!v)}
- toolbarWidget={() => (
-
- )}
- chartType={chart_type}
- isMobile={is_mobile}
- granularity={granularity}
- requestAPI={wsSendRequest}
- requestForget={wsForget}
- requestForgetStream={wsForgetStream}
- requestSubscribe={wsSubscribe}
- settings={settings}
- symbol={symbol}
- topWidgets={() => }
- isConnectionOpened={is_socket_opened}
- getMarketsOrder={getMarketsOrder}
- />
+
+ setChartStatus(!v)}
+ toolbarWidget={() => (
+
+ )}
+ chartType={chart_type}
+ isMobile={isMobile()}
+ enabledNavigationWidget={isDesktop()}
+ granularity={granularity}
+ requestAPI={wsSendRequest}
+ requestForget={wsForget}
+ requestForgetStream={wsForgetStream}
+ requestSubscribe={wsSubscribe}
+ settings={settings}
+ symbol={symbol}
+ topWidgets={() => }
+ isConnectionOpened={is_socket_opened}
+ getMarketsOrder={getMarketsOrder}
+ />
+
);
};
-export default connect(({ chart_store, common, ui }: RootStore) => ({
- is_mobile: ui.is_mobile,
- is_socket_opened: common.is_socket_opened,
- updateChartType: chart_store.updateChartType,
- updateGranularity: chart_store.updateGranularity,
- granularity: chart_store.granularity,
+export default connect(({ chart_store, common, ui, run_panel }: RootStore) => ({
chart_type: chart_store.chart_type,
+ getMarketsOrder: chart_store.getMarketsOrder,
+ granularity: chart_store.granularity,
+ last_contract: {
+ is_digit_contract: false,
+ },
+ is_drawer_open: run_panel.is_drawer_open,
+ is_socket_opened: common.is_socket_opened,
onSymbolChange: chart_store.onSymbolChange,
+ setChartStatus: chart_store.setChartStatus,
settings: {
assetInformation: false, // ui.is_chart_asset_info_visible,
countdown: true,
@@ -88,14 +101,11 @@ export default connect(({ chart_store, common, ui }: RootStore) => ({
position: ui.is_chart_layout_default ? 'bottom' : 'left',
theme: ui.is_dark_mode_on ? 'dark' : 'light',
},
- last_contract: {
- is_digit_contract: false,
- },
symbol: chart_store.symbol,
- setChartStatus: chart_store.setChartStatus,
+ updateChartType: chart_store.updateChartType,
+ updateGranularity: chart_store.updateGranularity,
wsForget: chart_store.wsForget,
wsForgetStream: chart_store.wsForgetStream,
wsSendRequest: chart_store.wsSendRequest,
wsSubscribe: chart_store.wsSubscribe,
- getMarketsOrder: chart_store.getMarketsOrder,
}))(Chart);
diff --git a/packages/bot-web-ui/src/components/chart/toolbar-widgets.tsx b/packages/bot-web-ui/src/components/chart/toolbar-widgets.tsx
index 96225e25b490..b3fe6cc97879 100644
--- a/packages/bot-web-ui/src/components/chart/toolbar-widgets.tsx
+++ b/packages/bot-web-ui/src/components/chart/toolbar-widgets.tsx
@@ -1,5 +1,6 @@
import React from 'react';
-import { ToolbarWidget, ChartMode, DrawTools, Share, StudyLegend, Views } from '@deriv/deriv-charts';
+import { ChartMode, DrawTools, Share, StudyLegend, ToolbarWidget, Views } from '@deriv/deriv-charts';
+import { isDesktop, isMobile } from '@deriv/shared';
type TToolbarWidgetsProps = {
updateChartType: (chart_type: string) => void;
@@ -7,12 +8,16 @@ type TToolbarWidgetsProps = {
};
const ToolbarWidgets = ({ updateChartType, updateGranularity }: TToolbarWidgetsProps) => (
-
+
-
-
-
-
+ {isDesktop() && (
+ <>
+
+
+
+
+ >
+ )}
);
diff --git a/packages/bot-web-ui/src/components/dashboard/bot-builder/bot-builder.tsx b/packages/bot-web-ui/src/components/dashboard/bot-builder/bot-builder.tsx
new file mode 100644
index 000000000000..2c67df5bcdd9
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/bot-builder/bot-builder.tsx
@@ -0,0 +1,103 @@
+import React from 'react';
+import classNames from 'classnames';
+import { DesktopWrapper, MobileWrapper } from '@deriv/components';
+import LoadModal from 'Components/load-modal';
+import SaveModal from 'Components/save-modal';
+import AppStore from 'Stores/app-store';
+import { connect } from 'Stores/connect';
+import RootStore from 'Stores/index';
+import { BOT_BUILDER_TOUR } from '../joyride-config';
+import QuickStrategy from '../quick-strategy';
+import ReactJoyrideWrapper from '../react-joyride-wrapper';
+import TourSlider from '../tour-slider';
+import WorkspaceWrapper from './workspace-wrapper';
+
+type TBotBuilder = {
+ app: AppStore;
+ active_tab: number;
+ has_started_onboarding_tour: boolean;
+ has_started_bot_builder_tour: boolean;
+ is_preview_on_popup: boolean;
+ is_dark_mode_on: boolean;
+ setOnBoardTourRunState: (has_started_onboarding_tour: boolean) => boolean;
+ loadFileFromRecent: () => void;
+ selected_strategy_id: string;
+ previewRecentStrategy: (selected_strategy_id: string) => void;
+};
+
+const BotBuilder = ({
+ app,
+ active_tab,
+ has_started_onboarding_tour,
+ has_started_bot_builder_tour,
+ is_preview_on_popup,
+}: TBotBuilder) => {
+ const [is_tour_running] = React.useState(true);
+ const { onMount, onUnmount } = app;
+ const el_ref = React.useRef(null);
+
+ React.useEffect(() => {
+ onMount();
+ return () => onUnmount();
+ }, []);
+
+ return (
+ <>
+
+ {is_preview_on_popup ? null : (
+
+
+
+ )}
+
+ {has_started_bot_builder_tour && active_tab === 1 && !has_started_onboarding_tour && (
+ <>
+
+
+
+
+
+
+ >
+ )}
+ {/* removed this outside from toolbar becuase it needs to loaded seperately without dependency */}
+
+
+
+ >
+ );
+};
+
+export default connect(({ app, dashboard }: RootStore) => ({
+ app,
+ active_tab: dashboard.active_tab,
+ has_started_onboarding_tour: dashboard.has_started_onboarding_tour,
+ has_started_bot_builder_tour: dashboard.has_started_bot_builder_tour,
+ is_preview_on_popup: dashboard.is_preview_on_popup,
+ setOnBoardTourRunState: dashboard.setOnBoardTourRunState,
+}))(BotBuilder);
diff --git a/packages/bot-web-ui/src/components/dashboard/bot-builder/index.ts b/packages/bot-web-ui/src/components/dashboard/bot-builder/index.ts
new file mode 100644
index 000000000000..1c8f93b29e84
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/bot-builder/index.ts
@@ -0,0 +1,4 @@
+import BotBuilder from './bot-builder';
+import './workspace.scss';
+
+export default BotBuilder;
diff --git a/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/__tests__/toolbar.spec.tsx b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/__tests__/toolbar.spec.tsx
new file mode 100644
index 000000000000..1e8d30f85574
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/__tests__/toolbar.spec.tsx
@@ -0,0 +1,66 @@
+import React from 'react';
+import { isDesktop, isMobile } from '@deriv/shared';
+import { render, screen } from '@testing-library/react';
+import Toolbar from '..';
+
+jest.mock('Stores/connect', () => ({
+ __esModule: true,
+ default: 'mockedDefaultExport',
+ connect:
+ () =>
+ (Component: T) =>
+ Component,
+}));
+
+jest.mock('@deriv/shared', () => ({
+ ...jest.requireActual('@deriv/shared'),
+ isMobile: jest.fn(() => false),
+ isDesktop: jest.fn(() => true),
+}));
+
+describe('Toolbar component', () => {
+ const mocked_props = {
+ active_tab: '0',
+ file_name: 'qwe',
+ has_redo_stack: false,
+ has_undo_stack: false,
+ is_drawer_open: false,
+ is_stop_button_disabled: false,
+ is_stop_button_visible: false,
+ closeResetDialog: jest.fn(),
+ onOkButtonClick: jest.fn(),
+ onResetClick: jest.fn(),
+ onRunButtonClick: jest.fn(),
+ onSortClick: jest.fn(),
+ onUndoClick: jest.fn(),
+ onZoomInOutClick: jest.fn(),
+ toggleSaveLoadModal: jest.fn(),
+ toggleLoadModal: jest.fn(),
+ toggleSaveModal: jest.fn(),
+ };
+
+ beforeEach(() => {
+ isDesktop.mockReturnValue(true);
+ isMobile.mockReturnValue(false);
+ jest.clearAllMocks();
+ });
+
+ it('should render Toolbar', () => {
+ render( );
+ expect(screen.getByTestId('dashboard__toolbar')).toBeInTheDocument();
+ });
+
+ it('Toolbar should renders a modal window, when the bot is running and dialog is open', () => {
+ render( );
+ expect(screen.getByTestId('dashboard__toolbar')).toBeInTheDocument();
+ expect(screen.getByRole('dialog')).toBeInTheDocument();
+ expect(screen.getByTestId('toolbar__dialog-text--second')).toBeInTheDocument();
+ });
+
+ it('Toolbar should renders a button, when it is mobile version', async () => {
+ isDesktop.mockReturnValue(false);
+ isMobile.mockReturnValue(true);
+ render( );
+ expect(await screen.findByRole('button')).toBeInTheDocument();
+ });
+});
diff --git a/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/icon-button.tsx b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/icon-button.tsx
new file mode 100644
index 000000000000..0800bcf6aaed
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/icon-button.tsx
@@ -0,0 +1,32 @@
+import React from 'react';
+import { Icon, Popover } from '@deriv/components';
+import { popover_zindex } from 'Constants/z-indexes';
+
+type TIconButton = {
+ icon: string;
+ icon_id: string;
+ iconOnClick: () => void;
+ icon_color?: string;
+ popover_message: string;
+};
+
+const IconButton = ({ popover_message, icon, icon_id, icon_color, iconOnClick }: TIconButton) => {
+ return (
+
+
+
+ );
+};
+
+export default IconButton;
diff --git a/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/index.ts b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/index.ts
new file mode 100644
index 000000000000..9f5a043b3f84
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/index.ts
@@ -0,0 +1,4 @@
+import Toolbar from './toolbar';
+import './toolbar.scss';
+
+export default Toolbar;
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
new file mode 100644
index 000000000000..bef7c154a4d6
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/run-strategy.tsx
@@ -0,0 +1,10 @@
+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-button.tsx b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/toolbar-button.tsx
new file mode 100644
index 000000000000..669512f94d11
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/toolbar-button.tsx
@@ -0,0 +1,28 @@
+import React from 'react';
+import { Button, Popover } from '@deriv/components';
+
+type TToolbarButton = {
+ popover_message?: string;
+ button_id: string;
+ button_classname: string;
+ buttonOnClick: () => void;
+ icon?: React.ReactElement;
+ button_text: string;
+};
+
+const ToolbarButton = ({
+ popover_message,
+ button_id,
+ button_classname,
+ buttonOnClick,
+ icon,
+ button_text,
+}: TToolbarButton) => (
+
+
+ {button_text}
+
+
+);
+
+export default ToolbarButton;
diff --git a/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/toolbar-icon.tsx b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/toolbar-icon.tsx
new file mode 100644
index 000000000000..0feeb407332e
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/toolbar-icon.tsx
@@ -0,0 +1,41 @@
+import React from 'react';
+import { Icon, Popover } from '@deriv/components';
+import { isMobile } from '@deriv/shared';
+import { popover_zindex } from 'Constants/z-indexes';
+
+type TToolbarIcon = {
+ popover_message: string;
+ icon: string;
+ icon_id: string;
+ action: () => void;
+ icon_color?: string;
+};
+
+const ToolbarIcon = ({ popover_message, icon, icon_id, icon_color, action }: TToolbarIcon) => {
+ const renderIcon = () => (
+
+ );
+
+ if (isMobile()) {
+ return renderIcon();
+ }
+
+ return (
+
+ {renderIcon()}
+
+ );
+};
+
+export default ToolbarIcon;
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
new file mode 100644
index 000000000000..52359c2b4d4d
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/toolbar.scss
@@ -0,0 +1,118 @@
+.toolbar {
+ height: 5.6rem;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0.5rem 0.6rem;
+ position: absolute;
+ top: -4rem;
+ left: 23.2rem;
+ background-color: var(--general-main-1);
+ @include mobile {
+ left: 1.4rem;
+ overflow-x: auto;
+ overflow-y: hidden;
+ width: 100%;
+
+ &::-webkit-scrollbar {
+ display: none;
+ }
+
+ -ms-overflow-style: none;
+ scrollbar-width: none;
+ }
+ z-index: 1;
+ &__btn {
+ background-color: var(--button-primary-default) !important;
+
+ &--icon {
+ display: flex;
+ justify-content: center;
+ height: calc(5.6rem - 1.6rem) !important;
+ @include desktop {
+ width: 22rem;
+ margin-bottom: 1rem;
+ }
+ @include mobile {
+ margin-bottom: 2rem;
+ }
+
+ & .dc-btn__icon {
+ padding-right: 0.4rem;
+ }
+ > * {
+ align-self: center;
+ }
+ &-text {
+ @include typeface(--title-center-bold-active, none);
+ }
+ }
+ }
+ &__section {
+ display: flex;
+
+ @include mobile {
+ margin-top: 2rem;
+ }
+
+ > * {
+ @include desktop {
+ align-self: center;
+ }
+ }
+ }
+ &__icon {
+ cursor: pointer;
+ border: none;
+ margin: auto 1.2rem;
+ height: 1.6rem;
+ width: 1.6rem;
+ fill: var(--text-prominent);
+ }
+ &__group {
+ display: flex;
+ border-radius: $BORDER_RADIUS;
+ border: solid 1px var(--border-normal);
+ height: 4rem;
+ &-btn {
+ padding: 0 1.2rem;
+ height: 4rem;
+ @include mobile {
+ padding: 0;
+ .dc-popover {
+ &__target {
+ width: 4rem;
+ }
+ }
+ }
+ > * {
+ align-self: center;
+ }
+ }
+ }
+ &__animation {
+ margin-right: 0.5rem;
+ max-width: 35rem;
+ }
+ &__dialog {
+ &-text--second {
+ margin-top: 2.4rem;
+ }
+ }
+}
+
+@keyframes spin {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+}
+
+.vertical-divider {
+ width: 0.1rem;
+ height: 1.7rem;
+ margin: 0.8rem;
+ background-color: var(--border-normal);
+}
diff --git a/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/toolbar.tsx b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/toolbar.tsx
new file mode 100644
index 000000000000..baeeb6daf0fb
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/toolbar.tsx
@@ -0,0 +1,105 @@
+import React from 'react';
+import { Dialog } from '@deriv/components';
+import { isMobile } from '@deriv/shared';
+import { Localize, localize } from '@deriv/translations';
+import { connect } from 'Stores/connect';
+import RootStore from 'Stores/index';
+import ToolbarButton from './toolbar-button';
+import WorkspaceGroup from './workspace-group';
+
+type TToolbar = {
+ active_tab: string;
+ file_name: string;
+ has_redo_stack: boolean;
+ has_undo_stack: boolean;
+ is_dialog_open: boolean;
+ is_drawer_open: boolean;
+ is_running: boolean;
+ is_stop_button_disabled: boolean;
+ is_stop_button_visible: boolean;
+ closeResetDialog: () => void;
+ onOkButtonClick: () => void;
+ onResetClick: () => void;
+ onRunButtonClick: () => void;
+ onSortClick: () => void;
+ onUndoClick: () => void;
+ onZoomInOutClick: () => void;
+ toggleSaveLoadModal: () => void;
+ toggleLoadModal: () => void;
+ toggleSaveModal: () => void;
+ loadDataStrategy: () => void;
+};
+
+const Toolbar = (props: TToolbar) => {
+ const { is_running, is_dialog_open, onOkButtonClick, closeResetDialog } = props;
+ const confirm_button_text = is_running ? localize('Yes') : localize('OK');
+ const cancel_button_text = is_running ? localize('No') : localize('Cancel');
+
+ return (
+
+
+
+ {isMobile() && (
+
+ )}
+
+
+
+
+ {is_running ? (
+ ,
+ ]}
+ />
+ ) : (
+ localize('Any unsaved changes will be lost.')
+ )}
+
+
+ );
+};
+
+export default connect(({ blockly_store, run_panel, save_modal, load_modal, toolbar, quick_strategy }: RootStore) => ({
+ active_tab: blockly_store.active_tab,
+ file_name: toolbar.file_name,
+ has_redo_stack: toolbar.has_redo_stack,
+ has_undo_stack: toolbar.has_undo_stack,
+ is_dialog_open: toolbar.is_dialog_open,
+ is_drawer_open: run_panel.is_drawer_open,
+ is_running: run_panel.is_running,
+ is_stop_button_disabled: run_panel.is_stop_button_disabled,
+ is_stop_button_visible: run_panel.is_stop_button_visible,
+ closeResetDialog: toolbar.closeResetDialog,
+ onOkButtonClick: toolbar.onResetOkButtonClick,
+ onResetClick: toolbar.onResetClick,
+ onRunButtonClick: run_panel.onRunButtonClick,
+ onSortClick: toolbar.onSortClick,
+ onUndoClick: toolbar.onUndoClick,
+ onZoomInOutClick: toolbar.onZoomInOutClick,
+ toggleLoadModal: load_modal.toggleLoadModal,
+ toggleSaveModal: save_modal.toggleSaveModal,
+ loadDataStrategy: quick_strategy.loadDataStrategy,
+}))(Toolbar);
diff --git a/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/workspace-group.tsx b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/workspace-group.tsx
new file mode 100644
index 000000000000..8c60785c5a99
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/workspace-group.tsx
@@ -0,0 +1,90 @@
+import React from 'react';
+import { localize } from '@deriv/translations';
+import { connect } from 'Stores/connect';
+import RootStore from 'Stores/index';
+import ToolbarIcon from './toolbar-icon';
+
+type TWorkspaceGroup = {
+ has_redo_stack: boolean;
+ has_undo_stack: boolean;
+ onResetClick: () => void;
+ onSortClick: () => void;
+ onUndoClick: (param?: boolean) => void;
+ onZoomInOutClick: (param?: boolean) => void;
+ setPreviewOnPopup: (param: boolean) => void;
+ toggleLoadModal: () => void;
+ toggleSaveModal: () => void;
+};
+
+const WorkspaceGroup = ({
+ has_redo_stack,
+ has_undo_stack,
+ onResetClick,
+ onSortClick,
+ onUndoClick,
+ onZoomInOutClick,
+ setPreviewOnPopup,
+ toggleLoadModal,
+ toggleSaveModal,
+}: TWorkspaceGroup) => (
+
+
+
{
+ setPreviewOnPopup(true);
+ toggleLoadModal();
+ }}
+ />
+
+
+ onUndoClick(/* redo */ false)}
+ />
+ onUndoClick(/* redo */ true)}
+ />
+
+ onZoomInOutClick(/* in */ true)}
+ />
+ onZoomInOutClick(/* in */ false)}
+ />
+
+);
+
+export default connect(({ dashboard }: RootStore) => ({
+ setPreviewOnPopup: dashboard.setPreviewOnPopup,
+}))(WorkspaceGroup);
diff --git a/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbox/__tests__/toolbox.spec.tsx b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbox/__tests__/toolbox.spec.tsx
new file mode 100644
index 000000000000..1799cd6428f4
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbox/__tests__/toolbox.spec.tsx
@@ -0,0 +1,53 @@
+import React from 'react';
+import { isMobile } from '@deriv/shared';
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import Toolbox from '../toolbox';
+
+jest.mock('Stores/connect', () => ({
+ __esModule: true,
+ default: 'mockedDefaultExport',
+ connect:
+ () =>
+ (Component: T) =>
+ Component,
+}));
+
+describe('Toolbox component', () => {
+ const mocked_props = {
+ hasSubCategory: jest.fn(),
+ is_search_loading: false,
+ onMount: jest.fn(),
+ onSearch: jest.fn(),
+ onSearchBlur: jest.fn(),
+ onSearchClear: jest.fn(),
+ onSearchKeyUp: jest.fn(),
+ onToolboxItemClick: jest.fn(),
+ onToolboxItemExpand: jest.fn(),
+ onUnmount: jest.fn(),
+ setVisibility: jest.fn(),
+ sub_category_index: [],
+ loadDataStrategy: jest.fn(),
+ };
+
+ it('should render Toolbox with content wrapper is open', () => {
+ render( );
+ expect(screen.getByTestId('dashboard__toolbox')).toBeInTheDocument();
+ expect(screen.getByTestId('db-toolbox__content-wrapper')).toHaveClass('db-toolbox__content-wrapper active');
+ });
+ it('should render Toolbox with content wrapper is open', () => {
+ const setVisibility = jest.fn();
+ render( );
+ expect(screen.getByTestId('db-toolbox__title')).toBeInTheDocument();
+
+ userEvent.click(screen.getByTestId('db-toolbox__title'));
+ expect(screen.getByTestId('db-toolbox__content-wrapper')).not.toHaveClass('db-toolbox__content-wrapper active');
+ expect(setVisibility).toHaveBeenCalled();
+ });
+ it('should not render Toolbox if it is mobile version', () => {
+ render( );
+ if (isMobile()) {
+ expect(screen.getByRole('dashboard__toolbox')).toBeEmptyDOMElement();
+ }
+ });
+});
diff --git a/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbox/index.ts b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbox/index.ts
new file mode 100644
index 000000000000..c708aa71bdd3
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbox/index.ts
@@ -0,0 +1,4 @@
+import Toolbox from './toolbox';
+import './toolbox.scss';
+
+export default Toolbox;
diff --git a/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbox/search-box/index.ts b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbox/search-box/index.ts
new file mode 100644
index 000000000000..6021d02fa09f
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbox/search-box/index.ts
@@ -0,0 +1,3 @@
+import SearchBox from './search-box';
+
+export default SearchBox;
diff --git a/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbox/search-box/search-box.tsx b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbox/search-box/search-box.tsx
new file mode 100644
index 000000000000..974516a4a022
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbox/search-box/search-box.tsx
@@ -0,0 +1,49 @@
+import React from 'react';
+import { Field as FormField, FieldProps, Form, Formik } from 'formik';
+import { Input } from '@deriv/components';
+import { localize } from '@deriv/translations';
+import SearchIcon from './search-icon';
+
+type TSearchBox = {
+ is_search_loading: boolean;
+ onSearch: () => void;
+ onSearchBlur: () => void;
+ onSearchClear: (param: (field: string, value: number | string, shouldValidate?: boolean) => void) => void;
+ onSearchKeyUp: (param: () => void) => void;
+};
+
+type TFormValues = { [key: string]: string };
+
+const SearchBox = ({ is_search_loading, onSearch, onSearchBlur, onSearchClear, onSearchKeyUp }: TSearchBox) => (
+
+
+ {({ submitForm, values: { search }, setFieldValue }) => (
+
+ )}
+
+
+);
+
+export default SearchBox;
diff --git a/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbox/search-box/search-icon.tsx b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbox/search-box/search-icon.tsx
new file mode 100644
index 000000000000..4083b76ce9fb
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbox/search-box/search-icon.tsx
@@ -0,0 +1,16 @@
+import React from 'react';
+import { Icon } from '@deriv/components';
+
+type TSearchIcon = {
+ search: string;
+ is_search_loading: boolean;
+ onClick: () => void;
+};
+
+const SearchIcon = ({ search, is_search_loading, onClick }: TSearchIcon) => {
+ if (!search) return ;
+ if (is_search_loading) return
;
+ return ;
+};
+
+export default SearchIcon;
diff --git a/packages/bot-web-ui/src/components/toolbox/toolbox-items.jsx b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbox/toolbox-items.tsx
similarity index 100%
rename from packages/bot-web-ui/src/components/toolbox/toolbox-items.jsx
rename to packages/bot-web-ui/src/components/dashboard/bot-builder/toolbox/toolbox-items.tsx
diff --git a/packages/bot-web-ui/src/components/main-content/toolbox.scss b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbox/toolbox.scss
similarity index 96%
rename from packages/bot-web-ui/src/components/main-content/toolbox.scss
rename to packages/bot-web-ui/src/components/dashboard/bot-builder/toolbox/toolbox.scss
index 2948bf904a83..1713b7f6721d 100644
--- a/packages/bot-web-ui/src/components/main-content/toolbox.scss
+++ b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbox/toolbox.scss
@@ -2,9 +2,10 @@
$toolbox: &;
$category-text-spacing: 10px;
- height: var(--bot-content-height) !important;
- top: 10.4rem !important;
width: 25.6rem !important;
+ position: absolute;
+ top: 0 !important;
+ height: 100%;
&__search {
padding: 0.8rem;
@@ -12,10 +13,12 @@
&-field {
margin-bottom: 0 !important;
+ padding-left: 0.8rem;
input::-ms-clear {
display: none;
}
+
.dc-input {
justify-content: center;
@@ -25,6 +28,7 @@
flex-grow: 1;
padding: 0.8rem;
}
+
&__leading-icon {
position: unset;
pointer-events: unset;
@@ -34,6 +38,7 @@
}
}
}
+
&__content {
display: flex;
flex-direction: column;
@@ -42,15 +47,17 @@
z-index: 1;
visibility: visible;
}
+
&__header {
padding: 8px;
position: relative;
font-weight: bold;
font-size: 1.6em;
- background-color: var(--general-section-5);
text-align: center;
color: var(--text-general);
+ background-color: var(--general-section-5);
}
+
&__item {
display: flex;
flex-direction: row;
@@ -61,6 +68,7 @@
color: var(--text-prominent);
}
}
+
&__category-arrow {
width: 16px;
height: 16px;
@@ -80,6 +88,7 @@
overflow-y: auto;
overflow-x: hidden;
}
+
&__category-text {
display: flex;
align-self: center;
@@ -87,10 +96,12 @@
margin: 10px;
width: 100%;
}
+
&__description {
font-size: 12px;
line-height: 1.4;
}
+
&__icon {
display: flex;
align-self: center;
@@ -101,11 +112,13 @@
width: 25px;
}
}
+
&__label {
font-weight: bold;
font-size: 14px;
line-height: 1.4;
}
+
&__row {
cursor: pointer;
border-top: 1px solid var(--general-section-6);
@@ -113,10 +126,12 @@
#{$toolbox}__category--selected {
background-color: var(--general-active);
}
+
&:last-of-type {
border-bottom: 1px solid var(--general-section-6);
}
}
+
&__button.dc-btn {
margin: 16px auto;
padding: 0;
@@ -127,6 +142,7 @@
font-weight: bold;
line-height: 1.4;
}
+
&__sub-category {
&-row {
padding: 1rem;
diff --git a/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbox/toolbox.tsx b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbox/toolbox.tsx
new file mode 100644
index 000000000000..3163d8151967
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbox/toolbox.tsx
@@ -0,0 +1,186 @@
+import React from 'react';
+import classNames from 'classnames';
+import { Icon, Text } from '@deriv/components';
+import { isMobile } from '@deriv/shared';
+import { localize } from '@deriv/translations';
+import { connect } from 'Stores/connect';
+import RootStore from 'Stores/index';
+import ToolbarButton from '../toolbar/toolbar-button';
+import SearchBox from './search-box';
+import { ToolboxItems } from './toolbox-items';
+
+type TToolbox = {
+ hasSubCategory: (param: HTMLCollection) => boolean;
+ is_search_loading: boolean;
+ is_toolbox_open: boolean;
+ onMount: (param?: React.RefObject) => void;
+ onSearch: () => void;
+ onSearchBlur: () => void;
+ onSearchClear: () => void;
+ onSearchKeyUp: () => void;
+ onToolboxItemClick: (category: ChildNode) => void;
+ onToolboxItemExpand: (index: number) => void;
+ onUnmount: () => void;
+ setVisibility: (param: boolean) => void;
+ sub_category_index: number[];
+ toggleDrawer: () => void;
+ toolbox_dom: HTMLElement;
+ loadDataStrategy: () => void;
+};
+
+const Toolbox = ({
+ hasSubCategory,
+ is_search_loading,
+ onMount,
+ onSearch,
+ onSearchBlur,
+ onSearchClear,
+ onSearchKeyUp,
+ onToolboxItemClick,
+ onToolboxItemExpand,
+ onUnmount,
+ setVisibility,
+ sub_category_index,
+ toolbox_dom,
+ loadDataStrategy,
+}: TToolbox) => {
+ const toolbox_ref = React.useRef(ToolboxItems);
+ const [is_open, setOpen] = React.useState(true);
+
+ React.useEffect(() => {
+ onMount(toolbox_ref);
+ return () => onUnmount();
+ }, []);
+
+ if (!isMobile()) {
+ return (
+
+ );
+ }
+ return null;
+};
+
+export default connect(({ toolbox, flyout, quick_strategy }: RootStore) => ({
+ hasSubCategory: toolbox.hasSubCategory,
+ is_search_loading: toolbox.is_search_loading,
+ is_toolbox_open: toolbox.is_toolbox_open,
+ onMount: toolbox.onMount,
+ onSearch: toolbox.onSearch,
+ onSearchBlur: toolbox.onSearchBlur,
+ onSearchClear: toolbox.onSearchClear,
+ onSearchKeyUp: toolbox.onSearchKeyUp,
+ onToolboxItemClick: toolbox.onToolboxItemClick,
+ onToolboxItemExpand: toolbox.onToolboxItemExpand,
+ onUnmount: toolbox.onUnmount,
+ setVisibility: flyout.setVisibility,
+ sub_category_index: toolbox.sub_category_index,
+ toggleDrawer: toolbox.toggleDrawer,
+ toolbox_dom: toolbox.toolbox_dom,
+ loadDataStrategy: quick_strategy.loadDataStrategy,
+}))(Toolbox);
diff --git a/packages/bot-web-ui/src/components/dashboard/bot-builder/workspace-wrapper.tsx b/packages/bot-web-ui/src/components/dashboard/bot-builder/workspace-wrapper.tsx
new file mode 100644
index 000000000000..f51b5284f881
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/bot-builder/workspace-wrapper.tsx
@@ -0,0 +1,43 @@
+import React from 'react';
+import Flyout from 'Components/flyout';
+import { connect } from 'Stores/connect';
+import RootStore from 'Stores/index';
+import StopBotModal from '../dashboard-component/load-bot-preview/stop-bot-modal';
+import Toolbar from './toolbar';
+import Toolbox from './toolbox';
+import './workspace.scss';
+
+type TWorkspaceWrapper = {
+ onMount: () => void;
+ onUnmount: () => void;
+ is_loading: boolean;
+};
+
+const WorkspaceWrapper = ({ onMount, onUnmount, is_loading }: TWorkspaceWrapper) => {
+ React.useEffect(() => {
+ onMount();
+ return () => {
+ onUnmount();
+ };
+ }, []);
+
+ if (is_loading) return null;
+
+ if (Blockly?.derivWorkspace)
+ return (
+
+
+
+
+
+
+ );
+
+ return null;
+};
+
+export default connect(({ blockly_store }: RootStore) => ({
+ onMount: blockly_store.onMount,
+ onUnmount: blockly_store.onUnmount,
+ is_loading: blockly_store.is_loading,
+}))(WorkspaceWrapper);
diff --git a/packages/bot-web-ui/src/components/main-content/workspace.scss b/packages/bot-web-ui/src/components/dashboard/bot-builder/workspace.scss
similarity index 78%
rename from packages/bot-web-ui/src/components/main-content/workspace.scss
rename to packages/bot-web-ui/src/components/dashboard/bot-builder/workspace.scss
index e56f444b1246..a3de1291af12 100644
--- a/packages/bot-web-ui/src/components/main-content/workspace.scss
+++ b/packages/bot-web-ui/src/components/dashboard/bot-builder/workspace.scss
@@ -1,5 +1,8 @@
#scratch_div {
position: relative;
+ width: calc(100vw - 3.2rem);
+ height: var(--bot-content-height);
+ top: 4rem;
}
.blocklyTextRootBlockHeader {
diff --git a/packages/bot-web-ui/src/components/dashboard/bot-notification.tsx b/packages/bot-web-ui/src/components/dashboard/bot-notification.tsx
new file mode 100644
index 000000000000..3f1573ed922b
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/bot-notification.tsx
@@ -0,0 +1,43 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import { Toast } from '@deriv/components';
+import { localize } from '@deriv/translations';
+import { connect } from 'Stores/connect';
+import RootStore from 'Stores/root-store';
+
+type TBotNotification = {
+ show_toast: boolean;
+ toast_message: string;
+ setOpenSettings: (toast_message: string, show_toast: boolean) => void;
+};
+
+const BotNotification = ({ show_toast, toast_message, setOpenSettings }: TBotNotification) => {
+ React.useEffect(() => {
+ setTimeout(() => {
+ setOpenSettings(toast_message, false);
+ }, 3000);
+ }, [show_toast]);
+ const el_portal = document.getElementById('popup_root');
+
+ if (el_portal && show_toast) {
+ return ReactDOM.createPortal(
+
+
+
+ {toast_message === 'delete'
+ ? localize('You’ve successfully deleted a bot.')
+ : localize('You’ve successfully imported a bot.')}
+
+
+
,
+ el_portal
+ );
+ }
+ return null;
+};
+
+export default connect(({ dashboard }: RootStore) => ({
+ show_toast: dashboard.show_toast,
+ toast_message: dashboard.toast_message,
+ setOpenSettings: dashboard.setOpenSettings,
+}))(BotNotification);
diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/__tests__/dashboard-component.spec.tsx b/packages/bot-web-ui/src/components/dashboard/dashboard-component/__tests__/dashboard-component.spec.tsx
new file mode 100644
index 000000000000..3a945f237abf
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/__tests__/dashboard-component.spec.tsx
@@ -0,0 +1,103 @@
+import React from 'react';
+import { isMobile } from '@deriv/shared';
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import Sidebar from '../sidebar';
+import UserGuide from '../user-guide';
+
+const mock_connect_props = {
+ dialog_options: {
+ title: 'string',
+ message: 'string',
+ ok_button_text: 'string',
+ cancel_button_text: 'string',
+ },
+ setStrategySaveType: jest.fn(),
+};
+
+jest.mock('Stores/connect.js', () => ({
+ __esModule: true,
+ default: 'mockedDefaultExport',
+ connect:
+ () =>
+ (Component: T) =>
+ props =>
+ Component({ ...props, ...mock_connect_props }),
+}));
+
+jest.mock('@deriv/components', () => {
+ const original_module = jest.requireActual('@deriv/components');
+
+ return {
+ ...original_module,
+ Cards: jest.fn(() => 'Cards'),
+ messageWithButton: jest.fn(() => 'messageWithButton'),
+ arrayAsMessage: jest.fn(() => 'arrayAsMessage'),
+ DesktopWrapper: jest.fn(() => 'DesktopWrapper'),
+ MobileWrapper: jest.fn(() => 'MobileWrapper'),
+ Text: jest.fn(() => 'Text'),
+ };
+});
+
+jest.mock('@deriv/shared', () => ({
+ ...jest.requireActual('@deriv/shared'),
+ isMobile: jest.fn(() => false),
+}));
+
+jest.mock('@deriv/bot-skeleton', () => ({
+ Blockly: jest.fn(),
+}));
+
+jest.mock('@deriv/components', () => ({
+ ...jest.requireActual('@deriv/components'),
+ Tabs: jest.fn(() => true),
+ performSelfExclusionCheck: jest.fn(),
+}));
+
+export const mocked_props = {
+ active_tab: '3',
+ is_tour_dialog_visible: true,
+ dialog_options: {
+ title: 'string',
+ message: 'string',
+ ok_button_text: 'string',
+ cancel_button_text: 'string',
+ },
+ Blockly: jest.fn(),
+ faq_search_value: '',
+ guide_list: [],
+ is_dialog_open: true,
+ onOkButtonClick: jest.fn(),
+ setActiveTab: jest.fn(() => 3),
+ setHasTourEnded: jest.fn(),
+ setOnBoardTourRunState: jest.fn(),
+ setTourActiv: jest.fn(),
+ setTourDialogVisibility: jest.fn(),
+ showVideoDialog: jest.fn(),
+ performSelfExclusionCheck: jest.fn(),
+ setActiveTabTutorial: jest.fn(),
+};
+
+describe(' ', () => {
+ it('should render PopoverComponent if isMobile is false', () => {
+ (isMobile as jest.Mock).mockReturnValue(true);
+ //render( );
+ //expect(screen.getByText(/Import a bot/i)).toBeInTheDocument();
+ });
+
+ it('user guide button click should be enabled', () => {
+ render( );
+ const use_guide_button = screen.getByTestId('btn-user-guide');
+ userEvent.click(use_guide_button);
+ expect(screen.getByTestId('btn-user-guide')).toBeInTheDocument();
+ expect(use_guide_button).toBeEnabled();
+ });
+
+ it('on user guide button click it should render the tutorials tab', () => {
+ render( );
+ const use_guide_button = screen.getByTestId('btn-user-guide');
+ userEvent.click(use_guide_button);
+ render( );
+ expect(mocked_props.setActiveTab).toHaveBeenCalledWith(3);
+ });
+});
diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/__tests__/user-guide.spec.tsx b/packages/bot-web-ui/src/components/dashboard/dashboard-component/__tests__/user-guide.spec.tsx
new file mode 100644
index 000000000000..0a64d5e07cbf
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/__tests__/user-guide.spec.tsx
@@ -0,0 +1,27 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import UserGuide from '../user-guide';
+import { mocked_props } from './dashboard-component.spec';
+
+jest.mock('@deriv/components', () => {
+ const original_module = jest.requireActual('@deriv/components');
+
+ return {
+ ...original_module,
+ Cards: jest.fn(() => 'Cards'),
+ messageWithButton: jest.fn(() => 'messageWithButton'),
+ arrayAsMessage: jest.fn(() => 'messageWithButton'),
+ DesktopWrapper: jest.fn(() => 'DesktopWrapper'),
+ MobileWrapper: jest.fn(() => 'MobileWrapper'),
+ Text: jest.fn(() => 'Text'),
+ };
+});
+describe(' ', () => {
+ it('renders user guide button', () => {
+ render( );
+ const use_guide_button = screen.getByTestId('btn-user-guide');
+ userEvent.click(use_guide_button);
+ expect(screen.getByTestId('btn-user-guide')).toBeInTheDocument();
+ });
+});
diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/cards.tsx b/packages/bot-web-ui/src/components/dashboard/dashboard-component/cards.tsx
new file mode 100644
index 000000000000..35224f8fa3cd
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/cards.tsx
@@ -0,0 +1,191 @@
+//kept sometihings commented beacuse of mobx to integrate popup functionality here
+import React from 'react';
+import classNames from 'classnames';
+import { DesktopWrapper, Dialog, Icon, MobileFullPageModal, MobileWrapper, Text } from '@deriv/components';
+import { localize } from '@deriv/translations';
+import { DBOT_TABS } from 'Constants/bot-contents';
+import { connect } from 'Stores/connect';
+import RootStore from 'Stores/index';
+import SaveModalStore from 'Stores/save-modal-store';
+import GoogleDrive from './load-bot-preview/google-drive';
+import Recent from './load-bot-preview/recent';
+
+type TCardProps = {
+ dialog_options: { [key: string]: string };
+ has_dashboard_strategies: boolean;
+ is_dialog_open: boolean;
+ is_mobile: boolean;
+ save_modal: SaveModalStore;
+ closeResetDialog: () => void;
+ handleFileChange: (e: React.ChangeEvent, flag?: boolean) => boolean;
+ loadFileFromLocal: () => void;
+ loadDataStrategy: () => void;
+ setActiveTab: (active_tab: number) => void;
+ setFileLoaded: (param: boolean) => void;
+ setPreviewOnPopup: (show: boolean) => void;
+ setOpenSettings: (toast_message: string, show_toast?: boolean) => void;
+ showVideoDialog: (param: { [key: string]: string | React.ReactNode }) => void;
+};
+
+type TCardArray = {
+ icon: string;
+ content: string;
+ method: () => void;
+};
+
+const Cards = ({
+ closeResetDialog,
+ dialog_options,
+ handleFileChange,
+ has_dashboard_strategies,
+ is_dialog_open,
+ is_mobile,
+ loadFileFromLocal,
+ setActiveTab,
+ setFileLoaded,
+ setPreviewOnPopup,
+ setOpenSettings,
+ showVideoDialog,
+ loadDataStrategy,
+}: TCardProps) => {
+ const [is_file_supported, setIsFileSupported] = React.useState(true);
+ const file_input_ref = React.useRef(null);
+
+ const openGoogleDriveDialog = () => {
+ showVideoDialog({
+ type: 'google',
+ });
+ };
+
+ const openFileLoader = () => {
+ file_input_ref?.current?.click();
+ };
+
+ const actions: TCardArray[] = [
+ {
+ icon: is_mobile ? 'IcLocal' : 'IcMyComputer',
+ content: is_mobile ? localize('Local') : localize('My computer'),
+ method: openFileLoader,
+ },
+ {
+ icon: 'IcGoogleDriveDbot',
+ content: localize('Google Drive'),
+ method: openGoogleDriveDialog,
+ },
+ {
+ icon: 'IcBotBuilder',
+ content: localize('Bot Builder'),
+ method: () => setActiveTab(DBOT_TABS.BOT_BUILDER),
+ },
+ {
+ icon: 'IcQuickStrategy',
+ content: localize('Quick strategy'),
+ method: () => {
+ setActiveTab(DBOT_TABS.BOT_BUILDER);
+ loadDataStrategy();
+ },
+ },
+ ];
+
+ return React.useMemo(
+ () => (
+
+
+ {actions.map(icons => {
+ const { icon, content, method } = icons;
+ return (
+
+
+
+ {content}
+
+
+ );
+ })}
+
{
+ setIsFileSupported(handleFileChange(e, false));
+ loadFileFromLocal();
+ setFileLoaded(true);
+ setOpenSettings('import');
+ }}
+ />
+
+
+
+
+
+
+ {
+ setPreviewOnPopup(false);
+ closeResetDialog();
+ }}
+ height_offset='80px'
+ page_overlay
+ >
+
+
+
+
+
+
+
+
+ ),
+ [is_dialog_open, has_dashboard_strategies]
+ );
+};
+
+export default connect(({ load_modal, dashboard, quick_strategy }: RootStore) => ({
+ closeResetDialog: dashboard.onCloseDialog,
+ dialog_options: dashboard.dialog_options,
+ handleFileChange: load_modal.handleFileChange,
+ is_dialog_open: dashboard.is_dialog_open,
+ loadFileFromLocal: load_modal.loadFileFromLocal,
+ onDriveConnect: load_modal.onDriveConnect,
+ setActiveTab: dashboard.setActiveTab,
+ setFileLoaded: dashboard.setFileLoaded,
+ setLoadedLocalFile: load_modal.setLoadedLocalFile,
+ setPreviewOnPopup: dashboard.setPreviewOnPopup,
+ setOpenSettings: dashboard.setOpenSettings,
+ showVideoDialog: dashboard.showVideoDialog,
+ loadDataStrategy: quick_strategy.loadDataStrategy,
+}))(Cards);
diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/constants.ts b/packages/bot-web-ui/src/components/dashboard/dashboard-component/constants.ts
new file mode 100644
index 000000000000..ecb4204b7d12
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/constants.ts
@@ -0,0 +1,34 @@
+import { localize } from '@deriv/translations';
+
+export type TSidebarItem = {
+ label: string;
+ content: string[];
+ link: boolean;
+};
+
+export const SIDEBAR_INTRO: TSidebarItem[] = [
+ {
+ label: localize('Welcome to Deriv Bot!'),
+ content: [
+ localize(
+ 'Ready to automate your trading strategy without writing any code? You’ve come to the right place.'
+ ),
+ localize('Check out these guides and FAQs to learn more about building your bot:'),
+ ],
+ link: false,
+ },
+ {
+ label: localize('Guide'),
+ content: [localize('Deriv Bot - your automated trading partner')],
+ link: true,
+ },
+ {
+ label: localize('FAQs'),
+ content: [
+ localize('What is Deriv Bot?'),
+ localize('Where do I find the blocks I need?'),
+ localize('How do I remove blocks from the workspace?'),
+ ],
+ link: true,
+ },
+];
diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/dashboard-component.tsx b/packages/bot-web-ui/src/components/dashboard/dashboard-component/dashboard-component.tsx
new file mode 100644
index 000000000000..dbbdb2a6b28c
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/dashboard-component.tsx
@@ -0,0 +1,135 @@
+import React from 'react';
+import classNames from 'classnames';
+import { DesktopWrapper, MobileWrapper, Text } from '@deriv/components';
+import { isMobile } from '@deriv/shared';
+import { observer } from '@deriv/stores';
+import { localize } from '@deriv/translations';
+import { useDBotStore } from 'Stores/useDBotStore';
+import Local from './load-bot-preview/local';
+import Cards from './cards';
+import InfoPanel from './info-panel';
+import UserGuide from './user-guide';
+
+type TMobileIconGuide = {
+ has_dashboard_strategies: boolean;
+ handleTabChange: (active_number: number) => void;
+};
+
+type TDashboardMobileCommonProps = {
+ is_mobile: boolean;
+ has_dashboard_strategies: boolean;
+};
+
+const DashboardTitle = ({ is_mobile, has_dashboard_strategies }: TDashboardMobileCommonProps) => (
+
+
+ {localize('Load or build your bot')}
+
+
+);
+
+const MobileIconGuide = ({ handleTabChange }: TMobileIconGuide) => (
+
+
+
+
+
+);
+
+const DashboardDescription = ({ is_mobile, has_dashboard_strategies }: TDashboardMobileCommonProps) => (
+
+
+ {localize(
+ 'Import a bot from your computer or Google Drive, build it from scratch, or start with a quick strategy.'
+ )}
+
+
+);
+
+const DashboardComponent = observer(({ handleTabChange }: TMobileIconGuide) => {
+ const { load_modal, dashboard } = useDBotStore();
+ const { dashboard_strategies } = load_modal;
+ const { setActiveTab, setActiveTabTutorial, has_started_onboarding_tour } = dashboard;
+ const has_dashboard_strategies = !!dashboard_strategies?.length;
+ const is_mobile = isMobile();
+
+ return (
+
+
+
+
+ {!has_dashboard_strategies && !is_mobile && (
+
+ )}
+
+ {!has_dashboard_strategies && (
+
+ )}
+
+
+
+ {is_mobile && !has_dashboard_strategies && (
+
+ )}
+ {(!is_mobile || (is_mobile && has_dashboard_strategies)) && (
+
+ )}
+
+
+ {is_mobile && !has_dashboard_strategies && (
+
+ )}
+
+
+
+
+ {has_dashboard_strategies && !is_mobile && (
+
+
+
+ )}
+
+
+
+
+ );
+});
+
+export default DashboardComponent;
diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/index.scss b/packages/bot-web-ui/src/components/dashboard/dashboard-component/index.scss
new file mode 100644
index 000000000000..20317cb17cb4
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/index.scss
@@ -0,0 +1,485 @@
+@mixin align-center {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.bot-dashboard {
+ position: relative;
+ height: calc(100vh - 8.4rem);
+ overflow: hidden;
+
+ @include mobile {
+ height: calc(100vh - 3.4rem);
+ }
+
+ .toolbar__section {
+ justify-content: end;
+
+ @include mobile {
+ column-gap: 1rem;
+ }
+ }
+
+ .run-panel__container {
+ height: var(--tab-content-height) !important;
+ position: sticky;
+ // TODO: need to set global variables for translation
+ transform: translateX(calc(367px));
+ transition: all 0.4s;
+ margin-top: 1rem;
+
+ &--tour-active {
+ transform: translateX(calc(0px)) !important;
+
+ .dc-drawer__toggle-icon--right {
+ transform: rotate(180deg) !important;
+ }
+ }
+ }
+
+ .dc-drawer--open {
+ @include desktop {
+ transform: translateX(calc(0px)) !important;
+ }
+
+ @include mobile {
+ transform: translateY(calc(-100% + 3.6rem));
+ }
+ }
+}
+
+.dashboard {
+ &__main {
+ display: flex;
+ overflow: hidden;
+ }
+
+ &__container {
+ border: none;
+ max-width: 100%;
+ width: 100%;
+
+ .dc-tabs {
+ &__active-line {
+ background-color: var(--general-main-1);
+ transition: none;
+ }
+
+ &__active {
+ background: var(--general-main-1);
+ }
+
+ &__list {
+ background: var(--general-section-1);
+ justify-content: start;
+
+ @include mobile {
+ overflow-x: auto;
+
+ &::-webkit-scrollbar {
+ display: none;
+ }
+
+ -ms-overflow-style: none;
+ scrollbar-width: none;
+ }
+
+ &--header {
+ &--dashboard {
+ &__tabs {
+ @include mobile {
+ width: 100%;
+ overflow-x: auto;
+ }
+ }
+ }
+ }
+ }
+
+ &__content {
+ @include mobile {
+ width: 100%;
+ overflow-x: auto;
+ }
+
+ &--dashboard {
+ &__tabs {
+ display: flex;
+ justify-content: space-between;
+
+ @include mobile {
+ flex-direction: column;
+ background-color: var(--general-section-1);
+ }
+ }
+ }
+ }
+
+ &__item {
+ border-top-left-radius: 1rem;
+ border-top-right-radius: 1rem;
+ height: 4.8rem;
+ @include align-center;
+
+ &__loaded {
+ text-align: left;
+ padding-left: 1.5rem;
+ }
+
+ svg {
+ width: 1.6rem;
+ height: 1.6rem;
+ padding-right: 0;
+ margin-right: 0.8rem;
+ }
+ }
+ }
+ }
+}
+
+.w-100 {
+ width: 100%;
+}
+
+.nm-48 {
+ margin-top: -4.8rem;
+}
+
+.db-sidebar {
+ position: relative;
+ background-color: var(--general-main-1);
+ padding: 2.4rem;
+ height: 100%;
+
+ &--block {
+ display: block;
+ }
+
+ display: none;
+
+ &__images {
+ background: var(--general-section-6);
+ }
+}
+
+.db-info-panel {
+ &__close-action {
+ position: absolute;
+ right: 1.4rem;
+ top: 1.4rem;
+ height: 2rem;
+ width: 2rem;
+ line-height: 2.3rem;
+ text-align: center;
+ border-radius: 1rem;
+
+ &:hover {
+ cursor: pointer;
+ background: var(--general-section-1);
+ }
+ }
+
+ &__content {
+ cursor: pointer;
+
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+}
+
+.tab {
+ &__dashboard {
+ display: flex;
+ width: 100%;
+
+ &--tour-active {
+ width: calc(100% - 36rem);
+
+ @include mobile {
+ width: 100%;
+ }
+ }
+
+ &__content {
+ min-width: 70%;
+ display: flex;
+ justify-content: center;
+ height: var(--tab-content-height);
+ flex-grow: 1;
+ background: var(--general-main-1);
+ overflow: hidden;
+ position: relative;
+
+ @include desktop {
+ align-items: center;
+ padding: 2.2rem 0;
+ }
+
+ @include mobile {
+ height: var(--tab-content-height-mobile);
+ align-items: flex-start;
+ }
+ }
+
+ &__mobile-container {
+ display: flex;
+ align-items: center;
+ margin: 1.6rem 1.6rem 1.4rem;
+ column-gap: 1rem;
+ height: 3.2rem;
+
+ &--minimized {
+ height: 3.6rem;
+ }
+ }
+
+ &__centered {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ width: 50%;
+
+ @include tablet {
+ width: 100%;
+ }
+
+ @include mobile {
+ width: 100%;
+ padding: 0 1rem;
+ }
+
+ &--not-listed {
+ @include desktop {
+ width: 50%;
+ }
+ }
+
+ &--listed {
+ margin-top: 10rem;
+ align-items: unset;
+ padding: 1rem 3rem;
+ }
+ }
+
+ &__preview {
+ width: 50%;
+ height: calc(100% + 2rem);
+
+ @include mobile {
+ display: none;
+ }
+
+ @include tablet {
+ display: none;
+ }
+
+ &__retrigger {
+ button {
+ background: $color-grey-2;
+ border-radius: 2.4rem;
+ outline: none;
+ border: none;
+ height: 4rem;
+ width: 12.8rem;
+ @include align-center;
+ cursor: pointer;
+
+ @include mobile {
+ width: 3.2rem;
+ height: 3.2rem;
+ padding: 0.8rem;
+ }
+ }
+
+ &__text {
+ margin-left: 0.4rem;
+ color: var(--text-less-prominent);
+ }
+ }
+ }
+
+ &__home {
+ &__retrigger {
+ position: absolute;
+ top: 0;
+ right: 0;
+ padding: 2rem;
+
+ button {
+ background: $color-grey-2;
+ border-radius: 2.4rem;
+ outline: none;
+ border: none;
+ height: 4rem;
+ width: 12.8rem;
+ @include align-center;
+ cursor: pointer;
+ }
+
+ &__text {
+ margin-left: 0.4rem;
+ color: var(--text-less-prominent);
+ }
+ }
+ }
+
+ &__header {
+ margin-bottom: 1.3rem;
+
+ &--not-listed {
+ @include mobile {
+ width: 90%;
+ text-align: end;
+ margin: 0 !important;
+ }
+ }
+ }
+
+ &__description {
+ @include desktop {
+ text-align: center;
+ margin-bottom: 4rem;
+ }
+
+ &__loaded {
+ text-align: left;
+
+ @include desktop {
+ margin-top: 12rem;
+ }
+
+ &--listed {
+ text-align: left;
+ padding: 2rem 2rem 0 0;
+ }
+
+ &--not-listed {
+ @include mobile {
+ text-align: center;
+ margin-bottom: 1.4rem;
+ padding: 0 0.6rem;
+ }
+ }
+ }
+ }
+
+ &__info-panel {
+ position: relative;
+ background-color: var(--general-main-1);
+ padding: 2.4rem;
+ margin-left: 1.6rem;
+ display: none;
+ height: calc(100vh - 16.7rem);
+ overflow-y: auto;
+
+ &--active {
+ display: block;
+ width: 30%;
+ }
+ }
+
+ &__table {
+ @include mobile-tablet-mix {
+ width: calc(100% - 9.3rem);
+ }
+
+ &--minimized {
+ width: 100%;
+ }
+
+ &__tiles {
+ word-wrap: break-word;
+ font-size: 1.3rem;
+ text-align: center;
+ display: flex;
+ justify-content: center;
+ align-items: flex-start;
+
+ @include mobile {
+ flex-wrap: wrap;
+ }
+
+ &--minimized {
+ align-items: flex-start;
+
+ @include mobile-tablet-mix {
+ display: flex;
+ justify-content: space-around;
+ flex-flow: unset;
+ }
+ }
+ }
+
+ &__disabled-card {
+ pointer-events: none;
+ cursor: not-allowed;
+ opacity: 0.6;
+ }
+
+ &__block {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-direction: column;
+ padding-right: 2.4rem;
+
+ @include mobile {
+ padding: 1rem;
+ }
+
+ &:hover {
+ cursor: pointer;
+ }
+
+ span {
+ width: 9.1rem;
+ word-wrap: break-word;
+ text-align: center;
+ }
+
+ &--minimized {
+ width: 6.4rem;
+
+ & .dc-text {
+ width: 8rem;
+ text-align: center;
+ }
+ }
+ }
+
+ &__images {
+ background-color: var(--general-section-1);
+ margin-bottom: 0.8rem;
+ border-radius: 0.8rem;
+ padding: 1.6rem;
+
+ &--minimized {
+ @include mobile {
+ width: 6.4rem;
+ height: 6.4rem;
+ padding: 0.8rem;
+ }
+ }
+ }
+
+ .load-strategy {
+ &__recent-item-time {
+ font-size: var(--text-size-xs);
+ }
+
+ &__recent-item-text {
+ display: -webkit-box;
+ max-width: 90%;
+ -webkit-line-clamp: 1;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ word-break: break-all;
+ }
+ }
+ }
+ }
+}
diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/index.ts b/packages/bot-web-ui/src/components/dashboard/dashboard-component/index.ts
new file mode 100644
index 000000000000..c6e9e4e29fbe
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/index.ts
@@ -0,0 +1,3 @@
+import DashboardComponent from './dashboard-component';
+
+export default DashboardComponent;
diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/info-panel.tsx b/packages/bot-web-ui/src/components/dashboard/dashboard-component/info-panel.tsx
new file mode 100644
index 000000000000..6b11a21f519d
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/info-panel.tsx
@@ -0,0 +1,107 @@
+import React from 'react';
+import classNames from 'classnames';
+import { DesktopWrapper, Icon, MobileWrapper, Modal, Text } from '@deriv/components';
+import { isMobile } from '@deriv/shared';
+import { DBOT_TABS } from 'Constants/bot-contents';
+import { connect } from 'Stores/connect';
+import RootStore from 'Stores/index';
+import { SIDEBAR_INTRO } from './constants';
+
+type TInfoPanel = {
+ has_started_onboarding_tour: boolean;
+ is_info_panel_visible: boolean;
+ setActiveTab: (param: number) => void;
+ setActiveTabTutorial: (param: number) => void;
+ setInfoPanelVisibility: (state: boolean) => void;
+};
+
+const InfoPanel = ({
+ has_started_onboarding_tour,
+ is_info_panel_visible,
+ setActiveTab,
+ setActiveTabTutorial,
+ setInfoPanelVisibility,
+}: TInfoPanel) => {
+ const is_mobile = isMobile();
+ const switchTab = (link: boolean, label: string) => {
+ const tutorial_link = link ? setActiveTab(DBOT_TABS.TUTORIAL) : null;
+ const tutorial_label = label === 'Guide' ? setActiveTabTutorial(0) : setActiveTabTutorial(1);
+ return {
+ tutorial_link,
+ tutorial_label,
+ };
+ };
+
+ const handleClose = () => {
+ setInfoPanelVisibility(false);
+ localStorage.setItem('dbot_should_show_info', JSON.stringify(Date.now()));
+ };
+
+ const renderInfo = () => (
+
+
+
+
+
+ {SIDEBAR_INTRO.map(sidebar_item => {
+ const { label, content, link } = sidebar_item;
+ return (
+
+
+ {label}
+
+ {content.map(text => (
+ switchTab(link, label)}
+ size={is_mobile ? 'xxs' : 's'}
+ >
+ {text}
+
+ ))}
+
+ );
+ })}
+
+ );
+
+ return (
+ <>
+
+ {!has_started_onboarding_tour && (
+
+ {renderInfo()}
+
+ )}
+
+
+
+ {renderInfo()}
+
+
+ >
+ );
+};
+
+export default connect(({ dashboard }: RootStore) => ({
+ has_started_onboarding_tour: dashboard.has_started_onboarding_tour,
+ is_info_panel_visible: dashboard.is_info_panel_visible,
+ setActiveTab: dashboard.setActiveTab,
+ setActiveTabTutorial: dashboard.setActiveTabTutorial,
+ setInfoPanelVisibility: dashboard.setInfoPanelVisibility,
+}))(InfoPanel);
diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/intro-card.tsx b/packages/bot-web-ui/src/components/dashboard/dashboard-component/intro-card.tsx
new file mode 100644
index 000000000000..0f94e0f86f6d
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/intro-card.tsx
@@ -0,0 +1,20 @@
+import React from 'react';
+import { TSidebarItem } from './constants';
+
+type TIntroCard = {
+ sidebar_item: TSidebarItem;
+};
+
+const Index = ({ sidebar_item }: TIntroCard) => {
+ const { label, content } = sidebar_item;
+ return (
+
+
{label}
+ {content?.map(text => (
+
{text}
+ ))}
+
+ );
+};
+
+export default Index;
diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/bot-preview.tsx b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/bot-preview.tsx
new file mode 100644
index 000000000000..1c39c2a400b0
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/bot-preview.tsx
@@ -0,0 +1,16 @@
+import React from 'react';
+import WorkspaceControl from './workspace-control';
+
+type TBotPreview = {
+ id_ref: HTMLElement | React.ReactNode | null;
+};
+
+const BotPreview = ({ id_ref }: TBotPreview) => {
+ return (
+
+
+
+ );
+};
+
+export default BotPreview;
diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/constants.ts b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/constants.ts
new file mode 100644
index 000000000000..b2c6de27a749
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/constants.ts
@@ -0,0 +1,47 @@
+import { localize } from '@deriv/translations';
+
+export const STRATEGY = {
+ EDIT: 'edit',
+ SAVE: 'save',
+ DELETE: 'delete',
+ PREVIEW: 'preview',
+ PREVIEW_LIST: 'list',
+};
+
+export const MENU_DESKTOP = [
+ {
+ type: STRATEGY.EDIT,
+ icon: 'IcEdit',
+ },
+ {
+ type: STRATEGY.SAVE,
+ icon: 'IcSave',
+ },
+ {
+ type: STRATEGY.DELETE,
+ icon: 'IcDelete',
+ },
+];
+
+export const CONTEXT_MENU_MOBILE = [
+ {
+ type: STRATEGY.PREVIEW_LIST,
+ icon: 'IcPreview',
+ label: localize('Preview'),
+ },
+ {
+ type: STRATEGY.EDIT,
+ icon: 'IcEdit',
+ label: localize('Edit'),
+ },
+ {
+ type: STRATEGY.SAVE,
+ icon: 'IcSave',
+ label: localize('Save'),
+ },
+ {
+ type: STRATEGY.DELETE,
+ icon: 'IcDelete',
+ label: localize('Delete'),
+ },
+];
diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/delete-dialog.scss b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/delete-dialog.scss
new file mode 100644
index 000000000000..dff114b2c6de
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/delete-dialog.scss
@@ -0,0 +1,21 @@
+.dc-dialog__delete-strategy--delete {
+ .dc-dialog {
+ &__footer {
+ @include mobile {
+ flex-wrap: unset;
+ display: flex;
+ justify-content: flex-end;
+ align-items: flex-start;
+ width: 100%;
+
+ button {
+ flex-basis: unset;
+ &:first-child {
+ margin-right: 1rem;
+ }
+ margin-bottom: unset;
+ }
+ }
+ }
+ }
+}
diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/delete-dialog.tsx b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/delete-dialog.tsx
new file mode 100644
index 000000000000..0702af559996
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/delete-dialog.tsx
@@ -0,0 +1,89 @@
+import React from 'react';
+import localForage from 'localforage';
+import LZString from 'lz-string';
+import { getSavedWorkspaces } from '@deriv/bot-skeleton';
+import { Dialog, Text } from '@deriv/components';
+import { localize } from '@deriv/translations';
+import { connect } from 'Stores/connect';
+
+type TDeleteDialog = {
+ is_delete_modal_open: boolean;
+ onToggleDeleteDialog: (param: boolean) => void;
+ removeBotStrategy: (param: string) => void;
+ selected_strategy_id: string;
+ setDashboardStrategies: (param: string[]) => void;
+ setStrategies: (param: string[]) => void;
+ setOpenSettings: (toast_message: string, show_toast: boolean) => void;
+};
+
+const DeleteDialog = ({
+ is_delete_modal_open,
+ onToggleDeleteDialog,
+ selected_strategy_id,
+ setDashboardStrategies,
+ setOpenSettings,
+}: TDeleteDialog) => {
+ const removeBotStrategy = async (strategy_id: string) => {
+ const workspaces = await getSavedWorkspaces();
+ workspaces.map((strategy_from_workspace: string[] | { [key: string]: string }, index: number) => {
+ if (strategy_from_workspace.id === strategy_id) {
+ if (index > -1) {
+ workspaces.splice(index, 1);
+ }
+ setDashboardStrategies(workspaces);
+ localForage.setItem('saved_workspaces', LZString.compress(JSON.stringify(workspaces)));
+ onToggleDeleteDialog(false);
+ }
+ });
+ };
+
+ const onHandleChange = (type: string, param: boolean) => {
+ if (type === 'confirm') {
+ removeBotStrategy(selected_strategy_id);
+ setOpenSettings('delete', true);
+ }
+ onToggleDeleteDialog(param);
+ };
+
+ return (
+
+
{
+ onHandleChange('confirm', false);
+ setOpenSettings('delete', true);
+ }}
+ cancel_button_text={localize('No')}
+ onCancel={() => {
+ onHandleChange('cancel', false);
+ }}
+ is_mobile_full_width={false}
+ className={'dc-dialog__delete-strategy--delete'}
+ has_close_icon
+ >
+
+
+ {localize('Your bot will be permanently deleted when you hit ')}
+ {localize('Yes, delete.')}
+
+
+
+
+ {localize('Are you sure you want to delete it?')}
+
+
+
+
+ );
+};
+
+export default connect(({ toolbar, load_modal, dashboard }) => ({
+ is_dialog_open: toolbar.is_dialog_open,
+ is_delete_modal_open: load_modal.is_delete_modal_open,
+ onToggleDeleteDialog: load_modal.onToggleDeleteDialog,
+ selected_strategy_id: load_modal.selected_strategy_id,
+ setDashboardStrategies: load_modal.setDashboardStrategies,
+ setOpenSettings: dashboard.setOpenSettings,
+}))(DeleteDialog);
diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/google-drive.scss b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/google-drive.scss
new file mode 100644
index 000000000000..920ce9cf06e4
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/google-drive.scss
@@ -0,0 +1,22 @@
+.dc-dialog__wrapper--google-drive {
+ .dc-dialog {
+ &__dialog {
+ width: unset;
+ max-width: unset;
+ height: unset;
+ max-height: unset;
+ padding: 2.4rem;
+ background-color: var(--general-main-2);
+ }
+ &__content {
+ .load-strategy {
+ &__container {
+ height: unset;
+ }
+ }
+ }
+ &__footer {
+ display: none;
+ }
+ }
+}
diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/google-drive.tsx b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/google-drive.tsx
new file mode 100644
index 000000000000..1b1fcee97c26
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/google-drive.tsx
@@ -0,0 +1,90 @@
+import React from 'react';
+import classnames from 'classnames';
+import { Button, Icon, StaticUrl } from '@deriv/components';
+import { isMobile } from '@deriv/shared';
+import { Localize, localize } from '@deriv/translations';
+import { connect } from 'Stores/connect';
+import RootStore from 'Stores/root-store';
+
+type TGoogleDriveProps = {
+ is_authorised: boolean;
+ is_open_button_loading: boolean;
+ onDriveConnect: () => void;
+ onDriveOpen: () => void;
+ setOpenSettings: (toast_message: string, show_toast?: boolean) => void;
+};
+
+const GoogleDrive = ({
+ is_authorised,
+ is_open_button_loading,
+ onDriveConnect,
+ onDriveOpen,
+ setOpenSettings,
+}: TGoogleDriveProps) => {
+ return (
+
+
+
+
+ {is_authorised ? (
+
+ ) : (
+ 'Google Drive'
+ )}
+
+ {is_authorised ? (
+
+
+ {
+ onDriveOpen();
+ setOpenSettings('import');
+ }}
+ is_loading={is_open_button_loading}
+ has_effect
+ primary
+ large
+ />
+
+ ) : (
+
+
+
+
+
+
+ ,
+ ]}
+ />
+
+
+
+
+ )}
+
+
+ );
+};
+
+export default connect(({ load_modal, google_drive, dashboard }: RootStore) => ({
+ is_authorised: google_drive.is_authorised,
+ is_open_button_loading: load_modal.is_open_button_loading,
+ onDriveConnect: load_modal.onDriveConnect,
+ onDriveOpen: load_modal.onDriveOpen,
+ setOpenSettings: dashboard.setOpenSettings,
+}))(GoogleDrive);
diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/icon-radio.tsx b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/icon-radio.tsx
new file mode 100644
index 000000000000..43252ec061d1
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/icon-radio.tsx
@@ -0,0 +1,57 @@
+import React from 'react';
+import classNames from 'classnames';
+import { Text } from '@deriv/components';
+import { localize } from '@deriv/translations';
+
+type TIconRadio = {
+ google_drive_connected: boolean;
+ icon: string;
+ text: string;
+ onDriveConnect: () => void;
+};
+const IconRadio = ({ icon, text, google_drive_connected, onDriveConnect }: TIconRadio) => {
+ const is_drive_radio = text === 'Google Drive';
+
+ return (
+
+
+ {icon &&
+ React.cloneElement(icon, {
+ className: classNames(
+ 'save-type__icon',
+ {
+ 'save-type__icon--active': is_drive_radio && google_drive_connected,
+ 'save-type__icon--disabled': is_drive_radio && !google_drive_connected,
+ },
+ icon.props.className
+ ),
+ })}
+
+ {localize(text)}
+
+
+ {is_drive_radio && (
+
+ {localize(google_drive_connected ? localize('Disconnect') : localize('Connect'))}
+
+ )}
+
+ );
+};
+
+export default IconRadio;
diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/index.scss b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/index.scss
new file mode 100644
index 000000000000..45bdc919eaab
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/index.scss
@@ -0,0 +1,661 @@
+@import './google-drive.scss';
+@import './delete-dialog.scss';
+
+.load-strategy {
+ &__wrapper {
+ position: fixed;
+ top: 4rem;
+ z-index: 10;
+ width: 100%;
+
+ .dc-mobile-full-page-modal {
+ @include mobile {
+ width: 100%;
+ }
+
+ &__body {
+ height: 100%;
+ }
+ .google-drive-label {
+ width: 100%;
+ }
+ }
+
+ & .dc-tabs {
+ @include mobile {
+ height: 100%;
+ display: unset;
+ flex: 1;
+
+ &__content {
+ height: calc(100% - 4rem);
+ }
+ }
+ }
+ }
+
+ &__container {
+ @include desktop {
+ height: calc(80vh - 15rem);
+
+ &--has-footer {
+ height: calc(80vh - 15rem);
+ }
+
+ > * {
+ height: 100%;
+ margin-top: 0.8rem;
+ }
+ }
+
+ @include tablet {
+ height: calc(80vh - 15rem);
+
+ &--has-footer {
+ height: calc(80vh - 15rem);
+ }
+
+ > * {
+ height: 100%;
+ margin-top: 0.8rem;
+ }
+ }
+
+ @include mobile {
+ height: 100%;
+ overflow: hidden;
+ }
+ }
+
+ &__title {
+ font-size: var(--text-size-s);
+ font-weight: 700;
+ margin: 1.6rem 1.6rem 1.6rem 0;
+ color: var(--text-general);
+
+ &--listed {
+ margin: 0 !important;
+ }
+ }
+
+ &__button-group {
+ display: flex;
+ justify-content: flex-end;
+ margin-right: 1.6rem;
+
+ &--clear {
+ height: 3.4rem;
+ width: 6.7rem;
+ background-color: $color-grey-6;
+ border-radius: 0.4rem;
+ color: $color-white;
+ outline: none;
+ border: none;
+ font-weight: 700;
+ font-size: 1.4rem;
+ cursor: pointer;
+ margin-right: 1.6rem;
+ }
+
+ &--open {
+ height: 3.4rem;
+ background-color: $color-red;
+ border-radius: 0.4rem;
+ color: $color-white;
+ outline: none;
+ border: none;
+ font-weight: 700;
+ font-size: '1.4rem';
+ cursor: pointer;
+ }
+ }
+
+ &__preview-workspace {
+ padding: 1.5rem;
+ border-radius: $BORDER_RADIUS;
+ border: solid 1px var(--border-normal);
+ height: calc(100vh - 32.4rem);
+ position: relative;
+ overflow: hidden;
+ margin: 0 1.6rem 1.6rem 0;
+
+ &-controls {
+ padding: 0.7rem 0.5rem;
+ display: flex;
+ flex-direction: column;
+ position: absolute;
+ right: 1.6rem;
+ bottom: 1.6rem;
+ border-radius: 3rem;
+ background-color: $color-grey-2;
+ box-shadow: 0.2rem 0.2rem 0.5rem var(--shadow-menu);
+ z-index: 99;
+ }
+
+ &-icon {
+ margin: 0.5rem;
+ cursor: pointer;
+ }
+ }
+
+ &__recent {
+ display: flex;
+ gap: 1.6rem;
+
+ &-preview-title {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ color: var(--text-general);
+ }
+
+ &__files {
+ flex-basis: 100%;
+ height: calc(100% - 3.8rem);
+
+ @include mobile {
+ overflow: auto;
+ height: calc(100vh - 35rem);
+ }
+
+ &__list {
+ overflow: auto;
+
+ @include mobile {
+ overflow: unset;
+ height: calc(100vh - 18rem);
+ padding-right: 1rem;
+ }
+
+ height: calc(100% - 15rem); // 100% - Title height
+ width: 100%;
+ }
+ }
+
+ &__preview {
+ flex-basis: 65%;
+ display: flex;
+ flex-direction: column;
+
+ &__title {
+ margin-left: 0;
+ }
+ }
+
+ &-empty {
+ display: flex;
+ align-items: center;
+ flex-direction: column;
+ justify-content: center;
+ text-align: center;
+
+ &-icon {
+ margin-bottom: 1.6rem;
+ }
+
+ &-title {
+ margin-bottom: 0.8rem;
+ font-size: var(--text-size-s);
+ font-weight: bold;
+ line-height: 2.4rem;
+ }
+
+ &-description {
+ margin-bottom: 1.6rem;
+ font-size: var(--text-size-xs);
+ line-height: 2rem;
+ }
+
+ &-expand {
+ margin-bottom: 0.8rem;
+ color: var(--brand-red-coral);
+ font-size: var(--text-size-xs);
+ font-weight: bold;
+ cursor: pointer;
+
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+
+ &-explanation {
+ font-size: var(--text-size-xxs);
+ text-align: left;
+ opacity: 0;
+
+ &-list {
+ margin-top: 0.8rem;
+ }
+
+ &--show {
+ opacity: 1;
+ width: fit-content;
+ }
+ }
+ }
+
+ &__empty {
+ text-align: center;
+ }
+
+ &-item {
+ position: relative;
+ display: grid;
+
+ &:nth-last-child(-n + 5) {
+ .load-strategy__recent-item__mobile {
+ top: -15rem;
+ &--min {
+ top: 1rem;
+ right: 6rem;
+ }
+ }
+ }
+
+ @include tablet {
+ grid-template-columns: 4fr 4fr 4fr 1fr;
+ }
+
+ @include mobile {
+ grid-template-columns: 4fr 4fr 4fr 1fr;
+ }
+
+ @include desktop {
+ grid-template-columns: repeat(3, 1fr) 1.5fr;
+ }
+
+ grid-template-areas: ('text location');
+ padding: 0.8rem;
+ align-items: center;
+ text-align: center;
+
+ @include mobile {
+ //TODO: without it doesn't work as expected, improve later
+ grid-template-columns: 1fr 0.8fr 0.6fr 0.3fr;
+ }
+
+ & .dc-text {
+ &:hover {
+ cursor: pointer;
+ }
+
+ @include mobile {
+ width: unset;
+ font-size: var(--text-size-xxs);
+ white-space: nowrap;
+ }
+ }
+
+ &--minimized {
+ grid-template-columns: 1fr 0.8fr 0.6fr 0.3fr;
+
+ .load-strategy__recent-item--minimized > div:last-child {
+ //iphone 14 specific
+ @media (min-height: 450px) and (max-height: 750px) {
+ padding-bottom: 10rem;
+ }
+ }
+ }
+
+ &__mobile {
+ background: var(--general-main-1);
+ min-width: 15rem;
+ box-shadow: 0 0 2rem rgba(0, 0, 0, 0.05), 0 1.6rem 2rem rgba(0, 0, 0, 0.05);
+ position: absolute;
+ right: 3rem;
+ top: 4rem;
+ z-index: 100;
+ display: none;
+
+ &--active {
+ display: block;
+ }
+ }
+
+ &__group {
+ display: flex;
+ align-items: center;
+ height: 4rem;
+ padding: 0.8rem 1.1rem;
+
+ &:last-child {
+ box-shadow: inset 0 0.1rem 0 $color-grey-2;
+ }
+
+ &__label {
+ margin-left: 0.8rem;
+ }
+ }
+
+ &.load-dialog {
+ padding: 1.6rem;
+ }
+
+ &__loaded {
+ text-align: left;
+ padding: 1rem 3rem;
+
+ @include tablet {
+ padding: 1rem 0.6rem;
+ }
+
+ @include mobile {
+ padding: 1rem 0.6rem;
+ }
+
+ .load-strategy__recent-item-text {
+ height: unset;
+ }
+ }
+
+ &__loaded--first-child {
+ border: none;
+
+ &:hover {
+ background: none;
+ background-color: none;
+ cursor: default;
+ }
+ }
+
+ &:not(:last-child) {
+ border-bottom: solid 1px var(--border-normal);
+ }
+
+ &--selected {
+ background-color: var(--state-active);
+ }
+
+ &-text {
+ display: flex;
+ justify-content: center;
+ width: 100%;
+ word-break: break-word;
+ margin-right: 1.6rem;
+ height: unset;
+
+ &.load-dialog {
+ display: flex;
+ text-align: left;
+ padding: 1.2rem 0;
+ }
+ }
+
+ &-title {
+ font-size: var(--text-size-xs);
+ color: var(--text-general);
+ width: 100%;
+ word-break: break-word;
+ margin-right: 1.6rem;
+ line-height: 1.25;
+ }
+
+ &-time {
+ font-size: var(--text-size-xxs);
+ color: var(--text-general);
+ width: 100%;
+ word-break: break-word;
+ margin-right: 1.6rem;
+ }
+
+ &-location {
+ display: flex;
+ align-items: center;
+ width: 100%;
+ word-break: break-word;
+ color: var(--text-general);
+
+ &.load-dialog {
+ justify-content: center;
+ }
+ }
+
+ &-saved {
+ margin-left: 1rem;
+ font-size: var(--text-size-xs);
+ line-height: 1.43;
+ word-break: break-word;
+ }
+
+ &__button {
+ display: flex;
+ margin-right: 2rem;
+ padding: 0.2rem;
+
+ &:last-child {
+ margin-right: 0;
+ }
+
+ align-items: center;
+ justify-content: flex-end;
+
+ .dc-icon {
+ &:hover {
+ cursor: pointer;
+ }
+ }
+ }
+ }
+ }
+
+ &__local {
+ &-dropzone {
+ @include desktop {
+ padding-top: 1.6rem;
+ }
+
+ @include mobile {
+ height: 100%;
+ padding: 1.6rem;
+ }
+
+ &-area {
+ align-items: center;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ border: dashed 0.2rem var(--border-normal);
+ border-radius: $BORDER_RADIUS;
+ height: 100%;
+ padding: 1.6rem;
+ }
+ }
+
+ &-icon {
+ margin-bottom: 1.6rem;
+ }
+
+ &-title {
+ margin-bottom: 1.6rem;
+ font-size: var(--text-size-s);
+ }
+
+ &-description {
+ margin-bottom: 1.6rem;
+ font-size: var(--text-size-xs);
+ }
+
+ &-preview {
+ display: flex;
+ flex-direction: column;
+ position: relative;
+
+ @include mobile {
+ padding: 1.6rem;
+ height: calc(100% - 7.4rem); // - footer height
+ &--active {
+ height: calc(70% - 7.4rem);
+ }
+ }
+
+ &--listed {
+ @include mobile {
+ padding: 0;
+ }
+ }
+
+ &-close {
+ background-image: radial-gradient(at right top);
+ position: absolute;
+ padding: 2.5rem;
+ border-bottom-left-radius: 50%;
+ right: 0;
+ top: 0;
+ z-index: 99;
+ cursor: pointer;
+ }
+ }
+
+ &-footer {
+ padding: 1.6rem;
+ display: flex;
+ justify-content: flex-end;
+ border-top: 1px solid var(--general-section-1);
+ }
+ }
+
+ &__google-drive {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+
+ @include mobile {
+ border: dashed 0.2rem var(--border-normal);
+ border-radius: $BORDER_RADIUS;
+ margin: 1.6rem;
+ padding: 1.6rem;
+ height: calc(100% - 3.2rem); // - 2x margin.
+ }
+
+ &-icon {
+ margin-bottom: 1.6rem;
+
+ &--disabled {
+ opacity: 0.32;
+ }
+ }
+
+ &-connected-text {
+ margin-bottom: 1.6rem;
+ font-size: var(--text-size-s);
+ font-weight: bold;
+ line-height: 2.4rem;
+ color: var(--text-general);
+ }
+
+ &-terms {
+ font-size: var(--text-size-xs);
+ line-height: 2rem;
+ margin: 1.6rem 0;
+ text-align: center;
+ color: var(--text-general);
+ }
+
+ &-text {
+ margin-bottom: 1rem;
+ }
+ }
+}
+
+.picker {
+ max-width: 98%;
+ border-radius: 0.8rem;
+ max-height: 99%;
+
+ &-content {
+ max-width: 98%;
+ padding: 1%;
+ }
+
+ @include mobile {
+ height: 100%;
+ width: 100%;
+ top: 0;
+ }
+}
+
+.dc-modal__container_load-strategy {
+ @include tablet {
+ width: calc(100vw - 4.8rem) !important;
+ }
+}
+
+.dc-dialog {
+ @include mobile {
+ &__wrapper--preview {
+ top: 6.5rem;
+
+ .dc-dialog {
+ &__dialog {
+ justify-content: flex-start;
+ min-width: 100vw;
+ min-height: 100vh;
+ padding: 0;
+ }
+
+ &__header {
+ &-wrapper {
+ margin-bottom: 0;
+
+ h1 {
+ width: 100%;
+ text-align: center;
+ }
+
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 4rem;
+ box-shadow: inset 0 -0.1rem 0 $color-grey-2;
+ }
+
+ &--close {
+ padding: 1.6rem;
+ margin-top: -1rem;
+ }
+ }
+
+ &__content {
+ max-width: unset;
+
+ .injectionDiv {
+ margin: 2.4rem;
+ height: calc(100vh - 26.5rem);
+ border: 1px solid $color-grey-6;
+ }
+
+ .load-strategy {
+ &__preview-workspace {
+ &-container {
+ position: relative;
+ margin-bottom: 3.2rem;
+ min-height: 33rem;
+ }
+
+ &-controls {
+ right: 3.6rem;
+ }
+ }
+
+ &__button-group {
+ box-shadow: inset 0 0.2rem 0rem $color-grey-2;
+ margin: 0;
+ padding: 1.6rem;
+ }
+ }
+ }
+
+ &__footer {
+ display: none;
+ }
+ }
+ }
+ }
+}
+
+.toolbar {
+ &__dialog {
+ transition: none;
+ }
+}
diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/index.ts b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/index.ts
new file mode 100644
index 000000000000..c9c70417bd2c
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/index.ts
@@ -0,0 +1,4 @@
+import LoadBotPreview from './local';
+import './index.scss';
+
+export default LoadBotPreview;
diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/local-footer.tsx b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/local-footer.tsx
new file mode 100644
index 000000000000..bd73f3be1675
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/local-footer.tsx
@@ -0,0 +1,54 @@
+import React from 'react';
+import { Button } from '@deriv/components';
+import { isMobile } from '@deriv/shared';
+import { localize } from '@deriv/translations';
+import { connect } from 'Stores/connect';
+import RootStore from 'Stores/index';
+import './index.scss';
+
+type Nullable = T | null;
+type TLocalFooter = {
+ is_open_button_loading: boolean;
+ loadFileFromLocal: () => void;
+ setLoadedLocalFile: (data: Nullable) => void;
+ setPreviewOnPopup: (param: boolean) => boolean;
+ toggleLoadModal: () => void;
+};
+
+const LocalFooter = ({
+ is_open_button_loading,
+ loadFileFromLocal,
+ setLoadedLocalFile,
+ setPreviewOnPopup,
+ toggleLoadModal,
+}: TLocalFooter) => {
+ const is_mobile = isMobile();
+ const Wrapper = is_mobile ? Button.Group : React.Fragment;
+ return (
+
+ {is_mobile && (
+ setLoadedLocalFile(null)} has_effect secondary large />
+ )}
+ {
+ loadFileFromLocal();
+ setPreviewOnPopup(false);
+ toggleLoadModal();
+ }}
+ is_loading={is_open_button_loading}
+ has_effect
+ primary
+ large
+ />
+
+ );
+};
+
+export default connect(({ load_modal, dashboard }: RootStore) => ({
+ is_open_button_loading: load_modal.is_open_button_loading,
+ loadFileFromLocal: load_modal.loadFileFromLocal,
+ setLoadedLocalFile: load_modal.setLoadedLocalFile,
+ setPreviewOnPopup: dashboard.setPreviewOnPopup,
+ toggleLoadModal: load_modal.toggleLoadModal,
+}))(LocalFooter);
diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/local.tsx b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/local.tsx
new file mode 100644
index 000000000000..a53650b5054c
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/local.tsx
@@ -0,0 +1,169 @@
+import React from 'react';
+import classNames from 'classnames';
+import { Dialog, Icon, MobileWrapper, Text } from '@deriv/components';
+import { isMobile } from '@deriv/shared';
+import { Localize, localize } from '@deriv/translations';
+import { DBOT_TABS } from 'Constants/bot-contents';
+import { clearInjectionDiv } from 'Constants/load-modal';
+import { connect } from 'Stores/connect';
+import RootStore from 'Stores/index';
+import BotPreview from './bot-preview';
+import './index.scss';
+
+type TWorkspace = {
+ id: string;
+ xml: string;
+ name: string;
+ timestamp: number;
+ save_type: string;
+};
+
+type Nullable = T | null;
+type TLocalComponent = {
+ handleFileChange: (e: React.ChangeEvent, data: boolean) => boolean;
+ loadFileFromRecent: () => void;
+ onConfirmSave: () => void;
+ onDrop: () => void;
+ previewRecentStrategy: () => void;
+ setActiveTab: (param: number) => void;
+ dashboard_strategies: Array;
+ setFileLoaded: (param: boolean) => void;
+ setLoadedLocalFile: (data: Nullable) => void;
+ setTourDialogVisibility: (param: boolean) => boolean;
+ setPreviewOnDialog: (param: boolean) => boolean;
+ has_mobile_preview_loaded: boolean;
+ setActiveTabTutorial: (param: boolean) => void;
+};
+
+const LocalComponent = ({
+ handleFileChange,
+ loadFileFromRecent,
+ onConfirmSave,
+ setActiveTab,
+ dashboard_strategies,
+ setPreviewOnDialog,
+ has_mobile_preview_loaded,
+ setActiveTabTutorial,
+}: TLocalComponent) => {
+ const file_input_ref = React.useRef(null);
+ const [is_file_supported, setIsFileSupported] = React.useState(true);
+ const el_ref = React.useRef(null);
+ const is_mobile = isMobile();
+ const has_dashboard_strategies = !!dashboard_strategies?.length;
+
+ React.useEffect(() => {
+ if (el_ref.current?.children.length === 3) {
+ el_ref?.current?.removeChild(el_ref?.current?.children[1]);
+ }
+ }, [el_ref.current?.children.length]);
+
+ const renderOpenButton = () => (
+ {
+ setPreviewOnDialog(false);
+ loadFileFromRecent();
+ setActiveTab(DBOT_TABS.BOT_BUILDER);
+ }}
+ >
+ {localize('Open')}
+
+ );
+ return (
+
+ {is_file_supported && (
+
+
+
+ {!is_mobile &&
}
+
+ {
+ setActiveTab(DBOT_TABS.TUTORIAL);
+ setActiveTabTutorial(0);
+ }}
+ >
+
+ {!is_mobile && (
+
+ {localize('User Guide')}
+
+ )}
+
+
+
+
+ {!is_mobile && (
+ <>
+
+
+
+
+ {
+ clearInjectionDiv('component', el_ref);
+ onConfirmSave();
+ setIsFileSupported(handleFileChange(e, false));
+ }}
+ />
+ {renderOpenButton()}
+
+ >
+ )}
+
+ setPreviewOnDialog(false)}
+ is_mobile_full_width
+ className='dc-dialog__wrapper--preview'
+ has_close_icon
+ title={localize('Preview')}
+ >
+
+ {renderOpenButton()}
+
+
+
+
+ )}
+
+ );
+};
+
+const Local = connect(({ load_modal, save_modal, dashboard }: RootStore) => ({
+ handleFileChange: load_modal.handleFileChange,
+ is_open_button_loading: load_modal.is_open_button_loading,
+ setLoadedLocalFile: load_modal.setLoadedLocalFile,
+ dashboard_strategies: load_modal.dashboard_strategies,
+ onConfirmSave: save_modal.onConfirmSave,
+ setActiveTab: dashboard.setActiveTab,
+ loadFileFromRecent: load_modal.loadFileFromRecent,
+ setFileLoaded: dashboard.setFileLoaded,
+ setPreviewOnDialog: dashboard.setPreviewOnDialog,
+ setActiveTabTutorial: dashboard.setActiveTabTutorial,
+ has_mobile_preview_loaded: dashboard.has_mobile_preview_loaded,
+}))(LocalComponent);
+
+export default Local;
diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/recent-footer.tsx b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/recent-footer.tsx
new file mode 100644
index 000000000000..a70673bf0266
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/recent-footer.tsx
@@ -0,0 +1,29 @@
+import React from 'react';
+import { Button } from '@deriv/components';
+import { localize } from '@deriv/translations';
+import { connect } from 'Stores/connect';
+import RootStore from 'Stores/index';
+import './index.scss';
+
+type TRecentFooter = {
+ is_open_button_loading: boolean;
+ loadFileFromRecent: () => void;
+};
+
+const RecentFooter = ({ is_open_button_loading, loadFileFromRecent }: TRecentFooter) => {
+ return (
+
+ );
+};
+
+export default connect(({ load_modal }: RootStore) => ({
+ is_open_button_loading: load_modal.is_open_button_loading,
+ loadFileFromRecent: load_modal.loadFileFromRecent,
+}))(RecentFooter);
diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/recent-workspace.tsx b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/recent-workspace.tsx
new file mode 100644
index 000000000000..8aa4f481ecfc
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/recent-workspace.tsx
@@ -0,0 +1,207 @@
+import React from 'react';
+import classnames from 'classnames';
+import { timeSince } from '@deriv/bot-skeleton';
+import { save_types } from '@deriv/bot-skeleton/src/constants/save-type';
+import { DesktopWrapper, Icon, MobileWrapper, Text } from '@deriv/components';
+import { isDesktop, isMobile } from '@deriv/shared';
+import { DBOT_TABS } from 'Constants/bot-contents';
+import { connect } from 'Stores/connect';
+import RootStore from 'Stores/index';
+import { useComponentVisibility } from '../../hooks/useComponentVisibility';
+import { CONTEXT_MENU_MOBILE, MENU_DESKTOP, STRATEGY } from './constants';
+import './index.scss';
+
+type TRecentWorkspace = {
+ active_tab: number;
+ dashboard_strategies: [];
+ getRecentFileIcon: (string: string) => void;
+ getSaveType: (type: string) => string;
+ index: number;
+ getSelectedStrategyID: (current_workspace_id: string) => void;
+ loadFileFromRecent: () => void;
+ onToggleDeleteDialog: (is_delete_modal_open: boolean) => void;
+ previewRecentStrategy: (workspaceId: string) => void;
+ selected_strategy_id: string;
+ setActiveTab: (active_tab: number) => void;
+ setPreviewOnDialog: (has_mobile_preview_loaded: boolean) => void;
+ toggleSaveModal: () => void;
+ workspace: { [key: string]: string };
+};
+
+const RecentWorkspace = ({
+ active_tab,
+ dashboard_strategies,
+ getRecentFileIcon,
+ getSaveType,
+ index,
+ loadFileFromRecent,
+ onToggleDeleteDialog,
+ previewRecentStrategy,
+ selected_strategy_id,
+ getSelectedStrategyID,
+ setActiveTab,
+ setPreviewOnDialog,
+ toggleSaveModal,
+ workspace,
+}: TRecentWorkspace) => {
+ const trigger_div_ref = React.useRef(null);
+ const toggle_ref = React.useRef(null);
+ const visible = useComponentVisibility(toggle_ref);
+ const { setDropdownVisibility, is_dropdown_visible } = visible;
+ const is_mobile = isMobile();
+ const is_desktop = isDesktop();
+
+ React.useEffect(() => {
+ if (dashboard_strategies && dashboard_strategies.length && index === 0) {
+ setTimeout(() => {
+ trigger_div_ref?.current?.click();
+ }, 50);
+ }
+ }, []);
+
+ const onToggleDropdown = (e: React.MouseEvent) => {
+ e.preventDefault();
+ setDropdownVisibility(!is_dropdown_visible);
+ };
+
+ const viewRecentStrategy = (type: string) => {
+ if (is_mobile && type === STRATEGY.PREVIEW_LIST) {
+ setPreviewOnDialog(true);
+ setTimeout(() => {
+ previewRecentStrategy(workspace.id);
+ }, 0); // made this async to give it a split second delay
+ }
+ if (selected_strategy_id !== workspace.id || (active_tab === 0 && type === STRATEGY.PREVIEW)) {
+ setTimeout(() => {
+ previewRecentStrategy(workspace.id);
+ }, 0); // made this async to give it a split second delay
+ }
+
+ switch (type) {
+ case 'edit': {
+ loadFileFromRecent();
+ setActiveTab(DBOT_TABS.BOT_BUILDER);
+ break;
+ }
+ case 'save': {
+ toggleSaveModal();
+ break;
+ }
+ case 'delete': {
+ onToggleDeleteDialog(true);
+ break;
+ }
+ default:
+ break;
+ }
+ };
+
+ const is_active_mobile = selected_strategy_id === workspace.id && is_dropdown_visible;
+
+ return (
+ {
+ e.stopPropagation(); //stop event bubbling for child element
+ if (is_dropdown_visible) setDropdownVisibility(false);
+ viewRecentStrategy(STRATEGY.PREVIEW);
+ getSelectedStrategyID(workspace.id);
+ }}
+ >
+
+
+
+ {workspace.name}
+
+
+
+
+
+ {timeSince(workspace.timestamp)}
+
+
+
+
+
+
+ {getSaveType(workspace.save_type)}
+
+
+
+
+
+ {MENU_DESKTOP.map(item => (
+
{
+ viewRecentStrategy(item.type);
+ }}
+ >
+
+
+ ))}
+
+
+
+
+
+
+
+ {CONTEXT_MENU_MOBILE.map(item => (
+
{
+ viewRecentStrategy(item.type);
+ }}
+ >
+
+
+
+
+ {item.label}
+
+
+ ))}
+
+
+
+ );
+};
+
+export default connect(({ load_modal, dashboard, save_modal }: RootStore) => ({
+ active_tab: dashboard.active_tab,
+ dashboard_strategies: load_modal.dashboard_strategies,
+ getRecentFileIcon: load_modal.getRecentFileIcon,
+ getSaveType: load_modal.getSaveType,
+ getSelectedStrategyID: load_modal.getSelectedStrategyID,
+ loadFileFromRecent: load_modal.loadFileFromRecent,
+ onToggleDeleteDialog: load_modal.onToggleDeleteDialog,
+ previewRecentStrategy: load_modal.previewRecentStrategy,
+ selected_strategy_id: load_modal.selected_strategy_id,
+ setActiveTab: dashboard.setActiveTab,
+ setPreviewOnDialog: dashboard.setPreviewOnDialog,
+ toggleSaveModal: save_modal.toggleSaveModal,
+}))(RecentWorkspace);
diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/recent.tsx b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/recent.tsx
new file mode 100644
index 000000000000..3652b6931f24
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/recent.tsx
@@ -0,0 +1,87 @@
+import React from 'react';
+import classNames from 'classnames';
+import { getSavedWorkspaces } from '@deriv/bot-skeleton';
+import { MobileWrapper, Text } from '@deriv/components';
+import { isMobile } from '@deriv/shared';
+import { Localize } from '@deriv/translations';
+import { connect } from 'Stores/connect';
+import RootStore from 'Stores/index';
+import { TWorkspace } from 'Stores/load-modal-store';
+import SaveModal from '../../../save-modal';
+import DeleteDialog from './delete-dialog';
+import RecentWorkspace from './recent-workspace';
+import './index.scss';
+
+type TRecentComponent = {
+ dashboard_strategies: Array;
+ setDashboardStrategies: (strategies: Array) => void;
+ setStrategySaveType: (param: string) => void;
+ strategy_save_type: string;
+};
+
+const HEADERS = ['Bot name', 'Last modified', 'Status'];
+
+const RecentComponent = ({
+ dashboard_strategies,
+ setDashboardStrategies,
+ setStrategySaveType,
+ strategy_save_type,
+}: TRecentComponent) => {
+ React.useEffect(() => {
+ setStrategySaveType('');
+ const getStrategies = async () => {
+ const recent_strategies = await getSavedWorkspaces();
+ setDashboardStrategies(recent_strategies);
+ };
+ getStrategies();
+ //this dependency is used when we use the save modal popup
+ }, [strategy_save_type]);
+
+ const is_mobile = isMobile();
+
+ if (!dashboard_strategies?.length) return null;
+ return (
+
+
+
+
+
+
+
+
+
+
+ {HEADERS.map(tab_name => {
+ return (
+
+ {tab_name}
+
+ );
+ })}
+
+ {dashboard_strategies.map((workspace, index) => {
+ return
;
+ })}
+
+
+
+
+
+
+
+
+ );
+};
+
+const Recent = connect(({ load_modal, dashboard }: RootStore) => ({
+ dashboard_strategies: load_modal.dashboard_strategies,
+ setDashboardStrategies: load_modal.setDashboardStrategies,
+ setStrategySaveType: dashboard.setStrategySaveType,
+ strategy_save_type: dashboard.strategy_save_type,
+}))(RecentComponent);
+
+export default Recent;
diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/save-modal.tsx b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/save-modal.tsx
new file mode 100644
index 000000000000..47f800ece5e5
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/save-modal.tsx
@@ -0,0 +1,238 @@
+import React from 'react';
+import classNames from 'classnames';
+import { Field, Form, Formik } from 'formik';
+import { config, save_types } from '@deriv/bot-skeleton';
+import {
+ Button,
+ Checkbox,
+ Icon,
+ Input,
+ MobileFullPageModal,
+ Modal,
+ RadioGroup,
+ Text,
+ ThemedScrollbars,
+} from '@deriv/components';
+import { isMobile } from '@deriv/shared';
+import { Localize, localize } from '@deriv/translations';
+import { connect } from 'Stores/connect';
+import IconRadio from './icon-radio';
+
+type TSaveModalForm = {
+ bot_name: string;
+ button_status: number;
+ google_drive_connected?: boolean;
+ is_authorised: boolean;
+ is_mobile?: boolean;
+ is_onscreen_keyboard_active?: boolean;
+ is_save_modal_open?: boolean;
+ icon?: string;
+ text?: string;
+ onConfirmSave: () => void;
+ onDriveConnect: () => void;
+ toggleSaveModal: () => void;
+ setCurrentFocus: () => void;
+ validateBotName: () => void;
+};
+
+const SaveModalForm = ({
+ bot_name,
+ button_status,
+ is_authorised,
+ onConfirmSave,
+ onDriveConnect,
+ validateBotName,
+ toggleSaveModal,
+ is_mobile,
+ is_onscreen_keyboard_active,
+ setCurrentFocus,
+}: TSaveModalForm) => (
+
+ {({ values: { is_local, save_as_collection }, setFieldValue, touched, errors }) => {
+ const content_height = !is_mobile ? '500px' : `calc(100%)`;
+ return (
+
+
+
+ );
+ }}
+
+);
+const SaveModal = ({
+ bot_name,
+ button_status,
+ is_authorised,
+ is_save_modal_open,
+ onConfirmSave,
+ onDriveConnect,
+ toggleSaveModal,
+ validateBotName,
+ setCurrentFocus,
+ is_onscreen_keyboard_active,
+}: TSaveModalForm) => {
+ const is_mobile = isMobile();
+ return is_mobile ? (
+
+
+
+ ) : (
+
+
+
+ );
+};
+
+export default connect(({ save_modal, google_drive, ui }) => ({
+ button_status: save_modal.button_status,
+ is_authorised: google_drive.is_authorised,
+ is_save_modal_open: save_modal.is_save_modal_open,
+ is_onscreen_keyboard_active: ui.is_onscreen_keyboard_active,
+ onConfirmSave: save_modal.onConfirmSave,
+ onDriveConnect: save_modal.onDriveConnect,
+ toggleSaveModal: save_modal.toggleSaveModal,
+ validateBotName: save_modal.validateBotName,
+ bot_name: save_modal.bot_name,
+ setCurrentFocus: ui.setCurrentFocus,
+}))(SaveModal);
diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/stop-bot-modal.tsx b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/stop-bot-modal.tsx
new file mode 100644
index 000000000000..e0a6fbafbf1b
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/stop-bot-modal.tsx
@@ -0,0 +1,43 @@
+import React from 'react';
+import { connect } from 'Stores/connect';
+import RootStore from 'Stores/root-store';
+import StopBotModalContent, { TStopBotModalContent } from '../stop-bot-modal-content';
+
+type TStopBotModal = TStopBotModalContent & {
+ stopMyBot: () => void;
+};
+
+const StopBotModal = ({
+ is_running,
+ is_contract_dialog_open,
+ is_stop_bot_dialog_open,
+ is_multiplier,
+ is_dialog_open,
+ closeMultiplierContract,
+ stopMyBot,
+ toggleStopBotDialog,
+}: TStopBotModal) => (
+
+);
+
+export default connect(({ run_panel, quick_strategy, summary_card }: RootStore) => ({
+ is_dialog_open: quick_strategy.is_dialog_open,
+ is_running: run_panel.is_running,
+ is_multiplier: summary_card.is_multiplier,
+ is_contract_dialog_open: quick_strategy.is_contract_dialog_open,
+ is_stop_bot_dialog_open: quick_strategy.is_stop_bot_dialog_open,
+ closeMultiplierContract: run_panel.closeMultiplierContract,
+ onOkButtonClick: run_panel.onOkButtonClick,
+ stopMyBot: run_panel.stopMyBot,
+ toggleStopBotDialog: quick_strategy.toggleStopBotDialog,
+ is_strategy_modal_open: quick_strategy.is_strategy_modal_open,
+}))(StopBotModal);
diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/workspace-control.tsx b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/workspace-control.tsx
new file mode 100644
index 000000000000..f657c674529a
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/workspace-control.tsx
@@ -0,0 +1,29 @@
+import React from 'react';
+import { Icon } from '@deriv/components';
+import { connect } from 'Stores/connect';
+import RootStore from 'Stores/index';
+
+type TWorkspaceControl = {
+ onZoomInOutClick: (param: boolean) => void;
+};
+
+const WorkspaceControl = ({ onZoomInOutClick }: TWorkspaceControl) => (
+
+ onZoomInOutClick(true)}
+ />
+ onZoomInOutClick(false)}
+ />
+
+);
+
+export default connect(({ dashboard }: RootStore) => ({
+ onZoomInOutClick: dashboard.onZoomInOutClick,
+}))(WorkspaceControl);
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
new file mode 100644
index 000000000000..c985dc6662ea
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/run-strategy.tsx
@@ -0,0 +1,10 @@
+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/dashboard-component/sidebar.tsx b/packages/bot-web-ui/src/components/dashboard/dashboard-component/sidebar.tsx
new file mode 100644
index 000000000000..18983c8264b5
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/sidebar.tsx
@@ -0,0 +1,42 @@
+import React from 'react';
+import classNames from 'classnames';
+import { Icon } from '@deriv/components';
+import { SIDEBAR_INTRO } from './constants';
+import IntroCard from './intro-card';
+
+type TSideBar = {
+ is_sidebar_open: boolean;
+ setSideBarState: (state: boolean) => void;
+};
+
+const Sidebar = ({ setSideBarState, is_sidebar_open }: TSideBar) => {
+ return (
+
+
+ {
+ setSideBarState(false);
+ }}
+ />
+
+ {SIDEBAR_INTRO.map(sidebar_item => {
+ const { label } = sidebar_item;
+ return (
+
+ ;
+
+ );
+ })}
+
+ );
+};
+
+export default Sidebar;
diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/stop-bot-modal-content.tsx b/packages/bot-web-ui/src/components/dashboard/dashboard-component/stop-bot-modal-content.tsx
new file mode 100644
index 000000000000..49d612739534
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/stop-bot-modal-content.tsx
@@ -0,0 +1,81 @@
+import React from 'react';
+import { Dialog, Text } from '@deriv/components';
+import { localize } from '@deriv/translations';
+
+export type TStopBotModalContent = {
+ is_running: boolean;
+ is_dialog_open: boolean;
+ is_contract_dialog_open: boolean;
+ is_stop_bot_dialog_open: boolean;
+ is_multiplier: boolean;
+ closeMultiplierContract: () => void;
+ onOkButtonClick: () => void;
+ toggleStopBotDialog: () => void;
+};
+
+const StopBotModalContent = ({
+ is_running,
+ is_multiplier,
+ is_contract_dialog_open,
+ is_stop_bot_dialog_open,
+ closeMultiplierContract,
+ onOkButtonClick,
+ toggleStopBotDialog,
+}: TStopBotModalContent) => {
+ const confirm_button_text = is_running && is_multiplier ? localize('Keep my contract') : localize('Stop my bot');
+ const cancel_button_text = is_running && is_multiplier ? localize('Close my contract') : localize('Back');
+ const title_text =
+ is_running && is_multiplier ? localize('Keep your current contract?') : localize('Stop your current bot?');
+ const toggle_dialog_or_stop = is_running && is_multiplier ? closeMultiplierContract : toggleStopBotDialog;
+
+ return (
+
+
+ {is_running && is_multiplier ? (
+ <>
+
+ {localize(
+ 'Close your contract now or keep it running. If you decide to keep it running, you can check and close it later on the '
+ )}
+
+ {localize('Reports')}
+
+ {localize(' page.')}
+
+
+ {localize('The Quick Strategy you just created will be loaded to the workspace.')}
+
+ >
+ ) : (
+ <>
+
+ {localize(
+ 'Stopping the current bot will load the Quick Strategy you just created to the workspace.'
+ )}
+
+
+ {localize(' Any open contracts can be viewed on the ')}
+
+ {localize('Reports')}
+
+ {localize(' page.')}
+
+ >
+ )}
+
+
+ );
+};
+
+export default StopBotModalContent;
diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/user-guide.tsx b/packages/bot-web-ui/src/components/dashboard/dashboard-component/user-guide.tsx
new file mode 100644
index 000000000000..dac85f096871
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/user-guide.tsx
@@ -0,0 +1,35 @@
+import React from 'react';
+import { Icon, Text } from '@deriv/components';
+import { localize } from '@deriv/translations';
+import { DBOT_TABS } from 'Constants/bot-contents';
+
+type TUserGuide = {
+ setActiveTab: (param: number) => void;
+ setActiveTabTutorial: (active_tab_tutorials: number) => void;
+};
+
+const UserGuide = ({ setActiveTab, setActiveTabTutorial }: TUserGuide) => {
+ return (
+
+ {
+ setActiveTab(DBOT_TABS.TUTORIAL);
+ setActiveTabTutorial(0);
+ }}
+ >
+
+
+ {localize('User Guide')}
+
+
+
+ );
+};
+
+export default UserGuide;
diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard.scss b/packages/bot-web-ui/src/components/dashboard/dashboard.scss
index 6ec317a459ff..0662098752f4 100644
--- a/packages/bot-web-ui/src/components/dashboard/dashboard.scss
+++ b/packages/bot-web-ui/src/components/dashboard/dashboard.scss
@@ -1,7 +1,684 @@
+@import '../dashboard/dashboard-component/index.scss';
+@import '../dashboard/tutorial-tab/index.scss';
+
+.position-center {
+ display: flex;
+ justify-content: center;
+}
+
.dashboard {
+ &__main {
+ width: 100%;
+ height: calc(100vh - 9rem);
+ padding: 1.6rem;
+ display: flex;
+ overflow: hidden;
+
+ @include mobile {
+ height: calc(100vh - 6rem);
+ padding: 0;
+ }
+ }
+
&__container {
- padding: 1.2rem;
- max-width: 100rem;
- border: solid 1px var(--general-section-1);
+ position: relative;
+ border: none;
+ max-width: 100%;
+ width: 100%;
+
+ &--active {
+ //css for onboard tour overlay
+ &:before {
+ content: '';
+ position: fixed;
+ left: 0;
+ top: 0px;
+ width: 100%;
+ height: 100%;
+ background: rgba(0, 0, 0, 0.72);
+ opacity: 0.7;
+ z-index: 100;
+ }
+ }
+
+ .dc-tabs {
+ &__active-line {
+ background-color: var(--general-main-1);
+ transition: none;
+ }
+
+ &__active {
+ background: var(--general-main-1);
+ }
+
+ &__list {
+ background: var(--general-section-1);
+ justify-content: start;
+
+ @include mobile {
+ overflow-x: auto;
+ overflow-y: hidden;
+
+ &::-webkit-scrollbar {
+ display: none;
+ }
+
+ -ms-overflow-style: none;
+ scrollbar-width: none;
+ }
+
+ &--header {
+ &--dashboard {
+ &__tabs {
+ @include mobile {
+ width: 100%;
+ overflow-x: auto;
+ padding: 0.8rem 1.6rem 0;
+ }
+ }
+ }
+ }
+
+ &--border-bottom {
+ @include mobile {
+ border-bottom: 0;
+ }
+ }
+ }
+
+ &__content {
+ @include mobile {
+ width: 100%;
+ overflow-x: auto;
+ }
+
+ &--dashboard {
+ &__tabs {
+ display: flex;
+ justify-content: space-between;
+
+ @include mobile {
+ flex-direction: column;
+ }
+ }
+ }
+ }
+
+ &__item {
+ border-top-left-radius: 1rem;
+ border-top-right-radius: 1rem;
+ height: 4.8rem;
+ padding: 0 2.4rem;
+ width: auto;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ &__loaded {
+ @include mobile {
+ text-align: left;
+ }
+
+ padding-left: 1.5rem;
+ }
+
+ svg {
+ width: 1.6rem;
+ height: 1.6rem;
+ padding-right: 0;
+ margin-right: 0.8rem;
+
+ @include mobile {
+ margin-right: 0.2rem;
+ // gave this -ve margin becaues we cannot
+ // overwrite the default core padding which
+ // will make the icon bigger
+ margin-top: -0.5rem;
+ }
+ }
+ }
+ }
+ }
+
+ &__chart-wrapper {
+ position: relative;
+ height: var(--tab-content-height);
+ width: 100%;
+ transition: all 0.4s;
+
+ @include mobile {
+ height: var(--tab-content-height-mobile);
+ background: var(--general-main-1);
+
+ .smartcharts {
+ height: calc(100% - 4rem);
+ z-index: 1;
+
+ &:has(.stxMenuActive) {
+ z-index: 99;
+ transition: all 1s ease-out;
+ }
+ }
+ }
+
+ &--expanded {
+ width: calc(100vw - 39.8rem);
+ }
+ }
+
+ &__toolbox {
+ position: absolute;
+ top: -4rem;
+ left: 0;
+ width: 23.6rem;
+ padding: 0.8rem;
+ background: var(--general-main-1);
+ z-index: 1;
+
+ .db-toolbox {
+ &__title {
+ height: 2.6rem;
+ line-height: 2.6rem;
+ position: relative;
+ cursor: pointer;
+
+ &__chevron {
+ position: absolute;
+ right: 0;
+ top: 0.3rem;
+ transition: transform 0.3s ease;
+
+ &--active {
+ transform: rotate(180deg);
+ top: -0.2rem;
+ }
+ }
+ }
+
+ &__content {
+ border: solid 1px var(--general-section-1);
+ }
+
+ &__category-menu {
+ height: calc(100vh - 33.6rem);
+ overflow-y: scroll;
+ }
+
+ &__search {
+ padding: 1.3rem 0.8rem;
+ height: 6rem;
+ }
+
+ &__content-wrapper {
+ height: 0;
+ }
+
+ &__content-wrapper.active {
+ height: 100%;
+ }
+ }
+ }
+
+ &__run-strategy-wrapper {
+ @include desktop {
+ position: absolute;
+ }
+
+ right: 1.6rem;
+ top: 1.6rem;
+ z-index: 2;
+
+ .animation {
+ &__button {
+ background-color: var(--purchase-main-1);
+ }
+
+ &__container {
+ background-color: var(--general-main-1);
+ }
+ }
+ }
+
+ &__sidebar-wrapper {
+ &--active {
+ position: fixed;
+ right: 0;
+ }
+
+ &--hidden {
+ display: none;
+ }
+ }
+}
+
+.bot-builder {
+ position: absolute;
+ top: 6.6rem;
+ left: 1.6rem;
+ z-index: -1;
+ background: var(--general-main-1);
+ height: var(--tab-content-height);
+
+ .injectionDiv {
+ height: calc(100vh - 20rem);
+ .blocklyTrash {
+ transition: all 0.4s;
+ @include mobile {
+ display: none;
+ }
+ }
+ }
+ &--tour-active {
+ .blocklyTrash {
+ display: none;
+ }
+ }
+
+ &--active {
+ z-index: 1;
+ }
+
+ &--inactive {
+ display: none;
+ }
+
+ @include mobile {
+ top: 5.6rem;
+ left: 0;
+ width: 100vw;
+ height: var(--tab-content-height-mobile);
+ }
+}
+
+.onboard {
+ &__header {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 2.4rem;
+ margin-top: -2rem;
+
+ &--close {
+ cursor: pointer;
+ }
+ }
+
+ &__label {
+ text-align: left;
+ margin-bottom: 2.4rem;
+ }
+
+ &__container {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ justify-content: center;
+ margin-bottom: 2.4rem;
+
+ img {
+ width: 97%;
+ }
+ }
+
+ &__content {
+ text-align: left;
+
+ &__block {
+ &:first-child {
+ margin-bottom: 2.4rem;
+ }
+ }
+ }
+}
+
+.dbot-onboarding__container {
+ img {
+ width: 100%;
+ }
+}
+
+@for $i from 0 through 6 {
+ #react-joyride-step-#{$i} {
+ .onboard__container {
+ .initial-loader {
+ width: 33rem;
+
+ @if ($i ==0 or $i ==5) {
+ height: 18.6rem;
+ } @else if ($i ==1) {
+ height: 18.4rem;
+ } @else if ($i ==2 or $i ==3) {
+ height: 18.7rem;
+ } @else if ($i ==6) {
+ height: 4.4rem;
+ }
+ }
+ @if ($i ==5) {
+ img {
+ width: 100%;
+ }
+ }
+ }
+ }
+}
+
+.joyride-content {
+ &__left {
+ text-align: left;
+
+ &__step-three {
+ margin-bottom: 0.8rem;
+ }
+ }
+
+ &__sub-title {
+ margin-top: 1rem;
+ }
+
+ ul {
+ list-style: disc;
+ margin-left: 4rem;
+ margin-top: 1rem;
+
+ li {
+ margin-bottom: 1rem;
+ }
+ }
+
+ img {
+ width: 100%;
+ margin-top: 1rem;
+ }
+
+ &__with-icon {
+ display: flex;
+ margin-bottom: 1rem;
+
+ &__right {
+ margin-left: 0.8rem;
+ }
+
+ &__left {
+ margin: 0.3rem 0 0 1.2rem;
+
+ @include mobile {
+ margin: 0;
+
+ svg {
+ margin: 0;
+ }
+ }
+ }
+ }
+}
+
+.dbot-slider {
+ display: flex;
+ justify-content: flex-start;
+ flex-direction: column;
+ align-items: center;
+ position: fixed;
+ left: 0;
+ bottom: 0;
+ z-index: 1000;
+ background-color: var(--general-section-1);
+ width: 100%;
+ border-top: solid 1px var(--border-normal);
+ height: 100vh;
+ padding: 1.6rem;
+
+ &--tour-position {
+ top: 0;
+ .progress-bar-circle:first-child {
+ display: none;
+ }
+ }
+
+ &--active {
+ height: auto;
+ }
+
+ &__navbar {
+ display: flex;
+ justify-content: space-between;
+ padding: 0 1.6rem;
+ width: 100%;
+ margin-bottom: 1.8rem;
+ }
+
+ &__title {
+ margin-bottom: 0.8rem;
+ }
+
+ &__content {
+ text-align: center;
+ padding: 0 1.6rem;
+ margin-bottom: 1.6rem;
+
+ &:last-child {
+ margin-bottom: 3rem;
+ }
+ }
+
+ &__image {
+ background: var(--general-section-1);
+ text-align: center;
+ padding: 0.8rem;
+ width: 100%;
+ margin-bottom: 0.8rem;
+ height: 50%;
+ }
+
+ img {
+ height: 100%;
+ }
+
+ &__progress-bar {
+ margin-bottom: 1.6rem;
+ }
+
+ &__status {
+ width: 100%;
+ margin-bottom: 1.6rem;
+ }
+
+ &__button-group {
+ display: flex;
+ padding: 0 1.6rem;
+
+ button {
+ height: 4rem;
+ width: 100%;
+ border: 0.2rem solid $color-grey-1;
+ background: $color-white;
+ border-radius: 0.4rem;
+ outline: none;
+ cursor: pointer;
+ margin-right: 0.8rem;
+
+ &.default {
+ background: transparent;
+ }
+
+ &.danger {
+ background: $color-red;
+ border: none;
+ margin-right: 0;
+
+ span {
+ color: $color-white;
+ }
+ }
+ }
+ }
+
+ &__bot-builder-tour {
+ height: unset;
+ background: $color-black-3;
+ border-top: 0.1rem solid $color-black-5;
+
+ .dbot-slider {
+ width: 100%;
+
+ &__accordion {
+ width: 100%;
+
+ .dc-accordion {
+ &__item {
+ border: none;
+ border-bottom: 0.1rem solid $color-black-5;
+
+ &-header {
+ color: $color-white;
+ text-align: center;
+ font-weight: 700;
+ font-size: 1.4rem;
+
+ &-icon {
+ filter: invert(1);
+ }
+ }
+
+ &-content {
+ color: $color-white;
+ }
+ }
+ }
+ }
+
+ &__content {
+ color: $color-white;
+ }
+
+ &__status {
+ height: 5rem;
+ display: flex;
+ @extend .position-center;
+ justify-content: flex-start;
+ }
+
+ &__progress-bar {
+ width: 50%;
+ margin-bottom: unset;
+ @extend .position-center;
+ align-items: center;
+ justify-content: flex-start;
+
+ .progress-bar-circle {
+ opacity: 0.16;
+ }
+ }
+
+ &__button-group {
+ width: 50%;
+ @extend .position-center;
+
+ .default {
+ background: transparent;
+
+ span {
+ color: $color-white;
+ }
+ }
+ }
+ }
+ }
+}
+
+.dbot-accordion {
+ width: 100%;
+
+ &__navbar {
+ display: flex;
+ width: 100%;
+ padding: 0.6rem 1.6rem;
+ border-bottom: solid 0.1rem $color-black-5;
+ cursor: pointer;
+ }
+
+ &__header {
+ width: 90%;
+ text-align: center;
+
+ span {
+ color: $color-white;
+ }
+ }
+
+ &__icon {
+ width: 10%;
+ color: $color-white;
+ text-align: end;
+
+ svg {
+ filter: invert(1);
+ }
+ }
+
+ &__content {
+ background-color: $color-black-3;
+ overflow: hidden;
+ transition: all 0.4s ease-in-out;
+ max-height: 0;
+ opacity: 0;
+ color: $color-white;
+ padding: 1rem 0;
+ font-size: 1.2rem;
+
+ &--open {
+ opacity: 1;
+ max-height: 50rem;
+ }
+
+ .joyride-content {
+ line-height: 1.8rem;
+ }
+ }
+}
+
+.bot-notification {
+ .dc-toast__message {
+ background: var(--text-prominent);
+ font-size: 1.2rem;
+ position: absolute;
+ top: -5rem;
+ left: 20rem;
+
+ @include mobile {
+ top: -12rem;
+ left: 10rem;
+ }
+ }
+}
+
+@keyframes blink {
+ 0%,
+ 100% {
+ border: 1px solid $color-red;
+ }
+
+ 50% {
+ border: 1px solid transparent;
+ }
+}
+
+.dbot-tour-blink {
+ -webkit-animation: blink 1.25s infinite;
+ -moz-animation: blink 1.25s ease-in infinite;
+ animation: blink 1.25s ease-in infinite;
+ border-radius: $BORDER-RADIUS * 12.5;
+ height: 2.5rem !important;
+ width: 2.5rem !important;
+ padding: 0.2rem 0;
+}
+
+.import-notification {
+ position: fixed;
+ z-index: 1;
+ left: 0;
+ bottom: 7rem;
+
+ .dc-toast__message {
+ background: var(--text-general);
+ color: var(--general-main-1);
}
}
diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard.tsx b/packages/bot-web-ui/src/components/dashboard/dashboard.tsx
index 841994e8de94..88491d3d57ac 100644
--- a/packages/bot-web-ui/src/components/dashboard/dashboard.tsx
+++ b/packages/bot-web-ui/src/components/dashboard/dashboard.tsx
@@ -1,31 +1,295 @@
import React from 'react';
-import { Tabs } from '@deriv/components';
+import classNames from 'classnames';
+import { initTrashCan } from '@deriv/bot-skeleton/src/scratch/hooks/trashcan';
+import { DesktopWrapper, Dialog, MobileWrapper, Tabs } from '@deriv/components';
+import { isMobile } from '@deriv/shared';
import { localize } from '@deriv/translations';
+import Chart from 'Components/chart';
+import { DBOT_TABS, TAB_IDS } from 'Constants/bot-contents';
+import { connect } from 'Stores/connect';
+import RootStore from 'Stores/index';
+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,
+ getTourSettings,
+ setTourSettings,
+ setTourType,
+ tour_status_ended,
+ tour_type,
+} from './joyride-config';
+import ReactJoyrideWrapper from './react-joyride-wrapper';
+import TourSlider from './tour-slider';
+import TourTriggrerDialog from './tour-trigger-dialog';
+import Tutorial from './tutorial-tab';
-const Dashboard: React.FC = () => {
- const [active_index, setActiveTabIndex] = React.useState(0);
+type TDialogOptions = {
+ title: string;
+ message: string;
+ cancel_button_text?: string;
+ ok_button_text?: string;
+};
+
+type TDashboard = {
+ active_tab: number;
+ dialog_options: TDialogOptions;
+ has_started_bot_builder_tour: boolean;
+ has_file_loaded: boolean;
+ has_started_onboarding_tour: boolean;
+ has_tour_started: boolean;
+ is_dialog_open: boolean;
+ is_drawer_open: boolean;
+ is_tour_dialog_visible: boolean;
+ is_strategy_modal_open: boolean;
+ onCancelButtonClick: () => void;
+ onCloseDialog: () => void;
+ onEntered: () => void;
+ onOkButtonClick: () => void;
+ setActiveTab: (active_tab: number) => void;
+ setBotBuilderTourState: (param: boolean) => void;
+ setOnBoardTourRunState: (param: boolean) => void;
+ setBotBuilderTokenCheck: (param: string | number) => void;
+ setOnBoardingTokenCheck: (param: string | number) => void;
+ setTourActive: (param: boolean) => void;
+ setTourDialogVisibility: (param: boolean) => void;
+ setHasTourEnded: (param: boolean) => void;
+};
+
+const Dashboard = ({
+ active_tab,
+ is_drawer_open,
+ dialog_options,
+ has_file_loaded,
+ has_tour_started,
+ has_started_onboarding_tour,
+ has_started_bot_builder_tour,
+ is_dialog_open,
+ is_tour_dialog_visible,
+ is_strategy_modal_open,
+ onCancelButtonClick,
+ onCloseDialog,
+ onEntered,
+ onOkButtonClick,
+ setActiveTab,
+ setBotBuilderTokenCheck,
+ setBotBuilderTourState,
+ setOnBoardingTokenCheck,
+ setOnBoardTourRunState,
+ setTourActive,
+ setTourDialogVisibility,
+ setHasTourEnded,
+}: TDashboard) => {
+ const { DASHBOARD, BOT_BUILDER, CHART } = DBOT_TABS;
+ const is_tour_complete = React.useRef(true);
+ let bot_tour_token: string | number = '';
+ let onboard_tour_token: string | number = '';
+ let storage = '';
+ let tour_status: { [key: string]: string };
+ const is_mobile = isMobile();
+
+ const setTourStatus = (status: { [key: string]: string }) => {
+ if (status) {
+ const { action } = status;
+ const actions = ['skip', 'close'];
+
+ if (actions.includes(action)) {
+ if (tour_type.key === 'bot_builder') {
+ setBotBuilderTourState(false);
+ } else {
+ setOnBoardTourRunState(false);
+ }
+ setTourActive(false);
+ }
+ }
+ };
+
+ React.useEffect(() => {
+ if (active_tab === BOT_BUILDER) {
+ if (is_drawer_open) {
+ initTrashCan(400);
+ } else {
+ initTrashCan(20);
+ }
+ setTimeout(() => {
+ window.dispatchEvent(new Event('resize')); // make the trash can work again after resize
+ }, 500);
+ }
+ if (active_tab === DASHBOARD && has_file_loaded) {
+ onEntered();
+ }
+ if (active_tab === DASHBOARD) {
+ setTourType('onboard_tour');
+ onboard_tour_token = getTourSettings('token');
+ setOnBoardingTokenCheck(onboard_tour_token);
+ }
+ if (active_tab === BOT_BUILDER && !has_started_onboarding_tour) {
+ setTourType('bot_builder');
+ bot_tour_token = getTourSettings('token');
+ setBotBuilderTokenCheck(bot_tour_token);
+ }
+
+ if (!is_tour_dialog_visible) {
+ window.removeEventListener('storage', botStorageSetting);
+ }
+ tour_status = getTourSettings('onboard_tour_status');
+ setTourStatus(tour_status);
+ }, [active_tab, is_drawer_open, has_started_onboarding_tour, tour_status_ended, is_tour_dialog_visible]);
+
+ const botStorageSetting = () => {
+ tour_status = getTourSettings('bot_builder_status');
+ if (tour_status_ended.key === 'finished' && !is_mobile) {
+ setTourDialogVisibility(true);
+ setHasTourEnded(true);
+ is_tour_complete.current = false;
+ window.removeEventListener('storage', botStorageSetting);
+ }
+ setTourStatus(tour_status);
+ bot_tour_token = getTourSettings('token');
+ if (active_tab === 1 && !storage.bot_builder_token && !has_started_onboarding_tour) {
+ setTourSettings(new Date().getTime(), `${tour_type.key}_token`);
+ }
+ };
+ if (!bot_tour_token && !is_mobile && !has_started_onboarding_tour) {
+ window.addEventListener('storage', botStorageSetting);
+ }
+
+ if (localStorage?.dbot_settings !== undefined) {
+ storage = JSON.parse(localStorage?.dbot_settings);
+ }
+
+ React.useEffect(() => {
+ const dbot_settings = JSON.parse(localStorage.getItem('dbot_settings') as string);
+ const has_onboard_token_set = active_tab === DASHBOARD && !dbot_settings?.onboard_tour_token;
+ const has_bot_builder_token_set = active_tab === BOT_BUILDER && !dbot_settings?.bot_builder_token;
+ const show_tour_dialog_desktop = (active_tab === DASHBOARD && !is_mobile) || active_tab === BOT_BUILDER;
+ const show_tour_dialog_mobile = active_tab !== DASHBOARD && is_mobile;
+ if (has_bot_builder_token_set || has_onboard_token_set) {
+ if (is_mobile && has_started_onboarding_tour) {
+ setTourActive(true);
+ setOnBoardTourRunState(true);
+ } else {
+ setHasTourEnded(false);
+ if (show_tour_dialog_mobile || show_tour_dialog_desktop) {
+ setTourDialogVisibility(true);
+ } else {
+ setTourActive(true);
+ setOnBoardTourRunState(true);
+ }
+ }
+ }
+ if (has_started_bot_builder_tour && active_tab !== BOT_BUILDER && is_mobile) {
+ setTourActive(false);
+ setBotBuilderTourState(false);
+ setTourSettings(new Date().getTime(), `${tour_type.key}_token`);
+ }
+ }, [active_tab]);
+
+ const handleTabChange = React.useCallback(
+ (tab_index: number) => {
+ setActiveTab(tab_index);
+ const el_id = TAB_IDS[tab_index];
+ if (el_id) {
+ const el_tab = document.getElementById(el_id);
+ el_tab?.scrollIntoView({
+ behavior: 'smooth',
+ block: 'center',
+ inline: 'center',
+ });
+ }
+ },
+ [active_tab]
+ );
return (
-
-
- {/* [Todo] needs to update tabs component children instead of using label property */}
-
-
-
Create or start a bot
-
-
-
-
-
Contennt 3
+
+
+
+
+ {has_tour_started &&
+ active_tab === DASHBOARD &&
+ (is_mobile ? (
+
+ ) : (
+
+ ))}
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+ {([BOT_BUILDER, CHART].includes(active_tab) || has_started_onboarding_tour) &&
+ !has_started_bot_builder_tour && }
-
-
+
+ {!is_strategy_modal_open && }
+
+ {dialog_options.message}
+
+
+
);
};
-export default Dashboard;
+export default connect(({ dashboard, run_panel, load_modal, quick_strategy }: RootStore) => ({
+ active_tab: dashboard.active_tab,
+ has_file_loaded: dashboard.has_file_loaded,
+ has_tour_started: dashboard.has_tour_started,
+ setTourActive: dashboard.setTourActive,
+ has_started_onboarding_tour: dashboard.has_started_onboarding_tour,
+ setOnBoardTourRunState: dashboard.setOnBoardTourRunState,
+ setBotBuilderTourState: dashboard.setBotBuilderTourState,
+ onEntered: load_modal.onEntered,
+ setTourDialogVisibility: dashboard.setTourDialogVisibility,
+ setHasTourEnded: dashboard.setHasTourEnded,
+ is_dialog_open: run_panel.is_dialog_open,
+ is_drawer_open: run_panel.is_drawer_open,
+ has_started_bot_builder_tour: dashboard.has_started_bot_builder_tour,
+ is_tour_dialog_visible: dashboard.is_tour_dialog_visible,
+ dialog_options: run_panel.dialog_options,
+ onCancelButtonClick: run_panel.onCancelButtonClick,
+ onCloseDialog: run_panel.onCloseDialog,
+ onOkButtonClick: run_panel.onOkButtonClick,
+ setActiveTab: dashboard.setActiveTab,
+ setBotBuilderTokenCheck: dashboard.setBotBuilderTokenCheck,
+ setOnBoardingTokenCheck: dashboard.setOnBoardingTokenCheck,
+ has_tour_ended: dashboard.has_tour_ended,
+ is_strategy_modal_open: quick_strategy.is_strategy_modal_open,
+}))(Dashboard);
diff --git a/packages/bot-web-ui/src/components/dashboard/hooks/index.ts b/packages/bot-web-ui/src/components/dashboard/hooks/index.ts
new file mode 100644
index 000000000000..d2f3289652d8
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/hooks/index.ts
@@ -0,0 +1 @@
+export * from './useComponentVisibility';
diff --git a/packages/bot-web-ui/src/components/dashboard/hooks/useComponentVisibility.tsx b/packages/bot-web-ui/src/components/dashboard/hooks/useComponentVisibility.tsx
new file mode 100644
index 000000000000..9e0589fa5c5f
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/hooks/useComponentVisibility.tsx
@@ -0,0 +1,35 @@
+import React from 'react';
+
+type TComponentVisibility = {
+ ref?: React.RefObject
;
+};
+
+export const useComponentVisibility = (ref: TComponentVisibility) => {
+ const [is_dropdown_visible, setDropdownVisibility] = React.useState(false);
+
+ const handleHideDropdown = (event: KeyboardEvent) => {
+ if (event.key.toUpperCase() === 'ESCAPE') {
+ setDropdownVisibility(false);
+ }
+ };
+
+ const handleClickOutside = (event: Event) => {
+ if (!ref?.current?.contains(event.target as Node)) {
+ setDropdownVisibility(false);
+ }
+ };
+
+ React.useEffect(() => {
+ document.addEventListener('keydown', handleHideDropdown, true);
+ document.addEventListener('click', handleClickOutside, true);
+ return () => {
+ document.removeEventListener('keydown', handleHideDropdown, true);
+ document.removeEventListener('click', handleClickOutside, true);
+ };
+ }, []);
+
+ return {
+ is_dropdown_visible,
+ setDropdownVisibility,
+ };
+};
diff --git a/packages/bot-web-ui/src/components/dashboard/joyride-config.tsx b/packages/bot-web-ui/src/components/dashboard/joyride-config.tsx
new file mode 100644
index 000000000000..96cad2a8f764
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/joyride-config.tsx
@@ -0,0 +1,556 @@
+import { getImageLocation } from '../../public-path';
+import React from 'react';
+import { CallBackProps } from 'react-joyride';
+import { Icon, Text } from '@deriv/components';
+import { Localize, localize } from '@deriv/translations';
+import { getSetting, storeSetting } from '../../utils/settings';
+import TourGuide from './tour-guide';
+
+type TJoyrideConfig = Record<
+ 'showProgress' | 'spotlightClicks' | 'disableBeacon' | 'disableOverlay' | 'disableCloseOnEsc',
+ boolean
+>;
+
+type TStep = {
+ label?: string;
+ content: Array;
+ type?: 'list' | 'text';
+};
+
+type TTourStatus = {
+ key: string;
+ toggle: string;
+ type: string;
+};
+
+export type TTourType = Pick;
+
+export const setTourSettings = (param: number | { [key: string]: string }, type: string) => {
+ if (type === `${tour_type.key}_token`) {
+ return storeSetting(`${tour_type.key}_token`, param);
+ }
+ return storeSetting(`${tour_type.key}_status`, param);
+};
+
+export const getTourSettings = (type: string) => {
+ if (type === 'token') {
+ return getSetting(`${tour_type.key}_token`);
+ }
+ return getSetting(`${tour_type.key}_status`);
+};
+
+export const Step = ({ label, content }: TStep) => {
+ return (
+
+
+ {label}
+
+ {content.map(item => item)}
+
+ );
+};
+
+export const tour_type: TTourType = {
+ key: 'onboard_tour',
+};
+
+export const setTourType = (param: string) => {
+ tour_type.key = param;
+};
+
+export const tour_status_ended: TTourStatus = {
+ key: '',
+ toggle: '',
+ type: `${tour_type.key}_status`,
+};
+
+let tour: { [key: string]: string } = {};
+let current_target: number | undefined;
+export const handleJoyrideCallback = (data: CallBackProps) => {
+ const { action, index, status } = data;
+ if (status === 'finished') {
+ tour_status_ended.key = status;
+ }
+ if (action === 'close') {
+ tour_status_ended.toggle = action;
+ }
+ if (current_target !== index) {
+ tour = {};
+ tour.status = status;
+ tour.action = action;
+ }
+ current_target = index;
+ setTourSettings(tour, 'tour');
+ //added trigger to create new listner on local storage
+ window.dispatchEvent(new Event('storage'));
+ setTourSettings(new Date().getTime(), `${tour_type.key}_token`);
+};
+
+const joyride_props: TJoyrideConfig = {
+ showProgress: false,
+ spotlightClicks: false,
+ disableBeacon: true,
+ disableOverlay: true,
+ disableCloseOnEsc: true,
+};
+
+export const DBOT_ONBOARDING = [
+ {
+ target: '#id-bot-builder',
+ content: (
+
+ ),
+ ...joyride_props,
+ disableOverlay: false,
+ },
+ {
+ target: '#id-charts',
+ content: (
+
+ ),
+ ...joyride_props,
+ disableOverlay: false,
+ },
+ {
+ target: '#id-tutorials',
+ content: (
+
+ ),
+ ...joyride_props,
+ disableOverlay: false,
+ },
+ {
+ target: '#tab__dashboard__table__tiles',
+ content: (
+
+ ),
+ placement: 'right',
+ ...joyride_props,
+ disableOverlay: false,
+ },
+ {
+ target: '[data-testid="drawer"]',
+ content: (
+
+ ),
+ placement: 'left',
+ ...joyride_props,
+ disableOverlay: false,
+ },
+ {
+ target: '.animation__wrapper',
+ content: (
+
+ ),
+ locale: { last: localize('Next') },
+ ...joyride_props,
+ disableOverlay: false,
+ },
+];
+
+const Step1 = ({ show_label = false }) => (
+
+ {show_label && (
+
+
+
+ )}
+
+ Trade parameters0> block.`}
+ components={[ ]}
+ />
+
+
+ ]} />
+
+
+
+
+ ]}
+ />
+
+
+ ]}
+ />
+
+
+ ]}
+ />
+
+
+ ]}
+ />
+
+
+
+
+);
+
+const Step1A = () => (
+
+
+ ]}
+ />
+
+
+
+
+ ]} />
+
+
+ ]}
+ />
+
+
+
+
+);
+
+const Step2 = ({ show_label = false }) => (
+
+ {show_label && (
+
+
+
+ )}
+
+
+ ]}
+ />
+
+
+ ]} />
+
+
+
+);
+
+const Step3 = ({ show_label = false }) => (
+
+ {show_label && (
+
+
+
+ )}
+
+
+ ]}
+ />
+
+
+);
+
+const Step4 = ({ show_label = false }) => (
+
+ {show_label && (
+
+
+
+ )}
+
+
+ ]}
+ />
+
+
+
+
+ ]}
+ />
+
+
+ ]}
+ />
+
+
+
+
+
+
+
+
+);
+
+const Step5 = ({ show_label = false }) => (
+
+ {show_label && (
+
+
+
+ )}
+
+
+ ]}
+ />
+
+
+
+
+ ]}
+ />
+
+
+ ]}
+ />
+
+
+ ]}
+ />
+
+
+ ]}
+ />
+
+
+
+
+
+
+
+);
+
+const Step6 = ({ show_label = false }) => (
+
+ {show_label && (
+
+
+
+ )}
+
+
+ ]}
+ />
+
+
+);
+
+export const BOT_BUILDER_TOUR = [
+ {
+ target: '.animation__wrapper',
+ content: ,
+ placement: 'right',
+ ...joyride_props,
+ },
+ {
+ target: '.animation__wrapper',
+ content: ,
+ placement: 'bottom',
+ ...joyride_props,
+ },
+ {
+ target: '.animation__wrapper',
+ content: ,
+ placement: 'right',
+ ...joyride_props,
+ },
+ {
+ target: '.animation__wrapper',
+ content: ,
+ placement: 'right',
+ ...joyride_props,
+ },
+ {
+ target: '.animation__wrapper',
+ content: ,
+ placement: 'right',
+ ...joyride_props,
+ },
+ {
+ target: '.animation__wrapper',
+ content: ,
+ placement: 'right',
+ ...joyride_props,
+ },
+ {
+ target: '.animation__wrapper',
+ content: ,
+ locale: { last: localize('Next') },
+ ...joyride_props,
+ },
+];
+
+export type TStepMobile = {
+ header: string;
+ content: React.ReactElement;
+ key: number;
+};
+
+export const BOT_BUILDER_MOBILE: TStepMobile[] = [
+ {
+ header: localize('Step 1'),
+ content: ,
+ key: 1,
+ },
+ {
+ header: localize('Step 2'),
+ content: (
+
+ ),
+ key: 2,
+ },
+ {
+ header: localize('Step 3'),
+ content: (
+
+ ),
+ key: 3,
+ },
+];
+
+export const DBOT_ONBOARDING_MOBILE = [
+ {
+ header: localize('Get started on Deriv Bot'),
+ content: [
+ ]}
+ />,
+ ],
+ key: 1,
+ step_key: 0,
+ },
+ {
+ header: localize('Build from scratch'),
+ img: getImageLocation('dbot-mobile-onboarding-step-1.gif'),
+ content: [
+ localize(
+ 'Import a bot from your mobile device or from Google drive, see a preview in the bot builder, and start trading by running the bot, or choose from our pre-made Quick Strategies. '
+ ),
+ ],
+ key: 2,
+ step_key: 1,
+ },
+ {
+ header: localize('Monitor the market'),
+ img: getImageLocation('dbot-mobile-onboarding-step-2.png'),
+ content: [localize('View the market price of your favourite assets.')],
+ key: 3,
+ step_key: 2,
+ },
+ {
+ header: localize('Guides and FAQs to help you'),
+ img: getImageLocation('dbot-mobile-onboarding-step-3.gif'),
+ content: [localize('Start with a video guide and the FAQs.')],
+ key: 4,
+ step_key: 3,
+ },
+ {
+ header: localize('Shortcuts'),
+ img: getImageLocation('dbot-mobile-onboarding-step-4.png'),
+ content: [localize('You can also use these shortcuts to import or build your bot.')],
+ key: 5,
+ step_key: 4,
+ },
+ {
+ header: localize('How is my bot doing?'),
+ img: getImageLocation('dbot-mobile-onboarding-step-5.gif'),
+ content: [localize("See your bot's performance in real-time.")],
+ key: 6,
+ step_key: 5,
+ },
+ {
+ header: localize('Run or stop your bot'),
+ img: getImageLocation('dbot-mobile-onboarding-step-6.gif'),
+ content: [localize('Click Run when you want to start trading, and click Stop when you want to stop.')],
+ key: 7,
+ step_key: 6,
+ },
+];
diff --git a/packages/bot-web-ui/src/components/dashboard/quick-strategy/index.ts b/packages/bot-web-ui/src/components/dashboard/quick-strategy/index.ts
new file mode 100755
index 000000000000..8b8241c8f66c
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/quick-strategy/index.ts
@@ -0,0 +1,4 @@
+import QuickStrategy from './quick-strategy';
+import './quick-strategy.scss';
+
+export default QuickStrategy;
diff --git a/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/components.types.ts b/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/components.types.ts
new file mode 100644
index 000000000000..808d2df1db42
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/components.types.ts
@@ -0,0 +1,149 @@
+import { FormikErrors, FormikProps } from 'formik';
+import {
+ TCreateStrategy,
+ TDropdownItems,
+ TDropdowns,
+ TDurationOptions,
+ TDurationUnitDropdown,
+ TFormValues,
+ TGetSizeDesc,
+ TInputBaseFields,
+ TInputsFieldNames,
+ TMarketOption,
+ TOnChangeDropdownItem,
+ TOnChangeInputValue,
+ TOnHideDropdownList,
+ TOnScrollStopDropdownList,
+ TQuickStrategyFormValues,
+ TSelectedValuesSelect,
+ TSelectsFieldNames,
+ TSetCurrentFocus,
+ TSetFieldValue,
+ TSymbolDropdown,
+ TTradeType,
+ TTradeTypeDropdown,
+ TTypeStrategiesDropdown,
+ TTypeStrategy,
+} from '../quick-strategy.types';
+import { TCommonInputsProperties } from './data/common-input-properties';
+import { TDataUniqInput } from './data/data-uniq-input-obj';
+
+export type TQuickStrategyForm = {
+ active_index: number;
+ duration_unit_dropdown: TDurationUnitDropdown;
+ types_strategies_dropdown: TTypeStrategiesDropdown;
+ initial_values: TQuickStrategyFormValues;
+ is_onscreen_keyboard_active: boolean;
+ is_stop_button_visible: boolean;
+ symbol_dropdown: TSymbolDropdown;
+ trade_type_dropdown: TTradeTypeDropdown;
+ selected_symbol: TMarketOption;
+ selected_trade_type: TTradeType;
+ selected_duration_unit: TDurationOptions;
+ selected_type_strategy: TTypeStrategy;
+ description: string;
+ is_running: boolean;
+ is_contract_dialog_open: boolean;
+ is_stop_bot_dialog_open: boolean;
+ createStrategy: TCreateStrategy;
+ getSizeDesc: TGetSizeDesc;
+ onChangeDropdownItem: TOnChangeDropdownItem;
+ onChangeInputValue: TOnChangeInputValue;
+ onHideDropdownList: TOnHideDropdownList;
+ onScrollStopDropdownList: TOnScrollStopDropdownList;
+ setCurrentFocus: TSetCurrentFocus;
+ setActiveTab: (active_tab: number) => void;
+ toggleStopBotDialog: () => void;
+};
+
+export type TQuickStrategyFields = {
+ types_strategies_dropdown: TTypeStrategiesDropdown;
+ symbol_dropdown: TSymbolDropdown;
+ trade_type_dropdown: TTradeTypeDropdown;
+ duration_unit_dropdown: TDurationUnitDropdown;
+ selected_type_strategy: TTypeStrategy;
+ selected_trade_type: TTradeType;
+ selected_symbol: TMarketOption;
+ selected_duration_unit: TDurationOptions;
+ onChangeDropdownItem: TOnChangeDropdownItem;
+ onHideDropdownList: TOnHideDropdownList;
+ setFieldValue: TSetFieldValue;
+ onScrollStopDropdownList: TOnScrollStopDropdownList;
+ handleChange: FormikProps['handleChange'];
+ onChangeInputValue: TOnChangeInputValue;
+ setCurrentFocus: TSetCurrentFocus;
+ values: TFormValues;
+ description: string;
+ errors: FormikErrors;
+};
+
+export type TQuickStrategyFooter = {
+ is_submit_enabled: boolean;
+ is_stop_button_visible: boolean;
+ is_running: boolean;
+ is_contract_dialog_open: boolean;
+ is_stop_bot_dialog_open: boolean;
+ setFieldValue: TSetFieldValue;
+ submitForm: FormikProps['submitForm'];
+ toggleStopBotDialog: () => void;
+};
+
+export type TDropdownLists = Record;
+export type TSelectedValues = Record;
+
+export type TSelectFieldProps = React.PropsWithChildren<{
+ field_name: TSelectsFieldNames;
+ id: string;
+ is_mobile: boolean;
+ dropdown_list: TDropdowns;
+ selected_value: Partial;
+ label: string;
+ select_value: TDropdownItems;
+ setFieldValue: TSetFieldValue;
+ className?: string;
+ is_able_disabled?: boolean;
+ values: TFormValues;
+ onChangeDropdownItem: TOnChangeDropdownItem;
+ onHideDropdownList: TOnHideDropdownList;
+ onScrollStopDropdownList: TOnScrollStopDropdownList;
+ selected_trade_type: TTradeType;
+ selected_symbol: TMarketOption;
+}>;
+
+export type TInputFieldProps = React.PropsWithChildren<
+ {
+ idx?: number;
+ handleChange: FormikProps['handleChange'];
+ onChangeInputValue: TOnChangeInputValue;
+ setCurrentFocus: TSetCurrentFocus;
+ is_mobile: boolean;
+ field_name?: TInputsFieldNames;
+ id?: string;
+ label?: string;
+ input_value?: TInputBaseFields;
+ placeholder?: string;
+ is_uniq_strategy_field?: boolean;
+ trailing_icon_message?: string;
+ uniq_selected_input?: TDataUniqInput;
+ errors: FormikErrors;
+ } & TCommonInputsProperties
+>;
+
+export type TTradeTypeOptionProps = React.PropsWithChildren<{
+ trade_type: TTradeType;
+}>;
+
+export type TMarketOptionProps = React.PropsWithChildren<{
+ symbol: TMarketOption;
+}>;
+
+type TSelectsAdditionalProps = { select_value?: TDropdownItems; field_name: TSelectsFieldNames | TInputsFieldNames };
+export type TSelects = Omit & TSelectsAdditionalProps;
+
+export type TInputs = Omit & {
+ is_input_field: boolean;
+ field_name: TSelectsFieldNames | TInputsFieldNames;
+};
+
+export type TDurationFields = TSelects &
+ Pick;
diff --git a/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/container.tsx b/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/container.tsx
new file mode 100644
index 000000000000..6b97c61ca7f3
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/container.tsx
@@ -0,0 +1,87 @@
+import React from 'react';
+import { TQuickStrategyProps, TSymbolItem } from '../quick-strategy.types';
+import { MarketOption, QuickStrategyForm, TradeTypeOption } from '.';
+
+const QuickStrategyContainer = (props: TQuickStrategyProps) => {
+ const {
+ symbol_dropdown,
+ trade_type_dropdown,
+ active_index,
+ description,
+ duration_unit_dropdown,
+ types_strategies_dropdown,
+ initial_values,
+ is_onscreen_keyboard_active,
+ is_stop_button_visible,
+ selected_symbol,
+ selected_trade_type,
+ selected_duration_unit,
+ selected_type_strategy,
+ is_running,
+ is_contract_dialog_open,
+ is_stop_bot_dialog_open,
+ createStrategy,
+ getSizeDesc,
+ onChangeDropdownItem,
+ onChangeInputValue,
+ onHideDropdownList,
+ onScrollStopDropdownList,
+ setCurrentFocus,
+ toggleStopBotDialog,
+ } = props;
+
+ const symbol_dropdown_options = React.useMemo(
+ () =>
+ symbol_dropdown
+ .map((symbol: TSymbolItem) => ({
+ component: ,
+ ...symbol,
+ }))
+ // Until Crypto enabled for Dbot
+ .filter(option => option.group !== 'Cryptocurrencies')
+ .filter(
+ option => option.text !== 'Volatility 150 (1s) Index' && option.text !== 'Volatility 250 (1s) Index'
+ ),
+ [symbol_dropdown]
+ );
+
+ const trade_type_dropdown_options = React.useMemo(
+ () =>
+ trade_type_dropdown.map(trade_type => ({
+ component: ,
+ ...trade_type,
+ })),
+ [trade_type_dropdown]
+ );
+
+ return (
+
+ );
+};
+
+export default React.memo(QuickStrategyContainer);
diff --git a/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/data/common-input-properties.ts b/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/data/common-input-properties.ts
new file mode 100644
index 000000000000..60776d308c74
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/data/common-input-properties.ts
@@ -0,0 +1,13 @@
+import { popover_zindex } from 'Constants/z-indexes';
+
+export type TCommonInputsProperties = {
+ className?: string;
+ zIndex?: number;
+};
+
+const common_inputs_properties: Readonly = {
+ className: 'quick-strategy__input',
+ zIndex: popover_zindex.QUICK_STRATEGY,
+};
+
+export default common_inputs_properties;
diff --git a/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/data/data-fields.ts b/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/data/data-fields.ts
new file mode 100644
index 000000000000..7f9ab8d5a313
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/data/data-fields.ts
@@ -0,0 +1,94 @@
+import { TDropdownItems, TInputBaseFields, TInputsFieldNames, TSelectsFieldNames } from '../../quick-strategy.types';
+import { common_inputs_properties } from '..';
+import { TCommonInputsProperties } from './common-input-properties';
+
+const getMessage = (property: string) => `The bot will stop trading if your total ${property} exceeds this amount.`;
+
+export type TDataFields = {
+ id: string;
+ field_name: TSelectsFieldNames | TInputsFieldNames;
+ className?: string;
+ input_value?: TInputBaseFields;
+ select_value?: TDropdownItems;
+ label: string;
+ placeholder?: string;
+ trailing_icon_message?: string;
+ is_able_disabled?: boolean;
+ is_uniq_strategy_field?: boolean;
+} & Readonly;
+
+const data_fields: ReadonlyArray = [
+ {
+ id: 'type-strategy',
+ field_name: 'quick-strategy__type-strategy',
+ className: '',
+ select_value: 'type-strategy',
+ label: '',
+ },
+ {
+ id: 'symbol',
+ field_name: 'quick-strategy__symbol',
+ className: 'quick-strategy__dropdown quick-strategy__leading',
+ select_value: 'symbol',
+ label: 'Asset',
+ },
+ {
+ id: 'trade-type',
+ field_name: 'quick-strategy__trade-type',
+ className: 'quick-strategy__dropdown quick-strategy__leading',
+ select_value: 'trade-type',
+ label: 'Trade type',
+ },
+ {
+ id: 'duration-unit',
+ field_name: 'quick-strategy__duration-unit',
+ className: '',
+ select_value: 'duration-unit',
+ label: 'Duration unit',
+ is_able_disabled: true,
+ },
+ {
+ id: 'duration-value',
+ field_name: 'quick-strategy__duration-value',
+ input_value: 'input_duration_value',
+ label: 'Duration value',
+ placeholder: '5',
+ trailing_icon_message: 'The trade length of your purchased contract.',
+ ...common_inputs_properties,
+ },
+ {
+ id: 'stake',
+ field_name: 'quick-strategy__stake',
+ input_value: 'input_stake',
+ label: 'Initial stake',
+ placeholder: '10',
+ trailing_icon_message: 'The amount that you pay to enter a trade.',
+ ...common_inputs_properties,
+ },
+ {
+ id: 'loss',
+ field_name: 'quick-strategy__loss',
+ input_value: 'input_loss',
+ label: 'Loss threshold',
+ placeholder: '5000',
+ trailing_icon_message: getMessage('loss'),
+ ...common_inputs_properties,
+ },
+ {
+ id: 'strategy-data',
+ is_uniq_strategy_field: true,
+ label: '',
+ field_name: '',
+ },
+ {
+ id: 'profit',
+ field_name: 'quick-strategy__profit',
+ input_value: 'input_profit',
+ label: 'Profit threshold',
+ placeholder: '5000',
+ trailing_icon_message: getMessage('profit'),
+ ...common_inputs_properties,
+ },
+];
+
+export default data_fields;
diff --git a/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/data/data-uniq-input-obj.ts b/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/data/data-uniq-input-obj.ts
new file mode 100644
index 000000000000..f1c1e68dc002
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/data/data-uniq-input-obj.ts
@@ -0,0 +1,50 @@
+import QuickStrategyStore from 'Stores/quick-strategy-store';
+import RootStore from 'Stores/root-store';
+import { TInputUniqFields } from '../../quick-strategy.types';
+import common_inputs_properties, { TCommonInputsProperties } from './common-input-properties';
+
+const quick_strategy_store = new QuickStrategyStore(RootStore);
+
+export type TDataUniqInput = {
+ id: TFieldNameUniqInput;
+ field_name: TFieldNameUniqInput;
+ input_value: TInputUniqFields;
+ label: TFieldNameUniqLabel;
+ placeholder: string;
+ trailing_icon_message: string;
+} & Readonly;
+
+type TFieldNameUniqInput = 'martingale-size' | 'alembert-unit' | 'oscar-unit';
+type TFieldNameUniqLabel = 'Size' | '' | 'Units';
+
+const data_uniq_input_obj: ReadonlyArray = [
+ {
+ id: 'martingale-size',
+ field_name: 'martingale-size',
+ input_value: 'input_martingale_size',
+ label: 'Size',
+ placeholder: '2',
+ trailing_icon_message: quick_strategy_store.getSizeDesc(0),
+ ...common_inputs_properties,
+ },
+ {
+ id: 'alembert-unit',
+ field_name: 'alembert-unit',
+ input_value: 'input_alembert_unit',
+ label: 'Units',
+ placeholder: '2',
+ trailing_icon_message: quick_strategy_store.getSizeDesc(1),
+ ...common_inputs_properties,
+ },
+ {
+ id: 'oscar-unit',
+ field_name: 'oscar-unit',
+ input_value: 'input_oscar_unit',
+ label: 'Units',
+ placeholder: '2',
+ trailing_icon_message: quick_strategy_store.getSizeDesc(2),
+ ...common_inputs_properties,
+ },
+];
+
+export default data_uniq_input_obj;
diff --git a/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/description.tsx b/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/description.tsx
new file mode 100644
index 000000000000..ac68892b031d
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/description.tsx
@@ -0,0 +1,24 @@
+import React from 'react';
+import { Text } from '@deriv/components';
+import { isMobile } from '@deriv/shared';
+
+type TDescription = {
+ id: string;
+ description: string;
+};
+
+const Description = ({ id, description }: TDescription) => (
+
+ {id === 'type-strategy' && (
+
+
+ {description}
+
+
+ )}
+
+);
+
+Description.displayName = 'Description';
+
+export default React.memo(Description);
diff --git a/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/duration-fields.tsx b/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/duration-fields.tsx
new file mode 100644
index 000000000000..d445de6de724
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/duration-fields.tsx
@@ -0,0 +1,56 @@
+import React from 'react';
+import classNames from 'classnames';
+import { isMobile } from '@deriv/shared';
+import { TDropdownItems, TSelectsFieldNames } from '../quick-strategy.types';
+import { TDurationFields } from './components.types';
+import { SelectField } from '.';
+
+const DurationFields = ({
+ id,
+ field_name,
+ dropdown_list,
+ selected_value,
+ label,
+ select_value,
+ className,
+ is_able_disabled,
+ values,
+ selected_trade_type,
+ selected_symbol,
+ setFieldValue,
+ onChangeDropdownItem,
+ onHideDropdownList,
+ onScrollStopDropdownList,
+}: TDurationFields) => {
+ const is_mobile = isMobile();
+ return id === 'duration-unit' ? (
+
+
+
+ ) : (
+ <>>
+ );
+};
+
+export default React.memo(DurationFields);
diff --git a/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/fields.tsx b/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/fields.tsx
new file mode 100644
index 000000000000..e1841f17b133
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/fields.tsx
@@ -0,0 +1,169 @@
+import React from 'react';
+import { TDropdownItems, TDropdowns, TSelectedValuesSelect } from '../quick-strategy.types';
+import { TDropdownLists, TQuickStrategyFields, TSelectedValues } from './components.types';
+import { data_fields, data_uniq_input_obj, Description, DurationFields, Inputs, Selects } from '.';
+
+const QuickStrategyFields = React.memo(
+ ({
+ types_strategies_dropdown,
+ symbol_dropdown,
+ trade_type_dropdown,
+ duration_unit_dropdown,
+ selected_type_strategy,
+ selected_trade_type,
+ selected_symbol,
+ selected_duration_unit,
+ onChangeDropdownItem,
+ onHideDropdownList,
+ setFieldValue,
+ onScrollStopDropdownList,
+ handleChange,
+ onChangeInputValue,
+ setCurrentFocus,
+ values,
+ description,
+ errors,
+ }: TQuickStrategyFields) => {
+ const uniq_selected_input = React.useMemo(
+ () => data_uniq_input_obj.filter((_elem, index) => index === selected_type_strategy.index)[0],
+ [selected_type_strategy]
+ );
+
+ const dropdown_lists: TDropdownLists = {
+ symbol: symbol_dropdown,
+ 'trade-type': trade_type_dropdown,
+ 'duration-unit': duration_unit_dropdown,
+ 'type-strategy': types_strategies_dropdown,
+ };
+
+ const selected_values: TSelectedValues = {
+ symbol: selected_symbol,
+ 'trade-type': selected_trade_type,
+ 'duration-unit': selected_duration_unit,
+ 'type-strategy': selected_type_strategy,
+ };
+
+ const fields = React.useMemo(
+ () => {
+ return data_fields.map((item, idx) => {
+ const {
+ id,
+ field_name,
+ className,
+ input_value,
+ select_value,
+ label,
+ placeholder,
+ trailing_icon_message,
+ zIndex,
+ is_able_disabled,
+ } = item;
+
+ const is_uniq_strategy_field = item?.is_uniq_strategy_field;
+ const is_input_field = is_uniq_strategy_field || !!input_value;
+ const is_select_field = !!select_value;
+
+ const dropdown_list: TDropdowns = !is_uniq_strategy_field
+ ? dropdown_lists[id as TDropdownItems]
+ : [];
+
+ const selected_value: Partial = !is_uniq_strategy_field
+ ? selected_values[id as TDropdownItems]
+ : {};
+
+ return (
+
+ {id === 'duration-unit' && (
+
+ )}
+ {is_input_field && (
+
+ )}
+ {is_select_field && id !== 'duration-unit' && (
+
+ )}
+
+
+ );
+ });
+ },
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ [
+ types_strategies_dropdown,
+ symbol_dropdown,
+ trade_type_dropdown,
+ duration_unit_dropdown,
+ selected_type_strategy,
+ selected_trade_type,
+ selected_symbol,
+ selected_duration_unit,
+ errors,
+ ]
+ );
+
+ return {fields}
;
+ },
+ (prevProps, nextProps) =>
+ prevProps.values === nextProps.values &&
+ prevProps.errors === nextProps.errors &&
+ prevProps.duration_unit_dropdown === nextProps.duration_unit_dropdown
+);
+
+QuickStrategyFields.displayName = 'QuickStrategyFields';
+
+export default QuickStrategyFields;
diff --git a/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/footer.tsx b/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/footer.tsx
new file mode 100644
index 000000000000..7cadb6bacd6a
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/footer.tsx
@@ -0,0 +1,58 @@
+import React from 'react';
+import { Button } from '@deriv/components';
+import { isDesktop } from '@deriv/shared';
+import { localize } from '@deriv/translations';
+import { TQuickStrategyFooter } from './components.types';
+
+const QuickStrategyFooter = ({
+ is_submit_enabled,
+ is_running,
+ setFieldValue,
+ submitForm,
+ toggleStopBotDialog,
+}: TQuickStrategyFooter) => {
+ const handleCreateEdit = React.useCallback(() => {
+ setFieldValue('button', 'edit');
+ submitForm();
+ }, [is_submit_enabled]);
+
+ const handleRun = React.useCallback(async () => {
+ if (is_running) {
+ await Promise.resolve(setFieldValue('button', 'edit'))
+ .then(() => submitForm())
+ .then(() => toggleStopBotDialog());
+ } else {
+ setFieldValue('button', 'run');
+ submitForm();
+ }
+ }, [is_submit_enabled]);
+
+ return (
+
+
+ {isDesktop() && (
+
+ )}
+
+
+
+ );
+};
+
+export default React.memo(QuickStrategyFooter);
diff --git a/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/form.tsx b/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/form.tsx
new file mode 100644
index 000000000000..78fef4482991
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/form.tsx
@@ -0,0 +1,139 @@
+import React from 'react';
+import classNames from 'classnames';
+import { Form, Formik, FormikProps } from 'formik';
+import * as Yup from 'yup';
+import { Text, ThemedScrollbars } from '@deriv/components';
+import { isMobile, isSafari } from '@deriv/shared';
+import { localize } from '@deriv/translations';
+import { TQuickStrategyFormValues } from '../quick-strategy.types';
+import { TQuickStrategyForm } from './components.types';
+import { QuickStrategyFields, QuickStrategyFooter } from '.';
+
+const QuickStrategyForm = ({
+ duration_unit_dropdown,
+ types_strategies_dropdown,
+ initial_values,
+ is_onscreen_keyboard_active,
+ is_stop_button_visible,
+ symbol_dropdown,
+ trade_type_dropdown,
+ selected_symbol,
+ selected_trade_type,
+ selected_duration_unit,
+ selected_type_strategy,
+ description,
+ is_contract_dialog_open,
+ is_stop_bot_dialog_open,
+ is_running,
+ createStrategy,
+ onChangeDropdownItem,
+ onChangeInputValue,
+ onHideDropdownList,
+ onScrollStopDropdownList,
+ setCurrentFocus,
+ toggleStopBotDialog,
+}: TQuickStrategyForm) => {
+ const { min, max } = selected_duration_unit;
+
+ const setDefaultValidationNumber = () =>
+ Yup.number()
+ .typeError(localize('Must be a number'))
+ .round('ceil')
+ .min(1, localize('Must be a number higher than 0'));
+
+ const SchemaFields = Yup.object().shape({
+ 'quick-strategy__duration-value': Yup.number()
+ .typeError(localize('Must be a number'))
+ .required(localize('Field cannot be empty'))
+ .min(min, localize('Minimum duration: {{ min }}', { min }))
+ .max(max, localize('Maximum duration: {{ max }}', { max })),
+ 'quick-strategy__stake': setDefaultValidationNumber(),
+ 'quick-strategy__loss': setDefaultValidationNumber(),
+ 'martingale-size': Yup.number()
+ .typeError(localize('Must be a number'))
+ .round('floor')
+ .min(2, localize('The value must be equal to or greater than 2.')),
+ 'alembert-unit': setDefaultValidationNumber(),
+ 'oscar-unit': setDefaultValidationNumber(),
+ 'quick-strategy__profit': setDefaultValidationNumber(),
+ });
+ return (
+
+ {({
+ errors,
+ handleChange,
+ values,
+ isSubmitting,
+ setFieldValue,
+ submitForm,
+ }: FormikProps) => {
+ const is_valid =
+ Object.keys(errors).length === 0 && !Object.values(values).some(elem => (elem as string) === '');
+ const is_submit_enabled = !isSubmitting && is_valid;
+ const is_mobile = isMobile();
+
+ return (
+
+ );
+ }}
+
+ );
+};
+
+export default React.memo(QuickStrategyForm);
diff --git a/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/index.ts b/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/index.ts
new file mode 100644
index 000000000000..807fdec5b73a
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/index.ts
@@ -0,0 +1,15 @@
+export { default as QuickStrategyContainer } from './container';
+export { default as common_inputs_properties } from './data/common-input-properties';
+export { default as data_fields } from './data/data-fields';
+export { default as data_uniq_input_obj } from './data/data-uniq-input-obj';
+export { default as Description } from './description';
+export { default as DurationFields } from './duration-fields';
+export { default as QuickStrategyFields } from './fields';
+export { default as QuickStrategyFooter } from './footer';
+export { default as QuickStrategyForm } from './form';
+export { default as InputField } from './input-field';
+export { default as Inputs } from './inputs';
+export { default as MarketOption } from './market-option';
+export { default as SelectField } from './select-field';
+export { default as Selects } from './selects';
+export { default as TradeTypeOption } from './trade-type-option';
diff --git a/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/input-field.tsx b/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/input-field.tsx
new file mode 100644
index 000000000000..d81a723d946b
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/input-field.tsx
@@ -0,0 +1,85 @@
+import React from 'react';
+import { Field, FieldProps } from 'formik';
+import { Icon, Input, Popover } from '@deriv/components';
+import { localize } from '@deriv/translations';
+import { TFormValues, TInputBaseFields, TInputCommonFields } from '../quick-strategy.types';
+import { TDataFields } from './data/data-fields';
+import { TDataUniqInput } from './data/data-uniq-input-obj';
+import { TInputFieldProps } from './components.types';
+import { data_fields } from '.';
+
+const InputField = ({
+ idx,
+ handleChange,
+ onChangeInputValue,
+ setCurrentFocus,
+ is_mobile,
+ field_name,
+ id,
+ className,
+ label,
+ input_value,
+ placeholder,
+ is_uniq_strategy_field,
+ trailing_icon_message,
+ zIndex,
+ uniq_selected_input,
+ errors,
+}: TInputFieldProps) => {
+ const dataField = () => {
+ if (is_uniq_strategy_field) {
+ return uniq_selected_input;
+ }
+ return idx ? data_fields[idx] : {};
+ };
+
+ const {
+ field_name: new_field_name,
+ id: new_id,
+ className: new_className,
+ label: new_label,
+ input_value: new_input_value,
+ placeholder: new_placeholder,
+ trailing_icon_message: new_trailing_icon_message,
+ zIndex: new_zIndex,
+ } = (dataField() as TDataUniqInput | TDataFields) || {};
+
+ return (
+
+ {({ field }: FieldProps) => {
+ return (
+ ) => {
+ handleChange(e);
+ onChangeInputValue(
+ input_value
+ ? (input_value as TInputBaseFields)
+ : (new_input_value as TInputCommonFields),
+ e
+ );
+ }}
+ onFocus={(e: React.FocusEvent) => setCurrentFocus(e.currentTarget.name)}
+ onBlur={() => setCurrentFocus(null)}
+ placeholder={placeholder || new_placeholder}
+ trailing_icon={
+
+
+
+ }
+ />
+ );
+ }}
+
+ );
+};
+
+export default React.memo(InputField);
diff --git a/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/inputs.tsx b/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/inputs.tsx
new file mode 100644
index 000000000000..33a9090969dd
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/inputs.tsx
@@ -0,0 +1,59 @@
+import React from 'react';
+import classNames from 'classnames';
+import { isMobile } from '@deriv/shared';
+import { TInputsFieldNames } from '../quick-strategy.types';
+import { TInputs } from './components.types';
+import { InputField } from '.';
+
+const Inputs = ({
+ idx,
+ field_name,
+ id,
+ className,
+ label,
+ input_value,
+ placeholder,
+ is_uniq_strategy_field,
+ trailing_icon_message,
+ zIndex,
+ uniq_selected_input,
+ errors,
+ is_input_field,
+ handleChange,
+ onChangeInputValue,
+ setCurrentFocus,
+}: TInputs) => {
+ const is_mobile = isMobile();
+ return is_input_field ? (
+
+
+
+ ) : (
+ <>>
+ );
+};
+
+Inputs.displayName = 'Inputs';
+
+export default React.memo(Inputs);
diff --git a/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/market-option.tsx b/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/market-option.tsx
new file mode 100644
index 000000000000..8cefe21ecba9
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/market-option.tsx
@@ -0,0 +1,14 @@
+import React from 'react';
+import { Icon, Text } from '@deriv/components';
+import { TMarketOptionProps } from './components.types';
+
+const MarketOption = ({ symbol }: TMarketOptionProps) => (
+
+
+
+ {symbol.text}
+
+
+);
+
+export default React.memo(MarketOption);
diff --git a/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/select-field.tsx b/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/select-field.tsx
new file mode 100644
index 000000000000..4f99e9612bd9
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/select-field.tsx
@@ -0,0 +1,85 @@
+import React from 'react';
+import { Field, FieldProps } from 'formik';
+import { Autocomplete, Icon, IconTradeTypes, SelectNative, Text } from '@deriv/components';
+import { localize } from '@deriv/translations';
+import { TFormValues, TSelectsFieldNames } from '../quick-strategy.types';
+import { TSelectFieldProps } from './components.types';
+
+const SelectField = React.memo(
+ ({
+ field_name,
+ id,
+ is_mobile,
+ dropdown_list,
+ selected_value,
+ label,
+ select_value,
+ setFieldValue,
+ className,
+ is_able_disabled,
+ values,
+ onChangeDropdownItem,
+ onHideDropdownList,
+ onScrollStopDropdownList,
+ selected_trade_type,
+ selected_symbol,
+ }: TSelectFieldProps) => (
+
+ {({ field }: FieldProps) => (
+ <>
+ {is_mobile ? (
+ ) => {
+ onChangeDropdownItem(select_value, e.target.value, setFieldValue);
+ }}
+ />
+ ) : (
+ {
+ onHideDropdownList(
+ select_value,
+ values[field.name] as TSelectsFieldNames,
+ setFieldValue
+ );
+ }}
+ onItemSelection={({ value }: { value: string }) => {
+ onChangeDropdownItem(select_value, value, setFieldValue);
+ }}
+ onScrollStop={() => onScrollStopDropdownList(select_value)}
+ leading_icon={
+ (select_value === 'trade-type' && selected_trade_type?.icon && (
+
+
+
+
+ )) ||
+ (select_value === 'symbol' && (selected_symbol?.value as string) && (
+
+ ))
+ }
+ />
+ )}
+ >
+ )}
+
+ ),
+ (prevProps, nextProps) => {
+ return (
+ prevProps.dropdown_list === nextProps.dropdown_list && prevProps.selected_value === nextProps.selected_value
+ );
+ }
+);
+SelectField.displayName = 'SelectField';
+
+export default SelectField;
diff --git a/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/selects.tsx b/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/selects.tsx
new file mode 100644
index 000000000000..ec2eeaf9473e
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/selects.tsx
@@ -0,0 +1,52 @@
+import React from 'react';
+import { isMobile } from '@deriv/shared';
+import { TDropdownItems, TSelectsFieldNames } from '../quick-strategy.types';
+import { TSelects } from './components.types';
+import { SelectField } from '.';
+
+const Selects = ({
+ field_name,
+ id,
+ dropdown_list,
+ selected_value,
+ label,
+ select_value,
+ setFieldValue,
+ className,
+ is_able_disabled,
+ values,
+ onChangeDropdownItem,
+ onHideDropdownList,
+ onScrollStopDropdownList,
+ selected_trade_type,
+ selected_symbol,
+ is_input_field,
+}: TSelects & { is_input_field: boolean }) =>
+ !is_input_field && id !== 'duration-unit' ? (
+
+
+
+ ) : (
+ <>>
+ );
+
+Selects.displayName = 'Selects';
+
+export default React.memo(Selects);
diff --git a/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/trade-type-option.tsx b/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/trade-type-option.tsx
new file mode 100644
index 000000000000..b4a647c97b9b
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy-components/trade-type-option.tsx
@@ -0,0 +1,15 @@
+import React from 'react';
+import { IconTradeTypes, Text } from '@deriv/components';
+import { TTradeTypeOptionProps } from './components.types';
+
+const TradeTypeOption = ({ trade_type }: TTradeTypeOptionProps) => (
+
+
+
+
+ {trade_type.text}
+
+
+);
+
+export default React.memo(TradeTypeOption);
diff --git a/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy.scss b/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy.scss
new file mode 100644
index 000000000000..ce06cfe5c282
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy.scss
@@ -0,0 +1,270 @@
+@mixin position-center {
+ display: flex;
+ justify-content: center;
+}
+
+.quick-strategy {
+ $quick-strategy: &;
+ height: var(--tab-content-height);
+ background: var(--general-main-1);
+ @include mobile {
+ height: var(--tab-content-height-mobile);
+ }
+ @include position-center;
+ width: 100%;
+
+ &__description {
+ margin: auto;
+ }
+
+ &__text {
+ grid-column: span 2;
+ @include mobile {
+ @include position-center;
+ }
+
+ &--margin {
+ margin: 1.5rem 0;
+ }
+ }
+
+ &__fields {
+ display: grid;
+ margin-top: 3.5rem;
+ grid-template-columns: 1fr 1fr;
+ row-gap: 3.5rem;
+ column-gap: 1.2rem;
+ :first-child {
+ grid-column: span 2;
+ }
+ :nth-child(3) {
+ grid-column: span 2;
+ }
+ :nth-child(4) {
+ grid-column: span 2;
+ }
+ @include mobile {
+ display: block;
+ margin-top: auto;
+ }
+ }
+
+ &__form {
+ @include mobile {
+ height: calc(100% - 8rem);
+ overflow: scroll;
+ &--active-keyboard {
+ height: 100%;
+ }
+ }
+
+ &-row {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-end;
+ @include mobile {
+ margin: 3rem 0;
+ }
+
+ .dc-dropdown-list__item--active {
+ font-weight: bold;
+ }
+
+ > * + * {
+ @include mobile {
+ margin-top: 3rem;
+ }
+ }
+
+ @include mobile {
+ flex-direction: column;
+ }
+ > * {
+ justify-content: center;
+ }
+ #{$quick-strategy}__input {
+ &-field {
+ &:focus,
+ &:disabled,
+ &:not(:focus):not([value='']) {
+ outline: none;
+
+ & ~ label {
+ transform: translate(0, -1.8rem) scale(0.875);
+ }
+ }
+ }
+ &-label {
+ font-size: var(--text-size-xxs);
+ }
+ .dc-field-error {
+ left: 0;
+ padding-top: 0.4rem;
+ padding-left: 0.4rem;
+ }
+ .dc-input__trailing-icon {
+ margin: 1.1rem;
+ position: unset;
+ height: 1.6rem;
+ }
+ }
+ }
+ &-content {
+ padding: 0 1.9rem 0 2.4rem;
+ margin-bottom: 12rem;
+
+ &--active-keyboard {
+ margin-bottom: 12rem;
+ }
+
+ &--safari-fix {
+ margin-bottom: 0;
+ padding-bottom: 10rem;
+ }
+ }
+ &-footer {
+ border-top: 1px solid var(--general-section-5);
+ bottom: 0;
+ left: 0;
+ background: var(--general-main-1);
+ width: 100%;
+ position: fixed;
+ @include desktop {
+ padding: 1.6rem 2.4rem;
+ }
+ @include mobile {
+ position: fixed;
+ padding: 1rem 2.4rem;
+ }
+ }
+ .dc-autocomplete {
+ margin-bottom: unset;
+ }
+ .dc-input {
+ margin-bottom: 0;
+ }
+ .dc-btn__group {
+ display: flex;
+ justify-content: flex-end;
+ flex-direction: row;
+ }
+ }
+ &__option {
+ display: flex;
+
+ > * {
+ align-self: center;
+ }
+ .icons-underlying {
+ margin-top: 0.5rem;
+ }
+ }
+ &__symbol {
+ margin-left: 0.8rem;
+ }
+ &__icon {
+ width: 2.4rem;
+ height: 2.4rem;
+ padding: 0.3rem;
+ margin: 0.2rem;
+ background-color: var(--general-section-1);
+ }
+ &__duration {
+ &-dropdown {
+ width: 100%;
+ margin-left: 0;
+ margin-right: 1.2rem;
+ }
+ }
+ &__leading {
+ .dc-input__field {
+ padding-left: 6rem;
+ }
+ .dc-input__leading-icon {
+ .dc-icon {
+ margin-left: 0.2rem;
+ margin-right: 0.3rem;
+ }
+ }
+ }
+ &__wrapper {
+ position: absolute;
+ top: 4rem;
+ height: 100%;
+
+ @include mobile {
+ & .dc-dropdown-list {
+ &__group {
+ &-header {
+ padding: 0.6rem 2rem !important;
+ color: var(--brand-dark-grey) !important;
+ justify-content: start !important;
+ }
+ }
+ }
+ & .quick-strategy__tabs {
+ padding: 0 0.5rem;
+ }
+ & .dc {
+ &-tabs {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+
+ &__list {
+ width: 100%;
+ height: 4rem;
+ }
+ &__item {
+ width: 50%;
+ }
+ &__content {
+ height: calc(100% - 4rem);
+ }
+ }
+ }
+ & .dc-page-overlay__header {
+ position: fixed;
+ top: 4rem;
+ width: 100%;
+ height: 4rem;
+ }
+ & .dc-page-overlay__content {
+ z-index: 5;
+ }
+ .dc-themed-scrollbars__autohide--is-hovered {
+ &::-webkit-scrollbar-thumb {
+ display: unset;
+ }
+ }
+ .dc-themed-scrollbars {
+ height: 100%;
+ }
+ }
+ }
+ // this css has been added to address the height of themed scrollbars in the quick strategy form
+ // TODO: this is a temporary fix until we have a better solution
+ &__form-scrollbar {
+ height: 600px;
+ @media (min-height: 534px) and (max-height: 767px) {
+ height: 500px;
+ }
+ @media (max-height: 533px) {
+ height: 500px;
+ }
+ }
+}
+
+/* stylelint-disable */
+@media (max-resolution: 200dpi) {
+ .quick-strategy__tab-content {
+ height: 100%;
+ }
+}
+/* stylelint-enable */
+
+.dc-modal-header--modal--strategy {
+ .dc-text {
+ font-size: var(--text-size-sm);
+ }
+}
diff --git a/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy.tsx b/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy.tsx
new file mode 100755
index 000000000000..9401f3905e71
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy.tsx
@@ -0,0 +1,70 @@
+import React from 'react';
+import { MobileFullPageModal, Modal } from '@deriv/components';
+import { isMobile } from '@deriv/shared';
+import { localize } from '@deriv/translations';
+import { connect } from 'Stores/connect';
+import RootStore from 'Stores/index';
+import { TQuickStrategyProps } from './quick-strategy.types';
+import { QuickStrategyContainer } from './quick-strategy-components';
+
+const QuickStrategy = (props: TQuickStrategyProps) => {
+ const is_mobile = isMobile();
+ const { is_strategy_modal_open, loadDataStrategy } = props;
+
+ return (
+ <>
+ {is_mobile ? (
+
+
+
+ ) : (
+
+
+
+
+
+ )}
+ >
+ );
+};
+
+export default connect(({ run_panel, quick_strategy, ui }: RootStore) => ({
+ active_index: quick_strategy.active_index,
+ description: quick_strategy.description,
+ createStrategy: quick_strategy.createStrategy,
+ duration_unit_dropdown: quick_strategy.duration_unit_dropdown,
+ types_strategies_dropdown: quick_strategy.types_strategies_dropdown,
+ getSizeDesc: quick_strategy.getSizeDesc,
+ initial_values: quick_strategy.initial_values,
+ is_onscreen_keyboard_active: ui.is_onscreen_keyboard_active,
+ is_stop_button_visible: run_panel.is_stop_button_visible,
+ onChangeDropdownItem: quick_strategy.onChangeDropdownItem,
+ onChangeInputValue: quick_strategy.onChangeInputValue,
+ onHideDropdownList: quick_strategy.onHideDropdownList,
+ onScrollStopDropdownList: quick_strategy.onScrollStopDropdownList,
+ selected_symbol: quick_strategy.selected_symbol,
+ selected_trade_type: quick_strategy.selected_trade_type,
+ selected_duration_unit: quick_strategy.selected_duration_unit,
+ selected_type_strategy: quick_strategy.selected_type_strategy,
+ symbol_dropdown: quick_strategy.symbol_dropdown,
+ loadDataStrategy: quick_strategy.loadDataStrategy,
+ trade_type_dropdown: quick_strategy.trade_type_dropdown,
+ is_strategy_modal_open: quick_strategy.is_strategy_modal_open,
+ setCurrentFocus: ui.setCurrentFocus,
+ toggleStopBotDialog: quick_strategy.toggleStopBotDialog,
+ is_running: run_panel.is_running,
+ is_contract_dialog_open: quick_strategy.is_contract_dialog_open,
+ is_stop_bot_dialog_open: quick_strategy.is_stop_bot_dialog_open,
+}))(QuickStrategy);
diff --git a/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy.types.ts b/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy.types.ts
new file mode 100644
index 000000000000..26bcc06be1a7
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy.types.ts
@@ -0,0 +1,199 @@
+import React from 'react';
+import { FormikHelpers as FormikActions } from 'formik';
+
+export type TDurationOptions = {
+ max: number;
+ min: number;
+ text: string;
+ value: string;
+};
+
+export type TTypeStrategy = {
+ description: string;
+ index: number;
+ text: string;
+ value: string;
+};
+type TIconTradeType = Array<'CALLE' | 'PUTE'>;
+
+export type TTradeType = {
+ group: string;
+ icon: TIconTradeType;
+ text: string;
+ value: string;
+};
+
+export type TTradeTypeContractsFor = {
+ icon: TIconTradeType;
+ name: string;
+ value: string;
+};
+
+type TStrategy = {
+ index: number;
+ label: string;
+ description: string;
+};
+export type TKeysStrategies = 'martingale' | 'dalembert' | 'oscars_grind';
+export type TStrategies = Record;
+
+export type TSymbol = Record<
+ 'market' | 'market_display' | 'submarket' | 'submarket_display' | 'symbol' | 'symbol_display',
+ string
+>;
+export type TSymbols = Array;
+
+export type TDuration = {
+ display: 'Ticks' | 'Minutes' | 'Hours' | 'Days';
+ max: number;
+ min: number;
+ unit: 't' | 'm' | 'h' | 'd';
+};
+
+export type TDurations = Array;
+
+export type TFieldsToUpdate = {
+ alembert_unit: string;
+ duration: string | number;
+ durationtype: string;
+ loss: string;
+ market: string;
+ oscar_unit: string;
+ profit: string;
+ size: string;
+ stake: string;
+ submarket: string;
+ symbol: string;
+ tradetype: string;
+ tradetypecat: string;
+};
+
+export type TMarketOption = {
+ group: string;
+ text: string;
+ value: string;
+};
+
+export type TTradeTypeDropdown = Array;
+
+export type TDropdownItems = 'symbol' | 'trade-type' | 'duration-unit' | 'type-strategy';
+
+export type TInputUniqFields = 'input_martingale_size' | 'input_alembert_unit' | 'input_oscar_unit';
+export type TInputBaseFields = 'input_duration_value' | 'input_stake' | 'input_loss' | 'input_profit';
+export type TInputCommonFields = TInputBaseFields | TInputUniqFields;
+
+export type TSetFieldValue = (
+ element: 'button' | 'quick-strategy__duration-unit' | 'quick-strategy__duration-value' | string,
+ action: 'run' | 'edit' | string | number
+) => void;
+
+export type TSelectsFieldNames =
+ | 'quick-strategy__type-strategy'
+ | 'quick-strategy__symbol'
+ | 'quick-strategy__trade-type'
+ | 'quick-strategy__duration-unit'
+ | '';
+
+export type TInputsFieldNames =
+ | 'quick-strategy__duration-value'
+ | 'quick-strategy__stake'
+ | 'quick-strategy__loss'
+ | 'quick-strategy__profit';
+
+export type TSymbolDropdownValue = 'group' | 'text' | 'value';
+
+export type TSymbolItem = Record;
+export type TSymbolDropdown = Array;
+
+export type TDurationUnitDropdown = Array;
+
+export type TTypeStrategiesDropdown = Array;
+
+export type TDropdowns = TSymbolDropdown | TTradeTypeDropdown | TDurationUnitDropdown | TTypeStrategiesDropdown;
+
+export type TSelectedValuesSelect = TTypeStrategy | TTradeType | TDurationOptions | TMarketOption;
+
+type TSetSelectedTradeType = (trade_type: TTradeType) => void;
+type TSetSelectedSymbol = (symbol: string) => void;
+type TSetSelectedDurationUnit = (duration_unit: TDurationOptions) => void;
+type TSetSelectedTypeStrategy = (type_strategy: TTypeStrategy) => void;
+
+type TSelectedField = TSetSelectedSymbol | TSetSelectedTradeType | TSetSelectedDurationUnit | TSetSelectedTypeStrategy;
+
+export type TFieldMapData = {
+ field_name: TSelectsFieldNames;
+ dropdown: TDropdowns;
+ selected: TSelectedValuesSelect;
+ setSelected: TSelectedField;
+};
+
+export type TFieldsMapData = Record;
+
+type TInitialUniqKeys = 'martingale-size' | 'alembert-unit' | 'oscar-unit';
+
+type TInitialKeys = TSelectsFieldNames | TInputsFieldNames | TInitialUniqKeys;
+
+export type TInitialValues = Record;
+export type TQuickStrategyFormValues = TInitialValues & Record<'button', 'run' | 'edit'>;
+
+export type TGetSizeDesc = (index: number) => string;
+
+export type TFormValues = { [key: string]: string };
+
+export type TOnChangeInputValue = (field: TInputCommonFields, event: React.ChangeEvent) => void;
+export type TSetCurrentFocus = (value: string | null) => void;
+export type TOnChangeDropdownItem = (type: TDropdownItems, value: string, setFieldValue: TSetFieldValue) => void;
+export type TOnHideDropdownList = (
+ type: TDropdownItems,
+ value: TSelectsFieldNames,
+ setFieldValue: TSetFieldValue
+) => void;
+export type TOnScrollStopDropdownList = (type: TDropdownItems) => void;
+export type TCreateStrategy = (
+ values: TQuickStrategyFormValues,
+ actions: FormikActions
+) => void;
+
+export type TQuickStrategyProps = React.PropsWithChildren<{
+ active_index: number;
+ description: string;
+ duration_unit_dropdown: TDurationUnitDropdown;
+ types_strategies_dropdown: TTypeStrategiesDropdown;
+ initial_values: TQuickStrategyFormValues;
+ is_onscreen_keyboard_active: boolean;
+ is_stop_button_visible: boolean;
+ is_strategy_modal_open: boolean;
+ selected_symbol: TMarketOption;
+ selected_trade_type: TTradeType;
+ selected_duration_unit: TDurationOptions;
+ selected_type_strategy: TTypeStrategy;
+ symbol_dropdown: TSymbolDropdown;
+ trade_type_dropdown: TTradeTypeDropdown;
+ is_running: boolean;
+ is_contract_dialog_open: boolean;
+ is_stop_bot_dialog_open: boolean;
+ createStrategy: TCreateStrategy;
+ getSizeDesc: TGetSizeDesc;
+ onChangeDropdownItem: TOnChangeDropdownItem;
+ onChangeInputValue: TOnChangeInputValue;
+ onHideDropdownList: TOnHideDropdownList;
+ onScrollStopDropdownList: TOnScrollStopDropdownList;
+ setActiveTypeStrategyIndex: (index: number) => void;
+ setCurrentFocus: TSetCurrentFocus;
+ toggleStopBotDialog: () => void;
+ loadDataStrategy: () => void;
+}>;
+
+export type TQSCache = {
+ selected_type_strategy?: TTypeStrategy;
+ selected_symbol?: TMarketOption;
+ input_stake?: string;
+ input_loss?: string;
+ input_martingale_size?: string;
+ input_profit?: string;
+ input_oscar_unit?: string;
+ input_alembert_unit?: string;
+ selected_trade_type?: TTradeType;
+ selected_duration_unit?: TDurationOptions;
+ input_duration_value?: number | string;
+};
diff --git a/packages/bot-web-ui/src/components/dashboard/react-joyride-wrapper.tsx b/packages/bot-web-ui/src/components/dashboard/react-joyride-wrapper.tsx
new file mode 100644
index 000000000000..c0fea61e4852
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/react-joyride-wrapper.tsx
@@ -0,0 +1,37 @@
+import React from 'react';
+import ReactJoyride, { Step, Styles } from 'react-joyride';
+import { handleJoyrideCallback } from './joyride-config';
+
+const ReactJoyrideWrapper = ({ steps, styles, ...props }: { steps: Step[]; styles: Styles }) => {
+ return (
+
+ );
+};
+
+export default ReactJoyrideWrapper;
diff --git a/packages/bot-web-ui/src/components/dashboard/tour-guide.tsx b/packages/bot-web-ui/src/components/dashboard/tour-guide.tsx
new file mode 100644
index 000000000000..57519e50dd96
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/tour-guide.tsx
@@ -0,0 +1,70 @@
+import React from 'react';
+import { Loading, Text } from '@deriv/components';
+import { localize } from '@deriv/translations';
+import { connect } from 'Stores/connect';
+import RootStore from 'Stores/index';
+
+type TTourGuide = {
+ content: string[];
+ img?: string;
+ label: string | boolean;
+ onCloseTour: () => void;
+ step_index: number;
+};
+
+const TourGuide = ({ content, img, label, onCloseTour, step_index }: TTourGuide) => {
+ const [has_image_loaded, setImageLoaded] = React.useState(false);
+
+ React.useEffect(() => {
+ if (img) {
+ const tour_image = new Image();
+ tour_image.onload = () => {
+ setImageLoaded(true);
+ };
+ tour_image.src = img;
+ }
+ }, [step_index]);
+
+ return (
+
+
+
+
+ {step_index}/6
+
+
+ {localize('Exit tour')}
+
+
+
+
+ {label}
+
+
+
+ {img && (
+
+ {has_image_loaded ?
:
}
+
+ )}
+
+
+ {content.map(content_text => {
+ return (
+
+
+ {content_text}
+
+
+ );
+ })}
+
+
+
+ );
+};
+export default connect(({ dashboard }: RootStore) => ({
+ setOnBoardTourRunState: dashboard.setOnBoardTourRunState,
+ setTourActive: dashboard.setTourActive,
+ onCloseTour: dashboard.onCloseTour,
+}))(TourGuide);
diff --git a/packages/bot-web-ui/src/components/dashboard/tour-slider.tsx b/packages/bot-web-ui/src/components/dashboard/tour-slider.tsx
new file mode 100644
index 000000000000..93d11e358546
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/tour-slider.tsx
@@ -0,0 +1,240 @@
+import React from 'react';
+import classNames from 'classnames';
+import { Icon, ProgressBarOnboarding, Text } from '@deriv/components';
+import { localize } from '@deriv/translations';
+import { connect } from 'Stores/connect';
+import RootStore from 'Stores/index';
+import { BOT_BUILDER_MOBILE, DBOT_ONBOARDING_MOBILE, TStepMobile } from './joyride-config';
+
+type TTourButton = {
+ type?: string;
+ onClick: () => void;
+ label: string;
+};
+
+type TTourSlider = {
+ has_started_bot_builder_tour: boolean;
+ has_started_onboarding_tour: boolean;
+ onCloseTour: () => void;
+ onTourEnd: (step: number, has_started_onboarding_tour: boolean) => void;
+ setTourActiveStep: (param: number) => void;
+ toggleTourLoadModal: (toggle: boolean) => void;
+};
+
+type TAccordion = {
+ content_data: TStepMobile;
+ expanded: boolean;
+};
+
+const TourButton = ({ label, type = 'default', ...props }: TTourButton) => {
+ return (
+
+
+ {label}
+
+
+ );
+};
+
+const Accordion = ({ content_data, expanded = false, ...props }: TAccordion) => {
+ const [is_open, setOpen] = React.useState(expanded);
+ const { content, header } = content_data;
+
+ return (
+
+
+
setOpen(!is_open)}>
+
+
+ {localize(header)}
+
+
+
+
+
+
+
+
+ {localize(content)}
+
+
+
+
+ );
+};
+
+const TourSlider = ({
+ onCloseTour,
+ onTourEnd,
+ has_started_onboarding_tour,
+ has_started_bot_builder_tour,
+ setTourActiveStep,
+ toggleTourLoadModal,
+}: TTourSlider) => {
+ const [step, setStep] = React.useState(1);
+ const [slider_content, setContent] = React.useState('');
+ const [slider_header, setheader] = React.useState('');
+ const [slider_image, setimg] = React.useState('');
+ const [step_key, setStepKey] = React.useState(0);
+
+ React.useEffect(() => {
+ setTourActiveStep(step);
+ Object.values(!has_started_onboarding_tour ? BOT_BUILDER_MOBILE : DBOT_ONBOARDING_MOBILE).forEach(data => {
+ if (data.key === step) {
+ setContent(data?.content);
+ setheader(data?.header);
+ setimg(data?.img);
+ setStepKey(data?.step_key);
+ }
+ });
+ const el_ref = document.querySelector('.toolbar__group-btn svg:nth-child(2)');
+ if (has_started_bot_builder_tour && step === 1) {
+ //component does not rerender
+ el_ref?.classList.add('dbot-tour-blink');
+ } else {
+ el_ref?.classList.remove('dbot-tour-blink');
+ }
+ if (has_started_bot_builder_tour && step === 2) {
+ toggleTourLoadModal(true);
+ } else toggleTourLoadModal(false);
+ }, [step]);
+
+ const onChange = React.useCallback(
+ (param: string) => {
+ const MOBILE_TOUR = !has_started_onboarding_tour ? BOT_BUILDER_MOBILE : DBOT_ONBOARDING_MOBILE;
+ if (param === 'inc' && step < Object.keys(MOBILE_TOUR).length) setStep(step + 1);
+ else if (param === 'dec' && step > 1) setStep(step - 1);
+ else if (param === 'skip') onCloseTour();
+ },
+ [step]
+ );
+
+ const content_data = BOT_BUILDER_MOBILE.find(({ key }) => key === step);
+ const onClickNext = React.useCallback(() => {
+ onChange('inc');
+ onTourEnd(step, has_started_onboarding_tour);
+ }, [step]);
+
+ const bot_tour_text = !has_started_onboarding_tour && step === 3 ? localize('Finish') : localize('Next');
+
+ const tour_button_text = has_started_onboarding_tour && step_key === 0 ? localize('Start') : bot_tour_text;
+
+ return (
+ <>
+
+ {has_started_onboarding_tour && slider_header && step_key !== 0 && (
+
+ {`${step_key}/6`}
+
+ {localize('Exit Tour')}
+
+
+ )}
+
+ {has_started_onboarding_tour && slider_header && (
+
+ {localize(slider_header)}
+
+ )}
+ {has_started_onboarding_tour && slider_image && (
+
+
+
+ )}
+ {has_started_onboarding_tour && slider_content && (
+ <>
+ {slider_content.map(data => {
+ return (
+
+ {localize(data)}
+
+ );
+ })}
+ >
+ )}
+ {!has_started_onboarding_tour && content_data &&
}
+
+
+ {(!has_started_onboarding_tour || (has_started_onboarding_tour && step !== 1)) && (
+
+ )}
+
+
+ {has_started_onboarding_tour && step === 1 && (
+ {
+ onChange('skip');
+ }}
+ label={localize('Skip')}
+ />
+ )}
+ {((has_started_bot_builder_tour && step !== 1) ||
+ (has_started_onboarding_tour && step !== 1 && step !== 2)) && (
+ {
+ onChange('dec');
+ }}
+ label={localize('Previous')}
+ />
+ )}
+
+
+
+
+ >
+ );
+};
+
+export default connect(({ dashboard, load_modal }: RootStore) => ({
+ active_tab: dashboard.active_tab,
+ has_started_bot_builder_tour: dashboard.has_started_bot_builder_tour,
+ has_started_onboarding_tour: dashboard.has_started_onboarding_tour,
+ onCloseTour: dashboard.onCloseTour,
+ onTourEnd: dashboard.onTourEnd,
+ setActiveTab: dashboard.setActiveTab,
+ setTourActiveStep: dashboard.setTourActiveStep,
+ toggleTourLoadModal: load_modal.toggleTourLoadModal,
+}))(TourSlider);
diff --git a/packages/bot-web-ui/src/components/dashboard/tour-trigger-dialog.spec.tsx b/packages/bot-web-ui/src/components/dashboard/tour-trigger-dialog.spec.tsx
new file mode 100644
index 000000000000..7f0516d763ee
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/tour-trigger-dialog.spec.tsx
@@ -0,0 +1,31 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import { mocked_props } from './dashboard-component/__tests__/dashboard-component.spec';
+import TourTriggrerDialog from './tour-trigger-dialog';
+
+const mock_connect_props = {
+ dialog_options: {
+ title: 'string',
+ message: 'string',
+ ok_button_text: 'string',
+ cancel_button_text: 'string',
+ },
+ setStrategySaveType: jest.fn(),
+};
+
+jest.mock('Stores/connect.js', () => ({
+ __esModule: true,
+ default: 'mockedDefaultExport',
+ connect:
+ () =>
+ (Component: T) =>
+ props =>
+ Component({ ...props, ...mock_connect_props }),
+}));
+
+describe(' ', () => {
+ it('renders tour trigger dialog', () => {
+ render( );
+ expect(screen.getByText(/Get started on Deriv Bot/i)).toBeInTheDocument();
+ });
+});
diff --git a/packages/bot-web-ui/src/components/dashboard/tour-trigger-dialog.tsx b/packages/bot-web-ui/src/components/dashboard/tour-trigger-dialog.tsx
new file mode 100644
index 000000000000..e7959c304a5a
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/tour-trigger-dialog.tsx
@@ -0,0 +1,188 @@
+import React from 'react';
+import classNames from 'classnames';
+import { Dialog, Text } from '@deriv/components';
+import { isMobile } from '@deriv/shared';
+import { Localize, localize } from '@deriv/translations';
+import { connect } from 'Stores/connect';
+import RootStore from 'Stores/index';
+import { setTourSettings, tour_status_ended, tour_type } from './joyride-config';
+
+type TTourTriggrerDialog = {
+ active_tab: number;
+ has_tour_ended: boolean;
+ is_tour_dialog_visible: boolean;
+ setTourDialogVisibility: (param: boolean) => void;
+ toggleOnConfirm: (active_tab: number, value: boolean) => void;
+};
+
+const TourTriggrerDialog = ({
+ active_tab,
+ has_tour_ended,
+ is_tour_dialog_visible,
+ setTourDialogVisibility,
+ toggleOnConfirm,
+}: TTourTriggrerDialog) => {
+ const is_mobile = isMobile();
+
+ const toggleTour = (value: boolean, type: string) => {
+ if (tour_type.key === 'onboard_tour') {
+ if (type === 'onConfirm') {
+ toggleOnConfirm(active_tab, value);
+ } else {
+ setTourSettings(new Date().getTime(), `${tour_type.key}_token`);
+ }
+ tour_type.key = 'onboard_tour';
+ } else if (tour_type.key === 'bot_builder') {
+ if (type === 'onConfirm') {
+ toggleOnConfirm(active_tab, value);
+ } else {
+ setTourSettings(new Date().getTime(), `${tour_type.key}_token`);
+ }
+ tour_type.key = 'bot_builder';
+ }
+ setTourDialogVisibility(false);
+ };
+
+ const dashboardTourContent = () => {
+ if (!has_tour_ended) {
+ return (
+ Start0> for a quick tour to help you get started.'}
+ components={[ ]}
+ />
+ );
+ }
+ return (
+ Tutorials0>.'} components={[ ]} />
+ );
+ };
+
+ const getTourHeaders = (tour_check: boolean, tab_id: number) => {
+ let text;
+ if (!tour_check) {
+ if (tab_id === 1) text = localize(is_mobile ? 'Bot Builder guide' : "Let's build a Bot!");
+ else text = localize('Get started on Deriv Bot');
+ } else if (tab_id === 1) text = localize('Congratulations');
+ else text = localize('Want to retake the tour?');
+ return text;
+ };
+
+ const getTourContent = (type: string) => {
+ return (
+ <>
+ {type === 'header' && getTourHeaders(has_tour_ended, active_tab)}
+ {type === 'content' && active_tab === 0 && dashboardTourContent()}
+ {type === 'content' &&
+ active_tab === 1 &&
+ (!has_tour_ended ? (
+ <>
+
+
+
+
+ Start0> button to begin and follow the tutorial.'
+ }
+ components={[ ]}
+ />
+
+
+ Tutorials0> tab.'
+ }
+ components={[ ]}
+ />
+
+ >
+ ) : (
+ <>
+
+
+
+
+ run the bot0> to test out the strategy.'}
+ components={[ ]}
+ />
+
+
+ Tutorials0> tab.'
+ }
+ components={[ ]}
+ />
+
+ >
+ ))}
+ >
+ );
+ };
+
+ const confirm_button = active_tab === 0 ? localize('Got it, thanks!') : localize('OK');
+
+ const onHandleConfirm = React.useCallback(() => {
+ const status = tour_status_ended.key === 'finished';
+ toggleTour(status ? false : !has_tour_ended, 'onConfirm');
+ tour_status_ended.key = '';
+ return status ? tour_status_ended.key : null;
+ }, [has_tour_ended, active_tab]);
+
+ return (
+
+
toggleTour(false, 'onCancel')}
+ confirm_button_text={has_tour_ended ? confirm_button : localize('Start')}
+ onConfirm={onHandleConfirm}
+ is_mobile_full_width
+ className={classNames('dc-dialog', {
+ 'tour-dialog': active_tab === 0 || active_tab === 1,
+ 'tour-dialog--end': (active_tab === 0 || active_tab === 1) && has_tour_ended,
+ })}
+ has_close_icon={false}
+ >
+
+
+ {is_tour_dialog_visible && getTourContent('header')}
+
+
+
+
+ {is_tour_dialog_visible && getTourContent('content')}
+
+
+
+
+ );
+};
+
+export default connect(({ dashboard }: RootStore) => ({
+ active_tab: dashboard.active_tab,
+ has_tour_ended: dashboard.has_tour_ended,
+ is_tour_dialog_visible: dashboard.is_tour_dialog_visible,
+ setTourDialogVisibility: dashboard.setTourDialogVisibility,
+ toggleOnConfirm: dashboard.toggleOnConfirm,
+}))(TourTriggrerDialog);
diff --git a/packages/bot-web-ui/src/components/dashboard/tutorial-tab/dialog.scss b/packages/bot-web-ui/src/components/dashboard/tutorial-tab/dialog.scss
new file mode 100644
index 000000000000..02a77f1b608b
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/tutorial-tab/dialog.scss
@@ -0,0 +1,98 @@
+.tutorials-wrap {
+ .dc-dialog {
+ &__dialog {
+ width: 80vw;
+ height: 80vh;
+ max-width: unset;
+ max-height: unset;
+ padding: 0;
+ border-radius: 0;
+ position: relative;
+ z-index: 1;
+ @include mobile {
+ width: 94%;
+ height: auto;
+ max-height: 80%;
+ }
+ }
+
+ &__header-wrapper {
+ &--end {
+ position: absolute;
+ top: 0;
+ right: 1rem;
+ z-index: 90;
+ }
+ }
+
+ &__content {
+ max-width: unset;
+ margin-bottom: 0;
+ height: 100%;
+ }
+
+ &__footer {
+ display: none;
+ }
+ }
+}
+
+.tour-dialog {
+ transition: unset;
+ .dc-dialog__dialog {
+ transform: unset;
+ opacity: unset;
+ transition: unset;
+
+ @include mobile {
+ width: 90vw;
+ padding: 1.6rem;
+ }
+ @include tablet {
+ width: 90vw;
+ padding: 1.6rem;
+ }
+ }
+ &--end {
+ .dc-btn--secondary {
+ display: none;
+ }
+
+ @include mobile {
+ .dc-btn--primary {
+ padding: 1rem;
+ }
+ }
+ }
+
+ .dc-dialog__content {
+ &__header {
+ text-align: left;
+ margin-bottom: 2.4rem;
+ }
+
+ &__description {
+ text-align: left;
+
+ &__text {
+ margin-bottom: 1.6rem;
+ }
+ }
+ }
+
+ .dc-dialog__footer {
+ @include mobile {
+ display: flex;
+ flex-direction: column;
+ height: unset;
+ align-content: flex-end;
+
+ button {
+ &:first-child {
+ margin-bottom: unset;
+ margin-right: 0.8rem;
+ }
+ }
+ }
+ }
+}
diff --git a/packages/bot-web-ui/src/components/dashboard/tutorial-tab/faq-content.tsx b/packages/bot-web-ui/src/components/dashboard/tutorial-tab/faq-content.tsx
new file mode 100644
index 000000000000..79c65d973507
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/tutorial-tab/faq-content.tsx
@@ -0,0 +1,82 @@
+import React from 'react';
+import { Accordion, Text } from '@deriv/components';
+import { isMobile } from '@deriv/shared';
+import { localize } from '@deriv/translations';
+import { connect } from 'Stores/connect';
+import RootStore from 'Stores/index';
+import { TDescription } from './tutorial-content';
+
+type TFAQContent = {
+ faq_list: TFAQList[];
+ faq_search_value: string;
+ hide_header?: boolean;
+};
+
+type TFAQList = {
+ title: string;
+ description: TDescription[];
+};
+
+const FAQ = ({ type, content, src }: TDescription) => {
+ if (type === 'image') return ;
+ const is_mobile = isMobile();
+
+ return (
+
+ );
+};
+
+const FAQContent = ({ faq_list, faq_search_value, hide_header = false }: TFAQContent) => {
+ const getList = () => {
+ return faq_list.map(({ title, description }: TFAQList) => ({
+ header: (
+
+ {title}
+
+ ),
+ content: description.map(item => ),
+ }));
+ };
+
+ return (
+
+
+ {!hide_header && (
+
+ {localize('FAQ')}
+
+ )}
+ {faq_list?.length ? (
+
+ ) : (
+
+
+ {localize('No results found "{{ faq_search_value }}"', {
+ faq_search_value,
+ })}
+
+
+ )}
+
+
+ );
+};
+
+export default connect(({ dashboard }: RootStore) => ({
+ faq_search_value: dashboard.faq_search_value,
+}))(FAQContent);
diff --git a/packages/bot-web-ui/src/components/dashboard/tutorial-tab/guide-content.tsx b/packages/bot-web-ui/src/components/dashboard/tutorial-tab/guide-content.tsx
new file mode 100644
index 000000000000..38f29d357a81
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/tutorial-tab/guide-content.tsx
@@ -0,0 +1,197 @@
+import React from 'react';
+import classNames from 'classnames';
+import { Dialog, Icon, Text } from '@deriv/components';
+import { isMobile } from '@deriv/shared';
+import { localize } from '@deriv/translations';
+import { DBOT_TABS } from 'Constants/bot-contents';
+import { connect } from 'Stores/connect';
+import RootStore from 'Stores/index';
+import { removeKeyValue } from '../../../utils/settings';
+import { tour_type } from '../joyride-config';
+
+type TGuideContent = {
+ dialog_options: { [key: string]: string };
+ faq_search_value: string;
+ guide_list: [];
+ is_dialog_open: boolean;
+ onOkButtonClick: () => void;
+ setActiveTab: (tab_title: number) => void;
+ setHasTourEnded: (param: boolean) => boolean;
+ setOnBoardTourRunState: (param: boolean) => boolean;
+ setTourActive: (param: boolean) => boolean;
+ setTourDialogVisibility: (param: boolean) => boolean;
+ showVideoDialog: (param: { [key: string]: string }) => void;
+};
+
+const GuideContent = ({
+ dialog_options,
+ faq_search_value,
+ guide_list,
+ is_dialog_open,
+ onOkButtonClick,
+ setActiveTab,
+ setHasTourEnded,
+ setOnBoardTourRunState,
+ setTourActive,
+ setTourDialogVisibility,
+ showVideoDialog,
+}: TGuideContent) => {
+ const triggerTour = (type: string) => {
+ const storage = JSON.parse(localStorage?.dbot_settings);
+ if (type === 'OnBoard') {
+ if (storage.onboard_tour_token) {
+ removeKeyValue('onboard_tour_token');
+ removeKeyValue('onboard_tour_status');
+ removeKeyValue('bot_builder_status');
+ }
+ tour_type.key = 'onboard_tour';
+ setHasTourEnded(false);
+ if (is_mobile) {
+ setTourActive(true);
+ setOnBoardTourRunState(true);
+ } else {
+ setTourDialogVisibility(true);
+ }
+ setActiveTab(DBOT_TABS.DASHBOARD);
+ } else {
+ if (storage.bot_builder_token) {
+ removeKeyValue('bot_builder_token');
+ removeKeyValue('bot_builder_status');
+ removeKeyValue('onboard_tour_status');
+ }
+ tour_type.key = 'bot_builder';
+ setHasTourEnded(false);
+ setTourDialogVisibility(true);
+ setActiveTab(DBOT_TABS.BOT_BUILDER);
+ }
+ };
+ const is_mobile = isMobile();
+
+ return React.useMemo(
+ () => (
+
+ {guide_list?.length > 0 && (
+
+ Step-by-step guides
+
+ )}
+
+ {guide_list &&
+ guide_list.map(({ id, content, src, type, subtype }) => {
+ return (
+ type === 'Tour' && (
+
triggerTour(subtype)}
+ >
+
+
+ {content}
+
+
+ )
+ );
+ })}
+
+ {guide_list?.length > 0 && (
+
+ Videos on Deriv Bot
+
+ )}
+
+ {guide_list &&
+ guide_list.map(({ id, content, url, type, src }) => {
+ return (
+ type !== 'Tour' && (
+
+
+
+
+ showVideoDialog({
+ type: 'url',
+ url,
+ })
+ }
+ />
+
+
+
+ {content}
+
+
+ )
+ );
+ })}
+ {!guide_list.length && (
+
+
+ {localize('No results found "{{ faq_search_value }}"', {
+ faq_search_value,
+ })}
+
+
+ )}
+
+
+
+
+
+ ),
+ [guide_list, is_dialog_open]
+ );
+};
+
+export default connect(({ dashboard, load_modal }: RootStore) => ({
+ dialog_options: dashboard.dialog_options,
+ faq_search_value: dashboard.faq_search_value,
+ is_dialog_open: dashboard.is_dialog_open,
+ onOkButtonClick: dashboard.onCloseDialog,
+ setActiveTab: dashboard.setActiveTab,
+ setHasTourEnded: dashboard.setHasTourEnded,
+ setOnBoardTourRunState: dashboard.setOnBoardTourRunState,
+ setTourActive: dashboard.setTourActive,
+ setTourDialogVisibility: dashboard.setTourDialogVisibility,
+ showVideoDialog: dashboard.showVideoDialog,
+ toggleLoadModal: load_modal.toggleLoadModal,
+}))(GuideContent);
diff --git a/packages/bot-web-ui/src/components/dashboard/tutorial-tab/index.scss b/packages/bot-web-ui/src/components/dashboard/tutorial-tab/index.scss
new file mode 100644
index 000000000000..011527635edd
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/tutorial-tab/index.scss
@@ -0,0 +1,154 @@
+@import './sidebar.scss';
+@import './dialog.scss';
+
+@mixin position-center {
+ display: flex;
+ justify-content: center;
+}
+
+@mixin align-center {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.tutorials-wrap {
+ padding-bottom: 20rem;
+ &--placeholder {
+ width: 28rem;
+ }
+ &--tour {
+ cursor: pointer;
+ }
+ &__group {
+ display: flex;
+ margin-top: 2.4rem;
+ margin-bottom: 5.2rem;
+ &__nosearch {
+ @include position-center;
+ width: 100%;
+ word-break: break-all;
+ }
+ &__cards {
+ display: flex;
+ text-align: center;
+ flex-direction: column;
+ margin-right: 2.4rem;
+ width: 21.5rem;
+ @include mobile {
+ flex-direction: row;
+ span {
+ text-align: left;
+ padding-left: 0.8rem;
+ width: calc(100% - 14.8rem);
+ }
+ }
+ }
+ @include mobile {
+ flex-direction: column;
+ margin-top: 1.4rem;
+ margin-bottom: 2.2rem;
+ }
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+
+ &__placeholder {
+ @include align-center;
+ background: var(--checkbox-disabled-grey);
+ margin-bottom: 0.8rem;
+ margin-right: 2.4rem;
+ height: 16rem;
+ width: 28rem;
+ background-size: 100% 100%;
+ @include mobile {
+ height: 8.7rem;
+ width: 14.8rem;
+ margin: 0;
+ }
+ &__tours {
+ height: 13.5rem;
+ background-position: center center;
+ margin-bottom: 0.8rem;
+ cursor: pointer;
+ @include mobile {
+ height: 8.7rem;
+ width: 14.8rem;
+ }
+ }
+
+ &__button-group {
+ @include align-center;
+ width: 20rem;
+ height: 7rem;
+ border-radius: 1rem;
+ cursor: pointer;
+ background-color: rgba(0, 0, 0, 0.5);
+ &--play {
+ filter: invert(1);
+ }
+ @include mobile {
+ height: 6rem;
+ width: 8rem;
+ }
+ }
+ &--disabled {
+ pointer-events: none;
+ }
+ }
+}
+
+.faq {
+ &__wrapper {
+ overflow: auto;
+ height: 100vh;
+ padding-bottom: 20rem;
+
+ @include mobile {
+ height: calc(100vh - 27rem);
+ .dc-accordion__wrapper > div:last-child {
+ //iphone 14 specific
+ @media (min-height: 450px) and (max-height: 750px) {
+ padding-bottom: 10rem;
+ }
+ }
+ }
+
+ &__nosearch {
+ @include position-center;
+ width: 100%;
+ word-break: break-all;
+ }
+ &__content {
+ width: 85%;
+
+ @include mobile {
+ width: 100%;
+ height: calc(100vh - 16rem);
+ }
+ }
+ .dc-accordion {
+ &__item {
+ border: unset;
+ border-bottom: 0.1rem solid var(--general-section-1);
+ &-header {
+ @include position-center;
+ justify-content: space-between;
+ }
+ &-content {
+ @include mobile {
+ width: 100%;
+
+ img {
+ width: 100%;
+ }
+ }
+ }
+ }
+ }
+ &__header {
+ margin: 0 0 1rem 0.5rem;
+ }
+ }
+}
diff --git a/packages/bot-web-ui/src/components/dashboard/tutorial-tab/index.ts b/packages/bot-web-ui/src/components/dashboard/tutorial-tab/index.ts
new file mode 100644
index 000000000000..84164e77bba7
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/tutorial-tab/index.ts
@@ -0,0 +1,4 @@
+import Sidebar from './sidebar';
+import './index.scss';
+
+export default Sidebar;
diff --git a/packages/bot-web-ui/src/components/dashboard/tutorial-tab/sidebar.scss b/packages/bot-web-ui/src/components/dashboard/tutorial-tab/sidebar.scss
new file mode 100644
index 000000000000..36608ef7057f
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/tutorial-tab/sidebar.scss
@@ -0,0 +1,103 @@
+.tutorials-wrapper {
+ width: 100%;
+ background: var(--general-main-1);
+ .dc-tabs {
+ &__wrapper {
+ padding: 1.6rem 0.8rem;
+
+ &__group {
+ display: flex;
+ width: 22.5rem;
+ position: relative;
+ justify-content: center;
+ align-items: center;
+ padding: 1.6rem 0;
+ z-index: 1;
+
+ svg {
+ position: absolute;
+ left: 1.5rem;
+ z-index: 1;
+ }
+
+ &__search-input {
+ width: 21rem;
+ height: 3.2rem;
+ border-radius: 0.4rem;
+ outline: none;
+ background-color: transparent;
+ font-size: 1.4rem;
+ padding-left: 3rem;
+ border: solid 0.1rem var(--checkbox-disabled-grey);
+ color: var(--text-general);
+
+ ::placeholder {
+ margin-left: 3.2rem;
+ }
+ }
+ }
+
+ .dc-tabs {
+ &__content {
+ margin: -6.5rem 2.4rem;
+ width: 100%;
+ }
+ &--top {
+ display: flex;
+ @include mobile {
+ height: calc(100vh - 22rem);
+ }
+ }
+ &__list {
+ width: 22.5rem;
+ display: flex;
+ flex-direction: column;
+ padding: 0 0.8rem;
+
+ &--border-bottom {
+ padding: 0 0.8rem;
+ height: 100vh;
+
+ &:first-child {
+ margin-top: -7.2rem;
+ }
+ }
+ }
+
+ &__item {
+ width: 100% !important;
+ &--top {
+ &:first-child {
+ margin-top: 7.5rem;
+ }
+ }
+ &--tutorials {
+ display: flex;
+ justify-content: flex-start;
+ padding: 0 1.6rem;
+ }
+ }
+
+ &__active {
+ background-color: var(--sidebar-tab);
+ border-radius: 0.4rem 0.4rem 0 0;
+ transition: all 0.6s;
+ }
+ }
+ }
+ }
+}
+
+.tutorials-mobile {
+ padding: 1.6rem;
+ overflow-x: hidden;
+ overflow-y: auto;
+ &__select {
+ height: 4rem;
+ margin-bottom: 1.5rem;
+ }
+ &__guide {
+ height: calc(100vh - 21.1rem);
+ overflow: auto;
+ }
+}
diff --git a/packages/bot-web-ui/src/components/dashboard/tutorial-tab/sidebar.tsx b/packages/bot-web-ui/src/components/dashboard/tutorial-tab/sidebar.tsx
new file mode 100644
index 000000000000..33b21097cc37
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/tutorial-tab/sidebar.tsx
@@ -0,0 +1,134 @@
+import React from 'react';
+import classNames from 'classnames';
+import debounce from 'lodash.debounce';
+import { DesktopWrapper, Icon, MobileWrapper, SelectNative, Tabs } from '@deriv/components';
+import { isMobile } from '@deriv/shared';
+import { localize } from '@deriv/translations';
+import { connect } from 'Stores/connect';
+import RootStore from 'Stores/index';
+import FAQContent from './faq-content';
+import GuideContent from './guide-content';
+import { faq_content, guide_content, user_guide_content } from './tutorial-content';
+
+type TSidebarProps = {
+ active_tab_tutorials: number;
+ active_tab: number;
+ faq_search_value: string;
+ setActiveTabTutorial: (active_tab_tutorials: number) => void;
+ setFAQSearchValue: (setFAQSearchValue: string) => void;
+};
+
+const Sidebar = ({
+ active_tab_tutorials,
+ active_tab,
+ faq_search_value,
+ setActiveTabTutorial,
+ setFAQSearchValue,
+}: TSidebarProps) => {
+ const guide_tab_content = [...user_guide_content, ...guide_content];
+ const [search_filtered_list, setsearchFilteredList] = React.useState(guide_tab_content);
+ const [search_faq_list, setsearchFAQList] = React.useState(faq_content);
+ const search_input = React.useRef(null);
+ const menu_items = [
+ {
+ label: localize('Guide'),
+ content: ,
+ },
+ {
+ label: localize('FAQ'),
+ content: ,
+ },
+ ];
+ const selected_tab = menu_items?.[active_tab_tutorials] || {};
+
+ React.useEffect(() => {
+ if (search_input?.current?.value) {
+ search_input.current.value = '';
+ setsearchFAQList([]);
+ }
+
+ setsearchFilteredList(guide_tab_content);
+ setsearchFAQList(faq_content);
+ }, [active_tab_tutorials, active_tab]);
+
+ React.useEffect(() => {
+ const content_list = active_tab_tutorials === 0 ? guide_tab_content : faq_content;
+ const filtered_list = content_list.filter(data => {
+ return content_list === guide_tab_content
+ ? data.content.toLowerCase().includes(faq_search_value)
+ : data.title.toLowerCase().includes(faq_search_value);
+ });
+ return active_tab_tutorials === 0 ? setsearchFilteredList(filtered_list) : setsearchFAQList(filtered_list);
+ }, [faq_search_value]);
+
+ const onSearch = (event: React.ChangeEvent) => {
+ const value = event.target.value;
+ debounce(() => {
+ setFAQSearchValue(value);
+ }, 700)();
+ };
+
+ const onChangeHandle = React.useCallback(
+ ({ target }: React.ChangeEvent) => {
+ setActiveTabTutorial(menu_items.findIndex(i => i.label === target.value));
+ },
+ [active_tab_tutorials]
+ );
+
+ return (
+ <>
+
+
+
+
+
+
+
+ {menu_items.map(({ label, content }) => (
+
+ {content}
+
+ ))}
+
+
+
+
+
+
+ ({ id: idx, value: label, text: label }))}
+ value={selected_tab.label}
+ label={''}
+ should_show_empty_option={false}
+ onChange={onChangeHandle}
+ />
+
+
+ {selected_tab.content}
+
+
+
+ >
+ );
+};
+
+export default connect(({ dashboard }: RootStore) => ({
+ active_tab_tutorials: dashboard.active_tab_tutorials,
+ active_tab: dashboard.active_tab,
+ faq_search_value: dashboard.faq_search_value,
+ setActiveTabTutorial: dashboard.setActiveTabTutorial,
+ setFAQSearchValue: dashboard.setFAQSearchValue,
+}))(Sidebar);
diff --git a/packages/bot-web-ui/src/components/dashboard/tutorial-tab/tutorial-content.tsx b/packages/bot-web-ui/src/components/dashboard/tutorial-tab/tutorial-content.tsx
new file mode 100644
index 000000000000..b142d944977f
--- /dev/null
+++ b/packages/bot-web-ui/src/components/dashboard/tutorial-tab/tutorial-content.tsx
@@ -0,0 +1,412 @@
+import { getImageLocation } from '../../../public-path';
+import { localize } from '@deriv/translations';
+
+export type TDescription = Pick;
+
+export type TFaqContent = Pick;
+
+export type TGuideContent = Omit;
+
+export type TUserGuideContent = Omit;
+
+export type TContent = {
+ content?: string;
+ description: TDescription[];
+ id: number;
+ src?: string;
+ subtype?: string;
+ title: string;
+ type: string;
+ url?: string;
+};
+export const user_guide_content: TUserGuideContent[] = [
+ {
+ id: 1,
+ type: 'Tour',
+ subtype: 'OnBoard',
+ content: localize('Get started on Deriv Bot'),
+ src: getImageLocation('onboard-tour.png'),
+ },
+ {
+ id: 2,
+ type: 'Tour',
+ subtype: 'BotBuilder',
+ content: localize('Let’s build a bot!'),
+ src: getImageLocation('builder-tour.png'),
+ },
+];
+
+export const guide_content: TGuideContent[] = [
+ {
+ id: 1,
+ type: 'DBotVideo',
+ content: localize('Deriv Bot - your automated trading partner'),
+ url: 'https://www.youtube.com/embed/QdI5zCkO4Gk',
+ src: getImageLocation('video_dbot.webp'),
+ },
+];
+
+export const faq_content: TFaqContent[] = [
+ {
+ title: localize('What is Deriv Bot?'),
+ description: [
+ {
+ type: 'text',
+ content: localize(
+ "Deriv Bot is a web-based strategy builder for trading digital options. It’s a platform where you can build your own automated trading bot using drag-and-drop 'blocks'."
+ ),
+ },
+ ],
+ },
+ {
+ title: localize('Where do I find the blocks I need?'),
+ description: [
+ {
+ type: 'text',
+ content: localize('Follow these steps:'),
+ },
+ {
+ type: 'text',
+ content: localize('1. Go to Bot Builder .'),
+ },
+ {
+ type: 'text',
+ content: localize(
+ "2. Under the Blocks menu , you'll see a list of categories. Blocks are grouped within these categories. Choose the block you want and drag them to the workspace."
+ ),
+ },
+ {
+ type: 'image',
+ src: getImageLocation('blocks_menu.png'),
+ },
+ {
+ type: 'text',
+ content: localize(
+ '3. You can also search for the blocks you want using the search bar above the categories.'
+ ),
+ },
+ {
+ type: 'image',
+ src: getImageLocation('blocks_menu_search.png'),
+ },
+ {
+ type: 'text',
+ content: localize(
+ 'For more info, check out this blog post on the basics of building a trading bot.'
+ ),
+ },
+ ],
+ },
+ {
+ title: localize('How do I remove blocks from the workspace?'),
+ description: [
+ {
+ type: 'text',
+ content: localize(
+ 'Click on the block you want to remove and press Delete on your keyboard.'
+ ),
+ },
+ ],
+ },
+ {
+ title: localize('How do I create variables?'),
+ description: [
+ {
+ type: 'text',
+ content: localize(
+ '1. Under the Blocks menu, go to Utility > Variables .'
+ ),
+ },
+ {
+ type: 'text',
+ content: localize(
+ '2. Enter a name for your variable, and hit Create . New blocks containing your new variable will appear below.'
+ ),
+ },
+ {
+ type: 'text',
+ content: localize('3. Choose the block you want and drag it to the workspace.'),
+ },
+ ],
+ },
+ {
+ title: localize('Do you offer pre-built trading bots on Deriv Bot?'),
+ description: [
+ {
+ type: 'text',
+ content: localize(
+ "Yes, you can get started with a pre-built bot using the Quick strategy feature. You’ll find some of the most popular trading strategies here: Martingale, D'Alembert, and Oscar's Grind. Just select the strategy, enter your trade parameters, and your bot will be created for you. You can always tweak the parameters later."
+ ),
+ },
+ ],
+ },
+ {
+ title: localize('What is a quick strategy?'),
+ description: [
+ {
+ type: 'text',
+ content: localize(
+ "A quick strategy is a ready-made strategy that you can use in Deriv Bot. There are 3 quick strategies you can choose from: Martingale, D'Alembert, and Oscar's Grind."
+ ),
+ },
+ {
+ type: 'text',
+ content: localize('Using a quick strategy '),
+ },
+ {
+ type: 'text',
+ content: localize('1. Go to Quick strategy and select the strategy you want.'),
+ },
+ {
+ type: 'text',
+ content: localize('2. Select the asset and trade type.'),
+ },
+ {
+ type: 'text',
+ content: localize('3. Set your trade parameters and hit Create .'),
+ },
+ {
+ type: 'text',
+ content: localize(
+ '4. Once the blocks are loaded onto the workspace, tweak the parameters if you want, or hit Run to start trading.'
+ ),
+ },
+ {
+ type: 'text',
+ content: localize(
+ '5. Hit Save to download your bot. You can choose to download your bot to your device or your Google Drive.'
+ ),
+ },
+ ],
+ },
+ {
+ title: localize('How do I save my strategy?'),
+ description: [
+ {
+ type: 'text',
+ content: localize(
+ 'In Bot Builder , hit Save on the toolbar at the top to download your bot. Give your bot a name, and choose to download your bot to your device or Google Drive. Your bot will be downloaded as an XML file.'
+ ),
+ },
+ ],
+ },
+ {
+ title: localize('How do I import my own trading bot into Deriv Bot?'),
+ description: [
+ {
+ type: 'text',
+ content: localize(
+ 'Just drag the XML file from your computer onto the workspace, and your bot will be loaded accordingly. Alternatively, you can hit Import in Bot Builder , and choose to import your bot from your computer or from your Google Drive.'
+ ),
+ },
+ {
+ type: 'text',
+ content: localize('Import from your computer '),
+ },
+ {
+ type: 'text',
+ content: localize(
+ '1. After hitting Import , select Local and click Continue .'
+ ),
+ },
+ {
+ type: 'text',
+ content: localize('2. Select your XML file and hit Open .'),
+ },
+ {
+ type: 'text',
+ content: localize('3. Your bot will be loaded accordingly.'),
+ },
+ {
+ type: 'text',
+ content: localize('Import from your Google Drive '),
+ },
+ {
+ type: 'text',
+ content: localize(
+ '1. After hitting Import , select Google Drive and click Continue .'
+ ),
+ },
+ {
+ type: 'text',
+ content: localize('2. Select your XML file and hit Select .'),
+ },
+ {
+ type: 'text',
+ content: localize('3. Your bot will be loaded accordingly.'),
+ },
+ ],
+ },
+ {
+ title: localize('How do I reset the workspace?'),
+ description: [
+ {
+ type: 'text',
+ content: localize(
+ 'In Bot Builder , hit Reset on the toolbar at the top. This will clear the workspace. Please note that any unsaved changes will be lost.'
+ ),
+ },
+ ],
+ },
+ {
+ title: localize('How do I clear my transaction log?'),
+ description: [
+ {
+ type: 'text',
+ content: localize('1. Hit Reset at the bottom of stats panel.'),
+ },
+ {
+ type: 'image',
+ src: getImageLocation('reset_transaction_log.png'),
+ },
+ {
+ type: 'text',
+ content: localize('2. Hit Ok to confirm.'),
+ },
+ {
+ type: 'image',
+ src: getImageLocation('reset_transaction_log_message.png'),
+ },
+ ],
+ },
+ {
+ title: localize('How do I control my losses with Deriv Bot?'),
+ description: [
+ {
+ type: 'text',
+ content: localize(
+ 'There are several ways to control your losses with Deriv Bot. Here’s a simple example of how you can implement loss control in your strategy:'
+ ),
+ },
+ {
+ type: 'image',
+ src: getImageLocation('loss_control.png'),
+ },
+ {
+ type: 'text',
+ content: localize('1. Create the following variables:'),
+ },
+ {
+ type: 'text',
+ content: localize(
+ '- currentPL : Use this variable to store the cumulative profit or loss while your bot is running. Set the initial value to 0 .'
+ ),
+ },
+ {
+ type: 'text',
+ content: localize(
+ '- currentStake : Use this variable to store the stake amount used in the last contract. You can assign any amount you want, but it must be a positive number.'
+ ),
+ },
+ {
+ type: 'text',
+ content: localize(
+ '- maximumLoss : Use this variable to store your maximum loss limit. You can assign any amount you want, but it must be a positive number.'
+ ),
+ },
+ {
+ type: 'text',
+ content: localize(
+ '- tradeAgain : Use this variable to stop trading when your loss limit is reached. Set the initial value to true .'
+ ),
+ },
+ {
+ type: 'image',
+ src: getImageLocation('loss_control_trade_parameters.png'),
+ },
+ {
+ type: 'text',
+ content: localize(
+ '2. Use a logic block to check if currentPL exceeds maximumLoss . If it does, set tradeAgain to false to prevent the bot from running another cycle.'
+ ),
+ },
+ {
+ type: 'image',
+ src: getImageLocation('loss_control_purchase_conditions.png'),
+ },
+ {
+ type: 'text',
+ content: localize(
+ '3. Update currentPL with the profit from the last contract. If the last contract was lost, the value of currentPL will be negative.'
+ ),
+ },
+ {
+ type: 'image',
+ src: getImageLocation('loss_control_restart_trade_conditions.png'),
+ },
+ ],
+ },
+ {
+ title: localize('Can I run Deriv Bot on multiple tabs in my web browser?'),
+ description: [
+ {
+ type: 'text',
+ content: localize(
+ 'Yes, you can. However, there are limits on your account, such as maximum number of open positions and maximum aggregate payouts on open positions. So, just keep these limits in mind when opening multiple positions. You can find more info about these limits at Settings > Account limits .'
+ ),
+ },
+ ],
+ },
+ {
+ title: localize('Can I trade cryptocurrencies on Deriv Bot?'),
+ description: [
+ {
+ type: 'text',
+ content: localize("No, we don't offer cryptocurrencies on Deriv Bot."),
+ },
+ ],
+ },
+ {
+ title: localize('Do you sell trading bots?'),
+ description: [
+ {
+ type: 'text',
+ content: localize(
+ "No, we don't. However, you'll find quick strategies on Deriv Bot that'll help you build your own trading bot for free."
+ ),
+ },
+ ],
+ },
+ {
+ title: localize('In which countries is Deriv Bot available?'),
+ description: [
+ {
+ type: 'text',
+ content: localize(
+ 'We offer our services in all countries, except for the ones mentioned in our terms and conditions. '
+ ),
+ },
+ ],
+ },
+ {
+ title: localize('If I close my web browser, will Deriv Bot continue to run?'),
+ description: [
+ {
+ type: 'text',
+ content: localize('No, Deriv Bot will stop running when your web browser is closed.'),
+ },
+ ],
+ },
+ {
+ title: localize('What are the most popular strategies for automated trading?'),
+ description: [
+ {
+ type: 'text',
+ content: localize(
+ "Three of the most commonly used strategies in automated trading are Martingale, D'Alembert, and Oscar's Grind — you can find them all ready-made and waiting for you in Deriv Bot."
+ ),
+ },
+ ],
+ },
+ {
+ title: localize('How do I build a trading bot?'),
+ description: [
+ {
+ type: 'text',
+ content: localize(
+ 'Watch this video to learn how to build a trading bot on Deriv Bot. Also, check out this blog post on building a trading bot.'
+ ),
+ },
+ ],
+ },
+];
diff --git a/packages/bot-web-ui/src/components/download/download.tsx b/packages/bot-web-ui/src/components/download/download.tsx
index b68dcba8facc..40424fca8338 100644
--- a/packages/bot-web-ui/src/components/download/download.tsx
+++ b/packages/bot-web-ui/src/components/download/download.tsx
@@ -1,8 +1,8 @@
import React from 'react';
import { Button, Icon, Popover } from '@deriv/components';
import { localize } from '@deriv/translations';
-import RootStore from 'Stores/index';
import { connect } from 'Stores/connect';
+import RootStore from 'Stores/index';
type TDownloadProps = {
onClickDownloadTransaction: () => void;
diff --git a/packages/bot-web-ui/src/components/flyout/flyout-block-group.jsx b/packages/bot-web-ui/src/components/flyout/flyout-block-group.jsx
index 1e8242f94fc7..b4e9693270de 100644
--- a/packages/bot-web-ui/src/components/flyout/flyout-block-group.jsx
+++ b/packages/bot-web-ui/src/components/flyout/flyout-block-group.jsx
@@ -1,8 +1,8 @@
-import { Button, Text } from '@deriv/components';
import React from 'react';
+import classNames from 'classnames';
import { PropTypes } from 'prop-types';
+import { Button, Text } from '@deriv/components';
import { Localize } from '@deriv/translations';
-import classNames from 'classnames';
import FlyoutBlock from './flyout-block.jsx';
const FlyoutBlockGroup = ({ onInfoClick, block_node, is_active, should_hide_display_name }) => {
diff --git a/packages/bot-web-ui/src/components/flyout/flyout.jsx b/packages/bot-web-ui/src/components/flyout/flyout.jsx
index 4e1009db0084..b30fa383f9f1 100644
--- a/packages/bot-web-ui/src/components/flyout/flyout.jsx
+++ b/packages/bot-web-ui/src/components/flyout/flyout.jsx
@@ -1,9 +1,9 @@
-import classNames from 'classnames';
import React from 'react';
+import classNames from 'classnames';
import PropTypes from 'prop-types';
-import { localize } from '@deriv/translations';
+import { Icon, Input, Text, ThemedScrollbars } from '@deriv/components';
import { getPlatformSettings } from '@deriv/shared';
-import { Icon, ThemedScrollbars, Input, Text } from '@deriv/components';
+import { localize } from '@deriv/translations';
import { help_content_config } from 'Utils/help-content/help-content.config';
import { connect } from 'Stores/connect';
import FlyoutBlockGroup from './flyout-block-group.jsx';
@@ -36,20 +36,6 @@ const FlyoutContent = props => {
first_get_variable_block_index,
} = props;
- React.useEffect(() => {
- flyout_ref.current.scrollTop = 0;
- }, [selected_category]);
-
- React.useEffect(() => {
- const view = flyout_ref.current.querySelector('.flyout__item--active');
- if (view) {
- view.scrollIntoView({
- behavior: 'smooth',
- block: 'start',
- });
- }
- }, [active_helper]);
-
return (
{
is_visible && (
{
diff --git a/packages/bot-web-ui/src/components/journal/journal-components/index.ts b/packages/bot-web-ui/src/components/journal/journal-components/index.ts
index 7a8a89d01289..ad72c129d6de 100644
--- a/packages/bot-web-ui/src/components/journal/journal-components/index.ts
+++ b/packages/bot-web-ui/src/components/journal/journal-components/index.ts
@@ -1,7 +1,7 @@
-export { default as FormatMessage } from './format-message';
export { default as DateItem } from './date-item';
export { default as FilterDialog } from './filter-dialog';
export { default as Filters } from './filters';
+export { default as FormatMessage } from './format-message';
export { default as JournalItem } from './journal-item';
export { default as JournalLoader } from './journal-loader';
export { default as JournalTools } from './journal-tools';
diff --git a/packages/bot-web-ui/src/components/journal/journal-components/journal-item.tsx b/packages/bot-web-ui/src/components/journal/journal-components/journal-item.tsx
index 1012e66c6708..e08429e07b37 100644
--- a/packages/bot-web-ui/src/components/journal/journal-components/journal-item.tsx
+++ b/packages/bot-web-ui/src/components/journal/journal-components/journal-item.tsx
@@ -1,10 +1,10 @@
-import { message_types } from '@deriv/bot-skeleton';
-import { useNewRowTransition } from '@deriv/shared';
-import classnames from 'classnames';
import React from 'react';
+import classnames from 'classnames';
import { CSSTransition } from 'react-transition-group';
+import { message_types } from '@deriv/bot-skeleton';
+import { useNewRowTransition } from '@deriv/shared';
import { TJournalItemExtra, TJournalItemProps } from '../journal.types';
-import { FormatMessage, DateItem } from '.';
+import { DateItem, FormatMessage } from '.';
const getJournalItemContent = (
message: string | ((value: () => void) => string),
diff --git a/packages/bot-web-ui/src/components/journal/journal-components/journal-loader.tsx b/packages/bot-web-ui/src/components/journal/journal-components/journal-loader.tsx
index 67df6108fe6e..3e95c52ac306 100644
--- a/packages/bot-web-ui/src/components/journal/journal-components/journal-loader.tsx
+++ b/packages/bot-web-ui/src/components/journal/journal-components/journal-loader.tsx
@@ -1,5 +1,5 @@
-import classnames from 'classnames';
import React from 'react';
+import classnames from 'classnames';
import ContentLoader from 'react-content-loader';
const JournalLoader = ({ is_mobile }: { is_mobile: boolean }) => (
diff --git a/packages/bot-web-ui/src/components/journal/journal-components/journal-tools.tsx b/packages/bot-web-ui/src/components/journal/journal-components/journal-tools.tsx
index 1a5fccd5597d..409d491ca092 100644
--- a/packages/bot-web-ui/src/components/journal/journal-components/journal-tools.tsx
+++ b/packages/bot-web-ui/src/components/journal/journal-components/journal-tools.tsx
@@ -1,10 +1,10 @@
+import React from 'react';
+import { CSSTransition } from 'react-transition-group';
import { DesktopWrapper, Icon, Text } from '@deriv/components';
import { Localize } from '@deriv/translations';
import Download from 'Components/download';
-import React from 'react';
-import { CSSTransition } from 'react-transition-group';
-import { FilterDialog } from '.';
import { TJournalToolsProps } from '../journal.types';
+import { FilterDialog } from '.';
const JournalTools = ({
checked_filters,
diff --git a/packages/bot-web-ui/src/components/journal/journal.scss b/packages/bot-web-ui/src/components/journal/journal.scss
index 3ee640ca40e0..d3815d3782bc 100644
--- a/packages/bot-web-ui/src/components/journal/journal.scss
+++ b/packages/bot-web-ui/src/components/journal/journal.scss
@@ -16,10 +16,14 @@
}
&__message {
margin: 0 auto;
+ & .dc-text {
+ line-height: var(--text-lh-xxl);
+ }
}
&__list {
- list-style-type: circle;
+ list-style-type: disc;
margin-left: 20px;
+ line-height: var(--text-lh-xl);
li::marker {
color: var(--text-less-prominent);
@@ -30,7 +34,7 @@
padding: 16px;
&-list {
- height: calc(100% - 4.2rem);
+ height: calc(100% - 7.2rem);
}
&-content {
> * {
diff --git a/packages/bot-web-ui/src/components/journal/journal.tsx b/packages/bot-web-ui/src/components/journal/journal.tsx
index 5005381d87c3..42a37429c97c 100644
--- a/packages/bot-web-ui/src/components/journal/journal.tsx
+++ b/packages/bot-web-ui/src/components/journal/journal.tsx
@@ -1,17 +1,17 @@
+import React from 'react';
+import classnames from 'classnames';
import { DataList, Icon, Text } from '@deriv/components';
+import { isMobile } from '@deriv/shared';
import { localize } from '@deriv/translations';
-import classnames from 'classnames';
import { contract_stages } from 'Constants/contract-stage';
-import React from 'react';
import { connect } from 'Stores/connect';
import RootStore from 'Stores/index';
-import { JournalItem, JournalLoader, JournalTools } from './journal-components';
import { TCheckedFilters, TFilterMessageValues, TJournalDataListArgs, TJournalProps } from './journal.types';
+import { JournalItem, JournalLoader, JournalTools } from './journal-components';
const Journal = ({
contract_stage,
filtered_messages,
- is_mobile,
is_stop_button_visible,
unfiltered_messages,
...props
@@ -19,6 +19,7 @@ const Journal = ({
const filtered_messages_length = Array.isArray(filtered_messages) && filtered_messages.length;
const unfiltered_messages_length = Array.isArray(unfiltered_messages) && unfiltered_messages.length;
const { checked_filters } = props;
+ const is_mobile = isMobile();
return (
({
+export default connect(({ journal, run_panel }: RootStore) => ({
checked_filters: journal.checked_filters,
filterMessage: journal.filterMessage,
filters: journal.filters,
@@ -97,5 +98,4 @@ export default connect(({ journal, run_panel, ui }: RootStore) => ({
unfiltered_messages: journal.unfiltered_messages,
is_stop_button_visible: run_panel.is_stop_button_visible,
contract_stage: run_panel.contract_stage,
- is_mobile: ui.is_mobile,
}))(Journal);
diff --git a/packages/bot-web-ui/src/components/journal/journal.types.ts b/packages/bot-web-ui/src/components/journal/journal.types.ts
index 52a7f4f0970c..93b420e1c809 100644
--- a/packages/bot-web-ui/src/components/journal/journal.types.ts
+++ b/packages/bot-web-ui/src/components/journal/journal.types.ts
@@ -59,7 +59,6 @@ export type TJournalProps = {
contract_stage: number;
filtered_messages: TFilterMessageProps | [];
is_drawer_open: boolean;
- is_mobile: boolean;
is_stop_button_visible: boolean;
unfiltered_messages: TFilterMessageProps;
checked_filters: TCheckedFilters;
diff --git a/packages/bot-web-ui/src/components/load-modal/google-drive.jsx b/packages/bot-web-ui/src/components/load-modal/google-drive.jsx
deleted file mode 100644
index 4126ef8ea65e..000000000000
--- a/packages/bot-web-ui/src/components/load-modal/google-drive.jsx
+++ /dev/null
@@ -1,76 +0,0 @@
-import classnames from 'classnames';
-import React from 'react';
-import { PropTypes } from 'prop-types';
-import { Button, Icon, StaticUrl } from '@deriv/components';
-import { Localize, localize } from '@deriv/translations';
-import { connect } from 'Stores/connect';
-
-const GoogleDrive = ({ is_authorised, is_open_button_loading, onDriveConnect, onDriveOpen, is_mobile }) => {
- return (
-
-
-
-
- {is_authorised ? (
-
- ) : (
- 'Google Drive'
- )}
-
- {is_authorised ? (
-
-
-
-
- ) : (
-
-
-
- ,
- ,
- ]}
- />
-
-
- )}
-
-
- );
-};
-
-GoogleDrive.propTypes = {
- is_authorised: PropTypes.bool,
- is_mobile: PropTypes.bool,
- is_open_button_loading: PropTypes.bool,
- onDriveConnect: PropTypes.func,
- onDriveOpen: PropTypes.func,
-};
-
-export default connect(({ load_modal, ui, google_drive }) => ({
- is_authorised: google_drive.is_authorised,
- is_mobile: ui.is_mobile,
- is_open_button_loading: load_modal.is_open_button_loading,
- onDriveConnect: load_modal.onDriveConnect,
- onDriveOpen: load_modal.onDriveOpen,
-}))(GoogleDrive);
diff --git a/packages/bot-web-ui/src/components/load-modal/index.js b/packages/bot-web-ui/src/components/load-modal/index.js
deleted file mode 100644
index 301c7b133fdc..000000000000
--- a/packages/bot-web-ui/src/components/load-modal/index.js
+++ /dev/null
@@ -1,4 +0,0 @@
-import LoadModal from './load-modal.jsx';
-import './load-modal.scss';
-
-export default LoadModal;
diff --git a/packages/bot-web-ui/src/components/load-modal/index.ts b/packages/bot-web-ui/src/components/load-modal/index.ts
new file mode 100644
index 000000000000..28a32562fda4
--- /dev/null
+++ b/packages/bot-web-ui/src/components/load-modal/index.ts
@@ -0,0 +1,4 @@
+import LoadModal from './load-modal';
+import './load-modal.scss';
+
+export default LoadModal;
diff --git a/packages/bot-web-ui/src/components/load-modal/load-modal.jsx b/packages/bot-web-ui/src/components/load-modal/load-modal.jsx
deleted file mode 100644
index e5822e569090..000000000000
--- a/packages/bot-web-ui/src/components/load-modal/load-modal.jsx
+++ /dev/null
@@ -1,105 +0,0 @@
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Modal, Tabs, MobileFullPageModal } from '@deriv/components';
-import { localize } from '@deriv/translations';
-import { connect } from 'Stores/connect';
-import { tabs_title } from 'Constants/load-modal';
-import GoogleDrive from './google-drive.jsx';
-import Local from './local.jsx';
-import Recent from './recent.jsx';
-
-const LoadModal = ({
- active_index,
- is_load_modal_open,
- is_mobile,
- loaded_local_file,
- onEntered,
- recent_strategies,
- setActiveTabIndex,
- tab_name,
- toggleLoadModal,
-}) => {
- const header_text = localize('Load strategy');
-
- if (is_mobile) {
- return (
-
-
-
-
-
-
-
-
-
-
- );
- }
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {loaded_local_file && tab_name === tabs_title.TAB_LOCAL && (
-
-
-
- )}
- {recent_strategies.length > 0 && tab_name === tabs_title.TAB_RECENT && (
-
-
-
- )}
-
- );
-};
-
-LoadModal.propTypes = {
- active_index: PropTypes.number,
- is_load_modal_open: PropTypes.bool,
- is_mobile: PropTypes.bool,
- loaded_local_file: PropTypes.string,
- onEntered: PropTypes.func,
- recent_strategies: PropTypes.array,
- setActiveTabIndex: PropTypes.func,
- toggleLoadModal: PropTypes.func,
-};
-
-export default connect(({ load_modal, ui }) => ({
- active_index: load_modal.active_index,
- is_load_modal_open: load_modal.is_load_modal_open,
- is_mobile: ui.is_mobile,
- loaded_local_file: load_modal.loaded_local_file,
- onEntered: load_modal.onEntered,
- recent_strategies: load_modal.recent_strategies,
- setActiveTabIndex: load_modal.setActiveTabIndex,
- tab_name: load_modal.tab_name,
- toggleLoadModal: load_modal.toggleLoadModal,
-}))(LoadModal);
diff --git a/packages/bot-web-ui/src/components/load-modal/load-modal.scss b/packages/bot-web-ui/src/components/load-modal/load-modal.scss
index 852e1d7e1d34..8d3009f1ada8 100644
--- a/packages/bot-web-ui/src/components/load-modal/load-modal.scss
+++ b/packages/bot-web-ui/src/components/load-modal/load-modal.scss
@@ -1,311 +1,73 @@
-.load-strategy {
- &__wrapper {
- position: fixed;
- top: 40px;
- z-index: 10;
- width: 100%;
+#modal_root {
+ .load-strategy {
+ &__recent {
+ &-item {
+ height: 10rem;
+ padding: 1.6rem;
+ grid-template-columns: 1fr 1fr;
- .dc-mobile-full-page-modal {
- @include mobile {
- width: 100%;
- }
+ &-location {
+ justify-content: center;
+ }
- &__body {
- height: 100%;
- }
- }
- & .dc-tabs {
- @include mobile {
- height: 100%;
- display: unset;
- flex: 1;
+ &-text {
+ height: unset;
+ flex-direction: column;
+ text-align: left;
+ }
- &__content {
- height: calc(100% - 40px);
+ &-title {
+ font-size: var(--text-size-s);
}
- }
- }
- }
- &__container {
- @include desktop {
- height: calc(80vh - 0.8rem - 4rem - 4.8rem - 5.8rem);
- &--has-footer {
- height: calc(80vh - 0.8rem - 4rem - 4.8rem - 5.8rem - 7.4rem);
- }
- > * {
- height: 100%;
- margin-top: 0.8rem;
+ &-saved {
+ font-size: var(--text-size-s);
+ }
}
- }
- @include tablet {
- height: calc(80vh - 0.8rem - 4rem - 4.8rem - 5.8rem);
- &--has-footer {
- height: calc(80vh - 0.8rem - 4rem - 4.8rem - 5.8rem - 7.4rem);
- }
- > * {
+ &-files {
+ width: 35%;
+ overflow: auto;
height: 100%;
- margin-top: 0.8rem;
- }
- }
- @include mobile {
- height: 100%;
- }
- }
- &__title {
- font-size: var(--text-size-s);
- font-weight: 700;
- margin: 1.5rem;
- }
- &__preview-workspace {
- padding: 1.5rem;
- border-radius: $BORDER_RADIUS;
- border: solid 1px var(--border-normal);
- height: calc(100% - 3.7rem); // TODO: What is 37px?
- position: relative;
-
- &-controls {
- padding: 0.7rem 0.5rem;
- display: flex;
- flex-direction: column;
- position: absolute;
- right: 1.6rem;
- bottom: 1.6rem;
- border-radius: 3rem;
- background-color: $color-grey-2;
- box-shadow: 0.2rem 0.2rem 0.5rem var(--shadow-menu);
- z-index: 99;
- }
- &-icon {
- margin: 0.5rem;
- cursor: pointer;
- }
- }
- &__recent {
- display: flex;
- flex-direction: row;
- gap: 1.6rem;
-
- &-files {
- flex-basis: 35%;
-
- &-list {
- overflow: auto; /* TODO: ThemedScrollbars! */
- height: calc(100% - 52px); // 100% - Title height
}
- }
- &-preview {
- flex-basis: 65%;
- display: flex;
- flex-direction: column;
- &-title {
- margin-left: 0;
- }
- }
- &-empty {
- display: flex;
- align-items: center;
- flex-direction: column;
- justify-content: center;
- text-align: center;
+ &-preview {
+ width: 65%;
- &-icon {
- margin-bottom: 1.6rem;
- }
- &-title {
- margin-bottom: 0.8rem;
- font-size: var(--text-size-s);
- font-weight: bold;
- line-height: 2.4rem;
- }
- &-description {
- margin-bottom: 1.6rem;
- font-size: var(--text-size-xs);
- line-height: 2rem;
- }
- &-expand {
- margin-bottom: 0.8rem;
- color: var(--brand-red-coral);
- font-size: var(--text-size-xs);
- font-weight: bold;
- cursor: pointer;
-
- &:hover {
- text-decoration: underline;
+ .load-strategy__preview-workspace {
+ height: calc(100% - 5.2rem);
+ min-height: unset;
+ margin: 0;
}
- }
- &-explanation {
- font-size: var(--text-size-xxs);
- text-align: left;
- opacity: 0;
- &-list {
- margin-top: 0.8rem;
- }
- &--show {
- opacity: 1;
- width: fit-content;
+ &-title {
+ margin: 1.5rem 0;
+ margin-left: 0;
}
}
}
- &-item {
- display: grid;
- grid-template-columns: 1fr 1fr;
- grid-template-areas: ('text location');
- padding: 1.6rem;
- height: 100px;
- &:not(.load-strategy__recent-item--selected):hover {
- background-color: var(--state-hover);
- cursor: pointer;
- }
- &:not(:last-child) {
- border-bottom: solid 0.1rem var(--border-normal);
- }
- &--selected {
- background-color: var(--state-active);
- }
- &-text {
- flex-direction: column;
- display: flex;
- justify-content: center;
- }
- &-title {
- font-size: var(--text-size-s);
- }
- &-time {
- font-size: var(--text-size-xxs);
- }
- &-location {
- flex-direction: row;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- &-saved {
- margin-left: 1rem;
- font-size: var(--text-size-s);
- line-height: 1.43;
- }
- }
- }
- &__local {
- &-dropzone {
- @include desktop {
- padding-top: 1.6rem;
- }
- @include mobile {
- height: 100%;
- padding: 1.6rem;
- }
-
- &-area {
- align-items: center;
- display: flex;
- flex-direction: column;
- justify-content: center;
- border: dashed 0.2rem var(--border-normal);
- border-radius: $BORDER_RADIUS;
- height: 100%;
- padding: 1.6rem;
+ &__container {
+ &--has-footer {
+ height: calc(80vh - 21rem);
+ margin-top: -1rem;
}
}
- &-icon {
- margin-bottom: 1.6rem;
- }
- &-title {
- margin-bottom: 1.6rem;
- font-size: var(--text-size-s);
- }
- &-description {
- margin-bottom: 1.6rem;
- font-size: var(--text-size-xs);
- }
- &-preview {
- display: flex;
- flex-direction: column;
- position: relative;
-
- @include mobile {
- padding: 1.6rem;
- height: calc(100% - 7.4rem); // - footer height
- }
- &-close {
- // TODO : Dark Theme
- background-image: radial-gradient(at right top);
- position: absolute;
- padding: 25px;
- border-bottom-left-radius: 50%;
- right: 0;
- top: 0;
- z-index: 99;
- cursor: pointer;
- }
- }
- &-footer {
- padding: 1.6rem;
- display: flex;
- justify-content: flex-end;
- border-top: 1px solid var(--general-section-1);
+ &__title {
+ margin: 1.5rem;
}
}
- &__google-drive {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
-
- @include mobile {
- border: dashed 0.2rem var(--border-normal);
- border-radius: $BORDER_RADIUS;
- margin: 1.6rem;
- padding: 1.6rem;
- height: calc(100% - 3.2rem); // - 2x margin.
- }
-
- &-icon {
- margin-bottom: 1.6rem;
- &--disabled {
- opacity: 0.32;
- }
- }
- &-text {
- margin-bottom: 1.6rem;
- font-size: var(--text-size-s);
- font-weight: bold;
- line-height: 2.4rem;
- }
- &-terms {
- font-size: var(--text-size-xs);
- line-height: 2rem;
- margin-top: 1.6rem;
- text-align: center;
- color: var(--text-general);
- }
+ .load-strategy__preview-workspace {
+ min-height: unset;
+ height: unset;
+ margin: 0;
}
}
-.picker {
- max-width: 98%;
- border-radius: 8px;
- max-height: 99%;
-
- &-content {
- max-width: 98%;
- padding: 1%;
- }
- @include mobile {
- height: 100%;
- width: 100%;
- top: 0px;
- }
-}
-.dc-modal__container_load-strategy {
- @include tablet {
- width: calc(100vw - 4.8rem) !important;
- }
+.load-strategy__recent-item-text {
+ height: 10rem;
+ flex-direction: column;
+ text-align: left;
}
diff --git a/packages/bot-web-ui/src/components/load-modal/load-modal.tsx b/packages/bot-web-ui/src/components/load-modal/load-modal.tsx
new file mode 100644
index 000000000000..3e07e73567b0
--- /dev/null
+++ b/packages/bot-web-ui/src/components/load-modal/load-modal.tsx
@@ -0,0 +1,115 @@
+import React from 'react';
+import { MobileFullPageModal, Modal, Tabs } from '@deriv/components';
+import { isMobile } from '@deriv/shared';
+import { localize } from '@deriv/translations';
+import { tabs_title } from 'Constants/load-modal';
+import { connect } from 'Stores/connect';
+import RootStore from 'Stores/root-store';
+import GoogleDrive from '../dashboard/dashboard-component/load-bot-preview/google-drive';
+import Local from './local';
+import LocalFooter from './local-footer';
+import Recent from './recent';
+import RecentFooter from './recent-footer';
+
+type TLoadModalProps = {
+ active_index: number;
+ is_load_modal_open: boolean;
+ loaded_local_file: string;
+ onEntered: () => void;
+ recent_strategies: any[];
+ setActiveTabIndex: () => void;
+ setPreviewOnPopup: (show: boolean) => void;
+ tab_name: string;
+ toggleLoadModal: () => void;
+};
+
+const LoadModal = ({
+ active_index,
+ is_load_modal_open,
+ loaded_local_file,
+ onEntered,
+ recent_strategies,
+ setActiveTabIndex,
+ setPreviewOnPopup,
+ tab_name,
+ toggleLoadModal,
+}: TLoadModalProps) => {
+ const header_text = localize('Load strategy');
+
+ if (isMobile()) {
+ return (
+
{
+ setPreviewOnPopup(false);
+ toggleLoadModal();
+ }}
+ height_offset='80px'
+ page_overlay
+ >
+
+
+
+
+
+
+
+
+
+ );
+ }
+
+ const has_loaded_file = loaded_local_file && tab_name === tabs_title.TAB_LOCAL;
+ const has_recent_strategies = recent_strategies.length > 0 && tab_name === tabs_title.TAB_RECENT;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {has_recent_strategies && (
+
+
+
+ )}
+ {has_loaded_file && (
+
+
+
+ )}
+
+ );
+};
+
+export default connect(({ load_modal, dashboard }: RootStore) => ({
+ active_index: load_modal.active_index,
+ is_load_modal_open: load_modal.is_load_modal_open,
+ loaded_local_file: load_modal.loaded_local_file,
+ onEntered: load_modal.onEntered,
+ recent_strategies: load_modal.recent_strategies,
+ setActiveTabIndex: load_modal.setActiveTabIndex,
+ tab_name: load_modal.tab_name,
+ toggleLoadModal: load_modal.toggleLoadModal,
+ setPreviewOnPopup: dashboard.setPreviewOnPopup,
+}))(LoadModal);
diff --git a/packages/bot-web-ui/src/components/load-modal/local-footer.jsx b/packages/bot-web-ui/src/components/load-modal/local-footer.jsx
deleted file mode 100644
index 8b2288b50043..000000000000
--- a/packages/bot-web-ui/src/components/load-modal/local-footer.jsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import React from 'react';
-import { PropTypes } from 'prop-types';
-import { Button } from '@deriv/components';
-import { localize } from '@deriv/translations';
-import { connect } from 'Stores/connect';
-
-const LocalFooter = ({ is_mobile, is_open_button_loading, loadFileFromLocal, setLoadedLocalFile }) => {
- const Wrapper = is_mobile ? Button.Group : React.Fragment;
- return (
-
- {is_mobile && (
- setLoadedLocalFile(null)} has_effect secondary large />
- )}
-
-
- );
-};
-
-LocalFooter.propTypes = {
- is_mobile: PropTypes.bool,
- is_open_button_loading: PropTypes.bool,
- loadFileFromLocal: PropTypes.func,
- setLoadedLocalFile: PropTypes.bool,
-};
-
-export default connect(({ load_modal, ui }) => ({
- is_mobile: ui.is_mobile,
- is_open_button_loading: load_modal.is_open_button_loading,
- loadFileFromLocal: load_modal.loadFileFromLocal,
- setLoadedLocalFile: load_modal.setLoadedLocalFile,
-}))(LocalFooter);
diff --git a/packages/bot-web-ui/src/components/load-modal/local-footer.tsx b/packages/bot-web-ui/src/components/load-modal/local-footer.tsx
new file mode 100644
index 000000000000..384056add341
--- /dev/null
+++ b/packages/bot-web-ui/src/components/load-modal/local-footer.tsx
@@ -0,0 +1,57 @@
+import React from 'react';
+import { Button } from '@deriv/components';
+import { isMobile } from '@deriv/shared';
+import { localize } from '@deriv/translations';
+import { connect } from 'Stores/connect';
+import RootStore from 'Stores/root-store';
+
+type TLocalFooterProps = {
+ is_open_button_loading: boolean;
+ loadFileFromLocal: () => void;
+ setLoadedLocalFile: (loaded_local_file: boolean | null) => void;
+ setOpenSettings: (toast_message: string, show_toast?: boolean) => void;
+ setPreviewOnPopup: (show: boolean) => void;
+ toggleLoadModal: () => void;
+};
+
+const LocalFooter = ({
+ is_open_button_loading,
+ loadFileFromLocal,
+ setLoadedLocalFile,
+ setOpenSettings,
+ setPreviewOnPopup,
+ toggleLoadModal,
+}: TLocalFooterProps) => {
+ const is_mobile = isMobile();
+ const Wrapper = is_mobile ? Button.Group : React.Fragment;
+
+ return (
+
+ {is_mobile && (
+ setLoadedLocalFile(null)} has_effect secondary large />
+ )}
+ {
+ loadFileFromLocal();
+ toggleLoadModal();
+ setPreviewOnPopup(false);
+ setOpenSettings('import');
+ }}
+ is_loading={is_open_button_loading}
+ has_effect
+ primary
+ large
+ />
+
+ );
+};
+
+export default connect(({ load_modal, dashboard }: RootStore) => ({
+ is_open_button_loading: load_modal.is_open_button_loading,
+ loadFileFromLocal: load_modal.loadFileFromLocal,
+ setLoadedLocalFile: load_modal.setLoadedLocalFile,
+ setOpenSettings: dashboard.setOpenSettings,
+ setPreviewOnPopup: dashboard.setPreviewOnPopup,
+ toggleLoadModal: load_modal.toggleLoadModal,
+}))(LocalFooter);
diff --git a/packages/bot-web-ui/src/components/load-modal/local.jsx b/packages/bot-web-ui/src/components/load-modal/local.jsx
deleted file mode 100644
index c5912ac0012b..000000000000
--- a/packages/bot-web-ui/src/components/load-modal/local.jsx
+++ /dev/null
@@ -1,104 +0,0 @@
-import React from 'react';
-import { PropTypes } from 'prop-types';
-import { Button, Icon } from '@deriv/components';
-import { Localize, localize } from '@deriv/translations';
-import { connect } from 'Stores/connect';
-import LocalFooter from './local-footer.jsx';
-import WorkspaceControl from './workspace-control.jsx';
-
-const LocalComponent = ({ handleFileChange, is_mobile, loaded_local_file, setLoadedLocalFile }) => {
- const file_input_ref = React.useRef(null);
- const [is_file_supported, setIsFileSupported] = React.useState(true);
-
- if (loaded_local_file && is_file_supported) {
- return (
-
-
-
-
-
-
-
- {!is_mobile && (
-
- setLoadedLocalFile(null)} />
-
- )}
-
-
-
-
- {is_mobile && (
-
-
-
- )}
-
- );
- }
-
- return (
-
-
-
setIsFileSupported(handleFileChange(e, false))}
- />
-
{
- handleFileChange(e, false);
- }}
- >
- {is_mobile ? (
-
- ) : (
-
-
-
-
-
-
-
-
-
- )}
-
file_input_ref.current.click()}
- has_effect
- primary
- large
- />
-
-
-
- );
-};
-
-LocalComponent.propTypes = {
- handleFileChange: PropTypes.func,
- is_mobile: PropTypes.bool,
- is_open_button_loading: PropTypes.bool,
- loaded_local_file: PropTypes.string,
- setLoadedLocalFile: PropTypes.func,
-};
-
-const Local = connect(({ load_modal, ui }) => ({
- handleFileChange: load_modal.handleFileChange,
- is_mobile: ui.is_mobile,
- is_open_button_loading: load_modal.is_open_button_loading,
- loaded_local_file: load_modal.loaded_local_file,
- setLoadedLocalFile: load_modal.setLoadedLocalFile,
-}))(LocalComponent);
-
-Local.Footer = LocalFooter;
-
-export default Local;
diff --git a/packages/bot-web-ui/src/components/load-modal/local.tsx b/packages/bot-web-ui/src/components/load-modal/local.tsx
new file mode 100644
index 000000000000..bdbc581830dd
--- /dev/null
+++ b/packages/bot-web-ui/src/components/load-modal/local.tsx
@@ -0,0 +1,118 @@
+import React from 'react';
+import classNames from 'classnames';
+import { Button, Icon } from '@deriv/components';
+import { isMobile } from '@deriv/shared';
+import { Localize, localize } from '@deriv/translations';
+import { connect } from 'Stores/connect';
+import RootStore from 'Stores/root-store';
+import LocalFooter from './local-footer';
+import WorkspaceControl from './workspace-control';
+
+type TLocalComponentProps = {
+ active_tab: number;
+ has_started_bot_builder_tour: boolean;
+ handleFileChange: (
+ e: React.MouseEvent | React.FormEvent
| DragEvent,
+ is_body?: boolean
+ ) => boolean;
+ is_open_button_loading: boolean;
+ loaded_local_file: string;
+ setLoadedLocalFile: (loaded_local_file: boolean | null) => void;
+};
+
+const LocalComponent = ({
+ active_tab,
+ has_started_bot_builder_tour,
+ handleFileChange,
+ loaded_local_file,
+ setLoadedLocalFile,
+}: TLocalComponentProps) => {
+ const file_input_ref = React.useRef(null);
+ const [is_file_supported, setIsFileSupported] = React.useState(true);
+ const is_mobile = isMobile();
+
+ if (loaded_local_file && is_file_supported) {
+ return (
+
+
+
+
+
+
+
+
+ setLoadedLocalFile(null)} />
+
+
+
+
+
+ {is_mobile && (
+
+
+
+ )}
+
+ );
+ }
+
+ return (
+
+
+
setIsFileSupported(handleFileChange(e, false))}
+ />
+
{
+ handleFileChange(e, false);
+ }}
+ >
+ {is_mobile ? (
+
+ ) : (
+
+
+
+
+
+
+
+
+
+ )}
+
file_input_ref.current.click()}
+ has_effect
+ primary
+ large
+ />
+
+
+
+ );
+};
+
+const Local = connect(({ load_modal, dashboard }: RootStore) => ({
+ active_tab: dashboard.active_tab,
+ has_started_bot_builder_tour: dashboard.has_started_bot_builder_tour,
+ handleFileChange: load_modal.handleFileChange,
+ is_open_button_loading: load_modal.is_open_button_loading,
+ loaded_local_file: load_modal.loaded_local_file,
+ setLoadedLocalFile: load_modal.setLoadedLocalFile,
+}))(LocalComponent);
+
+export default Local;
diff --git a/packages/bot-web-ui/src/components/load-modal/recent-footer.jsx b/packages/bot-web-ui/src/components/load-modal/recent-footer.jsx
deleted file mode 100644
index 35a9dea2748c..000000000000
--- a/packages/bot-web-ui/src/components/load-modal/recent-footer.jsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import React from 'react';
-import { Button } from '@deriv/components';
-import { localize } from '@deriv/translations';
-import { PropTypes } from 'mobx-react';
-import { connect } from 'Stores/connect';
-
-const RecentFooter = ({ is_open_button_loading, loadFileFromRecent }) => {
- return (
-
- );
-};
-
-RecentFooter.propTypes = {
- is_open_button_loading: PropTypes.bool,
- loadFileFromRecent: PropTypes.func,
-};
-
-export default connect(({ load_modal }) => ({
- is_open_button_loading: load_modal.is_open_button_loading,
- loadFileFromRecent: load_modal.loadFileFromRecent,
-}))(RecentFooter);
diff --git a/packages/bot-web-ui/src/components/load-modal/recent-footer.tsx b/packages/bot-web-ui/src/components/load-modal/recent-footer.tsx
new file mode 100644
index 000000000000..158f33b1b1a8
--- /dev/null
+++ b/packages/bot-web-ui/src/components/load-modal/recent-footer.tsx
@@ -0,0 +1,41 @@
+import React from 'react';
+import { Button } from '@deriv/components';
+import { localize } from '@deriv/translations';
+import { connect } from 'Stores/connect';
+import RootStore from 'Stores/root-store';
+
+type TRecentFooterProps = {
+ is_open_button_loading: boolean;
+ loadFileFromRecent: () => void;
+ setOpenSettings: (toast_message: string, show_toast?: boolean) => void;
+ toggleLoadModal: () => void;
+};
+
+const RecentFooter = ({
+ is_open_button_loading,
+ loadFileFromRecent,
+ setOpenSettings,
+ toggleLoadModal,
+}: TRecentFooterProps) => {
+ return (
+ {
+ loadFileFromRecent();
+ toggleLoadModal();
+ setOpenSettings('import');
+ }}
+ is_loading={is_open_button_loading}
+ has_effect
+ primary
+ large
+ />
+ );
+};
+
+export default connect(({ load_modal, dashboard }: RootStore) => ({
+ is_open_button_loading: load_modal.is_open_button_loading,
+ loadFileFromRecent: load_modal.loadFileFromRecent,
+ setOpenSettings: dashboard.setOpenSettings,
+ toggleLoadModal: load_modal.toggleLoadModal,
+}))(RecentFooter);
diff --git a/packages/bot-web-ui/src/components/load-modal/recent-workspace.jsx b/packages/bot-web-ui/src/components/load-modal/recent-workspace.jsx
deleted file mode 100644
index a1f8a157c2b3..000000000000
--- a/packages/bot-web-ui/src/components/load-modal/recent-workspace.jsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import classnames from 'classnames';
-import React from 'react';
-import { PropTypes } from 'prop-types';
-import { Icon } from '@deriv/components';
-import { timeSince } from '@deriv/bot-skeleton';
-import { save_types } from '@deriv/bot-skeleton/src/constants/save-type';
-import { connect } from 'Stores/connect';
-
-const RecentWorkspace = ({
- getRecentFileIcon,
- getSaveType,
- previewRecentStrategy,
- selected_strategy_id,
- workspace,
-}) => {
- return (
- previewRecentStrategy(workspace.id)}
- >
-
-
{workspace.name}
-
{timeSince(workspace.timestamp)}
-
-
-
-
{getSaveType(workspace.save_type)}
-
-
- );
-};
-
-RecentWorkspace.propTypes = {
- getRecentFileIcon: PropTypes.func,
- getSaveType: PropTypes.func,
- previewRecentStrategy: PropTypes.func,
- selected_strategy_id: PropTypes.string,
-};
-
-export default connect(({ load_modal }) => ({
- getRecentFileIcon: load_modal.getRecentFileIcon,
- getSaveType: load_modal.getSaveType,
- previewRecentStrategy: load_modal.previewRecentStrategy,
- selected_strategy_id: load_modal.selected_strategy_id,
-}))(RecentWorkspace);
diff --git a/packages/bot-web-ui/src/components/load-modal/recent-workspace.tsx b/packages/bot-web-ui/src/components/load-modal/recent-workspace.tsx
new file mode 100644
index 000000000000..b118c28464b8
--- /dev/null
+++ b/packages/bot-web-ui/src/components/load-modal/recent-workspace.tsx
@@ -0,0 +1,54 @@
+import React from 'react';
+import classnames from 'classnames';
+import { timeSince } from '@deriv/bot-skeleton';
+import { save_types } from '@deriv/bot-skeleton/src/constants/save-type';
+import { Icon } from '@deriv/components';
+import { connect } from 'Stores/connect';
+import RootStore from 'Stores/root-store';
+
+type TRecentWorkspaceProps = {
+ getRecentFileIcon: (workspace_type: string) => string;
+ getSaveType: (workspace_type: string) => string;
+ previewRecentStrategy: (workspaceId: string) => void;
+ selected_strategy_id: string;
+ workspace: { [key: string]: any };
+};
+
+const RecentWorkspace = ({
+ getRecentFileIcon,
+ getSaveType,
+ previewRecentStrategy,
+ selected_strategy_id,
+ workspace,
+}: TRecentWorkspaceProps) => {
+ return (
+ previewRecentStrategy(workspace.id)}
+ >
+
+
{workspace.name}
+
{timeSince(workspace.timestamp)}
+
+
+
+
{getSaveType(workspace.save_type)}
+
+
+ );
+};
+
+export default connect(({ load_modal }: RootStore) => ({
+ getRecentFileIcon: load_modal.getRecentFileIcon,
+ getSaveType: load_modal.getSaveType,
+ previewRecentStrategy: load_modal.previewRecentStrategy,
+ selected_strategy_id: load_modal.selected_strategy_id,
+}))(RecentWorkspace);
diff --git a/packages/bot-web-ui/src/components/load-modal/recent.jsx b/packages/bot-web-ui/src/components/load-modal/recent.jsx
deleted file mode 100644
index 9e1c55ceffdd..000000000000
--- a/packages/bot-web-ui/src/components/load-modal/recent.jsx
+++ /dev/null
@@ -1,94 +0,0 @@
-import classnames from 'classnames';
-import React from 'react';
-import { PropTypes } from 'prop-types';
-import { Icon } from '@deriv/components';
-import { Localize } from '@deriv/translations';
-import { connect } from 'Stores/connect';
-import RecentFooter from './recent-footer.jsx';
-import RecentWorkspace from './recent-workspace.jsx';
-import WorkspaceControl from './workspace-control.jsx';
-
-const RecentComponent = ({ is_explanation_expand, recent_strategies, toggleExplanationExpand }) => {
- if (recent_strategies.length) {
- return (
-
-
-
-
-
-
-
- {recent_strategies.map(workspace => (
-
- ))}
-
-
-
-
-
- );
- }
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-};
-
-RecentComponent.propTypes = {
- is_explanation_expand: PropTypes.bool,
- recent_strategies: PropTypes.array,
- toggleExplanationExpand: PropTypes.bool,
-};
-
-const Recent = connect(({ load_modal }) => ({
- is_explanation_expand: load_modal.is_explanation_expand,
- recent_strategies: load_modal.recent_strategies,
- toggleExplanationExpand: load_modal.toggleExplanationExpand,
-}))(RecentComponent);
-
-Recent.Footer = RecentFooter;
-Recent.Workspace = RecentWorkspace;
-
-export default Recent;
diff --git a/packages/bot-web-ui/src/components/load-modal/recent.tsx b/packages/bot-web-ui/src/components/load-modal/recent.tsx
new file mode 100644
index 000000000000..a29dc547512c
--- /dev/null
+++ b/packages/bot-web-ui/src/components/load-modal/recent.tsx
@@ -0,0 +1,94 @@
+import React from 'react';
+import classnames from 'classnames';
+import { Icon } from '@deriv/components';
+import { Localize } from '@deriv/translations';
+import { connect } from 'Stores/connect';
+import RootStore from 'Stores/root-store';
+import RecentWorkspace from './recent-workspace';
+import WorkspaceControl from './workspace-control';
+
+type TRecentComponentProps = {
+ is_explanation_expand: boolean;
+ recent_strategies: any[];
+ toggleExplanationExpand: boolean;
+};
+
+const RecentComponent = ({
+ is_explanation_expand,
+ recent_strategies,
+ toggleExplanationExpand,
+}: TRecentComponentProps) => {
+ if (recent_strategies.length) {
+ return (
+
+
+
+
+
+
+
+ {recent_strategies.map(workspace => (
+
+ ))}
+
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+const Recent = connect(({ load_modal }: RootStore) => ({
+ is_explanation_expand: load_modal.is_explanation_expand,
+ recent_strategies: load_modal.recent_strategies,
+ toggleExplanationExpand: load_modal.toggleExplanationExpand,
+}))(RecentComponent);
+
+export default Recent;
diff --git a/packages/bot-web-ui/src/components/load-modal/workspace-control.jsx b/packages/bot-web-ui/src/components/load-modal/workspace-control.jsx
deleted file mode 100644
index 48eb0db146b3..000000000000
--- a/packages/bot-web-ui/src/components/load-modal/workspace-control.jsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import React from 'react';
-import { PropTypes } from 'prop-types';
-import { Icon } from '@deriv/components';
-import { connect } from 'Stores/connect';
-
-const WorkspaceControl = ({ onZoomInOutClick }) => (
-
- onZoomInOutClick(true)}
- />
- onZoomInOutClick(false)}
- />
-
-);
-
-WorkspaceControl.propTypes = {
- onZoomInOutClick: PropTypes.func,
-};
-
-export default connect(({ load_modal }) => ({
- onZoomInOutClick: load_modal.onZoomInOutClick,
-}))(WorkspaceControl);
diff --git a/packages/bot-web-ui/src/components/load-modal/workspace-control.tsx b/packages/bot-web-ui/src/components/load-modal/workspace-control.tsx
new file mode 100644
index 000000000000..c7c66f699541
--- /dev/null
+++ b/packages/bot-web-ui/src/components/load-modal/workspace-control.tsx
@@ -0,0 +1,27 @@
+import React from 'react';
+import { Icon } from '@deriv/components';
+import { connect } from 'Stores/connect';
+import RootStore from 'Stores/root-store';
+
+type TWorkspaceControlProps = {
+ onZoomInOutClick: (zoom_in: boolean) => void;
+};
+
+const WorkspaceControl = ({ onZoomInOutClick }: TWorkspaceControlProps) => (
+
+ onZoomInOutClick(true)}
+ />
+ onZoomInOutClick(false)}
+ />
+
+);
+
+export default connect(({ dashboard }: RootStore) => ({
+ onZoomInOutClick: dashboard.onZoomInOutClick,
+}))(WorkspaceControl);
diff --git a/packages/bot-web-ui/src/components/main-content/index.js b/packages/bot-web-ui/src/components/main-content/index.js
deleted file mode 100644
index 3c51bd680917..000000000000
--- a/packages/bot-web-ui/src/components/main-content/index.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import MainContent from './main-content.jsx';
-import './toolbox.scss';
-import './workspace.scss';
-
-export default MainContent;
diff --git a/packages/bot-web-ui/src/components/main-content/main-content.jsx b/packages/bot-web-ui/src/components/main-content/main-content.jsx
deleted file mode 100644
index 71955f971ff3..000000000000
--- a/packages/bot-web-ui/src/components/main-content/main-content.jsx
+++ /dev/null
@@ -1,72 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import Toolbox from 'Components/toolbox';
-import Flyout from 'Components/flyout';
-import Chart from 'Components/chart';
-import { tabs_title } from 'Constants/bot-contents';
-import { connect } from 'Stores/connect';
-
-const MainContentContainer = ({ active_tab, is_loading }) => {
- if (is_loading) return null;
-
- return (
-
- {Blockly.derivWorkspace && (
-
- {active_tab === tabs_title.WORKSPACE && (
-
-
-
-
- )}
- {active_tab === tabs_title.CHART && (
-
- )}
-
- )}
-
- );
-};
-
-const MainContent = ({ active_tab, is_loading, onMount, onUnmount }) => {
- React.useEffect(() => {
- onMount();
- return () => onUnmount();
- }, [onMount, onUnmount]);
-
- return (
-
-
-
- );
-};
-
-MainContent.propTypes = {
- active_tab: PropTypes.string,
- is_loading: PropTypes.bool,
- onMount: PropTypes.func,
- onUnmount: PropTypes.func,
-};
-
-export default connect(({ blockly_store, main_content }) => ({
- active_tab: main_content.active_tab,
- is_loading: blockly_store.is_loading,
- onMount: main_content.onMount,
- onUnmount: main_content.onUnmount,
-}))(MainContent);
diff --git a/packages/bot-web-ui/src/components/network-toast-popup/network-toast-popup.jsx b/packages/bot-web-ui/src/components/network-toast-popup/network-toast-popup.jsx
index be94bf2291f5..cafc369966b3 100644
--- a/packages/bot-web-ui/src/components/network-toast-popup/network-toast-popup.jsx
+++ b/packages/bot-web-ui/src/components/network-toast-popup/network-toast-popup.jsx
@@ -1,6 +1,6 @@
+import React from 'react';
import classNames from 'classnames';
import PropTypes from 'prop-types';
-import React from 'react';
import ReactDOM from 'react-dom';
import { MobileWrapper, Toast } from '@deriv/components';
import { connect } from 'Stores/connect';
diff --git a/packages/bot-web-ui/src/components/notify-item/index.js b/packages/bot-web-ui/src/components/notify-item/index.js
index 10e7aa736b8b..d5199f07b61d 100644
--- a/packages/bot-web-ui/src/components/notify-item/index.js
+++ b/packages/bot-web-ui/src/components/notify-item/index.js
@@ -1,4 +1,4 @@
-import { messageWithButton, messageWithImage, arrayAsMessage } from './notify-item.jsx';
+import { arrayAsMessage, messageWithButton, messageWithImage } from './notify-item.jsx';
import './notify-item.scss';
-export { messageWithButton, messageWithImage, arrayAsMessage };
+export { arrayAsMessage, messageWithButton, messageWithImage };
diff --git a/packages/bot-web-ui/src/components/notify-item/notify-item.jsx b/packages/bot-web-ui/src/components/notify-item/notify-item.jsx
index 813149da67c0..ef37861da106 100644
--- a/packages/bot-web-ui/src/components/notify-item/notify-item.jsx
+++ b/packages/bot-web-ui/src/components/notify-item/notify-item.jsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { Button, Icon, ExpansionPanel } from '@deriv/components';
+import { Button, ExpansionPanel, Icon } from '@deriv/components';
const getIcon = type => {
switch (type) {
diff --git a/packages/bot-web-ui/src/components/quick-strategy/index.js b/packages/bot-web-ui/src/components/quick-strategy/index.js
deleted file mode 100644
index f2087204cca1..000000000000
--- a/packages/bot-web-ui/src/components/quick-strategy/index.js
+++ /dev/null
@@ -1,4 +0,0 @@
-import QuickStrategy from './quick-strategy.jsx';
-import './quick-strategy.scss';
-
-export default QuickStrategy;
diff --git a/packages/bot-web-ui/src/components/quick-strategy/quick-strategy.jsx b/packages/bot-web-ui/src/components/quick-strategy/quick-strategy.jsx
deleted file mode 100644
index 6e421b1f24a7..000000000000
--- a/packages/bot-web-ui/src/components/quick-strategy/quick-strategy.jsx
+++ /dev/null
@@ -1,651 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import {
- Autocomplete,
- SelectNative,
- Button,
- Icon,
- Input,
- Modal,
- Popover,
- Tabs,
- IconTradeTypes,
- ThemedScrollbars,
- MobileFullPageModal,
- Text,
-} from '@deriv/components';
-import classNames from 'classnames';
-
-import { localize } from '@deriv/translations';
-import { isSafari } from '@deriv/shared';
-import { Formik, Form, Field } from 'formik';
-import { config } from '@deriv/bot-skeleton';
-import { popover_zindex } from 'Constants/z-indexes';
-import { connect } from 'Stores/connect';
-
-const InputSize = ({
- onChangeInputValue,
- handleChange,
- active_index,
- getSizeDesc,
- getSizeText,
- initial_errors,
- errors,
- setCurrentFocus,
- touched,
- is_mobile,
-}) => {
- const field_name = Object.freeze({
- 0: 'quick-strategy__size',
- 1: 'alembert-unit',
- 2: 'oscar-unit',
- });
- const input_name = Object.freeze({
- 0: 'input_size',
- 1: 'input_alembert_unit',
- 2: 'input_oscar_unit',
- });
-
- return (
-
- {({ field }) => (
- {
- handleChange(e);
- onChangeInputValue(input_name[active_index], e);
- }}
- onFocus={e => setCurrentFocus(e.currentTarget.name)}
- onBlur={() => setCurrentFocus(null)}
- placeholder='2'
- trailing_icon={
-
-
-
- }
- />
- )}
-
- );
-};
-const QuickStrategyForm = ({
- active_index,
- createStrategy,
- duration_unit_dropdown,
- getSizeDesc,
- getSizeText,
- initial_errors,
- initial_values,
- is_onscreen_keyboard_active,
- is_stop_button_visible,
- onChangeDropdownItem,
- onChangeInputValue,
- onHideDropdownList,
- onScrollStopDropdownList,
- symbol_dropdown,
- trade_type_dropdown,
- validateQuickStrategy,
- is_mobile,
- selected_symbol,
- selected_trade_type,
- selected_duration_unit,
- description,
- setCurrentFocus,
-}) => (
-
- {({ errors, handleChange, values, isSubmitting, setFieldValue, touched, submitForm }) => {
- // Check values in favour of isValid, this is a hack to persist validation through tab switching.
-
- const validation_errors = validateQuickStrategy(values);
- const is_valid = Object.keys(validation_errors).length === 0;
-
- const is_submit_enabled = !isSubmitting && is_valid;
-
- return (
-
- );
- }}
-
-);
-
-const MarketOption = ({ symbol }) => (
-
-
-
- {symbol.text}
-
-
-);
-
-const TradeTypeOption = ({ trade_type }) => (
-
-
-
-
- {trade_type.text}
-
-
-);
-const ContentRenderer = props => {
- const { strategies } = config;
- const {
- setActiveTabIndex,
- symbol_dropdown,
- trade_type_dropdown,
- active_index,
- createStrategy,
- duration_unit_dropdown,
- getSizeDesc,
- getSizeText,
- initial_errors,
- initial_values,
- is_onscreen_keyboard_active,
- is_mobile,
- is_stop_button_visible,
- onChangeDropdownItem,
- onChangeInputValue,
- onHideDropdownList,
- onScrollStopDropdownList,
- validateQuickStrategy,
- selected_symbol,
- selected_trade_type,
- setCurrentFocus,
- selected_duration_unit,
- } = props;
- const symbol_dropdown_options = symbol_dropdown
- .map(symbol => ({
- component: ,
- ...symbol,
- }))
- .filter(option => option.group !== 'Cryptocurrencies'); // Until Crypto enabled for Dbot
- const trade_type_dropdown_options = trade_type_dropdown.map(trade_type => ({
- component: ,
- ...trade_type,
- }));
-
- return (
-
- {Object.keys(strategies).map(key => {
- const { index, label, description } = strategies[key];
- return (
-
-
-
- );
- })}
-
- );
-};
-
-const QuickStrategy = props => {
- const { is_strategy_modal_open, is_mobile, toggleStrategyModal } = props;
-
- return (
- <>
- {is_mobile ? (
-
-
-
- ) : (
-
-
-
-
-
- )}
- >
- );
-};
-
-QuickStrategy.propTypes = {
- active_index: PropTypes.number,
- createStrategy: PropTypes.func,
- duration_unit_dropdown: PropTypes.array,
- getSizeDesc: PropTypes.func,
- getSizeText: PropTypes.func,
- initial_errors: PropTypes.object,
- initial_values: PropTypes.object,
- is_mobile: PropTypes.bool,
- is_stop_button_visible: PropTypes.bool,
- is_strategy_modal_open: PropTypes.bool,
- onChangeDropdownItem: PropTypes.func,
- onChangeInputValue: PropTypes.func,
- onChangeSymbolInput: PropTypes.func,
- onHideDropdownList: PropTypes.func,
- onScrollStopDropdownList: PropTypes.func,
- setActiveTabIndex: PropTypes.func,
- setCurrentFocus: PropTypes.func,
- selected_symbol: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
- selected_trade_type: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
- selected_duration_unit: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
- symbol_dropdown: PropTypes.array,
- toggleStrategyModal: PropTypes.func,
- trade_type_dropdown: PropTypes.array,
- validateQuickStrategy: PropTypes.func,
-};
-
-export default connect(({ run_panel, quick_strategy, ui }) => ({
- active_index: quick_strategy.active_index,
- createStrategy: quick_strategy.createStrategy,
- duration_unit_dropdown: quick_strategy.duration_unit_dropdown,
- getSizeDesc: quick_strategy.getSizeDesc,
- getSizeText: quick_strategy.getSizeText,
- initial_errors: quick_strategy.initial_errors,
- initial_values: quick_strategy.initial_values,
- is_onscreen_keyboard_active: ui.is_onscreen_keyboard_active,
- is_mobile: ui.is_mobile,
- is_stop_button_visible: run_panel.is_stop_button_visible,
- is_strategy_modal_open: quick_strategy.is_strategy_modal_open,
- onChangeDropdownItem: quick_strategy.onChangeDropdownItem,
- onChangeInputValue: quick_strategy.onChangeInputValue,
- onChangeSymbolInput: quick_strategy.onChangeSymbolInput,
- onHideDropdownList: quick_strategy.onHideDropdownList,
- onScrollStopDropdownList: quick_strategy.onScrollStopDropdownList,
- setActiveTabIndex: quick_strategy.setActiveTabIndex,
- selected_symbol: quick_strategy.selected_symbol,
- selected_trade_type: quick_strategy.selected_trade_type,
- selected_duration_unit: quick_strategy.selected_duration_unit,
- symbol_dropdown: quick_strategy.symbol_dropdown,
- toggleStrategyModal: quick_strategy.toggleStrategyModal,
- trade_type_dropdown: quick_strategy.trade_type_dropdown,
- validateQuickStrategy: quick_strategy.validateQuickStrategy,
- setCurrentFocus: ui.setCurrentFocus,
-}))(QuickStrategy);
diff --git a/packages/bot-web-ui/src/components/quick-strategy/quick-strategy.scss b/packages/bot-web-ui/src/components/quick-strategy/quick-strategy.scss
deleted file mode 100644
index 342350b8e432..000000000000
--- a/packages/bot-web-ui/src/components/quick-strategy/quick-strategy.scss
+++ /dev/null
@@ -1,246 +0,0 @@
-.dc-modal {
- $strategy-modal: 'modal--strategy';
-
- &__container {
- &_#{$strategy-modal} {
- max-height: 57.1rem !important;
-
- .modal__scrollbar {
- max-height: 516px;
- margin-bottom: 75px;
- }
- .modal__content {
- padding: 1.6rem 2.4rem;
- height: calc(100% - 200px);
- max-height: 636px;
- }
- }
- }
-}
-
-.dc-mobile-full-page-modal.quick-strategy__wrapper {
- height: 100%;
- overflow-y: visible;
-}
-
-.quick-strategy {
- $quick-strategy: &;
-
- &__description {
- margin: 15px 0;
- font-size: 14px;
- font-weight: normal;
- line-height: 1.43;
- color: var(--text-general);
- }
- &__tab-content {
- height: calc(100vh - 330px);
- overflow: auto;
- }
- &__form {
- @include mobile {
- height: calc(100% - 8rem);
- overflow: scroll;
- &--active-keyboard {
- height: 100%;
- }
- }
-
- &-row {
- align-items: flex-end;
- display: flex;
- justify-content: space-between;
- margin: 4.5rem 0;
-
- > * + * {
- @include mobile {
- margin-top: 4.5rem;
- }
- }
-
- @include mobile {
- flex-direction: column;
- }
-
- &:first-child {
- margin-top: 0;
- }
- > * {
- justify-content: center;
- }
- #{$quick-strategy}__input {
- &-field {
- &:focus,
- &:disabled,
- &:not(:focus):not([value='']) {
- outline: none;
-
- & ~ label {
- transform: translate(0, -1.8rem) scale(0.875);
- }
- }
- }
- &-label {
- font-size: var(--text-size-xxs);
- }
- .dc-field-error {
- left: 0;
- padding-top: 0.4rem;
- padding-left: 0.4rem;
- }
- .dc-input__trailing-icon {
- margin: 1.1rem;
- position: unset;
- height: 1.6rem;
- }
- }
- &--multiple {
- > *:first-child {
- margin-right: 1.2rem;
- }
- > * + * {
- margin-left: 1.2rem;
- }
- }
- }
- &-content {
- margin-bottom: 12rem;
- padding: 0 1.6rem;
-
- &--active-keyboard {
- margin-bottom: 12rem;
- }
-
- &--safari-fix {
- margin-bottom: 0;
- padding-bottom: 10rem;
- }
- }
- &-footer {
- border-top: 1px solid var(--general-section-1);
- padding: 1.6rem 2.4rem;
- position: fixed;
- bottom: 0px;
- left: 0px;
- background: var(--general-main-2);
- width: 100%;
-
- &--active-keyboard {
- position: unset;
- display: block;
- }
- }
- .dc-autocomplete {
- margin-bottom: unset;
- }
- .dc-input {
- margin-bottom: 0;
- }
- .dc-btn__group {
- display: flex;
- justify-content: flex-end;
- flex-direction: row;
- }
- }
- &__option {
- display: flex;
-
- > * {
- align-self: center;
- }
- .icons-underlying {
- margin-top: 5px;
- }
- }
- &__symbol {
- margin-left: 8px;
- }
- &__icon {
- width: 24px;
- height: 24px;
- padding: 3px;
- margin: 2px;
- background-color: var(--general-section-1);
- }
- &__duration {
- &-dropdown {
- width: 100%;
- margin-left: 0;
- margin-right: 1.2rem;
- }
- }
- &__leading {
- .dc-input__field {
- padding-left: 6rem;
- }
- .dc-input__leading-icon {
- .dc-icon {
- margin-left: 0.2rem;
- margin-right: 0.3rem;
- }
- }
- }
- &__wrapper {
- position: absolute;
- top: 40px;
- height: 100%;
-
- @include mobile {
- & .dc-dropdown-list {
- &__group {
- &-header {
- padding: 0.6rem 2rem !important;
- color: var(--brand-dark-grey) !important;
- justify-content: start !important;
- }
- }
- }
- & .quick-strategy__tabs {
- padding: 0 0.5rem;
- }
- & .dc {
- &-tabs {
- display: flex;
- flex-direction: column;
- height: 100%;
-
- &__list {
- width: 100%;
- height: 4rem;
- }
- &__item {
- width: 50%;
- }
- &__content {
- height: calc(100% - 4rem);
- }
- }
- }
- & .dc-page-overlay__header {
- position: fixed;
- top: 4rem;
- width: 100%;
- height: 4rem;
- }
- & .dc-page-overlay__content {
- z-index: 5;
- }
- .dc-themed-scrollbars__autohide--is-hovered {
- &::-webkit-scrollbar-thumb {
- display: unset;
- }
- }
- .dc-themed-scrollbars {
- height: 100%;
- }
- }
- }
-}
-
-/* stylelint-disable */
-@media (max-resolution: 200dpi) {
- .quick-strategy__tab-content {
- height: 100%;
- }
-}
-/* stylelint-enable */
diff --git a/packages/bot-web-ui/src/components/route-prompt-dialog/index.js b/packages/bot-web-ui/src/components/route-prompt-dialog/index.js
deleted file mode 100644
index 4606e36fd51c..000000000000
--- a/packages/bot-web-ui/src/components/route-prompt-dialog/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import RoutePromptDialog from './route-prompt-dialog.jsx';
-
-export default RoutePromptDialog;
diff --git a/packages/bot-web-ui/src/components/route-prompt-dialog/index.ts b/packages/bot-web-ui/src/components/route-prompt-dialog/index.ts
new file mode 100644
index 000000000000..a506ef60fbd6
--- /dev/null
+++ b/packages/bot-web-ui/src/components/route-prompt-dialog/index.ts
@@ -0,0 +1,3 @@
+import RoutePromptDialog from './route-prompt-dialog';
+
+export default RoutePromptDialog;
diff --git a/packages/bot-web-ui/src/components/route-prompt-dialog/route-prompt-dialog.jsx b/packages/bot-web-ui/src/components/route-prompt-dialog/route-prompt-dialog.jsx
deleted file mode 100644
index b748bc5c6aaf..000000000000
--- a/packages/bot-web-ui/src/components/route-prompt-dialog/route-prompt-dialog.jsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import React from 'react';
-import { localize, Localize } from '@deriv/translations';
-import { Dialog } from '@deriv/components';
-import { connect } from 'Stores/connect';
-
-const RoutePromptDialog = ({ continueRoute, is_confirmed, last_location, should_show, onCancel, onConfirm }) => {
- React.useEffect(continueRoute, [is_confirmed, last_location, continueRoute]);
-
- return (
-
-
-
- );
-};
-
-export default connect(({ route_prompt_dialog }) => ({
- continueRoute: route_prompt_dialog.continueRoute,
- should_show: route_prompt_dialog.should_show,
- is_confirmed: route_prompt_dialog.is_confirmed,
- last_location: route_prompt_dialog.last_location,
- onCancel: route_prompt_dialog.onCancel,
- onConfirm: route_prompt_dialog.onConfirm,
-}))(RoutePromptDialog);
diff --git a/packages/bot-web-ui/src/components/route-prompt-dialog/route-prompt-dialog.tsx b/packages/bot-web-ui/src/components/route-prompt-dialog/route-prompt-dialog.tsx
new file mode 100644
index 000000000000..e134b87426c2
--- /dev/null
+++ b/packages/bot-web-ui/src/components/route-prompt-dialog/route-prompt-dialog.tsx
@@ -0,0 +1,47 @@
+import React from 'react';
+import { Dialog } from '@deriv/components';
+import { Localize, localize } from '@deriv/translations';
+import { connect } from 'Stores/connect';
+import RootStore from 'Stores/index';
+
+type TRoutePromptDialog = {
+ onCancel: () => void;
+ onConfirm: () => void;
+ continueRoute: () => void;
+ is_confirmed: boolean;
+ should_show: boolean;
+ last_location: string[];
+};
+
+const RoutePromptDialog = ({
+ continueRoute,
+ is_confirmed,
+ last_location,
+ should_show,
+ onCancel,
+ onConfirm,
+}: TRoutePromptDialog) => {
+ React.useEffect(continueRoute, [is_confirmed, last_location, continueRoute]);
+
+ return (
+
+
+
+ );
+};
+
+export default connect(({ route_prompt_dialog }: RootStore) => ({
+ continueRoute: route_prompt_dialog.continueRoute,
+ should_show: route_prompt_dialog.should_show,
+ is_confirmed: route_prompt_dialog.is_confirmed,
+ last_location: route_prompt_dialog.last_location,
+ onCancel: route_prompt_dialog.onCancel,
+ onConfirm: route_prompt_dialog.onConfirm,
+}))(RoutePromptDialog);
diff --git a/packages/bot-web-ui/src/components/run-panel/run-panel.jsx b/packages/bot-web-ui/src/components/run-panel/run-panel.jsx
index a5d100c6cfcf..2b04c7445eb4 100644
--- a/packages/bot-web-ui/src/components/run-panel/run-panel.jsx
+++ b/packages/bot-web-ui/src/components/run-panel/run-panel.jsx
@@ -1,13 +1,14 @@
-import { Button, Dialog, Drawer, Modal, Money, Tabs, ThemedScrollbars, Text } from '@deriv/components';
-import PropTypes from 'prop-types';
-import classNames from 'classnames';
import React from 'react';
-import { localize, Localize } from '@deriv/translations';
+import classNames from 'classnames';
+import PropTypes from 'prop-types';
+import { Button, Drawer, Modal, Money, Tabs, Text, ThemedScrollbars } from '@deriv/components';
+import { isMobile } from '@deriv/shared';
+import { Localize, localize } from '@deriv/translations';
import Journal from 'Components/journal';
+import SelfExclusion from 'Components/self-exclusion';
import Summary from 'Components/summary';
-import Transactions from 'Components/transactions';
import TradeAnimation from 'Components/trade-animation';
-import SelfExclusion from 'Components/self-exclusion';
+import Transactions from 'Components/transactions';
import { popover_zindex } from 'Constants/z-indexes';
import { connect } from 'Stores/connect';
@@ -25,12 +26,14 @@ const StatisticsSummary = ({
number_of_runs,
total_stake,
total_payout,
+ has_started_onboarding_tour,
toggleStatisticsInfoModal,
total_profit,
won_contracts,
}) => (
@@ -79,7 +82,7 @@ const DrawerHeader = ({ is_clear_stat_disabled, is_mobile, is_drawer_open, onCle
/>
);
-const DrawerContent = ({ active_index, is_drawer_open, setActiveTabIndex, ...props }) => {
+const DrawerContent = ({ active_index, is_drawer_open, has_started_onboarding_tour, setActiveTabIndex, ...props }) => {
return (
<>
@@ -93,7 +96,9 @@ const DrawerContent = ({ active_index, is_drawer_open, setActiveTabIndex, ...pro
- {is_drawer_open && active_index !== 2 && }
+ {is_drawer_open && active_index !== 2 && (
+
+ )}
>
);
};
@@ -180,19 +185,14 @@ const StatisticsInfoModal = ({ is_mobile, is_statistics_info_modal_open, toggleS
const RunPanel = ({
active_index,
currency,
- dialog_options,
+ has_started_onboarding_tour,
is_clear_stat_disabled,
- is_dialog_open,
is_drawer_open,
- is_mobile,
is_statistics_info_modal_open,
lost_contracts,
number_of_runs,
- onCancelButtonClick,
onClearStatClick,
- onCloseDialog,
onMount,
- onOkButtonClick,
onRunButtonClick,
onUnmount,
setActiveTabIndex,
@@ -203,11 +203,19 @@ const RunPanel = ({
total_stake,
won_contracts,
}) => {
+ const is_mobile = isMobile();
+
React.useEffect(() => {
onMount();
return () => onUnmount();
}, [onMount, onUnmount]);
+ React.useEffect(() => {
+ if (is_mobile) {
+ toggleDrawer(false);
+ }
+ }, []);
+
const content = (
);
@@ -238,10 +247,13 @@ const RunPanel = ({
return (
<>
-
+
{is_mobile && }
-
- {dialog_options.message}
-
({
+export default connect(({ run_panel, core, dashboard }) => ({
active_index: run_panel.active_index,
currency: core.client.currency,
- dialog_options: run_panel.dialog_options,
+ has_started_onboarding_tour: dashboard.has_started_onboarding_tour,
is_clear_stat_disabled: run_panel.is_clear_stat_disabled,
- is_dialog_open: run_panel.is_dialog_open,
is_drawer_open: run_panel.is_drawer_open,
- is_mobile: ui.is_mobile,
is_statistics_info_modal_open: run_panel.is_statistics_info_modal_open,
lost_contracts: run_panel.statistics.lost_contracts,
number_of_runs: run_panel.statistics.number_of_runs,
- onCancelButtonClick: run_panel.onCancelButtonClick,
onClearStatClick: run_panel.onClearStatClick,
- onCloseDialog: run_panel.onCloseDialog,
onMount: run_panel.onMount,
- onOkButtonClick: run_panel.onOkButtonClick,
onRunButtonClick: run_panel.onRunButtonClick,
onUnmount: run_panel.onUnmount,
setActiveTabIndex: run_panel.setActiveTabIndex,
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 16f228922462..c52f75b5c477 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
@@ -3,6 +3,7 @@
**/
// TODO: [fix-dc-bundle] Fix import issue with Deriv Component stylesheets (app should take precedence, and not repeat)
.run-panel {
+ height: 0;
&__container {
height: var(--bot-content-height) !important;
top: 10.4rem !important;
@@ -15,6 +16,7 @@
top: 0;
left: 0;
width: 100vw;
+ z-index: 99;
&-closed {
position: unset;
@@ -43,11 +45,16 @@
display: flex;
flex-direction: column;
align-items: flex-end;
- width: 350px;
+ width: 35rem;
background-color: var(--general-section-2);
- position: fixed;
- bottom: 5.7rem;
+ @include mobile {
+ margin: 0;
+ position: fixed;
+ }
+ &--tour-active {
+ display: none;
+ }
&--info {
display: flex;
justify-content: center;
@@ -98,6 +105,9 @@
}
}
&__content {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
overflow: hidden !important;
}
&__buttons {
@@ -109,16 +119,22 @@
}
&-tab {
&__content {
- height: var(--drawer-content-height);
+ height: calc(100vh - 42rem);
+ overflow: hidden;
&--no-stat {
height: var(--drawer-content-height-no-stat);
}
&--mobile {
+ display: flex;
height: var(--drawer-content-height-mobile);
position: fixed;
bottom: 15.7rem;
width: 100vw;
+ padding: 0.4rem 0;
+ }
+ &--summary-tab {
+ padding: 0.8rem 1.6rem;
}
}
}
@@ -144,7 +160,7 @@
width: 100%;
background-color: var(--general-main-1);
border-top: solid 2px var(--general-section-1);
- z-index: 5;
+ z-index: 8;
}
&__buttons {
padding: 0.8rem 2.4rem;
diff --git a/packages/bot-web-ui/src/components/save-modal/save-modal.jsx b/packages/bot-web-ui/src/components/save-modal/save-modal.jsx
index 68ccf4790026..c6f0a31ebb58 100644
--- a/packages/bot-web-ui/src/components/save-modal/save-modal.jsx
+++ b/packages/bot-web-ui/src/components/save-modal/save-modal.jsx
@@ -1,20 +1,11 @@
+import React from 'react';
import classNames from 'classnames';
+import { Field, Form, Formik } from 'formik';
import PropTypes from 'prop-types';
-import React from 'react';
-import {
- Button,
- Checkbox,
- Icon,
- Modal,
- RadioGroup,
- Input,
- MobileFullPageModal,
- ThemedScrollbars,
- Text,
-} from '@deriv/components';
-import { Formik, Form, Field } from 'formik';
-import { Localize, localize } from '@deriv/translations';
import { config, save_types } from '@deriv/bot-skeleton';
+import { Button, Icon, Input, MobileFullPageModal, Modal, RadioGroup, Text, ThemedScrollbars } from '@deriv/components';
+import { isMobile } from '@deriv/shared';
+import { localize } from '@deriv/translations';
import { connect } from 'Stores/connect';
const SaveModalForm = ({
@@ -38,12 +29,18 @@ const SaveModalForm = ({
validate={validateBotName}
onSubmit={onConfirmSave}
>
- {({ values: { is_local, save_as_collection }, setFieldValue, touched, errors }) => {
+ {({ values: { is_local }, setFieldValue, touched, errors }) => {
const content_height = !is_mobile ? '500px' : `calc(100%)`;
return (
@@ -160,15 +135,15 @@ const SaveModal = ({
onDriveConnect,
toggleSaveModal,
validateBotName,
- is_mobile,
setCurrentFocus,
is_onscreen_keyboard_active,
-}) =>
- is_mobile ? (
+}) => {
+ const is_mobile = isMobile();
+ return is_mobile ? (
) : (
);
+};
const IconRadio = ({ icon, text, google_drive_connected, onDriveConnect }) => {
const is_drive_radio = text === 'Google Drive';
@@ -256,7 +232,6 @@ const IconRadio = ({ icon, text, google_drive_connected, onDriveConnect }) => {
SaveModal.propTypes = {
button_status: PropTypes.number,
is_authorised: PropTypes.bool,
- is_mobile: PropTypes.bool,
is_save_modal_open: PropTypes.bool,
onConfirmSave: PropTypes.func,
onDriveConnect: PropTypes.func,
@@ -268,7 +243,6 @@ SaveModal.propTypes = {
export default connect(({ save_modal, google_drive, ui }) => ({
button_status: save_modal.button_status,
is_authorised: google_drive.is_authorised,
- is_mobile: ui.is_mobile,
is_save_modal_open: save_modal.is_save_modal_open,
is_onscreen_keyboard_active: ui.is_onscreen_keyboard_active,
onConfirmSave: save_modal.onConfirmSave,
diff --git a/packages/bot-web-ui/src/components/save-modal/save-modal.scss b/packages/bot-web-ui/src/components/save-modal/save-modal.scss
index e936d96001db..28b050149b7e 100644
--- a/packages/bot-web-ui/src/components/save-modal/save-modal.scss
+++ b/packages/bot-web-ui/src/components/save-modal/save-modal.scss
@@ -76,27 +76,12 @@ div.radio-group {
}
.save-type {
- &__checkbox-text {
- font-size: 14px;
- font-weight: bold;
- line-height: 1.43;
- letter-spacing: normal;
- color: var(--text-prominent);
- }
- &__checkbox-description {
- font-size: 14px;
- font-weight: normal;
- line-height: 1.43;
- letter-spacing: normal;
- color: var(--text-prominent);
- margin: 8px 0 0 32px;
- }
&__container {
text-align: center;
}
&__input {
// TODO: [fix-dc-bundle] Fix import issue with Deriv Component stylesheets (app should take precedence, and not repeat)
- margin: 12px 0 0 !important;
+ margin: 2rem 0 0 !important;
}
&__radio {
text-align: center;
@@ -130,6 +115,7 @@ div.radio-group {
}
& .dc-input {
width: 100% !important;
+ margin: 3rem 0 0 !important;
}
& .dc-radio-group__item {
width: calc(50vw - 24px) !important;
@@ -139,7 +125,7 @@ div.radio-group {
position: relative;
}
& .modal__content {
- padding: 0px 1.6rem;
+ padding: 3rem 1.6rem;
height: calc(100% - #{$MOBILE_WRAPPER_FOOTER_HEIGHT});
}
& .modal__footer {
diff --git a/packages/bot-web-ui/src/components/self-exclusion/index.js b/packages/bot-web-ui/src/components/self-exclusion/index.js
index a9f2272d794c..7683ab59e2b4 100644
--- a/packages/bot-web-ui/src/components/self-exclusion/index.js
+++ b/packages/bot-web-ui/src/components/self-exclusion/index.js
@@ -1,3 +1,3 @@
-import SelfExclusion from './self-exclusion.jsx';
+import SelfExclusion from './self-exclusion';
export default SelfExclusion;
diff --git a/packages/bot-web-ui/src/components/self-exclusion/self-exclusion.jsx b/packages/bot-web-ui/src/components/self-exclusion/self-exclusion.jsx
index 9d37cb0f064c..a0f09ef11acf 100644
--- a/packages/bot-web-ui/src/components/self-exclusion/self-exclusion.jsx
+++ b/packages/bot-web-ui/src/components/self-exclusion/self-exclusion.jsx
@@ -1,15 +1,15 @@
import React from 'react';
+import classNames from 'classnames';
+import { Field, Form, Formik } from 'formik';
import PropTypes from 'prop-types';
-import { Input, Button, Modal, MobileWrapper, Div100vhContainer, FadeWrapper, PageOverlay } from '@deriv/components';
+import { Button, Div100vhContainer, FadeWrapper, Input, MobileWrapper, Modal, PageOverlay } from '@deriv/components';
+import { isMobile } from '@deriv/shared';
import { localize } from '@deriv/translations';
-import { Formik, Form, Field } from 'formik';
-import classNames from 'classnames';
import { connect } from 'Stores/connect';
const SelfExclusionForm = props => {
const [max_losses_error, setMaxLossesError] = React.useState('');
const {
- is_mobile,
is_onscreen_keyboard_active,
is_logged_in,
initial_values,
@@ -152,7 +152,8 @@ const SelfExclusionForm = props => {
@@ -187,10 +188,10 @@ const SelfExclusionForm = props => {
};
const SelfExclusion = props => {
- const { is_restricted, resetSelfExclusion, is_mobile } = props;
+ const { is_restricted, resetSelfExclusion } = props;
return (
<>
- {is_mobile ? (
+ {isMobile() ? (
@@ -219,7 +220,6 @@ const SelfExclusion = props => {
SelfExclusion.propTypes = {
is_onscreen_keyboard_active: PropTypes.bool,
- is_mobile: PropTypes.bool,
is_logged_in: PropTypes.bool,
is_restricted: PropTypes.bool,
initial_values: PropTypes.object,
@@ -235,7 +235,6 @@ SelfExclusion.propTypes = {
export default connect(({ client, self_exclusion, ui }) => ({
initial_values: self_exclusion.initial_values,
is_onscreen_keyboard_active: ui.is_onscreen_keyboard_active,
- is_mobile: ui.is_mobile,
is_logged_in: client.is_logged_in,
is_restricted: self_exclusion.is_restricted,
api_max_losses: self_exclusion.api_max_losses,
diff --git a/packages/bot-web-ui/src/components/summary/summary-card.scss b/packages/bot-web-ui/src/components/summary/summary-card.scss
index 7717ace4bb1d..671635f310fb 100644
--- a/packages/bot-web-ui/src/components/summary/summary-card.scss
+++ b/packages/bot-web-ui/src/components/summary/summary-card.scss
@@ -1,22 +1,24 @@
.db-summary-card {
$contract-card: &;
-
- border: 1px solid var(--border-disabled);
- border-radius: $BORDER_RADIUS;
box-sizing: border-box;
display: flex;
flex-direction: column;
position: relative;
+ @include desktop {
+ width: 100%;
+ }
&--inactive {
- background-color: var(--general-section-2);
- border: 1px solid var(--general-main-1);
color: var(--text-general);
font-size: 14px;
justify-content: center;
margin: auto;
text-align: center;
- height: 157px;
+ line-height: 2rem;
+ padding: 0 3rem;
+ & .dc-text {
+ text-align: center;
+ }
}
&--is-loading {
background-color: inherit;
@@ -28,9 +30,6 @@
&:hover {
border: 1px solid var(--border-disabled);
}
- &-mobile {
- border: 1px solid var(--border-normal);
- }
}
.dc-contract-card {
padding: 1.6rem 1.6rem 0.8rem;
@@ -44,7 +43,7 @@
}
&--mobile {
.dc-contract-card {
- padding: 1.6rem 0.8rem 0.8rem;
+ padding: 1.5rem 3.4rem;
}
.dc-contract-card__grid-underlying-trade--mobile {
grid-template-columns: 1fr 1.25fr;
diff --git a/packages/bot-web-ui/src/components/summary/summary-card.tsx b/packages/bot-web-ui/src/components/summary/summary-card.tsx
index 3a7f43a519a0..8ff77faaff3a 100644
--- a/packages/bot-web-ui/src/components/summary/summary-card.tsx
+++ b/packages/bot-web-ui/src/components/summary/summary-card.tsx
@@ -1,11 +1,12 @@
import React from 'react';
import classNames from 'classnames';
+import { ContractCard, Text } from '@deriv/components';
+import { isMobile } from '@deriv/shared';
import { localize } from '@deriv/translations';
-import { ContractCard } from '@deriv/components';
import ContractCardLoader from 'Components/contract-card-loading';
import { getCardLabels, getContractTypeDisplay } from 'Constants/contract';
-import { connect } from 'Stores/connect';
import { connectWithContractUpdate } from 'Utils/multiplier';
+import { connect } from 'Stores/connect';
import RootStore from 'Stores/index';
import { TSummaryCardProps } from './summary-card.types';
@@ -17,7 +18,6 @@ const SummaryCard = ({
is_contract_completed,
is_contract_loading,
is_contract_inactive,
- is_mobile,
is_multiplier,
onClickSell,
is_sell_requested,
@@ -25,6 +25,7 @@ const SummaryCard = ({
server_time,
setCurrentFocus,
}: TSummaryCardProps) => {
+ const is_mobile = isMobile();
const card_header = (
)}
{!is_contract_loading && !contract_info && (
-
- {localize('Build a bot from the start menu then hit the run button to run the bot.')}
-
+
+ {localize('When you’re ready to trade, hit ')}
+ {localize('Run')}
+ {localize('. You’ll be able to track your bot’s performance here.')}
+
)}
);
@@ -114,13 +117,10 @@ const SummaryCard = ({
export default connect(({ summary_card, common, run_panel, ui }: RootStore) => ({
addToast: ui.addToast,
- contract_info: summary_card.contract_info,
contract_store: summary_card,
current_focus: ui.current_focus,
is_contract_completed: summary_card.is_contract_completed,
is_contract_inactive: summary_card.is_contract_inactive,
- is_contract_loading: summary_card.is_contract_loading,
- is_mobile: ui.is_mobile,
is_multiplier: summary_card.is_multiplier,
onClickSell: run_panel.onClickSell,
is_sell_requested: run_panel.is_sell_requested,
diff --git a/packages/bot-web-ui/src/components/summary/summary-card.types.ts b/packages/bot-web-ui/src/components/summary/summary-card.types.ts
index 2025d5ce75e3..9a7971169bcd 100644
--- a/packages/bot-web-ui/src/components/summary/summary-card.types.ts
+++ b/packages/bot-web-ui/src/components/summary/summary-card.types.ts
@@ -5,7 +5,7 @@ type TTransactionIds = {
buy: number;
};
-type TContractInfo = {
+export type TContractInfo = {
accountID?: number;
account_id: number;
barrier: string;
@@ -65,7 +65,6 @@ export interface TSummaryCardProps {
is_contract_completed: boolean;
is_contract_loading: boolean;
is_contract_inactive: boolean;
- is_mobile: boolean;
is_multiplier: boolean;
onClickSell: () => void;
is_sell_requested: boolean;
diff --git a/packages/bot-web-ui/src/components/summary/summary.scss b/packages/bot-web-ui/src/components/summary/summary.scss
index 834a94471511..b1ff7dfd950b 100644
--- a/packages/bot-web-ui/src/components/summary/summary.scss
+++ b/packages/bot-web-ui/src/components/summary/summary.scss
@@ -1,5 +1,15 @@
.summary {
- padding: 16px;
+ display: flex;
+ align-items: center;
+ flex-direction: column;
+ background-color: var(--general-section-2);
+ @include desktop {
+ height: inherit;
+ }
+
+ &--loading {
+ width: 100%;
+ }
&__tiles {
display: flex;
@@ -7,6 +17,7 @@
justify-content: space-between;
margin: 24px 0;
}
+
&__tile {
display: flex;
flex-direction: column;
diff --git a/packages/bot-web-ui/src/components/summary/summary.tsx b/packages/bot-web-ui/src/components/summary/summary.tsx
index bbb8a45177f1..041c2138f383 100644
--- a/packages/bot-web-ui/src/components/summary/summary.tsx
+++ b/packages/bot-web-ui/src/components/summary/summary.tsx
@@ -1,28 +1,42 @@
-import classnames from 'classnames';
import React from 'react';
+import classnames from 'classnames';
import { ThemedScrollbars } from '@deriv/components';
+import { isMobile } from '@deriv/shared';
import { connect } from 'Stores/connect';
-import SummaryCard from './summary-card';
import RootStore from 'Stores/index';
+import SummaryCard from './summary-card';
+import { TContractInfo } from './summary-card.types';
-interface TSummaryProps {
- is_mobile: boolean;
+type TSummary = {
is_drawer_open: boolean;
-}
+ is_contract_loading: boolean;
+ contract_info?: TContractInfo;
+};
-const Summary = ({ is_mobile, is_drawer_open }: TSummaryProps) => (
-
-
-
-
-
-);
+const Summary = ({ is_drawer_open, is_contract_loading, contract_info }: TSummary) => {
+ const is_mobile = isMobile();
+ return (
+
+
+
+
+
+ );
+};
-export default connect(({ ui }: RootStore) => ({
- is_mobile: ui.is_mobile,
+export default connect(({ summary_card }: RootStore) => ({
+ is_contract_loading: summary_card.is_contract_loading,
+ contract_info: summary_card.contract_info,
}))(Summary);
diff --git a/packages/bot-web-ui/src/components/toolbar/index.js b/packages/bot-web-ui/src/components/toolbar/index.js
deleted file mode 100644
index 5df6b858bd02..000000000000
--- a/packages/bot-web-ui/src/components/toolbar/index.js
+++ /dev/null
@@ -1,4 +0,0 @@
-import Toolbar from './toolbar.jsx';
-import './toolbar.scss';
-
-export default Toolbar;
diff --git a/packages/bot-web-ui/src/components/toolbar/toolbar.jsx b/packages/bot-web-ui/src/components/toolbar/toolbar.jsx
deleted file mode 100644
index c1e2c22b52a2..000000000000
--- a/packages/bot-web-ui/src/components/toolbar/toolbar.jsx
+++ /dev/null
@@ -1,237 +0,0 @@
-import { Button, Icon, ThemedScrollbars, Popover, Dialog } from '@deriv/components';
-import { Localize, localize } from '@deriv/translations';
-import PropTypes from 'prop-types';
-import React from 'react';
-import LoadModal from 'Components/load-modal';
-import SaveModal from 'Components/save-modal';
-import TradeAnimation from 'Components/trade-animation';
-import { tabs_title } from 'Constants/bot-contents';
-import { popover_zindex } from 'Constants/z-indexes';
-import { connect } from 'Stores/connect';
-
-const IconButton = ({ popover_message, icon, icon_id, icon_color, iconOnClick }) => (
-
-
-
-);
-
-const ToolbarButton = ({ popover_message, button_id, button_classname, buttonOnClick, icon, button_text }) => (
-
-
- {button_text}
-
-
-);
-
-const WorkspaceGroup = ({
- has_redo_stack,
- has_undo_stack,
- onResetClick,
- onSortClick,
- onUndoClick,
- onZoomInOutClick,
- toggleLoadModal,
- toggleSaveModal,
-}) => (
-
-
-
-
-
-
onUndoClick(/* redo */ false)}
- />
- onUndoClick(/* redo */ true)}
- />
-
- onZoomInOutClick(/* in */ true)}
- />
- onZoomInOutClick(/* in */ false)}
- />
-
-);
-
-const Toolbar = ({ onMount, onUnmount, ...other_props }) => {
- const {
- is_mobile,
- is_running,
- active_tab,
- is_dialog_open,
- onOkButtonClick,
- closeResetDialog,
- toggleStrategyModal,
- toggleLoadModal,
- toggleSaveModal,
- } = other_props;
-
- return (
- <>
- {is_mobile ? (
-
-
- }
- button_text={localize('Load')}
- />
- }
- button_text={localize('Quick')}
- />
- }
- button_text={localize('Save')}
- />
-
-
- ) : (
-
-
-
- }
- button_text={localize('Quick strategy')}
- />
- {active_tab === tabs_title.WORKSPACE && }
-
-
-
-
-
-
- )}
-
-
-
- {is_running ? (
- ]}
- />
- ) : (
- localize('Any unsaved changes will be lost.')
- )}
-
- >
- );
-};
-
-Toolbar.propTypes = {
- active_tab: PropTypes.string,
- file_name: PropTypes.string,
- has_redo_stack: PropTypes.bool,
- has_undo_stack: PropTypes.bool,
- is_dialog_open: PropTypes.bool,
- is_drawer_open: PropTypes.bool,
- is_running: PropTypes.bool,
- is_search_loading: PropTypes.bool,
- is_stop_button_disabled: PropTypes.bool,
- is_stop_button_visible: PropTypes.bool,
- closeResetDialog: PropTypes.func,
- onGoogleDriveClick: PropTypes.func,
- onMount: PropTypes.func,
- onOkButtonClick: PropTypes.func,
- onResetClick: PropTypes.func,
- onRunButtonClick: PropTypes.func,
- onSortClick: PropTypes.func,
- onUndoClick: PropTypes.func,
- onUnmount: PropTypes.func,
- onZoomInOutClick: PropTypes.func,
- toggleSaveLoadModal: PropTypes.func,
-};
-
-export default connect(({ main_content, run_panel, save_modal, load_modal, toolbar, ui, quick_strategy }) => ({
- active_tab: main_content.active_tab,
- file_name: toolbar.file_name,
- has_redo_stack: toolbar.has_redo_stack,
- has_undo_stack: toolbar.has_undo_stack,
- is_dialog_open: toolbar.is_dialog_open,
- is_drawer_open: run_panel.is_drawer_open,
- is_mobile: ui.is_mobile,
- is_running: run_panel.is_running,
- is_search_loading: toolbar.is_search_loading,
- is_stop_button_disabled: run_panel.is_stop_button_disabled,
- is_stop_button_visible: run_panel.is_stop_button_visible,
- closeResetDialog: toolbar.closeResetDialog,
- onGoogleDriveClick: toolbar.onGoogleDriveClick,
- onMount: toolbar.onMount,
- onOkButtonClick: toolbar.onResetOkButtonClick,
- onResetClick: toolbar.onResetClick,
- onRunButtonClick: run_panel.onRunButtonClick,
- onSortClick: toolbar.onSortClick,
- onUndoClick: toolbar.onUndoClick,
- onUnmount: toolbar.onUnmount,
- onZoomInOutClick: toolbar.onZoomInOutClick,
- toggleLoadModal: load_modal.toggleLoadModal,
- toggleSaveModal: save_modal.toggleSaveModal,
- toggleStrategyModal: quick_strategy.toggleStrategyModal,
-}))(Toolbar);
diff --git a/packages/bot-web-ui/src/components/toolbar/toolbar.scss b/packages/bot-web-ui/src/components/toolbar/toolbar.scss
deleted file mode 100644
index 464230f332e8..000000000000
--- a/packages/bot-web-ui/src/components/toolbar/toolbar.scss
+++ /dev/null
@@ -1,85 +0,0 @@
-.toolbar {
- height: 56px;
- display: flex;
- justify-content: space-between;
- align-items: center;
- box-shadow: inset 0 1px 0 0 var(--general-section-1), inset 0 -1px 0 0 var(--general-section-1);
- padding: 5px 6px;
-
- &__btn {
- background-color: var(--button-primary-default) !important;
-
- &--icon {
- display: flex;
- justify-content: center;
- margin: 0 4px;
- height: calc(56px - 16px) !important;
-
- & .dc-btn__icon {
- padding-right: 0.4rem;
- }
- > * {
- align-self: center;
- }
- &-text {
- @include typeface(--title-center-bold-active, none);
- }
- }
- }
- &__section {
- display: flex;
-
- > * {
- align-self: center;
- margin: 0 4px;
- }
- }
- &__icon {
- cursor: pointer;
- border: none;
- margin: auto 12px;
- height: 16px;
- width: 16px;
- fill: var(--text-prominent);
- }
- &__group {
- display: flex;
- border-radius: $BORDER_RADIUS;
- border: solid 1px var(--border-normal);
- height: 40px;
-
- &-btn {
- padding: 0px 12px;
- height: 40px;
-
- > * {
- align-self: center;
- }
- }
- }
- &__animation {
- margin-right: 0.5rem;
- max-width: 35rem;
- }
- &__dialog {
- &-text--second {
- margin-top: 2.4rem;
- }
- }
-}
-
-@keyframes spin {
- 0% {
- transform: rotate(0deg);
- }
- 100% {
- transform: rotate(360deg);
- }
-}
-
-.vertical-divider {
- width: 1px;
- height: 17px;
- margin: 8px;
- background-color: var(--border-normal);
-}
diff --git a/packages/bot-web-ui/src/components/toolbox/index.js b/packages/bot-web-ui/src/components/toolbox/index.js
deleted file mode 100644
index 77f451b63346..000000000000
--- a/packages/bot-web-ui/src/components/toolbox/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import Toolbox from './toolbox.jsx';
-
-export default Toolbox;
diff --git a/packages/bot-web-ui/src/components/toolbox/toolbox.jsx b/packages/bot-web-ui/src/components/toolbox/toolbox.jsx
deleted file mode 100644
index fbc766f91a38..000000000000
--- a/packages/bot-web-ui/src/components/toolbox/toolbox.jsx
+++ /dev/null
@@ -1,196 +0,0 @@
-import classNames from 'classnames';
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Field as FormField, Formik, Form } from 'formik';
-import { Drawer, Input, Icon, Text } from '@deriv/components';
-import { localize } from '@deriv/translations';
-import { ToolboxItems } from './toolbox-items.jsx';
-import { connect } from '../../stores/connect';
-import { popover_zindex } from '../../constants/z-indexes';
-
-const SearchBox = ({ is_search_loading, onSearch, onSearchBlur, onSearchClear, onSearchKeyUp }) => (
-
-
- {({ submitForm, values: { search }, setFieldValue }) => (
-
- )}
-
-
-);
-
-const Toolbox = ({
- onMount,
- onUnmount,
- hasSubCategory,
- is_mobile,
- is_search_loading,
- is_toolbox_open,
- onSearch,
- onSearchBlur,
- onSearchClear,
- onSearchKeyUp,
- onToolboxItemClick,
- onToolboxItemExpand,
- sub_category_index,
- toggleDrawer,
- toolbox_dom,
-}) => {
- const toolbox_ref = React.useRef(ToolboxItems);
-
- React.useEffect(() => {
- onMount(toolbox_ref);
- return () => onUnmount();
- }, [onMount, onUnmount]);
-
- if (is_mobile) {
- return null;
- }
-
- return (
-
-
-
- );
-};
-
-Toolbox.PropTypes = {
- hasSubCategory: PropTypes.func,
- is_mobile: PropTypes.bool,
- is_search_loading: PropTypes.bool,
- is_toolbox_open: PropTypes.bool,
- onMount: PropTypes.func,
- onSearch: PropTypes.func,
- onSearchBlur: PropTypes.func,
- onSearchClear: PropTypes.func,
- onSearchKeyUp: PropTypes.func,
- onToolboxItemClick: PropTypes.func,
- onToolboxItemExpand: PropTypes.func,
- onUnmount: PropTypes.func,
- sub_category_index: PropTypes.array,
- toggleDrawer: PropTypes.func,
- toolbox_dom: PropTypes.arrayOf(NodeList),
-};
-
-export default connect(({ toolbox, ui }) => ({
- hasSubCategory: toolbox.hasSubCategory,
- is_mobile: ui.is_mobile,
- is_search_loading: toolbox.is_search_loading,
- is_toolbox_open: toolbox.is_toolbox_open,
- onMount: toolbox.onMount,
- onSearch: toolbox.onSearch,
- onSearchBlur: toolbox.onSearchBlur,
- onSearchClear: toolbox.onSearchClear,
- onSearchKeyUp: toolbox.onSearchKeyUp,
- onToolboxItemClick: toolbox.onToolboxItemClick,
- onToolboxItemExpand: toolbox.onToolboxItemExpand,
- onUnmount: toolbox.onUnmount,
- sub_category_index: toolbox.sub_category_index,
- toggleDrawer: toolbox.toggleDrawer,
- toolbox_dom: toolbox.toolbox_dom,
-}))(Toolbox);
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 de40ff31ad8c..9c8ef62a2ec9 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,8 +1,9 @@
-import classNames from 'classnames';
import React from 'react';
+import classNames from 'classnames';
import PropTypes from 'prop-types';
import { Button, Icon, Modal, Text } from '@deriv/components';
-import { localize, Localize } from '@deriv/translations';
+import { isMobile } from '@deriv/shared';
+import { Localize, localize } from '@deriv/translations';
import ContractResultOverlay from 'Components/contract-result-overlay';
import { contract_stages } from 'Constants/contract-stage';
import { connect } from 'Stores/connect';
@@ -75,7 +76,6 @@ const TradeAnimation = ({
contract_stage,
is_animation_info_modal_open,
is_contract_completed,
- is_mobile,
is_stop_button_visible,
is_stop_button_disabled,
profit,
@@ -89,7 +89,6 @@ const TradeAnimation = ({
}) => {
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();
@@ -165,7 +164,7 @@ const TradeAnimation = ({
{info_direction === 'right' &&
}
@@ -187,11 +186,10 @@ TradeAnimation.propTypes = {
should_show_overlay: PropTypes.bool,
};
-export default connect(({ summary_card, run_panel, toolbar, ui, client }) => ({
+export default connect(({ summary_card, run_panel, toolbar, client }) => ({
contract_stage: run_panel.contract_stage,
is_animation_info_modal_open: toolbar.is_animation_info_modal_open,
is_contract_completed: summary_card.is_contract_completed,
- is_mobile: ui.is_mobile,
is_stop_button_visible: run_panel.is_stop_button_visible,
is_stop_button_disabled: run_panel.is_stop_button_disabled,
onRunButtonClick: run_panel.onRunButtonClick,
diff --git a/packages/bot-web-ui/src/components/transactions/transaction.jsx b/packages/bot-web-ui/src/components/transactions/transaction.jsx
index cc8238a8b442..c2e731aebe5f 100644
--- a/packages/bot-web-ui/src/components/transactions/transaction.jsx
+++ b/packages/bot-web-ui/src/components/transactions/transaction.jsx
@@ -1,11 +1,11 @@
-import classNames from 'classnames';
-import { Icon, Money, Popover, IconTradeTypes } from '@deriv/components';
-import { localize } from '@deriv/translations';
-import { convertDateFormat } from '@deriv/shared';
import React from 'react';
-import ContentLoader from 'react-content-loader';
+import classNames from 'classnames';
import PropTypes from 'prop-types';
+import ContentLoader from 'react-content-loader';
import { getContractTypeName } from '@deriv/bot-skeleton';
+import { Icon, IconTradeTypes, Money, Popover } from '@deriv/components';
+import { convertDateFormat } from '@deriv/shared';
+import { localize } from '@deriv/translations';
import { popover_zindex } from 'Constants/z-indexes';
import { connect } from 'Stores/connect';
diff --git a/packages/bot-web-ui/src/components/transactions/transactions.jsx b/packages/bot-web-ui/src/components/transactions/transactions.jsx
index ac8c527cf4aa..5ff532a24a12 100644
--- a/packages/bot-web-ui/src/components/transactions/transactions.jsx
+++ b/packages/bot-web-ui/src/components/transactions/transactions.jsx
@@ -1,10 +1,10 @@
+import React from 'react';
import classnames from 'classnames';
-import { Icon, DesktopWrapper, DataList, ThemedScrollbars, Text } from '@deriv/components';
-import { localize } from '@deriv/translations';
-import { useNewRowTransition } from '@deriv/shared';
import { PropTypes } from 'prop-types';
-import React from 'react';
import { CSSTransition } from 'react-transition-group';
+import { DataList, DesktopWrapper, Icon, Text, ThemedScrollbars } from '@deriv/components';
+import { isMobile, useNewRowTransition } from '@deriv/shared';
+import { localize } from '@deriv/translations';
import Download from 'Components/download';
import { contract_stages } from 'Constants/contract-stage';
import { transaction_elements } from 'Constants/transactions';
@@ -36,12 +36,14 @@ const TransactionItem = ({ row, is_new_row }) => {
}
};
-const Transactions = ({ contract_stage, elements, is_drawer_open, is_mobile, onMount, onUnmount }) => {
+const Transactions = ({ contract_stage, elements, is_drawer_open, onMount, onUnmount }) => {
React.useEffect(() => {
onMount();
return () => onUnmount();
}, [onMount, onUnmount]);
+ const is_mobile = isMobile();
+
return (
({
+export default connect(({ transactions, run_panel }) => ({
contract_stage: run_panel.contract_stage,
elements: transactions.elements,
- is_mobile: ui.is_mobile,
onMount: transactions.onMount,
onUnmount: transactions.onUnmount,
}))(Transactions);
diff --git a/packages/bot-web-ui/src/components/transactions/transactions.scss b/packages/bot-web-ui/src/components/transactions/transactions.scss
index 61da1b5c1322..01779d899e84 100644
--- a/packages/bot-web-ui/src/components/transactions/transactions.scss
+++ b/packages/bot-web-ui/src/components/transactions/transactions.scss
@@ -1,6 +1,7 @@
.transactions {
$transaction: &;
$grid-template-columns: 0.8fr 1fr 1.1fr;
+ flex-direction: column;
&-empty {
height: 100%;
@@ -28,12 +29,15 @@
&__message {
margin: 0 auto;
margin-bottom: 2rem;
+ & .dc-text {
+ line-height: var(--text-lh-xxl);
+ }
}
&__list {
- list-style-type: circle;
+ list-style-type: disc;
margin-left: 2rem;
font-size: var(--text-size-xxs);
- line-height: var(--text-lh-m);
+ line-height: var(--text-lh-xl);
color: var(--text-less-prominent);
}
}
@@ -64,7 +68,7 @@
}
}
&__content {
- height: calc(100% - 9.2rem);
+ height: calc(100% - 9rem);
&--mobile {
height: calc(100% - 4.2rem);
diff --git a/packages/bot-web-ui/src/constants/bot-contents.ts b/packages/bot-web-ui/src/constants/bot-contents.ts
index f4d2b243b9e2..64e8fa63b58a 100644
--- a/packages/bot-web-ui/src/constants/bot-contents.ts
+++ b/packages/bot-web-ui/src/constants/bot-contents.ts
@@ -1,8 +1,23 @@
type TTabsTitleProps = {
- [key: string]: string;
+ [key: string]: string | number;
+};
+
+type TDashboardTabsProps = {
+ [key: string]: number;
};
export const tabs_title: TTabsTitleProps = Object.freeze({
WORKSPACE: 'Workspace',
CHART: 'Chart',
});
+
+export const DBOT_TABS: TDashboardTabsProps = Object.freeze({
+ DASHBOARD: 0,
+ BOT_BUILDER: 1,
+ CHART: 2,
+ TUTORIAL: 3,
+});
+
+export const MAX_STRATEGIES = 10;
+
+export const TAB_IDS = ['id-dbot-dashboard', 'id-bot-builder', 'id-charts', 'id-tutorials'];
diff --git a/packages/bot-web-ui/src/constants/contract.js b/packages/bot-web-ui/src/constants/contract.js
index 55b89c3971ce..b13a1fd2afcf 100644
--- a/packages/bot-web-ui/src/constants/contract.js
+++ b/packages/bot-web-ui/src/constants/contract.js
@@ -1,5 +1,5 @@
-import { localize } from '@deriv/translations';
import { getTotalProfit } from '@deriv/shared';
+import { localize } from '@deriv/translations';
import { getBuyPrice } from 'Utils/multiplier';
export const getCardLabels = () => ({
@@ -16,6 +16,7 @@ export const getCardLabels = () => ({
PROFIT_LOSS: localize('Profit/Loss:'),
POTENTIAL_PROFIT_LOSS: localize('Potential profit/loss:'),
INDICATIVE_PRICE: localize('Indicative price:'),
+ INITIAL_STAKE: localize('Initial stake:'),
PAYOUT: localize('Sell Price:'),
PURCHASE_PRICE: localize('Buy price:'),
POTENTIAL_PAYOUT: localize('Payout limit:'),
diff --git a/packages/bot-web-ui/src/constants/load-modal.js b/packages/bot-web-ui/src/constants/load-modal.js
index 2df51945123b..860e2a615f19 100644
--- a/packages/bot-web-ui/src/constants/load-modal.js
+++ b/packages/bot-web-ui/src/constants/load-modal.js
@@ -3,3 +3,13 @@ export const tabs_title = Object.freeze({
TAB_GOOGLE: 'google_tab',
TAB_RECENT: 'recent_tab',
});
+
+export const clearInjectionDiv = (type, el_ref) => {
+ if (type === 'store') {
+ if (el_ref && el_ref.getElementsByClassName('injectionDiv').length > 1) {
+ el_ref.removeChild(el_ref.getElementsByClassName('injectionDiv')[0]);
+ }
+ } else {
+ el_ref?.current?.removeChild(el_ref?.current?.children[0]);
+ }
+};
diff --git a/packages/bot-web-ui/src/constants/z-indexes.js b/packages/bot-web-ui/src/constants/z-indexes.js
index cd08a11043e6..c08c4b3a1c48 100644
--- a/packages/bot-web-ui/src/constants/z-indexes.js
+++ b/packages/bot-web-ui/src/constants/z-indexes.js
@@ -3,5 +3,5 @@ export const popover_zindex = Object.freeze({
TOOLBAR: 100,
TRANSACTION: 10,
SUMMARY_TOOLTIPS: 5,
- RUN_PANEL: 1,
+ RUN_PANEL: 8,
});
diff --git a/packages/bot-web-ui/src/public-path.js b/packages/bot-web-ui/src/public-path.js
deleted file mode 100644
index c58be9d4b4f4..000000000000
--- a/packages/bot-web-ui/src/public-path.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import { setSmartChartsPublicPath } from '@deriv/deriv-charts';
-
-const getUrlBase = (path = '') => {
- const l = window.location;
-
- if (!/^\/(br_)/.test(l.pathname)) return path;
-
- return `/${l.pathname.split('/')[1]}${/^\//.test(path) ? path : `/${path}`}`;
-};
-
-export function setBotPublicPath(path) {
- __webpack_public_path__ = path; // eslint-disable-line no-global-assign
-}
-
-setBotPublicPath(getUrlBase('/'));
-setSmartChartsPublicPath(getUrlBase('/js/smartcharts/'));
diff --git a/packages/bot-web-ui/src/public-path.ts b/packages/bot-web-ui/src/public-path.ts
new file mode 100644
index 000000000000..3c1441472533
--- /dev/null
+++ b/packages/bot-web-ui/src/public-path.ts
@@ -0,0 +1,20 @@
+import { setSmartChartsPublicPath } from '@deriv/deriv-charts';
+
+const getUrlBase = (path = '') => {
+ const l = window.location;
+
+ if (!/^\/(br_)/.test(l.pathname)) return path;
+
+ const get_path = /^\//.test(path) ? path : `/${path}`;
+
+ return `/${l.pathname.split('/')[1]}${get_path}`;
+};
+
+export function setBotPublicPath(path: string) {
+ __webpack_public_path__ = path; // eslint-disable-line no-global-assign
+}
+
+export const getImageLocation = (image_name: string) => getUrlBase(`/public/images/common/${image_name}`);
+
+setBotPublicPath(getUrlBase('/'));
+setSmartChartsPublicPath(getUrlBase('/js/smartcharts/'));
diff --git a/packages/bot-web-ui/src/public/images/common/static_images/ic-new-user-step-five.png b/packages/bot-web-ui/src/public/images/common/static_images/ic-new-user-step-five.png
new file mode 100644
index 000000000000..4570613506fc
Binary files /dev/null and b/packages/bot-web-ui/src/public/images/common/static_images/ic-new-user-step-five.png differ
diff --git a/packages/bot-web-ui/src/public/images/common/static_images/ic-new-user-step-four.png b/packages/bot-web-ui/src/public/images/common/static_images/ic-new-user-step-four.png
new file mode 100644
index 000000000000..2444c0ca7837
Binary files /dev/null and b/packages/bot-web-ui/src/public/images/common/static_images/ic-new-user-step-four.png differ
diff --git a/packages/bot-web-ui/src/public/images/common/static_images/ic-new-user-step-seven.png b/packages/bot-web-ui/src/public/images/common/static_images/ic-new-user-step-seven.png
new file mode 100644
index 000000000000..fcb111c3a4d1
Binary files /dev/null and b/packages/bot-web-ui/src/public/images/common/static_images/ic-new-user-step-seven.png differ
diff --git a/packages/bot-web-ui/src/public/images/common/static_images/ic-new-user-step-six.png b/packages/bot-web-ui/src/public/images/common/static_images/ic-new-user-step-six.png
new file mode 100644
index 000000000000..b15494a1f90b
Binary files /dev/null and b/packages/bot-web-ui/src/public/images/common/static_images/ic-new-user-step-six.png differ
diff --git a/packages/bot-web-ui/src/public/images/common/static_images/ic-new-user-step-three.png b/packages/bot-web-ui/src/public/images/common/static_images/ic-new-user-step-three.png
new file mode 100644
index 000000000000..2c1fd8b7f255
Binary files /dev/null and b/packages/bot-web-ui/src/public/images/common/static_images/ic-new-user-step-three.png differ
diff --git a/packages/bot-web-ui/src/public/images/common/static_images/ic-new-user-step-two.png b/packages/bot-web-ui/src/public/images/common/static_images/ic-new-user-step-two.png
new file mode 100644
index 000000000000..d42d7c5c761e
Binary files /dev/null and b/packages/bot-web-ui/src/public/images/common/static_images/ic-new-user-step-two.png differ
diff --git a/packages/bot-web-ui/src/stores/app-store.js b/packages/bot-web-ui/src/stores/app-store.js
index d2a9da9a6f3b..58a6abf6fa28 100644
--- a/packages/bot-web-ui/src/stores/app-store.js
+++ b/packages/bot-web-ui/src/stores/app-store.js
@@ -1,29 +1,48 @@
-import { action, reaction, makeObservable } from 'mobx';
-import { isEuResidenceWithOnlyVRTC, showDigitalOptionsUnavailableError, routes } from '@deriv/shared';
+import { action, makeObservable, reaction } from 'mobx';
+import { ApiHelpers, DBot, runIrreversibleEvents } from '@deriv/bot-skeleton';
+import { isEuResidenceWithOnlyVRTC, routes, showDigitalOptionsUnavailableError } from '@deriv/shared';
import { localize } from '@deriv/translations';
-import { runIrreversibleEvents, ApiHelpers, DBot } from '@deriv/bot-skeleton';
export default class AppStore {
- constructor(root_store) {
+ constructor(root_store, core) {
makeObservable(this, {
onMount: action.bound,
onUnmount: action.bound,
+ onBeforeUnload: action.bound,
+ registerReloadOnLanguageChange: action.bound,
+ registerCurrencyReaction: action.bound,
+ registerOnAccountSwitch: action.bound,
+ registerLandingCompanyChangeReaction: action.bound,
+ registerResidenceChangeReaction: action.bound,
+ setDBotEngineStores: action.bound,
+ onClickOutsideBlockly: action.bound,
+ showDigitalOptionsMaltainvestError: action.bound,
});
this.root_store = root_store;
+ this.core = core;
this.dbot_store = null;
this.api_helpers_store = null;
+ this.timer = null;
}
onMount() {
- const { blockly_store, core, main_content } = this.root_store;
- const { client, common, ui } = core;
+ const { blockly_store, run_panel } = this.root_store;
+ const { client, common } = this.core;
+
+ this.timer = setInterval(() => {
+ if (window.sendRequestsStatistic) {
+ window.sendRequestsStatistic(run_panel?.is_running);
+ performance.clearMeasures();
+ }
+ }, 10000);
+
this.showDigitalOptionsMaltainvestError(client, common);
- blockly_store.startLoading();
- DBot.initWorkspace(__webpack_public_path__, this.dbot_store, this.api_helpers_store, ui.is_mobile).then(() => {
- main_content.setContainerSize();
- blockly_store.endLoading();
+ blockly_store.setLoading(true);
+ DBot.initWorkspace(__webpack_public_path__, this.dbot_store, this.api_helpers_store).then(() => {
+ blockly_store.setContainerSize();
+ blockly_store.setLoading(false);
});
this.registerReloadOnLanguageChange(this);
this.registerCurrencyReaction.call(this);
@@ -34,7 +53,7 @@ export default class AppStore {
window.addEventListener('click', this.onClickOutsideBlockly);
window.addEventListener('beforeunload', this.onBeforeUnload);
- main_content.getCachedActiveTab();
+ blockly_store.getCachedActiveTab();
}
onUnmount() {
@@ -64,10 +83,16 @@ export default class AppStore {
window.removeEventListener('beforeunload', this.onBeforeUnload);
// Ensure account switch is re-enabled.
- const { ui } = this.root_store.core;
+ const { ui } = this.core;
ui.setAccountSwitcherDisabledMessage(false);
ui.setPromptHandler(false);
+
+ if (this.timer) clearInterval(this.timer);
+ if (window.sendRequestsStatistic) {
+ window.sendRequestsStatistic(false);
+ performance.clearMeasures();
+ }
}
onBeforeUnload = event => {
@@ -79,7 +104,7 @@ export default class AppStore {
};
registerReloadOnLanguageChange() {
this.disposeReloadOnLanguageChangeReaction = reaction(
- () => this.root_store.common.current_language,
+ () => this.core.common.current_language,
() => {
// temporarily added this to refresh just dbot in case of changing language,
// otherwise it should change language without refresh.
@@ -93,7 +118,7 @@ export default class AppStore {
registerCurrencyReaction() {
// Syncs all trade options blocks' currency with the client's active currency.
this.disposeCurrencyReaction = reaction(
- () => this.root_store.core.client.currency,
+ () => this.core.client.currency,
currency => {
if (!Blockly.derivWorkspace) return;
@@ -112,7 +137,7 @@ export default class AppStore {
}
registerOnAccountSwitch() {
- const { client, common } = this.root_store.core;
+ const { client, common } = this.core;
this.disposeSwitchAccountListener = reaction(
() => client.switch_broadcast,
@@ -141,7 +166,7 @@ export default class AppStore {
}
registerLandingCompanyChangeReaction() {
- const { client, common } = this.root_store.core;
+ const { client, common } = this.core;
this.disposeLandingCompanyChangeReaction = reaction(
() => client.landing_company_shortcode,
@@ -156,7 +181,7 @@ export default class AppStore {
text: localize(
'We’re working to have this available for you soon. If you have another account, switch to that account to continue trading. You may add a Deriv MT5 Financial.'
),
- title: localize('DBot is not available for this account'),
+ title: localize('Deriv Bot is not available for this account'),
link: localize('Go to Deriv MT5 dashboard'),
});
}
@@ -165,7 +190,7 @@ export default class AppStore {
}
registerResidenceChangeReaction() {
- const { client, common } = this.root_store.core;
+ const { client, common } = this.core;
this.disposeResidenceChangeReaction = reaction(
() => client.account_settings.country_code,
@@ -180,7 +205,7 @@ export default class AppStore {
text: localize(
'We’re working to have this available for you soon. If you have another account, switch to that account to continue trading. You may add a Deriv MT5 Financial.'
),
- title: localize('DBot is not available for this account'),
+ title: localize('Deriv Bot is not available for this account'),
link: localize('Go to Deriv MT5 dashboard'),
});
}
@@ -190,37 +215,29 @@ export default class AppStore {
setDBotEngineStores() {
// DO NOT pass the rootstore in, if you need a prop define it in dbot-skeleton-store ans pass it through.
- const {
- core: { client },
- flyout,
- toolbar,
- save_modal,
- quick_strategy,
- load_modal,
- blockly_store,
- summary_card,
- } = this.root_store;
+ const { flyout, toolbar, save_modal, dashboard, quick_strategy, load_modal, blockly_store, summary_card } =
+ this.root_store;
+ const { client } = this.core;
const { handleFileChange } = load_modal;
- const { toggleStrategyModal } = quick_strategy;
- const { startLoading, endLoading } = blockly_store;
- const { populateConfig, setContractUpdateConfig } = summary_card;
+ const { loadDataStrategy } = quick_strategy;
+ const { setLoading } = blockly_store;
+ const { setContractUpdateConfig } = summary_card;
this.dbot_store = {
- is_mobile: false,
client,
flyout,
- populateConfig,
toolbar,
save_modal,
- startLoading,
+ dashboard,
+ load_modal,
+ setLoading,
setContractUpdateConfig,
- endLoading,
- toggleStrategyModal,
+ loadDataStrategy,
handleFileChange,
};
this.api_helpers_store = {
- server_time: this.root_store.server_time,
+ server_time: this.core.common.server_time,
ws: this.root_store.ws,
};
}
@@ -247,7 +264,7 @@ export default class AppStore {
text: localize(
'We’re working to have this available for you soon. If you have another account, switch to that account to continue trading. You may add a Deriv MT5 Financial.'
),
- title: localize('DBot is not available for this account'),
+ title: localize('Deriv Bot is not available for this account'),
link: localize('Go to Deriv MT5 dashboard'),
});
} else if (common.has_error) {
diff --git a/packages/bot-web-ui/src/stores/blockly-store.js b/packages/bot-web-ui/src/stores/blockly-store.js
deleted file mode 100644
index 879bb943a413..000000000000
--- a/packages/bot-web-ui/src/stores/blockly-store.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import { action, observable, makeObservable } from 'mobx';
-
-export default class BlocklyStore {
- constructor(root_store) {
- makeObservable(this, {
- is_loading: observable,
- startLoading: action.bound,
- endLoading: action.bound,
- });
-
- this.root_store = root_store;
- }
-
- is_loading = false;
-
- startLoading() {
- this.is_loading = true;
- }
-
- endLoading() {
- this.is_loading = false;
- }
-}
diff --git a/packages/bot-web-ui/src/stores/blockly-store.ts b/packages/bot-web-ui/src/stores/blockly-store.ts
new file mode 100644
index 000000000000..08729aff403f
--- /dev/null
+++ b/packages/bot-web-ui/src/stores/blockly-store.ts
@@ -0,0 +1,67 @@
+import { action, makeObservable, observable } from 'mobx';
+import { onWorkspaceResize } from '@deriv/bot-skeleton';
+import { tabs_title } from 'Constants/bot-contents';
+import { getSetting, storeSetting } from 'Utils/settings';
+import RootStore from './root-store';
+
+export interface IBlocklyStore {
+ is_loading: boolean;
+ active_tab: string;
+ setActiveTab: (tab: string) => void;
+ setContainerSize: () => void;
+ onMount: () => void;
+ onUnmount: () => void;
+ setLoading: () => void;
+ getCachedActiveTab: () => void;
+}
+
+export default class BlocklyStore implements IBlocklyStore {
+ root_store: RootStore;
+
+ constructor(root_store: RootStore) {
+ makeObservable(this, {
+ is_loading: observable,
+ active_tab: observable,
+ setActiveTab: action.bound,
+ setContainerSize: action.bound,
+ onMount: action.bound,
+ getCachedActiveTab: action.bound,
+ onUnmount: action.bound,
+ setLoading: action.bound,
+ });
+ this.root_store = root_store;
+ }
+
+ is_loading = false;
+
+ active_tab = tabs_title.WORKSPACE;
+
+ setActiveTab(tab: string): void {
+ this.active_tab = tab;
+ storeSetting('active_tab', this.active_tab);
+ }
+
+ setContainerSize(): void {
+ if (this.active_tab === tabs_title.WORKSPACE) {
+ onWorkspaceResize();
+ }
+ }
+
+ onMount(): void {
+ window.addEventListener('resize', this.setContainerSize);
+ }
+
+ getCachedActiveTab(): void {
+ if (getSetting('active_tab')) {
+ this.active_tab = getSetting('active_tab');
+ }
+ }
+
+ onUnmount(): void {
+ window.removeEventListener('resize', this.setContainerSize);
+ }
+
+ setLoading(is_loading: boolean): void {
+ this.is_loading = is_loading;
+ }
+}
diff --git a/packages/bot-web-ui/src/stores/chart-store.js b/packages/bot-web-ui/src/stores/chart-store.js
index 6176f5557cd5..522ce6ef6785 100644
--- a/packages/bot-web-ui/src/stores/chart-store.js
+++ b/packages/bot-web-ui/src/stores/chart-store.js
@@ -1,4 +1,4 @@
-import { action, computed, observable, reaction, makeObservable } from 'mobx';
+import { action, computed, makeObservable, observable, reaction } from 'mobx';
// import { tabs_title } from '../constants/bot-contents';
import { ServerTime } from '@deriv/bot-skeleton';
@@ -20,6 +20,11 @@ export default class ChartStore {
updateGranularity: action.bound,
updateChartType: action.bound,
setChartStatus: action.bound,
+ wsSubscribe: action.bound,
+ wsForget: action.bound,
+ wsForgetStream: action.bound,
+ wsSendRequest: action.bound,
+ getMarketsOrder: action.bound,
});
this.root_store = root_store;
diff --git a/packages/bot-web-ui/src/stores/connect.js b/packages/bot-web-ui/src/stores/connect.js
index 3dcec298c8a1..8e604f1b83d3 100644
--- a/packages/bot-web-ui/src/stores/connect.js
+++ b/packages/bot-web-ui/src/stores/connect.js
@@ -1,5 +1,5 @@
-import { useObserver } from 'mobx-react';
import React from 'react';
+import { useObserver } from 'mobx-react';
const isClassComponent = Component =>
!!(typeof Component === 'function' && Component.prototype && Component.prototype.isReactComponent);
diff --git a/packages/bot-web-ui/src/stores/dashboard-store.ts b/packages/bot-web-ui/src/stores/dashboard-store.ts
new file mode 100644
index 000000000000..baf272925d36
--- /dev/null
+++ b/packages/bot-web-ui/src/stores/dashboard-store.ts
@@ -0,0 +1,313 @@
+import { action, computed, makeObservable, observable, reaction } from 'mobx';
+import { blocksCoordinate, setColors } from '@deriv/bot-skeleton';
+import { isMobile } from '@deriv/shared';
+import { clearInjectionDiv } from 'Constants/load-modal';
+import { setTourSettings, tour_type, TTourType } from '../components/dashboard/joyride-config';
+import RootStore from './root-store';
+
+export interface IDashboardStore {
+ active_tab: number;
+ dialog_options: { [key: string]: string };
+ faq_search_value: string | null;
+ has_builder_token: string | number;
+ has_mobile_preview_loaded: boolean;
+ has_onboarding_token: string | number;
+ has_started_bot_builder_tour: boolean;
+ has_started_onboarding_tour: boolean;
+ has_tour_ended: boolean;
+ initInfoPanel: () => void;
+ is_dialog_open: boolean;
+ is_file_supported: boolean;
+ is_info_panel_visible: boolean;
+ is_preview_on_popup: boolean;
+ onCloseDialog: () => void;
+ onCloseTour: (param: Partial
) => void;
+ onTourEnd: (step: number, has_started_onboarding_tour: boolean) => void;
+ setActiveTab: (active_tab: number) => void;
+ setActiveTabTutorial: (active_tab_tutorials: number) => void;
+ setFAQSearchValue: (faq_search_value: string) => void;
+ setHasTourEnded: (has_tour_ended: boolean) => void;
+ setInfoPanelVisibility: (visibility: boolean) => void;
+ setIsFileSupported: (is_file_supported: boolean) => void;
+ setOnBoardTourRunState: (has_started_onboarding_tour: boolean) => void;
+ setOpenSettings: (toast_message: string, show_toast: boolean) => void;
+ setPreviewOnDialog: (has_mobile_preview_loaded: boolean) => void;
+ show_toast: boolean;
+ showVideoDialog: (param: { [key: string]: string }) => void;
+ strategy_save_type: string;
+ toast_message: string;
+}
+
+export default class DashboardStore implements IDashboardStore {
+ root_store: RootStore;
+
+ constructor(root_store: RootStore) {
+ makeObservable(this, {
+ active_tab_tutorials: observable,
+ active_tab: observable,
+ dialog_options: observable,
+ faq_search_value: observable,
+ getFileArray: observable,
+ has_builder_token: observable,
+ has_file_loaded: observable,
+ has_mobile_preview_loaded: observable,
+ has_onboarding_token: observable,
+ has_started_bot_builder_tour: observable,
+ has_started_onboarding_tour: observable,
+ has_tour_ended: observable,
+ has_tour_started: observable,
+ initInfoPanel: action.bound,
+ is_dialog_open: observable,
+ is_file_supported: observable,
+ is_info_panel_visible: observable,
+ is_preview_on_popup: observable,
+ is_tour_dialog_visible: observable,
+ is_dark_mode: computed,
+ onCloseDialog: action.bound,
+ onCloseTour: action.bound,
+ onTourEnd: action.bound,
+ setActiveTab: action.bound,
+ setActiveTabTutorial: action.bound,
+ setBotBuilderTokenCheck: action.bound,
+ setBotBuilderTourState: action.bound,
+ setFAQSearchValue: action.bound,
+ setFileLoaded: action.bound,
+ setHasTourEnded: action.bound,
+ setInfoPanelVisibility: action.bound,
+ setIsFileSupported: action.bound,
+ setOnBoardingTokenCheck: action.bound,
+ setOnBoardTourRunState: action.bound,
+ setPreviewOnDialog: action.bound,
+ setPreviewOnPopup: action.bound,
+ setTourActive: action.bound,
+ setTourDialogVisibility: action.bound,
+ setOpenSettings: action.bound,
+ show_toast: observable,
+ showVideoDialog: action.bound,
+ strategy_save_type: observable,
+ toggleOnConfirm: action.bound,
+ toast_message: observable,
+ setStrategySaveType: action.bound,
+ });
+ this.root_store = root_store;
+ const {
+ load_modal: { previewRecentStrategy, current_workspace_id },
+ } = this.root_store;
+
+ const refreshBotBuilderTheme = () => {
+ Blockly.derivWorkspace.asyncClear();
+ Blockly.Xml.domToWorkspace(
+ Blockly.Xml.textToDom(Blockly.derivWorkspace.strategy_to_load),
+ Blockly.derivWorkspace
+ );
+ };
+
+ reaction(
+ () => this.is_dark_mode,
+ () => {
+ setColors(this.is_dark_mode);
+ if (this.active_tab === 1) {
+ refreshBotBuilderTheme();
+ } else {
+ refreshBotBuilderTheme();
+ previewRecentStrategy(current_workspace_id);
+ }
+ }
+ );
+ reaction(
+ () => this.is_preview_on_popup,
+ async is_preview_on_popup => {
+ if (is_preview_on_popup) {
+ this.setPreviewOnPopup(false);
+ }
+ }
+ );
+ this.initInfoPanel();
+ }
+
+ active_tab = 0;
+ active_tab_tutorials = 0;
+ active_tour_step_number = 0;
+ dialog_options = {};
+ faq_search_value = null || '';
+ getFileArray = [];
+ has_builder_token = '';
+ has_file_loaded = false;
+ has_mobile_preview_loaded = false;
+ has_onboarding_token = '';
+ has_started_bot_builder_tour = false;
+ has_started_onboarding_tour = false;
+ has_tour_ended = false;
+ has_tour_started = false;
+ is_dialog_open = false;
+ is_file_supported = false;
+ is_info_panel_visible = false;
+ is_preview_on_popup = false;
+ is_tour_dialog_visible = false;
+ show_toast = false;
+ strategy_save_type = 'unsaved';
+ toast_message = '';
+
+ get is_dark_mode() {
+ const {
+ app: {
+ core: {
+ ui: { is_dark_mode_on },
+ },
+ },
+ } = this.root_store;
+ return is_dark_mode_on;
+ }
+
+ setOpenSettings = (toast_message: string, show_toast = true) => {
+ this.toast_message = toast_message;
+ this.show_toast = show_toast;
+ };
+
+ setIsFileSupported = (is_file_supported: boolean) => {
+ this.is_file_supported = is_file_supported;
+ };
+
+ initInfoPanel() {
+ if (!localStorage.getItem('dbot_should_show_info')) this.is_info_panel_visible = true;
+ }
+
+ setTourActiveStep = (active_tour_step_number: number) => {
+ this.active_tour_step_number = active_tour_step_number;
+ };
+
+ setPreviewOnDialog = (has_mobile_preview_loaded: boolean) => {
+ this.has_mobile_preview_loaded = has_mobile_preview_loaded;
+ const {
+ load_modal: { onLoadModalClose },
+ } = this.root_store;
+ onLoadModalClose();
+ };
+
+ setStrategySaveType = (strategy_save_type: string) => {
+ this.strategy_save_type = strategy_save_type;
+ };
+
+ setHasTourEnded = (has_tour_ended: boolean): void => {
+ this.has_tour_ended = has_tour_ended;
+ };
+
+ setBotBuilderTokenCheck = (has_builder_token: string | number): void => {
+ this.has_builder_token = has_builder_token;
+ };
+
+ setOnBoardingTokenCheck = (has_onboarding_token: string | number): void => {
+ this.has_onboarding_token = has_onboarding_token;
+ };
+
+ setBotBuilderTourState = (has_started_bot_builder_tour: boolean): void => {
+ this.has_started_bot_builder_tour = has_started_bot_builder_tour;
+ };
+
+ setPreviewOnPopup = (is_preview_on_popup: boolean): void => {
+ this.is_preview_on_popup = is_preview_on_popup;
+ };
+
+ setOnBoardTourRunState = (has_started_onboarding_tour: boolean): void => {
+ this.has_started_onboarding_tour = has_started_onboarding_tour;
+ };
+
+ setTourDialogVisibility = (is_tour_dialog_visible: boolean): void => {
+ this.is_tour_dialog_visible = is_tour_dialog_visible;
+ };
+
+ setTourActive = (has_tour_started: boolean): void => {
+ this.has_tour_started = has_tour_started;
+ };
+
+ setFileLoaded = (has_file_loaded: boolean): void => {
+ this.has_file_loaded = has_file_loaded;
+ clearInjectionDiv('store', document.getElementById('load-strategy__blockly-container'));
+ };
+
+ onCloseDialog = (): void => {
+ this.is_dialog_open = false;
+ };
+
+ setActiveTab = (active_tab: number): void => {
+ this.active_tab = active_tab;
+ if (this.has_started_bot_builder_tour) {
+ this.setTourActive(false);
+ this.setBotBuilderTourState(false);
+ }
+ if (this.active_tab === 1) {
+ blocksCoordinate();
+ }
+ };
+
+ setActiveTabTutorial = (active_tab_tutorials: number): void => {
+ this.active_tab_tutorials = active_tab_tutorials;
+ };
+
+ setFAQSearchValue = (faq_search_value: string): void => {
+ this.faq_search_value = faq_search_value;
+ };
+
+ showVideoDialog = (param: { [key: string]: string }): void => {
+ const { url, type } = param;
+ const dialog_type = ['google', 'url'];
+ if (dialog_type.includes(type)) {
+ if (type === 'url') {
+ this.dialog_options = {
+ url,
+ };
+ }
+ this.is_dialog_open = true;
+ } else {
+ this.is_dialog_open = false;
+ }
+ };
+
+ setInfoPanelVisibility = (is_info_panel_visible: boolean): void => {
+ this.is_info_panel_visible = is_info_panel_visible;
+ };
+
+ onZoomInOutClick = (is_zoom_in: boolean): void => {
+ const workspace = Blockly.mainWorkspace;
+ const metrics = workspace.getMetrics();
+ const addition = is_zoom_in ? 1 : -1;
+
+ workspace.zoom(metrics.viewWidth / 2, metrics.viewHeight / 2, addition);
+ };
+
+ toggleOnConfirm = (active_tab: number, value: boolean): void => {
+ if (active_tab === 0) {
+ this.setTourActive(value);
+ this.setOnBoardTourRunState(value);
+ } else {
+ this.setBotBuilderTourState(value);
+ }
+ this.setHasTourEnded(value);
+ };
+
+ onCloseTour = (): void => {
+ this.setOnBoardTourRunState(false);
+ this.setBotBuilderTourState(false);
+ setTourSettings(new Date().getTime(), `${tour_type.key}_token`);
+ this.setTourActive(false);
+ };
+ setTourEnd = (param: TTourType): void => {
+ const { key } = param;
+ this.setHasTourEnded(true);
+ if (!isMobile()) this.setTourDialogVisibility(true);
+ this.setTourActive(false);
+ setTourSettings(new Date().getTime(), `${key}_token`);
+ };
+
+ onTourEnd = (step: number, has_started_onboarding_tour: boolean): void => {
+ if (step === 7) {
+ this.onCloseTour();
+ this.setTourEnd(tour_type);
+ this.setTourDialogVisibility(true);
+ }
+ if (!has_started_onboarding_tour && step === 3) {
+ this.onCloseTour();
+ this.setTourEnd(tour_type);
+ }
+ };
+}
diff --git a/packages/bot-web-ui/src/stores/data-collection-store.js b/packages/bot-web-ui/src/stores/data-collection-store.js
index 979a05aac964..2aae1d51089c 100644
--- a/packages/bot-web-ui/src/stores/data-collection-store.js
+++ b/packages/bot-web-ui/src/stores/data-collection-store.js
@@ -1,10 +1,29 @@
-import { reaction } from 'mobx';
import crc32 from 'crc-32/crc32';
-import { isProduction, cloneObject } from '@deriv/shared';
+import { action, makeObservable, observable, reaction } from 'mobx';
import { DBot } from '@deriv/bot-skeleton';
+import { cloneObject, isProduction } from '@deriv/shared';
export default class DataCollectionStore {
- constructor(root_store) {
+ constructor(root_store, core) {
+ makeObservable(this, {
+ IS_PENDING: observable,
+ IS_PROCESSED: observable,
+ endpoint: observable,
+ run_id: observable,
+ run_start: observable,
+ should_post_xml: observable,
+ strategy_content: observable,
+ transaction_ids: observable,
+ trackRun: action.bound,
+ trackTransaction: action.bound,
+ setRunId: action.bound,
+ setRunStart: action.bound,
+ setStrategyContent: action.bound,
+ cleanXmlDom: action.bound,
+ getHash: action.bound,
+ });
+ this.root_store = root_store;
+ this.core = core;
if (isProduction() || /(.*?)\.binary.sx$/.test(window.location.hostname)) {
this.root_store = root_store;
@@ -42,8 +61,8 @@ export default class DataCollectionStore {
this.setStrategyContent(xml_string);
}
- this.setRunId(this.getHash(xml_hash + this.root_store.core.client.loginid + Math.random()));
- this.setRunStart(this.root_store.common.server_time.unix());
+ this.setRunId(this.getHash(xml_hash + this.core.client.loginid + Math.random()));
+ this.setRunStart(this.core.common.server_time.unix());
}
async trackTransaction(contracts) {
diff --git a/packages/bot-web-ui/src/stores/download-store.js b/packages/bot-web-ui/src/stores/download-store.js
index 510f7bbc1d1e..1b87f8090a74 100644
--- a/packages/bot-web-ui/src/stores/download-store.js
+++ b/packages/bot-web-ui/src/stores/download-store.js
@@ -1,12 +1,13 @@
import { action, makeObservable } from 'mobx';
-import { localize } from '@deriv/translations';
import { log_types } from '@deriv/bot-skeleton';
+import { localize } from '@deriv/translations';
export default class DownloadStore {
constructor(root_store) {
makeObservable(this, {
onClickDownloadTransaction: action.bound,
onClickDownloadJournal: action.bound,
+ getSuccessJournalMessage: action.bound,
});
this.root_store = root_store;
diff --git a/packages/bot-web-ui/src/stores/flyout-help-store.js b/packages/bot-web-ui/src/stores/flyout-help-store.js
index 19a29a8d135d..19187fc2b844 100644
--- a/packages/bot-web-ui/src/stores/flyout-help-store.js
+++ b/packages/bot-web-ui/src/stores/flyout-help-store.js
@@ -1,4 +1,4 @@
-import { observable, action, runInAction, makeObservable } from 'mobx';
+import { action, makeObservable, observable, runInAction } from 'mobx';
import { config } from '@deriv/bot-skeleton';
import { help_content_config } from 'Utils/help-content/help-content.config';
import * as help_strings from 'Utils/help-content/help-strings';
@@ -21,6 +21,9 @@ export default class FlyoutHelpStore {
initFlyoutHelp: action.bound,
updateSequenceButtons: action.bound,
setExamples: action.bound,
+ getHelpContent: action.bound,
+ getFilledBlocksIndex: action.bound,
+ getNextHelpContentIndex: action.bound,
});
this.root_store = root_store;
diff --git a/packages/bot-web-ui/src/stores/flyout-store.js b/packages/bot-web-ui/src/stores/flyout-store.js
index 9dd59a541851..8e3808335ff7 100644
--- a/packages/bot-web-ui/src/stores/flyout-store.js
+++ b/packages/bot-web-ui/src/stores/flyout-store.js
@@ -1,5 +1,5 @@
/* eslint-disable no-underscore-dangle */
-import { observable, action, computed, makeObservable } from 'mobx';
+import { action, computed, makeObservable, observable } from 'mobx';
import { config } from '@deriv/bot-skeleton';
import GTM from 'Utils/gtm';
diff --git a/packages/bot-web-ui/src/stores/google-drive-store.js b/packages/bot-web-ui/src/stores/google-drive-store.js
index 22cdde7f3d02..b1a6ea018946 100644
--- a/packages/bot-web-ui/src/stores/google-drive-store.js
+++ b/packages/bot-web-ui/src/stores/google-drive-store.js
@@ -1,6 +1,6 @@
-import { observable, action, makeObservable } from 'mobx';
-import { localize, getLanguage } from '@deriv/translations';
-import { importExternal, config } from '@deriv/bot-skeleton';
+import { action, makeObservable, observable } from 'mobx';
+import { config, importExternal } from '@deriv/bot-skeleton';
+import { getLanguage, localize } from '@deriv/translations';
import { button_status } from 'Constants/button-status';
export default class GoogleDriveStore {
@@ -10,6 +10,15 @@ export default class GoogleDriveStore {
updateSigninStatus: action.bound,
saveFile: action.bound,
loadFile: action.bound,
+ setKey: action.bound,
+ initialise: action.bound,
+ signIn: action.bound,
+ signOut: action.bound,
+ getPickerLanguage: action.bound,
+ checkFolderExists: action.bound,
+ createSaveFilePicker: action.bound,
+ createLoadFilePicker: action.bound,
+ showGoogleDriveFilePicker: action.bound,
});
this.root_store = root_store;
@@ -25,10 +34,10 @@ export default class GoogleDriveStore {
is_authorised = !!localStorage.getItem('google_access_token');
setKey = () => {
- const { aid, cid, api, scope, discovery_docs } = config.gd;
- this.client_id = cid;
- this.app_id = aid;
- this.api_key = api;
+ const { scope, discovery_docs } = config.gd;
+ this.client_id = process.env.GD_CLIENT_ID;
+ this.app_id = process.env.GD_APP_ID;
+ this.api_key = process.env.GD_API_KEY;
this.scope = scope;
this.discovery_docs = discovery_docs;
};
diff --git a/packages/bot-web-ui/src/stores/index.js b/packages/bot-web-ui/src/stores/index.js
deleted file mode 100644
index b521ccc6d327..000000000000
--- a/packages/bot-web-ui/src/stores/index.js
+++ /dev/null
@@ -1,56 +0,0 @@
-import ChartStore from './chart-store';
-import DownloadStore from './download-store';
-import FlyoutStore from './flyout-store';
-import FlyoutHelpStore from './flyout-help-store';
-import GoogleDriveStore from './google-drive-store';
-import JournalStore from './journal-store';
-import LoadModalStore from './load-modal-store';
-import RunPanelStore from './run-panel-store';
-import SaveModalStore from './save-modal-store';
-import SummaryStore from './summary-store';
-import SummaryCardStore from './summary-card-store';
-import ToolbarStore from './toolbar-store';
-import TransactionsStore from './transactions-store';
-import QuickStrategyStore from './quick-strategy-store';
-import MainContentStore from './main-content-store';
-import RoutePromptDialogStore from './route-prompt-dialog-store';
-import DataCollectionStore from './data-collection-store';
-import BlocklyStore from './blockly-store';
-import SelfExclusionStore from './self-exclusion-store';
-import ToolboxStore from './toolbox-store';
-import AppStore from './app-store';
-
-export default class RootStore {
- constructor(core, ws, dbot) {
- this.core = core;
- this.ui = core.ui;
- this.common = core.common;
- this.notifications = core.notifications;
- this.ws = ws;
- this.dbot = dbot;
- this.server_time = core.common.server_time;
- this.app = new AppStore(this);
- this.summary_card = new SummaryCardStore(this);
- this.blockly_store = new BlocklyStore(this);
- this.download = new DownloadStore(this);
- this.flyout = new FlyoutStore(this);
- this.flyout_help = new FlyoutHelpStore(this);
- this.google_drive = new GoogleDriveStore(this);
- this.journal = new JournalStore(this);
- this.load_modal = new LoadModalStore(this);
- this.run_panel = new RunPanelStore(this);
- this.save_modal = new SaveModalStore(this);
- this.summary = new SummaryStore(this);
- this.transactions = new TransactionsStore(this);
- this.toolbar = new ToolbarStore(this);
- this.toolbox = new ToolboxStore(this);
- this.quick_strategy = new QuickStrategyStore(this);
- this.route_prompt_dialog = new RoutePromptDialogStore(this);
- this.self_exclusion = new SelfExclusionStore(this);
-
- // need to be at last for dependency
- this.chart_store = new ChartStore(this);
- this.main_content = new MainContentStore(this);
- this.data_collection_store = new DataCollectionStore(this);
- }
-}
diff --git a/packages/bot-web-ui/src/stores/index.ts b/packages/bot-web-ui/src/stores/index.ts
new file mode 100644
index 000000000000..42e67c4a2593
--- /dev/null
+++ b/packages/bot-web-ui/src/stores/index.ts
@@ -0,0 +1,3 @@
+import RootStore from './root-store';
+
+export default RootStore;
diff --git a/packages/bot-web-ui/src/stores/journal-store.js b/packages/bot-web-ui/src/stores/journal-store.js
index 219962025be4..70814f364f2e 100644
--- a/packages/bot-web-ui/src/stores/journal-store.js
+++ b/packages/bot-web-ui/src/stores/journal-store.js
@@ -1,17 +1,18 @@
-import { observable, action, computed, reaction, when, makeObservable } from 'mobx';
-import { localize } from '@deriv/translations';
-import { formatDate } from '@deriv/shared';
+import { action, computed, makeObservable, observable, reaction, when } from 'mobx';
import { log_types, message_types } from '@deriv/bot-skeleton';
import { config } from '@deriv/bot-skeleton/src/constants/config';
-import { storeSetting, getSetting } from '../utils/settings';
+import { formatDate } from '@deriv/shared';
+import { localize } from '@deriv/translations';
import { isCustomJournalMessage } from '../utils/journal-notifications';
import { getStoredItemsByKey, getStoredItemsByUser, setStoredItemsByKey } from '../utils/session-storage';
+import { getSetting, storeSetting } from '../utils/settings';
export default class JournalStore {
- constructor(root_store) {
+ constructor(root_store, core) {
makeObservable(this, {
is_filter_dialog_visible: observable,
journal_filters: observable.shallow,
+ filters: observable.shallow,
unfiltered_messages: observable.shallow,
toggleFilterDialog: action.bound,
onLogSuccess: action.bound,
@@ -19,12 +20,17 @@ export default class JournalStore {
onNotify: action.bound,
pushMessage: action.bound,
filtered_messages: computed,
+ getServerTime: action.bound,
+ playAudio: action.bound,
checked_filters: computed,
filterMessage: action.bound,
clear: action.bound,
+ welcomeBackUser: action.bound,
+ registerReactions: action.bound,
});
this.root_store = root_store;
+ this.core = core;
this.disposeReactionsFn = this.registerReactions();
// Add a "Welcome back!" message when messages were restored.
@@ -49,10 +55,10 @@ export default class JournalStore {
];
journal_filters = getSetting('journal_filter') || this.filters.map(filter => filter.id);
- unfiltered_messages = getStoredItemsByUser(this.JOURNAL_CACHE, this.root_store?.core.client.loginid, []);
+ unfiltered_messages = getStoredItemsByUser(this.JOURNAL_CACHE, this.core?.client.loginid, []);
getServerTime() {
- return this.root_store?.core.common.server_time.get();
+ return this.core?.common.server_time.get();
}
playAudio = sound => {
@@ -138,7 +144,7 @@ export default class JournalStore {
}
registerReactions() {
- const { client } = this.root_store?.core;
+ const { client } = this.core;
// Write journal messages to session storage on each change in unfiltered messages.
const disposeWriteJournalMessageListener = reaction(
diff --git a/packages/bot-web-ui/src/stores/load-modal-store.js b/packages/bot-web-ui/src/stores/load-modal-store.js
deleted file mode 100644
index af1431d14901..000000000000
--- a/packages/bot-web-ui/src/stores/load-modal-store.js
+++ /dev/null
@@ -1,338 +0,0 @@
-import { observable, action, computed, reaction, makeObservable } from 'mobx';
-import { localize } from '@deriv/translations';
-import { load, config, save_types, getSavedWorkspaces, removeExistingWorkspace } from '@deriv/bot-skeleton';
-import { tabs_title } from 'Constants/load-modal';
-
-export default class LoadModalStore {
- constructor(root_store) {
- makeObservable(this, {
- active_index: observable,
- is_load_modal_open: observable,
- is_explanation_expand: observable,
- is_open_button_loading: observable,
- loaded_local_file: observable,
- recent_strategies: observable,
- selected_strategy_id: observable,
- preview_workspace: computed,
- selected_strategy: computed,
- tab_name: computed,
- handleFileChange: action.bound,
- loadFileFromRecent: action.bound,
- loadFileFromLocal: action.bound,
- onActiveIndexChange: action.bound,
- onDriveConnect: action.bound,
- onDriveOpen: action.bound,
- onEntered: action.bound,
- onLoadModalClose: action.bound,
- onZoomInOutClick: action.bound,
- previewRecentStrategy: action.bound,
- setActiveTabIndex: action.bound,
- setLoadedLocalFile: action.bound,
- setRecentStrategies: action.bound,
- setSelectedStrategyId: action.bound,
- toggleExplanationExpand: action.bound,
- toggleLoadModal: action.bound,
- });
-
- this.root_store = root_store;
-
- reaction(
- () => this.active_index,
- () => this.onActiveIndexChange()
- );
- reaction(
- () => this.is_load_modal_open,
- async is_load_modal_open => {
- if (is_load_modal_open) {
- this.setRecentStrategies((await getSavedWorkspaces()) || []);
- } else {
- this.onLoadModalClose();
- }
- }
- );
- }
-
- recent_workspace;
- local_workspace;
- drop_zone;
-
- active_index = 0;
- is_load_modal_open = false;
- is_explanation_expand = false;
- is_open_button_loading = false;
- loaded_local_file = null;
- recent_strategies = [];
- selected_strategy_id = undefined;
-
- get preview_workspace() {
- if (this.tab_name === tabs_title.TAB_LOCAL) return this.local_workspace;
- if (this.tab_name === tabs_title.TAB_RECENT) return this.recent_workspace;
- return null;
- }
-
- get selected_strategy() {
- if (this.recent_strategies.length > 0) {
- return this.recent_strategies.find(ws => ws.id === this.selected_strategy_id) || this.recent_strategies[0];
- }
-
- return null;
- }
-
- get tab_name() {
- if (this.root_store.ui.is_mobile) {
- if (this.active_index === 0) return tabs_title.TAB_LOCAL;
- if (this.active_index === 1) return tabs_title.TAB_GOOGLE;
- }
- if (this.active_index === 0) return tabs_title.TAB_RECENT;
- if (this.active_index === 1) return tabs_title.TAB_LOCAL;
- if (this.active_index === 2) return tabs_title.TAB_GOOGLE;
- return '';
- }
-
- handleFileChange(event, is_body = true) {
- let files;
- if (event.type === 'drop') {
- event.stopPropagation();
- event.preventDefault();
-
- ({ files } = event.dataTransfer);
- } else {
- ({ files } = event.target);
- }
-
- files = Array.from(files);
-
- if (!is_body) {
- if (files[0].name.includes('xml')) {
- this.setLoadedLocalFile(files[0]);
- } else {
- return false;
- }
- }
- this.readFile(!is_body, event, files[0]);
- event.target.value = '';
- return true;
- }
-
- loadFileFromRecent() {
- this.is_open_button_loading = true;
-
- if (!this.selected_strategy) {
- this.is_open_button_loading = false;
- return;
- }
-
- removeExistingWorkspace(this.selected_strategy.id);
- load({
- block_string: this.selected_strategy.xml,
- strategy_id: this.selected_strategy.id,
- file_name: this.selected_strategy.name,
- workspace: Blockly.derivWorkspace,
- });
- this.is_open_button_loading = false;
- this.toggleLoadModal();
- }
-
- loadFileFromLocal() {
- this.is_open_button_loading = true;
- this.readFile(false, {}, this.loaded_local_file);
- this.is_open_button_loading = false;
- this.toggleLoadModal();
- }
-
- onActiveIndexChange() {
- if (this.tab_name === tabs_title.TAB_RECENT) {
- if (this.selected_strategy) {
- this.previewRecentStrategy(this.selected_strategy_id);
- }
- } else {
- // eslint-disable-next-line no-lonely-if
- if (this.recent_workspace) {
- setTimeout(() => {
- // Dispose of recent workspace when switching away from Recent tab.
- // Process in next cycle so user doesn't have to wait.
- this.recent_workspace.dispose();
- this.recent_workspace = null;
- });
- }
- }
-
- if (this.tab_name === tabs_title.TAB_LOCAL) {
- if (!this.drop_zone) {
- this.drop_zone = document.querySelector('load-strategy__local-dropzone-area');
-
- if (this.drop_zone) {
- this.drop_zone.addEventListener('drop', event => this.handleFileChange(event, false));
- }
- }
- } else {
- // Dispose of local workspace when switching away from Local tab.
- // eslint-disable-next-line no-lonely-if
- if (this.local_workspace) {
- setTimeout(() => {
- this.local_workspace.dispose();
- this.local_workspace = null;
- this.setLoadedLocalFile(null);
- });
- }
- }
-
- // Forget about drop zone when not on Local tab.
- if (this.tab_name !== tabs_title.TAB_LOCAL && this.drop_zone) {
- this.drop_zone.removeEventListener('drop', event => this.handleFileChange(event, false));
- }
- }
-
- async onDriveConnect() {
- const { google_drive } = this.root_store;
-
- if (google_drive.is_authorised) {
- google_drive.signOut();
- } else {
- google_drive.signIn();
- }
- }
-
- async onDriveOpen() {
- const { loadFile } = this.root_store.google_drive;
- const { xml_doc, file_name } = await loadFile();
- load({ block_string: xml_doc, file_name, workspace: Blockly.derivWorkspace, from: save_types.GOOGLE_DRIVE });
- this.toggleLoadModal();
- }
-
- onEntered() {
- if (this.tab_name === tabs_title.TAB_RECENT && this.selected_strategy) {
- this.previewRecentStrategy(this.selected_strategy.id);
- }
- }
-
- onLoadModalClose() {
- if (this.recent_workspace) {
- this.recent_workspace.dispose();
- this.recent_workspace = null;
- }
- if (this.local_workspace) {
- this.local_workspace.dispose();
- this.local_workspace = null;
- }
-
- this.setActiveTabIndex(0); // Reset to first tab.
- this.setLoadedLocalFile(null);
- }
-
- onZoomInOutClick(is_zoom_in) {
- if (this.preview_workspace) {
- this.preview_workspace.zoomCenter(is_zoom_in ? 1 : -1);
- }
- }
-
- previewRecentStrategy(workspace_id) {
- this.setSelectedStrategyId(workspace_id);
-
- if (!this.selected_strategy) {
- return;
- }
-
- if (!this.recent_workspace || !this.recent_workspace.rendered) {
- const ref = document.getElementById('load-strategy__blockly-container');
-
- if (!ref) {
- // eslint-disable-next-line no-console
- console.warn('Could not find preview workspace element.');
- return;
- }
-
- this.recent_workspace = Blockly.inject(ref, {
- media: `${__webpack_public_path__}media/`,
- zoom: {
- wheel: true,
- startScale: config.workspaces.previewWorkspaceStartScale,
- },
- readOnly: true,
- scrollbars: true,
- });
- }
-
- load({ block_string: this.selected_strategy.xml, drop_event: {}, workspace: this.recent_workspace });
- }
-
- setActiveTabIndex(index) {
- this.active_index = index;
- }
-
- setLoadedLocalFile(loaded_local_file) {
- this.loaded_local_file = loaded_local_file;
- }
-
- setRecentStrategies(recent_strategies) {
- this.recent_strategies = recent_strategies;
- }
-
- setSelectedStrategyId(selected_strategy_id) {
- this.selected_strategy_id = selected_strategy_id;
- }
-
- toggleExplanationExpand() {
- this.is_explanation_expand = !this.is_explanation_expand;
- }
-
- toggleLoadModal() {
- this.is_load_modal_open = !this.is_load_modal_open;
- }
-
- getRecentFileIcon = save_type => {
- switch (save_type) {
- case save_types.UNSAVED:
- return 'IcReports';
- case save_types.LOCAL:
- return 'IcDesktop';
- case save_types.GOOGLE_DRIVE:
- return 'IcGoogleDrive';
- default:
- return 'IcReports';
- }
- };
-
- getSaveType = save_type => {
- switch (save_type) {
- case save_types.UNSAVED:
- return localize('Unsaved');
- case save_types.LOCAL:
- return localize('Local');
- case save_types.GOOGLE_DRIVE:
- return localize('Google Drive');
- default:
- return localize('Unsaved');
- }
- };
-
- readFile = (is_preview, drop_event, file) => {
- const file_name = file && file.name.replace(/\.[^/.]+$/, '');
- const reader = new FileReader();
-
- reader.onload = action(e => {
- const load_options = { block_string: e.target.result, drop_event, from: save_types.LOCAL };
-
- if (is_preview) {
- const ref = document.getElementById('load-strategy__blockly-container');
-
- this.local_workspace = Blockly.inject(ref, {
- media: `${__webpack_public_path__}media/`, // eslint-disable-line
- zoom: {
- wheel: false,
- startScale: config.workspaces.previewWorkspaceStartScale,
- },
- readOnly: true,
- scrollbars: true,
- });
- load_options.workspace = this.local_workspace;
- } else {
- load_options.workspace = Blockly.derivWorkspace;
- load_options.file_name = file_name;
- }
-
- load(load_options);
- });
- reader.readAsText(file);
- };
-}
diff --git a/packages/bot-web-ui/src/stores/load-modal-store.ts b/packages/bot-web-ui/src/stores/load-modal-store.ts
new file mode 100644
index 000000000000..0ed63d706c65
--- /dev/null
+++ b/packages/bot-web-ui/src/stores/load-modal-store.ts
@@ -0,0 +1,480 @@
+import React from 'react';
+import { action, autorun, computed, makeObservable, observable, reaction } from 'mobx';
+import {
+ config,
+ getSavedWorkspaces,
+ load,
+ observer as globalObserver,
+ removeExistingWorkspace,
+ save_types,
+ setColors,
+} from '@deriv/bot-skeleton';
+import { isMobile } from '@deriv/shared';
+import { localize } from '@deriv/translations';
+import { clearInjectionDiv, tabs_title } from 'Constants/load-modal';
+import RootStore from './root-store';
+
+export type TWorkspace = {
+ id: string;
+ xml: string;
+ name: string;
+ timestamp: number;
+ save_type: string;
+};
+
+interface ILoadModalStore {
+ active_index: number;
+ is_load_modal_open: boolean;
+ is_explanation_expand: boolean;
+ is_open_button_loading: boolean;
+ is_strategy_loaded: boolean;
+ loaded_local_file: boolean | null;
+ recent_strategies: string[];
+ dashboard_strategies: Array;
+ selected_strategy_id: string[] | string | undefined;
+ is_strategy_removed: boolean;
+ is_delete_modal_open: boolean;
+ current_workspace_id: string;
+ getSelectedStrategyID: (current_workspace_id: string) => void;
+ refreshStrategies: () => void;
+ refreshStrategiesTheme: () => void;
+ preview_workspace: () => void;
+ handleFileChange: (
+ event: React.MouseEvent | React.FormEvent | DragEvent,
+ is_body: boolean
+ ) => boolean;
+ loadFileFromLocal: () => void;
+ onActiveIndexChange: () => void;
+ onDriveConnect: () => void;
+ onDriveOpen: () => void;
+ onEntered: () => void;
+ onLoadModalClose: () => void;
+ onToggleDeleteDialog: (is_delete_modal_open: boolean) => void;
+ onZoomInOutClick: (is_zoom_in: string) => void;
+ previewRecentStrategy: (workspace_id: string) => void;
+ setActiveTabIndex: (index: number) => void;
+ setLoadedLocalFile: (loaded_local_file: boolean | null) => void;
+ setRecentStrategies: (recent_strategies: string[]) => void;
+ setSelectedStrategyId: (selected_strategy_id: string[] | undefined) => void;
+ toggleExplanationExpand: () => void;
+ toggleLoadModal: () => void;
+ toggleTourLoadModal: (toggle: boolean) => void;
+ readFile: (is_preview: boolean, drop_event: DragEvent, file: File) => void;
+ updateListStrategies: (workspaces: Array) => void;
+ getRecentFileIcon: (save_type: { [key: string]: string } | string) => string;
+ getSaveType: (save_type: { [key: string]: string } | string) => string;
+}
+
+export default class LoadModalStore implements ILoadModalStore {
+ root_store: RootStore;
+
+ constructor(root_store: RootStore) {
+ makeObservable(this, {
+ active_index: observable,
+ is_load_modal_open: observable,
+ is_explanation_expand: observable,
+ is_open_button_loading: observable,
+ is_strategy_loaded: observable,
+ is_delete_modal_open: observable,
+ is_strategy_removed: observable,
+ loaded_local_file: observable,
+ recent_strategies: observable,
+ dashboard_strategies: observable,
+ selected_strategy_id: observable,
+ current_workspace_id: observable,
+ preview_workspace: computed,
+ selected_strategy: computed,
+ tab_name: computed,
+ getSelectedStrategyID: action.bound,
+ refreshStrategies: action.bound,
+ refreshStrategiesTheme: action.bound,
+ handleFileChange: action.bound,
+ loadFileFromRecent: action.bound,
+ loadFileFromLocal: action.bound,
+ onActiveIndexChange: action.bound,
+ onDriveConnect: action.bound,
+ onDriveOpen: action.bound,
+ onEntered: action.bound,
+ onLoadModalClose: action.bound,
+ onZoomInOutClick: action.bound,
+ previewRecentStrategy: action.bound,
+ setActiveTabIndex: action.bound,
+ setLoadedLocalFile: action.bound,
+ setRecentStrategies: action.bound,
+ setSelectedStrategyId: action.bound,
+ toggleExplanationExpand: action.bound,
+ toggleLoadModal: action.bound,
+ toggleTourLoadModal: action.bound,
+ readFile: action.bound,
+ setDashboardStrategies: action.bound,
+ updateListStrategies: action.bound,
+ });
+
+ this.root_store = root_store;
+
+ reaction(
+ () => this.active_index,
+ () => this.onActiveIndexChange()
+ );
+ reaction(
+ () => this.is_load_modal_open,
+ async is_load_modal_open => {
+ if (is_load_modal_open) {
+ this.setRecentStrategies((await getSavedWorkspaces()) || []);
+ } else {
+ this.onLoadModalClose();
+ }
+ }
+ );
+ }
+
+ recent_workspace;
+ local_workspace;
+ drop_zone;
+
+ active_index = 0;
+ is_load_modal_open = false;
+ is_explanation_expand = false;
+ is_open_button_loading = false;
+ loaded_local_file = null;
+ recent_strategies = [];
+ dashboard_strategies = [];
+ selected_strategy_id = undefined;
+ is_strategy_loaded = false;
+ is_delete_modal_open = false;
+ is_strategy_removed = false;
+ current_workspace_id = '';
+
+ get preview_workspace() {
+ if (this.tab_name === tabs_title.TAB_LOCAL) return this.local_workspace;
+ if (this.tab_name === tabs_title.TAB_RECENT) return this.recent_workspace;
+ return null;
+ }
+
+ get selected_strategy() {
+ return (
+ this.dashboard_strategies.find(ws => ws.id === this.selected_strategy_id) || this.dashboard_strategies[0]
+ );
+ }
+
+ get tab_name() {
+ if (isMobile()) {
+ if (this.active_index === 0) return tabs_title.TAB_LOCAL;
+ if (this.active_index === 1) return tabs_title.TAB_GOOGLE;
+ }
+ if (this.active_index === 0) return tabs_title.TAB_RECENT;
+ if (this.active_index === 1) return tabs_title.TAB_LOCAL;
+ if (this.active_index === 2) return tabs_title.TAB_GOOGLE;
+ return '';
+ }
+
+ getSelectedStrategyID = (current_workspace_id: string) => {
+ this.current_workspace_id = current_workspace_id;
+ };
+
+ setDashboardStrategies(strategies: Array) {
+ this.dashboard_strategies = strategies;
+ if (!strategies.length) {
+ this.selected_strategy_id = undefined;
+ }
+ }
+
+ async getDashboardStrategies() {
+ const recent_strategies = await getSavedWorkspaces();
+ this.dashboard_strategies = recent_strategies;
+ }
+
+ handleFileChange = (
+ event: React.MouseEvent | React.FormEvent | DragEvent,
+ is_body = true
+ ): boolean => {
+ let files;
+ if (event.type === 'drop') {
+ event.stopPropagation();
+ event.preventDefault();
+
+ ({ files } = event.dataTransfer);
+ } else {
+ ({ files } = event.target);
+ }
+
+ const [file] = files;
+
+ if (!is_body) {
+ if (file.name.includes('xml')) {
+ this.setLoadedLocalFile(file);
+ this.getDashboardStrategies();
+ } else {
+ return false;
+ }
+ }
+ this.readFile(!is_body, event, file);
+ event.target.value = '';
+ return true;
+ };
+ refreshStrategiesTheme = (strategy = this.selected_strategy?.xml): void => {
+ if (strategy) load({ block_string: strategy, drop_event: {}, workspace: this.recent_workspace });
+ };
+ loadFileFromRecent = async (): void => {
+ this.is_open_button_loading = true;
+ if (!this.selected_strategy) {
+ Blockly.derivWorkspace.asyncClear();
+ Blockly.Xml.domToWorkspace(
+ Blockly.Xml.textToDom(Blockly.derivWorkspace.strategy_to_load),
+ Blockly.derivWorkspace
+ );
+ this.is_open_button_loading = false;
+ return;
+ }
+
+ removeExistingWorkspace(this.selected_strategy.id);
+ load({
+ block_string: this.selected_strategy.xml,
+ strategy_id: this.selected_strategy.id,
+ file_name: this.selected_strategy.name,
+ workspace: Blockly.derivWorkspace,
+ from: this.selected_strategy.save_type,
+ });
+ const recent_files = await getSavedWorkspaces();
+ recent_files.map(strategy => {
+ const { xml, id } = strategy;
+ if (this.selected_strategy.id === id) {
+ Blockly.derivWorkspace.strategy_to_load = xml;
+ }
+ });
+ this.is_open_button_loading = false;
+ };
+
+ loadFileFromLocal = (): void => {
+ this.is_open_button_loading = true;
+ this.readFile(false, {}, this.loaded_local_file);
+ this.is_open_button_loading = false;
+ };
+
+ onActiveIndexChange = (): void => {
+ if (this.tab_name === tabs_title.TAB_RECENT) {
+ this.previewRecentStrategy(this.selected_strategy_id);
+ } else {
+ // eslint-disable-next-line no-lonely-if
+ if (this.recent_workspace) {
+ setTimeout(() => {
+ // Dispose of recent workspace when switching away from Recent tab.
+ // Process in next cycle so user doesn't have to wait.
+ this.recent_workspace.dispose();
+ this.recent_workspace = null;
+ });
+ }
+ }
+
+ if (this.tab_name === tabs_title.TAB_LOCAL) {
+ if (!this.drop_zone) {
+ this.drop_zone = document.querySelector('load-strategy__local-dropzone-area');
+
+ if (this.drop_zone) {
+ this.drop_zone.addEventListener('drop', event => this.handleFileChange(event, false));
+ }
+ }
+ } else {
+ // Dispose of local workspace when switching away from Local tab.
+ // eslint-disable-next-line no-lonely-if
+ if (this.local_workspace) {
+ setTimeout(() => {
+ this.local_workspace.dispose();
+ this.local_workspace = null;
+ this.setLoadedLocalFile(null);
+ }, 0);
+ }
+ }
+
+ // Forget about drop zone when not on Local tab.
+ if (this.tab_name !== tabs_title.TAB_LOCAL && this.drop_zone) {
+ this.drop_zone.removeEventListener('drop', event => this.handleFileChange(event, false));
+ }
+ };
+
+ onDriveConnect = (): void => {
+ const { google_drive } = this.root_store;
+
+ if (google_drive.is_authorised) {
+ google_drive.signOut();
+ } else {
+ google_drive.signIn();
+ }
+ };
+
+ async onDriveOpen() {
+ const { loadFile } = this.root_store.google_drive;
+ const { xml_doc, file_name } = await loadFile();
+ load({ block_string: xml_doc, file_name, workspace: Blockly.derivWorkspace, from: save_types.GOOGLE_DRIVE });
+ const { active_tab } = this.root_store.dashboard;
+ if (active_tab === 1) this.toggleLoadModal();
+
+ this.root_store.dashboard.is_dialog_open = false;
+ }
+
+ onEntered = (): void => {
+ this.previewRecentStrategy(this.selected_strategy_id);
+ };
+
+ onLoadModalClose = (): void => {
+ if (this.recent_workspace) {
+ this.recent_workspace = null;
+ }
+ if (this.local_workspace) {
+ this.local_workspace = null;
+ }
+
+ this.setActiveTabIndex(0); // Reset to first tab.
+ this.setLoadedLocalFile(null);
+ };
+
+ onZoomInOutClick = (is_zoom_in: string): void => {
+ if (this.preview_workspace) {
+ this.preview_workspace.zoomCenter(is_zoom_in ? 1 : -1);
+ }
+ };
+
+ previewRecentStrategy = (workspace_id: string): void => {
+ this.setSelectedStrategyId(workspace_id);
+ if (!workspace_id) this.setSelectedStrategyId(this.current_workspace_id);
+ if (!this.selected_strategy) {
+ return;
+ }
+ const {
+ dashboard: { active_tab },
+ } = this.root_store;
+ //removed the dispose here so on switch of tab it does not
+ //throw xml error
+ if (active_tab === 1 && !this.is_load_modal_open) {
+ this.recent_workspace = null;
+ this.setLoadedLocalFile(null);
+ }
+ //to load the bot on first load
+ if (this.tab_name !== tabs_title.TAB_LOCAL && this.recent_workspace) {
+ clearInjectionDiv('store', document.getElementById('load-strategy__blockly-container'));
+ this.recent_workspace.dispose();
+ this.recent_workspace = null;
+ }
+ if (!this.recent_workspace || !this.recent_workspace.rendered) {
+ const ref = document.getElementById('load-strategy__blockly-container');
+ if (!ref) {
+ // eslint-disable-next-line no-console
+ console.warn('Could not find preview workspace element.');
+ return;
+ }
+
+ this.recent_workspace = Blockly.inject(ref, {
+ media: `${__webpack_public_path__}media/`,
+ zoom: {
+ wheel: true,
+ startScale: config.workspaces.previewWorkspaceStartScale,
+ },
+ readOnly: true,
+ scrollbars: true,
+ });
+ }
+ const dark_mode = document.body.classList.contains('theme--dark');
+ setColors(dark_mode);
+ this.refreshStrategiesTheme();
+ const {
+ save_modal: { updateBotName },
+ } = this.root_store;
+ updateBotName(this.selected_strategy.name);
+ };
+
+ setActiveTabIndex = (index: number): void => {
+ this.active_index = index;
+ };
+
+ setLoadedLocalFile = (loaded_local_file: boolean | null): void => {
+ this.loaded_local_file = loaded_local_file;
+ };
+
+ setRecentStrategies = (recent_strategies: string[]): void => {
+ this.recent_strategies = recent_strategies;
+ };
+
+ refreshStrategies = (): void => {
+ this.setRecentStrategies(this.recent_strategies);
+ };
+
+ setSelectedStrategyId = (selected_strategy_id: string[] | undefined): void => {
+ this.selected_strategy_id = selected_strategy_id;
+ };
+
+ toggleExplanationExpand = (): void => {
+ this.is_explanation_expand = !this.is_explanation_expand;
+ };
+
+ toggleLoadModal = (): void => {
+ this.is_load_modal_open = !this.is_load_modal_open;
+ if (this.selected_strategy_id) this.previewRecentStrategy(this.selected_strategy_id);
+ };
+
+ toggleTourLoadModal = (toggle = !this.is_load_modal_open) => {
+ this.is_load_modal_open = toggle;
+ };
+
+ updateListStrategies = (workspaces: Array): void => {
+ if (workspaces) {
+ (this.dashboard_strategies as Array) = workspaces;
+ }
+ };
+
+ getRecentFileIcon = (save_type: { [key: string]: string } | string): string => {
+ switch (save_type) {
+ case save_types.UNSAVED:
+ return 'IcReports';
+ case save_types.LOCAL:
+ return 'IcMyComputer';
+ case save_types.GOOGLE_DRIVE:
+ return 'IcGoogleDrive';
+ default:
+ return 'IcReports';
+ }
+ };
+
+ getSaveType = (save_type: { [key: string]: string } | string): string => {
+ switch (save_type) {
+ case save_types.UNSAVED:
+ return localize('Unsaved');
+ case save_types.LOCAL:
+ return localize('Local');
+ case save_types.GOOGLE_DRIVE:
+ return localize('Google Drive');
+ default:
+ return localize('Unsaved');
+ }
+ };
+
+ onToggleDeleteDialog = (is_delete_modal_open: boolean): void => {
+ this.is_delete_modal_open = is_delete_modal_open;
+ };
+
+ readFile = (is_preview: boolean, drop_event: DragEvent, file: File): void => {
+ const file_name = file && file.name.replace(/\.[^/.]+$/, '');
+ const reader = new FileReader();
+ reader.onload = action(e => {
+ const load_options = { block_string: e.target.result, drop_event, from: save_types.LOCAL };
+ if (is_preview) {
+ const ref = document.getElementById('load-strategy__blockly-container');
+
+ this.local_workspace = Blockly.inject(ref, {
+ media: `${__webpack_public_path__}media/`, // eslint-disable-line
+ zoom: {
+ wheel: false,
+ startScale: config.workspaces.previewWorkspaceStartScale,
+ },
+ readOnly: true,
+ scrollbars: true,
+ });
+ load_options.workspace = this.local_workspace;
+ } else {
+ load_options.workspace = Blockly.derivWorkspace;
+ load_options.file_name = file_name;
+ }
+ load(load_options);
+ });
+ reader.readAsText(file);
+ };
+}
diff --git a/packages/bot-web-ui/src/stores/main-content-store.js b/packages/bot-web-ui/src/stores/main-content-store.js
deleted file mode 100644
index 3c5dbc2d51ba..000000000000
--- a/packages/bot-web-ui/src/stores/main-content-store.js
+++ /dev/null
@@ -1,46 +0,0 @@
-import { action, observable, makeObservable } from 'mobx';
-import { onWorkspaceResize } from '@deriv/bot-skeleton';
-import { tabs_title } from 'Constants/bot-contents';
-import { storeSetting, getSetting } from 'Utils/settings';
-
-export default class MainContentStore {
- constructor(root_store) {
- makeObservable(this, {
- active_tab: observable,
- setActiveTab: action.bound,
- setContainerSize: action.bound,
- onMount: action.bound,
- getCachedActiveTab: action.bound,
- onUnmount: action.bound,
- });
-
- this.root_store = root_store;
- }
-
- active_tab = tabs_title.WORKSPACE;
-
- setActiveTab(tab) {
- this.active_tab = tab;
- storeSetting('active_tab', this.active_tab);
- }
-
- setContainerSize() {
- if (this.active_tab === tabs_title.WORKSPACE) {
- onWorkspaceResize();
- }
- }
-
- onMount() {
- window.addEventListener('resize', this.setContainerSize);
- }
-
- getCachedActiveTab() {
- if (getSetting('active_tab')) {
- this.active_tab = getSetting('active_tab');
- }
- }
-
- onUnmount() {
- window.removeEventListener('resize', this.setContainerSize);
- }
-}
diff --git a/packages/bot-web-ui/src/stores/quick-strategy-store.js b/packages/bot-web-ui/src/stores/quick-strategy-store.js
deleted file mode 100644
index 37dd627f6cdf..000000000000
--- a/packages/bot-web-ui/src/stores/quick-strategy-store.js
+++ /dev/null
@@ -1,562 +0,0 @@
-import { computed, observable, action, runInAction, makeObservable } from 'mobx';
-import { localize } from '@deriv/translations';
-import { ApiHelpers, config, load } from '@deriv/bot-skeleton';
-import { save_types } from '@deriv/bot-skeleton/src/constants/save-type';
-import GTM from 'Utils/gtm';
-import { storeSetting, getSetting } from 'Utils/settings';
-
-export default class QuickStrategyStore {
- constructor(root_store) {
- makeObservable(this, {
- selected_symbol: observable,
- selected_trade_type: observable,
- selected_duration_unit: observable,
- input_duration_value: observable,
- input_stake: observable,
- input_size: observable,
- input_alembert_unit: observable,
- input_oscar_unit: observable,
- input_loss: observable,
- input_profit: observable,
- is_strategy_modal_open: observable,
- active_index: observable,
- symbol_dropdown: observable,
- trade_type_dropdown: observable,
- duration_unit_dropdown: observable,
- initial_values: computed,
- initial_errors: computed,
- setActiveTabIndex: action.bound,
- setDurationUnitDropdown: action.bound,
- setSymbolDropdown: action.bound,
- setTradeTypeDropdown: action.bound,
- setSelectedDurationUnit: action.bound,
- setSelectedSymbol: action.bound,
- setSelectedTradeType: action.bound,
- setDurationInputValue: action.bound,
- onChangeDropdownItem: action.bound,
- onChangeInputValue: action.bound,
- onHideDropdownList: action.bound,
- toggleStrategyModal: action.bound,
- createStrategy: action.bound,
- updateSymbolDropdown: action.bound,
- updateTradeTypeDropdown: action.bound,
- updateDurationDropdown: action.bound,
- updateDurationValue: action.bound,
- validateQuickStrategy: action.bound,
- });
-
- this.root_store = root_store;
- this.qs_cache = getSetting('quick_strategy') || {};
- }
- selected_symbol = this.qs_cache?.selected_symbol || '';
- selected_trade_type = this.qs_cache?.selected_trade_type || '';
- selected_duration_unit = this.qs_cache?.selected_duration_unit || '';
- input_duration_value = this.qs_cache?.input_duration_value || '';
- input_stake = this.qs_cache?.input_stake || '';
- input_size = this.qs_cache?.input_size || '';
- input_alembert_unit = this.qs_cache?.input_alembert_unit || '';
- input_oscar_unit = this.qs_cache?.input_oscar_unit || '';
- input_loss = this.qs_cache?.input_loss || '';
- input_profit = this.qs_cache?.input_profit || '';
-
- is_strategy_modal_open = false;
- active_index = 0;
- symbol_dropdown = [];
- trade_type_dropdown = [];
- duration_unit_dropdown = [];
-
- get initial_values() {
- const init = {
- 'quick-strategy__symbol': this.getFieldValue(this.symbol_dropdown, this.selected_symbol.value) || '',
- 'quick-strategy__trade-type':
- this.getFieldValue(this.trade_type_dropdown, this.selected_trade_type.value) || '',
- 'quick-strategy__duration-unit':
- this.getFieldValue(this.duration_unit_dropdown, this.selected_duration_unit.value) || '',
- 'quick-strategy__duration-value': this.input_duration_value || '',
- 'quick-strategy__stake': this.input_stake,
- ...(this.active_index === 0 && { 'quick-strategy__size': this.input_size }),
- ...(this.active_index === 1 && { 'alembert-unit': this.input_alembert_unit }),
- ...(this.active_index === 2 && { 'oscar-unit': this.input_oscar_unit }),
-
- 'quick-strategy__loss': this.input_loss,
- 'quick-strategy__profit': this.input_profit,
- };
- storeSetting('quick_strategy', this.qs_cache);
-
- return init;
- }
-
- get initial_errors() {
- // Persist errors through tab switch + remount.
- return this.validateQuickStrategy(this.initial_values, true);
- }
-
- setActiveTabIndex(index) {
- this.active_index = index;
- }
-
- setDurationUnitDropdown(duration_unit_options) {
- this.duration_unit_dropdown = duration_unit_options;
- }
-
- setSymbolDropdown(symbol_options) {
- this.symbol_dropdown = symbol_options;
- }
-
- setTradeTypeDropdown(trade_type_options) {
- this.trade_type_dropdown = trade_type_options;
- }
-
- setSelectedDurationUnit(duration_unit) {
- this.qs_cache.selected_duration_unit = duration_unit;
- this.selected_duration_unit = duration_unit;
- }
-
- setSelectedSymbol(symbol) {
- this.qs_cache.selected_symbol = symbol;
- this.selected_symbol = symbol;
- delete this.qs_cache?.selected_duration_unit;
- delete this.qs_cache?.duration_value;
- delete this.qs_cache?.selected_trade_type;
- }
-
- setSelectedTradeType(trade_type) {
- this.qs_cache.selected_trade_type = trade_type;
- this.selected_trade_type = trade_type;
- delete this.qs_cache?.selected_duration_unit;
- delete this.qs_cache?.duration_value;
- }
-
- setDurationInputValue(duration_value) {
- this.qs_cache.input_duration_value = duration_value;
- this.input_duration_value = duration_value;
- }
-
- onChangeDropdownItem(type, value, setFieldValue) {
- if (!value) {
- return;
- }
-
- const field_map = this.getFieldMap(type);
- if (type === 'symbol') {
- this.updateTradeTypeDropdown(value, setFieldValue);
-
- const symbol = this.symbol_dropdown.find(item => item.value === value);
- this.setSelectedSymbol(symbol);
-
- if (symbol) {
- setFieldValue(field_map.field_name, symbol.text);
- }
- } else if (type === 'trade-type') {
- this.updateDurationDropdown(this.selected_symbol.value, value, setFieldValue);
-
- const trade_type = this.trade_type_dropdown.find(item => item.value === value);
- this.setSelectedTradeType(trade_type);
-
- if (trade_type) {
- setFieldValue(field_map.field_name, trade_type.text);
- }
- } else if (type === 'duration-unit') {
- this.updateDurationValue(value, setFieldValue);
-
- const duration_unit = this.duration_unit_dropdown.find(item => item.value === value);
- this.setSelectedDurationUnit(duration_unit);
-
- if (duration_unit) {
- setFieldValue('quick-strategy__duration-unit', duration_unit.text);
- }
- }
- }
-
- onChangeInputValue(field, event) {
- this.qs_cache[field] = event.currentTarget.value;
- this[field] = event.currentTarget.value;
- storeSetting('quick_strategy', this.qs_cache);
- }
-
- onHideDropdownList(type, value, setFieldValue) {
- const field_map = this.getFieldMap(type);
- const item = field_map.dropdown.find(i => i.text.toLowerCase() === value.toLowerCase()) || field_map.selected;
-
- // Don't allow bogus input.
- if (!item) {
- setFieldValue(field_map.field_name, '');
- return;
- }
- // Restore value if user closed list.
- if (item.text !== value) {
- setFieldValue(field_map.field_name, item.text);
- }
- // Update item if different item was typed.
- if (item !== field_map.selected) {
- field_map.setSelected(item);
- }
- }
-
- async toggleStrategyModal() {
- this.root_store.flyout.setVisibility(false);
- this.is_strategy_modal_open = !this.is_strategy_modal_open;
-
- if (this.is_strategy_modal_open) {
- await this.updateSymbolDropdown();
- }
- }
-
- async createStrategy({ button }) {
- const symbol = this.selected_symbol.value;
- const trade_type = this.selected_trade_type.value;
- const duration_unit = this.selected_duration_unit.value;
- const duration_value = this.input_duration_value;
- const stake = this.input_stake;
- const size = this.input_size;
- const alembert_unit = this.input_alembert_unit;
- const oscar_unit = this.input_oscar_unit;
- const loss = this.input_loss;
- const profit = this.input_profit;
-
- const { contracts_for } = ApiHelpers.instance;
- const market = await contracts_for.getMarketBySymbol(symbol);
- const submarket = await contracts_for.getSubmarketBySymbol(symbol);
- const trade_type_cat = await contracts_for.getTradeTypeCategoryByTradeType(trade_type);
-
- const { strategies } = config;
- const strategy_name = Object.keys(strategies).find(s => strategies[s].index === this.active_index);
- const strategy_xml = await import(/* webpackChunkName: `[request]` */ `../xml/${strategy_name}.xml`);
- const strategy_dom = Blockly.Xml.textToDom(strategy_xml.default);
-
- const modifyValueInputs = (key, value) => {
- const el_value_inputs = strategy_dom.querySelectorAll(`value[strategy_value="${key}"]`);
-
- el_value_inputs.forEach(el_value_input => {
- el_value_input.innerHTML = `${value} `;
- });
- };
-
- const modifyFieldDropdownValues = (name, value) => {
- const el_blocks = strategy_dom.querySelectorAll(`field[name="${`${name.toUpperCase()}_LIST`}"]`);
-
- el_blocks.forEach(el_block => {
- el_block.innerHTML = value;
- });
- };
-
- const fields_to_update = {
- market,
- submarket,
- symbol,
- tradetype: trade_type,
- tradetypecat: trade_type_cat,
- durationtype: duration_unit,
- duration: duration_value,
- stake,
- size,
- alembert_unit,
- oscar_unit,
- loss,
- profit,
- };
-
- Object.keys(fields_to_update).forEach(key => {
- const value = fields_to_update[key];
-
- if (!isNaN(value)) {
- modifyValueInputs(key, value);
- } else if (typeof value === 'string') {
- modifyFieldDropdownValues(key, value);
- }
- });
-
- const file_name = strategies?.[strategy_name]?.label || localize('Unknown');
- const { derivWorkspace: workspace } = Blockly;
-
- load({ block_string: Blockly.Xml.domToText(strategy_dom), file_name, workspace, from: save_types.UNSAVED });
-
- if (button === 'run') {
- workspace
- .waitForBlockEvent({
- block_type: 'trade_definition',
- event_type: Blockly.Events.BLOCK_CREATE,
- timeout: 5000,
- })
- .then(() => {
- this.root_store.run_panel.onRunButtonClick();
- });
- }
-
- if (this.is_strategy_modal_open) {
- this.toggleStrategyModal();
- }
- }
-
- async updateSymbolDropdown() {
- const { active_symbols } = ApiHelpers.instance;
- const symbols = active_symbols.getAllSymbols(/* should_be_open */ true);
- const symbol_options = symbols.map(symbol => ({
- group: symbol.submarket_display,
- text: symbol.symbol_display,
- value: symbol.symbol,
- }));
-
- this.setSymbolDropdown(symbol_options);
-
- if (!this.selected_symbol && symbol_options.length) {
- this.selected_symbol = symbol_options[0];
- }
- await this.updateTradeTypeDropdown(this.selected_symbol.value);
- }
-
- async updateTradeTypeDropdown(symbol, setFieldValue) {
- const { contracts_for } = ApiHelpers.instance;
- const trade_type_options = [];
- const market = contracts_for.getMarketBySymbol(symbol);
- const submarket = contracts_for.getSubmarketBySymbol(symbol);
- const trade_type_categories = await contracts_for.getTradeTypeCategories(market, submarket, symbol);
-
- const filtered_trade_type_categories = [];
-
- for (let i = 0; i < trade_type_categories.length; i++) {
- const trade_type_category = trade_type_categories[i];
- // eslint-disable-next-line no-await-in-loop
- const trade_types = await contracts_for.getTradeTypeByTradeCategory(
- market,
- submarket,
- symbol,
- trade_type_category[1]
- );
-
- // TODO: Temporary filtering of barrier + prediction types. Should later
- // render more inputs for these types. We should only filter out trade type
- // categories which only feature prediction/barrier trade types. e.g.
- // in Digits category, users can still purchase Even/Odd types.
- let hidden_categories = 0;
-
- for (let j = 0; j < trade_types.length; j++) {
- const trade_type = trade_types[j];
- const has_barrier = config.BARRIER_TRADE_TYPES.includes(trade_type.value);
- const has_prediction = config.PREDICTION_TRADE_TYPES.includes(trade_type.value);
-
- if (has_barrier || has_prediction) {
- hidden_categories++;
- }
- }
-
- if (hidden_categories < trade_types.length) {
- filtered_trade_type_categories.push(trade_type_category);
- }
- }
-
- for (let i = 0; i < filtered_trade_type_categories.length; i++) {
- const trade_type_category = filtered_trade_type_categories[i]; // e.g. ['Up/Down', 'callput']
- // eslint-disable-next-line no-await-in-loop
- const trade_types = await contracts_for.getTradeTypeByTradeCategory(
- market,
- submarket,
- symbol,
- trade_type_category[1]
- );
-
- trade_types.forEach(trade_type => {
- const has_barrier = config.BARRIER_TRADE_TYPES.includes(trade_type.value);
- const has_prediction = config.PREDICTION_TRADE_TYPES.includes(trade_type.value);
- const is_muliplier = ['multiplier'].includes(trade_type.value);
-
- // TODO: Render extra inputs for barrier + prediction and multiplier types.
- if (!has_barrier && !has_prediction && !is_muliplier) {
- trade_type_options.push({
- text: trade_type.name,
- value: trade_type.value,
- group: trade_type_category[0],
- icon: trade_type.icon,
- });
- }
- });
- }
-
- this.setTradeTypeDropdown(trade_type_options);
- let first_trade_type = trade_type_options[0];
-
- if (this.selected_trade_type && trade_type_options.some(e => e.value === this.selected_trade_type.value)) {
- first_trade_type = this.selected_trade_type;
- runInAction(() => {
- first_trade_type.text = this.getFieldValue(this.trade_type_dropdown, this.selected_trade_type.value);
- });
- } else {
- delete this.qs_cache?.selected_trade_type;
- }
- if (first_trade_type) {
- this.setSelectedTradeType(first_trade_type);
- await this.updateDurationDropdown(
- this.selected_symbol.value,
- this.selected_trade_type.value,
- setFieldValue
- );
-
- if (setFieldValue) {
- setFieldValue('quick-strategy__trade-type', first_trade_type.text);
- }
- }
- }
-
- async updateDurationDropdown(symbol, trade_type, setFieldValue) {
- const { contracts_for } = ApiHelpers.instance;
- const durations = await contracts_for.getDurations(symbol, trade_type);
- const duration_options = durations.map(duration => ({
- text: duration.display,
- value: duration.unit,
- min: duration.min,
- max: duration.max,
- }));
- this.setDurationUnitDropdown(duration_options);
- let first_duration_unit = duration_options[0];
- if (this.selected_duration_unit && duration_options.some(e => e.value === this.selected_duration_unit.value)) {
- first_duration_unit = this.selected_duration_unit;
- runInAction(() => {
- first_duration_unit.text = this.getFieldValue(duration_options, this.selected_duration_unit.value);
- });
- } else {
- delete this.qs_cache?.selected_duration_unit;
- }
- if (first_duration_unit) {
- this.setSelectedDurationUnit(first_duration_unit);
- this.updateDurationValue(this.selected_duration_unit.value, setFieldValue);
-
- if (setFieldValue) {
- setFieldValue('quick-strategy__duration-unit', first_duration_unit.text);
- }
- }
- }
-
- async updateDurationValue(duration_type, setFieldValue) {
- const { contracts_for } = ApiHelpers.instance;
- const durations = await contracts_for.getDurations(this.selected_symbol.value, this.selected_trade_type.value);
- const min_duration = durations.find(duration => duration.unit === duration_type);
- if (min_duration) {
- let duration_input_value = min_duration.min;
- const cache_unit = this.qs_cache?.input_duration_value;
- if (cache_unit && cache_unit < min_duration.max && cache_unit > min_duration.min) {
- duration_input_value = cache_unit;
- } else {
- delete this.qs_cache?.input_duration_value;
- }
- this.setDurationInputValue(duration_input_value);
-
- if (setFieldValue) {
- setFieldValue('quick-strategy__duration-value', duration_input_value);
- }
- }
- }
-
- validateQuickStrategy(values, should_ignore_empty = false) {
- const errors = {};
- const number_fields = [
- 'quick-strategy__duration-value',
- 'quick-strategy__stake',
- ...(this.active_index === 0 ? ['quick-strategy__size'] : []),
- ...(this.active_index === 1 ? ['alembert-unit'] : []),
- ...(this.active_index === 2 ? ['oscar-unit'] : []),
- 'quick-strategy__profit',
- 'quick-strategy__loss',
- ];
- Object.keys(values).forEach(key => {
- const value = values[key];
-
- if (should_ignore_empty && !value) {
- return;
- }
-
- if (number_fields.includes(key)) {
- if (isNaN(value)) {
- errors[key] = localize('Must be a number');
- } else if (value <= 0) {
- errors[key] = localize('Must be a number higher than 0');
- } else if (/^0+(?=\d)/.test(value)) {
- errors[key] = localize('Invalid number format');
- }
- }
-
- if (value === '') {
- errors[key] = localize('Field cannot be empty');
- }
- if (key === 'quick-strategy__size' && values[key] < 2) {
- errors[key] = localize('Value must be higher than 2');
- }
- });
-
- const duration = this.duration_unit_dropdown.find(d => d.text === values['quick-strategy__duration-unit']);
-
- if (duration) {
- const { min, max } = duration;
-
- if (values['quick-strategy__duration-value'] < min) {
- errors['quick-strategy__duration-value'] = localize('Minimum duration: {{ min }}', { min });
- } else if (values['quick-strategy__duration-value'] > max) {
- errors['quick-strategy__duration-value'] = localize('Maximum duration: {{ max }}', { max });
- }
- }
- return errors;
- }
-
- onScrollStopDropdownList = type => {
- GTM.pushDataLayer({ event: `dbot_quick_strategy_scroll_${type}` });
- };
-
- getSizeDesc = index => {
- switch (index) {
- case 0:
- return localize(
- 'The multiplier amount used to increase your stake if you’re losing a trade. Value must be higher than 2.'
- );
- case 1:
- return localize('The amount that you may add to your stake if you’re losing a trade.');
- case 2:
- return localize('The amount that you may add to your stake after each successful trade.');
- default:
- return '';
- }
- };
-
- getSizeText = index => {
- switch (index) {
- case 0:
- return localize('Size');
- case 1:
- case 2:
- return localize('Units');
- default:
- return '';
- }
- };
-
- getFieldMap = type => {
- const field_mapping = {
- symbol: {
- field_name: 'quick-strategy__symbol',
- dropdown: this.symbol_dropdown,
- selected: this.selected_symbol,
- setSelected: this.setSelectedSymbol,
- },
- 'trade-type': {
- field_name: 'quick-strategy__trade-type',
- dropdown: this.trade_type_dropdown,
- selected: this.selected_trade_type,
- setSelected: this.setSelectedTradeType,
- },
- 'duration-unit': {
- field_name: 'quick-strategy__duration-unit',
- dropdown: this.duration_unit_dropdown,
- selected: this.selected_duration_unit,
- setSelected: this.setSelectedDurationUnit,
- },
- };
- return field_mapping[type];
- };
-
- getFieldValue = (list_items, value) => {
- const dropdown_items = Array.isArray(list_items) ? list_items : [].concat(...Object.values(list_items));
- const list_obj = dropdown_items.find(item =>
- typeof item.value !== 'string' ? item.value === value : item.value?.toLowerCase() === value?.toLowerCase()
- );
-
- return list_obj?.text || '';
- };
-}
diff --git a/packages/bot-web-ui/src/stores/quick-strategy-store.ts b/packages/bot-web-ui/src/stores/quick-strategy-store.ts
new file mode 100644
index 000000000000..a6d079b9513f
--- /dev/null
+++ b/packages/bot-web-ui/src/stores/quick-strategy-store.ts
@@ -0,0 +1,622 @@
+import { action, computed, makeObservable, observable, runInAction } from 'mobx';
+import { ApiHelpers, config, load } from '@deriv/bot-skeleton';
+import { save_types } from '@deriv/bot-skeleton/src/constants/save-type';
+import { localize } from '@deriv/translations';
+import GTM from 'Utils/gtm';
+import { getSetting, storeSetting } from 'Utils/settings';
+import {
+ TDropdownItems,
+ TDropdowns,
+ TDurationOptions,
+ TDurations,
+ TDurationUnitDropdown,
+ TFieldMapData,
+ TFieldsToUpdate,
+ TInputCommonFields,
+ TKeysStrategies,
+ TMarketOption,
+ TQSCache,
+ TSelectedValuesSelect,
+ TSelectsFieldNames,
+ TSetFieldValue,
+ TStrategies,
+ TSymbol,
+ TSymbolDropdown,
+ TTradeType,
+ TTradeTypeContractsFor,
+ TTradeTypeDropdown,
+ TTypeStrategiesDropdown,
+ TTypeStrategy,
+} from '../components/dashboard/quick-strategy/quick-strategy.types';
+import RootStore from './root-store';
+
+export default class QuickStrategyStore {
+ root_store: RootStore;
+ qs_cache: TQSCache = (getSetting('quick_strategy') as TQSCache) || {};
+
+ constructor(root_store: RootStore) {
+ makeObservable(this, {
+ selected_symbol: observable,
+ selected_trade_type: observable,
+ selected_type_strategy: observable,
+ selected_duration_unit: observable,
+ input_duration_value: observable,
+ input_stake: observable,
+ input_alembert_unit: observable,
+ input_martingale_size: observable,
+ input_oscar_unit: observable,
+ input_loss: observable,
+ input_profit: observable,
+ is_strategy_modal_open: observable,
+ active_index: observable,
+ symbol_dropdown: observable,
+ trade_type_dropdown: observable,
+ duration_unit_dropdown: observable,
+ description: observable,
+ is_contract_dialog_open: observable,
+ is_stop_bot_dialog_open: observable,
+ initial_values: computed,
+ types_strategies_dropdown: observable,
+ onScrollStopDropdownList: action.bound,
+ getSizeDesc: action.bound,
+ getFieldMap: action.bound,
+ getFieldValue: action.bound,
+ getQuickStrategyFields: action.bound,
+ setDescription: action.bound,
+ setActiveTypeStrategyIndex: action.bound,
+ setDurationUnitDropdown: action.bound,
+ setSymbolDropdown: action.bound,
+ setTradeTypeDropdown: action.bound,
+ setTypesStrategiesDropdown: action.bound,
+ setSelectedTypeStrategy: action.bound,
+ setSelectedDurationUnit: action.bound,
+ setSelectedSymbol: action.bound,
+ setSelectedTradeType: action.bound,
+ setDurationInputValue: action.bound,
+ onChangeDropdownItem: action.bound,
+ onChangeInputValue: action.bound,
+ onHideDropdownList: action.bound,
+ loadDataStrategy: action.bound,
+ createStrategy: action.bound,
+ updateSymbolDropdown: action.bound,
+ updateTypesStrategiesDropdown: action.bound,
+ updateTradeTypeDropdown: action.bound,
+ updateDurationDropdown: action.bound,
+ updateDurationValue: action.bound,
+ });
+
+ this.root_store = root_store;
+ }
+ selected_symbol: TMarketOption = (this.qs_cache.selected_symbol as TMarketOption) || {};
+ selected_trade_type: TTradeType = (this.qs_cache.selected_trade_type as TTradeType) || {};
+ selected_type_strategy: TTypeStrategy = (this.qs_cache.selected_type_strategy as TTypeStrategy) || {};
+ selected_duration_unit: TDurationOptions = (this.qs_cache.selected_duration_unit as TDurationOptions) || {};
+ input_duration_value: string | number = this.qs_cache.input_duration_value || '';
+ input_stake: string = this.qs_cache.input_stake || '';
+ input_martingale_size: string = this.qs_cache.input_martingale_size || '';
+ input_alembert_unit: string = this.qs_cache.input_alembert_unit || '';
+ input_oscar_unit: string = this.qs_cache.input_oscar_unit || '';
+ input_loss: string = this.qs_cache.input_loss || '';
+ input_profit: string = this.qs_cache.input_profit || '';
+ active_index: number = this.selected_type_strategy.index || 0;
+ description: string = this.qs_cache.selected_type_strategy?.description || '';
+ types_strategies_dropdown: TTypeStrategiesDropdown = [];
+ symbol_dropdown: TSymbolDropdown = [];
+ trade_type_dropdown: TTradeTypeDropdown = [];
+ duration_unit_dropdown: TDurationUnitDropdown = [];
+ is_contract_dialog_open = false;
+ is_stop_bot_dialog_open = false;
+ is_strategy_modal_open = false;
+
+ get initial_values() {
+ const init = {
+ 'quick-strategy__type-strategy':
+ this.getFieldValue(this.types_strategies_dropdown, this.selected_type_strategy.value) || '',
+ 'quick-strategy__symbol': this.getFieldValue(this.symbol_dropdown, this.selected_symbol.value) || '',
+ 'quick-strategy__trade-type':
+ this.getFieldValue(this.trade_type_dropdown, this.selected_trade_type.value) || '',
+ 'quick-strategy__duration-unit':
+ this.getFieldValue(this.duration_unit_dropdown, this.selected_duration_unit.value) || '',
+ 'quick-strategy__duration-value': this.input_duration_value || '',
+ 'quick-strategy__stake': this.input_stake,
+ ...(this.active_index === 0 && { 'martingale-size': this.input_martingale_size || '' }),
+ ...(this.active_index === 1 && { 'alembert-unit': this.input_alembert_unit || '' }),
+ ...(this.active_index === 2 && { 'oscar-unit': this.input_oscar_unit || '' }),
+
+ 'quick-strategy__loss': this.input_loss || '',
+ 'quick-strategy__profit': this.input_profit || '',
+ };
+ storeSetting('quick_strategy', this.qs_cache);
+
+ return init;
+ }
+
+ setActiveTypeStrategyIndex(index: number): void {
+ this.active_index = index;
+ }
+
+ setDescription(type_strategy: TTypeStrategy): void {
+ this.description =
+ this.types_strategies_dropdown?.find(strategy => strategy.value === type_strategy.value)?.description || '';
+ }
+
+ setDurationUnitDropdown(duration_unit_options: TDurationUnitDropdown): void {
+ this.duration_unit_dropdown = duration_unit_options;
+ }
+
+ setSymbolDropdown(symbol_options: TSymbolDropdown): void {
+ this.symbol_dropdown = symbol_options;
+ }
+
+ setTradeTypeDropdown(trade_type_options: TTradeTypeDropdown): void {
+ this.trade_type_dropdown = trade_type_options;
+ }
+
+ setSelectedDurationUnit(duration_unit: TDurationOptions): void {
+ this.qs_cache.selected_duration_unit = duration_unit;
+ this.selected_duration_unit = duration_unit;
+ }
+
+ setTypesStrategiesDropdown(types_strategies_options: TTypeStrategiesDropdown): void {
+ this.types_strategies_dropdown = types_strategies_options;
+ }
+
+ setSelectedTypeStrategy(type_strategy: TTypeStrategy): void {
+ this.qs_cache.selected_type_strategy = type_strategy;
+ this.selected_type_strategy = type_strategy;
+ this.setDescription(type_strategy);
+ }
+
+ setSelectedSymbol(symbol: TMarketOption): void {
+ this.qs_cache.selected_symbol = symbol;
+ this.selected_symbol = symbol;
+ delete this.qs_cache.selected_duration_unit;
+ delete this.qs_cache.selected_trade_type;
+ }
+
+ setSelectedTradeType(trade_type: TTradeType): void {
+ this.qs_cache.selected_trade_type = trade_type;
+ this.selected_trade_type = trade_type;
+ delete this.qs_cache.selected_duration_unit;
+ }
+
+ setDurationInputValue(duration_value: string | number): void {
+ this.qs_cache.input_duration_value = duration_value;
+ this.input_duration_value = duration_value;
+ }
+
+ onChangeDropdownItem(type: TDropdownItems, value: string, setFieldValue: TSetFieldValue): void {
+ if (!value) {
+ return;
+ }
+
+ const field_map = this.getFieldMap(type);
+ if (type === 'symbol') {
+ this.updateTradeTypeDropdown(value, setFieldValue);
+
+ const symbol = this.symbol_dropdown.find(item => item.value === value);
+
+ if (symbol) {
+ this.setSelectedSymbol(symbol);
+ setFieldValue(field_map?.field_name, symbol.text);
+ }
+ } else if (type === 'trade-type') {
+ this.updateDurationDropdown(this.selected_symbol.value, value, setFieldValue);
+
+ const trade_type = this.trade_type_dropdown.find(item => item.value === value);
+
+ if (trade_type) {
+ this.setSelectedTradeType(trade_type);
+ setFieldValue(field_map?.field_name, trade_type.text);
+ }
+ } else if (type === 'duration-unit') {
+ this.updateDurationValue(value, setFieldValue);
+
+ const duration_unit = this.duration_unit_dropdown.find(item => item.value === value);
+
+ if (duration_unit) {
+ this.setSelectedDurationUnit(duration_unit);
+ setFieldValue(field_map?.field_name, duration_unit.text);
+ }
+ } else if (type === 'type-strategy') {
+ const typeStrategy = this.types_strategies_dropdown.find(item => item.value === value);
+
+ if (typeStrategy) {
+ this.setSelectedTypeStrategy(typeStrategy);
+ this.setActiveTypeStrategyIndex(typeStrategy.index);
+ setFieldValue(field_map?.field_name, typeStrategy.text);
+ }
+ }
+ }
+
+ onChangeInputValue(field: TInputCommonFields, event: React.ChangeEvent): void {
+ this.qs_cache[field] = event.currentTarget.value;
+ this[field] = event.currentTarget.value;
+ storeSetting('quick_strategy', this.qs_cache);
+ }
+
+ onHideDropdownList(type: TDropdownItems, value: TSelectsFieldNames, setFieldValue: TSetFieldValue): void {
+ const field_map = this.getFieldMap(type);
+ const item = field_map.dropdown?.find(i => i.text.toLowerCase() === value.toLowerCase()) || field_map.selected;
+
+ // Don't allow bogus input.
+ if (!item) {
+ setFieldValue(field_map?.field_name, '');
+ return;
+ }
+ // Restore value if user closed list.
+ if (item.text !== value) {
+ setFieldValue(field_map?.field_name, item.text);
+ }
+ // Update item if different item was typed.
+ if (item !== field_map.selected) {
+ field_map.setSelected(item);
+ }
+ }
+
+ async loadDataStrategy() {
+ this.root_store.flyout.setVisibility(false);
+ this.is_strategy_modal_open = !this.is_strategy_modal_open;
+
+ if (this.is_strategy_modal_open) {
+ await this.updateSymbolDropdown();
+ await this.updateTypesStrategiesDropdown();
+ }
+ }
+
+ async createStrategy({ button }: Record<'button', 'run' | 'edit'>) {
+ const symbol = this.selected_symbol.value;
+ const trade_type = this.selected_trade_type.value;
+ const duration_unit = this.selected_duration_unit.value;
+ const duration_value = this.input_duration_value;
+ const stake = this.input_stake;
+ const size = this.input_martingale_size;
+ const alembert_unit = this.input_alembert_unit;
+ const oscar_unit = this.input_oscar_unit;
+ const loss = this.input_loss;
+ const profit = this.input_profit;
+
+ const { contracts_for } = ApiHelpers.instance;
+ const market = await contracts_for.getMarketBySymbol(symbol);
+ const submarket = await contracts_for.getSubmarketBySymbol(symbol);
+ const trade_type_cat = await contracts_for.getTradeTypeCategoryByTradeType(trade_type);
+
+ const { strategies } = config;
+ const strategy_name = Object.keys(strategies).find(s => strategies[s].index === this.active_index);
+ const strategy_xml = await import(/* webpackChunkName: `[request]` */ `../xml/${strategy_name}.xml`);
+ const strategy_dom = Blockly.Xml.textToDom(strategy_xml.default);
+
+ const modifyValueInputs = (key: string, value: number) => {
+ const el_value_inputs = strategy_dom.querySelectorAll(`value[strategy_value="${key}"]`);
+
+ el_value_inputs.forEach((el_value_input: HTMLElement) => {
+ el_value_input.innerHTML = `${value} `;
+ });
+ };
+
+ const modifyFieldDropdownValues = (name: string, value: string) => {
+ const name_list = `${name.toUpperCase()}_LIST`;
+ const el_blocks = strategy_dom.querySelectorAll(`field[name="${name_list}"]`);
+
+ el_blocks.forEach((el_block: HTMLElement) => {
+ el_block.innerHTML = value;
+ });
+ };
+
+ const fields_to_update: TFieldsToUpdate = {
+ market,
+ submarket,
+ symbol,
+ tradetype: trade_type,
+ tradetypecat: trade_type_cat,
+ durationtype: duration_unit,
+ duration: duration_value,
+ stake,
+ size,
+ alembert_unit,
+ oscar_unit,
+ loss,
+ profit,
+ };
+
+ Object.keys(fields_to_update).forEach(key => {
+ const value = fields_to_update[key as keyof typeof fields_to_update];
+
+ if (!isNaN(value as number)) {
+ modifyValueInputs(key, value as number);
+ } else if (typeof value === 'string') {
+ modifyFieldDropdownValues(key, value);
+ }
+ });
+
+ const file_name = (strategies as TStrategies)?.[strategy_name as TKeysStrategies]?.label || localize('Unknown');
+
+ const { derivWorkspace: workspace } = Blockly;
+
+ load({ block_string: Blockly.Xml.domToText(strategy_dom), file_name, workspace, from: save_types.UNSAVED });
+
+ if (button === 'run') {
+ workspace
+ .waitForBlockEvent({
+ block_type: 'trade_definition',
+ event_type: Blockly.Events.BLOCK_CREATE,
+ timeout: 5000,
+ })
+ .then(() => {
+ this.root_store.run_panel.onRunButtonClick();
+ });
+ }
+ if (this.is_strategy_modal_open) {
+ this.loadDataStrategy();
+ }
+ }
+
+ async updateSymbolDropdown() {
+ const { active_symbols } = ApiHelpers.instance;
+ const symbols = active_symbols.getAllSymbols(/* should_be_open */ true);
+
+ const symbol_options = symbols.map((symbol: TSymbol) => ({
+ group: symbol.submarket_display,
+ text: symbol.symbol_display,
+ value: symbol.symbol,
+ }));
+
+ this.setSymbolDropdown(symbol_options);
+
+ if (!this.selected_symbol.value && symbol_options.length) {
+ this.selected_symbol = symbol_options[0];
+ }
+
+ await this.updateTradeTypeDropdown(this.selected_symbol.value);
+ }
+
+ async updateTradeTypeDropdown(symbol: string, setFieldValue?: TSetFieldValue) {
+ const { contracts_for } = ApiHelpers.instance;
+ const trade_type_options: TTradeTypeDropdown = [];
+ const market = contracts_for.getMarketBySymbol(symbol);
+ const submarket = contracts_for.getSubmarketBySymbol(symbol);
+ const trade_type_categories = await contracts_for.getTradeTypeCategories(market, submarket, symbol);
+
+ const filtered_trade_type_categories = [];
+
+ for (let i = 0; i < trade_type_categories.length; i++) {
+ const trade_type_category = trade_type_categories[i];
+ // eslint-disable-next-line no-await-in-loop
+ const trade_types = await contracts_for.getTradeTypeByTradeCategory(
+ market,
+ submarket,
+ symbol,
+ trade_type_category[1]
+ );
+
+ const hidden_categories = this.getHiddenCategories(trade_types);
+
+ if (hidden_categories < trade_types.length) {
+ filtered_trade_type_categories.push(trade_type_category);
+ }
+ }
+
+ for (let i = 0; i < filtered_trade_type_categories.length; i++) {
+ const trade_type_category = filtered_trade_type_categories[i]; // e.g. ['Up/Down', 'callput']
+ // eslint-disable-next-line no-await-in-loop
+ const trade_types = await contracts_for.getTradeTypeByTradeCategory(
+ market,
+ submarket,
+ symbol,
+ trade_type_category[1]
+ );
+
+ trade_type_options.push(...this.getTradeTypeOptions(trade_types, trade_type_category));
+ }
+
+ this.setTradeTypeDropdown(trade_type_options);
+ let first_trade_type = trade_type_options[0];
+
+ if (this.selected_trade_type && trade_type_options.some(e => e.value === this.selected_trade_type.value)) {
+ first_trade_type = this.selected_trade_type;
+ runInAction(() => {
+ first_trade_type.text = this.getFieldValue(this.trade_type_dropdown, this.selected_trade_type.value);
+ });
+ } else {
+ delete this.qs_cache?.selected_trade_type;
+ }
+ if (first_trade_type) {
+ this.setSelectedTradeType(first_trade_type);
+ await this.updateDurationDropdown(
+ this.selected_symbol.value,
+ this.selected_trade_type.value,
+ setFieldValue
+ );
+
+ if (setFieldValue) {
+ setFieldValue('quick-strategy__trade-type', first_trade_type.text);
+ }
+ }
+ }
+
+ async updateTypesStrategiesDropdown() {
+ const { strategies } = config;
+ const types_strategies = Object.values(strategies as TStrategies).map(strategy => ({
+ index: strategy.index,
+ text: strategy.label,
+ value: strategy.label,
+ description: strategy.description,
+ }));
+
+ this.setTypesStrategiesDropdown(types_strategies);
+ let first_type_strategy = types_strategies[0];
+ if (this.selected_type_strategy && types_strategies.some(e => e.value === this.selected_type_strategy.value)) {
+ first_type_strategy = this.selected_type_strategy;
+ runInAction(() => {
+ first_type_strategy.text = this.getFieldValue(types_strategies, this.selected_type_strategy.value);
+ });
+ } else {
+ delete this.qs_cache.selected_type_strategy;
+ }
+ if (first_type_strategy) {
+ this.setSelectedTypeStrategy(first_type_strategy);
+ }
+ }
+
+ async updateDurationDropdown(symbol: string, trade_type: string, setFieldValue?: TSetFieldValue) {
+ const { contracts_for } = ApiHelpers.instance;
+ const durations = await contracts_for.getDurations(symbol, trade_type);
+
+ const duration_options = (durations as TDurations).map(duration => ({
+ text: duration.display,
+ value: duration.unit,
+ min: duration.min,
+ max: duration.max,
+ }));
+ this.setDurationUnitDropdown(duration_options);
+ let first_duration_unit: TDurationOptions = duration_options[0];
+ if (this.selected_duration_unit && duration_options?.some(e => e.value === this.selected_duration_unit.value)) {
+ first_duration_unit =
+ duration_options?.find(e => e.value === this.selected_duration_unit.value) ||
+ this.selected_duration_unit;
+ runInAction(() => {
+ first_duration_unit.text = this.getFieldValue(duration_options, this.selected_duration_unit.value);
+ });
+ } else {
+ delete this.qs_cache?.selected_duration_unit;
+ }
+ if (first_duration_unit) {
+ this.setSelectedDurationUnit(first_duration_unit);
+ this.updateDurationValue(
+ this.qs_cache?.selected_duration_unit?.value || this.selected_duration_unit.value,
+ setFieldValue
+ );
+
+ if (setFieldValue) {
+ setFieldValue('quick-strategy__duration-unit', first_duration_unit.text);
+ }
+ }
+ }
+
+ async updateDurationValue(duration_type: string, setFieldValue?: TSetFieldValue) {
+ const { contracts_for } = ApiHelpers.instance;
+ const durations = await contracts_for.getDurations(this.selected_symbol.value, this.selected_trade_type.value);
+ const min_duration = (durations as TDurations).find(duration => duration.unit === duration_type);
+ if (min_duration) {
+ let duration_input_value: number | string = min_duration.min;
+ const cache_unit = this.qs_cache?.input_duration_value;
+ if (cache_unit && cache_unit < min_duration.max && cache_unit > min_duration.min) {
+ duration_input_value = cache_unit;
+ } else {
+ delete this.qs_cache?.input_duration_value;
+ }
+ this.setDurationInputValue(duration_input_value);
+
+ if (setFieldValue) {
+ setFieldValue('quick-strategy__duration-value', duration_input_value);
+ }
+ }
+ }
+
+ onScrollStopDropdownList = (type: TDropdownItems): void => {
+ GTM.pushDataLayer({ event: `dbot_quick_strategy_scroll_${type}` });
+ };
+
+ getSizeDesc = (index: number): string => {
+ switch (index) {
+ case 0:
+ return 'The multiplier amount used to increase your stake if you’re losing a trade. Value must be higher than 2.';
+ case 1:
+ return 'The amount that you may add to your stake if you’re losing a trade.';
+ case 2:
+ return 'The amount that you may add to your stake after each successful trade.';
+ default:
+ return '';
+ }
+ };
+
+ getFieldMap = (type: TDropdownItems): TFieldMapData => {
+ const field_mapping = {
+ symbol: {
+ field_name: 'quick-strategy__symbol',
+ dropdown: this.symbol_dropdown,
+ selected: this.selected_symbol,
+ setSelected: this.setSelectedSymbol,
+ },
+ 'trade-type': {
+ field_name: 'quick-strategy__trade-type',
+ dropdown: this.trade_type_dropdown,
+ selected: this.selected_trade_type,
+ setSelected: this.setSelectedTradeType,
+ },
+ 'duration-unit': {
+ field_name: 'quick-strategy__duration-unit',
+ dropdown: this.duration_unit_dropdown,
+ selected: this.selected_duration_unit,
+ setSelected: this.setSelectedDurationUnit,
+ },
+ 'type-strategy': {
+ field_name: 'quick-strategy__type-strategy',
+ dropdown: this.types_strategies_dropdown,
+ selected: this.selected_type_strategy,
+ setSelected: this.setSelectedTypeStrategy,
+ },
+ };
+ return field_mapping[type] as TFieldMapData;
+ };
+
+ getFieldValue = (list_items: TDropdowns, value: string): string => {
+ const list_obj: TSelectedValuesSelect =
+ list_items?.find(item =>
+ typeof item.value !== 'string'
+ ? item.value === value
+ : item.value?.toLowerCase() === value?.toLowerCase()
+ ) || {};
+
+ return typeof list_obj !== 'string' ? list_obj?.text : '';
+ };
+
+ getQuickStrategyFields = (): void => {
+ return getSetting('quick_strategy');
+ };
+
+ getHiddenCategories = (trade_types: Array) => {
+ // TODO: Temporary filtering of barrier + prediction types. Should later
+ // render more inputs for these types. We should only filter out trade type
+ // categories which only feature prediction/barrier trade types. e.g.
+ // in Digits category, users can still purchase Even/Odd types.
+ let hidden_categories = 0;
+
+ for (let j = 0; j < trade_types.length; j++) {
+ const trade_type = trade_types[j];
+ const has_barrier = config.BARRIER_TRADE_TYPES.includes(trade_type.value);
+ const has_prediction = config.PREDICTION_TRADE_TYPES.includes(trade_type.value);
+
+ if (has_barrier || has_prediction) {
+ hidden_categories++;
+ }
+ }
+
+ return hidden_categories;
+ };
+
+ getTradeTypeOptions = (trade_types: Array, trade_type_category: Array) => {
+ const trade_type_options: TTradeTypeDropdown = [];
+ trade_types.forEach((trade_type: TTradeTypeContractsFor) => {
+ const has_barrier = config.BARRIER_TRADE_TYPES.includes(trade_type.value);
+ const has_prediction = config.PREDICTION_TRADE_TYPES.includes(trade_type.value);
+ const is_muliplier = ['multiplier'].includes(trade_type.value);
+
+ // TODO: Render extra inputs for barrier + prediction and multiplier types.
+ if (!has_barrier && !has_prediction && !is_muliplier) {
+ trade_type_options.push({
+ text: trade_type.name,
+ value: trade_type.value,
+ group: trade_type_category[0],
+ icon: trade_type.icon,
+ });
+ }
+ });
+ return trade_type_options;
+ };
+
+ toggleStopBotDialog = (): void => {
+ this.is_contract_dialog_open = !this.is_contract_dialog_open;
+ this.is_stop_bot_dialog_open = !this.is_stop_bot_dialog_open;
+ };
+}
diff --git a/packages/bot-web-ui/src/stores/root-store.ts b/packages/bot-web-ui/src/stores/root-store.ts
new file mode 100644
index 000000000000..785f18bdc804
--- /dev/null
+++ b/packages/bot-web-ui/src/stores/root-store.ts
@@ -0,0 +1,78 @@
+import type { TDbot, TRootStore, TWebSocket } from 'Types';
+import AppStore from './app-store';
+import BlocklyStore from './blockly-store';
+import ChartStore from './chart-store';
+import DashboardStore from './dashboard-store';
+import DataCollectionStore from './data-collection-store';
+import DownloadStore from './download-store';
+import FlyoutHelpStore from './flyout-help-store';
+import FlyoutStore from './flyout-store';
+import GoogleDriveStore from './google-drive-store';
+import JournalStore from './journal-store';
+import LoadModalStore from './load-modal-store';
+import QuickStrategyStore from './quick-strategy-store';
+import RoutePromptDialogStore from './route-prompt-dialog-store';
+import RunPanelStore from './run-panel-store';
+import SaveModalStore from './save-modal-store';
+import SelfExclusionStore from './self-exclusion-store';
+import SummaryCardStore from './summary-card-store';
+import SummaryStore from './summary-store';
+import ToolbarStore from './toolbar-store';
+import ToolboxStore from './toolbox-store';
+import TransactionsStore from './transactions-store';
+
+// TODO: need to write types for the individual classes and convert them to ts
+export default class RootStore {
+ public ws: TWebSocket;
+ public dbot: TDbot;
+ public app: AppStore;
+ public summary_card: SummaryCardStore;
+ public download: DownloadStore;
+ public flyout: FlyoutStore;
+ public flyout_help: FlyoutHelpStore;
+ public google_drive: GoogleDriveStore;
+ public journal: JournalStore;
+ public load_modal: LoadModalStore;
+ public run_panel: RunPanelStore;
+ public save_modal: SaveModalStore;
+ public summary: SummaryStore;
+ public transactions: TransactionsStore;
+ public toolbar: ToolbarStore;
+ public toolbox: ToolboxStore;
+ public quick_strategy: QuickStrategyStore;
+ public route_prompt_dialog: RoutePromptDialogStore;
+ public self_exclusion: SelfExclusionStore;
+ public dashboard: DashboardStore;
+
+ public chart_store: ChartStore;
+ public blockly_store: BlocklyStore;
+ public data_collection_store: DataCollectionStore;
+
+ constructor(core: TRootStore, ws: TWebSocket, dbot: TDbot) {
+ this.ws = ws;
+ this.dbot = dbot;
+ this.app = new AppStore(this, core);
+ this.summary_card = new SummaryCardStore(this, core);
+ this.download = new DownloadStore(this);
+ this.flyout = new FlyoutStore(this);
+ this.flyout_help = new FlyoutHelpStore(this);
+ this.google_drive = new GoogleDriveStore(this);
+ this.journal = new JournalStore(this, core);
+ this.load_modal = new LoadModalStore(this);
+ this.run_panel = new RunPanelStore(this, core);
+ this.save_modal = new SaveModalStore(this);
+ this.summary = new SummaryStore(this);
+ this.transactions = new TransactionsStore(this, core);
+ this.toolbar = new ToolbarStore(this);
+ this.toolbox = new ToolboxStore(this, core);
+ this.quick_strategy = new QuickStrategyStore(this);
+ this.route_prompt_dialog = new RoutePromptDialogStore(this, core);
+ this.self_exclusion = new SelfExclusionStore(this, core);
+ this.dashboard = new DashboardStore(this);
+
+ // need to be at last for dependency
+ this.chart_store = new ChartStore(this);
+ this.blockly_store = new BlocklyStore(this);
+ this.data_collection_store = new DataCollectionStore(this, core);
+ }
+}
diff --git a/packages/bot-web-ui/src/stores/route-prompt-dialog-store.js b/packages/bot-web-ui/src/stores/route-prompt-dialog-store.js
index e3e47c179547..d6d6a56e9644 100644
--- a/packages/bot-web-ui/src/stores/route-prompt-dialog-store.js
+++ b/packages/bot-web-ui/src/stores/route-prompt-dialog-store.js
@@ -1,7 +1,7 @@
-import { observable, action, makeObservable } from 'mobx';
+import { action, makeObservable, observable } from 'mobx';
export default class RoutePromptDialogStore {
- constructor(root_store) {
+ constructor(root_store, core) {
makeObservable(this, {
should_show: observable,
is_confirmed: observable,
@@ -13,6 +13,7 @@ export default class RoutePromptDialogStore {
});
this.root_store = root_store;
+ this.core = core;
}
should_show = false;
@@ -39,6 +40,7 @@ export default class RoutePromptDialogStore {
}
continueRoute() {
- if (this.is_confirmed && this.last_location) this.root_store.common.routeTo(this.last_location.pathname);
+ const { common } = this.core;
+ if (this.is_confirmed && this.last_location) common.routeTo(this.last_location.pathname);
}
}
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 c35b2390467e..8db047bbeec6 100644
--- a/packages/bot-web-ui/src/stores/run-panel-store.js
+++ b/packages/bot-web-ui/src/stores/run-panel-store.js
@@ -1,14 +1,14 @@
import React from 'react';
-import { observable, action, reaction, computed, runInAction, makeObservable } from 'mobx';
-import { localize, Localize } from '@deriv/translations';
-import { error_types, unrecoverable_errors, observer, message_types } from '@deriv/bot-skeleton';
+import { action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
+import { error_types, message_types, observer, unrecoverable_errors } from '@deriv/bot-skeleton';
+import { isSafari, mobileOSDetect } from '@deriv/shared';
+import { Localize, localize } from '@deriv/translations';
import { contract_stages } from 'Constants/contract-stage';
import { run_panel } from 'Constants/run-panel';
import { journalError, switch_account_notification } from 'Utils/bot-notifications';
-import { isSafari, mobileOSDetect } from '@deriv/shared';
export default class RunPanelStore {
- constructor(root_store) {
+ constructor(root_store, core) {
makeObservable(this, {
active_index: observable,
contract_stage: observable,
@@ -19,11 +19,12 @@ export default class RunPanelStore {
is_drawer_open: observable,
is_dialog_open: observable,
is_sell_requested: observable,
+ run_id: observable,
+ error_type: observable,
statistics: computed,
is_stop_button_visible: computed,
is_stop_button_disabled: computed,
is_clear_stat_disabled: computed,
- onRunButtonClick: action.bound,
onStopButtonClick: action.bound,
stopBot: action.bound,
onClearStatClick: action.bound,
@@ -39,7 +40,6 @@ export default class RunPanelStore {
showClearStatDialog: action.bound,
showIncompatibleStrategyDialog: action.bound,
showContractUpdateErrorDialog: action.bound,
- onBotRunningEvent: action.bound,
onBotSellEvent: action.bound,
onBotStopEvent: action.bound,
onBotTradeAgain: action.bound,
@@ -55,10 +55,20 @@ export default class RunPanelStore {
onMount: action.bound,
onUnmount: action.bound,
handleInvalidToken: action.bound,
+ onRunButtonClick: action.bound,
+ registerBotListeners: action.bound,
+ registerReactions: action.bound,
+ onBotRunningEvent: action.bound,
+ unregisterBotListeners: action.bound,
+ clear: action.bound,
+ preloadAudio: action.bound,
+ stopMyBot: action.bound,
+ closeMultiplierContract: action.bound,
});
this.root_store = root_store;
this.dbot = this.root_store.dbot;
+ this.core = core;
this.disposeReactionsFn = this.registerReactions();
}
@@ -137,10 +147,16 @@ export default class RunPanelStore {
}
async onRunButtonClick() {
- const { core, summary_card, route_prompt_dialog, self_exclusion } = this.root_store;
- const { client, ui } = core;
- const is_ios = mobileOSDetect() === 'iOS';
+ if (window.sendRequestsStatistic) {
+ performance.mark('bot-start');
+ window.sendRequestsStatistic(false);
+ performance.clearMeasures();
+ }
+ const { summary_card, route_prompt_dialog, self_exclusion } = this.root_store;
+ const { client, ui } = this.core;
+ const is_ios = mobileOSDetect() === 'iOS';
+ this.dbot.saveRecentWorkspace();
this.dbot.unHighlightAllBlocks();
if (!client.is_logged_in) {
this.showLoginDialog();
@@ -195,7 +211,7 @@ export default class RunPanelStore {
}
stopBot() {
- const { ui } = this.root_store.core;
+ const { ui } = this.core;
this.dbot.stopBot();
@@ -220,6 +236,10 @@ export default class RunPanelStore {
if (this.error_type) {
this.error_type = undefined;
}
+ if (window.sendRequestsStatistic) {
+ window.sendRequestsStatistic(true);
+ performance.clearMeasures();
+ }
}
onClearStatClick() {
@@ -258,9 +278,31 @@ export default class RunPanelStore {
this.is_dialog_open = false;
}
+ stopMyBot() {
+ const { summary_card, quick_strategy } = this.root_store;
+ const { ui } = this.core;
+ const { toggleStopBotDialog } = quick_strategy;
+
+ ui.setPromptHandler(false);
+ this.dbot.terminateBot();
+ this.onCloseDialog();
+ summary_card.clear();
+ toggleStopBotDialog();
+ }
+
+ closeMultiplierContract() {
+ const { quick_strategy } = this.root_store;
+ const { toggleStopBotDialog } = quick_strategy;
+
+ this.onClickSell();
+ this.stopBot();
+ this.onCloseDialog();
+ toggleStopBotDialog();
+ }
+
showStopMultiplierContractDialog() {
- const { summary_card, core } = this.root_store;
- const { ui } = core;
+ const { summary_card } = this.root_store;
+ const { ui } = this.core;
this.onOkButtonClick = () => {
ui.setPromptHandler(false);
@@ -309,8 +351,8 @@ export default class RunPanelStore {
this.onOkButtonClick = this.onCloseDialog;
this.onCancelButtonClick = undefined;
this.dialog_options = {
- title: localize("DBot isn't quite ready for real accounts"),
- message: localize('Please switch to your demo account to run your DBot.'),
+ title: localize("Deriv Bot isn't quite ready for real accounts"),
+ message: localize('Please switch to your demo account to run your Deriv Bot.'),
};
this.is_dialog_open = true;
}
@@ -335,7 +377,7 @@ export default class RunPanelStore {
this.onCancelButtonClick = undefined;
this.dialog_options = {
title: localize('Import error'),
- message: localize('This strategy is currently not compatible with DBot.'),
+ message: localize('This strategy is currently not compatible with Deriv Bot.'),
};
this.is_dialog_open = true;
}
@@ -366,7 +408,7 @@ export default class RunPanelStore {
}
registerReactions() {
- const { client, common, notifications } = this.root_store.core;
+ const { client, common, notifications } = this.core;
const registerIsSocketOpenedListener = () => {
if (common.is_socket_opened) {
@@ -436,7 +478,7 @@ export default class RunPanelStore {
onBotStopEvent() {
const { self_exclusion, summary_card } = this.root_store;
- const { ui } = this.root_store.core;
+ const { ui } = this.core;
const indicateBotStopped = () => {
this.error_type = undefined;
this.setIsRunning(false);
@@ -501,10 +543,10 @@ export default class RunPanelStore {
this.root_store.transactions.setActiveTransactionId(null);
const { buy } = contract_status;
- const { is_virtual } = this.root_store.core.client;
+ const { is_virtual } = this.core.client;
if (!is_virtual) {
- this.root_store.core.gtm.pushDataLayer({ event: 'dbot_purchase', buy_price: buy.buy_price });
+ this.core.gtm.pushDataLayer({ event: 'dbot_purchase', buy_price: buy.buy_price });
}
break;
@@ -555,7 +597,8 @@ export default class RunPanelStore {
}
showErrorMessage(data) {
- const { journal, notifications } = this.root_store;
+ const { journal } = this.root_store;
+ const { notifications } = this.core;
journal.onError(data);
if (journal.journal_filters.some(filter => filter === message_types.ERROR)) {
this.toggleDrawer(true);
@@ -567,7 +610,8 @@ export default class RunPanelStore {
}
switchToJournal() {
- const { journal, notifications } = this.root_store;
+ const { journal } = this.root_store;
+ const { notifications } = this.core;
journal.journal_filters.push(message_types.ERROR);
this.setActiveTabIndex(run_panel.JOURNAL);
this.toggleDrawer(true);
@@ -607,11 +651,13 @@ export default class RunPanelStore {
onUnmount() {
const { journal, summary_card, transactions } = this.root_store;
- this.unregisterBotListeners();
- this.disposeReactionsFn();
- journal.disposeReactionsFn();
- summary_card.disposeReactionsFn();
- transactions.disposeReactionsFn();
+ if (!this.is_running) {
+ this.unregisterBotListeners();
+ this.disposeReactionsFn();
+ journal.disposeReactionsFn();
+ summary_card.disposeReactionsFn();
+ transactions.disposeReactionsFn();
+ }
observer.unregisterAll('ui.log.error');
observer.unregisterAll('ui.log.notify');
@@ -620,7 +666,7 @@ export default class RunPanelStore {
}
async handleInvalidToken() {
- const { client } = this.root_store.core;
+ const { client } = this.core;
await client.logout();
this.setActiveTabIndex(run_panel.SUMMARY);
}
diff --git a/packages/bot-web-ui/src/stores/save-modal-store.js b/packages/bot-web-ui/src/stores/save-modal-store.js
deleted file mode 100644
index f64973d36dec..000000000000
--- a/packages/bot-web-ui/src/stores/save-modal-store.js
+++ /dev/null
@@ -1,88 +0,0 @@
-import { observable, action, makeObservable } from 'mobx';
-import { localize } from '@deriv/translations';
-import { saveWorkspaceToRecent, save_types, save, updateWorkspaceName } from '@deriv/bot-skeleton';
-import { button_status } from 'Constants/button-status';
-
-export default class SaveModalStore {
- is_save_modal_open = false;
- button_status = button_status.NORMAL;
- bot_name;
-
- constructor(root_store) {
- makeObservable(this, {
- is_save_modal_open: observable,
- button_status: observable,
- bot_name: observable,
- toggleSaveModal: action.bound,
- onConfirmSave: action.bound,
- updateBotName: action.bound,
- onDriveConnect: action.bound,
- setButtonStatus: action.bound,
- });
-
- this.root_store = root_store;
- }
-
- toggleSaveModal() {
- if (!this.is_save_modal_open) {
- this.setButtonStatus(button_status.NORMAL);
- }
-
- this.is_save_modal_open = !this.is_save_modal_open;
- }
-
- validateBotName = values => {
- const errors = {};
-
- if (values.bot_name.trim() === '') {
- errors.bot_name = localize('Strategy name cannot be empty');
- }
-
- return errors;
- };
-
- async onConfirmSave({ is_local, save_as_collection, bot_name }) {
- this.setButtonStatus(button_status.LOADING);
-
- const { saveFile } = this.root_store.google_drive;
- const xml = Blockly.Xml.workspaceToDom(Blockly.derivWorkspace);
-
- xml.setAttribute('is_dbot', 'true');
- xml.setAttribute('collection', save_as_collection ? 'true' : 'false');
-
- if (is_local) {
- save(bot_name, save_as_collection, xml);
- } else {
- await saveFile({
- name: bot_name,
- content: Blockly.Xml.domToPrettyText(xml),
- mimeType: 'application/xml',
- });
-
- this.setButtonStatus(button_status.COMPLETED);
- }
-
- this.updateBotName(bot_name);
- saveWorkspaceToRecent(xml, is_local ? save_types.LOCAL : save_types.GOOGLE_DRIVE);
- this.toggleSaveModal();
- }
-
- updateBotName(bot_name) {
- this.bot_name = bot_name;
- updateWorkspaceName();
- }
-
- async onDriveConnect() {
- const { google_drive } = this.root_store;
-
- if (google_drive.is_authorised) {
- google_drive.signOut();
- } else {
- google_drive.signIn();
- }
- }
-
- setButtonStatus(status) {
- this.button_status = status;
- }
-}
diff --git a/packages/bot-web-ui/src/stores/save-modal-store.ts b/packages/bot-web-ui/src/stores/save-modal-store.ts
new file mode 100644
index 000000000000..0ea7b4f44f2f
--- /dev/null
+++ b/packages/bot-web-ui/src/stores/save-modal-store.ts
@@ -0,0 +1,181 @@
+import localForage from 'localforage';
+import LZString from 'lz-string';
+import { action, makeObservable, observable } from 'mobx';
+import {
+ getSavedWorkspaces,
+ observer as globalObserver,
+ save,
+ save_types,
+ saveWorkspaceToRecent,
+ updateWorkspaceName,
+} from '@deriv/bot-skeleton';
+import { localize } from '@deriv/translations';
+import { MAX_STRATEGIES } from 'Constants/bot-contents';
+import { button_status } from 'Constants/button-status';
+import RootStore from './root-store';
+
+interface ISaveModalStore {
+ is_save_modal_open: boolean;
+ button_status: { [key: string]: string } | number;
+ bot_name: { [key: string]: string } | string;
+ toggleSaveModal: () => void;
+ validateBotName: (values: string) => { [key: string]: string };
+ onConfirmSave: () => void;
+ updateBotName: (bot_name: { [key: string]: string } | string) => void;
+ setButtonStatus: (status: { [key: string]: string } | string | number) => void;
+}
+
+export default class SaveModalStore implements ISaveModalStore {
+ root_store: RootStore;
+
+ constructor(root_store: RootStore) {
+ makeObservable(this, {
+ is_save_modal_open: observable,
+ button_status: observable,
+ bot_name: observable,
+ toggleSaveModal: action.bound,
+ validateBotName: action.bound,
+ onConfirmSave: action.bound,
+ updateBotName: action.bound,
+ onDriveConnect: action.bound,
+ setButtonStatus: action.bound,
+ });
+
+ this.root_store = root_store;
+ }
+ is_save_modal_open = false;
+ button_status = button_status.NORMAL;
+ bot_name = '';
+
+ toggleSaveModal = (): void => {
+ if (!this.is_save_modal_open) {
+ this.setButtonStatus(button_status.NORMAL);
+ }
+
+ this.is_save_modal_open = !this.is_save_modal_open;
+ };
+
+ validateBotName = (values: string): { [key: string]: string } => {
+ const errors = {};
+
+ if (values.bot_name.trim() === '') {
+ errors.bot_name = localize('Strategy name cannot be empty');
+ }
+
+ return errors;
+ };
+
+ addStrategyToWorkspace = async (
+ workspace_id: string,
+ is_local: boolean,
+ save_as_collection: boolean,
+ bot_name: string,
+ xml: string
+ ) => {
+ try {
+ const workspace = await getSavedWorkspaces();
+ const current_workspace_index = workspace.findIndex(strategy => strategy.id === workspace_id);
+ const {
+ load_modal: { getSaveType },
+ } = this.root_store;
+ const local_type = is_local ? save_types.LOCAL : save_types.GOOGLE_DRIVE;
+ const save_collection = save_as_collection ? save_types.UNSAVED : local_type;
+ const type = save_collection;
+
+ const save_type = getSaveType(type)?.toLowerCase();
+
+ const workspace_structure = {
+ id: workspace_id,
+ xml: Blockly.Xml.domToText(xml),
+ name: bot_name,
+ timestamp: Date.now(),
+ save_type,
+ };
+
+ if (current_workspace_index >= 0) {
+ const current_workspace = workspace_structure;
+ workspace[current_workspace_index] = current_workspace;
+ } else {
+ workspace.push(workspace_structure);
+ }
+
+ workspace
+ .sort((a, b) => {
+ return new Date(a.timestamp) - new Date(b.timestamp);
+ })
+ .reverse();
+
+ if (workspace.length > MAX_STRATEGIES) {
+ workspace.pop();
+ }
+ const {
+ load_modal: { setRecentStrategies },
+ } = this.root_store;
+ localForage.setItem('saved_workspaces', LZString.compress(JSON.stringify(workspace)));
+ const updated_strategies = await getSavedWorkspaces();
+ setRecentStrategies(updated_strategies);
+ const {
+ dashboard: { setStrategySaveType },
+ } = this.root_store;
+ setStrategySaveType(save_type);
+ } catch (error) {
+ globalObserver.emit('Error', error);
+ }
+ };
+
+ async onConfirmSave({ is_local, save_as_collection, bot_name }) {
+ this.setButtonStatus(button_status.LOADING);
+
+ const { saveFile } = this.root_store.google_drive;
+ const xml = Blockly.Xml.workspaceToDom(Blockly.derivWorkspace);
+
+ xml.setAttribute('is_dbot', 'true');
+ xml.setAttribute('collection', save_as_collection ? 'true' : 'false');
+
+ if (is_local) {
+ save(bot_name, save_as_collection, xml);
+ } else {
+ await saveFile({
+ name: bot_name,
+ content: Blockly.Xml.domToPrettyText(xml),
+ mimeType: 'application/xml',
+ });
+
+ this.setButtonStatus(button_status.COMPLETED);
+ }
+ const {
+ dashboard: { active_tab },
+ } = this.root_store;
+ const {
+ load_modal: { selected_strategy_id },
+ } = this.root_store;
+
+ if (active_tab === 0) {
+ const workspace_id = selected_strategy_id || Blockly.utils.genUid();
+ this.addStrategyToWorkspace(workspace_id, is_local, save_as_collection, bot_name, xml);
+ } else {
+ saveWorkspaceToRecent(xml, is_local ? save_types.LOCAL : save_types.GOOGLE_DRIVE);
+ }
+ this.updateBotName(bot_name);
+ this.toggleSaveModal();
+ }
+
+ updateBotName = (bot_name: { [key: string]: string } | string): void => {
+ this.bot_name = bot_name;
+ updateWorkspaceName();
+ };
+
+ async onDriveConnect() {
+ const { google_drive } = this.root_store;
+
+ if (google_drive.is_authorised) {
+ google_drive.signOut();
+ } else {
+ google_drive.signIn();
+ }
+ }
+
+ setButtonStatus = (status: { [key: string]: string } | string | number): void => {
+ this.button_status = status;
+ };
+}
diff --git a/packages/bot-web-ui/src/stores/self-exclusion-store.js b/packages/bot-web-ui/src/stores/self-exclusion-store.js
index 8ed7a5181d9f..91bec112bd0d 100644
--- a/packages/bot-web-ui/src/stores/self-exclusion-store.js
+++ b/packages/bot-web-ui/src/stores/self-exclusion-store.js
@@ -1,7 +1,7 @@
-import { observable, action, computed, makeObservable } from 'mobx';
+import { action, computed, makeObservable, observable } from 'mobx';
export default class SelfExclusionStore {
- constructor(root_store) {
+ constructor(root_store, core) {
makeObservable(this, {
api_max_losses: observable,
run_limit: observable,
@@ -16,6 +16,7 @@ export default class SelfExclusionStore {
});
this.root_store = root_store;
+ this.core = core;
}
api_max_losses = 0;
@@ -29,7 +30,7 @@ export default class SelfExclusionStore {
}
get should_bot_run() {
- const { client } = this.root_store.core;
+ const { client } = this.core;
if (client.is_eu && !client.is_virtual && (this.api_max_losses === 0 || this.run_limit === -1)) {
return false;
}
@@ -56,7 +57,7 @@ export default class SelfExclusionStore {
}
async checkRestriction() {
- const { client } = this.root_store.core;
+ const { client } = this.core;
await client.getSelfExclusion();
if (client.self_exclusion.max_losses) {
this.setApiMaxLosses(client.self_exclusion.max_losses);
diff --git a/packages/bot-web-ui/src/stores/summary-card-store.js b/packages/bot-web-ui/src/stores/summary-card-store.js
index 1d7566ff3b0a..daa95f947e82 100644
--- a/packages/bot-web-ui/src/stores/summary-card-store.js
+++ b/packages/bot-web-ui/src/stores/summary-card-store.js
@@ -1,7 +1,7 @@
-import { action, computed, observable, reaction, makeObservable } from 'mobx';
+import { action, computed, makeObservable, observable, reaction } from 'mobx';
import { getIndicativePrice, isEqualObject, isMultiplierContract } from '@deriv/shared';
-import { contract_stages } from 'Constants/contract-stage';
import { getValidationRules } from 'Constants/contract';
+import { contract_stages } from 'Constants/contract-stage';
import { getContractUpdateConfig } from 'Utils/multiplier';
import Validator from 'Utils/validator';
@@ -24,7 +24,7 @@ export default class SummaryCardStore {
profit = 0;
indicative = 0;
- constructor(root_store) {
+ constructor(root_store, core) {
makeObservable(this, {
contract_info: observable,
indicative_movement: observable,
@@ -35,6 +35,10 @@ export default class SummaryCardStore {
contract_update_stop_loss: observable,
has_contract_update_take_profit: observable,
has_contract_update_stop_loss: observable,
+ contract_update_config: observable,
+ contract_id: observable,
+ profit: observable,
+ indicative: observable,
is_contract_completed: computed,
is_contract_loading: computed,
is_contract_inactive: computed,
@@ -44,15 +48,16 @@ export default class SummaryCardStore {
getLimitOrder: action.bound,
onBotContractEvent: action.bound,
onChange: action.bound,
- populateConfig: action.bound,
populateContractUpdateConfig: action.bound,
setContractUpdateConfig: action.bound,
updateLimitOrder: action.bound,
setValidationErrorMessages: action,
validateProperty: action,
+ registerReactions: action.bound,
});
this.root_store = root_store;
+ this.core = core;
this.disposeReactionsFn = this.registerReactions();
}
@@ -141,14 +146,6 @@ export default class SummaryCardStore {
this.validateProperty(name, this[name]);
}
- populateConfig(contract_info) {
- this.contract_info = contract_info;
-
- if (this.is_multiplier && contract_info.contract_id && contract_info.limit_order) {
- this.populateContractUpdateConfig(this.contract_info);
- }
- }
-
populateContractUpdateConfig(response) {
const contract_update_config = getContractUpdateConfig(response);
@@ -228,7 +225,7 @@ export default class SummaryCardStore {
}
registerReactions() {
- const { client } = this.root_store.core;
+ const { client } = this.core;
this.disposeSwitchAcountListener = reaction(
() => client.loginid,
() => this.clear()
diff --git a/packages/bot-web-ui/src/stores/toolbar-store.js b/packages/bot-web-ui/src/stores/toolbar-store.js
deleted file mode 100644
index 8f616783dc82..000000000000
--- a/packages/bot-web-ui/src/stores/toolbar-store.js
+++ /dev/null
@@ -1,92 +0,0 @@
-import { action, observable, makeObservable } from 'mobx';
-import { runGroupedEvents, load, config } from '@deriv/bot-skeleton';
-
-export default class ToolbarStore {
- constructor(root_store) {
- makeObservable(this, {
- is_animation_info_modal_open: observable,
- is_dialog_open: observable,
- file_name: observable,
- has_undo_stack: observable,
- has_redo_stack: observable,
- toggleAnimationInfoModal: action.bound,
- onResetClick: action.bound,
- closeResetDialog: action.bound,
- onResetOkButtonClick: action.bound,
- onUndoClick: action.bound,
- setHasUndoStack: action.bound,
- setHasRedoStack: action.bound,
- });
-
- this.root_store = root_store;
- }
-
- is_animation_info_modal_open = false;
- is_dialog_open = false;
- file_name = config.default_file_name;
- has_undo_stack = false;
- has_redo_stack = false;
-
- toggleAnimationInfoModal() {
- this.is_animation_info_modal_open = !this.is_animation_info_modal_open;
- }
-
- onResetClick() {
- this.is_dialog_open = true;
- }
-
- closeResetDialog() {
- this.is_dialog_open = false;
- }
-
- onResetOkButtonClick() {
- runGroupedEvents(
- false,
- () => {
- const workspace = Blockly.derivWorkspace;
- workspace.current_strategy_id = Blockly.utils.genUid();
- load({
- block_string: workspace.cached_xml.main,
- file_name: config.default_file_name,
- workspace,
- });
- },
- 'reset'
- );
- this.is_dialog_open = false;
-
- const { run_panel } = this.root_store;
- if (run_panel.is_running) {
- this.root_store.run_panel.stopBot();
- }
- }
-
- onSortClick = () => {
- Blockly.derivWorkspace.cleanUp();
- };
-
- onUndoClick(is_redo) {
- Blockly.Events.setGroup('undo_clicked');
- Blockly.derivWorkspace.undo(is_redo);
- Blockly.svgResize(Blockly.derivWorkspace); // Called for CommentDelete event.
- this.setHasRedoStack();
- this.setHasUndoStack();
- Blockly.Events.setGroup(false);
- }
-
- onZoomInOutClick = is_zoom_in => {
- const workspace = Blockly.derivWorkspace;
- const metrics = workspace.getMetrics();
- const addition = is_zoom_in ? 1 : -1;
-
- workspace.zoom(metrics.viewWidth / 2, metrics.viewHeight / 2, addition);
- };
-
- setHasUndoStack() {
- this.has_undo_stack = Blockly.derivWorkspace.hasUndoStack();
- }
-
- setHasRedoStack() {
- this.has_redo_stack = Blockly.derivWorkspace?.hasRedoStack();
- }
-}
diff --git a/packages/bot-web-ui/src/stores/toolbar-store.ts b/packages/bot-web-ui/src/stores/toolbar-store.ts
new file mode 100644
index 000000000000..e614e5286b37
--- /dev/null
+++ b/packages/bot-web-ui/src/stores/toolbar-store.ts
@@ -0,0 +1,113 @@
+import { action, makeObservable, observable } from 'mobx';
+import { config, load, runGroupedEvents } from '@deriv/bot-skeleton';
+import RootStore from './root-store';
+
+interface IToolbarStore {
+ is_animation_info_modal_open: boolean;
+ is_dialog_open: boolean;
+ file_name: { [key: string]: string };
+ has_undo_stack: boolean;
+ has_redo_stack: boolean;
+ toggleAnimationInfoModal: () => void;
+ onResetClick: () => void;
+ closeResetDialog: () => void;
+ onResetOkButtonClick: () => void;
+ onSortClick: () => void;
+ onUndoClick: (is_redo: boolean) => void;
+ onZoomInOutClick: (is_zoom_in: boolean) => void;
+ setHasUndoStack: () => void;
+ setHasRedoStack: () => void;
+}
+
+export default class ToolbarStore implements IToolbarStore {
+ root_store: RootStore;
+
+ constructor(root_store: RootStore) {
+ makeObservable(this, {
+ is_animation_info_modal_open: observable,
+ is_dialog_open: observable,
+ file_name: observable,
+ has_undo_stack: observable,
+ has_redo_stack: observable,
+ toggleAnimationInfoModal: action.bound,
+ onResetClick: action.bound,
+ closeResetDialog: action.bound,
+ onResetOkButtonClick: action.bound,
+ onUndoClick: action.bound,
+ setHasUndoStack: action.bound,
+ setHasRedoStack: action.bound,
+ });
+
+ this.root_store = root_store;
+ }
+
+ is_animation_info_modal_open = false;
+ is_dialog_open = false;
+ file_name = config.default_file_name;
+ 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;
+ };
+
+ closeResetDialog = (): void => {
+ this.is_dialog_open = false;
+ };
+
+ onResetOkButtonClick = (): void => {
+ runGroupedEvents(
+ false,
+ () => {
+ const workspace = Blockly.derivWorkspace;
+ workspace.current_strategy_id = Blockly.utils.genUid();
+ load({
+ block_string: workspace.cached_xml.main,
+ file_name: config.default_file_name,
+ workspace,
+ });
+ Blockly.derivWorkspace.strategy_to_load = workspace.cached_xml.main;
+ },
+ 'reset'
+ );
+ this.is_dialog_open = false;
+
+ const { run_panel } = this.root_store;
+ if (run_panel.is_running) {
+ this.root_store.run_panel.stopBot();
+ }
+ };
+
+ onSortClick = () => {
+ Blockly.derivWorkspace.cleanUp();
+ };
+
+ onUndoClick = (is_redo: boolean): void => {
+ Blockly.Events.setGroup('undo_clicked');
+ Blockly.derivWorkspace.undo(is_redo);
+ Blockly.svgResize(Blockly.derivWorkspace); // Called for CommentDelete event.
+ this.setHasRedoStack();
+ this.setHasUndoStack();
+ Blockly.Events.setGroup(false);
+ };
+
+ onZoomInOutClick = (is_zoom_in: boolean): void => {
+ const workspace = Blockly.derivWorkspace;
+ const metrics = workspace.getMetrics();
+ const addition = is_zoom_in ? 1 : -1;
+
+ workspace.zoom(metrics.viewWidth / 2, metrics.viewHeight / 2, addition);
+ };
+
+ setHasUndoStack = (): void => {
+ this.has_undo_stack = Blockly.derivWorkspace?.hasUndoStack();
+ };
+
+ setHasRedoStack = (): void => {
+ this.has_redo_stack = Blockly.derivWorkspace?.hasRedoStack();
+ };
+}
diff --git a/packages/bot-web-ui/src/stores/toolbox-store.js b/packages/bot-web-ui/src/stores/toolbox-store.js
index 8c18cda6dfb3..b1a5f1ebc26a 100644
--- a/packages/bot-web-ui/src/stores/toolbox-store.js
+++ b/packages/bot-web-ui/src/stores/toolbox-store.js
@@ -1,10 +1,10 @@
-import { observable, action, reaction, makeObservable } from 'mobx';
-import { isMobile, isTabletDrawer } from '@deriv/shared';
-import { localize } from '@deriv/translations';
+import { action, makeObservable, observable, reaction } from 'mobx';
import { scrollWorkspace } from '@deriv/bot-skeleton';
+import { isMobile } from '@deriv/shared';
+import { localize } from '@deriv/translations';
export default class ToolboxStore {
- constructor(root_store) {
+ constructor(root_store, core) {
makeObservable(this, {
is_toolbox_open: observable,
is_search_loading: observable,
@@ -30,6 +30,7 @@ export default class ToolboxStore {
});
this.root_store = root_store;
+ this.core = core;
}
is_toolbox_open = true;
@@ -40,25 +41,22 @@ export default class ToolboxStore {
toolbox_examples = null;
onMount(toolbox_ref) {
- const { core } = this.root_store;
this.adjustWorkspace();
- if (!isTabletDrawer()) {
- this.toolbox_dom = Blockly.Xml.textToDom(toolbox_ref?.current);
- this.toolbox_examples = [...this.toolbox_dom.childNodes].find(el => el.tagName === 'examples');
- this.setWorkspaceOptions();
- this.disposeToolboxToggleReaction = reaction(
- () => this.is_toolbox_open,
- is_toolbox_open => {
- if (is_toolbox_open) {
- this.adjustWorkspace();
- // Emit event to GTM
- const { gtm } = core;
- gtm.pushDataLayer({ event: 'dbot_toolbox_visible', value: true });
- }
+ this.toolbox_dom = Blockly.Xml.textToDom(toolbox_ref?.current);
+ this.toolbox_examples = [...this.toolbox_dom.childNodes].find(el => el.tagName === 'examples');
+ this.setWorkspaceOptions();
+ this.disposeToolboxToggleReaction = reaction(
+ () => this.is_toolbox_open,
+ is_toolbox_open => {
+ if (is_toolbox_open) {
+ this.adjustWorkspace();
+ // Emit event to GTM
+ const { gtm } = this.core;
+ gtm.pushDataLayer({ event: 'dbot_toolbox_visible', value: true });
}
- );
- }
+ }
+ );
}
onUnmount() {
diff --git a/packages/bot-web-ui/src/stores/transactions-store.js b/packages/bot-web-ui/src/stores/transactions-store.js
index a9d4baaecea4..f1a6299903f0 100644
--- a/packages/bot-web-ui/src/stores/transactions-store.js
+++ b/packages/bot-web-ui/src/stores/transactions-store.js
@@ -1,31 +1,40 @@
-import { action, computed, observable, reaction, makeObservable } from 'mobx';
-import { formatDate, isEnded } from '@deriv/shared';
+import { action, computed, makeObservable, observable, reaction } from 'mobx';
import { log_types } from '@deriv/bot-skeleton';
+import { formatDate, isBot, isEnded } from '@deriv/shared';
import { transaction_elements } from '../constants/transactions';
import { getStoredItemsByKey, getStoredItemsByUser, setStoredItemsByKey } from '../utils/session-storage';
export default class TransactionsStore {
- constructor(root_store) {
+ constructor(root_store, core) {
makeObservable(this, {
elements: observable,
active_transaction_id: observable,
+ recovered_completed_transactions: observable,
+ recovered_transactions: observable,
+ is_called_proposal_open_contract: observable,
transactions: computed,
onBotContractEvent: action.bound,
pushTransaction: action.bound,
- setActiveTransactionId: action.bound,
onClickOutsideTransaction: action.bound,
onMount: action.bound,
onUnmount: action.bound,
clear: action.bound,
+ setActiveTransactionId: action.bound,
+ registerReactions: action.bound,
+ recoverPendingContracts: action.bound,
+ updateResultsCompletedContract: action.bound,
+ sortOutPositionsBeforeAction: action.bound,
+ recoverPendingContractsById: action.bound,
});
this.root_store = root_store;
+ this.core = core;
this.disposeReactionsFn = this.registerReactions();
}
TRANSACTION_CACHE = 'transaction_cache';
- elements = getStoredItemsByUser(this.TRANSACTION_CACHE, this.root_store?.core.client.loginid, []);
+ elements = getStoredItemsByUser(this.TRANSACTION_CACHE, this.core?.client.loginid, []);
active_transaction_id = null;
recovered_completed_transactions = [];
recovered_transactions = [];
@@ -138,7 +147,7 @@ export default class TransactionsStore {
}
registerReactions() {
- const { client } = this.root_store?.core;
+ const { client } = this.core;
// Write transactions to session storage on each change in transaction elements.
const disposeTransactionElementsListener = reaction(
@@ -150,12 +159,6 @@ export default class TransactionsStore {
}
);
- // Attempt to load cached transactions on client loginid change.
- const disposeClientLoginIdListener = reaction(
- () => client.loginid,
- () => (this.elements = getStoredItemsByUser(this.TRANSACTION_CACHE, client.loginid, []))
- );
-
// User could've left the page mid-contract. On initial load, try
// to recover any pending contracts so we can reflect accurate stats
// and transactions.
@@ -166,7 +169,6 @@ export default class TransactionsStore {
return () => {
disposeTransactionElementsListener();
- disposeClientLoginIdListener();
disposeRecoverContracts();
};
}
@@ -210,16 +212,20 @@ export default class TransactionsStore {
}
recoverPendingContractsById(contract_id) {
- const { ws, core } = this.root_store;
- const positions = core.portfolio.positions;
-
- ws.authorized.subscribeProposalOpenContract(contract_id, response => {
- this.is_called_proposal_open_contract = true;
- if (!response.error) {
- const { proposal_open_contract } = response;
- this.updateResultsCompletedContract(proposal_open_contract);
- }
- });
+ const { ws } = this.root_store;
+ const positions = this.core.portfolio.positions;
+
+ // TODO: the idea is to remove the POC calls completely
+ // but adding this check to prevent making POC calls only for bot as of now
+ if (!isBot()) {
+ ws.authorized.subscribeProposalOpenContract(contract_id, response => {
+ this.is_called_proposal_open_contract = true;
+ if (!response.error) {
+ const { proposal_open_contract } = response;
+ this.updateResultsCompletedContract(proposal_open_contract);
+ }
+ });
+ }
if (!this.is_called_proposal_open_contract) {
if (!this.elements.length) {
diff --git a/packages/bot-web-ui/src/stores/useDBotStore.tsx b/packages/bot-web-ui/src/stores/useDBotStore.tsx
new file mode 100644
index 000000000000..53f9fdf1ce26
--- /dev/null
+++ b/packages/bot-web-ui/src/stores/useDBotStore.tsx
@@ -0,0 +1,26 @@
+import React, { createContext, PropsWithChildren, useContext, useMemo } from 'react';
+import { DBot } from '@deriv/bot-skeleton';
+import { useStore } from '@deriv/stores';
+import type { TWebSocket } from 'Types';
+import RootStore from './root-store';
+
+const DBotStoreContext = createContext(null);
+
+const DBotStoreProvider = ({ children, ws }: PropsWithChildren<{ ws: TWebSocket }>) => {
+ const stores = useStore();
+ const memoizedValue = useMemo(() => new RootStore(stores, ws, DBot), []);
+
+ return {children} ;
+};
+
+const useDBotStore = () => {
+ const store = useContext(DBotStoreContext);
+
+ if (!store) {
+ throw new Error('useDBotStore must be used within DBotStoreProvider');
+ }
+
+ return store;
+};
+
+export { DBotStoreProvider, useDBotStore };
diff --git a/packages/bot-web-ui/src/types/dbot.types.ts b/packages/bot-web-ui/src/types/dbot.types.ts
new file mode 100644
index 000000000000..ccec44a600dd
--- /dev/null
+++ b/packages/bot-web-ui/src/types/dbot.types.ts
@@ -0,0 +1,31 @@
+// TODO: need to convert Dbot class to TS and write complete types
+export type TDbot = {
+ interpreter: unknown;
+ workspace: Blockly.WorkspaceSvg | null;
+ before_run_funcs: (() => boolean)[];
+ initWorkspace: (
+ public_path: string,
+ store: unknown,
+ api_helpers_store: unknown,
+ is_mobile: boolean
+ ) => Promise;
+ saveRecentWorkspace: () => void;
+ addBeforeRunFunction: (func: () => void) => void;
+ shouldRunBot: () => boolean;
+ runBot: () => void;
+ generateCode: (limitations?: Record) => string;
+ stopBot: () => void;
+ terminateBot: () => void;
+ terminateConnection: () => void;
+ unselectBlocks: () => boolean;
+ disableStrayBlocks: () => boolean;
+ disableBlocksRecursively: (block: Blockly.Block) => void;
+ checkForErroredBlocks: () => boolean;
+ centerAndHighlightBlock: (block_id: string, should_animate?: boolean) => void;
+ unHighlightAllBlocks: () => void;
+ checkForRequiredBlocks: () => boolean;
+ valueInputLimitationsListener: (event: any, force_check?: boolean) => void | boolean;
+ getStrategySounds: () => unknown[];
+ handleDragOver?: (event: any) => void;
+ handleDropOver?: (event: any, handleFileChange: () => void) => void;
+};
diff --git a/packages/bot-web-ui/src/types/index.ts b/packages/bot-web-ui/src/types/index.ts
new file mode 100644
index 000000000000..f84266305db1
--- /dev/null
+++ b/packages/bot-web-ui/src/types/index.ts
@@ -0,0 +1,3 @@
+export * from './dbot.types';
+export * from './root-stores.types';
+export * from './ws.types';
diff --git a/packages/bot-web-ui/src/types/root-stores.types.ts b/packages/bot-web-ui/src/types/root-stores.types.ts
new file mode 100644
index 000000000000..4473cd37193d
--- /dev/null
+++ b/packages/bot-web-ui/src/types/root-stores.types.ts
@@ -0,0 +1,9 @@
+import type { TCoreStores } from '@deriv/stores/types';
+
+/**
+ * @deprecated - Use `TStores` from `@deriv/stores` instead of this type.
+ */
+export type TRootStore = TCoreStores & {
+ gtm?: Record;
+ portfolio?: Record;
+};
diff --git a/packages/bot-web-ui/src/types/ws.types.ts b/packages/bot-web-ui/src/types/ws.types.ts
new file mode 100644
index 000000000000..b18aeb644fe1
--- /dev/null
+++ b/packages/bot-web-ui/src/types/ws.types.ts
@@ -0,0 +1,42 @@
+import {
+ PriceProposalOpenContractsRequest,
+ PriceProposalOpenContractsResponse,
+ TicksStreamRequest,
+ TicksStreamResponse,
+ UpdateContractRequest,
+ UpdateContractResponse,
+} from '@deriv/api-types';
+
+export type TServerError = {
+ code: string;
+ message: string;
+ details?: { [key: string]: string };
+ fields?: string[];
+};
+
+type TWebSocketCall = {
+ subscribeProposalOpenContract: (
+ contract_id: PriceProposalOpenContractsRequest['contract_id'],
+ callback: (response: PriceProposalOpenContractsResponse) => void
+ ) => void;
+ send?: (req?: Record) => Promise<{ error?: TServerError & Record }>;
+};
+
+export type TWebSocket = {
+ authorized: TWebSocketCall;
+ storage: {
+ send?: (req?: Record) => Promise<{ error?: TServerError & Record }>;
+ };
+ contractUpdate: (
+ contract_id: UpdateContractRequest['contract_id'],
+ limit_order: UpdateContractRequest['limit_order']
+ ) => Promise<{ error?: TServerError } & UpdateContractResponse>;
+ subscribeTicksHistory: (
+ req: TicksStreamRequest,
+ callback: () => void
+ ) => Promise<{ error?: TServerError } & TicksStreamResponse>;
+ forgetStream: (stream_id: string) => void;
+ activeSymbols: (mode?: 'string') => void;
+
+ send?: (req?: Record) => Promise<{ error?: TServerError & Record }>;
+};
diff --git a/packages/bot-web-ui/src/utils/bot-notifications.js b/packages/bot-web-ui/src/utils/bot-notifications.js
index 6f70cbde58ed..51cfd9571758 100644
--- a/packages/bot-web-ui/src/utils/bot-notifications.js
+++ b/packages/bot-web-ui/src/utils/bot-notifications.js
@@ -1,11 +1,11 @@
-import { localize } from '@deriv/translations';
import { platform_name } from '@deriv/shared';
+import { localize } from '@deriv/translations';
export const switch_account_notification = {
key: 'bot_switch_account',
header: localize('You have switched accounts.'),
message: localize(
- 'Our system will finish any DBot trades that are running, and DBot will not place any new trades.'
+ 'Our system will finish any Deriv Bot trades that are running, and Deriv Bot will not place any new trades.'
),
type: 'warning',
is_persistent: true,
diff --git a/packages/bot-web-ui/src/utils/help-content/help-strings/before_purchase.js b/packages/bot-web-ui/src/utils/help-content/help-strings/before_purchase.js
index fdb282fddfbf..ea58194631fd 100644
--- a/packages/bot-web-ui/src/utils/help-content/help-strings/before_purchase.js
+++ b/packages/bot-web-ui/src/utils/help-content/help-strings/before_purchase.js
@@ -3,7 +3,7 @@ import { localize } from '@deriv/translations';
export default {
text: [
localize(
- 'This block is mandatory. Only one copy of this block is allowed. It is added to the canvas by default when you open DBot.'
+ 'This block is mandatory. Only one copy of this block is allowed. It is added to the canvas by default when you open Deriv Bot.'
),
localize(
'After defining trade parameters and trade options, you may want to instruct your bot to purchase contracts when specific conditions are met. To do that you can use conditional blocks and indicators blocks to help your bot to make decisions.'
diff --git a/packages/bot-web-ui/src/utils/help-content/help-strings/index.js b/packages/bot-web-ui/src/utils/help-content/help-strings/index.js
index 56634f403146..6fde9938fd8e 100644
--- a/packages/bot-web-ui/src/utils/help-content/help-strings/index.js
+++ b/packages/bot-web-ui/src/utils/help-content/help-strings/index.js
@@ -36,6 +36,6 @@ export { default as todatetime } from './todatetime';
export { default as totimestamp } from './totimestamp';
export { default as trade_again } from './trade_again';
export { default as trade_definition } from './trade_definition';
-export { default as trade_definition_tradeoptions } from './trade_definition_tradeoptions';
export { default as trade_definition_multiplier } from './trade_definition_multiplier';
+export { default as trade_definition_tradeoptions } from './trade_definition_tradeoptions';
export { default as variables_set } from './variables_set';
diff --git a/packages/bot-web-ui/src/utils/help-content/help-strings/notify_telegram.js b/packages/bot-web-ui/src/utils/help-content/help-strings/notify_telegram.js
index 9279be0d10ba..cfa529529cbe 100644
--- a/packages/bot-web-ui/src/utils/help-content/help-strings/notify_telegram.js
+++ b/packages/bot-web-ui/src/utils/help-content/help-strings/notify_telegram.js
@@ -20,7 +20,7 @@ export default {
),
localize('- Find the chat ID property in the response, and copy the value of the id property'),
localize(
- '4. Come back to DBot and add the Notify Telegram block to the workspace. Paste the Telegram API token and chat ID into the block fields accordingly.'
+ '4. Come back to Deriv Bot and add the Notify Telegram block to the workspace. Paste the Telegram API token and chat ID into the block fields accordingly.'
),
],
};
diff --git a/packages/bot-web-ui/src/utils/help-content/help-strings/trade_definition_multiplier.js b/packages/bot-web-ui/src/utils/help-content/help-strings/trade_definition_multiplier.js
index 67d94376cd77..31c5e31af256 100644
--- a/packages/bot-web-ui/src/utils/help-content/help-strings/trade_definition_multiplier.js
+++ b/packages/bot-web-ui/src/utils/help-content/help-strings/trade_definition_multiplier.js
@@ -1,6 +1,6 @@
import React from 'react';
-import { localize, Localize } from '@deriv/translations';
import { StaticUrl } from '@deriv/components';
+import { Localize, localize } from '@deriv/translations';
export default {
text: [
diff --git a/packages/bot-web-ui/src/utils/journal-notifications.js b/packages/bot-web-ui/src/utils/journal-notifications.js
index 793de19203a5..ea469f70e0c0 100644
--- a/packages/bot-web-ui/src/utils/journal-notifications.js
+++ b/packages/bot-web-ui/src/utils/journal-notifications.js
@@ -1,5 +1,5 @@
import { localize } from '@deriv/translations';
-import { messageWithButton, arrayAsMessage } from 'Components';
+import { arrayAsMessage, messageWithButton } from 'Components';
const showErrorMessageWithButton = (message, block_id, showErrorMessage, centerAndHighlightBlock) => {
showErrorMessage(
diff --git a/packages/bot-web-ui/src/utils/settings.js b/packages/bot-web-ui/src/utils/settings.js
index f5d79ae1f68b..336d5e580096 100644
--- a/packages/bot-web-ui/src/utils/settings.js
+++ b/packages/bot-web-ui/src/utils/settings.js
@@ -18,3 +18,10 @@ export const storeSetting = (key, value) => {
settings[key] = value;
localStorage.setItem('dbot_settings', JSON.stringify(settings));
};
+
+export const removeKeyValue = key => {
+ const settings = getSettingsFromLocal() || {};
+ delete settings[key];
+
+ localStorage.setItem('dbot_settings', JSON.stringify(settings));
+};
diff --git a/packages/bot-web-ui/tsconfig.json b/packages/bot-web-ui/tsconfig.json
index 88a9e9ea389d..dde6f4b634ed 100644
--- a/packages/bot-web-ui/tsconfig.json
+++ b/packages/bot-web-ui/tsconfig.json
@@ -1,6 +1,7 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
+ "experimentalDecorators": true,
"outDir": "./dist",
"baseUrl": "./",
"paths": {
@@ -8,7 +9,8 @@
"Constants/*": ["src/constants/*"],
"Stores/*": ["src/stores/*"],
"Utils/*": ["src/utils/*"],
- "@deriv/*": ["../*/src"]
+ "@deriv/*": ["../*/src"],
+ "Types": ["src/types"],
}
},
"include": ["src", "../../utils.d.ts"]
diff --git a/packages/bot-web-ui/webpack.config.js b/packages/bot-web-ui/webpack.config.js
index b2b98dee19de..9dc1a21b863b 100644
--- a/packages/bot-web-ui/webpack.config.js
+++ b/packages/bot-web-ui/webpack.config.js
@@ -1,11 +1,13 @@
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
+const DefinePlugin = require('webpack').DefinePlugin;
+const Dotenv = require('dotenv-webpack');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const path = require('path');
const StyleLintPlugin = require('stylelint-webpack-plugin');
const SpriteLoaderPlugin = require('svg-sprite-loader/plugin');
-const is_release = process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'staging';
+const IS_RELEASE = process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'staging';
const output = {
path: path.resolve(__dirname, 'dist'),
@@ -29,8 +31,8 @@ module.exports = function (env) {
publicPath: '/dist/',
disableHostCheck: true,
},
- mode: is_release ? 'production' : 'development',
- devtool: is_release ? undefined : 'eval-cheap-module-source-map',
+ mode: IS_RELEASE ? 'production' : 'development',
+ devtool: IS_RELEASE ? 'source-map' : 'eval-cheap-module-source-map',
target: 'web',
module: {
rules: [
@@ -50,13 +52,13 @@ module.exports = function (env) {
{
loader: 'css-loader',
options: {
- sourceMap: true,
+ sourceMap: !IS_RELEASE,
url: false,
},
},
{
loader: 'sass-loader',
- options: { sourceMap: true },
+ options: { sourceMap: !IS_RELEASE },
},
{
loader: 'sass-resources-loader',
@@ -85,11 +87,6 @@ module.exports = function (env) {
},
],
},
- {
- test: /\.(js|jsx|ts|tsx)$/,
- exclude: /node_modules/,
- loader: '@deriv/shared/src/loaders/react-import-loader.js',
- },
{
test: /\.(js|jsx|ts|tsx)$/,
exclude: /node_modules/,
@@ -112,10 +109,25 @@ module.exports = function (env) {
Constants: path.resolve(__dirname, './src/constants'),
Stores: path.resolve(__dirname, './src/stores'),
Utils: path.resolve(__dirname, './src/utils'),
+ Types: path.resolve(__dirname, 'src/types'),
},
extensions: ['.js', '.jsx', '.ts', '.tsx'],
},
plugins: [
+ new Dotenv(),
+ new DefinePlugin({
+ 'process.env.GD_CLIENT_ID': JSON.stringify(process.env.GD_CLIENT_ID),
+ 'process.env.GD_API_KEY': JSON.stringify(process.env.GD_API_KEY),
+ 'process.env.GD_APP_ID': JSON.stringify(process.env.GD_APP_ID),
+ 'process.env.DATADOG_APPLICATION_ID': JSON.stringify(process.env.DATADOG_APPLICATION_ID),
+ 'process.env.DATADOG_CLIENT_TOKEN_LOGS': JSON.stringify(process.env.DATADOG_CLIENT_TOKEN_LOGS),
+ 'process.env.DATADOG_SESSION_REPLAY_SAMPLE_RATE': JSON.stringify(
+ process.env.DATADOG_SESSION_REPLAY_SAMPLE_RATE
+ ),
+ 'process.env.DATADOG_SESSION_SAMPLE_RATE': JSON.stringify(process.env.DATADOG_SESSION_SAMPLE_RATE),
+ 'process.env.CIRCLE_TAG': JSON.stringify(process.env.CIRCLE_TAG),
+ 'process.env.CIRCLE_JOB': JSON.stringify(process.env.CIRCLE_JOB),
+ }),
new CleanWebpackPlugin(),
new MiniCssExtractPlugin({
filename: 'bot/css/bot.main.[contenthash].css',
diff --git a/packages/cashier/build/loaders-config.js b/packages/cashier/build/loaders-config.js
index ad9c88dbf5c5..e1807cc0bdb3 100644
--- a/packages/cashier/build/loaders-config.js
+++ b/packages/cashier/build/loaders-config.js
@@ -1,5 +1,6 @@
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const path = require('path');
+const { IS_RELEASE } = require('./constants');
const js_loaders = [
{
@@ -69,13 +70,13 @@ const css_loaders = [
{
loader: 'css-loader',
options: {
- sourceMap: true,
+ sourceMap: !IS_RELEASE,
},
},
{
loader: 'postcss-loader',
options: {
- sourceMap: true,
+ sourceMap: !IS_RELEASE,
postcssOptions: {
config: path.resolve(__dirname),
},
@@ -91,7 +92,7 @@ const css_loaders = [
{
loader: 'sass-loader',
options: {
- sourceMap: true,
+ sourceMap: !IS_RELEASE,
},
},
{
diff --git a/packages/cashier/build/webpack.config.js b/packages/cashier/build/webpack.config.js
index 7ea77ae88fdd..2fd499302b02 100644
--- a/packages/cashier/build/webpack.config.js
+++ b/packages/cashier/build/webpack.config.js
@@ -6,7 +6,7 @@ module.exports = function (env) {
return {
context: path.resolve(__dirname, '../src'),
- devtool: IS_RELEASE ? undefined : 'eval-cheap-module-source-map',
+ devtool: IS_RELEASE ? 'source-map' : 'eval-cheap-module-source-map',
entry: {
cashier: path.resolve(__dirname, '../src', 'index.tsx'),
'cashier-store': 'Stores/cashier-store',
diff --git a/packages/cashier/package.json b/packages/cashier/package.json
index 02163fd4c667..bd0c368f18e9 100644
--- a/packages/cashier/package.json
+++ b/packages/cashier/package.json
@@ -12,7 +12,7 @@
"test": "__tests__"
},
"engines": {
- "node": "^16.16.0"
+ "node": "18.x"
},
"files": [
"lib"
@@ -27,9 +27,9 @@
"scripts": {
"test": "echo \"No test specified\"",
"test:eslint": "eslint \"./src/**/*.?(js|jsx|ts|tsx)\"",
- "serve": "echo \"Serving...\" && webpack --progress --watch --config \"./build/webpack.config.js\"",
+ "serve": "echo \"Serving...\" && NODE_OPTIONS='-r ts-node/register' webpack --progress --watch --config \"./build/webpack.config.js\"",
"build:travis": "rimraf dist && webpack --config \"./build/webpack.config.js\" --mode=production",
- "build": "f () { webpack --config \"./build/webpack.config.js\" --env base=$1;}; f"
+ "build": "f () { NODE_OPTIONS='-r ts-node/register' webpack --config \"./build/webpack.config.js\" --env base=$1;}; f"
},
"bugs": {
"url": "https://github.com/binary-com/deriv-app/issues"
@@ -47,6 +47,7 @@
"@deriv/ui": "^0.6.0",
"classnames": "^2.2.6",
"formik": "^2.1.4",
+ "framer-motion": "^6.5.1",
"loadjs": "^4.2.0",
"lodash.debounce": "^4.0.8",
"mobx": "^6.6.1",
@@ -57,7 +58,6 @@
"react-content-loader": "^6.2.0",
"react-dom": "^17.0.2",
"react-loadable": "^5.5.0",
- "framer-motion": "^6.5.1",
"react-router": "^5.2.0",
"react-router-dom": "^5.2.0"
},
@@ -75,28 +75,29 @@
"@babel/preset-env": "^7.12.11",
"@babel/preset-react": "^7.16.7",
"@testing-library/react": "^12.0.0",
+ "@testing-library/user-event": "^13.5.0",
"@types/loadjs": "^4.0.1",
+ "@types/qrcode.react": "^1.0.2",
"@types/react": "^18.0.7",
"@types/react-dom": "^18.0.0",
- "@testing-library/user-event": "^13.5.0",
- "@types/qrcode.react": "^1.0.2",
"babel-loader": "^8.1.0",
"clean-webpack-plugin": "^3.0.0",
"copy-webpack-plugin": "^9.0.1",
"css-loader": "^5.0.1",
"css-minimizer-webpack-plugin": "^3.0.1",
"file-loader": "^6.2.0",
+ "history": "^5.0.0",
"mini-css-extract-plugin": "^1.3.4",
"node-sass": "^7.0.1",
"postcss-loader": "^6.2.1",
"postcss-preset-env": "^7.4.3",
- "postcss-scss": "^4.0.3",
+ "postcss-scss": "^4.0.6",
"react-svg-loader": "^3.0.3",
"resolve-url-loader": "^3.1.2",
"sass-loader": "^12.6.0",
"sass-resources-loader": "^2.1.1",
"typescript": "^4.6.3",
- "webpack": "^5.46.0",
+ "webpack": "^5.81.0",
"webpack-cli": "^4.7.2",
"webpack-manifest-plugin": "^4.0.2",
"webpack-node-externals": "^2.5.2"
diff --git a/packages/cashier/src/app-content.tsx b/packages/cashier/src/app-content.tsx
index 76a4160a938b..1bb01a34ade5 100644
--- a/packages/cashier/src/app-content.tsx
+++ b/packages/cashier/src/app-content.tsx
@@ -1,12 +1,14 @@
import React from 'react';
-import Routes from 'Containers/routes';
import { observer, useStore } from '@deriv/stores';
import { useTheme } from '@deriv/ui';
+import Routes from './containers/routes';
+import useUnsafeCashierRouteHandler from './containers/routes/useUnsafeCashierRouteHandler';
const AppContent = observer(() => {
const { ui } = useStore();
const { is_dark_mode_on, notification_messages_ui: Notifications } = ui;
const { setColorMode } = useTheme();
+ useUnsafeCashierRouteHandler();
React.useEffect(() => {
const theme = (is_dark_mode_on ? 'dark' : 'light') as Parameters[0];
diff --git a/packages/cashier/src/app.jsx b/packages/cashier/src/app.jsx
deleted file mode 100644
index a5b2620f6947..000000000000
--- a/packages/cashier/src/app.jsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import React from 'react';
-import { setWebsocket } from '@deriv/shared';
-import { init } from 'Utils/server_time';
-import CashierProviders from './cashier-providers';
-import AppContent from './app-content';
-
-const App = ({ passthrough: { WS, root_store } }) => {
- React.useEffect(() => {
- setWebsocket(WS);
- init();
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
-
- return (
-
-
-
- );
-};
-
-export default App;
diff --git a/packages/cashier/src/app.tsx b/packages/cashier/src/app.tsx
new file mode 100644
index 000000000000..7a0cfdcf4736
--- /dev/null
+++ b/packages/cashier/src/app.tsx
@@ -0,0 +1,30 @@
+import React from 'react';
+import { setWebsocket } from '@deriv/shared';
+import { init } from 'Utils/server_time';
+import CashierProviders from './cashier-providers';
+import type { TWebSocket } from './types';
+import type { TCoreStores } from '@deriv/stores/types';
+import AppContent from './app-content';
+
+type TAppProps = {
+ passthrough: {
+ WS: TWebSocket;
+ root_store: TCoreStores;
+ };
+};
+
+const App = ({ passthrough: { WS, root_store } }: TAppProps) => {
+ React.useEffect(() => {
+ setWebsocket(WS);
+ init();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ return (
+
+
+
+ );
+};
+
+export default App;
diff --git a/packages/cashier/src/assets/svgs/trading-platform/ic-appstore-cfds.svg b/packages/cashier/src/assets/svgs/trading-platform/ic-appstore-cfds.svg
index 79ad1d5036b4..88174804c30a 100644
--- a/packages/cashier/src/assets/svgs/trading-platform/ic-appstore-cfds.svg
+++ b/packages/cashier/src/assets/svgs/trading-platform/ic-appstore-cfds.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/packages/cashier/src/assets/svgs/trading-platform/ic-appstore-derived.svg b/packages/cashier/src/assets/svgs/trading-platform/ic-appstore-derived.svg
index f6c606e75663..52b8a909ccd6 100644
--- a/packages/cashier/src/assets/svgs/trading-platform/ic-appstore-derived.svg
+++ b/packages/cashier/src/assets/svgs/trading-platform/ic-appstore-derived.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/packages/cashier/src/assets/svgs/trading-platform/ic-appstore-financial.svg b/packages/cashier/src/assets/svgs/trading-platform/ic-appstore-financial.svg
index 7439664ff816..2ecb392ebc1e 100644
--- a/packages/cashier/src/assets/svgs/trading-platform/ic-appstore-financial.svg
+++ b/packages/cashier/src/assets/svgs/trading-platform/ic-appstore-financial.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/packages/cashier/src/assets/svgs/trading-platform/ic-appstore-options.svg b/packages/cashier/src/assets/svgs/trading-platform/ic-appstore-options.svg
index 51aec331b309..41ab47489199 100644
--- a/packages/cashier/src/assets/svgs/trading-platform/ic-appstore-options.svg
+++ b/packages/cashier/src/assets/svgs/trading-platform/ic-appstore-options.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/packages/cashier/src/assets/svgs/trading-platform/ic-appstore-swap-free.svg b/packages/cashier/src/assets/svgs/trading-platform/ic-appstore-swap-free.svg
new file mode 100644
index 000000000000..7b464b0951e6
--- /dev/null
+++ b/packages/cashier/src/assets/svgs/trading-platform/ic-appstore-swap-free.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/cashier/src/assets/svgs/trading-platform/index.tsx b/packages/cashier/src/assets/svgs/trading-platform/index.tsx
index 9c9989562c7a..1eb92893a296 100644
--- a/packages/cashier/src/assets/svgs/trading-platform/index.tsx
+++ b/packages/cashier/src/assets/svgs/trading-platform/index.tsx
@@ -3,6 +3,7 @@ import Derived from './ic-appstore-derived.svg';
import Financial from './ic-appstore-financial.svg';
import Options from './ic-appstore-options.svg';
import CFDs from './ic-appstore-cfds.svg';
+import SwapFree from './ic-appstore-swap-free.svg';
export interface IconProps {
icon: T;
@@ -16,6 +17,7 @@ export const PlatformIcons = {
Financial,
Options,
CFDs,
+ SwapFree,
};
const TradingPlatformIcon = ({ icon, className, size, onClick }: IconProps) => {
diff --git a/packages/cashier/src/cashier-providers.tsx b/packages/cashier/src/cashier-providers.tsx
index b1f4b9c7265d..e4d139f49e95 100644
--- a/packages/cashier/src/cashier-providers.tsx
+++ b/packages/cashier/src/cashier-providers.tsx
@@ -3,9 +3,9 @@ import { APIProvider } from '@deriv/api';
import { StoreProvider } from '@deriv/stores';
import { ThemeProvider } from '@deriv/ui';
import { CashierStoreProvider } from './stores/useCashierStores';
-import { TRootStore } from 'Types';
+import type { TCoreStores } from '@deriv/stores/types';
-const CashierProviders = ({ children, store }: React.PropsWithChildren<{ store: TRootStore }>) => {
+const CashierProviders = ({ children, store }: React.PropsWithChildren<{ store: TCoreStores }>) => {
return (
diff --git a/packages/cashier/src/components/account-platform-icon/account-platform-icon.tsx b/packages/cashier/src/components/account-platform-icon/account-platform-icon.tsx
index a2fc8e3bca62..0cc4e54db2e9 100644
--- a/packages/cashier/src/components/account-platform-icon/account-platform-icon.tsx
+++ b/packages/cashier/src/components/account-platform-icon/account-platform-icon.tsx
@@ -1,7 +1,7 @@
import React from 'react';
import { Icon } from '@deriv/components';
import TradingPlatformIcon from 'Assets/svgs/trading-platform';
-import { TAccountsList } from 'Types';
+import { TAccountsList } from '../../types';
type TAccountPlatformIcon = {
size: number;
@@ -30,6 +30,9 @@ const AccountPlatformIcon = ({
icon={account.platform_icon || `IcCurrency-${account?.currency?.toLowerCase()}`}
size={size}
className={icon_class_name}
+ data_testid={`dt_account_platform_icon_${
+ account.platform_icon || `currency_${account?.currency?.toLowerCase()}`
+ }`}
/>
);
};
diff --git a/packages/cashier/src/components/account-prompt-dialog/__tests__/account-prompt-dialog.spec.tsx b/packages/cashier/src/components/account-prompt-dialog/__tests__/account-prompt-dialog.spec.tsx
deleted file mode 100644
index cf393a6d5ed9..000000000000
--- a/packages/cashier/src/components/account-prompt-dialog/__tests__/account-prompt-dialog.spec.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import React from 'react';
-import { render, screen } from '@testing-library/react';
-import AccountPromptDialog from '../account-prompt-dialog';
-import CashierProviders from '../../../cashier-providers';
-
-jest.mock('@deriv/components', () => ({
- ...jest.requireActual('@deriv/components'),
- Dialog: () => Dialog
,
-}));
-
-describe(' ', () => {
- const mockRootStore = {
- client: {
- accounts: {
- CR90000001: { is_virtual: 0, currency: 'USD' },
- CR90000002: { is_virtual: 0, currency: 'BTC' },
- },
- },
- modules: {
- cashier: {
- account_prompt_dialog: {
- continueRoute: jest.fn(),
- is_confirmed: false,
- last_location: '',
- onCancel: jest.fn(),
- onConfirm: jest.fn(),
- should_show: true,
- },
- },
- },
- };
- it('should render dialog', () => {
- render( , {
- wrapper: ({ children }) => {children} ,
- });
-
- expect(screen.getByText('Dialog')).toBeInTheDocument();
- });
-});
diff --git a/packages/cashier/src/components/account-prompt-dialog/account-prompt-dialog.tsx b/packages/cashier/src/components/account-prompt-dialog/account-prompt-dialog.tsx
deleted file mode 100644
index 2000fcc67895..000000000000
--- a/packages/cashier/src/components/account-prompt-dialog/account-prompt-dialog.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import React from 'react';
-import { Dialog } from '@deriv/components';
-import { isCryptocurrency } from '@deriv/shared';
-import { localize, Localize } from '@deriv/translations';
-import { useStore, observer } from '@deriv/stores';
-import { useCashierStore } from '../../stores/useCashierStores';
-
-const AccountPromptDialog = observer(() => {
- const { client } = useStore();
- const { accounts } = client;
- const { account_prompt_dialog } = useCashierStore();
- const { continueRoute, is_confirmed, last_location, onCancel, onConfirm, should_show } = account_prompt_dialog;
-
- React.useEffect(continueRoute, [is_confirmed, last_location, continueRoute]);
-
- const non_crypto_account_loginid = React.useMemo(
- () =>
- Object.entries(accounts).reduce((initial_value, [loginid, settings]) => {
- return !settings.is_virtual && !isCryptocurrency(settings.currency) ? loginid : initial_value;
- }, ''),
- [accounts]
- );
-
- const non_crypto_currency = non_crypto_account_loginid && accounts[non_crypto_account_loginid].currency;
-
- return (
-
-
-
- );
-});
-
-export default AccountPromptDialog;
diff --git a/packages/cashier/src/components/account-prompt-dialog/index.ts b/packages/cashier/src/components/account-prompt-dialog/index.ts
deleted file mode 100644
index 96805fec7b2c..000000000000
--- a/packages/cashier/src/components/account-prompt-dialog/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import AccountPromptDialog from './account-prompt-dialog';
-
-export default AccountPromptDialog;
diff --git a/packages/cashier/src/components/alert-banner/__tests__/alert-banner.spec.tsx b/packages/cashier/src/components/alert-banner/__tests__/alert-banner.spec.tsx
new file mode 100644
index 000000000000..0182a5656440
--- /dev/null
+++ b/packages/cashier/src/components/alert-banner/__tests__/alert-banner.spec.tsx
@@ -0,0 +1,16 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import AlertBanner from '../alert-banner';
+
+describe(' ', () => {
+ it('should render the icon and message props passed for the banner', () => {
+ const props: React.ComponentProps = {
+ icon: 'IcAlertWarningDark',
+ message: 'Alert banner test message.',
+ };
+ render( );
+ const dt_alert_banner_icon = screen.getByTestId('dt_alert_banner_icon');
+ expect(dt_alert_banner_icon).toBeInTheDocument();
+ expect(screen.getByText(props.message)).toBeInTheDocument();
+ });
+});
diff --git a/packages/cashier/src/components/alert-banner/alert-banner.scss b/packages/cashier/src/components/alert-banner/alert-banner.scss
new file mode 100644
index 000000000000..02a81acfc4a0
--- /dev/null
+++ b/packages/cashier/src/components/alert-banner/alert-banner.scss
@@ -0,0 +1,13 @@
+.alert-banner {
+ display: grid;
+ grid-template-columns: auto auto;
+ width: auto;
+ padding: 0.8rem;
+ margin: 2.4rem 0;
+ border-radius: $BORDER_RADIUS;
+ background-color: $alpha-color-yellow-1;
+
+ svg {
+ margin: 0.3rem 0.3rem 0 0;
+ }
+}
diff --git a/packages/cashier/src/components/alert-banner/alert-banner.tsx b/packages/cashier/src/components/alert-banner/alert-banner.tsx
new file mode 100644
index 000000000000..f2d5537ead7f
--- /dev/null
+++ b/packages/cashier/src/components/alert-banner/alert-banner.tsx
@@ -0,0 +1,23 @@
+import React from 'react';
+import classNames from 'classnames';
+import { Icon, Text } from '@deriv/components';
+import './alert-banner.scss';
+
+type TAlertBanner = {
+ className?: string;
+ icon: string;
+ message: string;
+};
+
+const AlertBanner = ({ className, icon, message }: TAlertBanner) => {
+ return (
+
+
+
+ {message}
+
+
+ );
+};
+
+export default AlertBanner;
diff --git a/packages/cashier/src/components/cashier-container/real/__tests__/real.spec.tsx b/packages/cashier/src/components/cashier-container/real/__tests__/real.spec.tsx
index 7e694cd94e32..b9328b65bfb3 100644
--- a/packages/cashier/src/components/cashier-container/real/__tests__/real.spec.tsx
+++ b/packages/cashier/src/components/cashier-container/real/__tests__/real.spec.tsx
@@ -24,6 +24,8 @@ describe(' ', () => {
clearIframe: jest.fn(),
iframe_height: 100,
iframe_url: 'https://www.test_url.com',
+ checkIframeLoaded: jest.fn(),
+ setContainerHeight: jest.fn(),
},
deposit: {
onMountDeposit: jest.fn(),
@@ -34,7 +36,7 @@ describe(' ', () => {
};
});
- const mockRootStore = mockStore({});
+ const mock_root_store = mockStore({});
it('should show loader when is_loading is true or iframe_height is equal to 0', () => {
(useCashierStore as jest.Mock).mockReturnValueOnce({
@@ -43,7 +45,7 @@ describe(' ', () => {
});
const { rerender } = render(
-
+
);
@@ -56,7 +58,7 @@ describe(' ', () => {
});
rerender(
-
+
);
@@ -66,7 +68,7 @@ describe(' ', () => {
it('should render an iframe if iframe_url is not an empty string', () => {
render(
-
+
);
@@ -77,7 +79,7 @@ describe(' ', () => {
describe('Breadcrumb visibility', () => {
it('should show breadcrumbs only on deposit page and only for non EU clients', () => {
render(
-
+
);
@@ -93,7 +95,7 @@ describe(' ', () => {
});
render(
-
+
);
@@ -104,7 +106,7 @@ describe(' ', () => {
it('should not show breadcrumbs on withdraw page', () => {
render(
-
+
);
@@ -116,7 +118,7 @@ describe(' ', () => {
it('should trigger setIsDeposit callback when the user clicks on Cashier breadcrumb', () => {
render(
-
+
);
@@ -131,7 +133,7 @@ describe(' ', () => {
it('should trigger clearIframe and onMountDeposit callbacks when component is destroyed on deposit page', () => {
const { unmount } = render(
-
+
);
diff --git a/packages/cashier/src/components/cashier-container/real/real.tsx b/packages/cashier/src/components/cashier-container/real/real.tsx
index 5559e237f72f..4a0e2440cc34 100644
--- a/packages/cashier/src/components/cashier-container/real/real.tsx
+++ b/packages/cashier/src/components/cashier-container/real/real.tsx
@@ -1,21 +1,17 @@
import React from 'react';
import { Loading } from '@deriv/components';
-import { useStore } from '@deriv/stores';
+import { useStore, observer } from '@deriv/stores';
import CashierBreadcrumb from '../../cashier-breadcrumb';
import { useCashierStore } from '../../../stores/useCashierStores';
import './real.scss';
-const Real = ({ is_deposit = false }: { is_deposit?: boolean }) => {
- const {
- traders_hub: { is_low_risk_cr_eu_real },
- } = useStore();
-
+const Real = observer(({ is_deposit = false }: { is_deposit?: boolean }) => {
+ const { traders_hub, ui } = useStore();
+ const { is_low_risk_cr_eu_real } = traders_hub;
+ const { is_dark_mode_on } = ui;
const { iframe, deposit, general_store } = useCashierStore();
-
- const { clearIframe, iframe_height, iframe_url } = iframe;
-
+ const { clearIframe, iframe_height, iframe_url, checkIframeLoaded, setContainerHeight } = iframe;
const { is_loading } = general_store;
-
const { onMountDeposit } = deposit;
const should_show_breadcrumbs = !is_low_risk_cr_eu_real && is_deposit && Boolean(iframe_height);
@@ -28,6 +24,12 @@ const Real = ({ is_deposit = false }: { is_deposit?: boolean }) => {
};
}, [clearIframe, onMountDeposit]);
+ React.useEffect(() => {
+ // To show loading state when switching theme
+ setContainerHeight(0);
+ checkIframeLoaded();
+ }, [checkIframeLoaded, is_dark_mode_on, setContainerHeight]);
+
return (
{should_show_loader && }
@@ -36,7 +38,7 @@ const Real = ({ is_deposit = false }: { is_deposit?: boolean }) => {
);
-};
+});
export default Real;
diff --git a/packages/cashier/src/components/cashier-container/virtual/__tests__/virtual.spec.tsx b/packages/cashier/src/components/cashier-container/virtual/__tests__/virtual.spec.tsx
index ad139added4a..4c2daaec1a42 100644
--- a/packages/cashier/src/components/cashier-container/virtual/__tests__/virtual.spec.tsx
+++ b/packages/cashier/src/components/cashier-container/virtual/__tests__/virtual.spec.tsx
@@ -1,5 +1,6 @@
import React from 'react';
import { fireEvent, render, screen } from '@testing-library/react';
+import { mockStore } from '@deriv/stores';
import { createBrowserHistory } from 'history';
import { Router } from 'react-router';
import Virtual from '../virtual';
@@ -7,12 +8,12 @@ import CashierProviders from '../../../../cashier-providers';
describe(' ', () => {
const history = createBrowserHistory();
- let mockRootStore;
+ let mockRootStore: ReturnType;
beforeEach(() => {
- mockRootStore = {
+ mockRootStore = mockStore({
ui: { is_dark_mode_on: true, toggleAccountsDialog: jest.fn() },
- };
+ });
});
it('component should render', () => {
diff --git a/packages/cashier/src/components/cashier-container/virtual/virtual.tsx b/packages/cashier/src/components/cashier-container/virtual/virtual.tsx
index 73c395505059..6ff660e6c9e3 100644
--- a/packages/cashier/src/components/cashier-container/virtual/virtual.tsx
+++ b/packages/cashier/src/components/cashier-container/virtual/virtual.tsx
@@ -40,13 +40,7 @@ const Virtual = observer(() => {
i18n_default_text='You need to switch to a real money account to use this feature.<0/>You can do this by selecting a real account from the <1>Account Switcher.1>'
components={[
,
- {
- toggleAccountsDialog();
- }}
- />,
+ ,
]}
/>
diff --git a/packages/cashier/src/components/cashier-locked/__tests__/cashier-locked.spec.tsx b/packages/cashier/src/components/cashier-locked/__tests__/cashier-locked.spec.tsx
index fabe00443810..6ab3ef62a46a 100644
--- a/packages/cashier/src/components/cashier-locked/__tests__/cashier-locked.spec.tsx
+++ b/packages/cashier/src/components/cashier-locked/__tests__/cashier-locked.spec.tsx
@@ -1,38 +1,29 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import CashierLocked from '../cashier-locked';
-import { useDepositLocked } from '@deriv/hooks';
-import { TRootStore } from 'Types';
+import { useCashierLocked, useDepositLocked } from '@deriv/hooks';
+import { mockStore } from '@deriv/stores';
import CashierProviders from '../../../cashier-providers';
jest.mock('@deriv/hooks', () => ({
...jest.requireActual('@deriv/hooks'),
useDepositLocked: jest.fn(() => false),
+ useCashierLocked: jest.fn(() => false),
}));
-
-jest.mock('@deriv/hooks', () => ({
- ...jest.requireActual('@deriv/hooks'),
- useDepositLocked: jest.fn(() => false),
-}));
-
-jest.mock('@deriv/hooks', () => ({
- ...jest.requireActual('@deriv/hooks'),
- useDepositLocked: jest.fn(() => false),
-}));
+const mockUseDepositLocked = useDepositLocked as jest.MockedFunction;
+const mockUseCashierLocked = useCashierLocked as jest.MockedFunction;
describe(' ', () => {
- it('should show the proper message if there is a cryptocashier maintenance', () => {
- const mockRootStore: DeepPartial = {
+ beforeEach(() => {
+ mockUseDepositLocked.mockReturnValue(false);
+ mockUseCashierLocked.mockReturnValue(false);
+ });
+
+ it('should show the proper message if there is a crypto cashier maintenance', () => {
+ const mock_root_store = mockStore({
client: {
- account_status: {
- cashier_validation: [],
- },
- accounts: undefined,
- loginid: undefined,
+ account_status: { cashier_validation: ['system_maintenance'] },
current_currency_type: 'crypto',
- is_deposit_lock: useDepositLocked.mockReturnValue(false),
- is_withdrawal_lock: false,
- is_identity_verification_needed: false,
mt5_login_list: [
{
account_type: 'demo',
@@ -40,13 +31,10 @@ describe(' ', () => {
},
],
},
- modules: { cashier: { general_store: { is_cashier_locked: false, is_system_maintenance: true } } },
- };
+ });
render( , {
- wrapper: ({ children }) => (
- {children}
- ),
+ wrapper: ({ children }) => {children} ,
});
expect(
@@ -57,17 +45,11 @@ describe(' ', () => {
});
it('should show the proper message if crypto withdrawal is suspended', () => {
- const mockRootStore: DeepPartial = {
+ const mock_root_store = mockStore({
client: {
- account_status: {
- cashier_validation: [],
- },
- accounts: undefined,
- loginid: undefined,
+ account_status: { cashier_validation: ['system_maintenance'] },
current_currency_type: 'crypto',
- is_deposit_lock: useDepositLocked.mockReturnValue(false),
is_withdrawal_lock: true,
- is_identity_verification_needed: false,
mt5_login_list: [
{
account_type: 'demo',
@@ -75,13 +57,10 @@ describe(' ', () => {
},
],
},
- modules: { cashier: { general_store: { is_cashier_locked: false, is_system_maintenance: true } } },
- };
+ });
render( , {
- wrapper: ({ children }) => (
- {children}
- ),
+ wrapper: ({ children }) => {children} ,
});
expect(
@@ -92,17 +71,10 @@ describe(' ', () => {
});
it('should show the proper message if crypto deposit is suspended', () => {
- const mockRootStore: DeepPartial = {
+ const mock_root_store = mockStore({
client: {
- account_status: {
- cashier_validation: [],
- },
- accounts: undefined,
- loginid: undefined,
+ account_status: { cashier_validation: ['system_maintenance'] },
current_currency_type: 'crypto',
- is_deposit_lock: useDepositLocked.mockReturnValue(true),
- is_withdrawal_lock: false,
- is_identity_verification_needed: false,
mt5_login_list: [
{
account_type: 'demo',
@@ -110,13 +82,11 @@ describe(' ', () => {
},
],
},
- modules: { cashier: { general_store: { is_cashier_locked: false, is_system_maintenance: true } } },
- };
+ });
+ mockUseDepositLocked.mockReturnValue(true);
render( , {
- wrapper: ({ children }) => (
- {children}
- ),
+ wrapper: ({ children }) => {children} ,
});
expect(
@@ -127,17 +97,10 @@ describe(' ', () => {
});
it('should show the proper message if there is a cashier maintenance', () => {
- const mockRootStore: DeepPartial = {
+ const mock_root_store = mockStore({
client: {
- account_status: {
- cashier_validation: [],
- },
- accounts: undefined,
- loginid: undefined,
+ account_status: { cashier_validation: ['system_maintenance'] },
current_currency_type: 'fiat',
- is_deposit_lock: useDepositLocked.mockReturnValue(false),
- is_withdrawal_lock: false,
- is_identity_verification_needed: false,
mt5_login_list: [
{
account_type: 'demo',
@@ -145,13 +108,10 @@ describe(' ', () => {
},
],
},
- modules: { cashier: { general_store: { is_cashier_locked: false, is_system_maintenance: true } } },
- };
+ });
render( , {
- wrapper: ({ children }) => (
- {children}
- ),
+ wrapper: ({ children }) => {children} ,
});
expect(
@@ -162,17 +122,10 @@ describe(' ', () => {
});
it('should show the proper message if the client does not provide residence', () => {
- const mockRootStore: DeepPartial = {
+ const mock_root_store = mockStore({
client: {
- account_status: {
- cashier_validation: ['no_residence'],
- },
- accounts: undefined,
- loginid: undefined,
+ account_status: { cashier_validation: ['no_residence'] },
current_currency_type: 'fiat',
- is_deposit_lock: useDepositLocked.mockReturnValue(false),
- is_withdrawal_lock: false,
- is_identity_verification_needed: false,
mt5_login_list: [
{
account_type: 'demo',
@@ -180,13 +133,11 @@ describe(' ', () => {
},
],
},
- modules: { cashier: { general_store: { is_cashier_locked: true, is_system_maintenance: false } } },
- };
+ });
+ mockUseCashierLocked.mockReturnValue(true);
render( , {
- wrapper: ({ children }) => (
- {children}
- ),
+ wrapper: ({ children }) => {children} ,
});
expect(
@@ -197,17 +148,10 @@ describe(' ', () => {
});
it('should show the proper message if the documents are expired', () => {
- const mockRootStore: DeepPartial = {
+ const mock_root_store = mockStore({
client: {
- account_status: {
- cashier_validation: ['documents_expired'],
- },
- accounts: undefined,
- loginid: undefined,
+ account_status: { cashier_validation: ['documents_expired'] },
current_currency_type: 'fiat',
- is_deposit_lock: useDepositLocked.mockReturnValue(false),
- is_withdrawal_lock: false,
- is_identity_verification_needed: false,
mt5_login_list: [
{
account_type: 'demo',
@@ -215,13 +159,11 @@ describe(' ', () => {
},
],
},
- modules: { cashier: { general_store: { is_cashier_locked: true, is_system_maintenance: false } } },
- };
+ });
+ mockUseCashierLocked.mockReturnValue(true);
render( , {
- wrapper: ({ children }) => (
- {children}
- ),
+ wrapper: ({ children }) => {children} ,
});
expect(
@@ -232,17 +174,10 @@ describe(' ', () => {
});
it('should show the proper message if the client has cashier_locked_status', () => {
- const mockRootStore: DeepPartial = {
+ const mock_root_store = mockStore({
client: {
- account_status: {
- cashier_validation: ['cashier_locked_status'],
- },
- accounts: undefined,
- loginid: undefined,
+ account_status: { cashier_validation: ['cashier_locked_status'] },
current_currency_type: 'fiat',
- is_deposit_lock: useDepositLocked.mockReturnValue(false),
- is_withdrawal_lock: false,
- is_identity_verification_needed: false,
mt5_login_list: [
{
account_type: 'demo',
@@ -250,13 +185,11 @@ describe(' ', () => {
},
],
},
- modules: { cashier: { general_store: { is_cashier_locked: true, is_system_maintenance: false } } },
- };
+ });
+ mockUseCashierLocked.mockReturnValue(true);
const { container } = render( , {
- wrapper: ({ children }) => (
- {children}
- ),
+ wrapper: ({ children }) => {children} ,
});
expect(container).toHaveTextContent(
@@ -265,17 +198,10 @@ describe(' ', () => {
});
it('should show the proper message if the client has disabled_status', () => {
- const mockRootStore: DeepPartial = {
+ const mock_root_store = mockStore({
client: {
- account_status: {
- cashier_validation: ['disabled_status'],
- },
- accounts: undefined,
- loginid: undefined,
+ account_status: { cashier_validation: ['disabled_status'] },
current_currency_type: 'fiat',
- is_deposit_lock: useDepositLocked.mockReturnValue(false),
- is_withdrawal_lock: false,
- is_identity_verification_needed: false,
mt5_login_list: [
{
account_type: 'demo',
@@ -283,13 +209,11 @@ describe(' ', () => {
},
],
},
- modules: { cashier: { general_store: { is_cashier_locked: true, is_system_maintenance: false } } },
- };
+ });
+ mockUseCashierLocked.mockReturnValue(true);
const { container } = render( , {
- wrapper: ({ children }) => (
- {children}
- ),
+ wrapper: ({ children }) => {children} ,
});
expect(container).toHaveTextContent(
@@ -298,17 +222,10 @@ describe(' ', () => {
});
it('should show the proper message if the client account has no currency', () => {
- const mockRootStore: DeepPartial = {
+ const mock_root_store = mockStore({
client: {
- account_status: {
- cashier_validation: ['ASK_CURRENCY'],
- },
- accounts: undefined,
- loginid: undefined,
+ account_status: { cashier_validation: ['ASK_CURRENCY'] },
current_currency_type: 'fiat',
- is_deposit_lock: useDepositLocked.mockReturnValue(false),
- is_withdrawal_lock: false,
- is_identity_verification_needed: false,
mt5_login_list: [
{
account_type: 'demo',
@@ -316,13 +233,11 @@ describe(' ', () => {
},
],
},
- modules: { cashier: { general_store: { is_cashier_locked: true, is_system_maintenance: false } } },
- };
+ });
+ mockUseCashierLocked.mockReturnValue(true);
render( , {
- wrapper: ({ children }) => (
- {children}
- ),
+ wrapper: ({ children }) => {children} ,
});
expect(
@@ -331,17 +246,10 @@ describe(' ', () => {
});
it('should show the proper message if the client is not fully authenticated', () => {
- const mockRootStore: DeepPartial = {
+ const mock_root_store = mockStore({
client: {
- account_status: {
- cashier_validation: ['ASK_AUTHENTICATE'],
- },
- accounts: undefined,
- loginid: undefined,
+ account_status: { cashier_validation: ['ASK_AUTHENTICATE'] },
current_currency_type: 'fiat',
- is_deposit_lock: useDepositLocked.mockReturnValue(false),
- is_withdrawal_lock: false,
- is_identity_verification_needed: false,
mt5_login_list: [
{
account_type: 'demo',
@@ -349,30 +257,21 @@ describe(' ', () => {
},
],
},
- modules: { cashier: { general_store: { is_cashier_locked: true, is_system_maintenance: false } } },
- };
+ });
+ mockUseCashierLocked.mockReturnValue(true);
render( , {
- wrapper: ({ children }) => (
- {children}
- ),
+ wrapper: ({ children }) => {children} ,
});
expect(screen.getByText(/Your account has not been authenticated./i)).toBeInTheDocument();
});
it('should show the proper message if the client has ask_financial_risk_approval status', () => {
- const mockRootStore: DeepPartial = {
+ const mock_root_store = mockStore({
client: {
- account_status: {
- cashier_validation: ['ASK_FINANCIAL_RISK_APPROVAL'],
- },
- accounts: undefined,
- loginid: undefined,
+ account_status: { cashier_validation: ['ASK_FINANCIAL_RISK_APPROVAL'] },
current_currency_type: 'fiat',
- is_deposit_lock: useDepositLocked.mockReturnValue(false),
- is_withdrawal_lock: false,
- is_identity_verification_needed: false,
mt5_login_list: [
{
account_type: 'demo',
@@ -380,13 +279,11 @@ describe('