diff --git a/.github/actions/build_and_push_docker_image/action.yml b/.github/actions/build_and_push_docker_image/action.yml deleted file mode 100644 index a652cfb01e45..000000000000 --- a/.github/actions/build_and_push_docker_image/action.yml +++ /dev/null @@ -1,77 +0,0 @@ -name: build_and_push_docker_image_and_k8s -description: Build docker image and push to docker hub and K8S -inputs: - DOCKERHUB_ORGANISATION: - description: "Dockerhub Organisation" - required: true - DOCKERHUB_USERNAME: - description: "Dockerhub Username" - required: true - DOCKERHUB_PASSWORD: - description: "Dockerhub Password" - required: true - K8S_NAMESPACE: - description: "K8S namespace" - required: true - K8S_SERVICE: - description: "K8S service" - required: true - KUBE_SERVER: - description: "K8S server" - required: true - SERVICEACCOUNT_TOKEN: - description: "K8S service account token" - required: true - CA_CRT: - description: "K8S CA_CRT" - required: true - APP_VERSION: - description: "App version" - required: true -runs: - using: composite - steps: - - name: Build docker image 🐳 - env: - DOCKERHUB_ORGANISATION: ${{ inputs.DOCKERHUB_ORGANISATION }} - APP_NAME: deriv-app - APP_VERSION: ${{ inputs.APP_VERSION }} - run: docker build -t ${DOCKERHUB_ORGANISATION}/${APP_NAME}:${APP_VERSION} -t ${DOCKERHUB_ORGANISATION}/${APP_NAME}:${{ github.ref_name }} . - shell: bash - - name: Verify nginx image - env: - DOCKERHUB_ORGANISATION: ${{ inputs.DOCKERHUB_ORGANISATION }} - APP_NAME: deriv-app - run: | - set -e - docker run --rm ${DOCKERHUB_ORGANISATION}/${APP_NAME}:${{ github.ref_name }} nginx -t - echo "docker image validated successfully" - shell: bash - - name: Pushing Image to docker hub 🐳 - env: - DOCKERHUB_ORGANISATION: ${{ inputs.DOCKERHUB_ORGANISATION }} - APP_NAME: deriv-app - APP_VERSION: ${{ inputs.APP_VERSION }} - DOCKERHUB_USERNAME: ${{ inputs.DOCKERHUB_USERNAME }} - DOCKERHUB_PASSWORD: ${{ inputs.DOCKERHUB_PASSWORD }} - run: | - echo $DOCKERHUB_PASSWORD | docker login -u $DOCKERHUB_USERNAME --password-stdin - docker push ${DOCKERHUB_ORGANISATION}/${APP_NAME}:${APP_VERSION} - docker push ${DOCKERHUB_ORGANISATION}/${APP_NAME}:${{ github.ref_name }} - shell: bash - - name: Deploy 🚀 - env: - NAMESPACE: ${{ inputs.K8S_NAMESPACE }} - KUBE_SERVER: ${{ inputs.KUBE_SERVER }} - SERVICEACCOUNT_TOKEN: ${{ inputs.SERVICEACCOUNT_TOKEN }} - DOCKERHUB_ORGANISATION: ${{ inputs.DOCKERHUB_ORGANISATION }} - CA_CRT: ${{ inputs.CA_CRT }} - APP_NAME: deriv-app - APP_VERSION: ${{ inputs.APP_VERSION }} - run: | - git clone https://github.com/binary-com/devops-ci-scripts - cd devops-ci-scripts/k8s-build_tools - echo $CA_CRT | base64 --decode > ca.crt - export CA="ca.crt" - ./release.sh ${APP_NAME} ${{ github.ref_name }} - shell: bash diff --git a/.github/workflows/build_docker_image.yml b/.github/workflows/build_docker_image.yml index 224db6c1edf0..81319ee12bf9 100644 --- a/.github/workflows/build_docker_image.yml +++ b/.github/workflows/build_docker_image.yml @@ -1,5 +1,8 @@ name: Build Docker image and push to dockerhub on: + workflow_run: + workflows: ["Deriv App Staging Workflow"] + types: [completed] workflow_dispatch: inputs: docker_image_tag_name: @@ -11,7 +14,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - tag_name: ${{ github.event.inputs.docker_image_tag_name }} + tag_name: ${{ github.event.inputs.docker_image_tag_name || github.ref_name }} permissions: packages: write contents: read @@ -22,6 +25,12 @@ jobs: - name: Check out the repo uses: actions/checkout@v4 + - name: Convert branch name to lowercase + id: branch_name + run: | + branch_name="$tag_name" + echo "image_name=${branch_name,,}" >> "$GITHUB_OUTPUT" + - name: Log in to Docker Hub uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a with: @@ -54,10 +63,16 @@ jobs: TRUSTPILOT_API_KEY: ${{ secrets.TRUSTPILOT_API_KEY }} - name: Run Build Docker - run: docker build -t ${{ secrets.WEB_ACCESS_DOCKERHUB_USERNAME }}/$tag_name . --platform=linux/amd64 + env: + IMAGE_NAME: ${{ steps.branch_name.outputs.image_name }} + run: docker build -t ${{ secrets.WEB_ACCESS_DOCKERHUB_USERNAME }}/$IMAGE_NAME . --platform=linux/amd64 - name: Run Tag Docker - run: docker tag ${{ secrets.WEB_ACCESS_DOCKERHUB_USERNAME }}/$tag_name ${{ secrets.WEB_ACCESS_DOCKERHUB_USERNAME }}/$tag_name + env: + IMAGE_NAME: ${{ steps.branch_name.outputs.image_name }} + run: docker tag ${{ secrets.WEB_ACCESS_DOCKERHUB_USERNAME }}/$IMAGE_NAME ${{ secrets.WEB_ACCESS_DOCKERHUB_USERNAME }}/$IMAGE_NAME - name: Run Push Docker - run: docker push ${{ secrets.WEB_ACCESS_DOCKERHUB_USERNAME }}/$tag_name + env: + IMAGE_NAME: ${{ steps.branch_name.outputs.image_name }} + run: docker push ${{ secrets.WEB_ACCESS_DOCKERHUB_USERNAME }}/$IMAGE_NAME diff --git a/.github/workflows/coveralls.yml b/.github/workflows/coveralls.yml index 0d212094bfdb..94139a4332b0 100644 --- a/.github/workflows/coveralls.yml +++ b/.github/workflows/coveralls.yml @@ -8,7 +8,7 @@ name: Coveralls jobs: build: name: Reporter - runs-on: Runner_8cores_Deriv-app + runs-on: Runner_16cores_Deriv-app steps: - name: Checkout uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml deleted file mode 100644 index 24e0cfd216e5..000000000000 --- a/.github/workflows/integration-tests.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Deriv App Integration Test Workflow -on: - pull_request: - branches: - - master - push: - branches: - - master -jobs: - integration_tests: - name: Run Integration Tests - runs-on: Runner_8cores_Deriv-app - environment: Preview - steps: - - name: Checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - - name: Setup Node - uses: "./.github/actions/setup_node" - - name: Install dependencies - uses: "./.github/actions/npm_install_from_cache" - # - name: Invalidate NPM Cache - # if: github.event_name == 'push' && github.ref == 'refs/heads/master' - # uses: "./.github/actions/invalidate_npm_cache" - - name: Build - run: npm run build:all - - name: Install Playwright Browsers - run: npx playwright install - - name: Run component tests - run: npm run test:component diff --git a/.github/workflows/release_production.yml b/.github/workflows/release_production.yml index a86de0a7ecde..b4c58d8e34f0 100644 --- a/.github/workflows/release_production.yml +++ b/.github/workflows/release_production.yml @@ -103,24 +103,6 @@ jobs: with: name: build path: packages/core/dist - - name: Build Docker image and push to Docker hub and K8S - id: build_and_push_docker_image - uses: "./.github/actions/build_and_push_docker_image" - with: - DOCKERHUB_USERNAME: ${{ secrets.DOCKER_USERNAME }} - DOCKERHUB_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} - DOCKERHUB_ORGANISATION: ${{ secrets.DOCKERHUB_ORGANISATION }} - K8S_NAMESPACE: ${{ secrets.K8S_NAMESPACE }} - KUBE_SERVER: ${{ secrets.KUBE_SERVER }} - SERVICEACCOUNT_TOKEN: ${{ secrets.SERVICEACCOUNT_TOKEN }} - CA_CRT: ${{ secrets.CA_CRT }} - APP_VERSION: latest - - name: Send Slack Notification on Docker Publish and Kubernetes Deployment Failure - if: ${{ steps.build_and_push_docker_image.outcome != 'success' }} - uses: "deriv-com/shared-actions/.github/actions/send_slack_notification@master" - with: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} - MESSAGE: "${{ env.RELEASE_TYPE }} Docker Publish and Kubernetes Deployment for app.deriv.com with version ${{ needs.build_test_and_publish.outputs.RELEASE_VERSION }} has Failed" - name: Upload to vercel id: vercel-upload uses: 'deriv-com/shared-actions/.github/actions/vercel_DR_publish@master' diff --git a/.gitignore b/.gitignore index 0bc41f0b25af..fd58296a6a40 100644 --- a/.gitignore +++ b/.gitignore @@ -24,7 +24,8 @@ packages/appstore/lib/ packages/appstore/.out packages/wallets/src/translations/messages.json .env -nx-cloud.env +.env.* +*.env test-results/ playwright-report/ playwright/.cache/ @@ -32,4 +33,4 @@ playwright/.cache/ packages/*/stats.json packages/*/report.json packages/*/analyzed.html -packages/*/treemap.html \ No newline at end of file +packages/*/treemap.html diff --git a/__mocks__/translation.mock.js b/__mocks__/translation.mock.js index 0dd0e76a20d9..3e2aefafa2f1 100644 --- a/__mocks__/translation.mock.js +++ b/__mocks__/translation.mock.js @@ -1,35 +1,32 @@ import React from 'react'; +const replaceValue = (text, values) => { + return text.replace(/{{(\w+)}}/g, (match, key) => { + // If the value is an empty string, return an empty fragment to render nothing + if (values[key] === '') { + return ''; + } + return values[key] || match; + }); +}; + const Localize = ({ i18n_default_text, components = [], values = {} }) => { - // Split text into parts, extracting placeholders for components and values + // Split text into parts, extracting placeholders for components const parts = i18n_default_text.split(/(<\d+>.*?<\/\d+>|{{\w+}})/g); - const replaceValues = text => { - return text.replace(/{{(\w+)}}/g, (match, key) => values[key] || match); - }; - return ( <> {parts.map((part, index) => { - // Replace component placeholders with actual components + // Handle component placeholders const componentMatch = part.match(/<(\d+)>(.*?)<\/\1>/); - if (componentMatch) { const componentIndex = parseInt(componentMatch[1]); - - // Replace values wrapped in components with actual values - const content = replaceValues(componentMatch[2]); + const content = replaceValue(componentMatch[2], values); const Component = components[componentIndex]; return Component ? React.cloneElement(Component, { key: index, children: content }) : content; } - - // Replace value placeholders with actual values - const valueMatch = part.match(/{{(\w+)}}/); - if (valueMatch) { - const valueKey = valueMatch[1]; - return values[valueKey] || part; - } - return part; + // Replace placeholders with actual values + return replaceValue(part, values); })} ); diff --git a/package-lock.json b/package-lock.json index f9dcf4328c48..f474f136102f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,13 +16,13 @@ "@datadog/browser-rum": "^5.11.0", "@deriv-com/analytics": "1.11.0", "@deriv-com/quill-tokens": "^2.0.4", - "@deriv-com/quill-ui": "1.13.23", - "@deriv-com/translations": "1.3.4", - "@deriv-com/ui": "1.29.9", + "@deriv-com/quill-ui": "1.13.42", + "@deriv-com/translations": "1.3.5", + "@deriv-com/ui": "1.29.10", "@deriv-com/utils": "^0.0.25", "@deriv/api-types": "1.0.172", "@deriv/deriv-api": "^1.0.15", - "@deriv/deriv-charts": "^2.2.0", + "@deriv/deriv-charts": "^2.3.0", "@deriv/js-interpreter": "^3.0.0", "@deriv/quill-design": "^1.3.2", "@deriv/quill-icons": "1.23.3", @@ -2961,9 +2961,9 @@ } }, "node_modules/@deriv-com/quill-ui": { - "version": "1.13.23", - "resolved": "https://registry.npmjs.org/@deriv-com/quill-ui/-/quill-ui-1.13.23.tgz", - "integrity": "sha512-jzg2z/3u0A7mLcEbrw8EZBpyE005x1xAyeIccER8xKY8ejmu9cMIFl8pe6UlQGUNBHpL2GFowTXY0kO4CZn+/Q==", + "version": "1.13.42", + "resolved": "https://registry.npmjs.org/@deriv-com/quill-ui/-/quill-ui-1.13.42.tgz", + "integrity": "sha512-/YPbFP4O6DUi0EQ9Qc0KRZ0JZxk56Hz0+MCm7R76gv42bAEKF7N2VYUYlbGOGJC+6Uesh3YIpujYB/JyyXui/Q==", "dependencies": { "@deriv-com/quill-tokens": "^2.0.8", "@deriv/quill-icons": "^1.22.10", @@ -3182,9 +3182,9 @@ } }, "node_modules/@deriv/deriv-charts": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@deriv/deriv-charts/-/deriv-charts-2.2.0.tgz", - "integrity": "sha512-q60cKdWrSHXeSbpY3WohMkmYn+mK4jlJJS+/ytmtOxPAUOdpIkplku8Jvq6+4kGuVAND1SqvwUJpMkK+767MQg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@deriv/deriv-charts/-/deriv-charts-2.3.0.tgz", + "integrity": "sha512-+PRhXVeCgJmo+3feuyA68vhSZkJaj7u3saQlX4pFiwzDcDLPuAYBWQQA7jrYeoxH/BeKhpN+5qkJGxSRMQVfGA==", "dependencies": { "@types/lodash.set": "^4.3.7", "@welldone-software/why-did-you-render": "^3.3.8", diff --git a/packages/account/package.json b/packages/account/package.json index fe56643046cf..83f68175fa4f 100644 --- a/packages/account/package.json +++ b/packages/account/package.json @@ -31,11 +31,11 @@ "dependencies": { "@binary-com/binary-document-uploader": "^2.4.8", "@deriv-com/analytics": "1.11.0", - "@deriv-com/translations": "1.3.4", + "@deriv-com/translations": "1.3.5", "@deriv-com/utils": "^0.0.25", - "@deriv-com/ui": "1.29.9", + "@deriv-com/ui": "1.29.10", "@deriv/api": "^1.0.0", - "@deriv-com/quill-ui": "1.13.23", + "@deriv-com/quill-ui": "1.13.42", "@deriv/components": "^1.0.0", "@deriv/hooks": "^1.0.0", "@deriv/integration": "1.0.0", diff --git a/packages/account/src/App.tsx b/packages/account/src/App.tsx index 8a3e72c4ebaf..bc4c391be62c 100644 --- a/packages/account/src/App.tsx +++ b/packages/account/src/App.tsx @@ -1,6 +1,6 @@ import Routes from './Containers/routes'; import ResetTradingPassword from './Containers/reset-trading-password'; -import { NetworkStatusToastErrorPopup } from './Containers/toast-popup'; +import NetworkStatusToastPopup from './Components/network-status-toast-popup/network-status-toast-popup'; import { APIProvider } from '@deriv/api'; import { StoreProvider } from '@deriv/stores'; import { TCoreStores } from '@deriv/stores/types'; @@ -21,7 +21,6 @@ const App = ({ passthrough }: TAppProps) => { return ( - {Notifications && } @@ -29,6 +28,7 @@ const App = ({ passthrough }: TAppProps) => { + ); }; diff --git a/packages/account/src/Components/account-limits/__tests__/account-limit-overlay.spec.tsx b/packages/account/src/Components/account-limits/__tests__/account-limit-overlay.spec.tsx index b61ef8b811ea..4d9feb8cad77 100644 --- a/packages/account/src/Components/account-limits/__tests__/account-limit-overlay.spec.tsx +++ b/packages/account/src/Components/account-limits/__tests__/account-limit-overlay.spec.tsx @@ -36,7 +36,7 @@ describe('', () => { it('should go to help-centre page if the Help Centre link on the text is clicked', () => { render(); - expect(screen.getByText('Help Centre').hasAttribute('href')); + expect(screen.getByText(/Help Centre/).hasAttribute('href')); }); it('should show Done Button', () => { render(); diff --git a/packages/account/src/Components/account-limits/__tests__/account-limits-turnover-limit-row.spec.tsx b/packages/account/src/Components/account-limits/__tests__/account-limits-turnover-limit-row.spec.tsx index 388d45caa98a..08897b992c8e 100644 --- a/packages/account/src/Components/account-limits/__tests__/account-limits-turnover-limit-row.spec.tsx +++ b/packages/account/src/Components/account-limits/__tests__/account-limits-turnover-limit-row.spec.tsx @@ -1,12 +1,14 @@ import React from 'react'; import { screen, render } from '@testing-library/react'; -import { formatMoney } from '@deriv/shared'; import AccountLimitsTurnoverLimitRow from '../account-limits-turnover-limit-row'; import AccountLimitsContext from '../account-limits-context'; +import { FormatUtils } from '@deriv-com/utils'; -jest.mock('@deriv/shared', () => ({ - ...jest.requireActual('@deriv/shared'), - formatMoney: jest.fn(), +jest.mock('@deriv-com/utils', () => ({ + ...jest.requireActual('@deriv-com/utils'), + FormatUtils: { + formatMoney: jest.fn(), + }, })); const AccountLimitsTurnoverLimitRowComponent = (props: React.ComponentProps) => ( @@ -47,6 +49,6 @@ describe('', () => { container: document.body.appendChild(document.createElement('tbody')), }); - expect(formatMoney).toHaveBeenCalledWith('AUD', 100000, true); + expect(FormatUtils.formatMoney).toHaveBeenCalledWith(100000, { currency: 'AUD' }); }); }); diff --git a/packages/account/src/Components/account-limits/__tests__/account-limits.spec.tsx b/packages/account/src/Components/account-limits/__tests__/account-limits.spec.tsx index 7ce758a1d55e..283149f1c575 100644 --- a/packages/account/src/Components/account-limits/__tests__/account-limits.spec.tsx +++ b/packages/account/src/Components/account-limits/__tests__/account-limits.spec.tsx @@ -1,10 +1,10 @@ import React from 'react'; import { screen, render } from '@testing-library/react'; -import { formatMoney } from '@deriv/shared'; import { useDevice } from '@deriv-com/ui'; import AccountLimits from '../account-limits'; import { BrowserRouter } from 'react-router-dom'; import { StoreProvider, mockStore } from '@deriv/stores'; +import { FormatUtils } from '@deriv-com/utils'; jest.mock('@deriv/components', () => { const original_module = jest.requireActual('@deriv/components'); @@ -25,9 +25,11 @@ jest.mock('@deriv-com/ui', () => ({ useDevice: jest.fn(() => ({ isDesktop: true })), })); -jest.mock('@deriv/shared', () => ({ - ...jest.requireActual('@deriv/shared'), - formatMoney: jest.fn(), +jest.mock('@deriv-com/utils', () => ({ + ...jest.requireActual('@deriv-com/utils'), + FormatUtils: { + formatMoney: jest.fn(), + }, })); jest.mock('Components/demo-message', () => jest.fn(() => 'mockedDemoMessage')); @@ -288,7 +290,7 @@ describe('', () => { ); const { account_balance } = store.client.account_limits; - expect(formatMoney).toHaveBeenCalledWith(store.client.currency, account_balance, true); + expect(FormatUtils.formatMoney).toHaveBeenCalledWith(account_balance, { currency: store.client.currency }); }); it('should render Trading limits table and its maximum daily turnover contents properly', () => { diff --git a/packages/account/src/Components/account-limits/account-limits-article.tsx b/packages/account/src/Components/account-limits/account-limits-article.tsx index 03a2fee6ce33..d435b0b6fab8 100644 --- a/packages/account/src/Components/account-limits/account-limits-article.tsx +++ b/packages/account/src/Components/account-limits/account-limits-article.tsx @@ -1,13 +1,13 @@ -import React from 'react'; -import { Localize, localize } from '@deriv/translations'; +import { Localize, useTranslations } from '@deriv-com/translations'; import AccountArticle from '../article'; const getDescription = () => [ , ]; -const AccountLimitsArticle = () => ( - -); +const AccountLimitsArticle = () => { + const { localize } = useTranslations(); + return ; +}; export default AccountLimitsArticle; diff --git a/packages/account/src/Components/account-limits/account-limits-context.tsx b/packages/account/src/Components/account-limits/account-limits-context.tsx index 65423704ad3d..467ac797742b 100644 --- a/packages/account/src/Components/account-limits/account-limits-context.tsx +++ b/packages/account/src/Components/account-limits/account-limits-context.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import { createContext } from 'react'; export type TAccountLimitsContext = { currency: string; @@ -7,7 +7,7 @@ export type TAccountLimitsContext = { toggleOverlay?: () => void; }; -const AccountLimitsContext = React.createContext({ +const AccountLimitsContext = createContext({ currency: '', overlay_ref: document.createElement('div'), }); diff --git a/packages/account/src/Components/account-limits/account-limits-extra-info.tsx b/packages/account/src/Components/account-limits/account-limits-extra-info.tsx index 07a1b632bd64..4c282b46e816 100644 --- a/packages/account/src/Components/account-limits/account-limits-extra-info.tsx +++ b/packages/account/src/Components/account-limits/account-limits-extra-info.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { Popover, Text } from '@deriv/components'; import { useDevice } from '@deriv-com/ui'; diff --git a/packages/account/src/Components/account-limits/account-limits-footer.tsx b/packages/account/src/Components/account-limits/account-limits-footer.tsx index 8b598e33af10..c111415b8363 100644 --- a/packages/account/src/Components/account-limits/account-limits-footer.tsx +++ b/packages/account/src/Components/account-limits/account-limits-footer.tsx @@ -1,11 +1,11 @@ -import React from 'react'; +import { useContext } from 'react'; import { createPortal } from 'react-dom'; import { Text } from '@deriv/components'; -import { Localize } from '@deriv/translations'; +import { Localize } from '@deriv-com/translations'; import AccountLimitsContext, { TAccountLimitsContext } from './account-limits-context'; const AccountLimitsFooterPortal = () => { - const { footer_ref, toggleOverlay } = React.useContext(AccountLimitsContext); + const { footer_ref, toggleOverlay } = useContext(AccountLimitsContext); return createPortal( diff --git a/packages/account/src/Components/account-limits/account-limits-overlay.tsx b/packages/account/src/Components/account-limits/account-limits-overlay.tsx index 4fd3c6547b76..ea31b7ee933d 100644 --- a/packages/account/src/Components/account-limits/account-limits-overlay.tsx +++ b/packages/account/src/Components/account-limits/account-limits-overlay.tsx @@ -1,10 +1,11 @@ -import React from 'react'; +import { useContext } from 'react'; import { Popup, StaticUrl } from '@deriv/components'; -import { Localize, localize } from '@deriv/translations'; +import { Localize, useTranslations } from '@deriv-com/translations'; import AccountLimitsContext from './account-limits-context'; const AccountLimitsOverlay = () => { - const { overlay_ref, toggleOverlay } = React.useContext(AccountLimitsContext); + const { localize } = useTranslations(); + const { overlay_ref, toggleOverlay } = useContext(AccountLimitsContext); return ( React.ReactElement; + renderExtraInfo: () => ReactElement; }; const AccountLimitsTableCell = ({ @@ -15,7 +15,7 @@ const AccountLimitsTableCell = ({ is_hint, level, renderExtraInfo, -}: React.PropsWithChildren>) => { +}: PropsWithChildren>) => { const text_size = is_hint ? 'xxxs' : 'xxs'; return ( diff --git a/packages/account/src/Components/account-limits/account-limits-table-header.tsx b/packages/account/src/Components/account-limits/account-limits-table-header.tsx index 96cef796795d..df453ad6cb5b 100644 --- a/packages/account/src/Components/account-limits/account-limits-table-header.tsx +++ b/packages/account/src/Components/account-limits/account-limits-table-header.tsx @@ -1,17 +1,17 @@ -import React from 'react'; +import { ReactNode, PropsWithChildren } from 'react'; import clsx from 'clsx'; import { Text } from '@deriv/components'; type TAccountLimitsTableHeader = { align: 'left' | 'right'; - renderExtraInfo: () => React.ReactNode; + renderExtraInfo: () => ReactNode; }; const AccountLimitsTableHeader = ({ align, children, renderExtraInfo, -}: React.PropsWithChildren>) => { +}: PropsWithChildren>) => { return ( { - const { currency } = React.useContext(AccountLimitsContext); + const { currency } = useContext(AccountLimitsContext); return ( - + {collection?.map(({ name, turnover_limit, level }) => ( @@ -28,11 +28,11 @@ const AccountLimitsTurnoverLimitRow = ({ collection, title }: TAccountLimitsTurn {name} - {formatMoney(currency, turnover_limit, true)} + {FormatUtils.formatMoney(turnover_limit, { currency: currency as CurrencyConstants.Currency })} ))} - + ); }; diff --git a/packages/account/src/Components/account-limits/account-limits.tsx b/packages/account/src/Components/account-limits/account-limits.tsx index faa0ee1df9fe..0142b02ba3b2 100644 --- a/packages/account/src/Components/account-limits/account-limits.tsx +++ b/packages/account/src/Components/account-limits/account-limits.tsx @@ -1,10 +1,11 @@ -import React from 'react'; +import { RefObject, useState, useEffect } from 'react'; import { FormikValues } from 'formik'; import clsx from 'clsx'; -import { formatMoney, useIsMounted } from '@deriv/shared'; +import { useIsMounted } from '@deriv/shared'; +import { FormatUtils, CurrencyConstants } from '@deriv-com/utils'; import { Loading, ThemedScrollbars } from '@deriv/components'; import { useDevice } from '@deriv-com/ui'; -import { Localize, localize } from '@deriv/translations'; +import { Localize, useTranslations } from '@deriv-com/translations'; import { observer, useStore } from '@deriv/stores'; import DemoMessage from '../demo-message'; import LoadErrorMessage from '../load-error-message'; @@ -19,7 +20,7 @@ import AccountLimitsTurnoverLimitRow, { TAccountLimitsCollection } from './accou import WithdrawalLimitsTable from './withdrawal-limits-table'; type TAccountLimits = { - footer_ref?: React.RefObject; + footer_ref?: RefObject; is_app_settings?: boolean; overlay_ref: HTMLDivElement; setIsOverlayShown?: (is_overlay_shown?: boolean) => void; @@ -37,18 +38,19 @@ const AccountLimits = observer( should_bypass_scrollbars, should_show_article = true, }: TAccountLimits) => { + const { localize } = useTranslations(); const { client } = useStore(); const { account_limits, account_status, currency, getLimits, is_virtual, is_switching } = client; const isMounted = useIsMounted(); - const [is_loading, setLoading] = React.useState(true); - const [is_overlay_shown, setIsOverlayShown] = React.useState(false); + const [is_loading, setLoading] = useState(true); + const [is_overlay_shown, setIsOverlayShown] = useState(false); const { isDesktop } = useDevice(); const handleGetLimitsResponse = () => { if (isMounted()) setLoading(false); }; - React.useEffect(() => { + useEffect(() => { if (is_virtual) { setLoading(false); } else { @@ -57,13 +59,13 @@ const AccountLimits = observer( // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - React.useEffect(() => { + useEffect(() => { if (!is_virtual && account_limits && is_loading && Object.keys(account_status).length > 0) { setLoading(false); } }, [account_limits, is_virtual, is_loading, account_status]); - React.useEffect(() => { + useEffect(() => { if (typeof setIsPopupOverlayShown === 'function') { setIsPopupOverlayShown(is_overlay_shown); } @@ -165,7 +167,9 @@ const AccountLimits = observer( {/* null or 0 are expected form BE when max balance limit is not set */} {account_balance ? ( - formatMoney(currency, account_balance, true) + FormatUtils.formatMoney(account_balance, { + currency: currency as CurrencyConstants.Currency, + }) ) : ( )} @@ -184,7 +188,9 @@ const AccountLimits = observer( - {formatMoney(currency, payout as number, true)} + {FormatUtils.formatMoney(payout as number, { + currency: currency as CurrencyConstants.Currency, + })} diff --git a/packages/account/src/Components/account-limits/withdrawal-limits-table.tsx b/packages/account/src/Components/account-limits/withdrawal-limits-table.tsx index 693dacc962b6..d25ca4eb36da 100644 --- a/packages/account/src/Components/account-limits/withdrawal-limits-table.tsx +++ b/packages/account/src/Components/account-limits/withdrawal-limits-table.tsx @@ -1,6 +1,6 @@ -import React from 'react'; +import { Fragment } from 'react'; import { Text } from '@deriv/components'; -import { formatMoney } from '@deriv/shared'; +import { FormatUtils, CurrencyConstants } from '@deriv-com/utils'; import { Localize } from '@deriv/translations'; import { observer, useStore } from '@deriv/stores'; import AccountLimitsTableCell from './account-limits-table-cell'; @@ -17,7 +17,7 @@ const WithdrawalLimitsTable = observer( const { client } = useStore(); const { currency, is_fully_authenticated } = client; return ( - + @@ -33,13 +33,15 @@ const WithdrawalLimitsTable = observer( {!is_fully_authenticated && ( - + - {formatMoney(currency, num_of_days_limit ?? 0, true)} + {FormatUtils.formatMoney((num_of_days_limit as number) ?? 0, { + currency: currency as CurrencyConstants.Currency, + })} @@ -47,7 +49,9 @@ const WithdrawalLimitsTable = observer( - {formatMoney(currency, withdrawal_since_inception_monetary ?? 0, true)} + {FormatUtils.formatMoney((withdrawal_since_inception_monetary as number) ?? 0, { + currency: currency as CurrencyConstants.Currency, + })} @@ -55,10 +59,12 @@ const WithdrawalLimitsTable = observer( - {formatMoney(currency, remainder ?? '', true)} + {FormatUtils.formatMoney((remainder as number) ?? 0, { + currency: currency as CurrencyConstants.Currency, + })} - + )}
@@ -71,7 +77,7 @@ const WithdrawalLimitsTable = observer( )} -
+ ); } ); diff --git a/packages/account/src/Components/api-token/api-token-article.tsx b/packages/account/src/Components/api-token/api-token-article.tsx index 00a86872244d..772268f31a2e 100644 --- a/packages/account/src/Components/api-token/api-token-article.tsx +++ b/packages/account/src/Components/api-token/api-token-article.tsx @@ -1,5 +1,4 @@ -import React from 'react'; -import { Localize } from '@deriv/translations'; +import { Localize } from '@deriv-com/translations'; import AccountArticle from '../article'; const ApiTokenArticle = () => ( diff --git a/packages/account/src/Components/api-token/api-token-card.tsx b/packages/account/src/Components/api-token/api-token-card.tsx index 72184ec1dd4a..f9d6cae3e6ed 100644 --- a/packages/account/src/Components/api-token/api-token-card.tsx +++ b/packages/account/src/Components/api-token/api-token-card.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import { PropsWithChildren } from 'react'; import { Field, FieldProps } from 'formik'; import { CompositeCheckbox } from '@deriv/components'; @@ -8,7 +8,7 @@ type TApiTokenCard = { name: string; }; -const ApiTokenCard = ({ name, display_name, description, children }: React.PropsWithChildren) => { +const ApiTokenCard = ({ name, display_name, description, children }: PropsWithChildren) => { return ( {({ field, form: { setFieldValue } }: FieldProps) => ( diff --git a/packages/account/src/Components/api-token/api-token-clipboard.tsx b/packages/account/src/Components/api-token/api-token-clipboard.tsx index cbdc68425fd2..54a07a5e85ef 100644 --- a/packages/account/src/Components/api-token/api-token-clipboard.tsx +++ b/packages/account/src/Components/api-token/api-token-clipboard.tsx @@ -1,7 +1,7 @@ -import React from 'react'; +import { Fragment, useState, useEffect } from 'react'; import { useIsMounted } from '@deriv/shared'; import { Button, Icon, Modal, Text, Popover, useCopyToClipboard } from '@deriv/components'; -import { Localize } from '@deriv/translations'; +import { Localize } from '@deriv-com/translations'; import { TPopoverAlignment } from '../../Types'; type TApiTokenClipboard = { @@ -26,7 +26,7 @@ const WarningNoteBullet = ({ message }: TWarningNoteBullet) => ( ); const WarningDialogMessage = () => ( - + @@ -37,7 +37,7 @@ const WarningDialogMessage = () => ( /> } /> - + ); const ApiTokenClipboard = ({ @@ -48,8 +48,8 @@ const ApiTokenClipboard = ({ popover_alignment = 'bottom', }: TApiTokenClipboard) => { const [is_copied, copyToClipboard, setIsCopied] = useCopyToClipboard(); - const [is_modal_open, setIsModalOpen] = React.useState(false); - const [is_popover_open, setIsPopoverOpen] = React.useState(false); + const [is_modal_open, setIsModalOpen] = useState(false); + const [is_popover_open, setIsPopoverOpen] = useState(false); const isMounted = useIsMounted(); let timeout_clipboard: NodeJS.Timeout | undefined, timeout_clipboard_2: NodeJS.Timeout | undefined; const has_admin_scope = scopes?.includes('Admin'); @@ -88,7 +88,7 @@ const ApiTokenClipboard = ({ } else onClick(); }; - React.useEffect(() => { + useEffect(() => { return () => { clearTimeout(timeout_clipboard); clearTimeout(timeout_clipboard_2); @@ -96,7 +96,7 @@ const ApiTokenClipboard = ({ }, [timeout_clipboard, timeout_clipboard_2]); return ( - + @@ -125,7 +125,7 @@ const ApiTokenClipboard = ({ onMouseLeave={onMouseLeaveHandler} /> - + ); }; diff --git a/packages/account/src/Components/api-token/api-token-context.ts b/packages/account/src/Components/api-token/api-token-context.ts index 84465b0a9705..ae61881e6c36 100644 --- a/packages/account/src/Components/api-token/api-token-context.ts +++ b/packages/account/src/Components/api-token/api-token-context.ts @@ -1,7 +1,7 @@ -import React from 'react'; +import { createContext } from 'react'; import { TApiContext } from '../../Types'; -const ApiTokenContext = React.createContext({ +const ApiTokenContext = createContext({ api_tokens: [], deleteToken: () => Promise.resolve(), }); diff --git a/packages/account/src/Components/api-token/api-token-delete-button.tsx b/packages/account/src/Components/api-token/api-token-delete-button.tsx index 4f036a8a23a2..bcb9f5a5ca4f 100644 --- a/packages/account/src/Components/api-token/api-token-delete-button.tsx +++ b/packages/account/src/Components/api-token/api-token-delete-button.tsx @@ -1,7 +1,7 @@ -import React from 'react'; +import { useContext, useState, Fragment } from 'react'; import { Button, Icon, Modal, Text, Popover } from '@deriv/components'; import { useIsMounted } from '@deriv/shared'; -import { Localize } from '@deriv/translations'; +import { Localize } from '@deriv-com/translations'; import ApiTokenContext from './api-token-context'; import { TPopoverAlignment, TFormattedToken, TApiContext } from '../../Types'; import { useDevice } from '@deriv-com/ui'; @@ -13,10 +13,10 @@ type TApiTokenDeleteButton = { const ApiTokenDeleteButton = ({ token, popover_alignment = 'left' }: TApiTokenDeleteButton) => { const { isDesktop } = useDevice(); - const { deleteToken } = React.useContext(ApiTokenContext); - const [is_deleting, setIsDeleting] = React.useState(false); - const [is_loading, setIsLoading] = React.useState(false); - const [is_popover_open, setIsPopoverOpen] = React.useState(false); + const { deleteToken } = useContext(ApiTokenContext); + const [is_deleting, setIsDeleting] = useState(false); + const [is_loading, setIsLoading] = useState(false); + const [is_popover_open, setIsPopoverOpen] = useState(false); const isMounted = useIsMounted(); const getConfirmationBeforeDelete = () => { @@ -44,7 +44,7 @@ const ApiTokenDeleteButton = ({ token, popover_alignment = 'left' }: TApiTokenDe }; return ( - + @@ -89,7 +89,7 @@ const ApiTokenDeleteButton = ({ token, popover_alignment = 'left' }: TApiTokenDe onMouseLeave={onMouseLeaveHandler} /> - + ); }; diff --git a/packages/account/src/Components/api-token/api-token-table-row-cell.tsx b/packages/account/src/Components/api-token/api-token-table-row-cell.tsx index a5e2f7faedba..bd0a797d58e7 100644 --- a/packages/account/src/Components/api-token/api-token-table-row-cell.tsx +++ b/packages/account/src/Components/api-token/api-token-table-row-cell.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import { PropsWithChildren } from 'react'; import clsx from 'clsx'; import { Text } from '@deriv/components'; @@ -11,7 +11,7 @@ const ApiTokenTableRowCell = ({ className, children, should_bypass_text, -}: React.PropsWithChildren>) => { +}: PropsWithChildren>) => { if (should_bypass_text) { return {children}; } diff --git a/packages/account/src/Components/api-token/api-token-table-row-header.tsx b/packages/account/src/Components/api-token/api-token-table-row-header.tsx index 4e8166cc233a..2a1f8542d437 100644 --- a/packages/account/src/Components/api-token/api-token-table-row-header.tsx +++ b/packages/account/src/Components/api-token/api-token-table-row-header.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { Text } from '@deriv/components'; type TApiTokenTableRowHeader = { diff --git a/packages/account/src/Components/api-token/api-token-table-row-token-cell.tsx b/packages/account/src/Components/api-token/api-token-table-row-token-cell.tsx index 581d7cfb800e..c61ab1d25a54 100644 --- a/packages/account/src/Components/api-token/api-token-table-row-token-cell.tsx +++ b/packages/account/src/Components/api-token/api-token-table-row-token-cell.tsx @@ -1,6 +1,6 @@ -import React from 'react'; +import { useState } from 'react'; import { Icon, Text, Popover } from '@deriv/components'; -import { Localize } from '@deriv/translations'; +import { Localize } from '@deriv-com/translations'; import ApiTokenClipboard from './api-token-clipboard'; type TApiTokenTableRowTokenCell = { @@ -17,7 +17,7 @@ const HiddenPasswordDots = () => ( ); const ApiTokenTableRowTokenCell = ({ token, scopes }: TApiTokenTableRowTokenCell) => { - const [should_show_token, setShouldShowToken] = React.useState(false); + const [should_show_token, setShouldShowToken] = useState(false); const toggleTokenVisibility = () => { setShouldShowToken(prev_value => !prev_value); diff --git a/packages/account/src/Components/api-token/api-token-table.tsx b/packages/account/src/Components/api-token/api-token-table.tsx index 379260ae15b1..9763c22a0e73 100644 --- a/packages/account/src/Components/api-token/api-token-table.tsx +++ b/packages/account/src/Components/api-token/api-token-table.tsx @@ -1,7 +1,7 @@ -import React from 'react'; +import { useContext, Fragment } from 'react'; import { Text } from '@deriv/components'; import { formatDate } from '@deriv/shared'; -import { Localize, localize } from '@deriv/translations'; +import { Localize, useTranslations } from '@deriv-com/translations'; import { useDevice } from '@deriv-com/ui'; import ApiTokenContext from './api-token-context'; import ApiTokenDeleteButton from './api-token-delete-button'; @@ -12,8 +12,9 @@ import ApiTokenTableRowTokenCell from './api-token-table-row-token-cell'; import { TApiContext, TToken } from '../../Types'; const ApiTokenTable = () => { - const { api_tokens } = React.useContext(ApiTokenContext); + const { api_tokens } = useContext(ApiTokenContext); const { isDesktop } = useDevice(); + const { localize } = useTranslations(); const formatTokenScopes = (str: string) => { const replace_filter = str.replace(/[-_]/g, ' '); @@ -51,7 +52,7 @@ const ApiTokenTable = () => { }; if (!isDesktop) { return ( - + {api_tokens?.map((token_data: TToken) => { const token = getScopeValue(token_data); return ( @@ -102,7 +103,7 @@ const ApiTokenTable = () => { ); })} - + ); } diff --git a/packages/account/src/Components/article/article.tsx b/packages/account/src/Components/article/article.tsx index 7f72feead6e6..fff2b6ea9b52 100644 --- a/packages/account/src/Components/article/article.tsx +++ b/packages/account/src/Components/article/article.tsx @@ -1,12 +1,12 @@ -import React from 'react'; +import { ReactNode, Fragment } from 'react'; import { Icon, Text } from '@deriv/components'; -import { Localize } from '@deriv/translations'; +import { Localize } from '@deriv-com/translations'; import './article.scss'; import clsx from 'clsx'; export type TArticle = { title: JSX.Element | string; - descriptions: Array; + descriptions: Array; onClickLearnMore?: () => void; className?: string; }; @@ -21,7 +21,7 @@ const Article = ({ title, descriptions, onClickLearnMore, className }: TArticle) {title} {has_descriptions && ( - + {has_single_description ? ( {descriptions[0]} @@ -37,7 +37,7 @@ const Article = ({ title, descriptions, onClickLearnMore, className }: TArticle) ))} )} - + )} {onClickLearnMore && (
diff --git a/packages/account/src/Components/demo-message/__test__/demo-message.spec.tsx b/packages/account/src/Components/demo-message/__tests__/demo-message.spec.tsx similarity index 100% rename from packages/account/src/Components/demo-message/__test__/demo-message.spec.tsx rename to packages/account/src/Components/demo-message/__tests__/demo-message.spec.tsx diff --git a/packages/account/src/Components/demo-message/demo-message.tsx b/packages/account/src/Components/demo-message/demo-message.tsx index 43588e720d5c..fe409688d96a 100644 --- a/packages/account/src/Components/demo-message/demo-message.tsx +++ b/packages/account/src/Components/demo-message/demo-message.tsx @@ -1,16 +1,19 @@ -import { localize } from '@deriv/translations'; +import { useTranslations } from '@deriv-com/translations'; import IconWithMessage from '../icon-with-message'; type TDemoMessage = { has_button?: boolean; }; -const DemoMessage = ({ has_button }: TDemoMessage) => ( - -); +const DemoMessage = ({ has_button }: TDemoMessage) => { + const { localize } = useTranslations(); + return ( + + ); +}; export default DemoMessage; diff --git a/packages/account/src/Components/error-component/error-component.spec.tsx b/packages/account/src/Components/error-component/__tests__/error-component.spec.tsx similarity index 89% rename from packages/account/src/Components/error-component/error-component.spec.tsx rename to packages/account/src/Components/error-component/__tests__/error-component.spec.tsx index 8c7091e8656b..9c7bd47ed4be 100644 --- a/packages/account/src/Components/error-component/error-component.spec.tsx +++ b/packages/account/src/Components/error-component/__tests__/error-component.spec.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { screen, render } from '@testing-library/react'; -import ErrorComponent from './error-component'; +import ErrorComponent from '../error-component'; jest.mock('@deriv/components', () => ({ ...jest.requireActual('@deriv/components'), diff --git a/packages/account/src/Components/error-component/error-component.tsx b/packages/account/src/Components/error-component/error-component.tsx index 5f8dd903d2d2..b7795ac85fd3 100644 --- a/packages/account/src/Components/error-component/error-component.tsx +++ b/packages/account/src/Components/error-component/error-component.tsx @@ -1,6 +1,5 @@ -import React from 'react'; import { PageError } from '@deriv/components'; -import { Localize } from '@deriv/translations'; +import { Localize } from '@deriv-com/translations'; import { routes } from '@deriv/shared'; type TErrorComponent = { diff --git a/packages/account/src/Components/error-component/index.js b/packages/account/src/Components/error-component/index.js deleted file mode 100644 index d25ba71f1f18..000000000000 --- a/packages/account/src/Components/error-component/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import ErrorComponent from './error-component.tsx'; - -export default ErrorComponent; diff --git a/packages/account/src/Components/error-component/index.ts b/packages/account/src/Components/error-component/index.ts new file mode 100644 index 000000000000..8585809410a1 --- /dev/null +++ b/packages/account/src/Components/error-component/index.ts @@ -0,0 +1,3 @@ +import ErrorComponent from './error-component'; + +export default ErrorComponent; diff --git a/packages/account/src/Components/network-status-toast-popup/__tests__/network-status-toast-popup.spec.tsx b/packages/account/src/Components/network-status-toast-popup/__tests__/network-status-toast-popup.spec.tsx new file mode 100644 index 000000000000..bc83c538316a --- /dev/null +++ b/packages/account/src/Components/network-status-toast-popup/__tests__/network-status-toast-popup.spec.tsx @@ -0,0 +1,135 @@ +import { act, render, screen } from '@testing-library/react'; +import { mockStore, StoreProvider } from '@deriv/stores'; +import { useDevice } from '@deriv-com/ui'; +import NetworkStatusToastPopup from '../network-status-toast-popup'; + +jest.mock('@deriv-com/ui', () => ({ + ...jest.requireActual('@deriv-com/ui'), + useDevice: jest.fn(() => ({ isMobile: true })), +})); + +jest.mock('@deriv-com/quill-ui', () => ({ + Snackbar: jest.fn(({ message, isVisible }) => (isVisible ?
{message}
: null)), +})); + +describe('NetworkStatusToastPopup', () => { + const offline = 'Offline'; + const online = 'Online'; + const connecting = 'Connecting to server'; + + let popup_root_el: Element; + + beforeAll(() => { + popup_root_el = document.createElement('div'); + popup_root_el.setAttribute('id', 'popup_root'); + document.body.appendChild(popup_root_el); + }); + + afterAll(() => { + document.body.removeChild(popup_root_el); + }); + + const mock_store = mockStore({ + common: { network_status: { class: offline.toLowerCase(), tooltip: offline } }, + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + + it('should render NetworkStatusToastPopup when offline', () => { + render(, { wrapper }); + + expect(screen.queryByText(connecting)).not.toBeInTheDocument(); + expect(screen.queryByText(online)).not.toBeInTheDocument(); + expect(screen.getByText(offline)).toBeInTheDocument(); + }); + + it('should render NetworkStatusToastPopup when trying to connect', async () => { + jest.useFakeTimers(); + + mock_store.common.network_status.class = connecting.toLowerCase(); + mock_store.common.network_status.tooltip = connecting; + + const { rerender } = render( + + + + ); + + expect(screen.queryByText(offline)).not.toBeInTheDocument(); + expect(screen.queryByText(online)).not.toBeInTheDocument(); + expect(screen.getByText(connecting)).toBeInTheDocument(); + + const mock_store_updated = mockStore({ + common: { network_status: { class: online.toLowerCase(), tooltip: online } }, + }); + + rerender( + + + + ); + + expect(screen.queryByText(offline)).not.toBeInTheDocument(); + expect(screen.queryByText(connecting)).not.toBeInTheDocument(); + expect(screen.getByText(online)).toBeInTheDocument(); + + act(() => { + jest.advanceTimersByTime(1500); + }); + + expect(screen.queryByText(offline)).not.toBeInTheDocument(); + expect(screen.queryByText(connecting)).not.toBeInTheDocument(); + expect(screen.queryByText(online)).not.toBeInTheDocument(); + + jest.useRealTimers(); + }); + + it('should not render NetworkStatusToastPopup when no message', () => { + mock_store.common.network_status.class = offline.toLowerCase(); + mock_store.common.network_status.tooltip = ''; + + render(, { wrapper }); + + expect(screen.queryByText(offline)).not.toBeInTheDocument(); + expect(screen.queryByText(connecting)).not.toBeInTheDocument(); + expect(screen.queryByText(online)).not.toBeInTheDocument(); + }); + + it('should not render NetworkStatusToastPopup for desktop', () => { + (useDevice as jest.Mock).mockReturnValue({ + isDesktop: true, + isTablet: false, + isMobile: false, + }); + mock_store.common.network_status.class = offline.toLowerCase(); + mock_store.common.network_status.tooltip = offline; + + render(, { wrapper }); + + expect(screen.queryByText(offline)).not.toBeInTheDocument(); + expect(screen.queryByText(connecting)).not.toBeInTheDocument(); + expect(screen.queryByText(online)).not.toBeInTheDocument(); + }); + + it('should not render NetworkStatusToastPopup for tablet', () => { + (useDevice as jest.Mock).mockReturnValue({ + isDesktop: false, + isTablet: true, + isMobile: false, + }); + mock_store.common.network_status.class = offline.toLowerCase(); + mock_store.common.network_status.tooltip = offline; + + render(, { wrapper }); + + expect(screen.queryByText(offline)).not.toBeInTheDocument(); + expect(screen.queryByText(connecting)).not.toBeInTheDocument(); + expect(screen.queryByText(online)).not.toBeInTheDocument(); + }); +}); diff --git a/packages/account/src/Components/network-status-toast-popup/index.ts b/packages/account/src/Components/network-status-toast-popup/index.ts new file mode 100644 index 000000000000..62b0df54d854 --- /dev/null +++ b/packages/account/src/Components/network-status-toast-popup/index.ts @@ -0,0 +1,3 @@ +import NetworkStatusToastPopup from './network-status-toast-popup'; + +export default NetworkStatusToastPopup; diff --git a/packages/account/src/Components/network-status-toast-popup/network-status-toast-popup.scss b/packages/account/src/Components/network-status-toast-popup/network-status-toast-popup.scss new file mode 100644 index 000000000000..0a9a8ce79d98 --- /dev/null +++ b/packages/account/src/Components/network-status-toast-popup/network-status-toast-popup.scss @@ -0,0 +1,9 @@ +.network-status { + &__container { + .snackbar { + //center fixed element with dynamic width + left: 50%; + transform: translateX(-50%); + } + } +} diff --git a/packages/account/src/Components/network-status-toast-popup/network-status-toast-popup.tsx b/packages/account/src/Components/network-status-toast-popup/network-status-toast-popup.tsx new file mode 100644 index 000000000000..e6cdafe80004 --- /dev/null +++ b/packages/account/src/Components/network-status-toast-popup/network-status-toast-popup.tsx @@ -0,0 +1,47 @@ +import { useEffect, useLayoutEffect, useState } from 'react'; +import { createPortal } from 'react-dom'; +import { observer, useStore } from '@deriv/stores'; +import { useDevice } from '@deriv-com/ui'; +import { Snackbar } from '@deriv-com/quill-ui'; +import './network-status-toast-popup.scss'; + +type TNetworkStatusToast = { + status: string; + portal_id: string; + message: string; +}; + +const NetworkStatusToast = ({ status, portal_id, message }: TNetworkStatusToast) => { + const [is_visible, setIsVisible] = useState(false); + const { isMobile } = useDevice(); + const new_portal_id = document.getElementById(portal_id); + + useEffect(() => { + if (!new_portal_id || !message || !isMobile) return; + if (!is_visible && status !== 'online') { + setIsVisible(true); + } else if (is_visible && status === 'online') { + setTimeout(() => { + setIsVisible(false); + }, 1500); + } + }, [isMobile, is_visible, message, new_portal_id, status]); + + if (!new_portal_id || !message || !isMobile) return null; + + return createPortal( +
+ +
, + new_portal_id + ); +}; + +const NetworkStatusToastPopup = observer(() => { + const { + common: { network_status }, + } = useStore(); + return ; +}); + +export default NetworkStatusToastPopup; diff --git a/packages/account/src/Components/self-exclusion/self-exclusion-article-content.tsx b/packages/account/src/Components/self-exclusion/self-exclusion-article-content.tsx index 32eacf25b05e..91d9f1696a3c 100644 --- a/packages/account/src/Components/self-exclusion/self-exclusion-article-content.tsx +++ b/packages/account/src/Components/self-exclusion/self-exclusion-article-content.tsx @@ -1,7 +1,7 @@ +import { useContext } from 'react'; import clsx from 'clsx'; -import React from 'react'; -import { getStaticUrl } from '@deriv/shared'; -import { Localize, localize } from '@deriv/translations'; +import { URLUtils } from '@deriv-com/utils'; +import { Localize, useTranslations } from '@deriv-com/translations'; import { Button, Icon, OpenLiveChatLink, Popup, Text } from '@deriv/components'; import SelfExclusionContext from './self-exclusion-context'; @@ -24,7 +24,7 @@ export const selfExclusionArticleItems = ({ is_eu, is_app_settings }: TSelfExclu className='link' rel='noopener noreferrer' target='_blank' - href={getStaticUrl('/responsible')} + href={URLUtils.getDerivStaticURL('/responsible')} />, ]} /> @@ -56,7 +56,7 @@ export const selfExclusionArticleItems = ({ is_eu, is_app_settings }: TSelfExclu className='link' rel='noopener noreferrer' target='_blank' - href={getStaticUrl('/contact_us')} + href={URLUtils.getDerivStaticURL('/contact_us')} />, ]} /> @@ -78,7 +78,7 @@ export const selfExclusionArticleItems = ({ is_eu, is_app_settings }: TSelfExclu className='link' rel='noopener noreferrer' target='_blank' - href={getStaticUrl('/responsible')} + href={URLUtils.getDerivStaticURL('/responsible')} />, ]} /> @@ -119,7 +119,8 @@ export const selfExclusionArticleItems = ({ is_eu, is_app_settings }: TSelfExclu }; const SelfExclusionArticleContent = ({ is_in_overlay }: Partial) => { - const { is_app_settings, toggleArticle, overlay_ref, is_eu } = React.useContext(SelfExclusionContext); + const { is_app_settings, toggleArticle, overlay_ref, is_eu } = useContext(SelfExclusionContext); + const { localize } = useTranslations(); const keyed_article_items = selfExclusionArticleItems({ is_eu, is_app_settings }); if (is_in_overlay) { diff --git a/packages/account/src/Components/self-exclusion/self-exclusion-confirm-page.tsx b/packages/account/src/Components/self-exclusion/self-exclusion-confirm-page.tsx index 78a7953b032c..92e8accd01ca 100644 --- a/packages/account/src/Components/self-exclusion/self-exclusion-confirm-page.tsx +++ b/packages/account/src/Components/self-exclusion/self-exclusion-confirm-page.tsx @@ -1,22 +1,24 @@ -import React from 'react'; +import { useContext, Fragment } from 'react'; import { Button, Icon, StaticUrl, Text } from '@deriv/components'; import { FormikValues, useFormikContext } from 'formik'; -import { formatMoney, toMoment } from '@deriv/shared'; -import { Localize, localize } from '@deriv/translations'; +import { toMoment } from '@deriv/shared'; +import { FormatUtils, CurrencyConstants } from '@deriv-com/utils'; +import { Localize, useTranslations } from '@deriv-com/translations'; import SelfExclusionContext from './self-exclusion-context'; import SelfExclusionConfirmLimits from './self-exclusion-confirm-limits'; const SelfExclusionConfirmPage = () => { const { backFromConfirmLimits, currency, currency_display, exclusion_texts, is_eu, state } = - React.useContext(SelfExclusionContext); + useContext(SelfExclusionContext); const { isSubmitting, values } = useFormikContext(); + const { localize } = useTranslations(); if (state?.show_confirm) { return ; } return ( - +
@@ -57,7 +59,9 @@ const SelfExclusionConfirmPage = () => { if (need_date_format.includes(key)) { value = toMoment(values[key]).format('DD/MM/YYYY'); } else if (need_money_format.includes(key)) { - value = `${formatMoney(currency, +values[key], true)} ${currency_display}`; + value = `${FormatUtils.formatMoney(+values[key], { + currency: currency as CurrencyConstants.Currency, + })} ${currency_display}`; } else if (need_minutes.includes(key)) { value = localize('{{value}} mins', { value: values[key] }); } else if (need_amount.includes(key)) { @@ -106,7 +110,7 @@ const SelfExclusionConfirmPage = () => { )}
-
+ ); }; diff --git a/packages/account/src/Components/self-exclusion/self-exclusion-context.tsx b/packages/account/src/Components/self-exclusion/self-exclusion-context.tsx index 7416191b2467..4e3b285489ba 100644 --- a/packages/account/src/Components/self-exclusion/self-exclusion-context.tsx +++ b/packages/account/src/Components/self-exclusion/self-exclusion-context.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import { createContext } from 'react'; import { FormikHelpers, FormikValues } from 'formik'; export type TSelfExclusionContext = { @@ -23,7 +23,7 @@ export type TSelfExclusionContext = { backToReview?: () => void; }; -const SelfExclusionContext = React.createContext({ +const SelfExclusionContext = createContext({ overlay_ref: document.createElement('div'), currency: '', handleSubmit: () => null, diff --git a/packages/account/src/Components/self-exclusion/self-exclusion-footer.tsx b/packages/account/src/Components/self-exclusion/self-exclusion-footer.tsx index 9a8a27d6fac1..3ad139dacfcf 100644 --- a/packages/account/src/Components/self-exclusion/self-exclusion-footer.tsx +++ b/packages/account/src/Components/self-exclusion/self-exclusion-footer.tsx @@ -1,17 +1,17 @@ -import React from 'react'; +import { useContext, Fragment } from 'react'; import { createPortal } from 'react-dom'; import { FormikValues, useFormikContext } from 'formik'; import { Button, Text } from '@deriv/components'; -import { Localize } from '@deriv/translations'; +import { Localize } from '@deriv-com/translations'; import SelfExclusionContext from './self-exclusion-context'; const SelfExclusionFooter = () => { - const { footer_ref, goToConfirm, toggleArticle } = React.useContext(SelfExclusionContext); + const { footer_ref, goToConfirm, toggleArticle } = useContext(SelfExclusionContext); const { dirty, isSubmitting, isValid, values } = useFormikContext(); if (footer_ref) { return createPortal( - <> +
@@ -27,7 +27,7 @@ const SelfExclusionFooter = () => { > - , + , footer_ref ); } diff --git a/packages/account/src/Components/self-exclusion/self-exclusion-form.tsx b/packages/account/src/Components/self-exclusion/self-exclusion-form.tsx index b60cf260e497..5ad052078b73 100644 --- a/packages/account/src/Components/self-exclusion/self-exclusion-form.tsx +++ b/packages/account/src/Components/self-exclusion/self-exclusion-form.tsx @@ -1,11 +1,11 @@ -import React from 'react'; +import { useContext } from 'react'; import { Form, Formik } from 'formik'; import SelfExclusionContext from './self-exclusion-context'; import SelfExclusionConfirmPage from './self-exclusion-confirm-page'; import SelfExclusionInputs from './self-exclusion-inputs'; const SelfExclusionForm = () => { - const { handleSubmit, state, validateFields } = React.useContext(SelfExclusionContext); + const { handleSubmit, state, validateFields } = useContext(SelfExclusionContext); return ( diff --git a/packages/account/src/Components/self-exclusion/self-exclusion-inputs.tsx b/packages/account/src/Components/self-exclusion/self-exclusion-inputs.tsx index 98776d6a22d3..732b19340446 100644 --- a/packages/account/src/Components/self-exclusion/self-exclusion-inputs.tsx +++ b/packages/account/src/Components/self-exclusion/self-exclusion-inputs.tsx @@ -1,9 +1,9 @@ -import React from 'react'; +import { ReactElement, useContext, Fragment, FunctionComponent, ChangeEvent } from 'react'; import clsx from 'clsx'; import { Button, DatePicker, Input, Text } from '@deriv/components'; import { epochToMoment, toMoment } from '@deriv/shared'; import { useDevice } from '@deriv-com/ui'; -import { Localize, localize } from '@deriv/translations'; +import { Localize, useTranslations } from '@deriv-com/translations'; import { Field, FormikComputedProps, @@ -18,7 +18,7 @@ import SelfExclusionContext from './self-exclusion-context'; import SelfExclusionFooter from './self-exclusion-footer'; type TSectionTitle = { - title: React.ReactElement; + title: ReactElement; has_border_line?: boolean; }; @@ -50,10 +50,10 @@ const SectionTitle = ({ title, has_border_line }: TSectionTitle) => { }; const StakeLossAndLimitsInputs = () => { - const { currency_display, getMaxLength } = React.useContext(SelfExclusionContext); + const { currency_display, getMaxLength } = useContext(SelfExclusionContext); const { errors, handleBlur, handleChange, values }: TFormikContext = useFormikContext(); return ( - + } />
@@ -186,17 +186,18 @@ const StakeLossAndLimitsInputs = () => {
-
+ ); }; const SessionAndLoginLimitsInputs = () => { - const { is_tablet, session_duration_digits } = React.useContext(SelfExclusionContext); + const { is_tablet, session_duration_digits } = useContext(SelfExclusionContext); const { errors, handleBlur, handleChange, setFieldValue, values }: TFormikContext = useFormikContext(); + const { localize } = useTranslations(); return ( - + } />
@@ -233,7 +234,7 @@ const SessionAndLoginLimitsInputs = () => { className='da-self-exclusion__input' label={localize('Date')} value={values.timeout_until && epochToMoment(values.timeout_until)} - onChange={({ target }: React.ChangeEvent) => + onChange={({ target }: ChangeEvent) => setFieldValue( 'timeout_until', target?.value ? toMoment(target.value).unix() : '', @@ -261,7 +262,7 @@ const SessionAndLoginLimitsInputs = () => { className='da-self-exclusion__input' label={localize('Date')} value={values.exclude_until} - onChange={({ target }: React.ChangeEvent) => + onChange={({ target }: ChangeEvent) => setFieldValue( 'exclude_until', target?.value ? toMoment(target.value).format('YYYY-MM-DD') : '', @@ -277,16 +278,17 @@ const SessionAndLoginLimitsInputs = () => {
-
+ ); }; const MaximumAccountBalanceAndOpenPositionsInputs = () => { - const { currency_display, getMaxLength } = React.useContext(SelfExclusionContext); + const { currency_display, getMaxLength } = useContext(SelfExclusionContext); const { errors, handleBlur, handleChange, values }: TFormikContext = useFormikContext(); + const { localize } = useTranslations(); return ( - + } />
@@ -332,12 +334,12 @@ const MaximumAccountBalanceAndOpenPositionsInputs = () => {
-
+ ); }; const MaximumDepositLimitInputs = () => { - const { currency, is_mf, getMaxLength } = React.useContext(SelfExclusionContext); + const { currency, is_mf, getMaxLength } = useContext(SelfExclusionContext); const { errors, handleBlur, handleChange, values }: TFormikContext = useFormikContext(); if (!is_mf) { @@ -345,7 +347,7 @@ const MaximumDepositLimitInputs = () => { } return ( - + } />
@@ -421,14 +423,14 @@ const MaximumDepositLimitInputs = () => {
-
+ ); }; const SelfExclusionInputs = () => { - const { footer_ref, goToConfirm, is_app_settings } = React.useContext(SelfExclusionContext); + const { footer_ref, goToConfirm, is_app_settings } = useContext(SelfExclusionContext); const { dirty, isSubmitting, isValid, values }: TFormikContext = useFormikContext(); - const versions: Record }> = { + const versions: Record }> = { // App-specific settings, i.e. user accessing app settings from App Store or // through DWallet App header. app_settings: { @@ -448,16 +450,16 @@ const SelfExclusionInputs = () => { }; return ( - + {Object.keys(versions).map((version_name: string) => { const version = versions[version_name]; if (!version.condition) return null; return ( - + {version.components.map((Component, component_idx) => ( ))} - + ); })} {footer_ref ? ( @@ -476,7 +478,7 @@ const SelfExclusionInputs = () => {
)} - + ); }; diff --git a/packages/account/src/Components/self-exclusion/self-exclusion-modal.tsx b/packages/account/src/Components/self-exclusion/self-exclusion-modal.tsx index f50166c05337..cc3c3b19c8c8 100644 --- a/packages/account/src/Components/self-exclusion/self-exclusion-modal.tsx +++ b/packages/account/src/Components/self-exclusion/self-exclusion-modal.tsx @@ -1,10 +1,10 @@ -import React from 'react'; +import { useContext } from 'react'; import { Modal, ThemedScrollbars } from '@deriv/components'; import SelfExclusionContext from './self-exclusion-context'; import SelfExclusionArticleContent from './self-exclusion-article-content'; const SelfExclusionModal = () => { - const { state, toggleArticle } = React.useContext(SelfExclusionContext); + const { state, toggleArticle } = useContext(SelfExclusionContext); return ( diff --git a/packages/account/src/Components/self-exclusion/self-exclusion-wrapper.tsx b/packages/account/src/Components/self-exclusion/self-exclusion-wrapper.tsx index d712687b3b29..9762d5256b21 100644 --- a/packages/account/src/Components/self-exclusion/self-exclusion-wrapper.tsx +++ b/packages/account/src/Components/self-exclusion/self-exclusion-wrapper.tsx @@ -1,12 +1,12 @@ -import React from 'react'; +import { ReactNode, useContext } from 'react'; import clsx from 'clsx'; import { Div100vhContainer, ThemedScrollbars } from '@deriv/components'; import SelfExclusionArticle from './self-exclusion-article'; import SelfExclusionContext from './self-exclusion-context'; import { useDevice } from '@deriv-com/ui'; -const SelfExclusionWrapper = ({ children }: { children?: React.ReactNode }) => { - const { is_app_settings, is_wrapper_bypassed, state } = React.useContext(SelfExclusionContext); +const SelfExclusionWrapper = ({ children }: { children?: ReactNode }) => { + const { is_app_settings, is_wrapper_bypassed, state } = useContext(SelfExclusionContext); const { isDesktop } = useDevice(); // "is_wrapper_bypassed" is currently used for a hosted . diff --git a/packages/account/src/Components/sent-email-modal/sent-email-modal.tsx b/packages/account/src/Components/sent-email-modal/sent-email-modal.tsx index 6922a9a537b3..b529f47b1c1d 100644 --- a/packages/account/src/Components/sent-email-modal/sent-email-modal.tsx +++ b/packages/account/src/Components/sent-email-modal/sent-email-modal.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { localize, Localize } from '@deriv/translations'; +import { ReactElement } from 'react'; +import { Localize, useTranslations, localize } from '@deriv-com/translations'; import { Div100vhContainer, Icon, MobileDialog, Modal, SendEmailTemplate, Text, Popover } from '@deriv/components'; import { getPlatformSettings, CFD_PLATFORMS } from '@deriv/shared'; import { useDevice } from '@deriv-com/ui'; @@ -16,7 +16,7 @@ type TSentEmailModal = { type TNoEmailContentItem = { key: string; icon: string; - content: string | React.ReactElement; + content: string | ReactElement; }; const getNoEmailContentStrings = (): TNoEmailContentItem[] => { @@ -57,9 +57,10 @@ const SentEmailModal = ({ onClose, }: TSentEmailModal) => { const { isDesktop } = useDevice(); + const { localize } = useTranslations(); const getSubtitle = () => { - let subtitle: string | React.ReactElement = ''; + let subtitle: string | ReactElement = ''; switch (identifier_title) { case CFD_PLATFORMS.DXTRADE: subtitle = ( @@ -113,7 +114,7 @@ const SentEmailModal = ({ /> ) : null; - const sent_email_template: React.ReactElement = ( + const sent_email_template: ReactElement = ( void; @@ -23,6 +22,7 @@ const UnlinkAccountModal = ({ onClose, is_open, identifier_title, onClickSendEma onClose(); onClickSendEmail(); }; + const { localize } = useTranslations(); return ( diff --git a/packages/account/src/Components/unlink-modal/__tests__/unlink-modal.spec.tsx b/packages/account/src/Components/unlink-modal/__tests__/unlink-modal.spec.tsx deleted file mode 100644 index 4fa1024b9c61..000000000000 --- a/packages/account/src/Components/unlink-modal/__tests__/unlink-modal.spec.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; -import { render, screen } from '@testing-library/react'; -import { TUnlinkModal, UnlinkModal } from '../unlink-modal'; - -const modalRoot: HTMLDivElement = document.createElement('div'); -modalRoot.setAttribute('id', 'modal_root'); -document.body.appendChild(modalRoot); - -describe('', () => { - const mock_props: TUnlinkModal = { - identifier_title: 'test title', - is_open: true, - onClose: jest.fn(), - onClickSendEmail: jest.fn(), - }; - - it('should show the proper messages', () => { - render(); - - expect(screen.getByText('Are you sure you want to unlink from test title?')).toBeInTheDocument(); - expect(screen.getByText('You will need to set a password to complete the process.')).toBeInTheDocument(); - expect(screen.getByText('Unlink from test title')).toBeInTheDocument(); - }); -}); diff --git a/packages/account/src/Components/unlink-modal/index.ts b/packages/account/src/Components/unlink-modal/index.ts deleted file mode 100644 index 8d75e1af2b6e..000000000000 --- a/packages/account/src/Components/unlink-modal/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { UnlinkModal } from './unlink-modal'; - -export default UnlinkModal; diff --git a/packages/account/src/Components/unlink-modal/unlink-modal.tsx b/packages/account/src/Components/unlink-modal/unlink-modal.tsx deleted file mode 100644 index 2269521f9e4e..000000000000 --- a/packages/account/src/Components/unlink-modal/unlink-modal.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React from 'react'; -import { Button, Modal, Text } from '@deriv/components'; -import { localize, Localize } from '@deriv/translations'; - -export type TUnlinkModal = { - identifier_title: string; - is_open: boolean; - onClose: () => void; - onClickSendEmail: () => void; -}; - -export const UnlinkModal = ({ identifier_title, is_open, onClickSendEmail, onClose }: TUnlinkModal) => { - return ( - - } - toggleModal={onClose} - width='440px' - > - - - {localize('You will need to set a password to complete the process.')} - - - - - - - ); -}; diff --git a/packages/account/src/Constants/api-token-card-details.tsx b/packages/account/src/Constants/api-token-card-details.tsx index 135bfff62530..ab17ec561378 100644 --- a/packages/account/src/Constants/api-token-card-details.tsx +++ b/packages/account/src/Constants/api-token-card-details.tsx @@ -1,5 +1,4 @@ -import React from 'react'; -import { Localize } from '@deriv/translations'; +import { Localize } from '@deriv-com/translations'; export const API_TOKEN_CARD_DETAILS = [ { diff --git a/packages/account/src/Constants/closing-account-config.tsx b/packages/account/src/Constants/closing-account-config.tsx index 7d124698e3b4..aca10e07919f 100644 --- a/packages/account/src/Constants/closing-account-config.tsx +++ b/packages/account/src/Constants/closing-account-config.tsx @@ -1,5 +1,4 @@ -import React from 'react'; -import { Localize } from '@deriv/translations'; +import { Localize } from '@deriv-com/translations'; export const MAX_ALLOWED_REASONS_FOR_CLOSING_ACCOUNT = 3; export const CHARACTER_LIMIT_FOR_CLOSING_ACCOUNT = 110; diff --git a/packages/account/src/Constants/connected-apps-config.tsx b/packages/account/src/Constants/connected-apps-config.tsx index 18fd3e3f4b6a..d59bb13c51ae 100644 --- a/packages/account/src/Constants/connected-apps-config.tsx +++ b/packages/account/src/Constants/connected-apps-config.tsx @@ -1,5 +1,4 @@ -import React from 'react'; -import { Localize } from '@deriv/translations'; +import { Localize } from '@deriv-com/translations'; export const CONNECTED_APPS_INFO_BULLETS = [ { diff --git a/packages/account/src/Constants/get-login-history-table-headers.tsx b/packages/account/src/Constants/get-login-history-table-headers.tsx index 213df905095d..18f84e20886a 100644 --- a/packages/account/src/Constants/get-login-history-table-headers.tsx +++ b/packages/account/src/Constants/get-login-history-table-headers.tsx @@ -1,4 +1,4 @@ -import { Localize } from '@deriv/translations'; +import { Localize } from '@deriv-com/translations'; const getLoginHistoryTableHeaders = () => ({ date_title: , diff --git a/packages/account/src/Containers/reset-trading-password.tsx b/packages/account/src/Containers/reset-trading-password.tsx index be15a2ff43d3..3fe86c01272e 100644 --- a/packages/account/src/Containers/reset-trading-password.tsx +++ b/packages/account/src/Containers/reset-trading-password.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import { useRef } from 'react'; import { useLocation } from 'react-router-dom'; import { CFD_PLATFORMS } from '@deriv/shared'; import { observer, useStore } from '@deriv/stores'; @@ -9,7 +9,7 @@ const ResetTradingPassword = observer(() => { const { ui, client } = useStore(); const { enableApp, disableApp, is_loading, setCFDPasswordResetModal, is_cfd_reset_password_modal_enabled } = ui; const location = useLocation(); - const platform = React.useRef(''); + const platform = useRef(''); const query_params = new URLSearchParams(location.search); const cfd_platform = /^trading_platform_(.*)_password_reset$/.exec(query_params.get('action') ?? '')?.[1]; if (cfd_platform) { diff --git a/packages/account/src/Containers/toast-popup.tsx b/packages/account/src/Containers/toast-popup.tsx deleted file mode 100644 index 6d9f896b0c38..000000000000 --- a/packages/account/src/Containers/toast-popup.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import clsx from 'clsx'; -import React from 'react'; -import ReactDOM from 'react-dom'; -import { Toast } from '@deriv/components'; -import { observer, useStore } from '@deriv/stores'; -import { useDevice } from '@deriv-com/ui'; - -type TNetworkStatusToastError = { - status: string; - portal_id: string; - message: string; -}; - -/** - * Network status Toast components - */ -const NetworkStatusToastError = ({ status, portal_id, message }: TNetworkStatusToastError) => { - const [is_open, setIsOpen] = React.useState(false); - const { isDesktop } = useDevice(); - const new_portal_id = document.getElementById(portal_id); - - if (!new_portal_id || !message) return null; - - if (!is_open && status !== 'online') { - setIsOpen(true); // open if status === 'blinker' or 'offline' - } else if (is_open && status === 'online') { - setTimeout(() => { - setIsOpen(false); - }, 1500); - } - - return ReactDOM.createPortal( - !isDesktop && ( - - {message} - - ), - new_portal_id - ); -}; - -export const NetworkStatusToastErrorPopup = observer(() => { - const { - common: { network_status }, - } = useStore(); - return ( - - ); -}); diff --git a/packages/account/src/Helpers/utils.tsx b/packages/account/src/Helpers/utils.tsx index 70147b7f7faf..4e41947ea623 100644 --- a/packages/account/src/Helpers/utils.tsx +++ b/packages/account/src/Helpers/utils.tsx @@ -11,7 +11,7 @@ import { IDV_ERROR_STATUS, AUTH_STATUS_CODES, } from '@deriv/shared'; -import { localize } from '@deriv/translations'; +import { localize } from '@deriv-com/translations'; import { getIDVDocuments } from '../Configs/idv-document-config'; import { TServerError } from '../Types'; import { LANGUAGE_CODES } from '../Constants/onfido'; diff --git a/packages/account/src/Modules/Page404/Components/Page404.tsx b/packages/account/src/Modules/Page404/Components/Page404.tsx index 981431ce85c8..2a45f147b80a 100644 --- a/packages/account/src/Modules/Page404/Components/Page404.tsx +++ b/packages/account/src/Modules/Page404/Components/Page404.tsx @@ -1,21 +1,23 @@ -import React from 'react'; import { PageError } from '@deriv/components'; import { routes, getUrlBase } from '@deriv/shared'; -import { localize } from '@deriv/translations'; +import { useTranslations } from '@deriv-com/translations'; -const Page404 = () => ( - -); +const Page404 = () => { + const { localize } = useTranslations(); + return ( + + ); +}; export default Page404; diff --git a/packages/account/src/Sections/Security/AccountClosed/account-closed.tsx b/packages/account/src/Sections/Security/AccountClosed/account-closed.tsx index c73992325596..3c1a975050f5 100644 --- a/packages/account/src/Sections/Security/AccountClosed/account-closed.tsx +++ b/packages/account/src/Sections/Security/AccountClosed/account-closed.tsx @@ -1,24 +1,24 @@ -import React from 'react'; +import { useState, useCallback, useEffect } from 'react'; import { Modal, Text } from '@deriv/components'; -import { Localize } from '@deriv/translations'; import { observer, useStore } from '@deriv/stores'; -import { getStaticUrl } from '@deriv/shared'; +import { URLUtils } from '@deriv-com/utils'; +import { Localize } from '@deriv-com/translations'; const AccountClosed = observer(() => { const { client } = useStore(); const { logout } = client; - const [is_modal_open, setModalState] = React.useState(true); - const [timer, setTimer] = React.useState(10); + const [is_modal_open, setModalState] = useState(true); + const [timer, setTimer] = useState(10); - const counter = React.useCallback(() => { + const counter = useCallback(() => { if (timer > 0) { setTimer(timer - 1); } else { - window.location.href = getStaticUrl('/'); + window.location.href = URLUtils.getDerivStaticURL('/'); } }, [timer]); - React.useEffect(() => { + useEffect(() => { window.history.pushState(null, '', '/'); logout(); const handleInterval = setInterval(() => counter(), 1000); diff --git a/packages/account/src/Sections/Security/AccountLimits/account-limits-info.tsx b/packages/account/src/Sections/Security/AccountLimits/account-limits-info.tsx index 3afa2b7b31b7..d39f0119d0cc 100644 --- a/packages/account/src/Sections/Security/AccountLimits/account-limits-info.tsx +++ b/packages/account/src/Sections/Security/AccountLimits/account-limits-info.tsx @@ -1,6 +1,6 @@ -import React from 'react'; +import { Fragment } from 'react'; import { Icon, Text } from '@deriv/components'; -import { localize, Localize } from '@deriv/translations'; +import { localize, Localize } from '@deriv-com/translations'; const currency_name_map = { BTC: { display_code: 'BTC', name: localize('Bitcoin') }, @@ -22,9 +22,9 @@ type TAccountLimitsInfo = { }; const AccountLimitsInfo = ({ currency, is_virtual = false }: TAccountLimitsInfo) => ( - <> + {!is_virtual && ( - <> + )} - + )} - + ); export default AccountLimitsInfo; diff --git a/packages/account/src/Sections/Security/ApiToken/api-token.tsx b/packages/account/src/Sections/Security/ApiToken/api-token.tsx index 488f669118ed..bf8f427fcbac 100644 --- a/packages/account/src/Sections/Security/ApiToken/api-token.tsx +++ b/packages/account/src/Sections/Security/ApiToken/api-token.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import { useRef, useReducer, useEffect, ChangeEvent } from 'react'; import clsx from 'clsx'; import { Formik, Form, Field, FormikErrors, FieldProps, FormikHelpers } from 'formik'; import { ApiToken as TApitoken, APITokenResponse as TAPITokenResponse } from '@deriv/api-types'; @@ -6,7 +6,7 @@ import { Timeline, Input, Button, ThemedScrollbars, Loading } from '@deriv/compo import { useDevice } from '@deriv-com/ui'; import { getPropertyValue, WS } from '@deriv/shared'; import { observer, useStore } from '@deriv/stores'; -import { Localize, localize } from '@deriv/translations'; +import { Localize, useTranslations } from '@deriv-com/translations'; import { TToken } from '../../../Types'; import { ApiTokenContext, ApiTokenArticle, ApiTokenCard, ApiTokenTable } from '../../../Components/api-token'; import InlineNoteWithIcon from '../../../Components/inline-note-with-icon'; @@ -36,10 +36,11 @@ type TApiTokenForm = { const ApiToken = observer(() => { const { client } = useStore(); const { is_switching } = client; - const prev_is_switching = React.useRef(is_switching); + const prev_is_switching = useRef(is_switching); const { isDesktop } = useDevice(); + const { localize } = useTranslations(); - const [state, setState] = React.useReducer( + const [state, setState] = useReducer( (prev_state: Partial, value: Partial) => ({ ...prev_state, ...value, @@ -55,10 +56,10 @@ const ApiToken = observer(() => { } ); - const handle_submit_timeout_ref = React.useRef(); - const delete_token_timeout_ref = React.useRef(); + const handle_submit_timeout_ref = useRef(); + const delete_token_timeout_ref = useRef(); - React.useEffect(() => { + useEffect(() => { getApiTokens(); return () => { @@ -67,7 +68,7 @@ const ApiToken = observer(() => { }; }, []); - React.useEffect(() => { + useEffect(() => { if (prev_is_switching.current !== is_switching) { prev_is_switching.current = is_switching; getApiTokens(); @@ -248,7 +249,7 @@ const ApiToken = observer(() => { className='da-api-token__input dc-input__input-group' label={localize('Token name')} value={values.token_name} - onChange={(e: React.ChangeEvent) => { + onChange={(e: ChangeEvent) => { setFieldTouched('token_name', true); handleChange(e); }} diff --git a/packages/account/src/Sections/Security/ClosingAccount/closing-account-general-error-content.tsx b/packages/account/src/Sections/Security/ClosingAccount/closing-account-general-error-content.tsx index 8a277b3aec26..678165940bf2 100644 --- a/packages/account/src/Sections/Security/ClosingAccount/closing-account-general-error-content.tsx +++ b/packages/account/src/Sections/Security/ClosingAccount/closing-account-general-error-content.tsx @@ -1,6 +1,6 @@ -import React from 'react'; +import { Fragment } from 'react'; import { Button } from '@deriv/components'; -import { Localize } from '@deriv/translations'; +import { Localize } from '@deriv-com/translations'; type TClosingAccountGeneralErrorContentProps = { message: string; @@ -8,7 +8,7 @@ type TClosingAccountGeneralErrorContentProps = { }; const ClosingAccountGeneralErrorContent = ({ message, onClick }: TClosingAccountGeneralErrorContentProps) => ( - +
{message}
@@ -17,7 +17,7 @@ const ClosingAccountGeneralErrorContent = ({ message, onClick }: TClosingAccount -
+ ); export default ClosingAccountGeneralErrorContent; diff --git a/packages/account/src/Sections/Security/ClosingAccount/closing-account-pending-conditions/closing-account-has-pending-conditions.tsx b/packages/account/src/Sections/Security/ClosingAccount/closing-account-pending-conditions/closing-account-has-pending-conditions.tsx index 58cdb0452655..3b34d675f4f3 100644 --- a/packages/account/src/Sections/Security/ClosingAccount/closing-account-pending-conditions/closing-account-has-pending-conditions.tsx +++ b/packages/account/src/Sections/Security/ClosingAccount/closing-account-pending-conditions/closing-account-has-pending-conditions.tsx @@ -1,9 +1,10 @@ -import React from 'react'; +import { Fragment } from 'react'; import { DetailsOfEachMT5Loginid } from '@deriv/api-types'; import { Button, Money, ThemedScrollbars } from '@deriv/components'; -import { CFD_PLATFORMS, formatMoney } from '@deriv/shared'; +import { CFD_PLATFORMS } from '@deriv/shared'; import { observer, useStore } from '@deriv/stores'; -import { Localize } from '@deriv/translations'; +import { Localize } from '@deriv-com/translations'; +import { FormatUtils, CurrencyConstants } from '@deriv-com/utils'; import { TAccounts, TDetailsOfDerivAccount, @@ -124,7 +125,7 @@ const ClosingAccountHasPendingConditions = observer( } return ( - + {!!deriv_open_positions.length && ( ) @@ -239,7 +242,7 @@ const ClosingAccountHasPendingConditions = observer( - + ); } ); diff --git a/packages/account/src/Sections/Security/ClosingAccount/closing-account-pending-conditions/closing-account-pending-balance.tsx b/packages/account/src/Sections/Security/ClosingAccount/closing-account-pending-conditions/closing-account-pending-balance.tsx index d5d0b0533213..8d712e55a39c 100644 --- a/packages/account/src/Sections/Security/ClosingAccount/closing-account-pending-conditions/closing-account-pending-balance.tsx +++ b/packages/account/src/Sections/Security/ClosingAccount/closing-account-pending-conditions/closing-account-pending-balance.tsx @@ -1,15 +1,8 @@ -import React from 'react'; import { Money } from '@deriv/components'; -import { - CFD_PLATFORMS, - formatMoney, - getCFDAccount, - getCFDAccountDisplay, - getCFDPlatformLabel, - getMT5Icon, -} from '@deriv/shared'; +import { CFD_PLATFORMS, getCFDAccount, getCFDAccountDisplay, getCFDPlatformLabel, getMT5Icon } from '@deriv/shared'; +import { FormatUtils, CurrencyConstants } from '@deriv-com/utils'; import { observer, useStore } from '@deriv/stores'; -import { Localize } from '@deriv/translations'; +import { Localize } from '@deriv-com/translations'; import { TCFDPlatform, TDetailsOfDerivXAccount, TDetailsOfMT5Account } from '../../../../Types'; import ClosingAccountPendingWrapper from './closing-account-pending-wrapper'; import ClosingAccountPendingContent from './closing-account-pending-content'; @@ -75,7 +68,9 @@ const ClosingAccountPendingBalance = observer(({ platform, account_balance }: TC account.currency && ( ) diff --git a/packages/account/src/Sections/Security/ClosingAccount/closing-account-pending-conditions/closing-account-pending-content.tsx b/packages/account/src/Sections/Security/ClosingAccount/closing-account-pending-conditions/closing-account-pending-content.tsx index 77ca44523eb6..634bfdf849a1 100644 --- a/packages/account/src/Sections/Security/ClosingAccount/closing-account-pending-conditions/closing-account-pending-content.tsx +++ b/packages/account/src/Sections/Security/ClosingAccount/closing-account-pending-conditions/closing-account-pending-content.tsx @@ -1,11 +1,11 @@ -import React from 'react'; +import { ReactNode } from 'react'; import { Icon, Text } from '@deriv/components'; type TClosingAccountPendingContentProps = { currency_icon: string; loginid?: string; - title?: React.ReactNode; - value: React.ReactNode; + title?: ReactNode; + value: ReactNode; }; const ClosingAccountPendingContent = ({ currency_icon, loginid, title, value }: TClosingAccountPendingContentProps) => ( diff --git a/packages/account/src/Sections/Security/ClosingAccount/closing-account-pending-conditions/closing-account-pending-positions.tsx b/packages/account/src/Sections/Security/ClosingAccount/closing-account-pending-conditions/closing-account-pending-positions.tsx index cc5e7a27b434..fa372338304f 100644 --- a/packages/account/src/Sections/Security/ClosingAccount/closing-account-pending-conditions/closing-account-pending-positions.tsx +++ b/packages/account/src/Sections/Security/ClosingAccount/closing-account-pending-conditions/closing-account-pending-positions.tsx @@ -1,7 +1,6 @@ -import React from 'react'; import { CFD_PLATFORMS, getCFDAccount, getCFDAccountDisplay, getCFDPlatformLabel, getMT5Icon } from '@deriv/shared'; import { observer, useStore } from '@deriv/stores'; -import { Localize } from '@deriv/translations'; +import { Localize } from '@deriv-com/translations'; import { TCFDPlatform, TDetailsOfDerivXAccount, TDetailsOfMT5Account } from '../../../../Types'; import ClosingAccountPendingWrapper from './closing-account-pending-wrapper'; import ClosingAccountPendingContent from './closing-account-pending-content'; diff --git a/packages/account/src/Sections/Security/ClosingAccount/closing-account-pending-conditions/closing-account-pending-wrapper.tsx b/packages/account/src/Sections/Security/ClosingAccount/closing-account-pending-conditions/closing-account-pending-wrapper.tsx index 3b9e5a45d638..3d4daeaba811 100644 --- a/packages/account/src/Sections/Security/ClosingAccount/closing-account-pending-conditions/closing-account-pending-wrapper.tsx +++ b/packages/account/src/Sections/Security/ClosingAccount/closing-account-pending-conditions/closing-account-pending-wrapper.tsx @@ -1,16 +1,16 @@ -import React from 'react'; +import { PropsWithChildren, ReactNode } from 'react'; import { Text } from '@deriv/components'; type TClosingAccountPendingWrapperProps = { title: JSX.Element; - description?: React.ReactNode; + description?: ReactNode; }; const ClosingAccountPendingWrapper = ({ children, title, description, -}: React.PropsWithChildren) => ( +}: PropsWithChildren) => (
{title} diff --git a/packages/account/src/Sections/Security/ClosingAccount/closing-account-reason-form.tsx b/packages/account/src/Sections/Security/ClosingAccount/closing-account-reason-form.tsx index bd930b59b3da..26b19714f54b 100644 --- a/packages/account/src/Sections/Security/ClosingAccount/closing-account-reason-form.tsx +++ b/packages/account/src/Sections/Security/ClosingAccount/closing-account-reason-form.tsx @@ -1,7 +1,7 @@ -import React from 'react'; +import { useReducer, useEffect, ChangeEvent, ClipboardEvent } from 'react'; import { Field, Form, Formik, FormikErrors, FieldProps, FormikValues } from 'formik'; import { Checkbox, FormSubmitButton, Input, Text } from '@deriv/components'; -import { localize, Localize } from '@deriv/translations'; +import { useTranslations, Localize } from '@deriv-com/translations'; import { TClosingAccountFormValues } from '../../../Types'; import { CHARACTER_LIMIT_FOR_CLOSING_ACCOUNT, @@ -71,11 +71,12 @@ const reducer = (state: TCustomState, action: TAction) => { }; const ClosingAccountReasonForm = ({ onBackClick, onConfirmClick }: TClosingAccountReasonFormProps) => { - const [state, dispatch] = React.useReducer(reducer, initial_state); + const [state, dispatch] = useReducer(reducer, initial_state); + const { localize } = useTranslations(); const { is_checkbox_disabled, total_checkbox_checked, remaining_characters, total_accumulated_characters } = state; - React.useEffect(() => { + useEffect(() => { if (total_checkbox_checked === MAX_ALLOWED_REASONS_FOR_CLOSING_ACCOUNT) { dispatch({ type: SET_CHECKBOX_DISABLED, payload: true }); } else if (is_checkbox_disabled) dispatch({ type: SET_CHECKBOX_DISABLED, payload: false }); @@ -127,9 +128,9 @@ const ClosingAccountReasonForm = ({ onBackClick, onConfirmClick }: TClosingAccou }; const handleInputChange = ( - event: React.ChangeEvent, + event: ChangeEvent, old_value: string, - onChange: (event: React.ChangeEvent) => void + onChange: (event: ChangeEvent) => void ) => { const value = event.target.value; const is_delete_action = old_value.length > value.length; @@ -144,7 +145,7 @@ const ClosingAccountReasonForm = ({ onBackClick, onConfirmClick }: TClosingAccou } }; - const handleInputPaste = async (e: React.ClipboardEvent): Promise => { + const handleInputPaste = async (e: ClipboardEvent): Promise => { const clipboardData = e.clipboardData.getData('text') || (await navigator.clipboard.readText()); if (remaining_characters <= 0 || clipboardData.length > remaining_characters) { @@ -208,7 +209,7 @@ const ClosingAccountReasonForm = ({ onBackClick, onConfirmClick }: TClosingAccou name='other_trading_platforms' value={values.other_trading_platforms} max_characters={CHARACTER_LIMIT_FOR_CLOSING_ACCOUNT} - onChange={(e: React.ChangeEvent) => + onChange={(e: ChangeEvent) => handleInputChange(e, values.other_trading_platforms, handleChange) } onPaste={handleInputPaste} @@ -227,7 +228,7 @@ const ClosingAccountReasonForm = ({ onBackClick, onConfirmClick }: TClosingAccou name='do_to_improve' value={values.do_to_improve} max_characters={CHARACTER_LIMIT_FOR_CLOSING_ACCOUNT} - onChange={(e: React.ChangeEvent) => + onChange={(e: ChangeEvent) => handleInputChange(e, values.do_to_improve, handleChange) } onPaste={handleInputPaste} diff --git a/packages/account/src/Sections/Security/ClosingAccount/closing-account-reason.tsx b/packages/account/src/Sections/Security/ClosingAccount/closing-account-reason.tsx index 54ef127f5134..d0c644dab6f2 100644 --- a/packages/account/src/Sections/Security/ClosingAccount/closing-account-reason.tsx +++ b/packages/account/src/Sections/Security/ClosingAccount/closing-account-reason.tsx @@ -1,9 +1,9 @@ -import React from 'react'; +import { useState, useEffect } from 'react'; import { Redirect } from 'react-router-dom'; import { Loading, Modal, Text } from '@deriv/components'; import { routes } from '@deriv/shared'; import { useCloseDerivAccount } from '@deriv/api'; -import { Localize } from '@deriv/translations'; +import { Localize } from '@deriv-com/translations'; import { MAX_ALLOWED_REASONS_FOR_CLOSING_ACCOUNT } from '../../../Constants/closing-account-config'; import ClosingAccountHasPendingConditions from './closing-account-pending-conditions/closing-account-has-pending-conditions'; import ClosingAccountReasonForm from './closing-account-reason-form'; @@ -17,11 +17,11 @@ type TClosingAccountReasonProps = { const ClosingAccountReason = ({ redirectToSteps }: TClosingAccountReasonProps) => { const { mutate, error, isSuccess, isLoading } = useCloseDerivAccount(); - const [reasons_to_close_account, setReasonsToCloseAccount] = React.useState(''); - const [error_info, setErrorInfo] = React.useState(''); - const [show_warning_modal, setShowWarningModal] = React.useState(false); + const [reasons_to_close_account, setReasonsToCloseAccount] = useState(''); + const [error_info, setErrorInfo] = useState(''); + const [show_warning_modal, setShowWarningModal] = useState(false); - React.useEffect(() => { + useEffect(() => { if (error) { if (typeof error === 'object' && 'code' in error) { const { code } = error; diff --git a/packages/account/src/Sections/Security/ClosingAccount/closing-account-steps.tsx b/packages/account/src/Sections/Security/ClosingAccount/closing-account-steps.tsx index 2acaf81eacda..d1015e330572 100644 --- a/packages/account/src/Sections/Security/ClosingAccount/closing-account-steps.tsx +++ b/packages/account/src/Sections/Security/ClosingAccount/closing-account-steps.tsx @@ -1,7 +1,6 @@ -import React from 'react'; import { Link } from 'react-router-dom'; import { observer, useStore } from '@deriv/stores'; -import { Localize } from '@deriv/translations'; +import { Localize } from '@deriv-com/translations'; import { Button, StaticUrl, Text } from '@deriv/components'; type TClosingAccountStepsProps = { diff --git a/packages/account/src/Sections/Security/ClosingAccount/closing-account-warning-modal.tsx b/packages/account/src/Sections/Security/ClosingAccount/closing-account-warning-modal.tsx index 79534f39fabc..b6d1ee9f0ef2 100644 --- a/packages/account/src/Sections/Security/ClosingAccount/closing-account-warning-modal.tsx +++ b/packages/account/src/Sections/Security/ClosingAccount/closing-account-warning-modal.tsx @@ -1,5 +1,4 @@ -import React from 'react'; -import { localize, Localize } from '@deriv/translations'; +import { Localize, useTranslations } from '@deriv-com/translations'; import { FormSubmitButton, Icon, Text, Modal } from '@deriv/components'; type TClosingAccountWarningModalProps = { @@ -13,6 +12,8 @@ const ClosingAccountWarningModal = ({ startDeactivating, closeWarningModal, }: TClosingAccountWarningModalProps) => { + const { localize } = useTranslations(); + return (
diff --git a/packages/account/src/Sections/Security/ClosingAccount/closing-account.tsx b/packages/account/src/Sections/Security/ClosingAccount/closing-account.tsx index a924ef8ec7a0..b023d797e82e 100644 --- a/packages/account/src/Sections/Security/ClosingAccount/closing-account.tsx +++ b/packages/account/src/Sections/Security/ClosingAccount/closing-account.tsx @@ -1,9 +1,9 @@ -import React from 'react'; +import { useState } from 'react'; import ClosingAccountSteps from './closing-account-steps'; import ClosingAccountReason from './closing-account-reason'; const ClosingAccount = () => { - const [render_close_account_reason, setRenderCloseAccountReason] = React.useState(false); + const [render_close_account_reason, setRenderCloseAccountReason] = useState(false); const redirectToReasons = () => { setRenderCloseAccountReason(true); }; diff --git a/packages/account/src/Sections/Security/ConnectedApps/connected-apps-earn-more.tsx b/packages/account/src/Sections/Security/ConnectedApps/connected-apps-earn-more.tsx index 6592d0a4eade..f19a921bcf3a 100644 --- a/packages/account/src/Sections/Security/ConnectedApps/connected-apps-earn-more.tsx +++ b/packages/account/src/Sections/Security/ConnectedApps/connected-apps-earn-more.tsx @@ -1,4 +1,4 @@ -import { Localize } from '@deriv/translations'; +import { Localize } from '@deriv-com/translations'; import Article from '../../../Components/article'; const openDerivAPIWebsite = () => { diff --git a/packages/account/src/Sections/Security/ConnectedApps/connected-apps-empty.tsx b/packages/account/src/Sections/Security/ConnectedApps/connected-apps-empty.tsx index baddacfb5cb0..3286c2a9706c 100644 --- a/packages/account/src/Sections/Security/ConnectedApps/connected-apps-empty.tsx +++ b/packages/account/src/Sections/Security/ConnectedApps/connected-apps-empty.tsx @@ -1,5 +1,5 @@ import { Text } from '@deriv/components'; -import { Localize } from '@deriv/translations'; +import { Localize } from '@deriv-com/translations'; import { useDevice } from '@deriv-com/ui'; import ConnectedAppsInfoBullets from './connected-apps-info-bullets'; diff --git a/packages/account/src/Sections/Security/ConnectedApps/connected-apps-info.tsx b/packages/account/src/Sections/Security/ConnectedApps/connected-apps-info.tsx index 21595cac83d8..7b4de61cb219 100644 --- a/packages/account/src/Sections/Security/ConnectedApps/connected-apps-info.tsx +++ b/packages/account/src/Sections/Security/ConnectedApps/connected-apps-info.tsx @@ -1,6 +1,6 @@ -import React from 'react'; +import { Fragment } from 'react'; import { InlineMessage, Text } from '@deriv/components'; -import { Localize } from '@deriv/translations'; +import { Localize } from '@deriv-com/translations'; import { observer } from '@deriv/stores'; import { useDevice } from '@deriv-com/ui'; import ConnectedAppsInfoBullets from './connected-apps-info-bullets'; @@ -15,12 +15,12 @@ const ConnectedAppsInfo = observer(() => { type='information' size='md' message={ - + - + } /> ); diff --git a/packages/account/src/Sections/Security/ConnectedApps/connected-apps-know-more.tsx b/packages/account/src/Sections/Security/ConnectedApps/connected-apps-know-more.tsx index 2b60ee1874c6..b849eca564d5 100644 --- a/packages/account/src/Sections/Security/ConnectedApps/connected-apps-know-more.tsx +++ b/packages/account/src/Sections/Security/ConnectedApps/connected-apps-know-more.tsx @@ -1,4 +1,4 @@ -import { Localize } from '@deriv/translations'; +import { Localize } from '@deriv-com/translations'; import Article from '../../../Components/article'; const openAPIManagingWebsite = () => { diff --git a/packages/account/src/Sections/Security/ConnectedApps/connected-apps-revoke-modal.tsx b/packages/account/src/Sections/Security/ConnectedApps/connected-apps-revoke-modal.tsx index 081fcacd1795..af3198c261a4 100644 --- a/packages/account/src/Sections/Security/ConnectedApps/connected-apps-revoke-modal.tsx +++ b/packages/account/src/Sections/Security/ConnectedApps/connected-apps-revoke-modal.tsx @@ -1,6 +1,5 @@ -import React from 'react'; import { Button, Icon, Modal, Text } from '@deriv/components'; -import { Localize } from '@deriv/translations'; +import { Localize } from '@deriv-com/translations'; type TConnectedAppsRevokeModalProps = { handleRevokeAccess: () => void; diff --git a/packages/account/src/Sections/Security/ConnectedApps/connected-apps.tsx b/packages/account/src/Sections/Security/ConnectedApps/connected-apps.tsx index 52dd695d5690..7778f7c07933 100644 --- a/packages/account/src/Sections/Security/ConnectedApps/connected-apps.tsx +++ b/packages/account/src/Sections/Security/ConnectedApps/connected-apps.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import { useState, useEffect, useCallback } from 'react'; import { OauthApps } from '@deriv/api-types'; import { Loading } from '@deriv/components'; import { observer } from '@deriv/stores'; @@ -16,13 +16,13 @@ import './connected-apps.scss'; const ConnectedApps = observer(() => { const { isDesktop } = useDevice(); - const [is_loading, setLoading] = React.useState(true); - const [is_modal_open, setIsModalOpen] = React.useState(false); - const [selected_app_id, setSelectedAppId] = React.useState(null); - const [is_error, setError] = React.useState(false); - const [connected_apps, setConnectedApps] = React.useState([]); + const [is_loading, setLoading] = useState(true); + const [is_modal_open, setIsModalOpen] = useState(false); + const [selected_app_id, setSelectedAppId] = useState(null); + const [is_error, setError] = useState(false); + const [connected_apps, setConnectedApps] = useState([]); - React.useEffect(() => { + useEffect(() => { /* eslint-disable no-console */ fetchConnectedApps().catch(error => console.error('error: ', error)); }, []); @@ -36,12 +36,12 @@ const ConnectedApps = observer(() => { } }; - const handleToggleModal = React.useCallback((app_id: number | null = null) => { + const handleToggleModal = useCallback((app_id: number | null = null) => { setIsModalOpen(is_modal_open => !is_modal_open); setSelectedAppId(app_id); }, []); - const revokeConnectedApp = React.useCallback(async (app_id: number | null) => { + const revokeConnectedApp = useCallback(async (app_id: number | null) => { setLoading(true); const response = await WS.authorized.send({ revoke_oauth_app: app_id }); if (!response.error) { @@ -53,7 +53,7 @@ const ConnectedApps = observer(() => { } }, []); - const handleRevokeAccess = React.useCallback(() => { + const handleRevokeAccess = useCallback(() => { setIsModalOpen(false); revokeConnectedApp(selected_app_id); }, [revokeConnectedApp, selected_app_id]); diff --git a/packages/account/src/Sections/Security/ConnectedApps/data-list-template-entry.tsx b/packages/account/src/Sections/Security/ConnectedApps/data-list-template-entry.tsx index 961acb1673c6..f256374dbf8b 100644 --- a/packages/account/src/Sections/Security/ConnectedApps/data-list-template-entry.tsx +++ b/packages/account/src/Sections/Security/ConnectedApps/data-list-template-entry.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import { Fragment } from 'react'; import { Text } from '@deriv/components'; type TDataListTemplateEntry = { @@ -7,12 +7,12 @@ type TDataListTemplateEntry = { }; const DataListTemplateEntry = ({ title, content }: TDataListTemplateEntry) => ( - + {title} {content} - + ); export default DataListTemplateEntry; diff --git a/packages/account/src/Sections/Security/ConnectedApps/data-list-template.tsx b/packages/account/src/Sections/Security/ConnectedApps/data-list-template.tsx index 795b6b059faf..82fdf58f9c70 100644 --- a/packages/account/src/Sections/Security/ConnectedApps/data-list-template.tsx +++ b/packages/account/src/Sections/Security/ConnectedApps/data-list-template.tsx @@ -1,8 +1,7 @@ -import React from 'react'; import { OauthApps } from '@deriv/api-types'; import { Button } from '@deriv/components'; import { toMoment } from '@deriv/shared'; -import { Localize } from '@deriv/translations'; +import { Localize } from '@deriv-com/translations'; import DataListTemplateEntry from './data-list-template-entry'; import { getConnectedAppsScopes } from './template-helper'; diff --git a/packages/account/src/Sections/Security/ConnectedApps/data-table-template.tsx b/packages/account/src/Sections/Security/ConnectedApps/data-table-template.tsx index 608a027e0513..eb870ea98456 100644 --- a/packages/account/src/Sections/Security/ConnectedApps/data-table-template.tsx +++ b/packages/account/src/Sections/Security/ConnectedApps/data-table-template.tsx @@ -1,9 +1,9 @@ -import React from 'react'; +import { Fragment } from 'react'; import clsx from 'clsx'; import { OauthApps } from '@deriv/api-types'; import { Button, Text } from '@deriv/components'; import { toMoment } from '@deriv/shared'; -import { Localize } from '@deriv/translations'; +import { Localize } from '@deriv-com/translations'; import { getConnectedAppsColumnNames, getConnectedAppsScopes } from './template-helper'; type TDataTableTemplate = { connected_apps: OauthApps; handleToggleModal: (app_id: number) => void }; @@ -23,7 +23,7 @@ const DataTableTemplate = ({ connected_apps, handleToggleModal }: TDataTableTemp ))} {connected_apps.map(connected_app => ( - + {connected_app.name} @@ -38,7 +38,7 @@ const DataTableTemplate = ({ connected_apps, handleToggleModal }: TDataTableTemp - + ))}
); diff --git a/packages/account/src/Sections/Security/ConnectedApps/template-helper.tsx b/packages/account/src/Sections/Security/ConnectedApps/template-helper.tsx index a3992fff7a27..e7c5bfa039c6 100644 --- a/packages/account/src/Sections/Security/ConnectedApps/template-helper.tsx +++ b/packages/account/src/Sections/Security/ConnectedApps/template-helper.tsx @@ -1,5 +1,4 @@ -import React from 'react'; -import { Localize, localize } from '@deriv/translations'; +import { Localize, localize } from '@deriv-com/translations'; export const getConnectedAppsColumnNames = () => [ , diff --git a/packages/account/src/Sections/Security/LoginHistory/list-cell.tsx b/packages/account/src/Sections/Security/LoginHistory/list-cell.tsx index 460b114c1e8a..1e8995e0aa13 100644 --- a/packages/account/src/Sections/Security/LoginHistory/list-cell.tsx +++ b/packages/account/src/Sections/Security/LoginHistory/list-cell.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import { Fragment } from 'react'; import clsx from 'clsx'; import { Text } from '@deriv/components'; @@ -10,7 +10,7 @@ type TListCell = { }; const ListCell = ({ title, text, className, align = 'left' }: TListCell) => ( - + {title} @@ -22,7 +22,7 @@ const ListCell = ({ title, text, className, align = 'left' }: TListCell) => ( > {text}
- + ); export default ListCell; diff --git a/packages/account/src/Sections/Security/LoginHistory/login-history-list-row.tsx b/packages/account/src/Sections/Security/LoginHistory/login-history-list-row.tsx index 928e80bae998..a18d69f25184 100644 --- a/packages/account/src/Sections/Security/LoginHistory/login-history-list-row.tsx +++ b/packages/account/src/Sections/Security/LoginHistory/login-history-list-row.tsx @@ -1,7 +1,7 @@ import clsx from 'clsx'; import { TLoginHistoryItems } from '../../../Types'; import { Table } from '@deriv/components'; -import { Localize } from '@deriv/translations'; +import { Localize } from '@deriv-com/translations'; import { useDevice } from '@deriv-com/ui'; import getLoginHistoryTableHeaders from '../../../Constants/get-login-history-table-headers'; import ListCell from './list-cell'; diff --git a/packages/account/src/Sections/Security/LoginHistory/login-history-table-row.tsx b/packages/account/src/Sections/Security/LoginHistory/login-history-table-row.tsx index b6aac821c789..da8e5ccc39cd 100644 --- a/packages/account/src/Sections/Security/LoginHistory/login-history-table-row.tsx +++ b/packages/account/src/Sections/Security/LoginHistory/login-history-table-row.tsx @@ -1,5 +1,5 @@ import { Table, Text } from '@deriv/components'; -import { Localize } from '@deriv/translations'; +import { Localize } from '@deriv-com/translations'; import { TLoginHistoryItems } from '../../../Types'; const LoginHistoryTableRow = ({ id, date, action, browser, ip, status }: TLoginHistoryItems) => { diff --git a/packages/account/src/Sections/Security/Passkeys/passkeys.tsx b/packages/account/src/Sections/Security/Passkeys/passkeys.tsx index a259e674e386..f9d6163f0b71 100644 --- a/packages/account/src/Sections/Security/Passkeys/passkeys.tsx +++ b/packages/account/src/Sections/Security/Passkeys/passkeys.tsx @@ -1,4 +1,4 @@ -import React, { Fragment, useEffect, useRef, useState } from 'react'; +import { Fragment, useEffect, useRef, useState } from 'react'; import { Redirect, useHistory } from 'react-router-dom'; import { InlineMessage, Loading } from '@deriv/components'; import { useGetPasskeysList, useRegisterPasskey, useRenamePasskey } from '@deriv/hooks'; @@ -39,7 +39,7 @@ const Passkeys = observer(() => { const error_modal_timeout = useRef | null>(null); const snackbar_timeout = useRef | null>(null); - const prev_passkey_status = React.useRef(PASSKEY_STATUS_CODES.LIST); + const prev_passkey_status = useRef(PASSKEY_STATUS_CODES.LIST); const history = useHistory(); @@ -72,6 +72,7 @@ const Passkeys = observer(() => { } else { setPasskeyStatus(PASSKEY_STATUS_CODES.LIST); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [is_passkeys_list_loading, passkeys_list?.length]); useEffect(() => { diff --git a/packages/account/src/Sections/Security/Passwords/deriv-email.tsx b/packages/account/src/Sections/Security/Passwords/deriv-email.tsx index ea762e4832a8..a20d4f6a6290 100644 --- a/packages/account/src/Sections/Security/Passwords/deriv-email.tsx +++ b/packages/account/src/Sections/Security/Passwords/deriv-email.tsx @@ -1,9 +1,9 @@ -import React from 'react'; +import { Fragment, useState } from 'react'; import { Button, Text, Input } from '@deriv/components'; import { useVerifyEmail } from '@deriv/api'; import { toTitleCase } from '@deriv/shared'; import { observer, useStore } from '@deriv/stores'; -import { Localize, localize } from '@deriv/translations'; +import { Localize, useTranslations } from '@deriv-com/translations'; import FormSubHeader from '../../../Components/form-sub-header'; import SentEmailModal from '../../../Components/sent-email-modal'; import UnlinkAccountModal from '../../../Components/unlink-account-modal'; @@ -13,7 +13,7 @@ type TVerifyEmailPayload = Parameters['mutate' /** * Display the user's email address and a button to change it. * @name DerivEmail - * @returns {React.ReactNode} + * @returns { ReactNode } */ const DerivEmail = observer(() => { const { @@ -21,8 +21,9 @@ const DerivEmail = observer(() => { client: { social_identity_provider, is_social_signup, email }, } = useStore(); const { mutate } = useVerifyEmail(); - const [is_unlink_account_modal_open, setIsUnlinkAccountModalOpen] = React.useState(false); - const [is_send_email_modal_open, setIsSendEmailModalOpen] = React.useState(false); + const { localize } = useTranslations(); + const [is_unlink_account_modal_open, setIsUnlinkAccountModalOpen] = useState(false); + const [is_send_email_modal_open, setIsSendEmailModalOpen] = useState(false); const payload: TVerifyEmailPayload = { verify_email: email, type: 'request_email' }; @@ -42,7 +43,7 @@ const DerivEmail = observer(() => { }; return ( - +
@@ -90,7 +91,7 @@ const DerivEmail = observer(() => { is_modal_when_mobile={true} />
-
+ ); }); diff --git a/packages/account/src/Sections/Security/Passwords/deriv-password.tsx b/packages/account/src/Sections/Security/Passwords/deriv-password.tsx index 1b0f3e242746..16c6dc81b391 100644 --- a/packages/account/src/Sections/Security/Passwords/deriv-password.tsx +++ b/packages/account/src/Sections/Security/Passwords/deriv-password.tsx @@ -1,15 +1,12 @@ -import React from 'react'; - +import { Fragment, useState } from 'react'; import { useVerifyEmail } from '@deriv/api'; import { Button, Icon, Popover, Text } from '@deriv/components'; import { getBrandWebsiteName, getPlatformSettings, toTitleCase } from '@deriv/shared'; import { observer, useStore } from '@deriv/stores'; -import { Localize, localize } from '@deriv/translations'; - +import { Localize, useTranslations } from '@deriv-com/translations'; import { BrandDerivLogoCoralIcon } from '@deriv/quill-icons'; import FormSubHeader from '../../../Components/form-sub-header'; import SentEmailModal from '../../../Components/sent-email-modal'; - import PlatformDescription from './platform-description'; /** @@ -18,13 +15,14 @@ import PlatformDescription from './platform-description'; * @returns {React.ReactNode} */ const DerivPassword = observer(() => { + const { localize } = useTranslations(); const { traders_hub: { is_eu_user, financial_restricted_countries }, client: { social_identity_provider, is_social_signup, email }, } = useStore(); const { mutate } = useVerifyEmail(); - const [is_sent_email_modal_open, setIsSentEmailModalOpen] = React.useState(false); + const [is_sent_email_modal_open, setIsSentEmailModalOpen] = useState(false); const onClickSendEmail = () => { if (social_identity_provider === 'apple') { @@ -51,10 +49,10 @@ const DerivPassword = observer(() => { }; return ( - +
- + { /> {!is_eu_user && !financial_restricted_countries && ( - + { description='smarttrader' /> - + )} {(!is_eu_user || financial_restricted_countries) && ( - + { description='ctrader' /> - + )}
-
+ {is_social_signup ? ( - +
{social_identity_provider ? ( - + { /> - + ) : ( '' )}
-
+ ) : (
@@ -173,7 +171,7 @@ const DerivPassword = observer(() => { is_modal_when_mobile={true} />
- + ); }); diff --git a/packages/account/src/Sections/Security/Passwords/passwords-platform.tsx b/packages/account/src/Sections/Security/Passwords/passwords-platform.tsx index c9c2899fd5c0..eb9f6f544908 100644 --- a/packages/account/src/Sections/Security/Passwords/passwords-platform.tsx +++ b/packages/account/src/Sections/Security/Passwords/passwords-platform.tsx @@ -1,8 +1,8 @@ -import React from 'react'; +import { useState, Fragment } from 'react'; import { useMutation } from '@deriv/api'; import { CFD_PLATFORMS, getPlatformSettings } from '@deriv/shared'; import { observer, useStore } from '@deriv/stores'; -import { Localize, localize } from '@deriv/translations'; +import { Localize, useTranslations } from '@deriv-com/translations'; import FormSubHeader from '../../../Components/form-sub-header'; import SentEmailModal from '../../../Components/sent-email-modal'; import PlatformPartials from './platform-partials'; @@ -22,13 +22,14 @@ type TPasswordsPlatformProps = { const PasswordsPlatform = observer( ({ has_dxtrade_accounts = false, has_mt5_accounts = false }: TPasswordsPlatformProps) => { const { mutate } = useMutation('verify_email'); + const { localize } = useTranslations(); const { client: { email }, } = useStore(); - const [identifier, setIdentifier] = React.useState(''); - const [is_sent_email_modal_open, setIsSentEmailModalOpen] = React.useState(false); + const [identifier, setIdentifier] = useState(''); + const [is_sent_email_modal_open, setIsSentEmailModalOpen] = useState(false); const platform_name_dxtrade = getPlatformSettings('dxtrade').name; @@ -64,7 +65,7 @@ const PasswordsPlatform = observer( }; return ( - +
{has_mt5_accounts && ( @@ -93,7 +94,7 @@ const PasswordsPlatform = observer( is_modal_when_mobile />
-
+ ); } ); diff --git a/packages/account/src/Sections/Security/Passwords/passwords.tsx b/packages/account/src/Sections/Security/Passwords/passwords.tsx index 59a4dfde3931..f94ea57b94ec 100644 --- a/packages/account/src/Sections/Security/Passwords/passwords.tsx +++ b/packages/account/src/Sections/Security/Passwords/passwords.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import { useState, useEffect, ReactNode } from 'react'; import { Loading } from '@deriv/components'; import { observer, useStore } from '@deriv/stores'; import DerivPassword from './deriv-password'; @@ -8,7 +8,7 @@ import PasswordsPlatform from './passwords-platform'; /** * Displays the Email, Password, section under Account settings. * @name Passwords - * @returns {React.ReactNode} + * @returns {ReactNode} */ const Passwords = observer(() => { const { client, common } = useStore(); @@ -23,11 +23,11 @@ const Passwords = observer(() => { } = client; const { is_from_derivgo } = common; - const [is_loading, setIsLoading] = React.useState(true); + const [is_loading, setIsLoading] = useState(true); const has_mt5_accounts = mt5_login_list?.length > 0 || !is_mt5_password_not_set; const has_dxtrade_accounts = dxtrade_accounts_list?.length > 0 || !is_dxtrade_password_not_set; - React.useEffect(() => { + useEffect(() => { if ( is_populating_mt5_account_list === false && is_populating_dxtrade_account_list === false && diff --git a/packages/account/src/Sections/Security/Passwords/platform-description.tsx b/packages/account/src/Sections/Security/Passwords/platform-description.tsx index ecbfbff8dad2..b117eaebb96a 100644 --- a/packages/account/src/Sections/Security/Passwords/platform-description.tsx +++ b/packages/account/src/Sections/Security/Passwords/platform-description.tsx @@ -1,5 +1,4 @@ -import React from 'react'; -import { Localize } from '@deriv/translations'; +import { Localize } from '@deriv-com/translations'; type TPlatformDescription = { brand_website_name: string; diff --git a/packages/account/src/Sections/Security/Passwords/platform-partials.tsx b/packages/account/src/Sections/Security/Passwords/platform-partials.tsx index 92e03a2f108c..85e7e68db98a 100644 --- a/packages/account/src/Sections/Security/Passwords/platform-partials.tsx +++ b/packages/account/src/Sections/Security/Passwords/platform-partials.tsx @@ -1,7 +1,7 @@ -import React from 'react'; +import { Fragment } from 'react'; import { Button, Icon, Popover, Text } from '@deriv/components'; import { getPlatformSettings, CFD_PLATFORMS } from '@deriv/shared'; -import { localize } from '@deriv/translations'; +import { useTranslations } from '@deriv-com/translations'; type TPlatformPartialsProps = { description: JSX.Element; @@ -19,9 +19,10 @@ type TPlatformPartialsProps = { */ const PlatformPartials = ({ description, type, handleClick }: TPlatformPartialsProps) => { const platform_config = getPlatformSettings(type); + const { localize } = useTranslations(); return ( - + {description} @@ -38,7 +39,7 @@ const PlatformPartials = ({ description, type, handleClick }: TPlatformPartialsP large />
- + ); }; diff --git a/packages/account/src/Sections/Security/SelfExclusion/self-exclusion.tsx b/packages/account/src/Sections/Security/SelfExclusion/self-exclusion.tsx index ff31b485617d..ffc2437da592 100644 --- a/packages/account/src/Sections/Security/SelfExclusion/self-exclusion.tsx +++ b/packages/account/src/Sections/Security/SelfExclusion/self-exclusion.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import { Dispatch, SetStateAction, useRef, useReducer, useEffect } from 'react'; import { Loading } from '@deriv/components'; import { getBrandWebsiteName, @@ -10,7 +10,7 @@ import { useIsMounted, WS, } from '@deriv/shared'; -import { localize } from '@deriv/translations'; +import { useTranslations } from '@deriv-com/translations'; import DemoMessage from '../../../Components/demo-message'; import '../../../Components/self-exclusion/self-exclusion.scss'; import LoadErrorMessage from '../../../Components/load-error-message'; @@ -25,7 +25,7 @@ import { observer, useStore } from '@deriv/stores'; type TSelfExclusion = { is_app_settings?: boolean; overlay_ref: HTMLDivElement; - setIsOverlayShown?: React.Dispatch>; + setIsOverlayShown?: Dispatch>; }; type TExclusionData = { max_deposit: string; @@ -71,6 +71,7 @@ type TResponse = { const SelfExclusion = observer(({ is_app_settings, overlay_ref, setIsOverlayShown }: TSelfExclusion) => { const { client, ui } = useStore(); + const { localize } = useTranslations(); const { currency, is_virtual, @@ -110,9 +111,9 @@ const SelfExclusion = observer(({ is_app_settings, overlay_ref, setIsOverlayShow max_open_bets: localize('Max. open positions'), }; - const prev_is_switching = React.useRef(null); - const exclusion_limits = React.useRef({}); - const exclusion_data = React.useRef({ + const prev_is_switching = useRef(null); + const exclusion_limits = useRef({}); + const exclusion_data = useRef({ max_deposit: '', max_turnover: '', max_losses: '', @@ -142,14 +143,14 @@ const SelfExclusion = observer(({ is_app_settings, overlay_ref, setIsOverlayShow }); const isMounted = useIsMounted(); - const [state, setState] = React.useReducer<(prev_state: TCustomState, value: TCustomState) => TCustomState>( + const [state, setState] = useReducer<(prev_state: TCustomState, value: TCustomState) => TCustomState>( (prev_state, value) => { return { ...prev_state, ...value }; }, initial_state ); - React.useEffect(() => { + useEffect(() => { if (is_virtual) { setState({ is_loading: false }); } else { @@ -163,7 +164,7 @@ const SelfExclusion = observer(({ is_app_settings, overlay_ref, setIsOverlayShow // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - React.useEffect(() => { + useEffect(() => { if (prev_is_switching.current !== is_switching) { prev_is_switching.current = is_switching; @@ -174,7 +175,7 @@ const SelfExclusion = observer(({ is_app_settings, overlay_ref, setIsOverlayShow // eslint-disable-next-line react-hooks/exhaustive-deps }, [is_switching]); - React.useEffect(() => { + useEffect(() => { setIsOverlayShown?.(!!state?.show_article); }, [state.show_article, setIsOverlayShown]); diff --git a/packages/account/src/Sections/Security/TwoFactorAuthentication/digit-form.tsx b/packages/account/src/Sections/Security/TwoFactorAuthentication/digit-form.tsx index a6d9b9f50241..cd606a9e3877 100644 --- a/packages/account/src/Sections/Security/TwoFactorAuthentication/digit-form.tsx +++ b/packages/account/src/Sections/Security/TwoFactorAuthentication/digit-form.tsx @@ -1,9 +1,9 @@ -import React from 'react'; +import { useRef, useState, useEffect, ChangeEvent } from 'react'; import clsx from 'clsx'; import { Formik, Form, Field, FormikProps, FormikHelpers, FieldProps } from 'formik'; import { Input, Button } from '@deriv/components'; import { getPropertyValue, WS } from '@deriv/shared'; -import { localize } from '@deriv/translations'; +import { useTranslations } from '@deriv-com/translations'; import { observer, useStore } from '@deriv/stores'; type TResponse = { @@ -19,20 +19,21 @@ type TDigitFormValues = { const DigitForm = observer(() => { const { client, common } = useStore(); + const { localize } = useTranslations(); const { is_language_changing } = common; const { has_enabled_two_fa, setTwoFAChangedStatus, setTwoFAStatus } = client; - const [is_success, setSuccess] = React.useState(false); - const [is_ready_for_verification, setReadyForVerification] = React.useState(false); + const [is_success, setSuccess] = useState(false); + const [is_ready_for_verification, setReadyForVerification] = useState(false); const button_text = has_enabled_two_fa ? localize('Disable') : localize('Enable'); - const formik_ref = React.useRef>(null); + const formik_ref = useRef>(null); let enable_response: TResponse; const initial_form = { digit_code: '', }; - React.useEffect(() => { + useEffect(() => { if (is_language_changing) { formik_ref.current?.setFieldTouched('digit_code'); } @@ -87,7 +88,7 @@ const DigitForm = observer(() => { className='two-factor__input' label={localize('Authentication code')} value={values.digit_code} - onChange={(e: React.ChangeEvent) => { + onChange={(e: ChangeEvent) => { handleChange(e); setReadyForVerification(false); }} diff --git a/packages/account/src/Sections/Security/TwoFactorAuthentication/two-factor-authentication-article.tsx b/packages/account/src/Sections/Security/TwoFactorAuthentication/two-factor-authentication-article.tsx index 8c0d2ce677ba..c8e83b9934e4 100644 --- a/packages/account/src/Sections/Security/TwoFactorAuthentication/two-factor-authentication-article.tsx +++ b/packages/account/src/Sections/Security/TwoFactorAuthentication/two-factor-authentication-article.tsx @@ -1,17 +1,19 @@ -import React from 'react'; -import { localize, Localize } from '@deriv/translations'; +import { Localize, useTranslations } from '@deriv-com/translations'; import AccountArticle from '../../../Components/article'; -const TwoFactorAuthenticationArticle = () => ( - , - ]} - /> -); +const TwoFactorAuthenticationArticle = () => { + const { localize } = useTranslations(); + return ( + , + ]} + /> + ); +}; export default TwoFactorAuthenticationArticle; diff --git a/packages/account/src/Sections/Security/TwoFactorAuthentication/two-factor-authentication.tsx b/packages/account/src/Sections/Security/TwoFactorAuthentication/two-factor-authentication.tsx index d8dff8117e5c..5179d7f94c85 100644 --- a/packages/account/src/Sections/Security/TwoFactorAuthentication/two-factor-authentication.tsx +++ b/packages/account/src/Sections/Security/TwoFactorAuthentication/two-factor-authentication.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import { useState, useCallback, useEffect } from 'react'; import { getPropertyValue, WS } from '@deriv/shared'; import LoadErrorMessage from '../../../Components/load-error-message'; import { observer, useStore } from '@deriv/stores'; @@ -10,13 +10,13 @@ const TwoFactorAuthentication = observer(() => { const { client } = useStore(); const { email_address, getTwoFAStatus, has_enabled_two_fa, is_switching } = client; - const [is_loading, setLoading] = React.useState(true); - const [is_qr_loading, setQrLoading] = React.useState(false); - const [error_message, setErrorMessage] = React.useState(''); - const [secret_key, setSecretKey] = React.useState(''); - const [qr_secret_key, setQrSecretKey] = React.useState(''); + const [is_loading, setLoading] = useState(true); + const [is_qr_loading, setQrLoading] = useState(false); + const [error_message, setErrorMessage] = useState(''); + const [secret_key, setSecretKey] = useState(''); + const [qr_secret_key, setQrSecretKey] = useState(''); - const generateQrCode = React.useCallback(async () => { + const generateQrCode = useCallback(async () => { setQrLoading(true); const generate_response = await WS.authorized.accountSecurity({ account_security: 1, @@ -36,7 +36,7 @@ const TwoFactorAuthentication = observer(() => { setQrLoading(false); }, [email_address]); - const getDigitStatus = React.useCallback(async () => { + const getDigitStatus = useCallback(async () => { const status_response = await getTwoFAStatus(); // status_response can be boolean or an error object if (typeof status_response !== 'boolean') { @@ -50,7 +50,7 @@ const TwoFactorAuthentication = observer(() => { setLoading(false); }, [getTwoFAStatus, generateQrCode]); - React.useEffect(() => { + useEffect(() => { getDigitStatus(); }, [getDigitStatus, has_enabled_two_fa]); diff --git a/packages/account/src/Sections/Security/TwoFactorAuthentication/two-factor-disabled.tsx b/packages/account/src/Sections/Security/TwoFactorAuthentication/two-factor-disabled.tsx index 95dd4099d5ba..fe76800ca50a 100644 --- a/packages/account/src/Sections/Security/TwoFactorAuthentication/two-factor-disabled.tsx +++ b/packages/account/src/Sections/Security/TwoFactorAuthentication/two-factor-disabled.tsx @@ -1,8 +1,8 @@ -import React from 'react'; +import { Fragment } from 'react'; import QRCodeSVG from 'qrcode.react'; import { ThemedScrollbars, Text, Timeline, Loading, Clipboard } from '@deriv/components'; import TwoFactorAuthenticationArticle from './two-factor-authentication-article'; -import { Localize, localize } from '@deriv/translations'; +import { Localize, useTranslations } from '@deriv-com/translations'; import { useDevice } from '@deriv-com/ui'; import DigitForm from './digit-form'; @@ -14,8 +14,9 @@ type TTwoFactorDisabled = { const TwoFactorDisabled = ({ secret_key, qr_secret_key, is_qr_loading }: TTwoFactorDisabled) => { const { isDesktop } = useDevice(); + const { localize } = useTranslations(); return ( - + {!isDesktop && } @@ -51,7 +52,7 @@ const TwoFactorDisabled = ({ secret_key, qr_secret_key, is_qr_loading }: TTwoFac {is_qr_loading ? ( ) : ( - + {qr_secret_key && (
@@ -59,7 +60,7 @@ const TwoFactorDisabled = ({ secret_key, qr_secret_key, is_qr_loading }: TTwoFac )} {secret_key && ( - + @@ -72,9 +73,9 @@ const TwoFactorDisabled = ({ secret_key, qr_secret_key, is_qr_loading }: TTwoFac className='two-factor__qr--clipboard' />
-
+
)} -
+ )} @@ -88,7 +89,7 @@ const TwoFactorDisabled = ({ secret_key, qr_secret_key, is_qr_loading }: TTwoFac {isDesktop && } - + ); }; diff --git a/packages/account/src/Sections/Security/TwoFactorAuthentication/two-factor-enabled.tsx b/packages/account/src/Sections/Security/TwoFactorAuthentication/two-factor-enabled.tsx index d531d3a5338f..68a460806b94 100644 --- a/packages/account/src/Sections/Security/TwoFactorAuthentication/two-factor-enabled.tsx +++ b/packages/account/src/Sections/Security/TwoFactorAuthentication/two-factor-enabled.tsx @@ -1,8 +1,7 @@ -import React from 'react'; import { Icon, ThemedScrollbars, Text } from '@deriv/components'; import { useDevice } from '@deriv-com/ui'; import DigitForm from './digit-form'; -import { Localize } from '@deriv/translations'; +import { Localize } from '@deriv-com/translations'; const TwoFactorEnabled = () => { const { isDesktop } = useDevice(); diff --git a/packages/account/src/Styles/account.scss b/packages/account/src/Styles/account.scss index 3e197ef50b2d..3a593971ccef 100644 --- a/packages/account/src/Styles/account.scss +++ b/packages/account/src/Styles/account.scss @@ -1694,7 +1694,6 @@ $MIN_HEIGHT_FLOATING: calc( color: var(--text-prominent); font-size: var(--text-size-xs); line-height: 1.43; - font-weight: bold; &__title { margin-bottom: 18px; diff --git a/packages/api-v2/src/hooks/__tests__/useOnfido.spec.ts b/packages/api-v2/src/hooks/__tests__/useOnfido.spec.ts index 33243a502b82..2c444e7de297 100644 --- a/packages/api-v2/src/hooks/__tests__/useOnfido.spec.ts +++ b/packages/api-v2/src/hooks/__tests__/useOnfido.spec.ts @@ -52,7 +52,7 @@ describe('useOnfido', () => { // Assert that the necessary data is returned expect(result.current.isOnfidoInitialized).toBe(true); - expect(result.current.isServiceTokenLoading).toBe(false); + expect(result.current.isLoading).toBe(false); expect(result.current.serviceTokenError).toBeNull(); expect(result.current.onfidoInitializationError).toBeNull(); expect(result.current.data.onfidoRef.current).not.toBeNull(); diff --git a/packages/api-v2/src/hooks/index.ts b/packages/api-v2/src/hooks/index.ts index 4b643d03b685..922687b4f016 100644 --- a/packages/api-v2/src/hooks/index.ts +++ b/packages/api-v2/src/hooks/index.ts @@ -79,3 +79,4 @@ export { default as useExchangeRates } from './useExchangeRates'; export { default as useIsDIELEnabled } from './useIsDIELEnabled'; export { default as useKycAuthStatus } from './useKycAuthStatus'; export { default as useClientCountry } from './useClientCountry'; +export { DocumentUploadStatus } from './useDocumentUpload'; diff --git a/packages/api-v2/src/hooks/useCryptoTransactions.ts b/packages/api-v2/src/hooks/useCryptoTransactions.ts index 5bfd032d6801..f061c492ffb3 100644 --- a/packages/api-v2/src/hooks/useCryptoTransactions.ts +++ b/packages/api-v2/src/hooks/useCryptoTransactions.ts @@ -1,5 +1,4 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; -import { getTruncatedString } from '@deriv/utils'; import useSubscription from '../useSubscription'; import useActiveAccount from './useActiveAccount'; import useAuthorize from './useAuthorize'; @@ -23,83 +22,10 @@ type TModifiedTransaction = Omit { - switch (transaction.status_code) { - case 'CONFIRMED': - return 'Confirmed'; - case 'ERROR': - return 'NA'; - default: - return transaction.confirmations ?? 'Pending'; - } -}; - -const getStatusName = (status_code: TModifiedTransaction['status_code']) => { - switch (status_code) { - case 'CONFIRMED': - case 'SENT': - return 'Successful'; - case 'ERROR': - case 'REJECTED': - case 'REVERTED': - return 'Unsuccessful'; - case 'PENDING': - case 'PERFORMING_BLOCKCHAIN_TXN': - case 'PROCESSING': - case 'REVERTING': - case 'VERIFIED': - return 'In process'; - case 'CANCELLED': - return 'Cancelled'; - case 'LOCKED': - return 'In review'; - default: - return ''; - } -}; - -const getStatusDescription = ( - transaction_type: TModifiedTransaction['transaction_type'], - status_code: TModifiedTransaction['status_code'] -) => { - switch (status_code) { - // deposit-specific: - case 'CONFIRMED': - return 'Your deposit is successful.'; - case 'PENDING': - return "We've received your request and are waiting for more blockchain confirmations."; - // withdrawal-specific: - case 'CANCELLED': - return "You've cancelled your withdrawal request."; - case 'LOCKED': - return "We're reviewing your withdrawal request. You may still cancel this transaction if you wish.\nOnce we start processing, you won't be able to cancel."; - case 'PERFORMING_BLOCKCHAIN_TXN': - return "We're sending your request to the blockchain."; - case 'PROCESSING': - return "We're awaiting confirmation from the blockchain."; - case 'REJECTED': - case 'REVERTED': - return "Your withdrawal is unsuccessful. We've sent you an email with more information."; - case 'REVERTING': - case 'VERIFIED': - return "We're processing your withdrawal."; - case 'SENT': - return 'Your withdrawal is successful.'; - // both: - case 'ERROR': - return `Your ${transaction_type} is unsuccessful due to an error on the blockchain. Please contact ${ - transaction_type === 'deposit' ? 'your crypto wallet service provider' : 'us via live chat' - } for more info.`; - default: - return ''; - } -}; - /** A custom hook that returns the list of pending crypto transactions for the current user. */ const useCryptoTransactions = () => { const { subscribe, data, ...rest } = useSubscription('cashier_payments'); const [transactions, setTransactions] = useState(); - const { data: { preferred_language }, } = useAuthorize(); @@ -146,29 +72,15 @@ const useCryptoTransactions = () => { return transactions.map(transaction => ({ ...transaction, - /** Description of a transaction status */ - description: getStatusDescription(transaction.transaction_type, transaction.status_code), /** Formatted amount */ formatted_amount: displayMoney(transaction.amount || 0, display_code, { fractional_digits, preferred_language, }), - /** Formatted transaction hash */ - formatted_transaction_hash: transaction.transaction_hash - ? getTruncatedString(transaction.transaction_hash, { type: 'middle' }) - : 'Pending', - /** Formatted address hash */ - formatted_address_hash: transaction.address_hash - ? getTruncatedString(transaction.address_hash, { type: 'middle' }) - : 'NA', - /** Formatted confirmations status */ - formatted_confirmations: getFormattedConfirmations(transaction), /** Determine if the transaction is a deposit or not. */ is_deposit: transaction.transaction_type === 'deposit', /** Determine if the transaction is a withdrawal or not. */ is_withdrawal: transaction.transaction_type === 'withdrawal', - /** Status name */ - status_name: getStatusName(transaction.status_code), })); }, [display_code, fractional_digits, preferred_language, transactions]); diff --git a/packages/api-v2/src/hooks/useDocumentUpload.ts b/packages/api-v2/src/hooks/useDocumentUpload.ts index 424d2ae111c0..18de2627d808 100644 --- a/packages/api-v2/src/hooks/useDocumentUpload.ts +++ b/packages/api-v2/src/hooks/useDocumentUpload.ts @@ -1,84 +1,151 @@ -import { useCallback, useMemo, useState } from 'react'; -import useMutation from '../useMutation'; -import { compressImageFile, generateChunks, numToUint8Array, readFile } from '../utils'; +import { useState } from 'react'; import md5 from 'md5'; -import useAPI from '../useAPI'; +import { TSocketError, TSocketRequestPayload, TSocketResponse } from '../../types'; +import { useAPIContext } from '../APIProvider'; +import { compressImageFile, generateChunks, numToUint8Array, readFile } from '../utils'; -type TDocumentUploadPayload = Parameters>['mutate']>[0]['payload']; -type TUploadPayload = Omit & { - file?: File; +type TDocumentUploadRequest = TSocketRequestPayload<'document_upload'>; +type TDocumentUploadRequestPayload = Partial & { file?: File }; +type TDocumentUploadResponse = TSocketResponse<'document_upload'> & TSocketError<'document_upload'>; + +type TFileInfo = { + fileBuffer: Uint8Array; + fileType: File['type']; }; -/** A custom hook to handle document file uploads to our backend. */ +export enum DocumentUploadStatus { + LOADING = 'loading', + IDLE = 'idle', + ERROR = 'error', + SUCCESS = 'success', +} + +const REQ_TIMEOUT = 20000; + const useDocumentUpload = () => { - const { - data, - isLoading: _isLoading, - isSuccess: _isSuccess, - mutateAsync, - status, - ...rest - } = useMutation('document_upload'); - const [isDocumentUploaded, setIsDocumentUploaded] = useState(false); - - const { connection } = useAPI(); - - const isLoading = _isLoading || (!isDocumentUploaded && status === 'success'); - const isSuccess = _isSuccess && isDocumentUploaded; - - const upload = useCallback( - async (payload: TUploadPayload) => { - if (!payload?.file) return Promise.reject(new Error('No file selected')); - const file = payload.file; - delete payload.file; - const fileBlob = await compressImageFile(file); - const modifiedFile = await readFile(fileBlob); - // @ts-expect-error type mismatch - const fileBuffer = new Uint8Array(modifiedFile.buffer); - const checksum = md5(Array.from(fileBuffer)); - - const updatedPayload = { - ...payload, - document_format: file.type - .split('/')[1] - .toLocaleUpperCase() as TDocumentUploadPayload['document_format'], - expected_checksum: checksum, - file_size: fileBuffer.length, - passthrough: { - document_upload: true, - }, + const { wsClient, connection } = useAPIContext(); + const [status, setStatus] = useState(DocumentUploadStatus.IDLE); + + const getFileInfo = async (file: TDocumentUploadRequestPayload['file']): Promise => { + if (!file) return Promise.reject(new Error('No file selected')); + + const fileType = file.type; + const fileBlob = await compressImageFile(file); + const modifiedFile = await readFile(fileBlob); + // @ts-expect-error type mismatch + const fileBuffer = new Uint8Array(modifiedFile.buffer); + return { fileBuffer, fileType }; + }; + + /** Perform the initial handshake to get the upload_id from BE */ + const handshake = async ({ fileType, fileBuffer }: TFileInfo, payload: TDocumentUploadRequestPayload) => { + const checksum = md5(Array.from(fileBuffer)); + + const updatedPayload = { + ...payload, + document_format: fileType + .split('/')[1] + .toLocaleUpperCase() as TDocumentUploadRequestPayload['document_format'], + expected_checksum: checksum, + file_size: fileBuffer.length, + passthrough: { + document_upload: true, + }, + }; + + try { + const response = (await wsClient.request( + 'document_upload', + updatedPayload + )) as Promise; + return response; + } catch (error) { + return error as TDocumentUploadResponse; + } + }; + + /** asynchronously sends file data over WS */ + const sendFile = (fileBuffer: TFileInfo['fileBuffer'], response: TDocumentUploadResponse) => { + const chunks = generateChunks(fileBuffer, {}); + const id = numToUint8Array(response?.document_upload?.upload_id || 0); + const type = numToUint8Array(response?.document_upload?.call_type || 0); + + chunks.forEach(chunk => { + const size = numToUint8Array(chunk.length); + const payload = new Uint8Array([...type, ...id, ...size, ...chunk]); + connection?.send(payload); + }); + }; + + /** Initiates file upload and handles the 2nd response received */ + const fileUploader = async (fileBuffer: TFileInfo['fileBuffer'], response: TDocumentUploadResponse) => { + /** Request id of the initial document_upload call */ + const reqId = response.req_id; + /** Upload id received from BE for the particular file which is appended to every chunk uploaded */ + const uploadId = response.document_upload?.upload_id; + /** Timeout reference for removing WS eventListener */ + let timeout: NodeJS.Timeout; + + return new Promise((resolve, reject) => { + timeout = setTimeout(() => { + wsClient.ws?.removeEventListener('message', handleUploadStatus); + reject(new Error(`Request timeout for document_upload`)); + }, REQ_TIMEOUT); + + const handleUploadStatus = (messageEvent: MessageEvent) => { + const data = JSON.parse(messageEvent.data) as TDocumentUploadResponse; + + if (data.req_id !== reqId && data.document_upload?.upload_id !== uploadId) { + return; + } + if (data.error) { + wsClient.ws?.removeEventListener('message', handleUploadStatus); + clearTimeout(timeout); + setStatus(DocumentUploadStatus.ERROR); + reject(data); + return; + } + + if (data.document_upload && data.document_upload?.status === 'failure') { + wsClient.ws?.removeEventListener('message', handleUploadStatus); + + clearTimeout(timeout); + setStatus(DocumentUploadStatus.ERROR); + reject(data); + return; + } + + if (data.document_upload && data.document_upload?.status === 'success') { + wsClient.ws?.removeEventListener('message', handleUploadStatus); + clearTimeout(timeout); + setStatus(DocumentUploadStatus.SUCCESS); + resolve(data); + } }; - setIsDocumentUploaded(false); - await mutateAsync({ payload: updatedPayload }).then(async res => { - const chunks = generateChunks(fileBuffer, {}); - const id = numToUint8Array(res?.document_upload?.upload_id || 0); - const type = numToUint8Array(res?.document_upload?.call_type || 0); - - chunks.forEach(chunk => { - const size = numToUint8Array(chunk.length); - const payload = new Uint8Array([...type, ...id, ...size, ...chunk]); - connection?.send(payload); - }); - setIsDocumentUploaded(true); - }); - }, - [connection, mutateAsync] - ); - - const modified_response = useMemo(() => ({ ...data?.document_upload }), [data?.document_upload]); + + wsClient.ws?.addEventListener('message', handleUploadStatus); + + sendFile(fileBuffer, response); + }) as Promise; + }; + + const upload = async (payload: TDocumentUploadRequestPayload) => { + setStatus(DocumentUploadStatus.LOADING); + const { file, ...rest } = payload; + const fileInfo = await getFileInfo(file); + const handshakeResponse = await handshake(fileInfo, rest); + if (handshakeResponse.error) { + setStatus(DocumentUploadStatus.ERROR); + return Promise.reject(handshakeResponse); + } + const uploadResponse = await fileUploader(fileInfo.fileBuffer, handshakeResponse); + return Promise.resolve(uploadResponse); + }; return { - /** The upload response */ - data: modified_response, - /** Function to upload the document */ upload, - /** Mutation status */ status, - /** Whether the mutation is loading */ - isLoading, - /** Whether the mutation is successful */ - isSuccess, - ...rest, + resetStatus: setStatus, }; }; diff --git a/packages/api-v2/src/hooks/useOnfido.ts b/packages/api-v2/src/hooks/useOnfido.ts index c1a787785455..9ea18790a770 100644 --- a/packages/api-v2/src/hooks/useOnfido.ts +++ b/packages/api-v2/src/hooks/useOnfido.ts @@ -183,7 +183,7 @@ const useOnfido = (country?: string, selectedDocument?: string) => { hasSubmitted, }, isOnfidoInitialized, - isServiceTokenLoading, + isLoading: isServiceTokenLoading || isOnfidoLoading, serviceTokenError, onfidoInitializationError, }; diff --git a/packages/api-v2/src/hooks/usePOI.ts b/packages/api-v2/src/hooks/usePOI.ts index c672af19cfac..6bee99a6dfde 100644 --- a/packages/api-v2/src/hooks/usePOI.ts +++ b/packages/api-v2/src/hooks/usePOI.ts @@ -9,36 +9,13 @@ const usePOI = () => { const { data: residence_list_data, isSuccess: isResidenceListSuccess } = useResidenceList(); const { data: get_settings_data, isSuccess: isGetSettingsSuccess } = useSettings(); - const previous_service = useMemo(() => { - const latest_poi_attempt = authentication_data?.attempts?.latest; - return latest_poi_attempt?.service; - }, [authentication_data?.attempts?.latest]); - /** * @description Get the previous POI attempts details (if any) */ - const previous_poi = useMemo(() => { - if (!previous_service) { - return null; - } - - const services = authentication_data?.identity?.services; - if (services && services.manual) { - return { - service: previous_service, - status: services.manual.status, - }; - } - - const current_service = services?.[previous_service as 'idv' | 'onfido']; - return { - service: previous_service, - status: current_service?.status, - reported_properties: current_service?.reported_properties, - last_rejected: current_service?.last_rejected, - submissions_left: current_service?.submissions_left || 0, - }; - }, [previous_service, authentication_data?.identity?.services]); + const previous_service = useMemo(() => { + const latest_poi_attempt = authentication_data?.attempts?.latest; + return latest_poi_attempt; + }, [authentication_data?.attempts?.latest]); /** * @description Get the current step based on a few checks. Returns configuration for document validation as well. @@ -86,7 +63,7 @@ const usePOI = () => { return { ...authentication_data?.identity, - previous: previous_poi, + previous: previous_service, current: current_poi, is_pending: authentication_data?.identity?.status === 'pending', is_rejected: authentication_data?.identity?.status === 'rejected', @@ -94,7 +71,7 @@ const usePOI = () => { is_suspected: authentication_data?.identity?.status === 'suspected', is_verified: authentication_data?.identity?.status === 'verified', }; - }, [authentication_data, current_poi, previous_poi]); + }, [authentication_data, current_poi, previous_service]); return { data: modified_verification_data, diff --git a/packages/appstore/package.json b/packages/appstore/package.json index 3904f1726f76..c0517eb890f7 100644 --- a/packages/appstore/package.json +++ b/packages/appstore/package.json @@ -27,8 +27,8 @@ "license": "Apache-2.0", "dependencies": { "@deriv-com/analytics": "1.11.0", - "@deriv-com/translations": "1.3.4", - "@deriv-com/ui": "1.29.9", + "@deriv-com/translations": "1.3.5", + "@deriv-com/ui": "1.29.10", "@deriv/account": "^1.0.0", "@deriv/cashier": "^1.0.0", "@deriv/cfd": "^1.0.0", @@ -45,8 +45,7 @@ "react-content-loader": "^6.2.0", "react-joyride": "^2.5.3", "react-router": "^5.2.0", - "react-router-dom": "^5.2.0", - "@cloudflare/stream-react": "^1.9.1" + "react-router-dom": "^5.2.0" }, "devDependencies": { "@babel/eslint-parser": "^7.17.0", diff --git a/packages/appstore/src/components/account-transfer-modal/__tests__/account-transfer-modal.spec.tsx b/packages/appstore/src/components/account-transfer-modal/__tests__/account-transfer-modal.spec.tsx index a20f72c5cdc3..dee3ad94b9b4 100644 --- a/packages/appstore/src/components/account-transfer-modal/__tests__/account-transfer-modal.spec.tsx +++ b/packages/appstore/src/components/account-transfer-modal/__tests__/account-transfer-modal.spec.tsx @@ -2,6 +2,7 @@ import React from 'react'; import AccountTransferModal from '../account-transfer-modal'; import { render, screen } from '@testing-library/react'; import { StoreProvider, mockStore } from '@deriv/stores'; +import { MemoryRouter } from 'react-router-dom'; jest.mock('@deriv/cashier/src/pages/account-transfer', () => jest.fn(() =>
AccountTransfer
)); @@ -24,6 +25,7 @@ describe('AccountTransferModal', () => { account_transfer: { is_transfer_confirm: false, should_switch_account: false, + error: '', }, general_store: { setActiveTab: jest.fn() }, }, @@ -50,6 +52,7 @@ describe('AccountTransferModal', () => { account_transfer: { is_transfer_confirm: false, should_switch_account: false, + error: '', }, general_store: { setActiveTab: jest.fn() }, }, @@ -77,6 +80,7 @@ describe('AccountTransferModal', () => { account_transfer: { is_transfer_confirm: false, should_switch_account: true, + error: '', }, general_store: { setActiveTab: jest.fn() }, }, @@ -99,4 +103,38 @@ describe('AccountTransferModal', () => { 'dc-modal-header__title--account-transfer-modal' ); }); + + it('should open error dialog when financial assessment error is present', async () => { + const mock = mockStore({ + modules: { + cashier: { + account_transfer: { + is_transfer_confirm: false, + should_switch_account: true, + error: { + code: 'FinancialAssessmentRequired', + message: 'Please complete your financial assessment.', + }, + }, + general_store: { setActiveTab: jest.fn() }, + }, + }, + }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + + {children} + + ); + + render( + , + { + wrapper, + } + ); + + expect(screen.getByText('Cashier Error')).toBeInTheDocument(); + expect(screen.getByText(/financial assessment/i)).toBeInTheDocument(); + }); }); diff --git a/packages/appstore/src/components/account-transfer-modal/account-transfer-modal.tsx b/packages/appstore/src/components/account-transfer-modal/account-transfer-modal.tsx index 8f7b1504e8f4..1be8581829cd 100644 --- a/packages/appstore/src/components/account-transfer-modal/account-transfer-modal.tsx +++ b/packages/appstore/src/components/account-transfer-modal/account-transfer-modal.tsx @@ -1,9 +1,9 @@ import React from 'react'; -import { useHistory } from 'react-router-dom'; -import { Modal } from '@deriv/components'; +import { Link, useHistory } from 'react-router-dom'; +import { Modal, Dialog } from '@deriv/components'; import { routes } from '@deriv/shared'; import { useStore, observer } from '@deriv/stores'; -import { Localize } from '@deriv/translations'; +import { Localize, localize } from '@deriv/translations'; import AccountTransfer from '@deriv/cashier/src/pages/account-transfer'; import './account-transfer-modal.scss'; @@ -16,17 +16,19 @@ const AccountTransferModal = observer(({ is_modal_open, toggleModal }: TAccountT const { modules: { cashier: { - account_transfer: { is_transfer_confirm, should_switch_account, setShouldSwitchAccount }, + account_transfer: { is_transfer_confirm, should_switch_account, setShouldSwitchAccount, error }, general_store: { setActiveTab }, }, }, traders_hub: { closeModal, setSelectedAccount }, } = useStore(); + const [is_error_dialog_open, setIsErrorDialogOpen] = React.useState(false); const history = useHistory(); React.useEffect(() => { if (is_modal_open) setActiveTab('account_transfer'); + if (error.code === 'FinancialAssessmentRequired') setIsErrorDialogOpen(true); return () => { if (is_modal_open) { @@ -35,9 +37,10 @@ const AccountTransferModal = observer(({ is_modal_open, toggleModal }: TAccountT setActiveTab('deposit'); closeModal(); } + if (error.code === 'FinancialAssessmentRequired') setIsErrorDialogOpen(false); }; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [is_modal_open]); + }, [is_modal_open, error.code]); const modal_title = !is_transfer_confirm && ; @@ -51,6 +54,32 @@ const AccountTransferModal = observer(({ is_modal_open, toggleModal }: TAccountT history.push(routes.cashier_acc_transfer); }; + const dismissError = () => { + toggleModal(); + setIsErrorDialogOpen(false); + error.setErrorMessage({ code: '', message: '' }, null, false); + }; + + if (is_error_dialog_open) { + return ( + + , + ]} + /> + + ); + } + return ( { const eu_user = content_flag === ContentFlag.LOW_RISK_CR_EU || content_flag === ContentFlag.EU_REAL; + const [is_traders_dashboard_tracking_enabled] = useGrowthbookGetFeatureValue({ + featureFlag: 'ce_tradershub_dashboard_tracking', + defaultValue: false, + }); + return (
@@ -30,12 +36,15 @@ const AddOptions = observer(() => { type='submit' has_effect onClick={() => { - Analytics.trackEvent('ce_tradershub_dashboard_form', { - action: 'account_get', - form_name: 'traders_hub_default', - account_mode: selected_account_type, - account_name: 'cfd_banner', - }); + if (is_traders_dashboard_tracking_enabled) { + Analytics.trackEvent('ce_tradershub_dashboard_form', { + action: 'account_get', + form_name: 'traders_hub_default', + account_mode: selected_account_type, + account_name: 'cfd_banner', + }); + } + if (is_real && eu_user) { if (real_account_creation_unlock_date) { setShouldShowCooldownModal(true); diff --git a/packages/appstore/src/components/app-content.tsx b/packages/appstore/src/components/app-content.tsx index 2b1acd225114..e74851a13216 100644 --- a/packages/appstore/src/components/app-content.tsx +++ b/packages/appstore/src/components/app-content.tsx @@ -1,5 +1,6 @@ import React, { useEffect } from 'react'; import { routes } from '@deriv/shared'; +import { useGrowthbookGetFeatureValue } from '@deriv/hooks'; import { observer, useStore } from '@deriv/stores'; import { Analytics } from '@deriv-com/analytics'; import Routes from 'Components/routes/routes'; @@ -8,17 +9,28 @@ import './app.scss'; import './temporary-overrides.scss'; const AppContent: React.FC = observer(() => { - const { ui, traders_hub } = useStore(); + const { ui, traders_hub, client } = useStore(); const { is_dark_mode_on } = ui; const { selected_account_type } = traders_hub; + const [is_traders_dashboard_tracking_enabled] = useGrowthbookGetFeatureValue({ + featureFlag: 'ce_tradershub_dashboard_tracking', + defaultValue: false, + }); + + useEffect(() => { + client.setTradersHubTracking(is_traders_dashboard_tracking_enabled); + }, [is_traders_dashboard_tracking_enabled]); + useEffect(() => { if (selected_account_type) { - Analytics.trackEvent('ce_tradershub_dashboard_form', { - action: 'open', - form_name: 'traders_hub_default', - account_mode: selected_account_type, - }); + if (is_traders_dashboard_tracking_enabled) { + Analytics.trackEvent('ce_tradershub_dashboard_form', { + action: 'open', + form_name: 'traders_hub_default', + account_mode: selected_account_type, + }); + } } }, [selected_account_type]); diff --git a/packages/appstore/src/components/banners/business-closure-banner/__tests__/business-closure-banner.spec.tsx b/packages/appstore/src/components/banners/business-closure-banner/__tests__/business-closure-banner.spec.tsx new file mode 100644 index 000000000000..cc025a2fd2b4 --- /dev/null +++ b/packages/appstore/src/components/banners/business-closure-banner/__tests__/business-closure-banner.spec.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { StoreProvider, mockStore } from '@deriv/stores'; +import BusinessClosureBanner from '../business-closure-banner'; + +describe('', () => { + const mockDefault = mockStore({ client: { is_account_to_be_closed_by_residence: false } }); + + const wrapper = (mock: ReturnType = mockDefault) => { + const Component = ({ children }: { children: JSX.Element }) => ( + {children} + ); + return Component; + }; + + it('should render nothing by default', () => { + render(, { + wrapper: wrapper(), + }); + + expect(screen.queryByText(/Due to business changes/)).not.toBeInTheDocument(); + }); + + it('should render banner content if is_account_to_be_closed_by_residence === true ', () => { + const mock = mockStore({ client: { is_account_to_be_closed_by_residence: true } }); + + render(, { + wrapper: wrapper(mock), + }); + + expect(screen.queryByText(/Due to business changes/)).toBeInTheDocument(); + }); +}); diff --git a/packages/appstore/src/components/banners/business-closure-banner/business-closure-banner.scss b/packages/appstore/src/components/banners/business-closure-banner/business-closure-banner.scss new file mode 100644 index 000000000000..5775fc55e842 --- /dev/null +++ b/packages/appstore/src/components/banners/business-closure-banner/business-closure-banner.scss @@ -0,0 +1,14 @@ +.business-closure-banner { + padding-inline: 1.6rem; + // One more solution just change the color and make it without opacity instead of height calc (background-color: #fff4e4;) + + @include desktop-screen { + position: sticky; + top: 0; + z-index: 2; + + & + div { + height: calc(100vh - $HEADER_HEIGHT - $FOOTER_HEIGHT - 3.4rem); + } + } +} diff --git a/packages/appstore/src/components/banners/business-closure-banner/business-closure-banner.tsx b/packages/appstore/src/components/banners/business-closure-banner/business-closure-banner.tsx new file mode 100644 index 000000000000..178dd2b55e12 --- /dev/null +++ b/packages/appstore/src/components/banners/business-closure-banner/business-closure-banner.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { InlineMessage, Text } from '@deriv-com/ui'; +import { observer, useStore } from '@deriv/stores'; +import { Localize } from '@deriv/translations'; +import { formatDate } from '@deriv/shared'; +import './business-closure-banner.scss'; + +const BusinessClosureBanner = observer(() => { + const { client } = useStore(); + const { is_account_to_be_closed_by_residence, account_time_of_closure } = client; + + if (!is_account_to_be_closed_by_residence) return null; + + return ( + + + + + + ); +}); + +export default BusinessClosureBanner; diff --git a/packages/appstore/src/components/banners/business-closure-banner/index.ts b/packages/appstore/src/components/banners/business-closure-banner/index.ts new file mode 100644 index 000000000000..aec0d25a54cd --- /dev/null +++ b/packages/appstore/src/components/banners/business-closure-banner/index.ts @@ -0,0 +1,3 @@ +import BusinessClosureBanner from './business-closure-banner'; + +export default BusinessClosureBanner; diff --git a/packages/appstore/src/components/cfds-listing/cfds-listing.scss b/packages/appstore/src/components/cfds-listing/cfds-listing.scss index 74adf423407e..0dfc32050ed2 100644 --- a/packages/appstore/src/components/cfds-listing/cfds-listing.scss +++ b/packages/appstore/src/components/cfds-listing/cfds-listing.scss @@ -36,6 +36,8 @@ } } &__footer-button { + width: 100%; + display: flex; background-color: var(--general-main-1); button { height: 4rem; @@ -45,6 +47,7 @@ } } @include mobile-or-tablet-screen { + padding: 1rem; bottom: 0; position: sticky; justify-content: center; @@ -1530,9 +1533,11 @@ padding: 0.8rem 2rem 2.4rem; } &__body { - padding: 8.9rem 2.4rem; - &-has-open-positions { - padding: 6rem 2.4rem; + padding: 5.2rem 2.4rem; + &-bullets--list { + list-style: disc; + margin-top: 1rem; + margin-inline-start: 2rem; } } &__title { @@ -1573,6 +1578,9 @@ width: 100%; } } + @include mobile-screen { + bottom: 4rem; + } } } @@ -2023,15 +2031,6 @@ flex: 1; } } - & .dc-password-meter__container { - flex-grow: 1; - margin: auto; - - @include mobile-or-tablet-screen { - width: calc(100vw - 4.8rem); - max-width: 30rem; - } - } & .mt5-password-field { margin-bottom: 1em; width: 80%; @@ -2466,4 +2465,9 @@ border-bottom: 1px solid var(--general-hover); } } + @include mobile-or-tablet-screen { + &__footer-button { + padding: 1rem; + } + } } diff --git a/packages/appstore/src/components/cfds-listing/index.tsx b/packages/appstore/src/components/cfds-listing/index.tsx index a18ae85135c2..4cf6b62f0703 100644 --- a/packages/appstore/src/components/cfds-listing/index.tsx +++ b/packages/appstore/src/components/cfds-listing/index.tsx @@ -20,7 +20,7 @@ import PlatformLoader from 'Components/pre-loader/platform-loader'; import CompareAccount from 'Components/compare-account'; import CFDsDescription from 'Components/elements/cfds-description'; import { getHasDivider } from 'Constants/utils'; -import { useMT5SVGEligibleToMigrate } from '@deriv/hooks'; +import { useMT5SVGEligibleToMigrate, useGrowthbookGetFeatureValue } from '@deriv/hooks'; import './cfds-listing.scss'; const MigrationBanner = makeLazyLoader( @@ -88,6 +88,11 @@ const CFDsListing = observer(() => { is_idv_revoked, } = getAuthenticationStatusInfo(account_status); + const [is_traders_dashboard_tracking_enabled] = useGrowthbookGetFeatureValue({ + featureFlag: 'ce_tradershub_dashboard_tracking', + defaultValue: false, + }); + const { has_svg_accounts_to_migrate } = useMT5SVGEligibleToMigrate(); const getAuthStatus = (status_list: boolean[]) => status_list.some(status => status); @@ -236,12 +241,15 @@ const CFDsListing = observer(() => { has_divider={(!is_eu_user || is_demo) && getHasDivider(index, list_size, 3)} onAction={(e?: React.MouseEvent) => { if (existing_account.action_type === 'get') { - Analytics.trackEvent('ce_tradershub_dashboard_form', { - action: 'account_get', - form_name: 'traders_hub_default', - account_mode: selected_account_type, - account_name: track_account_subtitle, - }); + if (is_traders_dashboard_tracking_enabled) { + Analytics.trackEvent('ce_tradershub_dashboard_form', { + action: 'account_get', + form_name: 'traders_hub_default', + account_mode: selected_account_type, + account_name: track_account_subtitle, + }); + } + if (real_account_creation_unlock_date && no_real_mf_account_eu_regulator) { setShouldShowCooldownModal(true); } else if (no_real_cr_non_eu_regulator || no_real_mf_account_eu_regulator) { @@ -259,30 +267,39 @@ const CFDsListing = observer(() => { const button_name = e?.currentTarget?.name; setProduct(existing_account.product); if (button_name === 'transfer-btn') { - Analytics.trackEvent('ce_tradershub_dashboard_form', { - action: 'account_transfer', - form_name: 'traders_hub_default', - account_mode: selected_account_type, - account_name: track_account_subtitle, - }); + if (is_traders_dashboard_tracking_enabled) { + Analytics.trackEvent('ce_tradershub_dashboard_form', { + action: 'account_transfer', + form_name: 'traders_hub_default', + account_mode: selected_account_type, + account_name: track_account_subtitle, + }); + } + toggleAccountTransferModal(); setSelectedAccount(existing_account); } else if (button_name === 'topup-btn') { - Analytics.trackEvent('ce_tradershub_dashboard_form', { - action: 'account_topup', - form_name: 'traders_hub_default', - account_mode: selected_account_type, - account_name: track_account_subtitle, - }); + if (is_traders_dashboard_tracking_enabled) { + Analytics.trackEvent('ce_tradershub_dashboard_form', { + action: 'account_topup', + form_name: 'traders_hub_default', + account_mode: selected_account_type, + account_name: track_account_subtitle, + }); + } + showTopUpModal(existing_account); setAppstorePlatform(existing_account.platform); } else { - Analytics.trackEvent('ce_tradershub_dashboard_form', { - action: 'account_open', - form_name: 'traders_hub_default', - account_mode: selected_account_type, - account_name: track_account_subtitle, - }); + if (is_traders_dashboard_tracking_enabled) { + Analytics.trackEvent('ce_tradershub_dashboard_form', { + action: 'account_open', + form_name: 'traders_hub_default', + account_mode: selected_account_type, + account_name: track_account_subtitle, + }); + } + if (has_mt5_account_status === MT5_ACCOUNT_STATUS.FAILED && is_eu_user) { setIsMT5VerificationFailedModal(true); openFailedVerificationModal(existing_account); @@ -342,29 +359,38 @@ const CFDsListing = observer(() => { const button_name = e?.currentTarget?.name; setProduct(); if (button_name === 'transfer-btn') { - Analytics.trackEvent('ce_tradershub_dashboard_form', { - action: 'account_transfer', - form_name: 'traders_hub_default', - account_mode: selected_account_type, - account_name: track_account_name, - }); + if (is_traders_dashboard_tracking_enabled) { + Analytics.trackEvent('ce_tradershub_dashboard_form', { + action: 'account_transfer', + form_name: 'traders_hub_default', + account_mode: selected_account_type, + account_name: track_account_name, + }); + } + toggleCTraderTransferModal(); } else if (button_name === 'topup-btn') { - Analytics.trackEvent('ce_tradershub_dashboard_form', { - action: 'account_topup', - form_name: 'traders_hub_default', - account_mode: selected_account_type, - account_name: track_account_name, - }); + if (is_traders_dashboard_tracking_enabled) { + Analytics.trackEvent('ce_tradershub_dashboard_form', { + action: 'account_topup', + form_name: 'traders_hub_default', + account_mode: selected_account_type, + account_name: track_account_name, + }); + } + showTopUpModal(existing_account); setAppstorePlatform(account.platform); } else { - Analytics.trackEvent('ce_tradershub_dashboard_form', { - action: 'account_open', - form_name: 'traders_hub_default', - account_mode: selected_account_type, - account_name: track_account_name, - }); + if (is_traders_dashboard_tracking_enabled) { + Analytics.trackEvent('ce_tradershub_dashboard_form', { + action: 'account_open', + form_name: 'traders_hub_default', + account_mode: selected_account_type, + account_name: track_account_name, + }); + } + startTrade(account.platform, existing_account); } }} @@ -381,12 +407,14 @@ const CFDsListing = observer(() => { description={account.description} onAction={() => { setProduct(); - Analytics.trackEvent('ce_tradershub_dashboard_form', { - action: 'account_get', - form_name: 'traders_hub_default', - account_mode: selected_account_type, - account_name: track_account_name, - }); + if (is_traders_dashboard_tracking_enabled) { + Analytics.trackEvent('ce_tradershub_dashboard_form', { + action: 'account_get', + form_name: 'traders_hub_default', + account_mode: selected_account_type, + account_name: track_account_name, + }); + } if ((has_no_real_account || no_CR_account) && is_real) { openDerivRealAccountNeededModal(); } else { @@ -443,30 +471,39 @@ const CFDsListing = observer(() => { const button_name = e?.currentTarget?.name; setProduct(); if (button_name === 'transfer-btn') { - Analytics.trackEvent('ce_tradershub_dashboard_form', { - action: 'account_transfer', - form_name: 'traders_hub_default', - account_mode: selected_account_type, - account_name: track_account_name, - }); + if (is_traders_dashboard_tracking_enabled) { + Analytics.trackEvent('ce_tradershub_dashboard_form', { + action: 'account_transfer', + form_name: 'traders_hub_default', + account_mode: selected_account_type, + account_name: track_account_name, + }); + } + toggleAccountTransferModal(); setSelectedAccount(existing_account); } else if (button_name === 'topup-btn') { - Analytics.trackEvent('ce_tradershub_dashboard_form', { - action: 'account_topup', - form_name: 'traders_hub_default', - account_mode: selected_account_type, - account_name: track_account_name, - }); + if (is_traders_dashboard_tracking_enabled) { + Analytics.trackEvent('ce_tradershub_dashboard_form', { + action: 'account_topup', + form_name: 'traders_hub_default', + account_mode: selected_account_type, + account_name: track_account_name, + }); + } + showTopUpModal(existing_account); setAppstorePlatform(account.platform); } else { - Analytics.trackEvent('ce_tradershub_dashboard_form', { - action: 'account_open', - form_name: 'traders_hub_default', - account_mode: selected_account_type, - account_name: track_account_name, - }); + if (is_traders_dashboard_tracking_enabled) { + Analytics.trackEvent('ce_tradershub_dashboard_form', { + action: 'account_open', + form_name: 'traders_hub_default', + account_mode: selected_account_type, + account_name: track_account_name, + }); + } + startTrade(account.platform, existing_account); } }} @@ -483,12 +520,14 @@ const CFDsListing = observer(() => { description={account.description} onAction={() => { setProduct(); - Analytics.trackEvent('ce_tradershub_dashboard_form', { - action: 'account_get', - form_name: 'traders_hub_default', - account_mode: selected_account_type, - account_name: track_account_name, - }); + if (is_traders_dashboard_tracking_enabled) { + Analytics.trackEvent('ce_tradershub_dashboard_form', { + action: 'account_get', + form_name: 'traders_hub_default', + account_mode: selected_account_type, + account_name: track_account_name, + }); + } if ((has_no_real_account || no_CR_account) && is_real) { openDerivRealAccountNeededModal(); } else { diff --git a/packages/appstore/src/components/compare-account/compare-account.tsx b/packages/appstore/src/components/compare-account/compare-account.tsx index 723b8d14cff7..4072a18df010 100644 --- a/packages/appstore/src/components/compare-account/compare-account.tsx +++ b/packages/appstore/src/components/compare-account/compare-account.tsx @@ -4,6 +4,7 @@ import { Localize } from '@deriv/translations'; import { Analytics } from '@deriv-com/analytics'; import { useHistory } from 'react-router-dom'; import { routes } from '@deriv/shared'; +import { useGrowthbookGetFeatureValue } from '@deriv/hooks'; import { useStore, observer } from '@deriv/stores'; type TCompareAccount = { @@ -15,16 +16,23 @@ const CompareAccount = observer(({ accounts_sub_text, is_desktop }: TCompareAcco const history = useHistory(); const { traders_hub } = useStore(); const { selected_account_type } = traders_hub; + const [is_traders_dashboard_tracking_enabled] = useGrowthbookGetFeatureValue({ + featureFlag: 'ce_tradershub_dashboard_tracking', + defaultValue: false, + }); + return (
{ history.push(routes.compare_cfds); - Analytics.trackEvent('ce_tradershub_dashboard_form', { - action: 'compare_accounts_push', - form_name: 'traders_hub_default', - account_mode: selected_account_type, - }); + if (is_traders_dashboard_tracking_enabled) { + Analytics.trackEvent('ce_tradershub_dashboard_form', { + action: 'compare_accounts_push', + form_name: 'traders_hub_default', + account_mode: selected_account_type, + }); + } }} > { - Analytics.trackEvent('ce_tradershub_dashboard_form', { - action: 'account_logo_push', - form_name: 'traders_hub_default', - account_mode: selected_account_type, - account_name: !is_real ? `${sub_title === undefined ? name : sub_title}` : name, - }); + if (is_traders_dashboard_tracking_enabled) { + Analytics.trackEvent('ce_tradershub_dashboard_form', { + action: 'account_logo_push', + form_name: 'traders_hub_default', + account_mode: selected_account_type, + account_name: !is_real ? `${sub_title === undefined ? name : sub_title}` : name, + }); + } + if (is_deriv_platform) { switch (name) { case DERIV_PLATFORM_NAMES.TRADER: diff --git a/packages/appstore/src/components/currency-switcher-card/real/real-account-card.tsx b/packages/appstore/src/components/currency-switcher-card/real/real-account-card.tsx index d98891367cb3..e5f09af52706 100644 --- a/packages/appstore/src/components/currency-switcher-card/real/real-account-card.tsx +++ b/packages/appstore/src/components/currency-switcher-card/real/real-account-card.tsx @@ -6,6 +6,7 @@ import { Localize } from '@deriv/translations'; import { Analytics } from '@deriv-com/analytics'; import BalanceText from 'Components/elements/text/balance-text'; import CurrencySwitcherContainer from 'Components/containers/currency-switcher-container'; +import { useGrowthbookGetFeatureValue } from '@deriv/hooks'; import { useStore, observer } from '@deriv/stores'; import { IsIconCurrency } from 'Assets/svgs/currency'; @@ -30,6 +31,11 @@ const RealAccountCard = observer(() => { const uppercase_currency = currency?.toUpperCase(); const get_currency = IsIconCurrency(uppercase_currency) ? uppercase_currency : 'Unknown'; + const [is_traders_dashboard_tracking_enabled] = useGrowthbookGetFeatureValue({ + featureFlag: 'ce_tradershub_dashboard_tracking', + defaultValue: false, + }); + return ( { currency && (
)}
botAction('DELETE', bot_id)} + className={classNames('ssb-list__menu__item', { + 'ssb-list__menu__item--disabled': disable_delete, + })} + onClick={() => { + if (!disable_delete) { + botAction('DELETE', bot_id); + } + }} tabIndex={0} onKeyDown={(e: React.KeyboardEvent) => { - if (e.key === 'Enter') { + if (e.key === 'Enter' && !disable_delete) { botAction('DELETE', bot_id); } }} diff --git a/packages/bot-web-ui/src/pages/server-side-bot/bot-list/bot-list.scss b/packages/bot-web-ui/src/pages/server-side-bot/bot-list/bot-list.scss index f239c0c257e0..27fdb37a6e82 100644 --- a/packages/bot-web-ui/src/pages/server-side-bot/bot-list/bot-list.scss +++ b/packages/bot-web-ui/src/pages/server-side-bot/bot-list/bot-list.scss @@ -93,9 +93,18 @@ margin-right: 8px; } - &:hover { + &:not(&--disabled):hover { + cursor: pointer; background-color: var(--state-hover); } + &--disabled { + background-color: var(--state-hover); + opacity: 0.5; + cursor: not-allowed; + &:hover { + background-color: var(--state-hover); + } + } } } } diff --git a/packages/bot-web-ui/src/pages/server-side-bot/bot-list/bot-list.tsx b/packages/bot-web-ui/src/pages/server-side-bot/bot-list/bot-list.tsx index b96904102107..99feb58f60bf 100644 --- a/packages/bot-web-ui/src/pages/server-side-bot/bot-list/bot-list.tsx +++ b/packages/bot-web-ui/src/pages/server-side-bot/bot-list/bot-list.tsx @@ -81,8 +81,7 @@ const BotList: React.FC = observer(({ setFormVisibility }) => { stopBot(bot_id); break; case 'OPEN': - // eslint-disable-next-line no-console - console.log('OPEN'); + setActiveBotId(bot_id); closeMenu(); break; case 'DELETE': @@ -111,6 +110,7 @@ const BotList: React.FC = observer(({ setFormVisibility }) => { useOnClickOutside(menu_ref, closeMenu, event => menu_open.visible && !menu_ref?.current.contains(event.target)); const has_list = !!bot_list?.length; + const should_disable_delete = menu_open.bot_id === active_bot.bot_id && active_bot.status === 'running'; return ( <> @@ -123,6 +123,7 @@ const BotList: React.FC = observer(({ setFormVisibility }) => { bot_id={menu_open.bot_id} botAction={botAction} is_mobile={is_mobile} + disable_delete={should_disable_delete} />
diff --git a/packages/bot-web-ui/src/stores/load-modal-store.ts b/packages/bot-web-ui/src/stores/load-modal-store.ts index 0317e77c7c7e..a4c308e599b4 100644 --- a/packages/bot-web-ui/src/stores/load-modal-store.ts +++ b/packages/bot-web-ui/src/stores/load-modal-store.ts @@ -7,13 +7,12 @@ import { TStores } from '@deriv/stores/types'; import { localize } from '@deriv/translations'; import { clearInjectionDiv, tabs_title } from 'Constants/load-modal'; import { TStrategy } from 'Types'; -import { rudderStackSendSwitchLoadStrategyTabEvent } from '../analytics/rudderstack-bot-builder'; import { rudderStackSendUploadStrategyCompletedEvent, rudderStackSendUploadStrategyFailedEvent, rudderStackSendUploadStrategyStartEvent, } from '../analytics/rudderstack-common-events'; -import { getStrategyType, LOAD_MODAL_TABS } from '../analytics/utils'; +import { getStrategyType } from '../analytics/utils'; import RootStore from './root-store'; interface ILoadModalStore { @@ -50,7 +49,7 @@ interface ILoadModalStore { onToggleDeleteDialog: (is_delete_modal_open: boolean) => void; onZoomInOutClick: (is_zoom_in: string) => void; previewRecentStrategy: (workspace_id: string) => void; - setActiveTabIndex: (index: number, is_default: boolean) => void; + setActiveTabIndex: (index: number) => void; setLoadedLocalFile: (loaded_local_file: File | null) => void; setDashboardStrategies: (strategies: Array) => void; setRecentStrategies: (recent_strategies: TStrategy[]) => void; @@ -456,15 +455,8 @@ export default class LoadModalStore implements ILoadModalStore { this.refreshStrategiesTheme(); }; - setActiveTabIndex = (index: number, is_default: boolean): void => { + setActiveTabIndex = (index: number): void => { this.active_index = index; - if (!is_default) { - const { ui } = this.core; - const { is_mobile } = ui; - rudderStackSendSwitchLoadStrategyTabEvent({ - load_strategy_tab: LOAD_MODAL_TABS[index + (is_mobile ? 1 : 0)], - }); - } }; setLoadedLocalFile = (loaded_local_file: File | null): void => { diff --git a/packages/bot-web-ui/src/stores/summary-card-store.ts b/packages/bot-web-ui/src/stores/summary-card-store.ts index f35a1ea6b447..4f4a4ff4a196 100644 --- a/packages/bot-web-ui/src/stores/summary-card-store.ts +++ b/packages/bot-web-ui/src/stores/summary-card-store.ts @@ -47,6 +47,7 @@ export default class SummaryCardStore { contract_id?: string | null = null; profit?: number = 0; indicative?: number = 0; + is_bot_running?: boolean = false; constructor(root_store: RootStore, core: TStores) { makeObservable(this, { @@ -59,6 +60,7 @@ export default class SummaryCardStore { contract_update_stop_loss: observable, has_contract_update_take_profit: observable, has_contract_update_stop_loss: observable, + is_bot_running: observable, contract_update_config: observable, contract_id: observable, profit: observable, @@ -74,6 +76,7 @@ export default class SummaryCardStore { onChange: action.bound, populateContractUpdateConfig: action.bound, setContractUpdateConfig: action.bound, + setIsBotRunning: action.bound, updateLimitOrder: action.bound, setValidationErrorMessages: action, validateProperty: action, @@ -212,6 +215,26 @@ export default class SummaryCardStore { } } + /** + * Sets the bot's running state based on whether the contract is still loading + */ + setIsBotRunning() { + if (!this.is_contract_loading) { + this.is_bot_running = false; + return; + } + + const onTimeout = () => { + if (this.is_contract_loading) { + this.is_bot_running = true; + this.root_store.run_panel.setContractStage(contract_stages.RUNNING); + } + }; + + const timeout = setTimeout(onTimeout, 5000); + return () => clearTimeout(timeout); + } + updateLimitOrder() { const limit_order = this.getLimitOrder(); diff --git a/packages/cashier-v2/package.json b/packages/cashier-v2/package.json index c98247c437ab..423077d4ad24 100644 --- a/packages/cashier-v2/package.json +++ b/packages/cashier-v2/package.json @@ -14,7 +14,7 @@ "start": "rimraf dist && npm run test && npm run serve" }, "dependencies": { - "@deriv-com/ui": "1.29.9", + "@deriv-com/ui": "1.29.10", "@deriv-com/utils": "^0.0.25", "@deriv/api-v2": "^1.0.0", "@deriv/integration": "^1.0.0", diff --git a/packages/cashier/package.json b/packages/cashier/package.json index 12402ee5f6fa..1f053f99647f 100644 --- a/packages/cashier/package.json +++ b/packages/cashier/package.json @@ -37,6 +37,7 @@ "url": "https://github.com/binary-com/deriv-app/issues" }, "dependencies": { + "@deriv-com/analytics": "1.11.0", "@deriv/api": "^1.0.0", "@deriv/api-types": "1.0.172", "@deriv/components": "^1.0.0", diff --git a/packages/cashier/src/components/cashier-container/real/real.scss b/packages/cashier/src/components/cashier-container/real/real.scss index 156134338ee5..cf7dfd990046 100644 --- a/packages/cashier/src/components/cashier-container/real/real.scss +++ b/packages/cashier/src/components/cashier-container/real/real.scss @@ -3,4 +3,20 @@ width: 100%; height: 80vh; } + + &__iframe { + @include mobile { + position: absolute; + height: calc(100% - 3.2rem); + inset-inline-end: 0; + padding: 0 1.6rem; + + + .page-container__sidebar--right { + position: relative; + top: calc(100% + 1.6rem); + padding-bottom: 1.6rem; + flex: none; + } + } + } } diff --git a/packages/cashier/src/components/cashier-container/real/real.tsx b/packages/cashier/src/components/cashier-container/real/real.tsx index 7957b165eaa8..e1e91acb67f2 100644 --- a/packages/cashier/src/components/cashier-container/real/real.tsx +++ b/packages/cashier/src/components/cashier-container/real/real.tsx @@ -29,7 +29,7 @@ const Real = observer(() => { {should_show_loader && } {iframe_url && (