diff --git a/packages/account/src/Components/poa/continue-trading-button/__tests__/continue-trading-button.spec.tsx b/packages/account/src/Components/poa/continue-trading-button/__tests__/continue-trading-button.spec.tsx index 68669854752e..793228ad9afa 100644 --- a/packages/account/src/Components/poa/continue-trading-button/__tests__/continue-trading-button.spec.tsx +++ b/packages/account/src/Components/poa/continue-trading-button/__tests__/continue-trading-button.spec.tsx @@ -23,6 +23,6 @@ describe('', () => { renderWithRouter(); const continue_btn_text = screen.getByTestId('continue_btn_text'); fireEvent.click(continue_btn_text); - expect(history.location.pathname).toBe(routes.root); + expect(history.location.pathname).toBe(routes.trade); }); }); diff --git a/packages/account/src/Components/poa/continue-trading-button/continue-trading-button.tsx b/packages/account/src/Components/poa/continue-trading-button/continue-trading-button.tsx index 60e2c1ef75cb..18cb0e711e4e 100644 --- a/packages/account/src/Components/poa/continue-trading-button/continue-trading-button.tsx +++ b/packages/account/src/Components/poa/continue-trading-button/continue-trading-button.tsx @@ -2,6 +2,7 @@ import React from 'react'; import classNames from 'classnames'; import { ButtonLink, Text } from '@deriv/components'; import { Localize } from '@deriv/translations'; +import { routes } from '@deriv/shared/src/utils/routes/routes'; type TContinueTradingButtonProps = { className?: string }; @@ -12,7 +13,7 @@ type TContinueTradingButtonProps = { className?: string }; * @returns React Element */ export const ContinueTradingButton = ({ className }: TContinueTradingButtonProps) => ( - + diff --git a/packages/account/src/Containers/Account/page-overlay-wrapper.tsx b/packages/account/src/Containers/Account/page-overlay-wrapper.tsx index d288ff8469ce..f35613f5f28a 100644 --- a/packages/account/src/Containers/Account/page-overlay-wrapper.tsx +++ b/packages/account/src/Containers/Account/page-overlay-wrapper.tsx @@ -24,7 +24,7 @@ const PageOverlayWrapper = observer(({ routes, subroutes }: PageOverlayWrapperPr const history = useHistory(); const { client, common, ui } = useStore(); const { is_mobile } = ui; - const { has_wallet, logout } = client; + const { logout } = client; const { is_from_derivgo } = common; const passkeysMenuCloseActionEventTrack = React.useCallback(() => { @@ -46,8 +46,8 @@ const PageOverlayWrapper = observer(({ routes, subroutes }: PageOverlayWrapperPr passkeysMenuCloseActionEventTrack(); } - has_wallet ? history.push(shared_routes.wallets) : history.push(shared_routes.traders_hub); - }, [history, has_wallet]); + history.push(shared_routes.traders_hub); + }, [history]); const selected_route = getSelectedRoute({ routes: subroutes as Array, pathname: location.pathname }); diff --git a/packages/api-v2/src/hooks/useOnfido.ts b/packages/api-v2/src/hooks/useOnfido.ts index a7f059093b46..c1a787785455 100644 --- a/packages/api-v2/src/hooks/useOnfido.ts +++ b/packages/api-v2/src/hooks/useOnfido.ts @@ -164,7 +164,7 @@ const useOnfido = (country?: string, selectedDocument?: string) => { document.body.appendChild(linkNode); scriptNode.addEventListener('load', () => { - initOnfido(); + initOnfido(); setIsOnfidoLoading(false); }); } diff --git a/packages/appstore/package.json b/packages/appstore/package.json index 0280c266be7e..134217c3505b 100644 --- a/packages/appstore/package.json +++ b/packages/appstore/package.json @@ -35,6 +35,7 @@ "@deriv/stores": "^1.0.0", "@deriv/translations": "^1.0.0", "@deriv/hooks": "^1.0.0", + "@deriv/wallets": "^1.0.0", "classnames": "^2.2.6", "mobx": "^6.6.1", "mobx-react-lite": "^3.4.0", diff --git a/packages/appstore/src/components/cfds-listing-logged-out/cfds-listing-logged-out.scss b/packages/appstore/src/components/cfds-listing-logged-out/cfds-listing-logged-out.scss new file mode 100644 index 000000000000..5be4a1a91798 --- /dev/null +++ b/packages/appstore/src/components/cfds-listing-logged-out/cfds-listing-logged-out.scss @@ -0,0 +1,15 @@ +.cfds-listing-logged-out { + &__cfd-full-row { + user-select: none; + display: grid; + width: 100%; + grid-column: 1 / span 3; + } + + &__divider { + width: 100%; + height: 1px; + background-color: var(--general-hover); + border: none; + } +} diff --git a/packages/appstore/src/components/cfds-listing-logged-out/cfds-listing-logged-out.tsx b/packages/appstore/src/components/cfds-listing-logged-out/cfds-listing-logged-out.tsx new file mode 100644 index 000000000000..8fcfe5be63ce --- /dev/null +++ b/packages/appstore/src/components/cfds-listing-logged-out/cfds-listing-logged-out.tsx @@ -0,0 +1,105 @@ +import React from 'react'; +import { observer, useStore } from '@deriv/stores'; +import { Text } from '@deriv/components'; +import { redirectToLogin } from '@deriv/shared'; +import { getLanguage, localize } from '@deriv/translations'; +import { getHasDivider } from 'Constants/utils'; +import ListingContainer from 'Components/containers/listing-container'; +import TradingAppCard from 'Components/containers/trading-app-card'; +import CFDsDescription from 'Components/elements/cfds-description'; +import CFDsTitle from 'Components/elements/cfds-title'; +import './cfds-listing-logged-out.scss'; + +const CFDsListingLoggedOut = observer(() => { + const { traders_hub } = useStore(); + const { + available_dxtrade_accounts, + available_ctrader_accounts, + combined_cfd_mt5_accounts, + selected_region, + is_eu_user, + } = traders_hub; + + return ( + } description={}> +
+ + {localize('Deriv MT5')} + +
+ {combined_cfd_mt5_accounts.map((existing_account, index: number) => { + const list_size = combined_cfd_mt5_accounts.length; + return ( + redirectToLogin(false, getLanguage())} + market_type='all' + /> + ); + })} + {!is_eu_user && ( +
+
+
+ )} + {!is_eu_user && ( +
+ {localize('Deriv cTrader')} +
+ )} + {available_ctrader_accounts.map(account => ( + redirectToLogin(false, getLanguage())} + key={`trading_app_card_${account.name}`} + market_type='all' + /> + ))} + {!is_eu_user && ( + +
+
+
+ +
+ + {localize('Deriv X')} + +
+
+ )} + {available_dxtrade_accounts?.map(account => ( + redirectToLogin(false, getLanguage())} + key={`trading_app_card_${account.name}`} + market_type='all' + /> + ))} +
+ ); +}); + +export default CFDsListingLoggedOut; diff --git a/packages/appstore/src/components/cfds-listing-logged-out/index.tsx b/packages/appstore/src/components/cfds-listing-logged-out/index.tsx new file mode 100644 index 000000000000..9c03436c0bca --- /dev/null +++ b/packages/appstore/src/components/cfds-listing-logged-out/index.tsx @@ -0,0 +1,3 @@ +import CFDsListingLoggedOut from './cfds-listing-logged-out'; + +export default CFDsListingLoggedOut; diff --git a/packages/appstore/src/components/containers/trading-app-card.tsx b/packages/appstore/src/components/containers/trading-app-card.tsx index c465e348bb4a..bf5970583492 100644 --- a/packages/appstore/src/components/containers/trading-app-card.tsx +++ b/packages/appstore/src/components/containers/trading-app-card.tsx @@ -55,7 +55,7 @@ const TradingAppCard = ({ const { is_eu_user, is_demo_low_risk, content_flag, is_real, selected_account_type } = traders_hub; const { current_language } = common; const { is_account_being_created } = cfd; - const { account_status: { authentication } = {} } = client; + const { account_status: { authentication } = {}, is_logged_in } = client; const [is_open_position_svg_modal_open, setIsOpenPositionSvgModalOpen] = React.useState(false); const demo_label = localize('Demo'); diff --git a/packages/appstore/src/components/elements/cfds-description/cfds-description.tsx b/packages/appstore/src/components/elements/cfds-description/cfds-description.tsx new file mode 100644 index 000000000000..a4330feeb655 --- /dev/null +++ b/packages/appstore/src/components/elements/cfds-description/cfds-description.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { Text, StaticUrl } from '@deriv/components'; +import { Localize } from '@deriv/translations'; + +const CFDsDescription = () => { + return ( + + ]} + /> + + ); +}; + +export default CFDsDescription; diff --git a/packages/appstore/src/components/elements/cfds-description/index.tsx b/packages/appstore/src/components/elements/cfds-description/index.tsx new file mode 100644 index 000000000000..c3a512b9de29 --- /dev/null +++ b/packages/appstore/src/components/elements/cfds-description/index.tsx @@ -0,0 +1,3 @@ +import CFDsDescription from './cfds-description'; + +export default CFDsDescription; diff --git a/packages/appstore/src/components/elements/cfds-title/cfds-title.scss b/packages/appstore/src/components/elements/cfds-title/cfds-title.scss new file mode 100644 index 000000000000..98d5a88586e0 --- /dev/null +++ b/packages/appstore/src/components/elements/cfds-title/cfds-title.scss @@ -0,0 +1,5 @@ +.cfds-title { + display: flex; + justify-content: flex-start; + align-items: center; +} diff --git a/packages/appstore/src/components/elements/cfds-title/cfds-title.tsx b/packages/appstore/src/components/elements/cfds-title/cfds-title.tsx new file mode 100644 index 000000000000..969b3c10d4bc --- /dev/null +++ b/packages/appstore/src/components/elements/cfds-title/cfds-title.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { Text } from '@deriv/components'; +import { observer, useStore } from '@deriv/stores'; +import { localize } from '@deriv/translations'; +import './cfds-title.scss'; + +const CFDsTitle = observer(() => { + const { ui } = useStore(); + const { is_mobile } = ui; + + if (is_mobile) return null; + return ( +
+ + {localize('CFDs')} + +
+ ); +}); + +export default CFDsTitle; diff --git a/packages/appstore/src/components/elements/cfds-title/index.tsx b/packages/appstore/src/components/elements/cfds-title/index.tsx new file mode 100644 index 000000000000..d84f5d3f9fde --- /dev/null +++ b/packages/appstore/src/components/elements/cfds-title/index.tsx @@ -0,0 +1,3 @@ +import CFDsTitle from './cfds-title'; + +export default CFDsTitle; diff --git a/packages/appstore/src/components/elements/options-description/index.tsx b/packages/appstore/src/components/elements/options-description/index.tsx new file mode 100644 index 000000000000..e04fe83e4d6c --- /dev/null +++ b/packages/appstore/src/components/elements/options-description/index.tsx @@ -0,0 +1,3 @@ +import OptionsTitle from './options-description'; + +export default OptionsTitle; diff --git a/packages/appstore/src/components/elements/options-description/options-description.tsx b/packages/appstore/src/components/elements/options-description/options-description.tsx new file mode 100644 index 000000000000..092f8612ad53 --- /dev/null +++ b/packages/appstore/src/components/elements/options-description/options-description.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { Text, StaticUrl } from '@deriv/components'; +import { Localize } from '@deriv/translations'; + +type TOptionsDescription = { + is_eu_user: boolean; +}; + +const OptionsDescription = ({ is_eu_user }: TOptionsDescription) => { + return is_eu_user ? ( + + ]} + /> + + ) : ( +
+ + , + ]} + /> + +
+ ); +}; + +export default OptionsDescription; diff --git a/packages/appstore/src/components/elements/options-title/index.tsx b/packages/appstore/src/components/elements/options-title/index.tsx new file mode 100644 index 000000000000..d0a0ddf17aed --- /dev/null +++ b/packages/appstore/src/components/elements/options-title/index.tsx @@ -0,0 +1,3 @@ +import OptionsTitle from './options-title'; + +export default OptionsTitle; diff --git a/packages/appstore/src/components/elements/options-title/options-title.tsx b/packages/appstore/src/components/elements/options-title/options-title.tsx new file mode 100644 index 000000000000..42ed13befff2 --- /dev/null +++ b/packages/appstore/src/components/elements/options-title/options-title.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { Text } from '@deriv/components'; +import { observer, useStore } from '@deriv/stores'; +import { Localize } from '@deriv/translations'; + +type TOptionsTitle = { + is_eu_user: boolean; +}; + +const OptionsTitle = observer(({ is_eu_user }: TOptionsTitle) => { + const { ui } = useStore(); + const { is_mobile } = ui; + + if (is_mobile) return null; + return is_eu_user ? ( + + + + ) : ( + + + + ); +}); + +export default OptionsTitle; diff --git a/packages/appstore/src/components/get-started-trading-banner/get-started-trading-banner.scss b/packages/appstore/src/components/get-started-trading-banner/get-started-trading-banner.scss new file mode 100644 index 000000000000..9cdd0f69afe1 --- /dev/null +++ b/packages/appstore/src/components/get-started-trading-banner/get-started-trading-banner.scss @@ -0,0 +1,55 @@ +.get-started-trading-banner { + border: 1px solid var(--general-section-5); + border-radius: $BORDER_RADIUS * 4; + + &__content { + position: relative; + display: flex; + justify-content: space-between; + + border-radius: $BORDER_RADIUS * 4 $BORDER_RADIUS * 4 0 0; + background: var(--traders-hub-logged-out-banner-bg-color); + height: 18rem; + background-image: url('./../../public/images/traders-hub-logged-out-banner-bg-desktop.svg'); + background-repeat: no-repeat; + + @include mobile { + height: 14.4rem; + background-image: url('./../../public/images/traders-hub-logged-out-banner-bg-responsive.svg'); + background-repeat: no-repeat; + } + } + + &__description { + display: flex; + flex-direction: column; + align-items: flex-start; + align-self: center; + gap: 1.6rem; + margin-inline-start: 4.8rem; + + @include mobile { + margin-inline-start: 1.6rem; + } + } + + &__button { + padding: 1.2rem 1.6rem; + border-radius: $BORDER_RADIUS * 3; + + @include mobile { + padding: 0.5rem 1.2rem; + } + } + + &__image { + position: absolute; + right: 0; + margin-inline-end: 1.2rem; + + @include mobile { + margin-inline-end: 0; + align-self: flex-end; + } + } +} diff --git a/packages/appstore/src/components/get-started-trading-banner/get-started-trading-banner.tsx b/packages/appstore/src/components/get-started-trading-banner/get-started-trading-banner.tsx new file mode 100644 index 000000000000..81398f3bf279 --- /dev/null +++ b/packages/appstore/src/components/get-started-trading-banner/get-started-trading-banner.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import { getLanguage, Localize, localize } from '@deriv/translations'; +import { Button, Text, Icon } from '@deriv/components'; +import { useStore, observer } from '@deriv/stores'; +import { redirectToLogin } from '@deriv/shared'; +import TrustpilotWidget from 'Components/trustpilot-widget'; +import './get-started-trading-banner.scss'; + +const GetStartedTradingBanner = observer(() => { + const { ui, traders_hub } = useStore(); + const { is_mobile } = ui; + const { is_eu_user } = traders_hub; + + const desktopWidth = is_eu_user ? 326 : 445; + const desktopHeight = is_eu_user ? 174 : 176; + const responsiveWidth = 180; + const responsiveHeight = 116; + + return ( +
+
+
+ + + +
+ +
+ +
+ ); +}); + +export default GetStartedTradingBanner; diff --git a/packages/appstore/src/components/get-started-trading-banner/index.ts b/packages/appstore/src/components/get-started-trading-banner/index.ts new file mode 100644 index 000000000000..232e758172c7 --- /dev/null +++ b/packages/appstore/src/components/get-started-trading-banner/index.ts @@ -0,0 +1,3 @@ +import GetStartedTradingBanner from './get-started-trading-banner'; + +export default GetStartedTradingBanner; diff --git a/packages/appstore/src/components/multi-action-button-group/__test__/multi-action-button-group.spec.tsx b/packages/appstore/src/components/multi-action-button-group/__test__/multi-action-button-group.spec.tsx index d7071ce40418..bc49d3b9d3fd 100644 --- a/packages/appstore/src/components/multi-action-button-group/__test__/multi-action-button-group.spec.tsx +++ b/packages/appstore/src/components/multi-action-button-group/__test__/multi-action-button-group.spec.tsx @@ -7,7 +7,7 @@ import { createBrowserHistory } from 'history'; import { routes } from '@deriv/shared'; const mock_props = { - link_to: routes.trader, + link_to: routes.trade, onAction: jest.fn(), is_buttons_disabled: false, is_real: true, @@ -69,10 +69,6 @@ describe('Test Cases for Multi Action Button Group:', () => { ); - - const open_btn = screen.getByText('Open'); - userEvent.click(open_btn); - - expect(history.location.pathname).toBe(routes.trade); + expect(screen.getByRole('link')).toHaveAttribute('href', routes.trade); }); }); diff --git a/packages/appstore/src/components/options-multipliers-listing-logged-out/index.tsx b/packages/appstore/src/components/options-multipliers-listing-logged-out/index.tsx new file mode 100644 index 000000000000..c4ffbe469387 --- /dev/null +++ b/packages/appstore/src/components/options-multipliers-listing-logged-out/index.tsx @@ -0,0 +1,3 @@ +import OptionsAndMultipliersListingLoggedOut from './options-multipliers-listing-logged-out'; + +export default OptionsAndMultipliersListingLoggedOut; diff --git a/packages/appstore/src/components/options-multipliers-listing-logged-out/options-multipliers-listing-logged-out.scss b/packages/appstore/src/components/options-multipliers-listing-logged-out/options-multipliers-listing-logged-out.scss new file mode 100644 index 000000000000..71d62a4eb072 --- /dev/null +++ b/packages/appstore/src/components/options-multipliers-listing-logged-out/options-multipliers-listing-logged-out.scss @@ -0,0 +1,5 @@ +.options-multipliers-listing-logged-out { + &__description { + max-width: 65rem; + } +} diff --git a/packages/appstore/src/components/options-multipliers-listing-logged-out/options-multipliers-listing-logged-out.tsx b/packages/appstore/src/components/options-multipliers-listing-logged-out/options-multipliers-listing-logged-out.tsx new file mode 100644 index 000000000000..3389cefcd389 --- /dev/null +++ b/packages/appstore/src/components/options-multipliers-listing-logged-out/options-multipliers-listing-logged-out.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import { useStore, observer } from '@deriv/stores'; +import ListingContainer from 'Components/containers/listing-container'; +import TradingAppCard from 'Components/containers/trading-app-card'; +import OptionsDescription from 'Components/elements/options-description'; +import OptionsTitle from 'Components/elements/options-title'; +import { BrandConfig } from 'Constants/platform-config'; +import { getHasDivider } from 'Constants/utils'; +import { isEuCountry } from '@deriv/shared'; +import './options-multipliers-listing-logged-out.scss'; + +const OptionsAndMultipliersListingLoggedOut = observer(() => { + const { traders_hub, client } = useStore(); + const { clients_country } = client; + const { available_platforms, is_eu_user } = traders_hub; + + const logged_out_available_platforms = isEuCountry(clients_country) + ? available_platforms.filter(platform => ['EU', 'All'].some(region => region === platform.availability)) + : available_platforms.filter(platform => ['Non-EU', 'All'].some(region => region === platform.availability)); + + return ( + } + description={} + > + {logged_out_available_platforms.map((available_platform: BrandConfig, index: number) => ( + + ))} + + ); +}); + +export default OptionsAndMultipliersListingLoggedOut; diff --git a/packages/appstore/src/components/ordered-platform-sections/index.ts b/packages/appstore/src/components/ordered-platform-sections/index.ts new file mode 100644 index 000000000000..2348ad9ea7c3 --- /dev/null +++ b/packages/appstore/src/components/ordered-platform-sections/index.ts @@ -0,0 +1,3 @@ +import OrderedPlatformSections from './ordered-platform-sections'; + +export default OrderedPlatformSections; diff --git a/packages/appstore/src/components/ordered-platform-sections/ordered-platform-sections.scss b/packages/appstore/src/components/ordered-platform-sections/ordered-platform-sections.scss new file mode 100644 index 000000000000..012ade862281 --- /dev/null +++ b/packages/appstore/src/components/ordered-platform-sections/ordered-platform-sections.scss @@ -0,0 +1,9 @@ +.ordered-platform-sections { + display: flex; + flex-direction: column; + gap: 2.4rem; + + &__reversed { + flex-direction: column-reverse; + } +} diff --git a/packages/appstore/src/components/ordered-platform-sections/ordered-platform-sections.tsx b/packages/appstore/src/components/ordered-platform-sections/ordered-platform-sections.tsx new file mode 100644 index 000000000000..f38d19f50e56 --- /dev/null +++ b/packages/appstore/src/components/ordered-platform-sections/ordered-platform-sections.tsx @@ -0,0 +1,53 @@ +import React from 'react'; +import classNames from 'classnames'; +import { observer, useStore } from '@deriv/stores'; +import OptionsAndMultipliersListingLoggedOut from 'Components/options-multipliers-listing-logged-out'; +import CFDsListingLoggedOut from 'Components/cfds-listing-logged-out'; +import './ordered-platform-sections.scss'; + +type TGetOrderedPlatformSections = { + is_cfd_visible?: boolean; + is_options_and_multipliers_visible?: boolean; +}; + +type TOrderedPlatformSections = { + isDesktop?: boolean; +}; + +const GetOrderedPlatformSections = observer( + ({ is_cfd_visible = true, is_options_and_multipliers_visible = true }: TGetOrderedPlatformSections) => { + const { traders_hub } = useStore(); + const { is_eu_user } = traders_hub; + + return ( +
+ {is_options_and_multipliers_visible && } + {is_cfd_visible && } +
+ ); + } +); + +const OrderedPlatformSections = observer(({ isDesktop = false }: TOrderedPlatformSections) => { + const { traders_hub, client } = useStore(); + const { is_mt5_allowed, is_logged_in } = client; + const { selected_platform_type } = traders_hub; + + if ((is_logged_in && is_mt5_allowed) || !is_logged_in) { + return isDesktop ? ( + + ) : ( + + ); + } + return ; +}); + +export default OrderedPlatformSections; diff --git a/packages/appstore/src/components/routes/routes.tsx b/packages/appstore/src/components/routes/routes.tsx index 9532212afebe..5e9773354107 100644 --- a/packages/appstore/src/components/routes/routes.tsx +++ b/packages/appstore/src/components/routes/routes.tsx @@ -3,34 +3,46 @@ import { Loading } from '@deriv/components'; import { observer, useStore } from '@deriv/stores'; import { localize } from '@deriv/translations'; import { routes } from '@deriv/shared'; -import { Switch, useHistory } from 'react-router-dom'; +import { Switch } from 'react-router-dom'; import RouteWithSubroutes from './route-with-sub-routes.jsx'; const Onboarding = React.lazy(() => import(/* webpackChunkName: "modules-onboarding" */ 'Modules/onboarding')); const TradersHub = React.lazy(() => import(/* webpackChunkName: "modules-traders-hub" */ 'Modules/traders-hub')); +const TradersHubLoggedOut = React.lazy( + () => import(/* webpackChunkName: "modules-traders-hub-logged-out" */ 'Modules/traders-hub-logged-out') +); +const Page404 = React.lazy(() => import(/* */ 'Modules/Page404')); const Routes: React.FC = observer(() => { const { client } = useStore(); - const { has_wallet } = client; - const history = useHistory(); + const { is_logged_in, is_logging_in } = client; - React.useLayoutEffect(() => { - if (has_wallet) history.push(routes.wallets); - }, [history, has_wallet]); + const title_TH = localize("Trader's Hub"); + const title_TH_logged_out = localize('Deriv App'); + + const componentToRender = () => { + if (is_logged_in || is_logging_in) { + return TradersHub; + } + return TradersHubLoggedOut; + }; return ( }> localize("Trader's Hub")} + path={routes.traders_hub} + exact + component={componentToRender()} + getTitle={() => (is_logged_in || is_logging_in ? title_TH : title_TH_logged_out)} /> localize('Onboarding')} /> + localize('Deriv App')} /> ); diff --git a/packages/appstore/src/components/tabs-or-title/index.ts b/packages/appstore/src/components/tabs-or-title/index.ts new file mode 100644 index 000000000000..1f0bd8623917 --- /dev/null +++ b/packages/appstore/src/components/tabs-or-title/index.ts @@ -0,0 +1,3 @@ +import TabsOrTitle from './tabs-or-title'; + +export default TabsOrTitle; diff --git a/packages/appstore/src/components/tabs-or-title/tabs-or-title.scss b/packages/appstore/src/components/tabs-or-title/tabs-or-title.scss new file mode 100644 index 000000000000..a8aebecd713e --- /dev/null +++ b/packages/appstore/src/components/tabs-or-title/tabs-or-title.scss @@ -0,0 +1,11 @@ +.tabs-or-title { + &__button-toggle { + border-radius: $BORDER_RADIUS; + padding: 0.4rem; + background-color: var(--general-section-1); + } + + &__mt5-not-allowed { + margin-top: 2rem; + } +} diff --git a/packages/appstore/src/components/tabs-or-title/tabs-or-title.tsx b/packages/appstore/src/components/tabs-or-title/tabs-or-title.tsx new file mode 100644 index 000000000000..b1a2ee13c7cb --- /dev/null +++ b/packages/appstore/src/components/tabs-or-title/tabs-or-title.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { observer, useStore } from '@deriv/stores'; +import { ButtonToggle, Text } from '@deriv/components'; +import { Localize } from '@deriv/translations'; +import { getPlatformToggleOptions } from 'Helpers'; +import './tabs-or-title.scss'; + +const TabsOrTitle = observer(() => { + const { traders_hub, client } = useStore(); + const { is_mt5_allowed, is_logged_in } = client; + const { selected_platform_type, setTogglePlatformType, is_eu_user } = traders_hub; + + const platform_toggle_options = getPlatformToggleOptions(is_eu_user); + const platform_toggle_options_eu = getPlatformToggleOptions(is_eu_user).reverse(); + + const platformTypeChange = (event: { + target: { + value: string; + name: string; + }; + }) => { + setTogglePlatformType(event.target.value); + }; + + return (is_logged_in && is_mt5_allowed) || !is_logged_in ? ( + + ) : ( +
+ + + +
+ ); +}); + +export default TabsOrTitle; diff --git a/packages/appstore/src/components/trustpilot-star-rating/index.ts b/packages/appstore/src/components/trustpilot-star-rating/index.ts new file mode 100644 index 000000000000..27cb7c9bfd2b --- /dev/null +++ b/packages/appstore/src/components/trustpilot-star-rating/index.ts @@ -0,0 +1,3 @@ +import TrustpilotStarRating from './trustpilot-star-rating'; + +export default TrustpilotStarRating; diff --git a/packages/appstore/src/components/trustpilot-star-rating/trustpilot-star-rating.scss b/packages/appstore/src/components/trustpilot-star-rating/trustpilot-star-rating.scss new file mode 100644 index 000000000000..c527c6373621 --- /dev/null +++ b/packages/appstore/src/components/trustpilot-star-rating/trustpilot-star-rating.scss @@ -0,0 +1,10 @@ +.trustpilot-star-rating { + display: flex; + justify-content: center; + align-items: center; + gap: 0.2rem; + + &__item { + padding: 0 0.2rem; + } +} diff --git a/packages/appstore/src/components/trustpilot-star-rating/trustpilot-star-rating.tsx b/packages/appstore/src/components/trustpilot-star-rating/trustpilot-star-rating.tsx new file mode 100644 index 000000000000..4e759b481f76 --- /dev/null +++ b/packages/appstore/src/components/trustpilot-star-rating/trustpilot-star-rating.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { Icon } from '@deriv/components'; +import './trustpilot-star-rating.scss'; + +const TrustpilotStarRating = ({ score }: { score: number }) => { + return ( +
+ {[...Array(5)].map((_, idx) => ( +
+ +
+ ))} +
+ ); +}; + +export default TrustpilotStarRating; diff --git a/packages/appstore/src/components/trustpilot-widget/index.ts b/packages/appstore/src/components/trustpilot-widget/index.ts new file mode 100644 index 000000000000..d7cd36fdcb47 --- /dev/null +++ b/packages/appstore/src/components/trustpilot-widget/index.ts @@ -0,0 +1,3 @@ +import TrustpilotWidget from './trustpilot-widget'; + +export default TrustpilotWidget; diff --git a/packages/appstore/src/components/trustpilot-widget/trustpilot-widget.scss b/packages/appstore/src/components/trustpilot-widget/trustpilot-widget.scss new file mode 100644 index 000000000000..595c3a8a5682 --- /dev/null +++ b/packages/appstore/src/components/trustpilot-widget/trustpilot-widget.scss @@ -0,0 +1,13 @@ +.trustpilot-widget { + &__content { + display: flex; + justify-content: center; + align-items: center; + gap: 2.4rem; + padding: 1.6rem 0; + } + + a { + text-decoration: none; + } +} diff --git a/packages/appstore/src/components/trustpilot-widget/trustpilot-widget.tsx b/packages/appstore/src/components/trustpilot-widget/trustpilot-widget.tsx new file mode 100644 index 000000000000..6458ff0194db --- /dev/null +++ b/packages/appstore/src/components/trustpilot-widget/trustpilot-widget.tsx @@ -0,0 +1,59 @@ +import React, { useEffect, useState } from 'react'; +import { Icon, Text } from '@deriv/components'; +import { useStore, observer } from '@deriv/stores'; +import { Localize } from '@deriv/translations'; +import { TTrustpilotWidgetData } from 'Types'; +import { fetchTrustpilotData } from 'Helpers'; +import TrustpilotStarRating from 'Components/trustpilot-star-rating'; +import './trustpilot-widget.scss'; + +const TrustpilotWidget = observer(() => { + const [trustpilotData, setTrustpilotData] = useState(); + const { ui } = useStore(); + const { is_mobile } = ui; + + useEffect(() => { + const getTrustpilotData = async () => { + const res = await fetchTrustpilotData(); + setTrustpilotData(res); + }; + + getTrustpilotData(); + }, []); + + if (!trustpilotData) return null; + + return ( + + ); +}); + +export default TrustpilotWidget; diff --git a/packages/appstore/src/helpers/account-helper.ts b/packages/appstore/src/helpers/account-helper.ts index 1f3c68f06231..9a939d6e6157 100644 --- a/packages/appstore/src/helpers/account-helper.ts +++ b/packages/appstore/src/helpers/account-helper.ts @@ -1,4 +1,5 @@ import { isCryptocurrency } from '@deriv/shared'; +import { localize } from '@deriv/translations'; type TAccountProps = { a_currency: string; @@ -35,3 +36,8 @@ export const getSortedAccountList = (account_list: TAccountProps, accounts: TAcc return 1; }); }; + +export const getPlatformToggleOptions = (is_eu_title: boolean) => [ + { text: is_eu_title ? localize('Multipliers') : localize('Options'), value: 'options' }, + { text: localize('CFDs'), value: 'cfd' }, +]; diff --git a/packages/appstore/src/helpers/index.ts b/packages/appstore/src/helpers/index.ts index afd9ee6328ca..f4ebebf671e3 100644 --- a/packages/appstore/src/helpers/index.ts +++ b/packages/appstore/src/helpers/index.ts @@ -1,2 +1,3 @@ export * from './account-helper'; export * from './total-assets-helper'; +export * from './trustpilot-helper'; diff --git a/packages/appstore/src/helpers/trustpilot-helper.ts b/packages/appstore/src/helpers/trustpilot-helper.ts new file mode 100644 index 000000000000..39fd501f462a --- /dev/null +++ b/packages/appstore/src/helpers/trustpilot-helper.ts @@ -0,0 +1,48 @@ +import { TTrustpilotWidgetData } from 'Types'; + +export const fetchTrustpilotData = async () => { + const defaultData = { + stars: 4.5, + trustScore: 4.5, + numberOfReviews: Number(47748).toLocaleString(), + }; + + try { + const appName = 'deriv.com'; + const apiKey = process.env.TRUSTPILOT_API_KEY; + + if (!appName || !apiKey) { + return { + ...defaultData, + error: 'Trustpilot app name or API key is missing', + }; + } + + const url = `https://api.trustpilot.com/v1/business-units/find?name=${appName}&apikey=${apiKey}`; + const response = await fetch(url); + + if (!response.ok) { + return { + ...defaultData, + error: `Network response was not ok: ${response.statusText}`, + }; + } + + const result = await response.json(); + + const trustpilotData: TTrustpilotWidgetData = { + stars: result.score?.stars || defaultData.stars, + trustScore: result.score?.trustScore || defaultData.trustScore, + numberOfReviews: result.numberOfReviews?.total?.toLocaleString() || defaultData.numberOfReviews, + }; + + return trustpilotData; + } catch (error) { + const trustpilotData: TTrustpilotWidgetData = { + ...defaultData, + error: `Something wrong: error = ${error}`, + }; + + return trustpilotData; + } +}; diff --git a/packages/appstore/src/modules/Page404/Components/Page404.tsx b/packages/appstore/src/modules/Page404/Components/Page404.tsx new file mode 100644 index 000000000000..448e3952ddc7 --- /dev/null +++ b/packages/appstore/src/modules/Page404/Components/Page404.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { PageError } from '@deriv/components'; +import { routes, getUrlBase } from '@deriv/shared'; +import { localize } from '@deriv/translations'; + +const Page404 = () => ( + +); + +export default Page404; diff --git a/packages/appstore/src/modules/Page404/Components/__tests__/Page404.spec.tsx b/packages/appstore/src/modules/Page404/Components/__tests__/Page404.spec.tsx new file mode 100644 index 000000000000..9d334c1850fb --- /dev/null +++ b/packages/appstore/src/modules/Page404/Components/__tests__/Page404.spec.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { Router } from 'react-router'; +import { createBrowserHistory } from 'history'; +import { render, screen } from '@testing-library/react'; +import Page404 from '../Page404'; + +describe('Page404', () => { + const browser_history = createBrowserHistory(); + + it('should render Page404', () => { + render( + + + + ); + + expect(screen.getByText('We couldn’t find that page')).toBeInTheDocument(); + }); +}); diff --git a/packages/appstore/src/modules/Page404/index.ts b/packages/appstore/src/modules/Page404/index.ts new file mode 100644 index 000000000000..04dbb70feef6 --- /dev/null +++ b/packages/appstore/src/modules/Page404/index.ts @@ -0,0 +1,3 @@ +import Page404 from './Components/Page404'; + +export default Page404; diff --git a/packages/appstore/src/modules/traders-hub-logged-out/index.tsx b/packages/appstore/src/modules/traders-hub-logged-out/index.tsx new file mode 100644 index 000000000000..d554a7a23180 --- /dev/null +++ b/packages/appstore/src/modules/traders-hub-logged-out/index.tsx @@ -0,0 +1,3 @@ +import TradersHubLoggedOut from './traders-hub-logged-out'; + +export default TradersHubLoggedOut; diff --git a/packages/appstore/src/modules/traders-hub-logged-out/traders-hub-logged-out.scss b/packages/appstore/src/modules/traders-hub-logged-out/traders-hub-logged-out.scss new file mode 100644 index 000000000000..3e2e64035769 --- /dev/null +++ b/packages/appstore/src/modules/traders-hub-logged-out/traders-hub-logged-out.scss @@ -0,0 +1,27 @@ +.traders-hub-logged-out { + max-width: 120rem; + display: flex; + flex-direction: column; + gap: 2.4rem; + margin: auto; + padding: 4rem; + + @include mobile { + padding: 2rem; + width: 100%; + } + + &__eu-user { + @include mobile { + min-height: 650px; + } + } + + &__mobile { + overflow: scroll !important; + + &::-webkit-scrollbar { + display: none; + } + } +} diff --git a/packages/appstore/src/modules/traders-hub-logged-out/traders-hub-logged-out.tsx b/packages/appstore/src/modules/traders-hub-logged-out/traders-hub-logged-out.tsx new file mode 100644 index 000000000000..0dcd18a66c5f --- /dev/null +++ b/packages/appstore/src/modules/traders-hub-logged-out/traders-hub-logged-out.tsx @@ -0,0 +1,54 @@ +import React from 'react'; +import classNames from 'classnames'; +import { observer, useStore } from '@deriv/stores'; +import { Div100vhContainer, DesktopWrapper, MobileWrapper, Loading, Text } from '@deriv/components'; +import { isEuCountry } from '@deriv/shared'; +import { Localize } from '@deriv/translations'; +import OrderedPlatformSections from 'Components/ordered-platform-sections'; +import GetStartedTradingBanner from 'Components/get-started-trading-banner'; +import TabsOrTitle from 'Components/tabs-or-title'; +import './traders-hub-logged-out.scss'; + +const TradersHubLoggedOut = observer(() => { + const { traders_hub, client, ui } = useStore(); + const { is_desktop } = ui; + const { clients_country } = client; + const { setTogglePlatformType, selectRegion, is_eu_user } = traders_hub; + + React.useEffect(() => { + if (clients_country) { + if (isEuCountry(clients_country)) { + setTogglePlatformType('cfd'); + selectRegion('EU'); + } else { + selectRegion('Non-EU'); + } + } + }, [clients_country, setTogglePlatformType]); + + if (!clients_country) return ; + + return ( + +
+ + + + + + + + + + + +
+
+ ); +}); + +export default TradersHubLoggedOut; diff --git a/packages/appstore/src/public/images/traders-hub-logged-out-banner-bg-desktop.svg b/packages/appstore/src/public/images/traders-hub-logged-out-banner-bg-desktop.svg new file mode 100644 index 000000000000..59878d9f1a4b --- /dev/null +++ b/packages/appstore/src/public/images/traders-hub-logged-out-banner-bg-desktop.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/appstore/src/public/images/traders-hub-logged-out-banner-bg-responsive.svg b/packages/appstore/src/public/images/traders-hub-logged-out-banner-bg-responsive.svg new file mode 100644 index 000000000000..77a6ac786e2b --- /dev/null +++ b/packages/appstore/src/public/images/traders-hub-logged-out-banner-bg-responsive.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/appstore/src/types/common.types.ts b/packages/appstore/src/types/common.types.ts index 3041ab6c551b..5040a4ab6174 100644 --- a/packages/appstore/src/types/common.types.ts +++ b/packages/appstore/src/types/common.types.ts @@ -1,6 +1,5 @@ import { DetailsOfEachMT5Loginid } from '@deriv/api-types'; import { useStore } from '@deriv/stores'; - import { PlatformIcons } from 'Assets/svgs/trading-platform'; import { RegionAvailability } from 'Constants/platform-config'; @@ -237,3 +236,10 @@ export type TRealWalletsUpgradeSteps = { current_step: number; }; }; + +export type TTrustpilotWidgetData = { + stars: number; + trustScore: number; + numberOfReviews: string; + error?: string; +}; diff --git a/packages/appstore/tsconfig.json b/packages/appstore/tsconfig.json index ad130b89d98e..411e481ddc02 100644 --- a/packages/appstore/tsconfig.json +++ b/packages/appstore/tsconfig.json @@ -15,6 +15,7 @@ "Stores": ["src/stores/index"], "Types": ["src/types"], "Utils": ["src/utils"], + "Helpers": ["src/helpers"], "@deriv/*": ["../*/src"] } }, diff --git a/packages/appstore/webpack.config.js b/packages/appstore/webpack.config.js index 73609e9186ab..19b2996e2d65 100644 --- a/packages/appstore/webpack.config.js +++ b/packages/appstore/webpack.config.js @@ -61,6 +61,7 @@ module.exports = function (env) { Types: path.resolve(__dirname, 'src/types'), Utils: path.resolve(__dirname, 'src/utils'), Hooks: path.resolve(__dirname, 'src/hooks'), + Helpers: path.resolve(__dirname, 'src/helpers'), }, extensions: ['.ts', '.tsx', '.js'], }, diff --git a/packages/cashier/src/containers/routes/binary-routes.tsx b/packages/cashier/src/containers/routes/binary-routes.tsx index b9aaad547a30..a72047a6c75b 100644 --- a/packages/cashier/src/containers/routes/binary-routes.tsx +++ b/packages/cashier/src/containers/routes/binary-routes.tsx @@ -1,10 +1,12 @@ import React, { useEffect } from 'react'; import { Switch } from 'react-router-dom'; +import { useFeatureFlags } from '@deriv/hooks'; +import { routes } from '@deriv/shared'; +import { useStore } from '@deriv/stores'; import { Localize, localize } from '@deriv/translations'; +import Page404 from 'Components/page-404'; import getRoutesConfig from 'Constants/routes-config'; import RouteWithSubRoutes from './route-with-sub-routes'; -import { useFeatureFlags } from '@deriv/hooks'; -import { routes } from '@deriv/shared'; type TBinaryRoutesProps = { is_logged_in: boolean; @@ -30,6 +32,8 @@ const cashierV2RoutesConfig = { const BinaryRoutes = (props: TBinaryRoutesProps) => { const { is_p2p_v2_enabled } = useFeatureFlags(); const [routesConfig, setRoutesConfig] = React.useState(getRoutesConfig()); + const { client } = useStore(); + const { has_wallet } = client; useEffect(() => { const isRouteAdded = (routePath: string) => routesConfig[0].routes?.some(route => route.path === routePath); @@ -43,6 +47,8 @@ const BinaryRoutes = (props: TBinaryRoutesProps) => { } }, [is_p2p_v2_enabled, routesConfig]); + if (has_wallet) return ; + return ( }> diff --git a/packages/cfd/src/Containers/__tests__/cfd-password-modal.spec.js b/packages/cfd/src/Containers/__tests__/cfd-password-modal.spec.js index e754e10622d0..b068c370a9c4 100644 --- a/packages/cfd/src/Containers/__tests__/cfd-password-modal.spec.js +++ b/packages/cfd/src/Containers/__tests__/cfd-password-modal.spec.js @@ -1,7 +1,7 @@ import React from 'react'; import { Router } from 'react-router'; import { createBrowserHistory } from 'history'; -import { WS, getErrorMessages, validPassword, Jurisdiction } from '@deriv/shared'; +import { WS, getErrorMessages, validPassword, Jurisdiction, routes } from '@deriv/shared'; import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import CFDPasswordModal from '../cfd-password-modal'; import CFDProviders from '../../cfd-providers'; @@ -91,7 +91,24 @@ describe('', () => { document.body.removeChild(modal_root_el); }); + const originalWindowLocation = window.location; + + beforeEach(() => { + Object.defineProperty(window, 'location', { + configurable: true, + enumerable: true, + value: { + pathname: routes.trade, + }, + }); + }); + afterEach(() => { + Object.defineProperty(window, 'location', { + configurable: true, + enumerable: true, + value: originalWindowLocation, + }); jest.clearAllMocks(); }); diff --git a/packages/components/src/components/button/button.scss b/packages/components/src/components/button/button.scss index d7e9f086fab6..c114544c9414 100644 --- a/packages/components/src/components/button/button.scss +++ b/packages/components/src/components/button/button.scss @@ -180,6 +180,20 @@ color: var(--text-colored-background); } } + &--black { + background: var(--button-get-started-bg); + &:hover:not([disabled]) { + opacity: 0.7; + } + &:active:not([disabled]) { + opacity: 0.7; + } + + .dc-btn__text, + .dc-btn__icon { + color: var(--general-main-1); + } + } &__small { height: 2.4rem; min-width: 4.8rem; diff --git a/packages/components/src/components/button/button.tsx b/packages/components/src/components/button/button.tsx index 99997d4d9c28..97e5f4e252cd 100644 --- a/packages/components/src/components/button/button.tsx +++ b/packages/components/src/components/button/button.tsx @@ -6,6 +6,7 @@ import Text from '../text'; export type TButtonCommonProps = { alternate: boolean; + black: boolean; blue: boolean; green: boolean; has_effect: boolean; @@ -45,6 +46,7 @@ const ButtonGroup = ({ children, className }: TButtonGroupProps) => (
{children}
); const Button = ({ + black, blue, children, className = '', @@ -83,6 +85,7 @@ const Button = ({ { 'dc-btn__effect': has_effect, 'dc-btn--primary': primary, + 'dc-btn--black': black, 'dc-btn--blue': blue, 'dc-btn--secondary': secondary, 'dc-btn--tertiary': tertiary, diff --git a/packages/components/src/components/icon/appstore/ic-appstore-logged-out-eu-coins-desktop.svg b/packages/components/src/components/icon/appstore/ic-appstore-logged-out-eu-coins-desktop.svg new file mode 100644 index 000000000000..ef0215f62857 --- /dev/null +++ b/packages/components/src/components/icon/appstore/ic-appstore-logged-out-eu-coins-desktop.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/src/components/icon/appstore/ic-appstore-logged-out-eu-coins-responsive.svg b/packages/components/src/components/icon/appstore/ic-appstore-logged-out-eu-coins-responsive.svg new file mode 100644 index 000000000000..7bff5b57793b --- /dev/null +++ b/packages/components/src/components/icon/appstore/ic-appstore-logged-out-eu-coins-responsive.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/src/components/icon/appstore/ic-appstore-logged-out-non-eu-coins-desktop.svg b/packages/components/src/components/icon/appstore/ic-appstore-logged-out-non-eu-coins-desktop.svg new file mode 100644 index 000000000000..2cdc0131c604 --- /dev/null +++ b/packages/components/src/components/icon/appstore/ic-appstore-logged-out-non-eu-coins-desktop.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/src/components/icon/appstore/ic-appstore-logged-out-non-eu-coins-responsive.svg b/packages/components/src/components/icon/appstore/ic-appstore-logged-out-non-eu-coins-responsive.svg new file mode 100644 index 000000000000..1b146fa7db16 --- /dev/null +++ b/packages/components/src/components/icon/appstore/ic-appstore-logged-out-non-eu-coins-responsive.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/src/components/icon/appstore/ic-appstore-trustpilot-logo.svg b/packages/components/src/components/icon/appstore/ic-appstore-trustpilot-logo.svg new file mode 100644 index 000000000000..cbb1afe6f38b --- /dev/null +++ b/packages/components/src/components/icon/appstore/ic-appstore-trustpilot-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/src/components/icon/appstore/ic-appstore-trustpilot-star.svg b/packages/components/src/components/icon/appstore/ic-appstore-trustpilot-star.svg new file mode 100644 index 000000000000..755fb0c57e47 --- /dev/null +++ b/packages/components/src/components/icon/appstore/ic-appstore-trustpilot-star.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/src/components/icon/common/ic-deriv-short-logo.svg b/packages/components/src/components/icon/common/ic-deriv-short-logo.svg new file mode 100644 index 000000000000..78c46cdb15c9 --- /dev/null +++ b/packages/components/src/components/icon/common/ic-deriv-short-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/src/components/icon/icons.js b/packages/components/src/components/icon/icons.js index 76bf9c82a119..aed7b3089e82 100644 --- a/packages/components/src/components/icon/icons.js +++ b/packages/components/src/components/icon/icons.js @@ -22,6 +22,10 @@ import './appstore/ic-appstore-home.svg'; import './appstore/ic-appstore-information.svg'; import './appstore/ic-appstore-link-wallet.svg'; import './appstore/ic-appstore-linked-wallets.svg'; +import './appstore/ic-appstore-logged-out-eu-coins-desktop.svg'; +import './appstore/ic-appstore-logged-out-eu-coins-responsive.svg'; +import './appstore/ic-appstore-logged-out-non-eu-coins-desktop.svg'; +import './appstore/ic-appstore-logged-out-non-eu-coins-responsive.svg'; import './appstore/ic-appstore-menu-homepage.svg'; import './appstore/ic-appstore-multipliers-trade-type.svg'; import './appstore/ic-appstore-option-trade-type.svg'; @@ -34,6 +38,8 @@ import './appstore/ic-appstore-traders-hub-home.svg'; import './appstore/ic-appstore-trading-hub-beta.svg'; import './appstore/ic-appstore-trading-hub-onboarding-dark.svg'; import './appstore/ic-appstore-trading-hub-onboarding.svg'; +import './appstore/ic-appstore-trustpilot-logo.svg'; +import './appstore/ic-appstore-trustpilot-star.svg'; import './appstore/ic-appstore-wallet-aud-light.svg'; import './appstore/ic-appstore-wallet-bitcoin-light.svg'; import './appstore/ic-appstore-wallet-card-placeholder-dark-red-line.svg'; @@ -371,6 +377,7 @@ import './common/ic-demo-reset-balance-done.svg'; import './common/ic-demo-reset-balance.svg'; import './common/ic-demo.svg'; import './common/ic-deriv-outline.svg'; +import './common/ic-deriv-short-logo.svg'; import './common/ic-deriv.svg'; import './common/ic-desktop-outline.svg'; import './common/ic-desktop.svg'; diff --git a/packages/components/src/components/route-with-subroutes/route-with-subroutes.tsx b/packages/components/src/components/route-with-subroutes/route-with-subroutes.tsx index f0aa214b0ac2..53c3686f7546 100644 --- a/packages/components/src/components/route-with-subroutes/route-with-subroutes.tsx +++ b/packages/components/src/components/route-with-subroutes/route-with-subroutes.tsx @@ -66,7 +66,7 @@ const RouteWithSubRoutes = ({ if (should_redirect_login) { redirectToLogin(is_logged_in, language); } else { - result = ; + result = ; } } else { const default_subroute = routes.find(r => r.default); @@ -81,7 +81,7 @@ const RouteWithSubRoutes = ({ ) : ( - {should_redirect ? : } + {should_redirect ? : } )} diff --git a/packages/core/src/App/AppContent.tsx b/packages/core/src/App/AppContent.tsx index d3149f0f068d..13c23d783dac 100644 --- a/packages/core/src/App/AppContent.tsx +++ b/packages/core/src/App/AppContent.tsx @@ -7,10 +7,8 @@ import { observer, useStore } from '@deriv/stores'; import { getLanguage } from '@deriv/translations'; import { Analytics } from '@deriv-com/analytics'; import { browserSupportsWebAuthn } from '@simplewebauthn/browser'; - import BinaryBotIFrame from 'Modules/BinaryBotIFrame'; import SmartTraderIFrame from 'Modules/SmartTraderIFrame'; - import ErrorBoundary from './Components/Elements/Errors/error-boundary.jsx'; import AppToastMessages from './Containers/app-toast-messages.jsx'; import AppContents from './Containers/Layout/app-contents.jsx'; diff --git a/packages/core/src/App/Components/Layout/Header/menu-link.tsx b/packages/core/src/App/Components/Layout/Header/menu-link.tsx index 570024cb85c8..4aa7f32b9dde 100644 --- a/packages/core/src/App/Components/Layout/Header/menu-link.tsx +++ b/packages/core/src/App/Components/Layout/Header/menu-link.tsx @@ -85,10 +85,8 @@ const MenuLink = observer( } if (is_cashier_link && is_virtual && !has_any_real_account) { - const toggle_modal_routes = window.location.pathname === routes.root || traders_hub_path; - const handleClickCashier = () => { - if (toggle_modal_routes) { + if (traders_hub_path) { toggleReadyToDepositModal(); } onClickLink?.(); @@ -136,7 +134,7 @@ const MenuLink = observer( className={is_trade_text ? '' : 'header__menu-mobile-link-text'} as='h3' size='xs' - weight={window.location.pathname === '/' && is_trade_text ? 'bold' : undefined} + weight={window.location.pathname === routes.trade && is_trade_text ? 'bold' : undefined} > {text} @@ -152,7 +150,6 @@ const MenuLink = observer( 'header__menu-mobile-link--disabled': is_disabled, 'header__menu-mobile-link--active': is_active, })} - active_class='header__menu-mobile-link--active' onClick={onClickLink} data-testid={data_testid} > @@ -161,7 +158,7 @@ const MenuLink = observer( className={is_trade_text ? '' : 'header__menu-mobile-link-text'} as='h3' size='xs' - weight={window.location.pathname === '/' && is_trade_text ? 'bold' : undefined} + weight={window.location.pathname === routes.trade && is_trade_text ? 'bold' : undefined} > {text} diff --git a/packages/core/src/App/Components/Layout/Header/menu-links.jsx b/packages/core/src/App/Components/Layout/Header/menu-links.jsx index db17c7c07bc9..1bc5efdcda79 100644 --- a/packages/core/src/App/Components/Layout/Header/menu-links.jsx +++ b/packages/core/src/App/Components/Layout/Header/menu-links.jsx @@ -46,9 +46,7 @@ const CashierTab = observer(() => { const history = useHistory(); const toggle_modal_routes = - window.location.pathname === routes.root || - window.location.pathname === routes.traders_hub || - window.location.pathname === routes.bot; + window.location.pathname === routes.traders_hub || window.location.pathname === routes.bot; const toggleModal = () => { if (toggle_modal_routes && !has_any_real_account) { diff --git a/packages/core/src/App/Components/Layout/Header/toggle-menu-drawer.jsx b/packages/core/src/App/Components/Layout/Header/toggle-menu-drawer.jsx index d2a66df685e2..77ad377ddcdc 100644 --- a/packages/core/src/App/Components/Layout/Header/toggle-menu-drawer.jsx +++ b/packages/core/src/App/Components/Layout/Header/toggle-menu-drawer.jsx @@ -66,7 +66,6 @@ const ToggleMenuDrawer = observer(({ platform_config }) => { const is_trading_hub_category = route.startsWith(routes.traders_hub) || route.startsWith(routes.cashier) || route.startsWith(routes.account); - const is_wallets_category = route.startsWith(routes.wallets); const isMounted = useIsMounted(); const { data } = useRemoteConfig(isMounted()); @@ -108,10 +107,8 @@ const ToggleMenuDrawer = observer(({ platform_config }) => { const location = window.location.pathname; - if (location === routes.traders_hub || is_trading_hub_category) { - primary_routes = [routes.account, routes.cashier]; - } else if (has_wallet || location === routes.wallets) { - primary_routes = [routes.reports, routes.account]; + if (location === is_trading_hub_category) { + primary_routes = has_wallet ? [routes.reports, routes.account] : [routes.account, routes.cashier]; } else { primary_routes = [routes.reports, routes.account, routes.cashier]; } @@ -313,7 +310,7 @@ const ToggleMenuDrawer = observer(({ platform_config }) => {
- {!is_trading_hub_category && !is_wallets_category && ( + { { setTogglePlatformType={setTogglePlatformType} /> - )} - + } +
+ + + {is_logged_in && ( )} - {!is_trading_hub_category && !is_wallets_category && ( + { - )} + } {primary_routes_config.map((route_config, idx) => getRoutesWithSubMenu(route_config, idx) )} @@ -382,7 +385,6 @@ const ToggleMenuDrawer = observer(({ platform_config }) => {
)} - {HelpCentreRoute()} {is_logged_in && ( diff --git a/packages/core/src/App/Constants/routes-config.js b/packages/core/src/App/Constants/routes-config.js index 06a047a4ffb9..1f800b8d8f59 100644 --- a/packages/core/src/App/Constants/routes-config.js +++ b/packages/core/src/App/Constants/routes-config.js @@ -4,6 +4,7 @@ import { makeLazyLoader, routes, moduleLoader } from '@deriv/shared'; import { Loading } from '@deriv/components'; import { localize } from '@deriv/translations'; import Redirect from 'App/Containers/Redirect'; +import RootComponent from 'App/Containers/RootComponent'; import Endpoint from 'Modules/Endpoint'; const CFDCompareAccounts = React.lazy(() => @@ -53,20 +54,6 @@ const Bot = React.lazy(() => }) ); -const AppStore = React.lazy(() => - moduleLoader(() => { - // eslint-disable-next-line import/no-unresolved - return import(/* webpackChunkName: "appstore" */ '@deriv/appstore'); - }) -); - -const Wallets = React.lazy(() => - moduleLoader(() => { - // eslint-disable-next-line import/no-unresolved - return import(/* webpackChunkName: "wallets" */ '@deriv/wallets'); - }) -); - const TradersHub = React.lazy(() => moduleLoader(() => { // eslint-disable-next-line import/no-unresolved @@ -102,6 +89,10 @@ const Cashier_V2 = React.lazy(() => }) ); +const RedirectToNewTradersHub = () => { + return ; +}; + const getModules = () => { const modules = [ { @@ -283,18 +274,6 @@ const getModules = () => { }, ], }, - { - path: routes.traders_hub, - component: AppStore, - is_authenticated: true, - getTitle: () => localize("Trader's Hub"), - }, - { - path: routes.wallets, - component: Wallets, - is_authenticated: true, - getTitle: () => localize('Wallets'), - }, { path: routes.cashier_p2p_v2, component: P2P_V2, @@ -319,25 +298,6 @@ const getModules = () => { is_authenticated: true, getTitle: () => localize('Cashier'), }, - { - path: routes.onboarding, - component: AppStore, - is_authenticated: false, - getTitle: () => localize('Appstore'), - routes: [ - { - path: routes.traders_hub, - component: AppStore, - getTitle: () => localize("Trader's Hub"), - }, - { - path: routes.onboarding, - component: AppStore, - is_authenticated: false, - getTitle: () => localize('Onboarding'), - }, - ], - }, { path: routes.cashier, component: Cashier, @@ -431,18 +391,27 @@ const getModules = () => { ], }, { - path: routes.root, + path: routes.trade, component: Trader, getTitle: () => localize('Trader'), - routes: [ - { - path: routes.contract, - component: Trader, - getTitle: () => localize('Contract Details'), - is_authenticated: true, - }, - { path: routes.error404, component: Trader, getTitle: () => localize('Error 404') }, - ], + }, + { + path: routes.contract, + component: Trader, + getTitle: () => localize('Contract Details'), + is_authenticated: true, + }, + { + path: routes.old_traders_hub, + component: RedirectToNewTradersHub, + is_authenticated: false, + getTitle: () => localize("Trader's Hub"), + }, + { + path: routes.traders_hub, + component: RootComponent, + is_authenticated: false, + getTitle: () => localize("Trader's Hub"), }, ]; @@ -457,7 +426,7 @@ const lazyLoadComplaintsPolicy = makeLazyLoader( // Order matters // TODO: search tag: test-route-parent-info -> Enable test for getting route parent info when there are nested routes const initRoutesConfig = () => [ - { path: routes.index, component: RouterRedirect, getTitle: () => '', to: routes.root }, + { path: routes.index, component: RouterRedirect, getTitle: () => '', to: routes.traders_hub }, { path: routes.endpoint, component: Endpoint, getTitle: () => 'Endpoint' }, // doesn't need localization as it's for internal use { path: routes.redirect, component: Redirect, getTitle: () => localize('Redirect') }, { diff --git a/packages/core/src/App/Containers/AccountSwitcherWallet/account-switcher-wallet-mobile.tsx b/packages/core/src/App/Containers/AccountSwitcherWallet/account-switcher-wallet-mobile.tsx index 7320d0e593e1..d3c7810e6894 100644 --- a/packages/core/src/App/Containers/AccountSwitcherWallet/account-switcher-wallet-mobile.tsx +++ b/packages/core/src/App/Containers/AccountSwitcherWallet/account-switcher-wallet-mobile.tsx @@ -26,7 +26,7 @@ export const AccountSwitcherWalletMobile = observer(({ is_visible, toggle, login const handleTradersHubRedirect = () => { closeAccountsDialog(); - history.push(routes.wallets); + history.push(routes.traders_hub); }; const handleManageFundsRedirect = () => { diff --git a/packages/core/src/App/Containers/AccountSwitcherWallet/account-switcher-wallet.tsx b/packages/core/src/App/Containers/AccountSwitcherWallet/account-switcher-wallet.tsx index 8ee7106952ce..8ed4182b10c1 100644 --- a/packages/core/src/App/Containers/AccountSwitcherWallet/account-switcher-wallet.tsx +++ b/packages/core/src/App/Containers/AccountSwitcherWallet/account-switcher-wallet.tsx @@ -40,7 +40,7 @@ export const AccountSwitcherWallet = observer(({ is_visible, toggle }: TAccountS const handleTradersHubRedirect = async () => { closeAccountsDialog(); - history.push(routes.wallets); + history.push(routes.traders_hub); }; return ( diff --git a/packages/core/src/App/Containers/Layout/header/__tests__/default-header.spec.tsx b/packages/core/src/App/Containers/Layout/header/__tests__/default-header.spec.tsx index 7e01b972ae7f..be7d2f00dbfc 100644 --- a/packages/core/src/App/Containers/Layout/header/__tests__/default-header.spec.tsx +++ b/packages/core/src/App/Containers/Layout/header/__tests__/default-header.spec.tsx @@ -12,6 +12,7 @@ jest.mock('App/Components/Layout/Header/toggle-menu-drawer.jsx', () => jest.fn(() =>
Mocked Toggle Menu Drawer
) ); jest.mock('../header-account-actions', () => jest.fn(() =>
Mocked Header Account Action
)); +jest.mock('../deriv-short-logo', () => jest.fn(() =>
Deriv Short Logo
)); describe('DefaultHeader', () => { const mock_store = mockStore({ ui: { is_desktop: true, is_real_acc_signup_on: true } }); diff --git a/packages/core/src/App/Containers/Layout/header/__tests__/default-mobile-links.spec.tsx b/packages/core/src/App/Containers/Layout/header/__tests__/default-mobile-links.spec.tsx index 893dfb24a8ad..bf9dfec47eb7 100644 --- a/packages/core/src/App/Containers/Layout/header/__tests__/default-mobile-links.spec.tsx +++ b/packages/core/src/App/Containers/Layout/header/__tests__/default-mobile-links.spec.tsx @@ -1,8 +1,17 @@ import React from 'react'; +import { StoreProvider, mockStore } from '@deriv/stores'; +import { BrowserHistory, createBrowserHistory } from 'history'; +import { Router } from 'react-router'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { routes } from '@deriv/shared'; import DefaultMobileLinks from '../default-mobile-links'; +import { useIsRealAccountNeededForCashier } from '@deriv/hooks'; +jest.mock('@deriv/hooks', () => ({ + ...jest.requireActual('@deriv/hooks'), + useIsRealAccountNeededForCashier: jest.fn(() => false), +})); jest.mock('App/Components/Routes', () => ({ BinaryLink: jest.fn(() =>
MockedBinaryLink to Account Settings
), })); @@ -10,30 +19,60 @@ jest.mock('../show-notifications', () => jest.fn(() =>
MockedShowNotifications
) ); -jest.mock('@deriv/stores', () => ({ - ...jest.requireActual('@deriv/stores'), - useStore: jest.fn(() => ({ client: { has_wallet: false } })), -})); - describe('DefaultMobileLinks', () => { - const mock_props: React.ComponentProps = { - handleClickCashier: jest.fn(), + let history: BrowserHistory, mock_store: ReturnType; + + beforeEach(() => { + mock_store = mockStore({ + client: { has_wallet: false, has_any_real_account: true, is_virtual: false }, + ui: { + toggleNeedRealAccountForCashierModal: jest.fn(), + toggleReadyToDepositModal: jest.fn(), + }, + }); + history = createBrowserHistory(); + }); + + const wrapper = ({ children }: { children: React.ReactNode }) => { + return ( + + {children} + + ); }; it('should render "DefaultMobileLinks" with Notifications & link to Account Settings', () => { - render(); + render(, { wrapper }); expect(screen.getByText('MockedShowNotifications')).toBeInTheDocument(); expect(screen.getByText('MockedBinaryLink to Account Settings')).toBeInTheDocument(); }); it('should display the cashier button', () => { - render(); + render(, { wrapper }); expect(screen.getByRole('button', { name: 'Cashier' })).toBeInTheDocument(); }); - it('should fire the "handleClickCashier" event on clicking the button', () => { - render(); - userEvent.click(screen.getByRole('button', { name: 'Cashier' })); - expect(mock_props.handleClickCashier).toHaveBeenCalledTimes(1); + it('should trigger `toggleReadyToDepositModal` if user does not have any real account and active account is virtual', () => { + mock_store.client.has_any_real_account = false; + mock_store.client.is_virtual = true; + render(, { wrapper }); + const cashierButton = screen.getByRole('button', { name: 'Cashier' }); + userEvent.click(cashierButton); + expect(mock_store.ui.toggleReadyToDepositModal).toHaveBeenCalledTimes(1); + }); + + it('should trigger `toggleNeedRealAccountForCashierModal` if user does not have any real regulated account', () => { + (useIsRealAccountNeededForCashier as jest.Mock).mockReturnValueOnce(true); + render(, { wrapper }); + const cashierButton = screen.getByRole('button', { name: 'Cashier' }); + userEvent.click(cashierButton); + expect(mock_store.ui.toggleNeedRealAccountForCashierModal).toHaveBeenCalledTimes(1); + }); + + it('should navigate to `/cashier/deposit` if user has real account', () => { + render(, { wrapper }); + const cashierButton = screen.getByRole('button', { name: 'Cashier' }); + userEvent.click(cashierButton); + expect(history.location.pathname).toBe(routes.cashier_deposit); }); }); diff --git a/packages/core/src/App/Containers/Layout/header/__tests__/dtrader-header.spec.tsx b/packages/core/src/App/Containers/Layout/header/__tests__/dtrader-header.spec.tsx index 134e1c10dc1e..dab4e5c0895f 100644 --- a/packages/core/src/App/Containers/Layout/header/__tests__/dtrader-header.spec.tsx +++ b/packages/core/src/App/Containers/Layout/header/__tests__/dtrader-header.spec.tsx @@ -13,6 +13,7 @@ jest.mock('App/Components/Layout/Header/toggle-menu-drawer.jsx', () => ); jest.mock('../header-account-actions', () => jest.fn(() =>
Mocked Header Account Action
)); jest.mock('../traders-hub-home-button', () => jest.fn(() =>
Mocked Traders Home Button
)); +jest.mock('../deriv-short-logo', () => jest.fn(() =>
Deriv Short Logo
)); describe('DTraderHeader', () => { const mock_store = mockStore({ ui: { is_desktop: true, is_real_acc_signup_on: true } }); diff --git a/packages/core/src/App/Containers/Layout/header/__tests__/header.spec.tsx b/packages/core/src/App/Containers/Layout/header/__tests__/header.spec.tsx index 6a92d406e1ea..4490425da3e0 100644 --- a/packages/core/src/App/Containers/Layout/header/__tests__/header.spec.tsx +++ b/packages/core/src/App/Containers/Layout/header/__tests__/header.spec.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { useLocation } from 'react-router-dom'; import { StoreProvider, mockStore } from '@deriv/stores'; import { render, screen } from '@testing-library/react'; +import { routes } from '@deriv/shared'; import Header from '../header'; jest.mock('@deriv/hooks', () => ({ @@ -33,7 +34,7 @@ describe('Header', () => { it('should render the "TradersHubHeader" component if user is logged in and in traders hub route', async () => { (useLocation as jest.Mock).mockReturnValue({ - pathname: '/appstore/traders-hub', + pathname: routes.traders_hub, }); renderComponent(); expect(await screen.findByTestId('dt_traders_hub_header')).toBeInTheDocument(); @@ -42,7 +43,7 @@ describe('Header', () => { it('should render the "DTraderHeader" component if user is logged in and not in the traders hub route', async () => { (useLocation as jest.Mock).mockReturnValue({ - pathname: '/', + pathname: routes.trade, }); renderComponent(); expect(await screen.findByTestId('dt_dtrader_header')).toBeInTheDocument(); diff --git a/packages/core/src/App/Containers/Layout/header/__tests__/traders-hub-home-button.spec.tsx b/packages/core/src/App/Containers/Layout/header/__tests__/traders-hub-home-button.spec.tsx index b0cd400a98d2..c842c8b892d1 100644 --- a/packages/core/src/App/Containers/Layout/header/__tests__/traders-hub-home-button.spec.tsx +++ b/packages/core/src/App/Containers/Layout/header/__tests__/traders-hub-home-button.spec.tsx @@ -3,11 +3,13 @@ import { StoreProvider, mockStore } from '@deriv/stores'; import { render, screen } from '@testing-library/react'; import TradersHubHomeButton from '../traders-hub-home-button'; -jest.mock('react-router', () => ({ - ...jest.requireActual('react-router'), - useHistory: () => ({ history: {} }), - useLocation: () => ({ pathname: '/appstore/traders-hub' }), -})); +jest.mock('react-router', () => { + return { + ...jest.requireActual('react-router'), + useHistory: () => ({ history: {} }), + useLocation: () => ({ pathname: '/' }), + }; +}); describe('TradersHubHomeButton', () => { it("should display the text Trader's Hub in the header", () => { diff --git a/packages/core/src/App/Containers/Layout/header/default-header.tsx b/packages/core/src/App/Containers/Layout/header/default-header.tsx index 14c250c7c55f..e3740f47bc90 100644 --- a/packages/core/src/App/Containers/Layout/header/default-header.tsx +++ b/packages/core/src/App/Containers/Layout/header/default-header.tsx @@ -11,6 +11,7 @@ import ToggleMenuDrawer from 'App/Components/Layout/Header/toggle-menu-drawer.js import platform_config from 'App/Constants/platform-config'; import { useHistory } from 'react-router-dom'; import HeaderAccountActions from './header-account-actions'; +import DerivShortLogo from './deriv-short-logo'; const DefaultHeader = observer(() => { const { client, common, notifications, traders_hub, ui } = useStore(); @@ -78,23 +79,27 @@ const DefaultHeader = observer(() => { >
- {!is_mobile && ( - - )} - {is_mobile && ( + {is_mobile ? ( {header_extension && is_logged_in && (
{header_extension}
)} + +
+ ) : ( + + +
+ )} diff --git a/packages/core/src/App/Containers/Layout/header/default-mobile-links.tsx b/packages/core/src/App/Containers/Layout/header/default-mobile-links.tsx index dc3d7f83868f..7a005ebcab94 100644 --- a/packages/core/src/App/Containers/Layout/header/default-mobile-links.tsx +++ b/packages/core/src/App/Containers/Layout/header/default-mobile-links.tsx @@ -1,20 +1,37 @@ import React from 'react'; - +import { useHistory } from 'react-router-dom'; import { Button, Icon } from '@deriv/components'; +import { useIsRealAccountNeededForCashier } from '@deriv/hooks'; import { routes } from '@deriv/shared'; import { useStore } from '@deriv/stores'; import { Localize } from '@deriv/translations'; - import { BinaryLink } from 'App/Components/Routes'; import ShowNotifications from './show-notifications'; -type TDefaultMobileLinks = { - handleClickCashier: () => void; -}; +const DefaultMobileLinks = React.memo(() => { + const { client, ui } = useStore(); + const { has_any_real_account, has_wallet, is_virtual } = client; + const { toggleNeedRealAccountForCashierModal, toggleReadyToDepositModal } = ui; + + const history = useHistory(); + + const real_account_needed_for_cashier = useIsRealAccountNeededForCashier(); + + const toggleModal = () => { + if (!has_any_real_account) { + toggleReadyToDepositModal(); + } else if (history.location.pathname === routes.traders_hub) { + toggleNeedRealAccountForCashierModal(); + } + }; -const DefaultMobileLinks = React.memo(({ handleClickCashier }: TDefaultMobileLinks) => { - const { client } = useStore(); - const { has_wallet } = client; + const handleClickCashier = () => { + if ((!has_any_real_account && is_virtual) || real_account_needed_for_cashier) { + toggleModal(); + } else { + history.push(routes.cashier_deposit); + } + }; return ( diff --git a/packages/core/src/App/Containers/Layout/header/deriv-short-logo.tsx b/packages/core/src/App/Containers/Layout/header/deriv-short-logo.tsx new file mode 100644 index 000000000000..b636ac96b500 --- /dev/null +++ b/packages/core/src/App/Containers/Layout/header/deriv-short-logo.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { StaticUrl } from '@deriv/components'; +import DerivBrandShortLogo from 'Assets/SvgComponents/header/deriv-logo-short.svg'; + +const DerivShortLogo = () => { + return ( +
+ + + +
+ ); +}; + +export default DerivShortLogo; diff --git a/packages/core/src/App/Containers/Layout/header/dtrader-header.tsx b/packages/core/src/App/Containers/Layout/header/dtrader-header.tsx index 48ca7df32ff7..ea9172b6c42a 100644 --- a/packages/core/src/App/Containers/Layout/header/dtrader-header.tsx +++ b/packages/core/src/App/Containers/Layout/header/dtrader-header.tsx @@ -12,6 +12,7 @@ import ToggleMenuDrawer from 'App/Components/Layout/Header/toggle-menu-drawer.js import { AccountsInfoLoader } from 'App/Components/Layout/Header/Components/Preloader'; import TradersHubHomeButton from './traders-hub-home-button'; import HeaderAccountActions from './header-account-actions'; +import DerivShortLogo from './deriv-short-logo'; const DTraderHeader = observer(() => { const { client, common, ui, notifications, traders_hub } = useStore(); @@ -86,23 +87,26 @@ const DTraderHeader = observer(() => { >
- {!is_mobile && ( - - )} - {is_mobile && ( + {is_mobile ? ( {header_extension && is_logged_in && (
{header_extension}
)}
+ ) : ( + + +
+ + + )} - {!is_mobile && }
diff --git a/packages/core/src/App/Containers/Layout/header/header.tsx b/packages/core/src/App/Containers/Layout/header/header.tsx index d64912d999b3..67acc380ff80 100644 --- a/packages/core/src/App/Containers/Layout/header/header.tsx +++ b/packages/core/src/App/Containers/Layout/header/header.tsx @@ -50,7 +50,7 @@ const Header = observer(() => { const { pathname } = useLocation(); const { is_mobile } = useDevice(); - const is_wallets_cashier_route = pathname.includes(routes.wallets_cashier); + const is_wallets_cashier_route = pathname.includes(routes.wallets); const traders_hub_routes = [ @@ -58,7 +58,6 @@ const Header = observer(() => { routes.traders_hub_v2, routes.account, routes.cashier, - routes.wallets, routes.wallets_compare_accounts, routes.compare_accounts, routes.compare_cfds, diff --git a/packages/core/src/App/Containers/Layout/header/traders-hub-header-wallets.tsx b/packages/core/src/App/Containers/Layout/header/traders-hub-header-wallets.tsx index 856621f73975..9df90855b50f 100644 --- a/packages/core/src/App/Containers/Layout/header/traders-hub-header-wallets.tsx +++ b/packages/core/src/App/Containers/Layout/header/traders-hub-header-wallets.tsx @@ -1,9 +1,7 @@ import * as React from 'react'; import classNames from 'classnames'; -import { useHistory, useLocation } from 'react-router-dom'; import { DesktopWrapper, Icon, MobileWrapper, Popover, StaticUrl } from '@deriv/components'; -import { useIsRealAccountNeededForCashier } from '@deriv/hooks'; -import { routes, platforms, formatMoney } from '@deriv/shared'; +import { routes, platforms } from '@deriv/shared'; import { observer, useStore } from '@deriv/stores'; import { Localize } from '@deriv/translations'; import { MenuLinks } from 'App/Components/Layout/Header'; @@ -12,7 +10,6 @@ import ToggleMenuDrawer from 'App/Components/Layout/Header/toggle-menu-drawer.js import { BinaryLink } from 'App/Components/Routes'; import DerivBrandShortLogo from 'Assets/SvgComponents/header/deriv-logo-short.svg'; import RealAccountSignup from 'App/Containers/RealAccountSignup'; -import AccountInfo from 'App/Components/Layout/Header/account-info'; import SetAccountCurrencyModal from 'App/Containers/SetAccountCurrencyModal'; import CurrencySelectionModal from '../../CurrencySelectionModal'; import DefaultMobileLinks from './default-mobile-links'; @@ -25,25 +22,10 @@ type TPlatforms = typeof platforms; const TradersHubHeaderWallets = observer(() => { const { client, common, traders_hub, ui } = useStore(); - const { account_type, balance, currency, has_any_real_account, is_eu, is_logged_in, is_mt5_allowed, is_virtual } = - client; + const { is_logged_in, is_mt5_allowed } = client; const { platform } = common; const { modal_data } = traders_hub; - const { - header_extension, - is_accounts_switcher_on, - is_app_disabled, - is_route_modal_on, - account_switcher_disabled_message, - toggleAccountsDialog, - toggleNeedRealAccountForCashierModal, - toggleReadyToDepositModal, - } = ui; - const history = useHistory(); - const { pathname } = useLocation(); - const cashier_routes = pathname.startsWith(routes.cashier); - const real_account_needed_for_cashier = useIsRealAccountNeededForCashier(); - const account_balance = formatMoney(currency, balance ?? '', true); + const { header_extension, is_app_disabled, is_route_modal_on } = ui; const filterPlatformsForClients = (payload: TPlatformConfig) => payload.filter(config => { @@ -53,22 +35,6 @@ const TradersHubHeaderWallets = observer(() => { return true; }); - const toggleModal = () => { - if (!has_any_real_account) { - toggleReadyToDepositModal(); - } else if (window.location.pathname === routes.traders_hub) { - toggleNeedRealAccountForCashierModal(); - } - }; - - const handleClickCashier = () => { - if ((!has_any_real_account && is_virtual) || real_account_needed_for_cashier) { - toggleModal(); - } else { - history.push(routes.cashier_deposit); - } - }; - return (
{ {header_extension && is_logged_in &&
{header_extension}
} -
+
@@ -124,21 +86,6 @@ const TradersHubHeaderWallets = observer(() => { - {cashier_routes && ( -
- -
- )}
@@ -146,28 +93,7 @@ const TradersHubHeaderWallets = observer(() => {
- {cashier_routes ? ( - -
- -
-
- -
-
- ) : ( - - )} +
diff --git a/packages/core/src/App/Containers/Layout/header/traders-hub-header.tsx b/packages/core/src/App/Containers/Layout/header/traders-hub-header.tsx index b2c4de85b1a5..419199fa8f7e 100644 --- a/packages/core/src/App/Containers/Layout/header/traders-hub-header.tsx +++ b/packages/core/src/App/Containers/Layout/header/traders-hub-header.tsx @@ -1,8 +1,7 @@ import * as React from 'react'; import classNames from 'classnames'; -import { useHistory, useLocation } from 'react-router-dom'; -import { Icon, Popover, StaticUrl, Loading } from '@deriv/components'; -import { useIsRealAccountNeededForCashier } from '@deriv/hooks'; +import { useLocation } from 'react-router-dom'; +import { Icon, Popover, Loading } from '@deriv/components'; import { routes, platforms, formatMoney, makeLazyLoader, moduleLoader } from '@deriv/shared'; import { observer, useStore } from '@deriv/stores'; import { Localize } from '@deriv/translations'; @@ -10,10 +9,10 @@ import { MenuLinks } from 'App/Components/Layout/Header'; import platform_config from 'App/Constants/platform-config'; import ToggleMenuDrawer from 'App/Components/Layout/Header/toggle-menu-drawer.jsx'; import { BinaryLink } from 'App/Components/Routes'; -import DerivBrandLogo from 'Assets/SvgComponents/header/deriv-rebranding-logo.svg'; import DefaultMobileLinks from './default-mobile-links'; import ShowNotifications from './show-notifications'; import TradersHubHomeButton from './traders-hub-home-button'; +import DerivShortLogo from './deriv-short-logo'; type TPlatformConfig = typeof platform_config; type TPlatforms = typeof platforms; @@ -55,8 +54,7 @@ const CurrencySelectionModal = makeLazyLoader( const TradersHubHeader = observer(() => { const { client, common, traders_hub, ui } = useStore(); - const { account_type, balance, currency, has_any_real_account, is_eu, is_logged_in, is_mt5_allowed, is_virtual } = - client; + const { account_type, balance, currency, is_eu, is_logged_in, is_mt5_allowed, is_virtual } = client; const { platform } = common; const { modal_data } = traders_hub; const { @@ -67,16 +65,12 @@ const TradersHubHeader = observer(() => { is_route_modal_on, account_switcher_disabled_message, toggleAccountsDialog, - toggleNeedRealAccountForCashierModal, - toggleReadyToDepositModal, is_real_acc_signup_on, is_set_currency_modal_visible, } = ui; - const history = useHistory(); const { pathname } = useLocation(); const cashier_routes = pathname.startsWith(routes.cashier); - const real_account_needed_for_cashier = useIsRealAccountNeededForCashier(); const account_balance = formatMoney(currency, balance ?? '', true); const filterPlatformsForClients = (payload: TPlatformConfig) => @@ -87,22 +81,6 @@ const TradersHubHeader = observer(() => { return true; }); - const toggleModal = () => { - if (!has_any_real_account) { - toggleReadyToDepositModal(); - } else if (window.location.pathname === routes.traders_hub) { - toggleNeedRealAccountForCashierModal(); - } - }; - - const handleClickCashier = () => { - if ((!has_any_real_account && is_virtual) || real_account_needed_for_cashier) { - toggleModal(); - } else { - history.push(routes.cashier_deposit); - } - }; - return (
{ 'traders-hub-header__logo-wrapper--cashier': cashier_routes, })} > - - - +
{!is_mobile && ( @@ -197,7 +173,7 @@ const TradersHubHeader = observer(() => {
) : ( - + )}
diff --git a/packages/core/src/App/Containers/Layout/header/traders-hub-home-button.tsx b/packages/core/src/App/Containers/Layout/header/traders-hub-home-button.tsx index 474beae0d20e..d8b830f6ff87 100644 --- a/packages/core/src/App/Containers/Layout/header/traders-hub-home-button.tsx +++ b/packages/core/src/App/Containers/Layout/header/traders-hub-home-button.tsx @@ -26,9 +26,7 @@ const TradersHubHomeButton = observer(() => { } const redirectRoutes = () => { - if (has_wallet) { - return routes.wallets; - } else if (is_next_tradershub_enabled) { + if (is_next_tradershub_enabled) { return routes.traders_hub_v2; } return routes.traders_hub; @@ -39,9 +37,7 @@ const TradersHubHomeButton = observer(() => { data-testid='dt_traders_hub_home_button' className={classNames('traders-hub-header__tradershub', { 'traders-hub-header__tradershub--active': - pathname === routes.traders_hub || - pathname === routes.traders_hub_v2 || - pathname === routes.wallets, + pathname === routes.traders_hub || pathname === routes.traders_hub_v2, })} onClick={() => history.push(redirectRoutes())} > diff --git a/packages/core/src/App/Containers/Modals/wallets-upgrade-logout-modal/wallets-upgrade-logout-modal.tsx b/packages/core/src/App/Containers/Modals/wallets-upgrade-logout-modal/wallets-upgrade-logout-modal.tsx index f1d7cf86bf6a..838d72539de6 100644 --- a/packages/core/src/App/Containers/Modals/wallets-upgrade-logout-modal/wallets-upgrade-logout-modal.tsx +++ b/packages/core/src/App/Containers/Modals/wallets-upgrade-logout-modal/wallets-upgrade-logout-modal.tsx @@ -22,7 +22,7 @@ const WalletsUpgradeLogoutModal = observer(() => { secure: true, }); logout().then(() => { - window.location.href = routes.wallets; + window.location.href = routes.traders_hub; redirectToLogin(false, getLanguage()); }); }} diff --git a/packages/core/src/App/Containers/NotificationsDialog/notification-list-wrapper.tsx b/packages/core/src/App/Containers/NotificationsDialog/notification-list-wrapper.tsx index 63b25501e89b..216acd8b644c 100644 --- a/packages/core/src/App/Containers/NotificationsDialog/notification-list-wrapper.tsx +++ b/packages/core/src/App/Containers/NotificationsDialog/notification-list-wrapper.tsx @@ -17,14 +17,13 @@ const NotificationListWrapperForwardRef = React.forwardRef( const { is_mobile } = ui; const traders_hub = window.location.pathname === routes.traders_hub; - const wallets_path = window.location.pathname.startsWith(routes.wallets); return (
diff --git a/packages/core/src/App/Containers/Redirect/redirect.jsx b/packages/core/src/App/Containers/Redirect/redirect.jsx index e5b6fe4dcb0d..c44ccef4b683 100644 --- a/packages/core/src/App/Containers/Redirect/redirect.jsx +++ b/packages/core/src/App/Containers/Redirect/redirect.jsx @@ -31,7 +31,7 @@ const Redirect = observer(() => { const ext_platform_url = url_params.get('ext_platform_url'); const redirectToExternalPlatform = url => { - history.push(`${routes.root}?ext_platform_url=${url}`); + history.push(`${routes.traders_hub}?ext_platform_url=${url}`); redirected_to_route = true; }; setVerificationCode(code_param, action_param); @@ -94,28 +94,19 @@ const Redirect = observer(() => { if (redirect_to) { let pathname = ''; let hash = ''; - const main_screen_route = has_wallet ? routes.wallets : routes.traders_hub; switch (redirect_to) { case '1': - pathname = routes.traders_hub; - break; - case '10': - pathname = main_screen_route; - hash = 'real'; - break; - case '11': - pathname = main_screen_route; - hash = 'demo'; - break; case '2': pathname = routes.traders_hub; break; + case '10': case '20': - pathname = main_screen_route; + pathname = routes.traders_hub; hash = 'real'; break; + case '11': case '21': - pathname = main_screen_route; + pathname = routes.traders_hub; hash = 'demo'; break; case '3': @@ -231,7 +222,7 @@ const Redirect = observer(() => { const is_demo = localStorage.getItem('cfd_reset_password_intent')?.includes('demo'); if (has_wallet) { history.push({ - pathname: routes.wallets, + pathname: routes.traders_hub, search: url_query_string, }); } else { @@ -253,9 +244,9 @@ const Redirect = observer(() => { break; } - if (!redirected_to_route && history.location.pathname !== routes.root) { + if (!redirected_to_route && history.location.pathname !== routes.traders_hub) { history.push({ - pathname: routes.root, + pathname: routes.traders_hub, search: url_query_string, }); } diff --git a/packages/core/src/App/Containers/RootComponent/index.js b/packages/core/src/App/Containers/RootComponent/index.js new file mode 100644 index 000000000000..d807a1fc3997 --- /dev/null +++ b/packages/core/src/App/Containers/RootComponent/index.js @@ -0,0 +1,3 @@ +import RootComponent from './root-component.jsx'; + +export default RootComponent; diff --git a/packages/core/src/App/Containers/RootComponent/root-component.jsx b/packages/core/src/App/Containers/RootComponent/root-component.jsx new file mode 100644 index 000000000000..8a46dac2ed87 --- /dev/null +++ b/packages/core/src/App/Containers/RootComponent/root-component.jsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { moduleLoader } from '@deriv/shared'; +import { observer, useStore } from '@deriv/stores'; + +const AppStore = React.lazy(() => + moduleLoader(() => { + // eslint-disable-next-line import/no-unresolved + return import(/* webpackChunkName: "appstore" */ '@deriv/appstore'); + }) +); + +const Wallets = React.lazy(() => + moduleLoader(() => { + // eslint-disable-next-line import/no-unresolved + return import(/* webpackChunkName: "wallets" */ '@deriv/wallets'); + }) +); + +const RootComponent = observer(props => { + const { client } = useStore(); + const { has_wallet } = client; + + return has_wallet ? : ; +}); + +export default RootComponent; diff --git a/packages/core/src/Stores/client-store.js b/packages/core/src/Stores/client-store.js index abac8e76ec03..e99f39315e0a 100644 --- a/packages/core/src/Stores/client-store.js +++ b/packages/core/src/Stores/client-store.js @@ -1548,6 +1548,11 @@ export default class ClientStore extends BaseStore { '_filteredParams', ]; + // redirect to the DTrader of there is needed query params + if (!window.location.pathname.endsWith(routes.trade) && /chart_type|interval|symbol|trade_type/.test(search)) { + window.history.replaceState({}, document.title, routes.trade + search); + } + const authorize_response = await this.setUserLogin(login_new_user); if (action_param === 'signup') { @@ -1947,7 +1952,7 @@ export default class ClientStore extends BaseStore { } //temporary workaround to sync this.loginid with selected wallet loginid - if (window.location.pathname.includes(routes.wallets_cashier)) { + if (window.location.pathname.includes(routes.wallets)) { this.resetLocalStorageValues(localStorage.getItem('active_loginid') ?? this.loginid); return; } @@ -2192,10 +2197,10 @@ export default class ClientStore extends BaseStore { const redirect_url = sessionStorage.getItem('redirect_url'); - const target_url = this.has_wallet ? routes.wallets : routes.traders_hub; + const target_url = routes.traders_hub; if ( - (redirect_url?.endsWith('/') || + (redirect_url?.endsWith(routes.trade) || redirect_url?.endsWith(routes.bot) || /chart_type|interval|symbol|trade_type/.test(redirect_url)) && (isTestLink() || isProduction() || isLocal() || isStaging() || isTestDerivApp()) diff --git a/packages/core/src/sass/app/_common/layout/header.scss b/packages/core/src/sass/app/_common/layout/header.scss index 22fd79bbc7b1..6ad9064e63df 100644 --- a/packages/core/src/sass/app/_common/layout/header.scss +++ b/packages/core/src/sass/app/_common/layout/header.scss @@ -381,6 +381,17 @@ height: #{$MOBILE_HEADER_HEIGHT - 1px}; } } + + &__divider { + width: 0.1rem; + height: 3rem; + margin-left: 0.8rem; + background: var(--general-section-2); + + @include mobile { + height: 2.4rem; + } + } } .header-v2 { diff --git a/packages/reports/src/Components/Elements/Modals/ServicesErrorModal/__tests__/insufficient-balance-modal.spec.tsx b/packages/reports/src/Components/Elements/Modals/ServicesErrorModal/__tests__/insufficient-balance-modal.spec.tsx index fe4cdde3c975..9db5a1c8c48b 100644 --- a/packages/reports/src/Components/Elements/Modals/ServicesErrorModal/__tests__/insufficient-balance-modal.spec.tsx +++ b/packages/reports/src/Components/Elements/Modals/ServicesErrorModal/__tests__/insufficient-balance-modal.spec.tsx @@ -3,6 +3,7 @@ import { screen, render } from '@testing-library/react'; import InsufficientBalanceModal from '../insufficient-balance-modal'; import { createBrowserHistory } from 'history'; import { Router } from 'react-router-dom'; +import { StoreProvider, mockStore } from '@deriv/stores'; import { routes } from '@deriv/shared'; import userEvent from '@testing-library/user-event'; @@ -19,16 +20,6 @@ type TModal = React.FC<{ }>; }; -jest.mock('@deriv/stores', () => ({ - ...jest.requireActual('@deriv/stores'), - observer: jest.fn(x => x), - useStore: jest.fn(() => ({ - ui: { - is_mobile: false, - }, - })), -})); - jest.mock('@deriv/components', () => { const original_module = jest.requireActual('@deriv/components'); const Modal: TModal = jest.fn(({ children, is_open, title }) => { @@ -58,35 +49,51 @@ describe('', () => { message: 'test', toggleModal: jest.fn(), }; + let mock_store: ReturnType; + + beforeEach(() => { + mock_store = mockStore({ + client: { has_wallet: false }, + ui: { + is_mobile: false, + }, + }); + }); const history = createBrowserHistory(); - const renderWithRouter = (component: React.ReactElement) => { - return render({component}); + + const wrapper = ({ children }: { children: React.ReactNode }) => { + return ( + + {children} + + ); }; it('modal title, and modal description should be rendered', () => { - renderWithRouter(); + render(, { wrapper }); expect(screen.getByText(/insufficient balance/i)).toBeInTheDocument(); expect(screen.getByText(/test/i)).toBeInTheDocument(); }); it('button text should be OK if is_virtual is true and toggleModal should be called if user clicks on the button', () => { - renderWithRouter(); + render(, { wrapper }); const button = screen.getByText(/ok/i); expect(button).toBeInTheDocument(); userEvent.click(button); expect(mocked_props.toggleModal).toHaveBeenCalled(); }); - it('button text should be "Deposit now" if is_virtual is false and should navigate to bla bla if you click on the button', () => { + it('button text should be "Deposit now" if is_virtual is false and should navigate to wallets overlay deposit tab if client has CRW account and click on the button', () => { mocked_props.is_virtual = false; - renderWithRouter(); + mock_store.client.has_wallet = true; + render(, { wrapper }); const button = screen.getByText(/deposit now/i); expect(button).toBeInTheDocument(); userEvent.click(button); - expect(history.location.pathname).toBe(routes.cashier_deposit); + expect(history.location.pathname).toBe(routes.wallets_deposit); }); it('should return null when is_visible is false', () => { mocked_props.is_visible = false; - const { container } = renderWithRouter(); + const { container } = render(, { wrapper }); expect(container).toBeEmptyDOMElement(); }); }); diff --git a/packages/reports/src/Components/Elements/Modals/ServicesErrorModal/insufficient-balance-modal.tsx b/packages/reports/src/Components/Elements/Modals/ServicesErrorModal/insufficient-balance-modal.tsx index 349ec805ad09..613a80d9c015 100644 --- a/packages/reports/src/Components/Elements/Modals/ServicesErrorModal/insufficient-balance-modal.tsx +++ b/packages/reports/src/Components/Elements/Modals/ServicesErrorModal/insufficient-balance-modal.tsx @@ -16,7 +16,9 @@ const InsufficientBalanceModal = observer( ({ history, is_virtual, is_visible, message, toggleModal }: TInsufficientBalanceModal) => { const { ui: { is_mobile }, + client, } = useStore(); + const { has_wallet } = client; return ( { if (!is_virtual) { - history?.push?.(routes.cashier_deposit); + history?.push?.(has_wallet ? routes.wallets_deposit : routes.cashier_deposit); } else { toggleModal(); } diff --git a/packages/shared/src/styles/constants.scss b/packages/shared/src/styles/constants.scss index 52ca4e6a001b..ec3511f71ada 100644 --- a/packages/shared/src/styles/constants.scss +++ b/packages/shared/src/styles/constants.scss @@ -58,6 +58,7 @@ $color-grey-11: #fafafa; $color-grey-12: #f5f7fa; $color-grey-13: #2e2e2e; $color-grey-14: #e2e5e7; +$color-grey-15: #f3f4f5; $color-orange: #ff6444; $color-purple: #722fe4; $color-red: #ff444f; diff --git a/packages/shared/src/styles/themes.scss b/packages/shared/src/styles/themes.scss index dc70412f0f67..a9ca24b48b35 100644 --- a/packages/shared/src/styles/themes.scss +++ b/packages/shared/src/styles/themes.scss @@ -9,6 +9,7 @@ --text-size-xsm: 1.8rem; --text-size-sm: 2rem; --text-size-m: 2.4rem; + --text-size-xm: 2.8rem; --text-size-l: 3.2rem; --text-size-xl: 4.8rem; --text-size-xxl: 6.4rem; @@ -120,6 +121,7 @@ --button-toggle-primary: #{$color-blue-3}; --button-toggle-secondary: #{$color-grey-5}; --button-toggle-alternate: #{$color-white}; + --button-get-started-bg: #{$color-black-7}; // Overlay --overlay-outside-dialog: #{$alpha-color-black-1}; --overlay-inside-dialog: #{$alpha-color-white-1}; @@ -202,6 +204,7 @@ --badge-green: #{$color-green-3}; //TradersHub Banner --traders-hub-banner-border-color: #{$color-grey-4}; + --traders-hub-logged-out-banner-bg-color: #{$color-grey-15}; // wallets --wallets-banner-ready-bg-color: #{$ready-banner-bg-color}; --wallets-banner-ready-tick-bg-color: #{$ready-banner-tick-bg-color}; @@ -262,6 +265,7 @@ --purchase-section-2: #{$color-red-3}; --purchase-disabled-main: #{$color-black-4}; --purchase-disabled-section: #{$color-black}; + // Buttons --button-primary-default: var(--brand-red-coral); --button-secondary-default: #{$color-grey-7}; --button-tertiary-default: transparent; @@ -273,6 +277,7 @@ --button-toggle-primary: #{$color-blue-3}; --button-toggle-secondary: #{$color-black-8}; --button-toggle-alternate: #{$color-black-8}; + --button-get-started-bg: #{$color-white}; // Overlay --overlay-outside-dialog: #{$alpha-color-black-1}; --overlay-inside-dialog: #{$alpha-color-black-2}; @@ -338,6 +343,7 @@ --badge-green: #{$color-green-3}; //TradersHub Banner --traders-hub-banner-border-color: #{$color-black-5}; + --traders-hub-logged-out-banner-bg-color: #{$color-black-5}; // wallets --wallets-banner-ready-bg-color: #{$ready-banner-bg-color}; --wallets-banner-ready-tick-bg-color: #{$ready-banner-tick-bg-color}; diff --git a/packages/shared/src/utils/contract/__tests__/trade-url-params-config.spec.ts b/packages/shared/src/utils/contract/__tests__/trade-url-params-config.spec.ts index 870c98345cea..2496d9ea8e42 100644 --- a/packages/shared/src/utils/contract/__tests__/trade-url-params-config.spec.ts +++ b/packages/shared/src/utils/contract/__tests__/trade-url-params-config.spec.ts @@ -39,7 +39,10 @@ describe('getTradeURLParams', () => { Object.defineProperty(window, 'location', { configurable: true, enumerable: true, - value: new URL('https://localhost:8443/'), + value: { + hostname: 'https://localhost:8443/', + pathname: routes.trade, + }, }); }); @@ -128,28 +131,40 @@ describe('setTradeURLParams', () => { setTradeURLParams({ granularity: 0, }); - expect(spyHistoryReplaceState).toBeCalledWith({}, document.title, `/?interval=${oneTickInterval}`); + expect(spyHistoryReplaceState).toBeCalledWith( + {}, + document.title, + `${routes.trade}?interval=${oneTickInterval}` + ); }); it('should set chart_type query param into URL based on the received chart_type value', () => { const spyHistoryReplaceState = jest.spyOn(window.history, 'replaceState'); setTradeURLParams({ chartType: areaChartType.value, }); - expect(spyHistoryReplaceState).toBeCalledWith({}, document.title, `/?chart_type=${areaChartType.text}`); + expect(spyHistoryReplaceState).toBeCalledWith( + {}, + document.title, + `${routes.trade}?chart_type=${areaChartType.text}` + ); }); it('should set symbol query param into URL based on the received symbol value', () => { const spyHistoryReplaceState = jest.spyOn(window.history, 'replaceState'); setTradeURLParams({ symbol, }); - expect(spyHistoryReplaceState).toBeCalledWith({}, document.title, `/?symbol=${symbol}`); + expect(spyHistoryReplaceState).toBeCalledWith({}, document.title, `${routes.trade}?symbol=${symbol}`); }); it('should set trade_type query param into URL based on the received contract_type value', () => { const spyHistoryReplaceState = jest.spyOn(window.history, 'replaceState'); setTradeURLParams({ contractType: TRADE_TYPES.ACCUMULATOR, }); - expect(spyHistoryReplaceState).toBeCalledWith({}, document.title, `/?trade_type=${TRADE_TYPES.ACCUMULATOR}`); + expect(spyHistoryReplaceState).toBeCalledWith( + {}, + document.title, + `${routes.trade}?trade_type=${TRADE_TYPES.ACCUMULATOR}` + ); }); it('should not set any query params into URL when called with empty object', () => { const spyHistoryReplaceState = jest.spyOn(window.history, 'replaceState'); diff --git a/packages/shared/src/utils/routes/routes.ts b/packages/shared/src/utils/routes/routes.ts index 789eb267a371..105e70633ad1 100644 --- a/packages/shared/src/utils/routes/routes.ts +++ b/packages/shared/src/utils/routes/routes.ts @@ -1,7 +1,23 @@ import { getUrlSmartTrader, getUrlBinaryBot } from '../url/helpers'; export const routes = { + reset_password: '/', error404: '/404', + index: '/index', + redirect: '/redirect', + endpoint: '/endpoint', + complaints_policy: '/complaints-policy', + contract: '/contract/:contract_id', + + // platforms + mt5: '/mt5', + dxtrade: '/derivx', + bot: '/bot', + trade: '/dtrader', + smarttrader: getUrlSmartTrader(), + binarybot: getUrlBinaryBot(), + + // account account: '/account', trading_assessment: '/account/trading-assessment', languages: '/account/languages', @@ -22,29 +38,26 @@ export const routes = { login_history: '/account/login-history', two_factor_authentication: '/account/two-factor-authentication', self_exclusion: '/account/self-exclusion', + + // settings + settings: '/settings', account_password: '/settings/account_password', apps: '/settings/apps', cashier_password: '/settings/cashier_password', - contract: '/contract/:contract_id', exclusion: '/settings/exclusion', financial: '/settings/financial', history: '/settings/history', - index: '/index', limits: '/settings/limits', - mt5: '/mt5', - dxtrade: '/derivx', + token: '/settings/token', personal: '/settings/personal', + + // reports + reports: '/reports', positions: '/reports/positions', profit: '/reports/profit', - reports: '/reports', - root: '/', - reset_password: '/', - redirect: '/redirect', - settings: '/settings', statement: '/reports/statement', - token: '/settings/token', - trade: '/', - bot: '/bot', + + // cashier cashier: '/cashier', cashier_deposit: '/cashier/deposit', cashier_withdrawal: '/cashier/withdrawal', @@ -55,6 +68,7 @@ export const routes = { cashier_onramp: '/cashier/on-ramp', cashier_p2p: '/cashier/p2p', cashier_p2p_v2: '/cashier/p2p-v2', + cashier_pa_transfer: '/cashier/payment-agent-transfer', // P2P p2p_verification: '/cashier/p2p/verification', @@ -65,26 +79,21 @@ export const routes = { p2p_advertiser_page: '/cashier/p2p/advertiser', p2p_v2_inner: '/cashier/p2p-v2/inner', - cashier_pa_transfer: '/cashier/payment-agent-transfer', - smarttrader: getUrlSmartTrader(), - binarybot: getUrlBinaryBot(), - endpoint: '/endpoint', - complaints_policy: '/complaints-policy', - // Appstore - appstore: '/appstore', - traders_hub: '/appstore/traders-hub', - onboarding: '/appstore/onboarding', - compare_cfds: '/appstore/cfd-compare-acccounts', + old_traders_hub: '/appstore/traders-hub', + traders_hub: '/', + onboarding: '/onboarding', + compare_cfds: '/cfd-compare-acccounts', // Wallets - wallets: '/wallets', - wallets_cashier: '/wallets/cashier', - wallets_deposit: '/wallets/cashier/deposit', - wallets_withdrawal: '/wallets/cashier/withdraw', - wallets_transfer: '/wallets/cashier/transfer', - wallets_transactions: '/wallets/cashier/transactions', - wallets_compare_accounts: '/wallets/compare-accounts', + wallets: '/wallet', + wallets_deposit: '/wallet/deposit', + wallets_withdrawal: '/wallet/withdrawal', + wallets_transfer: '/wallet/account-transfer', + wallets_transactions: '/wallet/transactions', + wallets_compare_accounts: '/compare-accounts', + wallets_on_ramp: '/wallet/on-ramp', + wallets_reset_balance: '/wallet/reset-balance', // Traders Hub traders_hub_v2: '/traders-hub', diff --git a/packages/stores/types.ts b/packages/stores/types.ts index 6063cae32edd..f918787c7116 100644 --- a/packages/stores/types.ts +++ b/packages/stores/types.ts @@ -67,6 +67,7 @@ type TRoutes = | '/reports/profit' | '/reports' | '/' + | '/dtrader' | '/redirect' | '/settings' | '/reports/statement' @@ -88,7 +89,14 @@ type TRoutes = | '/appstore' | '/appstore/traders-hub' | '/appstore/onboarding' - | '/wallets'; + | '/wallet' + | '/wallet/deposit' + | '/wallet/withdrawal' + | '/wallet/account-transfer' + | '/wallet/reset-balance' + | '/wallet/transactions' + | '/wallet/on-ramp' + | '/compare-accounts'; type TPopulateSettingsExtensionsMenuItem = { icon: string; diff --git a/packages/trader/src/App/Components/Elements/Modals/ServicesErrorModal/__tests__/insufficient-balance-modal.spec.tsx b/packages/trader/src/App/Components/Elements/Modals/ServicesErrorModal/__tests__/insufficient-balance-modal.spec.tsx index fe4cdde3c975..22091acaddd1 100644 --- a/packages/trader/src/App/Components/Elements/Modals/ServicesErrorModal/__tests__/insufficient-balance-modal.spec.tsx +++ b/packages/trader/src/App/Components/Elements/Modals/ServicesErrorModal/__tests__/insufficient-balance-modal.spec.tsx @@ -3,6 +3,7 @@ import { screen, render } from '@testing-library/react'; import InsufficientBalanceModal from '../insufficient-balance-modal'; import { createBrowserHistory } from 'history'; import { Router } from 'react-router-dom'; +import { StoreProvider, mockStore } from '@deriv/stores'; import { routes } from '@deriv/shared'; import userEvent from '@testing-library/user-event'; @@ -19,16 +20,6 @@ type TModal = React.FC<{ }>; }; -jest.mock('@deriv/stores', () => ({ - ...jest.requireActual('@deriv/stores'), - observer: jest.fn(x => x), - useStore: jest.fn(() => ({ - ui: { - is_mobile: false, - }, - })), -})); - jest.mock('@deriv/components', () => { const original_module = jest.requireActual('@deriv/components'); const Modal: TModal = jest.fn(({ children, is_open, title }) => { @@ -58,35 +49,59 @@ describe('', () => { message: 'test', toggleModal: jest.fn(), }; + let mock_store: ReturnType; + + beforeEach(() => { + mock_store = mockStore({ + client: { has_wallet: false }, + ui: { + is_mobile: false, + }, + }); + }); const history = createBrowserHistory(); - const renderWithRouter = (component: React.ReactElement) => { - return render({component}); + + const wrapper = ({ children }: { children: React.ReactNode }) => { + return ( + + {children} + + ); }; it('modal title, and modal description should be rendered', () => { - renderWithRouter(); + render(, { wrapper }); expect(screen.getByText(/insufficient balance/i)).toBeInTheDocument(); expect(screen.getByText(/test/i)).toBeInTheDocument(); }); it('button text should be OK if is_virtual is true and toggleModal should be called if user clicks on the button', () => { - renderWithRouter(); + render(, { wrapper }); const button = screen.getByText(/ok/i); expect(button).toBeInTheDocument(); userEvent.click(button); expect(mocked_props.toggleModal).toHaveBeenCalled(); }); - it('button text should be "Deposit now" if is_virtual is false and should navigate to bla bla if you click on the button', () => { + it('button text should be "Deposit now" if is_virtual is false and should navigate to cashier deposit page if client has CR account and click on the button', () => { mocked_props.is_virtual = false; - renderWithRouter(); + render(, { wrapper }); const button = screen.getByText(/deposit now/i); expect(button).toBeInTheDocument(); userEvent.click(button); expect(history.location.pathname).toBe(routes.cashier_deposit); }); + it('button text should be "Deposit now" if is_virtual is false and should navigate to wallets overlay deposit tab if client has CRW account and click on the button', () => { + mocked_props.is_virtual = false; + mock_store.client.has_wallet = true; + render(, { wrapper }); + const button = screen.getByText(/deposit now/i); + expect(button).toBeInTheDocument(); + userEvent.click(button); + expect(history.location.pathname).toBe(routes.wallets_deposit); + }); it('should return null when is_visible is false', () => { mocked_props.is_visible = false; - const { container } = renderWithRouter(); + const { container } = render(, { wrapper }); expect(container).toBeEmptyDOMElement(); }); }); diff --git a/packages/trader/src/App/Components/Elements/Modals/ServicesErrorModal/insufficient-balance-modal.tsx b/packages/trader/src/App/Components/Elements/Modals/ServicesErrorModal/insufficient-balance-modal.tsx index f4fe48a7c416..9d76b3bee56b 100644 --- a/packages/trader/src/App/Components/Elements/Modals/ServicesErrorModal/insufficient-balance-modal.tsx +++ b/packages/trader/src/App/Components/Elements/Modals/ServicesErrorModal/insufficient-balance-modal.tsx @@ -16,7 +16,9 @@ const InsufficientBalanceModal = observer( ({ history, is_virtual, is_visible, message, toggleModal }: TInsufficientBalanceModal) => { const { ui: { is_mobile }, + client, } = useStore(); + const { has_wallet } = client; return ( { if (!is_virtual) { - history?.push?.(routes.cashier_deposit); + history?.push?.(has_wallet ? routes.wallets_deposit : routes.cashier_deposit); } else { toggleModal(); } diff --git a/packages/trader/src/App/Components/Routes/__tests__/binary-link.spec.tsx b/packages/trader/src/App/Components/Routes/__tests__/binary-link.spec.tsx index c37ff374e150..948f5760581e 100644 --- a/packages/trader/src/App/Components/Routes/__tests__/binary-link.spec.tsx +++ b/packages/trader/src/App/Components/Routes/__tests__/binary-link.spec.tsx @@ -2,6 +2,8 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import { BrowserRouter } from 'react-router-dom'; import { BinaryLink } from '../index'; +import userEvent from '@testing-library/user-event'; +import { routes } from '@deriv/shared'; type TMockBinaryLink = { to?: string; @@ -17,22 +19,24 @@ const MockBinaryLink = ({ to }: TMockBinaryLink) => ( describe('BinaryLink component', () => { it('should render "children" when passed in', () => { - render(); + render(); expect(screen.getByTestId('dt_child')).toBeInTheDocument(); }); it('should have "active_class" when passed in', () => { - render(); - expect(screen.getByTestId('dt_binary_link')).toHaveClass('active_class'); + render(); + userEvent.click(screen.getByTestId('dt_binary_link')); + const link = screen.getByTestId('dt_binary_link'); + expect(link).toHaveClass('active_class'); }); - it('should render "NavLink" when "to" property is passed', () => { - render(); + it('should render "NavLink" when valid "to" property is passed', () => { + render(); expect(screen.getByTestId('dt_binary_link')).toBeInTheDocument(); }); - it('should render "a" element whe property "to" is not passed', () => { - render(); - expect(screen.getByTestId('dt_binary_link')).toBeInTheDocument(); + it('throws an error for an invalid route', () => { + const viewComponent = () => render(); + expect(viewComponent).toThrowError('Route not found: /invalid'); }); }); diff --git a/packages/trader/src/App/Constants/__tests__/routes-config.spec.tsx b/packages/trader/src/App/Constants/__tests__/routes-config.spec.tsx index 106188e63030..19278fe068b6 100644 --- a/packages/trader/src/App/Constants/__tests__/routes-config.spec.tsx +++ b/packages/trader/src/App/Constants/__tests__/routes-config.spec.tsx @@ -2,6 +2,7 @@ import React, { ComponentProps } from 'react'; import { Router } from 'react-router'; import { createMemoryHistory } from 'history'; import { mockStore } from '@deriv/stores'; +import { routes as routesList } from '@deriv/shared'; import { render, screen, waitFor } from '@testing-library/react'; import BinaryRoutes from 'App/Components/Routes'; import TraderProviders from '../../../trader-providers'; @@ -49,7 +50,7 @@ describe('Routes Config', () => { it('should return routes with contract route', async () => { const routes = getRoutesConfig(); - expect(routes?.[0]?.path).toBe('/contract/:contract_id'); + expect(routes?.[0]?.path).toBe(routesList.contract); expect(routes?.[0]?.getTitle?.()).toBe('Contract Details'); expect(routes?.[0]?.is_authenticated).toBe(true); const history = createMemoryHistory(); @@ -62,11 +63,11 @@ describe('Routes Config', () => { it('should return routes with trade route', async () => { const routes = getRoutesConfig(); - expect(routes?.[1]?.path).toBe('/'); + expect(routes?.[1]?.path).toBe(routesList.trade); expect(routes?.[1]?.getTitle?.()).toBe('Trader'); expect(routes?.[1]?.exact).toBe(true); const history = createMemoryHistory(); - history.push('/'); + history.push(routesList.trade); render(); await waitFor(() => { expect(screen.getByText('Trader')).toBeInTheDocument(); diff --git a/packages/trader/src/App/Containers/Routes/routes.tsx b/packages/trader/src/App/Containers/Routes/routes.tsx index 76f9130024f7..457d8f4b9a68 100644 --- a/packages/trader/src/App/Containers/Routes/routes.tsx +++ b/packages/trader/src/App/Containers/Routes/routes.tsx @@ -20,7 +20,7 @@ type TTradePageMountingMiddlewareParams = { path_to: string; }; -export const checkRoutingMatch = (route_list: Array, path = '') => { +export const checkRoutingMatch = (route_list: Array, path = '/dtrader') => { return route_list.some(route => !!matchPath(path, { path: route, exact: true })); }; diff --git a/packages/trader/src/App/Containers/__tests__/trade-footer-extensions.spec.tsx b/packages/trader/src/App/Containers/__tests__/trade-footer-extensions.spec.tsx index c39e637b94b7..ec7b116efd17 100644 --- a/packages/trader/src/App/Containers/__tests__/trade-footer-extensions.spec.tsx +++ b/packages/trader/src/App/Containers/__tests__/trade-footer-extensions.spec.tsx @@ -5,6 +5,7 @@ import TradeFooterExtensions from '../trade-footer-extensions'; import { mockStore } from '@deriv/stores'; import { RouteComponentProps, Router } from 'react-router-dom'; import { MemoryHistory, createMemoryHistory } from 'history'; +import { routes } from '@deriv/shared'; describe('', () => { let mock_store: ReturnType, @@ -41,7 +42,7 @@ describe('', () => { mock_store.client.is_logged_in = true; router_prop = { location: { - pathname: '/', + pathname: routes.trade, }, }; renderTraderFooterExtensions(router_prop); @@ -57,7 +58,7 @@ describe('', () => { it('should call populateFooterExtensions with empty array when pathname is not trader', () => { router_prop = { location: { - pathname: '/cashier', + pathname: routes.cashier, }, }; renderTraderFooterExtensions(router_prop); diff --git a/packages/tradershub/src/constants/constants.tsx b/packages/tradershub/src/constants/constants.tsx index a04f442b6b78..f9315cae6943 100644 --- a/packages/tradershub/src/constants/constants.tsx +++ b/packages/tradershub/src/constants/constants.tsx @@ -28,7 +28,7 @@ export const optionsAndMultipliersContent = (isEU: boolean) => [ { description: isEU ? 'Multipliers trading platform.' : 'Options and multipliers trading platform.', icon: , - redirect: '/', + redirect: '/dtrader', smallIcon: , title: 'Deriv Trader', }, diff --git a/packages/wallets/component-tests/crypto-payment-redirection.spec.tsx b/packages/wallets/component-tests/crypto-payment-redirection.spec.tsx index 03a75577901a..72b328bb5bbf 100644 --- a/packages/wallets/component-tests/crypto-payment-redirection.spec.tsx +++ b/packages/wallets/component-tests/crypto-payment-redirection.spec.tsx @@ -28,14 +28,14 @@ test.describe('Wallets - Crypto withdrawal', () => { }, }); - await page.goto(`${baseURL}/wallets`); + await page.goto(`${baseURL}/`); await page.click('.wallets-textfield__field--listcard'); await page.click('#downshift-0-item-1'); }); test('render withdrawal form with all elements', async ({ baseURL, page }) => { - await page.goto(`${baseURL}/wallets/cashier/withdraw?verification=XXXX`); + await page.goto(`${baseURL}/wallet/withdrawal?verification=XXXX`); // #cryptoAddress await expect(page.locator('#cryptoAddress')).toBeVisible(); @@ -59,7 +59,7 @@ test.describe('Wallets - Crypto withdrawal', () => { }); test('displays validation messages for address field', async ({ baseURL, page }) => { - await page.goto(`${baseURL}/wallets/cashier/withdraw?verification=XXXX`); + await page.goto(`${baseURL}/wallet/withdrawal?verification=XXXX`); // given initial state, no validation message should be visible let validationMessage = await page.locator('text=This field is required.'); @@ -85,7 +85,7 @@ test.describe('Wallets - Crypto withdrawal', () => { }); test('balance meter is empty initially', async ({ baseURL, page }) => { - await page.goto(`${baseURL}/wallets/cashier/withdraw?verification=XXXX`); + await page.goto(`${baseURL}/wallet/withdrawal?verification=XXXX`); // percentage selector, visible await expect(page.locator('.wallets-percentage-selector')).toBeVisible(); @@ -119,7 +119,7 @@ test.describe('Wallets - Crypto withdrawal', () => { baseURL, page, }) => { - await page.goto(`${baseURL}/wallets/cashier/withdraw?verification=XXXX`); + await page.goto(`${baseURL}/wallet/withdrawal?verification=XXXX`); // percentage selector, visible await expect(page.locator('.wallets-percentage-selector')).toBeVisible(); @@ -156,7 +156,7 @@ test.describe('Wallets - Crypto withdrawal', () => { baseURL, page, }) => { - await page.goto(`${baseURL}/wallets/cashier/withdraw?verification=XXXX`); + await page.goto(`${baseURL}/wallet/withdrawal?verification=XXXX`); // percentage selector, visible await expect(page.locator('.wallets-percentage-selector')).toBeVisible(); @@ -190,7 +190,7 @@ test.describe('Wallets - Crypto withdrawal', () => { }); test('balance meter displays still displays 100% when amount exceeds balance', async ({ baseURL, page }) => { - await page.goto(`${baseURL}/wallets/cashier/withdraw?verification=XXXX`); + await page.goto(`${baseURL}/wallet/withdrawal?verification=XXXX`); // percentage selector, visible await expect(page.locator('.wallets-percentage-selector')).toBeVisible(); @@ -224,7 +224,7 @@ test.describe('Wallets - Crypto withdrawal', () => { }); test('validates crypto input against current balance and minimum withdrawal amount', async ({ baseURL, page }) => { - await page.goto(`${baseURL}/wallets/cashier/withdraw?verification=XXXX`); + await page.goto(`${baseURL}/wallet/withdrawal?verification=XXXX`); // given initial state, no validation message should be shown await expect( @@ -283,7 +283,7 @@ test.describe('Wallets - Crypto withdrawal', () => { }); test('converts fiat to crypto and vice versa', async ({ baseURL, page }) => { - await page.goto(`${baseURL}/wallets/cashier/withdraw?verification=XXXX`); + await page.goto(`${baseURL}/wallet/withdrawal?verification=XXXX`); // given some amount of crypto being put to input, convert it to fiat await page.fill('#cryptoAmount', '10'); @@ -295,7 +295,7 @@ test.describe('Wallets - Crypto withdrawal', () => { }); test('submit button validity', async ({ baseURL, page }) => { - await page.goto(`${baseURL}/wallets/cashier/withdraw?verification=XXXX`); + await page.goto(`${baseURL}/wallet/withdrawal?verification=XXXX`); // given initial state, submit button should be disabled await expect(page.locator('.wallets-withdrawal-crypto-form__submit button[type="submit"]')).toBeDisabled(); @@ -318,7 +318,7 @@ test.describe('Wallets - Crypto withdrawal', () => { }); test('spinner while submitting', async ({ baseURL, page }) => { - await page.goto(`${baseURL}/wallets/cashier/withdraw?verification=XXXX`); + await page.goto(`${baseURL}/wallet/withdrawal?verification=XXXX`); // given initial state, submit button should be disabled await expect(page.locator('.wallets-withdrawal-crypto-form__submit button[type="submit"]')).toBeDisabled(); diff --git a/packages/wallets/component-tests/menu.spec.tsx b/packages/wallets/component-tests/menu.spec.tsx index 75a62da3d268..a74e7d66eb95 100644 --- a/packages/wallets/component-tests/menu.spec.tsx +++ b/packages/wallets/component-tests/menu.spec.tsx @@ -24,7 +24,7 @@ test.describe('Wallets - Traders Hub', () => { accounts: DEFAULT_WALLET_ACCOUNTS, }, }); - await page.goto(`${baseURL}/wallets`); + await page.goto(`${baseURL}/`); const balanceContainer = await page.textContent('.wallets-balance__container'); expect(balanceContainer).toContain('9,988,000.89 USD'); diff --git a/packages/wallets/component-tests/wallets-carousel-content.spec.tsx b/packages/wallets/component-tests/wallets-carousel-content.spec.tsx index 3ac1cb01cf60..f7c766dcf5c2 100644 --- a/packages/wallets/component-tests/wallets-carousel-content.spec.tsx +++ b/packages/wallets/component-tests/wallets-carousel-content.spec.tsx @@ -57,7 +57,7 @@ test.describe('Wallets - Mobile carousel', () => { }); test('renders cards for all wallets', async ({ baseURL }) => { - await mobilePage.goto(`${baseURL}/wallets`); + await mobilePage.goto(`${baseURL}/`); // Ensure the carousel is loaded and visible await mobilePage.waitForSelector(CAROUSEL_SELECTOR); @@ -90,7 +90,7 @@ test.describe('Wallets - Mobile carousel', () => { }); test('renders progress bar with active item and updates it when swiping', async ({ baseURL }) => { - await mobilePage.goto(`${baseURL}/wallets`); + await mobilePage.goto(`${baseURL}/`); const activeProgressBarItem = mobilePage.locator('.wallets-progress-bar div:nth-child(1)'); const progressBarItemClass = await activeProgressBarItem.getAttribute('class'); @@ -117,7 +117,7 @@ test.describe('Wallets - Mobile carousel', () => { test('switches account when clicking on progress bar', async ({ baseURL }) => { // given - await mobilePage.goto(`${baseURL}/wallets`); + await mobilePage.goto(`${baseURL}/`); const progressBarItem = mobilePage.locator('.wallets-progress-bar div:nth-child(3)'); diff --git a/packages/wallets/src/AppContent.scss b/packages/wallets/src/AppContent.scss index e7855a5e5a3d..471a0417d705 100644 --- a/packages/wallets/src/AppContent.scss +++ b/packages/wallets/src/AppContent.scss @@ -5,7 +5,7 @@ gap: 2.4rem; width: 100%; align-self: stretch; - background: var(--system-light-7-secondary-background, #f2f3f4); + background-color: var(--general-main-1, #ffffff); min-height: calc(100vh - 8.4rem); // 100vh - (4.8rem header + 3.6rem footer) @include mobile { diff --git a/packages/wallets/src/components/DerivAppsSection/DerivAppsSuccessFooter.tsx b/packages/wallets/src/components/DerivAppsSection/DerivAppsSuccessFooter.tsx index fc576c15f735..d6221522bbed 100644 --- a/packages/wallets/src/components/DerivAppsSection/DerivAppsSuccessFooter.tsx +++ b/packages/wallets/src/components/DerivAppsSection/DerivAppsSuccessFooter.tsx @@ -18,7 +18,7 @@ const DerivAppsSuccessFooter = () => { { - history.push('/wallets/cashier/transfer'); + history.push('/wallet/account-transfer'); hide(); }} size='lg' diff --git a/packages/wallets/src/components/DerivAppsSection/DerivAppsTradingAccount.tsx b/packages/wallets/src/components/DerivAppsSection/DerivAppsTradingAccount.tsx index 16bfc88928f4..3c123808796f 100644 --- a/packages/wallets/src/components/DerivAppsSection/DerivAppsTradingAccount.tsx +++ b/packages/wallets/src/components/DerivAppsSection/DerivAppsTradingAccount.tsx @@ -39,8 +39,8 @@ const DerivAppsTradingAccount: React.FC = () => { className='wallets-deriv-apps-section__button' onClick={() => { activeWallet?.is_virtual - ? history.push('/wallets/cashier/reset-balance') - : history.push('/wallets/cashier/transfer', { + ? history.push('/wallet/reset-balance') + : history.push('/wallet/account-transfer', { toAccountLoginId: activeLinkedToTradingAccount?.loginid, }); }} diff --git a/packages/wallets/src/components/Page404/Page404.scss b/packages/wallets/src/components/Page404/Page404.scss new file mode 100644 index 000000000000..5cef58c4a322 --- /dev/null +++ b/packages/wallets/src/components/Page404/Page404.scss @@ -0,0 +1,5 @@ +.wallets-page-404 { + display: flex; + align-items: center; + height: calc(100vh - 8.4rem); +} diff --git a/packages/wallets/src/components/Page404/Page404.tsx b/packages/wallets/src/components/Page404/Page404.tsx new file mode 100644 index 000000000000..0f516223f761 --- /dev/null +++ b/packages/wallets/src/components/Page404/Page404.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import { Trans } from 'react-i18next'; +import { useHistory } from 'react-router-dom'; +import useDevice from '../../hooks/useDevice'; +import { WalletButton, WalletText } from '../Base'; +import { WalletsActionScreen } from '../WalletsActionScreen'; +import './Page404.scss'; + +const Page404 = () => { + const { isMobile } = useDevice(); + const titleSize = isMobile ? 'md' : '2xl'; + const descriptionSize = isMobile ? 'sm' : 'md'; + const buttonSize = isMobile ? 'lg' : 'md'; + const buttonTextSize = isMobile ? 'md' : 'sm'; + + const history = useHistory(); + + const errorImage = isMobile ? ( + {'404'} + ) : ( + {'404'} + ); + + return ( +
+ ( + { + history.push('/'); + }} + size={buttonSize} + > + + + + + )} + title="We couldn't find that page" + titleSize={titleSize} + /> +
+ ); +}; + +export default Page404; diff --git a/packages/wallets/src/components/Page404/index.ts b/packages/wallets/src/components/Page404/index.ts new file mode 100644 index 000000000000..2cdfcb114cb8 --- /dev/null +++ b/packages/wallets/src/components/Page404/index.ts @@ -0,0 +1 @@ +export { default as Page404 } from './Page404'; diff --git a/packages/wallets/src/components/WalletListCardActions/WalletListCardActions.tsx b/packages/wallets/src/components/WalletListCardActions/WalletListCardActions.tsx index 4cf78bc0d3fa..234c82e9cd8b 100644 --- a/packages/wallets/src/components/WalletListCardActions/WalletListCardActions.tsx +++ b/packages/wallets/src/components/WalletListCardActions/WalletListCardActions.tsx @@ -25,7 +25,7 @@ const getWalletHeaderButtons = (isDemo?: boolean) => { className: 'wallets-mobile-actions-content-icon', color: 'white', icon: , - name: 'withdraw', + name: 'withdrawal', text: 'Withdraw', variant: 'outlined', }, @@ -33,22 +33,16 @@ const getWalletHeaderButtons = (isDemo?: boolean) => { className: 'wallets-mobile-actions-content-icon', color: 'white', icon: , - name: 'transfer', + name: 'account-transfer', text: 'Transfer', variant: 'outlined', }, ] as const; // Filter out the "Withdraw" button when is_demo is true - const filteredButtons = isDemo ? buttons.filter(button => button.name !== 'withdraw') : buttons; + const filteredButtons = isDemo ? buttons.filter(button => button.name !== 'withdrawal') : buttons; - const orderForDemo = ['reset-balance', 'transfer']; - - const sortedButtons = isDemo - ? [...filteredButtons].sort((a, b) => orderForDemo.indexOf(a.name) - orderForDemo.indexOf(b.name)) - : filteredButtons; - - return sortedButtons; + return filteredButtons; }; const WalletListCardActions = () => { @@ -71,7 +65,7 @@ const WalletListCardActions = () => { color={button.color} icon={button.icon} onClick={() => { - history.push(`/wallets/cashier/${button.name}`); + history.push(`/wallet/${button.name}`); }} size='lg' /> @@ -92,7 +86,7 @@ const WalletListCardActions = () => { icon={button.icon} key={button.name} onClick={() => { - history.push(`/wallets/cashier/${button.name}`); + history.push(`/wallet/${button.name}`); }} rounded='lg' variant={button.variant} diff --git a/packages/wallets/src/components/WalletListCardActions/__tests__/WalletListCardActions.spec.tsx b/packages/wallets/src/components/WalletListCardActions/__tests__/WalletListCardActions.spec.tsx index 2c1c9c9ce3de..9e98469c48ec 100644 --- a/packages/wallets/src/components/WalletListCardActions/__tests__/WalletListCardActions.spec.tsx +++ b/packages/wallets/src/components/WalletListCardActions/__tests__/WalletListCardActions.spec.tsx @@ -90,7 +90,7 @@ describe('WalletListCardActions', () => { render(, { wrapper }); screen.getByRole('button', { name: 'deposit' }).click(); - expect(history.location.pathname).toBe('/wallets/cashier/deposit'); + expect(history.location.pathname).toBe('/wallet/deposit'); }); it('should render the actions for mobile', () => { @@ -109,25 +109,25 @@ describe('WalletListCardActions', () => { wrapper, }); screen.getByRole('button', { name: 'deposit' }).click(); - expect(history.location.pathname).toBe('/wallets/cashier/deposit'); + expect(history.location.pathname).toBe('/wallet/deposit'); }); it('should redirect to cashier page when clicking on deposit', () => { render(, { wrapper }); screen.getByRole('button', { name: 'deposit' }).click(); - expect(history.location.pathname).toBe('/wallets/cashier/deposit'); + expect(history.location.pathname).toBe('/wallet/deposit'); }); it('should redirect to cashier page when clicking on withdraw', () => { render(, { wrapper }); - screen.getByRole('button', { name: 'withdraw' }).click(); - expect(history.location.pathname).toBe('/wallets/cashier/withdraw'); + screen.getByRole('button', { name: 'withdrawal' }).click(); + expect(history.location.pathname).toBe('/wallet/withdrawal'); }); it('should redirect to cashier page when clicking on transfer', () => { render(, { wrapper }); - screen.getByRole('button', { name: 'transfer' }).click(); - expect(history.location.pathname).toBe('/wallets/cashier/transfer'); + screen.getByRole('button', { name: 'account-transfer' }).click(); + expect(history.location.pathname).toBe('/wallet/account-transfer'); }); it('should redirect to cashier page when clicking on reset balance', () => { @@ -144,6 +144,6 @@ describe('WalletListCardActions', () => { render(, { wrapper }); screen.getByRole('button', { name: 'reset-balance' }).click(); - expect(history.location.pathname).toBe('/wallets/cashier/reset-balance'); + expect(history.location.pathname).toBe('/wallet/reset-balance'); }); }); diff --git a/packages/wallets/src/components/WalletNoWalletFoundState/WalletNoWalletFoundState.scss b/packages/wallets/src/components/WalletNoWalletFoundState/WalletNoWalletFoundState.scss deleted file mode 100644 index 9ee90447a9b7..000000000000 --- a/packages/wallets/src/components/WalletNoWalletFoundState/WalletNoWalletFoundState.scss +++ /dev/null @@ -1,40 +0,0 @@ -.wallets-no-wallet-found-state { - width: 100%; - height: calc(100vh - 84px); - display: flex; - align-items: center; - justify-content: center; - gap: 0.5rem; - background: var(--system-light-8-primary-background, #fff); - - @include mobile { - height: calc(100vh - 44px); - flex-direction: column; - } - - &__container { - display: flex; - flex-direction: column; - align-items: start; - gap: 4rem; - padding-inline: 0.5rem; - - @include mobile { - align-items: center; - } - } - - &__content { - display: flex; - flex-direction: column; - gap: 1rem; - - @include mobile { - align-items: center; - } - } - - &__emphasized-text { - color: var(--brand-coral, #ff444f); - } -} diff --git a/packages/wallets/src/components/WalletNoWalletFoundState/WalletNoWalletFoundState.tsx b/packages/wallets/src/components/WalletNoWalletFoundState/WalletNoWalletFoundState.tsx deleted file mode 100644 index 9c5b8a3da665..000000000000 --- a/packages/wallets/src/components/WalletNoWalletFoundState/WalletNoWalletFoundState.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; -import { useHistory } from 'react-router-dom'; -import useDevice from '../../hooks/useDevice'; -import NoWalletIcon from '../../public/images/no-wallet.svg'; -import { WalletButton, WalletText } from '../Base'; -import './WalletNoWalletFoundState.scss'; - -const WalletNoWalletFoundState: React.FC = () => { - const { isMobile } = useDevice(); - const history = useHistory(); - - return ( -
- -
-
- - You have no wallet account 🐣 - - - Disable the next_wallet{' '} - feature flag to see Trader's Hub. - -
- history.push('/endpoint')} size='lg'> - Endpoint - -
-
- ); -}; - -export default WalletNoWalletFoundState; diff --git a/packages/wallets/src/components/WalletNoWalletFoundState/index.ts b/packages/wallets/src/components/WalletNoWalletFoundState/index.ts deleted file mode 100644 index 2d5186e3af2c..000000000000 --- a/packages/wallets/src/components/WalletNoWalletFoundState/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as WalletNoWalletFoundState } from './WalletNoWalletFoundState'; diff --git a/packages/wallets/src/components/WalletsAddMoreCardBanner/WalletsAddMoreCardBanner.tsx b/packages/wallets/src/components/WalletsAddMoreCardBanner/WalletsAddMoreCardBanner.tsx index 056166d1645c..7b2bff25d297 100644 --- a/packages/wallets/src/components/WalletsAddMoreCardBanner/WalletsAddMoreCardBanner.tsx +++ b/packages/wallets/src/components/WalletsAddMoreCardBanner/WalletsAddMoreCardBanner.tsx @@ -31,7 +31,7 @@ const WalletsAddMoreCardBanner: React.FC = ({ modal.hide()} variant='outlined'> Maybe later - history.push('/wallets/cashier/deposit')}>Deposit now + history.push('/wallet/deposit')}>Deposit now
), [history] // eslint-disable-line react-hooks/exhaustive-deps @@ -57,7 +57,7 @@ const WalletsAddMoreCardBanner: React.FC = ({ displayBalance={data?.display_balance ?? `0.00 ${data?.currency}`} landingCompany={data?.landing_company_shortcode} onPrimaryButtonClick={() => { - history.push('/wallets/cashier/deposit'); + history.push('/wallet/deposit'); modal.hide(); }} onSecondaryButtonClick={() => modal.hide()} diff --git a/packages/wallets/src/components/WalletsCarouselContent/WalletsCarouselContent.tsx b/packages/wallets/src/components/WalletsCarouselContent/WalletsCarouselContent.tsx index 2bda08ec3f7f..9263b4a818ec 100644 --- a/packages/wallets/src/components/WalletsCarouselContent/WalletsCarouselContent.tsx +++ b/packages/wallets/src/components/WalletsCarouselContent/WalletsCarouselContent.tsx @@ -125,9 +125,7 @@ const WalletsCarouselContent: React.FC = () => { walletsCarouselEmblaApi?.scrollTo(index); walletAccountsList && setSelectedLoginId(walletAccountsList[index].loginid); account.is_active && - (account.is_virtual - ? history.push('/wallets/cashier/reset-balance') - : history.push('/wallets/cashier/deposit')); + (account.is_virtual ? history.push('/wallet/reset-balance') : history.push('/wallet/deposit')); }, [walletsCarouselEmblaApi, walletAccountsList, history] ); diff --git a/packages/wallets/src/components/WalletsCarouselHeader/WalletsCarouselHeader.tsx b/packages/wallets/src/components/WalletsCarouselHeader/WalletsCarouselHeader.tsx index fa4e91cc63a3..c3c4091734c5 100644 --- a/packages/wallets/src/components/WalletsCarouselHeader/WalletsCarouselHeader.tsx +++ b/packages/wallets/src/components/WalletsCarouselHeader/WalletsCarouselHeader.tsx @@ -44,7 +44,7 @@ const WalletsCarouselHeader: React.FC = ({ balance, currency, hidden, is icon={} iconSize='lg' onClick={() => { - history.push(`/wallets/cashier/transfer`); + history.push('/wallet/account-transfer'); }} size='lg' /> diff --git a/packages/wallets/src/components/WalletsCarouselHeader/__tests__/WalletsCarouselHeader.spec.tsx b/packages/wallets/src/components/WalletsCarouselHeader/__tests__/WalletsCarouselHeader.spec.tsx index 4085cc6afaab..1f340cde6fb2 100644 --- a/packages/wallets/src/components/WalletsCarouselHeader/__tests__/WalletsCarouselHeader.spec.tsx +++ b/packages/wallets/src/components/WalletsCarouselHeader/__tests__/WalletsCarouselHeader.spec.tsx @@ -37,7 +37,7 @@ describe('WalletsCarouselHeader', () => { fireEvent.click(screen.getByTestId('dt_wallets_carousel_header_button')); - expect(useHistory().push).toHaveBeenCalledWith('/wallets/cashier/transfer'); + expect(useHistory().push).toHaveBeenCalledWith('/wallet/account-transfer'); }); it('should display loader when balance is loading', () => { diff --git a/packages/wallets/src/components/index.ts b/packages/wallets/src/components/index.ts index a54f63662f5b..938a2b06458c 100644 --- a/packages/wallets/src/components/index.ts +++ b/packages/wallets/src/components/index.ts @@ -30,7 +30,6 @@ export * from './WalletListCardDropdown'; export * from './WalletListHeader'; export * from './WalletMarketCurrencyIcon'; export * from './WalletMarketIcon'; -export * from './WalletNoWalletFoundState'; export * from './WalletsActionScreen'; export * from './WalletsAddMoreCarousel'; export * from './WalletsAppLinkedWithWalletIcon'; diff --git a/packages/wallets/src/constants/constants.tsx b/packages/wallets/src/constants/constants.tsx index 022299c17f20..6f6f422efcc1 100644 --- a/packages/wallets/src/constants/constants.tsx +++ b/packages/wallets/src/constants/constants.tsx @@ -13,7 +13,7 @@ export const optionsAndMultipliersContent = [ { description: i18n.t('The options and multipliers trading platform.'), icon: , - redirect: '/', + redirect: '/dtrader', smallIcon: , title: i18n.t('Deriv Trader'), }, diff --git a/packages/wallets/src/features/cashier/components/WalletCashierContent/WalletCashierContent.tsx b/packages/wallets/src/features/cashier/components/WalletCashierContent/WalletCashierContent.tsx index ca90607ead0e..44a5717f27da 100644 --- a/packages/wallets/src/features/cashier/components/WalletCashierContent/WalletCashierContent.tsx +++ b/packages/wallets/src/features/cashier/components/WalletCashierContent/WalletCashierContent.tsx @@ -11,17 +11,17 @@ import { CashierLocked, DepositLocked, WithdrawalLocked } from '../../modules'; const WalletCashierContent = () => { const history = useHistory(); - const isDeposit = useRouteMatch('/wallets/cashier/deposit'); - const isFiatOnRamp = useRouteMatch('/wallets/cashier/on-ramp'); - const isResetBalance = useRouteMatch('/wallets/cashier/reset-balance'); - const isTransfer = useRouteMatch('/wallets/cashier/transfer'); - const isTransactions = useRouteMatch('/wallets/cashier/transactions'); - const isWithdraw = useRouteMatch('/wallets/cashier/withdraw'); + const isDeposit = useRouteMatch('/wallet/deposit'); + const isFiatOnRamp = useRouteMatch('/wallet/on-ramp'); + const isResetBalance = useRouteMatch('/wallet/reset-balance'); + const isTransfer = useRouteMatch('/wallet/account-transfer'); + const isTransactions = useRouteMatch('/wallet/transactions'); + const isWithdraw = useRouteMatch('/wallet/withdrawal'); useEffect(() => { // redirect to deposit page if no other page is matched if (!isTransfer && !isDeposit && !isTransactions && !isWithdraw && !isResetBalance && !isFiatOnRamp) { - history.push('/wallets/cashier/deposit'); + history.push('/wallet/deposit'); } }, [isTransfer, isDeposit, isTransactions, isWithdraw, isResetBalance, isFiatOnRamp, history]); diff --git a/packages/wallets/src/features/cashier/components/WalletCashierHeader/WalletCashierHeader.tsx b/packages/wallets/src/features/cashier/components/WalletCashierHeader/WalletCashierHeader.tsx index 127ae2295a55..9d369d347d1d 100644 --- a/packages/wallets/src/features/cashier/components/WalletCashierHeader/WalletCashierHeader.tsx +++ b/packages/wallets/src/features/cashier/components/WalletCashierHeader/WalletCashierHeader.tsx @@ -29,12 +29,12 @@ const realAccountTabs = [ }, { icon: , - path: 'withdraw', + path: 'withdrawal', text: i18n.t('Withdraw'), }, { icon: , - path: 'transfer', + path: 'account-transfer', text: i18n.t('Transfer'), }, { @@ -45,16 +45,16 @@ const realAccountTabs = [ ] as const; const virtualAccountTabs = [ - { - icon: , - path: 'transfer', - text: i18n.t('Transfer'), - }, { icon: , path: 'reset-balance', text: i18n.t('Reset Balance'), }, + { + icon: , + path: 'account-transfer', + text: i18n.t('Transfer'), + }, { icon: , path: 'transactions', @@ -144,23 +144,23 @@ const WalletCashierHeader: React.FC = ({ hideWalletDetails }) => { 'wallets-cashier-header__close-icon--white': activeWallet?.is_virtual, })} iconSize='xs' - onClick={() => history.push('/wallets')} + onClick={() => history.push('/')} />
{tabs.map(tab => { const isActiveTab = - location.pathname === `/wallets/cashier/on-ramp` + location.pathname === `/wallet/on-ramp` ? tab.path === 'deposit' - : location.pathname === `/wallets/cashier/${tab.path}`; + : location.pathname === `/wallet/${tab.path}`; return (