diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 6f4ca91bac74..7c7af76890b8 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -53,6 +53,11 @@ # vinu-deriv # wojciech-deriv # yashim-deriv +# farabi-deriv +# maryia-matskevich-deriv +# shahzaib-deriv +# rupato-deriv +# akmal-deriv ###################################################################################################### # @@ -119,7 +124,7 @@ /packages/core/**/* @ali-hosseini-deriv @amina-deriv @balakrishna-deriv @farrah-deriv @habib-deriv @matin-deriv @nijil-deriv @sandeep-deriv @wojciech-deriv @yashim-deriv /packages/shared/**/* @ali-hosseini-deriv @amina-deriv @balakrishna-deriv @farrah-deriv @habib-deriv @matin-deriv @nijil-deriv @sandeep-deriv @wojciech-deriv @yashim-deriv /packages/components/**/* @ali-hosseini-deriv @amina-deriv @balakrishna-deriv @farrah-deriv @habib-deriv @matin-deriv @nijil-deriv @sandeep-deriv @wojciech-deriv @yashim-deriv -/packages/translations/**/* @ali-hosseini-deriv @amina-deriv @balakrishna-deriv @farrah-deriv @habib-deriv @matin-deriv @nijil-deriv @sandeep-deriv @wojciech-deriv @yashim-deriv +/packages/translations/**/* @ali-hosseini-deriv @amina-deriv @balakrishna-deriv @farrah-deriv @habib-deriv @matin-deriv @nijil-deriv @sandeep-deriv @wojciech-deriv @yashim-deriv @heorhi-deriv @jim-deriv @vinu-deriv @nada-deriv @hirad-deriv @farabi-deriv @likhith-deriv @maryia-matskevich-deriv @shahzaib-deriv @rupato-deriv @akmal-deriv /packages/utils/**/* @ali-hosseini-deriv @amina-deriv @balakrishna-deriv @farrah-deriv @habib-deriv @matin-deriv @nijil-deriv @sandeep-deriv @wojciech-deriv @yashim-deriv /packages/hooks/**/* @ali-hosseini-deriv @amina-deriv @balakrishna-deriv @farrah-deriv @habib-deriv @matin-deriv @nijil-deriv @sandeep-deriv @wojciech-deriv @yashim-deriv /packages/stores/**/* @ali-hosseini-deriv @amina-deriv @balakrishna-deriv @farrah-deriv @habib-deriv @matin-deriv @nijil-deriv @sandeep-deriv @wojciech-deriv @yashim-deriv diff --git a/.github/actions/publish_to_pages_uat/action.yml b/.github/actions/publish_to_pages_uat/action.yml index 3d84e8df0aa6..1132f0a22056 100644 --- a/.github/actions/publish_to_pages_uat/action.yml +++ b/.github/actions/publish_to_pages_uat/action.yml @@ -7,6 +7,9 @@ inputs: CLOUDFLARE_API_TOKEN: description: 'Cloudflare token' required: true + BRANCH_NAME: + description: 'Branch name' + required: true runs: using: composite steps: @@ -17,6 +20,6 @@ runs: run: | npm i wrangler@3.1.0 cd packages/core - npx wrangler pages deploy dist/ --project-name=deriv-app-pages --branch=uat - echo "New uat website - https://uat.cf-pages-deriv-app.deriv.com" + npx wrangler pages deploy dist/ --project-name=deriv-app-pages --branch=${{ inputs.BRANCH_NAME }} + echo "New uat website - https://${{ inputs.BRANCH_NAME }}.cf-pages-deriv-app.deriv.com" shell: bash diff --git a/.github/workflows/release_uat.yml b/.github/workflows/release_uat.yml index 87b9b6685092..a3b877ca4183 100644 --- a/.github/workflows/release_uat.yml +++ b/.github/workflows/release_uat.yml @@ -53,3 +53,4 @@ jobs: with: CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + BRANCH_NAME: uat diff --git a/.github/workflows/release_uat2.yml b/.github/workflows/release_uat2.yml new file mode 100644 index 000000000000..4ff8991ca0c3 --- /dev/null +++ b/.github/workflows/release_uat2.yml @@ -0,0 +1,53 @@ +name: Deriv App Release to UAT2 Environment +on: + workflow_dispatch: + +jobs: + build_test_and_publish: + name: Build, Test and Publish to Cloudflare UAT2 + runs-on: Runner_16cores_Deriv-app + environment: Staging + 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: Download Remote Config Backup File + uses: ./.github/actions/download_remote_config_backup + with: + REMOTE_CONFIG_URL: ${{ vars.REMOTE_CONFIG_URL }} + - name: Build + uses: "./.github/actions/build" + with: + NODE_ENV: staging + CROWDIN_WALLETS_API_KEY: ${{ secrets.CROWDIN_WALLETS_API_KEY }} + DATADOG_APPLICATION_ID: ${{ vars.DATADOG_APPLICATION_ID }} + DATADOG_CLIENT_TOKEN: ${{ vars.DATADOG_CLIENT_TOKEN }} + IS_GROWTHBOOK_ENABLED: ${{ vars.IS_GROWTHBOOK_ENABLED }} + DATADOG_CLIENT_TOKEN_LOGS: ${{ vars.DATADOG_CLIENT_TOKEN_LOGS }} + DATADOG_SESSION_REPLAY_SAMPLE_RATE: ${{ vars.DATADOG_SESSION_REPLAY_SAMPLE_RATE }} + DATADOG_SESSION_SAMPLE_RATE: ${{ vars.DATADOG_SESSION_SAMPLE_RATE }} + DATADOG_SESSION_SAMPLE_RATE_LOGS: ${{ vars.DATADOG_SESSION_SAMPLE_RATE_LOGS }} + GD_API_KEY: ${{ secrets.GD_API_KEY }} + GD_APP_ID: ${{ secrets.GD_APP_ID }} + GD_CLIENT_ID: ${{ secrets.GD_CLIENT_ID }} + RUDDERSTACK_KEY: ${{ vars.RUDDERSTACK_KEY }} + GROWTHBOOK_CLIENT_KEY: ${{ vars.GROWTHBOOK_CLIENT_KEY }} + GROWTHBOOK_DECRYPTION_KEY: ${{ vars.GROWTHBOOK_DECRYPTION_KEY }} + REF_NAME: ${{ github.ref_name }} + REMOTE_CONFIG_URL: ${{ vars.REMOTE_CONFIG_URL }} + TRUSTPILOT_API_KEY: ${{ secrets.TRUSTPILOT_API_KEY }} + - name: Versioning + uses: "./.github/actions/versioning" + with: + release_type: uat2 + - name: Run tests + run: npm test + - name: Publish to Cloudflare Pages UAT2 + uses: "./.github/actions/publish_to_pages_uat" + with: + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + BRANCH_NAME: uat2 diff --git a/package-lock.json b/package-lock.json index 2553ae9b5fbb..2c548106c628 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "@deriv-com/quill-tokens": "2.0.4", "@deriv-com/quill-ui": "1.16.20", "@deriv-com/translations": "1.3.9", - "@deriv-com/ui": "1.35.2", + "@deriv-com/ui": "1.36.2", "@deriv-com/utils": "^0.0.34", "@deriv/api-types": "1.0.172", "@deriv/deriv-api": "^1.0.15", @@ -26,7 +26,6 @@ "@deriv/js-interpreter": "^3.0.0", "@deriv/quill-design": "^1.3.2", "@deriv/quill-icons": "1.23.3", - "@deriv/ui": "^0.6.0", "@livechat/customer-sdk": "^2.0.4", "@lottiefiles/dotlottie-react": "0.7.2", "@sendbird/chat": "^4.9.7", @@ -3422,9 +3421,9 @@ } }, "node_modules/@deriv-com/ui": { - "version": "1.35.2", - "resolved": "https://registry.npmjs.org/@deriv-com/ui/-/ui-1.35.2.tgz", - "integrity": "sha512-zL0ocZ8S6JID6e3T9S6e+PELiO/KWwsP6BxPb8g1ByvdzG9kTnGJDYQOjvXe3h9Fr+g4W/rCezI2ZDXYFuZFVA==", + "version": "1.36.2", + "resolved": "https://registry.npmjs.org/@deriv-com/ui/-/ui-1.36.2.tgz", + "integrity": "sha512-c0TzpRJ99hGzH8U3kb8JPRHKL+pCi4SY3R7gM7v8q7Ry3pZqivV8L7y/A70TOf7jINSrQxECBqwe1fAMyIJFsw==", "dependencies": { "@popperjs/core": "^2.11.8", "@types/react-modal": "^3.16.3", @@ -3549,25 +3548,6 @@ "react-dom": ">= 16" } }, - "node_modules/@deriv/ui": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@deriv/ui/-/ui-0.6.0.tgz", - "integrity": "sha512-1w/XMuY9BT+xPYJeStdDFat7KlEGfmUT5/5QbIwrDvXDiJVU8G0vvTurhLEBl9YPcQtFPCA27Ma1Nrw3/KQp6g==", - "dependencies": { - "@radix-ui/react-checkbox": "^1.0.0", - "@radix-ui/react-dialog": "^1.0.0", - "@radix-ui/react-radio-group": "^1.0.0", - "@radix-ui/react-switch": "^1.0.0", - "@radix-ui/react-tabs": "^1.0.0", - "@radix-ui/react-tooltip": "^1.0.0", - "@stitches/react": "^1.2.8", - "classnames": "^2.3.1" - }, - "peerDependencies": { - "react": "^16.0.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0" - } - }, "node_modules/@devtools-ds/object-inspector": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@devtools-ds/object-inspector/-/object-inspector-1.2.1.tgz", @@ -3957,40 +3937,6 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@floating-ui/core": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.8.tgz", - "integrity": "sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==", - "dependencies": { - "@floating-ui/utils": "^0.2.8" - } - }, - "node_modules/@floating-ui/dom": { - "version": "1.6.11", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.11.tgz", - "integrity": "sha512-qkMCxSR24v2vGkhYDo/UzxfJN3D4syqSjyuTFz6C7XcpU1pASPRieNI0Kj5VP3/503mOfYiGY891ugBX1GlABQ==", - "dependencies": { - "@floating-ui/core": "^1.6.0", - "@floating-ui/utils": "^0.2.8" - } - }, - "node_modules/@floating-ui/react-dom": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz", - "integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==", - "dependencies": { - "@floating-ui/dom": "^1.0.0" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/@floating-ui/utils": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.8.tgz", - "integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==" - }, "node_modules/@gar/promisify": { "version": "1.1.3", "license": "MIT" @@ -10222,691 +10168,6 @@ "url": "https://opencollective.com/popperjs" } }, - "node_modules/@radix-ui/primitive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", - "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" - }, - "node_modules/@radix-ui/react-arrow": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz", - "integrity": "sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw==", - "dependencies": { - "@radix-ui/react-primitive": "2.0.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-checkbox": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.1.2.tgz", - "integrity": "sha512-/i0fl686zaJbDQLNKrkCbMyDm6FQMt4jg323k7HuqitoANm9sE23Ql8yOK3Wusk34HSLKDChhMux05FnP6KUkw==", - "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-presence": "1.1.1", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-controllable-state": "1.1.0", - "@radix-ui/react-use-previous": "1.1.0", - "@radix-ui/react-use-size": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-collection": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.0.tgz", - "integrity": "sha512-GZsZslMJEyo1VKm5L1ZJY8tGDxZNPAoUeQUIbKeJfoi7Q4kmig5AsgLMYYuyYbfjd8fBmFORAIwYAkXMnXZgZw==", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-slot": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-context": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", - "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", - "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-context": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", - "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dialog": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.2.tgz", - "integrity": "sha512-Yj4dZtqa2o+kG61fzB0H2qUvmwBA2oyQroGLyNtBj1beo1khoQ3q1a2AO8rrQYjd8256CO9+N8L9tvsS+bnIyA==", - "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-dismissable-layer": "1.1.1", - "@radix-ui/react-focus-guards": "1.1.1", - "@radix-ui/react-focus-scope": "1.1.0", - "@radix-ui/react-id": "1.1.0", - "@radix-ui/react-portal": "1.1.2", - "@radix-ui/react-presence": "1.1.1", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-slot": "1.1.0", - "@radix-ui/react-use-controllable-state": "1.1.0", - "aria-hidden": "^1.1.1", - "react-remove-scroll": "2.6.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-direction": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", - "integrity": "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.1.tgz", - "integrity": "sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==", - "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-callback-ref": "1.1.0", - "@radix-ui/react-use-escape-keydown": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-focus-guards": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz", - "integrity": "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-focus-scope": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.0.tgz", - "integrity": "sha512-200UD8zylvEyL8Bx+z76RJnASR2gRMuxlgFCPAe/Q/679a/r0eK3MBVYMb7vZODZcffZBdob1EGnky78xmVvcA==", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-callback-ref": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-id": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz", - "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popper": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.0.tgz", - "integrity": "sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg==", - "dependencies": { - "@floating-ui/react-dom": "^2.0.0", - "@radix-ui/react-arrow": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-callback-ref": "1.1.0", - "@radix-ui/react-use-layout-effect": "1.1.0", - "@radix-ui/react-use-rect": "1.1.0", - "@radix-ui/react-use-size": "1.1.0", - "@radix-ui/rect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popper/node_modules/@radix-ui/react-context": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", - "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-portal": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.2.tgz", - "integrity": "sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==", - "dependencies": { - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-presence": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.1.tgz", - "integrity": "sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-primitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", - "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", - "dependencies": { - "@radix-ui/react-slot": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-radio-group": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.2.1.tgz", - "integrity": "sha512-kdbv54g4vfRjja9DNWPMxKvXblzqbpEC8kspEkZ6dVP7kQksGCn+iZHkcCz2nb00+lPdRvxrqy4WrvvV1cNqrQ==", - "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-direction": "1.1.0", - "@radix-ui/react-presence": "1.1.1", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-roving-focus": "1.1.0", - "@radix-ui/react-use-controllable-state": "1.1.0", - "@radix-ui/react-use-previous": "1.1.0", - "@radix-ui/react-use-size": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-roving-focus": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.0.tgz", - "integrity": "sha512-EA6AMGeq9AEeQDeSH0aZgG198qkfHSbvWTf1HvoDmOB5bBG/qTxjYMWUKMnYiV6J/iP/J8MEFSuB2zRU2n7ODA==", - "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-collection": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.0", - "@radix-ui/react-direction": "1.1.0", - "@radix-ui/react-id": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-callback-ref": "1.1.0", - "@radix-ui/react-use-controllable-state": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-context": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", - "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", - "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-switch": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.1.1.tgz", - "integrity": "sha512-diPqDDoBcZPSicYoMWdWx+bCPuTRH4QSp9J+65IvtdS0Kuzt67bI6n32vCj8q6NZmYW/ah+2orOtMwcX5eQwIg==", - "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-controllable-state": "1.1.0", - "@radix-ui/react-use-previous": "1.1.0", - "@radix-ui/react-use-size": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tabs": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.1.tgz", - "integrity": "sha512-3GBUDmP2DvzmtYLMsHmpA1GtR46ZDZ+OreXM/N+kkQJOPIgytFWWTfDQmBQKBvaFS0Vno0FktdbVzN28KGrMdw==", - "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-direction": "1.1.0", - "@radix-ui/react-id": "1.1.0", - "@radix-ui/react-presence": "1.1.1", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-roving-focus": "1.1.0", - "@radix-ui/react-use-controllable-state": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tooltip": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.3.tgz", - "integrity": "sha512-Z4w1FIS0BqVFI2c1jZvb/uDVJijJjJ2ZMuPV81oVgTZ7g3BZxobplnMVvXtFWgtozdvYJ+MFWtwkM5S2HnAong==", - "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-dismissable-layer": "1.1.1", - "@radix-ui/react-id": "1.1.0", - "@radix-ui/react-popper": "1.2.0", - "@radix-ui/react-portal": "1.1.2", - "@radix-ui/react-presence": "1.1.1", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-slot": "1.1.0", - "@radix-ui/react-use-controllable-state": "1.1.0", - "@radix-ui/react-visually-hidden": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", - "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", - "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", - "dependencies": { - "@radix-ui/react-use-callback-ref": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-escape-keydown": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz", - "integrity": "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==", - "dependencies": { - "@radix-ui/react-use-callback-ref": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz", - "integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-previous": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.0.tgz", - "integrity": "sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-rect": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz", - "integrity": "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==", - "dependencies": { - "@radix-ui/rect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-size": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz", - "integrity": "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-visually-hidden": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.0.tgz", - "integrity": "sha512-N8MDZqtgCgG5S3aV60INAB475osJousYpZ4cTJ2cFbMpdHS5Y6loLTH8LPtkj2QN0x93J30HT/M3qJXM0+lyeQ==", - "dependencies": { - "@radix-ui/react-primitive": "2.0.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/rect": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz", - "integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==" - }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -10981,6 +10242,19 @@ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==" }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz", + "integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rudderstack/analytics-js": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/@rudderstack/analytics-js/-/analytics-js-3.7.0.tgz", @@ -12008,14 +11282,6 @@ "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==", "peer": true }, - "node_modules/@stitches/react": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@stitches/react/-/react-1.2.8.tgz", - "integrity": "sha512-9g9dWI4gsSVe8bNLlb+lMkBYsnIKCZTmvqvDG+Avnn69XfmHZKiaMrx7cgTaddq7aTPPmXiTsbFcUy0xgI4+wA==", - "peerDependencies": { - "react": ">= 16.3.0" - } - }, "node_modules/@storybook/addon-actions": { "version": "6.5.16", "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-6.5.16.tgz", @@ -19916,7 +19182,7 @@ }, "node_modules/@types/react-dom": { "version": "18.0.9", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@types/react": "*" @@ -21272,17 +20538,6 @@ "integrity": "sha512-F2+Hkm9xFaRg+GkaNnbwXNDV5O6pnCFEmqyhvfC/Ic5LbgOWjJh3L+mN/s91rxVL3znE7DYVpW0GJFT+4YBgWw==", "peer": true }, - "node_modules/aria-hidden": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.4.tgz", - "integrity": "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==", - "dependencies": { - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/aria-query": { "version": "5.1.3", "license": "Apache-2.0", @@ -31542,14 +30797,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-nonce": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", - "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", - "engines": { - "node": ">=6" - } - }, "node_modules/get-own-enumerable-property-symbols": { "version": "3.0.2", "license": "ISC" @@ -38455,7 +37702,6 @@ }, "node_modules/jsonparse": { "version": "1.3.1", - "dev": true, "engines": [ "node >= 0.2.0" ], @@ -48049,51 +47295,6 @@ "node": ">=0.10.0" } }, - "node_modules/react-remove-scroll": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.0.tgz", - "integrity": "sha512-I2U4JVEsQenxDAKaVa3VZ/JeJZe0/2DxPWL8Tj8yLKctQJQiZM52pn/GWFpSp8dftjM3pSAHVJZscAnC/y+ySQ==", - "dependencies": { - "react-remove-scroll-bar": "^2.3.6", - "react-style-singleton": "^2.2.1", - "tslib": "^2.1.0", - "use-callback-ref": "^1.3.0", - "use-sidecar": "^1.1.2" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/react-remove-scroll-bar": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz", - "integrity": "sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==", - "dependencies": { - "react-style-singleton": "^2.2.1", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/react-router": { "version": "5.3.4", "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz", @@ -48212,28 +47413,6 @@ "react-dom": ">=16.8.0" } }, - "node_modules/react-style-singleton": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", - "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==", - "dependencies": { - "get-nonce": "^1.0.0", - "invariant": "^2.2.4", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/react-svg-core": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/react-svg-core/-/react-svg-core-3.0.3.tgz", diff --git a/packages/account/package.json b/packages/account/package.json index 8bd7ff2a0e26..8ae45fdc9e6e 100644 --- a/packages/account/package.json +++ b/packages/account/package.json @@ -33,7 +33,7 @@ "@deriv-com/analytics": "1.14.0", "@deriv-com/translations": "1.3.9", "@deriv-com/utils": "^0.0.34", - "@deriv-com/ui": "1.35.2", + "@deriv-com/ui": "1.36.2", "@deriv/api": "^1.0.0", "@deriv-com/quill-ui": "1.16.20", "@deriv/components": "^1.0.0", diff --git a/packages/account/src/Components/terms-of-use/terms-of-use-messages.tsx b/packages/account/src/Components/terms-of-use/terms-of-use-messages.tsx index 3afdb6899cf9..b27f3fe89c40 100644 --- a/packages/account/src/Components/terms-of-use/terms-of-use-messages.tsx +++ b/packages/account/src/Components/terms-of-use/terms-of-use-messages.tsx @@ -50,17 +50,6 @@ export const BrokerSpecificMessage = ({ target }: { target: TBrokerCodes }) => { - {target === Jurisdiction.MALTA_INVEST && ( - -
- - - - - - -
- )} ); }; diff --git a/packages/account/src/Sections/Profile/PersonalDetails/personal-details-form.tsx b/packages/account/src/Sections/Profile/PersonalDetails/personal-details-form.tsx index 67ea9e2d0d8d..27b875ad23d9 100644 --- a/packages/account/src/Sections/Profile/PersonalDetails/personal-details-form.tsx +++ b/packages/account/src/Sections/Profile/PersonalDetails/personal-details-form.tsx @@ -409,46 +409,52 @@ const PersonalDetailsForm = observer(() => { {!is_virtual && (
- ) => { - let phone_number = e.target.value.replace(/\D/g, ''); - phone_number = phone_number.length === 0 ? '+' : `+${phone_number}`; - setFieldValue('phone', phone_number, true); - setStatus(''); - }} - onBlur={handleBlur} - required - error={errors.phone} - disabled={ - isFieldDisabled('phone') || - !!next_email_otp_request_timer || - is_email_otp_timer_loading - } - data-testid='dt_phone' - /> - {isPhoneNumberVerificationEnabled && ( - - )} +
+
+ ) => { + let phone_number = e.target.value.replace(/\D/g, ''); + phone_number = + phone_number.length === 0 ? '+' : `+${phone_number}`; + setFieldValue('phone', phone_number, true); + setStatus(''); + }} + onBlur={handleBlur} + required + error={errors.phone} + disabled={ + isFieldDisabled('phone') || + !!next_email_otp_request_timer || + is_email_otp_timer_loading + } + data-testid='dt_phone' + /> +
+ {isPhoneNumberVerificationEnabled && ( + + )} +
)} diff --git a/packages/account/src/Sections/Profile/PersonalDetails/verify-button.scss b/packages/account/src/Sections/Profile/PersonalDetails/verify-button.scss index d15f65b61ed5..7796badfa4e1 100644 --- a/packages/account/src/Sections/Profile/PersonalDetails/verify-button.scss +++ b/packages/account/src/Sections/Profile/PersonalDetails/verify-button.scss @@ -1,7 +1,4 @@ .phone-verification-button { - position: absolute; - inset-inline-end: 0; - top: 0; border-end-start-radius: 0; border-start-start-radius: 0; } diff --git a/packages/account/src/Sections/Profile/PhoneVerification/confirm-phone-number.tsx b/packages/account/src/Sections/Profile/PhoneVerification/confirm-phone-number.tsx index 99fb1be02e61..842e0e38cea6 100644 --- a/packages/account/src/Sections/Profile/PhoneVerification/confirm-phone-number.tsx +++ b/packages/account/src/Sections/Profile/PhoneVerification/confirm-phone-number.tsx @@ -123,6 +123,7 @@ const ConfirmPhoneNumber = observer(({ show_confirm_phone_number, setOtpVerifica value={phone_number} status={error_message ? 'error' : 'neutral'} message={error_message} + className='phone-verification__card--inputfield__phone-number-input' onChange={handleOnChangePhoneNumber} addonLabel='+' /> diff --git a/packages/account/src/Sections/Profile/PhoneVerification/otp-verification.tsx b/packages/account/src/Sections/Profile/PhoneVerification/otp-verification.tsx index 78bfd0ee2a6b..1e8514c6c6f9 100644 --- a/packages/account/src/Sections/Profile/PhoneVerification/otp-verification.tsx +++ b/packages/account/src/Sections/Profile/PhoneVerification/otp-verification.tsx @@ -156,7 +156,7 @@ const OTPVerification = observer(({ phone_verification_type, setOtpVerification {should_show_phone_number_otp ? ( , + , ]} /> diff --git a/packages/account/src/Sections/Profile/PhoneVerification/phone-number-verified-modal.tsx b/packages/account/src/Sections/Profile/PhoneVerification/phone-number-verified-modal.tsx index c317fdea0d50..759f4c1c064a 100644 --- a/packages/account/src/Sections/Profile/PhoneVerification/phone-number-verified-modal.tsx +++ b/packages/account/src/Sections/Profile/PhoneVerification/phone-number-verified-modal.tsx @@ -31,7 +31,7 @@ const PhoneNumberVerifiedModal = observer(({ should_show_phone_number_verified_m account_settings: { phone }, }, } = useStore(); - const { setIsPhoneVerificationCompleted } = ui; + const { setIsPhoneVerificationCompleted, setShouldShowPhoneNumberOTP } = ui; const { trackPhoneVerificationEvents } = usePhoneVerificationAnalytics(); useEffect(() => { @@ -40,6 +40,7 @@ const PhoneNumberVerifiedModal = observer(({ should_show_phone_number_verified_m action: 'open', subform_name: 'verification_successful', }); + setShouldShowPhoneNumberOTP(false); setIsPhoneVerificationCompleted(true); } }, [should_show_phone_number_verified_modal, trackPhoneVerificationEvents]); @@ -59,12 +60,15 @@ const PhoneNumberVerifiedModal = observer(({ should_show_phone_number_verified_m } />
- - - +
+
+ {phone} +
+   + + + +
diff --git a/packages/account/src/Sections/Profile/PhoneVerification/phone-verification.scss b/packages/account/src/Sections/Profile/PhoneVerification/phone-verification.scss index 87bfbe546c18..6fa163a08d7b 100644 --- a/packages/account/src/Sections/Profile/PhoneVerification/phone-verification.scss +++ b/packages/account/src/Sections/Profile/PhoneVerification/phone-verification.scss @@ -14,6 +14,15 @@ gap: 0.8rem; margin-top: 2.4rem; } + &__phone-number-container { + display: flex; + + &__phone-number { + @include rtl { + direction: ltr; + } + } + } } } @@ -72,6 +81,15 @@ @include mobile-screen { width: 100%; } + &__phone-number-input { + @include rtl { + direction: ltr; + + .label { + direction: rtl; + } + } + } &__livechat { color: var(--core-color-solid-red-900, $color-red-12); @@ -104,6 +122,12 @@ display: flex; flex-direction: column; text-align: center; + + &__phone-number { + @include rtl { + direction: ltr; + } + } } &-otp-container { diff --git a/packages/account/src/Sections/Profile/PhoneVerification/verification-link-expired-modal.tsx b/packages/account/src/Sections/Profile/PhoneVerification/verification-link-expired-modal.tsx index 1006af277cf8..e623556e1e82 100644 --- a/packages/account/src/Sections/Profile/PhoneVerification/verification-link-expired-modal.tsx +++ b/packages/account/src/Sections/Profile/PhoneVerification/verification-link-expired-modal.tsx @@ -63,8 +63,10 @@ const VerificationLinkExpiredModal = observer( isOpened={should_show_verification_link_expired_modal} isPrimaryButtonDisabled={!!next_email_otp_request_timer || is_email_otp_timer_loading} buttonColor='coral' + showCrossIcon={false} + showHandleBar={false} + disableCloseOnOverlay isNonExpandable - shouldCloseModalOnSwipeDown primaryButtonCallback={handleSendNewLinkButton} primaryButtonLabel={ ) => { ); } - useEffect(() => { - return () => { - connectionRef.current?.close(); - reactQueryRef.current?.clear(); - }; - }, []); - const setOnReconnected = useCallback((onReconnected: () => void) => { onReconnectedRef.current = onReconnected; }, []); @@ -190,7 +183,8 @@ const APIProvider = ({ children }: PropsWithChildren) => { }); } - connectionRef.current?.close(); + wsClientRef.current?.close(); + reactQueryRef.current?.clear(); }; }, []); diff --git a/packages/api-v2/src/ws-client/__tests__/subscription.spec.ts b/packages/api-v2/src/ws-client/__tests__/subscription.spec.ts index 87a3ff0ff27d..781f9eefe196 100644 --- a/packages/api-v2/src/ws-client/__tests__/subscription.spec.ts +++ b/packages/api-v2/src/ws-client/__tests__/subscription.spec.ts @@ -1,14 +1,19 @@ import BackendSubscription from '../subscription'; -import request from '../request'; +import { send } from '../request'; import mockWebSocketFactory, { WebSocketMock } from '../mock-websocket-factory'; -// Mock the request function -jest.mock('../request', () => - jest - .fn() - .mockImplementation(() => - Promise.resolve({ result: 'data received from send', subscription: { id: 'SUBSCRIPTION_ID' } }) - ) -); + +jest.mock('../request', () => { + return { + __esModule: true, // This property makes it work with ES6 imports + default: jest.fn().mockImplementation(() => { + return Promise.resolve({ result: 'data received from send', subscription: { id: 'SUBSCRIPTION_ID' } }); + }), + send: jest.fn().mockImplementation(() => { + // Mock implementation for send method to avoid empty function lint error + return Promise.resolve(); + }), + }; +}); const ENDPOINT = 'balance'; @@ -180,10 +185,10 @@ describe('Subscription', () => { ); await subscribePromise; - // // Execute unsubscribe + // Execute unsubscribe await backendSubscription.unsubscribe(); - expect(request).toHaveBeenCalledWith(mockWs, 'forget', { forget: 'SUBSCRIPTION_ID' }); + expect(send).toHaveBeenCalledWith(mockWs, 'forget', { forget: 'SUBSCRIPTION_ID' }); }); test('when websocket closes, removes websocket message and close listeners', async () => { diff --git a/packages/api-v2/src/ws-client/__tests__/subscriptions-manager.spec.ts b/packages/api-v2/src/ws-client/__tests__/subscriptions-manager.spec.ts index 5060c6cffac7..d7a2ca33a1e0 100644 --- a/packages/api-v2/src/ws-client/__tests__/subscriptions-manager.spec.ts +++ b/packages/api-v2/src/ws-client/__tests__/subscriptions-manager.spec.ts @@ -272,4 +272,59 @@ describe('SubscriptionsManager', () => { expect(backendUnsubscribeSpy).toHaveBeenCalledTimes(1); }); + + it('close unsubscribes all backend subscriptions', async () => { + const unsubscribeSpy = jest.spyOn(Subscription.prototype, 'unsubscribe'); + + const onData1 = jest.fn(); + const onData2 = jest.fn(); + + const subscriptionPromise1 = subscriptionsManager.subscribe('website_status', { payload: 'payload1' }, onData1); + + mockWs.respondFromServer( + JSON.stringify({ data: 'initial data', req_id: 1, subscription: { id: 'SUBSCRIPTION_ID' } }) + ); + + const subscriptionPromise2 = subscriptionsManager.subscribe('website_status', { payload: 'payload2' }, onData2); + + mockWs.respondFromServer( + JSON.stringify({ data: 'initial data', req_id: 2, subscription: { id: 'SUBSCRIPTION_ID' } }) + ); + + await subscriptionPromise1; + await subscriptionPromise2; + + await subscriptionsManager.close(); + + expect(unsubscribeSpy).toHaveBeenCalledTimes(2); + }); + + it('close clears subscriptions map', async () => { + const onData1 = jest.fn(); + const onData2 = jest.fn(); + + const subscriptionPromise1 = subscriptionsManager.subscribe('website_status', { payload: 'payload1' }, onData1); + + mockWs.respondFromServer( + JSON.stringify({ data: 'initial data', req_id: 1, subscription: { id: 'SUBSCRIPTION_ID' } }) + ); + + const subscriptionPromise2 = subscriptionsManager.subscribe('website_status', { payload: 'payload2' }, onData2); + + mockWs.respondFromServer( + JSON.stringify({ data: 'initial data', req_id: 2, subscription: { id: 'SUBSCRIPTION_ID' } }) + ); + + await subscriptionPromise1; + await subscriptionPromise2; + + await subscriptionsManager.close(); + + expect(subscriptionsManager.backendSubscriptions.size).toBe(0); + }); + + it('does not crash if no authorized websocket available', async () => { + subscriptionsManager.setAuthorizedWs(); + await subscriptionsManager.close(); + }); }); diff --git a/packages/api-v2/src/ws-client/mock-websocket-factory.ts b/packages/api-v2/src/ws-client/mock-websocket-factory.ts index 84fcbbd381dd..d4a815512f6e 100644 --- a/packages/api-v2/src/ws-client/mock-websocket-factory.ts +++ b/packages/api-v2/src/ws-client/mock-websocket-factory.ts @@ -16,6 +16,12 @@ function mockWebSocketFactory(): WebSocketMock { const handlers: Handlers = {}; return jest.fn().mockImplementation(() => ({ + readyState: 1, + CONNECTING: 0, + OPEN: 1, + CLOSING: 2, + CLOSED: 3, + send: jest.fn(), close: jest.fn(() => { const closeHandlers = handlers.close; diff --git a/packages/api-v2/src/ws-client/request.ts b/packages/api-v2/src/ws-client/request.ts index 54e192910750..7c89a53ca25e 100644 --- a/packages/api-v2/src/ws-client/request.ts +++ b/packages/api-v2/src/ws-client/request.ts @@ -24,6 +24,16 @@ function request( const req_id = ++reqSeqNumber; const promise: Promise> = new Promise((resolve, reject) => { + if (ws.readyState === ws.CLOSED || ws.readyState === ws.CLOSING) { + reject(new Error('WS is closed or closing')); + return; + } + + if (ws.readyState !== ws.OPEN) { + reject(new Error('WS is not open')); + return; + } + const timeout: NodeJS.Timeout = setTimeout(() => { ws.removeEventListener('message', receive); reject(new Error(`Request timeout, request: ${name}, payload: ${JSON.stringify(payload)}`)); @@ -60,6 +70,37 @@ function request( return promise; } +/** + * responsible for sending request over given WS and thats it, + * response is not expected, fire and forget, + * e.g. to unsubscribe - send unsubscribe request away and don't wait for response, e.g. when closing connection + */ +function send( + ws: WebSocket, + name: TSocketEndpointNames, + payload: TSocketRequestPayload['payload'] +): void { + const req_id = ++reqSeqNumber; + + if (ws.readyState === ws.CLOSED || ws.readyState === ws.CLOSING) { + console.error('WS is closed or closing'); // eslint-disable-line no-console + return; + } + + if (ws.readyState !== ws.OPEN) { + console.error('WS is not open'); // eslint-disable-line no-console + return; + } + + ws.send( + JSON.stringify({ + [name]: 1, + ...payload, + req_id, + }) + ); +} + /** * reset request sequence number * used in tests @@ -71,3 +112,4 @@ export function resetReqSeqNumber() { } export default request; +export { send }; diff --git a/packages/api-v2/src/ws-client/subscription.ts b/packages/api-v2/src/ws-client/subscription.ts index 20699436f70e..0de7aa0d3531 100644 --- a/packages/api-v2/src/ws-client/subscription.ts +++ b/packages/api-v2/src/ws-client/subscription.ts @@ -1,4 +1,4 @@ -import request from './request'; +import request, { send } from './request'; import { TSocketResponse, TSocketRequestPayload, @@ -27,7 +27,11 @@ export default class Subscription { listeners: Array<(data: TSocketResponse) => void>; - setAuthorizedWs(authorizedWs: WebSocket) { + setAuthorizedWs(authorizedWs?: WebSocket) { + if (!authorizedWs) { + return; + } + this.authorizedWs = authorizedWs; this.authorizedWs.removeEventListener('message', this.boundOnWsMessage); @@ -59,7 +63,7 @@ export default class Subscription { async unsubscribe() { this.authorizedWs.removeEventListener('message', this.boundOnWsMessage); this.authorizedWs.removeEventListener('close', this.boundOnWsClose); - await request(this.authorizedWs, 'forget', { forget: this.subscriptionId }); + send(this.authorizedWs, 'forget', { forget: this.subscriptionId }); } onWsClose() { diff --git a/packages/api-v2/src/ws-client/subscriptions-manager.ts b/packages/api-v2/src/ws-client/subscriptions-manager.ts index 1d7e4f00c1dc..9a5e556bde77 100644 --- a/packages/api-v2/src/ws-client/subscriptions-manager.ts +++ b/packages/api-v2/src/ws-client/subscriptions-manager.ts @@ -4,9 +4,9 @@ import { getQueryKeys } from '../utils'; export default class SubscriptionsManager { backendSubscriptions: Map = new Map(); - authorizedWs: WebSocket | undefined; + authorizedWs?: WebSocket; - setAuthorizedWs(authorizedWs: WebSocket) { + setAuthorizedWs(authorizedWs?: WebSocket) { this.authorizedWs = authorizedWs; this.backendSubscriptions.forEach(subscription => { @@ -14,6 +14,23 @@ export default class SubscriptionsManager { }); } + async close() { + if (!this.authorizedWs) { + return; + } + + // Collect promises from the async unsubscribe calls + const unsubscribePromises = Array.from(this.backendSubscriptions.values()).map(async backendSubscription => { + await backendSubscription.unsubscribe(); + }); + + // Clear the subscriptions map after all promises have resolved + this.backendSubscriptions.clear(); + + // Await all the unsubscribe promises to finish + await Promise.all(unsubscribePromises); + } + async subscribe( name: TSocketSubscribableEndpointNames, payload: TSocketRequestPayload['payload'], diff --git a/packages/api-v2/src/ws-client/ws-client.ts b/packages/api-v2/src/ws-client/ws-client.ts index 66e471950102..6c153bba4890 100644 --- a/packages/api-v2/src/ws-client/ws-client.ts +++ b/packages/api-v2/src/ws-client/ws-client.ts @@ -62,4 +62,9 @@ export default class WSClient { ) { return this.subscriptionManager?.subscribe(name, payload, onData); } + + async close() { + await this.subscriptionManager.close(); + this.ws?.close(); + } } diff --git a/packages/appstore/package.json b/packages/appstore/package.json index f5d73bcb4665..b6b6ecff5c56 100644 --- a/packages/appstore/package.json +++ b/packages/appstore/package.json @@ -28,7 +28,7 @@ "dependencies": { "@deriv-com/analytics": "1.14.0", "@deriv-com/translations": "1.3.9", - "@deriv-com/ui": "1.35.2", + "@deriv-com/ui": "1.36.2", "@deriv/account": "^1.0.0", "@deriv/cashier": "^1.0.0", "@deriv/cfd": "^1.0.0", diff --git a/packages/appstore/src/components/disclaimer/disclaimer.tsx b/packages/appstore/src/components/disclaimer/disclaimer.tsx index e8895bdcba95..f825d7a9c75f 100644 --- a/packages/appstore/src/components/disclaimer/disclaimer.tsx +++ b/packages/appstore/src/components/disclaimer/disclaimer.tsx @@ -10,7 +10,7 @@ const Disclaimer = () => { return (
- +
diff --git a/packages/bot-skeleton/src/utils/date-time-helper.js b/packages/bot-skeleton/src/utils/date-time-helper.js index 30f593273b17..4afcc26807dc 100644 --- a/packages/bot-skeleton/src/utils/date-time-helper.js +++ b/packages/bot-skeleton/src/utils/date-time-helper.js @@ -10,9 +10,12 @@ export const timeSince = timestamp => { if (secondPast < 3600) { return localize('{{minutePast}}m ago', { minutePast: parseInt(secondPast / 60) }); } - if (secondPast <= 86400) { + if (secondPast < 86400) { return localize('{{hourPast}}h ago', { hourPast: parseInt(secondPast / 3600) }); } + if (secondPast <= 432000) { + return localize('{{days}} days ago', { days: parseInt(secondPast / 86400) }); + } const timestampDate = new Date(timestamp); const day = timestampDate.getDate(); diff --git a/packages/bot-web-ui/package.json b/packages/bot-web-ui/package.json index 645f4d7e0d11..b7bed122c466 100644 --- a/packages/bot-web-ui/package.json +++ b/packages/bot-web-ui/package.json @@ -71,7 +71,7 @@ "webpack-cli": "^4.7.2" }, "dependencies": { - "@deriv-com/ui": "1.35.2", + "@deriv-com/ui": "1.36.2", "@deriv/api": "^1.0.0", "@deriv/api-types": "1.0.172", "@deriv/bot-skeleton": "^1.0.0", diff --git a/packages/bot-web-ui/src/pages/dashboard/announcements/__tests__/announcement-dialog.spec.tsx b/packages/bot-web-ui/src/pages/dashboard/announcements/__tests__/announcement-dialog.spec.tsx index 1cfd3d050364..f9269b88a1b8 100644 --- a/packages/bot-web-ui/src/pages/dashboard/announcements/__tests__/announcement-dialog.spec.tsx +++ b/packages/bot-web-ui/src/pages/dashboard/announcements/__tests__/announcement-dialog.spec.tsx @@ -1,10 +1,10 @@ import React from 'react'; import { mockStore, StoreProvider } from '@deriv/stores'; import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { mock_ws } from 'Utils/mock'; import RootStore from 'Stores/index'; import { DBotStoreProvider, mockDBotStore } from 'Stores/useDBotStore'; -import userEvent from '@testing-library/user-event'; import AnnouncementDialog from '../announcement-dialog'; import { ANNOUNCEMENTS } from '../config'; diff --git a/packages/bot-web-ui/src/pages/dashboard/announcements/__tests__/announcement.spec.tsx b/packages/bot-web-ui/src/pages/dashboard/announcements/__tests__/announcement.spec.tsx index b56f68a0e158..7a6c28e22749 100644 --- a/packages/bot-web-ui/src/pages/dashboard/announcements/__tests__/announcement.spec.tsx +++ b/packages/bot-web-ui/src/pages/dashboard/announcements/__tests__/announcement.spec.tsx @@ -1,11 +1,11 @@ import React from 'react'; import { mockStore, StoreProvider } from '@deriv/stores'; +import { Notifications as Announcement } from '@deriv-com/ui'; import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { mock_ws } from 'Utils/mock'; import RootStore from 'Stores/index'; import { DBotStoreProvider, mockDBotStore } from 'Stores/useDBotStore'; -import { Notifications as Announcement } from '@deriv-com/ui'; -import userEvent from '@testing-library/user-event'; jest.mock('@deriv/bot-skeleton/src/scratch/dbot', () => jest.fn()); diff --git a/packages/bot-web-ui/src/pages/dashboard/announcements/__tests__/announcements.spec.tsx b/packages/bot-web-ui/src/pages/dashboard/announcements/__tests__/announcements.spec.tsx index 53ee20721529..b6fd20aa9fd2 100644 --- a/packages/bot-web-ui/src/pages/dashboard/announcements/__tests__/announcements.spec.tsx +++ b/packages/bot-web-ui/src/pages/dashboard/announcements/__tests__/announcements.spec.tsx @@ -1,12 +1,12 @@ import React from 'react'; import { mockStore, StoreProvider } from '@deriv/stores'; import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { DBOT_TABS } from 'Constants/bot-contents'; import { mock_ws } from 'Utils/mock'; import RootStore from 'Stores/index'; import { DBotStoreProvider, mockDBotStore } from 'Stores/useDBotStore'; import Announcements from '../announcements'; -import userEvent from '@testing-library/user-event'; -import { DBOT_TABS } from 'Constants/bot-contents'; import { BOT_ANNOUNCEMENTS_LIST } from '../config'; jest.mock('@deriv/bot-skeleton/src/scratch/dbot', () => jest.fn()); diff --git a/packages/bot-web-ui/src/pages/dashboard/announcements/announcement-dialog.tsx b/packages/bot-web-ui/src/pages/dashboard/announcements/announcement-dialog.tsx index 9711d62793e3..4f5a11561bc4 100644 --- a/packages/bot-web-ui/src/pages/dashboard/announcements/announcement-dialog.tsx +++ b/packages/bot-web-ui/src/pages/dashboard/announcements/announcement-dialog.tsx @@ -1,9 +1,9 @@ import React from 'react'; import { Dialog, Text } from '@deriv/components'; import { LabelPairedCheckCaptionFillIcon } from '@deriv/quill-icons'; -import './announcement-dialog.scss'; -import { TAnnounce, TContentItem } from './config'; import { IconAnnounceModal } from './announcement-components'; +import { TAnnounce, TContentItem } from './config'; +import './announcement-dialog.scss'; type TAccumulatorAnnouncementDialog = { announcement: TAnnounce; diff --git a/packages/bot-web-ui/src/pages/dashboard/announcements/announcements.tsx b/packages/bot-web-ui/src/pages/dashboard/announcements/announcements.tsx index 8574f66088ff..6ca0793b21bc 100644 --- a/packages/bot-web-ui/src/pages/dashboard/announcements/announcements.tsx +++ b/packages/bot-web-ui/src/pages/dashboard/announcements/announcements.tsx @@ -1,17 +1,18 @@ import React from 'react'; -import { observer } from '@deriv/stores'; +import classNames from 'classnames'; +import { useHistory } from 'react-router-dom'; import { Text } from '@deriv/components'; -import { Notifications as Announcement } from '@deriv-com/ui'; import { StandaloneBullhornRegularIcon } from '@deriv/quill-icons'; -import { useHistory } from 'react-router-dom'; -import classNames from 'classnames'; +import { observer } from '@deriv/stores'; import { localize } from '@deriv/translations'; +import { Notifications as Announcement } from '@deriv-com/ui'; +import { useDBotStore } from 'Stores/useDBotStore'; +import { guide_content } from '../../tutorials/constants'; +import { performButtonAction } from './utils/accumulator-helper-functions'; +import { MessageAnnounce, TitleAnnounce } from './announcement-components'; import AnnouncementDialog from './announcement-dialog'; import { BOT_ANNOUNCEMENTS_LIST, TAnnouncement, TNotifications } from './config'; import './announcements.scss'; -import { MessageAnnounce, TitleAnnounce } from './announcement-components'; -import { performButtonAction } from './utils/accumulator-helper-functions'; -import { useDBotStore } from 'Stores/useDBotStore'; type TAnnouncements = { is_mobile?: boolean; @@ -22,6 +23,7 @@ type TAnnouncements = { const Announcements = observer(({ is_mobile, is_tablet, handleTabChange }: TAnnouncements) => { const { load_modal: { toggleLoadModal }, + dashboard: { showVideoDialog }, } = useDBotStore(); const [is_announce_dialog_open, setIsAnnounceDialogOpen] = React.useState(false); const [is_open_announce_list, setIsOpenAnnounceList] = React.useState(false); @@ -92,9 +94,19 @@ const Announcements = observer(({ is_mobile, is_tablet, handleTabChange }: TAnno // eslint-disable-next-line react-hooks/exhaustive-deps }, [read_announcements_map]); + const openAccumulatorsVideo = () => { + const accumulators_video = guide_content.find(guide_content => guide_content.id === 4); + if (accumulators_video) { + showVideoDialog({ url: accumulators_video.url, type: 'url' }); + } + }; + const handleOnCancel = () => { if (selected_announcement?.switch_tab_on_cancel) { handleTabChange(selected_announcement.switch_tab_on_cancel); + if (selected_announcement.announcement.id === 'ACCUMULATOR_ANNOUNCE') { + openAccumulatorsVideo(); + } } selected_announcement?.onCancel?.(); setSelectedAnnouncement(null); @@ -157,6 +169,7 @@ const Announcements = observer(({ is_mobile, is_tablet, handleTabChange }: TAnno setIsOpen={setIsOpenAnnounceList} notifications={notifications} excludedClickOutsideClass={action_button_class_name} + {...(is_mobile && { appElement: document.getElementById('modal_root') })} />
{selected_announcement?.announcement && ( diff --git a/packages/bot-web-ui/src/pages/dashboard/announcements/config.tsx b/packages/bot-web-ui/src/pages/dashboard/announcements/config.tsx index e35992248bad..00d5f7035525 100644 --- a/packages/bot-web-ui/src/pages/dashboard/announcements/config.tsx +++ b/packages/bot-web-ui/src/pages/dashboard/announcements/config.tsx @@ -1,9 +1,9 @@ import React from 'react'; +import { OpenLiveChatLink } from '@deriv/components'; import { Localize, localize } from '@deriv/translations'; import { DBOT_TABS } from 'Constants/bot-contents'; import { handleOnConfirmAccumulator } from './utils/accumulator-helper-functions'; import { IconAnnounce } from './announcement-components'; -import { OpenLiveChatLink } from '@deriv/components'; export type TContentItem = { id: number; @@ -144,7 +144,7 @@ export const ANNOUNCEMENTS: Record = { }, switch_tab_on_cancel: DBOT_TABS.TUTORIAL, switch_tab_on_confirm: DBOT_TABS.BOT_BUILDER, - onConfirm: handleOnConfirmAccumulator, + onConfirm: () => handleOnConfirmAccumulator(), }, }; diff --git a/packages/bot-web-ui/src/pages/dashboard/bot-list/index.scss b/packages/bot-web-ui/src/pages/dashboard/bot-list/index.scss index 81f54ac60ff5..4c3a40437696 100644 --- a/packages/bot-web-ui/src/pages/dashboard/bot-list/index.scss +++ b/packages/bot-web-ui/src/pages/dashboard/bot-list/index.scss @@ -368,7 +368,7 @@ overflow: auto; @include mobile-screen { - height: calc(100vh - 46rem); + height: calc(100vh - 55rem); } } diff --git a/packages/bot-web-ui/src/pages/dashboard/bot-list/recent-workspace.tsx b/packages/bot-web-ui/src/pages/dashboard/bot-list/recent-workspace.tsx index a19612183c1b..50dca94b9e63 100644 --- a/packages/bot-web-ui/src/pages/dashboard/bot-list/recent-workspace.tsx +++ b/packages/bot-web-ui/src/pages/dashboard/bot-list/recent-workspace.tsx @@ -11,6 +11,7 @@ import { rudderStackSendDashboardClickEvent } from '../../../analytics/ruddersta import { useComponentVisibility } from '../../../hooks'; import { TRecentStrategy } from './types'; import './index.scss'; +import { localize } from '@deriv/translations'; type TRecentWorkspace = { workspace: TRecentStrategy; @@ -79,7 +80,6 @@ const RecentWorkspace = observer(({ workspace }: TRecentWorkspace) => { const is_active_mobile = selected_strategy_id === workspace.id && is_dropdown_visible; const text_size = is_desktop ? 'xs' : 'xxs'; - return (
{
- {workspace.name} + {workspace.name || localize('Untitled Bot')}
diff --git a/packages/bot-web-ui/src/pages/main/main.tsx b/packages/bot-web-ui/src/pages/main/main.tsx index 4ba91840680a..76eff964b778 100644 --- a/packages/bot-web-ui/src/pages/main/main.tsx +++ b/packages/bot-web-ui/src/pages/main/main.tsx @@ -84,7 +84,7 @@ const AppWrapper = observer(() => { } else { window.location.hash = hash[active_tab] || hash[0]; } - if (tour_list[active_tab] !== active_tour) { + if (active_tour !== '') { setActiveTour(''); } // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/packages/bot-web-ui/src/pages/tutorials/constants.ts b/packages/bot-web-ui/src/pages/tutorials/constants.ts index 69e81a6a3638..cb81af05facd 100644 --- a/packages/bot-web-ui/src/pages/tutorials/constants.ts +++ b/packages/bot-web-ui/src/pages/tutorials/constants.ts @@ -107,7 +107,7 @@ export const faq_content: TFaqContent[] = [ { type: 'text', content: localize( - 'For more info, check out this blog post on the basics of building a trading bot.' + 'For more info, check out this blog post on the basics of building a trading bot.' ), }, ], @@ -446,7 +446,7 @@ export const faq_content: TFaqContent[] = [ { type: 'text', content: localize( - 'Watch this video to learn how to build a trading bot on Deriv Bot. Also, check out this blog post on building a trading bot.' + 'Watch this video to learn how to build a trading bot on Deriv Bot. Also, check out this blog post on building a trading bot.' ), }, ], diff --git a/packages/bot-web-ui/src/pages/tutorials/dbot-tours/onboarding-tour/onboarding-tour-mobile.tsx b/packages/bot-web-ui/src/pages/tutorials/dbot-tours/onboarding-tour/onboarding-tour-mobile.tsx index f8f697b0dcdd..e35d4cee8a9d 100644 --- a/packages/bot-web-ui/src/pages/tutorials/dbot-tours/onboarding-tour/onboarding-tour-mobile.tsx +++ b/packages/bot-web-ui/src/pages/tutorials/dbot-tours/onboarding-tour/onboarding-tour-mobile.tsx @@ -32,6 +32,16 @@ const OnboardingTourMobile = observer(() => { const hide_prev_button = [1, 2, 8]; const is_tour_active = active_tour === 'onboarding'; + React.useEffect(() => { + const checkTokenForTour = () => { + const token = getSetting('onboard_tour_token'); + if (!token && active_tab === 0) { + setActiveTour('onboarding'); + } + }; + checkTokenForTour(); + }, [active_tab, active_tour]); + React.useEffect(() => { DBOT_ONBOARDING_MOBILE.forEach(data => { if (data.tour_step_key === tour_step) { @@ -42,9 +52,6 @@ const OnboardingTourMobile = observer(() => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [tour_step]); - const token = getSetting('onboard_tour_token'); - if (!token && active_tab === 0) setActiveTour('onboarding'); - if (!active_tour) { return null; } diff --git a/packages/bot-web-ui/src/pages/tutorials/tutorials.scss b/packages/bot-web-ui/src/pages/tutorials/tutorials.scss index bb65e10ef9a1..51b822764b11 100644 --- a/packages/bot-web-ui/src/pages/tutorials/tutorials.scss +++ b/packages/bot-web-ui/src/pages/tutorials/tutorials.scss @@ -143,6 +143,11 @@ } .faq { + &__description { + a { + text-decoration: underline; + } + } &__wrapper { overflow: auto; padding-bottom: 18px; diff --git a/packages/cashier/package.json b/packages/cashier/package.json index f9709361a71a..fae3d4d6f533 100644 --- a/packages/cashier/package.json +++ b/packages/cashier/package.json @@ -37,8 +37,8 @@ "url": "https://github.com/deriv-com/deriv-app/issues" }, "dependencies": { - "@deriv-com/ui": "1.35.2", "@deriv-com/analytics": "1.14.0", + "@deriv-com/ui": "1.36.2", "@deriv/api": "^1.0.0", "@deriv/api-types": "1.0.172", "@deriv/components": "^1.0.0", @@ -48,7 +48,6 @@ "@deriv/shared": "^1.0.0", "@deriv/stores": "^1.0.0", "@deriv/translations": "^1.0.0", - "@deriv/ui": "^0.6.0", "classnames": "^2.2.6", "formik": "^2.1.4", "framer-motion": "^6.5.1", diff --git a/packages/cashier/src/app-content.tsx b/packages/cashier/src/app-content.tsx index 63f8fc2680f2..9c2d875c370c 100644 --- a/packages/cashier/src/app-content.tsx +++ b/packages/cashier/src/app-content.tsx @@ -1,22 +1,13 @@ import React from 'react'; import { observer, useStore } from '@deriv/stores'; -import { useTheme } from '@deriv/ui'; import Routes from './containers/routes'; import useUnsafeCashierRouteHandler from './containers/routes/useUnsafeCashierRouteHandler'; const AppContent: React.FC = observer(() => { const { ui } = useStore(); - const { is_dark_mode_on, notification_messages_ui: Notifications } = ui; - const { setColorMode } = useTheme(); + const { notification_messages_ui: Notifications } = ui; useUnsafeCashierRouteHandler(); - React.useEffect(() => { - const theme = is_dark_mode_on ? 'dark' : 'light'; - // @ts-expect-error setColorMode accepts a enum as a parameter which is not exported, - // It should be refactored to union type instead. - setColorMode(theme); - }, [is_dark_mode_on, setColorMode]); - return ( <> {Notifications && } diff --git a/packages/cashier/src/cashier-providers.tsx b/packages/cashier/src/cashier-providers.tsx index a0d87fa8b1ea..57678693e705 100644 --- a/packages/cashier/src/cashier-providers.tsx +++ b/packages/cashier/src/cashier-providers.tsx @@ -1,15 +1,12 @@ import React from 'react'; import { StoreProvider } from '@deriv/stores'; -import { ThemeProvider } from '@deriv/ui'; import { CashierStoreProvider } from './stores/useCashierStores'; type TProps = { store: React.ComponentProps['store'] }; const CashierProviders: React.FC> = ({ children, store }) => ( - - {children} - + {children} ); diff --git a/packages/cashier/src/components/cashier-breadcrumb/cashier-breadcrumb.scss b/packages/cashier/src/components/cashier-breadcrumb/cashier-breadcrumb.scss deleted file mode 100644 index c5eb45eaa3c3..000000000000 --- a/packages/cashier/src/components/cashier-breadcrumb/cashier-breadcrumb.scss +++ /dev/null @@ -1,15 +0,0 @@ -.cashier-breadcrumb { - width: 100%; - - ul { - & > *:not(:last-child) { - cursor: pointer; - } - - @include rtl { - li { - float: right; - } - } - } -} diff --git a/packages/cashier/src/components/cashier-breadcrumb/cashier-breadcrumb.tsx b/packages/cashier/src/components/cashier-breadcrumb/cashier-breadcrumb.tsx index bc47749c01d9..445469bd347e 100644 --- a/packages/cashier/src/components/cashier-breadcrumb/cashier-breadcrumb.tsx +++ b/packages/cashier/src/components/cashier-breadcrumb/cashier-breadcrumb.tsx @@ -1,25 +1,24 @@ import React from 'react'; +import { Breadcrumbs } from '@deriv-com/ui'; import { localize } from '@deriv/translations'; -import { Breadcrumb } from '@deriv/ui'; import { useCashierStore } from '../../stores/useCashierStores'; -import './cashier-breadcrumb.scss'; const CashierBreadcrumb = () => { const { general_store } = useCashierStore(); const { is_crypto, is_deposit, setIsDeposit } = general_store; const is_deposit_crypto = is_deposit && is_crypto; - const deposit_crypto_crumbs: React.ComponentProps['items'] = [ + const deposit_crypto_crumbs: React.ComponentProps['items'] = [ { value: 0, text: localize('Cashier') }, { value: 1, text: localize('Deposit cryptocurrencies') }, ]; - const deposit_fiat_crumbs: React.ComponentProps['items'] = [ + const deposit_fiat_crumbs: React.ComponentProps['items'] = [ { value: 0, text: localize('Cashier') }, { value: 1, text: localize('Deposit via bank wire, credit card, and e-wallet') }, ]; - const onBreadcrumbHandler: React.ComponentProps['handleOnClick'] = item => { + const onBreadcrumbHandler: React.ComponentProps['handleOnClick'] = item => { switch (item.value) { case 0: setIsDeposit(false); @@ -28,14 +27,11 @@ const CashierBreadcrumb = () => { } }; - // TODO: improve Breadcrumb component in deriv-ui project that it can accept custom classnames return ( -
- -
+ ); }; diff --git a/packages/cashier/src/modules/deposit-crypto/components/deposit-crypto-disclaimers/__test__/deposit-crypto-disclaimers.test.tsx b/packages/cashier/src/modules/deposit-crypto/components/deposit-crypto-disclaimers/__test__/deposit-crypto-disclaimers.test.tsx index fac6c009a0e7..4eeee6996942 100644 --- a/packages/cashier/src/modules/deposit-crypto/components/deposit-crypto-disclaimers/__test__/deposit-crypto-disclaimers.test.tsx +++ b/packages/cashier/src/modules/deposit-crypto/components/deposit-crypto-disclaimers/__test__/deposit-crypto-disclaimers.test.tsx @@ -40,7 +40,9 @@ describe('DepositCryptoDisclaimers', () => { expect(screen.getByText('Only send Bitcoin (BTC) to this address.')).toBeInTheDocument(); expect( - screen.getByText('Make sure to copy the Deriv BTC Wallet address to your crypto wallet.') + screen.getByText( + 'Make sure to copy the Deriv BTC account address above and paste it into your crypto wallet.' + ) ).toBeInTheDocument(); expect(screen.getByText(/Bitcoin \(BTC\) network/)).toBeInTheDocument(); }); @@ -52,7 +54,9 @@ describe('DepositCryptoDisclaimers', () => { expect(screen.getByText('Only send Ethereum (ETH) to this address.')).toBeInTheDocument(); expect( - screen.getByText('Make sure to copy the Deriv ETH Wallet address to your crypto wallet.') + screen.getByText( + 'Make sure to copy the Deriv ETH account address above and paste it into your crypto wallet.' + ) ).toBeInTheDocument(); expect(screen.getByText(/Ethereum \(ETH\) network/)).toBeInTheDocument(); }); @@ -64,7 +68,9 @@ describe('DepositCryptoDisclaimers', () => { expect(screen.getByText('Only send Litecoin (LTC) to this address.')).toBeInTheDocument(); expect( - screen.getByText('Make sure to copy the Deriv LTC Wallet address to your crypto wallet.') + screen.getByText( + 'Make sure to copy the Deriv LTC account address above and paste it into your crypto wallet.' + ) ).toBeInTheDocument(); expect(screen.getByText(/Litecoin \(LTC\) network/)).toBeInTheDocument(); }); @@ -76,7 +82,9 @@ describe('DepositCryptoDisclaimers', () => { expect(screen.getByText('Only send USD Coin (USDC) to this address.')).toBeInTheDocument(); expect( - screen.getByText('Make sure to copy the Deriv USDC Wallet address to your crypto wallet.') + screen.getByText( + 'Make sure to copy the Deriv USDC account address above and paste it into your crypto wallet.' + ) ).toBeInTheDocument(); expect(screen.getByText(/Ethereum \(ERC20\) network/)).toBeInTheDocument(); }); @@ -88,7 +96,9 @@ describe('DepositCryptoDisclaimers', () => { expect(screen.getByText('Only send TerraUSD (USDT) to this address.')).toBeInTheDocument(); expect( - screen.getByText('Make sure to copy the Deriv UST Wallet address to your crypto wallet.') + screen.getByText( + 'Make sure to copy the Deriv UST account address above and paste it into your crypto wallet.' + ) ).toBeInTheDocument(); expect(screen.getByText(/Omnicore network/)).toBeInTheDocument(); }); @@ -100,7 +110,9 @@ describe('DepositCryptoDisclaimers', () => { expect(screen.getByText('Only send ERC20 (eUSDT) to this address.')).toBeInTheDocument(); expect( - screen.getByText('Make sure to copy the Deriv eUSDT Wallet address to your crypto wallet.') + screen.getByText( + 'Make sure to copy the Deriv eUSDT account address above and paste it into your crypto wallet.' + ) ).toBeInTheDocument(); expect(screen.getByText(/\(ERC20\) network/)).toBeInTheDocument(); }); diff --git a/packages/cashier/src/modules/deposit-crypto/components/deposit-crypto-disclaimers/deposit-crypto-disclaimers.tsx b/packages/cashier/src/modules/deposit-crypto/components/deposit-crypto-disclaimers/deposit-crypto-disclaimers.tsx index 02037c746bbe..031e32c83a40 100644 --- a/packages/cashier/src/modules/deposit-crypto/components/deposit-crypto-disclaimers/deposit-crypto-disclaimers.tsx +++ b/packages/cashier/src/modules/deposit-crypto/components/deposit-crypto-disclaimers/deposit-crypto-disclaimers.tsx @@ -63,7 +63,7 @@ const DepositCryptoDisclaimers: React.FC = observer(() => {
  • diff --git a/packages/cfd/package.json b/packages/cfd/package.json index fb83c651db33..c86b01893362 100644 --- a/packages/cfd/package.json +++ b/packages/cfd/package.json @@ -85,7 +85,7 @@ "webpack-node-externals": "^2.5.2" }, "dependencies": { - "@deriv-com/ui": "1.35.2", + "@deriv-com/ui": "1.36.2", "@deriv-com/analytics": "1.14.0", "@deriv-com/translations": "1.3.9", "@deriv-com/utils": "^0.0.34", diff --git a/packages/components/package.json b/packages/components/package.json index 194e4567b144..d7095dc48e0f 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -74,7 +74,7 @@ "dependencies": { "@cloudflare/stream-react": "^1.9.1", "@contentpass/zxcvbn": "^4.4.3", - "@deriv-com/ui": "1.35.2", + "@deriv-com/ui": "1.36.2", "@deriv/hooks": "^1.0.0", "@deriv/shared": "^1.0.0", "@deriv/stores": "^1.0.0", diff --git a/packages/core/package.json b/packages/core/package.json index 54c2e2cef8f4..29745b5d1942 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -100,8 +100,8 @@ "@deriv-com/quill-tokens": "2.0.4", "@deriv-com/quill-ui": "1.16.20", "@deriv-com/translations": "1.3.9", + "@deriv-com/ui": "1.36.2", "@deriv-com/utils": "^0.0.34", - "@deriv-com/ui": "1.35.2", "@deriv/account": "^1.0.0", "@deriv/api": "^1.0.0", "@deriv/appstore": "^0.0.4", @@ -158,4 +158,4 @@ "react-window": "^1.8.5", "usehooks-ts": "^2.7.0" } -} \ No newline at end of file +} diff --git a/packages/core/src/Stores/contract-trade-store.js b/packages/core/src/Stores/contract-trade-store.js index ec6e8ec2f8ef..3484eac77ba9 100644 --- a/packages/core/src/Stores/contract-trade-store.js +++ b/packages/core/src/Stores/contract-trade-store.js @@ -8,10 +8,12 @@ import { isAccumulatorContractOpen, isCallPut, isDesktop, + isDigitContract, isEnded, isHighLow, isMultiplierContract, isTurbosContract, + isUpDownContract, isVanillaContract, LocalStore, setTradeURLParams, @@ -31,10 +33,10 @@ export default class ContractTradeStore extends BaseStore { error_message = ''; // Chart specific observables - granularity = +LocalStore.get('contract_trade.granularity') || 0; - chart_type = LocalStore.get('contract_trade.chart_style') || 'line'; - prev_chart_type = ''; - prev_granularity = null; + saved_granularity = +LocalStore.get('contract_trade.granularity'); + saved_chart_type = LocalStore.get('contract_trade.chart_style'); + chart_type = ''; + granularity = null; // Accumulator barriers data: accu_barriers_timeout_id = null; @@ -45,34 +47,36 @@ export default class ContractTradeStore extends BaseStore { super({ root_store }); makeObservable(this, { + accountSwitchListener: action.bound, accu_barriers_timeout_id: observable, accumulator_barriers_data: observable.struct, accumulator_contract_barriers_data: observable.struct, + addContract: action.bound, + chart_type: observable, clearAccumulatorBarriersData: action.bound, + clearError: action.bound, contracts: observable.shallow, - has_crossed_accu_barriers: computed, - has_error: observable, error_message: observable, + filtered_contracts: computed, + getContractById: action.bound, granularity: observable, - chart_type: observable, - updateAccumulatorBarriersData: action.bound, - updateChartType: action.bound, - updateGranularity: action.bound, + has_crossed_accu_barriers: computed, + has_error: observable, + last_contract: computed, markers_array: computed, - filtered_contracts: computed, - addContract: action.bound, - removeContract: action.bound, - accountSwitchListener: action.bound, onUnmount: override, - prev_chart_type: observable, - prev_granularity: observable, - updateProposal: action.bound, - last_contract: computed, - clearError: action.bound, - getContractById: action.bound, prev_contract: computed, - savePreviousChartMode: action.bound, + removeContract: action.bound, + saveChartType: action.bound, + saved_chart_type: observable, + saved_granularity: observable, + saveGranularity: action.bound, + setChartTypeAndGranularity: action.bound, setNewAccumulatorBarriersData: action.bound, + updateAccumulatorBarriersData: action.bound, + updateChartType: action.bound, + updateGranularity: action.bound, + updateProposal: action.bound, }); this.root_store = root_store; @@ -133,6 +137,22 @@ export default class ContractTradeStore extends BaseStore { } } + setChartTypeAndGranularity(type, granularity) { + this.chart_type = type; + this.granularity = granularity; + if (this.granularity === 0) { + this.root_store.notifications.removeNotificationMessage(switch_to_tick_chart); + } + } + + saveChartType(chart_type) { + this.saved_chart_type = chart_type; + } + + saveGranularity(granularity) { + this.saved_granularity = granularity; + } + updateAccumulatorBarriersData({ accumulators_high_barrier, accumulators_low_barrier, @@ -202,12 +222,19 @@ export default class ContractTradeStore extends BaseStore { } updateChartType(type) { + const { contract_type } = JSON.parse(sessionStorage.getItem('trade_store')) || {}; + const is_ticks_contract = + isDigitContract(contract_type) || isAccumulatorContract(contract_type) || isUpDownContract(contract_type); LocalStore.set('contract_trade.chart_style', type); this.chart_type = type; setTradeURLParams({ chartType: this.chart_type }); + !is_ticks_contract && this.saveChartType(this.chart_type); } updateGranularity(granularity) { + const { contract_type } = JSON.parse(sessionStorage.getItem('trade_store')) || {}; + const is_ticks_contract = + isDigitContract(contract_type) || isAccumulatorContract(contract_type) || isUpDownContract(contract_type); const tick_chart_types = ['line', 'candles', 'hollow', 'ohlc']; if (granularity === 0 && tick_chart_types.indexOf(this.chart_type) === -1) { @@ -220,11 +247,7 @@ export default class ContractTradeStore extends BaseStore { this.root_store.notifications.removeNotificationMessage(switch_to_tick_chart); } setTradeURLParams({ granularity: this.granularity }); - } - - savePreviousChartMode(chart_type, granularity) { - this.prev_chart_type = chart_type; - this.prev_granularity = granularity; + !is_ticks_contract && this.saveGranularity(this.granularity); } applicable_contracts = () => { diff --git a/packages/p2p/package.json b/packages/p2p/package.json index cfba55801fd6..7c7b6da8138b 100644 --- a/packages/p2p/package.json +++ b/packages/p2p/package.json @@ -32,7 +32,7 @@ "author": "", "license": "ISC", "dependencies": { - "@deriv-com/ui": "1.35.2", + "@deriv-com/ui": "1.36.2", "@deriv-com/analytics": "1.14.0", "@deriv-com/utils": "^0.0.34", "@deriv/api": "^1.0.0", diff --git a/packages/p2p/src/translations/sw.json b/packages/p2p/src/translations/sw.json index fd821ebdbd56..a2f59637a90b 100644 --- a/packages/p2p/src/translations/sw.json +++ b/packages/p2p/src/translations/sw.json @@ -162,7 +162,7 @@ "1210647712": "02:30 pm", "1220160135": "Unaunda oda ya kununua <0>{{currency}} {{input_amount}} kwa <1>{{local_currency}} {{received_amount}}.", "1228352589": "Bado haijakadiriwa", - "1228998709": "<0>Important: Deriv haitawahi kuwasiliana nawe kupitia WhatsApp kukuuliza kuhusu taarifa zako binafsi. Daima puuza ujumbe wowote kutoka kwa nambari zinazodai kuwa ni kutoka Deriv.", + "1228998709": "<0>Muhimu: Deriv haitawahi kuwasiliana nawe kupitia WhatsApp kukuuliza kuhusu taarifa zako binafsi. Daima puuza ujumbe wowote kutoka kwenye nambari zinazodai kuwa ni kutoka Deriv.", "1229976478": "Utaweza kuona matangazo ya {{ advertiser_name }}. Wataweza kuweka maagizo kwenye matangazo yako pia.", "1236083813": "Taarifa za malipo yako", "1237846039": "07:30 am", @@ -392,7 +392,7 @@ "-1667041441": "Kiwango (1 {{ offered_currency }})", "-792015701": "Deriv P2P cashier haipatikani katika nchi yako.", "-1983512566": "Mazungumzo hayo yamefungwa.", - "-1386739462": "<0>Note: Katika kesi ya mzozo, tutatumia chat hii kama kumbukumbu.", + "-1386739462": "<0>Kumbuka: Katika kesi ya mzozo, tutatumia mazungumzo haya kama kumbukumbu.", "-283017497": "Jaribu tena", "-360975483": "Hujafanya muamala wa aina hii katika kipindi hiki.", "-979459594": "Kununu/Kuuza", diff --git a/packages/reports/package.json b/packages/reports/package.json index 2698d630746f..0c7894319033 100644 --- a/packages/reports/package.json +++ b/packages/reports/package.json @@ -77,7 +77,7 @@ "webpack-node-externals": "^2.5.2" }, "dependencies": { - "@deriv-com/ui": "1.35.2", + "@deriv-com/ui": "1.36.2", "@deriv-com/analytics": "1.14.0", "@deriv/components": "^1.0.0", "@deriv/deriv-api": "^1.0.15", diff --git a/packages/shared/src/utils/constants/contract.ts b/packages/shared/src/utils/constants/contract.ts index 87706451cfad..38f9a2510b6a 100644 --- a/packages/shared/src/utils/constants/contract.ts +++ b/packages/shared/src/utils/constants/contract.ts @@ -30,7 +30,7 @@ type TContractTypesConfig = { basis: string[]; components: string[]; barrier_count?: number; - config?: { hide_duration?: boolean }; + config?: { hide_duration?: boolean; default_stake?: number }; }; type TGetContractTypesConfig = (symbol?: string) => Record; diff --git a/packages/shared/src/utils/contract/__tests__/trade-url-params-config.spec.ts b/packages/shared/src/utils/contract/__tests__/trade-url-params-config.spec.ts index 2496d9ea8e42..65f3472cd93e 100644 --- a/packages/shared/src/utils/contract/__tests__/trade-url-params-config.spec.ts +++ b/packages/shared/src/utils/contract/__tests__/trade-url-params-config.spec.ts @@ -4,7 +4,9 @@ import { TRADE_TYPES } from '../contract'; import { routes } from '../../routes'; const areaChartType = { text: 'area', value: 'line' }; +const candleChartType = { text: 'candle', value: 'candles' }; const oneTickInterval = '1t'; +const oneMinuteInterval = '1m'; const symbol = 'R_100'; describe('getTradeURLParams', () => { @@ -56,10 +58,10 @@ describe('getTradeURLParams', () => { }); it('should return an object with chartType & interval based on the URL query params when called without arguments', () => { - location.search = `?symbol=${symbol}&trade_type=${TRADE_TYPES.ACCUMULATOR}&chart_type=${areaChartType.text}&interval=${oneTickInterval}`; + location.search = `?symbol=${symbol}&trade_type=${TRADE_TYPES.VANILLA}&chart_type=${candleChartType.text}&interval=${oneMinuteInterval}`; expect(getTradeURLParams()).toMatchObject({ - chartType: areaChartType.value, - granularity: 0, + chartType: candleChartType.value, + granularity: 60, }); }); it('should return an object without granularity if interval in the URL is incorrect', () => { @@ -74,13 +76,6 @@ describe('getTradeURLParams', () => { granularity: 0, }); }); - it('should return an object with "area" chartType if interval is 1t even if chart_type in the URL is valid but not area', () => { - location.search = `?symbol=${symbol}&trade_type=${TRADE_TYPES.ACCUMULATOR}&chart_type=hollow&interval=${oneTickInterval}`; - expect(getTradeURLParams()).toMatchObject({ - chartType: areaChartType.value, - granularity: 0, - }); - }); it('should return an object with symbol based on the URL query param when active_symbols is passed', () => { location.search = `?symbol=${symbol}`; expect(getTradeURLParams({ active_symbols: activeSymbols })).toMatchObject({ @@ -88,10 +83,10 @@ describe('getTradeURLParams', () => { }); }); it('should return an object with showModal & without symbol if symbol in the URL is incorrect and when called with active_symbols', () => { - location.search = `?symbol=BLA&chart_type=${areaChartType.text}&interval=${oneTickInterval}`; + location.search = `?symbol=BLA&chart_type=${candleChartType.text}&interval=${oneMinuteInterval}`; expect(getTradeURLParams({ active_symbols: activeSymbols })).toMatchObject({ - chartType: areaChartType.value, - granularity: 0, + chartType: candleChartType.value, + granularity: 60, showModal: true, }); }); @@ -102,10 +97,10 @@ describe('getTradeURLParams', () => { }); }); it('should return an object with showModal & without contractType if trade_type in the URL is incorrect', () => { - location.search = `?trade_type=BLA&chart_type=${areaChartType.text}&interval=${oneTickInterval}`; + location.search = `?trade_type=BLA&chart_type=${candleChartType.text}&interval=${oneMinuteInterval}`; expect(getTradeURLParams({ contract_types_list: contractTypesList })).toMatchObject({ - chartType: areaChartType.value, - granularity: 0, + chartType: candleChartType.value, + granularity: 60, showModal: true, }); }); diff --git a/packages/shared/src/utils/contract/contract.tsx b/packages/shared/src/utils/contract/contract.tsx index af420b235a26..3d9685c5d9d5 100644 --- a/packages/shared/src/utils/contract/contract.tsx +++ b/packages/shared/src/utils/contract/contract.tsx @@ -174,6 +174,8 @@ export const isResetContract = (contract_type = '') => /RESET/i.test(contract_ty export const isCryptoContract = (underlying = '') => underlying.startsWith('cry'); +export const isUpDownContract = (contract_type = '') => /rise_fall|high_low/i.test(contract_type); + export const getAccuBarriersDefaultTimeout = (symbol: string) => { return symbols_2s.includes(symbol) ? DELAY_TIME_1S_SYMBOL * 2 : DELAY_TIME_1S_SYMBOL; }; diff --git a/packages/shared/src/utils/contract/trade-url-params-config.ts b/packages/shared/src/utils/contract/trade-url-params-config.ts index d7cb1a7ced03..7310e5bd41f4 100644 --- a/packages/shared/src/utils/contract/trade-url-params-config.ts +++ b/packages/shared/src/utils/contract/trade-url-params-config.ts @@ -61,7 +61,7 @@ export const getTradeURLParams = ({ active_symbols = [], contract_types_list = { }>((acc, [key, value]) => ({ ...acc, [key]: value }), {}); const validInterval = tradeURLParamsConfig.interval.find(item => item.text === interval); const validChartType = tradeURLParamsConfig.chartType.find(item => item.text === chart_type); - const chartTypeParam = Number(validInterval?.value) === 0 ? 'line' : validChartType?.value; + const chartTypeParam = Number(validInterval?.value) === 0 ? 'candles' : validChartType?.value; const isSymbolValid = active_symbols.some(item => item.symbol === symbol); const contractList = Object.keys(contract_types_list).reduce((acc, key) => { const categories: TTradeTypesCategories['Ups & Downs']['categories'] = diff --git a/packages/stores/src/mockStore.ts b/packages/stores/src/mockStore.ts index 74a520842b50..da18e6b5c700 100644 --- a/packages/stores/src/mockStore.ts +++ b/packages/stores/src/mockStore.ts @@ -660,11 +660,13 @@ const mock = (): TStores & { is_mock: boolean } => { last_contract: {}, markers_array: [], onUnmount: jest.fn(), - prev_chart_type: '', prev_contract: {}, - prev_granularity: null, removeContract: jest.fn(), - savePreviousChartMode: jest.fn(), + saveChartType: jest.fn(), + saved_chart_type: '', + saved_granularity: null, + saveGranularity: jest.fn(), + setChartTypeAndGranularity: jest.fn(), setNewAccumulatorBarriersData: jest.fn(), updateAccumulatorBarriersData: jest.fn(), updateChartType: jest.fn(), @@ -780,6 +782,7 @@ const mock = (): TStores & { is_mock: boolean } => { setTradeTypeTab: jest.fn(), setV2ParamsInitialValues: jest.fn(), setPayoutPerPoint: jest.fn(), + setDefaultStake: jest.fn(), stake_boundary: {}, start_date: 0, stop_loss: 0, diff --git a/packages/stores/types.ts b/packages/stores/types.ts index 20a32f8bf7e8..50dc2696623b 100644 --- a/packages/stores/types.ts +++ b/packages/stores/types.ts @@ -986,11 +986,13 @@ type TContractTradeStore = { epoch_array: [number]; }>; onUnmount: () => void; - prev_chart_type: string; prev_contract: TContractStore | Record; - prev_granularity: number | null; removeContract: (data: { contract_id: string }) => void; - savePreviousChartMode: (chart_type: string, granularity: number | null) => void; + saveChartType: (chart_type: string) => void; + saved_chart_type: string; + saved_granularity: number | null; + saveGranularity: (granularity: number | null) => void; + setChartTypeAndGranularity: any; setNewAccumulatorBarriersData: ( new_barriers_data: TAccumulatorBarriersData, should_update_contract_barriers?: boolean diff --git a/packages/trader/package.json b/packages/trader/package.json index 9382adb4fa5d..5ffff819763d 100644 --- a/packages/trader/package.json +++ b/packages/trader/package.json @@ -92,7 +92,7 @@ "@deriv-com/quill-tokens": "2.0.4", "@deriv-com/quill-ui": "1.16.20", "@deriv-com/utils": "^0.0.34", - "@deriv-com/ui": "1.35.2", + "@deriv-com/ui": "1.36.2", "@deriv/api-types": "1.0.172", "@deriv/components": "^1.0.0", "@deriv/deriv-api": "^1.0.15", diff --git a/packages/trader/src/AppV2/Components/DraggableList/draggable-list-item.tsx b/packages/trader/src/AppV2/Components/DraggableList/draggable-list-item.tsx index 003fab70aca6..2ca87e480b99 100644 --- a/packages/trader/src/AppV2/Components/DraggableList/draggable-list-item.tsx +++ b/packages/trader/src/AppV2/Components/DraggableList/draggable-list-item.tsx @@ -51,7 +51,7 @@ const DraggableListItem: React.FC = ({ if (!is_moved) setIsMoved(true); }} onTouchEnd={() => { - if (!is_moved && onRightIconClick) { + if (!is_moved && onRightIconClick && !disabled) { onRightIconClick(); } setIsMoved(false); diff --git a/packages/trader/src/AppV2/Components/Guide/__tests__/guide.spec.tsx b/packages/trader/src/AppV2/Components/Guide/__tests__/guide.spec.tsx index 8bedc6504173..2d73e04829a9 100644 --- a/packages/trader/src/AppV2/Components/Guide/__tests__/guide.spec.tsx +++ b/packages/trader/src/AppV2/Components/Guide/__tests__/guide.spec.tsx @@ -142,24 +142,6 @@ describe('Guide', () => { ); }); - it('should render component with correct title description for Vanillas if show_guide_for_selected_contract === true and is_vanilla === true', () => { - default_mock_store.modules.trade.is_vanilla = true; - default_mock_store.modules.trade.contract_type = TRADE_TYPES.VANILLA; - - renderGuide({ show_guide_for_selected_contract: true }); - - userEvent.click(screen.getByRole('button')); - - expect(screen.queryByText(trade_types)).not.toBeInTheDocument(); - expect(screen.getByText(CONTRACT_LIST.VANILLAS)).toBeInTheDocument(); - - AVAILABLE_CONTRACTS.forEach(({ id }) => - id === CONTRACT_LIST.VANILLAS - ? expect(screen.getByText(id)).toBeInTheDocument() - : expect(screen.queryByText(id)).not.toBeInTheDocument() - ); - }); - it('should render term definition if user clicked on it', () => { renderGuide(); diff --git a/packages/trader/src/AppV2/Components/Guide/guide-description-modal.tsx b/packages/trader/src/AppV2/Components/Guide/guide-description-modal.tsx index 352d1423b75a..a2ce0417595b 100644 --- a/packages/trader/src/AppV2/Components/Guide/guide-description-modal.tsx +++ b/packages/trader/src/AppV2/Components/Guide/guide-description-modal.tsx @@ -11,7 +11,7 @@ type TGuideDescriptionModal = { contract_list: { tradeType: React.ReactNode; id: string }[]; is_dark_mode_on?: boolean; is_open?: boolean; - onChipSelect: (e: React.MouseEvent) => void; + onChipSelect: (id: string) => void; onClose: () => void; onTermClick: (term: string) => void; selected_contract_type: string; @@ -58,7 +58,7 @@ const GuideDescriptionModal = ({ {contract_list.map(({ tradeType, id }: { tradeType: React.ReactNode; id: string }) => ( onChipSelect(id)} selected={id === selected_contract_type} > {tradeType} diff --git a/packages/trader/src/AppV2/Components/Guide/guide.tsx b/packages/trader/src/AppV2/Components/Guide/guide.tsx index b0d16c5639dd..44d0914f1f65 100644 --- a/packages/trader/src/AppV2/Components/Guide/guide.tsx +++ b/packages/trader/src/AppV2/Components/Guide/guide.tsx @@ -7,7 +7,6 @@ import { useTraderStore } from 'Stores/useTraderStores'; import { AVAILABLE_CONTRACTS, CONTRACT_LIST } from 'AppV2/Utils/trade-types-utils'; import GuideDefinitionModal from './guide-definition-modal'; import GuideDescriptionModal from './guide-description-modal'; -import { getContractTypesConfig } from '@deriv/shared'; import useContractsForCompany from 'AppV2/Hooks/useContractsForCompany'; type TGuide = { @@ -19,8 +18,8 @@ const Guide = observer(({ has_label, show_guide_for_selected_contract }: TGuide) const { ui: { is_dark_mode_on }, } = useStore(); - const { contract_type, is_vanilla } = useTraderStore(); - const contract_type_title = is_vanilla ? CONTRACT_LIST.VANILLAS : getContractTypesConfig()[contract_type]?.title; + const { contract_type } = useTraderStore(); + const contract_type_title = AVAILABLE_CONTRACTS.find(item => item.for.includes(contract_type))?.id ?? ''; const { trade_types } = useContractsForCompany(); const order = [ CONTRACT_LIST.RISE_FALL, @@ -47,9 +46,7 @@ const Guide = observer(({ has_label, show_guide_for_selected_contract }: TGuide) const [selected_contract_type, setSelectedContractType] = React.useState(contract_type_title); const [selected_term, setSelectedTerm] = React.useState(''); - const onChipSelect = React.useCallback((e: React.MouseEvent) => { - setSelectedContractType((e.target as EventTarget & HTMLButtonElement).textContent ?? ''); - }, []); + const onChipSelect = React.useCallback((id: string) => setSelectedContractType(id ?? ''), []); const onClose = React.useCallback(() => setIsDescriptionOpened(false), []); diff --git a/packages/trader/src/AppV2/Components/TradeParameters/Duration/__tests__/duration.spec.tsx b/packages/trader/src/AppV2/Components/TradeParameters/Duration/__tests__/duration.spec.tsx index c7d383c0a68d..7fa5d5455bef 100644 --- a/packages/trader/src/AppV2/Components/TradeParameters/Duration/__tests__/duration.spec.tsx +++ b/packages/trader/src/AppV2/Components/TradeParameters/Duration/__tests__/duration.spec.tsx @@ -5,6 +5,7 @@ import TraderProviders from '../../../../../trader-providers'; import { mockStore } from '@deriv/stores'; import { TCoreStores } from '@deriv/stores/types'; import userEvent from '@testing-library/user-event'; +import { useSnackbar } from '@deriv-com/quill-ui'; global.ResizeObserver = jest.fn().mockImplementation(() => ({ observe: jest.fn(), @@ -14,23 +15,63 @@ global.ResizeObserver = jest.fn().mockImplementation(() => ({ global.ResizeObserver = ResizeObserver; +jest.mock('@deriv-com/quill-ui', () => ({ + ...jest.requireActual('@deriv-com/quill-ui'), + useSnackbar: jest.fn(), +})); + +jest.mock('AppV2/Hooks/useActiveSymbols', () => ({ + __esModule: true, + default: jest.fn(() => ({ + activeSymbols: [ + { symbol: 'EURUSD', display_name: 'EUR/USD', exchange_is_open: 1 }, + { symbol: 'GBPUSD', display_name: 'GBP/USD', exchange_is_open: 0 }, + { symbol: 'CADAUD', display_name: 'CAD/AUD', exchange_is_open: 0 }, + ], + })), +})); + +jest.mock('@deriv/shared', () => ({ + ...jest.requireActual('@deriv/shared'), + toMoment: jest.fn(() => ({ + clone: jest.fn(), + isSame: jest.fn(), + })), +})); + describe('Duration', () => { - let default_trade_store: TCoreStores; + let default_trade_store: TCoreStores, mockOnChangeMultiple: jest.Mock; beforeEach(() => { + mockOnChangeMultiple = jest.fn(); default_trade_store = mockStore({ modules: { trade: { onChange: jest.fn(), - validation_errors: { barrier_1: [] }, + validation_errors: { duration: [] }, duration: 30, duration_unit: 'm', expiry_type: 'duration', + expiry_time: '', + proposal_info: {}, + onChangeMultiple: mockOnChangeMultiple, + duration_min_max: { + tick: { min: 1, max: 10 }, + intraday: { min: 60, max: 3600 }, + daily: { min: 86400, max: 172800 }, + }, + start_time: null, + symbol: 'EURUSD', }, }, }); }); + const mockAddSnackbar = jest.fn(); + + beforeAll(() => { + (useSnackbar as jest.Mock).mockReturnValue({ addSnackbar: mockAddSnackbar }); + }); const mockDuration = () => { render( @@ -52,6 +93,18 @@ describe('Duration', () => { expect(screen.getByDisplayValue('2 hours 5 minutes')).toBeInTheDocument(); }); + it('should render the correct value for duration in end time', () => { + default_trade_store.modules.trade.duration = 1; + default_trade_store.modules.trade.expiry_time = '23:55'; + default_trade_store.modules.trade.expiry_type = 'endtime'; + const RealDate = Date; + global.Date = jest.fn(() => new RealDate(2024, 0, 1)) as any; + mockDuration(); + expect(screen.getByLabelText('Duration')).toBeInTheDocument(); + expect(screen.getByDisplayValue('Ends on 1 Jan 2024 23:55 GMT')).toBeInTheDocument(); + global.Date = RealDate; + }); + it('should open the ActionSheet when the text field is clicked', () => { default_trade_store.modules.trade.expiry_time = '12:30'; mockDuration(); @@ -62,10 +115,75 @@ describe('Duration', () => { expect(screen.getByRole('dialog')).toBeInTheDocument(); }); - it('should display expiry time in GMT when expiry_type is "time"', () => { - default_trade_store.modules.trade.expiry_type = 'end time'; - default_trade_store.modules.trade.expiry_time = '12:30'; + it('should display a validation error message if there is a duration error', async () => { + default_trade_store.modules.trade.validation_errors.duration = [ + { message: 'Invalid duration', error_field: 'duration' }, + ]; + mockDuration(); + await expect(mockAddSnackbar).toHaveBeenCalled(); + }); + + it('should display the market closed message when the market is closed', () => { + default_trade_store.modules.trade.symbol = 'GBPUSD'; + mockDuration(); + expect(screen.getByText(/duration/i)).toBeInTheDocument(); + expect(screen.getByRole('textbox')).toBeDisabled(); + }); + + it('should set the correct end date when duration is set in days', () => { + default_trade_store.modules.trade.duration = 3; + default_trade_store.modules.trade.duration_unit = 'd'; + mockDuration(); + expect(screen.getByDisplayValue(/ends on/i)).toBeInTheDocument(); + }); + + it('should calculate the correct duration based on the smallest unit from the store', () => { mockDuration(); - expect(screen.getByDisplayValue('Ends at 12:30 GMT')).toBeInTheDocument(); + const smallest_duration = screen.getByDisplayValue('30 minutes'); + expect(smallest_duration).toBeInTheDocument(); + }); + + it('should display duration in seconds if provided', () => { + default_trade_store.modules.trade.duration = 45; + default_trade_store.modules.trade.duration_unit = 's'; + mockDuration(); + expect(screen.getByDisplayValue('45 seconds')).toBeInTheDocument(); + }); + + it('should display correct duration in ticks when tick duration is selected', () => { + default_trade_store.modules.trade.duration = 5; + default_trade_store.modules.trade.duration_unit = 't'; + mockDuration(); + expect(screen.getByDisplayValue('5 ticks')).toBeInTheDocument(); + }); + + it('should update the selected hour and unit when the component is opened', () => { + default_trade_store.modules.trade.duration_unit = 'm'; + default_trade_store.modules.trade.duration = 125; + mockDuration(); + + const textField = screen.getByLabelText('Duration'); + userEvent.click(textField); + + expect(screen.getByDisplayValue('2 hours 5 minutes')).toBeInTheDocument(); + }); + + it('should update the selected unit and time when expiry_time is set', () => { + const mockDate = new Date('2024-10-08T08:00:00Z'); + jest.spyOn(global.Date.prototype, 'getTime').mockReturnValue(mockDate.getTime()); + jest.spyOn(global.Date.prototype, 'toLocaleDateString').mockReturnValue('8 Oct 2024'); + jest.spyOn(global.Date.prototype, 'toISOString').mockReturnValue('2024-10-08T08:00:00Z'); + + default_trade_store.modules.trade.expiry_time = '14:00'; + default_trade_store.modules.trade.expiry_type = 'endtime'; + + mockDuration(); + + const textField = screen.getByLabelText('Duration'); + userEvent.click(textField); + + expect(screen.getByDisplayValue('Ends on 8 Oct 2024 14:00 GMT')).toBeInTheDocument(); + + jest.restoreAllMocks(); }); }); diff --git a/packages/trader/src/AppV2/Components/TradeParameters/Duration/__tests__/duration_container.spec.tsx b/packages/trader/src/AppV2/Components/TradeParameters/Duration/__tests__/duration_container.spec.tsx index 64ab49185cab..bbf985c93bca 100644 --- a/packages/trader/src/AppV2/Components/TradeParameters/Duration/__tests__/duration_container.spec.tsx +++ b/packages/trader/src/AppV2/Components/TradeParameters/Duration/__tests__/duration_container.spec.tsx @@ -1,10 +1,11 @@ import React from 'react'; -import { render, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import DurationActionSheetContainer from '../container'; import { mockStore } from '@deriv/stores'; import { TCoreStores } from '@deriv/stores/types'; import TraderProviders from '../../../../../trader-providers'; import userEvent from '@testing-library/user-event'; +import { ContractType } from 'Stores/Modules/Trading/Helpers/contract-type'; global.ResizeObserver = jest.fn().mockImplementation(() => ({ observe: jest.fn(), @@ -15,11 +16,17 @@ global.ResizeObserver = jest.fn().mockImplementation(() => ({ global.ResizeObserver = ResizeObserver; global.HTMLElement.prototype.scrollIntoView = jest.fn(); -jest.mock('@deriv/quill-icons', () => ({ - ...jest.requireActual('@deriv/quill-icons'), - LabelPairedCalendarSmBoldIcon: jest.fn(({ onClick }) => ( - - )), +jest.mock('Stores/Modules/Trading/Helpers/contract-type', () => ({ + ContractType: { + getTradingEvents: jest.fn(), + }, +})); + +jest.mock('AppV2/Hooks/useActiveSymbols', () => ({ + __esModule: true, + default: jest.fn(() => ({ + activeSymbols: [{ symbol: '1HZ100V', display_name: '"Volatility 100 (1s) Index"', exchange_is_open: 1 }], + })), })); jest.mock('@deriv-com/quill-ui', () => ({ @@ -28,7 +35,7 @@ jest.mock('@deriv-com/quill-ui', () => ({
    + + +
    + + + + + + +
    ); }; diff --git a/packages/wallets/src/features/cfd/__tests__/CFDPlatformsListEmptyState.spec.tsx b/packages/wallets/src/features/cfd/__tests__/CFDPlatformsListEmptyState.spec.tsx index 6c9b53c7e9d2..f0f2017a26cc 100644 --- a/packages/wallets/src/features/cfd/__tests__/CFDPlatformsListEmptyState.spec.tsx +++ b/packages/wallets/src/features/cfd/__tests__/CFDPlatformsListEmptyState.spec.tsx @@ -32,6 +32,7 @@ describe('CFDPlatformsListEmptyState', () => { it('renders proper content', () => { render(); + expect(screen.getByText('Transfer funds')).toBeInTheDocument(); expect( screen.getByText( `To trade CFDs, you'll need to use your ${fiatCurrency} Wallet. Click Transfer to move your ${cryptoCurrency} to your ${fiatCurrency} Wallet.` @@ -42,7 +43,7 @@ describe('CFDPlatformsListEmptyState', () => { it('redirects to `wallet/account-transfer` route if the user is clicking on `Transfer` button', () => { render(); - const transferBtn = screen.getByRole('button', { name: 'Transfer' }); + const transferBtn = screen.getByTestId('dt_cfd_empty_state_transfer_button'); userEvent.click(transferBtn); expect(mockPush).toHaveBeenCalledWith('/wallet/account-transfer', { shouldSelectDefaultWallet: true }); diff --git a/packages/wallets/src/features/cfd/components/CFDPlatformsListAccounts/CFDPlatformsListAccounts.tsx b/packages/wallets/src/features/cfd/components/CFDPlatformsListAccounts/CFDPlatformsListAccounts.tsx index 993685301818..68f97577d784 100644 --- a/packages/wallets/src/features/cfd/components/CFDPlatformsListAccounts/CFDPlatformsListAccounts.tsx +++ b/packages/wallets/src/features/cfd/components/CFDPlatformsListAccounts/CFDPlatformsListAccounts.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { useCtraderAccountsList, useDxtradeAccountsList, useSortedMT5Accounts } from '@deriv/api-v2'; +import { useCtraderAccountsList, useDxtradeAccountsList, useLandingCompany, useSortedMT5Accounts } from '@deriv/api-v2'; import { TradingAppCardLoader } from '../../../../components/SkeletonLoader'; import { AddedCTraderAccountsList, @@ -27,13 +27,20 @@ const CFDPlatformsListAccounts: React.FC = () => { isFetchedAfterMount: isDxtradeFetchedAfterMount, isLoading: isDxtradeLoading, } = useDxtradeAccountsList(); + const { data: landingCompany, isLoading: isLandingCompanyLoading } = useLandingCompany(); - const isLoading = isMT5Loading || isCTraderLoading || isDxtradeLoading; + const isLoading = isMT5Loading || isCTraderLoading || isDxtradeLoading || isLandingCompanyLoading; const isFetchedAfterMount = isMT5FetchedAfterMount || isCtraderFetchedAfterMount || isDxtradeFetchedAfterMount; const hasCTraderAccount = !!ctraderAccountsList?.length; const hasDxtradeAccount = !!dxtradeAccountsList?.length; + const financialRestrictedCountry = + landingCompany?.financial_company?.shortcode === 'svg' && !landingCompany?.gaming_company; + const cfdRestrictedCountry = + landingCompany?.gaming_company?.shortcode === 'svg' && !landingCompany.financial_company; + const isRestricted = financialRestrictedCountry || cfdRestrictedCountry; + if (isLoading || !isFetchedAfterMount) { return (
    @@ -54,8 +61,12 @@ const CFDPlatformsListAccounts: React.FC = () => { ); })} - {hasCTraderAccount ? : } - {hasDxtradeAccount ? : } + {!isRestricted && ( + <> + {hasCTraderAccount ? : } + {hasDxtradeAccount ? : } + + )}
    ); }; diff --git a/packages/wallets/src/features/cfd/flows/CTrader/AvailableCTraderAccountsList/AvailableCTraderAccountsList.tsx b/packages/wallets/src/features/cfd/flows/CTrader/AvailableCTraderAccountsList/AvailableCTraderAccountsList.tsx index 315710cd5800..dcc4b261b65f 100644 --- a/packages/wallets/src/features/cfd/flows/CTrader/AvailableCTraderAccountsList/AvailableCTraderAccountsList.tsx +++ b/packages/wallets/src/features/cfd/flows/CTrader/AvailableCTraderAccountsList/AvailableCTraderAccountsList.tsx @@ -43,7 +43,7 @@ const AvailableCTraderAccountsList: React.FC = () => { show( ); diff --git a/packages/wallets/src/features/cfd/modals/CTraderSuccessModal/CTraderSuccessModal.tsx b/packages/wallets/src/features/cfd/modals/CTraderSuccessModal/CTraderSuccessModal.tsx index 94db2a868c54..8e217a2b4334 100644 --- a/packages/wallets/src/features/cfd/modals/CTraderSuccessModal/CTraderSuccessModal.tsx +++ b/packages/wallets/src/features/cfd/modals/CTraderSuccessModal/CTraderSuccessModal.tsx @@ -11,7 +11,7 @@ import { CTraderSuccessModalButtons } from './components'; type TCTraderSuccessModal = { createdAccount?: THooks.CreateOtherCFDAccount; - isDemo: boolean; + isDemo?: boolean; walletCurrencyType: THooks.WalletAccountsList['wallet_currency_type']; }; @@ -38,6 +38,18 @@ const CTraderSuccessModal = ({ createdAccount, isDemo, walletCurrencyType }: TCT } ); + const title = isDemo ? ( + + ) : ( + + ); + if (isDesktop) { return ( @@ -49,19 +61,7 @@ const CTraderSuccessModal = ({ createdAccount, isDemo, walletCurrencyType }: TCT displayBalance={cTraderAccount.display_balance} marketType='all' platform={PlatformDetails.ctrader.platform} - title={ - isDemo ? ( - - ) : ( - - ) - } + title={title} /> ); @@ -81,15 +81,7 @@ const CTraderSuccessModal = ({ createdAccount, isDemo, walletCurrencyType }: TCT displayBalance={cTraderAccount.display_balance} marketType='all' platform={PlatformDetails.ctrader.platform} - title={ - - } + title={title} /> ); diff --git a/packages/wallets/src/features/cfd/modals/CTraderSuccessModal/components/CTraderSuccessModalButtons.tsx b/packages/wallets/src/features/cfd/modals/CTraderSuccessModal/components/CTraderSuccessModalButtons.tsx index 0c3f33399f62..2cdba448220c 100644 --- a/packages/wallets/src/features/cfd/modals/CTraderSuccessModal/components/CTraderSuccessModalButtons.tsx +++ b/packages/wallets/src/features/cfd/modals/CTraderSuccessModal/components/CTraderSuccessModalButtons.tsx @@ -8,7 +8,7 @@ import { THooks } from '../../../../../types'; type TCTraderSuccessModalButtons = { createdAccount?: THooks.CreateOtherCFDAccount; hide: () => void; - isDemo: boolean; + isDemo?: boolean; }; const CTraderSuccessModalButtons = ({ createdAccount, hide, isDemo }: TCTraderSuccessModalButtons) => { diff --git a/packages/wallets/src/features/cfd/modals/CreatePasswordModal/CreatePasswordModal.tsx b/packages/wallets/src/features/cfd/modals/CreatePasswordModal/CreatePasswordModal.tsx index 0e8233e8f498..38a84c09016f 100644 --- a/packages/wallets/src/features/cfd/modals/CreatePasswordModal/CreatePasswordModal.tsx +++ b/packages/wallets/src/features/cfd/modals/CreatePasswordModal/CreatePasswordModal.tsx @@ -2,7 +2,8 @@ import React, { ComponentProps, FC } from 'react'; import { Localize } from '@deriv-com/translations'; import { Button, useDevice } from '@deriv-com/ui'; import { ModalStepWrapper, ModalWrapper } from '../../../../components'; -import { PlatformDetails } from '../../constants'; +import { validPassword, validPasswordMT5 } from '../../../../utils/password-validation'; +import { CFD_PLATFORMS, PlatformDetails } from '../../constants'; import { CreatePassword } from '../../screens'; import '../EnterPasswordModal/EnterPasswordModal.scss'; @@ -14,6 +15,9 @@ const CreatePasswordModal: FC> = ({ platform, }) => { const { isDesktop } = useDevice(); + const isMT5 = platform === CFD_PLATFORMS.MT5; + const disableButton = isMT5 ? !validPasswordMT5(password) : !validPassword(password); + if (isDesktop) { return ( @@ -36,7 +40,7 @@ const CreatePasswordModal: FC> = ({ return (
    -
    - ) : ( - - - - - ); + const renderButton = isDemo ? ( +
    + +
    + ) : ( + + + + + ); + + const title = isDemo ? ( + + ) : ( + + ); if (isDesktop) { return ( @@ -61,15 +69,7 @@ const SuccessModal: FC = ({ displayBalance={displayBalance} marketType={marketType} platform={platform} - title={ - - } + title={title} />
    ); @@ -83,15 +83,7 @@ const SuccessModal: FC = ({ displayBalance={displayBalance} marketType={marketType} platform={platform} - title={ - - } + title={title} /> ); diff --git a/packages/wallets/src/features/cfd/screens/EnterPassword/EnterPassword.tsx b/packages/wallets/src/features/cfd/screens/EnterPassword/EnterPassword.tsx index 53352db9e24f..d856a7b87424 100644 --- a/packages/wallets/src/features/cfd/screens/EnterPassword/EnterPassword.tsx +++ b/packages/wallets/src/features/cfd/screens/EnterPassword/EnterPassword.tsx @@ -4,10 +4,10 @@ import { Localize, useTranslations } from '@deriv-com/translations'; import { Button, Text, useDevice } from '@deriv-com/ui'; import { WalletPasswordFieldLazy } from '../../../../components/Base'; import { THooks, TMarketTypes, TPlatforms } from '../../../../types'; -import { validPassword } from '../../../../utils/password-validation'; import { CFD_PLATFORMS, getMarketTypeDetails, JURISDICTION, PlatformDetails } from '../../constants'; import { TModifiedMT5Accounts } from '../../types'; import { MT5LicenceMessage, MT5PasswordModalTnc } from '../components'; +import { validPassword, validPasswordMT5 } from '../../../../utils/password-validation'; import './EnterPassword.scss'; // Note: this component requires a proper refactor to remove props for keys available under the `account` prop @@ -52,6 +52,8 @@ const EnterPassword: React.FC = ({ const { localize } = useTranslations(); const { data } = useActiveWalletAccount(); + const isMT5 = platform === CFD_PLATFORMS.MT5; + const disableButton = isMT5 ? !validPasswordMT5(password) : !validPassword(password); const accountType = data?.is_virtual ? localize('Demo') : localize('Real'); const title = PlatformDetails[platform].title; const marketTypeTitle = @@ -120,7 +122,7 @@ const EnterPassword: React.FC = ({